异常处理与MiniDump 用于投放市场c++异常捕获
最近一段时间,新上线的软件在外场偶尔会出现异常崩溃的情况。由于试用范围比较分散,很难一一前往现场定位问题。而传统的log日志方法,在崩溃的情况下,并不能比较准确的表示出问题位置,这使得软件调试进程缓慢。
后在公司前辈的指点下,我们想到了使用window自带的dumpfile来记录崩溃时刻的堆栈信息,这样配合log日志记录,能够快速的定位出问题点。大大提高了系统调试效率。
经过一段时间的调试,现在项目已相对稳定了。想记录下此方法,以待后续类似情况下使用。
- //使所有版本都可以捕获到异常
- void DisableSetUnhandledExceptionFilter()
- {
- void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")), "SetUnhandledExceptionFilter");
- if (addr)
- {
- unsigned char code[16];
- int size = 0;
- code[size++] = 0x33;
- code[size++] = 0xC0;
- code[size++] = 0xC2;
- code[size++] = 0x04;
- code[size++] = 0x00;
- DWORD dwOldFlag, dwTempFlag;
- VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
- WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
- VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
- }
- }
- //程序未捕获的异常处理函数
- LONG WINAPI ExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)
- {
- ::AfxMessageBox("ExceptionFilter");
- HANDLE hFile = ::CreateFile( _T("C:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
- if( hFile != INVALID_HANDLE_VALUE)
- {
- MINIDUMP_EXCEPTION_INFORMATION einfo;
- einfo.ThreadId = ::GetCurrentThreadId();
- einfo.ExceptionPointers = ExceptionInfo;
- einfo.ClientPointers = FALSE;
- ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpWithFullMemory, &einfo, NULL, NULL);
- ::CloseHandle(hFile);
- }
- return 0;
- }
- 然后在程序入口将异常处理接口声明即可。
- //调试信息
- ::SetUnhandledExceptionFilter(ExceptionFilter); //设置异常处理函数
- DisableSetUnhandledExceptionFilter(); //获取未处理的异常
这样,在程序异常时,就可以在C盘根目录下记录一个dumpfile.dmp的文件。这个文件会比较大,一般有100多M,其中信息比log形式的日志丰富很多,包括了异常时的堆栈调用关系以及各对象的值。,在VS中可以直接打开。如果保留了和当时编译软件一致的代码备份的话,可以直接使用VS的debug功能定位到问题代码行,否则,debug定位是到汇编代码行,看起来比较麻烦。
一、 综述
总算讲到MiniDump了。
Dump有多有用我都无法尽数,基本上属于定位错误修复BUG的倚天剑。(日志可以算是屠龙刀)这些都是对于那些不是必出的BUG,放在外面运行的时候出现的BUG而言的,那些能够通过简单调试就能发现的BUG,一般都不足为惧。
二、 基本应用
MiniDump之所以叫MiniDump,自然是有其Mini之处。。。(废话),呵呵,MS提供了一个API函数,MiniDumpWriteDump,(在Dbghelp.h中声明,需要导入DbgHelp.lib使用)所以我才将其称为MiniDump,其实Dump也能表达同样的意思。。。。
MiniDump最简单的应用在于程序崩溃的时候,将崩溃时那一刻的信息写进一个文件,以方便以后查找错误。使用方法说简单就简单,说难也难。
1. 怎么感知到程序的崩溃?
Window提供了较为方便的方法去感知到程序的几种崩溃情况。
在《Breakpad在进程中完成dump的流程描述》一文中,我描述了一下Breakpad获取到程序崩溃的方法,事实上,这也是典型的Windows下感知程序崩溃的方法,那篇文章是刚开始工作的时候,完成公司自己的ExceptionHandle库的时候写的工作笔记,现在看起来也还是有一定的参考价值。
Windows下感知程序崩溃(其实就是运行时的严重错误)的方法有3个核心的函数,分别如下:
SetUnhandledExceptionFilter(HandleException)确定出现没有控制的异常发生时调用的函数为HandleException.
_set_invalid_parameter_handler(HandleInvalidParameter)确定出现无效参数调用发生时调用的函数为HandleInvalidParameter.
_set_purecall_handler(HandlePureVirtualCall)确定纯虚函数调用发生时调用的函数为HandlePureVirtualCall.
3个函数的使用方法一致,都是在发生自己关心的(见上面的描述)异常时,调用参数传进来回调函数,Windows会将崩溃信息通过参数传入回调函数,这时候就是进行Dump的绝佳时机。详细的信息可以查阅MSDN,我这里就不复制资料了,那样有copy文档之嫌,这里以SetUnhandledExceptionFilter为例,演示实际与MiniDumpWriteDump配合使用的情况。像这些比较复杂的API,MSDN中连个Example都没有,说实话,当时掌握花了一点时间。
ExceptionExample:
#include <windows.h>
#include <Dbghelp.h>
using namespace std;
#pragma auto_inline (off)
#pragma comment( lib, "DbgHelp" )
// 为了程序的简洁和集中关注关心的东西,按示例程序的惯例忽略错误检查,实际使用时请注意
LONG WINAPI MyUnhandledExceptionFilter(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
HANDLE lhDumpFile = CreateFile(_T("DumpFile.dmp"), GENERIC_WRITE,
0, NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);
MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;
loExceptionInfo.ExceptionPointers = ExceptionInfo;
loExceptionInfo.ThreadId = GetCurrentThreadId();
loExceptionInfo.ClientPointers = TRUE;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),lhDumpFile, MiniDumpNormal,
&loExceptionInfo, NULL, NULL);
CloseHandle(lhDumpFile);
return EXCEPTION_EXECUTE_HANDLER;
}
void Fun2()
{
int *p = NULL;
*p = 0;
}
void Fun()
{
Fun2();
}
int main()
{
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
Fun();
return 1;
}
API的调用仅仅作为释放,查看下MSDN就知道使用方法了,
#pragma auto_inline (off)
#pragma comment( lib, "DbgHelp" )
两句讲一下,第一句是取消掉自动内联效果,这样才能达到更好的演示效果,不然,Fun,Fun2这种简单的函数会被自动内联,那么也就没有堆栈,不好看到实际中Dump的作用。效果与VS2005编译选项的,C/C++->优化->内联函数展开->only _inline一样。
第二句是标志导入DbgHelp库,以使用MiniDumpWriteMiniDump API。与VS2005编译选项的链接器->输入->附加依赖项中添加dbgHelp.lib效果一样。
实际运行程序,(不能在VS中调试运行,不然异常控制权总是会被VS掌握,那么,总是没有办法让),可以获得一个名叫DumpFile.dmp的文件,此文件就是我们折腾了半天所谓的dump文件了。其他两个函数与MiniDumpWriteMiniDump的配合使用方式也类似,就不多说了。
2. Dump文件的使用
Dump文件的在Windows下的使用非常简单,但是就是因为太过于简单,所以网上的描述也是非常简单,想起来,那时候折腾出Dump文件时非常兴奋,解决发现拿dump文件没有办法,网上简单的描述用VS打开调试的方法总是没有头绪。。。。呵呵
正确的使用方法是,将崩溃程序的dmp, pdb,exe文件都放在同一个目录下,然后双击运行dmp,(或者用VS打开),然后就会出现一个名为dumpfile的解决方案并且包含一个dumpfile的工程,此时右键点击此工程,选择调试->启动新实例(或者启动并进入单步调试新实例)都行,此时程序会自动的调到源码中崩溃的那一行,并且在call stack中有完整的堆栈信息,并且临时变量的值也可以通过VS显示出来,还有模块信息,线程信息,
在上例中,堆栈信息是:
> Exception.exe!Fun2() 行36 C++
Exception.exe!main() 行50 C++
Exception.exe!__tmainCRTStartup() 行597 + 0x17 字节 C
kernel32.dll!7c817077()
[下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]
ntdll.dll!7c93005d()
然后,寄存器的值为:
EAX = 00000000 EBX = 00000000 ECX = 0000B623
EDX = 7C92E514 ESI = 00000001 EDI = 00403384
EIP = 00401072 ESP = 0013FF7C EBP = 0013FFC0
EFL = 00010246
在normal模式下,dump文件速度较快,但是没有内存信息,你甚至可以通过调整MiniDumpWriteMiniDump的参数来将运行时的整个内存都dump下来,这些都非常简单,查看一下MSDN MiniDumpWriteMiniDump的信息即可。
有了这些信息,程序的错误定位(C++下一般是空指针的访问比较多)已经是非常明朗的了,再配合日志,一般的错误不难发现。这里顺带说明一下,当运行的程序被改名或者糅合进其他地方后运行,用这样的方式,一开始堆栈信息中是没有完整的信息的,这时候可以在堆栈信息中,用右键菜单中的加载符号,选择合适的文件pdb,这样信息就出来了。。。。。(以前这个问题困扰了我们一天)
三、 高级应用
程序崩溃的问题解决了,问题是,有很多时候,很多程序是不允许随便崩溃的,这样,在程序崩溃后再去发现问题就有些晚了,那么,有没有程序不崩溃时也能发现问题的方法呢?前面描述的SEH就是一种让程序不崩溃的方法,不过在那种方式下,按以前描述的方法,崩溃是不崩溃了,但是实际上,掩盖了很多问题,对于问题的发现有些不利的地方。本文前面描述过了,MiniDump是一种快速发现问题的好方法,但是却没有办法避免程序崩溃,那么终极办法是啥呢?我们的目的既然是程序不崩溃+快速发现问题,那么终极办法自然就是SEH+MiniDump了:)SEH和MiniDump都是Windows的特性,MS也的确提供了结合的方式。见下面的例子,呵呵,别太激动了。。。。这也是我们公司的服务器从内测时一天多次无任何通知,预告,警告的崩溃(总监甚至还曾因为我的问题,半夜3点爬起来解决服务器崩溃问题)到现在服务器基本做到永不崩溃,即便出现问题了也有充足的时间从容的解决,然后在服务器中发通告,告诉文件服务器需要临时维护。。。。呵呵,都依赖于此终极解决方案。。。。。
SEH的用法和特性讲解这里不重复了,见前面的文章。《异常处理与MiniDump详解(3)
SEH(Structured Exception Handling)》
要想利用MiniDumpWriteMiniDump,需要获取的是MINIDUMP_EXCEPTION_INFORMATION结构的信息,这个结构中最重要的信息来源于PEXCEPTION_POINTERS的信息,这个信息在上述的例子中是在程序崩溃的时候,由Windows作为参数传入我们设定好的异常处理函数的,现在最主要的问题就是从哪里获取到这个异常信息了,通过MSDN,我们查到了GetExceptionInformation的函数,返回的就是这个信息,见MSDN:
LPEXCEPTION_POINTERS GetExceptionInformation(void);
不过,这里MS给出了一个notice:
The Microsoft C/C++ Optimizing Compiler interprets this function as a keyword, and its use outside the appropriate exception-handling syntax generates a compiler error.
事实上,刚开始我使用的时候,哪个地方都试遍了,果然都是报编译错误。因为此函数使用方式如此奇怪,并且没有example。。。。。最后在绝望中。。。看到了Platform Builder for Microsoft Windows CE 5.0的词函数的说明,里面有个说明,然后我吐血了。。。。
try
{
// try block
}
except (FilterFunction(GetExceptionInformation())
{
// exception handler block
}
原来是这样使用的啊。。。。。。。。。。晕
HandleWithoutCrash例子:
#include <windows.h>
#include <Dbghelp.h>
using namespace std;
#pragma auto_inline (off)
#pragma comment( lib, "DbgHelp" )
// 为了程序的简洁和集中关注关心的东西,按示例程序的惯例忽略错误检查,实际使用时请注意
LONG WINAPI MyUnhandledExceptionFilter(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
HANDLE lhDumpFile = CreateFile(_T("DumpFile.dmp"), GENERIC_WRITE,
0, NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);
MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;
loExceptionInfo.ExceptionPointers = ExceptionInfo;
loExceptionInfo.ThreadId = GetCurrentThreadId();
loExceptionInfo.ClientPointers = TRUE;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),lhDumpFile, MiniDumpNormal,
&loExceptionInfo, NULL, NULL);
CloseHandle(lhDumpFile);
return EXCEPTION_EXECUTE_HANDLER;
}
void Fun2()
{
__try
{
static bool b = false;
if(!b)
{
b = true;
int *p = NULL;
*p = 0;
}
else
{
MessageBox(NULL, _T("Here"), _T(""), MB_OK);
}
}
__except(MyUnhandledExceptionFilter(GetExceptionInformation()))
{
}
}
void Fun()
{
Fun2();
}
int main()
{
Fun();
Fun();
return 1;
}
这里例子中,你可以调试程序了,因为程序不会崩溃,这样VS不会和你抢异常的控制。同时,看到dump文件的同时,也可以看到,程序实际上是继续运行了下去,因为MessageBox还是弹出来了。这。。。就是我们想要的。。。。。
我突然想到一首歌。。。。“I want to nobody but you...I want nobody but you.......”呵呵,目的达到了,惊艳吗?
这里有几个要点,GetExceptionInformation()仅仅只能在__except的MS所谓的Filter中调用,其他地方会报编译错误,其次,返回的值和一般的__except的意义是一样的,要想程序运行,需要返回EXCEPTION_EXECUTE_HANDLER表示异常得到了控制。其他几个值的含义见前篇的SEH。
四、 参考资料
1. MSDN—Visual Studio 2005 附带版,Microsoft
2. Windows用户态程序高效排错,熊力著,电子工业出版社
异常处理与MiniDump 用于投放市场c++异常捕获的更多相关文章
- 异常处理与MiniDump详解(1) C++异常(转)
异常处理与MiniDump详解(1) C++异常 write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 一. 综述 我很少敢为自己写 ...
- 异常处理与MiniDump详解(2) 智能指针与C++异常
write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 一. 综述 <异常处理与MiniDump详解(1) C++异常>稍 ...
- 异常处理与MiniDump详解(4) MiniDump
http://blog.csdn.net/vagrxie/article/details/4398721 异常处理与MiniDump详解(4) MiniDump 分类: [Wi ...
- 异常处理与MiniDump详解(转)
一. 综述 总算讲到MiniDump了. Dump有多有用我都无法尽数,基本上属于定位错误修复BUG的倚天剑.(日志可以算是屠龙刀)这些都是对于那些不是必出的BUG,放在外面运行的时候出现的BUG ...
- T-SQL编程中的异常处理-异常捕获(catch)与抛出异常(throw)
本文出处: http://www.cnblogs.com/wy123/p/6743515.html T-SQL编程与应用程序一样,都有异常处理机制,比如异常的捕获与异常的抛出,本文简单介绍异常捕获与异 ...
- 异常处理的捕捉:try{}catch(异常类 变量)finally{最终执行}
可以对异常进行针对性处理的方式.try{ //需要被检查的异常 }catch(异常类 变量)//该变量用于接收发生的异常{ //处理异常的代码 }finally{ //一定会被执行的代码. }
- T-SQL编程中的异常处理-异常捕获(try catch)与抛出异常(throw)
本文出处: http://www.cnblogs.com/wy123/p/6743515.html T-SQL编程与应用程序一样,都有异常处理机制,比如异常的捕获与异常的抛出(try catch th ...
- python异常处理-异常捕获-抛出异常-断言-自定义异常-UDP通信-socketserver模块应用-3
异常捕获 异常:程序在运行过程中出现了不可预知的错误,并且该错误没有对应的处理机制,那么就会以异常的形式表现出来 影响:整个程序无法再正常运行 异常的结构 异常的类型 NameError 异常的信息 ...
- python入门19 异常及异常处理 异常捕获
常见异常举例 """ 一些异常举例:""" '''模块不存在 ModuleNotFoundError: No module named 'd ...
随机推荐
- 一个封装不错的 TcpClient 类
using System;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading; nam ...
- python之多态与多态性
1.多态的概念:多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的的概念依赖于继承) 比如:序列类型有多种形态:字符串,列表,元组 动物有多种形态:人,狗,猪 import abc cl ...
- .net core中的对象池
asp.net core中通过扩展库的方式提供给了一个标准的对象池ObjectPool,定义在Microsoft.Extensions.ObjectPool.dll 程序集中.它本身是个纯虚的抽象类, ...
- C#网络编程技术微软Socket实战项目演练(三)
一.课程介绍 本次分享课程属于<C#高级编程实战技能开发宝典课程系列>中的第三部分,阿笨后续会计划将实际项目中的一些比较实用的关于C#高级编程的技巧分享出来给大家进行学习,不断的收集.整理 ...
- 简易RPC框架-SPI
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- springboot拦截器@Autowired为null解决
问题原因 拦截器加载的时间点在springcontext之前,所以在拦截器中注入自然为null 文件解决 在spring配置文件中这样写 @Bean public HandlerInterceptor ...
- [转]通过设置nginx的client_max_body_size解决nginx+php上传大文件的问题
转:http://blog.csdn.net/zhengwish/article/details/51602059 通过设置nginx的client_max_body_size解决nginx+php上 ...
- AXI总线介绍
原帖地址:https://wenku.baidu.com/view/7c424c17e3bd960590c69ec3d5bbfd0a7956d5b9.html 1.AXI简介 AXI(Advanc ...
- 使用日志服务进行Kubernetes日志采集
阿里云容器服务Kubernetes集群集成了日志服务(SLS),您可在创建集群时启用日志服务,快速采集Kubernetes 集群的容器日志,包括容器的标准输出以及容器内的文本文件. 新建 Kubern ...
- JPA学习笔记(3)——JPA注解
Entity Table Id GeneratedValue Basic Column Transient Temporal @Entity @Entity 标注用于实体类声明语句之前.指出该Java ...