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:
Masami Hiramatsu (Google)
2024-12-26 14:13:59 +09:00
committed by Steven Rostedt (Google)
parent 7495e179b4
commit 4346ba1604
10 changed files with 545 additions and 252 deletions

View File

@@ -135,6 +135,12 @@ ftrace_regs_get_frame_pointer(const struct ftrace_regs *fregs)
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 *
ftrace_partial_regs(const struct ftrace_regs *fregs, struct pt_regs *regs)
{

View File

@@ -61,6 +61,12 @@ ftrace_regs_set_instruction_pointer(struct ftrace_regs *fregs, unsigned long ip)
#define ftrace_regs_get_frame_pointer(fregs) \
(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
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *op, struct ftrace_regs *fregs);

View File

@@ -57,6 +57,12 @@ ftrace_regs_set_instruction_pointer(struct ftrace_regs *fregs,
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;
#define ftrace_graph_func ftrace_graph_func

View File

@@ -186,6 +186,11 @@ static __always_inline unsigned long ftrace_regs_get_return_value(const struct f
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,
unsigned long ret)
{

View File

@@ -77,6 +77,12 @@ ftrace_regs_get_frame_pointer(struct ftrace_regs *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 { \
(_regs)->psw.mask = 0; \
(_regs)->psw.addr = arch_ftrace_regs(fregs)->regs.psw.addr; \

View File

@@ -58,6 +58,12 @@ arch_ftrace_get_regs(struct ftrace_regs *fregs)
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;
#define ftrace_graph_func ftrace_graph_func
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,

View File

@@ -5,10 +5,11 @@
#include <linux/compiler.h>
#include <linux/ftrace.h>
#include <linux/rethook.h>
#include <linux/rcupdate.h>
#include <linux/refcount.h>
#include <linux/slab.h>
struct fprobe;
typedef int (*fprobe_entry_cb)(struct fprobe *fp, unsigned long entry_ip,
unsigned long ret_ip, struct ftrace_regs *regs,
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,
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.
* @ops: The ftrace_ops.
*
* @nmissed: The counter for missing events.
* @flags: The status flag.
* @rethook: The rethook data structure. (internal data)
* @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.
* @exit_handler: The callback function for function exit.
* @hlist_array: The fprobe_hlist for fprobe search from IP hash table.
*/
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 int flags;
struct rethook *rethook;
size_t entry_data_size;
int nr_maxactive;
fprobe_entry_cb entry_handler;
fprobe_exit_cb exit_handler;
struct fprobe_hlist *hlist_array;
};
/* This fprobe is soft-disabled. */
@@ -121,4 +144,9 @@ static inline void enable_fprobe(struct fprobe *fp)
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

View File

@@ -307,11 +307,9 @@ config DYNAMIC_FTRACE_WITH_ARGS
config FPROBE
bool "Kernel Function Probe (fprobe)"
depends on FUNCTION_TRACER
depends on DYNAMIC_FTRACE_WITH_REGS || DYNAMIC_FTRACE_WITH_ARGS
depends on HAVE_FTRACE_REGS_HAVING_PT_REGS || !HAVE_DYNAMIC_FTRACE_WITH_ARGS
depends on HAVE_RETHOOK
select RETHOOK
depends on HAVE_FUNCTION_GRAPH_FREGS && HAVE_FTRACE_GRAPH_FUNC
depends on DYNAMIC_FTRACE_WITH_ARGS
select FUNCTION_GRAPH_TRACER
default n
help
This option enables kernel function probe (fprobe) based on ftrace.

File diff suppressed because it is too large Load Diff

View File

@@ -17,10 +17,8 @@ static u32 rand1, entry_val, exit_val;
/* Use indirect calls to avoid inlining the target functions */
static u32 (*target)(u32 value);
static u32 (*target2)(u32 value);
static u32 (*target_nest)(u32 value, u32 (*nest)(u32));
static unsigned long target_ip;
static unsigned long target2_ip;
static unsigned long target_nest_ip;
static int entry_return_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;
}
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,
unsigned long ret_ip,
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);
}
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) */
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));
}
/* 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)
{
struct fprobe fp = {
@@ -247,10 +205,8 @@ static int fprobe_test_init(struct kunit *test)
rand1 = get_random_u32_above(div_factor);
target = fprobe_selftest_target;
target2 = fprobe_selftest_target2;
target_nest = fprobe_selftest_nest_target;
target_ip = get_ftrace_location(target);
target2_ip = get_ftrace_location(target2);
target_nest_ip = get_ftrace_location(target_nest);
return 0;
}
@@ -260,7 +216,6 @@ static struct kunit_case fprobe_testcases[] = {
KUNIT_CASE(test_fprobe),
KUNIT_CASE(test_fprobe_syms),
KUNIT_CASE(test_fprobe_data),
KUNIT_CASE(test_fprobe_nest),
KUNIT_CASE(test_fprobe_skip),
{}
};