近期在學習 Linux 高級調試技術時,針對動態庫鏈接機制進行了一系列實驗。本文將詳細記錄 PLT(Procedure Linkage Table,過程鏈接表)與 GOT(Global Offset Table,全局偏移表)的交互過程,並通過 GDB 反匯編分析動態鏈接的延遲綁定特性。
測試程式碼簡單調用 puts 函數:
#include <stdio.h>
int fun() {
printf("hello world\n");
}
int main() {
while(1) {
fun();
}
return 1;
}
在 fun 函數入口設置斷點,啟動 GDB 進行動態分析。
(gdb) disassemble fun
Dump of assembler code for function fun:
0x080483d4 <+0>: push %ebp
0x080483d5 <+1>: mov %esp,%ebp
0x080483d7 <+3>: sub $0x18,%esp
0x080483da <+6>: movl $0x80484d0,(%esp)
0x080483e1 <+13>: call 0x80482f0 <puts@plt>
0x080483e6 <+18>: leave
0x080483e7 <+19>: ret
End of assembler dump.
偏移量 +13 處的 call 0x80482f0 即為對 puts@plt 的調用。
(gdb) disassemble 0x80482f0
Dump of assembler code for function puts@plt:
0x080482f0 <+0>: jmp *0x804a000
0x080482f6 <+6>: push $0x0
0x080482fb <+11>: jmp 0x80482e0
End of assembler dump.
PLT 條目包含三條指令:
0x804a000(即 puts@got.plt)0x80482e0),進入動態鏈接器解析流程(gdb) x/x 0x804a000
0x804a000 <puts@got.plt>: 0x080482f6
初始時 GOT 項指向 PLT 條目中的下一條指令(push $0x0),而非真實函數地址。
(gdb) x/10i 0x80482e0
0x80482e0: pushl 0x8049ff8
=> 0x80482e6: jmp *0x8049ffc
查看 0x8049ffc(_GLOBAL_OFFSET_TABLE_+8):
(gdb) x/x 0x8049ffc
0x8049ffc <_GLOBAL_OFFSET_TABLE_+8>: 0xb7ff2690
該地址指向動態鏈接器 ld-linux.so.2 內部的 _dl_runtime_resolve 函數,負責符號解析與重定位。
首次調用結束後,再次檢視 GOT 項:
(gdb) x/x 0x804a000
0x804a000 <puts@got.plt>: 0xb7e866a0
地址已更新為 puts 在 libc 中的真實實現地址。後續調用將直接跳轉至此,無需再經動態鏈接器。
動態鏈接採用延遲綁定策略:
此機制平衡了程式啟動速度(避免解析所有符號)與運行效率(熱點函數僅解析一次)。