diff --git a/lib/libimhex/include/hex/api/event.hpp b/lib/libimhex/include/hex/api/event.hpp index 0253bef1f..b215775c2 100644 --- a/lib/libimhex/include/hex/api/event.hpp +++ b/lib/libimhex/include/hex/api/event.hpp @@ -155,6 +155,12 @@ namespace hex { }; /* Default Events */ + + /** + * @brief Called when Imhex finished startup, and will enter the main window rendering loop + */ + EVENT_DEF(EventImHexStartupFinished); + EVENT_DEF(EventFileLoaded, std::fs::path); EVENT_DEF(EventDataChanged); EVENT_DEF(EventHighlightingChanged); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 164942e31..56b82279d 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -2,6 +2,7 @@ project(main) add_executable(main ${APPLICATION_TYPE} source/main.cpp + source/crash_handlers.cpp source/window/window.cpp source/window/win_window.cpp diff --git a/main/include/crash_handlers.hpp b/main/include/crash_handlers.hpp new file mode 100644 index 000000000..fd4c27bc4 --- /dev/null +++ b/main/include/crash_handlers.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace hex::crash { + + void setupCrashHandlers(); +} \ No newline at end of file diff --git a/main/include/window.hpp b/main/include/window.hpp index af3bba29b..6472c9885 100644 --- a/main/include/window.hpp +++ b/main/include/window.hpp @@ -14,6 +14,8 @@ struct ImGuiSettingsHandler; namespace hex { + std::fs::path getImGuiSettingsPath(); + void nativeErrorMessage(const std::string &message); class Window { @@ -59,8 +61,6 @@ namespace hex { std::list m_popupsToOpen; std::vector m_pressedKeys; - std::fs::path m_imguiSettingsPath; - bool m_mouseButtonDown = false; bool m_hadEvent = false; diff --git a/main/source/crash_handlers.cpp b/main/source/crash_handlers.cpp new file mode 100644 index 000000000..d71b48f86 --- /dev/null +++ b/main/source/crash_handlers.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include + +#include +#include + +#include "window.hpp" + +#include + +#include + +#include +#include + +namespace hex::crash { + + constexpr static auto CrashBackupFileName = "crash_backup.hexproj"; + constexpr static auto Signals = {SIGSEGV, SIGILL, SIGABRT,SIGFPE}; + + static std::terminate_handler originalHandler; + + static void sendNativeMessage(const std::string& message) { + hex::nativeErrorMessage(hex::format("ImHex crashed during its loading.\nError: {}", message)); + } + + // function that decides what should happen on a crash + // (either sending a message or saving a crash file, depending on when the crash occured) + static std::function crashCallback = sendNativeMessage; + + static void saveCrashFile(const std::string& message) { + hex::unused(message); + + nlohmann::json crashData{ + {"logFile", wolv::util::toUTF8String(hex::log::getFile().getPath())}, + {"project", wolv::util::toUTF8String(ProjectFile::getPath())}, + }; + + for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { + wolv::io::File file(path / "crash.json", wolv::io::File::Mode::Write); + if (file.isValid()) { + file.writeString(crashData.dump(4)); + file.close(); + log::info("Wrote crash.json file to {}", wolv::util::toUTF8String(file.getPath())); + return; + } + } + log::warn("Could not write crash.json file !"); + } + + + // Custom signal handler to print various information and a stacktrace when the application crashes + static void signalHandler(int signalNumber, const std::string &signalName) { + log::fatal("Terminating with signal '{}' ({})", signalName, signalNumber); + + // trigger the crash callback + crashCallback(hex::format("Received signal '{}' ({})", signalName, signalNumber)); + + // Trigger an event so that plugins can handle crashes + // It may affect things (like the project path), + // so we do this after saving the crash file + EventManager::post(signalNumber); + + // Detect if the crash was due to an uncaught exception + if (std::uncaught_exceptions() > 0) { + log::fatal("Uncaught exception thrown!"); + } + + // Reset the signal handler to the default handler + for(auto signal : Signals) std::signal(signal, SIG_DFL); + + // Print stack trace + for (const auto &stackFrame : stacktrace::getStackTrace()) { + if (stackFrame.line == 0) + log::fatal(" {}", stackFrame.function); + else + log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function); + } + + // Trigger a breakpoint if we're in a debug build or raise the signal again for the default handler to handle it + #if defined(DEBUG) + assert(!"Debug build, triggering breakpoint"); + #else + std::raise(signalNumber); + #endif + } + + // setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex + void setupCrashHandlers() { + // Register signal handlers + { + #define HANDLE_SIGNAL(name) \ + std::signal(name, [](int signalNumber){ \ + signalHandler(signalNumber, #name); \ + }) + + HANDLE_SIGNAL(SIGSEGV); + HANDLE_SIGNAL(SIGILL); + HANDLE_SIGNAL(SIGABRT); + HANDLE_SIGNAL(SIGFPE); + + #undef HANDLE_SIGNAL + } + + originalHandler = std::set_terminate([]{ + try { + std::rethrow_exception(std::current_exception()); + } catch (std::exception &ex) { + std::string exceptionStr = hex::format("{}()::what() -> {}", + llvm::itaniumDemangle(typeid(ex).name(), nullptr, nullptr, nullptr), ex.what() + ); + log::fatal("Program terminated with uncaught exception: {}", exceptionStr); + + // handle crash callback + crashCallback(hex::format("Uncaught exception: {}", exceptionStr)); + + // reset signal handlers prior to calling the original handler, because it may raise a signal + for(auto signal : Signals) std::signal(signal, SIG_DFL); + + // call the original handler of C++ std + originalHandler(); + + log::error("Should not happen: original std::set_terminate handler returned. Terminating manually"); + exit(EXIT_FAILURE); + } + log::error("Should not happen: catch block should be executed and terminate the program. Terminating manually"); + exit(EXIT_FAILURE); + + }); + + // Save a backup project when the application crashes + // We need to save the project no mater if it is dirty, + // because this save is responsible for telling us which files + // were opened in case there wasn't a project + EventManager::subscribe([](int) { + auto imguiSettingsPath = hex::getImGuiSettingsPath(); + if (!imguiSettingsPath.empty()) + ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(imguiSettingsPath).c_str()); + + for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { + if (ProjectFile::store(path / CrashBackupFileName)) + break; + } + }); + + EventManager::subscribe([]{ + crashCallback = saveCrashFile; + }); + } +} \ No newline at end of file diff --git a/main/source/main.cpp b/main/source/main.cpp index 12eac19be..111657360 100644 --- a/main/source/main.cpp +++ b/main/source/main.cpp @@ -3,9 +3,11 @@ #include #include "window.hpp" +#include "crash_handlers.hpp" #include "init/splash_window.hpp" #include "init/tasks.hpp" +#include "init/tasks.hpp" #include #include @@ -15,6 +17,7 @@ int main(int argc, char **argv, char **envp) { using namespace hex; + hex::crash::setupCrashHandlers(); ImHexApi::System::impl::setProgramArguments(argc, argv, envp); // Check if ImHex is installed in portable mode @@ -79,6 +82,8 @@ int main(int argc, char **argv, char **envp) { } // Render the main window + + EventManager::post(); window.loop(); } diff --git a/main/source/window/window.cpp b/main/source/window/window.cpp index 847f56cc4..a756b6b92 100644 --- a/main/source/window/window.cpp +++ b/main/source/window/window.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #define IMGUI_DEFINE_MATH_OPERATORS @@ -45,61 +44,13 @@ namespace hex { using namespace std::literals::chrono_literals; - static void saveCrashFile(){ - nlohmann::json crashData{ - {"logFile", wolv::util::toUTF8String(hex::log::getFile().getPath())}, - {"project", wolv::util::toUTF8String(ProjectFile::getPath())}, - }; - - for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { - wolv::io::File file(path / "crash.json", wolv::io::File::Mode::Write); - if (file.isValid()) { - file.writeString(crashData.dump(4)); - file.close(); - log::info("Wrote crash.json file to {}", wolv::util::toUTF8String(file.getPath())); - return; - } - } - log::warn("Could not write crash.json file !"); - } + static std::fs::path s_imguiSettingsPath; - // Custom signal handler to print various information and a stacktrace when the application crashes - static void signalHandler(int signalNumber, const std::string &signalName) { - log::fatal("Terminating with signal '{}' ({})", signalName, signalNumber); - - // save crash.json file - saveCrashFile(); - - // Trigger an event so that plugins can handle crashes - // It may affect things (like the project path), - // so we do this after saving the crash file - EventManager::post(signalNumber); - - // Detect if the crash was due to an uncaught exception - if (std::uncaught_exceptions() > 0) { - log::fatal("Uncaught exception thrown!"); - } - - // Reset the signal handler to the default handler - std::signal(SIGSEGV, SIG_DFL); - std::signal(SIGILL, SIG_DFL); - std::signal(SIGABRT, SIG_DFL); - std::signal(SIGFPE, SIG_DFL); - - // Print stack trace - for (const auto &stackFrame : stacktrace::getStackTrace()) { - if (stackFrame.line == 0) - log::fatal(" {}", stackFrame.function); - else - log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function); - } - - // Trigger a breakpoint if we're in a debug build or raise the signal again for the default handler to handle it - #if defined(DEBUG) - assert(!"Debug build, triggering breakpoint"); - #else - std::raise(signalNumber); - #endif + /** + * @brief returns the path to load/save imgui settings to, or an empty path if no location was found + */ + std::fs::path getImGuiSettingsPath() { + return s_imguiSettingsPath; } Window::Window() { @@ -152,8 +103,6 @@ namespace hex { this->exitGLFW(); } - static std::terminate_handler originalHandler; - void Window::registerEventHandlers() { // Initialize default theme EventManager::post("Dark"); @@ -197,60 +146,12 @@ namespace hex { glfwSetWindowTitle(this->m_window, title.c_str()); }); - constexpr static auto CrashBackupFileName = "crash_backup.hexproj"; - - // Save a backup project when the application crashes - // We need to save the project no mater if it is dirty, - // because this save is responsible for telling us which files - // were opened in case there wasn't a project - EventManager::subscribe(this, [this](int) { - ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(this->m_imguiSettingsPath).c_str()); - - for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { - if (ProjectFile::store(path / CrashBackupFileName)) - break; - } - }); - // Handle opening popups EventManager::subscribe(this, [this](auto name) { std::scoped_lock lock(this->m_popupMutex); this->m_popupsToOpen.push_back(name); }); - - // Register signal handlers - { - #define HANDLE_SIGNAL(name) \ - std::signal(name, [](int signalNumber){ \ - signalHandler(signalNumber, #name); \ - }) - - HANDLE_SIGNAL(SIGSEGV); - HANDLE_SIGNAL(SIGILL); - HANDLE_SIGNAL(SIGABRT); - HANDLE_SIGNAL(SIGFPE); - - #undef HANDLE_SIGNAL - } - - originalHandler = std::set_terminate([]{ - try { - std::rethrow_exception(std::current_exception()); - } catch (std::exception &ex) { - log::fatal( - "Program terminated with uncaught exception: {}()::what() -> {}", - llvm::itaniumDemangle(typeid(ex).name(), nullptr, nullptr, nullptr), - ex.what() - ); - } - - // the handler should eventually release a signal, which will be caught and used to handle the crash - originalHandler(); - - log::error("Should not happen: original std::set_terminate handler returned. Terminating manually"); - exit(EXIT_FAILURE); - }); } void Window::loop() { @@ -1100,15 +1001,16 @@ namespace hex { ImGui::GetCurrentContext()->SettingsHandlers.push_back(handler); for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { - if (std::fs::exists(dir) && fs::isPathWritable(dir)) { - this->m_imguiSettingsPath = dir / "interface.ini"; - io.IniFilename = nullptr; + if (std::fs::exists(dir) && (fs::isPathWritable(dir))) { + s_imguiSettingsPath = dir / "interface.ini"; break; } } - if (!this->m_imguiSettingsPath.empty() && wolv::io::fs::exists(this->m_imguiSettingsPath)) - ImGui::LoadIniSettingsFromDisk(wolv::util::toUTF8String(this->m_imguiSettingsPath).c_str()); + if (!s_imguiSettingsPath.empty() && wolv::io::fs::exists(s_imguiSettingsPath)) { + io.IniFilename = nullptr; + ImGui::LoadIniSettingsFromDisk(wolv::util::toUTF8String(s_imguiSettingsPath).c_str()); + } } ImGui_ImplGlfw_InitForOpenGL(this->m_window, true); @@ -1135,7 +1037,7 @@ namespace hex { void Window::exitImGui() { delete static_cast(ImGui::GetIO().UserData); - ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(this->m_imguiSettingsPath).c_str()); + ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(s_imguiSettingsPath).c_str()); ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown();