【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
exited 该⼯具能够按照我们使⽤CrashRpt设置的⽅式,将⽣成的错误报告按照我们指定的⽅式(HTTP、SMTP或者MAPI)发送给我们。
(3)⾃动异常信息处理⼯具crprober
该⼯具能够在后台接收CrashSender发送给我们的错误报告,通过分析错误报告后以⽂本的形式输出程序的异常信息。
⼆、下载安装 crashrpt
(1)下载crashrpt
下载解压后的⽬录如下图所⽰:其中bin⽬录中包含使⽤vc10编译出来的所有crashrpt相关库和程序,include和lib⽬录中包含了开发所需要的头⽂件以及lib⽂件。
(2)使⽤vc编译crashrpt
如果你不介意程序在发布时带上vc10的运⾏库,或者你的程序就是⽤vc10开发的,你可以直接使⽤这些编译好的⼆进制⽂件。
如果你想crashrpt和你的程序依赖相同的vc运⾏库,那么你需要使⽤你的vc重新编译crashrpt。
对于使⽤除vc10之外的其它vc版本的朋友,如果要编译crashrpt,则需要使⽤开源交叉编译⼯具cmake,我们需要使⽤cmake⽣成和你vc版本相同的解决⽅案以及⼯程⽂件。
{
安装完成后,运⾏cmake-gui,在where is the source code⽂本框以及where to build the binaries⽂本框中输⼊crashrpt的顶层⽬录,也就是包含⽂件的⽬录,
然后点击Configure按钮,然后在弹出的对话框中选择你的vc版本并点击finish,则出现如图所⽰的输出: 在列表框中选择和你的vc版本匹配的选项,最后点击Gnerate按钮,
就可以⽣成与你vc版本相匹配的解决⽅案和⼯程⽂件了。
}
使⽤vc打开⽣成的解决⽅案⽂件,在这⾥我使⽤的vc版本是vc9,该解决⽅案中有下图所⽰的⼯程: 在解决⽅案处点击右键菜单中的build,即可⽣成crashrpt。
三、⽣成错误报告
下⾯让我们来看⼀下如何使⽤crashrpt库来⽣成错误报告。
⾸先我们需要声明⼀个CR_INSTALgL_INFO结构体,然后按照⾃⼰的需要对其进⾏设置之后,即可以使⽤crInstall函数向程序中安装crashrpt中的异常处理函数。
调⽤该函数之后,如果程序中出现了未捕获的异常,则crashrpt会捕获该异常并⽣成MiniDump⽂件,
除了MiniDump⽂件之外,我们还可以通过调⽤ crAddFile2函数够将指定的⽂件加⼊到错误报告中,例如我们可以将程序相关的⽇志⽂件加⼊到错误报告中,以便我们更好的分析程序的内部状态,然后通过这些信息更快的到程序出错的原因;
除了能够添加指定的⽂件以外,我们还能够通过调⽤ crAddScreenshot函数添加屏幕截图,这样在程序崩溃的时候,我们能够将当时的屏幕截图包含到错误报告中;
有时候运⾏程序的硬件也可能是导致程序崩溃的原因,我们可以通过调⽤ crAddProperty函数,将⾃定义信息添加到错误报告中的xml描述⽂件⾥;
最后,我们还可以调⽤crAddRegKey函数将注册表的相关信息包含到错误报告中;
请记住在程序结束之前调⽤crUninstall函数清理crashrpt所使⽤的相关资源。
四、⽰例
现在让我们来看⼀个使⽤crashrpt库的⽰例MyApp,程序MyApp拥有2个线程,主线程负责与⽤户进⾏交互,另外⼀个⼯作线程负责处理⼀些需要花费⼤量时间才能完成的操作。
程序还将创建⼀个⽇志⽂件,我们可以使⽤crashrpt库在程序崩溃的时候将该⽂件和MiniDump⽂件⼀起打包发送给我们,以便于我们更好地分析出程序崩溃的原因。
程序的代码如下所⽰:
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "CrashRpt.h"
<span >// 包含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");
info.pszUrl = _T("myapp/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("myapp/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(""), 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(""), _T("wt"));
if(err!=0 || g_hLog==NULL)
{
_tprintf_s(_T("Error \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、以及crashrpt_lang.ini放到正确的路径中,在默认情况下,该路径即是和应⽤程序相同的路径。其中的XXXX指的是crashrpt的版本号,这篇⽂章中的版本号为1300。
最后发⼀段我使⽤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);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论