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


    返回目录

    PE

           PE头,紧跟在COFF头的后面,提供了OS加载器的信息。虽然这个头被称为可选择的头(optional header),它只是可选择的,在某种意义上是说,对象文件通常不包括它。对于PE文件而言,这个头是强制性的。

           PE文件的大小是不固定的。它取决于定义在头中的数据目录的数量,并由COFF头中的SizeOfOptionalHeader字段详细指明。定义在Winnt.h中的PE头的结构如下:

    typedef struct _IMAGE_OPTIONAL_HEADER {
         // Standard fields
         WORD    Magic;
         BYTE    MajorLinkerVersion;
         BYTE    MinorLinkerVersion;
         DWORD   SizeOfCode;
         DWORD   SizeOfInitializedData;
         DWORD   SizeOfUninitializedData;
         DWORD   AddressOfEntryPoint;
         DWORD   BaseOfCode;
         DWORD   BaseOfData;
         // NT additional fields
         DWORD   ImageBase;
         DWORD   SectionAlignment;
         DWORD   FileAlignment;
         WORD    MajorOperatingSystemVersion;
         WORD    MinorOperatingSystemVersion;
         WORD    MajorImageVersion;
         WORD    MinorImageVersion;
         WORD    MajorSubsystemVersion;
         WORD    MinorSubsystemVersion;
         DWORD   Win32VersionValue;
         DWORD   SizeOfImage;
         DWORD   SizeOfHeaders;
         DWORD   CheckSum;
         WORD    Subsystem;
         WORD    DllCharacteristics;
         DWORD   SizeOfStackReserve;
         DWORD   SizeOfStackCommit;
         DWORD   SizeOfHeapReserve;
         DWORD   SizeOfHeapCommit;
         DWORD   LoaderFlags;
         DWORD   NumberOfRvaAndSizes;
         IMAGE_DATA_DIRECTORY 
         DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] ;
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

    4-4 描述了PE头的字段

    4-4 PE头字段

    偏移量 32/64

    大小 32/64

    字段名

    描述

    0

    2

    Magic

    “魔数(Magic number)”,识别了映像文件的状态。对于32PE文件而言,可采用的值是0x010B;对于64PE文件而言,则是0x020B;对于一个ROM映像文件而言,则是0x107。托管的PE文件必须有设置为0x010B0x020B的字段(只适于64位映像文件的2.0或更后期的版本)。

    2

    1

    MajorLinkerVersion

    连接器的主版本。VC++连接器设置这个字段为8;被其它编译器使用的纯IL文件生成器做着同样的事情。在早期的版本中,这个字段被相应的设置67

    3

    1

    MinorLinkerVersion

    连接器的次版本

    4

    4

    SizeOfCode

    代码区段(.text)的大小,或者如果这些多样的代码区段存在的话,就是所有这些代码区段的总合。IL编译器总是发布一个简单的代码区段。

    8

    4

    SizeOfInitializedData

    初始化数据区段的大小(保存在相应的section头的SizeOfRawData字段中)或者所有这些section的总合。这个初始化的数据定义为一个特定的数值,存储在磁盘的映像文件中。

    12

    4

    SizeOfUninitializedData

    未初始化的数据区段(.bss)的大小或所有这样的区段的总合。这个数据并不是磁盘文件的一部分并且不具备明确的值,但是在这个文件被加载时,OS加载器会为这个数据提交内存空间。

    16

    4

    AddressOfEntryPoint

    RVA的入口点方法。对于非托管的PE文件,这个值可以是0。对于托管的PE文件,这个值总是指向CLR调用的stub

    20

    4

    BaseOfCode

    文件代码区段的开始部分的RVA

    24/-

    4/-

    BaseOfData

    文件数据区段的开始部分的RVA。这个入口不存在于64位可选择的头中。

    28/24

    4/8

    ImageBase

    映像的首选起始虚拟地址;必须排列在64KB的边界上(0x10000)。在ILAsm中,这个字段可以通过指令.imagebase<integer value>/或命令行选项/BASE=<integer value>显示的指定。这个命令行选项优先于指令。

    32

    4

    SectionAlignment

    区段加载到内存后的对齐值。。这个设置必须大于等于FileAlignment字段的值。默认为内存页的大小。

    36

    4

    FileAlignment

    在磁盘映像 区段的对齐值。这个值应该是2的幂,从51264000(从0x2000x10000)。如果SectionAlignment被设置为小于内存页的大小,FileAlignment必须与SectionAlignment相匹配。在ILAsm中,这个字段由指令.file alignment <integer value>/或命令行选项/ALIGNMENT=<integer value>显示指明。这个命令行选项优先于指令。

    40

    2

    MajorOperatingSystemVersion

    所需操作系统的主版本号。

    42

    2

    MinorOperatingSystemVersion

    所需操作系统的次版本号。

    44

    2

    MajorImageVersion

    应用程序的主版本号。

    46

    2

    MinorImageVersion

    应用程序的次版本号。

    48

    2

    MajorSubsystemVersion

    子系统的主版本号。

    50

    2

    MinorSubsystemVersion

    子系统的次版本号。

    52

    4

    Win32VersionValue

    保留的。

    56

    4

    SizeOfImage

    映像文件的大小(按字节),包括了所有的头。这个字段必须被设置为SectionAlignment值的若干倍。

    60

    4

    SizeOfHeaders

    MS-DOS头和stubCOFF头、PE头和section头的大小总合,成上舍入为FileAlignment值的若干倍。

    64

    4

    CheckSum

    磁盘映像文件的Checksum

    68

    2

    Subsystem

    需要运行这个映像文件的用户接口子系统。这个值定义在Winnt.h中,如下:

    NATIVE(1):不需要子系统(例如,一个设备驱动)。

    WINDOWS_GUI(2) :运行在Windows GUI子系统上。

    WINDOWS_CUI(3):运行在Windows控制台模式。

    OS2_CUI (5):运行在OS/2 1.x控制台模式。

    POSIX_CUI (7):运行在POSIX控制台模式。

    NATIVE_WINDOWS (8):映像文件是一个本地的Win9x驱动。

    WINDOWS_CE_GUI (9):运行在Windows CE GUI子系统上。

    ILAsm中,这个字段由指令.subsystem<integer value>/或命令行选项/SUBSYSTEM=<integer value>显示指明。这个命令行选项优先于指令。

    70

    2

    DllCharacteristics

    在映像文件的1.0版本中,总是被设置为0。在托管文件的1.1或更新版本中,总是被设置为0x400:没有非托管的Windows结构化异常处理。

    72

    4/8

    SizeOfStackReserve

    用于初始线程栈的虚拟内存大小。只有SizeOfStackCommit字段(所指定大小的内存)被提交了,余下部分会随着使用逐页增加。对于32位映像的默认值为1MB,对于64位映像的则为4MB。在ILAsm中,这个字段由指令.stackreserve <integer value>/或命令行选项/STACK=<integer value>显示指明。这个命令行选项优先于指令。

    76/80

    4/8

    SizeOfStackCommit

    初始提交于初始线程栈的虚拟内存大小。对于32位映像默认为一页(4KB),对于64位映像默认为16KB

    80/88

    4/8

    SizeOfHeapReserve

    用于初始进程堆的虚拟内存大小。只有SizeOfHeapCommit字段被提交;剩下的在一页的增加中是有效的。对于32位和64位映像默认值都是1MB

    84/96

    4/8

    SizeOfHeapCommit

    初始提交给进程堆的虚拟内存大小。对于32位映像默认为4KB(一个操作系统的内存页),对于64位映像默认为2KB

    88/104

    4

    LoaderFlags

    已经被废弃,设置为0

    92/108

    4

    NumberOfRvaAndSizes

    DataDirectory数组中的入口数量,至少为16。虽然理论上发布大于16个数据目录是可能的,但是所有现有的托管编译器严格地发布16个数据目录,伴随着第16个(最后一个)数据目录从来不使用(保留的)。

    包包译注:魔数(Magic number),一个不能提供任何额外信息的数字,当你看到它时,会感到“莫名其妙”。因此,判定一个数字是否是magic number的依据,是它的出现是否能提供足够的信息让你理解其所指代的行为和场景。

    数据目录表

    数据目录表开始于一个32PE头的96个偏移量处和64PE头的112个偏移量处。数据目录表中的每一个入口都包括了RVA,以及这个独特的数据目录表所描述的表或字符串的大小;这些信息由操作系统使用。数据目录表入口是一个定义在Winnt.h中的8字节结构,如下:

    typedef struct _IMAGE_DATA_DIRECTORY {

    DWORD   VirtualAddress;

    DWORD   Size;

    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

    然而,第一个字段,命名为VirtualAddress的那个,不是一个虚拟地址,而是一个RVA。在这个表给出的RVA并没有必要指向一个section的起始部分,而这个包含特殊表的section也没有必要有特殊的名称。第二个字段是字节大小。

    在数据目录表中定义了16种数据目录表:

    1. 导出型目录表地址和大小。这个表包括了4种其它的表的信息,这些表保存了描述PE文件的非托管导出型数据。在这些托管的编译器中,只有VC++链接器和ILAsm是可以暴露托管方法的,它们被托管的PE文件导出为非托管的导出,由非托管的调用器使用。参见第18章获取更多细节。
    2. 导入型目录表地址和大小:这个表包括了由PE文件使用的非托管导入型数据。在这些托管的编译器中,只有VC++链接器才很需要这个表,导入这个非托管的外部方法,这个方法使用在非托管本地代码中,而这些代码则内嵌在当前的托管PE文件中。其他编译器——包括IL编译器,并不会内嵌这些非托管的本地代码到托管的PE文件中,因此由这些编译器生成的文件的导入表包含着一个单一的入口,而这就是CLR的入口函数。
    3. 资源表地址和大小:这个表包含内嵌在PE文件中的非托管资源,托管资源并不是这个数据的一部分。
    4. 异常表地址和大小:这个表只包含非托管的异常信息。
    5. 证书表地址和大小:这个地址入口指向一个由属性证书(attribute certificate)组成的表(用于文件验证),这个表不会作为映像文件的一部分被加载到内存。同样的,这个入口的第一个字段是一个文件指针而不是一个RVA。这个表的每个入口包括了一个4字节的文件指针,指向各自的属性证书,并具有4字节的大小。
    6. 基本重定位(Base Relocation)地址和大小:这将在本章后面一些详细讨论,参见“重定位”章节。
    7. 调试数据地址和大小:一个托管的PE文件并没有携带内嵌的调试数据;调试数据发布在一个PDB文件中。因此这种数据目录也是全都为0的,或者指向一个类型为2IMAGE_DEBUG_TYPE_CODEVIEW)的单一的30字节调试目录的入口,这将依次指向一个CodeView样式的头,包括着指向PDB文件的路径。IL编译器和C#VB.NET编译器将这些数据发布到.text区段中。
    8. 架构数据地址和大小:特定于架构的数据。这个数据目录(全都设置为0)没有用于I386IA64AMD64架构。
    9. 全局指针:存储在全局指针寄存器的RVA值。这个大小必须被设置为0。如果目标架构没有使用全局指针的概念,这个数据目录就全都被设置为0(例如I386AMD64)。
    10. TLS地址和大小:在托管编译器中,只有VC++链接器和IL编译器能够生成这些使用了TLSthread storage data)数据的代码。
    11. 加载配置表地址和大小:特定于Window NT家族操作系统的数据。(例如,GlobalFlag值)
    12. 绑定导入型表地址和大小:这个表是一个由绑定导入型描述符组成的数组,它们中的每一个都描述了一个DLL。这个映像与DLL在创建映像的时候密切相关。描述符还携带了绑定的时间戳,同时如果这种绑定是现时的,这个OS加载器将这些绑定设置为API导入的一个捷径。否则,加载器忽视这些绑定并通过这个导入表解决这些输入型的API
    13. 导入型地址表地址和大小:这个表(IAT)会在导出目录表中被引用到(数据目录1)。
    14. 延迟导入型描述符地址和大小:包括一个32ImgDelayDescr结构的数组,每个结构描述了一个延迟加载的导入。延迟加载的导入是这样一些DLL,它们被描述为隐式的导入,而被加载为显示的导入(通过对LoadLibrary这个API的调用)。动态库的延迟加载是按需执行的——在第一次调用这个DLL的时候。这就不同于隐式的导入,后者在导入的可执行体初始化的时候就立即被加载。
    15. CLR头地址和大小:CLR头结构将会在这一章的后面详细描述(参见“CLR头”)。
    16. 保留的:全都设置为0

    Section

    Section头的表必须紧跟在PE头后面。由于没有文件头直接地指向这个Section表,这个表的位置被计算为这些文件头的全部大小加上1

    COFF头的NumberOfSections字段,定义了section头这个表中入口的数量。Section头在这个表中的索引是从0开始的,伴随着由链接器定义的section顺序。这些Section按照Section头表中定义的顺序,一个接一个地连续地存放,(正如你所知道的那样)起始RVA对齐到PE头的SectionAlignment字段所指定的值。

    Section头是定义在Winnt.h中的一个40字节的结构体,如下:

    typedef struct _IMAGE_SECTION_HEADER {
         BYTE    Name[
    8];
         union {
         DWORD   PhysicalAddress;
         DWORD   VirtualSize;
    } Misc;
         DWORD   VirtualAddress;
         DWORD   SizeOfRawData;
         DWORD   PointerToRawData;
         DWORD   PointerToRelocations;
         DWORD   PointerToLinenumbers;
         WORD    NumberOfRelocations;
         WORD    NumberOfLinenumbers;
         DWORD   Characteristics;
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

        这些字段包含在IMAGE_SECTION_HEADER结构中,如下:

    Name8字节ASCII字符串):表示section的名称。Section名称开始于一个点(例如,.reloc)。如果Section名称包含正好8个字符,这个空的休止符就会被省略。如果Section名称少于8个字符,这个名称的数组就会以空字符来填充。映像文件不能有多于8个字符的section名称。然而,在对象文件中,Section名称可以再长一些。(想象一个冗长的文件生成器发布一个名为.myownsectionnobodyyelsecouldevergrokSection。)在这种情形中,名称被放置在字符串表中,而字段包括了字符“/”在第一个字节中,紧跟着是一个ASCII字符串——包括了在字符串表中相应偏移量的一个十进制表示值。

    PhysicalAddress/ VirtualSize4字节无符号整型):在映像文件中,这个字段保存了这一section中的代码或数据的准确(未对齐的)字节大小。

    VirtualAddress4字节无符号整型):不管它的名称是什么,这个字段保存了section开始部分的RVA

    SizeOfRawData4字节无符号整型):在一个映像文件中,这个字段保存了磁盘上初始数据的字节大小,将大量在PE头中详细指出的FileAlignment值聚拢起来。如果SizeOfRawData小于VirtualSize,这个section的剩余部分被空字节填充——当它在内存上展开的时候。

    PointerToRawData4字节无符号整型):这个字段保存了指向Section的第一页的一个文件指针。在映像文件中,这个值应该是大量在PE头文件中详细指定的FileAlignment值。

    PointerToRelocations4字节无符号整型):这是一个指向Section的重定位入口起始位置的文件指针。在映像文件中,这个字段不再使用并应该被设置为0

    PointerToLinenumbers4字节无符号整型):这个字段保存了指向Section的行号入口起始位置的一个文件指针。在托管PE文件中,COFF行号被剥去了,而这个字段必须被设置为0

    NumberOfRelocations2字节无符号整型):在托管的映像文件中,这个字段应该被设置为0

    NumberOfLinenumbers2字节无符号整型):在托管的映像文件中,这个字段应该被设置为0

    Characteristics4字节无符号整型):这个字段详细指明了映像文件的特性,并保存了这些二进制标记的位或运算值,如表4-5所描述。

    这些section的特性标记定义在Winnt.h中。其中一些标记是保留的,而一些是只相对于对象文件的。表4-5列出了这些对PE文件有效的标记。所有标记的名称以IMAGE_SCN开始,我将会和通常一样将其忽略;换句话说,IMAGE_SCN_SCALE_INDEX将会变成_SCALE_INDEX

    4-5 PE文件中Section的特性标记

    标记

    描述

    _SCALE_INDEX

    0x00000001

    TLS描述符的表索引是依比例决定的。

    _CNT_CODE

    0x00000020

    Section包括了可执行的代码。在IL编译器生成的PE文件中,只有.text这个section能够携带这种标记。

    _CNT_INITIALIZED_DATA

    0x00000040

    Section包括了初始化的数据。

    _CNT_UNINITIALIZED_DATA

    0x00000080

    Section包括了未初始化的数据。

    _LNK_INFO

    0x00000200

    Section包括了评论或一些其它类型的辅助信息。

    _NO_DEFER_SPEC_EXC

    0x00004000

    在这个sectionTLBtranslation look aside buffer)入口中,重新设置暂定的异常处理位。

    _LNK_NRELOC_OVFL

    0x01000000

    Section包括扩展的重定向。

    _MEM_DISCARDABLE

    0x02000000

    Section可以按需被废弃

    _MEM_NOT_CACHED

    0x04000000

    Section能够被缓存。

    _MEM_NOT_PAGED

    0x08000000

    Section不能被分页。

    _MEM_SHARED

    0x10000000

    Section可以在内存中共享。

    _MEM_EXECUTE

    0x20000000

    Section可以作为代码被执行。在IL编译器生成的PE文件中,只有.text这个section能够携带这种标记。

    _MEM_READ

    0x40000000

    Section可以被读取。

    _MEM_WRITE

    0x80000000

    Section可以被写入。在由IL编译器生成的PE文件中,只有.sdata.tls 这些section能够携带这种标记。

    下面的标记是不允许出现在section的托管文件中的:IMAGE_SCN_SCALE_INDEXIMAGE_SCN_NO_DEFER_SPEC_EXCIMAGE_SCN_LNK_NRELOC_OVFLIMAGE_SCN_MEM_SHARED

    IL编译器在PE文件中生成下面的section

    .text:一个只读的section,包括了CLR头、元数据、IL代码、托管异常处理信息以及资源

    .sdata:一个可读写的section,包括了数据

    .reloc:一个只读的section,包括了重定位

    .rsrc:一个只读的section,包括了非托管的资源

    .tls:一个可读写的section,包括了TLS数据

     

  • 相关阅读:
    正则表达式复习 (?<=) (?=)
    HTML 30分钟入门教程
    C# 多线程详解
    C# List
    C# 枚举
    C# 线程数
    C# 泛型2
    C# 泛型
    C# 结构体
    不用Google Adsense的84个赚钱方法
  • 原文地址:https://www.cnblogs.com/Jax/p/1259492.html
Copyright © 2020-2023  润新知