Ask Learn
Preview
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
Sign inThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
The Visual Studio debugger supports a kind of breakpoint called Data Breakpoint, sometimes it is also called watchpoint. Data breakpoint is architecture dependant, as it requires hardware support provided by CPU. For x86, this will be the DR (Debug Register).
The following code demonstrates how to use the x86 debug register by implementing a very simple native debugger.
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <stdio.h>
HMODULE g_hModule; /* linear address of exe */
LPVOID g_pOEP; /* original entry point */
BYTE g_bBreakPoint;
BYTE g_bINT3 = 0xcc; /* debug break instruction */
BOOL g_fDebugRegisterSupported = FALSE; /* hardware DR supported */
int WINAPI ExeEntry()
{
if(IsDebuggerPresent())
{
g_hModule = GetModuleHandle(NULL); /* HMODULE is always 4 bytes aligned */
printf("HMODULE: %p\n", g_hModule /* insert a space here... */,
*(INT32*)(g_hModule) /* read 4 bytes */);
}
else
{
CONTEXT context;
context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
PROCESS_INFORMATION processInformation;
STARTUPINFO startupInfo = {sizeof(STARTUPINFO)};
if(CreateProcess(NULL, GetCommandLine(), NULL, NULL, FALSE, DEBUG_PROCESS,
NULL, NULL, &startupInfo, &processInformation))
{
DWORD dwContinueDebugStatus = DBG_CONTINUE;
while(dwContinueDebugStatus)
{
DEBUG_EVENT debugEvent;
WaitForDebugEvent(&debugEvent, INFINITE);
switch(debugEvent.dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
g_pOEP = (LPVOID)(debugEvent.u.CreateProcessInfo.lpStartAddress);
g_hModule = (HMODULE)(debugEvent.u.CreateProcessInfo.lpBaseOfImage);
CloseHandle(debugEvent.u.CreateProcessInfo.hFile);
printf("CREATE_PROCESS_DEBUG_EVENT @%p OEP=%p\n", g_hModule, g_pOEP);
break;
case EXCEPTION_DEBUG_EVENT:
printf("EXCEPTION_DEBUG_EVENT PID=%d TID=%d @%p\n",
debugEvent.dwProcessId, debugEvent.dwThreadId,
debugEvent.u.Exception.ExceptionRecord.ExceptionAddress);
GetThreadContext(processInformation.hThread, &context);
switch(debugEvent.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_BREAKPOINT:
if(debugEvent.u.Exception.dwFirstChance)
{
if(debugEvent.u.Exception.ExceptionRecord.ExceptionAddress == g_pOEP)
{
LPVOID IP = (LPVOID)(--context.Eip);
WriteProcessMemory(processInformation.hProcess, IP,
&g_bBreakPoint, 1, NULL);
FlushInstructionCache(processInformation.hProcess, IP, 1);
context.Dr0 = (DWORD)(g_hModule);
context.Dr7 = 0x000f0101;
}
else
{
printf("\tbp $exentry\n");
ReadProcessMemory(processInformation.hProcess, g_pOEP,
&g_bBreakPoint, 1, NULL);
WriteProcessMemory(processInformation.hProcess, g_pOEP,
&g_bINT3, 1, NULL);
FlushInstructionCache(processInformation.hProcess, g_pOEP, 1);
}
}
break;
case EXCEPTION_SINGLE_STEP:
printf("EXCEPTION_SINGLE_STEP DR6=%08X\n", context.Dr6);
g_fDebugRegisterSupported = TRUE;
break;
}
context.Dr6 = 0;
SetThreadContext(processInformation.hThread, &context);
break;
case EXIT_PROCESS_DEBUG_EVENT:
dwContinueDebugStatus = 0;
printf("EXIT_PROCESS_DEBUG_EVENT\n");
break;
case LOAD_DLL_DEBUG_EVENT:
CloseHandle(debugEvent.u.LoadDll.hFile);
break;
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId,
dwContinueDebugStatus);
}
CloseHandle(processInformation.hThread);
CloseHandle(processInformation.hProcess);
}
printf("Debug Register Test: %s\n", g_fDebugRegisterSupported ?
"Hardware DR supported" : "Hardware DR not supported");
}
return ERROR_SUCCESS;
}
To compile the source code, you may use Visual Studio or either of the following compilers (x86 32bit):
cl.exe watchpoint.cpp kernel32.lib msvcrt.lib /GS- /link /ENTRY:ExeEntry /NODEFAULTLIB /SUBSYSTEM:CONSOLE
gcc.exe -fno-exceptions -fno-rtti -s -Os -o watchpoint watchpoint.cpp -Wl,--stack,65536
A few things to mention:
Data breakpoints respect the CPU working mode, which means the linear address is used if paging enabled, and physical address used if paging disabled.
Each CPU core has its own set of debug registers. This wouldn't be a problem for user mode as the operating system maintains context switching, but it will be very different if you are implementing kernel mode driver which runs above dispatch level, or custom interrupt vector.
Virtual machine sometimes does not implement DR, this is true for Virtual PC and VMware if you don't have hardware virtualization enabled.
To make the sample easier, our trivial debugger assumes we only have one debugee and the debugee is single-threaded. For the real debugger, PID and TID should be used to get the correct handle (and should have it cached) in order to support multi-threaded debugee, as well as debugging multiple programs in a single debugger instance.
A little modification is required in order to support 64bit:
if(debugEvent.u.Exception.ExceptionRecord.ExceptionAddress == g_pOEP)
{
#if defined(_M_IX86)
LPVOID IP = (LPVOID)(--context.Eip);
#elif defined(_M_X64)
LPVOID IP = (LPVOID)(--context.Rip);
#endif
WriteProcessMemory(processInformation.hProcess, IP,
&g_bBreakPoint, 1, NULL);
FlushInstructionCache(processInformation.hProcess, IP, 1);
#if defined(_M_IX86)
context.Dr0 = (DWORD)(g_hModule);
#elif defined(_M_X64)
context.Dr0 = (DWORD64)(g_hModule);
#endif
context.Dr7 = 0x000f0101;
}
ExeEntry is used as the entry point instead of the one provided by the C Runtime, which is not a good practice, as this might cause subtle CRT initialization problem. The main reason of doing this is that CRT initialization code reads from the module header just as we did, which would also trigger our watchpoint. To verify this:
Change the following code by inserting a blank space:
printf("HMODULE: %p\n", g_hModule /* insert a space here... */,
*(INT32*)(g_hModule) /* read 4 bytes */);
After the change, your code would look like:
printf("HMODULE: %p\n", g_hModule /* insert a space here... * /,
*(INT32*)(g_hModule) /* read 4 bytes */);
Now change ExeEntry to int __cdecl main() , and recompile without the /ENTRY:ExeEntry flag.
Run and see what happened, on my machine, this would look like:
CREATE_PROCESS_DEBUG_EVENT @00250000 OEP=00251978
EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @77DD04F6
bp $exentry EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @00251978
EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @002518CA
EXCEPTION_SINGLE_STEP DR6=FFFF0FF1
EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @0025104F
EXCEPTION_SINGLE_STEP DR6=FFFF0FF1 HMODULE: 00250000
EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @67D278FC
EXCEPTION_SINGLE_STEP DR6=FFFF0FF1
EXIT_PROCESS_DEBUG_EVENT
Debug Register Test: Hardware DR supported
Homework:
For the homework, you would want to either check the Intel x86 specification and do verification by playing around the code.
Ask Learn is an AI assistant that can answer questions, clarify concepts, and define terms using trusted Microsoft documentation.
Please sign in to use Ask Learn.
Sign in