ELF靜態鏈結

2022-07-09 21:36:11 字數 3522 閱讀 5721

一直對elf目標檔案是怎樣鏈結成可執行檔案感到比較的疑惑,elf檔案裡面的重定位段是怎樣解決符號引用問題的?前幾天偶然看了《深入理解計算機系統》裡面講了這個問題,看了之後對裡面的實現機制終於有了一定的理解。

當有鏈結器鏈結多個可重定位的共享物件時,共享物件時怎樣合併的呢?很簡單,鏈結器將相同型別的節合併在一起,比如將所有輸入檔案的.text

合併到輸出檔案的

.text

段,接著是

.data

段,.bss

段等。

鏈結器掃瞄所有的輸入目標檔案,並且獲得它們各個節的長度,屬性和位置,並將輸入目標檔案中的符號表中所有的符號定義和符號引用收集起來,統一放到乙個全域性符號表中。鏈結器能夠獲得所有輸入目標檔案的節長度,並將它們合併,並計算出輸出檔案中各個節合併後的段長度和位置,並建立段和節之間的對映關係。也就是說,在將輸入檔案的各個節對映到段中後,輸入檔案中的各個節在鏈結後的虛擬位址就已經確定了,那麼全域性符號表中的符號位址就可以知道了。

知道了定義符號的虛擬位址,合併了各個節,是不是就大功告成了?顯然沒有這麼簡單,由於輸入給鏈結器的檔案都是可重定位的目標檔案,這些目標檔案中引用符號的地方存放的位址肯定不是最終的虛擬位址,因為這個時候符號位址還不確定。那麼鏈結器在知道了符號的各個虛擬位址後怎樣來修改引用符號的位址為實際的符號虛擬位址呢?這個工作是通過重定位目標檔案中的重定位表來實現的。

對於每個要重定位的elf

節都有乙個對應的重定位表,而乙個重定位表往往就是

elf檔案的乙個節。比如**節

.text

有要重定位的地方,那麼會有乙個相對應的

.rel.text

的節儲存

.text

的重定位表;如果資料節

.data

有要被重定位的地方,也會有乙個相對應的叫

.rel.data

的節與之對應。

每個要被重定位的地方叫做乙個重定位入口(relocation entry

),重定位入口的偏移(

offset

)表示該入口在要被重定位的節中的位置。重定位表的結構很簡單,它是乙個

elf32_rel

結構的陣列,每個陣列元素對應乙個重定位入口。

elf32_rel

定義如下:

typedef struct

elf32_rel elf32_rel;

r_offset表示重定位入口的偏移。對於可重定位檔案來說,這個值是該重定位入口所要修正的位置的第乙個位元組相對於節起始的偏移;對於可執行檔案或共享物件檔案來說,這個值是該重定位入口所要修正的位置的第乙個位元組的虛擬位址。

r_info表示重定位入口的型別和符號。這個成員的高

8位表示重定位入口的型別,低

24位表示重定位入口的符號在符號表中的下標。

第一行迭代每個節,第二行迭代這個節中的每乙個重定位表項,為了簡單,我們假設節是乙個位元組陣列,每個重定位表項都是elf32_rel型別,並假設當這個演算法執行時,鏈結器也已經知道了每個節的執行時位址(定義為addr(

s))和每個符號的執行時位址(定義為addr(

r.symbol

))。根據重定位入口的型別,需要分兩個情況來修改引用位址。

重定位pc

相對引用

假設函式printf

在main

函式被呼叫,現在來看下它在

main

函式中是怎麼被呼叫的:

1b:   e8 fc ff ff ff          call

7

我們可以看到call

指令在節的偏移量為

0x7,這條指令由

1個位元組的操作碼

0xe8和32

位元的引用

0xfffffffc(-4

)組成。我們也可以看到對這個引用的重定位項:

r.offset = 0x7

r.symbol = printf

r.type = r_386_pc32

重定位的項告訴鏈結器修改32位pc

相對引用在節的偏移量為

0x7

addr(s) = addr(.text) = 0x80483b4

addr(r.symbol) = addr(printf) = 0x80483c8

refaddr = addr(s) + r.offset

= 0x80483b4 + 0x7

= 0x80483bb

修改引用符號位址的值,使得引用指向printf

*refptr = (unsigned)(addr(r.symbol) + *refptr - refaddr)

= (unsigned)(0x80483c8 + (-

4) - 0x80483bb)

= (unsigned)(0x9)

這樣,printf

的重定位值就為

0x9。當程式執行時,由於

call

指令時相對

pc定址的,所以計算出的

printf

pc + 0x9 = 0x80483bf + 0x9 = 0x80483c8
重定位絕對位址引用假設定義了下面乙個全域性變數:

int *bufp0 = &buf[0]
由於bufp0

是乙個初始化了的資料物件,它會被存放在

.data

節中。由於它被初始化為全域性變數

buf[0]

的位址,因此

bufp0

需要被重定位。下面是

bufp0

在.data

節中的反彙編**:

00000000

0: 00

0000

00

我們可以看到.data

節中包含了乙個簡單的

32位的引用,指標

bufp0

的值位0x0

。重定位表項告訴鏈結器這是乙個

32位的絕對位址引用,相對於

.data

節的偏移量為

0。它必須被重定位以指向

buf[0]

addr(r.symbol) = addr(buf) = 0x8049454

*refptr = (unsigned)(addr(r.symbol) + *refptr)

= (unsigned)0x8049454 + 0

) = (unsigned)(0x8049454)

ELF動態鏈結

在現代的linux 系統中,假設乙個普通的程式會使用到 c語言靜態庫至少 1mb以上,那麼,如果我們的機器執行 100個這樣的程式,就用浪費近 100mb 的記憶體 如果磁碟有 2000 個這樣的程式,就要浪費 2gb的記憶體。靜態鏈結對程式的更新 發布等也會帶來問題。比如程式program1 使用...

ELF檔案的鏈結和裝載

elf的全稱是可執行可鏈結的格式 符號表的symtab,對應的字串可以在strtab找到 重要的頭有3個elf頭 首部的固定位元組長度 程式表頭 program table header 一般緊鄰elf頭後面,節表頭 section header table 一般對於可執行檔案僅僅借助程式表頭就能想...

動態鏈結 靜態鏈結

在linux系統中,ld鏈結器將彙編器編譯出來的目標檔案和靜態庫里的.a檔案鏈結生成可執行檔案。靜態庫中的.a檔案的 會在靜態鏈結過程中新增到可執行檔案中,可執行檔案會變得很大。與靜態鏈結不同,linux系統的ld鏈結器會將動態庫.so檔案進行符號重定位生成可執行檔案,動態庫.so檔案並不新增到可執...