本篇读书笔记主要参考自《深入解析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. [ZJOI2011]细胞——斐波那契数列+矩阵加速+dp

    Description bzoj2323 Solution 题目看起来非常复杂. 本质不同的细胞这个条件显然太啰嗦, 是否有些可以挖掘的性质? 1.发现,只要第一次分裂不同,那么互相之间一定是不同的( ...

  2. dorado事件

    //----------------------------------------------------------------------------// //校验实体数据是否填写if(enti ...

  3. NYOJ--139

    原题链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=139 分析:cantor展开,康托展开用于计算一个排列的序数.公式为: X=a[n]*n! ...

  4. array_diff、array_diff_key、array_diff_ukey、array_diff_assoc、array_diff_uassoc 的用法

    <?php // array_diff* 系列的函数都返回关联数组// array_diff* 系列函数返回数组的差集(返回在第一个参数中, 但不在其他参数中的元素) $array1 = [ ' ...

  5. java-压缩文件成zip文件(多文件/单文件/多目录/单目录/无目录),用于下载

    本博客是自己在学习和工作途中的积累与总结,仅供自己参考,也欢迎大家转载,转载时请注明出处. http://www.cnblogs.com/king-xg/p/6424788.html 上代码: pac ...

  6. [实战篇]Tomcat发布项目-虚拟目录

    在二阶段学习的过程中,我一直使用MyEclipse的方式把工作空间的项目发布到webapps目录下,这种方式自我感觉在实际开发中应用能在70%左右,但是如何涉及到一些上传操作等操作, 从新发布项目之后 ...

  7. base64解码

    网络传输经常用base64编码的数据,因此我们需要将其解码成正常字符集合. base64.h #ifdef __cplusplus extern "C" { #endif char ...

  8. python requests https 访问出错

    错误提示: /usr/local/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:100: InsecurePla ...

  9. MongoDB - MongoDB CRUD Operations, Insert Documents

    MongoDB provides the following methods for inserting documents into a collection: db.collection.inse ...

  10. [php]几个常用函数

    count(arr);用于统计数组的元素个数 is_array(arr);判断给定变量是不是数组 var_dump(var||arr);打印数组或变量信息(类型和值): print_r(var||ar ...