【轉載】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找不到合適的資源,並負責任地告訴大家資源我編譯通過了。
【轉載】C++程序崩潰排查方法