十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
本篇文章為大家展示了怎么深入理解GOT表和PLT表,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。
創(chuàng)新互聯(lián)是一家專業(yè)提供梅縣企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、H5高端網(wǎng)站建設(shè)、小程序制作等業(yè)務(wù)。10年已為梅縣眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設(shè)公司優(yōu)惠進(jìn)行中。
操作系統(tǒng)通常使用動態(tài)鏈接的方法來提高程序運(yùn)行的效率。在動態(tài)鏈接的情況下,程序加載的時候并不會把鏈接庫中所有函數(shù)都一起加載進(jìn)來,而是程序執(zhí)行的時候按需加載,如果有函數(shù)并沒有被調(diào)用,那么它就不會在程序生命中被加載進(jìn)來。這樣的設(shè)計(jì)就能提高程序運(yùn)行的流暢度,也減少了內(nèi)存空間。而且現(xiàn)代操作系統(tǒng)不允許修改代碼段,只能修改數(shù)據(jù)段,那么GOT表與PLT表就應(yīng)運(yùn)而生。
我們先簡單看一個例子

我們跟進(jìn)一下scanf@plt

會發(fā)現(xiàn),有三行代碼
jmp 一個地址 push 一個值到棧里面 jmp 一個地址
看函數(shù)的名字就可以知道這是scanf函數(shù)的plt表,先不著急去了解plt是做什么用的,我們繼續(xù)往下看我們先看一下第一個jmp是什么跳到哪里。

其實(shí)這是plt表對應(yīng)函數(shù)的got表,而且我們會發(fā)現(xiàn)0x201020的值是壓棧命令的地址,其他地方為0,此時就想問:
一、got表與plt表有什么意義,為什么要跳來跳去?
二、got表與plt表有什么聯(lián)系,有木有什么對應(yīng)關(guān)系?
那么帶著疑問先看答案,再去印證我們要明白操作系統(tǒng)通常使用動態(tài)鏈接的方法來提高程序運(yùn)行的效率,而且不能回寫到代碼段上。
在上面例子中我們可以看到,call scanf —> scanf的plt表 —>scanf的got表,至于got表的值暫時先不管,我們此刻可以形成這樣一個思維,它能從got表中找到真實(shí)的scanf函數(shù)供程序加載運(yùn)行。
我們這么認(rèn)為后,那么這就變成了一個間接尋址的過程

我們就把獲取數(shù)據(jù)段存放函數(shù)地址的那一小段代碼稱為PLT(Procedure Linkage Table)過程鏈接表存放函數(shù)地址的數(shù)據(jù)段稱為GOT(Global Offset Table)全局偏移表。我們形成這么一個思維后,再去仔細(xì)理解里面的細(xì)節(jié)。
已經(jīng)明白了這么一個大致過程后,我們來看一下這其中是怎么一步一步調(diào)用的上面有幾個疑點(diǎn)需要去解決:
一、got表怎么知道scanf函數(shù)的真實(shí)地址?
二、got表與plt表的結(jié)構(gòu)是什么?我們先來看plt表剛才發(fā)現(xiàn)scanf@plt表第三行代碼是 jmp 一個地址 ,跟進(jìn)看一下是什么

其實(shí)這是一個程序PLT表的開始(plt[0]),它做的事情是:
push got[1] jmp **got[2]
后面是每個函數(shù)的plt表。此時我們再看一下這個神秘的GOT表

除了這兩個(printf和scanf函數(shù)的push 0xn的地址,也就是對應(yīng)的plt表的第二條代碼的地址),其它的got[1], got[2] 為0,那么plt表指向?yàn)?的got表干什么呢?因?yàn)槲覀兟湎铝艘粋€條件,現(xiàn)代操作系統(tǒng)不允許修改代碼段,只能修改數(shù)據(jù)段,也就是回寫,更專業(yè)的稱謂應(yīng)該是運(yùn)行時重定位。我們把程序運(yùn)行起來,我們之前的地址和保存的內(nèi)容就變了在這之前,我們先把鏈接時的內(nèi)容保存一下,做一個對比

② 尋找printf的plt表 ③ jmp到plt[0] ④ jmp got[2] -> 0x00000 ⑤⑥ printf和scanf的got[3] got[4] -> plt[1] plt[2]的第二條代碼的地址 ⑦⑧ 證實(shí)上面一點(diǎn)
運(yùn)行程序,在scanf處下斷點(diǎn)

可以發(fā)現(xiàn),此時scanf@plt表變了,查看got[4]里內(nèi)容

依然是push 0x1所在地址繼續(xù)調(diào)試,直到這里,got[4]地址被修改

此時想問了,這是哪里?


然后就是got[2]中call<_dl_fixup>從而修改got[3]中的地址;
那么問題就來了,剛才got[2]處不是0嗎,怎么現(xiàn)在又是這個(_dl_runtime_resolve)?這就是運(yùn)行時重定位。
其實(shí)got表的前三項(xiàng)是:
got[0]:address of .dynamic section 也就是本ELF動態(tài)段(.dynamic段)的裝載地址 got[1]:address of link_map object( 編譯時填充0)也就是本ELF的link_map數(shù)據(jù)結(jié)構(gòu)描述符地址,作用:link_map結(jié)構(gòu),結(jié)合.rel.plt段的偏移量,才能真正找到該elf的.rel.plt got[2]:address of _dl_runtime_resolve function (編譯時填充為0) 也就是_dl_runtime_resolve函數(shù)的地址,來得到真正的函數(shù)地址,回寫到對應(yīng)的got表位置中。
那么此刻,got表怎么知道scanf函數(shù)的真實(shí)地址?
這個問題已經(jīng)解決了。我們可以看一下其中的裝載過程:


說到這個,可以看到在_dl_runtimw_resolve之前和之后,會將真正的函數(shù)地址,也就是glibc運(yùn)行庫中的函數(shù)的地址,回寫到代碼段,就是got[n](n>=3)中。也就是說在函數(shù)第一次調(diào)用時,才通過連接器動態(tài)解析并加載到.got.plt中,而這個過程稱之為延時加載或者惰性加載。
到這里,也要接近尾聲了,當(dāng)?shù)诙握{(diào)用同一個函數(shù)的時候,就不會與第一次一樣那么麻煩了,因?yàn)間ot[n]中已經(jīng)有了真實(shí)地址,直接jmp該地址即可。
上述內(nèi)容就是怎么深入理解GOT表和PLT表,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。