Hi,
Here is the 10th version of fprobe. In this version, the rethook arm support
([7/12]) comes back :-D, add an internal rethook flag for identify the context
([3-8/12]), and simplifies the ftrace_location lookup loops([2/12]) according
to Jiri's comment :)
The previous version (v9) is here[1];
[1] https://lore.kernel.org/all/164655933970.1674510.3809060481512713846.stgit@devnote2/T/#u
This series introduces the fprobe, the function entry/exit probe
with multiple probe point support for x86, arm64 and powerpc64le.
This also introduces the rethook for hooking function return as same as
the kretprobe does. This abstraction will help us to generalize the fgraph
tracer, because we can just switch to it from the rethook in fprobe,
depending on the kernel configuration.
The patch [1/10] is from Jiri's series[2].
[2] https://lore.kernel.org/all/[email protected]/T/#u
And the patch [9/10] adds the FPROBE_FL_KPROBE_SHARED flag for the case
if user wants to share the same code (or share a same resource) on the
fprobe and the kprobes.
I forcibly updated my kprobes/fprobe branch, you can pull this series
from:
https://git.kernel.org/pub/scm/linux/kernel/git/mhiramat/linux.git kprobes/fprobe
Thank you,
---
Jiri Olsa (1):
ftrace: Add ftrace_set_filter_ips function
Masami Hiramatsu (11):
fprobe: Add ftrace based probe APIs
rethook: Add a generic return hook
rethook: x86: Add rethook x86 implementation
arm64: rethook: Add arm64 rethook implementation
powerpc: Add rethook support
ARM: rethook: Add rethook arm implementation
fprobe: Add exit_handler support
fprobe: Add sample program for fprobe
fprobe: Introduce FPROBE_FL_KPROBE_SHARED flag for fprobe
docs: fprobe: Add fprobe description to ftrace-use.rst
fprobe: Add a selftest for fprobe
Documentation/trace/fprobe.rst | 171 +++++++++++++
Documentation/trace/index.rst | 1
arch/arm/Kconfig | 1
arch/arm/include/asm/stacktrace.h | 4
arch/arm/kernel/stacktrace.c | 6
arch/arm/probes/Makefile | 1
arch/arm/probes/rethook.c | 101 ++++++++
arch/arm64/Kconfig | 1
arch/arm64/include/asm/stacktrace.h | 2
arch/arm64/kernel/probes/Makefile | 1
arch/arm64/kernel/probes/rethook.c | 25 ++
arch/arm64/kernel/probes/rethook_trampoline.S | 87 +++++++
arch/arm64/kernel/stacktrace.c | 7 -
arch/powerpc/Kconfig | 1
arch/powerpc/kernel/Makefile | 1
arch/powerpc/kernel/rethook.c | 72 +++++
arch/x86/Kconfig | 1
arch/x86/include/asm/unwind.h | 8 +
arch/x86/kernel/Makefile | 1
arch/x86/kernel/kprobes/common.h | 1
arch/x86/kernel/rethook.c | 115 +++++++++
include/linux/fprobe.h | 105 ++++++++
include/linux/ftrace.h | 3
include/linux/kprobes.h | 3
include/linux/rethook.h | 100 ++++++++
include/linux/sched.h | 3
kernel/exit.c | 2
kernel/fork.c | 3
kernel/trace/Kconfig | 26 ++
kernel/trace/Makefile | 2
kernel/trace/fprobe.c | 332 +++++++++++++++++++++++++
kernel/trace/ftrace.c | 58 ++++
kernel/trace/rethook.c | 317 ++++++++++++++++++++++++
lib/Kconfig.debug | 12 +
lib/Makefile | 2
lib/test_fprobe.c | 174 +++++++++++++
samples/Kconfig | 7 +
samples/Makefile | 1
samples/fprobe/Makefile | 3
samples/fprobe/fprobe_example.c | 120 +++++++++
40 files changed, 1867 insertions(+), 14 deletions(-)
create mode 100644 Documentation/trace/fprobe.rst
create mode 100644 arch/arm/probes/rethook.c
create mode 100644 arch/arm64/kernel/probes/rethook.c
create mode 100644 arch/arm64/kernel/probes/rethook_trampoline.S
create mode 100644 arch/powerpc/kernel/rethook.c
create mode 100644 arch/x86/kernel/rethook.c
create mode 100644 include/linux/fprobe.h
create mode 100644 include/linux/rethook.h
create mode 100644 kernel/trace/fprobe.c
create mode 100644 kernel/trace/rethook.c
create mode 100644 lib/test_fprobe.c
create mode 100644 samples/fprobe/Makefile
create mode 100644 samples/fprobe/fprobe_example.c
--
Masami Hiramatsu (Linaro) <[email protected]>
Add rethook for x86 implementation. Most of the code has been copied from
kretprobes on x86.
Signed-off-by: Masami Hiramatsu <[email protected]>
---
Changes in v10:
- Add a dummy @mcount to arch_rethook_prepare().
Changes in v5:
- Fix a build error if !CONFIG_KRETPROBES and !CONFIG_RETHOOK.
Changes in v4:
- fix stack backtrace as same as kretprobe does.
---
arch/x86/Kconfig | 1
arch/x86/include/asm/unwind.h | 8 ++-
arch/x86/kernel/Makefile | 1
arch/x86/kernel/kprobes/common.h | 1
arch/x86/kernel/rethook.c | 115 ++++++++++++++++++++++++++++++++++++++
5 files changed, 125 insertions(+), 1 deletion(-)
create mode 100644 arch/x86/kernel/rethook.c
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 9f5bd41bf660..a91373c56d9f 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -220,6 +220,7 @@ config X86
select HAVE_KPROBES_ON_FTRACE
select HAVE_FUNCTION_ERROR_INJECTION
select HAVE_KRETPROBES
+ select HAVE_RETHOOK
select HAVE_KVM
select HAVE_LIVEPATCH if X86_64
select HAVE_MIXED_BREAKPOINTS_REGS
diff --git a/arch/x86/include/asm/unwind.h b/arch/x86/include/asm/unwind.h
index 2a1f8734416d..192df5b2094d 100644
--- a/arch/x86/include/asm/unwind.h
+++ b/arch/x86/include/asm/unwind.h
@@ -5,6 +5,7 @@
#include <linux/sched.h>
#include <linux/ftrace.h>
#include <linux/kprobes.h>
+#include <linux/rethook.h>
#include <asm/ptrace.h>
#include <asm/stacktrace.h>
@@ -16,7 +17,7 @@ struct unwind_state {
unsigned long stack_mask;
struct task_struct *task;
int graph_idx;
-#ifdef CONFIG_KRETPROBES
+#if defined(CONFIG_KRETPROBES) || defined(CONFIG_RETHOOK)
struct llist_node *kr_cur;
#endif
bool error;
@@ -107,6 +108,11 @@ static inline
unsigned long unwind_recover_kretprobe(struct unwind_state *state,
unsigned long addr, unsigned long *addr_p)
{
+#ifdef CONFIG_RETHOOK
+ if (is_rethook_trampoline(addr))
+ return rethook_find_ret_addr(state->task, (unsigned long)addr_p,
+ &state->kr_cur);
+#endif
#ifdef CONFIG_KRETPROBES
return is_kretprobe_trampoline(addr) ?
kretprobe_find_ret_addr(state->task, addr_p, &state->kr_cur) :
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 6aef9ee28a39..792a893a5cc5 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -106,6 +106,7 @@ obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o
obj-$(CONFIG_FTRACE_SYSCALLS) += ftrace.o
obj-$(CONFIG_X86_TSC) += trace_clock.o
obj-$(CONFIG_TRACING) += trace.o
+obj-$(CONFIG_RETHOOK) += rethook.o
obj-$(CONFIG_CRASH_CORE) += crash_core_$(BITS).o
obj-$(CONFIG_KEXEC_CORE) += machine_kexec_$(BITS).o
obj-$(CONFIG_KEXEC_CORE) += relocate_kernel_$(BITS).o crash.o
diff --git a/arch/x86/kernel/kprobes/common.h b/arch/x86/kernel/kprobes/common.h
index 7d3a2e2daf01..c993521d4933 100644
--- a/arch/x86/kernel/kprobes/common.h
+++ b/arch/x86/kernel/kprobes/common.h
@@ -6,6 +6,7 @@
#include <asm/asm.h>
#include <asm/frame.h>
+#include <asm/insn.h>
#ifdef CONFIG_X86_64
diff --git a/arch/x86/kernel/rethook.c b/arch/x86/kernel/rethook.c
new file mode 100644
index 000000000000..3a8aeae11a9f
--- /dev/null
+++ b/arch/x86/kernel/rethook.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * x86 implementation of rethook. Mostly copied from arch/x86/kernel/kprobes/core.c.
+ */
+#include <linux/bug.h>
+#include <linux/rethook.h>
+#include <linux/kprobes.h>
+
+#include "kprobes/common.h"
+
+/*
+ * Called from arch_rethook_trampoline
+ */
+__used __visible void arch_rethook_trampoline_callback(struct pt_regs *regs)
+{
+ unsigned long *frame_pointer;
+
+ /* fixup registers */
+ regs->cs = __KERNEL_CS;
+#ifdef CONFIG_X86_32
+ regs->gs = 0;
+#endif
+ regs->ip = (unsigned long)&arch_rethook_trampoline;
+ regs->orig_ax = ~0UL;
+ regs->sp += sizeof(long);
+ frame_pointer = ®s->sp + 1;
+
+ /*
+ * The return address at 'frame_pointer' is recovered by the
+ * arch_rethook_fixup_return() which called from this
+ * rethook_trampoline_handler().
+ */
+ rethook_trampoline_handler(regs, (unsigned long)frame_pointer);
+
+ /*
+ * Copy FLAGS to 'pt_regs::sp' so that arch_rethook_trapmoline()
+ * can do RET right after POPF.
+ */
+ regs->sp = regs->flags;
+}
+NOKPROBE_SYMBOL(arch_rethook_trampoline_callback);
+
+/*
+ * When a target function returns, this code saves registers and calls
+ * arch_rethook_trampoline_callback(), which calls the rethook handler.
+ */
+asm(
+ ".text\n"
+ ".global arch_rethook_trampoline\n"
+ ".type arch_rethook_trampoline, @function\n"
+ "arch_rethook_trampoline:\n"
+#ifdef CONFIG_X86_64
+ /* Push a fake return address to tell the unwinder it's a kretprobe. */
+ " pushq $arch_rethook_trampoline\n"
+ UNWIND_HINT_FUNC
+ /* Save the 'sp - 8', this will be fixed later. */
+ " pushq %rsp\n"
+ " pushfq\n"
+ SAVE_REGS_STRING
+ " movq %rsp, %rdi\n"
+ " call arch_rethook_trampoline_callback\n"
+ RESTORE_REGS_STRING
+ /* In the callback function, 'regs->flags' is copied to 'regs->sp'. */
+ " addq $8, %rsp\n"
+ " popfq\n"
+#else
+ /* Push a fake return address to tell the unwinder it's a kretprobe. */
+ " pushl $arch_rethook_trampoline\n"
+ UNWIND_HINT_FUNC
+ /* Save the 'sp - 4', this will be fixed later. */
+ " pushl %esp\n"
+ " pushfl\n"
+ SAVE_REGS_STRING
+ " movl %esp, %eax\n"
+ " call arch_rethook_trampoline_callback\n"
+ RESTORE_REGS_STRING
+ /* In the callback function, 'regs->flags' is copied to 'regs->sp'. */
+ " addl $4, %esp\n"
+ " popfl\n"
+#endif
+ " ret\n"
+ ".size arch_rethook_trampoline, .-arch_rethook_trampoline\n"
+);
+NOKPROBE_SYMBOL(arch_rethook_trampoline);
+/*
+ * arch_rethook_trampoline() skips updating frame pointer. The frame pointer
+ * saved in arch_rethook_trampoline_callback() points to the real caller
+ * function's frame pointer. Thus the arch_rethook_trampoline() doesn't have
+ * a standard stack frame with CONFIG_FRAME_POINTER=y.
+ * Let's mark it non-standard function. Anyway, FP unwinder can correctly
+ * unwind without the hint.
+ */
+STACK_FRAME_NON_STANDARD_FP(arch_rethook_trampoline);
+
+/* This is called from rethook_trampoline_handler(). */
+void arch_rethook_fixup_return(struct pt_regs *regs,
+ unsigned long correct_ret_addr)
+{
+ unsigned long *frame_pointer = ®s->sp + 1;
+
+ /* Replace fake return address with real one. */
+ *frame_pointer = correct_ret_addr;
+}
+
+void arch_rethook_prepare(struct rethook_node *rh, struct pt_regs *regs, bool mcount)
+{
+ unsigned long *stack = (unsigned long *)regs->sp;
+
+ rh->ret_addr = stack[0];
+ rh->frame = regs->sp;
+
+ /* Replace the return addr with trampoline addr */
+ stack[0] = (unsigned long) arch_rethook_trampoline;
+}
+NOKPROBE_SYMBOL(arch_rethook_prepare);
Introduce FPROBE_FL_KPROBE_SHARED flag for sharing fprobe callback with
kprobes safely from the viewpoint of recursion.
Since the recursion safety of the fprobe (and ftrace) is a bit different
from the kprobes, this may cause an issue if user wants to run the same
code from the fprobe and the kprobes.
The kprobes has per-cpu 'current_kprobe' variable which protects the
kprobe handler from recursion in any case. On the other hand, the fprobe
uses only ftrace_test_recursion_trylock(), which will allow interrupt
context calls another (or same) fprobe during the fprobe user handler is
running.
This is not a matter in cases if the common callback shared among the
kprobes and the fprobe has its own recursion detection, or it can handle
the recursion in the different contexts (normal/interrupt/NMI.)
But if it relies on the 'current_kprobe' recursion lock, it has to check
kprobe_running() and use kprobe_busy_*() APIs.
Fprobe has FPROBE_FL_KPROBE_SHARED flag to do this. If your common callback
code will be shared with kprobes, please set FPROBE_FL_KPROBE_SHARED
*before* registering the fprobe, like;
fprobe.flags = FPROBE_FL_KPROBE_SHARED;
register_fprobe(&fprobe, "func*", NULL);
This will protect your common callback from the nested call.
Signed-off-by: Masami Hiramatsu <[email protected]>
---
include/linux/fprobe.h | 12 ++++++++++++
include/linux/kprobes.h | 3 +++
kernel/trace/fprobe.c | 19 ++++++++++++++++++-
3 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/include/linux/fprobe.h b/include/linux/fprobe.h
index 8eefec2b485e..1c2bde0ead73 100644
--- a/include/linux/fprobe.h
+++ b/include/linux/fprobe.h
@@ -34,13 +34,25 @@ struct fprobe {
void (*exit_handler)(struct fprobe *fp, unsigned long entry_ip, struct pt_regs *regs);
};
+/* This fprobe is soft-disabled. */
#define FPROBE_FL_DISABLED 1
+/*
+ * This fprobe handler will be shared with kprobes.
+ * This flag must be set before registering.
+ */
+#define FPROBE_FL_KPROBE_SHARED 2
+
static inline bool fprobe_disabled(struct fprobe *fp)
{
return (fp) ? fp->flags & FPROBE_FL_DISABLED : false;
}
+static inline bool fprobe_shared_with_kprobes(struct fprobe *fp)
+{
+ return (fp) ? fp->flags & FPROBE_FL_KPROBE_SHARED : false;
+}
+
#ifdef CONFIG_FPROBE
int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter);
int register_fprobe_ips(struct fprobe *fp, unsigned long *addrs, int num);
diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h
index 19b884353b15..5f1859836deb 100644
--- a/include/linux/kprobes.h
+++ b/include/linux/kprobes.h
@@ -427,6 +427,9 @@ static inline struct kprobe *kprobe_running(void)
{
return NULL;
}
+#define kprobe_busy_begin() do {} while (0)
+#define kprobe_busy_end() do {} while (0)
+
static inline int register_kprobe(struct kprobe *p)
{
return -EOPNOTSUPP;
diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c
index 38073632bfe4..8b2dd5b9dcd1 100644
--- a/kernel/trace/fprobe.c
+++ b/kernel/trace/fprobe.c
@@ -56,6 +56,20 @@ static void fprobe_handler(unsigned long ip, unsigned long parent_ip,
}
NOKPROBE_SYMBOL(fprobe_handler);
+static void fprobe_kprobe_handler(unsigned long ip, unsigned long parent_ip,
+ struct ftrace_ops *ops, struct ftrace_regs *fregs)
+{
+ struct fprobe *fp = container_of(ops, struct fprobe, ops);
+
+ if (unlikely(kprobe_running())) {
+ fp->nmissed++;
+ return;
+ }
+ kprobe_busy_begin();
+ fprobe_handler(ip, parent_ip, ops, fregs);
+ kprobe_busy_end();
+}
+
static void fprobe_exit_handler(struct rethook_node *rh, void *data,
struct pt_regs *regs)
{
@@ -110,7 +124,10 @@ static unsigned long *get_ftrace_locations(const char **syms, int num)
static void fprobe_init(struct fprobe *fp)
{
fp->nmissed = 0;
- fp->ops.func = fprobe_handler;
+ if (fprobe_shared_with_kprobes(fp))
+ fp->ops.func = fprobe_kprobe_handler;
+ else
+ fp->ops.func = fprobe_handler;
fp->ops.flags |= FTRACE_OPS_FL_SAVE_REGS;
}
Add rethook powerpc64 implementation. Most of the code has been copied from
kretprobes on powerpc64.
Signed-off-by: Masami Hiramatsu <[email protected]>
---
Changes in v10:
- Add a dummy @mcount to arch_rethook_prepare().
---
arch/powerpc/Kconfig | 1 +
arch/powerpc/kernel/Makefile | 1 +
arch/powerpc/kernel/rethook.c | 72 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 74 insertions(+)
create mode 100644 arch/powerpc/kernel/rethook.c
diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index b779603978e1..5feaa241fb56 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -229,6 +229,7 @@ config PPC
select HAVE_PERF_EVENTS_NMI if PPC64
select HAVE_PERF_REGS
select HAVE_PERF_USER_STACK_DUMP
+ select HAVE_RETHOOK if KPROBES
select HAVE_REGS_AND_STACK_ACCESS_API
select HAVE_RELIABLE_STACKTRACE
select HAVE_RSEQ
diff --git a/arch/powerpc/kernel/Makefile b/arch/powerpc/kernel/Makefile
index 4d7829399570..feb24ea83ca6 100644
--- a/arch/powerpc/kernel/Makefile
+++ b/arch/powerpc/kernel/Makefile
@@ -115,6 +115,7 @@ obj-$(CONFIG_SMP) += smp.o
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_OPTPROBES) += optprobes.o optprobes_head.o
obj-$(CONFIG_KPROBES_ON_FTRACE) += kprobes-ftrace.o
+obj-$(CONFIG_RETHOOK) += rethook.o
obj-$(CONFIG_UPROBES) += uprobes.o
obj-$(CONFIG_PPC_UDBG_16550) += legacy_serial.o udbg_16550.o
obj-$(CONFIG_SWIOTLB) += dma-swiotlb.o
diff --git a/arch/powerpc/kernel/rethook.c b/arch/powerpc/kernel/rethook.c
new file mode 100644
index 000000000000..a8a128748efa
--- /dev/null
+++ b/arch/powerpc/kernel/rethook.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PowerPC implementation of rethook. This depends on kprobes.
+ */
+
+#include <linux/kprobes.h>
+#include <linux/rethook.h>
+
+/*
+ * Function return trampoline:
+ * - init_kprobes() establishes a probepoint here
+ * - When the probed function returns, this probe
+ * causes the handlers to fire
+ */
+asm(".global arch_rethook_trampoline\n"
+ ".type arch_rethook_trampoline, @function\n"
+ "arch_rethook_trampoline:\n"
+ "nop\n"
+ "blr\n"
+ ".size arch_rethook_trampoline, .-arch_rethook_trampoline\n");
+
+/*
+ * Called when the probe at kretprobe trampoline is hit
+ */
+static int trampoline_rethook_handler(struct kprobe *p, struct pt_regs *regs)
+{
+ unsigned long orig_ret_address;
+
+ orig_ret_address = rethook_trampoline_handler(regs, 0);
+ /*
+ * We get here through one of two paths:
+ * 1. by taking a trap -> kprobe_handler() -> here
+ * 2. by optprobe branch -> optimized_callback() -> opt_pre_handler() -> here
+ *
+ * When going back through (1), we need regs->nip to be setup properly
+ * as it is used to determine the return address from the trap.
+ * For (2), since nip is not honoured with optprobes, we instead setup
+ * the link register properly so that the subsequent 'blr' in
+ * __kretprobe_trampoline jumps back to the right instruction.
+ *
+ * For nip, we should set the address to the previous instruction since
+ * we end up emulating it in kprobe_handler(), which increments the nip
+ * again.
+ */
+ regs_set_return_ip(regs, orig_ret_address - 4);
+ regs->link = orig_ret_address;
+
+ return 0;
+}
+NOKPROBE_SYMBOL(trampoline_rethook_handler);
+
+void arch_rethook_prepare(struct rethook_node *rh, struct pt_regs *regs, bool mcount)
+{
+ rh->ret_addr = regs->link;
+ rh->frame = 0;
+
+ /* Replace the return addr with trampoline addr */
+ regs->link = (unsigned long)arch_rethook_trampoline;
+}
+NOKPROBE_SYMBOL(arch_prepare_kretprobe);
+
+static struct kprobe trampoline_p = {
+ .addr = (kprobe_opcode_t *) &arch_rethook_trampoline,
+ .pre_handler = trampoline_rethook_handler
+};
+
+static int init_arch_rethook(void)
+{
+ return register_kprobe(&trampoline_p);
+}
+
+core_initcall(init_arch_rethook);
From: Jiri Olsa <[email protected]>
Adding ftrace_set_filter_ips function to be able to set filter on
multiple ip addresses at once.
With the kprobe multi attach interface we have cases where we need to
initialize ftrace_ops object with thousands of functions, so having
single function diving into ftrace_hash_move_and_update_ops with
ftrace_lock is faster.
The functions ips are passed as unsigned long array with count.
Signed-off-by: Jiri Olsa <[email protected]>
---
Changes in v6: [Masami]
- Fix a typo and add a comment.
---
include/linux/ftrace.h | 3 ++
kernel/trace/ftrace.c | 58 +++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h
index 9999e29187de..60847cbce0da 100644
--- a/include/linux/ftrace.h
+++ b/include/linux/ftrace.h
@@ -512,6 +512,8 @@ struct dyn_ftrace {
int ftrace_set_filter_ip(struct ftrace_ops *ops, unsigned long ip,
int remove, int reset);
+int ftrace_set_filter_ips(struct ftrace_ops *ops, unsigned long *ips,
+ unsigned int cnt, int remove, int reset);
int ftrace_set_filter(struct ftrace_ops *ops, unsigned char *buf,
int len, int reset);
int ftrace_set_notrace(struct ftrace_ops *ops, unsigned char *buf,
@@ -802,6 +804,7 @@ static inline unsigned long ftrace_location(unsigned long ip)
#define ftrace_regex_open(ops, flag, inod, file) ({ -ENODEV; })
#define ftrace_set_early_filter(ops, buf, enable) do { } while (0)
#define ftrace_set_filter_ip(ops, ip, remove, reset) ({ -ENODEV; })
+#define ftrace_set_filter_ips(ops, ips, cnt, remove, reset) ({ -ENODEV; })
#define ftrace_set_filter(ops, buf, len, reset) ({ -ENODEV; })
#define ftrace_set_notrace(ops, buf, len, reset) ({ -ENODEV; })
#define ftrace_free_filter(ops) do { } while (0)
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index a4b462b6f944..93e992962ada 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -4958,7 +4958,7 @@ ftrace_notrace_write(struct file *file, const char __user *ubuf,
}
static int
-ftrace_match_addr(struct ftrace_hash *hash, unsigned long ip, int remove)
+__ftrace_match_addr(struct ftrace_hash *hash, unsigned long ip, int remove)
{
struct ftrace_func_entry *entry;
@@ -4976,9 +4976,30 @@ ftrace_match_addr(struct ftrace_hash *hash, unsigned long ip, int remove)
return add_hash_entry(hash, ip);
}
+static int
+ftrace_match_addr(struct ftrace_hash *hash, unsigned long *ips,
+ unsigned int cnt, int remove)
+{
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < cnt; i++) {
+ err = __ftrace_match_addr(hash, ips[i], remove);
+ if (err) {
+ /*
+ * This expects the @hash is a temporary hash and if this
+ * fails the caller must free the @hash.
+ */
+ return err;
+ }
+ }
+ return 0;
+}
+
static int
ftrace_set_hash(struct ftrace_ops *ops, unsigned char *buf, int len,
- unsigned long ip, int remove, int reset, int enable)
+ unsigned long *ips, unsigned int cnt,
+ int remove, int reset, int enable)
{
struct ftrace_hash **orig_hash;
struct ftrace_hash *hash;
@@ -5008,8 +5029,8 @@ ftrace_set_hash(struct ftrace_ops *ops, unsigned char *buf, int len,
ret = -EINVAL;
goto out_regex_unlock;
}
- if (ip) {
- ret = ftrace_match_addr(hash, ip, remove);
+ if (ips) {
+ ret = ftrace_match_addr(hash, ips, cnt, remove);
if (ret < 0)
goto out_regex_unlock;
}
@@ -5026,10 +5047,10 @@ ftrace_set_hash(struct ftrace_ops *ops, unsigned char *buf, int len,
}
static int
-ftrace_set_addr(struct ftrace_ops *ops, unsigned long ip, int remove,
- int reset, int enable)
+ftrace_set_addr(struct ftrace_ops *ops, unsigned long *ips, unsigned int cnt,
+ int remove, int reset, int enable)
{
- return ftrace_set_hash(ops, NULL, 0, ip, remove, reset, enable);
+ return ftrace_set_hash(ops, NULL, 0, ips, cnt, remove, reset, enable);
}
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
@@ -5634,10 +5655,29 @@ int ftrace_set_filter_ip(struct ftrace_ops *ops, unsigned long ip,
int remove, int reset)
{
ftrace_ops_init(ops);
- return ftrace_set_addr(ops, ip, remove, reset, 1);
+ return ftrace_set_addr(ops, &ip, 1, remove, reset, 1);
}
EXPORT_SYMBOL_GPL(ftrace_set_filter_ip);
+/**
+ * ftrace_set_filter_ips - set functions to filter on in ftrace by addresses
+ * @ops - the ops to set the filter with
+ * @ips - the array of addresses to add to or remove from the filter.
+ * @cnt - the number of addresses in @ips
+ * @remove - non zero to remove ips from the filter
+ * @reset - non zero to reset all filters before applying this filter.
+ *
+ * Filters denote which functions should be enabled when tracing is enabled
+ * If @ips array or any ip specified within is NULL , it fails to update filter.
+ */
+int ftrace_set_filter_ips(struct ftrace_ops *ops, unsigned long *ips,
+ unsigned int cnt, int remove, int reset)
+{
+ ftrace_ops_init(ops);
+ return ftrace_set_addr(ops, ips, cnt, remove, reset, 1);
+}
+EXPORT_SYMBOL_GPL(ftrace_set_filter_ips);
+
/**
* ftrace_ops_set_global_filter - setup ops to use global filters
* @ops - the ops which will use the global filters
@@ -5659,7 +5699,7 @@ static int
ftrace_set_regex(struct ftrace_ops *ops, unsigned char *buf, int len,
int reset, int enable)
{
- return ftrace_set_hash(ops, buf, len, 0, 0, reset, enable);
+ return ftrace_set_hash(ops, buf, len, NULL, 0, 0, reset, enable);
}
/**
Add a KUnit based selftest for fprobe interface.
Signed-off-by: Masami Hiramatsu <[email protected]>
---
Changes in v9:
- Rename fprobe_target* to fprobe_selftest_target*.
- Find the correct expected ip by ftrace_location_range().
- Since the ftrace_location_range() is not exposed to module, make
this test only for embedded.
- Add entry only test.
- Reset the fprobe structure before reuse it.
---
lib/Kconfig.debug | 12 ++++
lib/Makefile | 2 +
lib/test_fprobe.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 188 insertions(+)
create mode 100644 lib/test_fprobe.c
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 14b89aa37c5c..ffc469a12afc 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2100,6 +2100,18 @@ config KPROBES_SANITY_TEST
Say N if you are unsure.
+config FPROBE_SANITY_TEST
+ bool "Self test for fprobe"
+ depends on DEBUG_KERNEL
+ depends on FPROBE
+ depends on KUNIT
+ help
+ This option will enable testing the fprobe when the system boot.
+ A series of tests are made to verify that the fprobe is functioning
+ properly.
+
+ Say N if you are unsure.
+
config BACKTRACE_SELF_TEST
tristate "Self test for the backtrace code"
depends on DEBUG_KERNEL
diff --git a/lib/Makefile b/lib/Makefile
index 300f569c626b..154008764b16 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -103,6 +103,8 @@ obj-$(CONFIG_TEST_HMM) += test_hmm.o
obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
+CFLAGS_test_fprobe.o += $(CC_FLAGS_FTRACE)
+obj-$(CONFIG_FPROBE_SANITY_TEST) += test_fprobe.o
#
# CFLAGS for compiling floating point code inside the kernel. x86/Makefile turns
# off the generation of FPU/SSE* instructions for kernel proper but FPU_FLAGS
diff --git a/lib/test_fprobe.c b/lib/test_fprobe.c
new file mode 100644
index 000000000000..ed70637a2ffa
--- /dev/null
+++ b/lib/test_fprobe.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * test_fprobe.c - simple sanity test for fprobe
+ */
+
+#include <linux/kernel.h>
+#include <linux/fprobe.h>
+#include <linux/random.h>
+#include <kunit/test.h>
+
+#define div_factor 3
+
+static struct kunit *current_test;
+
+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 unsigned long target_ip;
+static unsigned long target2_ip;
+
+static noinline u32 fprobe_selftest_target(u32 value)
+{
+ return (value / div_factor);
+}
+
+static noinline u32 fprobe_selftest_target2(u32 value)
+{
+ return (value / div_factor) + 1;
+}
+
+static notrace void fp_entry_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
+{
+ KUNIT_EXPECT_FALSE(current_test, preemptible());
+ /* This can be called on the fprobe_selftest_target and the fprobe_selftest_target2 */
+ if (ip != target_ip)
+ KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
+ entry_val = (rand1 / div_factor);
+}
+
+static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
+{
+ unsigned long ret = regs_return_value(regs);
+
+ KUNIT_EXPECT_FALSE(current_test, preemptible());
+ if (ip != target_ip) {
+ KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
+ KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor) + 1);
+ } else
+ KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor));
+ KUNIT_EXPECT_EQ(current_test, entry_val, (rand1 / div_factor));
+ exit_val = entry_val + div_factor;
+}
+
+/* Test entry only (no rethook) */
+static void test_fprobe_entry(struct kunit *test)
+{
+ struct fprobe fp_entry = {
+ .entry_handler = fp_entry_handler,
+ };
+
+ current_test = test;
+
+ /* Before register, unregister should be failed. */
+ KUNIT_EXPECT_NE(test, 0, unregister_fprobe(&fp_entry));
+ KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp_entry, "fprobe_selftest_target*", NULL));
+
+ entry_val = 0;
+ exit_val = 0;
+ target(rand1);
+ KUNIT_EXPECT_NE(test, 0, entry_val);
+ KUNIT_EXPECT_EQ(test, 0, exit_val);
+
+ entry_val = 0;
+ exit_val = 0;
+ target2(rand1);
+ KUNIT_EXPECT_NE(test, 0, entry_val);
+ KUNIT_EXPECT_EQ(test, 0, exit_val);
+
+ KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp_entry));
+}
+
+static void test_fprobe(struct kunit *test)
+{
+ struct fprobe fp = {
+ .entry_handler = fp_entry_handler,
+ .exit_handler = fp_exit_handler,
+ };
+
+ current_test = test;
+ KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp, "fprobe_selftest_target*", NULL));
+
+ entry_val = 0;
+ exit_val = 0;
+ target(rand1);
+ KUNIT_EXPECT_NE(test, 0, entry_val);
+ KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
+
+ entry_val = 0;
+ exit_val = 0;
+ target2(rand1);
+ KUNIT_EXPECT_NE(test, 0, entry_val);
+ KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
+
+ KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
+}
+
+static void test_fprobe_syms(struct kunit *test)
+{
+ static const char *syms[] = {"fprobe_selftest_target", "fprobe_selftest_target2"};
+ struct fprobe fp = {
+ .entry_handler = fp_entry_handler,
+ .exit_handler = fp_exit_handler,
+ };
+
+ current_test = test;
+ KUNIT_EXPECT_EQ(test, 0, register_fprobe_syms(&fp, syms, 2));
+
+ entry_val = 0;
+ exit_val = 0;
+ target(rand1);
+ KUNIT_EXPECT_NE(test, 0, entry_val);
+ KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
+
+ entry_val = 0;
+ exit_val = 0;
+ target2(rand1);
+ KUNIT_EXPECT_NE(test, 0, entry_val);
+ KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
+
+ KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
+}
+
+static unsigned long get_ftrace_location(void *func)
+{
+ unsigned long size, addr = (unsigned long)func;
+
+ if (!kallsyms_lookup_size_offset(addr, &size, NULL) || !size)
+ return 0;
+
+ return ftrace_location_range(addr, addr + size - 1);
+}
+
+static int fprobe_test_init(struct kunit *test)
+{
+ do {
+ rand1 = prandom_u32();
+ } while (rand1 <= div_factor);
+
+ target = fprobe_selftest_target;
+ target2 = fprobe_selftest_target2;
+ target_ip = get_ftrace_location(target);
+ target2_ip = get_ftrace_location(target2);
+
+ return 0;
+}
+
+static struct kunit_case fprobe_testcases[] = {
+ KUNIT_CASE(test_fprobe_entry),
+ KUNIT_CASE(test_fprobe),
+ KUNIT_CASE(test_fprobe_syms),
+ {}
+};
+
+static struct kunit_suite fprobe_test_suite = {
+ .name = "fprobe_test",
+ .init = fprobe_test_init,
+ .test_cases = fprobe_testcases,
+};
+
+kunit_test_suites(&fprobe_test_suite);
+
+MODULE_LICENSE("GPL");
On Wed, 9 Mar 2022 16:40:00 -0800
Andrii Nakryiko <[email protected]> wrote:
> On Wed, Mar 9, 2022 at 4:17 PM Masami Hiramatsu <[email protected]> wrote:
> >
> > Hi,
> >
> > On Tue, 8 Mar 2022 20:10:48 +0900
> > Masami Hiramatsu <[email protected]> wrote:
> >
> > > Add a KUnit based selftest for fprobe interface.
> > >
> > > Signed-off-by: Masami Hiramatsu <[email protected]>
> > > ---
> > > Changes in v9:
> > > - Rename fprobe_target* to fprobe_selftest_target*.
> > > - Find the correct expected ip by ftrace_location_range().
> > > - Since the ftrace_location_range() is not exposed to module, make
> > > this test only for embedded.
> > > - Add entry only test.
> > > - Reset the fprobe structure before reuse it.
> > > ---
> > > lib/Kconfig.debug | 12 ++++
> > > lib/Makefile | 2 +
> > > lib/test_fprobe.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> > > 3 files changed, 188 insertions(+)
> > > create mode 100644 lib/test_fprobe.c
> > >
> > > diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> > > index 14b89aa37c5c..ffc469a12afc 100644
> > > --- a/lib/Kconfig.debug
> > > +++ b/lib/Kconfig.debug
> > > @@ -2100,6 +2100,18 @@ config KPROBES_SANITY_TEST
> > >
> > > Say N if you are unsure.
> > >
> > > +config FPROBE_SANITY_TEST
> > > + bool "Self test for fprobe"
> > > + depends on DEBUG_KERNEL
> > > + depends on FPROBE
> > > + depends on KUNIT
> >
> > Hmm, this caused a build error with allmodconfig because KUNIT=m but FPROBE_SANITY_TEST=y.
> > Let me fix this issue.
>
> Please base on top of bpf-next and add [PATCH v11 bpf-next] to subject.
OK, let me rebase on it.
There are master and for-next branch, which one is better to use?
Thank you,
>
> >
> > Thank you,
> >
> > > + help
> > > + This option will enable testing the fprobe when the system boot.
> > > + A series of tests are made to verify that the fprobe is functioning
> > > + properly.
> > > +
> > > + Say N if you are unsure.
> > > +
> > > config BACKTRACE_SELF_TEST
> > > tristate "Self test for the backtrace code"
> > > depends on DEBUG_KERNEL
> > > diff --git a/lib/Makefile b/lib/Makefile
> > > index 300f569c626b..154008764b16 100644
> > > --- a/lib/Makefile
> > > +++ b/lib/Makefile
> > > @@ -103,6 +103,8 @@ obj-$(CONFIG_TEST_HMM) += test_hmm.o
> > > obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
> > > obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
> > > obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
> > > +CFLAGS_test_fprobe.o += $(CC_FLAGS_FTRACE)
> > > +obj-$(CONFIG_FPROBE_SANITY_TEST) += test_fprobe.o
> > > #
> > > # CFLAGS for compiling floating point code inside the kernel. x86/Makefile turns
> > > # off the generation of FPU/SSE* instructions for kernel proper but FPU_FLAGS
> > > diff --git a/lib/test_fprobe.c b/lib/test_fprobe.c
> > > new file mode 100644
> > > index 000000000000..ed70637a2ffa
> > > --- /dev/null
> > > +++ b/lib/test_fprobe.c
> > > @@ -0,0 +1,174 @@
> > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > +/*
> > > + * test_fprobe.c - simple sanity test for fprobe
> > > + */
> > > +
> > > +#include <linux/kernel.h>
> > > +#include <linux/fprobe.h>
> > > +#include <linux/random.h>
> > > +#include <kunit/test.h>
> > > +
> > > +#define div_factor 3
> > > +
> > > +static struct kunit *current_test;
> > > +
> > > +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 unsigned long target_ip;
> > > +static unsigned long target2_ip;
> > > +
> > > +static noinline u32 fprobe_selftest_target(u32 value)
> > > +{
> > > + return (value / div_factor);
> > > +}
> > > +
> > > +static noinline u32 fprobe_selftest_target2(u32 value)
> > > +{
> > > + return (value / div_factor) + 1;
> > > +}
> > > +
> > > +static notrace void fp_entry_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> > > +{
> > > + KUNIT_EXPECT_FALSE(current_test, preemptible());
> > > + /* This can be called on the fprobe_selftest_target and the fprobe_selftest_target2 */
> > > + if (ip != target_ip)
> > > + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> > > + entry_val = (rand1 / div_factor);
> > > +}
> > > +
> > > +static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> > > +{
> > > + unsigned long ret = regs_return_value(regs);
> > > +
> > > + KUNIT_EXPECT_FALSE(current_test, preemptible());
> > > + if (ip != target_ip) {
> > > + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> > > + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor) + 1);
> > > + } else
> > > + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor));
> > > + KUNIT_EXPECT_EQ(current_test, entry_val, (rand1 / div_factor));
> > > + exit_val = entry_val + div_factor;
> > > +}
> > > +
> > > +/* Test entry only (no rethook) */
> > > +static void test_fprobe_entry(struct kunit *test)
> > > +{
> > > + struct fprobe fp_entry = {
> > > + .entry_handler = fp_entry_handler,
> > > + };
> > > +
> > > + current_test = test;
> > > +
> > > + /* Before register, unregister should be failed. */
> > > + KUNIT_EXPECT_NE(test, 0, unregister_fprobe(&fp_entry));
> > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp_entry, "fprobe_selftest_target*", NULL));
> > > +
> > > + entry_val = 0;
> > > + exit_val = 0;
> > > + target(rand1);
> > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > + KUNIT_EXPECT_EQ(test, 0, exit_val);
> > > +
> > > + entry_val = 0;
> > > + exit_val = 0;
> > > + target2(rand1);
> > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > + KUNIT_EXPECT_EQ(test, 0, exit_val);
> > > +
> > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp_entry));
> > > +}
> > > +
> > > +static void test_fprobe(struct kunit *test)
> > > +{
> > > + struct fprobe fp = {
> > > + .entry_handler = fp_entry_handler,
> > > + .exit_handler = fp_exit_handler,
> > > + };
> > > +
> > > + current_test = test;
> > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp, "fprobe_selftest_target*", NULL));
> > > +
> > > + entry_val = 0;
> > > + exit_val = 0;
> > > + target(rand1);
> > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > +
> > > + entry_val = 0;
> > > + exit_val = 0;
> > > + target2(rand1);
> > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > +
> > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> > > +}
> > > +
> > > +static void test_fprobe_syms(struct kunit *test)
> > > +{
> > > + static const char *syms[] = {"fprobe_selftest_target", "fprobe_selftest_target2"};
> > > + struct fprobe fp = {
> > > + .entry_handler = fp_entry_handler,
> > > + .exit_handler = fp_exit_handler,
> > > + };
> > > +
> > > + current_test = test;
> > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe_syms(&fp, syms, 2));
> > > +
> > > + entry_val = 0;
> > > + exit_val = 0;
> > > + target(rand1);
> > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > +
> > > + entry_val = 0;
> > > + exit_val = 0;
> > > + target2(rand1);
> > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > +
> > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> > > +}
> > > +
> > > +static unsigned long get_ftrace_location(void *func)
> > > +{
> > > + unsigned long size, addr = (unsigned long)func;
> > > +
> > > + if (!kallsyms_lookup_size_offset(addr, &size, NULL) || !size)
> > > + return 0;
> > > +
> > > + return ftrace_location_range(addr, addr + size - 1);
> > > +}
> > > +
> > > +static int fprobe_test_init(struct kunit *test)
> > > +{
> > > + do {
> > > + rand1 = prandom_u32();
> > > + } while (rand1 <= div_factor);
> > > +
> > > + target = fprobe_selftest_target;
> > > + target2 = fprobe_selftest_target2;
> > > + target_ip = get_ftrace_location(target);
> > > + target2_ip = get_ftrace_location(target2);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static struct kunit_case fprobe_testcases[] = {
> > > + KUNIT_CASE(test_fprobe_entry),
> > > + KUNIT_CASE(test_fprobe),
> > > + KUNIT_CASE(test_fprobe_syms),
> > > + {}
> > > +};
> > > +
> > > +static struct kunit_suite fprobe_test_suite = {
> > > + .name = "fprobe_test",
> > > + .init = fprobe_test_init,
> > > + .test_cases = fprobe_testcases,
> > > +};
> > > +
> > > +kunit_test_suites(&fprobe_test_suite);
> > > +
> > > +MODULE_LICENSE("GPL");
> > >
> >
> >
> > --
> > Masami Hiramatsu <[email protected]>
--
Masami Hiramatsu <[email protected]>
On Wed, Mar 9, 2022 at 4:17 PM Masami Hiramatsu <[email protected]> wrote:
>
> Hi,
>
> On Tue, 8 Mar 2022 20:10:48 +0900
> Masami Hiramatsu <[email protected]> wrote:
>
> > Add a KUnit based selftest for fprobe interface.
> >
> > Signed-off-by: Masami Hiramatsu <[email protected]>
> > ---
> > Changes in v9:
> > - Rename fprobe_target* to fprobe_selftest_target*.
> > - Find the correct expected ip by ftrace_location_range().
> > - Since the ftrace_location_range() is not exposed to module, make
> > this test only for embedded.
> > - Add entry only test.
> > - Reset the fprobe structure before reuse it.
> > ---
> > lib/Kconfig.debug | 12 ++++
> > lib/Makefile | 2 +
> > lib/test_fprobe.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 188 insertions(+)
> > create mode 100644 lib/test_fprobe.c
> >
> > diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> > index 14b89aa37c5c..ffc469a12afc 100644
> > --- a/lib/Kconfig.debug
> > +++ b/lib/Kconfig.debug
> > @@ -2100,6 +2100,18 @@ config KPROBES_SANITY_TEST
> >
> > Say N if you are unsure.
> >
> > +config FPROBE_SANITY_TEST
> > + bool "Self test for fprobe"
> > + depends on DEBUG_KERNEL
> > + depends on FPROBE
> > + depends on KUNIT
>
> Hmm, this caused a build error with allmodconfig because KUNIT=m but FPROBE_SANITY_TEST=y.
> Let me fix this issue.
Please base on top of bpf-next and add [PATCH v11 bpf-next] to subject.
>
> Thank you,
>
> > + help
> > + This option will enable testing the fprobe when the system boot.
> > + A series of tests are made to verify that the fprobe is functioning
> > + properly.
> > +
> > + Say N if you are unsure.
> > +
> > config BACKTRACE_SELF_TEST
> > tristate "Self test for the backtrace code"
> > depends on DEBUG_KERNEL
> > diff --git a/lib/Makefile b/lib/Makefile
> > index 300f569c626b..154008764b16 100644
> > --- a/lib/Makefile
> > +++ b/lib/Makefile
> > @@ -103,6 +103,8 @@ obj-$(CONFIG_TEST_HMM) += test_hmm.o
> > obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
> > obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
> > obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
> > +CFLAGS_test_fprobe.o += $(CC_FLAGS_FTRACE)
> > +obj-$(CONFIG_FPROBE_SANITY_TEST) += test_fprobe.o
> > #
> > # CFLAGS for compiling floating point code inside the kernel. x86/Makefile turns
> > # off the generation of FPU/SSE* instructions for kernel proper but FPU_FLAGS
> > diff --git a/lib/test_fprobe.c b/lib/test_fprobe.c
> > new file mode 100644
> > index 000000000000..ed70637a2ffa
> > --- /dev/null
> > +++ b/lib/test_fprobe.c
> > @@ -0,0 +1,174 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * test_fprobe.c - simple sanity test for fprobe
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/fprobe.h>
> > +#include <linux/random.h>
> > +#include <kunit/test.h>
> > +
> > +#define div_factor 3
> > +
> > +static struct kunit *current_test;
> > +
> > +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 unsigned long target_ip;
> > +static unsigned long target2_ip;
> > +
> > +static noinline u32 fprobe_selftest_target(u32 value)
> > +{
> > + return (value / div_factor);
> > +}
> > +
> > +static noinline u32 fprobe_selftest_target2(u32 value)
> > +{
> > + return (value / div_factor) + 1;
> > +}
> > +
> > +static notrace void fp_entry_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> > +{
> > + KUNIT_EXPECT_FALSE(current_test, preemptible());
> > + /* This can be called on the fprobe_selftest_target and the fprobe_selftest_target2 */
> > + if (ip != target_ip)
> > + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> > + entry_val = (rand1 / div_factor);
> > +}
> > +
> > +static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> > +{
> > + unsigned long ret = regs_return_value(regs);
> > +
> > + KUNIT_EXPECT_FALSE(current_test, preemptible());
> > + if (ip != target_ip) {
> > + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> > + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor) + 1);
> > + } else
> > + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor));
> > + KUNIT_EXPECT_EQ(current_test, entry_val, (rand1 / div_factor));
> > + exit_val = entry_val + div_factor;
> > +}
> > +
> > +/* Test entry only (no rethook) */
> > +static void test_fprobe_entry(struct kunit *test)
> > +{
> > + struct fprobe fp_entry = {
> > + .entry_handler = fp_entry_handler,
> > + };
> > +
> > + current_test = test;
> > +
> > + /* Before register, unregister should be failed. */
> > + KUNIT_EXPECT_NE(test, 0, unregister_fprobe(&fp_entry));
> > + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp_entry, "fprobe_selftest_target*", NULL));
> > +
> > + entry_val = 0;
> > + exit_val = 0;
> > + target(rand1);
> > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > + KUNIT_EXPECT_EQ(test, 0, exit_val);
> > +
> > + entry_val = 0;
> > + exit_val = 0;
> > + target2(rand1);
> > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > + KUNIT_EXPECT_EQ(test, 0, exit_val);
> > +
> > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp_entry));
> > +}
> > +
> > +static void test_fprobe(struct kunit *test)
> > +{
> > + struct fprobe fp = {
> > + .entry_handler = fp_entry_handler,
> > + .exit_handler = fp_exit_handler,
> > + };
> > +
> > + current_test = test;
> > + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp, "fprobe_selftest_target*", NULL));
> > +
> > + entry_val = 0;
> > + exit_val = 0;
> > + target(rand1);
> > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > +
> > + entry_val = 0;
> > + exit_val = 0;
> > + target2(rand1);
> > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > +
> > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> > +}
> > +
> > +static void test_fprobe_syms(struct kunit *test)
> > +{
> > + static const char *syms[] = {"fprobe_selftest_target", "fprobe_selftest_target2"};
> > + struct fprobe fp = {
> > + .entry_handler = fp_entry_handler,
> > + .exit_handler = fp_exit_handler,
> > + };
> > +
> > + current_test = test;
> > + KUNIT_EXPECT_EQ(test, 0, register_fprobe_syms(&fp, syms, 2));
> > +
> > + entry_val = 0;
> > + exit_val = 0;
> > + target(rand1);
> > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > +
> > + entry_val = 0;
> > + exit_val = 0;
> > + target2(rand1);
> > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > +
> > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> > +}
> > +
> > +static unsigned long get_ftrace_location(void *func)
> > +{
> > + unsigned long size, addr = (unsigned long)func;
> > +
> > + if (!kallsyms_lookup_size_offset(addr, &size, NULL) || !size)
> > + return 0;
> > +
> > + return ftrace_location_range(addr, addr + size - 1);
> > +}
> > +
> > +static int fprobe_test_init(struct kunit *test)
> > +{
> > + do {
> > + rand1 = prandom_u32();
> > + } while (rand1 <= div_factor);
> > +
> > + target = fprobe_selftest_target;
> > + target2 = fprobe_selftest_target2;
> > + target_ip = get_ftrace_location(target);
> > + target2_ip = get_ftrace_location(target2);
> > +
> > + return 0;
> > +}
> > +
> > +static struct kunit_case fprobe_testcases[] = {
> > + KUNIT_CASE(test_fprobe_entry),
> > + KUNIT_CASE(test_fprobe),
> > + KUNIT_CASE(test_fprobe_syms),
> > + {}
> > +};
> > +
> > +static struct kunit_suite fprobe_test_suite = {
> > + .name = "fprobe_test",
> > + .init = fprobe_test_init,
> > + .test_cases = fprobe_testcases,
> > +};
> > +
> > +kunit_test_suites(&fprobe_test_suite);
> > +
> > +MODULE_LICENSE("GPL");
> >
>
>
> --
> Masami Hiramatsu <[email protected]>
On Thu, Mar 10, 2022 at 7:11 AM Masami Hiramatsu <[email protected]> wrote:
>
> On Wed, 9 Mar 2022 16:40:00 -0800
> Andrii Nakryiko <[email protected]> wrote:
>
> > On Wed, Mar 9, 2022 at 4:17 PM Masami Hiramatsu <[email protected]> wrote:
> > >
> > > Hi,
> > >
> > > On Tue, 8 Mar 2022 20:10:48 +0900
> > > Masami Hiramatsu <[email protected]> wrote:
> > >
> > > > Add a KUnit based selftest for fprobe interface.
> > > >
> > > > Signed-off-by: Masami Hiramatsu <[email protected]>
> > > > ---
> > > > Changes in v9:
> > > > - Rename fprobe_target* to fprobe_selftest_target*.
> > > > - Find the correct expected ip by ftrace_location_range().
> > > > - Since the ftrace_location_range() is not exposed to module, make
> > > > this test only for embedded.
> > > > - Add entry only test.
> > > > - Reset the fprobe structure before reuse it.
> > > > ---
> > > > lib/Kconfig.debug | 12 ++++
> > > > lib/Makefile | 2 +
> > > > lib/test_fprobe.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> > > > 3 files changed, 188 insertions(+)
> > > > create mode 100644 lib/test_fprobe.c
> > > >
> > > > diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> > > > index 14b89aa37c5c..ffc469a12afc 100644
> > > > --- a/lib/Kconfig.debug
> > > > +++ b/lib/Kconfig.debug
> > > > @@ -2100,6 +2100,18 @@ config KPROBES_SANITY_TEST
> > > >
> > > > Say N if you are unsure.
> > > >
> > > > +config FPROBE_SANITY_TEST
> > > > + bool "Self test for fprobe"
> > > > + depends on DEBUG_KERNEL
> > > > + depends on FPROBE
> > > > + depends on KUNIT
> > >
> > > Hmm, this caused a build error with allmodconfig because KUNIT=m but FPROBE_SANITY_TEST=y.
> > > Let me fix this issue.
> >
> > Please base on top of bpf-next and add [PATCH v11 bpf-next] to subject.
>
> OK, let me rebase on it.
> There are master and for-next branch, which one is better to use?
>
Sorry, missed your reply earlier. Always rebase against master.
You forgot to add "bpf-next" into [PATCH] prefix, so I had to manually
mark it in patchworks as delegated to bpf queue (this is necessary for
our CI to properly pick it up). For future submissions to bpf-next,
please don't forget to add "bpf-next" marker.
> Thank you,
>
> >
> > >
> > > Thank you,
> > >
> > > > + help
> > > > + This option will enable testing the fprobe when the system boot.
> > > > + A series of tests are made to verify that the fprobe is functioning
> > > > + properly.
> > > > +
> > > > + Say N if you are unsure.
> > > > +
> > > > config BACKTRACE_SELF_TEST
> > > > tristate "Self test for the backtrace code"
> > > > depends on DEBUG_KERNEL
> > > > diff --git a/lib/Makefile b/lib/Makefile
> > > > index 300f569c626b..154008764b16 100644
> > > > --- a/lib/Makefile
> > > > +++ b/lib/Makefile
> > > > @@ -103,6 +103,8 @@ obj-$(CONFIG_TEST_HMM) += test_hmm.o
> > > > obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
> > > > obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
> > > > obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
> > > > +CFLAGS_test_fprobe.o += $(CC_FLAGS_FTRACE)
> > > > +obj-$(CONFIG_FPROBE_SANITY_TEST) += test_fprobe.o
> > > > #
> > > > # CFLAGS for compiling floating point code inside the kernel. x86/Makefile turns
> > > > # off the generation of FPU/SSE* instructions for kernel proper but FPU_FLAGS
> > > > diff --git a/lib/test_fprobe.c b/lib/test_fprobe.c
> > > > new file mode 100644
> > > > index 000000000000..ed70637a2ffa
> > > > --- /dev/null
> > > > +++ b/lib/test_fprobe.c
> > > > @@ -0,0 +1,174 @@
> > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > +/*
> > > > + * test_fprobe.c - simple sanity test for fprobe
> > > > + */
> > > > +
> > > > +#include <linux/kernel.h>
> > > > +#include <linux/fprobe.h>
> > > > +#include <linux/random.h>
> > > > +#include <kunit/test.h>
> > > > +
> > > > +#define div_factor 3
> > > > +
> > > > +static struct kunit *current_test;
> > > > +
> > > > +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 unsigned long target_ip;
> > > > +static unsigned long target2_ip;
> > > > +
> > > > +static noinline u32 fprobe_selftest_target(u32 value)
> > > > +{
> > > > + return (value / div_factor);
> > > > +}
> > > > +
> > > > +static noinline u32 fprobe_selftest_target2(u32 value)
> > > > +{
> > > > + return (value / div_factor) + 1;
> > > > +}
> > > > +
> > > > +static notrace void fp_entry_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> > > > +{
> > > > + KUNIT_EXPECT_FALSE(current_test, preemptible());
> > > > + /* This can be called on the fprobe_selftest_target and the fprobe_selftest_target2 */
> > > > + if (ip != target_ip)
> > > > + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> > > > + entry_val = (rand1 / div_factor);
> > > > +}
> > > > +
> > > > +static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> > > > +{
> > > > + unsigned long ret = regs_return_value(regs);
> > > > +
> > > > + KUNIT_EXPECT_FALSE(current_test, preemptible());
> > > > + if (ip != target_ip) {
> > > > + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> > > > + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor) + 1);
> > > > + } else
> > > > + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor));
> > > > + KUNIT_EXPECT_EQ(current_test, entry_val, (rand1 / div_factor));
> > > > + exit_val = entry_val + div_factor;
> > > > +}
> > > > +
> > > > +/* Test entry only (no rethook) */
> > > > +static void test_fprobe_entry(struct kunit *test)
> > > > +{
> > > > + struct fprobe fp_entry = {
> > > > + .entry_handler = fp_entry_handler,
> > > > + };
> > > > +
> > > > + current_test = test;
> > > > +
> > > > + /* Before register, unregister should be failed. */
> > > > + KUNIT_EXPECT_NE(test, 0, unregister_fprobe(&fp_entry));
> > > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp_entry, "fprobe_selftest_target*", NULL));
> > > > +
> > > > + entry_val = 0;
> > > > + exit_val = 0;
> > > > + target(rand1);
> > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > + KUNIT_EXPECT_EQ(test, 0, exit_val);
> > > > +
> > > > + entry_val = 0;
> > > > + exit_val = 0;
> > > > + target2(rand1);
> > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > + KUNIT_EXPECT_EQ(test, 0, exit_val);
> > > > +
> > > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp_entry));
> > > > +}
> > > > +
> > > > +static void test_fprobe(struct kunit *test)
> > > > +{
> > > > + struct fprobe fp = {
> > > > + .entry_handler = fp_entry_handler,
> > > > + .exit_handler = fp_exit_handler,
> > > > + };
> > > > +
> > > > + current_test = test;
> > > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp, "fprobe_selftest_target*", NULL));
> > > > +
> > > > + entry_val = 0;
> > > > + exit_val = 0;
> > > > + target(rand1);
> > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > > +
> > > > + entry_val = 0;
> > > > + exit_val = 0;
> > > > + target2(rand1);
> > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > > +
> > > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> > > > +}
> > > > +
> > > > +static void test_fprobe_syms(struct kunit *test)
> > > > +{
> > > > + static const char *syms[] = {"fprobe_selftest_target", "fprobe_selftest_target2"};
> > > > + struct fprobe fp = {
> > > > + .entry_handler = fp_entry_handler,
> > > > + .exit_handler = fp_exit_handler,
> > > > + };
> > > > +
> > > > + current_test = test;
> > > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe_syms(&fp, syms, 2));
> > > > +
> > > > + entry_val = 0;
> > > > + exit_val = 0;
> > > > + target(rand1);
> > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > > +
> > > > + entry_val = 0;
> > > > + exit_val = 0;
> > > > + target2(rand1);
> > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > > +
> > > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> > > > +}
> > > > +
> > > > +static unsigned long get_ftrace_location(void *func)
> > > > +{
> > > > + unsigned long size, addr = (unsigned long)func;
> > > > +
> > > > + if (!kallsyms_lookup_size_offset(addr, &size, NULL) || !size)
> > > > + return 0;
> > > > +
> > > > + return ftrace_location_range(addr, addr + size - 1);
> > > > +}
> > > > +
> > > > +static int fprobe_test_init(struct kunit *test)
> > > > +{
> > > > + do {
> > > > + rand1 = prandom_u32();
> > > > + } while (rand1 <= div_factor);
> > > > +
> > > > + target = fprobe_selftest_target;
> > > > + target2 = fprobe_selftest_target2;
> > > > + target_ip = get_ftrace_location(target);
> > > > + target2_ip = get_ftrace_location(target2);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static struct kunit_case fprobe_testcases[] = {
> > > > + KUNIT_CASE(test_fprobe_entry),
> > > > + KUNIT_CASE(test_fprobe),
> > > > + KUNIT_CASE(test_fprobe_syms),
> > > > + {}
> > > > +};
> > > > +
> > > > +static struct kunit_suite fprobe_test_suite = {
> > > > + .name = "fprobe_test",
> > > > + .init = fprobe_test_init,
> > > > + .test_cases = fprobe_testcases,
> > > > +};
> > > > +
> > > > +kunit_test_suites(&fprobe_test_suite);
> > > > +
> > > > +MODULE_LICENSE("GPL");
> > > >
> > >
> > >
> > > --
> > > Masami Hiramatsu <[email protected]>
>
>
> --
> Masami Hiramatsu <[email protected]>
Hi,
On Tue, 8 Mar 2022 20:10:48 +0900
Masami Hiramatsu <[email protected]> wrote:
> Add a KUnit based selftest for fprobe interface.
>
> Signed-off-by: Masami Hiramatsu <[email protected]>
> ---
> Changes in v9:
> - Rename fprobe_target* to fprobe_selftest_target*.
> - Find the correct expected ip by ftrace_location_range().
> - Since the ftrace_location_range() is not exposed to module, make
> this test only for embedded.
> - Add entry only test.
> - Reset the fprobe structure before reuse it.
> ---
> lib/Kconfig.debug | 12 ++++
> lib/Makefile | 2 +
> lib/test_fprobe.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 188 insertions(+)
> create mode 100644 lib/test_fprobe.c
>
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index 14b89aa37c5c..ffc469a12afc 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -2100,6 +2100,18 @@ config KPROBES_SANITY_TEST
>
> Say N if you are unsure.
>
> +config FPROBE_SANITY_TEST
> + bool "Self test for fprobe"
> + depends on DEBUG_KERNEL
> + depends on FPROBE
> + depends on KUNIT
Hmm, this caused a build error with allmodconfig because KUNIT=m but FPROBE_SANITY_TEST=y.
Let me fix this issue.
Thank you,
> + help
> + This option will enable testing the fprobe when the system boot.
> + A series of tests are made to verify that the fprobe is functioning
> + properly.
> +
> + Say N if you are unsure.
> +
> config BACKTRACE_SELF_TEST
> tristate "Self test for the backtrace code"
> depends on DEBUG_KERNEL
> diff --git a/lib/Makefile b/lib/Makefile
> index 300f569c626b..154008764b16 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -103,6 +103,8 @@ obj-$(CONFIG_TEST_HMM) += test_hmm.o
> obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
> obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
> obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
> +CFLAGS_test_fprobe.o += $(CC_FLAGS_FTRACE)
> +obj-$(CONFIG_FPROBE_SANITY_TEST) += test_fprobe.o
> #
> # CFLAGS for compiling floating point code inside the kernel. x86/Makefile turns
> # off the generation of FPU/SSE* instructions for kernel proper but FPU_FLAGS
> diff --git a/lib/test_fprobe.c b/lib/test_fprobe.c
> new file mode 100644
> index 000000000000..ed70637a2ffa
> --- /dev/null
> +++ b/lib/test_fprobe.c
> @@ -0,0 +1,174 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * test_fprobe.c - simple sanity test for fprobe
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/fprobe.h>
> +#include <linux/random.h>
> +#include <kunit/test.h>
> +
> +#define div_factor 3
> +
> +static struct kunit *current_test;
> +
> +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 unsigned long target_ip;
> +static unsigned long target2_ip;
> +
> +static noinline u32 fprobe_selftest_target(u32 value)
> +{
> + return (value / div_factor);
> +}
> +
> +static noinline u32 fprobe_selftest_target2(u32 value)
> +{
> + return (value / div_factor) + 1;
> +}
> +
> +static notrace void fp_entry_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> +{
> + KUNIT_EXPECT_FALSE(current_test, preemptible());
> + /* This can be called on the fprobe_selftest_target and the fprobe_selftest_target2 */
> + if (ip != target_ip)
> + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> + entry_val = (rand1 / div_factor);
> +}
> +
> +static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> +{
> + unsigned long ret = regs_return_value(regs);
> +
> + KUNIT_EXPECT_FALSE(current_test, preemptible());
> + if (ip != target_ip) {
> + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor) + 1);
> + } else
> + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor));
> + KUNIT_EXPECT_EQ(current_test, entry_val, (rand1 / div_factor));
> + exit_val = entry_val + div_factor;
> +}
> +
> +/* Test entry only (no rethook) */
> +static void test_fprobe_entry(struct kunit *test)
> +{
> + struct fprobe fp_entry = {
> + .entry_handler = fp_entry_handler,
> + };
> +
> + current_test = test;
> +
> + /* Before register, unregister should be failed. */
> + KUNIT_EXPECT_NE(test, 0, unregister_fprobe(&fp_entry));
> + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp_entry, "fprobe_selftest_target*", NULL));
> +
> + entry_val = 0;
> + exit_val = 0;
> + target(rand1);
> + KUNIT_EXPECT_NE(test, 0, entry_val);
> + KUNIT_EXPECT_EQ(test, 0, exit_val);
> +
> + entry_val = 0;
> + exit_val = 0;
> + target2(rand1);
> + KUNIT_EXPECT_NE(test, 0, entry_val);
> + KUNIT_EXPECT_EQ(test, 0, exit_val);
> +
> + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp_entry));
> +}
> +
> +static void test_fprobe(struct kunit *test)
> +{
> + struct fprobe fp = {
> + .entry_handler = fp_entry_handler,
> + .exit_handler = fp_exit_handler,
> + };
> +
> + current_test = test;
> + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp, "fprobe_selftest_target*", NULL));
> +
> + entry_val = 0;
> + exit_val = 0;
> + target(rand1);
> + KUNIT_EXPECT_NE(test, 0, entry_val);
> + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> +
> + entry_val = 0;
> + exit_val = 0;
> + target2(rand1);
> + KUNIT_EXPECT_NE(test, 0, entry_val);
> + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> +
> + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> +}
> +
> +static void test_fprobe_syms(struct kunit *test)
> +{
> + static const char *syms[] = {"fprobe_selftest_target", "fprobe_selftest_target2"};
> + struct fprobe fp = {
> + .entry_handler = fp_entry_handler,
> + .exit_handler = fp_exit_handler,
> + };
> +
> + current_test = test;
> + KUNIT_EXPECT_EQ(test, 0, register_fprobe_syms(&fp, syms, 2));
> +
> + entry_val = 0;
> + exit_val = 0;
> + target(rand1);
> + KUNIT_EXPECT_NE(test, 0, entry_val);
> + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> +
> + entry_val = 0;
> + exit_val = 0;
> + target2(rand1);
> + KUNIT_EXPECT_NE(test, 0, entry_val);
> + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> +
> + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> +}
> +
> +static unsigned long get_ftrace_location(void *func)
> +{
> + unsigned long size, addr = (unsigned long)func;
> +
> + if (!kallsyms_lookup_size_offset(addr, &size, NULL) || !size)
> + return 0;
> +
> + return ftrace_location_range(addr, addr + size - 1);
> +}
> +
> +static int fprobe_test_init(struct kunit *test)
> +{
> + do {
> + rand1 = prandom_u32();
> + } while (rand1 <= div_factor);
> +
> + target = fprobe_selftest_target;
> + target2 = fprobe_selftest_target2;
> + target_ip = get_ftrace_location(target);
> + target2_ip = get_ftrace_location(target2);
> +
> + return 0;
> +}
> +
> +static struct kunit_case fprobe_testcases[] = {
> + KUNIT_CASE(test_fprobe_entry),
> + KUNIT_CASE(test_fprobe),
> + KUNIT_CASE(test_fprobe_syms),
> + {}
> +};
> +
> +static struct kunit_suite fprobe_test_suite = {
> + .name = "fprobe_test",
> + .init = fprobe_test_init,
> + .test_cases = fprobe_testcases,
> +};
> +
> +kunit_test_suites(&fprobe_test_suite);
> +
> +MODULE_LICENSE("GPL");
>
--
Masami Hiramatsu <[email protected]>
On Fri, 11 Mar 2022 10:35:30 -0800
Andrii Nakryiko <[email protected]> wrote:
> On Thu, Mar 10, 2022 at 7:11 AM Masami Hiramatsu <[email protected]> wrote:
> >
> > On Wed, 9 Mar 2022 16:40:00 -0800
> > Andrii Nakryiko <[email protected]> wrote:
> >
> > > On Wed, Mar 9, 2022 at 4:17 PM Masami Hiramatsu <[email protected]> wrote:
> > > >
> > > > Hi,
> > > >
> > > > On Tue, 8 Mar 2022 20:10:48 +0900
> > > > Masami Hiramatsu <[email protected]> wrote:
> > > >
> > > > > Add a KUnit based selftest for fprobe interface.
> > > > >
> > > > > Signed-off-by: Masami Hiramatsu <[email protected]>
> > > > > ---
> > > > > Changes in v9:
> > > > > - Rename fprobe_target* to fprobe_selftest_target*.
> > > > > - Find the correct expected ip by ftrace_location_range().
> > > > > - Since the ftrace_location_range() is not exposed to module, make
> > > > > this test only for embedded.
> > > > > - Add entry only test.
> > > > > - Reset the fprobe structure before reuse it.
> > > > > ---
> > > > > lib/Kconfig.debug | 12 ++++
> > > > > lib/Makefile | 2 +
> > > > > lib/test_fprobe.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> > > > > 3 files changed, 188 insertions(+)
> > > > > create mode 100644 lib/test_fprobe.c
> > > > >
> > > > > diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> > > > > index 14b89aa37c5c..ffc469a12afc 100644
> > > > > --- a/lib/Kconfig.debug
> > > > > +++ b/lib/Kconfig.debug
> > > > > @@ -2100,6 +2100,18 @@ config KPROBES_SANITY_TEST
> > > > >
> > > > > Say N if you are unsure.
> > > > >
> > > > > +config FPROBE_SANITY_TEST
> > > > > + bool "Self test for fprobe"
> > > > > + depends on DEBUG_KERNEL
> > > > > + depends on FPROBE
> > > > > + depends on KUNIT
> > > >
> > > > Hmm, this caused a build error with allmodconfig because KUNIT=m but FPROBE_SANITY_TEST=y.
> > > > Let me fix this issue.
> > >
> > > Please base on top of bpf-next and add [PATCH v11 bpf-next] to subject.
> >
> > OK, let me rebase on it.
> > There are master and for-next branch, which one is better to use?
> >
>
> Sorry, missed your reply earlier. Always rebase against master.
>
> You forgot to add "bpf-next" into [PATCH] prefix, so I had to manually
> mark it in patchworks as delegated to bpf queue (this is necessary for
> our CI to properly pick it up). For future submissions to bpf-next,
> please don't forget to add "bpf-next" marker.
>
Oops, sorry, I forgot that. Should I resend with that tag?
Thank you,
> > Thank you,
> >
> > >
> > > >
> > > > Thank you,
> > > >
> > > > > + help
> > > > > + This option will enable testing the fprobe when the system boot.
> > > > > + A series of tests are made to verify that the fprobe is functioning
> > > > > + properly.
> > > > > +
> > > > > + Say N if you are unsure.
> > > > > +
> > > > > config BACKTRACE_SELF_TEST
> > > > > tristate "Self test for the backtrace code"
> > > > > depends on DEBUG_KERNEL
> > > > > diff --git a/lib/Makefile b/lib/Makefile
> > > > > index 300f569c626b..154008764b16 100644
> > > > > --- a/lib/Makefile
> > > > > +++ b/lib/Makefile
> > > > > @@ -103,6 +103,8 @@ obj-$(CONFIG_TEST_HMM) += test_hmm.o
> > > > > obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
> > > > > obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
> > > > > obj-$(CONFIG_TEST_REF_TRACKER) += test_ref_tracker.o
> > > > > +CFLAGS_test_fprobe.o += $(CC_FLAGS_FTRACE)
> > > > > +obj-$(CONFIG_FPROBE_SANITY_TEST) += test_fprobe.o
> > > > > #
> > > > > # CFLAGS for compiling floating point code inside the kernel. x86/Makefile turns
> > > > > # off the generation of FPU/SSE* instructions for kernel proper but FPU_FLAGS
> > > > > diff --git a/lib/test_fprobe.c b/lib/test_fprobe.c
> > > > > new file mode 100644
> > > > > index 000000000000..ed70637a2ffa
> > > > > --- /dev/null
> > > > > +++ b/lib/test_fprobe.c
> > > > > @@ -0,0 +1,174 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > > > +/*
> > > > > + * test_fprobe.c - simple sanity test for fprobe
> > > > > + */
> > > > > +
> > > > > +#include <linux/kernel.h>
> > > > > +#include <linux/fprobe.h>
> > > > > +#include <linux/random.h>
> > > > > +#include <kunit/test.h>
> > > > > +
> > > > > +#define div_factor 3
> > > > > +
> > > > > +static struct kunit *current_test;
> > > > > +
> > > > > +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 unsigned long target_ip;
> > > > > +static unsigned long target2_ip;
> > > > > +
> > > > > +static noinline u32 fprobe_selftest_target(u32 value)
> > > > > +{
> > > > > + return (value / div_factor);
> > > > > +}
> > > > > +
> > > > > +static noinline u32 fprobe_selftest_target2(u32 value)
> > > > > +{
> > > > > + return (value / div_factor) + 1;
> > > > > +}
> > > > > +
> > > > > +static notrace void fp_entry_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> > > > > +{
> > > > > + KUNIT_EXPECT_FALSE(current_test, preemptible());
> > > > > + /* This can be called on the fprobe_selftest_target and the fprobe_selftest_target2 */
> > > > > + if (ip != target_ip)
> > > > > + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> > > > > + entry_val = (rand1 / div_factor);
> > > > > +}
> > > > > +
> > > > > +static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip, struct pt_regs *regs)
> > > > > +{
> > > > > + unsigned long ret = regs_return_value(regs);
> > > > > +
> > > > > + KUNIT_EXPECT_FALSE(current_test, preemptible());
> > > > > + if (ip != target_ip) {
> > > > > + KUNIT_EXPECT_EQ(current_test, ip, target2_ip);
> > > > > + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor) + 1);
> > > > > + } else
> > > > > + KUNIT_EXPECT_EQ(current_test, ret, (rand1 / div_factor));
> > > > > + KUNIT_EXPECT_EQ(current_test, entry_val, (rand1 / div_factor));
> > > > > + exit_val = entry_val + div_factor;
> > > > > +}
> > > > > +
> > > > > +/* Test entry only (no rethook) */
> > > > > +static void test_fprobe_entry(struct kunit *test)
> > > > > +{
> > > > > + struct fprobe fp_entry = {
> > > > > + .entry_handler = fp_entry_handler,
> > > > > + };
> > > > > +
> > > > > + current_test = test;
> > > > > +
> > > > > + /* Before register, unregister should be failed. */
> > > > > + KUNIT_EXPECT_NE(test, 0, unregister_fprobe(&fp_entry));
> > > > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp_entry, "fprobe_selftest_target*", NULL));
> > > > > +
> > > > > + entry_val = 0;
> > > > > + exit_val = 0;
> > > > > + target(rand1);
> > > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > > + KUNIT_EXPECT_EQ(test, 0, exit_val);
> > > > > +
> > > > > + entry_val = 0;
> > > > > + exit_val = 0;
> > > > > + target2(rand1);
> > > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > > + KUNIT_EXPECT_EQ(test, 0, exit_val);
> > > > > +
> > > > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp_entry));
> > > > > +}
> > > > > +
> > > > > +static void test_fprobe(struct kunit *test)
> > > > > +{
> > > > > + struct fprobe fp = {
> > > > > + .entry_handler = fp_entry_handler,
> > > > > + .exit_handler = fp_exit_handler,
> > > > > + };
> > > > > +
> > > > > + current_test = test;
> > > > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe(&fp, "fprobe_selftest_target*", NULL));
> > > > > +
> > > > > + entry_val = 0;
> > > > > + exit_val = 0;
> > > > > + target(rand1);
> > > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > > > +
> > > > > + entry_val = 0;
> > > > > + exit_val = 0;
> > > > > + target2(rand1);
> > > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > > > +
> > > > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> > > > > +}
> > > > > +
> > > > > +static void test_fprobe_syms(struct kunit *test)
> > > > > +{
> > > > > + static const char *syms[] = {"fprobe_selftest_target", "fprobe_selftest_target2"};
> > > > > + struct fprobe fp = {
> > > > > + .entry_handler = fp_entry_handler,
> > > > > + .exit_handler = fp_exit_handler,
> > > > > + };
> > > > > +
> > > > > + current_test = test;
> > > > > + KUNIT_EXPECT_EQ(test, 0, register_fprobe_syms(&fp, syms, 2));
> > > > > +
> > > > > + entry_val = 0;
> > > > > + exit_val = 0;
> > > > > + target(rand1);
> > > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > > > +
> > > > > + entry_val = 0;
> > > > > + exit_val = 0;
> > > > > + target2(rand1);
> > > > > + KUNIT_EXPECT_NE(test, 0, entry_val);
> > > > > + KUNIT_EXPECT_EQ(test, entry_val + div_factor, exit_val);
> > > > > +
> > > > > + KUNIT_EXPECT_EQ(test, 0, unregister_fprobe(&fp));
> > > > > +}
> > > > > +
> > > > > +static unsigned long get_ftrace_location(void *func)
> > > > > +{
> > > > > + unsigned long size, addr = (unsigned long)func;
> > > > > +
> > > > > + if (!kallsyms_lookup_size_offset(addr, &size, NULL) || !size)
> > > > > + return 0;
> > > > > +
> > > > > + return ftrace_location_range(addr, addr + size - 1);
> > > > > +}
> > > > > +
> > > > > +static int fprobe_test_init(struct kunit *test)
> > > > > +{
> > > > > + do {
> > > > > + rand1 = prandom_u32();
> > > > > + } while (rand1 <= div_factor);
> > > > > +
> > > > > + target = fprobe_selftest_target;
> > > > > + target2 = fprobe_selftest_target2;
> > > > > + target_ip = get_ftrace_location(target);
> > > > > + target2_ip = get_ftrace_location(target2);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static struct kunit_case fprobe_testcases[] = {
> > > > > + KUNIT_CASE(test_fprobe_entry),
> > > > > + KUNIT_CASE(test_fprobe),
> > > > > + KUNIT_CASE(test_fprobe_syms),
> > > > > + {}
> > > > > +};
> > > > > +
> > > > > +static struct kunit_suite fprobe_test_suite = {
> > > > > + .name = "fprobe_test",
> > > > > + .init = fprobe_test_init,
> > > > > + .test_cases = fprobe_testcases,
> > > > > +};
> > > > > +
> > > > > +kunit_test_suites(&fprobe_test_suite);
> > > > > +
> > > > > +MODULE_LICENSE("GPL");
> > > > >
> > > >
> > > >
> > > > --
> > > > Masami Hiramatsu <[email protected]>
> >
> >
> > --
> > Masami Hiramatsu <[email protected]>
--
Masami Hiramatsu <[email protected]>