liba.so
为什么,链接时ld 必须能够定位test
?liba.so
因为对我来说,除了确认' 的存在之外,似乎 ld 并没有做太多其他事情。例如,readelf --dynamic ./test
只根据需要运行列表libb.so
,所以我猜动态链接器必须自己发现libb.so -> liba.so
依赖关系,并自己搜索liba.so
.
好吧,如果我正确理解链接过程,ld实际上甚至不需要定位libb.so
. 它可以忽略所有未解析的引用,test
希望动态链接器libb.so
在运行时加载时能够解析它们。但是如果ld以这种方式进行,许多“未定义的引用”错误将不会在链接时检测到,而是会在尝试test
在运行时加载时发现。所以ld只是做额外的检查,以确保在依赖test
的共享库中可以真正找到所有本身找不到的符号。test
因此,如果test
程序有“未定义的引用”错误(某些变量或函数test
本身没有找到,也没有libb.so
),这在链接时变得很明显,而不仅仅是在运行时。因此,这种行为只是额外的健全性检查。
但ld走得更远。当您链接时test
,ld还会检查是否在依赖libb.so
的共享库中找到了所有未解析的引用libb.so
(在我们的例子中,libb.so
依赖于liba.so
,因此它需要liba.so
在链接时定位)。好吧,实际上ld在链接时已经完成了这项检查libb.so
。为什么它会第二次进行此检查...也许ld的开发人员发现,当您尝试将程序链接到可能在链接时加载的过时库时,这种双重检查对于检测损坏的依赖关系很有用,但现在它可以'不被加载,因为它所依赖的库已更新(例如,liba.so
后来对其进行了重新设计,并从中删除了一些功能)。
UPD
只是做了几个实验。看来我的假设“实际上 ld 在链接时已经完成了这个检查libb.so
”是错误的。
让我们假设liba.c
有以下内容:
int liba_func(int i)
{
return i + 1;
}
并libb.c
具有下一个:
int liba_func(int i);
int liba_nonexistent_func(int i);
int libb_func(int i)
{
return liba_func(i + 1) + liba_nonexistent_func(i + 2);
}
和test.c
#include <stdio.h>
int libb_func(int i);
int main(int argc, char *argv[])
{
fprintf(stdout, "%d\n", libb_func(argc));
return 0;
}
链接时libb.so
:
gcc -o libb.so -fPIC -shared libb.c liba.so
链接器不会生成任何liba_nonexistent_func
无法解决的错误消息,而是默默地生成损坏的共享库libb.so
。该行为与使用arlibb.a
创建静态库 ( )相同,但它也不能解析生成库的符号。
但是当您尝试链接时test
:
gcc -o test -Wl,-rpath-link=./ test.c libb.so
你得到错误:
libb.so: undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status
如果ld不递归扫描所有共享库,则无法检测到此类错误。所以看来这个问题的答案和我上面说的一样:ld需要-rpath-link以确保链接的可执行文件可以稍后通过动态加载来加载。只是一个健全的检查。
UPD2
尽早检查未解析的引用是有意义的(在链接时libb.so
),但由于某些原因ld不这样做。这可能是为了允许对共享库进行循环依赖。
liba.c
可以有以下实现:
int libb_func(int i);
int liba_func(int i)
{
int (*func_ptr)(int) = libb_func;
return i + (int)func_ptr;
}
所以liba.so
使用libb.so
和libb.so
使用liba.so
(最好不要做这样的事情)。这成功编译并工作:
$ gcc -o liba.so -fPIC -shared liba.c
$ gcc -o libb.so -fPIC -shared libb.c liba.so
$ gcc -o test test.c -Wl,-rpath=./ libb.so
$ ./test
-1217026998
虽然readelf说liba.so
不需要libb.so
:
$ readelf -d liba.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [libc.so.6]
$ readelf -d libb.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [liba.so]
0x00000001 (NEEDED) Shared library: [libc.so.6]
如果ld在链接共享库期间检查未解析的符号,则liba.so
无法链接 of。
请注意,我使用-rpath key 而不是-rpath-link。不同之处在于-rpath-link在链接时仅用于检查是否可以解析最终可执行文件中的所有符号,而-rpath实际上将您指定为参数的路径嵌入到 ELF 中:
$ readelf -d test | grep RPATH
0x0000000f (RPATH) Library rpath: [./]
因此,test
如果共享库 (liba.so
和libb.so
) 位于您当前的工作目录 ( ./
) 中,现在就可以运行了。如果您只是使用-rpath-link,则 ELF 中将没有这样的条目test
,您必须将共享库的路径添加到/etc/ld.so.conf
文件或LD_LIBRARY_PATH
环境变量中。
UPD3
实际上可以在链接共享库期间检查未解析的符号,--no-undefined
必须使用选项来执行此操作:
$ gcc -Wl,--no-undefined -o libb.so -fPIC -shared libb.c liba.so
/tmp/cc1D6uiS.o: In function `libb_func':
libb.c:(.text+0x2d): undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status
我还发现了一篇很好的文章,它阐明了链接依赖于其他共享库的共享库的许多方面:
更好地理解 Linux 二级依赖关系解决与示例。