1
0
mirror of synced 2024-11-29 01:44:31 +01:00
ImHex/main/source/crash_handlers.cpp

173 lines
6.2 KiB
C++

#include <hex/api/project_file_manager.hpp>
#include <hex/api/task.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>
#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 };
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<void(const std::string&)> 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<EventAbnormalTermination>(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<EventImHexStartupFinished>([] {
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, false))
break;
}
});
});
// change the crash callback when ImHex has finished startup
EventManager::subscribe<EventImHexStartupFinished>([]{
crashCallback = saveCrashFile;
});
}
void resetCrashHandlers() {
std::set_terminate(originalHandler);
for(auto signal : Signals) std::signal(signal, SIG_DFL);
}
}