20135302魏靜靜——《深刻理解計算機系統》第7章 學習筆記

《深刻理解計算機系統》第7章   連接

本章主要內容:數據結構

  • 連接——靜態連接、動態連接(連接又包括兩個主要任務:符號解析和重定位)
  • 符號——全局符號和本地符號、符號表、符號解析
  • 連接文件的建立及引用——gcc、ar rcs、sharedj及fPIC命令參數
  • 重定位——重定位條目、重定位符號引用(PC相對引用和絕對引用)
  • 目標文件——可重定位目標文件(其中又詳細介紹了ELF可重定位文件的結構及格式)、可執行目標文件、共享目標文件
        連接(linking)是將各類代碼和數據部分收集起來並組合成爲一個單一文件的過程,這個文件可被加載(或被拷貝)到存儲器並執行。


連接能夠執行於編譯時,即源代碼被翻譯成機器代碼時;也可執行於加載時,即在程序被加載器加載到存儲器並執行時;甚至執行於運行時,由應用程序來執行。

1.編譯器驅動程序

 

大多數編譯系統提供編譯器驅動程序(compiler driver),它表明用戶在須要時調用語言預處理器、編譯器、彙編器和連接器。函數

GNU編譯系統編譯源碼:工具

  • 首先,運行C預處理器(cpp),將.c文件翻譯成.i文件;
  • 接着,運行C編譯器(cc1),將.i文件翻譯成ASCII彙編語言文件.s文件;
  • 而後,運行彙編器(as),將.s文件翻譯成可重定位目標文件.o文件;
  • 最後,運行連接器(ld),將各.o文件組合起來,建立一個可執行目標文件。

2.靜態連接

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

爲了構造可執行文件,連接器必須完成兩個主要任務:spa

  • 符號解析(symbol resolution)。目標文件定義和引用符號。符號解析的目的是將每一個符號引用恰好和一個符號定義聯繫起來。
  • 重定位(relocation)。編譯器和彙編器生成從地址0開始的代碼和數據節。連接器經過把每一個符號定義與一個存儲器位置聯繫起來,而後修改全部對這些符號的引用,使得它們指向這個存儲器位置,從而重定位這些節。

3.目標文件

目標文件有三種形式:可重定位目標文件。能夠在編譯時與其它可重定位目標文件合併起來,建立一個可執行目標文件。操作系統

  • 可執行目標文件。可被直接拷貝到存儲器並執行。
  • 共享目標文件。在加載或運行時被動態地加載到存儲器並連接。

編譯器和彙編器生成可重定位目標文件(包括共享目標文件)。連接器生成可執行目標文件。
現代Unix系統使用可執行和可連接格式(ELF)。


可重定位目標文件命令行

一個典型的可重定位目標文件包含下面幾個節:
.text:已編譯程序的機器代碼。
.rodata:只讀數據。
.data:已初始化的全局C變量。局部C變量在運行時保存在棧中,既不出如今.data節中,也不出如今.bss節中。
.bss:未初始化的全局C變量。翻譯

4. 符號和符號表

每一個可重定位目標模塊m都有一個符號表,它包含m所定義和引用的符號的信息。3d

  • 在連接器的上下文中,有三種不一樣的符號:

    • 一、由m定義並能被其餘模塊引用的全局符號。全局連接器符號對應於非靜態的C函數以及被定義爲不帶C static屬性的全局變量。
    • 二、由其餘模塊定義並被模塊m引用的全局符號。這些符號稱爲外部符號(external),對應於定義在其餘模塊中的C函數和變量。
    • 三、只被模塊m定義和引用的本地符號。有的本地連接器符號對應於帶static屬性的C函數和全局變量。
  • 符號表

    • 每一個符號都和目標的某個節相關聯,由section字段表示。
    • section字段三個特殊的僞節
      • ABS:不應被重定位的符號。
      • UNDEF:未定義的符號,在本目標模塊中引用,但在其餘地方定義。
      • COMMON:未被分配位置的未初始化數據目標。
    • Ndx=1表示.test節,Ndx=3表示.data節。

5. 符號解析

  • 多重定義的全局符號

    • 強符號:函數和已經初始化的全局變量
    • 弱符號:未初始化的全局變量code

    • 規則:

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

全部的編譯系統都提供一種機制,將全部相關的目標模塊打包成爲一個單獨的文件,稱爲靜態庫(Linux下是存檔文件,Windows下是lib),能夠用作連接器的輸入。
    • 當連接器構造一個輸出的可執行文件時,它只拷貝靜態庫裏被應用程序引用的目標模塊。
    • 存檔文件:一組鏈接起來的可重定位目標文件的集合,有一個頭部用來描述每一個成員目標文件的大小和位置。存檔文件名由後綴.a標識。
    • 連接時加上-static參數:告訴編譯器驅動程序,連接器應該構建一個徹底連接的可執行目標文件,它能夠加載到存儲器並執行,在加載時無需更進一步的連接。

6. 重定位

  • 重定位節和符號定義:

    • 連接器將全部相同類型的節合併爲同一類型的新的聚合節,將運行時存儲器地址賦給新的聚合節,賦給輸入模塊定義的每一個節,以及賦給輸入模塊定義的每一個符號。
    • 此時,程序中的每一個指令和全局變量都有惟一的運行時存儲器地址了。
  • 重定位節中的符號引用:

    • 連接器修改代碼節和數據節中對每一個符號的引用,使得它們指向正確的運行時地址。
    • 連接器依賴於稱爲重定位條目的可重定位目標模塊中的數據結構。
  • 重定位符號引用

    • 相對引用
    • 絕對引用

7. 可執行目標文件及加載

(1)可執行目標文件

  • C程序開始時是一組ASCII文本文件,已經被轉化爲一個二進制文件,且這個二進制文件包含加載程序到存儲器並運行它所需的全部信息。

  • 段頭部表:可執行文件的連續片被映射到連續的存儲器段,段頭部表描述了這種關係。

(2)加載可執行目標文件

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

  • 用戶棧老是最大的合法用戶地址開始,向下增加的(向低存儲器地址方向增加)。從棧的上部開始的段是爲操做系統駐留存儲器的部分(也就是內核)的代碼和數據保留的。

  • 當加載器運行時,它建立如上圖所示的存儲器映像。在可執行文件中段頭部表的指導下,加載器將可執行文件的相關內容拷貝到代碼和數據段。
  • 接下來,加載器跳轉到程序的入口點,也就是符號_start的地址。在_start地址處的啓動代碼(startup code)是在目標文件ctrl.o中定義的,對全部的C程序都是同樣的。

8. 動態鏈接共享庫

  • 共享庫是一個目標模塊,在運行時,能夠加載到任意的存儲器地址,並和一個在存儲器中的程序連接起來。這個過程稱爲動態連接,是由一個叫作動態連接器的程序來執行的。
  • 共享庫也稱爲共享目標,在Unix系統中一般用.so後綴來表示。微軟的操做系統大量地利用了共享庫,它們稱爲DLL(動態連接庫)。
  • 共享庫是以兩種不一樣的方式來「共享」的(在Windows中分別稱爲「隱式連接」和「顯示連接」)。
    • 首先,在任何給定的文件系統中,對於一個庫只有一個.so文件。全部引用該庫的可執行目標文件共享這個.so文件中的代碼和數據,而不是像靜態庫的內容那樣被拷貝和嵌入引用它們的可執行的文件中。
    • 其次,在存儲器中,一個共享庫的.text節 一個副本能夠被不一樣的正在運行的進程共享。
  • 與位置無關的代碼PIC

編譯庫代碼,使得不須要連接器修改庫代碼就能夠在任何地址加載和執行這些代碼。
    • 用戶對GCC使用-fPIC選項指示GNU生成PIC代碼

9. 處理目標文件的工具

  • AR:建立靜態庫,插入、刪除、列出和提取成員。
  • READELF:顯示一個目標文件的完整結構,包括ELF頭中的編碼的全部信息。包含SIZE和NM的功能。
  • OBJDUMP:全部二進制工具之母,可以顯示一個目標文件中全部的信息。它最大的做用是反彙編.text節中的二進制指令。
  • LDD:列出一個可執行文件在運行時所須要的共享庫。
  • STRINGS:列出一個目標文件中全部可打印的字符串。
  • STRIP:從目標文件中刪除符號的信息。
  • NM:列出一個目標文件的符號表中定義的符號。
  • SIZE:目標文件中節的名字和大小。
相關文章
相關標籤/搜索