在catch块

时间:2016-12-09 11:21:26

标签: c++ exception-handling stack-trace backtrace

我想打印异常的回溯(该点处的状态是抛出的位置),但是在catch块中。

我也只是针对gcc,但是希望将不可移植的代码保持在最低限度(我知道我在示例中显示的堆栈打印代码是不可移植的)。

据我所知,到目前为止,在catch区块中,堆栈已经部分不合适,因此不再需要读取。 公平地说,它应该仍然存在(在内存中),如果没有创建局部变量并且没有完成方法调用,但我不确定我会以这种方式读取它是多么安全。

我还注意到,如果我注册一个terminate方法(通过std :: set_terminate)并且没有catch块,那么处理程序可以使用抛出未处理异常的位置的完整堆栈。我猜这是因为它完全解开了,但堆栈中的原始值没有被覆盖(并且很可能终止处理程序以某种方式具有独立的调用堆栈。)

我在gcc下测试了这个:

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

#include <execinfo.h>

using namespace std;

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)
    {
        cout << stack_syms[i] << "\n";
    }
    free(stack_syms);

    exit(1);
}

void level3() { throw std::runtime_error("oops"); }
void level2() { level3(); }
void level1() { level2(); }

如果我像这样使用它,则会丢失异常回溯(只有main和handler在调用堆栈中):

void main()
{
    try { level1(); }
    catch(...) { handler();}
}

但如果我这样称呼它,那么抛出异常时的回溯就会被打印出来:

void main()
{
    std::set_terminate(handler);
    level1();
}

我的用例的一些上下文:我有两个正在运行的进程,一个生成请求,另一个将处理它们。执行这些请求有时会导致异常。在这一点上,回溯有助于弄清楚它失败的原因(what()经常没有足够的信息),但我不能让异常达到主要,因为两个进程都需要继续工作。我只需要打印回溯并返回一条消息,表示无法执行。

编辑:一个常见的解决方案就是建议解决这个问题,它有一个基本的异常类,可以在构造时捕获调用堆栈,并且可以在以后打印出来。这当然是一种可能的解决方案(如果找不到更好的解决方案,我可能不得不求助于此。)

我现在正在避免它,因为:

  1. 在我抛出异常
  2. 时,它会增加一些(相当大的)开销来捕获调用堆栈
  3. 我有相对较高比例的案例,我可以在放弃并打印堆栈跟踪之前实际处理异常。

2 个答案:

答案 0 :(得分:1)

您可以创建一个自定义异常类,在其构造函数中调用backtrace并将缓冲区存储在其自身内。

捕获时,此类异常将具有打印跟踪所需的数据。实际上,处理程序很可能是异常的成员函数。请注意,跟踪将包括对异常构造函数的额外调用。

只要您只抛出自定义异常或从中派生的异常,这就有效。

答案 1 :(得分:0)

我能建议的最好是在投掷时收集堆栈跟踪。

这段代码可能需要一些改进,但它应该给你一个想法:

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

#include <execinfo.h>

using namespace std;


inline
auto make_stack_trace(int depth = 20) {
    auto trace_elems = std::vector<void *>(depth);
    auto trace_elem_count = backtrace(trace_elems.data(), depth);
    char **stack_syms = backtrace_symbols(trace_elems.data(), trace_elem_count);

    std::vector<std::string> symbols;
    symbols.reserve(trace_elem_count);
    for (int i = 0; i < trace_elem_count; ++i) {
        symbols.emplace_back(stack_syms[i]);
    }
    free(stack_syms);

    return symbols;
}

struct tracer {
    tracer(const std::vector<std::string> &trace)
            : trace_(trace) {}

    friend std::ostream &operator<<(std::ostream &os, const tracer &t) {
        const char *sep = "";
        for (const auto &line : t.trace_) {
            os << sep << line;
            sep = "\n";
        }
        return os;
    }

    const std::vector<std::string> &trace_;
};

struct has_stack_trace {
    has_stack_trace(std::vector<std::string> trace) : trace_(std::move(trace)) {}

    auto trace() const {
        return tracer(trace_);
    }

    virtual const char* what() const noexcept = 0;

    std::vector<std::string> trace_;
};

template<class Exception>
struct with_stack_trace : has_stack_trace, Exception {
    template<class...Args>
    with_stack_trace(Args &&...args)
            : has_stack_trace(make_stack_trace()), Exception(std::forward<Args>(args)...) {
    }

    virtual const char* what() const noexcept override
    {
        return Exception::what();
    }

};


void level3() { throw with_stack_trace<std::runtime_error>("oops"); }

void level2() { level3(); }

void level1() { level2(); }


int main() {
    try {
        level1();
    }
    catch (has_stack_trace const &e) {
        std::cout << e.what() << std::endl;
        std::cout << e.trace() << std::endl;
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl;
    }
}

示例输出:

oops
0   parallel-find                       0x000000010bad09e9 _Z16make_stack_tracei + 105
1   parallel-find                       0x000000010bad08ec _ZN16with_stack_traceISt13runtime_errorEC2IJRA5_KcEEEDpOT_ + 44
2   parallel-find                       0x000000010bacf46d _ZN16with_stack_traceISt13runtime_errorEC1IJRA5_KcEEEDpOT_ + 29
3   parallel-find                       0x000000010bacf40a _Z6level3v + 42
4   parallel-find                       0x000000010bacf4a9 _Z6level2v + 9
5   parallel-find                       0x000000010bacf4b9 _Z6level1v + 9
6   parallel-find                       0x000000010bacf4d7 main + 23
7   libdyld.dylib                       0x00007fffa6346255 start + 1
8   ???                                 0x0000000000000001 0x0 + 1

Process finished with exit code 0