【发布时间】:2019-01-07 09:45:05
【问题描述】:
我想知道ldd 到底是做什么的。它是否只打印来自DT_NEEDED 结构的.dynamic 部分的库?据我所知,这不是解析ldd 输入库的所有未定义符号所需的库的完整列表。在这种情况下,ldd到底有什么用?
还是真的列出了ldd的输入库实际依赖的所有库?
这不是关于ldd是否显示依赖关系的问题-这是关于ldd报告的库是否解析ldd输入库的所有未定义符号的问题。
【问题讨论】:
我想知道ldd 到底是做什么的。它是否只打印来自DT_NEEDED 结构的.dynamic 部分的库?据我所知,这不是解析ldd 输入库的所有未定义符号所需的库的完整列表。在这种情况下,ldd到底有什么用?
还是真的列出了ldd的输入库实际依赖的所有库?
这不是关于ldd是否显示依赖关系的问题-这是关于ldd报告的库是否解析ldd输入库的所有未定义符号的问题。
【问题讨论】:
ldd是否仅打印来自DT_NEEDED结构的.dynamic部分的库?
不,readelf --dynamic 就是这样做的。
ldd到底有什么用?
ldd 显示运行时链接器ld.so 在启动可执行文件或加载共享库时加载哪些库。这是一个递归过程,例如可执行文件需要一个共享库 (DT_NEEDED),以便加载该库。然后它继续加载加载的库的依赖项(DT_NEEDED)等等。
您不一定需要ldd,您只需设置LD_DEBUG=all 环境变量即可使ld.so 打印该信息等等。请参阅man ld.so 了解更多信息。
每个加载的可执行文件或共享库都将其定义的导出动态符号公开为查找范围(哈希表)。 查找范围形成一个列表。解析未定义符号时,ld.so 遍历查找范围并找到第一个定义符号并解析符号引用的符号。如果ld.so 到达查找范围的末尾,它会将符号报告为未解析。
未解析的符号名称与它应该来自的可执行/共享库之间没有对应关系。 ld.so 以递归方式从 DT_NEEDED 部分加载所有共享库,构建查找范围列表,然后在其中查找未解析的符号。
【讨论】:
无法从共享对象本身派生解析共享对象中未定义符号所需的库列表。这样的列表可能存在也可能不存在。使用世界上任何现有库都无法解析的未定义符号库很容易创建。
# cat test.c
extern void foo99988776543quzzu();
void test() {
foo99988776543quzzu();
}
# gcc -fPIC -shared -o libtest.so test.c
在这里,我们创建了一个带有未定义符号的库,世界上没有其他库可以满足它——直到我们构建一个。
# cat foo.c
void foo99988776543quzzu() {}
# gcc -fPIC -shared -o libfoo.so foo.c
世界上没有任何魔法可以帮助ldd libtest.so 找到 libfoo.so。但是很容易从 libtest.so 和 libfoo.so 构建一个可加载、可运行的程序。
# cat main.c
extern void test();
int main() { test(); }
# gcc main.c -lfoo -ltest -L. -Wl,-rpath=.
# ./a.out
ldd 不会尝试生成解析未定义符号所需的不可能的库列表。它完全按照锡上所说的那样做:
ldd 打印命令行指定的每个程序或共享对象所需的共享对象(共享库)。
这里的“必需”一词并不意味着“需要解析未定义的符号”。如前所述,无法生成解析未定义符号所需的对象列表。相反,“必需”是指一组动态依赖项,也就是“DT_NEEDED 递归所需的共享对象”,详见 ldd(1) 和 ld.so(8)。
ldd 到底有什么用?
DT_NEEDED 部分包含 sonames。 ldd 递归地收集这些 soname,并使用 DT_RUNPATH、DT_RPATH、LD_LIBRARY_PATH、/etc/ld.so.conf 以及我们本周搜索的任何位置中的信息将它们映射到 文件路径。所以ldd 的输出包含共享对象的文件路径列表,当ldd 命令行上的共享对象被加载时,这些文件路径将被加载。这是一个例子:
#ldd ./test
linux-vdso.so.1 (0x00007fff0593f000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fe7e6776000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe7e6385000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fe7e5fe7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe7e6d01000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe7e5dcf000)
即使对于最不经意的观察者来说,这也是一大堆有用的信息。例如,我们看到test 似乎是一个用于 x86 Linux 的 64 位 C++ 程序,它使用相对较新版本的gcc 或兼容的编译器构建。我们还看到它没有第三方依赖。
另一方面,
# ldd /usr/bin/kdiff3
linux-vdso.so.1 (0x00007ffeeed79000)
libkparts.so.4 => /usr/lib/libkparts.so.4 (0x00007f801a14d000)
libkio.so.5 => /usr/lib/libkio.so.5 (0x00007f8019c9b000)
libkdeui.so.5 => /usr/lib/libkdeui.so.5 (0x00007f8019637000)
libkdecore.so.5 => /usr/lib/libkdecore.so.5 (0x00007f801916b000)
libQtCore.so.4 => /usr/lib/x86_64-linux-gnu/libQtCore.so.4 (0x00007f8018c79000)
libQtGui.so.4 => /usr/lib/x86_64-linux-gnu/libQtGui.so.4 (0x00007f8017f84000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f8017bfb000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f801785d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f801746c000)
libQtXml.so.4 => /usr/lib/x86_64-linux-gnu/libQtXml.so.4 (0x00007f8017226000)
libQtNetwork.so.4 => /usr/lib/x86_64-linux-gnu/libQtNetwork.so.4 (0x00007f8016ed1000)
libQtSvg.so.4 => /usr/lib/x86_64-linux-gnu/libQtSvg.so.4 (0x00007f8016c78000)
libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007f8016940000)
... many more lines ...
列出了很多依赖项。如果加载失败,我们应该可以使用列表找出原因。例如,任何写有=> not found 的行都会很有帮助。
【讨论】:
ldd 报告的库是否解析输入库的所有未定义引用?
没有。可以链接包含未定义引用的共享库 (这是司空见惯的)。所以它可能被链接包含未定义的引用 将不会被它的任何(递归)DSO 依赖项或确实存在的任何 DSO 或目标文件解析。
foo.c
#include <stdio.h>
extern void bar(void);
void foo(void)
{
puts(__func__);
bar();
}
制作共享库:
$ gcc -shared -o libfoo.so foo.c
ldd libfoo.so 递归列出libfoo.so 的 DSO 依赖关系:
$ ldd libfoo.so
linux-vdso.so.1 (0x00007ffc30bf5000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd19209b000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd19268e000)
它们都没有解析对bar的未定义引用。
【讨论】:
ldd 报告的共享库或程序的动态依赖关系?