如何定位Release 版本中程序崩溃的位置 ---利用map文件 拦截windows崩溃函数
1 案例描述
作为Windows程序员,平时最担心见到的事情可能就是程序发生了崩溃(异常),这时Windows会提示该程序执行了非法操作,即将关闭。请与您的供应商联系。呵呵,这句微软的“名言”,恐怕是程序员最怕见也最常见的东西了。
在一个大型软件的测试过程中,初期出现程序崩溃似乎成了不可避免的事。其实测试中出现程序崩溃并不可怕,反而是测试的成功。作为开发的我们更需要关心的是程序中的哪个函数或哪一行导致了系统崩溃,这样才能有针对性的进行改正。
本文描述了自己总结的几种定位崩溃的办法。
2 案例分析
以下是几种常见的崩溃现象及对应的处理办法:
1. 对于Release版本必现的崩溃且在Debug版本上也崩溃的程序。
解决思路:去掉所有断点,直接在Debug版本上运行程序,在程序崩溃时,VC会自动跳转定位到崩溃代码行, 这种方法最简单也最常用。
2. 对于在Debug版本上不崩溃但Release版本崩溃的程序,很有可能是Debug和Release版本的差异。例如Debug版本所有成员在构造时会被清0,而Release版本所有成员在构造时是内存里面的原始值,而且Debug有运行时库做保护,这些都会导致某些程序在Debug正常而Release崩溃。
解决思路:1)在程序中加打印,通过程序崩溃之前的打印定位出错位置; 2)逐段注释代码,直到程序不崩溃为止。这种方法耗时较长,对程序员要求较高,而且对于那种不是必现的bug或者很难搭建执行环境的情况就较难处理了。
3. 对于在客户现场崩溃的情况,显然不适合直接带一台电脑去调试。
解决思路:应该有文件记录下崩溃信息,客服人员可以将崩溃信息文件发送给程序员,以便程序员查询崩溃原因,然后利用编译时生成MAP文件(工程信息文件,存放在版本编译机中)的信息来定位问题函数或问题代码行。下面就这种方法展开讨论一下:
3 解决过程
对于上节第三种情况,也是最难解决的情况,解决过程如下:
1. 崩溃回调注册,拦截Windows程序崩溃;
2. 在回调处理中,输出崩溃原因,崩溃内存地址,崩溃堆栈;
3. 工程输出map文件;
4. 通过崩溃内存地址以及map文件找出崩溃的函数。
5. 使用COD文件精确定位崩溃行
3.1 崩溃回调注册
实际上,只靠Windows的错误消息对话框提供的信息量是很有限的。用SetUnhandledExceptionFilter注册自定义错误处理回调函数,可以替换Win32默认的异常处理过滤器(top-level exception filter),而且能打印出崩溃堆栈,这对定位崩溃原因非常有用。
SetUnhandledExceptionFilter的函数原型:
|
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter( LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ); |
功 能:注册和注销异常处理回调;
用 法:第一次调用注册异常处理回调,第二次调用注销;
返回值:返回当前的exception filter。需要保存这个函数指针,在注销异常处理回调的时候,以此为参数再次调用SetUnhandledExceptionFilter。打印异常处理也需要此值。
参数: 异常处理的回调函数;
3.2 输出崩溃信息
崩溃信息在异常回调函数中打印,输出到程序执行目录下的文件:
异常处理回调的函数原形:
|
LONG WINAPI CallBackDebugInfo ( EXCEPTION_POINTERS *pException); |
功 能:异常处理回调处理,打印崩溃信息;
用 法:注册自定义错误处理回调:SetUnhandledExceptionFilter (CallBackDebugInfo);
返回值:EXCEPTION_CONTINUE_EXECUTION – 错误已经被修复,从异常发生处继续执行
EXCEPTION_CONTINUE_SEARCH – 继续查找异常过滤器
EXCEPTION_EXECUTE_HANDLER – 正常返回
参数: 崩溃信息结构,包含崩溃原因、崩溃模块、崩溃地址、崩溃堆栈等;
常见崩溃原因有:
EXCEPTION_ACCESS_VIOLATION = C0000005h 读写内存错误
EXCEPTION_INT_DIVIDE_BY_ZERO = C0000094h 除0错误
EXCEPTION_STACK_OVERFLOW = C00000FDh 堆栈溢出或者越界
EXCEPTION_GUARD_PAGE = 80000001h 由Virtual Alloc建立起来的属性页冲突
EXCEPTION_NONCONTINUABLE_EXCEPTION = C0000025h不可持续异常,程序无法恢复执行,异常处理例程不应处理这个异常
EXCEPTION_INVALID_DISPOSITION = C0000026h在异常处理过程中系统使用的代码
EXCEPTION_BREAKPOINT = 80000003h 调试时中断(INT 3)
EXCEPTION_SINGLE_STEP = 80000004h 单步调试状态(INT 1)
3.3 输出map文件
map文件记录程序的全局符号、源文件和代码行号信息,是整个程序工程信息的静态文本。通过文本阅读工具如Ultra Edit或记事本就可以打开Map文件。
在 VC 中,打开“Project Settings”选项页,选择 C/C++ 选项卡,并在最下面的 Project Options 里面输入:/Zd ,然后选择 Link 选项卡,选中“Generate mapfile”复选框。并在最下面的 Project Options 里面输入:/mapinfo:lines,表示生成 map 文件时,加入行信息。
最后编译就可以生成 MAP 文件,可以在工程的Debug或Release目录下找到刚刚生成的MAP文件,文件名为“工程名.map”。
3.4 使用map文件找出崩溃函数
通过上面的步骤,已经得到了 MAP 文件,那么我们该如何利用它呢?下面一步步演示使用MAP文件定位程序崩溃行的过程。
1.我们先在代码中加入非法内存操作(最常见的异常)的代码:
|
BOOL CMainFrameDlg::OnInitDialog() { ::SetProp(m_hWnd,AfxGetApp()->m_pszExeName, (HANDLE)1); s32 *p=NULL; *p= 123; |
2.执行程序,程序在开始就异常,在异常打印文件中打印了如下信息:
|
======================== 崩溃信息 ========================== 崩溃时间: 2009/06/02 16:58:22 崩溃原因:非法内存操作 异常代码 = c0000005 异常地址 = 0x0045a76f 异常模块: E:\ccroot\liuxiaojing_Enterprise\Enterprise_VOB\70-nms1\pcmt2\prj_win32\Release\pcmt2.exe Section name: .text - offset(rva) : 0x0005976f ---------------------- Trips of Stack ---------------------- E:\ccroot\liuxiaojing_Enterprise\Enterprise_VOB\70-nms1\pcmt2\prj_win32\Release\pcmt2.exe name : pcmtver - location: 2bef |
3.确定崩溃地址是:0x0005976f,在Map文件中定位函数:
|
0001:00059420 ?OnCreate@CMainFrameDlg@@IAEHPAUtagCREATESTRUCTA@@@Z 0045a420 f MainFrameDlg.obj 0001:00059460 ?SetTooltips@CMainFrameDlg@@AAEXXZ 0045a460 f MainFrameDlg.obj 0001:00059700 ?OnTranslate@CMainFrameDlg@@IAEJIJ@Z 0045a700 f MainFrameDlg.obj 0001:00059730 ?OnInitDialog@CMainFrameDlg@@MAEHXZ 0045a730 f MainFrameDlg.obj 0001:00059a10 ?OnSysCommand@CMainFrameDlg@@IAEXIJ@Z 0045aa10 f MainFrameDlg.obj 0001:00059c20 ?OnPaint@CMainFrameDlg@@IAEXXZ 0045ac20 f MainFrameDlg.obj |
根据00059730< 0005976f < 00059a10 ,确定是在CMainFrameDlg 的OnInitDialog函数中的某一行产生了异常。
3.5 使用map代码行定位崩溃行区间
|
Line numbers for .\Release\MainFrameDlg.obj(E:\ccroot\liuxiaojing_Enterprise\Enterprise_VOB\70-nms1\pcmt2\source\MainFrameDlg.cpp) segment .text 498 0001:00059647 499 0001:00059667 501 0001:0005966e 502 0001:000596af 503 0001:000596ed 506 0001:00059700 507 0001:00059703 508 0001:00059708 510 0001:0005970f 511 0001:00059720 512 0001:00059723 515 0001:00059730 516 0001:0005974e 521 0001:0005976d 524 0001:0005977e 526 0001:0005978b |
我们在map文件的代码行信息里查找不超过计算结果0x0005976f,但可以找最接近的数。发现是MainFrameDlg.cpp 文件中的:521 0001:0005976d,而程序实际崩溃行在519(注释行和空行也要计算在内),非常接近实际崩溃行了,考虑到程序实际执行的是汇编指令,我们可以在(516 ~524)行区间内寻找到实际崩溃行。
如何定位Release 版本中程序崩溃的位置 ---利用map文件 拦截windows崩溃函数的更多相关文章
- spring cloud:Edgware.RELEASE版本中zuul回退方法的变化
Edgware.RELEASE以前的版本中,zuul网关中有一个ZuulFallbackProvider接口,代码如下: public interface ZuulFallbackProvider { ...
- IOS 项目release版本中关闭NSlog的打印
在-Prefix.pch文件中添加如下代码: #ifdef DEBUG #define NSLog(...) NSLog(__VA_ARGS__) #define debugMethod() NSLo ...
- C#.NET常见问题(FAQ)-如何生成release版本的程序,生成debug版本的程序
除了右击项目在生成中配置改成Release还要在顶部切换成Release 更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空间: http://i.youku.com/acetaohai1 ...
- C#如何生成release版本的程序,生成debug版本的程序
除了右击项目在生成中配置改成Release还要在顶部切换成Release
- debug、 release两个版本中正确运行的一些经验
在Qt编程中,默认的是debug版本,在编译器中可以正常的使用,但是单独运行.exe可执行文件时却发现系统提示缺少文件.其实就是缺少必要的.dll动态库文件.根据提示添加需要的.dll动态库文件即可. ...
- 【转】调试Release发布版程序的Crash错误
http://www.cppblog.com/Walker/archive/2012/11/08/146153.html http://blog.sina.com.cn/s/blog_48f93b53 ...
- 调试Release发布版程序的Crash错误
http://www.cppblog.com/Walker/archive/2012/11/08/146153.html http://blog.sina.com.cn/s/blog_48f93b53 ...
- 调试Release发布版程序的Crash错误(转)
http://blog.sina.com.cn/s/blog_48f93b530100fsln.html 在Windows平台下用C++开发应用程序,最不想见到的情况恐怕就是程序崩溃,而要想解决引起问 ...
- MFC中release版本和debug版本区别
最近MFC写了个程序,生成release版,原来正常,后来删掉了些控件再编译运行,结果竟然报内存读写错误,debug却是正常的.后来将“Project Settings” 中 “C++/C ...
随机推荐
- VR开发中性能问题—OculusWaitForGPU
http://blog.csdn.net/cartzhang/article/details/50788894 VR开发中性能问题-OculusWaitForGPU 本文章由cartzhang编写,转 ...
- visual studio 2015预览版系统需求
visual studio 2015预览版的系统需求跟visual studio 2013的一样. 支持visual studio 2015 preview的操作系统:Windows 8.1(x86 ...
- jFinal中报对应模型不存在的错误(The Table mapping of model: demo.User not exists)
jFinal中报对应模型不存在的错误(The Table mapping of model: demo.User not exists) 贴出错误: java.lang.RuntimeExceptio ...
- ios 解析json,xml
一.发送用户名和密码给服务器(走HTTP协议) // 创建一个URL : 请求路径 NSString *urlStr = [NSString stringWithFormat:@"ht ...
- Codewars编辑题--今天升到了7段
今天做的题目是:写一个函数toWeirdCase(),对给定的一个字符串string进行偶数位(包括0)变成大写的操作,字符串string分为单个单词的字符串和多个单词组成的句子.效果应该是这个样子滴 ...
- 老老实实学习WCF[第二篇] 配置wcf
老老实实学WCF 第二篇 配置WCF 在上一篇中,我们在一个控制台应用程序中编写了一个简单的WCF服务并承载了它.先回顾一下服务端的代码: using System; using System.Col ...
- JAVA解析xml的五种方式比较
1)DOM解析 DOM是html和xml的应用程序接口(API),以层次结构(类似于树型)来组织节点和信息片段,映射XML文档的结构,允许获取 和操作文档的任意部分,是W3C的官方标准 [优点] ① ...
- spring定时器 @Scheduled
需要注意的几点: 1.spring的@Scheduled注解 需要写在实现上. 2. 定时器的任务方法不能有返回值(如果有返回值,spring初始化的时候会告诉你有个错误.需要设定一个proxyta ...
- 暑假集训(5)第一弹——— Super Jumping! Jumping! Jumping!(hdu1087)
题意概括:在上次与娑殚的三次博弈中,你们都取得了胜利.便向娑殚提出要求,借助他的力量,传送到一个安全的地方. 你们的愿望达成了,不过,你和小A似乎失散了. 街上人来人往的特别热闹,每一个人的脸上都洋溢 ...
- Shell 循环读取文件
使用Shell将Windows环境下的文件拷贝到Linux下面的用法. 在linux下,将dos文件格式转换成linux文件格式的用法,vi打开,然后转到命令格式,执行,然后保存,就可以转换成linu ...