Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I find where an exception was thrown in C++?

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?

like image 453
alexgolec Avatar asked Mar 14 '10 18:03

alexgolec


People also ask

How do you find out where an exception is thrown?

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.

Where does the exception are handled?

An Exception is an unwanted event that interrupts the normal flow of the program. When an exception occurs program execution gets terminated. In such cases we get a system generated error message. The good thing about exceptions is that they can be handled in Java.

Is there exception handling in C?

The C programming language does not support exception handling nor error handling. It is an additional feature offered by C. In spite of the absence of this feature, there are certain ways to implement error handling in C. Generally, in case of an error, most of the functions either return a null value or -1.

Where does execution resume after an exception has been thrown and caught?

the execution resumes where the exception is caught, that is at the beginning of the catch block which specifically address the current exception type. the catch block is executed, the other catch blocks are ignored (think of multiple catch block as a switch statement).


1 Answers

Here's some info that may be of use in debugging your problem

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]  
like image 113
jschmier Avatar answered Oct 10 '22 02:10

jschmier