可移植可執行

可移植性可執行檔案（英語：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 中）。

佈局結構

1.1 檔案頭部

1.1.1 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 的開始位置。

1.1.2 COFF file header

PE 簽名是一個 4 位元組的項：字元 P 和 E，隨後是 2 個空位元組。

在 Winnt.h 中定義的標準 COFF 頭的結構IMAGEFILE_HEADER

1.1.3 optional header

Object 檔案不含這部分，所以稱為 “可選頭”。

在 Winnt.h 中定義的 optional header 的結構IMAGEOPTIONAL_HEADER

1.1.3.1 Standard COFF Fields

1.1.3.2 Windows Specific Fields

1.1.3.3 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（IMAGEDEBUGTYPE_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

1.2 節

節（section）是 PE 檔案中儲存資料的劃分。通常，載入到記憶體後，同一節的資料具有相同的記憶體訪問屬性（可讀 / 可寫 / 可執行等）。

節表緊隨檔案頭部。由於沒有檔案頭具有直接指向節表的指標，所以節表的位置被計算為檔案頭的大小再加上 1。

COFF 頭的 NumberOfSections 欄位，定義了節表中節的數量。在節頭表中，節的索引是基於 1，並且節的順序是由連結器確定。節按照節表中定義的順序連續存放，起始 RVA 根據 PE 頭的 SectionAlignment 欄位值進行對齊。

節頭是一個定義在 Winnt.h 中的 40 位元組的結構 IMAGESECTIONHEADER:

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 預設程式碼節

1.2.1 符號表

COFF File Header 中的欄位 PointerToSymbolTable 給出了符號表地址，欄位 NumberOfSymbols 給出了符號表條目數量。對於映象檔案，COFF 除錯資訊是過時的，因此該欄位為空。

typedef struct {

union {

char enameESYMNMLEN;

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 的尺寸
escnum - 符號所屬的節的編號。節表中的節從 1 開始編號。節號 0 表示未定義（外部）符號；-1 表示絕對符號（evalue 是個常量而非地址）；-2 表示除錯符號。
e_type - 符號型別。由基型別與派生型別組成，如 “指標到整型”。
esclass - 儲存類。CFCN，值 101，".bf" 或 ".ef" - 函式的開始 / 結束。C_FILE，值 103，表示函式名。
e_numaux - 輔助條目（18 個位元組長）的數量（通常為 0 或 1）。符號表中的符號允許緊隨其後有額外的輔助條目。

1.2.2 字串表

字串表儲存長度大於 8 的符號。字串表緊隨符號表。首先讀出 4 個位元組，為符號表的位元組長度。隨後的 4 個位元組總為 0。到符號表的引用地址總是從這 4 個 0 位元組開始。示例程式碼如下：

int i;

char *s;

read (fd, &i, 4);

s = (char *) malloc (i);

memset (s, 0, 4);

read (fd, s+4, i-4);

1.2.3 行號

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. 每個函式在行號表中有一個條目，其 llnno 為 0, lsymndx 為符號表中該函式。其後是該函式每一行的條目，llnno 甚至為該函式內的行號 (1..N), lpaddr 設定為該行的第一條彙編程式碼的地址.

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