总目录
1. WinDbg概述
2. WinDbg主要功能
3. WinDbg程序调试示例
4. CPU寄存器及指令系统
5. CPU保护模式概述
6. 汇编语言不等于CPU指令
7. 用WinDbg观察托管程序架构
8. Windows PE/COFF文件格式简述
9. 让WinDbg自动打开DotNet Runtime源程序
10. WinDbg综合实战
前言
本文主要介绍.NET平台的Process(进程)、Application Momain(应用程序域)、Assembly(应用程序)和Module(模块)之间的关系,并在此基础上演示如何使用WinDbg找到我们想操控的类型和方法。
概念
进程(process)是操作系统的概念(动态的、内存的),而不是文件的(静态)概念。
当我们在Windows环境下双击一个exe可执行文件时,Windows会为该程序创建一个进程和一个主线程,然后通过主线程去运行这个程序。不同进程之间的虚拟地址空间是硬件隔离的,以确保一个程序中的数据或代码不会被另外一个程序非法访问(读、写、执行)。
如果被加载的可执行文件是一个.NET应用程序时,如果该.NET应用程序包含托管代码(绝大多数情况都是如此),那么Windows为该应用程序创建了process和主线程后,又会同时将托管程序CLR加载到用户进程空间,然后将程序交给CLR去管理和执行。
CLR为了提高程序运行效率,又引入了应用程序域(Application Domain)这个概念。应用程序域可以简单看作是CLR管理的Process,不同的Application Domain之间完全隔离,Assembly必须被装入到Application Domain之后才能运行。
我简单画了一张图,不过这个图并不科学,因为进程Process和线程Thread(图中未画出)是操作系统的概念,Application Domain是CLR运行时的概念,大类上说以上三个概念都是动态的。与之对应,程序集Assembly和模块Module则是编译器的概念,是静态的。不过暂时用这个图也可以凑合,大概有助于把关系梳理清楚。

对于NET Framework应用程序,一个CLR进程中最少有三个Application Domain,分别是System Domain, Shared Domain和Default Domain,其结构如《Advanced .NET debugging》一书中的插图Figure 2-4所示:

不过现代.NET(.NET 5.0及以上)应用中没有了Shared Domain了,只留下了System Domain和Default Domain(名字是Domain 1),当然.NET应用程序还可以继续创建Domain,所以我的图中又画出了一个Domain2。
每个Domain下面可以有1 ~ n个Assembly,而每个Assembly又包括1 ~ m个Module。
有了这种对应关系,在WinDbg中,查找各个构建(Component)就有了基础。
查询举例
将如下列表所示的C#程序Core.exe加载到WinDbg,输入g命令运行,程序会被Debugger.Break()断下。
//file: Core.dll
using System.Diagnostics;
namespace BasicGrammar;
class Program
{
static void Main()
{
Person person = new Person();
person.name = "Robert";
person.age = 20;
Debugger.Break();
}
public class Person
{
public string? name;
public int age;
public int GetAge()
{ return age; }
}
}
输入sos的扩展命令!dumpdomain,可以得到当前进程中所有Domain信息:
0:000> !dumpdomain
--------------------------------------
System Domain: 00007ffbb5ac40c0
LowFrequencyHeap: 00007FFBB5AC4598
HighFrequencyHeap: 00007FFBB5AC4628
StubHeap: 00007FFBB5AC46B8
Stage: OPEN
Name: None
--------------------------------------
Domain 1: 00000000004ee8d0
LowFrequencyHeap: 00007FFBB5AC4598
HighFrequencyHeap: 00007FFBB5AC4628
StubHeap: 00007FFBB5AC46B8
Stage: OPEN
Name: clrhost
Assembly: 00000000004fed30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Private.CoreLib.dll]
ClassLoader: 00000000004FEDC0
Module
00007ffb55ab4000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Private.CoreLib.dll
Assembly: 00000000004acb60 [E:\test\a\Core\bin\Debug\net8.0\Core.dll]
ClassLoader: 00000000004AFAD0
Module
00007ffb55c9e0a0 E:\test\a\Core\bin\Debug\net8.0\Core.dll
Assembly: 00000000004b2790 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Runtime.dll]
ClassLoader: 00000000004AF260
Module
00007ffb55c9fbb8 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.3\System.Runtime.dll
可以看到当前进程包括了一个System Domain和一个Domain 1,而Domain 1种又包括了System.Private.CoreLib.dll,Core.dll和System.Runtime.dll三个Assembly,而且每个Assembly只包含了一个module。
显然,Core.dll就是我们自己写的C#代码生成的程序集。
可以通过!dumpmodule -mt 00007ffb55c9e0a0得到Core.dll中的定义的类型(TypeDef)和引用的外部类型(TypeRef):
0:000> !dumpmodule -mt 00007ffb55c9e0a0
Name: E:\test\a\Core\bin\Debug\net8.0\Core.dll
Attributes: PEFile
TransientFlags: 00209011
Assembly: 00000000004acb60
BaseAddress: 0000000002330000
PEAssembly: 00000000004AED90
ModuleId: 00007FFB55C9E458
ModuleIndex: 0000000000000001
LoaderHeap: 00007FFBB5AC4588
TypeDefToMethodTableMap: 00007FFB55CA4320
TypeRefToMethodTableMap: 00007FFB55CA4340
MethodDefToDescMap: 00007FFB55CA4438
FieldDefToDescMap: 00007FFB55CA4460
MemberRefToDescMap: 00007FFB55CA43C0
FileReferencesMap: 0000000000000000
AssemblyReferencesMap: 00007FFB55CA4480
MetaData start address: 00000000023320AC (1416 bytes)
Types defined in this module
MT TypeDef Name
------------------------------------------------------------------------------
00007ffb55cc00e8 0x02000002 BasicGrammar.Program
00007ffb55cc93f8 0x02000003 BasicGrammar.Program+Person
Types referenced in this module
MT TypeRef Name
------------------------------------------------------------------------------
00007ffb55b45fa8 0x0200000d System.Object
00007ffb55cc9608 0x0200000f System.Diagnostics.Debugger
假设我们还想查Person类中有哪些方法,则可以继续使用如下命令:
0:000> !dumpmt -md 00007ffb55cc93f8
EEClass: 00007ffb55cd1f18
Module: 00007ffb55c9e0a0
Name: BasicGrammar.Program+Person
mdToken: 0000000002000003
File: E:\test\a\Core\bin\Debug\net8.0\Core.dll
AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet.
BaseSize: 0x20
ComponentSize: 0x0
DynamicStatics: false
ContainsPointers: true
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
00007FFB55B50048 00007ffb55b45f38 NONE System.Object.Finalize()
00007FFB55B50060 00007ffb55b45f48 NONE System.Object.ToString()
00007FFB55B50078 00007ffb55b45f58 NONE System.Object.Equals(System.Object)
00007FFB55B500C0 00007ffb55b45f98 NONE System.Object.GetHashCode()
00007FFB55CBB948 00007ffb55cc93d0 JIT BasicGrammar.Program+Person..ctor()
00007FFB55CBB930 00007ffb55cc93b8 NONE BasicGrammar.Program+Person.GetAge()
从上面列表可见,所有从父类继承过来的方法及Person类自己定义的方法GetAge都在(.ctor是默认构造方法)
如果想查Person类有哪些字段,则可以使用如下命令:
0:000> !DumpClass 00007ffb55cd1f18
Class Name: BasicGrammar.Program+Person
mdToken: 0000000002000003
File: E:\test\a\Core\bin\Debug\net8.0\Core.dll
Parent Class: 00007ffb55b3f5b0
Module: 00007ffb55c9e0a0
Method Table: 00007ffb55cc93f8
Vtable Slots: 4
Total Method Slots: 5
Class Attributes: 100002
NumInstanceFields: 2
NumStaticFields: 0
MT Field Offset Type VT Attr Value Name
00007ffb55bfec08 4000001 8 System.String 0 instance name
00007ffb55b81188 4000002 10 System.Int32 1 instance age
0:000> !DumpClass 00007ffb55cd1f18
Class Name: BasicGrammar.Program+Person
mdToken: 0000000002000003
File: E:\test\a\Core\bin\Debug\net8.0\Core.dll
Parent Class: 00007ffb55b3f5b0
Module: 00007ffb55c9e0a0
Method Table: 00007ffb55cc93f8
Vtable Slots: 4
Total Method Slots: 5
Class Attributes: 100002
NumInstanceFields: 2
NumStaticFields: 0
MT Field Offset Type VT Attr Value Name
00007ffb55bfec08 4000001 8 System.String 0 instance name
00007ffb55b81188 4000002 10 System.Int32 1 instance age
看最下面两行,字段name和age都有了。
总结
知道了层次关系,我们拿到一个应用程序时,就可以按自顶向下的方法,逐层逼近,查得该程序的各项信息:
- !dumpdomain查到assembly和module
- !dumpmodule -mt ModuleAddress查到MethodTable
- !dumpmt -md MethodTable查到方法列表和EEClass
- !DumpClass EEClass查到字段

4万+

被折叠的 条评论
为什么被折叠?



