1. 程式人生 > >VS2008除錯Release程式--Dump檔案方式

VS2008除錯Release程式--Dump檔案方式

               

Windows平臺下用C++開發應用程式,最不想見到的情況恐怕就是程式崩潰,而要想解決引起問題的bug,最困難的應該就是除錯release版本了。因為release版本來就少了很多除錯資訊,更何況一般都是釋出出去由使用者使用,crash的現場很難保留和重現。目前有一些方法可以解決:崩潰地址 + MAP檔案;MAP檔案;SetUnhandledExceptionFilter + Minidump。本文重點解決Minidump方式。

一、Minidump檔案生成

1、Minidump概念

    minidump(小儲存器轉儲)可以理解為一個dump檔案,裡面記錄了能夠幫助除錯crash的最小有用資訊。實際上,如果你在系統屬性 ->

高階 -> 啟動和故障恢復 ->設定 ->寫入除錯資訊中選擇小記憶體轉儲(64 KB)”的話,當系統意外停止時都會在C:\Windows\Minidump\路徑下生成一個.dmp字尾的檔案,這個檔案就是minidump檔案,只不過這個是核心態的minidump

我們要生成的是使用者態的minidump,檔案中包含了程式執行的模組資訊、執行緒資訊、堆疊呼叫資訊等。而且為了符合其mini的特性,dump檔案是壓縮過的。

2、生成minidump檔案

通過drwtsn32NTSDCDB等除錯工具生成Dump檔案, drwtsn32存在的缺點雖然NTSDCDB可以完全解決,但並不是所有的作業系統中都安裝了NTSD

CDB等除錯工具。根據MiniDumpWriteDump介面,完全可以程式自動生成Dump檔案。

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*.dumpdbghelp.dll這四個檔案需要放在同一目錄下才好除錯,雙擊dump檔案時,就可以自動關聯到出錯程式碼位置。

4、為了獲取更多更深入的除錯資訊,需要把程式優化開關設定成禁用。

5、當異常程式碼定位成功以後,如果無法阻止異常的產生,可以用 __try結構包裝異常程式碼,__try try不同,前者可以捕獲非法指標產生的異常。

__try {

// 會異常的函式

}

__except( EXCEPTION_EXECUTE_HANDLER ){

// 異常處理

}

<這個優化,不設定,也可以dump到錯誤。>

如果是dll,設定上面這幾個屬性就可以了,在.exe主程式中執行異常處理函式RunCrashHandler()。

二、除錯Minidump檔案

  1. 雙擊minidump檔案(*.dmp)。預設會啟動vs2008。
  2. 選單Tools/Options, Debugging/Symbols,增加PDB檔案路徑。注:如果minidump檔案與pdb檔案在同一目錄,就不用設定這個了。
  3. 若除錯的程式需要微軟基礎庫的PDB資訊,可以增加一個路徑為:
  4. 在介面下方Cache Symbol From symbol…選擇本地儲存這些Symbols的路徑。 注:如果本地已儲存過微軟基礎庫的pdb,就直接按照此步操作設定本地路徑,不必執行上一步操作了。
  5. 設定程式碼路徑:

設定程式碼路徑:

剛開啟的dmp工程,進入解決方案的屬性。在這裡輸入源程式的程式碼路徑。注:一定是sln所在的路徑,而不是vcproj的路徑!

按F5,debug吧。