---
# System prepended metadata

title: 程式設計師的自我修養 - 連結、載入、程式庫 Ch5 Windows PE/COFF
tags: [程式設計師的自我修養 - 連結、載入、程式庫, ' 讀書']

---

# Ch5 Windows PE/COFF

[toc]

## 5.1 簡介

- PE 和 ELF 都源自 COFF 格式
- PE: 32bit, PE32+: 64bit
- 和 ELF 一樣 PE 也是基於 section 的格式，程式碼放 `.code` 資料放 `.data`
- 也允許在程式中指定 section 位置，像 gcc 中的 `__attribute__((section("name")))`
```c=
#pragma data_seg("FOO")
int bar = 1;
#pragma data_seg(".data")
```

## 5.2 PE 的前身 - COFF

嘗試在 Windows 編譯 `SimpleSection.c`

```=
cl /c /Za SimpleSection.c
dumpbin /ALL SimpleSection.obj
```

- `/c`: 編譯時不 linking
- `/Za`: 禁用 MSVC C/C++ 的 extension

```
$ dumpbin /SUMMARY SimpleSection.obj
Microsoft (R) COFF/PE Dumper Version 14.38.33133.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file SimpleSection.obj

File Type: COFF OBJECT

  Summary

           4 .bss
          40 .chks64
           C .data
          90 .debug$S
          18 .drectve
          18 .pdata
          64 .text$mn
          10 .xdata
```

### COFF 結構

COFF 格式與 ELF 很像，由檔頭與 Section 組成:
- 檔頭(Image Header)
- Section Header(Section Table)

![image](https://hackmd.io/_uploads/HyGgIKzq6.png)

在 C 中的 struct 由 `IMAGE_FILE_HEADER` 表示，放在 `WinNT.h` 裏頭:

```c=
typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;               // 目標機器
    WORD    NumberOfSections;      // Section 數量
    DWORD   TimeDateStamp;         // 創建時間
    DWORD   PointerToSymbolTable;  // 指向 Symbol Table 的指標
    DWORD   NumberOfSymbols;       // Symbol 的數量
    WORD    SizeOfOptionalHeader;  // Optional Header 的大小，只存在 PE 中，COFF 沒有
    WORD    Characteristics;       // 額外資訊
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
```

可以觀察 `dumpbin` 最開始輸出的訊息，直接對應到了這個 struct 的值

```
Microsoft (R) COFF/PE Dumper Version 14.38.33133.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file SimpleSection.obj

File Type: COFF OBJECT

FILE HEADER VALUES
            8664 machine (x64)
               8 number of sections
        65B503A5 time date stamp Sat Jan 27 21:22:45 2024
             342 file pointer to symbol table
              21 number of symbols
               0 size of optional header
               0 characteristics
```

後面跟著就是 Section Header 的陣列，在 C 中的 struct 是: `IMAGE_SECTION_HEADER`

```c=
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  // 名稱
    union {
            DWORD   PhysicalAddress;        // 物理地址
            DWORD   VirtualSize;            // 載入記憶體後的大小
    } Misc;
    DWORD   VirtualAddress;                 // 虛擬地址
    DWORD   SizeOfRawData;                  // Section 在文件中的大小 (可能和 VirtualSize 不一樣)
    DWORD   PointerToRawData;               // 指向 Section 在文件中的位置           
    DWORD   PointerToRelocations;           // 指向重定位表在文件中的位置
    DWORD   PointerToLinenumbers;           // 指向 COFF line number entries 的指標
    WORD    NumberOfRelocations;            // Relocation entries 的數量
    WORD    NumberOfLinenumbers;            // COFF line number entries的數量
    DWORD   Characteristics;                // section 的資訊，包含 section 的類型、
                                            // 對齊方式、wrx 權限等，這些資訊用 bit flag
                                            // 方式儲存。
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
```
每一個表的 header 後面接著 section 的實際內容，這裡與 ELF 幾乎一樣，唯有兩個 section 只有在 PE 裡面有： `.drectve` 和 `.debug$S`。

> https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header

## 5.3 `.drectve` Linking directive message

```=
SECTION HEADER #1
.drectve name
       0 physical address
       0 virtual address
      18 size of raw data
     154 file pointer to raw data (00000154 to 0000016B)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
  100A00 flags
         Info
         Remove
         1 byte align

RAW DATA #1
  00000000: 20 20 20 2F 44 45 46 41 55 4C 54 4C 49 42 3A 22     /DEFAULTLIB:"
  00000010: 4C 49 42 43 4D 54 22 20                          LIBCMT" 

   Linker Directives
   -----------------
   /DEFAULTLIB:LIBCMT
```

`.drectve` 為 "Directive" 縮寫，儲存 compiler 告訴 linker 如何 linking object file。 內容包含了 section 的地址、長度、位置、和 flag ，即 `IMAGE_SECTION_HEADER` 中的 `Characteristics` 成員，上面範例的值是 `0x100A00` 其意義如 5-2 所示:

![image](https://hackmd.io/_uploads/rJF5e9fqa.png)


接著就是 `RAW DATA #1`，這是原始數據， dumpbin 解析出來的訊息為 "/DEFAULTLIB:'LIBCMT'"， linker 就要去 lib 下面找出 libcmt.lib，並 link 進來。linker 拿到 SimpleSection.obj 時看到這個訊息，他就會自動加上 /DEFAULT:'LIBCMT' 到 linker 參數中。

> LIBCMT 是 Library C Multithreaded 的縮寫，代表 MSVC 的靜態連接的多線程 CRT


## 5.4 Debug Info

COFF 中所有以 `.debug` 開頭的 section 都包含 debug 訊息。

- `.debug$S`: 符號(Symbol) 相關的 debug 訊息
- `.debug$P`: Precompiled Header 相關的 debug 訊息
- `.debug$T`: 類型(Type) 相關的 debug 訊息

從 SimpleSection 的 dumpbin 中可以看到，只有 `.debug$S`，可以從中看到目標文件的原始路徑、編譯器資訊等

```=
SECTION HEADER #2
.debug$S name
       0 physical address
       0 virtual address
      90 size of raw data
     16C file pointer to raw data (0000016C to 000001FB)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42100040 flags
         Initialized Data
         Discardable
         1 byte align
         Read Only

RAW DATA #2
  00000000: 04 00 00 00 F1 00 00 00 83 00 00 00 45 00 01 11  ....?......E...
  00000010: 00 00 00 00 43 3A 5C 55 73 65 72 73 5C 72 6F 79  ....C:\Users\roy
  00000020: 34 38 30 31 5C 44 65 73 6B 74 6F 70 5C 50 72 6F  4801\Desktop\Pro
  00000030: 67 72 61 6D 5C 74 6D 70 5C 4C 69 6E 6B 69 6E 67  gram\tmp\Linking
  00000040: 5C 53 69 6D 70 6C 65 53 65 63 74 69 6F 6E 2E 6F  \SimpleSection.o
  00000050: 62 6A 00 3A 00 3C 11 00 62 00 00 D0 00 13 00 26  bj.:.<..b..?..&
  00000060: 00 6D 81 00 00 13 00 26 00 6D 81 00 00 4D 69 63  .m.....&.m...Mic
  00000070: 72 6F 73 6F 66 74 20 28 52 29 20 4F 70 74 69 6D  rosoft (R) Optim
  00000080: 69 7A 69 6E 67 20 43 6F 6D 70 69 6C 65 72 00 00  izing Compiler..
```

> 延伸: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-debug-section

## 5.5 大家都有符號表

COFF 符號表，基本上和 ELF 的一樣，包含了符號名稱、類型、位置等

```=
COFF SYMBOL TABLE
000 0104816D ABS    notype       Static       | @comp.id
001 80010190 ABS    notype       Static       | @feat.00
002 00000002 ABS    notype       Static       | @vol.md
003 00000000 SECT1  notype       Static       | .drectve
    Section length   18, #relocs    0, #linenums    0, checksum        0
005 00000000 SECT2  notype       Static       | .debug$S
    Section length   90, #relocs    0, #linenums    0, checksum        0
007 00000000 SECT3  notype       Static       | .data
    Section length    C, #relocs    0, #linenums    0, checksum AC5AB941
009 00000000 SECT3  notype       External     | global_init_var
00A 00000004 UNDEF  notype       External     | global_uninit_var
00B 00000000 SECT4  notype       Static       | .text$mn
    Section length   64, #relocs    5, #linenums    0, checksum  D696A53
00D 00000000 UNDEF  notype ()    External     | printf
00E 00000000 SECT4  notype ()    External     | func1
00F 00000030 SECT4  notype ()    External     | main
010 00000000 SECT4  notype       Label        | $LN3
011 00000030 SECT4  notype       Label        | $LN3
012 00000000 SECT5  notype       Static       | .xdata
    Section length   10, #relocs    0, #linenums    0, checksum 434E1581
014 00000000 SECT5  notype       Static       | $unwind$func1
015 00000000 SECT6  notype       Static       | .pdata
    Section length   18, #relocs    6, #linenums    0, checksum 5710F00F
017 00000000 SECT6  notype       Static       | $pdata$func1
018 00000008 SECT5  notype       Static       | $unwind$main
019 0000000C SECT6  notype       Static       | $pdata$main
01A 00000004 SECT3  notype       Static       | $SG7474
01B 00000008 SECT3  notype       Static       | ?static_var@?1??main@@9@9 (`main'::`2'::static_var)
01C 00000000 SECT7  notype       Static       | .bss
    Section length    4, #relocs    0, #linenums    0, checksum        0
01E 00000000 SECT7  notype       Static       | ?static_var2@?1??main@@9@9 (`main'::`2'::static_var2)
01F 00000000 SECT8  notype       Static       | .chks64
    Section length   40, #relocs    0, #linenums    0, checksum        0
```

- 每一行從左邊依序是：
    1. symbol 的 index
    2. symbol 大小
    3. 符號所在的位置：
        - `ABS`：表示符號不存在任何一個 section
        - `SECT1`：等同 SECTION #1，表示這個符號存在於第一個 section
        - `UNDEF`：未定義
    4. 符號類型
        - `notype`: 變數和其他類型
        - `notype()`: funciton
    5. 符號的可見範圍：
        - `static`：local variable，只有 object file 的內部可見
        - `external`：global variable，可以被其他 object file 引用
    6. 符號名稱 

- 怎麼看這張表?
    - 例如 `global_uninit_var`
    ```
    00A 00000004 UNDEF  notype       External     | global_uninit_var
    ```
    - `00A`: 第10個變數
    - `00000004`: 大小為 4 bytes
    - `UNDEF`: 不存在於任何一個 section
    - `notype`: global_uninit_var 是一個變數
    - `External`: 可以被其他的 object file 引用
    - `global_uninit_var`: 這個變數的名字


- 比較特殊的是 `$SG7474`
    - 其實是指向 `"%d\n"` 的符號，但因為 string constant 沒有名字所以編譯器自動產生了一個
    - ELF 並沒有這種符號
    ```
    SECTION HEADER #3
       .data name
           0 physical address
           0 virtual address
           C size of raw data
         1FC file pointer to raw data (000001FC to 00000207)
           0 file pointer to relocation table
           0 file pointer to line numbers
           0 number of relocations
           0 number of line numbers
    C0300040 flags
             Initialized Data
             4 byte align
             Read Write

    RAW DATA #3
      00000000: 54 00 00 00 25 64 0A 00 55 00 00 00              T...%d..U...
    ```

- 指向段的符號
    - `dumpbin` 還會解析該符號指向的 section 資訊
    ```
    007 00000000 SECT3  notype       Static       | .data
        Section length    C, #relocs    0, #linenums    0, checksum AC5AB941
    ```

## 5.6 PE format

PE format 是基於 COFF 的擴展，跟 COFF 最大的差別在多了: DOS MZ Header 和 stub、原本的 `IMAGE_FILE_HEADER` 變成 `IMAGE_NT_HEADER`。

![image](https://hackmd.io/_uploads/r1PHEFXca.png)

> DOS 和 Windows 的程式 extension 都是 `.exe`，但 DOS 的執行檔格式是 `MZ`，PE 為了向後相容，所以檔頭就有 Image DOS Header 和 Image DOS Stub，這部分和 MZ 格式是一樣的(為了讓 DOS 也可以打開)
> 但 PE 所產生的 `IMAGE_DOS_HEADER` 其中的 `e_cs` 和 `e_ip` (EntryPoint address) 其實指向 Image DOS Stub，裡面包含了一小段程式，只用來輸出 `This program cannot be run in DOS`。

```c=
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
```

`IMAGE_DOS_HEADER` 裡面大多數成員都不重要，只有 `e_lfanew` 代表 PE 檔頭(`IMAGE_NT_HEADERS`) 在檔案中的偏移。
如果該 field 為 `0` 代表他是 DOS MZ；不為 `0` 代表是 Windows PE。

`IMAGE_NT_HEADERS` 是真正的 PE 檔頭，包含了一個 magic 和兩個 struct，`Signature` 永遠是 `"PE\x00\x00"`(`0x00004550`)，再來是 `IMAGE_FILE_HEADER` 和 PE擴展檔頭`IMAGE_OPTIONAL_HEADER`。

```c=
typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
```

`IMAGE_FILE_HEADER` 與 COFF 一樣，`IMAGE_OPTIONAL_HEADER` 就是 PE 的檔頭部分。

:::spoiler `IMAGE_OPTIONAL_HEADER32`
```c=
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;
```
:::
:::spoiler `IMAGE_OPTIONAL_HEADER64`
```c=
typedef struct _IMAGE_OPTIONAL_HEADER64 {
    WORD        Magic;
    BYTE        MajorLinkerVersion;
    BYTE        MinorLinkerVersion;
    DWORD       SizeOfCode;
    DWORD       SizeOfInitializedData;
    DWORD       SizeOfUninitializedData;
    DWORD       AddressOfEntryPoint;
    DWORD       BaseOfCode;
    ULONGLONG   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;
    ULONGLONG   SizeOfStackReserve;
    ULONGLONG   SizeOfStackCommit;
    ULONGLONG   SizeOfHeapReserve;
    ULONGLONG   SizeOfHeapCommit;
    DWORD       LoaderFlags;
    DWORD       NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
```
:::

- MSVC 用 `_WIN64` 這個 macro 來切換 image header 的版本。
```c=
#ifdef _WIN64
typedef IMAGE_OPTIONAL_HEADER64             IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER64            PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC         IMAGE_NT_OPTIONAL_HDR64_MAGIC
#else
typedef IMAGE_OPTIONAL_HEADER32             IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER32            PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC         IMAGE_NT_OPTIONAL_HDR32_MAGIC
#endif
```

:::info
這本書沒有很詳細講每個成員的意義，這章節只講和 static linking 相關的
:::

### 5.6.1 PE Data Directory

對於 Windows 的 Loader 來說，在載入 PE 時，會去看 `IMAGE_OPTIONAL_HEADER` 裏頭的叫做 `DataDirectory` 的成員，裡面記載了關於 Load 所需要的資料結構: 導入表、導出表、資源、重定位表等。

```c=
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16
```

這個陣列每個 Entry 代表的意義都定義好了，例如第 `0` 個就是 `IMAGE_DIRECTORY_ENTRY_EXPORT`，也就是導出表(Export Table)所在的地址和大小。

```c=
// Directory Entries

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
```

:::info
這本書沒有很詳細講每個成員的意義
:::


## Links

https://courses.cs.washington.edu/courses/cse378/03wi/lectures/LinkerFiles/coff.pdf






