mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
313 lines
8.5 KiB
C++
313 lines
8.5 KiB
C++
#include "global_define.h"
|
|
#include "eqemu_logsys.h"
|
|
#include "crash.h"
|
|
#include "strings.h"
|
|
#include "process/process.h"
|
|
#include "http/httplib.h"
|
|
#include "http/uri.h"
|
|
#include "json/json.h"
|
|
#include "version.h"
|
|
#include "eqemu_config.h"
|
|
#include "serverinfo.h"
|
|
#include "rulesys.h"
|
|
#include "platform.h"
|
|
|
|
#include <cstdio>
|
|
#include <vector>
|
|
|
|
#ifdef _WINDOWS
|
|
#define popen _popen
|
|
#endif
|
|
|
|
void SendCrashReport(const std::string &crash_report)
|
|
{
|
|
// can configure multiple endpoints if need be
|
|
std::vector<std::string> endpoints = {
|
|
"https://spire.akkadius.com/api/v1/analytics/server-crash-report",
|
|
// "http://localhost:3010/api/v1/analytics/server-crash-report", // development
|
|
};
|
|
|
|
EQEmuLogSys* log = EQEmuLogSys::Instance();
|
|
|
|
auto config = EQEmuConfig::get();
|
|
for (auto &e: endpoints) {
|
|
uri u(e);
|
|
|
|
std::string base_url = fmt::format("{}://{}", u.get_scheme(), u.get_host());
|
|
if (u.get_port()) {
|
|
base_url += fmt::format(":{}", u.get_port());
|
|
}
|
|
|
|
// client
|
|
httplib::Client r(base_url);
|
|
r.set_connection_timeout(1, 0);
|
|
r.set_read_timeout(1, 0);
|
|
r.set_write_timeout(1, 0);
|
|
|
|
// os info
|
|
auto os = EQ::GetOS();
|
|
auto cpus = EQ::GetCPUs();
|
|
auto process_id = EQ::GetPID();
|
|
auto rss = EQ::GetRSS() / 1048576.0;
|
|
auto uptime = static_cast<uint32>(EQ::GetUptime());
|
|
|
|
// payload
|
|
Json::Value p;
|
|
p["platform_name"] = GetPlatformName();
|
|
p["crash_report"] = crash_report;
|
|
p["server_version"] = CURRENT_VERSION;
|
|
p["compile_date"] = COMPILE_DATE;
|
|
p["compile_time"] = COMPILE_TIME;
|
|
p["server_name"] = config->LongName;
|
|
p["server_short_name"] = config->ShortName;
|
|
p["uptime"] = uptime;
|
|
p["os_machine"] = os.machine;
|
|
p["os_release"] = os.release;
|
|
p["os_version"] = os.version;
|
|
p["os_sysname"] = os.sysname;
|
|
p["process_id"] = process_id;
|
|
p["rss_memory"] = rss;
|
|
p["cpus"] = cpus.size();
|
|
p["origination_info"] = "";
|
|
|
|
if (!log->origination_info.zone_short_name.empty()) {
|
|
p["origination_info"] = fmt::format(
|
|
"{} ({}) instance_id [{}]",
|
|
log->origination_info.zone_short_name,
|
|
log->origination_info.zone_long_name,
|
|
log->origination_info.instance_id
|
|
);
|
|
}
|
|
|
|
std::stringstream payload;
|
|
payload << p;
|
|
|
|
if (auto res = r.Post(e, payload.str(), "application/json")) {
|
|
if (res->status == 200) {
|
|
LogInfo("Sent crash report");
|
|
}
|
|
else {
|
|
LogError("Failed to send crash report to [{}]", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(_WINDOWS) && defined(CRASH_LOGGING)
|
|
#include "StackWalker.h"
|
|
|
|
class EQEmuStackWalker : public StackWalker
|
|
{
|
|
public:
|
|
EQEmuStackWalker() : StackWalker() { }
|
|
EQEmuStackWalker(DWORD dwProcessId, HANDLE hProcess) : StackWalker(dwProcessId, hProcess) { }
|
|
virtual void OnOutput(LPCSTR szText) {
|
|
char buffer[4096];
|
|
for (int i = 0; i < 4096; ++i) {
|
|
if (szText[i] == 0) {
|
|
buffer[i] = '\0';
|
|
break;
|
|
}
|
|
|
|
if (szText[i] == '\n' || szText[i] == '\r') {
|
|
buffer[i] = ' ';
|
|
}
|
|
else {
|
|
buffer[i] = szText[i];
|
|
}
|
|
}
|
|
|
|
std::string line = buffer;
|
|
_lines.push_back(line);
|
|
|
|
Log(Logs::General, Logs::Crash, buffer);
|
|
StackWalker::OnOutput(szText);
|
|
}
|
|
|
|
const std::vector<std::string>& GetLines() { return _lines; }
|
|
private:
|
|
std::vector<std::string> _lines;
|
|
};
|
|
|
|
LONG WINAPI windows_exception_handler(EXCEPTION_POINTERS *ExceptionInfo)
|
|
{
|
|
switch(ExceptionInfo->ExceptionRecord->ExceptionCode)
|
|
{
|
|
case EXCEPTION_ACCESS_VIOLATION:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_ACCESS_VIOLATION");
|
|
break;
|
|
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_ARRAY_BOUNDS_EXCEEDED");
|
|
break;
|
|
case EXCEPTION_BREAKPOINT:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_BREAKPOINT");
|
|
break;
|
|
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_DATATYPE_MISALIGNMENT");
|
|
break;
|
|
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_FLT_DENORMAL_OPERAND");
|
|
break;
|
|
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_FLT_DIVIDE_BY_ZERO");
|
|
break;
|
|
case EXCEPTION_FLT_INEXACT_RESULT:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_FLT_INEXACT_RESULT");
|
|
break;
|
|
case EXCEPTION_FLT_INVALID_OPERATION:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_FLT_INVALID_OPERATION");
|
|
break;
|
|
case EXCEPTION_FLT_OVERFLOW:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_FLT_OVERFLOW");
|
|
break;
|
|
case EXCEPTION_FLT_STACK_CHECK:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_FLT_STACK_CHECK");
|
|
break;
|
|
case EXCEPTION_FLT_UNDERFLOW:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_FLT_UNDERFLOW");
|
|
break;
|
|
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_ILLEGAL_INSTRUCTION");
|
|
break;
|
|
case EXCEPTION_IN_PAGE_ERROR:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_IN_PAGE_ERROR");
|
|
break;
|
|
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_INT_DIVIDE_BY_ZERO");
|
|
break;
|
|
case EXCEPTION_INT_OVERFLOW:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_INT_OVERFLOW");
|
|
break;
|
|
case EXCEPTION_INVALID_DISPOSITION:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_INVALID_DISPOSITION");
|
|
break;
|
|
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_NONCONTINUABLE_EXCEPTION");
|
|
break;
|
|
case EXCEPTION_PRIV_INSTRUCTION:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_PRIV_INSTRUCTION");
|
|
break;
|
|
case EXCEPTION_SINGLE_STEP:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_SINGLE_STEP");
|
|
break;
|
|
case EXCEPTION_STACK_OVERFLOW:
|
|
Log(Logs::General, Logs::Crash, "EXCEPTION_STACK_OVERFLOW");
|
|
break;
|
|
default:
|
|
Log(Logs::General, Logs::Crash, "Unknown Exception");
|
|
break;
|
|
}
|
|
|
|
if(EXCEPTION_STACK_OVERFLOW != ExceptionInfo->ExceptionRecord->ExceptionCode)
|
|
{
|
|
EQEmuStackWalker sw;
|
|
sw.ShowCallstack(GetCurrentThread(), ExceptionInfo->ContextRecord);
|
|
|
|
if (RuleB(Analytics, CrashReporting)) {
|
|
std::string crash_report;
|
|
auto& lines = sw.GetLines();
|
|
|
|
for (auto& line : lines) {
|
|
crash_report += line;
|
|
crash_report += "\n";
|
|
}
|
|
|
|
SendCrashReport(crash_report);
|
|
}
|
|
}
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
void set_exception_handler() {
|
|
SetUnhandledExceptionFilter(windows_exception_handler);
|
|
}
|
|
#else
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <sys/fcntl.h>
|
|
|
|
#ifdef __FreeBSD__
|
|
#include <signal.h>
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
void print_trace()
|
|
{
|
|
bool does_gdb_exist = Strings::Contains(Process::execute("gdb -v"), "GNU");
|
|
if (!does_gdb_exist) {
|
|
LogCrash(
|
|
"[Error] GDB is not installed, if you want crash dumps on Linux to work properly you will need GDB installed"
|
|
);
|
|
std::exit(1);
|
|
}
|
|
|
|
auto uid = geteuid();
|
|
std::string temp_output_file = fmt::format("/tmp/dump-output-{}", Strings::Random(10));
|
|
|
|
// check for passwordless sudo if not root
|
|
if (uid != 0) {
|
|
bool sudo_password_required = Strings::Contains(Process::execute("sudo -n true"), "a password is required");
|
|
if (sudo_password_required) {
|
|
LogCrash(
|
|
"[Error] Current user does not have passwordless sudo installed. It is required to automatically process crash dumps with GDB as non-root."
|
|
);
|
|
std::exit(1);
|
|
}
|
|
}
|
|
|
|
char pid_buf[30];
|
|
sprintf(pid_buf, "%d", getpid());
|
|
char name_buf[512];
|
|
name_buf[readlink("/proc/self/exe", name_buf, 511)] = 0;
|
|
int child_pid = fork();
|
|
if (!child_pid) {
|
|
int fd = open(temp_output_file.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
|
|
dup2(fd, 1); // redirect output to stderr
|
|
fprintf(stdout, "stack trace for %s pid=%s\n", name_buf, pid_buf);
|
|
if (uid == 0) {
|
|
execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
|
|
}
|
|
else {
|
|
execlp("sudo", "gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
|
|
}
|
|
|
|
close(fd);
|
|
|
|
abort(); /* If gdb failed to start */
|
|
}
|
|
else {
|
|
waitpid(child_pid, nullptr, 0);
|
|
}
|
|
|
|
std::ifstream input(temp_output_file);
|
|
std::string crash_report;
|
|
for (std::string line; getline(input, line);) {
|
|
LogCrash("{}", line);
|
|
crash_report += fmt::format("{}\n", line);
|
|
}
|
|
|
|
std::remove(temp_output_file.c_str());
|
|
|
|
if (RuleB(Analytics, CrashReporting)) {
|
|
SendCrashReport(crash_report);
|
|
}
|
|
|
|
EQEmuLogSys::Instance()->CloseFileLogs();
|
|
|
|
exit(1);
|
|
}
|
|
|
|
// crash is off or an unhandled platform
|
|
void set_exception_handler()
|
|
{
|
|
signal(SIGABRT, reinterpret_cast<void (*)(int)>(print_trace));
|
|
signal(SIGFPE, reinterpret_cast<void (*)(int)>(print_trace));
|
|
signal(SIGFPE, reinterpret_cast<void (*)(int)>(print_trace));
|
|
signal(SIGSEGV, reinterpret_cast<void (*)(int)>(print_trace));
|
|
}
|
|
#endif
|