From a1998f2f7ce4153d670e2e5cb5018366517cc1ca Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sun, 20 Aug 2023 16:20:03 +0100 Subject: Get things at least compiling under Linux Nothing really works yet, but at least test.h and fastspin are fixed and some of the issues with RTTI and libdl and stuff are maybe kind of sorted, subject to more testing later. The main issue now seems to be the cvar interface not quite lining up and crashing pretty much immediately. That'll probably take a lot more debugging to figure out, which likely still won't be a priority for quite a while. --- README | 17 ++++++++-------- compile | 16 +++++++-------- src/abi.h | 50 +++++++++++++++++++++++++++++++++------------ src/ac.c | 6 +++--- src/build/cmeta.c | 35 +++++++++++++------------------- src/build/codegen.c | 5 +++-- src/build/mkgamedata.c | 2 +- src/chunklets/fastspin.c | 1 + src/con_.c | 5 ++++- src/con_.h | 18 ++++++++-------- src/extmalloc.c | 50 +++++++++++++++++++++++++++------------------ src/extmalloc.h | 8 ++++++++ src/gameinfo.c | 3 ++- src/hook.c | 45 ++++++++++++++++++---------------------- src/hook.h | 6 +++++- src/os-unix.h | 53 ++++++++++++++++++++++++++++++++++++------------ src/sst.c | 44 ++++++++++++++++++++++++++++++++-------- src/stubs/tier0.c | 6 +++++- test/test.h | 2 +- 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 #include #include #include @@ -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 #include +#include // 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 + * Copyright © 2023 Michael Smith * * 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 +#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 + * Copyright © 2023 Michael Smith * * 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 +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 - 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 // unsetenv +#endif #include #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__) \ }; \ -- cgit v1.2.3