Change: recover when possible from crashes during a crash (#11238)

pull/603/merge
Patric Stout 9 months ago committed by GitHub
parent 99e4a14cdf
commit b00e483b0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -367,11 +367,12 @@ bool CrashLog::WriteCrashLog()
* Write the (crash) dump to a file.
*
* @note Sets \c crashdump_filename when there is a successful return.
* @return 1 iff the crashdump was successfully created, -1 if it failed, 0 if not implemented.
* @return True iff the crashdump was successfully created.
*/
/* virtual */ int CrashLog::WriteCrashDump()
/* virtual */ bool CrashLog::WriteCrashDump()
{
return 0;
fmt::print("No method to create a crash.dmp available.\n");
return false;
}
/**
@ -443,7 +444,7 @@ bool CrashLog::MakeCrashLog()
fmt::print("Crash log generated.\n\n");
fmt::print("Writing crash log to disk...\n");
bool bret = this->WriteCrashLog();
bool bret = this->TryExecute("crashlog", [this]() { return this->WriteCrashLog(); });
if (bret) {
fmt::print("Crash log written to {}. Please add this file to any bug reports.\n\n", this->crashlog_filename);
} else {
@ -452,18 +453,15 @@ bool CrashLog::MakeCrashLog()
}
fmt::print("Writing crash dump to disk...\n");
int dret = this->WriteCrashDump();
if (dret < 0) {
fmt::print("Writing crash dump failed.\n\n");
ret = false;
} else if (dret > 0) {
bret = this->TryExecute("crashdump", [this]() { return this->WriteCrashDump(); });
if (bret) {
fmt::print("Crash dump written to {}. Please add this file to any bug reports.\n\n", this->crashdump_filename);
} else {
fmt::print("Skipped; missing dependency to create crash dump.\n");
fmt::print("Writing crash dump failed.\n\n");
}
fmt::print("Writing crash savegame...\n");
bret = this->WriteSavegame();
bret = this->TryExecute("savegame", [this]() { return this->WriteSavegame(); });
if (bret) {
fmt::print("Crash savegame written to {}. Please add this file and the last (auto)save to any bug reports.\n\n", this->savegame_filename);
} else {
@ -472,7 +470,7 @@ bool CrashLog::MakeCrashLog()
}
fmt::print("Writing crash screenshot...\n");
bret = this->WriteScreenshot();
bret = this->TryExecute("screenshot", [this]() { return this->WriteScreenshot(); });
if (bret) {
fmt::print("Crash screenshot written to {}. Please add this file to any bug reports.\n\n", this->screenshot_filename);
} else {
@ -480,7 +478,7 @@ bool CrashLog::MakeCrashLog()
fmt::print("Writing crash screenshot failed.\n\n");
}
this->SendSurvey();
this->TryExecute("survey", [this]() { this->SendSurvey(); return true; });
return ret;
}

@ -52,6 +52,17 @@ protected:
std::string CreateFileName(const char *ext, bool with_dir = true) const;
/**
* Execute the func() and return its value. If any exception / signal / crash happens,
* catch it and return false. This function should, in theory, never not return, even
* in the worst conditions.
*
* @param section_name The name of the section to be executed. Printed when a crash happens.
* @param func The function to call.
* @return true iff the function returned true.
*/
virtual bool TryExecute(std::string_view section_name, std::function<bool()> &&func) = 0;
public:
/** Stub destructor to silence some compilers. */
virtual ~CrashLog() = default;
@ -65,7 +76,7 @@ public:
void FillCrashLog(std::back_insert_iterator<std::string> &output_iterator) const;
bool WriteCrashLog();
virtual int WriteCrashDump();
virtual bool WriteCrashDump();
bool WriteSavegame();
bool WriteScreenshot();

@ -16,6 +16,7 @@
#include "../../video/video_driver.hpp"
#include "macos.h"
#include <setjmp.h>
#include <signal.h>
#include <mach-o/arch.h>
#include <dlfcn.h>
@ -37,6 +38,9 @@
#define MAX_STACK_FRAMES 64
/** The signals we want our crash handler to handle. */
static constexpr int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL, SIGSYS, SIGQUIT };
/**
* OSX implementation for the crash logger.
*/
@ -154,12 +158,37 @@ class CrashLogOSX : public CrashLog {
return succeeded;
}
int WriteCrashDump() override
bool WriteCrashDump() override
{
return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this) ? 1 : -1;
return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this);
}
#endif
/* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
{
this->try_execute_active = true;
/* Setup a longjump in case a crash happens. */
if (setjmp(this->internal_fault_jmp_buf) != 0) {
fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
/* Reset the signals and continue on. The handler is responsible for dealing with the crash. */
sigset_t sigs;
sigemptyset(&sigs);
for (int signum : _signals_to_handle) {
sigaddset(&sigs, signum);
}
sigprocmask(SIG_UNBLOCK, &sigs, nullptr);
this->try_execute_active = false;
return false;
}
bool res = func();
this->try_execute_active = false;
return res;
}
public:
/**
* A crash log is always generated by signal.
@ -182,52 +211,108 @@ public:
ShowMacDialog(crash_title, message.c_str(), "Quit");
}
/** Buffer to track the long jump set setup. */
jmp_buf internal_fault_jmp_buf;
/** Whether we are in a TryExecute block. */
bool try_execute_active = false;
/** Points to the current crash log. */
static CrashLogOSX *current;
};
/** The signals we want our crash handler to handle. */
static const int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL, SIGSYS };
/* static */ CrashLogOSX *CrashLogOSX::current = nullptr;
/**
* Set a signal handler for all signals we want to capture.
*
* @param handler The handler to use.
* @return sigset_t A sigset_t containing all signals we want to capture.
*/
static sigset_t SetSignals(void(*handler)(int))
{
sigset_t sigs;
sigemptyset(&sigs);
for (int signum : _signals_to_handle) {
sigaddset(&sigs, signum);
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sa.sa_handler = handler;
sa.sa_mask = sigs;
for (int signum : _signals_to_handle) {
sigaction(signum, &sa, nullptr);
}
return sigs;
}
/**
* Entry point for a crash that happened during the handling of a crash.
*
* @param signum the signal that caused us to crash.
*/
static void CDECL HandleInternalCrash(int signum)
{
if (CrashLogOSX::current == nullptr || !CrashLogOSX::current->try_execute_active) {
fmt::print("Something went seriously wrong when creating the crash log. Aborting.\n");
_exit(1);
}
longjmp(CrashLogOSX::current->internal_fault_jmp_buf, 1);
}
/**
* Entry point for the crash handler.
* @note Not static so it shows up in the backtrace.
*
* @param signum the signal that caused us to crash.
*/
void CDECL HandleCrash(int signum)
static void CDECL HandleCrash(int signum)
{
/* Disable all handling of signals by us, so we don't go into infinite loops. */
for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) {
signal(*i, SIG_DFL);
if (CrashLogOSX::current != nullptr) {
CrashLog::AfterCrashLogCleanup();
_exit(2);
}
/* Capture crashing during the handling of a crash. */
sigset_t sigs = SetSignals(HandleInternalCrash);
sigset_t old_sigset;
sigprocmask(SIG_UNBLOCK, &sigs, &old_sigset);
if (_gamelog.TestEmergency()) {
ShowMacDialog("A serious fault condition occurred in the game. The game will shut down.",
"As you loaded an emergency savegame no crash information will be generated.\n",
"Quit");
abort();
_exit(3);
}
if (SaveloadCrashWithMissingNewGRFs()) {
ShowMacDialog("A serious fault condition occurred in the game. The game will shut down.",
"As you loaded an savegame for which you do not have the required NewGRFs no crash information will be generated.\n",
"Quit");
abort();
_exit(3);
}
CrashLogOSX log(signum);
log.MakeCrashLog();
CrashLogOSX *log = new CrashLogOSX(signum);
CrashLogOSX::current = log;
log->MakeCrashLog();
if (VideoDriver::GetInstance() == nullptr || VideoDriver::GetInstance()->HasGUI()) {
log.DisplayCrashDialog();
log->DisplayCrashDialog();
}
CrashLog::AfterCrashLogCleanup();
abort();
_exit(2);
}
/* static */ void CrashLog::InitialiseCrashLog()
{
for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) {
signal(*i, HandleCrash);
}
SetSignals(HandleCrash);
}
/* static */ void CrashLog::InitThread()

@ -14,6 +14,7 @@
#include "../../gamelog.h"
#include "../../saveload/saveload.h"
#include <setjmp.h>
#include <signal.h>
#include <sys/utsname.h>
@ -30,8 +31,17 @@
# include <client/linux/handler/exception_handler.h>
#endif
#if defined(__EMSCRIPTEN__)
# include <emscripten.h>
/* We avoid abort(), as it is a SIGBART, and use _exit() instead. But emscripten doesn't know _exit(). */
# define _exit emscripten_force_exit
#endif
#include "../../safeguards.h"
/** The signals we want our crash handler to handle. */
static constexpr int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL, SIGQUIT };
/**
* Unix implementation for the crash logger.
*/
@ -100,12 +110,37 @@ class CrashLogUnix : public CrashLog {
return succeeded;
}
int WriteCrashDump() override
bool WriteCrashDump() override
{
return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this) ? 1 : -1;
return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this);
}
#endif
/* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
{
this->try_execute_active = true;
/* Setup a longjump in case a crash happens. */
if (setjmp(this->internal_fault_jmp_buf) != 0) {
fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
/* Reset the signals and continue on. The handler is responsible for dealing with the crash. */
sigset_t sigs;
sigemptyset(&sigs);
for (int signum : _signals_to_handle) {
sigaddset(&sigs, signum);
}
sigprocmask(SIG_UNBLOCK, &sigs, nullptr);
this->try_execute_active = false;
return false;
}
bool res = func();
this->try_execute_active = false;
return res;
}
public:
/**
* A crash log is always generated by signal.
@ -115,48 +150,104 @@ public:
signum(signum)
{
}
/** Buffer to track the long jump set setup. */
jmp_buf internal_fault_jmp_buf;
/** Whether we are in a TryExecute block. */
bool try_execute_active = false;
/** Points to the current crash log. */
static CrashLogUnix *current;
};
/** The signals we want our crash handler to handle. */
static const int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL };
/* static */ CrashLogUnix *CrashLogUnix::current = nullptr;
/**
* Set a signal handler for all signals we want to capture.
*
* @param handler The handler to use.
* @return sigset_t A sigset_t containing all signals we want to capture.
*/
static sigset_t SetSignals(void(*handler)(int))
{
sigset_t sigs;
sigemptyset(&sigs);
for (int signum : _signals_to_handle) {
sigaddset(&sigs, signum);
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sa.sa_handler = handler;
sa.sa_mask = sigs;
for (int signum : _signals_to_handle) {
sigaction(signum, &sa, nullptr);
}
return sigs;
}
/**
* Entry point for a crash that happened during the handling of a crash.
*
* @param signum the signal that caused us to crash.
*/
static void CDECL HandleInternalCrash(int signum)
{
if (CrashLogUnix::current == nullptr || !CrashLogUnix::current->try_execute_active) {
fmt::print("Something went seriously wrong when creating the crash log. Aborting.\n");
_exit(1);
}
longjmp(CrashLogUnix::current->internal_fault_jmp_buf, 1);
}
/**
* Entry point for the crash handler.
* @note Not static so it shows up in the backtrace.
*
* @param signum the signal that caused us to crash.
*/
static void CDECL HandleCrash(int signum)
{
/* Disable all handling of signals by us, so we don't go into infinite loops. */
for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) {
signal(*i, SIG_DFL);
if (CrashLogUnix::current != nullptr) {
CrashLog::AfterCrashLogCleanup();
_exit(2);
}
/* Capture crashing during the handling of a crash. */
sigset_t sigs = SetSignals(HandleInternalCrash);
sigset_t old_sigset;
sigprocmask(SIG_UNBLOCK, &sigs, &old_sigset);
if (_gamelog.TestEmergency()) {
fmt::print("A serious fault condition occurred in the game. The game will shut down.\n");
fmt::print("As you loaded an emergency savegame no crash information will be generated.\n");
abort();
_exit(3);
}
if (SaveloadCrashWithMissingNewGRFs()) {
fmt::print("A serious fault condition occurred in the game. The game will shut down.\n");
fmt::print("As you loaded an savegame for which you do not have the required NewGRFs\n");
fmt::print("no crash information will be generated.\n");
abort();
_exit(3);
}
CrashLogUnix log(signum);
log.MakeCrashLog();
CrashLogUnix *log = new CrashLogUnix(signum);
CrashLogUnix::current = log;
log->MakeCrashLog();
CrashLog::AfterCrashLogCleanup();
abort();
_exit(2);
}
/* static */ void CrashLog::InitialiseCrashLog()
{
for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) {
signal(*i, HandleCrash);
}
SetSignals(HandleCrash);
}
/* static */ void CrashLog::InitThread()

@ -25,6 +25,8 @@
#if defined(_MSC_VER)
# include <dbghelp.h>
#else
# include <setjmp.h>
#endif
#ifdef WITH_UNOFFICIAL_BREAKPAD
@ -33,6 +35,21 @@
#include "../../safeguards.h"
/** Exception code used for custom abort. */
static constexpr DWORD CUSTOM_ABORT_EXCEPTION = 0xE1212012;
/**
* Forcefully try to terminate the application.
*
* @param exit_code The exit code to return.
*/
static void NORETURN ImmediateExitProcess(uint exit_code)
{
/* TerminateProcess may fail in some special edge cases; fall back to ExitProcess in this case. */
TerminateProcess(GetCurrentProcess(), exit_code);
ExitProcess(exit_code);
}
/**
* Windows implementation for the crash logger.
*/
@ -55,21 +72,65 @@ public:
return succeeded;
}
int WriteCrashDump() override
bool WriteCrashDump() override
{
return google_breakpad::ExceptionHandler::WriteMinidump(OTTD2FS(_personal_dir), MinidumpCallback, this) ? 1 : -1;
return google_breakpad::ExceptionHandler::WriteMinidump(OTTD2FS(_personal_dir), MinidumpCallback, this);
}
#endif
#if defined(_MSC_VER)
/* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
{
this->try_execute_active = true;
bool res;
__try {
res = func();
} __except (EXCEPTION_EXECUTE_HANDLER) {
fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
res = false;
}
this->try_execute_active = false;
return res;
}
#else
/* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
{
this->try_execute_active = true;
/* Setup a longjump in case a crash happens. */
if (setjmp(this->internal_fault_jmp_buf) != 0) {
fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
this->try_execute_active = false;
return false;
}
bool res = func();
this->try_execute_active = false;
return res;
}
#endif /* _MSC_VER */
/**
* A crash log is always generated when it's generated.
* @param ep the data related to the exception.
*/
CrashLogWindows(EXCEPTION_POINTERS *ep = nullptr) : ep(ep) {}
CrashLogWindows(EXCEPTION_POINTERS *ep = nullptr) :
ep(ep)
{
}
/**
* Points to the current crash log.
*/
#if !defined(_MSC_VER)
/** Buffer to track the long jump set setup. */
jmp_buf internal_fault_jmp_buf;
#endif
/** Whether we are in a TryExecute block. */
bool try_execute_active = false;
/** Points to the current crash log. */
static CrashLogWindows *current;
};
@ -248,7 +309,7 @@ static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
if (CrashLogWindows::current != nullptr) {
CrashLog::AfterCrashLogCleanup();
ExitProcess(2);
ImmediateExitProcess(2);
}
if (_gamelog.TestEmergency()) {
@ -256,7 +317,7 @@ static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
L"A serious fault condition occurred in the game. The game will shut down.\n"
L"As you loaded an emergency savegame no crash information will be generated.\n";
MessageBox(nullptr, _emergency_crash, L"Fatal Application Failure", MB_ICONERROR);
ExitProcess(3);
ImmediateExitProcess(3);
}
if (SaveloadCrashWithMissingNewGRFs()) {
@ -265,7 +326,7 @@ static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
L"As you loaded an savegame for which you do not have the required NewGRFs\n"
L"no crash information will be generated.\n";
MessageBox(nullptr, _saveload_crash, L"Fatal Application Failure", MB_ICONERROR);
ExitProcess(3);
ImmediateExitProcess(3);
}
CrashLogWindows *log = new CrashLogWindows(ep);
@ -293,9 +354,32 @@ static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
return EXCEPTION_EXECUTE_HANDLER;
}
static LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS *ep)
{
if (CrashLogWindows::current != nullptr && CrashLogWindows::current->try_execute_active) {
#if defined(_MSC_VER)
return EXCEPTION_CONTINUE_SEARCH;
#else
longjmp(CrashLogWindows::current->internal_fault_jmp_buf, 1);
#endif
}
if (ep->ExceptionRecord->ExceptionCode == 0xC0000374 /* heap corruption */) {
return ExceptionHandler(ep);
}
if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
return ExceptionHandler(ep);
}
if (ep->ExceptionRecord->ExceptionCode == CUSTOM_ABORT_EXCEPTION) {
return ExceptionHandler(ep);
}
return EXCEPTION_CONTINUE_SEARCH;
}
static void CDECL CustomAbort(int signal)
{
RaiseException(0xE1212012, 0, 0, nullptr);
RaiseException(CUSTOM_ABORT_EXCEPTION, 0, 0, nullptr);
}
/* static */ void CrashLog::InitialiseCrashLog()
@ -309,6 +393,7 @@ static void CDECL CustomAbort(int signal)
_set_abort_behavior(0, _WRITE_ABORT_MSG);
#endif
SetUnhandledExceptionFilter(ExceptionHandler);
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
}
/* static */ void CrashLog::InitThread()
@ -422,7 +507,7 @@ static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARA
switch (wParam) {
case 12: // Close
CrashLog::AfterCrashLogCleanup();
ExitProcess(2);
ImmediateExitProcess(2);
case 15: // Expand window to show crash-message
_expanded = !_expanded;
SetWndSize(wnd, _expanded);
@ -431,7 +516,7 @@ static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARA
return TRUE;
case WM_CLOSE:
CrashLog::AfterCrashLogCleanup();
ExitProcess(2);
ImmediateExitProcess(2);
}
return FALSE;

Loading…
Cancel
Save