原文作者: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. JAVA调用oracle存储过程实例

    1.创建添加存储过程 CREATEORREPLACEPROCEDURE stu_proc(v_id INNUMBER, v_name INVARCHAR2, v_age INNUMBER) AS BE ...

  2. 九度oj 题目1041:Simple Sorting

    题目描述: You are given an unsorted array of integer numbers. Your task is to sort this array and kill p ...

  3. 九度oj 题目1362:左旋转字符串(Move!Move!!Move!!!)

    题目描述: 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果.对于一个给定的字符序列S,请你把其循环左移K位后的序列输出.例如,字符序列S=”ab ...

  4. Unity3D - UGUI组件的中英文对照

  5. Welcome-to-Swift-20扩展(Extensions)

    扩展就是向一个已有的类.结构体或枚举类型添加新功能(functionality).这包括在没有权限获取原始源代码的情况下扩展类型的能力(即逆向建模).扩展和 Objective-C 中的分类(cate ...

  6. 刷题总结——you are the one(hdu4283)

    题目: The TV shows such as You Are the One has been very popular. In order to meet the need of boys wh ...

  7. poj3311Hie with the Pie

    Hie with the Pie Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 7599   Accepted: 4088 ...

  8. css3实现连续不断的波浪

    给的波浪要比容器大,然后在左边准备一个相同的,注意,波浪头尾要能衔接起来,接着运动的长度为波浪的宽度,然后不断重复就好了

  9. UVA 10131 Is Bigger Smarter?(DP最长上升子序列)

    Description   Question 1: Is Bigger Smarter? The Problem Some people think that the bigger an elepha ...

  10. UVA 10003 Cutting Sticks(区间dp)

    Description    Cutting Sticks  You have to cut a wood stick into pieces. The most affordable company ...