10#include "../../stdafx.h"
11#include "../../crashlog.h"
13#include "../../core/math_func.hpp"
14#include "../../string_func.h"
15#include "../../fileio_func.h"
16#include "../../strings_func.h"
17#include "../../gamelog.h"
18#include "../../saveload/saveload.h"
19#include "../../video/video_driver.hpp"
20#include "../../library_loader.h"
33#ifdef WITH_UNOFFICIAL_BREAKPAD
34# include <client/windows/handler/exception_handler.h>
37#include "../../safeguards.h"
44 {EXCEPTION_ACCESS_VIOLATION,
"EXCEPTION_ACCESS_VIOLATION"},
45 {EXCEPTION_ARRAY_BOUNDS_EXCEEDED,
"EXCEPTION_ARRAY_BOUNDS_EXCEEDED"},
46 {EXCEPTION_BREAKPOINT,
"EXCEPTION_BREAKPOINT"},
47 {EXCEPTION_DATATYPE_MISALIGNMENT,
"EXCEPTION_DATATYPE_MISALIGNMENT"},
48 {EXCEPTION_FLT_DENORMAL_OPERAND,
"EXCEPTION_FLT_DENORMAL_OPERAND"},
49 {EXCEPTION_FLT_DIVIDE_BY_ZERO,
"EXCEPTION_FLT_DIVIDE_BY_ZERO"},
50 {EXCEPTION_FLT_INEXACT_RESULT,
"EXCEPTION_FLT_INEXACT_RESULT"},
51 {EXCEPTION_FLT_INVALID_OPERATION,
"EXCEPTION_FLT_INVALID_OPERATION"},
52 {EXCEPTION_FLT_OVERFLOW,
"EXCEPTION_FLT_OVERFLOW"},
53 {EXCEPTION_FLT_STACK_CHECK,
"EXCEPTION_FLT_STACK_CHECK"},
54 {EXCEPTION_FLT_UNDERFLOW,
"EXCEPTION_FLT_UNDERFLOW"},
55 {EXCEPTION_GUARD_PAGE,
"EXCEPTION_GUARD_PAGE"},
56 {EXCEPTION_ILLEGAL_INSTRUCTION,
"EXCEPTION_ILLEGAL_INSTRUCTION"},
57 {EXCEPTION_IN_PAGE_ERROR,
"EXCEPTION_IN_PAGE_ERROR"},
58 {EXCEPTION_INT_DIVIDE_BY_ZERO,
"EXCEPTION_INT_DIVIDE_BY_ZERO"},
59 {EXCEPTION_INT_OVERFLOW,
"EXCEPTION_INT_OVERFLOW"},
60 {EXCEPTION_INVALID_DISPOSITION,
"EXCEPTION_INVALID_DISPOSITION"},
61 {EXCEPTION_INVALID_HANDLE,
"EXCEPTION_INVALID_HANDLE"},
62 {EXCEPTION_NONCONTINUABLE_EXCEPTION,
"EXCEPTION_NONCONTINUABLE_EXCEPTION"},
63 {EXCEPTION_PRIV_INSTRUCTION,
"EXCEPTION_PRIV_INSTRUCTION"},
64 {EXCEPTION_SINGLE_STEP,
"EXCEPTION_SINGLE_STEP"},
65 {EXCEPTION_STACK_OVERFLOW,
"EXCEPTION_STACK_OVERFLOW"},
66 {STATUS_UNWIND_CONSOLIDATE,
"STATUS_UNWIND_CONSOLIDATE"},
77 TerminateProcess(GetCurrentProcess(), exit_code);
78 ExitProcess(exit_code);
86 EXCEPTION_POINTERS *
ep;
90 survey[
"id"] =
ep->ExceptionRecord->ExceptionCode;
94 survey[
"reason"] =
"Unknown exception code";
101#ifdef WITH_UNOFFICIAL_BREAKPAD
102 static bool MinidumpCallback(
const wchar_t *dump_dir,
const wchar_t *minidump_id,
void *context, EXCEPTION_POINTERS *, MDRawAssertionInfo *,
bool succeeded)
107 std::rename(fmt::format(
"{}/{}.dmp",
FS2OTTD(dump_dir),
FS2OTTD(minidump_id)).c_str(), crashlog->crashdump_filename.c_str());
113 return google_breakpad::ExceptionHandler::WriteMinidump(
OTTD2FS(
_personal_dir), MinidumpCallback,
this);
118 bool TryExecute(std::string_view section_name, std::function<
bool()> &&func)
override
125 } __except (EXCEPTION_EXECUTE_HANDLER) {
126 fmt::print(
"Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
134 bool TryExecute(std::string_view section_name, std::function<
bool()> &&func)
override
140 fmt::print(
"Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
161#if !defined(_MSC_VER)
176static const uint MAX_SYMBOL_LEN = 512;
177static const uint MAX_FRAMES = 64;
183 BOOL (WINAPI * pSymInitialize)(HANDLE, PCSTR, BOOL);
184 BOOL (WINAPI * pSymSetOptions)(DWORD);
185 BOOL (WINAPI * pSymCleanup)(HANDLE);
186 BOOL (WINAPI * pStackWalk64)(DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64);
187 PVOID (WINAPI * pSymFunctionTableAccess64)(HANDLE, DWORD64);
188 DWORD64 (WINAPI * pSymGetModuleBase64)(HANDLE, DWORD64);
189 BOOL (WINAPI * pSymGetModuleInfo64)(HANDLE, DWORD64, PIMAGEHLP_MODULE64);
190 BOOL (WINAPI * pSymGetSymFromAddr64)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64);
191 BOOL (WINAPI * pSymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
204 survey = nlohmann::json::array();
209 HANDLE hCur = GetCurrentProcess();
210 proc.pSymInitialize(hCur,
nullptr, TRUE);
212 proc.pSymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_UNDNAME);
216 memset(&frame, 0,
sizeof(frame));
218 frame.AddrPC.Offset =
ep->ContextRecord->Rip;
219 frame.AddrFrame.Offset =
ep->ContextRecord->Rbp;
220 frame.AddrStack.Offset =
ep->ContextRecord->Rsp;
221#elif defined(_M_IX86)
222 frame.AddrPC.Offset =
ep->ContextRecord->Eip;
223 frame.AddrFrame.Offset =
ep->ContextRecord->Ebp;
224 frame.AddrStack.Offset =
ep->ContextRecord->Esp;
225#elif defined(_M_ARM64)
226 frame.AddrPC.Offset =
ep->ContextRecord->Pc;
227 frame.AddrFrame.Offset =
ep->ContextRecord->Fp;
228 frame.AddrStack.Offset =
ep->ContextRecord->Sp;
230 frame.AddrPC.Mode = AddrModeFlat;
231 frame.AddrFrame.Mode = AddrModeFlat;
232 frame.AddrStack.Mode = AddrModeFlat;
236 memcpy(&ctx,
ep->ContextRecord,
sizeof(ctx));
242 std::array<char,
sizeof(IMAGEHLP_SYMBOL64) + MAX_SYMBOL_LEN> sym_info_raw{};
243 IMAGEHLP_SYMBOL64 *sym_info =
reinterpret_cast<IMAGEHLP_SYMBOL64*
>(sym_info_raw.data());
244 sym_info->SizeOfStruct =
sizeof(IMAGEHLP_SYMBOL64);
245 sym_info->MaxNameLength = MAX_SYMBOL_LEN;
248 for (uint num = 0; num < MAX_FRAMES; num++) {
249 if (!proc.pStackWalk64(
251 IMAGE_FILE_MACHINE_AMD64,
253 IMAGE_FILE_MACHINE_I386,
255 hCur, GetCurrentThread(), &frame, &ctx,
nullptr, proc.pSymFunctionTableAccess64, proc.pSymGetModuleBase64,
nullptr))
break;
257 if (frame.AddrPC.Offset == frame.AddrReturn.Offset) {
258 survey.push_back(
"<infinite loop>");
263 const char *mod_name =
"???";
265 IMAGEHLP_MODULE64
module;
266 module.SizeOfStruct = sizeof(module);
267 if (proc.pSymGetModuleInfo64(hCur, frame.AddrPC.Offset, &module)) {
268 mod_name =
module.ModuleName;
272 std::string
message = fmt::format(
"{:20s} {:X}", mod_name, frame.AddrPC.Offset);
276 if (proc.pSymGetSymFromAddr64(hCur, frame.AddrPC.Offset, &offset, sym_info)) {
277 message += fmt::format(
" {} + {}", sym_info->Name, offset);
280 IMAGEHLP_LINE64 line;
281 line.SizeOfStruct =
sizeof(IMAGEHLP_LINE64);
282 if (proc.pSymGetLineFromAddr64(hCur, frame.AddrPC.Offset, &line_offs, &line)) {
283 message += fmt::format(
" ({}:{})", line.FileName, line.LineNumber);
290 proc.pSymCleanup(hCur);
300extern bool CloseConsoleLogIfActive();
301static void ShowCrashlogWindow();
309static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
315 SetWindowLongPtr(GetActiveWindow(), GWLP_WNDPROC, (LONG_PTR)&DefWindowProc);
323 static const wchar_t _emergency_crash[] =
324 L
"A serious fault condition occurred in the game. The game will shut down.\n"
325 L
"As you loaded an emergency savegame no crash information will be generated.\n";
326 MessageBox(
nullptr, _emergency_crash, L
"Fatal Application Failure", MB_ICONERROR);
331 static const wchar_t _saveload_crash[] =
332 L
"A serious fault condition occurred in the game. The game will shut down.\n"
333 L
"As you loaded an savegame for which you do not have the required NewGRFs\n"
334 L
"no crash information will be generated.\n";
335 MessageBox(
nullptr, _saveload_crash, L
"Fatal Application Failure", MB_ICONERROR);
344 CloseConsoleLogIfActive();
348 ep->ContextRecord->Rip = (DWORD64)ShowCrashlogWindow;
349 ep->ContextRecord->Rsp = (DWORD64)
_safe_esp;
350#elif defined(_M_IX86)
351 ep->ContextRecord->Eip = (DWORD)ShowCrashlogWindow;
352 ep->ContextRecord->Esp = (DWORD)
_safe_esp;
353#elif defined(_M_ARM64)
354 ep->ContextRecord->Pc = (DWORD64)ShowCrashlogWindow;
355 ep->ContextRecord->Sp = (DWORD64)
_safe_esp;
357 return EXCEPTION_CONTINUE_EXECUTION;
361 return EXCEPTION_EXECUTE_HANDLER;
364static LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS *ep)
368 return EXCEPTION_CONTINUE_SEARCH;
374 if (ep->ExceptionRecord->ExceptionCode == 0xC0000374 ) {
375 return ExceptionHandler(ep);
377 if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
378 return ExceptionHandler(ep);
381 return ExceptionHandler(ep);
384 return EXCEPTION_CONTINUE_SEARCH;
387static void CDECL CustomAbort(
int)
397 signal(SIGABRT, CustomAbort);
400 _set_abort_behavior(0, _WRITE_ABORT_MSG);
402 SetUnhandledExceptionFilter(ExceptionHandler);
403 AddVectoredExceptionHandler(1, VectoredExceptionHandler);
408#if defined(_M_AMD64) || defined(_M_ARM64)
410 RtlCaptureContext(&ctx);
416# if defined(_M_ARM64)
423# if defined(_MSC_VER)
428 asm(
"movl %%esp, %0" :
"=rm" (safe_esp));
436static bool _expanded;
438static const wchar_t _crash_desc[] =
439 L
"A serious fault condition occurred in the game. The game will shut down.\n"
440 L
"Please send crash.json.log, crash.dmp, and crash.sav to the developers.\n"
441 L
"This will greatly help debugging.\n\n"
442 L
"https://github.com/OpenTTD/OpenTTD/issues\n\n"
445static const wchar_t *
const _expand_texts[] = {L
"S&how report >>", L
"&Hide report <<" };
447static void SetWndSize(HWND wnd,
int mode)
451 GetWindowRect(wnd, &r);
452 SetDlgItemText(wnd, 15, _expand_texts[mode == 1]);
455 GetWindowRect(GetDlgItem(wnd, 11), &r2);
456 int offs = r2.bottom - r2.top + 10;
457 if (mode == 0) offs = -offs;
458 SetWindowPos(wnd, HWND_TOPMOST, 0, 0,
459 r.right - r.left, r.bottom - r.top + offs, SWP_NOMOVE | SWP_NOZORDER);
461 SetWindowPos(wnd, HWND_TOPMOST,
462 (GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left)) / 2,
463 (GetSystemMetrics(SM_CYSCREEN) - (r.bottom - r.top)) / 2,
468static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM)
471 case WM_INITDIALOG: {
473 size_t crashlog_length = crashlog.size() + 1;
475 crashlog_length += std::ranges::count(crashlog,
'\n');
477 const size_t filename_count = 4;
478 const size_t filename_buf_length = MAX_PATH + 1;
479 const size_t crash_desc_buf_length =
lengthof(_crash_desc) + filename_buf_length * filename_count + 1;
485 const size_t total_length = crash_desc_buf_length *
sizeof(wchar_t) +
486 crashlog_length *
sizeof(
wchar_t) +
487 filename_buf_length *
sizeof(wchar_t) * filename_count +
489 void *raw_buffer = VirtualAlloc(
nullptr, total_length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
491 wchar_t *crash_desc_buf =
reinterpret_cast<wchar_t *
>(raw_buffer);
492 wchar_t *crashlog_buf = crash_desc_buf + crash_desc_buf_length;
493 wchar_t *filename_buf = crashlog_buf + crashlog_length;
494 char *crashlog_dos_nl =
reinterpret_cast<char *
>(filename_buf + filename_buf_length * filename_count);
497 const char *crashlog_unix_nl = crashlog.data();
498 char *p = crashlog_dos_nl;
500 while ((c = Utf8Consume(&crashlog_unix_nl))) {
508 crash_desc_buf_length,
516 SetDlgItemText(wnd, 10, crash_desc_buf);
517 SetDlgItemText(wnd, 11,
convert_to_fs(crashlog_dos_nl, {crashlog_buf, crashlog_length}));
518 SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
527 _expanded = !_expanded;
528 SetWndSize(wnd, _expanded);
540static void ShowCrashlogWindow()
543 ShowWindow(GetActiveWindow(), FALSE);
544 DialogBox(GetModuleHandle(
nullptr), MAKEINTRESOURCE(100),
nullptr, CrashDialogFunc);
bool SaveloadCrashWithMissingNewGRFs()
Did loading the savegame cause a crash? If so, were NewGRFs missing?
Windows implementation for the crash logger.
static CrashLogWindows * current
Points to the current crash log.
void SurveyStacktrace(nlohmann::json &survey) const override
Convert stacktrace to JSON.
bool TryExecute(std::string_view section_name, std::function< bool()> &&func) override
Execute the func() and return its value.
bool try_execute_active
Whether we are in a TryExecute block.
EXCEPTION_POINTERS * ep
Information about the encountered exception.
jmp_buf internal_fault_jmp_buf
Buffer to track the long jump set setup.
CrashLogWindows(EXCEPTION_POINTERS *ep=nullptr)
A crash log is always generated when it's generated.
void SurveyCrash(nlohmann::json &survey) const override
Convert system crash reason to JSON.
Helper class for creating crash logs.
void MakeCrashLog()
Makes the crash log, writes it to a file and then subsequently tries to make a crash dump and crash s...
std::string CreateFileName(const char *ext, bool with_dir=true) const
Create a timestamped filename.
static std::string message
Error message coming from FatalError(format, ...).
virtual bool WriteCrashDump()
Write the (crash) dump to a file.
static void InitThread()
Prepare crash log handler for a newly started thread.
static void AfterCrashLogCleanup()
Try to close the sound/video stuff so it doesn't keep lingering around incorrect video states or so,...
static void InitialiseCrashLog()
Initialiser for crash logs; do the appropriate things so crashes are handled by our crash handler ins...
bool TestEmergency()
Finds out if current game is a loaded emergency savegame.
bool HasError()
Check whether an error occurred while loading the library or a function.
Function GetFunction(const std::string &symbol_name)
Get a function from a loaded library.
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
static const std::map< DWORD, std::string > exception_code_to_name
A map between exception code and its name.
static void ImmediateExitProcess(uint exit_code)
Forcefully try to terminate the application.
static constexpr DWORD CUSTOM_ABORT_EXCEPTION
Exception code used for custom abort.
thread_local void * _safe_esp
Stack pointer for use when 'starting' the crash handler.
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Gamelog _gamelog
Gamelog instance.
#define lengthof(array)
Return the length of an fixed size array.
size_t Utf8Encode(T buf, char32_t c)
Encode a unicode character and place it in the buffer.
std::wstring OTTD2FS(const std::string &name)
Convert from OpenTTD's encoding to a wide string.
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
wchar_t * convert_to_fs(const std::string_view src, std::span< wchar_t > dst_buf)
Convert from OpenTTD's encoding to that of the environment in UNICODE.
declarations of functions for MS windows systems