鏈接器的任務(wù)
在上一篇文章中,我們提到鏈接是將多個(gè)可重定位目標(biāo)文件鏈接成一個(gè)可執(zhí)行目標(biāo)文件皿渗。必須要完成2件事
- 符號(hào)解析斩芭,將每一個(gè)符號(hào)引用的定義聯(lián)系起來(lái)轻腺,比如foo.c中的num定義的地方是在main.c中乐疆。
- 重定位,編譯器和匯編器生成從0地址開始的代碼和數(shù)據(jù)節(jié)(下文會(huì)提到)贬养,鏈接器把每一個(gè)符號(hào)定義與存儲(chǔ)器位置聯(lián)系起來(lái)挤土,然后修改所有對(duì)這些符號(hào)的引用。使得它們有正確的存儲(chǔ)器地址误算。從而重定位這些節(jié)仰美。
目標(biāo)文件
目標(biāo)文件一共有3類:
- 可重定位目標(biāo)文件,包含二進(jìn)制代碼和數(shù)據(jù)儿礼,可以在咖杂;鏈接器中和其他可重定位目標(biāo)文件合并為1個(gè)可執(zhí)行目標(biāo)文件。
- 可執(zhí)行目標(biāo)文件蚊夫,包行二進(jìn)制代碼和數(shù)據(jù)诉字。
- 共享目標(biāo)文件,一種特殊類型的可重定位目標(biāo)文件,可以在加載或者運(yùn)行時(shí)被動(dòng)態(tài)的加載到存儲(chǔ)器并鏈接壤圃。
可重定位目標(biāo)文件
||
|---------|-----------|
|ELF頭|包括目標(biāo)ELF頭的大小陵霉,目標(biāo)文件的類型,機(jī)器類型,節(jié)頭部表等信息主要用來(lái)幫助鏈接器語(yǔ)法分析個(gè)解釋目標(biāo)文件的信息。
|.text|已編譯程序的機(jī)器代碼|
|.rodata|只讀數(shù)據(jù)抬探,比如"hello world"字符串和開關(guān)語(yǔ)句跳轉(zhuǎn)表
|.data|已初始化的全局C變量
|.bss|未初始化的全局C變量
|.symtab|符號(hào)表袱院,存放程序定義和引用的函數(shù)和全局變量的消息。有別于編譯器的符號(hào)表
|......|......|
符號(hào)和符號(hào)表
每個(gè)可重定位目標(biāo)模塊m都有一個(gè)符號(hào)表棺聊,它包含m所定義和引用的符號(hào)信息。
- 由m定義并能被其他模塊所引用的全局符號(hào),對(duì)應(yīng)于非靜態(tài)的static屬性的C函數(shù)和一級(jí)定義為不帶static屬性的全局變量扁凛。
- 由其他模塊定義并被模塊m引用的全局符號(hào)。這些符號(hào)稱為外部符號(hào)(external)對(duì)應(yīng)于定義在其他模塊中的C函數(shù)和全局變量
- 只被模塊m定義和引用的本地符號(hào)闯传,有的本地鏈接器符號(hào)對(duì)應(yīng)于帶static屬性的C函數(shù)和全局變量谨朝。
可重定位目標(biāo)文件中有匯編器定義的符號(hào)表。
// ELF符號(hào)表?xiàng)l目
typedef struct{
int name; //
int value; // 符號(hào)地址甥绿,可重定位來(lái)說(shuō)是節(jié)起始位置的偏移字币,對(duì)于可執(zhí)行來(lái)說(shuō)是絕對(duì)運(yùn)行的地址。
int size; // 目標(biāo)的大小
char type: 4 // 通常是數(shù)據(jù)或者函數(shù)
char binding: 4 // 本地符號(hào)還是全局符號(hào)
char section // 每個(gè)符號(hào)都和目標(biāo)文件的某個(gè)節(jié)相關(guān)聯(lián)共缕,這個(gè)字段表示某個(gè)節(jié)洗出,該字段是到節(jié)頭部表的索引。
......
}ELF_Symbol
關(guān)于節(jié)頭部表參考網(wǎng)友ELF文件-節(jié)和節(jié)頭
符號(hào)解析
鏈接器解析符號(hào)引用的方法是將每一個(gè)引用與它輸入的可重定位目標(biāo)文件的符號(hào)表中的一個(gè)確定的符號(hào)定義聯(lián)系起來(lái)在這里會(huì)有個(gè)問(wèn)題图谷,比如在C++/Java中函數(shù)可以重載翩活,那么它們函數(shù)名的符號(hào)表不是沖突了嗎?
在這里便贵,編譯器采用了一種叫做name mangling(中文有多種翻譯菠镇,但都感覺怪怪的,這里不翻譯了)的做法承璃。其實(shí)質(zhì)就是將每個(gè)方法和參數(shù)列表組合編碼成對(duì)鏈接器來(lái)說(shuō)唯一的名字利耍。比如Foo::bar(int, long)被編碼為bar__Fooil.這也從另一個(gè)角度說(shuō)明了為什么函數(shù)重載區(qū)分度為不同的參數(shù)類型和個(gè)數(shù)。