分类 调试 下的文章

我们都知道,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

 

很多时候,在调试会话中,我们需要加载/重新加载特定模块的符号(在WinDbg的上下文中)。例如,我们开始调试,在调试时设置正常的符号路径并下载,发现我们需要特定模块的符号。我们修改符号文件路径。如果我们这样做了,
.reload /f
它从同一个位置再次加载所有符号,这可能需要时间。
所以,只找到了加载一个特定模块符号的命令,
.reload /f @“mydll.dll”
这将使用最新的符号路径加载mydll.dll的符号。

分析是一种强大的VisualC++特性,可以帮助发现bug。然而,它有时忽略了关键问题。在这篇简短的文章中,我描述了一个有趣的危险模式,它无法识别,并解释了一个真正的解决方法。

如果您想查看任何windbg扩展所支持的命令,可以采用各种方法。

  • 你可以用!<ext_name>.help命令查看该扩展支持的所有命令。用扩展模块名替换<ext_name>。(注意:只有特定扩展支持help命令时,此操作才有效。)
  • 您可以在Dependency Walker中打开扩展DLL,它将在导出的功能面板中显示所有命令!command它背后没有任何魔力,它只是一个事实,WinDbg有非常简单的扩展模型,在这里您需要为每个命令实现导出的函数。当我们在Windbg中使用调试器扩展命令时,Windbg只是为那个函数执行GetProcAddress并调用它。依赖性walker具有显示模块导出函数的功能。

 

分析是一种强大的VisualC++特性,可以帮助发现bug。然而,它有时会在不存在问题的情况下发现问题。不幸的是,这些误报,就像这里报道的那样,使/分析变得不那么有用,因为严重的错误最终会被“狼”的叫声淹没。在这篇文章中,我描述了另一个令人沮丧的误报,它展示了一个编译器完全无法理解别名的例子。