讓程式在崩潰時體面的退出之CallStack
在我的那篇《讓程式在崩潰時體面的退出之Unhandled Exception》中提供了一個捕捉程式崩潰事件的方法,可以新增程式碼在程式崩潰的時候做出適當的處理。不過,只知道程式在什麼時候崩潰,但是不知道為什麼崩潰,這對於程式開發者來說沒有任何意義。因為如果不知道程式崩潰的原因,就沒法去找到程式碼中的缺陷,當然就沒法去修改程式碼而避免程式的崩潰。
所有除錯過程式碼的開發者都知道CallStack的重要性。如果在程式崩潰的時候得到CallStack,那麼就能定位程式崩潰的具體位置,並最終找到解決方法。那麼有沒有什麼方法在程式崩潰的時候得到CallStack呢?答案是肯定的。微軟提供了一個DbgHelp.dll,裡面包含了一系列的Windows API來供開發者呼叫。它是一個除錯跟蹤相關的模組,用於跟蹤程序工作,在程序崩潰時收集程式產生異常時的堆疊資訊,以供開發人員分析,從而很快找出使程式出現異常的原因。
下面用具體的例子程式碼來說明怎樣使用DbgHelp.dll中的Windows API來得到CallStack。程式碼裡面有詳細的註釋來幫助理解。
用VC建立一個名為Test的控制檯程式,新增下面的程式碼。
// 一個有函式呼叫的類
//
class CrashTest
{
public:
void Test()
{
Crash();
}
private:
void Crash()
{
// 除零,人為的使程式崩潰
//
int i = 13;
int j = 0;
int m = i / j;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
CrashTest test;
test.Test();
return 0;
}
編譯上面的程式碼,到工程的Debug目錄下找到編譯好的Test.exe,雙擊執行,程式就會崩潰。從程式碼我們知道函式呼叫順序是main() -> CrashTest::Test() -> CrashTest::Crash()。那麼怎麼在程式崩潰的時候得到CallStack呢?首先,先新增一些工具函式。
#include <Windows.h> #include <DbgHelp.h> #include <iostream> #include <vector> // 新增對dbghelp.lib的編譯依賴 // #pragma comment(lib, "dbghelp.lib") using namespace std; const int MAX_ADDRESS_LENGTH = 32; const int MAX_NAME_LENGTH = 1024; // 崩潰資訊 // struct CrashInfo { CHAR ErrorCode[MAX_ADDRESS_LENGTH]; CHAR Address[MAX_ADDRESS_LENGTH]; CHAR Flags[MAX_ADDRESS_LENGTH]; }; // CallStack資訊 // struct CallStackInfo { CHAR ModuleName[MAX_NAME_LENGTH]; CHAR MethodName[MAX_NAME_LENGTH]; CHAR FileName[MAX_NAME_LENGTH]; CHAR LineNumber[MAX_NAME_LENGTH]; }; // 安全拷貝字串函式 // void SafeStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc) { if (nMaxDestSize <= 0) return; if (strlen(szSrc) < nMaxDestSize) { strcpy_s(szDest, nMaxDestSize, szSrc); } else { strncpy_s(szDest, nMaxDestSize, szSrc, nMaxDestSize); szDest[nMaxDestSize-1] = '\0'; } } // 得到程式崩潰資訊 // CrashInfo GetCrashInfo(const EXCEPTION_RECORD *pRecord) { CrashInfo crashinfo; SafeStrCpy(crashinfo.Address, MAX_ADDRESS_LENGTH, "N/A"); SafeStrCpy(crashinfo.ErrorCode, MAX_ADDRESS_LENGTH, "N/A"); SafeStrCpy(crashinfo.Flags, MAX_ADDRESS_LENGTH, "N/A"); sprintf_s(crashinfo.Address, "%08X", pRecord->ExceptionAddress); sprintf_s(crashinfo.ErrorCode, "%08X", pRecord->ExceptionCode); sprintf_s(crashinfo.Flags, "%08X", pRecord->ExceptionFlags); return crashinfo; } // 得到CallStack資訊 // vector<CallStackInfo> GetCallStack(const CONTEXT *pContext) { HANDLE hProcess = GetCurrentProcess(); SymInitialize(hProcess, NULL, TRUE); vector<CallStackInfo> arrCallStackInfo; CONTEXT c = *pContext; STACKFRAME64 sf; memset(&sf, 0, sizeof(STACKFRAME64)); DWORD dwImageType = IMAGE_FILE_MACHINE_I386; // 不同的CPU型別,具體資訊可查詢MSDN // #ifdef _M_IX86 sf.AddrPC.Offset = c.Eip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = c.Esp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = c.Ebp; sf.AddrFrame.Mode = AddrModeFlat; #elif _M_X64 dwImageType = IMAGE_FILE_MACHINE_AMD64; sf.AddrPC.Offset = c.Rip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrFrame.Offset = c.Rsp; sf.AddrFrame.Mode = AddrModeFlat; sf.AddrStack.Offset = c.Rsp; sf.AddrStack.Mode = AddrModeFlat; #elif _M_IA64 dwImageType = IMAGE_FILE_MACHINE_IA64; sf.AddrPC.Offset = c.StIIP; sf.AddrPC.Mode = AddrModeFlat; sf.AddrFrame.Offset = c.IntSp; sf.AddrFrame.Mode = AddrModeFlat; sf.AddrBStore.Offset = c.RsBSP; sf.AddrBStore.Mode = AddrModeFlat; sf.AddrStack.Offset = c.IntSp; sf.AddrStack.Mode = AddrModeFlat; #else #error "Platform not supported!" #endif HANDLE hThread = GetCurrentThread(); while (true) { // 該函式是實現這個功能的最重要的一個函式 // 函式的用法以及引數和返回值的具體解釋可以查詢MSDN // if (!StackWalk64(dwImageType, hProcess, hThread, &sf, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { break; } if (sf.AddrFrame.Offset == 0) { break; } CallStackInfo callstackinfo; SafeStrCpy(callstackinfo.MethodName, MAX_NAME_LENGTH, "N/A"); SafeStrCpy(callstackinfo.FileName, MAX_NAME_LENGTH, "N/A"); SafeStrCpy(callstackinfo.ModuleName, MAX_NAME_LENGTH, "N/A"); SafeStrCpy(callstackinfo.LineNumber, MAX_NAME_LENGTH, "N/A"); BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_NAME_LENGTH]; IMAGEHLP_SYMBOL64 *pSymbol = (IMAGEHLP_SYMBOL64*)symbolBuffer; memset(pSymbol, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAME_LENGTH); pSymbol->SizeOfStruct = sizeof(symbolBuffer); pSymbol->MaxNameLength = MAX_NAME_LENGTH; DWORD symDisplacement = 0; // 得到函式名 // if (SymGetSymFromAddr64(hProcess, sf.AddrPC.Offset, NULL, pSymbol)) { SafeStrCpy(callstackinfo.MethodName, MAX_NAME_LENGTH, pSymbol->Name); } IMAGEHLP_LINE64 lineInfo; memset(&lineInfo, 0, sizeof(IMAGEHLP_LINE64)); lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64); DWORD dwLineDisplacement; // 得到檔名和所在的程式碼行 // if (SymGetLineFromAddr64(hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo)) { SafeStrCpy(callstackinfo.FileName, MAX_NAME_LENGTH, lineInfo.FileName); sprintf_s(callstackinfo.LineNumber, "%d", lineInfo.LineNumber); } IMAGEHLP_MODULE64 moduleInfo; memset(&moduleInfo, 0, sizeof(IMAGEHLP_MODULE64)); moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); // 得到模組名 // if (SymGetModuleInfo64(hProcess, sf.AddrPC.Offset, &moduleInfo)) { SafeStrCpy(callstackinfo.ModuleName, MAX_NAME_LENGTH, moduleInfo.ModuleName); } arrCallStackInfo.push_back(callstackinfo); } SymCleanup(hProcess); return arrCallStackInfo; }
然後,就可以用《讓程式在崩潰時體面的退出之Unhandled Exception》中提供的捕捉程式崩潰事件的方法新增一個回撥函式,在這個函式裡面呼叫上面的函式來得到程式崩潰時的CallStack。
// 處理Unhandled Exception的回撥函式
//
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
{
// 確保有足夠的棧空間
//
#ifdef _M_IX86
if (pException->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
{
static char TempStack[1024 * 128];
__asm mov eax,offset TempStack[1024 * 128];
__asm mov esp,eax;
}
#endif
CrashInfo crashinfo = GetCrashInfo(pException->ExceptionRecord);
// 輸出Crash資訊
//
cout << "ErrorCode: " << crashinfo.ErrorCode << endl;
cout << "Address: " << crashinfo.Address << endl;
cout << "Flags: " << crashinfo.Flags << endl;
vector<CallStackInfo> arrCallStackInfo = GetCallStack(pException->ContextRecord);
// 輸出CallStack
//
cout << "CallStack: " << endl;
for (vector<CallStackInfo>::iterator i = arrCallStackInfo.begin(); i != arrCallStackInfo.end(); ++i)
{
CallStackInfo callstackinfo = (*i);
cout << callstackinfo.MethodName << "() : [" << callstackinfo.ModuleName << "] (File: " << callstackinfo.FileName << " @Line " << callstackinfo.LineNumber << ")" << endl;
}
// 這裡彈出一個錯誤對話方塊並退出程式
//
FatalAppExit(-1, _T("*** Unhandled Exception! ***"));
return EXCEPTION_EXECUTE_HANDLER;
}
最後,在main函式的開頭新增下面的程式碼。
// 設定處理Unhandled Exception的回撥函式
//
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);
編譯上面的程式碼,到工程的Debug目錄下找到編譯好的Test.exe,雙擊執行,程式在崩潰的時候就會輸出CallStack。
相關推薦
讓程式在崩潰時體面的退出之CallStack
在我的那篇《讓程式在崩潰時體面的退出之Unhandled Exception》中提供了一個捕捉程式崩潰事件的方法,可以新增程式碼在程式崩潰的時候做出適當的處理。不過,只知道程式在什麼時候崩潰,但是不知道為什麼崩潰,這對於程式開發者來說沒有任何意義。因為如果不知
讓程式在崩潰時體面的退出之CallStack(轉)
在我的那篇《讓程式在崩潰時體面的退出之Unhandled Exception》中提供了一個捕捉程式崩潰事件的方法,可以新增程式碼在程式崩潰的時候做出適當的處理。不過,只知道程式在什麼時候崩潰,但是不知道為什麼崩潰,這對於程式開發者來說沒有任何意義。因為如果不知道程式崩潰
讓程式在崩潰時體面的退出之Dump檔案
在我的那篇《讓程式在崩潰時體面的退出之CallStack》中提供了一個在程式崩潰時得到CallStack的方法。可是要想得到CallStack,必須有pdb檔案的支援。但是一般情況下,釋出出去的程式都是 Release版本的,都不會附帶pdb檔案。那麼我們怎麼能在程式崩潰的
讓程式在崩潰時體面的退出之Unhandled Exception
程式是由程式碼編譯出來的,而程式碼是由人寫的。人非聖賢,孰能無過。所以由人寫的程式碼有缺陷是很正常的。當然很多異常都在開發階段被考慮到而添加了處理程式碼,或者用try/catch對可能
使用Dump檔案讓程式在崩潰時體面的退出
在我的那篇《讓程式在崩潰時體面的退出之CallStack》中提供了一個在程式崩潰時得到CallStack的方法。可是要想得到CallStack,必須有pdb檔案的支援。但是一般情況下,釋出出去的程式都是Release版本的,都不會附帶pdb檔案。那麼我們怎麼能在程式崩潰的時
如何防止後臺執行緒丟擲的異常讓程式崩潰退出
如果你的程式拋了異常,你是怎麼處理的呢?等待程式崩潰退出?還是進行補救? 如果是做 UI 開發,很容易就找到 Dispatcher.UnhandledException 事件,然後在事件中進行補救。如果補救成功,可以設定 e.Handled = true 來阻
windows程式崩潰時自動生成dump檔案方法
/****************第一步新增createdump.h********************************* 新增一個頭檔案:createdump.h #pragma once #include <windows.h> #inclu
C++程式執行時記憶體佈局之----------區域性變數,全域性變數,靜態變數,函式程式碼,new出來的變數
宣告兩點: (1)開發測試環境為VS2010+WindowsXP32位; (2)記憶體佈局指的是虛擬記憶體地址,不是實體地址。 1.測試程式碼 #include <iostream> using namespace std; int g_int_a; i
利用Windows自帶的功能當程式崩潰時產生崩潰轉儲檔案(dmp)
何志丹 以管理員身份 執行 :OpenDump.bat 其本質是寫登錄檔。 執行後: 任何程式崩潰都會在C:\CrashDump 產生dmp檔案(比較大,約50到200M)。 至少在Win7、Win10的電腦,Win10的平板上執行正確。 OpenDump.bat @e
gdb除錯命令及程式崩潰時的核心轉存core dump
1.gcc -g filename.c -o filename 需要生成帶除錯資訊的檔案 2.除錯 方式一:gdb filename 除錯file可執行檔案 方式二:>>gdb >>file filename $gd
windows 應用程式崩潰時的記憶體轉儲及dump檔案的分析
1、在現場設定程式崩潰時的自動記憶體轉儲,得到dump檔案 在windows 登錄檔如下項: //HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/AeDebug
讓程式崩潰但是不閃退的方法(可以用在真機測試上)
/** 讓程式崩潰但是不閃退的方法 */ void handException(NSException * exception){ /** 彈出提示框 */ UIAlertView * alert = [[UIAlertView alloc]in
C++ 記錄Windows程式崩潰時的dumpfile
【原理】 windows程式當遇到異常,沒有try-catch或者try-catch也無法捕獲到的異常時,程式就會自動退出,如果這時候沒有dump檔案的話,我們是沒有得到任何程式退出的資訊。在windows程式異常退出之前,會預先呼叫一個在程式中註冊的異常處理回撥
記錄程式崩潰時的呼叫堆疊
最近有個使用者遇到程式Crash問題,但我們的機器都不能重現,於是在網上搜了一把,發現有個MSJExceptionHandler類還比較好用,故整理了一下供大家參考。 這個類的使用方法很簡單,只要把這個類加入到你的工程(不管是MFC,com,dll都可以)中一起編譯就可以了
程式崩潰時生成Dump檔案
Dump檔案是程序的記憶體映象,可以把程式執行時的狀態完整的儲存下來,之後通過除錯工具可查出崩潰大致原因。 SetUnhandledExceptionFilter()設定一個在程式崩潰時被呼叫的回撥函式。MiniDumpWriteDump()建立Dump檔案。 我寫了一個
C++程式崩潰時,生成dmp除錯檔案
包含如下標頭檔案 和 引用Lib庫 #include <dbghelp.h> #pragma comment(lib, "dbghelp.lib")定義 LONG WINAPI MyUnh
ArcGIS for Android 1.1 MapView 的Activity退出時整個程式崩潰問題
在專案中當我們從一個activity中跳轉到Mapview的activity中後,點選回退鍵後,等待不長時間程式崩潰問題,此時報call to OpenGL ES API with no current context (logged once per thread),
關於QT在開啟子視窗時程式崩潰的其中一個原因分析
其實這個問題當時是糾結了我很長的一段時間,這段時間裡面,我一直在網上面找相關的資料但是卻沒有有用的資訊。 但是在後面的一個機緣巧合之下,我通過函式執行順序來Debug,慢慢的發現問題出現在什麼地方了。現在來總結一下這個問題吧。 其實我現在的經驗覺得,對於QT裡面(由於QT是基於C++的),不
利用MapFile定位程式崩潰(報紅牌)時的程式碼位置
原文:http://www.codeproject.com/KB/debug/mapfile.aspx 1、生成MapFile Project—Setting—C+±—DebugInfo,選擇Line Numbers Only Project—Setting—Link—選擇Generat
PHP 程式一直執行,讓 伺服器 崩潰
<?php set_time_limit(0); //設定不超時,程式一直執行。 ignore_user_abort(true); //即使Client斷開(如關掉瀏覽器),PHP指令碼也可以繼續執行. $bool=1; $i=0; while($bool>=0){