zoukankan      html  css  js  c++  java
  • 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中可以得到验证,如下:

     

    复制代码
    代码
    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,或者服务器端错误等等。

     

    复制代码
    代码
    _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处理相关的常见几个系统服务和相关函数

     

    复制代码
    代码
     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

  • 相关阅读:
    WPF 使用 Direct2D1 画图 绘制基本图形
    WPF 使用 Direct2D1 画图 绘制基本图形
    dot net core 使用 IPC 进程通信
    dot net core 使用 IPC 进程通信
    win2d 图片水印
    win2d 图片水印
    Java实现 LeetCode 240 搜索二维矩阵 II(二)
    PHP closedir() 函数
    PHP chroot() 函数
    PHP chdir() 函数
  • 原文地址:https://www.cnblogs.com/vcerror/p/4289178.html
Copyright © 2011-2022 走看看