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文件是原来自带的.当对资源进行过修改添加之类的时,新添加的资源的 ...
随机推荐
- max os x lighttpd + php + mysql 部署
手贱,升级了max os x 到Yosemite,系统默认装了nginx,php,开机会自动启动!1 开机启动脚本默认在下面位置: /Library/LaunchDaemons/com.root.ng ...
- C之面向对象编程20170707
语言只是工具,设计思维才是根本.C虽然是面向过程的语言,但也是可以实现面向对象编程的,本文就是介绍如何使用C语言实现面向对象编程. 我们知道面向对象主要有三大特性:封装,继承,和多态,下面就从这个三个 ...
- ural 1297 后缀数组 最长回文子串
https://vjudge.net/problem/URAL-1297 题意: 给出一个字符串求最长回文子串 代码: //论文题,把字符串反过来复制一遍到后边,中间用一个没出现的字符隔开,然后就是枚 ...
- 6.UiWatcher API 详细介绍
Tip: 1.监听器不是完能的,所以若用例需要设置监听器防止用例被打断,最好把延迟时间调高一点 2.UiDevice是不会触发监听功能的 3.监听器在方法体或者循环体中是程序还是会被打断的 4.监听器 ...
- 微信小程序语音识别
语音识别现在已经发展的很成熟了,经过比对发现百度对开发者比较友好,提供很多种语言的SDK,对python来说直接安装 pip install baidu-aip 文档写的也不错 具体参考:http: ...
- 58到家mysql数据库军规及解读分享
一.基础规范 (1)必须使用InnoDB存储引擎 解读:支持事务.行级锁.并发性能更好.CPU及内存缓存页优化使得资源利用率更高 (2)必须使用UTF8字符集 解读:万国码,无需转码,无乱码风险,节省 ...
- 主席树 或者 离散化+分块 BZOJ 4636
4636: 蒟蒻的数列 Time Limit: 30 Sec Memory Limit: 256 MBSubmit: 381 Solved: 177[Submit][Status][Discuss ...
- Codeforces Round #191 (Div. 2) B. Hungry Sequence(素数筛选法)
. Hungry Sequence time limit per test 1 second memory limit per test 256 megabytes input standard in ...
- sublime text3 编辑器常用快捷键
选择类 Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本. Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同文本进行同时编辑.举个栗子:快速选中并更改所有相同的变量名.函数 ...
- 【CodeForces】679 B. Bear and Tower of Cubes
[题目]B. Bear and Tower of Cubes [题意]有若干积木体积为1^3,2^3,...k^3,对于一个总体积X要求每次贪心地取<=X的最大积木拼上去(每个只能取一次)最后总 ...