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

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

问题描述

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

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

解决方案

这里有一些信息可能在调试你的问题时有用

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


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


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

未捕获的异常有一个简短的讨论在Bruce Eckel 在 C++ 中的思考,第 2 版,这可能也会有所帮助.


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


注意:我说可能是因为 C++ 通过使用语言结构将错误处理和报告代码与普通代码分开来支持非本地错误处理.catch 块可以并且通常位于与抛出点不同的函数/方法中.在评论中也向我指出(感谢 Dan)无论堆栈是否是实现定义的在调用 terminate() 之前展开.

更新:我拼凑了一个名为的 Linux 测试程序,该程序在通过 set_terminate() 设置的 terminate() 函数中生成回溯,并且SIGABRT 的信号处理程序中的另一个.两个回溯都正确显示了未处理异常的位置.

更新 2: 感谢 在 terminate 中捕获未捕获的异常,我学到了一些新技巧;包括在终止处理程序中重新抛出未捕获的异常.需要注意的是,自定义终止处理程序中的空 throw 语句适用于 GCC,而不是可移植的解决方案.

代码:

#ifndef _GNU_SOURCE#define _GNU_SOURCE#万一#ifndef __USE_GNU#define __USE_GNU#万一#include <execinfo.h>#include <signal.h>#include <string.h>#include <iostream>#include <cstdlib>#include <stdexcept>无效我的终止(无效);命名空间{//调用 set_terminate 作为全局常量初始化的一部分static const bool SET_TERMINATE = std::set_terminate(my_terminate);}//这个结构反映了/usr/include/asm/ucontext.h 中的结构typedef 结构 _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 <<信号"<<sig_num<<"("<<strsignal(sig_num)<<"),地址是"<<info->si_addr <<"来自<<caller_address <<std::endl;无效*数组[50];int size = backtrace(array, 50);std::cerr <<__FUNCTION__ <<"回溯返回<<尺寸 <<"帧

";//用调用者的地址覆盖 sigaction数组[1] = caller_address;char ** messages = backtrace_symbols(array, size);//跳过第一个堆栈帧(点这里)for (int i = 1; i < size && messages != NULL; ++i) {std::cerr <<"[bt]:(<<i<<")"<<消息[i] <<std::endl;}std::cerr <<std::endl;免费(消息);退出(EXIT_FAILURE);}无效 my_terminate() {静态布尔尝试_抛出=假;尝试 {//尝试一次重新抛出当前活动的异常if (!tried_throw++) 抛出;}catch (const std::exception &e) {std::cerr <<__FUNCTION__ <<"捕获未处理的异常.什么():<<e.what() <<std::endl;}抓住 (...) {std::cerr <<__FUNCTION__ <<"捕获未知/未处理的异常."<<std::endl;}无效*数组[50];int size = backtrace(array, 50);std::cerr <<__FUNCTION__ <<"回溯返回<<尺寸 <<"帧

";char ** messages = backtrace_symbols(array, size);for (int i = 0; i < size && messages != NULL; ++i) {std::cerr <<"[bt]:(<<i<<")"<<消息[i] <<std::endl;}std::cerr <<std::endl;免费(消息);中止();}int throw_exception() {//抛出一个未处理的运行时错误throw std::runtime_error("RUNTIME ERROR!");返回0;}int foo2() {抛出异常();返回0;}int foo1() {foo2();返回0;}int main(int argc, char ** argv) {结构 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 <<信号的错误设置处理程序"<<SIGABRT<<"("<<strsignal(SIGABRT)<<")
";退出(EXIT_FAILURE);}foo1();退出(EXIT_SUCCESS);}

输出:

<上一页>my_terminate 捕获了未经处理的异常.什么():运行时错误!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天全站免登陆