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

This commit is contained in:
Patric Stout
2023-08-27 21:54:05 +02:00
committed by GitHub
parent 99e4a14cdf
commit b00e483b0f
5 changed files with 330 additions and 60 deletions

View File

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