#include "Diagnostics.h" #include "Game.h" #include "Logger.h" // If we're on Win32, we'll include our minidump writer #ifdef _WIN32 #include #include #include "Game.h" #include "Logger.h" void make_minidump(EXCEPTION_POINTERS* e) { auto hDbgHelp = LoadLibraryA("dbghelp"); if (hDbgHelp == nullptr) return; auto pMiniDumpWriteDump = (decltype(&MiniDumpWriteDump))GetProcAddress(hDbgHelp, "MiniDumpWriteDump"); if (pMiniDumpWriteDump == nullptr) return; char name[MAX_PATH]; { auto nameEnd = name + GetModuleFileNameA(GetModuleHandleA(0), name, MAX_PATH); SYSTEMTIME t; GetSystemTime(&t); wsprintfA(nameEnd - strlen(".exe"), "_%4d%02d%02d_%02d%02d%02d.dmp", t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond); } Log::Info("Creating crash dump {:s}", name); auto hFile = CreateFileA(name, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) return; MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; exceptionInfo.ThreadId = GetCurrentThreadId(); exceptionInfo.ExceptionPointers = e; exceptionInfo.ClientPointers = FALSE; auto dumped = pMiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory), e ? &exceptionInfo : nullptr, nullptr, nullptr); CloseHandle(hFile); return; } LONG CALLBACK unhandled_handler(EXCEPTION_POINTERS* e) { make_minidump(e); if (Game::logger) Game::logger->Flush(); // Flush our log if we have one, before exiting. return EXCEPTION_CONTINUE_SEARCH; } #endif #if defined(__linux__) //&& !defined(__clang__) // backtrace is a gcc exclusive system library #include #include #include #include #include #include #include #include #if defined(INCLUDE_BACKTRACE) #include #include struct bt_ctx { struct backtrace_state* state; int error; }; static inline void Bt(struct backtrace_state* state) { std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; Log::Info("backtrace is enabled, crash dump located at {:s}", fileName); FILE* file = fopen(fileName.c_str(), "w+"); if (file != nullptr) { backtrace_print(state, 2, file); fclose(file); } backtrace_print(state, 2, stdout); } static void ErrorCallback(void* data, const char* msg, int errnum) { auto* ctx = (struct bt_ctx*)data; fmt::print(stderr, "ERROR: {:s} ({:d})", msg, errnum); ctx->error = 1; std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; FILE* file = fopen(fileName.c_str(), "w+"); if (file != nullptr) { fmt::print(file, "ERROR: {:s} ({:d})", msg, errnum); fclose(file); } } #endif #include "Demangler.h" void GenerateDump() { std::string cmd = "sudo gcore " + std::to_string(getpid()); int ret = system(cmd.c_str()); // Saving a return just to prevent warning } void CatchUnhandled(int sig) { std::exception_ptr eptr = std::current_exception(); try { if (eptr) std::rethrow_exception(eptr); } catch(const std::exception& e) { Log::Warn("Caught exception: '{:s}'", e.what()); } #ifndef INCLUDE_BACKTRACE std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; Log::Warn("Encountered signal {:d}, creating crash dump {:s}", sig, fileName); if (Diagnostics::GetProduceMemoryDump()) { GenerateDump(); } constexpr uint8_t MaxStackTrace = 32; void* array[MaxStackTrace]; size_t size; // get void*'s for all entries on the stack size = backtrace(array, MaxStackTrace); # if defined(__GNUG__) // Loop through the returned addresses, and get the symbols to be demangled char** strings = backtrace_symbols(array, size); FILE* file = fopen(fileName.c_str(), "w+"); if (file != NULL) { fmt::println(file, "Error: signal {:d}:", sig); } // Print the stack trace for (size_t i = 0; i < size; i++) { // Take a string like './WorldServer(_ZN19SlashCommandHandler17HandleChatCommandERKSbIDsSt11char_traitsIDsESaIDsEEP6EntityRK13SystemAddress+0x6187) [0x55869c44ecf7]' // and extract '_ZN19SlashCommandHandler17HandleChatCommandERKSbIDsSt11char_traitsIDsESaIDsEEP6EntityRK13SystemAddress' from it to be demangled into a proper name std::string functionName = strings[i]; std::string::size_type start = functionName.find('('); std::string::size_type end = functionName.find('+'); if (start != std::string::npos && end != std::string::npos) { std::string demangled = functionName.substr(start + 1, end - start - 1); demangled = Demangler::Demangle(demangled.c_str()); // If the demangled string is not empty, then we can replace the mangled string with the demangled one if (!demangled.empty()) { demangled.push_back('('); demangled += functionName.substr(end); functionName = demangled; } } Log::Info("[{:02d}] {:s}", i, functionName); if (file != NULL) { fmt::println(file, "[{:02d}] {:s}", i, functionName); } } # else // defined(__GNUG__) backtrace_symbols_fd(array, size, STDOUT_FILENO); # endif // defined(__GNUG__) #else // INCLUDE_BACKTRACE struct backtrace_state* state = backtrace_create_state( Diagnostics::GetProcessFileName().c_str(), BACKTRACE_SUPPORTS_THREADS, ErrorCallback, nullptr); struct bt_ctx ctx = { state, 0 }; Bt(state); #endif // INCLUDE_BACKTRACE exit(EXIT_FAILURE); } void CritErrHdlr(int sig_num, siginfo_t* info, void* ucontext) { CatchUnhandled(sig_num); } void OnTerminate() { CatchUnhandled(-1); } void MakeBacktrace() { struct sigaction sigact; sigact.sa_sigaction = CritErrHdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO; if (sigaction(SIGSEGV, &sigact, nullptr) != 0 || sigaction(SIGFPE, &sigact, nullptr) != 0 || sigaction(SIGABRT, &sigact, nullptr) != 0 || sigaction(SIGILL, &sigact, nullptr) != 0) { fmt::println(stderr, "error setting signal handler for {:d} ({:s})", SIGSEGV, strsignal(SIGSEGV)); exit(EXIT_FAILURE); } std::set_terminate(OnTerminate); } #endif void Diagnostics::Initialize() { #ifdef _WIN32 SetUnhandledExceptionFilter(unhandled_handler); #elif defined(__linux__) //&& !defined(__clang__) MakeBacktrace(); #else fprintf(stderr, "Diagnostics not supported on this platform.\n"); #endif } std::string Diagnostics::m_ProcessName{}; std::string Diagnostics::m_ProcessFileName{}; std::string Diagnostics::m_OutDirectory{}; bool Diagnostics::m_ProduceMemoryDump{}; void Diagnostics::SetProcessName(const std::string& name) { m_ProcessName = name; } void Diagnostics::SetProcessFileName(const std::string& name) { m_ProcessFileName = name; } void Diagnostics::SetOutDirectory(const std::string& path) { m_OutDirectory = path; } void Diagnostics::SetProduceMemoryDump(bool value) { m_ProduceMemoryDump = value; } const std::string& Diagnostics::GetProcessName() { return m_ProcessName; } const std::string& Diagnostics::GetProcessFileName() { return m_ProcessFileName; } const std::string& Diagnostics::GetOutDirectory() { return m_OutDirectory; } bool Diagnostics::GetProduceMemoryDump() { return m_ProduceMemoryDump; }