这是 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 }
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 }
四、基础知识
在这一段内容中,有的小节可能会包含两个部分,分别是 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
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
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)、LowFrequencyHeap、HighFrequencyHeap 和 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
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(值类型的地址)。
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,效果如图:
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
红色标注的就是我们定义的类型,依次是:Program、TypeSample 和 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.Program,mdToken的值:0000000002000002,02000002为了两个部分,高位表示(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 高级调试这条路,也刚刚起步,还有很多要学的地方。皇天不负有心人,努力,不辜负自己,我相信付出就有回报,再者说,学习的过程,有时候,虽然很痛苦,但是,学有所成,学有所懂,这个开心的感觉还是不可言喻的。不忘初心,继续努力。做自己喜欢做的,开心就好。