English Version go here.
By Alva Chien
Part I: Basics of the Microsoft .NET Framework
Chapter 1: The Architecture of the .NET framework Development Platform
1. .Net中的common language runtime (CLR) 是一个可以被多个不同语言使用的runtime. 不管使用哪种编译器,所生成的结果都是一个managed module. 一个managed module是一个标准的Windows Portable executable (PE) 文件,这个文件必须由CLR来执行.
一个managed module的组成:
Part |
Description |
PE Header |
标准的 Windows PE header, 类似于Common Object File Format (COFF) header. 这个header包含了文件类型: GUI, CUI, or DLL, 它同样包含了一个文件创建时的时间戳. 对只包含IL代码的的modules,PE header中的大部分信息被忽略. 对包含native CPU代码的module,这个header包含了关于native CPU代码的信息. |
CLR Header |
包含了一个managed module所必须的信息(由CLR和其工具解析) . 这个header包含了所需的CLR版本和一些状态标志位, managed module的入口方法(Main方法,一个MethodDef metadata token), metadata的位置/大小, 资源, strong name, 一些标志位和其他信息. |
Metadata |
每个managed module都包含metadata表. 这些表有两种类型: 定义types和members的的表和定义Referenced的type和members的表. |
Intermediate language (IL) code |
由编译器编译的代码,真正执行时候CLR会将IL代码编译为native CPU instructions. |
2. 一个 assembly是一个或多个managed modules/资源文件的逻辑组合, 它是最小的可重用,拥有版本信息和安全信息的单元. PE文件包含一块叫做manifest的数据块. 一个manifest是另一个metadata tables的集合. 这些tables定义了组成一个assembly的文件, 由这些文件定义的exported types, 跟这个assembly关联的资源或数据文件. 一个典型的例子: 把一些较少使用的types或资源定义在assembly的一个独立文件中,这个文件只会在其中的type或资源被使用的时候才被加载.
3. 可以通过查找MSCorEE.dll 文件来判断.NET是否被安装,这个文件位于%windir%\system32 文件夹中. 但是,一台机器允许同时安装几个版本的.Net Framewor.可以通过查看一下注册表的键值来判断当前.NET的版本: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy
4. 如何装载 CLR:
- 当创建一个EXE assembly, 编译器/链接器在生成的assembly’s PE file header嵌入一些特别的信息在.text section.
- 当创建一个DLL assembly, 如果一个unmanaged程序调用LoadLibrary 装载它, 这个DLL’s 入口函数知道如果装载CLR来正确处理assembly中的代码.
5. 当创建一个EXE assembly, 一个的6字节的x86 sub function被嵌入到.text section: JMP _CoreExeMain. 这个_CoreExeMain函数从MSCorEE.dll导入, 这个MSCoreEE.dll (Microsoft Component Object Runtime Execution Engine) 在assembly file’s .idata section定义了引用. 所以当assembly像正常程序一样启动时, MSCoreEE.dll被导入到这个进程的地址空间, 这时_CorExemain 函数的地址被获取,同时JMP instruction被执行. 这个函数将初始化CLR并且查找这个可执行assembly’s CLR header的可执行入口方法,这个方法的IL代码将被编译为native CPU instructions, 随后CLR跳转这个native code, 这时,程序已经启动.
6. 当创建一个DLL assembly, 一个类似的6字节长的x86 stub function被嵌入到.text section: JMP _CorDllMain. _CorDllMain 函数同样从MSCorEE.dll中引入, 这个DLL’s .idata section 中包含MSCorEE.dll的引用定义. 所以, 当LoadLibrary 执行时, _CorDllMain 被调用来初始化CLR并返回给应用程序来继续执行.
7. 这个6字节的stub function仅仅在非Windows XP系统中被添加. 在Windows XP以及以后版本中, OS loader检查嵌入managed code的文件的PE file header的directory entry 14. (IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR定义在WinNT.h) 如果这个directory entry存在并且拥有一个非0值, OS loader忽略这个文件的导入 (.idata) section并且自动装载MSCorEE.dll到进程的地址空间并直接跳转到对应的函数.
8. 最后一个关于managed PE文件的注意点: 他们总是使用32位PE文件格式, 而不是64位PE文件格式. 在64位系统上, OS loader检查这个managed 32位PE文件并知道如何去创建一个64位地址空间.
9. 至于知识产权保护, 对于所有的编译位IL的代码并且IL代码能被很容易被Disassembler程序reverse, 可以使用一个第三方提供的obfuscator工具. 这些工具“scramble” managed module’s metadata中所有private symbols的名称. 另外,可以把一些代码编译为unmanaged DLL并在assembly中调用它.
10. 所有的高级语言都只是提供了CLR的一个功能子集. 但是,IL提供了CLR所有的功能.
11. 如果Assembly中的一个方法第一次被调用,它的IL代码需要被编译为native CPU代码, 这部分功能由CLR’s JIT (just-in-time)完成. JIT编译器在内存中保存了native CPU instructions, 所以当应用程序终止时候,编译的代码都将失效. 另外一个值得记住的重点是JIT编译过程当中有一个专门的优化编译代码的过程.
12. Microsoft .Net framework提供了一个名为NGen.exe的工具; 这个工具编译一个assembly的全部IL代码为native CPU代码并将结果保存到一个硬盘文件.
13. IL是一个stack-based的语言, 所有的IL instructions会把operands压栈,并将结果弹出. IL没有提供操作CPU寄存器的指令.