Skip to content

Instantly share code, notes, and snippets.

@travisdowns
Created December 4, 2019 18:48
Show Gist options
  • Save travisdowns/eeb5845cd4c1c0380a896a1d50eb854d to your computer and use it in GitHub Desktop.
Save travisdowns/eeb5845cd4c1c0380a896a1d50eb854d to your computer and use it in GitHub Desktop.
/*
* cxxstacks.hpp
*/
#ifndef CXXSTACKS_HPP_
#define CXXSTACKS_HPP_
#include <string.h>
#include <string>
#include "backward-cpp/backward.hpp"
/**
* The maximum number of frames we will attempt to capture at exception time:
* frames beyond this will be omitted.
*/
#define CXXSTACKS_MAX_FRAMES 256
namespace cxxstack {
using namespace backward;
class TraceFormatter {
public:
virtual std::string formatFrame(size_t frame_num, const ResolvedTrace &rt) const = 0;
virtual ~TraceFormatter() {}
};
/**
* A default formatter that prints the stacks in some reasonable format.
*/
class DefaultFormatter : public TraceFormatter {
virtual std::string formatFrame(size_t frame_num, const ResolvedTrace &trace) const override {
(void)frame_num;
const std::string& function = trace.source.function.empty() ? trace.object_function : trace.source.function;
std::stringstream ss;
addFrame(ss, function, trace.source, trace.object_filename);
ss << " [" << trace.addr << "]" << std::endl;
for (const auto &loc : trace.inliners) {
addFrame(ss, loc.function, loc, trace.object_filename);
ss << " (inlined)" << std::endl;
}
return ss.str();
}
static void addFrame(std::stringstream& ss, const std::string& function, const ResolvedTrace::SourceLoc &loc, const std::string& binaryName) {
ss << "\tat " << abbreviateFunction(function) << formatSourceLoc(loc) << " in " << extractFilename(binaryName);
}
/**
* C++ function names get nasty long especially with templates parameters (which may themselves be templates, etc).
* Try to cut down on the length by removing the outermost template arguments.
*/
static std::string abbreviateFunction(const std::string &fname) {
if (fname.find('<') == std::string::npos) {
return fname;
}
std::string trimmed;
int nesting = 0;
for (char c : fname) {
if (c == '>') {
nesting--;
}
if (nesting == 0) {
trimmed += c;
}
if (c == '<') {
nesting++;
}
}
return trimmed;
}
static std::string extractFilename(const std::string &path) {
size_t last_slash = path.find_last_of('/');
return last_slash == std::string::npos ? path : path.substr(last_slash + 1);
}
static std::string formatSourceLoc(const ResolvedTrace::SourceLoc &loc) {
if (!loc.filename.empty() || loc.line != 0) {
return std::string(" (") + extractFilename(loc.filename) + ":" + std::to_string(loc.line) + ")";
} else {
return "";
}
}
};
template <typename B>
class stacked_exception : public B {
std::string name;
StackTrace st;
mutable char* whatmsg;
protected:
/**
* The name of this exception, by default the name of the underlying (base) exception class.
*/
virtual std::string exception_name() const noexcept {
return name;
}
public:
explicit stacked_exception(const std::string& msg, const std::string& name = "")
: B(msg), name{name}, whatmsg(nullptr)
{
st.load_here(CXXSTACKS_MAX_FRAMES);
}
virtual const char* what() const noexcept {
if (whatmsg) {
return whatmsg;
}
const std::string& name = exception_name();
std::string msg;
msg += (name.empty() ? "" : name + " : ") + B::what() + "\n";
msg += stack_trace();
try {
whatmsg = new char[msg.length() + 1];
memcpy(whatmsg, msg.c_str(), msg.length() + 1);
} catch (std::bad_alloc&) {
return "stacked_exception: allocation failed";
}
return whatmsg;
}
std::string stack_trace() const noexcept {
return stack_trace(DefaultFormatter{});
}
virtual std::string stack_trace(const TraceFormatter& formatter) const noexcept
{
TraceResolver resolver;
resolver.load_stacktrace(st);
std::string full_trace;
for (size_t i = 0; i < st.size(); ++i) {
ResolvedTrace trace = resolver.resolve(st[i]);
if (!skipFrame(trace)) {
full_trace += formatter.formatFrame(i, trace);
}
}
return full_trace;
}
/**
* We want to skip some frames:
* 1) The first few frames (the exact number depends on the optimization level) which are in the guts of
* the cxxstacks and backward implementation and don't have anything to do with user code.
* 2) The last frame which has IP = -1 and no other details (usually precedes _start).
*/
static bool skipFrame(const ResolvedTrace& t) {
for (auto impl_string : {"backward::details::unwind", "backward::StackTraceImpl", "cxxstack"}) {
if (t.object_function.find(impl_string) != std::string::npos) {
return true;
}
}
return t.addr == (void *)-1;
}
virtual ~stacked_exception() {
delete[] whatmsg;
}
};
struct logic_error : public stacked_exception<std::logic_error> {
explicit logic_error(const std::string& msg) : stacked_exception<std::logic_error>(msg, "std::logic_error") {}
};
} // namespace cxxstack
#endif /* CXXSTACKS_HPP_ */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment