1. 程式人生 > >也談Release版本排錯

也談Release版本排錯

通常Release除錯都是先通過SetUnhandledExceptionFilter捕獲異常,然後生成報告檔案,最後定位程式碼行,主要以下兩種方法:

(一)通過遍歷呼叫棧,將其呼叫棧資訊輸出到檔案。然後查找出錯地址。

查詢方式有兩種:

1)通過編譯器生成的包含行資訊的map檔案定位出錯位置。

通過在工程屬性”-“link”-“Project Options”手工輸入 /mapinfo:lines,生成包含行資訊map檔案。查詢時首先根據出錯地址範圍找到obj檔名,檢視obj檔案對應的行資訊,根據出錯地址範圍定位程式碼行。

2)通過編譯器生成的pdb檔案定位出錯位置。

debug

版本會自動生成pdb檔案,Release版本需要在工程屬性”-“link”面板中勾上選項“Generate debug info”,然後在工程屬性” -“C/C++”面板的“Debug Info”列表框選中“Program Database”

pdb檔案中查找出錯地址所在的程式碼行,需要通過dbghelp庫(包含在windbg目錄下),通過SymFromAddr函式可以獲取符號資訊,SymGetLineFromAddr64獲取所在程式碼行。

遍歷呼叫棧方法方法也有兩種:

1)自己遍歷呼叫棧

這種方法的缺陷是Release版本通常會使用FPOFrame-Pointer Omisstion)

優化,(注:在VC編譯器中可以在工程屬性”—>C/C++”—>“Project Options中去掉選項Oy-關閉PFO優化),PFO優化主要是通過省略呼叫時棧指標的儲存恢復等操作提高程式碼效率。下面自己遍歷呼叫棧的方法對採用了FPO優化的模組可能會遍歷不完全,遺漏掉一些函式。因此,即使自己的模組關閉了FPO,但第三方模組使用了FPO,如果報錯的地址位於第三方dll內(例如mfc42.dll),將有可能回溯不到自己模組內有問題的函式,從而很難定位bug

自己遍歷基於以下原理(這個原理只適用於沒有采用FPO優化的函式):

1 函式呼叫時call指令將返回地址(通常是下一條指令的地址)壓入堆疊 。

2 函式執行第一行會將 ebp壓入堆疊,儲存它以使得當函式返回能恢復ebp

3 Copy當前棧位置esp ebp

4.然後esp自減以空出棧空間容納函式的區域性變數

因此當前函式內的ebp即為第2步壓入ebp後的棧頂位置,由此可推匯出上一層函式的ebp[ebp],而上一層函式返回地址即為前一個壓入棧的值,即[ebp+4],由此可以一步步往上回溯呼叫棧。

2)通過dbghelp庫函式StackWalk64遍歷堆疊。

這種方式可以選擇是否載入pdb,對於做了那些被FPO優化的函式,pdb儲存了相關資料來幫助遍歷呼叫棧,如果不能載入到正確的pdbStackWalk64將使用前面介紹的基於ebp的方式遍歷呼叫棧,從而漏掉那些被FPO優化的函式。

(二)通過生成mini dump檔案定位bug

通過dbghelp庫函式MiniDumpWriteDump將出錯時資訊寫入檔案,然後用windbg開啟dump檔案,配置好symbols路徑,exe檔案路徑,source code 路徑,輸入.ecxr命令,就可以檢視詳細的呼叫棧,並能自動開啟原始檔定位到程式碼行。因此這種方法是簡單和最可靠的方法。

下面是一個簡單的dume類,只要添入到工程即可,出錯時會自動生成dum檔案。

#include <windows.h>

#include <tchar.h>

#include <assert.h>

//for VC6

#ifndef __in_bcount_opt

#define __in_bcount_opt(x)

#endif

#ifndef __out_bcount_opt

#define __out_bcount_opt(x)

#endif

//end (for VC6)

#include "dbghelp.h"

typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(

IN HANDLE hProcess,

IN DWORD ProcessId,

IN HANDLE hFile,

IN MINIDUMP_TYPE DumpType,

IN CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, OPTIONAL

IN CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, OPTIONAL

IN CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam OPTIONAL

);

class CMiniDumper

{

public:

CMiniDumper();

private:

static LPTOP_LEVEL_EXCEPTION_FILTER s_pPrevFilter;

static long WINAPI UnhandledExceptionFilter( struct _EXCEPTION_POINTERS *pExceptionInfo );

};

CMiniDumper g_minObject;

LPTOP_LEVEL_EXCEPTION_FILTER CMiniDumper::s_pPrevFilter = 0;

CMiniDumper::CMiniDumper()

{

assert(!s_pPrevFilter);

s_pPrevFilter = ::SetUnhandledExceptionFilter(UnhandledExceptionFilter);

}

long CMiniDumper::UnhandledExceptionFilter( struct _EXCEPTION_POINTERS *pExceptionInfo )

{

long ret = EXCEPTION_CONTINUE_SEARCH;

TCHAR szDbgHelpPath[_MAX_PATH] = {0};

TCHAR szDumpPath[_MAX_PATH] = {0};

TCHAR szPath[_MAX_PATH] = {0};

if (GetModuleFileName(NULL, szPath, _MAX_PATH))

{

TCHAR szDrive[_MAX_DRIVE] = {0};

TCHAR szDir[_MAX_DIR] = {0};

TCHAR szFileName[_MAX_FNAME] = {0};

_tsplitpath(szPath, szDrive, szDir, szFileName, 0);

_tcsncat(szDbgHelpPath, szDrive, _MAX_PATH);

_tcsncat(szDbgHelpPath, szDir, _MAX_PATH - _tcslen(szDbgHelpPath) - 1);

_tcsncat(szDbgHelpPath, _T("dbghelp.dll"), _MAX_PATH - _tcslen(szDbgHelpPath) - 1);

_tcsncat(szDumpPath, szDrive, _MAX_PATH);

_tcsncat(szDumpPath, szDir, _MAX_PATH - _tcslen(szDumpPath) - 1);

_tcsncat(szDumpPath, szFileName, _MAX_PATH - _tcslen(szDumpPath) - 1);

_tcsncat(szDumpPath, _T(".dmp"), _MAX_PATH - _tcslen(szDumpPath) - 1);

}

HMODULE hDll = ::LoadLibrary(szDbgHelpPath);

if (hDll==NULL)

hDll = ::LoadLibrary(_T("dbghelp.dll"));

assert(hDll);

if (hDll)

{

MINIDUMPWRITEDUMP pWriteDumpFun = (MINIDUMPWRITEDUMP)::GetProcAddress(hDll, "MiniDumpWriteDump");

if (pWriteDumpFun)

{

// create the file

HANDLE hFile = ::CreateFile(szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,

FILE_ATTRIBUTE_NORMAL, NULL);

if (hFile != INVALID_HANDLE_VALUE)

{

_MINIDUMP_EXCEPTION_INFORMATION ExInfo;

ExInfo.ThreadId = ::GetCurrentThreadId();

ExInfo.ExceptionPointers = pExceptionInfo;

ExInfo.ClientPointers = FALSE;

// write the dump

if (pWriteDumpFun(GetCurrentProcess(), GetCurrentProcessId(),

hFile, MiniDumpNormal, pExceptionInfo!=0? &ExInfo: 0, NULL, NULL))

ret = EXCEPTION_EXECUTE_HANDLER;

::CloseHandle(hFile);

}

}

}

if (s_pPrevFilter)

ret = s_pPrevFilter(pExceptionInfo);

return ret;

}