[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.
This commit is contained in:
Bill Currie 2023-12-01 02:57:25 +09:00
parent 52210b8c55
commit 1a83fe21c1
7 changed files with 184 additions and 18 deletions

View file

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

View file

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

View file

@ -73,6 +73,7 @@
#include <sys/time.h>
#endif
#include <sys/types.h>
#include <unwind.h>
#ifdef HAVE_DIRECT_H
#include <direct.h>
@ -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);
}

View file

@ -28,8 +28,6 @@
#ifndef __server_h
#define __server_h
#include <setjmp.h>
#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;

View file

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

View file

@ -56,7 +56,6 @@
#include <ctype.h>
#include <errno.h>
#include <setjmp.h>
#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;

View file

@ -58,7 +58,6 @@
#include <stdarg.h>
#include <stdlib.h>
#include <setjmp.h>
#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;