Wednesday 11 March 2009

hijacking throw

Here's continuation about internals of g++ throw mechanism.

Recent post showed how to effectively detect any exception type at runtime. It lacked one important information - where was the exception thrown. Who have made this.

Who is responsible for "bad argument", "logic_error" or anything. Who? Who nows?

On g++ exceptions are raised using __cxa_throw call. This is is runtime call that given given exception object (and some stuff) starts searching for exception handler and starts unwinding stack. Eventually it aborts if no handler was found.

How about hijacking this call? In gnu tool chain it's easy. __cxa_throw resides in libstc++.so. Lazy linkage and dlopen permits us to ovoerride __cxa_throw and the load original version from appropriate libstdc++.so. Well look at source code:

#include <typeinfo>
#include <stdexcept>
#include <iostream>
#include <dlfcn.h>

typedef void (*cxa_throw_type)(void* , void *, void (*) (void *));
cxa_throw_type orig_cxa_throw = 0;

void load_orig_throw_code()
{
 void* orig_libcxx = dlopen("/usr/lib64/libstdc++.so.6", RTLD_LAZY);
 std::cerr << "loaded orig libc++ " << orig_libcxx<< "\n";
 orig_cxa_throw = (cxa_throw_type)( dlsym(orig_libcxx, "__cxa_throw") );
 std::cerr << "loaded orig_cxa_throw " << (void*)orig_cxa_throw << "\n";
 dlclose(orig_libcxx);
}

extern "C" 
void __cxa_throw(void *thrown_exception, void *pvtinfo, void (*dest) (void *) ) {
 std::type_info const* tinfo = reinterpret_cast<std::type_info const*>(pvtinfo); 
 std::cerr << "YEAH! detected throw of exception of " << tinfo->name()<< "\n";
 if( orig_cxa_throw == 0 )
  load_orig_throw_code();
 orig_cxa_throw(thrown_exception, pvtinfo, dest);
}

void foo() {
 std::cerr << "throwing exception\n";
 throw std::runtime_error("akuku");
}

int main()
{
 try { foo(); }
 catch(std::exception& e) {
  std::cerr << "exception successfully caught at main\n";
 }
 return 0;
}
Voila. The output:
$ ./hijack
loaded orig libc++ 0x2a95587000
loaded orig_cxa_throw 0x34e35af1a0
throwing exception
YEAH! detected throw of exception of St13runtime_error
exception successfully caught at main
How there are some problems and things to do:
  • how the hell should we know what is the path to libstdc++.so (it can't be hardcoded as in this proof-of-concept)
  • we must use backtrace and some "debug info" retriever to show where is the exception throws
  • initialization of orig_cxa_throw must be more safe

But for now it works at least on 32&64 bit linuxes. I've got to test in on some production code.

4 comments:

Honggang said...

I am thinking the same thing, my original solution is to rewrite __cxa_throw function in glibc, but your hijack ideal is much better, thanks for sharing.

For backtrace information, I use following piece of code :
void
myfunc3(void)
{
int j, nptrs;
#define SIZE 100
void *buffer[100];
char **strings;

nptrs = backtrace(buffer, SIZE);
printf("backtrace() returned %d addresses\n", nptrs);

/* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
would produce similar output to the following: */

strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}

for (j = 0; j < nptrs; j++)
printf("%s\n", strings[j]);

free(strings);
}

The code comes from Linux man page, here is the link:
http://linux.die.net/man/3/backtrace

Unfortunately, backtrace() function is not supported in MIPS platform, which is our platform. It works fine on x86 platforms.

Cheers
Honggang

zbigg said...

Hello,

I also use backtrace but usually with signal handlers - you know at least program area that caused crash and it's useful when dealing with crashes that didn't left core file.

If you really need backtrace and your exception is "critical one" your should consider abort() which causes core dump if allowed.

Anonymous said...

You can use dlsym(RTLD_NEXT, "__cxa_throw") instead of requiring the path to libstdc++. This is also better because a minimal C++ application might use libsupc++ instead of libstdc++.

zbigg said...

@Anonymous, yeah you're absolutely right my production version of code shown in this blog is using RTLD_NEXT for long time i just forgot to update blog post (and yes i didn't know about this flag when i was writing this).

In fact, i've took this "hijacking" concept from this blog entry: http://technopark02.blogspot.com/2005/05/solaris-32-bits-fopen-and-max-number.html ... and author of this post already uses this RTLD_NEXT magic dlsym behaviour.

I wish woe32 DLL system had facility like this :/