鏈接器(Linker)是一個程序,將一個或多個由編譯器或匯編器生成的目標文件外加庫鏈接為一個可執行文件。目標文件是包括機器碼和鏈接器可用信息的程序模塊。簡單的講,鏈接器的工作就是解析未定義的符號引用,將目標文件中的占位符替換為符號的地址。鏈接器還要完成程序中各目標文件的地址空間的組織,這可能涉及重定位工作。
概述
計算機程序通常由幾個部分或模塊組成;這些部件/模塊不需要包含在單個對象文件中,在這種情況下,它們通過符號作為地址相互引用到其他模塊中,這些模塊在鏈接執行時映射到內存地址。
雖然鏈接過程旨在最終將這些獨立的部分組合在一起,但有很多充分的理由在源代碼級別單獨開發這些部分。這些原因包括易于將幾個較小的部分組織在一個整體上,并且能夠更好地定義每個單獨部分的目的和職責,這對于管理復雜性和提高軟件架構的長期可維護性至關重要。
通常,目標文件可以包含三種符號:
定義的“外部”符號,有時稱為“公共”或“入口”符號,允許其他模塊調用它未定義的“外部”符號,引用定義這些符號的其他模塊,以及本地符號,在對象文件內部使用,以方便重新定位。
對于大多數編譯器,每個目標文件都是編譯一個輸入源代碼文件的結果。當一個程序包含多個對象文件時,鏈接器將這些文件組合成一個統一的可執行程序,并在符號進行時解析符號。
鏈接器可以從稱為庫或運行時庫的集合中獲取對象。大多數鏈接器不會在輸出可執行文件中包含靜態庫中的所有目標文件;它們僅包括庫中由其他對象文件或庫直接或間接引用的對象文件。但是對于共享庫,必須在運行時加載整個庫,因為不知道在運行時將調用哪些函數或方法。因此,庫鏈接可能是一個迭代過程,一些引用的模塊需要鏈接其他模塊,依此類推。庫的存在目的多種多樣,默認情況下通常鏈接一個或多個系統庫。
鏈接器還負責在程序的地址空間中排列對象。這可能涉及將假定特定基址的代碼重新定位到另一個基址中。由于編譯器很少知道對象將駐留在何處,因此它通常假定一個固定的基本位置(例如,零)。重新定位機器代碼可能涉及絕對跳轉、負載和存儲的重新定位。
鏈接器的可執行輸出在最終加載到內存中時(就在執行之前)可能需要另一個重定位傳遞。在提供虛擬內存的硬件上通常省略此傳遞:每個程序都放入自己的地址空間中,因此即使所有程序都在同一基址加載,也不會發生沖突。如果可執行文件是與位置無關的可執行文件,也可以省略此傳遞。
在某些 unix 變體(例如 SINTRAN III)上,由鏈接器執行的過程(將目標文件組裝到程序中)稱為加載(如將可執行代碼加載到文件上)。 此外,在某些操作系統中,同一個程序同時處理鏈接和加載程序(動態鏈接)的工作。
動態鏈接
許多操作系統環境允許動態鏈接,將某些未定義符號的解析推遲到程序運行。這意味著可執行代碼仍包含未定義的符號,以及將為這些符號提供定義的對象或庫列表。加載程序也將加載這些對象/庫,并執行最終鏈接。
這種方法有兩個優點:
也有缺點:
封閉式或虛擬環境可以進一步允許系統管理員減輕或權衡這些單獨的利弊。
靜態鏈接
靜態鏈接是鏈接器將程序中使用的所有庫例程復制到可執行映像中的結果。與動態鏈接相比,這可能需要更多的磁盤空間和內存,但更便攜,因為它不需要在運行它的系統上存在庫。靜態鏈接還可以防止“DLL 地獄”,因為每個程序都包含它所需的庫例程版本,并且與其他程序沒有沖突。僅使用庫中的幾個例程的程序不需要安裝整個庫。
搬遷
由于編譯器沒有關于最終輸出中對象布局的信息,因此它無法利用對另一個對象的地址提出要求的更短或更有效的指令。例如,跳轉指令可以引用絕對地址或與當前位置的偏移量,并且偏移量可以根據到目標的距離用不同的長度表示。通過首先生成最保守的指令(通常是最大的相對或絕對變體,取決于平臺)并添加放松提示,可以在最終鏈接期間替換更短或更有效的指令。在跳轉優化方面,這也稱為自動跳轉大小。只有在讀取所有輸入對象并分配臨時地址后,才能執行此步驟;鏈接器放寬傳遞隨后會重新分配地址,這反過來可能會允許發生更多潛在的放寬。通常,替換序列較短,這使得該過程始終收斂于給定固定對象順序的最佳解;如果不是這種情況,松弛可能會發生沖突,鏈接器需要權衡任一選項的優點。
雖然指令放寬通常發生在鏈接時,但模塊內部放寬已經可以作為編譯時優化過程的一部分進行。在某些情況下,松弛也可能在加載時發生,作為重定位過程的一部分,或與動態死碼消除技術相結合。
聯動編輯器
在 IBM System/360 大型機環境(如 OS/360)中,包括 z/建筑 大型機的 z/OS,這種類型的程序稱為鏈接編輯器。顧名思義,鏈接編輯器具有允許添加、替換和/或刪除單個程序部分的附加功能。操作系統(如 OS/360)具有可執行加載模塊的格式,其中包含有關程序組件部分的補充數據,以便可以替換單個程序部分,并更新程序的其他部分,以便可重定位地址和其他引用可以由鏈接編輯器更正,作為該過程的一部分。
這樣做的一個優點是,它允許維護程序,而不必保留所有中間對象文件,也不必重新編譯未更改的程序部分。它還允許以小文件(最初是卡組)的形式分發程序更新,僅包含要替換的對象模塊。在此類系統中,目標代碼采用 80 字節穿孔卡圖像的形式和格式,以便可以使用該介質將更新引入系統。在 OS/360 的更高發行版和后續系統中,裝入模塊包含有關組件模塊版本的附加數據,以創建可跟蹤的更新記錄。它還允許從已鏈接的加載模塊中添加、更改或刪除疊加結構。
術語“鏈接編輯器”不應被解釋為暗示程序在用戶交互模式下運行,如文本編輯器。它用于批處理模式執行,編輯命令由用戶以順序組織的文件(如穿孔卡、DASD 或磁帶)提供。
鏈接編輯(IBM 命名法)或合并或收集(ICL 命名法)是指鏈接編輯者或合并者將各個部分組合成一個可重定位的二進制文件的行為,而在目標地址加載和重定位到絕對二進制文件通常被認為是一個單獨的步驟。
鏈接器控制腳本
一開始,鏈接器使用戶對生成的輸出對象文件的排列的控制非常有限。隨著目標系統變得復雜,具有不同的內存要求,例如嵌入式系統,有必要讓用戶控制生成具有特定要求的輸出對象文件,例如定義段的基址。鏈接器控制腳本用于此目的。
GNU鏈接器
GNU鏈接器(或GNU ld)是GNU項目的Unix命令ld的自由軟件實現,它是GNU二進制工具(binutils)的一部分。GNU ld可以接受鏈接器腳本,以對鏈接過程行使更大的控制。除了傳統的GNU ld,還有一個名為gold的精簡ELF-only版本。其他鏈接器如LLVM項目的lld和mold也提供了與GNU工具兼容的高效替代方案。
參考資料 >