SEH无法捕捉内核代码的除零异常
在下面这小段代码中,存在一个故意的除零操作,并且使用了Windows操作系统的结构化异常处理(SEH)机制对其进行了保护。
59: n=1;
60: m=0;
61: __try
62: {
63: n=n/m;
64: }
65: __except(EXCEPTION_EXECUTE_HANDLER)
66: {
67: DBGOUT(("Caught divide by zero safely."));
68: }
如果以上代码是包含在用户态程序中的,也就是说在用户态执行,那么这个SEH会起作用,捕捉到这个异常。但是如果这段代码是在内核态中执行,那么这个SEH保护不会起作用。更准确的说,今天的Windows系统(至少是XP,Server 2003)内核会直接调用keBugCheck产生蓝屏,而不会交给异常保护块来处理这个异常。
其详细过程如下,当除零异常发生后,异常处理函数KiTrap00会被执行。这个函数会调用另一个名为Ki386CheckDivideByZeroTrap的内核函数。Ki386CheckDivideByZeroTrap在被调用后,会检查异常是否来源于内核代码,从汇编代码可以清楚的看到这一点:
kd> u nt!Ki386CheckDivideByZeroTrap la
nt!Ki386CheckDivideByZeroTrap:
80607316 6a38 push 38h
80607318 6800565380 push offset nt!_real+0x78 (80535600)
8060731d e8ffa9f0ff call nt!_SEH_prolog (80511d21)
80607322 c745e4940000c0 mov dword ptr [ebp-1Ch],0C0000094h
80607329 8b4508 mov eax,dword ptr [ebp+8]
8060732c 83786c08 cmp dword ptr [eax+6Ch],8
80607330 7508 jne nt!Ki386CheckDivideByZeroTrap+0x23 (8060733a)
80607332 6a7f push 7Fh
80607334 e876f3f1ff call nt!KeBugCheck (805266af)
80607339 cc int 3
第4行是把参数1(KTRAP_FRAME结构)赋给EAX。第5行是比较这个结构的6C偏移是否等于8。如果相等,接下来的两行(6、7行)就是调用KeBugCheck产生蓝屏。蓝屏的代码是7F,即UNEXPECTED_KERNEL_MODE_TRAP。
事实上,这里就是Windows内核就是在检查异常是否发生在内核态。6C字段实际上是发生异常时的CS寄存器值,即代码段的段选择子值。8代表了内核代码段的段选择子。以下是KTRAP_FRAME结构在Windows XP系统中的的定义:
kd> dt nt!_KTRAP_FRAME
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
+0x010 TempSegCs : Uint4B
+0x014 TempEsp : Uint4B
+0x018 Dr0 : Uint4B
+0x01c Dr1 : Uint4B
+0x020 Dr2 : Uint4B
+0x024 Dr3 : Uint4B
+0x028 Dr6 : Uint4B
+0x02c Dr7 : Uint4B
+0x030 SegGs : Uint4B
+0x034 SegEs : Uint4B
+0x038 SegDs : Uint4B
+0x03c Edx : Uint4B
+0x040 Ecx : Uint4B
+0x044 Eax : Uint4B
+0x048 PreviousPreviousMode : Uint4B
+0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x050 SegFs : Uint4B
+0x054 Edi : Uint4B
+0x058 Esi : Uint4B
+0x05c Ebx : Uint4B
+0x060 Ebp : Uint4B
+0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B
+0x06c SegCs : Uint4B
+0x070 EFlags : Uint4B
+0x074 HardwareEsp : Uint4B
+0x078 HardwareSegSs : Uint4B
+0x07c V86Es : Uint4B
+0x080 V86Ds : Uint4B
+0x084 V86Fs : Uint4B
+0x088 V86Gs : Uint4B
终上,一旦Ki386CheckDivideByZeroTrap检测到除零异常来源于内核代码就会立刻发起蓝屏,根本就进入常规的异常分发流程,因此也就不会执行什么SEH处理器了。