ELF,動(dòng)態(tài)加載及反匯編Disassemble


Linux ELF文件格式

ELF文件有三種類(lèi)型: 可重定位文件:也就是通常稱的目標(biāo)文件,后綴為.o。 共享文件:也就是通常稱的庫(kù)文件,后綴為.so。 可執(zhí)行文件:可執(zhí)行文件的格式與上述兩種文件的格式之間的區(qū)別主要在于觀察的角度不同:一種稱為連接視圖(Linking View),一種稱為執(zhí)行視圖(Execution View)。

ELF文件的總體布局

段(Segment)由若干個(gè)節(jié)(Section)構(gòu)成,節(jié)頭表(Section header table)對(duì)每一個(gè)節(jié)的信息有相關(guān)描述。

ELF頭(ELF header)

ELF header的數(shù)據(jù)結(jié)構(gòu)
典型ELF header by readelf

程序頭表(Program header table)

它是一個(gè)結(jié)構(gòu)數(shù)組,包含了ELF頭表中字段e_phnum定義的條目,結(jié)構(gòu)描述一個(gè)段或其他系統(tǒng)準(zhǔn)備執(zhí)行該程序所需要的信息。

Program header 數(shù)據(jù)結(jié)構(gòu)
Program header舉例

對(duì)一個(gè)ELF可執(zhí)行程序而言,一個(gè)基本的段是標(biāo)記p_type為PT_INTERP的段,它表明了運(yùn)行此程序所需要的程序解釋器(/lib64/ld-linux-x86-64.so.2),實(shí)際上也就是動(dòng)態(tài)連接器(dynamic linker)。最重要的段是標(biāo)記p_type為PT_LOAD的段,它表明了為運(yùn)行程序而需要加載到內(nèi)存的數(shù)據(jù)。查看上面實(shí)際輸入,可以看見(jiàn)有兩個(gè)可LOAD段,第一個(gè)為只讀可執(zhí)行(FLg為R E),第二個(gè)為可讀可寫(xiě)(Flg為RW)。

節(jié)到段的映射

.text 段:存儲(chǔ)只讀程序
.data 段:存儲(chǔ)已經(jīng)初始化的全局變量和靜態(tài)變量
.bss 段:存儲(chǔ)未初始化的全局變量和靜態(tài)變量,因?yàn)檫@些變量的值為0,所以這個(gè)段在文件當(dāng)中不占據(jù)空間
.rodata 段:存儲(chǔ)只讀數(shù)據(jù),比如字符串常量

PIC

ELF可以生成一種特殊的代碼——與位置無(wú)關(guān)的代碼(position-independent code,PIC)。用戶對(duì)gcc使用-fPIC指示GNU編譯系統(tǒng)生成PIC代碼。它是實(shí)現(xiàn)共享庫(kù)或共享可執(zhí)行代碼的基礎(chǔ).這種代碼的特殊性在于它可以加載到內(nèi)存地址空間的任何地址執(zhí)行.這也是加載器可以很方便的在進(jìn)程中動(dòng)態(tài)鏈接共享庫(kù)。 PIC的實(shí)現(xiàn)運(yùn)用了一個(gè)事實(shí),就是代碼段中任何指令和數(shù)據(jù)段中的任何變量之間的距離都是一個(gè)與代碼段和數(shù)據(jù)段的絕對(duì)存儲(chǔ)器位置無(wú)關(guān)的常量。

因此,編譯器在數(shù)據(jù)段開(kāi)始的地方創(chuàng)建了一個(gè)表.叫做全局偏移量表(global offset table.GOT)。GOT包含每個(gè)被這個(gè)目標(biāo)模塊引用的全局?jǐn)?shù)據(jù)目標(biāo)的表目。編譯器還為GOT中每個(gè)表目生成一個(gè)重定位記錄。在加載時(shí),動(dòng)態(tài)鏈接器會(huì)重定位GOT中的每個(gè)表目,使得它包含正確的絕對(duì)地址。PIC代碼在代碼中實(shí)現(xiàn)通過(guò)GOT間接的引用每個(gè)全局變量,這樣,代碼中本來(lái)簡(jiǎn)單的數(shù)據(jù)引用就變得復(fù)雜,必須加入得到GOT適當(dāng)表目?jī)?nèi)容的指令。對(duì)只讀數(shù)據(jù)的引用也根據(jù)同樣的道理,所以,加上 IC編譯成的代碼比一般的代碼開(kāi)銷(xiāo)大。

如果一個(gè)elf可執(zhí)行文件需要調(diào)用定義在共享庫(kù)中的任何函數(shù),那么它就有自己的GOT和PLT(procedure linkage table,過(guò)程鏈接表).這兩個(gè)節(jié)之間的交互可以實(shí)現(xiàn)延遲綁定(lazy binging),這種方法將過(guò)程地址的綁定推遲到第一次調(diào)用該函數(shù)。為了實(shí)現(xiàn)延遲綁定,GOT的頭三條表目是特殊的:GOT[0]包含.dynamic 段的地址,.dynamic段包含了動(dòng)態(tài)鏈接器用來(lái)綁定過(guò)程地址的信息,比如符號(hào)的位置和重定位信息;GOT[1]包含動(dòng)態(tài)鏈接器的標(biāo)識(shí);GOT[2]包含動(dòng)態(tài)鏈接器的延遲綁定代碼的入口點(diǎn)。GOT的其他表目為本模塊要引用的一個(gè)全局變量或函數(shù)的地址。PLT是一個(gè)以16字節(jié)(32位平臺(tái)中)表目的數(shù)組形式出現(xiàn)的代碼序列。其中PLT[0]是一個(gè)特殊的表目,它跳轉(zhuǎn)到動(dòng)態(tài)鏈接器中執(zhí)行;每個(gè)定義在共享庫(kù)中并被本模塊調(diào)用的函數(shù)在PLT中都有一個(gè)表目,從 PLT[1]開(kāi)始.模塊對(duì)函數(shù)的調(diào)用會(huì)轉(zhuǎn)到相應(yīng)PLT表目中執(zhí)行,這些表目由三條指令構(gòu)成。第一條指令是跳轉(zhuǎn)到相應(yīng)的GOT存儲(chǔ)的地址值中.第二條指令把函數(shù)相應(yīng)的ID壓入棧中,第三條指令跳轉(zhuǎn)到PLT[O]中調(diào)用動(dòng)態(tài)鏈接器解析函數(shù)地址,并把函數(shù)真正地址存入相應(yīng)的GOT表目中。被調(diào)用函數(shù)GOT相應(yīng)表目中存儲(chǔ)的最初地址為相應(yīng)PLT表目中第二條指令的地址值,函數(shù)第一次被調(diào)用后.GOT表目中的值就為函數(shù)的真正地址。因此,第一次調(diào)用函數(shù)時(shí)開(kāi)銷(xiāo)比較大.但是其后的每次調(diào)用都只會(huì)花費(fèi)一條指令和一個(gè)間接的存儲(chǔ)器引用。?

關(guān)鍵節(jié)(section)詳解

.got

這是我們常說(shuō)的GOT, 即Global Offset Table, 全局偏移表. 這是鏈接器在執(zhí)行鏈接時(shí)
實(shí)際上要填充的部分, 保存了所有外部符號(hào)的地址信息.
不過(guò)值得注意的是, 在i386架構(gòu)下, 除了每個(gè)函數(shù)占用一個(gè)GOT表項(xiàng)外,GOT表項(xiàng)還保留了
3個(gè)公共表項(xiàng), 每項(xiàng)32位(4字節(jié)), 保存在前三個(gè)位置, 分別是:

got[0]: 本ELF動(dòng)態(tài)段(.dynamic段)的裝載地址
got[1]: 本ELF的link_map數(shù)據(jù)結(jié)構(gòu)描述符地址
got[2]:?_dl_runtime_resolve函數(shù)的地址

.plt

這也是我們常說(shuō)的PLT, 即Procedure Linkage Table, 進(jìn)程鏈接表. 這個(gè)表里包含了一些代碼, 用來(lái)
(1)調(diào)用鏈接器來(lái)解析某個(gè)外部函數(shù)的地址, 并填充到.got.plt中, 然后跳轉(zhuǎn)到該函數(shù); 或者
(2)直接在.got.plt中查找并跳轉(zhuǎn)到對(duì)應(yīng)外部函數(shù)(如果已經(jīng)填充過(guò)).

.got.plt

.got.plt相當(dāng)于.plt的GOT全局偏移表, 其內(nèi)容有兩種情況,
1)如果在之前查找過(guò)該符號(hào),內(nèi)容為外部函數(shù)的具體地址.
2)如果沒(méi)查找過(guò), 則內(nèi)容為跳轉(zhuǎn)回.plt的代碼, 并執(zhí)行查找.

.plt.got

不知何用

各section讀寫(xiě)屬性

函數(shù)調(diào)用過(guò)程

第一次call:func -> jump to .plt[0] -> jump to .got.plt 這時(shí)地址內(nèi)容尚未更新,為.plt[1] -> jump to .plt[1],push函數(shù)idx? -> jump to .plt初始地址 -> jump to .got.plt got[2], 相當(dāng)于call?_dl_runtime_resolve ->修改 .got.plt 地址。

第二次call:func -> jump to .plt[0] -> jump to .got.plt, 這時(shí)內(nèi)容有效

這個(gè)過(guò)程叫延時(shí)加載或Lazy加載。因此需要.plt可執(zhí)行,.got.plt可寫(xiě)

反編譯工具

readelf

objcopy

objdump

XED:?https://intelxed.github.io/

./xed -ir xxxx.bin

引用:

https://www.ibm.com/developerworks/cn/linux/l-excutff/index.html
https://vaqeteart.iteye.com/blog/1118753
https://www.cnblogs.com/pannengzhi/p/2018-04-09-about-got-plt.html

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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