真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

.NET應(yīng)用程序調(diào)試—原理、工具、方法-創(chuàng)新互聯(lián)

閱讀目錄:

目前創(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é)力一起成長,共同發(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)試方式及場景

    • 4.1.本機(jī)調(diào)試(Attach Process,調(diào)試器啟動)

    • 4.2.不中斷調(diào)試或者稱事后調(diào)試(對Dump文件進(jìn)行調(diào)試)

  • 5.一般調(diào)試步驟

    • 5.1.設(shè)置符號文件(公有符號、私有符號)

    • 5.2.加載.NET程序擴(kuò)展調(diào)試包(SOS.DLL、SOSEX.DLL)

    • 5.3.調(diào)試的三種命令類型(標(biāo)準(zhǔn)命令、元命令、擴(kuò)展命令)

  • 6.調(diào)試擴(kuò)展的幾個比較常用的命令(SOS.DLL、SOSEX.DLL)

  • 7.簡單示例,常見的線上兩類問題

    • 7.1.內(nèi)存問題(內(nèi)存偏高,內(nèi)存溢出)

    • 7.2.線程問題(CPU過高,線程死鎖)

  • 8.獲取Dump文件時的重要注意事項(xiàng)

  • 9.總結(jié)

1.背景介紹

隨著應(yīng)用程序的復(fù)雜度不斷上升,要想將好的設(shè)計(jì)思想穩(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應(yīng)用程序調(diào)試—原理、工具、方法

所有對.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)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

安裝過后的菜單中就只有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)境版本)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

WinDbg是默認(rèn)的調(diào)試工具,但是在工具箱中還有幾個控制臺調(diào)試工具,他們行必之下比較輕量簡單,有些任務(wù)比較好執(zhí)行,在配合cmd使用會很方便,比如工具箱中的tlist.exe用來查看進(jìn)程信息的小工具就非常方便。

圖4:(方便查看進(jìn)程ID)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

這樣我們就可以很方便的attach到一個指定的進(jìn)程進(jìn)行調(diào)試。

Windows調(diào)試工具箱中有很多其他的工具,需要用的話可以使用cmd切換到當(dāng)前安裝的目錄下:C:\Program Files\Debugging 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.stevestechspot.com/downloads/sosex_32.zip

64位:http://www.stevestechspot.com/downloads/sosex_64.zip

具體的幫助文檔可以查看該工程師的博客來了解詳情。這兩個版本用來調(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。在這兩個地址下面都可以找到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.DLL\SOSEXDLL或者其他調(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位置)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

當(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)試器注入線程)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

此時,調(diào)試器使用一個注入線程將.NET程序在執(zhí)行時中斷,原理就是通過發(fā)送線程中斷命令來達(dá)到控制目標(biāo)線程,那么首先要能夠與原線程通訊才行,所以需要注入托管線程。(注意:注入的線程不一定就是托管.NET線程,嚴(yán)重它最好的方法就是查看所有所有的進(jìn)程內(nèi)線程和所有托管線程,對比一下就知道了。),其實(shí)這個ID為3的線程是調(diào)試器會話線程。

圖7:(切換到原托管線程)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

我們通過~0s命令切換到我們需要調(diào)試的原托管線程中,比如,在執(zhí)行!ClrStack命令時,就需要切換到當(dāng)前線程上執(zhí)行。

我們需要驗(yàn)證它是否是注入了托管線程還是非托管線程。

圖8:(托管線程列表)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

使用!Threads命令可以查看進(jìn)程內(nèi)所有的托管線程,僅僅是托管線程,此命令是無法查看非托管線程的,接下來我們使用另外一個命令來查看所有的線程。

圖9:(所有的執(zhí)行時線程)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

這樣我們就可以判斷出,調(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)試)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

設(shè)置斷點(diǎn),然后在”即時窗口“(調(diào)試->窗口->即時)中加載擴(kuò)展SOS.DLL。

圖11:(在VisualStudio2012中加載SOS.dll擴(kuò)展).NET應(yīng)用程序調(diào)試—原理、工具、方法

這樣的便利性大大提高我們在調(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)試程序)

.NET應(yīng)用程序調(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)在用戶信息臨時文件下面,比如:XXX\Users\Administrator\AppData\Local\Temp,獲取完dump文件后任務(wù)管理器會有提示路徑的。

圖13:(使用任務(wù)管理器獲取dump文件).NET應(yīng)用程序調(diào)試—原理、工具、方法

圖14:

.NET應(yīng)用程序調(diào)試—原理、工具、方法

使用任務(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)程中)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

進(jìn)入到ntsd調(diào)試器中,然后使用.dump/mf d:\order.dmp 命令獲取dump文件到D盤。

圖16:(使用NTSD.exe獲取dump文件)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

此時我們就成功的獲取到了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è)置符號文件路徑)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

首先通過.sympath d:,設(shè)置了符號路徑為D盤,然后又使用.symfix+ d:,是設(shè)置私有符號路徑,并且使用d盤為緩存路徑。在最后一個紅線中我們能看出來。

為什么使用.symfix 時要帶上一個+號,其實(shí)是告訴調(diào)試器我們是多加一個符號位置,而不是覆蓋原有符號位置。

設(shè)置好了兩個符號位置后需要使用.reload命令來重新加載模塊,這樣調(diào)試器才會去符號位置去加載這些符號。

圖18:(加載的符號文件)

.NET應(yīng)用程序調(diào)試—原理、工具、方法

調(diào)試器會自動的將公有符號下載到你剛才設(shè)置的緩存目錄中。

5.2.加載.NET程序擴(kuò)展調(diào)試包(SOS.DLL、SOSEX.DLL)

對.NET程序分析當(dāng)然是需要加載SOS擴(kuò)展了。加載SOS擴(kuò)展有兩個命令可以使用,第一個是.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.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:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.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)然這個純粹是我的個人感覺,排名不分先后。

!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  (查看某個類型在堆中的信息)

0:000> !dumpheap -type System.String
Address    MT    Size
10731228 624aacc0    14
107312c4 624aacc0    22   
107312dc 624aacc0    78
10731370 624aacc0    28

可以一眼看出哪些對象過大,這里我是為了演示而用,一般在項(xiàng)目開發(fā)中,我們都大概知道哪些對象可能會有內(nèi)存問題,比如:同步數(shù)據(jù)時的緩存對象。

!dumpobj 10731228 (查看對象詳情)

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)然還有很多其他很不錯的命令,這里我個人覺得這幾個比較常用,要想了解所有的命令可是在調(diào)試器中使用擴(kuò)展命令!help來查看所有的命令幫助。

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 " 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

7.簡單示例,常見的線上兩類問題

這里我們使用兩個小示例直觀的感受一下接觸.NET運(yùn)行時狀態(tài)的感受,盡管真實(shí)的問題可能比這個復(fù)雜很多,但是解決問題的思路是一樣的。

7.1.內(nèi)存問題(內(nèi)存偏高,內(nèi)存溢出)

服務(wù)程序最怕的性能問題之一就是內(nèi)存,當(dāng)內(nèi)存很高的情況下我們能夠通過對dump文件進(jìn)行查看,看哪些對象導(dǎo)致內(nèi)存一直高。當(dāng)內(nèi)存一直高的情況下就會容易導(dǎo)致內(nèi)存溢出異常,甚至是GC頻繁的執(zhí)行,當(dāng)GC一執(zhí)行就會導(dǎo)致服務(wù)并發(fā)下降,因?yàn)樗獟炱鹚械木€程(這里指的是服務(wù)器模式的.NETCLR,相對應(yīng)的還有工作站模式的.NETCLR)。

namespace OrderManager
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("app begin...");
            Console.ReadLine();
            List l = new List(); 
            for (int i = 0; i < 9999999; i++)
            {
                byte[] b = new byte[1000]; 
                l.Add(b); 
                Console.WriteLine(i);
            } 
            Console.WriteLine("end begin...");
            Console.ReadLine();
        }
    }
}

這一段代碼會一直分配內(nèi)存直到最后內(nèi)存溢出異常終止程序,我們在內(nèi)存比較的情況下來獲取一個dump文件,然后通過適當(dāng)?shù)拿顏矶ㄎ荒膫€對象占用內(nèi)存過高。

在不知道對象類型的情況下比較簡單的方式就是使用:0:000> !dumpheap -stat,命令,該命令的意思是統(tǒng)計(jì)當(dāng)前堆的信息,在這里就可以一眼找到哪個對象占用多少內(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[]

最后一個顯然內(nèi)存占用比較高,占了304844554 bite,如果你想在此情況下知道對象的內(nèi)存地址你就直接使用!dumpheap ,不帶任何參數(shù)。由于此命令會導(dǎo)致很多輸出,我這里就寫出輸出內(nèi)容了。通過!dumpheap 會得到內(nèi)存很高的對象地址,02d55368,這個地址就是System.Byte[]對象,為了找到對象在哪里分配的,我們需要使用!gcroot 02d55368,命令,查看對象的根在哪里。

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)問題。如果你還不死心的話可以使用!dumpobj 查看List對象。

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)試器啟動或者是附加進(jìn)程的方式的化,要記住切換到適當(dāng)?shù)木€程上才能看行。

7.2.線程問題(CPU過高,線程死鎖)

CPU過高也是線上比較棘手的問題之一,查看CPU過高的步驟一般分為兩步,查看線程的執(zhí)行時間,然后切換到線程上下文,執(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

測試線程ID為0的執(zhí)行時間比較大,我們需要切換到線程0上去執(zhí)行查看調(diào)用堆棧信息,~0s。

0:000> !ClrStack -a

0028f348 62b897f9 System.IO.TextWriter+SyncTextWriter.WriteLine(Int32)
  PARAMETERS:
    this () = 0x01b74258
    value =

0028f358 62a66313 System.Console.WriteLine(Int32)
  PARAMETERS:
    value =

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]

我們會發(fā)現(xiàn)在Main方法中有一個本地變量0x0028f380 ,保存的值是0x01b746c0,它就是指向剛才分配很多內(nèi)存的List對象。

線程死鎖比較復(fù)雜,這里只給我認(rèn)為比較簡單的命令,通過此命令可以一眼看出哪個線程持有了哪個鎖,目前在等待哪個鎖。

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

這是兩個鎖,也就是兩個對象同步塊。進(jìn)一步使用SOSEX.dll中的!dlk查看死鎖的自動化檢查信息。

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.

注意我加粗的那段話,檢測到死鎖。

8.獲取Dump文件時的重要注意事項(xiàng)

在獲取dump文件方面我也要分享一下重要的注意事項(xiàng)。如果獲取dump文件不正確的話是無法進(jìn)行分析的,會出現(xiàn)任何奇怪的問題。

第一個就是使用64位機(jī)器上的任務(wù)管理獲取32位進(jìn)程dump文件,這通常是發(fā)生在服務(wù)器上,由于服務(wù)器IIS默認(rèn)的啟動進(jìn)程方式是64位的,但是也有些情況下會變成32位的。

圖19:

.NET應(yīng)用程序調(diào)試—原理、工具、方法

如果進(jìn)程是以32位方式運(yùn)行的,那么這個時候獲取出來的dump文件是不好分析的,此時應(yīng)該使用調(diào)試器工具進(jìn)行dump的獲取。獲取出來的dump文件和分析機(jī)器上的調(diào)試器環(huán)境不一致的情況下會出現(xiàn)如下幾個錯誤。

圖20:

.NET應(yīng)用程序調(diào)試—原理、工具、方法

這個問題是未能加載正確版本的mscordacwks.dll .NETDAC調(diào)式組件。

圖21:

.NET應(yīng)用程序調(diào)試—原理、工具、方法

這個問題是當(dāng)前SOS.dll和.NET程序所使用的.NET版本不一致,這個問題的出現(xiàn)一般都是我們通過.load xx\xx\SOS.dll,手動方式加載的。

圖22:

.NET應(yīng)用程序調(diào)試—原理、工具、方法

這個問題出現(xiàn)有好幾種可能性,對常見的問題就是未能使用正確的方法或者工具獲取dump文件,導(dǎo)致dum文件獲取的機(jī)器和本地調(diào)試的機(jī)器整個環(huán)境不一致。

9.總結(jié)

本篇文章分享我對.NET應(yīng)用程序調(diào)試方面學(xué)習(xí)和實(shí)踐的一些經(jīng)驗(yàn),供廣大博友參考。如果想系統(tǒng)的學(xué)習(xí)一下這方面的知識可以參考《.NET高級調(diào)試》一書,此書非常底層,對.NET運(yùn)行時原理講的很透徹,可以作為深入學(xué)習(xí).NET的一門參考書。

作者:王清培

出處:http://wangqingpei557.blog.51cto.com/

本文版權(quán)歸作者和51CTO共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。


標(biāo)題名稱:.NET應(yīng)用程序調(diào)試—原理、工具、方法-創(chuàng)新互聯(lián)
本文鏈接:http://weahome.cn/article/dddcie.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部