# Ch6 可執行文件的裝載與進程
[toc]
## 6.1 Process Virtual Address Space
每個程式執行起來後,都有自己的虛擬地址空間(Virtual Address Space),大小受限於 CPU 的尋址上限,即 CPU 的位寬 (32bit or 64bit),對於 32bit CPU 來說,虛擬地址的位置就是 `0x00000000 ~ 0xFFFFFFFF`;對於 64 bit CPU 來說,就是 `0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF` (約 16 EB)。
在 C 語言中,pointer 的大小可以推測出虛擬地址空間的大小,32bit 下就是 4byte;64bit 下就是 8byte。
OS 為了達到監控 process 的目的,不讓 process 任意使用記憶體,包含大小以及位置。如果 process 讀取了未經授權的記憶體位置,在 Windows 下就是 Access Violation、Linux 下就是 Segmentation fault。

32-bit 程式整個能使用的虛擬記憶體 4GB 的空間被劃分成兩個部分: `0xC0000000 ~ 0xFFFFFFFF` 是作業系統、`0x00000000 ~ 0xBFFFFFFF` 是 User Process。
所以原則上 32-bit 程式最多只能用 3GB 的記憶體,但對於一些應用來說不夠用,當然升級成 64-bit 是方法,但還有一種 PAE(Physical Address Extension)的處理器擴展。
### PAE (Physical Address Extension)
程式的 virtual address 還是 4GB 因為受限於尋址的 pointer 大小(32bit, 4byte),但程式使用的實際記憶體空間卻可以超過 4GB,因為 CPU 實際的物理地址大小比 32 大。(Intel 從 1995 的 Pentium Pro 就使用 36-bit 的物理地址)
實際上 proecess 可以透過一個窗口向 OS alloc 和 mapping 等去取用那些多出來的記憶體,例如說:program 0x10000000 ~ 0x20000000 (256MB) 的虛擬地址作為窗口,並且可以依據需求切換 0x10000000 ~ 0x20000000 所 mapping 到的位置。
透過 page 的機制,可以使程式訪問更多的物理記憶體,而這種技術 Intel 叫做 PAE (Physical Address Extension),實體記憶體上限從 4GB 變成 64GB。
## 6.2 Loading 的方式
最簡單的方法就是把整個程式讀進記憶體中,但是過研究發現可以有更好的方式,只要留下需要的部分即可。
- 常見的兩種動態裝載的方法
- 覆蓋裝載 Overlay
- 分頁 Paging
### 6.2.1 Overlay 覆蓋裝載
在 Virtual Memory 發明前的技術,現在幾乎被淘汰,只有在嵌入式的環境還有可能用

覆蓋裝載就是將記憶體分配交給寫程式的人,寫程式必須手動將程式分割成好幾塊,然後寫一個 Overlay Manager 來管理這些代碼塊甚麼時候要留在 memory,什麼時候要換掉。
上圖 A 和 B 之間不會有相互調用,所以我們可以讓他們共用同一塊記憶體,當程式呼叫到 A 時,Overlay Manager 會將 A 載入到記憶體,直接覆蓋掉,反之亦然。
> 就像 C 語言的 `union` 那樣

實際情況當然很複雜,對於這個樹上任意模塊到 root 的路徑都必須同時存在記憶體中,以保證程式執行返回的正確性(否則被蓋掉了就回不去了)。也禁止跨 subtree 調用。
### 6.2.2 Paging
將記憶體分割成 Page 為單位的大小的區塊,常見的大小有: 4KB, 8KB, 2MB, 4MB 等。Intel IA32 使用 4KB 大小的 page,那 512 MB 的物理記憶體就有 `512 * 1024 * 1024 / 4096 = 131 072` 個 page。
假設我們有 16KB 的記憶體,page 大小 4KB,有 4 個 page,如下圖:

假設程式大小 32KB,所以被分成 8 個 page,編號 P0~P7,很明顯不可能全部載入到記憶體中。

如果程式運作時需要 P0, P3, P5, P6 四個 page,loader 這樣載入就可以讓程式開始運作,當運作到一半時需要一個新的 page 且實體記憶體已經滿了,這時候就要看設計時如何替換掉 page:如果是 FIFO 那麼會替換掉 F0;如果是 LRU 那麼會替換掉最少使用的那塊以增加 hit rate。
## 6.3 從 OS 角度看可執行檔的 loading
如果系統在載入程式是直接用物理地址的話,每次 page 都要進行 relocate,更好的做法是使用虛擬記憶體,硬體提供 MMU 轉換地址。
### 6.3.1 Process 建立
創建一個 Process,然後載入可執行文件並執行,在 virtual memory 的情況下,可以分成三步驟:
1. 建立一個獨立的虛擬地址空間
- 在 Linux 上面就是單純的分配一個 page directory,其他什麼事情都沒做
- 本意是在設定 virtual page 與 physical memory 的映射關係
2. 讀取 exe 檔頭,建立「虛擬空間與可執行文件」的映射關係
- 在 page fault 發生時,OS 會先分配一個 physical page 再來負責從硬碟找出缺的那一個 page,再來設定 virtual page 與 physical page 的映射關係
- loading 本意就是要讓 OS 知道要去 disk 上的的可執行檔的哪個位置找到需要的 page,也就是可執行檔與虛擬空間的映射關係
3. 將 PC 設定到程式的 Entry point
- OS 透過設置 CPU 的暫存器控制權轉交給 process ,跳到程式的 Entry Point
- 牽涉 Kernel Stack 與 User Stack 的切換、CPU 執行權限切換等,其實沒有想像中的簡單
- 從這開始 process 正式開始執行
> 可執行文件在 Loading 時是載入到被映射的虛擬空間,因此也稱作映像文件(Image)
程式在載入到 Process 的虛擬空間映射關係(圖 6-5):`.text` 的虛擬地址 `0x08048000` 開始,檔案大小 `0x000e1`,記憶體對齊為 `0x1000` (一個 page 大小),但 `.text` 大小其實不到一個 page,因為齊所以占用整個 page

上面的映射關係儲存在 OS 內部的資料結構中,Linux 叫做 **VMA(Virtual Memory Area)**;Windows 叫做 **Virtual Setcion**。
所以 OS 在建立 Process 後,會在 Process 的 struct 中新增一個 `.text` 段的 VMA 為 `0x08048000~0x08049000` 對應到 ELF 中的 `.text` section ,權限可讀。
### 6.3.2 Page Fault
- 當程式 loading 完成之後,CPU 正要從在 `0x08048000` 開始執行程式,但是發現 `0x08048000` ~ `0x08049000` 這一個 page 並不存在於記憶體中,所以引發了一次 page fault
- 當發生 page fault 時,程式將 CPU 控制權交還給 OS,讓 OS 處理 page fault。OS 透過「虛擬空間與可執行文件」的映射找出需要的 page 位於硬碟的某一個地方,然後找出空白 page 的 VMA、計算 offset、在物理記憶體上分配一塊空間、建立映射,最後把控制權還給 process,讓 process 繼續執行
- 當 page fault 頻繁發生導致記憶體不夠用時怎麼辦?這時候 OS 要有處理垃圾回收的能力,這裡開始涉及 OS 的功能,所以本書不再贅述

## 6.4 Process Virtual Memory Address Space Layout
### 6.4.1 ELF Linking View and Execution View
前面例子只有一個 VMA ,但實際程式是數個段組成的,當 ELF 被映射時,是以系統 page 的大小作為單位的,每個段應該要是 page size 的整數倍才最省空間,否則就會浪費。
對於 OS 來說,它不關心可執行文件各個 Section 的實際內容,只關心 Section 的執行權限(可讀、可寫、可執行)。
常見的權限有以下三種:
- 可讀可執行 e.g. `.text`
- 可讀可寫 e.g. `.data`, `.bss`
- 只可讀 e.g. `.rodata`

為了節省空間 ELF 引進一種概念叫做 **"Segment"**:由多個相同讀寫權限的 Section 組成,一個 Segment 可以由一個或是多個 section 組成。多個相同權限的 section 會放在同一個 segment 裡面,系統的邏輯是按照 segment 來製作 paging。
圖6-7裡面可以看到如果將 `.text` 和 `.init` 分成兩種 page 的話就會浪費一些空間,反之如果將 `.text` 和 `.init` 組合成一個 segment 然後做成分頁的話就會減少浪費。
下面例子用 `gcc -static SectionMapping.c -o SectionMapping`
```c=
// SectionMapping.c
#include <stdlib.h>
int main()
{
while(1)
sleep(1000);
return 0;
}
```
使用 `readelf -S` 可以看到總共有 32 個段
```
$ readelf -W -S ./SectionMapping
There are 32 section headers, starting at offset 0xd4618:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .note.gnu.property NOTE 0000000000400270 000270 000020 00 A 0 0 8
[ 2] .note.gnu.build-id NOTE 0000000000400290 000290 000024 00 A 0 0 4
[ 3] .note.ABI-tag NOTE 00000000004002b4 0002b4 000020 00 A 0 0 4
[ 4] .rela.plt RELA 00000000004002d8 0002d8 000240 18 AI 0 20 8
[ 5] .init PROGBITS 0000000000401000 001000 00001b 00 AX 0 0 4
[ 6] .plt PROGBITS 0000000000401020 001020 000180 00 AX 0 0 16
[ 7] .text PROGBITS 00000000004011a0 0011a0 091b50 00 AX 0 0 16
[ 8] __libc_freeres_fn PROGBITS 0000000000492cf0 092cf0 001ca0 00 AX 0 0 16
[ 9] .fini PROGBITS 0000000000494990 094990 00000d 00 AX 0 0 4
[10] .rodata PROGBITS 0000000000495000 095000 01bfec 00 A 0 0 32
[11] .stapsdt.base PROGBITS 00000000004b0fec 0b0fec 000001 00 A 0 0 1
[12] .eh_frame PROGBITS 00000000004b0ff0 0b0ff0 00a63c 00 A 0 0 8
[13] .gcc_except_table PROGBITS 00000000004bb62c 0bb62c 0000b1 00 A 0 0 1
[14] .tdata PROGBITS 00000000004bd0c0 0bc0c0 000020 00 WAT 0 0 8
[15] .tbss NOBITS 00000000004bd0e0 0bc0e0 000040 00 WAT 0 0 8
[16] .init_array INIT_ARRAY 00000000004bd0e0 0bc0e0 000010 08 WA 0 0 8
[17] .fini_array FINI_ARRAY 00000000004bd0f0 0bc0f0 000010 08 WA 0 0 8
[18] .data.rel.ro PROGBITS 00000000004bd100 0bc100 002df4 00 WA 0 0 32
[19] .got PROGBITS 00000000004bfef8 0beef8 0000f0 00 WA 0 0 8
[20] .got.plt PROGBITS 00000000004c0000 0bf000 0000d8 08 WA 0 0 8
[21] .data PROGBITS 00000000004c00e0 0bf0e0 001a50 00 WA 0 0 32
[22] __libc_subfreeres PROGBITS 00000000004c1b30 0c0b30 000048 00 WA 0 0 8
[23] __libc_IO_vtables PROGBITS 00000000004c1b80 0c0b80 0006a8 00 WA 0 0 32
[24] __libc_atexit PROGBITS 00000000004c2228 0c1228 000008 00 WA 0 0 8
[25] .bss NOBITS 00000000004c2240 0c1230 001718 00 WA 0 0 32
[26] __libc_freeres_ptrs NOBITS 00000000004c3958 0c1230 000028 00 WA 0 0 8
[27] .comment PROGBITS 0000000000000000 0c1230 00002b 01 MS 0 0 1
[28] .note.stapsdt NOTE 0000000000000000 0c125c 0013e8 00 0 0 4
[29] .symtab SYMTAB 0000000000000000 0c2648 00afb0 18 30 735 8
[30] .strtab STRTAB 0000000000000000 0cd5f8 006ec5 00 0 0 1
[31] .shstrtab STRTAB 0000000000000000 0d44bd 000157 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
```
描述 Segment 的叫做 **Program Header** ,可以用 `readelf -l ./SectionMapping`
```
$ readelf -W -l ./SectionMapping
Elf file type is EXEC (Executable file)
Entry point 0x401b90
There are 10 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x000518 0x000518 R 0x1000
LOAD 0x001000 0x0000000000401000 0x0000000000401000 0x09399d 0x09399d R E 0x1000
LOAD 0x095000 0x0000000000495000 0x0000000000495000 0x0266dd 0x0266dd R 0x1000
LOAD 0x0bc0c0 0x00000000004bd0c0 0x00000000004bd0c0 0x005170 0x0068c0 RW 0x1000
NOTE 0x000270 0x0000000000400270 0x0000000000400270 0x000020 0x000020 R 0x8
NOTE 0x000290 0x0000000000400290 0x0000000000400290 0x000044 0x000044 R 0x4
TLS 0x0bc0c0 0x00000000004bd0c0 0x00000000004bd0c0 0x000020 0x000060 R 0x8
GNU_PROPERTY 0x000270 0x0000000000400270 0x0000000000400270 0x000020 0x000020 R 0x8
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x0bc0c0 0x00000000004bd0c0 0x00000000004bd0c0 0x002f40 0x002f40 R 0x1
Section to Segment mapping:
Segment Sections...
00 .note.gnu.property .note.gnu.build-id .note.ABI-tag .rela.plt
01 .init .plt .text __libc_freeres_fn .fini
02 .rodata .stapsdt.base .eh_frame .gcc_except_table
03 .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data __libc_subfreeres __libc_IO_vtables __libc_atexit .bss __libc_freeres_ptrs
04 .note.gnu.property
05 .note.gnu.build-id .note.ABI-tag
06 .tdata .tbss
07 .note.gnu.property
08
09 .tdata .init_array .fini_array .data.rel.ro .got
```
有標記 `LOAD` 的才需要被映射,其他的是在 Load 時輔助用的。圖 6.8 表示 section 與 Process VMA 的映射關係。

Segment 和 Section 各自從不同的角度來描述同一個 ELF 檔案,稱作 View,從 Section 角度叫做 Linking View;從 Segment 角度叫做 Execution View。
儲存 Segment 的資料結構:Program Header Table,可執行檔案和共享庫都有、object file 沒有。
``` c
typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;
typedef struct
{
Elf64_Word p_type; /* Segment type */
Elf64_Word p_flags; /* Segment flags */
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment */
} Elf64_Phdr;
```

```c=
#define PT_NULL 0 /* Program header table entry unused */
#define PT_LOAD 1 /* Loadable program segment */
#define PT_DYNAMIC 2 /* Dynamic linking information */
#define PT_INTERP 3 /* Program interpreter */
#define PT_NOTE 4 /* Auxiliary information */
#define PT_SHLIB 5 /* Reserved */
#define PT_PHDR 6 /* Entry for header table itself */
#define PT_TLS 7 /* Thread-local storage segment */
#define PT_NUM 8 /* Number of defined types */
#define PT_LOOS 0x60000000 /* Start of OS-specific */
#define PT_GNU_EH_FRAME 0x6474e550 /* GCC .eh_frame_hdr segment */
#define PT_GNU_STACK 0x6474e551 /* Indicates stack executability */
#define PT_GNU_RELRO 0x6474e552 /* Read-only after relocation */
#define PT_GNU_PROPERTY 0x6474e553 /* GNU property */
#define PT_GNU_SFRAME 0x6474e554 /* SFrame segment. */
#define PT_LOSUNW 0x6ffffffa
#define PT_SUNWBSS 0x6ffffffa /* Sun Specific segment */
#define PT_SUNWSTACK 0x6ffffffb /* Stack segment */
#define PT_HISUNW 0x6fffffff
#define PT_HIOS 0x6fffffff /* End of OS-specific */
#define PT_LOPROC 0x70000000 /* Start of processor-specific */
#define PT_HIPROC 0x7fffffff /* End of processor-specific */
```
對於 `LOAD` 類型的 Segment 其 `p_memsz` 絕對大於等於 `p_filesz`:`.bss` 已經被合併入 `LOAD` 的 Segment,在 loading 時會把那些變數載入後展開,所以 `LOAD` 的 Segment 的實際大小一定會大於在檔案中的佔用大小。
### 6.4.2 Heap 和 Stack
堆疊(Stack)和堆積(Heap)也會在 Process 中使用 VMA 來管理地址空間。一個 Process 通常都有一個 Stack 和一個 Heap,可以用 `/proc` 來觀察。
```=
$ ./SectionMapping &
[1] 1707361
$ cat /proc/1707361/maps
000000400000-000000401000 r--p 00000000 fc:01 2099350 /root/test/a.out
000000401000-000000495000 r-xp 00001000 fc:01 2099350 /root/test/a.out
000000495000-0000004bc000 r--p 00095000 fc:01 2099350 /root/test/a.out
0000004bd000-0000004c0000 r--p 000bc000 fc:01 2099350 /root/test/a.out
0000004c0000-0000004c3000 rw-p 000bf000 fc:01 2099350 /root/test/a.out
0000004c3000-0000004c4000 rw-p 00000000 00:00 0
00000212e000-000002151000 rw-p 00000000 00:00 0 [heap]
7ffe8a8d0000-7ffe8a8f1000 rw-p 00000000 00:00 0 [stack]
7ffe8a9ca000-7ffe8a9cd000 r--p 00000000 00:00 0 [vvar]
7ffe8a9cd000-7ffe8a9ce000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
```
> pmap
- 解讀
1. VMA 位址範圍
2. VMA 權限: "r"表示可讀, "w"表示可寫, "x"表示可執行, "p"表示私有(Copy on Write), "s"表示共享
3. VMA mapping 到 Segment 的 offset
4. 文件所在的設備的主設備號和次設備號
5. 映像檔案的節點號
6. 映像檔案的路徑
有些 VMA 並沒有設備號和節點,這種叫做 Anonymous VMA。其中 Heap 和 Stack 就是 Anonymous VMA。
`vdso` 是一塊於 kernel space (x86:`>= 0xC0000000`, x86_64: `>= 0x0000800000000000`)的 VMA,用途是要和 kernel 進行通信用,書中不再進行細講。
- Code VMA: 可讀、可執行、不可寫,有映像檔案
- Data VMA: 可讀、可寫、不可執行,有映像檔案
- Heap VMA: 可讀、可寫、不可執行,無映像檔案,可向上擴展
- Stack VMA: 可讀、可寫、不可執行,無映像檔案,可向下擴展

### 6.4.3 Heap 的最大 allocate 數量
以下程式可以測試 `malloc` 可以申請最大的記憶體
```cpp=
#include <stdio.h>
#include <stdlib.h>
unsigned maximum = 0;
int main()
{
unsigned blocksize[] = {1024 * 1024, 1024, 1};
int i, count;
for(i = 0; i < 3; i++) {
for(count = 1; ; count++)
{
void *block = malloc( maximum + blocksize[i] * count );
if(block) {
maximum = maximum + blocksize[i] * count;
free(block);
} else {
break;
}
}
}
printf("maximum malloc size = %u bytes\n", maximum);
}
```
>(實際實驗不知道為什麼沒有辦法要到最大的量的記憶體。)
x86 Linux 機器上面可以要到 2.9GB,理論上 Linux VMA 分配給執行緒的大小有 3GB,中間的差有可能會因為作業系統版本、程式大小、使用到的 lib 數量不同而有差距,甚至每次結果都會不一樣。有些 OS 有使用位址空間組態隨機載入(ASLR)所以使得 heap 的空間減少;在第4部分會詳細的介紹。
### 6.4.4 Section 地址對齊
在映射過程中是以 page 為單位,對於 x86 來說就是 4KB(4096 bytes),因此想要映射物理記憶體和 Process VMA 這段*記憶體大小必須是 4096 的整數倍*,且*起始位置(實體記憶體和 VMA)也必須是 4096 倍*。
以下是一個例子有一個 ELF 有三個 Segment: SEG0~2,可以發現每個 Segment 都不是 4096 的整數倍。

一種最簡單的映射方式就是直接每個 Section 映射成一個 Segment (如表6-4),但這樣會浪費很多空間。


為了節省記憶體空間,有一種做法是讓各個段銜接部分共用同一個實體 page,然後將該實體 page 映射兩次(圖 6-11)。
這種方法從原本要使用 5 個 Physical Page,到只要 3 個 Physical Page。在這種映射方法下,對於一個 Physical Page 來說,可能包含了多個 Section 的資料。


因為 Section 位址對齊的關係,各個 Section 的虛擬位址就不是 Page 長度的整數倍了。

書上VMA0的起始地址為 `0x08048000`、VMA1 的起始地址為 `0x080B99E8`,這些地址是如何計算出來的?
VMA0 的長度為 `0x709E5`,所以 VMA0 結束地址為 `0x080B89E5`,因為 Physical Page 的大小為 `0x1000` 所以 VMA1 應該要再加上 `0x1000`,最後因為要對齊 4 bytes 所以 VMA1 的起始地址為 `0x080B89E5 + 0x1000 + 對齊 = 0x080B99E5 + 對齊 = 0x080B99E8`
ELF 檔案中,對於任何可裝載的 Segment 來說,它的 `p_vaddr` 除以對齊大小的餘數會與 `p_offset` 除以對齊大小的餘數**相等**。
```
>>> 0x080b99e8 % 0x1000
2536
>>> 0x0709e8 % 0x1000
2536
```
### 6.4.5 Stack 初始化
會將環境變數和程式的執行參數放在 Stack 中: 假設我們的環境變數是以下
```
HOME=/home/user
PATH=/usr/bin
```
且使用 `./program 123` 來執行此程式,假設該程式的 Stack 開始位置是 `0xBF802000` 則程式啟動後 Stack 的狀態以圖6-12 所示:

Runtime Library 會將 Stack 裡的 argument count 和 pointer 傳給 `main()` 也就是 `argc` 和 `argv`。
```c=
int execve(const char *pathname, char *const _Nullable argv[],
char *const _Nullable envp[]);
```
> https://man7.org/linux/man-pages/man2/execve.2.html
## 6.5 Linux kernel 載入 ELF 過程簡介

> 參考 angelboy 簡報
## 6.6 Windows PE 的裝載
PE 文件的裝載,所有 Section 的起始位置都是 page 的倍數,如果該 Section 的大小不是 page 的整數倍,在映射時會向上補齊整數倍。所以 32bit PE 所有 Section 的起始地址和長度都是 4096 的整數倍。
PE 中有個術語叫做 RVA (Relative Virtual Address) 就是 Offset,相對於 PE 裝載 base address 的 offset address 。例如一個 PE 被裝載到 VA(Virtual Address) `0x00400000`,那麼 RVA `0x1000` 的地址就是 `0x00401000`。
PE 在裝載時會有一個 Target Address ,就是所謂的 Base Address,因為 PE 可以裝載到任何位置,所以 base address 會變化,因此要有 RVA 的機制。
最後簡單的敘述 PE loading 的過程:
1. 讀取文件第一個 sectioin (DOS Stub, PE Header)
2. 檢查 Process Address Space 中目標地址是否可用,如果不可用則尋找一個新的裝載地址。但是基本上不太可能被佔用。
3. 根據 Section Table 中的資訊,將所有的 Section 映射到 Address Space 相對應的位置
4. 如果裝載地址不是目標地址,則需要 Rebasing
5. 裝在需要的 DLL
6. 對 PE 的 import 進行解析
7. 根據 PE 所指定的參數,建立 Stack 和 Heap
8. 建立 main thread 並開始執行
在 PE 中,與裝載相關的資訊放在 PE Optional Header 和 Section Table,以下表格說明了 PE Optional Header 中與裝載有關的 field,一部分是和 Process Initialization 和 Runtime Library 有關的,書的後面會介紹。
