以前就发现LH的Exception Handling有变化。当程序崩溃后,程序会被系统挂在发生问题的汇编代码上,这样直接用调试器上去就可以看了。当时一直想研究下具体怎么实现的。另外在具体的异常派发也没有详细地用调试器走一遍。今天有时间,所以就用调试器跑了一遍一个简单的AV。
 
我最佩服的Windows用户态代码有两个。其中一个是异常派发,另外一个是CLR引擎。异常派发的代码加在一起,估计也不会超过5000行。但是异常派发这个东西从用户态到核心态,从C++到CLR,跟系统相关,跟编译器相关,跟调试器相关,跟编译选项相关,跟系统安全设置相关,跟注册表设定相关...我了解AEDebug,了解UnhandledExceptionFilter,了解/EHa,了解FIEO,了解windbg,了解__try/__except,可以我也从来没能够把整个异常派发的流程完整地调过一遍。
 
下面的代码非常简单,就是一个普通的AV。大家看看有多复杂。我尽可能地理清了用户态的流程和关键函数。但是对于其中的具体环节,比如stack frame怎么恢复,trylevel怎么遍历,stack unwind怎么实现等等,都没有具体深入。如果有兴趣的可以看看中间的过程,没有兴趣的可以直接到最后看结论。
 
代码非常简单

#include "windows.h"
class dummy
{
public:
 ~dummy(); 
};
dummy::~dummy()
{
 MessageBox(0,L"aaa",0,0);
};
int _tmain(int argc, _TCHAR* argv[])

 dummy d;
 char c=getchar();
 if(c=='2')
  SetErrorMode(2);
 if(c=='3') 
  throw 1; 
 int *p=0;
 int j=*p;
 printf("%d/n",j); 
 return 0;
}
 
运行后,输入字符5,使得j=*p发生Access Violation。下面是异常发生后的处理。系统是Longhorn Server RC1。
 
首先自然是让AV触发了:
0:001> g
(ec8.978): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=7ffd4000 ecx=47c1164d edx=00496008 esi=00000000 edi=0012ff1c
eip=0042ecfa esp=0012fe2c ebp=0012ff1c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
*** WARNING: Unable to verify checksum for crashstudy.exe
*** ERROR: Module load completed but symbols could not be loaded for crashstudy.exe
crashstudy+0x2ecfa:
0042ecfa 8b08            mov     ecx,dword ptr [eax]  ds:0023:00000000=????????
 
接下来的端点是在ntdll!KiUserExceptionDispatcher。因为用户态的异常,会直接被核心通过这个函数派发出来。
这个核心函数非常简单,就是几行汇编。这个函数首先通过FS寄存器寻找Exception handler head,得到的head如下:
 
0:000> kL
ChildEBP RetAddr 
0012fc10 77148107 ntdll!RtlDispatchException+0x3a
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b

0:000> r
eax=0012ff2c ebx=0012fc28 ecx=0012fc04 edx=0012fb8c esi=0012fc28 edi=00001771
eip=77126612 esp=0012fba0 ebp=0012fc10 iopl=0         nv up ei ng nz na po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283
ntdll!RtlDispatchException+0x3a:
77126612 8365f000        and     dword ptr [ebp-10h],0 ss:0023:0012fc00=0012fb38
0:000> dt EXCEPTION_REGISTRATION_RECORD  0012ff2c
   +0x000 Next             : 0x0012ff78 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x0040b1a5     crashstudy!_EH_prolog3_catch+0
0:000> dt EXCEPTION_REGISTRATION_RECORD  0x0012ff78
   +0x000 Next             : 0x0012ffc4 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x00402550     crashstudy!_except_handler4+0
0:000> dt EXCEPTION_REGISTRATION_RECORD  0x0012ffc4
   +0x000 Next             : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x770fa033     ntdll!_except_handler4+0
 
从上面的输出可以看到,三个异常处理函数分别是crashstudy!_EH_prolog3_catch,rashstudy!_except_handler和ntdll!_except_handler4。注意名字,前两个是编译器编译到程序中的,最后一个是系统默认的最后一环异常处理函数。用!exchain命令可以更直观地输出结果:
 
0:000> !exchain
0012ff2c: crashstudy!_EH_prolog3_catch+47 (0040b1a5)
0012ff78: crashstudy!_except_handler4+0 (00402550)
0012ffc4: ntdll!_except_handler4+0 (770fa033)

首先触发的当然就是_EP-prolog3_catch了:
 
0:000> kL
ChildEBP RetAddr 
0012fb60 7714827b crashstudy!_EH_prolog3_catch+0x47
0012fc10 77148107 ntdll!ExecuteHandler+0x24
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
crashstudy!_EH_prolog3_catch函数的目的是什么呢,请看:
 
0:000> kL
ChildEBP RetAddr 
0012facc 0040b015 crashstudy!FindHandler+0x336
0012fb00 00409fcd crashstudy!__InternalCxxFrameHandler+0xd9
0012fb3c 771482a9 crashstudy!__CxxFrameHandler3+0x26
0012fb60 7714827b ntdll!ExecuteHandler2+0x26
0012fc10 77148107 ntdll!ExecuteHandler+0x24
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
注意看,这里一路调上来,就到了FindHandler函数。这是C Runtime中非常重要的函数。比如说:
 
0:000> u crashstudy!FindHandler+0x27c
crashstudy!FindHandler+0x27c
0040ae4a 6a01            push    1
0040ae4c 56              push    esi
0040ae4d e837f7ffff      call    crashstudy!__DestructExceptionObject (0040a589)
0040ae52 59              pop     ecx
0040ae53 59              pop     ecx
0040ae54 807dff00        cmp     byte ptr [ebp-1],0
0040ae58 0f85ae000000    jne     crashstudy!FindHandler+0x33e (0040af0c)
0040ae5e 8b07            mov     eax,dword ptr [edi]
 
C++相关的异常派发,stack object的析构,就是在这里完成。这里由于发生的异常是AV,所以找C++ handler是找不到的。找不到的结果呢,就是程序就会退回到
 
0:000> kL
ChildEBP RetAddr 
0012fc10 77148107 ntdll!RtlDispatchException+0x124
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
既然没有找到对应的handler,RtlDispatchException函数继续寻找了。这次当然就会来到crashstudy!_except_handler4:

0:000> kL
ChildEBP RetAddr 
0012fb3c 771482a9 crashstudy!_except_handler4
0012fb60 7714827b ntdll!ExecuteHandler2+0x26
0012fc10 77148107 ntdll!ExecuteHandler+0x24
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
这个_except_handler4函数有什么用呢,这个函数的作用不比寻找C++ exception handler的作用小。这里先不说有什么用,大家只要记得,这个函数一路调用过去,最后的callstack会成为这样。
 
0:000> kL
ChildEBP RetAddr 
0012fa48 7702ea1d crashstudy!__CxxUnhandledExceptionFilter
0012fad4 00402d34 kernel32!UnhandledExceptionFilter+0x137
0012faf4 004013d9 crashstudy!_XcptFilter+0x6a
0012fb00 00405280 crashstudy!__tmainCRTStartup+0x188
0012fb14 004025de crashstudy!_EH4_CallFilterFunc+0x12
0012fb3c 771482a9 crashstudy!_except_handler4+0x8e
0012fb60 7714827b ntdll!ExecuteHandler2+0x26
0012fc10 77148107 ntdll!ExecuteHandler+0x24
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
注意哦,这里的crashstudy!__tmainCRTStartup出现了两次,最后会调用到crashstudy!_XcptFilter这个函数以及kernel32!UnhandledExceptionFilter函数。这一系列的调用有什么用呢,等下再说。总之这里暂时没有_except_handler4什么事情。于是调用又会回到RtlDispatchException函数继续寻找。
 
0:000> kL
ChildEBP RetAddr 
0012fc10 77148107 ntdll!RtlDispatchException+0xcb
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
接下来的callstack就比较奇怪了:
 
0:000> kL
ChildEBP RetAddr 
0012faf8 77167323 kernel32!UnhandledExceptionFilter+0xb0
0012fb00 770fa214 ntdll!__RtlUserThreadStart+0x6f
0012fb14 770f44fc ntdll!_EH4_CallFilterFunc+0x12
0012fb3c 771482a9 ntdll!_except_handler4+0x8e
0012fb60 7714827b ntdll!ExecuteHandler2+0x26
0012fc10 77148107 ntdll!ExecuteHandler+0x24
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
kernel32!UnhandledExceptionFilter又执行了一次。不过这次不是通过crashstudy!_XcptFilter执行的了。这次还是没有找到对应的handler,由于所有的handler都尝试完了,所以这次就回到了
 
0:000> kL
ChildEBP RetAddr 
0012ff38 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
这个时候ntdll!KiUserExceptionDispatche作了一个让我非常惊讶的事情。他居然去调用了ntdll!ZwRaiseException,让这个Access Violation再发生一次!
 
0:000> u eip
ntdll!KiUserExceptionDispatcher+0x20
77148118 59              pop     ecx
77148119 6a00            push    0
7714811b 51              push    ecx
7714811c 53              push    ebx
7714811d e84ef2ffff      call    ntdll!ZwRaiseException (77147370)
77148122 83c4ec          add     esp,0FFFFFFECh
77148125 890424          mov     dword ptr [esp],eax
77148128 c744240401000000 mov     dword ptr [esp+4],1
 
这个时候再去调RaiseException会发生什么暂且不说。现在拿到手的信息有:
 
1. 三个Exception handler,分别是
crashstudy!_EH_prolog3_catch+47 (0040b1a5)
crashstudy!_except_handler4+0 (00402550)
ntdll!_except_handler4+0 (770fa033)
第一个会去找C++的try/catch block,以及destruct C++的object。第二个和第三个handler有什么用还不清楚。
 
2. 在第二次和第三次exception handler执行的时候,kernel32!UnhandledExceptionFilter都会执行。这个函数有啥用也还不清楚。
接下来需要继续试验了。重新运行这个程序,这次输入2,让SetErrorMode(2)执行。
 
这种情况下第一次的异常派发没有变,第二次的异常派法到下面这个callstack的时候,也没有变化:
 
0:000> kL
ChildEBP RetAddr 
0012fad4 00402d34 kernel32!UnhandledExceptionFilter+0x167
0012faf4 004013d9 crashstudy!_XcptFilter+0x6a
0012fb00 00405280 crashstudy!__tmainCRTStartup+0x188
0012fb14 004025de crashstudy!_EH4_CallFilterFunc+0x12
0012fb3c 771482a9 crashstudy!_except_handler4+0x8e
0012fb60 7714827b ntdll!ExecuteHandler2+0x26
0012fc10 77148107 ntdll!ExecuteHandler+0x24
0012fc10 00401066 ntdll!KiUserExceptionDispatcher+0xf
0012ff38 004013af crashstudy!wmain+0x46
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
变化在于kernel32!UnhandledExceptionFilter这个函数。这个函数会检查ErrorMode。如果ErrorMode不是2,它就返回Continue Search,如果是2,它就返会Execute handler flag。当Execute handler flag返回后,callstack变为:
 
0:000> kL
ChildEBP RetAddr 
0012f728 7714827b crashstudy!_EH_prolog3_catch+0x47
0012faf0 004052b3 ntdll!ExecuteHandler+0x24
0012fb14 00402674 crashstudy!_EH4_GlobalUnwind+0x15
0012fb3c 771482a9 crashstudy!_except_handler4+0x124
00000000 00000000 ntdll!ExecuteHandler2+0x26
 
注意看,这个时候_EH_prolog3_catch又开始执行了。但是注意看哦,一个重要的地方就是crashstudy!_EH4_GlobalUnwind函数。另外一个重要的地方就是当前的callstack里面,居然没有crashstudy的main。原因后面再说。如果g的话,会看到MessageBox。这个时候断下来看,会看到下面的callstack:
 
0:000> kL
ChildEBP RetAddr 
0012f348 765a0b66 ntdll!KiFastSystemCallRet
0012f34c 7658ac92 USER32!NtUserWaitMessage+0xc
0012f380 7658b8ed USER32!DialogBox2+0x202
0012f3a8 765dcc8c USER32!InternalDialogBox+0xd0
0012f448 765dd20e USER32!SoftModalMessageBox+0x69f
0012f598 765dd344 USER32!MessageBoxWorker+0x2c7
0012f5f0 765dd5c0 USER32!MessageBoxTimeoutW+0x7f
0012f610 765dd65c USER32!MessageBoxExW+0x1b
0012f62c 00401011 USER32!MessageBoxW+0x45
0012f640 0040b047 crashstudy!dummy::~dummy+0x11
0012ff38 004013af crashstudy!_NLG_Return
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x15e
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b

注意看,这里看不到ntdll!KiUserExceptionDispatcher哦!原因是UnahandledExceptionFilter返回了execute_handler_flag表示找到了handler,这个时候就要准备执行handler啦。handler是什么暂且不说,在执行handler以前呢,首先是要unwind stack.
 
Unwind stack是什么意思呢。我们知道,代码中如果有try或者__try,catch或者__finally,这里的catch和__finally在异常发生后都会执行的。如果函数有嵌套,每一层的__finally都需要执行。除此以外,每一个function里面的local C++ object都需要destruct。问题在于,catch,__finally和destruct的执行上下文应该是怎样的?比如说有一个C++ object,如果desctuct发生在没有发生异常的时候,callstack中是没有Exception Record,也没有什么KiUserExceptionDispatcher的。但是如果异常发生,为了保证destruct的成功完成,以及__finally等执行上下文跟没有发生异常时候的上下文一致,系统需要把发生异常时候的callstack,“伪装”成没有异常发生时候一样。这里的"伪装",就是通过unwind stack来完成的。unwind stack的意思,就是在异常发生后,执行对应的处理之前,恢复stack的状态,使得stack的状态跟没有异常发生时候一样,这样才能保证程序逻辑的正确运行。
 
在前面的callstack中,看到了__tmainCRTStartup出现了两次,最后会调用到crashstudy!_XcptFilter这个函数,原因是在__tmainCRTStartup中,CRT使用了__try __except。当异常发生后,系统会调用对应的exception filter去判断__tmainCRTStartup的__try和__except是否可以处理这个异常。所以系统会去执行__tmainCRTStartup中对应的代码,以及__tmainCRTStartup的exception filter函数crashstudy!_XcptFilter。这就是callstack中__tmainCRTStartup出现两次的原因。我们假设这里的异常__tmainCRTStartup可以处理,接下来系统就调用object destructor和__except block。这个时候系统就需要伪装成异常没有发生时候的上下文来执行了,通过unwind,stack会被恢复成异常从来没有发生过的样子,这样才能保证局部变量的访问,函数的返回等等代码逻辑正确执行。这个时候去看callstack的话,ntdll!KiUserExceptionDispatcher都是看不到的。
 
上面的call stack是unwind 以后的状态,unwind的过程包含destruct C++ object,所以上面才有MessageBox。当MessageBox返回后,callstack的状态变为:

0:000> kL
ChildEBP RetAddr 
0012fef0 00402846 kernel32!ExitProcess
0012fef8 00402a4b crashstudy!__crtExitProcess+0x14
0012ff30 00402a82 crashstudy!doexit+0xb5
0012ff40 004013f1 crashstudy!_exit+0xd
0012ff88 76fd1d02 crashstudy!__tmainCRTStartup+0x1a0
0012ff94 771285eb kernel32!BaseThreadInitThunk+0xe
0012ffd4 771285be ntdll!__RtlUserThreadStart+0x23
0012ffec 00000000 ntdll!_RtlUserThreadStart+0x1b
 
这里ExitProcess得到调用,程序马上就要退出拉!ExitProcess调用的原因在于__tmainCRTStartup的逻辑。__tmainCRTStartup的逻辑是:
__try
{
winmain();
}
__except ( _XcptFilter() )
{
ExitProcess();
}
 
由于上面返回了Execute handler flag,所以这里__except里面的内容开始执行,程序就退出了。
 
总的来说,这里的异常处理逻辑是:
 
1. 语言相关的异常处理是挂在顶端的。比如C++相关的在crashstudy!_EH_prolog3_catch。如果使用CLR,这里的异常处理函数指向mscorwks。这个函数负责程序所控制的异常派发和处理。语言相关的异常处理一般在这里完成。
2. 跟C/C++库相关的异常处理挂在crashstudy!_except_handler4。这个函数用途很多。它是C/C++ Runtime的异常处理守护函数。所有的unhandled C++ exception到这里就为止了,提供了C/C++ exception default hander。同时C/C++的异常库函数实现,也是在这里完成的。
3. 系统默认的异常处理在ntdll!_except_handler4。所有的未处理异常,最后就会掉到这里。这个函数的实现跟CRT的_except_handler4差不多,另外一个用途就是作为系统函数,如果第三方需要实现自己的异常处理逻辑,比如注册自己的异常处理函数和异常代码,这个系统函数就会做对应的派发。
4. CRT库中并不需要显式地使用上面这些函数。通过__try关键字,C/C++编译器生成了上面相关函数的调用逻辑。C/C++库函数的实现中,只需要简单地用__try把main包起来,然后在__except里面做对应处理就可以了。真正实用到上面函数的,主要是编译器。
5. 当找到异常处理函数后,程序会发生UnWind。只有当UnWind发生的时候,C++ destruc才会得到调用。在前面的第一种情况中,由于一直都没有找到对应的处理函数,所以这里的~dummy是没有得到调用的
6. kernel32!UnhandledExceptionFilter非常重要。Win32相关的异常处理函数逻辑都在这里面。比如SetErrorMode,SetUnhandledExceptionFilter的逻辑,都是依靠这个函数来完成的。在Vista/Longhorn以前,这个函数还负责处理UnhandledException。当发生AV的时候,程序会弹出一个框框,这个逻辑就是在kernel32!UnhandledExceptionFilter中实现的。在以往的系统中,启动AEDebug 中的项目也是在这里。
7. Vista上的异常处理发生了很多改变。一个变化就是前面提到的,如果AV没有处理,KiUserExceptionDispatcher会调用ZWRaiseException再次引发一次。而XP的实现就如前面地六点所描述一样。另外Vista上面新增加了很多安全性检查,防止通过Exceptionhanlder造成***。
8. 如果比较NT4到Vista的异常处理实现,会发现其中有翻天覆地的变化。首先是编译器上,VC6,VC2003,VC2005对C++和SEH的支持都不一样。请参考后面的链接。其次是Error Reporting功能。从最开始的Dr. Waston到XP的Error Reporting Service,然后到Vista的Werfault.exe,变化巨大。
9. 今天重新研究这里的异常派发,是因为Vista上对未处理异常的新特性。就是这里的RaiseException再次调用。在XP上,如果程序AV,看到错误界面后,这个时候用调试器上去,看到的信息是kernel32!UnhandledExceptionFilter中显示对话框的逻辑。如果需要找到异常地址,需要手动用调试器恢复。但是Vista上当看到错误信息后,直接上调试器,看到目标进程就停在发生错误的汇编那里。这是通过kernel32!UnhandledExceptionFilter调用ZWRaiseException和内核改变完成的。
10. 如果需要调试异常派发,需要用下面的断点欺骗系统。否则异常就直接发到调试器中来了
kernel32!BasepIsDebugPortPresent+0x2e "r eax=0;g"
11. 只有纯净的C++异常处理才有标准可循。如果把C++异常处理跟SEH混用,或者程序中会发生AV,或者跟CLR异常作interop,不同情况下会有不同的行为。
12. 调试的时候,不是所有的函数都可以随便用调试器断下来的。比如你要是尝试step in到kernel32!BasepIsDebugPortPresent里面,就会发现调试器完全失效。
13. Vista新增的异常处理机制需要跟内核配合。中间通过LPC调用,跟svchost通信,启动Werfault错误报告程序。而目标程序的挂起,是从kernel直接完成的。这里内部机制如何,可能需要配合kd才好研究了。跟传统的Dr. Waston, AEDebug相比,现在的机制非常方便调试人员检查程序的崩溃。