(翻译)《Expert .NET 2.0 IL Assembler》 第四章 托管可执行体文件的结构 4.2 CLR头(一)

返回目录

 

CLR

PE头的第15个目录入口包括了RVA和映像文件中的CLR头的大小。这个CLR头,包括了所有特定于CLR的数据入口和其他的信息,应该位于这个映像文件中的一段只读区段。IL编译器将CLR头放在了.text区段。

 

头的结构

CLR头定义在CorHdr.h中——一个头文件被分配为Microsoft .NET Framework SDK的一部分,如下:

typedef struct IMAGE_COR20_HEADER
{
     ULONG                   cb; 
     USHORT                  MajorRuntimeVersion;
     USHORT                  MinorRuntimeVersion;
     // Symbol table and startup information
     IMAGE_DATA_DIRECTORY    MetaData; 
     ULONG                   Flags; 
     union {
          DWORD               EntryPointToken;
          DWORD               EntryPointRVA;
     };
     // Binding information
     IMAGE_DATA_DIRECTORY    Resources;
     IMAGE_DATA_DIRECTORY    StrongNameSignature;
     // Regular fixup and binding information
     IMAGE_DATA_DIRECTORY    CodeManagerTable;
     IMAGE_DATA_DIRECTORY    VTableFixups;
     IMAGE_DATA_DIRECTORY    ExportAddressTableJumps;
     IMAGE_DATA_DIRECTORY    ManagedNativeHeader;
} IMAGE_COR20_HEADER;

4-6对这个头的字段提供了进一步的着眼。

 

4-6 CLR头的字段

偏移量

大小

字段名

描述

0

4

Cb

头的字节大小。

4

2

MajorRuntimeVersion

CLR需要运行程序的最小版本的主版本号。

6

2

MinorRuntimeVersion

CLR需要运行程序的最小版本的次版本号。

8

8

MetaData

RVA和元数据的大小。

16

4

Flags

二进制标记,在接下来的章节讨论。在ILAsm中,你可以通过显示地使用指令.corflags <integer value>/或命令行选项/FLAGS=<integer value>详细指明这个值。这个命令行选项优先于指令。

20

4

EntryPointToken/EntryPointRVA

这个映像文件的入口点的元数据识别符(符号);对于DLL映像而言可以是0。这个字段识别了属于这个模块的一个方法或包括这个入口点方法的一个模块。在2.0或更新的版本中,这个字段可能包括内嵌的本地入口点方法的RVA

24

8

Resources

RVA和托管资源的大小。

32

8

StrongNameSignature

RVA和用于这个PE文件的哈希数据的大小,由加载器在绑定和版本控制中使用。

40

8

CodeManagerTable

RVA和代码管理表的大小。在现有的CLR发布版本中,这个字段是保留的,并被设置为0

48

8

VTableFixups

RVA和一个由虚拟表(v-表)修正组成的数组的字节大小。在当前托管的编译器中,只有VC++连接器和IL编译器能够生成这个数组。

56

8

ExportAddressTableJumps

RVA和由jump thunk的地址组成的数组的大小。在托管的编译器中,只有8.0之前版本的VC++能够生成这种表,这将允许导出内嵌在托管PE文件中的非托管本地方法。在CLR2.0版本中,这个入口是废弃的并且必须被设置为0

64

8

ManagedNativeHeader

为预编译映像而保留的,被设置为0

 

Flags字段

CLR头的Flags字段保存了下列位标志的位或运算:

COMIMAGE_FLAGS_ILONLY (0x00000001):这个映像文件只包括IL代码,而没有内嵌的本地非托管代码——除开始的stub外(只是简单地执行一个到CLR进入点的跳转)。CLR——意识到操作系统(如Windows XP或更新的操作系统)忽略开始部分的stub并自动调用CLR,到目前为止,所有的实际意图都是为了这个文件可以被认为是纯净的IL。然而,在Windows XP或更新操作系统下运行的时候,设置这个标记会引起特定的问题。如果设置了这个标记,Windows XP或更新操作系统的加载器不仅忽略了开始部分的stub,还忽略了.reloc区段,在这种情形中后者将包括一个对CLR入口点的单一的重定位(或者说在特定于IA64映像中的单独的一对重定位)。然而,.reloc区段既可以包括.tls区段的开始和结束位置的重定位,还包括被称为data on data的重定向(就是说,数据常量指向其它的数据常量)。在这些现有的托管的编译器中,只有VC++IL编译器可以生成这些项。VC++7.0版本和7.1版本(对应CLR1.0版本和1.1版本)并没有设置这个标记,因为他生成的映像文件从来不是纯净的IL。在2.0版本中,这种情形有了改变,而当前,VC++IL编译器是唯一的两个能够生成纯净IL映像文件的工具,而这些图像需要在.reloc区段中额外的重定向。为了解决这个问题,如果发布的是基于BLS的数据或data on dataIL编译器就会清除这个标记;如果目标平台是32位的,IL编译器就会替代地设置这个OMIMAGE_FLAGS_32BITREQUIRED标记。

COMIMAGE_FLAGS_32BITREQUIRED (0x00000002):映像文件只可以被加载进一个32位进程中。这个标记是单独设置的——当本地非托管代码内嵌在PE文件中,或者当.reloc区段包括了额外的重定向的时候,或者与_ILONLY联合起来被设置——当可执行体不仅包括额外的重定向而且在某些方面是以特定于32位的(例如,调用一个非托管32位特定的API或者使用4字节的整型来存储指针)。

COMIMAGE_FLAGS_IL_LIBRARY (0x00000004):这个标记是废弃的并且不能被设置。使用.corflags指令设置它——正如IL编译器允许的,将会生成你的未加载的模块。

COMIMAGE_FLAGS_STRONGNAMESIGNED (0x00000008):映像文件是受强签名保护的。强签名。这个强签名包括了公钥和哈希签名,并作为一个编译集同一性的一部分,伴随着这个编译集的名称、版本号以及文化信息。当这个强签名过程被应用到一个映像文件的时候,就会设置这个标记。没有编译器,包括ILAsm在内,可以显示地设置这个标记。

COMIMAGE_FLAGS_NATIVE_ENTRYPOINT (0x00000010):执行体的进入点是一个非托管的方法。CLR头的EntryPointToken/EntryPointRVA字段包括了这个本地方法的RVA。这个标记是在CLR 2.0版本中引进的。

COMIMAGE_FLAGS_TRACKDEBUGDATA (0x00010000)CLR加载器和IL编译器被要求对方法的调试信息进行跟踪。这个方法没有被使用。

 

EntryPointToken字段

CLR头的EntryPointToken字段包括了一个符号(元数据标志符)——一个方法定义(MethodDef)或一个文件引用(File)。MethodDef符号将定义在模块(一个托管的PE文件)中的方法识别为入口点方法。File记号只用在一种情形中:在多模块编译集的主模块的CLR头中,当进入点方法定义在这个编译集的另一个模块中(由文件引用识别)。在这种情形中,这个由文件引用识别的模块包括了在它所在的CLR头的EntryPointToken字段中相应的MethodDef记号。

EntryPointToken必须在可以运行的执行体(EXE文件)中详细指明。例如,如果源代码没有定义入口点,IL编译器甚至都不会尝试生成一个EXE文件。CLR加载器利用了入口点方法的签名上的局限性:这个方法必须返回一个有符号或无符号的4字节的整数或者void,而且它至多必须有一个stringstring[]string向量)类型的参数。

没有这些不能运行的可执行体(DLL文件),这将是一个不一样的故事。纯ILDLL并不需要定义了的入口点方法,并且在它们的CLR头中的EntryPointToken字段必需被设置为0

混合代码的DLL——包括了IL和内嵌的非托管代码——由VC++编译器和链接器生成的,必须在这个DLL被调用时直接运行非托管本地函数DllMain,从而为非托管的本地DLL组件执行必要的初始化。这个非托管方法的签名必须是如下这样的:

int DllMain(HINSTANCE, DWORD, void *);

为了对托管代码和CLR可见,DllMain函数必须被声明为一个对内嵌的本地方法的平台调用(本地P/Invoke,也被称为IJW——It Just Works)。参见18章获取关于托管和非托管代码互操作的更多细节。

2.0版本开始,你可以详细指明非托管的入口点方法而不用本地平台调用。既然这样,由OMIMAGE_FLAGS_NATIVE_ENTRYPOINT设定标记指定,这个EntryPointRVA字段(别名为EntryPointToken)包括了本地入口点方法的RVA

这个被CLR头的EntryPointToken/EntryPointRVA字段指向的方法并没有对PE头指向的AddressOfEntryPoint方法做些什么。AddressOfEntryPoint总是指向CLR调用stub,这对于CLR是不可见的,不在元数据中反映出来并因此而没有一个符号。

 

VTableFixups字段

CLR头的VTableFixups字段是一个数据目录,包括了RVA和映像文件的V-表的修正表的大小。托管和非托管方法使用了不同的数据格式,因此当一个托管方法必须被非托管代码调用时,CLR就会为其创建一个封装的thunk,这将执行数据转换,而这个thunk的地址就被放置于相应的地址表中。如果托管方法是被内嵌在当前托管PE文件中的非托管代码调用的,这个thunk地址会进入这个文件的V-表。如果托管方法导出为非托管的,并且在这个托管PE文件外的任何地方使用,相应的v-表入口地址必须也进入导出型地址表。在加载的时候(也就是在磁盘的映像文件中),这个V-表的入口包括了相应的方法符号。

这些V-表的修正代表了创建这些thunk所必需的CLR初始信息。V-表的修正定义在下面的CorHdr.h中:

typedef struct _IMAGE_COR_VTABLEFIXUP {
     ULONG       RVA;
     USHORT      Count;
     USHORT      Type; 
} IMAGE_COR_VTABLEFIXUP;

     在这个定义中,RVA指向了V-表的槽的位置,这个槽包括了方法的符号。Count详细指出了槽中入口的数量,1或者更多,例如,对同一个方法的多种实现是存在的,相互覆盖。Type是以下标记的位或运算,为CLR提供了关于槽的信息以及使用它能做些什么:

COR_VTABLE_32BIT (0x01):每个入口都是32位宽的。

COR_VTABLE_64BIT (0x02):每个入口都是64位宽的。

COR_VTABLE_FROM_UNMANAGED (0x04):由CLR创建的thunk必须提供托管和非托管代码间的数据封送。

COR_VTABLE_CALL_MOST_DERIVED (0x10):这个标志当前并不使用。

显然,头两个标记是互相排斥的。V-表的这些槽必须是一个紧跟着另一个——就是说,V-表必须是连续的。

V-表位于一个可读写的区段,因为它应该在图像被加载进内存后安排处理。相反,非托管图像中的V-表位于一个只读区段内。

在现有的托管编译器中,只有VC++IL编译器可以定义V-表和它的修正。

 

StrongNameSignature字段

CLR头的StrongNameSignature字段包括了RVA和哈希强签名的大小,这将由CLR使用来确定映像文件的真实性。在映像文件被创建之后,使用由映像文件制造者提供的私钥对其进行哈希处理,而结果哈希块被写入到在映像文件中分配的空间。

如果即使连映像文件的一个单字节也是后期修改的,真实性检查就会失败,而且映像文件也不会被加载。强签名并不会经历一个反复的过程;如果你使用IL反编译器反编译一个强名称模块,接着重新编译它,这个模块必须重新进行强名称签名。

IL编译器将强名称签名放进映像文件的.text区段中。

 

posted @ 2008-08-27 23:40  包建强  Views(1096)  Comments(0Edit  收藏  举报