/*************************************************************************/ /* crash_handler_windows.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "crash_handler_windows.h" #include "core/os/os.h" #include "core/string/print_string.h" #include "core/config/project_settings.h" #include "core/version.h" #include "main/main.h" #ifdef CRASH_HANDLER_EXCEPTION // Backtrace code code based on: https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app #include #include #include #include #include #pragma comment(lib, "psapi.lib") #pragma comment(lib, "dbghelp.lib") // Some versions of imagehlp.dll lack the proper packing directives themselves // so we need to do it. #pragma pack(push, before_imagehlp, 8) #include #pragma pack(pop, before_imagehlp) struct module_data { std::string image_name; std::string module_name; void *base_address; DWORD load_size; }; class symbol { typedef IMAGEHLP_SYMBOL64 sym_type; sym_type *sym; static const int max_name_len = 1024; public: symbol(HANDLE process, DWORD64 address) : sym((sym_type *)::operator new(sizeof(*sym) + max_name_len)) { memset(sym, '\0', sizeof(*sym) + max_name_len); sym->SizeOfStruct = sizeof(*sym); sym->MaxNameLength = max_name_len; DWORD64 displacement; SymGetSymFromAddr64(process, address, &displacement, sym); } std::string name() { return std::string(sym->Name); } std::string undecorated_name() { if (*sym->Name == '\0') return ""; std::vector und_name(max_name_len); UnDecorateSymbolName(sym->Name, &und_name[0], max_name_len, UNDNAME_COMPLETE); return std::string(&und_name[0], strlen(&und_name[0])); } }; class get_mod_info { HANDLE process; public: get_mod_info(HANDLE h) : process(h) {} module_data operator()(HMODULE module) { module_data ret; char temp[4096]; MODULEINFO mi; GetModuleInformation(process, module, &mi, sizeof(mi)); ret.base_address = mi.lpBaseOfDll; ret.load_size = mi.SizeOfImage; GetModuleFileNameEx(process, module, temp, sizeof(temp)); ret.image_name = temp; GetModuleBaseName(process, module, temp, sizeof(temp)); ret.module_name = temp; std::vector img(ret.image_name.begin(), ret.image_name.end()); std::vector mod(ret.module_name.begin(), ret.module_name.end()); SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size); return ret; } }; DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { HANDLE process = GetCurrentProcess(); HANDLE hThread = GetCurrentThread(); DWORD offset_from_symbol = 0; IMAGEHLP_LINE64 line = { 0 }; std::vector modules; DWORD cbNeeded; std::vector module_handles(1); if (OS::get_singleton() == nullptr || OS::get_singleton()->is_disable_crash_handler() || IsDebuggerPresent()) { return EXCEPTION_CONTINUE_SEARCH; } String msg; const ProjectSettings *proj_settings = ProjectSettings::get_singleton(); if (proj_settings) { msg = proj_settings->get("debug/settings/crash_handler/message"); } // Tell MainLoop about the crash. This can be handled by users too in Node. if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); } print_error("\n================================================================"); print_error(vformat("%s: Program crashed", __FUNCTION__)); // Print the engine version just before, so that people are reminded to include the version in backtrace reports. if (String(VERSION_HASH).empty()) { print_error(vformat("Engine version: %s", VERSION_FULL_NAME)); } else { print_error(vformat("Engine version: %s (%s)", VERSION_FULL_NAME, VERSION_HASH)); } print_error(vformat("Dumping the backtrace. %s", msg)); // Load the symbols: if (!SymInitialize(process, nullptr, false)) { return EXCEPTION_CONTINUE_SEARCH; } SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_EXACT_SYMBOLS); EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); module_handles.resize(cbNeeded / sizeof(HMODULE)); EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); std::transform(module_handles.begin(), module_handles.end(), std::back_inserter(modules), get_mod_info(process)); void *base = modules[0].base_address; // Setup stuff: CONTEXT *context = ep->ContextRecord; STACKFRAME64 frame; bool skip_first = false; frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat; #ifdef _M_X64 frame.AddrPC.Offset = context->Rip; frame.AddrStack.Offset = context->Rsp; frame.AddrFrame.Offset = context->Rbp; #else frame.AddrPC.Offset = context->Eip; frame.AddrStack.Offset = context->Esp; frame.AddrFrame.Offset = context->Ebp; // Skip the first one to avoid a duplicate on 32-bit mode skip_first = true; #endif line.SizeOfStruct = sizeof(line); IMAGE_NT_HEADERS *h = ImageNtHeader(base); DWORD image_type = h->FileHeader.Machine; int n = 0; do { if (skip_first) { skip_first = false; } else { if (frame.AddrPC.Offset != 0) { std::string fnName = symbol(process, frame.AddrPC.Offset).undecorated_name(); if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &offset_from_symbol, &line)) { print_error(vformat("[%d] %s (%s:%d)", n, fnName.c_str(), (char *)line.FileName, (int)line.LineNumber)); } else { print_error(vformat("[%d] %s", n, fnName.c_str())); } } else { print_error(vformat("[%d] ???", n)); } n++; } if (!StackWalk64(image_type, process, hThread, &frame, context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) { break; } } while (frame.AddrReturn.Offset != 0 && n < 256); print_error("-- END OF BACKTRACE --"); print_error("================================================================"); SymCleanup(process); // Pass the exception to the OS return EXCEPTION_CONTINUE_SEARCH; } #endif CrashHandler::CrashHandler() { disabled = false; } CrashHandler::~CrashHandler() { } void CrashHandler::disable() { if (disabled) { return; } disabled = true; } void CrashHandler::initialize() { }