【问题标题】:c++ stack trace from unhandled exception?来自未处理异常的 C++ 堆栈跟踪?
【发布时间】:2023-03-14 21:46:01
【问题描述】:

这个问题以前被问过,并且有特定于 windows 的答案,但没有令人满意的 gcc 答案。我可以使用set_terminate() 设置一个函数,当抛出未处理的异常时将调用该函数(代替terminate())。我知道如何使用回溯库从程序中的给定点生成堆栈跟踪。但是,当调用我的终止替换时,这将无济于事,因为此时堆栈已被展开。

然而,如果我只是允许程序abort(),它将产生一个核心转储,其中包含从引发异常的点开始的完整堆栈信息。所以信息就在那里——但是有没有一种程序化的方式来获取它,例如它可以被记录,而不是必须检查一个核心文件?

【问题讨论】:

标签: c++ exception-handling stack-trace callstack unhandled-exception


【解决方案1】:

编辑答案:

您可以使用std::set_terminate

#include <cstdlib>
#include <iostream>
#include <stdexcept>

#include <execinfo.h>

void
handler()
{
    void *trace_elems[20];
    int trace_elem_count(backtrace( trace_elems, 20 ));
    char **stack_syms(backtrace_symbols( trace_elems, trace_elem_count ));
    for ( int i = 0 ; i < trace_elem_count ; ++i )
    {
        std::cout << stack_syms[i] << "\n";
    }
    free( stack_syms );

    exit(1);
}   

int foo()
{
    throw std::runtime_error( "hello" );
}   

void bar()
{
    foo();
}

void baz()
{
    bar();
}

int
main()
{
    std::set_terminate( handler );
    baz();
}

给出这个输出:

samm@macmini ~> ./a.out 
./a.out [0x10000d20]
/usr/lib/libstdc++.so.6 [0xf9bb8c8]
/usr/lib/libstdc++.so.6 [0xf9bb90c]
/usr/lib/libstdc++.so.6 [0xf9bbaa0]
./a.out [0x10000c18]
./a.out [0x10000c70]
./a.out [0x10000ca0]
./a.out [0x10000cdc]
/lib/libc.so.6 [0xfe4dd80]
/lib/libc.so.6 [0xfe4dfc0]
samjmill@bgqfen4 ~> 

假设您的二进制文件中有调试符号,然后您可以使用 addr2line 构造一个更漂亮的堆栈跟踪事后

samm@macmini ~> addr2line 0x10000c18
/home/samm/foo.cc:23
samm@macmini ~> 

原答案如下


我过去曾使用boost::error_info 将堆栈跟踪使用backtraceexecinfo.h 注入到引发的异常中。

typedef boost::error_info<struct tag_stack_str,std::string> stack_info;

那么当捕获异常时,你可以这样做

} catch ( const std::exception& e ) {                                                                                                            
    if ( std::string const *stack boost::get_error_info<stack_error_info>(e) ) {                    
        std::cout << stack << std::endl;
    }
}

【讨论】:

  • 是的,正如我所说,我知道如何在当前点获取跟踪。显然,如果我将它存储到每个异常中,那就是这样。但问题假设我没有这样做——也许是因为懒惰,也许是因为我无权访问源代码。
  • 由于未捕获异常,堆栈展开后,您希望堆栈跟踪看起来像什么?
  • 就像我在不拦截 terminate() 的情况下将 gdb 应用到核心文件时得到的堆栈跟踪一样——正如我的问题中明确指出的那样。
  • @c-urchin 我认为终止处理程序是在展开之前调用的,不是吗?在这种情况下,没有魔法(投掷不会放松,事实是那里有东西可以接住它)。如果你要抓住它 - 你会失去那个回溯。还有关于 addr2line - 它非常有用 - 我曾经在事后脚本中使用它并格式化输出,使其更具可读性。

    最后一点......如果你只是用 -rdynamic 编译,你就不需要 sddr2line ,虽然使用 addr2line 你可以对 C++ 进行解构,所以那里也有很多好处

  • Sam,你的示例程序是邪恶的。它改变了你的用户名和主机名;-)
【解决方案2】:

然而,如果我只是让程序中止(),它将产生一个核心转储,其中包含从引发异常的点开始的完整堆栈信息。所以信息就在那里——但是有没有一种程序化的方式来获取它,例如它可以被记录,而不是必须检查一个核心文件?

我怀疑我的经验是否能满足您的需求,但无论如何都可以。

我正在重载abort():通过在 libc 之前添加我自己的目标文件或使用 LD_PRELOAD。在我自己的abort() 版本中,我正在启动调试器,告诉它附加到进程(好吧,我肯定知道我的PID)并将堆栈跟踪转储到文件中(命令通过命令行传递给调试器)。调试器完成后,使用例如终止进程_exit(100).

那是在使用 GDB 的 Linux 上。在 Solaris 上,我经常使用类似的技巧,但由于没有健全的调试器,我使用 pstack 工具:system("pstack &lt;PID&gt;")

【讨论】:

    【解决方案3】:

    您可以使用libunwind(只需将-lunwind 添加到链接器参数)(使用clang++ 3.6 测试):

    demagle.hpp:

    #pragma once
    
    char const *
    get_demangled_name(char const * const symbol) noexcept;
    

    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:

    #pragma once
    #include <ostream>
    
    void
    backtrace(std::ostream & _out) noexcept;
    

    backtrace.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:

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

    good article与此问题有关。

    【讨论】:

    • 不是真的:just add -lunwind to linker parameters。您需要先在系统上安装该库。
    • @AleksanderFular 当然,这是暗示的。
    猜你喜欢
    • 2017-05-08
    • 1970-01-01
    • 2017-08-06
    • 1970-01-01
    • 2013-09-25
    • 2012-09-21
    • 2015-06-23
    • 2011-01-05
    • 2013-07-31
    相关资源
    最近更新 更多