windows异常处理机制
- 格式:doc
- 大小:190.50 KB
- 文档页数:8
Windows异常处理机制
在windows操作系统下,异常处理机制与调试机制息息相关。理解异常处理机制对于
程序的调试有很大帮助。下文将尝试从异常的产生,异常的分发,异常的处理几个角度进行
探讨,辅以多种异常处理的示例代码进行分析,以期学习更加灵活的调试技巧。
异常的产生
异常和中断非常相似但又有区别。中断可在任何时候发生,与CPU正在执行什么指令
无关,中断主要由I/O设备、处理器时钟或定时器等硬件引发,可以被允许或取消。而异常
是由于CPU执行了某些指令引起的,可以包括存储器存取违规、除0或者特定调试指令等,
内核也将系统服务视为异常。
异常发生时,cpu无法继续运行。此时它将当前线程状态保存,并从中断向量表中根据
异常原因取出对应的异常处理入口,将控制权交给异常处理程序。下表为中断向量表中的异
常原因及对应intel保留的中断号
名字原因
中断
号
0x0 除法错误1、DIV和IDIV指令除0.2、除法结果溢出
0x1 调试陷阱1、EFLAG的TF位置位2、执行到调试寄存器(DR0-DR4)设置的断点3、执行INT1指令
0x2 NMI中断将CPU的NMI输入引脚置位(该异常为硬件发生非屏蔽中断而保留)
0x3 断点执行INT3指令
0x4 整数溢出执行INTO指令且OF位置位
BOUND指令比较的值在给定范围外
0x5 BOUND边界检查
错误
0x6 无效操作码指令无法识别
0x7 协处理器不可用1、CR0的EM位置位时执行任何协处理器指令2、协处理器工作时执行了环境切换
0x8 双重异常处理异常时发生另一个异常
0x9 协处理器段超限浮点指令引用内存超过段尾
0xA 无效任务段任务段包含的描述符无效(windows不使用TSS进行环境切换,所以发生该异常说明有其它问题)
0xB 段不存在被引用的段被换出内存
0xC 堆栈错误1、被引用内存超出堆栈段限制2、加载入SS寄存器的描述符的present位置0
0xD 一般保护性错误所有其它异常处理例程无法处理的异常
0xE 页面错误1、访问的地址未被换入内存2、访问操作违反页保护规则
0x10 协处理器出错CR0的EM位置位时执行WAIT或ESCape指令
0x11 对齐检查错误对齐检查开启时(EFLAG对齐位置位)访问未对齐数据
上述异常中,从应用层角度看最为常见的有
1,除零异常,中断号0x0,通常原因为div指令被除数为0
2,调试陷阱,中断号0x1,产生此异常原因通常为单步执行(od的f7,windbg的f11)或硬件断点(windbg的ba指令下的断点产生的异常即为此异常)
3,断点(常称为软断点或cc断点),中断号0x3,此异常用来调试(od的f2,windbg 的bp、bu指令均使用此异常),触发此异常的方法为在应用层直接执行int3指令。
4,页面错误,中断号0xe,产生此异常原因有二。一是访问的线性地址是合法的但页面被换出,在这种情况下,异常处理程序会将磁盘里的内容交换至物理内存,并继续执行。第一种情况对应用层而言是完全透明的。第二种情况为访问违例,如访问一个不存在的线性地址,或违反页保护规则。
异常的分发
异常产生时cpu控制权交给异常处理程序之后,分两种情况。一是os内核可以处理该异常,比如缺页中断,前文已经提到,内核将会直接将磁盘中数据换入物理内存,然后iret 返回异常发生之前的位置,此时线程是完全感觉不到发生了什么。Linux下的fork新任务共享内存写时复制即利用此异常实现。
另一种是内核无法处理该异常。此时如果此异常来自内核,则直接蓝屏。如果此异常来自用户层,根据不同情况将该异常发送给用户层。
上文所谓的不同情况实际上有两种可能,一为进程被调试,二为进程未被调试。被调试进程处理流程如下
流程1
1,检测发生异常的进程是否被调试,发现调试器
2,将异常信息包装为DEBUG_EVENT(调试事件),发送给调试器进程
3,调试器通过WaitForDebugEvent获得该调试事件
4,调试器通过ContinueDebugEvent告知操作系统继续运行,并告知是否已处理该异常5,如果调试器已处理该异常,则被调试线程继续运行,如果未处理,则进入流程2一个典型的调试器调试循环逻辑如下
流程2
1,发生异常进程未被调试,则将异常信息包装为一个Exception Record,连同线程上下文环境Context压至用户态栈。
2,在内核态栈上构造一个陷阱帧(TrapFrame),陷阱帧eip为用户态函数KiUserExceptionDisptcher ,然后iret返回用户态。简言之,相当于调用了一次用户态函数(位于ntdll.dll中)KiUserExceptionDisptcher(ExceptionRecord, ThreadContext).
3,故名思义,KiUserExceptionDispatcher为用户态异常分发器。他的工作流程如下
4,如上图,KiUserExceptionDispatcher将异常记录和线程环境原封不动的发给RtlDispatchException进行实际异常分发。如果返回值非零(处理成功),则调用系统调用ZwContinue返回到异常发生处继续执行。如果返回值为0(处理失败),则调用系统调用ZwRaiseException告知操作系统此进程已无法继续。
5,由4可知,用户态异常分发最核心的函数实际上为RtlDispatchException。该函数比较重要,所以将他的逻辑还原如下(伪代码)