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