如何使用WinDbg分析VC++应用程序的VC故障灯转储

将“{$Path}”替换为要存储pdb符号表文件嘚路径比如:C:\PDB

于是在VC里写程序调试时,或者用IDA时都会从网上自动下载符号表……

但是VC运行程序时会自动下载完所有要用到的pdb,所以第┅次启动会很慢若想立即看结果,只要把网络断开就行了……

VC调试dmp文件时是否有选项加载时間戳不对的PDB? [问题点数:20分结帖人lw1a2]

因为用WinDBG看堆栈,参数的值都不对

应该都一样的时间戳不对,说明PDB跟程序不匹配那用哪个调试器看參数应该都一样

代码和二进制文件是对应的,只不过重编了时间戳不一样

写过小程序试验过,VC中能看到的WinDBG中看不到

匿名用户不能发表囙复!

framework的代码还没有开始执行客户的頁面,所以跟客户的代码无关通过代码检查,发现该空指针是作为函数参数从调用者(caller)传到被调用者(callee)的当callee使用这个指针的时候問题发生。接下来应该检查caller为什么没有把正确的指针传入callee

奇怪的时候,caller中这个指针已经正常初始化了是一个合法的指针,调用call语句执荇callee的以前这个指针已经被正确地push到stack上了。为什么caller从stack上拿的时候却拿到一个空指针呢?再次单步跟踪发现问题在于caller把参数放到了callee的[ebp+8],但是callee在使用这个参数的时候却访问[ebp+c]。是不是跟前一个案例很像但是这次的凶手不是编译器,而是文件版本Caller和callee的代码位于两個不同的DLL,其中caller是.NET

compilation没有打开导致每一个ASP.NET页面都会被编译成一个单独的DLL文件。运行一段时间后就可以看到几千个DLL文件加载到进程中。一個极端的例子是5000个DLL把2GB内存平均分成5000份导致每一份的大小在400KB左右(假设DLL本身只占用1个字节),于是无法申请大于400KB的内存哪怕总的内存还昰接近2GB。对于这种情况的检查很简单列一下当前进程中所有加载起来的DLL就可以看出问题来。

对于小块Heap的频繁使用导致的内存分片可以參考下面的解释:

为了更好地理解上面的解释,考虑这样的情况假设开发人员设计了一个数据结构来描述一首歌曲,数据结构分成两部汾第一部分是歌曲的名字、作者和其他相关的描述性信息,第二部分是歌曲的二进制内容显然第一部分比第二部分小得多。假设第一蔀分长度1KB第二部分399KB。每处理一首歌需要调用两次内存分配函数分别分配数据结构第一部分和第二部分需要的空间。

假设每次处理完成後只释放了数据结构的第二部分,忘记释放第一部分这样每处理一次,就会留下1个1KB的数据块没有释放程序长时间运行后,留下的1KB数據块就会很多虽然HeapManager的薄计信息中可能记录了有很多399KB的数据块可以分配,但是如果要申请500KB的内存就会因为找不到连续的内存块而失败。對于内存碎片的调试可以参考最后的案例讨论。在Windows 2000上可以用下面的方法来缓解问题:

关于 CLR上内存碎片的讨论和图文详解,请参考:

另外一种内存问题是Stack overrun和Stack corruptionStack overrun很简单,一般是递归函数缺少结束条件导致函数调用过深从而把stack地址用光,比如下面的代码:

只要在调试器里重現问题调试器立刻就会收到Stack overflow Exception。检查callstack就可以立刻看出问题所在:

 

在当前的计算机架构上Stack是保存运行信息的地方。当Stack损坏后当前执行情況的所有信息都丢失了,所以调试器在这种情况下没有用武之地比如下面的代码:

在VS2005中用下面的参数,在debug模式下编译:

在调试器中运行看到的结果是:

在Windbg里面看到EIP,EBP都指向非法地址callstack的信息已经被冲毁,根本找不到任何线索进行调试对于Stack corruption,行之有效的方法是首先对问題作大致定位然后检查相关函数,在可疑函数中添加代码写log文件当问题发生后从log文件中找到线索。

前面提到了分配1023个字节的问题在噭活pageheap后,同时使用/unaligned参数才可以检测到这类问题。详细情况请参考KB816542中关于/unaligned的介绍

同理,下面这段代码默认情况下使用pageheap也不会崩溃:

上面兩个例子说明由于4KB的粒度限制哪怕使用pageheap,也需要根据pageheap的原理来调整参数以便覆盖多种情况。

Pageheap的另外一个功能是trace作用是记录Heap的历史操莋。激活pageheap的trace功能后Heap Manager会在内存中开辟一块专门的空间来记录每次Heap的操作,比如Heap的分配和释放把操作Heap的callstack记录下来。当问题发生后在Windbg中可鉯检查Heap操作的历史记录,方便调试参考下面一个例子:

该程序在release模式下,不激活pageheap是不会崩溃的激活pageheap后,在Windbg中运行会看到:

发生崩溃的Free函数调用(后面一次Free调用)的返回地址是所以后面一次Free是在的前一行被调用的。接下来分析第一次Free调用发生的地方发生问题的Heap地址是Free函数的参数0x3f5858,在Windbg中使用!heap命令加上–p –a参数打印出保存下来的callstack:

上面的callstack就是Heap Manager保存的这是Heap地址的历史操作。从保存的callstack看到在0x401010地址是MSVCR80!free调用的返回地址,所以和两个地址就是对同一个Heap地址两次调用Free后的两个返回地址。检查这两个地址的前一条汇编语句就能找到对应的Free调用:

這里可以看到,对应的问题的确是前后调用delete和delete []导致的对应的源代码地址大约在win32.cpp的74行。(源代码中delete和delete[]是在两个自定义函数中被调用的。這里看不到free1和free2两个函数的原因在于release模式下编译器做了inline优化)

同时可以检查一下Heap 指针0x3f5858前后的内容:

这里的红色dcba其实是一个标志位,标志位湔面的地址保存的其实就是这个Heap地址的历史操作记录通过Windbg的dds命令,可以直接检查保存下来的callstack:

了解这个标志位的好处是可以利用这个特点来解决memory leak和fragmentation。由于发生泄漏的内存往往是相同callstack分配的所以泄漏比较严重的程度时,程序中残留的大多数的Heap指针都是泄漏掉的内存地址通过在程序中搜索每一个Heap 指针的标志位,就可以找到这些指针分别对应的callstack如果某些callstack出现得非常频繁,这些callstack往往就跟memory leak相关下面就是一個使用这个方法解决memory leak的案例。

客户程序在内存占用只有300MB左右的时候对malloc的调用就会失败。通过检查问题发生时候的dump文件发现问题是由heap fragmentation导致的。客户的程序有大量的小块内存没有及时释放导致分片严重。

激活pageheap后再次抓取问题发生时的dump,然后使用下面命令在内存空间搜索dcba標志位:

根据搜索结果使用下面的命令来随机打印callstack,看到:

 

正常情况下内存指针分配的callstack是随机的。但是上面的却看到大多数内存指针嘟由固定的callstack分配该callstack很有可能就是泄漏的根源。拿到客户的PDB文件后把偏移跟源代码对应起来,很快就找到了申请这些内存的源代码客戶检查源代码后发现这就是问题根源。添加对应的内存释放代码后问题解决。

我要回帖

更多关于 VC故障灯 的文章

 

随机推荐