VC下发布的Release版程序的异常捕捉
寻找Release版程发生异常退出的地方比Debug版麻烦得多。发生异常的时候windows通常会弹出一个错误对话框,点击详细信息,我们能获得出错的地址和大概的出错信息,然后可以用以下办法分析我们的程序。
  
一.     用MAP文件定位异常代码位置。
1.         如何生成map文件
打开“Project → Project Settings”,选择 C/C++ 选项卡,在“Debug Info”栏选择“Line Numbers Only”(或者在最下面的 Project Options 里面输入:/Zd),然后要选择 Link 选项卡,选中“Generate mapfile”复选框,并再次编辑 Project Options,输入:/mapinfo:lines,以便在 MAP 文件中加入行信息。然后编译工程则可以在输出目录得到同名的.map文件。
2.         使用map文件定位发生异常的代码行
编译得到的map文件可以用文本方式打开,大致是这样的格式:(括号内是PomeloWu填加的注释)
0729                 (←工程名)    
Timestamp is 42e9bc51 (Fri Jul 29 14:19:13 2005)    (←时间戳)    
Preferred load address is 00400000         (←基址)
  ……(Data段描述,省略)    
Address         Publics by Value              Rva+Base     Lib:Object   0001:00000000       ?_GetBaseMessageMap@C0729App@@KGPBUAFX_MSGMAP@@XZ 00401000 f   0729.obj……(↑这一行开始是函数信息,下面省略)
Line numbers for ./Release/ShowDlg.obj(C:/0729/ShowDlg.cpp) segment .text      24 0001:00003f90    28 0001:00003fce    29 0001:00003fd1    30 0001:00003fd4……(行号信息,前面的数字是行号,后一个数字是偏移量,下面省略)
  在获得程序异常的地址以后,首先通过函数信息部分定位出错的OBJ和函数。做法是用获得的异常地址与Rva+Base栏地址进行比较(Rva,偏移地址;Base,基址)。找到最后一个比获得的异常地址小的那个函数,那就是出错的函数。
之后,用获得的异常地址减去该函数的Rva+Base,就得到了异常行代码相对于函数起始地址的偏移。在“Line number for”部分找到相对应的模块,并把其后的行号信息与上面减得的偏移量对比,找到最接近的一个,前面的行号大致就是目标行了。
  
二.     获得错误的详细信息。
实际上,光靠Windows的错误消息对话框提供的信息量是很有限的,用自己写的exception filter可以获得更多的错误信息。用SetUnhandledExceptionFilter设定自定义错误处理回调函数替换Win32默认的top-level exception filter:
²         SetUnhandledExceptionFilter的函数原型:
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
  LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
                                   // exception filter function  
);
²         SetUnhandledExceptionFilter返回当前的exception filter。应当保存这个函数指针并在不再需要使用自定义错误处理函数的时候当作参数再次调用SetUnhandledExceptionFilter。
  
²         lpTopLevelExceptionFilter 是自定义的exception filter函数指针,如果传入NULL值则指定UnhandledExceptionFilter来负责异常处理。lpTopLevelExceptionFilter其函数原型应该是与UnhandledExceptionFilter同型:
LONG WINAPI UnhandledExceptionFilter(
  STRUCT _EXCEPTION_POINTERS *ExceptionInfo   // address of
                                              // exception info
);
²         lpTopLevelExceptionFilter的返回值应该是下面3种之一:
EXCEPTION_EXECUTE_HANDLER = 1
EXCEPTION_CONTINUE_EXECUTION = -1
这两个返回值都应该由调用UnhandledExceptionFilter后返回。
EXCEPTION_EXECUTE_HANDLER 表示进程结束
EXCEPTION_CONTINUE_EXECUTION表示处理异常之后继续执行
EXCEPTION_CONTINUE_SEARCH = 0
进行系统通常的异常处理(错误消息对话框)
  
²         lpTopLevelExceptionFilter的唯一的参数是_EXCEPTION_POINTERS结构指针。
typedef struct _EXCEPTION_POINTERS { // exp
    PEXCEPTION_RECORD ExceptionRecord;
    PCONTEXT ContextRecord;
} EXCEPTION_POINTERS;
其中PCONTEXT是一个指向进程上下文结构的指针,保存了各个寄存器在异常发生的时候的值,详细信息参考《Windows核心编程》。
ExceptionRecord则指向另一个结构体EXCEPTION_RECORD:
typedef struct _EXCEPTION_RECORD { // exr
    DWORD ExceptionCode;
    DWORD ExceptionFlags;
    struct _EXCEPTION_RECORD *ExceptionRecord;
    PVOID ExceptionAddress;
    DWORD NumberParameters;
    DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
DWORD ExceptionCode;
异常代码,指出异常原因。常见异常代码有:
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)
DWORD ExceptionFlags;
异常标志
0,表示可修复异常
EXCEPTION_NONCONTINUABLE = 1,表示不可修复异常。在不可修复异常后尝试继续执行会导致EXCEPTION_NONCONTINUABLE_EXCEPTION = C0000025H异常。
struct _EXCEPTION_RECORD *ExceptionRecord;
当异常处理程序中发生异常时,此字段被填充,否则为NULL
PVOID ExceptionAddress;
发生异常的地址(EIP)
DWORD NumberParameters;
规定与异常相关的参数数量(0-15),是ExceptionInformation数组中元素个数。
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
异常描述信息,大多数异常都未定义此数组,仅有EXCEPTION_ACCESS_VIOLATION 异常的描述信息:
ExceptionInformation[0],描述导致异常的操作类型
= 0 读异常
= 1 写异常
ExceptionInformation[1],发生读写异常的内存地址  
也就是说,只要注册了自己写的这个exception filter,一旦发生异常,进入这个exception filter,从参数我们就能获得各种需要的信息了。而这个exception filter需要做的就是保存这些信息,然后将异常处理的事情交还给系统就行了:
// in the beginning
// Install the unhandled exception filter function
g_previousFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
  
// exception filter
LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
{
    WriteLogFile(pExceptionInfo);  // 写入文件
    if ( g_previousFilter )
           return g_previousFilter( pExceptionInfo );
        else
            return EXCEPTION_CONTINUE_SEARCH;
    }
  
三.     使用COD文件精确分析异常原因
说精确分析多少有点言过其实,发生异常的情况各不相同,分析真正原因很可能是一件极其复杂的事情。不过用COD文件能比MAP文件更精确地定位产生异常的位置。结合汇编代码和自定义的exception filter获得的错误情报寄存器状态等各种信息,找到异常发生的直接原因是很容易的。
1.         如何生成cod文件
仍然是打开“Project → Project Settings”,选择 C/C++ 选项卡,在“Category”栏选择“Listing Files”然后在Listing file type栏选择“Assembly with Machine Code”。重新编译工程后则可以在输出目录看到与每一个.cpp文件同名的.cod文件。
2.         Cod文件的使用
首先还是利用map文件用获得的程序异常地址通过函数信息部分定位出错的OBJ和函数,并同样记录偏移地址(用获得的异常地址减去该函数的Rva+Base的差值)。然后,在相应的cod文件中(而不是在map文件后面的行号信息部分)来查找出错的函数,找到如下的格式:
?OnPaint@CShowDlg @@IAEXXZ PROC NEAR ; CShowDlg::OnPaint, COMDAT   ; 81   : {   (←格式为: 行号 : 源代码)     00000 83 ec 64 sub esp, 100 ; 00000064H (↑偏移地址)  (↖机器码)   (↑汇编码)   00003 56 push esi   ; 82   : if (IsIconic()) (下面省略)
   找到出错的函数以后,再用偏移地址就能找到准确的异常发生的地方。然后通过源程序、汇编码即可进行更详尽的分析了。

VC下发布的Release版程序的异常捕捉相关推荐

  1. Qt软件发布(版本信息,Release版程序,代码打包,制作安装包)

    序言 当我们完成了Qt程序的开发,希望交予测试,或是正式发布的时候,需要将我们的程序进行层层封装,最终以一个安装包的形式呈现给用户.专业版的软件发布,以Qt软件为例,需要三个步骤:生成版本信息,生成R ...

  2. VC下Debug 和Release 区别【转】

    在工具栏上点右键,选择编译,将编译win32debug改为win32 Release即可.如果需要调试的话,还需要再改回来方可. 最近写代码过程中,发现 Debug 下运行正常,Release 下就会 ...

  3. assert 闪退 android,strcpy函数导致release版程序崩溃

    最近在写一个读取模型文件的小程序.很随意的使用了strcpy函数进行char字符数组的拷贝,这个数组是需要传递给PostMessage作为WPARAM的参数.代码部分如下: char pStrCurr ...

  4. VC++下使用ADO编写数据库程序 – 操作大全

    准备: (1).引入ADO类 1 #import "c:/program files/common files/system/ado/msado15.dll" / 2 no_nam ...

  5. 关于Debug版正常运行,release版运行崩溃的问题

    关于Debug版正常运行,release版运行崩溃的问题 通常情况下,Debug版用于程序的调试优化,尽可能将遇到的BUG找到并解决掉: 这样编译出的Release版才可能有效可靠的运行.然而如果编译 ...

  6. win任务栏计算机变未知,如何解决Win7打开程序出现异常未知的软件异常

    时我们打开win7系统电脑时,上面的某个程序软件会打不开,出现错误提示"应用程序发生异常未知的软件异常,今天学习啦小编给大家介绍下如何解决Win7打开程序出现异常未知的软件异常吧. 解决Wi ...

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

    订阅 调试Release发布版程序的Crash错误 http://dingchaoqun12.blog.163.com/blog/static/116062504201152834814661/ 在W ...

  8. 转:调试Release发布版程序的Crash错误

    在Windows平台下用C++开发应用程序,最不想见到的情况恐怕就是程序崩溃,而要想解决引起问题的bug,最困难的应该就是调试release版本了.因为release版本来就少了很多调试信息,更何况一 ...

  9. 项目发布Debug和Release版的区别

    https://www.cnblogs.com/taiyonghai/p/6126074.html 一.Debug和Release的区别 Debug:调试版本,包含调试信息,所以容量比Release大 ...

最新文章

  1. php 多维数组排序_已迁移
  2. maven指定构建的编码格式
  3. 修改unity变量名但不丢失序列化值
  4. NAND FLASH 和NOR FLASH工作原理
  5. 批量绑定(bulk binds):FOR循环与FORALL的性能比较
  6. Gradle task
  7. oracle 表关联索引优化,Oracle执行计划调优-超级大表关联超级小表的性能调优
  8. PhpStudy升级数据库到mysql5.7方法
  9. java压缩图片maven_java – 在maven构建中集成yahoo smush.it以进行图像压缩
  10. linux系统密码自动丢失,Linux系统密码丢失后的5种解决方法
  11. 怎么用U盘安装ubuntu系统具体步骤图文详解
  12. 盘点 7 个超级 Nice 的微信小程序项目
  13. 专业人员选择关键词的标准和原则
  14. “百度有啊”可以访问了,大家预测一把其前景如何?
  15. window驱动签名相关以及WHQL(代码签名证书:赛门铁克和DigiCert证书)
  16. Elasticsearch:使用向量搜索来查询及比较文字 - NLP text embedding
  17. python多重插补_5.4 缺失值插补
  18. web前端零基础html5
  19. 《程序员修炼之道:从小工到专家》The Pragmatic Programmer: From Journeymen to Master
  20. Apache官网下载ant软件包及安装详解

热门文章

  1. 就编程而言,可移植性意味着什么?
  2. eureka自我保护功能
  3. Linux 中/etc/profile、~/.bash_profile 环境变量配置以及区别
  4. SpringCloud 入门教程(五): Ribbon实现客户端的负载均衡
  5. cjson 对象是json数组型结构体_C语言 - cJSON解析特定格式 含有数组array类型的数据...
  6. python解析不完全的html_【已解决】Scrapy的Python中如何解析部分的html字符串并格式化为html网页源码...
  7. hive 删除分区_数据仓库工具hive面试题集锦(纯干货)
  8. 2018中国国际大数据大会专属报名通道(免费)开通啦!
  9. 【软件工程】抽象泄漏
  10. 【JSP】JSP的运行原理