Reputation: 33
I want to print the backtrace of an exception (the state at the point is where was thrown), but in a catch block.
I am also just targetting gcc, but would like to keep unportable code to a minimum (I know the stack printing code I show in my examples is non-portable).
From what I understood so far, in the catch block the stack was already partially unwond, so it is no longer there to be read. To be fair, it should still be there (in memory), if no local variables are created and no method calls are done, but I'm not sure how safe I would be trying to read it this way.
I also noticed that if I register a terminate method (via std::set_terminate) and have no catch block, then the full stack at the point where the unhandled exception was thrown is available to the handler. I'm guessing this is because it was unwound totally, but the original values in the stack was not overwritten (and most likely the terminate handler somehow has an indepentent call stack).
I tested this under 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(); }
If I use it like this, the exception backtrace is lost (only main and handler are in the call stack):
void main()
{
try { level1(); }
catch(...) { handler();}
}
But if I call it like this, the backtrace at the point the exception was thrown is printed:
void main()
{
std::set_terminate(handler);
level1();
}
Some context on my use case: I have two running processes, one produces requests and the other will process them. The execution of these requests can sometimes result in exceptions. At that point the backtrace is usefull to figure out why it failed (what() very often does not have enough information), but I cannot let the exception reach main, since both processes need to continue working. I just need to print the backtrace and return a message signalling failure to execute.
EDIT: A common solution, that is being suggested to this problem is having a base exception class that captures the call stack on construction, and has it available to print out later. This is certainly a possible solution (and one I might have to resort to, if no better solution is found).
I am avoiding it at the moment because:
Upvotes: 1
Views: 1359
Reputation: 69892
Best I can suggest is to collect the stack trace at the point of the throw.
This code may need a little refining, but it should give you an idea:
#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;
}
}
Sample output:
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
Upvotes: 0
Reputation: 238351
You could create a custom exception class, that would in its constructor, call backtrace
and store the buffer within itself.
When caught, such exception would have the required data to print the trace. In fact, the handler could well be a member function of the exception. Note that the trace would include the extra call to the constructor of the exception.
This works as long as you only throw the custom exception or exceptions derived from it.
Upvotes: 1