Linux hasst static binaries

Ich staune immer wieder darüber, wie etwas so “einfaches” wie statisch Abhängigkeiten unter Linux so schwer bzw. unmöglich gemacht werden.

Denn eigentlich sind Programme mit dem Compiler Flag -static etwas ganz Tolles … denn sie laufen unter jedem Linux-System (naja, zumindest unter vielen davon) ohne dass man Bibliotheken nachinstallieren muss.

… doch dann fangen die Probleme an.


Es geht wieder mal um die alte Geschichte:

Ich hätte gerne (so wie unter Windows) ein Programm, dass ich einfach auf ein Zielsystem kopiere und es dort ohne weitere Schritte läuft.

In Windows kompiliert man eine solche EXE gegen die statische Version der C-Runtime und achtet darauf, andere Nicht-OS-Libs ebenso statisch zu linken.

Am Ende hat man eine etwas größere, dafür aber unabhängige EXE, die in jeder Windows-Variante läuft.

In der Linux GCC-Welt gibt es ein solches Compiler-Feature auch, und es heißt -static. Mit diesem Flag werden alle sonst dynamisch gelinkten Bibliotheken wie pthread, dl oder libc statisch in das Binary eingebunden.

Theoretisch kann man ein solches auf Ubuntu erstelltest Binary nehmen und auf OpenSUSE oder Alpine ausführen. Und bei einfachen Hello-World Apps funktioniert das auch wunderbar.

Problem “dl”: Dynamic Linker

dlopen und dlsym sind in Linux kein Betriebssystem-Feature wie es LoadLibrary und GetProcAddress unter Windows sind, sondern werden von der Bibliothek dl nachgeliefert.
Zusätzlich ist der Startup-Code der C-Bibliothek darauf ausgerichtet, Datenblöcke beim Start durchzulaufen, die der Compiler/Linker eingefügt hat, um dynamische Bibliotheken zu öffnen (open) und in den Prozessraum zu laden (mmap).

Mit -static wird diese Prozedur umgangen und der Compiler sucht von allen notwendigen Bibliotheken die statische Variante und fügt sie in die finale Binärdatei ein.

In einem solchen Binary sind “dynamische” Features nicht mehr vorhanden und auch dlopen wird zu einer Funktion, die nichts mehr tun kann und mit einer “not-supported” Meldung abbricht.

Bislang ist es mir nicht gelungen eine allgemeine Verwendung von dlopen() mit -static zu finden, die auf allen Plattformen lauffähig wäre.

Meine Debian-Derivate melden dann durch eine Warnung, dass man jetzt nur noch jene Bibliotheken dynamisch laden kann, die mit der gleichen Version der glibc erstellt wurden.
Und Alpine Linux liefert den erwähnten Not-Supported Errorcode.

Nicht -static aber -BStatic

Alternativ kann man ein Binary auch -dynamic kompilieren (der Default-Fall und das Gegenteil von -static) und dann aber beim Linken auf statische Versionen der notwendigen Bibliotheken verweisen.

In CMake ging das mit Linker-Flags wie:

  • -Wl,-Bstatic -lc
    linkt die C-Runtime statisch
  • -Wl,-Bstatic -lpthread
    linkt pthread statisch
  • -Wl,-Bstatic -ldl
    linkt dl statisch

Hier steckt der Teufel wieder im Detail, was man wo darf und was wo nicht.

Am Ende fand ich aber auch hier keine Variante, die “portable” Binaries für mehrere Linux-Varianten ermöglichte.

Fazit

Ich bleibe also bei “dynamischen” Binaries, weil das GATE Framework eben von anderen externen Bibliotheken abhängig ist.
Ginge es ausschließlich um C-Runtime Funktionen wäre vielleicht ein Möglichkeit offen reine statische und portable Binaries zu erzeugen, doch die Kombination mit dl* Funktionen schließt diese Option (aus meiner aktuellen Sicht) aus.

Gelernt habe ich allerdings, dass die glibc einigermaßen kompatibel geblieben ist und so kann ich ein Binary, das mit GCC5 unter Ubuntu 16.04 gebaut wurde, auch in den Folgeversion (Ubuntu 22.04), Debian und OpenSUSE ohne weitere Installationen nutzen.

Ich muss daher in Zukunft bei Linux nicht Distributionen unterscheiden, sondern deren Bindung an lib-c Implementierungen.
Und da kenne ich aktuell eben nur glibc und musl-libc.