天天看點

如何定位Release 版本中程式崩潰的位置 ---利用map檔案 攔截windows崩潰函數 1       案例描述 2       案例分析 3       解決過程

作為Windows程式員,平時最擔心見到的事情可能就是程式發生了崩潰(異常),這時Windows會提示該程式執行了非法操作,即将關閉。請與您的供應商聯系。呵呵,這句微軟的“名言”,恐怕是程式員最怕見也最常見的東西了。

在一個大型軟體的測試過程中,初期出現程式崩潰似乎成了不可避免的事。其實測試中出現程式崩潰并不可怕,反而是測試的成功。作為開發的我們更需要關心的是程式中的哪個函數或哪一行導緻了系統崩潰,這樣才能有針對性的進行改正。

本文描述了自己總結的幾種定位崩潰的辦法。

以下是幾種常見的崩潰現象及對應的處理辦法:

1.        對于Release版本必現的崩潰且在Debug版本上也崩潰的程式。

解決思路:去掉所有斷點,直接在Debug版本上運作程式,在程式崩潰時,VC會自動跳轉定位到崩潰代碼行, 這種方法最簡單也最常用。

2.        對于在Debug版本上不崩潰但Release版本崩潰的程式,很有可能是Debug和Release版本的差異。例如Debug版本所有成員在構造時會被清0,而Release版本所有成員在構造時是記憶體裡面的原始值,而且Debug有運作時庫做保護,這些都會導緻某些程式在Debug正常而Release崩潰。

解決思路:1)在程式中加列印,通過程式崩潰之前的列印定位出錯位置; 2)逐段注釋代碼,直到程式不崩潰為止。這種方法耗時較長,對程式員要求較高,而且對于那種不是必現的bug或者很難搭建執行環境的情況就較難處理了。

3.        對于在客戶現場崩潰的情況,顯然不适合直接帶一台電腦去調試。

解決思路:應該有檔案記錄下崩潰資訊,客服人員可以将崩潰資訊檔案發送給程式員,以便程式員查詢崩潰原因,然後利用編譯時生成MAP檔案(工程資訊檔案,存放在版本編譯機中)的資訊來定位問題函數或問題代碼行。下面就這種方法展開讨論一下:

對于上節第三種情況,也是最難解決的情況,解決過程如下:

1.      崩潰回調注冊,攔截Windows程式崩潰;

2.      在回調進行中,輸出崩潰原因,崩潰記憶體位址,崩潰堆棧;

3.      工程輸出map檔案;

4.      通過崩潰記憶體位址以及map檔案找出崩潰的函數。

5.      使用COD檔案精确定位崩潰行

實際上,隻靠Windows的錯誤消息對話框提供的資訊量是很有限的。用SetUnhandledExceptionFilter注冊自定義錯誤處理回調函數,可以替換Win32預設的異常處理過濾器(top-level exception filter),而且能列印出崩潰堆棧,這對定位崩潰原因非常有用。

SetUnhandledExceptionFilter的函數原型:

LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(

LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ); 

功  能:注冊和登出異常處理回調;

用  法:第一次調用注冊異常處理回調,第二次調用登出;

傳回值:傳回目前的exception filter。需要儲存這個函數指針,在登出異常處理回調的時候,以此為參數再次調用SetUnhandledExceptionFilter。列印異常處理也需要此值。

參數: 異常處理的回調函數;

崩潰資訊在異常回調函數中列印,輸出到程式執行目錄下的檔案:

異常處理回調的函數原形:

LONG WINAPI CallBackDebugInfo ( EXCEPTION_POINTERS *pException); 

功  能:異常處理回調處理,列印崩潰資訊;

用  法:注冊自定義錯誤處理回調:SetUnhandledExceptionFilter (CallBackDebugInfo);

傳回值:EXCEPTION_CONTINUE_EXECUTION –  錯誤已經被修複,從異常發生處繼續執行

EXCEPTION_CONTINUE_SEARCH    –  繼續查找異常過濾器

EXCEPTION_EXECUTE_HANDLER   –  正常傳回

參數: 崩潰資訊結構,包含崩潰原因、崩潰子產品、崩潰位址、崩潰堆棧等;

常見崩潰原因有:

EXCEPTION_ACCESS_VIOLATION = C0000005h   讀寫記憶體錯誤

EXCEPTION_INT_DIVIDE_BY_ZERO = C0000094h  除0錯誤

EXCEPTION_STACK_OVERFLOW = C00000FDh  堆棧溢出或者越界

EXCEPTION_GUARD_PAGE = 80000001h 由Virtual Alloc建立起來的屬性頁沖突

EXCEPTION_NONCONTINUABLE_EXCEPTION = C0000025h不可持續異常,程式無法恢複執行,異常處理例程不應處理這個異常

EXCEPTION_INVALID_DISPOSITION = C0000026h在異常處理過程中系統使用的代碼

EXCEPTION_BREAKPOINT = 80000003h  調試時中斷(INT 3)

EXCEPTION_SINGLE_STEP = 80000004h  單步調試狀态(INT 1)

map檔案記錄程式的全局符号、源檔案和代碼行号資訊,是整個程式工程資訊的靜态文本。通過文本閱讀工具如Ultra Edit或記事本就可以打開Map檔案。

在 VC 中,打開“Project Settings”選項頁,選擇 C/C++ 頁籤,并在最下面的 Project Options 裡面輸入:/Zd ,然後選擇 Link 頁籤,選中“Generate mapfile”複選框。并在最下面的 Project Options 裡面輸入:/mapinfo:lines,表示生成 map 檔案時,加入行資訊。

最後編譯就可以生成 MAP 檔案,可以在工程的Debug或Release目錄下找到剛剛生成的MAP檔案,檔案名為“工程名.map”。

      通過上面的步驟,已經得到了 MAP 檔案,那麼我們該如何利用它呢?下面一步步示範使用MAP檔案定位程式崩潰行的過程。

      1.我們先在代碼中加入非法記憶體操作(最常見的異常)的代碼:

BOOL CMainFrameDlg::OnInitDialog()

{

         ::SetProp(m_hWnd,AfxGetApp()->m_pszExeName, (HANDLE)1);

         s32 *p=NULL;

         *p= 123;

2.執行程式,程式在開始就異常,在異常列印檔案中列印了如下資訊:

======================== 崩潰資訊 ==========================

崩潰時間: 2009/06/02 16:58:22

崩潰原因:非法記憶體操作

異常代碼 = c0000005

異常位址 = 0x0045a76f

異常子產品: E:\ccroot\liuxiaojing_Enterprise\Enterprise_VOB\70-nms1\pcmt2\prj_win32\Release\pcmt2.exe

Section name: .text - offset(rva) : 0x0005976f

---------------------- Trips of Stack ----------------------

E:\ccroot\liuxiaojing_Enterprise\Enterprise_VOB\70-nms1\pcmt2\prj_win32\Release\pcmt2.exe

name : pcmtver - location: 2bef

3.确定崩潰位址是:0x0005976f,在Map檔案中定位函數:

0001:00059420 ?OnCreate@CMainFrameDlg@@IAEHPAUtagCREATESTRUCTA@@@Z 0045a420 f  MainFrameDlg.obj

 0001:00059460       ?SetTooltips@CMainFrameDlg@@AAEXXZ 0045a460 f   MainFrameDlg.obj

 0001:00059700       ?OnTranslate@CMainFrameDlg@@IAEJIJ@Z 0045a700 f   MainFrameDlg.obj

 0001:00059730       ?OnInitDialog@CMainFrameDlg@@MAEHXZ 0045a730 f   MainFrameDlg.obj

 0001:00059a10  ?OnSysCommand@CMainFrameDlg@@IAEXIJ@Z 0045aa10 f   MainFrameDlg.obj

 0001:00059c20       ?OnPaint@CMainFrameDlg@@IAEXXZ 0045ac20 f   MainFrameDlg.obj

根據00059730< 0005976f < 00059a10 ,确定是在CMainFrameDlg 的OnInitDialog函數中的某一行産生了異常。

Line numbers for .\Release\MainFrameDlg.obj(E:\ccroot\liuxiaojing_Enterprise\Enterprise_VOB\70-nms1\pcmt2\source\MainFrameDlg.cpp) segment .text

   498 0001:00059647   499 0001:00059667   501 0001:0005966e   502 0001:000596af

   503 0001:000596ed   506 0001:00059700   507 0001:00059703   508 0001:00059708

   510 0001:0005970f   511 0001:00059720   512 0001:00059723   515 0001:00059730

   516 0001:0005974e   521 0001:0005976d   524 0001:0005977e   526 0001:0005978b

我們在map檔案的代碼行資訊裡查找不超過計算結果0x0005976f,但可以找最接近的數。發現是MainFrameDlg.cpp 檔案中的:521 0001:0005976d,而程式實際崩潰行在519(注釋行和空行也要計算在内),非常接近實際崩潰行了,考慮到程式實際執行的是彙編指令,我們可以在(516 ~524)行區間内尋找到實際崩潰行。