Portable Linux Binaries

about | archive

[ 2008-February-13 20:07 ]

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.

Floating Point Exception (SIGFPE)

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.

Statically Link libstdc++

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.

Wrong glibc versions

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

Useful References