原文地址:http://blogs.msdn.com/b/oldnewthing/archive/2006/08/21/710754.aspx
大家也许经常盯着调用栈,寻找捕捉到的异常以及在哪里发生的异常。
ChildEBP RetAddr Args to Child
030c21d0 76df3448 00000000 030c6138 76db6b0d ntdll!DbgBreakPoint
030c21dc 76db6b0d 030c2204 77b8d585 030c220c ole32!PeekMessageExceptionFilter+0x42
030c21e4 77b8d585 030c220c 00000000 030c220c ole32!CCliModalLoop::MyPeekMessage+0x41
030c220c 77f36992 030c25d0 030c6128 030c22e8 msvcrt!_except_handler3+0x61
030c2230 77f36964 030c25d0 030c6128 030c22e8 ntdll!ExecuteHandler2+0x26
030c22d8 77f36884 030c1000 030c22e8 00010007 ntdll!ExecuteHandler+0x24
030c25b8 77f6e0dd 030c25d0 00000000 00000000 ntdll!RtlRaiseException+0x3d
030c262c 77d3c239 77d4a4b6 77d3e2c5 030c3767 ntdll!RtlDeactivateActivationContextUnsafeFast+0x233
030c2630 77d4a4b6 77d3e2c5 030c3767 030c26a0 USER32!UserCallWinProcCheckWow+0x167
030c2634 77d3e2c5 030c3767 030c26a0 77d4a46f USER32!_NLG_Return2
030c265c 77d3e288 030c57b4 ffffffff 030c2688 USER32!__local_unwind2+0x70
030c2688 77f36992 030c26f8 030c57b4 030c27a4 USER32!_except_handler3+0xd5
030c26ac 77f36964 030c26f8 030c57b4 030c27a4 ntdll!ExecuteHandler2+0x26
030c2a74 77b8d36d 030c6128 77b8d36d 00000000 ntdll!ExecuteHandler+0x24
030c2a9c 77b8d59d 030c6128 030c2ac0 00000000 msvcrt!__global_unwind2+0x18
030c2ac0 77f36992 030c2ba4 030c6128 030c2bc0 msvcrt!_except_handler3+0x75
030c2ae4 77f36964 030c2ba4 030c6128 030c2bc0 ntdll!ExecuteHandler2+0x26
030c2b8c 77f36796 030c1000 030c2bc0 030c2ba4 ntdll!ExecuteHandler+0x24
030c2b8c 77b7aa54 030c1000 030c2bc0 030c2ba4 ntdll!KiUserExceptionDispatcher+0xe
030c3300 77b7b4dc 030c3324 7715b1b4 00000000 msvcrt!_woutput_l+0x18
(大家可以从符号服务器上获取到需要的调试符号,如果你的磁盘空间上G的话,也可以一次下载全部的符号信息。不过即便你打包下载了符号信息,你还是得需要符号服务器。因为调试信息会随着系统文件的更新而相应更新。)
这里可以看到,PeekMessageExceptionFilter函数捕捉到了一个异常。那是什么异常?首先,异常过滤函数会被传入一个EXCEPTION_POINTERS结果作为参数。
typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
现在,我们看一下PeekMessageExceptionFilter的参数:
030c21dc 76db6b0d 030c2204 77b8d585 030c220c ole32!PeekMessageExceptionFilter+0x42
0:0000> dd 030c2204 l2
030c2204 030c25d0 030c22e8
-------- --------
.exr .cxr
第一个值是ExceptionRecord的指针,第二个值是ContextRecord的指针。你可以通过输入命令.exr 030c25d0查看异常信息,还可以通过.cxr 030c22e8查看异常的上下文内容(比如,异常发生时在执行什么指令)。这两个值也作为ExecuteHandler2函数的第一和第三个参数出现。
(译注:.cxr实际上会执行切换上下文的操作。)
现在就用.exr 030c25d0看一下异常信息,异常代码是c015000f,正好是STATUS_SXS_EARLY_DEACTIVATION。切换到发生异常的上下文,调用栈显示如下:
ChildEBP RetAddr
030c262c 77d3c239 77d4a4b6 77d3e2c5 030c3767 ntdll!RtlDeactivateActivationContextUnsafeFast+0x233
030c2630 77d4a4b6 77d3e2c5 030c3767 030c26a0 USER32!UserCallWinProcCheckWow+0x167
030c2634 77d3e2c5 030c3767 030c26a0 77d4a46f USER32!_NLG_Return2
030c265c 77d3e288 030c57b4 ffffffff 030c2688 USER32!__local_unwind2+0x70
030c2688 77f36992 030c26f8 030c57b4 030c27a4 USER32!_except_handler3+0xd5
030c26ac 77f36964 030c26f8 030c57b4 030c27a4 ntdll!ExecuteHandler2+0x26
030c2a74 77b8d36d 030c6128 77b8d36d 00000000 ntdll!ExecuteHandler+0x24
030c2a9c 77b8d59d 030c6128 030c2ac0 00000000 msvcrt!__global_unwind2+0x18
030c2ac0 77f36992 030c2ba4 030c6128 030c2bc0 msvcrt!_except_handler3+0x75
030c2ae4 77f36964 030c2ba4 030c6128 030c2bc0 ntdll!ExecuteHandler2+0x26
030c2b8c 77f36796 030c1000 030c2bc0 030c2ba4 ntdll!ExecuteHandler+0x24
030c2b8c 77b7aa54 030c1000 030c2bc0 030c2ba4 ntdll!KiUserExceptionDispatcher+0xe
030c3300 77b7b4dc 030c3324 7715b1b4 00000000 msvcrt!_woutput_l+0x18
哇,在处理一个异常的时候又发生了另外一个异常。(碰巧这次在原来的调用栈里一眼就能看出来,但是通常情况下,找到后面的异常可能没那么容易。)
拿这个异常重复一次上面的练习:
0:000> .exr 030c2ba4
ExceptionAddress: 77b7aa54 (msvcrt!_woutput_l+0x00000018)
ExceptionCode: c00000fd (Stack overflow)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 030c2e88
0:000> .cxr 030c2bc0
eax=030c33b0 ebx=00000000 ecx=0000005c edx=00000000 esi=030c33c4 edi=030c33c4
eip=77b7aa54 esp=030c2e8c ebp=030c3300 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
msvcrt!_woutput_l+0x18:
001b:77b7aa54 53 push ebx
哈哈,原来SXS异常是由堆栈溢出引起的。在这个上下文里,你可以使用“k”命令查看是如何引发这个结果的。
这个bug是由于错误的递归导致的栈溢出引发的。当时线程正在为调用一个COM对象,等待它返回。而这个时候又收到了一个新的请求。问题的细节在这里并不重要;本文的目的就是展示如何在调用栈上挖掘异常指针,从中了解引发问题的Win32异常的信息。