靜態鏈接器如何將多個 .o 目標文件合併為單一可執行文件?本文以 GCC/Linux/ELF32 為例,拆解「空間分配 → 符號解析 → 重定位」三大核心階段,附 objdump 實證分析。
鏈接器掃描所有輸入文件,收集段信息(長度、屬性、位置),**按名稱合併同類段**:
.text → 一個大 .text.data → 一個大 .data.bss → 一個大 .bss優勢:段數量固定、消除內部碎片、利於內存映射。同時構建**全局符號表**,彙總所有文件的符號定義與引用。
合併段後,原符號地址失效,需完成:
extern int shared)綁定到唯一定義(b.c 中的 shared)// b.c
int shared = 1;
void swap(int *a, int *b) { *a ^= *b ^= *a ^= *b; }
// a.c
extern int shared;
int main() { int a=100; swap(&a, &shared); return 0; }
編譯:gcc -c a.c b.c → a.o、b.o
鏈接:gcc a.o b.o -o ab
# objdump -h a.o b.o ab a.o/.text: VMA=0x0 b.o/.text: VMA=0x0 ab/.text: VMA=0x08048000 // Linux 32位默認加載地址
# objdump -r a.o RELOCATION RECORDS FOR .text: OFFSET TYPE VALUE 00000011 R_386_32 shared // 指令中 shared 地址佔位 00000020 R_386_PC32 swap // call swap 相對偏移佔位
# objdump -d a.o 11: c7 44 24 04 00 00 00 00 movl $0x0, 0x4(%esp) # shared 佔位 0 20: e8 fc ff ff ff call 0x21 # swap 佔位 -4 (相對偏移) # objdump -d ab (鏈接後) 80480a5: c7 44 24 04 58 91 04 08 movl $0x8049158, 0x4(%esp) # shared 真實地址 80480b4: e8 07 00 00 00 call 0x80480c0 # swap 真實入口
S + A = 0x08049158 + 0 = 0x08049158(小端存儲 58 91 04 08)S + A - P = 0x080480c0 + (-4) - 0x080480b5 = 0x7(小端 07 00 00 00)完全匹配鏈接後機器碼!
typedef struct {
Elf32_Addr r_offset; // 需修正位置在段內偏移
Elf32_Word r_info; // 符號表索引 + 重定位類型
} Elf32_Rel;
r_info 高 24 位 = 符號索引,低 8 位 = 類型(R_386_32 絕對、R_386_PC32 相對)。
理解此流程,是排查「未定義引用」「多重定義」「地址錯誤」等鏈接錯誤的理論基石。