目录
1、概述
2、dump文件的分类
2.1、dump按大小分类
2.2、查看dump文件中函数调用堆栈中变量的值
3、调用SetUnhandledExceptionFilter设置异常处理回调函数,然后调用MiniDumpWriteDump生成dump文件
4、使用Google开源库CrashRpt捕获异常,并自动生成dump文件
4.1、开源库CrashRpt捕获异常的原理及缺陷
4.2、使用微软detours技术对CrashRpt进行改进
5、通过Windows任务管理器导出目标进程的dump文件
6、从正在动态调试的Windbg中导出dump文件
6.1、有时需要将Windbg动态调试目标进程
6.2、将Windbg附加到目标进程上的方法
6.3、使用.dump命令导出dump文件
7、最后
C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达600多个,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新480多篇,订阅量已达数百个,持续更新中...)https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到实战(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html 我们在处理C++软件运行异常崩溃问题时,主要通过事后分析dump文件去排查。这就需要程序能感知到异常崩溃并自动生成包含异常上下文信息的dump文件。除了程序自动生成dump文件外,有时我们可能需要通过其他途径去产生dump文件。今天我们就来详细地讲讲dump文件的相关内容。
1、概述
我们在处理C++软件运行异常崩溃问题时,主要通过事后分析dump文件去排查,能否在异常发生时自动生成dump文件就显得尤为关键。
对于Windows系统,一般我们会在程序中安装异常捕获模块,去自动捕获软件异常,即当软件发生异常时,异常捕获模块能感知到,并自动生成包含异常上下文的dump文件。
对于Linux系统,则系统会自动生成包含异常上下文的coredump文件,我们可以配置生成的文件名称。本文主要讲述Windows系统中dump文件的相关内容。
Windows程序中安装的异常捕获模块主要用来捕获发生异常崩溃的场景,有些在运行过程中发生的非崩溃性异常,异常捕获模块可能感知不到,也就无法生成dump文件了,比如程序中发生了死循环、死锁等非崩溃的运行异常时,异常捕获模块是感知不到的,这时候如果需要导出dump文件,就需要使用其他手段了,比如到任务管理器中找到目标进程导出dump文件,或者直接将Windbg附加到目标进程上使用.dump命令导出dump文件。
此外,异常捕获模块只能捕获大部分情况下的异常崩溃,有少部分场景是捕获不到的,这时可能就需要使用Windbg(WIndows平台)或者gdb(Linux平台)调试器附加到目标进程上进行动态调试了。如果调试器在动态调试过程中,程序发生了异常,调试器就会第一时间感知到并中断下来,此时就可以去直接分析异常,查看此时的函数调用堆栈。也可以通过命令手动将dump文件导出到磁盘上,供事后分析。
因为有时我们很难在短时间内分析出问题,就可以将包含异常上下文的信息导出到dump文件中,事后再进行详细分析。
在Windows平台上,产生dump文件的方式主要有三种:
1)异常捕获模块感知到异常时自动生成dump文件;
2)通过Windows任务管理器导出dump文件;
3)用Windbg动态调试时用Windbg命令导出dump文件。
此外,有时Windows系统可能会帮我们生成dump文件。有时软件中内嵌的异常捕获模块没捕获到异常,可以尝试到系统应用程序日志中查看系统有没有自动帮我们生成dump文件,相关实战案例可以查看我之前写的文章:
使用Windbg分析从系统应用程序日志中找到的系统自动生成的dump文件去排查程序崩溃问题https://blog.csdn.net/chenlycly/article/details/132024253
2、dump文件的分类
2.1、dump按大小分类
根据生成的dump文件的大小,可以将dump文件分两类,一类是很小的mini dump文件,一类是很大的全dump文件。软件的异常捕获模块在感知到异常时自动生成的dump文件,会自动保存在指定的目录中,可能会因为崩溃多次生成多个dump文件,会占用用户很多的磁盘空间,所以对于自动生成的dump文件,我们一般生成只包含部分信息的mini dump文件,mini dump文件的大小一般在几MB左右。
异常捕获模块是通过调用API函数MiniDumpWriteDump生成dump文件的,那如何去控制生成的dump文件的大小的呢?是通过设置调用MiniDumpWriteDump函数时传入的参数,去控制dump文件大小的。
从Windows任务管理器中导出的dump文件、从动态调试的Windbg中使用.dump命令导出的dump文件,一般都是包含进程完整信息的全dump文件。全dump文件则比较大,一般都有几百MB,甚至有1GB以上。因为全dump文件中保存了进程的所有内存信息,所以全dump文件的大小,接近对应进程占用的总虚拟内存的大小,所以文件会比较大。
2.2、查看dump文件中函数调用堆栈中变量的值
一般我们在分析dump文件时,会先去看发生异常的那条汇编指令以及相关寄存器的值,然后去查看发生异常的线程的函数调用堆栈,然后对照到C++源代码去分析问题。有时,我们还需要去查看Windbg中线程函数调用堆栈中某个函数中局部变量或者类对象的成员变量值,去辅助分析问题,变量的值可能是很重要的线索,查看变量值的截图如下:
但能不能查看到目标变量的值(变量内存中的值),取决于dump文件的类型。对于mini dump文件,只能看到部分变量的值,很多变量的值是看到不到的,能否看到目标变量的值是要看运气的。而全dump文件,则是包含了进程所有内存的信息,是可以看到所有变量的值的。
通过查看目标变量的值去定位问题的方法,我们在项目中已经用过多次了,比如下面的三篇文章:
通过查看Windbg中的变量值去定位C++软件异常问题https://blog.csdn.net/chenlycly/article/details/125731044通过查看windbg中变量值去定位C++软件异常的又一典型案例分享https://blog.csdn.net/chenlycly/article/details/125793532
通过查看Windbg中汇编指令及内存中的值去定位软件崩溃问题https://blog.csdn.net/chenlycly/article/details/127033741
关于使用Windbg静态分析dump文件的一般步骤,可以查看我的文章:
使用Windbg静态分析dump文件的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/130873143
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到580多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章已经更新到200篇以上,持续更新中!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达220多个,专栏文章已经更新到480多篇,持续更新中!欢迎订阅!)
C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。
专栏3:
C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!
专栏4:
VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5: (本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到260多篇,持续更新中!欢迎订阅!)
C++ 软件开发从入门到实战(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
3、调用SetUnhandledExceptionFilter设置异常处理回调函数,然后调用MiniDumpWriteDump生成dump文件
程序中可以调用系统API函数SetUnhandledExceptionFilter去设置异常处理回调函数,当发生异常时系统会回调这个函数,这样程序就能感知到,然后在回调函数中调用系统API函数MiniDumpWriteDump就可以生成dump文件了。相关代码如下所示:
// unhandled exception callback set with SetUnhandledExceptionFilter()
static LONG WINAPI SEHUnhandledExceptionFilter(EXCEPTION_POINTERS* pExInfo)
{HANDLE hDumpFile;hDumpFile = CreateFile(sFile, GENERIC_READ | GENERIC_WRITE,FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);MINIDUMP_EXCEPTION_INFORMATION ExpParam;ExpParam.ThreadId = GetCurrentThreadId();ExpParam.ExceptionPointers = pExceptionPointers;ExpParam.ClientPointers = TRUE;MINIDUMP_TYPE MiniDumpWithDataSegs = (MINIDUMP_TYPE)(MiniDumpNormal| MiniDumpWithHandleData| MiniDumpWithUnloadedModules| MiniDumpWithIndirectlyReferencedMemory| MiniDumpScanMemory| MiniDumpWithProcessThreadData| MiniDumpWithThreadInfo);BOOL bMiniDumpSuccessful = FALSE;bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);return EXCEPTION_EXECUTE_HANDLER;
}// Install structured exception handler
LPTOP_LEVEL_EXCEPTION_FILTER pOldExceptionFilter = SetUnhandledExceptionFilter( SEHUnhandledExceptionFilter );
软件从上层到底层一般会包含多个dll模块,模板中可能创建了线程,整个进程中会包含多个线程,SetUnhandledExceptionFilter不能作用于所有的线程,即不能捕获所有线程的异常,所以这种方法不够通用,需要改进。
4、使用Google开源库CrashRpt捕获异常,并自动生成dump文件
可以使用Google的C++开源异常捕获库CrashRpt,将CrashRpt引入到项目中,作为程序的异常捕获库。
4.1、开源库CrashRpt捕获异常的原理及缺陷
开源的CrashRpt异常捕获库是动态地将已加载的库的导入表中创建线程的API函数CreateThread HOOK成自定义的MyCreateThread函数(不管调用哪个创建线程的接口,最终都会走到CreateThread API接口中的),这样就会走进HOOK函数MyCreateThread,在该函数中就额可以调用系统API函数SetUnhandledExceptionFilter给每个创建的线程挂载异常处理回调函数了。
但CrashRpt这种处理机制是有缺陷的,没法给软件所有模块的所有线程都挂载上异常处理回调函数的,只能给在CrashRpt库之前加载的dll库挂载异常处理回调函数,在CrashRpt之后加载的库就没法去HOOK了,这样就会导致没进行HOOK操作的那些dll库中发生的异常都捕获不到了。在exe启动时,会把所有依赖的库加载到进程空间中,我们没法控制所有的库都在CrashRpt库之前被加载的,这也导致了有些模块的异常崩溃CrashRpt是捕获不到的。
4.2、使用微软detours技术对CrashRpt进行改进
后来我们针对上述缺陷,对CrashRpt库进行了改进,使用微软开源的detours项目中的代码将windows系统库中的UnhandledExceptionFilter接口给HOOK掉。因为基本所有的异常都会最终进入到该系统函数中,我们将UnhandledExceptionFilter接口HOOK成我们自定义的接口,我们就能在该自定义的接口中感知到几乎所有的异常了。感知到异常后,就可以生成包含异常上下文的dump文件了。这样就能很好的解决老版本CrashRpt不能hook后加载的库的问题,新版本的CrashRpt就可以作用于当前进程的所有模块了,基本可以捕获到进程的所有异常了。
当然改进后的CrashRpt也不是所有的异常都能捕获到,但可以捕获大概90%以上的异常。对于捕获不到的场景,可以尝试将Windbg调试器挂载到目标进程上,看看动态调试时能否感知到。对于将Windbg挂载到目标进程上进行动态调试的相关说明,下面会详细地讲到。
在应用程序中安装一个异常捕获模块,已经成为一种标配了。通过这个异常捕获模块,去感知(捕获)应用程序中发生的异常或崩溃,自动生成包含异常上下文的dump文件。事后使用Windbg去分析dump文件,去分析软件异常!这一策略和方式,对于一些很难复现的问题,或者只在客户环境中才能出现的问题,非常有用!
当前深度改造的异常捕获库,极大地提高了异常捕获的效率,几乎可以捕获到所有的异常:
1)原生的开源库CrashRpt,对多线程支持的不好,且其实现机制(通过HOOK模块导入表中的CreateThread函数)导致其只能捕获在该库之前加载dll模块中的异常,之后加载的dll库发生异常是捕获不到的。
2)改进后的库,提供了对多线程的支持。
3)通过微软开源的Detours技术,彻底解决了CrashRpt很多异常捕获不到的问题,极大地提升了捕获的效率。
4)具体如何使用本异常捕获库,压缩包中提供了一个demo程序,可以参照demo中的做法。如果要获取到该改进的异常捕获库,可以联系微信kvsthinking!
5、通过Windows任务管理器导出目标进程的dump文件
当程序运行弹出报错提示框或者程序卡死时,程序的进程还在的(进程还没退出),可以到Windows任务管理器中找到该进程,将包含进程上下文信息的dump文件导出来。具体的操作步骤是,打开Windows任务管理器找到目标进程,右键点击该进程,在弹出的右键菜单中点击“创建转储文件”菜单项,如下所示:
即可完成进程dump文件的导出了。
当然出现这类问题时,我们可以直接将Windbg调试器直接挂载到出问题的进程上,去直接查看进程上下文,查看线程的函数调用堆栈。但问题可能出现在客户的机器上(不能占用客户时间或者客户因为安全涉密问题没法进行远程操作),或者将Windbg挂到目标进程上后一时半会分析不出问题,这些时候就可以选择从Windows任务管理器中导出目标进程的dump文件。
这种导出dump文件的方式我们会时不时地使用到,比如前段时间帮兄弟项目组排查他们软件死锁问题时,当时的dump文件就是从任务管理器中导出的。
6、从正在动态调试的Windbg中导出dump文件
6.1、有时需要将Windbg动态调试目标进程
有少数异常崩溃,程序中安装的异常捕获模板是捕获不到的,当遇到这类问题时,可以尝试将Windbg附加到目标进程上调试运行,看看Windbg在异常发生时能否感知到异常。当Windbg感知到异常时,就会中断下来,这样我们就可以进行分析及其他操作了。
对于好复现的崩溃,这种方法处理很快;对于很难复现的问题,只能将Windbg附加到进程上和进程一起运行,直到出现异常为止(遇到这类难复现的问题时,我们都是让测试同事挂着Windbg和程序一起跑的)。
关于使用Windbg进行动态调试的案例,可以参见之前的文章:
C++程序中执行abort等操作导致没有生成dump文件的问题案例分析https://blog.csdn.net/chenlycly/article/details/129003869 关于使用Windbg调试目标进程的一般步骤,可以查看我的文章:
使用Windbg动态调试目标进程的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/131029795 有人可能会说,如果Windbg在动态调试的过程中捕获到了异常直接分析就好了,为啥还要从Windbg导出dump文件呢?这不是多此一举吗?其实是这样的,有的问题可能没法很快分析出来,我们不能长时间占用别人的电脑,别人也有很多活要干的,那么这时就可以从Windbg中导出dump文件,事后将dump取来进行详细的分析。
6.2、将Windbg附加到目标进程上的方法
将Windbg附加到目标进程上进行动态调试有两种方式:
1)直接使用Windbg打开目标exe程序,即使用Windbg启动目标进程;
2)将Windbg附加到已经运行的进程上。
具体使用哪种方式,要看具体的场景。如果问题出在程序启动的过程中,则需要选择使用Windbg启动程序;如果是运行过程中出现的问题,则两种方式都可以使用。
6.3、使用.dump命令导出dump文件
可以从动态调试的Windbg中导出包含进程当前上下文的dump文件,以供事后分析。从Windbg中导出dump文件的命令如下:
.dump /ma D:\20221118.dmp
其中.dump是命令名,/ma是参数,D:\20221118.dmp是存放dump文件的完整路径,Windbg中的运行效果如下:
参数/ma是导出包含所有信息的dump文件,即全dump文件。至于.dump命令还支持哪些命令,可以到Windbg的帮助文档中查看:
前段时间兄弟项目组遇到的一个软件崩溃问题就是通过Windbg动态调试(附加到目标进程上)捕获到的。当时测试同事找到了崩溃复现的方法,很容易复现,但软件中安装的异常捕获模块就是捕获不到,后来让测试同事手动将Windbg附加到目标进程后复现崩溃,Windbg捕获到了异常,手动使用命令将dump文件导了出来。
7、最后
本文结合多年的项目实战经验,详细讲述了dump文件的分类与dump文件生成方法,希望这些内容能对相关的朋友们提供一些借鉴和参考。