From ce74915c147ee37a518df9ce5b44853700aa2b23 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Mon, 26 May 2025 20:15:20 +0200 Subject: [PATCH] feat: Add full exception tracing support --- .gitignore | 1 + CMakeLists.txt | 1 + cmake/build_helpers.cmake | 24 -------- lib/libimhex/CMakeLists.txt | 2 +- lib/trace/CMakeLists.txt | 55 +++++++++++++++++ lib/trace/include/hex/trace/exceptions.hpp | 11 ++++ .../trace/include/hex/trace}/stacktrace.hpp | 9 +-- lib/trace/source/exceptions.cpp | 27 +++++++++ lib/trace/source/instr_entry.cpp | 28 +++++++++ {main/gui => lib/trace}/source/stacktrace.cpp | 60 ++++++++++++++----- main/gui/CMakeLists.txt | 5 +- main/gui/include/window.hpp | 3 + main/gui/source/crash_handlers.cpp | 11 ++-- plugins/builtin/source/content/events.cpp | 9 +++ 14 files changed, 194 insertions(+), 52 deletions(-) create mode 100644 lib/trace/CMakeLists.txt create mode 100644 lib/trace/include/hex/trace/exceptions.hpp rename {main/gui/include => lib/trace/include/hex/trace}/stacktrace.hpp (73%) create mode 100644 lib/trace/source/exceptions.cpp create mode 100644 lib/trace/source/instr_entry.cpp rename {main/gui => lib/trace}/source/stacktrace.cpp (82%) diff --git a/.gitignore b/.gitignore index 3a04116a2..667c5d31f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ local/ venv/ .cache/ install/ +out/ *.mgc *.kdev4 diff --git a/CMakeLists.txt b/CMakeLists.txt index e0b467aa7..170167ffa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ addBundledLibraries() add_subdirectory(lib/libimhex) add_subdirectory(main) addPluginDirectories() +add_subdirectory(lib/trace) # Add unit tests if (IMHEX_ENABLE_UNIT_TESTS) diff --git a/cmake/build_helpers.cmake b/cmake/build_helpers.cmake index e6ab8a3e1..cacaea8c5 100644 --- a/cmake/build_helpers.cmake +++ b/cmake/build_helpers.cmake @@ -868,30 +868,6 @@ macro(addBundledLibraries) find_package(mbedTLS 3.4.0 REQUIRED) find_package(Magic 5.39 REQUIRED) - - if (NOT IMHEX_DISABLE_STACKTRACE) - if (WIN32) - message(STATUS "StackWalk enabled!") - set(LIBBACKTRACE_LIBRARIES DbgHelp.lib) - else () - find_package(Backtrace) - if (${Backtrace_FOUND}) - message(STATUS "Backtrace enabled! Header: ${Backtrace_HEADER}") - - if (Backtrace_HEADER STREQUAL "backtrace.h") - set(LIBBACKTRACE_LIBRARIES ${Backtrace_LIBRARY}) - set(LIBBACKTRACE_INCLUDE_DIRS ${Backtrace_INCLUDE_DIR}) - add_compile_definitions(BACKTRACE_HEADER=<${Backtrace_HEADER}>) - add_compile_definitions(HEX_HAS_BACKTRACE) - elseif (Backtrace_HEADER STREQUAL "execinfo.h") - set(LIBBACKTRACE_LIBRARIES ${Backtrace_LIBRARY}) - set(LIBBACKTRACE_INCLUDE_DIRS ${Backtrace_INCLUDE_DIR}) - add_compile_definitions(BACKTRACE_HEADER=<${Backtrace_HEADER}>) - add_compile_definitions(HEX_HAS_EXECINFO) - endif() - endif() - endif() - endif() endmacro() function(enableUnityBuild TARGET) diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index 7c99e9687..ec00836c6 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -170,7 +170,7 @@ if (NOT IMHEX_EXTERNAL_PLUGIN_BUILD) precompileHeaders(libimhex "${CMAKE_CURRENT_SOURCE_DIR}/include") endif() -target_link_libraries(libimhex ${LIBIMHEX_LIBRARY_TYPE} ${NLOHMANN_JSON_LIBRARIES} imgui_all_includes ${FMT_LIBRARIES} ${LUNASVG_LIBRARIES} ${BOOST_LIBRARIES}) +target_link_libraries(libimhex ${LIBIMHEX_LIBRARY_TYPE} ${NLOHMANN_JSON_LIBRARIES} imgui_all_includes ${FMT_LIBRARIES} ${LUNASVG_LIBRARIES} ${BOOST_LIBRARIES} tracing) set_property(TARGET libimhex PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE) diff --git a/lib/trace/CMakeLists.txt b/lib/trace/CMakeLists.txt new file mode 100644 index 000000000..ec78c2e90 --- /dev/null +++ b/lib/trace/CMakeLists.txt @@ -0,0 +1,55 @@ +project(tracing) + +option(IMHEX_TRACE_EXCEPTIONS "Hook thrown exceptions to display a stack trace when possible" ON) + +add_library(tracing OBJECT + source/stacktrace.cpp + source/exceptions.cpp +) +target_include_directories(tracing PUBLIC include) + +if (NOT IMHEX_DISABLE_STACKTRACE) + include(CheckSourceRuns) + set(CMAKE_REQUIRED_LINK_OPTIONS "-lstdc++exp") + check_source_runs(CXX " + #include + int main() { + auto stacktrace = std::stacktrace::current(); + } + " HAVE_STDCPPEXP) + if (HAVE_STDCPPEXP) + target_link_libraries(tracing PRIVATE stdc++exp) + endif() + + if (WIN32) + message(STATUS "StackWalk enabled!") + target_link_libraries(tracing PRIVATE DbgHelp.lib) + else () + find_package(Backtrace) + if (${Backtrace_FOUND}) + message(STATUS "Backtrace enabled! Header: ${Backtrace_HEADER}") + + if (Backtrace_HEADER STREQUAL "backtrace.h") + target_link_libraries(tracing PRIVATE ${Backtrace_LIBRARY}) + target_include_directories(tracing PRIVATE ${Backtrace_INCLUDE_DIR}) + target_compile_definitions(tracing PRIVATE BACKTRACE_HEADER=<${Backtrace_HEADER}>) + target_compile_definitions(tracing PRIVATE HEX_HAS_BACKTRACE) + elseif (Backtrace_HEADER STREQUAL "execinfo.h") + target_link_libraries(tracing PRIVATE ${Backtrace_LIBRARY}) + target_include_directories(tracing PRIVATE ${Backtrace_INCLUDE_DIR}) + target_compile_definitions(tracing PRIVATE BACKTRACE_HEADER=<${Backtrace_HEADER}>) + target_compile_definitions(tracing PRIVATE HEX_HAS_EXECINFO) + endif() + endif() + endif() + + target_link_libraries(tracing PRIVATE LLVMDemangle) +endif() + +if (IMHEX_TRACE_EXCEPTIONS) + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_link_options(tracing PUBLIC "-Wl,--wrap=__cxa_throw") + elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Not supported currently + endif() +endif() diff --git a/lib/trace/include/hex/trace/exceptions.hpp b/lib/trace/include/hex/trace/exceptions.hpp new file mode 100644 index 000000000..9fa88bd58 --- /dev/null +++ b/lib/trace/include/hex/trace/exceptions.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include + +namespace hex::trace { + + std::optional getLastExceptionStackTrace(); + +} \ No newline at end of file diff --git a/main/gui/include/stacktrace.hpp b/lib/trace/include/hex/trace/stacktrace.hpp similarity index 73% rename from main/gui/include/stacktrace.hpp rename to lib/trace/include/hex/trace/stacktrace.hpp index 5a76b7b96..5206cc0ba 100644 --- a/main/gui/include/stacktrace.hpp +++ b/lib/trace/include/hex/trace/stacktrace.hpp @@ -1,18 +1,19 @@ #pragma once -#include - +#include #include #include -namespace hex::stacktrace { +namespace hex::trace { struct StackFrame { std::string file; std::string function; - u32 line; + std::uint32_t line; }; + using StackTrace = std::vector; + void initialize(); struct StackTraceResult { diff --git a/lib/trace/source/exceptions.cpp b/lib/trace/source/exceptions.cpp new file mode 100644 index 000000000..a15882947 --- /dev/null +++ b/lib/trace/source/exceptions.cpp @@ -0,0 +1,27 @@ +#include + +namespace hex::trace { + + static std::optional s_lastExceptionStackTrace; + std::optional getLastExceptionStackTrace() { + if (!s_lastExceptionStackTrace.has_value()) + return std::nullopt; + + auto result = s_lastExceptionStackTrace.value(); + s_lastExceptionStackTrace.reset(); + + return result; + } + +} + +extern "C" { + + [[noreturn]] void __real___cxa_throw(void* thrownException, void* type, void (*destructor)(void*)); + [[noreturn]] void __wrap___cxa_throw(void* thrownException, void* type, void (*destructor)(void*)) { + hex::trace::s_lastExceptionStackTrace = hex::trace::getStackTrace(); + __real___cxa_throw(thrownException, type, destructor); + } + +} + diff --git a/lib/trace/source/instr_entry.cpp b/lib/trace/source/instr_entry.cpp new file mode 100644 index 000000000..e255f0143 --- /dev/null +++ b/lib/trace/source/instr_entry.cpp @@ -0,0 +1,28 @@ +#include + +namespace hex { + + void functionEntry(void *); + void functionExit(void *); + +} + +extern "C" { + + static std::mutex s_mutex; + + [[gnu::no_instrument_function]] + void __cyg_profile_func_enter(void *functionAddress, void *) { + std::scoped_lock lock(s_mutex); + + hex::functionEntry(functionAddress); + } + + [[gnu::no_instrument_function]] + void __cyg_profile_func_exit(void *functionAddress, void *) { + std::scoped_lock lock(s_mutex); + + hex::functionExit(functionAddress); + } + +} \ No newline at end of file diff --git a/main/gui/source/stacktrace.cpp b/lib/trace/source/stacktrace.cpp similarity index 82% rename from main/gui/source/stacktrace.cpp rename to lib/trace/source/stacktrace.cpp index a08109504..ebd2377a5 100644 --- a/main/gui/source/stacktrace.cpp +++ b/lib/trace/source/stacktrace.cpp @@ -1,7 +1,6 @@ -#include -#include +#include +#include -#include #include namespace { @@ -18,12 +17,41 @@ namespace { } -#if defined(OS_WINDOWS) + +#if __has_include() + + #include + + namespace hex::trace { + + void initialize() { + + } + + StackTraceResult getStackTrace() { + StackTrace result; + + auto stackTrace = std::stacktrace::current(); + + for (const auto &entry : stackTrace) { + if (entry.source_line() == 0 && entry.source_file().empty()) + result.emplace_back("", "??", 0); + else + result.emplace_back(entry.source_file(), entry.description(), entry.source_line()); + } + + return { result, "std::stacktrace" }; + } + + } + +#elif defined(OS_WINDOWS) #include #include + #include - namespace hex::stacktrace { + namespace hex::trace { void initialize() { @@ -93,7 +121,7 @@ namespace { DWORD displacementLine = 0; - u32 lineNumber = 0; + std::uint32_t lineNumber = 0; const char *fileName; if (SymGetLineFromAddr64(process, stackFrame.AddrPC.Offset, &displacementLine, &line) == TRUE) { lineNumber = line.LineNumber; @@ -119,10 +147,11 @@ namespace { #if __has_include(BACKTRACE_HEADER) #include BACKTRACE_HEADER - #include + #include #include + #include - namespace hex::stacktrace { + namespace hex::trace { void initialize() { @@ -138,7 +167,7 @@ namespace { for (size_t i = 0; i < count; i += 1) { dladdr(addresses[i], &info); - auto fileName = info.dli_fname != nullptr ? std::fs::path(info.dli_fname).filename().string() : "??"; + auto fileName = info.dli_fname != nullptr ? std::filesystem::path(info.dli_fname).filename().string() : "??"; auto demangledName = info.dli_sname != nullptr ? tryDemangle(info.dli_sname) : "??"; result.push_back(StackFrame { std::move(fileName), std::move(demangledName), 0 }); @@ -156,10 +185,10 @@ namespace { #if __has_include(BACKTRACE_HEADER) #include BACKTRACE_HEADER - #include - #include - namespace hex::stacktrace { + #include + + namespace hex::trace { static struct backtrace_state *s_backtraceState; @@ -167,14 +196,13 @@ namespace { void initialize() { if (auto executablePath = wolv::io::fs::getExecutablePath(); executablePath.has_value()) { static std::string path = executablePath->string(); - s_backtraceState = backtrace_create_state(path.c_str(), 1, [](void *, const char *msg, int) { log::error("{}", msg); }, nullptr); + s_backtraceState = backtrace_create_state(path.c_str(), 1, [](void *, const char *, int) { }, nullptr); } } StackTraceResult getStackTrace() { static std::vector result; - result.clear(); if (s_backtraceState != nullptr) { backtrace_full(s_backtraceState, 0, [](void *, uintptr_t, const char *fileName, int lineNumber, const char *function) -> int { if (fileName == nullptr) @@ -182,7 +210,7 @@ namespace { if (function == nullptr) function = "??"; - result.push_back(StackFrame { std::fs::path(fileName).filename().string(), tryDemangle(function), u32(lineNumber) }); + result.push_back(StackFrame { std::filesystem::path(fileName).filename().string(), tryDemangle(function), std::uint32_t(lineNumber) }); return 0; }, nullptr, nullptr); @@ -198,7 +226,7 @@ namespace { #else - namespace hex::stacktrace { + namespace hex::trace { void initialize() { } StackTraceResult getStackTrace() { diff --git a/main/gui/CMakeLists.txt b/main/gui/CMakeLists.txt index 4723c5925..d1d7cd7fa 100644 --- a/main/gui/CMakeLists.txt +++ b/main/gui/CMakeLists.txt @@ -3,7 +3,6 @@ project(main) add_executable(main ${APPLICATION_TYPE} source/main.cpp source/crash_handlers.cpp - source/stacktrace.cpp source/window/window.cpp source/window/win_window.cpp @@ -38,7 +37,7 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../lib/external/libromfs ${CMAKE add_dependencies(imhex_all main) if (EMSCRIPTEN) - target_link_options(main PRIVATE -sUSE_GLFW=3 -sUSE_PTHREADS=1 -sALLOW_MEMORY_GROWTH=1) + target_link_options(main PRIVATE -sUSE_GLFW=3 -sUSE_PTHREADS=1 -sALLOW_MEMORY_GROWTH=1 -Wno-pthreads-mem-growth) target_link_options(main PRIVATE -sTOTAL_MEMORY=134217728) target_link_options(main PRIVATE -sMAX_WEBGL_VERSION=2) target_link_options(main PRIVATE -sEXPORTED_RUNTIME_METHODS=ccall) @@ -82,4 +81,4 @@ precompileHeaders(main ${CMAKE_CURRENT_SOURCE_DIR}/include) if (APPLE) add_compile_definitions(GL_SILENCE_DEPRECATION) -endif () +endif () \ No newline at end of file diff --git a/main/gui/include/window.hpp b/main/gui/include/window.hpp index 48a5b82c0..6c8691161 100644 --- a/main/gui/include/window.hpp +++ b/main/gui/include/window.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -37,6 +39,7 @@ namespace hex { void beginNativeWindowFrame(); void endNativeWindowFrame(); void drawTitleBar(); + void drawView(const std::string &name, const std::unique_ptr &view); void frameBegin(); void frame(); diff --git a/main/gui/source/crash_handlers.cpp b/main/gui/source/crash_handlers.cpp index 004100b23..cd29938ca 100644 --- a/main/gui/source/crash_handlers.cpp +++ b/main/gui/source/crash_handlers.cpp @@ -11,11 +11,14 @@ #include #include -#include +#include #include #include +#include +#include + #include #include #include @@ -65,7 +68,7 @@ namespace hex::crash { } static void printStackTrace() { - auto stackTraceResult = stacktrace::getStackTrace(); + auto stackTraceResult = trace::getStackTrace(); log::fatal("Printing stacktrace using implementation '{}'", stackTraceResult.implementationName); for (const auto &stackFrame : stackTraceResult.stackFrames) { if (stackFrame.line == 0) @@ -153,7 +156,7 @@ namespace hex::crash { try { std::rethrow_exception(std::current_exception()); } catch (std::exception &ex) { - std::string exceptionStr = hex::format("{}()::what() -> {}", llvm::demangle(typeid(ex).name()), ex.what()); + std::string exceptionStr = hex::format("{}()::what() -> {}", llvm::demangle(std::string("_Z") + typeid(ex).name()), ex.what()); handleCrash(exceptionStr); log::fatal("Program terminated with uncaught exception: {}", exceptionStr); @@ -164,7 +167,7 @@ namespace hex::crash { // Setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex void setupCrashHandlers() { - stacktrace::initialize(); + trace::initialize(); // Register signal handlers { diff --git a/plugins/builtin/source/content/events.cpp b/plugins/builtin/source/content/events.cpp index add42472d..732d55080 100644 --- a/plugins/builtin/source/content/events.cpp +++ b/plugins/builtin/source/content/events.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include #include @@ -66,6 +68,13 @@ namespace hex::plugin::builtin { static bool imhexClosing = false; EventCrashRecovered::subscribe([](const std::exception &e) { PopupCrashRecovered::open(e); + + auto stackTrace = hex::trace::getLastExceptionStackTrace(); + if (stackTrace.has_value()) { + for (const auto &entry : stackTrace->stackFrames) { + hex::log::fatal(" {} at {}:{}", entry.function, entry.file, entry.line); + } + } }); EventWindowClosing::subscribe([](GLFWwindow *window) {