/*
* =============================================================================
* Accelerator Extension
* Copyright (C) 2011 Asher Baker (asherkin). All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
#include "extension.h"
#ifndef PLATFORM_ARCH_FOLDER
#define PLATFORM_ARCH_FOLDER ""
#endif
#include
#include
#include "MemoryDownloader.h"
#if defined _LINUX
#include "client/linux/handler/exception_handler.h"
#include "common/linux/linux_libc_support.h"
#include "third_party/lss/linux_syscall_support.h"
#include "common/linux/dump_symbols.h"
#include "common/path_helper.h"
#include
#include
#include
#include
class StderrInhibitor
{
FILE *saved_stderr = nullptr;
public:
StderrInhibitor() {
saved_stderr = fdopen(dup(fileno(stderr)), "w");
if (freopen(_PATH_DEVNULL, "w", stderr)) {
// If it fails, not a lot we can (or should) do.
// Add this brace section to silence gcc warnings.
}
}
~StderrInhibitor() {
fflush(stderr);
dup2(fileno(saved_stderr), fileno(stderr));
fclose(saved_stderr);
}
};
// Taken from https://hg.mozilla.org/mozilla-central/file/3eb7623b5e63b37823d5e9c562d56e586604c823/build/unix/stdc%2B%2Bcompat/stdc%2B%2Bcompat.cpp
extern "C" void __attribute__((weak)) __cxa_throw_bad_array_new_length() {
abort();
}
namespace std {
/* We shouldn't be throwing exceptions at all, but it sadly turns out
we call STL (inline) functions that do. */
void __attribute__((weak)) __throw_out_of_range_fmt(char const* fmt, ...) {
va_list ap;
char buf[1024]; // That should be big enough.
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
buf[sizeof(buf) - 1] = 0;
va_end(ap);
__throw_range_error(buf);
}
} // namespace std
// Updated versions of the SM ones for C++14
void operator delete(void *ptr, size_t sz) {
free(ptr);
}
void operator delete[](void *ptr, size_t sz) {
free(ptr);
}
#elif defined _WINDOWS
#define _STDINT // ~.~
#include "client/windows/handler/exception_handler.h"
#else
#error Bad platform.
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
Accelerator g_accelerator;
SMEXT_LINK(&g_accelerator);
IWebternet *webternet;
IGameConfig *gameconfig;
typedef void (*GetSpew_t)(char *buffer, unsigned int length);
GetSpew_t GetSpew;
#if defined _WINDOWS
typedef void(__fastcall *GetSpewFastcall_t)(char *buffer, unsigned int length);
GetSpewFastcall_t GetSpewFastcall;
#endif
char spewBuffer[65536]; // Hi.
char crashMap[256];
char crashGamePath[512];
char crashCommandLine[1024];
char crashSourceModPath[512];
char crashGameDirectory[256];
char crashSourceModVersion[32];
char steamInf[1024];
char dumpStoragePath[512];
char logPath[512];
google_breakpad::ExceptionHandler *handler = NULL;
#if defined _LINUX
void terminateHandler()
{
const char *msg = "missing exception";
std::exception_ptr pEx = std::current_exception();
if (pEx) {
try {
std::rethrow_exception(pEx);
} catch(const std::exception &e) {
msg = strdup(e.what());
} catch(...) {
msg = "unknown exception";
}
}
size_t msgLength = strlen(msg) + 2;
volatile char * volatile msgForCrashDumps = (char *)alloca(msgLength);
strcpy((char *)msgForCrashDumps + 1, msg);
abort();
}
void (*SignalHandler)(int, siginfo_t *, void *);
const int kExceptionSignals[] = {
SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS
};
const int kNumHandledSignals = sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]);
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
{
//printf("Wrote minidump to: %s\n", descriptor.path());
if (succeeded) {
sys_write(STDOUT_FILENO, "Wrote minidump to: ", 19);
} else {
sys_write(STDOUT_FILENO, "Failed to write minidump to: ", 29);
}
sys_write(STDOUT_FILENO, descriptor.path(), my_strlen(descriptor.path()));
sys_write(STDOUT_FILENO, "\n", 1);
if (!succeeded) {
return succeeded;
}
my_strlcpy(dumpStoragePath, descriptor.path(), sizeof(dumpStoragePath));
my_strlcat(dumpStoragePath, ".txt", sizeof(dumpStoragePath));
int extra = sys_open(dumpStoragePath, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (extra == -1) {
sys_write(STDOUT_FILENO, "Failed to open metadata file!\n", 30);
return succeeded;
}
sys_write(extra, "-------- CONFIG BEGIN --------", 30);
sys_write(extra, "\nMap=", 5);
sys_write(extra, crashMap, my_strlen(crashMap));
sys_write(extra, "\nGamePath=", 10);
sys_write(extra, crashGamePath, my_strlen(crashGamePath));
sys_write(extra, "\nCommandLine=", 13);
sys_write(extra, crashCommandLine, my_strlen(crashCommandLine));
sys_write(extra, "\nSourceModPath=", 15);
sys_write(extra, crashSourceModPath, my_strlen(crashSourceModPath));
sys_write(extra, "\nGameDirectory=", 15);
sys_write(extra, crashGameDirectory, my_strlen(crashGameDirectory));
if (crashSourceModVersion[0]) {
sys_write(extra, "\nSourceModVersion=", 18);
sys_write(extra, crashSourceModVersion, my_strlen(crashSourceModVersion));
}
sys_write(extra, "\nExtensionVersion=", 18);
sys_write(extra, SM_VERSION, my_strlen(SM_VERSION));
sys_write(extra, "\nExtensionBuild=", 16);
sys_write(extra, SM_BUILD_UNIQUEID, my_strlen(SM_BUILD_UNIQUEID));
sys_write(extra, steamInf, my_strlen(steamInf));
sys_write(extra, "\n-------- CONFIG END --------\n", 30);
if (GetSpew) {
GetSpew(spewBuffer, sizeof(spewBuffer));
if (my_strlen(spewBuffer) > 0) {
sys_write(extra, "-------- CONSOLE HISTORY BEGIN --------\n", 40);
sys_write(extra, spewBuffer, my_strlen(spewBuffer));
sys_write(extra, "-------- CONSOLE HISTORY END --------\n", 38);
}
}
sys_close(extra);
return succeeded;
}
void OnGameFrame(bool simulating)
{
std::set_terminate(terminateHandler);
bool weHaveBeenFuckedOver = false;
struct sigaction oact;
for (int i = 0; i < kNumHandledSignals; ++i) {
sigaction(kExceptionSignals[i], NULL, &oact);
if (oact.sa_sigaction != SignalHandler) {
weHaveBeenFuckedOver = true;
break;
}
}
if (!weHaveBeenFuckedOver) {
return;
}
struct sigaction act;
memset(&act, 0, sizeof(act));
sigemptyset(&act.sa_mask);
for (int i = 0; i < kNumHandledSignals; ++i) {
sigaddset(&act.sa_mask, kExceptionSignals[i]);
}
act.sa_sigaction = SignalHandler;
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
for (int i = 0; i < kNumHandledSignals; ++i) {
sigaction(kExceptionSignals[i], &act, NULL);
}
}
#elif defined _WINDOWS
void *vectoredHandler = NULL;
LONG CALLBACK BreakpadVectoredHandler(_In_ PEXCEPTION_POINTERS ExceptionInfo)
{
switch (ExceptionInfo->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_INVALID_HANDLE:
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
case EXCEPTION_DATATYPE_MISALIGNMENT:
case EXCEPTION_ILLEGAL_INSTRUCTION:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
case EXCEPTION_STACK_OVERFLOW:
case 0xC0000409: // STATUS_STACK_BUFFER_OVERRUN
case 0xC0000374: // STATUS_HEAP_CORRUPTION
break;
case 0: // Valve use this for Sys_Error.
if ((ExceptionInfo->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) == 0)
return EXCEPTION_CONTINUE_SEARCH;
break;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
if (handler->WriteMinidumpForException(ExceptionInfo))
{
// Stop the handler thread from deadlocking us.
delete handler;
// Stop Valve's handler being called.
ExceptionInfo->ExceptionRecord->ExceptionCode = EXCEPTION_BREAKPOINT;
return EXCEPTION_EXECUTE_HANDLER;
} else {
return EXCEPTION_CONTINUE_SEARCH;
}
}
static bool dumpCallback(const wchar_t* dump_path,
const wchar_t* minidump_id,
void* context,
EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion,
bool succeeded)
{
if (!succeeded) {
printf("Failed to write minidump to: %ls\\%ls.dmp\n", dump_path, minidump_id);
return succeeded;
}
printf("Wrote minidump to: %ls\\%ls.dmp\n", dump_path, minidump_id);
sprintf(dumpStoragePath, "%ls\\%ls.dmp.txt", dump_path, minidump_id);
FILE *extra = fopen(dumpStoragePath, "wb");
if (!extra) {
printf("Failed to open metadata file!\n");
return succeeded;
}
fprintf(extra, "-------- CONFIG BEGIN --------");
fprintf(extra, "\nMap=%s", crashMap);
fprintf(extra, "\nGamePath=%s", crashGamePath);
fprintf(extra, "\nCommandLine=%s", crashCommandLine);
fprintf(extra, "\nSourceModPath=%s", crashSourceModPath);
fprintf(extra, "\nGameDirectory=%s", crashGameDirectory);
if (crashSourceModVersion[0]) {
fprintf(extra, "\nSourceModVersion=%s", crashSourceModVersion);
}
fprintf(extra, "\nExtensionVersion=%s", SM_VERSION);
fprintf(extra, "\nExtensionBuild=%s", SM_BUILD_UNIQUEID);
fprintf(extra, "%s", steamInf);
fprintf(extra, "\n-------- CONFIG END --------\n");
if (GetSpew || GetSpewFastcall) {
if (GetSpew) {
GetSpew(spewBuffer, sizeof(spewBuffer));
} else if (GetSpewFastcall) {
GetSpewFastcall(spewBuffer, sizeof(spewBuffer));
}
if (spewBuffer[0]) {
fprintf(extra, "-------- CONSOLE HISTORY BEGIN --------\n%s-------- CONSOLE HISTORY END --------\n", spewBuffer);
}
}
fclose(extra);
return succeeded;
}
#else
#error Bad platform.
#endif
class ClogInhibitor
{
std::streambuf *saved_clog = nullptr;
public:
ClogInhibitor() {
saved_clog = std::clog.rdbuf();
std::clog.rdbuf(nullptr);
}
~ClogInhibitor() {
std::clog.rdbuf(saved_clog);
}
};
class UploadThread: public IThread
{
FILE *log = nullptr;
char serverId[38] = "";
void RunThread(IThreadHandle *pHandle) {
rootconsole->ConsolePrint("Accelerator upload thread started.");
log = fopen(logPath, "a");
if (!log) {
g_pSM->LogError(myself, "Failed to open Accelerator log file: %s", logPath);
}
char path[512];
g_pSM->Format(path, sizeof(path), "%s/server-id.txt", dumpStoragePath);
FILE *serverIdFile = fopen(path, "r");
if (serverIdFile) {
fread(serverId, 1, sizeof(serverId) - 1, serverIdFile);
if (!feof(serverIdFile) || strlen(serverId) != 36) {
serverId[0] = '\0';
}
fclose(serverIdFile);
}
if (!serverId[0]) {
serverIdFile = fopen(path, "w");
if (serverIdFile) {
g_pSM->Format(serverId, sizeof(serverId), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
rand() % 255, rand() % 255, rand() % 255, rand() % 255, rand() % 255, rand() % 255, 0x40 | ((rand() % 255) & 0x0F), rand() % 255,
0x80 | ((rand() % 255) & 0x3F), rand() % 255, rand() % 255, rand() % 255, rand() % 255, rand() % 255, rand() % 255, rand() % 255);
fputs(serverId, serverIdFile);
fclose(serverIdFile);
}
}
IDirectory *dumps = libsys->OpenDirectory(dumpStoragePath);
int skip = 0;
int count = 0;
int failed = 0;
char metapath[512];
char presubmitToken[512];
char response[512];
while (dumps->MoreFiles()) {
if (!dumps->IsEntryFile()) {
dumps->NextEntry();
continue;
}
const char *name = dumps->GetEntryName();
int namelen = strlen(name);
if (namelen < 4 || strcmp(&name[namelen-4], ".dmp") != 0) {
dumps->NextEntry();
continue;
}
g_pSM->Format(path, sizeof(path), "%s/%s", dumpStoragePath, name);
g_pSM->Format(metapath, sizeof(metapath), "%s.txt", path);
if (!libsys->PathExists(metapath)) {
metapath[0] = '\0';
}
presubmitToken[0] = '\0';
PresubmitResponse presubmitResponse = kPRUploadCrashDumpAndMetadata;
const char *presubmitOption = g_pSM->GetCoreConfigValue("MinidumpPresubmit");
bool canPresubmit = !presubmitOption || (tolower(presubmitOption[0]) == 'y' || presubmitOption[0] == '1');
if (canPresubmit) {
presubmitResponse = PresubmitCrashDump(path, presubmitToken, sizeof(presubmitToken));
}
switch (presubmitResponse) {
case kPRLocalError:
failed++;
g_pSM->LogError(myself, "Accelerator failed to locally process crash dump");
if (log) fprintf(log, "Failed to locally process crash dump");
break;
case kPRRemoteError:
case kPRUploadCrashDumpAndMetadata:
case kPRUploadMetadataOnly:
if (UploadCrashDump((presubmitResponse == kPRUploadMetadataOnly) ? nullptr : path, metapath, presubmitToken, response, sizeof(response))) {
count++;
g_pSM->LogError(myself, "Accelerator uploaded crash dump: %s", response);
if (log) fprintf(log, "Uploaded crash dump: %s\n", response);
} else {
failed++;
g_pSM->LogError(myself, "Accelerator failed to upload crash dump: %s", response);
if (log) fprintf(log, "Failed to upload crash dump: %s\n", response);
}
break;
case kPRDontUpload:
skip++;
g_pSM->LogError(myself, "Accelerator crash dump upload skipped by server");
if (log) fprintf(log, "Skipped due to server request\n");
break;
}
if (metapath[0]) {
unlink(metapath);
}
unlink(path);
if (log) fflush(log);
dumps->NextEntry();
}
libsys->CloseDirectory(dumps);
if (log) {
fclose(log);
log = nullptr;
}
rootconsole->ConsolePrint("Accelerator upload thread finished. (%d skipped, %d uploaded, %d failed)", skip, count, failed);
}
void OnTerminate(IThreadHandle *pHandle, bool cancel) {
rootconsole->ConsolePrint("Accelerator upload thread terminated. (canceled = %s)", (cancel ? "true" : "false"));
}
#if defined _LINUX
bool UploadSymbolFile(const google_breakpad::CodeModule *module, const char *presubmitToken) {
auto debugFile = module->debug_file();
std::string vdsoOutputPath = "";
if (debugFile == "linux-gate.so") {
FILE *auxvFile = fopen("/proc/self/auxv", "rb");
if (auxvFile) {
char vdsoOutputPathBuffer[512];
g_pSM->BuildPath(Path_SM, vdsoOutputPathBuffer, sizeof(vdsoOutputPathBuffer), "data/dumps/linux-gate.so");
vdsoOutputPath = vdsoOutputPathBuffer;
while (!feof(auxvFile)) {
int auxvEntryId = 0;
fread(&auxvEntryId, sizeof(auxvEntryId), 1, auxvFile);
long auxvEntryValue = 0;
fread(&auxvEntryValue, sizeof(auxvEntryValue), 1, auxvFile);
if (auxvEntryId == 0) break;
if (auxvEntryId != 33) continue; // AT_SYSINFO_EHDR
Elf32_Ehdr *vdsoHdr = (Elf32_Ehdr *)auxvEntryValue;
auto vdsoSize = vdsoHdr->e_shoff + (vdsoHdr->e_shentsize * vdsoHdr->e_shnum);
void *vdsoBuffer = malloc(vdsoSize);
memcpy(vdsoBuffer, vdsoHdr, vdsoSize);
FILE *vdsoFile = fopen(vdsoOutputPath.c_str(), "wb");
if (vdsoFile) {
fwrite(vdsoBuffer, 1, vdsoSize, vdsoFile);
fclose(vdsoFile);
debugFile = vdsoOutputPath;
}
free(vdsoBuffer);
break;
}
fclose(auxvFile);
}
}
if (debugFile[0] != '/') {
return false;
}
if (log) fprintf(log, "Submitting symbols for %s\n", debugFile.c_str());
auto debugFileDir = google_breakpad::DirName(debugFile);
std::vector debug_dirs{
debugFileDir,
debugFileDir + "/.debug",
"/usr/lib/debug" + debugFileDir,
};
std::ostringstream outputStream;
google_breakpad::DumpOptions options(ALL_SYMBOL_DATA, true);
{
StderrInhibitor stdrrInhibitor;
if (!WriteSymbolFile(debugFileDir, debugFile, "Linux", debug_dirs, options, outputStream)) {
outputStream.str("");
outputStream.clear();
// Try again without debug dirs.
if (!WriteSymbolFile(debugFileDir, debugFile, "Linux", {}, options, outputStream)) {
if (log) fprintf(log, "Failed to process symbol file\n");
return false;
}
}
}
auto output = outputStream.str();
// output = output.substr(0, output.find("\n"));
// printf(">>> %s\n", output.c_str());
if (debugFile == vdsoOutputPath) {
unlink(vdsoOutputPath.c_str());
}
IWebForm *form = webternet->CreateForm();
const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
if (minidumpAccount && minidumpAccount[0]) form->AddString("UserID", minidumpAccount);
form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
form->AddString("ServerID", serverId);
if (presubmitToken && presubmitToken[0]) {
form->AddString("PresubmitToken", presubmitToken);
}
form->AddString("symbol_file", output.c_str());
MemoryDownloader data;
IWebTransfer *xfer = webternet->CreateSession();
xfer->SetFailOnHTTPError(true);
const char *symbolUrl = g_pSM->GetCoreConfigValue("MinidumpSymbolUrl");
if (!symbolUrl) symbolUrl = "http://crash.limetech.org/symbols/submit";
bool symbolUploaded = xfer->PostAndDownload(symbolUrl, form, &data, NULL);
if (!symbolUploaded) {
if (log) fprintf(log, "Symbol upload failed: %s (%d)\n", xfer->LastErrorMessage(), xfer->LastErrorCode());
return false;
}
int responseSize = data.GetSize();
char *response = new char[responseSize + 1];
strncpy(response, data.GetBuffer(), responseSize + 1);
response[responseSize] = '\0';
while (responseSize > 0 && response[responseSize - 1] == '\n') {
response[--responseSize] = '\0';
}
if (log) fprintf(log, "Symbol upload complete: %s\n", response);
delete[] response;
return true;
}
#endif
bool UploadModuleFile(const google_breakpad::CodeModule *module, const char *presubmitToken) {
const auto &codeFile = module->code_file();
#ifndef WIN32
if (codeFile[0] != '/') {
#else
if (codeFile[1] != ':') {
#endif
return false;
}
if (log) fprintf(log, "Submitting binary for %s\n", codeFile.c_str());
IWebForm *form = webternet->CreateForm();
const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
if (minidumpAccount && minidumpAccount[0]) form->AddString("UserID", minidumpAccount);
form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
form->AddString("ServerID", serverId);
if (presubmitToken && presubmitToken[0]) {
form->AddString("PresubmitToken", presubmitToken);
}
form->AddString("debug_identifier", module->debug_identifier().c_str());
form->AddString("code_identifier", module->code_identifier().c_str());
form->AddFile("code_file", codeFile.c_str());
MemoryDownloader data;
IWebTransfer *xfer = webternet->CreateSession();
xfer->SetFailOnHTTPError(true);
const char *binaryUrl = g_pSM->GetCoreConfigValue("MinidumpBinaryUrl");
if (!binaryUrl) binaryUrl = "http://crash.limetech.org/binary/submit";
bool binaryUploaded = xfer->PostAndDownload(binaryUrl, form, &data, NULL);
if (!binaryUploaded) {
if (log) fprintf(log, "Binary upload failed: %s (%d)\n", xfer->LastErrorMessage(), xfer->LastErrorCode());
return false;
}
int responseSize = data.GetSize();
char *response = new char[responseSize + 1];
strncpy(response, data.GetBuffer(), responseSize + 1);
response[responseSize] = '\0';
while (responseSize > 0 && response[responseSize - 1] == '\n') {
response[--responseSize] = '\0';
}
if (log) fprintf(log, "Binary upload complete: %s\n", response);
delete[] response;
return true;
}
enum ModuleType {
kMTUnknown,
kMTSystem,
kMTGame,
kMTAddon,
kMTExtension,
};
const char *ModuleTypeCode[5] = {
"Unknown",
"System",
"Game",
"Addon",
"Extension",
};
#ifndef WIN32
#define PATH_SEP "/"
#else
#define PATH_SEP "\\"
#endif
bool PathPrefixMatches(const std::string &prefix, const std::string &path) {
#ifndef WIN32
return strncmp(prefix.c_str(), path.c_str(), prefix.length()) == 0;
#else
return _strnicmp(prefix.c_str(), path.c_str(), prefix.length()) == 0;
#endif
}
struct PathComparator {
struct compare {
bool operator() (const unsigned char &a, const unsigned char &b) const {
#ifndef WIN32
return a < b;
#else
return tolower(a) < tolower(b);
#endif
}
};
bool operator() (const std::string &a, const std::string &b) const {
return !std::lexicographical_compare(
a.begin(), a.end(),
b.begin(), b.end(),
compare());
};
};
std::map modulePathMap;
bool InitModuleClassificationMap(const std::string &base) {
if (!modulePathMap.empty()) {
modulePathMap.clear();
}
modulePathMap[base] = kMTGame;
modulePathMap[std::string(crashGamePath) + PATH_SEP "addons" PATH_SEP] = kMTAddon;
modulePathMap[std::string(crashSourceModPath) + PATH_SEP "extensions" PATH_SEP] = kMTExtension;
return true;
}
ModuleType ClassifyModule(const google_breakpad::CodeModule *module) {
if (modulePathMap.empty()) {
return kMTUnknown;
}
const auto &codeFile = module->code_file();
#ifndef WIN32
if (codeFile == "linux-gate.so") {
return kMTSystem;
}
if (codeFile[0] != '/') {
#else
if (codeFile[1] != ':') {
#endif
return kMTUnknown;
}
for (decltype(modulePathMap)::const_iterator i = modulePathMap.begin(); i != modulePathMap.end(); ++i) {
if (PathPrefixMatches(i->first, codeFile)) {
return i->second;
}
}
return kMTSystem;
}
std::string PathnameStripper_Directory(const std::string &path) {
std::string::size_type slash = path.rfind('/');
std::string::size_type backslash = path.rfind('\\');
std::string::size_type file_start = 0;
if (slash != std::string::npos && (backslash == std::string::npos || slash > backslash)) {
file_start = slash + 1;
} else if (backslash != string::npos) {
file_start = backslash + 1;
}
return path.substr(0, file_start);
}
enum PresubmitResponse {
kPRLocalError,
kPRRemoteError,
kPRDontUpload,
kPRUploadCrashDumpAndMetadata,
kPRUploadMetadataOnly,
};
PresubmitResponse PresubmitCrashDump(const char *path, char *tokenBuffer, size_t tokenBufferLength) {
google_breakpad::ProcessState processState;
google_breakpad::ProcessResult processResult;
google_breakpad::MinidumpProcessor minidumpProcessor(nullptr, nullptr);
{
ClogInhibitor clogInhibitor;
processResult = minidumpProcessor.Process(path, &processState);
}
if (processResult != google_breakpad::PROCESS_OK) {
return kPRLocalError;
}
std::string os_short = "";
std::string cpu_arch = "";
if (processState.system_info()) {
os_short = processState.system_info()->os_short;
if (os_short.empty()) {
os_short = processState.system_info()->os;
}
cpu_arch = processState.system_info()->cpu;
}
int requestingThread = processState.requesting_thread();
if (requestingThread == -1) {
requestingThread = 0;
}
const google_breakpad::CallStack *stack = processState.threads()->at(requestingThread);
if (!stack) {
return kPRLocalError;
}
int frameCount = stack->frames()->size();
if (frameCount > 1024) {
frameCount = 1024;
}
std::ostringstream summaryStream;
summaryStream << 2 << "|" << processState.time_date_stamp() << "|" << os_short << "|" << cpu_arch << "|" << processState.crashed() << "|" << processState.crash_reason() << "|" << std::hex << processState.crash_address() << std::dec << "|" << requestingThread;
std::map moduleMap;
unsigned int moduleCount = processState.modules() ? processState.modules()->module_count() : 0;
for (unsigned int moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
auto module = processState.modules()->GetModuleAtIndex(moduleIndex);
moduleMap[module] = moduleIndex;
auto debugFile = google_breakpad::PathnameStripper::File(module->debug_file());
auto debugIdentifier = module->debug_identifier();
summaryStream << "|M|" << debugFile << "|" << debugIdentifier;
}
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
auto frame = stack->frames()->at(frameIndex);
int moduleIndex = -1;
auto moduleOffset = frame->ReturnAddress();
if (frame->module) {
moduleIndex = moduleMap[frame->module];
moduleOffset -= frame->module->base_address();
}
summaryStream << "|F|" << moduleIndex << "|" << std::hex << moduleOffset << std::dec;
}
auto summaryLine = summaryStream.str();
// printf("%s\n", summaryLine.c_str());
IWebForm *form = webternet->CreateForm();
const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
if (minidumpAccount && minidumpAccount[0]) form->AddString("UserID", minidumpAccount);
form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
form->AddString("ServerID", serverId);
form->AddString("CrashSignature", summaryLine.c_str());
MemoryDownloader data;
IWebTransfer *xfer = webternet->CreateSession();
xfer->SetFailOnHTTPError(true);
const char *minidumpUrl = g_pSM->GetCoreConfigValue("MinidumpUrl");
if (!minidumpUrl) minidumpUrl = "http://crash.limetech.org/submit";
bool uploaded = xfer->PostAndDownload(minidumpUrl, form, &data, NULL);
if (!uploaded) {
if (log) fprintf(log, "Presubmit failed: %s (%d)\n", xfer->LastErrorMessage(), xfer->LastErrorCode());
return kPRRemoteError;
}
int responseSize = data.GetSize();
char *response = new char[responseSize + 1];
strncpy(response, data.GetBuffer(), responseSize + 1);
response[responseSize] = '\0';
while (responseSize > 0 && response[responseSize - 1] == '\n') {
response[--responseSize] = '\0';
}
//if (log) fprintf(log, "Presubmit complete: %s\n", response);
if (responseSize < 2) {
if (log) fprintf(log, "Presubmit response too short\n");
delete[] response;
return kPRRemoteError;
}
if (response[0] == 'E') {
if (log) fprintf(log, "Presubmit error: %s\n", &response[2]);
delete[] response;
return kPRRemoteError;
}
PresubmitResponse presubmitResponse = kPRRemoteError;
if (response[0] == 'Y') presubmitResponse = kPRUploadCrashDumpAndMetadata;
else if (response[0] == 'N') presubmitResponse = kPRDontUpload;
else if (response[0] == 'M') presubmitResponse = kPRUploadMetadataOnly;
else return kPRRemoteError;
if (response[1] != '|') {
if (log) fprintf(log, "Response delimiter missing\n");
delete[] response;
return kPRRemoteError;
}
unsigned int responseCount = responseSize - 2;
if (responseCount < moduleCount) {
if (log) fprintf(log, "Response module list doesn't match sent list (%d < %d)\n", responseCount, moduleCount);
delete[] response;
return presubmitResponse;
}
// There was a presubmit token included.
if (tokenBuffer && responseCount > moduleCount && response[2 + moduleCount] == '|') {
int tokenStart = 2 + moduleCount + 1;
int tokenEnd = tokenStart;
while (tokenEnd < responseSize && response[tokenEnd] != '|') {
tokenEnd++;
}
size_t tokenLength = tokenEnd - tokenStart;
if (tokenLength < tokenBufferLength) {
strncpy(tokenBuffer, &response[tokenStart], tokenLength);
tokenBuffer[tokenLength] = '\0';
}
if (log) fprintf(log, "Got a presubmit token from server: %s\n", tokenBuffer);
}
if (moduleCount > 0) {
auto mainModule = processState.modules()->GetMainModule();
auto executableBaseDir = PathnameStripper_Directory(mainModule->code_file());
InitModuleClassificationMap(executableBaseDir);
// 0 = Disabled
// 1 = System Only
// 2 = System + Game
// 3 = System + Game + Addons
const char *symbolSubmitOptionStr = g_pSM->GetCoreConfigValue("MinidumpSymbolUpload");
int symbolSubmitOption = symbolSubmitOptionStr ? atoi(symbolSubmitOptionStr) : 3;
const char *binarySubmitOption = g_pSM->GetCoreConfigValue("MinidumpBinaryUpload");
bool canBinarySubmit = !binarySubmitOption || (tolower(binarySubmitOption[0]) == 'y' || binarySubmitOption[0] == '1');
for (unsigned int moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
bool submitSymbols = false;
bool submitBinary = (response[2 + moduleIndex] == 'U');
#if defined _LINUX
submitSymbols = (response[2 + moduleIndex] == 'Y');
#endif
if (!submitSymbols && !submitBinary) {
continue;
}
auto module = processState.modules()->GetModuleAtIndex(moduleIndex);
auto moduleType = ClassifyModule(module);
if (log) fprintf(log, "Classified module %s as %s\n", module->code_file().c_str(), ModuleTypeCode[moduleType]);
switch (moduleType) {
case kMTUnknown:
continue;
case kMTSystem:
if (symbolSubmitOption < 1) {
continue;
}
break;
case kMTGame:
if (symbolSubmitOption < 2) {
continue;
}
break;
case kMTAddon:
case kMTExtension:
if (symbolSubmitOption < 3) {
continue;
}
break;
}
if (canBinarySubmit && submitBinary) {
UploadModuleFile(module, tokenBuffer);
}
#if defined _LINUX
if (submitSymbols) {
UploadSymbolFile(module, tokenBuffer);
}
#endif
}
}
delete[] response;
return presubmitResponse;
}
bool UploadCrashDump(const char *path, const char *metapath, const char *presubmitToken, char *response, int maxlen) {
IWebForm *form = webternet->CreateForm();
const char *minidumpAccount = g_pSM->GetCoreConfigValue("MinidumpAccount");
if (minidumpAccount && minidumpAccount[0]) form->AddString("UserID", minidumpAccount);
form->AddString("GameDirectory", crashGameDirectory);
form->AddString("ExtensionVersion", SMEXT_CONF_VERSION);
form->AddString("ServerID", serverId);
if (presubmitToken && presubmitToken[0]) {
form->AddString("PresubmitToken", presubmitToken);
}
if (path && path[0]) {
form->AddFile("upload_file_minidump", path);
}
if (metapath && metapath[0]) {
form->AddFile("upload_file_metadata", metapath);
}
MemoryDownloader data;
IWebTransfer *xfer = webternet->CreateSession();
xfer->SetFailOnHTTPError(true);
const char *minidumpUrl = g_pSM->GetCoreConfigValue("MinidumpUrl");
if (!minidumpUrl) minidumpUrl = "http://crash.limetech.org/submit";
bool uploaded = xfer->PostAndDownload(minidumpUrl, form, &data, NULL);
if (response) {
if (uploaded) {
int responseSize = data.GetSize();
if (responseSize >= maxlen) responseSize = maxlen - 1;
strncpy(response, data.GetBuffer(), responseSize);
response[responseSize] = '\0';
while (responseSize > 0 && response[responseSize - 1] == '\n') {
response[--responseSize] = '\0';
}
} else {
g_pSM->Format(response, maxlen, "%s (%d)", xfer->LastErrorMessage(), xfer->LastErrorCode());
}
}
return uploaded;
}
} uploadThread;
class VFuncEmptyClass {};
const char *GetCmdLine()
{
static int getCmdLineOffset = 0;
if (getCmdLineOffset == 0) {
if (!gameconfig || !gameconfig->GetOffset("GetCmdLine", &getCmdLineOffset)) {
return "";
}
if (getCmdLineOffset == 0) {
return "";
}
}
void *cmdline = gamehelpers->GetValveCommandLine();
void **vtable = *(void ***)cmdline;
void *vfunc = vtable[getCmdLineOffset];
union {
const char *(VFuncEmptyClass::*mfpnew)();
#ifndef WIN32
struct {
void *addr;
intptr_t adjustor;
} s;
} u;
u.s.addr = vfunc;
u.s.adjustor = 0;
#else
void *addr;
} u;
u.addr = vfunc;
#endif
return (const char *)(reinterpret_cast(cmdline)->*u.mfpnew)();
}
bool Accelerator::SDK_OnLoad(char *error, size_t maxlength, bool late)
{
sharesys->AddDependency(myself, "webternet.ext", true, true);
SM_GET_IFACE(WEBTERNET, webternet);
g_pSM->BuildPath(Path_SM, dumpStoragePath, sizeof(dumpStoragePath), "data/dumps");
if (!libsys->IsPathDirectory(dumpStoragePath))
{
if (!libsys->CreateFolder(dumpStoragePath))
{
if (error)
g_pSM->Format(error, maxlength, "%s didn't exist and we couldn't create it :(", dumpStoragePath);
return false;
}
}
g_pSM->BuildPath(Path_SM, logPath, sizeof(logPath), "logs/accelerator.log");
// Get these early so the upload thread can use them.
strncpy(crashGamePath, g_pSM->GetGamePath(), sizeof(crashGamePath) - 1);
strncpy(crashSourceModPath, g_pSM->GetSourceModPath(), sizeof(crashSourceModPath) - 1);
strncpy(crashGameDirectory, g_pSM->GetGameFolderName(), sizeof(crashGameDirectory) - 1);
threader->MakeThread(&uploadThread);
do {
char gameconfigError[256];
if (!gameconfs->LoadGameConfigFile("accelerator.games", &gameconfig, gameconfigError, sizeof(gameconfigError))) {
smutils->LogMessage(myself, "WARNING: Failed to load gamedata file, console output and command line will not be included in crash reports: %s", gameconfigError);
break;
}
bool useFastcall = false;
#if defined _WINDOWS
const char *fastcall = gameconfig->GetKeyValue("UseFastcall");
if (fastcall && strcmp(fastcall, "yes") == 0) {
useFastcall = true;
}
if (useFastcall && !gameconfig->GetMemSig("GetSpewFastcall", (void **)&GetSpewFastcall)) {
smutils->LogMessage(myself, "WARNING: GetSpewFastcall not found in gamedata, console output will not be included in crash reports.");
break;
}
#endif
if (!useFastcall && !gameconfig->GetMemSig("GetSpew", (void **)&GetSpew)) {
smutils->LogMessage(myself, "WARNING: GetSpew not found in gamedata, console output will not be included in crash reports.");
break;
}
if (!GetSpew
#if defined _WINDOWS
&& !GetSpewFastcall
#endif
) {
smutils->LogMessage(myself, "WARNING: Sigscan for GetSpew failed, console output will not be included in crash reports.");
break;
}
} while(false);
#if defined _LINUX
google_breakpad::MinidumpDescriptor descriptor(dumpStoragePath);
handler = new google_breakpad::ExceptionHandler(descriptor, NULL, dumpCallback, NULL, true, -1);
struct sigaction oact;
sigaction(SIGSEGV, NULL, &oact);
SignalHandler = oact.sa_sigaction;
g_pSM->AddGameFrameHook(OnGameFrame);
#elif defined _WINDOWS
wchar_t *buf = new wchar_t[sizeof(dumpStoragePath)];
size_t num_chars = mbstowcs(buf, dumpStoragePath, sizeof(dumpStoragePath));
handler = new google_breakpad::ExceptionHandler(
std::wstring(buf, num_chars), NULL, dumpCallback, NULL, google_breakpad::ExceptionHandler::HANDLER_ALL,
static_cast(MiniDumpWithUnloadedModules | MiniDumpWithFullMemoryInfo), static_cast(NULL), NULL);
vectoredHandler = AddVectoredExceptionHandler(0, BreakpadVectoredHandler);
delete buf;
#else
#error Bad platform.
#endif
do {
char spJitPath[512];
g_pSM->BuildPath(Path_SM, spJitPath, sizeof(spJitPath), "bin/" PLATFORM_ARCH_FOLDER "sourcepawn.jit.x86." PLATFORM_LIB_EXT);
char spJitError[255];
std::unique_ptr spJit(libsys->OpenLibrary(spJitPath, spJitError, sizeof(spJitError)));
if (!spJit) {
smutils->LogMessage(myself, "WARNING: Failed to load SourcePawn library %s: %s", spJitPath, spJitError);
break;
}
GetSourcePawnFactoryFn factoryFn = (GetSourcePawnFactoryFn)spJit->GetSymbolAddress("GetSourcePawnFactory");
if (!factoryFn) {
smutils->LogMessage(myself, "WARNING: SourcePawn library is out of date: No factory function.");
break;
}
ISourcePawnFactory *spFactory = factoryFn(0x0207);
if (!spFactory) {
smutils->LogMessage(myself, "WARNING: SourcePawn library is out of date: Failed to get version 2.7", 0x0207);
break;
}
ISourcePawnEnvironment *spEnvironment = spFactory->CurrentEnvironment();
if (!spEnvironment) {
smutils->LogMessage(myself, "WARNING: Could not get SourcePawn environment.");
break;
}
ISourcePawnEngine2 *spEngine2 = spEnvironment->APIv2();
if (!spEngine2) {
smutils->LogMessage(myself, "WARNING: Could not get SourcePawn engine2.");
break;
}
strncpy(crashSourceModVersion, spEngine2->GetVersionString(), sizeof(crashSourceModVersion));
} while(false);
plsys->AddPluginsListener(this);
IPluginIterator *iterator = plsys->GetPluginIterator();
while (iterator->MorePlugins()) {
IPlugin *plugin = iterator->GetPlugin();
if (plugin->GetStatus() == Plugin_Running) {
this->OnPluginLoaded(plugin);
}
iterator->NextPlugin();
}
delete iterator;
strncpy(crashCommandLine, GetCmdLine(), sizeof(crashCommandLine) - 1);
char steamInfPath[512];
g_pSM->BuildPath(Path_Game, steamInfPath, sizeof(steamInfPath), "steam.inf");
FILE *steamInfFile = fopen(steamInfPath, "rb");
if (steamInfFile) {
char steamInfTemp[1024] = {0};
fread(steamInfTemp, sizeof(char), sizeof(steamInfTemp) - 1, steamInfFile);
fclose(steamInfFile);
unsigned commentChars = 0;
unsigned valueChars = 0;
unsigned source = 0;
strcpy(steamInf, "\nSteam_");
unsigned target = 7; // strlen("\nSteam_");
while (true) {
if (steamInfTemp[source] == '\0') {
source++;
break;
}
if (steamInfTemp[source] == '/') {
source++;
commentChars++;
continue;
}
if (commentChars == 1) {
commentChars = 0;
steamInf[target++] = '/';
valueChars++;
}
if (steamInfTemp[source] == '\r') {
source++;
continue;
}
if (steamInfTemp[source] == '\n') {
commentChars = 0;
source++;
if (steamInfTemp[source] == '\0') {
break;
}
if (valueChars > 0) {
valueChars = 0;
strcpy(&steamInf[target], "\nSteam_");
target += 7;
}
continue;
}
if (commentChars >= 2) {
source++;
continue;
}
steamInf[target++] = steamInfTemp[source++];
valueChars++;
}
}
if (late) {
this->OnCoreMapStart(NULL, 0, 0);
}
return true;
}
void Accelerator::SDK_OnUnload()
{
plsys->RemovePluginsListener(this);
#if defined _LINUX
g_pSM->RemoveGameFrameHook(OnGameFrame);
#elif defined _WINDOWS
if (vectoredHandler) {
RemoveVectoredExceptionHandler(vectoredHandler);
}
#else
#error Bad platform.
#endif
delete handler;
}
void Accelerator::OnCoreMapStart(edict_t *pEdictList, int edictCount, int clientMax)
{
strncpy(crashMap, gamehelpers->GetCurrentMap(), sizeof(crashMap) - 1);
}
/* 010 Editor Template
uint64 headerMagic;
uint32 version;
uint32 size;
uint32 count;
struct {
uint32 size;
uint32 context ;
char file[];
uint32 count;
struct {
uint32 pcode ;
char name[];
} functions[count] ;
} plugins[count] ;
uint64 tailMagic;
*/
unsigned char *serializedPluginContexts = nullptr;
std::map pluginContextMap;
void SerializePluginContexts()
{
if (serializedPluginContexts) {
handler->UnregisterAppMemory(serializedPluginContexts);
free(serializedPluginContexts);
serializedPluginContexts = nullptr;
}
uint32_t count = pluginContextMap.size();
if (count == 0) {
return;
}
uint32_t size = 0;
size += sizeof(uint64_t); // header magic
size += sizeof(uint32_t); // version
size += sizeof(uint32_t); // size
size += sizeof(uint32_t); // count
for (auto &it : pluginContextMap) {
unsigned char *buffer = it.second;
uint32_t bufferSize;
memcpy(&bufferSize, buffer, sizeof(uint32_t));
size += bufferSize;
}
size += sizeof(uint64_t); // tail magic
serializedPluginContexts = (unsigned char *)malloc(size);
handler->RegisterAppMemory(serializedPluginContexts, size);
unsigned char *cursor = serializedPluginContexts;
uint64_t headerMagic = 103582791429521979ULL;
memcpy(cursor, &headerMagic, sizeof(uint64_t));
cursor += sizeof(uint64_t);
uint32_t version = 1;
memcpy(cursor, &version, sizeof(uint32_t));
cursor += sizeof(uint32_t);
memcpy(cursor, &size, sizeof(uint32_t));
cursor += sizeof(uint32_t);
memcpy(cursor, &count, sizeof(uint32_t));
cursor += sizeof(uint32_t);
for (auto &it : pluginContextMap) {
unsigned char *buffer = it.second;
uint32_t bufferSize;
memcpy(&bufferSize, buffer, sizeof(uint32_t));
memcpy(cursor, buffer, bufferSize);
cursor += bufferSize;
}
uint64_t tailMagic = 76561197987819599ULL;
memcpy(cursor, &tailMagic, sizeof(uint64_t));
cursor += sizeof(uint64_t);
}
void Accelerator::OnPluginLoaded(IPlugin *plugin)
{
IPluginRuntime *runtime = plugin->GetRuntime();
IPluginContext *context = plugin->GetBaseContext();
if (!runtime || !context) {
return;
}
const char *filename = plugin->GetFilename();
size_t filenameSize = strlen(filename) + 1;
uint32_t size = 0;
size += sizeof(uint32_t); // size
size += sizeof(void *); // GetBaseContext
size += filenameSize;
uint32_t count = runtime->GetPublicsNum();
size += sizeof(uint32_t); // count
size += count * sizeof(uint32_t); // pubinfo->code_offs
for (uint32_t i = 0; i < count; ++i) {
sp_public_t *pubinfo;
runtime->GetPublicByIndex(i, &pubinfo);
size += strlen(pubinfo->name) + 1;
}
unsigned char *buffer = (unsigned char *)malloc(size);
unsigned char *cursor = buffer;
memcpy(cursor, &size, sizeof(uint32_t));
cursor += sizeof(uint32_t);
memcpy(cursor, &context, sizeof(void *));
cursor += sizeof(void *);
memcpy(cursor, filename, filenameSize);
cursor += filenameSize;
memcpy(cursor, &count, sizeof(uint32_t));
cursor += sizeof(uint32_t);
for (uint32_t i = 0; i < count; ++i) {
sp_public_t *pubinfo;
runtime->GetPublicByIndex(i, &pubinfo);
memcpy(cursor, &pubinfo->code_offs, sizeof(uint32_t));
cursor += sizeof(uint32_t);
size_t nameSize = strlen(pubinfo->name) + 1;
memcpy(cursor, pubinfo->name, nameSize);
cursor += nameSize;
}
pluginContextMap[context] = buffer;
SerializePluginContexts();
}
void Accelerator::OnPluginUnloaded(IPlugin *plugin)
{
IPluginContext *context = plugin->GetBaseContext();
if (!context) {
return;
}
auto it = pluginContextMap.find(context);
if (it != pluginContextMap.end()) {
free(it->second);
pluginContextMap.erase(it);
}
SerializePluginContexts();
}