/* * ============================================================================= * 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(); }