戻り値を確認する
戻り値は eax に入る
戻り値は eax レジスタに入ります。
ですから、ある関数の戻り値を見ようと思ったら、その関数実行時点のリターンアドレスまで進み、 eax レジスタの値を見ればよいのです。
実行例
それでは次のプログラムで、コマンドの練習をしてみましょう。
次のコードでは main 関数から Foo 関数を呼びます。Foo 関数では E_NOTIMPL という値を return するだけです。 この状況で Foo が返した E_NOTIMPL の実際の値を確認してみましょう。
#include <windows.h> #include <stdio.h> HRESULT Foo() { return E_NOTIMPL; } int main(int argc, char* argv[]) { Foo(); return 0; }
上記を retval.cpp という名前で保存します。makefile は次の通りです。
LINK32=link.exe ALL : "$(OUTDIR)\$(TARGETNAME).exe" CPPFLAGS=\ /nologo\ /MT\ /W3\ /Fo"$(OUTDIR)\\"\ /Fd"$(OUTDIR)\\"\ /c\ /Zi LINK32_FLAGS=\ /nologo\ /subsystem:console\ /pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\ /machine:I386\ /out:"$(OUTDIR)\$(TARGETNAME).exe"\ /DEBUG\ /RELEASE LINK32_OBJS= \ "$(OUTDIR)\$(TARGETNAME).obj" "$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS) $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS) "$(OUTDIR)" : @if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" .cpp{$(OUTDIR)}.obj: $(CPP) $(CPPFLAGS) $<
これを nmake すると、chk サブディレクトリに retval.exe が作られます。
以上が準備です。このプログラムを使って、Foo 関数が返す値を確認してみましょう。
> c:\debuggers\cdb retval.exe Microsoft (R) Windows Debugger Version 6.10.0003.233 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: retval.exe Symbol search path is: srv*C:\websymbols*http://msdl.microsoft.com/download/symbols Executable search path is: ModLoad: 00400000 00417000 retval.exe ModLoad: 77600000 77727000 ntdll.dll ModLoad: 75ed0000 75fab000 C:\Windows\system32\kernel32.dll (19b4.1a34): Break instruction exception - code 80000003 (first chance) eax=00000000 ebx=00000000 ecx=0012fb08 edx=77659a94 esi=fffffffe edi=7765b6f8 eip=77647dfe esp=0012fb20 ebp=0012fb50 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ntdll!DbgBreakPoint: 77647dfe cc int 3 0:000> x retval!*Foo* 00401000 retval!Foo (void) 0:000> bp retval!Foo ← Foo 関数にブレークポイントを設定 0:000> g ← 実行 Breakpoint 0 hit ← Foo を呼び出すところでブレーク eax=00902390 ebx=7ffdf000 ecx=00000001 edx=77659a94 esi=00000000 edi=00000000 eip=00401000 esp=0012ff3c ebp=0012ff40 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 retval!Foo: 00401000 55 push ebp 0:000> kbn 100 ←スタックバックトレースを確認 # ChildEBP RetAddr Args to Child 00 0012ff38 00401018 0012ff88 00401187 00000001 retval!Foo ← Foo のリターンアドレス 01 0012ff40 00401187 00000001 00902360 00902390 retval!main+0x8 02 0012ff88 75f14911 7ffdf000 0012ffd4 7763e4b6 retval!__tmainCRTStartup+0xfb 03 0012ff94 7763e4b6 7ffdf000 21e9e3ab 00000000 kernel32!BaseThreadInitThunk+0xe 04 0012ffd4 7763e489 004011de 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x23 05 0012ffec 00000000 004011de 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b 0:000> g 00401018 ← リターンアドレスまで実行 eax=80004001 ebx=7ffdf000 ecx=00000001 edx=77659a94 esi=00000000 edi=00000000 ↑ eax に戻り値が入っている eip=00401018 esp=0012ff40 ebp=0012ff40 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 retval!main+0x8: 00401018 33c0 xor eax,eax
上記の結果、Foo の戻り値 E_NOTIMPL の値は 80004001 となりました。 確認のため WinError.h をみてみると確かに次のように定義されていました。
#define E_NOTIMPL _HRESULT_TYPEDEF_(0x80004001L)
リターンアドレスは擬似レジスタ @$ra
尚、上記ではリターンアドレスを確認するためにわざわざスタックバックトレースをみて、 そこからリターンアドレスを持ってきました。もちろん、これでよいのですが、 デバッガでは擬似レジスタ @$ra がリターンアドレスを表しますので、これを使うとより簡単です。
0:000> bp retval!Foo 0:000> g Breakpoint 0 hit eax=002f2390 ebx=7ffdd000 ecx=00000001 edx=77659a94 esi=00000000 edi=00000000 eip=00401000 esp=0012ff3c ebp=0012ff40 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 retval!Foo: 00401000 55 push ebp 0:000> g @$ra eax=80004001 ebx=7ffdd000 ecx=00000001 edx=77659a94 esi=00000000 edi=00000000 eip=00401018 esp=0012ff40 ebp=0012ff40 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 retval!main+0x8: 00401018 33c0 xor eax,eax
戻り値を出力させたい場合
また、さらに関数の戻り値をトレースしたいだけの場合は、次のようにコマンド付きでブレークポイントを設定することも可能です。
0:000> bp retval!Foo "g @$ra; r eax; g" 0:000> g eax=80004001
"g @$ra; r eax; g" の部分は、ブレークポイントがヒットしたときに実行したいコマンドを並べています。 つまり、「Foo 関数でブレークしたら g @$ra でリターンアドレスまで実行し、r eax コマンドでその時の eax の値を出力し、 g コマンドでプログラムの実行を継続する」 という意味になります。
ちなみに、ここで j コマンドを用いることによって、戻り値が特定の値の場合にのみ処理を停止させるなどすることができます。