编译期链接时共享库路径搜索优先级实验
前言
《共享库链接和加载时的路径搜索优先级》 中提到,使用g++时,共享库在编译期链接时的库路径搜索优先级为:-L指定的路径
>LIBRARY_PATH记录的路径
>默认路径
。
本实验分三步验证上述结论
①单独测试每种方法指定的路径的可行性
②对比测试三种方法间的优先级
③使用DEBUG模式,查看链接器输出的详细信息,二次验证上述结论
值得注意的是,我看网上都说LIBRARY_PATH指定的路径
优先级要大于默认路径
的优先级,但是就我的测试结果来看,结论是相反的(可能是我使用了g++而不是直接使用底层的ld?)。
实验环境
操作系统:Ubuntu 20.04
编译器:g++-11.4.0
make:GNU Make 4.2.1
目录说明
项目的目录结构如下:
1 | . |
其中:
lib
为存放共享库的文件夹obj
为存放可重定位目标文件的文件夹libhello.cpp
和libhello_alt.cpp
为共享库源码(用于模拟不同版本的共享库),他们中都只有一个hello函数,两个hello函数的函数签名完全相同。其中libhello.cpp
将被编译为libhello.so.1.1.0
(soname为libhello.so.1
),libhello_alt.cpp
将被编译为libhello.so.2.1.0
(soname为libhello.so.2
)mian.cpp
为主函数,其中调用了hello函数makefile
为自动化构建脚本
在附录中,我将提供本次实验涉及到的代码。
准备工作
在终端中进入项目路径,并输入make
,会在./lib
下生成libhello.so.1.1.0
与libhello.so.2.1.0
,在./obj
下生成main.o
。
生成后项目的目录结构如下:
1 | . |
单独测试
不配置路径
不做任何路径的配置并且不在默认路径下放置libhello.so
文件,查看是否可以将main.o
和hello
的共享库文件链接成功。
直接使用makefile中预设好的命令即可完成上述操作:
1 | make main_none |
输出:
可以看到由于我们没有配置任何额外的搜索路径,并且没有在默认搜索路径下放置libhello.so
文件,链接器就找不到相应的共享库文件,就会链接失败。
单次实验结束后,使用make clean
命令清除本次实验生成的文件,然后再次使用make
命令重新生成共享库文件和可重定位目标文件。(每次做完一个小实验,都要重复此步骤,后不赘述)
默认路径
将libhello.so.1.1.0
拷贝至默认搜索路径/usr/lib
,并在/usr/lib
下创建一个软链接(libhello.so
)指向它,然后进行链接操作,查看是否可以将main.o
和hello
的共享库文件链接成功。
直接使用makefile中预设好的命令即可完成上述操作:
1 | make main_default |
输出:
没有报错。
然后使用readelf -d
查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。
LIBRARY_PATH
创建路径/opt/hellolib
,将libhello.so.1.1.0
拷贝至/opt/hellolib
,并在/opt/hellolib
下创建一个软链接(libhello.so
)指向它。然后将/opt/hellolib
添加至LIBRARY_PATH
并进行main.o
和hello
的共享库文件的链接操作,查看是否可以链接成功。
直接使用makefile中预设好的命令即可完成上述操作:
1 | make main_library_path |
输出:
没有报错。
然后使用readelf -d
查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。
-L
创建路径/opt/hellolib
,将libhello.so.1.1.0
拷贝至/opt/hellolib
,并在/opt/hellolib
下创建一个软链接(libhello.so
)指向它,然后添加链接选项-L/opt/hellolib
并进行链接操作,查看是否可以将main.o
和hello
的共享库文件链接成功。
直接使用makefile中预设好的命令即可完成上述操作:
1 | make main_l |
输出:
没有报错。
然后使用readelf -d
查看可执行文件的动态段信息,可见链接成功,共享库的soname已经被写入到可执行文件的动态段信息中了。
优先级测试
默认路径和LIBRARY_PATH
- ①将
libhello.so.2.1.0
拷贝至默认搜索路径/usr/lib
,并在/usr/lib
下创建一个软链接(libhello.so
)指向它。 - ②创建路径
/opt/hellolib
,将libhello.so.1.1.0
拷贝至/opt/hellolib
,并在/opt/hellolib
下创建一个软链接(libhello.so
)指向它。 - ③将
/opt/hellolib
添加至LIBRARY_PATH
并进行main.o
和hello
的共享库文件的链接操作。
直接使用makefile中预设好的命令即可完成上述操作:
1 | make cmp_default_libpath |
输出:
然后使用readelf -d
查看可执行文件的动态段信息,可见链接成功,并且链接的是默认路径下的共享库文件libhello.so.2.1.0
(其soname为libhello.so.2
)。因此可以得出结论:默认路径
搜索优先级要高于LIBRARY_PATH指定的路径
的搜索优先级。
对于上述结论,将会在后文的DEBUG模式中给出更详细的验证。
-L和默认路径
- ①创建路径
/opt/hellolib
,将libhello.so.2.1.0
拷贝至/opt/hellolib
,并在/opt/hellolib
下创建一个软链接(libhello.so
)指向它。 - ②将
libhello.so.1.1.0
拷贝至默认搜索路径/usr/lib
,并在/usr/lib
下创建一个软链接(libhello.so
)指向它。 - ③添加链接选项
-L/opt/hellolib
并进行main.o
和hello
的共享库文件的链接操作。
直接使用makefile中预设好的命令即可完成上述操作:
1 | make cmp_l_default |
输出:
然后使用readelf -d
查看可执行文件的动态段信息,可见链接成功,并且链接的是-L指定路径下的共享库文件libhello.so.2.1.0
(其soname为libhello.so.2
)。因此可以得出结论:-L指定路径
搜索优先级要高于默认搜索路径
的搜索优先级。
对于上述结论,将会在后文的DEBUG模式中给出更详细的验证。
DEBUG模式
在makefile中我添加了一个用于对比三种路径优先级的目标cmp_all
,其中
- -L指定路径为
/opt/hellolib_L
- 默认路径为
/usr/lib
- LIBRARY_PATH指定路径为
/opt/hellolib
,.so
文件(libhello.so.1.1.0
的软链接)仅放置于此路径下。
此外我还预设了一个DEBUG模式,开启DEBUG模式可以查看编译过程的详细信息,开启的方法就是在命令后面添加DEBUG_MODE=1
,例如:
1 | make cmp_all DEBUG_MODE=1 |
下面我们就使用DEBUG模式运行cmp_all
查看其输出(输出信息很多,我截取关键部分讲解):
编译器配置详细信息
我们先看一下gcc在编译过程中输出的编译器配置详细信息:
图片中的文字内容如下:
1 | LIBRARY_PATH= |
我们可以发现编译器列出了系统环境变量LIBRARY_PATH的内容,包含:
- ①我们向环境变量添加的
/opt/hellolib/
,其所处位置应该是由编译器规定的 - ②系统默认的库路径(
/usr/lib
和/lib
),位于最后 - ③根据编译器配置自动添加的路径,如
/usr/lib/gcc/x86_64-linux-gnu/11/
等
然后再往下看,COLLECT_GCC_OPTIONS
列出了传递给g++的一些选项:
图片中的文字内容如下(省略了一部分不需要关注的):
1 | COLLECT_GCC_OPTIONS=... |
可以发现:
- ①我们通过
-L
显式添加的路径/opt/hellolib_L
被排在了最前面 - ②
LIBRARY_PATH
中的路径(除了/usr/lib/
和/lib/
,原因暂时未知),都被加上-L
并传给了COLLECT_GCC_OPTIONS
,并排在/opt/hellolib_L
之后。
链接器详细信息
然后我们再看链接器输出的详细信息:
图片中的文字内容如下:
1 | SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); |
SEARCH_DIR
指令是用来指定链接器在搜索动态和静态库文件时应当考虑的目录,这些路径通常包括系统的标准库目录,如/usr/lib
和/lib
等。但是注意,通过-L
指定的路径会在运行时临时添加到SEARCH_DIR
列表的前面,即-L
指定的路径搜索优先级更高。
DEBUG总结
至此,我们可以简单总结一下上述信息:
- 我们设置的LIBRARY_PATH的值会传给编译器
- 编译器根据自己的配置以及我们手动赋予的LIBRARY_PATH变量的值,生成一个新的LIBRARY_PATH(我们手动赋予的LIBRARY_PATH变量的值处于一个特定的位置),并将这个新的LIBRARY_PATH的值(除了
/usr/lib
和/lib
)加上-L
传递给编译器 - 我们显式使用
-L
指定的路径也被传递给编译器,并位于所有-L
选项的最前面
而且对于编译器配置的路径,如/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/
,其本质就是/usr/lib/
(这也是默认路径
优先级大于LIBRARY_PATH指定路径
优先级的原因)。
因此对于-L指定路径
,LIBRARY_PATH指定路径
和默认路径
,最终都被转化为-L
的形式传递给编译器,且他们排列优先级为:
-L指定路径
>默认路径
>LIBRARY_PATH指定路径
因此他们的搜索优先级也是符合上述排列。
验证
最后我们可以通过链接器在链接特定库(比如我们的libhello
)时的搜索过程验证上述结论:
可见链接器先是搜索我们使用-L
指定的路径/opt/hellolib_L
,然后搜索编译器配置的路径/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/
(其本质就是默认路径/usr/lib/
),最后搜索LIBRARY_PATH指定的路径/opt/hellolib
。证明了编译过程中链接时库搜索路径的优先级为
-L指定路径>默认路径>LIBRARY_PATH指定路径
默认路径>LIBRARY_PATH原因
如上文所述,g++根据自己的配置将例如:
1 | /usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/ |
的路径添加到了LIBRARY_PATH中,而且位于用户设置的LIBRARY_PATH之前。这个路径的本质就是/usr/lib/
。这就导致最终出现默认路径搜索优先级大于LIBRARY_PATH指定路径的搜索优先级的现象。
至于手动使用ld
去链接.o
和.so
文件,后面有机会再做测试。
小结
死记优先级没必要,因为实际情况中还会遇到很多其他的规则。知道如何去看链接器的链接过程(如路径搜索顺序)才是排查问题的关键。
附录
库文件源码
1 | //file: libhello.cpp |
1 | //file: libhello_alt.cpp |
主程序源码
1 | //file: main.cpp |
makefile
1 | # file: makefile |
- 标题: 编译期链接时共享库路径搜索优先级实验
- 作者: paw5zx
- 创建于 : 2024-07-29 17:59:22
- 更新于 : 2024-08-20 16:34:35
- 链接: https://paw5zx.github.io/cpp-shared-lib-search-priority-test-linking/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。