本質(zhì):
鏈接器本質(zhì)上也是一個程序:是將編譯器產(chǎn)生的目標文件打包成可執(zhí)行文件或者庫文件或者目標文件的程序。
靜態(tài)鏈接和動態(tài)鏈接
靜態(tài)鏈接的意思是說把所有的機器指令一股腦全部打包到可執(zhí)行程序中,
動態(tài)鏈接的意思是我們不把動態(tài)鏈接的部分打包到可執(zhí)行程序,而是在可執(zhí)行程序運行起來后去內(nèi)存中找動態(tài)鏈接的那部分代碼。
動態(tài)鏈接兩種情況
- load-time dynamic linking(加載時動態(tài)鏈接)
- run-time dynamic linking(運行時動態(tài)鏈接)
符號表
第一階段
編譯器在將源文件編譯生成目標文件時可以確定一下兩件事:
- 定義在該源文件中函數(shù)的內(nèi)存地址
- 定義在該源文件中全局變量的內(nèi)存地址
這里的內(nèi)存地址其實只是相對地址,相對于誰的呢,相對于自己的。為什么只是一個相對地址。因為在生成一個目標文件時編譯器并不知道這個目標文件要和哪些目標文件進行鏈接生成最后的可執(zhí)行文件,而鏈接器是知道要鏈接哪些目標文件的。因此編譯器僅僅生成一個相對地址。
編譯器在編譯過程中每次遇到一個全局變量或者函數(shù)名都會在符號表中添加一項,最終編譯器會統(tǒng)計出如下所示的一張符號表。
相對地址是編譯器在編譯過程中確定了,在鏈接器完成后被鏈接器修正為最終地址,而對于編譯器沒有確定的所引用的外部函數(shù)以及變量的地址,編譯器將其記錄在了.rel.text和.rel.data中。
第二階段
由于在第一階段中,所有函數(shù)以及數(shù)據(jù)都有了最終地址,因此重定位的第二階段就相對簡單了。我們知道編譯器引用外部變量時將機器指令中的引用地址設(shè)置為空(比如call 0x000000),并將該信息記錄在了目標文件的.rel.text以及.rel.data段中。因此在這個階段鏈接器依次掃描所有的.rel.text以及.rel.data段并找到相應(yīng)變量的最終地址(這些位置都已在第一階段確定),并將機器指令中的0x000000修正為所引用變量的最終地址就可以了。
符號表編譯的時候開始生成,最終完成是在鏈接后。
重定向
鏈接器會將所有的目標文件進行合并,所有目標文件的數(shù)據(jù)段合并到可執(zhí)行文件的數(shù)據(jù)段,所有目標文件的代碼段合并到可執(zhí)行文件的代碼段。當(dāng)所有合并完成后,各個目標文件中的相對地址也就確定了。因此在這個階段,鏈接器需要修正目標文件中的相對地址。