ネィティブデバッガを作る

WindowsOSやMozillaなどアプリケーションのデバッグ情報を送信する機能を備えたソフトウェアが増えています。Debugging Functions APIを使って実行中の不正なメモリアクセスなどを監視してデバッグ情報を取得する機能を確認してみましょう。
まずデバッグを行うにはCreateProcess APIを使ってデバッガからデバッグ対象のプロセスをDEBUG_PROCESSオプション付きで起動するか、あるいはデバッグ対象のプロセスからデバッガをDEBUG_ONLY_THIS_PROCESSオプション付きで起動する必要があります。
続いてWaitForDebugEvent APIを使用してデバッグ対象プロセス内でイベントが発生するのを待ちます。ここでは不正なメモリアクセスエラーだけを対象として考えます。dwDebugEventCodeがEXCEPTION_DEBUG_EVENT、ExceptionCodeがEXCEPTION_ACCESS_VIOLATIONの場合にエラーをトラップしていみます。これ以外にDLLのLoad/Unload、プロセスやスレッドの開始/停止、デバッグ標準出力へのメッセージや、プロセス内で発生した各種例外を取得することができます。
デバッグ対象プロセスの状態を取得するために、GetThreadContext APIを使用します。これによりデバッグ対象プロセス内で不正なメモリアクセスが発生した時のCPUレジスタの状態を取得できます。取得されるCONTEXT構造体の内容は使用しているCPUのアーキテクチャによって異なります。デバッグにこの情報を利用するためには、各種CPIアーキテクチャ毎にレジスタがどのように扱われるのか熟知している必要があります。
今回はx86 CPUアーキテクチャを対象としますので、CONTEXT構造体のeipレジスタに注目してください。eipレジスタの値が不正なメモリアクセスが発生した時に実行されていたアプリケーションコードの場所をしめします。あとはMAPファイルなどを確認して問題箇所を特定します
デバッグイベントに対する処理が終わったら、ContinueDebugEvent APIを呼び出します。このAPIの呼び出しによりデバッグイベントの発生により一時停止していしるデバッグ対象プロセスの処理を再開させます。

// デバッグ対象のプロセスを開始する
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInfo;
::memset(&processInfo, 0x00, sizeof(processInfo));
::memset(&startupInfo, 0x00, sizeof(startupInfo));
startupInfo.cb		= sizeof(startupInfo);
if (CreateProcess(NULL, m_cmdLineInfo.m_runAppName.GetBuffer(), NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &startupInfo, &processInfo))
{
// デバッグイベントを待つ
DEBUG_EVENT debugEvent;
while (WaitForDebugEvent(&debugEvent, INFINITE))
{
if (EXCEPTION_DEBUG_EVENT == debugEvent.dwDebugEventCode)
{
if (EXCEPTION_ACCESS_VIOLATION == debugEvent.u.Exception.ExceptionRecord.ExceptionCode)
{
// スレッドハンドルの取得
HANDLE hThread(0);
hThread = ::OpenThread(THREAD_GET_CONTEXT, FALSE, debugEvent->dwThreadId);
if (NULL != hThread)
{
// スレッドの状態を取得
CONTEXT		threadContext;
threadContext.ContextFlags	= CONTEXT_ALL;
if (GetThreadContext(hThread, &threadContext))
{
// ここでデバッグ処理をおこなう
}
}
}
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
}
}