From 774d2ac28ac592f18e4af84183fdabe98fd58f47 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 9 Feb 2025 15:42:28 +0300 Subject: [PATCH] engine: platform: posix: add libbacktrace support for crash handler --- 3rdparty/libbacktrace/wscript | 183 +++++++++++++++++++++ engine/platform/posix/crash_libbacktrace.c | 178 ++++++++++++++++++++ engine/platform/posix/crash_posix.c | 16 +- engine/wscript | 2 +- wscript | 1 + 5 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 3rdparty/libbacktrace/wscript create mode 100644 engine/platform/posix/crash_libbacktrace.c diff --git a/3rdparty/libbacktrace/wscript b/3rdparty/libbacktrace/wscript new file mode 100644 index 00000000..6fdb064d --- /dev/null +++ b/3rdparty/libbacktrace/wscript @@ -0,0 +1,183 @@ +#! /usr/bin/env python +# encoding: utf-8 + +from waflib import TaskGen + +FRAGMENT_ATOMIC='''int i; +int main(void) { + __atomic_load_n(&i, __ATOMIC_ACQUIRE); + __atomic_store_n(&i, 1, __ATOMIC_RELEASE); + return 0; +}''' + +FRAGMENT_SYNC='''int i; +int main (void) { + __sync_bool_compare_and_swap (&i, i, i); + __sync_lock_test_and_set (&i, 1); + __sync_lock_release (&i); + return 0; +}''' + +FRAGMENT_GETPAGESIZE='''#include +int main(void) { return getpagesize() }''' + +FRAGMENT_GETEXECNAME='''#include +int main(void) { return getexecname() != 0 }''' + +FRAGMENT_STRNLEN='''#include +int main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }''' + +FRAGMENT_DL_ITERATE_PHDR='''#include <%s> +int main(void) { return dl_iterate_phdr(0, 0); }''' + +FRAGMENT_FCNTL='''#include +int main(void) { return fcntl(0, 0, 0); }''' + +FRAGMENT_GETIPINFO='''#include "unwind.h" +struct _Unwind_Context *context; +int ip_before_insn = 0; +int main(void) { return _Unwind_GetIPInfo(context, &ip_before_insn); }''' + +FRAGMENT_LOADQUERY='''#include +#include +int main(void) { return loadquery(0, 0, 0); }''' + +FRAGMENT_KERN_PROC='''#include +#if !defined(%s) || !defined(KERN_PROC_PATHNAME) +#error +#endif +int main(void) { return 0; }''' + +FRAGMENT_LSTAT='''#include +struct stat st; +int main(int argc, char **argv) { return lstat(argv[0], &st); }''' + +FRAGMENT_READLINK='''#include +char buf[100]; +int main(int argc, char **argv) { return readlink(argv[0], buf, sizeof(buf)); }''' + +def options(opt): + pass + +def configure(conf): + # add unsupported platforms here + if conf.env.DEST_OS in ['nswitch', 'psvita', 'dos']: + return + + # win32 has it's own dbghelp-based backtrace, that's why we ship PDBs + if conf.env.COMPILER_CC == 'msvc': + return + + if not conf.path.find_dir('libbacktrace') or not conf.path.find_dir('libbacktrace/config'): + conf.fatal('Can\'t find libbacktrace submodule. Run `git submodule update --init --recursive`.') + return + + if conf.env.DEST_SIZEOF_VOID_P == 8: + conf.define('BACKTRACE_ELF_SIZE', 64) + conf.define('BACKTRACE_XCOFF_SIZE', 64) + else: + conf.define('BACKTRACE_ELF_SIZE', 32) + conf.define('BACKTRACE_XCOFF_SIZE', 32) + + conf.define('_ALL_SOURCE', 1) + conf.define('_GNU_SOURCE', 1) + conf.define('_POSIX_PTHREAD_SEMANTICS', 1) + conf.define('_TANDEM_SOURCE', 1) + conf.define('__EXTENSIONS__', 1) + conf.define('_DARWIN_USE_64_BIT_INODE', 1) + conf.define('_LARGE_FILES', 1) + conf.define('_POSIX_SOURCE', 1) + conf.define('_POSIX_1_SOURCE', 1) + conf.check_large_file(compiler='c', execute=False, mandatory=False) # sets _FILE_OFFSET_BITS + + conf.env.CFLAGS_EXTRAFLAGS = conf.filter_cflags(['-funwind-tables', '-g'], []) + + if conf.filter_cflags(['-frandom-seed=test'], []): + conf.env.HAVE_FRANDOM_SEED = True + + def check_header(hdr): + return {'header_name':hdr, 'msg':'... %s header' % hdr, 'mandatory':False, 'id':hdr} + + conf.multicheck( + check_header('dlfcn.h'), + check_header('inttypes.h'), + check_header('link.h'), + check_header('sys/link.h'), + check_header('mach-o/dyld.h'), + check_header('memory.h'), + check_header('stdint.h'), + check_header('stdlib.h'), + check_header('strings.h'), + check_header('string.h'), + check_header('sys/ldr.h'), + check_header('sys/mman.h'), + check_header('sys/stat.h'), + check_header('sys/types.h'), + check_header('tlhelp32.h'), + check_header('unistd.h'), + check_header('windows.h'), + + {'fragment':FRAGMENT_ATOMIC, 'msg':'... __atomic extensions', 'define_name':'HAVE_ATOMIC_FUNCTIONS', 'mandatory':False}, + {'fragment':FRAGMENT_SYNC, 'msg':'... __sync extensions', 'define_name':'HAVE_SYNC_FUNCTIONS', 'mandatory':False}, + {'fragment':FRAGMENT_GETPAGESIZE, 'msg':'... getpagesize function', 'define_name':'HAVE_DECL_GETPAGESIZE', 'mandatory':False}, + {'fragment':FRAGMENT_STRNLEN, 'msg':'... strnlen function', 'define_name':'HAVE_DECL_STRNLEN', 'mandatory':False}, + {'fragment':FRAGMENT_DL_ITERATE_PHDR % 'link.h', 'msg':'... dl_iterate_phdr function in link.h', 'define_name':'HAVE_DL_ITERATE_PHDR', 'after_tests': ['link.h'], 'mandatory':False}, + {'fragment':FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', 'msg':'... dl_iterate_phdr function in sys/link.h', 'define_name':'HAVE_DL_ITERATE_PHDR', 'after_tests': ['sys/link.h'], 'mandatory':False}, + {'fragment':FRAGMENT_FCNTL,'msg':'... fnctl function', 'define_name':'HAVE_FCNTL', 'mandatory':False}, + {'fragment':FRAGMENT_GETEXECNAME,'msg':'... getexecname function','define_name':'HAVE_GETEXECNAME', 'mandatory':False}, + {'fragment':FRAGMENT_GETIPINFO, 'msg':'... _Unwind_GetIPInfo function', 'define_name':'HAVE_GETIPINFO', 'mandatory':False}, + {'fragment':FRAGMENT_KERN_PROC % 'KERN_PROC', 'msg': '... KERN_PROC and KERN_PROC_PATHNAME defines', 'define_name':'HAVE_KERN_PROC', 'mandatory':False}, + {'fragment':FRAGMENT_KERN_PROC % 'KERN_PROC_ARGS', 'msg': '... KERN_PROC_ARGS and KERN_PROC_PATHNAME defines', 'define_name':'HAVE_KERN_PROC_ARGS', 'mandatory':False}, + {'fragment':FRAGMENT_LOADQUERY, 'msg': '... loadquery function', 'define_name':'HAVE_LOADQUERY', 'mandatory':False}, + {'fragment':FRAGMENT_LSTAT, 'msg': '... lstat function', 'define_name':'HAVE_LSTAT', 'mandatory':False}, + {'fragment':FRAGMENT_READLINK, 'msg': '... readlink function', 'define_name':'HAVE_READLINK', 'mandatory':False}, + + {'lib':'lzma', 'define_name':'HAVE_LIBLZMA', 'uselib_store':'lzma', 'msg':'... lzma library', 'mandatory':False}, + {'lib':'z', 'define_name':'HAVE_ZLIB', 'uselib_store':'z', 'msg':'... zlib library', 'mandatory':False}, + {'lib':'zstd', 'define_name':'HAVE_ZSTD', 'uselib_store':'zstd', 'msg':'... zstd library', 'mandatory':False}, + msg='Checking for in parallel' + ) + + conf.write_config_header() + + conf.define('BACKTRACE_SUPPORTED', 1) + conf.define('BACKTRACE_USES_MALLOC', 0) + conf.define('BACKTRACE_SUPPORTS_THREADS', 1) + conf.define('BACKTRACE_SUPPORTS_DATA', conf.env.DEST_BINFMT in ['elf', 'mac-o']) + + conf.write_config_header('backtrace-supported.h') + +@TaskGen.feature('frandomseed') +@TaskGen.after_method('process_source') +@TaskGen.before_method('apply_link') +def process_frandom_seed(ctx): + tasks = getattr(ctx, 'compiled_tasks', []) + + for task in tasks: + out = task.outputs[0] + task.env.CFLAGS = list(task.env.CFLAGS) # need a copy + task.env.CFLAGS += ['-frandom-seed=%s' % out.path_from(out.ctx.bldnode)] + +def build(bld): + # we specifically only want mmap-based allocators because calling malloc is not safe from signal handlers + sources = ['atomic.c', 'dwarf.c', 'fileline.c', 'posix.c', 'print.c', 'sort.c', 'state.c', 'backtrace.c', 'simple.c', 'mmap.c', 'mmapio.c'] + + if bld.env.DEST_BINFMT == 'pe': + sources += ['pecoff.c'] + elif bld.env.DEST_BINFMT == 'mac-o': + sources += ['macho.c'] + elif bld.env.DEST_BINFMT == 'elf': + sources += ['elf.c'] + else: + sources += ['unknown.c'] + + task = bld.stlib( + source = ['libbacktrace/' + i for i in sources], + target = 'libbacktrace', + features = 'frandomseed' if bld.env.HAVE_FRANDOM_SEED else '', + use = 'EXTRAFLAGS lzma z zstd', + includes = '. libbacktrace/', + export_defines = 'HAVE_LIBBACKTRACE=1', + export_includes = 'libbacktrace/' + ) + diff --git a/engine/platform/posix/crash_libbacktrace.c b/engine/platform/posix/crash_libbacktrace.c new file mode 100644 index 00000000..46303b9b --- /dev/null +++ b/engine/platform/posix/crash_libbacktrace.c @@ -0,0 +1,178 @@ +/* +crashhandler.c - advanced crashhandler +Copyright (C) 2016 Mittorn + +This program 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 3 of the License, or +(at your option) any later version. + +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. +*/ + +#if HAVE_LIBBACKTRACE +#include +#include "common.h" +#include "backtrace.h" + + +static struct backtrace_state *g_bt_state; +static qboolean enable_libbacktrace; + +static void Sys_BacktraceError( void *data, const char *msg, int errnum ) +{ + if( errnum < 0 ) + { + Con_Printf( S_ERROR "no symbol info, no libbacktrace\n" ); + return; + } + + Con_Printf( S_ERROR "libbacktrace error: %s (%d)\n", msg, errnum ); + + enable_libbacktrace = false; +} + +struct print_data +{ + char *message; + size_t message_size; + int len; + int idx; +}; + +static void Sys_AppendPrint( struct print_data *pd, const char *fmt, ... ) +{ + va_list va; + int len; + + va_start( va, fmt ); + len = Q_vsnprintf( pd->message, pd->message_size, fmt, va ); + va_end( va ); + + if( len > 0 ) + { + pd->message += len; + pd->len += len; + pd->message_size -= len; + } +} + +static void Sys_BacktracePrintError( void *data, const char *msg, int errnum ) +{ + struct print_data *pd = data; + Sys_AppendPrint( pd, "%2d: error: %s (%d)\n", pd->idx++, msg, errnum ); +} + +static void Sys_BacktracePrintSyminfo( void *data, uintptr_t pc, const char *symname, uintptr_t symval, uintptr_t symsize ) +{ + struct print_data *pd = data; + Dl_info dlinfo = { 0 }; + const char *module_name; + + if( dladdr((void *)pc, &dlinfo )) + module_name = dlinfo.dli_fname; + else module_name = NULL; + + if( symname ) + { + if( module_name ) + Sys_AppendPrint( pd, "%2d: <%s+%d> (%s)\n", pd->idx++, symname, pc - symval, module_name ); + else + Sys_AppendPrint( pd, "%2d: <%s+%d>\n", pd->idx++, symname, pc - symval ); + } + else + { + if( module_name ) + Sys_AppendPrint( pd, "%2d: %p (%s)\n", pd->idx++, pc, module_name ); + else + Sys_AppendPrint( pd, "%2d: %p\n", pd->idx++, pc ); + } +} + +static int Sys_BacktracePrintFull( void *data, uintptr_t pc, const char *filename, int lineno, const char *function ) +{ + struct print_data *pd = data; + Dl_info dlinfo = { 0 }; + const char *module_name; + + if( dladdr((void *)pc, &dlinfo )) + module_name = dlinfo.dli_fname; + else module_name = NULL; + + if( filename && lineno && function ) + { + if( module_name ) + Sys_AppendPrint( pd, "%2d: %s (%s:%d) (%s)\n", pd->idx++, function, filename, lineno, module_name ); + else + Sys_AppendPrint( pd, "%2d: %s (%s:%d)\n", pd->idx++, function, filename, lineno ); + } + else + { + backtrace_syminfo( g_bt_state, pc, Sys_BacktracePrintSyminfo, Sys_BacktraceError, data ); + } + + return 0; +} + +void Sys_CrashLibbacktrace( int signal, siginfo_t *si, void *context ) +{ + char message[8192]; + int len, logfd; + struct print_data pd = { .idx = 0 }; + + (void)context; + + // safe actions first, stack and memory may be corrupted + len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n", + Q_buildnum(), g_buildcommit, Q_buildos(), Q_buildarch() ); + +#if !XASH_FREEBSD && !XASH_NETBSD && !XASH_OPENBSD && !XASH_APPLE // they don't have si_ptr + len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p %p\n", signal, si->si_errno, si->si_code, si->si_addr, si->si_ptr ); +#else + len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p\n", signal, si->si_errno, si->si_code, si->si_addr ); +#endif + + write( STDERR_FILENO, message, len ); + + // flush buffers before writing directly to descriptors + fflush( stdout ); + fflush( stderr ); + + // now get log fd and write trace directly to log + logfd = Sys_LogFileNo(); + write( logfd, message, len ); + + pd.message = message + len; + pd.message_size = sizeof( message ) - len; + pd.len = 0; + + if( g_bt_state ) + backtrace_full( g_bt_state, 0, Sys_BacktracePrintFull, Sys_BacktracePrintError, &pd ); + + // put MessageBox as Sys_Error + Msg( "%s\n", message ); +#ifdef XASH_SDL + SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); +#endif + host.crashed = true; + Platform_MessageBox( "Xash Error", message, false ); + + // log saved, now we can try to save configs and close log correctly, it may crash + if( host.type == HOST_NORMAL ) + CL_Crashed(); + host.status = HOST_CRASHED; + + Sys_Quit( "crashed" ); +} + +qboolean Sys_SetupLibbacktrace( void ) +{ + enable_libbacktrace = true; + g_bt_state = backtrace_create_state( argv0, true, Sys_BacktraceError, NULL ); + return g_bt_state != NULL && enable_libbacktrace; +} + +#endif // HAVE_EXECINFO diff --git a/engine/platform/posix/crash_posix.c b/engine/platform/posix/crash_posix.c index 0292009f..63a408a6 100644 --- a/engine/platform/posix/crash_posix.c +++ b/engine/platform/posix/crash_posix.c @@ -28,9 +28,11 @@ GNU General Public License for more details. #include "library.h" void Sys_Crash( int signal, siginfo_t *si, void *context ); +void Sys_CrashLibbacktrace( int signal, siginfo_t *si, void *context ); +qboolean Sys_SetupLibbacktrace( void ); static struct sigaction oldFilter; -#if !HAVE_EXECINFO +#if !HAVE_EXECINFO && !HAVE_LIBBACKTRACE #define STACK_BACKTRACE_STR "Stack backtrace:\n" #define STACK_DUMP_STR "Stack dump:\n" @@ -209,12 +211,22 @@ void Sys_Crash( int signal, siginfo_t *si, void *context ) void Sys_SetupCrashHandler( const char *argv0 ) { struct sigaction act = { 0 }; - act.sa_sigaction = Sys_Crash; +#if HAVE_LIBBACKTRACE + if( Sys_SetupLibbacktrace()) + { + act.sa_sigaction = Sys_CrashLibbacktrace; + } + else +#endif + { + act.sa_sigaction = Sys_Crash; + } act.sa_flags = SA_SIGINFO | SA_ONSTACK; sigaction( SIGSEGV, &act, &oldFilter ); sigaction( SIGABRT, &act, &oldFilter ); sigaction( SIGBUS, &act, &oldFilter ); sigaction( SIGILL, &act, &oldFilter ); + } void Sys_RestoreCrashHandler( void ) diff --git a/engine/wscript b/engine/wscript index 432dc9b8..e09db606 100644 --- a/engine/wscript +++ b/engine/wscript @@ -136,7 +136,7 @@ def build(bld): # public includes for renderers and utils use bld(name = 'engine_includes', export_includes = '. common common/imagelib', use = 'filesystem_includes') - libs = ['engine_includes', 'public', 'dllemu', 'werror'] + libs = ['engine_includes', 'public', 'dllemu', 'werror', 'libbacktrace'] includes = ['server', 'client', 'client/vgui', 'common/soundlib'] # basic build: dedicated only diff --git a/wscript b/wscript index 1753aa24..14e8d61a 100644 --- a/wscript +++ b/wscript @@ -84,6 +84,7 @@ SUBDIRS = [ Subproject('filesystem'), Subproject('stub/server'), Subproject('dllemu'), + Subproject('3rdparty/libbacktrace'), # disable only by engine feature, makes no sense to even parse subprojects in dedicated mode Subproject('3rdparty/extras', lambda x: x.env.CLIENT and x.env.DEST_OS != 'android'),