Seite 1 von 1

Exceptions und Shared Objects

Verfasst: 29.05.2011, 23:10
von dawit
Schönen guten Abend,
ich hab da so ein Problem mit Exceptions und Dynamic Shared Objects (unter Linux).
Ich hab ein Lib (*.so) und eine Anwendung, die diese benutzt. Die Lib stellt u.a ein Interface bereit, das von der Anwendung implementiert wird und dessen Funktionen dann wiederum in der Library aufgerufen werden. In einer dieser virtuellen Funktionen, die in der Anwendung definiert und in der Lib aufgerufen werden, wird eine Lib-Funktion aufgerufen, die eine Exception wirft. Diese Exception kann ich jetzt irgendwie nur innerhalb der virtuellen Funktion fangen, aber nicht außerhalb in der main Funktion.
ich merke gerade, dass das alles ein bisschen wirr klingt, deshalb mal ein bisschen Code.

Code: Alles auswählen

SomeInterface::func()
{
    ...
    someLibFunc(); // wirft exception
    ...
}

int main()
{
    SomeInterface foo;
    Library lib(&foo);

    try
    {
        lib.doSomething(); // ruft SomeInterface::func() auf
    }
    catch(std::exception const &e)
    {
        // Exception wird nie gefangen
    }
}


Fangen kann ich die Exception nur in SomeInterface::func():

Code: Alles auswählen

SomeInterface::func()
{
    try
    {
        someLibFunc(); // wirft exception
    }
    catch(std::exception const &e)
    {
        // das geht
    }
}


Kann man dieses Problem irgendwie umgehen, so dass man die Exceptions auch in der main-Funktion fangen kann?


EDIT: Mist, der Fehler scheint doch woanders zu liegen, denn in einem Minimalbeispiel funktioniert alles prima. Jetzt muss ich wohl irgendwie den Unterschiede finden...

Re: Exceptions und Shared Objects

Verfasst: 30.05.2011, 15:38
von Krishty
Zumindest unter Windows ist das unmöglich weil die Typinformation, mit der die Ausnahme ausgestattet wird um einen sie erwartenden catch-Block zu finden, sich von Modul zu Modul unterscheidet:

Da das Prozessmodul nicht weiß, welche Typen eine zur Laufzeit gebundene Bibliothek nutzt und umgekehrt die Bibliothek nicht weiß, welche Typen das Prozessmodul nutzt, weicht die Typinformation – die im simpelsten Fall einfach nur ein int mit einer „magischen“ Zahl ist – fast zwangsläufig ab. Kommt dann im Prozessmodul eine Ausnahme mit Typinformation aus der Laufzeitbibliothek an, rauscht sie im besten Fall bis zum nächsten catch(...)-Block durch oder landet im schlimmsten Fall in einem vollkommen falschen catch-Block weil die Typinformation zufällig der eines komplett anderen Typs aus dem Prozessmodul entspreicht.

Die Lösungen – entweder vom Modul unabhängige Typinformation à les Klartextnamen zu nutzen oder die Listen beim Laden der Laufzeitbibliothek mit derer des Prozessmoduls zu vereinen – sind eigentlich so unpraktikabel, dass es mich wundern würde, wenn das überhaupt ein Paar aus Compiler und Betriebssystem tun würde.

Darum bleibt mir unter Windows x86 nur: Jeder Aufruf einer (nicht als throw() oder noexcept dekorierten) Funktion aus einer Laufzeitbibliothek muss in einen catch(...)-Block gewickelt werden, der den unbekannten Fehler behandelt und die Ausnahme verschwinden lässt (auf keinen Fall per throw; erneut wirft). (Es reicht nicht, einfach einen catch(...)-Block irgendwo weiter außen zu haben, sobald du weiter innen einen anderen catch-Block hast – der könnte dann ja fälschlicherweise angesteuert werden.) Es ist unmöglich, mit Ausnahmen Information über Modulgrenzen hinweg zu transportieren. Sie sind quasi dein Ereignishorizont.

Der Minimalfall klappt meiner Meinung nach nur, weil zufälligerweise beide Module so ähnliche Typen nutzen, dass die Typinformation in beiden Modulen gleich ist. Das ist glücklicher Zufall, mehr nicht. Du kannst mit deiner Anwendung eigentlich noch froh sein, dass innerhalb des Prozessmoduls nichts gefangen wird – würde gefangen, dann wahrscheinlich mit einem falschen catch-Block und von einer bloßen Zugriffsverletzung zu einem komplexen Symptom inkonsistenten Stapelspeichers könnte im Grunde alles passieren.

Wie gesagt – bezieht sich auf Windows; es gibt ja jede Menge anderer Ausnahmemodelle, die dein System implementieren könnte. Aber ich finde es unwahrscheinlich, dass es überhaupt irgendwo durch mehr als Glück klappt und nicht bloß eine riesige Zeitbombe ist.

Gruß, Ky

Re: Exceptions und Shared Objects

Verfasst: 30.05.2011, 16:24
von dot
Krishty hat geschrieben:Zumindest unter Windows ist das unmöglich [...]
Ich würde sagen das ist rein prinzipiell unmöglich da es für exceptions keinen Binärstandard gibt und weder .so noch .dll mehr als einfache C-Style Interfaces unterstützen. Aber gerade unter Windows bietet das Betriebssystem einen plattformspezifischen Weg um mit Exceptions umzugehen...

Re: Exceptions und Shared Objects

Verfasst: 30.05.2011, 17:32
von dawit
Mist. Nachdem ich das hier gelesen hatte, dachte ich, es wäre kein großes Ding, das zum Laufen zu kriegen.
Habe ich das richtig verstanden, dass zur Laufzeit nicht erkannt wird, dass der Typ der geworfenen Exception und der zu fangenden Exception übereinstimmen bzw. dass sie wirklich nicht übereinstimmen, wenn sie von std::exception ableiten und unterschiedliche Standardbibliotheken gelinkt wurden, und dass somit die Exception bestenfalls nicht gefangen wird?
Sollte man also, wenn man sich nicht nur auf sein Glück verlassen möchte, alle Exceptions abfangen, bevor sie das Modul verlassen, und dann mit Return-Codes weitermachen, wie es auch hier vorgeschlagen wurde?

Re: Exceptions und Shared Objects

Verfasst: 30.05.2011, 17:56
von Krishty
dawit hat geschrieben:Mist. Nachdem ich das hier gelesen hatte, dachte ich, es wäre kein großes Ding, das zum Laufen zu kriegen.
You must do this because even if (e.g.) the exception type's implementation code lives in DLL A, when DLL B throws an instance of that type, the catch handler in DLL C will look for the typeinfo in DLL B.
Krass. Sie grasen wirklich die Typinformation aller geladenen Module ab, wenn sie eine Behandlung suchen. Ausnahmen über Modulgrenzen sind dort also tatsächlich möglich.
The upshot of this is that if you forget your preprocessor defines in just one object file, or if at any time a throwable type is not declared explicitly public, the -fvisibility=hidden will cause it to be marked hidden in that object file, which overrides all the other definitions with default visibility and causes the typeinfo to vanish in the outputted binary (which then causes any throws of that type to cause terminate() to be called in the catching binary). Your binaries will link perfectly and appear to work correctly, even though they don't.
Falls du die Ausnahmen tatsächlich durchziehen willst, würde ich mit dem Durchkämmen aller Dateien auf vergessene #defines anfangen.

Persönlich würde ich aber alle Funktionen der Laufzeitbibliothek noexcept bzw. throw() dekorieren und die Fehlerbehandlung durch Rückgabewerte realisieren, wie im Thread. War ja auch meiner :)

Re: Exceptions und Shared Objects

Verfasst: 30.05.2011, 18:26
von dawit
Falls du die Ausnahmen tatsächlich durchziehen willst, würde ich mit dem Durchkämmen aller Dateien auf vergessene #defines anfangen.
Im Moment exportiere ich standardmäßig alle Symbole (-fvisibility=default), weshalb ich denke, dass es an etwas anderem liegen muss.

Aber wie Vorgeschlagen werden ich wohl an den Modulgrenzen auf Exceptions verzichten (das ganze soll schließlich Platformunbabhängig sein), wobei ich trotzdem gerne wüsste, was der Fehler ist.
Danke schon mal für die Hilfe!

Re: Exceptions und Shared Objects

Verfasst: 14.06.2011, 20:31
von dawit
So, nach kurzer Pause aufgrund mangelnder Motivation habe ich mir dass ganze noch einmal angesehen und versteh's immer noch nicht:
  • Exceptions aus dem Konstruktor einer Klasse des Moduls werden im Hauptprogramm problemlos gefangen (aber wirklich nur aus dem Konstruktor oder aus dort aufgerufenen Funktionen)
  • Sonstige geworfene Exceptions werden nicht einmal von einem catch(...) { } gefangen.
  • Bei nicht gefangenen Exceptions wird nicht einmal terminate() aufgerufen (das Programm wird einfach beendet)
Google: lang gesucht, nix gefunden.
Hat von euch zufällig jemand eine Idee?

Re: Exceptions und Shared Objects

Verfasst: 15.06.2011, 00:06
von dot
Ich kann nur wiederholen: Exceptions funktionieren über Modulgrenzen nicht, zumindest nicht portabel und es ist absolut keine gute Idee an sowas auch nur ansatzweise zu denken. Shared Objects und DLLs unterstützen nur pure C-Interfaces und das lässt sich schon rein aus Prinzip nicht ändern.

Re: Exceptions und Shared Objects

Verfasst: 15.06.2011, 10:12
von dawit
Ich kann nur wiederholen: Exceptions funktionieren über Modulgrenzen nicht, zumindest nicht portabel und es ist absolut keine gute Idee an sowas auch nur ansatzweise zu denken.
Ja, ich hatte auch nicht vor das wirklich zu benutzen, mir gings nur ums Prinzip, da ich den oben verlinkten Artikel so verstanden hatte, dass, zumindest unter Linux, Exceptions theoretisch möglich sind.

Der Fehler war übrigens im Destruktor, der angeforderte Objekte wieder freigab. Beim Wurf der Exception waren diese Objekte noch nicht initialisiert und so stürzte das Programm beim Aufruf des Destrukors ab, weshalb die Exception auch nicht gefangen werden konnte. Wird die Exception aber aus dem Konstruktor geworfen, so wird der Destruktor nicht aufgerufen, das Programm stürzt nicht ab und die Exception kann gefangen werden.