views:

712

answers:

5

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?

A: 

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
reason for downvote?
Ben Voigt
Probably because "let me know if you want an outline" is not a useful answer. But _Unwind_Backtrace is; compensating.
Thomas
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
A: 

Check this thread, perhaps it helps:

http://stackoverflow.com/questions/276102/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.

nabulke
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
+2  A: 

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
-1 You do not `throw new excation(...)` but `throw exception(...)` C++ is not Java,
Artyom
Okay, I fixed it. Forgive a programmer that works in both Java and C++ maybe?
Erik Hermansen
+7  A: 

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

If an exception is uncaught, the special library function terminate() is automatically called. Terminate is actually a pointer to a function and default value is the Standard C library function 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 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.

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 <exception>

// 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()
{
    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(int)
{
    // throw an unhandled exception
    throw 0;
    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);
    }

    std::set_terminate(my_terminate);

    foo1();

    exit(EXIT_SUCCESS);
}

Output:

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
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
I suspected the same, which is what prompted me to write the test.
jschmier
Dan
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
"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
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
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
+1  A: 

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
he said gdb, which pretty much rules out Windows/Visual Studio.
Ben Voigt
Well he says he'd like something "short of gdb", but he does not explicitly refer to any OS/Compiler. Thats the problem of people not declaring such stuff.
RED SOFT ADAIR