7. WinDbg观察托管程序架构

总目录

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所示:

App Domain
不过现代.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都有了。

总结

知道了层次关系,我们拿到一个应用程序时,就可以按自顶向下的方法,逐层逼近,查得该程序的各项信息:

  1. !dumpdomain查到assembly和module
  2. !dumpmodule -mt ModuleAddress查到MethodTable
  3. !dumpmt -md MethodTable查到方法列表和EEClass
  4. !DumpClass EEClass查到字段
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值