在靜態鏈接過程中主要發生了兩件事。一是空間與地址分配,鏈接器掃描所有輸入文件的段,合并相似段并且重新計算段長度和在虛擬內存中的映射關系,收集所有的符號放到全局符號表中。二是符號解析與重定位,鏈接器收集所有的段信息和重定位信息并且進行符號解析和重定位、調整代碼中的地址等。
1. 空間與地址分配
我們用一個簡單的例子來分析一下靜態鏈接過程中的空間與地址分配情況。
/* a.c */
extern int shared;int main()
{int a = 100;swap(&a, &shared);
}
/* b.c */
int shared = 1;void swap(int* a, int* b)
{*a ^= *b ^= *a ^= *b;
}
gcc -c a.c b.c #獲取a.o 和 b.o
ld a.o b.o -e main -o ab -lc #將a.o b.o鏈接生成ab
輸入目標文件的同名段將會被合并
a.o的段結構--------------->
b.o 的段結構------------>
ab的段結構---------------->
對比一下a.o b.o和ab的.text .data段size,我們可以發現ab文件的段是a.o文件和b.o文件同名段的組合。
字段\文件 | a.o | b.o | ab |
---|---|---|---|
.text | size: 0x00000058 | sze: 0x0000004f | size: 0x000000a7 |
.data | size: 0x00000000 | size: 0x00000004 | size: 0x00000004 |
鏈接后文件才會有VMA
觀察上述a.o b.o 和 ab 文件.text段的VMA地址可以發現,a.o和b.o文件中的.text的VMA地址都是0,只有ab文件的.text段的VMA地址不是0而是0x00401030。為什么是0x00401030?正常ELF可執行文件會被加載到0x400000開始的內存中,最開始加載的是程序頭等段,然后才是代碼數據段。
確定無需重定位的符號地址
在目標文件中,無需重定位的符號地址都是相對與段地址的偏移。那么掃描所有輸入文件符號表的時候,會為所有的符號偏移加上重新計算出的段虛擬地址就是符號的新的虛擬地址。例如,上面的例子中a.o文件中main函數相對.text段的偏移是X,而經過計算后.text的虛擬地址是0x401030,那么main函數符號的虛擬地址就是0x401030+X。
2. 符號解析與重定位
鏈接器首先會掃描所有目標文件的符號表,將所有符號進行虛擬地址換算。然后掃描所有的重定位表,根據重定位表中的符號項名稱在符號表中找到,再根據對應符號表中符號項的地址定位到需要替換的符號位置進行符號地址替換。