From 1a83fe21c1b82eb0898b9aa6559fb46b4b6f90a2 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Fri, 1 Dec 2023 02:57:25 +0900 Subject: [PATCH] [util] Add Sys_setjmp and Sys_longjmp Host_Error and Host_EndGame use setjmp/longjmp to implement an exception of sorts, but this messes with tracy's state even with cleanup attributes. However, it turns out that those cleanup attributes are exactly how gcc implements C++ destructors, and so the standard Unwind api (part of libgcc) respects them (so long as -fexceptions is enabled for C). Thus... replace longjmp with an implementation that uses Unwind to unwind the stack and call the cleanup functions as needed. This is actually important for more than just tracy as the cleanup attributed vars can be thread locks. --- Makefile.am | 2 +- include/QF/sys.h | 4 ++ libs/util/sys.c | 169 ++++++++++++++++++++++++++++++++++++++++++++ nq/include/server.h | 4 -- nq/source/host.c | 10 +-- qw/source/cl_main.c | 9 ++- qw/source/sv_main.c | 4 +- 7 files changed, 184 insertions(+), 18 deletions(-) diff --git a/Makefile.am b/Makefile.am index 4a8067843..6f486b2e1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,7 +25,7 @@ NOCONV_DIST= \ $(distdir)/include/win32/resources/icon1XP.ico BUILT_SOURCES = $(top_srcdir)/.version -AM_CFLAGS= $(TRACY_CFLAGS) -funwind-tables -include qftracy.h +AM_CFLAGS= $(TRACY_CFLAGS) -fexceptions -include qftracy.h AM_CXXFLAGS= $(TRACY_CFLAGS) -include qftracy.h AM_CPPFLAGS= -I$(top_srcdir)/include $(UNWIND_CFLAGS) $(PTHREAD_CFLAGS) $(FNM_FLAGS) $(NCURSES_CFLAGS) $(FREETYPE_CFLAGS) $(HARFBUZZ_CFLAGS) $(VULKAN_CPPFLAGS) $(LIBCURL_CFLAGS) diff --git a/include/QF/sys.h b/include/QF/sys.h index c2a6cd30b..5a71b7ae7 100644 --- a/include/QF/sys.h +++ b/include/QF/sys.h @@ -202,6 +202,10 @@ char *Sys_ExpandSquiggle (const char *path); int Sys_UniqueFile (struct dstring_s *name, const char *prefix, const char *suffix, int mindigits); +typedef intptr_t sys_jmpbuf[5]; +#define Sys_setjmp(jmpbuf) __builtin_setjmp(jmpbuf) +void Sys_longjmp (sys_jmpbuf jmpbuf) __attribute__((noreturn)); + ///@} #endif//__QF_sys_h diff --git a/libs/util/sys.c b/libs/util/sys.c index 9ca06db65..7643c75c3 100644 --- a/libs/util/sys.c +++ b/libs/util/sys.c @@ -73,6 +73,7 @@ #include #endif #include +#include #ifdef HAVE_DIRECT_H #include @@ -85,6 +86,7 @@ #include "QF/cvar.h" #include "QF/dstring.h" #include "QF/mathlib.h" +#include "QF/msg.h" #include "QF/sys.h" #include "QF/quakefs.h" #include "QF/va.h" @@ -1261,3 +1263,170 @@ Sys_Shutdown (void) sys_debuglog_data = 0; } } + +#ifndef _WIN32 +#define DW_EH_PE_absptr 0x00 +#define DW_EH_PE_omit 0xff + +#define DW_EH_PE_uleb128 0x01 +#define DW_EH_PE_udata2 0x02 +#define DW_EH_PE_udata4 0x03 +#define DW_EH_PE_udata8 0x04 +#define DW_EH_PE_sleb128 0x09 +#define DW_EH_PE_sdata2 0x0A +#define DW_EH_PE_sdata4 0x0B +#define DW_EH_PE_sdata8 0x0C +#define DW_EH_PE_signed 0x08 + +#define DW_EH_PE_pcrel 0x10 +#define DW_EH_PE_textrel 0x20 +#define DW_EH_PE_datarel 0x30 +#define DW_EH_PE_funcrel 0x40 +#define DW_EH_PE_aligned 0x50 + + +typedef struct { + uintptr_t start; + uintptr_t lpstart; + uintptr_t ttype_base; + const byte *ttype; + const byte *action_table; + byte ttype_encoding; + byte cs_encoding; +} lsd_header_t; + +static uintptr_t +read_value (byte enc, qmsg_t *msg) +{ + if (enc == DW_EH_PE_aligned) { + Sys_Error ("unexpected DW_EH_PE_aligned\n"); + } + uintptr_t res = 0; + switch (enc & 0x0f) { + case DW_EH_PE_uleb128: + res = MSG_ReadUleb128 (msg); + break; + case DW_EH_PE_sleb128: + res = MSG_ReadSleb128 (msg); + break; + default: + Sys_Error ("unexpected dwarf encoding: %d\n", enc); + break; + } + if (res && enc & 0xf0) { + Sys_Error ("unexpected dwarf encoding: %d\n", enc); + } + return res; +} + +static lsd_header_t +read_lsd_header (qmsg_t *msg, uintptr_t start) +{ + lsd_header_t hdr; + uintptr_t tmp; + byte enc = MSG_ReadByte (msg); + hdr.start = start; + if (enc == DW_EH_PE_omit) { + hdr.lpstart = start; + } else { + hdr.lpstart = read_value (enc, msg); + } + hdr.ttype_encoding = MSG_ReadByte (msg); + if (hdr.ttype_encoding == DW_EH_PE_omit) { + hdr.ttype = 0; + } else { + tmp = MSG_ReadUleb128 (msg); + hdr.ttype = msg->message->data + msg->readcount + tmp; + } + hdr.cs_encoding = MSG_ReadByte (msg);; + tmp = MSG_ReadUleb128 (msg); + hdr.action_table = msg->message->data + msg->readcount + tmp; + return hdr; +} + +#endif + +static _Unwind_Reason_Code +sys_stop (int version, _Unwind_Action actions, + _Unwind_Exception_Class excls, + struct _Unwind_Exception *exobj, + struct _Unwind_Context *context, void *_jmpbuf) +{ + if (version != 1) { + Sys_Error ("unknown unwind version"); + } + intptr_t *jmpbuf = _jmpbuf; +#ifdef _WIN32 + if (!context) { + // if we get here, then attempting to delete the exception contextuuu + // can fail + __builtin_longjmp (jmpbuf, 1); + } + uintptr_t target_cfa = jmpbuf[2]; + uintptr_t cfa = _Unwind_GetCFA (context); + if (target_cfa < cfa) { + // we've gone past the target frame (there were no intervening + // cleanup points) so just jump out to avoid cleaning up something + // we shouldn't touch + __builtin_longjmp (jmpbuf, 1); + } + // These are not meant to be read, let alone written, but this is + // the only way to communicate the target frame/ip to RtlUnwindEx + exobj->private_[1] = target_cfa; + exobj->private_[2] = jmpbuf[1]; // target ip + return _URC_NO_REASON; +#else + if ((actions & _UA_END_OF_STACK)) { + Sys_Error ("Sys_longjmp called outside a Sys_setjmp context"); + } + uintptr_t target_cfa = jmpbuf[2]; + uintptr_t cfa = _Unwind_GetCFA (context); + int no_dec_ip; + uintptr_t ip = _Unwind_GetIPInfo (context, &no_dec_ip); + if (!ip) { + Sys_Error ("null ip\n"); + } + if (cfa != target_cfa) { + return _URC_NO_REASON; + } + ip -= !no_dec_ip; + byte *lsd = _Unwind_GetLanguageSpecificData (context); + if (lsd) { + sizebuf_t message = { .data = lsd, .cursize = -1 }; + qmsg_t msg = { .message = &message }; + auto region_start = _Unwind_GetRegionStart (context); + lsd_header_t hdr = read_lsd_header (&msg, region_start); + message.cursize = hdr.action_table - message.data; + bool match = false; + bool cleanup = false; + while (!match && msg.readcount < message.cursize) { + auto cs_start = read_value (hdr.cs_encoding, &msg); + auto cs_len = read_value (hdr.cs_encoding, &msg); + auto cs_lp = read_value (hdr.cs_encoding, &msg); + /*auto cs_action =*/ read_value (hdr.cs_encoding, &msg); + cs_start += hdr.start; + match = ip >= cs_start && ip < cs_start + cs_len; + cleanup = cs_lp; + } + if (!match) { + Sys_Error ("could not find ip in call site list"); + } + if (cleanup) { + // the call site has cleanup associated with it, so return to + // let the unwind system deal with the cleanup (we'll get called + // again with the same cfa) + return _URC_NO_REASON; + } + } + _Unwind_DeleteException (exobj); + __builtin_longjmp (jmpbuf, 1); +#endif +} + +void +Sys_longjmp (sys_jmpbuf jmpbuf) +{ + static struct _Unwind_Exception sys_exception; + auto res = _Unwind_ForcedUnwind (&sys_exception, sys_stop, jmpbuf); + Sys_Error ("_Unwind_ForcedUnwind returned %d", res); +} diff --git a/nq/include/server.h b/nq/include/server.h index f2dca7e4b..1d1a105aa 100644 --- a/nq/include/server.h +++ b/nq/include/server.h @@ -28,8 +28,6 @@ #ifndef __server_h #define __server_h -#include - #include "QF/info.h" #include "QF/model.h" #include "QF/quakeio.h" @@ -232,8 +230,6 @@ extern server_t sv; // local server extern client_t *host_client; -extern jmp_buf host_abortserver; - extern double host_time; extern double sv_frametime; diff --git a/nq/source/host.c b/nq/source/host.c index f6308408c..ada316379 100644 --- a/nq/source/host.c +++ b/nq/source/host.c @@ -86,7 +86,7 @@ size_t minimum_memory; client_t *host_client; // current client -jmp_buf host_abortserver; +static sys_jmpbuf host_abortserver; float host_mem_size; static cvar_t host_mem_size_cvar = { @@ -286,7 +286,7 @@ Host_EndGame (const char *message, ...) else CL_Disconnect (); - longjmp (host_abortserver, 1); + Sys_longjmp (host_abortserver); } /* @@ -328,7 +328,7 @@ Host_Error (const char *error, ...) inerror = false; - longjmp (host_abortserver, 1); + Sys_longjmp (host_abortserver); } static void @@ -678,14 +678,14 @@ Host_FilterTime (float time) static void _Host_Frame (float time) { - qfZoneScoped (true); static int first = 1; float sleeptime; - if (setjmp (host_abortserver)) + if (Sys_setjmp (host_abortserver)) return; // something bad happened, or the // server disconnected + qfZoneScoped (true); rand (); // keep the random time dependent if (cls.demo_capture) diff --git a/qw/source/cl_main.c b/qw/source/cl_main.c index 16b6f45cc..9b55e582b 100644 --- a/qw/source/cl_main.c +++ b/qw/source/cl_main.c @@ -56,7 +56,6 @@ #include #include -#include #include "qfalloca.h" @@ -451,7 +450,7 @@ static cvar_t host_speeds_cvar = { int fps_count; -jmp_buf host_abort; +static sys_jmpbuf host_abort; char *server_version = NULL; // version of server we connected to @@ -1684,7 +1683,7 @@ Host_EndGame (const char *message, ...) CL_Disconnect (); - longjmp (host_abort, 1); + Sys_longjmp (host_abort); } /* @@ -1716,7 +1715,7 @@ Host_Error (const char *error, ...) inerror = false; if (host_initialized) { - longjmp (host_abort, 1); + Sys_longjmp (host_abort); } else { Sys_Error ("Host_Error: %s", str->str); } @@ -1866,7 +1865,7 @@ Host_Frame (float time) float sleeptime; int pass1, pass2, pass3; - if (setjmp (host_abort)) + if (Sys_setjmp (host_abort)) // something bad happened, or the server disconnected return; diff --git a/qw/source/sv_main.c b/qw/source/sv_main.c index 5753c094a..5cfbee767 100644 --- a/qw/source/sv_main.c +++ b/qw/source/sv_main.c @@ -58,7 +58,6 @@ #include #include -#include #include "QF/cbuf.h" #include "QF/idparse.h" @@ -2078,8 +2077,7 @@ SV_OutOfBandPrint (netadr_t adr, const char *format, ...) static void SV_ReadPackets (void) { - //NOTE star volatile, not volatile star - client_t *volatile cl; // * volatile for longjmp + client_t *cl; int i; int qport; double until;