dlclose() -> delete auf alle zeiger?!



  • Hallo!

    Meine dynamische lib soll für mein hauptprogramm ein datenbaum erstellen:
    Es folgt Pseudocode 🙂

    struct data
    {
        std::vector<dataType*> content;
    };
    
    data* global_ptr = 0; //Für andere funktionen, die daten schreiben...
    
    void andereFunktion();
    extern "C" void load(&data)
    {
       global_ptr = &data;
    
       while(nochDatenDa())
       {
            data.content.push_back(new dataType());
            andereFunktion();
       };
    }
    
    typedef void (*load_func_ptr)(data&);
    
    data load()
    {
         plugin = dlopen("plugin.so", RTLD_LAZY);
    
         load_func_ptr do_it = dlsym(plugin, "load");
    
         data whatIwant;
         do_it(whatIwant);
    
    //!!!!!! Es treten beim zugriff auf daten.content.at(X) nach dem laden Speicherzugriffsfehler auf,
    //!!!!!! sofern ich dlclose nicht ausklammere:
         //dlclose(plugin);  
    
        return data; 
    }
    

    Ich hoffe das ist selbsterklärend 🤡 :xmas1: :xmas2:



  • wenn du dlcose() ausführst, wird die bibliothek auch wieder (in den meisten fällen) entladen.



  • namenlos schrieb:

    wenn du dlcose() ausführst, wird die bibliothek auch wieder (in den meisten fällen) entladen.

    und das heißt konkret? Alles was die bibliothek mit new erstellt hat wird gelöscht?!



  • nein. new sachen liegen am heap (was dann unter umständen zu speicherlecks führt). aber alle funktionen und globalen und statischen daten.



  • namenlos schrieb:

    nein. new sachen liegen am heap (was dann unter umständen zu speicherlecks führt). aber alle funktionen und globalen und statischen daten.

    genau ... ! Diese new sachen führen zu seg faults!!! Scheinbar werden die gelöscht wenn ich dlclose ausführe! Wie kann ich die daten sicher behalten?!



  • daten, die du am heap allokierst, bleiben solange, bis etwas delete aufruft. dlcose() tut das nicht. das tust du im code irgendwo im code. leider steht von deinem code zu wenig da. vielleicht liegt es aber auch gar nicht an heap-daten sondern an statischen oder globalen.
    kennst du valgrind? http://valgrind.org/. wenn du alles mit debugging symbolen compilierst (option -g beim gcc), bekommst du mit valgrind genau heraus, bei welchem speicherzugriff der fehler auftritt. dann hast du einen anhaltspunkt, welche daten fehlen, sobald das plugin entladen wurde.



  • Ja, valgrind kenne ich, das sagt mir auch brav zB folgendes bis es abstürzt:

    ==26674== 29,412 bytes in 2,451 blocks are definitely lost in loss record 411 of
    430
    ==26674== at 0x4C21759: operator new(unsigned long) (in /usr/lib64/valgrind/a
    md64-linux/vgpreload_memcheck.so)
    ==26674== by 0xBC4603D: ???
    ==26674== by 0xBC46303: ???
    ==26674== by 0xBC42E73: ???
    ==26674== by 0x409503: formats::loadData(char*) (in /home/mod/LSD/build/bin/lin/lsd)

    Allerdings weit und breit nicht von einem delete. Ich finde halt auch äußerst merkwürdig das einfach alles perfekt funktioniert wenn ich das dlclose auskommentiere! 😕 😕 😕 😕



  • die valgrind ausgabe gibt für drei funktionen fragezeichen aus und überhaupt keine programmzeilen. du musst mit -g compilieren, damit er die programmzeilen ausgibt. dann weißt du genau, in welcher zeile der fehler auftritt. das hilft dir bestimmt weiter. ich kenne deinen code nicht.



  • Ja, mit -g war es auch nicht sonderlich hilfreich, oder ich kann nicht damit umgehen? 🙄

    Jedenfalls bin ich nach stundenlangem auskommentieren und rumprobieren zu folgendem Schluss gekommen:
    Der absturz wird durch das ausführen einer beliebigen virtuellen Funktion von dataType erzeugt!
    (zur erinnerung: dataType wird in der zu ladenden dll erzeugt und an das hauptprogramm weitergegeben, welche dann unter umständen virtuelle funktionen ausführt! Dazu kompiliere ich so:

    g++ -Wall -g -fPIC -c dataType.cpp -o dataType.o
    g++ -Wall -g -fPIC -c plugin.cpp -o example.o
    g++ -Wall -g -fPIC -shared dataType.o plugin.o -o plugin.so

    ?! Bin ich jetzt auf der richtigen Fährte? 😞

    //Edit: Wenn ich auf die übergebenen dataType* ein delete ausführe, crasht das programm ebenfalls:

    Finished loading! (0.1 seconds)

    Program received signal SIGSEGV, Segmentation fault.
    0x00000000004093e7 in formats::loadModel (
    file=0x6b9be8 "/home/mod/LSD/build/bin/lin/data/test.dat") at src/loader.cpp:202
    202 delete data.m.back();

    Hier stimmt wohl was grundlegendes nicht?!

    //Edit 2: Nicht das jetzt der falsche eindruck entsteht: mit nicht-virtuellen funktionen lässt sich mit den daten tadellos arbeiten!

    //Edit 3: valgrind sagt zu dem delete problem folgendes:

    Finished loading! (47.17 seconds)
    ==9662==
    ==9662== Invalid read of size 8
    ==9662== at 0x4093E7: formats::loadModel(char*) (loader.cpp:202)
    ==9662== by 0x40D224: main (main.cpp:74)
    ==9662== Address 0x8F98D38 is not stack'd, malloc'd or (recently) free'd
    ==9662==
    ==9662== Process terminating with default action of signal 11 (SIGSEGV)
    ==9662== Access not within mapped region at address 0x8F98D38
    ==9662== at 0x4093E7: formats::loadModel(char*) (loader.cpp:202)
    ==9662== by 0x40D224: main (main.cpp:74)
    ==9662==
    ==9662== ERROR SUMMARY: 11 errors from 6 contexts (suppressed: 8 from 1)
    ==9662== malloc/free: in use at exit: 3,212,214 bytes in 88,476 blocks.
    ==9662== malloc/free: 968,935 allocs, 880,459 frees, 37,260,798 bytes allocated.
    ==9662== For counts of detected errors, rerun with: -v
    ==9662== searching for pointers to 88,476 not-freed blocks.
    ==9662== checked 5,102,592 bytes.



  • Ich habe mal den bug in nem mini-programm reproduziert:
    http://rapidshare.de/files/38061656/bug.zip.html :xmas2: :xmas1:



  • Ich habe jetzt endlich zum thema ergoogeln können! Es ist wohl so das die shared library einen eigenen heap/heap manager besitzt. Dynamisch angeforderter Speicher muss daher innerhalb des moduls auch wieder entfernt werden. Im hauptprogramm kann da nix nachträglich gelöscht werden. Richtig? :xmas1:

    Forzsetzung folgt... 🙄

    P.S: Danke namensloser



  • könntest du mir bitte die url zu dieser information zeigen? wäre nett.

    hier ein programm, das im prinzip nichts anderes tut, als eine lib zu laden, die einen int allokiert. wenn du dir die orte ansiehst, an denen die ints (einer von der lib allokiert, einer direkt im programm) stehen, zeigt sich, dass hier der gleiche heap verwendet wird. außerdem passiert keine segfault und valgrind --tool=memcheck ./libexe zeigt auch keine fehler. es gibt nur einen heap.

    lib:

    extern "C" int* allocate(void) { return new int; }
    

    exe:

    #include <dlfcn.h>
    #include <iostream>
    
    typedef int* (*allocate)(void);
    
    int main(void) {
      void* handle = dlopen("./liblib.so.1", RTLD_LAZY);
      if(!handle) {
        std::cerr << "failed" << std::endl;
        return 1;
      }
    
      allocate a = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
      if(!a) {
        std::cerr << "failed2" << std::endl;
        return 1;
      }
    
      int* data = a();
      std::cout << static_cast<void*>(data) << std::endl;
      *data = 123;
      int* data2 = new int;
      std::cout << static_cast<void*>(data2) << std::endl;
      dlclose(handle);
      std::cout << *data << std::endl;
      return 0;
    }
    


  • Für mal mein beispielprogramm aus ... du wirst schon sehen! *URL :xmas1: such*



  • Ich muss sagen ich verstehe das Problem nicht. Warum willst Du die Bibliothek überhaupt mit dlclose schließen, wenn Du sie noch benutzen willst? Das macht man am Programmende, fertig ist die Kiste, oder?



  • es ist nicht gesagt, dass er das plugin noch braucht. es ist sicher keine schlechte idee, plugins dann zu entladen, sobald sie nicht mehr benötigt werden.



  • Ich glaube es steht ausser frage des ist unangebracht ist einfach das plugin geöffnet zu lassen um den bug zu umgehen (Vll will der user später ja eine neue version des plugins in sein verzeichnis packen ohne dass seine bisherige arbeit weg ist, oder ich möchte mehr als ein plugin verwenden ohne dass ich ein stack voller plugin handle speichere ... 😉 )

    Ich habe hier nochmal ein einfaches beispiel gemacht... was denn irgendwer eine lösung?

    "daten":

    #ifndef DATA_H
    #define DATA_H
    
    #include <iostream>
    
    class data
    {
    public:
    	data(int d) : i(d) {}
    	virtual ~data() {}
    	virtual void print(){ std::cout <<  i << std::endl;}
    
    private:
    	int i;
    };
    
    #endif
    

    plugin:

    #include "data.h"
    
    extern "C" data* allocate(void)
    {
    	return new data(13);
    }
    

    programm:

    #include "data.h"
    
    using namespace std;
    
    typedef data* (*allocate)();
    
    int main()
    {
    	void* handle = dlopen("./liblib.so", RTLD_LAZY);
    	if(!handle){
    		std::cerr << "failed" << std::endl;
    		return 1;
    	}
    
    	allocate load = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
    	if(!load) {
    		std::cerr << "failed2" << std::endl;
    		return 1;
    	}
    
    	data* d = load();
    
    	dlclose(handle);
    
    	d->print(); //absturz
    	delete d; //absturz
    }
    

    Also wenn das noch dieses Jahr gelöst wird, wäre weihnachten für mich gerettet! :xmas1: :xmas2: Danke danke! 🤡



  • Das Problem hier ist, das die Addresse unter der sich die funktion data::print befindet nicht mehr gültig ist. Das selbe gilt auch für die delete zeile, wobei es hier um die destructor-funktion handelt.

    Der Code für die Klasse data befindet sich einmal im Programm und einmal im plugin (liblib.so).

    Jede Methode einer Klasseninstanz ist im Speicher nur eine art funktions-zeiger auf die eigentliche Methode der Klasse, welche sich im Data-segment der geladenen Binary.
    In deinem Falle ist also der Code für die Klasse data zweimal im speicher vorhanden. Einmal im Data-Speicherbereich des Programms und das 2. mal im Data-Speicherbereich des Plugins.

    Beim erzeugen einer Instanz einer Klasse müssen dann die addressen der Methoden aufgelöst werden. Sprich die "funktions-zeiger" der Instanz müssen mit der addresse der dazugehörigen Methode, welche sich im data-segment der geladenen binaries befinden, gefüllt werden.

    Beim suchen der Addressen der gesuchten Methoden wird in dem binary in dem die Instanz erzeugt wurde begonnen. In diesem Falle findet der Laufzeitcode die gesuchten Methoden im data-Speicherbereich des Plugins.

    Durch das entladen des Plugins wird der Speicherbereich (data-segment), welche vom Plugin verwendet wurde, wieder freigegeben. Und dadurch sind die Addressen der Methoden in der Instanz der Klasse data, welche sich hinter der variable "d" verbirgt, nicht mehr gültig.

    Um das Beispiel zum laufen zu bringen gibt es folgende (mir bekannte) möglichkeiten:
    1. die Klasse implementiert einen Copy-Constructor, oder
    2. das Plugin wird erst nachdem verwenden der erzeugten Klasseninstanz entladen.

    Hier die Änderungen des Beispiel-codes
    zu Möglichkeit 1:

    data.h:

    #ifndef DATA_H
    #define DATA_H
    
    #include <iostream>
    
    class data
    {
    public:
        data(int d) : i(d) {}
    
        // Copy-Konstruktoren
        data(data *p)
        {
    	std::cout<<"*-copy-ctor"<<std::endl;
    	i = p->i;
        }
        data(data &p)
        {
            std::cout<<"&-copy-ctor"<<std::endl;
    	i = p.i;
        }
        virtual ~data() {std::cout<<"dtor data"<<std::endl;}
        virtual void print(){ std::cout <<  i << std::endl;}
    
    private:
        int i;
    };
    
    #endif
    

    programm:

    #include "data.h"
    #include "dlfcn.h"
    using namespace std;
    
    typedef data* (*allocate)();
    
    int main()
    {
        void* handle = dlopen("./liblib.so", RTLD_LAZY);
        if(!handle){
            std::cerr << "failed" << std::endl;
            return 1;
        }
    
        allocate load = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
        if(!load) {
            std::cerr << "failed2" << std::endl;
            return 1;
        }
    
        data *d = load();
        data p(d);
        delete d;
    
        dlclose(handle);
    
        p.print();
    }
    

    Zur Möglichkeit 2:

    Programm:

    #include "data.h"
    
    using namespace std;
    
    typedef data* (*allocate)();
    
    int main()
    {
        void* handle = dlopen("./liblib.so", RTLD_LAZY);
        if(!handle){
            std::cerr << "failed" << std::endl;
            return 1;
        }
    
        allocate load = reinterpret_cast<allocate>(dlsym(handle, "allocate"));
        if(!load) {
            std::cerr << "failed2" << std::endl;
            return 1;
        }
    
        data* d = load();
    
        d->print();
        delete d;
        dlclose(handle);
    }
    


  • Herzlichen dank für die erklärung!

    Ich mache es derzeit ähnlich wie in Möglichkeit 1 von dir beschrieben, habe das allerdings mehr als notlösung angesehen .... Bringt der kopierkonstruktor gegenüber operator= irgnendwelche Vorteile?



  • Pigeon schrieb:

    Herzlichen dank für die erklärung!

    Ich mache es derzeit ähnlich wie in Möglichkeit 1 von dir beschrieben, habe das allerdings mehr als notlösung angesehen .... Bringt der kopierkonstruktor gegenüber operator= irgnendwelche Vorteile?

    Ach den zuweisungs-operator (operator 🙂 habe ich ja ganz vergessen 🙂
    Soweit ich weis hat der Copy-Konstruktor eigentlich keine Vorteile gegenüber dem zuweisungs-operator.
    Es kann aber sinnvoll sein beide zu implementieren, denn ich hatte schon das phänomen, das der Copy-Konstruktor vom compiler verwendet wurde, obwohl ich dachte der zuweisungs-operator würde verwendet werden.

    egal du du jetzt den Copy-Konstruktor, den Zuweisungs-operator oder beide verwendest, in deinem Beispiel musst du auf jeden fall über eine temporäre variable gehen, um das im plugin dynamische(per new) erzeugte Objekt wieder freizugeben.
    Ansonsten erzeugst du ein Speicherleak.



  • zuweisungsoperator und copy constructor sind zwei ganz verschiedene dinge. man kann sich nicht für eines entscheiden. der zuweisungsoperator wird verwendet, um einer existierenden instanz einer klasse, etwas neues zuzuweisen. der copy constructor wird verwendet, um eine neue instanz als kopie einer anderen zu erzeugen.

    klasse k1;
    klasse k2 = k1;
    klasse k3(k1);
    

    hier wird 2 mal der copy-constructor aufgerufen. auch in zeile 2 und trotz des zuweisungszeichens.
    wenn du eine deep copy (ich weiß, ein fachvokbal... http://de.wikipedia.org/wiki/Kopierkonstruktor#Verwendung) erzeugen willst, musst du zumindest den copy constructor implementieren. die zuweisung kannst du auch auf andere art (z.b. per assign()-methode) realisieren.


Anmelden zum Antworten