2019-09-16 19:28:39 +00:00
|
|
|
/*
|
|
|
|
===========================================================================
|
2020-06-01 18:28:44 +00:00
|
|
|
Copyright (C) 2017-2020 Gian 'myT' Schellenbaum
|
2019-09-16 19:28:39 +00:00
|
|
|
|
|
|
|
This file is part of Challenge Quake 3 (CNQ3).
|
|
|
|
|
|
|
|
Challenge Quake 3 is free software; you can redistribute it
|
|
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
|
|
or (at your option) any later version.
|
|
|
|
|
|
|
|
Challenge Quake 3 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 Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
// save and print useful data for JSON crash report files
|
|
|
|
|
2017-03-14 04:11:12 +00:00
|
|
|
#include "crash.h"
|
|
|
|
#include "git.h"
|
|
|
|
#include "vm_local.h"
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
2017-10-10 17:36:12 +00:00
|
|
|
char gitHeadHash[24]; // SHA-1 -> 160 bits -> 20 hex chars
|
|
|
|
vm_t* vm;
|
|
|
|
unsigned int crc32;
|
|
|
|
} vmCrash_t;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
vmCrash_t vm[VM_COUNT];
|
|
|
|
char modName[64];
|
|
|
|
char modVersion[64];
|
|
|
|
} crash_t;
|
|
|
|
|
|
|
|
|
|
|
|
static crash_t crash;
|
2017-03-14 04:11:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
static qbool IsVMIndexValid(vmIndex_t vmIndex)
|
|
|
|
{
|
|
|
|
return vmIndex >= 0 && vmIndex < VM_COUNT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char* GetVMName(vmIndex_t vmIndex)
|
|
|
|
{
|
|
|
|
switch (vmIndex) {
|
|
|
|
case VM_CGAME: return "cgame";
|
|
|
|
case VM_GAME: return "game";
|
|
|
|
case VM_UI: return "ui";
|
|
|
|
default: return "unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Crash_SaveQVMPointer(vmIndex_t vmIndex, vm_t* vm)
|
|
|
|
{
|
|
|
|
if (IsVMIndexValid(vmIndex))
|
2017-10-10 17:36:12 +00:00
|
|
|
crash.vm[vmIndex].vm = vm;
|
2017-03-14 04:11:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Crash_SaveQVMChecksum(vmIndex_t vmIndex, unsigned int crc32)
|
|
|
|
{
|
|
|
|
if (IsVMIndexValid(vmIndex))
|
2017-10-10 17:36:12 +00:00
|
|
|
crash.vm[vmIndex].crc32 = crc32;
|
2017-03-14 04:11:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Crash_SaveQVMGitString(const char* varName, const char* varValue)
|
|
|
|
{
|
|
|
|
if (strstr(varName, "gitHeadHash") == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
vmIndex_t index;
|
|
|
|
if (strstr(varName, "cg_") == varName)
|
|
|
|
index = VM_CGAME;
|
|
|
|
else if (strstr(varName, "g_") == varName)
|
|
|
|
index = VM_GAME;
|
|
|
|
else if (strstr(varName, "ui_") == varName)
|
|
|
|
index = VM_UI;
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
|
2017-10-10 17:36:12 +00:00
|
|
|
Q_strncpyz(crash.vm[index].gitHeadHash, varValue, sizeof(crash.vm[index].gitHeadHash));
|
2017-03-14 04:11:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Crash_SaveModName(const char* modName)
|
|
|
|
{
|
|
|
|
if (modName && *modName != '\0')
|
2017-10-10 17:36:12 +00:00
|
|
|
Q_strncpyz(crash.modName, modName, sizeof(crash.modName));
|
2017-03-14 04:11:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Crash_SaveModVersion(const char* modVersion)
|
|
|
|
{
|
|
|
|
if (modVersion && *modVersion != '\0')
|
2017-10-10 17:36:12 +00:00
|
|
|
Q_strncpyz(crash.modVersion, modVersion, sizeof(crash.modVersion));
|
2017-03-14 04:11:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void PrintQVMInfo(vmIndex_t vmIndex)
|
|
|
|
{
|
|
|
|
static char callStack[MAX_VM_CALL_STACK_DEPTH * 12];
|
|
|
|
|
2017-10-10 17:36:12 +00:00
|
|
|
vmCrash_t* vm = &crash.vm[vmIndex];
|
2017-03-14 04:11:12 +00:00
|
|
|
if (vm->crc32 == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONW_BeginObject();
|
|
|
|
|
|
|
|
JSONW_IntegerValue("index", vmIndex);
|
|
|
|
JSONW_StringValue("name", GetVMName(vmIndex));
|
|
|
|
JSONW_BooleanValue("loaded", vm->vm != NULL);
|
|
|
|
if (vm->crc32)
|
|
|
|
JSONW_HexValue("crc32", vm->crc32);
|
|
|
|
JSONW_StringValue("git_head_hash", vm->gitHeadHash);
|
|
|
|
|
|
|
|
if (vm->vm != NULL) {
|
|
|
|
vm_t* const vmp = vm->vm;
|
|
|
|
JSONW_IntegerValue("call_stack_depth_current", vmp->callStackDepth);
|
|
|
|
JSONW_IntegerValue("call_stack_depth_previous", vmp->lastCallStackDepth);
|
|
|
|
|
|
|
|
int d = vmp->callStackDepth;
|
|
|
|
qbool current = qtrue;
|
|
|
|
if (d <= 0 || d > MAX_VM_CALL_STACK_DEPTH) {
|
|
|
|
d = vmp->lastCallStackDepth;
|
|
|
|
current = qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d <= 0 || d > MAX_VM_CALL_STACK_DEPTH)
|
|
|
|
d = 0;
|
|
|
|
|
|
|
|
if (d > 0) {
|
|
|
|
JSONW_BooleanValue("call_stack_current", current);
|
|
|
|
JSONW_BooleanValue("call_stack_limit_reached", d == MAX_VM_CALL_STACK_DEPTH);
|
|
|
|
|
|
|
|
callStack[0] = '\0';
|
|
|
|
for (int i = 0; i < d; i++) {
|
|
|
|
Q_strcat(callStack, sizeof(callStack), Q_itohex(vmp->callStack[i], qtrue, qtrue));
|
|
|
|
if (i - 1 < d)
|
|
|
|
Q_strcat(callStack, sizeof(callStack), " ");
|
|
|
|
}
|
|
|
|
JSONW_StringValue("call_stack", callStack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JSONW_EndObject();
|
|
|
|
}
|
|
|
|
|
|
|
|
static qbool IsAnyVMLoaded()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < VM_COUNT; i++) {
|
2017-10-10 17:36:12 +00:00
|
|
|
if (crash.vm[i].crc32 != 0)
|
2017-03-14 04:11:12 +00:00
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int CRC32_HashFile(const char* filePath)
|
|
|
|
{
|
|
|
|
enum { BUFFER_SIZE = 16 << 10 }; // 16 KB
|
|
|
|
static byte buffer[BUFFER_SIZE];
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
if (stat(filePath, &st) != 0 || st.st_size == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
FILE* const file = fopen(filePath, "rb");
|
|
|
|
if (file == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
const unsigned int fileSize = (unsigned int)st.st_size;
|
|
|
|
const unsigned int fullBlocks = fileSize / (unsigned int)BUFFER_SIZE;
|
|
|
|
const unsigned int lastBlockSize = fileSize - fullBlocks * (unsigned int)BUFFER_SIZE;
|
|
|
|
|
|
|
|
unsigned int crc32 = 0;
|
2017-09-12 01:21:11 +00:00
|
|
|
CRC32_Begin(&crc32);
|
2017-03-14 04:11:12 +00:00
|
|
|
|
|
|
|
for(unsigned int i = 0; i < fullBlocks; ++i) {
|
|
|
|
if (fread(buffer, BUFFER_SIZE, 1, file) != 1) {
|
|
|
|
fclose(file);
|
|
|
|
return 0;
|
|
|
|
}
|
2017-09-12 01:21:11 +00:00
|
|
|
CRC32_ProcessBlock(&crc32, buffer, BUFFER_SIZE);
|
2017-03-14 04:11:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(lastBlockSize > 0) {
|
|
|
|
if (fread(buffer, lastBlockSize, 1, file) != 1) {
|
|
|
|
fclose(file);
|
|
|
|
return 0;
|
|
|
|
}
|
2017-09-12 01:21:11 +00:00
|
|
|
CRC32_ProcessBlock(&crc32, buffer, lastBlockSize);
|
2017-03-14 04:11:12 +00:00
|
|
|
}
|
|
|
|
|
2017-09-12 01:21:11 +00:00
|
|
|
CRC32_End(&crc32);
|
2017-03-14 04:11:12 +00:00
|
|
|
|
|
|
|
fclose(file);
|
|
|
|
|
|
|
|
return crc32;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Crash_PrintToFile(const char* engineFilePath)
|
|
|
|
{
|
|
|
|
JSONW_StringValue("engine_version", Q3_VERSION);
|
|
|
|
JSONW_StringValue("engine_build_date", __DATE__);
|
|
|
|
JSONW_StringValue("engine_arch", ARCH_STRING);
|
|
|
|
#ifdef DEDICATED
|
|
|
|
JSONW_BooleanValue("engine_ded_server", qtrue);
|
|
|
|
#else
|
|
|
|
JSONW_BooleanValue("engine_ded_server", qfalse);
|
|
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
|
|
JSONW_BooleanValue("engine_debug", qtrue);
|
|
|
|
#else
|
|
|
|
JSONW_BooleanValue("engine_debug", qfalse);
|
|
|
|
#endif
|
|
|
|
#ifdef CNQ3_DEV
|
|
|
|
JSONW_BooleanValue("engine_dev_build", qtrue);
|
|
|
|
#else
|
|
|
|
JSONW_BooleanValue("engine_dev_build", qfalse);
|
|
|
|
#endif
|
|
|
|
JSONW_StringValue("engine_git_branch", GIT_BRANCH);
|
|
|
|
JSONW_StringValue("engine_git_commit", GIT_COMMIT);
|
|
|
|
const unsigned int crc32 = CRC32_HashFile(engineFilePath);
|
|
|
|
if (crc32)
|
|
|
|
JSONW_HexValue("engine_crc32", crc32);
|
2017-10-10 17:36:12 +00:00
|
|
|
JSONW_StringValue("mod_name", crash.modName);
|
|
|
|
JSONW_StringValue("mod_version", crash.modVersion);
|
2017-03-14 04:11:12 +00:00
|
|
|
|
|
|
|
if (IsAnyVMLoaded()) {
|
|
|
|
JSONW_BeginNamedArray("vms");
|
|
|
|
for (int i = 0; i < VM_COUNT; i++) {
|
|
|
|
PrintQVMInfo((vmIndex_t)i);
|
|
|
|
}
|
|
|
|
JSONW_EndArray();
|
|
|
|
}
|
|
|
|
}
|
2017-10-10 17:36:12 +00:00
|
|
|
|
|
|
|
|
2020-01-06 00:04:16 +00:00
|
|
|
#if defined(__linux__) || defined(__FreeBSD__)
|
2017-10-10 17:36:12 +00:00
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
static void Print(int fd, const char* msg)
|
|
|
|
{
|
|
|
|
write(fd, msg, strlen(msg));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void PrintVMStackTrace(int fd, vmIndex_t vmIndex)
|
|
|
|
{
|
|
|
|
Print(fd, GetVMName(vmIndex));
|
|
|
|
Print(fd, ": ");
|
|
|
|
|
|
|
|
vmCrash_t* const vmc = &crash.vm[vmIndex];
|
|
|
|
vm_t* const vm = vmc->vm;
|
|
|
|
if (vm == NULL) {
|
|
|
|
Print(fd, "not loaded\r\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Print(fd, "crc32 ");
|
|
|
|
Print(fd, Q_itohex(vmc->crc32, qtrue, qtrue));
|
|
|
|
Print(fd, " - ");
|
|
|
|
|
|
|
|
int d = vm->callStackDepth;
|
|
|
|
qbool current = qtrue;
|
|
|
|
if (d <= 0 || d > MAX_VM_CALL_STACK_DEPTH) {
|
|
|
|
d = vm->lastCallStackDepth;
|
|
|
|
current = qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d <= 0 || d > MAX_VM_CALL_STACK_DEPTH)
|
|
|
|
d = 0;
|
|
|
|
|
|
|
|
Print(fd, current ? "current " : "old ");
|
|
|
|
if (d > 0) {
|
|
|
|
for (int i = 0; i < d; i++) {
|
|
|
|
Print(fd, Q_itohex(vm->callStack[i], qtrue, qtrue));
|
|
|
|
if (i - 1 < d)
|
|
|
|
Print(fd, " ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Print(fd, "\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Crash_PrintVMStackTracesASS(int fd)
|
|
|
|
{
|
|
|
|
if (crash.modName[0] != '\0') {
|
|
|
|
Print(fd, "mod: ");
|
|
|
|
Print(fd, crash.modName);
|
|
|
|
Print(fd, " ");
|
|
|
|
Print(fd, crash.modVersion);
|
|
|
|
Print(fd, "\r\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < VM_COUNT; i++) {
|
|
|
|
PrintVMStackTrace(fd, (vmIndex_t)i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|