スタックバックトレース

ブレークポイントを設定し、確認したい場所で止まったとき、最初に見るのはスタックバックトレースであることが多いです。

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 は、これらのデータの最初の三つ分を表示しているに過ぎないのです。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説