GNU GCC使用ld鏈接器進行鏈接的完整過程是怎樣的?

最近在看ld的連接腳本,但是感覺自己不是很清楚它的鏈接過程,所以對腳本的很多地方不理解。


如果是想問鏈接這個過程發生了什麼 我可以來稍微講講

鏈接的過程實際上是為了解決多個文件之間符號引用的問題(symbol resolution)。編譯時編譯器只對單個文件進行處理,如果該文件裡面需要引用到其他文件中的符號(例如全局變數或者函數),那麼這時在這個文件中該符號的地址是沒法確定的,只能等鏈接器把所有的目標文件連接到一起的時候才能確定最終的地址。

這個填寫地址的過程,根據填寫的地址的類型和時機,可以分為解決程序內部跨文件引用的鏈接時重定位、引用外部庫文件的裝載時重定位和引用外部庫文件時為了加快載入速度而引入的延遲綁定

ld里做的事主要是解決鏈接時重定位。這裡會引入一個重定位表的數據結構,在ELF文件中是以一個segment的方式存在,通常叫.rel.data和.rel.text(分別對應數據段和代碼段的重定位表)。其定義在elf.h

/* Relocation table entry without addend (in section of type SHT_REL). */

typedef struct

{

Elf32_Addr r_offset; /* Address */

Elf32_Word r_info; /* Relocation type and symbol index */

} Elf32_Rel;

r_offset 是重定位時需要修正的起始位置相對於段起始的偏移。

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里這條跳轉指令跳轉目標的值變成了什麼。

executable中這裡的值變成了0x09,也就是跳轉到0x08048414(0x804840b + 0x09),正好就是b函數的起始偏移。這就是鏈接器所做的工作之一,也是最重要的工作之一。當然鏈接器還做了很多其他的工作,例如為動態鏈接開闢了GOT、為延遲綁定引入PLT等等。如果想要進一步了解,可以仔細看看程序員的自我修養 (豆瓣)(淺顯易懂,但是不夠深入)、CS:APP2e, Bryant and O"Hallaron(中文名貌似叫深入理解計算機系統,我大CMU的經典課15213的教材)和Linkers and Loaders (豆瓣)(程序員的自我修養裡面很多東西都是直接從這本書里翻過去的,想深入了解可以仔細研讀)。


深入理解計算機系統 第7章 鏈接


題主可以看看《程序員的自我修養》,裡面主要就講這些。


不清楚樓主想知道的是Script的各部分用法還是ld這個程序的原理。

用法可看Manual:https://sourceware.org/binutils/docs/ld/

至於說ld是如何實現的,找了一圈沒找到有人寫文章介紹,只能結合編譯原理的知識啃源碼了。

謝邀


對鏈接器來說,一邊是輸入,即一堆obj lib,每個對象文件都有一堆sections,另一邊呢是輸出,即segment. 那麼鏈接器需要知道生成哪些segment ,哪些輸入section 屬於哪個segment。鏈接腳本主要就是來控制這個輸入-輸出的映射規則,給你更精細的控制。預設情況下,有一套默認的規則(gnu ld有默認腳本,-ve可見)。


Advanced C and C++ Compiling


樓主並不必要糾結ld, relocation才是linker的核心

關鍵是真正理解relocation

順便可以考考樓主,如何(有沒有可能)才能寫出一個不需要連接器的C程序(hello world)?


mk


推薦閱讀:

VC++ __FUNCTION__的實現原理是什麼?能通過這個拿到整個的函數列表嗎?
做編譯器的人如果遇到了bug,他們怎麼判斷是編譯器自己的問題還是編譯這個編譯器的編譯器的問題?
學習編譯原理只是研究lex和yacc嗎?
sibling call是什麼?
現在的編譯器的inline策略是怎樣的?

TAG:編程 | C編程語言 | GCC | 編譯器 | 鏈接 |