Skip to content

Commit

Permalink
impr: Handle crashes that may happen before the main loop (WerWolv#1115)
Browse files Browse the repository at this point in the history
Draft because I absolutely do not trust myself writing good code at 2AM.
I will review it tomorrow
  • Loading branch information
iTrooz authored Jun 1, 2023
1 parent 9dafdeb commit 117832e
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 113 deletions.
6 changes: 6 additions & 0 deletions lib/libimhex/include/hex/api/event.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ namespace hex {
};

/* Default Events */

/**
* @brief Called when Imhex finished startup, and will enter the main window rendering loop
*/
EVENT_DEF(EventImHexStartupFinished);

EVENT_DEF(EventFileLoaded, std::fs::path);
EVENT_DEF(EventDataChanged);
EVENT_DEF(EventHighlightingChanged);
Expand Down
1 change: 1 addition & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ project(main)

add_executable(main ${APPLICATION_TYPE}
source/main.cpp
source/crash_handlers.cpp

source/window/window.cpp
source/window/win_window.cpp
Expand Down
6 changes: 6 additions & 0 deletions main/include/crash_handlers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

namespace hex::crash {

void setupCrashHandlers();
}
4 changes: 2 additions & 2 deletions main/include/window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ struct ImGuiSettingsHandler;

namespace hex {

std::fs::path getImGuiSettingsPath();

void nativeErrorMessage(const std::string &message);

class Window {
Expand Down Expand Up @@ -59,8 +61,6 @@ namespace hex {
std::list<std::string> m_popupsToOpen;
std::vector<int> m_pressedKeys;

std::fs::path m_imguiSettingsPath;

bool m_mouseButtonDown = false;

bool m_hadEvent = false;
Expand Down
152 changes: 152 additions & 0 deletions main/source/crash_handlers.cpp
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;
});
}
}
5 changes: 5 additions & 0 deletions main/source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
#include <hex/helpers/logger.hpp>

#include "window.hpp"
#include "crash_handlers.hpp"

#include "init/splash_window.hpp"
#include "init/tasks.hpp"
#include "init/tasks.hpp"

#include <hex/api/task.hpp>
#include <hex/api/project_file_manager.hpp>
Expand All @@ -15,6 +17,7 @@

int main(int argc, char **argv, char **envp) {
using namespace hex;
hex::crash::setupCrashHandlers();
ImHexApi::System::impl::setProgramArguments(argc, argv, envp);

// Check if ImHex is installed in portable mode
Expand Down Expand Up @@ -79,6 +82,8 @@ int main(int argc, char **argv, char **envp) {
}

// Render the main window

EventManager::post<EventImHexStartupFinished>();
window.loop();
}

Expand Down
Loading

0 comments on commit 117832e

Please sign in to comment.