Will a Linux executable compiled on one “flavor” of Linux run on a different one?

Will the executable of a small, extremely simple program, such as the one shown below, that is compiled on one flavor of Linux run on a different flavor? Or would it need to be recompiled?

Does machine architecture matter in a case such as this?

int main()
{
  return (99);
}

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

In short: If you’re taking a compiled binary from one host to another using the same (or a compatible) architecture, you may be perfectly fine taking it to another distribution. However as complexity of the code increases, the likelihood of being linked against a library that is not installed; installed in another location; or installed at a different version, increases. Taking for instance your code, for which ldd reports the following dependencies when compiled with gcc -o exit-test exit-test.c on a (Debian-derived) Ubuntu Linux host:

$ ldd exit-test
    linux-gate.so.1 =>  (0xb7748000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb757b000)
    /lib/ld-linux.so.2 (0x8005a000)

Obviously this binary won’t run if I kick it over to, say, a Mac (./exit-test: cannot execute binary file: Exec format error). Let’s try moving it to a RHEL box:

$ ./exit-test
-bash: ./exit-test: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory

Oh dear. Why might this be?

$ ls /lib/ld-l* # reference the `ldd` output above
ls: cannot access /lib/ld-l*: No such file or directory

Even for this use-case, forklifting it failed due to missing shared libraries.

However, if I compile it with gcc -static exit-test-static exit-test.c, porting it to the system without the libraries works just fine. At the expense, of course, of disk space:

$ ls -l ./exit-test{,-static}
-rwxr-xr-x  1 username  groupname    7312 Jan 29 14:18 ./exit-test
-rwxr-xr-x  1 username  groupname  728228 Jan 29 14:27 ./exit-test-static

Another viable solution would be to install the requisite libraries on the new host.

As with many things in the U&L universe, this is a cat with many skins, two of which are outlined above.

Method 2

It depends. Something compiled for IA-32 (Intel 32-bit) may run on amd64 as Linux on Intel retains backwards compatibility with 32-bit applications (with suitable software installed). Here’s your code compiled on RedHat 7.3 32-bit system (circa 2002, gcc version 2.96) and then the binary copied over to and run on a Centos 7.4 64-bit system (circa 2017):

-bash-4.2$ file code
code: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.2.5, not stripped
-bash-4.2$ ./code
-bash: ./code: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
-bash-4.2$ sudo yum -y install glibc.i686
...
-bash-4.2$ ./code ; echo $?
99

Ancient RedHat 7.3 to Centos 7.4 (essentially RedHat Enterprise Linux 7.4) is staying in the same “distribution” family, so will likely have better portability than going from some random “Linux from scratch” install from 2002 to some other random Linux distribution in 2018.

Something compiled for amd64 would not run on 32-bit only releases of Linux (old hardware does not know about new hardware). This is also true for new software compiled on modern systems intended to be run on ancient old things, as libraries and even system calls may not be backwards portable, so may require compilation tricks, or obtaining an old compiler and so forth, or possibly instead compiling on the old system. (This is a good reason to keep virtual machines of ancient old things around.)

Architecture does matter; amd64 (or IA-32) is vastly different from ARM or MIPS so the binary from one of those would not be expected to run on another. At the assembly level the main section of your code on IA-32 compiles via gcc -S code.c to

main:
    pushl %ebp
    movl %esp,%ebp
    movl $99,%eax
    popl %ebp
    ret

which an amd64 system can deal with (on a Linux system–OpenBSD by contrast on amd64 does not support 32-bit binaries; backwards compatibility with old archs does give attackers wiggle room, e.g. CVE-2014-8866 and friends). Meanwhile on a big-endian MIPS system main instead compiles to:

main:
        .frame  $fp,8,$31
        .mask   0x40000000,-4
        .fmask  0x00000000,0
        .set    noreorder
        .set    nomacro
        addiu   $sp,$sp,-8
        sw      $fp,4($sp)
        move    $fp,$sp
        li      $2,99
        move    $sp,$fp
        lw      $fp,4($sp)
        addiu   $sp,$sp,8
        j       $31
        nop

which an Intel processor will have no idea what to do with, and likewise for the Intel assembly on MIPS.

You could possibly use QEMU or some other emulator to run foreign code (perhaps very, very slowly).

However! Your code is very simple code, so will have fewer portability issues than anything else; programs typically make use of libraries that have changed over time (glibc, openssl, …); for those one may also need to install older versions of various libraries (RedHat for example typically puts “compat” somewhere in the package name for such)

compat-glibc.x86_64                     1:2.12-4.el7.centos

or possibly worry about ABI changes (Application Binary Interface) for way old things that use glibc, or more recently changes due to C++11 or other C++ releases. One could also compile static (greatly increasing the binary size on disk) to try to avoid library issues, though whether some old binary did this depends on whether the old Linux distribution was compiling most everything dynamic (RedHat: yes) or not. On the other hand, things like patchelf can rejigger dynamic (ELF, but probably not a.out format) binaries to use other libraries.

However! Being able to run a program is one thing, and actually doing something useful with it another. Old 32-bit Intel binaries may have security issues if they depend on a version of OpenSSL that has some horrible and not-backported security problem in it, or the program may not be able to negotiate at all with modern web servers (as the modern servers reject the old protocols and ciphers of the old program), or SSH protocol version 1 is no longer supported, or …

Method 3

Adding to the excellent @thrig and @DopeGhoti answers: Unix or Unix-like OSes, including Linux, were traditionally always designed and aligned more for the portability of source code than binaries.

If having nothing hardware specific or being a simple source as in your example, you may move it without any problem at all from between pretty much any version of Linux or architecture as source code as long as the destination servers have the C development packages installed, the necessary libraries, and the corresponding development libraries installed.

As far porting more advanced code from older versions of Linux distant in time, or more specific programs like kernel modules for different kernel versions, you might have to adapt and modify source code to account for deprecated libraries/APIs/ABIs.

Method 4

By default, you’ll almost certainly run into problems with external libraries. Some of the other answers go into more details about those problems, so I won’t duplicate their work.

You can, however, compile many programs – even non-trivial ones – to be portable between Linux systems. The key is toolkit called the Linux Standard Base. The LSB is designed for creating just these types of portable applications. Compile an application for LSB v5.0 and it will run on any other Linux environment (of the same architecture) that implements LSB v5.0. A few Linux distros are LSB compliant, and others include LSB toolkits/libraries as an installable package. If you build your application using the LSB tools (like the lsbcc wrapper for gcc) and link to the LSB version of libraries, you’ll create a portable application.

Method 5

Maybe.

Things that tend to break it include.

  1. Different architectures. Obviously totally different architectures won’t work (unless you have something like user mode qemu with binfmt_misc but that is hardly a normal configuration). x86 binaries may work on amd64 but only if the required 32-bit libraries are available.
  2. Library versions. If the soversion is wrong then it won’t find the library at all. If the soversion is the same but the binary is built against a newer version of the library than it’s running against then it may fail to load because of new symbols or new versions of symbols. In particular glibc is a heavy user of symbol versioning, so binaries built against a newer glibc are very likely to fail with an older glibc.

If you avoid using any fast-changing libraries, avoid changes of architecture and build on the oldest distro you want to target you have a good chance of making one binary work on many distros.

Method 6

In addition to some of the things mentioned previously, there have been some changes in executable file format. For the most part, linux uses ELF, but older versions used a.out or COFF.

The start of a wikihole:

https://en.wikipedia.org/wiki/Comparison_of_executable_file_formats

There might be a way to get older versions to run newer formats, but I’ve personally never looked into it.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x