【Crash】C++程序崩溃排查方法

摘要:
windows下C++程序发布版本崩溃的故障排除方法。重点是crashrpt。CrashRpt库支持处理我知道的所有Windows C/C++程序引发的各种异常,还可以捕获C++异常、信号和调用各种CRT库中函数时的错误。该程序还将创建一个日志文件。我们可以使用crashrpt库将此文件和MiniDump文件打包在一起,并在程序崩溃时将其发送给我们,这样我们就可以更好地分析程序崩溃的原因。
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 "));

for(;;)

{

// 在这里模拟一处内存越界

int* p = NULL;

*p = 13;

}

log_write(_T("Leaving the thread proc "));

// 清理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 "), 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 "));

return 1; // Couldn't open log file

}

log_write(_T("Started successfully "));

HANDLE hWorkingThread = CreateThread(NULL, 0,

ThreadProc, (LPVOID)NULL, 0, NULL);

log_write(_T("Created working thread "));

TCHAR* szFormatString = NULL;

_tprintf_s(szFormatString);

WaitForSingleObject(hWorkingThread, INFINITE);

log_write(_T("Working thread has exited "));

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++程序崩溃排查方法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇OpenCV学习系列教程第二篇:轨迹条HP DL380服务器RAID信息丢失数据恢复方法和数据恢复过程分享下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

fopen()函数

1.2 文件的输入输出函数 键盘、显示器、打印机、磁盘驱动器等逻辑设备, 其输入输出都能够通过文件管理的方法来完毕。而在编程时使用最多的要算是磁盘文件, 因此本节主要以磁盘文件为主, 具体介绍Turbo C2.0提供的文件操作函数, 当然这些对文件的操作函数也适合于非磁盘文件的情况。 另外, Turbo C2.0提供了两类关于文件的函...

提高代码质量的三要素

程序员在职业生涯中难免要接受编程面试。有些程序员由于平时没有养成良好的编程习惯,在面试时写出的代码质量不高,最终遗憾地与心仪的公司和职位失之交臂。因此,如何在面试时能写出高质量的代码,是很多程序员关心的问题。         代码的规范性        面试官是根据应聘者写出的代码来决定是否录用一个应聘者的。应聘者首先要把代码写得规范,才可以避免很多低...

Quartz-2D绘图之图形上下文详解

  上一篇文章大概描述了下Quartz里面大体所包含的东西,但是对具体的细节实现以及如何调用相应API却没有讲。这篇文章就先讲讲图形上下文(Graphics Context)的具体操作。   所谓Graphics Context,其实就是表示了一个绘制目标,也就是你打算绘制的地方,它包含绘制系统用于完成绘制指令的绘制参数和设备相关信息。Graphics C...

【转】VC 模式对话框和非模式对话框的创建,销毁和区别

原文网址:http://blog.csdn.net/mycaibo/article/details/6002151 VC 模式对话框和非模式对话框的创建,销毁和区别  在WIN32中,模式对话框的创建一般是使用DialogBox来进行创建的。而非模式对话框则是利用CreateWindow来创建的。在MFC或是WTL中,模式对话框一般是使用DoModal,...

C++ 常见崩溃问题分析

一、前言 从事自动化测试平台开发的编程实践中,遭遇了几个程序崩溃问题,解决它们颇费了不少心思,解决过程中的曲折和彻夜的辗转反侧却历历在目,一直寻思写点东西,为这段难忘的经历留点纪念,总结惨痛的教训带来的经验,以期通过自己的经历为他人和自己带来福祉:写出更高质量的程序; 由于 C 和 C++ 这两种语言血缘非常近,文本亦对 C 编程语言有借鉴作用; 二、C+...

Delphi笔记数据库开发

Delphi笔记-数据库开发  数据集的每一列被称为一个字段,每一行被称为一个记录。 •数据集:是一个分立的数据记录的集合。数据集由VCL的TDataSet表示。 •表:一种特殊类型的数据集。表一般是一个实际存储在磁盘上、包含有数据记录的文件。VCL的TTable类中封装了它的各种功能。 •查询:也是一种特殊类型的数据集。它可以被看作是执行了特殊命令后所产...