標籤:
第七章 連結
姓名:王瑋怡 學號:20135116
一、關於連結
1、含義
連結是將各種代碼和資料部分收集起來並組合成為一個單一檔案的過程,這個檔案可被載入(或被拷貝)到儲存空間並執行。連結是由連結器程式自動執行的。
2、執行時間
二、編譯器驅動程式
驅動編譯器:代表使用者在需要時調用語言前置處理器、編譯器、彙編器和連結器。
三、靜態連結
1、靜態連結器
Unix的靜態連結器(static linker)ld,以一組可重位目標檔案和命令列參數作為輸入,產生一個完全連結的可以載入和啟動並執行可執行目標檔案作為輸出。輸入的可重定位目標檔案由各種不同的代碼和資料節(section)組成。指令在一個節中,初始化的全域變數在另一個節中,而未初始化的變數又在另外一個節中。
2、連結器的兩個任務
- 符號解析:將每個符號引用剛好與一個符號定義聯絡起來
- 重定位:連結器將每一個符號定義與一個儲存空間位置串連起來,然後修改這些符號引用,使得他們指向這個儲存空間位置,從而重定位
四、目標檔案
目標檔案的三種形式
- 可重定位目標檔案(編譯器和彙編器可產生)
- 可執行目標檔案(連結器可產生)
- 共用目標檔案(編譯器和彙編器可產生)
五、可重定位目標檔案
六、符號和符號表
1、連結器的上下文中,有三種不同的符號:
- 由m定義並能被其他模組引用的全域符號。全域連結器符號對應於非靜態C函數以及被定義為不帶C static屬性的全域變數。
- 由其他模組定義並被模組m引用的全域符號。這些符號稱為外部符號(external),對應於定義在其他模組中的C函數和變數。
- 只被模組m定義和引用的本地符號。有的本地連結器符號對應於帶static屬性的C函數和全域變數。
2、符號表
七、符號解析
1、連結器如何解析多重定義的全域符號
(1)強符號:函數和已初始化的全域變數
(2)弱符號:未初始化的全域變數
(3)處理規則:
- 不允許有多個強符號。
- 如果有一個強符號和多個弱符號,那麼選擇強符號。
- 如果有多個弱符號,那麼從這些弱符號中任意選擇一個。
2、與靜態庫連結
所有的編譯系統都提供一種機制,將所有相關的目標模組打包成為一個單獨的檔案,稱為靜態庫,可以用做連結器的輸入。其中,在Linux下是封存檔案,Windows下是lib。
在符號解析的階段,連結器從左至右按照它們在編譯器驅動程式命令列上出現的相同順序來掃描可重定位目標檔案和封存檔案。(驅動程式自動將命令列中所有的.c檔案翻譯成.o檔案),在這次掃描中,連結器維持一個可重定位目標檔案的集合E(這個集合中的檔案會被合并起來形成可執行檔),一個未解析的符號(即引用了但是尚未定義的符號)集合U,以及一個在前面輸入檔案中已定義的符號集D,初始時,E、U和D都是空的。
七、重定位
1、重定位的兩個步驟
(1)重定位節和符號定義
- 連結器將所有相同類型的節合并為同一類型的新的彙總節,將運行時儲存空間地址賦給新的彙總節,賦給輸入模組定義的每個節,以及賦給輸入模組定義的每個符號。
- 完成這一步後,程式中的每個指令和全域變數都有唯一的運行時儲存空間地址了。
(2)重定義節中的符號引用
- 連結器修改代碼節和資料節中對每個符號的引用,使得它們指向正確的運行時地址。
- 連結器依賴於稱為重定位條目的可重定位目標模組中的資料結構。
2、重定位條目
(1)無論何時彙編器遇到對最終位置位置的目標引用,它就會產生一個重定位條目,告訴連結器在將目標檔案合并成可執行檔時如何修改這個引用。
(2)代碼的重定位條目放在.rel.text中。
(3)已初始化的資料的重定位條目放在.rel.data中。
(4)ELF定義了11種不同的重定位類型。兩種最基本的重定位類型:
- *R_386_PC32 重定位一個使用32位PC相對位址的引用。
- *R_386_32 重定位一個使用32位絕對位址的引用。
- offset:需要被修改的引用的節位移
- symbol:標識被修改的引用應該指向的符號
- type:告知連結器如何修改新的引用
3、重定位器號引用
(1)相對參照
(2)絕對引用
八、可執行目標檔案
九、載入可執行目標檔案
載入器將可執行目標檔案中的執行代碼和資料從磁碟拷貝到儲存空間中,然後通過跳轉到程式的第一條指令或進入點來運行該程式。這個將程式拷貝到儲存空間並啟動並執行過程叫做載入。
- 在32位Linux系統中,程式碼片段總是從地址0x08048000處開始。
- 資料區段是在接下來的下一個4KB對齊的地址處。
- 運行時堆在讀/寫段之後接下來的第一個4KB對齊的地址處,並通過調用malloc庫往上增長。
- 有一個段是為共用庫保留的。
- 使用者棧總是最大的合法使用者地址開始,向下增長的(向低儲存空間地址方向增長)。從棧的上部開始的段是為作業系統駐留儲存空間的部分(也就是核心)的代碼和資料保留的。
十、動態連結共用庫
1、靜態庫的缺點:
- 靜態庫在更新時,使用該庫的程式需要與更新的庫進行重新連結。
- 由於使用靜態庫的程式在連結時都會拷貝靜態庫裡被應用程式引用的目標模組,像printf和scanf這樣的函數的代碼在運行時都會被複製到每個運行進程的文本段中,這造成了冗餘,浪費了稀缺的儲存空間資源。
2、共用庫
- 共用庫是一個目標模組,在運行時,可以載入到任意的儲存空間地址,並和一個在儲存空間中的程式連結起來。這個過程稱為動態連結,是由一個叫做動態連結器的程式來執行的。
- 共用庫也稱為共用目標,在Unix系統中通常用.so尾碼來表示。微軟的作業系統大量地利用了共用庫,它們稱為DLL(動態連結程式庫)。
- 共用庫是以兩種不同的方式來“共用”的(在Windows中分別稱為“隱式連結”和“顯示連結”)。
首先,在任何給定的檔案系統中,對於一個庫只有一個.so檔案。所有引用該庫的可執行目標檔案共用這個.so檔案中的代碼和資料,而不是像靜態庫的內容那樣被拷貝和嵌入引用它們的可執行檔檔案中。其次,在儲存空間中,一個共用庫的.text節 一個副本可以被不同的正在啟動並執行進程共用。
十一、從應用程式中載入和連結共用庫
#include<dlfcn.h>
void *dlopen(const char *filename,int flag); //返回:若成功則為指向控制代碼的指標,若出錯則為NULL
void *dlsym(void *handle,char *symbol); //返回:若成功則為指向符號的指標,若出錯則為NULL
int dlclose(void *handle); //返回:若成功則為0,若出錯則為-1
const char *dlerror(void);
十二、與位置無關的代碼(PIC)
- 編譯庫代碼,使得不需要連結器修改庫代碼就可以在任何地址載入和執行這些代碼。
- 使用者對GCC使用
-fPIC選項指示GNU產生PIC代碼
十三、處理目標檔案的工具
十四、總結
連結可以在編譯時間由靜態編譯器來完成,也可以在載入時和運行時由動態連結器來完成。連結器處理成為目標檔案的二進位檔案,它有三種不同的形式:可重定位的、可執行檔和共用的。可重定位的目標檔案由靜態連結器合并成為一個可執行檔目標檔案,它可以載入到儲存空間中並執行。共用目標檔案(共用庫)是在運行時由動態連結器連結和載入的,或者隱含地在調用程式被載入和開始執行時,或者根據需要在程式調用dlopen庫的函數時。
《深入理解電腦系統》第七章學習筆記