got plt類似與Windows PE文件中IAT(Import Address Table)。
要使的代碼地址無關,基本思想就是把與地址相關的部分放到數據段里面。
ELF的做法是在數據段里面建立一個指向這些變量的指針數組,稱為全局偏移表(Global Offset Table,GOT),當代碼需要引用該全局變量時,可以通過GOT中相對應的項間接引用。
GOT本身是放在數據段,所以可以在模塊加載時被修改。延遲綁定,基本思想就是當函數第一次被用到時才進行綁定
一段非常簡單的代碼:
#include <stdio.h> #include <stdlib.h> int helloWorld(){ printf("HelloWorld\n"); return 0; } int main(){ helloWorld(); return 0; }
第一條指令是通過GOT間接跳轉的指令。如果連接器在初始化階段已經初始化該項,並且將puts()的地址填充入該項,那么這個跳轉指令的結果就是我們所期望的,跳轉到puts(),實現函數正確調用。
但是為了實現延遲綁定,鏈接器在初始化階段並沒有將puts()的地址填入該項,而是將下一條指令的地址填入到put@got.plt項中。
所以第一條指令的效果是跳轉到第二條指令,相當於是沒有任何操作的。第二條指令是將一個數字n壓入堆棧,這個數字是puts()的符號引用在重定位表".rel.plt"中的下標。
接着又是一條push指令將模塊ID壓入到堆棧,然后跳轉到0xf7ff04f0執行,那么這個地址是什么地址?答案是_dl_runtime_resolve,下文會給出解釋。
ELF將GOT拆分為兩個表,".got"和".got.plt"。其中".got"用來保存全局變量引用的地址,“.got.plt”用來保存函數引用的地址,對於外部函數的引用全部放在".got.plt"中。
通過上面兩幅圖可以看出:R_386_GLOB_DAT是位於.got段的,R_386_JUMP_SLOT位於.got.plt 段。
".got.plt"前三項是有特殊意義的。
第一項保存的是“.dynamic”段的地址;
第二項保存的是本模塊的ID,也就是之前看到的push進的那個值;
第三項保存的是_dl_runtime_resolve()函數的地址,之前跳轉的地址0xf7ff04f0。
之后就是地址,可以看到都是指向.plt段中,在鏈接時,.plt段通常和代碼段等一起合並成一個可讀可執行的Segment。
可以看到puts@plt的地址0x80482f6位於.plt 中。而且puts在got表項中存放的地址0x080482f6就是puts@plt第二條指令的地址。
參考資料:
《程序員的自我修養》動態鏈接
《深入理解計算機系統》第七章,鏈接