Compare commits

...

2 Commits

Author SHA1 Message Date
WerWolv
92b5fd84c2 impr: Simplify tracy integration 2024-06-01 23:57:34 +02:00
WerWolv
3e0bb6d8be feat: Added initial support for tracing function calls and printing exception stack traces 2024-06-01 16:36:36 +02:00
16 changed files with 274 additions and 124 deletions

View File

@@ -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)

View File

@@ -543,13 +543,9 @@ macro(setupDebugCompressionFlag)
if (NOT DEBUG_COMPRESSION_FLAG) # Cache variable
if (ZSTD_AVAILABLE_COMPILER AND ZSTD_AVAILABLE_LINKER)
message("Using Zstd compression for debug info because both compiler and linker support it")
set(DEBUG_COMPRESSION_FLAG "-gz=zstd" CACHE STRING "Cache to use for debug info compression")
elseif (COMPRESS_AVAILABLE_COMPILER AND COMPRESS_AVAILABLE_LINKER)
message("Using default compression for debug info because both compiler and linker support it")
set(DEBUG_COMPRESSION_FLAG "-gz" CACHE STRING "Cache to use for debug info compression")
else()
message("No compression available for debug info")
endif()
endif()
@@ -734,30 +730,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)

View File

@@ -37,7 +37,7 @@ macro(add_imhex_plugin)
# Add include directories and link libraries
target_include_directories(${IMHEX_PLUGIN_NAME} PUBLIC ${IMHEX_PLUGIN_INCLUDES})
target_link_libraries(${IMHEX_PLUGIN_NAME} PUBLIC ${IMHEX_PLUGIN_LIBRARIES})
target_link_libraries(${IMHEX_PLUGIN_NAME} PRIVATE libimhex ${FMT_LIBRARIES} imgui_all_includes libwolv)
target_link_libraries(${IMHEX_PLUGIN_NAME} PRIVATE libimhex ${FMT_LIBRARIES} imgui_all_includes libwolv tracing)
addIncludesFromLibrary(${IMHEX_PLUGIN_NAME} libpl)
addIncludesFromLibrary(${IMHEX_PLUGIN_NAME} libpl-gen)

View File

@@ -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)

64
lib/trace/CMakeLists.txt Normal file
View File

@@ -0,0 +1,64 @@
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" ON)
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/instrumentation.cpp
)
set(TRACY_ON_DEMAND ON CACHE INTERNAL "TRACY_ON_DEMAND")
set(TRACY_DELAYED_INIT ON CACHE INTERNAL "TRACY_DELAYED_INIT")
set(TRACY_ONLY_LOCALHOST ON CACHE INTERNAL "TRACY_ONLY_LOCALHOST")
set(TRACY_NO_FRAME_IMAGE ON CACHE INTERNAL "TRACY_NO_FRAME_IMAGE")
set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "BUILD_SHARED_LIBS")
FetchContent_Declare(
tracy
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
GIT_TAG master
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(tracy)
target_compile_options(TracyClient PRIVATE "-Wno-error")
target_link_libraries(tracing PUBLIC TracyClient)
target_compile_definitions(tracing PUBLIC IMHEX_USE_INSTRUMENTATION=1)
endif()

View File

@@ -0,0 +1,11 @@
#pragma once
#include <hex/trace/stacktrace.hpp>
#include <optional>
namespace hex::trace {
std::optional<StackTrace> getLastExceptionStackTrace();
}

View File

@@ -0,0 +1,18 @@
#pragma once
#if defined(IMHEX_USE_INSTRUMENTATION)
#include <tracy/Tracy.hpp>
#define IMHEX_TRACE_SCOPE ZoneScoped
#define IMHEX_TRACE_SCOPE_NAME(name) ZoneScoped; ZoneName(name, strlen(name))
#define IMHEX_START_FRAME_MARK FrameMarkStart("Frame")
#define IMHEX_END_FRAME_MARK FrameMarkEnd("Frame")
#define IMHEX_TRACE_MESSAGE(message) TracyMessage(message, strlen(message))
#else
#define IMHEX_TRACE_SCOPE_NAME(name)
#define IMHEX_END_FRAME_MARK
#endif

View File

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

View File

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

View File

@@ -0,0 +1 @@
#include <hex/trace/instrumentation.hpp>

View File

@@ -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,19 +17,48 @@ namespace {
}
#if defined(OS_WINDOWS)
#include <windows.h>
#include <dbghelp.h>
#if __has_include(<stacktrace>)
namespace hex::stacktrace {
#include <stacktrace>
namespace hex::trace {
void initialize() {
}
std::vector<StackFrame> getStackTrace() {
std::vector<StackFrame> stackTrace;
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::trace {
void initialize() {
}
StackTrace getStackTrace() {
StackTrace stackTrace;
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
@@ -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;
@@ -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());
@@ -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 });
@@ -147,10 +176,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;
@@ -158,14 +187,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);
}
}
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)
@@ -173,7 +201,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);
@@ -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 } }; }
}

View File

@@ -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
@@ -58,7 +57,7 @@ set_target_properties(main PROPERTIES
target_compile_definitions(main PRIVATE IMHEX_PROJECT_NAME="${PROJECT_NAME}")
target_link_libraries(main PRIVATE libromfs-imhex libimhex libwolv ${LIBBACKTRACE_LIBRARIES} LLVMDemangle)
target_link_libraries(main PRIVATE libromfs-imhex libimhex libwolv ${LIBBACKTRACE_LIBRARIES} LLVMDemangle tracing)
if (WIN32)
target_link_libraries(main PRIVATE usp10 wsock32 ws2_32 Dwmapi.lib)
else ()
@@ -69,4 +68,4 @@ precompileHeaders(main ${CMAKE_CURRENT_SOURCE_DIR}/include)
if (APPLE)
add_compile_definitions(GL_SILENCE_DEPRECATION)
endif ()
endif ()

View File

@@ -1,5 +1,7 @@
#pragma once
#include <hex.hpp>
#include <filesystem>
#include <memory>
#include <string>
@@ -34,6 +36,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();

View File

@@ -13,7 +13,7 @@
#include <nlohmann/json.hpp>
#include <stacktrace.hpp>
#include <hex/trace/stacktrace.hpp>
#include <llvm/Demangle/Demangle.h>
#include <csignal>
@@ -63,7 +63,7 @@ namespace hex::crash {
}
static void printStackTrace() {
for (const auto &stackFrame : stacktrace::getStackTrace()) {
for (const auto &stackFrame : trace::getStackTrace()) {
if (stackFrame.line == 0)
log::fatal(" ({}) | {}", stackFrame.file, stackFrame.function);
else
@@ -153,7 +153,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
{

View File

@@ -17,6 +17,8 @@
#include <hex/ui/view.hpp>
#include <hex/ui/popup.hpp>
#include <hex/trace/instrumentation.hpp>
#include <chrono>
#include <csignal>
@@ -236,7 +238,9 @@ namespace hex {
glfwSetWindowSizeLimits(m_window, lastWindowSize.x, lastWindowSize.y, lastWindowSize.x, lastWindowSize.y);
}
IMHEX_START_FRAME_MARK;
this->fullFrame();
IMHEX_END_FRAME_MARK;
ImHexApi::System::impl::setLastFrameTime(glfwGetTime() - m_lastStartFrameTime);
@@ -288,6 +292,8 @@ namespace hex {
}
void Window::frameBegin() {
IMHEX_TRACE_SCOPE;
// Start new ImGui Frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
@@ -546,72 +552,80 @@ namespace hex {
TaskManager::runDeferredCalls();
}
void Window::drawView(const std::string &name, const std::unique_ptr<View> &view) {
IMHEX_TRACE_SCOPE_NAME(name.c_str());
// Draw always visible views
view->drawAlwaysVisibleContent();
// Skip views that shouldn't be processed currently
if (!view->shouldProcess())
return;
const auto openViewCount = std::ranges::count_if(ContentRegistry::Views::impl::getEntries(), [](const auto &entry) {
const auto &[unlocalizedName, openView] = entry;
return openView->hasViewMenuItemEntry() && openView->shouldProcess();
});
ImGuiWindowClass windowClass = {};
windowClass.DockNodeFlagsOverrideSet |= ImGuiDockNodeFlags_NoCloseButton;
if (openViewCount <= 1 || LayoutManager::isLayoutLocked())
windowClass.DockNodeFlagsOverrideSet |= ImGuiDockNodeFlags_NoTabBar;
ImGui::SetNextWindowClass(&windowClass);
auto window = ImGui::FindWindowByName(view->getName().c_str());
if (window != nullptr && window->DockNode == nullptr)
ImGui::SetNextWindowBgAlpha(1.0F);
// Draw view
view->draw();
view->trackViewOpenState();
if (view->getWindowOpenState()) {
bool hasWindow = window != nullptr;
bool focused = false;
// Get the currently focused view
if (hasWindow && (window->Flags & ImGuiWindowFlags_Popup) != ImGuiWindowFlags_Popup) {
auto windowName = View::toWindowName(name);
ImGui::Begin(windowName.c_str());
// Detect if the window is focused
focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy);
// Dock the window if it's not already docked
if (view->didWindowJustOpen() && !ImGui::IsWindowDocked()) {
ImGui::DockBuilderDockWindow(windowName.c_str(), ImHexApi::System::getMainDockSpaceId());
EventViewOpened::post(view.get());
}
ImGui::End();
}
// Pass on currently pressed keys to the shortcut handler
auto &io = ImGui::GetIO();
for (const auto &key : m_pressedKeys) {
ShortcutManager::process(view.get(), io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, focused, key);
}
}
}
void Window::frame() {
auto &io = ImGui::GetIO();
IMHEX_TRACE_SCOPE;
// Loop through all views and draw them
for (auto &[name, view] : ContentRegistry::Views::impl::getEntries()) {
ImGui::GetCurrentContext()->NextWindowData.ClearFlags();
// Draw always visible views
view->drawAlwaysVisibleContent();
// Skip views that shouldn't be processed currently
if (!view->shouldProcess())
continue;
const auto openViewCount = std::ranges::count_if(ContentRegistry::Views::impl::getEntries(), [](const auto &entry) {
const auto &[unlocalizedName, openView] = entry;
return openView->hasViewMenuItemEntry() && openView->shouldProcess();
});
ImGuiWindowClass windowClass = {};
windowClass.DockNodeFlagsOverrideSet |= ImGuiDockNodeFlags_NoCloseButton;
if (openViewCount <= 1 || LayoutManager::isLayoutLocked())
windowClass.DockNodeFlagsOverrideSet |= ImGuiDockNodeFlags_NoTabBar;
ImGui::SetNextWindowClass(&windowClass);
auto window = ImGui::FindWindowByName(view->getName().c_str());
if (window != nullptr && window->DockNode == nullptr)
ImGui::SetNextWindowBgAlpha(1.0F);
// Draw view
view->draw();
view->trackViewOpenState();
if (view->getWindowOpenState()) {
bool hasWindow = window != nullptr;
bool focused = false;
// Get the currently focused view
if (hasWindow && (window->Flags & ImGuiWindowFlags_Popup) != ImGuiWindowFlags_Popup) {
auto windowName = View::toWindowName(name);
ImGui::Begin(windowName.c_str());
// Detect if the window is focused
focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy);
// Dock the window if it's not already docked
if (view->didWindowJustOpen() && !ImGui::IsWindowDocked()) {
ImGui::DockBuilderDockWindow(windowName.c_str(), ImHexApi::System::getMainDockSpaceId());
EventViewOpened::post(view.get());
}
ImGui::End();
}
// Pass on currently pressed keys to the shortcut handler
for (const auto &key : m_pressedKeys) {
ShortcutManager::process(view.get(), io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, focused, key);
}
}
drawView(name, view);
}
// Handle global shortcuts
auto &io = ImGui::GetIO();
for (const auto &key : m_pressedKeys) {
ShortcutManager::processGlobals(io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, key);
}
@@ -620,6 +634,8 @@ namespace hex {
}
void Window::frameEnd() {
IMHEX_TRACE_SCOPE;
EventFrameEnd::post();
TutorialManager::drawTutorial();
@@ -738,6 +754,7 @@ namespace hex {
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE);
if (initialWindowProperties.has_value()) {
glfwWindowHint(GLFW_MAXIMIZED, initialWindowProperties->maximized);

View File

@@ -6,6 +6,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>
@@ -53,6 +55,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) {
hex::log::fatal(" {} at {}:{}", entry.function, entry.file, entry.line);
}
}
});
EventWindowClosing::subscribe([](GLFWwindow *window) {