浅析Windows异常处理结构与实现
Windows异常处理是操作系统处理程序错误的一种手段,一般有两种:SHE(结构化异常处理)和VEH(向量化异常处理),而UEF(TopLevalEH顶层异常处理)是基于SHE的(这之后应该还有一种VCH),下面我就从上面三方面分析一下Windows异常处理结构。
一、异常处理的个人理解简述
当正在运行一个程序产生了一个异常后,就会利用自身的异常处理来处理这个异常,这个过程将是VEH优先于SEH的,而如果存在调试器,就会先交给调试器处理(使用shift+F7/F8/F9会把异常交还程序),若调试器不能处理再交还给程序,若仍不能处理将再交给调试器,进行第2 次处理的机会,有无调试器的存在会影响UEF的使用,这个顶层异常处理在有调试器的情况下是不会被调用的,所以我们大概能把处理过程分为:
第一次交给调试器(进程必须被调试)->VEH->SHE->UEF(TopLevelEH 进程被调试时不会被执行)->第二次交给调试器(进程必须被调试)-> 调用异常端口通知csrss.exe
在这里列举一下常见的系统定义的异常:
异常 | 值 | 描述 |
---|---|---|
EXCEPTION_ACCESS_VIOLATION | 0xC0000005 | 程序企图读写一个不可访问的地址时引发的异常。例如企图读取0地址处的内存。 |
EXCEPTION_BREAKPOINT | 0x80000003 | 触发断点时引发的异常。 |
EXCEPTION_ILLEGAL_INSTRUCTION | 0xC000001D | 程序企图执行一个无效的指令时引发该异常。 |
EXCEPTION_INT_DIVIDE_BY_ZERO | 0xC0000094 | 整数除法的除数是0时引发该异常。 |
EXCEPTION_SINGLE_STEP | 0x80000004 | 标志寄存器的TF位为1时,每执行一条指令就会引发该异常。主要用于单步调试。 |
(VEH为什么优先于SEH呐?可以从二者的作用范围出发,SEH是基于线程的(由线程创建),而VEH则是基于进程的(由进程创建)。由此来看,SHE只能处理自身线程的异常,VEH却能处理整个进程的异常,所以每当异常产生时都会先调用VEH,后调用SHE。
二、从R3出发
说起异常的分发,那么这里故事线就稍稍有一点儿长了。因为Windows分为内核(R0)和应用层(R3),Windows的异常处理机制是内核和应用层都可以使用的。所以这里就需要进行一些区分了。因为如果内核产生了异常,那么自然内核的异常处理函数也在内核里面那么这个事情就好办了,我出现异常了我直接处理就好了。但是应用层如果出现了异常,那么一定他的处理函数是在应用层的,当产生异常的时候,异常的分发总是在内核,那么如果要处理应用层的异常,那么需要先切换到应用层然后才能去执行应用层的异常分发处理,我就只分析从R3出发的结构(太菜)。
在进入异常处理的时候,栈中会放置EXCEPTION_RECORD(异常结构信息)和CONTEXT(线程上下文)两个结构
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;
在R3中的起始位置为ntdll.dll中的KiUserExceptionDispatcher函数,由它调用ntdll!RtlDispatchException函数进行用户态的异常处理。
随后,进入VEH和SHE两种异常处理机制,其中包含SHE的顶级异常处理,但有调试器是不进行。
- 如果RtlDispatchException找到异常处理例程能够处理异常,则调用ZwContinue按照设置好的Context结构继续执行;
- 如果在调试异常时未能处理,调用ZwRaiseExcept