summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README17
-rwxr-xr-xcompile16
-rw-r--r--src/abi.h50
-rw-r--r--src/ac.c6
-rw-r--r--src/build/cmeta.c35
-rw-r--r--src/build/codegen.c5
-rw-r--r--src/build/mkgamedata.c2
-rw-r--r--src/chunklets/fastspin.c1
-rw-r--r--src/con_.c5
-rw-r--r--src/con_.h18
-rw-r--r--src/extmalloc.c50
-rw-r--r--src/extmalloc.h8
-rw-r--r--src/gameinfo.c3
-rw-r--r--src/hook.c45
-rw-r--r--src/hook.h6
-rw-r--r--src/os-unix.h53
-rw-r--r--src/sst.c44
-rw-r--r--src/stubs/tier0.c6
-rw-r--r--test/test.h2
19 files changed, 235 insertions, 137 deletions
diff --git a/README b/README
index 198942a..9669078 100644
--- a/README
+++ b/README
@@ -15,17 +15,16 @@ Windows:
Linux:
• Install Clang (and LLD) via your system package manager. Technically, GCC
- might be able to compile this too, but Clang is heavily preferred in the name
- of consistency and using GCC will require a decent amount of fiddling. It’s
- also not tested and might break and I probably won’t care that much.
- • Install 32-bit C libraries and the C library headers if they’re not already
- installed.
+ should be able to compile most of this too, but we are currently relying on
+ a Clang-specific extension or two, and GCC in general doesn't get tested nor
+ used for binary releases, so it's probably not worth wasting time on.
+ • Install 32-bit glibc and libstdc++ libraries and associated C headers if
+ they’re not already installed.
• Run ./compile (in lieu of a better build tool, to be added later).
-NOTE: Linux code doesn’t quite compile yet and even if it did it probably
-wouldn’t quite work. Essentially, it’s a placeholder to ensure that supporting
-Linux in the future isn’t totally impossible, but there’s still a bunch of code
-that would have to be written first. See also TODO/linux.
+NOTE: Linux code should compile now but still crashes on cvar registration and
+almost none of the features usefully work. In other words, it needs quite a lot
+more development before it's of use to anyone.
════ How and where to install ════
diff --git a/compile b/compile
index a94d8b1..ce45ced 100755
--- a/compile
+++ b/compile
@@ -14,18 +14,18 @@ esac
mkdir -p .build/include
-: "${CC:=clang --target=-i686-pc-linux-gnu -fuse-ld=lld}"
-: "${HOSTCC:=clang -fuse-ld=lld}"
+: "${CC:=clang --target=-i686-pc-linux-gnu}"
+: "${HOSTCC:=clang}"
warnings="-Wall -pedantic -Wno-parentheses -Wno-missing-braces \
-Wno-gnu-zero-variadic-macro-arguments"
dbg=0
if [ "$dbg" = 1 ]; then
- cflags="-Og -g3"
- ldflags="-Og -g3"
+ cflags="-O0 -g3"
+ ldflags="-O0 -g3"
else
- cflags="-O2"
+ cflags="-O2 -fvisibility=hidden"
ldflags="-O2 -s"
fi
@@ -38,14 +38,14 @@ cc() {
if [ "$_mn" = " -DMODULE_NAME=con_" ]; then _mn=" -DMODULE_NAME=con"
elif [ "$_mn" = "-DMODULE_NAME=sst" ]; then _mn=; fi
# note: using typeof and bool from C23 - see detailed comment in compile.bat
- $CC -m32 -c -flto -fpic $cflags $warnings -I.build/include \
+ $CC -m32 -c -flto -fpic -fno-ident $cflags $warnings -I.build/include \
-D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64$_mn \
-Dtypeof=__typeof -include stdbool.h -o ".build/${_bn%%.c}.o" "src/$1"
}
ld() {
- $CC -m32 -shared -flto -fpic -fno-ident -fuse-ld=lld $ldflags \
- -L.build -ldl -ltier0 -lvstdlib -o sst.so$objs
+ $CC -shared -flto -fpic -fuse-ld=lld $ldflags -L.build -ldl \
+ -ltier0 -lvstdlib -o sst.so$objs
}
src="\
diff --git a/src/abi.h b/src/abi.h
index f9b5b7c..98d972e 100644
--- a/src/abi.h
+++ b/src/abi.h
@@ -74,7 +74,7 @@ struct msvc_rtti_locator {
// I mean seriously look at this crap!
#define DEF_MSVC_BASIC_RTTI(mod, name, vtab, typestr) \
-const mod struct msvc_rtti_locator name; \
+mod const struct msvc_rtti_locator name; \
static const struct { \
struct msvc_rtti_descriptor_head d; \
char classname[sizeof("" typestr)]; \
@@ -93,24 +93,48 @@ mod const struct msvc_rtti_locator name = { \
#else
-#warning FIXME! More stuff needs to be implemented/fixed here!
+struct itanium_type_info_vtable {
+ void *dtor1, *dtor2;
+};
struct itanium_type_info {
- struct itanium_type_info_vtable {
- void *dtor1, *dtor2; // ???
- // there's some more functions here in libstdc++: is_pointer,
- // is_function, do_catch, etc. however they're not specified in itanium
- // abi doc. hoping to do without them, but we'll see I guess
- } *vtable;
+ struct itanium_type_info_vtable *vtable;
const char *name;
};
+struct itanium_vmi_type_info {
+ struct itanium_type_info base;
+ uint flags;
+ uint nbases;
+ // then there's a flexible array of `__base_class_type_info`s, but for our
+ // purposes we can just have zero bases and avoid dealing with more nonsense
+};
+
+struct itanium_type_info_vtable_wrapper {
+ // Oh CHRIST, Unix RTTI is bonkers too. Each type_info is itself one of
+ // several subclasses with its own RTTI; the RTTI has RTTI!
+ ssize topoffset;
+ struct itanium_type_info *rtti;
+ struct itanium_type_info_vtable vtable;
+};
+
+// `typeinfo for __cxxabiv1::__vmi_class_type_info` in libcxxabi - type_info
+// comparison is identity-based, so we need to import this from a shared object
+// in order to implement the same ABI. I honestly don't know whether this or the
+// MSVC design is more stupid.
+extern struct itanium_type_info _ZTIN10__cxxabiv121__vmi_class_type_infoE;
+
+// XXX: just static for now, as only used for cvars and lto would dedupe anyway.
+static void _itanium_type_info_dtor(void *this) {}
+
#define DEF_ITANIUM_BASIC_RTTI(mod, name, typestr) \
- mod struct itanium_type_info name = { \
- &(struct itanium_type_info_vtable){ \
- 0, 0 /* FIXME/TEMP: definitely need real functions here! */ \
- }, \
- typestr \
+ mod const struct itanium_vmi_type_info name = { \
+ &(struct itanium_type_info_vtable_wrapper){ \
+ 0, &_ZTIN10__cxxabiv121__vmi_class_type_infoE, \
+ (void *)&_itanium_type_info_dtor, (void *)&_itanium_type_info_dtor \
+ }.vtable, \
+ typestr, \
+ 0, 0 \
};
#endif
diff --git a/src/ac.c b/src/ac.c
index 263e114..228fa58 100644
--- a/src/ac.c
+++ b/src/ac.c
@@ -401,7 +401,7 @@ INIT {
if (madvise(keybox, 4096, MADV_DONTFORK) == -1 ||
madvise(keybox, 4096, MADV_DONTDUMP) == - 1 ||
mlock(keybox, 4096) == -1) {
- errormsg_errorstd("couldn't secure session state");
+ errmsg_errorstd("couldn't secure session state");
goto e;
}
// TODO(linux): call other init things
@@ -420,7 +420,7 @@ INIT {
#ifdef _WIN32
e: WerUnregisterExcludedMemoryBlock(keybox); // this'd better not fail!
e2: VirtualFree(keybox, 4096, MEM_RELEASE);
-#elif
+#else
e: munmap(keybox, 4096);
#endif
unhook_inline((void *)orig_DispatchInputEvent);
@@ -433,7 +433,7 @@ END {
WerUnregisterExcludedMemoryBlock(keybox); // this'd better not fail!
VirtualFree(keybox, 4096, MEM_RELEASE);
win32_end();
-#elif defined(__linux__)
+#else
munmap(keybox, 4096);
// TODO(linux): call other cleanup things
#endif
diff --git a/src/build/cmeta.c b/src/build/cmeta.c
index 40aba3a..0c9b3ca 100644
--- a/src/build/cmeta.c
+++ b/src/build/cmeta.c
@@ -97,24 +97,17 @@ static char *join_tokens(const Token *tok, const Token *end) {
#include "../3p/openbsd/asprintf.c" // missing from libc; plonked here for now
#endif
-static void die1(const char *s) {
+static void die(const char *s) {
fprintf(stderr, "cmeta: fatal: %s\n", s);
exit(100);
}
-#ifndef _WIN32
-static void die2(const char *s1, const char *s2) {
- fprintf(stderr, "cmeta: fatal: %s%s\n", s1, s2);
- exit(100);
-}
-#endif
-
static char *readsource(const os_char *f) {
int fd = os_open(f, O_RDONLY);
if (fd == -1) return 0;
uint bufsz = 8192;
char *buf = malloc(bufsz);
- if (!buf) die1("couldn't allocate memory");
+ if (!buf) die("couldn't allocate memory");
int nread;
int off = 0;
while ((nread = read(fd, buf + off, bufsz - off)) > 0) {
@@ -122,12 +115,12 @@ static char *readsource(const os_char *f) {
if (off == bufsz) {
bufsz *= 2;
// somewhat arbitrary cutoff
- if (bufsz == 1 << 30) die1("input file is too large");
+ if (bufsz == 1 << 30) die("input file is too large");
buf = realloc(buf, bufsz);
- if (!buf) die1("couldn't reallocate memory");
+ if (!buf) die("couldn't reallocate memory");
}
}
- if (nread == -1) die1("couldn't read file");
+ if (nread == -1) die("couldn't read file");
buf[off] = 0;
close(fd);
return buf;
@@ -141,7 +134,7 @@ const struct cmeta *cmeta_loadfile(const os_char *f) {
if (!buf) return 0;
#ifdef _WIN32
char *realname = malloc(wcslen(f) + 1);
- if (!realname) die1("couldn't allocate memory");
+ if (!realname) die("couldn't allocate memory");
// XXX: being lazy about Unicode right now; a general purpose tool should
// implement WTF8 or something. SST itself doesn't have any unicode paths
// though, so don't really care as much.
@@ -171,7 +164,7 @@ void cmeta_includes(const struct cmeta *cm,
if (tp->kind == TK_STR) {
// include strings are a special case; they don't have \escapes.
char *copy = malloc(tp->len - 1);
- if (!copy) die1("couldn't allocate memory");
+ if (!copy) die("couldn't allocate memory");
memcpy(copy, tp->loc + 1, tp->len - 2);
copy[tp->len - 2] = '\0';
cb(copy, false, ctxt);
@@ -240,13 +233,13 @@ void cmeta_conmacros(const struct cmeta *cm,
if (isplusminus) {
// XXX: this is stupid but whatever
char *plusname = malloc(sizeof("PLUS_") + tp->len);
- if (!plusname) die1("couldn't allocate memory");
+ if (!plusname) die("couldn't allocate memory");
memcpy(plusname, "PLUS_", 5);
memcpy(plusname + sizeof("PLUS_") - 1, tp->loc, tp->len);
plusname[sizeof("PLUS_") - 1 + tp->len] = '\0';
cb(plusname, false, unreg);
char *minusname = malloc(sizeof("MINUS_") + tp->len);
- if (!minusname) die1("couldn't allocate memory");
+ if (!minusname) die("couldn't allocate memory");
memcpy(minusname, "MINUS_", 5);
memcpy(minusname + sizeof("MINUS_") - 1, tp->loc, tp->len);
minusname[sizeof("MINUS_") - 1 + tp->len] = '\0';
@@ -254,7 +247,7 @@ void cmeta_conmacros(const struct cmeta *cm,
}
else {
char *name = malloc(tp->len + 1);
- if (!name) die1("couldn't allocate memory");
+ if (!name) die("couldn't allocate memory");
memcpy(name, tp->loc, tp->len);
name[tp->len] = '\0';
cb(name, isvar, unreg);
@@ -318,7 +311,7 @@ void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)(
if (equal(tp->next, "(") && tp->next->next) {
tp = tp->next->next;
char *param = malloc(tp->len + 1);
- if (!param) die1("couldn't allocate memory");
+ if (!param) die("couldn't allocate memory");
memcpy(param, tp->loc, tp->len);
param[tp->len] = '\0';
cb(type, param, ctxt);
@@ -335,10 +328,10 @@ static void pushmacroarg(const Token *last, const char *start,
struct vec_str *list) {
int len = last->loc - start + last->len;
char *dup = malloc(len + 1);
- if (!dup) die1("couldn't allocate memory");
+ if (!dup) die("couldn't allocate memory");
memcpy(dup, start, len);
dup[len] = '\0';
- if (!vec_push(list, dup)) die1("couldn't append to array");
+ if (!vec_push(list, dup)) die("couldn't append to array");
}
// XXX: maybe this should be used for the other functions too. it'd be less ugly
@@ -400,7 +393,7 @@ void cmeta_evhandlermacros(const struct cmeta *cm, const char *modname,
if (equal(tp, "HANDLE_EVENT") && equal(tp->next, "(")) {
tp = tp->next->next;
char *name = malloc(tp->len + 1);
- if (!name) die1("couldn't allocate memory");
+ if (!name) die("couldn't allocate memory");
memcpy(name, tp->loc, tp->len);
name[tp->len] = '\0';
cb_handler(name, modname);
diff --git a/src/build/codegen.c b/src/build/codegen.c
index bb25395..3cf4d8b 100644
--- a/src/build/codegen.c
+++ b/src/build/codegen.c
@@ -14,6 +14,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
+#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -118,7 +119,7 @@ static void onfeatinfo(enum cmeta_featmacro type, const char *param,
switch (type) {
case CMETA_FEAT_REQUIRE:; bool optional = false; goto dep;
case CMETA_FEAT_REQUEST: optional = true;
-dep: struct feature *dep = skiplist_get_feature(&features, param);
+dep:; struct feature *dep = skiplist_get_feature(&features, param);
if (optional) dep->is_requested = true;
if (!dep) {
fprintf(stderr, "codegen: error: feature `%s` tried to depend "
@@ -308,7 +309,7 @@ int OS_MAIN(int argc, os_char *argv[]) {
}
p[arglen] = '\0';
#else
- const char *p = p->path;
+ const char *p = pi->path;
#endif
const char *lastslash = p - 1;
for (; *p; ++p) {
diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c
index fdb2aef..d49eef5 100644
--- a/src/build/mkgamedata.c
+++ b/src/build/mkgamedata.c
@@ -121,7 +121,7 @@ static void kv_cb(enum kv_token type, const char *p, uint len, void *ctxt) {
}
state->curent = state->curent->parent;
break;
- case KV_VAL: case KV_VAL_QUOTED:
+ case KV_VAL: case KV_VAL_QUOTED:;
char *s = malloc(len + 1);
if (!s) die("couldn't allocate value string");
memcpy(s, p, len); s[len] = '\0';
diff --git a/src/chunklets/fastspin.c b/src/chunklets/fastspin.c
index bfaaf9b..e29bd68 100644
--- a/src/chunklets/fastspin.c
+++ b/src/chunklets/fastspin.c
@@ -51,6 +51,7 @@ void _mm_pause(); // don't pull in emmintrin.h for this
#include <linux/futex.h>
#include <sys/syscall.h>
+#include <unistd.h>
// some arches only have a _time64 variant. doesn't actually matter what
// timespec ABI is used here, as we don't use/expose that functionality
diff --git a/src/con_.c b/src/con_.c
index 6ae1738..a462286 100644
--- a/src/con_.c
+++ b/src/con_.c
@@ -295,10 +295,13 @@ struct _con_vtab_var_wrap _con_vtab_var_wrap = {
(void *)&AddFlags_var
};
-void *_con_vtab_iconvar[7] = {
+struct _con_vtab_iconvar_wrap _con_vtab_iconvar_wrap = {
#ifdef _WIN32
0 // because of crazy overload vtable order we can't prefill *anything*
#else
+ // RTTI members first on linux:
+ -offsetof(struct con_var, vtable_iconvar),
+ &varrtti,
// colour is the last of the 4 on linux so we can at least prefill these 3
(void *)&SetValue_str_thunk,
(void *)&SetValue_f_thunk,
diff --git a/src/con_.h b/src/con_.h
index 9e96741..db322bc 100644
--- a/src/con_.h
+++ b/src/con_.h
@@ -112,11 +112,6 @@ struct con_cmd { // ConCommand in engine
bool has_complcb : 1, use_newcb : 1, use_newcmdiface : 1;
};
-// con_var will be a bit different on linux; see offset_to_top etc.
-#ifdef __linux__
-#warning FIXME: redo multi-vtable crap for itanium ABI!
-#endif
-
struct con_var { // ConVar in engine
struct con_cmdbase base;
void **vtable_iconvar; // IConVar in engine (pure virtual)
@@ -175,7 +170,7 @@ con_cmdcbv1 con_getcmdcbv1(const struct con_cmd *cmd);
*/
#if defined(__GNUC__) || defined(__clang__)
#ifdef _WIN32
-#define __asm__(x) __asm__("_" x) // stupid mangling meme (not on linux, right?)
+#define __asm__(x) __asm__("_" x) // stupid mangling meme, only on windows!
#endif
void con_msg(const char *fmt, ...) _CON_PRINTF(1, 2) __asm__("Msg");
void con_warn(const char *fmt, ...) _CON_PRINTF(1, 2) __asm__("Warning");
@@ -214,12 +209,19 @@ extern struct _con_vtab_var_wrap {
#else
// itanium ABI also has the top offset/"whole object" offset in libstdc++
ssize topoffset;
- struct itanium_type_info *rtti;
+ const struct itanium_vmi_type_info *rtti;
#endif
void *vtable[19];
} _con_vtab_var_wrap;
#define _con_vtab_var (_con_vtab_var_wrap.vtable)
-extern void *_con_vtab_iconvar[];
+extern struct _con_vtab_iconvar_wrap {
+#ifndef _WIN32
+ ssize topoffset;
+ const struct itanium_vmi_type_info *rtti;
+#endif
+ void *vtable[7];
+} _con_vtab_iconvar_wrap;
+#define _con_vtab_iconvar _con_vtab_iconvar_wrap.vtable
#define _DEF_CVAR(name_, desc, value, hasmin_, min, hasmax_, max, flags_) \
static struct con_var _cvar_##name_ = { \
diff --git a/src/extmalloc.c b/src/extmalloc.c
index 3e14e0d..a23dc2a 100644
--- a/src/extmalloc.c
+++ b/src/extmalloc.c
@@ -1,5 +1,5 @@
/*
- * Copyright © 2021 Michael Smith <mikesmiffy128@gmail.com>
+ * Copyright © 2023 Michael Smith <mikesmiffy128@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -14,39 +14,49 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
+#ifndef _WIN32
+#include <stdlib.h>
+#endif
+
#include "intdefs.h"
#include "vcall.h"
-// XXX: this is duped from os.h because I don't want to pull in Windows.h,
-// consider splitting out the IMPORT/EXPORT defs to some other thing?
-#ifdef _WIN32
-#define IMPORT __declspec(dllimport) // only needed for variables
-#else
-#define IMPORT
-#endif
-
// XXX: not sure if "ext" is the best naming convention? use brain later
-IMPORT void *g_pMemAlloc;
+#ifdef _WIN32
+
+__declspec(dllimport) void *g_pMemAlloc;
// this interface has changed a bit between versions but thankfully the basic
// functions we care about have always been at the start - nice and easy.
-// unfortunately though, because the debug and non-debug versions are overloads
-// and Microsoft are a bunch of crazies who decided vtable order should be
-// affected by naming (overloads are grouped, and *reversed* inside of a
-// group!?), we get this amusing ABI difference between platforms:
-#ifdef _WIN32
+// note that since Microsoft are a bunch of crazies, overloads are grouped and
+// reversed so the vtable order here is maybe not what you'd expect otherwise.
DECL_VFUNC(void *, Alloc, 1, usize)
DECL_VFUNC(void *, Realloc, 3, void *, usize)
DECL_VFUNC(void, Free, 5, void *)
-#else
-DECL_VFUNC(void *, Alloc, 0, usize)
-DECL_VFUNC(void *, Realloc, 1, void *, usize)
-DECL_VFUNC(void, Free, 2, void *)
-#endif
void *extmalloc(usize sz) { return Alloc(g_pMemAlloc, sz); }
void *extrealloc(void *mem, usize sz) { return Realloc(g_pMemAlloc, mem, sz); }
void extfree(void *mem) { Free(g_pMemAlloc, mem); }
+#else
+
+void Error(const char *fmt, ...); // stub left out of con_.h (not that useful)
+
+// Linux Source doesn't seem to bother with the custom allocator stuff at all.
+// We still want to check for OoM though, because turning off overcommit is a
+// right, not a privilege. Like func_vehicle.
+void *extmalloc(usize sz) {
+ void *ret = malloc(sz);
+ if (!ret) Error("sst: out of memory");
+ return ret;
+}
+void *extrealloc(void *mem, usize sz) {
+ void *ret = realloc(mem, sz);
+ if (!ret) Error("sst: out of memory");
+ return ret;
+}
+// note: extfree is #defined to free in the header
+#endif
+
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/extmalloc.h b/src/extmalloc.h
index eb41730..6297360 100644
--- a/src/extmalloc.h
+++ b/src/extmalloc.h
@@ -23,10 +23,18 @@
* These functions are just like malloc/realloc/free, but they call into
* Valve's memory allocator wrapper, which ensures that allocations crossing
* plugin/engine boundaries won't cause any weird issues.
+ *
+ * On Linux, there is no allocation wrapper, but these still do their own OoM
+ * checking, so there's no need to think about that.
*/
void *extmalloc(usize sz);
void *extrealloc(void *mem, usize sz);
+#ifdef _WIN32
void extfree(void *mem);
+#else
+void free(void *);
+#define extfree free
+#endif
#endif
diff --git a/src/gameinfo.c b/src/gameinfo.c
index 3713d6c..85220a8 100644
--- a/src/gameinfo.c
+++ b/src/gameinfo.c
@@ -80,7 +80,8 @@ bool gameinfo_init(void) {
title[len - 9] = '\0';
}
#else
-#error TODO(linux): grab window handle and title from SDL (a bit involved...)
+//#error TODO(linux): grab window handle and title from SDL (a bit involved...)
+ gameinfo_title = "Linux Game With As Yet Unkown Title";
#endif
// SUPER crude algorithm to force uppercase titles like HALF-LIFE 2 or
diff --git a/src/hook.c b/src/hook.c
index 3d6c14d..ba02461 100644
--- a/src/hook.c
+++ b/src/hook.c
@@ -27,8 +27,6 @@
// Almost certainly breaks in some weird cases. Oh well! Most of the time,
// vtable hooking is more reliable, this is only for, uh, emergencies.
-#if defined(_WIN32) && !defined(_WIN64)
-
#if defined(__GNUC__) || defined(__clang__)
__attribute__((aligned(4096)))
#elif defined(_MSC_VER)
@@ -45,6 +43,18 @@ bool hook_init(void) {
return os_mprot(trampolines, sizeof(trampolines), PAGE_EXECUTE_READWRITE);
}
+static inline void iflush(void *p, int len) {
+#if defined(_WIN32)
+ // -1 is the current process, and it's a constant in the WDK, so it's
+ // assumed we can safely avoid the useless GetCurrentProcess call
+ FlushInstructionCache((void *)-1, p, len);
+#elif defined(__GNUC__)
+ __builtin___clear_cache((char *)p, (char *)p + len);
+#else
+#error no way to flush instruction cache
+#endif
+}
+
void *hook_inline(void *func_, void *target) {
uchar *func = func_;
// dumb hack: if we hit some thunk that immediately jumps elsewhere (which
@@ -74,30 +84,21 @@ void *hook_inline(void *func_, void *target) {
}
}
// for simplicity, just bump alloc the trampoline. no need to free anyway
- if (nexttrampoline - trampolines > sizeof(trampolines) - len - 6) goto nosp;
- // TODO(opt): stop pretending to be thread-safe, it's just slowing us down
- uchar *trampoline = (uchar *)InterlockedExchangeAdd(
- (volatile long *)&nexttrampoline, len + 6);
- // avoid TOCTOU
- if (trampoline - trampolines > sizeof(trampolines) - len - 6) {
-nosp: con_warn("hook_inline: out of trampoline space\n");
+ if (nexttrampoline - trampolines > sizeof(trampolines) - len - 6) {
+ con_warn("hook_inline: out of trampoline space\n");
return 0;
}
+ uchar *trampoline = nexttrampoline;
+ nexttrampoline += len + 6; // NOT thread-safe. we don't need that anyway!
*trampoline++ = len; // stick length in front for quicker unhooking
memcpy(trampoline, func, len);
trampoline[len] = X86_JMPIW;
uint diff = func - (trampoline + 5); // goto the continuation
memcpy(trampoline + len + 1, &diff, 4);
- uchar jmp[8];
- jmp[0] = X86_JMPIW;
diff = (uchar *)target - (func + 5); // goto the hook target
- memcpy(jmp + 1, &diff, 4);
- // pad with original bytes so we can do an 8-byte atomic write
- memcpy(jmp + 5, func + 5, 3);
- *(volatile uvlong *)func = *(uvlong *)jmp; // (assuming function is aligned)
- // -1 is the current process, and it's a constant in the WDK, so it's
- // assumed we can safely avoid the useless GetCurrentProcess call
- FlushInstructionCache((void *)-1, func, len);
+ func[0] = X86_JMPIW;
+ memcpy(func + 1, &diff, 4);
+ iflush(func, 5);
return trampoline;
}
@@ -107,13 +108,7 @@ void unhook_inline(void *orig) {
int off = mem_load32(p + len + 1);
uchar *q = p + off + 5;
memcpy(q, p, 5); // XXX: not atomic atm! (does any of it even need to be?)
- FlushInstructionCache((void *)-1, q, 5);
+ iflush(q, 5);
}
-#else
-
-// TODO(linux): Implement for Linux and/or x86_64 when needed...
-
-#endif
-
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/hook.h b/src/hook.h
index 8d91508..700adc9 100644
--- a/src/hook.h
+++ b/src/hook.h
@@ -1,5 +1,5 @@
/*
- * Copyright © 2021 Michael Smith <mikesmiffy128@gmail.com>
+ * Copyright © 2023 Michael Smith <mikesmiffy128@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -41,6 +41,10 @@ static inline void unhook_vtable(void **vtable, usize off, void *orig) {
/*
* Returns a trampoline pointer, or null if hooking failed. Unlike hook_vtable,
* handles memory protection for you.
+ *
+ * This function is not remotely thread-safe, and should never be called from
+ * any thread besides the main one nor be used to hook anything that gets called
+ * from other threads.
*/
void *hook_inline(void *func, void *target);
diff --git a/src/os-unix.h b/src/os-unix.h
index a25d8ed..097d300 100644
--- a/src/os-unix.h
+++ b/src/os-unix.h
@@ -47,31 +47,58 @@ typedef char os_char;
static inline void *os_dlopen(const char *name) {
return dlopen(name, RTLD_NOW);
}
-static inline void *os_dlhandle(const char *name) {
- void *ret = dlopen(name, RTLD_NOW | RTLD_NOLOAD);
- if (ret) dlclose(ret);
- return ret;
-}
#define os_dlsym dlsym
#ifdef __linux__
// note: this is glibc-specific. it shouldn't be used in build-time code, just
// the plugin itself (that really shouldn't be a problem).
+
+// private struct hidden behind _GNU_SOURCE. see dlinfo(3) or <link.h>
+struct gnu_link_map {
+ unsigned long l_addr;
+ const char *l_name;
+ void *l_ld;
+ struct gnu_link_map *l_next, *l_prev;
+ // [more private stuff below]
+};
+
+static inline void *os_dlhandle(const char *name) {
+ extern struct gnu_link_map *_os_lmbase; // note: defined in sst.c for now
+ if (!_os_lmbase) { // IMPORTANT: not thread safe. don't forget later!
+ _os_lmbase = (struct gnu_link_map *)dlopen("libc.so.6",
+ RTLD_LAZY | RTLD_NOLOAD);
+ dlclose(_os_lmbase); // assume success
+ while (_os_lmbase->l_prev) _os_lmbase = _os_lmbase->l_prev;
+ }
+ // this is a tiny bit crude, but basically okay. we just want to find
+ // something that roughly matches the basename, rather than needing an exact
+ // path, in a manner vaguely similar to Windows' GetModuleHandle(). that way
+ // we can just look up client.so or something without having to figure out
+ // where exactly that is.
+ for (struct gnu_link_map *lm = _os_lmbase; lm; lm = lm->l_next) {
+ if (name[0] == '/') {
+ if (!strcmp(name, lm->l_name)) return lm;
+ continue;
+ }
+ int namelen = strlen(lm->l_name);
+ int sublen = strlen(name);
+ if (sublen >= namelen) continue;
+ if (lm->l_name[namelen - sublen - 1] == '/' && !memcmp(
+ lm->l_name + namelen - sublen, name, sublen)) {
+ return lm;
+ }
+ }
+ return 0;
+}
+
static inline int os_dlfile(void *m, char *buf, int sz) {
- // private struct hidden behind _GNU_SOURCE. see dlinfo(3) or <link.h>
- struct gnu_link_map {
- unsigned long l_addr;
- const char *l_name;
- void *l_ld;
- struct gnu_link_map *l_next, *l_prev;
- // [more private stuff below]
- };
struct gnu_link_map *lm = m;
int ssz = strlen(lm->l_name) + 1;
if (ssz > sz) { errno = ENAMETOOLONG; return -1; }
memcpy(buf, lm->l_name, ssz);
return ssz;
}
+
#endif
// unix mprot flags are much nicer but cannot be defined in terms of the windows
diff --git a/src/sst.c b/src/sst.c
index 9fd1108..3618210 100644
--- a/src/sst.c
+++ b/src/sst.c
@@ -14,6 +14,9 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
+#ifndef _WIN32
+#include <stdlib.h> // unsetenv
+#endif
#include <string.h>
#ifdef _WIN32
@@ -71,6 +74,8 @@ static void *ownhandle(void) {
}
return cached;
}
+
+struct gnu_link_map *_os_lmbase = 0; // XXX: stupid place to put this, oh well
#endif
#ifdef _WIN32
@@ -139,9 +144,32 @@ DEF_CCMD_HERE(sst_autoload_enable, "Register SST to load on game startup", 0) {
return;
}
// arbitrary aesthetic judgement
- for (os_char *p = relpath; *p; ++p) if (*p == L'\\') *p = L'/';
+ for (ushort *p = relpath; *p; ++p) if (*p == L'\\') *p = L'/';
#else
-#error TODO(linux): implement this, it's late right now and I can't be bothered
+ const char *p = path, *q = startdir;
+ int slash = 0;
+ int i = 1;
+ for (;; ++i) {
+ if (p[i] == '/' && (q[i] == '/' || q[i] == '\0')) slash = i;
+ if (p[i] != q[i]) break;
+ }
+ int rellen = strlen(p + slash + 1) + 1; // include \0
+ char *r = relpath;
+ if (q[i]) {
+ if (r - relpath >= PATH_MAX - 3 - rellen) {
+ errmsg_errorx("path to game is too long"); // eh...
+ return;
+ }
+ for (;;) {
+ r[0] = '.'; r[1] = '.'; r[2] = '/';
+ r += 3;
+ for (;;) {
+ if (q[++i] == '/') break;
+ if (!q[i]) goto c;
+ }
+ }
+ }
+c: memcpy(r, p + slash + 1, rellen);
#endif
int len = os_strlen(gameinfo_gamedir);
if (len + sizeof("/addons/" VDFBASENAME ".vdf") >
@@ -231,8 +259,8 @@ static void do_featureinit(void) {
"CreateInterface"))) {
errmsg_warndl("couldn't get client's CreateInterface");
}
- void *inputsystemlib = os_dlhandle(OS_LIT("bin/") OS_LIT(OS_DLPREFIX)
- OS_LIT("inputsystem") OS_LIT(OS_DLSUFFIX));
+ void *inputsystemlib = os_dlhandle(OS_LIT("bin/") OS_LIT("inputsystem")
+ OS_LIT(OS_DLSUFFIX));
if (!inputsystemlib) {
errmsg_warndl("couldn't get the input system library");
}
@@ -240,8 +268,9 @@ static void do_featureinit(void) {
"CreateInterface"))) {
errmsg_warndl("couldn't get input system's CreateInterface");
}
- inputsystem = factory_inputsystem("InputSystemVersion001", 0);
- if (!inputsystem) errmsg_warnx("missing input system interface");
+ else if (!(inputsystem = factory_inputsystem("InputSystemVersion001", 0))) {
+ errmsg_warnx("missing input system interface");
+ }
// ... and now for the real magic!
initfeatures();
@@ -398,9 +427,6 @@ static void do_unload(void) {
}
#endif
endfeatures();
-#ifdef __linux__
- if (clientlib) dlclose(clientlib);
-#endif
con_disconnect();
}
diff --git a/src/stubs/tier0.c b/src/stubs/tier0.c
index 2c9c578..75fb2db 100644
--- a/src/stubs/tier0.c
+++ b/src/stubs/tier0.c
@@ -4,6 +4,10 @@
F(Msg)
F(Warning)
-V(g_pMemAlloc)
+#ifdef _WIN32
+V(g_pMemAlloc) // this doesn't exist at all on Linux
+#else
+F(Error) // only used for extmalloc() and nothing else :^)
+#endif
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/test/test.h b/test/test.h
index 3893359..cb806ea 100644
--- a/test/test.h
+++ b/test/test.h
@@ -69,7 +69,7 @@ static int _ntests = 0;
static struct _test _TESTCAT(_test_, __LINE__) = { \
.flags = _TEST_USE_DEFAULT_FLAGS, \
.timeout = _TEST_DEFAULT_TIMEOUT, \
- .desc = __FILE__":"_TESTSTR(__LINE__)": "desc_, \
+ .desc = __FILE__":"_TESTSTR(__LINE__)": "desc_ __VA_OPT__(,) \
__VA_ARGS__, \
._f = &_TESTCAT(_test_f_, __LINE__) \
}; \