Files
DarkflameServer/dCommon/Diagnostics.cpp
Aaron Kimbrell e213a7aeff feat: web dashboard and playground work
The NexusDashboard-parity dashboard (dDashboardServer) and everything built on it on the experimental branch:
accounts, characters, properties and moderation tools, permissions shared with in-game slash commands, economy
reports, World 3D and property 3D views with client scenery, scheduled events (features, vanity changes, live
events, announcements, restarts), vanity files and events, the CDClient browser, the message inspector with saved
captures, chat filter tools, community challenges, live ops, the AI moderator helper, and the server-side changes
they need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:43 -05:00

276 lines
7.5 KiB
C++

#include "Diagnostics.h"
#include <filesystem>
#include "Game.h"
#include "Logger.h"
// If we're on Win32, we'll include our minidump writer
#ifdef _WIN32
#include <Windows.h>
#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;
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("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 <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";
LOG("backtrace is enabled, crash dump located at %s", fileName.c_str());
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;
fprintf(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) {
fprintf(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("Caught exception: '%s'", e.what());
} catch (...) {
LOG("Caught unknown exception.");
}
#ifndef INCLUDE_BACKTRACE
std::string fileName = Diagnostics::GetOutDirectory() + "Crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
LOG("Encountered signal %i, creating crash dump %s", sig, fileName.c_str());
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) {
fprintf(file, "Error: signal %d:\n", 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("[%02zu] %s", i, functionName.c_str());
if (file != NULL) {
fprintf(file, "[%02zu] %s\n", i, functionName.c_str());
}
}
# 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) {
fprintf(stderr, "error setting signal handler for %d (%s)\n",
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) {
// Crash files are named by appending to this, so it must end in a separator; the folder may not exist yet
m_OutDirectory = path;
if (!m_OutDirectory.empty() && m_OutDirectory.back() != '/' && m_OutDirectory.back() != '\\') m_OutDirectory += '/';
std::error_code ec;
if (!m_OutDirectory.empty()) std::filesystem::create_directories(m_OutDirectory, ec);
}
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;
}