c++ exceptions implementierung



  • hallo,

    weiß jemand, wie c++ exceptions implementiert sind?

    in windows bzw. mit dem microsoft c++ compiler schaut es ja ungefähr so aus, wenn ich mich recht erinnere:

    user mode programm (also "unsere" applikation) macht throw->soft irq->betriebssystem interrupt handler fängt interrupt->aufrufen des exception handlers des usermode programms->dort dann durchgehen aller handler jeder funktion bis passender handler gefunden wurde.

    weiß jemand ob das der regelfall ist, oder ob das auch ganz anders implementiert sein kann?
    hat jemand vielleicht eine ahnung, wie es sich unter windows ce verhält bzw. wie man das rausfinden kann?



  • fdfdsdfsfdsfsfsf schrieb:

    weiß jemand, wie c++ exceptions implementiert sind?

    Das ist eine Frage, die Du auch recht leicht hättest ergoogeln können. Erste zwei Treffer von Stackoverflow:

    http://stackoverflow.com/questions/490773/how-is-the-c-exception-handling-runtime-implemented
    http://stackoverflow.com/questions/1995734/how-are-exceptions-implemented-under-the-hood



  • Exceptions in C++ mittels throw unterscheidet sich von Exceptions/Traps in Betriebssystemen. Ein Aufruf von throw in C++ sollte kein Betriebssystemaufruf ausloesen. Es ist ganz normaler Programmcode wie if oder while ohne syscall.



  • wenn Du es etwas einfacher fuer den Einstieg haben willst: Lies Dir mal durch, wie setmp() und longjmp() in C funktionieren. Dann hast Du das Prinzip verstanden.

    In C++ kommen noch die Behandlung der Destruktoren fuer das werfende Objekt und die auf dem Stackframe liegenden Objekte dazu.





  • knivil schrieb:

    Exceptions in C++ mittels throw unterscheidet sich von Exceptions/Traps in Betriebssystemen. Ein Aufruf von throw in C++ sollte kein Betriebssystemaufruf ausloesen. Es ist ganz normaler Programmcode wie if oder while ohne syscall.

    MSVC verwendet SEH (ein Windows Feature) um C++ Exceptions zu implementieren.
    Macht auch mächtig Sinn.
    So kann man Exceptions "durch" alles durchwerfen was SEH unterstützt, und das ist auf Windows ziemlich viel.
    U.a. das OS selbst, d.h. C++ Exceptions aus einer Callback-Funktion durch die OS Funktion durch und im C++ Programm wieder gefangen = kein Problem.

    Genau so C++ Exception aus einer Callback-Funktion durch eine DLL in irgendeiner Sprache (die halt SEH unterstützt) durch und wieder zurück nach C++.

    Ist ne feine Sache.



  • knivil schrieb:

    Es ist ganz normaler Programmcode wie if oder while ohne syscall.

    Das Exception Handling der Itanium C++ ABI kannst du nicht mit normalem Programmcode nachprogrammieren.

    Wenn da eine Exception auftritt wird der Instruction Pointer ausgelesen und in einer vom Ausführungscode unabhängigen Tabelle gesucht. Da steht dann der Handlercode. Es ist nicht nur unendlich mühsam, diesen Zero-Cost-Ablauf nachzuprogrammieren, sondern mit Standardmitteln schlicht unmöglich.


  • Mod

    fbaetjased schrieb:

    Es ist nicht nur unendlich mühsam, diesen Zero-Cost-Ablauf nachzuprogrammieren, sondern mit Standardmitteln schlicht unmöglich.

    Unmöglich ist ein hartes Wort. Was daran könntest du denn nicht mit Standardmitteln nachprogrammieren?
    Instructionpointer auslesen = magic value im Code beim Werfen der Exception
    In einer vom Ausführungscode unabhängigen Tabelle suchen = Globaler Index der magic values
    Handlercode anspringen und zum Catch springen = normale Funktionsaufrufe, bei Bedarf longjmp

    Es ist nicht nur unendlich mühsam

    Das ist es wohl. Aber das kann ein Computerprogramm (Compiler) für uns erledigen.

    Zero-Cost-Ablauf

    Wären immer noch 0 Kosten, da die Verwaltungsdaten und Handlercode zur Compilezeit erzeugt wurden.



  • SeppJ schrieb:

    Unmöglich ist ein hartes Wort. Was daran könntest du denn nicht mit Standardmitteln nachprogrammieren?
    Instructionpointer auslesen = magic value im Code beim Werfen der Exception

    Über den Instructionpointer kommt Itanium auch an den Callstack (sehr unexakt formuliert).

    Wie programmierst du das hier nach?

    [[noinline]] void f(int i) {
      if (i == 0) throw 0;
    }
    
    void g(int i) {
      lock_guard a; // lock1();
      f(i);
      lock_guard b; // lock2();
      f(i-1);
      // unlock2();
      // unlock1();
    }
    


  • Was soll der ganze Mist. Das ist das C++ Forum ...

    Itanium C++ ABI

    Und fuer welche Prozessoren/Systeme gibt es denn noch eine C++ ABI und wie unterscheiden sie sich? Ist das im C++ Standard festgelegt. Wenn es spezifisch fuer Winows sein soll, ab ins Windowsforum ...

    Auch wird die Ausgangssituation vernachlaessigt:

    user mode programm (also "unsere" applikation) macht throw->soft irq ...

    wie es sich unter windows ce verhält bzw. wie man das rausfinden kann

    Ja, man kann die Dokumentation speziell fuer Windows CE lesen.



  • knivil schrieb:

    Und fuer welche Prozessoren/Systeme gibt es denn noch eine C++ ABI und wie unterscheiden sie sich?

    Das Itanium-C++-ABI ist nicht unbedingt Itanium-spezifisch. Von einigen IA64-spezifischen Parts abgesehen wird es auch von anderen Compilern implementiert oder zumindest als Leitlinie genommen. Und zwar von jedem mir bekannten C++-Compiler, von MSVC- und Windows-Krams abgesehen

    http://mentorembedded.github.io/cxx-abi/abi.html



  • @SeppJ
    Wie willst du ohne Compiler-Support die Tables erstellen die du brauchst um ohne das Pflegen von dynamischen "wo bin ich" Strukturen zur Runtime, einfach nur aus dem IP + Callstack zu ermitteln welcher Handler zuständig ist und wie das Unwinding ablaufen muss?

    In Assembler kann man das nachprogrammieren, ja (wenn auch EXTRAM mühsam). In C++ nicht, nichtmal mit Inline-Assembler.


  • Mod

    hustbaer schrieb:

    @SeppJ
    Wie willst du ohne Compiler-Support die Tables erstellen die du brauchst um ohne das Pflegen von dynamischen "wo bin ich" Strukturen zur Runtime,

    Ja, ich hatte da an einen Trick gedacht, der aber nicht funktioniert hat. 😞 Ich fürcht, es gehen nur die nicht-ganz-zero-cost Exceptions, wie man sie teilweise aus anderen Implementierungen kennt.



  • SeppJ schrieb:

    Ich fürcht, es gehen nur die nicht-ganz-zero-cost Exceptions, wie man sie teilweise aus anderen Implementierungen kennt.

    Das "unmöglich" war dadurch motiviert, dass die Fallbackimplementierung des GCCs das typische setjmp/longjmp ist. Wenns was besseres gäbe, hätten die das wahrscheinlich implementiert.

    Ignoriert man die Grösse des Binaries sowie Startupzeit, geht es fast:

    #include <iostream>
    #include <setjmp.h>
    
    void _throw_(int n);
    
    template <int N,
              int UniquePrime = 2>
    void f(int i) { 
      if (i ==  0) _throw_(UniquePrime*N);
      if (i == 10) _throw_(UniquePrime*UniquePrime*N);
    }
    
    void lock() { std::cout << "lock()\n"; }
    void unlock() { std::cout << "unlock()\n"; }
    
    void g_unwind1() { unlock(); std::cout << "leave g()\n"; }
    void g_unwind2() { unlock(); }
    
    template <int N,
              int UniquePrime = 3>
    void g(int i) { 
      std::cout << "enter g("<<i<<")\n";
      lock();
      f<N*UniquePrime>(i); 
      lock();
      f<N*UniquePrime*UniquePrime>(i-1);
      g_unwind2();
      g_unwind1();
    }
    
    static jmp_buf buf;
    
    int main()
    {
      switch (setjmp(buf)) { // catch hat einmaligen Overhead, Weil jedes
                             // Programm (mit Codeduplikation) so transformiert
                             // werden kann, dass das catch im main steht.
      case 1: goto i1;
      case 2: goto i2;
      case 3: goto i3;
      case 4: goto i4;
      case 5: goto i5;
      }
      g< 5>( 2); // try { g(); } catch(...) {}
     i1:
      g< 7>( 1); // try { g(); } catch(...) {}
     i2:
      g<11>( 0); // try { g(); } catch(...) {}
     i3:
      g<13>(11); // try { g(); } catch(...) {}
     i4:
      g<17>(10); // try { g(); } catch(...) {}
     i5:;
    }
    
    void _throw_(int n)
    {
      std::cout << n << '\n';
      switch (n) {
      case 2*3*3*7:    g_unwind2(); g_unwind1(); longjmp(buf, 2);
      case 2*3*11:     g_unwind1(); longjmp(buf, 3);
      case 2*2*3*3*13: g_unwind2(); g_unwind1(); longjmp(buf, 4);
      case 2*2*3*17:   g_unwind1(); longjmp(buf, 5);
      default: std::cerr << "Table not complete: " << n << '\n';
      }
    }
    
    enter g(2)
    lock()
    lock()
    unlock()
    unlock()
    leave g()
    enter g(1)
    lock()
    lock()
    126
    unlock()
    unlock()
    leave g()
    enter g(0)
    lock()
    66
    unlock()
    leave g()
    enter g(11)
    lock()
    lock()
    468
    unlock()
    unlock()
    leave g()
    enter g(10)
    lock()
    204
    unlock()
    leave g()
    

    Vollständig geht es nur, wenn Parameter+Stackvariablen in einem globalen Array abgelegt werden. In Anbetracht der oben ignorierten Nachteile würde ich aber immer noch als Zero-Cost bezeichnen.


Anmelden zum Antworten