engine: platform: posix: add libbacktrace support for crash handler

This commit is contained in:
Alibek Omarov
2025-02-09 15:42:28 +03:00
parent 960d724828
commit 774d2ac28a
5 changed files with 377 additions and 3 deletions

183
3rdparty/libbacktrace/wscript vendored Normal file
View File

@@ -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 <unistd.h>
int main(void) { return getpagesize() }'''
FRAGMENT_GETEXECNAME='''#include <stdlib.h>
int main(void) { return getexecname() != 0 }'''
FRAGMENT_STRNLEN='''#include <string.h>
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 <fcntl.h>
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 <sys/ldr.h>
#include <sys/debug.h>
int main(void) { return loadquery(0, 0, 0); }'''
FRAGMENT_KERN_PROC='''#include <sys/sysctl.h>
#if !defined(%s) || !defined(KERN_PROC_PATHNAME)
#error
#endif
int main(void) { return 0; }'''
FRAGMENT_LSTAT='''#include <sys/stat.h>
struct stat st;
int main(int argc, char **argv) { return lstat(argv[0], &st); }'''
FRAGMENT_READLINK='''#include <unistd.h>
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/'
)

View File

@@ -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 <signal.h>
#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

View File

@@ -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 )

View File

@@ -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

View File

@@ -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'),