戻り値を確認する

戻り値は 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 コマンドを用いることによって、戻り値が特定の値の場合にのみ処理を停止させるなどすることができます。

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

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