45

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

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

还有一个共享对象,与前一个链接:

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

现在,在创建链接到 的可执行文件时libb.so,我必须将 -rpath-link 指定为 ld 以便它liba.so在发现libb.so依赖于它时可以找到:

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

否则ld会抱怨。

liba.so为什么,链接时ld 必须能够定位testliba.so因为对我来说,除了确认' 的存在之外,似乎 ld 并没有做太多其他事情。例如,readelf --dynamic ./test只根据需要运行列表libb.so,所以我猜动态链接器必须自己发现libb.so -> liba.so依赖关系,并自己搜索liba.so.

我在 x86-64 GNU/Linux 平台上,而 main() 例程 intest调用一个函数,libb.so而该函数又调用liba.so.

4

4 回答 4

33

liba.so为什么,链接时ld 必须能够定位testliba.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走得更远。当您链接时testld还会检查是否在依赖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.solibb.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

虽然readelfliba.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.solibb.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 二级依赖关系解决与示例

于 2016-03-02T13:10:55.180 回答
11

我想您需要知道何时使用-rpath选项和-rpath-link选项。首先我引用man ld指定的内容:

  1. -rpath 和 -rpath-link 的区别在于 -rpath 选项指定的目录包含在可执行文件中并在运行时使用,而 -rpath-link 选项仅在链接时有效。只有使用 --with-sysroot 选项配置的本机链接器和交叉链接器才支持以这种方式搜索 -rpath。

您必须区分链接时和运行时。根据您接受的 anton_rh 的回答,在编译和链接共享库或静态库时未启用检查未定义符号,但在编译和链接可执行文件时启用。(但请注意,存在一些共享库和可执行文件,例如,ld.so. 类型man ld.so以探索这个,我不知道在编译这些文件时是否启用了检查未定义的符号“双“种)。

So-rpath-link用于链接时检查,并-rpath用于链接时和运行时,因为rpath嵌入到 ELF 标头中。但是您应该小心,如果同时指定了这两个选项,则该-rpath-link选项将在链接时覆盖选项。-rpath

但是,为什么-rpath-option-rpath选择?我认为它们用于消除“过度链接”。请参阅此通过示例更好地理解 Linux 二级依赖关系解决。,只需用于ctrl + F导航到与“超链接”相关的内容。你应该关注为什么“overlinking”不好,并且由于我们采取的避免“overlinking”的方法,ld选项-rpath-link的存在-rpath是合理的:我们在编译和链接的命令中故意省略了一些库以避免“overlinking”,并且由于省略,ld需要-rpath-link-rpath定位这些省略的库。

于 2018-03-04T10:45:12.490 回答
7

您的系统,通过ld.so.conf,,ld.so.conf.d和系统环境,LD_LIBRARY_PATH等等,提供了系统范围的pkg-config库搜索路径,当您针对标准库构建时,这些路径由已安装的库通过信息等补充。当库位于定义的搜索路径中时,将自动遵循标准库搜索路径,从而允许找到所有必需的库。

对于您自己创建的自定义共享库,没有标准的运行时库搜索路径。您-L/path/to/lib在编译和链接期间通过指定指定库的搜索路径。对于非标准位置的库,可以选择在编译时将库搜索路径放在可执行文件的标头(ELF 标头)中,以便您的可执行文件可以找到所需的库。

rpath提供了一种在 ELF 标头中嵌入自定义运行时库搜索路径的方法,以便您也可以找到自定义库,而无需在每次使用时指定搜索路径。这也适用于依赖库的库。正如您所发现的,不仅在命令行上指定库的顺序很重要,您还必须为您链接的每个依赖库提供运行时库搜索路径或 rpath 信息,以便标头包含运行所需的所有库的位置。

来自评论的附录

我的问题主要是为什么 ld 必须“自动尝试查找共享库”(liba.so)并“将其包含在链接中”。

这就是工作方式ld。来自man ld“-rpath 选项也用于定位明确包含在链接中的共享对象所需的共享对象......如果在链接 ELF 可执行文件时不使用 -rpath,环境变量“LD_RUN_PATH”的内容将是如果已定义,则使用它。” 在您的情况下liba,它不位于,LD_RUN_PATH因此ld需要liba在编译可执行文件期间使用rpath(如​​上所述)或通过提供显式搜索路径来定位它。

其次,“将其包含在链接中”的真正含义是什么。对我来说,它似乎只是意味着:“确认它的存在”(liba.so),因为 libb.so 的 ELF 标头没有被修改(它们已经有一个针对 liba.so 的 NEEDED 标签),并且 exec 的标头仅声明 libb。所以需要。为什么 ld 关心找到 liba.so,它不能只将任务留给运行时链接器吗?

不,回到ld. 为了产生“良好的链接”ld必须能够定位所有依赖库。ld否则无法确保良好的链接。运行时链接器必须查找和加载,而不仅仅是查找程序所需的共享库。除非它自己可以在链接程序时找到所有需要的共享库ld,否则不能保证会发生这种情况。ld

于 2014-07-07T01:39:38.037 回答
1

您实际上并没有告诉 ld (链接libbliba在哪里 liba- 只是它是一个依赖项。快速ldd libb.so会告诉你它找不到liba

由于这些库可能不在链接器搜索路径中,因此在链接可执行文件时会出现链接器错误。请记住,当您链接 liba 本身时,libb 中的函数仍然是未解析的,但ld在您链接最终可执行文件之前,它的默认行为是不关心 DSO 中未解析的符号。

于 2014-07-06T23:58:29.807 回答