前言
《共享库链接和加载时的路径搜索优先级》 中提到,共享库在运行期加载时的路径搜索优先级为:
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
目录说明
项目的目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| . ├── 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
文件,查看程序是否可以运行成功。
直接运行脚本即可完成上述操作:
输出:
1
| ./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
)指向它
②运行可执行文件。
直接运行脚本即可完成上述操作:
输出:
1
| 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
⑤运行可执行文件
直接运行脚本即可完成上述操作:
输出:
1
| 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
)指向它
③运行可执行文件
直接运行脚本即可完成上述操作:
输出:
1
| 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
④运行可执行文件
直接运行脚本即可完成上述操作:
1
| sh ./run_ld_library_path.sh
|
输出:
1
| 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
)指向它
③运行可执行文件
直接运行脚本即可完成上述操作:
输出:
1
| 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
)指向它
⑥运行可执行文件
直接运行脚本即可完成上述操作:
在脚本中,我设置了环境变量LD_DEBUG=libs
,启用了动态链接器的调试输出(针对库的加载过程),因此终端输出了以下信息(仅截取关键部分):
1 2 3 4 5 6 7 8 9 10 11 12
| 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中记录的路径,默认路径。
因此验证了上述的路径搜索优先级的顺序。
附录
库文件源码
1 2 3 4 5 6
| #include <iostream> void hello() { std::cout << "Hello from the 1.1.0 library!" << std::endl; }
|
主程序源码
1 2 3 4 5 6 7
| extern void hello(); int main() { hello(); return 0; }
|
makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 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 $@ $^
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
1 2 3 4 5 6 7 8
| #!/bin/bash
make make hello
LD_DEBUG=libs ./hello
make clean
|
run_default
1 2 3 4 5 6 7 8 9 10 11 12
| #!/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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #!/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
1 2 3 4 5 6 7 8
| #!/bin/bash
make make hello_runpath
LD_DEBUG=libs ./hello_runpath
make clean
|
run_ld_library_path
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #!/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
1 2 3 4 5 6 7 8
| #!/bin/bash
make make hello_rpath
LD_DEBUG=libs ./hello_rpath
make clean
|
run_cmp_all
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #!/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
|