Net 高級調試之九:SOSEX 擴展命令介紹

一、介紹
    今天是《Net 高級調試》的第九篇文章。這篇文章設計的內容挺多的,比如:擴展的斷點支持,如何查找元數據,棧回溯,對象檢查,死鎖檢測等等,內容挺多的。功能特別強大,使用特別方便,但是需要說明一點,這些功能不是 SOS 的功能,是 SOSEX 的擴展功能,但是,這一系列功能只是支持 Net Framework,在 Net Core 跨平臺版本是不支持的。雖然這些都是基礎,如果這些掌握不好,以後的高級調試的道路,也不好走。當然了,第一次看視頻或者看書,是很迷糊的,不知道如何操作,還是那句老話,一遍不行,那就再來一遍,還不行,那就再來一遍,俗話說的好,書讀千遍,其意自現。
     如果在沒有說明的情況下,所有代碼的測試環境都是 Net Framewok 4.8,但是,有些項目需要使用 Net Core 的項目,我會在項目章節裏進行說明。好了,廢話不多說,開始我們今天的調試工作。

       調試環境我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
          操作系統:Windows Professional 10
          調試工具:Windbg Preview(可以去Microsoft Store 去下載)
          開發工具:Visual Studio 2022
          Net 版本:Net Framework 4.8
          CoreCLR源碼:源碼下載

二、基礎知識

    1、SOSEX調試擴展
        不得不說在調試 Net Framework 程序的時候,這個擴展調試組件的使用率是僅次於官方的 SOS 插件的,這個插件的一個特點就是能看到大量的命令是以 m 開頭的,對應於 非託管命令的託管命令的表示。

        這一篇文章只是介紹常用的幾個命令,如果大家想了解更多,可以使用【!sosex.help】命令,查看 SOSEX 插件的所有命令。

    2、幾個相當實用的擴展命令。

        2.1、!mbp 下斷點命令。
            相信大家都用過 !bp 命令,這個命令是可以對非託管函數下斷點,如果我們想對託管函數下斷點,就可以使用 【!mbp】 這個命令,它是【bp】命令託管形式,可以給託管方法下斷點。

            【!mbp】命令使用的時候要注意一點,命令後跟的是文件名,包含後綴名,如果類是獨立的類文件,就寫這個類文件的名稱就可以,如果是多個類包含在一個類文件裏,就寫包含多個類文件的名稱就可以。

        2.2、觀察對象佈局。
            一般我們使用【!do】命令觀察一個對象,但是這樣只能觀察到一個平面圖,不能查看到立體的對象,如果想更全面的瞭解一個對象,我們可以使用【!mdt】命令,立體對象是指:引用類型包含引用類型,具有多層,【!do】命令只能查看當前層對象的結構。

        2.3、查找託管堆中指定的字符串。
            這個在 dump 調試的過程中做託管的內存搜索很有用,使用【!strings -m:xxx】命令。

        2.4、搜索元數據。
            我們在以前的調試 Notepad 的 SaveFile 方法的時候,使用【x】命令,這裏也有對應的託管版本的命令,就是【!mx】,這個命令對於我們查找函數特別擁有。
        2.5、觀察 Free 塊。
            這是一個比較高級的命令,在分析託管堆碎片的時侯比較有用,能夠提高分析效率。我們可以使用【!mfrag】命令。

        2.6、死鎖檢測。
            大家都知道【死鎖】相互等待對方釋放鎖資源造成的,一旦出現了死鎖的問題,我們可以用手工的方式分析出來的,但是這樣費時費力,SOSEX 提供了一個檢測命令,就是【!dlk(deadlock)】命令,用來檢測死鎖問題。

三、調試過程
    廢話不多說,這一節是具體的調試操作的過程,又可以說是眼見爲實的過程,在開始之前,我還是要囉嗦兩句,這一節分爲兩個部分,第一部分是測試的源碼部分,沒有代碼,當然就談不上測試了,調試必須有載體。第二部分就是根據具體的代碼來證實我們學到的知識,是具體的眼見爲實。

    1、測試源碼
        1.1、Example_9_1_1 項目的源碼
            Program 的代碼:
 1 namespace Example_9_1_1
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             int a = 10;
 8             int b = 11;
 9             int c = 12;
10 
11             var person = new Person();
12 
13             person.Show();
14 
15             Console.ReadLine();
16         }
17     }
18 }
View Code

            Address的代碼:
1 namespace Example_9_1_1
2 {
3     public class Address
4     {
5         public string Name { get; set; }
6     }
7 }
View Code

            Person的代碼:
 1 namespace Example_9_1_1
 2 {
 3     public class Person
 4     {
 5         public string Name { get; set; }
 6 
 7         public Address Address { get; set; } = new Address() { Name = "河北省" };
 8 
 9         public void Show()
10         {
11             Console.WriteLine("Hello Person");
12         }
13     }
14 }
View Code

        1.2、Example_9_1_2 項目的源碼
 1 namespace Example_9_1_2
 2 {
 3     internal class Program
 4     {
 5         public static Person person=new Person();
 6         public static Student student = new Student();
 7 
 8         static void Main(string[] args)
 9         {
10             Task.Run(() =>
11             {
12                 lock (person)
13                 {
14                     Console.WriteLine($"tid={Environment.CurrentManagedThreadId},已經進入1111 Person");
15                     Thread.Sleep(1000);
16                     lock (student)
17                     {
18                         Console.WriteLine($"tid={Environment.CurrentManagedThreadId},已經進入1111 Student");
19                         Console.ReadLine();
20                     }
21                 }
22             });
23 
24             Task.Run(() =>
25             {
26                 lock(student)
27                 {
28                     Console.WriteLine($"tid={Environment.CurrentManagedThreadId},已經進入2222 Student");
29                     Thread.Sleep(1000);
30                     lock (person)
31                     {
32                         Console.WriteLine($"tid={Environment.CurrentManagedThreadId},已經進入2222 Person");
33                         Console.ReadLine();
34                     }
35                 }
36             });
37 
38             Console.ReadLine();
39         }
40     }
41 
42     public class Student
43     {
44     }
45 
46     public class Person
47     {
48     }
49 }
View Code

    2、眼見爲實
        項目的所有操作都是一樣的,所以就在這裏說明一下,但是每個測試例子,都需要重新啓動,並加載相應的應用程序,加載方法都是一樣的。流程如下:我們編譯項目,打開 Windbg,點擊【文件】----》【launch executable】附加程序,打開調試器的界面,程序已經處於中斷狀態。

        2.1、我們使用【!mbp】命令給託管函數下斷點(CLR沒加載就可以下斷點)。
            調試代碼:Example_9_1_1
            我們的任務:給 Program 類的 Main 方法的第十行:int b = 11;下斷點。
            我們進入 Windbg 界面,不需要運行,就可以直接下斷點,雖然這個時候 CLR 還沒有加載。【!mbp】命令後是程序文件名,必須包含文件擴展名,否則無效。
1 0:000> !mbp Program.cs 10
2 The CLR has not yet been initialized in the process.(CLR還沒有被初始化)
3 Breakpoint resolution will be attempted when the CLR is initialized.

            我們已經成功下了斷點,但是英文提示 CLR 還沒有被加載,我們使用【lm】命令查看一下加載模塊信息,顯示如下。

1 0:000> lm
2 start    end        module name
3 00a20000 00a28000   Example_9_1_1 C (pdb symbols)          C:\ProgramData\Dbg\sym\Example_9_1_1.pdb\...\Example_9_1_1.pdb
4 5bff0000 5c08f000   apphelp    (deferred)             
5 71520000 71572000   MSCOREE    (deferred)             
6 762f0000 76503000   KERNELBASE   (deferred)             
7 76ca0000 76d90000   KERNEL32   (deferred)             
8 76f10000 770b2000   ntdll      (pdb symbols)          C:\ProgramData\Dbg\sym\wntdll.pdb\DBC8C8F74C0E3696E951B77F0BB8569F1\wntdll.pdb

            的確如此,加載的模塊很少,根本沒又看到 CLR 的影子。
            然後,我們【g】一下,就會到我們下的斷點處暫停。效果如圖:
            

            接下來,我們在另外一個類中給一個方法直接下斷點,看看效果怎麼樣,如截圖:
            

             我們執行【!mbp】命令,後面跟類名,包含後綴名和行號。

1 0:000> !mbp Person.cs 13
2 The CLR has not yet been initialized in the process.
3 Breakpoint resolution will be attempted when the CLR is initialized.

             然後,我們【g】一下,就會到我們下的斷點處暫停。效果如圖:
             

        2.2、我們如何立體的觀察一個對象。
            調試代碼:Example_9_1_1
            我們來看看 Person 對象的結構,它包含 string 類型的 Name 字段,包含 Address 引用類型,Address 又包含 String 類型的 Name字段,我們如何立體的查看它的結構呢?
            我們進入到 Windbg 調試界面,如果有斷點存在,我們可以使用【bc *】命令將所有的斷點全部清除,然後再使用【g】命令,運行程序,會在【Console.ReadLine();】這樣代碼處暫停,然後我們點擊【Break】按鈕,就是調試程序了。
            我們現在託管堆中查找一下 Person 對象,有了對象,纔可以查看它的結構,使用【!dumpheap -type Person】命令查找。
1 0:006> !dumpheap -type Person
2  Address       MT     Size
3 029324c8 00ee4e28       16     
4 
5 Statistics:
6       MT    Count    TotalSize Class Name
7 00ee4e28        1           16 Example_9_1_1.Person
8 Total 1 objects

            上面列表中有了Person 對象地址和方法表的地址。

1 0:006> !mdt 029324c8
2 029324c8 (Example_9_1_1.Person)
3     <Name>k__BackingField:NULL (System.String)
4     <Address>k__BackingField:029324ec (Example_9_1_1.Address)
5 0:006> !mdt 029324c8 -r
6 029324c8 (Example_9_1_1.Person)
7     <Name>k__BackingField:NULL (System.String)
8     <Address>k__BackingField:029324ec (Example_9_1_1.Address)
9         <Name>k__BackingField:029324d8 (System.String) Length=3, String="河北省"

            如果我們想看【!mdt】如何使用,可以使用【!sosex.help !mdt】,這個輸出太多,摺疊了,可以自行查看。

  1 0:006> !sosex.help !mdt
  2 SOSEX - Copyright 2007-2014 by Steve Johnson - http://www.stevestechspot.com/
  3 To report bugs or offer feedback about SOSEX, please email [email protected]
  4 
  5 mdt
  6 Usage: !sosex.mdt [typename | paramname | localname | MT] [ADDR] [-r[:level]] [-e[:level]] [-start:index] [-count:n]
  7 
  8 Sample usages:
  9 "!sosex.mdt typeName"  (displays the names of the member fields of the specified type)
 10 "!sosex.mdt argName"   (displays the values of the fields of the specified parameter object. -r is valid)
 11 "!sosex.mdt localName" (displays the values of the fields of the specified local variable. -r is valid)
 12 "!sosex.mdt ADDR"      (displays the values of the fields of the object located at ADDR. -r is valid)
 13 "!sosex.mdt MT ADDR"   (displays the values of the fields of the value type specified by MT located at ADDR. -r is valid)
 14 
 15 Displays the fields of the specified object or type, optionally recursively.
 16 
 17 If -r is specified, fields will be displayed recursively down the object graph.  The -r switch is ONLY 
 18 applicable where an address is used, either by passing an address explicitly or when a param/local name 
 19 that resolves to an address is specified.  To limit the levels of the graph that are displayed, append the desired
 20 maximum level, preceded by a colon.  e.g. !mdt myVar -r:3
 21 
 22 The -e switch causes certain collection types to be expanded.  The currently expandable collection types are:
 23 Array, ArrayList, List, Hashtable and Dictionary.  You can also specify a maximum expansion level by appending 
 24 the desired maximum level, preceded by a colon.  e.g. !mdt myColl -e:3.  The minimum (and default) level is 2, 
 25 which means that the collection is expanded to show each element address and it's top level fields.
 26 
 27 If you pass -e for collection expansion, you can also pass -start:index to specify a start index and/or -count:n 
 28 to specify the number of elements to expand.
 29 
 30 Sample of collection expansion:
 31 0:000> !mdt -e ht1
 32 061ab360 (System.Collections.Hashtable)
 33 Count = 2
 34 [0] 061ab3a0
 35     key:061ab3c8 (BOXED System.Int32) BOXEDVAL=0x7b
 36     val:061ab3d4 (BOXED System.Int32) BOXEDVAL=0x4ce
 37 [1] 061ab3b8
 38     key:061ab3e0 (BOXED System.Int32) BOXEDVAL=0x1c8
 39     val:061ab3ec (BOXED System.Int32) BOXEDVAL=0x11d0
 40 
 41 0:000> !mdt -e ht2
 42 061ab3f8 (System.Collections.Hashtable)
 43 Count = 2
 44 [0] 061ab438
 45     key:061ab078 (System.String: "456key")
 46     val:061ab094 (System.String: "456value")
 47 [1] 061ab444
 48     key:061ab03c (System.String: "123key")
 49     val:061ab058 (System.String: "123value")
 50 
 51 The scope frame defaults to zero, but may be overridden via the !mframe command.  IMPORTANT:  The current
 52 scope frame corresponds to the frames listed by the !mk command.
 53 
 54 IMPORTANT NOTE: Sosex distinguishes between numeric and non-numeric strings in order to determine whether an
 55 address or a type/arg/local name is being passed in ambiguous circumstances.  If you want to pass a string value
 56 that could be interpreted as an expression by the debugger, enclose the name in single-quotes.  For example, for
 57 a local named d2, call: !mdt 'd2'.  If the name were not quoted in this circumstance, !mdt would attempt to display
 58 an object located at address 0xd2.
 59 
 60 Frame info for the sample output below:
 61 0:000> !mdv
 62 Frame 0x0: (ConsoleTestApp.ConsoleTestApp.Main(System.String[])):
 63 [A0]:args:0x2371804 (System.String[])
 64 [L0]:theGuid:{29b9c9c8-3751-42be-8c7a-8b92ff499588} VALTYPE (MT=6cd46c60, ADDR=002ff1bc) (System.Guid)
 65 [L1]:d2:0x63718e0 (System.AppDomain)
 66 [L2]:hMod:0x67280000 (System.Int32)
 67 [L3]:dummy:0x23721a4 (System.String) STRVAL="This is "THE" way to test!"
 68 [L4]:numThreads:0x2 (System.Int32)
 69 [L5]:theDate:2008/01/02 03:04:05.678 VALTYPE (MT=6cd49e98, ADDR=002ff1ac) (System.DateTime)
 70 [L6]:ts1:VALTYPE (MT=001e3198, ADDR=002ff1a4) (ConsoleTestApp.TestStruct)
 71 [L7]:ft:0x637544c (ConsoleTestApp.FTEST)
 72 [L8]:g1:<Retrieval mechanism not implemented. The variable type may be a generic type.>
 73 [L9]:g2:<Retrieval mechanism not implemented. The variable type may be a generic type.>
 74 [L10]:rnd:null (System.Random)
 75 [L11]:threads:null (System.Threading.Thread[])
 76 [L12]:i:0x0 (System.Int32)
 77 [L13]:ex:null (System.Exception)
 78 [L14]:CS$0$0000:VALTYPE (MT=001e3198, ADDR=002ff198) (ConsoleTestApp.TestStruct)
 79 [L15]:CS$4$0001:0x0 (System.Boolean)
 80 
 81 
 82 Sample output:
 83 0:000> !mdt theGuid
 84 002ff1bc (System.Guid) {29b9c9c8-3751-42be-8c7a-8b92ff499588} VALTYPE (MT=6cd46c60, ADDR=002ff1bc)
 85 
 86 0:000> !mdt ft
 87 0637544c (ConsoleTestApp.FTEST)
 88    _s1:06375460 (System.String: "String 1")
 89    _s2:06375484 (System.String: "String 2")
 90    _arr:063755c4 (System.String[,,], Elements: 8)
 91 
 92 0:000> !mdt 63718e0
 93 063718e0 (System.Runtime.Remoting.Proxies.__TransparentProxy)
 94    _rp:063718b4 (System.Runtime.Remoting.Proxies.RemotingProxy)
 95    _stubData:023725dc (BOXED System.IntPtr) VALTYPE (MT=6cd6b114, ADDR=023725e0)
 96    _pMT:6cd6902c (System.IntPtr)
 97    _pInterfaceMT:00000000 (System.IntPtr)
 98    _stub:6d601e70 (System.IntPtr)
 99 
100 0:000> !mdt 63718e0 -r
101 063718e0 (System.Runtime.Remoting.Proxies.__TransparentProxy)
102    _rp:063718b4 (System.Runtime.Remoting.Proxies.RemotingProxy)
103       _tp:063718e0 (System.Runtime.Remoting.Proxies.__TransparentProxy)
104          <RECURSIVE>
105       _identity:06371698 (System.Runtime.Remoting.Identity)
106          _flags:0x4 (System.Int32)
107          _tpOrObject:063718e0 (System.Runtime.Remoting.Proxies.__TransparentProxy)
108             <RECURSIVE>
109          _ObjURI:02376858 (System.String: "/f578dbe2_cf0c_4e30_882b_14126f0b1654/kq_om1xc5idnrbhnqnr77cs0_1.rem")
110          _URL:NULL (System.String)
111          _objRef:023769e0 (System.Runtime.Remoting.ObjRef)
112             uri:02376858 (System.String: "/f578dbe2_cf0c_4e30_882b_14126f0b1654/kq_om1xc5idnrbhnqnr77cs0_1.rem")
113             typeInfo:02376cc4 (System.Runtime.Remoting.TypeInfo)
114                serverType:02377ea4 (System.String: "System.AppDomain, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
115                serverHierarchy:NULL (System.Object[])
116                interfacesImplemented:0237808c (System.String[], Elements: 2)
117             envoyInfo:NULL (System.Runtime.Remoting.IEnvoyInfo)
118             channelInfo:0237872c (System.Runtime.Remoting.ChannelInfo)
119                channelData:02378738 (System.Object[], Elements: 1)
120             objrefFlags:0x0 (System.Int32)
121             srvIdentity:023769fc (System.Runtime.InteropServices.GCHandle) VALTYPE (MT=6cd6bcb8, ADDR=023769fc)
122                m_handle:004e11ec (System.IntPtr)
123             domainID:0x2 (System.Int32)
124          _channelSink:06371890 (System.Runtime.Remoting.Channels.CrossAppDomainSink)
125             _xadData:023753c0 (System.Runtime.Remoting.Channels.CrossAppDomainData)
126                _ContextID:023753e0 (BOXED System.Int32) BOXEDVAL=0x69B9B0
127                _DomainID:0x2 (System.Int32)
128                _processGuid:023750bc (System.String: "81174e11_728a_4211_a674_f6f4d79419ba")
129          _envoyChain:063718a8 (System.Runtime.Remoting.Messaging.EnvoyTerminatorSink)
130          _dph:NULL (System.Runtime.Remoting.Contexts.DynamicPropertyHolder)
131          _lease:NULL (System.Runtime.Remoting.Lifetime.Lease)
132       _serverObject:NULL (System.MarshalByRefObject)
133       _flags:0x3 (System.Runtime.Remoting.Proxies.RealProxyFlags)
134       _srvIdentity:063718d0 (System.Runtime.InteropServices.GCHandle) VALTYPE (MT=6cd6bcb8, ADDR=063718d0)
135          m_handle:004e11ec (System.IntPtr)
136       _optFlags:0x7000000 (System.Int32)
137       _domainID:0x2 (System.Int32)
138       _ccm:NULL (System.Runtime.Remoting.Messaging.ConstructorCallMessage)
139       _ctorThread:0x0 (System.Int32)
140    _stubData:023725dc (BOXED System.IntPtr) VALTYPE (MT=6cd6b114, ADDR=023725e0)
141       m_value:ffffffff (System.UIntPtr)
142    _pMT:6cd6902c (System.IntPtr)
143    _pInterfaceMT:00000000 (System.IntPtr)
144    _stub:6d601e70 (System.IntPtr)
145 
146 0:000> !mdt args
147 02371804 (System.String[], Elements: 0)
148 
149 0:000> !mdt 001e3198 002ff1a4 -r
150 002ff1a4 (ConsoleTestApp.TestStruct) VALTYPE (MT=001e3198, ADDR=002ff1a4)
151    Member1:0x4D2 (System.UInt32)
152    Member2:0x162E (System.UInt32)
View Code

        2.3、使用【!strings】命令在託管堆中查找字符串。
            調試代碼:Example_9_1_1
            我們進入到 Windbg 調試界面,如果有斷點存在,我們可以使用【bc *】命令將所有的斷點全部清除,然後再使用【g】命令,運行程序,會在【Console.ReadLine();】這樣代碼處暫停,然後我們點擊【Break】按鈕,就是調試程序了。
            如果我們直接使用【!strings】命令,沒有任何參數,會把託管堆中的所有字符串全部打印出來。
 1 0:006> !strings
 2 Address    Gen    Length   Value
 3 ---------------------------------------
 4 02931228    0          0   
 5 02931254    0         94   E:\Visual Studio 2022\...\Example_9_1_1\bin\Debug\
 6 02931320    0        118   E:\Visual Studio 2022\...\Example_9_1_1\bin\Debug\Example_9_1_1.exe.Config
 7 0293148c    0          4   true
 8 029314a4    0         32   PARTIAL_TRUST_VISIBLE_ASSEMBLIES
 9 02931538    0        111   E:\Visual Studio 2022\..\Example_9_1_1\bin\Debug\Example_9_1_1.exe
10 ...(省略很多)
11 02933d8c    0          8   encoding
12 02933dac    0          6   stream
13 0293421c    0          5   bytes
14 02934234    0          5   chars
15 0293424c    0          9   charCount
16 0293426c    0          9   charIndex
17 0293428c    0          9   byteCount
18 029349fc    0          5   count
19 02934a14    0          6   offset
20 ---------------------------------------
21 167 strings

            上面的內容太多,我省略了,沒必要全部顯示出來。

 1 0:006> !strings -m:河北*
 2 Address    Gen    Length   Value
 3 ---------------------------------------
 4 029324d8    0          3   河北省
 5 ---------------------------------------
 6 1 matching string
 7 0:006> !strings /m:河北*
 8 Address    Gen    Length   Value
 9 ---------------------------------------
10 029324d8    0          3   河北省
11 ---------------------------------------
12 1 matching string

            m 前的可以是英文橫線-,也可以是英文/斜線,可以使用 * 進行模糊匹配。有一點要注意:值是:河北省,可以通過 -m:河北*,這個參數是有效的,如果是 -m:河北省* 就是找不到的,體會使用的細節吧,完整字符串不需要增加 * 星號,增加時找不到的。

1 0:006> !strings /m:河北省
2 Address    Gen    Length   Value
3 ---------------------------------------
4 029324d8    0          3   河北省
5 ---------------------------------------
6 1 matching string

        2.4、我們在託管堆中使用【!mx】命令查找 Person 類型的 Show 方法。
            調試代碼:Example_9_1_1
            我們進入到 Windbg 調試界面,如果有斷點存在,我們可以使用【bc *】命令將所有的斷點全部清除,然後再使用【g】命令,運行程序,會在【Console.ReadLine();】這樣代碼處暫停,然後我們點擊【Break】按鈕,就是調試程序了。
1 0:006> !mx Example_9_1_1!*Show*
2 AppDomain 6e9fc7a8 (Shared Domain)
3 ---------------------------------------------------------
4 
5 AppDomain 00920db8 (Example_9_1_1.exe)
6 ---------------------------------------------------------
7 module: Example_9_1_1
8   class: Example_9_1_1.Person
9     Show()

            【!mx】命令的參數是:模塊名!*方法名*,* 星號表示模糊匹配。也可以通過【命名空間.類名.方法名】 來查找。

1 0:006> !mx Example_9_1_1!Example_9_1_1.Person.Show
2 AppDomain 6e9fc7a8 (Shared Domain)
3 ---------------------------------------------------------
4 
5 AppDomain 00920db8 (Example_9_1_1.exe)
6 ---------------------------------------------------------
7 module: Example_9_1_1
8   class: Example_9_1_1.Person
9     Show()

            紅色標記的 Show 方法,在 Windbg 裏是可以點擊的,就相當於執行【!dumpmd】命令,查看方法的描述符。

1 0:006> !dumpmd 00ee4e08
2 Method Name:  Example_9_1_1.Person.Show()
3 Class:        00ee1340
4 MethodTable:  00ee4e28
5 mdToken:      06000008
6 Module:       00ee4044
7 IsJitted:     yes
8 CodeAddr:     00f309d0
9 Transparency: Critical

            【!name2ee】可以查找方法,但是不支持模糊匹配,要把完整路徑,類名、方法名全部寫出來纔可以,否則找不到。

0:006> !name2ee Example_9_1_1!Example_9_1_1.Person.Show
Module:      00ee4044
Assembly:    Example_9_1_1.exe
Token:       06000008
MethodDesc:  00ee4e08
Name:        Example_9_1_1.Person.Show()
JITTED Code Address: 00f309d0

            省略模式是找不到的。

 1 0:006> !name2ee Example_9_1_1!*Show*
 2 Module:      00ee4044
 3 Assembly:    Example_9_1_1.exe
 4 0:006> !name2ee Example_9_1_1!Example_9_1_1.Show*
 5 Module:      00ee4044
 6 Assembly:    Example_9_1_1.exe
 7 0:006> !name2ee Example_9_1_1!Example_9_1_1.Person.S*
 8 Module:      00ee4044
 9 Assembly:    Example_9_1_1.exe
10 0:006> !name2ee Example_9_1_1!Example_9_1_1.Person.Sho*
11 Module:      00ee4044
12 Assembly:    Example_9_1_1.exe

            【!mx】命令可以支持模糊查詢,方法,類型都能查找,比如:我們能查找 Person 類型,Person 的成員都顯示出來了。

 1 0:006> !mx Example_9_1_1!*Person*
 2 AppDomain 6e9fc7a8 (Shared Domain)
 3 ---------------------------------------------------------
 4 
 5 AppDomain 00920db8 (Example_9_1_1.exe)
 6 ---------------------------------------------------------
 7 module: Example_9_1_1
 8   class: Example_9_1_1.Person
 9     get_Name()
10     set_Name(string)
11     get_Address()
12     set_Address(Example_9_1_1.Address)
13     Show()
14     .ctor()
15     k__BackingField {fieldtype: string} 
16     k__BackingField {fieldtype: Example_9_1_1.Address} 

        2.5、觀察 Free 塊。
            調試代碼:Example_9_1_1
            我們進入到 Windbg 調試界面,如果有斷點存在,我們可以使用【bc *】命令將所有的斷點全部清除,然後再使用【g】命令,運行程序,會在【Console.ReadLine();】這樣代碼處暫停,然後我們點擊【Break】按鈕,就是調試程序了。
            
1 0:006> !mfrag
2 Searching for pinned handles...
3 FreeBlock          Size   NextObject   MT         Name
4 -----------------------------------------------------------------
5 03931010             14   03931020     6ceb2788   System.Object[] (Pinned Handle @ 00ec13fc)
6 03932328             14   03932338     6ceb2788   System.Object[] (Pinned Handle @ 00ec13f4)
7 03932548             14   03932558     6ceb2788   System.Object[] (Pinned Handle @ 00ec13f0)
8 03933558             14   03933568     6ceb2788   System.Object[] (Pinned Handle @ 00ec13ec)
9 4 free blocks, 56 bytes

            如果大家不知道什麼是 free 塊,我們可以使用【!dumpheap -stat】命令,查看一下託管堆的統計情況。

 1 0:006> !dumpheap -stat
 2 Statistics:
 3       MT    Count    TotalSize Class Name
 4 6ceb5468        1           12 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]
 5 6ceb4888        1           12 System.Security.HostSecurityManager
 6 6ceb3d78        1           12 System.Collections.Generic.ObjectEqualityComparer`1[[System.Type, mscorlib]]
 7 ...
 8 00919e18       10          116      Free(這就是 free 塊,我們可以點擊前面的地址)
 9 ...
10 6ceb5c40        3          806 System.Byte[]
11 6ceb2c60       10         2988 System.Char[]
12 6ceb24e4      167         5906 System.String
13 6ceb2788        6        17748 System.Object[]
14 Total 335 objects

            這十個 free 塊的詳情如下:

 1 0:006> !DumpHeap /d -mt 00919e18
 2  Address       MT     Size
 3 02931000 00919e18       10 Free
 4 0293100c 00919e18       10 Free
 5 02931018 00919e18       10 Free
 6 02931fd0 00919e18       10 Free
 7 02933df4 00919e18       10 Free
 8 03931000 00919e18       10 Free
 9 03931010 00919e18       14 Free
10 03932328 00919e18       14 Free
11 03932548 00919e18       14 Free
12 03933558 00919e18       14 Free
13 
14 Statistics:
15       MT    Count    TotalSize Class Name
16 00919e18       10          116      Free
17 Total 10 objects

        2.6、我們使用【!dlk】命令檢測死鎖的問題。
            調試代碼:Example_9_1_2
            這個項目的調試過程有些不同,我們可以直接運行我們的應用程序,然後打開 Windbg,通過【File】菜單選擇【Attach to process】附加進程來調試程序。
            程序運行的結果如圖:
            

            只會輸出這兩行文字,其他的無法輸出,因爲已經死鎖了。剩下的就交給 Windbg 吧,Windbg 附加進程完畢,直接輸入【!dlk】命令來檢查。

 1 0:005> !dlk
 2 Examining SyncBlocks...
 3 Scanning for ReaderWriterLock instances...
 4 Scanning for holders of ReaderWriterLock locks...
 5 Scanning for ReaderWriterLockSlim instances...
 6 Scanning for holders of ReaderWriterLockSlim locks...
 7 Examining CriticalSections...
 8 Scanning for threads waiting on SyncBlocks...
 9 Scanning for threads waiting on ReaderWriterLock locks...
10 Scanning for threads waiting on ReaderWriterLocksSlim locks...
11 Scanning for threads waiting on CriticalSections...
12 *DEADLOCK DETECTED*
13 CLR thread 0x4 holds the lock on SyncBlock 016d038c OBJ:033024d4[Example_9_1_2.Student](Clr 4號線程擁有 Student 的鎖)
14 ...and is waiting for the lock on SyncBlock 016d0358 OBJ:033024c8[Example_9_1_2.Person](等待 Person 釋放鎖)
15 CLR thread 0x3 holds the lock on SyncBlock 016d0358 OBJ:033024c8[Example_9_1_2.Person](CLR 3 號線程擁有 Person 的鎖)
16 ...and is waiting for the lock on SyncBlock 016d038c OBJ:033024d4[Example_9_1_2.Student](等待 Student 對象釋放鎖)
17 CLR Thread 0x4 is waiting at System.Threading.Monitor.Enter(System.Object, Boolean ByRef)(+0x18 Native) [f:\dd\ndp\clr\src\BCL\system\threading\monitor.cs @ 62,9]
18 CLR Thread 0x3 is waiting at System.Threading.Monitor.Enter(System.Object, Boolean ByRef)(+0x18 Native) [f:\dd\ndp\clr\src\BCL\system\threading\monitor.cs @ 62,9]
19 
20 
21 1 deadlock detected.(檢測到死鎖)

            如果我們通過手工檢查,需要檢測同步塊索引。

 1 0:005> !syncblk
 2 Index         SyncBlock MonitorHeld Recursion Owning Thread Info          SyncBlock Owner
 3     8 016d0358            3         1 016e6710 43a8   3   033024c8 Example_9_1_2.Person
 4     9 016d038c            3         1 016e7738 3698   4   033024d4 Example_9_1_2.Student
 5 -----------------------------
 6 Total           9
 7 CCW             1
 8 RCW             2
 9 ComClassFactory 0
10 Free            0

            我們繼續使用【!t】命令,查看一下線程的信息。

 1 0:005> !t
 2 ThreadCount:      4
 3 UnstartedThread:  0
 4 BackgroundThread: 3
 5 PendingThread:    0
 6 DeadThread:       0
 7 Hosted Runtime:   no
 8                                                                          Lock  
 9        ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
10    0    1 2314 016a70d0     2a020 Preemptive  03304F08:00000000 016a03b8 1     MTA 
11    2    2 43b8 016b8088     2b220 Preemptive  00000000:00000000 016a03b8 0     MTA (Finalizer) 
12    3    3 43a8 016e6710   3029220 Preemptive  03306444:00000000 016a03b8 1     MTA (Threadpool Worker) 
13    4    4 3698 016e7738   3029220 Preemptive  0330E6E0:00000000 016a03b8 1     MTA (Threadpool Worker) 

            然後我們切換到3和4號線程分別看一下線程棧的情況。
            以下是3號線程的情況。

 1 0:005> ~~[43a8]s
 2 eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=00000001 edi=00000001
 3 eip=76f8166c esp=05c4f0f8 ebp=05c4f288 iopl=0         nv up ei pl nz na po nc
 4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
 5 ntdll!NtWaitForMultipleObjects+0xc:
 6 76f8166c c21400          ret     14h
 7 
 8 0:003> !clrstack
 9 OS Thread Id: 0x43a8 (3)
10 Child SP       IP Call Site
11 05c4f450 76f8166c [GCFrame: 05c4f450] 
12 05c4f530 76f8166c [GCFrame: 05c4f530] 
13 05c4f54c 76f8166c [HelperMethodFrame_1OBJ: 05c4f54c] System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
14 05c4f5c8 6d298468 System.Threading.Monitor.Enter(System.Object, Boolean ByRef) [f:\dd\ndp\clr\src\BCL\system\threading\monitor.cs @ 62]
15 05c4f5d8 01560d03 Example_9_1_2.Program+c.b__2_0() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_9_1_2\Program.cs @ 20]
16 05c4f640 6d2fd4bb System.Threading.Tasks.Task.InnerInvoke() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2884]
17 05c4f64c 6d2fb731 System.Threading.Tasks.Task.Execute() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2498]
18 05c4f670 6d2fb6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2861]
19 05c4f674 6d298604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 980]
20 05c4f6e0 6d298537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 928]
21 05c4f6f4 6d2fb4b2 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2827]
22 05c4f758 6d2fb357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2767]
23 05c4f768 6d2fb29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2704]
24 05c4f76c 6d26eb7d System.Threading.ThreadPoolWorkQueue.Dispatch() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 820]
25 05c4f7bc 6d26e9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 1161]
26 05c4f9dc 6e2bf036 [DebuggerU2MCatchHandlerFrame: 05c4f9dc] 

            我們看看 4 號線程棧的情況。

 1 0:003> ~~[3698]s
 2 eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=00000001 edi=00000001
 3 eip=76f8166c esp=05e0eba8 ebp=05e0ed38 iopl=0         nv up ei pl nz na po nc
 4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
 5 ntdll!NtWaitForMultipleObjects+0xc:
 6 76f8166c c21400          ret     14h
 7 
 8 
 9 0:004> !clrstack
10 OS Thread Id: 0x3698 (4)
11 Child SP       IP Call Site
12 05e0ef00 76f8166c [GCFrame: 05e0ef00] 
13 05e0efe0 76f8166c [GCFrame: 05e0efe0] 
14 05e0effc 76f8166c [HelperMethodFrame_1OBJ: 05e0effc] System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
15 05e0f078 6d298468 System.Threading.Monitor.Enter(System.Object, Boolean ByRef) [f:\dd\ndp\clr\src\BCL\system\threading\monitor.cs @ 62]
16 05e0f088 01560b73 Example_9_1_2.Program+c.b__2_1() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_9_1_2\Program.cs @ 34]
17 05e0f0f0 6d2fd4bb System.Threading.Tasks.Task.InnerInvoke() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2884]
18 05e0f0fc 6d2fb731 System.Threading.Tasks.Task.Execute() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2498]
19 05e0f120 6d2fb6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2861]
20 05e0f124 6d298604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 980]
21 05e0f190 6d298537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 928]
22 05e0f1a4 6d2fb4b2 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2827]
23 05e0f208 6d2fb357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2767]
24 05e0f218 6d2fb29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2704]
25 05e0f21c 6d26eb7d System.Threading.ThreadPoolWorkQueue.Dispatch() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 820]
26 05e0f26c 6d26e9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 1161]
27 05e0f48c 6e2bf036 [DebuggerU2MCatchHandlerFrame: 05e0f48c] 

            紅色部分是需要特別關注的


四、總結
    終於寫完了,寫作的過程是累並快樂着。學習過程真的沒那麼輕鬆,還好是自己比較喜歡這一行,否則真不知道自己能不能堅持下來。老話重談,《高級調試》的這本書第一遍看,真的很暈,第二遍稍微好點,不學不知道,一學嚇一跳,自己欠缺的很多。好了,不說了,不忘初心,繼續努力,希望老天不要辜負努力的人。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章