天天看點

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

程式執行前經曆了啥

當我們編寫了一個程式,你想法設法讓他運作,在你的編輯器編譯運作。

編譯是啥,運作前經曆了啥。

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位
用一張圖就是這樣表示的。

預處理器cpp:

gcc -E a.c -o a.i
           

假設看一個程式沒有include:

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位
深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

加入加了include

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位
深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位
是以預處理器cpp就是對這個程式進行預處理,可以展開頭檔案或者宏替換還有去掉注釋,有時候頭檔案裡面有條件編譯,
也可以翻譯成一個.i檔案。
           

編譯器cc1:

編譯階段是檢查文法,生成彙編:gcc -S a.i -o a.s
           

将預處理過的.i檔案搞成了彙程式設計式,至于裡面進行了怎樣的複雜過程,怎樣優化,我也不是很清楚,有時間研究,隻要知道它是用來翻譯成彙程式設計式就行了。

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

注意上面的兩個檔案都是ASCII檔案

彙編器as:

彙編代碼轉換機器碼 gcc -c a.s -o a.o
           

這個彙編器之後就是一個可重定位的目标程式了,等會兒看看啥樣子

連結器:

連結過程将多個目标文以及所需的庫檔案連結成最終的可執行檔案
gcc a.o -o a
           

今天就重點來初識一下連結器所做的連結過程

靜态連結

靜态連結就是根據一系列可重定位的目标檔案和指令行的參數,來生成一個已經完全連結的,而且可以加載運作的可執行檔案的過程

不知道什麼意思沒有關系,我們一步一步來梳理就行。

這個靜态連結由一個靜态連結器完成,主要執行兩個過程。

一個是

符号解析

,一個是

重定位

又懵了,什麼是符号,解析他幹嘛,重定位又是啥?

還是一步一步來

首先要知道啥時符号吧

符号:不說了,上圖吧。

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

似乎明白了,不就是一些符号啥的嘛,好像沒啥了不起的。先有個印象

符号解析這個過程先不說,先來看看那個我們之前所說的可重定位的目标檔案。

它是經彙程式設計式翻譯過來的二進制檔案。

首先要知道Linux系統使用可執行可連結格式,即ELF。

可重定位檔案分成了一節節的内容,即符合ELF格式的一種二進制檔案。

先來上一份代碼吧
int count = 0;
int boy = 1;
int k;
void main(){
        static int p = 1;
        count += p;
}

           
深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

我們使用

readelf -S b.o

檢視一下内容

我們需要重點觀察

.bss,.data,.text.symtab

我們通過圖檔可以知道它是一節節的組成的。

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

我們重點在于符号表.symtab

使用

readelf -s b.o

(這裡是小s)可以檢視符号表的内容

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

.symtab:

就是通過

readelf -s

> 指令弄出來的,上面這張圖,value對應的在對應節的偏移量,Size大小,Type類型,Bind是本地還是全局還是弱符号,Ndx對應于ELF檔案(可重定位目标檔案)的哪一節。

.bss:

對應上面圖檔Ndx項的4,用于存放未被初始化的全局變量和靜态C變量,但是有個情況,如果你指定了一個為初值為0的變量,就像上面的count,它是一個初始化為0的全局變量,但是它被配置設定在了.bss字段。還需要注意的就是,如果一個靜态變量被配置設定空間的話,它有個特殊的,它隻适合本地子產品,也就是說隻需要在本地保證唯一就行了,就算其他子產品有同名的強符号或者弱符号,本地子產品隻會引用本地的這個靜态變量,除非你指定了用外部子產品的。對于bss更細微的定義就是,它一般隻儲存未初始化的靜态變量和初始化位0的全局變量,靜态變量,而未初始化的全局變量暫時在符号表中定義為COMMON,就像上面的k一樣。

.data:

對應上面圖檔的3,用于存放已經初始化的全局變量和靜态C變量,但是初始值為0的沒有配置設定在這個地方,和上面一樣。

.text:

存放是編譯後的機器代碼

我們了解了符号和符号表,就可以來進行符号解析了

符号解析

就是将某個子產品的符号引用與這個符号在另一個子產品的定義相關聯
           
深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位
深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

寫了兩個程式

我們把他們編譯成可重定位的檔案

有三種集合,連結器進行符号解析時,需要建立維護這三個集合。

  • E:合并在一起的所有目标檔案(還未重定位)
  • U:沒有解析的符号(定義符号和引用符号還沒有被建立聯系)
  • D:定義符号的集合

當連結器接收到了一個可重定位檔案

看看它是不是庫檔案

庫檔案:将所有沒有解析的符号(U)與庫檔案比對,如果比對上了,就将比對的子產品放入E中,然後将這個U中的符号放到D中,知道U和D不在變化,庫檔案剩下的全不要了。

非庫檔案:就是可重定位的目标檔案,先把它放到E中,分别将未解析的符号和定義的符号放到U和D中,如果D中有未解析符号的定義,那麼就可以将U中的那個未解析的符号和D中定義的相關聯。

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位
深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

比如如果連結上面的兩個可重定位檔案

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

雖然說符号建立的關聯,但是僅僅建立了管理而已,我們根本不知道符号對應位址在哪裡。

深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

你看這些全是0,根本不知道對應的符号位址在哪裡,你要它怎麼去執行指令,這不是為難它嗎?

是以我們還需要一個過程,就是重定位

重定位

重定位就是為每個符号配置設定對應的位址

  • 合并相同類型的節,然後連結器将運作時的記憶體位址賦給新的聚合節,賦給輸入子產品定義的每個節,以及賦給輸入子產品定義的每個符号。
  • 然後對定義符号進行重定位
  • 對引用符号進行重定位,那麼每個被引用符号的位置,都為改成對應配置設定的位址

    你看将兩個可重定位檔案重定位後:

    深入了解計算機系統—連結——靜态連結程式執行前經曆了啥靜态連結重定位

所有的位址都變了。

相對尋址

對于一個重定位條目,裡面儲存了一個資料結構的資訊

第一就是給定重定位偏移量offset,還有就是修正值addend,還有重定位尋址類型(相對,絕對)

還有就是重定位的符号r.symbol

計算機會給我們的資訊就是每個節的資訊(比如.text代碼節,記作ADDR(s)),然後還有重定位符号位址(ADDR(r.symbol))

我們先根據重定位偏移量找到我們需要重定位的位址address = ADDR(s)+offset

然後我們要根據重定位符号位址和修正值,來更新重定位的引用位址

則ADDR(r.symbol) +addent-address就填充進我們要重定位的引用位址值

絕對尋址:

絕對尋址比較粗暴簡單,直接對符号位址加以修正 ADDR(r.symbol+addend)即可

繼續閱讀