1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
/*
* Copyright © 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
* 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 "intdefs.h"
#include "errmsg.h"
#include "feature.h"
#include "langext.h"
bool hook_init();
/*
* Replaces a vtable entry with a target function and returns the original
* function.
*/
static inline void *hook_vtable(void **vtable, usize off, void *target) {
void *orig = vtable[off];
vtable[off] = target;
return orig;
}
/*
* Puts an original function back after hooking.
*/
static inline void unhook_vtable(void **vtable, usize off, void *orig) {
vtable[off] = orig;
}
/*
* Finds the correct function prologue location to install an inline hook, and
* tries to initialise a trampoline with sufficient instructions and a jump back
* to enable calling the original function.
*
* 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.
* It automatically performs conventional error logging for both this step and
* the hook_inline_mprot() call below, and returns error codes that are
* convenient for use in a feature INIT function.
*
* When this function succeeds, the returned struct will have the prologue
* member set to the prologue or starting point of the hooked function (which is
* not always the same as the original function pointer). The trampoline
* parameter, being a pointer-to-pointer, is an output parameter to which a
* trampoline pointer will be written. The trampoline is a small run of
* instructions from the original function, followed by a jump back to it,
* allowing the original to be seamlessly called from a hook.
*
* In practically rare cases, this function will fail due to unsupported
* instructions in the function prologue. In such instances, the returned struct
* will have a null prologue, and the second member err, will point to a
* null-terminated string for error logging. In this case, the trampoline
* pointer will remain untouched.
*/
struct hook_inline_prep_ret {
void *prologue;
const char *err;
} hook_inline_prep(void *func, void **trampoline);
/*
* This is a small helper function to make the memory page containing a
* function's prologue writable, allowing an inline hook to be inserted with
* hook_inline_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.
* It automatically performs conventional error logging for both this step and
* the prior hook_inline_prep() call documented above, and returns error codes
* that are convenient for use in a feature INIT function.
*
* After using hook_inline_prep() to obtain the prologue and an appropriate
* trampoline, call this to unlock the prologue, and then use
* hook_inline_commit() to finalise the hook. In the event that multiple
* functions need to be hooked at once, the commit calls can be batched up at
* the end, removing the need for rollbacks since commitment is guaranteed to
* succeed after all setup is complete.
*
* This function returns true on success, or false if a failure occurs at the
* level of the OS memory protection API. os_lasterror() or errmsg_*sys() can be
* used to report such an error.
*/
bool hook_inline_mprot(void *func);
/*
* Finalises an inline hook set up using the hook_inline_prep() and
* hook_inline_mprot() functions above (or the hook_inline_featsetup() helper
* function below). prologue must be the prologue obtained via the
* aforementioned functons and target must be the function that will be jumped
* to in place of the original. It is very important that these functions are
* ABI-compatible lest obvious bad things happen.
*
* The resulting hook can be removed later by calling unhook_inline().
*/
void hook_inline_commit(void *restrict prologue, void *restrict target);
/*
* This is a helper specifically for use in feature INIT code. It doesn't make
* much sense to call it elsewhere.
*
* Combines the functionality of the hook_inline_prep() and hook_inline_mprot()
* functions above, logs to the console on error automatically in a conventional
* format, and returns an error status that can be propagated straight from a
* feature INIT function.
*
* func must point to the original function to be hooked, orig must point to
* your trampoline pointer (which can in turn be used to call the original
* function indirectly from within your hook or elsewhere), and fname should be
* the name of the function for error logging purposes.
*
* If the err member of the returned struct is nonzero, simply return it as-is.
* Otherwise, the prologue member will contain the prologue pointer to pass to
* hook_inline_commit() to finalise the hook.
*/
static inline struct hook_inline_featsetup_ret {
void *prologue;
int err;
} hook_inline_featsetup(void *func, void **orig, const char *fname) {
void *trampoline;
struct hook_inline_prep_ret prep = hook_inline_prep(func, &trampoline);
if_cold (prep.err) {
errmsg_warnx("couldn't hook %s function: %s", fname, prep.err);
return (struct hook_inline_featsetup_ret){0, FEAT_INCOMPAT};
}
if_cold (!hook_inline_mprot(prep.prologue)) {
errmsg_errorsys("couldn't hook %s function: %s", fname,
"couldn't make prologue writable");
return (struct hook_inline_featsetup_ret){0, FEAT_FAIL};
}
*orig = trampoline;
return (struct hook_inline_featsetup_ret){prep.prologue, 0};
}
/*
* Reverts a function to its original unhooked state. Takes the pointer to the
* callable "original" function, i.e. the trampoline, NOT the initial function
* pointer from before hooking.
*/
void unhook_inline(void *orig);
#endif
// vi: sw=4 ts=4 noet tw=80 cc=80
|