dlopen(NULL, 0)

Da Linux Distributionen hart daran arbeiten zu einander so inkompatibel wie möglich zu sein, suchte ich nach Strategien, wie man ohne Plattform-spezifische Linkereien so viel möglich an Features erhalten kann.

Aber um zu wissen, was einem überhaupt fehlt, muss man erst mal wissen, was man schon hat … und hier kommt dlopen ins Spiel.


Funktionen wie backtrace() oder getcontext() setcontext() und makecontext() swapcontext() sind zwar nicht 0815, aber auch nicht extrem selten. Schließlich helfen sie einem den Callstack “zu bearbeiten” oder Koroutinen oder eine spezielle Fehlerbehandlung zu implementieren.

Linux-Varianten, die mit der glibc aufgebaut wurden, integrieren diese Funktionen quasi in jeden Prozess, weil die glibc sie beinhaltet. Wie die “echte” Bibliothek heißt, ist versionsabhängig und grundsätzlich unbekannt, weil die Standard-Bibliothek automatisch gelinkt wird.

Ganz anders Alpine Linux oder andere Distros, die auf abgespeckte C-Libraries wie uClibc, musl-libc oder dietlibc aufbauen. Hier braucht man z.B.: libucontext.so.1 für die Context-Funktionen.

dlopen() ohne Bibliothek

dlopen bietet aber eine interessante Zusatzfunktion. Ruft man es nämlich mit

1void* process_handle = dlopen(NULL, RTLD_GLOBAL);
2void* ptr_function = dlsym(process_handle, "makecontext");

auf, erhält man Zugang zu allen bereits geladenen globalen Symbolen. dlsym kann nun von jeder Funktion, die bereits geladen ist, den Funktionszeigern ermitteln und zurückgeben.

Und auf diese Weise, kann man also zuerst “prüfen”, ob eine benötigte Funktion schon da ist.

Kein: if alpine, elseif debian …

Brauche ich also eine Funktion, die manchmal schon in der C-Bibliothek integriert ist und manchmal nicht, prüfe ich das zuerst mit dlopen(NULL) und nur wenn hier NULL zurückkommt, lade ich andere Bibliotheken und versuche mein Glück mit diesen.

Früher musste ich unseelige Spielereien wie

 1#include <my_lib/my_func.h>
 2typedef int(*my_func_ptr_t)(int param);
 3
 4#if defined(LINUX_MUSLIBC)
 5  void* my_lib = dlopen("libmy_lib.so", RTLD_GLOBAL);
 6  my_func_ptr_t my_func_ptr = dlsym(my_lib, "my_func");
 7#else
 8  my_func_ptr_t my_func_ptr = &my_func;
 9#endif

bemühen und den Code per CMake mit Hilfs-Makros anreichern.
Heute kann ich eine Array von Bibliotheksnamen anlegen und diese dann per Schleife durchlaufen lassen, wenn dlopen(NULL) eben nicht erfolgreich war.

Dieser Code ist dann OS-unabhängiger und läuft auf mehr Plattformen, weil er eben nicht per #ifdef auf eine Plattform fixiert ist.

Fazit

Das Thema, wie man Linux-Binaries baut, die überall laufen, ist ein schwieriges. Denn leider löst der Ansatz immer noch nicht das Problem, das z.B.: Alpine Linux und Debian zwei unterschiedliche Binaries brauchen.

Ich hatte auch schon mal mit dem -static Flag des GCC gespielt um absolut unabhängige Executables zu produzieren … doch bei denen funktioniert dann dlopen gar nicht mehr.
Womit der Ansatz leider auch wegfällt.

Zumindest wurden so wieder ein paar Makros wegrationalisiert.

Ein kleiner Schritt für die einzelne Projektdatei.
Aber ein großer für die Zukunft des Projekt.

📧 📋 🐘 | 🔔
 

Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!