午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

淺析ELF中的GOT與PLT

 waston 2022-11-24 發(fā)布于上海

一、ELF簡(jiǎn)介

現(xiàn)在PC平臺(tái)流行的可執(zhí)行文件格式主要是Windows下的PE(portable ExecutableLinux的ELF(Excutable Linkable Format)。

編譯器編譯源代碼后生成的文件叫做目標(biāo)文件,從目標(biāo)文件的結(jié)構(gòu)上講,

它是已經(jīng)編譯后的可執(zhí)行文件格式,只是還沒(méi)有鏈接的過(guò)程,其中可能有些符號(hào)或有些地址還沒(méi)有被調(diào)整。其實(shí)它本身就是按照可執(zhí)行文件格式存儲(chǔ)的,只是跟真正的可執(zhí)行文件在結(jié)構(gòu)上稍有不同。

簡(jiǎn)單的說(shuō),目標(biāo)文件就是源代碼編譯后但未進(jìn)行鏈接的那些中間文件(Winodws.objLinux下的.o) ,它跟可執(zhí)行文件的內(nèi)容結(jié)構(gòu)很相似,所以一般跟可執(zhí)行文件格式一起采用一種格式存儲(chǔ)。從某種意義上,可以把目標(biāo)文件和可執(zhí)行文件看成是一種類型的文件。在Windows下,稱之為PE-COEF文件格式,在Linux下,稱之為ELF文件。

另外,不光是可執(zhí)行文件(.exe、elf)按照可執(zhí)行文件格式存儲(chǔ),動(dòng)態(tài)鏈接庫(kù).dllwindows)、.so(linux)以及靜態(tài)連接庫(kù)(.lib(windows)、.a(linux))文件都按可執(zhí)行文件格式存儲(chǔ)。

二、ELF結(jié)構(gòu)

2.1一般目標(biāo)文件將符號(hào)表、調(diào)試信息、字符串等一些鏈接時(shí)所須要的信息,以“節(jié)”(Section)的形式存儲(chǔ),有時(shí)候也叫“段”(Segment),通常不加區(qū)別。

-代碼段(Code Section):存放源代碼編譯后的機(jī)器指令

  •  代碼段常見的名字:".code"   ".text"

-數(shù)據(jù)段(Data Section)  : 存放全局變量和局部靜態(tài)變量

     -     數(shù)據(jù)段常見的名字:“.data,".rodata",".comment",".bss"

    -    未初始化的全部變量和局部變量放在“.bss”里,僅僅作為預(yù)留位置,    沒(méi)有內(nèi)容在文件中也不占據(jù)空間

   -     只讀數(shù)據(jù)段(.rodata) 和注釋信息段(.comment) 


2.2在ELF文件中實(shí)際存在(占據(jù)空間)的也就是“.text”".data" ".rodata"和“.comment”這4個(gè)段


2.3 調(diào)試工具

  • Linux下 可以使用binutils的objdump工具來(lái)查看目標(biāo)文件內(nèi)部結(jié)構(gòu)
  • Linux下 還可以使用readelf來(lái)解析ELF文件

三、段表

與ELF文件中段有關(guān)的重要結(jié)構(gòu)就是段表(Section HeaderTable),該表描述了ELF文件包含的所有段的信息,比如每個(gè)段的段名、長(zhǎng)度、偏移、權(quán)限等屬性。


readelf輸出的結(jié)果就是ELF文件段表的內(nèi)容。


Section Table的長(zhǎng)度為0x1b8也就是440個(gè)字節(jié),11個(gè)段描述符(10個(gè)有效+1個(gè)NULL),每個(gè)段描述符為40個(gè)字節(jié)。

四、動(dòng)態(tài)鏈接

4.1 為了解決靜態(tài)鏈接空間浪費(fèi)和更新困難的問(wèn)題,最簡(jiǎn)單的辦法是把程序的模塊相互分割開來(lái),形成獨(dú)立的文件,而不再將它們靜態(tài)地鏈接在一起。

簡(jiǎn)單的說(shuō),就是不對(duì)那些組成程序的目標(biāo)文件進(jìn)行鏈接,等到程序要運(yùn)行的以后才進(jìn)行鏈接。也就是說(shuō),把鏈接這個(gè)過(guò)程推遲到了運(yùn)行時(shí)再進(jìn)行,這就是動(dòng)態(tài)鏈接的基本思想。


在Linux系統(tǒng)中,ELF動(dòng)態(tài)鏈接文件被稱為動(dòng)態(tài)共享對(duì)象,簡(jiǎn)稱共享對(duì)象,它們一般都是以".so"為拓展名的文件;而在Windows中,動(dòng)態(tài)鏈接文件被稱為動(dòng)態(tài)鏈接庫(kù),他們通常就是平時(shí)常見的“.dll”為拓展名的文件。

4.2 libc簡(jiǎn)介

  在Linux中,常用的C語(yǔ)言庫(kù)運(yùn)行庫(kù)glibc動(dòng)態(tài)鏈接形式保存在"/lib"目錄下,文件名叫做“l(fā)ibc.so”,整個(gè)系統(tǒng)只保留一份C語(yǔ)言庫(kù)的動(dòng)態(tài)鏈接文件“l(fā)ibc.so”,而所有的C語(yǔ)言編寫的、動(dòng)態(tài)鏈接的程序都可以在運(yùn)行時(shí)使用它。

當(dāng)程序被裝載時(shí),系統(tǒng)的動(dòng)態(tài)鏈接器 會(huì)將程序所需的動(dòng)態(tài)鏈接庫(kù)裝載到進(jìn)程的地址空間,并且將程序中所有未決議的符號(hào)綁定到相應(yīng)的動(dòng)態(tài)鏈接庫(kù)中,并進(jìn)行重定位工作。


動(dòng)態(tài)鏈接是把鏈接的過(guò)程從本來(lái)的程序被裝載前推遲到了裝載的時(shí)候。

這樣做雖然很靈活,但是性能受到影響。

這就引出了動(dòng)態(tài)鏈接的優(yōu)化方法。

4.3動(dòng)態(tài)鏈接程序運(yùn)行時(shí)地址空間分布


  • 可以看到,在整個(gè)進(jìn)程虛擬地址空間中,多了幾個(gè)文件的映射。Program1除了用到Lib.so以外,還用到了動(dòng)態(tài)鏈接形式的C語(yǔ)言運(yùn)行庫(kù)libc-2.6.1.so.
  • 可以通過(guò)readelf查看Lib.so的屬性,動(dòng)態(tài)文件除了文件類型與普通程序不同外,其他幾乎與普通程序一樣。

值得注意的是,動(dòng)態(tài)鏈接模塊裝載地址是從地址0x00000000開始的。我們知道這個(gè)地址是無(wú)效地址,而實(shí)際裝載地址是0xb7efc000。從中可以推斷,共享對(duì)象的最終裝載地址在編譯時(shí)是不確定的,而是動(dòng)態(tài)分配的。

原因是共享對(duì)象存在一些對(duì)象地址沖突的問(wèn)題,可能會(huì)包含一些絕對(duì)地址的引用

與此不同的是,可執(zhí)行文件往往是第一個(gè)被加載的文件,它可以選擇一個(gè)固定空間的地址,比如Linux下一般都是0x0804000,windows下一般都是0x0040000.

4.4裝載時(shí)重定位

基本思路是:在鏈接時(shí),對(duì)所有絕對(duì)地址的引用不作重定位,而把這一步推遲到裝載時(shí)再完成。一旦模塊裝載地址確定,即目標(biāo)地址確定,那么系統(tǒng)就對(duì)程序中所有的絕對(duì)地址引用進(jìn)行重定位。

假設(shè)函數(shù)foobar相對(duì)于代碼段的起始地址是0x100,當(dāng)模塊被裝載到0x10000000時(shí),我們假設(shè)代碼段位于模塊的嘴開始,即代碼段的裝載地址也是0x10000000,那么我們就可以確定foobar的地位為0x1000100。這時(shí)候,系統(tǒng)遍歷模塊中的重定位表,把所有對(duì)foobar的地址引用都重定位至0x10000100。

4.5地址無(wú)關(guān)代碼

裝載時(shí)重定位解決了動(dòng)態(tài)模塊中有絕對(duì)地址引用的問(wèn)題,但是又帶了指令部分無(wú)法在多個(gè)進(jìn)程間共享的問(wèn)題。

具體想法就是把程序模塊中共享的指令部分在裝載時(shí)不需要因?yàn)檠b載地址的改變而改變。把指令中那些需要被修改的部分分離出來(lái),跟數(shù)據(jù)部分放在一起,這樣指令部分就可以保持不變,而數(shù)據(jù)部分可以在每個(gè)進(jìn)程中擁有一個(gè)副本。這種方案就是目前的地址無(wú)關(guān)代碼(PIC)技術(shù)

具體方法:先分析模塊中各種類型的地址引用方式,把共享對(duì)象模塊中地址引用按照是否跨模塊分為兩類:模塊內(nèi)部引用和模塊外部引用。
;按照不同的引用方式又可以分為指令引用和數(shù)據(jù)訪問(wèn)。


4.6全局偏移表(GOT)

4.6.1對(duì)于類型三,我們需要用到代碼地址無(wú)關(guān)(PIC)技術(shù),基本的思想就是把跟地址相關(guān)部分放到數(shù)據(jù)段里面。

ELF的做法是在數(shù)據(jù)段里建立一個(gè)指向這些變量的指針數(shù)據(jù),稱為全局偏移表(GOT),當(dāng)代碼需要引用該全局變量時(shí),可以通過(guò)GOT中相對(duì)應(yīng)的項(xiàng)間接引用。

 


如圖,當(dāng)指令需要訪問(wèn)變量b時(shí),程序先會(huì)找到GOT,然后根據(jù)GOT中的變量所對(duì)應(yīng)的項(xiàng)找到變量的目標(biāo)地址。

由于GOT本身是放在數(shù)據(jù)段的,所以它可以在模塊裝載時(shí)被修改,并且每個(gè)進(jìn)程都可以有獨(dú)立的副本,相互不受影響。

可以使用objdump查看GOT的位置,以及GOT中變量的偏移


可以看到GOT在文件中的偏移是0x15d0


可以看到變量bGOT中的偏移是8,相當(dāng)于是第三項(xiàng)(每4個(gè)字節(jié)一項(xiàng))

4.6.2 對(duì)于模塊間調(diào)用和跳轉(zhuǎn),GOT中保存的是目標(biāo)函數(shù)的地址,可以借助GOT中的項(xiàng)進(jìn)行間接跳轉(zhuǎn)。

方法:先得到當(dāng)前指令地址PC,然后加上一個(gè)偏移地址得到函數(shù)地址在GOT中的偏移,然后一個(gè)間接調(diào)用


4.7 延遲綁定(PLT)

4.7.1 基本思想

動(dòng)態(tài)鏈接以犧牲一部份性能為代價(jià)。PLT是另一種優(yōu)化動(dòng)態(tài)鏈接性能的方法。

  • 在動(dòng)態(tài)鏈接下,程序模塊之間包含了大量的函數(shù)引用,所以在程序開始執(zhí)行前,會(huì)耗費(fèi)不少時(shí)間解決函數(shù)引用的符號(hào)查找以及重定位。
  • 但是,在一個(gè)程序運(yùn)行過(guò)程中,可能很多函數(shù)在程序執(zhí)行完時(shí)都不會(huì)被用刀,比如一些錯(cuò)誤處理函數(shù)。
  • 所以ELF采用了一種叫做延遲綁定的做法。
  • 基本思想:就是當(dāng)函數(shù)第一次被用到時(shí)才進(jìn)行綁定。如果沒(méi)有用則不進(jìn)行綁定,所以在開始時(shí)模塊間的函數(shù)調(diào)用都沒(méi)有進(jìn)行綁定,而是需要用到時(shí)才綁定。

4.7.2 具體做法

   -動(dòng)態(tài)鏈接器需要某個(gè)函數(shù)來(lái)完成地址綁定工作,這個(gè)函數(shù)至少要知道這個(gè)地址綁定發(fā)生在哪個(gè)模塊 哪個(gè)函數(shù),如lookup(module,function)。

glibc中,lookup的函數(shù)真名叫做_dl_runtime_reolve()

-   當(dāng)我們調(diào)用某個(gè)外部模塊時(shí),調(diào)用函數(shù)并不直接通過(guò)GOT跳轉(zhuǎn),而是通過(guò)一個(gè)叫做PLT項(xiàng)的結(jié)構(gòu)來(lái)進(jìn)行跳轉(zhuǎn),每個(gè)外部函數(shù)在PLT中都有一個(gè)相應(yīng)的項(xiàng),比如bar()函數(shù)在PLT中的項(xiàng)地址叫做bar@plt,具體實(shí)現(xiàn)

bar@plt

   jmp *(bar@GOT)

   push n

   push moduleID

  jump _dl_runtime_resolve

第一條指令是一條通過(guò)GOT間接跳轉(zhuǎn)指令,bar@GOT表示GOT中保存bar()這個(gè)函數(shù)的相應(yīng)項(xiàng)。

但是為了實(shí)現(xiàn)延遲綁定,連接器在初始化階段沒(méi)有將bar()地址填入GOT,而是將“push n”的地址填入到bar@GOT中,所以第一條指令的效果是跳轉(zhuǎn)到第二條指令,相當(dāng)于沒(méi)有進(jìn)行任何操作。第二條指令將n壓棧,接著將模塊ID壓棧,跳轉(zhuǎn)到_dl_runtime_resolve。實(shí)際上就是lookup(module,function)的調(diào)用。

_dl_runtime_resolve()在工作完成后將bar()真實(shí)地址填入bar@GOT中。

一旦bar()解析完畢,再次調(diào)用bar@plt時(shí),直接就能跳轉(zhuǎn)到bar()的真實(shí)地址。

4.7.3實(shí)際實(shí)現(xiàn)

PLT的真正實(shí)現(xiàn)要更復(fù)雜些,ELF將GOT拆分成兩個(gè)表“.got”和".got.plt",前者用來(lái)保存全局變量引用的地址,后者用來(lái)保存函數(shù)引用的地址。

也就是說(shuō),所有對(duì)于外部函數(shù)的引用被分離出來(lái)放到了“.got.plt”中


 -        參考資料:《程序員的自我修養(yǎng)》

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多