前面提到了dump檔案能儲存進程狀態,方便分析。由於dump檔案記錄的是進程某一時刻的具體資訊,所以儲存dump的時機非常重要。比如程式崩潰,dump應該選在引發崩潰的指令執行時(也就是1st chance exception發生的時候)擷取,這樣分析dump的時候就能夠看到問題的直接原因。
Adplus是跟Windbg在同一個目錄的VBS指令碼。Adplus主要是用來抓取dump檔案。 詳細的資訊,可以參考Windbg協助檔案中關於adplus的協助。有下面一些常見用法:
假設我們的目標程式是test.exe:
假設test.exe運行一段時間崩潰,在test.exe啟動後崩潰前的這個時間段,運行下面的命令監視:
Adplus –crash –pn test.exe –o C:\dumps |
當test.exe發生2nd chance exception崩潰的時候,adplus在C:\dumps產生full dump檔案。當發生1st chance AV exception, 或者1st chance breakpoint exception的時候,adplus在C:\dumps產生mini dump檔案。
也可以用:
Adplus –crash –pn test.exe –fullonfirst –o C:\dumps |
差別在於,加上-fullonfirst參數後,無論是1st chance exception還是2nd chance exception,都會產生full dump檔案。
假如test.exe發生deadlock,或者memory leak,並不是crash,需要擷取任意時刻的一個dump,可以用下面的命令:
Adplus –hang –pn test.exe –o C:\dumps |
該命令立刻把test.exe的full dump 抓到C:\dumps下。
Adplus更靈活的方法就是用-c參數帶設定檔。在設定檔裡面,可以選擇exception發生的時間,產生的dump是mini dump還是full dump,還可以設定斷點等等。對於adplus各項參數的選用原則,在最後一章還會作進一步介紹。
案例分析:華生醫生(Dr. Watson)在什麼情況下不能記錄Dump檔案
問題描述
客戶聲稱用VC開發的程式偶爾會崩潰。為了擷取詳細資料,客戶啟用了Dr. Watson,以便程式崩潰的時候可以自動擷取dump檔案。但是問題再次發生後,Dr. Watson並沒有記錄dump檔案。
背景知識
dump檔案包含的是記憶體鏡像資訊。在Windows系統上,dump檔案分為核心dump和使用者態dump兩種。前者一般用來分析核心相關的問題,比如驅動程式;後者一般用來分析使用者態程式的問題。如果不作說明,本書後面所指的dump都表示使用者態dump。使用者態的dump又分成mini dump和full dump。前者尺寸小,只記錄一些常用資訊;後者則是把目標進程使用者態的所有內容都記錄下來。Windows提供了MiniDumpWriteDump API可供程式調用來產生mini dump。通過調試器和相關工具,可以抓取目標程式的full dump。拿到dump後,可以通過調試器檢查dump中的內容,比如call stack,memory,exception等等。關於dump和調試器的更詳細資料,後面會有更多介紹。跟Dr. Watson相關的文檔是:
Description of the Dr. Watson for Windows (Drwtsn32.exe) Tool http://support.microsoft.com/?id=308538 Specifying the Debugger for Unhandled User Mode Exceptions http://support.microsoft.com/?id=121434 INFO: Choosing the Debugger That the System Will Spawn http://support.microsoft.com/?id=103861 |
也就是說,通過設定註冊表中的AeDebug項,可以在程式崩潰後,選擇調試器進行調試。選擇Dr. Watson就可以直接產生dump檔案。
問題分析
回到這個問題,客戶並沒有擷取到dump檔案,可能性有兩個:
1. Dr. Watson工作不正常。
2. 客戶的程式根本沒有崩潰,不過是正常退出而已。
為了測試第1點,提供了如下的代碼給客戶測試:
測試上面的代碼,Dr. Watson成功地擷取了dump檔案。也就是說,Dr. Watson工作是正常的。那看來客戶聲稱的崩潰可能並不是unhandled exception導致的。說不定在非預料情況下調用了ExitProcess,被客戶誤認為是崩潰。所以,抓取資訊不應該局限於unhandled exception,而應該檢查進程退出的原因。
當程式在Windbg調試器中退出的時候,系統會觸發調試器的進程退出訊息,可以在這個時候抓取dump來分析進程退出的原因。
如果讓客戶每次都先啟動Windbg,然後用Windbg啟動程式,操作起來很複雜。最好有一個自動的方法。Windows提供了讓指定程式隨調試器啟動的選項。設定註冊表後,當設定的進程啟動的時候,系統先啟動指定的調試器,然後把目標進程的地址和命令列作為參數傳遞給調試器,調試器再啟動目標進程調試。這個選項在無法手動從調試器中啟動程式的時候特別有用,比如調試先於使用者登入而啟動Windows Service程式,就必須使用這個方法:
How to debug Windows services http://support.microsoft.com/?kbid=824344
|
有趣的是,好多惡意程式也通過這個方法來達到載入進程的目的。很多人把這個方法叫做IFEO 劫持(Image File Execution Option Hacking)。
在Windbg目錄下,有一個叫做adplus.vbs的指令碼可以方便地調用Windbg來擷取dump檔案。所以這裡可以借用這個指令碼:
How to use ADPlus to troubleshoot "hangs" and "crashes" http://support.microsoft.com/kb/286350/EN-US/
|
指令碼的詳細說明可以參考adplus /?的協助。
新的做法
結合上面的資訊,具體做法是:
1. 在客戶機器的Image File Execution Options註冊表下面建立跟問題程式同名的鍵。
2. 在這個鍵的下面建立Debugger字串類型子鍵。
3. 設定Debugger= C:\Debuggers\autodump.bat。
4. 編輯C:\Debuggers\autodump.bat檔案的內容為如下:
cscript.exe C:\Debuggers\adplus.vbs -crash -o C:\dumps -quiet -sc %1
|
通過上面的設定,當程式啟動的時候,系統自動運行cscript.exe來執行adplus.vbs指令碼。Adplus.vbs指令碼的-sc參數指定需要啟動的目標進程路徑(路徑作為參數又系統傳入,bat檔案中的%1代表這個參數),-crash參數表示監視進程退出,-o參數指定dump檔案路徑,-quiet參數取消額外的提示。可以用notepad.exe作為小白鼠做一個實驗,看看關閉notepad.exe的時候,是否有dump產生。
根據上面的設定,問題再次發生後,C:\dumps目錄產生了兩個dump檔案。檔案名稱分別是:
PID-0__Spawned0__1st_chance_Process_Shut_Down__full_178C_DateTime_0928.dmp PID-0__Spawned0__2nd_chance_CPlusPlusEH__full_178C_2006-06-21_DateTime_0928.dmp
|
注意看第二個的名字,這個名字表示發生2nd chance的C++ exception!開啟這個dump後找到了對應的call stack,發現的確是客戶忘記了catch潛在的C++異常。修改代碼添加對應的catch後,問題解決。
問題解決了,可是為什麼華生醫生(Dr. Watson)抓不到dump呢
當然疑問並沒有隨著問題的解決而結束。既然是unhandled exception導致的crash,為什麼Dr. Watson抓不到呢?首先建立兩個不同的程式來測試Dr. Watson的行為:
int _tmain(int argc, _TCHAR* argv[]) { throw 1; return 0; } int _tmain(int argc, _TCHAR* argv[]) { int *p=0; *p=0; return 0; }
|
果然,對於第一個程式,Dr. Watson並沒有儲存dump檔案。對於第二個,Dr. Watson工作正常。看來的確跟異常類型相關。
仔細回憶一下。當AeDebug下的Auto設定為0的時候,系統會彈出前面提到的紅色框框。對於上面這兩個程式,框框的內容是不一樣的。
在我這裡,看到的對話方塊分別是(對話方塊出現的時候用Ctrl+C儲存的資訊):
--------------------------- Microsoft Visual C++ Debug Library --------------------------- Debug Error! Program: d:\xiongli\today\exceptioninject\debug\exceptioninject.exe This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information. (Press Retry to debug the application) --------------------------- Abort Retry Ignore --------------------------- --------------------------- exceptioninject.exe - Application Error --------------------------- The instruction at "0x00411908" referenced memory at "0x00000000". The memory could not be "written". Click on OK to terminate the program Click on CANCEL to debug the program --------------------------- OK Cancel ---------------------------
|
兩者行為完全不一樣!如果做更多的測試,會發現對話方塊的細節還跟編譯模式release/debug 相關。
程式可以通過SetUnhandledExceptionFilter函數來修改unhanded exception的預設處理函數。這裡,C++運行庫在初始化CRT(C Runtime)的時候,傳入了CRT的處理函數 (msvcrt!CxxUnhandledExceptionFilter)。如果發生unhandled exception,該函數會判斷異常的號碼,如果是C++異常,就會彈出第一個對話方塊,否則就交給系統預設的處理函數(kernel32!UnhandledExceptionFilter)處理。第一種情況的call stack 如下:
USER32!MessageBoxA MSVCR80D!__crtMessageBoxA MSVCR80D!__crtMessageWindowA MSVCR80D!_VCrtDbgReportA MSVCR80D!_CrtDbgReportV MSVCR80D!_CrtDbgReport MSVCR80D!_NMSG_WRITE MSVCR80D!abort MSVCR80D!terminate MSVCR80D!__CxxUnhandledExceptionFilter kernel32!UnhandledExceptionFilter MSVCR80D!_XcptFilter
|
第二種情況CRT交給系統處理。Callstack如下:
ntdll!KiFastSystemCallRet ntdll!ZwRaiseHardError+0xc kernel32!UnhandledExceptionFilter+0x4b4 release_crash!_XcptFilter+0x2e release_crash!mainCRTStartup+0x1aa release_crash!_except_handler3+0x61 ntdll!ExecuteHandler2+0x26 ntdll!ExecuteHandler+0x24 ntdll!KiUserExceptionDispatcher+0xe release_crash!main+0x28 release_crash!mainCRTStartup+0x170 kernel32!BaseProcessStart+0x23
|
詳細的資訊可以參考:
SetUnhandledExceptionFilter http://msdn.microsoft.com/library/default.asp? url=/library/en-us/debug/base/setunhandledexceptionfilter.asp UnhandledExceptionFilter http://msdn.microsoft.com/library/default.asp? url=/library/en-us/debug/base/unhandledexceptionfilter.asp
|
上面觀察到的資訊能解釋Dr. Watson的行為嗎?看起來似乎有關係。為了進一步確認這個問題,可以通過下面的測試,使用Windbg代替Dr. Watson,看看是否可以擷取dump。如果僅僅換一個調試器就可以擷取dump,那說明問題是跟調試器相關,跟程式拋出的異常無關。具體做法是:
1. 運行drwtsn32.exe –i註冊Dr. Watson。
2. 開啟AeDebug註冊表,找到Debugger項,裡面應該是drwtsn32 -p %ld -e %ld -g。
3. 修改Debugger為: C:\debuggers\windbg.exe -p %ld -e %ld -c ".dump /mfh C:\myfile.dmp ;q"。
當unhanded exception發生後,系統會啟動windbg.exe作為調試器載入到目標進程。但是windbg.exe不會自動擷取dump,所以需要用-c參數來指定初始命令。命令之間可以用分開分割。這裡的.dump /mfh C:\myfile.dmp命令就是用來產生dump檔案的。接下來的q命令是讓windbg.exe在dump產生完畢後自動結束。用這個方法,對於unhandled C++ exception,windbg.exe是可以擷取dump檔案的。所以我認為Dr. Watson這個工具在擷取dump的時候是有缺陷的。研究的發現在:
http://eparg.spaces.msn.com/blog/cns!59BFC22C0E7E1A76!1213.entry