dotnet学习笔记二 - 运行.net程序的秘密

.NET Framework 给我们提供了良好的开发平台。有很好的类库,可以跨语言,跨平台等等。但是他的内部实现细节是怎样的呢? .NET 编译出来的 exe 文件并不是机器码,它是怎样和 CLR 结合起来的呢?下面就让我们揭开这个小秘密。

首先做一个简单的 .NET 应用,把它编译成 EXE 文件。然后用 Visual Studio 6.0 带的工具 Depends 把它打开。如下图:

这里可以看到一个很奇怪的现象,我的 .net 应用程序只直接依赖于一个 dll – MSCOREE.DLL 。而且这个 DLL 输出的这么多函数中也只用到了一个 _CorExeMain 。我的这个 APPENDLINE.EXE 中还用到了自己做的一个 .NET 组件,在这里也看不到。

再让我们借助一些 PE 察看工具来分析一下这个 EXE 文件。这一步得到的结果是什么呢?在这个 PE 文件的入口函数上可以看到一条唯一的汇编语句:

JMP DS:_CorExeMain

经过上面的步骤,我们可以明确的断定所有的 .net 编译后的 EXE 文件一旦运行,就执行 MSCOREE.DLL 输出的一个函数 _CorExeMain 。而在开发过程中我们使用到的一些外部组件、控件由于是被 .net 编译器编译成了中间代码,在 Depends 中是无法看到的,所有使用外部组件的过程全部由 CLR 处理。

幸运的是微软这次公布了一个 CLI 实现的代码,我们可以看个究竟。我下载的代码中没有找到 _CorExeMain ,只找到了一个 _CorExeMain2 。参数有 5 个:

PBYTE pUnmappedPE, // -> memory mapped code

DWORD cUnmappedPE, // Size of memory mapped code

LPWSTR pImageNameIn, // -> Executable Name

LPWSTR pLoadersFileName, // -> Loaders Name

LPWSTR pCmdLine

一目了然, pUnmappedPE 和 cUnmappedPE 就是 .net 编译后的中间代码的内存缓冲和长度。 pImageNameIn 就是这个 EXE 的名字, pLoadersFileName 是载入者的名字, pCmdLine 就是命令参数。有了这几个参数我们就可以猜到 .net 将中间代码编译后放入到 PE 文件中,再加入一段代码直接执行 MSCOREE.DLL 的 _CorExeMain ,参数就是中间代码的内存指针。 JIT 就把这段中间代码编译成机器代码执行。

_CorExeMain2 代码不长,简单总结一些,做了如下的 5 步工作:

1. 验证签名。

2. 初始化 CLR 环境

3. 创建了一个代表 EXE 文件的 PEFile 对象

4. 使用 SystemDomain 的静态方法执行这个 PEFile 。

5. 执行后,做一些清除工作,退出。

很明显,上面 5 个步骤里最重要,最关键的就是第 4 步了,究竟做了什么呢?有兴趣的朋友可以去看微软的 CLI 代码,我在以后的文章中也会进一步分析。

Published At
Categories with Web编程
Tagged with
comments powered by Disqus