1. 程式人生 > >讓程式在崩潰時體面的退出之CallStack

讓程式在崩潰時體面的退出之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){