ハードウェアブレークポイントを使う
コードを実際に書いたことがなかったので書いてみた。デバッグレジスタは本来特権レベルでなければ操作できないが、Windowsの場合はSetThreadContext系APIを使って設定することができる。
ハードウェアブレークポイントはソフトウェアブレーク(int 3)ほど使われていないものの、ReadやWrite時にブレークさせることができたり、仮想メモリの書き換えを行わない(チェックサムなどの整合性検査で検知されない*1)で設定できたり、コンテキストに紐づくためスレッド単位の制御が可能であったりと、用途によっては有用だったりする。
以下のプログラムは、Sleepを実行するスレッドを2つ生成し、片方のスレッドのSleepにだけ実行時ブレークポイントを設定する。
// // hwbp.c // #include <windows.h> #include <stdio.h> // for see: IA-32 インテル アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル // 下巻:システム・プログラミング・ガイド - 15.2. デバッグレジスタ typedef union { ULONG_PTR Dr; struct Dr { unsigned int L0 :1; // セットすると現行タスクの関連ブレークポイントをイネーブルにする。 unsigned int G0 :1; // セットするとすべてのタスクの関連ブレークポイントをイネーブルにする。 unsigned int L1 :1; unsigned int G1 :1; unsigned int L2 :1; unsigned int G2 :1; unsigned int L3 :1; unsigned int G3 :1; unsigned int LE :1; // P6ファミリ・プロセッサおよびそれ以降の unsigned int GE :1; // IA-32プロセッサではサポートされていないが、下位互換性のためにセットすべき。 unsigned int Reserved1 :3; unsigned int GD :1; // セットするとデバッグレジスタ保護がイネーブルになる。 unsigned int Reserved2 :2; // デバッグレジスタにアクセスするどの MOV 命令の前でもデバッグ例外が発生する。 unsigned int Type0 :2; unsigned int Length0 :2; unsigned int Type1 :2; unsigned int Length1 :2; unsigned int Type2 :2; unsigned int Length2 :2; unsigned int Type3 :2; unsigned int Length3 :2; #ifdef _AMD64_ unsigned int Reserved3 :32; #endif } Fields; } DebugControlRegister; C_ASSERT(sizeof(DebugControlRegister) == sizeof(void*)); typedef enum { Byte, // b00 Word, // b01 Reserved, // b10 DWord // b11 } HWBLength; typedef enum { Execute, // b00 Write, // b01 IOReadWrite,// b10 CR4 の DE(デバッグ拡張)フラグをセットしている場合のみ ReadWrite // b11 } HWBType; typedef enum { Dr0, Dr1, Dr2, Dr3 } HWBRegister; BOOL SetHardwareBreakPoint( __in HANDLE Thread, __in ULONG_PTR Address, __in HWBLength Length, __in HWBType Type, __in HWBRegister DebugRegister) { CONTEXT Context; DebugControlRegister Dr7; if (SuspendThread(Thread) == -1) { return FALSE; } Context.ContextFlags = CONTEXT_DEBUG_REGISTERS | CONTEXT_CONTROL; if (!GetThreadContext(Thread, &Context)) { goto return_FALSE; } // invalid parameter check if (Length == Reserved) { goto return_FALSE; } // alignment check if ((Length == Word && Address % 2) || (Length == DWord && Address % 4)) { goto return_FALSE; } switch (DebugRegister) { case Dr0: Dr7.Dr = Context.Dr7; Dr7.Fields.L0 = 1; Dr7.Fields.LE = 1; Dr7.Fields.Length0 = Length; Dr7.Fields.Type0 = Type; Context.Dr7 = Dr7.Dr; Context.Dr0 = Address; break; case Dr1: Dr7.Dr = Context.Dr7; Dr7.Fields.L1 = 1; Dr7.Fields.LE = 1; Dr7.Fields.Length1 = Length; Dr7.Fields.Type1 = Type; Context.Dr7 = Dr7.Dr; Context.Dr1 = Address; break; case Dr2: Dr7.Dr = Context.Dr7; Dr7.Fields.L2 = 1; Dr7.Fields.LE = 1; Dr7.Fields.Length2 = Length; Dr7.Fields.Type2 = Type; Context.Dr7 = Dr7.Dr; Context.Dr2 = Address; break; case Dr3: Dr7.Dr = Context.Dr7; Dr7.Fields.L3 = 1; Dr7.Fields.LE = 1; Dr7.Fields.Length3 = Length; Dr7.Fields.Type3 = Type; Context.Dr7 = Dr7.Dr; Context.Dr3 = Address; break; default: goto return_FALSE; } if (!SetThreadContext(Thread, &Context)) { goto return_FALSE; } return (ResumeThread(Thread) != -1); return_FALSE: ResumeThread(Thread); return FALSE; } DWORD WINAPI StartRoutine(LPVOID lpThreadParameter) { for (;;) { LPEXCEPTION_POINTERS Exception; __try { printf("TID:0x%08X Enter Sleep\n", GetCurrentThreadId()); Sleep(INFINITE); } __except (Exception = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) { #ifdef _X86_ printf("Exception!! %p\n", Exception->ContextRecord->Eip); #else printf("Exception!! %p\n", Exception->ContextRecord->Rip); #endif } SleepEx(2000, FALSE); } return 0; } int main(int argc, char* argv[]) { HANDLE Thread; ULONG_PTR Address; Thread = CreateThread(NULL, 0, StartRoutine, NULL, CREATE_SUSPENDED, NULL); Address = (ULONG_PTR)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "Sleep"); printf("BreakPoint: %p\n", Address); if (!SetHardwareBreakPoint(Thread, Address, Byte, Execute, Dr0)) { return 1; } ResumeThread(Thread); CloseHandle(Thread); CloseHandle(CreateThread(NULL, 0, StartRoutine, NULL, 0, NULL)); Sleep(INFINITE); return 0; }
BreakPoint: 000000007784C120 TID:0x00000AB0 Enter Sleep TID:0x000012A4 Enter Sleep Exception!! 000000007784C120 TID:0x00000AB0 Enter Sleep Exception!! 000000007784C120 TID:0x00000AB0 Enter Sleep Exception!! 000000007784C120 TID:0x00000AB0 Enter Sleep Exception!! 000000007784C120 TID:0x00000AB0 Enter Sleep Exception!! 000000007784C120 ...
Visual Studioではデータ ブレークポイントとして近い機能が、WinDbgではそのものがba (Break on Access)として提供されている。