Friday, 6 March 2009

catch(...) on g++

Ever wondered how to get information what was actually caught you enter catch(...) clause ?

With g++ (on some popular platforms) it's possible retrieve pointer to type_info record of base exception object type and what more important, pointer to exception object itself.

[Note, all information comes from http://www.codesourcery.com/public/cxx-abi/abi-eh.html]

Let's go ...

First, of all g++ runtime stores exception handling state in some structure that is different for each thread:

struct __cxa_eh_globals {
   __cxa_exception * caughtExceptions;
   unsigned int        uncaughtExceptions;
};

You can obtain pointer to instance via

extern "C" __cxa_eh_globals* __cxa_get_globals();
call.

Next, when you got this you can look at first exception that you have - it's the one currently handled (i assume you're still in catch(...) clause). The __cxa_exception structure is quite almost obvious:

struct __cxa_exception {
   std::type_info * exceptionType;
   void (*exceptionDestructor) (void *);
   void* unexpectedHandler;
   void* terminateHandler;
   __cxa_exception * nextException;

   int   handlerCount;
   int   handlerSwitchValue;
   const char *        actionRecord;
   const char * languageSpecificData;
   void *  catchTemp;
   void *  adjustedPtr;

   void*         unwindHeader;
};

Here most important member are:

adjustedPtr
pointer to exception object
exceptionType
type_info of exception object

Having that we can retrieve information for any type of exception object we know. std::exception - just call what(), std::string (yeah who throws them) - just print it, CORBA::exception, all messy Xerces exceptions ... whoa!

Sample proof of concept. Tested on following configurations

Meat:

#include <map>
#include <string>
#include <typeinfo>
#include <iostream>
#include <sstream>

// catch clause analysis

struct __cxa_exception { 
    std::type_info * exceptionType;
    void (*exceptionDestructor) (void *); 
    void* unexpectedHandler;
    void* terminateHandler;
    __cxa_exception * nextException;

    int   handlerCount;
    int   handlerSwitchValue;
    const char *        actionRecord;
    const char * languageSpecificData;
    void *  catchTemp;
    void *  adjustedPtr;

    void*         unwindHeader;
};

struct __cxa_eh_globals {
    __cxa_exception * caughtExceptions;
    unsigned int        uncaughtExceptions;
};

extern "C" __cxa_eh_globals* __cxa_get_globals();

// yet another rtti like attempt
struct extended_type_info {
    virtual const char* type_name() = 0;
    virtual std::string to_string(void*) = 0;
    
    virtual ~extended_type_info() {}
};

typedef std::map<std::string, extended_type_info*> handler_map;

template <typename Callable>
void execute(Callable c, handler_map const& exc_handlers)
{
    try {
        c();
    } catch( ... ) {
        __cxa_eh_globals* gp = __cxa_get_globals();
        
        if( gp == 0 && gp->caughtExceptions == 0 ) {
            std::cerr << "unknown exception caught (unable to find exception record)" << "\n";
        } else {
            __cxa_exception* cp = gp->caughtExceptions;
            std::type_info* t = cp->exceptionType;
            void* exc_obj_ptr = cp->adjustedPtr;
            std::string exc_name(t->name());
            handler_map::const_iterator ieh = exc_handlers.find(exc_name);
            if( ieh != exc_handlers.end() ) {
                extended_type_info* ti = ieh->second;
                std::cerr << "exception caught " << ti->type_name() << "(" << ti->to_string(exc_obj_ptr) << ")\n";
            } else {
                std::cerr << "exception caught: " << t->name() << ", no handler found\n";
            }                
        }
    }
}

struct int_extended_type_info: public extended_type_info  {
    const char* type_name() { return "int"; }
    std::string to_string(void* p) {
        int* v = reinterpret_cast<int*>(p);
        std::ostringstream ss;
        ss << *v;
        return ss.str();
    }
};

struct std_string_extended_type_info: public extended_type_info  {
    const char* type_name() { return "std::string"; }
    std::string to_string(void* p) {
        std::string* v = reinterpret_cast<std::string*>(p);
        return *v;
    }
};

template <typename T>
class singletonof {
public:
    static T* get_ptr() { return &instance; }
private:
    static T instance;
};

template <typename T>
T singletonof<T>::instance;

// the code

void akuku()
{
    throw 321;
}

void akuku_string()
{
    throw std::string("akuku");
}

int main(int argc, char** argv)
{
    handler_map hm;
    hm["i"] = singletonof<int_extended_type_info>::get_ptr();
    hm["Ss"] = singletonof<std_string_extended_type_info>::get_ptr();
    execute(&akuku, hm);
    execute(&akuku_string, hm);
}
And the output is:
$ ./execution_monitor.exe
exception caught int(321)
exception caught std::string(akuku)
Cool!

Update 1 [2009-03-06]: typo(sstream), added info about tested platforms

Update 2 [2009-03-09]: tested also on gcc/solaris 9