DarkflameServer/dCommon/Diagnostics.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

268 lines
7.2 KiB
C++
Raw Permalink Normal View History

#include "Diagnostics.h"
#include "Game.h"
#include "Logger.h"
// If we're on Win32, we'll include our minidump writer
#ifdef _WIN32
2021-12-05 23:59:50 +00:00
#include <Windows.h>
2021-12-05 23:59:50 +00:00
#include <Dbghelp.h>
#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;
2022-07-28 13:39:57 +00:00
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);
}
2024-03-08 21:44:02 +00:00
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;
2022-07-28 13:39:57 +00:00
MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
exceptionInfo.ThreadId = GetCurrentThreadId();
exceptionInfo.ExceptionPointers = e;
exceptionInfo.ClientPointers = FALSE;
2022-07-28 13:39:57 +00:00
auto dumped = pMiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory),
e ? &exceptionInfo : nullptr,
nullptr,
nullptr);
2022-07-28 13:39:57 +00:00
CloseHandle(hFile);
2022-07-28 13:39:57 +00:00
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 <execinfo.h>
#include <ucontext.h>
#include <unistd.h>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#if defined(INCLUDE_BACKTRACE)
#include <backtrace.h>
#include <backtrace-supported.h>
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";
2024-03-08 21:44:02 +00:00
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);
}
2022-07-28 13:39:57 +00:00
backtrace_print(state, 2, stdout);
}
static void ErrorCallback(void* data, const char* msg, int errnum) {
auto* ctx = (struct bt_ctx*)data;
2024-03-08 21:44:02 +00:00
fmt::print(stderr, "ERROR: {:s} ({:d})", msg, errnum);
ctx->error = 1;
2022-07-28 13:39:57 +00:00
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
FILE* file = fopen(fileName.c_str(), "w+");
if (file != nullptr) {
2024-03-08 21:44:02 +00:00
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());
2023-04-12 16:48:20 +00:00
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) {
2024-03-08 21:44:02 +00:00
Log::Warn("Caught exception: '{:s}'", e.what());
}
#ifndef INCLUDE_BACKTRACE
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
2024-03-08 21:44:02 +00:00
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);
2022-07-28 13:39:57 +00:00
2023-10-23 00:58:19 +00:00
FILE* file = fopen(fileName.c_str(), "w+");
if (file != NULL) {
2024-03-08 21:44:02 +00:00
fmt::println(file, "Error: signal {:d}:", sig);
2023-10-23 00:58:19 +00:00
}
// 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);
2022-07-28 13:39:57 +00:00
demangled = Demangler::Demangle(demangled.c_str());
2022-07-28 13:39:57 +00:00
// 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;
}
}
2024-03-08 21:44:02 +00:00
Log::Info("[{:02d}] {:s}", i, functionName);
2023-10-23 00:58:19 +00:00
if (file != NULL) {
2024-03-08 21:44:02 +00:00
fmt::println(file, "[{:02d}] {:s}", i, functionName);
2023-10-23 00:58:19 +00:00
}
}
# 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) {
2024-03-08 21:44:02 +00:00
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;
}