* [RFC PATCH 1/4] tools/tracing: Add fetcharg performance micro-benchmark
2026-07-01 13:45 [RFC PATCH 0/4] tracing/probes: Optimize fetcharg with BPF Masami Hiramatsu (Google)
@ 2026-07-01 13:45 ` Masami Hiramatsu (Google)
2026-07-01 13:45 ` [RFC PATCH 2/4] tracing/probes: Compile all fetchargs into a single BPF program per event Masami Hiramatsu (Google)
` (2 subsequent siblings)
3 siblings, 0 replies; 12+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-07-01 13:45 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
linux-kselftest, bpf
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add a benchmark test module (fetcharg_bench) and bench_fetcharg.sh script
to measure the execution overhead of fetchargs across kprobe, fprobe,
and eprobe configurations.
The benchmark runs for a baseline (no probe events), 0-arguments,
1-argument, 2-arguments, and 3-arguments configurations, calculating
the estimated overhead in nanoseconds. It also supports a --debug option
to dump registered dynamic events.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
---
tools/tracing/benchmark/Kbuild | 3
tools/tracing/benchmark/Makefile | 12 +
tools/tracing/benchmark/bench_fetcharg.sh | 195 ++++++++++++++++++++++++
tools/tracing/benchmark/fetcharg_bench.c | 98 ++++++++++++
tools/tracing/benchmark/fetcharg_bench_trace.h | 37 +++++
5 files changed, 345 insertions(+)
create mode 100644 tools/tracing/benchmark/Kbuild
create mode 100644 tools/tracing/benchmark/Makefile
create mode 100755 tools/tracing/benchmark/bench_fetcharg.sh
create mode 100644 tools/tracing/benchmark/fetcharg_bench.c
create mode 100644 tools/tracing/benchmark/fetcharg_bench_trace.h
diff --git a/tools/tracing/benchmark/Kbuild b/tools/tracing/benchmark/Kbuild
new file mode 100644
index 000000000000..4c31b26ca51c
--- /dev/null
+++ b/tools/tracing/benchmark/Kbuild
@@ -0,0 +1,3 @@
+obj-m += fetcharg_bench.o
+
+ccflags-y += -I$(src)
diff --git a/tools/tracing/benchmark/Makefile b/tools/tracing/benchmark/Makefile
new file mode 100644
index 000000000000..bf5b3cbaff03
--- /dev/null
+++ b/tools/tracing/benchmark/Makefile
@@ -0,0 +1,12 @@
+ifeq ($(O),)
+KDIR ?= /lib/modules/$(shell uname -r)/build
+else
+KDIR := $(O)
+endif
+MDIR := $(CURDIR)
+
+all:
+ $(MAKE) -C $(KDIR) M=$(MDIR) modules
+
+clean:
+ $(MAKE) -C $(KDIR) M=$(MDIR) clean
diff --git a/tools/tracing/benchmark/bench_fetcharg.sh b/tools/tracing/benchmark/bench_fetcharg.sh
new file mode 100755
index 000000000000..0b2a2b8a896e
--- /dev/null
+++ b/tools/tracing/benchmark/bench_fetcharg.sh
@@ -0,0 +1,195 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# description: Benchmark fetcharg performance (baseline vs kprobe vs fprobe vs eprobe)
+
+DEBUG=0
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --debug|-d)
+ DEBUG=1
+ shift
+ ;;
+ *)
+ echo "Unknown option: $1"
+ echo "Usage: $0 [--debug|-d]"
+ exit 1
+ ;;
+ esac
+done
+
+DEBUGFS_MOUNT=$(grep ^debugfs /proc/mounts | awk '{print $2}')
+if [ -z "$DEBUGFS_MOUNT" ]; then
+ mount -t debugfs nodev /sys/kernel/debug
+ DEBUGFS_MOUNT="/sys/kernel/debug"
+fi
+
+TRACEFS_MOUNT=$(grep ^tracefs /proc/mounts | awk '{print $2}')
+if [ -z "$TRACEFS_MOUNT" ]; then
+ mount -t tracefs nodev /sys/kernel/tracing
+ TRACEFS_MOUNT="/sys/kernel/tracing"
+fi
+
+MOD_NAME="fetcharg_bench"
+MOD_FILE="./${MOD_NAME}.ko"
+
+if [ ! -f "$MOD_FILE" ]; then
+ echo "Module $MOD_FILE not found. Please run 'make' first."
+ exit 1
+fi
+
+rmmod $MOD_NAME 2>/dev/null
+insmod $MOD_FILE || { echo "Failed to load $MOD_FILE"; exit 1; }
+
+TRIGGER_FILE="${DEBUGFS_MOUNT}/fetcharg_benchmark/trigger"
+
+if [ ! -f "$TRIGGER_FILE" ]; then
+ echo "Trigger file $TRIGGER_FILE not found."
+ rmmod $MOD_NAME
+ exit 1
+fi
+
+DYN_EVENTS="${TRACEFS_MOUNT}/dynamic_events"
+
+# Helper to clear events
+clear_events() {
+ echo 0 > "${TRACEFS_MOUNT}/events/enable"
+ echo > "$DYN_EVENTS"
+}
+
+run_bench() {
+ if [ "$DEBUG" = "1" ]; then
+ echo "=== [DEBUG] dynamic_events ===" >&2
+ cat "$DYN_EVENTS" >&2
+ echo "==============================" >&2
+ fi
+ cat "$TRIGGER_FILE"
+}
+
+calc_overhead() {
+ local lps=$1
+ local base_lps=$2
+ if [ -z "$lps" ] || [ -z "$base_lps" ] || [ "$lps" = "-" ] || [ "$base_lps" = "-" ]; then
+ echo "-"
+ return
+ fi
+ awk -v lps="$lps" -v base_lps="$base_lps" 'BEGIN {
+ if (lps == 0 || base_lps == 0) {
+ print "-"
+ exit
+ }
+ t = 1000000000.0 / lps
+ t_base = 1000000000.0 / base_lps
+ diff = t - t_base
+ printf "%.2f ns", diff
+ }'
+}
+
+echo "Running Fetcharg Micro Benchmark..."
+echo "Please wait, this may take a few seconds..."
+
+# Baseline
+clear_events
+baseline=$(run_bench)
+
+# Kprobe
+clear_events
+echo "p:bench_kprobe fetcharg_bench_target" >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/kprobes/bench_kprobe/enable"
+kprobe_0=$(run_bench)
+
+clear_events
+echo "p:bench_kprobe fetcharg_bench_target a=\$arg1" >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/kprobes/bench_kprobe/enable"
+kprobe_1=$(run_bench)
+
+clear_events
+echo "p:bench_kprobe fetcharg_bench_target a=\$arg1 b=+0(+0(\$arg2)):u32" >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/kprobes/bench_kprobe/enable"
+kprobe_2=$(run_bench)
+
+clear_events
+echo "p:bench_kprobe fetcharg_bench_target a=\$arg1 b=+0(+0(\$arg2)):u32 c=+0(\$arg3):u32" \
+ >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/kprobes/bench_kprobe/enable"
+kprobe_3=$(run_bench)
+
+# Fprobe
+clear_events
+echo "f:bench_fprobe fetcharg_bench_target" >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/fprobes/bench_fprobe/enable"
+fprobe_0=$(run_bench)
+
+clear_events
+echo "f:bench_fprobe fetcharg_bench_target a=\$arg1" >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/fprobes/bench_fprobe/enable"
+fprobe_1=$(run_bench)
+
+clear_events
+echo "f:bench_fprobe fetcharg_bench_target a=\$arg1 b=+0(+0(\$arg2)):u32" >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/fprobes/bench_fprobe/enable"
+fprobe_2=$(run_bench)
+
+clear_events
+echo "f:bench_fprobe fetcharg_bench_target a=\$arg1 b=+0(+0(\$arg2)):u32 c=+0(\$arg3):u32" \
+ >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/fprobes/bench_fprobe/enable"
+fprobe_3=$(run_bench)
+
+# Eprobe
+clear_events
+echo "e:bench_eprobe fetcharg_bench/fetcharg_bench_event" >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/eprobes/bench_eprobe/enable"
+echo 1 > "${TRACEFS_MOUNT}/events/fetcharg_bench/fetcharg_bench_event/enable"
+eprobe_0=$(run_bench)
+
+clear_events
+echo "e:bench_eprobe fetcharg_bench/fetcharg_bench_event a=\$a" >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/eprobes/bench_eprobe/enable"
+echo 1 > "${TRACEFS_MOUNT}/events/fetcharg_bench/fetcharg_bench_event/enable"
+eprobe_1=$(run_bench)
+
+clear_events
+echo "e:bench_eprobe fetcharg_bench/fetcharg_bench_event a=\$a b=+0(+0(\$b_ptr)):u32" \
+ >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/eprobes/bench_eprobe/enable"
+echo 1 > "${TRACEFS_MOUNT}/events/fetcharg_bench/fetcharg_bench_event/enable"
+eprobe_2=$(run_bench)
+
+clear_events
+echo "e:bench_eprobe fetcharg_bench/fetcharg_bench_event a=\$a b=+0(+0(\$b_ptr)):u32 c=+0(\$c_ptr):u32" \
+ >> "$DYN_EVENTS"
+echo 1 > "${TRACEFS_MOUNT}/events/eprobes/bench_eprobe/enable"
+echo 1 > "${TRACEFS_MOUNT}/events/fetcharg_bench/fetcharg_bench_event/enable"
+eprobe_3=$(run_bench)
+
+echo "--------------------------------------------------------------------------------"
+echo "Configuration 0 Fetchargs 1 Fetcharg 2 Fetchargs 3 Fetchargs"
+echo "--------------------------------------------------------------------------------"
+printf "%-18s %15s %15s %18s %18s loops/sec\n" "Baseline" "$baseline" "-" "-" "-"
+printf "%-18s %15s %15s %18s %18s overhead\n" " " "-" "-" "-" "-"
+printf "%-18s %15s %15s %18s %18s loops/sec\n" \
+ "Kprobe" "$kprobe_0" "$kprobe_1" "$kprobe_2" "$kprobe_3"
+printf "%-18s %15s %15s %18s %18s overhead\n" " " \
+ "$(calc_overhead $kprobe_0 $baseline)" \
+ "$(calc_overhead $kprobe_1 $kprobe_0)" \
+ "$(calc_overhead $kprobe_2 $kprobe_0)" \
+ "$(calc_overhead $kprobe_3 $kprobe_0)"
+printf "%-18s %15s %15s %18s %18s loops/sec\n" \
+ "Fprobe" "$fprobe_0" "$fprobe_1" "$fprobe_2" "$fprobe_3"
+printf "%-18s %15s %15s %18s %18s overhead\n" " " \
+ "$(calc_overhead $fprobe_0 $baseline)" \
+ "$(calc_overhead $fprobe_1 $fprobe_0)" \
+ "$(calc_overhead $fprobe_2 $fprobe_0)" \
+ "$(calc_overhead $fprobe_3 $fprobe_0)"
+printf "%-18s %15s %15s %18s %18s loops/sec\n" \
+ "Eprobe" "$eprobe_0" "$eprobe_1" "$eprobe_2" "$eprobe_3"
+printf "%-18s %15s %15s %18s %18s overhead\n" " " \
+ "$(calc_overhead $eprobe_0 $baseline)" \
+ "$(calc_overhead $eprobe_1 $eprobe_0)" \
+ "$(calc_overhead $eprobe_2 $eprobe_0)" \
+ "$(calc_overhead $eprobe_3 $eprobe_0)"
+echo "--------------------------------------------------------------------------------"
+
+clear_events
+rmmod $MOD_NAME
+exit 0
diff --git a/tools/tracing/benchmark/fetcharg_bench.c b/tools/tracing/benchmark/fetcharg_bench.c
new file mode 100644
index 000000000000..af18183c1f5d
--- /dev/null
+++ b/tools/tracing/benchmark/fetcharg_bench.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/module.h>
+#include <linux/ktime.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+
+#define CREATE_TRACE_POINTS
+#include "fetcharg_bench_trace.h"
+
+static noinline int fetcharg_bench_target(int a, int **b, char *c)
+{
+ /* Prevent compiler from optimizing the loop out entirely */
+ asm volatile ("" : : "r"(a), "r"(b), "r"(c) : "memory");
+ trace_fetcharg_bench_event(a, b, c);
+ return a + **b;
+}
+
+/* Indirect pointer to prevent inlining */
+static int (*bench_func_ptr)(int, int **, char *) = fetcharg_bench_target;
+
+#define BENCH_ITERATIONS 1000000
+
+static ssize_t fetcharg_bench_read(struct file *file, char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char buf[64];
+ int len;
+ u64 start, current_time;
+ u64 elapsed;
+ u64 loops_per_sec;
+ int dummy = 0;
+ int a = 1;
+ int b_val = 2;
+ int *b_ptr = &b_val;
+ int **b = &b_ptr;
+ char c[] = "benchmark";
+ int i;
+
+ if (*ppos > 0)
+ return 0; /* EOF */
+
+ start = ktime_get_ns();
+ for (i = 0; i < BENCH_ITERATIONS; i++)
+ dummy += bench_func_ptr(a, b, c);
+ current_time = ktime_get_ns();
+
+ elapsed = current_time - start;
+ loops_per_sec = ((u64)BENCH_ITERATIONS * NSEC_PER_SEC) / elapsed;
+
+ len = snprintf(buf, sizeof(buf), "%llu\n", loops_per_sec);
+ if (len < 0)
+ return len;
+
+ if (copy_to_user(user_buf, buf, len))
+ return -EFAULT;
+
+ *ppos += len;
+
+ /*
+ * Use 'dummy' to ensure the compiler doesn't optimize out
+ * the call completely, though the asm volatile helps too.
+ */
+ if (dummy == 0xdeadbeef)
+ pr_info("dummy=%d\n", dummy);
+
+ return len;
+}
+
+static const struct file_operations fetcharg_bench_fops = {
+ .read = fetcharg_bench_read,
+ .open = simple_open,
+ .llseek = default_llseek,
+};
+
+static struct dentry *bench_dir;
+
+static int __init fetcharg_bench_init(void)
+{
+ bench_dir = debugfs_create_dir("fetcharg_benchmark", NULL);
+ if (!bench_dir)
+ return -ENOMEM;
+
+ debugfs_create_file("trigger", 0444, bench_dir, NULL, &fetcharg_bench_fops);
+
+ return 0;
+}
+
+static void __exit fetcharg_bench_exit(void)
+{
+ debugfs_remove_recursive(bench_dir);
+}
+
+module_init(fetcharg_bench_init);
+module_exit(fetcharg_bench_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Antigravity");
+MODULE_DESCRIPTION("Fetcharg performance benchmark test module");
diff --git a/tools/tracing/benchmark/fetcharg_bench_trace.h b/tools/tracing/benchmark/fetcharg_bench_trace.h
new file mode 100644
index 000000000000..6560f62337e3
--- /dev/null
+++ b/tools/tracing/benchmark/fetcharg_bench_trace.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM fetcharg_bench
+
+#if !defined(_FETCHARG_BENCH_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _FETCHARG_BENCH_TRACE_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(fetcharg_bench_event,
+
+ TP_PROTO(int a, int **b, char *c),
+
+ TP_ARGS(a, b, c),
+
+ TP_STRUCT__entry(
+ __field(int, a)
+ __field(int **, b_ptr)
+ __field(char *, c_ptr)
+ ),
+
+ TP_fast_assign(
+ __entry->a = a;
+ __entry->b_ptr = b;
+ __entry->c_ptr = c;
+ ),
+
+ TP_printk("a=%d b=%p c=%p", __entry->a, __entry->b_ptr, __entry->c_ptr)
+);
+
+#endif /* _FETCHARG_BENCH_TRACE_H */
+
+#undef TRACE_INCLUDE_PATH
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE fetcharg_bench_trace
+#include <trace/define_trace.h>
^ permalink raw reply related [flat|nested] 12+ messages in thread* [RFC PATCH 2/4] tracing/probes: Compile all fetchargs into a single BPF program per event
2026-07-01 13:45 [RFC PATCH 0/4] tracing/probes: Optimize fetcharg with BPF Masami Hiramatsu (Google)
2026-07-01 13:45 ` [RFC PATCH 1/4] tools/tracing: Add fetcharg performance micro-benchmark Masami Hiramatsu (Google)
@ 2026-07-01 13:45 ` Masami Hiramatsu (Google)
2026-07-01 18:41 ` Alexei Starovoitov
2026-07-01 13:45 ` [RFC PATCH 3/4] tracing: Add disable_bpf trace option to ignore eBPF for fetchargs Masami Hiramatsu (Google)
2026-07-01 13:46 ` [RFC PATCH 4/4] selftests/ftrace: Add a test for eBPF compiled fetchargs Masami Hiramatsu (Google)
3 siblings, 1 reply; 12+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-07-01 13:45 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
linux-kselftest, bpf
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Compile all fetch arguments of a trace probe event into a single BPF
program instead of separate programs per argument to reduce prologue
and dispatching overhead.
BPF-compatible arguments (such as register, immediate, dereferences,
and raw stores) are compiled, including registers mapping for x86_64,
arm64, and s390. If any argument requires non-BPF operations (such as
dynamic strings), we fallback to the interpreter loop for all arguments.
Also, correctly initialize prog->len to prevent invalid opcode execution in
the BPF interpreter.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
---
kernel/trace/trace_probe.c | 249 ++++++++++++++++++++++++++++++++++++++-
kernel/trace/trace_probe.h | 15 ++
kernel/trace/trace_probe_tmpl.h | 13 ++
3 files changed, 273 insertions(+), 4 deletions(-)
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index 18c212122344..0deb53c22ae3 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -2003,11 +2003,208 @@ static char *generate_probe_arg_name(const char *arg, int idx)
return name;
}
+#ifdef CONFIG_BPF_SYSCALL
+#include <linux/filter.h>
+#include <linux/uaccess.h>
+
+static int regs_get_kernel_argument_offset(unsigned int n)
+{
+#ifdef CONFIG_X86_64
+ static const int argument_offsets[] = {
+ offsetof(struct pt_regs, di),
+ offsetof(struct pt_regs, si),
+ offsetof(struct pt_regs, dx),
+ offsetof(struct pt_regs, cx),
+ offsetof(struct pt_regs, r8),
+ offsetof(struct pt_regs, r9),
+ };
+ if (n < ARRAY_SIZE(argument_offsets))
+ return argument_offsets[n];
+#elif defined(CONFIG_ARM64)
+ if (n < 8)
+ return offsetof(struct pt_regs, regs[n]);
+#elif defined(CONFIG_S390)
+ if (n < 5)
+ return offsetof(struct pt_regs, gprs[2 + n]);
+#endif
+ return -1;
+}
+
+static bool trace_probe_can_compile_bpf(struct trace_probe *tp)
+{
+ int i;
+
+ if (tp->nr_args == 0)
+ return false;
+
+ for (i = 0; i < tp->nr_args; i++) {
+ struct probe_arg *parg = &tp->args[i];
+ struct fetch_insn *code = parg->code;
+
+ while (code->op != FETCH_OP_END) {
+ switch (code->op) {
+ case FETCH_OP_REG:
+ case FETCH_OP_IMM:
+ case FETCH_OP_DEREF:
+ case FETCH_OP_ST_RAW:
+ case FETCH_OP_ST_MEM:
+ break;
+ case FETCH_OP_ARG:
+ if (regs_get_kernel_argument_offset(code->param) < 0)
+ return false;
+ break;
+ default:
+ return false;
+ }
+ code++;
+ }
+ }
+ return true;
+}
+
+static void trace_probe_compile_bpf(struct trace_probe *tp)
+{
+ struct bpf_insn *insns;
+ int i = 0;
+ struct bpf_prog *prog;
+ int err, idx;
+
+ if (!trace_probe_can_compile_bpf(tp))
+ return;
+
+ insns = kmalloc_array(512, sizeof(struct bpf_insn), GFP_KERNEL);
+ if (!insns)
+ return;
+
+ /* Prologue: R6 = ctx */
+ insns[i++] = BPF_MOV64_REG(BPF_REG_6, BPF_REG_1);
+ /* R7 = ctx->rec */
+ insns[i++] = BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
+ offsetof(struct fetch_bpf_ctx, rec));
+ /* R8 = ctx->data */
+ insns[i++] = BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_6,
+ offsetof(struct fetch_bpf_ctx, data));
+ /* R9 = total size (0) */
+ insns[i++] = BPF_MOV64_IMM(BPF_REG_9, 0);
+
+ for (idx = 0; idx < tp->nr_args; idx++) {
+ struct probe_arg *parg = &tp->args[idx];
+ struct fetch_insn *code = parg->code;
+
+ while (code->op != FETCH_OP_END && i < 500) {
+ switch (code->op) {
+ case FETCH_OP_REG:
+ /* R0 = *(unsigned long *)(R7 + code->param) */
+ insns[i++] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_7, code->param);
+ break;
+ case FETCH_OP_ARG: {
+ int offset = regs_get_kernel_argument_offset(code->param);
+ /* R0 = *(unsigned long *)(R7 + offset) */
+ insns[i++] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_7, offset);
+ break;
+ }
+ case FETCH_OP_IMM:
+ insns[i++] = BPF_LD_IMM64(BPF_REG_0, code->immediate);
+ break;
+ case FETCH_OP_DEREF:
+ /* Add offset: R3 = R0 + code->offset (src) */
+ insns[i++] = BPF_MOV64_REG(BPF_REG_2, BPF_REG_0);
+ if (code->offset)
+ insns[i++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_2,
+ code->offset);
+ /* R1 = dst (R10 - 8 on stack) */
+ insns[i++] = BPF_MOV64_REG(BPF_REG_1, BPF_REG_10);
+ insns[i++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -8);
+ /* R3 = size */
+ insns[i++] = BPF_MOV64_IMM(BPF_REG_3, sizeof(unsigned long));
+ /* Call copy_from_kernel_nofault(dst, src, size) */
+ insns[i++] = BPF_EMIT_CALL(copy_from_kernel_nofault);
+ /* if (R0 < 0) return R0; */
+ insns[i++] = BPF_JMP_IMM(BPF_JSGE, BPF_REG_0, 0, 1);
+ insns[i++] = BPF_EXIT_INSN();
+ /* R0 = *(unsigned long *)(R10 - 8) */
+ insns[i++] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_10, -8);
+ break;
+ case FETCH_OP_ST_RAW:
+ /* Store R0 into R8 (data) + parg->offset based on size */
+ switch (code->size) {
+ case 1:
+ insns[i++] = BPF_STX_MEM(BPF_B, BPF_REG_8, BPF_REG_0,
+ parg->offset);
+ break;
+ case 2:
+ insns[i++] = BPF_STX_MEM(BPF_H, BPF_REG_8, BPF_REG_0,
+ parg->offset);
+ break;
+ case 4:
+ insns[i++] = BPF_STX_MEM(BPF_W, BPF_REG_8, BPF_REG_0,
+ parg->offset);
+ break;
+ case 8:
+ insns[i++] = BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0,
+ parg->offset);
+ break;
+ }
+ break;
+ case FETCH_OP_ST_MEM:
+ /* Add offset: R2 = R0 + code->offset (src) */
+ insns[i++] = BPF_MOV64_REG(BPF_REG_2, BPF_REG_0);
+ if (code->offset)
+ insns[i++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_2,
+ code->offset);
+ /* R1 = dst (R8 + parg->offset) */
+ insns[i++] = BPF_MOV64_REG(BPF_REG_1, BPF_REG_8);
+ if (parg->offset)
+ insns[i++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_1,
+ parg->offset);
+ /* R3 = size */
+ insns[i++] = BPF_MOV64_IMM(BPF_REG_3, code->size);
+ /* Call copy_from_kernel_nofault(dst, src, size) */
+ insns[i++] = BPF_EMIT_CALL(copy_from_kernel_nofault);
+ /* if (R0 < 0) return R0; */
+ insns[i++] = BPF_JMP_IMM(BPF_JSGE, BPF_REG_0, 0, 1);
+ insns[i++] = BPF_EXIT_INSN();
+ break;
+ default:
+ goto out;
+ }
+ code++;
+ }
+ }
+
+ if (i >= 500)
+ goto out;
+
+ /* Epilogue: return R9 (0) */
+ insns[i++] = BPF_MOV64_REG(BPF_REG_0, BPF_REG_9);
+ insns[i++] = BPF_EXIT_INSN();
+
+ prog = bpf_prog_alloc(bpf_prog_size(i), 0);
+ if (!prog)
+ goto out;
+
+ prog->len = i;
+ memcpy(prog->insnsi, insns, prog->len * sizeof(struct bpf_insn));
+ prog->type = BPF_PROG_TYPE_KPROBE;
+
+ prog = bpf_prog_select_runtime(prog, &err);
+ if (IS_ERR(prog))
+ goto out;
+ tp->prog = prog;
+
+out:
+ kfree(insns);
+}
+#endif
+
+/* Parse an argument */
+/* The caller must pass a null-terminated argument string */
int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
struct traceprobe_parse_context *ctx)
{
struct probe_arg *parg = &tp->args[i];
const char *body;
+ int ret;
ctx->tp = tp;
body = strchr(arg, '=');
@@ -2038,7 +2235,11 @@ int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
}
ctx->offset = body - arg;
/* Parse fetch argument */
- return traceprobe_parse_probe_arg_body(body, &tp->size, parg, ctx);
+ ret = traceprobe_parse_probe_arg_body(body, &tp->size, parg, ctx);
+ if (ret)
+ return ret;
+
+ return 0;
}
void traceprobe_free_probe_arg(struct probe_arg *arg)
@@ -2443,6 +2644,13 @@ void trace_probe_cleanup(struct trace_probe *tp)
for (i = 0; i < tp->nr_args; i++)
traceprobe_free_probe_arg(&tp->args[i]);
+#ifdef CONFIG_BPF_SYSCALL
+ if (tp->prog) {
+ bpf_prog_put(tp->prog);
+ tp->prog = NULL;
+ }
+#endif
+
if (tp->entry_arg) {
kfree(tp->entry_arg);
tp->entry_arg = NULL;
@@ -2531,15 +2739,32 @@ int trace_probe_register_event_call(struct trace_probe *tp)
trace_probe_name(tp)))
return -EEXIST;
+#ifdef CONFIG_BPF_SYSCALL
+ trace_probe_compile_bpf(tp);
+#endif
+
ret = register_trace_event(&call->event);
- if (!ret)
- return -ENODEV;
+ if (!ret) {
+ ret = -ENODEV;
+ goto err_free_bpf;
+ }
ret = trace_add_event_call(call);
- if (ret)
+ if (ret) {
unregister_trace_event(&call->event);
+ goto err_free_bpf;
+ }
return ret;
+
+err_free_bpf:
+#ifdef CONFIG_BPF_SYSCALL
+ if (tp->prog) {
+ bpf_prog_put(tp->prog);
+ tp->prog = NULL;
+ }
+#endif
+ return ret;
}
int trace_probe_add_file(struct trace_probe *tp, struct trace_event_file *file)
@@ -2768,5 +2993,21 @@ void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp)
for (i = 0; i < tp->nr_args; i++)
trace_probe_dump_arg(m, &tp->args[i]);
+
+#ifdef CONFIG_BPF_SYSCALL
+ if (tp->prog) {
+ seq_printf(m, "# [BPF%s]:", tp->prog->jited ? "-JIT" : "");
+ for (i = 0; i < tp->prog->len; i++) {
+ struct bpf_insn *insn = &tp->prog->insnsi[i];
+
+ seq_printf(m, " %02x %02x %04x %08x", insn->code,
+ insn->dst_reg | (insn->src_reg << 4),
+ insn->off, insn->imm);
+ if (i < tp->prog->len - 1)
+ seq_putc(m, ',');
+ }
+ seq_putc(m, '\n');
+ }
+#endif
}
#endif /* CONFIG_PROBE_EVENTS_DUMP_FETCHARG */
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index e6268a8dc378..10589414451c 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -274,6 +274,9 @@ struct trace_probe {
ssize_t size; /* trace entry size */
unsigned int nr_args;
struct probe_entry_arg *entry_arg; /* This is only for return probe */
+#ifdef CONFIG_BPF_SYSCALL
+ struct bpf_prog *prog;
+#endif
struct probe_arg args[];
};
@@ -299,6 +302,7 @@ static inline void trace_probe_set_flag(struct trace_probe *tp,
smp_store_release(&tp->event->flags, tp->event->flags | flag);
}
+
static inline void trace_probe_clear_flag(struct trace_probe *tp,
unsigned int flag)
{
@@ -631,3 +635,14 @@ struct uprobe_dispatch_data {
struct trace_uprobe *tu;
unsigned long bp_addr;
};
+
+#ifdef CONFIG_BPF_SYSCALL
+#include <linux/filter.h>
+
+struct fetch_bpf_ctx {
+ void *rec;
+ void *edata;
+ void *data;
+ void *base;
+};
+#endif
diff --git a/kernel/trace/trace_probe_tmpl.h b/kernel/trace/trace_probe_tmpl.h
index 8db12f758fda..6ca2dfe59a0f 100644
--- a/kernel/trace/trace_probe_tmpl.h
+++ b/kernel/trace/trace_probe_tmpl.h
@@ -273,6 +273,19 @@ store_trace_args(void *data, struct trace_probe *tp, void *rec, void *edata,
u32 *dl; /* Data location */
int ret, i;
+#ifdef CONFIG_BPF_SYSCALL
+ if (tp->prog) {
+ struct fetch_bpf_ctx ctx = {
+ .rec = rec,
+ .edata = edata,
+ .data = data,
+ .base = base,
+ };
+ bpf_prog_run(tp->prog, &ctx);
+ return;
+ }
+#endif
+
for (i = 0; i < tp->nr_args; i++) {
arg = tp->args + i;
dl = data + arg->offset;
^ permalink raw reply related [flat|nested] 12+ messages in thread* [RFC PATCH 3/4] tracing: Add disable_bpf trace option to ignore eBPF for fetchargs
2026-07-01 13:45 [RFC PATCH 0/4] tracing/probes: Optimize fetcharg with BPF Masami Hiramatsu (Google)
2026-07-01 13:45 ` [RFC PATCH 1/4] tools/tracing: Add fetcharg performance micro-benchmark Masami Hiramatsu (Google)
2026-07-01 13:45 ` [RFC PATCH 2/4] tracing/probes: Compile all fetchargs into a single BPF program per event Masami Hiramatsu (Google)
@ 2026-07-01 13:45 ` Masami Hiramatsu (Google)
2026-07-01 13:46 ` [RFC PATCH 4/4] selftests/ftrace: Add a test for eBPF compiled fetchargs Masami Hiramatsu (Google)
3 siblings, 0 replies; 12+ messages in thread
From: Masami Hiramatsu (Google) @ 2026-07-01 13:45 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
linux-kselftest, bpf
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add a trace option "disable_bpf" to disable BPF execution for fetchargs,
forcing the execution to fallback to the interpreter loop. This is useful
for evaluating BPF compilation performance impact.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
---
kernel/trace/trace.c | 7 +++++++
kernel/trace/trace.h | 8 ++++++++
kernel/trace/trace_probe_tmpl.h | 2 +-
3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index c9e182d40059..7c0f7b629fcb 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -9940,6 +9940,13 @@ struct trace_array *trace_get_global_array(void)
}
#endif
+#ifdef CONFIG_BPF_SYSCALL
+bool trace_probe_bpf_disabled(void)
+{
+ return !!(global_trace.trace_flags & TRACE_ITER(DISABLE_BPF));
+}
+#endif
+
void __init early_trace_init(void)
{
if (tracepoint_printk) {
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 80fe152af1dd..bf83680e0ba7 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -1503,6 +1503,7 @@ extern int trace_get_user(struct trace_parser *parser, const char __user *ubuf,
C(PAUSE_ON_TRACE, "pause-on-trace"), \
C(HASH_PTR, "hash-ptr"), /* Print hashed pointer */ \
C(BITMASK_LIST, "bitmask-list"), \
+ C(DISABLE_BPF, "disable_bpf"), \
FUNCTION_FLAGS \
FGRAPH_FLAGS \
STACK_FLAGS \
@@ -2505,4 +2506,11 @@ static inline int rv_init_interface(void)
_args; \
})
+#ifdef CONFIG_BPF_SYSCALL
+bool trace_probe_bpf_disabled(void);
+#else
+static inline bool trace_probe_bpf_disabled(void) { return false; }
+#endif
+
#endif /* _LINUX_KERNEL_TRACE_H */
+
diff --git a/kernel/trace/trace_probe_tmpl.h b/kernel/trace/trace_probe_tmpl.h
index 6ca2dfe59a0f..015208aefbaf 100644
--- a/kernel/trace/trace_probe_tmpl.h
+++ b/kernel/trace/trace_probe_tmpl.h
@@ -274,7 +274,7 @@ store_trace_args(void *data, struct trace_probe *tp, void *rec, void *edata,
int ret, i;
#ifdef CONFIG_BPF_SYSCALL
- if (tp->prog) {
+ if (tp->prog && !trace_probe_bpf_disabled()) {
struct fetch_bpf_ctx ctx = {
.rec = rec,
.edata = edata,
^ permalink raw reply related [flat|nested] 12+ messages in thread