【Crash】C++程序崩溃排查方法
windows下C++程序release版本崩溃错误排查方法。
一个你精心设计的24小时不间断运行,多线程的程序,突然运行了几个月后崩了,此问题是非常难以排查的,也是很头疼的问题。
现利用Google开源工具crashrpt与Microsoft windbg工具,解决这个问题,并分享给大家。
使用工具Crashrpt、Windbg.因为windbg这个工具很常见,暂不介绍。其中重点介绍一下crashrpt。
一、crashrpt 简介
crashrpt是一个包含能够在程序出现各种类型未处理异常时生成程序错误报告,然后将该报告按照指定的方式(例如HTTP或者SMTP)发送给开发者,最后分析这些信息的工具。
crashrpt由3个部分组成:
(1)错误报告生成库CrashRpt
我们需要在自己的程序中使用该库捕获我们的程序没有处理的异常,在该库捕获到这些未处理的异常后,CrashRpt会生成MiniDump文件,
并将和你使用该库指定的信息(例如日志文件和屏幕截图等)一起打包成错误报告。
CrashRpt库支持处理我所知道的所有Windows C/C++程序抛出的各类异常,还能捕获C++异常、信号和调用各类CRT库中的函数出现的错误。
(2)异常信息发送工具CrashSender
该工具能够按照我们使用CrashRpt设置的方式,将生成的错误报告按照我们指定的方式(HTTP、SMTP或者MAPI)发送给我们。
(3)自动异常信息处理工具crprober
该工具能够在后台接收CrashSender发送给我们的错误报告,通过分析错误报告后以文本的形式输出程序的异常信息。
二、下载安装 crashrpt
(1)下载crashrpt
crashrpt下载地址:https://code.google.com/p/crashrpt
关于crashrpt更详细的介绍,可以参考面https://code.google.com/p/crashrpt/ (大陆可能很难访问到)以及http://crashrpt.sourceforge.net/docs/html/getting_started.html (大陆可能很难访问到)
下载解压后的目录如下图所示:其中bin目录中包含使用vc10编译出来的所有crashrpt相关库和程序,include和lib目录中包含了开发所需要的头文件以及lib文件。
(2)使用vc编译crashrpt
如果你不介意程序在发布时带上vc10的运行库,或者你的程序就是用vc10开发的,你可以直接使用这些编译好的二进制文件。
如果你想crashrpt和你的程序依赖相同的vc运行库,那么你需要使用你的vc重新编译crashrpt。
对于使用除vc10之外的其它vc版本的朋友,如果要编译crashrpt,则需要使用开源交叉编译工具cmake,我们需要使用cmake生成和你vc版本相同的解决方案以及工程文件。
{
附cmake安装使用方法:首先在http://cmake.org/cmake/resources/software.html (测试可以访问)中下载并安装适合你系统的最新版本cmake。
安装完成后,运行cmake-gui,在where is the source code文本框以及where to build the binaries文本框中输入crashrpt的顶层目录,也就是包含文件CMakeLists.txt的目录,
然后点击Configure按钮,然后在弹出的对话框中选择你的vc版本并点击finish,则出现如图所示的输出: 在列表框中选择和你的vc版本匹配的选项,最后点击Gnerate按钮,
就可以生成与你vc版本相匹配的解决方案和工程文件了。
}
使用vc打开生成的解决方案文件,在这里我使用的vc版本是vc9,该解决方案中有下图所示的工程: 在解决方案处点击右键菜单中的build,即可生成crashrpt。
三、生成错误报告
下面让我们来看一下如何使用crashrpt库来生成错误报告。
首先我们需要声明一个CR_INSTALgL_INFO结构体,然后按照自己的需要对其进行设置之后,即可以使用crInstall函数向程序中安装crashrpt中的异常处理函数。
调用该函数之后,如果程序中出现了未捕获的异常,则crashrpt会捕获该异常并生成MiniDump文件,
关于CR_INSTALgL_INFO结构体的详细描述,请参考http://crashrpt.sourceforge.net/docs/html/struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html。
除了MiniDump文件之外,我们还可以通过调用 crAddFile2函数够将指定的文件加入到错误报告中,例如我们可以将程序相关的日志文件加入到错误报告中,以便我们更好的分析程序的内部状态,然后通过这些信息更快的找到程序出错的原因;
除了能够添加指定的文件以外,我们还能够通过调用 crAddScreenshot函数添加屏幕截图,这样在程序崩溃的时候,我们能够将当时的屏幕截图包含到错误报告中;
有时候运行程序的硬件也可能是导致程序崩溃的原因,我们可以通过调用 crAddProperty函数,将自定义信息添加到错误报告中的xml描述文件里;
最后,我们还可以调用crAddRegKey函数将注册表的相关信息包含到错误报告中;
请记住在程序结束之前调用crUninstall函数清理crashrpt所使用的相关资源。
关于crashrpt使用的更详细介绍,请参考http://crashrpt.sourceforge.net/docs/html/using_crashrpt_api.html
四、示例
现在让我们来看一个使用crashrpt库的示例MyApp,程序MyApp拥有2个线程,主线程负责与用户进行交互,另外一个工作线程负责处理一些需要花费大量时间才能完成的操作。
程序还将创建一个日志文件,我们可以使用crashrpt库在程序崩溃的时候将该文件和MiniDump文件一起打包发送给我们,以便于我们更好地分析出程序崩溃的原因。
程序的代码如下所示:
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "CrashRpt.h"
<span style="font-family: monospace, fixed; ">// 包含crashrpt库使用所需要的头文件 </span>
FILE* g_hLog = NULL; // 日志文件句柄// 程序崩溃时由crashrpt调用的回调函数
BOOL WINAPI CrashCallback(LPVOID /*lpvState*/){
// 需要在这里关闭日志文件句柄,否则crashrpt无法对处于占用状态的文件进行操作
if(g_hLog!=NULL)
{
fclose(g_hLog);
g_hLog = NULL;
}
// 返回TRUE, 由crashrpt生成错误报告
return TRUE;}// 日志函数void log_write(LPCTSTR szFormat, ...){
if (g_hLog == NULL)
return;
va_list args;
va_start(args);
_vftprintf_s(g_hLog, szFormat, args);
fflush(g_hLog);}// 线程处理函数DWORD WINAPI ThreadProc(LPVOID lpParam){
// 在该线程中安装crashrpt库对未处理异常的处理
crInstallToCurrentThread2(0);
log_write(_T("Entering the thread proc\n"));
for(;;)
{
// 在这里模拟一处内存越界
int* p = NULL;
*p = 13;
}
log_write(_T("Leaving the thread proc\n"));
// 清理crashrpt资源
crUninstallFromCurrentThread();
return 0;}int _tmain(int argc, _TCHAR* argv[]){
// 设置crashrpt的各项参数
CR_INSTALL_INFO info;
memset(&info, 0, sizeof(CR_INSTALL_INFO));
info.cb = sizeof(CR_INSTALL_INFO);
info.pszAppName = _T("MyApp");
info.pszAppVersion = _T("1.0.0");
info.pszEmailSubject = _T("MyApp 1.0.0 Error Report");
info.pszEmailTo = _T("myapp_support@hotmail.com");
info.pszUrl = _T("http://myapp.com/tools/crashrpt.php");
info.pfnCrashCallback = CrashCallback;
info.uPriorities[CR_HTTP] = 3;
// 首先使用HTTP的方式发送错误报告
info.uPriorities[CR_SMTP] = 2;
// 然后使用SMTP的方式发送错误报告
info.uPriorities[CR_SMAPI] = 1; //最后尝试使用SMAPI的方式发送错误报告
// 捕获所有能够捕获的异常, 使用HTTP二进制编码的方式传输
info.dwFlags |= CR_INST_ALL_POSSIBLE_HANDLERS;
info.dwFlags |= CR_INST_HTTP_BINARY_ENCODING;
info.dwFlags |= CR_INST_APP_RESTART;
info.dwFlags |= CR_INST_SEND_QUEUED_REPORTS;
info.pszRestartCmdLine = _T("/restart");
// 隐私策略URL
info.pszPrivacyPolicyURL = _T("http://myapp.com/privacypolicy.html");
int nResult = crInstall(&info);
if(nResult!=0)
{
TCHAR szErrorMsg[512] = _T("");
crGetLastErrorMsg(szErrorMsg, 512);
_tprintf_s(_T("%s\n"), szErrorMsg);
return 1;
}
// 添加日志文件到错误报告中
crAddFile2(_T("log.txt"), NULL, _T("Log File"), CR_AF_MAKE_FILE_COPY);
// 添加程序崩溃时的截屏到错误报告中
crAddScreenshot(CR_AS_VIRTUAL_SCREEN);
// 添加任意的信息到错误报告中,这里以显卡信息作为示例
crAddProperty(_T("VideoCard"), _T("nVidia GeForce 8600 GTS"));
errno_t err = _tfopen_s(&g_hLog, _T("log.txt"), _T("wt"));
if(err!=0 || g_hLog==NULL)
{
_tprintf_s(_T("Error opening log.txt\n"));
return 1; // Couldn't open log file
}
log_write(_T("Started successfully\n"));
HANDLE hWorkingThread = CreateThread(NULL, 0,
ThreadProc, (LPVOID)NULL, 0, NULL);
log_write(_T("Created working thread\n"));
TCHAR* szFormatString = NULL;
_tprintf_s(szFormatString);
WaitForSingleObject(hWorkingThread, INFINITE);
log_write(_T("Working thread has exited\n"));
if(g_hLog!=NULL)
{
fclose(g_hLog);
g_hLog = NULL;
}
crUninstall();
return 0;}
该示例程序中有几点需要注意的地方:
1.如果想要在错误报告中包含日志文件,请记住一定要使用类似于示例中的CrashCallBack函数来设置CR_INSTALL_INFO中的pfnCrashCallback域,并在函数中关闭该日志文件的句柄。
2.根据我的使用经验,其实不需要在线程中使用crInstallToCurrentThread2/crUninstallFromCurrentThread这一对函数来安装异常处理过程,只要在主线程中调用了crInstall。就能够捕获到程序中所有线程中未处理的异常。
3.调用crInstall出错的原因一般是没有将CrashRptXXXX.dll、CrashSenderXXXX.exe以及crashrpt_lang.ini放到正确的路径中,在默认情况下,该路径即是和应用程序相同的路径。其中的XXXX指的是crashrpt的版本号,这篇文章中的版本号为1300。
关于该示例的原文介绍,请参考http://crashrpt.sourceforge.net/docs/html/simple_example.html
最后发一段我使用crashrpt的代码块,我使用的目的是将程序交给测试人员进行测试时,如果程序崩溃后,crashrpt将程序的错误报告保存到本地,测试人员发现程序崩溃后,将该报告发给我进行调试。
代码如下所示:
int main(int argc, char **argv){#if defined(WIN32) && defined(USE_CRASHRPT)
CR_INSTALL_INFO info = {0};
info.cb = sizeof(CR_INSTALL_INFO);
info.pszAppName = TEXT("xxx");
info.pszAppVersion = TEXT("0.1.0");
info.dwFlags |= CR_INST_ALL_POSSIBLE_HANDLERS;
info.dwFlags |= CR_INST_DONT_SEND_REPORT;
info.pszErrorReportSaveDir = TEXT("./xxx");
if (EXIT_SUCCESS != crInstall(&info))
{
TCHAR errorMsg[512];
crGetLastErrorMsg(errorMsg, 512);
std::cerr << errorMsg;
return EXIT_FAILURE;
}#endif
int ret = mainImpl(argc, argv);#if defined(WIN32) && defined(USE_CRASHRPT)
crUninstall();#endif
return ret;}
crashrpt是一个功能很强大的错误报告生成、发送以及分析工具。由于我的使用比较简单,所以我这里介绍的只是crashrpt功能的一小部分,按照crashrpt文档中的描述,crashrpt完全可以在使用http发送错误报告时,与我们所使用的BUG管理系统进行联动,我认为这样可以极大的提升BUG的修改效率,
如果对crashrpt有兴趣的朋友,可以参考http://crashrpt.sourceforge.net/docs/html/index.html 进行更深入的学习。
经测试通过的crashrpt资源下载地址:http://download.csdn.net/detail/dotnetpig/8543863
原文内容摘自:http://blog.csdn.net/lingchen214/article/details/11918977 谢谢作者的分享,我只是加工一下,把资源附给大家,以免去google找不到合适的资源,并负责任地告诉大家资源我编译通过了。
【Crash】C++程序崩溃排查方法的更多相关文章
- [CareerCup] 12.2 Find the Reason of Crash 找到程序崩溃的原因
12.2 You are given the source to an application which crashes when it is run. After running it ten t ...
- 水火难容:同步方法调用async方法引发的ASP.NET应用程序崩溃
之前只知道在同步方法中调用异步(async)方法时,如果用.Result等待调用结果,会造成线程死锁(deadlock).自己也吃过这个苦头,详见等到花儿也谢了的await. 昨天一个偶然的情况,造成 ...
- android 程序崩溃crash日志的捕捉
android 程序崩溃crash日志的捕捉 之前在项目开发过程中,一直会遇到程序崩溃了,但是测试組的哥哥们又没及时的导出日志.... 后来在诳群的时候听别人说起,腾讯有那么一个叫bugly的东西 将 ...
- Windbg抓取程序崩溃的dmp文件的方法
Windbg抓取程序崩溃的dmp文件的方法 一. 简介 windbg是在windows平台下,强大的用户态和内核态调试工具.相比较于Visual Studio,它是一个轻量级的调试工具, ...
- 《对“XXX::Invoke”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们》的问题的解决方法
<对“XXX::Invoke”类型的已垃圾回收委托进行了回调.这可能会导致应用程序崩溃.损坏和数据丢失.向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们& ...
- [ios-必看] IOS调试技巧:当程序崩溃的时候怎么办 iphone IOS
from:http://article.ityran.com/archives/1143 有这样一种情形:当我们正在快乐的致力于我们的app时,并且什么看都是无比顺利,但是突然,坑爹啊,它崩溃了.(悲 ...
- iOS - 捕获应用程序崩溃日志
作为一名iOS移动应用开发者,为了确保你的应用程序正确无误,在将应用程序提交到应用商店之前,你必定会进行大量的测试工作:而且在你测试的过程中应用程序运行的很好,但是在应用商店上线之后,还是有用户抱怨应 ...
- iOS开发-捕获程序崩溃日志
iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者,是大多数软件都选择的方法.下面就介绍如何在iOS中实现: 1. 在程序启动时加上一个异常捕获监听,用来处理程序崩溃时 ...
- Android 程序崩溃后的处理
在应用发布以后,由于安卓机型的千差万别 ,可能会出现各种各样的问题,这时候如果我们可以将这些信息收集起来,并进行修改就很不错了.下面就来讨论一下怎么处理程序崩溃以后,错误信息的手机. Java中已经提 ...
随机推荐
- 3.2 git命令大全
1. 常用命令 -- 查看 git remote:要查看当前配置有哪些远程仓库; git remote -v: -v 参数,你还可以看到每个别名的实际链接地址; git branch -a :查看远程 ...
- oracle的批量操作sql语句
1.批量删除/批量更新 mapper: <update id="updatePrjStateByFPrjId" parameterType="string" ...
- Java SE LinkedList的底层实现
关于实现链表的底层原理 链表便于增删,不便于查询 package com.littlepage.linkedList; /** * 基于底层实现LinkedList * @author Littlep ...
- 通过TortoiseSVN进行文件(夹)外链 External File
1.假设将server/a.lua文件外链到client文件夹中 2.在client文件夹空白处右键->TortoiseSVN->Properties->New->Extern ...
- python实现邮件接口——smtplib模块
1. 思路 使用脚本发送邮件的思路其实和客户端发送邮件一样,过程都是: 登录 —> 写邮件 —> 发送 只不过通过脚本发送时我们需要考虑到整个过程的方方面面.以下为思路导图: 2. Pyt ...
- RepBaseRepeatMaskerEdition下载 | RepeatMasker
开源的生物信息世界居然有这么个需要注册才能下载的工具,开源世界不是怎么方便怎么来吗? 这个注册真的麻烦,这里上传了一个可以使用的版本. RepBaseRepeatMaskerEdition-20170 ...
- 七种常见的核酸序列蛋白编码能力预测工具 | ncRNAs | lncRNA
注:这些工具的应用都是受限的,有些本来就是只能用于预测动物,在使用之前务必用ground truth数据来测试一些.我想预测某一个植物的转录本,所以可以拿已经注释得比较好的拟南芥来测试一下.(测试的结 ...
- (GoRails)链接link_to到当前页current Page 并使用参数 (类ActionController::Parameters)
https://gorails.com/episodes/rails-link-to-current-page-with-params?autoplay=1 如何链接到当前页并增加,移除,或者修改UR ...
- You Don't Know JS: Scope & Closures (第4章: Hoisting)
Chapter4: Hoisting 变量附加到哪个层次的scope,由它们在哪里和如何声明(let, var)来决定. Function scope/Block scope都有相同的法则:任何变量在 ...
- P2469 [SDOI2010]星际竞速
在何Au的讲解下终于搞明白了这个以前的坑. 首先考虑最小路径覆盖. 这个题意又要求我们求出的最大流为n-1(这样才能保证路径为1条) 考虑高速航行模式的图怎么建,只需要保证最大流的同时费用最小即可,显 ...