ImageVerifierCode 换一换
格式:DOCX , 页数:11 ,大小:23.35KB ,
资源ID:2964911      下载积分:1 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bingdoc.com/d-2964911.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(调试Release发布版程序的Crash错误.docx)为本站会员(b****1)主动上传,冰点文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰点文库(发送邮件至service@bingdoc.com或直接QQ联系客服),我们立即给予删除!

调试Release发布版程序的Crash错误.docx

1、调试Release发布版程序的Crash错误调试Release发布版程序的Crash错误在Windows平台下用C+开发应用程序,最不想见到的情况恐怕就是程序崩溃,而要想解决引起问题的bug,最困难的应该就是调试release版本了。因为release版本来就少了很多调试信息,更何况一般都是发布出去由用户使用,crash的现场很难保留和重现。本文将给出几个解决方案,完成对release版应用程序crash错误的调试。(本文只讨论Windows平台MSVC环境下的调试,对于其他平台和开发环境没有关注,请大家自己借鉴和尝试。)方案一:崩溃地址+MAP文件这种方案只能对VC7以前的版本开发的程序使用

2、。1、崩溃地址所谓崩溃地址就是引起程序崩溃的内存地址,在WinXP下应用程序crash的对话框如下图:上面第2张图中画红线的值为crash的代码偏移地址,第3张图为即crash绝对地址;一般引起crash的原因多为内存操作错误,我们用这两个地址和MAP文件就能定位出错的代码行。2、MAP文件MAP文件是记录应用程序信息的文件(文本文件),里面大概包含了程序的全局符号、源码模块名、源码文件和行号等信息,而这些信息能够帮助我们定位出错的代码行。怎样生成MAP文件呢?以VC6为例,在Project Settings-C/C+-Debug info中,选择Line Numbers Only;在Proj

3、ect Settings-Link中,选择Generate mapfile项,并在Project Options里面输入/MAPINFO:LINES和/MAPINFO:EXPORTS,重新编译程序就会生成.map文件。以上设置对应的编译链接选项分别分:/Zi-表示生成pdb调试信息;/MAP:filename-表示生成map文件名;/MAPINFO:EXPORTS-表示生成的map文件中加入exported functions(生成DLL文件时);/MAPINFO:LINES-表示生成的map文件中加入代码行信息。由于/MAPINFO:LINES选项在VC8以后的版本中不再支持,因此通过MAP

4、文件中的信息和crash地址定位出错代码行就比较困难了,所以这种方案只能在VC7及以前的版本中使用。一个MAP文件片段示例如下:图中Rva+Base列的地址为该行函数对应的函数绝对地址,Address列中冒号后面的地址为函数相对偏移地址。3、定位crash代码有了上面的介绍,定位crash代码就很简单了。用下面的公式来进行定位:崩溃行偏移=崩溃地址-崩溃函数绝对地址+函数相对偏移我们首先根据崩溃地址(绝对地址),按照找到第2张图中Rva+Base列的地址找到发生崩溃的函数(即崩溃地址大于该函数行的Rva+Base地址且小于下个函数的地址),然后找到该行对应的函数相对偏移地址,带入公式中,就得到

5、了崩溃行偏移,该值表示崩溃行的代码相对于代码所在函数的偏移量。用该值去与第3张图中对应函数冒号后面的偏移量去比较,最接近的值前面的那个十进制数即为代码所在函数中的行号。ok,到此我们已经成功找到了崩溃的代码行,只不过这种方法还是比较费力,并且限制比较多,我们看看下面的方案。上篇给出的方案一还要补充几句。通过crash地址+MAP文件来定位出错代码位置虽然需要经过比较复杂的地址计算,但却是最简单实现的方式。如果仅仅想通过崩溃地址定位出错的函数,就更加方便了。我在网上找到一个解析MAP文件的小工具,可以非常清晰的列出每个函数的地址,并且可以将分析表格导出为Excel文件。工具下载地址:,工具目录下

6、VCMapper.exe。另外上篇主要参考两篇文章:方案二:崩溃地址+MAP文件+COD文件由于VC8以后的版本都不再支持MAP文件中产生代码行信息,因此我们寻找另一种定位方式:COD文件。1、COD文件COD文件是一个包含了汇编码、二进制机器码和源代码对应信息的文件,每一个cpp都对应一个COD文件。通过这个文件,我们可以非常方便地进行定位。在VC6中生成COD文件的设置方式为:Project Settings-C/C+,在Category中选Listing Files,在Listing file type组合框中选Assembly,Machine code,and source。在VC8中

7、生成COD文件的设置方式为:Project Properties-C/C+-Output Files-Assembler Output项,选择Assembly,Machine code,and Source(/Facs)。2、定位崩溃行下面通过举例进行说明。现在我有一个基于对话框的MFC应用程序CrashTest,在CCrashTestDlg:OnInitDialog函数中写入导致crash的代码语句(第99行),源文件如下:根据崩溃地址(0x 004012A3)以及MAP文件(定位片段图片如下),定位crash函数为OnInitDialog;并且我们可以很容易地计算出崩溃地址相对于崩溃函数的

8、偏移量为0x 004012A3-0x 004011E0=0xC3。再来看看CrashTestDlg.cod文件,我们根据文件中源码信息找到OnInitDialog函数信息片段:可以看到图片中第一行为OnInitDialog函数汇编代码的起始行;找到int*p=NULL;这一句源码,其前面的98表示这行代码在源文件中的行号,下面的000c1表示相对于函数开始位置的偏移量,后面的33 c0为机器码,xor eax,eax为汇编码。那么我们根据前面算出来的偏移量0xC3,找到对应出错的语句为99行:*p=5;。总结一下定位步骤:1)根据公式崩溃语句在函数中偏移地址=崩溃地址-崩溃函数地址计算出偏移量

9、X;2)根据公式崩溃语句在COD文件中地址=崩溃函数在COD文件中地址+X计算出地址Y。其中崩溃函数在COD文件中地址为COD文件中函数起始括号后面表明的地址,一般情况下为0x0000;3)根据Y在COD文件中找到对应代码行。ok,方案二介绍完了。这种方法最大的好处是没有VC开发环境版本限制,而且COD文件里面包含的信息更加丰富,不但可以帮助我们定位crash,还能帮我们分析很多东西。当然,这也导致编译生成了很多信息文件。根据前面两篇博文,我们要定位崩溃行代码,必须要自己根据相关信息文件进行计算。如果需要处理的量比较大,恐怕会很费力气。有没有更简单快速的办法呢?最直接的想法就是写一个小工具,根

10、据规则和信息进行自动定位,不过开发起来也是要费一番功夫的。令人开心的是,我们可以找到类似的工具,而且是开源免费的!程序员的世界也许很多时候都是这么单纯而乐于分享!方案三:崩溃地址+PDB文件+CrashFinder CrashFinder是一个开源工具,作者是John Robbin,大家可以去他的blog上去找关于CrashFinder的信息。我们这里以CrashFinder2.5版本为例介绍,相关文章链接为:1、PDB文件PDB(Program Database)文件中包含了exe程序所有的调试相关信息,具体可以查阅MSDN。当编译选项设置为/Zi,链接选项设置为/DEBUG,/OPT:RE

11、F时,就会生成工程的.pdb文件。具体到VC2005中,就是Project Propertise-C/C+-General-Debug Information Format项设置为Program Database(/Zi),Linker-Debugging-Generate Debug Info项设置为Yes(/Debug),Linker-Optimization-References项设置为Eliminate Unreferenced Data(/OPT:REF)。只要设置以上选项,release版本也能生成PDB文件。当然,对应的应用程序也会稍大。2、CrashFinder CrashFi

12、nder能够运行需要两个条件:一是系统必须要有dbghelp.dll文件;二是PDB文件必须与exe文件在一个路径下。对于dbghelp.dll,一般在系统system32路径下都有,如果没有下载一个放到这个目录下就可以了。先看一下CrashFinder的界面。用起来也非常简单。首先选择File-New或点击工具栏新建按钮,选择要调试的exe文件打开,会发现exe及所依赖的dll文件信息都已经加载进来。在下半部分的编辑框中输入崩溃地址(16进制),点右边的Find按钮,就会在下面显示崩溃的源文件路径、名称以及崩溃所在行号了,如下图所示。用CrashFinder进行crash定位真的非常方便。但

13、是我在使用过程中发现了一个bug,每次启动程序后,直接新建的话加载进来的exe模块都显示叉,提示找不到debug symbols。但是用打开按钮随便打开一个文件失败后,再新建就能成功。猜测可能是直接新建,定位PDB文件时的路径不对引起的。有源码,但是懒的看了呵呵,大家感兴趣可以试一下。好了,方案三就介绍到这里,后面还有更加强大的方案前面几个方案都是直接定位crash的代码位置,但是在比较大型的程序中,只知道这个信息还是远远不够的,我们希望知道更多关于调用函数顺序及变量值等信息,也就是crash时调用堆栈信息。方案四:SetUnhandledExceptionFilter+StackWalker

14、这个方案需要自己动手往工程里添加代码了。要实现上面的想法,需要做两件事情:1、需要在crash时有机会对程序堆栈进行处理;2、对堆栈信息进行收集。1、SetUnhandleExceptionFilter函数Windows平台下的C+程序异常通常可分为两种:结构化异常(Structured Exception,可以理解为与操作系统相关的异常)和C+异常。对于结构化异常处理(SEH),可以找到很多资料,在此不细说。对于crash错误,一般由未被正常捕获的异常引起,Windows操作系统提供了一个API函数可以在程序crash之前有机会处理这些异常,就是SetUnhandleExceptionFil

15、ter函数。(C+也有一个类似函数set_terminate可以处理未被捕获的C+异常。)SetUnhandleExceptionFilter函数声明如下:LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(_in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);其中LPTOP_LEVEL_EXCEPTION_FILTER定义如下:typedef LONG(WINAPI*PTOP_LEVEL_EXCEPTION_FILTER)(_in struct _EX

16、CEPTION_POINTERS*ExceptionInfo);typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;简单来说,SetUnhandleExceptionFilter允许我们设置一个自己的函数作为全局SEH过滤函数,当程序crash前会调用我们的函数进行处理。我们可以利用的是_EXCEPTION_POINTERS结构类型的变量ExceptionInfo,它包含了对异常的描述以及发生异常的线程状态,过滤函数可以通过返回不同的值来让系统继续运行或退出应用程序。关于SetUnhandleExceptionFil

17、ter函数的具体用法和示例请参考MSDN。2、StackWalker现在我们已经有机会可以在crash之前对程序状态信息进行处理了,只需要生成并保存堆栈信息就大功告成了。Windows的dbghelp.dll库提供了一个函数可以得到当前堆栈信息:StackWalk64(在Win2K以前版本中为StackWalk)。该函数声明如下:BOOL WINAPI StackWalk64(_in DWORD MachineType,_in HANDLE hProcess,_in HANDLE hThread,_in_out LPSTACKFRAME64 StackFrame,_in_out PVOID C

18、ontextRecord,_in PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,_in PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,_in PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,_in PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);该函数的具体用法可以参考MSDN。在这里推荐一个牛人写好的StackWalker,可以直接拿来用,开源的。StackWalker提供了

19、一个基类,给出了几个简单的接口,可以方便地生成堆栈信息,并且支持一系列VC版本,非常好用。我们可以自己写一个子类,并重载虚函数OnOutput,就可以将堆栈信息输出为特定格式了。StackWalker的地址为:。不过对于Release版本来说,StackWalk64函数获得的堆栈信息有可能不完整。如果异常是由MFC的模块抛出,那么获得的堆栈可能缺少前面调用模块信息。另外,StackWalk64需要最新的dbghelp.dll文件支持才能工作;要正确输出crash的函数名和行号,需要要pdb文件支持。以上不足有可能影响输出信息的完整性和效果,而对于发布在外的程序,要带上pdb文件几乎不可能,因此

20、这个方案还是有缺憾的,比较适用于本地的release版本调试。下一篇我们将介绍一个更加完善的解决方案当我们把自己的release版本程序发布出去以后,一般都是在用户的机器上运行。这种情况下,对于第四种方案,因为需要pdb文件才能够正确生成堆栈调用的函数行号及代码行号,因此方案四只适用于本地release版的调试,否则只能生成不完整的堆栈信息。对于前三种方案,其实只需要用户告知崩溃地址,然后在本地查找crash地址就可以了,但是定位crash的过程非常不方便,如果crash的情况比较多,前三种方案都不合适。而且,前三种方案均不能生成堆栈调用信息,对于debug的作用有限。下面我们就来看一个更加完

21、善的解决方案。方案五:SetUnhandledExceptionFilter+Minidump SetUnhandleExceptionFilter函数我们已经介绍过了,本方案的思路还是要利用我们自己的异常处理函数,来生成minidump文件。1、Minidump概念minidump(小存储器转储)可以理解为一个dump文件,里面记录了能够帮助调试crash的最小有用信息。实际上,如果你在系统属性高级启动和故障恢复设置写入调试信息中选择小内存转储(64 KB)的话,当系统意外停止时都会在C:WindowsMinidump路径下生成一个.dmp后缀的文件,这个文件就是minidump文件,只不过

22、这个是内核态的minidump。我们要生成的是用户态的minidump,文件中包含了程序运行的模块信息、线程信息、堆栈调用信息等。而且为了符合其mini的特性,dump文件是压缩过的。2、生成minidump文件生成minidump文件的API函数是MiniDumpWriteDump,该函数需要dbghelp.lib支持,其原型如下BOOL WINAPI MiniDumpWriteDump(_in HANDLE hProcess,_in DWORD ProcessId,_in HANDLE hFile,_in MINIDUMP_TYPE DumpType,_in PMINIDUMP_EXCEP

23、TION_INFORMATION ExceptionParam,_in PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,_in PMINIDUMP_CALLBACK_INFORMATION CallbackParam);在我们的异常处理函数中加入以下代码:HANDLE hFile=:CreateFile(_T(E:dumpfile.dmp),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);if(hFile!=INVALID_HANDLE_VALUE)MINIDUMP

24、_EXCEPTION_INFORMATION einfo;einfo.ThreadId=:GetCurrentThreadId();einfo.ExceptionPointers=pExInfo;einfo.ClientPointers=FALSE;:MiniDumpWriteDump(:GetCurrentProcess(),:GetCurrentProcessId(),hFile,MiniDumpNormal,&einfo,NULL,NULL);:CloseHandle(hFile);其中,pExInfo变量为异常处理函数PEXCEPTION_POINTERS类型的参数。具体请参考MSDN

25、。3、调试minidump调试dump文件首先需要pdb文件,因此我们build程序时需要设置Debug Infomation Format为Program Database(/Zi)。其次,我们还要确保所用的dump文件与源代码、exe、pdb文件版本是一致的,这要求我们必须维护好程序版本信息。调试minidump最方便的环境就是VS了,我们只要将.dmp、.exe、.pdb文件放在一个路径下,保证源代码文件的路径与编译时的路径一致就可以了,剩下的就是VS帮我们完成。双击.dmp文件或者在文件打开工程中选择dump files,加载dump文件,然后按F5运行就能直接恢复crash时的现场了

26、,你可以定位crash的代码,可以查看调用堆栈,可以查看线程和模块信息一切都跟你设置断点调试一样,太强大了!看个截图吧。需要注意的是,对于release版的程序来说,很多代码是经过编译器优化过的,因此定位的时候可能会有所偏差,大家可以考虑设置选项去掉代码优化。其他可以调试minidump的工具还有WinDbg等,大家可以查阅相关资料。下一篇,我们将给出一个调试release发布程序的完美解决方案,适合用户量较大的应用发布程序的调试。上一篇我们已经给出了方案,能够非常方便的通过dump文件对crash错误进行调试和定位;从整个流程上看还差最后一步,即怎样拿到crash时产生的dump文件。如果可

27、以让用户把文件发送过来自然不错,但对于类似免费共享软件等在互联网上发布的程序呢?我们的用户是不确定的,而且用户量有可能非常大,即使我们能想办法联系到用户,总不能挨个去收集crash信息吧。我们需要一种方案,能够提供crash信息汇报功能。我们可以架设一台服务器专门进行信息收集,只要客户端在crash时正确汇报即可,但是相应的维护成本和开发难度也不可忽视。有没有更简单的方法呢?还记得我的博文吗?这就是简单有效的方法!方案六:minidump+email我们只需要在异常处理时,先生成minidump信息文件,再用方式将文件发送到指定邮箱就行了。剩下的就是我们每天查看邮箱,提取dump文件进行调试了

28、。1首先我们来看一下发送都需要哪些相关信息。a、发送端邮箱帐户;b、接收端邮箱帐户;c、标题,一般应有软件名称及版本信息;d、正文,一般应有简单的crash信息提示,以区别不同原因造成的crash;e、附件,当然就是我们的dump文件了,还可以加上软件生成的log文件等。当然,对于标题应该尽量多加一些信息区别引起crash的原因,比如将crash的地址信息加到标题中;因为当每天有成百上千的crash汇报上来,重复的crash占大多数,把时间都花在区分它们身上有点太浪费。由此看来,前面方案中提到的StackWalker还是有些用处的,我们可以用它来生成一些crash的文字描述信息,写到标题或正文

29、中去。dump文件的大小是否适合作为邮件的附件呢?实际上minidump产生的文件一般在几K到几十K之间,作为的附件没有任何问题。关于发送相关技术细节,已经在文中介绍了,大家可以参考。其实,对接受邮箱中邮件的处理还是很费时费力的,大家可以考虑写一些脚本将处理流程自动化,提高效率。2、google breakpad google breakpad是一个开源的跨平台crash report系统,光从开源和跨平台这两个特点上来看,它就足以称的上是一个完善而有效的工具了。其实,breakpad在整个crash report层次上给出了一个系统级的解决方案,也就是说它几乎能适应各种软件、各种平台的应用要求。breakpad的整体思路跟上面介绍的方案是相似的,只不过最后提交dump文件的方式更加完善。大家有兴趣可以去它的官方网址查阅相关资料:。ok,关于调试release发布程序的crash错误系列文章就写完了。这几篇文章给出的方案由简单到复杂,由简陋到完善,对crash调试有了一个比较全面的总结。当然,其中涉及到的概念和技术还很多,需要我们去不断学习和领悟,也希望大家能够互相交流。特别声明:1:资料来源于互联网,版权归属原作者2:资料内容属于网络意见,与本账号立场无关3:如有侵权,请告知,立即删除。

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2