標籤:
前言
這篇文章我們一起來分析一個從Windows Phone Dev Center上下載下來的dump file。首先按照我上一篇的步驟設定好我們的Windbg,並按住Ctrl +D開啟dumpfile。可以看到下面的介面:
分析一個dump file可以分解為4個步驟,第一步是資訊收集,第二步是定位異常上下文,第三步分析和推理出現問題的原因,第四步分析和定位我們的原始碼並進行修複和驗證。 資訊收集
我們可以使用一些命令瀏覽一下這個dump file對應的系統版本和一些模組的資訊輔助我們後面的分析。
1. version命令,查看系統版本號碼
2. lm命令,顯示當前載入的模組。
可以使用lmv命令查看所有模組的詳細資料,如果想看某一模組的詳細資料,需要使用參數m,比如查看System_Data_Linq_ni.dll的詳細資料,”lmv mSystem_Data_Linq_ni”
定位異常上下文
當異常發生的時候,寄存器的上下文會被異常分發器儲存在棧上。我們可以通過一些方法找到並恢複發生異常時候的上下文,從上下文中找到我們需要的資訊,這裡使用”!analyze -v”命令。
1. 首先確認發生異常的線程。有時候發生異常的線程不止一個,我們在使用 “!analyze –v”之前需要確認發生異常的線程。使用”~* kvb”命令來查看所有線程的呼叫堆疊。”~*“ 命令是枚舉所有的線程,”kvb“命令是列出線程的呼叫堆疊。
可以發現所有的線程都是等待狀態,只有線程0不是,從線程0的callstack可以看出來線程0就是我們要找的發生異常的那個線程,。
2. 切換到發生異常的線程,”~0 s”
3. 使用“!analyze –v”,這個擴充命令來協助我們找到發生異常時候的上下文,並顯示當時的呼叫堆疊,有些情況下給出的呼叫堆疊並不是發生異常的第一現場,遇到這種情況我們需要進一步分析。
分析和推測
在上面的顯示中,我們發現一個很有意思的託管呼叫堆疊,裡面反覆出現了”System.Diagnostics.StackTrace..ctor()+0x12”。看起來StackTrack這個類型的對象在構造的時候調用了自己的成員函數GetStackFramesInternal,而這個函數又去構造了新的StackTrack的對象,如此反覆以至於發生了迴圈調用而導致棧被耗盡,這裡並沒有給出與我們的代碼相關的調用,看起來很像一個.net framework的bug。那麼為什麼會發生這樣的調用呢?讓我們繼續進行分析,看看是哪裡引發了這個調用。
為了找到更多的線索,我們可以進一步查看發生異常的線程棧裡都保留了什麼,我們可以通過”!teb”命令來查看當前線程的屬性,並找到棧的基址和大小,有了棧的基址和大小,我們就可以查看裡面的內容了。
1. 查看當前線程的屬性,”!teb”命令
2. ”dps + 位址範圍”命令可以讓我們查看棧裡面保留的資訊。
跳過這些無效的內容,我們繼續往後查看。
紅線的模組和函數正是我們App中的代碼,我們可以做一個大膽的推理在這裡。我們的函數DecrementPendingAndFinishIfNecessary調用了Logger.Info函數,這個函數使用了系統的StackTrace.CaptureStackTrace來擷取當前的呼叫堆疊。那麼為什麼這個函數StackTrace.CaptureStackTrace又會去構造它自己的對象呢?讓我們開啟我們程式的原始碼進一步分析。 分析和定位我們的原始碼
開啟我們的代碼並找到Logger.Info的實現,紅色的代碼正是驗證了我們上面的推理。在一些極端的形況下,StackTrace會建立失敗並扔出異常,這個異常恰好被後面的catch塊捕獲再次調用了Logger的函數,而這個函數會再次建立StackTrace類型的對象,繼續觸發異常導致了反覆的調用。
private static void WriteLine(Level level, string message){ try { if (0 == message.Length) { return; } StackTrace st = new StackTrace(); // 1. 這裡exception string name = st.GetFrame(2).GetMethod().Name; string prefix = string.Format("[{0}]@{1}", level, name); message = prefix + "-" + message; } catch (Exception e) { Logger.Fatal("Faild in WriteLog,message:" + e.Message); // 2. 然後執行這裡 }}public static void Fatal(string message){ WriteLine(Level.Fatal, message); //3. 這裡繼續執行1, 1 繼續exception}
知道了原因,我們就可以修改代碼來修複了,最簡單的方法就是先去掉catch裡面的調用。
後續問題
那麼為什麼系統的StackTrack的GetStackFramesInternal會失敗呢?感興趣的同學可以嘗試反編譯命令來查看裡面的細節。
分享代碼,改變世界!
Windows Phone App的dump檔案執行個體分析-Stack Overflow