使用 Minidumps 和 Visual Studio .NET 進行崩潰後調試

來源:互聯網
上載者:User
本文關鍵字:Minidumps, Windows, SEH, VisualC, .NET

摘要

本文講述了 minidumps 是怎樣工作的、當你的程式崩潰的時候應該如何產生它們、以及如何在 Visual Studio .NET 中將它們重新讀入。

原文作者:Andy Pennell 中文翻譯:Victor 原文連結:http://www.codeproject.com/debug/postmortemdebug_standalone1.asp

如果你的程式在客戶的機器上崩潰了,那麼你現在可以使用 minidumps 和 Microsoft Visual Studio .NET 調試器在事後進行調試。本文講述了 minidumps 是怎樣工作的、當你的程式崩潰的時候應該如何產生它們、以及如何在 Visual Studio .NET 中將它們重新讀入。微軟的錯誤報表程式之所以能夠改進 Windows 作業系統和諸如 Visual Studio .NET 這樣的應用程式的穩定性,其關鍵就在於 minidumps。本文也講述了如何使用 Microsoft 符號伺服器自動地為系統組件尋找符號。你在閱讀本文前應該已經熟悉 Win32 和 C++ 編程了。

目錄

  • 什麼是 Minidump?
  • 建立一個 Minidump
  • 與 Build 相關的問題
  • 使用 MiniDumpWriteDump 寫一個 Minidump
  • 使用 Visual Studio .NET 讀入一個 Minidump
  • 微軟是如何使用 Minidumps 的
  • 進一步改進
  • 總結
  • 附:著作權及免責聲明
什麼是 Minidump?

一個 minidump 就是一個檔案,它包括了崩潰的應用程式中最重要的部分。它在使用者的機器上產生,然後使用者就可以將其提交給開發人員。開發人員可以裝入這個 dump,以便尋找崩潰的原因,並發布一個補丁。

從早期的 Windows NT 開始,Dr.Watson 程式就能夠產生以 .dmp 為副檔名的崩潰 dump 檔案。但是它們卻沒有想像中那樣有用,因為有兩個問題:

  1. 它們太大了。一個應用程式的 dump 包含了整個進程空間中的每一個位元組,所以即使是 Notepad 這麼簡單的程式崩潰後也會產生幾兆的 dump 檔案,如果是 Word 這樣的應用程式崩潰了的話,可能會產生上百兆的 dump 檔案。
  2. 它們所包含的內容並不都是有用的。事實上,Dr.Watson 是一個 just-in-time(JIT)調試器,但調試器很難得到一個已載入模組的完整路徑。完整的調試器,例如 Visual Studio 調試器,為了獲得這些路徑進行了很多額外的工作,但是 Dr.Watson 卻沒有。這通常就會導致毫無意義的模組名,如 MOD0000 等等。

Minidumps 在設計時就使用了幾個方法用來解決上述問題:

  • 只有幾個地區被儲存下來,而不是整個進程空間。儲存諸如 Kernel32.dll 這樣的模組簡直毫無意義;只要給出了版本號碼,就可以很容易地從一張 Windows CD 上拿到這個檔案。預設情況下,程式的記憶體堆是不儲存的;不需要調試一個崩潰比率高得令人乍舌的崩潰點。當然,如果你想的話,還是可以把堆儲存下來的。
  • Minidump 產生代碼可以得到精確、完整的模組資訊,包括它們的名字、路徑、版本資訊和內部時間戳記。
  • Minidump 產生代碼還可以得到線程列表,包括它們的上下文(也就是寄存器集合)和堆棧中的內容。
  • 整個檔案是壓縮的,進一步減小了大小。在 Windows XP 上,一個 Notepad 的 minidump 大概在 6K 左右,比上面提到的同一進程的崩潰 dump 小了將近 300 倍。

注意:Windows XP 在電腦停止回應後也會產生一種核心態的 minidumps,但是本文討論的是更常見的使用者態 minidumps。

建立一個 Minidump

有三種方法可以建立一個 minidump:

  • 在你自己的應用程式中添加代碼,在遇到一個未捕獲的異常時寫 minidump。
  • 在 Visual Studio .NET 的整合式開發環境中偵錯工具,單擊“調試”菜單上的“將轉儲另存新檔”。
  • 什麼也不做。

方法一將在下文中詳細討論。

方法二僅適用於一個已經裝了調試器的工作站,很有可能只在Team Dev內才有用(例如:在另一個開發人員或者測試人員的機器上)。如果你使用 Visual Studio .NET 對崩潰進行調試,那麼你就可以這麼做了。你可以儲存一個 Minidump 或者一個包含堆的 Minidump。你不需要任何符號或者 PDB 檔案就可以儲存一個 dump 檔案;但是在將它們重新讀入時你需要。

方法三隻在 Windows XP 下有效,如果程式遇到了一個未捕獲的異常,並且沒有啟動 JIT 偵錯器,系統就會自動建立一個 minidump。然後,這個 minidump 將直接提交給 Microsoft,你就沒有機會尋找崩潰的原因了。

與 Build 相關的問題

為了使你的程式在崩潰時建立 dumps,你必須配置你的產生選項使其產生完整的調試資訊,特別是在產生最終版的時候。產生 PDB 檔案之後,你必須將你要發布給使用者的每一個二進位檔案及其對應的 PDB 檔案進行歸檔;之後在調試那些使用者提交的 minidumps 時你將需要這些 PDB 檔案。

此外,確保你的二進位檔案中包含正確的版本資訊。你所發布的每一個組件的每一個版本都應該有一個不同的版本號碼,以便你能夠與 minidump 對應起來。版本欄位(二進位資源中的版本資訊——譯者注)可以幫你匹配這兩者。但調試器本身並不使用版本資訊,它是基於 PE 檔案頭內包含的內部時間戳記來匹配二進位檔案的。

輸出時,為最終版產生調試資訊會有一些小的影響。會產生一個 PDB 檔案,在產生所使用的機器上佔用一些空間,而且最終產生的二進位檔案會增大幾百個位元組以便在 可攜式執行檔中記錄調試目錄下的 PDB 檔案名。你不應該向使用者發布 PDB 檔案;這會使你的程式更容易地被使用者反向工程。

使用 MiniDumpWriteDump 寫一個 Minidump

用來儲存一個 minidump 的關鍵 API 是 MiniDumpWriteDump,它從 DbgHelp.dll 中匯出,這是一個 Platform SDK 中可以再分發的 DLL。確保你使用的是 Windows XP 版本 5.1.2600;更早期的 beta 和 release candidate 版中的這個 API 有問題,並且 Windows 2000 中包含的 5.0.x 版中沒有匯出這個函數。如果你有比 5.0 更早的版本,那麼它一定是來自於 System Debugger package(包括 WinDbg 等工具),並且它是不可再分發的。

在調用這個 API 之前,你需要用 SetUnhandledExceptionFilter API 設定一個未捕獲異常的處理器(Unhandled Exception Handler,也就是 Top-level Exception Filter,最後一個異常過濾器——譯者注),以便能夠捕獲到崩潰點。這樣就可以使這個 Filter 函數在程式遇到未捕獲的異常的時候被調用。在某些未捕獲的異常中,比方說兩次堆疊溢位(double stack fault,即在處理 stack fault 的時候又發生了 stack fault——譯者注),作業系統會立即結束應用程式,既不會調用 filter、也不會啟動 JIT 調試器(據查,網上資料說如果發生了 triple fault,CPU 將會關閉,到時候除了 Reset 訊號,就沒有什麼能救得了這台機器的了。所以可以認為 double fault 是非常嚴重的情況——譯者注)。

在你的 filter 函數中,你需要載入 DbgHelp.dll。這並不是調用 LoadLibrary 這麼簡單;在 Windows 2000 系統中,你將會訪問到 System32 目錄下的那個不正確的版本。示範代碼試著從 EXE 檔案所在的位置裝入這個庫。將正確版本的 DbgHelp.dll 放在 EXE 檔案所在的目錄下;否則的話,代碼只好進行一次普通的 LoadLibrary 調用,這也就使得程式只能在 Windows XP 下工作。

裝入 DLL 後,它接著檢查匯出函數名;如果正確的話,它會用合適的名字建立一個檔案,比方說用程式名加 .dmp 尾碼儲存在臨時目錄中。這個檔案控制代碼接著會傳遞給 API,並附上一些其它的資訊,諸如進程 ID 和所需的 dump 檔案類型等。例子中使用的是 MiniDumpNormal。也許你想使用 MiniDumpWithDataSegs 標誌位,也就是相當於 Visual Studio 調試器中的“附帶堆資訊的小型傾印”選項,這顯然會使 dump 檔案變得更大。

在 .dmp 檔案建立完成後,程式會詢問使用者將其儲存在什麼位置。然後使用者就可以通過 e-mail 或者使用 FTP 將檔案發送過來供你分析。

如果你想使用本文所提供的示範代碼的話,就在你的工程中加入 mdump.h 和 mdump.cpp 檔案,並聲明一個全域的 MiniDumper 對象。這個對象的建構函式需要一個參數,也就是 minidump 檔案的基礎檔案名稱。為了能夠正常運行,你還需要把正確的 DbgHelp.dll 放在 EXE 所在的目錄下。

不能使用調試器調試寫 minidump 的那段代碼(在示範代碼中,也就是 Minidumper::TopLevelFilter)。如果進程附加了一個調試器,那麼未捕獲異常的處理函數將永遠不會被調用(關於這一點,MSDN 中關於結構化異常處理的文檔說得很清楚:調試器有兩次機會處理一個異常,一次是在異常剛剛發生時,也就是 VC6 經常報出來的“First-chance exception”;還有一次是在執行了所有的 filter,發現沒有一個能夠處理之後,通知調試器發生了一個“Second-chance exception”,此時調試器會中斷程式並進入偵錯模式,因為絕大多數情況下這就屬於未捕獲的異常,是程式 BUG,在非調試環境下是要崩潰的——譯者注)。如果你遇到了問題需要調試的話,你需要使用 MessageBox 調試。

使用 Visual Studio .NET 讀入一個 Minidump

文章的這一部分使用了一個例子,在 Windows 2000 系統下手工建立了 Notepad 的一個 minidump,然後在 Windows XP 系統下調試。

啟動 Visual Studio .NET,單擊[檔案] 功能表上的“開啟解決方案”,在“檔案類型”下拉式清單方塊中選擇“轉儲檔案(*.dmp; *.mdmp)”(555~~~為什麼我在 Visual Studio .NET 2003 中找不到這一項……不過還好,可以直接雙擊 .dmp 檔案開啟——譯者注),找到 minidump 檔案,然後點擊“開啟”建立一個預設工程。

按 F5 在調試器中啟動這個 dump,這一步將為你開始調試提供一些資訊。調試器將建立一個假的進程;在輸出視窗中顯示了很多模組載入的訊息。此時調試器僅僅是在重建崩潰時的進程狀態。在顯示了一條 EXE 不包含調試資訊的警告訊息之後,調試器停在了使用者崩潰的地方,諸如一個非法訪問什麼的。這時候如果你查看調用棧視窗,你會發現缺少符號和很多有用的資訊。

圖 1:起初沒有符號檔案的堆棧視窗

為了讀取一個 minidump,你通常需要相關的二進位拷貝。為了找到正確的二進位檔案,開啟模組視窗。

圖 2:起初沒有二進位檔案的模組視窗

圖 2 展示了 Notepad 的例子,並且說明了兩個情況。首先,二進位所在的路徑名前標上了一個星號,這表示這些是在使用者機器上的模組路徑,但是在本地卻找不到對應的二進位檔案。其次,在“Information”一列中全都寫著“No matching binary found”。找到對應的二進位檔案的關鍵是注意“Version”欄位和檔案名稱。在這個例子中,大多數系統檔案的版本號碼都是 2195,也就是 Windows 2000。雖然無法從這些資訊上立即得知確切的 service pack (SP) 或者 quality fix engineering (QFE),但這些資訊可以從微軟的 DLL 協助資料庫中查詢到:http://support.microsoft.com/servicedesks/fileversion/dllinfo.asp。

現在,你需要找到一張 Windows 作業系統的 CD 或者已經安裝了正確版本的機器,然後將所需要的檔案複製到一個目錄。通常情況下沒有必要把進程中每個模組的二進位檔案都找出來,但是找出那些在每個調用棧上的關鍵模組是很重要的。這通常包括作業系統的二進位檔案(例如 Kernel32.dll)和你自己的二進位模組(在這個例子中就是 Notepad.exe)。

在你找到這些二進位檔案、並把它們拷貝到一個本地目錄之後,單擊“調試”菜單中的“停止調試”命令。然後在方案總管中,按右鍵工程表徵圖,在捷徑功能表上單擊“屬性”,你將看到“調試”屬性頁面。在“命令參數”中填入“MODPATH”,跟上一個等號,然後輸入二進位檔案所在的位置,如果有多個位置,可以用分號分隔。在這個例子中,它是:

MODPATH=m:/sysbits

在設定好了路徑之後,按 F5 重新裝入 minidump,MODPATH 的值將通過命令列參數傳遞給調試器;在 Visual Studio .NET 的後續版本中應該會有更方便的方法設定這個參數,或許可以作為一個選項出現在屬性對話方塊中。

儘管找到二進位檔案並不太可能改善調用棧視窗的情況,但是它卻能解決模組視窗中的問題, 3 所示:

圖 3:找到二進位檔案後的模組視窗

它現在顯示的不再是“No matching binary found”,而是“Cannot find or open a required DBG file”(我怎麼覺得這個地方的英文文法應該用“nor”而不是“or”……呵呵,不管它了,反正微軟程式中無傷大雅的語法錯誤已經不是第一次被發現了——譯者注)和“No symbols loaded”。前一條訊息出現在那些使用 DBG 檔案儲存體調試資訊的系統 DLL 上,後一條訊息則出現在使用 PDB 檔案的 DLL 上。找到對應的二進位檔案並不能使你看到調用棧;你還需要找到它們對應的調試資訊。

方法 A:坎坷之路

為了完整地分析一個 minidump,你需要找到所有的調試資訊。但為了節省時間,你可以只找那些你需要的資訊。本例中的調用棧列表包含了 User32.dll 和 Kernel32.dll,所以需要它們的調試資訊。

對應的調試資訊
作業系統 所需的檔案
Windows NT 4 DBGs
Windows 2000 DBGs, PDBs
Windows XP PDBs

一個找系統符號的好地方在 http://www.microsoft.com/ddk/debugging,你也可以在 Windows NT Server 和 Windows 2000 Server 作業系統的 Support CD 上找到系統符號。在本例中,它們被拷貝到了二進位代碼所在的位置。實際情況中你可能會遇到非微軟發布的二進位模組,這時候你就需要它們的 PDB 檔案了。同樣在本例中,Notepad 的 DBG 和 PDB 檔案也被拷貝了出來,因為它是我們使用的樣本應用程式。

在單擊“調試”菜單上的“停止調試”命令後再按 F5 就會看到調用棧列表, 4 所示。你也許發現了,由於添加了新的二進位檔案和調試資訊,調用棧發生了變化。這就是我們要的結果;只有在具有調試資訊的情況下才能準確地回溯一個調用棧,提供的資訊越詳細,你得到的堆棧就越精確,通常能夠把那些原來沒有顯示出來的棧幀資訊暴露出來。

在本例中並沒有崩潰。在實際情況中,這些資訊應該已經能夠協助你尋找出大概 70% 的崩潰原因。另外,本例中的調用棧列表是使用微軟隨系統組件提供的、經過刪減的符號檔案所產生的,所以沒有行號資訊。如果你使用自己產生的、完整的 PDB 檔案,你可以看到一個更詳盡的調用棧。

圖 4:找到符號和二進位檔案後的調用棧視窗

符號伺服器

如果你需要處理大量 minidumps,進行大範圍調試,那麼儲存和訪問所有的二進位檔案以及 PDB/DBG 檔案將變得很困難。Windows NT 中包含了一種叫做符號伺服器的技術,起初只是用來儲存符號檔案的,後來擴充到也支援二進位檔案的尋找。Windows NT 調試器是第一個支援它的工具,但實際上 Visual Studio .NET 也支援它,雖然沒有文檔提及(事實上,在 Visual Studio .NET 2003 的文檔中和 MSDN 的 Knowledge Base (KB) 中都有講到如何使用符號伺服器,KB 中的 Q319037 和 Q311503 分別講述了如何在 Visual Studio .NET 2002 和 Visual Studio 6.0 中使用符號伺服器,甚至還包括了一個詳細無比的、傻瓜式的教學視頻。在 MSDN 中搜尋索引鍵“symsrv.dll”可以找到這部分內容——譯者注)。關於符號伺服器,可以參考 http://www.microsoft.com/ddk/debugging/symbols.asp。

方法 B:康庄大道——使用符號伺服器

首先,去 http://www.microsoft.com/ddk/debugging 下載調試工具。你需要安裝 Symsrv.dll 檔案,你可以將它拷貝到 devenv.exe 所在的目錄下,或者放到你的 System32 目錄下,以便 Visual Studio .NET 可以訪問到它。在複製了 Symsrv.DLL 檔案之後,你就可以安全地卸載調試工具了。你還需要建立一個本地目錄,在本例中,建立了一個本地目錄 C:/localstore。

在工程屬性對話方塊中的“調試”屬性頁面上,填寫“符號路徑”:

SRV*c:/localstore*http://msdl.microsoft.com/download/symbols

這個字串會告訴調試器使用符號伺服器來擷取符號檔案,並在本地建立一個符號伺服器,用來存放符號檔案。現在,當你在 minidump 工程中按下 F5 後,符號檔案將從微軟的網站上拷貝到本機伺服器。在第一次下載後,之後的載入速度就會快很多,因為符號檔案將從本機伺服器中直接載入,不再需要通過 Web 下載了。

在調試微軟之外的程式時,你應該將方法 A 和方法 B 結合起來。用方法 A 獲得系統組件的符號檔案(反了吧?好像應該用方法 B 吧……不過原文確實是“Use A to get the system components”——譯者注),然後附上你自己的符號檔案路徑,用分號將它們隔開,例如:

c:/drop/build/myapp;SRV*c:/localstore*http://msdl.microsoft.com/download/symbols

由於符號伺服器是 Visual Studio .NET 中的一個沒有文檔說明的特性,所以沒有錯誤報表。如果運算式錯誤或者 Symsrv.dll 檔案的位置不正確,符號檔案就不能被載入,只能在模組視窗中顯示“No symbols loaded”的錯誤資訊。你也可以使用符號伺服器儲存和下載二進位檔案,但是 MODPATH 運算式需要使用“symsrv*symsrv.dll*”而不是“SRV*”(MSDN 中對於“srv”的解釋是:“This is shorthand for symsrv*symsrv.dll.”——譯者注)。

注意:微軟的符號伺服器不包含二進位檔案,但是你自己建立的符號伺服器卻可以。

符號伺服器不僅僅是用來調試 minidumps 的,它提供了一種“線上”調試的方法。在使用符號伺服器之前,別忘了正確地配置“調試”屬性頁面上的“符號路徑”選項。

微軟是如何使用 Minidumps 的

微軟使用 minidumps 改進其程式的曆史已經有一年多了(本文發佈於 2002 年 3 月 7 日——譯者注)。Microsoft Internet Explorer 5.5 和 Microsoft Office XP 是第一批與新版的 Dr.Watson 同時發布的產品。這個新版的 Dr.Watson 可以在程式停止回應時捕獲程式中未處理的異常,建立一個 minidump,然後詢問使用者是否願意將資訊提交給微軟。

進一步改進

在伺服器端,可以根據發生崩潰的組件和崩潰點對 minidumps 進行分析和歸類,這樣就可以使產品組得到程式的崩潰頻率、以及某個崩潰情況的發生頻率,小組也可以得到崩潰時的 minidumps 以便日後分析。在某些情況下,如果崩潰的原因已經完全查明,則使用者可以被引導到一個網頁,這個頁面上有已知的、可以暫時避免這種崩潰的方法,或者一個解決該問題的補丁。在 Internet Explorer 5.5 和 Office XP 發布後,有很多其他產品組已經開始使用類似的技術收集崩潰資訊了。它同時也是 Windows XP 的一個標準部分。

本文中討論的例子主要是用來理解 minidumps 的,沒有涉及到如何從使用者機器上返回 dump 檔案(在 CodeProject 還有一篇文章是專門講這個處理過程的,也就是著名的 BT 用戶端軟體 BitComet 所使用的崩潰資訊報告程式 XCrashReport——譯者注)。在最簡單的情況下,可以提示使用者使用 e-mail 發送 minidump 檔案。但是要注意,這可能會帶來隱私方面的問題,因為使用者資料也許就儲存在於堆棧中,對於一個包含完整堆資訊的 minidump 來說,則一定會有使用者資料在其中,這一點要讓使用者清楚。微軟的 Data Collection Policy 中有一些額外的資訊用來說明 minidumps 所包含的所有詳細內容,它位於:http://watson.microsoft.com/dw/1033/dcp.asp。

另外,如果在一個 Windows 服務中遇到了未處理的異常,那麼為它建立 minidumps 將是另一個挑戰。你需要處理案頭訪問(例如,如果沒有人在使用控制台,那麼你就無法提示他們)以及安全上下文。

總結

Minidumps 是一種新技術,它使得程式在使用者的機器上崩潰後也可以進行事後調試。向已有的程式中加入代碼、使其在遇到未捕獲的異常時自動建立 minidumps 也是一件很容易的事。Visual Studio .NET 可以很容易地載入它們,從而重現崩潰現場、使得開發人員可以偵錯工具。符號伺服器可以很輕鬆地找到系統符號檔案,協助分析。

附:著作權及免責聲明

本文的原文(英文)著作權歸原作者 Andy Pennell 所有。原作者並沒有授權 Victor 進行本文的中文翻譯工作,所以本文並不是原文的中文版本。譯者 Victor 不對原文、譯文及譯文注釋中可能出現的任何錯誤以及可能由其帶來的任何損失(直接或間接)承擔任何法律責任,包括但不限於技術錯誤、翻譯錯誤、語法錯誤、拼字錯誤等。Victor 保留本文的著作權,但 Victor 允許其他個人或單位在不得到 Victor 本人書面同意的情況下進行非商業目的且僅限於非商業目的的轉載且僅限於在互連網上轉載,但相應的轉載說明中必須包括本文的原始出處、英文版作者名 Andy Pennell、譯者名 Victor 以及原文連結。任何轉載行為必須包含本著作權及免責聲明,否則因此而引起的一切糾紛由轉載者負責。

相關文章

  • 位於 CodeProject 上的原文(英文)
  • 微軟的 DLL 協助資料庫
  • 微軟的 Data Collection Policy
  • 下載示範代碼(連結到 CodeProject)
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.