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

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

问题描述

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



有没有办法告诉我的异常来自于在gdb中设置catch throw,并为每个抛出的异常调用回溯。

解决方案

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



如果未捕获异常,则特殊库函数 std :: terminate() 被自动调用。终止实际上是一个指向函数的指针,默认值是标准C库函数 的std ::中止() 。如果对于未捕获的异常不发生清除,则可能有助于调试此问题,因为不调用析构函数。

†在 std :: terminate()被调用之前,它是否被实现定义。






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






您可以使用 terminate()函数/ error / set_terminaterel =nofollow noreferrer> std :: set_terminate() 。您应该能够在gdb中的终止函数上设置断点。您可以从 terminate()函数中生成堆栈回溯,并且此回溯可能有助于识别



有关 Bruce Eckel在C ++中的思考,第二版中的rel =nofollow noreferrer>未捕获的异常 Ed 也可能有帮助。






由于 terminate()调用默认情况下abort()默认情况下将导致 SIGABRT 信号),您可以可以设置 SIGABRT 处理程序,然后从信号处理程序内打印一个堆栈回溯。






注意:可以帮助您确定异常的位置。 我说可能,因为C ++通过使用语言结构来支持非本地错误处理,以将错误处理和报告代码与普通代码分开。捕获块可以并且经常位于与投掷点不同的功能/方法中。在我的评论中也提到了这一点(感谢 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);

命名空间{
//调用set_terminate作为全局常量初始化的一部分
static const bool SET_TERMINATE = std :: set_terminate(my_terminate);
}

//这个结构镜像在/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;

//从EIP(x86)中获取信号时的地址
void * caller_address =(void *)uc-> uc_mcontext.eip;

std :: cerr<<< 信号< sig_num
<< (< strsignal(sig_num)<),地址是
< info-> si_addr<<< from
<< caller_address<<的std :: ENDL;

void * array [50];
int size = backtrace(array,50);

std :: cerr<<< __FUNCTION__<< 返回追溯
<<尺寸<< frames\\\
\\\
;

//用调用者的地址覆盖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(EXIT_FAILURE);
}

void my_terminate(){
static bool trial_throw = false;

尝试{
//尝试一次重新抛出当前活动的异常
if(!尝试_throw ++)throw;
}
catch(const std :: exception& e){
std :: cerr<< __FUNCTION__<< ():
<< e.what()<的std :: ENDL;
}
catch(...){
std :: cerr<<< __FUNCTION__<< 捕获未知/未处理的异常。
<<的std :: ENDL;
}

void * array [50];
int size = backtrace(array,50);

std :: cerr<<< __FUNCTION__<< 返回追溯
<<尺寸<< frames\\\
\\\
;

char ** messages = backtrace_symbols(array,size); (int i = 0; i< size&&& message!= NULL; ++ i)

{
std :: cerr< [bt]:(< i,)<消息[i]<<的std :: ENDL;
}
std :: cerr<<<的std :: ENDL;

免费(消息);

abort();
}

int throw_exception(){
//抛出未处理的运行时错误
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< 信号的错误设置处理程序< SIGABRT
<< (<< strsignal(SIGABRT)<)\\\
;
exit(EXIT_FAILURE);
}

foo1();

exit(EXIT_SUCCESS);
}

输出:

 
my_terminate捕获未处理的异常。 what():RUNTIME ERROR!
my_terminate backtrace返回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从0x42029331
crit_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\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]

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

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