Skip to content

Commit

Permalink
feat: Added initial support for tracing function calls and printing e…
Browse files Browse the repository at this point in the history
…xception stack traces
  • Loading branch information
WerWolv committed Jun 1, 2024
1 parent cf34c4b commit 3e0bb6d
Show file tree
Hide file tree
Showing 16 changed files with 272 additions and 105 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ addBundledLibraries()
add_subdirectory(lib/libimhex)
add_subdirectory(main)
addPluginDirectories()
add_subdirectory(lib/trace)

# Add unit tests
if (IMHEX_ENABLE_UNIT_TESTS)
Expand Down
24 changes: 0 additions & 24 deletions cmake/build_helpers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -734,30 +734,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)
Expand Down
2 changes: 1 addition & 1 deletion lib/libimhex/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,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 ${MBEDTLS_LIBRARIES} ${FMT_LIBRARIES} ${LUNASVG_LIBRARIES})
target_link_libraries(libimhex ${LIBIMHEX_LIBRARY_TYPE} ${NLOHMANN_JSON_LIBRARIES} imgui_all_includes ${MBEDTLS_LIBRARIES} ${FMT_LIBRARIES} ${LUNASVG_LIBRARIES} tracing)

set_property(TARGET libimhex PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)

Expand Down
61 changes: 61 additions & 0 deletions lib/trace/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
project(tracing)

option(IMHEX_TRACE_EXCEPTIONS "Hook thrown exceptions to display a stack trace when possible" ON)
option(IMHEX_INSTRUMENT_FUNCTIONS "Hook all function entries and exits to profile things in Tracy" OFF)

add_library(tracing OBJECT
source/stacktrace.cpp
source/exceptions.cpp
)
target_include_directories(tracing PUBLIC include)
target_link_libraries(tracing PRIVATE stdc++exp)

if (NOT IMHEX_DISABLE_STACKTRACE)
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)
target_link_options(tracing PUBLIC "-Wl,--wrap=__cxa_throw")
endif()

if (IMHEX_INSTRUMENT_FUNCTIONS)
target_sources(tracing PUBLIC
source/instr_entry.cpp
source/instrumentation.cpp
)

FetchContent_Declare(
tracy
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
GIT_TAG v0.10
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(tracy)
target_compile_options(TracyClient PRIVATE "-Wno-error")

target_compile_options(tracing PUBLIC "-finstrument-functions")
target_link_libraries(tracing PRIVATE TracyClient)
endif()
11 changes: 11 additions & 0 deletions lib/trace/include/hex/trace/exceptions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <hex/trace/stacktrace.hpp>

#include <optional>

namespace hex::trace {

std::optional<StackTrace> getLastExceptionStackTrace();

}
6 changes: 6 additions & 0 deletions lib/trace/include/hex/trace/instrumentation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#include <map>
#include <memory>


Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
#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;
};

void initialize();
using StackTrace = std::vector<StackFrame>;

std::vector<StackFrame> getStackTrace();
void initialize();
StackTrace getStackTrace();

}
27 changes: 27 additions & 0 deletions lib/trace/source/exceptions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <hex/trace/exceptions.hpp>

namespace hex::trace {

static std::optional<StackTrace> s_lastExceptionStackTrace;
std::optional<StackTrace> 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 changes: 28 additions & 0 deletions lib/trace/source/instr_entry.cpp
Original file line number Diff line number Diff line change
@@ -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);
}

}
13 changes: 13 additions & 0 deletions lib/trace/source/instrumentation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <hex/trace/instrumentation.hpp>

namespace hex {

void functionEntry([[maybe_unused]] void *functionAddress) {

}

void functionExit([[maybe_unused]] void *functionAddress) {

}

}
74 changes: 51 additions & 23 deletions main/gui/source/stacktrace.cpp → lib/trace/source/stacktrace.cpp
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -18,19 +17,48 @@ namespace {

}

#if defined(OS_WINDOWS)

#if __has_include(<stacktrace>)

#include <stacktrace>

namespace hex::trace {

void initialize() {

}

StackTrace 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;
}

}

#elif defined(OS_WINDOWS)

#include <windows.h>
#include <dbghelp.h>
#include <array>

namespace hex::stacktrace {
namespace hex::trace {

void initialize() {

}

std::vector<StackFrame> getStackTrace() {
std::vector<StackFrame> stackTrace;
StackTrace getStackTrace() {
StackTrace stackTrace;

HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
Expand Down Expand Up @@ -84,7 +112,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;
Expand All @@ -110,17 +138,18 @@ 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() {

}

std::vector<StackFrame> getStackTrace() {
static std::vector<StackFrame> result;
StackTrace getStackTrace() {
StackTrace result;

std::array<void*, 128> addresses = {};
const size_t count = backtrace(addresses.data(), addresses.size());
Expand All @@ -129,7 +158,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 });
Expand All @@ -147,33 +176,32 @@ 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;


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);
}
}

std::vector<StackFrame> getStackTrace() {
static std::vector<StackFrame> result;
StackTrace getStackTrace() {
StackTrace 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)
fileName = "??";
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);
Expand All @@ -189,10 +217,10 @@ namespace {

#else

namespace hex::stacktrace {
namespace hex::trace {

void initialize() { }
std::vector<StackFrame> getStackTrace() { return { StackFrame { "??", "Stacktrace collecting not available!", 0 } }; }
StackTrace getStackTrace() { return { StackFrame { "??", "Stacktrace collecting not available!", 0 } }; }

}

Expand Down
Loading

0 comments on commit 3e0bb6d

Please sign in to comment.