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:
parent
c60e87870a
commit
6160a6b7d5
@ -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,
|
||||
|
@ -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'
|
||||
)
|
||||
|
@ -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];
|
||||
|
||||
timeData = localtime(&unixtime);
|
||||
if (local)
|
||||
{
|
||||
timeData = localtime(&unixtime);
|
||||
}
|
||||
else
|
||||
{
|
||||
timeData = gmtime(&unixtime);
|
||||
}
|
||||
|
||||
strftime(buffer, 128, dateFormat.c_str(), timeData);
|
||||
return ByteString(buffer);
|
||||
|
@ -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 &);
|
||||
|
@ -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
23
src/common/Defer.h
Normal 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();
|
||||
}
|
||||
}
|
||||
};
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,11 @@ common_files += files(
|
||||
'Common.cpp',
|
||||
)
|
||||
|
||||
use_bluescreen = not is_debug
|
||||
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())
|
||||
|
31
src/common/platform/stacktrace/Execinfo.cpp
Normal file
31
src/common/platform/stacktrace/Execinfo.cpp
Normal 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;
|
||||
}
|
||||
}
|
9
src/common/platform/stacktrace/Null.cpp
Normal file
9
src/common/platform/stacktrace/Null.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
#include "common/platform/Platform.h"
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
std::optional<std::vector<String>> StackTrace(StackTraceType)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
130
src/common/platform/stacktrace/Windows.cpp
Normal file
130
src/common/platform/stacktrace/Windows.cpp
Normal 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;
|
||||
}
|
||||
}
|
17
src/common/platform/stacktrace/meson.build
Normal file
17
src/common/platform/stacktrace/meson.build
Normal 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
|
Loading…
Reference in New Issue
Block a user