鏈接總結

本文主要參考書目爲《深入理解計算機系統》《程序員的自我修養》 

    鏈接的主要內容就是把各個模塊之間相互引用的部分處理好,使得各個模塊之間能夠正確的銜接。鏈接的過程主要包括了地址和空間分配、符號決議和重定位等這些步驟。 ----《程序員的自我修養》

    鏈接是將各種代碼和數據片段收集並組合成爲一個單一文件的過程,這個文件可被加載到內存並執行。  ----《深入理解計算機系統》

概述

    鏈接可以在編譯時靜態編譯器來完成,也可以在加載時和運行時動態鏈接器來完成。鏈接器處理稱爲目標文件的二進制文件,它有3種不同的形式:可重定位的、可執行的和共享的。可重定位的目標文件由靜態鏈接器合併成一個可執行的目標文件,它可以加載到內存中並執行。共享目標文件(共享庫)是在運行時由動態鏈接器鏈接和加載的,或者隱含地在調用程序被加載和開始執行時,或者根據需要在程序調用dlopen庫的函數時。

    鏈接器的兩個主要任務是符號解析重定位,符號解析將目標文件中的每個全局符號都綁定到一個唯一的定義,而重定位確定每個符號的最終內存地址,並修改對那些目標的引用。

    靜態鏈接器是由像GCC這樣的編譯驅動程序調用的。它們將多個可重定位目標文件合併成一個單獨的可執行目標文件。多個目標文件可以定義相同的符號,而鏈接器用來悄悄地解析這些多重定義的規則可能在用戶程序中引人微妙的錯誤。

    多個目標文件可以被連接到一個單獨的靜態庫中。鏈接器用庫來解析其他目標模塊中的符號引用。許多鏈接器通過從左到右的順序掃描來解析符號引用,這是另一個引起令人迷惑的鏈接時錯誤的來源。

    加載器將可執行文件的內容映射到內存,並運行這個程序。鏈接器還可能生成部分鏈接的可執行目標文件,這樣的文件中有對定義在共享庫中的例程和數據的未解析的引用。在加載時,加載器將部分鏈接的可執行文件映射到內存,然後調用動態鏈接器,它通過加載共享庫和重定位程序中的引用來完成鏈接任務。

    被編譯爲位置無關代碼的共享庫可以加載到任何地方,也可以在運行時被多個進程共享。爲了加載、鏈接和訪問共享庫的函數和數據,應用程序也可以在運行時使用動態鏈接器。

下圖是根據自己的理解畫的思維導圖


    符號:在鏈接中,我們將函數變量統稱爲符號,函數名或變量名就是符號名。我們可以將符號看作是鏈接中的粘合劑,整個鏈接過程正是基於符號才能正確完成的。

    符號解析。目標文件定義和引用符號,每個符號對應於一個函數、一個全局變量或一個靜態變量(即C語言中任何以static屬性聲明的變量)。符號解析的目的是將每個符號引用正好和一個符號定義關聯起來。

    鏈接器解析符號引用的方法是將每個引用與它輸入的可重定位目標文件的符號表中的一個確定的符號定義關聯起來。

    函數和已初始化的全局變量是強符號,未初始化的全局變量是弱符號

    根據強弱符號的定義, Linux鏈接器使用下面的規則來處理多重定義的符號名:

        規則1: 不允許有多個同名的強符號。

        規則2: 如果有一個強符號和多個弱符號同名,那麼選擇強符號。   

        規則3: 如果有多個弱符號同名, 那麼從這些弱符號中任意選擇一個。

    重定位。編譯器和彙編器生成從地址0開始的代碼和數據節。鏈接器通過把每個符號定義與一個內存位置關聯起來,從而重定位這些節,然後修改所有對這些符號的引用,使得它們指向這個內存位置。鏈接器使用匯編器產生的重定位條目的詳細指令,不加甄別地執行這樣的重定位。

    重定位由兩步組成:

    1)重定位節和符號定義。在這一步中,鏈接器將所有相同類型的節合併爲同一類型的新的聚合節。當這一步完成時,程序中的每條指令和全局變量都有唯一的運行時內存地址了。

    2)重定位節中的符號引用。在這一步中,鏈接器修改代碼節和數據節中對每個符號的 引用,使得它們指向正確的運行時地址。 

   可重定位目標文件:包含二進制代碼和數據, 其形式可以在編譯時與其他可重定位 目標文件合併起來,創建一個可執行目標文件。下圖展示了一個典型的ELF可重定位目標文件的格式。

    段表:一個描述文件中各個段的數組。 

    .text: 已編譯程序的機器代碼。

    .data: 已初始化的全局和靜態C變量。 局部C變量在運行時被保存在校中, 既不出 現在.data節中, 也不出現在.bss節中。

    .bss: 未初始化的全局和靜態C變量,以及所有被初始化爲0的全局或靜態變量。在 目標文件中這個節不佔據實際的空間,它僅僅是一個佔位符。目標文件格式區分已初始化和未初始化變量是爲了空間效率:在目標文件中,未初始化變量不需要佔據任何實際的磁盤空間。運行時,在內存中分配這些變量,初始值爲0。

   .rodata: 只讀數據, 比如printf語句中的格式串和開關語句的跳轉表。

   .symtab: 一個符號表,它存放在程序中定義和引用的函數和全局變量的信息。

 

    爲什麼要進行動態鏈接?

    要解決空間浪費更新困難這兩個問題最簡單的辦法就是把程序的模塊相互分割開來,形成獨立的文件,而不再將它們靜態地鏈接在一起。簡單地講,就是不對那些組成程序的目標文件進行鏈接,等到程序要運行時才進行鏈接。也就是說,把鏈接這個過程推遲到了運行時再進行,這就是動態鏈接的基本思想。。

 

    靜態庫:所有的編譯系統都提供一種機制, 將所有相關的目標模塊打包成爲一個單獨的文件,稱爲靜態庫(static library),它可以用做鏈接器的輸入。當鏈接器構造一個輸出的可執行文件時,它只複製靜態庫裏被應用程序引用的目標模塊。

    靜態庫的缺點:1)更新困難。顯式地將他們的程序與更新了的庫重新鏈接。  2)浪費空間。代碼會被複制到每個運行進程的文本段中。   

    共享庫 是致力於解決靜態庫缺陷的一個現代創新產物。共享庫是一個目標模塊,在運行或加載時,可以加載到任意的內存地址,並和一個在內存中的程序鏈接起來。這個過程稱爲動態鏈接(dynamic linking),是由一個叫做動態鏈接器(dynamic linker) 的程序來執行的。共享庫也稱爲共享目標(shared object),在Linux系統中通常用.so後綴來表示。微軟的操作系統大量地使用了共享庫,它們稱爲DLL(動態鏈接庫)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章