Win32 debug

Win32 debug

簡単なプログラムで debug の練習。
次のプログラムを VC++ Express でリリース形式で build.
badmem() でクラッシュする。


#include "stdafx.h"
#include
using namespace std;
void dummyf() {
printf("hello\n");
}

void badmem() {
int *ip = (int*)0x27777777;
*ip = 5;
}

struct en {
int _a;
void (*f)();
public:
en() : _a(0), f(dummyf) {}
};

class tc {
int f;
public:
virtual int doit(int a, int b, int c);
};

int tc::doit(int a, int b, int c)
{
struct en e;
int k = f + 3;
int x = a + b + k;
int y = c / 2;
int z = x + y;
e.f();
badmem();
return z;
}

int sum(int a, int b, int c)
{
int x = a + b;
int y = c / 2;
int z = x + y;
return z;
}

void makeCall(struct en *enp)
{
enp->f();
}

int _tmain(int argc, _TCHAR* argv[])
{
struct en a;
tc *b = new tc();
cout << "created tc instance" << endl;
b->doit(2,4,6);
int x = sum(4, 8, 16);
makeCall(&a);
return x;
}


Windows Vista Version 6000 MP (2 procs) Free x86 compatible
Product: WinNt, suite: SingleUserTS Personal
Debug session time: Sat May 31 03:33:00.000 2008 (GMT+9)
System Uptime: 1 days 19:53:20.931
Process Uptime: 0 days 0:01:46.000
.....
eax=0000000c ebx=00000000 ecx=00f8214c edx=77b20f34 esi=0000000c edi=00f833a8
eip=00f81030 esp=0012f978 ebp=0012f9d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
vctest!tc::doit+0x20:
00f81030 c7057777772705000000 mov dword ptr ds:[27777777h],5 ds:0023:27777777=????????
0:000> kv
ChildEBP RetAddr Args to Child
0012f978 00f81086 00000002 00000004 00000006 vctest!tc::doit+0x20 (FPO: [3,0,1]) (CONV: thiscall)
0012f98c 00f8153c 00000001 00640940 007317f8 vctest!wmain+0x46 (FPO: [2,0,1]) (CONV: cdecl)
0012f9d0 769a381b 7ffdf000 0012fa1c 77afa9bd vctest!__tmainCRTStartup+0x10f (FPO: [Non-Fpo]) (CONV: cdecl)
0012f9dc 77afa9bd 7ffdf000 00127d1c 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0012fa1c 00000000 00f81684 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x23 (FPO: [Non-Fpo])

tc::doit() は tc のインスタンス関数だが、多くの UNIX 系の実装とは異なり、this ポインタが最初の引数として渡されているわけではない。

WinDbg のヘルプを見ていくと x86コーリングコンベンションの説明があり、
this は ecx で渡すと書かれている。


0:000> uf tc::doit
vctest!tc::doit:
00f81010 8b44240c mov eax,dword ptr [esp+0Ch]
// c をロード
00f81014 99 cdq // convert dword (eax) to qword (edx:eax).
00f81015 2bc2 sub eax,edx // eax -= edx ???
00f81017 d1f8 sar eax,1 // eax >> 1 -> x
00f81019 034104 add eax,dword ptr [ecx+4]
// x += *(this+4) -> x += this.f
00f8101c 8b4c2408 mov ecx,dword ptr [esp+8]
// load b
00f81020 03442404 add eax,dword ptr [esp+4]
// x += a, here x = c/2 + f + a
00f81024 56 push esi
00f81025 8d740803 lea esi,[eax+ecx+3]
// esi = x = c/2 + f + a + b + 3
00f81029 e8d2ffffff call vctest!dummyf (00f81000)
// inline 展開された e.f()
00f8102e 8bc6 mov eax,esi
// return value を設定?
00f81030 c7057777772705000000 mov dword ptr ds:[27777777h],5
// inline 展開された badmem
00f8103a 5e pop esi
00f8103b c20c00 ret 0Ch

int tc::doit(int a, int b, int c)
{
struct en e;
int k = f + 3;
int x = a + b + k;
int y = c / 2;
int z = x + y;
e.f();
badmem();
return z;
}

この関数のスタックフレーム付近をダンプして見る。


0:000> dd 0012f978
0012f978 00641080 00f81086 00000002 00000004
org esi retp arg0 arg1
0012f988 00000006 00000001 00f8153c 00000001
arg2
0012f998 00640940 007317f8 18658db6 00000000
0012f9a8 00000000 7ffdf000 c0000005 00000000
0012f9b8 0012f9a0 0012f574 0012fa0c 00f81b75
0012f9c8 188f56b6 00000000 0012f9dc 769a381b
0012f9d8 7ffdf000 0012fa1c 77afa9bd 7ffdf000
0012f9e8 00127d1c 00000000 00000000 7ffdf000

dd はいってみれば 32-bit 値の配列として byte-swap をしてデータを
表示している。実際のメモリイメージは次のようになっている。


0:000> db 0012f978
0012f978 80 10 64 00 86 10 f8 00-02 00 00 00 04 00 00 00 ..d.............
0012f988 06 00 00 00 01 00 00 00-3c 15 f8 00 01 00 00 00 ........<.......
0012f998 40 09 64 00 f8 17 73 00-b6 8d 65 18 00 00 00 00 @.d...s...e.....
0012f9a8 00 00 00 00 00 f0 fd 7f-05 00 00 c0 00 00 00 00 ................
0012f9b8 a0 f9 12 00 74 f5 12 00-0c fa 12 00 75 1b f8 00 ....t.......u...
0012f9c8 b6 56 8f 18 00 00 00 00-dc f9 12 00 1b 38 9a 76 .V...........8.v
0012f9d8 00 f0 fd 7f 1c fa 12 00-bd a9 af 77 00 f0 fd 7f ...........w....
0012f9e8 1c 7d 12 00 00 00 00 00-00 00 00 00 00 f0 fd 7f .}..............

ecx は書き換えられてしまっていて、this が何だったのかはわからない。ただし、caller で設定しているはずなので、たどる方法がないか見てみる。


0:000> uf /o wmain
vctest!wmain:
00f81040 56 push esi
00f81041 6a08 push 8
00f81043 e848030000 call vctest!operator new (00f81390)
// new tc() -> eax
00f81048 83c404 add esp,4
00f8104b 85c0 test eax,eax
// eax (new tc() retval) check. If true, clear esi
00f8104d 740a je vctest!wmain+0x19 (00f81059)

vctest!wmain+0xf:
00f8104f c7006c21f800 mov dword ptr [eax],offset vctest!tc::`vftable' (00f8216c)
// this->__VFN_table = vctest!tc::`vftable'
00f81055 8bf0 mov esi,eax
// esi = eax = this
00f81057 eb02 jmp vctest!wmain+0x1b (00f8105b)

vctest!wmain+0x19:
00f81059 33f6 xor esi,esi

vctest!wmain+0x1b:
00f8105b a16020f800 mov eax,dword ptr [vctest!_imp_?endlstdYAAAV?$basic_ostreamDU?$char_traitsDstd (00f82060)]
00f81060 8b0d5c20f800 mov ecx,dword ptr [vctest!_imp_?coutstd (00f8205c)]
00f81066 50 push eax
00f81067 51 push ecx
00f81068 e833000000 call vctest!std::operator<< > (00f810a0)
00f8106d 83c404 add esp,4
00f81070 8bc8 mov ecx,eax
00f81072 ff154420f800 call dword ptr [vctest!_imp_??6?$basic_ostreamDU?$char_traitsDstdstdQAEAAV01P6AAAV01AAV01ZZ (00f82044)]
00f81078 8b16 mov edx,dword ptr [esi]
00f8107a 8b02 mov eax,dword ptr [edx]
00f8107c 6a06 push 6
00f8107e 6a04 push 4
00f81080 6a02 push 2
00f81082 8bce mov ecx,esi
// ecx はここで設定
00f81084 ffd0 call eax // ---> tc::doit()
00f81086 e875ffffff call vctest!dummyf (00f81000)
00f8108b b814000000 mov eax,14h
00f81090 5e pop esi
00f81091 c3 ret

esi は tc::doit() でほかの用途に使う前にスタックにプッシュしている。つまり、スタックのトップにあるはずだ。


0012f978 00641080 00f81086 00000002 00000004
org esi retp arg0 arg1

tc を使ってダンプしてみる。


0:000> dt 00641080 tc
vctest!tc
+0x000 __VFN_table : 0x00f8216c
+0x004 f : 0

0:000> dd 0x00f8216c
00f8216c 00f81010 00000048 00000000 00000000
00f8217c 00000000 00000000 00000000 00000000

wmain を見直すと後に this として使われる esi は次のように設定されている。


00f8104f c7006c21f800 mov dword ptr [eax],offset vctest!tc::`vftable' (00f8216c)
00f81055 8bf0 mov esi,eax

tc::'vtable' を表示してみる。


0:000> dd vctest!tc::`vftable'
00f8216c 00f81010 00000048 00000000 00000000
00f8217c 00000000 00000000 00000000 00000000

http://www.microsoft.com/japan/msdn/vs_previous/visualc/techmat/feature/jangrayhood/
に vftable の記述があるが、単に配列として存在するかの様な記述になっている。処理系によってはそのポインタからクラス名をたどれるものもあるのだが...

Calling Convention の最後にいくつか Idiom が記されていた。