天天看點

也談Release版本排錯

也談Release版本排錯 鄧立波 深圳,2008-6 作者聯系方式: email: [email protected] msn: [email protected] 通常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版本通常會使用FPO(Frame-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儲存了相關資料來幫助周遊調用棧,如果不能加載到正确的pdb,StackWalk64将使用前面介紹的基于ebp的方式周遊調用棧,進而漏掉那些被FPO優化的函數。 (二)通過生成mini dump檔案定位bug。 通過dbghelp庫函數MiniDumpWriteDump将出錯時資訊寫入檔案,然後用windbg打開dump檔案,配置好symbols路徑,exe檔案路徑,source code 路徑,輸入.ecxr指令,就可以檢視詳細的調用棧,并能自動打開源檔案定位到代碼行。是以這種方法是簡單和最可靠的方法。 下面是一個簡單的dume類,隻要添入到工程即可,出錯時會自動生成dum檔案。 #include #include #include //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; 本文來自CSDN部落格,轉載請标明出處:http://blog.csdn.net/wzsy/archive/2008/08/29/2847063.aspx

繼續閱讀