Skip to content

Commit

Permalink
log: add stacktrace support
Browse files Browse the repository at this point in the history
Resolves #1165
  • Loading branch information
rr- committed Feb 24, 2024
1 parent f5fc211 commit 1f900a6
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
- added the option to change weapon targets by tapping the look key like in TR4+ (#1145)
- added three targeting lock options: full lock always keeps target lock (OG), semi lock loses target lock if the enemy dies, and no lock loses target lock if the enemey goes out of sight or dies (TR4+) (#1146)
- added an option to the installer to install from a CD drive (#1144)
- added stack traces to logs for better crash debugging (#1165)
- changed the way music timestamps are internally handled – resets music position in existing saves

## [3.1.1](https://github.com/LostArtefacts/TR1X/compare/3.1...3.1.1) - 2024-01-19
Expand Down
14 changes: 14 additions & 0 deletions docker/game-linux/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ RUN apt-get update \



# libbacktrace
FROM base AS backtrace
RUN apt-get install -y git gcc autoconf libtool
RUN git clone https://github.com/LostArtefacts/libbacktrace/
RUN cd libbacktrace && \
autoreconf -fi && \
./configure \
--prefix=/ext/ \
&& make -j 4 \
&& make install



# libav
FROM base AS libav
RUN apt-get install -y \
Expand Down Expand Up @@ -134,5 +147,6 @@ RUN apt-get install -y \
COPY --from=upx /ext/upx /usr/local/bin/upx
COPY --from=libav /ext/ /ext/
COPY --from=sdl /ext/ /ext/
COPY --from=backtrace /ext/ /ext/

ENTRYPOINT ["/app/docker/game-linux/entrypoint-linux.sh"]
3 changes: 3 additions & 0 deletions docker/game-win/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ RUN apt-get update \
make



# zlib
FROM mingw as zlib
RUN git clone https://github.com/madler/zlib
Expand Down Expand Up @@ -85,6 +86,7 @@ RUN cd FFmpeg \
&& make install



# SDL
FROM mingw as sdl
RUN git clone https://github.com/libsdl-org/SDL -b SDL2
Expand Down Expand Up @@ -121,6 +123,7 @@ RUN cd SDL \
# && make install



# TR1X
FROM mingw

Expand Down
12 changes: 12 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ null_dep = dependency('', required: false)
dep_avcodec = dependency('libavcodec', static: staticdeps)
dep_avformat = dependency('libavformat', static: staticdeps)
dep_avutil = dependency('libavutil', static: staticdeps)
dep_backtrace = dependency('libbacktrace', static: staticdeps, required: false)
dep_swscale = dependency('libswscale', static: staticdeps)
dep_swresample = dependency('libswresample', static: staticdeps)

Expand Down Expand Up @@ -304,6 +305,17 @@ dependencies = [
dep_mathlibrary,
]

if dep_backtrace.found() and host_machine.system() == 'linux'
sources += ['src/specific/s_log_linux.c']
dependencies += [dep_backtrace]
elif dep_backtrace.found() and host_machine.system() == 'windows'
sources += ['src/specific/s_log_windows.c']
dep_win_dbghelp = c_compiler.find_library('dbghelp')
dependencies += [dep_win_dbghelp]
else
sources += ['src/specific/s_log_unknown.c']
endif

executable(
'TR1X',
sources,
Expand Down
3 changes: 3 additions & 0 deletions src/log.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "filesystem.h"
#include "memory.h"
#include "specific/s_log.h"

#include <stdarg.h>
#include <stdio.h>
Expand All @@ -13,6 +14,8 @@ void Log_Init(void)
char *full_path = File_GetFullPath("TR1X.log");
m_LogHandle = fopen(full_path, "w");
Memory_FreePointer(&full_path);

S_Log_Init();
}

void Log_Message(
Expand Down
3 changes: 3 additions & 0 deletions src/specific/s_log.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

void S_Log_Init(void);
51 changes: 51 additions & 0 deletions src/specific/s_log_linux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "specific/s_log.h"

#include "log.h"

#include <backtrace-supported.h>
#include <backtrace.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

static void S_Log_ErrorCallback(void *data, const char *msg, int errnum);
static int S_Log_FullTrace(
void *data, uintptr_t pc, const char *filename, int lineno,
const char *function);
static void S_Log_SignalHandler(int sig);

static void S_Log_ErrorCallback(void *data, const char *msg, int errnum)
{
LOG_ERROR("%s", msg);
}

static int S_Log_FullTrace(
void *data, uintptr_t pc, const char *filename, int lineno,
const char *function)
{
if (filename) {
LOG_INFO(
"0x%08X: %s (%s:%d)", pc, function ? function : "???",
filename ? filename : "???", lineno);
} else {
LOG_INFO("0x%08X: %s", pc, function ? function : "???");
}
return 0;
}

static void S_Log_SignalHandler(int sig)
{
LOG_ERROR("== CRASH REPORT ==");
LOG_INFO("SIGNAL: %d", sig);
LOG_INFO("STACK TRACE:");
struct backtrace_state *state = backtrace_create_state(
NULL, BACKTRACE_SUPPORTS_THREADS, S_Log_ErrorCallback, NULL);
backtrace_full(state, 0, S_Log_FullTrace, S_Log_ErrorCallback, NULL);
exit(EXIT_FAILURE);
}

void S_Log_Init(void)
{
signal(SIGSEGV, S_Log_SignalHandler);
signal(SIGFPE, S_Log_SignalHandler);
}
5 changes: 5 additions & 0 deletions src/specific/s_log_unknown.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "specific/s_log.h"

void S_Log_Init(void)
{
}
126 changes: 126 additions & 0 deletions src/specific/s_log_windows.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#include "specific/s_log.h"

#include "filesystem.h"
#include "log.h"

#include <process.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <dbghelp.h>
#include <tlhelp32.h>

static void S_Log_CreateMiniDump(EXCEPTION_POINTERS *ex);
static void S_Log_LogStackTraces(EXCEPTION_POINTERS *ex);

static void S_Log_CreateMiniDump(EXCEPTION_POINTERS *ex)
{
char *full_path = File_GetFullPath("TR1X.dmp");
HANDLE handle = CreateFile(
full_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
NULL);
MINIDUMP_EXCEPTION_INFORMATION dump_info;
dump_info.ExceptionPointers = ex;
dump_info.ThreadId = GetCurrentThreadId();
dump_info.ClientPointers = TRUE;
MiniDumpWriteDump(
GetCurrentProcess(), GetCurrentProcessId(), handle, MiniDumpNormal,
&dump_info, NULL, NULL);
CloseHandle(handle);

LOG_INFO("Crash dump info put in %s", full_path);
}

static void S_Log_LogStackTraces(EXCEPTION_POINTERS *ex)
{
LOG_ERROR("== CRASH REPORT ==");

HANDLE thread = GetCurrentThread();
HANDLE process = GetCurrentProcess();

CONTEXT context = {};
context.ContextFlags = CONTEXT_FULL;

if (thread != GetCurrentThread()) {
SuspendThread(thread);
if (GetThreadContext(thread, &context) == FALSE) {
printf("Failed to get context\n");
return;
}
ResumeThread(thread);
}
RtlCaptureContext(&context);

SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);

if (!SymInitialize(process, 0, TRUE)) {
LOG_ERROR("Failed to call SymInitialize");
return;
}

DWORD image;
STACKFRAME64 stackframe;
ZeroMemory(&stackframe, sizeof(STACKFRAME64));

#ifdef _M_IX86
image = IMAGE_FILE_MACHINE_I386;
stackframe.AddrPC.Offset = context.Eip;
stackframe.AddrPC.Mode = AddrModeFlat;
stackframe.AddrFrame.Offset = context.Ebp;
stackframe.AddrFrame.Mode = AddrModeFlat;
stackframe.AddrStack.Offset = context.Esp;
stackframe.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
image = IMAGE_FILE_MACHINE_AMD64;
stackframe.AddrPC.Offset = context.Rip;
stackframe.AddrPC.Mode = AddrModeFlat;
stackframe.AddrFrame.Offset = context.Rsp;
stackframe.AddrFrame.Mode = AddrModeFlat;
stackframe.AddrStack.Offset = context.Rsp;
stackframe.AddrStack.Mode = AddrModeFlat;
#endif

while (StackWalk64(
image, process, thread, &stackframe, &context, 0,
SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer;

symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;

DWORD64 displacement64 = 0;
if (SymFromAddr(
process, stackframe.AddrPC.Offset, &displacement64, symbol)) {
IMAGEHLP_LINE64 line;
DWORD displacement32;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
if (SymGetLineFromAddr64(
process, stackframe.AddrPC.Offset, &displacement32,
&line)) {
LOG_INFO(
"0x%08llX: %s (%s:%d)", symbol->Address, symbol->Name,
line.FileName, line.LineNumber);
} else {
LOG_INFO("0x%08llX: %s", symbol->Address, symbol->Name);
}
} else {
LOG_INFO("0x%08llX: ???", symbol->Address);
}
}

SymCleanup(process);
}

LONG WINAPI S_Log_CrashHandler(EXCEPTION_POINTERS *ex)
{
S_Log_CreateMiniDump(ex);
S_Log_LogStackTraces(ex);
return EXCEPTION_EXECUTE_HANDLER;
}

void S_Log_Init(void)
{
SetUnhandledExceptionFilter(S_Log_CrashHandler);
}
25 changes: 23 additions & 2 deletions tools/sort_imports
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ def fix_imports(path: Path) -> None:
)


def custom_sort(source: list[str], forced_order: list[str]) -> list[str]:
def key_func(item: str) -> tuple[int, int, str]:
if item in forced_order:
return (forced_order[0], forced_order.index(item))
return (item, 0)

return sorted(source, key=key_func)


def sort_imports(path: Path) -> None:
source = path.read_text()
rel_path = path.relative_to(SRC_PATH)
Expand All @@ -43,8 +52,17 @@ def sort_imports(path: Path) -> None:
"game/savegame/savegame.c": "game/savegame.h",
"specific/s_audio_sample.c": "specific/s_audio.h",
"specific/s_audio_stream.c": "specific/s_audio.h",
"specific/s_log_unknown.c": "specific/s_log.h",
"specific/s_log_linux.c": "specific/s_log.h",
"specific/s_log_windows.c": "specific/s_log.h",
}.get(str(rel_path), own_include)

forced_order = [
"<windows.h>",
"<dbghelp.h>",
"<tlhelp32.h>",
]

def cb(match):
includes = re.findall(r'#include (["<][^"<>]+[">])', match.group(0))
groups = {
Expand All @@ -63,7 +81,10 @@ def sort_imports(path: Path) -> None:
groups = {key: value for key, value in groups.items() if value}

ret = "\n\n".join(
"\n".join(f"#include {include}" for include in sorted(group))
"\n".join(
f"#include {include}"
for include in custom_sort(group, forced_order)
)
for group in groups.values()
).strip()
return ret
Expand All @@ -80,7 +101,7 @@ def sort_imports(path: Path) -> None:

def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(metavar="path", type=Path, nargs="*", dest='paths')
parser.add_argument(metavar="path", type=Path, nargs="*", dest="paths")
return parser.parse_args()


Expand Down

0 comments on commit 1f900a6

Please sign in to comment.