mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-03-27 23:37:05 -05:00
feat: Add full exception tracing support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ local/
|
||||
venv/
|
||||
.cache/
|
||||
install/
|
||||
out/
|
||||
|
||||
*.mgc
|
||||
*.kdev4
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
55
lib/trace/CMakeLists.txt
Normal file
55
lib/trace/CMakeLists.txt
Normal file
@@ -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 <stacktrace>
|
||||
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()
|
||||
11
lib/trace/include/hex/trace/exceptions.hpp
Normal file
11
lib/trace/include/hex/trace/exceptions.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <hex/trace/stacktrace.hpp>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace hex::trace {
|
||||
|
||||
std::optional<StackTraceResult> getLastExceptionStackTrace();
|
||||
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <hex.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace hex::stacktrace {
|
||||
namespace hex::trace {
|
||||
|
||||
struct StackFrame {
|
||||
std::string file;
|
||||
std::string function;
|
||||
u32 line;
|
||||
std::uint32_t line;
|
||||
};
|
||||
|
||||
using StackTrace = std::vector<StackFrame>;
|
||||
|
||||
void initialize();
|
||||
|
||||
struct StackTraceResult {
|
||||
27
lib/trace/source/exceptions.cpp
Normal file
27
lib/trace/source/exceptions.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <hex/trace/exceptions.hpp>
|
||||
|
||||
namespace hex::trace {
|
||||
|
||||
static std::optional<StackTraceResult> s_lastExceptionStackTrace;
|
||||
std::optional<StackTraceResult> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
28
lib/trace/source/instr_entry.cpp
Normal file
28
lib/trace/source/instr_entry.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <mutex>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <stacktrace.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <iostream>
|
||||
#include <hex/trace/stacktrace.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
|
||||
namespace {
|
||||
@@ -18,12 +17,41 @@ namespace {
|
||||
|
||||
}
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
|
||||
#if __has_include(<stacktrace>)
|
||||
|
||||
#include <stacktrace>
|
||||
|
||||
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 <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <array>
|
||||
|
||||
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 <hex/helpers/utils.hpp>
|
||||
#include <filesystem>
|
||||
#include <dlfcn.h>
|
||||
#include <array>
|
||||
|
||||
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 <hex/helpers/logger.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
namespace hex::stacktrace {
|
||||
#include <wolv/io/fs.hpp>
|
||||
|
||||
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<StackFrame> 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() {
|
||||
@@ -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 ()
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <hex.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
@@ -37,6 +39,7 @@ namespace hex {
|
||||
void beginNativeWindowFrame();
|
||||
void endNativeWindowFrame();
|
||||
void drawTitleBar();
|
||||
void drawView(const std::string &name, const std::unique_ptr<View> &view);
|
||||
|
||||
void frameBegin();
|
||||
void frame();
|
||||
|
||||
@@ -11,11 +11,14 @@
|
||||
|
||||
#include <window.hpp>
|
||||
#include <init/tasks.hpp>
|
||||
#include <stacktrace.hpp>
|
||||
#include <hex/trace/stacktrace.hpp>
|
||||
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <hex/trace/stacktrace.hpp>
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
|
||||
#include <csignal>
|
||||
#include <exception>
|
||||
#include <typeinfo>
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
#include <hex/api/workspace_manager.hpp>
|
||||
|
||||
#include <hex/trace/exceptions.hpp>
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
#include <hex/ui/view.hpp>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user