我只是好奇。我创建了一个共享对象:
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
中的函数
答案 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.so
而libb.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.conf
,ld.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
需要在编译可执行文件时找到liba
,rpath
(如上所述)或通过提供显式搜索路径。
其次是&#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
指定的内容:
- -rpath和-rpath-link之间的区别在于-rpath指定的目录 选项包含在可执行文件中并在运行时使用,而 -rpath-link选项仅在链接时有效。搜索 -rpath只有本机链接器和使用--with-sysroot选项配置的交叉连接器支持。
醇>
您必须区分链接时和运行时。根据您接受的anton_rh的答案,在编译和链接共享库或静态库时未启用检查未定义的符号,但在编译和链接可执行文件时启用了ENABLED。 (但请注意,存在一些共享库和可执行文件的文件,例如ld.so
。键入man ld.so
来探索此问题,我不知道是否检查对于未定义的符号,在编译&#34; dual&#34;种类的这些文件时启用。
因此-rpath-link
用于链接时检查,-rpath
用于链接时和运行时,因为rpath
嵌入到ELF头中。但是,如果指定了-rpath-link
选项,-rpath
选项将在链接时覆盖-rpath-option
选项,请注意这两个选项。
但是,为什么选择-rpath
和ctrl + F
?我认为它们用于消除&#34; overlinking&#34;。看到此Better understanding Linux secondary dependencies solving with examples.,只需使用ld
导航到与&#34; overlinking&#34;相关的内容。你应该关注为什么&#34; overlinking&#34;是不好的,因为我们采用的方法是避免&#34; overlinking&#34;,-rpath-link
选项-rpath
和ld
的存在是合理的:我们故意省略一些库用于编译和链接的命令,以避免&#34; overlinking&#34;,并且由于省略-rpath-link
需要-rpath
或FROM 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(将libb
与liba
联系起来 liba
) - 只是它是一个依赖项。快速ldd libb.so
会向您显示找不到liba
。
由于这些库可能不在链接器搜索路径中,因此链接可执行文件时会出现链接器错误。请记住,当您链接liba本身时,libb中的函数仍然尚未解析,但ld
的默认行为不关心DSO中未解析的符号,直到您链接为止最终的可执行文件。