Windows内核读书笔记——Windows异常分发处理机制
本篇读书笔记主要参考自《深入解析Windows操作系统》和《软件调试》这两本书。
IDT是处理异常,实现操作系统与CPU的交互的关口。
系统在初始化阶段会去填写这个结构。
IDT的每一个表项都成为门描述符,因为IDT的功能就像大门一样,从一个空间跳到另一个空间去执行。
IDT中包含三种门描述符
- 任务门描述符:用于任务切换
- 中断门描述符:用于描述中断处理例程
- 陷阱们描述符:用于描述异常处理例程
CPU如何使用IDT
cpu首先根据IDTR找到IDT,再利用向量号码找到门描述符。再去判断门描述符的类型,如果是任务描述符,CPU会执行硬件方式的任务切换,切换到描述符所定义的线程。
但是CPU的PCR结构中也会保存有IDT的基地址,这个用处我没搞懂,可能与内核初始化流程有关系,以后再来研究。
如果是陷阱或中断描述符,那么会去调用处理例程。X64架构不支持硬件方式的任务切换,也就不再存在任务门了。
在调用处理例程之前,CPU会把EFLAGS、CS、EIP压入栈,如果发生的异常有错误代码则把错误代码也压入堆栈。
这个过程就是所谓的构建陷阱帧(Context)和异常记录(EXCEPTION_RECORD)
windows系统使用EXCEPTION_RECORD结构描述异常。
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;
异常代码,可以认为是异常的别称,有的异常有异常代码有的则没有。
在经过中断或异常后会调用IDT中的处理例程比如KiTrap03,这个处理例程会调用CommonDispatchException函数。
CommonDispatchException函数会在栈中生成一个EXCEPTION_RECORD结构,把当前的数据情况写入到这个结构中。
然后会以这个结构为参数调用KiDispatchException来分发异常。
上面说的是硬件异常,接下来看看软件异常,软件异常是通过直接或间接调用内核函数NtRaiseException产生的,就是说软件异常不需要经过IDT分发等等这些东西只需要调用函数,而且用户层也可调用这个函数,用户层导出了一个RaiseException函数在kernel32.dll中,供用户产生一个自定义的异常。
RaiseException函数的实现原理是把相应的参数放入EXCEPTION_RECORD结构中,调用RtlRaiseException函数,这个函数把当前的线程上下文放入CONTEXT结构中,然后调用NtRaiseException。
NtRaiseException内部实现函数是KiRaiseException,我们看下面KiRaiseException的源码会发现这个函数还是调用 KiDispatchException来实现功能的。
//来自WRK1.2
NTSTATUS
KiRaiseException (
IN PEXCEPTION_RECORD ExceptionRecord,//异常记录
IN PCONTEXT ContextRecord,//线程上下文结构
IN PKEXCEPTION_FRAME ExceptionFrame,//不使用,为空
IN PKTRAP_FRAME TrapFrame,//栈帧基地址
IN BOOLEAN FirstChance//表示是第一轮还是第二轮处理
) {
//省略了部分内容
//把ContextRecord复制到当前线程的内核栈中
KeContextToKframes(TrapFrame,
ExceptionFrame,
ContextRecord,
ContextRecord->ContextFlags,
PreviousMode);
//把异常记录中的异常代码最高位清零,这样可以分辨出软件异常和CPU异常
ExceptionRecord->ExceptionCode &= ~KI_EXCEPTION_INTERNAL;
//调用分发异常的函数
KiDispatchException(ExceptionRecord,
ExceptionFrame,
TrapFrame,
PreviousMode,
FirstChance); return STATUS_SUCCESS;
}
所有说无论是CPU异常还是软件异常都会调用KiDispatchException来分发异常。
异常分发过程
在Windows内核方面的圣经《Windows Internals》中有一部分章节是介绍Windows系统异常分发机制的,其实那一章节的内容就是在讲述KiDispatchException函数的处理流程(虽然作者根本没有提到这个函数,因为Windows内核是不开源的,微软也没有提供手册)。
第一次看《Windows Internals》时,我还没有读过这个函数的实现。当时对书里的各种概念如二次调试、寻找调试器等等一脸懵逼。但是看了这个函数之后会发现真正的处理流程是很清晰的。
首先异常有两大类,发生在内核态下的异常和发生在用户态下的异常。为什么要分两种,这两者是不一样的。
if (PreviousMode == KernelMode)
{
//……
}
else
{
//……
}
函数一开始就把执行的流程完全的分成两部分了,可见内核异常与用户异常的分发是你走你的阳关道我走我的独木桥,两者是毫不关联的。
至于为什么要分为两个完全不同的流程,我想了一下用户态异常分发和内核态异常分发的相同点和不同点。
首先说说相同点:
1.都是基于二次调试机制
没有接触过异常分发的读者可能不明白什么叫二次调试机制。其实我们平时使用OD或是Windbg处理异常时就已经接触到二次调试机制了。比如,你用OD加载一个用户态程序,你就会接收到两次中断到调试器。其实这个是很常见的,尤其是以前玩CrackMe的时候经常可以碰到用触发异常来进行反调试的。这个时候如果你使用OD来忽略异常要连续忽略两次,这就是二次调试。
下面来做一个实验。
很简单的一个程序
#include "stdio.h" int main()
{
int num = / ;
printf("%d", num);
return ;
}
触发一个除零异常

VS2015编译不了这个,因为把除零视为错误了,不知道该怎么关这个保护机制。于是用C-Free 5编译了一下。运行后就是标准的Windows异常窗口,问你要不要挂载调试器啊什么的,这个选项是可以在注册表里面改的,下面也会研究一下这个标准的Windows异常窗口是哪里来的。这里先回归主题说一下二次调试的事情。
OD设置不忽略所有的异常,注意有SrongOD插件的要把插件关了。这样OD就不会忽略异常了,如果你用shift+F8来跳过这个异常,你会发现你要按两次shift+F8这个就是异常的二次调试。其实用Windbg可以更直观的看到二次调试的现象,用Windbg加载这个示例程序,按G继续,可以看到如图的情况

这个就是二次调试。正如我们所说的,这是内核态异常分发和用户态异常分发的共同点,两者都会有二次调试。我个人认为这是一种保护机制,是一种“再给一次机会”的机制,就算没有这个二次调试机制也是无关大局的。
2.都在寻找调试器
我们知道异常是无法被“自动”处理的,举个例子,以上面那个例子程序来说,如果你不是手动指定异常处理代码(如__try)的话,那么异常是不可能自己消失的。就是说系统不会自己改错,当然也有例外的情况,比如说缺页异常就是自己处理的,应该是在中断处理例程中就处理完毕了,不会进行异常分发。但是那个是系统故意留的,根本不能称作是真正意义上的异常。
那么,所谓的异常分发就是一个寻找调试器和异常处理代码的过程。这个才是本质,也是分发的目的,找不到就进行暴力结束机制。因为不可能带着错误继续运行下去。
相同点说完了,那么重点就是不同点了。
不同点:
1.CPU模式的切换
我们前面说了,无论是在用户态触发异常还是在内核态触发异常都会调用到这个KiDispatchException函数,就是说当用户态触发异常后需要转入内核态。这好像是一句废话,因为很多函数都是要从用户态转入内核态的啊。比如用户层的CreateFile就会到内核层的NtCreateFile函数。但是对于用户态异常分发来说却不是这么简单,因为要执行用户态的用户指定的异常处理代码,所以进入这个KiDispatchException函数后还要再转回用户态去寻找用户态的异常处理代码。而对于内核异常分发来说是不需要CPU执行状态转来转去。
2.寻找调试器的函数不同
一个是寻找内核调试器的,一个是寻找用户态调试器的。这两者本质不同。
这个就是一个完整的流程图,来自《软件调试》

Windows内核读书笔记——Windows异常分发处理机制的更多相关文章
- Windows内核读书笔记——SEH结构化异常处理
SEH是对windows系统中的异常分发和处理机制的总称,其实现分布在很多不同的模块中. SEH提供了终结处理和异常处理两种功能. 终结处理保证终结处理块中的程序一定会被执行 __try { //要保 ...
- 读书笔记|Windows 调试原理学习|持续更新
关于调试方面的学习笔记,主要来源于<软件调试>的读书笔记和梦织未来论坛的视频教程 1.调试器使用一个死循环监听调试信息. DebugActiveProcess(PID);while(TRU ...
- Windows驱动——读书笔记《Windows驱动开发技术详解》
=================================版权声明================================= 版权声明:原创文章 谢绝转载 请通过右侧公告中的“联系邮 ...
- 《android开发艺术探索》读书笔记(二)--IPC机制
接上篇<android开发艺术探索>读书笔记(一) No1: 在android中使用多进程只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process ...
- 读书笔记——Windows核心编程(8)Interlocked系列函数
先让我们来复习下小学知识 A+B=C//式中A为被加数,B为加数. A-B=C//式中A为被减数,B为减数. 再让我们来明确一个知识点:返回值为void的Windows函数意味着一定会执行成功. -- ...
- 读书笔记——Windows核心编程(13)Windows内存体系结构
对于32位进程(0x0000 0000~0xFFFF FFFF),有4GB的地址空间. 每个进程都有自己专有的地址空间,当进程的各个线程运行时,它们只能访问属于该进程的内存. 这4GB其实是虚拟地址空 ...
- 读书笔记——Windows环境下32位汇编语言程序设计(13)关于EXCEPTION_DEBUG_INFO结构体
在动手自己尝试编写书上第13章的例子Patch3时,遇到了一个结构体EXCEPTION_DEBUG_INFO. 这个结构体在MASM的windows.inc中的定义和MSDN中的定义不一样. (我使用 ...
- 读书笔记——Windows环境下32位汇编语言程序设计(9)ANSII字符大小写转大写
在罗云彬的<Windows环境下32位汇编语言程序设计>中第321页 ... .const szAllowedChar db '0123456789ABCDEFabcdef',08h .. ...
- 读书笔记——Windows环境下32位汇编语言程序设计(5)模态对话框
资源可以用VC之类的生成,然后拷贝出来. 例如:每一个MFC工程都有一个resource.h,没有做任何修改时,这个resource.h文件是原来自带的.当对资源进行过修改添加之类的时,新添加的资源的 ...
随机推荐
- 20165218 实验二 Java面向对象程序设计
实验二 Java面向对象程序设计 课程:java程序设计 姓名:赵冰雨 学号:20165218 指导教师:娄嘉鹏 实验日期:2018.4.16 实验密级:Java开发环境的熟悉 实验内容.步骤与体会: ...
- mysql数据库----下载安装、操作
一.mysql概述 1.什么是数据库 ? 答:数据的仓库,如:在ATM的示例中我们创建了一个 db 目录,称其为数据库 2.什么是 MySQL.Oracle.SQLite.Access.MS SQL ...
- Codeforces 932.F Escape Through Leaf
F. Escape Through Leaf time limit per test 3 seconds memory limit per test 256 megabytes input stand ...
- EA画时序图初试
1.步骤: 1. 新建一个项目: 2. Use Case Model右键-->添加图-->左边选择UML Behavioral,右边选择Sequence: 3. 选择工具栏中的工具,点击工 ...
- 《提升c++性能的编程技术》读书笔记
http://note.youdao.com/noteshare?id=9ab0eda264c85b774021426867e18eae
- mysql 如何解决字段不区分大小写的问题
当我们输入不管大小写都能查询到数据,例如:输入 aaa 或者aaA ,AAA都能查询同样的结果,说明查询条件对大小写不敏感. 解决方案一: 于是怀疑Mysql的问题.做个实验:直接使用客户端用sql查 ...
- duilib 修复CTreeViewUI控件动态添加子控件时,对是否显示判断不足的bug
转载请说明出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/42264947 这个bug我在仿酷狗开发日志里提到过,不过后来发现修复的不够 ...
- OpenCV---图像加载与保存
一:获取图像的信息 什么是图像: 结构化存储的数据信息 图像属性: -通道数目 -高与宽 -像素数据 -位图深度 import cv2 as cv def get_image_info(image): ...
- 维护后面的position sg函数概念,离线+线段 bzoj 3339
3339: Rmq Problem Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 1160 Solved: 596[Submit][Status][ ...
- Java 里快如闪电的线程间通讯
这个故事源自一个很简单的想法:创建一个对开发人员友好的.简单轻量的线程间通讯框架,完全不用锁.同步器.信号量.等待和通知,在Java里开发一个轻量.无锁的线程内通讯框架:并且也没有队列.消息.事件或任 ...