为什么ld需要-rpath-link链接一个可执行文件,因此需要另一个可执行文件?

我只是好奇在这里我创建了一个共享对象:

gcc -o liba.so -fPIC -shared liba.c

还有一个共享对象,与前者相连:

gcc -o libb.so -fPIC -shared libb.c liba.so

现在,当创建一个链接到libb.so的可执行文件时,我将不得不指定-rpath-link到ld,所以当发现libb.so依赖于它时,它可以找到liba.so:

gcc -o test -Wl,-rpath-link,./ test.c libb.so

否则ld会抱怨。

为什么呢,在连接测试时,ld必须能够定位liba.so?因为对我而言,看起来似乎没有其他的东西比确认liba.so的存在。例如,运行readelf –dynamic ./test只会根据需要列出libb.so,所以我猜动态链接器必须发现libb.so – > liba.so依赖于自己,并使其自己的搜索liba.so.

我在一个x86-64 GNU / Linux平台上,而test(测试)中的main() – 例程调用libb.so中的一个函数,这个函数又调用了一个liba.so中的一个函数。

Why is it, that ld MUST be able to locate liba.so when linking test? Because to me it doesn’t seem like ld is doing much else than confirming liba.so‘s existence. For instance, running readelf --dynamic ./test only lists libb.so as needed, so I guess the dynamic linker must discover the libb.so -> liba.so dependency on its own, and make it’s own search for liba.so.

那么如果我正确地理解链接过程,ld实际上不需要定位libb.so。它可以忽略所有未解决的引用,希望动态链接器在运行时加载libb.so时可以解决它们。但是,如果ld以这种方式执行,则在链接时不会检测到许多“未定义的引用”错误,而是在尝试在运行时加载测试时会发现。所以ld只是进行额外的检查,测试本身没有发现的所有符号都可以在测试依赖的共享库中找到。因此,如果测试程序具有“未定义的引用”错误(某些变量或函数在测试本身中未找到,而且在libb.so中都不存在),则在链接时,这不仅在运行时变得明显。因此,这种行为只是一个额外的理智检查。

但是,更进一步。当链接测试时,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。行为与使用ar的静态库(libb.a)一样,它也不会解析生成的库的符号。

但是当你尝试链接测试时:

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]

如果在链接共享库期间检查未解析的符号,那么liba.so的链接是不可能的。

请注意,我使用-rpath键而不是-rpath-link。区别在于链接时使用-rpath-link仅用于检查最终可执行文件中的所有符号是否可以解析,而-rpath实际上将您指定的参数嵌入到ELF中:

$ readelf -d test | grep RPATH
 0x0000000f (RPATH)                      Library rpath: [./]

因此,如果共享库(liba.so和libb.so)位于当前工作目录(./),则现在可以运行测试。如果您刚刚使用-rpath-link,则测试ELF中将不会有这样的条目,您必须将共享库的路径添加到/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

另外我发现一篇很好的文章,阐明了链接依赖于其他共享库的共享库的许多方面:
Better understanding Linux secondary dependencies solving with examples

http://stackoverflow.com/questions/24598047/why-does-ld-need-rpath-link-when-linking-an-executable-against-a-so-that-needs

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:为什么ld需要-rpath-link链接一个可执行文件,因此需要另一个可执行文件?