windows异常处理机制

  • 格式:doc
  • 大小:190.50 KB
  • 文档页数:8

下载文档原格式

  / 8
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

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。该函数比较重要,所以将他的逻辑还原如下(伪代码)