我是這么學(xué)習(xí) WinDBG 的

WinDBG 的命令簡(難)潔(懂),多(難)樣(記),功(反)能(人)強(類)。許多同學(xué)都感覺它功能強大但是不容易上手。今天我介紹另一種方式希望對大家有所幫助。

WinDBG 是一個調(diào)試器,那么什么是調(diào)試呢?我的理解,調(diào)試可以認(rèn)為是確認(rèn)應(yīng)用程序在此刻的狀態(tài)正確與否。如果狀態(tài)正確,那么一切正常,如果狀態(tài)錯誤,那么說明應(yīng)用程序的執(zhí)行出現(xiàn)了問題。而應(yīng)用程序的狀態(tài)是什么呢?其實就是應(yīng)用程序在內(nèi)存中的表示。那么,如果我們能夠理解應(yīng)用程序在內(nèi)存中的構(gòu)成,那么我們就可以找到各種各樣的狀態(tài),從而判斷程序運行的正確性了。

因此,WinDBG 的命令是什么并不重要,重要的是你知道如何用這些命令探索應(yīng)用程序各種狀態(tài)之間的聯(lián)系。下面我們就先簡要的看一看應(yīng)用程序的狀態(tài)是什么樣子的(我以 CLR 的應(yīng)用程序為例)。

應(yīng)用程序在內(nèi)存中是這樣的

這篇文章的關(guān)鍵在于思路而非包羅萬象的介紹應(yīng)用程序在內(nèi)存中的表示。如果要深入探索,還需要多下下功夫。

應(yīng)用程序的最上層結(jié)構(gòu)是進(jìn)程,進(jìn)程中包含了正在執(zhí)行的若干線程。

  • 一個線程實際上包含線程的上下文(例如一些安全上下文,當(dāng)前線程的線程內(nèi)存儲結(jié)構(gòu)等等),還有當(dāng)前線程的函數(shù)棧。
  • 函數(shù)棧包含若干的調(diào)用棧幀。每一個調(diào)用棧幀實際上代表了一個函數(shù)調(diào)用。包含調(diào)用的地址,本地棧變量,參數(shù),以及引用的堆上的變量。
  • 變量所引用的對象在內(nèi)存中也是有一定的結(jié)構(gòu)的。變量實際上就是一個托管的指針。
  • 而對象包含對象頭部信息(同步信息塊、運行時類型信息),和對象本身的數(shù)據(jù)(成員變量)
  • 運行時類型信息中又包含 MethodTable 信息。
  • 而對象所在的堆可能是第 0 代托管堆、第 1 代托管堆和第 2 代托管堆。還有可能在 LOH 上(大對象堆)。

如果我們畫一張圖,那么這張圖就類似這樣:

隨便一整的應(yīng)用程序的運行時結(jié)構(gòu)

調(diào)試就是一次旅游

那么調(diào)試就是從這張圖的任意一個點進(jìn)入,并且通過應(yīng)用程序運行時狀態(tài)的各個部分的關(guān)系,通過圖中的線進(jìn)行任意的游走,確定每一個部分是否正確的過程。

通過上述分析,那么我們只要解決兩個事情就可以了:

  1. 找一個入口點
  2. 在狀態(tài)的各個部分之間游走

我們不妨來嘗試一下。

找到一個入口點

現(xiàn)在請查一下 winDBG 的命令手冊,你可以從網(wǎng)上搜到各種各樣的資料。例如如下幾個:

  • 用如下的命令可以找到 Thread(線程)相關(guān)的信息:~、!runaway、!threads、~$thread id$s、!threadpool
  • 用如下的命令可以找到托管堆上存儲的變量的信息:!dumpheap、!pe
  • 用如下的命令可以找到特定類型的 MethodTable 信息:!name2ee
  • 在使用 sosex 擴展的情況下,可以用如下的命令獲得托管堆存儲的對象信息:!gcgen、!dumpgen
  • 使用如下的命令可以直接獲得對象的同步對象的信息:!synblk

在狀態(tài)的各個部分之間游走

現(xiàn)在我們有一個切入點了,那么我們就可以通過各個部分之間的關(guān)系將應(yīng)用程序執(zhí)行涉及到的信息一個不剩的找出來。例如:

  • 如果想從線程的信息查看線程的調(diào)用棧的信息,可以先使用 ~$thread id$s 切換到線程的上下文,然后使用 !clrstack 命令查看調(diào)用棧信息。
  • 如果想從調(diào)用棧查找某一個棧幀的局部變量和參數(shù)的話可以分別使用:!clrstack -l!clrstack -p
  • 如果想從變量的地址查看變量的存儲結(jié)構(gòu)那么可以使用 !dumpobj 或者 !dumparray 命令。
  • 如果有一個變量的地址但是想知道變量被那一個調(diào)用棧引用可以使用 !gcroot 命令,再通過 !clrstack 確認(rèn)特定的棧幀。

旅游總結(jié)

綜合前兩個小標(biāo)題的內(nèi)容我們可以制定如下的旅游攻略:

使用 winDBG 命令在應(yīng)用程序各部分間游走

此圖不全,僅僅是為了拋磚,希望各位能夠自己總結(jié),形成自己的知識體系。

旅游也有套路

應(yīng)用程序的內(nèi)部結(jié)構(gòu)還是有些復(fù)雜的。為了節(jié)省時間,大家都會總結(jié)各種套路。這就需要大家整理思路了。

例如,對于某次高的 CPU 占用。我選擇先從線程入手。首先使用 !runaway 命令查看線程的 CPU 時間。發(fā)現(xiàn) 20 多個線程沒完沒了的執(zhí)行了若干小時。而后通過 !clrstack 查看線程的調(diào)用棧發(fā)現(xiàn)這些線程的調(diào)用棧都在調(diào)用一個叫做 RenameXxx 的函數(shù)。

其實應(yīng)用程序很少會出現(xiàn)巧合,因此懷疑這個函數(shù)有問題。使用 !clrstack -p 查看了一下函數(shù)的參數(shù)和局部變量,發(fā)現(xiàn)一個參數(shù)含有一個字符串變量,其長度已達(dá) 1MB。使用 !dumpobj 發(fā)現(xiàn)其內(nèi)容為:fileName (1) (1) (1) (1) ... 至此基本斷定這個函數(shù)里有死循環(huán)的邏輯了。

網(wǎng)上還有很多既有的套路。例如 Tess 的博客。強烈建議各位讀者也用自己的博客、小本子、Evernote、OneNote 等各種媒介記錄自己的旅游套路。不知不覺你就會成為 winDBG 調(diào)試的老司機。

其實,這篇博客不光是為 winDBG 寫的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容