一、介绍
    这是我的《Advanced .Net Debugging》这个系列的第四篇文章。今天这篇文章的标题虽然叫做“基本调试任务”,但是这章的内容还是挺多的。由于内容太多,故原书的第三章内容我分两篇文章来写。上一篇我们了解了一些调试技巧,比如:单步调试、下断点、过程调试等,这篇文章主要涉及的内容是对象的转储,内存的转储,值类型的转储,引用类型的转储、数组的转储、异常的转储等。第一次说到“转储”,可能大家不知道什么意思,其实就是把我们想要的内容输出出来或者说是打印出来,方便我们分析问题。SOSEX扩展的内容我就省略了,因为我这个系列的是基于 .NET 8 版本来写的,SOSEX是基于 .NET Framework 版本的,如果大家想了解其内容,可以查看我的【高级调试】系列(我当前写的是《Advanced .Net Debugging》系列,是不一样的),当然,也可以看原书。【高级调试】系列主要是集中在 .NET Framework 版本的。如果我们想成为一名合格程序员,这些调试技巧都是必须要掌握的。
    如果在没有说明的情况下,所有代码的测试环境都是 Net 8.0,如果有变动,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。

     调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
        操作系统:Windows Professional 10
        调试工具:Windbg Preview(Debugger Client:1.2306.1401.0,Debugger engine:10.0.25877.1004)、NTSD(10.0.22621.2428 AMD64)
        下载地址:可以去Microsoft Store 去下载
        开发工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
        Net 版本:.Net 8.0
        CoreCLR源码:源码下载

二、目录结构

    为了让大家看的更清楚,也为了自己方便查找,我做了一个目录结构,可以直观的查看文章的布局、内容,可以有针对性查看

    2.1、内存转储
        A、基础知识
        B、眼见为实
            1)、使用【NTSD】调试
            2)、使用【Windbg Preview】调试
    2.2、值类型的转储
        A、基础知识
        B、眼见为实
            1)、使用【NTSD】调试
            2)、使用【Windbg Preview】调试
    2.3、转储基本的引用类型
        A、基础知识
        B、眼见为实
            1)、使用【NTSD】调试
            2)、使用【Windbg Preview】调试
    2.4、数组的转储
        A、基础知识
        B、眼见为实
            1)、使用【NTSD】调试
            2)、使用【Windbg Preview】调试
    2.5、栈上对象的转储
        A、基础知识
        B、眼见为实
            1)、使用【NTSD】调试
            2)、使用【Windbg Preview】调试
    2.6、找出对象的大小
        A、基础知识
        B、眼见为实
            1)、使用【NTSD】调试
            2)、使用【Windbg Preview】调试
    2.7、异常的转储
        A、基础知识
        B、眼见为实
            1)、使用【NTSD】调试
            2)、使用【Windbg Preview】调试

三、调试源码

    废话不多说,本节是调试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。

    3.1、ExampleCore_3_1_6
  1 using System.Diagnostics;
  2 
  3 namespace ExampleCore_3_1_6
  4 {
  5     public class ObjTypes
  6     {
  7         public struct Coordinate
  8         {
  9             public int xCord;
 10             public int yCord;
 11             public int zCord;
 12 
 13             public Coordinate(int x, int y, int z)
 14             {
 15                 xCord = x;
 16                 yCord = y;
 17                 zCord = z;
 18             }
 19         }
 20 
 21         private Coordinate coordinate;
 22 
 23         int[] intArray = [1, 2, 3, 4, 5];
 24 
 25         string[] strArray = ["Welcome", "to", "Advanced", ".NET", "Debugging"];
 26 
 27         static void Main(string[] args)
 28         {
 29             Coordinate point = new Coordinate(100, 100, 100);
 30             Console.WriteLine("Press any key to continue(AddCoordinate)");
 31             Console.ReadKey();
 32             ObjTypes ob = new ObjTypes();
 33             ob.AddCoordinate(point);
 34 
 35             Console.WriteLine("Press any key to continue(Arrays)");
 36             Console.ReadKey();
 37             ob.PrintArrays();
 38 
 39             Console.WriteLine("Press any key to continue(Generics)");
 40             Console.ReadKey();
 41             Comparer<int> c = new Comparer<int>();
 42             Console.WriteLine("Greater:{0}", c.GreaterThan(5, 10));
 43 
 44             Console.WriteLine("Preaa any key to continue(Exception)");
 45             Console.ReadKey();
 46             ob.ThrowException(null);
 47         }
 48        
 49         public void AddCoordinate(Coordinate coord)
 50         {
 51             coordinate.xCord += coord.xCord;
 52             coordinate.yCord += coord.yCord;
 53             coordinate.zCord += coord.zCord;
 54 
 55             Console.WriteLine("x:{0},y:{1},z:{2}", coordinate.xCord, coordinate.yCord, coordinate.zCord);
 56         }
 57 
 58         public void PrintArrays()
 59         {
 60             foreach (int i in intArray)
 61             {
 62                 Console.WriteLine("Int:{0}", i);
 63             }
 64             foreach (string s in strArray)
 65             {
 66                 Console.WriteLine("Str:{0}", s);
 67             }
 68         }
 69 
 70         public void ThrowException(ObjTypes? obj)
 71         {
 72             if (obj == null)
 73             {
 74                 throw new ArgumentException("Obj cannot be null");
 75             }
 76         }        
 77     }
 78     public class Comparer<T> where T : IComparable
 79     {
 80         public T GreaterThan(T d, T d2)
 81         {
 82             int ret = d.CompareTo(d2);
 83             if (ret > 0)
 84             {
 85                 return d;
 86             }
 87             else
 88             {
 89                 return d2;
 90             }
 91         }
 92 
 93         public T LessThan(T d, T d2)
 94         {
 95             int ret = d.CompareTo(d2);
 96             if (ret < 0)
 97             {
 98                 return d;
 99             }
100             else
101             {
102                 return d2;
103             }
104         }
105     }
106 }
View Code

    3.2、ExampleCore_3_1_7
 1 namespace ExampleCore_3_1_7
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             var person = new Person();
 8             Console.ReadLine();
 9         }
10     }
11     internal class Person
12     {
13         public int Age = 20;
14 
15         public string Name = "jack";
16     }
17 }
View Code

    3.3、ExampleCore_3_1_8
 1 namespace ExampleCore_3_1_8
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Console.WriteLine("Welcome to .NET Advanced Debugging!");
 8 
 9             Person person = new Person() { Name = "PatrickLiu", Age = 32, HomeAddress = new Address() { Country = "China", Province = "冀州", City = "直隶总督府", Region = "广平大街23号", PostalCode = "213339" } };
10 
11             Console.WriteLine($"名称:{person.Name},地址:{person.HomeAddress}");
12 
13             Console.Read();
14         }
15     }
16 
17     public class Person
18     {
19         public int Age { get; set; }
20         public string? Name { get; set; }
21         public Address? HomeAddress { get; set; }
22     }
23 
24     public class Address
25     {
26         public string? Country { get; set; }
27         public string? Province { get; set; }
28         public string? City { get; set; }
29         public string? Region { get; set; }
30         public string? PostalCode { get; set; }
31         public override string ToString()
32         {
33             return $"{Country}-{Province}-{City}-{Region}-{PostalCode}";
34         }
35     }
36 }
View Code

四、基础知识
    在这一段内容中,有的小节可能会包含两个部分,分别是 A 和 B,也有可能只包含 A,如果只包含 A 部分,A 字母会省略。A 是【基础知识】,讲解必要的知识点,B 是【眼见为实】,通过调试证明讲解的知识点。

    4.1、对象检查
        在大多数调试会话中,首先需要执行的任务之一就是分析应用程序的状态,以便确认应用程序的故障是由于某种无效状态造成的。本节,我们将介绍一些命令用来分析程序的状态,以确定程序的故障。我们先来介绍非托管调试器中一些常用的命令,然后再介绍在 SOS 调试扩展中针对托管代码调试的命令。
        4.1.1、内存转储
            A、基础知识
                在调试器中有很多命令都可以转储内存的内容,这个方式非常底层,从内存地址上观察地址上的内容。最常使用的命令是【d(显示内存)】,比如:【dp】命令。根据转储的数据类型不同,命令【d】也有很多不同的变化,比如:du,dw,db,da 等,如果想了解更多,可以查看 Windbg 的帮助文档,命令是【.hh】。
                其他一些变化形式:
                。du 命令把被转储的内存视作为 Unicode 字符。
                。da 命令把被转储的内存视作为 ASCII字符。
                。dw 命令把被转储的内存视作为字(word)。
                。db 命令把被转储的内存视作为字节值和 ASCII 字符。
                。dq 命令把被转储的内存视作为四字值(quad word)。

                .NET 类型共分为两种:值类型和引用类型 。值类型是从 System.ValueType 类型继承下来的,一般在线程栈上分配存储空间(指方法的参数和局部变量)。如果值类型包在了引用类型的内部,它会和引用类型一起在托管堆上存储。引用类型是从 System.Object 类型继承下来的,并且是在托管堆上分配存储空间的。
                我们知道所有类型的基类是 System.Object ,包括值类型。前面已经介绍过,值类型被分配在栈上,而引用类型则被分配在托管堆上。因此,如何把一个在栈上分配的值赋给一个在托管堆上分配的对象?答案是装箱操作。当把值类型赋给引用类型时,CLR将自动执行以下任务:
                1)在托管堆上分配内存。
                2)将值类型的内存(从栈上)复制到托管堆上新分配的引用类型中。
                与装箱相反的操作是拆箱,这个操作能将引用类型赋值给值类型。严格来说,拆箱操作的开销小于装箱操作的开销。因为当执行拆箱操作时,CLR并不会复制被装箱的值类型的内容,而是返回一个托管指针,指向包含在引用类型中的值类型实例。然而,在赋值操作中同样也包含拆箱操作,从而需要将一个被装箱的值类型复制到栈上,这个操作也会带来一定的开销。
                虽然这些操作对用户来说是透明的,但了解这些操作的内部机制是很重要的,因为它们能对调试会话产生影响(并且,当存在大量的装箱和拆箱等操作时,会对性能产生影响)。

            B、眼见为实
                1)、使用【NTSD】调试
                    调试源码:ExampleCore_3_1_7
                    我们编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】,输入命令:NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.EXE。
                    

                    打开【NTSD】调试器窗口。
                    

                    继续使用【g】命令,运行调试器,等调试器卡住后,按【ctrl+c】组合键键入调试器的中断模式。
                    切换到主线程,执行命令【~0s】。

1 0:009> ~0s
2 coreclr!GetThreadNULLOk+0x1e [inlined in coreclr!CrstBase::Enter+0x32]:
3 00007ff9`bd119fa2 488b34c8        mov     rsi,qword ptr [rax+rcx*8] ds:0000026c`600cb0b0=0000026c600ba2d0

                    使用【!clrstack -a】查看托管代码的线程调用栈。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x954 (0)
 3         Child SP               IP Call Site
 4 000000785C77DCE8 00007ff9bd119fa2 [ExternalMethodFrame: 000000785c77dce8]
 5 ......(省略了)
 6 000000785C77E4D0 00007FF95D621987 ExampleCore_3_1_7.Program.Main(System.String[])
 7     PARAMETERS:
 8         args (0x000000785C77E520) = 0x0000026c64808ea0
 9     LOCALS:
10         0x000000785C77E508 = 0x0000026c64809640

                    0x0000026c64809640 红色标注的就是 Person 类型的局部变量 person 的地址。我们可以使用【!dumpobj /d 0x0000026c64809640】查看 person 的详情。

 1 0:000> !dumpobj /d 0x0000026c64809640
 2 Name:        ExampleCore_3_1_7.Person
 3 MethodTable: 00007ff95d6d93e0(方法表地址)
 4 EEClass:     00007ff95d6e1f18
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff95d591188  4000001       10         System.Int32  1 instance               20 Age
11 00007ff95d60ec08  4000002        8        System.String  0 instance 000002acf67a04a0 Name
12 0:000>

                    同样,我们可以使用【dp】命令也能看到 person 的详情信息,只是不是很直观。

1 0:000> dp 0x0000026c64809640
2 0000026c`64809640  00007ff9`5d6d93e0 000002ac`f67a04a0
3 0000026c`64809650  00000000`00000014 00000000`00000000
4 0000026c`64809660  00007ff9`5d555fa8 00000000`00000000
5 0000026c`64809670  00000000`00000000 00007ff9`5d60ec08
6 0000026c`64809680  0065006b`0000000c 006c0065`006e0072
7 0000026c`64809690  0064002e`00320033 00000000`006c006c
8 0000026c`648096a0  00000000`00000000 00007ff9`5d700d68
9 0000026c`648096b0  00000000`00000000 00000000`00000000

                    上面使用【!dumpobj】和【dp】命令我们找到的方法表地址都是一样的。000002ac`f67a04a0 这个项就是 Name 域的地址,因为 Name 是引用类型,所以这里是一个地址。我们可以继续使用【!dumpobj /d 000002ac`f67a04a0】来验证。

 1 0:000> !dumpobj /d 000002ac`f67a04a0
 2 Name:        System.String
 3 MethodTable: 00007ff95d60ec08
 4 EEClass:     00007ff95d5ea500
 5 Tracked Type: false
 6 Size:        30(0x1e) bytes
 7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 8 String:      jack(我们赋的值)
 9 Fields:
10               MT    Field   Offset                 Type VT     Attr            Value Name
11 00007ff95d591188  400033b        8         System.Int32  1 instance                4 _stringLength
12 00007ff95d59b538  400033c        c          System.Char  1 instance               6a _firstChar
13 00007ff95d60ec08  400033a       c8        System.String  0   static 000002acf67a0008 Empty

                      我们可以使用【dumpobj】命令,当然也可以使用【dp】命令,都可以看到想看到的信息,不过是【dp】可读性差很多。
                      如果我们查看命令如何使用,可以使用【.hh】命令。

1 0:000> .hh

                      效果如图:
                      

                      就能打开调试的帮助文件。

                      

                      我们可以打开【索引】选项,查看我们想要查看的命令。
                      


                2)、使用【Windbg Preview】调试
                    调试源码:ExampleCore_3_1_7
                    我们编译项目,打开 Windbg,点击【文件】----》【Launch executable】附加程序 ExampleCore_3_1_7.exe,打开调试器的界面,程序已经处于中断状态。由于显示的内容太多,我们可以使用【.cls】命令清空调试器的界面。我们使用【g】命令继续运行程序,调试器会在【Console.ReadLine()】这行代码处卡住,我们点击【break】按钮,就可以调试程序了。
                    查看是否在主线程,如果不是,切换到主线程,执行命令【~0s】。
1 0:006> ~0s
2 ntdll!NtReadFile+0x14:
3 00007ffa`9576ae54 c3              ret

                    我们查看一下当前托管的线程栈,执行命令【!clrstack -a】。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x8fc (0)
 3         Child SP               IP Call Site
 4 000000D30A57E230 00007ffa9576ae54 [InlinedCallFrame: 000000d30a57e230] 
 5 000000D30A57E230 00007ff9e4f076eb [InlinedCallFrame: 000000d30a57e230] 
 6 ......(省略了)
 7 
 8 000000D30A57E580 00007ff95e471987 ExampleCore_3_1_7.Program.Main(System.String[]) [E:\Visual Studio\..\ExampleCore_3_1_7\Program.cs @ 8]
 9     PARAMETERS:
10         args (0x000000D30A57E5D0) = 0x0000021dc3808ea0
11     LOCALS:
12         0x000000D30A57E5B8 = 0x0000021dc3809640

                    0x0000021dc3809640 红色标注的就是 Person 类型的局部变量 person 的地址。我们直接使用【dp】命令转储出 person 的值。

1 0:000> dp 0x0000021dc3809640
2 0000021d`c3809640  00007ff9`5e5293e0 0000025e`558d04a0
3 0000021d`c3809650  00000000`00000014 00000000`00000000
4 0000021d`c3809660  00007ff9`5e3a5fa8 00000000`00000000
5 0000021d`c3809670  00000000`00000000 00007ff9`5e45ec08
6 0000021d`c3809680  0065006b`0000000c 006c0065`006e0072
7 0000021d`c3809690  0064002e`00320033 00000000`006c006c
8 0000021d`c38096a0  00000000`00000000 00007ff9`5e550d68
9 0000021d`c38096b0  00000000`00000000 00000000`00000000

                    最左边的一列给出了每行内存的起始地址,后面是内存的内容。00007ff9`5e5293e0 红色标注的就是方法表。我们可以验证。执行命令【!dumpheap -type Person】。

1 0:000> !dumpheap -type Person
2          Address               MT           Size
3     021dc3809640     7ff95e5293e0             32 
4 
5 Statistics:
6           MT Count TotalSize Class Name
7 7ff95e5293e0     1        32 ExampleCore_3_1_7.Person
8 Total 1 objects, 32 bytes

                    7ff95e5293e0 和 00007ff9`5e5293e0 两个值是一样的。
                    虽然直接输出内存的内容很有用,但是阅读起来就很麻烦。当我们调试托管代码时候,使用 SOS 扩展命令会提供更直接的信息。
                    如果我们想查看有关调试器的各种命令。我们可以使用【.hh】帮助文件。

1 windbg> .hh

                    同样能打开调试器的帮助窗口。
                    

 

        4.1.2、值类型的转储
            A、基础知识
                我们知道.NET 的类型分为值类型和引用类型,那我们如何判断一个指针指向的是否是值类型呢,最佳的方式就是使用【dumpobj】命令,但它只对引用类型有效。【dumpobj】命令的参数是一个指向引用类型的指针,如果指针指向的是值类型,【dumpobj】命令就会输出:<Note:this object has an  invalid CLASS field>Invalid object。
                在前面已经提到过,如果一个值类型没有作为某个引用类型的一部分(例如,在某个函数中声明一个局部变量或者一个值类型),那么它会被分配在栈上,并且可以使用前面介绍的d系列命令来转储值类型的内容。
                我们在使用调试器调试程序的时候,需要注意,如果我们是手动中断调试器的执行,执行有些命令会报错,比如:【!clrstack 】命令。当【!ClrStack】命令提示了一个错误,指出当前的线程上下文并不是一个有效的托管线程。由于我们手动地中断程序的执行,调试器的线程上下文是在调试器线程上,而这是一个非托管线程,因此在运行【!ClrStack】命令之前,必须首先切换到托管线程上下文。使用~命令将上下文切换到线程0,然后再次执行ClrStack命令。
                如果我们想转储值类型的内容,可以使用【!DumpVC <Method Table> <Value object start addr>】命令,<Method Table>:表示是方法表的地址,<Value object start addr>:表示的就是值类型的地址。


            B、眼见为实
                1)、使用【NTSD】调试
                    调试源码:ExampleCore_3_1_6
                    1.1)、查看独立的值类型
                        编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.exe】。
                        

                        打开【NTSD】调试器窗口。
                        

                        输出内容太多,使用【.cls】命令,清理一下屏幕。然后使用【g】命令,运行调试器。调试器输出:Press any key to continue(AddCoordinate),如图:
                        

                        输入【ctrl+c】组合键进入中断模式。我们现在查看一下托管线程栈,可以使用【!clrstack -a】命令。

1 0:002> !clrstack -a
2 OS Thread Id: 0x1764 (2)
3 Unable to walk the managed stack. The current thread is likely not a
4 managed thread. You can run !threads to get a list of managed threads in
5 the process
6 Failed to start stack walk: 80070057

                        如图:
                        

                        该命令执行错误,提示不是一个有效的托管线程,由于我们是手动中断程序的执行,调试器的线程上下文是在调试器线程上,它是一个非托管线程,因此,在执行该命令之前,需要切换到托管线程的上下文中。执行命令【~0s】。

1 0:002> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffb`2fdaae74 c3              ret
4 0:000>

                        我们再次执行【!clrstack -a】命令。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x2c1c (0)
 3         Child SP               IP Call Site
 4 000000CD41F7E5E8 00007ffb2fdaae74 [InlinedCallFrame: 000000cd41f7e5e8]
 5 000000CD41F7E5E8 00007ffb1b68787a [InlinedCallFrame: 000000cd41f7e5e8]
 6 。。。。。。(省略了)
 7 000000CD41F7E770 00007FF9F55919B6 ExampleCore_3_1_6.ObjTypes.Main(System.String[])
 8     PARAMETERS:
 9         args (0x000000CD41F7E840) = 0x000002a492808ea0
10     LOCALS:
11         0x000000CD41F7E820 = 0x0000006400000064
12         0x000000CD41F7E818 = 0x0000000000000000
13         0x000000CD41F7E810 = 0x0000000000000000

                        这时,【clrstack】命令输出了托管线程的栈回溯,包括每个栈帧的局部变量和参数。我们主要关注【ExampleCore_3_1_6.ObjTypes.Main】栈帧和地址【0x000000CD41F7E820】上的局部变量。【0x000000CD41F7E820】这个地址我们不知道它指向的是一个值类型还是引用类型。我们可以使用【dumpobj】命令做一个测试,因为该命令只对引用类型实例起作用。

1 0:000> !dumpobj 0x000000CD41F7E820
2 <Note: this object has an invalid CLASS field>
3 Invalid object

                        从这个输出结果可以知道,这个指针指向的肯定不是引用类型。我们可以通过命令【r】验证一下,可以观察【rsp】寄存器,它保存的是当前栈指针。

 1 0:000> r
 2 rax=0000000000000007 rbx=000000cd41f7e550 rcx=00000000000001c8
 3 rdx=0000000000000000 rsi=0000000000000000 rdi=00000000000001c8
 4 rip=00007ffb2fdaae74 rsp=000000cd41f7e388 rbp=000000cd41f7e490
 5  r8=000000cd41f7e388  r9=000000cd41f7e490 r10=0000000000000000
 6 r11=0000000000000246 r12=000000cd41f7e8a0 r13=0000000000000004
 7 r14=0000000000000003 r15=000000cd41f7ea00
 8 iopl=0         nv up ei pl zr na po nc
 9 cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000244
10 ntdll!NtDeviceIoControlFile+0x14:
11 00007ffb`2fdaae74 c3              ret

                        【rsp】寄存器的值是:000000CD41F7E388,我们正在分析的地址是:0x000000CD41F7E820,连个地址非常接近,说明我们分析的地址是栈地址,也就是这个地址存储的是值类型的值。

                        我们可以使用【dp】命令查看一下具体的内容。

1 0:000> dp 0x000000CD41F7E820
2 000000cd`41f7e820  00000064`00000064 00000000`00000064
3 000000cd`41f7e830  000000cd`41f7e860 00007ffa`550da1a3
4 000000cd`41f7e840  000002a4`92808ea0 000000cd`41f7ee88
5 000000cd`41f7e850  000000cd`41f7ee88 000000cd`41f7ea79
6 000000cd`41f7e860  000000cd`41f7e910 00000000`0000001d
7 000000cd`41f7e870  000000cd`41f7ea88 00007ffa`550614c9
8 000000cd`41f7e880  00000000`00000000 00000000`00000130
9 000000cd`41f7e890  000000cd`41f7ea88 00007ffa`54f8a456

                        00000064 的十进制的值就是100。后面有三个域的值是0x64。

1 0:000> ? 00000064
2 Evaluate expression: 100 = 00000000`00000064                  

                    1.2)、查看嵌入在引用类型中的值类型。
                        编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.exe】。
                        

                        打开【NTSD】调试器窗口。
                        

                        输出内容太多,使用【.cls】命令,清理一下屏幕。然后使用【g】命令,运行调试器。调试器输出:Press any key to continue(AddCoordinate),如图:
                        

                        按组合键【ctrl+c】,进入中断模式。我们直接使用【!bpmd ExampleCore_3_1_6 ExampleCore_3_1_6.ObjTypes.AddCoordinate】命令,在 ObjTypes 类型上的 AddCoordinate 的方法上设置断点。
                        
1 0:009> !bpmd ExampleCore_3_1_6 ExampleCore_3_1_6.ObjTypes.AddCoordinate
2 MethodDesc = 00007FF9F5630108
3 Adding pending breakpoints...

                        我们继续运行调试器。

0:000> g
(131c.3ca0): CLR notification exception - code e0444143 (first chance)
JITTED ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.AddCoordinate(Coordinate)
Setting breakpoint: bp 00007FF9F43B1CD0 [ExampleCore_3_1_6.ObjTypes.AddCoordinate(Coordinate)]
。。。

                        重新设置断点,除非并中断执行。我们执行【!clrstack -a】命令查看一下托管线程栈。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3df8 (0)
 3         Child SP               IP Call Site
 4 00000085A277E468 00007ffb2d7f9202 [HelperMethodFrame: 00000085a277e468] System.Diagnostics.Debugger.BreakInternal()
 5 00000085A277E570 00007FFA4FC654DA System.Diagnostics.Debugger.Break()
 6 
 7 00000085A277E5A0 00007FF9F0AB1D1A ExampleCore_3_1_6.ObjTypes.AddCoordinate(Coordinate)
 8     PARAMETERS:
 9         this (0x00000085A277E600) = 0x00000264de809ce0
10         coord (0x00000085A277E628) = 0x0000006400000064
11 
12 00000085A277E600 00007FF9F0AB1A11 ExampleCore_3_1_6.ObjTypes.Main(System.String[])
13     PARAMETERS:
14         args (0x00000085A277E6D0) = 0x00000264de808ea0
15     LOCALS:
16         0x00000085A277E6B0 = 0x0000006400000064
17         0x00000085A277E6A8 = 0x00000264de809ce0
18         0x00000085A277E6A0 = 0x0000000000000000

                        我们主要关注【ExampleCore_3_1_6.ObjTypes.AddCoordinate】栈帧和【this】指针。【this】指针指向的是当前的引用类型实例。我们可以使用【dumpobj】命令输出看一下。

 1 0:000> !dumpobj /d 0x00000264de809ce0
 2 Name:        ExampleCore_3_1_6.ObjTypes
 3 MethodTable: 00007ff9f0b60238
 4 EEClass:     00007ff9f0b4fcb8
 5 Tracked Type: false
 6 Size:        48(0x30) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff9f0b601c8  4000001       18 ...jTypes+Coordinate  1 instance 00000264de809cf8 coordinate
11 00007ff9f0a99df8  4000002        8       System.Int32[]  0 instance 00000264de809d10 intArray
12 00007ff9f0b3fac8  4000003       10      System.String[]  0 instance 00000264de809d80 strArray

                        红色标注的【coordinate】就是引用类型包含一个值类型的例子。【MT、Field、Offset、Type、VT、Attr、Value和 Name】这些域具体的意思,可以查看图表。
                        

                        我们有两种方式可以查看【coordinate】变量的具体值。第一种,使用【dp】命令。命令的参数分别是引用类型对象的地址(0x00000264de809ce0)和偏移(0x18)。

1 0:000> dp 0x00000264de809ce0+0x18
2 00000264`de809cf8  00000000`00000000 00000000`00000000
3 00000264`de809d08  00000000`00000000 00007ff9`f0a99df8
4 00000264`de809d18  00000000`00000005 00000002`00000001
5 00000264`de809d28  00000004`00000003 00000000`00000005
6 00000264`de809d38  00000000`00000000 00007ff9`f0bb4468
7 00000264`de809d48  00000000`00000000 00000000`00000000
8 00000264`de809d58  00000000`00000000 00000000`00000000
9 00000264`de809d68  00000000`00000000 00007ff9`f0bb3de0

                        第二种就是使用【!dumpvc mt addr】直接查看值类型,更直接。

 1 0:000> !dumpvc 00007ff9f0b601c8  00000264de809cf8
 2 Name:        ExampleCore_3_1_6.ObjTypes+Coordinate
 3 MethodTable: 00007ff9f0b601c8
 4 EEClass:     00007ff9f0b4fd30
 5 Size:        32(0x20) bytes
 6 File:        E:\Visual Studio 2022\Source\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
 7 Fields:
 8               MT    Field   Offset                 Type VT     Attr            Value Name
 9 00007ff9f0a21188  4000005        0         System.Int32  1 instance                0 xCord
10 00007ff9f0a21188  4000006        4         System.Int32  1 instance                0 yCord
11 00007ff9f0a21188  4000007        8         System.Int32  1 instance                0 zCord
                        
                2)、使用 【Windbg Preview】 调试
                    调试源码:ExampleCore_3_1_6
                    2.1)、查看独立的值类型
                        编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们使用【g】命令,继续运行程序,控制台程序输出:Press any key to continue(AddCoordinate),我们的调试器也处于卡住的状态。此时点击【break】按钮,就可以调试程序了。
                        首先,我们希望显示出托管调用栈以及相关的局部变量。使用【!clrstack -a】命令获取这些信息。
1 0:001> !clrstack -a
2 OS Thread Id: 0x239c (1)
3 Unable to walk the managed stack. The current thread is likely not a 
4 managed thread. You can run !clrthreads to get a list of managed threads in
5 the process
6 Failed to start stack walk: 80070057

                        【clrstack】命令出错了,错误原因是当前的线程上下文并不是一个有效的托管线程。由于我们手动的中断程序的执行,调试器的线程上下文是在调试器线程上,而这个线程是非托管的线程。因此,在执行【clrstack】命令之前,首先必须切换到托管线程上下文。使用【~】命令将上下文切换到线程0。

1 0:001> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffa`9576ae74 c3              ret

                        然后,再次执行【!clrstack -a】命令。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x584 (0)
 3         Child SP               IP Call Site
 4 000000F06737E948 00007ffa9576ae74 [InlinedCallFrame: 000000f06737e948] 
 5 000000F06737E948 00007ffa4f72787a [InlinedCallFrame: 000000f06737e948] 
 6 ......(省略了)
 7 
 8 000000F06737EAD0 00007ff95dd019b6 ExampleCore_3_1_6.ObjTypes.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_6\Program.cs @ 29]
 9     PARAMETERS:
10         args (0x000000F06737EBA0) = 0x0000020f54808ea0
11     LOCALS:
12         0x000000F06737EB80 = 0x0000006400000064
13         0x000000F06737EB78 = 0x0000000000000000
14         0x000000F06737EB70 = 0x0000000000000000

                        这就是【!clrstack】命令输出的托管线程的栈回溯,包括每个栈帧的局部变量和参数。在调用栈中,我们主要关注的是 ExampleCore_3_1_6.ObjTypes.Main 栈帧和位于地址 0x000000F06737EB80 上的局部变量(红色标注的)。
                        由于我们不知道这个局部变量指向的是值类型还是引用类型,因此我们可以使用【!dumpobj】做一下判断。

1 0:000> !dumpobj 0x000000F06737EB80
2 <Note: this object has an invalid CLASS field>
3 Invalid object

                        从命令的输出结果中可以看出,该地址的肯定不是引用类型。我们来验证,值类型被保存在栈上,如果发现某个地址位于当前栈指针的附近,那就可以证明是值类型了。我们使用【r】命令,观察 rsp 寄存器(保存的是当前栈指针)。

 1 0:000> r
 2 rax=0000000000000007 rbx=000000f06737e8b0 rcx=0000000000000058
 3 rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000058
 4 rip=00007ffa9576ae74 rsp=000000f06737e6e8 rbp=000000f06737e7f0
 5  r8=000000f06737e6e8  r9=000000f06737e7f0 r10=0000000000000000
 6 r11=0000000000000130 r12=000000f06737ec00 r13=0000000000000004
 7 r14=0000000000000003 r15=000000f06737ed60
 8 iopl=0         nv up ei pl zr na po nc
 9 cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000244
10 ntdll!NtDeviceIoControlFile+0x14:
11 00007ffa`9576ae74 c3              ret

                        在 rsp 寄存器中包含的值:000000f06737e6e8,它非常接近我们分析的地址:0x000000F06737EB80。说明我们分析的地址就是一个栈地址,我们使用【dp】查看一下详情。

1 0:000> dp 0x000000F06737EB80
2 000000f0`6737eb80  00000064`00000064 00000000`00000064
3 000000f0`6737eb90  000000f0`6737ebc0 00007ff9`bd85a1a3
4 000000f0`6737eba0  0000020f`54808ea0 000000f0`6737f1e8
5 000000f0`6737ebb0  000000f0`6737f1e8 000000f0`6737edd9
6 000000f0`6737ebc0  000000f0`6737ec70 00000000`0000001d
7 000000f0`6737ebd0  000000f0`6737ede8 00007ff9`bd7e14c9
8 000000f0`6737ebe0  00000000`00000000 00000000`00000130
9 000000f0`6737ebf0  000000f0`6737ede8 00007ff9`bd70a456

                        00000064 红色标注的十进制的值就是100。

1 0:000> ? 00000064
2 Evaluate expression: 100 = 00000000`00000064

                        有三个 00000064,分别对应 Coordinate 类型的各个域。                     

                    2.2)、查看引用类型中的值类型
                        【2.1 的例子我们看到了如何显示在函数内声明的值类型的内容。通常,值类型被嵌入在引用类型中并被保存在托管堆上。在这种情况下,我们不能直接使用内存转储命令,而需要借助一些辅助命令来转储值类型。
                        编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们使用【g】命令,继续运行程序,控制台程序输出:Press any key to continue(AddCoordinate),我们的调试器也处于卡住的状态。此时点击【break】按钮,就可以调试程序了。
                        由于我们是人工中断程序的执行,当前的线程上下文是调试器的线程上下文不是托管线程上下文,我们必须切换到托管线程的上下文,执行命令【~0s】。
1 0:007> !bpmd ExampleCore_3_1_6 ExampleCore_3_1_6.ObjTypes.AddCoordinate
2 MethodDesc = 00007FF9EDAA0108
3 Adding pending breakpoints...

                        断点设置好后,我们继续执行调试器,执行命令【g】。

1 0:007> g
2 (3310.3864): CLR notification exception - code e0444143 (first chance)
3 JITTED ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.AddCoordinate(Coordinate)
4 Setting breakpoint: bp 00007FF9ED9F1CE0 [ExampleCore_3_1_6.ObjTypes.AddCoordinate(Coordinate)]
5 Breakpoint 0 hit
6 ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.AddCoordinate:
7 00007ff9`ed9f1ce0 55              push    rbp

                        当前我们的调试器线程上下文也自动切换到托管线程的上下文,可以直接执行【!clrstack -a】命令。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3864 (0)
 3         Child SP               IP Call Site
 4 000000DEBDF7E968 00007ff9ed9f1ce0 ExampleCore_3_1_6.ObjTypes.AddCoordinate(Coordinate) [E:\Visual Studio\.\ExampleCore_3_1_6\Program.cs @ 48]
 5     PARAMETERS:
 6         this (<CLR reg>) = 0x0000027eb4409ce0
 7         coord (<CLR reg>) = 0x000000debdf7e998
 8 
 9 000000DEBDF7E970 00007ff9ed9f1a11 ExampleCore_3_1_6.ObjTypes.Main(System.String[]) [E:\Visual Studio\..\ExampleCore_3_1_6\Program.cs @ 31]
10     PARAMETERS:
11         args (0x000000DEBDF7EA40) = 0x0000027eb4408ea0
12     LOCALS:
13         0x000000DEBDF7EA20 = 0x0000006400000064
14         0x000000DEBDF7EA18 = 0x0000027eb4409ce0
15         0x000000DEBDF7EA10 = 0x0000000000000000

                        0x0000027eb4409ce0 红色标注的是 this 指针。this 指针指向当前的对象实例。我们使用【!dumpobj】命令查看一下 this 指针。

 1 0:000> !dumpobj /d 0x0000027eb4409ce0
 2 Name:        ExampleCore_3_1_6.ObjTypes
 3 MethodTable: 00007ff9edaa0238
 4 EEClass:     00007ff9eda8fca8
 5 Tracked Type: false
 6 Size:        48(0x30) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff9edaa01c8  4000001       18 ...jTypes+Coordinate  1 instance 0000027eb4409cf8 coordinate
11 00007ff9ed9d9df8  4000002        8       System.Int32[]  0 instance 0000027eb4409d10 intArray
12 00007ff9eda7fac8  4000003       10      System.String[]  0 instance 0000027eb4409d80 strArray

                   Fields 是最重要的信息,它包含了这个对象的元数据。各个域的表示信息可以看下表。
                   
                   如果要显示这个域本身的内容,有两种方式。其中一种是使用【dp】命令,参数是引用类型实例的地址(000001d9b1c09ce0)和偏移(0x18)。

1 0:000> dp 000001d9b1c09ce0+0x18
2 000001d9`b1c09cf8  00000000`00000000 00000000`00000000
3 000001d9`b1c09d08  00000000`00000000 00007ff9`ebf49df8
4 000001d9`b1c09d18  00000000`00000005 00000002`00000001
5 000001d9`b1c09d28  00000004`00000003 00000000`00000005
6 000001d9`b1c09d38  00000000`00000000 00007ff9`ec064468
7 000001d9`b1c09d48  00000000`00000000 00000000`00000000
8 000001d9`b1c09d58  00000000`00000000 00000000`00000000
9 000001d9`b1c09d68  00000000`00000000 00007ff9`ec063de0

                   如果我们想查看【...jTypes+Coordinate】的具体的值,可以执行命令【!dumpvc mt addr】。

 1 0:000> !dumpvc 00007ff9edaa01c8 0000027eb4409cf8
 2 Name:        ExampleCore_3_1_6.ObjTypes+Coordinate
 3 MethodTable: 00007ff9edaa01c8
 4 EEClass:     00007ff9eda8fd20
 5 Size:        32(0x20) bytes
 6 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
 7 Fields:
 8               MT    Field   Offset                 Type VT     Attr            Value Name
 9 00007ff9ed961188  4000005        0         System.Int32  1 instance                0 xCord
10 00007ff9ed961188  4000006        4         System.Int32  1 instance                0 yCord
11 00007ff9ed961188  4000007        8         System.Int32  1 instance                0 zCord

 

        4.1.3、引用类型的转储
            A、基础知识
                如果我们想将引用类型转储输出,很简单,直接使用【!dumpobj】命令就可以了。【!dumpobj】命令的参数可以直接跟引用类型的地址,如果不想输出域的内容,可以使用 -nofields 命令开关。这个命令也有一个简写形式就是【!do】。

                命令格式:!DumpObj [-nofields] <object address>

                其中 object address 是转储的对象地址。在默认情况下,【!DumpObj】 会转储出类型的信息及其相关的域。如果只需要一般性的信息,那么可以使用 -nofields 选项,它不输出域信息。


            B、眼见为实
                1)、使用【NTSD】调试
                    调试源码:ExampleCore_3_1_7
                    编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.exe】。
                    

                    打开【NTSD】调试器窗口。
                    

                    我们使用【g】命令,运行调试器,等到调试器暂停,我们按【ctr+c】组合键进入中断模式。
                    

                    由于我们手动中断调试器,需要执行线程上下文的切换,切换到托管线程的上下文,执行命令【~os】。

1 0:002> ~0s
2 coreclr!LookupMap<MethodTable *>::GetValueAt+0x3 [inlined in coreclr!ClassLoader::LoadTypeDefThrowing+0x89]:
3 00007ffa`536a5c49 48f7d2          not     rdx

                    现在就可以使用【!clrstack -a】命令查看托管线程栈了。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x290c (0)
 3         Child SP               IP Call Site
 4 00000068AEBEDE68 00007ffa536a5c49 [ExternalMethodFrame: 00000068aebede68]
 5 。。。。。。(省略了)
 6 00000068AEBEE650 00007FF9F3C51987 ExampleCore_3_1_7.Program.Main(System.String[])
 7     PARAMETERS:
 8         args (0x00000068AEBEE6A0) = 0x000001afc2408ea0
 9     LOCALS:
10         0x00000068AEBEE688 = 0x000001afc2409640
                      0x000001afc2409640 红色标注的就是 Person 类型的局部变量 person。
                      废话不多说,直接使用【!dumpobj】或者【!do】命令查看 Person 的详情了。
 1 0:000> !dumpobj 0x000001afc2409640
 2 Name:        ExampleCore_3_1_7.Person
 3 MethodTable: 00007ff9f3d093e0
 4 EEClass:     00007ff9f3d11f18
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff9f3bc1188  4000001       10         System.Int32  1 instance               20 Age
11 00007ff9f3c3ec08  4000002        8        System.String  0 instance 000001f0546704a0 Name
12 
13 
14 0:000> !do 0x000001afc2409640
15 Name:        ExampleCore_3_1_7.Person
16 MethodTable: 00007ff9f3d093e0
17 EEClass:     00007ff9f3d11f18
18 Tracked Type: false
19 Size:        32(0x20) bytes
20 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
21 Fields:
22               MT    Field   Offset                 Type VT     Attr            Value Name
23 00007ff9f3bc1188  4000001       10         System.Int32  1 instance               20 Age
24 00007ff9f3c3ec08  4000002        8        System.String  0 instance 000001f0546704a0 Name

                    如果不想输出【Fields】的内容,可以使用 -nofields 命令开关。

 1 0:000> !dumpobj -nofields 0x000001afc2409640
 2 Name:        ExampleCore_3_1_7.Person
 3 MethodTable: 00007ff9f3d093e0
 4 EEClass:     00007ff9f3d11f18
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
 8 
 9 0:000> !do -nofields 0x000001afc2409640
10 Name:        ExampleCore_3_1_7.Person
11 MethodTable: 00007ff9f3d093e0
12 EEClass:     00007ff9f3d11f18
13 Tracked Type: false
14 Size:        32(0x20) bytes
15 File:        E:\Visual Studio 2022\...ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
16 0:000>

                2)、使用【Windbg Preview】 调试
                    调试源码:ExampleCore_3_1_7
                    编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们使用【g】命令,继续运行程序,我们的调试器也处于卡住的状态。此时点击【break】按钮,就可以调试程序了。
                    由于我们手动中断了调试器,当前的线程上下文是调试器的,不是托管线程的,我们必须使用【~0s】命令,切换到托管线程。
1 0:006> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffb`2fdaae74 c3              ret

                    查看当前的托管线程的调用栈,使用【!clrstack -a】命令。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x99c (0)
 3         Child SP               IP Call Site
 4 0000006E2117E200 00007ffb2fdaae54 [InlinedCallFrame: 0000006e2117e200] 
 5 0000006E2117E200 00007ffaeadb76eb [InlinedCallFrame: 0000006e2117e200] 
 6 。。。。。。(省略了)
 7 
 8 0000006E2117E550 00007ff9f3461987 ExampleCore_3_1_7.Program.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_7\Program.cs @ 8]
 9     PARAMETERS:
10         args (0x0000006E2117E5A0) = 0x000001da19808ea0
11     LOCALS:
12         0x0000006E2117E588 = 0x000001da19809640

                    0x000001da19809640 这个地址就是引用类型 Person 的局部变量 person。我们可以直接使用【!dumpobj】或者【!do】命令。

 1 0:000> !DumpObj /d 000001da19809640
 2 Name:        ExampleCore_3_1_7.Person
 3 MethodTable: 00007ff9f35193e0
 4 EEClass:     00007ff9f3521f18
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff9f33d1188  4000001       10         System.Int32  1 instance               20 Age
11 00007ff9f344ec08  4000002        8        System.String  0 instance 0000021aab9904a0 Name
12 
13 
14 0:000> !do 000001da19809640
15 Name:        ExampleCore_3_1_7.Person
16 MethodTable: 00007ff9f35193e0
17 EEClass:     00007ff9f3521f18
18 Tracked Type: false
19 Size:        32(0x20) bytes
20 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
21 Fields:
22               MT    Field   Offset                 Type VT     Attr            Value Name
23 00007ff9f33d1188  4000001       10         System.Int32  1 instance               20 Age
24 00007ff9f344ec08  4000002        8        System.String  0 instance 0000021aab9904a0 Name

                    我们可以使用 -nofields 命令开关去掉域的内容。

 1 0:000> !DumpObj -nofields 000001da19809640
 2 Name:        ExampleCore_3_1_7.Person
 3 MethodTable: 00007ff9f35193e0
 4 EEClass:     00007ff9f3521f18
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
 8 
 9 
10 0:000> !do -nofields 000001da19809640
11 Name:        ExampleCore_3_1_7.Person
12 MethodTable: 00007ff9f35193e0
13 EEClass:     00007ff9f3521f18
14 Tracked Type: false
15 Size:        32(0x20) bytes
16 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_7\bin\Debug\net8.0\ExampleCore_3_1_7.dll
                    
        4.1.4、数组的转储
            A、基础知识
                CLR 将数组作为第一级(first class)引用类型(因为所有数组都是从 System.Array 继承下来的),因此我们可以直接使用【!dumpobj】命令或者【!dumparray】命令转储出数组的在内存中的内容。
                数组本身是引用类型,但是它的元素是可以区分为:值类型和引用类型的。由此,我们可以把数组称为值类型的数组和引用类型的数组。虽然有这个区别,但是它们的内存布局是一致的,只不过在查看元素的时候有区别,如果是值类型的元素,可以使用【!dumpvc】命令,如果是引用类型,可以使用【!dumpobj】命令查看。
                数组的内存布局如图:
                                
                转储数组不光可以使用【!DumpObj】命令,当然也可以使用【dp】命令,直接显示内存数据,还有一个命令可以直接显示数组的数据,这个命令就是【!DumpArray】。该命令的格式:!DumpArray [-start <startIndex>] [-length <length>] [-details] [-nofields] <array object address>
                -start 选项能控制从数组中的哪个索引开始,而-length 选项则控制显示数组中多少个元素。例如,如果希望转储出位于地址X上的数组的前三个元素,并从索引2开始,那么可以使用以下命令行:!DumpArray -start 2 -length 3 X。
                如果增加 -details 选项,【!DumpArray】会输出更为详细的信息,在数组中的每个元素上都执行【!DumpObj】和【!DumpVC】命令。最后,-nofields 选项能使得 DumpArray 在与 -detail 选项一起使用的情况下不输出与域相关的信息。
                这里的概念不多,但是,要演示的内容很多,而且,内容很简单,就不多说了。                

            B、眼见为实
                1)、使用【NTSD】调试
                    调试源码:ExampleCore_3_1_6
                    1.1)、值类型数组
                        编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.exe】。
                        

                        打开【NTSD】调试器窗口。
                        

                        继续使用【g】命令运行调试器,直到调试器输出:Press any key to continue(Arrays),然后,我们手动【ctrl+c】进入调试器的中断模式,就可以调试了。
                        

                        我们进入中断模式后,需要切换线程到托管线程,执行命令【~0s】。

1 0:002> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffa`eeaeae74 c3              ret

                        现在,我们就可以查看托管线程栈了,执行命令【!clrstack -a】。

0:000> !clrstack -a
OS Thread Id: 0x3394 (0)
        Child SP               IP Call Site
00000090F577E5E8 00007ffaeeaeae74 [InlinedCallFrame: 00000090f577e5e8]
00000090F577E5E8 00007ffadb81787a [InlinedCallFrame: 00000090f577e5e8]
。。。。。。(省略了)

00000090F577E770 00007FF9BE1E1A2D ExampleCore_3_1_6.ObjTypes.Main(System.String[])
    PARAMETERS:
        args (0x00000090F577E840) = 0x000002282e008ea0
    LOCALS:
        0x00000090F577E820 = 0x0000006400000064
        0x00000090F577E818 = 0x000002282e009ce0
        0x00000090F577E810 = 0x0000000000000000

                        0x000002282e009ce0 红色标注的就是 ObjTypes 类型的指针,我们可以使用【!do】命令查看它。

 1 0:000> !do 0x000002282e009ce0
 2 Name:        ExampleCore_3_1_6.ObjTypes
 3 MethodTable: 00007ff9be290238
 4 EEClass:     00007ff9be27fca8
 5 Tracked Type: false
 6 Size:        48(0x30) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff9be2901c8  4000001       18 ...jTypes+Coordinate  1 instance 000002282e009cf8 coordinate
11 00007ff9be1c9df8  4000002        8       System.Int32[]  0 instance 000002282e009d10 intArray
12 00007ff9be26fac8  4000003       10      System.String[]  0 instance 000002282e009d80 strArray
13 0:000>

                        这里我们开始关注 intArray 值类型数组,也就是红色标注的。intArray 数组的地址是:000002282e009d10,有了地址,我们就可以使用【!dumpobj 000002282e009d10】命令转储出详情。

1 0:000> !dumpobj 000002282e009d10
2 Name:        System.Int32[]
3 MethodTable: 00007ff9be1c9df8
4 EEClass:     00007ff9be1c9d78
5 Tracked Type: false
6 Size:        44(0x2c) bytes
7 Array:       Rank 1, Number of elements 5, Type Int32
8 Fields:
9 None

                        验证了我们的说法,输出了数组的名称,维度(Rank),元素个数(Number of elements)等。我们依然可以使用【dp 000002282e009d10】命令输出详情。
                        

                        我们使用【!dumpmt 00007ff9`be1c9df8】命令是不是数组的方法表。

 1 0:000> !dumpmt 00007ff9`be1c9df8
 2 EEClass:         00007FF9BE1C9D78
 3 Module:          00007FF9BE084000
 4 Name:            System.Int32[]
 5 mdToken:         0000000002000000
 6 File:            C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 7 BaseSize:        0x18
 8 ComponentSize:   0x4
 9 DynamicStatics:  false
10 ContainsPointers false
11 Slots in VTable: 28
12 Number of IFaces in IFaceMap: 6

                        当然,我们可以直接使用【!dumparray 000002282e009d10】命令查看 intArray 数组详情,这个命令更直观。

 1 0:000> !dumparray 000002282e009d10
 2 Name:        System.Int32[]
 3 MethodTable: 00007ff9be1c9df8
 4 EEClass:     00007ff9be1c9d78
 5 Size:        44(0x2c) bytes
 6 Array:       Rank 1, Number of elements 5, Type Int32
 7 Element Methodtable: 00007ff9be151188
 8 [0] 000002282e009d20
 9 [1] 000002282e009d24
10 [2] 000002282e009d28
11 [3] 000002282e009d2c
12 [4] 000002282e009d30
13 
14 0:000> !dumpvc 00007ff9be151188 000002282e009d20
15 Name:        System.Int32
16 MethodTable: 00007ff9be151188
17 EEClass:     00007ff9be141e20
18 Size:        24(0x18) bytes
19 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
20 Fields:
21               MT    Field   Offset                 Type VT     Attr            Value Name
22 00007ff9be151188  400051d        0         System.Int32  1 instance                1 m_value
23 
24 0:000> !dumpvc 00007ff9be151188 000002282e009d24
25 Name:        System.Int32
26 MethodTable: 00007ff9be151188
27 EEClass:     00007ff9be141e20
28 Size:        24(0x18) bytes
29 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
30 Fields:
31               MT    Field   Offset                 Type VT     Attr            Value Name
32 00007ff9be151188  400051d        0         System.Int32  1 instance                2 m_value
33 
34 0:000> !dumpvc 00007ff9be151188 000002282e009d28
35 Name:        System.Int32
36 MethodTable: 00007ff9be151188
37 EEClass:     00007ff9be141e20
38 Size:        24(0x18) bytes
39 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
40 Fields:
41               MT    Field   Offset                 Type VT     Attr            Value Name
42 00007ff9be151188  400051d        0         System.Int32  1 instance                3 m_value
43 
44 0:000> !dumpvc 00007ff9be151188 000002282e009d2c
45 Name:        System.Int32
46 MethodTable: 00007ff9be151188
47 EEClass:     00007ff9be141e20
48 Size:        24(0x18) bytes
49 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
50 Fields:
51               MT    Field   Offset                 Type VT     Attr            Value Name
52 00007ff9be151188  400051d        0         System.Int32  1 instance                4 m_value
53 
54 0:000> !dumpvc 00007ff9be151188 000002282e009d30
55 Name:        System.Int32
56 MethodTable: 00007ff9be151188
57 EEClass:     00007ff9be141e20
58 Size:        24(0x18) bytes
59 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
60 Fields:
61               MT    Field   Offset                 Type VT     Attr            Value Name
62 00007ff9be151188  400051d        0         System.Int32  1 instance                5 m_value
63 0:000>
                    1.2)、引用类型数组
                        编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.exe】。
                        
                        打开【NTSD】调试器窗口。
                        

                        继续使用【g】命令运行调试器,直到调试器输出:Press any key to continue(Arrays),然后,我们手动【ctrl+c】进入调试器的中断模式,就可以调试了。
                        

                        我们进入中断模式后,需要切换线程到托管线程,执行命令【~0s】。
1 0:002> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffa`eeaeae74 c3              ret

                        现在,我们就可以查看托管线程栈了,执行命令【!clrstack -a】。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3394 (0)
 3         Child SP               IP Call Site
 4 00000090F577E5E8 00007ffaeeaeae74 [InlinedCallFrame: 00000090f577e5e8]
 5 00000090F577E5E8 00007ffadb81787a [InlinedCallFrame: 00000090f577e5e8]
 6 。。。。。。(省略了)
 7 
 8 00000090F577E770 00007FF9BE1E1A2D ExampleCore_3_1_6.ObjTypes.Main(System.String[])
 9     PARAMETERS:
10         args (0x00000090F577E840) = 0x000002282e008ea0
11     LOCALS:
12         0x00000090F577E820 = 0x0000006400000064
13         0x00000090F577E818 = 0x000002282e009ce0
14         0x00000090F577E810 = 0x0000000000000000

                        0x000002282e009ce0 红色标注的就是 ObjTypes 类型的指针,我们可以使用【!do】命令查看它。

 1 0:000> !do 0x000002282e009ce0
 2 Name:        ExampleCore_3_1_6.ObjTypes
 3 MethodTable: 00007ff9be290238
 4 EEClass:     00007ff9be27fca8
 5 Tracked Type: false
 6 Size:        48(0x30) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff9be2901c8  4000001       18 ...jTypes+Coordinate  1 instance 000002282e009cf8 coordinate
11 00007ff9be1c9df8  4000002        8       System.Int32[]  0 instance 000002282e009d10 intArray
12 00007ff9be26fac8  4000003       10      System.String[]  0 instance 000002282e009d80 strArray

                        这里我们开始关注 strArray 引用类型数组,也就是红色标注的。我们先使用【dp 000002282e009d80】命令转储一下。
                        

                        执行【!dumpmt 00007ff9`be26fac8】命令查看 strArray 数组的方法表。

 1 0:000> !dumpmt 00007ff9`be26fac8
 2 EEClass:         00007FF9BE11C440
 3 Module:          00007FF9BE084000
 4 Name:            System.String[]
 5 mdToken:         0000000002000000
 6 File:            C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 7 BaseSize:        0x18
 8 ComponentSize:   0x8
 9 DynamicStatics:  false
10 ContainsPointers true
11 Slots in VTable: 28
12 Number of IFaces in IFaceMap: 6

                        我们可以使用【!dumpobj】命令查看查看元素的具体信息。

 1 0:000> !do -nofields 00000268`c0280b78
 2 Name:        System.String
 3 MethodTable: 00007ff9be1cec08
 4 EEClass:     00007ff9be1aa500
 5 Tracked Type: false
 6 Size:        36(0x24) bytes
 7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 8 String:      Welcome
 9 
10 0:000> !do -nofields 00000268`c0280ba0
11 Name:        System.String
12 MethodTable: 00007ff9be1cec08
13 EEClass:     00007ff9be1aa500
14 Tracked Type: false
15 Size:        26(0x1a) bytes
16 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
17 String:      to
18 
19 0:000> !do -nofields 00000268`c0280bc0
20 Name:        System.String
21 MethodTable: 00007ff9be1cec08
22 EEClass:     00007ff9be1aa500
23 Tracked Type: false
24 Size:        38(0x26) bytes
25 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
26 String:      Advanced
27 
28 0:000> !do -nofields 00000268`c0280be8
29 Name:        System.String
30 MethodTable: 00007ff9be1cec08
31 EEClass:     00007ff9be1aa500
32 Tracked Type: false
33 Size:        30(0x1e) bytes
34 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
35 String:      .NET
36 
37 0:000> !do -nofields 00000268`c0280c08
38 Name:        System.String
39 MethodTable: 00007ff9be1cec08
40 EEClass:     00007ff9be1aa500
41 Tracked Type: false
42 Size:        40(0x28) bytes
43 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
44 String:      Debugging

                        当然,还有一个更容易,更直接的命令,就是【!dumparray】可以输出所有数组的信息。
                        

                        内容很简单,就不赘述了。

1 0:000> !do -nofields 00000268c0280b78
2 Name:        System.String
3 MethodTable: 00007ff9be1cec08
4 EEClass:     00007ff9be1aa500
5 Tracked Type: false
6 Size:        36(0x24) bytes
7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
8 String:      Welcome

                        可以继续使用【!do】命令查看元素的内容,这里就不演示了。


                2)、使用【Windbg Preview】调试
                    调试源码:ExampleCore_3_1_6
                    2.1)、值类型数组
                        编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们使用【g】命令,继续运行程序,直到我们的控制台程序输出:Press any key to continue(Arrays),我们的调试器也处于卡住的状态。此时点击【break】按钮,就可以调试程序了。
                        由于我们手动中断调试器的执行,所以需要切换到托管线程的上下文中,执行命令【~0s】。
1 0:001> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffa`eeaeae74 c3              ret

                        现在我们需要查看一下托管线程的调用栈,执行命令【!clrstack -a】。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3144 (0)
 3         Child SP               IP Call Site
 4 0000000C91F7E668 00007ffaeeaeae74 [InlinedCallFrame: 0000000c91f7e668] 
 5 0000000C91F7E668 00007ffa375c787a [InlinedCallFrame: 0000000c91f7e668] 
 6 。。。。。。(省略了)
 7 
 8 0000000C91F7E7F0 00007ff9b6fa1a2d ExampleCore_3_1_6.ObjTypes.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_6\Program.cs @ 36]
 9     PARAMETERS:
10         args (0x0000000C91F7E8C0) = 0x000001cfe3408ea0
11     LOCALS:
12         0x0000000C91F7E8A0 = 0x0000006400000064
13         0x0000000C91F7E898 = 0x000001cfe3409ce0
14         0x0000000C91F7E890 = 0x0000000000000000

                        0x000001cfe3409ce0 标红的就是 ObjTypes 类型的指针,执行命令【!do 0x000001cfe3409ce0】确认一下。

 1 0:000> !do 0x000001cfe3409ce0
 2 Name:        ExampleCore_3_1_6.ObjTypes
 3 MethodTable: 00007ff9b7050238
 4 EEClass:     00007ff9b703fca8
 5 Tracked Type: false
 6 Size:        48(0x30) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff9b70501c8  4000001       18 ...jTypes+Coordinate  1 instance 000001cfe3409cf8 coordinate
11 00007ff9b6f89df8  4000002        8       System.Int32[]  0 instance 000001cfe3409d10 intArray
12 00007ff9b702fac8  4000003       10      System.String[]  0 instance 000001cfe3409d80 strArray

                        我们看到了 ExampleCore_3_1_6.ObjTypes 类型有两个数组的域,分别是:intArray 和 strArray。我们获取了 intArray数组的地址:000001cfe3409d10 ,我们使用【!dumpobj 000001cfe3409d10】命令查看一下 intArray 数组的详情。

1 0:000> !dumpobj 000001cfe3409d10
2 Name:        System.Int32[]
3 MethodTable: 00007ff9b6f89df8
4 EEClass:     00007ff9b6f89d78
5 Tracked Type: false
6 Size:        44(0x2c) bytes
7 Array:       Rank 1, Number of elements 5, Type Int32 (Print Array)
8 Fields:
9 None

                        【!dumpobj】命令输出了数组的名称(Name),方法表(MethodTable),数组的维度(Rank),数组的类型(Type)和数组的元素个数(Number of elements),但是我们没有看到数组的元素内容。如果想查看数组的内容,我们可以使用【dp】命令。

1 0:000> dp 000001cfe3409d10
2 000001cf`e3409d10  00007ff9`b6f89df8 00000000`00000005
3 000001cf`e3409d20  00000002`00000001 00000004`00000003
4 000001cf`e3409d30  00000000`00000005 00000000`00000000
5 000001cf`e3409d40  00007ff9`b70a4468 00000000`00000000
6 000001cf`e3409d50  00000000`00000000 00000000`00000000
7 000001cf`e3409d60  00000000`00000000 00000000`00000000
8 000001cf`e3409d70  00007ff9`b70a3de0 00000000`00000000
9 000001cf`e3409d80  00007ff9`b702fac8 00000000`00000005

                        00007ff9`b6f89df8 第一个值就是数组本身的方法表的地址00000000`00000005 表示的数组元素个数,是 5 个元素。00000002`0000000100000004`0000000300000000`00000005 就是数组的元素内容,分别是:1,2,3,4,5。
                        00007ff9`b6f89df8 这个地址是数组本身的方法表,我们可以执行【!dumpmt 00007ff9`b6f89df8】命令验证一下。

 1 0:000> !dumpmt 00007ff9`b6f89df8
 2 EEClass:             00007ff9b6f89d78
 3 Module:              00007ff9b6e44000
 4 Name:                System.Int32[]
 5 mdToken:             0000000002000000
 6 File:                C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 7 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
 8 BaseSize:            0x18
 9 ComponentSize:       0x4
10 DynamicStatics:      false
11 ContainsPointers:    false
12 Slots in VTable:     28
13 Number of IFaces in IFaceMap: 6                     

                    2.2)、引用类型数组
                        编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们使用【g】命令,继续运行程序,直到我们的控制台程序输出:Press any key to continue(Arrays),我们的调试器也处于卡住的状态。此时点击【break】按钮,就可以调试程序了。
                        由于我们手动中断调试器的执行,所以需要切换到托管线程的上下文中,执行命令【~0s】。
1 0:001> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffa`eeaeae74 c3              ret

                        现在我们需要查看一下托管线程的调用栈,执行命令【!clrstack -a】。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3144 (0)
 3         Child SP               IP Call Site
 4 0000000C91F7E668 00007ffaeeaeae74 [InlinedCallFrame: 0000000c91f7e668] 
 5 0000000C91F7E668 00007ffa375c787a [InlinedCallFrame: 0000000c91f7e668] 
 6 。。。。。。(省略了)
 7 
 8 0000000C91F7E7F0 00007ff9b6fa1a2d ExampleCore_3_1_6.ObjTypes.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_6\Program.cs @ 36]
 9     PARAMETERS:
10         args (0x0000000C91F7E8C0) = 0x000001cfe3408ea0
11     LOCALS:
12         0x0000000C91F7E8A0 = 0x0000006400000064
13         0x0000000C91F7E898 = 0x000001cfe3409ce0
14         0x0000000C91F7E890 = 0x0000000000000000

                        0x000001cfe3409ce0 标红的就是 ObjTypes 类型的指针,执行命令【!do 0x000001cfe3409ce0】确认一下。

 1 0:000> !do 0x000001cfe3409ce0
 2 Name:        ExampleCore_3_1_6.ObjTypes
 3 MethodTable: 00007ff9b7050238
 4 EEClass:     00007ff9b703fca8
 5 Tracked Type: false
 6 Size:        48(0x30) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff9b70501c8  4000001       18 ...jTypes+Coordinate  1 instance 000001cfe3409cf8 coordinate
11 00007ff9b6f89df8  4000002        8       System.Int32[]  0 instance 000001cfe3409d10 intArray
12 00007ff9b702fac8  4000003       10      System.String[]  0 instance 000001cfe3409d80 strArray
                        我们看到了 ExampleCore_3_1_6.ObjTypes 类型有两个数组的域,分别是:intArray 和 strArray。这次我们关注 strArray 字符串数据,我们获取了 strArray 数组的地址:000001cfe3409d80 ,我们使用【!dumpobj 000001cfe3409d80】命令查看一下 strArray 数组的详情。
1 0:000> !dumpobj /d 000001cfe3409d80
2 Name:        System.String[]
3 MethodTable: 00007ff9b702fac8
4 EEClass:     00007ff9b6edc440
5 Tracked Type: false
6 Size:        64(0x40) bytes
7 Array:       Rank 1, Number of elements 5, Type CLASS (Print Array)
8 Fields:
9 None

                        我们也可以使用【dp 000001cfe3409d80】命令查看它的显示。

0:000> dp 000001cfe3409d80
000001cf`e3409d80  00007ff9`b702fac8 00000000`00000005
000001cf`e3409d90  00000210`75660b78 00000210`75660ba0
000001cf`e3409da0  00000210`75660bc0 00000210`75660be8
000001cf`e3409db0  00000210`75660c08 00000000`00000000
000001cf`e3409dc0  00007ff9`b6f11188 00000000`00000064
000001cf`e3409dd0  00000000`00000000 00007ff9`b6f11188
000001cf`e3409de0  00000000`00000064 00000000`00000000
000001cf`e3409df0  00007ff9`b6f11188 00000000`00000064

                        00007ff9`b702fac8 就是 strArray 数组的方法表,我们可以使用【!dumpmt 00007ff9`b702fac8】验证这点。

 1 0:000> !dumpmt 00007ff9`b702fac8
 2 EEClass:             00007ff9b6edc440
 3 Module:              00007ff9b6e44000
 4 Name:                System.String[]
 5 mdToken:             0000000002000000
 6 File:                C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 7 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
 8 BaseSize:            0x18
 9 ComponentSize:       0x8
10 DynamicStatics:      false
11 ContainsPointers:    true
12 Slots in VTable:     28
13 Number of IFaces in IFaceMap: 6

                        【dp 000001cfe3409d80】命令的 00000000`00000005 这个值就是 strArray 数组的元素个数。【dp 000001cfe3409d80】命令的5个输出就是数组元素的值,由于它们是引用类型,我们可以依次使用【!dumpobj】命令输出它们的内容。

0:000> !dumpobj 00000210`75660b78
Name:        System.String
MethodTable: 00007ff9b6f8ec08
EEClass:     00007ff9b6f6a500
Tracked Type: false
Size:        36(0x24) bytes
File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
String:      Welcome
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff9b6f11188  400033b        8         System.Int32  1 instance                7 _stringLength
00007ff9b6f1b538  400033c        c          System.Char  1 instance               57 _firstChar
00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty

0:000> !dumpobj 00000210`75660ba0
Name:        System.String
MethodTable: 00007ff9b6f8ec08
EEClass:     00007ff9b6f6a500
Tracked Type: false
Size:        26(0x1a) bytes
File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
String:      to
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff9b6f11188  400033b        8         System.Int32  1 instance                2 _stringLength
00007ff9b6f1b538  400033c        c          System.Char  1 instance               74 _firstChar
00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty

0:000> !dumpobj 00000210`75660bc0
Name:        System.String
MethodTable: 00007ff9b6f8ec08
EEClass:     00007ff9b6f6a500
Tracked Type: false
Size:        38(0x26) bytes
File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
String:      Advanced
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff9b6f11188  400033b        8         System.Int32  1 instance                8 _stringLength
00007ff9b6f1b538  400033c        c          System.Char  1 instance               41 _firstChar
00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty

0:000> !dumpobj 00000210`75660be8
Name:        System.String
MethodTable: 00007ff9b6f8ec08
EEClass:     00007ff9b6f6a500
Tracked Type: false
Size:        30(0x1e) bytes
File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
String:      .NET
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff9b6f11188  400033b        8         System.Int32  1 instance                4 _stringLength
00007ff9b6f1b538  400033c        c          System.Char  1 instance               2e _firstChar
00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty

0:000> !dumpobj 00000210`75660c08
Name:        System.String
MethodTable: 00007ff9b6f8ec08
EEClass:     00007ff9b6f6a500
Tracked Type: false
Size:        40(0x28) bytes
File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
String:      Debugging
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff9b6f11188  400033b        8         System.Int32  1 instance                9 _stringLength
00007ff9b6f1b538  400033c        c          System.Char  1 instance               44 _firstChar
00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty

                        红色标注的就是数组元素具体的值。
                        上面的命令有些繁琐,我们可以直接使用【!dumparray】命令获取我们想要的数据。
                        我们直接输出 intArray,使用【!dumparray】命令。

 1 0:000> !dumparray 000001cfe3409d10
 2 Name:        System.Int32[]
 3 MethodTable: 00007ff9b6f89df8
 4 EEClass:     00007ff9b6f89d78
 5 Size:        44(0x2c) bytes
 6 Array:       Rank 1, Number of elements 5, Type Int32
 7 Element Methodtable: 00007ff9b6f11188
 8 [0] 000001cfe3409d20
 9 [1] 000001cfe3409d24
10 [2] 000001cfe3409d28
11 [3] 000001cfe3409d2c
12 [4] 000001cfe3409d30
13 
14 0:000> !DumpVC /d 00007ff9b6f11188 000001cfe3409d20
15 Name:        System.Int32
16 MethodTable: 00007ff9b6f11188
17 EEClass:     00007ff9b6f01e20
18 Size:        24(0x18) bytes
19 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
20 Fields:
21               MT    Field   Offset                 Type VT     Attr            Value Name
22 00007ff9b6f11188  400051d        0         System.Int32  1 instance                1 m_value
23 
24 0:000> !DumpVC /d 00007ff9b6f11188 000001cfe3409d24
25 Name:        System.Int32
26 MethodTable: 00007ff9b6f11188
27 EEClass:     00007ff9b6f01e20
28 Size:        24(0x18) bytes
29 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
30 Fields:
31               MT    Field   Offset                 Type VT     Attr            Value Name
32 00007ff9b6f11188  400051d        0         System.Int32  1 instance                2 m_value
33 
34 0:000> !DumpVC /d 00007ff9b6f11188 000001cfe3409d28
35 Name:        System.Int32
36 MethodTable: 00007ff9b6f11188
37 EEClass:     00007ff9b6f01e20
38 Size:        24(0x18) bytes
39 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
40 Fields:
41               MT    Field   Offset                 Type VT     Attr            Value Name
42 00007ff9b6f11188  400051d        0         System.Int32  1 instance                3 m_value
43 
44 0:000> !DumpVC /d 00007ff9b6f11188 000001cfe3409d2c
45 Name:        System.Int32
46 MethodTable: 00007ff9b6f11188
47 EEClass:     00007ff9b6f01e20
48 Size:        24(0x18) bytes
49 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
50 Fields:
51               MT    Field   Offset                 Type VT     Attr            Value Name
52 00007ff9b6f11188  400051d        0         System.Int32  1 instance                4 m_value
53 
54 0:000> !DumpVC /d 00007ff9b6f11188 000001cfe3409d30
55 Name:        System.Int32
56 MethodTable: 00007ff9b6f11188
57 EEClass:     00007ff9b6f01e20
58 Size:        24(0x18) bytes
59 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
60 Fields:
61               MT    Field   Offset                 Type VT     Attr            Value Name
62 00007ff9b6f11188  400051d        0         System.Int32  1 instance                5 m_value

                        我们直接输出strArray,使用【!dumparray】命令。

 1 0:000> !dumparray 000001cfe3409d80
 2 Name:        System.String[]
 3 MethodTable: 00007ff9b702fac8
 4 EEClass:     00007ff9b6edc440
 5 Size:        64(0x40) bytes
 6 Array:       Rank 1, Number of elements 5, Type CLASS
 7 Element Methodtable: 00007ff9b6f8ec08
 8 [0] 0000021075660b78
 9 [1] 0000021075660ba0
10 [2] 0000021075660bc0
11 [3] 0000021075660be8
12 [4] 0000021075660c08
13 
14 0:000> !DumpObj /d 0000021075660b78
15 Name:        System.String
16 MethodTable: 00007ff9b6f8ec08
17 EEClass:     00007ff9b6f6a500
18 Tracked Type: false
19 Size:        36(0x24) bytes
20 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
21 String:      Welcome
22 Fields:
23               MT    Field   Offset                 Type VT     Attr            Value Name
24 00007ff9b6f11188  400033b        8         System.Int32  1 instance                7 _stringLength
25 00007ff9b6f1b538  400033c        c          System.Char  1 instance               57 _firstChar
26 00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty
27 
28 0:000> !DumpObj /d 0000021075660ba0
29 Name:        System.String
30 MethodTable: 00007ff9b6f8ec08
31 EEClass:     00007ff9b6f6a500
32 Tracked Type: false
33 Size:        26(0x1a) bytes
34 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
35 String:      to
36 Fields:
37               MT    Field   Offset                 Type VT     Attr            Value Name
38 00007ff9b6f11188  400033b        8         System.Int32  1 instance                2 _stringLength
39 00007ff9b6f1b538  400033c        c          System.Char  1 instance               74 _firstChar
40 00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty
41 
42 0:000> !DumpObj /d 0000021075660bc0
43 Name:        System.String
44 MethodTable: 00007ff9b6f8ec08
45 EEClass:     00007ff9b6f6a500
46 Tracked Type: false
47 Size:        38(0x26) bytes
48 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
49 String:      Advanced
50 Fields:
51               MT    Field   Offset                 Type VT     Attr            Value Name
52 00007ff9b6f11188  400033b        8         System.Int32  1 instance                8 _stringLength
53 00007ff9b6f1b538  400033c        c          System.Char  1 instance               41 _firstChar
54 00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty
55 
56 0:000> !DumpObj /d 0000021075660be8
57 Name:        System.String
58 MethodTable: 00007ff9b6f8ec08
59 EEClass:     00007ff9b6f6a500
60 Tracked Type: false
61 Size:        30(0x1e) bytes
62 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
63 String:      .NET
64 Fields:
65               MT    Field   Offset                 Type VT     Attr            Value Name
66 00007ff9b6f11188  400033b        8         System.Int32  1 instance                4 _stringLength
67 00007ff9b6f1b538  400033c        c          System.Char  1 instance               2e _firstChar
68 00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty
69 
70 0:000> !DumpObj /d 0000021075660c08
71 Name:        System.String
72 MethodTable: 00007ff9b6f8ec08
73 EEClass:     00007ff9b6f6a500
74 Tracked Type: false
75 Size:        40(0x28) bytes
76 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
77 String:      Debugging
78 Fields:
79               MT    Field   Offset                 Type VT     Attr            Value Name
80 00007ff9b6f11188  400033b        8         System.Int32  1 instance                9 _stringLength
81 00007ff9b6f1b538  400033c        c          System.Char  1 instance               44 _firstChar
82 00007ff9b6f8ec08  400033a       c8        System.String  0   static 0000021075660008 Empty

        4.1.5、栈上对象的转储
            A、基础知识
                每个在 CLR 上下文中运行的托管线程都有一组相关数据。除了 CLR 维护的记录数据外,每个线程还包含了一些基本的信息,这些信息使CLR 能够维持栈和参数以及局部变量的完整性。在大多数时候,我们可以使用【!clrstack】命令找出每个栈帧的局部变量和参数,但是有时候需要对栈进行更深入的分析,就可以使用【!dumpstackobjects】命令。它能对栈进行遍历,并输出栈上所有的托管对象。
                命令格式:!DumpStackObjects [-verify] [top stack [bottom stack]],
                如果没有指定任何参数,那么 DumpStackObjects 会输出当前线程的所有托管对象。
                verify 选项表示对找到的每个托管对象进行一个验证过程,这对对象是否被破坏来说非常有用。
                如果想对这个命令的输出进行限制,需要指定一个范围(栈顶[top stack]和栈低[bottom stack])。
                这个命令确实有点长,当然也有一个缩写形式:dso,可以直接执行命令【!dso】。

            B、眼见为实
                1)、使用【NTSD】调试
                    调试源码:ExampleCore_3_1_6
                    编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.exe】。
                    
                    打开【NTSD】调试器窗口。
                    

                    继续使用【g】命令运行调试器,直到调试器输出:Press any key to continue(Generics),然后,我们手动【ctrl+c】进入调试器的中断模式,就可以调试了。
                    

                    我们进入中断模式后,需要切换线程到托管线程,执行命令【~0s】。

1 0:002> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffa`eeaeae74 c3              ret

                    我们直接执行【!dso】或者【!dumpstackobjects】命令。

 1 0:000> !dso
 2 OS Thread Id: 0x1d8c (0)
 3 RSP/REG          Object           Name
 4 000000F45E99E8A8 00000235eb220b30 Interop+INPUT_RECORD
 5 000000F45E99E918 00000235eb220b30 Interop+INPUT_RECORD
 6 000000F45E99E920 000001f559009c98 System.Object
 7 000000F45E99E950 00000235eb220560 System.String    Press any key to continue(Generics)
 8 000000F45E99E9B8 000001f559009c38 System.IO.TextWriter+SyncTextWriter
 9 000000F45E99E9C0 000001f559009ce0 ExampleCore_3_1_6.ObjTypes
10 000000F45E99EA28 000001f559009ce0 ExampleCore_3_1_6.ObjTypes
11 000000F45E99EA48 000001f559009ce0 ExampleCore_3_1_6.ObjTypes
12 000000F45E99EA70 000001f559008ea0 System.String[]
13 000000F45E99EB18 000001f559008ea0 System.String[]
14 000000F45E99ED10 000001f559008ea0 System.String[]
15 000000F45E99ED18 000001f559008ea0 System.String[]
16 000000F45E99EE30 000001f559008ea0 System.String[]
17 000000F45E99EEB0 000001f559008eb8 System.String    E:\Visual Studio 2022\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
18 000000F45E99EEC0 000001f559008ea0 System.String[]
19 000000F45E99EED0 000001f559008e80 System.String[]
20 000000F45E99EF08 000001f559008eb8 System.String    E:\Visual Studio 2022\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
21 000000F45E99F0B8 000001f559008ea0 System.String[]

                    在输出结果中有 3 列:
                    SP/REG:该列表式托管对象所在的栈地址。
                    Object:是托管对象的地址。我们可以使用【!dumpobj】命令查看对象的详情。
                    Name:该列表示托管对象的名称。
                    在输出的内容中,我们看到有几行是完全相同的,这也在预料之中,因为对象可能从一个函数传递另一个函数,其中每个栈帧都包含了对同一个对象的引用。

                2)、Windbg Preview 调试
                    调试源码:ExampleCore_3_1_6
                    编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们使用【g】命令,继续运行程序,直到我们的控制台程序输出:Press any key to continue(Generics),我们的调试器也处于卡住的状态。此时点击【break】按钮,就可以调试程序了。
                    由于我们手动中断调试器的执行,所以需要切换到托管线程的上下文中,执行命令【~0s】。
1 0:007> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffa`eeaeae74 c3              ret

                    直接执行命令【!DumpStackObjects】。

 1 0:000> !DumpStackObjects
 2 OS Thread Id: 0x3a04 (0)
 3           SP/REG           Object Name
 4     00c21e57e918     026cd5cb1378 System.String
 5     00c21e57ea28     026cd5cb0b30 Interop+INPUT_RECORD
 6     00c21e57ea98     026cd5cb0b30 Interop+INPUT_RECORD
 7     00c21e57eaa0     022c43c09c98 System.Object
 8     00c21e57ead0     026cd5cb0560 System.String
 9     00c21e57eb38     022c43c09c38 System.IO.TextWriter+SyncTextWriter
10     00c21e57eb40     022c43c09ce0 ExampleCore_3_1_6.ObjTypes
11     00c21e57eba8     022c43c09ce0 ExampleCore_3_1_6.ObjTypes
12     00c21e57ebc8     022c43c09ce0 ExampleCore_3_1_6.ObjTypes
13     00c21e57ebf0     022c43c08ea0 System.String[]
14     00c21e57ec98     022c43c08ea0 System.String[]
15     00c21e57ee90     022c43c08ea0 System.String[]
16     00c21e57ee98     022c43c08ea0 System.String[]
17     00c21e57efb0     022c43c08ea0 System.String[]
18     00c21e57f030     022c43c08eb8 System.String
19     00c21e57f040     022c43c08ea0 System.String[]
20     00c21e57f050     022c43c08e80 System.String[]
21     00c21e57f088     022c43c08eb8 System.String
22     00c21e57f238     022c43c08ea0 System.String[]

                    在输出结果中有 3 列:
                    SP/REG:该列表式托管对象所在的栈地址。
                    Object:是托管对象的地址。我们可以使用【!dumpobj】命令查看对象的详情。
                    Name:该列表示托管对象的名称。
                    在输出的内容中,我们看到有几行是完全相同的,这也在预料之中,因为对象可能从一个函数传递另一个函数,其中每个栈帧都包含了对同一个对象的引用。

                    
        4.1.6、找出对象的大小
            A、基础知识
                我们能找到一个对象的准确大小,对于我们排查一些问题是很有用的。我们知道【!dumpobj】命令可以输出一个对象的大小。如图:
                

                这种方法对我们找到单个对象的确切大小是非常有用的。但是,对象有时候会引用其他对象,而这些其他对象有可能引用另外的对象,以此类推。此时,知道对象的总体大小(包括遍历每个类型域的大小)能够有助于了解一些非常大和复杂的对象。我们可以使用【!objsize】命令。
                如果在运行【!ObjSize】命令时没有指定地址,那么这个命令将列出进程中所有托管线程中的所有对象的大小,需要说明一点,在【Windbg Preview】调试器里必须输入一个地址,如果在【NTSD】或者【CDB】调试器中可以执行【!ObjSize】命令,可以不用跟任何参数,效果如图
                


            B、眼见为实
                1)、使用【NTSD】调试
                    调试源码:ExampleCore_3_1_8
                    编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_8\bin\Debug\net8.0\ExampleCore_3_1_8.exe】。
                    

                    打开【NTSD】调试器窗口。
                    

                    我们通过【g】命令直接运行调试器,知道调试器输出:名称:PatrickLiu,地址:China-冀州-直隶总督府-广平大街23号-213339。此时,调试器卡住,我们按【ctrl+c】进入调试器中断模式。
                    

                    我们切换到托管线程0下,执行命令【~0s】。

1 0:001> ~0s
2 ucrtbase!strcmp+0x49:
3 00007ffa`ec654e69 4e8d0c10        lea     r9,[rax+r10]

                    我们使用【!clrstack -a】命令查看一下托管线程的调用栈。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x2798 (0)
 3         Child SP               IP Call Site
 4 0000006CF777E238 00007ffaec654e69 [ExternalMethodFrame: 0000006cf777e238]
 5 。。。。。。(省略了)
 6 0000006CF777E9A0 00007FF996FC1B34 ExampleCore_3_1_8.Program.Main(System.String[])
 7     PARAMETERS:
 8         args (0x0000006CF777EA40) = 0x0000023e84c08ea0
 9     LOCALS:
10         0x0000006CF777EA28 = 0x0000023e84c09c98
11         0x0000006CF777EA00 = 0x0000000000000000

                    0x0000023e84c09c98 红色标注的就是 Person 类型的局部变量 person 的地址,我们使用【!dumpobj 0x0000023e84c09c98】查看一下它的详情。

 1 0:000> !dumpobj 0x0000023e84c09c98
 2 Name:        ExampleCore_3_1_8.Person
 3 MethodTable: 00007ff997079480
 4 EEClass:     00007ff997082008
 5 Tracked Type: false
 6 Size:        40(0x28) bytes
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_8\bin\Debug\net8.0\ExampleCore_3_1_8.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff996f31188  4000001       18         System.Int32  1 instance               32 <Age>k__BackingField
11 00007ff996faec08  4000002        8        System.String  0 instance 0000027f16be0500 <Name>k__BackingField
12 00007ff99707d9a0  4000003       10 ...ore_3_1_8.Address  0 instance 0000023e84c09cc0 <HomeAddress>k__BackingField

                      红色标注就是它的大小。我们再使用【!ObjSize 0x0000023e84c09c98】命令查看一下它的大小。

1 0:000> !ObjSize 0x0000023e84c09c98
2 sizeof(0000023E84C09C98) = 320 (0x140) bytes (ExampleCore_3_1_8.Person)

                      【!objsize】命令和【!dumpobj】命令输出的大小区别还是挺大的。
                      【!objsize】命令在【NTSD】调试器模式下,如果没有指定任何地址,它会输出当前进程中所有托管线程上的所有托管对象的大小。Windbg Preview 执行错误:ystem.ArgumentException: Could not parse target object address: 0。

 1 0:000> !ObjSize
 2 Thread 2798 ([ExternalMethodFrame: 0000006cf777e238] ): 0000006cf777e810 -> 0000023e84c0b148: 4120 (0x1018) bytes (System.Byte[])
 3 Thread 2798 ([ExternalMethodFrame: 0000006cf777e238] ): 0000006cf777e818 -> 0000027f16be0a90: 32 (0x20) bytes (System.String)
 4 Thread 2798 (System.Text.DecoderDBCS.GetChars(Byte[], Int32, Int32, Char[], Int32, Boolean) [/_/src/libraries/Common/src/System/Text/DBCSDecoder.cs @ 130]): rbp: 0000023e84c0b0b8: 232 (0xe8) bytes (System.Text.DecoderDBCS)
 5 Thread 2798 (System.Text.DecoderDBCS.GetChars(Byte[], Int32, Int32, Char[], Int32, Boolean) [/_/src/libraries/Common/src/System/Text/DBCSDecoder.cs @ 130]): rsi: 0000023e84c0b148: 4120 (0x1018) bytes (System.Byte[])
 6 Thread 2798 (System.Text.DecoderDBCS.GetChars(Byte[], Int32, Int32, Char[], Int32, Boolean) [/_/src/libraries/Common/src/System/Text/DBCSDecoder.cs @ 130]): r15: 0000023e84c0c178: 32792 (0x8018) bytes (System.Char[])
 7 Thread 2798 (System.IO.StreamReader.ReadBuffer() [/_/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs @ 604]): rbx: 0000023e84c0b058: 37416 (0x9228) bytes (System.IO.StreamReader)
 8 Thread 2798 (System.IO.StreamReader.Read() [/_/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs @ 338]): rbx: 0000023e84c0b058: 37416 (0x9228) bytes (System.IO.StreamReader)
 9 Thread 2798 (System.IO.SyncTextReader.Read() [/_/src/libraries/System.Console/src/System/IO/SyncTextReader.cs @ 53]): rbp+10: 0000006cf777e970 -> 0000023e84c14190: 37440 (0x9240) bytes (System.IO.SyncTextReader)
10 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp-58: 0000006cf777e9d8 -> 0000006cf777ea00 (interior): 0 (0x0) bytes (unknown type)
11 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp-48: 0000006cf777e9e8 -> 0000006cf777ea00 (interior): 0 (0x0) bytes (unknown type)
12 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp-68: 0000006cf777e9c8 -> 0000023e84c0a9d8: 120 (0x78) bytes (System.String)
13 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp-60: 0000006cf777e9d0 -> 0000023e84c09cc0: 232 (0xe8) bytes (ExampleCore_3_1_8.Address)
14 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp-50: 0000006cf777e9e0 -> 0000027f16be0500: 48 (0x30) bytes (System.String)
15 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp-40: 0000006cf777e9f0 -> 0000023e84c09cc0: 232 (0xe8) bytes (ExampleCore_3_1_8.Address)
16 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp-38: 0000006cf777e9f8 -> 0000023e84c09c98: 320 (0x140) bytes (ExampleCore_3_1_8.Person)
17 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp-8: 0000006cf777ea28 -> 0000023e84c09c98: 320 (0x140) bytes (ExampleCore_3_1_8.Person)
18 Thread 2798 (ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio 2022\...\ExampleCore_3_1_8\Program.cs @ 13]): rbp+10: 0000006cf777ea40 -> 0000023e84c08ea0: 24 (0x18) bytes (System.String[])
19 Failed to enumerate GC references.
20 Failed to walk thread 31d4
21 Handle (strong): 0000023E804A1378 -> 0000023E84C0A2D8: 1024 (0x400) bytes (System.Object[])
22 Handle (strong): 0000023E804A1380 -> 0000023E84C0A228: 176 (0xb0) bytes (System.Object[])
23 Handle (strong): 0000023E804A1388 -> 0000023E84C0A180: 88 (0x58) bytes (System.Diagnostics.Tracing.EventPipeEventProvider)
24 Handle (strong): 0000023E804A1390 -> 0000023E84C0A0D0: 112 (0x70) bytes (System.Diagnostics.Tracing.EtwEventProvider)
25 Handle (strong): 0000023E804A1398 -> 0000023E82402020: 8232 (0x2028) bytes (System.Object[])
26 Handle (strong): 0000023E804A13A0 -> 0000023E84C092F0: 88 (0x58) bytes (System.Diagnostics.Tracing.EventPipeEventProvider)
27 Handle (strong): 0000023E804A13A8 -> 0000023E84C095B8: 88 (0x58) bytes (System.Diagnostics.Tracing.EventPipeEventProvider)
28 Handle (strong): 0000023E804A13B0 -> 0000023E84C09508: 112 (0x70) bytes (System.Diagnostics.Tracing.EtwEventProvider)
29 Handle (strong): 0000023E804A13B8 -> 0000023E84C09240: 112 (0x70) bytes (System.Diagnostics.Tracing.EtwEventProvider)
30 Handle (strong): 0000023E804A13C8 -> 0000023E84C00188: 128 (0x80) bytes (System.ExecutionEngineException)
31 Handle (strong): 0000023E804A13D0 -> 0000023E84C00108: 128 (0x80) bytes (System.StackOverflowException)
32 Handle (strong): 0000023E804A13D8 -> 0000023E84C00088: 128 (0x80) bytes (System.OutOfMemoryException)
33 Handle (strong): 0000023E804A13E0 -> 0000023E84C00028: 96 (0x60) bytes (System.Int32[])
34 Handle (strong): 0000023E804A13E8 -> 0000023E82400028: 87704 (0x15698) bytes (System.Object[])
35 Handle (pinned): 0000023E804A15F8 -> 0000023E84C00208: 24 (0x18) bytes (System.Object)

                2)、使用【Windbg Preview 】调试
                    调试源码:ExampleCore_3_1_8
                    编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们使用【g】命令,继续运行程序,直到我们的控制台程序输出:名称:PatrickLiu,地址:China-冀州-直隶总督府-广平大街23号-213339,我们的调试器也处于卡住的状态。此时点击【break】按钮,就可以调试程序了。
                    切换到托管线程,执行命令【~0s】。
1 0:007> ~0s
2 ntdll!NtReadFile+0x14:
3 00007ffa`eeaeae54 c3              ret

                    我们执行命令【!clrstack -a】查看托管线程栈。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3cf8 (0)
 3         Child SP               IP Call Site
 4 000000CD8377E2C0 00007ffaeeaeae54 [InlinedCallFrame: 000000cd8377e2c0] 
 5 000000CD8377E2C0 00007ffa2d8276eb [InlinedCallFrame: 000000cd8377e2c0] 
 6 。。。。。。(省略了)
 7 
 8 000000CD8377E590 00007ff997991b34 ExampleCore_3_1_8.Program.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_3_1_8\Program.cs @ 13]
 9     PARAMETERS:
10         args (0x000000CD8377E630) = 0x0000024827408ea0
11     LOCALS:
12         0x000000CD8377E618 = 0x0000024827409c98
13         0x000000CD8377E5F0 = 0x0000000000000000

                    0x0000024827409c98 红色标注的就是 Person 类型的局部变量 person 的地址。我们可以使用【!dumpobj 0x0000024827409c98】命令查看这个对象的大小。

 1 0:000> !dumpobj 0x0000024827409c98
 2 Name:        ExampleCore_3_1_8.Person
 3 MethodTable: 00007ff997a49480
 4 EEClass:     00007ff997a52008
 5 Tracked Type: false
 6 Size:        40(0x28) bytes(这里就是大小)
 7 File:        E:\Visual Studio 2022\...\ExampleCore_3_1_8\bin\Debug\net8.0\ExampleCore_3_1_8.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff997901188  4000001       18         System.Int32  1 instance               32 <Age>k__BackingField
11 00007ff99797ec08  4000002        8        System.String  0 instance 00000288b93b0500 <Name>k__BackingField
12 00007ff997a4d9a0  4000003       10 ...ore_3_1_8.Address  0 instance 0000024827409cc0 <HomeAddress>k__BackingField

                    我们使用【!objsize 0x0000024827409c98】这个命令看看它的大小。

 1 0:000> !objsize 0x0000024827409c98
 2 Objects which 24827409c98(ExampleCore_3_1_8.Person) transitively keep alive:
 3 
 4          Address               MT           Size
 5     024827409c98     7ff997a49480             40 
 6     0288b93b0500     7ff99797ec08             42 
 7     024827409cc0     7ff997a4d9a0             56 
 8     0288b93b0530     7ff99797ec08             32 
 9     0288b93b0550     7ff99797ec08             26 
10     0288b93b0570     7ff99797ec08             32 
11     0288b93b0590     7ff99797ec08             36 
12     0288b93b05b8     7ff99797ec08             34 
13 
14 Statistics:
15           MT Count TotalSize Class Name
16 7ff997a49480     1        40 ExampleCore_3_1_8.Person
17 7ff997a4d9a0     1        56 ExampleCore_3_1_8.Address
18 7ff99797ec08     6       202 System.String
19 Total 8 objects, 298 bytes

                    这个结果还是很详细的。我们看到了【!objsize】命令得到的是 298 bytes,而【!dumpobj】得到的大小是 40 bytes,这个差别还是挺大的。

        4.1.7、异常的转储
            A、基础知识
                Windows 的异常模型采用的是结构化异常处理(Structured Exception Handling,SEH)。同样,.NET异常模型也是构建在 Windows SEH模型之上的,并提供了基于对象的异常模型。CLR 在每个异常内携带的额外信息都被保存到托管堆上。所有 CLR 异常都以 SEH 异常的形式出现,错误码是:0xe0434352。既然所有的异常的错误码都是 0xe0434352,我们是如何区分异常的不同的呢?答案就是依靠异常中保存的扩展信息。(说明,原书错误码是:0xe0434f4d,平台是 .NET Framework,我的错误码是:0xe0434352,平台是.NET 8.0)
                使用 Windbg Preview 调试器的效果如图:

                
                使用 NTSD 调试器的效果如图:
                
                原著中的异常代码如图:
                
                在 CLR 看来,异常也是一种引用类型,所以也可以使用【!dumpobj】命令输出它的信息。这个命令有些繁琐,如果我们只希望显示异常的类型、栈回溯(包括内部栈回溯)以及信息,我们就可以使用【!PrintException】命令。这个命令的参数托管异常的地址。当然,我们在分析异常时,【!threads】命令也会经常用得到,它能显示出系统中各个托管线程的信息,包括该线程抛出的最后一个异常。
                【StopOnException】命令是在抛出特定异常时设置一个断点,和异常信息的转储关系不大。
                命令的格式:
                !StopOnException [-derived] [-create|-create2] <Exception> [<Pseudo-register number>],create 和 create2 这两个开关控制着断点是在第一次出现指定的异常时触发,还是第二次出现时触发。derived 开关会增加断点的范围,不仅包含指定的异常,而且还包括从指定异常派生的任意异常。pseudo-register 是可选的,表示命令将使用哪个伪寄存器设置断点,如果没有指定伪寄存器,默认是 $t1。
                例子:
                  !StopOnException -create System.ArgumentException,表示当第一次抛出 System.ArgumentException 异常时设置断点。
                  !StopOnException -derived System.Exception ,表示对从 System.Exception 派生下来的任意异常都设置一个断点。

            B、眼见为实
                1)、使用【NTSD】调试
                    调试源码:ExampleCore_3_1_6
                    编译项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.2】命令行工具,输入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.exe】。
                    

                    打开【NTSD】调试器。
                    

                    使用【g】命令运行调试器,直到调试器抛出异常,调试器中断执行。
                    

                    我们使用【kb】命令,查看托管调用栈的栈回溯。

0:000> kb
RetAddr               : Args to Child                                                           : Call Site
00007ffb`00a9ca6f     : 000001b8`72c0aca8 0000002c`9517e710 0000002c`9517ea00 00007ffb`00a7bbb4 : KERNELBASE!RaiseException+0x69
00007ffb`00a9c129     : 00000000`70000185 00007ffb`00d961c8 0000002c`9517eb88 0000002c`9517ed28 : coreclr!RaiseTheExceptionInternalOnly+0x26b
00007ffa`a0fc2416     : 000001b8`72c0aca8 000001f9`04c913c8 000001b8`700fa1e8 00000000`00000040 : coreclr!IL_Throw+0xb9
00007ffa`a0fc1afd     : 000001b8`72c09ce0 00000000`00000000 000001b8`6e7af410 000001b8`72c09c38 : 0x00007ffa`a0fc2416
00007ffb`00b2a1a3     : 000001b8`72c08ea0 0000002c`9517f128 0000002c`9517f128 0000002c`9517ed19 : 0x00007ffa`a0fc1afd
00007ffb`00ab14c9     : 00000000`00000000 00000000`00000130 0000002c`9517ed28 00007ffb`009da456 : coreclr!CallDescrWorkerInternal+0x83
(Inline Function)     : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!CallDescrWorkerWithHandler+0x56
00007ffb`009d75ac     : 0000002c`9517eda8 00000000`00000000 00000000`00000048 00007ffb`00ac28a6 : coreclr!MethodDescCallSite::CallTargetWorker+0x2a1
(Inline Function)     : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!MethodDescCallSite::Call+0xb
00007ffb`009d6f7a     : 000001b8`72c08ea0 000001b8`72c08ea0 00000000`00000000 0000002c`9517f128 : coreclr!RunMainInternal+0x11c
00007ffb`009d6b17     : 000001b8`6e7af410 000001b8`00000000 000001b8`6e7af410 00000000`00000000 : coreclr!RunMain+0xd2
00007ffb`009d7321     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000130 : coreclr!Assembly::ExecuteMainMethod+0x1bf
00007ffb`00ae7768     : 00000000`00000001 0000002c`9517f301 0000002c`9517f350 00007ffb`86ff23ea : coreclr!CorHost2::ExecuteAssembly+0x281
00007ffb`87012c36     : 000001b8`6e780fd0 000001b8`6e780310 00000000`00000000 000001b8`6e780310 : coreclr!coreclr_execute_assembly+0xd8
(Inline Function)     : --------`-------- --------`-------- --------`-------- --------`-------- : hostpolicy!coreclr_t::execute_assembly+0x2a
00007ffb`87012f1c     : 000001b8`6e76bcc8 0000002c`9517f579 00007ffb`8704c9c0 000001b8`6e76bcc8 : hostpolicy!run_app_for_context+0x596
00007ffb`8701385a     : 00000000`00000000 000001b8`6e76bcc0 000001b8`6e76bcc0 00000000`00000000 : hostpolicy!run_app+0x3c
00007ffb`b3e2b5c9     : 000001b8`6e77f438 000001b8`6e77f320 00000000`00000000 0000002c`9517f679 : hostpolicy!corehost_main+0x15a
00007ffb`b3e2e066     : 000001b8`6e77e330 0000002c`9517fa00 00000000`00000000 00000000`00000000 : hostfxr!execute_app+0x2e9
00007ffb`b3e302ec     : 00007ffb`b3e625f8 000001b8`6e77cb40 0000002c`9517f940 0000002c`9517f8f0 : hostfxr!`anonymous namespace'::read_config_and_execute+0xa6
00007ffb`b3e2e644     : 0000002c`9517fa00 0000002c`9517fa20 0000002c`9517f971 000001b8`6e77cf60 : hostfxr!fx_muxer_t::handle_exec_host_command+0x16c
00007ffb`b3e285a0     : 0000002c`9517fa20 000001b8`6e77c440 00000000`00000001 000001b8`6e760000 : hostfxr!fx_muxer_t::execute+0x494
*** WARNING: Unable to verify checksum for apphost.exe
00007ff6`5faef998     : 00007ffb`d241f4e8 00007ffb`b3e29b10 0000002c`9517fbc0 000001b8`6e77c130 : hostfxr!hostfxr_main_startupinfo+0xa0
00007ff6`5faefda6     : 00007ff6`5fafb6c0 00000000`00000007 000001b8`6e76bcc0 00000000`0000005e : apphost!exe_start+0x878
00007ff6`5faf12e8     : 00000000`00000000 00000000`00000000 000001b8`6e76bcc0 00000000`00000000 : apphost!wmain+0x146
(Inline Function)     : --------`-------- --------`-------- --------`-------- --------`-------- : apphost!invoke_main+0x22
00007ffb`d33f6fd4     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : apphost!__scrt_common_main_seh+0x10c
00007ffb`d4b7cec1     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
0:000>

                     接下来,我们找到 KERNELBASE!RaiseException 函数(000001b8`72c0aca8)的第一个参数,在该地址上执行【!dumpobj】命令,查看异常详情。

 1 0:000> !DumpObj 000001b8`72c0aca8
 2 Name:        System.ArgumentException
 3 MethodTable: 00007ffaa10467b0
 4 EEClass:     00007ffaa0f8cdf8
 5 Tracked Type: false
 6 Size:        136(0x88) bytes
 7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 0000000000000000  4000264        8 ...ection.MethodBase  0 instance 0000000000000000 _exceptionMethod
11 00007ffaa0faec08  4000265       10        System.String  0 instance 000001f904c913c8 _message
12 00007ffaa1043060  4000266       18 ...tions.IDictionary  0 instance 0000000000000000 _data
13 00007ffaa1040820  4000267       20     System.Exception  0 instance 0000000000000000 _innerException
14 00007ffaa0faec08  4000268       28        System.String  0 instance 0000000000000000 _helpURL
15 00007ffaa10aa858  4000269       30        System.Byte[]  0 instance 000001b872c0ad78 _stackTrace
16 00007ffaa10aa858  400026a       38        System.Byte[]  0 instance 0000000000000000 _watsonBuckets
17 00007ffaa0faec08  400026b       40        System.String  0 instance 0000000000000000 _stackTraceString
18 00007ffaa0faec08  400026c       48        System.String  0 instance 0000000000000000 _remoteStackTraceString
19 00007ffaa0efc4d8  400026d       50      System.Object[]  0 instance 0000000000000000 _dynamicMethods
20 00007ffaa0faec08  400026e       58        System.String  0 instance 0000000000000000 _source
21 00007ffaa0fa8b78  400026f       60       System.UIntPtr  1 instance 00007FFAA0FC2415 _ipForWatsonBuckets
22 00007ffaa0fa70a0  4000270       68        System.IntPtr  1 instance 0000000000000000 _xptrs
23 00007ffaa0f31188  4000271       70         System.Int32  1 instance       -532462766 _xcode
24 00007ffaa0f31188  4000272       74         System.Int32  1 instance      -2147024809 _HResult
25 00007ffaa0faec08  4000383       78        System.String  0 instance 0000000000000000 _paramName
26 0:000>

                    以上就是异常的详情,如果我们想查看具体提示消息,可以针对 _message 域的地址 000001f904c913c8 再次执行【!DumpObj】命令。

 1 0:000> !dumpobj 000001f904c913c8
 2 Name:        System.String
 3 MethodTable: 00007ffaa0faec08
 4 EEClass:     00007ffaa0f8a500
 5 Tracked Type: false
 6 Size:        58(0x3a) bytes
 7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 8 String:      Obj cannot be null
 9 Fields:
10               MT    Field   Offset                 Type VT     Attr            Value Name
11 00007ffaa0f31188  400033b        8         System.Int32  1 instance               18 _stringLength
12 00007ffaa0f3b538  400033c        c          System.Char  1 instance               4f _firstChar
13 00007ffaa0faec08  400033a       c8        System.String  0   static 000001f904c90008 Empty

                    【!dumpobj】命令有些繁琐,我们可以直接使用【!PrintException】命令输出异常信息。

 1 0:000> !PrintException 000001b8`72c0aca8
 2 WARNING: SOS needs to be upgraded for this version of the runtime. Some commands may not work correctly.
 3 For more information see https://go.microsoft.com/fwlink/?linkid=2135652
 4 
 5 Exception object: 000001b872c0aca8
 6 Exception type:   System.ArgumentException
 7 Message:          Obj cannot be null
 8 InnerException:   <none>
 9 StackTrace (generated):
10     SP               IP               Function
11     0000002C9517E9C0 00007FFAA0FC2415 ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.ThrowException(ExampleCore_3_1_6.ObjTypes)+0x85
12     0000002C9517EA10 00007FFAA0FC1AFC ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.Main(System.String[])+0x1cc
13 
14 StackTraceString: <none>
15 HResult: 80070057

                    在这里看的很清楚,包括异常的类型(Exception typeSystem.ArgumentException),异常的地址(Exception object: 000001b872c0aca8),异常抛出的方法(ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.ThrowException)等。
                    当然,我们可以使用【!threads】或者【!t】命令输出所有托管线程的信息,包括托管线程抛出的最有一个异常。
                    

                    【!threads】命令执行的效果。

 1 0:000> !threads
 2 ThreadCount:      3
 3 UnstartedThread:  0
 4 BackgroundThread: 2
 5 PendingThread:    0
 6 DeadThread:       0
 7 Hosted Runtime:   no
 8                                                                                                             Lock
 9  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
10    0    1     1190 000001B86E7AF410    2a020 Preemptive  000001B872C0ADF0:000001B872C0C638 000001B86E7F1530 -00001 MTA System.ArgumentException 000001b872c0aca8
11    6    2     3b64 000001B8700EDDA0    21220 Preemptive  0000000000000000:0000000000000000 000001B86E7F1530 -00001 Ukn (Finalizer)
12    7    3     1630 000001B86E7BBF00    2b220 Preemptive  0000000000000000:0000000000000000 000001B86E7F1530 -00001 MTA

                    红色标注的就是 ID 为 1 的托管线程抛出的异常(System.ArgumentException),异常的地址:000001b872c0aca8


                2)、使用【Windbg Preview】调试
                    调试源码:ExampleCore_3_1_6
                    编译项目,打开 Windbg,点击【文件】----》【launch executable】附加程序,打开调试器的界面,程序已经处于中断状态。我们使用【g】命令,继续运行程序,直到我们的控制台程序抛出一个异常,调试器停止执行。
 1 0:000> g
 2 ModLoad: 00007ffb`d4ac0000 00007ffb`d4af0000   C:\Windows\System32\IMM32.DLL
 3 ModLoad: 00007ffb`c8d40000 00007ffb`c8d99000   C:\Program Files\dotnet\host\fxr\8.0.2\hostfxr.dll
 4 ModLoad: 00007ffb`c8cd0000 00007ffb`c8d34000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\hostpolicy.dll
 5 ModLoad: 00007ffa`fcab0000 00007ffa`fcf98000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\coreclr.dll
 6 ModLoad: 00007ffb`d3b60000 00007ffb`d3c89000   C:\Windows\System32\ole32.dll
 7 ModLoad: 00007ffb`d34a0000 00007ffb`d37f4000   C:\Windows\System32\combase.dll
 8 ModLoad: 00007ffb`d3200000 00007ffb`d32d5000   C:\Windows\System32\OLEAUT32.dll
 9 ModLoad: 00007ffb`d1fc0000 00007ffb`d203f000   C:\Windows\System32\bcryptPrimitives.dll
10 (2e10.1988): Unknown exception - code 04242420 (first chance)
11 ModLoad: 00007ffa`fb810000 00007ffa`fc49c000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
12 ModLoad: 00007ffa`fb650000 00007ffa`fb809000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\clrjit.dll
13 ModLoad: 00007ffb`d1f00000 00007ffb`d1f13000   C:\Windows\System32\kernel.appcore.dll
14 ModLoad: 00000293`f6f10000 00000293`f6f18000   E:\Visual Studio 2022\...\ExampleCore_3_1_6\bin\Debug\net8.0\ExampleCore_3_1_6.dll
15 ModLoad: 00000293`f6f30000 00000293`f6f3e000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Runtime.dll
16 ModLoad: 00007ffb`c8c70000 00007ffb`c8c98000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Console.dll
17 ModLoad: 00007ffb`79470000 00007ffb`79482000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Threading.dll
18 ModLoad: 00000293`f6f40000 00000293`f6f48000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Text.Encoding.Extensions.dll
19 ModLoad: 00007ffb`5dc90000 00007ffb`5dca5000   C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Runtime.InteropServices.dll
20 (2e10.1988): CLR exception - code e0434352 (first chance)
21 (2e10.1988): CLR exception - code e0434352 (!!! second chance !!!)
22 KERNELBASE!RaiseException+0x69:
23 00007ffb`d2563e49 0f1f440000      nop     dword ptr [rax+rax]

                    我们使用【kb】命令查看非托管线程的调用栈,并显示前4个参数。

 1 0:000> kb
 2  # RetAddr               : Args to Child                                                           : Call Site
 3 00 00007ffa`fcb7ca6f     : 00000293`fb40aca8 000000d7`0477e1f0 000000d7`0477e4e0 00007ffa`fcb5bbb4 : KERNELBASE!RaiseException+0x69
 4 01 00007ffa`fcb7c129     : 00000000`70000185 00007ffa`fce761c8 000000d7`0477e668 000000d7`0477e808 : coreclr!RaiseTheExceptionInternalOnly+0x26b [D:\a\_work\1\s\src\coreclr\vm\excep.cpp @ 2795] 
 5 02 00007ffa`9d0a24b6     : 00000293`fb40aca8 000002d4`8d5813c8 00000293`f6fbe1e8 00000000`00000040 : coreclr!IL_Throw+0xb9 [D:\a\_work\1\s\src\coreclr\vm\jithelpers.cpp @ 4247] 
 6 03 00007ffa`9d0a1afd     : 00000293`fb409ce0 00000000`00000000 00000293`f6fa6280 00000293`fb409c38 : ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.ThrowException+0x86 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\Program.cs @ 76] 
 7 04 00007ffa`fcc0a1a3     : 00000293`fb408ea0 000000d7`0477ec08 000000d7`0477ec08 000000d7`0477e7f9 : ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.Main+0x1cd [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_6\Program.cs @ 46] 
 8 05 00007ffa`fcb914c9     : 00000000`00000000 00000000`00000130 000000d7`0477e808 00007ffa`fcaba456 : coreclr!CallDescrWorkerInternal+0x83 [D:\a\_work\1\s\src\coreclr\vm\amd64\CallDescrWorkerAMD64.asm @ 100] 
 9 06 (Inline Function)     : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!CallDescrWorkerWithHandler+0x56 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 67] 
10 07 00007ffa`fcab75ac     : 000000d7`0477e888 00000000`00000000 00000000`00000048 00007ffa`fcba28a6 : coreclr!MethodDescCallSite::CallTargetWorker+0x2a1 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 570] 
11 08 (Inline Function)     : --------`-------- --------`-------- --------`-------- --------`-------- : coreclr!MethodDescCallSite::Call+0xb [D:\a\_work\1\s\src\coreclr\vm\callhelpers.h @ 458] 
12 09 00007ffa`fcab6f7a     : 00000293`fb408ea0 00000293`fb408ea0 00000000`00000000 000000d7`0477ec08 : coreclr!RunMainInternal+0x11c [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1304] 
13 0a 00007ffa`fcab6b17     : 00000293`f6fa6280 00000293`00000000 00000293`f6fa6280 00000000`00000000 : coreclr!RunMain+0xd2 [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1375] 
14 0b 00007ffa`fcab7321     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000130 : coreclr!Assembly::ExecuteMainMethod+0x1bf [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1504] 
15 0c 00007ffa`fcbc7768     : 00000000`00000001 000000d7`0477ee01 000000d7`0477ee30 00007ffb`c8cd23ea : coreclr!CorHost2::ExecuteAssembly+0x281 [D:\a\_work\1\s\src\coreclr\vm\corhost.cpp @ 349] 
16 0d 00007ffb`c8cf2c36     : 00000293`f6f7a900 00000293`f6f7a660 00000000`00000000 00000293`f6f7a660 : coreclr!coreclr_execute_assembly+0xd8 [D:\a\_work\1\s\src\coreclr\dlls\mscoree\exports.cpp @ 504] 
17 0e (Inline Function)     : --------`-------- --------`-------- --------`-------- --------`-------- : hostpolicy!coreclr_t::execute_assembly+0x2a [D:\a\_work\1\s\src\native\corehost\hostpolicy\coreclr.cpp @ 109] 
18 0f 00007ffb`c8cf2f1c     : 00000293`f6f67da8 000000d7`0477f059 00007ffb`c8d2c9c0 00000293`f6f67da8 : hostpolicy!run_app_for_context+0x596 [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 256] 
19 10 00007ffb`c8cf385a     : 00000000`00000000 00000293`f6f67da0 00000293`f6f67da0 00000000`00000000 : hostpolicy!run_app+0x3c [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 285] 
20 11 00007ffb`c8d4b5c9     : 00000293`f6f769e8 00000293`f6f768d0 00000000`00000000 000000d7`0477f159 : hostpolicy!corehost_main+0x15a [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 426] 
21 12 00007ffb`c8d4e066     : 00000293`f6f77540 000000d7`0477f4e0 00000000`00000000 00000000`00000000 : hostfxr!execute_app+0x2e9 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 145] 
22 13 00007ffb`c8d502ec     : 00007ffb`c8d825f8 00000293`f6f780d0 000000d7`0477f420 000000d7`0477f3d0 : hostfxr!`anonymous namespace'::read_config_and_execute+0xa6 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 532] 
23 14 00007ffb`c8d4e644     : 000000d7`0477f4e0 000000d7`0477f500 000000d7`0477f451 00000293`f6f78401 : hostfxr!fx_muxer_t::handle_exec_host_command+0x16c [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 1007] 
24 15 00007ffb`c8d485a0     : 000000d7`0477f500 00000293`f6f76690 00000000`00000001 00000293`f6f60000 : hostfxr!fx_muxer_t::execute+0x494 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 578] 
25 16 00007ff7`6c9ff998     : 00007ffb`d241f4e8 00007ffb`c8d49b10 000000d7`0477f6a0 00000293`f6f76380 : hostfxr!hostfxr_main_startupinfo+0xa0 [D:\a\_work\1\s\src\native\corehost\fxr\hostfxr.cpp @ 62] 
26 17 00007ff7`6c9ffda6     : 00007ff7`6ca0b6c0 00000000`00000007 00000293`f6f67da0 00000000`0000005e : apphost!exe_start+0x878 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 240] 
27 18 00007ff7`6ca012e8     : 00000000`00000000 00000000`00000000 00000293`f6f67da0 00000000`00000000 : apphost!wmain+0x146 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 311] 
28 19 (Inline Function)     : --------`-------- --------`-------- --------`-------- --------`-------- : apphost!invoke_main+0x22 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 90] 
29 1a 00007ffb`d33f6fd4     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : apphost!__scrt_common_main_seh+0x10c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
30 1b 00007ffb`d4b7cec1     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
31 1c 00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21

                    接下来,找出 KERNELBASE!RaiseException 函数的(000001f8`4c80aca8)第一个参数,并在这个地址上执行【!dumpobj 000001f8`4c80aca8】命令。

 1 0:000> !DumpObj 000001f8`4c80aca8
 2 Name:        System.ArgumentException
 3 MethodTable: 00007ffaa06e67b0
 4 EEClass:     00007ffaa062cdf8
 5 Tracked Type: false
 6 Size:        136(0x88) bytes
 7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 0000000000000000  4000264        8 ...ection.MethodBase  0 instance 0000000000000000 _exceptionMethod
11 00007ffaa064ec08  4000265       10        System.String  0 instance 00000238deaf13c8 _message
12 00007ffaa06e3060  4000266       18 ...tions.IDictionary  0 instance 0000000000000000 _data
13 00007ffaa06e0820  4000267       20     System.Exception  0 instance 0000000000000000 _innerException
14 00007ffaa064ec08  4000268       28        System.String  0 instance 0000000000000000 _helpURL
15 00007ffaa074a858  4000269       30        System.Byte[]  0 instance 000001f84c80ad78 _stackTrace
16 00007ffaa074a858  400026a       38        System.Byte[]  0 instance 0000000000000000 _watsonBuckets
17 00007ffaa064ec08  400026b       40        System.String  0 instance 0000000000000000 _stackTraceString
18 00007ffaa064ec08  400026c       48        System.String  0 instance 0000000000000000 _remoteStackTraceString
19 00007ffaa059c4d8  400026d       50      System.Object[]  0 instance 0000000000000000 _dynamicMethods
20 00007ffaa064ec08  400026e       58        System.String  0 instance 0000000000000000 _source
21 00007ffaa0648b78  400026f       60       System.UIntPtr  1 instance 00007FFAA0662415 _ipForWatsonBuckets
22 00007ffaa06470a0  4000270       68        System.IntPtr  1 instance 0000000000000000 _xptrs
23 00007ffaa05d1188  4000271       70         System.Int32  1 instance       -532462766 _xcode
24 00007ffaa05d1188  4000272       74         System.Int32  1 instance      -2147024809 _HResult
25 00007ffaa064ec08  4000383       78        System.String  0 instance 0000000000000000 _paramName

                    从【!dumpobj】命令的输出中,我们可以看到托管代码异常的所有信息,包括异常的类型(System.ArgumentException)以及异常相关的所有域。我们可以针对标红的 _message 再次执行【!dumpobj 00000238deaf13c8】命令,查看异常的具体提示信息。

 1 0:000> !dumpobj 00000238deaf13c8
 2 Name:        System.String
 3 MethodTable: 00007ffaa064ec08
 4 EEClass:     00007ffaa062a500
 5 Tracked Type: false
 6 Size:        58(0x3a) bytes
 7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
 8 String:      Obj cannot be null
 9 Fields:
10               MT    Field   Offset                 Type VT     Attr            Value Name
11 00007ffaa05d1188  400033b        8         System.Int32  1 instance               18 _stringLength
12 00007ffaa05db538  400033c        c          System.Char  1 instance               4f _firstChar
13 00007ffaa064ec08  400033a       c8        System.String  0   static 00000238deaf0008 Empty

                    简写方式输出。

1 0:000> !DumpObj -nofields 00000238deaf13c8
2 Name:        System.String
3 MethodTable: 00007ffaa064ec08
4 EEClass:     00007ffaa062a500
5 Tracked Type: false
6 Size:        58(0x3a) bytes
7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.2\System.Private.CoreLib.dll
8 String:      Obj cannot be null

                    上面我们使用【!dumpobj】命令可以查看异常信息,但是比较繁琐。我们可以使用【!PrintException】命令输出异常信息。

 1 0:000> !PrintException 000001f8`4c80aca8
 2 Exception object: 000001f84c80aca8
 3 Exception type:   System.ArgumentException
 4 Message:          Obj cannot be null
 5 InnerException:   <none>
 6 StackTrace (generated):
 7     SP               IP               Function
 8     0000007BA577E6D0 00007FFAA0662415 ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.ThrowException(ExampleCore_3_1_6.ObjTypes)+0x85
 9     0000007BA577E720 00007FFAA0661AFC ExampleCore_3_1_6!ExampleCore_3_1_6.ObjTypes.Main(System.String[])+0x1cc
10 
11 StackTraceString: <none>
12 HResult: 80070057

                    这里输出的内容更清晰,是哪个方法抛出的异常都可以看到(ThrowException)。我们分析异常的时候,可以使用【!threads】或者【!t】命令查看所有托管线程信息,包括该线程抛出的最后一个异常。

 1 0:000> !threads
 2 ThreadCount:      3
 3 UnstartedThread:  0
 4 BackgroundThread: 2
 5 PendingThread:    0
 6 DeadThread:       0
 7 Hosted Runtime:   no
 8                                                                                                             Lock  
 9  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
10    0    1     379c 000001F8485263D0    2a020 Preemptive  000001F84C80ADF0:000001F84C80C638 000001f848568920 -00001 MTA System.ArgumentException 000001f84c80aca8
11    6    2     10d4 000001F8485D7C40    21220 Preemptive  0000000000000000:0000000000000000 000001f848568920 -00001 Ukn (Finalizer) 
12    7    3     3204 000001F8485346F0    2b220 Preemptive  0000000000000000:0000000000000000 000001f848568920 -00001 MTA 
13 
14 
15 0:000> !t
16 ThreadCount:      3
17 UnstartedThread:  0
18 BackgroundThread: 2
19 PendingThread:    0
20 DeadThread:       0
21 Hosted Runtime:   no
22                                                                                                             Lock  
23  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
24    0    1     379c 000001F8485263D0    2a020 Preemptive  000001F84C80ADF0:000001F84C80C638 000001f848568920 -00001 MTA System.ArgumentException 000001f84c80aca8
25    6    2     10d4 000001F8485D7C40    21220 Preemptive  0000000000000000:0000000000000000 000001f848568920 -00001 Ukn (Finalizer) 
26    7    3     3204 000001F8485346F0    2b220 Preemptive  0000000000000000:0000000000000000 000001f848568920 -00001 MTA 

                    我们看到了 ID 为 1 的线程抛出一个类型为 System.ArgumentException 异常,异常的地址:000001f84c80aca8 ,我们可以针对这个地址使用【!DumpObj】或者【!PrintException】命令查看异常的详情。


五、总结
    这篇文章的第二部分终于写完了,考虑到内容太多,分多篇写作了。只有所有代码自己都写了,都测试了,才会明白里面发生的事情。Net 高级调试这条路,也刚刚起步,还有很多要学的地方。皇天不负有心人,努力,不辜负自己,我相信付出就有回报,再者说,学习的过程,有时候,虽然很痛苦,但是,学有所成,学有所懂,这个开心的感觉还是不可言喻的。不忘初心,继续努力。做自己喜欢做的,开心就好。
posted on 2024-03-08 14:24  可均可可  阅读(388)  评论(0编辑  收藏  举报