The problem
Have you ever tried to distribute C/C++ applications to Linux users? If you haven’t, you might think it is as simple as grabbing the binary executable and distributing it to users. Unfortunately, distributing Linux binaries like this rarely works, here is why:
C/C++ applications on Linux are often linked with glibc
as their C runtime, which consists of a set of dynamic libraries such as
libc.so.6
, libpthread.so.0
and etc. The
original idea of dynamic libraries is to share code between
applications, which is as great as it sounds. However, it also creates
new problems: compatibility problems. The glibc
took an
unusual approach. Applications linked with an old version of
glibc
will continue to work with a newer version of
glibc
, which is good, however, applications linked with
newer glibc
refuse to work with old versions of
glibc
, which sucks. If a Linux binary works on your
computer, you can not copy it to another computer and expect it to
work!
Suboptimal solutions
Some people advise that you just build your applications on a very
old distro with a very old version of glibc
, which should
produce working binaries for most people. Some people also package their
applications for all major distros in a distro-specific format, such as
deb
for debians and rpm
for redhats. All of
this is madness, why should application distribution be distro-specific?
The Linux kernel maintains excellent compatibility for user space
applications, there must be a way to produce portable applications for
Linux.
Failed attempts
Since the glibc
are just a set of dynamics libraries.
You may be wondering why can’t we just bundle the glibc
with the application, like Windows applications bundling their VC
runtimes. Unfortunately, this does not work.
There are some extra steps when the Linux kernel runs a dynamically
linked executable. The kernel looks for a field in the ELF file, called
“ELF interpreter”, which is an absolute path to another ELF file, often
/lib64/ld-linux-x86-64.so.2
. The purpose of this thing is
to handle dynamic libraries, and it is the first thing that executes.
You can read more in this
article.
As you may have guessed, this “ELF interpreter” thing
(ld-linux-x86-64.so
) is also part of the libc, and
different versions are incompatible with each other. And it is an
absolute path embedded in the ELF binary. Thus, you can not bundle the
glibc
with your apps.
To create portable Linux binaries, dynamic linking can not be used.
So what about static linking? Most C/C++ libraries have no problem
linking statically unless it is glibc
. People who tried
static linking with glibc
often have horrible stories to
tell. Sometimes the statically linked program seems to work until the
name resolution doesn’t work, or other stuff like vdso
breaks.
The solution
Luckily, the static linking problem is only a glibc
problem. Another popular libc implementation, the musl
libc, has no problem
with static linking. musl
is the default libc used by the
Alpine Linux distro. To
produce a statically linked binary, simply build it on Alpine Linux, and
add the -static
flag to gcc
. Simple and
effective!
Alpine Linux is often used as the base image for containerized
applications. You can try it with docker:
docker pull alpine
.
Installing the C/C++ toolchain on Alpine:
apk update
apk upgrade
apk add coreutils gcc g++
Thanks to the musl
-based toolchain, the static linking
works out of the box, even for C++ applications with
libstdc++
dependency.
For third-party libraries, sometimes, Alpine Linux already has a
static library package with the name *-static
, like
xz-static
or sqlite-static
, which can be
grabbed with the apk add
command.