連結庫與位置無關程式碼:靜態/動態連結、PIC、GOT/PLT 機制深度解析
本文系統解析 Linux 下靜態連結、靜態共享庫、動態共享庫的區別,重點闡述位置無關程式碼(PIC)如何讓動態共享庫的程式碼段在多進程間真正共享,並詳細剖析 GOT、PLT、.dynamic 段的運作機制。
一、三種連結模式對比
| 模式 | 連結時機 | 裝載地址確定時機 | 代碼段共享 |
| 靜態連結 | 連結階段 | 連結階段 | 不可共享(每進程一份副本) |
| 靜態共享庫 | 連結階段 | 連結階段(OS 預分配虛擬地址) | 可共享,但地址固定,已淘汰 |
| 動態共享庫 | 執行階段 | 裝載階段(動態分配) | 可共享,需 PIC 支援 |
二、關鍵名詞釐清
- 連結:目標檔與庫檔建立符號關聯。
- 裝載:連結後映射至虛擬記憶體。
- 連結重定位:靜態連結時將符號參考解析為相對起始位址的偏移。
- 裝載重定位:動態連結時在裝載階段根據實際虛擬位址修正絕對位址引用。
三、共用物件結構分析
.data 段(全域/靜態變數)
- 裝載時併入可執行檔的
.got 段,每個進程擁有私有副本(寫時複製 COW)。
- 區域變數存放於堆疊,不在共享庫段內。
.interp 段
- 指定動態連結器路徑(如
/lib64/ld-linux-x86-64.so.2)。
.dynamic 段(動態連結關鍵資訊)
DT_SYMTAB:.dynsym 動態符號表位址。
DT_STRTAB:動態字串表位址。
DT_REL:重定位表位址(.rel.text、.rel.data)。
DT_NEEDED:依賴的其他共用物件名稱。
.symtab vs .dynsym
.symtab:靜態連結符號表,含所有符號(含區域符號)。
.dynsym:動態連結符號表,僅含外部可見符號,.dynsym ⊂ .symtab。
四、程式碼段與 PIC 實現
問題:非 PIC 程式碼含絕對位址引用
- 全域變數存取、函數呼叫若使用絕對位址,裝載到不同虛擬位址時會出錯。
- 強制固定裝載位址 → 退化為靜態共享庫,喪失靈活性。
解決方案:-fPIC 編譯選項
將程式碼段中所有絕對位址引用轉換為相對位址或間接引用:
| 引用類型 | PIC 處理方式 |
| 模組內部函數呼叫/跳轉 | PC-relative 相對位址(編譯時已知偏移) |
| 模組內部資料存取 | GOT 相對位址(透過 GOT 基址 + 偏移) |
| 跨模組資料存取 | 存入本模組 .got,執行期由動態連結器填入實際位址 |
| 跨模組函數呼叫 | 存入 .got.plt,首次呼叫觸發 _dl_runtime_resolve 延遲綁定 |
.got.plt 三大核心條目
.dynamic 段位址 → 查找動態符號表。
- 本模組 ID(如
libfoo.so 的 "foo")。
_dl_runtime_resolve 函數位址 → 執行期符號解析。
五、動態裝載流程簡述
- 核心將控制權交給動態連結器。
- 動態連結器靜態連結自舉,將可執行檔映射至固定位址。
- 讀取
.dynamic 中 DT_NEEDED,遞迴裝載依賴共用物件。
- 合併符號表至全域符號表,映射全域變數至私有資料段(COW)。
- 映射程式碼段(唯讀,多進程共享實體頁框)。
- 所有依賴就緒後,跳轉可執行檔入口點開始執行。
六、編譯實戰指令
# 產生位置無關目標檔
gcc -fPIC -c foo.c -o foo.o
# 產生動態共享庫
gcc -shared foo.o -o libfoo.so
# 連結使用動態庫
gcc main.c -L. -lfoo -o main
# 執行時需設定 LD_LIBRARY_PATH 或安裝至標準路徑
- 上一篇文章: Linux C 程式開發入門:Hello World 編譯、連結與執行
- 下一篇文章: C語言中 static 關鍵字的完整解析:靜態變量與靜態函數
Copyright ©
程式碼之家 All Rights Reserved