Win32环境下的程序崩溃异常定位
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)行区间内寻找到实际崩溃行。
Win32环境下的程序崩溃异常定位的更多相关文章
- NET环境下的未处理异常(unhandled exception)的解决方案
NET环境下的未处理异常(unhandled exception )的解决方案 .Net 框架提供了非常强大的异常处理机制,同时对一些非托管代码很难控制的系统问题比如指针越界,内存泄漏等提供了很好的解 ...
- QT中使用google breakpad捕获程序崩溃异常
今天给大家介绍一个在linux下如何捕获程序崩溃异常的方法 一.google breakpad源码的下载和编译 1.https://github.com/google/breakpad.git,源码地 ...
- Android程序崩溃异常收集框架
最近在写Android程序崩溃异常处理,完成之后,稍加封装与大家分享. 我的思路是这样的,在程序崩溃之后,将异常信息保存到一个日志文件中,然后对该文件进行处理,比如发送到邮箱,或发送到服务器. 所以, ...
- cefSharp在XP下使得程序崩溃记录
前言:这是一个奇葩的问题,到现在自己还没有搞明白问题出现在哪里,但是从问题总算是解决了,希望看到此文章的大牛,如果知道问题出在什么地方,可以告知一下. [一个在XP系统下面应用程序崩溃问题] 资源: ...
- Win32环境下代码注入与API钩子的实现(转)
本文详细的介绍了在Visual Studio(以下简称VS)下实现API钩子的编程方法,阅读本文需要基础:有操作系统的基本知识(进程管理,内存管理),会在VS下编写和调试Win32应用程序和动态链接库 ...
- Win32环境下代码注入与API钩子的实现
本文详细的介绍了在Visual Studio(以下简称VS)下实现API钩子的编程方法,阅读本文需要基础:有操作系统的基本知识(进程管理,内存管理),会在VS下编写和调试Win32应用程序和动态链接库 ...
- 如何在Linux桌面环境下自动启动程序?
大多数Linux桌面环境有各自的图形用户界面(GUI),让用户可以配置针对特定用户的自动启动程序或服务.本文将介绍如何在各种Linux桌面环境下,自动启动某个程序的方法. AD:WOT2014:用户标 ...
- 项目部署到liunx环境下访问接口返回异常
1.访问接口返回异常 已经连续踩了两次这个坑了.所以记下来了.方便下次搜索! 项目在window下运行正常,无任何异常! 但是部署到liunx环境下的服务器上就有问题 访问静态页面毫无问题,一旦涉及到 ...
- 用vs调式docker环境下web程序的一处坑
新建web项目,此时launchSettings.json文件为: "iisSettings": { "windowsAuthentication": fals ...
随机推荐
- 【笨木头Lua专栏】基础补充08:协同程序之resume-yield间的数据返回
这次要介绍几个事实上非常easy,可是一定要小心的返回值规则. 笨木头花心贡献,哈?花心?不,是用心~ 转载请注明.原文地址: http://www.benmutou.com/archives/173 ...
- BT基础知识简介
1. 蓝牙概述 无线局域网的通信 适用范围:10米到100米(根据发射功率的class不同有所差别,典型的class2为10m,而class1为100m,class3为1m) 应用: 局域网络 ...
- [译]URL和URI的区别
译者:华科小涛:http://www.cnblogs.com/hust-ghtao/: 初学http协议,就被这两个相似的术语搞蒙了,查了很多资料,总算搞清楚了.(找资料还是英文啊,靠谱...). 本 ...
- jsp_javabean
什么是javabean? 遵循一定的设计原则的任何java类都可以是javabean组件 1.可序列化 implements serializable 2.无参数的构造方法 3.私有属性 通过公有方法 ...
- zookeeer 集群和伪集群模式
环境变量设置: # .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # U ...
- 辛星和您一起手写CSS气泡
上文中我公布了一篇手写导航条的博客,那么这一篇博客我将和大家一起手写气泡.那么什么是气泡呢?先给那些刚入门的童鞋一个截图,来更好的认识一下什么是气泡把: 这就是一个简单的气泡啦,那么它主要用来干什么呢 ...
- K&R练习题6-1统计关键词出现的次数
这道练习题训练了: 1.结构体数组 2.二分查找 3.指针操作 ---- 都不难.但非常基础,我认为非常好,做完了记到博客上来,题目见k&R,实现例如以下: /* * Practice of ...
- android Gallery滑动不流畅的解决
import android.content.Context; import android.util.AttributeSet; import android.view.KeyEvent; impo ...
- 常用字符串string
字符串 特性: 不可变性 当一个字符串赋新值时,老值依旧在(靠GC来回收) 可以看做是char类型的只读数组 eg:string str="abcd"; str[0]---> ...
- Cloud Foundry 中国群英会【上海站、成都站】资料宣传
关注云计算和PaaS层的童鞋可以了解下: http://www.cloudfoundry-heroes-summit.com/shanghai http://www.cloudfoundry-hero ...