/* * Copyright © 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_HOOK_H #define INC_HOOK_H #include "asm.h" #include "intdefs.h" #include "errmsg.h" #include "feature.h" #include "langext.h" #define _HOOK_STR2(x) #x #define _HOOK_STR(x) _HOOK_STR2(x) #define _HOOK_CAT2(a, b, c, d) a##b##c##d #define _HOOK_CAT(a, b, c, d) _HOOK_CAT2(a, b, c, d) #ifdef MODULE_NAME #define _HOOK_MODNAME MODULE_NAME #else #define _HOOK_MODNAME G // kinda arbitrary thing for "global" #endif #if defined(__GNUC__) || defined(__clang__) #define _HOOK_UNUSED __attribute((unused)) #else #define _HOOK_UNUSED #endif // internal helpers, do not call directly uchar *_hook_getpos(uchar *); struct _hook_prep_ret { void *hookpos; int inslen; const char *err; } _hook_prep(uchar *func, uchar *trampoline); void _hook_inline_commit(uchar *restrict hookpos, const uchar *restrict target); void _unhook_inline(uchar *trampoline, int len); #define _DEF_TRAMPOLINE_ASM(symb) \ __asm ( \ ".pushsection " ASM_RWX_SECTION_STR ", \"" ASM_RWX_SECTION_FLAGS "\"\n" \ ".globl " symb "\n" \ symb ":\n" \ ".space 24\n" \ ".popsection\n" \ ); // internal macro detail, don't use #define _DEF_TRAMPOLINE(ftype, name, symb) \ _DEF_TRAMPOLINE_ASM(symb) \ typeof(*(typeof(ftype))0) name __asm(symb); /* this declares the function! */ /* * Creates a callable trampoline function backed by a chunk of uninitialised rwx * memory. Calling this on its own will crash, but the inline hooking system * can use it to create the wrapper/trampoline function used to call an original * function from a hook. * * Note that it's usually unnecessary to create these manually. Most of the * time this is handled by DEF_TRAMPOLINE(). */ #define DEF_TRAMPOLINE(ftype, name) \ _DEF_TRAMPOLINE(ftype, name, ASM_MANGLE_STR( \ _HOOK_STR(_HOOK_CAT(_hook_t_, _HOOK_MODNAME, _, name)))) /* * Equivalent to DEF_INLINE_HOOK(), but with a manually-specified trampoline * function (see DEF_TRAMPOLINE() for how to create one of those). * * It is typically unnecessary to use this and DEF_INLINE_HOOK() should be used * instead. */ #define DEF_INLINE_HOOK_WITHTRAMPOLINE(ftype, name, trampoline) \ static schar _hook_inslen_##name; \ static inline struct hook_prep_ret_##name { \ void *hookpos; \ const char *err; \ } hook_prep_##name(typeof(ftype) func) { \ struct _hook_prep_ret r = _hook_prep((uchar *)func, \ (uchar *)&trampoline); \ _hook_inslen_##name = r.inslen; \ return (struct hook_prep_ret_##name){r.hookpos, r.err}; \ } \ static inline void hook_commit_##name(void *hookpos, \ typeof(ftype) target) { \ _hook_inline_commit((uchar *)hookpos, (const uchar *)target); \ } \ static inline _HOOK_UNUSED void unhook_##name() { \ _unhook_inline((void *)&trampoline, _hook_inslen_##name); \ } \ static inline _HOOK_UNUSED struct hook_featsetup_ret_##name { \ void *hookpos; \ int err; \ } hook_featsetup_##name(typeof(ftype) f) { \ struct hook_prep_ret_##name ret = hook_prep_##name(f); \ if_cold (ret.err) { \ errmsg_warnx("couldn't hook %s function: %s", #name, ret.err); \ return (struct hook_featsetup_ret_##name){0, FEAT_INCOMPAT}; \ } \ if_cold (!hook_inline_mprot(ret.hookpos)) { \ errmsg_errorsys("couldn't hook %s function: %s", #name, \ "couldn't make hook point writable"); \ return (struct hook_featsetup_ret_##name){0, FEAT_FAIL}; \ } \ return (struct hook_featsetup_ret_##name){ret.hookpos, 0}; \ } /* * Creates a set of inline hooking functions for hooking a particular named * function. ftype specifies a function pointer type, generally defined first as * _func as a matter of convention. * * Defines the following functions: * * static orig_(); * * This is the trampoline function, which is dynamically generated by * hook_prep_() and allows wrapping the original function while it is * otherwise redirected to the hook target. * * Calling this before the inline hook has been committed will likely crash * or otherwise result in undefined behaviour. * * static inline struct hook_featsetup_ret_ { * void *hookpos; * int err; * } hook_featsetup_(ftype f); * * This is a higher-level helper function intended for use in plugin feature * initialisation (see INIT in feature.h). It combines the efforts of * hook_prep_() and hook_inline_mprot() (see below), and also performs * appropriate error logging. * * The err member of the returned struct is 0 on success, or a suitable * feature return code (see FEAT_* in feature.h) on failure, so the status can * just be returned directly from INIT if nonzero. * * On success, the hookpos member of the struct is ready for passing to * hook_commit_() to finish setting up the hook. * * Generally, in feature code, there is no reason to call hook_prep_() * directly, but it is documented below anyway. * * static struct hook_prep_ret_ { * void *hookpos; * const char *err; * } hook_prep_(ftype func); * * This finds the correct jump point to pass to hook_inline_mprot() and then * hook_commit_(), and sets up the trampoline (see orig_() above). * * The struct field hookpos, if not null, should be passed to * hook_commit_() to finish installing the hook. * * If hookpos is null, something went wrong, and the null-terminated string * err will provide a log message. * * static void hook_commit_(void *hookpos, ftype target); * * This finalises an inline hook to jump to the function pointed to by target. * hookpos should have been successfully passed to hook_inline_mprot() first, * otherwise the code will still be read only, likely causing a crash. * * static void unhook_() * * This undoes an inline hook, allowing the original code to function as it * did before without further interception. */ #define DEF_INLINE_HOOK(ftype, name) \ DEF_TRAMPOLINE(ftype, orig_##name) \ DEF_INLINE_HOOK_WITHTRAMPOLINE(ftype, name, orig_##name) /* * Very similar to DEF_INLINE_HOOK, except does not allow calling into the * original function while the hook is installed. The hook_prep_() and * hook_featsetup_() functions have simpler return values as a result: * hook_prep_() cannot fail, stores the hook point internally and just * returns it as a convenience for passing to hook_mprot(); and * hook_featsetup_() only returns an error code since the hook point is * no longer required for hook_commit_(). hook_commit_ also lacks * the hookpos parameter as a result. */ #define DEF_INLINE_HOOK_NOTRAMPOLINE(ftype, name) \ static uchar *_hook_origpos_##name; \ static uchar _hook_origbytes_##name[5]; \ static inline void *hook_prep_##name(typeof(ftype) func) { \ _hook_origpos_##name = _hook_getpos((uchar *)func); \ *(int *)_hook_origbytes_##name = *(int *)_hook_origpos_##name; \ _hook_origbytes_##name[4] = ((uchar *)_hook_origpos_##name)[4]; \ return _hook_origpos_##name; \ } \ static inline void hook_commit_##name(typeof(ftype) target) { \ _hook_inline_commit(_hook_origpos_##name, (const uchar *)target); \ } \ static inline _HOOK_UNUSED void unhook_##name() { \ *(int *)_hook_origpos_##name = *(int *)_hook_origbytes_##name; \ _hook_origpos_##name[4] = _hook_origbytes_##name[4]; \ } \ static inline _HOOK_UNUSED int hook_featsetup_##name(typeof(ftype) f) { \ void *hookpos = hook_prep_##name(f); \ if_cold (!hook_inline_mprot(hookpos)) { \ errmsg_errorsys("couldn't hook %s function: %s", #name, \ "couldn't make hook point writable"); \ return FEAT_FAIL; \ } \ return 0; \ } /* * Equivalent to DEF_VTABLE_HOOK(), but with a manually specified original * function pointer which has to have been defined already. * * In most cases, DEF_VTABLE_HOOK() should be used instead. This exists mainly * for space-saving union shenanigans which are done in a very small handful of * places (and with highly dubious necessity). */ #define DEF_VTABLE_HOOK_WITHORIG(ftype, name, origp) \ static inline void hook_##name(void **vtable, ssize idx, \ typeof(ftype) target) { \ (origp) = (typeof(ftype))vtable[idx]; \ vtable[idx] = (void *)(target); \ } \ static inline void _HOOK_UNUSED unhook_##name(void **vtable, ssize idx) { \ vtable[idx] = (void *)(origp); \ } /* * Creates a set of virtual table hooking functions for hooking a particular * named function. ftype specifies a function pointer type, generally defined * first as _func as a matter of convention. * * Defines the following functions: * * static orig_(); * * As an implementation detail, this is really a function pointer, but exists * to allow calling the original function. It is also used to unhook the * function again later, so generally should not be modified/used to point to * something else. * * static void hook_(void **vtable, ssize idx, ftype target); * * Installs a virtual table hook by swapping the function at the given index. * The virtual table must have first been made writable with os_mprot(). * * static void unhook_(void **vtable, ssize idx); * * Removes a virtual table hook by swapping back the original function * pointer. The index must be the same one that was used for hook_(). */ #define DEF_VTABLE_HOOK(ftype, name) \ static typeof(ftype) orig_##name; \ DEF_VTABLE_HOOK_WITHORIG(ftype, name, orig_##name) /* * This is a small helper function to make a function's hook point - found by * hook_prep_*() - writable, allowing an inline hook to be inserted with * hook_commit_*(). * * This is a low-level API and in most cases, if doing hooking from inside a * plugin feature, the hook_inline_featsetup() function should be used instead. */ bool hook_inline_mprot(void *hookpos); #endif // vi: sw=4 ts=4 noet tw=80 cc=80