fix: Make ImHex exit cleanly when crashing

This commit is contained in:
WerWolv 2023-08-25 22:53:33 +02:00
parent 175e66a60e
commit 7476ae230c
1 changed files with 59 additions and 42 deletions

View File

@ -1,6 +1,8 @@
#include <hex/api/project_file_manager.hpp> #include <hex/api/project_file_manager.hpp>
#include <hex/api/task.hpp> #include <hex/api/task.hpp>
#include <init/tasks.hpp>
#include <hex/helpers/logger.hpp> #include <hex/helpers/logger.hpp>
#include <hex/helpers/fs.hpp> #include <hex/helpers/fs.hpp>
#include <hex/helpers/stacktrace.hpp> #include <hex/helpers/stacktrace.hpp>
@ -26,8 +28,6 @@ namespace hex::crash {
constexpr static auto CrashBackupFileName = "crash_backup.hexproj"; constexpr static auto CrashBackupFileName = "crash_backup.hexproj";
constexpr static auto Signals = { SIGSEGV, SIGILL, SIGABRT, SIGFPE }; constexpr static auto Signals = { SIGSEGV, SIGILL, SIGABRT, SIGFPE };
static std::terminate_handler originalHandler;
void resetCrashHandlers(); void resetCrashHandlers();
static void sendNativeMessage(const std::string& message) { static void sendNativeMessage(const std::string& message) {
@ -68,13 +68,39 @@ namespace hex::crash {
} }
} }
void handleCrash(const std::string &msg, int signalNumber) { extern "C" void triggerSafeShutdown(int signalNumber = 0) {
// Trigger an event so that plugins can handle crashes
EventManager::post<EventAbnormalTermination>(signalNumber);
// Run exit tasks
for (const auto &[name, task, async] : init::getExitTasks())
task();
// 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)
#if defined(OS_WINDOWS)
__debugbreak();
#else
raise(SIGTRAP);
#endif
#else
if (signalNumber == 0)
std::abort();
else
std::exit(signalNumber);
#endif
}
void handleCrash(const std::string &msg) {
crashCallback(msg); crashCallback(msg);
printStackTrace(); printStackTrace();
// Trigger an event so that plugins can handle crashes fflush(stdout);
EventManager::post<EventAbnormalTermination>(signalNumber); fflush(stderr);
} }
// Custom signal handler to print various information and a stacktrace when the application crashes // Custom signal handler to print various information and a stacktrace when the application crashes
@ -83,19 +109,31 @@ namespace hex::crash {
resetCrashHandlers(); resetCrashHandlers();
// Actually handle the crash // Actually handle the crash
handleCrash(hex::format("Received signal '{}' ({})", signalName, signalNumber), signalNumber); handleCrash(hex::format("Received signal '{}' ({})", signalName, signalNumber));
// Detect if the crash was due to an uncaught exception // Detect if the crash was due to an uncaught exception
if (std::uncaught_exceptions() > 0) { if (std::uncaught_exceptions() > 0) {
log::fatal("Uncaught exception thrown!"); 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 triggerSafeShutdown(signalNumber);
#if defined(DEBUG) }
assert(!"Debug build, triggering breakpoint");
#else static void uncaughtExceptionHandler() {
std::exit(signalNumber); // Reset crash handlers, so we can't have a recursion if this code crashes
#endif resetCrashHandlers();
handleCrash("Uncaught exception!");
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);
}
triggerSafeShutdown();
} }
// Setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex // Setup functions to handle signals, uncaught exception, or similar stuff that will crash ImHex
@ -115,31 +153,8 @@ namespace hex::crash {
#undef HANDLE_SIGNAL #undef HANDLE_SIGNAL
} }
// Reset uncaught C++ exception handler // Configure the uncaught exception handler
{ std::set_terminate(uncaughtExceptionHandler);
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 // Save a backup project when the application crashes
// We need to save the project no mater if it is dirty, // We need to save the project no mater if it is dirty,
@ -152,10 +167,12 @@ namespace hex::crash {
if (!imguiSettingsPath.empty()) if (!imguiSettingsPath.empty())
ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(imguiSettingsPath).c_str()); ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(imguiSettingsPath).c_str());
if (ImHexApi::Provider::isValid()) {
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) {
if (ProjectFile::store(path / CrashBackupFileName, false)) if (ProjectFile::store(path / CrashBackupFileName, false))
break; break;
} }
}
}); });
}); });
@ -166,7 +183,7 @@ namespace hex::crash {
} }
void resetCrashHandlers() { void resetCrashHandlers() {
std::set_terminate(originalHandler); std::set_terminate(nullptr);
for(auto signal : Signals) std::signal(signal, SIG_DFL); for(auto signal : Signals) std::signal(signal, SIG_DFL);
} }