SIGSEGV beim Aufruf einer virtuellen Methode



  • Hey Leute,

    ich schreib mir grad einen C++Wrapper für Lighttpd-Plugins. Das funktioniert auch fast alles so wie ich mir das denke, nur sobald ich die Methoden meiner Klasse virtuell mach, dann fliegt beim Aufruf dieser Methoden eine SIGSEGV.
    Wie ist das Ganze aufgebaut? Lighttpd ist ein lightweight WebServer der gern im Embedded Bereich genutzt wird. Für diesen Server kann man Plugins schreiben (in Form von dynamischen Libs). Einsprungspunkt in die Lib ist folgende Funktion:

    extern "C" int mod_test_plugin_init(plugin* p){ ... }
    

    Wobei "mod_test" der Name des Plugins ist. "plugin" ist eine Struktur die einige Funktions-Zeiger enthält. Jenachdem was man in seinem Plugin implementieren möchte. Ich habe mir ein Template geschrieben, welches statische Methoden definiert, dessen Zeiger in der plugin-Struktur registriert werden. In diesen statischen Methoden wird dann die eigentliche (virtuelle) Member-Methode meiner Klasse aufgerufen. Ein sehr vereinfachtes Beispiel sieht so aus:

    template <typename T>
    class PluginImpl
    {
    public:
        static void* initData()
        {
            return new T();
        }
    
        static handler_t cleanupData(server *srv, void *pData)
        {
            delete reinterpret_cast<T*>(pData);
            return HANDLER_GO_ON;
        }
    
        static handler_t some_callback_impl(server* srv, connection* con, void* pData)
        {
            return reinterpret_cast<T*>(pData)->some_callback(src, con); // hier knallt es wenn die Methode virtuell ist
        }
    
        static int init(plugin* p)
        {
            p->name     = buffer_init_string(name);
            p->version  = LIGHTTPD_VERSION_ID;
            p->init     = &initData; // initData initialisiert die Daten des Plugins/ erzeugt das Objekt der Klasse T
            p->cleanup  = &cleanupData;
            p->data     = NULL; // hier werden die Daten aus initData gespeichert (macht der Server automatisch)
    
            p->some_callback = &some_callback_impl; // hier werden die Callbacks initialisiert
    
            return 0;
        }
    }
    
    class MyPlugin
    {
    public:
        virtual handler_t some_callback(server* srv, connection* con)
        {
            // do something
        }
    }
    
    extern "C" int mod_test_plugin_init(plugin* p) // Einsprungspunkt in das Plugin
    {
        return isef::PluginImpl<MyPlugin>::init(p);
    }
    

    Ich nutze GCC 4.7.3 bzw. G++ 4.7.3 und C++11. Kann sich das von euch jmd erklären, warum die virtuellen Methoden da ne SIGSEGV werfen? Ich nehme an das aus irgend einem Grund die vtable zerstört wird. Nur die Frage warum? 😕

    MfG Bergmann.


  • Mod

    Bei solchen Fehlern ist ein vollständiges Minimalbeispiel wichtig: Wie man Probleme nachstellbar und nachvollziehbar macht

    Ansonsten ist das ein großes Ratespiel, bei dem es tausende mögliche Antworten gibt, die aber auch allesamt als "du hast was falsch gemacht" zusammengefasst werden können.



  • http://stackoverflow.com/questions/310451/should-i-use-static-cast-or-reinterpret-cast-when-casting-a-void-to-whatever

    Keine Ahnung, ob das tatsächlich nicht funktioniert, wenn man es dennoch macht. Der offensichtliche Fall, dass der Zeiger nicht gültig ist, wird ja hoffentlich schon verifiziert worden sein.


  • Mod

    SeppJ schrieb:

    Ansonsten ist das ein großes Ratespiel, bei dem es tausende mögliche Antworten gibt, die aber auch allesamt als "du hast was falsch gemacht" zusammengefasst werden können.

    Größtenteils. Erzähl mir nicht dass Compiler keine Bugs haben. 😉



  • decimad schrieb:

    http://stackoverflow.com/questions/310451/should-i-use-static-cast-or-reinterpret-cast-when-casting-a-void-to-whatever

    Keine Ahnung, ob das tatsächlich nicht funktioniert, wenn man es dennoch macht. Der offensichtliche Fall, dass der Zeiger nicht gültig ist, wird ja hoffentlich schon verifiziert worden sein.

    reinterpret_cast in beide Richtungen sollte IMO garantierterweise funktionieren.
    Implizit nach void und dann mit reinterpret_cast zurück ist aber vermutlich nicht OK.
    Lieber implizit nach void und dann static_cast.
    Oder gleich 2x explizit mit static_cast.



  • Hey Leute,

    Danke für die Antworten.

    @SeppJ: das ist das Minimalbeispiel. Ist alles wichtige drin. Der WebServer ruft mod_test_plugin_init auf. Dort wird die plugin-Struktur des Servers initialisiert und die entsprechenden Callbacks eingehängt. Dann ruft der Server über das Callback initData auf, wo die Plugin-Daten initialisiert werden sollen (sprich ich lege eine Instanz meiner Plugin-Klasse an). Wenn der Server mir jetzt irgend ein Ereigniss mitteilen möchte ruft er (ebenfalls über ein Callback) some_callback_impl auf. Dort werden die Plugin-Daten die vorher von initData erstellt wurden mit übergeben. Diese caste ich in mein Objekt und ruf die entsprechende Member-Methode auf. Wenn diese Methode virtuell ist fliegt eine SIGSEGV, ansonsten geht es ohne Probleme.

    @decimad, hustbaer: Hab ich eben mal probiert. Hilft leider nicht. Hab jetzt bei beiden casts (in initData und in some_callback_impl) ein static_cast drin. Die Zeiger hab ich überprüft, sind überall die selben.

    MfG Bergmann.



  • Das Problem mit deinem Minimalbeispiel ist, dass wir es nicht compilieren können um den SIGSEV zu reproduzieren (ich habs jetzt ehrlich gesagt nicht probiert aber in wirklichkeit gibts wohl keinen some_callback in Lighttpd). Ein möglichst einfaches Beispiel, dass man tatsächlich compilieren kann wäre für uns hilfreich.

    Ich vermute der Fehler liegt woanders und nicht in diesem Schnipsel Code den du uns zeigst. Wenn die Adresse deines Pointers sich nicht ändern kann es ja eigentlich nur noch sein, dass irgendwer dein Objekt zerschießt. Das passiert vermutlich auch ohne virtuelle Funktionen aber weil es dann keinen vtable gibt fällt es erstmal nicht auf. Kannst du mal valgrind oder so drüber laufen lassen?



  • Stimmt, wir sind ja embedded unterwegs... Da gibts natürlich Stack-Overflows und DMA-Operationen, die Amok laufen und was nicht noch alles... Schwer sowas mitzubekommen... Was Du machen könntest, wäre einen Memory-Watchpoint auf den Bereich Deines Objekts zu legen. Da bekämst Du wenigstens mit wann jemand den Vtable-Pointer überschreibt.



  • Hey,

    hab grad mal nen Dump von dem Objekt gemacht. Nach dem anlegen stimmt die vtable noch, im Callback ist sie kaputt, also schreibt der da irgendwo im RAM rum wo er nicht zugreifen sollte.
    Das Projekt ist zwar für ein EmbeddedSystem, der Fehler lässt sich aber auch auf meiner Entwicklungs-Maschiene (Ubuntu x64) reproduzieren. GDB spinnt grad rum, ich guck mal das ich den zum laufen bekomm um mir dann mal nen Memory-Watchpoint drauf zu legen.

    MfG Bergmann.



  • Bergmann89 schrieb:

    das ist das Minimalbeispiel.

    Nein, es ist garkein Minimalbeispiel. Es kompiliert nicht ( some_callback , src in Z18, includes?). Daher kannst du nicht überprüft haben, dass der Fehler noch auftritt.



  • Habs gefunden. Das Problem saß wie üblich vor'm PC. Die Daten die im initData erzeugt werden müssen eine Struktur sein, die am Anfang ein paar Felder besitzt, die vom Lighttpd geschrieben werden. Deshalb hat er dann dort auch die vtable des Objekts überschrieben. Die Methode hab ich jetzt wiefolgt abgeändert:

    struct PluginData
    {
        PLUGIN_DATA; // besagte Plugin Daten vom Lighttpd
        T* ref;
    };
    
    static void* initData()
    {
        PluginData *p = new PluginData();
        p->ref = new T();
        return static_cast<void*>(p);
    }
    

    Danke für die Hilfe und die Mühe 😃


Log in to reply