From 7b12eb811ff62d9d14ccb7c152a9821796efe9a5 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sun, 24 Apr 2022 03:27:35 +0100 Subject: Replace udis86 with a very small x86 decoder hook_inline() uses the new x86_len() function to get instruction lengths instead of doing full-blown disassembly, which should be a tiny bit quicker, and also removes the next for about 90KiB of lookup tables and such in the final binary. The code-digging logic in demorecord is also rewritten to be opcode-based rather than mnenmonic based. In general, going forward the plan is to always rely on opcodes and thus avoid a bunch of disassembly work every plugin load. udis86 is still in the tree for now to provide dbg_asmdump(), but it's only compiled into debug builds and left out of releases completely. As such, the whole BSD licence statement is also gone from the distribution LICENCE files. There's now also a dbg_toghidra() which spits out a rebased address to look stuff up for proper reverse engineering, which might be more useful than dbg_asmdump() anyway. If nobody ends up using the latter ever again, udis86 could get chucked completely. We'll see. Also shoehorned into this commit are a couple more forgotten copyright year bumps and some general minor cleanup here and there, because I couldn't be bothered wading through all the diff hunks. Oh, and makebindist.bat now makes an effort to make the zip file timestamps predictable/reproducible. That should be a different commit for sure, but oh well too bad. --- .gitignore | 1 + compile | 5 +- compile.bat | 11 +- dist/LICENCE.linux | 28 ---- dist/LICENCE.windows | 28 ---- src/3p/README | 3 + src/autojump.c | 31 ++-- src/build/cmeta.c | 2 +- src/build/cmeta.h | 2 +- src/build/codegen.c | 2 +- src/con_.c | 2 +- src/con_.h | 10 +- src/dbg.c | 21 ++- src/dbg.h | 17 +- src/demorec.c | 112 +++++-------- src/hook.c | 47 +++--- src/os-unix.h | 2 + src/udis86.c | 2 - src/x86.c | 87 ++++++++++ src/x86.h | 451 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/bitbuf.test.c | 2 +- test/hook.test.c | 8 +- test/kv.test.c | 2 +- tools/mkbindist.bat | 5 +- 24 files changed, 686 insertions(+), 195 deletions(-) create mode 100644 src/x86.c create mode 100644 src/x86.h diff --git a/.gitignore b/.gitignore index 5cfdbc5..d880120 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /sst.pdb /sst.so /compile_commands.json +/compile_flags.txt # a place to plonk crap you don't wanna commit yet /junk/ diff --git a/compile b/compile index 072a1eb..fdeb843 100755 --- a/compile +++ b/compile @@ -40,7 +40,6 @@ ld() { src="\ autojump.c con_.c - dbg.c demorec.c extmalloc.c fixes.c @@ -49,7 +48,11 @@ src="\ hook.c kv.c sst.c + x86.c" +if [ "$dbg" = 1 ]; then src="$src \ + dbg.c udis86.c" +fi $HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -o .build/codegen \ src/build/codegen.c src/build/cmeta.c diff --git a/compile.bat b/compile.bat index f363eef..faef34f 100644 --- a/compile.bat +++ b/compile.bat @@ -39,15 +39,14 @@ goto :eof -o .build/codegen.exe src/build/codegen.c src/build/cmeta.c || exit /b %HOSTCC% -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -ladvapi32 ^ -o .build/mkgamedata.exe src/build/mkgamedata.c src/kv.c || exit /b -.build\codegen.exe src/autojump.c src/con_.c src/dbg.c src/demorec.c src/extmalloc.c ^ -src/fixes.c src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/rinput.c src/sst.c src/udis86.c || exit /b +.build\codegen.exe src/autojump.c src/con_.c src/demorec.c src/extmalloc.c src/fixes.c ^ +src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/rinput.c src/sst.c src/x86.c || exit /b .build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv || exit /b llvm-rc /FO .build\dll.res src\dll.rc || exit /b %CC% -shared -O0 -w -o .build/tier0.dll src/stubs/tier0.c %CC% -shared -O0 -w -o .build/vstdlib.dll src/stubs/vstdlib.c call :cc src/autojump.c || exit /b call :cc src/con_.c || exit /b -call :cc src/dbg.c || exit /b call :cc src/demorec.c || exit /b call :cc src/extmalloc.c || exit /b call :cc src/fixes.c || exit /b @@ -57,7 +56,11 @@ call :cc src/hook.c || exit /b call :cc src/kv.c || exit /b call :cc src/rinput.c || exit /b call :cc src/sst.c || exit /b -call :cc src/udis86.c || exit /b +call :cc src/x86.c || exit /b +if "%dbg%"=="1" ( + call :cc src/dbg.c || exit /b + call :cc src/udis86.c || exit /b +) %CC% -shared -flto %ldflags% -Wl,/IMPLIB:.build/sst.lib,/Brepro ^ -L.build -luser32 -ladvapi32 -lshlwapi -ltier0 -lvstdlib -o sst.dll%objs% .build/dll.res || exit /b :: get rid of another useless file (can we just not create this???) diff --git a/dist/LICENCE.linux b/dist/LICENCE.linux index 13cd6b3..535f40f 100644 --- a/dist/LICENCE.linux +++ b/dist/LICENCE.linux @@ -16,33 +16,5 @@ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ════════════════════════════════════════════════════════════════════════════════ - -sst.so also includes code from the udis86 project, licensed as follows: -════════════════════════════════════════════════════════════════════════════════ -Copyright © 2002–2009 Vivek Thampi -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -• Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -• Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -════════════════════════════════════════════════════════════════════════════════ - Please respect these terms when distributing copies of sst.so — doing so is as simple as keeping this LICENCE file in place. Thanks, and have fun! :^) diff --git a/dist/LICENCE.windows b/dist/LICENCE.windows index 13f282f..7ebf1f1 100644 --- a/dist/LICENCE.windows +++ b/dist/LICENCE.windows @@ -16,33 +16,5 @@ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ════════════════════════════════════════════════════════════════════════════════ - -sst.dll also includes code from the udis86 project, licensed as follows: -════════════════════════════════════════════════════════════════════════════════ -Copyright © 2002–2009 Vivek Thampi -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -• Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -• Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -════════════════════════════════════════════════════════════════════════════════ - Please respect these terms when distributing copies of sst.dll — doing so is as simple as keeping this LICENCE file in place. Thanks, and have fun! :^) diff --git a/src/3p/README b/src/3p/README index e0c40a6..1617ff0 100644 --- a/src/3p/README +++ b/src/3p/README @@ -2,6 +2,9 @@ These are imported 3rd party library sources, wrangled for ease of plonking into the build (e.g. relative #includes, etc.). Used in SST itself: + [none currently, stuff will probably be added later] + +Used in debug builds, but not compiled into releases: - udis86 Used at build time: diff --git a/src/autojump.c b/src/autojump.c index e91c854..6c30df9 100644 --- a/src/autojump.c +++ b/src/autojump.c @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 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 @@ -30,29 +30,18 @@ DEF_CVAR(sst_autojump, "Jump upon hitting the ground while holding space", 0, struct vec3f { float x, y, z; }; struct CMoveData { - bool firstrun : 1; - bool gamecodemoved : 1; + bool firstrun : 1, gamecodemoved : 1; ulong playerhandle; int impulse; - struct vec3f viewangles; - struct vec3f absviewangles; - int buttons; - int oldbuttons; - float mv_forward; - float mv_side; - float mv_up; - float maxspeed; - float clientmaxspeed; - struct vec3f vel; - struct vec3f angles; - struct vec3f oldangles; + struct vec3f viewangles, absviewangles; + int buttons, oldbuttons; + float mv_forward, mv_side, mv_up; + float maxspeed, clmaxspeed; + struct vec3f vel, angles, oldangles; float out_stepheight; - struct vec3f out_wishvel; - struct vec3f out_jumpvel; - struct vec3f constraint_center; - float constraint_radius; - float constraint_width; - float constraint_speedfactor; + struct vec3f out_wishvel, out_jumpvel; + struct vec3f constraint_centre; + float constraint_radius, constraint_width, constraint_speedfactor; struct vec3f origin; }; diff --git a/src/build/cmeta.c b/src/build/cmeta.c index 4e1eb4a..3d9281a 100644 --- a/src/build/cmeta.c +++ b/src/build/cmeta.c @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 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 diff --git a/src/build/cmeta.h b/src/build/cmeta.h index 18ff62c..20672f4 100644 --- a/src/build/cmeta.h +++ b/src/build/cmeta.h @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 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 diff --git a/src/build/codegen.c b/src/build/codegen.c index 38645e5..6d5fc99 100644 --- a/src/build/codegen.c +++ b/src/build/codegen.c @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 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 diff --git a/src/con_.c b/src/con_.c index 925eafb..b69b1a6 100644 --- a/src/con_.c +++ b/src/con_.c @@ -1,4 +1,4 @@ -/* XXX: THIS FILE SHOULD BE CALLED `con.c` BUT WINDOWS IS STUPID */ +/* THIS FILE SHOULD BE CALLED `con.c` BUT WINDOWS IS STUPID */ /* * Copyright © 2022 Michael Smith * diff --git a/src/con_.h b/src/con_.h index c0c37f1..a5d0c87 100644 --- a/src/con_.h +++ b/src/con_.h @@ -1,4 +1,4 @@ -/* XXX: THIS FILE SHOULD BE CALLED `con.h` BUT WINDOWS IS STUPID */ +/* THIS FILE SHOULD BE CALLED `con.h` BUT WINDOWS IS STUPID */ /* * Copyright © 2022 Michael Smith * @@ -40,7 +40,7 @@ struct con_cmdargs { const char *argv[CON_CMD_MAX_ARGC]; }; -/* an ARGB colour, passed to con_colourmsg */ +/* an RGBA colour, passed to con_colourmsg */ struct con_colour { union { struct { u8 r, g, b, a; }; @@ -236,7 +236,11 @@ extern void *_con_vtab_iconvar[]; .parent = &_cvar_##name_, /* bizarre, but how the engine does it */ \ .defaultval = _Generic(value, char *: value, int: #value, \ float: #value), \ - .strlen = _Generic(value, char *: sizeof(value), \ + /* N.B. the NOLINT comment below isn't for you, the reader, it's for the + computer, because clangd decided the only way to turn off a bogus + warning is to write a bogus comment. Also note, this comment you're + reading now isn't very useful either, I'm just angry. */ \ + .strlen = _Generic(value, char *: sizeof(value), /*NOLINT*/ \ default: sizeof(#value)), \ .fval = _Generic(value, char *: 0, int: value, float: value), \ .ival = _Generic(value, char *: 0, int: value, float: (int)value), \ diff --git a/src/dbg.c b/src/dbg.c index 20f0271..c7af49a 100644 --- a/src/dbg.c +++ b/src/dbg.c @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 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,6 +14,12 @@ * PERFORMANCE OF THIS SOFTWARE. */ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif + #include "con_.h" #include "intdefs.h" #include "ppmagic.h" @@ -46,4 +52,17 @@ void dbg_asmdump(char *name, const void *p, int len) { } } +#ifdef _WIN32 +usize dbg_toghidra(void *addr) { + void *mod; + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (ushort *)addr, + (HMODULE *)&mod/*please leave me alone*/)) { + con_warn("dbg_toghidra: couldn't get base address\n"); + return 0; + } + return (char *)addr - (char *)mod + 0x10000000; +} +#endif + // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/dbg.h b/src/dbg.h index 12d7e0c..e3625e0 100644 --- a/src/dbg.h +++ b/src/dbg.h @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 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 @@ -17,12 +17,23 @@ #ifndef INC_DBG_H #define INC_DBG_H -/* prints out a basic hexadecimal listing of a byte range */ +/* + * These functions can all be used for development and debugging but aren't + * available to release builds; this header shouldn't even be #included in real + * code that's committed to a repo. + */ + +/* Prints out a basic hexadecimal listing of a byte range. */ void dbg_hexdump(char *name, const void *p, int len); -/* prints out a disassembly of some instructions in memory */ +/* Prints out a disassembly of some instructions in memory. */ void dbg_asmdump(char *name, const void *p, int len); +#ifdef _WIN32 // at least for now +/* Returns a function's Ghidra address, assuming default project offsets. */ +void *dbg_toghidra(void *addr); +#endif + #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/demorec.c b/src/demorec.c index bcc7367..a384e55 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -1,6 +1,6 @@ /* * Copyright © 2021 Willian Henrique - * Copyright © 2021 Michael Smith + * Copyright © 2022 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 @@ -27,8 +27,9 @@ #include "intdefs.h" #include "mem.h" #include "os.h" -#include "udis86.h" +#include "ppmagic.h" #include "vcall.h" +#include "x86.h" #define SIGNONSTATE_SPAWN 5 // ready to receive entity packets #define SIGNONSTATE_FULL 6 // fully connected, first non-delta packet received @@ -95,7 +96,6 @@ static void hook_stop_callback(const struct con_cmdargs *args) { orig_stop_callback(args); } - // The engine allows usermessages up to 255 bytes, we add 2 bytes of overhead, // and then there's the leading bits before that too (see create_message) static char bb_buf[DEMOREC_CUSTOM_MSG_MAX + 4]; @@ -139,75 +139,59 @@ void demorec_writecustom(void *buf, int len) { bitbuf_reset(&bb); } +// XXX: probably want some general foreach-instruction macro once we start doing +// this kind of hackery in multiple different places +#define NEXT_INSN(p) do { \ + int _len = x86_len(p); \ + if (_len == -1) { \ + con_warn("demorec: %s: unknown or invalid instruction\n", __func__); \ + return false; \ + } \ + (p) += _len; \ +} while (0) // This finds the "demorecorder" global variable (the engine-wide CDemoRecorder // instance). -static inline void *find_demorecorder(struct con_cmd *cmd_stop) { - // The "stop" command calls the virtual function demorecorder.IsRecording(), - // so just look for the load of the "this" pointer - struct ud udis; - ud_init(&udis); - ud_set_mode(&udis, 32); - ud_set_input_buffer(&udis, (uchar *)con_getcmdcb(cmd_stop), 32); - while (ud_disassemble(&udis)) { +static inline bool find_demorecorder(struct con_cmd *cmd_stop) { #ifdef _WIN32 - if (ud_insn_mnemonic(&udis) == UD_Imov) { - const struct ud_operand *dest = ud_insn_opr(&udis, 0); - const struct ud_operand *src = ud_insn_opr(&udis, 1); - // looking for a mov from an address into ECX, as per thiscall - if (dest->type == UD_OP_REG && dest->base == UD_R_ECX && - src->type == UD_OP_MEM) { - return *(void **)src->lval.udword; - } + uchar *stopcb = (uchar *)con_getcmdcb(cmd_stop); + // The "stop" command calls the virtual function demorecorder.IsRecording(), + // so just look for the load of the "this" pointer into ECX + for (uchar *p = stopcb; p - stopcb < 32;) { + if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 1, 5)) { + void **indirect = mem_loadptr(p + 2); + demorecorder = *indirect; + return true; } + NEXT_INSN(p); + } #else #warning TODO(linux): implement linux equivalent (cdecl!) - return 0; #endif - } - return 0; + return false; } // This finds "m_bRecording" and "m_nDemoNumber" using the pointer to the // original "StopRecording" demorecorder function. -static inline bool find_recmembers(void *stop_recording_func) { - struct ud udis; - ud_init(&udis); - ud_set_mode(&udis, 32); - // TODO(opt): consider the below: is it really needed? does it matter? - // way overshooting the size of the function in bytes to make sure it - // accomodates for possible differences in different games. we make sure - // to stop early if we find a RET so should be fine - ud_set_input_buffer(&udis, (uchar *)stop_recording_func, 200); - while (ud_disassemble(&udis)) { +static inline bool find_recmembers(void *stoprecording) { #ifdef _WIN32 - enum ud_mnemonic_code code = ud_insn_mnemonic(&udis); - if (code == UD_Imov) { - const struct ud_operand *dest = ud_insn_opr(&udis, 0); - const struct ud_operand *src = ud_insn_opr(&udis, 1); - // m_nDemoNumber and m_bRecording are both set to 0 - // looking for movs with immediates equal to 0 - // the byte immediate refers to m_bRecording - if (src->type == UD_OP_IMM && src->lval.ubyte == 0) { - if (src->size == 8) { - recording = (bool *)mem_offset(demorecorder, - dest->lval.udword); - } - else { - demonum = (int *)mem_offset(demorecorder, - dest->lval.udword); - } - if (recording && demonum) return true; // blegh - } + for (uchar *p = (uchar *)stoprecording; p - (uchar *)stoprecording < 128;) { + // m_nDemoNumber = 0 -> mov dword ptr [ + off], 0 + // XXX: might end up wanting constants for the MRM field masks? + if (p[0] == X86_MOVMIW && (p[1] & 0xC0) == 0x80 && + mem_load32(p + 6) == 0) { + demonum = mem_offset(demorecorder, mem_load32(p + 2)); } - else if (code == UD_Iret) { - return false; + // m_bRecording = false -> mov byte ptr [ + off], 0 + else if (p[0] == X86_MOVMI8 && (p[1] & 0xC0) == 0x80 && p[6] == 0) { + recording = mem_offset(demorecorder, mem_load32(p + 2)); } + if (recording && demonum) return true; // blegh + NEXT_INSN(p); + } #else // linux is probably different here idk -#warning TODO(linux): implement linux equivalent - return false; +#warning TODO(linux): implement linux equivalent (???) #endif - } return false; } @@ -215,6 +199,7 @@ static inline bool find_recmembers(void *stop_recording_func) { // network packet, wraps it up in the appropriate demo framing format and writes // it out to the demo file being recorded. static bool find_WriteMessages(void) { + // TODO(compat): probably rewrite this to just scan for a call instruction! const uchar *insns = (*(uchar ***)demorecorder)[gamedata_vtidx_RecordPacket]; // RecordPacket calls WriteMessages pretty much right away: // 56 push esi @@ -228,10 +213,10 @@ static bool find_WriteMessages(void) { // So we just double check the byte pattern... static const uchar bytes[] = #ifdef _WIN32 -{0x56, 0x57, 0x8B, 0xF1, 0x8D, 0xBE, 0x8C, 0x06, 0x00, 0x00, 0x57, 0xE8}; + HEXBYTES(56, 57, 8B, F1, 8D, BE, 8C, 06, 00, 00, 57, E8); #else #warning This is possibly different on Linux too, have a look! -{-1, -1, -1, -1, -1, -1}; + {-1, -1, -1, -1, -1, -1}; #endif if (!memcmp(insns, bytes, sizeof(bytes))) { ssize off = mem_loadoffset(insns + sizeof(bytes)); @@ -249,21 +234,17 @@ bool demorec_init(void) { con_warn("demorec: missing gamedata entries for this engine\n"); return false; } - cmd_stop = con_findcmd("stop"); if (!cmd_stop) { // can *this* even happen? I hope not! con_warn("demorec: couldn't find \"stop\" command\n"); return false; } - - demorecorder = find_demorecorder(cmd_stop); - if (!demorecorder) { + if (!find_demorecorder(cmd_stop)) { con_warn("demorec: couldn't find demo recorder instance\n"); return false; } void **vtable = *(void ***)demorecorder; - // XXX: 16 is totally arbitrary here! figure out proper bounds later if (!os_mprot(vtable, 16 * sizeof(void *), PAGE_EXECUTE_READWRITE)) { #ifdef _WIN32 @@ -276,7 +257,7 @@ bool demorec_init(void) { return false; } - if (!find_recmembers(vtable[7])) { + if (!find_recmembers(vtable[7])) { // XXX: stop hardcoding this!? con_warn("demorec: couldn't find m_bRecording and m_nDemoNumber\n"); return false; } @@ -301,11 +282,6 @@ bool demorec_custom_init(void) { con_warn("demorec: custom: missing gamedata entries for this engine\n"); return false; } - // TODO(featgen): auto-check this factory - if (!factory_engine) { - con_warn("demorec: missing required interfaces\n"); - return false; - } // More UncraftedkNowledge: // > yeah okay so [the usermessage length is] 11 bits if the demo protocol diff --git a/src/hook.c b/src/hook.c index 6b9036c..56eaf7d 100644 --- a/src/hook.c +++ b/src/hook.c @@ -21,7 +21,7 @@ #include "intdefs.h" #include "mem.h" #include "os.h" -#include "udis86.h" +#include "x86.h" // Warning: half-arsed hacky implementation (because that's all we really need) // Almost certainly breaks in some weird cases. Oh well! Most of the time, @@ -39,50 +39,47 @@ static void setrwx(void) { os_mprot(trampolines, sizeof(trampolines), PAGE_EXECUTE_READWRITE); } -#define RELJMP 0xE9 // the first byte of a 5-byte jmp - void *hook_inline(void *func_, void *target) { uchar *func = func_; // dumb hack: rather than correcting jmp offsets and having to painstakingly // track them all, just look for the underlying thing being jmp-ed to and // hook _that_. - while (*func == RELJMP) func += mem_loadoffset(func + 1) + 5; + while (*func == X86_JMPIW) func += mem_loadoffset(func + 1) + 5; if (!os_mprot(func, 5, PAGE_EXECUTE_READWRITE)) return false; - struct ud udis; - ud_init(&udis); - ud_set_mode(&udis, 32); - // max insn length is 15, we overwrite 5, so max to copy is 4 + 15 = 19 - ud_set_input_buffer(&udis, func, 19); int len = 0; - do { - ud_disassemble(&udis); // assume we don't run out of bytes - // TODO(opt): this check should probably be gone, keeping it to make dev - // life easier/less crashy. Really, we should just refrain from hooking - // things that immediately call or branch (als, immediate jump is - // already handled above). - if (ud_insn_mnemonic(&udis) == UD_Ijmp || - ud_insn_mnemonic(&udis) == UD_Icall) { - con_warn("hook_inline: jmp/call adjustment NYI\n"); - return 0; + for (;;) { + if (func[len] == X86_CALL) { + con_warn("hook_inline: can't trampoline call instructions\n"); + return false; + } + int ilen = x86_len(func + len); + if (ilen == -1) { + con_warn("hook_inline: unknown or invalid instruction\n"); + return false; } - len += ud_insn_len(&udis); - } while (len < 5); + len += ilen; + if (len >= 5) break; + if (func[len] == X86_JMPIW) { + con_warn("hook_inline: can't trampoline jmp instructions\n"); + return false; + } + } // for simplicity, just bump alloc the trampoline. no need to free anyway - if (nexttrampoline - trampolines > sizeof(trampolines) - len - 6) goto nospc; + if (nexttrampoline - trampolines > sizeof(trampolines) - len - 6) goto nosp; uchar *trampoline = (uchar *)InterlockedExchangeAdd( (volatile long *)&nexttrampoline, len + 6); // avoid TOCTOU if (trampoline - trampolines > sizeof(trampolines) - len - 6) { -nospc: con_warn("hook_inline: out of trampoline space\n"); +nosp: con_warn("hook_inline: out of trampoline space\n"); return 0; } *trampoline++ = len; // stick length in front for quicker unhooking memcpy(trampoline, func, len); - trampoline[len] = RELJMP; + trampoline[len] = X86_JMPIW; uint diff = func - (trampoline + 5); // goto the continuation memcpy(trampoline + len + 1, &diff, 4); uchar jmp[8]; - jmp[0] = RELJMP; + 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 diff --git a/src/os-unix.h b/src/os-unix.h index 8b7bf27..5c3c604 100644 --- a/src/os-unix.h +++ b/src/os-unix.h @@ -45,6 +45,7 @@ typedef char os_char; #define os_dlsym dlsym +#ifdef __linux__ static inline bool os_dlfile(void *m, char *buf, int sz) { // NOTE: this might be linux/glibc-specific (I haven't checked every // implementation). this is fine as we don't use it in any build-time code, @@ -54,6 +55,7 @@ static inline bool os_dlfile(void *m, char *buf, int sz) { if (ssz > sz) { errno = ENAMETOOLONG; return false; } memcpy(buf, lm->l_name, ssz); return true; } +#endif // unix mprot flags are much nicer but cannot be defined in terms of the windows // ones, so we use the windows ones and define them in terms of the unix ones. diff --git a/src/udis86.c b/src/udis86.c index fcb3656..754ff83 100644 --- a/src/udis86.c +++ b/src/udis86.c @@ -3,8 +3,6 @@ #include "3p/udis86/udis86.c" #include "3p/udis86/decode.c" #include "3p/udis86/itab.c" -// this stuff is optional but llvm is smart enough to remove it if it's unused, -// so we keep it in here to be able to use it conveniently for debugging etc. #include "3p/udis86/syn.c" #include "3p/udis86/syn-intel.c" diff --git a/src/x86.c b/src/x86.c new file mode 100644 index 0000000..e33efbb --- /dev/null +++ b/src/x86.c @@ -0,0 +1,87 @@ +/* + * Copyright © 2022 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 + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "intdefs.h" +#include "x86.h" + +static int mrm(uchar b, int addrlen) { + // I won't lie: I don't *entirely* understand this particular logic. I + // largely based it on some public domain code I found on the internet + if (addrlen == 4 || b & 0xC0) { + int sib = addrlen == 4 && b < 0xC0 && (b & 7) == 4; + switch (b & 0xC0) { + // disp8 + case 0x40: return 2 + sib; + // disp16/32 + case 0: if ((b & 7) == 5) case 0x80: return 1 + addrlen + sib; + } + // disp8/32 + if (sib && (b & 7) == 5) return b & 0x40 ? 3 : 6; + } + if (addrlen == 2 && b == 0x26) return 3; + return 1; // NOTE: include the mrm itself in the byte count +} + +int x86_len(const void *insn_) { +#define CASES(name, _) case name: + const uchar *insn = insn_; + int pfxlen = 0, addrlen = 4, operandlen = 4; + +p: switch (*insn) { + case X86_PFX_ADSZ: addrlen = 2; goto P; // bit dumb sorry + case X86_PFX_OPSZ: operandlen = 2; +P: X86_SEG_PREFIXES(CASES) + case X86_PFX_LOCK: case X86_PFX_REPN: case X86_PFX_REP: + // instruction can only be 15 bytes. this could go over, oh well, + // just don't want to loop for 8 million years + if (++pfxlen == 14) return -1; + ++insn; + goto p; + } + + switch (*insn) { + X86_OPS_1BYTE_NO(CASES) return pfxlen + 1; + X86_OPS_1BYTE_I8(CASES) operandlen = 1; + X86_OPS_1BYTE_IW(CASES) return pfxlen + 1 + operandlen; + X86_OPS_1BYTE_I16(CASES) return pfxlen + 3; + X86_OPS_1BYTE_MRM(CASES) return pfxlen + 1 + mrm(insn[1], addrlen); + X86_OPS_1BYTE_MRM_I8(CASES) operandlen = 1; + X86_OPS_1BYTE_MRM_IW(CASES) + return pfxlen + 1 + operandlen + mrm(insn[1], addrlen); + case X86_ENTER: return pfxlen + 4; + case X86_CRAZY8: operandlen = 1; + case X86_CRAZYW: + if ((insn[1] & 0x38) >= 0x10) operandlen = 0; + return pfxlen + 2 + operandlen + mrm(insn[1], addrlen); + case X86_2BYTE: ++insn; goto b2; + } + return -1; + +b2: switch (*insn) { + // we don't support any 3 byte ops for now, implement if ever needed... + case X86_3BYTE1: case X86_3BYTE2: case X86_3DNOW: return -1; + X86_OPS_2BYTE_NO(CASES) return pfxlen + 2; + X86_OPS_2BYTE_IW(CASES) return pfxlen + 2 + operandlen; + X86_OPS_2BYTE_MRM(CASES) return pfxlen + 2 + mrm(insn[1], addrlen); + X86_OPS_2BYTE_MRM_I8(CASES) operandlen = 1; + return pfxlen + 2 + operandlen + mrm(insn[1], addrlen); + } + + return -1; +#undef CASES +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/x86.h b/src/x86.h new file mode 100644 index 0000000..46a34b2 --- /dev/null +++ b/src/x86.h @@ -0,0 +1,451 @@ +/* + * Copyright © 2022 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 + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INC_X86_H +#define INC_X86_H + +/* + * Opcode-based X86 instruction analysis. In other words, *NOT* a disassembler. + * Only cares about the instructions we expect to see in basic 32-bit userspace + * functions; there's no kernel-mode instructions, no MMX/3DNow!/SSE/AVX, no + * REX, EVEX, yadda yadda. + */ + +// XXX: no BOUND (0x62): ambiguous with EVEX prefix - can't be arsed! + +/* Instruction prefixes: segments */ +#define X86_SEG_PREFIXES(X) \ + X(X86_PFX_ES, 0x26) \ + X(X86_PFX_CS, 0x2E) \ + X(X86_PFX_SS, 0x36) \ + X(X86_PFX_DS, 0x3E) \ + X(X86_PFX_FS, 0x64) \ + X(X86_PFX_GS, 0x65) + +/* Instruction prefixes: operations */ +#define X86_OP_PREFIXES(X) \ + X(X86_PFX_OPSZ, 0x66) \ + X(X86_PFX_ADSZ, 0x67) \ + X(X86_PFX_LOCK, 0xF0) \ + X(X86_PFX_REPN, 0xF2) \ + X(X86_PFX_REP, 0xF3) + +/* All instruction prefixes */ +#define X86_PREFIXES(X) X86_SEG_PREFIXES(X) X86_OP_PREFIXES(X) + +/* Single-byte opcodes with no operands */ +#define X86_OPS_1BYTE_NO(X) \ + X(X86_PUSHES, 0x06) \ + X(X86_POPES, 0x07) \ + X(X86_PUSHCS, 0x0E) \ + X(X86_PUSHSS, 0x16) \ + X(X86_POPSS, 0x17) \ + X(X86_PUSHDS, 0x1E) \ + X(X86_POPDS, 0x1F) \ + X(X86_DAA, 0x27) \ + X(X86_DAS, 0x2F) \ + X(X86_AAA, 0x37) \ + X(X86_AAS, 0x3F) \ + X(X86_INCEAX, 0x40) \ + X(X86_INCECX, 0x41) \ + X(X86_INCEDX, 0x42) \ + X(X86_INCEBX, 0x43) \ + X(X86_INCESP, 0x44) \ + X(X86_INCEBP, 0x45) \ + X(X86_INCESI, 0x46) \ + X(X86_INCEDI, 0x47) \ + X(X86_DECEAX, 0x48) \ + X(X86_DECECX, 0x49) \ + X(X86_DECEDX, 0x4A) \ + X(X86_DECEBX, 0x4B) \ + X(X86_DECESP, 0x4C) \ + X(X86_DECEBP, 0x4D) \ + X(X86_DECESI, 0x4E) \ + X(X86_DECEDI, 0x4F) \ + X(X86_PUSHEAX, 0x50) \ + X(X86_PUSHECX, 0x51) \ + X(X86_PUSHEDX, 0x52) \ + X(X86_PUSHEBX, 0x53) \ + X(X86_PUSHESP, 0x54) \ + X(X86_PUSHEBP, 0x55) \ + X(X86_PUSHESI, 0x56) \ + X(X86_PUSHEDI, 0x57) \ + X(X86_POPEAX, 0x58) \ + X(X86_POPECX, 0x59) \ + X(X86_POPEDX, 0x5A) \ + X(X86_POPEBX, 0x5B) \ + X(X86_POPESP, 0x5C) \ + X(X86_POPEBP, 0x5D) \ + X(X86_POPESI, 0x5E) \ + X(X86_POPEDI, 0x5F) \ + X(X86_PUSHA, 0x60) \ + X(X86_POPA, 0x61) \ + X(X86_NOP, 0x90) \ + X(X86_XCHGECXEAX, 0x91) \ + X(X86_XCHGEDXEAX, 0x92) \ + X(X86_XCHGEBXEAX, 0x93) \ + X(X86_XCHGESPEAX, 0x94) \ + X(X86_XCHGEBPEAX, 0x95) \ + X(X86_XCHGESIEAX, 0x96) \ + X(X86_XCHGEDIEAX, 0x97) \ + X(X86_CWDE, 0x98) \ + X(X86_CDQ, 0x99) \ + X(X86_WAIT, 0x9B) \ + X(X86_PUSHF, 0x9C) \ + X(X86_POPF, 0x9D) \ + X(X86_SAHF, 0x9E) \ + X(X86_LAHF, 0x9F) \ + X(X86_MOVS8, 0xA4) \ + X(X86_MOVSW, 0xA5) \ + X(X86_CMPS8, 0xA6) \ + X(X86_CMPSW, 0xA7) \ + X(X86_STOS8, 0xAA) \ + X(X86_STOSD, 0xAB) \ + X(X86_LODS8, 0xAC) \ + X(X86_LODSD, 0xAD) \ + X(X86_SCAS8, 0xAE) \ + X(X86_SCASD, 0xAF) \ + X(X86_RET, 0xC3) \ + X(X86_LEAVE, 0xC9) \ + X(X86_RETF, 0xCB) \ + X(X86_INT3, 0xCC) \ + X(X86_INTO, 0xCE) \ + X(X86_XLAT, 0xD7) \ + X(X86_JMPI8, 0xEB) \ + X(X86_CMC, 0xF5) \ + X(X86_CLC, 0xF8) \ + X(X86_STC, 0xF9) \ + X(X86_CLI, 0xFA) \ + X(X86_STI, 0xFB) \ + X(X86_CLD, 0xFC) \ + X(X86_STD, 0xFD) + +/* Single-byte opcodes with a 1-byte immediate operand */ +#define X86_OPS_1BYTE_I8(X) \ + X(X86_ADDALI, 0x04) \ + X(X86_ORALI, 0x0C) \ + X(X86_ADCALI, 0x14) \ + X(X86_SBBALI, 0x1C) \ + X(X86_ANDALI, 0x24) \ + X(X86_SUBALI, 0x2C) \ + X(X86_XORALI, 0x34) \ + X(X86_CMPALI, 0x3C) \ + X(X86_PUSHI8, 0x6A) \ + X(X86_MOVALII, 0xA0) /* From offset (indirect) */ \ + X(X86_MOVIIAL, 0xA2) /* To offset (indirect) */ \ + X(X86_TESTALI, 0xA8) \ + X(X86_JO, 0x70) \ + X(X86_JNO, 0x71) \ + X(X86_JB, 0x72) /* AKA JC */ \ + X(X86_JNB, 0x73) /* AKA JNC */ \ + X(X86_JZ, 0x74) /* AKA JE */ \ + X(X86_JNZ, 0x75) /* AKA JNZ */ \ + X(X86_JNA, 0x76) /* AKA JBE */ \ + X(X86_JA, 0x77) /* AKA JNBE */ \ + X(X86_JS, 0x78) \ + X(X86_JNS, 0x79) \ + X(X86_JP, 0x7A) \ + X(X86_JNP, 0x7B) \ + X(X86_JL, 0x7C) /* AKA JNGE */ \ + X(X86_JNL, 0x7D) /* AKA JGE */ \ + X(X86_JNG, 0x7E) /* AKA JLE */ \ + X(X86_JG, 0x7F) /* AKA JNLE */ \ + X(X86_MOVALI, 0xB0) \ + X(X86_MOVCLI, 0xB1) \ + X(X86_MOVDLI, 0xB2) \ + X(X86_MOVBLI, 0xB3) \ + X(X86_MOVAHI, 0xB4) \ + X(X86_MOVCHI, 0xB5) \ + X(X86_MOVDHI, 0xB6) \ + X(X86_MOVBHI, 0xB7) \ + X(X86_INT, 0xCD) \ + X(X86_AMX, 0xD4) /* Note: D4 0A is referred to as AAM */ \ + X(X86_ADX, 0xD5) /* Note: D4 0A is referred to as AAD */ \ + X(X86_LOOPNZ, 0xE0) /* AKA LOOPNE */ \ + X(X86_LOOPZ, 0xE1) /* AKA LOOPE */ \ + X(X86_LOOP, 0xE2) \ + X(X86_JCXZ, 0xE3) + +/* Single-byte opcodes with a word-sized immediate operand */ +#define X86_OPS_1BYTE_IW(X) \ + X(X86_ADDEAXI, 0x05) \ + X(X86_OREAXI, 0x0D) \ + X(X86_ADCEAXI, 0x15) \ + X(X86_SBBEAXI, 0x1D) \ + X(X86_ANDEAXI, 0x25) \ + X(X86_SUBEAXI, 0x2D) \ + X(X86_XOREAXI, 0x35) \ + X(X86_CMPEAXI, 0x3D) \ + X(X86_PUSHIW, 0x68) \ + X(X86_MOVEAXII, 0xA1) /* From offset (indirect) */ \ + X(X86_MOVIIEAX, 0xA3) /* To offset (indirect) */ \ + X(X86_TESTEAXI, 0xA9) \ + X(X86_MOVEAXI, 0xB8) \ + X(X86_MOVECXI, 0xB9) \ + X(X86_MOVEDXI, 0xBA) \ + X(X86_MOVEBXI, 0xBB) \ + X(X86_MOVESPI, 0xBC) \ + X(X86_MOVEBPI, 0xBD) \ + X(X86_MOVESII, 0xBE) \ + X(X86_MOVEDII, 0xBF) \ + X(X86_CALL, 0xE8) \ + X(X86_JMPIW, 0xE9) + +/* Single-byte opcodes with 16-bit immediate operands, regardless of prefixes */ +#define X86_OPS_1BYTE_I16(X) \ + X(X86_RETI16, 0xC2) \ + X(X86_RETFI16, 0xCA) + +/* + * Single-byte opcodes with a ModRM. `MR` in a name means the ModRM is the + * destination, `RM` means it's the source. + */ +#define X86_OPS_1BYTE_MRM(X) \ + X(X86_ADDMR8, 0x00) \ + X(X86_ADDMRW, 0x01) \ + X(X86_ADDRM8, 0x02) \ + X(X86_ADDRMW, 0x03) \ + X(X86_ORMR8, 0x08) \ + X(X86_ORMRW, 0x09) \ + X(X86_ORRM8, 0x0A) \ + X(X86_ORRMW, 0x0B) \ + X(X86_ADCMR8, 0x10) \ + X(X86_ADCMRW, 0x11) \ + X(X86_ADCRM8, 0x12) \ + X(X86_ADCRMW, 0x13) \ + X(X86_SBBMR8, 0x18) \ + X(X86_SBBMRW, 0x19) \ + X(X86_SBBRM8, 0x1A) \ + X(X86_SBBRMW, 0x1B) \ + X(X86_ANDMR8, 0x20) \ + X(X86_ANDMRW, 0x21) \ + X(X86_ANDRM8, 0x22) \ + X(X86_ANDRMW, 0x23) \ + X(X86_SUBMR8, 0x28) \ + X(X86_SUBMRW, 0x29) \ + X(X86_SUBRM8, 0x2A) \ + X(X86_SUBRMW, 0x2B) \ + X(X86_XORMR8, 0x30) \ + X(X86_XORMRW, 0x31) \ + X(X86_XORRM8, 0x32) \ + X(X86_XORRMW, 0x33) \ + X(X86_CMPMR8, 0x38) \ + X(X86_CMPMRW, 0x39) \ + X(X86_CMPRM8, 0x3A) \ + X(X86_CMPRMW, 0x3B) \ + X(X86_ARPL, 0x63) \ + X(X86_TESTMR8, 0x84) \ + X(X86_TESTMRW, 0x85) \ + X(X86_XCHGMR8, 0x86) \ + X(X86_XCHGMRW, 0x87) \ + X(X86_MOVMR8, 0x88) \ + X(X86_MOVMRW, 0x89) \ + X(X86_MOVRM8, 0x8A) \ + X(X86_MOVRMW, 0x8B) \ + X(X86_MOVMS, 0x8C) /* Load 4 bytes from segment register */ \ + X(X86_LEA, 0x8D) \ + X(X86_MOVSM, 0x8E) /* Store 4 bytes to segment register */ \ + X(X86_POPM, 0x8F) \ + X(X86_LES, 0xC4) \ + X(X86_LDS, 0xC5) \ + X(X86_SHIFTM18, 0xD0) /* Shift/roll by 1 place */ \ + X(X86_SHIFTM1W, 0xD1) /* Shift/roll by 1 place */ \ + X(X86_SHIFTMCL8, 0xD2) /* Shift/roll by CL places */ \ + X(X86_SHIFTMCLW, 0xD3) /* Shift/roll by CL places */ \ + X(X86_FLTBLK1, 0xD8) /* Various float ops (1/8) */ \ + X(X86_FLTBLK2, 0xD9) /* Various float ops (2/8) */ \ + X(X86_FLTBLK3, 0xDA) /* Various float ops (3/8) */ \ + X(X86_FLTBLK4, 0xDB) /* Various float ops (4/8) */ \ + X(X86_FLTBLK5, 0xDC) /* Various float ops (5/8) */ \ + X(X86_FLTBLK6, 0xDD) /* Various float ops (6/8) */ \ + X(X86_FLTBLK7, 0xDE) /* Various float ops (7/8) */ \ + X(X86_FLTBLK8, 0xDF) /* Various float ops (8/8) */ \ + X(X86_MISCM8, 0xFE) /* Only documented for MRM.reg in {0, 1} */ \ + X(X86_MISCMW, 0xFF) + +/* Single-byte opcodes with a ModRM and a 1-byte immediate operand */ +#define X86_OPS_1BYTE_MRM_I8(X) \ + X(X86_IMULMI8, 0x6B) /* 3-operand multiply */ \ + X(X86_ALUMI8, 0x80) /* ALU op in MRM.reg, from immediate */ \ + X(X86_ALUMI8X, 0x82) /* ALU op in MRM.reg, from immediate, redundant?? */ \ + X(X86_ALUMI8S, 0x83) /* ALU op in MRM.reg, from immediate, sign-extend */ \ + X(X86_SHIFTMI8, 0xC0) /* Shift/roll by imm8 places */ \ + X(X86_SHIFTMIW, 0xC1) /* Shift/roll by imm8 places */ \ + X(X86_MOVMI8, 0xC6) /* Note: RM.reg must be 0 */ + +/* Single-byte opcodes with a ModRM and a word-sized immediate operand */ +#define X86_OPS_1BYTE_MRM_IW(X) \ + X(X86_IMULMIW, 0x69) /* 3-operand multiply */ \ + X(X86_ALUMIW, 0x81) /* ALU op in MRM.reg, from immediate */ \ + X(X86_MOVMIW, 0xC7) /* Note: MRM.reg must be 0 */ + +/* All single-byte x86 instructions */ +#define X86_OPS_1BYTE(X) \ + X86_OPS_1BYTE_NO(X) \ + X86_OPS_1BYTE_I8(X) \ + X86_OPS_1BYTE_IW(X) \ + X86_OPS_1BYTE_I16(X) \ + X86_OPS_1BYTE_MRM(X) \ + X86_OPS_1BYTE_MRM_I8(X) \ + X86_OPS_1BYTE_MRM_IW(X) \ + X(X86_ENTER, 0xC8) /* Dumb special case insn: imm16 followed by imm8 */ \ + X(X86_CRAZY8, 0xF6) /* CRAZY reg-encoded block, has imm8 IFF reg < 2 */ \ + X(X86_CRAZYW, 0xF7) /* CRAZY reg-encoded block, has imm32/16 IFF reg < 2 */ + +/* Second bytes of opcodes with no operands */ +#define X86_OPS_2BYTE_NO(X) \ + X(X86_2B_RDTSC, 0x31) \ + X(X86_2B_RDPMD, 0x33) \ + X(X86_2B_SYSENTER, 0x34) \ + X(X86_2B_PUSHFS, 0xA0) \ + X(X86_2B_POPFS, 0xA1) \ + X(X86_2B_CPUID, 0xA2) \ + X(X86_2B_PUSHGS, 0xA8) \ + X(X86_2B_POPGS, 0xA9) \ + X(X86_2B_RSM, 0xAA) \ + X(X86_2B_BSWAPEAX, 0xC8) \ + X(X86_2B_BSWAPECX, 0xC9) \ + X(X86_2B_BSWAPEDX, 0xCA) \ + X(X86_2B_BSWAPEBX, 0xCB) \ + X(X86_2B_BSWAPESP, 0xCC) \ + X(X86_2B_BSWAPEBP, 0xCD) \ + X(X86_2B_BSWAPESI, 0xCE) \ + X(X86_2B_BSWAPEDI, 0xCF) + +/* Second bytes of opcodes with a word-sized immediate operand */ +#define X86_OPS_2BYTE_IW(X) \ + X(X86_2B_JOII, 0x80) /* From offset (indirect) */ \ + X(X86_2B_JNOII, 0x81) /* From offset (indirect) */ \ + X(X86_2B_JBII, 0x82) /* AKA JC; from offset (indirect) */ \ + X(X86_2B_JNBII, 0x83) /* AKA JNC; from offset (indirect) */ \ + X(X86_2B_JZII, 0x84) /* AKA JE; from offset (indirect) */ \ + X(X86_2B_JNZII, 0x85) /* AKA JNZ; from offset (indirect) */ \ + X(X86_2B_JNAII, 0x86) /* AKA JBE; from offset (indirect) */ \ + X(X86_2B_JAII, 0x87) /* AKA JNBE; from offset (indirect) */ \ + X(X86_2B_JSII, 0x88) /* From offset (indirect) */ \ + X(X86_2B_JNSII, 0x89) /* From offset (indirect) */ \ + X(X86_2B_JPII, 0x8A) /* From offset (indirect) */ \ + X(X86_2B_JNPII, 0x8B) /* From offset (indirect) */ \ + X(X86_2B_JLII, 0x8C) /* AKA JNGE; from offset (indirect) */ \ + X(X86_2B_JNLII, 0x8D) /* AKA JGE; from offset (indirect) */ \ + X(X86_2B_JNGII, 0x8E) /* AKA JLE; from offset (indirect) */ \ + X(X86_2B_JGII, 0x8F) /* AKA JNLE; from offset (indirect) */ + +/* Second bytes of opcodes with a ModRM */ +#define X86_OPS_2BYTE_MRM(X) \ + X(X86_2B_NOP, 0x0D) /* Variable length NOP (3-9 with prefix) */ \ + X(X86_2B_HINTS1, 0x18) /* Prefetch and hint-nop block 1/8 */ \ + X(X86_2B_HINTS2, 0x19) /* Prefetch and hint-nop block 2/8 */ \ + X(X86_2B_HINTS3, 0x1A) /* Prefetch and hint-nop block 3/8 */ \ + X(X86_2B_HINTS4, 0x1B) /* Prefetch and hint-nop block 4/8 */ \ + X(X86_2B_HINTS5, 0x1C) /* Prefetch and hint-nop block 5/8 */ \ + X(X86_2B_HINTS6, 0x1D) /* Prefetch and hint-nop block 6/8 */ \ + X(X86_2B_HINTS7, 0x1E) /* Prefetch and hint-nop block 7/8 */ \ + X(X86_2B_HINTS8, 0x1F) /* Prefetch and hint-nop block 8/8 */ \ + X(X86_2B_CMOVO, 0x40) \ + X(X86_2B_CMOVNO, 0x41) \ + X(X86_2B_CMOVB, 0x42) /* AKA CMOVC */ \ + X(X86_2B_CMOVNB, 0x43) /* AKA CMOVNC */ \ + X(X86_2B_CMOVZ, 0x44) /* AKA CMOVE */ \ + X(X86_2B_CMOVNZ, 0x45) /* AKA CMOVNE */ \ + X(X86_2B_CMOVNA, 0x46) /* AKA CMOVBE */ \ + X(X86_2B_CMOVA, 0x47) /* AKA CMOVNBE */ \ + X(X86_2B_CMOVS, 0x48) \ + X(X86_2B_CMOVNS, 0x49) \ + X(X86_2B_CMOVP, 0x4A) \ + X(X86_2B_CMOVNP, 0x4B) \ + X(X86_2B_CMOVL, 0x4C) /* AKA CMOVNGE */ \ + X(X86_2B_CMOVNL, 0x4D) /* AKA CMOVGE */ \ + X(X86_2B_CMOVNG, 0x4E) /* AKA CMOVLE */ \ + X(X86_2B_CMOVG, 0x4F) /* AKA CMOVNLE */ \ + X(X86_2B_SETO, 0x90) \ + X(X86_2B_SETNO, 0x91) \ + X(X86_2B_SETB, 0x92) /* AKA SETC */ \ + X(X86_2B_SETNB, 0x93) /* AKA SETNC */ \ + X(X86_2B_SETZ, 0x94) /* AKA SETE */ \ + X(X86_2B_SETNZ, 0x95) /* AKA SETNZ */ \ + X(X86_2B_SETNA, 0x96) /* AKA SETBE */ \ + X(X86_2B_SETA, 0x97) /* AKA SETNBE */ \ + X(X86_2B_SETS, 0x98) \ + X(X86_2B_SETNS, 0x99) \ + X(X86_2B_SETP, 0x9A) \ + X(X86_2B_SETNP, 0x9B) \ + X(X86_2B_SETL, 0x9C) /* AKA SETNGE */ \ + X(X86_2B_SETNL, 0x9D) /* AKA SETGE */ \ + X(X86_2B_SETNG, 0x9E) /* AKA SETLE */ \ + X(X86_2B_SETG, 0x9F) /* AKA SETNLE */ \ + X(X86_2B_BTMR, 0xA3) \ + X(X86_2B_SHLDMRCL, 0xA5) \ + X(X86_2B_BTS, 0xAB) \ + X(X86_2B_SHRDMRCL, 0xAD) \ + X(X86_2B_MISC, 0xAE) /* Float env stuff, memory fences */ \ + X(X86_2B_IMUL, 0xAF) \ + X(X86_2B_CMPXCHG8, 0xB0) \ + X(X86_2B_CMPXCHGW, 0xB1) \ + X(X86_2B_MOVZX8, 0xB6) \ + X(X86_2B_MOVZXW, 0xB7) \ + X(X86_2B_POPCNT, 0xB8) \ + X(X86_2B_BTCRM, 0xBB) \ + X(X86_2B_BSF, 0xBC) \ + X(X86_2B_BSR, 0xBD) \ + X(X86_2B_MOVSX8, 0xBE) \ + X(X86_2B_MOVSXW, 0xBF) \ + X(X86_2B_XADDRM8, 0xC0) \ + X(X86_2B_XADDRMW, 0xC1) \ + /* NOTE: this one is actually a block with some VMX stuff too; it's only + CMPXCHG64 (CMPXCHG8B if you prefer) if MRM.reg = 1, but naming it this + way seemed more useful since it's what you'll see in normal userspace + programs, which is what we're interested in. */ \ + X(X86_2B_CMPXCHG64, 0xC7) + +/* Second bytes of opcodes with a ModRM and a 1-byte immediate operand */ +#define X86_OPS_2BYTE_MRM_I8(X) \ + X(X86_2B_SHLDMRI, 0xA4) \ + X(X86_2B_SHRDMRI, 0xAC) \ + X(X86_2B_BTXMI, 0xBA) /* BT/BTS/BTR/BTC depending on MRM.reg (4-7) */ + +#define X86_OPS_2BYTE(X) \ + X86_OPS_2BYTE_NO(X) \ + X86_OPS_2BYTE_IW(X) \ + X86_OPS_2BYTE_MRM(X) \ + X86_OPS_2BYTE_MRM_I8(X) + +#define _X86_ENUM(name, value) name = value, +enum { + X86_PREFIXES(_X86_ENUM) + X86_OPS_1BYTE(_X86_ENUM) + X86_2BYTE = 0x0F, /* First byte of a 2- or 3-byte opcode */ + X86_OPS_2BYTE(_X86_ENUM) + X86_3BYTE1 = 0x38, /* One of the two second bytes of a three-byte opcode */ + X86_3BYTE2 = 0x3A, /* The other second byte of a three-byte opcode */ + X86_3DNOW = 0x0F /* The second byte of a three-byte 3DNow! opcode */ +}; +#undef _X86_ENUM + +/* + * Returns the length of an instruction, or -1 if it's a "known unknown" or + * invalid instruction. Doesn't handle unknown unknowns: may explode or hang on + * arbitrary untrusted data. Also doesn't handle, among other things, 3DNow!, + * SSE, MMX, AVX, and such. Aims to be small and fast rather than comprehensive. + */ +int x86_len(const void *insn); + +/* Constructs a ModRM byte, assuming the parameters are all in range. */ +#define X86_MODRM(mod, reg, rm) (unsigned char)((mod) << 6 | (reg) << 3 | rm) + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/test/bitbuf.test.c b/test/bitbuf.test.c index 58d1c4d..324a7f6 100644 --- a/test/bitbuf.test.c +++ b/test/bitbuf.test.c @@ -14,7 +14,7 @@ static union { } bb_buf; static struct bitbuf bb = {bb_buf.buf, 512, 512 * 8, 0, false, false, "test"}; -TEST("The possible UB in bitbuf_appendbuf shouldn't trigger horrible bugs", 0) { +TEST("The possible UB in bitbuf_appendbuf shouldn't trigger horrible bugs") { char unalign[3] = {'X', 'X', 'X'}; char _buf[32 + sizeof(bitbuf_cell)]; char *buf = _buf; diff --git a/test/hook.test.c b/test/hook.test.c index 50e07a8..b2e841c 100644 --- a/test/hook.test.c +++ b/test/hook.test.c @@ -4,7 +4,7 @@ #ifdef _WIN32 -#include "../src/udis86.c" +#include "../src/x86.c" #include "../src/hook.c" #include @@ -32,20 +32,20 @@ static int other_hook(int a, int b) { return orig_other_function(a, b) + 5; } -TEST("Inline hooks should be able to wrap the original function", 0) { +TEST("Inline hooks should be able to wrap the original function") { orig_some_function = hook_inline(&some_function, &some_hook); if (!orig_some_function) return false; return some_function(5, 5) == 15; } -TEST("Inline hooks should be removable again", 0) { +TEST("Inline hooks should be removable again") { orig_some_function = hook_inline(&some_function, &some_hook); if (!orig_some_function) return false; unhook_inline(orig_some_function); return some_function(5, 5) == 10; } -TEST("Multiple functions should be able to be inline hooked at once", 0) { +TEST("Multiple functions should be able to be inline hooked at once") { orig_some_function = hook_inline(&some_function, &some_hook); if (!orig_some_function) return false; diff --git a/test/kv.test.c b/test/kv.test.c index 12f2801..7f82b8a 100644 --- a/test/kv.test.c +++ b/test/kv.test.c @@ -33,7 +33,7 @@ Val2// comment\n\ "; static const int sz = sizeof(data) - 1; -TEST("parsing should work with any buffer size", 0) { +TEST("parsing should work with any buffer size") { for (int chunksz = 3; chunksz <= sz; ++chunksz) { struct kv_parser kvp = {0}; // sending data in chunks to test reentrancy diff --git a/tools/mkbindist.bat b/tools/mkbindist.bat index 687761d..75d4ea1 100644 --- a/tools/mkbindist.bat +++ b/tools/mkbindist.bat @@ -20,8 +20,11 @@ set name=sst-v%major%.%minor%-BETA-win32 md TEMP-%name% || exit /B copy sst.dll TEMP-%name%\sst.dll || exit /B copy dist\LICENCE.windows TEMP-%name%\LICENCE || exit /B +:: arbitrary dates to make zip deterministic! consider changing on next actual release date! +powershell (Get-Item TEMP-%name%\sst.dll).LastWriteTime = new-object DateTime 2022, 1, 1, 0, 0, 0 +powershell (Get-Item TEMP-%name%\LICENCE).LastWriteTime = new-object DateTime 2022, 1, 1, 0, 0, 0 pushd TEMP-%name% -"%SEVENZIP%" a %name%.zip sst.dll LICENCE || exit /B +"%SEVENZIP%" a -mtc=off %name%.zip sst.dll LICENCE || exit /B move %name%.zip ..\release\%name%.zip popd rd /s /q TEMP-%name%\ || exit /B -- cgit v1.2.3