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 <[email protected]>
---
 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


Reply via email to