ほっしーの技術ネタ備忘録

技術ネタの備忘録です。基本的に私が忘れないためのものです。他の人の役にも立つといいなぁ。

ブレークポイントの仕掛け方


デバッガで必要な機能もう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 は自動でクリアされないので、ブレークポイントの場合は手動でクリアします。
また、このままだと連続してブレークポイントにかかるため、とりあえず全部停止します。