[转]VC下发布的Release版程序的异常捕捉

转载 2013年12月04日 11:44:25

转自:http://blog.csdn.net/xwjbs/article/details/1423992


寻找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版程序的异常捕捉

   寻找Release版程发生异常退出的地方比Debug版麻烦得多。发生异常的时候windows通常会弹出一个错误对话框,点击详细信息,我们能获得出错的地址和大概的出错信息,然后可以用以下办法分析我...
  • xwjbs
  • xwjbs
  • 2006年12月01日 10:43
  • 2448

VC下发布的Release版程序的异常捕捉

VC下发布的Release版程序的异常捕捉寻找Release版程发生异常退出的地方比Debug版麻烦得多。发生异常的时候windows通常会弹出一个错误对话框,点击详细信息,我们能获得出错的地址和大概...
  • i_like_cpp
  • i_like_cpp
  • 2008年02月14日 09:54
  • 831

VC下发布的Release版程序的异常捕捉

  寻找Release版程发生异常退出的地方比Debug版麻烦得多。发生异常的时候windows通常会弹出一个错误对话框,点击详细信息,我们能获得出错的地址和大概的出错信息,然后可以用以下办法分析我们...
  • iiprogram
  • iiprogram
  • 2006年12月21日 08:32
  • 1164

如何使用VC++6.0发布程序(即release版本程序)

大家都知道VC编译器默认生成debug版本的程序,但是debug版本程序无法运行在没有安装VC的电脑上, 这就要就我们生成release版本的程序,因为release版本在未安装VC的电脑上也能运行(...
  • rl529014
  • rl529014
  • 2016年04月21日 21:04
  • 2130

解决try-catch 在RELEASE版无法捕捉错误

问题: try-catch 在RELEASE版无法捕捉错误,而DEBUG可以。答案:这个跟C++的try-catch异常捕获的两种模式有关。同步模式和异步模式。其中前者不能捕获内存访问错误,后者可以捕...
  • wzsy
  • wzsy
  • 2010年01月20日 11:09
  • 1798

VS2010无法捕获try catch的问题 解决

最近将公司的项目从VS2003升级到2010发现一个问题,VS2010编绎发布版本的时候,无法捕获到try catch的问题,导致程序经常挂掉,查了相关资料发现是VS2010发布Release默认不捕...
  • zhaobangyu
  • zhaobangyu
  • 2016年02月27日 15:45
  • 1836

QT5.7 Windows下发布release程序

在Windows发布release程序有两种方式: 一、使用Qt自带工具windeployqt进行。具体步骤如下: 1、把自己编译的exe,放入一个新的空文件夹中。我这里是在桌面新建了一个test...
  • thedarkfairytale
  • thedarkfairytale
  • 2017年11月23日 14:15
  • 234

VC++异常捕获

这里强调了是VC++中的异常捕获; 通常在C++中的异常捕获是: void Func1() { int* p = 0x00000000;   // pointer to NULL *p = 10;...
  • chinabinlang
  • chinabinlang
  • 2016年06月12日 11:50
  • 961

Debug系列:VC++程序Release版崩溃的解决办法

from: http://rendao.org/blog/1999/ Debug系列:VC++程序Release版崩溃的解决办法 VC++程序发布后,如果在客户那运行崩溃,且研发环境下无法本地...
  • eatjpg
  • eatjpg
  • 2015年09月22日 14:02
  • 361

vs下debug运行正常,release下出现异常

VC下Debug和Release区别 最近写代码过程中,发现 Debug 下运行正常,Release 下就会出现问题,百思不得其解,而Release 下又无法进行调试,于是只能采用printf方...
  • xietingcandice
  • xietingcandice
  • 2013年06月13日 15:07
  • 2349
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:[转]VC下发布的Release版程序的异常捕捉
举报原因:
原因补充:

(最多只允许输入30个字)