当有异常发生时,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公布了两个API,AddVectoredExceptionHandle和RemoveVectoredExceptionHandle来分别注册和注销回调函数VectoredHandle。

例如:

PVOID AddVectoredExceptionHandle(ULONG FirstHandle, PVECTORED_EXCEPTION_HANDLE   VectoredHandle)。

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

SEH与VEH区别和联系:

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

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

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

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

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

Windows异常分发的更多相关文章

  1. Windows内核读书笔记——Windows异常分发处理机制

    本篇读书笔记主要参考自<深入解析Windows操作系统>和<软件调试>这两本书. IDT是处理异常,实现操作系统与CPU的交互的关口. 系统在初始化阶段会去填写这个结构. ID ...

  2. Windows异常分发函数---KiUserExceptionDispatcher

    简介 KiUserExceptionDispatcher 是SEH分发器的用户模式的负责函数.当一个异常发生的时候,该异常将生成一个异常事件,内核检查该异常是否是由于执行用户模式代码导致的.如果是这样 ...

  3. Windows异常的分发处理流程

    根据异常来源,一般分硬件异常和软件异常,它们处理的流程大致一样,下面简单讲一下. 如果是硬件异常,CPU会根据中断类型号转而执行对应的中断处理程序.CPU会在IDT中查找对应的函数来处理,各个异常处理 ...

  4. 反调试——Windows异常-SEH

    反调试--Windows异常-SEH 概念: SEH:Structured Exception Handling SEH是Windows默认的异常处理机制 如何使用 在代码中使用 __try​​__e ...

  5. windows异常事件对应的ID

    转载地址: Windows 2008 R2查看异常关机或开机事件ID https://blog.csdn.net/hejun1218/article/details/81059327

  6. Windows异常

    一.什么是异常 异常指的是在程序运行过程中发生的异常事件,通常是由外部问题(如硬件错误.输入错误)所导致的.简单来说异常就是对于非预期状况的处理,当我们在运行某个程序时出现了异常状况,就会进入异常处理 ...

  7. windows异常调用顺序

    (一) 发生异常时系统的处理顺序(by Jeremy Gordon, Hume): 1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统     挂 ...

  8. windows异常演示,指定异常类型,然后生成异常

    #include "stdafx.h"#include <Windows.h>#include <float.h> DWORD Filter (LPEXCE ...

  9. Windows异常相关数据结构

    当一个异常发生时,操作系统要向引起异常的线程的栈里压入三个结构,这三个结构是:E X C E P T I O N _ R E C O R D结构.C O N T E X T结构和E X C E P T ...

随机推荐

  1. [atAGC047F]Rooks

    如果将$x$和$y$都离散,那么删除的点的$x_{i}$和$y_{i}$必然都组成了一个完整的区间(包括过程中) 将所有点按$x$排序,再令$f[i][j][0/1]$表示当删除完区间$[i,j]$且 ...

  2. shell常用集锦

    该文整理一些常用的shell用法,及语法,并非介绍如何使用 变量 变量可分为两类:环境变量ENV(全局)和局部变量. bash环境变量 变量名 含义 _= 上一条命令的最后一个参数 BASH_VERS ...

  3. Atcoder Grand Contest 001E - BBQ Hard(组合意义转化,思维题)

    Atcoder 题面传送门 & 洛谷题面传送门 Yet another 思维题-- 注意到此题 \(n\) 数据范围很大,但是 \(a_i,b_i\) 数据范围很小,这能给我们什么启发呢? 观 ...

  4. Pacbio三代基因组组装简介

    参考: 视频PPT来自欧易生物讲座:如何开启一个动植物基因组三代de novo项目?

  5. 自定义char类型字符,django中事务

    自定义char类型字符 # 自定义char类型,继承Field父类 class MyCharField(Field): def __init__(self, max_length, *args, ** ...

  6. 基于Kubernetes实现前后端应用的金丝雀发布

    基于Kubernetes实现前后端应用的金丝雀发布 公司的研发管理平台实现了Gitlab+Kubernetes的Devops,在ToB和ToC场景中,由于用户量大,且预发布环境和生产环境或多或少存在差 ...

  7. Qemu/kvm虚拟化源码解析学习视频资料

    地址链接:tao宝搜索:Linux云计算KVM Qemu虚拟化视频源码讲解+实践​https://item.taobao.com/item.htm?ft=t&id=646300730262 L ...

  8. Pytorch学习笔记08----优化器算法Optimizer详解(SGD、Adam)

    1.优化器算法简述 首先来看一下梯度下降最常见的三种变形 BGD,SGD,MBGD,这三种形式的区别就是取决于我们用多少数据来计算目标函数的梯度,这样的话自然就涉及到一个 trade-off,即参数更 ...

  9. 日常Java 2021/10/10

    多态就是同一个行为具有多个不同表现形式的能力 多态就是同一个接口,使用不同的实例而执行不同操作 多态的优点 1.消除类型之间的耦合关系 2.可替换性 3.可扩充性 4.接口性 5.灵活性 6.简化性 ...

  10. day05文件编辑命令

    day05文件编辑命令 mv命令:移动文件 mv命令:mv命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中. 格式:mv [原来的文件路径] [现在的文件路径] mv命令后面既可以跟 ...