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

后端 未结 7 1265
[愿得一人]
[愿得一人] 2020-11-28 18:55

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 illo

相关标签:
7条回答
  • 2020-11-28 19:46

    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]
    
    
    0 讨论(0)
提交回复
热议问题