レガシー Windows API(概要)について

ここで紹介しているAPIは、Windows95、Windows NT3.5時代のAPI群であり、最新のAPIは含まれていないことに注しして欲しい。なお、当時の解説書は現在よりもより詳細に解説しているため、参考になるはずである。

  • レイアウトを変換する際、崩れているページがあります。
  • 概要およびSampleコードはありますが、APIの解説はありません。

ご要望があれば旧版のWin32 API(HELP形式)をお譲りします。問い合わせフォームより申し込みください。

Windows API デバッグの概要

「デバッガ」とは、 プログラムの開発時にエラーを発見して修正するためのアプリケーションです。Microsoft(R) Windowsでは、 基礎的なイベント駆動型デバッガを作成するための関数をいくつかサポートしています。「イベント駆動」とは、 デバッグ対象のプロセスであるエラーが発生するたびにデバッガが通知を受け取ることを意味します。デバッガは、 通知を受け取ると、 イベントに応じた動作を実行します。

ここでは、 まず、 一部の標準Windows関数 (特にプロセス、 スレッド、 例外処理などのための関数) のデバッグ用機能について説明します。

次に、 Windowsのデバッグ用関数について説明します。デバッグ用関数によって、 デバッグ イベントの待機、 ブレークポイント例外の発生、 デバッガへの制御の移行などを行うことができます。

次に示すトピックでは、 Windowsのデバッグ サポートについて説明しています。

プロセス、 スレッド、 例外などのための関数によるサポート

デバッグ用関数

デバッグ用サポートの使用

デバッグ関数

プロセス、 スレッド、 例外などのための関数によるサポート

デバッグに不可欠な関数のいくつかは、 実際にはプロセスやスレッド、 例外処理などのアーキテクチャの一部です。ここでは、 デバッグの対象となるプロセスの起動や、 スレッドのコンテキストや実行の検査と操作について説明します。また、 デバッガが例外を処理する方法についても説明します。

プロセス関数

デバッガは、 CreateProcess関数によってプロセスを起動してデバッグできます。CreateProcessfdwCreateパラメータには、 デバッグ操作の種類をしています。このパラメータにDEBUG_PROCESSフラグを指定すると、 デバッガは、 新しいプロセスをデバッグします。また、 DEBUG_PROCESSフラグを設定せずに作成されている子プロセスもすべてデバッグします。

fdwCreateDEBUG_PROCESSフラグとDEBUG_ONLY_THIS_PROCESSフラグを指定すると、 デバッガは新しいプロセスをデバッグしますが、 その子プロセスはデバッグしません。

デバッガは、 DEBUG_PROCESSフラグをセットしてプロセスを作成することによって、 ほかのデバッガをデバッグできます。新しいプロセス (デバッグされるデバッガ) は、 DEBUG_PROCESSフラグをセットしてプロセスを作成しなければなりません。

デバッガは、 OpenProcess関数によって、 既存のプロセスの識別子を取得できます (DebugActiveProcess関数は、 この識別子を使って、 デバッガをプロセスに接続します)。通常、 デバッガは、 PROCESS_VM_READフラグとPROCESS_VM_WRITEフラグをセットしてプロセスをオープンします。これらのフラグによって、 デバッガは、 ReadProcessMemory関数やWriteProcessMemory関数を使ってプロセスの仮想メモリを読み書きできます。プロセスについて詳しくは、 プロセスとスレッドの概要を参照してください。

スレッド関数

CreateThread関数は、 プロセスの新しいスレッドを作成します。通常、 デバッガは、 スレッドのレジスタの内容を調べたり変更しなければなりません。そのような作業を行うには、 スレッドへの適切なアクセス (THREAD_GET_CONTEXTTHREAD_SET_CONTEXT) を指定してDuplicateHandle関数を呼び出すことによって、 スレッドのハンドルを取得しなければなりません。

スレッドへの適切なアクセスを持つプロセスは、 GetThreadContext関数を使ってそのスレッドのレジスタを調べたり、 SetThreadContext関数を使ってスレッドのレジスタの内容を設定できます。

また、 プロセスは、 スレッドに対するTHREAD_SUSPEND_RESUMEアクセスを取得することもできます。デバッガは、 このアクセスによって、 SuspendThread関数やResumeThread関数を使ってスレッドの実行を制御できます。スレッドについて詳しくは、 プロセスとスレッドの概要を参照してください。

例外処理

デバッグされているプロセスで例外が発生すると、 カーネルは、 デバッガにその例外を渡して通知します。これは、 「初回通知」と呼ばれます。その後、 カーネルは、 デバッグされているプロセスのすべてのスレッドを中断します。

デバッガが例外を処理しなければ、 カーネルは、 適切な例外ハンドラを見つけようとします。適切な例外ハンドラがなければ、 カーネルは、 例外が発生したことをもう一度デバッガに通知します。これは、 「最終通知」と呼ばれます。最終通知の後でデバッガが例外を処理しなければ、 カーネルは、 デバッグされているプロセスを終了させます。

例外処理について詳しくは、 構造化例外処理の概要を参照してください。

デバッグ用関数

Windowsデバッグ用関数のほとんどはデバッガの作成に使われますが、 デバッグされるプロセスで使われる関数もいくつかあります。

プロセスを起動してデバッグするには、 CreateProcessを使います。

すでに実行中のプロセスをデバッグするには、 OpenProcessで取得したプロセス識別子を使ってDebugActiveProcessを呼び出します。DebugActiveProcessは、 アクティブなプロセスにデバッガを接続します。この場合、 デバッグできるのはアクティブなプロセスだけで、 そのプロセスの子プロセスはデバッグできません。DebugActiveProcessを使うには、 デバッガは実行中のプロセスに対する適切なアクセスを持っていなければなりません。アクセス権については、 セキュリティの概要を参照してください。

デバッガがデバッグしようとするプロセスを作成したり、 デバッグしようとするプロセスに自分自身を接続すると、 カーネルは、 そのプロセスで発生したデバッグ イベントをすべてデバッガに通知します。また、 指定されていれば、 そのプロセスの子プロセスのデバッグ イベントも通知します。デバッグ イベントについて詳しくは、 デバッグ イベントを参照してください。

デバッガは、 自分自身のメイン ループの最初でWaitForDebugEvent関数を使います。この関数は、 デバッグ イベントが発生するまでデバッガをブロックします。デバッグ イベントが発生すると、 カーネルは、 デバッグされているプロセスのスレッドをすべて中断し、 デバッガにそのイベントを通知します。デバッガは、 次に示す関数を使って、 ユーザーと対話したり、 デバッグされているプロセスの状態を操作できます。

FlushInstructionCache GetThreadContext GetThreadSelectorEntry ReadProcessMemory SetThreadContext WriteProcessMemory

GetThreadSelectorEntryは、 指定されたセレクタとスレッドのディスクリプタ テーブル エントリを返します。デバッガは、 ディスクリプタ テーブル エントリを使って、 セグメント相対アドレスをリニア仮想アドレスに変換します。ReadProcessMemory関数とWriteProcessMemory関数には、 リニア仮想アドレスが必要です。

デバッガは、 デバッグされているプロセスのメモリの読み取りや命令を含むメモリの命令キャッシュへの書き込みを頻繁に行います。デバッガは、 命令を書き込んだら、 FlushInstructionCacheを呼び出して、 キャッシュされている命令をフラッシュしてください。

デバッガは、 メイン ループの最後でContinueDebugEvent関数を使います。この関数は、 デバッグされているプロセスの実行が再開されるようにします。

デバッガとの通信

OutputDebugString関数は、 OUTPUT_DEBUG_STRING_EVENTデバッグ イベントを生成して、 デバッグされているプロセスからデバッガに文字列を送ります。

DebugBreak関数は、 現在のプロセスでブレークポイント例外を発生させます。「ブレークポイント」とは、 プログラムのコード、 変数、 レジスタの値などを調べたり、 必要に応じて変更を行ったり実行の再開や終了を行えるようにするため、 プログラム内の実行を中断する位置です。

FatalExit関数は、 現在のプロセスを終了してデバッガに実行制御を渡しますが、 DebugBreakとは異なり、 例外を生成しません。プロセスのメモリの解放やファイルのクローズが行われなくなる可能性があるため、 この関数は最後の手段として使ってください。

デバッグ イベント

「デバッグ イベント」とは、 デバッグされているプロセスで発生するイベントで、 発生するとカーネルがデバッガに通知します。デバッグ イベントには、 プロセスの作成、 スレッドの作成、 ダイナミック リンク ライブラリ (DLL) のロードやアンロード、 出力文字列の送信、 例外の生成などがあります。

デバッガがデバッグ イベントを待機しているときにデバッグ イベントが発生すると、 カーネルは、 WaitForDebugEventで指定されたDEBUG_EVENT構造体にイベントを記述する情報を設定します。

カーネルは、 デバッグ イベントをデバッガに通知するとき、 対象とするプロセスのスレッドをすべて中断させます。スレッドの実行は、 デバッガがContinueDebugEventを使ってデバッグ イベントを続行するまで再開されません。プロセスのデバッグ中には、 次に示すデバッグ イベントが発生する可能性があります。

デバッグ イベント 説明

CREATE_PROCESS_DEBUG_EVENT

デバッグされているプロセスで新しいプロセスが作成されたときや、 すでにアクティブなプロセスのデバッグをデバッガが開始したときに生成されます。カーネルは、 このデバッグ イベントを生成してから、 プロセスのユーザー モードでの実行を開始し、 新しいプロセスのそのほかのデバッグ イベントを生成します。

DEBUG_EVENT構造体には、 CREATE_PROCESS_DEBUG_INFO構造体が含まれています。この構造体には、 新しいプロセスのハンドルやプロセスのイメージ ファイルのハンドル、 プロセスの初期スレッドのハンドルなどの、 新しいプロセスを記述する情報が設定されています。

プロセスのハンドルには、 PROCESS_VM_READアクセスとPROCESS_VM_WRITEアクセスが設定されています。スレッドに対してこれらの種類のアクセスを持つデバッガは、 ReadProcessMemory関数とWriteProcessMemory関数を使って、 プロセスのメモリを読み書きできます。

プロセスのイメージ ファイルのハンドルにはGENERIC_READアクセスが設定されており、 イメージ ファイルは読み取り共有でオープンされています。

プロセスの初期スレッドには、 スレッドに対するTHREAD_GET_CONTEXTアクセス、 THREAD_SET_CONTEXTアクセス、 THREAD_SUSPEND_RESUMEアクセスが設定されています。スレッドに対するこれらの種類のアクセスを持つデバッガは、 GetThreadContext関数とSetThreadContext関数を使ってスレッドのレジスタを読み書きでき、 SuspendThread関数とResumeThread関数を使ってスレッドの中断や再開ができます。

CREATE_THREAD_DEBUG_EVENT

デバッグされているプロセスで新しいスレッドが作成されるか、 すでにアクティブなプロセスのデバッグをデバッガが開始すると生成されます。このデバッグ イベントは、 新しいスレッドのユーザー モードでの実行が開始される前に送られます。

DEBUG_EVENT構造体には、 CREATE_THREAD_DEBUG_INFO構造体が含まれています。この構造体には、 新しいスレッドのハンドルとスレッドの開始アドレスが設定されています。スレッドのハンドルには、 スレッドに対するTHREAD_GET_CONTEXTアクセス、 THREAD_SET_CONTEXTアクセス、 THREAD_SUSPEND_RESUMEアクセスが設定されています。スレッドに対するこれらの種類のアクセスを持つデバッガは、 GetThreadContext関数とSetThreadContext関数を使ってスレッドのレジスタを読み書きしたり、 SuspendThread関数とResumeThread関数を使ってスレッドの中断や再開ができます。

EXCEPTION_DEBUG_EVENT

デバッグされているプロセスで例外が発生すると生成されます。発生する可能性のある例外には、 アクセス不可能なメモリへのアクセス、 ブレークポイント命令の実行、 0による除算などの構造化例外処理で示されている例外があります。

DEBUG_EVENT構造体には、 EXCEPTION_DEBUG_INFO構造体が含まれています。この構造体は、 デバッグ イベントの原因となった例外を記述します。

コンソール プロセスのデバッグ中には、 標準の例外条件に加えて、 追加の例外コードを発生させることができます。カーネルは、 Ctrl+Cシグナルを処理するコンソール プロセスがデバッグ中であるとき、 そのプロセスにCtrl+Cが入力されると、 DBG_CONTROL_C例外コードを生成します。この例外コードはアプリケーションによって処理されるべきものではありませんので、 アプリケーションでは例外ハンドルを使ってこの例外コードを処理しないでください。この例外コードはデバッガのために呼び起こされるものであり、 デバッガがコンソール プロセスにアタッチされるときだけ呼び起こされます。

プロセスがデバッグ中でない場合、 またはデバッガがDBG_CONTROL_C例外を未処理のまま (gnコマンドを介して) 渡す場合は、 SetConsoleCtrlHandlerで説明されているように、 アプリケーションのハンドラ関数のリストが検索されます。

デバッガが (ghコマンドを介して) DBG_CONTROL_C例外を処理する場合には、 アプリケーションはCtrl+Cを認知しません。ただし、 アラート可能待機が終了する場合は例外です。この場合には、 次のようなコードで認知されるのがふつうです。

while ((inputChar = getchar()) != EOF) ...

while (gets(inputString)) ...

このようにすると、 このコード内で読み取り待機の終了を中止するためにデバッガを使うことができなくなります。

EXIT_PROCESS_DEBUG_EVENT

デバッグされているプロセスの最後のスレッドが終了すると生成されます。カーネルは、 プロセスのダイナミック リンク ライブラリ (DLL) をアンロードしてプロセスの終了コードを更新した後でこのデバッグ イベントを生成します。

DEBUG_EVENT構造体には、 終了コードを示すEXIT_PROCESS_DEBUG_INFO構造体が含まれています。

デバッガは、 このデバッグ イベントを受け取ったら、 そのプロセスに関する内部構造体をすべて解放します。カーネルは、 終了するプロセスとそのプロセスのすべてのスレッドのデバッガ側のハンドルをクローズします。

EXIT_THREAD_DEBUG_EVENT

デバッグされているプロセスに属するスレッドが終了すると生成されます。カーネルは、 スレッドの終了コードを更新するとすぐにこのデバッグ イベントを生成します。

DEBUG_EVENT構造体には、 終了コードを示すEXIT_THREAD_DEBUG_INFO構造体が含まれています。

デバッガは、 このデバッグ イベントを受け取ったら、 そのスレッドに関する内部構造体をすべて解放します。カーネルは、 終了するスレッドのデバッガ側のハンドルをクローズします。

終了するスレッドがプロセスの最後のプロセスであるときは、 このデバッグ イベントは発生しません。この場合、 代わりにEXIT_PROCESS_DEBUG_EVENTデバッグ イベントが発生します。

LOAD_DLL_DEBUG_EVENT

デバッグされているプロセスがDLLをロードすると生成されます。システム ローダーがDLLへのリンクを解決したり、 デバッグされているプロセスがLoadLibrary関数を使用すると、 このデバッグ イベントが発生します。このデバッグ イベントは、 カーネルがDLLを最初にプロセスの仮想アドレス空間に接続したときだけ発生します。

DEBUG_EVENT構造体には、 LOAD_DLL_DEBUG_INFO構造体が含まれています。この構造体には、 新しくロードされるDLLのハンドルやDLLのベース アドレスなどのDLLを記述する情報が設定されています。

通常、 デバッガは、 このデバッグ イベントを受け取ると、 DLLに関連付けられているシンボル テーブルをロードします。

OUTPUT_DEBUG_STRING_EVENT

デバッグされているプロセスがOutputDebugString関数を使用すると生成されます。

DEBUG_EVENT構造体には、 OUTPUT_DEBUG_STRING_INFO構造体が含まれています。この構造体は、 デバッグ用文字列のアドレス、 長さ、 書式を示します。

UNLOAD_DLL_DEBUG_EVENT

デバッグされているプロセスがFreeLibrary関数を使ってDLLをアンロードすると生成されます。このデバッグ イベントは、 DLLがプロセスのアドレス空間から最終的にアンロードされるとき (DLLの使用カウントが0になったとき) だけ発生します。

DEBUG_EVENT構造体には、 UNLOAD_DLL_DEBUG_INFO構造体が含まれています。この構造体は、 DLLをアンロードするプロセスのアドレス空間内でのDLLのベース アドレスを示します。

通常、 デバッガは、 このデバッグ イベントを受け取ると、 DLLに関連付けられているシンボル テーブルをアンロードします。

プロセスが終了すると、 カーネルはプロセスのDLLを自動的にアンロードしますが、 UNLOAD_DLL_DEBUG_EVENTデバッグ イベントは生成しません。

デバッグ用サポートの使用

次に示す例では、 WaitForDebugEvent関数とContinueDebugEvent関数を使って、 簡単なデバッガを作成しています。

DEBUG_EVENT DebugEv;/* debugging event info. */
DWORD dwContinueStatus = DBG_CONTINUE; /* exception continuation */

for(;;) {

/*
* Wait for a debugging event to occur. The second parameter
* indicates that the function does not return until
* a debugging event occurs.
*/

WaitForDebugEvent(&DebugEv, INFINITE);

/* Process the debugging event code. */

switch (DebugEv.dwDebugEventCode) {

case EXCEPTION_DEBUG_EVENT:

/*
* Process the exception code. When handling
* exceptions, remember to set the continuation
* status parameter (dwContinueStatus). This value
* is used by the ContinueDebugEvent function.
*/

switch (DebugEv.u.Exception.ExceptionRecord.ExceptionCode) {

case EXCEPTION_ACCESS_VIOLATION:

/*
* First chance: Pass this on to the kernel.
* Last chance: Display an appropriate error.
*/

case EXCEPTION_BREAKPOINT:

/*
* First chance: Display the current
* instruction and register values.
*/

case EXCEPTION_DATATYPE_MISALIGNMENT:

/*
* First chance: Pass this on to the kernel.
* Last chance: Display an appropriate error.
*/

case EXCEPTION_SINGLE_STEP:

/*
* First chance: Update the display of the
* current instruction and register values.
*/

case DBG_CONTROL_C:

/*
* First chance: Pass this on to the kernel.
* Last chance: Display an appropriate error.
*/

/* Handle other exceptions. */


}

case CREATE_THREAD_DEBUG_EVENT:

/*
* As needed, examine or change the thread's registers
* with the GetThreadContext and SetThreadContext functions;
* suspend and resume thread execution with the
* SuspendThread and ResumeThread functions.
*/

case CREATE_PROCESS_DEBUG_EVENT:

/*
* As needed, examine or change the registers of the process's
* initial thread with the GetThreadContext and SetThreadContext
* functions; read from and write to the process's virtual memory
* with the ReadProcessMemory and WriteProcessMemory functions;
* and suspend and resume thread execution with the SuspendThread
* and ResumeThread functions.
*/


case EXIT_THREAD_DEBUG_EVENT:

/* Display the thread's exit code. */


case EXIT_PROCESS_DEBUG_EVENT:

/* Display the process's exit code. */


case LOAD_DLL_DEBUG_EVENT:

/*
* Read the debugging information included
* in the newly loaded DLL.
*/

case UNLOAD_DLL_DEBUG_EVENT:

/*
* Display a message that the DLL has
* been unloaded.
*/

case OUTPUT_DEBUG_STRING_EVENT:

/* Display the output debugging string. */


}

/* Resume executing the thread that reported the debugging event. */

ContinueDebugEvent(DebugEv.dwProcessId,
DebugEv.dwThreadId, dwContinueStatus);

}

デバッグ関数

デバッグのサポートに関する関数を次に示します。

ContinueDebugEvent

DebugActiveProcess

DebugBreak

FatalAppExit

FatalExit

FlushInstructionCache

GetThreadContext

GetThreadSelectorEntry

OutputDebugString

ReadProcessMemory

SetDebugErrorLevel

SetThreadContext

WaitForDebugEvent

WriteProcessMemory

▲ページトップに戻る

【本を読んでもよくらからない】 … 個別指導でわかりやすくお教えします
inserted by FC2 system