コンソールプログラムならは Control-c で DBG_CONTROL_C 例外を発生させる。
76FF15DE add esp,4
混合ウィンドウでアドレスを見ると問題の箇所がわかる。
test.exe の 0x76ff15de でハンドルされていない例外が発生しました:
0xC0000005: 場所 0x00000000 を読み込み中にアクセス違反が発生しました。
例外の種類には以下のものがある
■ WaitForDebugEvent
SYNTAX
BOOL WaitForDebugEvent(
LPDEBUG_EVENT event, // デバッグイベントの情報が入った構造体へのポインタ
DWORD time // 待つ時間 ( msec )
);
DESC
デバッグ中のプロセスでデバッグイベントが発生するのを待つ。
WaitForDebugEvent 関数を呼び出すことができるのは
デバッグ中のプロセスを作成したスレッドだけ。
■ ContinueDebugEvent
SYNTAX
BOOL ContinueDebugEvent(
DWORD dwProcessId, // 続行するプロセス
DWORD dwThreadId, // 続行するスレッド
DWORD dwContinueStatus // 続行状態
);
デバッグイベントを報告したスレッドをデバッガが続行できるようにします。
OS に継続を通知する。
■ CreateProcess
SYNTAX
BOOL CreateProcess(
LPCTSTR lpApplicationName, // 実行可能モジュールの名前
LPTSTR cmdline, // コマンドライン文字列
LPSECURITY_ATTRIBUTES lpProcessAttributes, // セキュリティ記述子
LPSECURITY_ATTRIBUTES lpThreadAttributes, // セキュリティ記述子
BOOL bInheritHandles, // ハンドルの継承オプション
DWORD dwCreationFlags, // フラグ
LPVOID lpEnvironment, // 新しい環境ブロック
LPCTSTR sDir, // カレントディレクトリの名前
LPSTARTUPINFO pStartupInfo, // スタートアップ情報
LPPROCESS_INFORMATION pProcessInformation // プロセス情報
);
DESC
DESC
新しい 1 個のプロセスと、そのプライマリスレッドを作成
Windows OS の元では
実行ファイルを開くことはプロセスの生成と同じ
任意のプログラムをコマンドラインで、あるいは引数付で実行することができます
実行開始時の状態を指定したり実行したプロセスやプロセスのメインスレッドのハンドルも得られるので
プログラムの終了を待つなど柔軟な制御ができる
適当なエディタ機能とCreateProcess()を組み合わせれば
「編集したソースをコマンドラインでコンパイラに渡してコンパイル、コンパイル終了を待って実行」
という簡単な「統合開発環境」を開発することもできる
lpProcessAttributes
構造体で子プロセスが
取得したハンドルを 継承 できるかどうかを指定する
NULL を指定すると
取得したハンドルを継承できない
ノートパッドを起動する。
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb=sizeof( si );
CreateProcess(
NULL,
"notepad",
NULL,NULL,
FALSE,
NORMAL_PRIORITY_CLASS, // Thread Schedular の 優先順位 は普通
NULL,NULL,
&si,&pi
);
コマンドライン文字列は引数も含めてすべてを指定する。
コンソールウィンドウを新規に与えるには CREATE_NEW_CONSOLE フラグをたてる
CreateProcess(
NULL,
"ping localhost",
NULL,NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,NULL,
&si,&pi
);
プロセスの終了まちをする。
WaitForSingleObject( pi.hProcess, INFINITE );
フラグは次のようなものを指定できる
CREATE_NEW_CONSOLE
親のコンソールを継承せず新しいコンソールを持ちます。DETACHED_PROCESS フラグと同時に指定することはできません。
CREATE_NO_WINDOW
コンソールアプリケーションを起動する場合にのみ有効です。
このフラグを指定すると、コンソールウィンドウなしでアプリケーションを実行する。
DEBUG_PROCESS
呼び出し側プロセスをデバッガ、新しいプロセスをデバッグ対象として扱う。
OS は、デバッグ対象のプロセス内で発生するすべてのデバッグイベントを呼び出し側スレッドへ通知します。
このフラグを指定してプロセスを作成すると
呼び出し側スレッド(CreateProcess 関数を呼び出したスレッド)だけが 関数を呼び出せます。
DEBUG_ONLY_THIS_PROCESS
呼び出し側プロセスがデバッグ対象であるときに、
このフラグを指定せずに CreateProcess 関数を呼び出すと
呼び出し側プロセスを扱っているデバッガは、新しいプロセスもデバッグ対象とします。
呼び出し側プロセスがデバッグ対象ではない場合、デバッグ関連の動作は発生しません。
プロセスの実行でブロックされるため、スレッドで CreateProcess を実行すると
ユーザーの応答をうけることができる。
DWORD WINAPI func(LPVOID lpArg) {
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
si.cb=sizeof(si);
CreateProcess(NULL,(LPTSTR)lpArg,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,
NULL,NULL,&si,&pi);
CloseHandle(pi.hThread);
// 終了まち
WaitForSingleObject( pi.hProcess, INFINITE );
CloseHandle(pi.hProcess);
PostMessage(hwMain,WM_USER,0,0);
ExitThread(0);
return 0;
}
■ breakpoint
POINT
ブレークポイントを仕込むには、インストラクションポインタの場所に
ブレークポイント命令を入れるだけ。
Intel 系 CPU では ニーモニックは INT 3 。
オペコードは CC。
POINT
デバッガでブレークポイントを仕込むにはこのオペコードで上書きしてしまえばいい。
■ OpenProcess
SYNTAX
HANDLE OpenProcess(
DWORD dwDesiredAccess, // アクセスフラグ
BOOL bInheritHandle, // ハンドルの継承オプション
DWORD id // ハンドルを取得したいプロセスのID
);
DESC
既存のプロセスオブジェクトのハンドルを開く。
■ GetCurrentThread
SYNTAX
■ GetThreadContext
SYNTAX
BOOL GetThreadContext(
HANDLE hThread, // 情報を取得したいスレッドのハンドル
LPCONTEXT lpContext // コンテキスト情報
);
RET
0 : 失敗
!0 : 成功
DESC
指定したスレッドのコンテキスト( レジスタ( ISP, ESP )の値など )を取得する。
WARNING
実行中のスレッドには利用することができないので, SuspendThread() を使ってとめる。
情報を取得するには スレッドをとめる必要がある。
スレッドへの THREAD_GET_CONTEXT アクセス権が必要。
CONTEXT stCtx ;
stCtx.ContextFlags = CONTEXT_FULL ;
GetThreadContext ( GetCurrentThread ( ) , &stCtx ) );
// レジスタの値がとれる。
stCtx.Eip;
stCtx.Esp;
stCtx.Ebp;
■ スタックトレース(StackTrace)
シンボルテーブル情報があればスタックトレースをできる。
#include< imagehlp.h>
#pragma comment(lib, "imagehlp.lib")
LONG CALLBACK SWFilter(EXCEPTION_POINTERS *ExInfo)
{
STACKFRAME sf;
BOOL bResult;
PIMAGEHLP_SYMBOL pSym;
DWORD Disp;
printf("例外発生\n");
// シンボル情報格納用バッファの初期化
pSym = (PIMAGEHLP_SYMBOL)GlobalAlloc(GMEM_FIXED, 10000);
pSym->SizeOfStruct = 10000;
pSym->MaxNameLength = 10000 - sizeof(IMAGEHLP_SYMBOL);
// スタックフレームの初期化
ZeroMemory(&sf, sizeof(sf));
sf.AddrPC.Offset = ExInfo->ContextRecord->Eip;
sf.AddrStack.Offset = ExInfo->ContextRecord->Esp;
sf.AddrFrame.Offset = ExInfo->ContextRecord->Ebp;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrStack.Mode = AddrModeFlat;
sf.AddrFrame.Mode = AddrModeFlat;
// シンボルハンドラの初期化
SymInitialize(GetCurrentProcess(), NULL, TRUE);
// スタックフレームを順に表示する
for(;;) {
// 次のスタックフレームの取得
bResult = StackWalk(
IMAGE_FILE_MACHINE_I386,
GetCurrentProcess(),
GetCurrentThread(),
&sf,
NULL,
NULL,
SymFunctionTableAccess,
SymGetModuleBase,
NULL);
// 失敗ならば、ループを抜ける
if(!bResult || sf.AddrFrame.Offset == 0) break;
// プログラムカウンタから関数名を取得
bResult = SymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &Disp, pSym);
// 取得結果を表示
if(bResult) printf("--- 0x%08x %s() + 0x%x\n", sf.AddrPC.Offset,
pSym->Name,
Disp);
else printf("%08x, ---", sf.AddrPC.Offset);
}
// 後処理
SymCleanup( GetCurrentProcess() );
GlobalFree( pSym );
return(EXCEPTION_EXECUTE_HANDLER);
}
■ StackWalk
SYNTAX
BOOL StackWalk(
DWORD MachineType, // IMAGE_FILE_MACHINE_I386 ;
HANDLE hProcess,
HANDLE hThread,
LPSTACKFRAME StackFrame,
PVOID ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine,
PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine,
PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, // 関数ポインタ
PTRANSLATE_ADDRESS_ROUTINE TranslateAddress // NULL
);
■ SymSetOptions
SYMOPT_LOAD_LINES : 行番号をロードする
SymSetOptions ( dwOpts | SYMOPT_LOAD_LINES ) ;
■ SymInitialize
BOOL SymInitialize(
HANDLE hProcess,
PSTR UserSearchPath, // シンボル情報ファイルの検索パス
BOOL fInvadeProcess
);
RET
TRUE : 成功
FALSE : 失敗
DESC
プロセス用のシンボルハンドラを初期化する。
シンボルハンドラの観点では、
プロセスはシンボル情報を収集するときに利用できる便利なオブジェクトです。
シンボルハンドラを利用するのは、
デバッグ対象のプロセス用のシンボルをロードする必要のあるデバッガや他のツールです。
HANDLE hProcess = GetCurrentProcess () ;
if ( FALSE == g_cSym.SymInitialize ( hProcess ,
NULL ,
FALSE ) )
if ( GetThreadContext ( GetCurrentThread ( ) , &stCtx ) )
■ GlobalAlloc
SYNTAX
HGLOBAL GlobalAlloc(
UINT flag, // 割り当て方法
DWORD sz // サイズ
);
RET
NULL : 失敗
DESC
指定された Byte 数を Heap から割り当てる。
POINT
「固定メモリ」と「移動可能メモリ」の二種類のメモリを割り当てる機能がある。
固定メモリ
割り当てたメモリ領域のアドレスが返される
移動可能メモリ
メモリ領域を識別するハンドルが返され、メモリ領域自体は任意に移動される
移動可能メモリはvmem機構の存在しなかったWin16の時代の名残。
vmem機構の備わったWin32環境ではほとんど意味はない。
ただし、DDEやクリップボードなどへのデータの送受信など
特定の目的で使用する場合はある。
// 固定 Memory Address として割り振る
// Win32 では 物理 Memory の 移動, Swap はされる
// 理論上は 固定 ということ
//
GlobalAlloc( GMEM_FIXED, sz );
// 仮想 Memory 空間を移動できる
GlobalAlloc( GMEM_MOVEABLE, sz );
■ SymGetSymFromAddr
SYNTAX
BOOL SymGetSymFromAddr(
HANDLE hProcess,
DWORD Address, // 検索したいシンボルのアドレス( シンボルの境界でなくてもかまいません。
アドレスがシンボルの先頭とシンボルの終わり
( シンボルの先頭にシンボルサイズを足した位置)の間にある場合そのシンボルが見つかる)
PDWORD Displacement, // シンボルの先頭からの変位( オフセット)、または 0 を指定します。
PIMAGEHLP_SYMBOL Symbol // シンボル名を受け取る構造体
);
DESC
プログラムカウンタから関数名を取得する。
指定されたアドレスに置かれているシンボルを検索します。
■ SymGetLineFromAddr
SYNTAX
BOOL SymGetLineFromAddr(
HANDLE hProcess,
DWORD dwAddr,
PDWORD pdwDisplacement,
PIMAGEHLP_LINE Line
);
DESC
指定されたアドレスに対応するソースコードの行、ファイル名を検索する。
SymGetLineFromAddr を呼び出すに
Line バッファを割り当て、 構造体の必要なメンバ( サイズなど )をセットすること。
IMAGEHLP_LINE stIHL ;
ZeroMemory ( &stIHL , sizeof ( IMAGEHLP_LINE ) ) ;
stIHL.SizeOfStruct = sizeof ( IMAGEHLP_LINE ) ;
if ( 0 != SymGetLineFromAddr ( dwAddr ,
&dwDisp ,
&stIHL ) )
{
// Put this on the next line and indented a bit.
pCurrPos += wsprintf ( pCurrPos ,
_T ( "\n\t\t%s, Line %d" ) ,
stIHL.FileName ,
stIHL.LineNumber ) ;
// オフセットがあれば
if ( 0 != dwDisp )
{
pCurrPos += wsprintf ( pCurrPos ,
_T ( " + %d bytes" ) ,
dwDisp ) ;
}
}
■ SymCleanup
SYNTAX
BOOL SymCleanup(
HANDLE hProcess
);
DESC
プロセスハンドルに関連付けられているすべてのリソースの割り当てを解除する。
■ ReadProcessMemory
SYNTAX
BOOL ReadProcessMemory(
HANDLE hProcess, // プロセスのハンドル
LPCVOID lpBaseAddress, // 読み取り開始アドレス
LPVOID buf, // データを格納するバッファ
DWORD size, // 読むバイト数
LPDWORD lpNumberOfBytesRead // 読み取ったバイト数
);
DESC
指定されたプロセスのアドレス空間から指定された範囲のデータを
現在のプロセスの指定されたバッファへコピーします。
PROCESS_VM_READ アクセス権付きのハンドルを備えている任意のプロセスは
この関数を呼び出せます。
通常は、デバッグ中のプロセスを読み取り対象にする。( それ以外にも使ってもよい。 )
POINT
プロセスのハンドルは自身でも良いし、別のプロセスでも良い。
デバッガがデバッグ対象のプロセスの仮想メモリ空間の値を変更するために使う。
■ WriteProcessMemory
■ SetUnhandledExceptionFilter
SYNTAX
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
LPTOP_LEVEL_EXCEPTION_FILTER callback
);
DESC
Win32 が各スレッドとプロセスの最上位に置くトップレベル例外ハンドラを呼び出し元アプリケーションに置き換えます
この関数の呼び出し後
デバッグ中でないプロセスで例外が発生し
その例外が Win32 未処理例外フィルタに到着すると、
そのフィルタによって、callback で指定した例外フィルタ関数が呼ばれる。
コールバックされる関数は次のシグネーチャをもつ
LONG CALLBACK SWFilter( EXCEPTION_POINTERS * );