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:
iTrooz
2023-06-01 18:35:41 +02:00
committed by GitHub
parent 9dafdeb70a
commit 117832e007
7 changed files with 185 additions and 113 deletions

View File

@@ -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();