鏈接器的任務(wù)
在上一篇文章中,我們提到鏈接是將多個(gè)可重定位目標(biāo)文件鏈接成一個(gè)可執(zhí)行目標(biāo)文件。必須要完成2件事
- 符號解析,將每一個(gè)符號引用的定義聯(lián)系起來,比如foo.c中的num定義的地方是在main.c中。
- 重定位,編譯器和匯編器生成從0地址開始的代碼和數(shù)據(jù)節(jié)(下文會提到),鏈接器把每一個(gè)符號定義與存儲器位置聯(lián)系起來,然后修改所有對這些符號的引用。使得它們有正確的存儲器地址。從而重定位這些節(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í)被動態(tài)的加載到存儲器并鏈接。
可重定位目標(biāo)文件
||
|---------|-----------|
|ELF頭|包括目標(biāo)ELF頭的大小,目標(biāo)文件的類型,機(jī)器類型,節(jié)頭部表等信息主要用來幫助鏈接器語法分析個(gè)解釋目標(biāo)文件的信息。
|.text|已編譯程序的機(jī)器代碼|
|.rodata|只讀數(shù)據(jù),比如"hello world"字符串和開關(guān)語句跳轉(zhuǎn)表
|.data|已初始化的全局C變量
|.bss|未初始化的全局C變量
|.symtab|符號表,存放程序定義和引用的函數(shù)和全局變量的消息。有別于編譯器的符號表
|......|......|
符號和符號表
每個(gè)可重定位目標(biāo)模塊m都有一個(gè)符號表,它包含m所定義和引用的符號信息。
- 由m定義并能被其他模塊所引用的全局符號,對應(yīng)于非靜態(tài)的static屬性的C函數(shù)和一級定義為不帶static屬性的全局變量。
- 由其他模塊定義并被模塊m引用的全局符號。這些符號稱為外部符號(external)對應(yīng)于定義在其他模塊中的C函數(shù)和全局變量
- 只被模塊m定義和引用的本地符號,有的本地鏈接器符號對應(yīng)于帶static屬性的C函數(shù)和全局變量。
可重定位目標(biāo)文件中有匯編器定義的符號表。
// ELF符號表?xiàng)l目
typedef struct{
int name; //
int value; // 符號地址,可重定位來說是節(jié)起始位置的偏移,對于可執(zhí)行來說是絕對運(yùn)行的地址。
int size; // 目標(biāo)的大小
char type: 4 // 通常是數(shù)據(jù)或者函數(shù)
char binding: 4 // 本地符號還是全局符號
char section // 每個(gè)符號都和目標(biāo)文件的某個(gè)節(jié)相關(guān)聯(lián),這個(gè)字段表示某個(gè)節(jié),該字段是到節(jié)頭部表的索引。
......
}ELF_Symbol
關(guān)于節(jié)頭部表參考網(wǎng)友ELF文件-節(jié)和節(jié)頭
符號解析
鏈接器解析符號引用的方法是將每一個(gè)引用與它輸入的可重定位目標(biāo)文件的符號表中的一個(gè)確定的符號定義聯(lián)系起來在這里會有個(gè)問題,比如在C++/Java中函數(shù)可以重載,那么它們函數(shù)名的符號表不是沖突了嗎?
在這里,編譯器采用了一種叫做name mangling(中文有多種翻譯,但都感覺怪怪的,這里不翻譯了)的做法。其實(shí)質(zhì)就是將每個(gè)方法和參數(shù)列表組合編碼成對鏈接器來說唯一的名字。比如Foo::bar(int, long)被編碼為bar__Fooil.這也從另一個(gè)角度說明了為什么函數(shù)重載區(qū)分度為不同的參數(shù)類型和個(gè)數(shù)。