--- tags: extract --- # [自我修養](https://www.tenlong.com.tw/products/9789861818283) --- # CH01 --- # CH02 編譯/連結 (廢) 整合開發環境 (IDE) 包辦一切,鮮少揭露細節。 ## 2.1 (廢) Hello World (? ### 2.1.1 前處理 處理「前處理」指令 - 標頭:`#include` * 補上除錯行號資訊 - 巨集:`#define` - 條件:`#if`/`#ifdef`/… - 會刪除註解嗎?? ### 2.1.2 編譯 最核心部份,詳見 2.2。 ### 2.1.3 組譯 組合語言 → 機器碼 ### 2.1.4 連結 (廢) ## 2.2 編譯 - 高階語言 → 組合語言 * 高階:專注於邏輯,易撰寫。 * 組合:瑣碎,平台相依。 ### 2.2.1 切字詞 lex ### 2.2.2 串語法 yacc ### 2.2.3 檢查語法之語意 ### 2.2.4 中間碼 與 最佳化 ### 2.2.5 目的碼 與 最佳化 ## 2.3 連結⑴ - 程式指令的位置若移動,跳轉的目標位址皆須 **重定 (relocaion)**。 - 組合語言利用 **符號 (symbol)** 來標記位址 - 藉由拆分為多個模組而 **連結 (linking)** 成大型程式 ## 2.4 連結⑵ 總之就是 調整位址 ## 2.5 (廢) --- # CH03 目的檔 (廢) 目的檔 是編譯後但未連結 (未調整符號、位址) 的 執行檔 片段。 ## 3.1 目的檔 - 執行檔 * 主流都是 COFF (Common Object File Format) 的變形 + Linux: ELF (Executable Linkable Format) + Windows: PE (Portable Executable) * 其他 + Unix: a.out + Intel/Microsoft: OMF (Object Module Format) + MS-DOS: `.com` - 目的檔:編譯後,但未連結的中間檔。 * 附檔名 + Linux: `*.o` + Windows: `*.obj` * 通常內容與執行檔格式相同 - 靜態/動態 函式庫 * 附檔名 + Linux: `.a`/`.so` + Windows: `.lib`/`.dll` * 也與執行檔格式相同 * 靜態函式庫:封裝多個目的檔,再加上索引。 - Linux 指令:`file` 以檢視格式 ## 3.2 內容 - 各種所需內容,以 section/segment 形式儲存。 * 機器指令 (`.code`/`.text`) * 資料 (`.data`) * 符號表 * 除錯訊息 - 檔頭 (ELF 為例) * 檔案屬性 * section table - 不佔空間的 `.bss` * 原可放在 `.data`,但畢竟 內容皆為 0。 * 像是 無初始值的 全域變數 - 為何程式與資料分開放? * 分別設定區塊的權限 (尤其防止指令遭惡意改寫) * 快取 程式指令 * 共用 唯讀資料 (指令,圖示、圖片等資源) ## 3.3 深入細節 (廢) 我:用 TCP/IP 比喻比較容易理解嗎? - 以刻意設計的原始碼為例 - 目的檔 檢視工具 * binutils 的 `objdump` (跨平台,在 Windows 也支援 PE) * Linux 的 `readelf` * (`size`) - 分析資訊 * 區段長度:`Size` * 區段位置:`File off` (file offset) * 各種屬性:(第二行) + `CONTENTS` -- 區段在檔案中存在 + `ALLOC` -- …?? ### 3.3.1 指令區段 - `objdump -s` (以 16 進位) 印出區段內容 - `objdump -d` 反組譯 ### 3.3.2 資料區段 - `.data` 區段 * 有初始值的 全域/區域靜態 變數 - `.rodata` 區段 * 常數 (`const`)、字串常數 * 好處 + 從系統層確切支援 `const`:誤觸則作業系統認作非法操作 + (部份嵌入式系統) 可採用唯讀記憶體,更加安全。 * 部份編譯器仍置於 `.data` ### 3.3.3 BSS - `.bss` 區段 * 無初始值的 全域/區域靜態 變數 - 有時 無初始值的 全域 變數,並不放在任何區段。 * 只在 符號表 留有 未定義的 common 符號 * 連結 才納入 `.bss` * 無初始值的 靜態 (`static`) 全域變數,直接在 `.bss` 中。 ### 3.3.4 其他 - 系統保留 區段名稱,皆以 `.` 開頭。 - 可自訂區段 (如安插音樂檔) - 區段可同名 - `__attribute__((section("foobar")))` 將變數或程式放入指定區段 ## 3.4 ELF 檔案結構 - header * 檔案版本 * 目標機器型號 * 程式進入點 * … - sections * `.text` * `.data` * `.bss` * … - section header table * 區段名稱 * 區段長度 * 偏移位置 * 讀寫權限 * 其他屬性 - 字串表 - 符號表 ### 3.4.1 header - `readelf -h` 來檢視內容 - `Elf32_Ehdr` -- [elf.h](https://github.com/torvalds/linux/blob/v5.5/include/uapi/linux/elf.h#L203-L237) **型別** | | 32-bit | | 64-bit | | | --------:|:------------- | ----------:|:------------- | ----------:| | 位址 | `Elf32_Addr` | `uint32_t` | `Elf64_Addr` | `uint64_t` | | 位移 | `Elf32_Off` | `uint32_t` | `Elf64_Off` | `uint64_t` | | 短正整數 | `Elf32_Half` | `uint16_t` | `Elf64_Half` | `uint16_t` | | 正整數 | `Elf32_Word` | `uint32_t` | `Elf64_Word` | `uint32_t` | | 整數 | `Elf32_Sword` | `int32_t` | `Elf64_Sword` | `int32_t` | **欄位** - `e_ident` * 0-3:`0x7f 0x45 0x4c 0x46` (`<DEL>ELF`) * 4:位址長度 + 32-bit + 64-bit * 5:位元組序 + little-endian + big-endian * 6:ELF 版本 * 其他未定義 - `e_type`:檔案類型 * 中間檔 * 執行檔 * 動態函式庫 - `e_machine`:機器類型 * Intel x86 * … - `e_version` - `e_entry` - `e_phoff` - `e_shoff`:段頭表位置 - `e_flags` - `e_ehsize` - `e_phentsize` - `e_phnum` - `e_shentsize` - `e_shnum` - `e_shstrndx` ### 3.4.2 段頭表 - `readelf -S` 來檢視內容 * `objdump -h` 省略了部份輔助性區段 - 描述每個區段的資訊 - `Elf32_Shdr` -- [elf.h](https://github.com/torvalds/linux/blob/v5.5/include/uapi/linux/elf.h#L302-L328) **欄位** - `sh_name`:區段名稱 - `sh_type`:區段類型 * 程式區段 * 符號表 * 字串表 * 重定表 * … - `sh_flags` * 可寫入 * 需要分配空間 * 可執行 - `sh_addr` - `sh_offset` - `sh_size` - `sh_link` - `sh_info` - `sh_addralign` - `sh_entsize` ### 3.4.3 重定表 - 每個需要重定位址的區段 (程式碼/資料),皆有一個對應的重定表。 * 如 `.text` 與 `.rel.text` - 詳細於下章討論 ### 3.4.4 字串表 - 字串長度往往不固定,不易置於固定大小的結構。 - 集中編列於字串表,並以索引值引用。 * 給出索引值即可,無須記錄長度。 - 區段 * `.strtab` (string table) * `.shstrtab` (section header string table) ## 3.5 符號表 - 藉 **符號** 在 目的檔 間相互 **引用** - 符號表 * 符號:名稱 + 對應位址 - 類型 * 全域符號:給別人引用 * 外部符號:引用自別人 * 區段名稱:區段的起始位址 * 區域符號:僅內部使用 * 行號資訊:與指令位址之對應 (除錯用,非必要) - 查看 * `readelf` * `objdump` * `nm` ### 3.5.1 結構 - `.symtab` 區段 - `Elf32_Sym` - [elf.h](https://github.com/torvalds/linux/blob/v5.5/include/uapi/linux/elf.h#L182-L200) **欄位** - `st_name`:符號名稱 (字串表) - `st_value`:不同符號有不同意思 - `st_size`:資料型別的大小 - `st_info` * symbol binding + 區域符號 + 全域符號 + 弱引用 * symbol type + 未知 + 變數/陣列 + 函式 + (區域符號) 區段 + (區域符號) 檔案名稱 (常為對應的原始檔檔名) - `st_other`:未使用 - `st_shndx`:符號所在區段於段頭表之索引 * `SHN_ABS`:絕對的值 * `SHN_COMMON`:COMMON 區塊的符號 * `SHN_UNDEF`:未定義 (在其他目的檔) ### 3.5.2 特殊符號 詳見 4.6 連結流程控制 - `_executable_start` 指令區段開頭 (不是進入點) - `_etext` 指令區段結尾 - `_edata` 資料區段結尾 - `_end` 程式結尾 (?? ### 3.5.3 名稱修飾 (decoration/mangling) - 不修飾的話,名稱會衝突。 * 語言之間 + C: `_foo` (舊 Linux、Windows,或以參數開關) + Fortran: `_foo_` * 模組之間 → 以 namespace 解決 **C++** - 為了支援 * class * overload * namespace - 函式簽章 (signature) * GCC 方式 + `c++filt` * Visual C++ 方式 + 非公開,但有 API 供轉換。 - 全域變數/靜態區域 * 不納入型別 - 方式不同故無法互通 ### 3.5.4 `extern "C"` - C++ 以 `extern "C"` 選用 C 語言的修飾方式 - 搭配 `#ifdef __cplusplus` 以相容 C/C++ ### 3.5.5 強/弱 符號 - 定義時決定,與引用無關。 * 強符號 + 預設函式 + 經初始化的全域變數 * 弱符號 + 未初始化的全域變數 + `__attribute__((weak))` * 同名符號中, + 強符號只能有一個,且由之代表。 + 若只有弱符號,則空間最大者代表。(不建議有不同型別) - 引用 * 強引用:未定義則會報錯 * 弱引用:未定義也不報錯 + `__attribute__((weakref))` + 預設為 0 - 用途:連結時強符號覆蓋弱符號,藉此得知該模組是否存在。 ## 3.6 除錯資訊 - 目的檔 也可容納 除錯資訊 * 指令位址與原始碼行號的對應 * 函式、變數的類型 * 結構的定義 * … - 指令 * 加入除錯資訊: `gcc -g` * 去掉除錯資訊: `strip` - 格式 * Linux: DWARF (Debug With Arbitrary Record Format) 標準 * Windows: CodeView ## 3.7 下一章討論如何組合起來 --- # CH04 靜態連結 連結多個 目的檔 成 執行檔 - `a.c` * `main` * 使用 `shared` * 使用 `swap` - `b.c` * 提供 `shared` * 提供 `swap` `a.o` + `b.o` → `ab` ## 4.1 (廢) ## 4.1.1 直接組合 需要對齊分頁,而有空間浪費。 ## 4.1.2 合併同類區段 - 空間分配 * 執行檔內容 * 記憶體虛擬位址安排 ← 重點 - 兩步連結 1. 空間位址分配 2. 符號解析重定 - 以 `objdump` 觀察 `ld` 前後 * 區段合併了 * VMA (Virtual Memory Address) 安排好了 ## 4.1.3 符號位址 各個符號的最終位置也確定了 ## 4.2 符號解析與重定 ### 4.2.1 重定 指令使用到符號之處也要逐一修正 ### 4.2.2 重定表 - 重定區段 (`.text` 便有 `.rel.text`) - 重定項 * 在區段的位置 * 類型 * 符號表索引 ### 4.2.3 解析 重定表上的 `GLOBAL` 類型符號會有不少是 `UND` (undefined) ### 4.2.4 位址修正 - 絕對位址 - 相對位址 ## 4.3 --- # CH07 動態連結 ## 7.1 好處都有啥? - 靜態連結 仍有缺點 * 共用函式庫 重複,造成磁碟與記憶體浪費。 * 更新/發佈 需全數更新 - 若在執行時才連結 * 還能選擇性載入 * 但有版本管理的問題 (DLL Hell) - 實作面 * 需 作業系統 支援 * 動態連結有一定的效能損失 ## 7.2 例子 - 先以 ELF 為例 (PE 略有不同) * prog1.c * prog2.c * lib.c * lib.h - 本應重定位址的函式,藉 .so 得知為 動態符號,而留至執行時再進行。 - 位址空間如何配置? → 趁程式執行時查看:`cat /proc/12345/maps` * `lib.so` * `libc-2.6.1.so` * `ld-2.6.so` (動態連結器) * 共用函式庫的位址未定 ## 7.3