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

閱讀目錄:

目前創(chuàng)新互聯(lián)已為上千的企業(yè)提供了網站建設、域名、網站空間、網站托管、企業(yè)網站設計、涪城網站維護等服務,公司將堅持客戶導向、應用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
  • 1.背景介紹

  • 2.基本原理(Windows調試工具箱、.NET調試擴展SOS.DLL、SOSEX.DLL)

    • 2.1.Windows調試工具箱

    • 2.2..NET調試擴展包,SOS.DLL、SOSEX.DLL

    • 2.3.調試系統(tǒng)的基本流程及架構(.NETDAC概念、mscordacwks.dll)

    • 2.4.VisualStudio中集成擴展調試(更加細粒度的調試程序)

  • 3.調試程序類型(客戶端程序、服務端程序)

  • 4.調試方式及場景

    • 4.1.本機調試(Attach Process,調試器啟動)

    • 4.2.不中斷調試或者稱事后調試(對Dump文件進行調試)

  • 5.一般調試步驟

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

    • 5.2.加載.NET程序擴展調試包(SOS.DLL、SOSEX.DLL)

    • 5.3.調試的三種命令類型(標準命令、元命令、擴展命令)

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

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

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

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

  • 8.獲取Dump文件時的重要注意事項

  • 9.總結

1.背景介紹

隨著應用程序的復雜度不斷上升,要想將好的設計思想穩(wěn)定的落實到線上,我們需要具備解決問題的能力。需要具備對運行時的錯誤進行定位且快速的解決它的能力。本篇文章我將分享一下我對.NET應用程序調試方面的學習和使用總結。

其實對調試程序的使用是不難的,關鍵是知道它的調試原理才行,因為調試一個程序或者dump文件,都需要了解一定的.NET調試的原理才行,比如你在附加到進程調試時在執(zhí)行某個SOS擴展命令是需要切換到指定線程上的,而調試dump文件就不需要,但是對Dump文件的分析有些SOS擴展命令是不能用的,類似這樣的問題,一旦出現(xiàn)你就一頭霧水,所以花點時間學習一下原理是有必要的。

2.基本原理(Windows調試工具箱、.NET調試擴展SOS.DLL、SOSEX.DLL)

在Windows平臺上調試應用程序選Windows調試工具箱,該工具箱包含了一套專門用來針對Windows進行很多復雜場景調試所需要的工具和組件。需要注意的是此工具箱是針對于非托管.NET平臺用的,意思就是說此工具箱的所有工具和組件默認是不能夠進行.NET應用程序調試的,只能用來對原生Windows程序進行調試。

那么.NET平臺也并不是有自己一套專用的調試工具箱,畢竟.NET還是屬于Windows平臺的,所以很大部分的運行時原理還是基于Windows的,要想在原生的調試器中對.NET這個具有虛擬運行時程序進行調試就需要專門的翻譯器才能夠執(zhí)行。SOS.DLL、SOSEX.DLL這兩個就是用來對.NET程序在Windows調試工具中起到翻譯作用的調試器擴展。簡單講就是,這兩個組件是.NET項目組專門開發(fā)出來用來對.NET應用程序進行方便調試用的,當然不用這兩個擴展也能調試.NET程序,只不過就會很困難,會被很多細節(jié)束縛住。有了這個調試擴展之后,我們就可以讓原生Windows調試器正確的翻譯出.NET相關概念。

圖1:(Windows調試工具執(zhí)行流程)

.NET應用程序調試—原理、工具、方法

所有對.NET程序發(fā)起的調試會話都要經過.NET調試擴展組件進行翻譯才行,也就是要使用.NET調試擴展的調試命令來調試.NET程序。上圖中,我們如果要想調試.NET程序就需要將.NET調試擴展組件加載到Windows調試工具中去,然后才能方便在Windows調試工具中使用。

2.1.Windows調試工具箱

Windows調試工具箱中包含了很多調試工具,都是用來輔助于我們進行方便調試用的。Windows調試工具箱分為兩個執(zhí)行版本,X86、X64這兩個版本是專門用來分析不同的運行時環(huán)境的,如果你的分析環(huán)境是32位的你就需要使用X86的版本,同理,如果是用64位的環(huán)境就需要使用X64的版本。

下載地址為:http://www.microsoft.com/whdc/devtools/debugging/default.aspx

記住選擇你需要的版本,建議你兩個版本都下載,因為你隨時需要針對Dump文件進行分析,而Dump文件是隨時都有可能是兩個版本。

Windows工具箱中的默認使用WinDbg.exe作為調試選,它是一個GUI程序。

圖2:(默認的Windows調試工具,WinDbg)

.NET應用程序調試—原理、工具、方法

安裝過后的菜單中就只有WinDbg作為調試選擇。

這里需要注意的是,當你啟動了WinDbg之后要留意程序的名字和標題,因為當你存在兩個版本的WinDbg時會容易搞錯,在調試時會有各種奇怪的問題出現(xiàn),當你找了半天之后結果發(fā)現(xiàn)是因為用錯了版本,那就正的無語了。

圖3:(注意運行WinDbg的環(huán)境版本)

.NET應用程序調試—原理、工具、方法

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

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

.NET應用程序調試—原理、工具、方法

這樣我們就可以很方便的attach到一個指定的進程進行調試。

Windows調試工具箱中有很多其他的工具,需要用的話可以使用cmd切換到當前安裝的目錄下:C:\Program Files\Debugging Tools for Windows (x86),或者你直接到工具的安裝目錄運行也行,這就看此工具是不是支持手動無參數(shù)啟動了。

2.2..NET調試擴展包,SOS.DLL、SOSEX.DLL

.NET調試擴展包分為兩個,一個是SOS.DLL,該擴展包是.NET平臺的一部分,屬于官方版本。而SOSEX.DLL是微軟的一名叫“Steve Johnson”軟件工程師開發(fā),屬于個人維護的,用來增強SOS.DLL功能的,在SOSEX.DLL有很多功能比較強大的擴展命令。

下載地址為:

32位:http://www.stevestechspot.com/downloads/sosex_32.zip

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

具體的幫助文檔可以查看該工程師的博客來了解詳情。這兩個版本用來調試不同環(huán)境的程序的,如果你的程序是運行在32位環(huán)境下,就用32位的SOSEX,同理,用在64位下就用64位SOSEX。

而SOS.DLL擴展包是跟著.NETFramework一起安裝的,地址位于:C:\Windows\Microsoft.NET\Framework\v4.0.30319。如果你是64位系統(tǒng)的話地址就是:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319。在這兩個地址下面都可以找到SOS.dll文件,不同的目錄下對應于調試不同機器類型的.NET程序。

有了這兩個擴展包之后就可以在WinDbg中對.NET程序進行分析了,具體使用我們后面會介紹。

2.3.調試系統(tǒng)的基本流程及架構(.NETDAC概念、mscordacwks.dll)

有一個很重要的原理我覺得很有必要講一下,就是.NETDAC概念。

其實.NETDAC也就是.NET Data Access .NET數(shù)據(jù)訪問層,這個是專門用來提供給SOS.DLL\SOSEXDLL或者其他調試擴展包使用的,所有的調試擴展組件必須通過這個DAC才能訪問到.NET運行時的數(shù)據(jù),所以在初次使用SOS的時候會經常碰見加載錯誤的mscordacwks.dll文件,此文件就是DAC的物理文件。

這個文件和SOS擴展文件一樣,都有這不同的版本,當加載不同類型的.NET程序時會使用到不同版本的mscordacwks.dll文件,當然大部分情況下此文件時自動加載的,只有出現(xiàn)你分析的文件與生成調試文件的環(huán)境不一致時才會出現(xiàn)頭疼的問題。

圖5:(mscordacwks.dll位置)

.NET應用程序調試—原理、工具、方法

當你知道這個組件是工作于此位置時,當出現(xiàn)跟它相關的錯誤提示時你就不需要擔心了,無非就是文件加載的位置或者版本不匹配而已。

調試器會話、調試器注入線程

還有一點我覺得也很有必要介紹的就是有關調試器如何調試.NET程序的,當我們在使用調試器啟動被調試程序或者將調試器附加到被調試進程時,其實調試器會注入一些線程到.NET程序中,讓調試線程與.NET程序原本的線程在一個.NET執(zhí)行環(huán)境中,這樣的目的是能夠起到最.NET程序在執(zhí)行時的控制,比如中斷執(zhí)行,設置斷點。當我們需要執(zhí)行某些跟線程上下文相關的擴展命令時就需要切換到正確的線程上去。

圖6:(調試器注入線程)

.NET應用程序調試—原理、工具、方法

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

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

.NET應用程序調試—原理、工具、方法

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

我們需要驗證它是否是注入了托管線程還是非托管線程。

圖8:(托管線程列表)

.NET應用程序調試—原理、工具、方法

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

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

.NET應用程序調試—原理、工具、方法

這樣我們就可以判斷出,調試器使用了ID位7的作為目前的調試會話線程。知道這些背后的原理很重要,當你在執(zhí)行某個調試命令時你就會發(fā)現(xiàn)此命令是否需要在.NET線程中執(zhí)行,還是說可以在調試器會話線程中執(zhí)行,一般dump類的命令都是可以遠程執(zhí)行的,也就是說在調試器會話中執(zhí)行,當需要跟蹤.NET線程內部過程時就需要切換到.NET線程上去執(zhí)行。

2.4.VisualStudio中集成擴展調試(更加細粒度的調試程序)

SOS擴展也是可以和VisualStudio進行集成的,這樣真的方便了我們調試一些性能要求比較高的程序,當程序運行一段時間后我們用VS附加到進程,然后查看一些重要的對象數(shù)據(jù),但是此時我們看不到.NET運行時的一些數(shù)據(jù),比如:對象的代齡,托管堆的大小,線程池的任務等。通過集成SOS擴展會讓我們對程序的運行時有了一個更加方便的跟蹤。

圖10:(打開本地代碼調試)

.NET應用程序調試—原理、工具、方法

設置斷點,然后在”即時窗口“(調試->窗口->即時)中加載擴展SOS.DLL。

圖11:(在VisualStudio2012中加載SOS.dll擴展).NET應用程序調試—原理、工具、方法

這樣的便利性大大提高我們在調試程序內存方面、線程方面的好處,我們可以適當?shù)淖鰤毫y試,然后Attach process,執(zhí)行SOS擴展命名來查看內存問題,當需要調試程序邏輯時在單步調式C#代碼,一舉兩得。

3.調試程序類型(客戶端程序、服務端程序)

.NET程序主要分為兩類,一類是客戶端程序,另一類是服務端程序。對于這兩類程序來說前者調試時基本上可以通過附加進程的方式進行調試,而對于服務端程序則不行,因為服務程序通常是運行在一個復雜的線上環(huán)境中,我們沒有任何權限或機會去接觸,此時是通過獲取進程的dump文件來進行分析。

客戶端程序也大概分為控制臺、Winform兩種,服務端程序都是基于ASP.NET框架,宿主與IIS進程中。

4.調試方式及場景

針對不同類型的程序及場景需要使用不同的方式進行調試,客戶端程序中的控制臺程序基本上可以通過在調試器中啟動的方式進行調試。如果是GUI程序則需要附加進程方式。服務端程序如果在條件允許下也是可以使用附加進程的方式進行調試的,但是這一般不太可能,因為一旦附加進程將block住所有的線程活動。

4.1.本機調試(Attach Process,調試器啟動)

本機調試可以直接在調試器中啟動程序,WinDbg打開后,在文件中有一個Open Executable,可以打開一個可執(zhí)行文件。如果是使用NTSD控制臺調試器,則需要在NTSD后面跟上程序的執(zhí)行路徑。

圖12:(ntsd.exe打開調試程序)

.NET應用程序調試—原理、工具、方法

同樣,在WinDbg中也有一個附加進程的選項,NTSD也是一樣,操作起來都比較簡單,需要注意的是當你對進程進行附加時要清楚此進程是多少位的,然后你需要選擇正確的調試器進行調試。

4.2.不中斷調試或者稱事后調試(對Dump文件進行調試)

在不能夠對被調試程序直接調試時我們就需要此程序的進程鏡像文件,此鏡像文件就是進程在某一個時刻的快照,通過分析這個快照,我們也是可以定位出問題的。首先我們需要使用適當?shù)墓ぞ邅慝@取進程的dump文件,操作系統(tǒng)本身的任務管理器就有這個功能,dump文件的存放位置默認在用戶信息臨時文件下面,比如:XXX\Users\Administrator\AppData\Local\Temp,獲取完dump文件后任務管理器會有提示路徑的。

圖13:(使用任務管理器獲取dump文件).NET應用程序調試—原理、工具、方法

圖14:

.NET應用程序調試—原理、工具、方法

使用任務管理器獲取dump文件固然很方便,但是有一個問題就是如果當前機器是64位的,并且你的進程是以32位方式運行的,那么此時你獲取出來的dump文件是64位的,當你通過32位的調試器無法進行分析,甚至會有各種其他的問題,這些問題就是因為獲取dump文件的機器環(huán)境和你預想的不一致。這個時候我們希望能夠通過很明了的方式來獲取dump文件,就是通過調試器來獲取dump文件。

通過調試器來獲取dump文件有很多好處,可以設置很多選項,包括只獲取進程的哪部分鏡像數(shù)據(jù)等。

先通過tlist.exe查看所有進程列表,會有一個進程ID號,有了ID號才能進行獲取。

圖15:(tlist、ntsd 進入到指定進程中)

.NET應用程序調試—原理、工具、方法

進入到ntsd調試器中,然后使用.dump/mf d:\order.dmp 命令獲取dump文件到D盤。

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

.NET應用程序調試—原理、工具、方法

此時我們就成功的獲取到了dump文件。

通過調試器獲取dump文件比較穩(wěn)定可靠,因為機器運行環(huán)境的不同,通過任務管理器獲取的dump文件會存在一些無法預知的問題,你并不清楚,當前任務管理器是使用哪個版本的環(huán)境輸出調試信息的。

有了dump文件之后就是通過調試工具打開就行了,WinDbg就有一個菜單專門打開dump文件的,Open Crash Dump。使用ntsd需要使用命令ntsd -z d:\order.dmp。

5.一般調試步驟

知道了調試的一些原理和工具之后我們來看一下調試的基本步驟,這些步驟都具體是指的什么意思,有哪些好處。

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

設置符號文件的目的是為了能夠在調試器中正確的對應到源代碼的位置和一些元數(shù)據(jù)信息。符號文件都是*.pdb文件名。符號文件分為公有和私有兩種,公有的都是公司公開出去用于幫助調試用的,而私有的是公司內部使用的,為什么要區(qū)分公有和私有,是為了防止逆向工程。

圖17:(設置符號文件路徑)

.NET應用程序調試—原理、工具、方法

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

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

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

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

.NET應用程序調試—原理、工具、方法

調試器會自動的將公有符號下載到你剛才設置的緩存目錄中。

5.2.加載.NET程序擴展調試包(SOS.DLL、SOSEX.DLL)

對.NET程序分析當然是需要加載SOS擴展了。加載SOS擴展有兩個命令可以使用,第一個是.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll,.load命令是要給出sos.dll絕對路徑的。第二個是.loadby sos modulename,.loadby 命令是可以根據(jù)已經加載的模塊名稱來加載SOS.dll擴展。使用第一個命令有一個問題就是,我們需要人工的判斷當前環(huán)境到底是需要什么版本的SOS擴展,而使用.loadby是可以根據(jù)已經加載的模塊來自動的查找對應的SOS擴展。

0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll

0:000> .loadby sos.dll clrjit

使用.loadby 命令很容易的就可以加載SOS擴展,而不需要自己去判斷當前程序是.NET什么版本的。

5.3.調試的三種命令類型(標準命令、元命令、擴展命令)

在使用調試器調試程序時,所要使用的命令主要分為三類。

第一類是標準命令,就是不帶任何符號開始的命令,比如:pb、lmvm。這一類命令是所有Windows調試工具箱中的調試工具通用的,不管你是使用ntsd還是winDbg都可以。

第二類命令是元命令,就是使用"."號開始的命令,這一類命令并不是在所有調試工具中通用的。第三類是擴展命令,擴展命令就是各個調試器擴展出來的命令,也就是以"!"開始的命令,如:!dumpheap -stat,!dumpstatcobjects。

6.調試擴展的幾個比較常用的命令(SOS.DLL、SOSEX.DLL)

當然這個純粹是我的個人感覺,排名不分先后。

!dumpheap -stat (查看托管堆統(tǒng)計信息)

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

可以一眼看出哪些對象過大,這里我是為了演示而用,一般在項目開發(fā)中,我們都大概知道哪些對象可能會有內存問題,比如:同步數(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

當然還有很多其他很不錯的命令,這里我個人覺得這幾個比較常用,要想了解所有的命令可是在調試器中使用擴展命令!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 <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

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

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

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

服務程序最怕的性能問題之一就是內存,當內存很高的情況下我們能夠通過對dump文件進行查看,看哪些對象導致內存一直高。當內存一直高的情況下就會容易導致內存溢出異常,甚至是GC頻繁的執(zhí)行,當GC一執(zhí)行就會導致服務并發(fā)下降,因為它要掛起所有的線程(這里指的是服務器模式的.NETCLR,相對應的還有工作站模式的.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();
        }
    }
}

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

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

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[]

最后一個顯然內存占用比較高,占了304844554 bite,如果你想在此情況下知道對象的內存地址你就直接使用!dumpheap ,不帶任何參數(shù)。由于此命令會導致很多輸出,我這里就寫出輸出內容了。通過!dumpheap 會得到內存很高的對象地址,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 命令的話,當你使用調試器啟動或者是附加進程的方式的化,要記住切換到適當?shù)木€程上才能看行。

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

CPU過高也是線上比較棘手的問題之一,查看CPU過高的步驟一般分為兩步,查看線程的執(zhí)行時間,然后切換到線程上下文,執(zhí)行!ClrStack -a,看當前線程在哪里工作,到底做什么操作呢。

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í)行查看調用堆棧信息,~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]

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

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

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

這是兩個鎖,也就是兩個對象同步塊。進一步使用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文件時的重要注意事項

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

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

圖19:

.NET應用程序調試—原理、工具、方法

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

圖20:

.NET應用程序調試—原理、工具、方法

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

圖21:

.NET應用程序調試—原理、工具、方法

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

圖22:

.NET應用程序調試—原理、工具、方法

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

9.總結

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

作者:王清培

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

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

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

本文名稱:.NET應用程序調試—原理、工具、方法-創(chuàng)新互聯(lián)
網頁鏈接:http://muchs.cn/article4/dddcie.html

成都網站建設公司_創(chuàng)新互聯(lián),為您提供關鍵詞優(yōu)化、商城網站、網站策劃、定制網站網站制作、微信小程序

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

手機網站建設