本篇读书笔记主要参考自《深入解析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异常分发处理机制的更多相关文章

  1. Windows内核读书笔记——SEH结构化异常处理

    SEH是对windows系统中的异常分发和处理机制的总称,其实现分布在很多不同的模块中. SEH提供了终结处理和异常处理两种功能. 终结处理保证终结处理块中的程序一定会被执行 __try { //要保 ...

  2. 读书笔记|Windows 调试原理学习|持续更新

    关于调试方面的学习笔记,主要来源于<软件调试>的读书笔记和梦织未来论坛的视频教程 1.调试器使用一个死循环监听调试信息. DebugActiveProcess(PID);while(TRU ...

  3. Windows驱动——读书笔记《Windows驱动开发技术详解》

    =================================版权声明================================= 版权声明:原创文章 谢绝转载  请通过右侧公告中的“联系邮 ...

  4. 《android开发艺术探索》读书笔记(二)--IPC机制

    接上篇<android开发艺术探索>读书笔记(一) No1: 在android中使用多进程只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process ...

  5. 读书笔记——Windows核心编程(8)Interlocked系列函数

    先让我们来复习下小学知识 A+B=C//式中A为被加数,B为加数. A-B=C//式中A为被减数,B为减数. 再让我们来明确一个知识点:返回值为void的Windows函数意味着一定会执行成功. -- ...

  6. 读书笔记——Windows核心编程(13)Windows内存体系结构

    对于32位进程(0x0000 0000~0xFFFF FFFF),有4GB的地址空间. 每个进程都有自己专有的地址空间,当进程的各个线程运行时,它们只能访问属于该进程的内存. 这4GB其实是虚拟地址空 ...

  7. 读书笔记——Windows环境下32位汇编语言程序设计(13)关于EXCEPTION_DEBUG_INFO结构体

    在动手自己尝试编写书上第13章的例子Patch3时,遇到了一个结构体EXCEPTION_DEBUG_INFO. 这个结构体在MASM的windows.inc中的定义和MSDN中的定义不一样. (我使用 ...

  8. 读书笔记——Windows环境下32位汇编语言程序设计(9)ANSII字符大小写转大写

    在罗云彬的<Windows环境下32位汇编语言程序设计>中第321页 ... .const szAllowedChar db '0123456789ABCDEFabcdef',08h .. ...

  9. 读书笔记——Windows环境下32位汇编语言程序设计(5)模态对话框

    资源可以用VC之类的生成,然后拷贝出来. 例如:每一个MFC工程都有一个resource.h,没有做任何修改时,这个resource.h文件是原来自带的.当对资源进行过修改添加之类的时,新添加的资源的 ...

随机推荐

  1. 【bzoj2402】陶陶的难题II

    Portal -->bzoj2402 Solution 这题的话,看到答案的形式想到分数规划(Portal -->[learning]) 套路一波,记当前二分的\(mid\)为\(\lam ...

  2. centos 前端环境搭建

    Node.js 安装 wget 下载安装 yum -y install gcc make gcc-c++ openssl-devel wget node v6.11.0 下载 wget https:/ ...

  3. [机器学习]-Adaboost提升算法从原理到实践

    1.基本思想: 综合某些专家的判断,往往要比一个专家单独的判断要好.在”强可学习”和”弱可学习”的概念上来说就是我们通过对多个弱可学习的算法进行”组合提升或者说是强化”得到一个性能赶超强可学习算法的算 ...

  4. mysql 查询小demo

    两张表的的结构如下,需求是写出从one表到two表和从two表到one表的查询转换. create table student_one( name varchar(50) default '' not ...

  5. 垂直居中vertical-align

    vertical-align对一些特定显示样式(例如单元格显示方式:table-cell)的元素才会起作用. div{display:table-cell; vertical-align:middle ...

  6. 如何编写高质量的 jQuery 代码?

    想必大家对于jQuery这个最流行的javascript类库都不陌生,而且只要是前端开发人员肯定或多或少的使用或者接触过,在今天的这篇文章中,我们将介绍一些书写高质量jQuery代码的原则,我们不单单 ...

  7. 前端观察:HTML5做得好,收入不比 iOS 差

    谷歌奋进的绿色机器人超过了苹果的市场占有率,这一市场现象,导致了愈来愈多的开发者转投html5的怀抱.而HTML5将成为开发者拥抱同时拥抱Android和IOS的不二法门. 根据著名移动生态系统分析公 ...

  8. ASP.Net中自定义Http处理及应用之HttpModule篇

    HttpHandler实现了类似于ISAPI Extention的功能,他处理请求(Request)的信息和发送响应(Response).HttpHandler功能的实现通过实现IHttpHandle ...

  9. Mock InjectMocks ( @Mock 和 @InjectMocks )区别

    之前一直对这两个注解的区别不是很明白. 搜到过一篇博客园的文章举例说明了代码行为的区别.后来在stackoverflow上看到一个问答简单明了的解释了这两个注解在定义上的区别: 在此翻译记录一下: / ...

  10. LintCode 539: Move Zeroes

    终于下决心开始刷题了! 选择LintCode而不是LeetCode主要是因为LintCode操作更顺手,希望能够坚持下去. 还是循序渐进吧,数据结构和算法的东西很久没碰都很生疏了,先找找感觉. 这是一 ...