#include <hex/api/project_file_manager.hpp> #include <hex/api/task_manager.hpp> #include <hex/api/workspace_manager.hpp> #include <hex/helpers/logger.hpp> #include <hex/helpers/fs.hpp> #include <hex/helpers/default_paths.hpp> #include <wolv/utils/string.hpp> #include <window.hpp> #include <init/tasks.hpp> #include <stacktrace.hpp> #include <llvm/Demangle/Demangle.h> #include <nlohmann/json.hpp> #include <csignal> #include <exception> #include <typeinfo> #if defined (OS_MACOS) #include <sys/utsname.h> #endif namespace hex::crash { constexpr static auto CrashBackupFileName = "crash_backup.hexproj"; constexpr static auto Signals = { SIGSEGV, SIGILL, SIGABRT, SIGFPE }; void resetCrashHandlers(); static void sendNativeMessage(const std::string& message) { hex::nativeErrorMessage(hex::format("ImHex crashed during initial setup!\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) using CrashCallback = void (*) (const std::string&); static CrashCallback crashCallback = sendNativeMessage; static void saveCrashFile(const std::string& message) { log::fatal(message); nlohmann::json crashData { { "logFile", wolv::io::fs::toNormalizedPathString(hex::log::impl::getFile().getPath()) }, { "project", wolv::io::fs::toNormalizedPathString(ProjectFile::getPath()) }, }; for (const auto &path : paths::Config.write()) { 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() { auto stackTraceResult = stacktrace::getStackTrace(); log::fatal("Printing stacktrace using implementation '{}'", stackTraceResult.implementationName); for (const auto &stackFrame : stackTraceResult.stackFrames) { if (stackFrame.line == 0) log::fatal(" ({}) | {}", stackFrame.file, stackFrame.function); else log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function); } } extern "C" void triggerSafeShutdown(int signalNumber = 0) { // Trigger an event so that plugins can handle crashes EventAbnormalTermination::post(signalNumber); // Run exit tasks init::runExitTasks(); // Terminate all asynchronous tasks TaskManager::exit(); // 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) static bool breakpointTriggered = false; if (!breakpointTriggered) { breakpointTriggered = true; #if defined(OS_WINDOWS) __debugbreak(); #else raise(SIGTRAP); #endif } std::exit(signalNumber); #else if (signalNumber == 0) std::abort(); else std::exit(signalNumber); #endif } void handleCrash(const std::string &msg) { // Call the crash callback crashCallback(msg); // Print the stacktrace to the console or log file printStackTrace(); // Flush all streams fflush(stdout); fflush(stderr); } // Custom signal handler to print various information and a stacktrace when the application crashes static void signalHandler(int signalNumber, const std::string &signalName) { if (signalNumber == SIGINT) { ImHexApi::System::closeImHex(); return; } // 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)); // Detect if the crash was due to an uncaught exception if (std::uncaught_exceptions() > 0) { log::fatal("Uncaught exception thrown!"); } triggerSafeShutdown(signalNumber); } static void uncaughtExceptionHandler() { // Reset crash handlers, so we can't have a recursion if this code crashes resetCrashHandlers(); // Print the current exception info try { std::rethrow_exception(std::current_exception()); } catch (std::exception &ex) { std::string exceptionStr = hex::format("{}()::what() -> {}", llvm::itaniumDemangle(typeid(ex).name()), ex.what()); handleCrash(exceptionStr); log::fatal("Program terminated with uncaught exception: {}", exceptionStr); } triggerSafeShutdown(); } // Setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex void setupCrashHandlers() { stacktrace::initialize(); // 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); HANDLE_SIGNAL(SIGINT); #if defined (SIGBUS) HANDLE_SIGNAL(SIGBUS); #endif #undef HANDLE_SIGNAL } // Configure the uncaught exception handler std::set_terminate(uncaughtExceptionHandler); // 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 EventImHexStartupFinished::subscribe([] { EventAbnormalTermination::subscribe([](int) { WorkspaceManager::exportToFile(); // Create crash backup if any providers are open if (ImHexApi::Provider::isValid()) { for (const auto &path : paths::Config.write()) { if (ProjectFile::store(path / CrashBackupFileName, false)) break; } } }); }); // Change the crash callback when ImHex has finished startup EventImHexStartupFinished::subscribe([]{ crashCallback = saveCrashFile; }); } void resetCrashHandlers() { std::set_terminate(nullptr); for (auto signal : Signals) std::signal(signal, SIG_DFL); } }