原文作者: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,当然我们须要给它我们上面找到的这些信息。事实上非常easy。仅仅要上面没找错,我们给 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)的更多相关文章

  1. 怎样重建一个损坏的调用堆栈(callstack)

    原文作者:Aaron Ballman原文时间:2011年07月04日原文地址:http://blog.aaronballman.com/2011/07/reconstructing-a-corrupt ...

  2. JS引擎是如何工作的?从调用堆栈到Promise

    摘要: 理解 JS 引擎运行原理. 作者:前端小智 原文:JS引擎:它们是如何工作的?从调用堆栈到Promise,需要知道的所有内容 Fundebug经授权转载,版权归原作者所有. 为了保证可读性,本 ...

  3. 每周一个js重要概念之一 调用堆栈

    js写了也有两年多了,大到复杂的后台系统,小到页面,还有日均300万的网页主站,HTML5的适配页面等等. 框架也杂七杂八接触了不少,从小的jquery.bootstrap.echarts等等,到大一 ...

  4. 在 Visual Studio 中调试时映射调用堆栈上的方法

    本文转自:https://msdn.microsoft.com/zh-cn/library/dn194476.aspx 1.创建代码图,以便在调试时对调用堆栈进行可视化跟踪. 你可以在图中进行标注以跟 ...

  5. delphi中获取调用堆栈信息

    异常堆栈有利于分析程序的错误,Delphi的Exception有StackTrace属性,但是值为空,因为StackTrace的信息收集Delphi委托给了第三方组件来完成,真是脑子有毛病! 借助于m ...

  6. Asp.Net异常:"由于代码已经过优化或者本机框架位于调用堆栈之上,无法计算表达式的值"的解决方法

    今天项目中碰到一个以前从没有见过的异常信息“由于代码已经过优化或者本机框架位于调用堆栈之上,无法计算表达式的值”,于是查了一下资料,原来此异常是由于我在代码中使用了"Response.End ...

  7. vs2010 调试 调用堆栈 窗口

    msdn 如何使用call stack窗口: http://msdn.microsoft.com/zh-cn/library/a3694ts5(v=vs.90).aspx 使用“调用堆栈”窗口可以查看 ...

  8. Windbg查看调用堆栈(k*)

            无论是分析程序崩溃原因,还是解决程序hang问题,我们最常查看的就是程序调用堆栈.学会windbg调用堆栈命令,以及理解堆栈中的各个参数的意义就显得至关重要. 上图就是一个典型的Win ...

  9. JavaScript是如何工作的:引擎,运行时和调用堆栈的概述!

    摘要: 理解JS执行原理. 原文:JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 本文是旨在深入研究JavaScrip ...

随机推荐

  1. 九度oj 题目1080:进制转换

    题目描述: 将M进制的数X转换为N进制的数输出. 输入: 输入的第一行包括两个整数:M和N(2<=M,N<=36). 下面的一行输入一个数X,X是M进制的数,现在要求你将M进制的数X转换成 ...

  2. 开发者选择短视频SDK,为何青睐七牛云?

    从文字到图片再到视频的互联网内容媒介发展途径,随着 5g 技术的逐渐落地愈发清晰.短视频市场中的角力也随着诸多资本和创业者的涌入,进入到白热化阶段.这样的情况下,选择合适的短视频SDK产品就显得尤为重 ...

  3. 【bzoj3307】雨天的尾巴 权值线段树合并

    题目描述 N个点,形成一个树状结构.有M次发放,每次选择两个点x,y,对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入 第一行数字N,M接下来 ...

  4. spring中MessageSource的配置使用方法2--ReloadableResourceBundleMessageSource

    如何在spring mvc框架中实现MessageSource来管理国际资源文件呢 如下: 1.在applicationContext.xml文件内配置如下 <span style=" ...

  5. spring-boot项目热部署以及spring-devtools导致同类不能转换

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring- ...

  6. *NOI热身赛C. 小x的城池

    码农题gun.

  7. 标准C程序设计七---70

    Linux应用             编程深入            语言编程 标准C程序设计七---经典C11程序设计    以下内容为阅读:    <标准C程序设计>(第7版) 作者 ...

  8. LeetCode OJ--Permutations *

    https://oj.leetcode.com/problems/permutations/ 写出一列数的全排列 #include <iostream> #include <vect ...

  9. AC日记——Super Mario hdu 4417

    Super Mario Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  10. js-无缝向上滚动

    这种节奏经常用在相同布局内容多的地方,列如排行榜,新闻等地方.为了效率,在此做个笔记 HTML: <div id="divgd"> <div id="b ...