可移植可执行

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

##### Standard COFF Fields

##### Windows Specific Fields

##### 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 位元組無符號整型。這個欄位指定了映像文件的特徵，並保存了這些二進位標誌的位或運算值。

常見的節：[1] （頁面存檔備份，存於網際網路檔案館）

* .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 調試信息是過時的，因此該欄位為空。

符號表包含了所有符號與元符號的條目。

* 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 位元組開始。示例代碼如下：

#### 行號

每個可執行的節有自己的行號表。節中的每個函數獨立編號，函數的首行 (有左花括號的行) 編號為 1. 每個函數在行號表中有一個條目，其 l\_lnno 為 0, l\_symndx 為符號表中該函數。其後是該函數每一行的條目，l\_lnno 甚至為該函數內的行號 (1..N), l\_paddr 設置為該行的第一條彙編代碼的地址.

為得到絕對行號，需要在符號表中找到該函數的 "beginning of function" symbol (類型 C\_FCN) 為該函數的絕對行號，然後加上函數內的相對行號.

## 參見

* PE infection
* EXE 格式
* 可執行與可鏈接格式
* Mach-O
* A.out
* 可執行文件格式比較
* 可執行文件壓縮
* ar (Unix)
* 應用程式虛擬化

## 外部連結

* 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 PistelliArchive.is 的存檔，存檔日期 2013-01-30
* 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 （頁面存檔備份，存於網際網路檔案館）