可移植可執行

可移植性可執行文件（英語：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