怎样重建一个损坏的调用堆栈(callstack)
原文作者:Aaron Ballman
原文时间:2011年07月04日
原文地址:http://blog.aaronballman.com/2011/07/reconstructing-a-corrupted-stack-crawl/
翻译:magictong
时间:2014年05月29日夜
后记:可惜原始的DUMP文件作者并没有上传
在我的日常工作中,我经常阅读来之微软WinQual(译注:https://sysdev.microsoft.com/ http://en.wikipedia.org/wiki/Winqual)的报告。这些报告里面一般包含着dump文件(译注:崩溃转储文件,我们一般都是叫dump文件,是一种软件崩溃之后产生的文件,可用于事后调试),从这些dump文件里面我可以分析出一些常用的软件里面到底出了什么问题,造成它崩溃了。总而言之,这是一个超赞的系统,我强烈建议各个独立软件开发商(原文:ISV)去上面注册(尤其是这个系统对任何人都是免费的,只要你的可执行文件是正确签名的)。最近我拿到了一个堆栈已经被严重破坏了的dump文件,我想和大家讨论一下怎么使用Windbg工具来重建它的调用堆栈(callstack)。
在开始之前,让我们先看看一个原始的调用堆栈是什么样子的,在Windbg里面运行“k”命令即可。
0:000> k
ChildEBP RetAddr
028b89cc 77c75350 ntdll!KiFastSystemCallRet
028b89d0 77c4b208 ntdll!ZwTerminateProcess+0xc
028b89e0 763e41ec ntdll!RtlExitUserProcess+0x7a
028b89f4 10056386 kernel32!ExitProcess+0x12
WARNING: Stack unwind information not available. Following frames may be wrong.
028b89fc 100565a0 EyeOneIO!I1_SynchronizeWhitebases+0xf0f6
028b8a0c 10054803 EyeOneIO!I1_SynchronizeWhitebases+0xf310
00000000 00000000 EyeOneIO!I1_SynchronizeWhitebases+0xd573
从上面的调用堆栈来看,有几个特征表明这个堆栈已经被破坏了。首先,调用堆栈的基址不可能从0x00000000开始。通常情况下,它从main函数的入口地址开始,或者从一个线程的入口地址开始,但是从上面的调用堆栈来看我们没看看到这个特征。另外,Windbg也发出了“Stack unwind information not available. Following frames may be wrong.”的警告(译注:这句警告的意思就是说,下面的栈帧可能是错误的)。
第一步,既然堆栈已经错误了,我们当然需要重建当前执行现成的堆栈,并找到当前现成堆栈的起始位置。这里有个简单的扩展命令可以查看,使用!teb即可(译注:!teb用于查看当前线程执行环境):
0:000> !teb
TEB at 7ffdb000
ExceptionList: 028b8a28
StackBase: 028c0000
StackLimit: 028b6000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffdb000
EnvironmentPointer: 00000000
ClientId: 00000a4c . 00000e3c
RpcHandle: 00000000
Tls Storage: 7ffdb02c
PEB Address: 7ffdf000
LastErrorValue: 14007
LastStatusValue: c0150008
Count Owned Locks: 0
HardErrorMode: 0
看上面!teb命令显示的结果里面,StackBase和StackLimit告诉了我们当前线程的堆栈在内存中的范围,因此我们现在可以转储这个范围内的地址,然后从里面寻找一些有意义和有用的东西(译注:就是把内存地址和对应的符号地址对应起来,然后寻找和当前的线程有关的调用堆栈)。Windbg里面有个专门的dds命令就是用来做这个事情的,dds命令需要你指定一个起始地址,然后它从给定的起始地址开始转储一定范围内的地址,并且尝试把每个地址里面的内容和符合(symbol)对应起来(译注:假如可以对应的话)。dds转储的内容包含三列数据,第一列显示的是顺序递增的地址,第二列是显示地址里面的数据,第三列是符号名称,如果地址里面的数据可以被成功解析为一个符号的话,否则第三列就是显示的空白。
把真实的栈转储出来看看(省略了一些无关项):
(译注:使用命令 dds 028b6000,要显示更后面的内容可以在028b6000的后面加上一个偏移之后再对新地址使用 dds 命令)
028b6000 00000000
...
028bf9d8 00000000
028bf9dc 00000000
028bf9e0 79035b7f
028bf9e4 028bfa1c
028bf9e8 6e760b5b i1IO!i1IO::measureOneStrip+0xbb
028bf9ec 42b840fc
...
028bfa18 00000000
028bfa1c 028bfd98
028bfa20 6e763387 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467
028bfa24 42b840fc
...
028bfd94 00000006
028bfd98 028bfe2c
028bfd9c 6e761062 i1IO!i1IO::_advancedMeasureThreaded+0x222
028bfda0 013a8520
028bfda4 79035e2e
...
028bfe28 00000000
028bfe2c 028bfe38
028bfe30 763ed0e9 kernel32!BaseThreadInitThunk+0xe
028bfe34 012118e0
028bfe38 028bfe78
028bfe3c 77c516c3 ntdll!__RtlUserThreadStart+0x23
028bfe40 012118e0
...
028bfe74 00000000
028bfe78 028bfe90
028bfe7c 77c51696 ntdll!_RtlUserThreadStart+0x1b
028bfe80 6e760e40 i1IO!i1IO::_advancedMeasureThreaded
...
028c0000 ????????
实际上转储出来的堆栈比上面列出来的大得多,不过为了简单起见,我只保留一些相关的部分。
现在要做的第一件事情就是定位到callstack的起始位置。在这个例子里面,RtlUserThreadStart看起来很像是这个起始位置,因为它是线程的起始调用函数。在找到起始点之后,获取起始点的前一个堆栈地址A(第一列),然后在堆栈的内容里面(第二列)寻找是否有等于A的堆栈B(向低地址寻找,因为堆栈是向低地址增长的),然后再在堆栈内容里面寻找是否有等于B的堆栈地址C……,按照这种方法不停的搜索内存,直到不能再找到任何东西或者找到空地址。
(译注:这个就是利用的标准函数栈帧的基本原理,对此处不理解的可以去了解下标准函数栈帧,一般没有经过FPO优化的调用函数链,可以通过EBP的值在整个堆栈上面串联起来,其实Windbg自己也是这么找的,而本文讨论的恰恰是因为堆栈被破坏之后,Windbg找不到正确的callstack之后,我们怎么手动恢复的问题)
在我们这个例子里面,我们从下面的堆栈开始找:
028bfe78 028bfe90
028bfe7c 77c51696 ntdll!_RtlUserThreadStart+0x1b
搜索地址028bfe78,得到下面的堆栈:
028bfe38 028bfe78
028bfe3c 77c516c3 ntdll!__RtlUserThreadStart+0x23
搜索地址028bfe38,得到下面的堆栈:
028bfe2c 028bfe38
028bfe30 763ed0e9 kernel32!BaseThreadInitThunk+0xe
搜索地址028bfe2c,得到下面的堆栈:
028bfd98 028bfe2c
028bfd9c 6e761062 i1IO!i1IO::_advancedMeasureThreaded+0x222
搜索地址028bfd98,得到下面的堆栈:
028bfa1c 028bfd98
028bfa20 6e763387 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467
搜索地址028bfa1c,得到下面的堆栈:
028bf9e4 028bfa1c
028bf9e8 6e760b5b i1IO!i1IO::measureOneStrip+0xbb
现在,继续搜索028bf9e4已经不能再在堆栈里面找到信息了,也就是说我们可能已经找到了最终出问题的函数位置,我们可以使用Windbg尝试修复我们的callstack,当然我们需要给它我们上面找到的这些信息。其实很简单,只要上面没找错,我们给 k 命令指明一个确定地址,通过 L 参数传递进去(译注:用上面我们最后找到的028bfa1c),那么Windbg马上就会给我们一个更加友好的callstack信息。
0:000> k L=028bf9e4
ChildEBP RetAddr
028b89cc 77c75350 ntdll!KiFastSystemCallRet
028b89d0 77c4b208 ntdll!ZwTerminateProcess+0xc
028bf9e4 6e760b5b ntdll!RtlExitUserProcess+0x7a
028bfa1c 6e763387 i1IO!i1IO::measureOneStrip+0xbb
028bfd98 6e761062 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467
028bfe2c 763ed0e9 i1IO!i1IO::_advancedMeasureThreaded+0x222
028bfe38 77c516c3 kernel32!BaseThreadInitThunk+0xe
028bfe78 77c51696 ntdll!__RtlUserThreadStart+0x23
028bfe90 00000000 ntdll!_RtlUserThreadStart+0x1b
现在我们看到的callstack是不是更加完整并且合理了?!没有了调用栈帧错误的警告,而且callstack的调用基址也正常了。
希望上面介绍的这种方法能给你的调试工作带来一些帮助。
怎样重建一个损坏的调用堆栈(callstack)的更多相关文章
- 如何重建一个损坏的调用堆栈(callstack)
原文作者:Aaron Ballman原文时间:2011年07月04日原文地址:http://blog.aaronballman.com/2011/07/reconstructing-a-corrupt ...
- JS引擎是如何工作的?从调用堆栈到Promise
摘要: 理解 JS 引擎运行原理. 作者:前端小智 原文:JS引擎:它们是如何工作的?从调用堆栈到Promise,需要知道的所有内容 Fundebug经授权转载,版权归原作者所有. 为了保证可读性,本 ...
- 每周一个js重要概念之一 调用堆栈
js写了也有两年多了,大到复杂的后台系统,小到页面,还有日均300万的网页主站,HTML5的适配页面等等. 框架也杂七杂八接触了不少,从小的jquery.bootstrap.echarts等等,到大一 ...
- 在 Visual Studio 中调试时映射调用堆栈上的方法
本文转自:https://msdn.microsoft.com/zh-cn/library/dn194476.aspx 1.创建代码图,以便在调试时对调用堆栈进行可视化跟踪. 你可以在图中进行标注以跟 ...
- delphi中获取调用堆栈信息
异常堆栈有利于分析程序的错误,Delphi的Exception有StackTrace属性,但是值为空,因为StackTrace的信息收集Delphi委托给了第三方组件来完成,真是脑子有毛病! 借助于m ...
- Asp.Net异常:"由于代码已经过优化或者本机框架位于调用堆栈之上,无法计算表达式的值"的解决方法
今天项目中碰到一个以前从没有见过的异常信息“由于代码已经过优化或者本机框架位于调用堆栈之上,无法计算表达式的值”,于是查了一下资料,原来此异常是由于我在代码中使用了"Response.End ...
- vs2010 调试 调用堆栈 窗口
msdn 如何使用call stack窗口: http://msdn.microsoft.com/zh-cn/library/a3694ts5(v=vs.90).aspx 使用“调用堆栈”窗口可以查看 ...
- Windbg查看调用堆栈(k*)
无论是分析程序崩溃原因,还是解决程序hang问题,我们最常查看的就是程序调用堆栈.学会windbg调用堆栈命令,以及理解堆栈中的各个参数的意义就显得至关重要. 上图就是一个典型的Win ...
- JavaScript是如何工作的:引擎,运行时和调用堆栈的概述!
摘要: 理解JS执行原理. 原文:JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 本文是旨在深入研究JavaScrip ...
随机推荐
- C语言如何在两个文件中访问同一个全局变量
方法一: 不使用头文件. 1.c 中 int var; 2.c 中 extern int var; 方法二: 使用头文件. 1.c 中 int var; 不必添加#include "1.h& ...
- Cocoa层粒子发射器动画添加多个cell的一种重构
在iOS动画之旅第19章中最后的挑战中需要我们在雪花例子发生器中添加多个雪花贴图,也就是多个cell,因为我们不可能将每个cell的参数都重新写一遍,所以有必要写一个helper方法来做这件事: fu ...
- Scikit-learn:主要模块和基本使用方法
http://blog.csdn.net/pipisorry/article/details/52128222 scikit-learn: Machine Learning in Python.sci ...
- [Mysql]Innodb 独立表空间和共享表空间
innodb有2中表空间方式: 共享表空间 和 独立表空间 查询数据的设置: show variables like '%per_table'; 默认是共享表空间,独立表空间在配置文件中添加 inno ...
- Android自定义处理崩溃异常
用过安卓手机的用户以及安卓开发者们会时长碰到程序异常退出的情况,普通用户遇到这种情况,肯定非常恼火,甚至会骂一生垃圾软件,然后卸载掉.那么开发者们在开发过程中遇到这种情况给怎么办呢,当然,你不可能世界 ...
- JAVA面向对象-----this的概述
this关键字代表是对象的引用.也就是this在指向一个对象,所指向的对象就是调用该函数的对象引用. 1:没有this会出现什么问题 1:定义Person类 1:有姓名年龄成员变量,有说话的方法. 2 ...
- Hadoop与分布式数据处理 Spark VS Hadoop有哪些异同点?
Spark是一个开源的通用并行分布式计算框架,由加州大学伯克利分校的AMP实验室开发,支持内存计算.多迭代批量处理.即席查询.流处理和图计算等多种范式.Spark内存计算框架适合各种迭代算法和交互式数 ...
- Struts 2 之配置文件
Struts 1使用ActionServlet作为分发器,而Struts 2使用Filter作为分发器.如果有多个Filter,要把Struts 2的分发器Filter放在最后 web.xml < ...
- SSH框架实现仿淘宝购物demo
还记得六月份实习的时候,曾经做过一个电商的项目,项目里面需要实现类似淘宝购物车的移动端的demo,随着项目的进行,再一次跟购物车碰面,但是今天呢,不是移动端的需求,如何使用SSH框架实现类似淘宝的购物 ...
- UNIX环境高级编程——初始化一个守护进程
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h&g ...