如何找到在C ++中抛出异常的位置?

我有一个程序在某处引发一个未捕获的异常。 我所得到的只是一个抛出异常的报告,并没有关于抛出异常的信息。 编译为包含调试符号的程序似乎不合逻辑,不会通知我在代码中生成异常的位置。

有没有什么办法可以告诉我哪些异常是由于在gdb中设置'catch throw'而导致的,并且为每个抛出的异常调用了一个回溯?


以下是可能用于调试问题的一些信息

如果一个异常未被捕获,则自动调用特殊库函数std::terminate() 。 Terminate实际上是一个指向函数的指针,默认值是标准C库函数std::abort() 。 如果未发生未捕获的异常†,则可能实际上有助于调试此问题,因为不会调用析构函数。
†在std::terminate()被调用之前,是否实现定义堆栈是否展开。


调用abort()通常可用于生成可以分析以确定异常原因的核心转储。 确保您通过ulimit -c unlimited (Linux)启用核心转储。


你可以使用std::set_terminate()来安装你自己的terminate()函数。 你应该可以在gdb的终止函数中设置一个断点。 您可能能够从terminate()函数生成堆栈回溯,并且此回溯可以帮助识别异常的位置。

在Bruce Eckel的Thinking in C ++第二版中有一个关于未捕获的异常的简短讨论,这可能也会有所帮助。


由于terminate()默认调用abort() (默认情况下会导致SIGABRT信号),因此您可以设置SIGABRT处理程序,然后从信号处理程序中打印堆栈回溯。 该回溯可以帮助识别异常的位置。


注:我说可能是因为C ++通过使用语言结构来分隔错误处理和报告代码与普通代码的支持非本地错误处理。 catch块可以并且通常位于与投掷点不同的功能/方法中。 我还在评论中指出了这一点(感谢Dan),它是实现定义了在调用terminate()之前是否展开堆栈。

更新:我把一个Linux测试程序放在一起调用,它通过set_terminate()在一个terminate()函数集中产生一个回溯,而在另一个SIGABRT的信号处理程序中产生一个回溯。 两个回溯都正确显示未处理的异常的位置。

更新2:感谢关于在终止内捕获未捕获的异常的博客文章,我学到了一些新的技巧; 包括在终止处理程序中重新抛出未捕获的异常。 需要注意的是,自定义终止处理程序中的空throw语句适用于GCC,并不是一个便携式解决方案。

码:

#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 << " framesnn";

    // 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 << " framesnn";

    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);
}

输出:

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]


正如你所说,我们可以在gdb中使用'catch throw',并为每个抛出的异常调用'backtrace'。 虽然手动执行通常太繁琐,但gdb允许进程的自动化。 这允许看到抛出的所有异常的回溯,包括最后未被捕获的异常:

GDB>

set pagination off
catch throw
commands
backtrace
continue
end
run

如果没有进一步的手动干预,这会产生大量的回溯,其中包括最后一个未捕获的异常:

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.

这里有一篇很棒的博客文章:http://741mhz.com/throw-stacktrace/


您可以创建一个宏,如:

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

...它会给你抛出异常的位置(当然不是堆栈跟踪)。 你需要从一些接受上述构造函数的基类中派生你的异常。

链接地址: http://www.djcxy.com/p/86043.html

上一篇: How do I find where an exception was thrown in C++?

下一篇: C++ display stack trace on exception