Windows的SEH机理简要介绍

1.异常分类

一般来说,我们把Exception分为2类,一类是CPU产生的异常,我们称之为CPU异常(或者硬件异常)。另一类为是通过调用RaiseException API产生的软件异常,我们称之为软件异常。

Windows使用同一的方式(KiDispatchException)来描述和分发这两类异常。但是,在处理各自异常时,会略有区别。

 

一般来说,异常处理过程可以分为2个阶段,第1阶段:异常登记过程;第2阶段:异常分发过程。下面分别简要介绍。

 

2.异常登记

1) CPU异常(硬件异常)登记:

在windows kernel中,存在一张中断描述符表(IDT, Interupt Descriptor Table). IDT是一张位于内核态物理内存中的线性表,其有256个表项。IDT中的每个表项叫做门描述符(Gate Descriptor)。门描述符的基本作用就是将CPU异常对应的中断号与其对应的异常处理函数KiTrapXX关联起来。

例如,0号中断(即除0错误)对应的处理例程为nt!KiTrap00

同时,我们可以通过以下debug comnand来列出IDT表中的各个表项。

lkd>!idt -a

 

对于CPU异常,通过中断向量找到其中断处理例程 KiTrapXX后,该KiTrapXX会调用CommDispatchException函数,其会获取异常发生时候的适当参数,用来初始化EXCEPTION_RECORD结构体之后,开始调用KiDispatchException进行异常分发。

(简述如下:中断向量 - 〉KiTrapXX - > CommonDispatchException - >KiDispatchException)

EXCEPTION_RECORD的结构如下:
0:000> dt ntdll!_EXCEPTION_RECORD
   +0x000 ExceptionCode    : Int4B
   +0x004 ExceptionFlags   : Uint4B
   +0x008 ExceptionRecord  : Ptr32 _EXCEPTION_RECORD
   +0x00c ExceptionAddress : Ptr32 Void
   +0x010 NumberParameters : Uint4B
   +0x014 ExceptionInformation : [15] Uint4B

 

2) 软件异常登记

软件异常是通过直接或者间接调用内核服务NtRaiseException而产生的。而用户态中可以通过RaiseException API,或者Try-catch等高级语言来调用这个内核服务,而通过RaiseException来登记软件异常的过程可以简单表述如下:

RaiseException在初始化一个EXCEPTION_RECORD结构体之后,开始调用NTDLL中的RtlRaiseException; RtlRaiseException在初始化CONTEXT结构体之后,开始调用内核中NtRaiseException, NtRaiseException再调用另外一个内核函数KiRaiseException。接下来KiRaiseException会调用KiDispatchException开始异常的分发。

如下所示:

CONTEXT是一个用来保存用户态-核心态切换现场的数据结构,主要是切换状态时候的各个寄存器的状态,其结构如下:
struct _CONTEXT
   +0x000 ContextFlags     : Uint4B
  ...

  ...   

   +0x09c Edi              : Uint4B
   +0x0a0 Esi              : Uint4B
   +0x0a4 Ebx              : Uint4B
   +0x0a8 Edx              : Uint4B
   +0x0ac Ecx              : Uint4B
   +0x0b0 Eax              : Uint4B
   +0x0b4 Ebp              : Uint4B
   +0x0b8 Eip              : Uint4B
   +0x0c4 Esp              : Uint4B
   ...

   ...

 

3. 异常派发过程(Dispatch Exception)

当产生CPU异常或者软件异常之后,最后都会调用到系统服务KiDispatchException进行异常的派发和处理。 对于CPU异常和软件异常,其处理过程略有不同。下面分别简要介绍

1)CPU异常派发过程:

 对于第一轮的异常,其会尝试先让内核调试器来处理该异常(KiDebugRoutine)。如果KiDebugRoutine返回为True,也就是内核调试器处理了该异常,那么便停止异常分发。否则,会调用kernel mode下的RtlDispatchException (NTOSKRNL)来试图寻找已经注册的结构化异常处理器。

 

如果没有相应的异常处理器,系统会尝试进行第二次分发。如果这次KeDebugRoutine仍然返回FAlSE,表明这是一个无人处理的异常,从而调用KeBugCheckEx引发蓝屏。

 其过程如下所示:

2) 软件异常派发过程:

 当软件异常被派发到user-mode之后,如何处理这个exception呢?实际上,在TEB中有一个非常重要的结构体,叫做_NT_TIB。在_NT_TIB中有一个_EXCEPTION_REGISTRATION_RECORD类型的字段叫做exceptionlist, 他的值就是指向异常处理器(_exception_handler)的首地址。EXCEPTION_REGISTRATION_RECORD是一个单向链表。

 

那么这个EXCEPTION_REGISTRATION_RECORD的首地址值从何而来呢?他是保存在FS:[0]寄存器中的。也就是说,当异常发生时,取得FS:[0]中的值,即为EXCEPTION_REGISTRATION_RECORD的首地址。我们从windbg中可以得到验证,如下:

 

ExpandedBlockStart.gif 代码
0 : 000 >   ! teb
TEB at 7ffdf000
    ExceptionList:        
0012fd04
    StackBase:            
00130000
    StackLimit:           0012e000
    SubSystemTib:         
00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 
00000000
    Self:                 7ffdf000
    EnvironmentPointer:   
00000000
    ClientId:             0000312c . 00001a50
    RpcHandle:            
00000000
    Tls Storage:          
00000000
    PEB Address:          7ffdb000
    LastErrorValue:       
0
    LastStatusValue:      c0000135
    Count Owned Locks:    
0
    HardErrorMode:        
0
0 : 000 >  r fs
fs
= 0000003b
0 : 000 >  dd fs:[ 0 ] L4
003b: 00000000    0012fd04  00130000  0012e000  00000000

 

 

在得到该异常处理链表之后,便开始遍历该链表。在遍历链表的过程中,当前节点的Exception_hanlder会判断是否能否handle当前的异常。如不能,则返回枚举类型_EXCEPTION_DISPOSITION的一个值 (ExceptionContinueSearch),以便让其继续向后遍历该链表,直到找到该exception_handler,并最终返回ExceptionContinueExecution,以便停止向下遍历的过程。其过程如下图所示:

 

但是,如果遍历到最后都没有找到handle当前exception的exception handler,那么便会触发unhandled exception并最终调用ntdll!RtlUnhandledExceptionFilter,对于桌面型应用程序,其就会崩溃; 而对于服务端程序,为了更好的用户体验,这时候比如asp.net 的runtime 就会捕捉到该exception,在客户端可能就看到service unavailable,或者服务器端错误等等。

 

ExpandedBlockStart.gif 代码
_TEB (thread environment blcok即线程环境块)定义如下:
=============
typedef struct
_TEB
{
   
+ 0x000  NtTib            :  _NT_TIB
   
+ 0x01c  EnvironmentPointer : Ptr32 Void
   
+ 0x020  ClientId         : _CLIENT_ID
   
+ 0x028  ActiveRpcHandle  : Ptr32 Void
   
+ 0x02c  ThreadLocalStoragePointer : Ptr32 Void
   
+ 0x030  ProcessEnvironmentBlock : Ptr32 _PEB
   
+ 0x034  LastErrorValue   : Uint4B
   ...
   ...   
 } TEB
   
_NT_TIB的结构定义如下:
===========
typedef struct
_NT_TIB
{
   
+ 0x000   ExceptionList     : Ptr32  _EXCEPTION_REGISTRATION_RECORD
   
+ 0x004  StackBase        : Ptr32 Void
   
+ 0x008  StackLimit       : Ptr32 Void
   
...
   ...
    + 0x018  Self             : Ptr32 _NT_TIB
}NT_TIB
而ntdll
! _EXCEPTION_REGISTRATION_RECORD的定义如下:
===========
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
   
+ 0x000  Next             : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   
+ 0x004  Handler          : Ptr32     _EXCEPTION_DISPOSITION 
}EXCEPTION_REGISTRATION_RECORD
 
ntdll!_EXCEPTION_DISPOSITION的定义如下:
============
  typedef enum _EXCEPTION_DISPOSITION
{
  ExceptionContinueExecution = 0
   ExceptionContinueSearch = 1
   ExceptionNestedException = 2
   ExceptionCollidedUnwind = 3
}EXCEPTION_DISPOSITION

 

 附件1:
NTDLL模块中与exception处理相关的常见几个系统服务和相关函数

 

ExpandedBlockStart.gif 代码
 ntdll ! RtlpUnhandledExceptionFilter 
 ntdll
! RtlpDphRaiseException 
 ntdll
! RtlpHeapExceptionFilter
 ntdll
! RtlpDphUnexpectedExceptionFilter 
 ntdll
! RtlUnhandledExceptionFilter2 
 ntdll
! RtlSetUnhandledExceptionFilter 
 ntdll
! RtlDispatchException 
 ntdll
! RtlRaiseException 
 ntdll
! RtlpExecuteHandlerForException

 ntdll
! KiRaiseUserExceptionDispatcher 
 ntdll
! KiUserCallbackExceptionHandler 
 ntdll
! KiUserExceptionDispatcher 
 ntdll
! KiUserApcExceptionHandler

  参考文档:

=======

A Crash Course on the Depths of Win32™ Structured Exception Handling

http://www.microsoft.com/msj/0197/exception/exception.aspx

 

转载于:https://www.cnblogs.com/Winston/archive/2010/03/16/1687649.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值