dlclose verursacht einen segfault
-
Hallo,
ich habe eine frage zur benutzung von dlload und dlclose:
Und zwar lade ich ein Plugin mit dlload und will es dann wieder mit dlclose schließen. Das funktioniert auch ganz prima, wenn in der zwischenzeit die Datei auf der festplatte nicht geändert wurde (will sagen, dass ich das zu ladende plugin neu kompiliert habe, oder was auch immer)Beispielcode der zum absturz führt:
#include <dlfcn.h> int main() { void* pHandle = dlopen( "/path/to/the/plugin", RTLD_NOW ); getchar(); // nun hat man genug zeit, das plugin auf der festplatte zu ändern dlclose( pHandle ); }
Das ist jetzt nur ein beispielcode. Im realen problem geht es darum bildverarbeitungsplugins zu entwickeln, und diese in einem Programm zu testen. nun habe ich keine lust jedes mal mein Programm zu beenden, sondern will im laufenden Betrieb, die geänderten Bildverarbeitungsplugins laden...
Muss ich das plugin mit irgendwelchen speziellen compilerflags compilieren, oder dlopen irgendwelche flags mitgeben (also RTLD_GLOBAL, oder sowas). Die manpage ist dazu leider überhaupt nicht hilfreich.
Ich versteh sowieso nicht, wieso die Applikation abstürzt, denn eigentlich sollte doch die Library in den Arbeitsspeicher geladen werden, und damit unabhängig von der datei auf der festplatte sein, oder?Gruß
Jocker
-
also ich kann das Problem nicht nachvollziehen.
Überprüfst du überhaupt ob der Rückgabewert von dlopen != NULL ist?Denn dein Beispiel führt bei mir nur zu einem Segfault, wenn pHandle == NULL ist.
Ich denke eher das dein Programm irgendwo noch eine Referenz auf Daten hält, welche von der lib erzeugt wurden, und diese nach dlclose verwenden möchte.
EDIT: ok es kommt doch zum Speicherzugriffsfehler, hatte nur mit nem veränderten symlink getestet und da gab es keine Probleme
EDIT2: jetzt wirds komisch, ich habe das ganze nochmal mit einer selbst erstellen lib getestet. Wenn ich die lib ändere, während das test programm noch läuft und die lib per dlopen geladen hat, und dann das Programm sich beendet kommt es nicht zu einem Segfault.
Zu einem Segfault kam es nur, als ich 2 komplett verschiedene libs zum testen verwendet habe.
-
Programmcode wird nur dann in den Hauptspeicher geladen, wenn er auch benötigt wird. Deshalb kann es gut sein, dass Teile deiner .so erst dann geladen werden, wenn du das Plugin entladen möchtest. Wenn diese Teile nicht mehr zum ursprünglichen Code passen gibt es einen Absturz. Oft gibt es einen SIGSEGV, SIGBUS oder SIGILL.
Also erst Plugin entladen, dann kompilieren und dann neu laden.
Es kann auch sein, dass es hilft das Plugin erst zu löschen bevor man es neu kompiliert. Solange das Programm das Plugin noch offen hat, kann es auf die Daten zugreifen. Das neukompilierte Plugin dagegen sollte unabhänig von der alten in einer neuen Datei landen, obwohl sie den gleichen Namen hat, wie das alte.
-
Das heißt also in Kurzform, dass es gewolltest verhalten ist, und ich absolut nichts daran ändern kann? Das problem ist halt, dass ich nicht immer daran denke, erst das plugin zu entladen, um es dann neu zu kompilieren. Und sobald es neu kompiliert ist, ist es leider zu spät es zu entladen...
Naja irgendwie schade dieses verhalten, da ich von Linux gewohnt bin, dass ich bedenkenlos dateien löschen kann, auch wenn sie gerade verwendet werden (und ich kann auch shared libs ändern, die nicht per dlopen geladen wurden, sonder mit ldd angelinkt wurden). Deshalb versteh ich nicht, wieso das mit dlopen nicht funktioniert...
-
Ja es ist gewollt. Es gibt Unixe, die dich vor sowas schützen, aber nicht Linux.
Das Problem taucht auch nur bei dir als Entwickler auf. Es passiert ja sonst nicht oft, dass Binärcode eines laufenden Programms sich einfach ändert.
-
eigentlich passiert es ständig, dass sich binärcode einfach so ändert... nämlich genau dann, wenn ich ein update des Systems durchführe, und das programm noch geöffnet habe. Ich sehe es z.B. nicht ein KDE neuzustarten, nur weil es gerade geupdatet wird, d.h. dass sich die KDELIBS zwar ändern, aber noch immer alles schön funktioniert... Wenn ich das nächste mal neu starte habe ich auch schön das neue KDE, ohne mir großartig gedanken darüber gemacht zu haben, dass ich erst kde schließen muss, und es erst dann updaten darf.
Wohingegen ich mir gerade z.B. bei Compiz-Fusion nicht mehr sicher bin, ob ich das so einfach updaten darf, da hier ebenfalls ziemlich viel mit plugins realisiert ist, und diese dürfen sich anscheinend nicht ändern
irgendwie finde ich dieses Verhalten inkonsistent, aber ich lebe halt damit, wenn es tatsächlich gewollt ist...Ich werde wohl einfach die Librarys die ich nachladen möchte, erst ins temp-verzeichnis kopieren, und dann kann ich auch die tatsächliche lib ändern, und reloaden (hässlich, aber momentan die einzige möglichkeit die ich sehe das zu realisieren)
-
Was sagt eigentlich valgrind zu deinem Problem?
-
@Jocker16
du hast ein wichtiges detail übersehen. Ponto hat es schon angesprochen:
es gibt einen unterschied zw löschen und neu erstellen und einfach nur neu schreiben. wenn eine datei neu geschrieben wird, ist sie noch die gleiche, die sie davor war - nur mit neuem inhalt. ist allerdings eine datei gelöscht und eine neue unter dem gleichen namen (und pfad) erstellt worden, so ist die zweite dann auch wirlich eine neue datei.solange eine datei geöffnet ist, bleibt sie auch nach einem löschvorgang erhalten. damit ist es kein problem, ein plugin oder eine bibliothek upzudaten, während sie verwendet wird.
wird allerdings die existierende datei mit neuem inhalt gefüllt, wird auch dieser dann geladen (entweder während das programm noch läuft oder beim beenden). gerade c++ führt beim beenden noch mal code aus, um zb die dtors der statischen variablen aufzurufen.
bei kde ist das etwas anders. kde ist ja nicht ein prozess sondern besteht auf vielen. je nachdem, wie du den prozess startest, werden bibliotheken verschieden geladen.
lädst du den konqueror zb per f2, wird er von einem kdeprogramm gestartet. dieses kde programm enthält schon verschiedene kdelibs. dh, sie müssen nicht neu geladen werden beim exec, da sie vor dem fork schon verfügbar waren. deshalb wird ein per f2 gestarteter konqueror die alten kdelibs verwenden, aber unter umständen neue plugins, die noch nicht geladen waren.
wenn du den konqueror an der konsole lädst, startet die bash ihn, die natürlich keine kdelibs schon enthält. daraus folgt, dass die neuen kdelibs geladen werden.
es ist leider nicht so leicht, hier konsistenz reinzubringen. eine lösung dafür sind die snapshot features von dateisystemen. solaris' zfs hat so eines. das neue btrfs für linux soll diese feature auch bekommen. damit wäre es möglich, beim programm start einen snapshot der programmverzeichnisse (/bin, /sbin, /usr, ...) zu erzeugen, sodass diese konsistent bleiben und updates erst bei einem neustart (oder durch expliziten willen des users) commitet werden.
-
ok, ich dachte eigentlich immer, dass kopieren in etwa der aktion gleichkommt wie alte datei löschen und neue datei anlegen (ich hab nämlich nie auf der frisch kompilierten lib gearbeitet, sondern immer erstmal kopiert)
Valgrind sagt folgendes:
29483== Invalid read of size 8 ==29483== at 0x5EFC989: (within /home/jocker/libtestplugin.so) ==29483== by 0x5EFCB70: (within /home/jocker/libtestplugin.so) ==29483== by 0x401203B: (within /lib64/ld-2.6.1.so) ==29483== by 0x400C9F5: (within /lib64/ld-2.6.1.so) ==29483== by 0x4E2833C: (within /lib64/libdl-2.6.1.so) ==29483== by 0x4E2806E: dlclose (in /lib64/libdl-2.6.1.so) ==29483== by 0x400E22: main (in /home/jocker/a.out) ==29483== Address 0x200df0 is not stack'd, malloc'd or (recently) free'd
libtestplugin.so wurde aus nur einer datei erstellt, nämlich test.cpp. Inhalt dieser datei war:
#include <string> class Testclass { public: Testclass( std::string sName ); private: std::string m_sName; }; Testclass::Testclass( std::string sName ) { m_sName = sName; } // g++ -c -fPIC -O2 -pipe -o test.o test.cpp // g++ -shared -o libtestplugin.so test.o // cp libtestplugin.so /home/jocker
Da der tipp funktioniert mit dem erst löschen, dann kopieren, werde ich wohl in mein Makefile erstmal ein rm DESTINATION aufrufen, bevor ich die datei nach DESTINATION kopiere.
Falls noch jemand irgendwelchen genialen Ideen hat, immer her damit, aber ich glaube es gibt (leider) nicht mehr viel hinzuzufügen
-
debuggen wird erst dann nützlich, wenn du deinen code mit debugging symbolen compilierst. die -g optionen des gcc helfen dir dabei. valgrind kann dir dann die zeile im sourcecode anzeigen, in der der fehler passiert.
-
ok, hab jetzt sowohl die testlib, als auch das testprogramm mit debug-infos kompiliert, bekomme allerdings bei valgrind keine infos in welcher zeile der fehler passiert, und gdb sagt mir:
0x00007f9698142789 in ?? () from /home/jocker/libtestplugin.so
Keine ahnung wieso er mir die Zeilennummer nicht sagt.
Wie kann ich testen, ob ein programm wirklich debug symbole enthält?
Werd jetzt auch mal versuchen die glibc mit debug infos zu kompilieren, vllt zeigt mir ja gdb/valgrind dann mehr.
-
Jocker16 schrieb:
ok, hab jetzt sowohl die testlib, als auch das testprogramm mit debug-infos kompiliert, bekomme allerdings bei valgrind keine infos in welcher zeile der fehler passiert, und gdb sagt mir:
0x00007f9698142789 in ?? () from /home/jocker/libtestplugin.so
Keine ahnung wieso er mir die Zeilennummer nicht sagt.
Wie kann ich testen, ob ein programm wirklich debug symbole enthält?
Werd jetzt auch mal versuchen die glibc mit debug infos zu kompilieren, vllt zeigt mir ja gdb/valgrind dann mehr.Wahrscheinlich wird durch den Fehler einfach irgendein Code ausgeführt, der sich in irgendwo in testplugin.so befindet. Das kann Code sein, für den es keine Debuginformationen gibt, weil der vom GCC automatisch eingefügt wird, oder es ist nicht mal Programmcode.