【问题标题】:Do libraries reported by ldd resolve all undefined references of an input library?ldd 报告的库是否解析输入库的所有未定义引用?
【发布时间】:2019-01-07 09:45:05
【问题描述】:

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

还是真的列出了ldd的输入库实际依赖的所有库?

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

【问题讨论】:

    标签: linux ld ldd


    【解决方案1】:

    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 部分加载所有共享库,构建查找范围列表,然后在其中查找未解析的符号。

    How To Write Shared Libraries by U. Drepper 详细解释了这一点。

    【讨论】:

    • 您描述的递归过程似乎不正确恕我直言 - 该库可能没有 DT_NEEDED 结构,但仍有许多未解析的符号。这就是为什么我要问这个问题并想要更多关于图书馆 ldd 找到什么以及它如何找到它们的详细信息。如果你确定你的答案,请纠正我。
    • @Alexey 你没有问它如何解析符号。你一直在问这些链接器,未解决的符号问题。现在你应该已经阅读了akkadia.org/drepper/dsohowto.pdf,它详细回答了你所有的问题。
    • 好吧,我写了“我想知道 ldd 到底是做什么的。”然而,主要问题是 ldd 报告的库是否解析了所有未定义的符号。
    • @Alexey 简短的回答是“有时”。阅读那篇论文。
    • 是的,我必须阅读该文件。但也许你现在可以更正你的答案,简要解释为什么“不”,然后我接受它。还是谢谢你!
    【解决方案2】:

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

    # 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 的行都会很有帮助。

    【讨论】:

    • 假设 ./test 是一个共享库。那我们还能看到它没有第三方依赖吗?
    • @Alexey 它具有未解析的符号但没有依赖关系,因为任何库或可执行文件都可以提供要解析的符号。
    • 依赖意味着有一个未定义的引用。如果存在未定义的引用,则存在依赖关系。对吗?
    • @Alexey 这两个断言都不正确。依赖项是共享对象名称。未解析的引用是符号名称。它们是完全不同的东西,任何一个都可以独立存在。
    【解决方案3】:

    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的未定义引用。

    【讨论】:

    • 现在假设我们要创建一个使用 main.c 中的函数 foo 的应用程序:gcc main.c -lfoo -llibc -o main.exe。我们将在 libfoo.so 的源 foo.c 中获得对函数 bar 的未解析引用,对吗?我们已经添加了 ldd libfoo.so 报告的所有库,但我们仍然在 libfoo.so 中有未解析的引用。所以我的问题是 ldd 及其输出有什么用?
    • @Alexey ldd 的目的是向您显示共享库或动态链接程序的递归链接中每个共享库的名称,以及运行时加载程序的实际文件(如果有)的名称映射该名称。您可能想问一个不同的问题:为什么我想知道ldd 报告的共享库或程序的动态依赖关系?
    • 我的困惑是因为 ldd 似乎与未定义的引用无关(或一点)。哪些编程任务需要 ldd?
    • @Alexey 这确实是一个不同的问题,如果您发布它,我和毫无疑问其他人可以回答。在 SO 上一次一个问题。
    • 这里是stackoverflow.com/questions/54092584/what-is-the-purpose-of-ldd 但并不是每个人都同意这应该是一个不同的帖子,因为我在这里问过“ldd 到底有什么用”。
    猜你喜欢
    • 2023-03-11
    • 2011-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-31
    • 1970-01-01
    • 2011-02-08
    相关资源
    最近更新 更多