鏈接種類
-
編譯時 靜態庫,整合到可執行文件中
-
加載時 動態庫,在load時loader看到interp節,調用動態連接器
-
運行時 由代碼決定加載那個,可以實現熱更新
GCC參數
- -o outputfile
- -O Optimize 優化
- -fpic 位置無關
- -shared 共享庫
- -static 靜態鏈接,完全鏈接
- -D ifdef的參數
- -I include 路徑
- -L 靜態庫路徑
- -llib 靜態庫縮寫
鏈接工作
符號解析
代碼中引用的函數和變量
編譯的原則是每個源代碼文件形成一個目標文件,當需要跨文件或者跨文件夾訪問或調用時,即有多個文件,那么在編譯時,有些符號並沒有實際的地址,符號解析的作用就是讓每個符號綁定一個地址。
怎么做?
編譯成功的的目標文件是一個ELF格式,有頭部、節段、節頭部表組成。
其中.symtab
是符號表,模塊中所有出現的符號都會在里面。
.rel.text
和 .rel.data
是重定位的信息,標記了.text和.data 節段中鏈接后需要修改的部分的位置
.symtab
中的標志表示了這個符號是本地還是外部,是否需要解析。
三種符號
- 全局符號,定義在本模塊中,所有的模塊都能看到,如全局變量,函數
- 外部符號,定義在其他模塊中,本模塊引用,extern 定義的
- 局部符號,帶有static屬性的全局變量和函數
鏈接的過程,為未定義的符號找到一個地址,可能多個模塊中有相同的符號,取哪一個取決於符號的強弱,是否定義了。連接器會把未解析的符號不斷地解析到定義的地方,在鏈接靜態庫時,其中引用的模塊必須排在定義的模塊前面
重定位(relocation)
重定位會把所有的模塊中相同的section聚合在一起,然后給每個section一個運行時的地址,然后根據重定位section中的記錄去修改二進制的內容。
typedef struct
{
long offset; /* Offset of the reference to relocate */
long type : 32, /* Relocation type */
symbol : 32; /* Symbol table index */
long addend; /* Constant part of relocation expression */
} Elf64_Rela
位置無關代碼
gcc -shared -fpic -o a.so b.o c.o
專門為動態庫設計的,可以實現不管放在哪個地址都能正常的引用,動態庫在完成鏈接的時候,代碼並沒有進入可執行文件中,只是賦值了一些重定位和符號表的信息,包括動態庫的路徑信息。
動態庫在磁盤上只有一份,代碼段內存中也只有一份
位置無關代碼重定位時修改的是section前面的引用表GOT和PLT 相當於抽象了一層,多了一次訪問,但是重定位時不需要修改代碼,只要改引用層就行。
打樁
打樁的目的就是在不改變源代碼的情況下,實現功能的轉移
-
編譯時 通過宏替換標准庫的函數,這一步是在預處理時產生的,分離式編譯的模塊發生了改變
-
鏈接時 通過gcc的參數,改變符號解析,來達到打樁的目的
gcc -Wl,--wrap,malloc -Wl,--wrap,free a.o b.o
會把對
malloc
的引用重定位到__wrap_malloc
,把__real_malloc
的引用重定位到malloc(這里就是標准庫) -
運行時 通過
LD_PRELOAD
環境變量改變了動態庫的尋找路徑# bash 中 LD_PRELOAD="./a.so" ./b.out
能對malloc函數進行打樁是因為C語言的標准庫libc.so都是動態庫