为什么链接
test
时,ld必须能够找到
liba.so
?因为在我看来,ld除了确认
liba.so
的存在之外,并没有做其他很多事情。例如,运行
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的开发人员发现这个双重检查有用,可以检测到在尝试将程序链接到过时的库时出现的损坏的依赖项,该库可能在它被链接的时候可以加载,但现在由于它所依赖的库已更新(例如,从中删除了一些函数),因此无法加载。
更新
刚刚做了一些实验。似乎我的假设“实际上,在链接
libb.so
时,ld已经完成了这个检查”是错误的。
让我们假设
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
。这个行为与使用ar生成静态库 (libb.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以确保被动态加载的链接可执行文件可以稍后加载。只是一个健全性检查。
更新2
尽早检查未解决的引用可能是有意义的(在链接
libb.so
时),但由于某些原因,
ld并不这样做。这可能是为允许共享库之间进行循环依赖。
liba.c
可以采用以下实现:
int libb_func(int i);
int liba_func(int i)
{
int
因此,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
。
请注意,我使用了
-rpath 关键字而不是
-rpath-link。区别在于
-rpath-link 仅在链接时用于检查最终可执行文件中的所有符号是否能够被解析,而
-rpath 实际上将您指定的路径嵌入到 ELF 中:
$ readelf -d test | grep RPATH
0x0000000f (RPATH) Library rpath: [./]
如果共享库(liba.so
和libb.so
)位于您当前的工作目录(./
),则现在可以运行test
。 如果您只是使用-rpath-link,则在test
ELF中没有这样的条目,您必须将共享库路径添加到/etc/ld.so.conf
文件或LD_LIBRARY_PATH
环境变量中。
更新3
实际上可以在链接共享库时检查未解析的符号,必须使用--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次要依赖关系的解决方法
.html
替换最后一个斜杠),链接就应该会修复。 - Edward