加载.NET程序集

分析.NET应用程序启动过程的最佳方式就是观察一个简单的.NET命令行程序。程序的源代码和程序集分别位于以下文件夹中:

源代码文件:C:\\adnd\\chapter1\\MDASample

程序集文件:C:\\adndbin\\01mdasample.exe

如果运行上面的程序,它会成功执行,如清单2-2所示。

清单2-2执行02simple.exe

 

由于.NET应用程序在执行时要预先加载CLR,那么Windows如何知道加载并初始化CLR?我们可能会做出一种假设:系统开发人员对 Windows加载器进行了改动以识别.NET程序集,并且当检测到.NET程序集时自动启动CLR。尽管这种假设只有部分是正确的,但在.NET之前的 Windows上执行.NET应用程序时,必须首先打补丁。回答这个问题的关键在于:对PE格式进行了扩展。在前面已经提到过,PE格式是Windows 可执行程序的文件格式,用来管理PE文件中代码的执行。可执行程序包括EXE、DLL、OBJ、SYS等文件。为了支持.NET,在PE文件格式中增加了 对程序集的支持,如图2-3所示。

现在,我们来分析当加载器遇到一个.NET应用程序时将发生哪些动作。在这个示例中使用02simple.exe,这个程序位于 C:\\ADNDBin。需要注意的是,这个示例程序是在Windows 2000上运行。之所以要在一个旧版本的Windows上运行,是因为在Windows 2000之后的版本中增加了一些改动,而这些改动将影响Windows加载器加载.NET程序集的方式(在本章的后面将进行介绍)。为了更好地说明这些概 念,我使用了一个工具dumpbin.exe,它能够解析PE文件格式并且以简洁易读的形式转储出PE文件的内容。在02simple.exe上运行 dumpbin.exe的结果被保存在一个文件中:

 
第一部分值得注意的信息是在Optional Header Values段中,如下所示:
 

上面的Entry Point域对应于PE文件中的AddressOfEntryPoint域,值为0x00402464。要找出位置0x00402464所对应的代码,需遥看PE映像中的.text段,具体来说就是如下面清单中的RAW DATA段:
 
上面信息中的粗体字节对应于AddressOfEntryPoint ,这些字节对应的机器指令为:
 

这里有一个问题:402000 表示什么意思?事实上,0x402000指向的是PE映像文件中的另一部分,也就是import段,在这个段中列出的是PE文件依赖的所有模块。在加载 时,系统将修正导入函数的实际地址,并执行正确的调用。要找到0x402000指向的内容,我们可以查看PE文件的导入段,可以发现以下内容:

 

可以看到,0x402000指向的是mscoree.dll(Micorsoft对象运行时执行引擎,Microsoft Object Runtime Execution Engine),这个库中包含了一个导出函数_CorExeMain。然后,前面的JMP 指令可以转换为以下伪码:

 

我们已经看到了,_CorExeMain是mscoree.dll的一部分,这个函数也是在加载.NET程序集时第一个被调用的函数。 mscoree.dll(和_CorExeMain)的主要作用就是启动CLR。 mscoree.dll在启动CLR时将执行一系列的工作:

1)通过查看PE文件中的元数据,找出.NET程序集是基于哪个版本的CLR构建的。

2)找出操作系统中正确版本CLR的路径。

3)加载并初始化CLR。

在CLR被初始化之后,在PE映像的CLR头中就可以找到程序集的入口点(Main())。然后,JIT开始编译并执行入口点。到目前为止,我们所 谈到的CLR还只是一个逻辑组件,而并没有提到它的各项功能具体由哪些映像来实现。CLR的大部分功能是由mscorwks.dll来实现的。而且,在任 何一台机器上都可能有多个版本的mscorwks.dll。例如,如果安装了.NET 1.1和.NET 2.0,那么在机器上将存在以下CLR DLL:

C:\\Windows\\Microsoft.NET\\Framework\\v1.1.4322\\mscorwks.dll

C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\mscorwks.dll

之所以要安装多个版本,是因危同的.NET应用程序可能需遥同版本的CLR。基于CLR 1.0编写的应用程序需要正确地加载1.0 版本的CLR,即使有.NET 2.0也不行。这种机制本质上是实现.NET中的平行执行模型(Side-By-Side Execution Model)。mscoree.dll的作用是通过查看PE映像文件中的CLR头来找出程序集需要使用哪个版本的CLR。具体来 说,mscoree.dll将查看CLR头中的MajorRuntimeVersion和MinorRuntimeVersion两个域,并且加载正确版 本的CLR。

到目前为止,我们已经介绍了.NET程序集(后缀名为.EXE)的整个启动流程。正如非托管的Windows应用程序能够支持像动态库这种可执行形 式,.NET也有着同样的方式。就加载器而言,.NET库与.NET可执行程序之间的唯一差别就是,在PE映像中导入的函数不是_CorExeMain, 而是_CorDllMain。

在加载mscoree.dll时有一个值得注意的问题,即为什么需要非托管的存根函数来调用_CorExeMain?因为PE映像文件包含了一 个.NET头,那么Windows加载器是否也可以将这个PE映像识别为一个.NET程序集并且自动加载mscoree.dll?是的,的确如此。在 Windows XP以及之后的版本中对Windows加载器进行了升级,使其能够识别出一个.NET程序集的PE映像,并且自动加载CLR。

.NET程序集的加载算法总结如下:

1)用户执行一个.NET程序集。

2)Windows加载器查看AddressOfEntryPoint 域,并找到PE映像文件的.text段。

3)位于AddressOfEntryPoint 位置上的字节只是一个JMP指令,这个指令跳转到mscoree.dll中的一个导入函数。

4)将执行控制转移到mscoree.dll中的函数_CorExeMain中,这个函数将启动CLR并且把执行控制转移到程序集的入口点。

PE文件格式是一种多用途的格式(从它可以很容易支持.NET程序集的特性也说明了这一点),它包含了与被加载和执行的PE映像相关的大量信息。本 节内容重点介绍了如何通过扩展PE文件格式来支持.NET程序集的执行。接下来,我们将深入分析CLR中其他的关键内容,首先介绍应用程序域。

原文地址:https://www.cnblogs.com/shihao/p/2500794.html