執行期載入時共享庫路徑搜尋優先順序實驗

paw5zx發表於2024-08-01

目錄
  • 前言
  • 實驗環境
  • 目錄說明
  • 單獨測試
    • 不配置路徑
    • 預設路徑
    • ld.so.cache
    • RUNPATH
    • LD_LIBRARY_PATH
    • RPATH
  • 優先順序測試
  • 附錄
    • 庫檔案原始碼
    • 主程式原始碼
    • makefile
    • 指令碼
      • run_none
      • run_default
      • run_ld_so_cache
      • run_runpath
      • run_ld_library_path
      • run_rpath
      • run_cmp_all

前言

《共享庫連結和載入時的路徑搜尋優先順序》中提到,共享庫在執行期載入時的路徑搜尋優先順序為:
RPATH>LD_LIBRARY_PATH>RUNPATH>/etc/ld.so.conf/etc/ld.so.conf.d/*>預設庫路徑

本實驗分兩步驗證上述結論
①單獨測試每種方法指定的路徑的可行性
②檢視連結器輸出的詳細資訊,驗證上述優先順序順序

實驗環境

作業系統:Ubuntu 20.04
編譯器:g++-11.4.0
make:GNU Make 4.2.1

目錄說明

專案的目錄結構如下:

.
├── lib
├── obj
├── libhello.cpp
├── main.cpp
├── run_cmp_all.sh
├── run_default.sh
├── run_ld_library_path.sh
├── run_ld_so_cache.sh
├── run_none.sh
├── run_rpath.sh
├── run_runpath.sh
└── makefile

其中:

  • lib為存放共享庫的資料夾
  • obj為存放可重定位目標檔案的資料夾
  • libhello.cpp為共享庫原始碼,它將被編譯為libhello.so.1.1.0(soname為libhello.so.1
  • mian.cpp為主函式,其中呼叫了hello函式
  • run_cmp_all.sh為對比測試上述路徑優先順序的指令碼
  • run_default.sh為單獨測試預設路徑可行性的指令碼
  • run_ld_library_path.sh為單獨測試LD_LIBRARY_PATH可行性的指令碼
  • run_ld_so_cache.sh為單獨測試ld.so.cache可行性的指令碼
  • run_none.sh為不配置任何路徑的指令碼,用於演示搜尋不到動態庫檔案,程式無法執行的情況
  • run_rpath.sh為單獨測試RPATH可行性的指令碼
  • run_runpath.sh為單獨測試RUNPATH可行性的指令碼
  • makefile為自動化構建指令碼

本文主要討論執行期的載入過程,因此不對編譯期的過程做過多解釋,內容詳見makefile檔案

在附錄中,我將提供本次實驗涉及到的程式碼。

單獨測試

不配置路徑

不做任何路徑的配置並且不在預設路徑下放置libhello.so.1檔案,檢視程式是否可以執行成功。

直接執行指令碼即可完成上述操作:

sh ./run_none.sh

輸出:

./hello: error while loading shared libraries: libhello.so.1: cannot open shared object file: No such file or directory

可以看到由於我們沒有配置任何額外的搜尋路徑,並且沒有在預設搜尋路徑下放置libhello.so.1檔案,動態連結器就找不到相應的共享庫檔案,就會導致載入失敗,程式無法執行。

預設路徑

libhello.so.1.1.0複製至預設搜尋路徑/usr/lib,並在/usr/lib下建立一個軟連結(libhello.so.1)指向它
執行可執行檔案。

直接執行指令碼即可完成上述操作:

sh ./run_default.sh

輸出:

Hello from the 1.1.0 library!

沒有報錯。

ld.so.cache

建立路徑/opt/hellolib_runtime
libhello.so.1.1.0複製至/opt/hellolib_runtime,並在/opt/hellolib_runtime下建立一個軟連結(libhello.so.1)指向它
/etc/ld.so.conf.d中新增一個配置檔案libhello.conf,並向其中寫入動態庫檔案所在的路徑/opt/hellolib_runtime
使用ldconfig命令更新ld.so.cache
執行可執行檔案

直接執行指令碼即可完成上述操作:

sh ./run_ld_so_cache.sh

輸出:

Hello from the 1.1.0 library!

沒有報錯。

RUNPATH

在編譯期新增-Wl,--enable-new-dtags,-rpath,/opt/hellolib_runtime指定RUNPATH
libhello.so.1.1.0複製至/opt/hellolib_runtime,並在/opt/hellolib_runtime下建立一個軟連結(libhello.so.1)指向它
執行可執行檔案

直接執行指令碼即可完成上述操作:

sh ./run_runpath.sh

輸出:

Hello from the 1.1.0 library!

沒有報錯。

LD_LIBRARY_PATH

建立路徑/opt/hellolib_runtime
libhello.so.1.1.0複製至/opt/hellolib_runtime,並在/opt/hellolib_runtime下建立一個軟連結(libhello.so.1)指向它
向環境變數LD_LIBRARY_PATH中新增/opt/hellolib_runtime
執行可執行檔案

直接執行指令碼即可完成上述操作:

sh ./run_ld_library_path.sh

輸出:

Hello from the 1.1.0 library!

沒有報錯。

RPATH

在編譯期新增-Wl,--disable-new-dtags,-rpath,/opt/hellolib_runtime指定RPATH
libhello.so.1.1.0複製至/opt/hellolib_runtime,並在/opt/hellolib_runtime下建立一個軟連結(libhello.so.1)指向它
執行可執行檔案

直接執行指令碼即可完成上述操作:

sh ./run_rpath.sh

輸出:

Hello from the 1.1.0 library!

沒有報錯。

優先順序測試

在此模組我們要測試共享庫在執行期載入時的路徑搜尋優先順序(沒有測RUNPATH,有興趣的朋友可以自行測試一下),關鍵步驟如下:
建立路徑/opt/hellolib_runtime,/opt/hellolib_runtime1,/opt/hellolib_runtime2
在編譯期新增-Wl,--disable-new-dtags,-rpath,/opt/hellolib_runtime指定RPATH
向環境變數LD_LIBRARY_PATH中新增/opt/hellolib_runtime1
/etc/ld.so.conf.d中新增一個配置檔案libhello.conf,向其中寫入路徑/opt/hellolib_runtime2,並使用ldconfig命令更新ld.so.cache
libhello.so.1.1.0複製至預設搜尋路徑/usr/lib,並在/usr/lib下建立一個軟連結(libhello.so.1)指向它
執行可執行檔案

直接執行指令碼即可完成上述操作:

sh ./run_cmp_all.sh

在指令碼中,我設定了環境變數LD_DEBUG=libs,啟用了動態連結器的除錯輸出(針對庫的載入過程),因此終端輸出了以下資訊(僅擷取關鍵部分):

 	460445:     find library=libhello.so.1 [0]; searching
    460445:      search path=/opt/hellolib_runtime/tls/haswell/x86_64:/opt/hellolib_runtime/tls/haswell:/opt/hellolib_runtime/tls/x86_64:/opt/hellolib_runtime/tls:/opt/hellolib_runtime/haswell/x86_64:/opt/hellolib_runtime/haswell:/opt/hellolib_runtime/x86_64:/opt/hellolib_runtime            (RPATH from file ./hello_rpath)
...
    460445:       trying file=/opt/hellolib_runtime/libhello.so.1
    460445:      search path=/opt/hellolib_runtime1/tls/haswell/x86_64:/opt/hellolib_runtime1/tls/haswell:/opt/hellolib_runtime1/tls/x86_64:/opt/hellolib_runtime1/tls:/opt/hellolib_runtime1/haswell/x86_64:/opt/hellolib_runtime1/haswell:/opt/hellolib_runtime1/x86_64:/opt/hellolib_runtime1:tls/haswell/x86_64:tls/haswell:tls/x86_64:tls:haswell/x86_64:haswell:x86_64:               (LD_LIBRARY_PATH)
...
    460445:       trying file=/opt/hellolib_runtime1/libhello.so.1
...
    460445:      search cache=/etc/ld.so.cache
    460445:      search path=/lib/x86_64-linux-gnu/tls/haswell/x86_64:/lib/x86_64-linux-gnu/tls/haswell:/lib/x86_64-linux-gnu/tls/x86_64:/lib/x86_64-linux-gnu/tls:/lib/x86_64-linux-gnu/haswell/x86_64:/lib/x86_64-linux-gnu/haswell:/lib/x86_64-linux-gnu/x86_64:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu/tls/haswell/x86_64:/usr/lib/x86_64-linux-gnu/tls/haswell:/usr/lib/x86_64-linux-gnu/tls/x86_64:/usr/lib/x86_64-linux-gnu/tls:/usr/lib/x86_64-linux-gnu/haswell/x86_64:/usr/lib/x86_64-linux-gnu/haswell:/usr/lib/x86_64-linux-gnu/x86_64:/usr/lib/x86_64-linux-gnu:/lib/tls/haswell/x86_64:/lib/tls/haswell:/lib/tls/x86_64:/lib/tls:/lib/haswell/x86_64:/lib/haswell:/lib/x86_64:/lib:/usr/lib/tls/haswell/x86_64:/usr/lib/tls/haswell:/usr/lib/tls/x86_64:/usr/lib/tls:/usr/lib/haswell/x86_64:/usr/lib/haswell:/usr/lib/x86_64:/usr/lib            (system search path)
...
    460445:       trying file=/lib/libhello.so.1

可以看出動態連結器依次搜尋了:RPATH指定的路徑,LD_LIBRARY_PATH記錄的路徑,ld.so.cache中記錄的路徑,預設路徑。

因此驗證了上述的路徑搜尋優先順序的順序。

附錄

庫檔案原始碼

//file: libhello.cpp
#include <iostream>
void hello()
{
    std::cout << "Hello from the 1.1.0 library!" << std::endl;
}

主程式原始碼

//file: main.cpp
extern void hello();
int main()
{
    hello();
    return 0;
}

makefile

# file: makefile
CXX = g++
CXXFLAGS = -fPIC
LDFLAGS = -shared
DEBUG_MODE ?= 0
 
ifeq ($(DEBUG_MODE),1)
    DEBUG_OPTS = -v -Wl,--verbose
endif
 
all: lib/libhello.so.1.1.0 obj/main.o
 
lib/libhello.so.1.1.0: libhello.cpp
	$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -Wl,-soname,libhello.so.1
 
obj/main.o: main.cpp
	$(CXX) -c -o $@ $^
 
# 僅使用-L
hello: obj/main.o
	mkdir -p /opt/hellolib_compile
	cp ./lib/libhello.so.1.1.0 /opt/hellolib_compile
	ln -sf /opt/hellolib_compile/libhello.so.1.1.0 /opt/hellolib_compile/libhello.so
	$(CXX) $(DEBUG_OPTS) -o $@ $^ -L/opt/hellolib_compile -lhello

hello_rpath: obj/main.o
	mkdir -p /opt/hellolib_compile
	cp ./lib/libhello.so.1.1.0 /opt/hellolib_compile
	ln -sf /opt/hellolib_compile/libhello.so.1.1.0 /opt/hellolib_compile/libhello.so
	mkdir -p /opt/hellolib_runtime
	cp ./lib/libhello.so.1.1.0 /opt/hellolib_runtime
	ln -sf /opt/hellolib_runtime/libhello.so.1.1.0 /opt/hellolib_runtime/libhello.so.1
	$(CXX) $(DEBUG_OPTS) -o $@ $^ -Wl,--disable-new-dtags,-rpath,/opt/hellolib_runtime -L/opt/hellolib_compile -lhello

hello_runpath: obj/main.o
	mkdir -p /opt/hellolib_compile
	cp ./lib/libhello.so.1.1.0 /opt/hellolib_compile
	ln -sf /opt/hellolib_compile/libhello.so.1.1.0 /opt/hellolib_compile/libhello.so
	mkdir -p /opt/hellolib_runtime
	cp ./lib/libhello.so.1.1.0 /opt/hellolib_runtime
	ln -sf /opt/hellolib_runtime/libhello.so.1.1.0 /opt/hellolib_runtime/libhello.so.1
	$(CXX) $(DEBUG_OPTS) -o $@ $^ -Wl,--enable-new-dtags,-rpath,/opt/hellolib_runtime -L/opt/hellolib_compile -lhello
 
clean:
	rm -f ./lib/* ./obj/* ./hello*
	rm -f /usr/lib/libhello*
	rm -rf /opt/hellolib_compile*
	rm -rf /opt/hellolib_runtime*

.PHONY: clean hello hello_rpath hello_runpath

指令碼

run_none

#!/bin/bash

make
make hello

LD_DEBUG=libs ./hello

make clean

run_default

#!/bin/bash

make
make hello

cp ./lib/libhello.so.1.1.0 /usr/lib
ln -sf /usr/lib/libhello.so.1.1.0 /usr/lib/libhello.so.1

LD_DEBUG=libs ./hello

rm -f /usr/lib/libhello.so*
make clean

run_ld_so_cache

#!/bin/bash

make
make hello

mkdir -p /opt/hellolib_runtime
cp ./lib/libhello.so.1.1.0 /opt/hellolib_runtime
ln -sf /opt/hellolib_runtime/libhello.so.1.1.0 /opt/hellolib_runtime/libhello.so.1

echo "/opt/hellolib_runtime" > /etc/ld.so.conf.d/libhello.conf
ldconfig

LD_DEBUG=libs ./hello

rm -rf /opt/hellolib_runtime*
rm -f /etc/ld.so.conf.d/libhello.conf
ldconfig
make clean

run_runpath

#!/bin/bash

make
make hello_runpath

LD_DEBUG=libs ./hello_runpath

make clean

run_ld_library_path

#!/bin/bash

make
make hello

mkdir -p /opt/hellolib_runtime
cp ./lib/libhello.so.1.1.0 /opt/hellolib_runtime
ln -sf /opt/hellolib_runtime/libhello.so.1.1.0 /opt/hellolib_runtime/libhello.so.1

export LD_LIBRARY_PATH=/opt/hellolib_runtime:$LD_LIBRARY_PATH

LD_DEBUG=libs ./hello

rm -rf /opt/hellolib_runtime*
make clean

run_rpath

#!/bin/bash

make
make hello_rpath

LD_DEBUG=libs ./hello_rpath

make clean

run_cmp_all

#!/bin/bash

make
make hello_rpath
rm -rf /opt/hellolib_runtime/*

mkdir -p /opt/hellolib_runtime1
export LD_LIBRARY_PATH=/opt/hellolib_runtime1:$LD_LIBRARY_PATH

mkdir -p /opt/hellolib_runtime2
echo "/opt/hellolib_runtime2" > /etc/ld.so.conf.d/libhello.conf
ldconfig

cp ./lib/libhello.so.1.1.0 /usr/lib
ln -sf /usr/lib/libhello.so.1.1.0 /usr/lib/libhello.so.1

LD_DEBUG=libs ./hello_rpath

rm -f /usr/lib/libhello.so*
rm -f /etc/ld.so.conf.d/libhello.conf
ldconfig
rm -rf /opt/hellolib_runtime*
make clean

相關文章