forked from WerWolv/ImHex
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
impr: Handle crashes that may happen before the main loop (WerWolv#1115)
Draft because I absolutely do not trust myself writing good code at 2AM. I will review it tomorrow
- Loading branch information
Showing
7 changed files
with
185 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#pragma once | ||
|
||
namespace hex::crash { | ||
|
||
void setupCrashHandlers(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
#include <hex/api/project_file_manager.hpp> | ||
#include <hex/helpers/logger.hpp> | ||
#include <hex/helpers/fs.hpp> | ||
#include <hex/helpers/stacktrace.hpp> | ||
|
||
#include <wolv/io/fs.hpp> | ||
#include <wolv/utils/string.hpp> | ||
|
||
#include "window.hpp" | ||
|
||
#include <nlohmann/json.hpp> | ||
|
||
#include <llvm/Demangle/Demangle.h> | ||
|
||
#include <exception> | ||
#include <csignal> | ||
|
||
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<void(const std::string&)> 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<EventAbnormalTermination>(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<EventAbnormalTermination>([](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<EventImHexStartupFinished>([]{ | ||
crashCallback = saveCrashFile; | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.