Skip to content

Commit

Permalink
Fix windows crash viewer (#2987)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatusGuy authored Oct 6, 2024
1 parent 2d3a8d8 commit 66a150b
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 45 deletions.
5 changes: 5 additions & 0 deletions mk/cmake/SuperTux/BuildInstall.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ if(WIN32 AND NOT UNIX)
${CMAKE_CURRENT_SOURCE_DIR}/data/images/engine/icons/supertux.ico
DESTINATION ".")

# Install PDB files for use with the error handler.
install(FILES $<TARGET_PDB_FILE:supertux2>
DESTINATION ${INSTALL_SUBDIR_BIN}
OPTIONAL)

install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mk/msvc/run_supertux.bat
${CMAKE_CURRENT_SOURCE_DIR}/mk/msvc/run_supertux_portable.bat
DESTINATION ".")
Expand Down
153 changes: 129 additions & 24 deletions src/supertux/error_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
#endif

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <DbgHelp.h>
//#include <VersionHelpers.h>

Expand All @@ -49,47 +47,142 @@
#include <unistd.h>
#endif

bool ErrorHandler::m_handing_error = false;

void
ErrorHandler::set_handlers()
{
#ifdef WIN32
SetUnhandledExceptionFilter(supertux_seh_handler);
#elif defined(UNIX)
signal(SIGSEGV, handle_error);
signal(SIGABRT, handle_error);
#endif
}

#ifdef WIN32
static PCONTEXT pcontext = NULL;
#endif

std::string
ErrorHandler::get_stacktrace()
{
#ifdef WIN32
std::stringstream stacktrace;
// Adapted from SuperTuxKart, (C) 2013-2015 Lionel Fuentes, GPLv3

// Initialize symbols
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
if (!SymInitialize(GetCurrentProcess(), NULL, TRUE))
if (pcontext == NULL)
{
return "";
CONTEXT context;
std::memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);
pcontext = &context;
}

// Get current stack frame
void* stack[100];
WORD frames = CaptureStackBackTrace(0, 100, stack, NULL);
const HANDLE hProcess = GetCurrentProcess();
const HANDLE hThread = GetCurrentThread();

// Get symbols for each frame
SYMBOL_INFO* symbol = static_cast<SYMBOL_INFO*>(std::calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1));
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
// Since the stack trace can also be used for leak checks, don't
// initialise this all the time.
static bool first_time = true;

for (int i = 0; i < frames; i++)
// Initialize the symbol hander for the process
if (first_time)
{
SymFromAddr(GetCurrentProcess(), (DWORD64) stack[i], 0, symbol);
stacktrace << symbol->Name << " - 0x" << std::hex << symbol->Address << "\n";
// Get the file path of the executable
std::string path(MAX_PATH, 0);
GetModuleFileName(NULL, &path[0], MAX_PATH);

int size_needed = MultiByteToWideChar(CP_UTF8, 0, &path[0], (int) path.size(), NULL, 0);
std::wstring wpath(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, &path[0], (int) path.size(), &wpath[0], size_needed);

// Finally initialize the symbol handler.
BOOL bOk = SymInitializeW(hProcess, wpath.empty() ? NULL : wpath.c_str(), TRUE);
if (!bOk)
{
return "";
}

SymSetOptions(SYMOPT_LOAD_LINES);
first_time = false;
}

std::free(symbol);
SymCleanup(GetCurrentProcess());
std::stringstream callstack;

return stacktrace.str();
// Get the stack trace
{
// Initialize the STACKFRAME structure so that it
// corresponds to the current function call
STACKFRAME64 stackframe;
std::memset(&stackframe, 0, sizeof(stackframe));
stackframe.AddrPC.Mode = AddrModeFlat;
stackframe.AddrStack.Mode = AddrModeFlat;
stackframe.AddrFrame.Mode = AddrModeFlat;
#if defined(_M_ARM)
stackframe.AddrPC.Offset = pcontext->Pc;
stackframe.AddrStack.Offset = pcontext->Sp;
stackframe.AddrFrame.Offset = pcontext->R11;
const DWORD machine_type = IMAGE_FILE_MACHINE_ARM;
#elif defined(_M_ARM64)
stackframe.AddrPC.Offset = pcontext->Pc;
stackframe.AddrStack.Offset = pcontext->Sp;
stackframe.AddrFrame.Offset = pcontext->Fp;
const DWORD machine_type = IMAGE_FILE_MACHINE_ARM64;
#elif defined(_WIN64)
stackframe.AddrPC.Offset = pcontext->Rip;
stackframe.AddrStack.Offset = pcontext->Rsp;
stackframe.AddrFrame.Offset = pcontext->Rbp;
const DWORD machine_type = IMAGE_FILE_MACHINE_AMD64;
#else
stackframe.AddrPC.Offset = pcontext->Eip;
stackframe.AddrStack.Offset = pcontext->Esp;
stackframe.AddrFrame.Offset = pcontext->Ebp;
const DWORD machine_type = IMAGE_FILE_MACHINE_I386;
#endif

// Walk the stack
const int max_nb_calls = 32;
for (int i = 0; i < max_nb_calls ; i++)
{
const BOOL stackframe_ok = StackWalk64(machine_type, hProcess, hThread,
&stackframe, pcontext, NULL,
SymFunctionTableAccess64,
SymGetModuleBase64, NULL);
if (!stackframe_ok) break;

// Decode the symbol and add it to the call stack
DWORD64 sym_displacement;
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; // cppcheck-suppress unassignedVariable
PSYMBOL_INFO symbol = (PSYMBOL_INFO) buffer;
symbol->MaxNameLen = MAX_SYM_NAME;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);

if (!SymFromAddr(hProcess, stackframe.AddrPC.Offset,
&sym_displacement, symbol))
{
callstack << "<no symbol available>\n";
continue;
}

IMAGEHLP_LINE64 line64;
DWORD dwDisplacement = (DWORD) sym_displacement;
bool result = SymGetLineFromAddr64(hProcess,
stackframe.AddrPC.Offset,
&dwDisplacement, &line64);
if (result)
{
std::string s(line64.FileName);
callstack << symbol->Name << " ("
<< FileSystem::basename(s) << ":"
<< line64.LineNumber << ")\n";
}
else
{
callstack << symbol->Name << "\n";
}
}
}

return callstack.str();
#elif defined(UNIX)
void* array[128];
size_t size;
Expand Down Expand Up @@ -184,17 +277,27 @@ ErrorHandler::get_system_info()
#endif
}

#ifdef WIN32
LONG WINAPI
ErrorHandler::supertux_seh_handler(_EXCEPTION_POINTERS* ExceptionInfo)
{
pcontext = ExceptionInfo->ContextRecord;
error_dialog_crash(get_stacktrace());
return EXCEPTION_EXECUTE_HANDLER;
}
#else
[[ noreturn ]] void
ErrorHandler::handle_error(int sig)
{
if (m_handing_error)
static bool handling_error = false;
if (handling_error)
{
// Error happened again while handling another segfault. Abort now.
close_program();
}
else
{
m_handing_error = true;
handling_error = true;

// Do not use external stuff (like log_fatal) to limit the risk of causing
// another error, which would restart the handler again.
Expand All @@ -204,6 +307,7 @@ ErrorHandler::handle_error(int sig)
close_program();
}
}
#endif

void
ErrorHandler::error_dialog_crash(const std::string& stacktrace)
Expand Down Expand Up @@ -367,3 +471,4 @@ ErrorHandler::close_program()
}

/* EOF */

40 changes: 19 additions & 21 deletions src/supertux/error_handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,30 @@

#include <iostream>

class ErrorHandler final
{
public:
static void set_handlers();

static std::string get_stacktrace();
static std::string get_system_info();

static void error_dialog_crash(const std::string& stacktrace);
static void error_dialog_exception(const std::string& exception = "");
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

static void report_error(const std::string& details);
namespace ErrorHandler {
void set_handlers();

[[ noreturn ]] static void handle_error(int sig);
std::string get_stacktrace();
std::string get_system_info();

[[ noreturn ]] static void close_program();
void error_dialog_crash(const std::string& stacktrace);
void error_dialog_exception(const std::string& exception = "");

private:
static bool m_handing_error;
#ifdef WIN32
LONG WINAPI supertux_seh_handler(_In_ _EXCEPTION_POINTERS* ExceptionInfo);
//CONTEXT* pcontext;
#else
[[ noreturn ]] void handle_error(int sig);
#endif
void report_error(const std::string& details);

private:
ErrorHandler() = delete;
~ErrorHandler() = delete;
ErrorHandler(const ErrorHandler&) = delete;
ErrorHandler& operator=(const ErrorHandler&) = delete;
};
[[ noreturn ]] void close_program();
}

#endif

Expand Down

0 comments on commit 66a150b

Please sign in to comment.