一、简介
    这是 2024 新年后我的第一篇文章,也是我的《Advanced .Net Debugging》这个系列的第二篇文章。这篇文章告诉我们为了进行有效的程序调试,我们需要掌握哪些知识。言归正传,无论采取什么形式来分析问题,对被调试系统的底层了解的越多,就越有可能成功的找出问题的根源。在 Net 领域,同样适用,即我们需要理解【运行时 Runtime】本身的各种功能和行为。了解了垃圾收集器的工作原理将使你在调试内存泄漏问题是更加高效。了解了互用性的工作原理将使你在调试COM问题时更加高效,而了解了同步机制的工作原理将使你在调试挂起问题时更加高效。今天我们就来了解一些 Net 底层的东西,让我们做到知其然知其所以然。
    如果在没有说明的情况下,所有代码的测试环境都是 Net 8.0,如果有变动,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。

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

二、目录结构

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

    2.1、高层预览
    2.2、CLR 和 Windows 加载器
        A、基础知识
        B、眼见为实
        2.2.1、加载非托管映像
            A、基础知识
            B、眼见为实
        2.2.2、加载 .NET 程序集
            A、基础知识
            B、眼见为实
    2.3、应用程序域
        A、基础知识
        B、眼见为实
        2.3.1、系统应用程序域
        2.3.2、共享应用程序域(跨平台版本已经取消)
        2.3.3、默认应用程序域
    2.4、程序集简介
        A、基础知识
        B、眼见为实
    2.5、程序集清单
        A、基础知识
        B、眼见为实
    2.6、类型元数据
        A、基础知识
        B、眼见为实
        2.6.1、同步块表
            A、基础知识
            B、眼见为实
        2.6.2、类型句柄
            A、基础知识
            B、眼见为实
        2.6.3、方法描述符
            A、基础知识
            B、眼见为实
        2.6.4、模块
            A、基础知识
            B、眼见为实
        2.6.5、元数据标记
            A、基础知识
            B、眼见为实
        2.6.6、EEClass
            A、基础知识
            B、眼见为实
        
三、调试源码
    废话不多说,本节是调试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。

    3.1、ExampleCore_2_1_1
 1 using System.Diagnostics;
 2 
 3 namespace ExampleCore_2_1_1
 4 {
 5     internal class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Console.WriteLine("Welcome to Advanced .Net Debugging!");
10             Debugger.Break();
11         }
12     }
13 }
View Code

    3.2、ExampleCore_2_1_2

 1 using System.Diagnostics;
 2 
 3 namespace ExampleCore_2_1_2
 4 {
 5     internal class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             TypeSample sample = new TypeSample(10, 5, 10);
10             Debugger.Break();
11             sample.AddCoordinates();
12             Console.ReadLine();
13         }
14     }
15 
16     /// <summary>
17     /// 引用类型
18     /// </summary>
19     public class TypeSample
20     {
21         public TypeSample(int x, int y, int z)
22         {
23             coordinates.x = x;
24             coordinates.y = y;
25             coordinates.z = z;
26         }
27 
28         /// <summary>
29         /// 值类型
30         /// </summary>
31         private struct Coordinates
32         {
33             public int x;
34             public int y;
35             public int z;
36         }
37 
38         private Coordinates coordinates;
39 
40         public void AddCoordinates()
41         {
42             int hashCode = GetHashCode();
43             lock (this)
44             {
45                 Debugger.Break();
46                 Coordinates tempCoord;
47                 tempCoord.x = coordinates.x + 100;
48                 tempCoord.y = coordinates.y + 50;
49                 tempCoord.z = coordinates.z + 100;
50 
51                 Console.WriteLine("x={0},y={1},z={2}", tempCoord.x, tempCoord.y, tempCoord.z);
52             }
53         }
54     }
55 }
View Code

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

    1、高层预览
        从高层面上看来,Net 是一个虚拟的运行时环境,它包含了一个虚拟执行引擎---通用语言运行时(CLR)及其一组相关的框架库。我们通过一幅图来仔细查看和理解一下 Net 由那些组件构成的,效果如图:
            
        我们从这张图上可以看到 Net 分成四块,由里到外:ECMA、CLR、NET Framework、Net Application。
        ECMA:Net 的核心就是 ECMA 标准,表示 NET 运行时的所有实现都必须遵从这个标准,说白了,它就是一个规范,如果有人想实现自己的运行时,就按这个标准来肯定没问题。描述这个标准的文档叫通用语言基础架构(CLI)。CLI不仅为运行时本身定义了一组规则,还包括一组非常关键的通用类库,这组类库被称为【基础类型库(Base Class Libraries,BCL)】。
        CLR:它的中文名称叫【通用语言运行时】,它就是 Microsoft 对 CLI 的实现。
        Net 框架:该框架包含了开发人员在编写 Net 应用程序时需要使用的所有库。比如:WCF、WPF、Web MVC、Web API、WinForm 等。
        Net应用程序:我们自己编写的各种应用程序了,比如:MES、ERP、CMS等。

        我们知道了Net 的组成,也要知道Net的执行模型。效果如图:
        
        Net 程序源代码就是码农门写的东西,这些源代码经过编译器生成一种中间语言MSIL。非托管应用程序的源代码在被编译和链接之后将直接转换为特定于 CPU 的指令,而MSIL 则不同,它是一种与平台无关的更高级的语言。编译的输出结果就是程序集。当 Net 程序集运行时,CLR将被自动加载并开始执行MSIL,MSIL代码将被 JIT(即时编译器)转换为机器指令,来完成其功能。

    2、CLR 和 Windows 加载器
        A、基础知识
            托管程序和非托管程序是不一样的,非托管程序可以直接执行,但是 Net 的托管程序有两次编译,第一次编译获取的程序集不是机器码,不能直接执行的。那为什么 Windows 加载器可以将托管程序和非托管程序采取一样的启动方式呢?答案就是依赖 Windows 上的一种文件格式:可移植的可执行的文件格式(Portable Executable,PE)。PE映像文件一般结构如图:
                
            为了支持 PE 映像的执行,在PE的头部包含了叫做【AddressOfEntryPoint】的域,这个域表示PE文件的入口点位置。我们可以使用 PPEE 工具查看PE文件信息。PPEE 是用来查看 PE 文件格式的工具,使用很简单,菜单也很少。官网下载地址:https://www.mzrst.com/
            我在这里再次强调一次,当我们使用 PPEE 工具查看 PE 文件的时候,必须使用 .DLL,不要使用 .EXE,只有在 .DLL 的 PE 文件里才有针对 NET 扩展的数据结构。

        B、眼见为实
            调试源码:ExampleCore_2_1_1
            调试任务:通过 PPEE 工具了解 PE 文件
            这个项目不需要实际的代码,大家可以根据自己喜欢建立,使用 PPEE 文件打开我们编译而成的 .DLL 文件,在左侧有很多节点,我们可以依次点击【NT Header】--->【Optional Header】,在右侧就可以看到有一个栏目:AddressOfEntryPoint,因为我的程序是 Net 程序,Comment 里面显示的.text,在 Net 程序集中,这个值指向 .text 段中的一小段存根(stub)代码。 如图:
             
            在PE头文件还有一个域,是【DIRECTORY_ENTRY_COM_DESCRIPTOR】,这个域是专门为支持 Net 应用程序增加的,它的图标也是【Net】,在这个域中包含了许多的信息,比如:托管代码应用程序的入口点(EntryPointToken),目标 CLR 的主版本号(MajorRuntimeVersion)和从版本号(MinorRuntimeVersion),以及程序集的强名称签名(StrongNameSignature)等。如图:
                 

            在【DIRECTORY_ENTRY_COM_DESCRIPTOR】这个域下还有一个节点是【MetaData】,这个节点包含的是Net 程序包含的元数据结构信息,【DIRECTORY_ENTRY_COM_DESCRIPTOR】本身的【MetaData】字段包含了一个【.text】内容,在【.text】段中包含了程序集的元数据表,MSIL以及非托管启动存根代码。非托管启动存根代码包含了有 Windows 加载器执行以启动 PE 文件执行的代码。如图:

                 
            有了这些信息,Windows 可以知道要加载哪个版本的 CLR以及关于程序集本身的一些最基本信息。

        2.1、加载非托管映像
            A、基础知识
                我们看看 Windows 加载器是如何加载非托管的 PE 映像的。我们以 notepad.exe 为例(它的路径:C:\Windows\notepad.exe)。我们需要查看 notepad 的 PE 文件,如果想查看它的 PE文件,我们必须提前准备 PPEE.exe 工具,它是专门用于查看 PE 文件的工具。
                      【应用场合】查看 Windows 应用程序的 PE 文件。
                      【下载地址】https://www.mzrst.com/
                      【软件版本】1.12

                在开始操作之前,我们先来了解2个概念:文件偏移(file offset)和相对虚地址(Relative Virtual Address)。
                      文件偏移(file offset):指 PE 文件中任意位置的偏移量。
                      相对虚地址(Relative Virtual Address):仅当 PE 映像已经被加载后才需要使用这个值,它是在进程虚拟地址空间中的相对地址。例如:一个值为 0x200 RVA 表示当映像被加载到内存后离映像基地址 0x200 字节的位置。

            B、眼见为实
                调试源码:无源码
                调试任务:加载非托管映像(NotePad)
                我们使用 PPEE.exe 文件打开 notepad.exe 文件,当然,也可以直接将 notepad.exe 文件拖到 PPEE 工具的空白区,就会打开 notepad.exe 软件的 PE 文件,效果如图:
                

                我们在 PPEE 工作区左侧,依次点击【NT Header】--->【Optional Header】,在右侧我们就能看到【AddressOfEntryPoint】域,它的值是:00023BE0,这个值就是 RVA 的值,我们可以使用 Windbg 加载 notepad.exe,使用【lm】命令查看加载的所有模块。      
 1  0:007> lm
 2 start             end                 module name
 3 00007ff6`895a0000 00007ff6`895d8000   notepad    (pdb symbols)          C:\ProgramData\Dbg\sym\notepad.pdb\FF9C9991EA5CB351AF10D24FCBA2CE391\notepad.pdb
 4 00007ffe`f9b90000 00007ffe`f9c6c000   efswrt     (deferred)             
 5 00007fff`19aa0000 00007fff`19b06000   oleacc     (deferred)             
 6 00007fff`1b030000 00007fff`1b2ca000   COMCTL32   (deferred)             
 7 00007fff`24400000 00007fff`244fc000   textinputframework   (deferred)             
 8 00007fff`24650000 00007fff`24744000   MrmCoreR   (deferred)             
 9 00007fff`263c0000 00007fff`2646e000   TextShaping   (deferred)             
10 00007fff`278b0000 00007fff`27ab2000   twinapi_appcore   (deferred)             
11 00007fff`29240000 00007fff`2925d000   MPR        (deferred)             
12 00007fff`2c0f0000 00007fff`2c246000   wintypes   (deferred)             
13 00007fff`2c850000 00007fff`2cbaa000   CoreUIComponents   (deferred)             
14 00007fff`2cbb0000 00007fff`2cca2000   CoreMessaging   (deferred)             
15 00007fff`2cfc0000 00007fff`2d05f000   uxtheme    (deferred)             
16 00007fff`2e120000 00007fff`2e8aa000   windows_storage   (deferred)             
17 00007fff`2f9f0000 00007fff`2fa23000   ntmarta    (deferred)             
18 00007fff`306b0000 00007fff`306db000   Wldp       (deferred)             
19 00007fff`30ca0000 00007fff`30f67000   KERNELBASE   (deferred)             
20 00007fff`30f70000 00007fff`30f92000   win32u     (deferred)             
21 00007fff`30fa0000 00007fff`3103d000   msvcp_win   (deferred)             
22 00007fff`31230000 00007fff`3133a000   gdi32full   (deferred)             
23 00007fff`31500000 00007fff`3157f000   bcryptPrimitives   (deferred)             
24 00007fff`31650000 00007fff`31750000   ucrtbase   (deferred)             
25 00007fff`31750000 00007fff`31763000   kernel_appcore   (deferred)             
26 00007fff`31780000 00007fff`3181e000   msvcrt     (deferred)             
27 00007fff`31880000 00007fff`31928000   clbcatq    (deferred)             
28 00007fff`31930000 00007fff`31c84000   combase    (deferred)             
29 00007fff`31c90000 00007fff`31d2b000   sechost    (deferred)             
30 00007fff`31ef0000 00007fff`31f5b000   WS2_32     (deferred)             
31 00007fff`31f60000 00007fff`32100000   USER32     (deferred)             
32 00007fff`32230000 00007fff`32305000   OLEAUT32   (deferred)             
33 00007fff`32880000 00007fff`3292a000   ADVAPI32   (deferred)             
34 00007fff`32930000 00007fff`329ed000   KERNEL32   (pdb symbols)          C:\ProgramData\Dbg\sym\kernel32.pdb\85A257DB4B7B82F2E19AD96AB7BB116A1\kernel32.pdb
35 00007fff`32a10000 00007fff`32a65000   shlwapi    (deferred)             
36 00007fff`32a70000 00007fff`32a9a000   GDI32      (deferred)             
37 00007fff`32aa0000 00007fff`32bb5000   MSCTF      (deferred)             
38 00007fff`32bf0000 00007fff`32c9e000   shcore     (deferred)             
39 00007fff`32e30000 00007fff`32e60000   IMM32      (deferred)             
40 00007fff`32e60000 00007fff`32f83000   RPCRT4     (deferred)             
41 00007fff`33140000 00007fff`33871000   SHELL32    (deferred)             
42 00007fff`338d0000 00007fff`33ac4000   ntdll      (pdb symbols)          C:\ProgramData\Dbg\sym\ntdll.pdb\63E12347526A46144B98F8CF61CDED791\ntdll.pdb
View Code
                【00007ff6`895a0000-00007ff6`895d8000   notepad 】这行信息就是加载 notepad.exe 的起始和结束地址空间,也就是说 notepad.exe 实例被加载在地址 00007ff6`895a0000 处。我们又知道了 notepad.exe 的【AddressOfEntryPoint】的 RVA 值(00023BE0),用开始地址(也叫基地址:00007ff6`895a0000)加上 RVA 值就是 notepad.exe 的【wWinMainCRTStartup】的地址,这个函数也就是应用程序的入口点。我们可以使用 【u 00007ff6`895a0000+00023BE0】命令证明一下。
 1     0:007> u 00007ff6`895a0000+00023BE0
 2 notepad!wWinMainCRTStartup:
 3 00007ff6`895c3be0 4883ec28        sub     rsp,28h
 4 00007ff6`895c3be4 e86b090000      call    notepad!_security_init_cookie (00007ff6`895c4554)
 5 00007ff6`895c3be9 4883c428        add     rsp,28h
 6 00007ff6`895c3bed e96efeffff      jmp     notepad!__scrt_common_main_seh (00007ff6`895c3a60)
 7 00007ff6`895c3bf2 cc              int     3
 8 00007ff6`895c3bf3 cc              int     3
 9 00007ff6`895c3bf4 cc              int     3
10 00007ff6`895c3bf5 cc              int     3
                【wWinMainCRTStartup】是一个外层包装函数,它在调用 Notepad.exe 的 WinMain 函数之前执行一些 CRT 初始化工作。这就是 Windows 加载器在加载映像的过程中用到的信息,当然,PE 文件中还包含大量其他信息,这个过程也说明了 Windows 加载器是如何找到并执行 PE 映像文件的入口点。

        2.2、加载 Net 程序集
            A、基础知识
                在开始之前,要说明一下,现在的.Net 版本是跨平台的版本了,和 .Net Framework 版本有本质区别。在生成文件的时候也有很大的区别,会有一个 .EXE 文件和一个同名的 .DLL 文件,这个 .DLL 文件才是 .NET 下的程序集,无论是使用反编译工具还是 PE 工具,在查看的时候一定要注意。
                如果想观察 Windows 加载器是如何加载 Net 程序集的,最简单的办法就是观察一个简单的 NET 命令行程序。由于 NET 应用程序在执行时要预先加载 CLR,那 Windows 是如何加载并初始化 CLR 的。这就要提到之前说过的 PE 文件了,微软对 PE 文件进行了扩展。前面提到过,PE 格式是 Windows 可执行程序的文件格式,用来管理 PE 文件中代码的执行。可执行程序包括 EXE、DLL、OBJ、SYS 等文件。为了支持 NET,在 PE 文件格式中增加了对程序集的支持。
                效果如图:
                
                我在这里再次强调一次,当我们使用 PPEE 工具查看 PE 文件的时候,必须使用 .DLL,不要使用 .EXE,只有在 .DLL 的 PE 文件里才有针对 NET 扩展的数据结构。
                为了更好的说明 Windows 加载器是如何加载 .NET 程序集的,我使用了一个工具【dumpbin.exe】,它能够解析 PE 文件格式并且以简洁易读的形式转储 PE 文件的内容。官网下载地址:https://github.com/Delphier/dumpbin。对于我们而言,一般不需要独立下载该工具,【dumpbin.exe】这个工具是包含在 MSVC 工具集中的,如果我们在安装了 Visual Studio 的时候,并选择【使用 C++ 桌面开发】工作负载,Visual Studio 安装完成,【dumpbin.exe】工具也已经安装好了。
                效果如图:
                  

                如果没有安装或者不会使用,我们也可以在微软的网站上找到解决办法:https://learn.microsoft.com/zh-cn/cpp/build/building-on-the-command-line?view=msvc-170,我们安装好 Visual Studio 2022 后,在开始菜单里就可以找到 Visual Studio 的安装文件夹,里面包含很多【命令行工具】,这些工具就可以使用【dumpbin.exe】工具。

                      安装目录,如图:
                  
                运行效果,如图:
                  
   
            B、眼见为实
                调试源码:ExampleCore_2_1_1
                调试任务:见证 Windows 加载器加载 Net 程序集。              
                编译我们的项目,打开【Visual Studio 2022 Developer Command Prompt v17.9.6】,我们必须切换到程序集所在的目录,如果没有切换到程序集所在的目录,可以使用【dumpbin /all "E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_1\bin\Debug\net8.0\ExampleCore_2_1_1.dll"】,为什么程序集的路径要使用双引号括起来,是因为路径中有空格。在这里,我已经切换到程序集所在的目录,就直接执行【dumpbin /all ExampleCore_2_1_1.dll】命令,我将输出结果直接输出:
  1 D:\Program Files\Microsoft Visual Studio\2022\Community>dumpbin /all "E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_1\bin\Debug\net8.0\ExampleCore_2_1_1.dll"
  2 Microsoft (R) COFF/PE Dumper Version 14.39.33523.0
  3 Copyright (C) Microsoft Corporation.  All rights reserved.
  4 
  5 
  6 Dump of file E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_1\bin\Debug\net8.0\ExampleCore_2_1_1.dll
  7 
  8 PE signature found
  9 
 10 File Type: EXECUTABLE IMAGE
 11 
 12 FILE HEADER VALUES
 13              14C machine (x86)
 14                3 number of sections
 15         CEC16D79 time date stamp
 16                0 file pointer to symbol table
 17                0 number of symbols
 18               E0 size of optional header
 19               22 characteristics
 20                    Executable
 21                    Application can handle large (>2GB) addresses
 22 
 23 OPTIONAL HEADER VALUES
 24              10B magic # (PE32)
 25            48.00 linker version
 26              800 size of code
 27              800 size of initialized data
 28                0 size of uninitialized data
 29             2796 entry point (00402796)
 30             2000 base of code
 31             4000 base of data
 32           400000 image base (00400000 to 00407FFF)
 33             2000 section alignment
 34              200 file alignment
 35             4.00 operating system version
 36             0.00 image version
 37             4.00 subsystem version
 38                0 Win32 version
 39             8000 size of image
 40              200 size of headers
 41                0 checksum
 42                3 subsystem (Windows CUI)
 43             8560 DLL characteristics
 44                    High Entropy Virtual Addresses
 45                    Dynamic base
 46                    NX compatible
 47                    No structured exception handler
 48                    Terminal Server Aware
 49           100000 size of stack reserve
 50             1000 size of stack commit
 51           100000 size of heap reserve
 52             1000 size of heap commit
 53                0 loader flags
 54               10 number of directories
 55                0 [       0] RVA [size] of Export Directory
 56             2742 [      4F] RVA [size] of Import Directory
 57             4000 [     5A0] RVA [size] of Resource Directory
 58                0 [       0] RVA [size] of Exception Directory
 59                0 [       0] RVA [size] of Certificates Directory
 60             6000 [       C] RVA [size] of Base Relocation Directory
 61             2630 [      54] RVA [size] of Debug Directory
 62                0 [       0] RVA [size] of Architecture Directory
 63                0 [       0] RVA [size] of Global Pointer Directory
 64                0 [       0] RVA [size] of Thread Storage Directory
 65                0 [       0] RVA [size] of Load Configuration Directory
 66                0 [       0] RVA [size] of Bound Import Directory
 67             2000 [       8] RVA [size] of Import Address Table Directory
 68                0 [       0] RVA [size] of Delay Import Directory
 69             2008 [      48] RVA [size] of COM Descriptor Directory
 70                0 [       0] RVA [size] of Reserved Directory
 71 
 72 
 73 SECTION HEADER #1
 74    .text name
 75      79C virtual size
 76     2000 virtual address (00402000 to 0040279B)
 77      800 size of raw data
 78      200 file pointer to raw data (00000200 to 000009FF)
 79        0 file pointer to relocation table
 80        0 file pointer to line numbers
 81        0 number of relocations
 82        0 number of line numbers
 83 60000020 flags
 84          Code
 85          Execute Read
 86 
 87 RAW DATA #1
 88   00402000: 76 27 00 00 00 00 00 00 48 00 00 00 02 00 05 00  v'......H.......
 89   00402010: 70 20 00 00 C0 05 00 00 01 00 00 00 01 00 00 06  p ..?..........
 90   00402020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
 91   00402030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
 92   00402040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
 93   00402050: 4E 00 72 01 00 00 70 28 0D 00 00 0A 00 28 0E 00  N.r...p(.....(..
 94   00402060: 00 0A 00 2A 22 02 28 0F 00 00 0A 00 2A 00 00 00  ...*".(.....*...
 95   00402070: 42 53 4A 42 01 00 01 00 00 00 00 00 0C 00 00 00  BSJB............
 96   00402080: 76 34 2E 30 2E 33 30 33 31 39 00 00 00 00 05 00  v4.0.30319......
 97   00402090: 6C 00 00 00 C8 01 00 00 23 7E 00 00 34 02 00 00  l...?..#~..4...
 98   004020A0: 44 02 00 00 23 53 74 72 69 6E 67 73 00 00 00 00  D...#Strings....
 99   004020B0: 78 04 00 00 4C 00 00 00 23 55 53 00 C4 04 00 00  x...L...#US.?..
100   004020C0: 10 00 00 00 23 47 55 49 44 00 00 00 D4 04 00 00  ....#GUID...?..
101   004020D0: EC 00 00 00 23 42 6C 6F 62 00 00 00 00 00 00 00  ?..#Blob.......
102   004020E0: 02 00 00 01 47 15 00 00 09 00 00 00 00 FA 01 33  ....G........?3
103   004020F0: 00 16 00 00 01 00 00 00 10 00 00 00 02 00 00 00  ................
104   00402100: 02 00 00 00 01 00 00 00 0F 00 00 00 0C 00 00 00  ................
105   00402110: 01 00 00 00 02 00 00 00 00 00 AB 01 01 00 00 00  ..........?....
106   00402120: 00 00 06 00 02 01 09 02 06 00 6D 01 09 02 06 00  ..........m.....
107   00402130: 44 00 F6 01 0F 00 29 02 00 00 06 00 6F 00 8B 01  D.?..).....o...
108   00402140: 06 00 54 01 D5 01 06 00 CB 00 D5 01 06 00 88 00  ..T.?..??....
109   00402150: D5 01 06 00 A5 00 D5 01 06 00 22 01 D5 01 06 00  ?..??..".?..
110   00402160: 58 00 D5 01 06 00 EA 00 09 02 06 00 3D 02 C9 01  X.?..?....=.?
111   00402170: 06 00 3B 01 09 02 0A 00 23 00 C9 01 06 00 E7 01  ..;.....#.?..?
112   00402180: F6 01 00 00 00 00 13 00 00 00 00 00 01 00 01 00  ?..............
113   00402190: 00 00 10 00 C1 01 01 00 35 00 01 00 01 00 50 20  ....?..5.....P
114   004021A0: 00 00 00 00 91 00 D0 01 2C 00 01 00 64 20 00 00  ......?,...d ..
115   004021B0: 00 00 86 18 F0 01 06 00 02 00 00 00 01 00 38 02  ....?........8.
116   004021C0: 09 00 F0 01 01 00 11 00 F0 01 06 00 19 00 F0 01  ..?....?....?
117   004021D0: 0A 00 29 00 F0 01 10 00 31 00 F0 01 10 00 39 00  ..).?..1.?..9.
118   004021E0: F0 01 10 00 41 00 F0 01 10 00 49 00 F0 01 10 00  ?..A.?..I.?..
119   004021F0: 51 00 F0 01 10 00 59 00 F0 01 10 00 61 00 F0 01  Q.?..Y.?..a.?
120   00402200: 01 00 71 00 F0 01 15 00 79 00 3A 00 1A 00 81 00  ..q.?..y.:.....
121   00402210: A5 01 1F 00 69 00 F0 01 06 00 20 00 63 00 DB 00  ?..i.?.. .c.?
122   00402220: 27 00 5B 00 E1 00 2E 00 0B 00 32 00 2E 00 13 00  '.[.?....2.....
123   00402230: 3B 00 2E 00 1B 00 5A 00 2E 00 23 00 63 00 2E 00  ;.....Z...#.c...
124   00402240: 2B 00 A1 00 2E 00 33 00 B8 00 2E 00 3B 00 C3 00  +.?..3.?..;.?
125   00402250: 2E 00 43 00 D0 00 2E 00 4B 00 A1 00 2E 00 53 00  ..C.?..K.?..S.
126   00402260: A1 00 04 80 00 00 01 00 00 00 00 00 00 00 00 00  ?..............
127   00402270: 00 00 00 00 01 00 00 00 08 00 00 00 00 00 00 00  ................
128   00402280: 00 00 00 00 23 00 2B 00 00 00 00 00 08 00 00 00  ....#.+.........
129   00402290: 00 00 00 00 00 00 00 00 23 00 1C 00 00 00 00 00  ........#.......
130   004022A0: 00 00 00 00 00 45 78 61 6D 70 6C 65 43 6F 72 65  .....ExampleCore
131   004022B0: 5F 32 5F 31 5F 31 00 3C 4D 6F 64 75 6C 65 3E 00  _2_1_1.<Module>.
132   004022C0: 53 79 73 74 65 6D 2E 43 6F 6E 73 6F 6C 65 00 53  System.Console.S
133   004022D0: 79 73 74 65 6D 2E 52 75 6E 74 69 6D 65 00 57 72  ystem.Runtime.Wr
134   004022E0: 69 74 65 4C 69 6E 65 00 44 65 62 75 67 67 61 62  iteLine.Debuggab
135   004022F0: 6C 65 41 74 74 72 69 62 75 74 65 00 41 73 73 65  leAttribute.Asse
136   00402300: 6D 62 6C 79 54 69 74 6C 65 41 74 74 72 69 62 75  mblyTitleAttribu
137   00402310: 74 65 00 54 61 72 67 65 74 46 72 61 6D 65 77 6F  te.TargetFramewo
138   00402320: 72 6B 41 74 74 72 69 62 75 74 65 00 41 73 73 65  rkAttribute.Asse
139   00402330: 6D 62 6C 79 46 69 6C 65 56 65 72 73 69 6F 6E 41  mblyFileVersionA
140   00402340: 74 74 72 69 62 75 74 65 00 41 73 73 65 6D 62 6C  ttribute.Assembl
141   00402350: 79 49 6E 66 6F 72 6D 61 74 69 6F 6E 61 6C 56 65  yInformationalVe
142   00402360: 72 73 69 6F 6E 41 74 74 72 69 62 75 74 65 00 41  rsionAttribute.A
143   00402370: 73 73 65 6D 62 6C 79 43 6F 6E 66 69 67 75 72 61  ssemblyConfigura
144   00402380: 74 69 6F 6E 41 74 74 72 69 62 75 74 65 00 52 65  tionAttribute.Re
145   00402390: 66 53 61 66 65 74 79 52 75 6C 65 73 41 74 74 72  fSafetyRulesAttr
146   004023A0: 69 62 75 74 65 00 43 6F 6D 70 69 6C 61 74 69 6F  ibute.Compilatio
147   004023B0: 6E 52 65 6C 61 78 61 74 69 6F 6E 73 41 74 74 72  nRelaxationsAttr
148   004023C0: 69 62 75 74 65 00 41 73 73 65 6D 62 6C 79 50 72  ibute.AssemblyPr
149   004023D0: 6F 64 75 63 74 41 74 74 72 69 62 75 74 65 00 4E  oductAttribute.N
150   004023E0: 75 6C 6C 61 62 6C 65 43 6F 6E 74 65 78 74 41 74  ullableContextAt
151   004023F0: 74 72 69 62 75 74 65 00 41 73 73 65 6D 62 6C 79  tribute.Assembly
152   00402400: 43 6F 6D 70 61 6E 79 41 74 74 72 69 62 75 74 65  CompanyAttribute
153   00402410: 00 52 75 6E 74 69 6D 65 43 6F 6D 70 61 74 69 62  .RuntimeCompatib
154   00402420: 69 6C 69 74 79 41 74 74 72 69 62 75 74 65 00 53  ilityAttribute.S
155   00402430: 79 73 74 65 6D 2E 52 75 6E 74 69 6D 65 2E 56 65  ystem.Runtime.Ve
156   00402440: 72 73 69 6F 6E 69 6E 67 00 42 72 65 61 6B 00 45  rsioning.Break.E
157   00402450: 78 61 6D 70 6C 65 43 6F 72 65 5F 32 5F 31 5F 31  xampleCore_2_1_1
158   00402460: 2E 64 6C 6C 00 50 72 6F 67 72 61 6D 00 53 79 73  .dll.Program.Sys
159   00402470: 74 65 6D 00 4D 61 69 6E 00 53 79 73 74 65 6D 2E  tem.Main.System.
160   00402480: 52 65 66 6C 65 63 74 69 6F 6E 00 44 65 62 75 67  Reflection.Debug
161   00402490: 67 65 72 00 2E 63 74 6F 72 00 53 79 73 74 65 6D  ger..ctor.System
162   004024A0: 2E 44 69 61 67 6E 6F 73 74 69 63 73 00 53 79 73  .Diagnostics.Sys
163   004024B0: 74 65 6D 2E 52 75 6E 74 69 6D 65 2E 43 6F 6D 70  tem.Runtime.Comp
164   004024C0: 69 6C 65 72 53 65 72 76 69 63 65 73 00 44 65 62  ilerServices.Deb
165   004024D0: 75 67 67 69 6E 67 4D 6F 64 65 73 00 61 72 67 73  uggingModes.args
166   004024E0: 00 4F 62 6A 65 63 74 00 00 47 57 00 65 00 6C 00  .Object..GW.e.l.
167   004024F0: 63 00 6F 00 6D 00 65 00 20 00 74 00 6F 00 20 00  c.o.m.e. .t.o. .
168   00402500: 41 00 64 00 76 00 61 00 6E 00 63 00 65 00 64 00  A.d.v.a.n.c.e.d.
169   00402510: 20 00 2E 00 4E 00 65 00 74 00 20 00 44 00 65 00   ...N.e.t. .D.e.
170   00402520: 62 00 75 00 67 00 67 00 69 00 6E 00 67 00 21 00  b.u.g.g.i.n.g.!.
171   00402530: 00 00 00 00 E0 46 36 0A 2C CC 52 4A 91 E3 65 72  ....郌6.,蘎J.鉫r
172   00402540: 44 97 3C 06 00 04 20 01 01 08 03 20 00 01 05 20  D.<... .... ...
173   00402550: 01 01 11 11 04 20 01 01 0E 04 20 01 01 05 04 00  ..... .... .....
174   00402560: 01 01 0E 03 00 00 01 08 B0 3F 5F 7F 11 D5 0A 3A  ........?_..?:
175   00402570: 05 00 01 01 1D 0E 08 01 00 08 00 00 00 00 00 1E  ................
176   00402580: 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78  ....T..WrapNonEx
177   00402590: 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 08 01  ceptionThrows...
178   004025A0: 00 07 01 00 00 00 00 3D 01 00 18 2E 4E 45 54 43  .......=....NETC
179   004025B0: 6F 72 65 41 70 70 2C 56 65 72 73 69 6F 6E 3D 76  oreApp,Version=v
180   004025C0: 38 2E 30 01 00 54 0E 14 46 72 61 6D 65 77 6F 72  8.0..T..Framewor
181   004025D0: 6B 44 69 73 70 6C 61 79 4E 61 6D 65 08 2E 4E 45  kDisplayName..NE
182   004025E0: 54 20 38 2E 30 16 01 00 11 45 78 61 6D 70 6C 65  T 8.0....Example
183   004025F0: 43 6F 72 65 5F 32 5F 31 5F 31 00 00 0A 01 00 05  Core_2_1_1......
184   00402600: 44 65 62 75 67 00 00 0C 01 00 07 31 2E 30 2E 30  Debug......1.0.0
185   00402610: 2E 30 00 00 0A 01 00 05 31 2E 30 2E 30 00 00 05  .0......1.0.0...
186   00402620: 01 00 01 00 00 08 01 00 0B 00 00 00 00 00 00 00  ................
187   00402630: 00 00 00 00 E5 B4 CB 9E 00 01 4D 50 02 00 00 00  ....宕?..MP....
188   00402640: 97 00 00 00 84 26 00 00 84 08 00 00 00 00 00 00  .....&..........
189   00402650: 00 00 00 00 01 00 00 00 13 00 00 00 27 00 00 00  ............'...
190   00402660: 1B 27 00 00 1B 09 00 00 00 00 00 00 00 00 00 00  .'..............
191   00402670: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00  ................
192   00402680: 00 00 00 00 52 53 44 53 6C 94 6B 86 99 72 7A 46  ....RSDSl.k..rzF
193   00402690: A1 D2 BD 41 BE 8E BA 85 01 00 00 00 45 3A 5C 56  ∫紸??....E:\V
194   004026A0: 69 73 75 61 6C 20 53 74 75 64 69 6F 20 32 30 32  isual Studio 202
195   004026B0: 32 5C 53 6F 75 72 63 65 5C 50 72 6F 6A 65 63 74  2\Source\Project
196   004026C0: 73 5C 41 64 76 61 6E 63 65 64 44 65 62 75 67 2E  s\AdvancedDebug.
197   004026D0: 4E 65 74 46 72 61 6D 65 77 6F 72 6B 2E 54 65 73  NetFramework.Tes
198   004026E0: 74 5C 45 78 61 6D 70 6C 65 43 6F 72 65 5F 32 5F  t\ExampleCore_2_
199   004026F0: 31 5F 31 5C 6F 62 6A 5C 44 65 62 75 67 5C 6E 65  1_1\obj\Debug\ne
200   00402700: 74 38 2E 30 5C 45 78 61 6D 70 6C 65 43 6F 72 65  t8.0\ExampleCore
201   00402710: 5F 32 5F 31 5F 31 2E 70 64 62 00 53 48 41 32 35  _2_1_1.pdb.SHA25
202   00402720: 36 00 6C 94 6B 86 99 72 7A 46 A1 D2 BD 41 BE 8E  6.l.k..rzF∫紸?
203   00402730: BA 85 E5 B4 CB 1E 7D 25 6C 56 9B D4 0D 00 29 06  ?宕?}%lV.?.).
204   00402740: 3F 5D 6A 27 00 00 00 00 00 00 00 00 00 00 84 27  ?]j'...........'
205   00402750: 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00  ... ............
206   00402760: 00 00 00 00 00 00 00 00 00 00 76 27 00 00 00 00  ..........v'....
207   00402770: 00 00 00 00 00 00 00 00 5F 43 6F 72 45 78 65 4D  ........_CorExeM
208   00402780: 61 69 6E 00 6D 73 63 6F 72 65 65 2E 64 6C 6C 00  ain.mscoree.dll.
209   00402790: 00 00 00 00 00 00 FF 25 00 20 40 00              ......?%. @.
210 
211   Debug Directories
212 
213         Time Type        Size      RVA  Pointer
214     -------- ------- -------- -------- --------
215     9ECBB4E5 cv            97 00002684      884    Format: RSDS, {866B946C-7299-467A-A1D2-BD41BE8EBA85}, 1, E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_1\obj\Debug\net8.0\ExampleCore_2_1_1.pdb
216     00000000 pdbhash       27 0000271B      91B    SHA256: 6C 94 6B 86 99 72 7A 46 A1 D2 BD 41 BE 8E BA 85 E5 B4 CB 1E 7D 25 6C 56 9B D4 0D 00 29 06 3F 5D
217     00000000 repro          0 00000000        0
218 
219   clr Header:
220 
221               48 cb
222             2.05 runtime version
223             2070 [     5C0] RVA [size] of MetaData Directory
224                1 flags
225                    IL Only
226          6000001 entry point token
227                0 [       0] RVA [size] of Resources Directory
228                0 [       0] RVA [size] of StrongNameSignature Directory
229                0 [       0] RVA [size] of CodeManagerTable Directory
230                0 [       0] RVA [size] of VTableFixups Directory
231                0 [       0] RVA [size] of ExportAddressTableJumps Directory
232                0 [       0] RVA [size] of ManagedNativeHeader Directory
233 
234 
235   Section contains the following imports:
236 
237     mscoree.dll
238                 402000 Import Address Table
239                 40276A Import Name Table
240                      0 time date stamp
241                      0 Index of first forwarder reference
242 
243                     0 _CorExeMain
244 
245 SECTION HEADER #2
246    .rsrc name
247      5A0 virtual size
248     4000 virtual address (00404000 to 0040459F)
249      600 size of raw data
250      A00 file pointer to raw data (00000A00 to 00000FFF)
251        0 file pointer to relocation table
252        0 file pointer to line numbers
253        0 number of relocations
254        0 number of line numbers
255 40000040 flags
256          Initialized Data
257          Read Only
258 
259 RAW DATA #2
260   00404000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00  ................
261   00404010: 10 00 00 00 20 00 00 80 18 00 00 00 50 00 00 80  .... .......P...
262   00404020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00  ................
263   00404030: 01 00 00 00 38 00 00 80 00 00 00 00 00 00 00 00  ....8...........
264   00404040: 00 00 00 00 00 00 01 00 00 00 00 00 80 00 00 00  ................
265   00404050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00  ................
266   00404060: 01 00 00 00 68 00 00 80 00 00 00 00 00 00 00 00  ....h...........
267   00404070: 00 00 00 00 00 00 01 00 00 00 00 00 A0 03 00 00  ................
268   00404080: 90 40 00 00 10 03 00 00 00 00 00 00 00 00 00 00  .@..............
269   00404090: 10 03 34 00 00 00 56 00 53 00 5F 00 56 00 45 00  ..4...V.S._.V.E.
270   004040A0: 52 00 53 00 49 00 4F 00 4E 00 5F 00 49 00 4E 00  R.S.I.O.N._.I.N.
271   004040B0: 46 00 4F 00 00 00 00 00 BD 04 EF FE 00 00 01 00  F.O.....?稔....
272   004040C0: 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00  ................
273   004040D0: 3F 00 00 00 00 00 00 00 04 00 00 00 01 00 00 00  ?...............
274   004040E0: 00 00 00 00 00 00 00 00 00 00 00 00 44 00 00 00  ............D...
275   004040F0: 01 00 56 00 61 00 72 00 46 00 69 00 6C 00 65 00  ..V.a.r.F.i.l.e.
276   00404100: 49 00 6E 00 66 00 6F 00 00 00 00 00 24 00 04 00  I.n.f.o.....$...
277   00404110: 00 00 54 00 72 00 61 00 6E 00 73 00 6C 00 61 00  ..T.r.a.n.s.l.a.
278   00404120: 74 00 69 00 6F 00 6E 00 00 00 00 00 00 00 B0 04  t.i.o.n.......?
279   00404130: 70 02 00 00 01 00 53 00 74 00 72 00 69 00 6E 00  p.....S.t.r.i.n.
280   00404140: 67 00 46 00 69 00 6C 00 65 00 49 00 6E 00 66 00  g.F.i.l.e.I.n.f.
281   00404150: 6F 00 00 00 4C 02 00 00 01 00 30 00 30 00 30 00  o...L.....0.0.0.
282   00404160: 30 00 30 00 34 00 62 00 30 00 00 00 44 00 12 00  0.0.4.b.0...D...
283   00404170: 01 00 43 00 6F 00 6D 00 70 00 61 00 6E 00 79 00  ..C.o.m.p.a.n.y.
284   00404180: 4E 00 61 00 6D 00 65 00 00 00 00 00 45 00 78 00  N.a.m.e.....E.x.
285   00404190: 61 00 6D 00 70 00 6C 00 65 00 43 00 6F 00 72 00  a.m.p.l.e.C.o.r.
286   004041A0: 65 00 5F 00 32 00 5F 00 31 00 5F 00 31 00 00 00  e._.2._.1._.1...
287   004041B0: 4C 00 12 00 01 00 46 00 69 00 6C 00 65 00 44 00  L.....F.i.l.e.D.
288   004041C0: 65 00 73 00 63 00 72 00 69 00 70 00 74 00 69 00  e.s.c.r.i.p.t.i.
289   004041D0: 6F 00 6E 00 00 00 00 00 45 00 78 00 61 00 6D 00  o.n.....E.x.a.m.
290   004041E0: 70 00 6C 00 65 00 43 00 6F 00 72 00 65 00 5F 00  p.l.e.C.o.r.e._.
291   004041F0: 32 00 5F 00 31 00 5F 00 31 00 00 00 30 00 08 00  2._.1._.1...0...
292   00404200: 01 00 46 00 69 00 6C 00 65 00 56 00 65 00 72 00  ..F.i.l.e.V.e.r.
293   00404210: 73 00 69 00 6F 00 6E 00 00 00 00 00 31 00 2E 00  s.i.o.n.....1...
294   00404220: 30 00 2E 00 30 00 2E 00 30 00 00 00 4C 00 16 00  0...0...0...L...
295   00404230: 01 00 49 00 6E 00 74 00 65 00 72 00 6E 00 61 00  ..I.n.t.e.r.n.a.
296   00404240: 6C 00 4E 00 61 00 6D 00 65 00 00 00 45 00 78 00  l.N.a.m.e...E.x.
297   00404250: 61 00 6D 00 70 00 6C 00 65 00 43 00 6F 00 72 00  a.m.p.l.e.C.o.r.
298   00404260: 65 00 5F 00 32 00 5F 00 31 00 5F 00 31 00 2E 00  e._.2._.1._.1...
299   00404270: 64 00 6C 00 6C 00 00 00 28 00 02 00 01 00 4C 00  d.l.l...(.....L.
300   00404280: 65 00 67 00 61 00 6C 00 43 00 6F 00 70 00 79 00  e.g.a.l.C.o.p.y.
301   00404290: 72 00 69 00 67 00 68 00 74 00 00 00 20 00 00 00  r.i.g.h.t... ...
302   004042A0: 54 00 16 00 01 00 4F 00 72 00 69 00 67 00 69 00  T.....O.r.i.g.i.
303   004042B0: 6E 00 61 00 6C 00 46 00 69 00 6C 00 65 00 6E 00  n.a.l.F.i.l.e.n.
304   004042C0: 61 00 6D 00 65 00 00 00 45 00 78 00 61 00 6D 00  a.m.e...E.x.a.m.
305   004042D0: 70 00 6C 00 65 00 43 00 6F 00 72 00 65 00 5F 00  p.l.e.C.o.r.e._.
306   004042E0: 32 00 5F 00 31 00 5F 00 31 00 2E 00 64 00 6C 00  2._.1._.1...d.l.
307   004042F0: 6C 00 00 00 44 00 12 00 01 00 50 00 72 00 6F 00  l...D.....P.r.o.
308   00404300: 64 00 75 00 63 00 74 00 4E 00 61 00 6D 00 65 00  d.u.c.t.N.a.m.e.
309   00404310: 00 00 00 00 45 00 78 00 61 00 6D 00 70 00 6C 00  ....E.x.a.m.p.l.
310   00404320: 65 00 43 00 6F 00 72 00 65 00 5F 00 32 00 5F 00  e.C.o.r.e._.2._.
311   00404330: 31 00 5F 00 31 00 00 00 30 00 06 00 01 00 50 00  1._.1...0.....P.
312   00404340: 72 00 6F 00 64 00 75 00 63 00 74 00 56 00 65 00  r.o.d.u.c.t.V.e.
313   00404350: 72 00 73 00 69 00 6F 00 6E 00 00 00 31 00 2E 00  r.s.i.o.n...1...
314   00404360: 30 00 2E 00 30 00 00 00 38 00 08 00 01 00 41 00  0...0...8.....A.
315   00404370: 73 00 73 00 65 00 6D 00 62 00 6C 00 79 00 20 00  s.s.e.m.b.l.y. .
316   00404380: 56 00 65 00 72 00 73 00 69 00 6F 00 6E 00 00 00  V.e.r.s.i.o.n...
317   00404390: 31 00 2E 00 30 00 2E 00 30 00 2E 00 30 00 00 00  1...0...0...0...
318   004043A0: B0 43 00 00 EA 01 00 00 00 00 00 00 00 00 00 00  癈..?..........
319   004043B0: EF BB BF 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E  锘??xml version
320   004043C0: 3D 22 31 2E 30 22 20 65 6E 63 6F 64 69 6E 67 3D  ="1.0" encoding=
321   004043D0: 22 55 54 46 2D 38 22 20 73 74 61 6E 64 61 6C 6F  "UTF-8" standalo
322   004043E0: 6E 65 3D 22 79 65 73 22 3F 3E 0D 0A 0D 0A 3C 61  ne="yes"?>....<a
323   004043F0: 73 73 65 6D 62 6C 79 20 78 6D 6C 6E 73 3D 22 75  ssembly xmlns="u
324   00404400: 72 6E 3A 73 63 68 65 6D 61 73 2D 6D 69 63 72 6F  rn:schemas-micro
325   00404410: 73 6F 66 74 2D 63 6F 6D 3A 61 73 6D 2E 76 31 22  soft-com:asm.v1"
326   00404420: 20 6D 61 6E 69 66 65 73 74 56 65 72 73 69 6F 6E   manifestVersion
327   00404430: 3D 22 31 2E 30 22 3E 0D 0A 20 20 3C 61 73 73 65  ="1.0">..  <asse
328   00404440: 6D 62 6C 79 49 64 65 6E 74 69 74 79 20 76 65 72  mblyIdentity ver
329   00404450: 73 69 6F 6E 3D 22 31 2E 30 2E 30 2E 30 22 20 6E  sion="1.0.0.0" n
330   00404460: 61 6D 65 3D 22 4D 79 41 70 70 6C 69 63 61 74 69  ame="MyApplicati
331   00404470: 6F 6E 2E 61 70 70 22 2F 3E 0D 0A 20 20 3C 74 72  on.app"/>..  <tr
332   00404480: 75 73 74 49 6E 66 6F 20 78 6D 6C 6E 73 3D 22 75  ustInfo xmlns="u
333   00404490: 72 6E 3A 73 63 68 65 6D 61 73 2D 6D 69 63 72 6F  rn:schemas-micro
334   004044A0: 73 6F 66 74 2D 63 6F 6D 3A 61 73 6D 2E 76 32 22  soft-com:asm.v2"
335   004044B0: 3E 0D 0A 20 20 20 20 3C 73 65 63 75 72 69 74 79  >..    <security
336   004044C0: 3E 0D 0A 20 20 20 20 20 20 3C 72 65 71 75 65 73  >..      <reques
337   004044D0: 74 65 64 50 72 69 76 69 6C 65 67 65 73 20 78 6D  tedPrivileges xm
338   004044E0: 6C 6E 73 3D 22 75 72 6E 3A 73 63 68 65 6D 61 73  lns="urn:schemas
339   004044F0: 2D 6D 69 63 72 6F 73 6F 66 74 2D 63 6F 6D 3A 61  -microsoft-com:a
340   00404500: 73 6D 2E 76 33 22 3E 0D 0A 20 20 20 20 20 20 20  sm.v3">..
341   00404510: 20 3C 72 65 71 75 65 73 74 65 64 45 78 65 63 75   <requestedExecu
342   00404520: 74 69 6F 6E 4C 65 76 65 6C 20 6C 65 76 65 6C 3D  tionLevel level=
343   00404530: 22 61 73 49 6E 76 6F 6B 65 72 22 20 75 69 41 63  "asInvoker" uiAc
344   00404540: 63 65 73 73 3D 22 66 61 6C 73 65 22 2F 3E 0D 0A  cess="false"/>..
345   00404550: 20 20 20 20 20 20 3C 2F 72 65 71 75 65 73 74 65        </requeste
346   00404560: 64 50 72 69 76 69 6C 65 67 65 73 3E 0D 0A 20 20  dPrivileges>..
347   00404570: 20 20 3C 2F 73 65 63 75 72 69 74 79 3E 0D 0A 20    </security>..
348   00404580: 20 3C 2F 74 72 75 73 74 49 6E 66 6F 3E 0D 0A 3C   </trustInfo>..<
349   00404590: 2F 61 73 73 65 6D 62 6C 79 3E 00 00 00 00 00 00  /assembly>......
350 
351 SECTION HEADER #3
352   .reloc name
353        C virtual size
354     6000 virtual address (00406000 to 0040600B)
355      200 size of raw data
356     1000 file pointer to raw data (00001000 to 000011FF)
357        0 file pointer to relocation table
358        0 file pointer to line numbers
359        0 number of relocations
360        0 number of line numbers
361 42000040 flags
362          Initialized Data
363          Discardable
364          Read Only
365 
366 RAW DATA #3
367   00406000: 00 20 00 00 0C 00 00 00 98 37 00 00              . .......7..
368 
369 BASE RELOCATIONS #3
370     2000 RVA,        C SizeOfBlock
371      798  HIGHLOW            00402000
372        0  ABS
373 
374   Summary
375 
376         2000 .reloc
377         2000 .rsrc
378         2000 .text
                
                我们在【OPTIONAL HEADER VALUES】这个节,可以看到【2796 entry point (00402796)】这个值,这就是入口点。如图:
                  
                当然,我们也可以使用 PPEE 工具查看,它看的更清楚一点。我们将【ExampleCore_2_1_1.dll】文件拖到 PPEE.exe 应用中,我们在【NT Header】-->【Optional Header】节中看到【AddressOfEntryPoint】域的值是:00002796,效果如图:
                        

                上面的 Entry point 域对应于 PE 文件中的 【AddressOfEntryPoint】,值为:0x00402796。要找出位置 0x00402796 所对应的代码,需要查看 PE 文件映像中的 .text 段,具体来说就是如下面截图中的【RAW DATA】段:
1           00402750: 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00  ... ............
2           00402760: 00 00 00 00 00 00 00 00 00 00 76 27 00 00 00 00  ..........v'....
3           00402770: 00 00 00 00 00 00 00 00 5F 43 6F 72 45 78 65 4D  ........_CorExeM
4           00402780: 61 69 6E 00 6D 73 63 6F 72 65 65 2E 64 6C 6C 00  ain.mscoree.dll.
5           00402790: 00 00 00 00 00 00 FF 25 00 20 40 00              ......?%. @.
                      
                上面信息中的【FF 25 00 20 40 00】字节对应于【AddressOfEntryPoint】域,这些字节对应的机器指令为:JMP 402000。
                0x402000 是什么意思?事实上,0x402000 指向的是 PE 映像文件中的 import 段。在这个段中列出的是 PE 文件依赖的所有模块。
                效果如图:
                  
                  
                在加载时,系统将修正导入函数的实际地址,并执行正确的调用。要找到【0x402000】指向的内容,我们可以查看 PE 文件的导入段,可以发现一下内容(dumpbin.exe):
1          Section contains the following imports:
2 
3             mscoree.dll
4                         402000 Import Address Table
5                         40276A Import Name Table
6                              0 time date stamp
7                              0 Index of first forwarder reference
8 
9                             0 _CorExeMain

                  

                可以看到,0x402000 指向的是 mscoree.dll(Microsoft 对象运行时执行引擎,Microsoft Object Runtime Execution Engine),这个库中包含了一个导出函数_CorExeMain。然后,前面的 JMP 指令可以转换为一下伪码:JMP _CorExeMain。
                我们已经看到了,_CorExeMain 是 mscoree.dll 的一部分,这个函数也是在加载 NET 程序集时第一个被调用的函数。mscoree.dll(和_CorExeMain)的主要作用就是启动 CLR。mscoree.dll 在启动 CLR 时将执行一些列工作:
                (1)、查看 PE 文件中的元数据,找出 NET 程序集是基于哪个版本的 CLR 创建的。
                (2)、找到操作系统中正确版本 CLR 的路径。
                (3)、开始加载并初始化 CLR。
                当 CLR 被成功加载并初始化后,在 PE 映像的 CLR 头中就可以找到程序集的入口点(Main()),然后,JIT 开始编译并执行入口点。我们可以使用 PPEE.exe 工具查看一下【ExampleCore_2_1_1.dll】的 PE映像文件中有关 Net 元数据的扩展。效果如图:
                       
                以上我们说的 CLR 都是概念的东西,接下来,我就展示一下它是一个真实存在的东西。在任何一台机器上都有可能存在多个版本的 clr.dll,以下就是32位 4.0版本的 clr.dll,是64位 4.0版本的 clr.dll,其他版本大家自己去找吧,很容易的。
                      C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
                      C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
                      mscoree.dll 的作用就是通过查看 PE 映像文件的 CLR 头来找出程序集需要使用哪个版本的 CLR。具体来说,就是它主要查看两个域,分别是:MajorRuntimeVersion 和 MinorRuntimeVersion,就可以加载正确版本的 CLR。
                       
                我们总结 Net 程序集加载算法如下:
                  (1)、用户执行一个 NET 应用程序。
                  (2)、Windows 加载器查看【Optional Header】中的【AddressOfEntryPoint】域,并找到 PE 映像文件的 .text 段。
                  (3)、位于【AddressOfEntryPoint】域上 .text 的内容就是  JMP 字节指令,这个指令会加载【mscoree.dll】中的一个导入函数,函数名是:_CorExeMain。
                  (4)、将执行控制权转移到 【mscoree.dll】中的【_CorExeMain】中,这个函数将启动并加载CLR,并且找到程序集的入口点,最后把执行的控制权转移到程序集的入口点,开始执行我的程序。

    3、应用程序域
        A、基础知识
            我们知道 Windows 系统为了提升系统的稳定性和可靠性,实现了进程级别的隔离。同理,.Net 应用程序也是同样被限制在进程内执行。但是不同的是,.Net 引入了另一种逻辑隔离层,那就是我们通常所说的【应用程序域】。我们通过一张图片,看看进程和应用程序域的关系。效果如图:
            
            在图中,我已说明,在.Net 跨平台版本里是没有【共享应用程序域】了,大家需要注意。

            在任何启动了【CLR】的 Windows 进程中都会定义一个或者多个应用程序域。通常来说,应用程序域对于应用程序是透明的,大多数应用程序都不会显示的创建应用程序域。为了使运行的应用程序不会对系统的其他部分造成破坏,这些代码将会加载到自己的应用程序域中。对于没有显示创建应用程序域的应用程序来说,CLR 在加载的时候将创建2类应用程序(在.Net Framework 版本里创建3类应用程序域:System Domian、Shared Domian、Default Domain),换句话说,启动了 CLR 的进程在运行时至少拥有两类应用程序域(这种情况是 .Net 跨平台版本,.Net Framework 版本是三类应用程序域,因为我这个系列是使用的跨平台的版本,以后就不说明了)。

        B、眼见为实
            调试源码:ExampleCore_2_1_1
            调试任务:查看引用程序域
            我们打开 Windbg Preview,通过【File】-->【Launch executable】,加载我们的编译好的 ExampleCore_2_1_1.exe 文件。由于我们使用的最新的调试工具,它会自动加载 SOS.dll,当我们成功加载了 ExampleCore_2_1_1.exe 文件,这个时候 Windbg Preview,并没有加载 SOS.DLL,你可以通过【.chain】命令验证。我们通过【g】命令继续调试器,我们的应用程序输出:Welcome to Advanced .Net Debugging!,效果如图:
            
            程序在【Debugger.Break();】暂停,我们点击【Break】按钮,进入调试模式,可以使用【.chain】命令,就可以看到 SOS.DLL 已经加载了。
 1 0:000> .chain
 2 Extension DLL search Path:
 3     C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\WINXP;..\.dotnet\tools
 4 Extension DLL chain:
 5     sos: image 7.0.430602, API 2.0.0, built Wed Jun  7 08:01:54 2023
 6         [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\winext\sos\sos.dll]
 7     CLRComposition: image 10.0.25877.1004, API 0.0.0, 
 8         [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\winext\CLRComposition.dll]
 9     JsProvider: image 10.0.25877.1004, API 0.0.0, 
10         [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\winext\JsProvider.dll]
11     DbgModelApiXtn: image 10.0.25877.1004, API 0.0.0, 
12         [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\winext\DbgModelApiXtn.dll]
13     dbghelp: image 10.0.25877.1004, API 10.0.6, 
14         [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\dbghelp.dll]
15     exts: image 10.0.25877.1004, API 1.0.0, 
16         [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\WINXP\exts.dll]
17     uext: image 10.0.25877.1004, API 1.0.0, 
18         [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\winext\uext.dll]
19     ntsdexts: image 10.0.25877.1004, API 1.0.0, 
20         [path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2306.14001.0_x64__8wekyb3d8bbwe\amd64\WINXP\ntsdexts.dll]

            当然,我们也可以使用【!sos.help】命令,查看所有的 SOS 的命令。为了查看应用程序域,我们可以使用【!dumpdomain】命令。

 1 0:000> !dumpdomain
 2 --------------------------------------
 3 System Domain:      00007ffa16d19040
 4 LowFrequencyHeap:   00007FFA16D19518
 5 HighFrequencyHeap:  00007FFA16D195A8
 6 StubHeap:           00007FFA16D19638
 7 Stage:              OPEN
 8 Name:               None
 9 --------------------------------------
10 Domain 1:           00000252bb908650
11 LowFrequencyHeap:   00007FFA16D19518
12 HighFrequencyHeap:  00007FFA16D195A8
13 StubHeap:           00007FFA16D19638
14 Stage:              OPEN
15 Name:               clrhost
16 Assembly:           00000252bb8ce240 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Private.CoreLib.dll]
17 ClassLoader:        00000252BB8CE2D0
18   Module
19   00007ff9b6d24000    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Private.CoreLib.dll
20 
21 ..................(省略无用的信息)

            System Domain 就是系统级应用程序域,Domain 1就是我们默认的应用程序域。如果大家想查看 .Net Framework 版本的应用程序,就能看到3个,自己可以试试,我就不写了。

            输出的内容,我们必须了解一下。
            (1)、指向应用程序域的指针。这个参数可以作为【!dumpdomain】命令的输入参数,这样只输出指定应用程序域的信息。
                 我们获取【系统应用程序域】的信息。
1 0:000> !dumpdomain 00007ffa16d19040
2 --------------------------------------
3 System Domain:      00007ffa16d19040
4 LowFrequencyHeap:   00007FFA16D19518
5 HighFrequencyHeap:  00007FFA16D195A8
6 StubHeap:           00007FFA16D19638
7 Stage:              OPEN
8 Name:               None

            (2)、LowFrequencyHeapHighFrequencyHeap 和 StubHeap,通常,每个应用程序域都有相关 MSIL 代码,在 JIT 编译 MSIL 代码的过程中,需要保存与编译过程相关的数据,比如:编译生成的机器代码和方发表等。因此,每个应用程序域都需要创建一定数量的堆来存储这些数据。LowFrequencyHeap 在这个堆中包含的是一些较少被更新或被访问的数据,而 HighFrequencyHeap 堆中包含的是经常被频繁访问的数据。StubHeap 堆包含的是 CLR 执行互用性调用时需要的辅助数据。
            (3)、在应用程序域中加载的所有程序集。


        3.1、系统应用程序域
          1)、可以创建其他的应用程序域(共享应用程序域(Net Framework 版本)和默认应用程序域)。
          2)、将 mscorlib.dll 加载到共享应用程序域中。
          3)、记录进程中所有其他的应用程序域,包括提供加载和卸载应用程序的功能。
          4)、记录字符串池中字符串常量,因为允许任意字符串在每一个进程中都存在一个副本。
          5)、初始化特性类型的异常,例如:内存耗尽异常、栈溢出异常以及执行引擎异常等。

        3.2、共享应用程序域
          共享应用程序域这个域在书中有,我就保留了,但是,在 .Net 版本里已经没有这个应用程序域了。在 .net framework 版本里是存在的,在这个域中包含的是一些通用的代码,mscorlib.dll 被加载到这个应用程序域中,此外还包括在 System 命名空间下的一些基本类型(enum、String、ValueType、Array等),在大多数情况下,非用户代码将被加载到这个域中。

        3.3、默认应用程序域
          这个域中就是我们的应用程序生存的地方,位于默认应用程序域中的所有代码都只有在这个域中才有效。

    4、程序集简介
        A、基础知识
            程序集是 .Net 程序的主要构件和部署单元,.Net 的程序集是自包含的,也可以说是自描述的。程序集的自包含性对于消除 DLL Hell起到了积极作用。
            共有两类程序集:
              1)、共享程序集:指可以在不同应用程序中使用的程序集。由于共享程序集可以跨越不同的应用程序,所以必须是【强命名】的。通常,共享程序集被安装到全局程序集缓存中(GAC:Global Assembly Cache)。
              2)、私有程序集:指属于特性应用程序或者组件的程序集。当加载私有程序集时,它通常只会局限于某个应用程序域中。

            当加载私有程序集时,要么被加载到默认应用程序域中,要么是被加载到显示创建的应用程序域中。当程序集被加载到某个应用程序域时,它将停留在这个应用程序域中,直到这个应用程序域被销毁。由于程序集是局限在某个应用程序域中,那么对于任何一个应用程序域,我们如何找到其中加载了哪些程序集呢?我们可以使用【!dumpdomain】命令,查看某个进程中的所有应用程序域,也会列出每个应用程序域加载的程序集。有了程序集的地址,我们就可以使用【!dumpAssembly】命令,查看程序集的详情。

        B、眼见为实
            调试源码:ExampleCore_2_1_1
            调试任务:通过调试器观察程序集。
            编译项目,打开【Windbg Preview】调试器,依次点击【文件】----【Launch executable】,加载我们的可执行程序 ExampleCore_2_1_1.exe。进入调试器后,我们直接【g】运行调试器,调试器会自己中断执行。我们先执行【!dumpdomain】命令,查看我们程序的应用程序域的详情。
 1 0:000> !dumpdomain
 2 --------------------------------------
 3 System Domain:      00007ffe03a340c0
 4 LowFrequencyHeap:   00007FFE03A34598
 5 HighFrequencyHeap:  00007FFE03A34628
 6 StubHeap:           00007FFE03A346B8
 7 Stage:              OPEN
 8 Name:               None
 9 --------------------------------------
10 Domain 1:           000002117daef6d0
11 LowFrequencyHeap:   00007FFE03A34598
12 HighFrequencyHeap:  00007FFE03A34628
13 StubHeap:           00007FFE03A346B8
14 Stage:              OPEN
15 Name:               clrhost
16 Assembly:           000002117dab5520 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll]
17 ClassLoader:        000002117DAB55B0
18   Module
19   00007ffda3a14000    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll
20 
21 Assembly:           000002117da85ea0 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_1\bin\Debug\net8.0\ExampleCore_2_1_1.dll]
22 ClassLoader:        000002117DA85F30
23   Module
24   00007ffda3bfe0a0    E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_1\bin\Debug\net8.0\ExampleCore_2_1_1.dll
25 
26 Assembly:           000002117da84aa0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.dll]
27 ClassLoader:        000002117DA86010
28   Module
29   00007ffda3bffbc8    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.dll
30 
31 Assembly:           000002117f4f41b0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Console.dll]
32 ClassLoader:        000002117F4F4B30
33   Module
34   00007ffda3c29410    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Console.dll
35 
36 Assembly:           000002117f4f5d70 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Threading.dll]
37 ClassLoader:        000002117DAC6730
38   Module
39   00007ffda3c2aa30    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Threading.dll
40 
41 Assembly:           000002117f4f6660 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Text.Encoding.Extensions.dll]
42 ClassLoader:        000002117DAC7970
43   Module
44   00007ffda3c50c50    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Text.Encoding.Extensions.dll
45 
46 Assembly:           000002117dacb9a0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.InteropServices.dll]
47 ClassLoader:        000002117DACC260
48   Module
49   00007ffda3c53128    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.InteropServices.dll
             我们先使用【!dumpdomain 000002117daef6d0】命令查看指定应用程序域的信息,000002117daef6d0 是【Domain 1】的标识符。
 1 0:000> !dumpdomain 000002117daef6d0
 2 --------------------------------------
 3 Domain 1:           000002117daef6d0
 4 LowFrequencyHeap:   00007FFE03A34598
 5 HighFrequencyHeap:  00007FFE03A34628
 6 StubHeap:           00007FFE03A346B8
 7 Stage:              OPEN
 8 Name:               clrhost
 9 Assembly:           000002117dab5520 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll]
10 ClassLoader:        000002117DAB55B0
11   Module
12   00007ffda3a14000    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll
13 
14 Assembly:           000002117da85ea0 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_1\bin\Debug\net8.0\ExampleCore_2_1_1.dll]
15 ClassLoader:        000002117DA85F30
16   Module
17   00007ffda3bfe0a0    E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_1\bin\Debug\net8.0\ExampleCore_2_1_1.dll
18 
19 Assembly:           000002117da84aa0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.dll]
20 ClassLoader:        000002117DA86010
21   Module
22   00007ffda3bffbc8    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.dll
23 
24 Assembly:           000002117f4f41b0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Console.dll]
25 ClassLoader:        000002117F4F4B30
26   Module
27   00007ffda3c29410    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Console.dll
28 
29 Assembly:           000002117f4f5d70 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Threading.dll]
30 ClassLoader:        000002117DAC6730
31   Module
32   00007ffda3c2aa30    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Threading.dll
33 
34 Assembly:           000002117f4f6660 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Text.Encoding.Extensions.dll]
35 ClassLoader:        000002117DAC7970
36   Module
37   00007ffda3c50c50    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Text.Encoding.Extensions.dll
38 
39 Assembly:           000002117dacb9a0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.InteropServices.dll]
40 ClassLoader:        000002117DACC260
41   Module
42   00007ffda3c53128    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.InteropServices.dll

             红色标注的就是程序集的数据,我们就可以使用【!dumpAssembly 000002117dacb9a0】命令,查看程序集的详情。

1 0:000> !DumpAssembly /d 000002117dacb9a0
2 Parent Domain:      000002117daef6d0
3 Name:               C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.InteropServices.dll
4 ClassLoader:        000002117DACC260
5   Module
6   00007ffda3c53128    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.InteropServices.dll
            除了显示程序集的名称,还显示了程序集的安全描述符和包含该程序集的父应用程序域的地址。

    5、程序集清单
        A、基础知识
            既然程序集是.Net 应用程序的基础构件并且是完全自描述的,这些子描述信息存储在程序集的元数据段,这些信息被称为程序集的清单。通常,程序集的清单位于 PE 文件中,但并非一定要在这个位置。比如:如果程序集包含了多个模块,程序集清单就会保存到一个独立的文件中(也就是程序集的 PE 文件只包含了清单),在这个文件中包含的是在加载和使用各个模块的时候所引用的数据。我们来一个图展示一下单文件程序集和多文件程序集有关程序集清单的区别,如图:
            

            接下来,我们看看程序集清单包含了什么数据:
            1)、需要依赖的非托管代码模块列表。
            2)、要依赖的程序集列表。
            3)、程序集的版本。
            4)、程序集的公钥标记。
            5)、程序集的资源。
            6)、程序集的标志,比如:栈的预留空间、子系统等信息。

        B、眼见为实
            查看工具:PPEE,ILDasm
            下载地址:https://www.mzrst.com/
            调试项目:ExampleCore_2_1_1

            1)、使用 ILDASM 查看程序集清单。
                这本书中使用的是 ILDasm 工具,大家应该熟悉这个工具,想要查看反编译代码、元数据和程序集清单都会用到这个工具。使用起来也很简单,打开【Developer Command Prompt for vs 2022】命令行窗口,直接执行命令:ildasm assembliyName(程序集的名称,注意程序集的目录),就可以打开反编译窗口。这里需要说明一下,由于我使用的是 .Net 8.0 版本,使用的命令是【ildasm ExampleCore_2_1_1.dll】,这个名称是以 .dll 为后缀的,不是 .exe 的文件,如果是 .Net Framework 框架,则直接使用【ildasm ildasm ExampleCore_2_1_1.exe】命令。
                执行命令,如图:
                
                打开【IL DASM】窗口,如图:
                
                双击【MANIFEST】打开程序集清单窗口,如图:
                
                和
                

            2)、我们在使用一下 PPEE 查看一下程序集的清单
                使用 PPEE 查看程序集清单,很容易,而且看起来更清晰,更有条例。操作很简单,我们直接将我们的 .dll 文件拖到 PPEE 文件的主界面上,就可以打开我们程序集了。
                效果如图:
                

                当然里面还有很多其他项,大家可以自己点击进去看看,不是很难,我就不多说了。

    6、类型的元数据
        A、基础知识
            类型是 .Net 程序的基本编程单元,我们都知道的它又分为值类型和引用类型,值类型是保存在线程栈上的类型(这里指的是局部变量),包括:枚举、结构和简单类型(例如:int、float、char)等。通常,值类型都是一些占用内存比较小的类型。另外一种类型就是引用类型,它是在托管堆上分配的,并有垃圾收集器(Garbage Collector,GC)负责管理。当然,在引用类型中也可以包含值类型,在这种情况中,值类型将位于托管堆上并且有垃圾收集器管理。微软为什么对数据类型进行这样的区分,主要的考虑是效率,毕竟在托管堆上分配数据、处理数据和销毁数据都是一个比较消耗资源的操作。我们上一张图,来看一下值类型和引用类型在内存上分配的区别。
            
            左边是一个值类型,右边是一个引用类型。值类型表示是一个局部变量 localVar,这个变量是在类中的一个成员函数中生命的。在值类型中包含的是这个变量在栈上的地址,也就是 localVar 包含的是一个指针(0x0028f3c8),该指针指向栈中的一个位置,在这个位置上存储是值类型的实例。可以使用调试器中转出命令(例如:dp、dd 命令)来输出这个位置上的内容。引用类型包含的也是一个指针(0x0150588c),该指针指向位于托管堆上的一个引用类型的实例。托管堆由垃圾收集器负责管理。
            如何知道某个局部变量指向的是一个值类型还是一个引用类型?我们可以使用【!do/DumpObj】命令,如果可以显示有效内容就是引用类型,否则就是其他数据,不一定是值类型。我们知道值类型的参数保存在线程栈上,因此,值类型的局部变量的地址应该落在与当前栈指针接近的地方。我们可以通过【r(registers)】命令就可以知道当前栈指针的值。在【r】命令的输出结果中,寄存器 rsp(esp)的值就是当前栈指针的值。为了说明这个情况,就不单独做测试了,我把测试结果贴出来,当然测试源码就是 ExampleCore_2_1_2
 1 0:000> !clrstack -a
 2 OS Thread Id: 0x14ec (0)
 3         Child SP               IP Call Site
 4 00000077D057E518 00007ff976c8b502 [HelperMethodFrame: 00000077d057e518] System.Diagnostics.Debugger.BreakInternal()
 5 00000077D057E620 00007ff89616550a System.Diagnostics.Debugger.Break() [/_/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 18]
 6 
 7 00000077D057E650 00007ff8375c1acd ExampleCore_2_1_2.TypeSample.AddCoordinates() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_2\Program.cs @ 45]
 8     PARAMETERS:
 9         this (0x00000077D057E6D0) = 0x0000021e20409640
10     LOCALS:
11         0x00000077D057E6BC = 0x000000000378734a
12         0x00000077D057E6B0 = 0x0000021e20409640
13         0x00000077D057E6A8 = 0x0000000000000001
14         0x00000077D057E698 = 0x0000000000000000
15 
16 00000077D057E6D0 00007ff8375c19a5 ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_2\Program.cs @ 11]
17     PARAMETERS:
18         args (0x00000077D057E720) = 0x0000021e20408ea0
19     LOCALS:
20         0x00000077D057E708 = 0x0000021e20409640
21 
22 
23 0:000> r
24 rax=000000000002a020 rbx=00000077d057e818 rcx=0000000000000130
25 rdx=0000000000000009 rsi=00007ff8971f2290 rdi=0000021e1bd438d0
26 rip=00007ff976c8b502 rsp=00000077d057e498 rbp=00000077d057e6c0
27  r8=0000000000000130  r9=0000000000000000 r10=00000077d057df38
28 r11=0000000000001b33 r12=0000000000000009 r13=0000000000000000
29 r14=0000000000000000 r15=0000000000000130
30 iopl=0         nv up ei pl zr na po nc
31 cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
32 KERNELBASE!wil::details::DebugBreak+0x2:
33 00007ff9`76c8b502 cc              int     3

            以上就是测试的内容,我们看到 RSP 的值是 00000077d057e498,ExampleCore_2_1_2.Program.Main 和 ExampleCore_2_1_2.TypeSample.AddCoordinates 这两个栈帧标红的内容一比较,发现它们的地址是很相近的,说明它们都是栈地址。在说明一点,无论是引用类型还是值类型,起始地址都是栈地址。

            如果我们想查找某个对象的地址,可以使用【!clrstack】命令,该命令有三个命令开关,分别是:-a(a=all,显示所有参数和局部变量),-l(l=local,只显示局部变量),-p(p=parameter,只显示参数),该命令会显示托管线程的调用栈,并且给出每个栈帧中的参数和局部变量。
            如果我们想转储出引用类型,可以使用【!do】或者【!DumpObj】命令,该命令的参数就是引用类型的地址,它就能把对象的内容转储出来。
            如果我们想转储出值类型,就要使用【!dumpvc <Method Table> <Value object start addr>】,Method Table 表示值类型的方发表的地址,Value object start addr 表示值类型值的地址。
            我们知道了程序集是通过程序集清单来描述自己的,数据的类型是通过类型元数据描述的。在深入探讨类型元数据之前,还有一个问题需要解决,类型的实例在内存中是如何布局的。上图表示,图简单命令。
            
            在托管堆上的每个实例对象都包含以下信息
              1)、同步块(sync block):同步块可以是一个位掩码,也可以是由 CLR 维持的一个同步块表中的索引,其中包含了关于对象本身的辅助信息。
              2)类型句柄:它是 CLR 类型系统的基础,它可以对托管堆上的类型进行完整的描述。
              3)对象实例:在同步块和类型句柄之后就是实际的对象数据了。


        B、眼见为实
            源码项目:ExampleCore_2_1_2
            调试任务:通过调试器查看值类型和引用类型
            编译项目,然后打开【Windbg Preview】,依次点击【File】-->【Launch executable】,选择我们的 EXE 文件,选择【打开】,加载我们的应用程序,并进入调试器页面。此时,我们的应用程序并没有执行。使用【g】命令,运行调试器,等我们的控制台程序输出:x=110,y=55,z=110,然后点击【Break】按钮,进入调试状态,就可以调试我们的应用程序了。由于我们是手动中断,是在调试器线程的上下文环境中,我们必须切换到托管线程上下文环境中,否则有些命令是执行无效的。我们可以执行【~0s】,切换到主线程。

1 0:001> ~0s
2 ntdll!NtReadFile+0x14:
3 00007ffc`8c7aae54 c3              ret

            我们使用【!clrstack -a】命令,查看当前托管程序的调用栈所有的参数和局部变量,-a 指包括参数和局部变量,-l 指只有局部变量。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3e2c (0)
 3         Child SP               IP Call Site
 4 0000000E9377E790 00007ffc8c7aae54 [InlinedCallFrame: 0000000e9377e790] 
 5 0000000E9377E790 00007ffbcff076eb [InlinedCallFrame: 0000000e9377e790] 
 ......75 
76 0000000E9377EAE0 00007ffb477519a5 ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\ExampleCore_2_1_2\Program.cs @ 9]
77     PARAMETERS:
78         args (0x0000000E9377EB30) = 0x000001a410808ea0
79     LOCALS:
80         0x0000000E9377EB18 = 0x000001a410809640

            如图:
            
            我们可以使用【!do 000001a410809640】命令验证是不是 sample 变量。

 1 0:000> !do 0x000001a410809640
 2 Name:        ExampleCore_2_1_2.TypeSample
 3 MethodTable: 00007ffb47809460(这里是方法表)
 4 EEClass:     00007ffb47811f90
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\Source\...\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ffb47809408  4000001        8 ...ample+Coordinates  1 instance 000001a410809648 coordinates

            【!dumpObj】命令的参数是引用类型实例的地址,它能显示这个实例对象所有信息。在前面的输出中我们可以看到这个类型包含一个域,偏移(Offset)是8个字节,类型是(Type)Coordinates,并且 VT (ValueType)列的值是1(0就是引用类型),说明是一个值类型。在 Value 列给出来这个域所在的地址。如果要显示引用类型对象中的各个域,可以再次使用【dumpObj】命令,如果是值类型,可以直接使用【dumpvc】命令。
            解释如图:
            

            我们既然知道了局部变量的地址,又知道它是值类型,我们直接使用【!dumpVC】命令查看它的详情。        

 1 0:000> !DumpVC /d 00007ffb4eae9408 0000028dd4c09648
 2 Name:        ExampleCore_2_1_2.TypeSample+Coordinates
 3 MethodTable: 00007ffb4eae9408
 4 EEClass:     00007ffb4eaf2008
 5 Size:        32(0x20) bytes
 6 File:        E:\Visual Studio 2022\Source\...\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 7 Fields:
 8               MT    Field   Offset                 Type VT     Attr            Value Name
 9 00007ffb4e9a1188  4000002        0         System.Int32  1 instance               10 x
10 00007ffb4e9a1188  4000003        4         System.Int32  1 instance                5 y
11 00007ffb4e9a1188  4000004        8         System.Int32  1 instance               10 z

            该命令的格式:!DumpVC /d 00007ffb4eae9408(方法表的地址) 0000028dd4c09648(值类型的地址)。            
            

        6.1、同步块表
            A、基础知识
                在托管堆上的每个实例的前面都包含一个同步块索引,它指向 CLR 私有堆中的同步块表。在同步块表中包含的是指向各个同步块的指针,同步块可以包含很多信息,比如:对象的锁、互用性数据、应用程序域的索引、对象的散列码等。当然,也有可能在对象中不包含任何同步块数据,此时的同步块索引值是0。需要注意的是,在同步块中并不一定只包含简单的索引,也可以包含关于对象的其他辅助信息。
                在使用索引时需要特别注意,CLR 可以自由的移动/增长同步块表,同时却不一定对所有包含同步块的对象头进行调整。
                如果 CLR 为某个对象创建一个同步块索引,那我们如何查看通过这个索引指向的同步块表的具体内容呢?由于同步块表位于 CLR 的私有内存中,因此无法直接访问同步块表。但是,我们可以使用【!syncblk】命令,该命令可以给出同步块表的一些信息。该命令的参数就是同步块的索引值,也可以不带任何参数,如果不带任何参数,将转储出同步块表中的所有元素。

                还有一点需要注意,32 位系统和 64 位系统查看同步块索引减去的值不一样,32位系统减去 4 个字节,64 位系统减去 8 个字节。
                
            B、眼见为实
                源码项目:ExampleCore_2_1_2
                
调试任务:一个对象在获取锁和没有获取锁的的同步块是什么样子?                
                首先在 Main() 方法中设置了一个中断,在 AddCoordinates() 方法中设置了一个中断,之所以要设置两个中断,是为了说明一个对象在没有获取任何锁或者获取了一个锁这两种情况下的不同。
                我们首先编译我们的项目,打开【Windbg Preview】,依次点击【文件】-->【Launch executable】加载我们的可以执行程序。进入到调试器界面后,【g】继续运行,程序会在Main()方法第10行【Debugger.Break();】这行代码处暂停。效果如图:
                
                我们现在可以查看线程的调用栈,使用【!clrstack -a】命令。
 1 0:000> !clrstack -a
 2 OS Thread Id: 0x44e0 (0)
 3         Child SP               IP Call Site
 4 000000E60137E928 00007ff976c8b502 [HelperMethodFrame: 000000e60137e928] System.Diagnostics.Debugger.BreakInternal()
 5 000000E60137EA30 00007ff89278550a System.Diagnostics.Debugger.Break() [/_/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 18]
 6 
 7 000000E60137EA60 00007ff8335d1998 ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_2\Program.cs @ 10]
 8     PARAMETERS:
 9         args (0x000000E60137EAB0) = 0x000001ed34c08ea0
10     LOCALS:
11         0x000000E60137EA98 = 0x000001ed34c09640

                0x000001ed34c09640 红色标记的地址就是 TypeSample 引用类型的局部变量 sample 的地址。这个地址指向的是对象实例的起始位置,想要查看它的同步块索引,需要减去8(我的是64位,32位是减去 0x4)个字节(DWORD)。继续使用【dp 000001ed34c09640-0x8】命令查看数据。

1 0:000> dp 000001ed34c09640-0x8
2 000001ed`34c09638  00000000`00000000 00007ff8`33689460
3 000001ed`34c09648  00000005`0000000a 00000000`0000000a
4 000001ed`34c09658  00000000`00000000 00000000`00000000
5 000001ed`34c09668  00000000`00000000 00000000`00000000
6 000001ed`34c09678  00000000`00000000 00000000`00000000
7 000001ed`34c09688  00000000`00000000 00000000`00000000
8 000001ed`34c09698  00000000`00000000 00000000`00000000
9 000001ed`34c096a8  00000000`00000000 00000000`00000000

                在对位置【dp 000001ed34c09640-0x8】进行转储输出时结果是 0x0,也就是红色标记的值,这表示该对象并不包含相关的同步块索引。
                接下来,我们继续使用【g】命令,运行调试器,会在 AddCoordinates() 方法内的第 45 行【Debugger.Break();】代码处暂停。
                效果如图:
                

                我们再次执行【!clrstack -a】命令,查看线程托管代码的调用栈。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x44e0 (0)
 3         Child SP               IP Call Site
 4 000000E60137E8A8 00007ff976c8b502 [HelperMethodFrame: 000000e60137e8a8] System.Diagnostics.Debugger.BreakInternal()
 5 000000E60137E9B0 00007ff89278550a System.Diagnostics.Debugger.Break() [/_/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 18]
 6 
 7 000000E60137E9E0 00007ff8335d1acd ExampleCore_2_1_2.TypeSample.AddCoordinates() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_2\Program.cs @ 45]
 8     PARAMETERS:
 9         this (0x000000E60137EA60) = 0x000001ed34c09640
10     LOCALS:
11         0x000000E60137EA4C = 0x000000000378734a
12         0x000000E60137EA40 = 0x000001ed34c09640
13         0x000000E60137EA38 = 0x0000000000000001
14         0x000000E60137EA28 = 0x0000000000000000
15 
16 000000E60137EA60 00007ff8335d19a5 ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_2\Program.cs @ 11]
17     PARAMETERS:
18         args (0x000000E60137EAB0) = 0x000001ed34c08ea0
19     LOCALS:
20         0x000000E60137EA98 = 0x000001ed34c09640

                我们之所以再次使用【!clrstack -a】命令,是因为在垃圾收集器的空闲期间,托管堆上的对象可能会被移动到其他位置。在输出结果中,地址没有变化,说明对象没有被移动。我们继续使用【 dp 0x000001ed34c09640-0x8】命令。

1 0:000> dp 0x000001ed34c09640-0x8
2 000001ed`34c09638  08000001`00000000 00007ff8`33689460
3 000001ed`34c09648  00000005`0000000a 00000000`0000000a
4 000001ed`34c09658  00000000`00000000 00000000`00000000
5 000001ed`34c09668  00000000`00000000 00000000`00000000
6 000001ed`34c09678  00000000`00000000 00000000`00000000
7 000001ed`34c09688  00000000`00000000 00000000`00000000
8 000001ed`34c09698  00000000`00000000 00000000`00000000
9 000001ed`34c096a8  00000000`00000000 00000000`00000000

                这次我们看到了结果,红色加粗标注的 0x08000001,08表示这个同步块索引里包含了哈希值,因为我们调用了对象的 GetHashCode() 方法。红色尾部部分包含 1,说明对象包含一个同步块索引。
                如果想看同步块索引表中08000001处内容,可以使用【!syncblk】命令,输出所有同步快表中所有的元素。

1 0:000> !syncblk
2 Index         SyncBlock MonitorHeld Recursion Owning Thread Info          SyncBlock Owner
3     1 00000213FA113E08            1         1 00000213FA0F6B50 3088   0   00000213fe809640 ExampleCore_2_1_2.TypeSample
4 -----------------------------
5 Total           1
6 CCW             0
7 RCW             0
8 ComClassFactory 0
9 Free            0

                也可以使用【!syncblk 1】,只输出同步快表中索引值为1的信息,这里两个命令输出是一样的。

1 0:000> !syncblk 1
2 Index         SyncBlock MonitorHeld Recursion Owning Thread Info          SyncBlock Owner
3     1 00000213FA113E08            1         1 00000213FA0F6B50 3088   0   00000213fe809640 ExampleCore_2_1_2.TypeSample
4 -----------------------------
5 Total           1
6 CCW             0
7 RCW             0
8 ComClassFactory 0
9 Free            0

                从上面的输出可以看到,索引为1的同步块指向的是一个已锁定的监视器,由线程【00000213FA0F6B50】持有,也可以说持有锁的线程【00000213FA0F6B50】锁住了【ExampleCore_2_1_2.TypeSample】类型的实例说明一下,我们在 AddCoordinates() 方法中调用了 GetHashCode() 方法,之所以要这样做,是为了强制创建一个同步块入口,当调用【lock】语句时,它将判断是否存在一个同步块与对象相关,如果存在,则把同步块作为同步数据。如果不存在同步块,CLR 将初始化一个廋锁(thin lock),而瘦锁保存的位置与同步块不同。


        6.2、类型句柄
            A、基础知识
                引用类型的所有实例都被存储在托管堆上,这个堆是由 GC 来管理的。在所有实例中都包含一个【类型句柄】。简单来说,类型句柄指向的是某个类型的方法表。在方法表中包含了各种元数据,它们完整的描述了这个类型。【类型句柄】是 CLR 类型系统中的粘合剂,它把对象实例和其相关的所属类型数据关联起来。对象的【类型句柄】存储在托管堆上,它是一个指针,指向类型的方法表。我们先看看托管堆中的对象和方法表的结构,效果如图:
                 
                【类型句柄】指向的方法表包含了和类型相关的各种元数据,他们完整的描述了这个类型。在【类型句柄】指向的第一类数据中包含了关于类型本身的一些信息,我们列出一些,当然,这些内容都是比较老的,现在可能有变化。如图:
                
                我们可以使用【!DumpMT】命令直接查看某个类型的方法表。如果我们有了引用类型的地址,就可以使用【!do】或者【!DumpObj】命令查看它的详情,详情里面就包含方法表的地址,有了方法表的地址,我们在使用【!DumpMT】命令就可以知道方发表的详情了。

            B、眼见为实
                源码项目:ExampleCore_2_1_2
                调试任务:通过调试器了解引用类型的类型句柄
                编译好我们的项目,打开【Windbg Preview】,依次点击【File】-->【Launch executable】,选择加载我们的项目文件:ExampleCore_2_1_2.exe,进入到调试器界面。我们使用【g】命令,继续运行调试器,开始执行我们的程序,我们的程序会在 Main() 方法的【Debugger.Break()】这行代码处暂停。
                接下来,我们使用【!clrstack -a】命令查看一下托管代码的线程调用栈。
 1 0:000> !clrstack -a
 2 OS Thread Id: 0x24c0 (0)
 3         Child SP               IP Call Site
 4 0000000E5577EA08 00007ffb3eee9202 [HelperMethodFrame: 0000000e5577ea08] System.Diagnostics.Debugger.BreakInternal()
 5 0000000E5577EB10 00007ffa78ae60aa System.Diagnostics.Debugger.Break() [/_/src/coreclr/./System/Diagnostics/Debugger.cs @ 18]
 6 
 7 0000000E5577EB40 00007ffa19f91998 ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio\.\ExampleCore_2_1_2\Program.cs @ 10]
 8     PARAMETERS:
 9         args (0x0000000E5577EB90) = 0x00000129b7808ea0
10     LOCALS:
11         0x0000000E5577EB78 = 0x00000129b7809640

                红色标注的就是我们的局部变量,0x00000129b7809640 这个地址就是 TypeSample 类型的实例变量 sample。我们继续使用【dp 0x00000129b7809640】命令转储出数据结构。说明一下:如果是查找【同步块索引】,我们需要减去 0x4,如果想找到【类型句柄】,是不需要做多余的操作的,输出的结果值的第一个域值就是【类型句柄】的指针。

1 0:000> dp 0x00000129b7809640
2 00000129`b7809640  00007ffa`1a049460 00000005`0000000a
3 00000129`b7809650  00000000`0000000a 00000000`00000000
4 00000129`b7809660  00007ffa`19f01188 00000000`0000006e
5 00000129`b7809670  00000000`00000000 00007ffa`19f01188
6 00000129`b7809680  00000000`00000037 00000000`00000000
7 00000129`b7809690  00007ffa`19f01188 00000000`0000006e
8 00000129`b78096a0  00000000`00000000 00007ffa`19ec5fa8
9 00000129`b78096b0  00000000`00000000 00000000`00000000

                我加粗标红的值就是一个指针,它就是【类型句柄】,我们如果想查看【类型句柄】指向的方法表的具体内容,可以继续使用【dp】命令。

1 0:000> dp 00007ffa`1a049460
2 00007ffa`1a049460  00000020`00000000 00000004`00030080
3 00007ffa`1a049470  00007ffa`19ec5fa8 00007ffa`1a01e0a0
4 00007ffa`1a049480  00007ffa`1a0494a8 00007ffa`1a051f90
5 00007ffa`1a049490  00000000`00000000 00000000`00000000
6 00007ffa`1a0494a0  00007ffa`19ec5ff0 00000000`00000080
7 00007ffa`1a0494b0  00000000`00000000 00007ffa`1a049670
8 00007ffa`1a0494c0  90001560`31001ddb 00007ffa`1a03b960
9 00007ffa`1a0494d0  00007ffa`1a049670 00000000`00000000

                里面的内容还是很多的,要想搞清楚每个项目的内容,还需要下点功夫。我先到此为止。
                其实,我们可以使用【!dumpMT】这个命令查看方法表。

 1 0:000> dp 0x00000129b7809640
 2 00000129`b7809640  00007ffa`1a049460 00000005`0000000a
 3 00000129`b7809650  00000000`0000000a 00000000`00000000
 4 00000129`b7809660  00007ffa`19f01188 00000000`0000006e
 5 00000129`b7809670  00000000`00000000 00007ffa`19f01188
 6 00000129`b7809680  00000000`00000037 00000000`00000000
 7 00000129`b7809690  00007ffa`19f01188 00000000`0000006e
 8 00000129`b78096a0  00000000`00000000 00007ffa`19ec5fa8
 9 00000129`b78096b0  00000000`00000000 00000000`00000000
10 
11 
12 
13 0:000> !dumpmt 00007ffa`1a049460
14 EEClass:             00007ffa1a051f90
15 Module:              00007ffa1a01e0a0
16 Name:                ExampleCore_2_1_2.TypeSample
17 mdToken:             0000000002000003
18 File:                E:\Visual Studio 2022\...\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
19 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
20 BaseSize:            0x20
21 ComponentSize:       0x0
22 DynamicStatics:      false
23 ContainsPointers:    false
24 Slots in VTable:     6
25 Number of IFaces in IFaceMap: 0

                我们还有其他方法,可以先使用【!DumpObj】命令,然后再使用【!DumpMT】命令,也是可以的。

 1 0:000> !DumpObj /d 00000129b7809640(这个是TypeSmaple 类型局部变量的地址)
 2 Name:        ExampleCore_2_1_2.TypeSample
 3 MethodTable: 00007ffa1a049460(这个就是方法表的地址,也就是 DumpMT 命令的输入参数)
 4 EEClass:     00007ffa1a051f90
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\..\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ffa1a049408  4000001        8 ...ample+Coordinates  1 instance 00000129b7809648 coordinates
11 
12 
13 0:000> !DumpMT /d 00007ffa1a049460
14 EEClass:             00007ffa1a051f90
15 Module:              00007ffa1a01e0a0
16 Name:                ExampleCore_2_1_2.TypeSample
17 mdToken:             0000000002000003
18 File:                E:\Visual Studio 2022\Source\..\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
19 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
20 BaseSize:            0x20
21 ComponentSize:       0x0
22 DynamicStatics:      false
23 ContainsPointers:    false
24 Slots in VTable:     6
25 Number of IFaces in IFaceMap: 0

                无论使用什么命令执行获取方法表信息,都必须是先使用【!clrstack -a|-l】找到局部变量的地址,然后才可以继续。


        6.3、方法描述符
            A、基础知识
                我们知道了方法表是描述类型的,那方法是如何自描述的呢?答案是通过【方法描述符】来实现的,在【方法描述符】中包含了方法的详细信息,包括:方法的文本表示、它所在的模块、标记以及实现方法的代码的地址。
                要想找到指定方法的方法描述符,可以使用【!dumpMT】,同时使用 -md 开关。
                  
            B、眼见为实
                源码项目:ExampleCore_2_1_2
                调试任务:通过调试器了解方法描述符
                编译好我们的项目,打开【Windbg Preview】调试器,依次点击【文件】---->【Launch executable】,加载我们的项目文件:ExampleCore_2_1_2.exe,进入调试器界面。我们使用【g】命令继续运行调试器,调试器会在源码中 Main() 方法的【Debugger.Break()】这行代码处暂停。在开始之前,先查看一下调试器是否在主线程,如果不是,我们必须切换到主线程,执行命令【~0s】,我们就可以开始我们的调试了。               
 1 0:000> !clrstack -l
 2 OS Thread Id: 0x24c0 (0)
 3         Child SP               IP Call Site
 4 0000000E5577E7F0 00007ffb412eae54 [InlinedCallFrame: 0000000e5577e7f0] 
 5 0000000E5577E7F0 00007ffa784d76eb [InlinedCallFrame: 0000000e5577e7f0] 
 6 ......
 7 
 8 0000000E5577EB40 00007ffa19f919ac ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio\.\ExampleCore_2_1_2\Program.cs @ 12]
 9     LOCALS:
10         0x0000000E5577EB78 = 0x00000129b7809640

                 红色标注的就是我们 TypeSample 类型的局部变量 sample 的地址,我们使用【!DumpObj /d 0x00000129b7809640】命令查看对象的数据结构,从而找到该类型的【方法表】的地址。当然,使用【dp 0x00000129b7809640】命令也是可以的。

 1 0:000> dp 0x00000129b7809640
 2 00000129`b7809640  00007ffa`1a049460 00000005`0000000a
 3 00000129`b7809650  00000000`0000000a 00000000`00000000
 4 00000129`b7809660  00007ffa`19f01188 00000000`0000006e
 5 00000129`b7809670  00000000`00000000 00007ffa`19f01188
 6 00000129`b7809680  00000000`00000037 00000000`00000000
 7 00000129`b7809690  00007ffa`19f01188 00000000`0000006e
 8 00000129`b78096a0  00000000`00000000 00007ffa`19ec5fa8
 9 00000129`b78096b0  00000000`00000000 00000000`00000000
10 
11 
12 0:000> !DumpObj /d 0x00000129b7809640
13 Name:        ExampleCore_2_1_2.TypeSample
14 MethodTable: 00007ffa1a049460
15 EEClass:     00007ffa1a051f90
16 Tracked Type: false
17 Size:        32(0x20) bytes
18 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
19 Fields:
20               MT    Field   Offset                 Type VT     Attr            Value Name
21 00007ffa1a049408  4000001        8 ...ample+Coordinates  1 instance 00000129b7809648 coordinates

                两个命令执行的结果,红色标注的都是 TypeSample 类型的方法表,有了方法表的地址,我们就可以执行【!DumpMT -md 00007ffa1a049460】命令了。

 1 0:000> !DumpMT -md 00007ffa1a049460
 2 EEClass:             00007ffa1a051f90
 3 Module:              00007ffa1a01e0a0
 4 Name:                ExampleCore_2_1_2.TypeSample
 5 mdToken:             0000000002000003
 6 File:                E:\Visual Studio 2022\.\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 7 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
 8 BaseSize:            0x20
 9 ComponentSize:       0x0
10 DynamicStatics:      false
11 ContainsPointers:    false
12 Slots in VTable:     6
13 Number of IFaces in IFaceMap: 0
14 --------------------------------------
15 MethodDesc Table
16            Entry       MethodDesc    JIT Name
17 00007FFA19ED0048 00007ffa19ec5f38   NONE System.Object.Finalize()
18 00007FFA19ED0060 00007ffa19ec5f48   NONE System.Object.ToString()
19 00007FFA19ED0078 00007ffa19ec5f58   NONE System.Object.Equals(System.Object)
20 00007FFA1A0B9548 00007ffa19ec5f98 PreJIT System.Object.GetHashCode()
21 00007FFA1A03B930 00007ffa1a0493a8    JIT ExampleCore_2_1_2.TypeSample..ctor(Int32, Int32, Int32)
22 00007FFA1A03B948 00007ffa1a0493c0    JIT ExampleCore_2_1_2.TypeSample.AddCoordinates()

                MethodDesc Table 红色标注的列表输出了 TypeSample 类型所有方法描述符。
                1)、PreJIT:表示位于 Entry 地址处的代码已经被 JIT 预编译过了。
                2)、JIT:表示这段代码已经编译过了。
                3)、NONE:表示这段代码还没有被 JIT 编译过。
                如果我们想获取更详细的信息,可以将【MethodDesc】列的地址,传递给【!DumpMD】命令。

 1 0:000> !DumpMD 00007ffa1a0493a8 
 2 Method Name:          ExampleCore_2_1_2.TypeSample..ctor(Int32, Int32, Int32)
 3 Class:                00007ffa1a051f90
 4 MethodTable:          00007ffa1a049460
 5 mdToken:              0000000006000003
 6 Module:               00007ffa1a01e0a0
 7 IsJitted:             yes
 8 Current CodeAddr:     00007ffa19f919d0
 9 Version History:
10   ILCodeVersion:      0000000000000000
11   ReJIT ID:           0
12   IL Addr:            00000129b4ce2085
13      CodeAddr:           00007ffa19f919d0  (MinOptJitted)
14      NativeCodeVersion:  0000000000000000

                在这个输出中有两个要注意的项目,分别是:IsJitted 和 CodeAddr。如果【IsJitted】的值是 Yes,说明方法已经被JIT编译了。如果方法未编译,则是 no 值,【Current CodeAddr】的值也是:ffffffffffffffff,效果如图:
                

        6.4、模块
            A、基础知识
                我们知道程序集是 .Net 应用程序的逻辑容器,它可以包含一个或者多个模块,这些模块就是真正包含代码和资源的组件。当我们观察各种 CLR 数据结构时(例如:方法表、方法描述符等),会发现在这些数据结构中包含一个指向定义它们额模块。这一节,我们就主要关注模块,内容不是很多,主要就是演示。
                我们可以使用【DumpModule [-mt] <Module Address>】命令获取模块上的扩展信息。-mt 命令行开关可以输出在模块中定义和使用的所有类型。
                模块中包含一些映射数据,它们的主要作用就是将标记映射到底层的 CLR 数据结构。这些映射数据如下:
                1)、TypeDefToMethodTableMap:类型定义和方法表的映射
                2)、TypeRefToMethodTableMap:类型引用和方法表的映射
                3)、MethodDefToDescMap:方法定义和方法描述符的映射
                4)、FieldDefToDescMap:字段定义和方法描述符的映射
                5)、MemberRefToDescMap:成员引用和描述符的映射
                6)、FileReferencesMap:文件引用的映射
                7)、AssemblyReferencesMap:程序集引用的映射

            B、眼见为实
                调试源码:ExampleCore_2_1_2
                调试任务:通过调试器了解模块(Module)
                编译好我们的项目,打开【Windbg Preview】,依次点击【文件】---->【Launch executable】,加载我们的项目文件:ExampleCore_2_1_2.exe,选择【打开】按钮,进入调试器界面。通过【g】命令运行调试器。接下来,我们查看一下模块的信息。
                我们先查找一下托管程序的线程调用栈,找到我们需要的局部变量 sample。
1 0:000> !clrstack -l
2 OS Thread Id: 0xa5c (0)
3         Child SP               IP Call Site
4 000000D3F3B7EB48 00007ffa87319202 [HelperMethodFrame: 000000d3f3b7eb48] System.Diagnostics.Debugger.BreakInternal()
5 000000D3F3B7EC50 00007ff9c18660aa System.Diagnostics.Debugger.Break() [/_/src/coreclr/./src/System/Diagnostics/Debugger.cs @ 18]
6 
7 000000D3F3B7EC80 00007ff962951998 ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio\.\ExampleCore_2_1_2\Program.cs @ 10]
8     LOCALS:
9         0x000000D3F3B7ECB8 = 0x0000022d02409640

                红色标注的就是 TypeSmaple 类型的局部变量 sample 的地址。我们查看【!dumpobj 0x0000022d02409640】,就会输出局部变量的数据信息。

 1 0:000> !dumpobj /d 0x0000022d02409640
 2 Name:        ExampleCore_2_1_2.TypeSample
 3 MethodTable: 00007ff962a09460
 4 EEClass:     00007ff962a11f90
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff962a09408  4000001        8 ...ample+Coordinates  1 instance 0000022d02409648 coordinates

                红色标注的就是 TypeSample 类型的方法表的地址,我们可以继续使用【!dumpmt /d 00007ff962a09460】命令输出详情。

 1 0:000> !dumpmt 00007ff962a09460
 2 EEClass:             00007ff962a11f90
 3 Module:              00007ff9629de0a0
 4 Name:                ExampleCore_2_1_2.TypeSample
 5 mdToken:             0000000002000003
 6 File:                E:\Visual Studio 2022\.\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 7 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
 8 BaseSize:            0x20
 9 ComponentSize:       0x0
10 DynamicStatics:      false
11 ContainsPointers:    false
12 Slots in VTable:     6
13 Number of IFaces in IFaceMap: 0

                红色标注的就是方法表这个数据结构所属与的模块地址。我们可以使用【!dumpmodule /d 00007ff9629de0a0】命令查看有关模块的相信信息了。

 1 0:000> !dumpmodule /d 00007ff9629de0a0
 2 Name: E:\Visual Studio 2022\Source\...\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 3 Attributes:              PEFile 
 4 TransientFlags:          00209011 
 5 Assembly:                0000022cff24eba0
 6 BaseAddress:             0000022CFF6E0000
 7 PEAssembly:              0000022CFF24EA60
 8 ModuleId:                00007FF9629DE458
 9 ModuleIndex:             0000000000000001
10 LoaderHeap:              00007FF9C2809508
11 TypeDefToMethodTableMap: 00007FF9629E4320
12 TypeRefToMethodTableMap: 00007FF9629E4348
13 MethodDefToDescMap:      00007FF9629E4488
14 FieldDefToDescMap:       00007FF9629E44B0
15 MemberRefToDescMap:      00007FF9629E43E8
16 FileReferencesMap:       0000000000000000
17 AssemblyReferencesMap:   00007FF9629E44E0
18 MetaData start address:  0000022CFF6E2168 (1788 bytes)

                除了名称、属性、所属的程序集地址和加载器堆以外,还有一组映射(Map),这些映射只是将这些标记映射到底层的 CLR 数据结构。其实从命名上也可以看出端倪,不如:TypeDefToMethodTableMap:表示定义的类型与方法表中的映射,MethodDefToDescMap:定义的方法和描述符之间的映射。举个例子:如果要将一个方法定义标记映射到一个方法描述符,我们可以输出【MethodDefToDescMap】域的信息。我们使用【dp 00007FF9629E4488】查看。

1 0:000> dp 00007FF9629E4488
2 00007ff9`629e4488  00000000`00000000 00007ff9`62a000c0
3 00007ff9`629e4498  00007ff9`62a000d8 00007ff9`62a093a8
4 00007ff9`629e44a8  00007ff9`62a093c0 00000000`00000000
5 00007ff9`629e44b8  00007ff9`62a09378 00007ff9`62a093d8
6 00007ff9`629e44c8  00007ff9`62a093e8 00007ff9`62a093f8
7 00007ff9`629e44d8  00000000`00000000 00000000`00000000
8 00007ff9`629e44e8  00007ff9`629dfbc8 00007ff9`62a09760
9 00007ff9`629e44f8  00000000`00000000 00007ff9`629de0a0

                00007ff9`62a000c0 就是 Main() 方法的方法描述符的地址。00007ff9`62a000d8 就是 Program..ctor() 方法的描述符地址,00007ff9`62a093a8 就是 TypeSample..ctor(Int32, Int32, Int32) 方法的描述符地址。00007ff9`62a093c0 就是TypeSample.AddCoordinates()方发的描述符地址。

 1 0:000> !dumpmd /d 00007ff9`62a000c0
 2 Method Name:          ExampleCore_2_1_2.Program.Main(System.String[])
 3 Class:                00007ff9629efbc0
 4 MethodTable:          00007ff962a000e8
 5 mdToken:              0000000006000001
 6 Module:               00007ff9629de0a0
 7 IsJitted:             yes
 8 Current CodeAddr:     00007ff962951930
 9 Version History:
10   ILCodeVersion:      0000000000000000
11   ReJIT ID:           0
12   IL Addr:            0000022cff6e2050
13      CodeAddr:           00007ff962951930  (MinOptJitted)
14      NativeCodeVersion:  0000000000000000
15 
16 0:000> !dumpmd /d 00007ff9`62a000d8
17 Method Name:          ExampleCore_2_1_2.Program..ctor()
18 Class:                00007ff9629efbc0
19 MethodTable:          00007ff962a000e8
20 mdToken:              0000000006000002
21 Module:               00007ff9629de0a0
22 IsJitted:             no
23 Current CodeAddr:     ffffffffffffffff
24 Version History:
25   ILCodeVersion:      0000000000000000
26   ReJIT ID:           0
27   IL Addr:            0000022cff6e207c
28      CodeAddr:           0000000000000000  (MinOptJitted)
29      NativeCodeVersion:  0000000000000000
30 
31 0:000> !dumpmd /d 00007ff9`62a093a8
32 Method Name:          ExampleCore_2_1_2.TypeSample..ctor(Int32, Int32, Int32)
33 Class:                00007ff962a11f90
34 MethodTable:          00007ff962a09460
35 mdToken:              0000000006000003
36 Module:               00007ff9629de0a0
37 IsJitted:             yes
38 Current CodeAddr:     00007ff9629519d0
39 Version History:
40   ILCodeVersion:      0000000000000000
41   ReJIT ID:           0
42   IL Addr:            0000022cff6e2085
43      CodeAddr:           00007ff9629519d0  (MinOptJitted)
44      NativeCodeVersion:  0000000000000000
45 
46 0:000> !dumpmd /d 00007ff9`62a093c0
47 Method Name:          ExampleCore_2_1_2.TypeSample.AddCoordinates()
48 Class:                00007ff962a11f90
49 MethodTable:          00007ff962a09460
50 mdToken:              0000000006000004
51 Module:               00007ff9629de0a0
52 IsJitted:             no
53 Current CodeAddr:     ffffffffffffffff
54 Version History:
55   ILCodeVersion:      0000000000000000
56   ReJIT ID:           0
57   IL Addr:            0000022cff6e20b4
58      CodeAddr:           0000000000000000  (MinOptJitted)
59      NativeCodeVersion:  0000000000000000

                【dumpmodule】命令不仅可以输出模块的特定信息,还可以输出在模块中定义和使用的所有类型。只要加上 -mt 命令开关。执行命令【!dumpmodule -mt 00007ff9629de0a0】查看模块中所有和使用的类型。

 1 0:000> !dumpmodule -mt 00007ff9629de0a0
 2 Name: E:\Visual Studio 2022\.\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 3 Attributes:              PEFile 
 4 TransientFlags:          00209011 
 5 Assembly:                0000022cff24eba0
 6 BaseAddress:             0000022CFF6E0000
 7 PEAssembly:              0000022CFF24EA60
 8 ModuleId:                00007FF9629DE458
 9 ModuleIndex:             0000000000000001
10 LoaderHeap:              00007FF9C2809508
11 TypeDefToMethodTableMap: 00007FF9629E4320
12 TypeRefToMethodTableMap: 00007FF9629E4348
13 MethodDefToDescMap:      00007FF9629E4488
14 FieldDefToDescMap:       00007FF9629E44B0
15 MemberRefToDescMap:      00007FF9629E43E8
16 FileReferencesMap:       0000000000000000
17 AssemblyReferencesMap:   00007FF9629E44E0
18 MetaData start address:  0000022CFF6E2168 (1788 bytes)
19 
20 Types defined in this module
21 
22               MT          TypeDef Name
23 ------------------------------------------------------------------------------
24 00007ff962a000e8 0x02000002 ExampleCore_2_1_2.Program
25 00007ff962a09460 0x02000003 ExampleCore_2_1_2.TypeSample
26 00007ff962a09408 0x02000004 ExampleCore_2_1_2.TypeSample+Coordinates
27 
28 Types referenced in this module
29 
30               MT            TypeRef Name
31 ------------------------------------------------------------------------------
32 00007ff962885fa8 0x0200000d System.Object
33 00007ff9628860f0 0x0200000f System.ValueType
34 00007ff962a09670 0x02000010 System.Diagnostics.Debugger
35 00007ff962a0aa78 0x02000011 System.Console

        6.5、元数据标记
            a)、基础知识
                我们到现在为止,已经看到了很多运行时的数据结构,比如:程序集,模块,方法表和方法描述符等。所有这些数据结构都是为了支持 .NET 程序的类型系统和自描述属性。这些数据结构就是元数据,它们以表格的形式存储在运行时的引擎中。元数据表有很多,这是必须要知道的,这么多表也不可能全部讨论完,对于我们最重要的就是要知道 CLR 如何使用这些元数据以及如何通过元数据标记来引用它们。简单类说,元数据标记就是一个 4 字节的值。高位的 1 个字节表示该标记所引用的表。元数据表如图:
                
                例如:06000001的元数据标记表示指向方法定义表的(高位字节为0x06)中的第1个索引。效果如图:
                

            b)、眼见为实
                调试源码:ExampleCore_2_1_2
                调试任务:通过调试器了解元数据标记
                编译好我们的项目,打开【Windbg Preview】,依次点击【文件】---->【Launch executable】,加载我们的项目文件:ExampleCore_2_1_2.exe,选择【打开】按钮,进入调试器界面。通过【g】命令运行调试器。接下来,我们查看一下模块的信息。
                我们先来看看我们托管程序的线程调用栈,执行命令【!clrstack -l】。
1 0:000> !clrstack -l
2 OS Thread Id: 0xa5c (0)
3         Child SP               IP Call Site
4 000000D3F3B7EB48 00007ffa87319202 [HelperMethodFrame: 000000d3f3b7eb48] System.Diagnostics.Debugger.BreakInternal()
5 000000D3F3B7EC50 00007ff9c18660aa System.Diagnostics.Debugger.Break() [/_/src/coreclr/./Debugger.cs @ 18]
6 
7 000000D3F3B7EC80 00007ff962951998 ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio\.\ExampleCore_2_1_2\Program.cs @ 10]
8     LOCALS:
9         0x000000D3F3B7ECB8 = 0x0000022d02409640

                找到我们本地的局部变量 sample,然后输出它的内容,执行【!DumpObj /d 0000022d02409640】命令。

 1 0:000> !DumpObj /d 0000022d02409640
 2 Name:        ExampleCore_2_1_2.TypeSample
 3 MethodTable: 00007ff962a09460
 4 EEClass:     00007ff962a11f90
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\.\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff962a09408  4000001        8 ...ample+Coordinates  1 instance 0000022d02409648 coordinates

                我们知道了方法表,知道模块也就很容易了,执行命令【!DumpMT /d 00007ff962a09460】。

 1 0:000> !DumpMT /d 00007ff962a09460
 2 EEClass:             00007ff962a11f90
 3 Module:              00007ff9629de0a0
 4 Name:                ExampleCore_2_1_2.TypeSample
 5 mdToken:             0000000002000003
 6 File:                E:\Visual Studio 2022\.\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 7 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
 8 BaseSize:            0x20
 9 ComponentSize:       0x0
10 DynamicStatics:      false
11 ContainsPointers:    false
12 Slots in VTable:     6
13 Number of IFaces in IFaceMap: 0

                我们有了模块地址,就可以使用【!DumpModule /d 00007ff9629de0a0】命令,查看模块详情。

 1 0:000> !DumpModule /d 00007ff9629de0a0
 2 Name: E:\Visual Studio 2022\.\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 3 Attributes:              PEFile 
 4 TransientFlags:          00209011 
 5 Assembly:                0000022cff24eba0
 6 BaseAddress:             0000022CFF6E0000
 7 PEAssembly:              0000022CFF24EA60
 8 ModuleId:                00007FF9629DE458
 9 ModuleIndex:             0000000000000001
10 LoaderHeap:              00007FF9C2809508
11 TypeDefToMethodTableMap: 00007FF9629E4320
12 TypeRefToMethodTableMap: 00007FF9629E4348
13 MethodDefToDescMap:      00007FF9629E4488
14 FieldDefToDescMap:       00007FF9629E44B0
15 MemberRefToDescMap:      00007FF9629E43E8
16 FileReferencesMap:       0000000000000000
17 AssemblyReferencesMap:   00007FF9629E44E0
18 MetaData start address:  0000022CFF6E2168 (1788 bytes)

                红色标注的就是【!dumpmodule】命令输出中包含的一组常见的表映射。我们看看【TypeDefToMethodTableMap】这个域的值,它的地址:00007FF9629E4320,它将类型定义映射到相应的方法表。我们可以使用【dp】命令看一下具体数据。

1 0:000> dp 00007FF9629E4320
2 00007ff9`629e4320  00000000`00000000 00000000`00000000
3 00007ff9`629e4330  00007ff9`62a000e8 00007ff9`62a09460
4 00007ff9`629e4340  00007ff9`62a09408 00000000`00000000
5 00007ff9`629e4350  00000000`00000000 00000000`00000000
6 00007ff9`629e4360  00000000`00000000 00000000`00000000
7 00007ff9`629e4370  00000000`00000000 00000000`00000000
8 00007ff9`629e4380  00000000`00000000 00000000`00000000
9 00007ff9`629e4390  00000000`00000000 00000000`00000000

                红色标注的就是我们定义的类型,依次是:ProgramTypeSample 和 TypeSample+Coordinates,数据显示是如下。

 1 0:000> !dumpmt 00007ff9`62a000e8
 2 EEClass:             00007ff9629efbc0
 3 Module:              00007ff9629de0a0
 4 Name:                ExampleCore_2_1_2.Program
 5 mdToken:             0000000002000002
 6 File:                E:\Visual Studio 2022\Source\.\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 7 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
 8 BaseSize:            0x18
 9 ComponentSize:       0x0
10 DynamicStatics:      false
11 ContainsPointers:    false
12 Slots in VTable:     6
13 Number of IFaces in IFaceMap: 0
14 0:000> !dumpmt 00007ff9`62a09460
15 EEClass:             00007ff962a11f90
16 Module:              00007ff9629de0a0
17 Name:                ExampleCore_2_1_2.TypeSample
18 mdToken:             0000000002000003
19 File:                E:\Visual Studio 2022\Source\.ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
20 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
21 BaseSize:            0x20
22 ComponentSize:       0x0
23 DynamicStatics:      false
24 ContainsPointers:    false
25 Slots in VTable:     6
26 Number of IFaces in IFaceMap: 0
27 0:000> !dumpmt 00007ff9`62a09408
28 EEClass:             00007ff962a12008
29 Module:              00007ff9629de0a0
30 Name:                ExampleCore_2_1_2.TypeSample+Coordinates
31 mdToken:             0000000002000004
32 File:                E:\Visual Studio 2022\Source\.\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
33 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
34 BaseSize:            0x20
35 ComponentSize:       0x0
36 DynamicStatics:      false
37 ContainsPointers:    false
38 Slots in VTable:     4
39 Number of IFaces in IFaceMap: 0

                我们就拿第一个【00007ff9`62a000e8】进行说明,它定义的类型是:ExampleCore_2_1_2.Program,也就是 name 属性的值:ExampleCore_2_1_2.ProgrammdToken的值:000000000200000202000002为了两个部分,高位表示(0200)是一个类型,低位部分(0002)表示索引值为2。


        6.6、EEClass
            A、基础知识
                EEClass 和 MethodTable 是同级别的,用来描述 C# 的一个类,可以使用【!dumpclass】来显示类型的 EECLass 信息。从本质上来看,EEClass 和 MethodTable 他们又是两种截然不同的结构,不过从逻辑角度看,它们表示相同的概念。之所以有这种区分,主要是根据 CLR 针对类型域使用的频繁程度来决定的,频繁被使用的域存在方法表(Method Table)里,不太被频繁使用的域保存到 EEClass 数据结构中。
                面向对象语言(例如:C#)中的层次结构属性在 EECLass 结构中同样适用。当 CLR 加载一个类型时,会创建一个类型的 EEClass 节点层次结构,其中包含了指向父节点和兄弟节点的指针,这样就可以通过一种高效的方式来遍历这个层次结构。EECLass 中的大多数域都很简单,其中一个重要的域就是方法描述块域(methodDesc chunk),它包含了一个指针,指向类型中的第一组方法描述符,这就能够很容易遍历任意类型中的方法描述符。在每组方法描述符中又包含指向链表中下一组方法描述符的指针。
                我先上一个图,这个图比较老,有些内容已经变化了,大家需要注意。
                

            B、眼见为实
                调试源码:ExampleCore_2_1_2
                调试任务:通过调试器了解 EEClass
                编译好我们的项目,打开【Windbg Preview】,依次点击【文件】---->【Launch executable】,加载我们的项目文件:ExampleCore_2_1_2.exe,选择【打开】按钮,进入调试器界面。通过【g】命令运行调试器。接下来,我们查看一下模块的信息。
                我们先来看看我们托管程序的线程调用栈,执行命令【!clrstack -l】。
1 0:000> !clrstack -l
2 OS Thread Id: 0xa5c (0)
3         Child SP               IP Call Site
4 000000D3F3B7EB48 00007ffa87319202 [HelperMethodFrame: 000000d3f3b7eb48] System.Diagnostics.Debugger.BreakInternal()
5 000000D3F3B7EC50 00007ff9c18660aa System.Diagnostics.Debugger.Break() [/_/src/coreclr/./Debugger.cs @ 18]
6 
7 000000D3F3B7EC80 00007ff962951998 ExampleCore_2_1_2.Program.Main(System.String[]) [E:\Visual Studio\.\ExampleCore_2_1_2\Program.cs @ 10]
8     LOCALS:
9         0x000000D3F3B7ECB8 = 0x0000022d02409640

                0x0000022d02409640 这个就是我们的局部变量,可以使用【!dumpobj /d 0000022d02409640】显示类型的信息。

 1 0:000> !DumpObj /d 0000022d02409640
 2 Name:        ExampleCore_2_1_2.TypeSample
 3 MethodTable: 00007ff962a09460
 4 EEClass:     00007ff962a11f90
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_2_1_2\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff962a09408  4000001        8 ...ample+Coordinates  1 instance 0000022d02409648 coordinates

                以上红色标注的就是 EEClass 的地址,我们使用【!dumpclass /d 00007ff962a11f90】命令显示其数据。

 1 0:000> !dumpclass /d 00007ff962a11f90
 2 Class Name:      ExampleCore_2_1_2.TypeSample
 3 mdToken:         0000000002000003 (这是类型定义,索引为值:3)
 4 File:            E:\Visual Studio 2022\Source\P.\bin\Debug\net8.0\ExampleCore_2_1_2.dll
 5 Parent Class:    00007ff96287f5b0 (这是父类的地址)
 6 Module:          00007ff9629de0a0 (这是模块的地址)
 7 Method Table:    00007ff962a09460 (这是方法表的地址)
 8 Vtable Slots:    4 (TypeSample)
 9 Total Method Slots:  4 (TypeSample 类型方法总的数量 4个)
10 Class Attributes:    100001  
11 NumInstanceFields:   1(TypeSample类型实例字段有一个)
12 NumStaticFields:     0 (TypeSample 类型静态字段没有)
13               MT    Field   Offset                 Type VT     Attr            Value Name
14 00007ff962a09408  4000001        8 ...ample+Coordinates  1 instance           coordinates

五、总结
    站在高人的肩膀之上,自己轻松了很多,但是,自己还是一个小学生,Net 高级调试这条路,也刚刚起步,还有很多要学的地方。皇天不负有心人,努力,不辜负自己,我相信付出就有回报,再者说,学习的过程,有时候,虽然很痛苦,但是,学有所成,学有所懂,这个开心的感觉还是不可言喻的。不忘初心,继续努力。做自己喜欢做的,开心就好。
posted on 2024-02-27 10:59  可均可可  阅读(888)  评论(3编辑  收藏  举报