ldd --version 和 ldd -r -v a.out 的输出不匹配

Mismatch between output of ldd --version and ldd -r -v a.out

我正在尝试了解 ldd --versionldd -v a.out

的输出方式

我有下面的简单程序

#include <iostream>
#include <string>
#include <cstring>

int main()
{
    std::cout << "Hello world" << std::endl;
    std::string a = "Test string";
    char b[15] = {};
    memcpy(b, a.c_str(), 15);
    std::cout << b << std::endl;
    return 0;
}

我用下面的命令编译它

g++ --std=c++17 test.cpp

我想知道当我 运行 说 memcpy 时这个程序将使用哪个 glibc 版本。

ldd --version 在这个系统上的输出是:

ldd --version
ldd (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

ldd -v a.out的输出是

ldd -v a.out 
    linux-vdso.so.1 (0x00007ffe7d3f3000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f050bb2f000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f050bb14000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f050b922000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f050b7d3000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f050bd3a000)

    Version information:
    ./a.out:
        libgcc_s.so.1 (GCC_3.0) => /lib/x86_64-linux-gnu/libgcc_s.so.1
        libc.so.6 (GLIBC_2.4) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
        libstdc++.so.6 (GLIBCXX_3.4.21) => /usr/lib/x86_64-linux-gnu/libstdc++.so.6
        libstdc++.so.6 (CXXABI_1.3) => /usr/lib/x86_64-linux-gnu/libstdc++.so.6
        libstdc++.so.6 (GLIBCXX_3.4) => /usr/lib/x86_64-linux-gnu/libstdc++.so.6
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6:
        libm.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libm.so.6
        ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
        libgcc_s.so.1 (GCC_4.2.0) => /lib/x86_64-linux-gnu/libgcc_s.so.1
        libgcc_s.so.1 (GCC_3.4) => /lib/x86_64-linux-gnu/libgcc_s.so.1
        libgcc_s.so.1 (GCC_3.3) => /lib/x86_64-linux-gnu/libgcc_s.so.1
        libgcc_s.so.1 (GCC_3.0) => /lib/x86_64-linux-gnu/libgcc_s.so.1
        libc.so.6 (GLIBC_2.14) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.6) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.4) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.18) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.16) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.3) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.3.4) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.17) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.3.2) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
    /lib/x86_64-linux-gnu/libgcc_s.so.1:
        libc.so.6 (GLIBC_2.14) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
    /lib/x86_64-linux-gnu/libc.so.6:
        ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
        ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2
    /lib/x86_64-linux-gnu/libm.so.6:
        ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2
        libc.so.6 (GLIBC_2.4) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_PRIVATE) => /lib/x86_64-linux-gnu/libc.so.6

我不明白的是,如果 ldd --version 说我有可用的 GLIBC 版本 2.31 那么为什么我的可执行文件 ldd 输出说 GLIBC_2.4GLIBC_2.2.5 for a.out.

正确的理解方式是什么?

如果我在具有旧版本 libc.so 的系统上编译了一个二进制文件(假设 GLIBC 的最高版本为 2.17),然后在具有libc.so 的新版本(假设 GLIBC 的最高版本为 2.31)?

谢谢

您应该阅读 this answer,并查看 readelf -V a.out 的输出。

当程序被 linked 时,它会记录 link 时使用的(当前)符号版本。

您的程序使用的许多符号未更改,例如GLIBC_2.2.5,所以 ldd 说:您至少需要 版本 GLIBC_2.2.5(对于这些符号)。您使用的某些符号 GLIBC-2.16GLIBC-2.17GLIBC-2.18 中更改,因此 ldd 表示您也需要这些.

What would happen if I have compiled a binary on a system that has old version of libc.so (suppose has highest version of GLIBC as 2.17) and then run the binary on a system with new version of libc.so (suppose has highest version of GLIBC as 2.31) ?

记录的符号(编码为 a.out)将全部为 GLIBC_2.17 或更早版本,程序将 运行 在较新的系统上正常运行,因为 GLIBC 保证向后兼容性(程序建立在旧系统上继续 运行 在新系统上没问题)。

但是如果你做相反的事情——在 GLIBC-2.31 系统上构建并试图在 GLIBC-2.17 系统上 运行 程序,它 可能 (也可能不会,取决于它实际使用的符号)失败。

在您提供的示例中,GLIBC 的最高要求版本是 GLIBC_2.18。因此,这个特定的 a.outGLIBC-2.18 或更新的系统上可以正常工作,但在 GLIBC-2.17 或更旧的系统上会失败。

更新:

What happens if an executable that is compiled on an old system which has highest version of GLIBC_2_17, is run on a system that has GLIBC_2_31 available? Will the executable pick the latest symbols (if they are ABI complaint) eg say memcpy - exec compiled on old system (with GLIBC without vector support) when run on new system (with GLIBC that has memcpy with vector support) will pick memcpy from new GLIBC that has vector support.

是的,可执行文件将选择它 link 编辑时使用的版本,只要给定函数的 ABI 没有改变,它就会是最新版本。

memcpy 的情况尤其复杂一些。在 Fedora 35 x86_64 系统上 (GLIBC-2.34):

nm /lib64/libc.so.6 | grep ' memcpy'
00000000000a1560 i memcpy
00000000000a1560 i memcpy@@GLIBC_2.14
00000000000b9c30 T memcpy@GLIBC_2.2.5

你在这里可以看到 memcpy ABI 在 GLIBC-2.14 中发生了变化,它变成了 GNU indirect function。您可以在 link 中阅读详细信息,但 TL;DR 是程序调用的 actual 函数将取决于处理器功能。它可以是以下任何一项:

00000000001870b0 t __memcpy_avx512_no_vzeroupper
0000000000189f00 t __memcpy_avx512_unaligned
0000000000189f70 t __memcpy_avx512_unaligned_erms
00000000001828f0 t __memcpy_avx_unaligned
0000000000182960 t __memcpy_avx_unaligned_erms
000000000018b0e0 t __memcpy_avx_unaligned_erms_rtm
000000000018b070 t __memcpy_avx_unaligned_rtm
00000000000b9ca0 t __memcpy_erms
00000000001920b0 t __memcpy_evex_unaligned
0000000000192120 t __memcpy_evex_unaligned_erms
00000000000b9c30 t __memcpy_sse2_unaligned
00000000000b9d10 t __memcpy_sse2_unaligned_erms
000000000015d400 t __memcpy_ssse3
0000000000162990 t __memcpy_ssse3_back