#include #include #include #include #include #include #include #include #include #include #include #include #if defined (OS_MACOS) #include #endif namespace hex::crash { constexpr static auto CrashBackupFileName = "crash_backup.hexproj"; constexpr static auto Signals = { SIGSEGV, SIGILL, SIGABRT, SIGFPE }; static std::terminate_handler originalHandler; void resetCrashHandlers(); 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 occurred) static std::function crashCallback = sendNativeMessage; static void saveCrashFile(const std::string& message) { log::fatal(message); nlohmann::json crashData{ { "logFile", wolv::util::toUTF8String(hex::log::impl::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::Create); 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 void printStackTrace() { for (const auto &stackFrame : stacktrace::getStackTrace()) { if (stackFrame.line == 0) log::fatal(" {}", stackFrame.function); else log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function); } } void handleCrash(const std::string &msg, int signalNumber) { crashCallback(msg); printStackTrace(); // Trigger an event so that plugins can handle crashes EventManager::post(signalNumber); } // Custom signal handler to print various information and a stacktrace when the application crashes static void signalHandler(int signalNumber, const std::string &signalName) { // Reset crash handlers, so we can't have a recursion if this code crashes resetCrashHandlers(); // Actually handle the crash handleCrash(hex::format("Received signal '{}' ({})", signalName, signalNumber), signalNumber); // Detect if the crash was due to an uncaught exception if (std::uncaught_exceptions() > 0) { log::fatal("Uncaught exception thrown!"); } // 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::exit(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 } // Reset uncaught C++ exception handler { originalHandler = std::set_terminate([]{ // Reset crash handlers, so we can't have a recursion if this code crashes resetCrashHandlers(); handleCrash("Uncaught exception!", 0); 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); // Reset signal handlers prior to calling the original handler, because it may raise a signal for (auto signal : Signals) std::signal(signal, SIG_DFL); #if defined(DEBUG) assert(!"Debug build, triggering breakpoint"); #else std::exit(100); #endif } }); } // 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 // Only do it when ImHex has finished its loading EventManager::subscribe([] { 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, false)) break; } }); }); // change the crash callback when ImHex has finished startup EventManager::subscribe([]{ crashCallback = saveCrashFile; }); } void resetCrashHandlers() { std::set_terminate(originalHandler); for(auto signal : Signals) std::signal(signal, SIG_DFL); } }