ldd 报告的库是否解析输入库的所有未定义引用?

Do libraries reported by ldd resolve all undefined references of an input library?

我想知道 ldd 到底是做什么的。它是否仅打印来自 .dynamic 部分的 DT_NEEDED 结构的库?据我所知,这不是解析 ldd 输入库的所有未定义符号所需的库的完整列表。在这种情况下,ldd 有什么用呢?

或者它真的列出了 ldd 的输入库实际依赖的所有库吗?

这不是关于 ldd 是否显示依赖关系的问题 - 这是关于 ldd 报告的库是否解析 ldd 输入库的所有未定义符号的问题。

Does ldd print only libraries from DT_NEEDED structures of .dynamic section?

不,那是 readelf --dynamic 所做的。

what is the use of ldd at all?

ldd 显示运行时链接器 ld.so 在启动 executable 或加载共享库时加载的库。这是一个递归过程,例如executable 需要共享库 (DT_NEEDED),以便加载库。然后它继续加载加载库的依赖项(DT_NEEDED)等等。

您不一定需要 ldd,您只需设置 LD_DEBUG=all 环境变量即可使 ld.so 打印该信息等。有关详细信息,请参阅 man ld.so

每个加载的 executable 或共享库将其定义的导出动态符号公开为 查找范围 (散列 table)。 查找范围形成一个列表。解析未定义的符号时,ld.so 遍历查找范围并找到第一个定义符号并解析符号引用的范围。如果 ld.so 到达查找范围的末尾,它会将符号报告为未解析。

未解析的符号名称与它应该来自的 executable/shared 库之间没有对应关系。 ld.soDT_NEEDED 部分递归加载所有共享库,构建查找范围列表,然后在其中查找未解析的符号。

How To Write Shared Libraries by U. Drepper 对此进行了详细解释。

Do libraries reported by ldd resolve all undefined references of an input library?

没有。共享库可能链接包含未定义的引用 (这是司空见惯的)。所以它可能被链接包含未定义的引用 不会被它的任何(递归)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)
其中

None 解析了对 bar.

的未定义引用

无法从共享对象本身派生解析共享对象中未定义符号所需的库列表。这样的列表可能存在也可能不存在。使用世界上任何现有库都无法解析的未定义符号创建库很容易。

# 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 prints the shared objects (shared libraries) required by each program or shared object specified on the command line.

这里的"required"并不是"required to resolve undefined symbols"的意思。如前所述,无法生成解析未定义符号所需的对象列表。相反,"required" 表示动态依赖集 a.k.a。 "shared objects required by DT_NEEDED recursively",详见 ldd(1) 和 ld.so(8)。

what is the use of ldd at all?

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 的行都会非常有用。