<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          LWN:看一下動態(tài)鏈接過程!

          共 4588字,需瀏覽 10分鐘

           ·

          2024-04-11 01:27

          關(guān)注了就能看到更多這么棒的文章哦~

          A look at dynamic linking

          By Daroc Alden
          February 13, 2024
          Gemini translation
          https://lwn.net/Articles/961117/

          動態(tài)鏈接程序是現(xiàn)代 Linux 系統(tǒng)的關(guān)鍵組件,負(fù)責(zé)設(shè)置大多數(shù)進(jìn)程的地址空間。雖然隨著最初推動動態(tài)鏈接的因素變得越來越?jīng)]有那么重要了,使得靜態(tài)鏈接二進(jìn)制文件變得越來越流行,但是動態(tài)鏈接仍然是默認(rèn)設(shè)置。本文會介紹一下動態(tài)鏈接程序為執(zhí)行程序做準(zhǔn)備工作時的采取的步驟。

          調(diào)用鏈接程序(linker)

          當(dāng) Linux 內(nèi)核受命執(zhí)行一個程序時,它會查看文件頭以確定其是哪種類型的程序。然后,內(nèi)核查閱自身針對 shell 腳本和本機(jī)二進(jìn)制文件的內(nèi)置規(guī)則,以及 binfmt_misc 設(shè)置(這是允許用戶注冊自定義程序解釋器的一個內(nèi)核特性)以確定如何處理該程序。以 “#!” 開頭 的文件被標(biāo)識為作為該行其余部分中指定的解釋器的輸入。此功能就是讓內(nèi)核顯得可以直接執(zhí)行 shell 腳本的原因 — 實際上它執(zhí)行的是一個解釋器,并將腳本作為參數(shù)傳遞。另一方面,以 “\x7fELF”開頭的文件被識別為 ELF 文件。內(nèi)核首先查看該文件是否包含 程序標(biāo)頭 中的 PT_INTERP 元素。若存在,則此元素表明該程序是動態(tài)鏈接的。

          PT_INTERP 元素還指定程序期望哪個動態(tài)鏈接程序進(jìn)行鏈接 — 又稱解釋器,令人困惑。在 Linux 中,動態(tài)鏈接程序通常存儲在特定體系結(jié)構(gòu)的路徑下,例如 /lib64/ld-linux-x86-64.so.2. 不允許鏈接程序本身動態(tài)鏈接,以避免無限嵌套。找到動態(tài)鏈接程序后,內(nèi)核會以與其他任何靜態(tài)鏈接可執(zhí)行文件相同的 方式 設(shè)置其初始地址空間,并給它一個指向要執(zhí)行的程序的打開的文件描述符。

          重定位

          動態(tài)鏈接程序的工作是對進(jìn)程的地址空間進(jìn)行安排,使其既包含主可執(zhí)行文件本身,還包含該文件所依賴的所有庫。對于現(xiàn)代可執(zhí)行文件來說,這幾乎肯定會加載 位置無關(guān)代碼(position-independent code),這種代碼設(shè)計用于從非固定基本地址運行。許多代碼段仍然需要知道內(nèi)存的不同部分(section)的位置,因此位置無關(guān)可執(zhí)行文件包含一份 “重定位(relocations)”列表:動態(tài)鏈接程序需用內(nèi)存中各種組件的實際地址來修補的二進(jìn)制文件中特定位置。在大多數(shù)程序中,大部分重定位都是對 全局偏移表 (GOT) 的修補,其中包含指向全局變量或已加載的段(section),還有鏈接時常量(link-time)的指針。

          允許動態(tài)鏈接程序?qū)⒉煌慕M件加載至不同的地址能夠起到幾個作用。最早的位置無關(guān)代碼形式存在于使用基址寄存器訪問內(nèi)存地址的體系結(jié)構(gòu)中,使得一個程序的多個副本能夠以不同的地址駐留在內(nèi)存中。動態(tài)地址轉(zhuǎn)換(dynamic address translation)的發(fā)明使得該動機(jī)變得無關(guān)緊要了。現(xiàn)代工具鏈針對 地址空間布局隨機(jī)化 (ASLR) 使用了這個靈活功能,允許動態(tài)鏈接程序向所選位置添加一個隨機(jī)值。靜態(tài)鏈接帶有位置無關(guān)代碼的程序(包括動態(tài)鏈接程序本身)都將會加載到內(nèi)核選擇的隨機(jī)地址上。因此,動態(tài)鏈接程序的第一個主要任務(wù)是讀取它自己的程序標(biāo)頭并對其本身進(jìn)行重定位。此過程包括用全局結(jié)構(gòu)和函數(shù)的位置,來修補后續(xù)代碼 — 在進(jìn)行這些重定位之前,鏈接器會小心不從其他編譯單元調(diào)用任何函數(shù)或訪問任何全局變量。

          動態(tài)鏈接器進(jìn)行重定位后,就可以調(diào)用其特定平臺的函數(shù)來執(zhí)行特定操作系統(tǒng)的設(shè)置。在 Linux 中,它調(diào)用 brk() 來設(shè)置進(jìn)程的數(shù)據(jù)段,包括為其自身狀態(tài)分配一些空間,并通知 malloc() 將分配放在新分配的空間中(它與程序最終的堆是分開的)。此時, malloc() 引用了一個臨時實現(xiàn),該實現(xiàn)甚至無法釋放內(nèi)存,該實現(xiàn)會在動態(tài)鏈接器識別并對所需依賴項應(yīng)用重定位時來使用。

          完成依賴項識別的第一步是設(shè)置鏈接映射(link map) — 即由 dlinfo() 使用的記錄信息結(jié)構(gòu)。完成后,動態(tài)鏈接器會找到內(nèi)核映射的 vDSO,一個共享對象包括可以在無需切換至內(nèi)核的情況下為某些系統(tǒng)調(diào)用服務(wù)的代碼,這是一種常用于 gettimeofday() 系統(tǒng)調(diào)用的技術(shù)。動態(tài)鏈接器將 vDSO 放入鏈接映射中,以便處理其他依賴項的鏈接時所使用的同一代碼可以處理調(diào)用 vDSO 的共享對象。

          到此,似乎可以實際讀取和鏈接程序所依賴的共享對象了,但還需要完成一個步驟。用戶可以通過在 LD_PRELOAD 環(huán)境變量中指定共享對象來在運行時覆蓋函數(shù)。通過這種方式覆蓋函數(shù)可以用于許多目的,例如,對應(yīng)用程序進(jìn)行調(diào)試、使用備用分配器(例如 Boehm-Demers-Weiser 型保守垃圾回收器 或 jemalloc)、或使用諸如 libfaketime 之類的工具來偽造該應(yīng)用程序的時間和日期。動態(tài)鏈接器首先解析預(yù)加載庫,這樣在以后鏈接庫時,它可以直接向它們發(fā)送已覆蓋的函數(shù)定義。

          現(xiàn)在,動態(tài)鏈接器已經(jīng)具備完成程序地址空間排列所需的一切條件。從正在加載的程序開始,鏈接器在程序頭中查找 DT_NEEDED 聲明,這些聲明表明程序依賴另一個共享對象。鏈接器會遞歸搜索這些 DT_NEEDED 聲明,構(gòu)建程序的任何傳遞依賴項所需的所有共享對象的列表。 DT_NEEDED 條目可以包含絕對路徑,但也可以包含通過查閱 LD_LIBRARY_PATH 環(huán)境變量中的目錄或一組默認(rèn)目錄來解析的相對路徑。然后,動態(tài)鏈接器反向遍歷這個依賴項的列表,這樣,某一共享對象的依賴項在其加載之前就已經(jīng)加載好了。

          對于每個共享對象,鏈接器都會打開已解析的文件,將其加載到地址空間中新分配(并使用 ASLR 隨機(jī)化處理過)的位置,然后執(zhí)行共享對象頭中列出的重定位集。然后,它將共享對象添加到鏈接映射中。

          其中的例外是動態(tài)鏈接器自身。程序允許將動態(tài)鏈接器依賴為庫,以提供諸如 dlinfo() 之類的功能,但是如果鏈接器在循環(huán)中間對自己應(yīng)用重定位,它就會損壞,因此它將自身排除在依賴項列表之外。如上所述,它已經(jīng)對自己應(yīng)用了重定位。但是,鏈接器偶爾會使用可通過預(yù)加載庫覆蓋的函數(shù)。因此,一旦程序的所有依賴項都已放入地址空間,鏈接器就會對自己最后進(jìn)行一次重定位,以便現(xiàn)在它可以引用其使用的任何函數(shù)的已覆蓋版本?,F(xiàn)在,動態(tài)鏈接器使用的 malloc() 實現(xiàn)從到此為止使用的簡化實現(xiàn)切換到程序的其余部分使用的通用實現(xiàn)。

          隨著所有共享庫的加載,動態(tài)鏈接器已經(jīng)完成了大部分工作。但是,在跳轉(zhuǎn)到主程序之前,它會設(shè)置 線程本地存儲 (TLS),并執(zhí)行由 C 庫要求的任何初始化。然后,它把內(nèi)核提供程序的參數(shù)狀態(tài)及其環(huán)境恢復(fù)出來,并跳轉(zhuǎn)到程序的入口點。

          用戶代碼

          人們可能會認(rèn)為動態(tài)鏈接器的職責(zé)在主程序開始時就已結(jié)束,但事實并非如此。它不僅必須對運行時需要的 dlopen() 新共享對象的請求進(jìn)行服務(wù),而且還有一部分工作會在所加載程序的生命周期內(nèi)運行:更新過程鏈接表 (PLT, Procedure Linkage Table)。

          盡管程序可以簡單地使用重定位來直接修補程序正文中的 CALL 指令,但是這樣的“文本重定位(text relocation)”會導(dǎo)致兩個性能問題。首先,由于所需的重定位數(shù)將取決于調(diào)用給定函數(shù)的次數(shù)(可能很大),因此將這些重定位最初應(yīng)用于共享對象可能較慢。其次,由于文本重定位會弄臟包含程序可執(zhí)行代碼的內(nèi)存頁面,因此運行同一程序的不同進(jìn)程不再能夠共享相同的底層內(nèi)存,從而增加了程序的內(nèi)存使用情況。這些性能問題意味著動態(tài)鏈接器的維護(hù)者 普遍反對 進(jìn)行文本重定位。

          通過創(chuàng)建一個 PLT — 一個特殊的獨立部分,其中包含對每個外部定義函數(shù)的間接引用,現(xiàn)代編譯器和鏈接器可以解決這個問題。從程序內(nèi)部調(diào)用這些函數(shù)會編譯為對 PLT 的調(diào)用,然后 PLT 中包含一個跳轉(zhuǎn)到外部函數(shù)的真實位置的指令。PLT 也可以直接使用文本重定位,但大多數(shù)體系結(jié)構(gòu)改為通過第二個 GOT(從程序的正常 GOT 分離,在其自己稱為 “.plt.got” 的 section 中)中存儲的函數(shù)指針進(jìn)行間接跳轉(zhuǎn)。對于無法以緊湊方式對跳轉(zhuǎn)到地址空間的任意部分進(jìn)行編碼的體系結(jié)構(gòu)來說,分離函數(shù)指針很有用,但對于一項最終提升性能的技巧來說也很有用:延遲鏈接。

          bcc28f3e995527805a50ab257daa645d.webp

          與那些指向數(shù)據(jù)、在程序開始運行之前需要解析的重定位不同(因為動態(tài)鏈接器無法知道何時會訪問它們),PLT 的 GOT 中的重定位不必立即應(yīng)用。動態(tài)鏈接器最初用來自動態(tài)鏈接器自身的一個函數(shù)的地址來填充 PLT 的 GOT。此函數(shù)查詢鏈接映射以確定有問題的外部符號位于何處,然后重寫 PLT 的 GOT 的相應(yīng)條目。僅對實際調(diào)用的外部函數(shù)執(zhí)行鏈接,而且僅在程序啟動后執(zhí)行。對于大多數(shù)程序來說,這可以提高性能并使程序的初始啟動速度更快。

          可以使用 LD_BIND_NOW 環(huán)境變量,或通過使用 “-z now” 鏈接器選項編譯程序來關(guān)閉延遲鏈接。就新發(fā)布的 glibc 版本 2.39 來說,glibc 的動態(tài)鏈接器還支持重寫 PLT 元素以使用直接跳轉(zhuǎn)而不是間接跳轉(zhuǎn),用在一些關(guān)注性能的系統(tǒng)上。報道 文章中公開宣稱,引入 PLT 重寫是出于安全動機(jī),但 一位評論者 指出,前兩種方法的存在表明 PLT 重寫最有用之處在于作為性能調(diào)整設(shè)置。然而,有一個好處是可以禁用延遲鏈接:它允許 "遷移只讀(Relocation Read-Only, RELRO)",這是一種安全緩解措施,這種情況下動態(tài)鏈接器在填入所有的 GOT(和 PLT 的 GOT)后將其重新映射為只讀,從而阻止攻擊覆蓋它們以控制進(jìn)程的控制流。

          對于大多數(shù)程序而言,動態(tài)鏈接器基本上是不可見的,但它在建立每個進(jìn)程的地址空間方面發(fā)揮了至關(guān)重要的作用。大多數(shù)程序員絕不會需要與它設(shè)置程序運行的具體過程打交道。但是,動態(tài)鏈接器明顯的穩(wěn)定性掩蓋了其中大量令人意外的復(fù)雜性,這是為了確保能夠有效準(zhǔn)備程序以便執(zhí)行。

          全文完
          LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。

          歡迎分享、轉(zhuǎn)載及基于現(xiàn)有協(xié)議再創(chuàng)作~

          長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~



          瀏覽 34
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  av影音先锋电影网一区二区三区 | 一级操比比 | 五月婷婷色综合 | 黄色视频链接在线观看 | 中文字幕亚洲无码在线 |