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 的代码:
https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gifhttps://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif 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的代码:
https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gifhttps://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif1 namespace Example_9_1_12 {3 public class Address4 {5 public string Name { get; set; }6 }7 }View Code
Person的代码:
https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gifhttps://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif 1 namespace Example_9_1_1 2 { 3 public class Person 4 { 5 public string Name { get; set; } 67 public Address Address { get; set; } = new Address() { Name = "河北省" }; 89 public void Show()10 {11 Console.WriteLine("Hello Person");12 }13 }14 }View Code
1.2、Example_9_1_2 项目的源码
https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gifhttps://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif 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(); 78 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 Student43 {44 }45 46 public class Person47 {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 102 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> lm2 start end module name3 00a20000 00a28000 Example_9_1_1 C (pdb symbols) C:\ProgramData\Dbg\sym\Example_9_1_1.pdb\...\Example_9_1_1.pdb4 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】一下,就会到我们下的断点处暂停。效果如图:
https://img2023.cnblogs.com/blog/1048776/202311/1048776-20231115112753248-892379690.png
接下来,我们在另外一个类中给一个方法直接下断点,看看效果怎么样,如截图:
https://img2023.cnblogs.com/blog/1048776/202311/1048776-20231115113225876-40104240.png
我们执行【!mbp】命令,后面跟类名,包含后缀名和行号。
1 0:000> !mbp Person.cs 132 The CLR has not yet been initialized in the process.3 Breakpoint resolution will be attempted when the CLR is initialized. 然后,我们【g】一下,就会到我们下的断点处暂停。效果如图:
https://img2023.cnblogs.com/blog/1048776/202311/1048776-20231115113709760-673144813.png
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 Person2Address MT Size3 029324c8 00ee4e28 16 4 5 Statistics:6 MT Count TotalSize Class Name7 00ee4e28 1 16 Example_9_1_1.Person8 Total 1 objects 上面列表中有了Person 对象地址和方法表的地址。
1 0:006> !mdt 029324c82 029324c8 (Example_9_1_1.Person)3 k__BackingField:NULL (System.String)4 k__BackingField:029324ec (Example_9_1_1.Address)5 0:006> !mdt 029324c8 -r6 029324c8 (Example_9_1_1.Person)7 k__BackingField:NULL (System.String)8 k__BackingField:029324ec (Example_9_1_1.Address)9 k__BackingField:029324d8 (System.String) Length=3, String="河北省" 如果我们想看【!mdt】如何使用,可以使用【!sosex.help !mdt】,这个输出太多,折叠了,可以自行查看。
https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gifhttps://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif1 0:006> !sosex.help !mdt2 SOSEX - Copyright 2007-2014 by Steve Johnson - http://www.stevestechspot.com/3 To report bugs or offer feedback about SOSEX, please email sjjohnson@pobox.com4 5 mdt6 Usage: !sosex.mdt [-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) 1415 Displays the fields of the specified object or type, optionally recursively. 1617 If -r is specified, fields will be displayed recursively down the object graph.The -r switch is ONLY18 applicable where an address is used, either by passing an address explicitly or when a param/local name19 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 2122 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 appending24 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. 2627 If you pass -e for collection expansion, you can also pass -start:index to specify a start index and/or -count:n28 to specify the number of elements to expand. 2930 Sample of collection expansion: 31 0:000> !mdt -e ht1 32 061ab360 (System.Collections.Hashtable) 33 Count = 2 34 061ab3a0 35 key:061ab3c8 (BOXED System.Int32) BOXEDVAL=0x7b 36 val:061ab3d4 (BOXED System.Int32) BOXEDVAL=0x4ce 37 061ab3b8 38 key:061ab3e0 (BOXED System.Int32) BOXEDVAL=0x1c8 39 val:061ab3ec (BOXED System.Int32) BOXEDVAL=0x11d0 4041 0:000> !mdt -e ht2 42 061ab3f8 (System.Collections.Hashtable) 43 Count = 2 44 061ab438 45 key:061ab078 (System.String: "456key") 46 val:061ab094 (System.String: "456value") 47 061ab444 48 key:061ab03c (System.String: "123key") 49 val:061ab058 (System.String: "123value") 5051 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. 5354 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. 5960 Frame info for the sample output below: 61 0:000> !mdv 62 Frame 0x0: (ConsoleTestApp.ConsoleTestApp.Main(System.String[])): 63 :args:0x2371804 (System.String[]) 64 :theGuid:{29b9c9c8-3751-42be-8c7a-8b92ff499588} VALTYPE (MT=6cd46c60, ADDR=002ff1bc) (System.Guid) 65 :d2:0x63718e0 (System.AppDomain) 66 :hMod:0x67280000 (System.Int32) 67 :dummy:0x23721a4 (System.String) STRVAL="This is "THE" way to test!" 68 :numThreads:0x2 (System.Int32) 69 :theDate:2008/01/02 03:04:05.678 VALTYPE (MT=6cd49e98, ADDR=002ff1ac) (System.DateTime) 70 :ts1:VALTYPE (MT=001e3198, ADDR=002ff1a4) (ConsoleTestApp.TestStruct) 71 :ft:0x637544c (ConsoleTestApp.FTEST) 72 :g1: 73 :g2: 74 :rnd:null (System.Random) 75 :threads:null (System.Threading.Thread[]) 76 :i:0x0 (System.Int32) 77 :ex:null (System.Exception) 78 :CS$0$0000:VALTYPE (MT=001e3198, ADDR=002ff198) (ConsoleTestApp.TestStruct) 79 :CS$4$0001:0x0 (System.Boolean) 808182 Sample output: 83 0:000> !mdt theGuid 84 002ff1bc (System.Guid) {29b9c9c8-3751-42be-8c7a-8b92ff499588} VALTYPE (MT=6cd46c60, ADDR=002ff1bc) 8586 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) 9192 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 -r101 063718e0 (System.Runtime.Remoting.Proxies.__TransparentProxy)102 _rp:063718b4 (System.Runtime.Remoting.Proxies.RemotingProxy)103 _tp:063718e0 (System.Runtime.Remoting.Proxies.__TransparentProxy)104 105 _identity:06371698 (System.Runtime.Remoting.Identity)106 _flags:0x4 (System.Int32)107 _tpOrObject:063718e0 (System.Runtime.Remoting.Proxies.__TransparentProxy)108 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=0x69B9B0127 _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 args147 02371804 (System.String[], Elements: 0)148 149 0:000> !mdt 001e3198 002ff1a4 -r150 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.exe10 ...(省略很多)11 02933d8c 0 8 encoding12 02933dac 0 6 stream13 0293421c 0 5 bytes14 02934234 0 5 chars15 0293424c 0 9 charCount16 0293426c 0 9 charIndex17 0293428c 0 9 byteCount18 029349fc 0 5 count19 02934a14 0 6 offset20 ---------------------------------------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 Value3 ---------------------------------------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_18 class: Example_9_1_1.Person9 Show() 【!mx】命令的参数是:模块名!*方法名*,* 星号表示模糊匹配。也可以通过【命名空间.类名.方法名】 来查找。
1 0:006> !mx Example_9_1_1!Example_9_1_1.Person.Show2 AppDomain 6e9fc7a8 (Shared Domain)3 ---------------------------------------------------------4 5 AppDomain 00920db8 (Example_9_1_1.exe)6 ---------------------------------------------------------7 module: Example_9_1_18 class: Example_9_1_1.Person9 Show() 红色标记的 Show 方法,在 Windbg 里是可以点击的,就相当于执行【!dumpmd】命令,查看方法的描述符。
1 0:006> !dumpmd 00ee4e082 Method Name:Example_9_1_1.Person.Show()3 Class: 00ee13404 MethodTable:00ee4e285 mdToken: 060000086 Module: 00ee40447 IsJitted: yes8 CodeAddr: 00f309d09 Transparency: Critical 【!name2ee】可以查找方法,但是不支持模糊匹配,要把完整路径,类名、方法名全部写出来才可以,否则找不到。
0:006> !name2ee Example_9_1_1!Example_9_1_1.Person.ShowModule: 00ee4044Assembly: Example_9_1_1.exeToken: 06000008MethodDesc:00ee4e08Name: 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.exe10 0:006> !name2ee Example_9_1_1!Example_9_1_1.Person.Sho*11 Module: 00ee404412 Assembly: Example_9_1_1.exe 【!mx】命令可以支持模糊查询,方法,类型都能查找,比如:我们能查找 Person 类型,Person 的成员都显示出来了。
1 0:006> !mx Example_9_1_1!*Person* 2 AppDomain 6e9fc7a8 (Shared Domain) 3 --------------------------------------------------------- 45 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> !mfrag2 Searching for pinned handles...3 FreeBlock Size NextObject MT Name4 -----------------------------------------------------------------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[] 5 6ceb4888 1 12 System.Security.HostSecurityManager 6 6ceb3d78 1 12 System.Collections.Generic.ObjectEqualityComparer`1[] 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.String13 6ceb2788 6 17748 System.Object[]14 Total 335 objects 这十个 free 块的详情如下:
1 0:006> !DumpHeap /d -mt 00919e18 2Address 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 Free10 03932328 00919e18 14 Free11 03932548 00919e18 14 Free12 03933558 00919e18 14 Free13 14 Statistics:15 MT Count TotalSize Class Name16 00919e18 10 116 Free17 Total 10 objects
2.6、我们使用【!dlk】命令检测死锁的问题。
调试代码:Example_9_1_2
这个项目的调试过程有些不同,我们可以直接运行我们的应用程序,然后打开 Windbg,通过【File】菜单选择【Attach to process】附加进程来调试程序。
程序运行的结果如图:
https://img2023.cnblogs.com/blog/1048776/202311/1048776-20231115141515095-1378679252.png 只会输出这两行文字,其他的无法输出,因为已经死锁了。剩下的就交给 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(Clr 4号线程拥有 Student 的锁)14 ...and is waiting for the lock on SyncBlock 016d0358 OBJ:033024c8(等待 Person 释放锁)15 CLR thread 0x3 holds the lock on SyncBlock 016d0358 OBJ:033024c8(CLR 3 号线程拥有 Person 的锁)16 ...and is waiting for the lock on SyncBlock 016d038c OBJ:033024d4(等待 Student 对象释放锁)17 CLR Thread 0x4 is waiting at System.Threading.Monitor.Enter(System.Object, Boolean ByRef)(+0x18 Native) 18 CLR Thread 0x3 is waiting at System.Threading.Monitor.Enter(System.Object, Boolean ByRef)(+0x18 Native) 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 010 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 ContextDomain Count Apt Exception10 0 1 2314 016a70d0 2a020 Preemptive03304F08:00000000 016a03b8 1 MTA 11 2 2 43b8 016b8088 2b220 Preemptive00000000:00000000 016a03b8 0 MTA (Finalizer) 12 3 3 43a8 016e6710 3029220 Preemptive03306444:00000000 016a03b8 1 MTA (Threadpool Worker) 13 4 4 3698 016e7738 3029220 Preemptive0330E6E0:00000000 016a03b8 1 MTA (Threadpool Worker) 然后我们切换到3和4号线程分别看一下线程栈的情况。
以下是3号线程的情况。
1 0:005> ~~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=0023ss=002bds=002bes=002bfs=0053gs=002b efl=00000202 5 ntdll!NtWaitForMultipleObjects+0xc: 6 76f8166c c21400 ret 14h 78 0:003> !clrstack 9 OS Thread Id: 0x43a8 (3)10 Child SP IP Call Site11 05c4f450 76f8166c 12 05c4f530 76f8166c 13 05c4f54c 76f8166c System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)14 05c4f5c8 6d298468 System.Threading.Monitor.Enter(System.Object, Boolean ByRef) 15 05c4f5d8 01560d03 Example_9_1_2.Program+c.b__2_0() 16 05c4f640 6d2fd4bb System.Threading.Tasks.Task.InnerInvoke() 17 05c4f64c 6d2fb731 System.Threading.Tasks.Task.Execute() 18 05c4f670 6d2fb6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) 19 05c4f674 6d298604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) 20 05c4f6e0 6d298537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) 21 05c4f6f4 6d2fb4b2 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef) 22 05c4f758 6d2fb357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) 23 05c4f768 6d2fb29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 24 05c4f76c 6d26eb7d System.Threading.ThreadPoolWorkQueue.Dispatch() 25 05c4f7bc 6d26e9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() 26 05c4f9dc 6e2bf036 我们看看 4 号线程栈的情况。
1 0:003> ~~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=0023ss=002bds=002bes=002bfs=0053gs=002b efl=00000202 5 ntdll!NtWaitForMultipleObjects+0xc: 6 76f8166c c21400 ret 14h 789 0:004> !clrstack10 OS Thread Id: 0x3698 (4)11 Child SP IP Call Site12 05e0ef00 76f8166c 13 05e0efe0 76f8166c 14 05e0effc 76f8166c System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)15 05e0f078 6d298468 System.Threading.Monitor.Enter(System.Object, Boolean ByRef) 16 05e0f088 01560b73 Example_9_1_2.Program+c.b__2_1() 17 05e0f0f0 6d2fd4bb System.Threading.Tasks.Task.InnerInvoke() 18 05e0f0fc 6d2fb731 System.Threading.Tasks.Task.Execute() 19 05e0f120 6d2fb6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) 20 05e0f124 6d298604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) 21 05e0f190 6d298537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) 22 05e0f1a4 6d2fb4b2 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef) 23 05e0f208 6d2fb357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) 24 05e0f218 6d2fb29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 25 05e0f21c 6d26eb7d System.Threading.ThreadPoolWorkQueue.Dispatch() 26 05e0f26c 6d26e9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() 27 05e0f48c 6e2bf036 红色部分是需要特别关注的。
四、总结
终于写完了,写作的过程是累并快乐着。学习过程真的没那么轻松,还好是自己比较喜欢这一行,否则真不知道自己能不能坚持下来。老话重谈,《高级调试》的这本书第一遍看,真的很晕,第二遍稍微好点,不学不知道,一学吓一跳,自己欠缺的很多。好了,不说了,不忘初心,继续努力,希望老天不要辜负努力的人。
来源:https://www.cnblogs.com/PatrickLiu/archive/2023/11/16/17831265.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页:
[1]