ELF文件中包含了動態(tài)連接器的全路徑,內(nèi)核定位"正確"的動態(tài)連接器在內(nèi)存中的地址是"正確"運行可執(zhí)行文件的保證,參考資料 13討論了如何通過查找動態(tài)連接器在內(nèi)存中的地址以達到顛覆(Subversiver)動態(tài)連接機制的方法。
最后我們討論ELF文件的動態(tài)連接機制。每一個外部定義的符號在全局偏移表(Global Offset Table GOT)中有相應(yīng)的條目,如果符號是函數(shù)則在過程連接表(Procedure Linkage Table PLT)中也有相應(yīng)的條目,且一個PLT條目對應(yīng)一個GOT條目。對外部定義函數(shù)解析可能是整個ELF文件規(guī)范中最復(fù)雜的,下面是函數(shù)符號解析過程的一個描述。
1:代碼中調(diào)用外部函數(shù)func,語句形式為call 0xaabbccdd,地址0xaabbccdd實際上就是符號func在PLT表中對應(yīng)的條目地址(假設(shè)地址為標(biāo)號.PLT2)。
2:PLT表的形式如下
.PLT0: pushl 4(%ebx) /* GOT表的地址保存在寄存器ebx中 */ |
3:查看標(biāo)號.PLT2的語句,實際上是跳轉(zhuǎn)到符號func在GOT表中對應(yīng)的條目。
4:在符號沒有重定位前,GOT表中此符號對應(yīng)的地址為標(biāo)號.PLT2的下一條語句,即是pushl $offset,其中$offset是符號func的重定位偏移量。注意到這是一個二次跳轉(zhuǎn)。
5:在符號func的重定位偏移量壓棧后,控制跳到PLT表的第一條目,把GOT[1]的內(nèi)容壓棧,并跳轉(zhuǎn)到GOT[2]對應(yīng)的地址。
6:GOT[2]對應(yīng)的實際上是動態(tài)符號解析函數(shù)的代碼,在對符號func的地址解析后,會把func在內(nèi)存中的地址設(shè)置到GOT表中此符號對應(yīng)的條目中。
7:當(dāng)?shù)诙握{(diào)用此符號時,GOT表中對應(yīng)的條目已經(jīng)包含了此符號的地址,就可直接調(diào)用而不需要利用PLT表進行跳轉(zhuǎn)。
動態(tài)連接是比較復(fù)雜的,但為了獲得靈活性的代價通常就是復(fù)雜性。其最終目的是把GOT表中條目的值修改為符號的真實地址,這也可解釋節(jié).got包含在可讀可寫段中。
動態(tài)連接是一個非常重要的進步,這意味著庫文件可以被升級、移動到其他目錄等等而不需要重新編譯程序(當(dāng)然,這不意味庫可以任意修改,如函數(shù)入?yún)⒌膫€數(shù)、數(shù)據(jù)類型應(yīng)保持兼容性)。從很大程度上說,動態(tài)連接機制是ELF格式代替a.out格式的決定性原因。如果說面對對象的編程本質(zhì)是面對接口(interface)的編程,那么動態(tài)連接機制則是這種思想的地一個非常典型的應(yīng)用,具體的講,動態(tài)連接機制與設(shè)計模式中的橋接(BRIDGE)方法比較類似,而它的LAZY特性則與代理(PROXY)方法非常相似。動態(tài)連接操作的細節(jié)描述請參閱參考資料 8,9,10,11。通過閱讀命令readelf、objdump 的源代碼以及參考資料 14中所提及的相關(guān)軟件源代碼,可以對ELF文件的格式有更徹底的了解。
總結(jié)
不同時期的可執(zhí)行文件格式深刻的反映了技術(shù)進步的過程,技術(shù)進步通常是針對解決存在的問題和適應(yīng)新的環(huán)境。早期的UNIX系統(tǒng)使用a.out格式,隨著操作系統(tǒng)和硬件系統(tǒng)的進步,a.out格式的局限性越來越明顯。新的可執(zhí)行文件格式COFF在UNIX System VR3中出現(xiàn),COFF格式相對a.out格式最大變化是多了一個節(jié)頭表(section head table),能夠在包含基礎(chǔ)的文本段、數(shù)據(jù)段、BSS段之外包含更多的段,但是COFF對動態(tài)連接和C++程序的支持仍然比較困難。為了解決上述問題, UNIX系統(tǒng)實驗室(UNIX SYSTEM Laboratories USL) 開發(fā)出ELF文件格式,它被作為應(yīng)用程序二進制接口(Application binary Interface ABI)的一部分,其目的是替代傳統(tǒng)的a.out格式。例如,ELF文件格式中引入初始化段.init和結(jié)束段.fini(分別對應(yīng)構(gòu)造函數(shù)和析構(gòu)函數(shù))則主要是為了支持C++程序。1994年6月ELF格式出現(xiàn)在LINUX系統(tǒng)上,現(xiàn)在ELF格式作為UNIX/LINUX最主要的可執(zhí)行文件格式。當(dāng)然我們完全有理由相信,在將來還會有新的可執(zhí)行文件格式出現(xiàn)。
上述三種可執(zhí)行文件格式都很好的體現(xiàn)了設(shè)計思想中分層的概念,由一個總的頭部刻畫了文件的基本要素,再由若干子頭部/條目刻畫了文件的若干細節(jié)。比較一下可執(zhí)行文件格式和以太數(shù)據(jù)包中以太頭、IP頭、TCP頭的設(shè)計,我想我們能很好的感受分層這一重要的設(shè)計思想。參考資料 21從全局的角度討論了各種文件的格式,并提出一個比較夸張的結(jié)論:Everything Is Byte!
最后的題外話:大多數(shù)資料中對a.out格式的評價較低,常見的詞語有黑暗年代(dark ages)、丑陋(ugly)等等,當(dāng)然,從現(xiàn)代的觀點來看,的確是比較簡單,但是如果沒有曾經(jīng)的簡單何來今天的精巧?正如我們今天可以評價石器時代的技術(shù)是ugly,那么將來的人們也可以嘲諷今天的技術(shù)是非常ugly。我想我們也許應(yīng)該用更平和的心態(tài)來對曾經(jīng)的技術(shù)有一個公正的評價。


