Add stack trace to bluescreen on some systems

This is still complete nonsense on posix systems where bluescreen runs in a signal handler, but what can you do.
This commit is contained in:
Tamás Bálint Misius 2024-01-01 09:34:44 +01:00
parent c60e87870a
commit 6160a6b7d5
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
12 changed files with 290 additions and 23 deletions

View File

@ -361,6 +361,7 @@ else
ident_platform = 'UNKNOWN'
endif
project_deps = []
data_files = []
powder_deps = []
@ -378,7 +379,7 @@ if host_platform == 'emscripten'
endif
if get_option('build_powder')
powder_deps += [
powder_deps += project_deps + [
threads_dep,
zlib_dep,
png_dep,
@ -419,7 +420,7 @@ if get_option('build_render')
if host_platform == 'emscripten'
error('render does not target emscripten')
endif
render_deps = [
render_deps = project_deps + [
threads_dep,
zlib_dep,
bzip2_dep,
@ -445,7 +446,7 @@ if get_option('build_font')
if host_platform == 'emscripten'
error('font does not target emscripten')
endif
font_deps = [
font_deps = project_deps + [
threads_dep,
zlib_dep,
png_dep,

View File

@ -286,3 +286,10 @@ option(
value: true,
description: 'Enable platform clipboard, allows copying simulation data between different windows'
)
option(
'use_bluescreen',
type: 'combo',
choices: [ 'no', 'yes', 'auto' ],
value: 'auto',
description: 'Show blue error screen upon unhandled signals and exceptions'
)

View File

@ -10,12 +10,19 @@
#include "Format.h"
#include "graphics/Graphics.h"
ByteString format::UnixtimeToDate(time_t unixtime, ByteString dateFormat)
ByteString format::UnixtimeToDate(time_t unixtime, ByteString dateFormat, bool local)
{
struct tm * timeData;
char buffer[128];
if (local)
{
timeData = localtime(&unixtime);
}
else
{
timeData = gmtime(&unixtime);
}
strftime(buffer, 128, dateFormat.c_str(), timeData);
return ByteString(buffer);

View File

@ -11,7 +11,7 @@ namespace format
{
ByteString URLEncode(ByteString value);
ByteString URLDecode(ByteString value);
ByteString UnixtimeToDate(time_t unixtime, ByteString dateFomat = ByteString("%d %b %Y"));
ByteString UnixtimeToDate(time_t unixtime, ByteString dateFomat = ByteString("%d %b %Y"), bool local = true);
ByteString UnixtimeToDateMini(time_t unixtime);
String CleanString(String dirtyString, bool ascii, bool color, bool newlines, bool numeric = false);
std::vector<char> PixelsToPPM(PlaneAdapter<std::vector<pixel>> const &);

View File

@ -97,25 +97,46 @@ void TickClient()
Client::Ref().Tick();
}
void BlueScreen(String detailMessage)
void BlueScreen(String detailMessage, std::optional<std::vector<String>> stackTrace)
{
auto &engine = ui::Engine::Ref();
engine.g->BlendFilledRect(engine.g->Size().OriginRect(), 0x1172A9_rgb .WithAlpha(0xD2));
String errorTitle = "ERROR";
String errorDetails = "Details: " + detailMessage;
String errorHelp = String("An unrecoverable fault has occurred, please report the error by visiting the website below\n") + SCHEME + SERVER;
auto versionInfo = ByteString::Build("Version: ", VersionInfo(), "\nTag: ", VCS_TAG).FromUtf8();
String errorText;
auto addParapgraph = [&errorText](String str) {
errorText += str + "\n\n";
};
// We use the width of errorHelp to center, but heights of the individual texts for vertical spacing
auto pos = engine.g->Size() / 2 - Vec2(Graphics::TextSize(errorHelp).X / 2, 100);
engine.g->BlendText(pos, errorTitle, 0xFFFFFF_rgb .WithAlpha(0xFF));
pos.Y += 4 + Graphics::TextSize(errorTitle).Y;
engine.g->BlendText(pos, errorDetails, 0xFFFFFF_rgb .WithAlpha(0xFF));
pos.Y += 4 + Graphics::TextSize(errorDetails).Y;
engine.g->BlendText(pos, errorHelp, 0xFFFFFF_rgb .WithAlpha(0xFF));
pos.Y += 4 + Graphics::TextSize(errorHelp).Y;
engine.g->BlendText(pos, versionInfo, 0xFFFFFF_rgb .WithAlpha(0xFF));
auto crashPrevLogPath = ByteString("crash.prev.log");
auto crashLogPath = ByteString("crash.log");
Platform::RenameFile(crashLogPath, crashPrevLogPath, true);
StringBuilder crashInfo;
crashInfo << "ERROR - Details: " << detailMessage << "\n";
crashInfo << "An unrecoverable fault has occurred, please report it by visiting the website below\n\n " << SCHEME << SERVER << "\n\n";
crashInfo << "An attempt will be made to save all of this information to " << crashLogPath.FromUtf8() << " in your data folder.\n";
crashInfo << "Please attach this file to your report.\n\n";
crashInfo << "Version: " << VersionInfo().FromUtf8() << "\n";
crashInfo << "Tag: " << VCS_TAG << "\n";
crashInfo << "Date: " << format::UnixtimeToDate(time(NULL), "%Y-%m-%dT%H:%M:%SZ", false).FromUtf8() << "\n";
if (stackTrace)
{
crashInfo << "Stack trace:\n";
for (auto &item : *stackTrace)
{
crashInfo << " - " << item << "\n";
}
}
else
{
crashInfo << "Stack trace not available\n";
}
addParapgraph(crashInfo.Build());
engine.g->BlendText(ui::Point((engine.g->Size().X - 440) / 2, 80), errorText, 0xFFFFFF_rgb .WithAlpha(0xFF));
auto crashLogData = errorText.ToUtf8();
Platform::WriteFile(std::vector<char>(crashLogData.begin(), crashLogData.end()), crashLogPath);
//Death loop
SDL_Event event;
@ -151,7 +172,7 @@ void SigHandler(int signal)
break;
}
}
BlueScreen(ByteString(message).FromUtf8());
BlueScreen(ByteString(message).FromUtf8(), Platform::StackTrace(Platform::stackTraceFromHere));
}
constexpr int SCALE_MAXIMUM = 10;
@ -518,7 +539,7 @@ int Main(int argc, char *argv[])
}
catch (const std::exception &e)
{
BlueScreen(ByteString(e.what()).FromUtf8());
BlueScreen(ByteString(e.what()).FromUtf8(), Platform::StackTrace(Platform::stackTraceFromException));
}
}
else

23
src/common/Defer.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include <functional>
class Defer
{
std::function<void ()> func;
public:
Defer(std::function<void ()> newFunc) : func(newFunc)
{
}
Defer(const Defer &other) = delete;
Defer &operator =(const Defer &other) = delete;
~Defer()
{
if (func)
{
func();
}
}
};

View File

@ -1,6 +1,8 @@
#pragma once
#include "common/String.h"
#include <string>
#include <vector>
#include <optional>
namespace Platform
{
@ -67,6 +69,13 @@ namespace Platform
int InvokeMain(int argc, char *argv[]);
enum StackTraceType
{
stackTraceFromHere,
stackTraceFromException,
};
std::optional<std::vector<String>> StackTrace(StackTraceType StackTraceType);
void MarkPresentable();
}

View File

@ -2,7 +2,11 @@ common_files += files(
'Common.cpp',
)
if get_option('use_bluescreen') == 'auto'
use_bluescreen = not is_debug
else
use_bluescreen = get_option('use_bluescreen') == 'yes'
endif
can_install_enforce_no = false
set_window_icon = false
path_sep_char = '/'
@ -73,6 +77,14 @@ else
'DdirCommon.cpp',
)
endif
subdir('stacktrace')
if use_bluescreen
common_files += stacktrace_files
else
common_files += files('stacktrace/Null.cpp')
endif
conf_data.set('SET_WINDOW_ICON', set_window_icon.to_string())
conf_data.set('PATH_SEP_CHAR', path_sep_char)
conf_data.set('USE_BLUESCREEN', use_bluescreen.to_string())

View File

@ -0,0 +1,31 @@
#include "common/platform/Platform.h"
#include "common/Defer.h"
#include <execinfo.h>
#include <cstdint>
#include <array>
namespace Platform
{
std::optional<std::vector<String>> StackTrace(StackTraceType)
{
std::array<void *, 100> buf;
auto used = backtrace(&buf[0], buf.size());
auto *strs = backtrace_symbols(&buf[0], used);
Defer freeStrs([strs]() {
free(strs);
});
std::vector<String> res;
for (auto i = 0; i < used; ++i)
{
if (strs)
{
res.push_back(ByteString(strs[i]).FromUtf8());
}
else
{
res.push_back(String::Build("0x", Format::Hex(), uintptr_t(buf[i])));
}
}
return res;
}
}

View File

@ -0,0 +1,9 @@
#include "common/platform/Platform.h"
namespace Platform
{
std::optional<std::vector<String>> StackTrace(StackTraceType)
{
return std::nullopt;
}
}

View File

@ -0,0 +1,130 @@
#include "common/platform/Platform.h"
#include "common/Defer.h"
#include <windows.h>
#pragma pack(push, 8)
#include <dbghelp.h>
#pragma pack(pop)
#include <psapi.h>
#include <array>
#include <mutex>
#if defined(_MSC_VER) && _MSC_VER >= 1900
extern "C" void **__cdecl __current_exception_context();
#endif
static CONTEXT *CurrentExceptionContext()
{
// TODO: find the corresponding hack for mingw and older msvc; stack traces printed for exceptions are broken without it
CONTEXT **pctx = nullptr;
#if defined(_MSC_VER) && _MSC_VER >= 1900
pctx = (CONTEXT **)__current_exception_context();
#endif
return pctx ? *pctx : nullptr;
}
namespace Platform
{
std::optional<std::vector<String>> StackTrace(StackTraceType stackTraceType)
{
static std::mutex mx;
std::unique_lock lk(mx);
auto process = GetCurrentProcess();
auto thread = GetCurrentThread();
Defer symCleanup([process]() {
SymCleanup(process);
});
SymInitialize(process, NULL, TRUE);
CONTEXT context;
memset(&context, 0, sizeof(context));
switch (stackTraceType)
{
case stackTraceFromException:
if (auto *ec = CurrentExceptionContext())
{
if (ec->ContextFlags)
{
context = *ec;
}
}
if (!context.ContextFlags)
{
return std::nullopt;
}
break;
default:
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);
break;
}
STACKFRAME64 frame;
memset(&frame, 0, sizeof(frame));
DWORD machine;
#if defined(_M_IX86)
machine = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = context.Eip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Esp;
frame.AddrStack.Mode = AddrModeFlat;
#elif defined(_M_X64)
machine = IMAGE_FILE_MACHINE_AMD64;
frame.AddrPC.Offset = context.Rip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Rsp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Rsp;
frame.AddrStack.Mode = AddrModeFlat;
#elif defined(_M_IA64)
machine = IMAGE_FILE_MACHINE_IA64;
frame.AddrPC.Offset = context.StIIP;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.IntSp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrBStore.Offset = context.RsBSP;
frame.AddrBStore.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.IntSp;
frame.AddrStack.Mode = AddrModeFlat;
#elif defined(_M_ARM64)
machine = IMAGE_FILE_MACHINE_ARM64;
frame.AddrPC.Offset = context.Pc;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Fp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Sp;
frame.AddrStack.Mode = AddrModeFlat;
#else
return std::nullopt;
#endif
std::vector<String> res;
for (auto i = 0; i < 100; ++i)
{
if (!StackWalk64( machine, process, thread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
{
break;
}
StringBuilder addr;
addr << "0x" << Format::Hex() << uintptr_t(frame.AddrPC.Offset);
std::array<wchar_t, 1000> moduleBaseName;
HMODULE module;
MODULEINFO moduleInfo;
if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCWSTR)frame.AddrPC.Offset, &module) &&
GetModuleBaseNameW(process, module, &moduleBaseName[0], moduleBaseName.size()) &&
GetModuleInformation(process, module, &moduleInfo, sizeof(moduleInfo)))
{
auto offset = uintptr_t(frame.AddrPC.Offset) - uintptr_t(moduleInfo.lpBaseOfDll);
if (offset < moduleInfo.SizeOfImage)
{
addr << " (" << WinNarrow(&moduleBaseName[0]).FromUtf8() << "+0x" << Format::Hex() << offset << ")";
}
}
res.push_back(addr.Build());
}
return res;
}
}

View File

@ -0,0 +1,17 @@
if host_platform == 'windows'
if use_bluescreen
project_deps += [
c_compiler.find_library('dbghelp'),
c_compiler.find_library('psapi'),
]
endif
stacktrace_files = files('Windows.cpp')
elif host_platform == 'darwin'
# stacktrace_files = files('Execinfo.cpp') # macos has this but it's useless >_>
stacktrace_files = files('Null.cpp')
elif host_platform == 'linux'
# TODO: again, this is more like "posix" than "linux"
stacktrace_files = files('Execinfo.cpp')
else
stacktrace_files = files('Null.cpp')
endif