Linux tanghuimin 2.6.32-358.el6.x86_64 #1 SMP Fri Feb 22 00:31:26 UTC 2013 x86_64 GNU/Linux
用戶態 bash → fork() 創建子進程 → execve() → 內核 sys_execve() → do_execve() → load_elf_binary():
.interp 段,設置動態鏈接器路徑e_entry;.dynamic 中 DT_NEEDED 裝載依賴庫,構建全局符號表.init 段,最後移交控制權給程序入口.plt 中每個函數對應一個存根,首次調用跳轉至 _dl_runtime_resolve().got.plt 專存函數地址,前三項特殊:
.dynamic 段地址_dl_runtime_resolve() 地址// common.c - 共享庫
int val = 1;
int func(void) { return (val + 10); }
// test.c - 主程序
extern int val;
extern int func(void);
int main() { val = 10; func(); return 0; }
gcc -shared -fPIC -o common.so common.c gcc -o test test.c ./common.so
00000000004005f4 <main>: 4005f4: 55 push %rbp 4005f5: 48 89 e5 mov %rsp,%rbp 4005f8: c7 05 ae 03 20 00 0a movl $0xa,0x2003ae(%rip) # 6009b0 <val> 4005ff: 00 00 00 400602: e8 f9 fe ff ff callq 400500 <func@plt> 400607: b8 00 00 00 00 mov $0x0,%eax 40060c: c9 leaveq 40060d: c3 retq
val = 10:RIP 相對寻址訪問 0x6009b0(BSS 段副本)func():調用 func@plt (0x400500)0000000000400500 <func@plt>: 400500: ff 25 9a 04 20 00 jmpq *0x20049a(%rip) # 6009a0 <func@got.plt> 400506: 68 01 00 00 00 pushq $0x1 # 重定位索引 40050b: e9 d0 ff ff ff jmpq 4004e0 # 跳轉到 PLT[0] 通用入口 00000000004004e0 <.plt[0]>: 4004e0: ff 35 a2 04 20 00 pushq 0x2004a2(%rip) # module id (GOT+8) 4004e6: ff 25 a4 04 20 00 jmpq *0x2004a4(%rip) # _dl_runtime_resolve (GOT+16) 4004ec: 0f 1f 40 00 nopl 0x0(%rax)
首次調用流程:
func@plt → got.plt[func] 初始指向下一條指令 (0x400506) → 壓入重定位索引 → 跳轉 .plt[0] → 壓入 module id → 跳轉 _dl_runtime_resolve() → 完成重定位,將真實地址寫入 got.plt[func]。
二次調用:直接從 got.plt[func] 獲取真實地址跳轉。
# 斷點 main,運行前 val (0x6009b0) = 0x1 # 共享庫初始值 module_id (0x600988) = 0x41021188 # 已初始化 _dl_runtime_resolve = 0x40e146f0 # 已初始化 func@got.plt (0x6009a0) = 0x400506 # 仍指向存根下一條指令 # 執行 val=10 後 val = 0xa # 執行 func() 後 func@got.plt = 0xf7dfc55c # 已重定向至真實函數地址
通過實例完整展示了:ELF 裝載 → 動態鏈接器自舉 → 共享庫裝載 → GOT/PLT 機制 → 延遲綁定 → 運行時重定位的全過程。理解這一機制對調試動態鏈接問題、分析性能開銷、逆向工程均有重要價值。