閱讀目錄:
目前創(chuàng)新互聯(lián)已為上千的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)站空間、網(wǎng)站托管、企業(yè)網(wǎng)站設(shè)計(jì)、涪城網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。1.背景介紹
2.基本原理(Windows調(diào)試工具箱、.NET調(diào)試擴(kuò)展SOS.DLL、SOSEX.DLL)
2.1.Windows調(diào)試工具箱
2.2..NET調(diào)試擴(kuò)展包,SOS.DLL、SOSEX.DLL
2.3.調(diào)試系統(tǒng)的基本流程及架構(gòu)(.NETDAC概念、mscordacwks.dll)
2.4.VisualStudio中集成擴(kuò)展調(diào)試(更加細(xì)粒度的調(diào)試程序)
3.調(diào)試程序類型(客戶端程序、服務(wù)端程序)
4.調(diào)試方式及場(chǎng)景
4.1.本機(jī)調(diào)試(Attach Process,調(diào)試器啟動(dòng))
4.2.不中斷調(diào)試或者稱事后調(diào)試(對(duì)Dump文件進(jìn)行調(diào)試)
5.一般調(diào)試步驟
5.1.設(shè)置符號(hào)文件(公有符號(hào)、私有符號(hào))
5.2.加載.NET程序擴(kuò)展調(diào)試包(SOS.DLL、SOSEX.DLL)
5.3.調(diào)試的三種命令類型(標(biāo)準(zhǔn)命令、元命令、擴(kuò)展命令)
6.調(diào)試擴(kuò)展的幾個(gè)比較常用的命令(SOS.DLL、SOSEX.DLL)
7.簡(jiǎn)單示例,常見(jiàn)的線上兩類問(wèn)題
7.1.內(nèi)存問(wèn)題(內(nèi)存偏高,內(nèi)存溢出)
7.2.線程問(wèn)題(CPU過(guò)高,線程死鎖)
8.獲取Dump文件時(shí)的重要注意事項(xiàng)
9.總結(jié)
隨著應(yīng)用程序的復(fù)雜度不斷上升,要想將好的設(shè)計(jì)思想穩(wěn)定的落實(shí)到線上,我們需要具備解決問(wèn)題的能力。需要具備對(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ò)展命令是不能用的,類似這樣的問(wèn)題,一旦出現(xiàn)你就一頭霧水,所以花點(diǎn)時(shí)間學(xué)習(xí)一下原理是有必要的。
在Windows平臺(tái)上調(diào)試應(yīng)用程序選Windows調(diào)試工具箱,該工具箱包含了一套專門(mén)用來(lái)針對(duì)Windows進(jìn)行很多復(fù)雜場(chǎng)景調(diào)試所需要的工具和組件。需要注意的是此工具箱是針對(duì)于非托管.NET平臺(tái)用的,意思就是說(shuō)此工具箱的所有工具和組件默認(rèn)是不能夠進(jìn)行.NET應(yīng)用程序調(diào)試的,只能用來(lái)對(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)試就需要專門(mén)的翻譯器才能夠執(zhí)行。SOS.DLL、SOSEX.DLL這兩個(gè)就是用來(lái)對(duì).NET程序在Windows調(diào)試工具中起到翻譯作用的調(diào)試器擴(kuò)展。簡(jiǎn)單講就是,這兩個(gè)組件是.NET項(xiàng)目組專門(mén)開(kāi)發(fā)出來(lái)用來(lái)對(duì).NET應(yīng)用程序進(jìn)行方便調(diào)試用的,當(dāng)然不用這兩個(gè)擴(kuò)展也能調(diào)試.NET程序,只不過(guò)就會(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)過(guò).NET調(diào)試擴(kuò)展組件進(jìn)行翻譯才行,也就是要使用.NET調(diào)試擴(kuò)展的調(diào)試命令來(lái)調(diào)試.NET程序。上圖中,我們?nèi)绻胝{(diào)試.NET程序就需要將.NET調(diào)試擴(kuò)展組件加載到Windows調(diào)試工具中去,然后才能方便在Windows調(diào)試工具中使用。
Windows調(diào)試工具箱中包含了很多調(diào)試工具,都是用來(lái)輔助于我們進(jìn)行方便調(diào)試用的。Windows調(diào)試工具箱分為兩個(gè)執(zhí)行版本,X86、X64這兩個(gè)版本是專門(mén)用來(lái)分析不同的運(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)
安裝過(guò)后的菜單中就只有WinDbg作為調(diào)試選擇。
這里需要注意的是,當(dāng)你啟動(dòng)了WinDbg之后要留意程序的名字和標(biāo)題,因?yàn)楫?dāng)你存在兩個(gè)版本的WinDbg時(shí)會(huì)容易搞錯(cuò),在調(diào)試時(shí)會(huì)有各種奇怪的問(wèn)題出現(xiàn),當(dāng)你找了半天之后結(jié)果發(fā)現(xiàn)是因?yàn)橛缅e(cuò)了版本,那就正的無(wú)語(yǔ)了。
圖3:(注意運(yùn)行WinDbg的環(huán)境版本)
WinDbg是默認(rèn)的調(diào)試工具,但是在工具箱中還有幾個(gè)控制臺(tái)調(diào)試工具,他們行必之下比較輕量簡(jiǎn)單,有些任務(wù)比較好執(zhí)行,在配合cmd使用會(huì)很方便,比如工具箱中的tlist.exe用來(lái)查看進(jìn)程信息的小工具就非常方便。
圖4:(方便查看進(jìn)程ID)
這樣我們就可以很方便的attach到一個(gè)指定的進(jìn)程進(jìn)行調(diào)試。
Windows調(diào)試工具箱中有很多其他的工具,需要用的話可以使用cmd切換到當(dāng)前安裝的目錄下:C:\Program Files\Debugging Tools for Windows (x86),或者你直接到工具的安裝目錄運(yùn)行也行,這就看此工具是不是支持手動(dòng)無(wú)參數(shù)啟動(dòng)了。
.NET調(diào)試擴(kuò)展包分為兩個(gè),一個(gè)是SOS.DLL,該擴(kuò)展包是.NET平臺(tái)的一部分,屬于官方版本。而SOSEX.DLL是微軟的一名叫“Steve Johnson”軟件工程師開(kāi)發(fā),屬于個(gè)人維護(hù)的,用來(lái)增強(qiáng)SOS.DLL功能的,在SOSEX.DLL有很多功能比較強(qiáng)大的擴(kuò)展命令。
下載地址為:
32位:http://www.stevestechspot.com/downloads/sosex_32.zip
64位:http://www.stevestechspot.com/downloads/sosex_64.zip
具體的幫助文檔可以查看該工程師的博客來(lái)了解詳情。這兩個(gè)版本用來(lái)調(diào)試不同環(huán)境的程序的,如果你的程序是運(yùn)行在32位環(huán)境下,就用32位的SOSEX,同理,用在64位下就用64位SOSEX。
而SOS.DLL擴(kuò)展包是跟著.NETFramework一起安裝的,地址位于:C:\Windows\Microsoft.NET\Framework\v4.0.30319。如果你是64位系統(tǒng)的話地址就是:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319。在這兩個(gè)地址下面都可以找到SOS.dll文件,不同的目錄下對(duì)應(yīng)于調(diào)試不同機(jī)器類型的.NET程序。
有了這兩個(gè)擴(kuò)展包之后就可以在WinDbg中對(duì).NET程序進(jìn)行分析了,具體使用我們后面會(huì)介紹。
有一個(gè)很重要的原理我覺(jué)得很有必要講一下,就是.NETDAC概念。
其實(shí).NETDAC也就是.NET Data Access .NET數(shù)據(jù)訪問(wèn)層,這個(gè)是專門(mén)用來(lái)提供給SOS.DLL\SOSEXDLL或者其他調(diào)試擴(kuò)展包使用的,所有的調(diào)試擴(kuò)展組件必須通過(guò)這個(gè)DAC才能訪問(wèn)到.NET運(yùn)行時(shí)的數(shù)據(jù),所以在初次使用SOS的時(shí)候會(huì)經(jīng)常碰見(jiàn)加載錯(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)頭疼的問(wèn)題。
圖5:(mscordacwks.dll位置)
當(dāng)你知道這個(gè)組件是工作于此位置時(shí),當(dāng)出現(xiàn)跟它相關(guān)的錯(cuò)誤提示時(shí)你就不需要擔(dān)心了,無(wú)非就是文件加載的位置或者版本不匹配而已。
調(diào)試器會(huì)話、調(diào)試器注入線程
還有一點(diǎn)我覺(jué)得也很有必要介紹的就是有關(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í)中斷,原理就是通過(guò)發(fā)送線程中斷命令來(lái)達(dá)到控制目標(biāo)線程,那么首先要能夠與原線程通訊才行,所以需要注入托管線程。(注意:注入的線程不一定就是托管.NET線程,嚴(yán)重它最好的方法就是查看所有所有的進(jìn)程內(nèi)線程和所有托管線程,對(duì)比一下就知道了。),其實(shí)這個(gè)ID為3的線程是調(diào)試器會(huì)話線程。
圖7:(切換到原托管線程)
我們通過(guò)~0s命令切換到我們需要調(diào)試的原托管線程中,比如,在執(zhí)行!ClrStack命令時(shí),就需要切換到當(dāng)前線程上執(zhí)行。
我們需要驗(yàn)證它是否是注入了托管線程還是非托管線程。
圖8:(托管線程列表)
使用!Threads命令可以查看進(jìn)程內(nèi)所有的托管線程,僅僅是托管線程,此命令是無(wú)法查看非托管線程的,接下來(lái)我們使用另外一個(gè)命令來(lái)查看所有的線程。
圖9:(所有的執(zhí)行時(shí)線程)
這樣我們就可以判斷出,調(diào)試器使用了ID位7的作為目前的調(diào)試會(huì)話線程。知道這些背后的原理很重要,當(dāng)你在執(zhí)行某個(gè)調(diào)試命令時(shí)你就會(huì)發(fā)現(xiàn)此命令是否需要在.NET線程中執(zhí)行,還是說(shuō)可以在調(diào)試器會(huì)話線程中執(zhí)行,一般dump類的命令都是可以遠(yuǎn)程執(zhí)行的,也就是說(shuō)在調(diào)試器會(huì)話中執(zhí)行,當(dāng)需要跟蹤.NET線程內(nèi)部過(guò)程時(shí)就需要切換到.NET線程上去執(zhí)行。
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ù)等。通過(guò)集成SOS擴(kuò)展會(huì)讓我們對(duì)程序的運(yùn)行時(shí)有了一個(gè)更加方便的跟蹤。
圖10:(打開(kāi)本地代碼調(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ò)展命名來(lái)查看內(nèi)存問(wèn)題,當(dāng)需要調(diào)試程序邏輯時(shí)在單步調(diào)式C#代碼,一舉兩得。
客戶端程序也大概分為控制臺(tái)、Winform兩種,服務(wù)端程序都是基于ASP.NET框架,宿主與IIS進(jìn)程中。
針對(duì)不同類型的程序及場(chǎng)景需要使用不同的方式進(jìn)行調(diào)試,客戶端程序中的控制臺(tái)程序基本上可以通過(guò)在調(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)。
本機(jī)調(diào)試可以直接在調(diào)試器中啟動(dòng)程序,WinDbg打開(kāi)后,在文件中有一個(gè)Open Executable,可以打開(kāi)一個(gè)可執(zhí)行文件。如果是使用NTSD控制臺(tái)調(diào)試器,則需要在NTSD后面跟上程序的執(zhí)行路徑。
圖12:(ntsd.exe打開(kāi)調(diào)試程序)
同樣,在WinDbg中也有一個(gè)附加進(jìn)程的選項(xiàng),NTSD也是一樣,操作起來(lái)都比較簡(jiǎn)單,需要注意的是當(dāng)你對(duì)進(jìn)程進(jìn)行附加時(shí)要清楚此進(jìn)程是多少位的,然后你需要選擇正確的調(diào)試器進(jìn)行調(diào)試。
在不能夠?qū)Ρ徽{(diào)試程序直接調(diào)試時(shí)我們就需要此程序的進(jìn)程鏡像文件,此鏡像文件就是進(jìn)程在某一個(gè)時(shí)刻的快照,通過(guò)分析這個(gè)快照,我們也是可以定位出問(wèn)題的。首先我們需要使用適當(dāng)?shù)墓ぞ邅?lái)獲取進(jìn)程的dump文件,操作系統(tǒng)本身的任務(wù)管理器就有這個(gè)功能,dump文件的存放位置默認(rèn)在用戶信息臨時(shí)文件下面,比如:XXX\Users\Administrator\AppData\Local\Temp,獲取完dump文件后任務(wù)管理器會(huì)有提示路徑的。
圖13:(使用任務(wù)管理器獲取dump文件)
圖14:
使用任務(wù)管理器獲取dump文件固然很方便,但是有一個(gè)問(wèn)題就是如果當(dāng)前機(jī)器是64位的,并且你的進(jìn)程是以32位方式運(yùn)行的,那么此時(shí)你獲取出來(lái)的dump文件是64位的,當(dāng)你通過(guò)32位的調(diào)試器無(wú)法進(jìn)行分析,甚至?xí)懈鞣N其他的問(wèn)題,這些問(wèn)題就是因?yàn)楂@取dump文件的機(jī)器環(huán)境和你預(yù)想的不一致。這個(gè)時(shí)候我們希望能夠通過(guò)很明了的方式來(lái)獲取dump文件,就是通過(guò)調(diào)試器來(lái)獲取dump文件。
通過(guò)調(diào)試器來(lái)獲取dump文件有很多好處,可以設(shè)置很多選項(xiàng),包括只獲取進(jìn)程的哪部分鏡像數(shù)據(jù)等。
先通過(guò)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盤(pán)。
圖16:(使用NTSD.exe獲取dump文件)
此時(shí)我們就成功的獲取到了dump文件。
通過(guò)調(diào)試器獲取dump文件比較穩(wěn)定可靠,因?yàn)闄C(jī)器運(yùn)行環(huán)境的不同,通過(guò)任務(wù)管理器獲取的dump文件會(huì)存在一些無(wú)法預(yù)知的問(wèn)題,你并不清楚,當(dāng)前任務(wù)管理器是使用哪個(gè)版本的環(huán)境輸出調(diào)試信息的。
有了dump文件之后就是通過(guò)調(diào)試工具打開(kāi)就行了,WinDbg就有一個(gè)菜單專門(mén)打開(kāi)dump文件的,Open Crash Dump。使用ntsd需要使用命令ntsd -z d:\order.dmp。
知道了調(diào)試的一些原理和工具之后我們來(lái)看一下調(diào)試的基本步驟,這些步驟都具體是指的什么意思,有哪些好處。
設(shè)置符號(hào)文件的目的是為了能夠在調(diào)試器中正確的對(duì)應(yīng)到源代碼的位置和一些元數(shù)據(jù)信息。符號(hào)文件都是*.pdb文件名。符號(hào)文件分為公有和私有兩種,公有的都是公司公開(kāi)出去用于幫助調(diào)試用的,而私有的是公司內(nèi)部使用的,為什么要區(qū)分公有和私有,是為了防止逆向工程。
圖17:(設(shè)置符號(hào)文件路徑)
首先通過(guò).sympath d:,設(shè)置了符號(hào)路徑為D盤(pán),然后又使用.symfix+ d:,是設(shè)置私有符號(hào)路徑,并且使用d盤(pán)為緩存路徑。在最后一個(gè)紅線中我們能看出來(lái)。
為什么使用.symfix 時(shí)要帶上一個(gè)+號(hào),其實(shí)是告訴調(diào)試器我們是多加一個(gè)符號(hào)位置,而不是覆蓋原有符號(hào)位置。
設(shè)置好了兩個(gè)符號(hào)位置后需要使用.reload命令來(lái)重新加載模塊,這樣調(diào)試器才會(huì)去符號(hào)位置去加載這些符號(hào)。
圖18:(加載的符號(hào)文件)
調(diào)試器會(huì)自動(dòng)的將公有符號(hào)下載到你剛才設(shè)置的緩存目錄中。
對(duì).NET程序分析當(dāng)然是需要加載SOS擴(kuò)展了。加載SOS擴(kuò)展有兩個(gè)命令可以使用,第一個(gè)是.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll,.load命令是要給出sos.dll絕對(duì)路徑的。第二個(gè)是.loadby sos modulename,.loadby 命令是可以根據(jù)已經(jīng)加載的模塊名稱來(lái)加載SOS.dll擴(kuò)展。使用第一個(gè)命令有一個(gè)問(wèn)題就是,我們需要人工的判斷當(dāng)前環(huán)境到底是需要什么版本的SOS擴(kuò)展,而使用.loadby是可以根據(jù)已經(jīng)加載的模塊來(lái)自動(dòng)的查找對(duì)應(yīng)的SOS擴(kuò)展。
0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll
0:000> .loadby sos.dll clrjit
使用.loadby 命令很容易的就可以加載SOS擴(kuò)展,而不需要自己去判斷當(dāng)前程序是.NET什么版本的。
在使用調(diào)試器調(diào)試程序時(shí),所要使用的命令主要分為三類。
第一類是標(biāo)準(zhǔn)命令,就是不帶任何符號(hào)開(kāi)始的命令,比如:pb、lmvm。這一類命令是所有Windows調(diào)試工具箱中的調(diào)試工具通用的,不管你是使用ntsd還是winDbg都可以。
第二類命令是元命令,就是使用"."號(hào)開(kāi)始的命令,這一類命令并不是在所有調(diào)試工具中通用的。第三類是擴(kuò)展命令,擴(kuò)展命令就是各個(gè)調(diào)試器擴(kuò)展出來(lái)的命令,也就是以"!"開(kāi)始的命令,如:!dumpheap -stat,!dumpstatcobjects。
當(dāng)然這個(gè)純粹是我的個(gè)人感覺(jué),排名不分先后。
!dumpheap -stat (查看托管堆統(tǒng)計(jì)信息)
0:000> !dumpheap -stat
Statistics:
MT Count
TotalSize Class Name
65366e78 1 12
System.Collections.Generic.EnumEqualityComparer`1[[System.Web.Compilation.FolderLevelBuildProviderAppliesTo,
System.Web]]
653667cc 1 12
System.Collections.Generic.ObjectEqualityComparer`1[[System.Web.WebSockets.IAsyncAbortableWebSocket,
System.Web]]
65365f08 1 12
System.Lazy`1+Boxed[[System.Web.Security.Cryptography.AspNetCryptoServiceProvider,
System.Web]]
65365a34 1 12
System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper
65361e20
1 12 System.Web.Configuration.CustomErrorsMode
!dumpheap -type (查看某個(gè)類型在堆中的信息)
0:000> !dumpheap -type System.String
Address MT
Size
10731228 624aacc0 14
107312c4 624aacc0 22
107312dc 624aacc0 78
10731370 624aacc0 28
可以一眼看出哪些對(duì)象過(guò)大,這里我是為了演示而用,一般在項(xiàng)目開(kāi)發(fā)中,我們都大概知道哪些對(duì)象可能會(huì)有內(nèi)存問(wèn)題,比如:同步數(shù)據(jù)時(shí)的緩存對(duì)象。
!dumpobj 10731228 (查看對(duì)象詳情)
0:000> !dumpobj 10731228
Name:
System.String
MethodTable: 624aacc0
EEClass: 620b486c
Size:
14(0xe) bytes
File:
C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:
Fields:
MT Field Offset Type VT Attr
Value Name
624ac480 40000aa 4 System.Int32 1 instance
0 m_stringLength
624ab6b8 40000ab 8 System.Char 1
instance 0 m_firstChar
624aacc0 40000ac c
System.String 0 shared static Empty
>> Domain:Value
00dbe558:NotInit 00e11c90:NotInit 00e5f040:NotInit <<
!threads(查看托管線程)
0:000> !threads
ThreadCount: 17
UnstartedThread:
0
BackgroundThread: 12
PendingThread: 0
DeadThread:
5
Hosted Runtime:
no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context
Domain Count Apt Exception
7 1 43a8 00dc2620 28220 Preemptive
1484CA40:00000000 00dbe558 0 Ukn
15 2 4414 00dd38d0 2b220
Preemptive 00000000:00000000 00dbe558 0 MTA (Finalizer)
17 3 441c
00e09e88 102a220 Preemptive 00000000:00000000 00dbe558 0 MTA (Threadpool
Worker)
18 4 4420 00e0ce80 21220 Preemptive 00000000:00000000
00dbe558 0 Ukn
當(dāng)然還有很多其他很不錯(cuò)的命令,這里我個(gè)人覺(jué)得這幾個(gè)比較常用,要想了解所有的命令可是在調(diào)試器中使用擴(kuò)展命令!help來(lái)查看所有的命令幫助。
0:000> !help
-------------------------------------------------------------------------------
SOS
is a debugger extension DLL designed to aid in the debugging of
managed
programs. Functions are listed by category, then roughly in order
of
importance. Shortcut names for popular functions are listed in
parenthesis.
Type "!help <functionname>" for detailed info on that
function.
Object Inspection Examining code and
stacks
-----------------------------
-----------------------------
DumpObj (do)
Threads
DumpArray (da) ThreadState
DumpStackObjects
(dso) IP2MD
DumpHeap
U
DumpVC
DumpStack
GCRoot
EEStack
ObjSize
CLRStack
FinalizeQueue GCInfo
PrintException
(pe) EHInfo
TraverseHeap BPMD
COMState
Examining CLR data structures Diagnostic
Utilities
-----------------------------
-----------------------------
DumpDomain
VerifyHeap
EEHeap
VerifyObj
Name2EE
FindRoots
SyncBlk
HeapStat
DumpMT
GCWhere
DumpClass ListNearObj
(lno)
DumpMD
GCHandles
Token2EE
GCHandleLeaks
EEVersion FinalizeQueue
(fq)
DumpModule
FindAppDomain
ThreadPool
SaveModule
DumpAssembly ProcInfo
DumpSigElem StopOnException
(soe)
DumpRuntimeTypes
DumpLog
DumpSig
VMMap
RCWCleanupList
VMStat
DumpIL MinidumpMode
DumpRCW AnalyzeOOM (ao)
DumpCCW
Examining the GC history
Other
-----------------------------
-----------------------------
HistInit
FAQ
HistRoot
HistObj
HistObjFind
HistClear
這里我們使用兩個(gè)小示例直觀的感受一下接觸.NET運(yùn)行時(shí)狀態(tài)的感受,盡管真實(shí)的問(wèn)題可能比這個(gè)復(fù)雜很多,但是解決問(wèn)題的思路是一樣的。
服務(wù)程序最怕的性能問(wèn)題之一就是內(nèi)存,當(dāng)內(nèi)存很高的情況下我們能夠通過(guò)對(duì)dump文件進(jìn)行查看,看哪些對(duì)象導(dǎo)致內(nèi)存一直高。當(dāng)內(nèi)存一直高的情況下就會(huì)容易導(dǎo)致內(nèi)存溢出異常,甚至是GC頻繁的執(zhí)行,當(dāng)GC一執(zhí)行就會(huì)導(dǎo)致服務(wù)并發(fā)下降,因?yàn)樗獟炱鹚械木€程(這里指的是服務(wù)器模式的.NETCLR,相對(duì)應(yīng)的還有工作站模式的.NETCLR)。
namespace OrderManager { class Program { static void Main(string[] args) { Console.WriteLine("app begin..."); Console.ReadLine(); List<byte[]> l = new List<byte[]>(); for (int i = 0; i < 9999999; i++) { byte[] b = new byte[1000]; l.Add(b); Console.WriteLine(i); } Console.WriteLine("end begin..."); Console.ReadLine(); } } }
這一段代碼會(huì)一直分配內(nèi)存直到最后內(nèi)存溢出異常終止程序,我們?cè)趦?nèi)存比較的情況下來(lái)獲取一個(gè)dump文件,然后通過(guò)適當(dāng)?shù)拿顏?lái)定位哪個(gè)對(duì)象占用內(nèi)存過(guò)高。
在不知道對(duì)象類型的情況下比較簡(jiǎn)單的方式就是使用:0:000> !dumpheap -stat,命令,該命令的意思是統(tǒng)計(jì)當(dāng)前堆的信息,在這里就可以一眼找到哪個(gè)對(duì)象占用多少內(nèi)存。
0:000> !dumpheap -stat
Statistics:
MT Count TotalSize
Class Name
624ad6a8 1 12
System.Collections.Generic.GenericEqualityComparer`1[[System.String,
mscorlib]]
624ac480 1 12 System.Int32
624aa58c
1 12 System.Collections.Generic.ObjectEqualityComparer`1[[System.Type,
mscorlib]]
624adec0 1 16
System.Security.Policy.AssemblyEvidenceFactory
624ace34 1 16
System.Text.DecoderReplacementFallback
624acde4 1 16
System.Text.EncoderReplacementFallback
6247a840 1 16
System.IO.TextReader+SyncTextReader
624ade0c 1 20
Microsoft.Win32.SafeHandles.SafePEFileHandle
6245fe58 1 20
Microsoft.Win32.SafeHandles.SafeFileMappingHandle
6245fe08 1
20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
6245fd74
1 20 System.Text.InternalEncoderBestFitFallback
6245f714
1 20 System.IO.Stream+NullStream
624ad3d4 1 24
System.Version
6245fdc4 1 24
System.Text.InternalDecoderBestFitFallback
6245fa8c 1 24
System.IO.TextWriter+SyncTextWriter
00163170 1 24
System.Collections.Generic.List`1[[System.Byte[], mscorlib]]
624ad4b4
1 28 System.Text.StringBuilder
624ab0b4 1 28
System.SharedStatics
6247c1b8 1 28
System.Text.DBCSCodePageEncoding+DBCSDecoder
6245f94c 1 28
Microsoft.Win32.Win32Native+InputRecord
6245f664 1 28
System.Text.EncoderNLS
624ade68 1 32
System.Security.Policy.PEFileEvidenceFactory
624acc10 1 32
System.Text.UnicodeEncoding
624ab938 1 36
System.Security.PermissionSet
624aced8 2 40
Microsoft.Win32.SafeHandles.SafeFileHandle
624ab7b0 1 40
System.Security.Policy.Evidence
624aaa64 1 44
System.Threading.ReaderWriterLock
6247cd1c 1 44
System.Text.InternalEncoderBestFitFallbackBuffer
624aab90 1
48 System.Collections.Hashtable+bucket[]
620c2348 1 48
System.Collections.Generic.Dictionary`2[[System.String,
mscorlib],[System.Globalization.CultureData, mscorlib]]
620c2268
1 48 System.Collections.Generic.Dictionary`2[[System.Type,
mscorlib],[System.Security.Policy.EvidenceTypeDescriptor,
mscorlib]]
624acf98 1 52
System.Collections.Hashtable
624ab8d8 1 52
System.Threading.Thread
624acb20 2 56
System.Reflection.RuntimeAssembly
6245f994 2 56
System.IO.__ConsoleStream
624adaa8 1 60
System.IO.StreamWriter
624ad7b4 1 60
System.Collections.Generic.Dictionary`2+Entry[[System.String,
mscorlib],[System.Globalization.CultureData, mscorlib]][]
6249fbec
1 64 System.IO.StreamReader
624ab4e4 1 68
System.AppDomainSetup
6247c624 1 76
System.Text.DBCSCodePageEncoding
624ad474 1 84
System.Globalization.CalendarData
624ab060 7 84
System.Object
624aafe4 1 84
System.ExecutionEngineException
624aafa0 1 84
System.StackOverflowException
624aaf5c 1 84
System.OutOfMemoryException
624aae08 1 84
System.Exception
624ab130 1 112
System.AppDomain
624ad164 2 144
System.Globalization.CultureInfo
624ab028 2 168
System.Threading.ThreadAbortException
624ad82c 2 264
System.Globalization.NumberFormatInfo
624aa9f8 1 284
System.Collections.Generic.Dictionary`2+Entry[[System.Type,
mscorlib],[System.Security.Policy.EvidenceTypeDescriptor,
mscorlib]][]
624ac448 8 484 System.Int32[]
624ad3a0
2 616 System.Globalization.CultureData
624abe78 26
728 System.RuntimeType
624ab680 7 2910
System.Char[]
6245ab98 25 18064 System.Object[]
624aacc0
3283 85972 System.String
00363a78 7 2031754
Free
624696f8 2 2097184 System.Byte[][]
624acf54 301232
304844554 System.Byte[]
最后一個(gè)顯然內(nèi)存占用比較高,占了304844554 bite,如果你想在此情況下知道對(duì)象的內(nèi)存地址你就直接使用!dumpheap ,不帶任何參數(shù)。由于此命令會(huì)導(dǎo)致很多輸出,我這里就寫(xiě)出輸出內(nèi)容了。通過(guò)!dumpheap 會(huì)得到內(nèi)存很高的對(duì)象地址,02d55368,這個(gè)地址就是System.Byte[]對(duì)象,為了找到對(duì)象在哪里分配的,我們需要使用!gcroot 02d55368,命令,查看對(duì)象的根在哪里。
0:000> !gcroot 02d55368
Thread 143310:
0028f364
004f0100 OrderManager.Program.Main(System.String[])
[e:\NETDebug\DebugDemoProject\OrderManager\Program.cs @ 22]
ebp+18:
0028f380
-> 01b746c0
System.Collections.Generic.List`1[[System.Byte[], mscorlib]]
-> 02d55368 System.Byte[][]
知道了根就好辦多了,直接看源代碼就能發(fā)現(xiàn)問(wèn)題。如果你還不死心的話可以使用!dumpobj 查看List對(duì)象。
0:000> !dumpobj 01b746c0
Name:
System.Collections.Generic.List`1[[System.Byte[], mscorlib]]
MethodTable:
00163170
EEClass: 6211c8b0
Size: 24(0x18) bytes
File:
C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
6245ab98
4000c75 4 System.Object[] 0 instance 02d55368 _items
624ac480
4000c76 c System.Int32 1 instance 301229 _size
624ac480
4000c77 10 System.Int32 1 instance 301229 _version
624ab060
4000c78 8 System.Object 0 instance 00000000
_syncRoot
6245ab98 4000c79 0 System.Object[] 0 shared
static _emptyArray
>> Domain:Value dynamic statics NYI
00359520:NotInit <<
這里需要注意的是,如果你是想執(zhí)行!Clrstack -a 命令的話,當(dāng)你使用調(diào)試器啟動(dòng)或者是附加進(jìn)程的方式的化,要記住切換到適當(dāng)?shù)木€程上才能看行。
CPU過(guò)高也是線上比較棘手的問(wèn)題之一,查看CPU過(guò)高的步驟一般分為兩步,查看線程的執(zhí)行時(shí)間,然后切換到線程上下文,執(zhí)行!ClrStack -a,看當(dāng)前線程在哪里工作,到底做什么操作呢。
0:004> !runaway
User Mode Time
Thread
Time
0:143310 0 days 0:00:01.934
4:142ac0 0 days
0:00:00.046
7:143874 0 days 0:00:00.000
6:143870 0 days
0:00:00.000
5:14386c 0 days 0:00:00.000
3:1432ec 0 days
0:00:00.000
2:143384 0 days 0:00:00.000
1:143254 0 days
0:00:00.000
測(cè)試線程ID為0的執(zhí)行時(shí)間比較大,我們需要切換到線程0上去執(zhí)行查看調(diào)用堆棧信息,~0s。
0:000> !ClrStack -a
0028f348 62b897f9
System.IO.TextWriter+SyncTextWriter.WriteLine(Int32)
PARAMETERS:
this (<CLR reg>) = 0x01b74258
value =
<no data>
0028f358 62a66313 System.Console.WriteLine(Int32)
PARAMETERS:
value = <no data>
0028f364 004f0100 OrderManager.Program.Main(System.String[])
[e:\NETDebug\DebugDemoProject\OrderManager\Program.cs @ 22]
PARAMETERS:
args (0x0028f38c) = 0x01b71fe4
LOCALS:
0x0028f380 = 0x01b746c0
0x0028f388 = 0x000498ac
0x0028f37c
= 0x16a2e338
0x0028f384 = 0x00000001
0028f51c 63162952 [GCFrame: 0028f51c]
我們會(huì)發(fā)現(xiàn)在Main方法中有一個(gè)本地變量0x0028f380 ,保存的值是0x01b746c0,它就是指向剛才分配很多內(nèi)存的List<byte[]>對(duì)象。
線程死鎖比較復(fù)雜,這里只給我認(rèn)為比較簡(jiǎn)單的命令,通過(guò)此命令可以一眼看出哪個(gè)線程持有了哪個(gè)鎖,目前在等待哪個(gè)鎖。
0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning
Thread Info SyncBlock Owner
4 0021fb20 3 1
00221f98 14974c 3 01ae2394 OrderManager.ImportOrder
5
0021fb54 3 1 002234a8 149754 4 01ae23a0
OrderManager.ImportOrder
-----------------------------
Total
5
CCW 0
RCW 0
ComClassFactory
0
Free 0
這是兩個(gè)鎖,也就是兩個(gè)對(duì)象同步塊。進(jìn)一步使用SOSEX.dll中的!dlk查看死鎖的自動(dòng)化檢查信息。
0:000> !dlk
Examining SyncBlocks...
Scanning for
ReaderWriterLock instances...
Scanning for holders of ReaderWriterLock
locks...
Scanning for ReaderWriterLockSlim instances...
Scanning for
holders of ReaderWriterLockSlim locks...
Examining
CriticalSections...
Could not find symbol
ntdll!RtlCriticalSectionList.
Scanning for threads waiting on
SyncBlocks...
Scanning for threads waiting on ReaderWriterLock
locks...
Scanning for threads waiting on ReaderWriterLocksSlim
locks...
Scanning for threads waiting on
CriticalSections...
*DEADLOCK DETECTED*
CLR thread 0x3 holds the
lock on SyncBlock 0021fb20 OBJ:01ae2394[OrderManager.ImportOrder]
...and is
waiting for the lock on SyncBlock 0021fb54
OBJ:01ae23a0[OrderManager.ImportOrder]
CLR thread 0x4 holds the lock on
SyncBlock 0021fb54 OBJ:01ae23a0[OrderManager.ImportOrder]
...and is waiting
for the lock on SyncBlock 0021fb20
OBJ:01ae2394[OrderManager.ImportOrder]
CLR Thread 0x3 is waiting at
System.Threading.Monitor.Enter(System.Object, Boolean ByRef)(+0x17
Native)
CLR Thread 0x4 is waiting at
System.Threading.Monitor.Enter(System.Object, Boolean ByRef)(+0x17 Native)
1 deadlock detected.
注意我加粗的那段話,檢測(cè)到死鎖。
在獲取dump文件方面我也要分享一下重要的注意事項(xiàng)。如果獲取dump文件不正確的話是無(wú)法進(jìn)行分析的,會(huì)出現(xiàn)任何奇怪的問(wèn)題。
第一個(gè)就是使用64位機(jī)器上的任務(wù)管理獲取32位進(jìn)程dump文件,這通常是發(fā)生在服務(wù)器上,由于服務(wù)器IIS默認(rèn)的啟動(dòng)進(jìn)程方式是64位的,但是也有些情況下會(huì)變成32位的。
圖19:
如果進(jìn)程是以32位方式運(yùn)行的,那么這個(gè)時(shí)候獲取出來(lái)的dump文件是不好分析的,此時(shí)應(yīng)該使用調(diào)試器工具進(jìn)行dump的獲取。獲取出來(lái)的dump文件和分析機(jī)器上的調(diào)試器環(huán)境不一致的情況下會(huì)出現(xiàn)如下幾個(gè)錯(cuò)誤。
圖20:
這個(gè)問(wèn)題是未能加載正確版本的mscordacwks.dll .NETDAC調(diào)式組件。
圖21:
這個(gè)問(wèn)題是當(dāng)前SOS.dll和.NET程序所使用的.NET版本不一致,這個(gè)問(wèn)題的出現(xiàn)一般都是我們通過(guò).load xx\xx\SOS.dll,手動(dòng)方式加載的。
圖22:
這個(gè)問(wèn)題出現(xiàn)有好幾種可能性,對(duì)常見(jiàn)的問(wèn)題就是未能使用正確的方法或者工具獲取dump文件,導(dǎo)致dum文件獲取的機(jī)器和本地調(diào)試的機(jī)器整個(gè)環(huán)境不一致。
本篇文章分享我對(duì).NET應(yīng)用程序調(diào)試方面學(xué)習(xí)和實(shí)踐的一些經(jīng)驗(yàn),供廣大博友參考。如果想系統(tǒng)的學(xué)習(xí)一下這方面的知識(shí)可以參考《.NET高級(jí)調(diào)試》一書(shū),此書(shū)非常底層,對(duì).NET運(yùn)行時(shí)原理講的很透徹,可以作為深入學(xué)習(xí).NET的一門(mén)參考書(shū)。
作者:王清培
出處:http://wangqingpei557.blog.51cto.com/
本文版權(quán)歸作者和51CTO共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。
本文名稱:.NET應(yīng)用程序調(diào)試—原理、工具、方法-創(chuàng)新互聯(lián)
網(wǎng)頁(yè)鏈接:http://aaarwkj.com/article4/dddcie.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、商城網(wǎng)站、網(wǎng)站策劃、定制網(wǎng)站、網(wǎng)站制作、微信小程序
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容