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:
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; }
printf("HMODULE: %p\n", g_hModule /* insert a space here... */, *(INT32*)(g_hModule) /* read 4 bytes */);
printf("HMODULE: %p\n", g_hModule /* insert a space here... * /, *(INT32*)(g_hModule) /* read 4 bytes */);
CREATE_PROCESS_DEBUG_EVENT @00250000 OEP=00251978EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @77DD04F6 bp $exentry EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @00251978EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @002518CAEXCEPTION_SINGLE_STEP DR6=FFFF0FF1EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @0025104FEXCEPTION_SINGLE_STEP DR6=FFFF0FF1 HMODULE: 00250000EXCEPTION_DEBUG_EVENT PID=1560 TID=4852 @67D278FCEXCEPTION_SINGLE_STEP DR6=FFFF0FF1EXIT_PROCESS_DEBUG_EVENTDebug 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.
Nice work!
I always wondered - why doesn't VS support *read/write* data breakpoints? x86 processors do expose such states. Is it a legacy matter of conforming to other architectures?