为什么ld在链接可执行文件时需要-rpath-link,以便需要另一个?

时间:2014-07-06 16:40:57

标签: c gcc shared-libraries ld rpath

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

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

还有一个共享对象,它与前者相关联:

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

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

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

否则ld会抱怨。

为什么在链接liba.so时ld必须能够找到test?因为对我来说,似乎ld在确认liba.so存在方面做的事情并不多。例如,运行readelf --dynamic ./test仅根据需要列出libb.so,因此我想动态链接器必须自己发现libb.so -> liba.so依赖项,并使其自己搜索liba.so

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

4 个答案:

答案 0 :(得分:23)

  

为什么在链接liba.so时ld必须能够找到test?因为对我来说,似乎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中所有未解析的引用(在我们的案例中{{1} }}取决于libb.so,因此需要liba.so位于链接时。好吧,实际上 ld 在链接liba.so时已经完成了这项检查。为什么第二次进行此检查...也许 ld 的开发人员发现这种双重检查对于检测破坏的依赖关系非常有用,当您尝试将程序与可能在其中加载的过时库链接时已链接,但现在无法加载,因为它所依赖的库已更新(例如,libb.so后来被重新编写,并且某些功能已从中删除。)

<强> UPD

做了一些实验。似乎我的假设“实际上ld已经完成了这项检查,当它链接liba.so是错误的。

我们假设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。行为与使用 ar 制作静态库(libb.so)的行为相同,但 ar 也不会解析生成的库的符号。

但是当您尝试关联libb.a时:

test

你得到错误:

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

如果 ld 没有递归扫描所有共享库,则无法检测到此类错误。所以问题的答案似乎与我上面说的相同: ld 需要 -rpath-link 以确保以后可以加载链接的可执行文件通过动态加载。只是一个健全检查。

<强> UPD2

尽可能早地检查未解析的引用(链接libb.so: undefined reference to `liba_nonexistent_func' collect2: ld returned 1 exit status 时)是有意义的,但 ld 由于某些原因不会这样做。它可能允许为共享库创建循环依赖。

libb.so可以实现以下功能:

liba.c

因此int libb_func(int i); int liba_func(int i) { int (*func_ptr)(int) = libb_func; return i + (int)func_ptr; } 使用liba.solibb.so使用libb.so(最好不要做这样的事情)。这成功编译并运作:

liba.so

虽然 readelf 表示$ 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 不需要liba.so

libb.so

如果 ld 在链接共享库期间检查了未解析的符号,则无法链接$ 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]

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

liba.so

如果共享库($ readelf -d test | grep RPATH 0x0000000f (RPATH) Library rpath: [./] test)位于您当前的工作目录(liba.so),现在可以运行libb.so。如果您刚刚使用 -rpath-link ./ ELF中就没有这样的条目,您必须将共享库的路径添加到test文件或到/etc/ld.so.conf环境变量。

<强> UPD3

实际上可以在链接共享库期间检查未解析的符号,必须使用LD_LIBRARY_PATH选项来执行此操作:

--no-undefined

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

答案 1 :(得分:7)

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

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

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

来自评论的Addemdum

  

我的问题主要是为什么ld必须&#34;自动尝试找到   共享库&#34; (liba.so)和&#34;将其包含在链接&#34;。

这就是ld的工作方式。从man ld&#34;当找到链接中明确包含的共享对象所需的共享对象时,也会使用-rpath选项...如果在链接时未使用-rpath一个ELF可执行文件,环境变量的内容&#34; LD_RUN_PATH&#34;将被使用,如果它被定义。&#34;在您的情况下,liba并未位于LD_RUN_PATH中,因此ld需要在编译可执行文件时找到libarpath (如上所述)或通过提供显式搜索路径。

  

其次是&#34;将其包含在链接&#34;真正意思。对我来说似乎   它只是意味着:&#34;确认它的存在&#34; (liba.so&#39; s),因为   libb.so的ELF头文件未被修改(它们已经有一个NEEDED标签   对于liba.so),exec的头文件只声明libb.so为   需要的。为什么ld关心如何找到liba.so,它不仅可以离开   运行时链接器的任务?

不,回到ld的语义。为了生成&#34;良好的链接&#34; ld必须能够找到所有依赖库。 ld无法保证良好的联系。运行时链接程序必须查找并加载,而不仅仅是查找程序所需的共享库 ld无法保证会发生这种情况,除非ld本身可以找到所有需要的共享库,这是在链接程序时。

答案 2 :(得分:5)

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

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

您必须区分链接时和运行时。根据您接受的anton_rh的答案,在编译和链接共享库或静态库时未启用检查未定义的符号,但在编译和链接可执行文件时启用了ENABLED。 (但请注意,存在一些共享库和可执行文件的文件,例如ld.so。键入man ld.so来探索此问题,我不知道是否检查对于未定义的符号,在编译&#34; dual&#34;种类的这些文件时启用。

因此-rpath-link用于链接时检查,-rpath用于链接时和运行时,因为rpath嵌入到ELF头中。但是,如果指定了-rpath-link选项,-rpath选项将在链接时覆盖-rpath-option选项,请注意这两个选项。

但是,为什么选择-rpathctrl + F?我认为它们用于消除&#34; overlinking&#34;。看到此Better understanding Linux secondary dependencies solving with examples.,只需使用ld导航到与&#34; overlinking&#34;相关的内容。你应该关注为什么&#34; overlinking&#34;是不好的,因为我们采用的方法是避免&#34; overlinking&#34;,-rpath-link选项-rpathld的存在是合理的:我们故意省略一些库用于编译和链接的命令,以避免&#34; overlinking&#34;,并且由于省略-rpath-link需要-rpathFROM jenkins/jenkins:lts MAINTAINER icordoba@tripbru.com USER root RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/* RUN curl -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl RUN chmod +x /usr/local/bin/kubectl RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - RUN apt-get install -y nodejs RUN npm install -g jest-cli RUN curl -fsSL https://clis.ng.bluemix.net/install/linux | sh RUN bx plugin install container-registry -r Bluemix RUN bx plugin install container-service -r Bluemix RUN bx plugin install cloud-functions -r Bluemix 来找到这些省略的库。

答案 3 :(得分:1)

你实际上并没有告诉ld(将libbliba联系起来 liba) - 只是它是一个依赖项。快速ldd libb.so会向您显示找不到liba

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