You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
981 lines
35 KiB
C++
981 lines
35 KiB
C++
/*
|
|
* This file is part of OpenTTD.
|
|
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
|
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** @file crashlog_win.cpp Implementation of a crashlogger for Windows */
|
|
|
|
#include "../../stdafx.h"
|
|
#include "../../crashlog.h"
|
|
#include "../../crashlog_bfd.h"
|
|
#include "win32.h"
|
|
#include "../../core/alloc_func.hpp"
|
|
#include "../../core/math_func.hpp"
|
|
#include "../../string_func.h"
|
|
#include "../../fileio_func.h"
|
|
#include "../../strings_func.h"
|
|
#include "../../gamelog.h"
|
|
#include "../../sl/saveload.h"
|
|
#include "../../video/video_driver.hpp"
|
|
#include "../../library_loader.h"
|
|
#include "../../screenshot.h"
|
|
#include "../../debug.h"
|
|
#include "../../settings_type.h"
|
|
#include "../../thread.h"
|
|
#include "../../walltime_func.h"
|
|
#include "../../scope.h"
|
|
#if defined(WITH_DEMANGLE)
|
|
#include <cxxabi.h>
|
|
#endif
|
|
|
|
#include <windows.h>
|
|
#include <mmsystem.h>
|
|
#include <signal.h>
|
|
#include <psapi.h>
|
|
#include <memoryapi.h>
|
|
#if defined(_MSC_VER)
|
|
#include <excpt.h>
|
|
#else
|
|
#include <setjmp.h>
|
|
#endif
|
|
#include <atomic>
|
|
|
|
#include "../../safeguards.h"
|
|
|
|
/* printf format specification for 32/64-bit addresses. */
|
|
#ifdef _M_AMD64
|
|
#define PRINTF_PTR "0x%016" PRINTF_SIZEX_SUFFIX
|
|
#define PRINTF_LOC "%.16" PRINTF_SIZEX_SUFFIX
|
|
#else
|
|
#define PRINTF_PTR "0x%08" PRINTF_SIZEX_SUFFIX
|
|
#define PRINTF_LOC "%.8" PRINTF_SIZEX_SUFFIX
|
|
#endif
|
|
|
|
#if !defined(_MSC_VER) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
|
|
#pragma GCC diagnostic ignored "-Wclobbered"
|
|
#endif
|
|
|
|
[[noreturn]] static void 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.
|
|
*/
|
|
class CrashLogWindows : public CrashLog {
|
|
/** Information about the encountered exception */
|
|
EXCEPTION_POINTERS *ep;
|
|
|
|
public:
|
|
DWORD crash_thread_id;
|
|
std::atomic<uint32_t> other_crash_threads;
|
|
|
|
char *LogOSVersion(char *buffer, const char *last) const override;
|
|
char *LogError(char *buffer, const char *last, const char *message) const override;
|
|
#if defined(_MSC_VER) || defined(WITH_DBGHELP)
|
|
char *LogStacktrace(char *buffer, const char *last) const override;
|
|
#endif /* _MSC_VER || WITH_DBGHELP */
|
|
char *LogRegisters(char *buffer, const char *last) const override;
|
|
char *LogCrashTrailer(char *buffer, const char *last) const override;
|
|
|
|
protected:
|
|
char *TryCrashLogFaultSection(char *buffer, const char *last, const char *section_name, CrashLogSectionWriter writer) override;
|
|
void CrashLogFaultSectionCheckpoint(char *buffer) const override;
|
|
|
|
public:
|
|
#if defined(_MSC_VER)
|
|
int WriteCrashDump(char *filename, const char *filename_last) const override;
|
|
#endif /* _MSC_VER */
|
|
|
|
/** Buffer for the generated crash log */
|
|
std::span<char> crashlog_buffer;
|
|
|
|
/**
|
|
* 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), crash_thread_id(GetCurrentThreadId())
|
|
{
|
|
this->crashlog_filename[0] = '\0';
|
|
this->crashdump_filename[0] = '\0';
|
|
this->screenshot_filename[0] = '\0';
|
|
this->name_buffer[0] = '\0';
|
|
}
|
|
|
|
/**
|
|
* Points to the current crash log.
|
|
*/
|
|
static std::atomic<CrashLogWindows *> current;
|
|
|
|
char *internal_fault_saved_buffer = nullptr;
|
|
#if !defined(_MSC_VER)
|
|
jmp_buf internal_fault_jmp_buf;
|
|
#endif
|
|
};
|
|
|
|
/* static */ std::atomic<CrashLogWindows *> CrashLogWindows::current = nullptr;
|
|
|
|
/* virtual */ char *CrashLogWindows::LogOSVersion(char *buffer, const char *last) const
|
|
{
|
|
_OSVERSIONINFOA os;
|
|
os.dwOSVersionInfoSize = sizeof(os);
|
|
GetVersionExA(&os);
|
|
|
|
return buffer + seprintf(buffer, last,
|
|
"Operating system:\n"
|
|
" Name: Windows\n"
|
|
" Release: %d.%d.%d (%s)\n",
|
|
(int)os.dwMajorVersion,
|
|
(int)os.dwMinorVersion,
|
|
(int)os.dwBuildNumber,
|
|
os.szCSDVersion
|
|
);
|
|
|
|
}
|
|
|
|
static const char *GetAccessViolationTypeString(uint type)
|
|
{
|
|
switch (type) {
|
|
case 0:
|
|
return "read";
|
|
case 1:
|
|
return "write";
|
|
case 8:
|
|
return "user-mode DEP";
|
|
default:
|
|
return "???";
|
|
}
|
|
}
|
|
|
|
/* virtual */ char *CrashLogWindows::LogError(char *buffer, const char *last, const char *message) const
|
|
{
|
|
buffer += seprintf(buffer, last, "Crash reason:\n");
|
|
for (auto record = ep->ExceptionRecord; record != nullptr; record = record->ExceptionRecord) {
|
|
buffer += seprintf(buffer, last,
|
|
" Exception: %.8X\n"
|
|
" Location: " PRINTF_LOC "\n",
|
|
(int)record->ExceptionCode,
|
|
(size_t)record->ExceptionAddress
|
|
);
|
|
if (record->ExceptionCode == 0xC0000005 && record->NumberParameters == 2) {
|
|
buffer += seprintf(buffer, last,
|
|
" Fault type: %u (%s)\n"
|
|
" Fault addr: " PRINTF_LOC "\n",
|
|
(uint) record->ExceptionInformation[0],
|
|
GetAccessViolationTypeString(record->ExceptionInformation[0]),
|
|
(size_t)record->ExceptionInformation[1]
|
|
);
|
|
} else {
|
|
for (uint i = 0; i < (uint) record->NumberParameters; i++) {
|
|
buffer += seprintf(buffer, last,
|
|
" Info %u: " PRINTF_LOC "\n",
|
|
i,
|
|
(size_t)record->ExceptionInformation[i]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
this->CrashLogFaultSectionCheckpoint(buffer);
|
|
buffer += seprintf(buffer, last, " Message: %s\n\n",
|
|
message == nullptr ? "<none>" : message);
|
|
|
|
if (message != nullptr && strcasestr(message, "out of memory") != nullptr) {
|
|
PROCESS_MEMORY_COUNTERS pmc;
|
|
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
|
|
buffer += seprintf(buffer, last, " WorkingSetSize: " PRINTF_SIZE "\n", (size_t)pmc.WorkingSetSize);
|
|
buffer += seprintf(buffer, last, " PeakWorkingSetSize: " PRINTF_SIZE "\n", (size_t)pmc.PeakWorkingSetSize);
|
|
buffer += seprintf(buffer, last, " QuotaPagedPoolUsage: " PRINTF_SIZE "\n", (size_t)pmc.QuotaPagedPoolUsage);
|
|
buffer += seprintf(buffer, last, " QuotaPeakPagedPoolUsage: " PRINTF_SIZE "\n", (size_t)pmc.QuotaPeakPagedPoolUsage);
|
|
buffer += seprintf(buffer, last, " QuotaNonPagedPoolUsage: " PRINTF_SIZE "\n", (size_t)pmc.QuotaNonPagedPoolUsage);
|
|
buffer += seprintf(buffer, last, " QuotaPeakNonPagedPoolUsage: " PRINTF_SIZE "\n", (size_t)pmc.QuotaPeakNonPagedPoolUsage);
|
|
buffer += seprintf(buffer, last, " PagefileUsage: " PRINTF_SIZE "\n", (size_t)pmc.PagefileUsage);
|
|
buffer += seprintf(buffer, last, " PeakPagefileUsage: " PRINTF_SIZE "\n\n", (size_t)pmc.PeakPagefileUsage);
|
|
}
|
|
PERFORMANCE_INFORMATION perf;
|
|
if (GetPerformanceInfo(&perf, sizeof(perf))) {
|
|
buffer += seprintf(buffer, last, " CommitTotal: " PRINTF_SIZE "\n", (size_t)perf.CommitTotal);
|
|
buffer += seprintf(buffer, last, " CommitLimit: " PRINTF_SIZE "\n", (size_t)perf.CommitLimit);
|
|
buffer += seprintf(buffer, last, " CommitPeak: " PRINTF_SIZE "\n", (size_t)perf.CommitPeak);
|
|
buffer += seprintf(buffer, last, " PhysicalTotal: " PRINTF_SIZE "\n", (size_t)perf.PhysicalTotal);
|
|
buffer += seprintf(buffer, last, " PhysicalAvailable: " PRINTF_SIZE "\n", (size_t)perf.PhysicalAvailable);
|
|
buffer += seprintf(buffer, last, " SystemCache: " PRINTF_SIZE "\n", (size_t)perf.SystemCache);
|
|
buffer += seprintf(buffer, last, " KernelTotal: " PRINTF_SIZE "\n", (size_t)perf.KernelTotal);
|
|
buffer += seprintf(buffer, last, " KernelPaged: " PRINTF_SIZE "\n", (size_t)perf.KernelPaged);
|
|
buffer += seprintf(buffer, last, " KernelNonpaged: " PRINTF_SIZE "\n", (size_t)perf.KernelNonpaged);
|
|
buffer += seprintf(buffer, last, " PageSize: " PRINTF_SIZE "\n", (size_t)perf.PageSize);
|
|
buffer += seprintf(buffer, last, " HandleCount: %u\n", (uint)perf.HandleCount);
|
|
buffer += seprintf(buffer, last, " ProcessCount: %u\n", (uint)perf.ProcessCount);
|
|
buffer += seprintf(buffer, last, " ThreadCount: %u\n\n", (uint)perf.ThreadCount);
|
|
}
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/* virtual */ char *CrashLogWindows::LogRegisters(char *buffer, const char *last) const
|
|
{
|
|
buffer += seprintf(buffer, last, "Registers:\n");
|
|
#ifdef _M_AMD64
|
|
buffer += seprintf(buffer, last,
|
|
" RAX: " PRINTF_LOC " RBX: " PRINTF_LOC " RCX: " PRINTF_LOC " RDX: " PRINTF_LOC "\n"
|
|
" RSI: " PRINTF_LOC " RDI: " PRINTF_LOC " RBP: " PRINTF_LOC " RSP: " PRINTF_LOC "\n"
|
|
" R8: " PRINTF_LOC " R9: " PRINTF_LOC " R10: " PRINTF_LOC " R11: " PRINTF_LOC "\n"
|
|
" R12: " PRINTF_LOC " R13: " PRINTF_LOC " R14: " PRINTF_LOC " R15: " PRINTF_LOC "\n"
|
|
" RIP: " PRINTF_LOC " EFLAGS: %.8lX\n",
|
|
ep->ContextRecord->Rax,
|
|
ep->ContextRecord->Rbx,
|
|
ep->ContextRecord->Rcx,
|
|
ep->ContextRecord->Rdx,
|
|
ep->ContextRecord->Rsi,
|
|
ep->ContextRecord->Rdi,
|
|
ep->ContextRecord->Rbp,
|
|
ep->ContextRecord->Rsp,
|
|
ep->ContextRecord->R8,
|
|
ep->ContextRecord->R9,
|
|
ep->ContextRecord->R10,
|
|
ep->ContextRecord->R11,
|
|
ep->ContextRecord->R12,
|
|
ep->ContextRecord->R13,
|
|
ep->ContextRecord->R14,
|
|
ep->ContextRecord->R15,
|
|
ep->ContextRecord->Rip,
|
|
ep->ContextRecord->EFlags
|
|
);
|
|
#elif defined(_M_IX86)
|
|
buffer += seprintf(buffer, last,
|
|
" EAX: %.8X EBX: %.8X ECX: %.8X EDX: %.8X\n"
|
|
" ESI: %.8X EDI: %.8X EBP: %.8X ESP: %.8X\n"
|
|
" EIP: %.8X EFLAGS: %.8X\n",
|
|
(int)ep->ContextRecord->Eax,
|
|
(int)ep->ContextRecord->Ebx,
|
|
(int)ep->ContextRecord->Ecx,
|
|
(int)ep->ContextRecord->Edx,
|
|
(int)ep->ContextRecord->Esi,
|
|
(int)ep->ContextRecord->Edi,
|
|
(int)ep->ContextRecord->Ebp,
|
|
(int)ep->ContextRecord->Esp,
|
|
(int)ep->ContextRecord->Eip,
|
|
(int)ep->ContextRecord->EFlags
|
|
);
|
|
#elif defined(_M_ARM64)
|
|
buffer += seprintf(buffer, last,
|
|
" X0: " PRINTF_LOC " X1: " PRINTF_LOC " X2: " PRINTF_LOC " X3: " PRINTF_LOC "\n"
|
|
" X4: " PRINTF_LOC " X5: " PRINTF_LOC " X6: " PRINTF_LOC " X7: " PRINTF_LOC "\n"
|
|
" X8: " PRINTF_LOC " X9: " PRINTF_LOC " X10: " PRINTF_LOC " X11: " PRINTF_LOC "\n"
|
|
" X12: " PRINTF_LOC " X13: " PRINTF_LOC " X14: " PRINTF_LOC " X15: " PRINTF_LOC "\n"
|
|
" X16: " PRINTF_LOC " X17: " PRINTF_LOC " X18: " PRINTF_LOC " X19: " PRINTF_LOC "\n"
|
|
" X20: " PRINTF_LOC " X21: " PRINTF_LOC " X22: " PRINTF_LOC " X23: " PRINTF_LOC "\n"
|
|
" X24: " PRINTF_LOC " X25: " PRINTF_LOC " X26: " PRINTF_LOC " X27: " PRINTF_LOC "\n"
|
|
" X28: " PRINTF_LOC " Fp: " PRINTF_LOC " Lr: " PRINTF_LOC "\n",
|
|
ep->ContextRecord->X0,
|
|
ep->ContextRecord->X1,
|
|
ep->ContextRecord->X2,
|
|
ep->ContextRecord->X3,
|
|
ep->ContextRecord->X4,
|
|
ep->ContextRecord->X5,
|
|
ep->ContextRecord->X6,
|
|
ep->ContextRecord->X7,
|
|
ep->ContextRecord->X8,
|
|
ep->ContextRecord->X9,
|
|
ep->ContextRecord->X10,
|
|
ep->ContextRecord->X11,
|
|
ep->ContextRecord->X12,
|
|
ep->ContextRecord->X13,
|
|
ep->ContextRecord->X14,
|
|
ep->ContextRecord->X15,
|
|
ep->ContextRecord->X16,
|
|
ep->ContextRecord->X17,
|
|
ep->ContextRecord->X18,
|
|
ep->ContextRecord->X19,
|
|
ep->ContextRecord->X20,
|
|
ep->ContextRecord->X21,
|
|
ep->ContextRecord->X22,
|
|
ep->ContextRecord->X23,
|
|
ep->ContextRecord->X24,
|
|
ep->ContextRecord->X25,
|
|
ep->ContextRecord->X26,
|
|
ep->ContextRecord->X27,
|
|
ep->ContextRecord->X28,
|
|
ep->ContextRecord->Fp,
|
|
ep->ContextRecord->Lr
|
|
);
|
|
#endif
|
|
|
|
this->CrashLogFaultSectionCheckpoint(buffer);
|
|
|
|
buffer += seprintf(buffer, last, "\n Bytes at instruction pointer:\n");
|
|
#ifdef _M_AMD64
|
|
byte *b = (byte*)ep->ContextRecord->Rip;
|
|
#elif defined(_M_IX86)
|
|
byte *b = (byte*)ep->ContextRecord->Eip;
|
|
#elif defined(_M_ARM64)
|
|
byte *b = (byte*)ep->ContextRecord->Pc;
|
|
#endif
|
|
for (int i = 0; i != 24; i++) {
|
|
if (IsBadReadPtr(b, 1)) {
|
|
buffer += seprintf(buffer, last, " ??"); // OCR: WAS: , 0);
|
|
} else {
|
|
buffer += seprintf(buffer, last, " %.2X", *b);
|
|
}
|
|
b++;
|
|
}
|
|
return buffer + seprintf(buffer, last, "\n\n");
|
|
}
|
|
|
|
/**
|
|
* Log crash trailer
|
|
*/
|
|
char *CrashLogWindows::LogCrashTrailer(char *buffer, const char *last) const
|
|
{
|
|
uint32_t other_crashed_threads = this->other_crash_threads.load();
|
|
if (other_crashed_threads > 0) {
|
|
buffer += seprintf(buffer, last, "\n*** %u other threads have also crashed ***\n\n", other_crashed_threads);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
#if defined(_MSC_VER) || defined(WITH_DBGHELP)
|
|
static const uint MAX_SYMBOL_LEN = 512;
|
|
static const uint MAX_FRAMES = 64;
|
|
#if defined(_MSC_VER)
|
|
#pragma warning(disable:4091)
|
|
#endif
|
|
#include <dbghelp.h>
|
|
#if defined(_MSC_VER)
|
|
#pragma warning(default:4091)
|
|
#endif
|
|
|
|
/* virtual */ char *CrashLogWindows::LogStacktrace(char *buffer, const char *last) const
|
|
{
|
|
LibraryLoader dbghelp("dbghelp.dll");
|
|
struct ProcPtrs {
|
|
BOOL (WINAPI * pSymInitialize)(HANDLE, PCSTR, BOOL);
|
|
BOOL (WINAPI * pSymSetOptions)(DWORD);
|
|
BOOL (WINAPI * pSymCleanup)(HANDLE);
|
|
BOOL (WINAPI * pStackWalk64)(DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64);
|
|
PVOID (WINAPI * pSymFunctionTableAccess64)(HANDLE, DWORD64);
|
|
DWORD64 (WINAPI * pSymGetModuleBase64)(HANDLE, DWORD64);
|
|
BOOL (WINAPI * pSymGetModuleInfo64)(HANDLE, DWORD64, PIMAGEHLP_MODULE64);
|
|
BOOL (WINAPI * pSymGetSymFromAddr64)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64);
|
|
BOOL (WINAPI * pSymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
|
|
} proc = {
|
|
dbghelp.GetFunction("SymInitialize"),
|
|
dbghelp.GetFunction("SymSetOptions"),
|
|
dbghelp.GetFunction("SymCleanup"),
|
|
dbghelp.GetFunction("StackWalk64"),
|
|
dbghelp.GetFunction("SymFunctionTableAccess64"),
|
|
dbghelp.GetFunction("SymGetModuleBase64"),
|
|
dbghelp.GetFunction("SymGetModuleInfo64"),
|
|
dbghelp.GetFunction("SymGetSymFromAddr64"),
|
|
dbghelp.GetFunction("SymGetLineFromAddr64"),
|
|
};
|
|
|
|
buffer += seprintf(buffer, last, "Decoded stack trace:\n");
|
|
|
|
/* Try to load the functions from the DLL, if that fails because of a too old dbghelp.dll, just skip it. */
|
|
if (!dbghelp.HasError()) {
|
|
/* Initialize symbol handler. */
|
|
HANDLE hCur = GetCurrentProcess();
|
|
proc.pSymInitialize(hCur, nullptr, TRUE);
|
|
/* Load symbols only when needed, fail silently on errors, demangle symbol names. */
|
|
proc.pSymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_UNDNAME);
|
|
|
|
/* Initialize starting stack frame from context record. */
|
|
STACKFRAME64 frame;
|
|
memset(&frame, 0, sizeof(frame));
|
|
#ifdef _M_AMD64
|
|
frame.AddrPC.Offset = ep->ContextRecord->Rip;
|
|
frame.AddrFrame.Offset = ep->ContextRecord->Rbp;
|
|
frame.AddrStack.Offset = ep->ContextRecord->Rsp;
|
|
#elif defined(_M_IX86)
|
|
frame.AddrPC.Offset = ep->ContextRecord->Eip;
|
|
frame.AddrFrame.Offset = ep->ContextRecord->Ebp;
|
|
frame.AddrStack.Offset = ep->ContextRecord->Esp;
|
|
#elif defined(_M_ARM64)
|
|
frame.AddrPC.Offset = ep->ContextRecord->Pc;
|
|
frame.AddrFrame.Offset = ep->ContextRecord->Fp;
|
|
frame.AddrStack.Offset = ep->ContextRecord->Sp;
|
|
#endif
|
|
frame.AddrPC.Mode = AddrModeFlat;
|
|
frame.AddrFrame.Mode = AddrModeFlat;
|
|
frame.AddrStack.Mode = AddrModeFlat;
|
|
|
|
/* Copy context record as StackWalk64 may modify it. */
|
|
CONTEXT ctx;
|
|
memcpy(&ctx, ep->ContextRecord, sizeof(ctx));
|
|
|
|
/* Allocate space for symbol info. */
|
|
IMAGEHLP_SYMBOL64 *sym_info = (IMAGEHLP_SYMBOL64*)alloca(sizeof(IMAGEHLP_SYMBOL64) + MAX_SYMBOL_LEN - 1);
|
|
sym_info->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
|
sym_info->MaxNameLength = MAX_SYMBOL_LEN;
|
|
|
|
std::array<DWORD64, 8> last_offsets = {};
|
|
|
|
#if defined(WITH_BFD)
|
|
sym_bfd_obj_cache bfd_cache;
|
|
bfd_init();
|
|
#endif /* WITH_BFD */
|
|
|
|
/* Walk stack at most MAX_FRAMES deep in case the stack is corrupt. */
|
|
for (uint num = 0; num < MAX_FRAMES; num++) {
|
|
auto guard = scope_guard([&]() {
|
|
this->CrashLogFaultSectionCheckpoint(buffer);
|
|
});
|
|
if (!proc.pStackWalk64(
|
|
#ifdef _M_AMD64
|
|
IMAGE_FILE_MACHINE_AMD64,
|
|
#else
|
|
IMAGE_FILE_MACHINE_I386,
|
|
#endif
|
|
hCur, GetCurrentThread(), &frame, &ctx, nullptr, proc.pSymFunctionTableAccess64, proc.pSymGetModuleBase64, nullptr)) break;
|
|
|
|
if (std::find_if(last_offsets.begin(), last_offsets.end(), [&](DWORD64 offset) { return offset != frame.AddrPC.Offset; }) == last_offsets.end()) {
|
|
buffer += seprintf(buffer, last, " <infinite loop>\n");
|
|
break;
|
|
}
|
|
|
|
last_offsets[num % last_offsets.size()] = frame.AddrPC.Offset;
|
|
|
|
/* Get module name. */
|
|
const char *mod_name = "???";
|
|
const char *image_name = nullptr;
|
|
[[maybe_unused]] DWORD64 image_base = 0;
|
|
|
|
IMAGEHLP_MODULE64 module;
|
|
module.SizeOfStruct = sizeof(module);
|
|
if (proc.pSymGetModuleInfo64(hCur, frame.AddrPC.Offset, &module)) {
|
|
mod_name = module.ModuleName;
|
|
image_name = module.ImageName;
|
|
image_base = module.BaseOfImage;
|
|
}
|
|
|
|
/* Print module and instruction pointer. */
|
|
buffer += seprintf(buffer, last, "[%02d] %-20s " PRINTF_PTR, num, mod_name, (uintptr_t) frame.AddrPC.Offset);
|
|
|
|
/* Get symbol name and line info if possible. */
|
|
DWORD64 offset;
|
|
if (proc.pSymGetSymFromAddr64(hCur, frame.AddrPC.Offset, &offset, sym_info)) {
|
|
buffer += seprintf(buffer, last, " %s + " OTTD_PRINTF64U, sym_info->Name, offset);
|
|
|
|
DWORD line_offs;
|
|
IMAGEHLP_LINE64 line;
|
|
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
|
if (proc.pSymGetLineFromAddr64(hCur, frame.AddrPC.Offset, &line_offs, &line)) {
|
|
buffer += seprintf(buffer, last, " (%s:%u)", line.FileName, (uint) line.LineNumber);
|
|
}
|
|
} else if (image_name != nullptr) {
|
|
#if defined (WITH_BFD)
|
|
/* subtract one to get the line before the return address, i.e. the function call line */
|
|
sym_info_bfd bfd_info(static_cast<bfd_vma>(frame.AddrPC.Offset) - static_cast<bfd_vma>(image_base) - 1);
|
|
lookup_addr_bfd(image_name, bfd_cache, bfd_info);
|
|
if (bfd_info.function_name != nullptr) {
|
|
const char *func_name = bfd_info.function_name;
|
|
#if defined(WITH_DEMANGLE)
|
|
int status = -1;
|
|
char *demangled = abi::__cxa_demangle(func_name, nullptr, 0, &status);
|
|
if (demangled != nullptr && status == 0) {
|
|
func_name = demangled;
|
|
}
|
|
#endif
|
|
bool symbol_ok = strncmp(func_name, ".rdata$", 7) != 0 && strncmp(func_name, ".debug_loc", 10) != 0;
|
|
if (symbol_ok) {
|
|
buffer += seprintf(buffer, last, " %s", func_name);
|
|
}
|
|
#if defined(WITH_DEMANGLE)
|
|
free(demangled);
|
|
#endif
|
|
if (symbol_ok && bfd_info.function_addr) {
|
|
if (bfd_info.function_addr > frame.AddrPC.Offset) {
|
|
buffer += seprintf(buffer, last, " - " OTTD_PRINTF64U, static_cast<DWORD64>(bfd_info.function_addr) - frame.AddrPC.Offset);
|
|
} else {
|
|
buffer += seprintf(buffer, last, " + " OTTD_PRINTF64U, frame.AddrPC.Offset - static_cast<DWORD64>(bfd_info.function_addr));
|
|
}
|
|
}
|
|
}
|
|
if (bfd_info.file_name != nullptr) {
|
|
buffer += seprintf(buffer, last, " (%s:%d)", bfd_info.file_name, bfd_info.line);
|
|
}
|
|
if (bfd_info.found && bfd_info.abfd) {
|
|
const char *file_name = nullptr;
|
|
const char *func_name = nullptr;
|
|
uint line_num = 0;
|
|
uint iteration_limit = 32;
|
|
while (iteration_limit-- && bfd_find_inliner_info(bfd_info.abfd, &file_name, &func_name, &line_num)) {
|
|
buffer += seprintf(buffer, last, "\n[inlined]%*s", (int)(19 + (sizeof(void *) * 2)), "");
|
|
if (func_name) {
|
|
int status = -1;
|
|
char *demangled = nullptr;
|
|
#if defined(WITH_DEMANGLE)
|
|
demangled = abi::__cxa_demangle(func_name, nullptr, 0, &status);
|
|
#endif
|
|
const char *name = (demangled != nullptr && status == 0) ? demangled : func_name;
|
|
buffer += seprintf(buffer, last, " %s", name);
|
|
free(demangled);
|
|
}
|
|
if (file_name != nullptr) {
|
|
buffer += seprintf(buffer, last, " (%s:%u)", file_name, line_num);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
buffer += seprintf(buffer, last, "\n");
|
|
}
|
|
|
|
proc.pSymCleanup(hCur);
|
|
}
|
|
|
|
return buffer + seprintf(buffer, last, "\n");;
|
|
}
|
|
#endif /* _MSC_VER || WITH_DBGHELP */
|
|
|
|
#if defined(_MSC_VER)
|
|
/* virtual */ int CrashLogWindows::WriteCrashDump(char *filename, const char *filename_last) const
|
|
{
|
|
if (_settings_client.gui.developer == 0) return 0;
|
|
|
|
int ret = 0;
|
|
HMODULE dbghelp = LoadLibrary(L"dbghelp.dll");
|
|
if (dbghelp != nullptr) {
|
|
typedef BOOL (WINAPI *MiniDumpWriteDumpT)(HANDLE, DWORD, HANDLE,
|
|
MINIDUMP_TYPE,
|
|
CONST PMINIDUMP_EXCEPTION_INFORMATION,
|
|
CONST PMINIDUMP_USER_STREAM_INFORMATION,
|
|
CONST PMINIDUMP_CALLBACK_INFORMATION);
|
|
MiniDumpWriteDumpT funcMiniDumpWriteDump = (MiniDumpWriteDumpT) GetProcAddress(dbghelp, "MiniDumpWriteDump");
|
|
if (funcMiniDumpWriteDump != nullptr) {
|
|
seprintf(filename, filename_last, "%scrash.dmp", _personal_dir.c_str());
|
|
HANDLE file = CreateFile(OTTD2FS(filename).c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, 0);
|
|
HANDLE proc = GetCurrentProcess();
|
|
DWORD procid = GetCurrentProcessId();
|
|
MINIDUMP_EXCEPTION_INFORMATION mdei;
|
|
MINIDUMP_USER_STREAM userstream;
|
|
MINIDUMP_USER_STREAM_INFORMATION musi;
|
|
|
|
userstream.Type = LastReservedStream + 1;
|
|
userstream.Buffer = const_cast<void *>(static_cast<const void*>(this->crashlog_buffer.data()));
|
|
userstream.BufferSize = (ULONG)strlen(this->crashlog_buffer.data()) + 1;
|
|
|
|
musi.UserStreamCount = 1;
|
|
musi.UserStreamArray = &userstream;
|
|
|
|
mdei.ThreadId = GetCurrentThreadId();
|
|
mdei.ExceptionPointers = ep;
|
|
mdei.ClientPointers = false;
|
|
|
|
funcMiniDumpWriteDump(proc, procid, file, MiniDumpWithDataSegs, &mdei, &musi, nullptr);
|
|
ret = 1;
|
|
} else {
|
|
ret = -1;
|
|
}
|
|
FreeLibrary(dbghelp);
|
|
}
|
|
return ret;
|
|
}
|
|
#endif /* _MSC_VER || WITH_DBGHELP */
|
|
|
|
#if defined(_MSC_VER)
|
|
/**
|
|
* Call the handler from within an SEH try/except block for the case where we trigger another exception
|
|
* during the course of calling the given log section writer.
|
|
*
|
|
* If an exception does occur, restore the buffer pointer to either the original value, or
|
|
* the value provided in any later checkpoint.
|
|
* Insert a message describing the problem and give up on the section.
|
|
*/
|
|
/* virtual */ char *CrashLogWindows::TryCrashLogFaultSection(char *buffer, const char *last, const char *section_name, CrashLogSectionWriter writer)
|
|
{
|
|
this->FlushCrashLogBuffer();
|
|
this->internal_fault_saved_buffer = buffer;
|
|
|
|
__try {
|
|
buffer = writer(this, buffer, last);
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
if (this->internal_fault_saved_buffer == nullptr) {
|
|
/* if we get here, things are unrecoverable */
|
|
ImmediateExitProcess(43);
|
|
}
|
|
|
|
buffer = this->internal_fault_saved_buffer;
|
|
this->internal_fault_saved_buffer = nullptr;
|
|
|
|
buffer += seprintf(buffer, last, "\nSomething went seriously wrong when attempting to fill the '%s' section of the crash log: exception: %.8X.\n", section_name, GetExceptionCode());
|
|
buffer += seprintf(buffer, last, "This is probably due to an invalid pointer or other corrupt data.\n\n");
|
|
}
|
|
|
|
this->internal_fault_saved_buffer = nullptr;
|
|
return buffer;
|
|
}
|
|
#else /* _MSC_VER */
|
|
/**
|
|
* Set up a longjmp to be called from the vectored exception handler for the case where we trigger another exception
|
|
* during the course of calling the given log section writer.
|
|
*
|
|
* If an exception does occur, restore the buffer pointer to either the original value, or
|
|
* the value provided in any later checkpoint.
|
|
* Insert a message describing the problem and give up on the section.
|
|
*/
|
|
/* virtual */ char *CrashLogWindows::TryCrashLogFaultSection(char *buffer, const char *last, const char *section_name, CrashLogSectionWriter writer)
|
|
{
|
|
this->FlushCrashLogBuffer();
|
|
this->internal_fault_saved_buffer = buffer;
|
|
|
|
int exception_num = setjmp(this->internal_fault_jmp_buf);
|
|
if (exception_num != 0) {
|
|
if (this->internal_fault_saved_buffer == nullptr) {
|
|
/* if we get here, things are unrecoverable */
|
|
ImmediateExitProcess(43);
|
|
}
|
|
|
|
buffer = this->internal_fault_saved_buffer;
|
|
this->internal_fault_saved_buffer = nullptr;
|
|
|
|
buffer += seprintf(buffer, last, "\nSomething went seriously wrong when attempting to fill the '%s' section of the crash log: exception: %.8X.\n", section_name, exception_num);
|
|
buffer += seprintf(buffer, last, "This is probably due to an invalid pointer or other corrupt data.\n\n");
|
|
|
|
return buffer;
|
|
}
|
|
|
|
buffer = writer(this, buffer, last);
|
|
this->internal_fault_saved_buffer = nullptr;
|
|
return buffer;
|
|
}
|
|
#endif /* _MSC_VER */
|
|
|
|
/* virtual */ void CrashLogWindows::CrashLogFaultSectionCheckpoint(char *buffer) const
|
|
{
|
|
CrashLogWindows *self = const_cast<CrashLogWindows *>(this);
|
|
|
|
if (self->internal_fault_saved_buffer != nullptr && buffer > self->internal_fault_saved_buffer) {
|
|
self->internal_fault_saved_buffer = buffer;
|
|
}
|
|
|
|
self->FlushCrashLogBuffer();
|
|
}
|
|
|
|
extern bool CloseConsoleLogIfActive();
|
|
static void ShowCrashlogWindow();
|
|
|
|
/**
|
|
* Stack pointer for use when 'starting' the crash handler.
|
|
* Not static as gcc's inline assembly needs it that way.
|
|
*/
|
|
thread_local void *_safe_esp = nullptr;
|
|
|
|
static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
|
|
{
|
|
/* Restore system timer resolution. */
|
|
timeEndPeriod(1);
|
|
|
|
/* Disable our event loop. */
|
|
SetWindowLongPtr(GetActiveWindow(), GWLP_WNDPROC, (LONG_PTR)&DefWindowProc);
|
|
|
|
CrashLogWindows *log = nullptr;
|
|
CrashLogWindows *cur = CrashLogWindows::current.load();
|
|
do {
|
|
if (cur != nullptr) {
|
|
if (cur->crash_thread_id == GetCurrentThreadId()) {
|
|
/* The same thread has recursively reached the exception handler */
|
|
CrashLog::AfterCrashLogCleanup();
|
|
ImmediateExitProcess(2);
|
|
} else {
|
|
/* Another thread has also reached the exception handler, just pause/suspend it */
|
|
cur->other_crash_threads++;
|
|
while (true) {
|
|
Sleep(INFINITE);
|
|
}
|
|
}
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
} else if (log == nullptr) {
|
|
/* Use VirtualAlloc to allocate the buffer for the crash log object and text buffer.
|
|
* It is too large for the stack, and the crash may have been caused by heap corruption.
|
|
* Make the crash log text buffer at least 4 x 64k, round allocation up to multiple of 64k. */
|
|
const size_t alloc_size = Align(sizeof(CrashLogWindows) + 0x40000, 0x10000);
|
|
void *raw_buffer = VirtualAlloc(nullptr, alloc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
|
log = new (raw_buffer) CrashLogWindows(ep);
|
|
log->crashlog_buffer = std::span<char>(reinterpret_cast<char *>(raw_buffer) + sizeof(CrashLogWindows), reinterpret_cast<char *>(raw_buffer) + alloc_size);
|
|
}
|
|
} while (!CrashLogWindows::current.compare_exchange_weak(cur, log));
|
|
|
|
const char *abort_reason = CrashLog::GetAbortCrashlogReason();
|
|
if (abort_reason != nullptr) {
|
|
wchar_t _emergency_crash[512];
|
|
_snwprintf(_emergency_crash, lengthof(_emergency_crash),
|
|
L"A serious fault condition occurred in the game. The game will shut down. (%s)\n", OTTD2FS(abort_reason).c_str());
|
|
MessageBox(nullptr, _emergency_crash, L"Fatal Application Failure", MB_ICONERROR);
|
|
ImmediateExitProcess(3);
|
|
}
|
|
|
|
log->MakeCrashLog(log->crashlog_buffer.data(), log->crashlog_buffer.data() + log->crashlog_buffer.size() - 1);
|
|
|
|
/* Close any possible log files */
|
|
CloseConsoleLogIfActive();
|
|
|
|
void *crash_win_esp = _safe_esp;
|
|
if (crash_win_esp == nullptr) {
|
|
/* If _safe_esp is not set for the current thread,
|
|
* try to read the stack base from the thread environment block instead. */
|
|
#ifdef _M_AMD64
|
|
/* The stack pointer for AMD64 must always be 16-byte aligned inside a
|
|
* function. As we are simulating a function call with the safe ESP value,
|
|
* we need to subtract 8 for the imaginary return address otherwise stack
|
|
* alignment would be wrong in the called function. */
|
|
crash_win_esp = (void *)(__readgsqword(8) - 8);
|
|
#elif defined(_M_IX86)
|
|
crash_win_esp = (void *)__readfsdword(4);
|
|
#endif
|
|
}
|
|
|
|
if ((VideoDriver::GetInstance() == nullptr || VideoDriver::GetInstance()->HasGUI()) && crash_win_esp != nullptr) {
|
|
#ifdef _M_AMD64
|
|
ep->ContextRecord->Rip = (DWORD64)ShowCrashlogWindow;
|
|
ep->ContextRecord->Rsp = (DWORD64)crash_win_esp;
|
|
#elif defined(_M_IX86)
|
|
ep->ContextRecord->Eip = (DWORD)ShowCrashlogWindow;
|
|
ep->ContextRecord->Esp = (DWORD)crash_win_esp;
|
|
#elif defined(_M_ARM64)
|
|
ep->ContextRecord->Pc = (DWORD64)ShowCrashlogWindow;
|
|
ep->ContextRecord->Sp = (DWORD64)crash_win_esp;
|
|
#endif
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
|
|
CrashLog::AfterCrashLogCleanup();
|
|
ImmediateExitProcess(1);
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
static LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS *ep)
|
|
{
|
|
CrashLogWindows *cur = CrashLogWindows::current.load();
|
|
if (cur != nullptr && cur->crash_thread_id == GetCurrentThreadId() && cur->internal_fault_saved_buffer != nullptr) {
|
|
#if defined(_MSC_VER)
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
#else
|
|
longjmp(cur->internal_fault_jmp_buf, ep->ExceptionRecord->ExceptionCode);
|
|
#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 == 0xE1212012) {
|
|
return ExceptionHandler(ep);
|
|
}
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
static void CDECL CustomAbort(int)
|
|
{
|
|
RaiseException(0xE1212012, 0, 0, nullptr);
|
|
}
|
|
|
|
/* static */ void CrashLog::InitialiseCrashLog()
|
|
{
|
|
CrashLog::InitThread();
|
|
|
|
/* SIGABRT is not an unhandled exception, so we need to intercept it. */
|
|
signal(SIGABRT, CustomAbort);
|
|
#if defined(_MSC_VER)
|
|
/* Don't show abort message as we will get the crashlog window anyway. */
|
|
_set_abort_behavior(0, _WRITE_ABORT_MSG);
|
|
#endif
|
|
SetUnhandledExceptionFilter(ExceptionHandler);
|
|
|
|
using VEX_HANDLER_TYPE = LONG WINAPI (EXCEPTION_POINTERS *);
|
|
void* (WINAPI *AddVectoredExceptionHandler)(ULONG, VEX_HANDLER_TYPE*);
|
|
static LibraryLoader _kernel32("Kernel32.dll");
|
|
AddVectoredExceptionHandler = _kernel32.GetFunction("AddVectoredExceptionHandler");
|
|
if (AddVectoredExceptionHandler != nullptr) {
|
|
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
|
|
}
|
|
}
|
|
|
|
/* static */ void CrashLog::InitThread()
|
|
{
|
|
#if defined(_M_AMD64) || defined(_M_ARM64)
|
|
CONTEXT ctx;
|
|
RtlCaptureContext(&ctx);
|
|
|
|
/* The stack pointer for AMD64 must always be 16-byte aligned inside a
|
|
* function. As we are simulating a function call with the safe ESP value,
|
|
* we need to subtract 8 for the imaginary return address otherwise stack
|
|
* alignment would be wrong in the called function. */
|
|
# if defined(_M_ARM64)
|
|
_safe_esp = (void *)(ctx.Sp - 8);
|
|
# else
|
|
_safe_esp = (void *)(ctx.Rsp - 8);
|
|
# endif
|
|
#else
|
|
void *safe_esp = nullptr;
|
|
# if defined(_MSC_VER)
|
|
_asm {
|
|
mov safe_esp, esp
|
|
}
|
|
# else
|
|
asm("movl %%esp, %0" : "=rm" (safe_esp));
|
|
# endif
|
|
_safe_esp = safe_esp;
|
|
#endif
|
|
}
|
|
|
|
/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info)
|
|
{
|
|
CrashLogWindows log(nullptr);
|
|
log.MakeDesyncCrashLog(log_in, log_out, info);
|
|
}
|
|
|
|
/* static */ void CrashLog::InconsistencyLog(const InconsistencyExtraInfo &info)
|
|
{
|
|
CrashLogWindows log(nullptr);
|
|
log.MakeInconsistencyLog(info);
|
|
}
|
|
|
|
/* static */ void CrashLog::VersionInfoLog(char *buffer, const char *last)
|
|
{
|
|
CrashLogWindows log(nullptr);
|
|
log.FillVersionInfoLog(buffer, last);
|
|
}
|
|
|
|
/* The crash log GUI */
|
|
|
|
static bool _expanded;
|
|
|
|
static const TCHAR _crash_desc[] =
|
|
L"A serious fault condition occurred in the game. The game will shut down.\n"
|
|
L"Please send the crash information (log files and crash saves, if any) to the patchpack developer.\n"
|
|
L"This will greatly help debugging. The correct place to do this is https://www.tt-forums.net/viewtopic.php?f=33&t=73469"
|
|
L" or https://github.com/JGRennison/OpenTTD-patches\n"
|
|
L"The information contained in the report is displayed below.\n";
|
|
|
|
static const wchar_t * const _expand_texts[] = {L"S&how report >>", L"&Hide report <<" };
|
|
|
|
static void SetWndSize(HWND wnd, int mode)
|
|
{
|
|
RECT r, r2;
|
|
|
|
GetWindowRect(wnd, &r);
|
|
SetDlgItemText(wnd, 15, _expand_texts[mode == 1]);
|
|
|
|
if (mode >= 0) {
|
|
GetWindowRect(GetDlgItem(wnd, 11), &r2);
|
|
int offs = r2.bottom - r2.top + 10;
|
|
if (mode == 0) offs = -offs;
|
|
SetWindowPos(wnd, HWND_TOPMOST, 0, 0,
|
|
r.right - r.left, r.bottom - r.top + offs, SWP_NOMOVE | SWP_NOZORDER);
|
|
} else {
|
|
SetWindowPos(wnd, HWND_TOPMOST,
|
|
(GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left)) / 2,
|
|
(GetSystemMetrics(SM_CYSCREEN) - (r.bottom - r.top)) / 2,
|
|
0, 0, SWP_NOSIZE);
|
|
}
|
|
}
|
|
|
|
static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM)
|
|
{
|
|
switch (msg) {
|
|
case WM_INITDIALOG: {
|
|
uint crashlog_length = 0;
|
|
CrashLogWindows *cur = CrashLogWindows::current.load();
|
|
for (const char *p = cur->crashlog_buffer.data(); *p != 0; p++) {
|
|
if (*p == '\n') {
|
|
/* Reserve extra space for LF to CRLF conversion */
|
|
crashlog_length++;
|
|
}
|
|
crashlog_length++;
|
|
}
|
|
|
|
/* We need to put the crash-log in a separate buffer because the default
|
|
* buffer in MB_TO_WIDE is not large enough (512 chars).
|
|
* Use VirtualAlloc to allocate pages for the buffer to avoid overflowing the stack,
|
|
* due to the increased maximum size of the crash log.
|
|
* Avoid the heap in case the crash is because the heap became corrupted. */
|
|
const size_t crash_desc_buf_length = lengthof(_crash_desc) + (MAX_PATH * 4); // Add an extra MAX_PATH for additional space
|
|
const size_t crash_msgW_length = ((crashlog_length + 16) * 3) / 2;
|
|
const size_t dos_nl_length = (crashlog_length + 16);
|
|
void *raw_buffer = VirtualAlloc(nullptr, (crash_desc_buf_length * sizeof(wchar_t)) + (crash_msgW_length * sizeof(wchar_t)) + dos_nl_length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
|
|
|
wchar_t *crash_desc_buf = reinterpret_cast<wchar_t *>(raw_buffer);
|
|
wchar_t *crash_msgW = crash_desc_buf + crash_desc_buf_length;
|
|
char *dos_nl = reinterpret_cast<char *>(crash_msgW + crash_msgW_length);
|
|
|
|
/* Convert unix -> dos newlines because the edit box only supports that properly :( */
|
|
const char *unix_nl = cur->crashlog_buffer.data();
|
|
char *p = dos_nl;
|
|
char32_t c;
|
|
while ((c = Utf8Consume(&unix_nl)) && p < (dos_nl + dos_nl_length - 1) - 4) { // 4 is max number of bytes per character
|
|
if (c == '\n') p += Utf8Encode(p, '\r');
|
|
p += Utf8Encode(p, c);
|
|
}
|
|
*p = '\0';
|
|
|
|
/* Add path to all files to the crash window text */
|
|
const wchar_t * const crash_desc_buf_last = crash_desc_buf + crash_desc_buf_length - 1;
|
|
wcsncpy_s(crash_desc_buf, crash_desc_buf_length, _crash_desc, _TRUNCATE);
|
|
wchar_t *desc = crash_desc_buf + wcslen(crash_desc_buf);
|
|
|
|
auto append_str = [&](std::string_view name) {
|
|
if (desc >= crash_desc_buf_last - 1) return;
|
|
desc += MultiByteToWideChar(CP_UTF8, 0, name.data(), (int)name.size(), desc, (int)(crash_desc_buf_last - desc));
|
|
*desc = L'\0';
|
|
};
|
|
auto append_newline = [&]() {
|
|
if (desc >= crash_desc_buf_last - 1) return;
|
|
*desc = L'\n';
|
|
desc++;
|
|
*desc = L'\0';
|
|
};
|
|
|
|
append_str(cur->crashlog_filename);
|
|
if (_settings_client.gui.developer > 0 && cur->crashdump_filename[0] != 0) {
|
|
append_newline();
|
|
append_str(cur->crashdump_filename);
|
|
}
|
|
if (cur->savegame_filename[0] != 0) {
|
|
append_newline();
|
|
append_str(cur->savegame_filename);
|
|
}
|
|
if (cur->screenshot_filename[0] != 0) {
|
|
append_newline();
|
|
append_str(cur->screenshot_filename);
|
|
}
|
|
|
|
SetDlgItemText(wnd, 10, crash_desc_buf);
|
|
SetDlgItemText(wnd, 11, convert_to_fs(dos_nl, crash_msgW, crash_msgW_length));
|
|
SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
|
|
SetWndSize(wnd, -1);
|
|
} return TRUE;
|
|
case WM_COMMAND:
|
|
switch (wParam) {
|
|
case 12: // Close
|
|
CrashLog::AfterCrashLogCleanup();
|
|
ImmediateExitProcess(2);
|
|
case 15: // Expand window to show crash-message
|
|
_expanded = !_expanded;
|
|
SetWndSize(wnd, _expanded);
|
|
break;
|
|
}
|
|
return TRUE;
|
|
case WM_CLOSE:
|
|
CrashLog::AfterCrashLogCleanup();
|
|
ImmediateExitProcess(2);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void ShowCrashlogWindow()
|
|
{
|
|
ShowCursor(TRUE);
|
|
ShowWindow(GetActiveWindow(), FALSE);
|
|
DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(100), nullptr, CrashDialogFunc);
|
|
}
|