DllMain unter Linux
« | 01 Mar 2020 | »Windows-API Nutzer kennen
natürlich die globale Funktion DllMain
, die vom Betriebssystem automatisch
beim Laden und Entladen einer DLL
ausgeführt wird. (Dass das auch bei jedem neuen Thread geschieht lassen wir
hier mal unter den Tisch fallen.)
Und Linux? Kann man auch in Linux erzwingen, dass Code ausgeführt wird, ohne dass explizit eine Funktion aufgerufen wird?
Ein erfahrener C++ Entwickler käme
jetzt auf die Idee, dass “globale” Objekte die Lösung wären.
Denn der Standard garantiert, dass diese vor dem Aufruf von main
konstruiert
und nach dem Verlassen von main
zerstört werden. Also etwas Code in den
Konstruktor und Destruktor
und wir sind fertig …
Allerdings steht im C/C++ Standard nichts über DLLs oder SOs, also Bibliotheken, die erst zur Laufzeit geladen und entladen werden.
Zum Glück haben alle mir bekannten Compiler Hersteller diesen undefinierten Bereich genutzt und mit jener Regel ausgefüllt:
Globale Objekte werden beim Laden die Bibliothek konstruiert noch bevor die Lade-Routine (
LoadLibrary
bzw.dlopen
) zurückkehrt. Und sie werden ebenso zerstört, bevor das Entladen (FreeLibrary
bzw.dlclose
) abgeschlossen ist.
Natürlich gibt es keine Reihenfolge, wie das ablaufen soll, außer dass das Ausnullen von globalen Datenfeldern noch vor den Konstruktoren stattfindet.
Doch Betriebssysteme wissen nichts von C++ Objekten, also wie läuft das intern ab?
Nun, ein Blick in die Implementierung der C Runtime hilft einem da weiter:
Windows
Unter Windows führt das System in Wahrheit nicht DllMain
aus, sondern
CRTDllMain
. Und diese Funktion implementieren alle Windows Compiler
automatisch mit Code, der die Liste der globalen Objekte durchgeht und
deren Konstruktoren aufruft, nachdem elementare CRT Initialisierungsroutinen
durchgelaufen sind.
Ist das erledigt, wird die “programmierbare” DllMain(..., DLL_PROCESS_ATTACH, ...)
aufgerufen.
Beim Entladen läuft das ganze rückwärts ab.
CRTDllMain(..., DLL_PROCESS_DETACH, ...)
ruft zuerst “unsere” DllMain
auf,
um im Anschluss alle globalen Objekte per Destruktor-Funktion zu zerstören.
Linux
Das unter Linux verbreitete ELF Format
für ausführbare Binärdateien beinhaltet Spezifikationen für besondere
Sektionen (.ctors
und .dtors
). Wenn der Linker dort Funktionen ablegt,
werden diese beim Laden und Entladen der Bibliothek hintereinander ausgeführt.
Im GCC erreicht man den Eintrag einer Funktion in solche Sektionen mit den
Attributen __attribute__((constructor))
und __attribute__((destructor))
.
Linux schreibt selbst also keinen binären Standard vor, doch an dieser Stelle
springt der GCC ein und setzt einen de-facto Standard
Es gibt somit keine sichtbare zentrale Funktion (wie DllMain
), die man
implementieren kann, sondern man schreibt sich einfach selbst Funktionen wie:
Für globale C++ Objekte legt der GCC automatisch entsprechende Funktionseinträge an.
Wozu braucht man das?
Es herrscht in einigen (fremden) Projekten die Meinung vor:
DllMain sollte nicht benutzt werden, da es nicht auf Linux portiert werden kann.
Nun, das stimmt nicht.
Wenn es möglich ist, sollte man stets vor dem Entladen eines Bibliothek versuchen, Ressourcen freizugeben und Datenverknüpfungen zu lösen.
Natürlich sollte eine “gute” Bibliothek ein init
und ein uninit
anbieten, doch wer garantiert uns, dass der Anwender diese Funktionen
zur rechten Zeit aufruft.
Daher sollte man solche Spezialitäten auch beim Entladen durchführen und zwar für alles, was nicht schon zuvor freigegeben wurde.
Übrigens, kleiner Fun-Fact am Rande:
Da hat sich wohl jemand als Hacker versucht und eine tolle Doku
erstellt, wie man sogar eine
EXE Datei wie eine DLL laden
und nutzen kann.
Das Thema hört sich spannend an und ich nehme es hiermit in meine Liste der “hoch interessanten” Themen auf.