The simple answer is anything >= X and/or Y should work.
You can get more specific using readelf
, at least for the library dependencies.
readelf -d yourfile
will have NEEDED
lines for the library SONAME
s that you require. For glibc this has been libc.so.6
for a long time now. If you go find that file, you'll see it's actually a symlink to a more specific version like libc-2.26.so
. The NEEDED
lines will matter more for other libraries that don't keep such a long-term stability, like libgit2.so.24
.
readelf -V yourfile
will tell you the actual symbol versions required. So if the highest you see GLIBC_2.14
, for example, then you'll need at least 2.14 to run it. Not all symbols will have version tags, especially from libraries that don't maintain a long term SONAME
, then they probably don't version symbols either.
It may be that the highest GLIBC_X.Y symbol used is actually less than the version of glibc you compiled against, which means you could indeed run against that earlier version. But if you need to run on that earlier version, it's best to just compile against that baseline so you're sure not to pick up newer symbols.
readelf -s yourfile
will show the symbol table, including versions, so you might see something like memcpy@GLIBC_2.14
. This can help you figure out which new symbols you're actually using.
aside on memcpy
memcpy
in particular is kind of interesting. My libc.so
has two, memcpy@GLIBC_2.14
and memcpy@GLIBC_2.2.5
. The difference is that in 2.14 they started enforcing that the source and destination must not overlap. But they treated this as an ABI change, and the old symbol still has the old behavior that was basically no different than memmove
. So your binaries that were linked to the old version won't be affected by the new behavior.
All this is to say how libraries are versioned. I'm actually not sure if there's a good way to tell what kernel version is really required. It usually has to do with what syscalls are made, but even if you trace that, it's still possible for a program to deal with ENOSYS
on an older system and try a different path. Even Rust itself does this in a few places, like using pipe2
so it can apply the O_CLOEXEC
flag, but falling back to plain pipe
as needed.