diff --git a/meson.build b/meson.build index 2530bcea6..1db48e960 100644 --- a/meson.build +++ b/meson.build @@ -365,6 +365,8 @@ project_deps = [] data_files = [] powder_deps = [] +project_export_dynamic = false + subdir('src') subdir('resources') @@ -411,6 +413,7 @@ if get_option('build_powder') win_subsystem: is_debug ? 'console' : 'windows', link_args: project_link_args, dependencies: powder_deps, + export_dynamic: project_export_dynamic, install: true, ) endif @@ -439,6 +442,7 @@ if get_option('build_render') cpp_args: project_cpp_args, link_args: render_link_args, dependencies: render_deps, + export_dynamic: project_export_dynamic, ) endif @@ -462,5 +466,6 @@ if get_option('build_font') cpp_args: project_cpp_args, link_args: project_link_args, dependencies: font_deps, + export_dynamic: project_export_dynamic, ) endif diff --git a/src/PowderToy.cpp b/src/PowderToy.cpp index d138571ba..6dbe975be 100644 --- a/src/PowderToy.cpp +++ b/src/PowderToy.cpp @@ -22,6 +22,7 @@ #include "gui/dialogues/ConfirmPrompt.h" #include "gui/dialogues/ErrorMessage.h" #include "gui/interface/Engine.h" +#include "gui/interface/TextWrapper.h" #include "Config.h" #include "SimulationConfig.h" #include @@ -104,11 +105,6 @@ static void BlueScreen(String detailMessage, std::optional> auto &engine = ui::Engine::Ref(); engine.g->BlendFilledRect(engine.g->Size().OriginRect(), 0x1172A9_rgb .WithAlpha(0xD2)); - String errorText; - auto addParapgraph = [&errorText](String str) { - errorText += str + "\n\n"; - }; - auto crashPrevLogPath = ByteString("crash.prev.log"); auto crashLogPath = ByteString("crash.log"); Platform::RenameFile(crashLogPath, crashPrevLogPath, true); @@ -133,11 +129,14 @@ static void BlueScreen(String detailMessage, std::optional> { 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)); + String errorText = crashInfo.Build(); + constexpr auto width = 440; + ui::TextWrapper tw; + tw.Update(errorText, true, width); + engine.g->BlendText(ui::Point((engine.g->Size().X - width) / 2, 80), tw.WrappedText(), 0xFFFFFF_rgb .WithAlpha(0xFF)); auto crashLogData = errorText.ToUtf8(); + std::cerr << crashLogData << std::endl; Platform::WriteFile(std::vector(crashLogData.begin(), crashLogData.end()), crashLogPath); //Death loop diff --git a/src/common/platform/meson.build b/src/common/platform/meson.build index 0856b35c2..230c078a1 100644 --- a/src/common/platform/meson.build +++ b/src/common/platform/meson.build @@ -78,10 +78,15 @@ else ) endif +bluescreen_export_symbols = false + subdir('stacktrace') if use_bluescreen common_files += stacktrace_files + if bluescreen_export_symbols + project_export_dynamic = true + endif else common_files += files('stacktrace/Null.cpp') endif diff --git a/src/common/platform/stacktrace/Execinfo.cpp b/src/common/platform/stacktrace/Execinfo.cpp index 041b54fa6..d0e309652 100644 --- a/src/common/platform/stacktrace/Execinfo.cpp +++ b/src/common/platform/stacktrace/Execinfo.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include namespace Platform { @@ -19,7 +21,27 @@ std::optional> StackTrace() { if (strs) { - res.push_back(ByteString(strs[i]).FromUtf8()); + auto line = ByteString(strs[i]); + if (auto beginSymbolName = line.SplitBy('(')) + { + auto afterBeginSymbolName = beginSymbolName.After(); + if (auto endSymbolName = afterBeginSymbolName.SplitBy('+')) + { + auto beforeSymbolName = beginSymbolName.Before(); + auto symbolName = endSymbolName.Before(); + auto afterSymbolName = endSymbolName.After(); + int status; + char *demangled = abi::__cxa_demangle(symbolName.c_str(), NULL, NULL, &status); + Defer freeDemangled([demangled]() { + free(demangled); + }); + if (!status) + { + line = ByteString::Build(beforeSymbolName, "(", demangled, "+", afterSymbolName); + } + } + } + res.push_back(line.FromUtf8()); } else { diff --git a/src/common/platform/stacktrace/Windows.cpp b/src/common/platform/stacktrace/Windows.cpp index f23ab803e..9b2069535 100644 --- a/src/common/platform/stacktrace/Windows.cpp +++ b/src/common/platform/stacktrace/Windows.cpp @@ -7,9 +7,46 @@ #include #include #include +#include namespace Platform { +struct SymbolInfo +{ + String name; + uintptr_t displacement; +}; +static std::optional GetSymbolInfo(HANDLE process, uintptr_t offset) +{ + DWORD64 displacement; + std::array symbolData{}; + auto &symbol = *reinterpret_cast(&symbolData[0]); + symbol.SizeOfStruct = sizeof(symbol); + symbol.MaxNameLen = symbolData.size() - sizeof(symbol); + if (SymFromAddrW(process, offset, &displacement, &symbol)) + { + return SymbolInfo{ WinNarrow(&symbol.Name[0]).FromUtf8(), uintptr_t(displacement) }; + } + return std::nullopt; +} + +struct ModuleInfo +{ + String name; + uintptr_t displacement; +}; +static std::optional GetModuleInfo(HANDLE process, uintptr_t offset) +{ + IMAGEHLP_MODULEW64 module{}; + module.SizeOfStruct = sizeof(module); + if (SymGetModuleInfoW64(process, offset, &module)) + { + auto displacement = offset - uintptr_t(module.BaseOfImage); + return ModuleInfo{ WinNarrow(&module.LoadedImageName[0]).FromUtf8(), displacement }; + } + return std::nullopt; +} + std::optional> StackTrace() { static std::mutex mx; @@ -22,13 +59,11 @@ std::optional> StackTrace() }); SymInitialize(process, NULL, TRUE); - CONTEXT context; - memset(&context, 0, sizeof(context)); + CONTEXT context{}; context.ContextFlags = CONTEXT_FULL; RtlCaptureContext(&context); - STACKFRAME64 frame; - memset(&frame, 0, sizeof(frame)); + STACKFRAME64 frame{}; DWORD machine; #if defined(_M_IX86) machine = IMAGE_FILE_MACHINE_I386; @@ -71,24 +106,29 @@ std::optional> StackTrace() std::vector res; for (auto i = 0; i < 100; ++i) { - if (!StackWalk64( machine, process, thread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) + if (!StackWalk64(machine, process, thread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { break; } + auto offset = uintptr_t(frame.AddrPC.Offset); StringBuilder addr; - addr << "0x" << Format::Hex() << uintptr_t(frame.AddrPC.Offset); - std::array 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))) + addr << Format::Hex(); + if (auto moduleInfo = GetModuleInfo(process, offset)) { - auto offset = uintptr_t(frame.AddrPC.Offset) - uintptr_t(moduleInfo.lpBaseOfDll); - if (offset < moduleInfo.SizeOfImage) + addr << moduleInfo->name << "("; + if (auto symbolInfo = GetSymbolInfo(process, offset)) { - addr << " (" << WinNarrow(&moduleBaseName[0]).FromUtf8() << "+0x" << Format::Hex() << offset << ")"; + addr << symbolInfo->name << "+0x" << symbolInfo->displacement; } + else + { + addr << "+0x" << moduleInfo->displacement; + } + addr << ") [0x" << offset << "]"; + } + else + { + addr << "0x" << offset; } res.push_back(addr.Build()); } diff --git a/src/common/platform/stacktrace/meson.build b/src/common/platform/stacktrace/meson.build index 5e7ccc1b1..572b90bc2 100644 --- a/src/common/platform/stacktrace/meson.build +++ b/src/common/platform/stacktrace/meson.build @@ -9,9 +9,13 @@ if host_platform == 'windows' elif host_platform == 'darwin' # TODO: good impl; current one is only slightly better than nothing stacktrace_files = files('Execinfo.cpp') + # export symbols so backtrace_symbols works, see https://www.gnu.org/software/libc/manual/html_node/Backtraces.html + bluescreen_export_symbols = true elif host_platform == 'linux' # TODO: again, this is more like "posix" than "linux" stacktrace_files = files('Execinfo.cpp') + # export symbols so backtrace_symbols works, see above + bluescreen_export_symbols = true else stacktrace_files = files('Null.cpp') endif