デバッガで必要な機能もう1つ。ブレークポイントですね。
これは x86 CPU のデバッグレジスタを利用して実装します。
まず仕掛け。
void SetBreakPoint( HANDLE hThread, DWORD dwAddress ) { CONTEXT ctx = { CONTEXT_DEBUG_REGISTERS }; GetThreadContext( hThread, &ctx ); ctx.Dr0 = dwAddress; ctx.Dr7 |= 0x00000001; SetThreadContext( hThread, &ctx ); }
Dr0, Dr1, Dr2, Dr3 と、4つのブレークポイントを仕掛けることが出来ます。
Dr7 はコントロール用のレジスタで、下位16bitで有効・無効を切り替え、上位16bitで用法を決めます。
まず下位16bit。Dr0〜Dr3 がそれぞれ、0x0001, 0x0004, 0x0010, 0x0040 を立てると有効になります。
上位16bitはとりあえず 0 にしておけば実行時ブレークポイントとして働きます。
(ちなみに 0x00030001 とかやると Dr0 のアドレスのメモリアクセスに対してブレークポイントになります)
2bits 2bits 2bits 2bits --+-----+-----+-----+-----+ Dr7 | Dr3 | Dr2 | Dr1 | Dr0 | --+-----+-----+-----+-----+
さて、デバッグレジスタを設定できたら実行再開します。
ブレークポイントに引っかかったら先ほど同様 EXCEPTION_SINGLE_STEP 例外が発生するので
case EXCEPTION_DEBUG_EVENT: if( evDebug.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP ) { print("BreakPoint!!\n"); } break;
こんな感じで拿捕してあげます。
Dr6 レジスタがブレークポイントの状態で、下位4bitでヒットしたブレークポイントが分かります。
Dr0〜Dr3 がそれぞれ 0x1, 0x2, 0x4, 0x8 に対応していて、立っているビットが今回ヒットしたブレークポイントです。
1bits 1bits 1bits 1bits --+-----+-----+-----+-----+ Dr6 | Dr3 | Dr2 | Dr1 | Dr0 | --+-----+-----+-----+-----+ <- どれにヒットしたか ->
シングルステップと同じ例外なので、併用する場合は
case EXCEPTION_DEBUG_EVENT: printf("EXCEPTION_DEBUG_EVENT\n"); if( evDebug.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP ) { CONTEXT ctx = { CONTEXT_CONTROL | CONTEXT_DEBUG_REGISTERS }; GetThreadContext( hThread, &ctx ); if( ctx.Dr6 & 0x00004000 ) { // SingleStep フラグ printf("EIP: 0x%08X\n", ctx.Eip ); SetSingleStepMode( hThread ); } else { printf("BreakPoint. Dr6: 0x%08X\n", ctx.Dr6); ctx.Dr6 = 0x00000000; // DebugStatus はクリアされない ctx.Dr7 = 0x00000000; // とりあえず全部クリア SetThreadContext( hThread, &ctx ); } } break;
このように、Dr6 レジスタのフラグを見て判定します。
Dr6 は自動でクリアされないので、ブレークポイントの場合は手動でクリアします。
また、このままだと連続してブレークポイントにかかるため、とりあえず全部停止します。