——————————————————————————————————————————————————————————————————————————

前一篇指出 tail_recursivef_factorial() 会递归调用自身来计算某个正整数的阶乘。当要计算的目标数值过大,经历多次调用后,

就会耗尽可用的内核栈,引发一次页错误异常,而转移控制到错误处理程序前再次向无效的内存地址压入“陷阱帧”则会让原本可

以处理的异常升级为“double fault”,致使系统崩溃。本篇通过试图计算 685! 来触发“double fault”并进行分析。

将编译好的驱动拷贝到被调试机器上,利用 sc.exe 把它加载至内核空间,源码中(参见上一篇)设置的初始断点被激活从而断入

调试机上的 WinDbg.exe,观察驱动入口点“DriverEntry()”内的局部变量,其中“Number”的值 0x2ad 正是要计算阶乘的数

685:

按下“g”键恢复执行,没多久就让系统崩溃了,这在我们的意料之中,如果没有连接宿主机上的调试器,目标系统就会直接

蓝屏,并且显示“bug check”代码——0000007F:

在 MSDN 网站上搜索该错误码,它对应于“UNEXPECTED_KERNEL_MODE_TRAP”,官方给出的解释如下:

The UNEXPECTED_KERNEL_MODE_TRAP bug check has a value of 0x0000007F.
This bug check indicates that the Intel CPU generated a trap and the kernel failed to catch this trap.

This trap could be a bound trap (a trap the kernel is not permitted to catch) or a double fault
(a fault that occurred while processing an earlier fault, which always results in a system failure).

这种错误是由于 Intel CPU 生成了一个陷阱(trap),而内核未能捕获这个陷阱。
此陷阱可能是一个受困陷阱(内核不允许捕获的陷阱),或一个“double fault”(当处理一个早先的错误时又出现一个错误,
这样就总是会导致系统故障)。

原文描述中的后一种情况(处理错误时又发生另一个错误)就是我们此刻的处境。

UNEXPECTED_KERNEL_MODE_TRAP 有四个参数,你可以从上一张图看到,首个参数值为“0x00000008(陷阱编号)”,

官方对该值的解释为:

0x00000008, or Double Fault, indicates that an exception occurs during a call to the handler for a prior exception.
Typically, the two exceptions are handled serially.
However, there are several exceptions that cannot be handled serially,
and in this situation the processor signals a double fault. There are two common causes of a double fault:

A kernel stack overflow. This overflow occurs when a guard page is hit, and the kernel tries to push a trap frame.
Because there is no stack left, a stack overflow results, causing the double fault.
If you think this overview has occurred, use !thread to determine the stack limits, and then use kb
(Display Stack Backtrace) with a large parameter (for example, kb 100) to display the full stack.

A hardware problem.

“Double Fault”,指明在调用前一个异常处理程序期间,又出现了一个异常。一般而言,两个异常是顺序处理的。
然而,有一些异常无法顺序处理,在这种情况下处理器就会发出一个“double fault”信号。有两种常见情况会导致
“double fault”:

1。一次内核栈溢出。当接触到一个保护页时就会发生此类溢出,然后内核试图向其中压入一个陷阱帧。
因为已经没有剩余栈可用了,导致又一次栈溢出,造成“double fault”。如果你认为发生了这种溢出,利用“!thread”调试器
命令确定栈界限,然后使用“kb”(显示栈回溯)命令,并带着较大的参数(比如 kb 100)来显示完整的栈。

2。硬件问题

遵循原文的指示,我们先检查当前线程的栈界限,然后执行栈回溯看是否真的越界了,如下图所示,内核栈边界在 8bf47000 处,

而发生异常前的最后一次递归调用的帧指针(ChildEBP)为 8bf47008 ,已经快要出界了:

“nt!KiTrap08”是实际的陷阱处理程序,有趣的是前面的陷阱编号(0x00000008)就在这个例程的名字中,这绝不是巧合,

实际上“nt!KiTrap08”就是“double fault”专用的异常处理程序!

传递给它的第三个参数“801d8940”同时也就是 UNEXPECTED_KERNEL_MODE_TRAP 的第二个参数,它是一

个“nt!_NT_TIB”结构的“SubSystemTib”字段值:

其实这个字段中包含的信息对于我们此刻的故障排查而言并不那么重要,只是怕有人好奇它的来龙去脉,才略作说明罢了。

上图中的 nt!KiTrap08 栈帧名称后面给出了一个 TSS(任务状态段)的段选择符为 28。这才是关键的信息,通过它可会回到

事故现场,分析异常发生时的上下文。这个段选择符存储在“nt!_KTSS”结构的首个字段(Backlink)内:

看到这里应该能够稍稍体会出内核中相关数据结构设计的多么用心良苦!

放下我们的多愁善感,利用调试器的“.tss”命令,后接段选择符,即可回到事故现场,如下所示,异常发生时,EIP 指向即将执行

的指令地址为 977bd06f,换言之是该地址处的“前一条”指令(push ecx)导致的异常,为啥这条压栈指令会导致异常呢?

你看“那时”的 esp 已经指向了内核栈的边界点(8bf47000),而压栈指令需要先把 esp 值减去 4 字节,然后再把 ecx 的内容

写入 8BF46FFC 地址处,该地址已经位于边界之外。

还记得前一篇我们计算出每次递归调用都会消耗掉 16 字节的内核栈空间吗?这出错前的最后一次调用中,试图消耗的最后 4 字节

就在边界之外!

低于 8bf47000 的虚拟内存没有分配实际的物理页,而且我们模拟对 8BF46FFC 执行物理地址转译也失败,证实是由于访问到

无效地址引发异常的(CR2 寄存器存储导致页错误的访问地址):

如前所述,页错误发生后,在控制权转移到 nt!KiTrap08 之前,再次向这个无效的地址压入一张“陷阱帧”,导致再度出现错误,

而 nt!KiTrap08 通过传递给它的首个参数(0x0000007f)明白了这是一个“double fault”,所以调用 nt!KiBugCheck,后者

探测调试器是否存在,决定是要绘制蓝屏还是断入调试器。这就是前面那张栈回溯输出的由来!

执行“kv 1000”回溯大范围的栈帧,你可以看到 683 次(栈帧编号 0x2b0 - 5)对 tail_recursivef_factorial() 的调用,在

我们的预测点(685 号栈帧)之前就发生了溢出:

最后介绍一个强大的命令“!analyze -v”,它会自动分析内核崩溃的原因,并给出所有对故障排除有帮助的信息,对于本例而言,

有价值的信息截图如下:

—————————————————————————————————————————————————————
小结:本篇以源码+调试+在线文档等综合手段排除了“double fault”蓝屏故障;编写驱动并上机调试是理解内核设计思想

的最佳途经!
—————————————————————————————————————————————————————

------ 解析因内核栈溢出导致的 “double fault” 蓝屏 ------的更多相关文章

  1. ------ 新春第一炮:阶乘算法性能分析与 double fault 蓝屏故障排查 Part I ------

    -------------------------------------------------------------------------- 春节期间闲来无事想研究下算法,上机测试代码却遇到了 ...

  2. win7下自写驱动导致开机蓝屏调试过程

    之前没有接触过驱动调试.这里上手就要解决一个因为某个自定义驱动导致的系统登陆后蓝屏问题,记录下来.   问题: 从客户那边弄来的一个虚拟机,已知是加了我们的驱动之后才会导致蓝屏. 解决过程:   使用 ...

  3. [转载] Win7KB3146706补丁导致蓝屏0x0000006B的修复方案

    进入winpe,将附件的蓝屏6B修复补丁kb3146706.zip的补丁替换windows/system32下面的ci.dll文件,里面有64和32位系统的,替换了文件就可以进入系统了. 启动进入系统 ...

  4. 【原创】驱动开发中Memory read error导致的蓝屏问题

    最近在看着<windows驱动开发技术详解>这本书,模仿着敲了第七章中的模拟文件读写部分.在Debug过程中,蓝屏了好多次并出现了各种奇葩的问题.在调了快两天之后,问题终于解决了!现在在这 ...

  5. C# 串口导致电脑蓝屏一个可能的原因

    在某些win7电脑上, 如果使用SerialPort对象的Read(byte[] buffer, int offset, int count)方法读取端口数据时, 若端口接受缓存区的数据少于count ...

  6. windows10蓝屏page fault in nonpaged area

    Windows系统最让人头疼的问题就是蓝屏了,总是出现得那么莫名其妙,而且造成原因也是千奇百怪的.所以,对于电脑蓝屏,系统迷也无法一次性讲清楚.前天,我的电脑就经历过这样的蓝屏page fault i ...

  7. Android内核栈溢出与ROP(CVE-2013-2597)

    一.准备 由于内核栈不可执行(NX),栈溢出利用需用到ROP.简单回顾一下ARM ROP. 漏洞演示代码如下,网上随便找了个. char *str="/system/bin/sh" ...

  8. 解析 Linux 内核可装载模块的版本检查机制

    转自:http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmodules/ 为保持 Linux 内核的稳定与可持续发展,内核在发展过程中引进了可 ...

  9. JAVA网络爬虫WebCollector深度解析——爬虫内核

    WebCollector爬虫官网:https://github.com/CrawlScript/WebCollector 技术讨论群:250108697 怎样将爬虫内核导入自己的项目? 1.进入爬虫官 ...

随机推荐

  1. C++输出二进制文件和文本文件

    所谓二进制文件和文本文件对于字母而言没有什么不同,都是存储该字母的ASCII码值.能引起不同的是数字和一些排版用符号的格式. 数字在二进制文件中会存储该数字的值,而文本文件中则首先将该数字视为字符量, ...

  2. [学习OpenCV攻略][013][Mat - 基本图像容器]

    Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针. 矩阵属于多个 Mat 对象, ...

  3. 从零开始学习前端JAVASCRIPT — 6、JavaScript基础DOM

    1:DOM(Document  Object  Model)的概念和作用 document对象是DOM核心对象:对html中的内容,属性,样式进行操作. 节点树中节点之间的关系:父子,兄弟. 2:DO ...

  4. electron 学习笔记

    一.快速搭建一个electron 项目结构 # 克隆示例项目的仓库 $ git clone https://github.com/electron/electron-quick-start # 进入这 ...

  5. IOS学习笔记25—HTTP操作之ASIHTTPRequest(一)

    ASIHTTPRequest是一个第三方开源项目,在现在的IOS应用中多使用到这个开源类库来提供网络操作,相比于SDK提供的网络操作类库,ASIHTTPRequest使用上更加方便.效率更高,同时功能 ...

  6. postgresql+mybatis返回值是数据库字段名

    mybatis 返回map的时候是下划线 role_id, user_id 两种解决方法 1.重命名 postgresql不支持驼峰 加上双引号重命名  SELECT role_id "ro ...

  7. php等比例压缩图片

    <?php function resizeImage($im,$maxwidth,$maxheight,$name,$filetype) { $pic_width = imagesx($im); ...

  8. 把一个DIV放到另一个div右下角

    父对象相对定位,子对象以父对象为参考点绝对定位:外层的div设置为相对定位,内层的div设置为绝对定位: <div id="box1"> <div id=&quo ...

  9. 使用logrotate分割tomcat日志

    转:https://www.52os.net/articles/using-logrotate-manage-tomcat-logs.html July 28, 2014 日志是Linux系统中最重要 ...

  10. Linux指令--cat,tac

    原文出处:http://www.cnblogs.com/peida/archive/2012/10/30/2746968.html cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内 ...