《深刻理解計算機系統》第七章
連接是將各類代碼和數據部分收集起來並組合成爲一個單一文件的過程,這個文件可被加載(或拷貝)到存儲器並執行。服務器
連接的時機數據結構
-
編譯時,也就是在源代碼被翻譯成機器代碼時函數
- 加載時,也就是在程序被加載器加載到存儲器並執行時。
- 運行時,由應用程序執行。
- 在現代系統中,連接是由連接器自動執行的。
7.1 編譯器驅動程序
編譯系統提供編譯驅動程序——調用語言預處理器、編譯器、彙編器和連接器。工具
(1)運行C預處理器:源程序main.c->ASCII碼中間文件main.i 性能
(2)運行C編譯器:main.i->ASCII碼彙編語言文件main.s
(3)運行彙編器:main.s->可重定位目標文件編碼
7.2 靜態連接
連接器必須完成兩個主要任務:spa
- 符號解析:目標文件定義和引用符號。符號解析的目的是將每一個符號引用恰好和一個符號定義聯繫起來。
- 重定位:編譯器和彙編器生成從地址0開始的餓代碼和數據節。連接器經過把每一個符號定義與一個存儲器位置聯繫起來,而後修改全部對這些符號的引用,使得它們指向這個存儲器位置,從而重定位這些節。
7.3 目標文件
連接器必須完成兩個主要任務:
- 符號解析——目標文件定義和引用符號。符號解析的目的是將每一個符號引用恰好和一個符號定義聯繫起來。
- 重定位——編譯器和彙編器生成從地址0開始的餓代碼和數據節。連接器經過把每一個符號定義與一個存儲器位置聯繫起來,而後修改全部對這些符號的引用,使得它們指向這個存儲器位置,從而重定位這些節。
7.4 可重定位目標文件
一個典型的ELF可重定位目標文件包含下面幾個節:操作系統
![](http://static.javashuo.com/static/loading.gif)
7.5 符號和符號表
在連接器的上下文中,有三種不一樣的符號:命令行
- 由m定義並能被其餘模塊引用的全局符號
- 由其餘模塊定義並被模塊m引用的全局符號
- 只被模塊m引用的本地符號
7.6 符號解析
7.6.1 連接器如何解析多重定義的全局符號
1.在編譯是,編譯器向彙編器輸出每一個全局符號,或者是強或者是弱,而彙編器把這個信息隱含地編碼在可重定位目標文件的符號表裏。函數和已初始化的全局變量時強符號,未初始化的全局變量是弱符號。翻譯
2.根據強弱符號的定義,Unix連接器使用下面的規則來處理多重定義的符號:
7.6.2 與靜態庫連接
全部的編譯系統都提供一種機制,將全部相關的目標模塊打包成爲一個單獨的文件,稱爲靜態庫(Linux下是存檔文件,Windows下是lib),能夠用作連接器的輸入。
- 當連接器構造一個輸出的可執行文件時,它只拷貝靜態庫裏被應用程序引用的目標模塊。
- 存檔文件:一組鏈接起來的可重定位目標文件的集合,有一個頭部用來描述每一個成員目標文件的大小和位置。存檔文件名由後綴.a標識。
- 連接時加上-static參數:告訴編譯器驅動程序,連接器應該構建一個徹底連接的可執行目標文件,它能夠加載到存儲器並執行,在加載時無需更進一步的連接。
7.6.3 連接器如何使用靜態庫來解析引用
圖2
通常準則:
7.7 重定位
重定位兩步:
- 重定位節和符號定義:
- 連接器將全部相同類型的節合併爲同一類型的新的聚合節,將運行時存儲器地址賦給新的聚合節,賦給輸入模塊定義的每一個節,以及賦給輸入模塊定義的每一個符號。
- 此時,程序中的每一個指令和全局變量都有惟一的運行時存儲器地址了。
- 重定位節中的符號引用:
- 連接器修改代碼節和數據節中對每一個符號的引用,使得它們指向正確的運行時地址。
- 連接器依賴於稱爲重定位條目的可重定位目標模塊中的數據結構。
7.7.1 重定位條目
ELF定義兩種最基本的重定位類型:
- R_386_PC32 重定位一個使用32位PC相對地址的引用。
- R_386_32 重定位一個使用32位絕對地址的引用。
7.7.2 重定位符號引用
引用類型:
7.8 可執行目標文件
ELF可執行文件被設計得很容易加載到存儲器,可執行文件的連續的片被映射到連續的存儲器段。段頭部表描述了這種映射關係。
可執行文件是徹底連接的(已被重定位了),因此它再也不須要.rel節。
7.9 加載可執行目標文件
加載器將可執行目標文件中的執行代碼和數據從磁盤拷貝到存儲器中,而後經過跳轉到程序的第一條指令或入口點來運行該程序。這個將程序拷貝到存儲器並運行的過程叫作加載。
7.10 動態連接共享庫
共享庫
- 共享庫是一個目標模塊,在運行時,能夠加載到任意的存儲器地址,並和一個在存儲器中的程序連接起來。這個過程稱爲動態連接,是由一個叫作動態連接器的程序來執行的。
- 共享庫也稱爲共享目標,在Unix系統中一般用.so後綴來表示。微軟的操做系統大量地利用了共享庫,它們稱爲DLL(動態連接庫)。
- 共享庫是以兩種不一樣的方式來「共享」的。
7.11 從應用程序中加載和連接共享庫
動態連接在現實中的例子:
7.12 與位置無關的代碼(PIC)
編譯庫代碼,使得不須要連接器修改庫代碼就能夠在任何地址加載和執行這些代碼。
- 用戶對GCC使用
-fPIC
選項指示GNU生成PIC代碼
常見PIC類型:
7.13 處理目標文件的工具
- AR:建立靜態庫,插入、刪除、列出和提取成員。
- STRINGS:列出一個目標文件中全部可打印的字符串。
- STRIP:從目標文件中刪除符號表信息。
- NM:列出一個目標文件中符號表定義的符號。
- SIZE:列出目標文件中節的名字和大小。
- READELF:可以顯示一個目標文件的全部信息。
- OBJDUMP:反彙編
- LDD:列出一個可執行文件運行時須要的共享庫。
7.14 小結
- 連接能夠在編譯時由靜態編譯器來完成,也能夠在加載時和運行時由動態連接器來完成。
- 連接器處理稱爲目標文件的二進制文件,它有三種不一樣的形式:可重定位的、可撕的和共享的:
- 可重定位的目標文件由靜態連接器合併成一個可執行的目標文件,它能夠加載到存儲器中並執行。
- 共享目標文件(共享庫)是在運行時由動態連接器連接和加載的,或者隱含地在調用程序被加載和開始執行時或者根據須要在程序調用dopen庫的函數時。
- 連接器的兩個主要任務是符號解析和重定位,符號解析將目標文件中的每一個全局符號都綁定到一個惟一的定義,而重定位肯定每一個符號的最終存儲器地址,並修改對那些目標的引用。
- 靜態連接器是由像G∝這樣的編譯驅動器調用的。
- 多個目標文件能夠定義相同的符號,而連接器用來悄悄地解析這些多重定義的規則可能在用戶程序中引入的微妙錯誤。