c++来自未处理异常的堆栈跟踪?

发布于 2024-09-12 12:33:56 字数 319 浏览 8 评论 0原文

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

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

This question has been asked before and there have been windows-specific answers but no satisfactory gcc answer. I can use set_terminate() to set a function that will be called (in place of terminate()) when an unhandled exception is thrown. I know how to use the backtrace library to generate a stack trace from a given point in the program. However, this won't help when my terminate-replacement is called since at that point the stack has been unwound.

Yet if I simply allow the program to abort(), it will produce a core-dump which contains the full stack information from the point at which the exception was thrown. So the information is there -- but is there a programmatic way to get it, for example so it can be logged, rather than having to examine a core file?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

缪败 2024-09-19 12:34:24

您可以使用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);
}

}

关于这个问题的好文章

You can use libunwind (just add -lunwind to linker parameters) (tested with 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);
}

}

There is good article concerning the issue.

终陌 2024-09-19 12:34:20

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

我怀疑我的经验是否适合您的需求,但无论如何,我的经验就在这里。

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

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

Yet if I simply allow the program to abort(), it will produce a core-dump which contains the full stack information from the point at which the exception was thrown. So the information is there -- but is there a programmatic way to get it, for example so it can be logged, rather than having to examine a core file?

I doubt my experience would fit your needs but here it goes anyway.

I was overloading abort(): either by adding my own object file before the libc or using LD_PRELOAD. In my own version of abort() I was starting the debugger telling it to attach to the process (well, I surely know my PID) and dump the stack trace into a file (commands were passed to the debugger via command line). After debugger had finished, terminate the process with e.g. _exit(100).

That was on Linux using GDB. On Solaris I routinely employ similar trick but due to unavailability of a sane debugger I use the pstack tool: system("pstack <PID>").

左耳近心 2024-09-19 12:34:17

编辑答案:

您可以使用 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 使用 execinfo.hbacktrace 注入堆栈跟踪> 进入抛出的异常。

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

Edited Answer:

You can use 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();
}

giving this output:

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

assuming you have debug symbols in your binary, you can then use addr2line to construct a prettier stack trace postmortem

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

original answer is below


I've done this in the past using boost::error_info to inject the stack trace using backtrace from execinfo.h into an exception that is thrown.

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

Then when catching the exceptions, you can do

} catch ( const std::exception& e ) {                                                                                                            
    if ( std::string const *stack boost::get_error_info<stack_error_info>(e) ) {                    
        std::cout << stack << std::endl;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文