I have a program that throws an uncaught exception somewhere. All I get is a report of an exception being thrown, and no information as to where it was thrown. It seems illogical for a program compiled to contain debug symbols not to notify me of where in my code an exception was generated.
Is there any way to tell where my exceptions are coming from short of setting 'catch throw' in gdb and calling a backtrace for every single thrown exception?
If an exception is uncaught, the special library function std::terminate()
is automatically called. Terminate is actually a pointer to a function and default value is the Standard C library function std::abort()
. If no cleanups occur for an uncaught exception†, it may actually be helpful in debugging this problem as no destructors are called.
†It is implementation-defined whether or not the stack is unwound before std::terminate()
is called.
A call to abort()
is often useful in generating a core dump that can be analyzed to determine the cause of the exception. Make sure that you enable core dumps via ulimit -c unlimited
(Linux).
You can install your own terminate()
function by using std::set_terminate()
. You should be able to set a breakpoint on your terminate function in gdb. You may be able to generate a stack backtrace from your terminate()
function and this backtrace may help in identifying the location of the exception.
There is a brief discussion on uncaught exceptions in Bruce Eckel's Thinking in C++, 2nd Ed that may be helpful as well.
Since terminate()
calls abort()
by default (which will cause a SIGABRT
signal by default), you may be able to set a SIGABRT
handler and then print a stack backtrace from within the signal handler. This backtrace may help in identifying the location of the exception.
Note: I say may because C++ supports non-local error handling through the use of language constructs to separate error handling and reporting code from ordinary code. The catch block can be, and often is, located in a different function/method than the point of throwing. It has also been pointed out to me in the comments (thanks Dan) that it is implementation-defined whether or not the stack is unwound before terminate()
is called.
Update: I threw together a Linux test program called that generates a backtrace in a terminate()
function set via set_terminate()
and another in a signal handler for SIGABRT
. Both backtraces correctly show the location of the unhandled exception.
Update 2: Thanks to a blog post on Catching uncaught exceptions within terminate, I learned a few new tricks; including the re-throwing of the uncaught exception within the terminate handler. It is important to note that the empty throw
statement within the custom terminate handler works with GCC and is not a portable solution.
Code:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <execinfo.h>
#include <signal.h>
#include <string.h>
#include <iostream>
#include <cstdlib>
#include <stdexcept>
void my_terminate(void);
namespace {
// invoke set_terminate as part of global constant initialization
static const bool SET_TERMINATE = std::set_terminate(my_terminate);
}
// This structure mirrors the one found in /usr/include/asm/ucontext.h
typedef struct _sig_ucontext {
unsigned long uc_flags;
struct ucontext *uc_link;
stack_t uc_stack;
struct sigcontext uc_mcontext;
sigset_t uc_sigmask;
} sig_ucontext_t;
void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) {
sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;
// Get the address at the time the signal was raised from the EIP (x86)
void * caller_address = (void *) uc->uc_mcontext.eip;
std::cerr << "signal " << sig_num
<< " (" << strsignal(sig_num) << "), address is "
<< info->si_addr << " from "
<< caller_address << std::endl;
void * array[50];
int size = backtrace(array, 50);
std::cerr << __FUNCTION__ << " backtrace returned "
<< size << " frames\n\n";
// overwrite sigaction with caller's address
array[1] = caller_address;
char ** messages = backtrace_symbols(array, size);
// skip first stack frame (points here)
for (int i = 1; i < size && messages != NULL; ++i) {
std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
}
std::cerr << std::endl;
free(messages);
exit(EXIT_FAILURE);
}
void my_terminate() {
static bool tried_throw = false;
try {
// try once to re-throw currently active exception
if (!tried_throw++) throw;
}
catch (const std::exception &e) {
std::cerr << __FUNCTION__ << " caught unhandled exception. what(): "
<< e.what() << std::endl;
}
catch (...) {
std::cerr << __FUNCTION__ << " caught unknown/unhandled exception."
<< std::endl;
}
void * array[50];
int size = backtrace(array, 50);
std::cerr << __FUNCTION__ << " backtrace returned "
<< size << " frames\n\n";
char ** messages = backtrace_symbols(array, size);
for (int i = 0; i < size && messages != NULL; ++i) {
std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
}
std::cerr << std::endl;
free(messages);
abort();
}
int throw_exception() {
// throw an unhandled runtime error
throw std::runtime_error("RUNTIME ERROR!");
return 0;
}
int foo2() {
throw_exception();
return 0;
}
int foo1() {
foo2();
return 0;
}
int main(int argc, char ** argv) {
struct sigaction sigact;
sigact.sa_sigaction = crit_err_hdlr;
sigact.sa_flags = SA_RESTART | SA_SIGINFO;
if (sigaction(SIGABRT, &sigact, (struct sigaction *)NULL) != 0) {
std::cerr << "error setting handler for signal " << SIGABRT
<< " (" << strsignal(SIGABRT) << ")\n";
exit(EXIT_FAILURE);
}
foo1();
exit(EXIT_SUCCESS);
}
Output:
my_terminate caught unhanded exception. what(): RUNTIME ERROR! my_terminate backtrace returned 10 frames [bt]: (0) ./test(my_terminate__Fv+0x1a) [0x8048e52] [bt]: (1) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa] [bt]: (2) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5] [bt]: (3) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf] [bt]: (4) ./test(throw_exception__Fv+0x68) [0x8049008] [bt]: (5) ./test(foo2__Fv+0xb) [0x8049043] [bt]: (6) ./test(foo1__Fv+0xb) [0x8049057] [bt]: (7) ./test(main+0xc1) [0x8049121] [bt]: (8) ./test(__libc_start_main+0x95) [0x42017589] [bt]: (9) ./test(__eh_alloc+0x3d) [0x8048b21] signal 6 (Aborted), address is 0x1239 from 0x42029331 crit_err_hdlr backtrace returned 13 frames [bt]: (1) ./test(kill+0x11) [0x42029331] [bt]: (2) ./test(abort+0x16e) [0x4202a8c2] [bt]: (3) ./test [0x8048f9f] [bt]: (4) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa] [bt]: (5) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5] [bt]: (6) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf] [bt]: (7) ./test(throw_exception__Fv+0x68) [0x8049008] [bt]: (8) ./test(foo2__Fv+0xb) [0x8049043] [bt]: (9) ./test(foo1__Fv+0xb) [0x8049057] [bt]: (10) ./test(main+0xc1) [0x8049121] [bt]: (11) ./test(__libc_start_main+0x95) [0x42017589] [bt]: (12) ./test(__eh_alloc+0x3d) [0x8048b21]