I am trying to make sense of how output of ldd --version
and ldd -v a.out
I have the below simple program
#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;
}
I compile it with following command
g++ --std=c++17 test.cpp
I want to find out which glibc version this program is going to use when I run say memcpy
.
The output of ldd --version
on this system is:
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.
The output of ldd -v a.out
is
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
What I can not understand is that if ldd --version
says that I have GLIBC version 2.31 available then why did my executables ldd output say GLIBC_2.4
and GLIBC_2.2.5
for a.out.
What is the right way to understand this?
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) ?
Thanks
You should read this answer, and look at the output from readelf -V a.out
.
When a program is linked, it records the symbol version(s) used (current) at the time of the link.
Many of the symbols your program is using have not changed since e.g. GLIBC_2.2.5
, so ldd
says: you need at least version GLIBC_2.2.5
(for these symbols). Some of the symbols you use have changed in GLIBC-2.16
, GLIBC-2.17
and GLIBC-2.18
, so ldd
says that you need these as well.
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) ?
The recorded symbols (encoded into a.out
) will all be GLIBC_2.17
or older, and the program will run fine on a newer system, because GLIBC guarantees backward compatibility (programs built on an older system continue to run fine on newer ones).
But if you did the inverse -- built on a GLIBC-2.31
system and tried to run the program on a GLIBC-2.17
one, it may (or may not, depending on which symbols it actually uses) fail.
In the example you provided, the highest required version of GLIBC
is GLIBC_2.18
. Therefore, this particular a.out
will work fine on a GLIBC-2.18
or newer system, but will fail on GLIBC-2.17
or older one.
Update:
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.
Yes, the executable will pick up the version it was linked with, and so long as the ABI for a given function hasn't changed, it will be the most recent version.
The case of memcpy
in particular is a bit more complicated. On Fedora 35 x86_64
system (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
What you can see here is that the memcpy
ABI changed in GLIBC-2.14
, and it became a GNU indirect function. You can read the details at the link, but TL;DR is that the actual function called by your program will depend on processor capabilities. It could be any one of these:
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