« もう既に今年も5月か | トップページ | Win32デバッグ(2)・・・クラス設計 »

2009年5月 3日 (日)

Win32デバッグ

プログラミングを行ってる人はソフトウェアを作成する過程で特定のソースコード位置などで実行を停止して、その時の変数の値を確認したり、また、1行ずつコードを実行して、処理の実際の流れを確認したりと、デバッガと呼ばれるソフトウェアもしくは機能を利用する事になると思うが、今回はそのようなデバッガやパフォーマンスプロファイラなどを作成するためにWindowsで提供されているデバッグAPIについて。

Windowsで提供されているデバッグAPIはイベント駆動型のモデルを採用していて、デバッグ対象のプロセス内でデバッグイベントと呼ばれるイベントが発生するのを待機し、発生したら発生したイベントに応じて処理を行うという、Windows上でGUIアプリケーションを作成する時に見かけられるWindowsメッセージを処理するためのメッセージループに似たループを行うことになる。発生するデバッグイベントには次のようなものがある。

デバッグイベント
種類 概要
CREATE_PROCESS_DEBUG_EVENT 新しいデバッグ対象のプロセスが作成される、または、既存のプロセスにデバッグのためアタッチされた時に発生
EXIT_PROCESS_DEBUG_EVENT デバッグ対象のプロセスが終了される時に発生
CREATE_THREAD_DEBUG_EVENT デバッグ対象のプロセス内で新しいスレッドが作成される時に発生
EXIT_THREAD_DEBUG_EVENT デバッグ対象のプロセス内のスレッドが終了する時に発生
LOAD_DLL_DEBUG_EVENT デバッグ対象のプロセス内でDLLがロードされる時に発生
UNLOAD_DLL_DEBUG_EVENT デバッグ対象のプロセス内でDLLがアンロードされる時に発生
EXCEPTION_DEBUG_EVENT デバッグ対象のプロセス内で例外が生成された時に発生
OUTPUT_DEBUG_STRING_EVENT デバッグ対象のプロセス内でOutputDebugString関数が呼び出された時に発生
RIP_DEBUG_EVENT

上記イベントの厳密な発生条件などはMSDNのここを参照。

新規にプロセスを作成してデバッグを開始するには、CreateProcess関数で第6引数dwCreationFlagsにDEBUG_PROCESSオプションを指定する。DEBUG_PROCESSを指定するとCreateProcess関数で作成されたプロセスによって作成される子孫のプロセスもデバッグ対象になるが、CreateProcess関数で作成されるプロセスだけに限定する場合は、DEBUG_PROCESSオプションの代わりにDEBUG_ONLY_THIS_PROCESSオプションを指定する(紛らわしいがDEBUG_PROCESSまたはDEBUG_ONLY_THIS_PROCESSのどちららかを指定する。論理和とって両方指定するのではない.ので注意)。

また、既存の実行中のプロセスのデバッグを開始するには、DebugActiveProcess関数で引数に既存の実行中のプロセスのプロセス識別子を指定し、プロセスにアタッチする。

CreateProcessもしくはDebugActiveProcessでデバッグを開始した後は、先ほど述べたループに突入するのであるが、Delphiで書くと全体の流れは次のような感じなる。

WaitForDebugEventはデバッグイベントが発生するのを待機する待機関数で、第2引数にタイムアウトを指定する。第2引数にINFINITEを指定するとこの関数を呼び出した側へWaitForDebugEvent関数から制御が返らない。デバッグベントが発生すると第1引数で指定したDEBUG_EVENT構造体に発生したイベントの種類、イベントが発生させたプロセスとスレッドの識別子、イベントの種類に応じた追加の情報が返される。

デバッグ対象のプロセス内でデバッグイベントが発生するとデバッグ対象のプロセス内のすべてのスレッドの実行が停止されるが、ContinueDebugEventで実行を再開する。

ContinueDebugEventの第1、2引数にはデバッグイベントが発生して、デバッグ対象のプロセスが停止した原因となったプロセス、スレッドの識別子を指定するが、これらの値はDEBUG_EVENT構造体のdwProcessId、dwThreadIdにWaitForDebugEvent関数から返されているのでこれらの値をそのまま指定する(もちろん、デバッグ対象がシングルスレッドアプリケーションで、CreateProcess関数を使ってデバッグを開始するのならCreateProcess関数で返されるPROCESS_INFORMATION構造体の値を指定してもいいのだが、DEBUG_EVENT構造体の値を使った方が汎用的である)。

ContinueDebugEventの第3引数には以下の続行オプションのいずれかを指定するのであるが、これは、ややこしいbearing

  • DBG_CONTINUE
  • DBG_EXCEPTION_NOT_HANDLED

完全に説明すると、Windowsの構造化例外処理(Structured Exception Handling)の話から書かないといけないので、EXCEPTION_DEBUG_EVENTイベント発生時以外は、MSDNのドキュメントに書いてあるように上記2つのオプションに違いはないので、上記サンプルではDBG_CONTINUEオプションを、EXCEPTION_DEBUG_EVENTイベント発生時は、発生した例外の種類がシングルステップ例外(EXCEPTION_SINGLE_STEP)、ブレークポイント例外(EXCEPTION_BREAKPOINT)以外の時は、通常の例外処理を行わせるためにDBG_EXCEPTION_NOT_HANDLEDを指定、シングルステップ例外、ブレークポイント例外の場合は、DBG_CONTINUEを指定した。

ということで。ははは。

とMSDNのドキュメントを反復してるだけでbearing、MSDNのドキュメントを読んだ方が正確だし手っ取り早いのであるが、ここでは、実際にデバッグAPIを使った時の注意点をいくつか。

まず、CREATE_PROCESS_DEBUG_EVENT、LOAD_DLL_DEBUG_EVENTイベント発生時に返されるファイルハンドル(hFileメンバ)について。これらイベントの発生時、DEBUG_EVENT構造体を通して、ロードされたプロセスの実行イメージのファイルハンドル(CREAET_PROCESS_DEBUG_EVENTの場合)、もしくは、ロードされたDLLのファイルハンドル(LOAD_DLL_DEBUG_EVENT)が返されるが、MSDNのドキュメントにもあるように、使い終わったらこのハンドルは明示的にCloseHandle関数でクローズしないとハンドルがオープンされたままになるので注意。上記のプログラム例では、88行目あたり。

また、

特に、EXIT_PROCESS_DEBUG_EVENTイベント発生時に、発生直後すぐにブレイクしてデバッグループを抜ける例をよく見かけるが、これだと、CREATE_PROCESS_DEBUG_EVENTイベントなどで返されるプロセスハンドルやスレッドハンドルがオープンされたままになるので、通常は1回、ContinueDebugEvent関数を呼んだ後にループからブレイクする方が無難である(ContinueDebugEvent関数で自動的にハンドルがクローズされるので)。上記のプログラム例では、66行目あたり。

最後は、WaitForDebugEvent関数はDEBUG_PROCESSまたはDEBUG_ONLY_THIS_PROCESSオプションを指定したCreateProcess関数またはDebugActiveProcess関数を呼んでデバッグを開始したスレッドでしか呼ぶことができないとの事。

とここまでくれば、後はデバッグAPIの他の関数である

  • ReadProcessMemory
  • WriteProcessMemory

を使えば、プロセスハンドルからそのプロセスのアドレス空間を読み書きする事ができるし、

  • GetThreadContext
  • SetThreadContext

を使えば、スレッドのコンテキスト(x86プラットフォームのWindowsの場合は、EAXやEBXなど各レジスタなど)を読み書きできる。これらのAPIは何もDEBUG_PROCESSまたはDEBUG_ONLY_THIS_PROCESSオプションを指定したCreateProcess関数によって作成されたプロセスやDebugActiveProcessでアタッチしたプロセスでなくてもよいのだが、使用する時は基本的に対象のプロセス・スレッドをSuspendThread関数などで停止していないとおかしくなると思う(WaitForDebugEvent関数でデバッグイベントを受信した時はデバッグ対象プロセス内のすべてのスレッドが停止しているのでSuspendThread関数で停止する必要はないが・・・)。

以上長々と書いたが次回以降は具体的に使ってみようと。

というより、TrueType/OpenTypeフォントの時の成果物であるT2FAnalyzerと同じようにブログ書きながら最終的に実用的?なソフトウェアが出来上がればいいのであるが・・・ははは。

« もう既に今年も5月か | トップページ | Win32デバッグ(2)・・・クラス設計 »

Windows」カテゴリの記事

デバッグ」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/1497665/39651727

この記事へのトラックバック一覧です: Win32デバッグ:

« もう既に今年も5月か | トップページ | Win32デバッグ(2)・・・クラス設計 »

自作ソフトウェア

無料ブログはココログ

メモ