mirror of https://github.com/WerWolv/ImHex.git
impr: Handle crashes that may happen before the main loop (#1115)
Draft because I absolutely do not trust myself writing good code at 2AM. I will review it tomorrow
This commit is contained in:
parent
9dafdeb70a
commit
117832e007
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
namespace hex::crash {
|
||||
|
||||
void setupCrashHandlers();
|
||||
}
|
|
@ -14,6 +14,8 @@ struct ImGuiSettingsHandler;
|
|||
|
||||
namespace hex {
|
||||
|
||||
std::fs::path getImGuiSettingsPath();
|
||||
|
||||
void nativeErrorMessage(const std::string &message);
|
||||
|
||||
class Window {
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -79,6 +82,8 @@ int main(int argc, char **argv, char **envp) {
|
|||
}
|
||||
|
||||
// Render the main window
|
||||
|
||||
EventManager::post<EventImHexStartupFinished>();
|
||||
window.loop();
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include <cassert>
|
||||
|
||||
#include <romfs/romfs.hpp>
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
|
@ -45,61 +44,13 @@ namespace hex {
|
|||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
static void saveCrashFile(){
|
||||
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 !");
|
||||
}
|
||||
static std::fs::path s_imguiSettingsPath;
|
||||
|
||||
// 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);
|
||||
|
||||
// save crash.json file
|
||||
saveCrashFile();
|
||||
|
||||
// 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
|
||||
std::signal(SIGSEGV, SIG_DFL);
|
||||
std::signal(SIGILL, SIG_DFL);
|
||||
std::signal(SIGABRT, SIG_DFL);
|
||||
std::signal(SIGFPE, 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
|
||||
/**
|
||||
* @brief returns the path to load/save imgui settings to, or an empty path if no location was found
|
||||
*/
|
||||
std::fs::path getImGuiSettingsPath() {
|
||||
return s_imguiSettingsPath;
|
||||
}
|
||||
|
||||
Window::Window() {
|
||||
|
@ -152,8 +103,6 @@ namespace hex {
|
|||
this->exitGLFW();
|
||||
}
|
||||
|
||||
static std::terminate_handler originalHandler;
|
||||
|
||||
void Window::registerEventHandlers() {
|
||||
// Initialize default theme
|
||||
EventManager::post<RequestChangeTheme>("Dark");
|
||||
|
@ -197,60 +146,12 @@ namespace hex {
|
|||
glfwSetWindowTitle(this->m_window, title.c_str());
|
||||
});
|
||||
|
||||
constexpr static auto CrashBackupFileName = "crash_backup.hexproj";
|
||||
|
||||
// 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>(this, [this](int) {
|
||||
ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(this->m_imguiSettingsPath).c_str());
|
||||
|
||||
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) {
|
||||
if (ProjectFile::store(path / CrashBackupFileName))
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle opening popups
|
||||
EventManager::subscribe<RequestOpenPopup>(this, [this](auto name) {
|
||||
std::scoped_lock lock(this->m_popupMutex);
|
||||
|
||||
this->m_popupsToOpen.push_back(name);
|
||||
});
|
||||
|
||||
// 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) {
|
||||
log::fatal(
|
||||
"Program terminated with uncaught exception: {}()::what() -> {}",
|
||||
llvm::itaniumDemangle(typeid(ex).name(), nullptr, nullptr, nullptr),
|
||||
ex.what()
|
||||
);
|
||||
}
|
||||
|
||||
// the handler should eventually release a signal, which will be caught and used to handle the crash
|
||||
originalHandler();
|
||||
|
||||
log::error("Should not happen: original std::set_terminate handler returned. Terminating manually");
|
||||
exit(EXIT_FAILURE);
|
||||
});
|
||||
}
|
||||
|
||||
void Window::loop() {
|
||||
|
@ -1100,15 +1001,16 @@ namespace hex {
|
|||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(handler);
|
||||
|
||||
for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) {
|
||||
if (std::fs::exists(dir) && fs::isPathWritable(dir)) {
|
||||
this->m_imguiSettingsPath = dir / "interface.ini";
|
||||
io.IniFilename = nullptr;
|
||||
if (std::fs::exists(dir) && (fs::isPathWritable(dir))) {
|
||||
s_imguiSettingsPath = dir / "interface.ini";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->m_imguiSettingsPath.empty() && wolv::io::fs::exists(this->m_imguiSettingsPath))
|
||||
ImGui::LoadIniSettingsFromDisk(wolv::util::toUTF8String(this->m_imguiSettingsPath).c_str());
|
||||
if (!s_imguiSettingsPath.empty() && wolv::io::fs::exists(s_imguiSettingsPath)) {
|
||||
io.IniFilename = nullptr;
|
||||
ImGui::LoadIniSettingsFromDisk(wolv::util::toUTF8String(s_imguiSettingsPath).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(this->m_window, true);
|
||||
|
@ -1135,7 +1037,7 @@ namespace hex {
|
|||
void Window::exitImGui() {
|
||||
delete static_cast<ImGui::ImHexCustomData *>(ImGui::GetIO().UserData);
|
||||
|
||||
ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(this->m_imguiSettingsPath).c_str());
|
||||
ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(s_imguiSettingsPath).c_str());
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
|
|
Loading…
Reference in New Issue