連接
連接的定義
連接是將各類代碼和數據部分收集起來並組合成爲一個單一文件的過程,這個文件可被加載(或被拷貝)到存儲並執行
連接能夠執行於編譯時,也就是在源代碼被翻譯成機器代碼時;也能夠執行於加載時,也就是在程序被加載器加載到存儲器並執行時javascript
靜態連接
javascript Unix的靜態連接器ld,以一組可重位目標文件和命令行參數做爲輸入,生成一個徹底連接的能夠加載和運行的可執行目標文件做爲輸出
輸入的可重定位目標文件由各類不一樣的代碼和數據節組成
指令在一個節中,初始化的全局變量在另外一個節中,而未初始化的變量又在另一個節中。javascriptjava
爲了構造可執行文件,連接器必須完成兩個主要任務:數據結構
- 符號解析(symbol resolution)。目標文件定義和引用符號。符號解析的目的是將每一個符號引用恰好和一個符號定義聯繫起來
- 重定位(relocation)。編譯器和彙編器生成從地址0開始的代碼和數據節。
目標文件
目標文件有三種形式:函數
- 可重定位目標文件。包含二進制代碼和數據,其形式能夠在編譯時與其餘可重定位目標文件合併起來,建立一個可執行目標文件
- 可執行目標文件。包含二進制代碼和數據,其形式能夠被直接拷貝到存儲器並執行
- 共享目標文件。一種特殊類型的可重定位目標文件,能夠在加載或者運行地被動態地加載到存儲器並連接
一個典型的ELF可重定位目標文件的格式:工具
- text:已編譯程序的機器代碼
- rodata:只讀數據,好比printf語句中的格式串和開關語句的跳轉表
- data:已初始化的全局C變量
- bss:未初始化的全局C變量
- symtab:一個符號表,它存放在程序中定義和引用的函數和全局變量的信息
- rel.text:一個.text節中位置的列表,當連接器把這個目標文件和其餘文件結合時,須要修改這些位置
- rel.data:被模塊引用或定義的任何全局變量的重定位信息
- debug:一個調試符號表,其條目是程序中定義的局部變量和類型定義,程序中定義和引用的全局變量,以及原始的C源文件
- line:原始C源程序中的行號和.text節中機器指令之間的映射。
- strtab:一個字符串表,其內容包括:.symtab和.debug節中的符號表,以及節頭部中的節名字。字符串表就是以null結尾的字符串序列
符號和符號表
每一個可重定位目標模塊m都有一個符號表,它包含m所定義和引用的符號的信息。在連接器的上下文中,有三種不一樣的符號:編碼
- 由m定義並能被其餘模塊引用的全局符號。全局連接器符號對應於非靜態的C函數以及被定義爲不帶C static屬性的全局變量。
- 由其餘模塊定義並被模塊m引用的全局符號。這些符號稱爲外部符號(external),對應於定義在其餘模塊中的C函數和變量。
- 只被模塊m定義和引用的本地符號。有的本地連接器符號對應於帶static屬性的C函數和全局變量。
符號解析
連接器解析符號引用的方法是將每一個引用與它輸入的可重定位目標文件的符號表中的一個肯定的符號定義聯繫起來操作系統
連接如何解析多重定義的全局符號
在編譯時,編譯器向彙編器輸出每一個全局符號,或者是強或者弱的符號,而彙編器會把這個信息隱含地編碼在可重定位目標文件的符號表裏。函數和已初始化的全局變量是強符號,未初始化的全局變量是弱符號命令行
根據強弱符號的定義,Unix連接器使用下面的規則來處理多重定義的符號(其它系統應該也適用的,I think,並且還要看編譯器):
- 規則1:不容許有多個強符號
- 規則2:若是有一個強符號和多個弱符號,那麼選擇強符號
- 規則3:若是有多個弱符號,那麼從這些弱符號中任意選擇一個
重定位
-------
重定位節和符號定義:
連接器將全部相同類型的節合併爲同一類型的新的聚合節,將運行時存儲器地址賦給新的聚合節,賦給輸入模塊定義的每一個節,以及賦給輸入模塊定義的每一個符號
此時,程序中的每一個指令和全局變量都有惟一的運行時存儲器地址了
重定位節中的符號引用:
連接器修改代碼節和數據節中對每一個符號的引用,使得它們指向正確的運行時地址
連接器依賴於稱爲重定位條目的可重定位目標模塊中的數據結構翻譯
重定位條目
不管什麼時候彙編器遇到對最終位置位置的目標引用,它就會生成一個重定位條目,告訴連接器在將目標文件合併成可執行文件時如何修改這個引用
代碼的重定位條目放在.rel.text中
已初始化的數據的重定位條目放在.rel.data中
ELF定義了11種不一樣的重定位類型debug
動態鏈接共享庫
靜態庫的缺點:
- 靜態庫在更新時,使用該庫的程序須要與更新的庫進行從新連接
- 因爲使用靜態庫的程序在連接時都會拷貝靜態庫裏被應用程序引用的目標模塊,像printf和scanf這樣的函數的代碼在運行時都會被複制到每一個運行進程的文本段中,這形成了冗餘,浪費了稀缺的存儲器資源
共享庫:
- 共享庫是一個目標模塊,在運行時,能夠加載到任意的存儲器地址,並和一個在存儲器中的程序連接起來。這個過程稱爲動態連接,是由一個叫作動態連接器的程序來執行的
- 共享庫也稱爲共享目標,在Unix系統中一般用.so後綴來表示。微軟的操做系統大量地利用了共享庫,它們稱爲DLL(動態連接庫)
- 共享庫是以兩種不一樣的方式來「共享」的(在Windows中分別稱爲「隱式連接」和「顯示連接」)
處理目標文件的工具
- AR:建立靜態庫,插入、刪除、列出和提取成員
- STRINGS:列出一個目標文件中全部可打印的字符串
- STRIP:從目標文件中刪除符號的信息
- NM:列出一個目標文件的符號表中定義的符號
- SIZE:目標文件中節的名字和大小
- READELF:顯示一個目標文件的完整結構,包括ELF頭中的編碼的全部信息。包含SIZE和NM的功能
- OBJDUMP:全部二進制工具之母,可以顯示一個目標文件中全部的信息。它最大的做用是反彙編.text節中的二進制指令
- LDD:列出一個可執行文件在運行時所須要的共享庫