怎樣分析Windows dump

freakish發表於2017-05-18

dump檔案從哪裡來?

  • 專案工程
    • 一般的專案都會有類似xxxProtect 這樣的工程,專門負責專案crash檔案的蒐集,上傳操作。其主要原理就是利用Windows API MiniDumpWriteDump 來生成dump檔案, 觸發dump抓取時機就是程式發生異常的時候, 當然也可以在任意一個執行緒中的任意時刻使用這個API進行抓取
  • 專業抓取工具
    • Windows自帶的任務管理就可以抓取,開啟資源管理器,在任意一個程式上右鍵 - 建立轉儲檔案即可
    • 其他類似專業工具

dump檔案可以解決那些問題?

  • 程式Crash
    • 當程式需執行發生異常的時候,如果我們的蒐集機制正確的完成dump檔案蒐集並上傳到伺服器,那麼我們就可以從伺服器獲取到這個dump檔案, 這個dump檔案記錄了程式發生異常的時候程式執行記憶體現場。因此透過分析這個執行上下文的快照,我們可以嘗試查詢問題發生的原因。
    • 有的時候,你會發現,使用者反饋說程式莫名的就退出了,完全沒有任何徵兆,也沒有彈出你們的dump檔案蒐集程式,這個時候只有一種原因,那就是程式發生了你們目前的蒐集機制無法捕獲的異常,這個時候應該怎麼辦呢?通常大家可以選擇兩個方案來嘗試縮小問題排查範圍:
      1. 透過客戶端日誌的最後一條訊息輸出來確定範圍
      2. 如果使用者肯配合,則可以透過給使用者的機器上傳Windbg這個輕量級的偵錯程式,讓使用者在偵錯程式下面執行,這樣當使用者發生異常的時候,就會被偵錯程式捕獲到,可以透過Windbg的dump生成命令生成dump檔案來進一步進行問題排查, 參考Windbg dump命令:.dump /ma C:\dumps\myapp.dmp,其中/ma選項說明要生成內容詳細的問題現場,但是生成的檔案可能比較大。如果選擇 /m選項,可能生成檔案比較小,但是提供的資訊就相對較少了,大家可以自己權衡。
  • 程式執行異常(如:介面卡死)
    • 在專案上線之後,可能我們都會遇到這樣一種場景,就是程式沒有發生崩潰,但是UI介面卻沒有響應了,這個時候你急切需要的就是要知道到底UI執行緒在幹什麼,為什麼不響應我的操作了。你需要的第一手資料就是當前執行程式的記憶體dump檔案,同樣你需要先採用前面介紹的方法來進行記憶體抓取(資源管理器或者專業工具)
    • 有了記憶體執行快照dump檔案,就可以開始根據經驗來判斷你首先要分析的是哪些物件了。就說介面卡死這種問題, 我們就是要看到底UI在這個時候在幹什麼這麼忙,導致連我們的鍵盤滑鼠這樣的高優先順序的操作都不響應了,比如我們會看當前UI執行緒的堆疊,看看最後是在做繁忙的任務,還是乾脆發生了死鎖。

分析dump之前,你需要哪些準備?

  • 工具
    • 自古是工欲善其事必先利其器, 所以要想從dump檔案中獲得重要問題資訊,沒有好的工具顯然不行,我推薦 Windbg 和 VisualStudioxxx
    • Windbg
      • 這個工具其實不用多說了,微軟親兒子,Windows環境下的各種問題分析利器,dump分析其實只是他的一小部分功能,更多強大的功能大家可以自行百度,我們單說他的dump分析功能:
        1. 支援Windows系統中系統模組符號自動匹配下載(尤其問題發生在系統模組的時候很有幫助)
        2. 輕量級,整個工具包很小,方便傳輸攜帶,無論本地使用還是上傳到使用者環境似乎都是首選
        3. 支援pdb符號時間戳模糊匹配,這個很重要,尤其是無法找到當時打包的時候生成的pdb
        4. 配套的分析命令和外掛,對分析問題很有幫助
    • Visual Studio
      • 宇宙第一IDE不是浪得虛名,提供了強大的程式碼編輯環境之外,同樣提供了強大的除錯支援,這裡dump分析也屬於她除錯功能的一部分
  • 符號檔案
    • 這個檔案太重要了,對於分析問題能提供極大的幫助,那麼問題來了:
      • pdb檔案從哪裡來? 在專案編譯打包的時候,編譯器會根據你的工程配置,在編譯輸出目錄生成 .pdb檔案,理論上每個獨立的模組(.dll .exe)都會有一個對應名稱的pdb檔案,比如你的工程主程式是main.exe, 那麼你應該會有main.pdb這麼個檔案,如圖1:
      • pdb檔案作用體現在哪裡? 我覺得還是直接上圖最能說明問題,如圖2、3的對比起來就很清楚了(有符號能提供更多的崩潰現場堆疊呼叫資訊):

常見dump場景分析例項

  • 例項場景一:程式崩潰分析
    • 這個例子我們選用Windbg來分析, 這裡我假設已經獲得了某個崩潰現場的檔案,名稱為A.dmp, 按如下步驟操作:
      1. 開啟Windbg工具,選擇選單 File ->Open Crash Dump, 選擇你的dump檔案並開啟,正常開啟完畢,應該是這樣的介面:
      2. 設定pdb符號, 選擇選單 File -> Symbol File Path, 在彈出的設定框中,填入你本地儲存的pdb的路徑,為了Windbg能夠自動載入系統模組的符號,建議在你的pdb的路徑之後新增分號,然後加入微軟的系統模組符號路徑,參考設定結果是這樣的: d:\my_project\bin\release\pdb;SRV*c:\mysymbol* http://msdl.microsoft.com/download/symbols,前半段是你的工程的pdb目錄,後半段是微軟模組符號路徑
      3. 如果分析的機器上有這個崩潰程式版本對應的原始碼,那就更好了,你可以繼續設定一下原始碼路徑,設定原始碼路徑有什麼好處呢?你會很開心的發現,當你透過命令讓Windbg分析crash現場的時候,Windbg能夠自動化的給你定位到發生問題的那行原始碼上, 但是請注意一個問題:如果你的原始碼並不是當初編譯這個程式的那份原始碼,Windgbg給你的結果就會迷惑你。舉例來說,如果你有一個原始碼檔案,有10行程式碼,這個原始碼在2017-06-01已經編譯過程式A,並且已經發布上線了,然後你在2017-06-02這一天在這個原始碼的第2行前面增加了幾行程式碼, 我們假定程式A在第5行發生了Crash, Windbg的自動分析就會給你自動定位到第5行, 但是其實這個時候因為你修改了原始碼,那麼這個時候的第5行已經不是當初的第5行了,Windbg就會迷惑你了, 這同時也顯示出了程式碼版本管理的重要性了。
    • 經過前面的幾部操作,我們已經做好了最基本的準備,下面可以嘗試一下Windbg的自動化分析了,為了保障符號的正常載入,我們首選讓Windbg進入符號模糊匹配的模式,意思就是告訴Windbg我設定的這個pdb的路徑是對的,如果pdb的時間戳對不上,你也給我強行載入吧,然後開始進入經典的三步(注意:如果左下角顯示 BUSY, 就要等它一會兒,執行完這一步再進入下一步的命令):
      1. 進入符號模糊匹配模式:!sym noisy on,
      2. 載入pdb:.reload /i
      3. 自動化分析:!analyze -v
    • 如果你不清楚在哪裡敲入命令,看我的圖4(命令輸入框位置),圖5(你應該得到的結果):
    • 結果有了, 這個結果怎麼看呢? 上面一幅圖說過了,很簡單,箭頭的地方就是輸入命令的地方,不說了。看下面的結果分析部分, 圖中標出了 第一處 第二處 第三處三個地方:
      1. 第一處, 代表當前崩潰的最後一層函式呼叫是什麼地方
      2. 第二處代表,根據當前設定的符號和原始碼路徑,是崩潰在這一行了
      3. 第三處這裡仔細看有一個 > 符號, 就是一個指示箭頭,標識當前崩潰的行是在哪一行
    • 工具的幫助有了,下面就是開始依賴個人程式設計經驗來分析了,首先看這一句 m_testPtr->SetEnable(false); 這樣一句話的發生了崩潰,只有2中可能, 第一種:m_testPtr = NULL(或野指標); 第二種:m_testPtr所處的環境是一個異常環境,根據這裡的實際情況就是, ClientLoginDialog物件的this指標是空(或野指標), 這個時候經過初步的判斷,我們就可以選擇不同的方法來排查了:
      1. 第一種就是直接看程式碼,查詢m_testPtr是否可能為NULL, 或者 ClientLoginDialog物件的this是否可能為空或者是野指標
      2. 用Windbg繼續排查, 這個時候繼續輸入一波命令:
        • 切換到異常執行緒:.ecxr
        • 列印異常堆疊:kvn
        • 這個時候,你應該進入這樣的節奏了,看圖:
        • 還是進入到了剛才的問題現場,這個時候我們輸入 r 命令,接著輸入u命令(目的是為了多列印一些崩潰點程式碼上下文),看崩潰現場詳細資訊:
        • 這個時候需要一點點彙編知識了,不然的話,你只有第一步中的那種方法來查程式碼了,我們直接看第二個箭頭,
        • 00402fbc 8b01 mov eax,dword ptr [ecx] ds:002b:00000000=????????,觀察第一個箭頭 ECX = 0, 大家都知道 0地址記憶體是不能訪問的,那到底是什麼地方為0了呢, 接著看下面幾句程式碼,從第二個紅箭頭的地方看到了Call, 這個是函式呼叫指令, 根據前面Windbg定位的 m_testPtr->SetEnable(false); 這句,我們知道肯定是在呼叫SetEnable這個函式, 而函式的地址來自 [eax+0ECh], 則只要EAX有效就行, 而EAX的值正是需要 崩潰的那一句 mov eax,dword ptr [ecx] ds:002b:00000000=????????來賦值, 所以這裡我們可以知道, 這裡的ECX 就是程式碼中的 m_testPtr 這個變數(這個因為所以的由來技術細節又來源於C++記憶體物件模型的知識,需要百度自行補充一下), 由此我們確定就是 m_testPtr 這個指標為空導致的問題,定位到這裡,沒有更多的路子走了,只能死看程式碼 + log來找問題了,Windbg也盡責完畢了。

光靠dump分析足夠嗎?

  • 從崩潰分析的部分可以知道, dump分析能告訴你是什麼地方出了問題, 但是最後問題發生的具體邏輯原因是找不到的, 你需要繼續看程式碼,然而經常看程式碼經常是很難找到問題, 所以你需要在開發的時候保持良好的log習慣,這個是關鍵時刻的救命草,好的log可以直接排查出來大多數問題,甚至都不用dump分析。
  • 所以,需要再強調一下的是,要有相對完善的log

相關文章