VS2008除錯Release程式--Dump檔案方式
在Windows平臺下用C++開發應用程式,最不想見到的情況恐怕就是程式崩潰,而要想解決引起問題的bug,最困難的應該就是除錯release版本了。因為release版本來就少了很多除錯資訊,更何況一般都是釋出出去由使用者使用,crash的現場很難保留和重現。目前有一些方法可以解決:崩潰地址 + MAP檔案;MAP檔案;SetUnhandledExceptionFilter + Minidump。本文重點解決Minidump方式。
一、Minidump檔案生成
1、Minidump概念
minidump(小儲存器轉儲)可以理解為一個dump檔案,裡面記錄了能夠幫助除錯crash的最小有用資訊。實際上,如果你在系統屬性 ->
我們要生成的是使用者態的minidump,檔案中包含了程式執行的模組資訊、執行緒資訊、堆疊呼叫資訊等。而且為了符合其mini的特性,dump檔案是壓縮過的。
2、生成minidump檔案
通過drwtsn32、NTSD、CDB等除錯工具生成Dump檔案, drwtsn32存在的缺點雖然NTSD、CDB可以完全解決,但並不是所有的作業系統中都安裝了NTSD
3、 自動生成Minidump檔案
當程式遇到未處理異常(主要指非指標造成)導致程式崩潰死,如果在異常發生之前呼叫了SetUnhandledExceptionFilter()函式,異常交給函式處理。MSDN中描述為:
Issuing SetUnhandledExceptionFilter replaces the existing top-level exception filter for all existing and all future threads in the calling process.
因而,在程式開始處增加SetUnhandledExceptionFilter()函式,並在函式中利用適當的方法生成Dump檔案,即可實現需要的功能。
生成dump檔案類(minidump.h)
#pragma once #include <windows.h>#include <imagehlp.h>#include <stdlib.h>#pragma comment(lib, "dbghelp.lib") inline BOOL IsDataSectionNeeded(const WCHAR* pModuleName){ if(pModuleName == 0) { return FALSE; } WCHAR szFileName[_MAX_FNAME] = L""; _wsplitpath(pModuleName, NULL, NULL, szFileName, NULL); if(wcsicmp(szFileName, L"ntdll") == 0) return TRUE; return FALSE; } inline BOOL CALLBACK MiniDumpCallback(PVOID pParam, const PMINIDUMP_CALLBACK_INPUT pInput, PMINIDUMP_CALLBACK_OUTPUT pOutput){ if(pInput == 0 || pOutput == 0) return FALSE; switch(pInput->CallbackType) { case ModuleCallback: if(pOutput->ModuleWriteFlags & ModuleWriteDataSeg) if(!IsDataSectionNeeded(pInput->Module.FullPath)) pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg); case IncludeModuleCallback: case IncludeThreadCallback: case ThreadCallback: case ThreadExCallback: return TRUE; default:; } return FALSE;} //建立Dump檔案inline void CreateMiniDump(EXCEPTION_POINTERS* pep, LPCTSTR strFileName){ HANDLE hFile = CreateFile(strFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) { MINIDUMP_EXCEPTION_INFORMATION mdei; mdei.ThreadId = GetCurrentThreadId(); mdei.ExceptionPointers = pep; mdei.ClientPointers = FALSE; MINIDUMP_CALLBACK_INFORMATION mci; mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback; mci.CallbackParam = 0; MINIDUMP_TYPE mdt = (MINIDUMP_TYPE)0x0000ffff; MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &mdei, NULL, &mci); CloseHandle(hFile); }} LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter){ return NULL;} BOOL PreventSetUnhandledExceptionFilter(){ HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll")); if (hKernel32 == NULL) return FALSE; void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter"); if(pOrgEntry == NULL) return FALSE; unsigned char newJump[ 100 ]; DWORD dwOrgEntryAddr = (DWORD) pOrgEntry; dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far void *pNewFunc = &MyDummySetUnhandledExceptionFilter; DWORD dwNewEntryAddr = (DWORD) pNewFunc; DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; newJump[ 0 ] = 0xE9; // JMP absolute memcpy(&newJump[ 1 ], &dwRelativeAddr, sizeof(pNewFunc)); SIZE_T bytesWritten; BOOL bRet = WriteProcessMemory(GetCurrentProcess(), pOrgEntry, newJump, sizeof(pNewFunc) + 1, &bytesWritten); return bRet;} LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException){ TCHAR szMbsFile[MAX_PATH] = { 0 }; ::GetModuleFileName(NULL, szMbsFile, MAX_PATH); TCHAR* pFind = _tcsrchr(szMbsFile, '\\'); if(pFind) { *(pFind+1) = 0; _tcscat(szMbsFile, _T("CreateMiniDump.dmp")); CreateMiniDump(pException,szMbsFile); } // TODO: MiniDumpWriteDump FatalAppExit(-1, _T("Fatal Error")); return EXCEPTION_CONTINUE_SEARCH;} //執行異常處理void RunCrashHandler(){ SetUnhandledExceptionFilter(UnhandledExceptionFilterEx); PreventSetUnhandledExceptionFilter();}
//測試實現檔案
// 一個有函式呼叫的類
//
classCrashTest
{
public:
voidTest()
{
Crash();
}
private:
voidCrash()
{
strcpy(NULL,"adfadfg");
}
};
int_tmain(intargc, _TCHAR*argv[])
{
//設定異常處理函式
RunCrashHandler();
CrashTesttest;
test.Test();
getchar();
return 0;
}
注意事項
1、需要配置debug選項,在C/C++選項à常規à除錯資訊格式(設定為程式資料庫(/Zi));在聯結器選項—>除錯à生成除錯資訊(設定為是);C/C++選項à優化à禁用。(參見下圖)
2、可執行檔案(exe)必須找到dbghelp.dll,才能生成Dump檔案。這個DLL可以從除錯工具包中找到。
3、*.exe、*.pdb、*.dump、dbghelp.dll這四個檔案需要放在同一目錄下才好除錯,雙擊dump檔案時,就可以自動關聯到出錯程式碼位置。
4、為了獲取更多更深入的除錯資訊,需要把程式優化開關設定成禁用。
5、當異常程式碼定位成功以後,如果無法阻止異常的產生,可以用 __try結構包裝異常程式碼,__try和 try不同,前者可以捕獲非法指標產生的異常。
__try {
// 會異常的函式
}
__except( EXCEPTION_EXECUTE_HANDLER ){
// 異常處理
}
<這個優化,不設定,也可以dump到錯誤。>
如果是dll,設定上面這幾個屬性就可以了,在.exe主程式中執行異常處理函式RunCrashHandler()。
二、除錯Minidump檔案
- 雙擊minidump檔案(*.dmp)。預設會啟動vs2008。
- 選單Tools/Options, Debugging/Symbols,增加PDB檔案路徑。注:如果minidump檔案與pdb檔案在同一目錄,就不用設定這個了。
- 若除錯的程式需要微軟基礎庫的PDB資訊,可以增加一個路徑為:
- 在介面下方Cache Symbol From symbol…選擇本地儲存這些Symbols的路徑。 注:如果本地已儲存過微軟基礎庫的pdb,就直接按照此步操作設定本地路徑,不必執行上一步操作了。
- 設定程式碼路徑:
設定程式碼路徑:
剛開啟的dmp工程,進入解決方案的屬性。在這裡輸入源程式的程式碼路徑。注:一定是sln所在的路徑,而不是vcproj的路徑!
按F5,debug吧。