* [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool
@ 2025-08-28 7:32 Jinchao Wang
2025-08-28 7:32 ` [PATCH 01/17] mm/ksw: add build system support Jinchao Wang
` (16 more replies)
0 siblings, 17 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
This patch series introduces **KStackWatch**, a lightweight kernel debugging tool
for detecting kernel stack corruption in real time.
The motivation comes from scenarios where corruption occurs silently in one function
but manifests later as a crash in another. Using KASAN may not reproduce the issue due
to its heavy overhead. with no direct call trace linking the two. Such bugs are often
extremely hard to debug with existing tools.
I demonstrate this scenario in **test2 (silent corruption test)**.
KStackWatch works by combining a hardware breakpoint with kprobe and fprobe.
It can watch a stack canary or a selected local variable and detects the moment the
corruption actually occurs. This allows developers to pinpoint the real source rather
than only observing the final crash.
Key features include:
- Lightweight overhead with minimal impact on bug reproducibility
- Real-time detection of stack corruption
- Simple configuration through `/proc/kstackwatch`
- Support for recursive depth filter
To validate the approach, the patch includes a test module and a test script.
---
This series builds on the previously proposed RFC[1] and incorporates feedback. The
changes are as follows:
Core Implementation
* Replaced kretprobe with fprobe for function exit hooking, as suggested
by Masami Hiramatsu.
* Introduced per-task depth logic to track recursion across scheduling
* Removed the use of workqueue for a more efficient corruption check
* Reordered patches for better logical flow
* Simplified and improved commit messages throughout the series
* Removed initial archcheck which should be improved later
Testing and Architecture
* Replaced the multiple-thread test with silent corruption test
* Split self-tests into a separate patch to improve clarity.
Maintenance
* Added a new entry for KStackWatch to the MAINTAINERS file.
[1] https://lore.kernel.org/lkml/20250818122720.434981-1-wangjinchao600@gmail.com/
---
The series is structured as follows:
Jinchao Wang (17):
mm/ksw: add build system support
mm/ksw: add ksw_config struct and parser
mm/ksw: add /proc/kstackwatch interface
mm/ksw: add HWBP pre-allocation support
x86/HWBP: introduce arch_reinstall_hw_breakpoint() for atomic context
mm/ksw: add atomic watch on/off operations
mm/ksw: add stack probe support
mm/ksw: implement stack canary and local var resolution logic
mm/ksw: add per-task recursion depth tracking
mm/ksw: coordinate watch and stack for full functionality
mm/ksw: add self-debug functions for kstackwatch watch
mm/ksw: add test module
mm/ksw: add stack overflow test
mm/ksw: add simplified silent corruption test
mm/ksw: add recursive corruption test
tools/kstackwatch: add interactive test script for KStackWatch
MAINTAINERS: add entry for KStackWatch (Kernel Stack Watch)
MAINTAINERS | 6 +
arch/x86/include/asm/hw_breakpoint.h | 1 +
arch/x86/kernel/hw_breakpoint.c | 50 +++++
mm/Kconfig.debug | 20 ++
mm/Makefile | 1 +
mm/kstackwatch/Makefile | 8 +
mm/kstackwatch/kernel.c | 260 +++++++++++++++++++++++
mm/kstackwatch/kstackwatch.h | 53 +++++
mm/kstackwatch/kstackwatch_test.c | 261 +++++++++++++++++++++++
mm/kstackwatch/stack.c | 289 ++++++++++++++++++++++++++
mm/kstackwatch/watch.c | 177 ++++++++++++++++
tools/kstackwatch/kstackwatch_test.sh | 118 +++++++++++
12 files changed, 1244 insertions(+)
create mode 100644 mm/kstackwatch/Makefile
create mode 100644 mm/kstackwatch/kernel.c
create mode 100644 mm/kstackwatch/kstackwatch.h
create mode 100644 mm/kstackwatch/kstackwatch_test.c
create mode 100644 mm/kstackwatch/stack.c
create mode 100644 mm/kstackwatch/watch.c
create mode 100644 tools/kstackwatch/kstackwatch_test.sh
--
2.43.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 01/17] mm/ksw: add build system support
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 02/17] mm/ksw: add ksw_config struct and parser Jinchao Wang
` (15 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Add Kconfig and Makefile infrastructure for KStackWatch, a real-time tool
for debugging kernel stack corruption.
The implementation is located under `mm/kstackwatch/`.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/Kconfig.debug | 10 ++++++++++
mm/Makefile | 1 +
mm/kstackwatch/Makefile | 2 ++
mm/kstackwatch/kernel.c | 24 ++++++++++++++++++++++++
mm/kstackwatch/kstackwatch.h | 5 +++++
mm/kstackwatch/stack.c | 1 +
mm/kstackwatch/watch.c | 1 +
7 files changed, 44 insertions(+)
create mode 100644 mm/kstackwatch/Makefile
create mode 100644 mm/kstackwatch/kernel.c
create mode 100644 mm/kstackwatch/kstackwatch.h
create mode 100644 mm/kstackwatch/stack.c
create mode 100644 mm/kstackwatch/watch.c
diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug
index 32b65073d0cc..c2cc8c7b595f 100644
--- a/mm/Kconfig.debug
+++ b/mm/Kconfig.debug
@@ -309,3 +309,13 @@ config PER_VMA_LOCK_STATS
overhead in the page fault path.
If in doubt, say N.
+
+config KSTACK_WATCH
+ tristate "Kernel Stack Watch"
+ depends on STACKPROTECTOR && HAVE_HW_BREAKPOINT && KPROBES && FPROBE
+ help
+ A lightweight real-time debugging tool to detect stack corruption.
+ It can watch either the canary or local variable and tracks
+ the recursive depth of the monitored function.
+
+ If unsure, say N.
diff --git a/mm/Makefile b/mm/Makefile
index ef54aa615d9d..665c9f2bf987 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_PAGE_POISONING) += page_poison.o
obj-$(CONFIG_KASAN) += kasan/
obj-$(CONFIG_KFENCE) += kfence/
obj-$(CONFIG_KMSAN) += kmsan/
+obj-$(CONFIG_KSTACK_WATCH) += kstackwatch/
obj-$(CONFIG_FAILSLAB) += failslab.o
obj-$(CONFIG_FAIL_PAGE_ALLOC) += fail_page_alloc.o
obj-$(CONFIG_MEMTEST) += memtest.o
diff --git a/mm/kstackwatch/Makefile b/mm/kstackwatch/Makefile
new file mode 100644
index 000000000000..84a46cb9a766
--- /dev/null
+++ b/mm/kstackwatch/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_KSTACK_WATCH) += kstackwatch.o
+kstackwatch-y := kernel.o stack.o watch.o
diff --git a/mm/kstackwatch/kernel.c b/mm/kstackwatch/kernel.c
new file mode 100644
index 000000000000..93379a0a0f7e
--- /dev/null
+++ b/mm/kstackwatch/kernel.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/module.h>
+
+MODULE_AUTHOR("Jinchao Wang");
+MODULE_DESCRIPTION("Kernel Stack Watch");
+MODULE_LICENSE("GPL");
+
+static int __init kstackwatch_init(void)
+{
+ pr_info("KSW: module loaded\n");
+ pr_info("KSW: usage:\n");
+ pr_info("KSW: echo 'function+ip_offset[+depth] [local_var_offset:local_var_len]' > /proc/kstackwatch\n");
+
+ return 0;
+}
+
+static void __exit kstackwatch_exit(void)
+{
+ pr_info("KSW: Module unloaded\n");
+}
+
+module_init(kstackwatch_init);
+module_exit(kstackwatch_exit);
diff --git a/mm/kstackwatch/kstackwatch.h b/mm/kstackwatch/kstackwatch.h
new file mode 100644
index 000000000000..0273ef478a26
--- /dev/null
+++ b/mm/kstackwatch/kstackwatch.h
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _KSTACKWATCH_H
+#define _KSTACKWATCH_H
+
+#endif /* _KSTACKWATCH_H */
diff --git a/mm/kstackwatch/stack.c b/mm/kstackwatch/stack.c
new file mode 100644
index 000000000000..cec594032515
--- /dev/null
+++ b/mm/kstackwatch/stack.c
@@ -0,0 +1 @@
+// SPDX-License-Identifier: GPL-2.0
diff --git a/mm/kstackwatch/watch.c b/mm/kstackwatch/watch.c
new file mode 100644
index 000000000000..cec594032515
--- /dev/null
+++ b/mm/kstackwatch/watch.c
@@ -0,0 +1 @@
+// SPDX-License-Identifier: GPL-2.0
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 02/17] mm/ksw: add ksw_config struct and parser
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
2025-08-28 7:32 ` [PATCH 01/17] mm/ksw: add build system support Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 03/17] mm/ksw: add /proc/kstackwatch interface Jinchao Wang
` (14 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Add struct ksw_config and ksw_parse_config() to parse user string.
Update `Makefile` to pass compilation.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/Makefile | 2 ++
mm/kstackwatch/kernel.c | 70 +++++++++++++++++++++++++++++++++++-
mm/kstackwatch/kstackwatch.h | 34 ++++++++++++++++++
3 files changed, 105 insertions(+), 1 deletion(-)
diff --git a/mm/kstackwatch/Makefile b/mm/kstackwatch/Makefile
index 84a46cb9a766..d422f0e114dd 100644
--- a/mm/kstackwatch/Makefile
+++ b/mm/kstackwatch/Makefile
@@ -1,2 +1,4 @@
obj-$(CONFIG_KSTACK_WATCH) += kstackwatch.o
kstackwatch-y := kernel.o stack.o watch.o
+
+CFLAGS_kernel.o := -Wno-error=unused-function
diff --git a/mm/kstackwatch/kernel.c b/mm/kstackwatch/kernel.c
index 93379a0a0f7e..4a6dc49449fe 100644
--- a/mm/kstackwatch/kernel.c
+++ b/mm/kstackwatch/kernel.c
@@ -1,11 +1,79 @@
// SPDX-License-Identifier: GPL-2.0
-
+#include <linux/kstrtox.h>
#include <linux/module.h>
+#include <linux/string.h>
+
+#include "kstackwatch.h"
MODULE_AUTHOR("Jinchao Wang");
MODULE_DESCRIPTION("Kernel Stack Watch");
MODULE_LICENSE("GPL");
+/*
+ * Format of the configuration string:
+ * function+ip_offset[+depth] [local_var_offset:local_var_len]
+ *
+ * - function : name of the target function
+ * - ip_offset : instruction pointer offset within the function
+ * - depth : recursion depth to watch
+ * - local_var_offset : offset from the stack pointer at function+ip_offset
+ * - local_var_len : length of the local variable
+ */
+static int ksw_parse_config(char *buf, struct ksw_config *config)
+{
+ char *func_part, *local_var_part = NULL;
+ char *token;
+
+ /* Set the watch type to the default canary-based monitoring */
+ config->type = WATCH_CANARY;
+
+ func_part = strim(buf);
+ strscpy(config->config_str, func_part, MAX_CONFIG_STR_LEN);
+
+ local_var_part = strchr(func_part, ' ');
+ if (local_var_part) {
+ *local_var_part = '\0'; // Terminate the function part
+ local_var_part = strim(local_var_part + 1);
+ }
+
+ /* 1. Parse the function part: function+ip_offset[+depth] */
+ token = strsep(&func_part, "+");
+ if (!token)
+ return -EINVAL;
+
+ strscpy(config->function, token, MAX_FUNC_NAME_LEN - 1);
+
+ token = strsep(&func_part, "+");
+ if (!token || kstrtou16(token, 0, &config->ip_offset)) {
+ pr_err("KSW: failed to parse instruction offset\n");
+ return -EINVAL;
+ }
+
+ token = strsep(&func_part, "+");
+ if (token && kstrtou16(token, 0, &config->depth)) {
+ pr_err("KSW: failed to parse depth\n");
+ return -EINVAL;
+ }
+ if (!local_var_part || !(*local_var_part))
+ return 0;
+
+ /* 2. Parse the optional local var: offset:len */
+ config->type = WATCH_LOCAL_VAR;
+ token = strsep(&local_var_part, ":");
+ if (!token || kstrtou16(token, 0, &config->local_var_offset)) {
+ pr_err("KSW: failed to parse stack variable offset\n");
+ return -EINVAL;
+ }
+
+ if (!local_var_part ||
+ kstrtou16(local_var_part, 0, &config->local_var_len)) {
+ pr_err("KSW: failed to parse stack variable length\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int __init kstackwatch_init(void)
{
pr_info("KSW: module loaded\n");
diff --git a/mm/kstackwatch/kstackwatch.h b/mm/kstackwatch/kstackwatch.h
index 0273ef478a26..b5f1835586c1 100644
--- a/mm/kstackwatch/kstackwatch.h
+++ b/mm/kstackwatch/kstackwatch.h
@@ -2,4 +2,38 @@
#ifndef _KSTACKWATCH_H
#define _KSTACKWATCH_H
+#include <linux/types.h>
+
+#define MAX_FUNC_NAME_LEN 64
+#define MAX_CONFIG_STR_LEN 128
+#define MAX_FRAME_SEARCH 128
+
+enum watch_type {
+ WATCH_CANARY = 0,
+ WATCH_LOCAL_VAR,
+};
+
+struct ksw_config {
+ /* function part */
+ char function[MAX_FUNC_NAME_LEN];
+ u16 ip_offset;
+ u16 depth;
+
+ /* local var, useless for canary watch */
+ /* offset from rsp at function+ip_offset */
+ u16 local_var_offset;
+
+ /*
+ * local var size (1,2,4,8 bytes)
+ * it will be the watching len
+ */
+ u16 local_var_len;
+
+ /* easy for understand*/
+ enum watch_type type;
+
+ /* save to show */
+ char config_str[MAX_CONFIG_STR_LEN];
+};
+
#endif /* _KSTACKWATCH_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 03/17] mm/ksw: add /proc/kstackwatch interface
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
2025-08-28 7:32 ` [PATCH 01/17] mm/ksw: add build system support Jinchao Wang
2025-08-28 7:32 ` [PATCH 02/17] mm/ksw: add ksw_config struct and parser Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 04/17] mm/ksw: add HWBP pre-allocation support Jinchao Wang
` (13 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Provide the /proc/kstackwatch file to read or update the KSW configuration.
Writing a valid config string starts watching; empty input stops watching.
Invalid input stops watching and reports an error.
Allocate a ksw_config struct on module init and free it on exit.
Manage watching state with ksw_start_watching() and ksw_stop_watching().
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kernel.c | 140 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
diff --git a/mm/kstackwatch/kernel.c b/mm/kstackwatch/kernel.c
index 4a6dc49449fe..95ade95abde1 100644
--- a/mm/kstackwatch/kernel.c
+++ b/mm/kstackwatch/kernel.c
@@ -1,7 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kstrtox.h>
#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
#include <linux/string.h>
+#include <linux/uaccess.h>
#include "kstackwatch.h"
@@ -9,6 +12,29 @@ MODULE_AUTHOR("Jinchao Wang");
MODULE_DESCRIPTION("Kernel Stack Watch");
MODULE_LICENSE("GPL");
+struct ksw_config *ksw_config;
+bool watching_active;
+
+bool panic_on_catch;
+module_param(panic_on_catch, bool, 0644);
+MODULE_PARM_DESC(panic_on_catch,
+ "Trigger a kernel panic immediately on corruption catch");
+
+static int ksw_start_watching(void)
+{
+ watching_active = true;
+
+ pr_info("KSW: start watching %s\n", ksw_config->config_str);
+ return 0;
+}
+
+static void ksw_stop_watching(void)
+{
+ watching_active = false;
+
+ pr_info("KSW: stop watching %s\n", ksw_config->config_str);
+}
+
/*
* Format of the configuration string:
* function+ip_offset[+depth] [local_var_offset:local_var_len]
@@ -74,8 +100,114 @@ static int ksw_parse_config(char *buf, struct ksw_config *config)
return 0;
}
+/**
+ * kstackwatch_proc_write - Handle writes to the /proc/kstackwatch file
+ * @file: file struct for the proc entry
+ * @buffer: user-space buffer containing the command string
+ * @count: the number of bytes from the user-space buffer
+ * @pos: file offset
+ *
+ * This function processes user input to control the kernel stack watcher
+ * Before attempting to process any new configuration. It handles three
+ * cases based on the input string, In all three cases, the system will
+ * clear its state first, with subsequent actions determined by the input:
+ *
+ * 1. An empty or whitespace-only string
+ * Return a success code
+ *
+ * 2. An invalid configuration string
+ * Return a error code
+ *
+ * 3. A valid configuration string
+ * Start a new stack watch, return a success code
+ *
+ * Return: The number of bytes successfully processed on success,
+ * or a negative error code on failure.
+ */
+static ssize_t kstackwatch_proc_write(struct file *file,
+ const char __user *buffer, size_t count,
+ loff_t *pos)
+{
+ char input[MAX_CONFIG_STR_LEN];
+ int ret;
+
+ if (count == 0 || count >= sizeof(input))
+ return -EINVAL;
+
+ if (copy_from_user(input, buffer, count))
+ return -EFAULT;
+
+ if (watching_active)
+ ksw_stop_watching();
+ memset(ksw_config, 0, sizeof(*ksw_config));
+
+ input[count] = '\0';
+ strim(input);
+
+ /* case 1 */
+ if (!strlen(input)) {
+ pr_info("KSW: config cleared\n");
+ return count;
+ }
+
+ ret = ksw_parse_config(input, ksw_config);
+ if (ret) {
+ /* case 2 */
+ pr_err("KSW: Failed to parse config %d\n", ret);
+ return ret;
+ }
+
+ /* case 3 */
+ ret = ksw_start_watching();
+ if (ret) {
+ pr_err("KSW: Failed to start watching with %d\n", ret);
+ return ret;
+ }
+
+ return count;
+}
+
+static int kstackwatch_proc_show(struct seq_file *m, void *v)
+{
+ if (watching_active) {
+ seq_printf(m, "%s\n", ksw_config->config_str);
+ } else {
+ seq_puts(m, "not watching\n");
+ seq_puts(m, "\nusage:\n");
+ seq_puts(
+ m,
+ " echo 'function+ip_offset[+depth] [local_var_offset:local_var_len]' > /proc/kstackwatch\n");
+ seq_puts(m, " Watch the canary without the local var part.");
+ }
+
+ return 0;
+}
+
+static int kstackwatch_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, kstackwatch_proc_show, NULL);
+}
+
+static const struct proc_ops kstackwatch_proc_ops = {
+ .proc_open = kstackwatch_proc_open,
+ .proc_read = seq_read,
+ .proc_write = kstackwatch_proc_write,
+ .proc_lseek = seq_lseek,
+ .proc_release = single_release,
+};
+
static int __init kstackwatch_init(void)
{
+ ksw_config = kmalloc(sizeof(*ksw_config), GFP_KERNEL);
+ if (!ksw_config)
+ return -ENOMEM;
+
+ /* Create proc interface */
+ if (!proc_create("kstackwatch", 0644, NULL, &kstackwatch_proc_ops)) {
+ pr_err("KSW: create proc kstackwatch fail");
+ return -ENOMEM;
+ }
+
pr_info("KSW: module loaded\n");
pr_info("KSW: usage:\n");
pr_info("KSW: echo 'function+ip_offset[+depth] [local_var_offset:local_var_len]' > /proc/kstackwatch\n");
@@ -85,6 +217,14 @@ static int __init kstackwatch_init(void)
static void __exit kstackwatch_exit(void)
{
+ /* Cleanup active watching */
+ if (watching_active)
+ ksw_stop_watching();
+
+ /* Remove proc interface */
+ remove_proc_entry("kstackwatch", NULL);
+ kfree(ksw_config);
+
pr_info("KSW: Module unloaded\n");
}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 04/17] mm/ksw: add HWBP pre-allocation support
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (2 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 03/17] mm/ksw: add /proc/kstackwatch interface Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 05/17] x86/HWBP: introduce arch_reinstall_hw_breakpoint() for atomic context Jinchao Wang
` (12 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Pre-allocate per-CPU hardware breakpoints at init with a dummy address,
which will be retargeted dynamically in kprobe handler. This avoids
allocation in atomic contexts.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kstackwatch.h | 6 ++++
mm/kstackwatch/watch.c | 62 ++++++++++++++++++++++++++++++++++++
2 files changed, 68 insertions(+)
diff --git a/mm/kstackwatch/kstackwatch.h b/mm/kstackwatch/kstackwatch.h
index b5f1835586c1..2318779bde70 100644
--- a/mm/kstackwatch/kstackwatch.h
+++ b/mm/kstackwatch/kstackwatch.h
@@ -36,4 +36,10 @@ struct ksw_config {
char config_str[MAX_CONFIG_STR_LEN];
};
+extern bool panic_on_catch;
+
+/* watch management */
+int ksw_watch_init(struct ksw_config *config);
+void ksw_watch_exit(void);
+
#endif /* _KSTACKWATCH_H */
diff --git a/mm/kstackwatch/watch.c b/mm/kstackwatch/watch.c
index cec594032515..e7ed88700b49 100644
--- a/mm/kstackwatch/watch.c
+++ b/mm/kstackwatch/watch.c
@@ -1 +1,63 @@
// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/hw_breakpoint.h>
+#include <linux/kern_levels.h>
+#include <linux/kprobes.h>
+#include <linux/printk.h>
+#include <linux/perf_event.h>
+#include <linux/sched/debug.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/stacktrace.h>
+
+#include "kstackwatch.h"
+
+#define MAX_STACK_ENTRIES 64
+
+struct perf_event *__percpu *watch_events;
+struct ksw_config *watch_config;
+
+static unsigned long long watch_holder;
+
+static void ksw_watch_handler(struct perf_event *bp,
+ struct perf_sample_data *data,
+ struct pt_regs *regs)
+{
+ pr_err("========== KStackWatch: Caught stack corruption =======\n");
+ pr_err("KSW: config %s\n", watch_config->config_str);
+ show_regs(regs);
+ pr_err("=================== KStackWatch End ==================\n");
+
+ if (panic_on_catch)
+ panic("KSW: Stack corruption detected");
+}
+
+int ksw_watch_init(struct ksw_config *config)
+{
+ struct perf_event_attr attr;
+
+ hw_breakpoint_init(&attr);
+ attr.bp_addr = (unsigned long)&watch_holder;
+ attr.bp_len = HW_BREAKPOINT_LEN_8;
+ attr.bp_type = HW_BREAKPOINT_W;
+ watch_events =
+ register_wide_hw_breakpoint(&attr, ksw_watch_handler, NULL);
+ if (IS_ERR((void *)watch_events)) {
+ int ret = PTR_ERR((void *)watch_events);
+
+ pr_err("KSW: failed to register wide hw breakpoint: %d\n", ret);
+ return ret;
+ }
+
+ watch_config = config;
+ pr_info("KSW: watch inited\n");
+ return 0;
+}
+
+void ksw_watch_exit(void)
+{
+ unregister_wide_hw_breakpoint(watch_events);
+ watch_events = NULL;
+
+ pr_info("KSW: watch exited\n");
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 05/17] x86/HWBP: introduce arch_reinstall_hw_breakpoint() for atomic context
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (3 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 04/17] mm/ksw: add HWBP pre-allocation support Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 06/17] mm/ksw: add atomic watch on/off operations Jinchao Wang
` (11 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Introduce arch_reinstall_hw_breakpoint() to update hardware breakpoint
parameters (address, length, type) without freeing and reallocating the
debug register slot.
This allows atomic updates in contexts where memory allocation is not
permitted, such as kprobe handlers.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
arch/x86/include/asm/hw_breakpoint.h | 1 +
arch/x86/kernel/hw_breakpoint.c | 50 ++++++++++++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/arch/x86/include/asm/hw_breakpoint.h b/arch/x86/include/asm/hw_breakpoint.h
index 0bc931cd0698..bb7c70ad22fe 100644
--- a/arch/x86/include/asm/hw_breakpoint.h
+++ b/arch/x86/include/asm/hw_breakpoint.h
@@ -59,6 +59,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 b01644c949b2..89135229ed21 100644
--- a/arch/x86/kernel/hw_breakpoint.c
+++ b/arch/x86/kernel/hw_breakpoint.c
@@ -132,6 +132,56 @@ int arch_install_hw_breakpoint(struct perf_event *bp)
return 0;
}
+/*
+ * Reinstall a hardware breakpoint on the current CPU.
+ *
+ * This function is used to re-establish a perf counter hardware breakpoint.
+ * It finds the debug address register slot previously allocated for the
+ * breakpoint and re-enables it by writing the address to the debug register
+ * and setting the corresponding bits in the debug control register (DR7).
+ *
+ * It is expected that the breakpoint's event context lock is already held
+ * and interrupts are disabled, ensuring atomicity and safety from other
+ * event handlers.
+ */
+int arch_reinstall_hw_breakpoint(struct perf_event *bp)
+{
+ struct arch_hw_breakpoint *info = counter_arch_bp(bp);
+ unsigned long *dr7;
+ int i;
+
+ 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)
+ break;
+ }
+
+ if (WARN_ONCE(i == HBP_NUM, "Can't find a matching breakpoint slot"))
+ return -EINVAL;
+
+ set_debugreg(info->address, i);
+ __this_cpu_write(cpu_debugreg[i], info->address);
+
+ dr7 = this_cpu_ptr(&cpu_dr7);
+ *dr7 |= encode_dr7(i, 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.
+ */
+ barrier();
+
+ set_debugreg(*dr7, 7);
+ if (info->mask)
+ amd_set_dr_addr_mask(info->mask, i);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(arch_reinstall_hw_breakpoint);
+
/*
* Uninstall the breakpoint contained in the given counter.
*
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 06/17] mm/ksw: add atomic watch on/off operations
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (4 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 05/17] x86/HWBP: introduce arch_reinstall_hw_breakpoint() for atomic context Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 07/17] mm/ksw: add stack probe support Jinchao Wang
` (10 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Add support to atomically turn the hardware watch on and off without
allocation overhead.
The watch is pre-allocated and later retargeted using
arch_reinstall_hw_breakpoint(). The current CPU is updated directly, while
other CPUs are updated asynchronously via smp_call_function_single_async().
This allows KStackWatch to switch the watch in kprobe/fprobe handlers.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kstackwatch.h | 2 +
mm/kstackwatch/watch.c | 99 ++++++++++++++++++++++++++++++++++++
2 files changed, 101 insertions(+)
diff --git a/mm/kstackwatch/kstackwatch.h b/mm/kstackwatch/kstackwatch.h
index 2318779bde70..13ef8c79f855 100644
--- a/mm/kstackwatch/kstackwatch.h
+++ b/mm/kstackwatch/kstackwatch.h
@@ -41,5 +41,7 @@ extern bool panic_on_catch;
/* watch management */
int ksw_watch_init(struct ksw_config *config);
void ksw_watch_exit(void);
+int ksw_watch_on(u64 watch_addr, u64 watch_len);
+void ksw_watch_off(void);
#endif /* _KSTACKWATCH_H */
diff --git a/mm/kstackwatch/watch.c b/mm/kstackwatch/watch.c
index e7ed88700b49..284facaac8fc 100644
--- a/mm/kstackwatch/watch.c
+++ b/mm/kstackwatch/watch.c
@@ -16,9 +16,21 @@
struct perf_event *__percpu *watch_events;
struct ksw_config *watch_config;
+static DEFINE_SPINLOCK(watch_lock);
static unsigned long long watch_holder;
+static struct watch_info {
+ u64 addr;
+ u64 len;
+} watch_info;
+
+static void ksw_watch_on_local_cpu(void *info);
+
+static DEFINE_PER_CPU(call_single_data_t,
+ watch_csd) = CSD_INIT(ksw_watch_on_local_cpu,
+ &watch_info);
+
static void ksw_watch_handler(struct perf_event *bp,
struct perf_sample_data *data,
struct pt_regs *regs)
@@ -32,6 +44,93 @@ static void ksw_watch_handler(struct perf_event *bp,
panic("KSW: Stack corruption detected");
}
+/*
+ * set up watchon current CPU
+ * addr and len updated by ksw_watch_on() already
+ */
+static void ksw_watch_on_local_cpu(void *data)
+{
+ struct perf_event *bp;
+ struct watch_info *watch_info = data;
+ int cpu = smp_processor_id();
+ int ret;
+
+ bp = *per_cpu_ptr(watch_events, cpu);
+ if (!bp)
+ return;
+ bp->attr.bp_addr = watch_info->addr;
+ bp->attr.bp_len = watch_info->len;
+ ret = hw_breakpoint_arch_parse(bp, &bp->attr, counter_arch_bp(bp));
+ if (ret) {
+ pr_err("KSW: failed to validate HWBP for CPU %d ret %d\n", cpu,
+ ret);
+ return;
+ }
+ ret = arch_reinstall_hw_breakpoint(bp);
+ if (ret) {
+ pr_err("KSW: failed to reinstall HWBP on CPU %d ret %d\n", cpu,
+ ret);
+ return;
+ }
+
+ if (bp->attr.bp_addr == (unsigned long)&watch_holder) {
+ pr_debug("KSW: watch off CPU %d\n", cpu);
+ } else {
+ pr_debug("KSW: watch on CPU %d at 0x%px (len %llu)\n", cpu,
+ (void *)bp->attr.bp_addr, bp->attr.bp_len);
+ }
+}
+
+int ksw_watch_on(u64 watch_addr, u64 watch_len)
+{
+ unsigned long flags;
+ int cpu;
+
+ if (!watch_addr) {
+ pr_err("KSW: watch with invalid address\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&watch_lock, flags);
+
+ /*
+ * check if already watched
+ */
+ if (watch_info.addr != 0 && // not uninit
+ watch_info.addr != (unsigned long)&watch_holder && // installed
+ watch_addr != (unsigned long)&watch_holder) { //not restore
+ spin_unlock_irqrestore(&watch_lock, flags);
+ return -EBUSY;
+ }
+
+ watch_info.addr = watch_addr;
+ watch_info.len = watch_len;
+
+ spin_unlock_irqrestore(&watch_lock, flags);
+
+ if (watch_addr == (unsigned long)&watch_holder)
+ pr_debug("KSW: watch off starting\n");
+ else
+ pr_debug("KSW: watch on starting\n");
+
+ for_each_online_cpu(cpu) {
+ if (cpu == raw_smp_processor_id()) {
+ ksw_watch_on_local_cpu(&watch_info);
+ } else {
+ call_single_data_t *csd = &per_cpu(watch_csd, cpu);
+
+ smp_call_function_single_async(cpu, csd);
+ }
+ }
+
+ return 0;
+}
+
+void ksw_watch_off(void)
+{
+ ksw_watch_on((unsigned long)&watch_holder, sizeof(watch_holder));
+}
+
int ksw_watch_init(struct ksw_config *config)
{
struct perf_event_attr attr;
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 07/17] mm/ksw: add stack probe support
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (5 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 06/17] mm/ksw: add atomic watch on/off operations Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 08/17] mm/ksw: implement stack canary and local var resolution logic Jinchao Wang
` (9 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Introduce stack management for KStackWatch using kprobes and fprobes to
enable dynamic watch switching:
- Entry: prepare target address/length and enable watch
- Exit: disable watch
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kstackwatch.h | 4 ++
mm/kstackwatch/stack.c | 91 ++++++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+)
diff --git a/mm/kstackwatch/kstackwatch.h b/mm/kstackwatch/kstackwatch.h
index 13ef8c79f855..bc8664af4fa6 100644
--- a/mm/kstackwatch/kstackwatch.h
+++ b/mm/kstackwatch/kstackwatch.h
@@ -38,6 +38,10 @@ struct ksw_config {
extern bool panic_on_catch;
+/* stack management */
+int ksw_stack_init(struct ksw_config *config);
+void ksw_stack_exit(void);
+
/* watch management */
int ksw_watch_init(struct ksw_config *config);
void ksw_watch_exit(void);
diff --git a/mm/kstackwatch/stack.c b/mm/kstackwatch/stack.c
index cec594032515..3b72177315cc 100644
--- a/mm/kstackwatch/stack.c
+++ b/mm/kstackwatch/stack.c
@@ -1 +1,92 @@
// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/fprobe.h>
+#include <linux/kprobes.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+
+#include "kstackwatch.h"
+
+struct ksw_config *probe_config;
+
+/* prepare watch_addr and watch_len for watch */
+static int ksw_stack_prepare_watch(struct pt_regs *regs,
+ struct ksw_config *config, u64 *watch_addr,
+ u64 *watch_len)
+{
+ /* TODO: implement logic */
+ *watch_addr = 0;
+ *watch_len = 0;
+ return 0;
+}
+
+static struct kprobe entry_probe;
+static struct fprobe exit_probe_fprobe;
+
+static void ksw_stack_entry_handler(struct kprobe *p, struct pt_regs *regs,
+ unsigned long flags)
+{
+ int ret;
+ u64 watch_addr;
+ u64 watch_len;
+
+ ret = ksw_stack_prepare_watch(regs, probe_config, &watch_addr,
+ &watch_len);
+ if (ret) {
+ pr_err("KSW: failed to prepare watch target: %d\n", ret);
+ return;
+ }
+
+ ret = ksw_watch_on(watch_addr, watch_len);
+ if (ret) {
+ pr_err("KSW: failed to watch on addr:0x%llx len:%llx %d\n",
+ watch_addr, watch_len, ret);
+ return;
+ }
+}
+
+static void ksw_stack_exit_handler(struct fprobe *fp, unsigned long ip,
+ unsigned long ret_ip,
+ struct ftrace_regs *regs, void *data)
+{
+ ksw_watch_off();
+}
+
+int ksw_stack_init(struct ksw_config *config)
+{
+ int ret;
+ char *symbuf = NULL;
+
+ /* Setup entry probe */
+ memset(&entry_probe, 0, sizeof(entry_probe));
+ entry_probe.symbol_name = config->function;
+ entry_probe.offset = config->ip_offset;
+ entry_probe.post_handler = ksw_stack_entry_handler;
+ probe_config = config;
+ ret = register_kprobe(&entry_probe);
+ if (ret < 0) {
+ pr_err("KSW: Failed to register kprobe ret %d\n", ret);
+ return ret;
+ }
+
+ /* Setup exit probe */
+ memset(&exit_probe_fprobe, 0, sizeof(exit_probe_fprobe));
+ exit_probe_fprobe.exit_handler = ksw_stack_exit_handler;
+ symbuf = probe_config->function;
+
+ ret = register_fprobe_syms(&exit_probe_fprobe, (const char **)&symbuf,
+ 1);
+ if (ret < 0) {
+ pr_err("KSW: register_fprobe_syms fail %d\n", ret);
+ unregister_kprobe(&entry_probe);
+ return ret;
+ }
+
+ return 0;
+}
+
+void ksw_stack_exit(void)
+{
+ unregister_fprobe(&exit_probe_fprobe);
+ unregister_kprobe(&entry_probe);
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 08/17] mm/ksw: implement stack canary and local var resolution logic
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (6 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 07/17] mm/ksw: add stack probe support Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 09/17] mm/ksw: add per-task recursion depth tracking Jinchao Wang
` (8 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Implement logic to resolve stack watch target for kstackwatch:
- Locate the stack canary within the current frame
- Resolve local variable offsets relative to the stack pointer
- Validate addresses against current task's stack bounds
This logic prepares watch addr and len for use in kprobe/fprobe handlers,
enabling dynamic stack monitoring.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/stack.c | 102 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 99 insertions(+), 3 deletions(-)
diff --git a/mm/kstackwatch/stack.c b/mm/kstackwatch/stack.c
index 3b72177315cc..1d9814a58fde 100644
--- a/mm/kstackwatch/stack.c
+++ b/mm/kstackwatch/stack.c
@@ -1,22 +1,118 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/fprobe.h>
+#include <linux/interrupt.h>
#include <linux/kprobes.h>
+#include <linux/percpu.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
+#include <linux/stackprotector.h>
#include "kstackwatch.h"
struct ksw_config *probe_config;
+/* Find canary address in current stack frame */
+static unsigned long ksw_stack_find_canary(struct pt_regs *regs)
+{
+ unsigned long *stack_ptr, *stack_end;
+ unsigned long expected_canary;
+ unsigned int i;
+
+ if (!regs || !regs->sp)
+ return 0;
+
+ stack_ptr = (unsigned long *)regs->sp;
+ stack_end =
+ (unsigned long *)current->stack + THREAD_SIZE / sizeof(long);
+ expected_canary = current->stack_canary;
+
+ for (i = 0; i < MAX_FRAME_SEARCH && &stack_ptr[i] < stack_end; i++) {
+ if (stack_ptr[i] == expected_canary) {
+ pr_info("KSW: canary found i:%d 0x%px\n", i,
+ &stack_ptr[i]);
+ return (unsigned long)&stack_ptr[i];
+ }
+ }
+
+ return 0;
+}
+
+/* Resolve stack offset to actual address */
+static unsigned long ksw_stack_resolve_offset(struct pt_regs *regs,
+ s64 local_var_offset)
+{
+ unsigned long stack_base;
+ unsigned long target_addr;
+
+ if (!regs)
+ return 0;
+
+ /* Use stack pointer as base for offset calculation */
+ stack_base = regs->sp;
+ target_addr = stack_base + local_var_offset;
+
+ pr_debug("KSW: stack resolve offset target: 0x%lx\n", target_addr);
+
+ return target_addr;
+}
+
+/* Validate that address is within current stack bounds */
+static int ksw_stack_validate_addr(unsigned long addr, size_t size)
+{
+ unsigned long stack_start, stack_end;
+
+ if (!addr || !size)
+ return -EINVAL;
+
+ stack_start = (unsigned long)current->stack;
+ stack_end = stack_start + THREAD_SIZE;
+
+ if (addr < stack_start || (addr + size) > stack_end) {
+ pr_warn("KSW: address 0x%lx (size %zu) outside stack bounds [0x%lx-0x%lx]\n",
+ addr, size, stack_start, stack_end);
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
/* prepare watch_addr and watch_len for watch */
static int ksw_stack_prepare_watch(struct pt_regs *regs,
struct ksw_config *config, u64 *watch_addr,
u64 *watch_len)
{
- /* TODO: implement logic */
- *watch_addr = 0;
- *watch_len = 0;
+ u64 addr;
+ u64 len;
+
+ /* Resolve addresses for all active watches */
+ switch (config->type) {
+ case WATCH_CANARY:
+ addr = ksw_stack_find_canary(regs);
+ len = 8;
+ break;
+
+ case WATCH_LOCAL_VAR:
+ addr = ksw_stack_resolve_offset(regs, config->local_var_offset);
+ if (!addr) {
+ pr_err("KSW: invalid stack var offset %u\n",
+ config->local_var_offset);
+ return -EINVAL;
+ }
+ if (ksw_stack_validate_addr(addr, config->local_var_len)) {
+ pr_err("KSW: invalid stack var len %u\n",
+ config->local_var_len);
+ }
+ len = config->local_var_len;
+ break;
+
+ default:
+ pr_warn("KSW: Unknown watch type %d\n", config->type);
+ return -EINVAL;
+ }
+
+ *watch_addr = addr;
+ *watch_len = len;
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 09/17] mm/ksw: add per-task recursion depth tracking
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (7 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 08/17] mm/ksw: implement stack canary and local var resolution logic Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 10/17] mm/ksw: coordinate watch and stack for full functionality Jinchao Wang
` (7 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Implement depth tracking for KStackWatch to support stack-level filtering.
Each task's recursive entry depth is stored in a global hash table keyed by
pid:
- get_recursive_depth()/set_recursive_depth() manage per-task depth
- reset_recursive_depth() clears all tracked entries
- entry/exit handlers increment or decrement depth and skip if the
current depth does not match the configured depth.
This works even across task scheduling or in interrupt context, since depth
is tracked per-task, ensuring KStackWatch can selectively monitor a
specific recursion level without redundant triggers.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/stack.c | 105 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 103 insertions(+), 2 deletions(-)
diff --git a/mm/kstackwatch/stack.c b/mm/kstackwatch/stack.c
index 1d9814a58fde..fe4cc1352cb1 100644
--- a/mm/kstackwatch/stack.c
+++ b/mm/kstackwatch/stack.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/fprobe.h>
+#include <linux/hashtable.h>
+#include <linux/hash.h>
#include <linux/interrupt.h>
#include <linux/kprobes.h>
#include <linux/percpu.h>
@@ -12,6 +14,81 @@
struct ksw_config *probe_config;
+#define DEPTH_HASH_BITS 8
+#define DEPTH_HASH_SIZE BIT(DEPTH_HASH_BITS)
+
+struct depth_entry {
+ pid_t pid;
+ int depth; /* starts from 0 */
+ struct hlist_node node;
+};
+
+static DEFINE_HASHTABLE(depth_hash, DEPTH_HASH_BITS);
+static DEFINE_SPINLOCK(depth_hash_lock);
+
+static int get_recursive_depth(void)
+{
+ struct depth_entry *entry;
+ pid_t pid = current->pid;
+ int depth = 0;
+
+ spin_lock(&depth_hash_lock);
+ hash_for_each_possible(depth_hash, entry, node,
+ hash_32(pid, DEPTH_HASH_BITS)) {
+ if (entry->pid == pid) {
+ depth = entry->depth;
+ break;
+ }
+ }
+ spin_unlock(&depth_hash_lock);
+ return depth;
+}
+
+static void set_recursive_depth(int depth)
+{
+ struct depth_entry *entry;
+ pid_t pid = current->pid;
+ bool found = false;
+
+ spin_lock(&depth_hash_lock);
+ hash_for_each_possible(depth_hash, entry, node,
+ hash_32(pid, DEPTH_HASH_BITS)) {
+ if (entry->pid == pid) {
+ entry->depth = depth;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found && depth > 0) {
+ entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+ if (entry) {
+ entry->pid = pid;
+ entry->depth = depth;
+ hash_add(depth_hash, &entry->node,
+ hash_32(pid, DEPTH_HASH_BITS));
+ }
+ } else if (found && depth == 0) {
+ hash_del(&entry->node);
+ kfree(entry);
+ }
+ spin_unlock(&depth_hash_lock);
+}
+
+static void reset_recursive_depth(void)
+{
+ struct depth_entry *entry;
+ struct hlist_node *tmp;
+ int bkt;
+
+ spin_lock(&depth_hash_lock);
+ hash_for_each_safe(depth_hash, bkt, tmp, entry, node) {
+ hash_del(&entry->node);
+ kfree(entry);
+ }
+ spin_unlock(&depth_hash_lock);
+}
+
/* Find canary address in current stack frame */
static unsigned long ksw_stack_find_canary(struct pt_regs *regs)
{
@@ -122,10 +199,21 @@ static struct fprobe exit_probe_fprobe;
static void ksw_stack_entry_handler(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
+ int cur_depth;
int ret;
u64 watch_addr;
u64 watch_len;
+ cur_depth = get_recursive_depth();
+ set_recursive_depth(cur_depth + 1);
+
+ /* depth start from 0 */
+ if (cur_depth != probe_config->depth) {
+ pr_info("KSW: config_depth:%u cur_depth:%d entry skipping\n",
+ probe_config->depth, cur_depth);
+ return;
+ }
+
ret = ksw_stack_prepare_watch(regs, probe_config, &watch_addr,
&watch_len);
if (ret) {
@@ -135,8 +223,8 @@ static void ksw_stack_entry_handler(struct kprobe *p, struct pt_regs *regs,
ret = ksw_watch_on(watch_addr, watch_len);
if (ret) {
- pr_err("KSW: failed to watch on addr:0x%llx len:%llx %d\n",
- watch_addr, watch_len, ret);
+ pr_err("KSW: failed to watch on depth:%d addr:0x%llx len:%llx %d\n",
+ cur_depth, watch_addr, watch_len, ret);
return;
}
}
@@ -145,6 +233,17 @@ static void ksw_stack_exit_handler(struct fprobe *fp, unsigned long ip,
unsigned long ret_ip,
struct ftrace_regs *regs, void *data)
{
+ int cur_depth;
+
+ cur_depth = get_recursive_depth() - 1;
+ set_recursive_depth(cur_depth);
+
+ if (cur_depth != probe_config->depth) {
+ pr_info("KSW: config_depth:%u cur_depth:%d exit skipping\n",
+ probe_config->depth, cur_depth);
+ return;
+ }
+
ksw_watch_off();
}
@@ -153,6 +252,8 @@ int ksw_stack_init(struct ksw_config *config)
int ret;
char *symbuf = NULL;
+ reset_recursive_depth();
+
/* Setup entry probe */
memset(&entry_probe, 0, sizeof(entry_probe));
entry_probe.symbol_name = config->function;
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 10/17] mm/ksw: coordinate watch and stack for full functionality
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (8 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 09/17] mm/ksw: add per-task recursion depth tracking Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 11/17] mm/ksw: add self-debug functions for kstackwatch watch Jinchao Wang
` (6 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
This patch connects the watch and stack so that all components function
together.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kernel.c | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/mm/kstackwatch/kernel.c b/mm/kstackwatch/kernel.c
index 95ade95abde1..4c5fbcaddab0 100644
--- a/mm/kstackwatch/kernel.c
+++ b/mm/kstackwatch/kernel.c
@@ -1,10 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
+#include <linux/kern_levels.h>
+#include <linux/kernel.h>
#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/uaccess.h>
+#include <linux/utsname.h>
#include "kstackwatch.h"
@@ -22,6 +25,29 @@ MODULE_PARM_DESC(panic_on_catch,
static int ksw_start_watching(void)
{
+ int ret;
+
+ if (strlen(ksw_config->function) == 0) {
+ pr_err("KSW: no target function specified\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Watch init will preallocate the HWBP,
+ * so it must happen before stack init
+ */
+ ret = ksw_watch_init(ksw_config);
+ if (ret) {
+ pr_err("KSW: ksw_watch_init ret: %d\n", ret);
+ return ret;
+ }
+
+ ret = ksw_stack_init(ksw_config);
+ if (ret) {
+ pr_err("KSW: ksw_stack_init_fprobe ret: %d\n", ret);
+ ksw_watch_exit();
+ return ret;
+ }
watching_active = true;
pr_info("KSW: start watching %s\n", ksw_config->config_str);
@@ -30,6 +56,8 @@ static int ksw_start_watching(void)
static void ksw_stop_watching(void)
{
+ ksw_stack_exit();
+ ksw_watch_exit();
watching_active = false;
pr_info("KSW: stop watching %s\n", ksw_config->config_str);
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 11/17] mm/ksw: add self-debug functions for kstackwatch watch
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (9 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 10/17] mm/ksw: coordinate watch and stack for full functionality Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 12/17] mm/ksw: add test module Jinchao Wang
` (5 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Introduce ksw_watch_show() and ksw_watch_fire() for runtime debugging of
kstackwatch's watch mechanism:
- ksw_watch_show(): prints the currently armed watch address and length
- ksw_watch_fire(): forcibly triggers the watch by writing to the watched
address
These functions help validate the dynamic watch behavior and facilitate
testing without requiring real events.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kstackwatch.h | 2 ++
mm/kstackwatch/watch.c | 17 ++++++++++++++++-
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/mm/kstackwatch/kstackwatch.h b/mm/kstackwatch/kstackwatch.h
index bc8664af4fa6..d1bb5ae75aae 100644
--- a/mm/kstackwatch/kstackwatch.h
+++ b/mm/kstackwatch/kstackwatch.h
@@ -47,5 +47,7 @@ int ksw_watch_init(struct ksw_config *config);
void ksw_watch_exit(void);
int ksw_watch_on(u64 watch_addr, u64 watch_len);
void ksw_watch_off(void);
+void ksw_watch_show(void);
+void ksw_watch_fire(void);
#endif /* _KSTACKWATCH_H */
diff --git a/mm/kstackwatch/watch.c b/mm/kstackwatch/watch.c
index 284facaac8fc..158843876958 100644
--- a/mm/kstackwatch/watch.c
+++ b/mm/kstackwatch/watch.c
@@ -118,7 +118,6 @@ int ksw_watch_on(u64 watch_addr, u64 watch_len)
ksw_watch_on_local_cpu(&watch_info);
} else {
call_single_data_t *csd = &per_cpu(watch_csd, cpu);
-
smp_call_function_single_async(cpu, csd);
}
}
@@ -160,3 +159,19 @@ void ksw_watch_exit(void)
pr_info("KSW: watch exited\n");
}
+
+/* self debug function */
+void ksw_watch_show(void)
+{
+ pr_info("KSW: watch target bp_addr: 0x%llx len:%llu\n",
+ watch_info.addr, watch_info.len);
+}
+
+/* self debug function */
+void ksw_watch_fire(void)
+{
+ char *ptr = (char *)watch_info.addr;
+
+ pr_warn("KSW: watch triggered immediately\n");
+ *ptr = 0x42; // This should trigger immediately
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 12/17] mm/ksw: add test module
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (10 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 11/17] mm/ksw: add self-debug functions for kstackwatch watch Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 13/17] mm/ksw: add stack overflow test Jinchao Wang
` (4 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Introduce a separate test module to validate KStackWatch functionality in
controlled scenarios, such as stack canary writes and simulated corruption.
The module provides a proc interface (/proc/kstackwatch_test) that allows
triggering specific test cases via simple commands:
- test0: directly corrupt the canary to verify watch/fire behavior
This helps development and validation of KStackWatch without affecting
normal kernel operation. Test module is built with optimizations disabled
to ensure predictable behavior.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/Kconfig.debug | 10 +++
mm/kstackwatch/Makefile | 6 +-
mm/kstackwatch/kstackwatch_test.c | 118 ++++++++++++++++++++++++++++++
3 files changed, 133 insertions(+), 1 deletion(-)
create mode 100644 mm/kstackwatch/kstackwatch_test.c
diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug
index c2cc8c7b595f..7fdf7b03d458 100644
--- a/mm/Kconfig.debug
+++ b/mm/Kconfig.debug
@@ -319,3 +319,13 @@ config KSTACK_WATCH
the recursive depth of the monitored function.
If unsure, say N.
+
+config KSTACK_WATCH_TEST
+ tristate "KStackWatch Test Module"
+ depends on KSTACK_WATCH
+ help
+ This module provides controlled stack exhaustion and overflow scenarios
+ to verify the functionality of KStackWatch. It is particularly useful
+ for development and validation of the KStachWatch mechanism.
+
+ If unsure, say N.
diff --git a/mm/kstackwatch/Makefile b/mm/kstackwatch/Makefile
index d422f0e114dd..c04c0780da01 100644
--- a/mm/kstackwatch/Makefile
+++ b/mm/kstackwatch/Makefile
@@ -1,4 +1,8 @@
obj-$(CONFIG_KSTACK_WATCH) += kstackwatch.o
kstackwatch-y := kernel.o stack.o watch.o
-CFLAGS_kernel.o := -Wno-error=unused-function
+obj-$(CONFIG_KSTACK_WATCH_TEST) += kstackwatch_test.o
+CFLAGS_kstackwatch_test.o := -fno-ipa-sra -fno-inline \
+ -fno-optimize-sibling-calls \
+ -fno-section-anchors \
+ -fno-pic -fno-pie -O0 -Og
diff --git a/mm/kstackwatch/kstackwatch_test.c b/mm/kstackwatch/kstackwatch_test.c
new file mode 100644
index 000000000000..bba2ab8530ed
--- /dev/null
+++ b/mm/kstackwatch/kstackwatch_test.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/compiler.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/prandom.h>
+#include <linux/printk.h>
+#include <linux/proc_fs.h>
+#include <linux/random.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include "kstackwatch.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jinchao Wang");
+MODULE_DESCRIPTION("Simple KStackWatch Test Module");
+
+static struct proc_dir_entry *test_proc;
+#define BUFFER_SIZE 4
+#define MAX_DEPTH 4
+
+/*
+ * Test Case 0: Write to the canary position directly (Canary Test)
+ * use a u64 buffer array to ensure the canary will be placed
+ * corrupt the stack canary using the debug function
+ */
+static void canary_test_write(void)
+{
+ u64 buffer[BUFFER_SIZE];
+
+ pr_info("KSW: test: starting %s with u64 write\n", __func__);
+ ksw_watch_show();
+ ksw_watch_fire();
+
+ buffer[0] = 0;
+
+ /* make sure the compiler do not drop assign action */
+ barrier_data(buffer);
+ pr_info("KSW: test: canary write test completed\n");
+}
+
+static ssize_t test_proc_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ char cmd[256];
+ int test_num;
+
+ if (count >= sizeof(cmd))
+ return -EINVAL;
+
+ if (copy_from_user(cmd, buffer, count))
+ return -EFAULT;
+
+ cmd[count] = '\0';
+ strim(cmd);
+
+ pr_info("KSW: test: received command: %s\n", cmd);
+
+ if (sscanf(cmd, "test%d", &test_num) == 1) {
+ switch (test_num) {
+ case 0:
+ pr_info("KSW: test: triggering canary write test\n");
+ canary_test_write();
+ break;
+
+ default:
+ pr_err("KSW: test: Unknown test number %d\n", test_num);
+ return -EINVAL;
+ }
+ } else {
+ pr_err("KSW: test: invalid command format. Use 'test1', 'test2', or 'test3'.\n");
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t test_proc_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ static const char usage[] =
+ "KStackWatch Simplified Test Module\n"
+ "==================================\n"
+ "Usage:\n"
+ " echo 'test0' > /proc/kstackwatch_test - canary write test\n";
+
+ return simple_read_from_buffer(buffer, count, pos, usage,
+ strlen(usage));
+}
+
+static const struct proc_ops test_proc_ops = {
+ .proc_read = test_proc_read,
+ .proc_write = test_proc_write,
+};
+
+static int __init kstackwatch_test_init(void)
+{
+ test_proc = proc_create("kstackwatch_test", 0644, NULL, &test_proc_ops);
+ if (!test_proc) {
+ pr_err("KSW: test: Failed to create proc entry\n");
+ return -ENOMEM;
+ }
+ pr_info("KSW: test: Module loaded, use 'cat /proc/kstackwatch_test' for usage\n");
+ return 0;
+}
+
+static void __exit kstackwatch_test_exit(void)
+{
+ if (test_proc)
+ remove_proc_entry("kstackwatch_test", NULL);
+ pr_info("KSW: test: Module unloaded\n");
+}
+
+module_init(kstackwatch_test_init);
+module_exit(kstackwatch_test_exit);
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 13/17] mm/ksw: add stack overflow test
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (11 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 12/17] mm/ksw: add test module Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 14/17] mm/ksw: add simplified silent corruption test Jinchao Wang
` (3 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Extend the test module with a new test case (test1) that intentionally
overflows a local u64 buffer to corrupt the stack canary. This helps
validate KStackWatch's detection of stack corruption under overflow
conditions.
The proc interface is updated to document the new test:
- test1: stack canary overflow test
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kstackwatch_test.c | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/mm/kstackwatch/kstackwatch_test.c b/mm/kstackwatch/kstackwatch_test.c
index bba2ab8530ed..138163472b03 100644
--- a/mm/kstackwatch/kstackwatch_test.c
+++ b/mm/kstackwatch/kstackwatch_test.c
@@ -42,6 +42,27 @@ static void canary_test_write(void)
pr_info("KSW: test: canary write test completed\n");
}
+/*
+ * Test Case 1: Stack Overflow (Canary Test)
+ * This function uses a u64 buffer 64-bit write
+ * to corrupt the stack canary with a single operation
+ */
+static void canary_test_overflow(void)
+{
+ u64 buffer[BUFFER_SIZE];
+
+ pr_info("KSW: test: starting %s with u64 write\n", __func__);
+ pr_info("KSW: test: buffer 0x%px\n", buffer);
+
+ /* intentionally overflow the u64 buffer. */
+ buffer[BUFFER_SIZE] = 0xdeadbeefdeadbeef;
+
+ /* make sure the compiler do not drop assign action */
+ barrier_data(buffer);
+
+ pr_info("KSW: test: canary overflow test completed\n");
+}
+
static ssize_t test_proc_write(struct file *file, const char __user *buffer,
size_t count, loff_t *pos)
{
@@ -65,7 +86,10 @@ static ssize_t test_proc_write(struct file *file, const char __user *buffer,
pr_info("KSW: test: triggering canary write test\n");
canary_test_write();
break;
-
+ case 1:
+ pr_info("KSW: test: triggering canary overflow test\n");
+ canary_test_overflow();
+ break;
default:
pr_err("KSW: test: Unknown test number %d\n", test_num);
return -EINVAL;
@@ -85,7 +109,8 @@ static ssize_t test_proc_read(struct file *file, char __user *buffer,
"KStackWatch Simplified Test Module\n"
"==================================\n"
"Usage:\n"
- " echo 'test0' > /proc/kstackwatch_test - canary write test\n";
+ " echo 'test0' > /proc/kstackwatch_test - Canary write test\n"
+ " echo 'test1' > /proc/kstackwatch_test - Canary overflow test\n";
return simple_read_from_buffer(buffer, count, pos, usage,
strlen(usage));
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 14/17] mm/ksw: add simplified silent corruption test
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (12 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 13/17] mm/ksw: add stack overflow test Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 15/17] mm/ksw: add recursive " Jinchao Wang
` (2 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Introduce a lightweight test case simulating “silent” stack corruption
where hapless() is unaware its local variable may have been modified.
This test is much simpler than real production scenarios but demonstrates
the core logic.
Test logic:
- buggy(): exposes a local variable via a global pointer without resetting
it, creating a dangling reference.
- unwitting(): a background kernel thread accesses the global pointer and
modifies the pointed memory.
- hapless(): operates on its local variable, unaware it may be modified.
This controlled, minimal scenario provides a simple way to validate
KStackWatch’s detection of unintended stack modifications or silient
corruption.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kstackwatch_test.c | 90 ++++++++++++++++++++++++++++++-
1 file changed, 89 insertions(+), 1 deletion(-)
diff --git a/mm/kstackwatch/kstackwatch_test.c b/mm/kstackwatch/kstackwatch_test.c
index 138163472b03..1f0d616db7c5 100644
--- a/mm/kstackwatch/kstackwatch_test.c
+++ b/mm/kstackwatch/kstackwatch_test.c
@@ -22,6 +22,9 @@ static struct proc_dir_entry *test_proc;
#define BUFFER_SIZE 4
#define MAX_DEPTH 4
+/* global variables for silient corruption test */
+static u64 *g_corrupt_ptr;
+
/*
* Test Case 0: Write to the canary position directly (Canary Test)
* use a u64 buffer array to ensure the canary will be placed
@@ -63,6 +66,86 @@ static void canary_test_overflow(void)
pr_info("KSW: test: canary overflow test completed\n");
}
+static void do_something(int min_ms, int max_ms)
+{
+ u32 rand;
+
+ get_random_bytes(&rand, sizeof(rand));
+ rand = min_ms + rand % (max_ms - min_ms + 1);
+ msleep(rand);
+}
+
+static void silent_corruption_buggy(int i)
+{
+ u64 local_var;
+
+ pr_info("KSW: test: starting %s\n", __func__);
+
+ pr_info("KSW: test: %s %d local_var addr: 0x%px\n", __func__, i,
+ &local_var);
+ WRITE_ONCE(g_corrupt_ptr, &local_var);
+
+ //buggy: return without reset g_corrupt_ptr
+}
+
+static int silent_corruption_unwitting(void *data)
+{
+ pr_debug("KSW: test: starting %s\n", __func__);
+ u64 *local_ptr;
+
+ do {
+ local_ptr = READ_ONCE(g_corrupt_ptr);
+ do_something(0, 300);
+ } while (!local_ptr);
+
+ local_ptr[0] = 0;
+
+ return 0;
+}
+
+static void silent_corruption_hapless(int i)
+{
+ u64 local_var;
+
+ pr_debug("KSW: test: starting %s %d\n", __func__, i);
+ get_random_bytes(&local_var, sizeof(local_var));
+ local_var = 0xff0000 + local_var % 0xffff;
+ pr_debug("KSW: test: %s local_var addr: 0x%px\n", __func__, &local_var);
+
+ do_something(50, 150);
+ if (local_var >= 0xff0000)
+ pr_info("KSW: test: %s %d happy with 0x%llx", __func__, i,
+ local_var);
+ else
+ pr_info("KSW: test: %s %d unhappy with 0x%llx", __func__, i,
+ local_var);
+}
+
+/*
+ * Test Case 2: Silient Corruption
+ * buggy() does not protect its local var correctly
+ * unwitting() simply does its intended work
+ * hapless() is unaware know what happened
+ */
+static void silent_corruption_test(void)
+{
+ struct task_struct *unwitting;
+
+ pr_info("KSW: test: starting %s\n", __func__);
+ WRITE_ONCE(g_corrupt_ptr, NULL);
+
+ unwitting = kthread_run(silent_corruption_unwitting, NULL,
+ "unwitting");
+ if (IS_ERR(unwitting)) {
+ pr_err("KSW: test: failed to create thread2\n");
+ return;
+ }
+
+ silent_corruption_buggy(0);
+ for (int i = 0; i < 10; i++)
+ silent_corruption_hapless(i);
+}
+
static ssize_t test_proc_write(struct file *file, const char __user *buffer,
size_t count, loff_t *pos)
{
@@ -90,6 +173,10 @@ static ssize_t test_proc_write(struct file *file, const char __user *buffer,
pr_info("KSW: test: triggering canary overflow test\n");
canary_test_overflow();
break;
+ case 2:
+ pr_info("KSW: test: triggering silent corruption test\n");
+ silent_corruption_test();
+ break;
default:
pr_err("KSW: test: Unknown test number %d\n", test_num);
return -EINVAL;
@@ -110,7 +197,8 @@ static ssize_t test_proc_read(struct file *file, char __user *buffer,
"==================================\n"
"Usage:\n"
" echo 'test0' > /proc/kstackwatch_test - Canary write test\n"
- " echo 'test1' > /proc/kstackwatch_test - Canary overflow test\n";
+ " echo 'test1' > /proc/kstackwatch_test - Canary overflow test\n"
+ " echo 'test2' > /proc/kstackwatch_test - Silent corruption test\n";
return simple_read_from_buffer(buffer, count, pos, usage,
strlen(usage));
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 15/17] mm/ksw: add recursive corruption test
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (13 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 14/17] mm/ksw: add simplified silent corruption test Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 16/17] tools/kstackwatch: add interactive test script for KStackWatch Jinchao Wang
2025-08-28 7:32 ` [PATCH 17/17] MAINTAINERS: add entry for KStackWatch (Kernel Stack Watch) Jinchao Wang
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Introduce a test case simulating stack corruption across recursive calls.
This scenario writes to a local buffer at every recursion depth up to a
configured maximum, allowing validation that KStackWatch can detect
corruption in nested stack frames.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
mm/kstackwatch/kstackwatch_test.c | 32 ++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
diff --git a/mm/kstackwatch/kstackwatch_test.c b/mm/kstackwatch/kstackwatch_test.c
index 1f0d616db7c5..cb216b6ee5d7 100644
--- a/mm/kstackwatch/kstackwatch_test.c
+++ b/mm/kstackwatch/kstackwatch_test.c
@@ -146,6 +146,30 @@ static void silent_corruption_test(void)
silent_corruption_hapless(i);
}
+/*
+ * Test Case 3: Recursive Call Corruption
+ * Check whether KStackWatch can handle corruption in a recursive call
+ * Write the local variable at every depth
+ * Configure /proc/kstackwatch to specify the corruption depth
+ * Verify that the watch is triggered
+ */
+static void recursive_corruption_test(int depth)
+{
+ u64 buffer[BUFFER_SIZE];
+
+ pr_info("KSW: test: recursive call at depth %d\n", depth);
+ pr_info("KSW: test: buffer 0x%px\n", buffer);
+ if (depth <= MAX_DEPTH)
+ recursive_corruption_test(depth + 1);
+
+ buffer[0] = depth;
+
+ /* make sure the compiler do not drop assign action */
+ barrier_data(buffer);
+
+ pr_info("KSW: test: returning from depth %d\n", depth);
+}
+
static ssize_t test_proc_write(struct file *file, const char __user *buffer,
size_t count, loff_t *pos)
{
@@ -177,6 +201,11 @@ static ssize_t test_proc_write(struct file *file, const char __user *buffer,
pr_info("KSW: test: triggering silent corruption test\n");
silent_corruption_test();
break;
+ case 3:
+ pr_info("KSW: test: triggering recursive corruption test\n");
+ /* depth start with 0 */
+ recursive_corruption_test(0);
+ break;
default:
pr_err("KSW: test: Unknown test number %d\n", test_num);
return -EINVAL;
@@ -198,7 +227,8 @@ static ssize_t test_proc_read(struct file *file, char __user *buffer,
"Usage:\n"
" echo 'test0' > /proc/kstackwatch_test - Canary write test\n"
" echo 'test1' > /proc/kstackwatch_test - Canary overflow test\n"
- " echo 'test2' > /proc/kstackwatch_test - Silent corruption test\n";
+ " echo 'test2' > /proc/kstackwatch_test - Silent corruption test\n"
+ " echo 'test3' > /proc/kstackwatch_test - Recursive corruption test\n";
return simple_read_from_buffer(buffer, count, pos, usage,
strlen(usage));
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 16/17] tools/kstackwatch: add interactive test script for KStackWatch
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (14 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 15/17] mm/ksw: add recursive " Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
2025-08-28 7:32 ` [PATCH 17/17] MAINTAINERS: add entry for KStackWatch (Kernel Stack Watch) Jinchao Wang
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Introduce kstackwatch_test.sh, a shell script to facilitate running various
test scenarios interactively or via command-line arguments.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
tools/kstackwatch/kstackwatch_test.sh | 118 ++++++++++++++++++++++++++
1 file changed, 118 insertions(+)
create mode 100644 tools/kstackwatch/kstackwatch_test.sh
diff --git a/tools/kstackwatch/kstackwatch_test.sh b/tools/kstackwatch/kstackwatch_test.sh
new file mode 100644
index 000000000000..af0fbdb3a975
--- /dev/null
+++ b/tools/kstackwatch/kstackwatch_test.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# --- Usage function ---
+usage() {
+ echo "======================================"
+ echo " KStackWatch Test Script Usage"
+ echo "======================================"
+ echo ""
+ echo "IMPORTANT: Before running, make sure you have updated the offset values!"
+ echo ""
+ echo "To find your offsets, use objdump:"
+ echo " objdump -S --disassemble=canary_test_write vmlinux"
+ echo ""
+ echo "Then search for your function names to find the instruction addresses."
+ echo "- Instruction offset: address relative to function's start"
+ echo "- Stack var offset: distance from stack base (%rbp) to the variable"
+ echo ""
+ echo "Usage: $0 [test_case_number]"
+ echo ""
+ echo "Available test cases:"
+ echo " 0 - Canary Write Test"
+ echo " 1 - Canary Overflow Test"
+ echo " 2 - Silient Corruption Test"
+ echo " 3 - Recursive Corruption Test"
+ echo ""
+ echo "======================================"
+ echo ""
+}
+
+# --- Interactive menu ---
+show_menu() {
+ echo "Select a test case to run:"
+ echo " 0) Canary Write Test"
+ echo " 1) Canary Overflow Test"
+ echo " 2) Silient Corruption Test"
+ echo " 3) Recursive Corruption Test"
+ echo " q) Quit"
+ echo ""
+ echo "WARNING: Each test may cause system crash/hang!"
+ echo ""
+ read -p "Enter your choice [0-3/q]: " choice
+ echo ""
+
+ case "$choice" in
+ 0) test0 ;;
+ 1) test1 ;;
+ 2) test2 ;;
+ 3) test3 ;;
+ q | Q)
+ echo "Exiting..."
+ exit 0
+ ;;
+ *)
+ echo "Invalid choice. Please try again."
+ echo ""
+ show_menu
+ ;;
+ esac
+}
+
+# --- Test Case 0: Canary Write ---
+test0() {
+ echo "=== Running Test Case 0: Canary Write ==="
+ # function+instruction_off[+depth] [local_var_offset:local_var_len]
+ echo "canary_test_write+0x12" >/proc/kstackwatch
+ echo "test0" >/proc/kstackwatch_test
+ echo >/proc/kstackwatch
+}
+
+# --- Test Case 1: Canary Overflow ---
+test1() {
+ echo "=== Running Test Case 1: Canary Overflow ==="
+ # function+instruction_off[+depth] [local_var_offset:local_var_len]
+ echo "canary_test_overflow+0x12" >/proc/kstackwatch
+ echo "test1" >/proc/kstackwatch_test
+ echo >/proc/kstackwatch
+
+}
+
+# --- Test Case 2: Silient Corruption ---
+test2() {
+ echo "=== Running Test Case 2: Silient Corruption ==="
+ # function+instruction_off[+depth] [local_var_offset:local_var_len]
+ echo "silent_corruption_hapless+0x7f 0:8" >/proc/kstackwatch
+ echo "test2" >/proc/kstackwatch_test
+ echo >/proc/kstackwatch
+}
+
+# --- Test Case 3: Recursive Corruption ---
+test3() {
+ echo "=== Running Test Case 3: Recursive Corruption ==="
+ # function+instruction_off[+depth] [local_var_offset:local_var_len]
+ echo "recursive_corruption_test+0x1b+3 0:8" >/proc/kstackwatch
+ echo "test3" >/proc/kstackwatch_test
+ echo >/proc/kstackwatch
+}
+
+# --- Main ---
+if [ -z "$1" ]; then
+ usage
+ echo ""
+ show_menu
+else
+ case "$1" in
+ 0) test0 ;;
+ 1) test1 ;;
+ 2) test2 ;;
+ 3) test3 ;;
+ help | --help | -h) usage ;;
+ *)
+ echo "Error: Invalid argument '$1'"
+ echo ""
+ usage
+ exit 1
+ ;;
+ esac
+fi
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 17/17] MAINTAINERS: add entry for KStackWatch (Kernel Stack Watch)
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
` (15 preceding siblings ...)
2025-08-28 7:32 ` [PATCH 16/17] tools/kstackwatch: add interactive test script for KStackWatch Jinchao Wang
@ 2025-08-28 7:32 ` Jinchao Wang
16 siblings, 0 replies; 18+ messages in thread
From: Jinchao Wang @ 2025-08-28 7:32 UTC (permalink / raw)
To: Andrew Morton, Masami Hiramatsu, Naveen N . Rao, linux-mm,
linux-trace-kernel
Cc: linux-kernel, Jinchao Wang
Add Jinchao Wang as the maintainer for KStackWatch, including its kernel
module and test script.
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
---
MAINTAINERS | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index fed6cd812d79..097a0462e604 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13354,6 +13354,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git
F: Documentation/dev-tools/kselftest*
F: tools/testing/selftests/
+KERNEL STACK WATCH
+M: Jinchao Wang <wangjinchao600@gmail.com>
+S: Maintained
+F: mm/kstackwatch/
+F: tools/kstackwatch/
+
KERNEL SMB3 SERVER (KSMBD)
M: Namjae Jeon <linkinjeon@kernel.org>
M: Namjae Jeon <linkinjeon@samba.org>
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
end of thread, other threads:[~2025-08-28 7:35 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-28 7:32 [PATCH 00/17] mm/ksw: Introduce real-time Kernel Stack Watch debugging tool Jinchao Wang
2025-08-28 7:32 ` [PATCH 01/17] mm/ksw: add build system support Jinchao Wang
2025-08-28 7:32 ` [PATCH 02/17] mm/ksw: add ksw_config struct and parser Jinchao Wang
2025-08-28 7:32 ` [PATCH 03/17] mm/ksw: add /proc/kstackwatch interface Jinchao Wang
2025-08-28 7:32 ` [PATCH 04/17] mm/ksw: add HWBP pre-allocation support Jinchao Wang
2025-08-28 7:32 ` [PATCH 05/17] x86/HWBP: introduce arch_reinstall_hw_breakpoint() for atomic context Jinchao Wang
2025-08-28 7:32 ` [PATCH 06/17] mm/ksw: add atomic watch on/off operations Jinchao Wang
2025-08-28 7:32 ` [PATCH 07/17] mm/ksw: add stack probe support Jinchao Wang
2025-08-28 7:32 ` [PATCH 08/17] mm/ksw: implement stack canary and local var resolution logic Jinchao Wang
2025-08-28 7:32 ` [PATCH 09/17] mm/ksw: add per-task recursion depth tracking Jinchao Wang
2025-08-28 7:32 ` [PATCH 10/17] mm/ksw: coordinate watch and stack for full functionality Jinchao Wang
2025-08-28 7:32 ` [PATCH 11/17] mm/ksw: add self-debug functions for kstackwatch watch Jinchao Wang
2025-08-28 7:32 ` [PATCH 12/17] mm/ksw: add test module Jinchao Wang
2025-08-28 7:32 ` [PATCH 13/17] mm/ksw: add stack overflow test Jinchao Wang
2025-08-28 7:32 ` [PATCH 14/17] mm/ksw: add simplified silent corruption test Jinchao Wang
2025-08-28 7:32 ` [PATCH 15/17] mm/ksw: add recursive " Jinchao Wang
2025-08-28 7:32 ` [PATCH 16/17] tools/kstackwatch: add interactive test script for KStackWatch Jinchao Wang
2025-08-28 7:32 ` [PATCH 17/17] MAINTAINERS: add entry for KStackWatch (Kernel Stack Watch) Jinchao Wang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).