From 3dec4376ce1b06989ff9ac0cced50d26c7012888 Mon Sep 17 00:00:00 2001 From: bones_was_here Date: Mon, 1 Jan 2024 22:55:45 +1000 Subject: [PATCH] sys: implement signal handling on all platforms Prints a stack trace when crashing if using glibc. Signed-off-by: bones_was_here --- cl_main.c | 2 ++ host.c | 4 +-- makefile.inc | 6 ++-- sys_shared.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++-- vid_null.c | 25 ----------------- 5 files changed, 85 insertions(+), 31 deletions(-) diff --git a/cl_main.c b/cl_main.c index 6b557283..7dffe2c8 100644 --- a/cl_main.c +++ b/cl_main.c @@ -439,6 +439,8 @@ void CL_DisconnectEx(qbool kicked, const char *fmt, ... ) MSG_WriteByte(&buf, clc_disconnect); if(cls.protocol == PROTOCOL_DARKPLACES8) MSG_WriteString(&buf, reason); + // DP8 TODO: write a simpler func that Sys_HandleCrash() calls + // to send a disconnect message indicating we crashed } NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, 0, false); diff --git a/host.c b/host.c index 7c89c589..fdfb6836 100644 --- a/host.c +++ b/host.c @@ -448,10 +448,10 @@ static void Host_Init (void) Sys_Init_Commands(); COM_Init_Commands(); - // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name) + // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name, gamename) FS_Init(); - // construct a version string for the corner of the console + // ASAP! construct a version string for the corner of the console and for crash messages dpsnprintf (engineversion, sizeof (engineversion), "%s %s%s, buildstring: %s", gamename, DP_OS_NAME, cls.state == ca_dedicated ? " dedicated" : "", buildstring); Con_Printf("%s\n", engineversion); diff --git a/makefile.inc b/makefile.inc index fc397415..40b70e21 100644 --- a/makefile.inc +++ b/makefile.inc @@ -216,8 +216,10 @@ CMD_UNIXMKDIR=mkdir -p ##### Linux specific variables ##### # Link -LDFLAGS_LINUXSV=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -LDFLAGS_LINUXSDL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl $(LDFLAGS_UNIXSDL) +# -rdynamic allows glibc backtrace_symbols_fd() to convert addresses to function names +# with a much smaller binary than with full debug symbols +LDFLAGS_LINUXSV=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -rdynamic +LDFLAGS_LINUXSDL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -rdynamic $(LDFLAGS_UNIXSDL) ##### Mac OS X specific variables ##### diff --git a/sys_shared.c b/sys_shared.c index 457abe36..e92eb94f 100644 --- a/sys_shared.c +++ b/sys_shared.c @@ -890,10 +890,83 @@ void Sys_MakeProcessMean (void) } #endif -int main (int argc, char **argv) +/** Halt and try not to catch fire. + * Writing to any file could corrupt it, + * any uneccessary code could crash while we crash. + * No malloc() (libgcc should be loaded already) or Con_Printf() allowed here. + */ +static void Sys_HandleCrash(int sig) +{ +#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1 + // Before doing anything else grab the stack frame addresses + #include + void *stackframes[32]; + int framecount = backtrace(stackframes, 32); +#endif + + // Windows doesn't have strsignal() + const char *sigdesc; + switch (sig) + { +#ifndef WIN32 // or SIGBUS + case SIGBUS: sigdesc = "Bus error"; break; +#endif + case SIGILL: sigdesc = "Illegal instruction"; break; + case SIGABRT: sigdesc = "Aborted"; break; + case SIGFPE: sigdesc = "Floating point exception"; break; + case SIGSEGV: sigdesc = "Segmentation fault"; break; + default: sigdesc = "Yo dawg, we hit a bug while hitting a bug"; + } + + fprintf(stderr, "\n\n\e[1;37;41m Engine Crash: %s (%d) \e[m\n", sigdesc, sig); +#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1 + // the first two addresses will be in this function and in signal() in libc + backtrace_symbols_fd(stackframes + 2, framecount - 2, fileno(stderr)); +#endif + fprintf(stderr, "\e[1m%s\e[m\n", engineversion); + + // DP8 TODO: send a disconnect message indicating we crashed, see CL_DisconnectEx() + + // don't want a dead window left blocking the OS UI or the crash dialog + VID_Shutdown(); + S_StopAllSounds(); + + Sys_SDL_Dialog("Engine Crash", sigdesc); + + exit (sig); +} + +static void Sys_HandleSignal(int sig) +{ +#ifdef WIN32 + // Windows users will likely never see this so no point replicating strsignal() + Con_Printf("\nReceived signal %d, exiting...\n", sig); +#else + Con_Printf("\nReceived %s signal (%d), exiting...\n", strsignal(sig), sig); +#endif + host.state = host_shutdown; +} + +/// SDL2 only handles SIGINT and SIGTERM by default and doesn't log anything +static void Sys_InitSignals(void) { - signal(SIGFPE, SIG_IGN); +// Windows docs say its signal() only accepts these ones + signal(SIGABRT, Sys_HandleCrash); + signal(SIGFPE, Sys_HandleCrash); + signal(SIGILL, Sys_HandleCrash); + signal(SIGINT, Sys_HandleSignal); + signal(SIGSEGV, Sys_HandleCrash); + signal(SIGTERM, Sys_HandleSignal); +#ifndef WIN32 + signal(SIGHUP, Sys_HandleSignal); + signal(SIGQUIT, Sys_HandleSignal); + signal(SIGBUS, Sys_HandleCrash); + signal(SIGPIPE, Sys_HandleSignal); +#endif +} +int main (int argc, char **argv) +{ sys.argc = argc; sys.argv = (const char **)argv; @@ -917,6 +990,8 @@ int main (int argc, char **argv) Sys_AllowProfiling(true); #endif + Sys_InitSignals(); + Host_Main(); Sys_Quit(0); diff --git a/vid_null.c b/vid_null.c index fb3cba08..b8621dc5 100644 --- a/vid_null.c +++ b/vid_null.c @@ -29,37 +29,12 @@ void VID_Shutdown(void) { } -#ifndef WIN32 -static void signal_handler(int sig) -{ - Con_Printf("Received signal %d, exiting...\n", sig); - Sys_Quit(1); -} - -static void InitSig(void) -{ - signal(SIGHUP, signal_handler); - signal(SIGINT, signal_handler); - signal(SIGQUIT, signal_handler); - signal(SIGILL, signal_handler); - signal(SIGTRAP, signal_handler); - signal(SIGIOT, signal_handler); - signal(SIGBUS, signal_handler); - signal(SIGFPE, signal_handler); - signal(SIGSEGV, signal_handler); - signal(SIGTERM, signal_handler); -} -#endif - void VID_Finish (void) { } void VID_Init(void) { -#ifndef WIN32 - InitSig(); // trap evil signals -#endif } qbool VID_InitMode(viddef_mode_t *mode) -- 2.39.2