1.背景介紹隨著應(yīng)用程序的復(fù)雜度不斷上升,要想將好的設(shè)計思想穩(wěn)定的落實(shí)到線上,我們需要具備解決問題的能力。需要具備對運(yùn)行時的錯誤進(jìn)行定位且快速的解決它的能力。本篇文章我將分享一下我對.NET應(yīng)用程序調(diào)試方面的學(xué)習(xí)和使用總結(jié)。 其實(shí)對調(diào)試程序的使用是不難的,關(guān)鍵是知道它的調(diào)試原理才行,因?yàn)檎{(diào)試一個程序或者dump文件,都需要了解一定的.NET調(diào)試的原理才行,比如你在附加到進(jìn)程調(diào)試時在執(zhí)行某個SOS擴(kuò)展命令是需要切換到指定線程上的,而調(diào)試dump文件就不需要,但是對Dump文件的分析有些SOS擴(kuò)展命令是不能用的,類似這樣的問題,一旦出現(xiàn)你就一頭霧水,所以花點(diǎn)時間學(xué)習(xí)一下原理是有必要的。 2.基本原理(Windows調(diào)試工具箱、.NET調(diào)試擴(kuò)展SOS.DLL、SOSEX.DLL)在Windows平臺上調(diào)試應(yīng)用程序首選Windows調(diào)試工具箱,該工具箱包含了一套專門用來針對Windows進(jìn)行很多復(fù)雜場景調(diào)試所需要的工具和組件。需要注意的是此工具箱是針對于非托管.NET平臺用的,意思就是說此工具箱的所有工具和組件默認(rèn)是不能夠進(jìn)行.NET應(yīng)用程序調(diào)試的,只能用來對原生Windows程序進(jìn)行調(diào)試。 那么.NET平臺也并不是有自己一套專用的調(diào)試工具箱,畢竟.NET還是屬于Windows平臺的,所以很大部分的運(yùn)行時原理還是基于Windows的,要想在原生的調(diào)試器中對.NET這個具有虛擬運(yùn)行時程序進(jìn)行調(diào)試就需要專門的翻譯器才能夠執(zhí)行。SOS.DLL、SOSEX.DLL這兩個就是用來對.NET程序在Windows調(diào)試工具中起到翻譯作用的調(diào)試器擴(kuò)展。簡單講就是,這兩個組件是.NET項(xiàng)目組專門開發(fā)出來用來對.NET應(yīng)用程序進(jìn)行方便調(diào)試用的,當(dāng)然不用這兩個擴(kuò)展也能調(diào)試.NET程序,只不過就會很困難,會被很多細(xì)節(jié)束縛住。有了這個調(diào)試擴(kuò)展之后,我們就可以讓原生Windows調(diào)試器正確的翻譯出.NET相關(guān)概念。 圖1:(Windows調(diào)試工具執(zhí)行流程) 所有對.NET程序發(fā)起的調(diào)試會話都要經(jīng)過.NET調(diào)試擴(kuò)展組件進(jìn)行翻譯才行,也就是要使用.NET調(diào)試擴(kuò)展的調(diào)試命令來調(diào)試.NET程序。上圖中,我們?nèi)绻胝{(diào)試.NET程序就需要將.NET調(diào)試擴(kuò)展組件加載到Windows調(diào)試工具中去,然后才能方便在Windows調(diào)試工具中使用。 2.1.Windows調(diào)試工具箱Windows調(diào)試工具箱中包含了很多調(diào)試工具,都是用來輔助于我們進(jìn)行方便調(diào)試用的。Windows調(diào)試工具箱分為兩個執(zhí)行版本,X86、X64這兩個版本是專門用來分析不同的運(yùn)行時環(huán)境的,如果你的分析環(huán)境是32位的你就需要使用X86的版本,同理,如果是用64位的環(huán)境就需要使用X64的版本。 下載地址為:http://www.microsoft.com/whdc/devtools/debugging/default.aspx 記住選擇你需要的版本,建議你兩個版本都下載,因?yàn)槟汶S時需要針對Dump文件進(jìn)行分析,而Dump文件是隨時都有可能是兩個版本。 Windows工具箱中的默認(rèn)使用WinDbg.exe作為調(diào)試首選,它是一個GUI程序。 圖2:(默認(rèn)的Windows調(diào)試工具,WinDbg) 安裝過后的菜單中就只有WinDbg作為調(diào)試選擇。 這里需要注意的是,當(dāng)你啟動了WinDbg之后要留意程序的名字和標(biāo)題,因?yàn)楫?dāng)你存在兩個版本的WinDbg時會容易搞錯,在調(diào)試時會有各種奇怪的問題出現(xiàn),當(dāng)你找了半天之后結(jié)果發(fā)現(xiàn)是因?yàn)橛缅e了版本,那就正的無語了。 圖3:(注意運(yùn)行WinDbg的環(huán)境版本) WinDbg是默認(rèn)的調(diào)試工具,但是在工具箱中還有幾個控制臺調(diào)試工具,他們行必之下比較輕量簡單,有些任務(wù)比較好執(zhí)行,在配合cmd使用會很方便,比如工具箱中的tlist.exe用來查看進(jìn)程信息的小工具就非常方便。 圖4:(方便查看進(jìn)程ID) 這樣我們就可以很方便的attach到一個指定的進(jìn)程進(jìn)行調(diào)試。 Windows調(diào)試工具箱中有很多其他的工具,需要用的話可以使用cmd切換到當(dāng)前安裝的目錄下:C:Program FilesDebugging Tools for Windows (x86),或者你直接到工具的安裝目錄運(yùn)行也行,這就看此工具是不是支持手動無參數(shù)啟動了。 2.2..NET調(diào)試擴(kuò)展包,SOS.DLL、SOSEX.DLL.NET調(diào)試擴(kuò)展包分為兩個,一個是SOS.DLL,該擴(kuò)展包是.NET平臺的一部分,屬于官方版本。而SOSEX.DLL是微軟的一名叫“Steve Johnson”軟件工程師開發(fā),屬于個人維護(hù)的,用來增強(qiáng)SOS.DLL功能的,在SOSEX.DLL有很多功能比較強(qiáng)大的擴(kuò)展命令。 下載地址為: 32位:http://www./downloads/sosex_32.zip 64位:http://www./downloads/sosex_64.zip 具體的幫助文檔可以查看該工程師的博客來了解詳情。這兩個版本用來調(diào)試不同環(huán)境的程序的,如果你的程序是運(yùn)行在32位環(huán)境下,就用32位的SOSEX,同理,用在64位下就用64位SOSEX。 而SOS.DLL擴(kuò)展包是跟著.NETFramework一起安裝的,地址位于:C:WindowsMicrosoft.NETFrameworkv4.0.30319。如果你是64位系統(tǒng)的話地址就是: C:WindowsMicrosoft.NETFramework64v4.0.30319。在這兩個地址下面都可以找到SOS.dll文件,不同的目錄下對應(yīng)于調(diào)試不同機(jī)器類型的.NET程序。 有了這兩個擴(kuò)展包之后就可以在WinDbg中對.NET程序進(jìn)行分析了,具體使用我們后面會介紹。 2.3.調(diào)試系統(tǒng)的基本流程及架構(gòu)(.NETDAC概念、mscordacwks.dll)有一個很重要的原理我覺得很有必要講一下,就是.NETDAC概念。 其實(shí).NETDAC也就是.NET Data Access .NET數(shù)據(jù)訪問層,這個是專門用來提供給SOS.DLLSOSEXDLL或者其他調(diào)試擴(kuò)展包使用的,所有的調(diào)試擴(kuò)展組件必須通過這個DAC才能訪問到.NET運(yùn)行時的數(shù)據(jù),所以在初次使用SOS的時候會經(jīng)常碰見加載錯誤的mscordacwks.dll文件,此文件就是DAC的物理文件。 這個文件和SOS擴(kuò)展文件一樣,都有這不同的版本,當(dāng)加載不同類型的.NET程序時會使用到不同版本的mscordacwks.dll文件,當(dāng)然大部分情況下此文件時自動加載的,只有出現(xiàn)你分析的文件與生成調(diào)試文件的環(huán)境不一致時才會出現(xiàn)頭疼的問題。 圖5:(mscordacwks.dll位置) 當(dāng)你知道這個組件是工作于此位置時,當(dāng)出現(xiàn)跟它相關(guān)的錯誤提示時你就不需要擔(dān)心了,無非就是文件加載的位置或者版本不匹配而已。 調(diào)試器會話、調(diào)試器注入線程 還有一點(diǎn)我覺得也很有必要介紹的就是有關(guān)調(diào)試器如何調(diào)試.NET程序的,當(dāng)我們在使用調(diào)試器啟動被調(diào)試程序或者將調(diào)試器附加到被調(diào)試進(jìn)程時,其實(shí)調(diào)試器會注入一些線程到.NET程序中,讓調(diào)試線程與.NET程序原本的線程在一個.NET執(zhí)行環(huán)境中,這樣的目的是能夠起到最.NET程序在執(zhí)行時的控制,比如中斷執(zhí)行,設(shè)置斷點(diǎn)。當(dāng)我們需要執(zhí)行某些跟線程上下文相關(guān)的擴(kuò)展命令時就需要切換到正確的線程上去。 圖6:(調(diào)試器注入線程) 此時,調(diào)試器使用一個注入線程將.NET程序在執(zhí)行時中斷,原理就是通過發(fā)送線程中斷命令來達(dá)到控制目標(biāo)線程,那么首先要能夠與原線程通訊才行,所以需要注入托管線程。(注意:注入的線程不一定就是托管.NET線程,嚴(yán)重它最好的方法就是查看所有所有的進(jìn)程內(nèi)線程和所有托管線程,對比一下就知道了。),其實(shí)這個ID為3的線程是調(diào)試器會話線程。 圖7:(切換到原托管線程) 我們通過~0s命令切換到我們需要調(diào)試的原托管線程中,比如,在執(zhí)行!ClrStack命令時,就需要切換到當(dāng)前線程上執(zhí)行。 我們需要驗(yàn)證它是否是注入了托管線程還是非托管線程。 圖8:(托管線程列表) 使用!Threads命令可以查看進(jìn)程內(nèi)所有的托管線程,僅僅是托管線程,此命令是無法查看非托管線程的,接下來我們使用另外一個命令來查看所有的線程。 圖9:(所有的執(zhí)行時線程) 這樣我們就可以判斷出,調(diào)試器使用了ID位7的作為目前的調(diào)試會話線程。知道這些背后的原理很重要,當(dāng)你在執(zhí)行某個調(diào)試命令時你就會發(fā)現(xiàn)此命令是否需要在.NET線程中執(zhí)行,還是說可以在調(diào)試器會話線程中執(zhí)行,一般dump類的命令都是可以遠(yuǎn)程執(zhí)行的,也就是說在調(diào)試器會話中執(zhí)行,當(dāng)需要跟蹤.NET線程內(nèi)部過程時就需要切換到.NET線程上去執(zhí)行。 2.4.VisualStudio中集成擴(kuò)展調(diào)試(更加細(xì)粒度的調(diào)試程序)SOS擴(kuò)展也是可以和VisualStudio進(jìn)行集成的,這樣真的方便了我們調(diào)試一些性能要求比較高的程序,當(dāng)程序運(yùn)行一段時間后我們用VS附加到進(jìn)程,然后查看一些重要的對象數(shù)據(jù),但是此時我們看不到.NET運(yùn)行時的一些數(shù)據(jù),比如:對象的代齡,托管堆的大小,線程池的任務(wù)等。通過集成SOS擴(kuò)展會讓我們對程序的運(yùn)行時有了一個更加方便的跟蹤。 圖10:(打開本地代碼調(diào)試) 設(shè)置斷點(diǎn),然后在”即時窗口“(調(diào)試->窗口->即時)中加載擴(kuò)展SOS.DLL。 圖11:(在VisualStudio2012中加載SOS.dll擴(kuò)展)
這樣的便利性大大提高我們在調(diào)試程序內(nèi)存方面、線程方面的好處,我們可以適當(dāng)?shù)淖鰤毫y試,然后Attach process,執(zhí)行SOS擴(kuò)展命名來查看內(nèi)存問題,當(dāng)需要調(diào)試程序邏輯時在單步調(diào)式C#代碼,一舉兩得。 3.調(diào)試程序類型(客戶端程序、服務(wù)端程序).NET程序主要分為兩類,一類是客戶端程序,另一類是服務(wù)端程序。對于這兩類程序來說前者調(diào)試時基本上可以通過附加進(jìn)程的方式進(jìn)行調(diào)試,而對于服務(wù)端程序則不行,因?yàn)榉?wù)程序通常是運(yùn)行在一個復(fù)雜的線上環(huán)境中,我們沒有任何權(quán)限或機(jī)會去接觸,此時是通過獲取進(jìn)程的dump文件來進(jìn)行分析。 客戶端程序也大概分為控制臺、Winform兩種,服務(wù)端程序都是基于ASP.NET框架,宿主與IIS進(jìn)程中。 4.調(diào)試方式及場景針對不同類型的程序及場景需要使用不同的方式進(jìn)行調(diào)試,客戶端程序中的控制臺程序基本上可以通過在調(diào)試器中啟動的方式進(jìn)行調(diào)試。如果是GUI程序則需要附加進(jìn)程方式。服務(wù)端程序如果在條件允許下也是可以使用附加進(jìn)程的方式進(jìn)行調(diào)試的,但是這一般不太可能,因?yàn)橐坏└郊舆M(jìn)程將block住所有的線程活動。 4.1.本機(jī)調(diào)試(Attach Process,調(diào)試器啟動)本機(jī)調(diào)試可以直接在調(diào)試器中啟動程序,WinDbg打開后,在文件中有一個Open Executable,可以打開一個可執(zhí)行文件。如果是使用NTSD控制臺調(diào)試器,則需要在NTSD后面跟上程序的執(zhí)行路徑。 圖12:(ntsd.exe打開調(diào)試程序)
同樣,在WinDbg中也有一個附加進(jìn)程的選項(xiàng),NTSD也是一樣,操作起來都比較簡單,需要注意的是當(dāng)你對進(jìn)程進(jìn)行附加時要清楚此進(jìn)程是多少位的,然后你需要選擇正確的調(diào)試器進(jìn)行調(diào)試。 4.2.不中斷調(diào)試或者稱事后調(diào)試(對Dump文件進(jìn)行調(diào)試)在不能夠?qū)Ρ徽{(diào)試程序直接調(diào)試時我們就需要此程序的進(jìn)程鏡像文件,此鏡像文件就是進(jìn)程在某一個時刻的快照,通過分析這個快照,我們也是可以定位出問題的。首先我們需要使用適當(dāng)?shù)墓ぞ邅慝@取進(jìn)程的dump文件,操作系統(tǒng)本身的任務(wù)管理器就有這個功能,dump文件的存放位置默認(rèn)在用戶信息臨時文件下面,比如:XXXUsersAdministratorAppDataLocalTemp,獲取完dump文件后任務(wù)管理器會有提示路徑的。 圖13:(使用任務(wù)管理器獲取dump文件)
圖14:
使用任務(wù)管理器獲取dump文件固然很方便,但是有一個問題就是如果當(dāng)前機(jī)器是64位的,并且你的進(jìn)程是以32位方式運(yùn)行的,那么此時你獲取出來的dump文件是64位的,當(dāng)你通過32位的調(diào)試器無法進(jìn)行分析,甚至?xí)懈鞣N其他的問題,這些問題就是因?yàn)楂@取dump文件的機(jī)器環(huán)境和你預(yù)想的不一致。這個時候我們希望能夠通過很明了的方式來獲取dump文件,就是通過調(diào)試器來獲取dump文件。 通過調(diào)試器來獲取dump文件有很多好處,可以設(shè)置很多選項(xiàng),包括只獲取進(jìn)程的哪部分鏡像數(shù)據(jù)等。 先通過tlist.exe查看所有進(jìn)程列表,會有一個進(jìn)程ID號,有了ID號才能進(jìn)行獲取。 圖15:(tlist、ntsd 進(jìn)入到指定進(jìn)程中)
進(jìn)入到ntsd調(diào)試器中,然后使用.dump/mf d:order.dmp 命令獲取dump文件到D盤。 圖16:(使用NTSD.exe獲取dump文件)
此時我們就成功的獲取到了dump文件。 通過調(diào)試器獲取dump文件比較穩(wěn)定可靠,因?yàn)闄C(jī)器運(yùn)行環(huán)境的不同,通過任務(wù)管理器獲取的dump文件會存在一些無法預(yù)知的問題,你并不清楚,當(dāng)前任務(wù)管理器是使用哪個版本的環(huán)境輸出調(diào)試信息的。 有了dump文件之后就是通過調(diào)試工具打開就行了,WinDbg就有一個菜單專門打開dump文件的,Open Crash Dump。使用ntsd需要使用命令ntsd -z d:order.dmp。 5.一般調(diào)試步驟知道了調(diào)試的一些原理和工具之后我們來看一下調(diào)試的基本步驟,這些步驟都具體是指的什么意思,有哪些好處。 5.1.設(shè)置符號文件(公有符號、私有符號)設(shè)置符號文件的目的是為了能夠在調(diào)試器中正確的對應(yīng)到源代碼的位置和一些元數(shù)據(jù)信息。符號文件都是*.pdb文件名。符號文件分為公有和私有兩種,公有的都是公司公開出去用于幫助調(diào)試用的,而私有的是公司內(nèi)部使用的,為什么要區(qū)分公有和私有,是為了防止逆向工程。 圖17:(設(shè)置符號文件路徑)
首先通過.sympath d:,設(shè)置了符號路徑為D盤,然后又使用.symfix+ d:,是設(shè)置私有符號路徑,并且使用d盤為緩存路徑。在最后一個紅線中我們能看出來。 為什么使用.symfix 時要帶上一個+號,其實(shí)是告訴調(diào)試器我們是多加一個符號位置,而不是覆蓋原有符號位置。 設(shè)置好了兩個符號位置后需要使用.reload命令來重新加載模塊,這樣調(diào)試器才會去符號位置去加載這些符號。 圖18:(加載的符號文件)
調(diào)試器會自動的將公有符號下載到你剛才設(shè)置的緩存目錄中。 5.2.加載.NET程序擴(kuò)展調(diào)試包(SOS.DLL、SOSEX.DLL)對.NET程序分析當(dāng)然是需要加載SOS擴(kuò)展了。加載SOS擴(kuò)展有兩個命令可以使用,第一個是.load C:WindowsMicrosoft.NETFrameworkv4.0.30319SOS.dll,.load命令是要給出sos.dll絕對路徑的。第二個是.loadby sos modulename,.loadby 命令是可以根據(jù)已經(jīng)加載的模塊名稱來加載SOS.dll擴(kuò)展。使用第一個命令有一個問題就是,我們需要人工的判斷當(dāng)前環(huán)境到底是需要什么版本的SOS擴(kuò)展,而使用.loadby是可以根據(jù)已經(jīng)加載的模塊來自動的查找對應(yīng)的SOS擴(kuò)展。 0:000> .load C:WindowsMicrosoft.NETFrameworkv4.0.30319SOS.dll 0:000> .loadby sos.dll clrjit 使用.loadby 命令很容易的就可以加載SOS擴(kuò)展,而不需要自己去判斷當(dāng)前程序是.NET什么版本的。 5.3.調(diào)試的三種命令類型(標(biāo)準(zhǔn)命令、元命令、擴(kuò)展命令)在使用調(diào)試器調(diào)試程序時,所要使用的命令主要分為三類。 第一類是標(biāo)準(zhǔn)命令,就是不帶任何符號開始的命令,比如:pb、lmvm。這一類命令是所有Windows調(diào)試工具箱中的調(diào)試工具通用的,不管你是使用ntsd還是winDbg都可以。 第二類命令是元命令,就是使用”.”號開始的命令,這一類命令并不是在所有調(diào)試工具中通用的。第三類是擴(kuò)展命令,擴(kuò)展命令就是各個調(diào)試器擴(kuò)展出來的命令,也就是以”!”開始的命令,如:!dumpheap -stat,!dumpstatcobjects。 6.調(diào)試擴(kuò)展的幾個比較常用的命令(SOS.DLL、SOSEX.DLL)當(dāng)然這個純粹是我的個人感覺,排名不分先后。
可以一眼看出哪些對象過大,這里我是為了演示而用,一般在項(xiàng)目開發(fā)中,我們都大概知道哪些對象可能會有內(nèi)存問題,比如:同步數(shù)據(jù)時的緩存對象。
當(dāng)然還有很多其他很不錯的命令,這里我個人覺得這幾個比較常用,要想了解所有的命令可是在調(diào)試器中使用擴(kuò)展命令!help來查看所有的命令幫助。
|
|
|