作業系統通常使用動態連結的方法來提高程式運行的效率。在動態連結的情況下,程式載入的時候並不會把連結庫中所有函數都一起載入進來,而是程式執行的時候按需加載,如果有函數並沒有被調用,那麼它就不會在程式生命中被加載進來。這樣的設計就能提高程式運作的流暢度,也減少了記憶體空間。而且現代作業系統不允許修改程式碼段,只能修改資料段,那麼GOT表與PLT表就應運而生。
我們先簡單來看一個例子
我們跟進scanf@plt
會發現,有三行程式碼
jmp 一个地址 push 一个值到栈里面 jmp 一个地址
看函數的名字就可以知道這是scanf函數的plt表,先不急著去了解plt是做什麼用的,我們繼續往下看我們先看一下第一個jmp是什麼跳到哪裡。
其實這是plt表對應函數的got表,而且我們會發現0x201020的值是壓棧指令的位址,其他地方為0,此時就想問:
一、got表與plt表有什麼意義,為什麼要跳來跳去?
二、got表與plt表有什麼聯繫,有木有什麼對應關係?
那麼帶著疑問先看答案,再去印證我們要明白作業系統通常使用動態連結的方法來提高程式運行的效率,而且不能回寫到程式碼片段上。
在上面例子中我們可以看到,call scanf —> scanf的plt表—>scanf的got表,至於got表的值暫時先不管,我們此刻可以形成這樣一個思維,它能從got表中找到真實的scanf函數供程式載入運行。
我們這麼認為後,那麼這就變成了一個間接尋址的過程
#我們就把獲取資料段存放函數位址的那一小段程式碼稱為PLT(Procedure Linkage Table)程序連結表存放函數位址的資料段稱為GOT(Global Offset Table)全域偏移表。我們形成這麼一個思考後,再去仔細理解裡面的細節。
已經明白了這麼一個大致過程後,我們來看一下這其中是怎麼一步一步調用的上面有幾個疑點需要去解決:
一、got表怎麼知道scanf函數的真實位址?
二、got表與plt表的結構是什麼?我們先來看plt表剛剛發現scanf@plt表第三行程式碼是jmp 一個位址,跟進看一下是什麼
其實這是一個程式PLT表的開始(plt[0]),它做的事情是:
push got[1] jmp **got[2]
後面是每個函數的plt表。此時我們再看一下這個神秘的GOT表
除了這兩個(printf和scanf函數的push 0xn的位址,也就是對應的plt表的第二條碼的位址),其它的got[1], got[2] 為0,那麼plt表指向為0的got表幹嘛?因為我們落下了一個條件,現代作業系統不允許修改程式碼段,只能修改資料段,也就是回寫,更專業的稱謂應該是執行時間重定位。我們把程式運行起來,我們之前的地址和保存的內容就變了在這之前,我們先把鏈接時的內容保存一下,做一個對比
② 寻找printf的plt表 ③ jmp到plt[0] ④ jmp got[2] -> 0x00000 ⑤⑥ printf和scanf的got[3] got[4] -> plt[1] plt[2]的第二条代码的地址 ⑦⑧ 证实上面一点
運行程序,在scanf處下斷點
可以發現,此時scanf@plt表變了,查看got[4]裡內容
還是push 0x1所在位址繼續調試,直到這裡,got[4]位址被修改
此時想問了,這是哪裡?
然後就是got[2]中call<_dl_fixup>從而修改got[3]中的位址;
那么问题就来了,刚才got[2]处不是0吗,怎么现在又是这个(_dl_runtime_resolve)?这就是运行时重定位。
其实got表的前三项是:
got[0]:address of .dynamic section 也就是本ELF动态段(.dynamic段)的装载地址 got[1]:address of link_map object( 编译时填充0)也就是本ELF的link_map数据结构描述符地址,作用:link_map结构,结合.rel.plt段的偏移量,才能真正找到该elf的.rel.plt got[2]:address of _dl_runtime_resolve function (编译时填充为0) 也就是_dl_runtime_resolve函数的地址,来得到真正的函数地址,回写到对应的got表位置中。
那么此刻,got表怎么知道scanf函数的真实地址?
这个问题已经解决了。我们可以看一下其中的装载过程:
说到这个,可以看到在_dl_runtimw_resolve之前和之后,会将真正的函数地址,也就是glibc运行库中的函数的地址,回写到代码段,就是got[n](n>=3)中。也就是说在函数第一次调用时,才通过连接器动态解析并加载到.got.plt中,而这个过程称之为延时加载或者惰性加载。
到这里,也要接近尾声了,当第二次调用同一个函数的时候,就不会与第一次一样那么麻烦了,因为got[n]中已经有了真实地址,直接jmp该地址即可。
以上是怎麼深入理解GOT表和PLT表的詳細內容。更多資訊請關注PHP中文網其他相關文章!