鏈結的過程實際上是為了解決多個檔案之間符號引用的問題(symbol resolution)。編譯時編譯器只對單個檔案進行處理,如果該檔案裡面需要引用到其他檔案中的符號(例如全域性變數或者函式),那麼這時在這個檔案中該符號的位址是沒法確定的,只能等鏈結器把所有的目標檔案連線到一起的時候才能確定最終的位址。
這個填寫位址的過程,根據填寫的位址的型別和時機,可以分為解決程式內部跨檔案引用的鏈結時重定位、引用外部庫檔案的裝載時重定位和引用外部庫檔案時為了加快載入速度而引入的延遲繫結。
ld裡做的事主要是解決鏈結時重定位。這裡會引入乙個重定位表的資料結構,在elf檔案中是以乙個segment的方式存在,通常叫.rel.data和.rel.text(分別對應資料段和**段的重定位表)。其定義在elf.h
/* relocation table entry without addend (in section of type sht_rel). */r_offset 是重定位時需要修正的起始位置相對於段起始的偏移。typedef struct
elf32_rel;
r_info 的低八位表示重定位入口的型別,高24位表示重定位的符號在符號表中的下標。重定位入口的型別是指計算最終位址時的演算法。不同的處理器指令格式不同,導致計算位址的方法也不一樣,需要使用這個變數來區分。
所以在得到乙個目標檔案(處於編譯之後鏈結之前的二進位制檔案)時,其裡面的重定位表的r_offset所指向的偏移位置裡面的值是0或者其他無意義的值,因為這個符號的定義在另外乙個檔案裡。只有在鏈結時,鏈結器有了所有檔案的資訊,才能把每個檔案需要的外部引用符號的實際位址確定,並且填入r_offset所在位置。
舉個栗子
我現在有這麼兩個.c檔案,a.c裡只有乙個main函式,裡面呼叫了位於b.c檔案中的void b()函式。在b()函式中只有一句話,輸出"hello world"。如下圖所示:
然後用gcc的-c選項把a.c和b.c分別編譯成目標檔案a.o和b.o。再用objdump -r來檢視一下a.o的重定位表,如下:
然後我們再用objdump -d來檢視a.o的**段中0x07處是什麼。
從這裡能看到此處的值是0xfffffffc(little-endian),是呼叫b函式的跳轉指令。雖然這個負值(-4)在跳轉指令中一般是有意義的,但是如果這麼跳轉,執行這一句之後會跳轉到0x07處(這裡的計算方法是用這條指令的下一條指令首偏移加上這個值,這個方法是被上一張中的type: r_386_pc32定義的,詳細的就不講了),也就是跳轉到條指令,這裡就變成死迴圈了,顯然不符合源**裡的意義。我們這裡先記下這個值。
然後我們再用gcc把兩個.o檔案鏈結成executable,然後再用objdump -d來檢視這個executable裡這條跳轉指令跳轉目標的值變成了什麼。
gcc編譯器的鏈結 裝載問題
gcc是一款linux系統上普遍使用的輕便型 編譯工具,在bash shell下通過命令列操作可完成程式 的編譯 彙編 鏈結工作。gcc基本編譯指令格式如下 gcc o executefile sourcefile.c l 庫檔案路徑 l 動態庫名字 i 標頭檔案路徑 編譯時,gcc按照從右向左的順...
Linux 安裝gcc編譯器
總結一下自己的學習經驗,學習時發現linux沒有安裝gcc,方式一 yum install gcc c 前提是可以聯網 方式二 進入linux桌面,找到 rpm ivh cpp 4.1.2 48.e15.i386.rpm 回車 rpm ivh kernel headers 2.6.18 194.el...
Linux 編譯器之 GCC
編輯器是指我用它來寫程式的 編輯 而我們寫的 語句,電腦是不懂的,我們需要把它轉成電腦能懂的語句,編譯器就是這樣的轉化工具。就是說,我們用編輯器編寫程式,由編譯器編譯後才可以執行!gcc gnu compiler collection,gnu 編譯器套件 是由gnu開發的程式語言編譯器。gcc 原本...