191 lines
4.7 KiB
C
191 lines
4.7 KiB
C
|
#include <linux/ftrace.h>
|
||
|
#include <linux/linkage.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/kprobes.h>
|
||
|
|
||
|
static unsigned long lookup_name(const char *name)
|
||
|
{
|
||
|
struct kprobe kp = {.symbol_name = name};
|
||
|
unsigned long retval;
|
||
|
|
||
|
if (register_kprobe(&kp) < 0)
|
||
|
return 0;
|
||
|
retval = (unsigned long)kp.addr;
|
||
|
unregister_kprobe(&kp);
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* There are two ways of preventing vicious recursive loops when hooking:
|
||
|
* - detect recusion using function return address (USE_FENTRY_OFFSET = 0)
|
||
|
* - avoid recusion by jumping over the ftrace call (USE_FENTRY_OFFSET = 1)
|
||
|
*/
|
||
|
#define USE_FENTRY_OFFSET 1
|
||
|
|
||
|
/**
|
||
|
* struct ftrace_hook - describes a single hook to install
|
||
|
*
|
||
|
* @name: name of the function to hook
|
||
|
*
|
||
|
* @function: pointer to the function to execute instead
|
||
|
*
|
||
|
* @original: pointer to the location where to save a pointer
|
||
|
* to the original function
|
||
|
*
|
||
|
* @address: kernel address of the function entry
|
||
|
*
|
||
|
* @ops: ftrace_ops state for this function hook
|
||
|
*
|
||
|
* The user should fill in only &name, &hook, &orig fields.
|
||
|
* Other fields are considered implementation details.
|
||
|
*/
|
||
|
struct ftrace_hook {
|
||
|
const char *name;
|
||
|
void *function;
|
||
|
void *original;
|
||
|
|
||
|
unsigned long address;
|
||
|
struct ftrace_ops ops;
|
||
|
};
|
||
|
|
||
|
static int fh_resolve_hook_address(struct ftrace_hook *hook)
|
||
|
{
|
||
|
hook->address = lookup_name(hook->name);
|
||
|
|
||
|
if (!hook->address) {
|
||
|
pr_debug("unresolved symbol: %s\n", hook->name);
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
// #if USE_FENTRY_OFFSET
|
||
|
*((unsigned long *)hook->original) = hook->address + MCOUNT_INSN_SIZE;
|
||
|
// #else
|
||
|
// *((unsigned long *)hook->original) = hook->address;
|
||
|
// #endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops,
|
||
|
struct ftrace_regs *fregs)
|
||
|
{
|
||
|
struct pt_regs *regs = ftrace_get_regs(fregs);
|
||
|
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
|
||
|
|
||
|
// #if USE_FENTRY_OFFSET
|
||
|
regs->ip = (unsigned long)hook->function;
|
||
|
// #else
|
||
|
// if (!within_module(parent_ip, THIS_MODULE))
|
||
|
// regs->ip = (unsigned long)hook->function;
|
||
|
// #endif
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fh_install_hooks() - register and enable a single hook
|
||
|
* @hook: a hook to install
|
||
|
*
|
||
|
* Returns: zero on success, negative error code otherwise.
|
||
|
*/
|
||
|
int fh_install_hook(struct ftrace_hook *hook)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = fh_resolve_hook_address(hook);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
/*
|
||
|
* We're going to modify %rip register so we'll need IPMODIFY flag
|
||
|
* and SAVE_REGS as its prerequisite. ftrace's anti-recursion guard
|
||
|
* is useless if we change %rip so disable it with RECURSION.
|
||
|
* We'll perform our own checks for trace function reentry.
|
||
|
*/
|
||
|
hook->ops.func = fh_ftrace_thunk;
|
||
|
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION | FTRACE_OPS_FL_IPMODIFY;
|
||
|
|
||
|
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
|
||
|
if (err) {
|
||
|
pr_debug("ftrace_set_filter_ip() failed: %d\n", err);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
err = register_ftrace_function(&hook->ops);
|
||
|
if (err) {
|
||
|
pr_debug("register_ftrace_function() failed: %d\n", err);
|
||
|
ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fh_remove_hooks() - disable and unregister a single hook
|
||
|
* @hook: a hook to remove
|
||
|
*/
|
||
|
void fh_remove_hook(struct ftrace_hook *hook)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = unregister_ftrace_function(&hook->ops);
|
||
|
if (err) {
|
||
|
pr_debug("unregister_ftrace_function() failed: %d\n", err);
|
||
|
}
|
||
|
|
||
|
err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
|
||
|
if (err) {
|
||
|
pr_debug("ftrace_set_filter_ip() failed: %d\n", err);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fh_install_hooks() - register and enable multiple hooks
|
||
|
* @hooks: array of hooks to install
|
||
|
* @count: number of hooks to install
|
||
|
*
|
||
|
* If some hooks fail to install then all hooks will be removed.
|
||
|
*
|
||
|
* Returns: zero on success, negative error code otherwise.
|
||
|
*/
|
||
|
int fh_install_hooks(struct ftrace_hook *hooks, size_t count)
|
||
|
{
|
||
|
int err;
|
||
|
size_t i;
|
||
|
|
||
|
for (i = 0; i < count; i++) {
|
||
|
err = fh_install_hook(&hooks[i]);
|
||
|
if (err)
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
error:
|
||
|
while (i != 0) {
|
||
|
fh_remove_hook(&hooks[--i]);
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fh_remove_hooks() - disable and unregister multiple hooks
|
||
|
* @hooks: array of hooks to remove
|
||
|
* @count: number of hooks to remove
|
||
|
*/
|
||
|
void fh_remove_hooks(struct ftrace_hook *hooks, size_t count)
|
||
|
{
|
||
|
size_t i;
|
||
|
|
||
|
for (i = 0; i < count; i++)
|
||
|
fh_remove_hook(&hooks[i]);
|
||
|
}
|
||
|
|
||
|
#define HOOK(_name, _function, _original) \
|
||
|
{ \
|
||
|
.name = ("__x64_" _name), .function = (_function), .original = (_original), \
|
||
|
}
|
||
|
|