Linux is not well known for its binary portability. Libraries vary from system to system, and the kernel interfaces have a tendency to change. However, it clearly is possible to ship a binary that runs on multiple systems. Notably, Adobe ships a binary Flash player for Linux that seems to successfully run on a wide variety of systems. Recently, I needed to build a binary on one system, and run it on another. It only used standard C library functions, so I expected it to be easy. It was not. The most reliable approach is to compile your code on an old system (or a chroot environment based on an old version of Linux), since Linux is typically fairly backwards compatible. However, here are some issues I've run into and ways to avoid them.
Binaries compiled on a newer system can crash with a floating point exception (SIGFPE) on an older system. It turns out that the linker in newer versions of Fedora emit a new .gnu.hash
section, instead of the old .hash
section. Older Linux systems won't understand how to link the binary, which causes this exception. Pass GCC the flag: -Wl,--hash-style=both
to solve the problem.
If you have a C++ binary, it depends on a version of the C++ standard library that may not exist on other systems. It takes some work, but it is possible to statically link libstdc++ to avoid this issue.
The C library on Linux uses symbol versioning to preserve backwards compatibility. However, if you build a binary on a newer system, you might end up with dependencies on the new version of glibc. To determine which versions your binary requires, run readelf -V [binary]
. You'll get output that looks like this:
Version needs section '.gnu.version_r' contains 5 entries: Addr: 0x000000000804901c Offset: 0x00101c Link to section: 7 (.dynstr) 000000: Version: 1 File: libpthread.so.0 Cnt: 1 0x0010: Name: GLIBC_2.0 Flags: none Version: 11 0x0020: Version: 1 File: librt.so.1 Cnt: 1 0x0030: Name: GLIBC_2.2 Flags: none Version: 8 0x0040: Version: 1 File: ld-linux.so.2 Cnt: 1 0x0050: Name: GLIBC_2.3 Flags: none Version: 6 0x0060: Version: 1 File: libgcc_s.so.1 Cnt: 3 0x0070: Name: GCC_4.2.0 Flags: none Version: 9 0x0080: Name: GCC_3.3 Flags: none Version: 5 0x0090: Name: GCC_3.0 Flags: none Version: 3 0x00a0: Version: 1 File: libc.so.6 Cnt: 4 0x00b0: Name: GLIBC_2.4 Flags: none Version: 10 0x00c0: Name: GLIBC_2.2 Flags: none Version: 7 0x00d0: Name: GLIBC_2.3.2 Flags: none Version: 4 0x00e0: Name: GLIBC_2.0 Flags: none Version: 2
If there is a version you don't expect, you can figure out what symbols require it with nm [binary] | grep [name]
. Example output:
U __stack_chk_fail@@GLIBC_2.4
This is a function needed by GCC's stack protector functionality. It crashes the program and prints an error when a stack attack is detected. It does not exist on older versions of glibc. This is enabled by default on Fedora and Ubuntu, so it needs to be explicitly disabled by compiling with -fnostack-protector
in order to avoid "missing symbol" errors.