* [RFC PATCH 1/4] kcsan: add targeted watchpoint matching
@ 2026-10-14 9:52 Hui Su
2026-10-15 14:33 ` Marco Elver
0 siblings, 1 reply; 3+ messages in thread
From: Hui Su @ 2026-10-14 9:52 UTC (permalink / raw)
To: elver, dvyukov
Cc: kasan-dev, corbet, skhan, rdunlap, workflows, linux-doc, linux-kernel
Add target state and a static key to KCSAN. When a target is set, only
non-atomic instrumented accesses containing that address are eligible for
watchpoint setup. Other accesses do not consume the skip interval. Keep
report filtering independent from target matching.
Signed-off-by: Hui Su <sh_def@163.com>
---
kernel/kcsan/core.c | 37 +++++++++++++++++++++++++++++++++++++
kernel/kcsan/kcsan.h | 10 ++++++++++
2 files changed, 47 insertions(+)
diff --git a/kernel/kcsan/core.c b/kernel/kcsan/core.c
index 2db82661cd60..2a58a3a53adb 100644
--- a/kernel/kcsan/core.c
+++ b/kernel/kcsan/core.c
@@ -12,10 +12,12 @@
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/init.h>
+#include <linux/jump_label.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/minmax.h>
#include <linux/moduleparam.h>
+#include <linux/mutex.h>
#include <linux/percpu.h>
#include <linux/preempt.h>
#include <linux/sched.h>
@@ -108,9 +110,40 @@ static atomic_long_t watchpoints[CONFIG_KCSAN_NUM_WATCHPOINTS + NUM_SLOTS-1];
*/
static DEFINE_PER_CPU(long, kcsan_skip);
+static DEFINE_STATIC_KEY_FALSE(kcsan_target_key);
+static DEFINE_MUTEX(kcsan_target_lock);
+static unsigned long kcsan_target_addr;
+
/* For kcsan_prandom_u32_max(). */
static DEFINE_PER_CPU(u32, kcsan_rand_state);
+void kcsan_target_set(unsigned long addr)
+{
+ unsigned long old;
+
+ mutex_lock(&kcsan_target_lock);
+ old = READ_ONCE(kcsan_target_addr);
+
+ if (!old && addr) {
+ /* Publish the address before enabling the fast-path branch. */
+ WRITE_ONCE(kcsan_target_addr, addr);
+ static_branch_enable(&kcsan_target_key);
+ } else if (old && !addr) {
+ /* Disable the fast-path branch before invalidating the target. */
+ static_branch_disable(&kcsan_target_key);
+ WRITE_ONCE(kcsan_target_addr, 0);
+ } else if (old && addr) {
+ WRITE_ONCE(kcsan_target_addr, addr);
+ }
+
+ mutex_unlock(&kcsan_target_lock);
+}
+
+unsigned long kcsan_target_get(void)
+{
+ return READ_ONCE(kcsan_target_addr);
+}
+
static __always_inline atomic_long_t *find_watchpoint(unsigned long addr,
size_t size,
bool expect_write,
@@ -277,6 +310,10 @@ should_watch(struct kcsan_ctx *ctx, const volatile void *ptr, size_t size, int t
if (is_atomic(ctx, ptr, size, type))
return false;
+ if (static_branch_unlikely(&kcsan_target_key))
+ return kcsan_target_matches(READ_ONCE(kcsan_target_addr),
+ (unsigned long)ptr, size);
+
if (this_cpu_dec_return(kcsan_skip) >= 0)
return false;
diff --git a/kernel/kcsan/kcsan.h b/kernel/kcsan/kcsan.h
index ae33c2a7f07e..2a36813c3a76 100644
--- a/kernel/kcsan/kcsan.h
+++ b/kernel/kcsan/kcsan.h
@@ -25,6 +25,16 @@ extern unsigned int kcsan_udelay_interrupt;
*/
extern bool kcsan_enabled;
+void kcsan_target_set(unsigned long addr);
+unsigned long kcsan_target_get(void);
+
+static __always_inline bool
+kcsan_target_matches(unsigned long target, unsigned long addr,
+ size_t size)
+{
+ return size && target >= addr && target - addr < size;
+}
+
/*
* Save/restore IRQ flags state trace dirtied by KCSAN.
*/
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [RFC PATCH 1/4] kcsan: add targeted watchpoint matching
2026-10-14 9:52 [RFC PATCH 1/4] kcsan: add targeted watchpoint matching Hui Su
@ 2026-10-15 14:33 ` Marco Elver
2026-10-15 15:27 ` Hui Su
0 siblings, 1 reply; 3+ messages in thread
From: Marco Elver @ 2026-10-15 14:33 UTC (permalink / raw)
To: Hui Su
Cc: dvyukov, kasan-dev, corbet, skhan, rdunlap, workflows, linux-doc,
linux-kernel
On Wed, 14 Oct 2026 at 11:52, Hui Su <sh_def@163.com> wrote:
>
> Add target state and a static key to KCSAN. When a target is set, only
> non-atomic instrumented accesses containing that address are eligible for
> watchpoint setup. Other accesses do not consume the skip interval. Keep
> report filtering independent from target matching.
>
> Signed-off-by: Hui Su <sh_def@163.com>
> ---
> kernel/kcsan/core.c | 37 +++++++++++++++++++++++++++++++++++++
> kernel/kcsan/kcsan.h | 10 ++++++++++
> 2 files changed, 47 insertions(+)
>
> diff --git a/kernel/kcsan/core.c b/kernel/kcsan/core.c
> index 2db82661cd60..2a58a3a53adb 100644
> --- a/kernel/kcsan/core.c
> +++ b/kernel/kcsan/core.c
> @@ -12,10 +12,12 @@
> #include <linux/delay.h>
> #include <linux/export.h>
> #include <linux/init.h>
> +#include <linux/jump_label.h>
> #include <linux/kernel.h>
> #include <linux/list.h>
> #include <linux/minmax.h>
> #include <linux/moduleparam.h>
> +#include <linux/mutex.h>
> #include <linux/percpu.h>
> #include <linux/preempt.h>
> #include <linux/sched.h>
> @@ -108,9 +110,40 @@ static atomic_long_t watchpoints[CONFIG_KCSAN_NUM_WATCHPOINTS + NUM_SLOTS-1];
> */
> static DEFINE_PER_CPU(long, kcsan_skip);
>
> +static DEFINE_STATIC_KEY_FALSE(kcsan_target_key);
> +static DEFINE_MUTEX(kcsan_target_lock);
> +static unsigned long kcsan_target_addr;
> +
> /* For kcsan_prandom_u32_max(). */
> static DEFINE_PER_CPU(u32, kcsan_rand_state);
>
> +void kcsan_target_set(unsigned long addr)
> +{
> + unsigned long old;
> +
> + mutex_lock(&kcsan_target_lock);
> + old = READ_ONCE(kcsan_target_addr);
> +
> + if (!old && addr) {
> + /* Publish the address before enabling the fast-path branch. */
> + WRITE_ONCE(kcsan_target_addr, addr);
> + static_branch_enable(&kcsan_target_key);
> + } else if (old && !addr) {
> + /* Disable the fast-path branch before invalidating the target. */
> + static_branch_disable(&kcsan_target_key);
> + WRITE_ONCE(kcsan_target_addr, 0);
> + } else if (old && addr) {
> + WRITE_ONCE(kcsan_target_addr, addr);
> + }
> +
> + mutex_unlock(&kcsan_target_lock);
> +}
> +
> +unsigned long kcsan_target_get(void)
> +{
> + return READ_ONCE(kcsan_target_addr);
> +}
> +
> static __always_inline atomic_long_t *find_watchpoint(unsigned long addr,
> size_t size,
> bool expect_write,
> @@ -277,6 +310,10 @@ should_watch(struct kcsan_ctx *ctx, const volatile void *ptr, size_t size, int t
> if (is_atomic(ctx, ptr, size, type))
> return false;
>
> + if (static_branch_unlikely(&kcsan_target_key))
> + return kcsan_target_matches(READ_ONCE(kcsan_target_addr),
> + (unsigned long)ptr, size);
> +
How does this change the generate asm (say on x86-64) of the fast
path? (E.g. take a look at __tsan_read8 disasm.)
We tried hard to e.g. avoid stack spills in the fast path, and if that
affects fast path codegen negatively, that'd be not so nice.
> if (this_cpu_dec_return(kcsan_skip) >= 0)
> return false;
>
> diff --git a/kernel/kcsan/kcsan.h b/kernel/kcsan/kcsan.h
> index ae33c2a7f07e..2a36813c3a76 100644
> --- a/kernel/kcsan/kcsan.h
> +++ b/kernel/kcsan/kcsan.h
> @@ -25,6 +25,16 @@ extern unsigned int kcsan_udelay_interrupt;
> */
> extern bool kcsan_enabled;
>
> +void kcsan_target_set(unsigned long addr);
> +unsigned long kcsan_target_get(void);
> +
> +static __always_inline bool
> +kcsan_target_matches(unsigned long target, unsigned long addr,
> + size_t size)
> +{
> + return size && target >= addr && target - addr < size;
> +}
> +
> /*
> * Save/restore IRQ flags state trace dirtied by KCSAN.
> */
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [RFC PATCH 1/4] kcsan: add targeted watchpoint matching
2026-10-15 14:33 ` Marco Elver
@ 2026-10-15 15:27 ` Hui Su
0 siblings, 0 replies; 3+ messages in thread
From: Hui Su @ 2026-10-15 15:27 UTC (permalink / raw)
To: Marco Elver
Cc: dvyukov, kasan-dev, corbet, skhan, rdunlap, workflows, linux-doc,
linux-kernel
> How does this change the generate asm (say on x86-64) of the fast
> path? (E.g. take a look at __tsan_read8 disasm.)
> We tried hard to e.g. avoid stack spills in the fast path, and if that
> affects fast path codegen negatively, that'd be not so nice.
Thanks, I checked this directly on x86-64, comparing baseline
4056b3c9d3c92ea45955641dbafeb9b960e146ef with patch 1
661e5012b2fd928a32b563c4337db3af9a71c743.
With GCC 15.2.0 and the exact RFC .config, __tsan_read8 has no new calls,
stack spills, stack-frame adjustment, or callee-saved register saves.
The relevant sequence from the linked image with the static key disabled is:
baseline (__tsan_read8):
+0xc5: cmp BYTE PTR [rax+0x10],0x0
+0xc9: jne ...
+0xcb: mov rdx,0xffffffffffffffff
+0xd2: xadd QWORD PTR gs:[...],rdx
patch 1, target disabled:
+0xc5: cmp BYTE PTR [rax+0x10],0x0
+0xc9: jne ...
+0xcb: 66 90 xchg ax,ax
+0xcd: mov rdx,0xffffffffffffffff
+0xd4: xadd QWORD PTR gs:[...],rdx
The original instruction stream is shifted by two bytes to make room for
the jump-label NOP. The target matcher is out of line on the cold branch.
In core.o, the symbol grows from 270 to 302 bytes, including that matcher.
For the Clang 21.1.8 comparison I used the same normalized .config for
both baseline and patch 1. This differs from the original RFC Clang build
by three Kconfig normalization changes. The paired __tsan_read8 comparison
likewise has no new calls, spills, stack-frame adjustment, or callee-saved
register saves. The disabled site is at +0x108 in core.o. The linked RFC
Clang image also has the 66 90 site. __tsan_read8 grows from 387 to 415
bytes in core.o due to the cold matcher.
I also checked __tsan_read1/2/4/16 and __tsan_write8; none gained calls
or spills. GCC's __tsan_write8 already has a push %rbx/pop sequence, which
is unchanged.
So I don't see a disabled-fast-path codegen regression from patch 1 in
either compiler build.
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-15 15:30 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-14 9:52 [RFC PATCH 1/4] kcsan: add targeted watchpoint matching Hui Su
2026-10-15 14:33 ` Marco Elver
2026-10-15 15:27 ` Hui Su
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®