• Windows异常分发


    当有异常发生时,CPU会通过IDT表找到异常处理函数,即内核中的KiTrapXX系列函数,然后转去执行。但是,KiTrapXX函数通常只是对异常做简单的表征和描述,为了支持调试和软件自己定义的异常处理函数,系统需要将异常分发给调试器或应用程序的处理函数。

    为了更好的管理异常,Windows系统定义了专门的数据结构EXCEPTION_RECORD来描述异常。

    typedef struct _EXCEPTION_RECORD {
      DWORD                    ExceptionCode;
      DWORD                    ExceptionFlags;
      struct _EXCEPTION_RECORD  *ExceptionRecord;
      PVOID                    ExceptionAddress;
      DWORD                    NumberParameters;
      ULONG_PTR                ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
    } EXCEPTION_RECORD, *PEXCEPTION_RECORD;

    ExceptionCode:异常代码,32位整数。

    ExceptionFlags:用来记录异常标志,它的每一位代表一种标志。

    ExceptionRecord:用来指向与该异常有关的另一个异常记录。

    ExceptionAddress;用来记录异常地址,错误类异常与陷阱类异常会有区别。

    NumberParameters:附加参数个数,即ExceptionInformation数组的有效个数。

    登记CPU异常

    对于CPU异常,KiTrapXX例程在完成针对本异常的特别动作后,通常会调用CommonDispatchException函数,它会在栈中分配一个EXCEPTION_RECORD结构,并把异常信息存储到该结构中。在准备好这个结构后,它会调用内核中的KiDispatchExcption函数来分发异常。

    登录软件异常 

    简单来捉,软件异常是通过直接或间接调用内核服务KiRaiseException而产生的。函数内部会把Context上下背景文复制到当前线程的内核栈,接下来调用KiDispatchExcption函数来进行分发。 

    综上所述,不管什么异常最后都会调用内核中的KiDispatchExcption函数进行分发,也就是说Windows用统一的方式来管理异常。

    VOID
    KiDispatchException (
        IN PEXCEPTION_RECORD ExceptionRecord,
        IN PKEXCEPTION_FRAME ExceptionFrame,
        IN PKTRAP_FRAME TrapFrame,
        IN KPROCESSOR_MODE PreviousMode,
        IN BOOLEAN FirstChance
        )

    ExceptionRecord:用来描述要分发的异常。

    ExceptionFrame:指向的KTRAP_FRAME结构,用来描述异常发生时的处理器状态,包括各种通用寄存器、调试寄存器、段寄存器等。

    PreviousMode:枚举,用来表示前一种状态是内核模式还是用户模式。

    FirstChance:表示第几轮分发。

    下面先来看看KiDispatchException 分发示意图

    从图中我们可以看到,KiDispatchException会先调用KeContextFromKframes函数,目的是根据TrapFrame参数指向的KTRAP_FRAME结构产生一个CONTEXT结构,以供向调试器和异常处理器函数报告异常时使用。

    接下来会根据模式是内核模式还是用户模式进行分发。下面具体说明。

    内核态异常的分发过程

    对于第一轮异常KiDispatchException会试图先通知内核调试器来处理异常,如果没有处理异常,那么会调用RtlDispatchExcption,试图寻找已经注册的结构化异常处理器(SEH)。

    如果也没有找到,那么就会给内核调试器第二次处理的机会。仍然返回FLASE的话,就会调用KeBugCheckEx触发蓝屏。

    用户态异常的分发过程

    首先,KiDispatchException会判断是否发送给内核调试器,但内核调试器通常不处理用户态异常,所以KiDispatchException会试图发送给用户态调试器,方法是调用DbgkForwardException。如果不成功,KiDispatchException下一步动作是试图寻找异常处理块来处理该异常,因为用户异常发生在用户态代码中,异常处理块也是在用户态代码中。所以需要转到用户态去执行。(这也就是相对于内核态异常的分发过程,用户态异常的分发过程会麻烦一点的原因,具体方式不再累赘,参考《软件调试》)如果最终也返回FALSE,那么就会分发第二轮。

    结构化异常处理SEH

    为了让系统和应用程序代码都可以简单方便地支持异常处理,Windows定义了一套标准的机制来处理代码的设计和编译,这套机制被称为结构化异常处理(Structured Exception Handling),简称SEH

    异常处理结构如下:

    __try
    {
    //被保护块
    }
    __except(过滤表达式)
    {
    //异常处理块
    }

    通过TEB结构的NtTib成员可以很容易的访问进程的SEH链,方法很简单。

    TEB.NtTib.ExceptionList成员是TEB结构体的第一个成员。FS段寄存器指向段内存的起始地址,TEB结构体即位于此,所以通过下列公式可以轻松获取TEB.NtTib.ExceptionList的地址。

    TEB.NtTib.ExceptionList = FS:[0]

    那么那汇编语言实现的话:

    PUSH  @Handler

    PUSH  DWORD  PTR FS:[0]

    MOV   DWORD  PTR FS:[0], ESP

    向量化异常处理VEH

    WindowsXP开始,Windows还支持一种名为向量化异常处理的异常处理机制,简称VEH

    SEH既可以在用户态又可以在内核态不同,VEH只能在用户态程序中。

    VEH的基本思想是通过注册一下的原型的回调函数来接收和处理异常。

    LONG CALLBACK VectoredHandle(PEXCEPTION_POINTERS  ExceptionInfo);

    相应的,Windows公布了两个APIAddVectoredExceptionHandle和RemoveVectoredExceptionHandle来分别注册和注销回调函数VectoredHandle。

    例如:

    PVOID AddVectoredExceptionHandle(ULONG FirstHandle, PVECTORED_EXCEPTION_HANDLE   VectoredHandle)。

    参数FirstHandle代表该函数被调用的顺序,0表示希望最后调用, 1表示希望最先调用。如果注册了多个回调函数,而且FirstHandle都是非零,那么最后注册的最先被调用。

    SEHVEH区别和联系:

    从应用范围:SEH可以在用户态代码中,也可以用在内核态代码中,但是VEH只能用在用户态代码中。

    从优先角度:对于同时注册了SEHVEH的代码所触发的异常,VEHSEH先得到处理权。

    从登记方式:SEH注册信息是固定结构存储在线程栈中,VEH的注册信息是存储在进程的内存堆中。

    从作用域: VEH对整个进程都有效,具有全局性。SEH是动态建立在所在函数函数栈上的,会随函数返回而销毁。

    从编译角度:SEH的登记和注销是依赖编译器编译时所产生的数据结构和代码的,VEH的注册和注销都是通过系统调用的API显示染成的,不需要经过编译器的特殊处理。

  • 相关阅读:
    docker 学习操作记录 4
    docker 学习操作记录 1
    centos 旧版本镜像
    zabbix 搭建 mysql 连接报错
    js判断是否为手机或是pc
    滚动翻页vue
    note
    Vue-cli 鼠标监听事件之滚动条
    adobe Keychain mac
    HTML协义代码
  • 原文地址:https://www.cnblogs.com/zibility/p/5689939.html
Copyright © 2020-2023  润新知