mijin2/source/mijin/debug/stacktrace.cpp

146 lines
3.4 KiB
C++

#include "./stacktrace.hpp"
#include <optional>
#include <string>
#include "../detect.hpp"
#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC
#define MIJIN_USE_LIBBACKTRACE 1
#else
#define MIJIN_USE_LIBBACKTRACE 0
#endif
#if MIJIN_USE_LIBBACKTRACE
#include <backtrace.h>
#endif
namespace mijin
{
namespace
{
//
// internal defines
//
//
// internal constants
//
//
// internal types
//
struct BacktraceData
{
std::optional<std::string> error;
std::vector<Stackframe> stackframes;
};
//
// internal variables
//
thread_local Optional<Stacktrace> gCurrentExceptionStackTrace;
//
// internal functions
//
#if MIJIN_USE_LIBBACKTRACE
int backtraceFullCallback(void* data, std::uintptr_t programCounter, const char* filename, int lineno, const char* function)
{
BacktraceData& btData = *static_cast<BacktraceData*>(data);
btData.stackframes.push_back({
.address = reinterpret_cast<void*>(programCounter),
.filename = filename ? filename : "<unknown>",
.function = function ? function : "<unknown>",
.lineNumber = lineno
});
return 0;
}
void backtraceErrorCallback(void* data, const char* msg, int /* errnum */)
{
BacktraceData& btData = *static_cast<BacktraceData*>(data);
btData.error = msg;
}
thread_local backtrace_state* gBacktraceState = nullptr;
#endif // MIJIN_USE_LIBBACKTRACE
} // namespace
//
// public functions
//
Result<Stacktrace> captureStacktrace(unsigned skipFrames) noexcept
{
#if MIJIN_USE_LIBBACKTRACE
BacktraceData btData;
if (gBacktraceState == nullptr)
{
gBacktraceState = backtrace_create_state(/*filename = */ nullptr, /* threaded = */ false, &backtraceErrorCallback, &btData);
}
if (btData.error.has_value())
{
return ResultError(std::move(*btData.error));
}
if (gBacktraceState == nullptr)
{
return ResultError("Error initializing libbacktrace.");
}
backtrace_full(gBacktraceState, static_cast<int>(skipFrames) + 1, &backtraceFullCallback, &backtraceErrorCallback, &btData);
if (btData.error.has_value())
{
return ResultError(std::move(*btData.error));
}
return Stacktrace(std::move(btData.stackframes));
#else // MIJIN_USE_LIBBACKTRACE
(void) skipFrames;
return {}; // TODO
#endif // MIJIN_USE_LIBBACKTRACE
}
const Optional<Stacktrace>& getExceptionStacktrace() noexcept
{
return gCurrentExceptionStackTrace;
}
} // namespace mijin
#if MIJIN_STDLIB == MIJIN_STDLIB_GLIBC
#include <dlfcn.h>
namespace
{
using cxa_throw_type = void (*)(void*, std::type_info*, void(*)(void*));
using cxa_rethrow_type = void (*)();
cxa_throw_type orig_cxa_throw = nullptr; // Address of the original __cxa_throw
// cxa_rethrow_type orig_cxa_rethrow = nullptr; // Address of the original __cxa_rethrow
extern "C" void __cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*))
{
if (!orig_cxa_throw) {
orig_cxa_throw = reinterpret_cast<cxa_throw_type>(dlsym(RTLD_NEXT, "__cxa_throw"));
}
if (mijin::Result<mijin::Stacktrace> stacktrace = mijin::captureStacktrace(); stacktrace.isSuccess())
{
mijin::gCurrentExceptionStackTrace = std::move(*stacktrace);
}
else
{
mijin::gCurrentExceptionStackTrace.reset();
}
if (orig_cxa_throw) {
orig_cxa_throw(thrown_exception, tinfo, dest);
}
else {
std::abort();
}
}
}
#endif