靜態(tài)鏈接

大多數(shù)編譯系統(tǒng)提供編譯器驅(qū)動(dòng)程序( compiler driver ),它代表用戶在需要時(shí)調(diào)用語言預(yù)處理器、編譯器、匯編器和鏈接器。比如 GCC (GNU Compiler Collection) GUN編譯器套件,其中 GUN 是一個(gè)開源組織,而 GCC 是他們開發(fā)的一款編譯器套件,應(yīng)用十分廣泛。

鏈接 是將各種代碼和數(shù)據(jù)片段收集并組合成為一個(gè)單一文件的過程,這個(gè)文件可被加載(復(fù)制)到內(nèi)存并執(zhí)行。

鏈接前 —— 預(yù)處理、編譯

C 的源代碼。在編寫出來后是不能直接被運(yùn)行的 —— 其實(shí)在完成編寫后只是一個(gè)文本文件而已。

complie.jpg

源代碼需要經(jīng)過:預(yù)處理、編譯和匯編這一過程后產(chǎn)生目標(biāo)文件

  • 預(yù)處理 :在這個(gè)階段會(huì)對(duì)源代碼進(jìn)行插入修改等。主要進(jìn)行:頭文件的插入(#include)、宏的替換(#define)、根據(jù)條件編譯指令(#ifdef、 #ifndef#else、#elif#endif)過濾掉沒必要的代碼和識(shí)別特殊符號(hào)進(jìn)行替換。
  • 編譯:主要是由編譯器完成。主要經(jīng)過:詞法分析、語意分析和中間代碼生成。一般來說會(huì)輸出匯編代碼
  • 匯編:主要是匯編器完成。輸入是有編譯器輸出的中間文件 —— 主要為匯編代碼,輸出機(jī)器語言文件。

目標(biāo)文件格式

在介紹鏈接之前需要對(duì)鏈接的目標(biāo) —— 目標(biāo)文件,的格式有所了解才可以清晰的理解鏈接的過程。

每個(gè)操作系統(tǒng)都有自己的目標(biāo)文件的格式。這里便于理解主要采用 Linux 系統(tǒng)下的目標(biāo)文件 ELF(Executeable Linkable Format) 做介紹。

程序的本質(zhì)就是對(duì)數(shù)據(jù)的處理。所以一段程序可以分成兩大部分:數(shù)據(jù)和指令。其中指令就是代表對(duì)數(shù)據(jù)的操作。所以在 ELF 中是分節(jié)儲(chǔ)存的。如圖:

elf_format.jpg

上圖中除了* File Header (文件頭)以外,還有 .text section、.data section* 和 .bss section

當(dāng)然上圖只是簡(jiǎn)化版的其中還有許多的其他的節(jié),為了便于理解就省略了其他的節(jié) 。

其中 File Header 它描述了整個(gè)文件的文件屬性,包括文件的類型,以及目標(biāo)操作系統(tǒng)的信息等。其中有一個(gè)重要的信息就是節(jié)表,節(jié)表保存了各個(gè) 節(jié)的位置以及屬性等節(jié)的信息。

.text 節(jié) (.text section) 保存了執(zhí)行語句的機(jī)器代碼。.data 節(jié) (.data section) 保存了已經(jīng)初始化的全局變量和局部靜態(tài)變量。.bss節(jié)(.bss section) 保存了未初始化的全局變量和局部靜態(tài)變量。所以總體來說,程序源代碼被編譯以后主要分成兩種段:程序指令和程序數(shù)據(jù)。.text 屬于程序指令,而 .bss.data屬于程序數(shù)據(jù)。

除了圖中出現(xiàn)的節(jié)以外,目標(biāo)文件還會(huì)包含一個(gè)符號(hào)表。符號(hào)表包含了該模塊所定義和引用的所有符號(hào)。

符號(hào):每個(gè)符號(hào)對(duì)應(yīng)于一個(gè)函數(shù)、一個(gè)全局變量或一個(gè)靜態(tài)變量(在C語言中用 static 申明的變量)。

鏈接

鏈接主要由鏈接器完成。鏈接器主要有兩大任務(wù):

  • 符號(hào)解析:將每個(gè)符號(hào)引用正好和一個(gè)符號(hào)定義關(guān)聯(lián)起來
  • 重定位:編譯器和匯編器生成的是地址從0開始的代碼和數(shù)據(jù)。通過把符號(hào)定義與一個(gè)確切的內(nèi)存地址相關(guān)聯(lián),從而修改這個(gè)符號(hào)引用的地方指向與其符號(hào)定義相關(guān)聯(lián)的內(nèi)存地址。

符號(hào)解析

符號(hào)表是由匯編器構(gòu)造的,使用編譯器輸出到匯編語言.s 文件中的符號(hào)。每個(gè)系統(tǒng)上符號(hào)表的數(shù)據(jù)結(jié)構(gòu)都是不同的。

鏈接器解析符號(hào)引用的方法是講每個(gè)引用與它輸入的目標(biāo)文件的符號(hào)表中的一個(gè)確定的符號(hào)定義關(guān)聯(lián)起來。

對(duì)于多重定義的全局符號(hào),鏈接器有他們自己的規(guī)則來處理多重定義的符號(hào)名比如:

  • 不允許有多個(gè)同名的強(qiáng)符號(hào)
  • 如果有一個(gè)強(qiáng)符號(hào)和多個(gè)弱符號(hào)同名,那么選擇強(qiáng)符號(hào)
  • 如果有多個(gè)弱符號(hào)同名,那么從這些若符號(hào)中任意選擇一個(gè)

強(qiáng)符號(hào):函數(shù)和已經(jīng)初始化的全局變量
弱符號(hào):未初始化的全局變量

當(dāng)鏈接器解析和靜態(tài)庫相關(guān)的符號(hào)時(shí),鏈接器會(huì)創(chuàng)建:文件集合E、未解析符號(hào)集合U和已定義符號(hào)集合D。

  1. 對(duì)于輸入的文件f進(jìn)行判斷是目標(biāo)文件還是存檔文件(庫文件):如果f是目標(biāo)文件,那么鏈接器吧f添加到E,在根據(jù)f中的符號(hào)引用的關(guān)系來修改U和D。
  2. 如果f是一個(gè)存檔文件(庫文件),那么鏈接器就嘗試匹配U中未解析的符號(hào)和有存檔文件(庫文件)成員定義的符號(hào)。如果存檔文件(庫文件)中某個(gè)成員m,定義了U集合中的符號(hào),那么就將m添加到E中,根據(jù)m的符號(hào)來修改U和D集合。對(duì)于存檔文件(庫文件)中的所有成員進(jìn)行以上的操作,知道U和D集合都不在改變,這個(gè)時(shí)候?qū)]在E集合中的成員丟棄。
  3. 當(dāng)鏈接器完成所有文件上的掃描以后,如果U集合是非空的話那么就輸出錯(cuò)誤鏈接終止。如果是空的那么就合并和重定位E集合中的文件,構(gòu)建輸出的可執(zhí)行文件。

重定位

在完成了符號(hào)解析以后,此時(shí),鏈接器就知道了整個(gè)模塊的確切大小。那么就可以開始重定位步驟,這個(gè)步驟主要由兩個(gè)部分組成:

  • 重定位節(jié)和符號(hào)定義:合并各個(gè)模塊中相同的節(jié)合并為同一類型的聚合節(jié) 。以及吧運(yùn)行時(shí)內(nèi)存地址賦給新的聚合節(jié)和模塊中的給個(gè)符號(hào)。
  • 重定位節(jié)中的符號(hào)引用:鏈接器修改代碼節(jié)和數(shù)據(jù)節(jié)中對(duì)每個(gè)符號(hào)的引用,使得它們指向正確的運(yùn)行時(shí)地址。

之前介紹過,在編譯器生成目標(biāo)文件時(shí)候,有許多不同的數(shù)據(jù)節(jié)和代碼節(jié)用來存放不同的代碼和變量等。比如: .data 節(jié)是用來存放已經(jīng)初始化的全局和靜態(tài)變量,.text 節(jié)是用來存放已編譯的程序的機(jī)器代碼,.bss 節(jié)是存放未初始化的全局變量和靜態(tài)變量。而在重定位節(jié)和符號(hào)定義的步驟中,鏈接器會(huì)吧各個(gè)目標(biāo)模塊的這些數(shù)據(jù)節(jié)和代碼節(jié)合并成一個(gè)數(shù)據(jù)節(jié)或者是代碼節(jié)。如多個(gè) .data 節(jié)會(huì)合并成一個(gè) .data 節(jié)。同時(shí)在這個(gè)步驟中鏈接器也會(huì)賦給輸入模塊定義的每個(gè)符號(hào)給地址,這樣到這一步驟完成時(shí),程序中的每條指令和全局變量都有唯一的運(yùn)行時(shí)內(nèi)存了。

在所有的符號(hào)都有了唯一的運(yùn)行時(shí)內(nèi)存后,接下來就是要替換掉相關(guān)的符號(hào)引用的以地址來代替了。在匯編器生成目標(biāo)模塊的時(shí)候任何對(duì)外部符號(hào)的引用的地方都會(huì)生成一個(gè)叫重定位的條目,而鏈接器則會(huì)根據(jù)這個(gè)重定位的條目來替換想對(duì)應(yīng)得地址。這就就合并成了一個(gè)可執(zhí)行文件。

到此為止,靜態(tài)鏈接的部分已經(jīng)完成。剩下的就是把可執(zhí)行文件裝載進(jìn)內(nèi)存然后運(yùn)行。同時(shí)動(dòng)態(tài)鏈接和靜態(tài)鏈接也是不同的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容