wenmo8 发布的文章

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

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

 

Windows错误报告(WER)提供了许多设置来自定义问题报告体验。所有这些设置都可以使用组策略进行设置。有些还可以在Windows 7、Windows 8的操作中心或Windows Vista的问题报告和解决方案中进行更改。WER设置位于以下注册表子项之一:

使用Microsoft Visual Studio 进行调试时,在打开托管调试的情况下从.NET项目进行调试时,您可能会受到各种不真正关心的异常的困扰。以下是如何设置你的环境来忽略那些愚蠢的东西,并保留好的东西。

 

具体内容:

  1. 勾选(在方框中打勾)两列中的所有类别。
  2. 选中然后取消选中托管调试助手以强制取消选中所有子项。
  3. 使用Add按钮,将C++(Exchange)、EEFileLoadExceptionHRException添加到C++异常类别中。取消选中它们。

使用纯本机调试器调试.NET应用程序时要忽略的其他一些异常:

  • C++ Exceptions
  • EEMessageException
  • Win32 Exceptions
  • 4242420 CLRDBG_NOTIFICATION_EXCEPTION_CODE
  • e0434352 CLR Exception

如果您像我一样,您可能会忘记需要查找某些特定信息的类和名称空间。或者你忘了要查找的字段名。例如当我需要从System.Data.SqlClient命名空间查找信息。
此脚本从连接检索特定信息。扩展它也很容易,因此可以使用相同的方法从其他名称空间和字段中提取信息。
就个人而言,我更喜欢基于DML(调试标记语言)的方法,因为我可以使用超链接和默认脚本调用:$$><。DML方法需要更多的工作和更低级的编程,但是可以提供更好的结果。

需要使用$$<而不是$$><。

 

Source code for CONNECTIONS_POOL.TXT:

$$

$$ =============================================================================

$$ Verify Connections from Pool.

$$

$$ Compatilibity: Win32, should work on Win64.

$$

$$ Attention! For .Net Framework 2.0, edit the script and remove the clr10\\ from it so it can use the

$$ sos.dll version 2.0

$$

$$ Usage: $$< to run the script. (note: Just $$< not $$><)

$$

$$ Requirements: Public symbols.

$$

$$ Roberto Alexis Farah


$$ All my scripts are provided "AS IS" with no warranties, and confer no rights.

$$ =============================================================================

$$

.shell -i - -ci ".foreach ( obj {!clr10\\sos.dumpheap -type System.Data.SqlClient.SqlInternalConnection -short} ) {!do ${obj} }" FIND "_fInPool"

.shell -i - -ci ".foreach ( obj {!clr10\\sos.dumpheap -type System.Data.SqlClient.SqlConnectionPoolControl -short} ) {!do ${obj} }" FIND "_fResetConnection"

.shell -i - -ci ".foreach ( obj {!clr10\\sos.dumpheap -type System.Data.SqlClient.SqlConnectionPoolControl -short} ) {!do ${obj} }" FIND "_maxPool"

$$

$$ Number of Connection Objects

$$ ============================

$$

!clr10\\sos.dumpheap -type System.Data.OleDb.OleDbConnection -stat

$$

$$ ===================================================================