diff --git a/LibConf b/LibConf index 4658eae..c4e55eb 100644 --- a/LibConf +++ b/LibConf @@ -3,6 +3,7 @@ Import('env') mijin_sources = Split(""" source/mijin/async/coroutine.cpp + source/mijin/debug/stacktrace.cpp source/mijin/debug/symbol_info.cpp source/mijin/io/process.cpp source/mijin/io/stream.cpp @@ -12,15 +13,18 @@ mijin_sources = Split(""" source/mijin/virtual_filesystem/stacked.cpp """) -env.UnityStaticLibrary( +lib_libbacktrace = env.Cook('libbacktrace') + +lib_mijin = env.UnityStaticLibrary( target = env['LIB_DIR'] + '/mijin', - source = mijin_sources + source = mijin_sources, + dependencies = [lib_libbacktrace] ) LIB_CONFIG = { 'CPPPATH': [env.Dir('source')], 'CPPDEFINES': [], - 'LIBS': ['mijin'] + 'DEPENDENCIES': [lib_mijin] } if env['BUILD_TYPE'] == 'debug': diff --git a/source/mijin/debug/stacktrace.cpp b/source/mijin/debug/stacktrace.cpp new file mode 100644 index 0000000..36f9fab --- /dev/null +++ b/source/mijin/debug/stacktrace.cpp @@ -0,0 +1,88 @@ + +#include "./stacktrace.hpp" + +#include +#include +#include +#include + +namespace mijin +{ +namespace +{ +// +// internal defines +// + +// +// internal constants +// + +// +// internal types +// + +struct BacktraceData +{ + std::optional error; + std::vector stackframes; +}; + +// +// internal variables +// + +// +// internal functions +// + +int backtraceFullCallback(void* data, std::uintptr_t programCounter, const char* filename, int lineno, const char* function) +{ + BacktraceData& btData = *static_cast(data); + btData.stackframes.push_back({ + .address = reinterpret_cast(programCounter), + .filename = filename ? filename : "", + .function = function ? function : "", + .lineNumber = lineno + }); + return 0; +} + +void backtraceErrorCallback(void* data, const char* msg, int /* errnum */) +{ + BacktraceData& btData = *static_cast(data); + btData.error = msg; +} + +backtrace_state* gBacktraceState = nullptr; +} // namespace + +// +// public functions +// + +Result captureStacktrace(unsigned skipFrames) noexcept +{ + 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(skipFrames) + 1, &backtraceFullCallback, &backtraceErrorCallback, &btData); + if (btData.error.has_value()) + { + return ResultError(std::move(*btData.error)); + } + + return Stacktrace(std::move(btData.stackframes)); +} + +} // namespace mijin diff --git a/source/mijin/debug/stacktrace.hpp b/source/mijin/debug/stacktrace.hpp new file mode 100644 index 0000000..3d750dd --- /dev/null +++ b/source/mijin/debug/stacktrace.hpp @@ -0,0 +1,85 @@ + +#pragma once + +#if !defined(MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED) +#define MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED 1 + +#include +#include +#include +#include "./symbol_info.hpp" +#include "../types/result.hpp" +#include "../util/iterators.hpp" + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +struct Stackframe +{ + void* address; + std::string filename; + std::string function; + int lineNumber; +}; + +class Stacktrace +{ +private: + std::vector frames_; +public: + Stacktrace() = default; + Stacktrace(const Stacktrace&) = default; + Stacktrace(Stacktrace&&) = default; + explicit Stacktrace(std::vector frames) noexcept : frames_(std::move(frames)) {} + + Stacktrace& operator=(const Stacktrace&) = default; + Stacktrace& operator=(Stacktrace&&) = default; + + [[nodiscard]] const std::vector& getFrames() const noexcept { return frames_; } +}; + +// +// public functions +// + +[[nodiscard]] Result captureStacktrace(unsigned skipFrames = 0) noexcept; + +template +TStream& operator<<(TStream& stream, const Stackframe& stackframe) +{ + stream << "[" << stackframe.address << "] " << stackframe.filename << ":" << stackframe.lineNumber << " in " << demangleCPPIdentifier(stackframe.function.c_str()); + + return stream; +} + +template +TStream& operator<<(TStream& stream, const Stacktrace& stacktrace) +{ + const int oldWidth = stream.width(); + const std::ios::fmtflags oldFlags = stream.flags(); + const int numDigits = static_cast(std::ceil(std::log10(stacktrace.getFrames().size()))); + stream << std::left; + for (const auto& [idx, frame] : mijin::enumerate(stacktrace.getFrames())) + { + stream << " #" << std::setw(numDigits) << idx << std::setw(0) << " at " << frame << "\n"; + } + stream << std::setw(oldWidth); + stream.flags(oldFlags); + return stream; +} + +} // namespace mijin + +#endif // !defined(MIJIN_DEBUG_STACKTRACE_HPP_INCLUDED) diff --git a/source/mijin/debug/symbol_info.cpp b/source/mijin/debug/symbol_info.cpp index fb6312b..86e321a 100644 --- a/source/mijin/debug/symbol_info.cpp +++ b/source/mijin/debug/symbol_info.cpp @@ -75,12 +75,12 @@ std::string demangleCPPIdentifier(const char* identifier) std::free(demangled); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) return name; } - return ""; + return identifier; } #else -std::string demangleCPPIdentifier(const char* /* identifier */) +std::string demangleCPPIdentifier(const char* identifier) { - return ""; + return identifier; } #endif diff --git a/source/mijin/types/result.hpp b/source/mijin/types/result.hpp new file mode 100644 index 0000000..5dcef34 --- /dev/null +++ b/source/mijin/types/result.hpp @@ -0,0 +1,75 @@ + +#pragma once + +#if !defined(MIJIN_TYPES_RESULT_HPP_INCLUDED) +#define MIJIN_TYPES_RESULT_HPP_INCLUDED 1 + +#include +#include + +namespace mijin +{ + +// +// public defines +// + +// +// public constants +// + +// +// public types +// + +template +class ResultBase +{ +private: + struct Empty {}; + + std::variant state_; +public: + ResultBase() = default; + ResultBase(const ResultBase&) = default; + ResultBase(ResultBase&&) = default; + ResultBase(TSuccess successValue) noexcept : state_(std::move(successValue)) {} + ResultBase(TError errorValue) noexcept : state_(std::move(errorValue)) {} + + ResultBase& operator=(const ResultBase&) = default; + ResultBase& operator=(ResultBase&&) = default; + + bool operator==(const ResultBase& other) const noexcept { return state_ == other.state_; } + bool operator!=(const ResultBase& other) const noexcept { return state_ != other.state_; } + operator bool() const noexcept { return isSuccess(); } + bool operator!() const noexcept { return !isSuccess(); } + TSuccess& operator*() noexcept { return getValue(); } + const TSuccess& operator*() const noexcept { return getValue(); } + TSuccess* operator->() noexcept { return &getValue(); } + const TSuccess* operator->() const noexcept { return &getValue(); } + + [[nodiscard]] bool isEmpty() const noexcept { return std::holds_alternative(state_); } + [[nodiscard]] bool isSuccess() const noexcept { return std::holds_alternative(state_); } + [[nodiscard]] bool isError() const noexcept { return std::holds_alternative(state_); } + + [[nodiscard]] TSuccess& getValue() noexcept { return std::get(state_); } + [[nodiscard]] TError& getError() noexcept { return std::get(state_); } + [[nodiscard]] const TSuccess& getValue() const noexcept { return std::get(state_); } + [[nodiscard]] const TError& getError() const noexcept { return std::get(state_); } +}; + +struct ResultError +{ + std::string message; +}; + +template +using Result = ResultBase; + +// +// public functions +// + +} // namespace mijin + +#endif // !defined(MIJIN_TYPES_RESULT_HPP_INCLUDED)