1. dump介绍

Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。Dump文件是用来给驱动程序编写人员调试驱动程序用的,这种文件必须用专用工具软件打开,比如使用WinDbg、VS打开。

Windows下Dump文件分为两大类,内核模式Dump和用户模式Dump。内核模式Dump是操作系统创建的崩溃转储,最经典的就是系统蓝屏,这时候会自动创建内核模式的Dump。用户模式Dump进一步可以分为完整Dump(Full Dump)和迷你Dump(Minidump)。完整Dump包含了某个进程完整的地址空间数据,以及许多用于调试的信息,而Minidump则有许多类型,根据需要可以包含不同的信息,有的可能只包含某个线程和部分模块的信息。在程序开发过程中出现的应用崩溃属于用户模式Dump。

在系统中出现异常或者崩溃的时候来生成dump文件,然后用调试器进行调试,这样就可以把生产环境中的dmp文件拷贝到自己的开发机上,调试就可以找到程序出错的位置。 在C++编程实践中,通常都会遇到内存访问无效、无效对象、堆栈溢出、空指针调用等常见的C/C++问题,而这些问题最后常会导致:系统崩溃。为解决崩溃问题常用的手段一个就是生成dump文件进行代码调试。

特别注意:使用dmp文件和pdb文件调试时dump、exe和pdb三个文件要保持版本一致。

2. Dump文件的生成

2.1 任务管理器

在程序崩溃后,先不关闭程序,在任务管理器中找到该程序对应的进程。右键—>创建转储文件。此时会在默认的目录下创建出一个dump文件。此种方法只适用于程序崩溃但没有立即自行退出的情况。倘若程序故障后自行退出,则此方法就难以应用。

2.2 修改注册表

可以在注册表中添加相关信息已确保系统在程序崩溃后自行保存一个dump文件

2.3 WinDbg抓取

程序运行崩溃后,先不关闭程序,将WinDbg附加到改进程上。执行命令:.dump –ma Test.dmp  ,则会产生一个Test.dmp的转储文件。

2.4 程序中加入存储Dump的代码

通过SetUnhandledExceptionFilter设置捕获dump的入口,然后通过MiniDumpWriteDump生成dump文件。

3. 调试dump文件

3.1 VS调试

用VS打开dmp文件。测试时 dmp文件时本地产生的,因此VS会依据dmp文件自行找到exe,pdb和源代码的路径。因此直接点击调试,程序会出错代码行中断。但若dmp文件是exe在另一台机器上产生的,则我们最好把exe,pdb,dmp放到同一文件夹下,必须保证pdb与出问题的exe是同一时间生成的,用VS打开dmp文件后还需要设置符号表文件路径和源代码路径。

 当把pdb文件与dmp文件放入同一目录下时,就不需设置其路径,否则需要设置:工具->选项->调试->符号:

设置源代码路径:属性->调试源代码:

3.2 WinDbg调试

(1)设置pdb路径:File ->Symbol File Path

(2)设置exe路径:File -> Image File Path

(3)设置源代码路径:File -> Source File Path(指sln所在目录)

(4)打开dmp文件:File ->Open Crash Dump

(5)执行命令 !analyze –v

 

4. dump文件创建和源码分析

dump的区别在主要取决于MiniDumpWriteDump的第四个参数MINIDUMP_TYPE。参数定义如下:

typedef enum _MINIDUMP_TYPE {

    MiniDumpNormal                         = 0x00000000,

    MiniDumpWithDataSegs                   = 0x00000001,

    MiniDumpWithFullMemory                 = 0x00000002,

    MiniDumpWithHandleData                 = 0x00000004,

    MiniDumpFilterMemory                   = 0x00000008,

    MiniDumpScanMemory                     = 0x00000010,

    MiniDumpWithUnloadedModules            = 0x00000020,

    MiniDumpWithIndirectlyReferencedMemory = 0x00000040,

    MiniDumpFilterModulePaths              = 0x00000080,

    MiniDumpWithProcessThreadData          = 0x00000100,

    MiniDumpWithPrivateReadWriteMemory     = 0x00000200,

    MiniDumpWithoutOptionalData            = 0x00000400,

    MiniDumpWithFullMemoryInfo             = 0x00000800,

    MiniDumpWithThreadInfo                 = 0x00001000,

    MiniDumpWithCodeSegs                   = 0x00002000,

    MiniDumpWithoutManagedState            = 0x00004000,

} MINIDUMP_TYPE;

MINIDUMP_TYPE枚举是一些标志,允许我们来控制minidump包含哪些内容。

MiniDumpNormal

MiniDumpNormal是一个特别的标志。它的值是0,意味着这个值永远隐含存在,甚至不需要显示指定。因此,我们可以假定这个标记代表了minidump中永远存在的一组基础数据集合。通过指定用户自定义的回调函数,可以过滤这些值。

MiniDumpWithFullMemory

这可能是除了MiniDumpNormal以外使用最多的标志了。如果指定了这个标志,minidump会包含进程地址空间中所有可读页面的内容。我们可以看到应用程序分配的所有内存,这使我们有很多的调试方法。可以查看存储在栈上、堆上、模块数据段的所有数据。甚至还可以看到线程和进程环境块(Process Environment Block和Thread Environment Bolck, PEB和TEB)的数据。这些没有公开的数据结构可以给我们的调试提供无价的帮助。

使用这个标记的唯一问题是会使minidump变得很大,至少有几MByte。另外,minidump的内容里面包含了冗余信息,所有可执行模块的代码段都包含在了里面。但是很多时候,我们很容易从其他地方获得可执行代码。

MiniDumpWithFullMemoryInfo

如果希望检查整个继承的虚拟内存布局,我们可以使用MiniDumpWithFullMemoryInfo标志。如果指定它,mindump会包含进程虚拟内存布局的完整信息。可以通过WinDbg的!vadump和!vprot命令查看。这个标志对minidump大小的影响取决于虚拟内存布局-每个有相似特性的页面区域(参考VirtualQuery函数说明)会增加48字节。

MiniDumpWithHandleData

如果指定这个标志,minidump会包括故障时刻进程故障表里面的所有句柄。可以用WinDbg的!handle来显示这些信息。

这个标志对于minidump大小的影响取决于进程句柄表中的句柄数量。

MiniDumpWithThreadInfo

MiniDumpWithThreadInfo可以帮助收集进程中线程的附加信息。对于每一个线程,会提供下列信息:

线程时间 (创建时间,执行用户代码和内核代码的时间)

入口地址

相关性

WinDbg中,可以通过.ttime命令查看线程时间。

MiniDumpWithUnloadedModules

包含未加载模块信息(windows server 2003 sp1, windows xp sp2及以上系统能获取到这块信息)