天天看點

DUMP檔案分析2:一個最簡單的DUMP分析示例

本節開始,我将在示例中給大家講述基本的DUMP檔案分析方法。讀者應該對Windows系統比較了解,同時比較熟悉Windbg。

本節的示例非常簡單,也非常經典,就是常常會遇到的通路空指針。Windows将程序記憶體空間的一段範圍設定為NULL(64KB),這段空間禁止通路。一旦我們在程式中不小心通路了這段位址(通常是指針未初始化),就會引起通路越界異常,造成程式崩潰,例如本示例:

DUMP檔案分析2:一個最簡單的DUMP分析示例

這是一個簡單的MFC程式,在Crash Me!的消息響應函數中,寫有以下代碼:

int* p = NULL;

int a = *p;
           

即刻意讓程式讀取空指針而崩潰。

首先,我們使用第1節中程式設計方法儲存DUMP,事實上,第1節中的示例就是本節的程式。我們将自動儲存的mini.dmp拖入Windbg中(直接拖入即可,當然也可以通過菜單“File”——“Open Crash Dump…”來打開),打開之後,可以看到一些資訊:

DUMP檔案分析2:一個最簡單的DUMP分析示例

可以看到詳細的DUMP類型,同時,Windbg還對DUMP檔案進行了初步分析,找到了異常。事實上,這正是示例程式發生的異常。(通路違例,異常代碼0xC0000005)。然而,僅僅知道發生異常的類型是不夠的,我們還需要更詳細的資訊,那麼,從什麼地方入手呢?

首先,我們總是期望調試器足夠智能,我們什麼都不用做不用想,調試器就可以把問題詳細的揭露出來,Windbg能夠做到嗎?答案是肯定的。Windbg帶有自動的異常分析指令(!analyze),我們輸入 !analyze -v,其中 -v 選項是盡可能輸出詳細的資訊。然後,我們得到了一大堆輸出。我們來詳細看一下這些輸出的含義,由于輸出比較長,我将分段來介紹。

0:000> !analyze -v
*********************************************************************                                                                             
*                        Exception Analysis                                                                                                          
*********************************************************************
Failed calling InternetOpenUrl, GLE=12029
           

微軟維護有線上的崩潰解決方案,這裡是嘗試連接配接線上資料庫,然而失敗。這個功能你應該已經看到過:

DUMP檔案分析2:一個最簡單的DUMP分析示例

如果你的電腦沒有連網,那麼你可能看不到該視窗或者視窗一閃而過。

FAULTING_IP: 
dump1+d
f7310d b08            mov     ecx,dword ptr [eax]
           

FAULTING_IP,很重要的資訊,IP就是EIP,即出錯的指令位址。

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: f7310d (dump1+)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 
NumberParameters: 
   Parameter[]: 
   Parameter[]: 
Attempt to read from address 
           

EXCEPTION_RECORD,很重要的資訊,記錄崩潰的資訊。可以看到異常位址,異常代碼以及具體的異常原因“嘗試讀取位址00000000”。

異常分類,這裡異常類别很明确NULL_POINTER_READ。

發生異常的程序名。

STACK_TEXT:  
WARNING: Stack unwind information not available. Following frames may be wrong.
0045ef80 579f7072    dump1+
0045efc4 579f77ba 0045f8dc   mfc90d!_AfxDispatchCmdMsg+ [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\cmdtarg.cpp @ ]
0045f028 579cd5f3    
...
           

STACK_TEXT很重要,顯示異常發生時的函數調用棧。最後一列容易了解,是子產品+函數,那前面還有5列數字,表示什麼呢?第一清單示函數調用時的EBP,第二清單示函數的傳回位址,後三列則是函數的參數(不完全)。

LAST_CONTROL_TRANSFER表示最後一次控制流的轉移,即最後一次函數調用,可以将後面的位址與調用棧比較。

FOLLOWUP_IP: 
dump1+d
f7310d b08            mov     ecx,dword ptr [eax]
           

與FAULTING_IP類似。

SYMBOL_STACK_INDEX:  0
SYMBOL_NAME:  dump1+1310d
FOLLOWUP_NAME:  MachineOwner
MODULE_NAME: dump1
IMAGE_NAME:  dump1.exe
           

表示崩潰指令所在的子產品,符号等等。其中SYMBOL_NAME可以明确的指出函數。

好,!analyze -v指令輸出的說明到此為止。經過上面的說明,我們已經知道了問題的原因,以及具體的位置。但你是否發現了一些問題,與dump1相關的内容都隻有相對于dump1子產品的位址(如dump1+1310d),并不知道具體是哪一個函數。這是因為Windbg沒有加載相應的pdb調試檔案,我們可以在Windbg的“Symbol File Path”中添加dump1的pdb檔案路徑,然後重新載入。你可能會問,如果是release版本,沒有生成pdb檔案怎麼辦?這樣的話對于調試來說就比較麻煩了,此時,你隻能通過逆向錯誤指令附近的代碼,來推測錯誤的位置。一般來說,為了調試友善,最好在生成Release版本時也生成調試檔案。加載pdb檔案之後,dump1内部的符号也解析了出來:

FAULTING_IP: 
dump1!Cdump1Dlg::OnBnClickedButton1+2d [d:\projects\dump\dump1\dump1\dump1dlg.cpp @ 160]
0017310d 8b08            mov     ecx,dword ptr [eax]
           

如果有源代碼的話,還可以直接定位源代碼:

FAULTING_SOURCE_CODE:  
   : void Cdump1Dlg::OnBnClickedButton1()
   : {
   :     int* p = NULL;
   : 
>  :     int a = *p;
   : }
           

這樣的話,錯誤就一目了然了。

[ PS:寫示例的時候沒有注意,如果你以Release編譯,并開啟優化的化,會發現不成功。這是由于代碼中 a 變量并沒有被使用,編譯的時候被優化掉了,你可以把 int a= *p 改為 printf(“%d\n”, *p); ]

繼續閱讀