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);
134 bool TryExecute(std::string_view section_name, std::function<
bool()> &&func)
override {
…}
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);
215 STACKFRAME64 frame{};
217 frame.AddrPC.Offset =
ep->ContextRecord->Rip;
218 frame.AddrFrame.Offset =
ep->ContextRecord->Rbp;
219 frame.AddrStack.Offset =
ep->ContextRecord->Rsp;
220#elif defined(_M_IX86)
221 frame.AddrPC.Offset =
ep->ContextRecord->Eip;
222 frame.AddrFrame.Offset =
ep->ContextRecord->Ebp;
223 frame.AddrStack.Offset =
ep->ContextRecord->Esp;
224#elif defined(_M_ARM64)
225 frame.AddrPC.Offset =
ep->ContextRecord->Pc;
226 frame.AddrFrame.Offset =
ep->ContextRecord->Fp;
227 frame.AddrStack.Offset =
ep->ContextRecord->Sp;
229 frame.AddrPC.Mode = AddrModeFlat;
230 frame.AddrFrame.Mode = AddrModeFlat;
231 frame.AddrStack.Mode = AddrModeFlat;
234 CONTEXT ctx = *
ep->ContextRecord;
240 std::array<char,
sizeof(IMAGEHLP_SYMBOL64) + MAX_SYMBOL_LEN> sym_info_raw{};
241 IMAGEHLP_SYMBOL64 *sym_info =
reinterpret_cast<IMAGEHLP_SYMBOL64*
>(sym_info_raw.data());
242 sym_info->SizeOfStruct =
sizeof(IMAGEHLP_SYMBOL64);
243 sym_info->MaxNameLength = MAX_SYMBOL_LEN;
246 for (uint num = 0; num < MAX_FRAMES; num++) {
247 if (!proc.pStackWalk64(
249 IMAGE_FILE_MACHINE_AMD64,
251 IMAGE_FILE_MACHINE_I386,
253 hCur, GetCurrentThread(), &frame, &ctx,
nullptr, proc.pSymFunctionTableAccess64, proc.pSymGetModuleBase64,
nullptr))
break;
255 if (frame.AddrPC.Offset == frame.AddrReturn.Offset) {
256 survey.push_back(
"<infinite loop>");
261 std::string_view mod_name =
"???";
263 IMAGEHLP_MODULE64
module;
264 module.SizeOfStruct = sizeof(module);
265 if (proc.pSymGetModuleInfo64(hCur, frame.AddrPC.Offset, &module)) {
266 mod_name =
module.ModuleName;
270 std::string
message = fmt::format(
"{:20s} {:X}", mod_name, frame.AddrPC.Offset);
274 if (proc.pSymGetSymFromAddr64(hCur, frame.AddrPC.Offset, &offset, sym_info)) {
275 format_append(
message,
" {} + {}", sym_info->Name, offset);
278 IMAGEHLP_LINE64 line;
279 line.SizeOfStruct =
sizeof(IMAGEHLP_LINE64);
280 if (proc.pSymGetLineFromAddr64(hCur, frame.AddrPC.Offset, &line_offs, &line)) {
281 format_append(
message,
" ({}:{})", line.FileName, line.LineNumber);
288 proc.pSymCleanup(hCur);
298extern bool CloseConsoleLogIfActive();
299static void ShowCrashlogWindow();
307static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
313 SetWindowLongPtr(GetActiveWindow(), GWLP_WNDPROC, (LONG_PTR)&DefWindowProc);
321 static const wchar_t _emergency_crash[] =
322 L
"A serious fault condition occurred in the game. The game will shut down.\n"
323 L
"As you loaded an emergency savegame no crash information will be generated.\n";
324 MessageBox(
nullptr, _emergency_crash, L
"Fatal Application Failure", MB_ICONERROR);
329 static const wchar_t _saveload_crash[] =
330 L
"A serious fault condition occurred in the game. The game will shut down.\n"
331 L
"As you loaded an savegame for which you do not have the required NewGRFs\n"
332 L
"no crash information will be generated.\n";
333 MessageBox(
nullptr, _saveload_crash, L
"Fatal Application Failure", MB_ICONERROR);
342 CloseConsoleLogIfActive();
346 ep->ContextRecord->Rip = (DWORD64)ShowCrashlogWindow;
347 ep->ContextRecord->Rsp = (DWORD64)
_safe_esp;
348#elif defined(_M_IX86)
349 ep->ContextRecord->Eip = (DWORD)ShowCrashlogWindow;
350 ep->ContextRecord->Esp = (DWORD)
_safe_esp;
351#elif defined(_M_ARM64)
352 ep->ContextRecord->Pc = (DWORD64)ShowCrashlogWindow;
353 ep->ContextRecord->Sp = (DWORD64)
_safe_esp;
355 return EXCEPTION_CONTINUE_EXECUTION;
359 return EXCEPTION_EXECUTE_HANDLER;
362static LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS *ep)
366 return EXCEPTION_CONTINUE_SEARCH;
372 if (ep->ExceptionRecord->ExceptionCode == 0xC0000374 ) {
373 return ExceptionHandler(ep);
375 if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
376 return ExceptionHandler(ep);
379 return ExceptionHandler(ep);
382 return EXCEPTION_CONTINUE_SEARCH;
385static void CDECL CustomAbort(
int)
395 signal(SIGABRT, CustomAbort);
398 _set_abort_behavior(0, _WRITE_ABORT_MSG);
400 SetUnhandledExceptionFilter(ExceptionHandler);
401 AddVectoredExceptionHandler(1, VectoredExceptionHandler);
406#if defined(_M_AMD64) || defined(_M_ARM64)
408 RtlCaptureContext(&ctx);
414# if defined(_M_ARM64)
421# if defined(_MSC_VER)
426 asm(
"movl %%esp, %0" :
"=rm" (safe_esp));
434static bool _expanded;
436static const wchar_t _crash_desc[] =
437 L
"A serious fault condition occurred in the game. The game will shut down.\n"
438 L
"Please send crash.json.log, crash.dmp, and crash.sav to the developers.\n"
439 L
"This will greatly help debugging.\n\n"
440 L
"https://github.com/OpenTTD/OpenTTD/issues\n\n"
443static const wchar_t *
const _expand_texts[] = {L
"S&how report >>", L
"&Hide report <<" };
445static void SetWndSize(HWND wnd,
int mode)
449 GetWindowRect(wnd, &r);
450 SetDlgItemText(wnd, 15, _expand_texts[mode == 1]);
453 GetWindowRect(GetDlgItem(wnd, 11), &r2);
454 int offs = r2.bottom - r2.top + 10;
455 if (mode == 0) offs = -offs;
456 SetWindowPos(wnd, HWND_TOPMOST, 0, 0,
457 r.right - r.left, r.bottom - r.top + offs, SWP_NOMOVE | SWP_NOZORDER);
459 SetWindowPos(wnd, HWND_TOPMOST,
460 (GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left)) / 2,
461 (GetSystemMetrics(SM_CYSCREEN) - (r.bottom - r.top)) / 2,
466static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM)
469 case WM_INITDIALOG: {
471 size_t crashlog_length = crashlog.size() + 1;
473 crashlog_length += std::ranges::count(crashlog,
'\n');
475 const size_t filename_count = 4;
476 const size_t filename_buf_length = MAX_PATH + 1;
477 const size_t crash_desc_buf_length =
lengthof(_crash_desc) + filename_buf_length * filename_count + 1;
483 const size_t total_length = crash_desc_buf_length *
sizeof(wchar_t) +
484 crashlog_length *
sizeof(
wchar_t) +
485 filename_buf_length *
sizeof(wchar_t) * filename_count +
487 void *raw_buffer = VirtualAlloc(
nullptr, total_length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
489 wchar_t *crash_desc_buf =
reinterpret_cast<wchar_t *
>(raw_buffer);
490 wchar_t *crashlog_buf = crash_desc_buf + crash_desc_buf_length;
491 wchar_t *filename_buf = crashlog_buf + crashlog_length;
492 char *crashlog_dos_nl =
reinterpret_cast<char *
>(filename_buf + filename_buf_length * filename_count);
496 char *p = crashlog_dos_nl;
497 for (
char c : crashlog) {
498 if (c ==
'\n') *(p++) =
'\r';
506 crash_desc_buf_length,
514 SetDlgItemText(wnd, 10, crash_desc_buf);
515 SetDlgItemText(wnd, 11,
convert_to_fs(crashlog_dos_nl, {crashlog_buf, crashlog_length}));
516 SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
525 _expanded = !_expanded;
526 SetWndSize(wnd, _expanded);
538static void ShowCrashlogWindow()
541 ShowWindow(GetActiveWindow(), FALSE);
542 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...
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,...
std::string CreateFileName(std::string_view ext, bool with_dir=true) const
Create a timestamped filename.
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.
std::wstring OTTD2FS(std::string_view name)
Convert from OpenTTD's encoding to a wide string.
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
wchar_t * convert_to_fs(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