110

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?

alexgolec
  • 26,898
  • 33
  • 107
  • 159
  • see http://stackoverflow.com/questions/2064468/exceptions-stacktrace – VolkerK Mar 14 '10 at 18:19
  • Catch the exception and see what the internal message is. Since it is good practice for an exception to be derived from one of the standard exceptions (std::runtime_error) you shoudl be able to catch it with catch(std::exception const& e) – Martin York Mar 14 '10 at 18:29
  • 1
    And std::exception/Std::runtime\_error solves the problem of finding out the "path" and origin of an exception? – VolkerK Mar 14 '10 at 18:33
  • 1
    As your question state gdb, I think your solution is already in SO : http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes I've used the solution described here and it works perfectly. – neuro Mar 15 '10 at 11:11
  • 2
    You should consider specifying the OS via a tag. Since you mention gdb, I would assume you are looking for a Linux solution and not Windows. – jschmier Mar 15 '10 at 18:50
  • @LokiAstari: In my code, I've determined my mystery exception (thrown by a third party library of course) is not caught by `const std::exception&` nor `const CException*`. Not sure how I continue. – Mooing Duck Apr 16 '15 at 18:52
  • @MooingDuck: Are you sure it is a C++ exception. Could it be a [structured exception](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680657%28v=vs.85%29.aspx) which is completely different things outside the language level. – Martin York Apr 16 '15 at 19:08
  • @LokiAstari: I caught it with a `catch(...)`, which I think excludes SEH. – Mooing Duck Apr 16 '15 at 19:12
  • You could try [std::current_exception](http://en.cppreference.com/w/cpp/error/current_exception) not sure how much help that is. Any documentation on your third party lib? – Martin York Apr 16 '15 at 19:17

7 Answers7

81

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]

jschmier
  • 15,458
  • 6
  • 54
  • 72
  • 1
    Very interesting. I always suspected that an unhandled exception would unwind the stack until it got to the top level (`main`) and *then* it would call `terminate()`. But your example shows that no unwinding is done at all, which is very cool. – Dan Mar 15 '10 at 18:59
  • I suspected the same, which is what prompted me to write the test. – jschmier Mar 15 '10 at 19:02
  • 6
    1) The `throw(int)` spec is unnecessary. 2) The `uc->uc_mcontext.eip` is probably very platform dependent (e.g., use `...rip` on a 64-bit platform). 3) Compile with `-rdynamic` so you get backtrace symbols. 4) Run `./a.out 2>&1 | c++filt` to get pretty backtrace symbols. – Dan Mar 15 '10 at 19:05
  • The `uc->uc_mcontext.eip` is platform specific (x86). See my answer here for details: http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes/1925461#1925461 – jschmier Mar 15 '10 at 19:14
  • 2
    "No cleanups occur for an uncaught exception." -- Actually, that is implementation-defined. See 15.3/9 and and 15.5.1/2 in the C++ spec. "In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before terminate() is called." Still, this is a great solution if your compiler supports it! – Dan Mar 24 '10 at 00:01
  • From GCC docs, just something to be aware of, not actually that helpful: "If you are having difficulty with uncaught exceptions and want a little bit of help debugging the causes of the core dumps, you can make use of a GNU extension, the verbose terminate handler." (In 3.4 and later, this is on by default) http://gcc.gnu.org/onlinedocs/libstdc++/manual/verbose_termination.html – Dan Mar 24 '10 at 06:38
  • From GCC docs, might be helpful in programatically demangling C++ names: `abi::__cxa_demangle()` is a new ABI-mandated entry point in the C++ runtime library for demangling and is used by the verbose terminate handler. http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html – jschmier Mar 29 '10 at 07:05
  • is there any way to get the exception's what() (if it's a std::exception)? – Philipp Sep 23 '11 at 15:04
  • Great write up. You may want to also give mention to the addr2line utility, which helps give more detailed, human-readable information, given an executable and an address. – arr_sea May 07 '13 at 17:25
  • 1
    `((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;` worked for my ARM target – stephen Dec 11 '15 at 18:23
  • 1
    A couple of notes: backtrace_symbols() does a malloc... so, you might want to pre-allocate a block of memory at startup, then deallocate it in my_terminate() just before calling backtrace_symbols() in the event you happen to be handling a std::bad_alloc() exception. Also, you can include and then use __cxa_demangle() to make something useful out of the mangled substring displayed between '(' and '+' in the output messages[] strings. – K Scott Piel Dec 28 '17 at 16:59
  • With GCC in a multi-threaded C++ application `std::terminate` is called after the stack is unwond. So you can't get a backtrace. It does work for single thread though. – Sogartar Jul 13 '18 at 20:55
  • This is a good answer, sadly the codeguru and networksomething links are dead – SimonC Jan 18 '22 at 15:52
  • @SimonC - I've updated the stale links on uncaught exceptions in Bruce Eckel's Thinking in C++, 2nd Ed. – jschmier May 18 '22 at 23:15
60

As you say, we can use 'catch throw' in gdb and call 'backtrace' for every single thrown exception. While that's usually too tedious to do manually, gdb allows automation of the process. That allows seeing the backtrace of all exceptions that are thrown, including the last uncaught one:

gdb>

set pagination off
catch throw
commands
backtrace
continue
end
run

Without further manual intervention, this generates lots of backtraces, including one for the last uncaught exception:

Catchpoint 1 (exception thrown), 0x00a30 in __cxa_throw () from libstdc++.so.6
#0  0x0da30 in __cxa_throw () from /usr/.../libstdc++.so.6
#1  0x021f2 in std::__throw_bad_weak_ptr () at .../shared_ptr_base.h:76
[...]
terminate called after throwing an instance of 'std::bad_weak_ptr'
  what():  bad_weak_ptr
Program received signal SIGABRT, Aborted.

Here's a great blog post wrapping this up: http://741mhz.com/throw-stacktrace [on archive.org]

szotsaki
  • 684
  • 7
  • 16
TimJ
  • 839
  • 7
  • 6
20

You can create a macro like:

#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )

...and it will give you the location where the exception is thrown (admittedly not the stack trace). It's necessary for you to derive your exceptions from some base class that takes the above constructor.

Erik Hermansen
  • 2,200
  • 3
  • 21
  • 41
  • 22
    -1 You do not `throw new excation(...)` but `throw exception(...)` C++ is not Java, – Artyom Mar 14 '10 at 19:53
  • 8
    Okay, I fixed it. Forgive a programmer that works in both Java and C++ maybe? – Erik Hermansen Mar 16 '10 at 18:01
  • While I have used this. The problem with it is that it doesn't tell what actually threw the exception. If for example you have 5 stoi calls in a try block you won't know which one actually is the culprit. – Banjocat Jan 30 '15 at 15:46
5

You can mark main tight places in your code as noexcept to locate an exception, then use libunwind (just add -lunwind to linker parameters) (tested with clang++ 3.6):

demagle.hpp:

#pragma once

char const *
get_demangled_name(char const * const symbol) noexcept;

demangle.cpp:

#include "demangle.hpp"

#include <memory>

#include <cstdlib>

#include <cxxabi.h>

namespace
{

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr< char, decltype(std::free) & > demangled_name{nullptr, std::free};
#pragma clang diagnostic pop

}

char const *
get_demangled_name(char const * const symbol) noexcept
{
    if (!symbol) {
        return "<null>";
    }
    int status = -4;
    demangled_name.reset(abi::__cxa_demangle(symbol, demangled_name.release(), nullptr, &status));
    return ((status == 0) ? demangled_name.get() : symbol);
}

backtrace.hpp:

#pragma once
#include <ostream>

void
backtrace(std::ostream & _out) noexcept;

backtrace.cpp:

#include "backtrace.hpp"

#include <iostream>
#include <iomanip>
#include <limits>
#include <ostream>

#include <cstdint>

#define UNW_LOCAL_ONLY
#include <libunwind.h>

namespace
{

void
print_reg(std::ostream & _out, unw_word_t reg) noexcept
{
    constexpr std::size_t address_width = std::numeric_limits< std::uintptr_t >::digits / 4;
    _out << "0x" << std::setfill('0') << std::setw(address_width) << reg;
}

char symbol[1024];

}

void
backtrace(std::ostream & _out) noexcept
{
    unw_cursor_t cursor;
    unw_context_t context;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    _out << std::hex << std::uppercase;
    while (0 < unw_step(&cursor)) {
        unw_word_t ip = 0;
        unw_get_reg(&cursor, UNW_REG_IP, &ip);
        if (ip == 0) {
            break;
        }
        unw_word_t sp = 0;
        unw_get_reg(&cursor, UNW_REG_SP, &sp);
        print_reg(_out, ip);
        _out << ": (SP:";
        print_reg(_out, sp);
        _out << ") ";
        unw_word_t offset = 0;
        if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) {
            _out << "(" << get_demangled_name(symbol) << " + 0x" << offset << ")\n\n";
        } else {
            _out << "-- error: unable to obtain symbol name for this frame\n\n";
        }
    }
    _out << std::flush;
}

backtrace_on_terminate.hpp:

#include "demangle.hpp"
#include "backtrace.hpp"

#include <iostream>
#include <type_traits>
#include <exception>
#include <memory>
#include <typeinfo>

#include <cstdlib>

#include <cxxabi.h>

namespace
{

[[noreturn]]
void
backtrace_on_terminate() noexcept;

static_assert(std::is_same< std::terminate_handler, decltype(&backtrace_on_terminate) >{});

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr< std::remove_pointer_t< std::terminate_handler >, decltype(std::set_terminate) & > terminate_handler{std::set_terminate(backtrace_on_terminate), std::set_terminate};
#pragma clang diagnostic pop

[[noreturn]]
void
backtrace_on_terminate() noexcept
{
    std::set_terminate(terminate_handler.release()); // to avoid infinite looping if any
    backtrace(std::clog);
    if (std::exception_ptr ep = std::current_exception()) {
        try {
            std::rethrow_exception(ep);
        } catch (std::exception const & e) {
            std::clog << "backtrace: unhandled exception std::exception:what(): " << e.what() << std::endl;
        } catch (...) {
            if (std::type_info * et = abi::__cxa_current_exception_type()) {
                std::clog << "backtrace: unhandled exception type: " << get_demangled_name(et->name()) << std::endl;
            } else {
                std::clog << "backtrace: unhandled unknown exception" << std::endl;
            }
        }
    }
    std::_Exit(EXIT_FAILURE); // change to desired return code
}

}

There is good article concerning the issue.

Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169
3

You did not pass information about what OS / Compiler you use.

In Visual Studio C++ Exceptions can be instrumented.

See "Visual C++ Exception-Handling Instrumentation" on ddj.com

My article "Postmortem Debugging", also on ddj.com includes code to use Win32 structured exception handling (used by the instrumentation) for logging etc.

RED SOFT ADAIR
  • 12,032
  • 10
  • 54
  • 92
1

I've got code to do this in Windows/Visual Studio, let me know if you want an outline. Don't know how to do it for dwarf2 code though, a quick google suggests that there's a function _Unwind_Backtrace in libgcc that probably is part of what you need.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Probably because "let me know if you want an outline" is not a useful answer. But _Unwind_Backtrace is; compensating. – Thomas Mar 15 '10 at 10:07
  • On the basis that the OP mentioned gdb, I guessed that Windows wasn't relevant. Alex was, of course, free to edit his question to say Windows. – Ben Voigt Mar 15 '10 at 14:30
1

Check this thread, perhaps it helps:

Catching all unhandled C++ exceptions?

I made good experiences with that software:

http://www.codeproject.com/KB/applications/blackbox.aspx

It can print out a stack trace to a file for any unhandled exception.

Community
  • 1
  • 1
nabulke
  • 11,025
  • 13
  • 65
  • 114
  • I think the point is that Alex wants a stacktrace like `exception thrown foo.c@54, ..., re-thrown bar.c@54, ....` without having to do it manually. – VolkerK Mar 14 '10 at 18:16