Minimal versions of runtime and Linux kernel on a target host


#1

I would like to understand the following rules/constraints. Could you please help?

  1. If a binary is compiled on Linux kernel version X and dynamic glibc version Y, what versions of kernel and runtime lib (relative to X and Y) are going to be required for the target host in order to run this binary?

  2. If a binary is compiled on Linux kernel version X and statically with musl version Y, what version of kernel (relative to X) is going to be required for the target host in order to run this binary? PS: I understand Y does not matter for target host in this case.

Thanks.


#2

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 SONAMEs 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.


#3

Thanks. It covers glibc perfectly.


#4

Musl is answered here https://wiki.musl-libc.org/supported-platforms.html

Rust is answered here https://forge.rust-lang.org/platform-support.html