今天是《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 }
Address的代码:
1 namespace Example_9_1_1 2 { 3 public class Address 4 { 5 public string Name { get; set; } 6 } 7 }
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 }
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 }
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】一下,就会到我们下的断点处暂停。效果如图:
调试代码: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 sjjohnson@pobox.com 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)
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]
红色部分是需要特别关注的。
四、总结
终于写完了,写作的过程是累并快乐着。学习过程真的没那么轻松,还好是自己比较喜欢这一行,否则真不知道自己能不能坚持下来。老话重谈,《高级调试》的这本书第一遍看,真的很晕,第二遍稍微好点,不学不知道,一学吓一跳,自己欠缺的很多。好了,不说了,不忘初心,继续努力,希望老天不要辜负努力的人。