Windows SEH学习 x86
windows 提供的异常处理机制实际上只是一个简单的框架。咱通常所用的异常处理(比如 C++ 的 throw、try、catch)都是编译器在系统提供的异常处理机制上进行加工了的增强版本。这里先抛开增强版的不提,先说原始版本。
原始版本的机制很简单:谁都可以触发异常,谁都可以处理异常(只要它能看得见)。但是不管是触发还是处理都得先注册。系统把这些注册信息保存在一个链表里,并且这个链表保存在线程的数据结构里。也就是说,异常所涉及的一些行为都是线程相关的。比如,线程 T1 触发的异常就只能由线程 T1 来处理,其他线程根本就不知道 T1 发生了什么事,更不会处理。等注册完毕后,线程就可以抛出或处理异常了,系统也可以做相应的管理工作了。
系统提供的管理工作简单来说包括(但不限于):找到触发异常的线程的异常处理链表(前面注册的那个),然后按照规则对该异常进行分发,根据分发后的处理结果再进行下一步的分发或者结束处理。
系统管理所使用的数据结构:
#define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD * POINTER_32)-1) typedef enum _EXCEPTION_DISPOSITION { ExceptionContinueExecution, ExceptionContinueSearch, ExceptionNestedException, ExceptionCollidedUnwind } EXCEPTION_DISPOSITION; typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD; typedef EXCEPTION_RECORD *PEXCEPTION_RECORD; typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) ( IN struct _EXCEPTION_RECORD *ExceptionRecord, IN PVOID EstablisherFrame, IN OUT struct _CONTEXT *ContextRecord, IN OUT PVOID DispatcherContext ); typedef struct _EXCEPTION_REGISTRATION_RECORD { //指向下一个 EXCEPTION_REGISTRATION_RECORD,由此构成一个异常注册信息链表。 //链表中的最后一个结点会将 Next 置为 EXCEPTION_CHAIN_END,表示链表到此结束。 struct _EXCEPTION_REGISTRATION_RECORD *Next; PEXCEPTION_ROUTINE Handler; //指向异常处理函数 } EXCEPTION_REGISTRATION_RECORD; typedef EXCEPTION_REGISTRATION_RECORD *PEXCEPTION_REGISTRATION_RECORD;
当接收到异常后,系统找到当前线程的异常链表,从链表中的第一个结点开始遍历,找到一个 EXCEPTION_REGISTRATION_RECORD 就调用它的 Handler,并把该异常(类型为 EXCEPTION_RECORD 的参数)表示传递给该 Handler,Handler 处理并返回一个类型为 EXCEPTION_DISPOSITION 的枚举值。该返回值指示系统下一步该做什么:
ExceptionContinueExecution 表示:已经处理了异常,回到异常触发点继续执行。
ExceptionContinueSearch 表示:没有处理异常,继续遍历异常链表。
ExceptionCollidedUnwind 表示在展开过程中再次触发异常。
ExceptionNestedException这里先不做解释
这样系统根据不同的返回值来继续遍历异常链表或者回到触发点继续执行。
内核模式异常处理:
首先,CPU 执行的指令触发了异常,CPU 改执行 IDT 中 KiTrap??,KiTrap?? 会调用 KiDispatchException。
该函数原型如下: 功能如名字一样,分发异常
VOID KiDispatchException ( IN PEXCEPTION_RECORD ExceptionRecord, IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame, IN KPROCESSOR_MODE PreviousMode, IN BOOLEAN FirstChance );
View CodeWrk中关于KiDispatchException的源代码在后面贴出,基本流程就是:
检查 ExceptionRecord->ExceptionCode,
如果是 STATUS_BREAKPOINT,那么将 CONTEXT::Eip 减一;
如果是 KI_EXCEPTION_ACCESS_VIOLATION,那么将检查是否是由 AtlThunk 触发(这个小环节没有深究),
如果是触发 NX(不可执行),那么将 ExceptionRecord->ExceptionInformation [0] 置为 0(貌似表示触发操作的类型,0表示读、1表示写),MSDN有详细解释,推荐阅读
如果 PreviousMode 是 KernelMode,
那么如果 FirstChance 为 TRUE,那么将该异常传达给内核调试器,如果内核调试器没有处理,那么调用 RtlDispatchException 进行处理。
如果 FirstChance 为 FALSE,那么再次将该异常传达给内核调试器,如果内核调试器没有处理,那么 BUGCHECK。
如果 PreviousMode 是 UserMode,
那么,如果 FirstChance 为 TRUE,那么将该异常传达给内核调试器,如果内核调试器没有处理,那么将异常传达给应用层调试器。
如果仍然没有处理,那么将 KTRAP_FRAME 和 EXCEPTION_RECORD 拷贝到 UserMode 的栈中,并设置 KTRAP_FRAME::Eip 设置为 ntdll!KiUserExceptionDispatcher,返回(将该异常交由应用层异常处理程序进行处理)。
如果 FirstChance 为 FALSE,那么再次将异常传达给应用层调试器,如果仍然没有处理,那么调用 ZwTerminateProcess 结束进程,并 BUGCHECK。
温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/69568.html