無論是靜態鏈接還是動態鏈接,初始都是操作系統讀取可執行文件的FILE_HEADER,以檢查文件格式、操作權限等屬性,然後根據段表獲取各個”segment”的VMA虛擬裝載位置、文件地址和操作屬性RWXP等,再根據相似屬性原則相連原則完成裝載,而後將控制權交給文件頭結構中e_entry
入口地址(ELF程序的入口虛擬地址,可重定位文件不可執行,故而爲0,靜態鏈接的可執行文件便是指向運行庫main初始化函數,動態鏈接的可執行文件則是指向動態鏈接程序的入口地址)。
在Linux下,動態鏈接器ld.so實際上本身也是一個共享對象.so(但它和別的共享對象不同的是,它不能引用其他共享對象),操作系統將ld.so裝載進虛擬進程空間後,將把控制權移交給動態鏈接器的程序入口。ld.so獲取控制權後,首先執行一系列自身的初始化操作,然後將會按照鏈式法則順序加載一系列共享對象.so。當所有.so裝載完畢後,纔將控制權重新交給可執行文件本身。
現在面對需要動態鏈接的可執行文件,系統該採用的動態鏈接器在哪?哪種動態鏈接器?這是由ELF可執行文件決定的。.interp段(interpreter解釋器)的內容很簡單,保留的就是:該可執行文件所需的動態鏈接器的路徑。
.interp段中的動態鏈接器路徑/lib/ld-linux.so.2.是個軟鏈接,其實指向的應該是/lib/ld-2.6.1.so,當系統Glibc庫升級時,該軟連接就是指向新的動態鏈接器,而不需要修改.interp中的路徑名來配合系統的升級。
也可以通過readlf
指令來獲取一個可執行文件所需要的動態鏈接器的路徑
$readlf -l a.out | grep interpreter
[Requesting program interpreter: /lib/ld-linux.so.2]
前面的文章說道外部函數地址GOT子表“.got.plt”的第一項便是指向.dynamic段。類似於.interp這樣的段,ELF中還有幾個段也是專門用來動態鏈接的,比如.dynamic段和.dynsym段。
.dynamic段保存了動態鏈接需要的基本信息:依賴哪些共享對象、動態鏈接符號表的位置、動態鏈接重定位表的位置、共享對象初始化代碼的地址。.dynamic段中是如下結構體數組,該結構體元素用來定義動態鏈接所需的各基本信息。
typedef struct{
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
…..
若是想直接查看當前文件依賴於那些共享對象,可用ldd指令
$ldd program1
linux-gate.so.1 => (0xffffe000) //該文件屬於內核虛擬共享對象,處在虛擬進程空間的高端,屬於內核區,涉及到內核加載
./Lib.so (0xb7f62000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e0d000)
/lib/ld-linux.so.2 (0xb7f66000)
可以看到.dynamic段像以前的FILE_HEADER一樣只是提供具體所需對象的索引數組,下面來繼續查看一些動態鏈接所需的關鍵信息,如動態符號表.dynsym。
爲了表示動態鏈接的模塊之間的符號導入導出關係,ELF提供了動態符號表.dynsym,與.symtab不同,前者只保存和動態鏈接相關的符號,而模塊內部符號是不保存的(專表專用,加速動態鏈接符號查找過程),故而ELF動態鏈接模塊同時擁有兩個表.symtab和.dynsym, .symtab包括所有符號,包括.dynsym中的符號。
動態符號表也需要一些輔助表,如動態符號字符串表.dynstr (dynamic string table)(因爲符號名稱是非等項的,爲了方便dynsym表的管理,顯然需要字符串這種不穩定分子轉移出來以實現dynsym表項等長),爲了加快符號的查找過程,往往還有輔助的符號哈希表.hash。
.so共享對象需要重定位的原因在於導入符號的存在,無論是可執行文件還是.so,一旦依賴於其他共享對象,也就是說有導入符號存在,那麼它的代碼或數據中就會有對導入符號的引用。在編譯時這些導入符號的地址未知,靜態鏈接中,這些未知的地址引用在最終鏈接時被修正;而在動態鏈接中,導入符號的地址只有在運行時才確定,所以需要在運行時將這些導入符號的引用修正,即需要重定位。(類似於Windows下的符號導出表和符號導入表)
對於使用PIC技術的可執行文件或共享對象來說,雖然他們的代碼段不需要重定位(因爲地址無關),但是.data段有可能包含了對絕對地址的引用,因爲代碼段中絕對地址相關的部分被分離了出來,變成了GOT,而GOT實際上是數據段的一部分。動態鏈接的文件中,有.rel.dyn和.rel.plt,其中.rel.dyn是對數據引用的修正,修正的位置位於.got以及數據段,而.rel.plt(延遲綁定)是對函數引用的修正,所修正的位置位於.got.plt。
靜態鏈接用到的重定位入口
1.R_386_32 絕對地址修正
2.R_386_PC32下一指令相對地址修正
動態鏈接用到的重定位入口類型
1.R_386_Relative:基址重置rebasing,基址+偏移地址
2.R_386_GLOB_DAT:對.got表中的數據符號的重定位標誌,在.got表中填充該數據符號的絕對地址
3.R_386_JUMP_SLOT:對.got.plt的重定位標誌,只需要根據.rel.plt表給出的該函數符號在.got.plt的偏移量找到該函數符號的位置,填寫上該函數加載後的地址
重定位表的意義在於提示動態鏈接器要修正的外部符號信息,可以配合實現PLT等延遲綁定機制,實現動態鏈接的快速進行。