我们都知道,C++的类成员函数调用离不this指针,this指针其实是作为隐形参数传递到成员函数的,VC++编译器是借助ecx寄存器来传递的。也正式这一点,很多调试器借助它来获取this指针。
在我们正向单步调试时,Windbg可以正确获取到this指针
0:000> dx Debugger.Sessions[0].Processes[5684].Threads[10640].Stack.Frames[21].SwitchTo();dv /t /v
Debugger.Sessions[0].Processes[5684].Threads[10640].Stack.Frames[21].SwitchTo()
0035ca04 class xtModel * this = 0xff2d3c48
又如:
0:000> dx Debugger.Sessions[0].Processes[5684].Threads[10640].Stack.Frames[32].SwitchTo();dv /t /v
Debugger.Sessions[0].Processes[5684].Threads[10640].Stack.Frames[32].SwitchTo()
0035d204 class MainWind * this = 0x28c204f0
但是当我们进行dmp分析,很多情况下windbg是获取不到的,因为进入到成员函数后,ecx被拿去做其他用途,最典型的就是在这个成员函数里调用其他类的成员函数,ecx被用来传递这个类的指针。又或是被用做循环计算器了,等等。总之是根据实际代码情况,由编译器决定。
通常情况下,我们拿dmp文件,用windbg打开,在确定崩溃调用栈后,还要查找真正崩溃的原因,也就是要查看相关数据。很多时候我们是要观察类成员的,那么这时候就是知道this指针了。幸运的情况下,我们切换栈帧后,windbg能自动获取并显示出this指针,单不是根据ecx来的,如下:0:000> .frame /r 20;dv /t /v
20 0035d264 55e3337e XXX!MainWind::XXX+0xd1
eax=0035c570 ebx=0000000f ecx=00000003 edx=00000000 esi=0000000f edi=763cb83f
eip=55d4a5c1 esp=0035d1f4 ebp=0035d264 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
XXX!MainWind::XXX+0xd1
55d4a5c1 83c40c add esp,0Ch
0035d204 class MainWind * this = 0x28c204f0
我们可以看到windbg自动得到this指针,但跟寄存器ecx的值不一样。很显然,ecx被做它用而被修改了,那么自动获取的this = 0x28c204f0的对不对呢?
我们看下XXX!MainWind::XXX的实现,如下:
0:000> uf XXX!MainWind::XXX
XXX!MainWind::XXX
14148 55d4a4f0 55 push ebp
14148 55d4a4f1 8bec mov ebp,esp
14148 55d4a4f3 6aff push 0FFFFFFFFh
14148 55d4a4f5 68a1967756 push offset _aullshr+0x55091 (567796a1)
14148 55d4a4fa 64a100000000 mov eax,dword ptr fs:[00000000h]
14148 55d4a500 50 push eax
14148 55d4a501 83ec54 sub esp,54h
14148 55d4a504 a1e453a956 mov eax,dword ptr [__security_cookie (56a953e4)]
14148 55d4a509 33c5 xor eax,ebp
14148 55d4a50b 8945f0 mov dword ptr [ebp-10h],eax
14148 55d4a50e 50 push eax
14148 55d4a50f 8d45f4 lea eax,[ebp-0Ch]
14148 55d4a512 64a300000000 mov dword ptr fs:[00000000h],eax
14148 55d4a518 894da0 mov dword ptr [ebp-60h],ecx
14149 55d4a51b e820041800 call 55eca940
14149 55d4a520 8945a4 mov dword ptr [ebp-5Ch],eax
14150 55d4a523 8d45d8 lea eax,[ebp-28h]
14150 55d4a526 50 push eax
14150 55d4a527 8b4da4 mov ecx,dword ptr [ebp-5Ch]
我们注意到,在函数的开头将ecx的值保存到栈里ebp-60h的地址,由前面的信息我们知道ebp=0035d264,那么ebp-60= 0035d204
0:000> dd 0035d204
0035d204 28c204f0 e3ed2f50 facf4fe0 00000000
0035d214 00000020 0035d2c4 00000011 0000001f
0035d224 e0d36fe0 763cb83f 5b1bbb10 5aeffcf3
0035d234 00000011 0000001f 706f7264 00000000
0035d244 00000009 00000000 00000004 0000000f
0035d254 abfbcb80 0035d834 567796a1 00000002
0035d264 0035d274 55e3337e 56af86a8 b221afec
0035d274 0035d288 55e12ca1 28c204f0 56af86a8
可知是this指针是对的。
我们看看,windbg获取错误的this指针情况,如下:
0:000> .frame /r 16;dv /t /v
16 0036d394 53735682 XXX!C::MainWndTest+0x266
eax=c86ff008 ebx=0036d3d8 ecx=00000008 edx=00000008 esi=00000002 edi=3f088b10
eip=536f0076 esp=0036d35c ebp=0036d394 iopl=0 nv up ei ng nz na po cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010283
XXX!MainWndTest+0x266:
536f0076 8bcf mov ecx,edi
@ecx class C* this = 0x00000008
看到了吗,直接从ecx拿this指针,很明显不对。根据上面的思路。我们先看看XXX!C::MainWndTest的实现
0:000> uf XXX!C::MainWndTest
XXX!C::MainWndTest
4044 536efe10 55 push ebp
4044 536efe11 8bec mov ebp,esp
4044 536efe13 6aff push 0FFFFFFFFh
4044 536efe15 6888e57753 push offset memmove_s+0xdcd4 (5377e588)
4044 536efe1a 64a100000000 mov eax,dword ptr fs:[00000000h]
4044 536efe20 50 push eax
4044 536efe21 83ec14 sub esp,14h
4044 536efe24 53 push ebx
4044 536efe25 56 push esi
4044 536efe26 57 push edi
4044 536efe27 a110337b53 mov eax,dword ptr [__security_cookie (537b3310)]
4044 536efe2c 33c5 xor eax,ebp
4044 536efe2e 50 push eax
4044 536efe2f 8d45f4 lea eax,[ebp-0Ch]
4044 536efe32 64a300000000 mov dword ptr fs:[00000000h],eax
4044 536efe38 8bf9 mov edi,ecx
可以看到把 ecx的值放到edi寄存器,可知this=3f088b10。经验证,是对的。
总结:
要想真正得到正确的this,需要看具体实现:有没有修改ecx;若修改ecx,必然会先把ecx保存,我们要找到保存的地址,也就得到了正确的ecx