Linux Trace Kernel
 help / color / mirror / Atom feed
* Re: [RFC v3 2/2] arm64: kprobes: Allow reentering kprobes while single-stepping
From: Will Deacon @ 2026-07-16 15:20 UTC (permalink / raw)
  To: Pu Hu
  Cc: mhiramat@kernel.org, ada.coupriediaz@arm.com,
	catalin.marinas@arm.com, davem@davemloft.net, Hongyan Xia,
	Jiazi Li, linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <0f049d38-b7f6-47f0-a410-95ad2aee7fd4@transsion.com>

On Thu, Jul 16, 2026 at 02:38:58PM +0000, Pu Hu wrote:
> On 7/16/2026 9:24 PM, Will Deacon wrote:
> > On Fri, Jul 10, 2026 at 06:32:55AM +0000, Pu Hu wrote:
> >> 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.
> > 
> > I don't understand this part. The single-step runs with debug exceptions
> > disabled (kprobes_save_local_irqflag() sets PSTATE.D) so how do we end
> > up taking one?
>
> You are right that the single-step runs with debug exceptions disabled.
> However, the case I was referring to is not a hardware breakpoint or a
> software-step exception, but another Breakpoint Instruction exception
> generated by executing a BRK instruction. A BRK instruction exception is 
> not masked by PSTATE.D, so it can still be taken while handling a kprobe.
> 
> As far as I understand the architecture, there are two different cases here:
> 
>    - Breakpoint Instruction exceptions, generated by executing a BRK
>      instruction.
>    - Breakpoint exceptions, generated by the debug logic, for example by
>      programmed breakpoint registers.
> 
> PSTATE.D masks debug exceptions such as hardware breakpoints, 
> watchpoints and software-step exceptions, but it does not mask 
> Breakpoint Instruction exceptions generated by BRK. This also seems 
> consistent with the pseudocode for BRK, 
> Arch64.SoftwareBreakpoint(imm16), which does not appear to check 
> PSTATE.D before taking the exception.
> 
> Therefore, even if kprobes_save_local_irqflag() sets PSTATE.D while 
> handling the first kprobe, if the code executed from that path reaches 
> another instruction patched with BRK, it can still take a Breakpoint 
> Instruction exception. In other words, the nested case I mentioned is 
> another kprobe BRK being hit, not a hardware debug exception or a 
> software-step exception.

Yes, that's correct, but if we're doing the out-of-line step, how do we
end up executing a BRK? Or are you saying that it's the kprobes
BRK64_OPCODE_KPROBES_SS instruction that we use to implement the
single-step that is the problem? If so, how does taking that exception
result in us executing tracing or perf code?

Sorry for all the questions, I just haven't understood what's going on
here from the commit message.

Will

^ permalink raw reply

* Re: [PATCH] rtla/cli: Unify and improve handling of invalid option arguments
From: Wander Lairson Costa @ 2026-07-16 16:20 UTC (permalink / raw)
  To: Tomas Glozar
  Cc: Steven Rostedt, John Kacur, Luis Goncalves, Crystal Wood,
	Costa Shulyupin, LKML, linux-trace-kernel
In-Reply-To: <20260716144901.1187474-1-tglozar@redhat.com>

On Thu, Jul 16, 2026 at 04:49:01PM +0200, Tomas Glozar wrote:
> The current handling of invalid command line option arguments is
> inconsistent:
> 
> - opt_llong_callback() treats non-numerical input the same as "-1",
>   which might or might not be rejected later.
> - opt_int_callback() returns -1 on non-numerical input without an error
>   message, which makes parsing fail silently (libsubcmd will
>   automatically print the usage of the option only, no error message).
> - custom callbacks abort command line parsing using fatal(), which
>   displays an error message and exits, without libsubcmd printing the
>   usage.
> 
> Unify this such that all invalid options, regardless of the format,
> print an error message similar to the out of range case:
> 
> Error: --opt: 'value' is not a valid XY
> 
> followed by the usage of the option, e.g.:
> 
> $ rtla timerlat hist --period=1us
>  Error: --period: '1us' is not a valid number
> 
>  Usage: rtla timerlat hist [<options>] [-h|--help]
> 
>     -p, --period <us>     timerlat period in us
> 
> As this is a libsubcmd help path, all option parsing failures now return
> the exit code of 129 (help).
> 
> The unified handling is implemented using a new error message helper,
> opt_err(), which is called from two new CLI-specific parsing functions,
> strtoll_safe() and strtoi_safe(), as well as from custom helpers.
> 
> Option callback tests are updated to cover the new behavior.
> 
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Tomas Glozar <tglozar@redhat.com>

Reviewed-by: Wander Lairson Costa <wander@redhat.com>


^ permalink raw reply

* Re: [PATCH v8 09/46] KVM: guest_memfd: Introduce function to check GFN private/shared status
From: Ackerley Tng @ 2026-07-16 17:20 UTC (permalink / raw)
  To: Binbin Wu
  Cc: aik, andrew.jones, brauner, chao.p.peng, david, jmattson,
	jthoughton, michael.roth, oupton, pankaj.gupta, qperret,
	rick.p.edgecombe, rientjes, shivankg, steven.price, tabba, willy,
	wyihan, yan.y.zhao, forkloop, pratyush, suzuki.poulose,
	aneesh.kumar, liam, Paolo Bonzini, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Shuah Khan,
	Vishal Annapurve, Andrew Morton, Chris Li, Kairui Song,
	Kemeng Shi, Nhat Pham, Barry Song, Axel Rasmussen, Yuanchu Xie,
	Wei Xu, Youngjun Park, Qi Zheng, Shakeel Butt, Kiryl Shutsemau,
	Baoquan He, Jason Gunthorpe, Vlastimil Babka, kvm, linux-kernel,
	linux-trace-kernel, linux-doc, linux-kselftest, linux-mm,
	linux-coco
In-Reply-To: <1b59fec2-a464-4429-8532-880394912af5@linux.intel.com>

Binbin Wu <binbin.wu@linux.intel.com> writes:

> On 6/19/2026 8:31 AM, Ackerley Tng via B4 Relay wrote:
>> From: Ackerley Tng <ackerleytng@google.com>
>>
>> Introduce function for KVM to check the private/shared status of guest
>            ^
> Nit:       a
>  > memory at a given GFN.
>>

For v9, I added the "a" in the commit messsage body but retained
"Introduce function" in the header (save characters, and make it sound
like a headline).

>>
>> [...snip...]
>>

^ permalink raw reply

* Re: [RFC v3 2/2] arm64: kprobes: Allow reentering kprobes while single-stepping
From: Hongyan Xia @ 2026-07-17  1:51 UTC (permalink / raw)
  To: Will Deacon, Pu Hu
  Cc: mhiramat@kernel.org, ada.coupriediaz@arm.com,
	catalin.marinas@arm.com, davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <alj2uxE1M8mcMBhA@willie-the-truck>

On 7/16/2026 11:20 PM, Will Deacon wrote:
> On Thu, Jul 16, 2026 at 02:38:58PM +0000, Pu Hu wrote:
>> On 7/16/2026 9:24 PM, Will Deacon wrote:
>>> On Fri, Jul 10, 2026 at 06:32:55AM +0000, Pu Hu wrote:
>>>> 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.
>>>
>>> I don't understand this part. The single-step runs with debug exceptions
>>> disabled (kprobes_save_local_irqflag() sets PSTATE.D) so how do we end
>>> up taking one?
>>
>> You are right that the single-step runs with debug exceptions disabled.
>> However, the case I was referring to is not a hardware breakpoint or a
>> software-step exception, but another Breakpoint Instruction exception
>> generated by executing a BRK instruction. A BRK instruction exception is
>> not masked by PSTATE.D, so it can still be taken while handling a kprobe.
>>
>> As far as I understand the architecture, there are two different cases here:
>>
>>     - Breakpoint Instruction exceptions, generated by executing a BRK
>>       instruction.
>>     - Breakpoint exceptions, generated by the debug logic, for example by
>>       programmed breakpoint registers.
>>
>> PSTATE.D masks debug exceptions such as hardware breakpoints,
>> watchpoints and software-step exceptions, but it does not mask
>> Breakpoint Instruction exceptions generated by BRK. This also seems
>> consistent with the pseudocode for BRK,
>> Arch64.SoftwareBreakpoint(imm16), which does not appear to check
>> PSTATE.D before taking the exception.
>>
>> Therefore, even if kprobes_save_local_irqflag() sets PSTATE.D while
>> handling the first kprobe, if the code executed from that path reaches
>> another instruction patched with BRK, it can still take a Breakpoint
>> Instruction exception. In other words, the nested case I mentioned is
>> another kprobe BRK being hit, not a hardware debug exception or a
>> software-step exception.
> 
> Yes, that's correct, but if we're doing the out-of-line step, how do we
> end up executing a BRK? Or are you saying that it's the kprobes
> BRK64_OPCODE_KPROBES_SS instruction that we use to implement the
> single-step that is the problem? If so, how does taking that exception
> result in us executing tracing or perf code?
> 
> Sorry for all the questions, I just haven't understood what's going on
> here from the commit message.

The key is that, when you use 'perf --call-graph dwarf' to sample 
certain events, kernel perf code will sample a piece of user stack each 
time those events are hit, and copy_to/from_user() triggers page faults. 
Say you are profiling preempt_enable events:

1st BRK -> preempt_disable() -> debug_exception() -> set SS state -> 
preempt_enable() -> triggers perf -> perf_sample() -> sample user stack 
using copy_to/from_user() -> page fault or 2nd BRK on the page fault path.

The key is perf sampling the user stack while the 1st BRK is still 
running. When a page fault is hit, a can of worms is released, including 
a possible 2nd BRK.

Hongyan

^ permalink raw reply

* [PATCH 0/2] tracing: Fix a kernel crash related to :mod: command
From: Masami Hiramatsu (Google) @ 2026-07-17  2:51 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
  Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
	linux-kselftest

Hi,

I found a kernel crash bug in tracing with :mod: command and dynamic
events. If we leaves a dynamic event and run :mod: command to set_event,
the kernel gets an oops.

Actually, this bug found by testing wprobe trigger test case, because
ftracetest has another bug which does not clean up the top-level event
filters/triggers before starting instance tests. Thus if the last test
case in the top-level instance fails and if it leaves a dynamic event,
that will be kept and not cleaned up. Then, test.d/event/event-mod.tc
hits this bug.

Here is the reproduce script.
-----
#!/bin/sh

TRACE_DIR="/sys/kernel/tracing"
if [ ! -d "$TRACE_DIR" ]; then
    TRACE_DIR="/sys/kernel/debug/tracing"
fi

if [ ! -d "$TRACE_DIR" ]; then
    echo "Error: Tracefs not found. Please mount tracefs."
    exit 1
fi

cd "$TRACE_DIR"

echo "=== 1. Clear existing triggers and dynamic events ==="
echo > events/sched/sched_process_fork/trigger || true
echo > dynamic_events || true

echo "=== 2. Create a kprobe event ==="
# Adding a kprobe on vfs_write
echo 'p:kprobes/my_kprobe vfs_write' >> dynamic_events
cat dynamic_events

echo "=== 3. Attach enable_event trigger targeting my_kprobe to sched_process_fork ==="
if [ -f "events/sched/sched_process_fork/trigger" ]; then
    echo 'enable_event:kprobes:my_kprobe' > events/sched/sched_process_fork/trigger
    cat events/sched/sched_process_fork/trigger
else
    echo "Error: sched_process_fork event trigger is not supported."
    exit 1
fi

echo "=== 4. Try to delete the kprobe event while the trigger is active ==="
echo "Attempting: echo -:kprobes/my_kprobe >> dynamic_events"
if ! echo '-:kprobes/my_kprobe' >> dynamic_events 2>/dev/null; then
    echo "Result: Successfully reproduced kprobe event deletion failure (Device or resource busy)!"
    echo "The 'my_kprobe' event remains in dynamic_events:"
    cat dynamic_events
else
    echo "Warning: Deletion succeeded? Check if the trigger was active."
fi

echo ""
echo "=== WARNING: The next step will crash the kernel if the union collision bug is not fixed! ==="
echo "Press Ctrl+C to stop now, or press Enter to trigger the kernel panic (via set_event ':mod:...' write)."
read tmp

echo "Triggering kernel panic..."
echo ':mod:trace-events-sample' > set_event
-----

Thanks,

---

Masami Hiramatsu (Google) (2):
      tracing: Fix union collision of module and refcnt for dynamic events
      selftests/ftrace: Reset triggers at top level before instance loop


 kernel/trace/trace_events.c               |    4 +++-
 tools/testing/selftests/ftrace/ftracetest |    1 +
 2 files changed, 4 insertions(+), 1 deletion(-)

--
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply

* [PATCH 1/2] tracing: Fix union collision of module and refcnt for dynamic events
From: Masami Hiramatsu (Google) @ 2026-07-17  2:51 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
  Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
	linux-kselftest
In-Reply-To: <178425669965.84440.3214667180548169854.stgit@devnote2>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

In 'struct trace_event_call', the 'module' pointer and the 'refcnt'
atomic variable share the same memory space in a union. For dynamic
events, the union member is 'refcnt', which acts as an active
reference counter.

When a dynamic event (such as kprobe, uprobe, fprobe, eprobe, or
wprobe) has a non-zero reference count (e.g. due to active event
triggers or perf attachments), its 'call->module' evaluates to a
small non-zero integer instead of NULL.

When filtering or setting events for a specific module (e.g., writing
':mod:<module>' to 'set_event'), the code in
'__ftrace_set_clr_event_nolock()' and 'update_event_fields()' reads
'call->module' directly without checking whether the event is dynamic.
This causes the kernel to treat the small integer (refcnt) as a
'struct module' pointer, leading to a NULL/invalid pointer dereference
(Oops) when dereferencing the module name.

Fix this by ensuring that the 'TRACE_EVENT_FL_DYNAMIC' flag is checked
before treating 'call->module' as a valid pointer in these code paths.

Fixes: 4c86bc531e60 ("tracing: Add :mod: command to enabled module events")
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 kernel/trace/trace_events.c |    4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index c46e623e7e0d..956692856fa8 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -1350,7 +1350,9 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match,
 		call = file->event_call;
 
 		/* If a module is specified, skip events that are not that module */
-		if (module && (!call->module || strcmp(module_name(call->module), module)))
+		if (module &&
+		    ((call->flags & TRACE_EVENT_FL_DYNAMIC) ||
+		     !call->module || strcmp(module_name(call->module), module)))
 			continue;
 
 		name = trace_event_name(call);


^ permalink raw reply related

* [PATCH 2/2] selftests/ftrace: Reset triggers at top level before instance loop
From: Masami Hiramatsu (Google) @ 2026-07-17  2:51 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Shuah Khan
  Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel,
	linux-kselftest
In-Reply-To: <178425669965.84440.3214667180548169854.stgit@devnote2>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

When running instance tests, 'ftracetest' creates a new ftrace instance
and runs the tests inside it. Before starting each test, it executes
'initialize_system()' to reset the ftrace state to initial-state.

However, since 'initialize_system()' is executed in the context of the
instance directory, it only cleans up triggers and filters of that
instance.
Any triggers or dynamic events left behind in the top-level instance by
previous failed top-level tests, are left completely untouched. These
top-level leftovers can cause subsequent instance-based tests to fail
or even crash the kernel.

Fix this by executing 'initialize_system()' in the top-level tracing
directory once before entering the instance loop.

Fixes: b5b77be812de ("selftests: ftrace: Allow some tests to be run in a tracing instance")
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 tools/testing/selftests/ftrace/ftracetest |    1 +
 1 file changed, 1 insertion(+)

diff --git a/tools/testing/selftests/ftrace/ftracetest b/tools/testing/selftests/ftrace/ftracetest
index 0a56bf209f6c..8ad2c385407e 100755
--- a/tools/testing/selftests/ftrace/ftracetest
+++ b/tools/testing/selftests/ftrace/ftracetest
@@ -503,6 +503,7 @@ for t in $TEST_CASES; do
 done
 
 # Test on instance loop
+(cd $TRACING_DIR; initialize_system)
 INSTANCE=" (instance) "
 for t in $TEST_CASES; do
   test_on_instance $t || continue


^ permalink raw reply related

* [PATCH v1 v1 1/7] ntfs3: add mount and log replay tracepoints
From: Baolin Liu @ 2026-07-17  3:22 UTC (permalink / raw)
  To: almaz.alexandrovich, rostedt, mhiramat, mathieu.desnoyers
  Cc: linux-kernel, ntfs3, linux-trace-kernel, liubaolin12138,
	liubaolin12138, Baolin Liu
In-Reply-To: <20260717032248.3318208-1-liubaolin12138@163.com>

From: Baolin Liu <liubaolin@kylinos.cn>

Add ntfs3 tracepoints for mount and log replay paths.

This adds trace events for ntfs_fill_super(), ntfs_init_from_boot(),
and log_replay() to help observe mount setup, boot sector parsing,
and $LogFile replay results.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 MAINTAINERS                  |  1 +
 fs/ntfs3/fslog.c             |  3 ++
 fs/ntfs3/super.c             | 11 ++++-
 include/trace/events/ntfs3.h | 85 ++++++++++++++++++++++++++++++++++++
 4 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 include/trace/events/ntfs3.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..05d815243cb8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19293,6 +19293,7 @@ W:	http://www.paragon-software.com/
 T:	git https://github.com/Paragon-Software-Group/linux-ntfs3.git
 F:	Documentation/filesystems/ntfs3.rst
 F:	fs/ntfs3/
+F:	include/trace/events/ntfs3.h
 
 NTSYNC SYNCHRONIZATION PRIMITIVE DRIVER
 M:	Elizabeth Figura <zfigura@codeweavers.com>
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index f038c799e7ac..5020da7e72f1 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -13,6 +13,7 @@
 #include "debug.h"
 #include "ntfs.h"
 #include "ntfs_fs.h"
+#include <trace/events/ntfs3.h>
 
 /*
  * LOG FILE structs
@@ -5362,6 +5363,8 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
 	else if (log->set_dirty)
 		ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
 
+	trace_ntfs3_log_replay(&ni->vfs_inode, *initialized, err);
+
 	kfree(log);
 
 	return err;
diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index 3305fe406cb2..7034ef257452 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -76,6 +76,9 @@
 #include "lib/lib.h"
 #endif
 
+#define CREATE_TRACE_POINTS
+#include <trace/events/ntfs3.h>
+
 #ifdef CONFIG_PRINTK
 /*
  * ntfs_printk - Trace warnings/notices/errors.
@@ -956,7 +959,7 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size,
 {
 	struct ntfs_sb_info *sbi = sb->s_fs_info;
 	int err;
-	u32 mb, gb, boot_sector_size, sct_per_clst, record_size;
+	u32 mb, gb, boot_sector_size = 0, sct_per_clst, record_size;
 	u64 sectors, clusters, mlcn, mlcn2, dev_size0;
 	struct NTFS_BOOT *boot;
 	struct buffer_head *bh;
@@ -1216,6 +1219,8 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size,
 	}
 
 out:
+	trace_ntfs3_init_from_boot(sb, sector_size, boot_sector_size,
+				 !!boot_block, err);
 	brelse(bh);
 
 	if (err == -EINVAL && !boot_block && dev_size0 > PAGE_SHIFT) {
@@ -1731,12 +1736,16 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
 	}
 
 	ntfs_create_procdir(sb);
+	trace_ntfs3_fill_super(sb, ro, sbi->cluster_size, sbi->record_size,
+			      sbi->index_size, 0);
 
 	return 0;
 
 put_inode_out:
 	iput(inode);
 out:
+	trace_ntfs3_fill_super(sb, ro, sbi->cluster_size, sbi->record_size,
+			      sbi->index_size, err);
 	/* sbi->options == options */
 	if (options) {
 		put_mount_options(sbi->options);
diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h
new file mode 100644
index 000000000000..d00f66e42ba8
--- /dev/null
+++ b/include/trace/events/ntfs3.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM ntfs3
+
+#if !defined(_TRACE_NTFS3_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_NTFS3_H
+
+#include <linux/fs.h>
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(ntfs3_fill_super,
+	TP_PROTO(struct super_block *sb, bool ro, u32 cluster_size,
+		 u32 record_size, u32 index_size, int err),
+	TP_ARGS(sb, ro, cluster_size, record_size, index_size, err),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(bool, ro)
+		__field(u32, cluster_size)
+		__field(u32, record_size)
+		__field(u32, index_size)
+		__field(int, err)
+	),
+	TP_fast_assign(
+		__entry->dev = sb->s_bdev->bd_dev;
+		__entry->ro = ro;
+		__entry->cluster_size = cluster_size;
+		__entry->record_size = record_size;
+		__entry->index_size = index_size;
+		__entry->err = err;
+	),
+	TP_printk("dev=(%d,%d) ro=%d cluster=%u record=%u index=%u err=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ro,
+		  __entry->cluster_size, __entry->record_size,
+		  __entry->index_size, __entry->err)
+);
+
+TRACE_EVENT(ntfs3_init_from_boot,
+	TP_PROTO(struct super_block *sb, u32 media_sector_size,
+		 u32 boot_sector_size, bool used_alt_boot, int err),
+	TP_ARGS(sb, media_sector_size, boot_sector_size, used_alt_boot, err),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(u32, media_sector_size)
+		__field(u32, boot_sector_size)
+		__field(bool, used_alt_boot)
+		__field(int, err)
+	),
+	TP_fast_assign(
+		__entry->dev = sb->s_bdev->bd_dev;
+		__entry->media_sector_size = media_sector_size;
+		__entry->boot_sector_size = boot_sector_size;
+		__entry->used_alt_boot = used_alt_boot;
+		__entry->err = err;
+	),
+	TP_printk("dev=(%d,%d) media_sector=%u boot_sector=%u alt_boot=%d err=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->media_sector_size, __entry->boot_sector_size,
+		  __entry->used_alt_boot, __entry->err)
+);
+
+TRACE_EVENT(ntfs3_log_replay,
+	TP_PROTO(struct inode *inode, bool initialized, int err),
+	TP_ARGS(inode, initialized, err),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(loff_t, size)
+		__field(bool, initialized)
+		__field(int, err)
+	),
+	TP_fast_assign(
+		__entry->dev = inode->i_sb->s_dev;
+		__entry->ino = inode->i_ino;
+		__entry->size = i_size_read(inode);
+		__entry->initialized = initialized;
+		__entry->err = err;
+	),
+	TP_printk("dev=(%d,%d) ino=%lu size=%lld initialized=%d err=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino,
+		  __entry->size, __entry->initialized, __entry->err)
+);
+
+#endif /* _TRACE_NTFS3_H */
+
+#include <trace/define_trace.h>
-- 
2.51.0


^ permalink raw reply related

* [PATCH v1 0/7] ntfs3: add tracepoints for core filesystem paths
From: Baolin Liu @ 2026-07-17  3:22 UTC (permalink / raw)
  To: almaz.alexandrovich, rostedt, mhiramat, mathieu.desnoyers
  Cc: linux-kernel, ntfs3, linux-trace-kernel, liubaolin12138,
	liubaolin12138, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

This series adds an initial set of tracepoints for ntfs3 core paths.

The tracepoints cover:
  - mount and log replay
  - namei operations
  - directory index operations
  - allocation and block mapping
  - iomap operations
  - file I/O entry points

Baolin Liu (7):
  ntfs3: add mount and log replay tracepoints
  ntfs3: add namei tracepoints
  ntfs3: add create inode tracepoint
  ntfs3: add directory index tracepoints
  ntfs3: add allocation tracepoints
  ntfs3: add iomap tracepoints
  ntfs3: add file I/O tracepoints

 MAINTAINERS                  |   1 +
 fs/ntfs3/attrib.c            |   9 +
 fs/ntfs3/dir.c               |   3 +
 fs/ntfs3/file.c              |   5 +
 fs/ntfs3/fslog.c             |   3 +
 fs/ntfs3/index.c             |   8 +
 fs/ntfs3/inode.c             |   7 +
 fs/ntfs3/namei.c             |   5 +
 fs/ntfs3/super.c             |  11 +-
 include/trace/events/ntfs3.h | 395 +++++++++++++++++++++++++++++++++++
 10 files changed, 446 insertions(+), 1 deletion(-)
 create mode 100644 include/trace/events/ntfs3.h

-- 
2.51.0


^ permalink raw reply

* [PATCH v1 v1 2/7] ntfs3: add namei tracepoints
From: Baolin Liu @ 2026-07-17  3:22 UTC (permalink / raw)
  To: almaz.alexandrovich, rostedt, mhiramat, mathieu.desnoyers
  Cc: linux-kernel, ntfs3, linux-trace-kernel, liubaolin12138,
	liubaolin12138, Baolin Liu
In-Reply-To: <20260717032248.3318208-1-liubaolin12138@163.com>

From: Baolin Liu <liubaolin@kylinos.cn>

Add ntfs3 tracepoints for namei operations.

This adds trace events for ntfs_lookup() and ntfs_rename()
to help observe directory lookup and rename activity.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs3/namei.c             |  5 ++++
 include/trace/events/ntfs3.h | 47 ++++++++++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+)

diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c
index c59de5f2fa97..c1523decee32 100644
--- a/fs/ntfs3/namei.c
+++ b/fs/ntfs3/namei.c
@@ -13,6 +13,7 @@
 #include "debug.h"
 #include "ntfs.h"
 #include "ntfs_fs.h"
+#include <trace/events/ntfs3.h>
 
 /*
  * fill_name_de - Format NTFS_DE in @buf.
@@ -72,6 +73,8 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry,
 	struct inode *inode;
 	int err;
 
+	trace_ntfs3_lookup(dir, dentry);
+
 	if (!uni)
 		inode = ERR_PTR(-ENOMEM);
 	else {
@@ -275,6 +278,8 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir,
 	if (unlikely(ntfs3_forced_shutdown(sb)))
 		return -EIO;
 
+	trace_ntfs3_rename(dir, dentry, new_dir, new_dentry);
+
 	if (flags & ~RENAME_NOREPLACE)
 		return -EINVAL;
 
diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h
index d00f66e42ba8..ba27531d889e 100644
--- a/include/trace/events/ntfs3.h
+++ b/include/trace/events/ntfs3.h
@@ -80,6 +80,53 @@ TRACE_EVENT(ntfs3_log_replay,
 		  __entry->size, __entry->initialized, __entry->err)
 );
 
+TRACE_EVENT(ntfs3_lookup,
+	TP_PROTO(struct inode *dir, struct dentry *dentry),
+	TP_ARGS(dir, dentry),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, parent_ino)
+		__string(name, dentry->d_name.name)
+		__field(unsigned int, name_len)
+	),
+	TP_fast_assign(
+		__entry->dev = dir->i_sb->s_dev;
+		__entry->parent_ino = dir->i_ino;
+		__assign_str(name);
+		__entry->name_len = dentry->d_name.len;
+	),
+	TP_printk("dev=(%d,%d) parent=%lu name=%s len=%u",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->parent_ino, __get_str(name),
+		  __entry->name_len)
+);
+
+TRACE_EVENT(ntfs3_rename,
+	TP_PROTO(struct inode *dir, struct dentry *dentry,
+		 struct inode *new_dir, struct dentry *new_dentry),
+	TP_ARGS(dir, dentry, new_dir, new_dentry),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, dir_ino)
+		__field(unsigned long, new_dir_ino)
+		__field(unsigned long, ino)
+		__string(old_name, dentry->d_name.name)
+		__string(new_name, new_dentry->d_name.name)
+	),
+	TP_fast_assign(
+		__entry->dev = dir->i_sb->s_dev;
+		__entry->dir_ino = dir->i_ino;
+		__entry->new_dir_ino = new_dir->i_ino;
+		__entry->ino = d_inode(dentry)->i_ino;
+		__assign_str(old_name);
+		__assign_str(new_name);
+	),
+	TP_printk("dev=(%d,%d) dir=%lu new_dir=%lu ino=%lu old=%s new=%s",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->dir_ino, __entry->new_dir_ino, __entry->ino,
+		  __get_str(old_name), __get_str(new_name))
+);
+
 #endif /* _TRACE_NTFS3_H */
 
 #include <trace/define_trace.h>
-- 
2.51.0


^ permalink raw reply related

* [PATCH v1 v1 5/7] ntfs3: add allocation tracepoints
From: Baolin Liu @ 2026-07-17  3:22 UTC (permalink / raw)
  To: almaz.alexandrovich, rostedt, mhiramat, mathieu.desnoyers
  Cc: linux-kernel, ntfs3, linux-trace-kernel, liubaolin12138,
	liubaolin12138, Baolin Liu
In-Reply-To: <20260717032248.3318208-1-liubaolin12138@163.com>

From: Baolin Liu <liubaolin@kylinos.cn>

Add ntfs3 tracepoints for allocation operations.

This adds trace events for attr_allocate_clusters(),
attr_set_size_ex(), and attr_data_get_block() to help
observe cluster allocation, size changes, and block
mapping activity.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs3/attrib.c            |  9 +++++
 include/trace/events/ntfs3.h | 78 ++++++++++++++++++++++++++++++++++++
 2 files changed, 87 insertions(+)

diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index c621a4c582f9..a793f17b6bb4 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -13,6 +13,7 @@
 #include "debug.h"
 #include "ntfs.h"
 #include "ntfs_fs.h"
+#include <trace/events/ntfs3.h>
 
 /*
  * You can set external NTFS_MIN_LOG2_OF_CLUMP/NTFS_MAX_LOG2_OF_CLUMP to manage
@@ -167,6 +168,8 @@ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run,
 	CLST flen, vcn0 = vcn, pre = pre_alloc ? *pre_alloc : 0;
 	size_t cnt = run->count;
 
+	trace_ntfs3_attr_allocate_clusters(sbi->sb, vcn, lcn, len, opt);
+
 	for (;;) {
 		err = ntfs_look_for_free_space(sbi, lcn, len + pre, &lcn, &flen,
 					       opt);
@@ -452,6 +455,9 @@ int attr_set_size_ex(struct ntfs_inode *ni, enum ATTR_TYPE type,
 	u32 align;
 	struct MFT_REC *rec;
 
+	trace_ntfs3_attr_set_size_ex(&ni->vfs_inode, le32_to_cpu(type), new_size,
+				    keep_prealloc, no_da);
+
 again:
 	alen = 0;
 	le_b = NULL;
@@ -960,6 +966,9 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
 	if (res)
 		*res = NULL;
 
+	trace_ntfs3_attr_data_get_block(&ni->vfs_inode, vcn, clen,
+					 new != NULL, zero, no_da);
+
 	/* Try to find in cache. */
 	down_read(&ni->file.run_lock);
 	if (run_lookup_entry_da(&ni->file.run, !no_da ? &ni->file.run_da : NULL,
diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h
index f9d7051cd14b..552f991b5257 100644
--- a/include/trace/events/ntfs3.h
+++ b/include/trace/events/ntfs3.h
@@ -229,6 +229,84 @@ TRACE_EVENT(ntfs3_indx_delete_entry,
 		  __entry->ino, __entry->type, __entry->key_len)
 );
 
+TRACE_EVENT(ntfs3_attr_allocate_clusters,
+	TP_PROTO(struct super_block *sb, u64 vcn, u64 lcn, u64 len, u32 opt),
+	TP_ARGS(sb, vcn, lcn, len, opt),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(u64, vcn)
+		__field(u64, lcn)
+		__field(u64, len)
+		__field(u32, opt)
+	),
+	TP_fast_assign(
+		__entry->dev = sb->s_dev;
+		__entry->vcn = vcn;
+		__entry->lcn = lcn;
+		__entry->len = len;
+		__entry->opt = opt;
+	),
+	TP_printk("dev=(%d,%d) vcn=%llu lcn=%llu len=%llu opt=0x%x",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->vcn, __entry->lcn, __entry->len, __entry->opt)
+);
+
+TRACE_EVENT(ntfs3_attr_set_size_ex,
+	TP_PROTO(struct inode *inode, u32 type, u64 new_size, bool keep_prealloc,
+		 bool no_da),
+	TP_ARGS(inode, type, new_size, keep_prealloc, no_da),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u32, type)
+		__field(loff_t, old_size)
+		__field(u64, new_size)
+		__field(bool, keep_prealloc)
+		__field(bool, no_da)
+	),
+	TP_fast_assign(
+		__entry->dev = inode->i_sb->s_dev;
+		__entry->ino = inode->i_ino;
+		__entry->type = type;
+		__entry->old_size = i_size_read(inode);
+		__entry->new_size = new_size;
+		__entry->keep_prealloc = keep_prealloc;
+		__entry->no_da = no_da;
+	),
+	TP_printk("dev=(%d,%d) ino=%lu type=0x%x old_size=%lld new_size=%llu keep_prealloc=%d no_da=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino,
+		  __entry->type, __entry->old_size, __entry->new_size,
+		  __entry->keep_prealloc, __entry->no_da)
+);
+
+TRACE_EVENT(ntfs3_attr_data_get_block,
+	TP_PROTO(struct inode *inode, u64 vcn, u64 clen, bool create,
+		 bool zero, bool no_da),
+	TP_ARGS(inode, vcn, clen, create, zero, no_da),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u64, vcn)
+		__field(u64, clen)
+		__field(bool, create)
+		__field(bool, zero)
+		__field(bool, no_da)
+	),
+	TP_fast_assign(
+		__entry->dev = inode->i_sb->s_dev;
+		__entry->ino = inode->i_ino;
+		__entry->vcn = vcn;
+		__entry->clen = clen;
+		__entry->create = create;
+		__entry->zero = zero;
+		__entry->no_da = no_da;
+	),
+	TP_printk("dev=(%d,%d) ino=%lu vcn=%llu clen=%llu create=%d zero=%d no_da=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino,
+		  __entry->vcn, __entry->clen, __entry->create,
+		  __entry->zero, __entry->no_da)
+);
+
 #endif /* _TRACE_NTFS3_H */
 
 #include <trace/define_trace.h>
-- 
2.51.0


^ permalink raw reply related

* [PATCH v1 v1 6/7] ntfs3: add iomap tracepoints
From: Baolin Liu @ 2026-07-17  3:22 UTC (permalink / raw)
  To: almaz.alexandrovich, rostedt, mhiramat, mathieu.desnoyers
  Cc: linux-kernel, ntfs3, linux-trace-kernel, liubaolin12138,
	liubaolin12138, Baolin Liu
In-Reply-To: <20260717032248.3318208-1-liubaolin12138@163.com>

From: Baolin Liu <liubaolin@kylinos.cn>

Add ntfs3 tracepoints for iomap operations.

This adds trace events for ntfs_iomap_begin() and
ntfs_iomap_end() to help observe iomap mapping activity.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs3/inode.c             |  4 +++
 include/trace/events/ntfs3.h | 50 ++++++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index 2bc4ce2ee8d8..bccb3feb5f3b 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -744,6 +744,8 @@ static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
 	bool new_clst = false;
 	bool no_da;
 	bool zero = false;
+
+	trace_ntfs3_iomap_begin(inode, offset, length, flags);
 	if (unlikely(ntfs3_forced_shutdown(sbi->sb)))
 		return -EIO;
 
@@ -882,6 +884,8 @@ static int ntfs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
 	struct ntfs_inode *ni = ntfs_i(inode);
 	loff_t endbyte = pos + written;
 
+	trace_ntfs3_iomap_end(inode, pos, length, written, flags);
+
 	if ((flags & IOMAP_WRITE) || (flags & IOMAP_ZERO)) {
 		if (iomap->type == IOMAP_INLINE) {
 			u32 data_size;
diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h
index 552f991b5257..a846aa61153c 100644
--- a/include/trace/events/ntfs3.h
+++ b/include/trace/events/ntfs3.h
@@ -307,6 +307,56 @@ TRACE_EVENT(ntfs3_attr_data_get_block,
 		  __entry->zero, __entry->no_da)
 );
 
+TRACE_EVENT(ntfs3_iomap_begin,
+	TP_PROTO(struct inode *inode, loff_t offset, loff_t length,
+		 unsigned int flags),
+	TP_ARGS(inode, offset, length, flags),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(loff_t, offset)
+		__field(loff_t, length)
+		__field(unsigned int, flags)
+	),
+	TP_fast_assign(
+		__entry->dev = inode->i_sb->s_dev;
+		__entry->ino = inode->i_ino;
+		__entry->offset = offset;
+		__entry->length = length;
+		__entry->flags = flags;
+	),
+	TP_printk("dev=(%d,%d) ino=%lu offset=%lld length=%lld flags=0x%x",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->offset, __entry->length,
+		  __entry->flags)
+);
+
+TRACE_EVENT(ntfs3_iomap_end,
+	TP_PROTO(struct inode *inode, loff_t pos, loff_t length,
+		 ssize_t written, unsigned int flags),
+	TP_ARGS(inode, pos, length, written, flags),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(loff_t, pos)
+		__field(loff_t, length)
+		__field(ssize_t, written)
+		__field(unsigned int, flags)
+	),
+	TP_fast_assign(
+		__entry->dev = inode->i_sb->s_dev;
+		__entry->ino = inode->i_ino;
+		__entry->pos = pos;
+		__entry->length = length;
+		__entry->written = written;
+		__entry->flags = flags;
+	),
+	TP_printk("dev=(%d,%d) ino=%lu pos=%lld length=%lld written=%zd flags=0x%x",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->pos, __entry->length,
+		  __entry->written, __entry->flags)
+);
+
 #endif /* _TRACE_NTFS3_H */
 
 #include <trace/define_trace.h>
-- 
2.51.0


^ permalink raw reply related

* [PATCH v1 v1 7/7] ntfs3: add file I/O tracepoints
From: Baolin Liu @ 2026-07-17  3:22 UTC (permalink / raw)
  To: almaz.alexandrovich, rostedt, mhiramat, mathieu.desnoyers
  Cc: linux-kernel, ntfs3, linux-trace-kernel, liubaolin12138,
	liubaolin12138, Baolin Liu
In-Reply-To: <20260717032248.3318208-1-liubaolin12138@163.com>

From: Baolin Liu <liubaolin@kylinos.cn>

Add ntfs3 tracepoints for file I/O operations.

This adds trace events for ntfs_file_read_iter() and
ntfs_file_write_iter() to help observe file read and
write activity.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs3/file.c              |  5 +++++
 include/trace/events/ntfs3.h | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 38 insertions(+)

diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index d601f088618c..a260a57ebd65 100644
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@ -20,6 +20,7 @@
 #include "debug.h"
 #include "ntfs.h"
 #include "ntfs_fs.h"
+#include <trace/events/ntfs3.h>
 
 /*
  * cifx, btrfs, exfat, ext4, f2fs use this constant.
@@ -822,6 +823,8 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
 	unsigned int dio_flags;
 	ssize_t err;
 
+	trace_ntfs3_file_read_iter(iocb, iter);
+
 	err = check_read_restriction(inode);
 	if (err)
 		return err;
@@ -1225,6 +1228,8 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	loff_t vbo, endbyte;
 	ssize_t ret, err;
 
+	trace_ntfs3_file_write_iter(iocb, from);
+
 	if (!inode_trylock(inode)) {
 		if (iocb->ki_flags & IOCB_NOWAIT)
 			return -EAGAIN;
diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h
index a846aa61153c..595314c09e43 100644
--- a/include/trace/events/ntfs3.h
+++ b/include/trace/events/ntfs3.h
@@ -357,6 +357,39 @@ TRACE_EVENT(ntfs3_iomap_end,
 		  __entry->written, __entry->flags)
 );
 
+DECLARE_EVENT_CLASS(ntfs3_file_class,
+	TP_PROTO(struct kiocb *iocb, struct iov_iter *iter),
+	TP_ARGS(iocb, iter),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(loff_t, size)
+		__field(loff_t, offset)
+		__field(size_t, count)
+	),
+	TP_fast_assign(
+		__entry->dev = file_inode(iocb->ki_filp)->i_sb->s_dev;
+		__entry->ino = file_inode(iocb->ki_filp)->i_ino;
+		__entry->size = i_size_read(file_inode(iocb->ki_filp));
+		__entry->offset = iocb->ki_pos;
+		__entry->count = iov_iter_count(iter);
+	),
+	TP_printk("dev=(%d,%d) ino=%lu size=%lld pos=%lld bytecount=%zu",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->size, __entry->offset,
+		  __entry->count)
+);
+
+DEFINE_EVENT(ntfs3_file_class, ntfs3_file_read_iter,
+	TP_PROTO(struct kiocb *iocb, struct iov_iter *iter),
+	TP_ARGS(iocb, iter)
+);
+
+DEFINE_EVENT(ntfs3_file_class, ntfs3_file_write_iter,
+	TP_PROTO(struct kiocb *iocb, struct iov_iter *iter),
+	TP_ARGS(iocb, iter)
+);
+
 #endif /* _TRACE_NTFS3_H */
 
 #include <trace/define_trace.h>
-- 
2.51.0


^ permalink raw reply related

* [PATCH v1 v1 3/7] ntfs3: add create inode tracepoint
From: Baolin Liu @ 2026-07-17  3:22 UTC (permalink / raw)
  To: almaz.alexandrovich, rostedt, mhiramat, mathieu.desnoyers
  Cc: linux-kernel, ntfs3, linux-trace-kernel, liubaolin12138,
	liubaolin12138, Baolin Liu
In-Reply-To: <20260717032248.3318208-1-liubaolin12138@163.com>

From: Baolin Liu <liubaolin@kylinos.cn>

Add an ntfs3 tracepoint for inode creation.

This adds a trace event for ntfs_create_inode() to help
observe inode creation activity.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs3/inode.c             |  3 +++
 include/trace/events/ntfs3.h | 21 +++++++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index c43101cc064d..2bc4ce2ee8d8 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -17,6 +17,7 @@
 #include "debug.h"
 #include "ntfs.h"
 #include "ntfs_fs.h"
+#include <trace/events/ntfs3.h>
 
 /*
  * ntfs_read_mft - Read record and parse MFT.
@@ -1206,6 +1207,8 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
 	/* New file will be resident or non resident. */
 	const bool new_file_resident = 1;
 
+	trace_ntfs3_create_inode(dir, dentry);
+
 	if (!fnd)
 		ni_lock_dir(dir_ni);
 
diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h
index ba27531d889e..cc9f4fee9ff8 100644
--- a/include/trace/events/ntfs3.h
+++ b/include/trace/events/ntfs3.h
@@ -127,6 +127,27 @@ TRACE_EVENT(ntfs3_rename,
 		  __get_str(old_name), __get_str(new_name))
 );
 
+TRACE_EVENT(ntfs3_create_inode,
+	TP_PROTO(struct inode *dir, struct dentry *dentry),
+	TP_ARGS(dir, dentry),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, parent_ino)
+		__string(name, dentry->d_name.name)
+		__field(unsigned int, name_len)
+	),
+	TP_fast_assign(
+		__entry->dev = dir->i_sb->s_dev;
+		__entry->parent_ino = dir->i_ino;
+		__assign_str(name);
+		__entry->name_len = dentry->d_name.len;
+	),
+	TP_printk("dev=(%d,%d) parent=%lu name=%s len=%u",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->parent_ino, __get_str(name),
+		  __entry->name_len)
+);
+
 #endif /* _TRACE_NTFS3_H */
 
 #include <trace/define_trace.h>
-- 
2.51.0


^ permalink raw reply related

* [PATCH v1 v1 4/7] ntfs3: add directory index tracepoints
From: Baolin Liu @ 2026-07-17  3:22 UTC (permalink / raw)
  To: almaz.alexandrovich, rostedt, mhiramat, mathieu.desnoyers
  Cc: linux-kernel, ntfs3, linux-trace-kernel, liubaolin12138,
	liubaolin12138, Baolin Liu
In-Reply-To: <20260717032248.3318208-1-liubaolin12138@163.com>

From: Baolin Liu <liubaolin@kylinos.cn>

Add ntfs3 tracepoints for directory index operations.

This adds trace events for dir_search_u(), indx_find(),
indx_insert_entry(), and indx_delete_entry() to help observe
directory index lookup, insert, and delete activity.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
 fs/ntfs3/dir.c               |  3 ++
 fs/ntfs3/index.c             |  8 ++++
 include/trace/events/ntfs3.h | 81 ++++++++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+)

diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
index 873d52233003..eb10e7ff45bc 100644
--- a/fs/ntfs3/dir.c
+++ b/fs/ntfs3/dir.c
@@ -14,6 +14,7 @@
 #include "debug.h"
 #include "ntfs.h"
 #include "ntfs_fs.h"
+#include <trace/events/ntfs3.h>
 
 /* Convert little endian UTF-16 to NLS string. */
 int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, const __le16 *name, u32 len,
@@ -243,6 +244,8 @@ struct inode *dir_search_u(struct inode *dir, const struct cpu_str *uni,
 	struct inode *inode = NULL;
 	struct ntfs_fnd *fnd_a = NULL;
 
+	trace_ntfs3_dir_search_u(dir, uni ? uni->len : 0);
+
 	if (!fnd) {
 		fnd_a = fnd_get();
 		if (!fnd_a) {
diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c
index 2b439ac04356..d4ca7b1bb524 100644
--- a/fs/ntfs3/index.c
+++ b/fs/ntfs3/index.c
@@ -13,6 +13,7 @@
 #include "debug.h"
 #include "ntfs.h"
 #include "ntfs_fs.h"
+#include <trace/events/ntfs3.h>
 
 static const struct INDEX_NAMES {
 	const __le16 *name;
@@ -1179,6 +1180,8 @@ int indx_find(struct ntfs_index *indx, struct ntfs_inode *ni,
 	if (!root)
 		root = indx_get_root(&ni->dir, ni, NULL, NULL);
 
+	trace_ntfs3_indx_find(&ni->vfs_inode, indx->type, key_len);
+
 	if (!root) {
 		/* Should not happen. */
 		return -EINVAL;
@@ -2051,6 +2054,9 @@ int indx_insert_entry(struct ntfs_index *indx, struct ntfs_inode *ni,
 		return -EINVAL;
 	}
 
+	trace_ntfs3_indx_insert_entry(&ni->vfs_inode, indx->type,
+				      le16_to_cpu(new_de->key_size), undo);
+
 	if (fnd_is_empty(fnd)) {
 		/*
 		 * Find the spot the tree where we want to
@@ -2409,6 +2415,8 @@ int indx_delete_entry(struct ntfs_index *indx, struct ntfs_inode *ni,
 		return -EINVAL;
 	}
 
+	trace_ntfs3_indx_delete_entry(&ni->vfs_inode, indx->type, key_len);
+
 	/* Locate the entry to remove. */
 	err = indx_find(indx, ni, root, key, key_len, ctx, &diff, &e, fnd);
 	if (err)
diff --git a/include/trace/events/ntfs3.h b/include/trace/events/ntfs3.h
index cc9f4fee9ff8..f9d7051cd14b 100644
--- a/include/trace/events/ntfs3.h
+++ b/include/trace/events/ntfs3.h
@@ -148,6 +148,87 @@ TRACE_EVENT(ntfs3_create_inode,
 		  __entry->name_len)
 );
 
+TRACE_EVENT(ntfs3_dir_search_u,
+	TP_PROTO(struct inode *dir, unsigned int name_len),
+	TP_ARGS(dir, name_len),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, dir_ino)
+		__field(unsigned int, name_len)
+	),
+	TP_fast_assign(
+		__entry->dev = dir->i_sb->s_dev;
+		__entry->dir_ino = dir->i_ino;
+		__entry->name_len = name_len;
+	),
+	TP_printk("dev=(%d,%d) dir=%lu name_len=%u",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->dir_ino, __entry->name_len)
+);
+
+TRACE_EVENT(ntfs3_indx_find,
+	TP_PROTO(struct inode *inode, u8 type, size_t key_len),
+	TP_ARGS(inode, type, key_len),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u8, type)
+		__field(size_t, key_len)
+	),
+	TP_fast_assign(
+		__entry->dev = inode->i_sb->s_dev;
+		__entry->ino = inode->i_ino;
+		__entry->type = type;
+		__entry->key_len = key_len;
+	),
+	TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%zu",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->type, __entry->key_len)
+);
+
+TRACE_EVENT(ntfs3_indx_insert_entry,
+	TP_PROTO(struct inode *inode, u8 type, u16 key_len, bool undo),
+	TP_ARGS(inode, type, key_len, undo),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u8, type)
+		__field(u16, key_len)
+		__field(bool, undo)
+	),
+	TP_fast_assign(
+		__entry->dev = inode->i_sb->s_dev;
+		__entry->ino = inode->i_ino;
+		__entry->type = type;
+		__entry->key_len = key_len;
+		__entry->undo = undo;
+	),
+	TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%u undo=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->type, __entry->key_len,
+		  __entry->undo)
+);
+
+TRACE_EVENT(ntfs3_indx_delete_entry,
+	TP_PROTO(struct inode *inode, u8 type, u32 key_len),
+	TP_ARGS(inode, type, key_len),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u8, type)
+		__field(u32, key_len)
+	),
+	TP_fast_assign(
+		__entry->dev = inode->i_sb->s_dev;
+		__entry->ino = inode->i_ino;
+		__entry->type = type;
+		__entry->key_len = key_len;
+	),
+	TP_printk("dev=(%d,%d) ino=%lu type=%u key_len=%u",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->type, __entry->key_len)
+);
+
 #endif /* _TRACE_NTFS3_H */
 
 #include <trace/define_trace.h>
-- 
2.51.0


^ permalink raw reply related

* [PATCH] uprobes: Restore original return address in uretprobe context
From: daichengrong @ 2026-07-17  8:23 UTC (permalink / raw)
  To: Paul Walmsley, Palmer Dabbelt, Albert Ou
  Cc: linux-riscv, linux-kernel, linux-trace-kernel, daichengrong

uretprobe replaces the original return address of a probed function with
a trampoline address to capture function return events.

After the trampoline is entered and the uretprobe handler completes, the
original return address needs to be restored in the user register context
to keep the register state consistent with the state before probing.

Add an architecture-specific hook for restoring the original return
address during uretprobe handling.

The initial implementation adds support for RISC-V. Other architectures
keep the default empty implementation until their corresponding restore
logic is implemented.

Signed-off-by: daichengrong <daichengrong@iscas.ac.cn>
---
 arch/riscv/kernel/probes/uprobes.c | 7 +++++++
 include/linux/uprobes.h            | 1 +
 kernel/events/uprobes.c            | 5 +++++
 3 files changed, 13 insertions(+)

diff --git a/arch/riscv/kernel/probes/uprobes.c b/arch/riscv/kernel/probes/uprobes.c
index eb177d0ce8ab..0b1b94d9683e 100644
--- a/arch/riscv/kernel/probes/uprobes.c
+++ b/arch/riscv/kernel/probes/uprobes.c
@@ -139,6 +139,13 @@ arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr,
 	return ra;
 }
 
+void
+arch_uretprobe_hijack_set_addr(unsigned long orig_ret_vaddr,
+				  struct pt_regs *regs)
+{
+	regs->ra = orig_ret_vaddr;
+}
+
 int arch_uprobe_exception_notify(struct notifier_block *self,
 				 unsigned long val, void *data)
 {
diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h
index f548fea2adec..2d0dc919845d 100644
--- a/include/linux/uprobes.h
+++ b/include/linux/uprobes.h
@@ -230,6 +230,7 @@ extern bool arch_uprobe_xol_was_trapped(struct task_struct *tsk);
 extern int  arch_uprobe_exception_notify(struct notifier_block *self, unsigned long val, void *data);
 extern void arch_uprobe_abort_xol(struct arch_uprobe *aup, struct pt_regs *regs);
 extern unsigned long arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr, struct pt_regs *regs);
+extern void arch_uretprobe_hijack_set_addr(unsigned long orig_ret_vaddr, struct pt_regs *regs);
 extern bool arch_uretprobe_is_alive(struct return_instance *ret, enum rp_check ctx, struct pt_regs *regs);
 extern bool arch_uprobe_ignore(struct arch_uprobe *aup, struct pt_regs *regs);
 extern void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index 4084e926e284..deecaae01ab7 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1748,6 +1748,10 @@ void * __weak arch_uretprobe_trampoline(unsigned long *psize)
 	return &insn;
 }
 
+void __weak arch_uretprobe_hijack_set_addr(unsigned long orig_ret_vaddr, struct pt_regs *regs)
+{
+}
+
 static struct xol_area *__create_xol_area(unsigned long vaddr)
 {
 	struct mm_struct *mm = current->mm;
@@ -2659,6 +2663,7 @@ void uprobe_handle_trampoline(struct pt_regs *regs)
 		valid = !next_chain || arch_uretprobe_is_alive(next_chain, RP_CHECK_RET, regs);
 
 		instruction_pointer_set(regs, ri->orig_ret_vaddr);
+		arch_uretprobe_hijack_set_addr(ri->orig_ret_vaddr, regs);
 		do {
 			/* pop current instance from the stack of pending return instances,
 			 * as it's not pending anymore: we just fixed up original
-- 
2.25.1


^ permalink raw reply related

* Re: [RFC v3 2/2] arm64: kprobes: Allow reentering kprobes while single-stepping
From: Will Deacon @ 2026-07-17 11:01 UTC (permalink / raw)
  To: Hongyan Xia
  Cc: Pu Hu, mhiramat@kernel.org, ada.coupriediaz@arm.com,
	catalin.marinas@arm.com, davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <e0e800f5-a6f8-48ae-b55c-5c3d928bf234@transsion.com>

On Fri, Jul 17, 2026 at 01:51:12AM +0000, Hongyan Xia wrote:
> On 7/16/2026 11:20 PM, Will Deacon wrote:
> > On Thu, Jul 16, 2026 at 02:38:58PM +0000, Pu Hu wrote:
> >> On 7/16/2026 9:24 PM, Will Deacon wrote:
> >>> On Fri, Jul 10, 2026 at 06:32:55AM +0000, Pu Hu wrote:
> >>>> 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.
> >>>
> >>> I don't understand this part. The single-step runs with debug exceptions
> >>> disabled (kprobes_save_local_irqflag() sets PSTATE.D) so how do we end
> >>> up taking one?
> >>
> >> You are right that the single-step runs with debug exceptions disabled.
> >> However, the case I was referring to is not a hardware breakpoint or a
> >> software-step exception, but another Breakpoint Instruction exception
> >> generated by executing a BRK instruction. A BRK instruction exception is
> >> not masked by PSTATE.D, so it can still be taken while handling a kprobe.
> >>
> >> As far as I understand the architecture, there are two different cases here:
> >>
> >>     - Breakpoint Instruction exceptions, generated by executing a BRK
> >>       instruction.
> >>     - Breakpoint exceptions, generated by the debug logic, for example by
> >>       programmed breakpoint registers.
> >>
> >> PSTATE.D masks debug exceptions such as hardware breakpoints,
> >> watchpoints and software-step exceptions, but it does not mask
> >> Breakpoint Instruction exceptions generated by BRK. This also seems
> >> consistent with the pseudocode for BRK,
> >> Arch64.SoftwareBreakpoint(imm16), which does not appear to check
> >> PSTATE.D before taking the exception.
> >>
> >> Therefore, even if kprobes_save_local_irqflag() sets PSTATE.D while
> >> handling the first kprobe, if the code executed from that path reaches
> >> another instruction patched with BRK, it can still take a Breakpoint
> >> Instruction exception. In other words, the nested case I mentioned is
> >> another kprobe BRK being hit, not a hardware debug exception or a
> >> software-step exception.
> > 
> > Yes, that's correct, but if we're doing the out-of-line step, how do we
> > end up executing a BRK? Or are you saying that it's the kprobes
> > BRK64_OPCODE_KPROBES_SS instruction that we use to implement the
> > single-step that is the problem? If so, how does taking that exception
> > result in us executing tracing or perf code?
> > 
> > Sorry for all the questions, I just haven't understood what's going on
> > here from the commit message.
> 
> The key is that, when you use 'perf --call-graph dwarf' to sample 
> certain events, kernel perf code will sample a piece of user stack each 
> time those events are hit, and copy_to/from_user() triggers page faults. 
> Say you are profiling preempt_enable events:
> 
> 1st BRK -> preempt_disable() -> debug_exception() -> set SS state -> 
> preempt_enable() -> triggers perf -> perf_sample() -> sample user stack 
> using copy_to/from_user() -> page fault or 2nd BRK on the page fault path.
> 
> The key is perf sampling the user stack while the 1st BRK is still 
> running. When a page fault is hit, a can of worms is released, including 
> a possible 2nd BRK.

Thanks. So perf is run synchronously from the debug exception entry path,
rather than because of a second exception taking place. Got it. But then
it sounds like we should really make the debug exception handling path (at
least, the part that runs for handling the kprobe step) noinstr to avoid
getting into this state to begin with. Is that practical?

Will

^ permalink raw reply

* Re: [RFC v3 2/2] arm64: kprobes: Allow reentering kprobes while single-stepping
From: Hongyan Xia @ 2026-07-17 11:31 UTC (permalink / raw)
  To: Will Deacon
  Cc: Pu Hu, mhiramat@kernel.org, ada.coupriediaz@arm.com,
	catalin.marinas@arm.com, davem@davemloft.net, Jiazi Li,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	naveen@kernel.org, yang@os.amperecomputing.com
In-Reply-To: <aloLb0j8sg0usmAF@willie-the-truck>

On 7/17/2026 7:01 PM, Will Deacon wrote:
> On Fri, Jul 17, 2026 at 01:51:12AM +0000, Hongyan Xia wrote:
>> On 7/16/2026 11:20 PM, Will Deacon wrote:
>>> On Thu, Jul 16, 2026 at 02:38:58PM +0000, Pu Hu wrote:
>>>> On 7/16/2026 9:24 PM, Will Deacon wrote:
>>>>> On Fri, Jul 10, 2026 at 06:32:55AM +0000, Pu Hu wrote:
>>>>>> 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.
>>>>>
>>>>> I don't understand this part. The single-step runs with debug exceptions
>>>>> disabled (kprobes_save_local_irqflag() sets PSTATE.D) so how do we end
>>>>> up taking one?
>>>>
>>>> You are right that the single-step runs with debug exceptions disabled.
>>>> However, the case I was referring to is not a hardware breakpoint or a
>>>> software-step exception, but another Breakpoint Instruction exception
>>>> generated by executing a BRK instruction. A BRK instruction exception is
>>>> not masked by PSTATE.D, so it can still be taken while handling a kprobe.
>>>>
>>>> As far as I understand the architecture, there are two different cases here:
>>>>
>>>>      - Breakpoint Instruction exceptions, generated by executing a BRK
>>>>        instruction.
>>>>      - Breakpoint exceptions, generated by the debug logic, for example by
>>>>        programmed breakpoint registers.
>>>>
>>>> PSTATE.D masks debug exceptions such as hardware breakpoints,
>>>> watchpoints and software-step exceptions, but it does not mask
>>>> Breakpoint Instruction exceptions generated by BRK. This also seems
>>>> consistent with the pseudocode for BRK,
>>>> Arch64.SoftwareBreakpoint(imm16), which does not appear to check
>>>> PSTATE.D before taking the exception.
>>>>
>>>> Therefore, even if kprobes_save_local_irqflag() sets PSTATE.D while
>>>> handling the first kprobe, if the code executed from that path reaches
>>>> another instruction patched with BRK, it can still take a Breakpoint
>>>> Instruction exception. In other words, the nested case I mentioned is
>>>> another kprobe BRK being hit, not a hardware debug exception or a
>>>> software-step exception.
>>>
>>> Yes, that's correct, but if we're doing the out-of-line step, how do we
>>> end up executing a BRK? Or are you saying that it's the kprobes
>>> BRK64_OPCODE_KPROBES_SS instruction that we use to implement the
>>> single-step that is the problem? If so, how does taking that exception
>>> result in us executing tracing or perf code?
>>>
>>> Sorry for all the questions, I just haven't understood what's going on
>>> here from the commit message.
>>
>> The key is that, when you use 'perf --call-graph dwarf' to sample
>> certain events, kernel perf code will sample a piece of user stack each
>> time those events are hit, and copy_to/from_user() triggers page faults.
>> Say you are profiling preempt_enable events:
>>
>> 1st BRK -> preempt_disable() -> debug_exception() -> set SS state ->
>> preempt_enable() -> triggers perf -> perf_sample() -> sample user stack
>> using copy_to/from_user() -> page fault or 2nd BRK on the page fault path.
>>
>> The key is perf sampling the user stack while the 1st BRK is still
>> running. When a page fault is hit, a can of worms is released, including
>> a possible 2nd BRK.
> 
> Thanks. So perf is run synchronously from the debug exception entry path,

Yes, exactly.

> rather than because of a second exception taking place. Got it. But then
> it sounds like we should really make the debug exception handling path (at
> least, the part that runs for handling the kprobe step) noinstr to avoid
> getting into this state to begin with. Is that practical?

Not sure about making the whole path noinstr (@Masami might have a 
better opinion on this than me). Personally I don't mind either 
disallowing it or making it correct.

But it might be a good idea not to diverge too much between ISAs. This 
patch is pretty much mirroring what the x86 side handles this situation.

> 
> Will

^ permalink raw reply

* [RFC PATCH v2 00/13] mm/kwatch: dynamic hardware watchpoints for hunting memory corruption
From: Jinchao Wang @ 2026-07-17 12:50 UTC (permalink / raw)
  To: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Steven Rostedt,
	Masami Hiramatsu
  Cc: Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
	Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
	Matthew Wilcox, Alan Stern, Randy Dunlap, Alexander Potapenko,
	Marco Elver, Mike Rapoport, linux-kernel, linux-mm,
	linux-trace-kernel, linux-perf-users, linux-doc, Jinchao Wang

Motivation
==========

The hardest memory corruption bugs are the silent ones: a rogue writer
scribbles over a live object through a stale pointer or a race, and
the victim crashes in a code path far away from the culprit. Any
single developer hits such a bug rarely, but across the kernel's code
base and install base they keep arriving, and each one is
disproportionately expensive to localize. The question to answer is
"who wrote to this object, and from where?", and it is hard to get at
with the existing tools:

 - The kernel's own reports - an oops on a clobbered pointer, a
   BUG_ON, a list-corruption warning - fire at the victim's access,
   not at the corrupting write.
 - KASAN/KFENCE catch memory-safety violations: out-of-bounds
   accesses and use-after-free. But they have a blind spot: a
   corrupting write can be fully memory-safe - a *valid* pointer, in
   bounds, to a live object, written just at the wrong time or to the
   wrong place - and then they stay silent by design. And even for
   the bugs they can catch, KASAN's rebuild, overhead and redzones
   change timing and layout enough that racy corruption often no
   longer reproduces.
 - Hardware watchpoints can catch the writer, but they are scarce
   (four slots per CPU on x86), they watch a fixed address, and
   registering/releasing one may sleep, so they cannot be managed
   from atomic context. Each way of using them also has practical
   constraints: the in-kernel API means writing a custom debugging
   patch for each hunt, kgdb needs a debug console and stops the
   whole machine at every interaction, and perf drives watchpoints
   from userspace, so the address must be known before the run.

Design
======

KWatch arms a hardware breakpoint on the exact address a function
invocation is operating on, for exactly as long as that invocation
runs. Four key designs make this work:

1. Hardware breakpoint pool. All breakpoints are preallocated and
   parked on a harmless dummy variable. Arming re-points one at the
   target address, using a small "reinstall" operation added to the
   hw_breakpoint layer; releasing parks it back on the dummy. The
   pool is managed locklessly and the arming path calls no sleeping
   API, so a breakpoint can be armed from whatever context the
   watched function runs in - real NMI excepted - and a hit can
   fire and be handled in any context.

2. Function-scoped watch window. A kretprobe pair opens the window
   at function entry - resolving the target address and arming a
   breakpoint - and closes it on return, releasing the breakpoint.
   A depth setting picks which level of a recursion opens the
   window, and hits are validated against the arming task and
   depth. The window is also what makes the scarce hardware
   affordable: every corruption happens within some execution
   context, so a breakpoint is armed only while that context runs.
   Global variables can also be watched without a window, in a
   time-bounded anchor session.

3. Watch expression engine. At each entry it evaluates the
   configured expression to resolve that invocation's target
   address. The base can be a function argument, the stack pointer,
   a symbol or an absolute address; offsets and pointer
   dereferences chain on top, so heap fields reachable from an
   argument, globals and stack slots are all expressible.

4. Painless deployment. KWatch is fully self-contained and can be
   built as a module, loaded only when a corruption hunt needs it.
   It is just a debugfs entry until a watch is configured; after
   that only the watched function pays the kprobe cost and the
   rest of the system runs at full speed, which keeps KWatch usable
   on busy, highly concurrent systems.

Together: point KWatch at the suspect function and field with a
single debugfs line, reproduce the bug, and the tracepoint reports
the writer - the writing instruction and its stack trace.

A real case: dummy_hcd
======================

Gadget requests were completing through a clobbered req->complete.
Months of KASAN-enabled syzkaller runs produced only downstream
symptoms, with no lead on the root cause. Watching the victim field
with KWatch:

  func_name=usb_gadget_giveback_request watch_expr=arg2+56 \
  watch_len=8

caught the writer in the act:

  kwatch_hit: KWatch HIT: time=370.399836 ip=memcpy+0xc/0x30
              addr=0xffff888109cf5218
   => usb_ep_queue+0xf1/0x3c0
   => raw_process_ep_io+0x5e4/0xd80
   => raw_ioctl+0x251c/0x41c0
   => __se_sys_ioctl+0xfc/0x170
   => do_syscall_64+0x174/0x580
   => entry_SYSCALL_64_after_hwframe+0x77/0x7f

on the same request that crashed an instant later - the crash RIP was
the just-written garbage value. Root cause: dummy_queue()'s single
shared fifo_req is struct-copied over while dummy_timer() is
mid-giveback. A fix based on this diagnosis has been picked up
into the USB tree [1] - KWatch's part was answering who clobbers
the pointer, and from where.

Series layout
=============

Patches 1-4: a minimal "reinstall" operation for hw_breakpoint.
Re-pointing an already-installed breakpoint from a kprobe handler is
not possible with the current API (register/unregister may sleep and
rebalances constraints); reinstall lets the arch rewrite a slot it
already owns, and modify_wide_hw_breakpoint_local() exposes that for
the local CPU - cross-CPU propagation is the caller's job (KWatch
uses async IPIs). Patch 4 is Masami Hiramatsu's work, carried
verbatim from his current wprobe series.

Patches 5-11: KWatch itself, in mm/kwatch/ (patch 7 exports
stack_trace_save_regs() for the modular build).

Patches 12-13: KUnit tests and documentation.

Testing
=======

The dummy_hcd hunt above exercised the function-window path against
a live reproducer. Global watching, session auto-stop and the KUnit
parser suite were verified end to end under QEMU on x86_64. Both
KWATCH=y and KWATCH=m build.

arm64
=====

This RFC deliberately targets x86 only. On arm64 the watchpoint
exception fires before the access, so the arch must single-step over
hits, and today it only does that for the default overflow handler.
Rather than hardcoding a KWatch hook into arm64 core code, I plan a
follow-up that adds a generic way for in-kernel breakpoint consumers
to request stepping, and arm64 support on top of it (a prototype
exists).

Changes in v2
=============

Addressing Steven Rostedt's review and the Sashiko AI review findings
on v1 [2]:

Tracepoint (Steven Rostedt's review):
 - u64 field first, count last, and the stack trace is a dynamic
   array sized to the captured depth (also fixes leaking the
   uninitialized tail of the fixed array).
 - div_u64 in TP_printk for 32-bit builds.

Scope:
 - Drop access_type: KWatch now always watches for writes. It is a
   corruption localizer, and write is the one type that matters for
   that job.

hw_breakpoint prerequisites (patches 2-4):
 - Restore the compiler barrier before the cpu_dr7 update on the
   disable path, lost in the install/uninstall unification.
 - Always push the AMD DR address mask, so a reinstall from a masked
   range breakpoint to an exact one clears the stale mask.
 - Patch 4 updated to Masami's latest version (wprobe v8): parse into
   a temporary arch_hw_breakpoint to keep error paths side-effect
   free, sync the logical bp->attr fields, -EOPNOTSUPP over -ENOSYS.

Runtime fixes:
 - Rate-limit only the cross-CPU arm broadcast, never the local
   re-point; suppressed broadcasts are counted and reported as
   arm_ipi_suppressed (was: a rate-limited arm skipped the local CPU
   too, missing the current window entirely).
 - The hit handler reports the per-CPU breakpoint address instead of
   the shared attribute another CPU may be re-pointing concurrently.
 - Release the context slot claimed by an entry that races the
   register/epoch-publish window (slot leak).
 - Clamp the context pool size to [256, 32768]: a u16 request above
   32768 wrapped roundup_pow_of_two() to zero.
 - nmi_rejected and arm_ipi_suppressed are session-scoped now.
 - The anchor thread sleeps in TASK_IDLE so a long session no longer
   inflates loadavg.
 - The debugfs read path takes the control mutex against concurrent
   writes and auto-stop.

Kconfig / tests / docs:
 - depends on KPROBES/KRETPROBES/STACKTRACE instead of select.
 - KWATCH_KUNIT_TEST now explicitly depends on KWATCH=y; the parser
   test gets a width-appropriate literal for 32-bit.
 - Document that argN bases are only meaningful at function entry,
   the IPI rate-limit consequences, and best-effort cleanup when a
   task dies abnormally inside the watched function.

Relationship to KStackWatch
===========================

KWatch grew out of KStackWatch [3], an earlier tool aimed at stack
corruption only, and has been substantially reworked since. The
hw_breakpoint prerequisites are carried over from that series.

Major changes since the KStackWatch v8 posting:

 - The watch expression engine widens the watchable range from the
   stack to any address expressible via function arguments, globals
   or stack addresses plus pointer dereference chains.
 - The task_struct and scheduler hooks are gone; KWatch is now fully
   self-contained, as described above.
 - A time-bounded anchor session was added for watching global
   variables (duration=N, auto-stop on expiry).
 - Hits are reported through a tracepoint carrying a stack trace
   instead of printk: safe in NMI-like contexts, and recoverable
   after a crash (ftrace_dump_on_oops, kdump, pstore).
 - Invocations in real NMI(-like) context are detected and rejected,
   with a visible nmi_rejected counter.
 - arm64 support and the auto-canary, profiling and test-module
   extras were dropped from this series to keep it reviewable.

Feedback on the design, the implementation or the usage is welcome;
if you are staring at a corruption that the existing tools cannot
attribute, give KWatch a try, or simply Cc me - I am glad to help.

[1] https://lore.kernel.org/all/20260714064829.172098-1-wangjinchao600@gmail.com/
[2] https://lore.kernel.org/all/20260714182243.10687-1-wangjinchao600@gmail.com/
[3] https://lore.kernel.org/all/20251110163634.3686676-1-wangjinchao600@gmail.com/

Jinchao Wang (12):
  arch: add HAVE_REINSTALL_HW_BREAKPOINT
  x86/hw_breakpoint: Unify breakpoint install/uninstall
  x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint
  mm/kwatch: add watch expression parser and dereference engine
  mm/kwatch: add lockless per-task context pool
  stacktrace: export stack_trace_save_regs()
  mm/kwatch: add hardware breakpoint backend
  mm/kwatch: add probe lifecycle runtime
  mm/kwatch: add anchor thread for global watchpoints
  mm/kwatch: add debugfs control plane
  mm/kwatch: add KUnit tests for the watch expression parser
  Documentation/dev-tools: document KWatch

Masami Hiramatsu (Google) (1):
  HWBP: Add modify_wide_hw_breakpoint_local() API

 Documentation/dev-tools/index.rst    |   1 +
 Documentation/dev-tools/kwatch.rst   | 207 ++++++++++++++
 MAINTAINERS                          |   8 +
 arch/Kconfig                         |  10 +
 arch/x86/Kconfig                     |   1 +
 arch/x86/include/asm/hw_breakpoint.h |   8 +
 arch/x86/kernel/hw_breakpoint.c      | 163 ++++++-----
 include/linux/hw_breakpoint.h        |   6 +
 include/trace/events/kwatch.h        |  68 +++++
 kernel/events/hw_breakpoint.c        |  43 +++
 kernel/stacktrace.c                  |   2 +
 mm/Kconfig                           |   1 +
 mm/Makefile                          |   1 +
 mm/kwatch/.kunitconfig               |   9 +
 mm/kwatch/Kconfig                    |  28 ++
 mm/kwatch/Makefile                   |   4 +
 mm/kwatch/anchor.c                   |  85 ++++++
 mm/kwatch/core.c                     | 324 ++++++++++++++++++++++
 mm/kwatch/deref.c                    | 174 ++++++++++++
 mm/kwatch/deref_test.c               | 146 ++++++++++
 mm/kwatch/hwbp.c                     | 388 +++++++++++++++++++++++++++
 mm/kwatch/kwatch.h                   | 101 +++++++
 mm/kwatch/probe.c                    | 275 +++++++++++++++++++
 mm/kwatch/task_ctx.c                 | 125 +++++++++
 24 files changed, 2115 insertions(+), 63 deletions(-)
 create mode 100644 Documentation/dev-tools/kwatch.rst
 create mode 100644 include/trace/events/kwatch.h
 create mode 100644 mm/kwatch/.kunitconfig
 create mode 100644 mm/kwatch/Kconfig
 create mode 100644 mm/kwatch/Makefile
 create mode 100644 mm/kwatch/anchor.c
 create mode 100644 mm/kwatch/core.c
 create mode 100644 mm/kwatch/deref.c
 create mode 100644 mm/kwatch/deref_test.c
 create mode 100644 mm/kwatch/hwbp.c
 create mode 100644 mm/kwatch/kwatch.h
 create mode 100644 mm/kwatch/probe.c
 create mode 100644 mm/kwatch/task_ctx.c

-- 
2.53.0


^ permalink raw reply

* [RFC PATCH v2 01/13] arch: add HAVE_REINSTALL_HW_BREAKPOINT
From: Jinchao Wang @ 2026-07-17 13:02 UTC (permalink / raw)
  To: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Steven Rostedt,
	Masami Hiramatsu
  Cc: Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
	Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
	Matthew Wilcox, Alan Stern, Randy Dunlap, Alexander Potapenko,
	Marco Elver, Mike Rapoport, linux-kernel, linux-mm,
	linux-trace-kernel, linux-perf-users, linux-doc, Jinchao Wang
In-Reply-To: <20260717125023.1895892-1-wangjinchao600@gmail.com>

Some architectures can update the address, length or type of an
installed hardware breakpoint in place, without releasing and
re-reserving its slot. Add an opt-in Kconfig symbol so generic code
can offer such an operation on architectures that implement
arch_reinstall_hw_breakpoint().

This is a prerequisite for KWatch, which re-points preallocated
per-CPU breakpoints from atomic context, where the register/release
path (which may sleep and rebalances slot constraints) cannot be
used.

Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
 arch/Kconfig | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/arch/Kconfig b/arch/Kconfig
index fa7507ac8e13..41b3784e0ddd 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -457,6 +457,16 @@ config HAVE_MIXED_BREAKPOINTS_REGS
 	  Select this option if your arch implements breakpoints under the
 	  latter fashion.
 
+config HAVE_REINSTALL_HW_BREAKPOINT
+	bool
+	depends on HAVE_HW_BREAKPOINT
+	help
+	  Depending on the arch implementation of hardware breakpoints,
+	  some of them are able to update the breakpoint configuration
+	  without release and reserve the hardware breakpoint register.
+	  What configuration is able to update depends on hardware and
+	  software implementation.
+
 config HAVE_USER_RETURN_NOTIFIER
 	bool
 
-- 
2.53.0


^ permalink raw reply related

* [RFC PATCH v2 02/13] x86/hw_breakpoint: Unify breakpoint install/uninstall
From: Jinchao Wang @ 2026-07-17 13:02 UTC (permalink / raw)
  To: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Steven Rostedt,
	Masami Hiramatsu
  Cc: Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
	Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
	Matthew Wilcox, Alan Stern, Randy Dunlap, Alexander Potapenko,
	Marco Elver, Mike Rapoport, linux-kernel, linux-mm,
	linux-trace-kernel, linux-perf-users, linux-doc, Jinchao Wang
In-Reply-To: <20260717125023.1895892-1-wangjinchao600@gmail.com>

Consolidate breakpoint management to reduce code duplication.
The diffstat was misleading, so the stripped code size is compared instead.
After refactoring, it is reduced from 11976 bytes to 11448 bytes on my
x86_64 system built with clang.

This also makes it easier to introduce arch_reinstall_hw_breakpoint().

In addition, including linux/types.h to fix a missing build dependency.

Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 arch/x86/include/asm/hw_breakpoint.h |   6 ++
 arch/x86/kernel/hw_breakpoint.c      | 153 ++++++++++++++++-----------
 2 files changed, 95 insertions(+), 64 deletions(-)

diff --git a/arch/x86/include/asm/hw_breakpoint.h b/arch/x86/include/asm/hw_breakpoint.h
index 0bc931cd0698..aa6adac6c3a2 100644
--- a/arch/x86/include/asm/hw_breakpoint.h
+++ b/arch/x86/include/asm/hw_breakpoint.h
@@ -5,6 +5,7 @@
 #include <uapi/asm/hw_breakpoint.h>
 
 #define	__ARCH_HW_BREAKPOINT_H
+#include <linux/types.h>
 
 /*
  * The name should probably be something dealt in
@@ -18,6 +19,11 @@ struct arch_hw_breakpoint {
 	u8		type;
 };
 
+enum bp_slot_action {
+	BP_SLOT_ACTION_INSTALL,
+	BP_SLOT_ACTION_UNINSTALL,
+};
+
 #include <linux/kdebug.h>
 #include <linux/percpu.h>
 #include <linux/list.h>
diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoint.c
index f846c15f21ca..76886467708b 100644
--- a/arch/x86/kernel/hw_breakpoint.c
+++ b/arch/x86/kernel/hw_breakpoint.c
@@ -49,7 +49,6 @@ static DEFINE_PER_CPU(unsigned long, cpu_debugreg[HBP_NUM]);
  */
 static DEFINE_PER_CPU(struct perf_event *, bp_per_reg[HBP_NUM]);
 
-
 static inline unsigned long
 __encode_dr7(int drnum, unsigned int len, unsigned int type)
 {
@@ -86,96 +85,122 @@ int decode_dr7(unsigned long dr7, int bpnum, unsigned *len, unsigned *type)
 }
 
 /*
- * Install a perf counter breakpoint.
- *
- * We seek a free debug address register and use it for this
- * breakpoint. Eventually we enable it in the debug control register.
- *
- * Atomic: we hold the counter->ctx->lock and we only handle variables
- * and registers local to this cpu.
+ * We seek a slot and change it or keep it based on the action.
+ * Returns slot number on success, negative error on failure.
+ * Must be called with IRQs disabled.
  */
-int arch_install_hw_breakpoint(struct perf_event *bp)
+static int manage_bp_slot(struct perf_event *bp, enum bp_slot_action action)
 {
-	struct arch_hw_breakpoint *info = counter_arch_bp(bp);
-	unsigned long *dr7;
-	int i;
-
-	lockdep_assert_irqs_disabled();
+	struct perf_event *old_bp;
+	struct perf_event *new_bp;
+	int slot;
+
+	switch (action) {
+	case BP_SLOT_ACTION_INSTALL:
+		old_bp = NULL;
+		new_bp = bp;
+		break;
+	case BP_SLOT_ACTION_UNINSTALL:
+		old_bp = bp;
+		new_bp = NULL;
+		break;
+	default:
+		return -EINVAL;
+	}
 
-	for (i = 0; i < HBP_NUM; i++) {
-		struct perf_event **slot = this_cpu_ptr(&bp_per_reg[i]);
+	for (slot = 0; slot < HBP_NUM; slot++) {
+		struct perf_event **curr = this_cpu_ptr(&bp_per_reg[slot]);
 
-		if (!*slot) {
-			*slot = bp;
-			break;
+		if (*curr == old_bp) {
+			*curr = new_bp;
+			return slot;
 		}
 	}
 
-	if (WARN_ONCE(i == HBP_NUM, "Can't find any breakpoint slot"))
-		return -EBUSY;
+	if (old_bp) {
+		WARN_ONCE(1, "Can't find matching breakpoint slot");
+		return -EINVAL;
+	}
 
-	set_debugreg(info->address, i);
-	__this_cpu_write(cpu_debugreg[i], info->address);
+	WARN_ONCE(1, "No free breakpoint slots");
+	return -EBUSY;
+}
 
-	dr7 = this_cpu_ptr(&cpu_dr7);
-	*dr7 |= encode_dr7(i, info->len, info->type);
+static void setup_hwbp(struct arch_hw_breakpoint *info, int slot, bool enable)
+{
+	unsigned long dr7;
+
+	set_debugreg(info->address, slot);
+	__this_cpu_write(cpu_debugreg[slot], info->address);
+
+	dr7 = this_cpu_read(cpu_dr7);
+	if (enable)
+		dr7 |= encode_dr7(slot, info->len, info->type);
+	else
+		dr7 &= ~__encode_dr7(slot, info->len, info->type);
 
 	/*
-	 * Ensure we first write cpu_dr7 before we set the DR7 register.
-	 * This ensures an NMI never see cpu_dr7 0 when DR7 is not.
+	 * Enabling:
+	 *   Ensure we first write cpu_dr7 before we set the DR7 register.
+	 *   This ensures an NMI never see cpu_dr7 0 when DR7 is not.
 	 */
+	if (enable)
+		this_cpu_write(cpu_dr7, dr7);
+
 	barrier();
 
-	set_debugreg(*dr7, 7);
-	if (info->mask)
-		amd_set_dr_addr_mask(info->mask, i);
+	set_debugreg(dr7, 7);
 
-	return 0;
+	/*
+	 * Always push the address mask, even when clearing it (info->mask == 0):
+	 * a REINSTALL from a masked range breakpoint to an exact one must drop
+	 * the stale mask, or the CPU keeps matching the wider range.
+	 * amd_set_dr_addr_mask() is a no-op without X86_FEATURE_BPEXT and skips
+	 * redundant MSR writes, so the unconditional call is cheap.
+	 */
+	amd_set_dr_addr_mask(enable ? info->mask : 0, slot);
+
+	/*
+	 * Disabling:
+	 *   Ensure the write to cpu_dr7 is after we've set the DR7 register.
+	 *   This ensures an NMI never see cpu_dr7 0 when DR7 is not.
+	 *   The barrier keeps the compiler from reordering the two: native
+	 *   set_debugreg() has no memory clobber of its own.
+	 */
+	if (!enable) {
+		barrier();
+		this_cpu_write(cpu_dr7, dr7);
+	}
 }
 
 /*
- * Uninstall the breakpoint contained in the given counter.
- *
- * First we search the debug address register it uses and then we disable
- * it.
- *
- * Atomic: we hold the counter->ctx->lock and we only handle variables
- * and registers local to this cpu.
+ * find suitable breakpoint slot and set it up based on the action
  */
-void arch_uninstall_hw_breakpoint(struct perf_event *bp)
+static int arch_manage_bp(struct perf_event *bp, enum bp_slot_action action)
 {
-	struct arch_hw_breakpoint *info = counter_arch_bp(bp);
-	unsigned long dr7;
-	int i;
+	struct arch_hw_breakpoint *info;
+	int slot;
 
 	lockdep_assert_irqs_disabled();
 
-	for (i = 0; i < HBP_NUM; i++) {
-		struct perf_event **slot = this_cpu_ptr(&bp_per_reg[i]);
-
-		if (*slot == bp) {
-			*slot = NULL;
-			break;
-		}
-	}
-
-	if (WARN_ONCE(i == HBP_NUM, "Can't find any breakpoint slot"))
-		return;
+	slot = manage_bp_slot(bp, action);
+	if (slot < 0)
+		return slot;
 
-	dr7 = this_cpu_read(cpu_dr7);
-	dr7 &= ~__encode_dr7(i, info->len, info->type);
+	info = counter_arch_bp(bp);
+	setup_hwbp(info, slot, action != BP_SLOT_ACTION_UNINSTALL);
 
-	set_debugreg(dr7, 7);
-	if (info->mask)
-		amd_set_dr_addr_mask(0, i);
+	return 0;
+}
 
-	/*
-	 * Ensure the write to cpu_dr7 is after we've set the DR7 register.
-	 * This ensures an NMI never see cpu_dr7 0 when DR7 is not.
-	 */
-	barrier();
+int arch_install_hw_breakpoint(struct perf_event *bp)
+{
+	return arch_manage_bp(bp, BP_SLOT_ACTION_INSTALL);
+}
 
-	this_cpu_write(cpu_dr7, dr7);
+void arch_uninstall_hw_breakpoint(struct perf_event *bp)
+{
+	arch_manage_bp(bp, BP_SLOT_ACTION_UNINSTALL);
 }
 
 static int arch_bp_generic_len(int x86_len)
-- 
2.53.0


^ permalink raw reply related

* [RFC PATCH v2 03/13] x86/hw_breakpoint: Add arch_reinstall_hw_breakpoint
From: Jinchao Wang @ 2026-07-17 13:03 UTC (permalink / raw)
  To: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Steven Rostedt,
	Masami Hiramatsu
  Cc: Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
	Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
	Matthew Wilcox, Alan Stern, Randy Dunlap, Alexander Potapenko,
	Marco Elver, Mike Rapoport, linux-kernel, linux-mm,
	linux-trace-kernel, linux-perf-users, linux-doc, Jinchao Wang
In-Reply-To: <20260717125023.1895892-1-wangjinchao600@gmail.com>

The new arch_reinstall_hw_breakpoint() function can be used in an
atomic context, unlike the more expensive free and re-allocation path.
This allows callers to efficiently re-establish an existing breakpoint,
and x86 advertises the capability via HAVE_REINSTALL_HW_BREAKPOINT.

Since a REINSTALL may change bp_len, setup_hwbp() must clear the
slot's stale len/type and enable bits in DR7 before re-encoding:
OR-merging the new encoding over the old one would keep the CPU
watching with the stale width (verified in QEMU by reading DR7 after
re-arming watch_len=1 over a len8 breakpoint: 0x999906aa merged
without the clearing, 0x199906aa with it).

Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
 arch/x86/Kconfig                     |  1 +
 arch/x86/include/asm/hw_breakpoint.h |  2 ++
 arch/x86/kernel/hw_breakpoint.c      | 16 ++++++++++++++--
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index bdad90f210e4..5be698db0241 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -246,6 +246,7 @@ config X86
 	select HAVE_FUNCTION_TRACER
 	select HAVE_GCC_PLUGINS
 	select HAVE_HW_BREAKPOINT
+	select HAVE_REINSTALL_HW_BREAKPOINT
 	select HAVE_IOREMAP_PROT
 	select HAVE_IRQ_EXIT_ON_IRQ_STACK	if X86_64
 	select HAVE_IRQ_TIME_ACCOUNTING
diff --git a/arch/x86/include/asm/hw_breakpoint.h b/arch/x86/include/asm/hw_breakpoint.h
index aa6adac6c3a2..c22cc4e87fc5 100644
--- a/arch/x86/include/asm/hw_breakpoint.h
+++ b/arch/x86/include/asm/hw_breakpoint.h
@@ -21,6 +21,7 @@ struct arch_hw_breakpoint {
 
 enum bp_slot_action {
 	BP_SLOT_ACTION_INSTALL,
+	BP_SLOT_ACTION_REINSTALL,
 	BP_SLOT_ACTION_UNINSTALL,
 };
 
@@ -65,6 +66,7 @@ extern int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
 
 
 int arch_install_hw_breakpoint(struct perf_event *bp);
+int arch_reinstall_hw_breakpoint(struct perf_event *bp);
 void arch_uninstall_hw_breakpoint(struct perf_event *bp);
 void hw_breakpoint_pmu_read(struct perf_event *bp);
 void hw_breakpoint_pmu_unthrottle(struct perf_event *bp);
diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoint.c
index 76886467708b..e2dbd43d8e39 100644
--- a/arch/x86/kernel/hw_breakpoint.c
+++ b/arch/x86/kernel/hw_breakpoint.c
@@ -100,6 +100,10 @@ static int manage_bp_slot(struct perf_event *bp, enum bp_slot_action action)
 		old_bp = NULL;
 		new_bp = bp;
 		break;
+	case BP_SLOT_ACTION_REINSTALL:
+		old_bp = bp;
+		new_bp = bp;
+		break;
 	case BP_SLOT_ACTION_UNINSTALL:
 		old_bp = bp;
 		new_bp = NULL;
@@ -134,10 +138,13 @@ static void setup_hwbp(struct arch_hw_breakpoint *info, int slot, bool enable)
 	__this_cpu_write(cpu_debugreg[slot], info->address);
 
 	dr7 = this_cpu_read(cpu_dr7);
+	/*
+	 * Clear the slot's stale len/type and enable bits first: a REINSTALL
+	 * with a different bp_len would otherwise OR-merge both encodings.
+	 */
+	dr7 &= ~__encode_dr7(slot, 0xf, 0);
 	if (enable)
 		dr7 |= encode_dr7(slot, info->len, info->type);
-	else
-		dr7 &= ~__encode_dr7(slot, info->len, info->type);
 
 	/*
 	 * Enabling:
@@ -198,6 +205,11 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
 	return arch_manage_bp(bp, BP_SLOT_ACTION_INSTALL);
 }
 
+int arch_reinstall_hw_breakpoint(struct perf_event *bp)
+{
+	return arch_manage_bp(bp, BP_SLOT_ACTION_REINSTALL);
+}
+
 void arch_uninstall_hw_breakpoint(struct perf_event *bp)
 {
 	arch_manage_bp(bp, BP_SLOT_ACTION_UNINSTALL);
-- 
2.53.0


^ permalink raw reply related

* [RFC PATCH v2 04/13] HWBP: Add modify_wide_hw_breakpoint_local() API
From: Jinchao Wang @ 2026-07-17 13:03 UTC (permalink / raw)
  To: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Steven Rostedt,
	Masami Hiramatsu
  Cc: Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
	Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
	Matthew Wilcox, Alan Stern, Randy Dunlap, Alexander Potapenko,
	Marco Elver, Mike Rapoport, linux-kernel, linux-mm,
	linux-trace-kernel, linux-perf-users, linux-doc, Jinchao Wang
In-Reply-To: <20260717125023.1895892-1-wangjinchao600@gmail.com>

From: "Masami Hiramatsu (Google)" <mhiramat@kernel.org>

Add modify_wide_hw_breakpoint_local() arch-wide interface which allows
hwbp users to update watch address on-line. This is available if the
arch supports CONFIG_HAVE_REINSTALL_HW_BREAKPOINT.
Note that this allows to change the type only for compatible types,
because it does not release and reserve the hwbp slot based on type.
For instance, you can not change HW_BREAKPOINT_W to HW_BREAKPOINT_X.

Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
 include/linux/hw_breakpoint.h |  6 +++++
 kernel/events/hw_breakpoint.c | 43 +++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h
index db199d653dd1..6754ffbee9ed 100644
--- a/include/linux/hw_breakpoint.h
+++ b/include/linux/hw_breakpoint.h
@@ -81,6 +81,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr,
 			    perf_overflow_handler_t triggered,
 			    void *context);
 
+extern int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+					   struct perf_event_attr *attr);
+
 extern int register_perf_hw_breakpoint(struct perf_event *bp);
 extern void unregister_hw_breakpoint(struct perf_event *bp);
 extern void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events);
@@ -124,6 +127,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr,
 			    perf_overflow_handler_t triggered,
 			    void *context)		{ return NULL; }
 static inline int
+modify_wide_hw_breakpoint_local(struct perf_event *bp,
+				struct perf_event_attr *attr) { return -EOPNOTSUPP; }
+static inline int
 register_perf_hw_breakpoint(struct perf_event *bp)	{ return -ENOSYS; }
 static inline void unregister_hw_breakpoint(struct perf_event *bp)	{ }
 static inline void
diff --git a/kernel/events/hw_breakpoint.c b/kernel/events/hw_breakpoint.c
index 789add0c185a..f4709c892d67 100644
--- a/kernel/events/hw_breakpoint.c
+++ b/kernel/events/hw_breakpoint.c
@@ -888,6 +888,49 @@ void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events)
 }
 EXPORT_SYMBOL_GPL(unregister_wide_hw_breakpoint);
 
+/**
+ * modify_wide_hw_breakpoint_local - update breakpoint config for local CPU
+ * @bp: the hwbp perf event for this CPU
+ * @attr: the new attribute for @bp
+ *
+ * This does not release and reserve the slot of a HWBP; it just reuses the
+ * current slot on local CPU. So the users must update the other CPUs by
+ * themselves.
+ * Also, since this does not release/reserve the slot, this can not change the
+ * type to incompatible type of the HWBP.
+ * Return err if attr is invalid or the CPU fails to update debug register
+ * for new @attr.
+ */
+#ifdef CONFIG_HAVE_REINSTALL_HW_BREAKPOINT
+int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+				    struct perf_event_attr *attr)
+{
+	struct arch_hw_breakpoint info;
+	int ret;
+
+	if (find_slot_idx(bp->attr.bp_type) != find_slot_idx(attr->bp_type))
+		return -EINVAL;
+
+	ret = hw_breakpoint_arch_parse(bp, attr, &info);
+	if (ret)
+		return ret;
+
+	*counter_arch_bp(bp) = info;
+	bp->attr.bp_addr = attr->bp_addr;
+	bp->attr.bp_type = attr->bp_type;
+	bp->attr.bp_len = attr->bp_len;
+
+	return arch_reinstall_hw_breakpoint(bp);
+}
+#else
+int modify_wide_hw_breakpoint_local(struct perf_event *bp,
+				    struct perf_event_attr *attr)
+{
+	return -EOPNOTSUPP;
+}
+#endif
+EXPORT_SYMBOL_GPL(modify_wide_hw_breakpoint_local);
+
 /**
  * hw_breakpoint_is_used - check if breakpoints are currently used
  *
-- 
2.53.0


^ permalink raw reply related

* [RFC PATCH v2 05/13] mm/kwatch: add watch expression parser and dereference engine
From: Jinchao Wang @ 2026-07-17 13:04 UTC (permalink / raw)
  To: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Steven Rostedt,
	Masami Hiramatsu
  Cc: Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
	Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
	Matthew Wilcox, Alan Stern, Randy Dunlap, Alexander Potapenko,
	Marco Elver, Mike Rapoport, linux-kernel, linux-mm,
	linux-trace-kernel, linux-perf-users, linux-doc, Jinchao Wang
In-Reply-To: <20260717125023.1895892-1-wangjinchao600@gmail.com>

KWatch watches a memory address that is only known once the target
function runs, e.g. "argument 1, plus 8, dereferenced once". Add the
two halves of that mechanism:

- kwatch_deref_parse() turns a textual watch expression
  {base}[+-off][->[+-]off]... into a kwatch_config: a base anchor
  (arg1..arg6, stack, an absolute address or - for built-in KWatch -
  a symbol name) plus a static offset chain.

- kwatch_deref_resolve() replays the chain at probe time against
  pt_regs. Every pointer load goes through get_kernel_nofault() and
  the final address must be a kernel address.

Also add the internal kwatch.h header shared by the rest of the
series. Nothing is built yet; the Kconfig entry comes with the
control plane.

Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
 mm/kwatch/Makefile |   3 +
 mm/kwatch/deref.c  | 174 +++++++++++++++++++++++++++++++++++++++++++++
 mm/kwatch/kwatch.h | 101 ++++++++++++++++++++++++++
 3 files changed, 278 insertions(+)
 create mode 100644 mm/kwatch/Makefile
 create mode 100644 mm/kwatch/deref.c
 create mode 100644 mm/kwatch/kwatch.h

diff --git a/mm/kwatch/Makefile b/mm/kwatch/Makefile
new file mode 100644
index 000000000000..69c21ae62123
--- /dev/null
+++ b/mm/kwatch/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_KWATCH) += kwatch.o
+
+kwatch-y := deref.o
diff --git a/mm/kwatch/deref.c b/mm/kwatch/deref.c
new file mode 100644
index 000000000000..a93c76139e7c
--- /dev/null
+++ b/mm/kwatch/deref.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/ptrace.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/kallsyms.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+
+#include "kwatch.h"
+
+int kwatch_deref_resolve(const struct kwatch_config *cfg, struct pt_regs *regs,
+			 unsigned long *out_addr, u16 *out_len)
+{
+	unsigned long addr = 0;
+	int i;
+
+	/* 1. Resolve the Base Anchor */
+	if (cfg->base == KWATCH_BASE_STACK) {
+		addr = kernel_stack_pointer(regs);
+		if (unlikely(!addr))
+			return -EINVAL;
+	} else if (cfg->base >= KWATCH_BASE_ARG1 &&
+		   cfg->base <= KWATCH_BASE_ARG6) {
+		int arg_idx = cfg->base - KWATCH_BASE_ARG1;
+
+		addr = regs_get_kernel_argument(regs, arg_idx);
+	} else if (cfg->base == KWATCH_BASE_ABS_ADDR ||
+		   cfg->base == KWATCH_BASE_GLOBAL_SYM) {
+		/* Zero-latency load of the static symbol location */
+		addr = cfg->sym_addr;
+	} else {
+		return -EINVAL;
+	}
+
+	/* 2. The Pointer-Chasing FSM */
+	for (i = 0; i < cfg->offset_count; i++) {
+		addr += cfg->offsets[i];
+
+		if (i < cfg->offset_count - 1) {
+			unsigned long next_addr;
+
+			/* Dynamically read the pointer contents at runtime */
+			if (get_kernel_nofault(next_addr, (unsigned long *)addr))
+				return -EFAULT;
+
+			addr = next_addr;
+		}
+	}
+
+	/* Enforce strict Kernel-Space boundary */
+	if (unlikely(addr < TASK_SIZE_MAX))
+		return -EINVAL;
+
+	*out_addr = addr;
+	*out_len = cfg->watch_len;
+	return 0;
+}
+
+int kwatch_deref_parse(struct kwatch_config *cfg, const char *watch_expr)
+{
+	char *p, *sep, *dup_expr;
+	char type = '\0';
+	bool is_deref = false;
+	int ret = 0;
+
+	dup_expr = kstrdup(watch_expr, GFP_KERNEL);
+	if (!dup_expr)
+		return -ENOMEM;
+
+	cfg->offset_count = 1;
+	cfg->offsets[0] = 0;
+
+	/* 1. Isolate and Resolve Base Anchor */
+	p = dup_expr;
+	sep = NULL;
+	while (*p) {
+		if (*p == '+') {
+			sep = p;
+			type = '+';
+			break;
+		}
+		if (*p == '-') {
+			sep = p;
+			type = '-';
+			if (p[1] == '>')
+				is_deref = true;
+			break;
+		}
+		p++;
+	}
+
+	if (type)
+		*sep = '\0';
+
+	if (!strcmp(dup_expr, "stack")) {
+		cfg->base = KWATCH_BASE_STACK;
+	} else if (!strncmp(dup_expr, "arg", 3) && strlen(dup_expr) == 4) {
+		int arg_num;
+
+		if (kstrtoint(dup_expr + 3, 10, &arg_num) || arg_num < 1 ||
+		    arg_num > 6) {
+			ret = -EINVAL;
+			goto out;
+		}
+		cfg->base = KWATCH_BASE_ARG1 + (arg_num - 1);
+	} else if (kstrtoul(dup_expr, 0, &cfg->sym_addr) == 0) {
+		cfg->base = KWATCH_BASE_ABS_ADDR;
+	} else {
+#if IS_BUILTIN(CONFIG_KWATCH)
+		cfg->sym_addr = kallsyms_lookup_name(dup_expr);
+		if (!cfg->sym_addr) {
+			pr_err("Failed to resolve symbol name: %s\n", dup_expr);
+			ret = -EINVAL;
+			goto out;
+		}
+		cfg->base = KWATCH_BASE_GLOBAL_SYM;
+#else
+		pr_err("cannot resolve symbol %s when built as a module, use a hex address\n",
+		       dup_expr);
+		ret = -EINVAL;
+		goto out;
+#endif
+	}
+
+	if (!type)
+		goto out;
+
+	/* 2. Resolve Base Offset (if + or - exists) */
+	if (!is_deref) {
+		char *next;
+
+		*sep = type; /* Restore the '+' or '-' for kstrtol */
+		next = strstr(sep, "->");
+		if (next)
+			*next = '\0';
+
+		if (kstrtol(sep, 0, &cfg->offsets[0])) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		p = next ? next + 2 : NULL;
+	} else {
+		/* Jump directly to the first dereference after '->' */
+		p = sep + 2;
+	}
+
+	/* 3. Resolve Dereference Chain */
+	while (p) {
+		char *next;
+
+		if (cfg->offset_count >= MAX_DEREF_CHAIN) {
+			ret = -E2BIG;
+			goto out;
+		}
+
+		next = strstr(p, "->");
+		if (next)
+			*next = '\0';
+
+		if (kstrtol(p, 0, &cfg->offsets[cfg->offset_count++])) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		p = next ? next + 2 : NULL;
+	}
+
+out:
+	kfree(dup_expr);
+	return ret;
+}
diff --git a/mm/kwatch/kwatch.h b/mm/kwatch/kwatch.h
new file mode 100644
index 000000000000..dbe0fd0e6a0d
--- /dev/null
+++ b/mm/kwatch/kwatch.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _MM_KWATCH_H
+#define _MM_KWATCH_H
+
+#include <linux/fprobe.h>
+#include <linux/kprobes.h>
+#include <linux/perf_event.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/compiler.h>
+#include <linux/atomic.h>
+
+#define MAX_CONFIG_STR_LEN 512
+#define MAX_DEREF_CHAIN 4
+
+struct kwatch_watchpoint;
+
+struct kwatch_tsk_ctx {
+	struct task_struct *task;
+	struct kwatch_watchpoint *wp;
+	u16 depth;
+	u32 epoch;
+};
+
+struct kwatch_watchpoint {
+	struct perf_event *__percpu *event;
+	call_single_data_t __percpu *csd_arm;
+	call_single_data_t __percpu *csd_disarm;
+	struct perf_event_attr attr;
+	atomic_t in_use; // multi-consumer safe get/put
+	struct list_head list; // for cpu online and offline
+
+	struct task_struct *arm_tsk;
+	atomic_t pending_ipis;
+	atomic_t refcount;
+	bool teardown;
+};
+
+enum kwatch_base_type {
+	KWATCH_BASE_STACK,
+	KWATCH_BASE_ABS_ADDR,
+	KWATCH_BASE_GLOBAL_SYM,
+	KWATCH_BASE_ARG1,
+	KWATCH_BASE_ARG2,
+	KWATCH_BASE_ARG3,
+	KWATCH_BASE_ARG4,
+	KWATCH_BASE_ARG5,
+	KWATCH_BASE_ARG6,
+};
+
+struct kwatch_config {
+	u16 max_watch;
+	char func_name[KSYM_NAME_LEN];
+	u16 func_offset;
+	u16 depth;
+	u16 duration;
+	u16 watch_len;
+
+	/* Unified Deref Engine State */
+	enum kwatch_base_type base;
+	char watch_expr[MAX_CONFIG_STR_LEN];
+	unsigned long sym_addr;
+	long offsets[MAX_DEREF_CHAIN];
+	u8 offset_count;
+	u16 max_concurrency;
+};
+
+int kwatch_hwbp_prealloc(u16 max_watch);
+void kwatch_hwbp_free(void);
+int kwatch_hwbp_get(struct kwatch_watchpoint **out_wp);
+void kwatch_hwbp_arm(struct kwatch_watchpoint *wp, unsigned long addr, u16 len);
+int kwatch_hwbp_put(struct kwatch_watchpoint *wp);
+
+int kwatch_probe_start(struct kwatch_config *cfg);
+void kwatch_probe_stop(void);
+void kwatch_probe_mute(bool mute);
+bool kwatch_probe_validate_hit(struct pt_regs *regs, struct task_struct *arm_tsk);
+unsigned long kwatch_probe_nmi_rejected(void);
+unsigned long kwatch_hwbp_arm_ipi_suppressed(void);
+
+int kwatch_tsk_ctx_prealloc(u16 max_concurrency);
+struct kwatch_tsk_ctx *kwatch_tsk_ctx_get(bool can_alloc);
+void kwatch_tsk_ctx_put(void);
+void kwatch_tsk_ctx_release(struct kwatch_tsk_ctx *ctx);
+void kwatch_tsk_ctx_reset(struct kwatch_tsk_ctx *ctx, u32 new_epoch);
+void kwatch_tsk_ctx_release_wps(void);
+void kwatch_tsk_ctx_free(void);
+
+void kwatch_global_anchor(unsigned long duration_sec);
+int kwatch_anchor_start(u16 duration);
+void kwatch_anchor_stop(void);
+void kwatch_anchor_cancel_work(void);
+bool kwatch_anchor_has_expired(void);
+void kwatch_anchor_clear_expired(void);
+void kwatch_auto_stop(void);
+
+int kwatch_deref_resolve(const struct kwatch_config *cfg, struct pt_regs *regs,
+			 unsigned long *out_addr, u16 *out_len);
+int kwatch_deref_parse(struct kwatch_config *cfg, const char *watch_expr);
+
+#endif /* _MM_KWATCH_H */
-- 
2.53.0


^ permalink raw reply related

* [RFC PATCH v2 06/13] mm/kwatch: add lockless per-task context pool
From: Jinchao Wang @ 2026-07-17 13:04 UTC (permalink / raw)
  To: Andrew Morton, Peter Zijlstra, Thomas Gleixner, Steven Rostedt,
	Masami Hiramatsu
  Cc: Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, x86,
	Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Mathieu Desnoyers, David Hildenbrand, Jonathan Corbet,
	Matthew Wilcox, Alan Stern, Randy Dunlap, Alexander Potapenko,
	Marco Elver, Mike Rapoport, linux-kernel, linux-mm,
	linux-trace-kernel, linux-perf-users, linux-doc, Jinchao Wang
In-Reply-To: <20260717125023.1895892-1-wangjinchao600@gmail.com>

A task that enters the watched function needs somewhere to keep its
window state (nesting depth, owned watchpoint, config epoch). The
lookup runs in kprobe and NMI-like contexts, so it must not allocate
or take locks.

Use a preallocated open-addressing array hashed by task_struct
pointer. Slots are claimed with cmpxchg() and released with
smp_store_release(); lookup is a read-only probe sequence. The pool
size (max_concurrency) bounds how many tasks can be inside watch
windows concurrently; excess tasks are simply not tracked.

Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
 mm/kwatch/Makefile   |   2 +-
 mm/kwatch/task_ctx.c | 125 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 126 insertions(+), 1 deletion(-)
 create mode 100644 mm/kwatch/task_ctx.c

diff --git a/mm/kwatch/Makefile b/mm/kwatch/Makefile
index 69c21ae62123..cc6574df0d68 100644
--- a/mm/kwatch/Makefile
+++ b/mm/kwatch/Makefile
@@ -1,3 +1,3 @@
 obj-$(CONFIG_KWATCH) += kwatch.o
 
-kwatch-y := deref.o
+kwatch-y := deref.o task_ctx.o
diff --git a/mm/kwatch/task_ctx.c b/mm/kwatch/task_ctx.c
new file mode 100644
index 000000000000..64383a4429e7
--- /dev/null
+++ b/mm/kwatch/task_ctx.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/slab.h>
+#include <linux/hash.h>
+#include <linux/sched.h>
+#include <linux/log2.h>
+#include "kwatch.h"
+
+static u16 kwatch_ctx_pool_size;
+static u16 kwatch_ctx_pool_mask;
+
+static struct kwatch_tsk_ctx *kwatch_ctx_pool;
+
+/* Pool size is a u16 and indexes a power-of-two hash table, so bound the
+ * request away from both the roundup_pow_of_two() u16 overflow (>32768 wraps
+ * to 0) and the degenerate size-1 case (ilog2(1) == 0 breaks hash_ptr()).
+ */
+#define KWATCH_CTX_POOL_MIN	256
+#define KWATCH_CTX_POOL_MAX	32768
+
+int kwatch_tsk_ctx_prealloc(u16 max_concurrency)
+{
+	if (max_concurrency < KWATCH_CTX_POOL_MIN)
+		max_concurrency = KWATCH_CTX_POOL_MIN;
+	else if (max_concurrency > KWATCH_CTX_POOL_MAX)
+		max_concurrency = KWATCH_CTX_POOL_MAX;
+
+	/*
+	 * Set the size/mask only when actually allocating, so they can never
+	 * drift out of sync with the live pool if prealloc is ever called
+	 * again without a matching free.
+	 */
+	if (unlikely(!kwatch_ctx_pool)) {
+		kwatch_ctx_pool_size = roundup_pow_of_two(max_concurrency);
+		kwatch_ctx_pool_mask = kwatch_ctx_pool_size - 1;
+
+		kwatch_ctx_pool = kcalloc(kwatch_ctx_pool_size,
+					  sizeof(struct kwatch_tsk_ctx),
+					  GFP_KERNEL);
+		if (!kwatch_ctx_pool)
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+struct kwatch_tsk_ctx *kwatch_tsk_ctx_get(bool can_alloc)
+{
+	int start_idx, i, idx;
+	struct task_struct *t;
+
+	if (unlikely(!kwatch_ctx_pool))
+		return NULL;
+
+	start_idx = hash_ptr(current, ilog2(kwatch_ctx_pool_size));
+
+	for (i = 0; i < kwatch_ctx_pool_size; i++) {
+		idx = (start_idx + i) & kwatch_ctx_pool_mask;
+		t = READ_ONCE(kwatch_ctx_pool[idx].task);
+		if (t == current)
+			return &kwatch_ctx_pool[idx];
+	}
+
+	if (!can_alloc)
+		return NULL;
+
+	for (i = 0; i < kwatch_ctx_pool_size; i++) {
+		idx = (start_idx + i) & kwatch_ctx_pool_mask;
+		t = READ_ONCE(kwatch_ctx_pool[idx].task);
+		if (!t) {
+			if (!cmpxchg(&kwatch_ctx_pool[idx].task, NULL, current))
+				return &kwatch_ctx_pool[idx];
+		}
+	}
+
+	return NULL;
+}
+
+void kwatch_tsk_ctx_reset(struct kwatch_tsk_ctx *ctx, u32 new_epoch)
+{
+	struct kwatch_watchpoint *wp = xchg(&ctx->wp, NULL);
+
+	if (wp)
+		kwatch_hwbp_put(wp);
+	ctx->depth = 0;
+	ctx->epoch = new_epoch;
+}
+
+/* Release a slot we hold a pointer to: disarm its wp and free the slot. */
+void kwatch_tsk_ctx_release(struct kwatch_tsk_ctx *ctx)
+{
+	kwatch_tsk_ctx_reset(ctx, 0);
+
+	/* Pairs with READ_ONCE() in kwatch_tsk_ctx_get() */
+	smp_store_release(&ctx->task, NULL);
+}
+
+void kwatch_tsk_ctx_put(void)
+{
+	struct kwatch_tsk_ctx *ctx = kwatch_tsk_ctx_get(false);
+
+	if (unlikely(!ctx))
+		return;
+
+	kwatch_tsk_ctx_release(ctx);
+}
+
+void kwatch_tsk_ctx_release_wps(void)
+{
+	int i;
+
+	if (!kwatch_ctx_pool)
+		return;
+
+	for (i = 0; i < kwatch_ctx_pool_size; i++) {
+		struct kwatch_watchpoint *wp = xchg(&kwatch_ctx_pool[i].wp,
+						    NULL);
+		if (wp)
+			kwatch_hwbp_put(wp);
+	}
+}
+
+void kwatch_tsk_ctx_free(void)
+{
+	kfree(kwatch_ctx_pool);
+	kwatch_ctx_pool = NULL;
+}
-- 
2.53.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox