スタックバックトレース
ブレークポイントを設定し、確認したい場所で止まったとき、最初に見るのはスタックバックトレースであることが多いです。
k コマンドで見ます。私が良く使うのは kbn 100 というオプションです。
スタックバックトレースの読み方
それでは、実例を交えてスタックバックトレースをみてみる練習をして見ましょう。
ちなみに、スタックを見るためには スタックフレーム の知識が必要になりますから、 まだ読んでいない人はスタックフレーム の記事を読んでから、この先に進んでくださいね。
それでは、ここでは次のような問題設定をしましょう。
メモ帳 (notepad.exe) にデバッガをアタッチして、ファイルを開く。 ファイルを開くタイミングにブレークポイントを設定して、デバッガからファイル名を確認する。
ではさっそくやってみましょう。
次のコマンドで、デバッガをアタッチした状態でメモ帳 (notepad.exe) を起動します。
C:\Debuggers> cdb notepad.exe
コマンドプロンプトが表示されたら、次のコマンドで CreateFileW のアドレスを確認します。
0:000> x kernel32!*CreateFileW*
7595cc4e kernel32!CreateFileW = <no type information>
759a7b3a kernel32!LZCreateFileW = <no type information>
0:000> bp 7595cc4e
0:000>
ここでは LZCreateFileW なんて関数も見えてますが、気にせず kernel32!CreateFileW のアドレスを持ってきて、 bp コマンドでブレークポイントを設定しています。
念のため、ちゃんと設定されたことを確認してみましょう。
ブレークポイントのリストを見るのは、bl コマンドです。
0:000> bl 0 e 7595cc4e 0001 (0001) 0:**** kernel32!CreateFileW 0:000>
ブレークポイントが設定できたので、プログラムを実行継続しましょう。
プログラムを実行するのは、g コマンドです。
0:000> g ModLoad: 758a0000 758be000 C:\Windows\system32\IMM32.DLL ModLoad: 76a30000 76af8000 C:\Windows\system32\MSCTF.dll ModLoad: 75a90000 75a99000 C:\Windows\system32\LPK.DLL ModLoad: 76b00000 76b7d000 C:\Windows\system32\USP10.dll ...
私の環境では、g コマンドで実行を再開してすぐにブレークポイントがヒットしました。
内部的にファイルを開いているのでしょう。
...
ModLoad: 77050000 770d4000 C:\Windows\system32\CLBCatQ.DLL
Breakpoint 0 hit
eax=00000000 ebx=00000001 ecx=0010edd8 edx=00000001 esi=00261628 edi=00000000
eip=7595cc4e esp=0010ecc4 ebp=0010ecf8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!CreateFileW:
7595cc4e 8bff mov edi,edi
0:000>
ここで、kbn コマンドでスタックバックトレースを確認します。
0:000> kbn 100 # ChildEBP RetAddr Args to Child 00 0010ecc0 77086d42 0010edd8 80000000 00000001 kernel32!CreateFileW 01 0010ecf8 77089eaa 0010edd8 00000001 00000000 CLBCatQ!StgIO::Open+0x10b 02 0010ed2c 770823a0 0010edd8 00000001 00000000 CLBCatQ!StgDatabase::InitDatabase+0x10a 03 0010ed50 7707f37a 0010edd8 00000001 00261020 CLBCatQ!OpenComponentLibraryEx+0x3e ... 27 0010f788 00501418 0010f7ac 00000000 00000000 USER32!GetMessageW+0x33 28 0010f7c8 0050195d 00500000 00000000 0024227b notepad!WinMain+0xec 29 0010f858 75954911 7ffdf000 0010f8a4 76f5e4b6 notepad!_initterm_e+0x1a1 2a 0010f864 76f5e4b6 7ffdf000 5810f3e9 00000000 kernel32!BaseThreadInitThunk+0xe 2b 0010f8a4 76f5e489 005031ed 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x23 2c 0010f8bc 00000000 005031ed 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b 0:000>
さて、ここでファイル名を見たいのですが、ファイル名はどこにあるでしょうか?
それを知るための前提の知識として、CreateFiel 関数の引数の意味を知らないといけません。
MSDN で CreateFile 関数をみると次のようなプロトタイプであることがわかります。
HANDLE WINAPI CreateFile( __in LPCTSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile );
つまり、ファイル名は第一引数に、ポインタとして渡されることがわかります。
上記のスタックバックトレースの中で、CreateFileW への第一引数は次の場所にあります。
# ChildEBP RetAddr Args to Child
00 0010ecc0 77086d42 0010edd8 80000000 00000001 kernel32!CreateFileW
これを値ではなく 「ポインタ」 として解釈すれば、0010edd8 番地にファイル名が記載されているであろうことがわかりますよね。
かつ、CreateFileW ですからワイドキャラクタのはずです。
従って、ワイドキャラクタの文字列を表示するコマンド du を用いて、0010edd8 番地をみれば、そこにファイル名が記載されていることがわかります。
0:000> du 0010edd8 0010edd8 "C:\Windows\Registration\R0000000" 0010ee18 "00026.clb"
簡単ですよね?
4つめ以降の引数はどこにあるの?
kbn コマンドに見える CreateFileW の行を見てみましょう。
# ChildEBP RetAddr Args to Child
00 0010ecc0 77086d42 0010edd8 80000000 00000001 kernel32!CreateFileW
よくみると、引数が3つしか表示されていません。 しかし、実際の CreateFileW の引数は上記の定義から7つあるはずです。
もし、4つめ以降の引数を見たい場合はどうしたらよいでしょうか?
その時は ChildEBP のアドレスからメモリをダンプすれば OK です。
これが EBP のアドレスであることを踏まえて考えると、黄色でマークした箇所が7つの引数に相当することがわかるでしょう。
0:000> dc 0010ecc0 0010ecc0 00000003 77086d42 0010edd8 80000000 ....Bm.w........ 0010ecd0 00000001 00000000 00000003 00000000 ................ 0010ece0 00000000 00000000 00261020 0010edd8 ........ .&..... 0010ecf0 00261628 00261628 0010ed2c 77089eaa (.&.(.&.,......w 0010ed00 0010edd8 00000001 00000000 00000000 ................ 0010ed10 00000000 0010ed68 00000000 76f67dc0 ....h........}.v 0010ed20 00000001 002615ac 00261628 0010ed50 ......&.(.&.P... 0010ed30 770823a0 0010edd8 00000001 00000000 .#.w............ 0:000>
kbn コマンドの結果にみえている Args to Child は、これらのデータの最初の三つ分を表示しているに過ぎないのです。