8. 托管PE文件结构

总目录

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托管程序的体系架构。

本文尝试一步一动,探究如下秘密:

  • C#程序如何被编译成MSIL中间语言
  • MSIL在可执行映像(.exe/.dll)中的保存格式
  • 可执行映像被装载进内存后的映像
  • CLR的运行时参与
  • CLR/Jitter如何将MSIL即时编译成本机代码

.NET模型

示例代码

示例代码很简单:用C#语言,.NET Framework 4.8框架,控制台应用,Debug | AnyCpu模式编译。

using System;
using System.Diagnostics;

namespace Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Debugger.Break();
            Person person = new Person();
            person.x = 100;
            Console.WriteLine(person.GetX());
            Debugger.Break();
        }
    }

    public class Person
    {
        public int x;
        public string y;
        public int GetX() { return x; }
    }
}

用ILSpy观察MSIL

C#被编译成exe文件以后,可以使用ILSpy.exe工具查看生成的MSIL代码:
打开ILSpy工具
文件 -> 打开 ->找到刚编译生成的Test.exe加载之
展开左侧的目录树,可以浏览查看MSIL,比如下图展示的是Test名称空间下Person类中GetX方法,以IL with C#模式展现在右侧主窗口中:
MSIL - GetX
我在之前的文章中曾通过举例详细分析过MSIL,但本文的视角相对更宏观,所以这里我们只看大的结构。

首先请注意:右上角红色方框中以注释方式说明GetX代码的起始地址是0x2090,但实际第一条MSIL开始于红色箭头指向的内存地址 0x0000209C,中间差了12个字节。请先记住这个疑问,后面再进行说明。

该地址保存着十六进制数据00,对应着MSIL语言的指令是空操作nop。也就是说,上图中主窗口左侧以0x开头的8位十六进制数据是内存地址偏移量RVA,中间实线红色框中是对应地址下的十六进制数据,也就是MSIL指令码;右侧红色虚线框中是指令码对应的MSIL助记符。

为什么nop指令对应的MSIL编码是00?很简单,微软规定的!查这里就可以看到:
nop
为了下一步研究,让我们将所有指令码按顺序找出来,以字节格式排列如下:

MSIL指令码: 00 02 7B 01 00 00 04 0A 2B 00 06 2A

也就是说,C#的一条语句return x,被csc编译器编译以后,生成了对应的MSIL指令码:00 02 7B 01 00 00 04 0A 2B 00 06 2A。

至此,我们通过示例,简单说明了C#代码和MSIL代码的对应关系。接下来我们到Test.exe文件中去查找这段MSIL指令码。

用十六进制编辑器查看exe文件

刚刚我们用ILSpy查看Test.exe文件,知道在其中一定是保存了GetX的MSIL代码。下面我们使用十六进制编辑器查找一下这段代码到底存储在什么位置:本文使用一款称为010Editor的软件:

用010Editor打开Test.exe,然后点击搜索按钮,选择搜索十六进制字节,并在搜索框中粘贴MSIL指令码:00 02 7B 01 00 00 04 0A 2B 00 06 2A,如下图所示:

十六进制查MSIL指令码
提醒:请留意这个截图显示的是文件最开始部分,也就是从文件偏移0000h开始的截图。后面介绍文件装载到内存时,我们会和此图对应。

回车后界面如下图。从图中知道,010Editor为我们找到了结果,位于029Ch地址处(最下面的红色方框所示)。点击中间椭圆框中的结果行,顶部主窗口会自动高亮选中对应的数据,合计12个字节,地址范围确实是029C ~ 02A7。

MSIL代码

至此,我们起码清楚了一件事:csc将C#方法编译成了MSIL,而MSIL在exe文件中是以MSIL指令码格式存在的,并且被保存到了文件偏移029Ch开始的12字节地址中。

那么,Test.exe中是否保存了我们的类Person?是否保存了方法名GetX?是否保存了Console.WriteLine方法名?我们同样可以通过010Editor的搜索功能查找,下面截图显示exe文件中确实有GetX和Console.WriteLine:

在这里插入图片描述
下面再查一下Person类:
在这里插入图片描述
也就是说,在exe文件中不仅保存了MSIL,还保存了类名、方法名。其实,exe文件中还保存了GetX属于哪个类,该类属于哪个程序集,以及每个类有哪些字段,是否有虚方法或静态方法等很多信息,只不过为了压缩文件尺寸,exe文件并不是按我们平时理解的逻辑顺序存放这些信息的,而是按照便于计算机查找和使用的方式进行的组织,从上面几个截图就可以知道,很多信息是散落到文件不同角落的,整个文件体系是用哪些链条把各种信息串起来的呢?

这个话题其实非常大,因为exe文件中有丰富的复杂的各种数据结构来支撑,首先该文件必须符合Windows可执行程序标准结构,也就是PE/COFF文件格式标准,因为只有如此才能在双击该文件时被操作系统加载到内存并执行;其次,虽然同样是exe文件,但托管平台下的exe文件并不包含CPU可执行的指令,而仅仅是中间代码MSIL,实际运行的时候必须额外加载CLR,来对这些MSIL进行即时编译,才能最后被CPU执行。所以托管exe文件一定是对传统本机exe文件做了扩展。

最后,与传统本机代码exe文件结构不同的是,托管exe文件中不仅包括了代码和数据,还包括了数据类型,也就是数据的数据,微软称之为metadata,因此CLR才能对此文件做出解释、安全管理及内存管理。

如果对托管映像文件详细结构感兴趣,建议查看ECMA335技术规范和微软开源.NET Runtime源代码。当然本文后面会通过举例简单介绍一部分数据结构。

托管exe的加载

生成exe文件只是万里长征第一步,因为程序是被用来运行的。接下来我们说说操作系统是如何将Test.exe加载到计算机内存中的。 我们使用WinDbg来做这部分的研究。

初始加载

打开WinDbg,加载可执行文件Test.exe。此过程中,WinDbg并不会等待所有必要的模块均加载且初始化完成以后才中断,而会努力在加载前期就将调试器断下,以便用户可以进一步观察加载过程。

WinDbg在这段启动过程中的屏幕完整输出如下:


************* Preparing the environment for Debugger Extensions Gallery repositories **************
   ExtensionRepository : Implicit
   UseExperimentalFeatureForNugetShare : false
   AllowNugetExeUpdate : false
   NonInteractiveNuget : true
   AllowNugetMSCredentialProviderInstall : false
   AllowParallelInitializationOfLocalRepositories : true

   EnableRedirectToV8JsProvider : false

   -- Configuring repositories
      ----> Repository : LocalInstalled, Enabled: true
      ----> Repository : UserExtensions, Enabled: true

>>>>>>>>>>>>> Preparing the environment for Debugger Extensions Gallery repositories completed, duration 0.000 seconds

************* Waiting for Debugger Extensions Gallery to Initialize **************

>>>>>>>>>>>>> Waiting for Debugger Extensions Gallery to Initialize completed, duration 0.312 seconds
   ----> Repository : UserExtensions, Enabled: true, Packages count: 0
   ----> Repository : LocalInstalled, Enabled: true, Packages count: 41

Microsoft (R) Windows Debugger Version 10.0.27553.1004 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: E:\Add\C2_1\Test\bin\Debug\Test.exe

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       srv*
Deferred                                       srv*c:\Symbols*http://msdl.microsoft.com/download/symbols
OK                                             E:\test\a\Core\bin\Debug\net8.0
Symbol search path is: srv*;srv*c:\Symbols*http://msdl.microsoft.com/download/symbols;E:\test\a\Core\bin\Debug\net8.0
Executable search path is: 
ModLoad: 00400000 00408000   Test.exe
ModLoad: 771a0000 77352000   ntdll.dll
ModLoad: 74da0000 74df5000   C:\Windows\SysWOW64\MSCOREE.DLL
ModLoad: 75b70000 75c60000   C:\Windows\SysWOW64\KERNEL32.dll
ModLoad: 758f0000 75b69000   C:\Windows\SysWOW64\KERNELBASE.dll
(598.1d2c): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=ddce0000 edx=00000000 esi=005d2b10 edi=003a2000
eip=772588b7 esp=0019f7f4 ebp=0019f820 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2b:
772588b7 cc              int     3

其中,请重点关注以下信息:

ModLoad: 00400000 00408000 Test.exe
ModLoad: 771a0000 77352000 ntdll.dll
ModLoad: 74da0000 74df5000 C:\Windows\SysWOW64\MSCOREE.DLL
ModLoad: 75b70000 75c60000 C:\Windows\SysWOW64\KERNEL32.dll
ModLoad: 758f0000 75b69000 C:\Windows\SysWOW64\KERNELBASE.dll

ModLoad实际就是Module Load,也就是加载的模块。

操作系统首先加载Test.exe文件,然后加载dtdll.dll文件(无论是运行托管exe还是传统本机代码exe,ntdll.dll都必须被加载进来,因为它是用户程序和操作系统内核代码进行信息交互的中间使者或联络员)。

然后,操作系统通过“阅读”该exe文件内容,知道这不是一个本机exe,而是一个托管代码,于是知道需要加载MSCOREE.DLL,因为托管代码的初始化需要通过调用MSCOREE.DLL中的_CorExeMain来切入程序入口方法。至于KERNEL32.DLL,是因为一个exe文件被加载以后,首先需要创建进程空间和初始化,然后创建主线程,主线程初始化就会用到KERNEL32!BaseThreadInitThunk;而KernelBase则是因为很多BCL函数都会用到,比如我们的Debugger.Break()方法。

映像文件的内存布局

因为上一步断点还太早,我们首先使用g命令继续执行一下加载进程,让程序自动断在第一句Debugger.Break()语句处。此时,初始化过程均已完毕。

此时,让我们看看Test.exe文件到底被操作系统加载到了哪里。根据下面这句输出,我们知道Test.exe是被加载到了 00400000 ~ 00408000地址空间范围内。

ModLoad: 00400000 00408000 Test.exe
此时,我们用 !address扩展命令查一下00400000 ~ 00408000的内存设置:

00400000 ~ 00408000

    0:000> !address

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
略......  
    3bf000   400000    41000 MEM_PRIVATE MEM_RESERVE                                    <unknown>  
+   400000   401000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
    401000   402000     1000 MEM_IMAGE   MEM_RESERVE                                    Image      [Test; "Test.exe"]
    402000   403000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
    403000   404000     1000 MEM_IMAGE   MEM_RESERVE                                    Image      [Test; "Test.exe"]
    404000   405000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
    405000   406000     1000 MEM_IMAGE   MEM_RESERVE                                    Image      [Test; "Test.exe"]
    406000   407000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
    407000   408000     1000 MEM_IMAGE   MEM_RESERVE                                    Image      [Test; "Test.exe"]
+   408000   410000     8000             MEM_FREE    PAGE_NOACCESS                      Free       
略......

下面我们将这段内存的内容和我们刚才用010Editor观察的Test.exe文件内容做一下对比:

0:000> db 400000 L1000
00400000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
00400010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
00400020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00400030  00 00 00 00 00 00 00 00-00 00 00 00 80 00 00 00  ................
00400040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
00400050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
00400060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS 
00400070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$.......
00400080  50 45 00 00 4c 01 03 00-85 ef a8 8f 00 00 00 00  PE..L...........
00400090  00 00 00 00 e0 00 22 00-0b 01 30 00 00 08 00 00  ......"...0.....
004000a0  00 08 00 00 00 00 00 00-a2 27 00 00 00 20 00 00  .........'... ..
004000b0  00 40 00 00 00 00 40 00-00 20 00 00 00 02 00 00  .@....@.. ......
004000c0  04 00 00 00 00 00 00 00-06 00 00 00 00 00 00 00  ................
004000d0  00 80 00 00 00 02 00 00-00 00 00 00 03 00 60 85  ..............`.
004000e0  00 00 10 00 00 10 00 00-00 00 10 00 00 10 00 00  ................
004000f0  00 00 00 00 10 00 00 00-00 00 00 00 00 00 00 00  ................
00400100  50 27 00 00 4f 00 00 00-00 40 00 00 8c 05 00 00  P'..O....@......
00400110  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00400120  00 60 00 00 0c 00 00 00-dc 26 00 00 38 00 00 00  .`.......&..8...
00400130  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00400140  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00400150  00 00 00 00 00 00 00 00-00 20 00 00 08 00 00 00  ......... ......
00400160  00 00 00 00 00 00 00 00-08 20 00 00 48 00 00 00  ......... ..H...
00400170  00 00 00 00 00 00 00 00-2e 74 65 78 74 00 00 00  .........text...
00400180  a8 07 00 00 00 20 00 00-00 08 00 00 00 02 00 00  ..... ..........
00400190  00 00 00 00 00 00 00 00-00 00 00 00 20 00 00 60  ............ ..`
004001a0  2e 72 73 72 63 00 00 00-8c 05 00 00 00 40 00 00  .rsrc........@..
004001b0  00 06 00 00 00 0a 00 00-00 00 00 00 00 00 00 00  ................
004001c0  00 00 00 00 40 00 00 40-2e 72 65 6c 6f 63 00 00  ....@..@.reloc..
004001d0  0c 00 00 00 00 60 00 00-00 02 00 00 00 10 00 00  .....`..........
004001e0  00 00 00 00 00 00 00 00-00 00 00 00 40 00 00 42  ............@..B
004001f0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
这以后全是0,故略......

这个列表是不是和本文第一张010Editor截图的布局完全一样?也就是说:

Test.exe文件000 ~ 1FF的内容被映射到了实际内存400000 ~ 4001FF

接下来继续查402000开始的区域:

0:000> db 402000 L1000
00402000  84 27 00 00 00 00 00 00-48 00 00 00 02 00 05 00  .'......H.......
00402010  b4 20 00 00 28 06 00 00-03 00 02 00 01 00 00 06  . ..(...........
00402020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00402030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00402040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00402050  13 30 02 00 28 00 00 00-01 00 00 11 00 28 0f 00  .0..(........(..
00402060  00 0a 00 73 04 00 00 06-0a 06 1f 64 7d 01 00 00  ...s.......d}...
00402070  04 06 6f 03 00 00 06 28-10 00 00 0a 00 28 0f 00  ..o....(.....(..
00402080  00 0a 00 2a 22 02 28 11-00 00 0a 00 2a 00 00 00  ...*".(.....*...
00402090  13 30 01 00 0c 00 00 00-02 00 00 11 00 02 7b 01  .0............{.
004020a0  00 00 04 0a 2b 00 06 2a-22 02 28 11 00 00 0a 00  ....+..*".(.....
004020b0  2a 00 00 00 42 53 4a 42-01 00 01 00 00 00 00 00  *...BSJB........
004020c0  0c 00 00 00 76 34 2e 30-2e 33 30 33 31 39 00 00  ....v4.0.30319..
004020d0  00 00 05 00 6c 00 00 00-18 02 00 00 23 7e 00 00  ....l.......#~..
004020e0  84 02 00 00 6c 02 00 00-23 53 74 72 69 6e 67 73  ....l...#Strings
004020f0  00 00 00 00 f0 04 00 00-04 00 00 00 23 55 53 00  ............#US.
00402100  f4 04 00 00 10 00 00 00-23 47 55 49 44 00 00 00  ........#GUID...
00402110  04 05 00 00 24 01 00 00-23 42 6c 6f 62 00 00 00  ....$...#Blob...
00402120  00 00 00 00 02 00 00 01-57 15 02 00 09 00 00 00  ........W.......
00402130  00 fa 01 33 00 16 00 00-01 00 00 00 12 00 00 00  ...3............
00402140  03 00 00 00 02 00 00 00-04 00 00 00 01 00 00 00  ................
00402150  11 00 00 00 0e 00 00 00-02 00 00 00 01 00 00 00  ................
00402160  01 00 00 00 00 00 8f 01-01 00 00 00 00 00 06 00  ................
00402170  04 01 26 02 06 00 71 01-26 02 06 00 38 00 f4 01  ..&...q.&...8...
00402180  0f 00 46 02 00 00 06 00-60 00 cc 01 06 00 e7 00  ..F.....`.......
00402190  cc 01 06 00 c8 00 cc 01-06 00 58 01 cc 01 06 00  ..........X.....
004021a0  24 01 cc 01 06 00 3d 01-cc 01 06 00 77 00 cc 01  $.....=.....w...
004021b0  06 00 4c 00 07 02 06 00-2a 00 07 02 06 00 ab 00  ..L.....*.......
004021c0  cc 01 06 00 92 00 98 01-06 00 5a 02 c0 01 06 00  ..........Z.....
004021d0  e5 01 f4 01 06 00 18 00-c0 01 00 00 00 00 01 00  ................
004021e0  00 00 00 00 01 00 01 00-00 00 10 00 b8 01 61 02  ..............a.
004021f0  41 00 01 00 01 00 01 00-10 00 de 01 61 02 41 00  A...........a.A.
00402200  01 00 03 00 06 00 66 02-35 00 06 00 68 02 38 00  ......f.5...h.8.
00402210  50 20 00 00 00 00 91 00-c7 01 3b 00 01 00 84 20  P ........;.... 
00402220  00 00 00 00 86 18 ee 01-06 00 02 00 90 20 00 00  ............. ..
00402230  00 00 86 00 0a 00 41 00-02 00 a8 20 00 00 00 00  ......A.... ....
00402240  86 18 ee 01 06 00 02 00-00 00 01 00 55 02 09 00  ............U...
00402250  ee 01 01 00 11 00 ee 01-06 00 19 00 ee 01 0a 00  ................
00402260  29 00 ee 01 10 00 31 00-ee 01 10 00 39 00 ee 01  ).....1.....9...
00402270  10 00 41 00 ee 01 10 00-49 00 ee 01 10 00 51 00  ..A.....I.....Q.
00402280  ee 01 10 00 59 00 ee 01-10 00 61 00 ee 01 15 00  ....Y.....a.....
00402290  69 00 ee 01 10 00 71 00-ee 01 10 00 79 00 ee 01  i.....q.....y...
004022a0  10 00 89 00 b2 01 1f 00-91 00 20 00 23 00 81 00  .......... .#...
004022b0  ee 01 06 00 2e 00 0b 00-45 00 2e 00 13 00 4e 00  ........E.....N.
004022c0  2e 00 1b 00 6d 00 2e 00-23 00 76 00 2e 00 2b 00  ....m...#.v...+.
004022d0  80 00 2e 00 33 00 80 00-2e 00 3b 00 80 00 2e 00  ....3.....;.....
004022e0  43 00 76 00 2e 00 4b 00-86 00 2e 00 53 00 80 00  C.v...K.....S...
004022f0  2e 00 5b 00 80 00 2e 00-63 00 9e 00 2e 00 6b 00  ..[.....c.....k.
00402300  c8 00 2e 00 73 00 d5 00-1a 00 28 00 04 80 00 00  ....s.....(.....
00402310  01 00 00 00 00 00 00 00-00 00 00 00 00 00 61 02  ..............a.
00402320  00 00 04 00 00 00 00 00-00 00 00 00 00 00 2c 00  ..............,.
00402330  0f 00 00 00 00 00 00 00-00 3c 4d 6f 64 75 6c 65  .........<Module
00402340  3e 00 47 65 74 58 00 6d-73 63 6f 72 6c 69 62 00  >.GetX.mscorlib.
00402350  43 6f 6e 73 6f 6c 65 00-57 72 69 74 65 4c 69 6e  Console.WriteLin
00402360  65 00 47 75 69 64 41 74-74 72 69 62 75 74 65 00  e.GuidAttribute.
00402370  44 65 62 75 67 67 61 62-6c 65 41 74 74 72 69 62  DebuggableAttrib
00402380  75 74 65 00 43 6f 6d 56-69 73 69 62 6c 65 41 74  ute.ComVisibleAt
00402390  74 72 69 62 75 74 65 00-41 73 73 65 6d 62 6c 79  tribute.Assembly
004023a0  54 69 74 6c 65 41 74 74-72 69 62 75 74 65 00 41  TitleAttribute.A
004023b0  73 73 65 6d 62 6c 79 54-72 61 64 65 6d 61 72 6b  ssemblyTrademark
004023c0  41 74 74 72 69 62 75 74-65 00 54 61 72 67 65 74  Attribute.Target
004023d0  46 72 61 6d 65 77 6f 72-6b 41 74 74 72 69 62 75  FrameworkAttribu
004023e0  74 65 00 41 73 73 65 6d-62 6c 79 46 69 6c 65 56  te.AssemblyFileV
004023f0  65 72 73 69 6f 6e 41 74-74 72 69 62 75 74 65 00  ersionAttribute.
00402400  41 73 73 65 6d 62 6c 79-43 6f 6e 66 69 67 75 72  AssemblyConfigur
00402410  61 74 69 6f 6e 41 74 74-72 69 62 75 74 65 00 41  ationAttribute.A
00402420  73 73 65 6d 62 6c 79 44-65 73 63 72 69 70 74 69  ssemblyDescripti
00402430  6f 6e 41 74 74 72 69 62-75 74 65 00 43 6f 6d 70  onAttribute.Comp
00402440  69 6c 61 74 69 6f 6e 52-65 6c 61 78 61 74 69 6f  ilationRelaxatio
00402450  6e 73 41 74 74 72 69 62-75 74 65 00 41 73 73 65  nsAttribute.Asse
00402460  6d 62 6c 79 50 72 6f 64-75 63 74 41 74 74 72 69  mblyProductAttri
00402470  62 75 74 65 00 41 73 73-65 6d 62 6c 79 43 6f 70  bute.AssemblyCop
00402480  79 72 69 67 68 74 41 74-74 72 69 62 75 74 65 00  yrightAttribute.
00402490  41 73 73 65 6d 62 6c 79-43 6f 6d 70 61 6e 79 41  AssemblyCompanyA
004024a0  74 74 72 69 62 75 74 65-00 52 75 6e 74 69 6d 65  ttribute.Runtime
004024b0  43 6f 6d 70 61 74 69 62-69 6c 69 74 79 41 74 74  CompatibilityAtt
004024c0  72 69 62 75 74 65 00 54-65 73 74 2e 65 78 65 00  ribute.Test.exe.
004024d0  53 79 73 74 65 6d 2e 52-75 6e 74 69 6d 65 2e 56  System.Runtime.V
004024e0  65 72 73 69 6f 6e 69 6e-67 00 42 72 65 61 6b 00  ersioning.Break.
004024f0  50 72 6f 67 72 61 6d 00-53 79 73 74 65 6d 00 4d  Program.System.M
00402500  61 69 6e 00 53 79 73 74-65 6d 2e 52 65 66 6c 65  ain.System.Refle
00402510  63 74 69 6f 6e 00 50 65-72 73 6f 6e 00 44 65 62  ction.Person.Deb
00402520  75 67 67 65 72 00 2e 63-74 6f 72 00 53 79 73 74  ugger..ctor.Syst
00402530  65 6d 2e 44 69 61 67 6e-6f 73 74 69 63 73 00 53  em.Diagnostics.S
00402540  79 73 74 65 6d 2e 52 75-6e 74 69 6d 65 2e 49 6e  ystem.Runtime.In
00402550  74 65 72 6f 70 53 65 72-76 69 63 65 73 00 53 79  teropServices.Sy
00402560  73 74 65 6d 2e 52 75 6e-74 69 6d 65 2e 43 6f 6d  stem.Runtime.Com
00402570  70 69 6c 65 72 53 65 72-76 69 63 65 73 00 44 65  pilerServices.De
00402580  62 75 67 67 69 6e 67 4d-6f 64 65 73 00 61 72 67  buggingModes.arg
00402590  73 00 4f 62 6a 65 63 74-00 54 65 73 74 00 78 00  s.Object.Test.x.
004025a0  79 00 00 00 00 00 00 00-8c 5e 75 b0 db e4 36 45  y........^u...6E
004025b0  b0 f9 1b 5d e9 be 36 a0-00 04 20 01 01 08 03 20  ...]..6... .... 
004025c0  00 01 05 20 01 01 11 11-04 20 01 01 0e 04 20 01  ... ..... .... .
004025d0  01 02 04 07 01 12 0c 03-00 00 01 04 00 01 01 08  ................
004025e0  03 07 01 08 08 b7 7a 5c-56 19 34 e0 89 02 06 08  ......z\V.4.....
004025f0  02 06 0e 05 00 01 01 1d-0e 03 20 00 08 08 01 00  .......... .....
00402600  08 00 00 00 00 00 1e 01-00 01 00 54 02 16 57 72  ...........T..Wr
00402610  61 70 4e 6f 6e 45 78 63-65 70 74 69 6f 6e 54 68  apNonExceptionTh
00402620  72 6f 77 73 01 08 01 00-07 01 00 00 00 00 09 01  rows............
00402630  00 04 54 65 73 74 00 00-05 01 00 00 00 00 17 01  ..Test..........
00402640  00 12 43 6f 70 79 72 69-67 68 74 20 c2 a9 20 20  ..Copyright ..  
00402650  32 30 32 34 00 00 29 01-00 24 63 37 37 35 32 32  2024..)..$c77522
00402660  65 64 2d 66 64 66 35 2d-34 66 39 39 2d 38 61 64  ed-fdf5-4f99-8ad
00402670  38 2d 62 34 66 38 61 30-61 36 66 61 61 38 00 00  8-b4f8a0a6faa8..
00402680  0c 01 00 07 31 2e 30 2e-30 2e 30 00 00 4d 01 00  ....1.0.0.0..M..
00402690  1c 2e 4e 45 54 46 72 61-6d 65 77 6f 72 6b 2c 56  ..NETFramework,V
004026a0  65 72 73 69 6f 6e 3d 76-34 2e 38 2e 31 01 00 54  ersion=v4.8.1..T
004026b0  0e 14 46 72 61 6d 65 77-6f 72 6b 44 69 73 70 6c  ..FrameworkDispl
004026c0  61 79 4e 61 6d 65 14 2e-4e 45 54 20 46 72 61 6d  ayName..NET Fram
004026d0  65 77 6f 72 6b 20 34 2e-38 2e 31 00 00 00 00 00  ework 4.8.1.....
004026e0  b8 33 e5 ef 00 00 00 00-02 00 00 00 3c 00 00 00  .3..........<...
004026f0  14 27 00 00 14 09 00 00-00 00 00 00 00 00 00 00  .'..............
00402700  00 00 00 00 10 00 00 00-00 00 00 00 00 00 00 00  ................
00402710  00 00 00 00 52 53 44 53-a3 0c 5d 72 82 e3 c5 42  ....RSDS..]r...B
00402720  88 3e 4f 80 4b 93 13 d2-01 00 00 00 45 3a 5c 41  .>O.K.......E:\A
00402730  64 64 5c 43 32 5f 31 5c-54 65 73 74 5c 6f 62 6a  dd\C2_1\Test\obj
00402740  5c 44 65 62 75 67 5c 54-65 73 74 2e 70 64 62 00  \Debug\Test.pdb.
00402750  78 27 00 00 00 00 00 00-00 00 00 00 92 27 00 00  x'...........'..
00402760  00 20 00 00 00 00 00 00-00 00 00 00 00 00 00 00  . ..............
00402770  00 00 00 00 00 00 00 00-84 27 00 00 00 00 00 00  .........'......
00402780  00 00 00 00 00 00 5f 43-6f 72 45 78 65 4d 61 69  ......_CorExeMai
00402790  6e 00 6d 73 63 6f 72 65-65 2e 64 6c 6c 00 00 00  n.mscoree.dll...
004027a0  00 00 ff 25 00 20 40 00-00 00 00 00 00 00 00 00  ...%. @.........
004027b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
这以后全是0,故略......

这个列表的内容是不是和下面的截图中Test.exe的200偏移开始区域一致?也就是说:

Test.exe文件200 ~ 9FF的内容被映射到了实际内存402000 ~ 4007FF

在这里插入图片描述
如果继续查,可以得到以下结论:

Test.exe文件A00 ~ FFF的内容被映射到了实际内存404000 ~ 4045FF
Test.exe文件1000 ~ 11FF的内容被映射到了实际内存406000 ~ 4061FF

也就是说,一个完整的Test.exe被操作系统加载到内存时,被分成了四份,为什么会这样?为了说明这个问题,我们还需要稍稍再多介绍一下exe文件结构。

再说托管映像

在这里插入图片描述
上面这张图中,红色框中 000 ~ 1FF总计512字节是exe文件头,该文件头主要包括四个部分:

  • Dos Header:000 ~ 03F,可以认为除了开始两个签名字节MZ必须有以外,其余都是DOS年代的产物,已经无用了;
  • Dos Stub:040 ~07F,这是一段DOS代码,如果在DOS下运行这个程序,则屏幕会显示:This program cannot be run in DOS mode. 也是无用部分;
  • NT Header:080 ~ 177, PE文件的关键数据结构之一,稍后会进一步介绍;
  • Section Header:178 ~ 1EF,称为“节标头”,主要描述了文件的分节信息,稍后介绍;

以上四个部分构成了整个PE文件格式的文件头,一般都是512字节(000 ~ 1FF)。下面结合实例说说的具体含义:

节,英文是section,是PE文件中一个专有概念,就像我们汇编语言程序的数据段、代码段类似,PE文件也是将不同数据组织在不同的节中。请注意上图中086h开始的Word,是0003,这个数据在PE文件格式定义中是Number of sections,也就是说,本exe文件总计包含3个节。

那么,这三个节都是什么名字?起始地址和长度分别是多少?这就是178 ~ 1EF对应的Section Header的任务。每个Section Header占40个字节,所以3个Section Header占120个字节,也就是178 ~ 1EF。

下面以第一个节 178 ~ 19F为例进行说明:
1)8个字节的节名称(178 ~ 17F),这里是 .text。.text节一般保存的是代码,MSIL一般就保存在.text节中。
2)4字节DWord(180 ~ 183),实际字节数,这里是000007A8,也就是说,.text节真正有效数据是07A8个字节。
3)4字节DWord(184 ~ 187),加载到内存时的起始偏移地址RVA,这里是00002000。它的意思是说:如果将Test.exe装载到起始地址为00000000的地址,那么.text节应该从00002000开始装载;如果操作系统将Test.exe装载到00400000开始的地址,那么.text节应该从00402000开始,这就是相对偏移地址(Relative Virtual Address: RVA)的含义。
4) 4字节DWord(188 ~ 18B),加上补0对齐字节以后,该节在exe文件中总计占多少字节。这里是00000800字节
5) 4字节DWord(18C ~ 18F)该.text节在本文件的起始地址偏移:00000200。
之后还有一个DWord : Pointer of relocations, 一个DWord:Pointer to LineNumbers,一个Word:Number of relocations, 一个Word:Number of LineNumbers。这四个部分仅用于非托管程序,我们无需考虑。

有了以上分析,我们知道Test.exe总计包含三个节,其中第一个节的名字是 .text,它在Test.exe文件中是以 0200偏移地址开始,总长度0800字节,应该被加载到内存相对地址RVA + 2000处。

有了以上信息,加上我们知道这次调试时Test.exe是被加载到400000开始的地址的,所以才有:

Test.exe文件200 ~ 9FF的内容被映射到了实际内存402000 ~ 4007FF

与此类似,下一个节是.rsrc节,也就是rescource节,在我们这个Test.exe文件中,它主要保存了程序版本号、文字编码格式、版权信息等。.rsrc节在文件中起始偏移地址是0A00,总计0600字节,将被加载到RVA 4000开始的偏移地址中。

所以才有了:
Test.exe文件A00 ~ FFF的内容被映射到了实际内存404000 ~ 4045FF

第三个节在文件中起始于001000h起始地址,被加载进内存时,需要加载到006000RVA地址,总长度0200字节。

映像文件构成

通过以上分析,我们知道了如下信息:

PE映像文件实际上是由文件头加上节构成的。针对我们的示例程序Test.exe,它的文件头又由四个子文件头构成,合计512(0x200)字节。紧跟文件头之后,分别是.text节占2048(0x800)字节; .rsrc节合计1536(0x600)字节; .reloc节合计512(0x200)字节。以上四部分字节数加起来等于:512+2048+1536+512 = 4608字节。
在这里插入图片描述
另外,前面我们说过,GetX方法对应的MSIL代码合计12个字节,地址是029C ~ 02A7,刚好在代码节,也就是.text节中。

映像文件加载基址

另外,为什么操作系统加载Test.exe时会加载到0x400000处,其实这也是exe文件中设定的。在NT Header下包含了三个大的部分,第一个部分是0080h开始的Word,4550是NT Header的签名;第二个部分是File Header,属于NT Header的下级Header;第三部分也是一个下级Header,称为Optional Header,这一部分信息也是非常关键,下面截图列出了一部分,其中的ImageBase = 400000h就是csc编译器建议的加载内存首地址。不过为了安全原因,现在Windows大多并不会按照这个地址加载,我调试时恰好使用了这个地址是对操作系统进行了特殊设置。但操作系统会严格按RVA去加载相对偏移地址的。
在这里插入图片描述
另外上图中其他信息也非常有用,比如AddressOfEntryPoint = 27A2,其实是告诉操作系统一旦加载了Test.exe,就可以通过调RVA = 27A2的代码来实现对MSCOREE.DLL中的_CorExeMain方法的调用,实现对CLR等与托管代码运行相关模块的加载。

阶段小节

以上,通过层层分解,我们终于知道了托管映像文件(Exe/DLL文件)中大致有哪些部分,以及Windows操作系统是如何将映像文件读到物理内存中的。

接下来,我们说说CLR,看看能否通过WinDbg找到CLR这个神秘管理者的影子。

CLR在哪儿?

让我们再回到WinDbg,再次用 !address扩展命令查询一下所有Image的加载地址:

0:000> !address -f:Image,MEM_COMMIT

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
  400000   401000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
  402000   403000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
  404000   405000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
  406000   407000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
70c90000 70c91000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
70c91000 70d00000    6f000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
70d00000 70d09000     9000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
70d09000 70d0a000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
70d0a000 70d0e000     4000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
716a0000 716a1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716a1000 716a2000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716a2000 716a5000     3000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716a5000 716a7000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716a7000 716a8000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716a8000 716aa000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716aa000 716ab000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716ab000 716ad000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716ad000 716af000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716af000 716b1000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
716b1000 7171b000    6a000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
7171b000 7171c000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
7171c000 71805000    e9000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71805000 71807000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71807000 71813000     c000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71813000 71814000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71814000 71828000    14000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71828000 7182a000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
7182a000 71830000     6000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71830000 71832000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71832000 71a59000   227000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71a59000 71a5a000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
71a5a000 729dc000   f82000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
729dc000 72ae8000   10c000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [mscorlib_ni; "C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\768f8782b919da642069c8cbb4d44aeb\mscorlib.ni.dll"]
72f40000 72f41000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ucrtbase_clr0400; "C:\Windows\SysWOW64\ucrtbase_clr0400.dll"]
72f41000 72feb000    aa000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [ucrtbase_clr0400; "C:\Windows\SysWOW64\ucrtbase_clr0400.dll"]
72feb000 72fed000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [ucrtbase_clr0400; "C:\Windows\SysWOW64\ucrtbase_clr0400.dll"]
72fed000 72ff3000     6000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ucrtbase_clr0400; "C:\Windows\SysWOW64\ucrtbase_clr0400.dll"]
73110000 73111000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [VCRUNTIME140_CLR0400; "C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll"]
73111000 73121000    10000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [VCRUNTIME140_CLR0400; "C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll"]
73121000 73122000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [VCRUNTIME140_CLR0400; "C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll"]
73122000 73125000     3000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [VCRUNTIME140_CLR0400; "C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll"]
73a20000 73a21000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
73a21000 73a32000    11000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
73a32000 73a33000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READWRITE             Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
73a33000 74164000   731000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74164000 74166000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74166000 74169000     3000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74169000 7416f000     6000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
7416f000 74172000     3000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74172000 74174000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74174000 74175000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74175000 74176000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74176000 741dc000    66000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74340000 74341000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [VERSION; "C:\Windows\SysWOW64\VERSION.dll"]
74341000 74344000     3000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [VERSION; "C:\Windows\SysWOW64\VERSION.dll"]
74344000 74345000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [VERSION; "C:\Windows\SysWOW64\VERSION.dll"]
74345000 74348000     3000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [VERSION; "C:\Windows\SysWOW64\VERSION.dll"]
74350000 74351000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [kernel_appcore; "C:\Windows\SysWOW64\kernel.appcore.dll"]
74351000 7435c000     b000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [kernel_appcore; "C:\Windows\SysWOW64\kernel.appcore.dll"]
7435c000 7435d000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [kernel_appcore; "C:\Windows\SysWOW64\kernel.appcore.dll"]
7435d000 74363000     6000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [kernel_appcore; "C:\Windows\SysWOW64\kernel.appcore.dll"]
74d00000 74d01000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [mscoreei; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll"]
74d01000 74d7b000    7a000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [mscoreei; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll"]
74d7b000 74d7c000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [mscoreei; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll"]
74d7c000 74d7e000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [mscoreei; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll"]
74d7e000 74d88000     a000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [mscoreei; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll"]
74da0000 74da1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [MSCOREE; "C:\Windows\SysWOW64\MSCOREE.DLL"]
74da1000 74dea000    49000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [MSCOREE; "C:\Windows\SysWOW64\MSCOREE.DLL"]
74dea000 74dee000     4000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [MSCOREE; "C:\Windows\SysWOW64\MSCOREE.DLL"]
74dee000 74df5000     7000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [MSCOREE; "C:\Windows\SysWOW64\MSCOREE.DLL"]
74e00000 74e01000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [sechost; "C:\Windows\SysWOW64\sechost.dll"]
74e01000 74e77000    76000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [sechost; "C:\Windows\SysWOW64\sechost.dll"]
74e77000 74e78000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [sechost; "C:\Windows\SysWOW64\sechost.dll"]
74e78000 74e79000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [sechost; "C:\Windows\SysWOW64\sechost.dll"]
74e79000 74e7a000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [sechost; "C:\Windows\SysWOW64\sechost.dll"]
74e7a000 74e7b000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [sechost; "C:\Windows\SysWOW64\sechost.dll"]
74e7b000 74e86000     b000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [sechost; "C:\Windows\SysWOW64\sechost.dll"]
74e90000 74e91000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ucrtbase; "C:\Windows\SysWOW64\ucrtbase.dll"]
74e91000 74f94000   103000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [ucrtbase; "C:\Windows\SysWOW64\ucrtbase.dll"]
74f94000 74f96000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [ucrtbase; "C:\Windows\SysWOW64\ucrtbase.dll"]
74f96000 74fa2000     c000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ucrtbase; "C:\Windows\SysWOW64\ucrtbase.dll"]
74fb0000 74fb1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [OLEAUT32; "C:\Windows\SysWOW64\OLEAUT32.dll"]
74fb1000 7503e000    8d000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [OLEAUT32; "C:\Windows\SysWOW64\OLEAUT32.dll"]
7503e000 75040000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [OLEAUT32; "C:\Windows\SysWOW64\OLEAUT32.dll"]
75040000 7504c000     c000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [OLEAUT32; "C:\Windows\SysWOW64\OLEAUT32.dll"]
75220000 75221000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [msvcp_win; "C:\Windows\SysWOW64\msvcp_win.dll"]
75221000 7528e000    6d000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [msvcp_win; "C:\Windows\SysWOW64\msvcp_win.dll"]
7528e000 7528f000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [msvcp_win; "C:\Windows\SysWOW64\msvcp_win.dll"]
7528f000 75291000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [msvcp_win; "C:\Windows\SysWOW64\msvcp_win.dll"]
75291000 75299000     8000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [msvcp_win; "C:\Windows\SysWOW64\msvcp_win.dll"]
752a0000 752a1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [gdi32full; "C:\Windows\SysWOW64\gdi32full.dll"]
752a1000 75360000    bf000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [gdi32full; "C:\Windows\SysWOW64\gdi32full.dll"]
75360000 75362000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [gdi32full; "C:\Windows\SysWOW64\gdi32full.dll"]
75362000 75363000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [gdi32full; "C:\Windows\SysWOW64\gdi32full.dll"]
75363000 75382000    1f000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [gdi32full; "C:\Windows\SysWOW64\gdi32full.dll"]
75390000 75391000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [USER32; "C:\Windows\SysWOW64\USER32.dll"]
75391000 7543c000    ab000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [USER32; "C:\Windows\SysWOW64\USER32.dll"]
7543c000 7543e000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [USER32; "C:\Windows\SysWOW64\USER32.dll"]
7543e000 7543f000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [USER32; "C:\Windows\SysWOW64\USER32.dll"]
7543f000 7553a000    fb000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [USER32; "C:\Windows\SysWOW64\USER32.dll"]
75540000 75541000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [RPCRT4; "C:\Windows\SysWOW64\RPCRT4.dll"]
75541000 755ea000    a9000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [RPCRT4; "C:\Windows\SysWOW64\RPCRT4.dll"]
755ea000 755eb000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [RPCRT4; "C:\Windows\SysWOW64\RPCRT4.dll"]
755eb000 755fa000     f000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [RPCRT4; "C:\Windows\SysWOW64\RPCRT4.dll"]
75600000 75601000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [SHLWAPI; "C:\Windows\SysWOW64\SHLWAPI.dll"]
75601000 75641000    40000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [SHLWAPI; "C:\Windows\SysWOW64\SHLWAPI.dll"]
75641000 75642000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [SHLWAPI; "C:\Windows\SysWOW64\SHLWAPI.dll"]
75642000 7564b000     9000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [SHLWAPI; "C:\Windows\SysWOW64\SHLWAPI.dll"]
756e0000 756e1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ole32; "C:\Windows\SysWOW64\ole32.dll"]
756e1000 757aa000    c9000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [ole32; "C:\Windows\SysWOW64\ole32.dll"]
757aa000 757ac000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [ole32; "C:\Windows\SysWOW64\ole32.dll"]
757ac000 75835000    89000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ole32; "C:\Windows\SysWOW64\ole32.dll"]
75840000 75841000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [bcrypt; "C:\Windows\SysWOW64\bcrypt.dll"]
75841000 75855000    14000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [bcrypt; "C:\Windows\SysWOW64\bcrypt.dll"]
75855000 75856000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [bcrypt; "C:\Windows\SysWOW64\bcrypt.dll"]
75856000 7585a000     4000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [bcrypt; "C:\Windows\SysWOW64\bcrypt.dll"]
758f0000 758f1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [KERNELBASE; "C:\Windows\SysWOW64\KERNELBASE.dll"]
758f1000 75b24000   233000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [KERNELBASE; "C:\Windows\SysWOW64\KERNELBASE.dll"]
75b24000 75b28000     4000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [KERNELBASE; "C:\Windows\SysWOW64\KERNELBASE.dll"]
75b28000 75b69000    41000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [KERNELBASE; "C:\Windows\SysWOW64\KERNELBASE.dll"]
75b70000 75b71000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [KERNEL32; "C:\Windows\SysWOW64\KERNEL32.dll"]
75b80000 75bec000    6c000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [KERNEL32; "C:\Windows\SysWOW64\KERNEL32.dll"]
75bf0000 75c1c000    2c000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [KERNEL32; "C:\Windows\SysWOW64\KERNEL32.dll"]
75c20000 75c21000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [KERNEL32; "C:\Windows\SysWOW64\KERNEL32.dll"]
75c30000 75c31000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [KERNEL32; "C:\Windows\SysWOW64\KERNEL32.dll"]
75c40000 75c41000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [KERNEL32; "C:\Windows\SysWOW64\KERNEL32.dll"]
75c50000 75c55000     5000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [KERNEL32; "C:\Windows\SysWOW64\KERNEL32.dll"]
765c0000 765c1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [bcryptPrimitives; "C:\Windows\SysWOW64\bcryptPrimitives.dll"]
765c1000 7661d000    5c000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [bcryptPrimitives; "C:\Windows\SysWOW64\bcryptPrimitives.dll"]
7661d000 7661e000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [bcryptPrimitives; "C:\Windows\SysWOW64\bcryptPrimitives.dll"]
7661e000 76623000     5000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [bcryptPrimitives; "C:\Windows\SysWOW64\bcryptPrimitives.dll"]
76630000 76631000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [combase; "C:\Windows\SysWOW64\combase.dll"]
76631000 7686d000   23c000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [combase; "C:\Windows\SysWOW64\combase.dll"]
7686d000 76871000     4000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [combase; "C:\Windows\SysWOW64\combase.dll"]
76871000 768ac000    3b000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [combase; "C:\Windows\SysWOW64\combase.dll"]
76ed0000 76ed1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ADVAPI32; "C:\Windows\SysWOW64\ADVAPI32.dll"]
76ed1000 76f3e000    6d000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [ADVAPI32; "C:\Windows\SysWOW64\ADVAPI32.dll"]
76f3e000 76f3f000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [ADVAPI32; "C:\Windows\SysWOW64\ADVAPI32.dll"]
76f3f000 76f40000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [ADVAPI32; "C:\Windows\SysWOW64\ADVAPI32.dll"]
76f40000 76f42000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [ADVAPI32; "C:\Windows\SysWOW64\ADVAPI32.dll"]
76f42000 76f4f000     d000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ADVAPI32; "C:\Windows\SysWOW64\ADVAPI32.dll"]
76f50000 76f51000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [GDI32; "C:\Windows\SysWOW64\GDI32.dll"]
76f51000 76f6c000    1b000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [GDI32; "C:\Windows\SysWOW64\GDI32.dll"]
76f6c000 76f6d000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [GDI32; "C:\Windows\SysWOW64\GDI32.dll"]
76f6d000 76f73000     6000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [GDI32; "C:\Windows\SysWOW64\GDI32.dll"]
77050000 77051000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [IMM32; "C:\Windows\SysWOW64\IMM32.DLL"]
77051000 7706b000    1a000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [IMM32; "C:\Windows\SysWOW64\IMM32.DLL"]
7706b000 7706c000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [IMM32; "C:\Windows\SysWOW64\IMM32.DLL"]
7706c000 77075000     9000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [IMM32; "C:\Windows\SysWOW64\IMM32.DLL"]
77080000 77081000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [win32u; "C:\Windows\SysWOW64\win32u.dll"]
77081000 77087000     6000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [win32u; "C:\Windows\SysWOW64\win32u.dll"]
77087000 77097000    10000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [win32u; "C:\Windows\SysWOW64\win32u.dll"]
77097000 77098000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [win32u; "C:\Windows\SysWOW64\win32u.dll"]
77098000 7709a000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [win32u; "C:\Windows\SysWOW64\win32u.dll"]
770b0000 770b1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [msvcrt; "C:\Windows\SysWOW64\msvcrt.dll"]
770b1000 77167000    b6000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [msvcrt; "C:\Windows\SysWOW64\msvcrt.dll"]
77167000 77169000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [msvcrt; "C:\Windows\SysWOW64\msvcrt.dll"]
77169000 7716d000     4000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [msvcrt; "C:\Windows\SysWOW64\msvcrt.dll"]
7716d000 77174000     7000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [msvcrt; "C:\Windows\SysWOW64\msvcrt.dll"]
771a0000 771a1000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ntdll; "ntdll.dll"]
771a1000 772cc000   12b000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [ntdll; "ntdll.dll"]
772cc000 772d2000     6000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [ntdll; "ntdll.dll"]
772d2000 77352000    80000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [ntdll; "ntdll.dll"]

我们知道,一旦操作系统为我们创建好了进程和主线程,那么4GB的虚拟内存空间(00000000 ~ FFFFFFFF)就都归我们使用了。其中00000000 ~ 7FFFFFFF这段地址空间是属于用户态地址,而80000000以上的2GB供内核使用,我们无权访问。

既然操作系统将我们的Test.exe加载到了400000开始的地址空间,操作系统会不会也将CLR加载到我们的用户态地址空间呢?如果仔细看以上地址分配,是可以找到加载CLR的地址的。下面将其他暂时不关心的Image行删除,仅保留Test.exe和CLR:

0:000> !address -f:Image,MEM_COMMIT

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
  400000   401000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
  402000   403000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
  404000   405000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
  406000   407000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [Test; "Test.exe"]
70c90000 70c91000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
70c91000 70d00000    6f000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
70d00000 70d09000     9000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
70d09000 70d0a000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
70d0a000 70d0e000     4000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clrjit; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll"]
73a20000 73a21000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
73a21000 73a32000    11000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
73a32000 73a33000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READWRITE             Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
73a33000 74164000   731000 MEM_IMAGE   MEM_COMMIT  PAGE_EXECUTE_READ                  Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74164000 74166000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74166000 74169000     3000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74169000 7416f000     6000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
7416f000 74172000     3000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74172000 74174000     2000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74174000 74175000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_WRITECOPY                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74175000 74176000     1000 MEM_IMAGE   MEM_COMMIT  PAGE_READWRITE                     Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]
74176000 741dc000    66000 MEM_IMAGE   MEM_COMMIT  PAGE_READONLY                      Image      [clr; "C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll"]

显然,所谓CLR其实由两个主要模块构成,一个是clr.dll,另一个是clrjit.dll。如果我们使用的.NET 5.0及以上版本,clr.dll会被替换成coreclr.dll。

顾名思义,clr.dll一定是包含了.NET的类型与管理规则,只有如此,才能通过它来管理应用程序的垃圾回收、内存管理、异常管理以及安全规则核实等关键工作。而clrjit.dll则主要负责将加载到400000空间的Test.exe中的MSIL即时编译成本机代码。

我们再随便从上面的列表中选一段可以执行的内存,比如:

73a21000 73a32000 11000 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READ

先查一下这段内存:

0:000> db 73a21000 l60
73a21000  8b 4d f4 64 89 0d 00 00-00 00 59 5f 5f 5e 5b 8b  .M.d......Y__^[.
73a21010  e5 5d 51 c3 ff 25 28 64-17 74 50 64 ff 35 00 00  .]Q..%(d.tPd.5..
73a21020  00 00 8d 44 24 0c 2b 64-24 0c 53 56 57 89 28 8b  ...D$.+d$.SVW.(.
73a21030  e8 a1 00 40 16 74 33 c5-50 ff 75 fc c7 45 fc ff  ...@.t3.P.u..E..
73a21040  ff ff ff 8d 45 f4 64 a3-00 00 00 00 c3 90 90 90  ....E.d.........
73a21050  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

显然,最后一行的90是CPU指令NOP,所以我们可以反汇编一下73a21000 ~ 73a21050代码:

0:000> u 73a21000 73a21050
clr!_EH_epilog3:
73a21000 8b4df4          mov     ecx,dword ptr [ebp-0Ch]
73a21003 64890d00000000  mov     dword ptr fs:[0],ecx
73a2100a 59              pop     ecx
73a2100b 5f              pop     edi
73a2100c 5f              pop     edi
73a2100d 5e              pop     esi
73a2100e 5b              pop     ebx
73a2100f 8be5            mov     esp,ebp
73a21011 5d              pop     ebp
73a21012 51              push    ecx
73a21013 c3              ret
clr!memset:
73a21014 ff2528641774    jmp     dword ptr [clr!_imp__memset (74176428)]
clr!_EH_prolog3:
73a2101a 50              push    eax
73a2101b 64ff3500000000  push    dword ptr fs:[0]
73a21022 8d44240c        lea     eax,[esp+0Ch]
73a21026 2b64240c        sub     esp,dword ptr [esp+0Ch]
73a2102a 53              push    ebx
73a2102b 56              push    esi
73a2102c 57              push    edi
73a2102d 8928            mov     dword ptr [eax],ebp
73a2102f 8be8            mov     ebp,eax
73a21031 a100401674      mov     eax,dword ptr [clr!__security_cookie (74164000)]
73a21036 33c5            xor     eax,ebp
73a21038 50              push    eax
73a21039 ff75fc          push    dword ptr [ebp-4]
73a2103c c745fcffffffff  mov     dword ptr [ebp-4],0FFFFFFFFh
73a21043 8d45f4          lea     eax,[ebp-0Ch]
73a21046 64a300000000    mov     dword ptr fs:[00000000h],eax
73a2104c c3              ret
clr!_EH_prolog3_GS:
73a2104d 90              nop
73a2104e 90              nop
73a2104f 90              nop
73a21050 90              nop

从汇编语言的标号我们就可以看出,clr.dll代码其实就在我们Test.exe文件的同一个进程空间中。上面这段代码包含了两段程序,分别是:clr!_EH_epilog3和clr!memset,而且从名字猜想,后者很可能就是为我们的应用程序设置内存的代码。

也就是说,CLR离我们很近:我们处于同一进程内存空间,且属于用户态模块,并不像内核态模块拥有绝对特权。

CLRJIT

我们最后再看一下即时编译器:clrjit。

首先我们看一下线程栈:

0:000> k
 # ChildEBP RetAddr      
00 0019f4c8 73e30ffd     KERNELBASE!wil::details::DebugBreak+0x2
01 0019f4c8 7222b1e5     clr!DebugDebugger::Break+0xa6
02 0019f4f0 04aa0872     mscorlib_ni!System.Diagnostics.Debugger.Break+0x5d [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91] 
03 0019f508 73a32536     Test!COM+_Entry_Point <PERF> (Test+0x46a0872) [E:\Add\C2_1\Test\Program.cs @ 14] 
04 0019f514 73a3e4f9     clr!CallDescrWorkerInternal+0x34
05 0019f568 73a3f1d7     clr!CallDescrWorkerWithHandler+0x6b
06 0019f5d8 73acd7c0     clr!MethodDescCallSite::CallTargetWorker+0x170
07 0019f6fc 73acd8c1     clr!RunMain+0x1c6
08 0019f968 73acd736     clr!Assembly::ExecuteMainMethod+0xf7
09 0019fe4c 73acdb44     clr!SystemDomain::ExecuteMainMethod+0x61c
0a 0019fea4 73acda8a     clr!ExecuteEXE+0x4c
0b 0019fee4 73b1e4bc     clr!_CorExeMainInternal+0xd8
0c 0019ff20 74d0a38e     clr!_CorExeMain+0x4d
0d 0019ff5c 74dafb6e     mscoreei!_CorExeMain+0x100
0e 0019ff6c 74db5708     MSCOREE!ShellShim__CorExeMain+0x9e
0f 0019ff84 75b87ba9     MSCOREE!_CorExeMain_Exported+0x8
10 0019ff84 7720c10b     KERNEL32!BaseThreadInitThunk+0x19
11 0019ffdc 7720c08f     ntdll!__RtlUserThreadStart+0x2b
12 0019ffec 00000000     ntdll!_RtlUserThreadStart+0x1b

从线程栈03行我们知道,现在程序已经运行到了C#源代码的第14行(可能是我写作过程中误操作),也就是说,已经暂停在下面代码的第二个Debugger.Break()方法上了。

        static void Main(string[] args)
        {
            Debugger.Break();
            Person person = new Person();
            person.x = 100;
            Console.WriteLine(person.GetX());
            Debugger.Break();
        }

因此,我们可以确信,此时Person类的GetX()方法一定已经被clrjit编译过了。
下面我们具体查一下。

找到clrjit证明

最后,我们使用WinDbg找到clr即时编译器工作的证据。为此,我们重新启动一次WinDbg,按之前文章介绍的WinDbg使用方法,单步执行到断点处于Console.WriteLine(person.GetX()),然后用!u命令查看Main方法的汇编代码:

0:000> !u @$scopeip
Normal JIT generated code
Test.Program.Main(System.String[])
Begin 00920848, size 73

E:\Add\C2_1\Test\Program.cs @ 9:
00920848 55              push    ebp
00920849 8bec            mov     ebp,esp
0092084b 83ec10          sub     esp,10h
0092084e 33c0            xor     eax,eax
00920850 8945f0          mov     dword ptr [ebp-10h],eax
00920853 8945f8          mov     dword ptr [ebp-8],eax
00920856 894dfc          mov     dword ptr [ebp-4],ecx
00920859 833d64438d0000  cmp     dword ptr ds:[8D4364h],0
00920860 7405            je      Test!COM+_Entry_Point <PERF> (Test+0x520867) (00920867)
00920862 e809084473      call    clr!JIT_DbgIsJustMyCode (73d61070)
00920867 33d2            xor     edx,edx
00920869 8955f4          mov     dword ptr [ebp-0Ch],edx
0092086c 90              nop

E:\Add\C2_1\Test\Program.cs @ 10:
0092086d e816a99071      call    mscorlib_ni!System.Diagnostics.Debugger.Break (7222b188)
00920872 90              nop

E:\Add\C2_1\Test\Program.cs @ 11:
00920873 b9604e8d00      mov     ecx,8D4E60h (MT: Test.Person)
00920878 e87728faff      call    008c30f4 (JitHelp: CORINFO_HELP_NEWSFAST)
0092087d 8945f0          mov     dword ptr [ebp-10h],eax
00920880 8b4df0          mov     ecx,dword ptr [ebp-10h]
00920883 ff15804e8d00    call    dword ptr ds:[8D4E80h] (Test.Person..ctor(), mdToken: 06000004)
00920889 8b45f0          mov     eax,dword ptr [ebp-10h]
0092088c 8945f4          mov     dword ptr [ebp-0Ch],eax

E:\Add\C2_1\Test\Program.cs @ 12:
0092088f 8b45f4          mov     eax,dword ptr [ebp-0Ch]
00920892 c7400864000000  mov     dword ptr [eax+8],64h

E:\Add\C2_1\Test\Program.cs @ 13:
>>> 00920899 8b4df4          mov     ecx,dword ptr [ebp-0Ch]
0092089c 3909            cmp     dword ptr [ecx],ecx
0092089e ff15484e8d00    call    dword ptr ds:[8D4E48h] (Test.Person.GetX(), mdToken: 06000003)
009208a4 8945f8          mov     dword ptr [ebp-8],eax
009208a7 8b4df8          mov     ecx,dword ptr [ebp-8]
009208aa e8c9d09071      call    mscorlib_ni!System.Console.WriteLine (7222d978)
009208af 90              nop

E:\Add\C2_1\Test\Program.cs @ 14:
009208b0 e8d3a89071      call    mscorlib_ni!System.Diagnostics.Debugger.Break (7222b188)
009208b5 90              nop

E:\Add\C2_1\Test\Program.cs @ 15:
009208b6 90              nop
009208b7 8be5            mov     esp,ebp
009208b9 5d              pop     ebp
009208ba c3              ret

接下来我们找到person对象:

0:000> !clrstack -l
OS Thread Id: 0x5c44 (0)
Child SP       IP Call Site
0019f4f8 00920899 Test.Program.Main(System.String[]) [E:\Add\C2_1\Test\Program.cs @ 13]
    LOCALS:
        0x0019f4fc = 0x02532450

0019f67c 73a32536 [GCFrame: 0019f67c] 

=================================================================================================

0:000> !do 0x02532450
Name:        Test.Person
MethodTable: 008d4e60
EEClass:     008d1308
Size:        16(0x10) bytes
File:        E:\Add\C2_1\Test\bin\Debug\Test.exe
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7171c524  4000001        8         System.Int32  1 instance      100 x
7171abdc  4000002        4        System.String  0 instance 00000000 y

=================================================================================================

0:000> !dumpmt -md 008d4e60
EEClass:         008d1308
Module:          008d40b8
Name:            Test.Person
mdToken:         02000003
File:            E:\Add\C2_1\Test\bin\Debug\Test.exe
BaseSize:        0x10
ComponentSize:   0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
   Entry MethodDe    JIT Name
71a89a88 716a9e9c PreJIT System.Object.ToString()
71b4a3e0 718061d0 PreJIT System.Object.Equals(System.Object)
71a78e70 718061f0 PreJIT System.Object.GetHashCode()
71b53760 718061f8 PreJIT System.Object.Finalize()
009208d0 008d4e4c    JIT Test.Person..ctor()
00920450 008d4e40   NONE Test.Person.GetX()

================================================================================================

0:000> !DumpMD 008d4e40  
Method Name:  Test.Person.GetX()
Class:        008d1308
MethodTable:  008d4e60
mdToken:      06000003
Module:       008d40b8
IsJitted:     no
CodeAddr:     ffffffff
Transparency: Critical
//显然此时代码尚未编译,不仅IsJitted是no,而且CodeAddr也是ffffffff
================================================================================================

//如果此时反编译GetX的入口地址,会发现实际上会调用clr!PrecodeFixupThunk (73a32610)
0:000> u 00920450
00920450 e8bb211173      call    clr!PrecodeFixupThunk (73a32610)
00920455 5e              pop     esi
00920456 0001            add     byte ptr [ecx],al
00920458 e8b3211173      call    clr!PrecodeFixupThunk (73a32610)
0092045d 5e              pop     esi
0092045e 0300            add     eax,dword ptr [eax]
00920460 40              inc     eax
00920461 4e              dec     esi

由此可以证明,当一个方法首次运行前,其MSIL是处于未编译状态的,入口地址指向了对clr!PrecodeFixupThunk (73a32610)的调用。

至于clr!PrecodeFixupThunk 后续又是如何调用clrjit,我本人也没有进一步跟踪,只不过我出于本能,猜想说不定clrjit或许有一个名字类似于CompileMethod的方法,于是试着搜了一下,结果真被找到了。

0:000> x clrjit!*Compilemethod*
7169a700          clrjit!CILJit::compileMethod (class ICorJitInfo *, struct CORINFO_METHOD_INFO *, unsigned int, unsigned char **, unsigned long *)

于是,可以在这个位置下一个断点:

0:000> bp 7169a700
0:000> g

程序被断下后,我们看看线程栈:

0:000> k
 # ChildEBP RetAddr      
00 0019ee74 73b979a5     clrjit!CILJit::compileMethod [f:\dd\ndp\clr\src\jit32\ee_il_dll.cpp @ 151] 
01 0019ee74 73b97a75     clr!invokeCompileMethodHelper+0x10b
02 0019eebc 73b97aca     clr!invokeCompileMethod+0x3d
03 0019ef28 73b97574     clr!CallCompileMethodWithSEHWrapper+0x3d
04 0019f2e8 73b97165     clr!UnsafeJitFunction+0x34a
05 0019f3e4 73b96c51     clr!MethodDesc::MakeJitWorker+0x48c
06 0019f454 73af9094     clr!MethodDesc::DoPrestub+0x596
07 0019f4cc 73ad29bb     clr!PreStubWorker+0xef
08 0019f4f0 02360889     clr!ThePreStub+0x11

CILJIT就分析到这里,下面继续执行源代码,直到被最后的Debugger.Break()断下。此时,GetX()方法已经被clrjit编译好了。再重新查询一下:

0:000> !dumpmt -md 008d4e60
EEClass:         008d1308
Module:          008d40b8
Name:            Test.Person
mdToken:         02000003
File:            E:\Add\C2_1\Test\bin\Debug\Test.exe
BaseSize:        0x10
ComponentSize:   0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
   Entry MethodDe    JIT Name
71a89a88 716a9e9c PreJIT System.Object.ToString()
71b4a3e0 718061d0 PreJIT System.Object.Equals(System.Object)
71a78e70 718061f0 PreJIT System.Object.GetHashCode()
71b53760 718061f8 PreJIT System.Object.Finalize()
009208d0 008d4e4c    JIT Test.Person..ctor()
00920908 008d4e40    JIT Test.Person.GetX()  //注意:此方法的Entry原来是00920450,现在变成了00920908

=========================================================================================
//反汇编一下新的入口地址
0:000> !u 00920908
Normal JIT generated code //这里明显显示,此代码是由jit产生的
Test.Person.GetX()
Begin 00920908, size 30

E:\Add\C2_1\Test\Program.cs @ 22:
>>> 00920908 55              push    ebp
00920909 8bec            mov     ebp,esp
0092090b 83ec08          sub     esp,8
0092090e 894df8          mov     dword ptr [ebp-8],ecx
00920911 833d64438d0000  cmp     dword ptr ds:[8D4364h],0
00920918 7405            je      0092091f
0092091a e851074473      call    clr!JIT_DbgIsJustMyCode (73d61070)
0092091f 33d2            xor     edx,edx
00920921 8955fc          mov     dword ptr [ebp-4],edx
00920924 90              nop
00920925 8b45f8          mov     eax,dword ptr [ebp-8]
00920928 8b4008          mov     eax,dword ptr [eax+8]
0092092b 8945fc          mov     dword ptr [ebp-4],eax
0092092e 90              nop
0092092f eb00            jmp     00920931
00920931 8b45fc          mov     eax,dword ptr [ebp-4]
00920934 8be5            mov     esp,ebp
00920936 5d              pop     ebp
00920937 c3              ret

===========================================================================================
//通过GetX()的MethodDesc,可以查到MSIL代码
0:000> !dumpil 008d4e40
ilAddr = 00402090
IL_0000: nop 
IL_0001: ldarg.0 
IL_0002: ldfld Test.Person::x
IL_0007: stloc.0 
IL_0008: br.s IL_000a
IL_000a: ldloc.0 
IL_000b: ret 

=========================================================================================
0:000> db 00402090 L20
00402090  13 30 01 00 0c 00 00 00-02 00 00 11 00 02 7b 01  .0............{.
004020a0  00 00 04 0a 2b 00 06 2a-22 02 28 11 00 00 0a 00  ....+..*".(.....

以上分析表明,当一段MSIL未被clrjit时,该方法的入口地址指向了clr代码,经过层层调用,最后进入到clrjit模块中,并最后将MSIL编程成了本机代码。一旦jit编译完成,CLR还会将原来方法的入口地址修改成刚生成的本级代码入口地址,以后如果再次调用这个方法,就无需再次编译了。

上面操作列表的最后两段使用了sos的扩展命令!dumpil转储了GetX方法的MSIL,显然此处查到的IL代码是和开始阶段贴图的ILSpy完全相同的。

另外,因为!dumpil的第一行显示了ilAddr = 00402090,所以我们接下来用db命令又查了一下内存,发现其实00402090地址并不是第一条MSIL指令码,真正的第一条指令码在0040209C处,这刚好呼应了我们通过ILSpy时提出的疑问。那么,多出来这12个字节究竟是何方神圣?

方法头

原来,在exe文件保存每个方法时,都需要首先为该方法创建一个方法头。

方法头有两种,一种是瘦型(Tiny)方法头,仅仅一个字节长,另一种是胖型(FAT)方法头,总计是12个字节长。本例中GetX对应的IL代码前12个字节就是胖方法头。

方法头的作用,首先告诉CLR方法头是胖型还是瘦型,以便让CLR找到真正的代码开始地址。其次,方法头还告诉CLR此方法中MSIL总长度,以便CLR可以知道编译到哪个位置结束。

印象中,我前面的某篇文章(忘记具体是那篇了)应该对方法头有更详细说明,大家可以搜搜看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值