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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2443135/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:29:58  来源:igfitidea点击:

How do I find where an exception was thrown in C++?

c++debuggingexception-handling

提问by Alex

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?

有什么方法可以告诉我的异常来自哪里,而没有在 gdb 中设置“catch throw”并为每个抛出的异常调用回溯?

采纳答案by jschmier

Here's some info that maybe 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 mayactually 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.

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



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).

对 的调用abort()通常可用于生成可以分析以确定异常原因的核心转储。确保通过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 maybe able to generate a stack backtrace from your terminate()function and this backtrace mayhelp in identifying the location of the exception.

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

There is a brief discussion on uncaught exceptionsin Bruce Eckel's Thinking in C++, 2nd Edthat may be helpful as well.

Bruce Eckel 的 Thinking in C++, 2nd Ed中对未捕获的异常进行了简短的讨论这也可能会有所帮助。



Since terminate()calls abort()by default (which will cause a SIGABRTsignal by default), you maybe able to set a SIGABRThandler and then print a stack backtrace from within the signal handler. This backtrace mayhelp in identifying the location of the exception.

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



Note:I say maybecause 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.

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

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.

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

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 throwstatement within the custom terminate handler works with GCC and is not a portable solution.

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

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]

回答by TimJ

As you say, we can use 'catch throw' in gdb and call 'backtrace' for every single thrown exception. While that's usually too tedious to do manually, gdb allows automation of the process. That allows seeing the backtrace of all exceptions that are thrown, including the last uncaught one:

正如您所说,我们可以在 gdb 中使用 'catch throw' 并为每个抛出的异常调用 'backtrace'。虽然这通常太繁琐而无法手动完成,但 gdb 允许该过程自动化。这允许查看抛出的所有异常的回溯,包括最后一个未捕获的异常:

gdb>

数据库>

set pagination off
catch throw
commands
backtrace
continue
end
run

Without further manual intervention, this generates lots of backtraces, including one for the last uncaught exception:

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

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.

Here's a great blog post wrapping this up: http://741mhz.com/throw-stacktrace[on archive.org]

这是一篇很棒的博客文章,对此进行了总结:http: //741mhz.com/throw-stacktrace[on archive.org]

回答by Erik Hermansen

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.

...它会给你抛出异常的位置(当然不是堆栈跟踪)。您有必要从采用上述构造函数的某个基类派生您的异常。

回答by Tomilov Anatoliy

You can mark main tight places in your code as noexceptto locate an exception, then use libunwind(just add -lunwindto linker parameters) (tested with clang++ 3.6):

您可以将代码中的主要紧缺位置标记为noexcept定位异常,然后使用libunwind(只需添加-lunwind到链接器参数)(使用 测试clang++ 3.6):

demagle.hpp:

demagle.hpp:

#pragma once

char const *
get_demangled_name(char const * const symbol) noexcept;

demangle.cpp:

demangle.cpp:

#include "demangle.hpp"

#include <memory>

#include <cstdlib>

#include <cxxabi.h>

namespace
{

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr< char, decltype(std::free) & > demangled_name{nullptr, std::free};
#pragma clang diagnostic pop

}

char const *
get_demangled_name(char const * const symbol) noexcept
{
    if (!symbol) {
        return "<null>";
    }
    int status = -4;
    demangled_name.reset(abi::__cxa_demangle(symbol, demangled_name.release(), nullptr, &status));
    return ((status == 0) ? demangled_name.get() : symbol);
}

backtrace.hpp:

回溯.hpp:

#pragma once
#include <ostream>

void
backtrace(std::ostream & _out) noexcept;

backtrace.cpp:

回溯.cpp:

#include "backtrace.hpp"

#include <iostream>
#include <iomanip>
#include <limits>
#include <ostream>

#include <cstdint>

#define UNW_LOCAL_ONLY
#include <libunwind.h>

namespace
{

void
print_reg(std::ostream & _out, unw_word_t reg) noexcept
{
    constexpr std::size_t address_width = std::numeric_limits< std::uintptr_t >::digits / 4;
    _out << "0x" << std::setfill('0') << std::setw(address_width) << reg;
}

char symbol[1024];

}

void
backtrace(std::ostream & _out) noexcept
{
    unw_cursor_t cursor;
    unw_context_t context;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    _out << std::hex << std::uppercase;
    while (0 < unw_step(&cursor)) {
        unw_word_t ip = 0;
        unw_get_reg(&cursor, UNW_REG_IP, &ip);
        if (ip == 0) {
            break;
        }
        unw_word_t sp = 0;
        unw_get_reg(&cursor, UNW_REG_SP, &sp);
        print_reg(_out, ip);
        _out << ": (SP:";
        print_reg(_out, sp);
        _out << ") ";
        unw_word_t offset = 0;
        if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) {
            _out << "(" << get_demangled_name(symbol) << " + 0x" << offset << ")\n\n";
        } else {
            _out << "-- error: unable to obtain symbol name for this frame\n\n";
        }
    }
    _out << std::flush;
}

backtrace_on_terminate.hpp:

backtrace_on_terminate.hpp:

#include "demangle.hpp"
#include "backtrace.hpp"

#include <iostream>
#include <type_traits>
#include <exception>
#include <memory>
#include <typeinfo>

#include <cstdlib>

#include <cxxabi.h>

namespace
{

[[noreturn]]
void
backtrace_on_terminate() noexcept;

static_assert(std::is_same< std::terminate_handler, decltype(&backtrace_on_terminate) >{});

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr< std::remove_pointer_t< std::terminate_handler >, decltype(std::set_terminate) & > terminate_handler{std::set_terminate(backtrace_on_terminate), std::set_terminate};
#pragma clang diagnostic pop

[[noreturn]]
void
backtrace_on_terminate() noexcept
{
    std::set_terminate(terminate_handler.release()); // to avoid infinite looping if any
    backtrace(std::clog);
    if (std::exception_ptr ep = std::current_exception()) {
        try {
            std::rethrow_exception(ep);
        } catch (std::exception const & e) {
            std::clog << "backtrace: unhandled exception std::exception:what(): " << e.what() << std::endl;
        } catch (...) {
            if (std::type_info * et = abi::__cxa_current_exception_type()) {
                std::clog << "backtrace: unhandled exception type: " << get_demangled_name(et->name()) << std::endl;
            } else {
                std::clog << "backtrace: unhandled unknown exception" << std::endl;
            }
        }
    }
    std::_Exit(EXIT_FAILURE); // change to desired return code
}

}

There is good articleconcerning the issue.

有关于这个问题的好文章

回答by RED SOFT ADAIR

You did not pass information about what OS / Compiler you use.

您没有传递有关您使用的操作系统/编译器的信息。

In Visual Studio C++ Exceptions can be instrumented.

在 Visual Studio C++ 中可以检测异常。

See "Visual C++ Exception-Handling Instrumentation"on ddj.com

请参阅 ddj.com 上的“Visual C++ 异常处理工具”

My article "Postmortem Debugging", also on ddj.com includes code to use Win32 structured exception handling (used by the instrumentation) for logging etc.

我的文章事后调试”,也在 ddj.com 上包含使用 Win32 结构化异常处理(由检测使用)进行日志记录等的代码。

回答by Ben Voigt

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.

我有在 Windows/Visual Studio 中执行此操作的代码,如果您需要大纲,请告诉我。虽然不知道如何为 dwarf2 代码做这件事,但一个快速的谷歌建议在 libgcc 中有一个函数 _Unwind_Backtrace 这可能是你需要的一部分。

回答by nabulke

Check this thread, perhaps it helps:

检查这个线程,也许它有帮助:

Catching all unhandled C++ exceptions?

捕获所有未处理的 C++ 异常?

I made good experiences with that software:

我使用该软件获得了很好的体验:

http://www.codeproject.com/KB/applications/blackbox.aspx

http://www.codeproject.com/KB/applications/blackbox.aspx

It can print out a stack trace to a file for any unhandled exception.

对于任何未处理的异常,它可以将堆栈跟踪打印到文件中。