* [PATCH] x86/stacktrace: Mark arch_stack_walk() and unwinder functions notrace
From: Yuanhe Shu @ 2026-07-06 9:54 UTC (permalink / raw)
To: Josh Poimboeuf, Peter Zijlstra, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86
Cc: H . Peter Anvin, Steven Rostedt, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, stable, Yuanhe Shu
When the function tracer's func_stack_trace option and the function graph
profiler (function_profile_enabled) are both active, a recursive ftrace
reentrance can occur, leading to a hard lockup. This was observed during
ftrace selftest (ftracetest-ktap) execution:
watchdog: Watchdog detected hard LOCKUP on cpu 204
RIP: profile_graph_entry+0xa0/0x160
Call Trace:
function_graph_enter+0xc9/0x120
arch_ftrace_ops_list_func+0x112/0x230
ftrace_call+0x5/0x44
unwind_next_frame+0x5/0x870 <-- traced by ftrace
arch_stack_walk+0x88/0xf0
stack_trace_save+0x4b/0x70
__ftrace_trace_stack+0x12e/0x170
function_stack_trace_call+0x7c/0xa0
arch_ftrace_ops_list_func+0x112/0x230
ftrace_call+0x5/0x44
irqtime_account_irq+0x5/0xb0
__irq_exit_rcu+0x12/0xc0
...
The root cause is a recursive ftrace reentrance:
function_stack_trace_call() invokes __trace_stack() ->
arch_stack_walk() -> unwind_next_frame() to capture a backtrace.
Since the unwinder functions (__unwind_start(),
unwind_next_frame(), unwind_get_return_address(),
unwind_get_return_address_ptr()) are not marked notrace, the
function graph tracer instruments them, re-entering the ftrace
infrastructure from within an ftrace callback. This results in a
hard lockup with interrupts disabled, detected by the watchdog NMI.
On arm64 and riscv, arch_stack_walk() has already been marked
noinstr to prevent this class of bugs. See
commit 0fbcd8abf337 ("arm64: Prohibit instrumentation on arch_stack_walk()")
and commit 23b2188920a2 ("riscv: stacktrace: convert arch_stack_walk() to noinstr").
However, x86 was not fixed because:
1) x86's return_address() uses the generic
__builtin_return_address() instead of arch_stack_walk(), so the
lockdep recursion path that triggered the arm64 fix does not
exist on x86.
2) On arm64, all unwinder helpers are __always_inline within
arch_stack_walk(), so a single noinstr annotation suffices.
On riscv, the helper walk_stackframe() was already marked
notrace. On x86 however, the ORC unwinder implements
__unwind_start(), unwind_next_frame(), and
unwind_get_return_address() as separate non-inline exported
functions without any instrumentation protection, so marking
only arch_stack_walk() is insufficient.
Fix this by marking arch_stack_walk() and the non-inline unwinder
functions it calls (__unwind_start(), unwind_next_frame(),
unwind_get_return_address(), unwind_get_return_address_ptr())
as notrace, preventing ftrace from instrumenting the entire stack
unwinding path.
Fixes: 3599fe12a125 ("x86/stacktrace: Use common infrastructure")
Cc: stable@vger.kernel.org
Signed-off-by: Yuanhe Shu <xiangzao@linux.alibaba.com>
---
arch/x86/include/asm/unwind.h | 10 +++++-----
arch/x86/kernel/stacktrace.c | 12 ++++++++++--
arch/x86/kernel/unwind_frame.c | 10 +++++-----
arch/x86/kernel/unwind_guess.c | 10 +++++-----
arch/x86/kernel/unwind_orc.c | 10 +++++-----
5 files changed, 30 insertions(+), 22 deletions(-)
diff --git a/arch/x86/include/asm/unwind.h b/arch/x86/include/asm/unwind.h
index 7cede4dc21f0..15b699f2edc0 100644
--- a/arch/x86/include/asm/unwind.h
+++ b/arch/x86/include/asm/unwind.h
@@ -39,11 +39,11 @@ struct unwind_state {
#endif
};
-void __unwind_start(struct unwind_state *state, struct task_struct *task,
- struct pt_regs *regs, unsigned long *first_frame);
-bool unwind_next_frame(struct unwind_state *state);
-unsigned long unwind_get_return_address(struct unwind_state *state);
-unsigned long *unwind_get_return_address_ptr(struct unwind_state *state);
+void notrace __unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs, unsigned long *first_frame);
+bool notrace unwind_next_frame(struct unwind_state *state);
+unsigned long notrace unwind_get_return_address(struct unwind_state *state);
+unsigned long *notrace unwind_get_return_address_ptr(struct unwind_state *state);
static inline bool unwind_done(struct unwind_state *state)
{
diff --git a/arch/x86/kernel/stacktrace.c b/arch/x86/kernel/stacktrace.c
index ee117fcf46ed..1e5a06439adb 100644
--- a/arch/x86/kernel/stacktrace.c
+++ b/arch/x86/kernel/stacktrace.c
@@ -12,8 +12,16 @@
#include <asm/stacktrace.h>
#include <asm/unwind.h>
-void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
- struct task_struct *task, struct pt_regs *regs)
+/*
+ * arch_stack_walk() and the functions it calls (__unwind_start(),
+ * unwind_next_frame(), unwind_get_return_address(),
+ * unwind_get_return_address_ptr()) must not be instrumented by ftrace,
+ * as they are invoked from within ftrace callbacks (e.g.,
+ * function_stack_trace_call). Tracing these functions would cause
+ * recursive ftrace reentrance, leading to a hard lockup.
+ */
+void notrace arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
+ struct task_struct *task, struct pt_regs *regs)
{
struct unwind_state state;
unsigned long addr;
diff --git a/arch/x86/kernel/unwind_frame.c b/arch/x86/kernel/unwind_frame.c
index d8ba93778ae3..07d1b9f0208f 100644
--- a/arch/x86/kernel/unwind_frame.c
+++ b/arch/x86/kernel/unwind_frame.c
@@ -11,7 +11,7 @@
#define FRAME_HEADER_SIZE (sizeof(long) * 2)
-unsigned long unwind_get_return_address(struct unwind_state *state)
+unsigned long notrace unwind_get_return_address(struct unwind_state *state)
{
if (unwind_done(state))
return 0;
@@ -20,7 +20,7 @@ unsigned long unwind_get_return_address(struct unwind_state *state)
}
EXPORT_SYMBOL_GPL(unwind_get_return_address);
-unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
+unsigned long *notrace unwind_get_return_address_ptr(struct unwind_state *state)
{
if (unwind_done(state))
return NULL;
@@ -261,7 +261,7 @@ static bool update_stack_state(struct unwind_state *state,
}
__no_kmsan_checks
-bool unwind_next_frame(struct unwind_state *state)
+bool notrace unwind_next_frame(struct unwind_state *state)
{
struct pt_regs *regs;
unsigned long *next_bp;
@@ -370,8 +370,8 @@ bool unwind_next_frame(struct unwind_state *state)
}
EXPORT_SYMBOL_GPL(unwind_next_frame);
-void __unwind_start(struct unwind_state *state, struct task_struct *task,
- struct pt_regs *regs, unsigned long *first_frame)
+void notrace __unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs, unsigned long *first_frame)
{
unsigned long *bp;
diff --git a/arch/x86/kernel/unwind_guess.c b/arch/x86/kernel/unwind_guess.c
index 884d68a6e714..22d12e79984b 100644
--- a/arch/x86/kernel/unwind_guess.c
+++ b/arch/x86/kernel/unwind_guess.c
@@ -6,7 +6,7 @@
#include <asm/stacktrace.h>
#include <asm/unwind.h>
-unsigned long unwind_get_return_address(struct unwind_state *state)
+unsigned long notrace unwind_get_return_address(struct unwind_state *state)
{
unsigned long addr;
@@ -19,12 +19,12 @@ unsigned long unwind_get_return_address(struct unwind_state *state)
}
EXPORT_SYMBOL_GPL(unwind_get_return_address);
-unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
+unsigned long *notrace unwind_get_return_address_ptr(struct unwind_state *state)
{
return NULL;
}
-bool unwind_next_frame(struct unwind_state *state)
+bool notrace unwind_next_frame(struct unwind_state *state)
{
struct stack_info *info = &state->stack_info;
@@ -48,8 +48,8 @@ bool unwind_next_frame(struct unwind_state *state)
}
EXPORT_SYMBOL_GPL(unwind_next_frame);
-void __unwind_start(struct unwind_state *state, struct task_struct *task,
- struct pt_regs *regs, unsigned long *first_frame)
+void notrace __unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs, unsigned long *first_frame)
{
memset(state, 0, sizeof(*state));
diff --git a/arch/x86/kernel/unwind_orc.c b/arch/x86/kernel/unwind_orc.c
index 6407bc9256bf..f2a450ee66e6 100644
--- a/arch/x86/kernel/unwind_orc.c
+++ b/arch/x86/kernel/unwind_orc.c
@@ -377,7 +377,7 @@ void __init unwind_init(void)
orc_init = true;
}
-unsigned long unwind_get_return_address(struct unwind_state *state)
+unsigned long notrace unwind_get_return_address(struct unwind_state *state)
{
if (unwind_done(state))
return 0;
@@ -386,7 +386,7 @@ unsigned long unwind_get_return_address(struct unwind_state *state)
}
EXPORT_SYMBOL_GPL(unwind_get_return_address);
-unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
+unsigned long *notrace unwind_get_return_address_ptr(struct unwind_state *state)
{
if (unwind_done(state))
return NULL;
@@ -481,7 +481,7 @@ static bool get_reg(struct unwind_state *state, unsigned int reg_off,
return false;
}
-bool unwind_next_frame(struct unwind_state *state)
+bool notrace unwind_next_frame(struct unwind_state *state)
{
unsigned long ip_p, sp, tmp, orig_ip = state->ip, prev_sp = state->sp;
enum stack_type prev_type = state->stack_info.type;
@@ -709,8 +709,8 @@ bool unwind_next_frame(struct unwind_state *state)
}
EXPORT_SYMBOL_GPL(unwind_next_frame);
-void __unwind_start(struct unwind_state *state, struct task_struct *task,
- struct pt_regs *regs, unsigned long *first_frame)
+void notrace __unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs, unsigned long *first_frame)
{
memset(state, 0, sizeof(*state));
state->task = task;
--
2.39.3
^ permalink raw reply related
* [RFC 3/3] selftests/kprobes: Add kprobe stress test for page fault handling
From: Pu Hu @ 2026-07-06 8:36 UTC (permalink / raw)
To: mhiramat@kernel.org
Cc: ada.coupriediaz@arm.com, catalin.marinas@arm.com,
davem@davemloft.net, Hongyan Xia, Pu Hu, Jiazi Li,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <20260706083636.159883-1-hupu@transsion.com>
From: Pu Hu <hupu@transsion.com>
Add a selftest that stresses kprobe handling around page fault paths.
This reproducer consists of:
- A kprobe module that probes folio_wait_bit_common()
- A userspace program that repeatedly triggers file-backed page faults
This is primarily intended to reproduce arm64 kprobe stability issues
related to XOL slot execution and fault handling during single-stepping.
Signed-off-by: Pu Hu <hupu@transsion.com>
---
tools/testing/selftests/kprobe/.gitignore | 2 +
tools/testing/selftests/kprobe/Makefile | 75 +++++++++++++
tools/testing/selftests/kprobe/fault_stress.c | 105 ++++++++++++++++++
.../selftests/kprobe/kprobe_folio_stress.c | 70 ++++++++++++
4 files changed, 252 insertions(+)
create mode 100644 tools/testing/selftests/kprobe/.gitignore
create mode 100644 tools/testing/selftests/kprobe/Makefile
create mode 100644 tools/testing/selftests/kprobe/fault_stress.c
create mode 100644 tools/testing/selftests/kprobe/kprobe_folio_stress.c
diff --git a/tools/testing/selftests/kprobe/.gitignore b/tools/testing/selftests/kprobe/.gitignore
new file mode 100644
index 000000000000..5fdb3ee02cf1
--- /dev/null
+++ b/tools/testing/selftests/kprobe/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+fault_stress
diff --git a/tools/testing/selftests/kprobe/Makefile b/tools/testing/selftests/kprobe/Makefile
new file mode 100644
index 000000000000..39f20de5370c
--- /dev/null
+++ b/tools/testing/selftests/kprobe/Makefile
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for kprobe fault stress test
+#
+
+ifneq ($(KERNELRELEASE),)
+
+# This part is used by kernel Kbuild when building the external module:
+#
+# make -C $(KDIR) M=$(CURDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
+#
+obj-m += kprobe_folio_stress.o
+
+ccflags-y += -g -O0 -fno-omit-frame-pointer
+
+else
+
+# This part is used when running make directly in this directory.
+#
+# make KDIR=$BUILD_DIR ARCH=$ARCH CROSS_COMPILE=$CROSS_COMPILE all
+#
+
+PWD := $(shell pwd)
+ARCH ?= arm64
+CROSS_COMPILE ?= aarch64-dumpstack-linux-gnu-
+KDIR ?= /lib/modules/$(shell uname -r)/build
+DEST_PATH ?= $(PWD)/out
+Q ?= @
+
+# Kernel module
+KP_MOD := kprobe_folio_stress
+KP_KO := $(KP_MOD).ko
+
+# Userspace test program
+UNIT_TEST := fault_stress
+UNIT_TEST_SRC := fault_stress.c
+
+# Userspace CFLAGS.
+# These options make userspace stacks easier to unwind.
+USER_CFLAGS := -static -g -O0 -fno-omit-frame-pointer -fasynchronous-unwind-tables
+USER_LIBS := -lm -lpthread
+
+.PHONY: all modules user install clean
+
+all: modules user
+
+modules:
+ $(Q)$(MAKE) -C $(KDIR) \
+ M=$(PWD) \
+ ARCH=$(ARCH) \
+ CROSS_COMPILE=$(CROSS_COMPILE) \
+ modules
+
+user:
+ $(Q)$(CROSS_COMPILE)gcc \
+ $(USER_CFLAGS) \
+ $(UNIT_TEST_SRC) \
+ -o $(UNIT_TEST) \
+ $(USER_LIBS)
+
+install: all
+ $(Q)mkdir -p $(DEST_PATH)
+ $(Q)cp -f $(KP_KO) $(DEST_PATH)/
+ $(Q)cp -f $(UNIT_TEST) $(DEST_PATH)/
+
+clean:
+ $(Q)$(MAKE) -C $(KDIR) \
+ M=$(PWD) \
+ ARCH=$(ARCH) \
+ CROSS_COMPILE=$(CROSS_COMPILE) \
+ clean
+ $(Q)rm -f $(UNIT_TEST)
+ $(Q)rm -rf $(DEST_PATH)
+
+endif
diff --git a/tools/testing/selftests/kprobe/fault_stress.c b/tools/testing/selftests/kprobe/fault_stress.c
new file mode 100644
index 000000000000..160e172dfa59
--- /dev/null
+++ b/tools/testing/selftests/kprobe/fault_stress.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * fault_stress.c - Userspace program to trigger file-backed page faults
+ *
+ * This program creates a file and repeatedly maps/unmaps it while
+ * accessing memory, triggering file-backed page faults. It's designed
+ * to work with kprobe-folio-stress.c to stress kprobe handling.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <pthread.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define FILE_SIZE (256UL * 1024 * 1024)
+#define NR_THREADS 8
+
+static void deep_call(int n)
+{
+ volatile char buf[4096];
+
+ memset((void *)buf, n, sizeof(buf));
+
+ if (n > 0)
+ deep_call(n - 1);
+ else
+ sched_yield();
+}
+
+static void *worker(void *arg)
+{
+ const char *path = arg;
+ int fd;
+ char *map;
+ unsigned long i;
+ volatile unsigned long sum = 0;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ perror("open");
+ return NULL;
+ }
+
+ map = mmap(NULL, FILE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (map == MAP_FAILED) {
+ perror("mmap");
+ close(fd);
+ return NULL;
+ }
+
+ for (;;) {
+ /*
+ * Drop the pages backing this mapping from the current
+ * process. Subsequent accesses are more likely to trigger
+ * file-backed page faults again.
+ */
+ madvise(map, FILE_SIZE, MADV_DONTNEED);
+
+ for (i = 0; i < FILE_SIZE; i += 4096 * 17) {
+ sum += map[i];
+ deep_call(64);
+ }
+ }
+
+ munmap(map, FILE_SIZE);
+ close(fd);
+ return NULL;
+}
+
+int main(void)
+{
+ pthread_t th[NR_THREADS];
+ const char *path = "/tmp/fault_stress_file";
+ int fd;
+ int i;
+
+ fd = open(path, O_CREAT | O_RDWR, 0644);
+ if (fd < 0) {
+ perror("open file");
+ return 1;
+ }
+
+ if (ftruncate(fd, FILE_SIZE) < 0) {
+ perror("ftruncate");
+ close(fd);
+ return 1;
+ }
+
+ close(fd);
+
+ for (i = 0; i < NR_THREADS; i++)
+ pthread_create(&th[i], NULL, worker, (void *)path);
+
+ for (i = 0; i < NR_THREADS; i++)
+ pthread_join(th[i], NULL);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/kprobe/kprobe_folio_stress.c b/tools/testing/selftests/kprobe/kprobe_folio_stress.c
new file mode 100644
index 000000000000..181a89f4c495
--- /dev/null
+++ b/tools/testing/selftests/kprobe/kprobe_folio_stress.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * kprobe_folio_stress.c - kprobe stress test for page fault handling
+ *
+ * This module installs a kprobe on folio_wait_bit_common() and is
+ * intended to be used together with fault_stress.c to stress kprobe
+ * handling around fault paths.
+ *
+ * Primary use case: reproduce arm64 kprobe stability issues related to
+ * XOL slot execution and fault handling during single-stepping.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/kprobes.h>
+#include <linux/sched.h>
+#include <linux/atomic.h>
+#include <linux/ratelimit.h>
+#include <linux/kallsyms.h>
+
+
+static atomic64_t kp_hit_count = ATOMIC64_INIT(0);
+
+static int folio_wait_bit_common_handler(
+ struct kprobe *p, struct pt_regs *regs)
+{
+ unsigned long hit;
+
+ hit = atomic64_inc_return(&kp_hit_count);
+
+ pr_info("kp_folio: hit=%lu comm=%s tgid=%d tid=%d\n",
+ hit, current->comm, current->tgid, current->pid);
+
+ return 0;
+}
+
+static struct kprobe kp_folio_probe = {
+ .symbol_name = "folio_wait_bit_common",
+ .pre_handler = folio_wait_bit_common_handler,
+};
+
+static int __init kprobe_folio_init(void)
+{
+ int ret;
+
+ ret = register_kprobe(&kp_folio_probe);
+ if (ret < 0) {
+ pr_err("kp_folio: register_kprobe failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ pr_info("kp_folio: kprobe registered at %pS, addr=%px\n",
+ kp_folio_probe.addr, kp_folio_probe.addr);
+
+ return 0;
+}
+
+static void __exit kprobe_folio_exit(void)
+{
+ unregister_kprobe(&kp_folio_probe);
+ pr_info("kp_folio: kprobe unregistered, total hits=%lld\n",
+ atomic64_read(&kp_hit_count));
+}
+
+module_init(kprobe_folio_init);
+module_exit(kprobe_folio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pu Hu <hupu@transsion.com>");
+MODULE_DESCRIPTION("kprobe stress test for folio_wait_bit_common");
--
2.43.0
^ permalink raw reply related
* [RFC 2/3] arm64: kprobes: Allow reentering kprobes while single-stepping
From: Pu Hu @ 2026-07-06 8:36 UTC (permalink / raw)
To: mhiramat@kernel.org
Cc: ada.coupriediaz@arm.com, catalin.marinas@arm.com,
davem@davemloft.net, Hongyan Xia, Pu Hu, Jiazi Li,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <20260706083636.159883-1-hupu@transsion.com>
From: Pu Hu <hupu@transsion.com>
A kprobe can be hit while another kprobe is in KPROBE_HIT_SS state. This
can happen when tracing or perf code runs from the debug exception path
while the first kprobe is preparing or executing its out-of-line
single-step instruction.
Currently arm64 treats a kprobe hit in KPROBE_HIT_SS as unrecoverable,
the same as a hit in KPROBE_REENTER. This is too strict. A hit in
KPROBE_HIT_SS is still a one-level reentry and can be handled by saving
the current kprobe state and setting up single-step for the new probe,
just like reentry from KPROBE_HIT_ACTIVE or KPROBE_HIT_SSDONE.
The truly unrecoverable case is hitting another kprobe while already in
KPROBE_REENTER, because the reentry save area has already been consumed.
Move KPROBE_HIT_SS to the recoverable reentry cases and leave
KPROBE_REENTER as the unrecoverable nested reentry case.
This mirrors the x86 fix in commit 6a5022a56ac3
("kprobes/x86: Allow to handle reentered kprobe on single-stepping").
Signed-off-by: Pu Hu <hupu@transsion.com>
Signed-off-by: Hongyan Xia <hongyan.xia@transsion.com>
---
arch/arm64/kernel/probes/kprobes.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
index e4d2852ce2fb..764b2228cca0 100644
--- a/arch/arm64/kernel/probes/kprobes.c
+++ b/arch/arm64/kernel/probes/kprobes.c
@@ -240,10 +240,16 @@ static int __kprobes reenter_kprobe(struct kprobe *p,
switch (kcb->kprobe_status) {
case KPROBE_HIT_SSDONE:
case KPROBE_HIT_ACTIVE:
+ case KPROBE_HIT_SS:
+ /*
+ * A probe can be hit while another kprobe is preparing or
+ * executing its XOL single-step instruction. This is still a
+ * recoverable one-level reentry, so handle it in the same way as
+ * reentry from KPROBE_HIT_ACTIVE or KPROBE_HIT_SSDONE.
+ */
kprobes_inc_nmissed_count(p);
setup_singlestep(p, regs, kcb, 1);
break;
- case KPROBE_HIT_SS:
case KPROBE_REENTER:
pr_warn("Failed to recover from reentered kprobes.\n");
dump_kprobe(p);
--
2.43.0
^ permalink raw reply related
* [RFC 0/3] arm64: kprobes: Fix single-step fault and reentry handling
From: Pu Hu @ 2026-07-06 8:36 UTC (permalink / raw)
To: mhiramat@kernel.org
Cc: ada.coupriediaz@arm.com, catalin.marinas@arm.com,
davem@davemloft.net, Hongyan Xia, Pu Hu, Jiazi Li,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <20260704234730.46d51c47d75e7d208e7bec9f@kernel.org>
From: Pu Hu <hupu@transsion.com>
This series fixes two arm64 kprobes issues observed when running
simpleperf with preemptirq tracepoints and dwarf callchains while a
kprobe is active on a frequently executed kernel function.
The crash happens in the kprobe debug exception path. While a kprobe is
preparing or executing its XOL single-step instruction, perf/trace code
can run in the same window. That code may either take a fault of its own
or hit another kprobe.
Patch 1 makes kprobe_fault_handler() handle a fault in
KPROBE_HIT_SS/KPROBE_REENTER only when the faulting PC points at the
current kprobe's XOL instruction. Otherwise the fault is left to the
normal fault handling path.
Patch 2 allows a kprobe hit in KPROBE_HIT_SS to be handled as a
recoverable one-level reentry. Only a hit while already in
KPROBE_REENTER remains unrecoverable.
Patch 3 adds a kprobes selftest which registers a kprobe on a frequently
executed filemap fault path and drives repeated file-backed page faults
from userspace. This provides a smaller reproducer for validating the
fault handling fix.
This follows the same logic as the existing x86 fixes:
6381c24cd6d5 ("kprobes/x86: Fix page-fault handling logic")
6a5022a56ac3 ("kprobes/x86: Allow to handle reentered kprobe on single-stepping")
Reproducer:
simpleperf record -p <pid> -f 10000 \
-e preemptirq:preempt_disable \
-e preemptirq:preempt_enable \
--duration 9 --call-graph dwarf \
-o /data/local/tmp/perf.data
The new selftest can be built from tools/testing/selftests/kprobe/ and
used to exercise the page fault handling path with the test kprobe
module loaded.
Before this series, the crash reproduced frequently. With both patches
applied, it was no longer reproduced in our testing.
Pu Hu (3):
arm64: kprobes: Do not handle non-XOL faults as kprobe faults
arm64: kprobes: Allow reentering kprobes while single-stepping
selftests/kprobes: Add kprobe stress test for page fault handling
arch/arm64/kernel/probes/kprobes.c | 22 +++-
tools/testing/selftests/kprobe/.gitignore | 2 +
tools/testing/selftests/kprobe/Makefile | 75 +++++++++++++
tools/testing/selftests/kprobe/fault_stress.c | 105 ++++++++++++++++++
.../selftests/kprobe/kprobe_folio_stress.c | 70 ++++++++++++
5 files changed, 273 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/kprobe/.gitignore
create mode 100644 tools/testing/selftests/kprobe/Makefile
create mode 100644 tools/testing/selftests/kprobe/fault_stress.c
create mode 100644 tools/testing/selftests/kprobe/kprobe_folio_stress.c
--
2.43.0
^ permalink raw reply
* [RFC 1/3] arm64: kprobes: Do not handle non-XOL faults as kprobe faults
From: Pu Hu @ 2026-07-06 8:36 UTC (permalink / raw)
To: mhiramat@kernel.org
Cc: ada.coupriediaz@arm.com, catalin.marinas@arm.com,
davem@davemloft.net, Hongyan Xia, Pu Hu, Jiazi Li,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
naveen@kernel.org, will@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <20260706083636.159883-1-hupu@transsion.com>
From: Pu Hu <hupu@transsion.com>
kprobe_fault_handler() handles faults taken while kprobes is in
KPROBE_HIT_SS or KPROBE_REENTER state as faults caused by the
single-stepped instruction.
That assumption is not always true. While a kprobe is preparing or
executing the out-of-line single-step instruction, other code may run
in that window. For example, perf or trace code can be invoked from the
debug exception path and may take a fault of its own. In that case the
fault did not happen on the kprobe XOL instruction, but the kprobe fault
handler may still try to recover it as a kprobe single-step fault.
This can corrupt the exception recovery flow and leave the real fault to
be handled with a wrong PC. A typical reproducer is running simpleperf
with preemptirq tracepoints and dwarf callchains while a kprobe is
installed on a frequently executed kernel function.
Fix this by handling faults in KPROBE_HIT_SS/KPROBE_REENTER only when
the faulting PC points at the current kprobe's XOL instruction. Faults
from any other PC are left to the normal fault handling path.
This follows the same idea as the x86 fix in commit 6381c24cd6d5
("kprobes/x86: Fix page-fault handling logic").
Signed-off-by: Pu Hu <hupu@transsion.com>
Signed-off-by: Hongyan Xia <hongyan.xia@transsion.com>
---
arch/arm64/kernel/probes/kprobes.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
index 43a0361a8bf0..e4d2852ce2fb 100644
--- a/arch/arm64/kernel/probes/kprobes.c
+++ b/arch/arm64/kernel/probes/kprobes.c
@@ -285,6 +285,20 @@ int __kprobes kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr)
switch (kcb->kprobe_status) {
case KPROBE_HIT_SS:
case KPROBE_REENTER:
+ /*
+ * A fault taken while a kprobe is single-stepping is not
+ * necessarily caused by the instruction in the XOL slot. For
+ * example, tracing or perf code running in this window may take
+ * an unrelated fault.
+ *
+ * Handle the fault here only when the faulting PC is the XOL
+ * instruction of the current kprobe. Otherwise let the normal
+ * fault handling path deal with it.
+ */
+ if (cur->ainsn.xol_insn &&
+ instruction_pointer(regs) != (unsigned long)cur->ainsn.xol_insn)
+ break;
+
/*
* We are here because the instruction being single
* stepped caused a page fault. We reset the current
--
2.43.0
^ permalink raw reply related
* [PATCH 2/4] refcount: add ref_trace_final_put tracepoint
From: Eugene Mavick @ 2026-07-04 11:34 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260704-refcount-final-put-trace-v1-0-b48746da1f27@mavick.dev>
Add the ref_trace_final_put tracepoint to __refcount_sub_and_test().
This fires when a refcount_t reaches zero, capturing the caller
address, the function name, and the refcount_t address.
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
include/linux/refcount.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/include/linux/refcount.h b/include/linux/refcount.h
index ba7657ced281..7b1fbf326a21 100644
--- a/include/linux/refcount.h
+++ b/include/linux/refcount.h
@@ -107,6 +107,7 @@
#include <linux/limits.h>
#include <linux/refcount_types.h>
#include <linux/spinlock_types.h>
+#include <linux/ref_trace.h>
struct mutex;
@@ -393,6 +394,7 @@ bool __refcount_sub_and_test(int i, refcount_t *r, int *oldp)
if (old > 0 && old == i) {
smp_acquire__after_ctrl_dep();
+ trace_ref_final_put(r);
return true;
}
--
2.51.2
^ permalink raw reply related
* [PATCH 4/4] kunit: add test for ref_trace_final_put
From: Eugene Mavick @ 2026-07-05 0:36 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-a41d3fd0e869@mavick.dev>
Add a KUnit test suite for the ref_trace_final_put tracepoint.
The test registers a probe function and triggers both refcount_t and
percpu_ref final put paths, verifying that the tracepoint fires
correctly and that the recorded fields match expected values.
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
MAINTAINERS | 1 +
lib/Kconfig | 10 ++++
lib/tests/Makefile | 1 +
lib/tests/ref_trace_kunit.c | 138 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 150 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 8dab37726b9d..7c387ed746ee 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4213,6 +4213,7 @@ F: include/*/atomic*.h
F: include/linux/ref_trace.h
F: include/linux/refcount.h
F: lib/ref_trace.h
+F: lib/tests/ref_trace_kunit.c
F: scripts/atomic/
F: rust/kernel/sync/atomic.rs
F: rust/kernel/sync/atomic/
diff --git a/lib/Kconfig b/lib/Kconfig
index 00a9509636c1..c29b2ccb3c31 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -52,6 +52,16 @@ config PACKING_KUNIT_TEST
When in doubt, say N.
+config REF_TRACE_KUNIT_TEST
+ bool "ref_trace kunit test" if !KUNIT_ALL_TESTS
+ depends on KUNIT && FTRACE
+ default KUNIT_ALL_TESTS
+ help
+ This option enables the KUnit test suite for the ref_trace_final_put
+ tracepoint.
+
+ If unsure, say N
+
config BITREVERSE
tristate
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 7e9c2fa52e35..828a030ad8c7 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -57,5 +57,6 @@ obj-$(CONFIG_USERCOPY_KUNIT_TEST) += usercopy_kunit.o
obj-$(CONFIG_UTIL_MACROS_KUNIT) += util_macros_kunit.o
obj-$(CONFIG_RATELIMIT_KUNIT_TEST) += test_ratelimit.o
obj-$(CONFIG_UUID_KUNIT_TEST) += uuid_kunit.o
+obj-$(CONFIG_REF_TRACE_KUNIT_TEST) += ref_trace_kunit.o
obj-$(CONFIG_TEST_RUNTIME_MODULE) += module/
diff --git a/lib/tests/ref_trace_kunit.c b/lib/tests/ref_trace_kunit.c
new file mode 100644
index 000000000000..2ece6c840fe0
--- /dev/null
+++ b/lib/tests/ref_trace_kunit.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <kunit/test.h>
+#include <linux/wait_bit.h>
+#include <linux/instruction_pointer.h>
+#include <linux/kallsyms.h>
+#include <linux/percpu-refcount.h>
+#include <linux/refcount.h>
+#include <trace/events/ref_trace.h>
+
+struct data {
+ unsigned long caller;
+ const char *fn;
+ const void *obj;
+ int *flag;
+ struct kunit *test;
+};
+
+struct data refc_chk;
+struct data pcpu_chk;
+
+//called when tracepoint fires
+static void probe(
+ void *ignore,
+ unsigned long caller,
+ const char *fn,
+ const void *obj)
+{
+ struct data *chk_val;
+
+ char func_name[KSYM_SYMBOL_LEN];
+
+ sprint_symbol_no_offset(func_name, caller);
+
+ if (!strcmp(func_name, "test_refcount")) {
+ chk_val = &refc_chk;
+ KUNIT_EXPECT_FALSE(chk_val->test, memcmp(obj, chk_val->obj, sizeof(refcount_t)));
+ } else if (!strcmp(func_name, "test_percpu")) {
+ chk_val = &pcpu_chk;
+ KUNIT_EXPECT_FALSE(chk_val->test, memcmp(
+ obj, chk_val->obj, sizeof(struct percpu_ref)));
+ } else {
+ //non test function origin trace events
+ return;
+ }
+
+ struct kunit *test = chk_val->test;
+ int *flag = chk_val->flag;
+
+ //ensure past flag writes are done before reading
+ KUNIT_EXPECT_EQ(test, 1, smp_load_acquire(flag));
+ KUNIT_EXPECT_EQ(test, caller, chk_val->caller);
+
+ smp_store_release(flag, 0); //signal probe completion
+}
+
+
+
+static void test_refcount(struct kunit *test)
+{
+ refcount_t refc;
+ int flag_addr = 0;
+
+ refc_chk.caller = (unsigned long)&test_refcount;
+ refc_chk.fn = "__refcount_sub_and_test";
+ refc_chk.obj = &refc;
+ refc_chk.flag = &flag_addr;
+ refc_chk.test = test;
+
+ int *flag = refc_chk.flag;
+
+ KUNIT_EXPECT_FALSE(test, register_trace_ref_trace_final_put(probe, NULL));
+
+ refcount_set(&refc, 2);
+
+ KUNIT_EXPECT_FALSE(test, refcount_dec_and_test(&refc));
+
+ smp_store_release(flag, 1); //signal final put can happen
+
+ KUNIT_EXPECT_TRUE(test, refcount_dec_and_test(&refc));
+
+ wait_var_event(flag, smp_load_acquire(flag)); //wait for probe completion
+ unregister_trace_ref_trace_final_put(probe, NULL);
+}
+
+static void dummy_release(struct percpu_ref *ref) {}
+
+static void test_percpu(struct kunit *test)
+{
+ struct percpu_ref pcpu_ref;
+
+ int flag_addr = 1;
+
+ pcpu_chk.caller = (unsigned long)&test_percpu;
+ pcpu_chk.fn = "percpu_ref_put_many";
+ pcpu_chk.obj = &pcpu_ref;
+ pcpu_chk.flag = &flag_addr;
+ pcpu_chk.test = test;
+
+ int *flag = pcpu_chk.flag;
+
+ KUNIT_EXPECT_FALSE(test, register_trace_ref_trace_final_put(probe, NULL));
+
+ KUNIT_EXPECT_FALSE(test, percpu_ref_init(&pcpu_ref, dummy_release, 0, GFP_KERNEL));
+
+ percpu_ref_get(&pcpu_ref);
+ percpu_ref_get(&pcpu_ref);
+
+ percpu_ref_put(&pcpu_ref);
+ percpu_ref_put(&pcpu_ref);
+
+ percpu_ref_switch_to_atomic_sync(&pcpu_ref);
+
+ smp_store_release(flag, 1); //signal final put can happen
+
+ percpu_ref_put(&pcpu_ref);
+
+ wait_var_event(flag, smp_load_acquire(flag)); //wait for probe completion
+ unregister_trace_ref_trace_final_put(probe, NULL);
+ percpu_ref_exit(&pcpu_ref);
+}
+
+static struct kunit_case __refdata ref_trace_test_cases[] = {
+ KUNIT_CASE(test_refcount),
+ KUNIT_CASE(test_percpu),
+ {}
+};
+
+static struct kunit_suite ref_trace_test_suite = {
+ .name = "ref-trace",
+ .test_cases = ref_trace_test_cases,
+};
+
+kunit_test_suites(&ref_trace_test_suite);
+
+MODULE_AUTHOR("Eugene Mavick <m@mavick.dev>");
+MODULE_DESCRIPTION("KUnit test for ref_trace");
+MODULE_LICENSE("GPL");
--
2.51.2
^ permalink raw reply related
* [PATCH 1/4] tracing: add ref_trace_final_put tracepoint
From: Eugene Mavick @ 2026-07-04 23:19 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-cdd0014626a9@mavick.dev>
Add ref_trace_final_put tracepoint and related core infrastructure
ref_trace_final_put fires when a reference
count reaches zero and the object enters its final release path.
The tracepoint records three fields:
- caller: function that called the refcounting
function(refcount_sub_and_test, percpu_ref_put_many)
- fn: refcounting function(eg refcount_sub_and_test)
- obj: refcount object(struct percpu_ref, refcount_t)
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
MAINTAINERS | 2 ++
include/linux/ref_trace.h | 26 +++++++++++++++++++++++
include/trace/events/ref_trace.h | 46 ++++++++++++++++++++++++++++++++++++++++
lib/Makefile | 2 ++
lib/ref_trace.c | 12 +++++++++++
5 files changed, 88 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 10e8253181d3..8dab37726b9d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4210,7 +4210,9 @@ S: Maintained
F: Documentation/atomic_*.txt
F: arch/*/include/asm/atomic*.h
F: include/*/atomic*.h
+F: include/linux/ref_trace.h
F: include/linux/refcount.h
+F: lib/ref_trace.h
F: scripts/atomic/
F: rust/kernel/sync/atomic.rs
F: rust/kernel/sync/atomic/
diff --git a/include/linux/ref_trace.h b/include/linux/ref_trace.h
new file mode 100644
index 000000000000..54e9fba9e3e9
--- /dev/null
+++ b/include/linux/ref_trace.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_REF_TRACE_H
+#define _LINUX_REF_TRACE_H
+
+#include <linux/tracepoint-defs.h>
+#include <linux/instruction_pointer.h>
+
+/* Declare the tracepoint so tracepoint_enabled() can be used */
+DECLARE_TRACEPOINT(ref_trace_final_put);
+
+#ifdef CONFIG_TRACEPOINTS
+/* Wrapper function implemented in lib/ref_trace.c */
+extern void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj);
+
+#define trace_ref_final_put(obj) \
+ do { \
+ if (tracepoint_enabled(ref_trace_final_put)) \
+ do_ref_trace_final_put(_RET_IP_, __func__, obj); \
+ } while (0)
+
+#else /* !CONFIG_TRACEPOINTS */
+static inline void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj) { }
+#define trace_ref_final_put(obj) do { } while (0)
+#endif
+
+#endif /* _LINUX_REF_TRACE_H */
diff --git a/include/trace/events/ref_trace.h b/include/trace/events/ref_trace.h
new file mode 100644
index 000000000000..e6037a325be2
--- /dev/null
+++ b/include/trace/events/ref_trace.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM ref_trace
+
+#if !defined(_TRACE_REF_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_REF_TRACE_H
+
+#include <linux/tracepoint.h>
+
+/**
+ * ref_trace_final_put - trace when a reference count reaches zero
+ * @caller: function that called the refcounting
+ * function(refcount_sub_and_test, percpu_ref_put_many)
+ * @fn: refcounting function(eg refcount_sub_and_test)
+ * @obj: refcount object(struct percpu_ref, refcount_t)
+ *
+ * Tracepoint instrumentation can be added using the ref_trace_final_put
+ * macro defined in include/linux/ref_trace.h
+ * which uses _RET_IP_ and __func__ for caller and fn arguments respectively,
+ * thus only requiring obj arg to be supplied
+ */
+TRACE_EVENT(ref_trace_final_put,
+
+ TP_PROTO(unsigned long caller, const char *fn, const void *obj),
+
+ TP_ARGS(caller, fn, obj),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, caller)
+ __string(fn, fn)
+ __field(const void *, obj)
+ ),
+
+ TP_fast_assign(
+ __entry->caller = caller;
+ __assign_str(fn);
+ __entry->obj = obj;
+ ),
+
+ TP_printk("caller=%pS fn=%s obj=%p", (void *)__entry->caller, __get_str(fn), __entry->obj)
+);
+
+#endif /* _TRACE_REF_TRACE_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/lib/Makefile b/lib/Makefile
index f33a24bf1c19..41737090a95d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -335,3 +335,5 @@ CONTEXT_ANALYSIS_test_context-analysis.o := y
obj-$(CONFIG_CONTEXT_ANALYSIS_TEST) += test_context-analysis.o
subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify
+
+obj-$(CONFIG_TRACEPOINTS) += ref_trace.o
diff --git a/lib/ref_trace.c b/lib/ref_trace.c
new file mode 100644
index 000000000000..25f7c17c7c47
--- /dev/null
+++ b/lib/ref_trace.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+#define CREATE_TRACE_POINTS
+#include <trace/events/ref_trace.h>
+
+//Wrapper function for functions defined entirely in header files
+void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj)
+{
+ trace_ref_trace_final_put(caller, fn, obj);
+}
+EXPORT_SYMBOL_GPL(do_ref_trace_final_put);
+
+EXPORT_TRACEPOINT_SYMBOL_GPL(ref_trace_final_put);
--
2.51.2
^ permalink raw reply related
* [PATCH 3/4] percpu-refcount: add ref_trace_final_put trace
From: Eugene Mavick @ 2026-07-04 11:34 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260704-refcount-final-put-trace-v1-0-b48746da1f27@mavick.dev>
Add the ref_trace_final_put tracepoint to percpu_ref_put_many().
The tracepoint fires when the atomic counter reaches zero in the
atomic fallback path (after percpu_ref_kill() has been called).
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
include/linux/percpu-refcount.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/include/linux/percpu-refcount.h b/include/linux/percpu-refcount.h
index d73a1c08c3e3..55543ecdf60c 100644
--- a/include/linux/percpu-refcount.h
+++ b/include/linux/percpu-refcount.h
@@ -55,6 +55,7 @@
#include <linux/rcupdate.h>
#include <linux/types.h>
#include <linux/gfp.h>
+#include <linux/ref_trace.h>
struct percpu_ref;
typedef void (percpu_ref_func_t)(struct percpu_ref *);
@@ -331,8 +332,10 @@ static inline void percpu_ref_put_many(struct percpu_ref *ref, unsigned long nr)
if (__ref_is_percpu(ref, &percpu_count))
this_cpu_sub(*percpu_count, nr);
- else if (unlikely(atomic_long_sub_and_test(nr, &ref->data->count)))
+ else if (unlikely(atomic_long_sub_and_test(nr, &ref->data->count))) {
+ trace_ref_final_put(ref);
ref->data->release(ref);
+ }
rcu_read_unlock();
}
--
2.51.2
^ permalink raw reply related
* [PATCH 4/4] kunit: add test for ref_trace_final_put
From: Eugene Mavick @ 2026-07-04 23:19 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-cdd0014626a9@mavick.dev>
Add a KUnit test suite for the ref_trace_final_put tracepoint.
The test registers a probe function and triggers both refcount_t and
percpu_ref final put paths, verifying that the tracepoint fires
correctly and that the recorded fields match expected values.
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
MAINTAINERS | 1 +
lib/Kconfig | 10 ++++
lib/tests/Makefile | 1 +
lib/tests/ref_trace_kunit.c | 138 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 150 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 8dab37726b9d..7c387ed746ee 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4213,6 +4213,7 @@ F: include/*/atomic*.h
F: include/linux/ref_trace.h
F: include/linux/refcount.h
F: lib/ref_trace.h
+F: lib/tests/ref_trace_kunit.c
F: scripts/atomic/
F: rust/kernel/sync/atomic.rs
F: rust/kernel/sync/atomic/
diff --git a/lib/Kconfig b/lib/Kconfig
index 00a9509636c1..c29b2ccb3c31 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -52,6 +52,16 @@ config PACKING_KUNIT_TEST
When in doubt, say N.
+config REF_TRACE_KUNIT_TEST
+ bool "ref_trace kunit test" if !KUNIT_ALL_TESTS
+ depends on KUNIT && FTRACE
+ default KUNIT_ALL_TESTS
+ help
+ This option enables the KUnit test suite for the ref_trace_final_put
+ tracepoint.
+
+ If unsure, say N
+
config BITREVERSE
tristate
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 7e9c2fa52e35..828a030ad8c7 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -57,5 +57,6 @@ obj-$(CONFIG_USERCOPY_KUNIT_TEST) += usercopy_kunit.o
obj-$(CONFIG_UTIL_MACROS_KUNIT) += util_macros_kunit.o
obj-$(CONFIG_RATELIMIT_KUNIT_TEST) += test_ratelimit.o
obj-$(CONFIG_UUID_KUNIT_TEST) += uuid_kunit.o
+obj-$(CONFIG_REF_TRACE_KUNIT_TEST) += ref_trace_kunit.o
obj-$(CONFIG_TEST_RUNTIME_MODULE) += module/
diff --git a/lib/tests/ref_trace_kunit.c b/lib/tests/ref_trace_kunit.c
new file mode 100644
index 000000000000..2ece6c840fe0
--- /dev/null
+++ b/lib/tests/ref_trace_kunit.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <kunit/test.h>
+#include <linux/wait_bit.h>
+#include <linux/instruction_pointer.h>
+#include <linux/kallsyms.h>
+#include <linux/percpu-refcount.h>
+#include <linux/refcount.h>
+#include <trace/events/ref_trace.h>
+
+struct data {
+ unsigned long caller;
+ const char *fn;
+ const void *obj;
+ int *flag;
+ struct kunit *test;
+};
+
+struct data refc_chk;
+struct data pcpu_chk;
+
+//called when tracepoint fires
+static void probe(
+ void *ignore,
+ unsigned long caller,
+ const char *fn,
+ const void *obj)
+{
+ struct data *chk_val;
+
+ char func_name[KSYM_SYMBOL_LEN];
+
+ sprint_symbol_no_offset(func_name, caller);
+
+ if (!strcmp(func_name, "test_refcount")) {
+ chk_val = &refc_chk;
+ KUNIT_EXPECT_FALSE(chk_val->test, memcmp(obj, chk_val->obj, sizeof(refcount_t)));
+ } else if (!strcmp(func_name, "test_percpu")) {
+ chk_val = &pcpu_chk;
+ KUNIT_EXPECT_FALSE(chk_val->test, memcmp(
+ obj, chk_val->obj, sizeof(struct percpu_ref)));
+ } else {
+ //non test function origin trace events
+ return;
+ }
+
+ struct kunit *test = chk_val->test;
+ int *flag = chk_val->flag;
+
+ //ensure past flag writes are done before reading
+ KUNIT_EXPECT_EQ(test, 1, smp_load_acquire(flag));
+ KUNIT_EXPECT_EQ(test, caller, chk_val->caller);
+
+ smp_store_release(flag, 0); //signal probe completion
+}
+
+
+
+static void test_refcount(struct kunit *test)
+{
+ refcount_t refc;
+ int flag_addr = 0;
+
+ refc_chk.caller = (unsigned long)&test_refcount;
+ refc_chk.fn = "__refcount_sub_and_test";
+ refc_chk.obj = &refc;
+ refc_chk.flag = &flag_addr;
+ refc_chk.test = test;
+
+ int *flag = refc_chk.flag;
+
+ KUNIT_EXPECT_FALSE(test, register_trace_ref_trace_final_put(probe, NULL));
+
+ refcount_set(&refc, 2);
+
+ KUNIT_EXPECT_FALSE(test, refcount_dec_and_test(&refc));
+
+ smp_store_release(flag, 1); //signal final put can happen
+
+ KUNIT_EXPECT_TRUE(test, refcount_dec_and_test(&refc));
+
+ wait_var_event(flag, smp_load_acquire(flag)); //wait for probe completion
+ unregister_trace_ref_trace_final_put(probe, NULL);
+}
+
+static void dummy_release(struct percpu_ref *ref) {}
+
+static void test_percpu(struct kunit *test)
+{
+ struct percpu_ref pcpu_ref;
+
+ int flag_addr = 1;
+
+ pcpu_chk.caller = (unsigned long)&test_percpu;
+ pcpu_chk.fn = "percpu_ref_put_many";
+ pcpu_chk.obj = &pcpu_ref;
+ pcpu_chk.flag = &flag_addr;
+ pcpu_chk.test = test;
+
+ int *flag = pcpu_chk.flag;
+
+ KUNIT_EXPECT_FALSE(test, register_trace_ref_trace_final_put(probe, NULL));
+
+ KUNIT_EXPECT_FALSE(test, percpu_ref_init(&pcpu_ref, dummy_release, 0, GFP_KERNEL));
+
+ percpu_ref_get(&pcpu_ref);
+ percpu_ref_get(&pcpu_ref);
+
+ percpu_ref_put(&pcpu_ref);
+ percpu_ref_put(&pcpu_ref);
+
+ percpu_ref_switch_to_atomic_sync(&pcpu_ref);
+
+ smp_store_release(flag, 1); //signal final put can happen
+
+ percpu_ref_put(&pcpu_ref);
+
+ wait_var_event(flag, smp_load_acquire(flag)); //wait for probe completion
+ unregister_trace_ref_trace_final_put(probe, NULL);
+ percpu_ref_exit(&pcpu_ref);
+}
+
+static struct kunit_case __refdata ref_trace_test_cases[] = {
+ KUNIT_CASE(test_refcount),
+ KUNIT_CASE(test_percpu),
+ {}
+};
+
+static struct kunit_suite ref_trace_test_suite = {
+ .name = "ref-trace",
+ .test_cases = ref_trace_test_cases,
+};
+
+kunit_test_suites(&ref_trace_test_suite);
+
+MODULE_AUTHOR("Eugene Mavick <m@mavick.dev>");
+MODULE_DESCRIPTION("KUnit test for ref_trace");
+MODULE_LICENSE("GPL");
--
2.51.2
^ permalink raw reply related
* [PATCH 3/4] percpu-refcount: add ref_trace_final_put trace
From: Eugene Mavick @ 2026-07-04 23:19 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-cdd0014626a9@mavick.dev>
Add the ref_trace_final_put tracepoint to percpu_ref_put_many().
The tracepoint fires when the atomic counter reaches zero in the
atomic fallback path (after percpu_ref_kill() has been called).
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
include/linux/percpu-refcount.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/include/linux/percpu-refcount.h b/include/linux/percpu-refcount.h
index d73a1c08c3e3..55543ecdf60c 100644
--- a/include/linux/percpu-refcount.h
+++ b/include/linux/percpu-refcount.h
@@ -55,6 +55,7 @@
#include <linux/rcupdate.h>
#include <linux/types.h>
#include <linux/gfp.h>
+#include <linux/ref_trace.h>
struct percpu_ref;
typedef void (percpu_ref_func_t)(struct percpu_ref *);
@@ -331,8 +332,10 @@ static inline void percpu_ref_put_many(struct percpu_ref *ref, unsigned long nr)
if (__ref_is_percpu(ref, &percpu_count))
this_cpu_sub(*percpu_count, nr);
- else if (unlikely(atomic_long_sub_and_test(nr, &ref->data->count)))
+ else if (unlikely(atomic_long_sub_and_test(nr, &ref->data->count))) {
+ trace_ref_final_put(ref);
ref->data->release(ref);
+ }
rcu_read_unlock();
}
--
2.51.2
^ permalink raw reply related
* [PATCH 2/4] refcount: add ref_trace_final_put tracepoint
From: Eugene Mavick @ 2026-07-05 0:36 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-a41d3fd0e869@mavick.dev>
Add the ref_trace_final_put tracepoint to __refcount_sub_and_test().
This fires when a refcount_t reaches zero, capturing the caller
address, the function name, and the refcount_t address.
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
include/linux/refcount.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/include/linux/refcount.h b/include/linux/refcount.h
index ba7657ced281..7b1fbf326a21 100644
--- a/include/linux/refcount.h
+++ b/include/linux/refcount.h
@@ -107,6 +107,7 @@
#include <linux/limits.h>
#include <linux/refcount_types.h>
#include <linux/spinlock_types.h>
+#include <linux/ref_trace.h>
struct mutex;
@@ -393,6 +394,7 @@ bool __refcount_sub_and_test(int i, refcount_t *r, int *oldp)
if (old > 0 && old == i) {
smp_acquire__after_ctrl_dep();
+ trace_ref_final_put(r);
return true;
}
--
2.51.2
^ permalink raw reply related
* [PATCH 0/4] tracing: add ref_trace_final_put tracing
From: Eugene Mavick @ 2026-07-04 23:19 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
When debugging use-after-free(UAF) bugs, knowing when the object reaches
0 references and enters final release can significantly aide the
debugging process.
There is currently no universal way to trace this information.
This patches traces the final puts in the most widely used refcounting
implementations, refcount_t(and thus kref which uses it), and
percpu-ref.
The tracepoint records three fields:
- caller: function that called the refcounting
function(refcount_sub_and_test, percpu_ref_put_many)
- fn: refcounting function(eg refcount_sub_and_test)
- obj: refcount object(struct percpu_ref, refcount_t)
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
Eugene Mavick (4):
tracing: add ref_trace_final_put tracepoint
refcount: add ref_trace_final_put tracepoint
percpu-refcount: add ref_trace_final_put trace
kunit: add test for ref_trace_final_put
MAINTAINERS | 3 +
include/linux/percpu-refcount.h | 5 +-
include/linux/ref_trace.h | 26 ++++++++
include/linux/refcount.h | 2 +
include/trace/events/ref_trace.h | 46 +++++++++++++
lib/Kconfig | 10 +++
lib/Makefile | 2 +
lib/ref_trace.c | 12 ++++
lib/tests/Makefile | 1 +
lib/tests/ref_trace_kunit.c | 138 +++++++++++++++++++++++++++++++++++++++
10 files changed, 244 insertions(+), 1 deletion(-)
---
base-commit: df685633c3dbc67441cc86f1c3fee58de4652ba2
change-id: 20260624-refcount-final-put-trace-49bd7c39bd5a
Best regards,
--
Eugene Mavick <m@mavick.dev>
^ permalink raw reply
* [PATCH 1/4] tracing: add ref_trace_final_put tracepoint
From: Eugene Mavick @ 2026-07-05 0:36 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-a41d3fd0e869@mavick.dev>
Add ref_trace_final_put tracepoint and related core infrastructure
ref_trace_final_put fires when a reference
count reaches zero and the object enters its final release path.
The tracepoint records three fields:
- caller: function that called the refcounting
function(refcount_sub_and_test, percpu_ref_put_many)
- fn: refcounting function(eg refcount_sub_and_test)
- obj: refcount object(struct percpu_ref, refcount_t)
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
MAINTAINERS | 2 ++
include/linux/ref_trace.h | 26 +++++++++++++++++++++++
include/trace/events/ref_trace.h | 46 ++++++++++++++++++++++++++++++++++++++++
lib/Makefile | 2 ++
lib/ref_trace.c | 12 +++++++++++
5 files changed, 88 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 10e8253181d3..8dab37726b9d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4210,7 +4210,9 @@ S: Maintained
F: Documentation/atomic_*.txt
F: arch/*/include/asm/atomic*.h
F: include/*/atomic*.h
+F: include/linux/ref_trace.h
F: include/linux/refcount.h
+F: lib/ref_trace.h
F: scripts/atomic/
F: rust/kernel/sync/atomic.rs
F: rust/kernel/sync/atomic/
diff --git a/include/linux/ref_trace.h b/include/linux/ref_trace.h
new file mode 100644
index 000000000000..54e9fba9e3e9
--- /dev/null
+++ b/include/linux/ref_trace.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_REF_TRACE_H
+#define _LINUX_REF_TRACE_H
+
+#include <linux/tracepoint-defs.h>
+#include <linux/instruction_pointer.h>
+
+/* Declare the tracepoint so tracepoint_enabled() can be used */
+DECLARE_TRACEPOINT(ref_trace_final_put);
+
+#ifdef CONFIG_TRACEPOINTS
+/* Wrapper function implemented in lib/ref_trace.c */
+extern void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj);
+
+#define trace_ref_final_put(obj) \
+ do { \
+ if (tracepoint_enabled(ref_trace_final_put)) \
+ do_ref_trace_final_put(_RET_IP_, __func__, obj); \
+ } while (0)
+
+#else /* !CONFIG_TRACEPOINTS */
+static inline void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj) { }
+#define trace_ref_final_put(obj) do { } while (0)
+#endif
+
+#endif /* _LINUX_REF_TRACE_H */
diff --git a/include/trace/events/ref_trace.h b/include/trace/events/ref_trace.h
new file mode 100644
index 000000000000..e6037a325be2
--- /dev/null
+++ b/include/trace/events/ref_trace.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM ref_trace
+
+#if !defined(_TRACE_REF_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_REF_TRACE_H
+
+#include <linux/tracepoint.h>
+
+/**
+ * ref_trace_final_put - trace when a reference count reaches zero
+ * @caller: function that called the refcounting
+ * function(refcount_sub_and_test, percpu_ref_put_many)
+ * @fn: refcounting function(eg refcount_sub_and_test)
+ * @obj: refcount object(struct percpu_ref, refcount_t)
+ *
+ * Tracepoint instrumentation can be added using the ref_trace_final_put
+ * macro defined in include/linux/ref_trace.h
+ * which uses _RET_IP_ and __func__ for caller and fn arguments respectively,
+ * thus only requiring obj arg to be supplied
+ */
+TRACE_EVENT(ref_trace_final_put,
+
+ TP_PROTO(unsigned long caller, const char *fn, const void *obj),
+
+ TP_ARGS(caller, fn, obj),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, caller)
+ __string(fn, fn)
+ __field(const void *, obj)
+ ),
+
+ TP_fast_assign(
+ __entry->caller = caller;
+ __assign_str(fn);
+ __entry->obj = obj;
+ ),
+
+ TP_printk("caller=%pS fn=%s obj=%p", (void *)__entry->caller, __get_str(fn), __entry->obj)
+);
+
+#endif /* _TRACE_REF_TRACE_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/lib/Makefile b/lib/Makefile
index f33a24bf1c19..41737090a95d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -335,3 +335,5 @@ CONTEXT_ANALYSIS_test_context-analysis.o := y
obj-$(CONFIG_CONTEXT_ANALYSIS_TEST) += test_context-analysis.o
subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify
+
+obj-$(CONFIG_TRACEPOINTS) += ref_trace.o
diff --git a/lib/ref_trace.c b/lib/ref_trace.c
new file mode 100644
index 000000000000..25f7c17c7c47
--- /dev/null
+++ b/lib/ref_trace.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+#define CREATE_TRACE_POINTS
+#include <trace/events/ref_trace.h>
+
+//Wrapper function for functions defined entirely in header files
+void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj)
+{
+ trace_ref_trace_final_put(caller, fn, obj);
+}
+EXPORT_SYMBOL_GPL(do_ref_trace_final_put);
+
+EXPORT_TRACEPOINT_SYMBOL_GPL(ref_trace_final_put);
--
2.51.2
^ permalink raw reply related
* [PATCH 2/4] refcount: add ref_trace_final_put tracepoint
From: Eugene Mavick @ 2026-07-04 23:19 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-cdd0014626a9@mavick.dev>
Add the ref_trace_final_put tracepoint to __refcount_sub_and_test().
This fires when a refcount_t reaches zero, capturing the caller
address, the function name, and the refcount_t address.
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
include/linux/refcount.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/include/linux/refcount.h b/include/linux/refcount.h
index ba7657ced281..7b1fbf326a21 100644
--- a/include/linux/refcount.h
+++ b/include/linux/refcount.h
@@ -107,6 +107,7 @@
#include <linux/limits.h>
#include <linux/refcount_types.h>
#include <linux/spinlock_types.h>
+#include <linux/ref_trace.h>
struct mutex;
@@ -393,6 +394,7 @@ bool __refcount_sub_and_test(int i, refcount_t *r, int *oldp)
if (old > 0 && old == i) {
smp_acquire__after_ctrl_dep();
+ trace_ref_final_put(r);
return true;
}
--
2.51.2
^ permalink raw reply related
* [PATCH 3/4] percpu-refcount: add ref_trace_final_put trace
From: Eugene Mavick @ 2026-07-05 0:36 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-a41d3fd0e869@mavick.dev>
Add the ref_trace_final_put tracepoint to percpu_ref_put_many().
The tracepoint fires when the atomic counter reaches zero in the
atomic fallback path (after percpu_ref_kill() has been called).
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
include/linux/percpu-refcount.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/include/linux/percpu-refcount.h b/include/linux/percpu-refcount.h
index d73a1c08c3e3..55543ecdf60c 100644
--- a/include/linux/percpu-refcount.h
+++ b/include/linux/percpu-refcount.h
@@ -55,6 +55,7 @@
#include <linux/rcupdate.h>
#include <linux/types.h>
#include <linux/gfp.h>
+#include <linux/ref_trace.h>
struct percpu_ref;
typedef void (percpu_ref_func_t)(struct percpu_ref *);
@@ -331,8 +332,10 @@ static inline void percpu_ref_put_many(struct percpu_ref *ref, unsigned long nr)
if (__ref_is_percpu(ref, &percpu_count))
this_cpu_sub(*percpu_count, nr);
- else if (unlikely(atomic_long_sub_and_test(nr, &ref->data->count)))
+ else if (unlikely(atomic_long_sub_and_test(nr, &ref->data->count))) {
+ trace_ref_final_put(ref);
ref->data->release(ref);
+ }
rcu_read_unlock();
}
--
2.51.2
^ permalink raw reply related
* [PATCH 0/4] tracing: add ref_trace_final_put tracing
From: Eugene Mavick @ 2026-07-05 0:36 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
When debugging use-after-free(UAF) bugs, knowing when the object reaches
0 references and enters final release can significantly aide the
debugging process.
There is currently no universal way to trace this information.
This patches traces the final puts in the most widely used refcounting
implementations, refcount_t(and thus kref which uses it), and
percpu-ref.
The tracepoint records three fields:
- caller: function that called the refcounting
function(refcount_sub_and_test, percpu_ref_put_many)
- fn: refcounting function(eg refcount_sub_and_test)
- obj: refcount object(struct percpu_ref, refcount_t)
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
Eugene Mavick (4):
tracing: add ref_trace_final_put tracepoint
refcount: add ref_trace_final_put tracepoint
percpu-refcount: add ref_trace_final_put trace
kunit: add test for ref_trace_final_put
MAINTAINERS | 3 +
include/linux/percpu-refcount.h | 5 +-
include/linux/ref_trace.h | 26 ++++++++
include/linux/refcount.h | 2 +
include/trace/events/ref_trace.h | 46 +++++++++++++
lib/Kconfig | 10 +++
lib/Makefile | 2 +
lib/ref_trace.c | 12 ++++
lib/tests/Makefile | 1 +
lib/tests/ref_trace_kunit.c | 138 +++++++++++++++++++++++++++++++++++++++
10 files changed, 244 insertions(+), 1 deletion(-)
---
base-commit: df685633c3dbc67441cc86f1c3fee58de4652ba2
change-id: 20260624-refcount-final-put-trace-49bd7c39bd5a
Best regards,
--
Eugene Mavick <m@mavick.dev>
^ permalink raw reply
* Re: [RFC PATCH v4 0/3] trace: stack trace deduplication for ftrace ring buffer
From: Li Pengfei @ 2026-07-06 6:31 UTC (permalink / raw)
To: rostedt, mhiramat
Cc: mathieu.desnoyers, mark.rutland, linux-trace-kernel, linux-kernel,
zhangbo56, lipengfei28
In-Reply-To: <20260616064119.438063-1-lipengfei28@xiaomi.com>
From: Pengfei Li <lipengfei28@xiaomi.com>
Hi Steven, Masami,
Gentle ping on this v4 series now that the 7.2 merge window has settled.
I'd especially value a quick steer on the two design questions from the
cover letter, since they shape how I respin:
1. Eager vs lazy allocation of the element pool (~8 MB at the default
bits=14 when CONFIG_FTRACE_STACKMAP=y, whether or not userspace ever
enables the option). Is eager allocation acceptable, or should v5
switch to lazy allocation on the first 'echo 1 > options/stackmap'?
2. Whether to introduce the stack_map_bin binary interface now, or ship
the text stack_map interface first and add the binary export once
trace-cmd / libtraceevent integration is designed.
Happy to respin in either direction. Full series for reference:
https://lore.kernel.org/all/20260616064119.438063-1-lipengfei28@xiaomi.com/
Thanks,
Pengfei
^ permalink raw reply
* [PATCH] trace: use strscpy() instead of strcpy() in trace_sched_switch
From: Po-Sheng Lin @ 2026-07-05 17:36 UTC (permalink / raw)
To: rostedt, mhiramat
Cc: mathieu.desnoyers, linux-kernel, linux-trace-kernel, Po-Sheng Lin
Replace strcpy() with strscpy() in __trace_find_cmdline() for
consistency with the existing strscpy() call in the same function,
and to avoid potential buffer overflow as flagged by the Kernel
Self Protection Project.
Signed-off-by: Po-Sheng Lin <posheng.lin.tw@gmail.com>
---
kernel/trace/trace_sched_switch.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/kernel/trace/trace_sched_switch.c b/kernel/trace/trace_sched_switch.c
index e9f0ff9626600..1bd351c1fbfb5 100644
--- a/kernel/trace/trace_sched_switch.c
+++ b/kernel/trace/trace_sched_switch.c
@@ -286,12 +286,12 @@ static void __trace_find_cmdline(int pid, char comm[])
int tpid;
if (!pid) {
- strcpy(comm, "<idle>");
+ strscpy(comm, "<idle>", TASK_COMM_LEN);
return;
}
if (WARN_ON_ONCE(pid < 0)) {
- strcpy(comm, "<XXX>");
+ strscpy(comm, "<XXX>", TASK_COMM_LEN);
return;
}
@@ -304,7 +304,7 @@ static void __trace_find_cmdline(int pid, char comm[])
return;
}
}
- strcpy(comm, "<...>");
+ strscpy(comm, "<...>", TASK_COMM_LEN);
}
void trace_find_cmdline(int pid, char comm[])
--
2.40.1
^ permalink raw reply related
* Re: [RFC] xdp: add device context to bpf_xdp_link_attach_failed tracepoint
From: Leon Hwang @ 2026-07-05 14:15 UTC (permalink / raw)
To: Masashi Honma
Cc: netdev, bpf, linux-trace-kernel, ast, daniel, kuba, hawk, andrii,
rostedt, mhiramat, edumazet, pabeni, linux-kernel
In-Reply-To: <CAFk-A4mUijKffHg3QhOxC7Ne2rk_GVbYY2UJ-Ek497avdubzgw@mail.gmail.com>
On 2026/7/5 10:11, Masashi Honma wrote:
> 2026年7月4日(土) 22:28 Leon Hwang <leon.hwang@linux.dev>:
>> Probably, you can get the 'extack->_msg' by tracing dev_xdp_attach using
>> kprobe+kretprobe or kprobe.session, if the extack is not NULL.
>
> Thanks, that's a nice pointer -- dev_xdp_attach() has both the net_device
> (so the ifindex, which lets us correlate a failure to a specific attach)
> and the extack, and it avoids depending on the tracepoint you want to
> retire.
>
> The tradeoff is that dev_xdp_attach() is a static internal function, so a
> probe on it can break across kernels (inlining/signature changes). For a
> best-effort error message that's tolerable with a graceful fallback, but
> it's a maintenance cost on our side.
>
> Since this is ultimately just an error-message improvement, and your
> in-band BPF_LINK_CREATE work would solve it cleanly for all link types, I
> think we'd lean toward waiting for that rather than adding an internal
> kprobe to Cilium. Do you have a rough timeline for the BPF_LINK_CREATE
> series? That would help us decide whether a stopgap is worth it.
I will post the RFC around October.
Thanks,
Leon
^ permalink raw reply
* [PATCH] rtla: Also link in ctype.c
From: Bastian Blank @ 2026-07-05 10:49 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar, linux-trace-kernel, linux-kernel
rtla started to only link parts of the tools library. It now misses the
ctype information used by all the related string operations. Just add
another single file to make it build again.
Signed-off-by: Bastian Blank <waldi@debian.org>
---
tools/tracing/rtla/Makefile | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile
index 60a102538988..387bc6cc18f0 100644
--- a/tools/tracing/rtla/Makefile
+++ b/tools/tracing/rtla/Makefile
@@ -45,6 +45,9 @@ else
LIB_OUTPUT = $(CURDIR)/lib
endif
+LIB_CTYPE = $(LIB_OUTPUT)/ctype.o
+LIB_CTYPE_SRC = $(srctree)/tools/lib/ctype.c
+
LIB_STRING = $(LIB_OUTPUT)/string.o
LIB_STRING_SRC = $(srctree)/tools/lib/string.c
@@ -117,12 +120,12 @@ tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c
$(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o"
endif
-$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
- $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
+$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R)
+ $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
-static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R)
+static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R)
$(eval LDFLAGS += -static)
- $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
+ $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS)
rtla.%: fixdep FORCE
make -f $(srctree)/tools/build/Makefile.build dir=. $@
@@ -150,6 +153,9 @@ $(LIB_STR_ERROR_R): $(LIB_STR_ERROR_R_SRC) | $(LIB_OUTPUT)
$(LIB_STRING): $(LIB_STRING_SRC) | $(LIB_OUTPUT)
$(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+$(LIB_CTYPE): $(LIB_CTYPE_SRC) | $(LIB_OUTPUT)
+ $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $<
+
libsubcmd-clean:
$(call QUIET_CLEAN, libsubcmd)
$(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT)
--
2.53.0
^ permalink raw reply related
* Re: [RFC] xdp: add device context to bpf_xdp_link_attach_failed tracepoint
From: Masashi Honma @ 2026-07-05 2:11 UTC (permalink / raw)
To: Leon Hwang
Cc: netdev, bpf, linux-trace-kernel, ast, daniel, kuba, hawk, andrii,
rostedt, mhiramat, edumazet, pabeni, linux-kernel
In-Reply-To: <216969a9-3584-4dc8-9e23-50fc18b31725@linux.dev>
2026年7月4日(土) 22:28 Leon Hwang <leon.hwang@linux.dev>:
> Probably, you can get the 'extack->_msg' by tracing dev_xdp_attach using
> kprobe+kretprobe or kprobe.session, if the extack is not NULL.
Thanks, that's a nice pointer -- dev_xdp_attach() has both the net_device
(so the ifindex, which lets us correlate a failure to a specific attach)
and the extack, and it avoids depending on the tracepoint you want to
retire.
The tradeoff is that dev_xdp_attach() is a static internal function, so a
probe on it can break across kernels (inlining/signature changes). For a
best-effort error message that's tolerable with a graceful fallback, but
it's a maintenance cost on our side.
Since this is ultimately just an error-message improvement, and your
in-band BPF_LINK_CREATE work would solve it cleanly for all link types, I
think we'd lean toward waiting for that rather than adding an internal
kprobe to Cilium. Do you have a rough timeline for the BPF_LINK_CREATE
series? That would help us decide whether a stopgap is worth it.
Regards,
Masashi Honma.
^ permalink raw reply
* [PATCH 4/4] kunit: add test for ref_trace_final_put
From: Eugene Mavick via B4 Relay @ 2026-07-05 1:20 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-0ae936edb750@mavick.dev>
From: Eugene Mavick <m@mavick.dev>
Add a KUnit test suite for the ref_trace_final_put tracepoint.
The test registers a probe function and triggers both refcount_t and
percpu_ref final put paths, verifying that the tracepoint fires
correctly and that the recorded fields match expected values.
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
MAINTAINERS | 1 +
lib/Kconfig | 10 ++++
lib/tests/Makefile | 1 +
lib/tests/ref_trace_kunit.c | 138 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 150 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 8dab37726b9d..7c387ed746ee 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4213,6 +4213,7 @@ F: include/*/atomic*.h
F: include/linux/ref_trace.h
F: include/linux/refcount.h
F: lib/ref_trace.h
+F: lib/tests/ref_trace_kunit.c
F: scripts/atomic/
F: rust/kernel/sync/atomic.rs
F: rust/kernel/sync/atomic/
diff --git a/lib/Kconfig b/lib/Kconfig
index 00a9509636c1..c29b2ccb3c31 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -52,6 +52,16 @@ config PACKING_KUNIT_TEST
When in doubt, say N.
+config REF_TRACE_KUNIT_TEST
+ bool "ref_trace kunit test" if !KUNIT_ALL_TESTS
+ depends on KUNIT && FTRACE
+ default KUNIT_ALL_TESTS
+ help
+ This option enables the KUnit test suite for the ref_trace_final_put
+ tracepoint.
+
+ If unsure, say N
+
config BITREVERSE
tristate
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 7e9c2fa52e35..828a030ad8c7 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -57,5 +57,6 @@ obj-$(CONFIG_USERCOPY_KUNIT_TEST) += usercopy_kunit.o
obj-$(CONFIG_UTIL_MACROS_KUNIT) += util_macros_kunit.o
obj-$(CONFIG_RATELIMIT_KUNIT_TEST) += test_ratelimit.o
obj-$(CONFIG_UUID_KUNIT_TEST) += uuid_kunit.o
+obj-$(CONFIG_REF_TRACE_KUNIT_TEST) += ref_trace_kunit.o
obj-$(CONFIG_TEST_RUNTIME_MODULE) += module/
diff --git a/lib/tests/ref_trace_kunit.c b/lib/tests/ref_trace_kunit.c
new file mode 100644
index 000000000000..2ece6c840fe0
--- /dev/null
+++ b/lib/tests/ref_trace_kunit.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <kunit/test.h>
+#include <linux/wait_bit.h>
+#include <linux/instruction_pointer.h>
+#include <linux/kallsyms.h>
+#include <linux/percpu-refcount.h>
+#include <linux/refcount.h>
+#include <trace/events/ref_trace.h>
+
+struct data {
+ unsigned long caller;
+ const char *fn;
+ const void *obj;
+ int *flag;
+ struct kunit *test;
+};
+
+struct data refc_chk;
+struct data pcpu_chk;
+
+//called when tracepoint fires
+static void probe(
+ void *ignore,
+ unsigned long caller,
+ const char *fn,
+ const void *obj)
+{
+ struct data *chk_val;
+
+ char func_name[KSYM_SYMBOL_LEN];
+
+ sprint_symbol_no_offset(func_name, caller);
+
+ if (!strcmp(func_name, "test_refcount")) {
+ chk_val = &refc_chk;
+ KUNIT_EXPECT_FALSE(chk_val->test, memcmp(obj, chk_val->obj, sizeof(refcount_t)));
+ } else if (!strcmp(func_name, "test_percpu")) {
+ chk_val = &pcpu_chk;
+ KUNIT_EXPECT_FALSE(chk_val->test, memcmp(
+ obj, chk_val->obj, sizeof(struct percpu_ref)));
+ } else {
+ //non test function origin trace events
+ return;
+ }
+
+ struct kunit *test = chk_val->test;
+ int *flag = chk_val->flag;
+
+ //ensure past flag writes are done before reading
+ KUNIT_EXPECT_EQ(test, 1, smp_load_acquire(flag));
+ KUNIT_EXPECT_EQ(test, caller, chk_val->caller);
+
+ smp_store_release(flag, 0); //signal probe completion
+}
+
+
+
+static void test_refcount(struct kunit *test)
+{
+ refcount_t refc;
+ int flag_addr = 0;
+
+ refc_chk.caller = (unsigned long)&test_refcount;
+ refc_chk.fn = "__refcount_sub_and_test";
+ refc_chk.obj = &refc;
+ refc_chk.flag = &flag_addr;
+ refc_chk.test = test;
+
+ int *flag = refc_chk.flag;
+
+ KUNIT_EXPECT_FALSE(test, register_trace_ref_trace_final_put(probe, NULL));
+
+ refcount_set(&refc, 2);
+
+ KUNIT_EXPECT_FALSE(test, refcount_dec_and_test(&refc));
+
+ smp_store_release(flag, 1); //signal final put can happen
+
+ KUNIT_EXPECT_TRUE(test, refcount_dec_and_test(&refc));
+
+ wait_var_event(flag, smp_load_acquire(flag)); //wait for probe completion
+ unregister_trace_ref_trace_final_put(probe, NULL);
+}
+
+static void dummy_release(struct percpu_ref *ref) {}
+
+static void test_percpu(struct kunit *test)
+{
+ struct percpu_ref pcpu_ref;
+
+ int flag_addr = 1;
+
+ pcpu_chk.caller = (unsigned long)&test_percpu;
+ pcpu_chk.fn = "percpu_ref_put_many";
+ pcpu_chk.obj = &pcpu_ref;
+ pcpu_chk.flag = &flag_addr;
+ pcpu_chk.test = test;
+
+ int *flag = pcpu_chk.flag;
+
+ KUNIT_EXPECT_FALSE(test, register_trace_ref_trace_final_put(probe, NULL));
+
+ KUNIT_EXPECT_FALSE(test, percpu_ref_init(&pcpu_ref, dummy_release, 0, GFP_KERNEL));
+
+ percpu_ref_get(&pcpu_ref);
+ percpu_ref_get(&pcpu_ref);
+
+ percpu_ref_put(&pcpu_ref);
+ percpu_ref_put(&pcpu_ref);
+
+ percpu_ref_switch_to_atomic_sync(&pcpu_ref);
+
+ smp_store_release(flag, 1); //signal final put can happen
+
+ percpu_ref_put(&pcpu_ref);
+
+ wait_var_event(flag, smp_load_acquire(flag)); //wait for probe completion
+ unregister_trace_ref_trace_final_put(probe, NULL);
+ percpu_ref_exit(&pcpu_ref);
+}
+
+static struct kunit_case __refdata ref_trace_test_cases[] = {
+ KUNIT_CASE(test_refcount),
+ KUNIT_CASE(test_percpu),
+ {}
+};
+
+static struct kunit_suite ref_trace_test_suite = {
+ .name = "ref-trace",
+ .test_cases = ref_trace_test_cases,
+};
+
+kunit_test_suites(&ref_trace_test_suite);
+
+MODULE_AUTHOR("Eugene Mavick <m@mavick.dev>");
+MODULE_DESCRIPTION("KUnit test for ref_trace");
+MODULE_LICENSE("GPL");
--
2.51.2
^ permalink raw reply related
* [PATCH 0/4] tracing: add ref_trace_final_put tracing
From: Eugene Mavick via B4 Relay @ 2026-07-05 1:20 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
When debugging use-after-free(UAF) bugs, knowing when the object reaches
0 references and enters final release can significantly aide the
debugging process.
There is currently no universal way to trace this information.
This patches traces the final puts in the most widely used refcounting
implementations, refcount_t(and thus kref which uses it), and
percpu-ref.
The tracepoint records three fields:
- caller: function that called the refcounting
function(refcount_sub_and_test, percpu_ref_put_many)
- fn: refcounting function(eg refcount_sub_and_test)
- obj: refcount object(struct percpu_ref, refcount_t)
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
Eugene Mavick (4):
tracing: add ref_trace_final_put tracepoint
refcount: add ref_trace_final_put tracepoint
percpu-refcount: add ref_trace_final_put trace
kunit: add test for ref_trace_final_put
MAINTAINERS | 3 +
include/linux/percpu-refcount.h | 5 +-
include/linux/ref_trace.h | 26 ++++++++
include/linux/refcount.h | 2 +
include/trace/events/ref_trace.h | 46 +++++++++++++
lib/Kconfig | 10 +++
lib/Makefile | 2 +
lib/ref_trace.c | 12 ++++
lib/tests/Makefile | 1 +
lib/tests/ref_trace_kunit.c | 138 +++++++++++++++++++++++++++++++++++++++
10 files changed, 244 insertions(+), 1 deletion(-)
---
base-commit: df685633c3dbc67441cc86f1c3fee58de4652ba2
change-id: 20260624-refcount-final-put-trace-49bd7c39bd5a
Best regards,
--
Eugene Mavick <m@mavick.dev>
^ permalink raw reply
* [PATCH 1/4] tracing: add ref_trace_final_put tracepoint
From: Eugene Mavick via B4 Relay @ 2026-07-05 1:20 UTC (permalink / raw)
To: Will Deacon, Peter Zijlstra, Boqun Feng, Mark Rutland, Gary Guo,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Andrew Morton, Dennis Zhou, Tejun Heo, Christoph Lameter
Cc: linux-kernel, linux-trace-kernel, linux-mm, Eugene Mavick
In-Reply-To: <20260705-refcount-final-put-trace-v1-0-0ae936edb750@mavick.dev>
From: Eugene Mavick <m@mavick.dev>
Add ref_trace_final_put tracepoint and related core infrastructure
ref_trace_final_put fires when a reference
count reaches zero and the object enters its final release path.
The tracepoint records three fields:
- caller: function that called the refcounting
function(refcount_sub_and_test, percpu_ref_put_many)
- fn: refcounting function(eg refcount_sub_and_test)
- obj: refcount object(struct percpu_ref, refcount_t)
Signed-off-by: Eugene Mavick <m@mavick.dev>
---
MAINTAINERS | 2 ++
include/linux/ref_trace.h | 26 +++++++++++++++++++++++
include/trace/events/ref_trace.h | 46 ++++++++++++++++++++++++++++++++++++++++
lib/Makefile | 2 ++
lib/ref_trace.c | 12 +++++++++++
5 files changed, 88 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 10e8253181d3..8dab37726b9d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4210,7 +4210,9 @@ S: Maintained
F: Documentation/atomic_*.txt
F: arch/*/include/asm/atomic*.h
F: include/*/atomic*.h
+F: include/linux/ref_trace.h
F: include/linux/refcount.h
+F: lib/ref_trace.h
F: scripts/atomic/
F: rust/kernel/sync/atomic.rs
F: rust/kernel/sync/atomic/
diff --git a/include/linux/ref_trace.h b/include/linux/ref_trace.h
new file mode 100644
index 000000000000..54e9fba9e3e9
--- /dev/null
+++ b/include/linux/ref_trace.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_REF_TRACE_H
+#define _LINUX_REF_TRACE_H
+
+#include <linux/tracepoint-defs.h>
+#include <linux/instruction_pointer.h>
+
+/* Declare the tracepoint so tracepoint_enabled() can be used */
+DECLARE_TRACEPOINT(ref_trace_final_put);
+
+#ifdef CONFIG_TRACEPOINTS
+/* Wrapper function implemented in lib/ref_trace.c */
+extern void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj);
+
+#define trace_ref_final_put(obj) \
+ do { \
+ if (tracepoint_enabled(ref_trace_final_put)) \
+ do_ref_trace_final_put(_RET_IP_, __func__, obj); \
+ } while (0)
+
+#else /* !CONFIG_TRACEPOINTS */
+static inline void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj) { }
+#define trace_ref_final_put(obj) do { } while (0)
+#endif
+
+#endif /* _LINUX_REF_TRACE_H */
diff --git a/include/trace/events/ref_trace.h b/include/trace/events/ref_trace.h
new file mode 100644
index 000000000000..e6037a325be2
--- /dev/null
+++ b/include/trace/events/ref_trace.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM ref_trace
+
+#if !defined(_TRACE_REF_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_REF_TRACE_H
+
+#include <linux/tracepoint.h>
+
+/**
+ * ref_trace_final_put - trace when a reference count reaches zero
+ * @caller: function that called the refcounting
+ * function(refcount_sub_and_test, percpu_ref_put_many)
+ * @fn: refcounting function(eg refcount_sub_and_test)
+ * @obj: refcount object(struct percpu_ref, refcount_t)
+ *
+ * Tracepoint instrumentation can be added using the ref_trace_final_put
+ * macro defined in include/linux/ref_trace.h
+ * which uses _RET_IP_ and __func__ for caller and fn arguments respectively,
+ * thus only requiring obj arg to be supplied
+ */
+TRACE_EVENT(ref_trace_final_put,
+
+ TP_PROTO(unsigned long caller, const char *fn, const void *obj),
+
+ TP_ARGS(caller, fn, obj),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, caller)
+ __string(fn, fn)
+ __field(const void *, obj)
+ ),
+
+ TP_fast_assign(
+ __entry->caller = caller;
+ __assign_str(fn);
+ __entry->obj = obj;
+ ),
+
+ TP_printk("caller=%pS fn=%s obj=%p", (void *)__entry->caller, __get_str(fn), __entry->obj)
+);
+
+#endif /* _TRACE_REF_TRACE_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/lib/Makefile b/lib/Makefile
index f33a24bf1c19..41737090a95d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -335,3 +335,5 @@ CONTEXT_ANALYSIS_test_context-analysis.o := y
obj-$(CONFIG_CONTEXT_ANALYSIS_TEST) += test_context-analysis.o
subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify
+
+obj-$(CONFIG_TRACEPOINTS) += ref_trace.o
diff --git a/lib/ref_trace.c b/lib/ref_trace.c
new file mode 100644
index 000000000000..25f7c17c7c47
--- /dev/null
+++ b/lib/ref_trace.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+#define CREATE_TRACE_POINTS
+#include <trace/events/ref_trace.h>
+
+//Wrapper function for functions defined entirely in header files
+void do_ref_trace_final_put(unsigned long caller, const char *fn, const void *obj)
+{
+ trace_ref_trace_final_put(caller, fn, obj);
+}
+EXPORT_SYMBOL_GPL(do_ref_trace_final_put);
+
+EXPORT_TRACEPOINT_SYMBOL_GPL(ref_trace_final_put);
--
2.51.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox