可移植可执行

可移植性可执行文件（英语：Portable Executable，缩写为PE）是一种用于可执行文件、目标文件和动态链接库的文件格式，主要使用在32位和64位的Windows操作系统上。“可移植的”是指该文件格式的通用性，可用于许多种不同的操作系统和体系结构中。PE文件格式封装了Windows操作系统加载可执行程序代码时所必需的一些信息。这些信息包括动态链接库、API导入和导出表、资源管理数据和线程局部存储数据。在Windows NT操作系统中，PE文件格式主要用于EXE文件、DLL文件、.sys（驱动程序）和其他文件类型。可扩展固件接口（EFI）技术规范书中说明PE格式是EFI环境中的标准可执行文件格式。开头为DOS头部。

PE格式是由Unix中的COFF格式修改而来的。在Windows开发环境中，PE格式也称为PE/COFF格式。

在Windows NT操作系统中，PE格式目前支持IA-32、IA-64和x86-64（AMD64/Intel64）的指令系统。在Windows 2000之前，Windows NT还支持MIPS、Alpha和PowerPC的指令系统。由于Windows CE也在使用PE文件格式，因此PE仍然支持几种不同型号的MIPS、ARM（包括Thumb）和SuperH指令系统。

PE文件格式的主要竞争对手是可执行与可链接格式（ELF）（使用于Linux和大多数Unix版本中）和Mach-O（使用于Mac OS X中）。

## 布局结构

### 文件头部

#### MS-DOS头与MS-DOS stub

MS-DOS头和MS-DOS Stub只存在于映像文件中。在MS-DOS下运行该应用程序，默认的Stub会打印出消息"This program cannot be run in DOS mode"。MS-DOS头的偏移位置0x3C处包括指向PE签名的文件指针，用于定位PE的开始位置。

#### COFF file header

PE签名是一个4字节的项：字符P和E，随后是2个空字节。

在Winnt.h中定义的标准COFF头的结构_IMAGE_FILE_HEADER

#### optional header

Object文件不含这部分，所以称为“可选头”。

在Winnt.h中定义的optional header的结构_IMAGE_OPTIONAL_HEADER

##### Data Directories

- Export Table: .edata Section
- Import Table: .idata Section.
- Resource Table: .rsrc Section.
- Exception Table: .pdata Section.
- Certificate Table: 指向Attribute Certificate Table(由用于文件验证的属性证书组成的表). 属性证书不会作为映像文件的一部分加载到内存。同样，这个地址项的第一个字段是文件指针而不是RVA。属性证书表的每个项都包括了一个4字节的文件指针，指向各自的属性证书，并具有4字节的大小。
- Base Relocation Table: .reloc Section
- Debug: .debug Section. 调试数据输出到PDB文件中，因此这个Data directory要么全都是0，或者只指向一个类型为2（IMAGE_DEBUG_TYPE_CODEVIEW）、30个字节的调试目录项，而这个项又指向一个包括PDB文件路径在内的、CodeReview风格的头。
- Architecture: 保留,必须为0
- Global Ptr: 在全局指针寄存器中存储的RVA值。该结构的大小必须设置为0。如果目标架构没有使用全局指针的概念，这个数据目录就全都设置为0（例如I386或AMD64）。
- TLS Table: .tls Section.
- Load Config Table: Load Configuration Structure. 特定于Window NT家族操作系统的数据（例如，GlobalFlag值）。
- Bound Import: 由绑定导入描述符组成的数组，其中的每个描述符都描述了一个DLL。在创建映像的时候，该映像与DLL绑定在一起。描述符中还携带这些绑定的时间戳，如果这些绑定是最新的，那么操作系统加载程序就会使用这些绑定以作为API导入的"快捷方式"。否则，加载程序就会忽视这些绑定并通过导入表解析这些导入API。
- IAT: Import Address Table. 这个表（IAT）会在导出目录表（第1个数据目录）中被引用。
- Delay Import Descriptor: Delay-Load Import Tables. 包括一个由32位ImgDelayDescr结构体组成的数组，每个结构体都描述了延迟加载的导入。延迟加载（delay load）的导入是这样一些DLL，它们被描述为隐式的导入，而作为显式的导入进行加载（通过对LoadLibrary这个API的调用）。动态库的延迟加载是根据需要--在第一次调用这样一个DLL的时候执行的。这与隐式的导入不同，后者在导入的可执行体初始化的时候就立即被加载。
- CLR Runtime Header: .cormeta Section (Object Only).
- 保留: 必须全为0

### 节

节（section）是PE文件中存储数据的划分。通常，加载到内存后，同一节的数据具有相同的内存访问属性（可读/可写/可执行等）。

节表紧随文件头部。由于没有文件头具有直接指向节表的指针，所以节表的位置被计算为文件头的大小再加上1。

COFF头的NumberOfSections字段，定义了节表中节的数量。在节头表中，节的索引是基于1，并且节的顺序是由链接器确定。节按照节表中定义的顺序连续存放，起始RVA根据PE头的SectionAlignment字段值进行对齐。

节头是一个定义在Winnt.h中的40字节的结构IMAGE_SECTION_HEADER:

- Name: 8字节的ASCII字符串。表示节的名称。节名称开始于一个点号（例如，.reloc）。如果节名称正好包含8个字符，就会省略null终结符。如果节名称小于8个字符，就会使用null字符来填充数组Name。映像文件不能使用超过8个字符的节名称。然而，在对象文件中，节名称可以更长一些, 在这种情况下，名称被放置在字符串表中，并且字段的第一个字节中包括了字符"/"，随后是一个ASCII字符串，包含有字符串表中相应偏移量的十进制表示。
- VirtualSize: 4字节无符号整型的Union。在映像文件中，这个字段保存了节中的代码或数据装入内存的实际（未对齐的）字节大小。如果改制大于本节的SizeOfRawData, 本节由0填充. 对于object文件该节为0.
- VirtualAddress: 4字节无符号整型。在映像文件中，为本节装入内存后相对于image base的偏移.
- SizeOfRawData: 4字节无符号整型。在映像文件中，这个字段保存了磁盘上需要初始化的数据的字节大小，向上舍入为FileAlignment的倍数。如果SizeOfRawData小于VirtualSize，那么装入内存时使用0来填充节的剩余部分。对于不需要初始化的数据,这个字段的值为0.
- PointerToRawData: 4字节无符号整型。保存了指向节的第一页的文件指针。在映像文件中，向上舍入为FileAlignment的倍数。对于不需要初始化的数据,这个字段的值为0.
- PointerToRelocations: 4字节无符号整型。这是一个文件指针，指向了节的重定位项的起始位置。在映像文件中，不使用这个字段，应该设置为0。
- PointerToLinenumbers: 4字节无符号整型。这个字段保存了一个文件指针，指向节的行号项的起始位置。在映像文件中, 该字段已经过时了,应设置为0
- NumberOfRelocations: 2字节无符号整型。节的重定位项的数量. 在映像文件中，设置为0。
- NumberOfLinenumbers: 2字节无符号整型. 节的行号条目的数量. 在映像文件中, 该字段已经过时了,应设置为0
- Characteristics: 4字节无符号整型。这个字段指定了映像文件的特征，并保存了这些二进制标志的位或运算值。

常见的节：

- .text: 只读的节，包括了CLR头、元数据、IL代码、托管异常处理信息以及资源。
- .sdata: 可读写的节，与GP相关的已初始化数据
- .reloc: 只读的节，基址重定位表包含了镜像中所有需要重定位的内容。NT头中的数据目录中的Base Relocation Table（基址重定位表）域给出了基址重定位表所占的字节数。基址重定位表被划分成许多块，每一块表示一个4K页面范围内的基址重定位信息，它必须从32位边界开始。
- .rsrc: 只读的节，包括了非托管的资源目录。
- .tls: 可读写的节，包括了TLS数据。
- .bss(Block Start with Symbol)：未初始化全局变量
- .textbss：未初始化的可执行代码节。也即这个节具有可执行属性，在PE文件中未实际占用硬盘文件空间，在加载到内存时未填充数据。这用于支持Visual Studio在Debug模式下动态编译代码功能，也即“Edit and Continue”功能。例如，一个函数在Visual Studio中设断点或单步调试，这时该函数在.text节中；修改源代码后继续执行这个函数，Visual Studio会重新编译这个函数并把它加载到.textbss节中的未利用地址空间（原为padding的部分），并修改对这个函数调用的跳转（jmp）表条目以及当前EIP寄存器值。
- .data 代码节
- .edata 导出表
- .idata 导入表
- .idlsym 包含已注册的SEH，它们用以支持IDL属性
- .pdata 64位程序的异常处理器的地址表 NT头中的Exception Table（异常表）域指向它。
- .rdata 只读的已初始化数据（用于常量）
- .sbss 与GP相关的未初始化数据
- .srdata 与GP相关的只读数据
- .text 默认代码节

#### 符号表

COFF File Header中的字段PointerToSymbolTable给出了符号表地址，字段NumberOfSymbols给出了符号表条目数量。对于映象文件，COFF调试信息是过时的，因此该字段为空。

```C
typedef struct {
  union {
    char e_name[E_SYMNMLEN];
    struct {
      unsigned long e_zeroes;
      unsigned long e_offset;
    } e;
  } e;
  unsigned long e_value;
  short e_scnum;
  unsigned short e_type;
  unsigned char e_sclass;
  unsigned char e_numaux;
} SYMENT;
```

符号表包含了所有符号与元符号的条目。

- e.e_name - 内联的符号名字（小于等于8字节）。
- e.e.e_zeroes - flag，用于判断是内联符号名字还是在字符串表中的符号名字
- e.e.e_offset - 字符串表中的符号名字的偏移值
- e_value - 符号的值。如表示函数的符号，值为函数的地址。还有相对于%ebp的变量地址、寄存器变量的寄存器号、结构成员相对偏移值、枚举成员值、struct/union/enum的尺寸
- e_scnum - 符号所属的节的编号。节表中的节从1开始编号。节号0表示未定义（外部）符号；-1表示绝对符号（e_value是个常量而非地址）；-2表示调试符号。
- e_type - 符号类型。由基类型与派生类型组成，如“指针到整型”。
- e_sclass - 存储类。C_FCN，值101，".bf"或".ef" - 函数的开始/结束。C_FILE，值103，表示函数名。
- e_numaux - 辅助条目（18个字节长）的数量（通常为0或1）。符号表中的符号允许紧随其后有额外的辅助条目。

#### 字符串表

字符串表保存长度大于8的符号。字符串表紧随符号表。首先读出4个字节，为符号表的字节长度。随后的4个字节总为0。到符号表的引用地址总是从这4个0字节开始。示例代码如下：

```C
    int i;
    char *s;
    read(fd, &i, 4);
    s = (char *)malloc(i);
    memset(s, 0, 4);
    read(fd, s+4, i-4);
```

#### 行号

```C
typedef struct {
  union {
    unsigned long l_symndx;  /* function name symbol index */
    unsigned long l_paddr;   /* address of line number     */
  } l_addr;
  unsigned short l_lnno;     /* line number                */
} LINENO;
```

每个可执行的节有自己的行号表. 节中的每个函数独立编号,函数的首行(有左花括号的行)编号为1. 每个函数在行号表中有一个条目, 其l_lnno为0, l_symndx为符号表中该函数. 其后是该函数每一行的条目, l_lnno甚至为该函数内的行号(1..N), l_paddr设置为该行的第一条汇编代码的地址.

为得到绝对行号, 需要在符号表中找到该函数的"beginning of function" symbol (类型C_FCN)为该函数的绝对行号,然后加上函数内的相对行号.

## 外部链接

- Microsoft Portable Executable and Common Object File Format Specification (latest edition, OOXML format)
- Microsoft Portable Executable and Common Object File Format Specification (1999 edition, .doc format)
- The original Portable Executable article by Matt Pietrek (MSDN Magazine, March 1994)
- Part I. An In-Depth Look into the Win32 Portable Executable File Format by Matt Pietrek (MSDN Magazine, February 2002)
- Part II. An In-Depth Look into the Win32 Portable Executable File Format by Matt Pietrek (MSDN Magazine, March 2002)
- The .NET File Format by Daniel Pistelli
- Ero Carrera's blog describing the PE header and how to walk through
- PE Internals provides an easy way to learn the Portable Executable File Format

## 基本资料

- 日语原文：可移植性可执行文件
- 扩展名：.acm, .ax, .cpl, .dll, .drv, .efi, .exe, .mui, .ocx, .scr, .sys, .tsp
- mime：application/vnd.microsoft.portable-executable
- 所有者：Microsoft
- 类型：二进制可执行文件、目标代码、函式库
- 扩展自：DOS MZ可执行文件、COFF