Try   HackMD

自我修養


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

型別

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

欄位

  • 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

欄位

  • 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.oab

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