動態鏈接下,無論時可執行文件還是共享對象,一旦對其他共享對象有依賴,也就是所有導入的符號時,那么代碼或數據中就會有對於導入符號的引用。而在編譯時期這些導入符號的確切地址時未知的。只有在運行期才能確定真正確切的地址
靜態編譯下,這些未知的地址會被編譯器一一修正。
對於動態鏈接來說,共享文件有兩種編譯方式(gcc -shared 和 gcc -fPIC -shared)
如果不使用PIC模式編譯,那么裝載時肯定是要重定位的,而且時每個進程都有一個副本(相對比較占用內存)
如果使用PIC模式編譯,將會在編譯期生成地址無關代碼(PIC Position-Independent Code),則代碼段可以實現多程序共享,而僅數據段部分會在每個程序中有一個副本(節省內存)
對於這兩種模式來說都是要重定位的,當相對PIC模式編譯的模塊僅需要對數據段進行重定位(因為代碼段中的絕對地址引用部分被分離到了GOT中,而GOT是數據段的一部分;數據段中也可能包含絕對地址的引用,正好重定位數據段)
靜態鏈接中,目標文件里面包含有用於重定位的表:代碼段重定位表“.rel.text”;數據段重定位表“.rel.data”。
動態鏈接中,目標文件的重定位表:“.rel.dyn”對數據引用的修正,修正的位置位於“.got”和數據段;“.rel.plt”對函數引用的修正,修正位置位於“.got.plt”。
可以通過readelf 查看一個動態鏈接文件的重定位表
1 readelf -r XXX.so
這里可以看到幾種重定位入口類型:
R_386_RELATIVE R_386_GLOB_DAT 和 R_386_JUMP_SLOT
R_386_GLOB_DAT(.rel.dyn中針對.got) 和 R_386_JUMP_SLOT(.rel.plt中針對.got.plt)表示被修正的位置只需要直接將符號的地址填入。
而在重定位表中的列Offset 表明了當前符號在“.got" 或“.got.plt”中的偏移,可以根據該值在兩個GOT表中尋找對應的位置,填入(連接器在全局符號表中查找)真實的外部符號地址。
R_386_RELATIVE 是基址重置。有些共享對象的數據段是無法做到地址無關的,比如:
static int a; static int *p = &a;
由於共享對象編譯時,基址是從0開始的,所以a的地址(在未重定位時)是相對與起始地址0的偏移(假設為B),則此時p的值為B;
而當共享對象裝載到指定進程中的地址C時,則變量a的地址將編程B+C,即p的值需要加上裝載的地址B。
R_386_RELATIVE 類型就是專門用來重定位指針變量p這種類型的。