菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
249
0

查找非托管异常的来源

原创
05/13 14:22
阅读数 86522

有时,您会查看异常抛出后处理程序中的调用堆栈。如果附加到弹出watson对话框的未处理异常,这是非常常见的。
它可能看起来像:

kernel32!WaitForSingleObject+0xf

devenv!DwCreateProcess+0xbb

devenv!fExceptionHandling+0x1cb

devenv!DwExceptionFilter+0x8b

0x535ef48

那没什么用。您真正想要的是查看抛出异常时的调用堆栈。在x86上执行此操作有一个技巧。(这可以调整为在64位平台上工作。)
它可以在实时调试和小型转储中工作,甚至在没有任何符号的情况下也可以工作。我将首先给出如何做到这一点的快速步骤,然后我将解释它的工作原理。

我怎么找到它?

 

使用这些Windbg指令,但是任何带有内存搜索('s')和set context('.cxr')命令的本机调试器也可以执行此操作。

  1. 转到感兴趣的线程。
  2. 在包含dword 0x1003f的堆栈上搜索第一个地址。在Windbg中,键入“s-ds esp L1000 1003f”。异常有效地将0x1003f推送到堆栈上,因此这将有效地在线程的当前堆栈上查找异常的上下文。
  3. 至少应显示一个结果,如下所示: 0535ef48  0001003f 00000000 00000000 00000000 ?...............
    每行中的第一个数字(0535ef48)是地址,行的其余部分是该地址的上下文。原来第一个dword 0x1003f是发生异常上下文结构,那么0535ef48将是上下文的地址。如果得到多个条目,请使用第一行,因为这将对应于最新的异常。
  4. 将当前上下文设置为指向步骤3的结果中的第一个数字(本例中为0535ef48)。在windbg中,键入“.cxr 0535ef48”。

现在应该多检查一下你的调用堆栈。在我的示例中,它看起来像:

mscordbi!CordbHashTable::GetBase

mscordbi!CordbThread::RefreshStack+0x349

mscordbi!CordbProcess::DispatchRCEvent+0x1291

mscordbi!CordbRCEventThread::ThreadProc+0x9

eax=00000000 ebx=04700168 ecx=00000048 edx=0535f230 esi=00000000 edi=0535f254

eip=636ac786 esp=0535f214 ebp=0535f228 iopl=0 nv up ei pl zr na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210246

mscordbi!CordbHashTable::GetBase:

636ac786 80791400 cmp byte ptr [ecx+0x14],0x0 ds:0023:0000005c=??

我的程序正在取消引用0x5c(=0x48+0x14)。难怪它坠毁了。
请注意,我们不需要为原始堆栈上的任何模块添加任何符号来找到它。即使我们根本没有任何符号,我们仍然可以对崩溃的代码进行反汇编。

为什么有用?

这里有一些关键数据。
1) 当操作系统抛出异常时,它将原始抛出站点的上下文推送到堆栈上。(这是异常指针的一部分)。
2) 在x86上,上下文结构中的第一个字段是一个标志字段,它总是设置为0x1003f。由于其他原因,此值不太可能随机出现在堆栈顶部。
3) 在x86上,堆栈增长。因此,当前堆栈指针(esp)表示0x1003f将出现的范围的高端。因此,如果我们要搜索接近堆栈顶部的内容(0x1000字节以内),我们可以在范围内(esp,esp-0x1000)进行搜索。
4) 搜索dw的内存。格式为“s–d<范围><值>”。<range>的格式可以是“<address>L<length>”,它将搜索范围(address,address length)中的“value”。因此,“s-d esp L1000 1003f”意味着“搜索范围内的dword 0x1003f(esp,esp-0x1000)”。0x1000是一个任意的数字,在这里似乎足够了。
5) 调试器可以从任何上下文执行stackwalk。大多数调试器只是自动使用线程的当前上下文(通过kernel32!GetThreadContext),但调试器没有理由不能使用任意上下文。Windbg提供了一个很好的命令,“.cxr”,它可以让您这样做。可以设置要检查的上下文。(VS也在添加此命令)。

因此,如果您回顾最初的步骤,可以看到步骤2在线程堆栈中搜索异常所推送的上下文,然后步骤4告诉调试器查看当前上下文。如果步骤3给出了多行,则可能表示存在嵌套异常。也可能是一个局部变量“inti=0x1003f”。在这两种情况下,都可以对所有值(从最新的值开始)尝试.cxr,以找到有意义的调用堆栈。

对于kicks,检查我们提供给.cxr的上下文指针,您可以自己看到它与register命令的输出相匹配:

0:015> dt _CONTEXT 0535ef48

   +0x000 ContextFlags : 0x1003f

   +0x004 Dr0 : 0

   +0x008 Dr1 : 0

   +0x00c Dr2 : 0

   +0x010 Dr3 : 0

   +0x014 Dr6 : 0

   +0x018 Dr7 : 0

   +0x01c FloatSave : _FLOATING_SAVE_AREA

   +0x08c SegGs : 0

   +0x090 SegFs : 0x3b

   +0x094 SegEs : 0x23

   +0x098 SegDs : 0x23

   +0x09c Edi : 0x535f254

   +0x0a0 Esi : 0

   +0x0a4 Ebx : 0x4700168

   +0x0a8 Edx : 0x535f230

   +0x0ac Ecx : 0x48

   +0x0b0 Eax : 0

   +0x0b4 Ebp : 0x535f228

   +0x0b8 Eip : 0x636ac786

   +0x0bc SegCs : 0x1b

   +0x0c0 EFlags : 0x210246

   +0x0c4 Esp : 0x535f214

   +0x0c8 SegSs : 0x23

   +0x0cc ExtendedRegisters : [512] "???"

0:015> r

Last set context:

eax=00000000 ebx=04700168 ecx=00000048 edx=0535f230 esi=00000000 edi=0535f254

eip=636ac786 esp=0535f214 ebp=0535f228 iopl=0 nv up ei pl zr na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210246

这主要适用于本机异常;但也适用于托管异常+SOS。
从经验上讲,我已经使用这种技术很长时间了,而且每次都非常有效。我还没有找到一个值为0x1003f的杂散局部。

发表评论

0/200
249 点赞
0 评论
收藏