【转载】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("[email protected]");

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找不到合适的资源,并负责任地告诉大家资源我编译通过了。

原文地址:https://www.cnblogs.com/pjl1119/p/8745714.html

时间: 2024-11-01 14:18:30

【转载】C++程序崩溃排查方法的相关文章

水火难容:同步方法调用async方法引发的ASP.NET应用程序崩溃

之前只知道在同步方法中调用异步(async)方法时,如果用.Result等待调用结果,会造成线程死锁(deadlock).自己也吃过这个苦头,详见等到花儿也谢了的await. 昨天一个偶然的情况,造成在同步方法中调用了async方法,并且没有使用.Result,结果造成整个ASP.NET应用程序的崩溃,见识了同步/异步水火难容的厉害. 当时的情况是这样的,发布了一个经过异步化改造的ASP.NET程序,其中有这样一个同步方法: public static void Notify(string ti

IOS调试技巧:当程序崩溃的时候怎么办 iphone IOS

转载:http://article.ityran.com/archives/1143 有这样一种情形:当我们正在快乐的致力于我们的app时,并且什么看都是无比顺利,但是突然,坑爹啊,它崩溃了.(悲伤地音乐响起) 我们需要做的第一件事就是:不要惊慌. 修复崩溃不是很困难的.假如你崩溃了,并且胡乱的改些东西,而且还在不停的念着咒语希望bug神奇的自动消失,你大多数情况下都会使情况更麻烦.相反的,你需要知道一些系统的方法,并且学习怎么找到崩溃和他的原因. 第一件需要知道的就是在你的代码中准确的找到cr

内存泄露从入门到精通三部曲之排查方法篇

内存泄露从入门到精通三部曲之排查方法篇 最原始的内存泄露测试 重复多次操作关键的可疑的路径,从内存监控工具中观察内存曲线,是否存在不断上升的趋势且不会在程序返回时明显回落.这种方式可以发现最基本,也是最明显的内存泄露问题,对用户价值最大,操作难度小,性价比极高. MAT内存分析工具 2.1 MAT分析heap的总内存占用大小来初步判断是否存在泄露 在Devices 中,点击要监控的程序. 点击Devices视图界面中最上方一排图标中的“Update Heap” 点击Heap视图 点击Heap视图

VS2005(vs2008,vs2010 VS2012)使用map文件查找程序崩溃原因

转载http://blog.csdn.net/luxiaoyu_sdc/article/details/6458872 一般程序崩溃可以通过debug,找到程序在那一行代码崩溃了,最近编一个多线程的程序,都不知道在那发生错误,多线程并发,又不好单行调试,终于找到一个比较好的方法来找原因,通过生成map文件,由于2005取消map文件生成行号信息(vc6.0下是可以生成行号信息的,不知道microsoft怎么想的,在2005上取消了),只能定位在那个函数发生崩溃.这里可以通过生成cod文件,即机器

QSqlQuery 可以让你的程序崩溃

linux平台下. 一个程序总是运行个两三天,或者一两天的时候突然崩溃了,以前发过一个讨论但是也没找到解决办法,使用的数据库是SQLITE 使用GDB跟踪程序,结果找到了崩溃的地方却显示栈被破坏显示不出调用的具体方法,运行了好几次都是这样.定位到了 __memmove_ssse3在libc里面. 为了恢复完整的栈信息在国外大牛那里找来两句话 (gdb)set $pc=*(void **)$esp (gdb)set $esp=$esp+4 执行完就可以查看堆栈了(32位平台).注意这个不能由cor

未捕获异常,现实程序崩溃闪退

碰到程序崩溃时,闪退效果,不会提示"xxx程序异常,退出程序".这样的效果就要使用到未捕获异常来实现,这里记录了我的一个写法.其实原理很简单,设置程序的未捕获异常监听,实现监听的一个方法,在该方法中现实直接没有提示的退出程序. 捕获异常工具类 package com.tdh.http; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.Thread.UncaughtExceptionHan

WinCE应用程序崩溃提示框的处理

WinCE的开发人员和WinCE设备的用户应该对下面这两个错误不陌生,"Application encountered a serious error and must shut down"和"出现严重错误,必须被关闭".WinCE下应用程序崩溃就会弹出这样的提示框,还会发出警告的声音.如果是在车里,那声音还是很刺耳的.不过,说实在的,开发人员看到这个可以接受,程序都是会出BUG的.但用户经常看到就不太应该了.我们应该完善代码,尽可能降低出现应用程序崩溃的概率. 很

Linux 使用core file文件快速定位程序崩溃代码行

问题描述 如果在 Linux下编写程序,有时运行程序的时候程序崩溃,比如说只有"Segmentation fault (core dumped) ",程序比较小的话,还可以一行一行查看,但是如果程序很庞大,一行行查询,效率非常低下.Linux下可以程序可以生成core file文件,借助gdb很快能定位到崩溃的代码行. 解决方案 测试程序,除零操作,程序会崩溃 /* test.c */ #include <stdio.h> #include <stdlib.h>

dll的内存申请和释放问题--Debug程序正常而Release程序崩溃

C++编程中经常遇到这样的需求:主函数需要调用一个功能函数并返回一块大小不定的存储着处理结果的内存,这时容易想到两种选择:一是使用vector类型的引用作为形参,无需考虑内存问题:二是使用指针,在主函数中定义指针,而在功能函数中申请内存.这两种处理方法本来没有问题,但如果功能函数是dll中的函数,那么就需要十分小心了. 下面我们直接上结论: 1. 如果使用vector类型作为dll库函数的形参,那么一定不能在库函数中更改vector的大小,而只能更改vector的内容: 2. 如果使用指针,且在