mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-26 09:10:13 +00:00
fprobe: Rewrite fprobe on function-graph tracer
Rewrite fprobe implementation on function-graph tracer. Major API changes are: - 'nr_maxactive' field is deprecated. - This depends on CONFIG_DYNAMIC_FTRACE_WITH_ARGS or !CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS, and CONFIG_HAVE_FUNCTION_GRAPH_FREGS. So currently works only on x86_64. - Currently the entry size is limited in 15 * sizeof(long). - If there is too many fprobe exit handler set on the same function, it will fail to probe. Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Heiko Carstens <hca@linux.ibm.com> # s390 Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com> Cc: Florent Revest <revest@chromium.org> Cc: Martin KaFai Lau <martin.lau@linux.dev> Cc: bpf <bpf@vger.kernel.org> Cc: Alexei Starovoitov <ast@kernel.org> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Alan Maguire <alan.maguire@oracle.com> Cc: Heiko Carstens <hca@linux.ibm.com> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Will Deacon <will@kernel.org> Cc: Huacai Chen <chenhuacai@kernel.org> Cc: WANG Xuerui <kernel@xen0n.name> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Nicholas Piggin <npiggin@gmail.com> Cc: Christophe Leroy <christophe.leroy@csgroup.eu> Cc: Naveen N Rao <naveen@kernel.org> Cc: Madhavan Srinivasan <maddy@linux.ibm.com> Cc: Paul Walmsley <paul.walmsley@sifive.com> Cc: Palmer Dabbelt <palmer@dabbelt.com> Cc: Albert Ou <aou@eecs.berkeley.edu> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Alexander Gordeev <agordeev@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@linux.ibm.com> Cc: Sven Schnelle <svens@linux.ibm.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Ingo Molnar <mingo@redhat.com> Cc: Borislav Petkov <bp@alien8.de> Cc: Dave Hansen <dave.hansen@linux.intel.com> Cc: x86@kernel.org Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Andrew Morton <akpm@linux-foundation.org> Link: https://lore.kernel.org/173519003970.391279.14406792285453830996.stgit@devnote2 Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
This commit is contained in:
parent
7495e179b4
commit
4346ba1604
@ -135,6 +135,12 @@ ftrace_regs_get_frame_pointer(const struct ftrace_regs *fregs)
|
|||||||
return arch_ftrace_regs(fregs)->fp;
|
return arch_ftrace_regs(fregs)->fp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static __always_inline unsigned long
|
||||||
|
ftrace_regs_get_return_address(const struct ftrace_regs *fregs)
|
||||||
|
{
|
||||||
|
return arch_ftrace_regs(fregs)->lr;
|
||||||
|
}
|
||||||
|
|
||||||
static __always_inline struct pt_regs *
|
static __always_inline struct pt_regs *
|
||||||
ftrace_partial_regs(const struct ftrace_regs *fregs, struct pt_regs *regs)
|
ftrace_partial_regs(const struct ftrace_regs *fregs, struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
|
@ -61,6 +61,12 @@ ftrace_regs_set_instruction_pointer(struct ftrace_regs *fregs, unsigned long ip)
|
|||||||
#define ftrace_regs_get_frame_pointer(fregs) \
|
#define ftrace_regs_get_frame_pointer(fregs) \
|
||||||
(arch_ftrace_regs(fregs)->regs.regs[22])
|
(arch_ftrace_regs(fregs)->regs.regs[22])
|
||||||
|
|
||||||
|
static __always_inline unsigned long
|
||||||
|
ftrace_regs_get_return_address(struct ftrace_regs *fregs)
|
||||||
|
{
|
||||||
|
return *(unsigned long *)(arch_ftrace_regs(fregs)->regs.regs[1]);
|
||||||
|
}
|
||||||
|
|
||||||
#define ftrace_graph_func ftrace_graph_func
|
#define ftrace_graph_func ftrace_graph_func
|
||||||
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
|
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
|
||||||
struct ftrace_ops *op, struct ftrace_regs *fregs);
|
struct ftrace_ops *op, struct ftrace_regs *fregs);
|
||||||
|
@ -57,6 +57,12 @@ ftrace_regs_set_instruction_pointer(struct ftrace_regs *fregs,
|
|||||||
regs_set_return_ip(&arch_ftrace_regs(fregs)->regs, ip);
|
regs_set_return_ip(&arch_ftrace_regs(fregs)->regs, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static __always_inline unsigned long
|
||||||
|
ftrace_regs_get_return_address(struct ftrace_regs *fregs)
|
||||||
|
{
|
||||||
|
return arch_ftrace_regs(fregs)->regs.link;
|
||||||
|
}
|
||||||
|
|
||||||
struct ftrace_ops;
|
struct ftrace_ops;
|
||||||
|
|
||||||
#define ftrace_graph_func ftrace_graph_func
|
#define ftrace_graph_func ftrace_graph_func
|
||||||
|
@ -186,6 +186,11 @@ static __always_inline unsigned long ftrace_regs_get_return_value(const struct f
|
|||||||
return arch_ftrace_regs(fregs)->a0;
|
return arch_ftrace_regs(fregs)->a0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static __always_inline unsigned long ftrace_regs_get_return_address(const struct ftrace_regs *fregs)
|
||||||
|
{
|
||||||
|
return arch_ftrace_regs(fregs)->ra;
|
||||||
|
}
|
||||||
|
|
||||||
static __always_inline void ftrace_regs_set_return_value(struct ftrace_regs *fregs,
|
static __always_inline void ftrace_regs_set_return_value(struct ftrace_regs *fregs,
|
||||||
unsigned long ret)
|
unsigned long ret)
|
||||||
{
|
{
|
||||||
|
@ -77,6 +77,12 @@ ftrace_regs_get_frame_pointer(struct ftrace_regs *fregs)
|
|||||||
return ftrace_regs_get_stack_pointer(fregs);
|
return ftrace_regs_get_stack_pointer(fregs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static __always_inline unsigned long
|
||||||
|
ftrace_regs_get_return_address(const struct ftrace_regs *fregs)
|
||||||
|
{
|
||||||
|
return arch_ftrace_regs(fregs)->regs.gprs[14];
|
||||||
|
}
|
||||||
|
|
||||||
#define arch_ftrace_fill_perf_regs(fregs, _regs) do { \
|
#define arch_ftrace_fill_perf_regs(fregs, _regs) do { \
|
||||||
(_regs)->psw.mask = 0; \
|
(_regs)->psw.mask = 0; \
|
||||||
(_regs)->psw.addr = arch_ftrace_regs(fregs)->regs.psw.addr; \
|
(_regs)->psw.addr = arch_ftrace_regs(fregs)->regs.psw.addr; \
|
||||||
|
@ -58,6 +58,12 @@ arch_ftrace_get_regs(struct ftrace_regs *fregs)
|
|||||||
do { arch_ftrace_regs(fregs)->regs.ip = (_ip); } while (0)
|
do { arch_ftrace_regs(fregs)->regs.ip = (_ip); } while (0)
|
||||||
|
|
||||||
|
|
||||||
|
static __always_inline unsigned long
|
||||||
|
ftrace_regs_get_return_address(struct ftrace_regs *fregs)
|
||||||
|
{
|
||||||
|
return *(unsigned long *)ftrace_regs_get_stack_pointer(fregs);
|
||||||
|
}
|
||||||
|
|
||||||
struct ftrace_ops;
|
struct ftrace_ops;
|
||||||
#define ftrace_graph_func ftrace_graph_func
|
#define ftrace_graph_func ftrace_graph_func
|
||||||
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
|
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
|
|
||||||
#include <linux/compiler.h>
|
#include <linux/compiler.h>
|
||||||
#include <linux/ftrace.h>
|
#include <linux/ftrace.h>
|
||||||
#include <linux/rethook.h>
|
#include <linux/rcupdate.h>
|
||||||
|
#include <linux/refcount.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
struct fprobe;
|
struct fprobe;
|
||||||
|
|
||||||
typedef int (*fprobe_entry_cb)(struct fprobe *fp, unsigned long entry_ip,
|
typedef int (*fprobe_entry_cb)(struct fprobe *fp, unsigned long entry_ip,
|
||||||
unsigned long ret_ip, struct ftrace_regs *regs,
|
unsigned long ret_ip, struct ftrace_regs *regs,
|
||||||
void *entry_data);
|
void *entry_data);
|
||||||
@ -17,35 +18,57 @@ typedef void (*fprobe_exit_cb)(struct fprobe *fp, unsigned long entry_ip,
|
|||||||
unsigned long ret_ip, struct ftrace_regs *regs,
|
unsigned long ret_ip, struct ftrace_regs *regs,
|
||||||
void *entry_data);
|
void *entry_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct fprobe_hlist_node - address based hash list node for fprobe.
|
||||||
|
*
|
||||||
|
* @hlist: The hlist node for address search hash table.
|
||||||
|
* @addr: One of the probing address of @fp.
|
||||||
|
* @fp: The fprobe which owns this.
|
||||||
|
*/
|
||||||
|
struct fprobe_hlist_node {
|
||||||
|
struct hlist_node hlist;
|
||||||
|
unsigned long addr;
|
||||||
|
struct fprobe *fp;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct fprobe_hlist - hash list nodes for fprobe.
|
||||||
|
*
|
||||||
|
* @hlist: The hlist node for existence checking hash table.
|
||||||
|
* @rcu: rcu_head for RCU deferred release.
|
||||||
|
* @fp: The fprobe which owns this fprobe_hlist.
|
||||||
|
* @size: The size of @array.
|
||||||
|
* @array: The fprobe_hlist_node for each address to probe.
|
||||||
|
*/
|
||||||
|
struct fprobe_hlist {
|
||||||
|
struct hlist_node hlist;
|
||||||
|
struct rcu_head rcu;
|
||||||
|
struct fprobe *fp;
|
||||||
|
int size;
|
||||||
|
struct fprobe_hlist_node array[] __counted_by(size);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct fprobe - ftrace based probe.
|
* struct fprobe - ftrace based probe.
|
||||||
* @ops: The ftrace_ops.
|
*
|
||||||
* @nmissed: The counter for missing events.
|
* @nmissed: The counter for missing events.
|
||||||
* @flags: The status flag.
|
* @flags: The status flag.
|
||||||
* @rethook: The rethook data structure. (internal data)
|
|
||||||
* @entry_data_size: The private data storage size.
|
* @entry_data_size: The private data storage size.
|
||||||
* @nr_maxactive: The max number of active functions.
|
* @nr_maxactive: The max number of active functions. (*deprecated)
|
||||||
* @entry_handler: The callback function for function entry.
|
* @entry_handler: The callback function for function entry.
|
||||||
* @exit_handler: The callback function for function exit.
|
* @exit_handler: The callback function for function exit.
|
||||||
|
* @hlist_array: The fprobe_hlist for fprobe search from IP hash table.
|
||||||
*/
|
*/
|
||||||
struct fprobe {
|
struct fprobe {
|
||||||
#ifdef CONFIG_FUNCTION_TRACER
|
|
||||||
/*
|
|
||||||
* If CONFIG_FUNCTION_TRACER is not set, CONFIG_FPROBE is disabled too.
|
|
||||||
* But user of fprobe may keep embedding the struct fprobe on their own
|
|
||||||
* code. To avoid build error, this will keep the fprobe data structure
|
|
||||||
* defined here, but remove ftrace_ops data structure.
|
|
||||||
*/
|
|
||||||
struct ftrace_ops ops;
|
|
||||||
#endif
|
|
||||||
unsigned long nmissed;
|
unsigned long nmissed;
|
||||||
unsigned int flags;
|
unsigned int flags;
|
||||||
struct rethook *rethook;
|
|
||||||
size_t entry_data_size;
|
size_t entry_data_size;
|
||||||
int nr_maxactive;
|
int nr_maxactive;
|
||||||
|
|
||||||
fprobe_entry_cb entry_handler;
|
fprobe_entry_cb entry_handler;
|
||||||
fprobe_exit_cb exit_handler;
|
fprobe_exit_cb exit_handler;
|
||||||
|
|
||||||
|
struct fprobe_hlist *hlist_array;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This fprobe is soft-disabled. */
|
/* This fprobe is soft-disabled. */
|
||||||
@ -121,4 +144,9 @@ static inline void enable_fprobe(struct fprobe *fp)
|
|||||||
fp->flags &= ~FPROBE_FL_DISABLED;
|
fp->flags &= ~FPROBE_FL_DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The entry data size is 4 bits (=16) * sizeof(long) in maximum */
|
||||||
|
#define FPROBE_DATA_SIZE_BITS 4
|
||||||
|
#define MAX_FPROBE_DATA_SIZE_WORD ((1L << FPROBE_DATA_SIZE_BITS) - 1)
|
||||||
|
#define MAX_FPROBE_DATA_SIZE (MAX_FPROBE_DATA_SIZE_WORD * sizeof(long))
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -307,11 +307,9 @@ config DYNAMIC_FTRACE_WITH_ARGS
|
|||||||
|
|
||||||
config FPROBE
|
config FPROBE
|
||||||
bool "Kernel Function Probe (fprobe)"
|
bool "Kernel Function Probe (fprobe)"
|
||||||
depends on FUNCTION_TRACER
|
depends on HAVE_FUNCTION_GRAPH_FREGS && HAVE_FTRACE_GRAPH_FUNC
|
||||||
depends on DYNAMIC_FTRACE_WITH_REGS || DYNAMIC_FTRACE_WITH_ARGS
|
depends on DYNAMIC_FTRACE_WITH_ARGS
|
||||||
depends on HAVE_FTRACE_REGS_HAVING_PT_REGS || !HAVE_DYNAMIC_FTRACE_WITH_ARGS
|
select FUNCTION_GRAPH_TRACER
|
||||||
depends on HAVE_RETHOOK
|
|
||||||
select RETHOOK
|
|
||||||
default n
|
default n
|
||||||
help
|
help
|
||||||
This option enables kernel function probe (fprobe) based on ftrace.
|
This option enables kernel function probe (fprobe) based on ftrace.
|
||||||
|
@ -8,98 +8,195 @@
|
|||||||
#include <linux/fprobe.h>
|
#include <linux/fprobe.h>
|
||||||
#include <linux/kallsyms.h>
|
#include <linux/kallsyms.h>
|
||||||
#include <linux/kprobes.h>
|
#include <linux/kprobes.h>
|
||||||
#include <linux/rethook.h>
|
#include <linux/list.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/sort.h>
|
#include <linux/sort.h>
|
||||||
|
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
|
||||||
struct fprobe_rethook_node {
|
#define FPROBE_IP_HASH_BITS 8
|
||||||
struct rethook_node node;
|
#define FPROBE_IP_TABLE_SIZE (1 << FPROBE_IP_HASH_BITS)
|
||||||
unsigned long entry_ip;
|
|
||||||
unsigned long entry_parent_ip;
|
|
||||||
char data[];
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline void __fprobe_handler(unsigned long ip, unsigned long parent_ip,
|
#define FPROBE_HASH_BITS 6
|
||||||
struct ftrace_ops *ops, struct ftrace_regs *fregs)
|
#define FPROBE_TABLE_SIZE (1 << FPROBE_HASH_BITS)
|
||||||
{
|
|
||||||
struct fprobe_rethook_node *fpr;
|
|
||||||
struct rethook_node *rh = NULL;
|
|
||||||
struct fprobe *fp;
|
|
||||||
void *entry_data = NULL;
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
fp = container_of(ops, struct fprobe, ops);
|
#define SIZE_IN_LONG(x) ((x + sizeof(long) - 1) >> (sizeof(long) == 8 ? 3 : 2))
|
||||||
|
|
||||||
if (fp->exit_handler) {
|
/*
|
||||||
rh = rethook_try_get(fp->rethook);
|
* fprobe_table: hold 'fprobe_hlist::hlist' for checking the fprobe still
|
||||||
if (!rh) {
|
* exists. The key is the address of fprobe instance.
|
||||||
fp->nmissed++;
|
* fprobe_ip_table: hold 'fprobe_hlist::array[*]' for searching the fprobe
|
||||||
return;
|
* instance related to the funciton address. The key is the ftrace IP
|
||||||
}
|
* address.
|
||||||
fpr = container_of(rh, struct fprobe_rethook_node, node);
|
*
|
||||||
fpr->entry_ip = ip;
|
* When unregistering the fprobe, fprobe_hlist::fp and fprobe_hlist::array[*].fp
|
||||||
fpr->entry_parent_ip = parent_ip;
|
* are set NULL and delete those from both hash tables (by hlist_del_rcu).
|
||||||
if (fp->entry_data_size)
|
* After an RCU grace period, the fprobe_hlist itself will be released.
|
||||||
entry_data = fpr->data;
|
*
|
||||||
}
|
* fprobe_table and fprobe_ip_table can be accessed from either
|
||||||
|
* - Normal hlist traversal and RCU add/del under 'fprobe_mutex' is held.
|
||||||
if (fp->entry_handler)
|
* - RCU hlist traversal under disabling preempt
|
||||||
ret = fp->entry_handler(fp, ip, parent_ip, fregs, entry_data);
|
|
||||||
|
|
||||||
/* If entry_handler returns !0, nmissed is not counted. */
|
|
||||||
if (rh) {
|
|
||||||
if (ret)
|
|
||||||
rethook_recycle(rh);
|
|
||||||
else
|
|
||||||
rethook_hook(rh, ftrace_get_regs(fregs), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fprobe_handler(unsigned long ip, unsigned long parent_ip,
|
|
||||||
struct ftrace_ops *ops, struct ftrace_regs *fregs)
|
|
||||||
{
|
|
||||||
struct fprobe *fp;
|
|
||||||
int bit;
|
|
||||||
|
|
||||||
fp = container_of(ops, struct fprobe, ops);
|
|
||||||
if (fprobe_disabled(fp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* recursion detection has to go before any traceable function and
|
|
||||||
* all functions before this point should be marked as notrace
|
|
||||||
*/
|
*/
|
||||||
bit = ftrace_test_recursion_trylock(ip, parent_ip);
|
static struct hlist_head fprobe_table[FPROBE_TABLE_SIZE];
|
||||||
if (bit < 0) {
|
static struct hlist_head fprobe_ip_table[FPROBE_IP_TABLE_SIZE];
|
||||||
fp->nmissed++;
|
static DEFINE_MUTEX(fprobe_mutex);
|
||||||
return;
|
|
||||||
}
|
|
||||||
__fprobe_handler(ip, parent_ip, ops, fregs);
|
|
||||||
ftrace_test_recursion_unlock(bit);
|
|
||||||
|
|
||||||
}
|
/*
|
||||||
NOKPROBE_SYMBOL(fprobe_handler);
|
* Find first fprobe in the hlist. It will be iterated twice in the entry
|
||||||
|
* probe, once for correcting the total required size, the second time is
|
||||||
static void fprobe_kprobe_handler(unsigned long ip, unsigned long parent_ip,
|
* calling back the user handlers.
|
||||||
struct ftrace_ops *ops, struct ftrace_regs *fregs)
|
* Thus the hlist in the fprobe_table must be sorted and new probe needs to
|
||||||
{
|
* be added *before* the first fprobe.
|
||||||
struct fprobe *fp;
|
|
||||||
int bit;
|
|
||||||
|
|
||||||
fp = container_of(ops, struct fprobe, ops);
|
|
||||||
if (fprobe_disabled(fp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* recursion detection has to go before any traceable function and
|
|
||||||
* all functions called before this point should be marked as notrace
|
|
||||||
*/
|
*/
|
||||||
bit = ftrace_test_recursion_trylock(ip, parent_ip);
|
static struct fprobe_hlist_node *find_first_fprobe_node(unsigned long ip)
|
||||||
if (bit < 0) {
|
{
|
||||||
fp->nmissed++;
|
struct fprobe_hlist_node *node;
|
||||||
|
struct hlist_head *head;
|
||||||
|
|
||||||
|
head = &fprobe_ip_table[hash_ptr((void *)ip, FPROBE_IP_HASH_BITS)];
|
||||||
|
hlist_for_each_entry_rcu(node, head, hlist,
|
||||||
|
lockdep_is_held(&fprobe_mutex)) {
|
||||||
|
if (node->addr == ip)
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
NOKPROBE_SYMBOL(find_first_fprobe_node);
|
||||||
|
|
||||||
|
/* Node insertion and deletion requires the fprobe_mutex */
|
||||||
|
static void insert_fprobe_node(struct fprobe_hlist_node *node)
|
||||||
|
{
|
||||||
|
unsigned long ip = node->addr;
|
||||||
|
struct fprobe_hlist_node *next;
|
||||||
|
struct hlist_head *head;
|
||||||
|
|
||||||
|
lockdep_assert_held(&fprobe_mutex);
|
||||||
|
|
||||||
|
next = find_first_fprobe_node(ip);
|
||||||
|
if (next) {
|
||||||
|
hlist_add_before_rcu(&node->hlist, &next->hlist);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
head = &fprobe_ip_table[hash_ptr((void *)ip, FPROBE_IP_HASH_BITS)];
|
||||||
|
hlist_add_head_rcu(&node->hlist, head);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return true if there are synonims */
|
||||||
|
static bool delete_fprobe_node(struct fprobe_hlist_node *node)
|
||||||
|
{
|
||||||
|
lockdep_assert_held(&fprobe_mutex);
|
||||||
|
|
||||||
|
WRITE_ONCE(node->fp, NULL);
|
||||||
|
hlist_del_rcu(&node->hlist);
|
||||||
|
return !!find_first_fprobe_node(node->addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check existence of the fprobe */
|
||||||
|
static bool is_fprobe_still_exist(struct fprobe *fp)
|
||||||
|
{
|
||||||
|
struct hlist_head *head;
|
||||||
|
struct fprobe_hlist *fph;
|
||||||
|
|
||||||
|
head = &fprobe_table[hash_ptr(fp, FPROBE_HASH_BITS)];
|
||||||
|
hlist_for_each_entry_rcu(fph, head, hlist,
|
||||||
|
lockdep_is_held(&fprobe_mutex)) {
|
||||||
|
if (fph->fp == fp)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NOKPROBE_SYMBOL(is_fprobe_still_exist);
|
||||||
|
|
||||||
|
static int add_fprobe_hash(struct fprobe *fp)
|
||||||
|
{
|
||||||
|
struct fprobe_hlist *fph = fp->hlist_array;
|
||||||
|
struct hlist_head *head;
|
||||||
|
|
||||||
|
lockdep_assert_held(&fprobe_mutex);
|
||||||
|
|
||||||
|
if (WARN_ON_ONCE(!fph))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (is_fprobe_still_exist(fp))
|
||||||
|
return -EEXIST;
|
||||||
|
|
||||||
|
head = &fprobe_table[hash_ptr(fp, FPROBE_HASH_BITS)];
|
||||||
|
hlist_add_head_rcu(&fp->hlist_array->hlist, head);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int del_fprobe_hash(struct fprobe *fp)
|
||||||
|
{
|
||||||
|
struct fprobe_hlist *fph = fp->hlist_array;
|
||||||
|
|
||||||
|
lockdep_assert_held(&fprobe_mutex);
|
||||||
|
|
||||||
|
if (WARN_ON_ONCE(!fph))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (!is_fprobe_still_exist(fp))
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
fph->fp = NULL;
|
||||||
|
hlist_del_rcu(&fph->hlist);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generic fprobe_header */
|
||||||
|
struct __fprobe_header {
|
||||||
|
struct fprobe *fp;
|
||||||
|
unsigned long size_words;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
#define FPROBE_HEADER_SIZE_IN_LONG SIZE_IN_LONG(sizeof(struct __fprobe_header))
|
||||||
|
|
||||||
|
static inline bool write_fprobe_header(unsigned long *stack,
|
||||||
|
struct fprobe *fp, unsigned int size_words)
|
||||||
|
{
|
||||||
|
struct __fprobe_header *fph = (struct __fprobe_header *)stack;
|
||||||
|
|
||||||
|
if (WARN_ON_ONCE(size_words > MAX_FPROBE_DATA_SIZE_WORD))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fph->fp = fp;
|
||||||
|
fph->size_words = size_words;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void read_fprobe_header(unsigned long *stack,
|
||||||
|
struct fprobe **fp, unsigned int *size_words)
|
||||||
|
{
|
||||||
|
struct __fprobe_header *fph = (struct __fprobe_header *)stack;
|
||||||
|
|
||||||
|
*fp = fph->fp;
|
||||||
|
*size_words = fph->size_words;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fprobe shadow stack management:
|
||||||
|
* Since fprobe shares a single fgraph_ops, it needs to share the stack entry
|
||||||
|
* among the probes on the same function exit. Note that a new probe can be
|
||||||
|
* registered before a target function is returning, we can not use the hash
|
||||||
|
* table to find the corresponding probes. Thus the probe address is stored on
|
||||||
|
* the shadow stack with its entry data size.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static inline int __fprobe_handler(unsigned long ip, unsigned long parent_ip,
|
||||||
|
struct fprobe *fp, struct ftrace_regs *fregs,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
if (!fp->entry_handler)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return fp->entry_handler(fp, ip, parent_ip, fregs, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int __fprobe_kprobe_handler(unsigned long ip, unsigned long parent_ip,
|
||||||
|
struct fprobe *fp, struct ftrace_regs *fregs,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
/*
|
/*
|
||||||
* This user handler is shared with other kprobes and is not expected to be
|
* This user handler is shared with other kprobes and is not expected to be
|
||||||
* called recursively. So if any other kprobe handler is running, this will
|
* called recursively. So if any other kprobe handler is running, this will
|
||||||
@ -108,45 +205,183 @@ static void fprobe_kprobe_handler(unsigned long ip, unsigned long parent_ip,
|
|||||||
*/
|
*/
|
||||||
if (unlikely(kprobe_running())) {
|
if (unlikely(kprobe_running())) {
|
||||||
fp->nmissed++;
|
fp->nmissed++;
|
||||||
goto recursion_unlock;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
kprobe_busy_begin();
|
kprobe_busy_begin();
|
||||||
__fprobe_handler(ip, parent_ip, ops, fregs);
|
ret = __fprobe_handler(ip, parent_ip, fp, fregs, data);
|
||||||
kprobe_busy_end();
|
kprobe_busy_end();
|
||||||
|
return ret;
|
||||||
recursion_unlock:
|
|
||||||
ftrace_test_recursion_unlock(bit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fprobe_exit_handler(struct rethook_node *rh, void *data,
|
static int fprobe_entry(struct ftrace_graph_ent *trace, struct fgraph_ops *gops,
|
||||||
unsigned long ret_ip, struct pt_regs *regs)
|
struct ftrace_regs *fregs)
|
||||||
{
|
{
|
||||||
struct fprobe *fp = (struct fprobe *)data;
|
struct fprobe_hlist_node *node, *first;
|
||||||
struct fprobe_rethook_node *fpr;
|
unsigned long *fgraph_data = NULL;
|
||||||
struct ftrace_regs *fregs = (struct ftrace_regs *)regs;
|
unsigned long func = trace->func;
|
||||||
int bit;
|
unsigned long ret_ip;
|
||||||
|
int reserved_words;
|
||||||
|
struct fprobe *fp;
|
||||||
|
int used, ret;
|
||||||
|
|
||||||
if (!fp || fprobe_disabled(fp))
|
if (WARN_ON_ONCE(!fregs))
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
fpr = container_of(rh, struct fprobe_rethook_node, node);
|
first = node = find_first_fprobe_node(func);
|
||||||
|
if (unlikely(!first))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
reserved_words = 0;
|
||||||
|
hlist_for_each_entry_from_rcu(node, hlist) {
|
||||||
|
if (node->addr != func)
|
||||||
|
break;
|
||||||
|
fp = READ_ONCE(node->fp);
|
||||||
|
if (!fp || !fp->exit_handler)
|
||||||
|
continue;
|
||||||
|
/*
|
||||||
|
* Since fprobe can be enabled until the next loop, we ignore the
|
||||||
|
* fprobe's disabled flag in this loop.
|
||||||
|
*/
|
||||||
|
reserved_words +=
|
||||||
|
FPROBE_HEADER_SIZE_IN_LONG + SIZE_IN_LONG(fp->entry_data_size);
|
||||||
|
}
|
||||||
|
node = first;
|
||||||
|
if (reserved_words) {
|
||||||
|
fgraph_data = fgraph_reserve_data(gops->idx, reserved_words * sizeof(long));
|
||||||
|
if (unlikely(!fgraph_data)) {
|
||||||
|
hlist_for_each_entry_from_rcu(node, hlist) {
|
||||||
|
if (node->addr != func)
|
||||||
|
break;
|
||||||
|
fp = READ_ONCE(node->fp);
|
||||||
|
if (fp && !fprobe_disabled(fp))
|
||||||
|
fp->nmissed++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* we need to assure no calls to traceable functions in-between the
|
* TODO: recursion detection has been done in the fgraph. Thus we need
|
||||||
* end of fprobe_handler and the beginning of fprobe_exit_handler.
|
* to add a callback to increment missed counter.
|
||||||
*/
|
*/
|
||||||
bit = ftrace_test_recursion_trylock(fpr->entry_ip, fpr->entry_parent_ip);
|
ret_ip = ftrace_regs_get_return_address(fregs);
|
||||||
if (bit < 0) {
|
used = 0;
|
||||||
fp->nmissed++;
|
hlist_for_each_entry_from_rcu(node, hlist) {
|
||||||
|
int data_size;
|
||||||
|
void *data;
|
||||||
|
|
||||||
|
if (node->addr != func)
|
||||||
|
break;
|
||||||
|
fp = READ_ONCE(node->fp);
|
||||||
|
if (!fp || fprobe_disabled(fp))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
data_size = fp->entry_data_size;
|
||||||
|
if (data_size && fp->exit_handler)
|
||||||
|
data = fgraph_data + used + FPROBE_HEADER_SIZE_IN_LONG;
|
||||||
|
else
|
||||||
|
data = NULL;
|
||||||
|
|
||||||
|
if (fprobe_shared_with_kprobes(fp))
|
||||||
|
ret = __fprobe_kprobe_handler(func, ret_ip, fp, fregs, data);
|
||||||
|
else
|
||||||
|
ret = __fprobe_handler(func, ret_ip, fp, fregs, data);
|
||||||
|
|
||||||
|
/* If entry_handler returns !0, nmissed is not counted but skips exit_handler. */
|
||||||
|
if (!ret && fp->exit_handler) {
|
||||||
|
int size_words = SIZE_IN_LONG(data_size);
|
||||||
|
|
||||||
|
if (write_fprobe_header(&fgraph_data[used], fp, size_words))
|
||||||
|
used += FPROBE_HEADER_SIZE_IN_LONG + size_words;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (used < reserved_words)
|
||||||
|
memset(fgraph_data + used, 0, reserved_words - used);
|
||||||
|
|
||||||
|
/* If any exit_handler is set, data must be used. */
|
||||||
|
return used != 0;
|
||||||
|
}
|
||||||
|
NOKPROBE_SYMBOL(fprobe_entry);
|
||||||
|
|
||||||
|
static void fprobe_return(struct ftrace_graph_ret *trace,
|
||||||
|
struct fgraph_ops *gops,
|
||||||
|
struct ftrace_regs *fregs)
|
||||||
|
{
|
||||||
|
unsigned long *fgraph_data = NULL;
|
||||||
|
unsigned long ret_ip;
|
||||||
|
struct fprobe *fp;
|
||||||
|
int size, curr;
|
||||||
|
int size_words;
|
||||||
|
|
||||||
|
fgraph_data = (unsigned long *)fgraph_retrieve_data(gops->idx, &size);
|
||||||
|
if (WARN_ON_ONCE(!fgraph_data))
|
||||||
|
return;
|
||||||
|
size_words = SIZE_IN_LONG(size);
|
||||||
|
ret_ip = ftrace_regs_get_instruction_pointer(fregs);
|
||||||
|
|
||||||
|
preempt_disable();
|
||||||
|
|
||||||
|
curr = 0;
|
||||||
|
while (size_words > curr) {
|
||||||
|
read_fprobe_header(&fgraph_data[curr], &fp, &size);
|
||||||
|
if (!fp)
|
||||||
|
break;
|
||||||
|
curr += FPROBE_HEADER_SIZE_IN_LONG;
|
||||||
|
if (is_fprobe_still_exist(fp) && !fprobe_disabled(fp)) {
|
||||||
|
if (WARN_ON_ONCE(curr + size > size_words))
|
||||||
|
break;
|
||||||
|
fp->exit_handler(fp, trace->func, ret_ip, fregs,
|
||||||
|
size ? fgraph_data + curr : NULL);
|
||||||
|
}
|
||||||
|
curr += size;
|
||||||
|
}
|
||||||
|
preempt_enable();
|
||||||
|
}
|
||||||
|
NOKPROBE_SYMBOL(fprobe_return);
|
||||||
|
|
||||||
|
static struct fgraph_ops fprobe_graph_ops = {
|
||||||
|
.entryfunc = fprobe_entry,
|
||||||
|
.retfunc = fprobe_return,
|
||||||
|
};
|
||||||
|
static int fprobe_graph_active;
|
||||||
|
|
||||||
|
/* Add @addrs to the ftrace filter and register fgraph if needed. */
|
||||||
|
static int fprobe_graph_add_ips(unsigned long *addrs, int num)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
lockdep_assert_held(&fprobe_mutex);
|
||||||
|
|
||||||
|
ret = ftrace_set_filter_ips(&fprobe_graph_ops.ops, addrs, num, 0, 0);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (!fprobe_graph_active) {
|
||||||
|
ret = register_ftrace_graph(&fprobe_graph_ops);
|
||||||
|
if (WARN_ON_ONCE(ret)) {
|
||||||
|
ftrace_free_filter(&fprobe_graph_ops.ops);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprobe_graph_active++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove @addrs from the ftrace filter and unregister fgraph if possible. */
|
||||||
|
static void fprobe_graph_remove_ips(unsigned long *addrs, int num)
|
||||||
|
{
|
||||||
|
lockdep_assert_held(&fprobe_mutex);
|
||||||
|
|
||||||
|
fprobe_graph_active--;
|
||||||
|
if (!fprobe_graph_active) {
|
||||||
|
/* Q: should we unregister it ? */
|
||||||
|
unregister_ftrace_graph(&fprobe_graph_ops);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fp->exit_handler(fp, fpr->entry_ip, ret_ip, fregs,
|
ftrace_set_filter_ips(&fprobe_graph_ops.ops, addrs, num, 1, 0);
|
||||||
fp->entry_data_size ? (void *)fpr->data : NULL);
|
|
||||||
ftrace_test_recursion_unlock(bit);
|
|
||||||
}
|
}
|
||||||
NOKPROBE_SYMBOL(fprobe_exit_handler);
|
|
||||||
|
|
||||||
static int symbols_cmp(const void *a, const void *b)
|
static int symbols_cmp(const void *a, const void *b)
|
||||||
{
|
{
|
||||||
@ -176,53 +411,96 @@ static unsigned long *get_ftrace_locations(const char **syms, int num)
|
|||||||
return ERR_PTR(-ENOENT);
|
return ERR_PTR(-ENOENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fprobe_init(struct fprobe *fp)
|
struct filter_match_data {
|
||||||
{
|
const char *filter;
|
||||||
fp->nmissed = 0;
|
const char *notfilter;
|
||||||
if (fprobe_shared_with_kprobes(fp))
|
size_t index;
|
||||||
fp->ops.func = fprobe_kprobe_handler;
|
size_t size;
|
||||||
else
|
unsigned long *addrs;
|
||||||
fp->ops.func = fprobe_handler;
|
};
|
||||||
|
|
||||||
fp->ops.flags |= FTRACE_OPS_FL_SAVE_REGS;
|
static int filter_match_callback(void *data, const char *name, unsigned long addr)
|
||||||
|
{
|
||||||
|
struct filter_match_data *match = data;
|
||||||
|
|
||||||
|
if (!glob_match(match->filter, name) ||
|
||||||
|
(match->notfilter && glob_match(match->notfilter, name)))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!ftrace_location(addr))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (match->addrs)
|
||||||
|
match->addrs[match->index] = addr;
|
||||||
|
|
||||||
|
match->index++;
|
||||||
|
return match->index == match->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fprobe_init_rethook(struct fprobe *fp, int num)
|
/*
|
||||||
|
* Make IP list from the filter/no-filter glob patterns.
|
||||||
|
* Return the number of matched symbols, or -ENOENT.
|
||||||
|
*/
|
||||||
|
static int ip_list_from_filter(const char *filter, const char *notfilter,
|
||||||
|
unsigned long *addrs, size_t size)
|
||||||
{
|
{
|
||||||
int size;
|
struct filter_match_data match = { .filter = filter, .notfilter = notfilter,
|
||||||
|
.index = 0, .size = size, .addrs = addrs};
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (!fp->exit_handler) {
|
ret = kallsyms_on_each_symbol(filter_match_callback, &match);
|
||||||
fp->rethook = NULL;
|
if (ret < 0)
|
||||||
return 0;
|
return ret;
|
||||||
}
|
ret = module_kallsyms_on_each_symbol(NULL, filter_match_callback, &match);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/* Initialize rethook if needed */
|
return match.index ?: -ENOENT;
|
||||||
if (fp->nr_maxactive)
|
|
||||||
num = fp->nr_maxactive;
|
|
||||||
else
|
|
||||||
num *= num_possible_cpus() * 2;
|
|
||||||
if (num <= 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
size = sizeof(struct fprobe_rethook_node) + fp->entry_data_size;
|
|
||||||
|
|
||||||
/* Initialize rethook */
|
|
||||||
fp->rethook = rethook_alloc((void *)fp, fprobe_exit_handler, size, num);
|
|
||||||
if (IS_ERR(fp->rethook))
|
|
||||||
return PTR_ERR(fp->rethook);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fprobe_fail_cleanup(struct fprobe *fp)
|
static void fprobe_fail_cleanup(struct fprobe *fp)
|
||||||
{
|
{
|
||||||
if (!IS_ERR_OR_NULL(fp->rethook)) {
|
kfree(fp->hlist_array);
|
||||||
/* Don't need to cleanup rethook->handler because this is not used. */
|
fp->hlist_array = NULL;
|
||||||
rethook_free(fp->rethook);
|
|
||||||
fp->rethook = NULL;
|
|
||||||
}
|
}
|
||||||
ftrace_free_filter(&fp->ops);
|
|
||||||
|
/* Initialize the fprobe data structure. */
|
||||||
|
static int fprobe_init(struct fprobe *fp, unsigned long *addrs, int num)
|
||||||
|
{
|
||||||
|
struct fprobe_hlist *hlist_array;
|
||||||
|
unsigned long addr;
|
||||||
|
int size, i;
|
||||||
|
|
||||||
|
if (!fp || !addrs || num <= 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
size = ALIGN(fp->entry_data_size, sizeof(long));
|
||||||
|
if (size > MAX_FPROBE_DATA_SIZE)
|
||||||
|
return -E2BIG;
|
||||||
|
fp->entry_data_size = size;
|
||||||
|
|
||||||
|
hlist_array = kzalloc(struct_size(hlist_array, array, num), GFP_KERNEL);
|
||||||
|
if (!hlist_array)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
fp->nmissed = 0;
|
||||||
|
|
||||||
|
hlist_array->size = num;
|
||||||
|
fp->hlist_array = hlist_array;
|
||||||
|
hlist_array->fp = fp;
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
hlist_array->array[i].fp = fp;
|
||||||
|
addr = ftrace_location(addrs[i]);
|
||||||
|
if (!addr) {
|
||||||
|
fprobe_fail_cleanup(fp);
|
||||||
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
hlist_array->array[i].addr = addr;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FPROBE_IPS_MAX INT_MAX
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* register_fprobe() - Register fprobe to ftrace by pattern.
|
* register_fprobe() - Register fprobe to ftrace by pattern.
|
||||||
@ -237,46 +515,24 @@ static void fprobe_fail_cleanup(struct fprobe *fp)
|
|||||||
*/
|
*/
|
||||||
int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter)
|
int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter)
|
||||||
{
|
{
|
||||||
struct ftrace_hash *hash;
|
unsigned long *addrs;
|
||||||
unsigned char *str;
|
int ret;
|
||||||
int ret, len;
|
|
||||||
|
|
||||||
if (!fp || !filter)
|
if (!fp || !filter)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
fprobe_init(fp);
|
ret = ip_list_from_filter(filter, notfilter, NULL, FPROBE_IPS_MAX);
|
||||||
|
if (ret < 0)
|
||||||
len = strlen(filter);
|
|
||||||
str = kstrdup(filter, GFP_KERNEL);
|
|
||||||
ret = ftrace_set_filter(&fp->ops, str, len, 0);
|
|
||||||
kfree(str);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (notfilter) {
|
addrs = kcalloc(ret, sizeof(unsigned long), GFP_KERNEL);
|
||||||
len = strlen(notfilter);
|
if (!addrs)
|
||||||
str = kstrdup(notfilter, GFP_KERNEL);
|
return -ENOMEM;
|
||||||
ret = ftrace_set_notrace(&fp->ops, str, len, 0);
|
ret = ip_list_from_filter(filter, notfilter, addrs, ret);
|
||||||
kfree(str);
|
if (ret > 0)
|
||||||
if (ret)
|
ret = register_fprobe_ips(fp, addrs, ret);
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO:
|
kfree(addrs);
|
||||||
* correctly calculate the total number of filtered symbols
|
|
||||||
* from both filter and notfilter.
|
|
||||||
*/
|
|
||||||
hash = rcu_access_pointer(fp->ops.local_hash.filter_hash);
|
|
||||||
if (WARN_ON_ONCE(!hash))
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
ret = fprobe_init_rethook(fp, (int)hash->count);
|
|
||||||
if (!ret)
|
|
||||||
ret = register_ftrace_function(&fp->ops);
|
|
||||||
|
|
||||||
out:
|
|
||||||
if (ret)
|
|
||||||
fprobe_fail_cleanup(fp);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(register_fprobe);
|
EXPORT_SYMBOL_GPL(register_fprobe);
|
||||||
@ -284,7 +540,7 @@ EXPORT_SYMBOL_GPL(register_fprobe);
|
|||||||
/**
|
/**
|
||||||
* register_fprobe_ips() - Register fprobe to ftrace by address.
|
* register_fprobe_ips() - Register fprobe to ftrace by address.
|
||||||
* @fp: A fprobe data structure to be registered.
|
* @fp: A fprobe data structure to be registered.
|
||||||
* @addrs: An array of target ftrace location addresses.
|
* @addrs: An array of target function address.
|
||||||
* @num: The number of entries of @addrs.
|
* @num: The number of entries of @addrs.
|
||||||
*
|
*
|
||||||
* Register @fp to ftrace for enabling the probe on the address given by @addrs.
|
* Register @fp to ftrace for enabling the probe on the address given by @addrs.
|
||||||
@ -296,23 +552,27 @@ EXPORT_SYMBOL_GPL(register_fprobe);
|
|||||||
*/
|
*/
|
||||||
int register_fprobe_ips(struct fprobe *fp, unsigned long *addrs, int num)
|
int register_fprobe_ips(struct fprobe *fp, unsigned long *addrs, int num)
|
||||||
{
|
{
|
||||||
int ret;
|
struct fprobe_hlist *hlist_array;
|
||||||
|
int ret, i;
|
||||||
|
|
||||||
if (!fp || !addrs || num <= 0)
|
ret = fprobe_init(fp, addrs, num);
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
fprobe_init(fp);
|
|
||||||
|
|
||||||
ret = ftrace_set_filter_ips(&fp->ops, addrs, num, 0, 0);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
ret = fprobe_init_rethook(fp, num);
|
mutex_lock(&fprobe_mutex);
|
||||||
if (!ret)
|
|
||||||
ret = register_ftrace_function(&fp->ops);
|
hlist_array = fp->hlist_array;
|
||||||
|
ret = fprobe_graph_add_ips(addrs, num);
|
||||||
|
if (!ret) {
|
||||||
|
add_fprobe_hash(fp);
|
||||||
|
for (i = 0; i < hlist_array->size; i++)
|
||||||
|
insert_fprobe_node(&hlist_array->array[i]);
|
||||||
|
}
|
||||||
|
mutex_unlock(&fprobe_mutex);
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
fprobe_fail_cleanup(fp);
|
fprobe_fail_cleanup(fp);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(register_fprobe_ips);
|
EXPORT_SYMBOL_GPL(register_fprobe_ips);
|
||||||
@ -350,14 +610,13 @@ EXPORT_SYMBOL_GPL(register_fprobe_syms);
|
|||||||
|
|
||||||
bool fprobe_is_registered(struct fprobe *fp)
|
bool fprobe_is_registered(struct fprobe *fp)
|
||||||
{
|
{
|
||||||
if (!fp || (fp->ops.saved_func != fprobe_handler &&
|
if (!fp || !fp->hlist_array)
|
||||||
fp->ops.saved_func != fprobe_kprobe_handler))
|
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unregister_fprobe() - Unregister fprobe from ftrace
|
* unregister_fprobe() - Unregister fprobe.
|
||||||
* @fp: A fprobe data structure to be unregistered.
|
* @fp: A fprobe data structure to be unregistered.
|
||||||
*
|
*
|
||||||
* Unregister fprobe (and remove ftrace hooks from the function entries).
|
* Unregister fprobe (and remove ftrace hooks from the function entries).
|
||||||
@ -366,23 +625,41 @@ bool fprobe_is_registered(struct fprobe *fp)
|
|||||||
*/
|
*/
|
||||||
int unregister_fprobe(struct fprobe *fp)
|
int unregister_fprobe(struct fprobe *fp)
|
||||||
{
|
{
|
||||||
int ret;
|
struct fprobe_hlist *hlist_array;
|
||||||
|
unsigned long *addrs = NULL;
|
||||||
|
int ret = 0, i, count;
|
||||||
|
|
||||||
if (!fprobe_is_registered(fp))
|
mutex_lock(&fprobe_mutex);
|
||||||
return -EINVAL;
|
if (!fp || !is_fprobe_still_exist(fp)) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IS_ERR_OR_NULL(fp->rethook))
|
hlist_array = fp->hlist_array;
|
||||||
rethook_stop(fp->rethook);
|
addrs = kcalloc(hlist_array->size, sizeof(unsigned long), GFP_KERNEL);
|
||||||
|
if (!addrs) {
|
||||||
|
ret = -ENOMEM; /* TODO: Fallback to one-by-one loop */
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
ret = unregister_ftrace_function(&fp->ops);
|
/* Remove non-synonim ips from table and hash */
|
||||||
if (ret < 0)
|
count = 0;
|
||||||
return ret;
|
for (i = 0; i < hlist_array->size; i++) {
|
||||||
|
if (!delete_fprobe_node(&hlist_array->array[i]))
|
||||||
|
addrs[count++] = hlist_array->array[i].addr;
|
||||||
|
}
|
||||||
|
del_fprobe_hash(fp);
|
||||||
|
|
||||||
if (!IS_ERR_OR_NULL(fp->rethook))
|
if (count)
|
||||||
rethook_free(fp->rethook);
|
fprobe_graph_remove_ips(addrs, count);
|
||||||
|
|
||||||
ftrace_free_filter(&fp->ops);
|
kfree_rcu(hlist_array, rcu);
|
||||||
|
fp->hlist_array = NULL;
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&fprobe_mutex);
|
||||||
|
|
||||||
|
kfree(addrs);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(unregister_fprobe);
|
EXPORT_SYMBOL_GPL(unregister_fprobe);
|
||||||
|
@ -17,10 +17,8 @@ static u32 rand1, entry_val, exit_val;
|
|||||||
/* Use indirect calls to avoid inlining the target functions */
|
/* Use indirect calls to avoid inlining the target functions */
|
||||||
static u32 (*target)(u32 value);
|
static u32 (*target)(u32 value);
|
||||||
static u32 (*target2)(u32 value);
|
static u32 (*target2)(u32 value);
|
||||||
static u32 (*target_nest)(u32 value, u32 (*nest)(u32));
|
|
||||||
static unsigned long target_ip;
|
static unsigned long target_ip;
|
||||||
static unsigned long target2_ip;
|
static unsigned long target2_ip;
|
||||||
static unsigned long target_nest_ip;
|
|
||||||
static int entry_return_value;
|
static int entry_return_value;
|
||||||
|
|
||||||
static noinline u32 fprobe_selftest_target(u32 value)
|
static noinline u32 fprobe_selftest_target(u32 value)
|
||||||
@ -33,11 +31,6 @@ static noinline u32 fprobe_selftest_target2(u32 value)
|
|||||||
return (value / div_factor) + 1;
|
return (value / div_factor) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static noinline u32 fprobe_selftest_nest_target(u32 value, u32 (*nest)(u32))
|
|
||||||
{
|
|
||||||
return nest(value + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static notrace int fp_entry_handler(struct fprobe *fp, unsigned long ip,
|
static notrace int fp_entry_handler(struct fprobe *fp, unsigned long ip,
|
||||||
unsigned long ret_ip,
|
unsigned long ret_ip,
|
||||||
struct ftrace_regs *fregs, void *data)
|
struct ftrace_regs *fregs, void *data)
|
||||||
@ -79,22 +72,6 @@ static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip,
|
|||||||
KUNIT_EXPECT_NULL(current_test, data);
|
KUNIT_EXPECT_NULL(current_test, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static notrace int nest_entry_handler(struct fprobe *fp, unsigned long ip,
|
|
||||||
unsigned long ret_ip,
|
|
||||||
struct ftrace_regs *fregs, void *data)
|
|
||||||
{
|
|
||||||
KUNIT_EXPECT_FALSE(current_test, preemptible());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static notrace void nest_exit_handler(struct fprobe *fp, unsigned long ip,
|
|
||||||
unsigned long ret_ip,
|
|
||||||
struct ftrace_regs *fregs, void *data)
|
|
||||||
{
|
|
||||||
KUNIT_EXPECT_FALSE(current_test, preemptible());
|
|
||||||
KUNIT_EXPECT_EQ(current_test, ip, target_nest_ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test entry only (no rethook) */
|
/* Test entry only (no rethook) */
|
||||||
static void test_fprobe_entry(struct kunit *test)
|
static void test_fprobe_entry(struct kunit *test)
|
||||||
{
|
{
|
||||||
@ -191,25 +168,6 @@ static void test_fprobe_data(struct kunit *test)
|
|||||||
KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
|
KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Test nr_maxactive */
|
|
||||||
static void test_fprobe_nest(struct kunit *test)
|
|
||||||
{
|
|
||||||
static const char *syms[] = {"fprobe_selftest_target", "fprobe_selftest_nest_target"};
|
|
||||||
struct fprobe fp = {
|
|
||||||
.entry_handler = nest_entry_handler,
|
|
||||||
.exit_handler = nest_exit_handler,
|
|
||||||
.nr_maxactive = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
current_test = test;
|
|
||||||
KUNIT_EXPECT_EQ(test, 0, register_fprobe_syms(&fp, syms, 2));
|
|
||||||
|
|
||||||
target_nest(rand1, target);
|
|
||||||
KUNIT_EXPECT_EQ(test, 1, fp.nmissed);
|
|
||||||
|
|
||||||
KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_fprobe_skip(struct kunit *test)
|
static void test_fprobe_skip(struct kunit *test)
|
||||||
{
|
{
|
||||||
struct fprobe fp = {
|
struct fprobe fp = {
|
||||||
@ -247,10 +205,8 @@ static int fprobe_test_init(struct kunit *test)
|
|||||||
rand1 = get_random_u32_above(div_factor);
|
rand1 = get_random_u32_above(div_factor);
|
||||||
target = fprobe_selftest_target;
|
target = fprobe_selftest_target;
|
||||||
target2 = fprobe_selftest_target2;
|
target2 = fprobe_selftest_target2;
|
||||||
target_nest = fprobe_selftest_nest_target;
|
|
||||||
target_ip = get_ftrace_location(target);
|
target_ip = get_ftrace_location(target);
|
||||||
target2_ip = get_ftrace_location(target2);
|
target2_ip = get_ftrace_location(target2);
|
||||||
target_nest_ip = get_ftrace_location(target_nest);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -260,7 +216,6 @@ static struct kunit_case fprobe_testcases[] = {
|
|||||||
KUNIT_CASE(test_fprobe),
|
KUNIT_CASE(test_fprobe),
|
||||||
KUNIT_CASE(test_fprobe_syms),
|
KUNIT_CASE(test_fprobe_syms),
|
||||||
KUNIT_CASE(test_fprobe_data),
|
KUNIT_CASE(test_fprobe_data),
|
||||||
KUNIT_CASE(test_fprobe_nest),
|
|
||||||
KUNIT_CASE(test_fprobe_skip),
|
KUNIT_CASE(test_fprobe_skip),
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user