ELF 文件 動態鏈接 - 地址無關代碼(GOT)
Linux 系統中,ELF動態鏈接文件被稱為 動態共享對象(DSO,Dynamic Shared Object),簡稱共享對象
文件拓展名為“.so”
動態鏈接下 一個程序可以被分成若幹個文件:程序的主要部分 - 可執行文件 和 程序所依賴的共享對象(一個或多個.so文件),它們都可稱作為程序的模塊。
動態鏈接文件(共享對象)的裝載地址為0x00000000;這並非工作時的實際地址,實際地址由裝載器根據當前進程地址空間的空閑情況來動態分配一塊足夠大的虛擬地址空間給共享對象。
裝載時重定位
基本思路:在鏈接時對所有的絕對地址的引用不作重定位,而把這一步放在裝載時完成。一旦模塊裝載完成,既目標地址確定,那麽系統將對程序中的所有絕對地址的引用進行重定位。
靜態鏈接時的重定位稱為 鏈接時重定位(Link Time Relocation)
動態鏈接時的重定位稱為 裝載時重定位(Load Time Relocation) gcc -shared
然而光有裝載時重定位也不能解決所有問題,因為對於動態鏈接文件來講,它時可以分為可修改數據部分和不可修改數據部分,如果只有裝載重定位,那每個程序必須都有一個共享對象的副本,這樣會很浪費內存
這樣就引入來下一個話題
地址無關代碼 (gcc -shared -fPIC)
基本思想: 把指令中需要被修改的部分分離出來,跟數據部分放在一起,這樣指令部分就保持不變了,而數據部分為每個進程都有一個副本,
這就是地址無關代碼(PIC, Position-independent Code)技術
共享對象模塊內的地址引用分為四種情況:
1. 模塊內部調用或跳轉:
它們之間位置固定,使用相對地址調用。
2. 模塊內部數據訪問
同樣使用相對尋址(使用當前(指令地址)PC + 偏移量)
3. 模塊與模塊之間的數據訪問(重點)
模塊之間的數據訪問,其目標地址需要等到裝載後才能確定。
這裏的基本思想時將這部分與地址相關的指令的當前地址放入到數據段裏面。
ELF 的做法是在數據段中建立了一個 指向這些變量的指針數組 ,也被稱為 全局偏移表(GOT, Global Offset Table),當代碼需要引用該全局變量時,通過GOT間接引用,
即GOT中記錄著該外部函數真正的地址,裝載器在做動態鏈接時,會查找每個外部符號的地址,然後填充到GOT的對應的項中。
GOT 如何做到與指令無關的呢?(在.so 文件中對應 .got 段)
1. 模塊在編譯期可以確定模塊內部變量相對與當前指令的偏移,同樣在編譯期也可以確定GOT相對於當前指令的偏移。確定GOT的位置和確定內部變量的位置方法上時一樣的,通過
得到PC值然後加上一個偏移量即可得到GOT的位置。當然GOT中的每個地址對應於哪個符號(變量,函數都是符號)由編譯期決定。
4. 模塊間跳用和跳轉
此時與3.中方法類似,只是對應的GOT的位置保存的時目標函數的地址,通過GOT實現間接跳轉
地址無關小結:
各種引用方式 | ||
指令跳轉和調用 | 數據訪問 | |
模塊內部 | 相對跳轉調用 | 相對訪問 |
模塊外部 | GOT間接跳轉訪問 | GOT間接跳轉訪問 |
ELF 文件 動態鏈接 - 地址無關代碼(GOT)