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

返回目录

 

     第1章介绍了托管可执行文件,被称为托管模块,并在CLR的环境中执行。在这一章,我将会展示这样一个文件的通用结构。这个托管模块的文件格式是标准的Microsoft Windows PE/COFF(可移植的执行体和公用对象文件格式)的扩展。因此,在形式上,任何托管模块都是一个恰当的PE/COFF文件,带着额外的特性以将其识别为一个托管的可执行文件。

     托管模块的文件格式符合Windows PE/COFF标准,而操作系统将托管模块当作一个可执行体。并且,一旦操作系统调用这个模块,这个扩展的特定于CLR的信息就允许运行时立刻获取对模块可执行体的控制

     4-1显示了这个托管的PE/COFF结构。

 

4-1一个托管可执行文件的通用结构

  

       既然ILAsm只生成了PE文件,这一章就将重点放在PE文件上——执行体,又被称为映像文件(Image File),因为它们可以被看作为“内存映像”——而不是纯净的COFF对象文件。(实际上,只有其中一个当前的托管编译器,Microsoft Visual C#生成了对象文件作为到PE文件的中间步骤。)

       对托管PE文件结构的分析使用了下面通用的定义:

       文件指针。在被加载器访问之前,文件中某一项的位置。这个位置是文件中的一个指针(偏移量),正如文件被存储在磁盘上一样。

       相对虚地址(RVA):一个项的地址,一旦该项被加载进内存,映像文件的基地址就会从中减去——换句话说,映像文件中的一个项的偏移量被加载进内存。一个项的RVA几乎总是不同于它在磁盘上文件的位置(文件指针)。

       虚地址(VA):与RVA相同,除了映像文件的基地址不会从中减去。这个地址被称为虚拟的,因为操作系统为每一个进程创建了一个截然不同的虚拟地址空间,独立于物理内存。出于大多数考虑,一个虚拟地址应该被简单地视作一个地址。一个虚拟地址不能被预言为一个RVA,因为如果存在一个冲突伴随着任意的映像文件被加载——基地址冲突,加载器可能不会加载映像文件到它首选的位置。

       SectionPE/COFF中的代码或数据的基本单元。除了代码和数据section外,一个映像文件可以包括大量的section,如.tls(线程本地存储)或.reloc(重定位),这是有特殊意图的。在这个section中的所有原始数据必须被连续加载。

 

       贯穿本章(并且确实的说是贯穿本书),我使用了术语“托管编译器”来表示一个编译器,它以CLR为目标并生成托管的PE文件。这个术语非必要地暗示了编译器本身是一个托管的应用程序。

 

PE/COFF

       4-2解释了一个特定于操作系统的PE文件头的结构。这个头包括一个MS-DOS头和stubPE签名,COFF头,PE头以及section头。下面的部分讨论了所有这些组件和PE头中的数据目录表。

 

MS-DOS/StubPE签名

       MS-DOS头和stub只存在于映像文件中。它们位于映像文件的起始位置,代表了一个有效的运行在DOS下的应用程序(这是否很令人兴奋呢?)。当映像文件运行在MS-DOS下的时候,默认的stub打印出消息“This message cannot be run in DOS mode”。特定于操作系统的头,可能是最没有意思的部分;唯一相关的事实是,MS-DOS头,在0x3C的偏移位置,包括了指向PE签名的文件指针,这将允许操作系统恰当地执行映像文件。

 

4-2 特定于操作系统的头的内存布局

 

 

       PE签名通常(不是必要的)直接位于MS-DOS stub之后,这是一个4字节的项,将文件识别为PE格式的映像文件。这个签名包含了字符PE,紧跟在2个空字节之后

 

COFF

       一个标准的COFF头直接位于PE签名之后。COFF头提供了一个PE/COFF文件的大部分通用字符,适用于对于对象和可执行文件。表4-1描述了COFF头的结构和它的字段的意思。

 

4-1 COFF头的格式

偏移量

大小

字段名

描述

0

2

Machine

识别目标机器的数字。(参见表4-2。)如果托管PE文件要提供各种各样的机器类型,这个字段就应该被设置为IMAGE_FILE_MACHINE_I3860x014C)。IL编译器具有命令行选项 /ITANIUM/X64以分别指定IMAGE_FILE_MACHINE_IA64IMAGE_FILE_MACHINE_AMD64这两个值

2

2

NumberOfSections

Section表中实体的数量,直接跟随在头的后面。

4

4

TimeDateStamp

文件创建的日期和时间。

8

4

PointerToSymbolTable

COFF符号表的文件指针。因为这个表从来不在托管PE文件中使用,这个字段必须被设置为0

12

4

NumberOfSymbols

COFF符号表中的实体数量。这个字段在托管PE文件中被设置为0

16

2

SizeOfOptionalHeader

PE头的大小。这个字段特定于PE文件;在COFF文件中被设置为0

18

2

Characteristics

这个标记指出了文件的特性。

 

标准COFF头的结构被定义在如下的Winnt.h 中:

typedef struct _IMAGE_FILE_HEADER {
     WORD    Machine;
     WORD    NumberOfSections;
     DWORD   TimeDateStamp;
     DWORD   PointerToSymbolTable;
     DWORD   NumberOfSymbols;
     WORD    SizeOfOptionalHeader;
     WORD    Characteristics;
} IMAGE_FILE_HEADER, 
*PIMAGE_FILE_HEADER;

     机器类型也被定义在Winnt.h 中,正如表4-2所示。每一个类型都被命名为IMAGE_FILE_MACHINE_XXX,为避免重复,我将其简写为_XXX.

 

4-2 机器字段值

常量*

描述

_UNKNOWN

0

只对于非托管PE文件而言,内容假定可适用于任何机器类型。

_I386

0x014c

Intel386或随后之上。对于纯的托管PE文件,内容可使用于任何机器类型。

_R3000

0x0162

MIPS little endian——在最重要的字节之前的最不重要的字节。0x0160 big endian——在最不重要的字节之前的最重要的字节。

_R4000

0x0166

MIPS little endian

_R10000

0x0168

MIPS little endian

_WCEMIPSV2

0x0169

运行在Microsoft Windows CE 2上的MIPS little endian

_ALPHA

0x0184

Alpha AXP

_SH3

0x01a2

SH3 little endian.

_SH3DSP

0x01a3

SH3DSP little endian.

_SH3E

0x01a4

SH3E little endian.

_SH4

0x01a6

SH4 little endian.

_ARM

0x01c0

ARM little endian.

_THUMB

0x01c2

ARM processor with Thumb decompressor.

_AM33

0x01d3

AM33处理器。

_POWERPC

0x01F0

IBM PowerPC little endian.

_POWERPCFP

0x01F1

带有浮点指针单元(FPU)IBM PowerPC little endian

_IA 64

0x0200

Intel IA64 (Itanium)

_MIPS16

0x0266

MIPS

_ALPHA64

0x0284

ALPHA AXP64.

_AXP64

0x0284

ALPHA AXP64.

_MIPSFPU

0x0366

带有FPUMIPS

_MIPSFPU16

0x0466

带有FPUMIPS16

_TRICORE

0x0520

Infineon

_AMD64

0x8664

AMD X64 Intel E64T 架构。

_M32R

0x9041

M32R little endian

*第一段“常量”列,省略了前缀IMAGE_FILE_MACHINE_XXX

 

    包包译注:little endianbig endian是表示计算机字节顺序的两种格式,所谓的字节顺序指的是长度跨越多个字节的数据的存放形式。

      假设从地址0x00000000开始的一个字中保存有数据0x1234abcd,那么在两种不同的内存顺序的机器上从字节的角度去看的话分别表示为:

1)       little endian:在内存中的存放顺序是0x00000000-0xcd0x00000001-0xab0x00000002-0x340x00000003-0x12

2)       big  endian:在内存中的存放顺序是0x00000000-0x120x00000001-0x340x00000002-0xab0x00000003-0xcd

需要特别说明的是,以上假设机器是每个内存单元以8位即一个字节为单位的。

简单的说,ittle endian把低字节存放在内存的低位;而big endian将低字节存放在内存的高位。

现在主流的CPUintel系列的是采用的little endian的格式存放数据,而motorola系列的CPU采用的是big endian

endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命另一个丢了王位。

我们一般将endian翻译成“字节序”,将big endianlittle endian称作“大尾”和“小尾”。但是在本书中保留英文原词。

          包包译注:Alpha AXP,也称为DEC Alpha,是64位的 RISC 微处理器,最初由DEC公司制造,并被用于DEC自己的工作站和服务器中。作为VAX的后续被开发,支援VMS操作系统,如 Digital UNIX。不久之后开放源代码的操作系统也可以在其上运行,如Linux BSD Microsoft 支持这款处理器,直到Windows NT 4.0 SP6 ,但是从Windows 2000 beta3 开始放弃了对Alpha的支援。

 

       COFF头的特征字段包含了指出PE/COFF文件特性的标记。这些标记定义在Winnt.h中,如表4-3所示。注意到这个表提到了纯净ILpure IL)的托管PE文件,纯净IL指出映像文件并不包含内嵌的本地代码。

       这些标记的命名都以IMAGE_FILE开始,清楚起见,接下来我将省略这些前缀。

 

4-3特征字段值

标记*

描述

_RELOCS_STRIPPED

0x0001

只是一个映像文件。这个标记指出文件不包括基本重定位并且必须在其相应的基地址加载。在基地址冲突的情形中,OS加载器会报告一个错误。这个标记不应该用于托管的PE文件的设置。

_EXECUTABLE_IMAGE

0x0002

标记指出了这个文件是一个映像文件(EXE DLL)。这个标记必须用于托管的PE文件的设置。如果没有被设置,这就通常为一个(目标代码)连接器错误。

_LINE_NUMS_STRIPPED

0x0004

COFF行号被移除。这个标记必须用于托管的PE文件的设置,因为它们不需要使用内嵌在PE文件自身的调试信息。取代的,调试信息被保存在伴随的PDBprogram database)文件中。

_LOCAL_SYMS_STRIPPED

0x0008

COFF符号表的本地符号的入口被移除。

这个标记应该用于托管的PE文件的设置,为了在之前给出的入口的原因。

_AGGRESIVE_WS_TRIM

0x0010

Aggressively trim the working set. This flag should not be set for pure-IL managed PE files.

侵略性的修整工作组。这个标记不应该用于纯IL的托管的PE文件的设置。

_LARGE_ADDRESS_AWARE

0x0020

应用程序能够处理超过2GB范围的地址。这个标记不应该用于1.01.1版本的托管的PE文件的设置,但是可以用于2.0版本的文件。

_BYTES_REVERSED_LO

0x0080

Little endian。这个标记应该用于托管的PE文件的设置。

_32BIT_MACHINE

0x0100

机器是基于23位的架构。这个标记通常是由当前版本的代码生成器创建的托管PE文件设置的。可是,2.0以及更新的版本,能够创建64位特殊的映像,这将不会使这个标记被设置。

_DEBUG_STRIPPED

0x0200

调试信息从映像文件中移除。

_REMOVABLE_RUN_FROM_SWAP

0x0400

如果映像文件是可移除的媒体,就从交换文件复制并运行它。这个标记不应该用于纯IL的托管的PE文件的设置。

_NET_RUN_FROM_SWAP

0x0800

If the image file is on a network, copy and run it from the swap file. This flag should not be set for pure-IL managed PE files.

如果映像文件是位于网络上的,就从交换文件复制并运行它。这个标记不应该用于纯IL的托管的PE文件的设置。

_SYSTEM

0x1000

映像文件是一个系统文件(例如,一个设备驱动)。这个标记不应该用于纯IL托管PE文件的设置。

_DLL

0x2000

映像文件是一个DLL而不是一个EXE。它不能直接运行。

_UP_SYSTEM_ONLY

0x4000

托管文件应该只允许运行在一个单处理器(uniprocessor)的机器上。这个标记不应该用于纯IL托管PE文件的设置。

_BYTES_REVERSED_HI

0x8000

Big endian。这个标记不应该用于纯IL托管PE文件的设置。

*第一段“常量”列,省略了前缀IMAGE_FILE_MACHINE_XXX

       这些典型的特征值由现有的PE文件生成器生成——除了由VC++链接器使用,还由所有剩下的Microsoft托管编译器使用,包括ILAsm——对于EXE映像文件而言是0x010EIMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LINE_NUMS_STRIPPED | IMAGE_FILE_LOCAL_SYMS_STRIPPED | IMAGE_FILE_32BIT_MACHINE)。

       对于一个DLL映像文件而言,这个值是0x210EIMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LINE_NUMS_STRIPPED | IMAGE_FILE_LOCAL_SYMS_STRIPPED | IMAGE_FILE_32BIT_MACHINE | IMAGE_FILE_DLL)。

       CLR 2.0版本中,如果这个映像是为64位目标平台而生成的,这些特征值可能没有IMAGE_FILE_32BIT_MACHINE标记。

 

posted @ 2008-08-04 08:47  包建强  Views(1681)  Comments(5Edit  收藏  举报