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

前一篇指出 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. Git 忽略提交 .gitignore

    在使用Git的过程中,我们喜欢有的文件比如日志,临时文件,编译的中间文件等不要提交到代码仓库,这时就要设置相应的忽略规则,来忽略这些文件的提交. Git 忽略文件提交的方法 有三种方法可以实现忽略Gi ...

  2. 【LeetCode】476. Number Complement (java实现)

    原题链接 https://leetcode.com/problems/number-complement/ 原题 Given a positive integer, output its comple ...

  3. windows平台下python 打包成exe可执行文件

    第一步 安装 pyinstaller 命令行下运行:pip install pyinstaller 第二步 打包安装 pyinstaller Test.py 第三步 完成 找到打包目录下dist目录  ...

  4. Linux的ls命令在Windows中的应用

    Linux的ls命令在Windows中的应用 注:ls是Linux中的命令.其作用是列出当前目录下的文件与文件夹.效果等同于Wndows中的dir指令. 如下图 下面是详细步骤 步骤一.在桌面新建一个 ...

  5. OBS studio最新版配置鉴权推流

    这两天在看百度的LSS音视频直播服务的sdk..sdk看了一圈,基本上只能操作个流什么的,查看流列表,域名之类的.按照百度这块的描述自己去实现这个显得不是那么明智我感觉.其次就是百度LSS的教程用的O ...

  6. scss 编译方法

    第一种: 手动创建 scss文件夹  用Node.js command promt  进入项目目录  在项目目录下面 输入  sass scss/main.scss css/main.css    s ...

  7. sitemesh网页布局

    看项目时发现对应页面下找不到侧栏部分代码,仔细观察后发现页面引入了sitemesh标签,查了下资料原来是页面用了sitemesh框架解!耦!了! 以前多个模块包含相同模块时总是include jsp文 ...

  8. Linxu指令--crond

    前一天学习了 at 命令是针对仅运行一次的任务,循环运行的例行性计划任务,linux系统则是由 cron (crond) 这个系统服务来控制的.Linux 系统上面原本就有非常多的计划性工作,因此这个 ...

  9. CGroup Namspace

    CGroup 介绍 CGroup 是 Control Groups 的缩写,是 Linux 内核提供的一种可以限制.记录.隔离 进程组 (process groups) 所使用的物力资源 (如 cpu ...

  10. 【转】Matlab作图语句小结

    之前用Matlab作图,从网上找了些别人的例子,然后慢慢调参数.其实对很多命令,特别是对句柄不是很了解,今天简单总结了一下.下面用几个例子来说明:     ]);  首先,gcf是当前figure对象 ...