《深刻理解計算機系統》 Chapter 7 讀書筆記

《深刻理解計算機系統》Chapter 7 讀書筆記

連接是將各類代碼和數據部分收集起來並組合成爲一個單一文件的過程,這個文件可被加載(貨被拷貝)到存儲器並執行。數據結構

連接的時機函數

  • 編譯時,也就是在源代碼被翻譯成機器代碼時
  • 加載時,也就是在程序被加載器加載到存儲器並執行時
  • 運行時,由應用程序執行

連接器使分離編譯稱爲可能。編碼

1、編譯器驅動程序

大部分編譯系統提供編譯驅動程序:表明用戶在須要時調用語言預處理器、編譯器、彙編器和連接器。spa

1.將示例程序從ASCⅡ碼源文件翻譯成可執行目標文件的步驟

1)運行C預處理器:源程序main.c->ASCII碼中間文件main.i

   (2)運行C編譯器:main.i->ASCII碼彙編語言文件main.s

   (3)運行彙編器:main.s->可重定位目標文件

2、靜態連接

靜態連接器以一組可重定位目標文件和命令行參數做爲輸入,生成一個徹底連接的能夠加載和運行的可執行目標文件做爲輸出。輸入的可重定位目標文件由各類不一樣的代碼和數據節(section)組成。指令在一個節中,初始化的全局變量在另外一個節中,而未初始化的變量又在另一個節中。操作系統

爲了構造可執行文件,連接器必須完成兩個任務:符號解析,重定位

3、目標文件

1.三種形式

  • 可重定位目標文件。包含二進制代碼和數據,其形式能夠在編譯時與其餘可重定位目標文件合併起來,建立一個可執行目標文件。
  • 可執行目標文件。包含二進制代碼和數據,其形式能夠被直接拷貝到存儲器並執行。
  • 共享目標文件。一種特殊類型的可重定位目標文件,能夠在加載或者運行地被動態地加載到存儲器並連接。
編譯器和彙編器生成可重定位目標文件(包括共享目標文件)。連接器生成可執行目標文件。從技術上來講,一個目標模塊就是一個字節序列,而一個目標文件就是一個存放在磁盤文件中的目標模塊。

4、可重定位目標文件

1.一個典型的ELF可重定位目標文件的格式:命令行

  • .text:已編譯程序的機器代碼。
  • .rodata:只讀數據,好比printf語句中的格式串和開關語句的跳轉表。
  • .data:已初始化的全局C變量。
  • .bss:未初始化的全局C變量。在目標文件中這個節不佔據實際的空間,它僅僅是一個佔位符。
  • .symtab:一個符號表,它存放在程序中定義和引用的函數和全局變量的信息。
  • .rel.text:一個.text節中位置的列表,當連接器把這個目標文件和其餘文件結合時,須要修改這些位置。
  • .rel.data:被模塊引用或定義的任何全局變量的重定位信息。
  • .debug:一個調試符號表,其條目是程序中定義的局部變量和類型定義,程序中定義和引用的全局變量,以及原始的C源文件。只有以-g選項調用編譯驅動程序時纔會獲得這張表。
  • .line:原始C源程序中的行號和.text節中機器指令之間的映射。
  • .strtab:一個字符串表,其內容包括:.symtab和.debug節中的符號表,以及節頭部中的節名字。字符串表就是以null結尾的字符串序列。

5、符號和符號表

每一個可重定位目標模塊m都有一個符號表,包含m所定義和引用的符號的信息。
在連接器的上下文中,三種不一樣的符號:
1.由m定義並能被其餘模塊引用的全局符號。全局連接器對應於非靜態的C函數以及被定義爲Cstatic 屬性的全局變量。
2.由其餘模塊定義並被模塊m以引用的全局符號——外部符號,對應於定義在其餘模塊中的C函數和變量
3.只被模塊m定義和引用的本地符號。

6、符號解析

 1.連接器如何解析多重定義的全局符號

在編譯時,編譯器向彙編器輸出每一個全局符號,或者是強或者是弱,而彙編器把這個信息隱含地編碼在可重定位目標文件的符號表裏。翻譯

函數和已初始化的全局變量時強符號;debug

未初始化的全局變量是弱符號。  unix

根據強弱符號的定義,Unix連接器使用下面的規則來處理多重定義的符號:調試

規則1:不容許有多個強符號。
規則2:若是有一個強符號和多個弱符號,那麼選擇強符號。
規則3:若是有多個弱符號,那麼從這些弱符號中任意選擇一個。

2.與靜態庫連接

全部的編譯系統都提供一種機制,將全部相關的目標模塊打包成爲一個單獨的文件,稱爲靜態庫

3.連接器如何使用靜態庫來解析引用

  • 對於命令行上的每一個輸入文件f,連接器會判斷f是一個目標文件仍是一個存檔文件。若是f是一個目標文件,那麼連接器吧f添加到E, 修改U和D來反映f中的符號定義和引用,並繼續下一個輸入文件。
  • 若是f是一個存檔文件,那麼連接器就嘗試匹配U中未解析的符號和由存檔文件成員定義的符號。若是某個存檔文件成員m,定義了一個符號來解析U中的一個引用,那麼就將m加到E中,而且連接器修改U和D來反映m中的符號定義和引用。對存檔文件中全部的成員目標文件都反覆進行這個過程,直到U和D都再也不發生變化。在此時,任何不包含在E中的目標文件都簡單地被丟棄,而連接器將繼續處理下一個輸入文件。
  • 若是當連接器完成對命令行上輸入文件的掃描後,U是非空的,那麼連接器就好輸出一個錯誤並終止。不然,它會合並和重定位E中的目標文件,從而構建輸出的可執行文件。

7、重定位

一旦連接器完成了符號解析這一步,它就是把代碼中的每一個符號引用和肯定的一個符號定義(即它的一個輸入目標模塊中的一個符號表條目)聯繫起來。

重定位由兩步組成:

  • 重定位節和符號定義。在這一步中,連接器將全部相同類型的節合併爲同一類型的新的聚合節。而後,連接器將運行時存儲器地址賦給新的聚合節,賦給輸入模塊定義的每一個節,以及賦給輸入模塊定義的每一個符號。當這一步完成時,程序中的每一個指令和全局變量都有惟一的運行時存儲器地址了。
  • 重定位節中的符號引用。在這一步中,連接器修改代碼節和數據節中對每一個符號的引用,使得它們指向正確的運行時地址。爲了執行這一步,連接器依賴於稱爲重定位條目的可重定位目標模塊中的數據結構。

1.重定位條目

當彙編器生成一個目標模塊時,它並不知道數據和代碼最終存放在存儲器中的什麼位置。它也不知道這個模塊引用的任何外部定義的函數或者全局變量的位置。因此,不管什麼時候彙編器遇到對最終位置位置的目標引用,它就會生成一個重定位條目,告訴連接器在將目標文件合併成可執行文件時如何修改這個引用。代碼的重定位條目放在.rel.text中 已初始化的數據的重定位條目放在.rel.data中。

8、可執行目標文件

可執行目標文件的格式相似於可重定位目標文件的格式。ELF頭部描述文件的整體格式。它還包括程序的入口點,也就是當程序運行時要執行的第一條指令的地址。.text 、.rodata和.data 節和可重定位目標文件中的節是類似的,除了這些節已經被重定位到它們最終的運行時存儲器地址之外。.init節定義了一個小函數,叫作_init,程序的初始化代碼會調用它。由於可執行文件是徹底連接的(已被重定位了),因此它再也不須要.rel節。

9、加載可執行目標文件

加載器將可執行目標文件中的執行代碼和數據從磁盤拷貝到存儲器中,而後經過跳轉到程序的第一條指令或入口點來運行該程序。這個將程序拷貝到存儲器並運行的過程叫作加載。

要運行可執行目標文件p,能夠在Unix外殼的命令行中輸入它的名字:

unix> ./p

10、動態連接共享庫

共享庫是一個目標模塊,在運行時,能夠加載到任意的存儲器地址,並和一個在存儲器中的程序連接起來。這個過程稱爲動態連接,是由一個叫作動態連接器的程序來執行的。

共享庫也稱爲共享目標,在Unix系統中一般用.so後綴來表示。微軟的操做系統大量地利用了共享庫,它們稱爲DLL(動態連接庫)。

相關文章
相關標籤/搜索