Linux執行時動態庫搜尋路徑優先順序

水云间月掌柜發表於2024-10-23

Windows執行時動態庫搜尋路徑優先順序:

在Windows執行時,動態庫(通常指DLL檔案)的搜尋路徑遵循一定的優先順序順序,以確保程式能夠正確地載入所需的動態庫。以下是對Windows執行時動態庫搜尋路徑優先順序的總結:

  1. 應用程式所在的目錄
  • 當一個應用程式(如exe檔案)嘗試載入一個DLL時,它首先會在自己所在的目錄中查詢該DLL檔案。這是搜尋路徑中的第一優先順序。
  1. 系統目錄(System32)
  • 如果在應用程式所在目錄中沒有找到DLL,系統接下來會在系統目錄中查詢。在Windows作業系統中,這通常是C:\Windows\System32目錄。此目錄包含了大量系統級的DLL檔案,這些檔案對於作業系統的正常執行至關重要。
  1. Windows目錄
  • 如果系統目錄中也沒有找到所需的DLL,搜尋會繼續到Windows目錄下,即C:\Windows
  1. 環境變數PATH中指定的目錄
  • 如果以上三個位置都沒有找到DLL,系統會檢查環境變數PATH中列出的所有目錄。PATH環境變數是一個系統級變數,它包含了多個目錄的路徑,用於指導系統在執行檔案或指令碼時搜尋相關檔案的路徑。因此,使用者可以透過修改PATH環境變數來新增額外的搜尋路徑。

一、Linux執行時動態庫搜尋路徑優先順序基礎:

在Linux系統中,執行時動態庫(shared libraries)的搜尋路徑優先順序是由多個因素決定的。以下是這些因素的詳細解釋和優先順序順序:

1. RPATH

  • 定義:RPATH(也稱為DT_RPATH)是在編譯時設定在可執行檔案中的路徑,它指定了程式執行時應該搜尋庫檔案的位置。
  • 優先順序:如果可執行檔案包含了RPATH,那麼連結器會首先按照RPATH指定的路徑來搜尋所需的庫。
  • 檢視和修改方法:可以使用readelf -d xxx | grep rpath來檢視庫檔案是否指定了rpath,可以在編譯時設定RPATH。
// 在編譯時設定rpath
if(OS_LINUX)
    set_target_properties(xxx PROPERTIES LINK_FLAGS "-Wl,-rpath='$ORIGIN' ")
endif()

備註:$ORIGIN是一個動態連結器的特殊變數,表示可執行檔案或共享庫檔案所在的目錄,可以用於指定相對路徑。

2. LD_LIBRARY_PATH環境變數

  • 定義:LD_LIBRARY_PATH是一個環境變數,使用者可以在執行時設定,以新增額外的庫搜尋路徑。
  • 優先順序:LD_LIBRARY_PATH中指定的路徑會在系統預設路徑之前進行查詢,但其優先順序低於RPATH。
  • 設定方法
    • 臨時設定:可以透過export LD_LIBRARY_PATH=/my/lib/path/來設定環境變數;
    • 永久設定(不安全不推薦):可以新增到shell的配置檔案中,如:~/.bashrc或~/.bash_profile,設定完後執行source命令即可立即生效。

3. RUNPATH

  • 定義:RUNPATH是另一個與RPATH相似的特性,同樣是在可執行檔案或共享庫被連結時設定,用於指定執行時查詢共享庫的路徑。值得注意的是,當RUNPATH存在時,它會覆蓋RPATH的設定。
  • 優先順序:RUNPATH中指定的路徑會在系統預設路徑之前進行查詢,但其優先順序低於LD_LIBRARY_PATH。
  • 檢視和設定方法:可以使用readelf -d xxx | grep runpath來檢視庫檔案是否指定了runpath,並使用工具如patchelf來設定和修改RUNPATH
// 使用工具修改runpath
patchelf --set-rpath '$ORIGIN' xxx

4. ld.so.cache

  • 定義:/etc/ld.so.cache是動態連結器ld.so的快取檔案,儲存了系統中所有可用的共享庫路徑和名稱資訊。/etc/ld.so.cache由ldconfig命令生成,ldconfig會掃描系統中指定路徑下的共享庫檔案,並更新/etc/ld.so.cache檔案
  • 優先順序:ld.so.cache的優先順序低於LD_LIBRARY_PATH和RUNPATH,但高於其他,這是為了快速找到和載入需要的共享庫。
  • 更新方法:執行sudo ldconfig命令來更新快取。
// /etc/ld.so.cache是二進位制檔案可以透過strings命令檢視其內容,例如檢視libstdc++.so的搜尋路徑
strings /etc/ld.so.cache | grep libstdc++.so

5. 配置檔案/etc/ld.so.conf及/etc/ld.so.conf.d/目錄

  • 定義:/etc/ld.so.conf是系統的庫配置檔案,包含了系統級別的庫搜尋路徑。/etc/ld.so.conf.d/目錄用於存放額外的配置檔案。
  • 優先順序:連結器會讀取這些檔案,並按照其中列出的路徑來搜尋庫,其優先順序低於LD_LIBRARY_PATH和RUNPATH。
  • 修改方法:修改這些檔案後,可以執行sudo ldconfig命令來更新快取。

6. 預設的系統路徑

  • 定義:在大多數Linux系統中,/lib和/usr/lib目錄(以及它們的64位對應目錄/lib64和/usr/lib64)都是預設的庫搜尋路徑。
  • 優先順序:這些路徑的優先順序最低,如果在前面的路徑中都沒有找到所需的庫,連結器會回退到這些預設路徑。

總結

Linux執行時動態庫的搜尋路徑優先順序大致如下:

  1. RPATH(DT_RPATH)
  2. LD_LIBRARY_PATH環境變數
  3. RUNPATH(如果DT_RPATH為空)
  4. ld.so.cache
  5. 配置檔案/etc/ld.so.conf及/etc/ld.so.conf.d/目錄
  6. 預設的系統路徑(/lib, /usr/lib, /lib64, /usr/lib64)
 1 開始
 2    ├──> 檢查RPATH(DT_RPATH)
 3    │       ├──> 如果找到庫,則載入並使用
 4    │       └──> 否則,繼續
 5    ├──> 檢查LD_LIBRARY_PATH環境變數
 6    │       ├──> 如果找到庫,則載入並使用
 7    │       └──> 否則,繼續
 8    ├──> 檢查RUNPATH(如果DT_RPATH為空)
 9    │       ├──> 如果找到庫,則載入並使用
10    │       └──> 否則,繼續
11    ├──> 搜尋ld.so.cache快取
12    │       ├──> 如果找到庫,則載入並使用
13    │       └──> 否則,繼續
14    ├──> 讀取配置檔案/etc/ld.so.conf及/etc/ld.so.conf.d/目錄
15    │       ├──> 對於每個配置檔案中的路徑
16    │       │       ├──> 搜尋該路徑
17    │       │       │       ├──> 如果找到庫,則載入並使用
18    │       │       │       └──> 否則,繼續搜尋
19    │       │       └──> 結束搜尋該路徑
20    │       └──> 如果所有配置檔案中均未找到,則繼續
21    └──> 搜尋預設的系統路徑(/lib, /usr/lib, /lib64, /usr/lib64)
22         ├──> 如果找到庫,則載入並使用
23         └──> 否則,報告錯誤,無法找到庫

請注意,這個順序可能會根據具體的Linux發行版和連結器實現而有所不同。在實際應用中,可以透過ldd命令來檢視一個可執行檔案或庫檔案的依賴關係,以及它們是如何被解析的。

補充說明:

$ORIGIN和./的區別

在Linux和其他類Unix系統中,當動態連結庫(如.so檔案)被載入時,系統需要知道從哪裡查詢這些庫。rpath(執行時庫搜尋路徑)是儲存在可執行檔案或庫本身中的一種機制,用於指示動態連結器在哪些位置查詢依賴的庫。

Library rpath:[$ORIGIN/]和 Library rpath:[./]指定了兩種不同的相對路徑:

1. [$ORIGIN/]:

  • $ORIGIN 是一個特殊的佔位符,代表可執行檔案或庫檔案自身的目錄;
  • 當設定為[$ORIGIN/]時,它告訴動態連結器在可執行檔案或庫所在的同一目錄下查詢依賴的庫;
  • 這意味著,如果你的可執行檔案位於/path/to/app/myapp,那麼動態連結器會在 /path/to/app目錄下查詢依賴的庫。

2. [. /]:

  • ./表示當前工作目錄,即啟動可執行檔案時所在的目錄;
  • 當設定為[./]時,它指示動態連結器在啟動可執行檔案的當前目錄下查詢依賴的庫;
  • 這意味著,如果你在 /home/user/目錄下執行/path/to/app/myapp,那麼動態連結器會在/home/user/ 目錄下查詢依賴的庫,而不是在可執行檔案所在的 /path/to/app/目錄下。

兩者的主要區別在於它們所引用的基準點不同:[$ORIGIN/]是相對於可執行檔案或庫的位置,而[./]是相對於當前工作目錄。在實際應用中,選擇哪種方式取決於你的具體需求,比如你的應用程式和庫是如何被部署和使用的。通常,[$ORIGIN/]更為靈活和可靠,因為它不依賴於使用者從哪個日錄啟動應用程式。

2)確認動態庫優先順序常用到的幾個命令

ldd:ldd 是一個工具,用於顯示可執行檔案或共享庫所依賴的共享庫。其使用方法為:ldd xxx 或者 ldd libA.so

readelf:readelf 是一個命令列工具,專門用於檢視ELF(Executable and Linkable Format)檔案的資訊。其用法包括:readelf -d xxx 或者 readelf -d libA.so

ps和lsof:在結合使用ps和lsof時,首先需要利用 ps -ef | grep xxx 命令來查詢符合要求的程序ID,然後利用 lsof -p [PID] 命令來檢視該程序開啟的所有檔案,從而確認模組載入的路徑。

strace:strace 是一個強大的工具,能夠透過列印出可執行程式的載入路徑來確定優先順序。其使用示例為:strace -e open,openat -o xxx.log ./xxx。該命令將執行名為xxx的可執行程式,並捕獲所有open和openat系統呼叫,將相關資訊輸出到xxx.log檔案中,透過檢視日誌也能確認模組載入的路徑。

二、Linux執行時動態庫搜尋路徑優先順序規則探索:

在充分掌握動態庫搜尋路徑優先順序的基石概念之後,我們能夠初步預估程式執行過程中如何精確定位並載入特定的動態庫。然而,在錯綜複雜的大型專案背景下,時常遭遇同一動態庫存在多個版本的場景。為確保程式準確無誤地載入所需版本的庫,深化對搜尋路徑優先順序原則的理解顯得尤為重要。

核心議題探討

利用ldd命令檢視一個可執行檔案或動態庫所依賴的共享庫清單時,此清單是否必然反映程式執行時將載入的動態庫?

針對此議題,可進一步細化為兩個關鍵問題進行剖析:

1. 雙重依賴下的優先順序:若可執行檔案與動態庫均對某一動態庫存在依賴,其搜尋與載入的優先順序機制如何?
2. 間接依賴的優先順序:在多個動態庫均依賴於同一動態庫,而該依賴鏈不直接關聯至可執行檔案時,其搜尋與載入的優先順序又將如何確定?

規則總結

  • 基礎步驟優先:總體上,遵循既定的優先順序基礎步驟進行搜尋。
  • 架構特定路徑優先:在每個搜尋路徑下,結合系統架構、執行緒區域性儲存等特性(如glibc-hwcaps/x86-64-v4、tls/x86_64/x86_64等),優先在經此拼接後的路徑中查詢。
  • 時序原則:依據動態庫載入的先後順序進行。
  • 同級動態庫優先原則:是指當動態庫依賴某一動態庫,而在其載入之前未載入其所依賴的動態庫,那麼同級別優先順序下,會優先按照動態庫自身指定的路徑查詢,然後再去可執行程式指定的路徑查詢。
  • 路徑去重:值得注意的是,對於已確認不存在的路徑,系統將避免重複搜尋。

例項分析

為增進理解,我們將透過具體例項進行預測與驗證,以直觀展示上述原則的應用與實踐。

此處省略例項分析圖片......

透過上圖可以看到動態庫的載入路徑與我們分析的結果一致,詳細的搜尋步驟可以透過strace命令驗證。

總結

本文詳盡地梳理了Linux系統中執行時動態庫(shared libraries)搜尋路徑優先順序的基本規則,並在此基礎上,藉助具體例項深入剖析並總結了這些優先順序規則的運作機理。此外,本文還針對實際專案中可能遇到的動態庫載入問題,提出了具有實用價值的排查參考方案。期望本文能為各位在未來工作中遭遇的動態庫依賴問題(諸如版本相容性問題等)提供有力支援與幫助。

相關文章