Windows 异常机制

异常的分类

(1)软件异常:由操作系统 / 应用程序 引发

用户:RaiseException -> RltRaiseException -> NtRaiseException -> KiRaiseException 
内核:RtlRaiseException -> NtRaiseException -> KiRaiseException

软件异常归根结底都是基于 RaiseException 这个用户态 API 和 NtRaiseException 的内核服务建立起来的。

void RaiseException(
	DWORD dwExceptionCode ,			// 异常状态码
	DWORD dwExceptionFlags,			// 异常标志(可持续 / 不可持续)
	DWORD nNumberofArguments,		// lpArguments[] 数组长度
	const DWORD* lpArguments		// 传递给异常处理程序的筛选表达式
);
// 具体作用就是将异常信息传递给 EXCEPTION_RECORD 这个结构,然后再调用 RtlRaiseException 函数
/*
	typedef struct _EXCEPTION_RECORD {
	  DWORD                     ExceptionCode;			// 异常状态码
	  DWORD                     ExceptionFlags;			// 异常标志
	  struct _EXCEPTION_RECORD  *ExceptionRecord;		// 指向下一个异常的指针
	  PVOID                     ExceptionAddress;		// 保存异常发生的地址
	  DWORD                     NumberParameters;		// ExceptionInformation[] 数组参数个数
	  ULONG_PTR                 ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];		// 异常描述信息
} EXCEPTION_RECORD;
*/

(2)硬件异常:由 CPU 引发

① 错误异常
处理错误异常时,操作系统首先保存当前环境(比如寄存器的值),然后调用相应的异常处理函数,如果异常处理成功则恢复现场环境继续执行。

② 陷阱异常
和错误异常不同,陷阱异常发生时会保存要执行的下一条指令的环境(而不是正在执行的指令的环境),调试器的断点就是基于陷阱异常实现的。

③ 终止异常
·主要用来处理严重的硬件错误,和上面的异常不同,这种异常不会恢复执行而是直接退出。

异常和中断的关系

中断可以再任何时候发生,和 CPU 正在执行什么指令无关,可以被取消。而异常是由于 CPU 执行了某条指令引起的,不能被取消。

异常的分发处理

异常产生时,CPU 会将控制权交给异常处理程序:
(1)内核可以处理这个异常
异常处理程序执行完后会恢复现场并继续执行,这个过程我们感知不到。
(2)内核不能处理这个异常
① 如果这个异常来自内核,蓝屏。
② 如果这个异常来自应用程序,则异常处理权转交给应用程序的异常处理函数。如果程序处理了异常则程序继续执行,如果没有处理这个异常则程序崩溃。

上面的 ② 有两种可能:应用程序被调试 / 没有被调试 ——

<1> 如果程序被调试,则异常处理权限转交给调试器(应用程序将调试信息包装成调试事件并发送给调试器,调试器使用 WaitForDebugEvent 获得该调试事件),调试器经过一系列操作后调用 ContinueDebugEvent 继续执行程序。如果调试器处理了这个异常则程序继续执行,如果没有处理则将异常处理权限转交给应用程序(也就是 <2> )。

<2> 如果程序没有被调试,则将异常信息连同线程上下文环境送入程序的栈中,并调用 KiUserExceptionDisptcher (由 ntdll 导出,所有的异常分发都会走这个函数)但KiUserExceptionDisptcher 实际不执行什么功能,实际发挥作用的是 RtlExceptionDisptcher,之后如果 RtlExceptionDisptcher 返回成功则调用 ZwContinue 继续执行,否则调用 ZwRaiseException 结束进程(如果正在被调试的话就把异常再次抛给调试器)。

但是如果调试器还是没有处理这个异常呢?(是个狠人)难道又要交给程序了吗(这样不就陷入一个死循环吗)?
但操作系统不傻,这时候操作系统会调用 ExceptionPort 通知 csrss.exe 弹出一个对话框:
Windows 异常机制_第1张图片csrss.exe 是 Windows 子系统的灵魂,它监管着系统内运行着的所有Windows进程和线程,每个进程在创建后都要到它这里注册登记后方能运行,退出时也要到此报告注销。除了掌管着各个进程的“生死存亡”,CSRSS在桌面管理、终端登录、控制台管理、HardError报告、和DOS虚拟机等方面也起着重要作用。如果尝试强行杀死CSRSS进程,系统便会蓝屏。

RtlExceptionDisptcher 的具体工作如下:

首先遍历 VEH 链表,逐个执行异常处理器,一旦某个处理器处理成功则返回成功,线程继续运行。如果 VEH 链表遍历完毕异常仍然没有被处理则遍历 SEH 链表,再逐个执行 SEH 的异常处理器一旦某个处理器处理成功则返回成功,线程继续运行。
如果 VEH 和 SEH 都没有处理这个异常,则进程结束。


硬件异常会通过IDT去调用异常处理例程(一般为KiTrap系列函数)。
软件异常则是通过API的层层调用传递异常的信息。

但无论是硬件异常还是软件异常,最后都会走到 KiDispatchException

VEH(向量化异常处理)

在每个进程的 ntdll.dll 中,有一张 VEH 链表,可以向其中添加一个节点来注册自己的异常处理器(通过调用 AddVectordExceptionHandler):

PVOID AddVectoredExceptionHandler(
  ULONG                       First,		// 0则把处理器放在链表尾,否则把处理器放在链表头
  PVECTORED_EXCEPTION_HANDLER Handler		// 自己定义的异常处理函数指针
);
// 注意:Handler 是进程级的,也就是说进程内的所有异常都会调用 Handler 指向的函数。

SEH(结构化异常处理)

SEH是基于线程的一种处理机制,依赖于栈进行存储和查找,所以也被称作是基于栈帧的异常处理机制。(所以就是说它只能处理自己线程的异常,而不是像 VEH 那样可以影响整个进程)

SEH 其实姐可以理解为高级语言中的 __try{} __except(){}(或 try , catch),它的作用就是构造一个 SEH 节点,并将该节点放入 SEH 链表头部。这个节点的结构如下:
在这里插入图片描述SEH 链表的表头指针永远都在 fs:[0] 这个位置。

Windows 异常机制_第2张图片
Windows 异常机制_第3张图片
Windows 异常机制_第4张图片
Windows 异常机制_第5张图片

UEF 和 VCH

Windows 异常机制_第6张图片

TopLevelEH(顶级异常处理)

本质上也是 SEH,在最顶层的SEH中,可以注册一个顶层异常处理器(和 SEH 不同的是它可以影响所有的线程)。

当 SEH 链表中的异常处理器都处理不了某个异常,在最顶层的SEH中就会检查是否注册了顶层异常处理,如果注册了顶级异常处理器,就会给 SEH “最后一次处理异常的机会” ,把异常抛给 TopLevelEH(但如果程序被调试时就会忽略 TopLevelEH)。

你可能感兴趣的:(基础知识)