如何找到在 C++ 中抛出异常的位置? [英] How do I find where an exception was thrown in C++?

查看:22
本文介绍了如何找到在 C++ 中抛出异常的位置?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

如果没有在 gdb 中设置catch throw"并为每个抛出的异常调用回溯,有什么方法可以判断我的异常来自哪里?

解决方案

这里的一些信息可能对调试您的问题有用

如果异常未被捕获,特殊库函数std::terminate() 被自动调用.Terminate 实际上是一个指向函数的指针,默认值是标准 C 库函数 std::abort().如果未对未捕获的异常进行清理,它可能实际上有助于调试此问题,因为没有调用析构函数.
†​​在调用 std::terminate() 之前是否解开堆栈由实现定义.


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


您可以使用std::set_terminate().您应该能够在 gdb 中的终止函数上设置断点.您可能能够从您的 terminate() 函数生成一个堆栈回溯,这个回溯可能有助于识别异常的位置.

未捕获的异常有一个简短的讨论在Bruce Eckel's 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#万一#ifndef __USE_GNU#define __USE_GNU#万一#include #include #include #include #include #include void my_terminate(void);命名空间{//调用 set_terminate 作为全局常量初始化的一部分static const bool SET_TERMINATE = std::set_terminate(my_terminate);}//此结构反映了在/usr/include/asm/ucontext.h 中找到的结构typedef struct _sig_ucontext {无符号长 uc_flags;结构 ucontext *uc_link;stack_t uc_stack;结构 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;//获取从 EIP (x86) 发出信号时的地址void * caller_address = (void *) uc->uc_mcontext.eip;std::cerr <<信号"<<信号编号<<"("<<strsignal(sig_num)<<"),地址是"<<信息->si_addr<<"来自"<<呼叫者地址<

输出:

<前>my_terminate 捕获未处理的异常.what(): 运行时错误!my_terminate 回溯返回 10 帧[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]信号 6(中止),地址是 0x1239 从 0x42029331crit_err_hdlr 回溯返回 13 帧[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]

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?

解决方案

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

";

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

";

    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) << ")
";
        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]

这篇关于如何找到在 C++ 中抛出异常的位置?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆