From nobody Fri Oct 3 11:15:09 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B5BDB343D84; Mon, 1 Sep 2025 14:44:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737891; cv=none; b=ewmoV3slU+/VXeDDDQe3pi5zR9aBwALClTfWTl2K/S9X90iVMyvG/AgLckD1GzIZb+Qf+g4wkjx7s+nxzOF/MsdONEo3yMiNuIQDco30sELKpPcnH4/9aWFW//02QrXdsyradCFyeLjcD2sBk2PToDvtYRaQY8cUcqdtdJB0v04= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737891; c=relaxed/simple; bh=TexR1Jlu74yN1ClKsSqnkr3UhwJH7VO/18tTFV1286U=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=RFJq+Stj/6bH8VRac4Z5knv5hM6DI0Li2DXGlAfJ8F82Qe1IvYIirZFFAq1vKSsroBjyN4wkUDZX1jmmvFhEKjYt7RBr5Xu6/pSYyv0AUuJmk3PtKDkL1ahjhRiXq6QByoRhz7dPM1nNhc9djjsEOOM0jOE8Mi0KeRP5P9kaZoI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=eNldhXmY; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="eNldhXmY" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 100D9C4CEF0; Mon, 1 Sep 2025 14:44:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1756737891; bh=TexR1Jlu74yN1ClKsSqnkr3UhwJH7VO/18tTFV1286U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eNldhXmY8acUlBR8Njbl8JRj0SS+md022UrpPYRAJPnG+dP2GxXmo1BlBejeoHf6Y i8FaZW/6N7tZVZatvpwP0+VnvtIBUMCIbpFIrE5+exY7H0RfQgAeheqmzYXW1P//wY 02CJcxo/VuQ90JU9NECf6AtvhcXaDDaQg1sABR6N0pYC7ON7p/8BAItsJXJa0EAE8u fdbAPY5ZEsEkc9iePWkWpeNkMx29hF07s6p12iRx8s8TFgyGstyMZ0Sk82fO9bXwjF 0lkH6MvQTyg7o67iSX0w4Jyj2Q++VSYQHmq8vReTRuZF6gMDtw4f5jt9NDt6a3tM8c oVL3ZzBtmg6tw== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [RFC PATCH 1/6] tracing: wprobe: Add watchpoint probe event based on hardware breakpoint Date: Mon, 1 Sep 2025 23:44:45 +0900 Message-ID: <175673788575.478080.17611922836391707019.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <175673787502.478080.3342912952394010967.stgit@devnote2> References: <175673787502.478080.3342912952394010967.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add a new probe event for the hardware breakpoint called wprobe-event. This wprobe allows user to trace (watch) the memory access at the specified memory address. The new syntax is; w[:[GROUP/]EVENT] [r|w|rw]@[ADDR|SYM][:SIZE] [FETCH_ARGs] User also can use $addr to fetch the accessed address. But no other variables are supported. To record updated value, use '+0($addr)'. For example, tracing updates of the jiffies; /sys/kernel/tracing # echo 'w:my_jiffies w@jiffies' >> dynamic_events /sys/kernel/tracing # cat dynamic_events w:wprobes/my_jiffies w@jiffies:4 /sys/kernel/tracing # echo 1 > events/wprobes/my_jiffies/enable /sys/kernel/tracing # head -n 20 trace | tail -n 5 # TASK-PID CPU# ||||| TIMESTAMP FUNCTION # | | | ||||| | | -0 [000] d.Z1. 206.547317: my_jiffies: (tick_do_upd= ate_jiffies64+0xbe/0x130) -0 [000] d.Z1. 206.548341: my_jiffies: (tick_do_upd= ate_jiffies64+0xbe/0x130) -0 [000] d.Z1. 206.549346: my_jiffies: (tick_do_upd= ate_jiffies64+0xbe/0x130) Signed-off-by: Masami Hiramatsu (Google) --- Documentation/trace/index.rst | 1=20 Documentation/trace/wprobetrace.rst | 69 ++++ include/linux/trace_events.h | 2=20 kernel/trace/Kconfig | 14 + kernel/trace/Makefile | 1=20 kernel/trace/trace.c | 9=20 kernel/trace/trace.h | 5=20 kernel/trace/trace_probe.c | 20 + kernel/trace/trace_probe.h | 8=20 kernel/trace/trace_wprobe.c | 685 +++++++++++++++++++++++++++++++= ++++ 10 files changed, 811 insertions(+), 3 deletions(-) create mode 100644 Documentation/trace/wprobetrace.rst create mode 100644 kernel/trace/trace_wprobe.c diff --git a/Documentation/trace/index.rst b/Documentation/trace/index.rst index b4a429dc4f7a..14de6858ae1b 100644 --- a/Documentation/trace/index.rst +++ b/Documentation/trace/index.rst @@ -36,6 +36,7 @@ the Linux kernel. kprobes kprobetrace fprobetrace + wprobetrace eprobetrace fprobe ring-buffer-design diff --git a/Documentation/trace/wprobetrace.rst b/Documentation/trace/wpro= betrace.rst new file mode 100644 index 000000000000..9774f57e2947 --- /dev/null +++ b/Documentation/trace/wprobetrace.rst @@ -0,0 +1,69 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Watchpoint probe (wprobe) Event Tracing +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +.. Author: Masami Hiramatsu + +Overview +-------- + +Wprobe event is a dynamic event based on the hardware breakpoint, which is +similar to other probe events, but it is for watching data access. It allo= ws +you to trace which code accesses a specified data. + +As same as other dynamic events, wprobe events are defined via +`dynamic_events` interface file on tracefs. + +Synopsis of wprobe-events +------------------------- +:: + + w:[GRP/][EVENT] SPEC [FETCHARGS] : Probe on data a= ccess + + GRP : Group name for wprobe. If omitted, use "wprobes" for it. + EVENT : Event name for wprobe. If omitted, an event name is + generated based on the address or symbol. + SPEC : Breakpoint specification. + [r|w|rw]@[:LENGTH] + + r|w|rw : Access type, r for read, w for write, and rw for both. + Use rw if omitted. + ADDRESS : Address to trace (hexadecimal). + SYMBOL : Symbol name to trace. + LENGTH : Length of the data to trace in bytes. (1, 2, 4, or 8) + + FETCHARGS : Arguments. Each probe can have up to 128 args. + $addr : Fetch the accessing address. + @ADDR : Fetch memory at ADDR (ADDR should be in kernel) + @SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbo= l) + +|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*1)(\= *2) + \IMM : Store an immediate value to the argument. + NAME=3DFETCHARG : Set NAME as the argument name of FETCHARG. + FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types + (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types + (x8/x16/x32/x64), "char", "string", "ustring", "symbol",= "symstr" + and bitfield are supported. + + (\*1) this is useful for fetching a field of data structures. + (\*2) "u" means user-space dereference. + +For the details of TYPE, see :ref:`kprobetrace documentation `. + +Usage examples +-------------- +Here is an example to add a wprobe event on a variable `jiffies`. +:: + + # echo 'w:my_jiffies w@jiffies' >> dynamic_events + # cat dynamic_events + w:wprobes/my_jiffies w@jiffies + # echo 1 > events/wprobes/enable + # cat trace | head + # TASK-PID CPU# ||||| TIMESTAMP FUNCTION + # | | | ||||| | | + -0 [000] d.Z1. 717.026259: my_jiffies: (tick_do_up= date_jiffies64+0xbe/0x130) + -0 [000] d.Z1. 717.026373: my_jiffies: (tick_do_up= date_jiffies64+0xbe/0x130) + +You can see the code which writes to `jiffies` is `do_timer()`. diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 04307a19cde3..7c65f2f73ff4 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -324,6 +324,7 @@ enum { TRACE_EVENT_FL_UPROBE_BIT, TRACE_EVENT_FL_EPROBE_BIT, TRACE_EVENT_FL_FPROBE_BIT, + TRACE_EVENT_FL_WPROBE_BIT, TRACE_EVENT_FL_CUSTOM_BIT, TRACE_EVENT_FL_TEST_STR_BIT, }; @@ -354,6 +355,7 @@ enum { TRACE_EVENT_FL_UPROBE =3D (1 << TRACE_EVENT_FL_UPROBE_BIT), TRACE_EVENT_FL_EPROBE =3D (1 << TRACE_EVENT_FL_EPROBE_BIT), TRACE_EVENT_FL_FPROBE =3D (1 << TRACE_EVENT_FL_FPROBE_BIT), + TRACE_EVENT_FL_WPROBE =3D (1 << TRACE_EVENT_FL_WPROBE_BIT), TRACE_EVENT_FL_CUSTOM =3D (1 << TRACE_EVENT_FL_CUSTOM_BIT), TRACE_EVENT_FL_TEST_STR =3D (1 << TRACE_EVENT_FL_TEST_STR_BIT), }; diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index d2c79da81e4f..dd8919386425 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -807,6 +807,20 @@ config EPROBE_EVENTS convert the type of an event field. For example, turn an address into a string. =20 +config WPROBE_EVENTS + bool "Enable wprobe-based dynamic events" + depends on TRACING + depends on HAVE_HW_BREAKPOINT + select PROBE_EVENTS + select DYNAMIC_EVENTS + default y + help + This allows the user to add watchpoint tracing events based on + hardware breakpoints on the fly via the ftrace interface. + + Those events can be inserted wherever hardware breakpoints can be + set, and record various register and memory values. + config BPF_EVENTS depends on BPF_SYSCALL depends on (KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index dcb4e02afc5f..1d57bb36c5fc 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile @@ -106,6 +106,7 @@ obj-$(CONFIG_FTRACE_RECORD_RECURSION) +=3D trace_recurs= ion_record.o obj-$(CONFIG_FPROBE) +=3D fprobe.o obj-$(CONFIG_RETHOOK) +=3D rethook.o obj-$(CONFIG_FPROBE_EVENTS) +=3D trace_fprobe.o +obj-$(CONFIG_WPROBE_EVENTS) +=3D trace_wprobe.o =20 obj-$(CONFIG_TRACEPOINT_BENCHMARK) +=3D trace_benchmark.o obj-$(CONFIG_RV) +=3D rv/ diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 4283ed4e8f59..ac2fc6d768ad 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -5506,8 +5506,12 @@ static const char readme_msg[] =3D " uprobe_events\t\t- Create/append/remove/show the userspace dynamic eve= nts\n" "\t\t\t Write into this file to define/undefine new trace events.\n" #endif +#ifdef CONFIG_WPROBE_EVENTS + " wprobe_events\t\t- Create/append/remove/show the hardware breakpoint d= ynamic events\n" + "\t\t\t Write into this file to define/undefine new trace events.\n" +#endif #if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS) || \ - defined(CONFIG_FPROBE_EVENTS) + defined(CONFIG_FPROBE_EVENTS) || defined(CONFIG_WPROBE_EVENTS) "\t accepts: event-definitions (one definition per line)\n" #if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS) "\t Format: p[:[/][]] []\n" @@ -5517,6 +5521,9 @@ static const char readme_msg[] =3D "\t f[:[/][]] [%return] []\n" "\t t[:[/][]] []\n" #endif +#ifdef CONFIG_WPROBE_EVENTS + "\t w[:[/][]] [r|w|rw]@[:]\n" +#endif #ifdef CONFIG_HIST_TRIGGERS "\t s:[synthetic/] []\n" #endif diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 1dbf1d3cf2f1..ae175ddc3f58 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -173,6 +173,11 @@ struct fexit_trace_entry_head { unsigned long ret_ip; }; =20 +struct wprobe_trace_entry_head { + struct trace_entry ent; + unsigned long ip; +}; + #define TRACE_BUF_SIZE 1024 =20 struct trace_array; diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 5b92376a58fc..ef500c373f3c 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -968,6 +968,24 @@ static int parse_probe_vars(char *orig_arg, const stru= ct fetch_type *t, goto inval; } =20 + /* wprobe only support "$addr" and "$value" variable */ + if (ctx->flags & TPARG_FL_WPROBE) { + if (!strcmp(arg, "addr")) { + code->op =3D FETCH_OP_BADDR; + return 0; + } + if (!strcmp(arg, "value")) { + code->op =3D FETCH_OP_BADDR; + code++; + code->op =3D FETCH_OP_DEREF; + code->offset =3D 0; + *pcode =3D code; + return 0; + } + err =3D TP_ERR_BAD_VAR; + goto inval; + } + if (str_has_prefix(arg, "retval")) { if (!(ctx->flags & TPARG_FL_RETURN)) { err =3D TP_ERR_RETVAL_ON_PROBE; @@ -1098,7 +1116,7 @@ parse_probe_arg(char *arg, const struct fetch_type *t= ype, break; =20 case '%': /* named register */ - if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) { + if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE | TPARG_FL_WPROBE)) { /* eprobe and fprobe do not handle registers */ trace_probe_log_err(ctx->offset, BAD_VAR); break; diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 76bf2dee8071..13c9dbc4d0e2 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -89,6 +89,7 @@ enum fetch_op { FETCH_OP_STACK, /* Stack : .param =3D index */ FETCH_OP_STACKP, /* Stack pointer */ FETCH_OP_RETVAL, /* Return value */ + FETCH_OP_BADDR, /* Break address */ FETCH_OP_IMM, /* Immediate : .immediate */ FETCH_OP_COMM, /* Current comm */ FETCH_OP_ARG, /* Function argument : .param */ @@ -396,6 +397,7 @@ static inline int traceprobe_get_entry_data_size(struct= trace_probe *tp) #define TPARG_FL_USER BIT(4) #define TPARG_FL_FPROBE BIT(5) #define TPARG_FL_TPOINT BIT(6) +#define TPARG_FL_WPROBE BIT(7) #define TPARG_FL_LOC_MASK GENMASK(4, 0) =20 static inline bool tparg_is_function_entry(unsigned int flags) @@ -556,7 +558,11 @@ extern int traceprobe_define_arg_fields(struct trace_e= vent_call *event_call, C(BAD_TYPE4STR, "This type does not fit for string."),\ C(NEED_STRING_TYPE, "$comm and immediate-string only accepts string type"= ),\ C(TOO_MANY_ARGS, "Too many arguments are specified"), \ - C(TOO_MANY_EARGS, "Too many entry arguments specified"), + C(TOO_MANY_EARGS, "Too many entry arguments specified"), \ + C(BAD_ACCESS_FMT, "Access memory address requires @"), \ + C(BAD_ACCESS_TYPE, "Bad memory access type"), \ + C(BAD_ACCESS_LEN, "This memory access length is not supported"), \ + C(BAD_ACCESS_ADDR, "Invalid access memory address"), =20 #undef C #define C(a, b) TP_ERR_##a diff --git a/kernel/trace/trace_wprobe.c b/kernel/trace/trace_wprobe.c new file mode 100644 index 000000000000..4b00a8e917c1 --- /dev/null +++ b/kernel/trace/trace_wprobe.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Hardware-breakpoint-based tracing events + * + * Copyright (C) 2023, Masami Hiramatsu + */ +#define pr_fmt(fmt) "trace_wprobe: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "trace_dynevent.h" +#include "trace_probe.h" +#include "trace_probe_kernel.h" +#include "trace_probe_tmpl.h" + +#define WPROBE_EVENT_SYSTEM "wprobes" + +static int trace_wprobe_create(const char *raw_command); +static int trace_wprobe_show(struct seq_file *m, struct dyn_event *ev); +static int trace_wprobe_release(struct dyn_event *ev); +static bool trace_wprobe_is_busy(struct dyn_event *ev); +static bool trace_wprobe_match(const char *system, const char *event, + int argc, const char **argv, struct dyn_event *ev); + +static struct dyn_event_operations trace_wprobe_ops =3D { + .create =3D trace_wprobe_create, + .show =3D trace_wprobe_show, + .is_busy =3D trace_wprobe_is_busy, + .free =3D trace_wprobe_release, + .match =3D trace_wprobe_match, +}; + +struct trace_wprobe { + struct dyn_event devent; + struct perf_event * __percpu *bp_event; + unsigned long addr; + int len; + int type; + const char *symbol; + struct trace_probe tp; +}; + +static bool is_trace_wprobe(struct dyn_event *ev) +{ + return ev->ops =3D=3D &trace_wprobe_ops; +} + +static struct trace_wprobe *to_trace_wprobe(struct dyn_event *ev) +{ + return container_of(ev, struct trace_wprobe, devent); +} + +#define for_each_trace_wprobe(pos, dpos) \ + for_each_dyn_event(dpos) \ + if (is_trace_wprobe(dpos) && (pos =3D to_trace_wprobe(dpos))) + +static bool trace_wprobe_is_busy(struct dyn_event *ev) +{ + struct trace_wprobe *tw =3D to_trace_wprobe(ev); + + return trace_probe_is_enabled(&tw->tp); +} + +static bool trace_wprobe_match(const char *system, const char *event, + int argc, const char **argv, struct dyn_event *ev) +{ + struct trace_wprobe *tw =3D to_trace_wprobe(ev); + + if (event[0] !=3D '\0' && strcmp(trace_probe_name(&tw->tp), event)) + return false; + + if (system && strcmp(trace_probe_group_name(&tw->tp), system)) + return false; + + /* TODO: match arguments */ + return true; +} + +/* + * Note that we don't verify the fetch_insn code, since it does not come + * from user space. + */ +static int +process_fetch_insn(struct fetch_insn *code, void *rec, void *edata, + void *dest, void *base) +{ + void *baddr =3D rec; + unsigned long val; + int ret; + +retry: + /* 1st stage: get value from context */ + switch (code->op) { + case FETCH_OP_BADDR: + val =3D (unsigned long)baddr; + break; + case FETCH_NOP_SYMBOL: /* Ignore a place holder */ + code++; + goto retry; + default: + ret =3D process_common_fetch_insn(code, &val); + if (ret < 0) + return ret; + } + code++; + + return process_fetch_insn_bottom(code, val, dest, base); +} +NOKPROBE_SYMBOL(process_fetch_insn) + +static void wprobe_trace_handler(struct trace_wprobe *tw, + struct perf_sample_data *data, + struct pt_regs *regs, + struct trace_event_file *trace_file) +{ + struct wprobe_trace_entry_head *entry; + struct trace_event_call *call =3D trace_probe_event_call(&tw->tp); + struct trace_event_buffer fbuffer; + int dsize; + + if (WARN_ON_ONCE(call !=3D trace_file->event_call)) + return; + + if (trace_trigger_soft_disabled(trace_file)) + return; + + dsize =3D __get_data_size(&tw->tp, (void *)tw->addr, NULL); + + entry =3D trace_event_buffer_reserve(&fbuffer, trace_file, + sizeof(*entry) + tw->tp.size + dsize); + if (!entry) + return; + + entry->ip =3D instruction_pointer(regs); + store_trace_args(&entry[1], &tw->tp, (void *)tw->addr, NULL, sizeof(*entr= y), dsize); + + fbuffer.regs =3D regs; + trace_event_buffer_commit(&fbuffer); +} + +static void wprobe_perf_handler(struct perf_event *bp, + struct perf_sample_data *data, + struct pt_regs *regs) +{ + struct trace_wprobe *tw =3D bp->overflow_handler_context; + struct event_file_link *link; + + trace_probe_for_each_link_rcu(link, &tw->tp) + wprobe_trace_handler(tw, data, regs, link->file); +} + +static int __register_trace_wprobe(struct trace_wprobe *tw) +{ + struct perf_event_attr attr; + + if (tw->bp_event) + return -EINVAL; + + hw_breakpoint_init(&attr); + attr.bp_addr =3D tw->addr; + attr.bp_len =3D tw->len; + attr.bp_type =3D tw->type; + + tw->bp_event =3D register_wide_hw_breakpoint(&attr, wprobe_perf_handler, = tw); + if (IS_ERR((void * __force)tw->bp_event)) { + int ret =3D PTR_ERR((void * __force)tw->bp_event); + + tw->bp_event =3D NULL; + return ret; + } + + return 0; +} + +static void __unregister_trace_wprobe(struct trace_wprobe *tw) +{ + if (tw->bp_event) { + unregister_wide_hw_breakpoint(tw->bp_event); + tw->bp_event =3D NULL; + } +} + +static void free_trace_wprobe(struct trace_wprobe *tw) +{ + if (tw) { + trace_probe_cleanup(&tw->tp); + kfree(tw->symbol); + kfree(tw); + } +} +DEFINE_FREE(free_trace_wprobe, struct trace_wprobe *, if (!IS_ERR_OR_NULL(= _T)) free_trace_wprobe(_T)); + +static struct trace_wprobe *alloc_trace_wprobe(const char *group, + const char *event, + const char *symbol, + unsigned long addr, + int len, int type, int nargs) +{ + struct trace_wprobe *tw __free(free_trace_wprobe) =3D NULL; + int ret; + + tw =3D kzalloc(struct_size(tw, tp.args, nargs), GFP_KERNEL); + if (!tw) + return ERR_PTR(-ENOMEM); + + if (symbol) { + tw->symbol =3D kstrdup(symbol, GFP_KERNEL); + if (!tw->symbol) + return ERR_PTR(-ENOMEM); + } + tw->addr =3D addr; + tw->len =3D len; + tw->type =3D type; + + ret =3D trace_probe_init(&tw->tp, event, group, false, nargs); + if (ret < 0) + return ERR_PTR(ret); + + dyn_event_init(&tw->devent, &trace_wprobe_ops); + return_ptr(tw); +} + +static struct trace_wprobe *find_trace_wprobe(const char *event, + const char *group) +{ + struct dyn_event *pos; + struct trace_wprobe *tw; + + for_each_trace_wprobe(tw, pos) + if (strcmp(trace_probe_name(&tw->tp), event) =3D=3D 0 && + strcmp(trace_probe_group_name(&tw->tp), group) =3D=3D 0) + return tw; + return NULL; +} + +static enum print_line_t +print_wprobe_event(struct trace_iterator *iter, int flags, + struct trace_event *event) +{ + struct wprobe_trace_entry_head *field; + struct trace_seq *s =3D &iter->seq; + struct trace_probe *tp; + + field =3D (struct wprobe_trace_entry_head *)iter->ent; + tp =3D trace_probe_primary_from_call( + container_of(event, struct trace_event_call, event)); + if (WARN_ON_ONCE(!tp)) + goto out; + + trace_seq_printf(s, "%s: (", trace_probe_name(tp)); + + if (!seq_print_ip_sym(s, field->ip, flags | TRACE_ITER_SYM_OFFSET)) + goto out; + + trace_seq_putc(s, ')'); + + if (trace_probe_print_args(s, tp->args, tp->nr_args, + (u8 *)&field[1], field) < 0) + goto out; + + trace_seq_putc(s, '\n'); +out: + return trace_handle_return(s); +} + +static int wprobe_event_define_fields(struct trace_event_call *event_call) +{ + int ret; + struct wprobe_trace_entry_head field; + struct trace_probe *tp; + + tp =3D trace_probe_primary_from_call(event_call); + if (WARN_ON_ONCE(!tp)) + return -ENOENT; + + DEFINE_FIELD(unsigned long, ip, FIELD_STRING_IP, 0); + + return traceprobe_define_arg_fields(event_call, sizeof(field), tp); +} + +static struct trace_event_functions wprobe_funcs =3D { + .trace =3D print_wprobe_event +}; + +static struct trace_event_fields wprobe_fields_array[] =3D { + { .type =3D TRACE_FUNCTION_TYPE, + .define_fields =3D wprobe_event_define_fields }, + {} +}; + +static int wprobe_register(struct trace_event_call *event, + enum trace_reg type, void *data); + +static inline void init_trace_event_call(struct trace_wprobe *tw) +{ + struct trace_event_call *call =3D trace_probe_event_call(&tw->tp); + + call->event.funcs =3D &wprobe_funcs; + call->class->fields_array =3D wprobe_fields_array; + call->flags =3D TRACE_EVENT_FL_WPROBE; + call->class->reg =3D wprobe_register; +} + +static int register_wprobe_event(struct trace_wprobe *tw) +{ + init_trace_event_call(tw); + return trace_probe_register_event_call(&tw->tp); +} + +static int register_trace_wprobe_event(struct trace_wprobe *tw) +{ + struct trace_wprobe *old_tb; + int ret; + + guard(mutex)(&event_mutex); + + old_tb =3D find_trace_wprobe(trace_probe_name(&tw->tp), + trace_probe_group_name(&tw->tp)); + if (old_tb) + return -EBUSY; + + ret =3D register_wprobe_event(tw); + if (ret) + return ret; + + dyn_event_add(&tw->devent, trace_probe_event_call(&tw->tp)); + return 0; +} +static int unregister_wprobe_event(struct trace_wprobe *tw) +{ + return trace_probe_unregister_event_call(&tw->tp); +} + +static int unregister_trace_wprobe(struct trace_wprobe *tw) +{ + if (trace_probe_has_sibling(&tw->tp)) + goto unreg; + + if (trace_probe_is_enabled(&tw->tp)) + return -EBUSY; + + if (trace_event_dyn_busy(trace_probe_event_call(&tw->tp))) + return -EBUSY; + + if (unregister_wprobe_event(tw)) + return -EBUSY; + +unreg: + __unregister_trace_wprobe(tw); + dyn_event_remove(&tw->devent); + trace_probe_unlink(&tw->tp); + + return 0; +} + +static int enable_trace_wprobe(struct trace_event_call *call, + struct trace_event_file *file) +{ + struct trace_probe *tp; + struct trace_wprobe *tw; + bool enabled; + int ret =3D 0; + + tp =3D trace_probe_primary_from_call(call); + if (WARN_ON_ONCE(!tp)) + return -ENODEV; + enabled =3D trace_probe_is_enabled(tp); + + if (file) { + ret =3D trace_probe_add_file(tp, file); + if (ret) + return ret; + } else { + trace_probe_set_flag(tp, TP_FLAG_PROFILE); + } + + if (!enabled) { + list_for_each_entry(tw, trace_probe_probe_list(tp), tp.list) { + ret =3D __register_trace_wprobe(tw); + if (ret < 0) { + /* TODO: rollback */ + return ret; + } + } + } + + return 0; +} + +static int disable_trace_wprobe(struct trace_event_call *call, + struct trace_event_file *file) +{ + struct trace_wprobe *tw; + struct trace_probe *tp; + + tp =3D trace_probe_primary_from_call(call); + if (WARN_ON_ONCE(!tp)) + return -ENODEV; + + if (file) { + if (!trace_probe_get_file_link(tp, file)) + return -ENOENT; + if (!trace_probe_has_single_file(tp)) + goto out; + trace_probe_clear_flag(tp, TP_FLAG_TRACE); + } else { + trace_probe_clear_flag(tp, TP_FLAG_PROFILE); + } + + if (!trace_probe_is_enabled(tp)) { + list_for_each_entry(tw, trace_probe_probe_list(tp), tp.list) { + __unregister_trace_wprobe(tw); + } + } + +out: + if (file) + trace_probe_remove_file(tp, file); + + return 0; +} + +static int wprobe_register(struct trace_event_call *event, + enum trace_reg type, void *data) +{ + struct trace_event_file *file =3D data; + + switch (type) { + case TRACE_REG_REGISTER: + return enable_trace_wprobe(event, file); + case TRACE_REG_UNREGISTER: + return disable_trace_wprobe(event, file); + +#ifdef CONFIG_PERF_EVENTS + case TRACE_REG_PERF_REGISTER: + return enable_trace_wprobe(event, NULL); + case TRACE_REG_PERF_UNREGISTER: + return disable_trace_wprobe(event, NULL); + case TRACE_REG_PERF_OPEN: + case TRACE_REG_PERF_CLOSE: + case TRACE_REG_PERF_ADD: + case TRACE_REG_PERF_DEL: + return 0; +#endif + } + return 0; +} + +static int parse_address_spec(const char *spec, unsigned long *addr, int *= type, + int *len, char **symbol) +{ + char *_spec __free(kfree) =3D NULL; + int _len =3D HW_BREAKPOINT_LEN_4; + int _type =3D HW_BREAKPOINT_RW; + unsigned long _addr =3D 0; + char *at, *col; + + _spec =3D kstrdup(spec, GFP_KERNEL); + if (!_spec) + return -ENOMEM; + + at =3D strchr(_spec, '@'); + col =3D strchr(_spec, ':'); + + if (!at) { + trace_probe_log_err(0, BAD_ACCESS_FMT); + return -EINVAL; + } + + if (at !=3D _spec) { + *at =3D '\0'; + + if (strcmp(_spec, "r") =3D=3D 0) + _type =3D HW_BREAKPOINT_R; + else if (strcmp(_spec, "w") =3D=3D 0) + _type =3D HW_BREAKPOINT_W; + else if (strcmp(_spec, "rw") =3D=3D 0) + _type =3D HW_BREAKPOINT_RW; + else { + trace_probe_log_err(0, BAD_ACCESS_TYPE); + return -EINVAL; + } + } + + if (col) { + *col =3D '\0'; + if (kstrtoint(col + 1, 0, &_len)) { + trace_probe_log_err(col + 1 - _spec, BAD_ACCESS_LEN); + return -EINVAL; + } + + switch (_len) { + case 1: + _len =3D HW_BREAKPOINT_LEN_1; + break; + case 2: + _len =3D HW_BREAKPOINT_LEN_2; + break; + case 4: + _len =3D HW_BREAKPOINT_LEN_4; + break; + case 8: + _len =3D HW_BREAKPOINT_LEN_8; + break; + default: + trace_probe_log_err(col + 1 - _spec, BAD_ACCESS_LEN); + return -EINVAL; + } + } + + if (kstrtoul(at + 1, 0, &_addr) !=3D 0) { + char *off_str =3D strpbrk(at + 1, "+-"); + int offset =3D 0; + + if (off_str) { + if (kstrtoint(off_str, 0, &offset) !=3D 0) { + trace_probe_log_err(off_str - _spec, BAD_PROBE_ADDR); + return -EINVAL; + } + *off_str =3D '\0'; + } + _addr =3D kallsyms_lookup_name(at + 1); + if (!_addr) { + trace_probe_log_err(at + 1 - _spec, BAD_ACCESS_ADDR); + return -ENOENT; + } + _addr +=3D offset; + *symbol =3D kstrdup(at + 1, GFP_KERNEL); + if (!*symbol) + return -ENOMEM; + } + + *addr =3D _addr; + *type =3D _type; + *len =3D _len; + return 0; +} + +static int __trace_wprobe_create(int argc, const char *argv[]) +{ + /* + * Argument syntax: + * b[:[GRP/][EVENT]] SPEC + * + * SPEC: + * [r|w|rw]@[ADDR|SYMBOL[+OFFS]][:LEN] + */ + struct traceprobe_parse_context *ctx __free(traceprobe_parse_context) =3D= NULL; + struct trace_wprobe *tw __free(free_trace_wprobe) =3D NULL; + const char *event =3D NULL, *group =3D WPROBE_EVENT_SYSTEM; + const char *tplog __free(trace_probe_log_clear) =3D NULL; + char *symbol =3D NULL; + unsigned long addr; + int len, type, i; + int ret =3D 0; + + if (argv[0][0] !=3D 'w') + return -ECANCELED; + + if (argc < 2) + return -EINVAL; + + tplog =3D trace_probe_log_init("wprobe", argc, argv); + + if (argv[0][1] !=3D '\0') { + if (argv[0][1] !=3D ':') { + trace_probe_log_set_index(0); + trace_probe_log_err(1, BAD_MAXACT_TYPE); + /* Invalid format */ + return -EINVAL; + } + event =3D &argv[0][2]; + } + + trace_probe_log_set_index(1); + ret =3D parse_address_spec(argv[1], &addr, &type, &len, &symbol); + if (ret < 0) + return ret; + + if (!event) + event =3D symbol ? symbol : "wprobe"; + + argc -=3D 2; argv +=3D 2; + tw =3D alloc_trace_wprobe(group, event, symbol, addr, len, type, argc); + if (IS_ERR(tw)) + return PTR_ERR(tw); + + ctx =3D kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->flags =3D TPARG_FL_KERNEL | TPARG_FL_WPROBE; + + /* parse arguments */ + for (i =3D 0; i < argc; i++) { + trace_probe_log_set_index(i + 2); + ctx->offset =3D 0; + ret =3D traceprobe_parse_probe_arg(&tw->tp, i, argv[i], ctx); + if (ret) + return ret; /* This can be -ENOMEM */ + } + + ret =3D traceprobe_set_print_fmt(&tw->tp, PROBE_PRINT_NORMAL); + if (ret < 0) + return ret; + + ret =3D register_trace_wprobe_event(tw); + if (!ret) + tw =3D NULL; /* To avoid free */ + + return ret; +} + +static int trace_wprobe_create(const char *raw_command) +{ + return trace_probe_create(raw_command, __trace_wprobe_create); +} + +static int trace_wprobe_release(struct dyn_event *ev) +{ + struct trace_wprobe *tw =3D to_trace_wprobe(ev); + int ret =3D unregister_trace_wprobe(tw); + + if (!ret) + free_trace_wprobe(tw); + return ret; +} + +static int trace_wprobe_show(struct seq_file *m, struct dyn_event *ev) +{ + struct trace_wprobe *tw =3D to_trace_wprobe(ev); + int i; + + seq_printf(m, "w:%s/%s", trace_probe_group_name(&tw->tp), + trace_probe_name(&tw->tp)); + + char type_char; + + if (tw->type =3D=3D HW_BREAKPOINT_R) + type_char =3D 'r'; + else if (tw->type =3D=3D HW_BREAKPOINT_W) + type_char =3D 'w'; + else + type_char =3D 'x'; /* Should be rw */ + + int len; + + if (tw->len =3D=3D HW_BREAKPOINT_LEN_1) + len =3D 1; + else if (tw->len =3D=3D HW_BREAKPOINT_LEN_2) + len =3D 2; + else if (tw->len =3D=3D HW_BREAKPOINT_LEN_4) + len =3D 4; + else + len =3D 8; + + if (tw->symbol) + seq_printf(m, " %c@%s:%d", type_char, tw->symbol, len); + else + seq_printf(m, " %c@0x%lx:%d", type_char, tw->addr, len); + + for (i =3D 0; i < tw->tp.nr_args; i++) + seq_printf(m, " %s=3D%s", tw->tp.args[i].name, tw->tp.args[i].comm); + seq_putc(m, '\n'); + + return 0; +} + +static __init int init_wprobe_trace(void) +{ + return dyn_event_register(&trace_wprobe_ops); +} +fs_initcall(init_wprobe_trace); + From nobody Fri Oct 3 11:15:09 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9017E345722; Mon, 1 Sep 2025 14:45:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737902; cv=none; b=kaeiCNCYoECa1r+jdUDdzSakGP2VYIKtb5OOshwK7sQ6SrOLLLnsAi4mbEt1og5IfJhmCfDHKgs/qEyMEoAF5fEQ92FfZlZRbWPFWEiaCGKZtryfHmHfTODZVADIrbEUNvtml5LwT1xmDCH5YRfqyyk3kxo+lFCvTdegtXP49mA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737902; c=relaxed/simple; bh=u2SnMniYqIdTGMz3VmsvHD4YSv0VfLGBYGvO+y0pNYU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=sRQAkumYMpA9zIh3K0VwHczMDesjskYn6cYC/OcUyxOK4iFI8RZDP84B3sKGxgM78p8AWTosEmKLooSqXvrhfvWE40fejihxIDgTF5UkbWfl7u9XDpKpisxNXxMDZ8PFWWX6xehe/eqAYD0q+4CmPWh/OgCvAP5abP6A2a94sY4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=KIDu+KgS; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="KIDu+KgS" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 62879C4CEF0; Mon, 1 Sep 2025 14:44:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1756737902; bh=u2SnMniYqIdTGMz3VmsvHD4YSv0VfLGBYGvO+y0pNYU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KIDu+KgSO3TM89vViYmPuGZMKp90ymDtpGDR4ijZRzgIUVE28FnJ1oUPDPUZfSZOg KTgm92Uc9Xlhxwti8hGBlkNxVr+RbpylysmofS80544xp5u5slkMDdGzcg+R2Z9Szm jN/UEdO7RQRnzc6QVbTWyDiCI1DYgm8YlxzO/9Yxqci1+DS0paK60NtR5kjkd5JXEC BN1ka4ntIEWUSYrJGXUcIrLqeKRDWz7A6yTdHNSreLThDm/Pc/lwGBfNVf0jn1vbX+ 4LS7K9JUvmbNFn0cLhNbH0N0gxD8BAs1jJE7hVHR/m8RGw5ZpsjgxkKAnN8MJuEPyP jAcBSb1Sr2IDg== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [RFC PATCH 2/6] x86/HWBP: introduce arch_reinstall_hw_breakpoint() for atomic context Date: Mon, 1 Sep 2025 23:44:56 +0900 Message-ID: <175673789661.478080.5655284926337640078.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <175673787502.478080.3342912952394010967.stgit@devnote2> References: <175673787502.478080.3342912952394010967.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: 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 --- 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 notifie= r_block *unused, =20 =20 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_breakpoin= t.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; } =20 +/* + * Reinstall a hardware breakpoint on the current CPU. + * + * This function is used to re-establish a perf counter hardware breakpoin= t. + * It finds the debug address register slot previously allocated for the + * breakpoint and re-enables it by writing the address to the debug regist= er + * 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 =3D counter_arch_bp(bp); + unsigned long *dr7; + int i; + + lockdep_assert_irqs_disabled(); + + for (i =3D 0; i < HBP_NUM; i++) { + struct perf_event **slot =3D this_cpu_ptr(&bp_per_reg[i]); + + if (*slot =3D=3D bp) + break; + } + + if (WARN_ONCE(i =3D=3D 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 =3D this_cpu_ptr(&cpu_dr7); + *dr7 |=3D 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. * From nobody Fri Oct 3 11:15:09 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 72AA8346A14; Mon, 1 Sep 2025 14:45:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737914; cv=none; b=vAVPgaOajypxANzHlDL60km5miMKx7eJjuzn50ZXVeM37jGE4XPBpIO2hUTUy6MiHw03CtnGh/BRYjCufgTMooNYdTbZ6aGNL0lyi539jkgCF+N+DraD4s3FfRNiw8pSXS3i87dttodL8faZoaKXcCQ+3XZr+3pOZ4i7XiZrtzk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737914; c=relaxed/simple; bh=gSiXuEAb7a/LqmE+DtueBaORM6s++MMCk7+u/7YFmUI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=TRCspbfYYM1c3+I9r0gB6rGrdb+VUsrMNH5LCUbzLZSpupeTxZKTpCVAiBzEOio0pTa7GUxkMug0gr3IjKZlFHYYUrSkE8rkr6uyxpHGgSEkD1sWHMR66x7vm7NJ6N4/RBajry0GBl/OF4ukisbyLjWqdjtXjhr29goEMnMVBXs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=KhHuGE4+; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="KhHuGE4+" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 719AEC4CEF0; Mon, 1 Sep 2025 14:45:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1756737914; bh=gSiXuEAb7a/LqmE+DtueBaORM6s++MMCk7+u/7YFmUI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KhHuGE4+SMlBw3aQ42UzR/Z/kT5TrLjjmDQhDX4KdOUxH5g7C/g5614vYgc67ZsjY I/mWJkp4ZkWMoptUJlqRFMAqy8yryIDj9GnjJN7hA05hemJaHt4XzrRrj1kzc+oFk6 jogJm/TIEwCoV3pMWPYl/CQS4ygd5llFm66hyJRqW/C2AUxxakdepoVbA50RRAJZhF vr4qO3yb91ydG1uqUV+L6xZc3qz8lHAjijrJ0LRTsrWukaTrP5AXDw5XZsmeqSPR4O h0uPY7gKCCTKuSi2N393IiJb/y+mlajzMpxGDljuy5oDcqrqe2Mx7YH0FSNAAFpZVk SHXJuIySjwdfg== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [RFC PATCH 3/6] HWBP: Add modify_wide_hw_breakpoint_local() API Date: Mon, 1 Sep 2025 23:45:08 +0900 Message-ID: <175673790787.478080.2462229320641296631.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <175673787502.478080.3342912952394010967.stgit@devnote2> References: <175673787502.478080.3342912952394010967.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add modify_wide_hw_breakpoint_local() arch-wide interface which allows hwbp users to update watch address on-line. This is available if the arch supports CONFIG_HAVE_REINSTALL_HW_BREAKPOINT. Note that this can not change the type because it does not release and reserve the hwbp slot based on type. Signed-off-by: Masami Hiramatsu (Google) --- arch/Kconfig | 10 +++++++++ arch/x86/Kconfig | 1 + arch/x86/include/asm/hw_breakpoint.h | 2 ++ arch/x86/kernel/hw_breakpoint.c | 11 ++++++++++ include/linux/hw_breakpoint.h | 6 ++++++ kernel/events/hw_breakpoint.c | 36 ++++++++++++++++++++++++++++++= ++++ 6 files changed, 66 insertions(+) diff --git a/arch/Kconfig b/arch/Kconfig index d1b4ffd6e085..e4787fc814df 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -418,6 +418,16 @@ config HAVE_MIXED_BREAKPOINTS_REGS Select this option if your arch implements breakpoints under the latter fashion. =20 +config HAVE_REINSTALL_HW_BREAKPOINT + bool + depends on HAVE_HW_BREAKPOINT + help + Depending on the arch implementation of hardware breakpoints, + some of them are able to update the breakpoint configuration + without release and reserve the hardware breakpoint register. + What configuration is able to update depends on hardware and + software implementation. + config HAVE_USER_RETURN_NOTIFIER bool =20 diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 58d890fe2100..49d4ce2af94c 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -247,6 +247,7 @@ config X86 select HAVE_FUNCTION_TRACER select HAVE_GCC_PLUGINS select HAVE_HW_BREAKPOINT + select HAVE_REINSTALL_HW_BREAKPOINT select HAVE_IOREMAP_PROT select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64 select HAVE_IRQ_TIME_ACCOUNTING diff --git a/arch/x86/include/asm/hw_breakpoint.h b/arch/x86/include/asm/hw= _breakpoint.h index bb7c70ad22fe..b3db25eb613f 100644 --- a/arch/x86/include/asm/hw_breakpoint.h +++ b/arch/x86/include/asm/hw_breakpoint.h @@ -64,6 +64,8 @@ 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); =20 +bool hw_breakpoint_arch_same_type(struct arch_hw_breakpoint *hw, + struct perf_event_attr *attr); extern void arch_fill_perf_breakpoint(struct perf_event *bp); =20 diff --git a/arch/x86/kernel/hw_breakpoint.c b/arch/x86/kernel/hw_breakpoin= t.c index 89135229ed21..7dfc88ff6cd9 100644 --- a/arch/x86/kernel/hw_breakpoint.c +++ b/arch/x86/kernel/hw_breakpoint.c @@ -278,6 +278,17 @@ int arch_bp_generic_fields(int x86_len, int x86_type, return 0; } =20 +bool hw_breakpoint_arch_same_type(struct arch_hw_breakpoint *hw, + struct perf_event_attr *attr) +{ + int glen, gtype; + + if (arch_bp_generic_fields(hw->len, hw->type, &glen, >ype) < 0) + return false; + + return gtype =3D=3D attr->bp_type; +} + /* * Check for virtual address in kernel space. */ diff --git a/include/linux/hw_breakpoint.h b/include/linux/hw_breakpoint.h index db199d653dd1..ea373f2587f8 100644 --- a/include/linux/hw_breakpoint.h +++ b/include/linux/hw_breakpoint.h @@ -81,6 +81,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *attr, perf_overflow_handler_t triggered, void *context); =20 +extern int modify_wide_hw_breakpoint_local(struct perf_event *bp, + struct perf_event_attr *attr); + extern int register_perf_hw_breakpoint(struct perf_event *bp); extern void unregister_hw_breakpoint(struct perf_event *bp); extern void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cp= u_events); @@ -124,6 +127,9 @@ register_wide_hw_breakpoint(struct perf_event_attr *att= r, perf_overflow_handler_t triggered, void *context) { return NULL; } static inline int +modify_wide_hw_breakpoint_local(struct perf_event *bp, + struct perf_event_attr *attr) { return -ENOSYS; } +static inline int register_perf_hw_breakpoint(struct perf_event *bp) { return -ENOSYS; } static inline void unregister_hw_breakpoint(struct perf_event *bp) { } static inline void diff --git a/kernel/events/hw_breakpoint.c b/kernel/events/hw_breakpoint.c index 8ec2cb688903..473a5b76941d 100644 --- a/kernel/events/hw_breakpoint.c +++ b/kernel/events/hw_breakpoint.c @@ -887,6 +887,42 @@ void unregister_wide_hw_breakpoint(struct perf_event *= __percpu *cpu_events) } EXPORT_SYMBOL_GPL(unregister_wide_hw_breakpoint); =20 +/** + * modify_wide_hw_breakpoint_local - update breakpoint config for local cpu + * @bp: the hwbp perf event for this cpu + * @attr: the new attribute for @bp + * + * This does not release and reserve the slot of HWBP, just reuse the curr= ent + * slot on local CPU. So the users must update the other CPUs by themselve= s. + * Also, since this does not release/reserve the slot, this can not change= the + * type of the HWBP. + * Return err if attr is invalid or the cpu fails to update debug register + * for new @attr. + */ +#ifdef CONFIG_HAVE_REINSTALL_HW_BREAKPOINT +int modify_wide_hw_breakpoint_local(struct perf_event *bp, + struct perf_event_attr *attr) +{ + int ret; + + if (!hw_breakpoint_arch_same_type(counter_arch_bp(bp), attr)) + return -EINVAL; + + ret =3D hw_breakpoint_arch_parse(bp, attr, counter_arch_bp(bp)); + if (ret) + return ret; + + return arch_reinstall_hw_breakpoint(bp); +} +#else +int modify_wide_hw_breakpoint_local(struct perf_event *bp, + struct perf_event_attr *attr) +{ + return -EOPNOTSUPP; +} +#endif +EXPORT_SYMBOL_GPL(modify_wide_hw_breakpoint_local); + /** * hw_breakpoint_is_used - check if breakpoints are currently used * From nobody Fri Oct 3 11:15:09 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1A8AE2F3C01; Mon, 1 Sep 2025 14:45:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737925; cv=none; b=EaEbcY+VrMlKgnnbHuYfJuwMtpoBpkCGdb+MZuhQCibceahDxW3mlRkSbcw3Ao9hN/CXL4VsbKFyb6Ghjd0ZMp+NUQ97MkanbiBVTuLdcgTnY5X3WJz5jSCd9dOWEr/fCtzfQmoEV+FcYQi8VrBrVQEqzdCnK4CvUfmE/3fFZ60= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737925; c=relaxed/simple; bh=LoJG8nz0zNgEcQHKrTjGS5ckrC3RDAUe+mWEA5UPsQM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=UIitW8+xzZ1uW4M/YfFMOmJ7G+JZib06RhJ03ltFCfaGggmW22PsPXV+7+StP33tPK0nnakJdCWSGRQCyeFnGY3QBRZGdmZRRh8CfZ0Mhfc8ZwD0vzJmPce70CCQ8rJQtaRVTDuWqkGyookI0wYTrKfGGZY4lXiRKWFBbUAJb4A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ubyeRu/B; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ubyeRu/B" Received: by smtp.kernel.org (Postfix) with ESMTPSA id AE355C4CEF0; Mon, 1 Sep 2025 14:45:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1756737924; bh=LoJG8nz0zNgEcQHKrTjGS5ckrC3RDAUe+mWEA5UPsQM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ubyeRu/BS9a6UGLzGGpzPoy17R+ojgvjr0bVqASObXsfF1qTysrSF3W9TYlSS2kkE zVV+umYLSdaZBIGlAihlclLGe5BO7n2sk2791MzSLnFIN4Juw6VzZD0447AxL/EzXo VtUSzoeViFLww+Co8La9YCDwQ0SOTEoUl74qUlrqDLLRs1FesT8gIvqiLRoVvVFXiq uCJZyXCtkPz4I3M4nVWhCZzRtZikLjhLHinItSTZHMLGIEslfAM52nVa/pYB7Qs4Ld O6/Q7ImYBXxEvv/1tPSB18FVgTjCiF8P4iZsnUp57hfQqiXvIct1l2QqQBC9Srp2Z4 hem0QlFIi0MVA== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [RFC PATCH 4/6] tracing: wprobe: Add wprobe event trigger Date: Mon, 1 Sep 2025 23:45:19 +0900 Message-ID: <175673791934.478080.13510585438052558733.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <175673787502.478080.3342912952394010967.stgit@devnote2> References: <175673787502.478080.3342912952394010967.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add wprobe event trigger to set and clear the watch event dynamically. This allows us to set an watchpoint on a given local variables and a slab object instead of static objects. The trigger syntax is below; set_wprobe:WPROBE:FIELD[+offset] [if FILTER] clear_wprobe:WPROBE As you can see, this only sets the address of wprobe, not changing type and length. That should be set when a new wprobe is created. Also, the WPROBE event must be disabled when setting the new trigger and it will be busy afterwards. Recommended usage is to add a new wprobe at NULL address and keep disabled. Signed-off-by: Masami Hiramatsu (Google) --- Documentation/trace/wprobetrace.rst | 60 +++++ include/linux/trace_events.h | 1=20 kernel/trace/Kconfig | 10 + kernel/trace/trace_wprobe.c | 426 +++++++++++++++++++++++++++++++= ++++ 4 files changed, 497 insertions(+) diff --git a/Documentation/trace/wprobetrace.rst b/Documentation/trace/wpro= betrace.rst index 9774f57e2947..67dbf2566289 100644 --- a/Documentation/trace/wprobetrace.rst +++ b/Documentation/trace/wprobetrace.rst @@ -67,3 +67,63 @@ Here is an example to add a wprobe event on a variable `= jiffies`. -0 [000] d.Z1. 717.026373: my_jiffies: (tick_do_up= date_jiffies64+0xbe/0x130) =20 You can see the code which writes to `jiffies` is `do_timer()`. + +Combination with trigger action +------------------------------- +The event trigger action can extend the utilization of this wprobe. + +- set_wprobe:WPEVENT:FIELD[+|-ADJUST] +- clear_wprobe:WPEVENT + +Set these triggers to the target event, then the WPROBE event will be +setup to trace the memory access at FIELD[+|-ADJUST] address. + +For example, trace the first 8 byte of the dentry data structure passed +to do_truncate() until it is deleted by __dentry_kill(). +(Note: all tracefs setup uses '>>' so that it does not kick do_truncate()) + + # echo 'w:watch rw@0:8 address=3D$addr value=3D+0($addr)' > dynamic_even= ts + + # echo 'f:truncate do_truncate dentry=3D$arg2' >> dynamic_events + # echo 'set_wprobe:watch:dentry' >> events/fprobes/truncate/trigger + + # echo 'f:dentry_kill __dentry_kill dentry=3D$arg1' >> dynamic_events + # echo 'clear_wprobe:watch' >> events/fprobes/dentry_kill/trigger + + # echo 1 >> events/fprobes/truncate/enable + # echo 1 >> events/fprobes/dentry_kill/enable + + # echo aaa > /tmp/hoge + # echo bbb > /tmp/hoge + # echo ccc > /tmp/hoge + # rm /tmp/hoge + +Then, the trace data will show; + +# tracer: nop +# +# entries-in-buffer/entries-written: 16/16 #P:8 +# +# _-----=3D> irqs-off/BH-disabled +# / _----=3D> need-resched +# | / _---=3D> hardirq/softirq +# || / _--=3D> preempt-depth +# ||| / _-=3D> migrate-disable +# |||| / delay +# TASK-PID CPU# ||||| TIMESTAMP FUNCTION +# [ 7.026136] sh (113) used greatest stack depth: 12912 bytes left + | | | ||||| | | + sh-113 [002] ..... 7.024402: truncate: (do_truncate+= 0x4/0x120) dentry=3D0xffff8880069194b8 + sh-113 [002] ..Zff 7.024822: watch: (lookup_fast+0xa= a/0x150) address=3D0xffff8880069194b8 value=3D0x200008 + sh-113 [002] ..Zff 7.024830: watch: (step_into+0x82/= 0x360) address=3D0xffff8880069194b8 value=3D0x200008 + sh-113 [002] ..Zff 7.024834: watch: (step_into+0x9f/= 0x360) address=3D0xffff8880069194b8 value=3D0x200008 + sh-113 [002] ..Zff 7.024839: watch: (path_openat+0xb= 3a/0xe70) address=3D0xffff8880069194b8 value=3D0x200008 + sh-113 [002] ..Zff 7.024843: watch: (path_openat+0xb= 9a/0xe70) address=3D0xffff8880069194b8 value=3D0x200008 + sh-113 [002] ..... 7.024847: truncate: (do_truncate+= 0x4/0x120) dentry=3D0xffff8880069194b8 + sh-113 [002] ...1. 7.025364: dentry_kill: (__dentry_= kill+0x0/0x220) dentry=3D0xffff888006919380 + sh-113 [002] ...1. 7.025511: dentry_kill: (__dentry_= kill+0x0/0x220) dentry=3D0xffff8880069195f0 + rm-118 [003] ...1. 7.027543: dentry_kill: (__dentry_= kill+0x0/0x220) dentry=3D0xffff8880069194b8 + sh-113 [002] ...2. 7.027825: dentry_kill: (__dentry_= kill+0x0/0x220) dentry=3D0xffff8880044429c0 + sh-113 [002] ...2. 7.027833: dentry_kill: (__dentry_= kill+0x0/0x220) dentry=3D0xffff888004442270 + +You can see the watch event is correctly configured on the dentry. diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 7c65f2f73ff4..6517c5d7e03e 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -720,6 +720,7 @@ enum event_trigger_type { ETT_EVENT_HIST =3D (1 << 4), ETT_HIST_ENABLE =3D (1 << 5), ETT_EVENT_EPROBE =3D (1 << 6), + ETT_EVENT_WPROBE =3D (1 << 7), }; =20 extern int filter_match_preds(struct event_filter *filter, void *rec); diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index dd8919386425..7396191ccb4d 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -821,6 +821,16 @@ config WPROBE_EVENTS Those events can be inserted wherever hardware breakpoints can be set, and record various register and memory values. =20 +config WPROBE_TRIGGERS + bool "Enable an event trigger for wprobe" + depends on WPROBE_EVENTS + depends on HAVE_REINSTALL_HW_BREAKPOINT + default y + help + This adds an event trigger which will set the wprobe on a specific + field of an event. This allows user to trace the memory access of + an address pointed by the event field. + config BPF_EVENTS depends on BPF_SYSCALL depends on (KPROBE_EVENTS || UPROBE_EVENTS) && PERF_EVENTS diff --git a/kernel/trace/trace_wprobe.c b/kernel/trace/trace_wprobe.c index 4b00a8e917c1..a758032429f3 100644 --- a/kernel/trace/trace_wprobe.c +++ b/kernel/trace/trace_wprobe.c @@ -6,6 +6,8 @@ */ #define pr_fmt(fmt) "trace_wprobe: " fmt =20 +#include +#include #include #include #include @@ -14,11 +16,14 @@ #include #include #include +#include #include #include +#include =20 #include =20 +#include "trace.h" #include "trace_dynevent.h" #include "trace_probe.h" #include "trace_probe_kernel.h" @@ -51,6 +56,12 @@ struct trace_wprobe { struct trace_probe tp; }; =20 +/* This must be called under cpu locked (e.g. preempt off, irq off) */ +static struct perf_event *trace_wprobe_local_perf(struct trace_wprobe *tw) +{ + return *this_cpu_ptr(tw->bp_event); +} + static bool is_trace_wprobe(struct dyn_event *ev) { return ev->ops =3D=3D &trace_wprobe_ops; @@ -683,3 +694,418 @@ static __init int init_wprobe_trace(void) } fs_initcall(init_wprobe_trace); =20 + +#ifdef CONFIG_WPROBE_TRIGGERS + +static int wprobe_trigger_global_enabled; + +#define SET_WPROBE_STR "set_wprobe" +#define CLEAR_WPROBE_STR "clear_wprobe" +#define WPROBE_DEFAULT_CLEAR_ADDRESS ((unsigned long)&wprobe_trigger_globa= l_enabled) + +struct wprobe_trigger_data { + struct trace_event_file *file; + struct trace_wprobe *tw; + + struct perf_event_attr attr; + raw_spinlock_t lock; /* lock protects attr */ + struct work_struct work;// TBD: use work + IPI or use sched/raw_syscall e= vent? + unsigned int offset; + long adjust; + const char *field; + // size must be unsigned long because it should be an address. + bool clear; +}; + +static int trace_wprobe_update_local(struct trace_wprobe *tw, + struct perf_event_attr *attr) +{ + struct perf_event *bp =3D trace_wprobe_local_perf(tw); + + return modify_wide_hw_breakpoint_local(bp, attr); +} + +static void wprobe_smp_update_func(void *data) +{ + struct wprobe_trigger_data *trigger_data =3D data; + unsigned long flags; + + raw_spin_lock_irqsave(&trigger_data->lock, flags); + trace_wprobe_update_local(trigger_data->tw, &trigger_data->attr); + raw_spin_unlock_irqrestore(&trigger_data->lock, flags); +} + +static void wprobe_work_func(struct work_struct *work) +{ + struct wprobe_trigger_data *data =3D container_of(work, struct wprobe_tri= gger_data, work); + + on_each_cpu(wprobe_smp_update_func, data, false); +} + +static void wprobe_trigger(struct event_trigger_data *data, + struct trace_buffer *buffer, void *rec, + struct ring_buffer_event *event) +{ + struct wprobe_trigger_data *wprobe_data =3D data->private_data; + struct perf_event_attr *attr =3D &wprobe_data->attr; + struct trace_wprobe *tw =3D wprobe_data->tw; + unsigned long addr, flags; + int ret =3D -EBUSY; + + addr =3D *(unsigned long *)(rec + wprobe_data->offset); + addr +=3D wprobe_data->adjust; + + raw_spin_lock_irqsave(&wprobe_data->lock, flags); + + if (!wprobe_data->clear) { + if (tw->addr !=3D WPROBE_DEFAULT_CLEAR_ADDRESS) + goto unlock; + + tw->addr =3D attr->bp_addr =3D addr; + ret =3D trace_wprobe_update_local(tw, attr); + if (WARN_ON_ONCE(ret)) + goto unlock; + clear_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &wprobe_data->file->flags); + } else { + if (tw->addr =3D=3D WPROBE_DEFAULT_CLEAR_ADDRESS) + goto unlock; + + tw->addr =3D attr->bp_addr =3D WPROBE_DEFAULT_CLEAR_ADDRESS; + ret =3D trace_wprobe_update_local(tw, attr); + if (WARN_ON_ONCE(ret)) + goto unlock; + set_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &wprobe_data->file->flags); + } + schedule_work(&wprobe_data->work); +unlock: + raw_spin_unlock_irqrestore(&wprobe_data->lock, flags); +} + +static void free_wprobe_trigger_data(struct wprobe_trigger_data *wprobe_da= ta) +{ + if (wprobe_data) + kfree(wprobe_data->field); + kfree(wprobe_data); +} + +DEFINE_FREE(free_wprobe_trigger_data, struct wprobe_trigger_data *, free_w= probe_trigger_data(_T)); + +static int wprobe_trigger_print(struct seq_file *m, + struct event_trigger_data *data) +{ + struct wprobe_trigger_data *wprobe_data =3D data->private_data; + + if (wprobe_data->clear) + seq_printf(m, "%s:%s", CLEAR_WPROBE_STR, + trace_event_name(wprobe_data->file->event_call)); + else + seq_printf(m, "%s:%s:%s%+ld", SET_WPROBE_STR, + trace_event_name(wprobe_data->file->event_call), + wprobe_data->field, wprobe_data->adjust); + + if (data->filter_str) + seq_printf(m, " if %s\n", data->filter_str); + else + seq_putc(m, '\n'); + + return 0; +} + +static struct wprobe_trigger_data * +wprobe_trigger_alloc(struct trace_wprobe *tw, struct trace_event_file *fil= e, + bool clear) +{ + struct wprobe_trigger_data *wprobe_data; + struct perf_event_attr *attr; + + wprobe_data =3D kzalloc(sizeof(*wprobe_data), GFP_KERNEL); + if (!wprobe_data) + return NULL; + + wprobe_data->tw =3D tw; + wprobe_data->clear =3D clear; + wprobe_data->file =3D file; + + attr =3D &wprobe_data->attr; + hw_breakpoint_init(attr); + attr->bp_type =3D tw->type; + attr->bp_addr =3D WPROBE_DEFAULT_CLEAR_ADDRESS; + attr->bp_len =3D tw->len; + + raw_spin_lock_init(&wprobe_data->lock); + INIT_WORK(&wprobe_data->work, wprobe_work_func); + + return wprobe_data; +} + +static void wprobe_trigger_free(struct event_trigger_data *data) +{ + struct wprobe_trigger_data *wprobe_data =3D data->private_data; + + if (WARN_ON_ONCE(data->ref <=3D 0)) + return; + + data->ref--; + if (!data->ref) { + /* Remove the SOFT_MODE flag */ + trace_event_enable_disable(wprobe_data->file, 0, 1); + trace_event_put_ref(wprobe_data->file->event_call); + trigger_data_free(data); + free_wprobe_trigger_data(wprobe_data); + } +} + +static const struct event_trigger_ops set_wprobe_trigger_ops =3D { + .trigger =3D wprobe_trigger, + .print =3D wprobe_trigger_print, + .init =3D event_trigger_init, + .free =3D wprobe_trigger_free, +}; + +static const struct event_trigger_ops clear_wprobe_trigger_ops =3D { + .trigger =3D wprobe_trigger, + .print =3D wprobe_trigger_print, + .init =3D event_trigger_init, + .free =3D wprobe_trigger_free, +}; + +static int wprobe_trigger_cmd_parse(struct event_command *cmd_ops, + struct trace_event_file *file, + char *glob, char *cmd, + char *param_and_filter) +{ + /* + * set_wp:EVENT:FIELD[+OFFS] + * clear_wp:EVENT + */ + struct wprobe_trigger_data *wprobe_data __free(free_wprobe_trigger_data) = =3D NULL; + struct event_trigger_data *trigger_data __free(kfree) =3D NULL; + struct ftrace_event_field *field =3D NULL; + struct trace_event_file *wprobe_file; + struct trace_array *tr =3D file->tr; + struct trace_event_call *event; + struct perf_event_attr *attr; + char *event_str, *field_str; + bool remove, clear =3D false; + struct trace_wprobe *tw; + char *param, *filter; + int ret; + + remove =3D event_trigger_check_remove(glob); + + if (!strcmp(cmd, CLEAR_WPROBE_STR)) + clear =3D true; + + if (event_trigger_empty_param(param_and_filter)) + return -EINVAL; + + ret =3D event_trigger_separate_filter(param_and_filter, ¶m, &filter, = true); + if (ret) + return ret; + + event_str =3D strsep(¶m, ":"); + + /* Find target wprobe */ + tw =3D find_trace_wprobe(event_str, WPROBE_EVENT_SYSTEM); + if (!tw) + return -ENOENT; + /* The target wprobe must not be used (unless clear) */ + if (!remove && !clear && trace_probe_is_enabled(&tw->tp)) + return -EBUSY; + + wprobe_file =3D find_event_file(tr, WPROBE_EVENT_SYSTEM, event_str); + if (!wprobe_file) + return -EINVAL; + + wprobe_data =3D wprobe_trigger_alloc(tw, wprobe_file, clear); + if (!wprobe_data) + return -ENOMEM; + attr =3D &wprobe_data->attr; + + if (!clear) { + char *offs; + + /* Find target field, which must be equivarent to "void *" */ + field_str =3D strsep(¶m, ":"); + if (!field_str) + return -EINVAL; + + offs =3D strpbrk(field_str, "+-"); + if (offs) { + long val; + + if (kstrtol(offs, 0, &val) < 0) + return -EINVAL; + wprobe_data->adjust =3D val; + *offs =3D '\0'; + } + + event =3D file->event_call; + field =3D trace_find_event_field(event, field_str); + if (!field) + return -ENOENT; + + if (field->size !=3D sizeof(void *)) + return -ENOEXEC; + wprobe_data->offset =3D field->offset; + wprobe_data->field =3D kstrdup(field_str, GFP_KERNEL); + if (!wprobe_data->field) + return -ENOMEM; + } + + trigger_data =3D trigger_data_alloc(cmd_ops, cmd, param, wprobe_data); + if (!trigger_data) + return -ENOMEM; + + /* Up the trigger_data count to make sure nothing frees it on failure */ + event_trigger_init(trigger_data); + + if (remove) { + event_trigger_unregister(cmd_ops, file, glob+1, trigger_data); + return 0; + } + + ret =3D event_trigger_parse_num(param, trigger_data); + if (ret) + return ret; + + ret =3D event_trigger_set_filter(cmd_ops, file, filter, trigger_data); + if (ret < 0) + return ret; + + /* Soft-enable (register) wprobe event on WPROBE_DEFAULT_CLEAR_ADDRESS */ + tw->addr =3D attr->bp_addr =3D WPROBE_DEFAULT_CLEAR_ADDRESS; + ret =3D trace_event_enable_disable(wprobe_file, 1, 1); + if (ret < 0) { + event_trigger_reset_filter(cmd_ops, trigger_data); + return ret; + } + ret =3D event_trigger_register(cmd_ops, file, glob, trigger_data); + if (ret) { + event_trigger_reset_filter(cmd_ops, trigger_data); + trace_event_enable_disable(wprobe_file, 0, 1); + return ret; + } + /* Make it NULL to avoid freeing trigger_data and wprobe_data by __free()= */ + trigger_data =3D NULL; + wprobe_data =3D NULL; + + return 0; +} + +/* Return true if there is a trigger which points the same wprobe */ +static bool wprobe_trigger_exist_same(struct event_trigger_data *test, + struct trace_event_file *file) +{ + struct wprobe_trigger_data *test_wprobe_data =3D test->private_data; + struct wprobe_trigger_data *wprobe_data; + struct event_trigger_data *iter; + + list_for_each_entry(iter, &file->triggers, list) { + wprobe_data =3D iter->private_data; + if (!wprobe_data || + iter->cmd_ops->trigger_type !=3D + test->cmd_ops->trigger_type) + continue; + if (wprobe_data->tw =3D=3D test_wprobe_data->tw) + return true; + } + return false; +} + +static int wprobe_register_trigger(char *glob, + struct event_trigger_data *data, + struct trace_event_file *file) +{ + int ret =3D 0; + + lockdep_assert_held(&event_mutex); + + /* The same wprobe is not accept on the same file (event) */ + if (wprobe_trigger_exist_same(data, file)) + return -EEXIST; + + if (data->ops->init) { + ret =3D data->ops->init(data); + if (ret < 0) + return ret; + } + + list_add_rcu(&data->list, &file->triggers); + + update_cond_flag(file); + ret =3D trace_event_trigger_enable_disable(file, 1); + if (ret < 0) { + list_del_rcu(&data->list); + update_cond_flag(file); + } + return ret; +} + +static void wprobe_unregister_trigger(char *glob, + struct event_trigger_data *data, + struct trace_event_file *file) +{ + lockdep_assert_held(&event_mutex); + + if (wprobe_trigger_exist_same(data, file)) { + list_del_rcu(&data->list); + trace_event_trigger_enable_disable(file, 0); + update_cond_flag(file); + } + + if (data && data->ops->free) + data->ops->free(data); +} + +static const struct event_trigger_ops * +wprobe_get_trigger_ops(char *cmd, char *param) +{ + if (!strcmp(cmd, SET_WPROBE_STR)) + return &set_wprobe_trigger_ops; + + return &clear_wprobe_trigger_ops; +} + +static struct event_command trigger_wprobe_set_cmd =3D { + .name =3D SET_WPROBE_STR, + .trigger_type =3D ETT_EVENT_WPROBE, + /* This triggers after when the event is recorded. */ + .flags =3D EVENT_CMD_FL_NEEDS_REC, + .parse =3D wprobe_trigger_cmd_parse, + .reg =3D wprobe_register_trigger, + .unreg =3D wprobe_unregister_trigger, + .get_trigger_ops =3D wprobe_get_trigger_ops, + .set_filter =3D set_trigger_filter, +}; + +static struct event_command trigger_wprobe_clear_cmd =3D { + .name =3D CLEAR_WPROBE_STR, + .trigger_type =3D ETT_EVENT_WPROBE, + /* This triggers after when the event is recorded. */ + .flags =3D EVENT_CMD_FL_NEEDS_REC, + .parse =3D wprobe_trigger_cmd_parse, + .reg =3D wprobe_register_trigger, + .unreg =3D wprobe_unregister_trigger, + .get_trigger_ops =3D wprobe_get_trigger_ops, + .set_filter =3D set_trigger_filter, +}; + +static __init int init_trigger_wprobe_cmds(void) +{ + int ret; + + ret =3D register_event_command(&trigger_wprobe_set_cmd); + if (WARN_ON(ret < 0)) + return ret; + ret =3D register_event_command(&trigger_wprobe_clear_cmd); + if (WARN_ON(ret < 0)) + unregister_event_command(&trigger_wprobe_set_cmd); + + if (!ret) + wprobe_trigger_global_enabled =3D 1; + + return ret; +} +fs_initcall(init_trigger_wprobe_cmds); +#endif /* CONFIG_WPROBE_TRIGGERS */ \ No newline at end of file From nobody Fri Oct 3 11:15:09 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7BBB834DCC1; Mon, 1 Sep 2025 14:45:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737935; cv=none; b=u97/2CnU5nZqeNsuLVAHSR0ysExW4PP+sHjmxE36ywkJeu4mJLG+KjW0c9aYb0uJM22pCc76kegs41dyNjEg9wYdelAJzDdCXgTacMVj9eNwGVVgq6lMDc8lHHn7l8olxiE80fOo6QXPcBsZIQOKuFmdNvktmAw+sAu6lwZiXqc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737935; c=relaxed/simple; bh=reVuS9zhTFmkX8jA2nOh2kSTCPa25rqxfdxv2B2YT/8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=D2AV+X6FKImYXO7lR+wr4Valn7OjBygLpXgy1AjbN/PvimROKySRGB4kGPkqwatlroPb6Y3tpuusuD7/kYPfv5KGt90GxlvEdpbOCsGyIonNOcEbkT2lvIc2keCXf7HbnVjLWuTRyFkWu7pvlUu9LRpUEpQA/ekokB2Ccirkk28= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=DJCMVDIr; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="DJCMVDIr" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4394AC4CEF5; Mon, 1 Sep 2025 14:45:32 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1756737935; bh=reVuS9zhTFmkX8jA2nOh2kSTCPa25rqxfdxv2B2YT/8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DJCMVDIr2ubHxTbsJxYY7L4cWlTJ5ERRFKLSscAj+fiDoUdwVO5URM9EWpIAa7u6n 7m5hZmHD5ybObHSszNGx1SAE5g4XkcWkOYyltIENdMbobsaucuQUBRTs+tHhlzfUvR SlYucCUssUN4SUNuWc8jJICjdbD+SI23NN6KeOVFjS+1Os8CPmiH8LutbXhMkCD1I2 ufugrhnr1ihlhTZrhuUiwp4h1A9zieyzkehKnxGlagdRLAD3zGRAPl6hpePwpjkzaV eRim0bWn2d4xWQraUTbXNxihG5AdRbzztNbFSej5QQPQrNJTTGZG1ZGAN+NJKy690R ZnQJh+z5XKhEw== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [RFC PATCH 5/6] selftests: tracing: Add a basic testcase for wprobe Date: Mon, 1 Sep 2025 23:45:29 +0900 Message-ID: <175673792989.478080.12561167604736619473.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <175673787502.478080.3342912952394010967.stgit@devnote2> References: <175673787502.478080.3342912952394010967.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add 'add_remove_wprobe.tc' testcase for testing wprobe event that tests adding and removing operations of the wprobe event. Signed-off-by: Masami Hiramatsu (Google) --- .../ftrace/test.d/dynevent/add_remove_wprobe.tc | 68 ++++++++++++++++= ++++ 1 file changed, 68 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/add_remo= ve_wprobe.tc diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wpro= be.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc new file mode 100644 index 000000000000..20774c7f69f8 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_wprobe.tc @@ -0,0 +1,68 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Generic dynamic event - add/remove wprobe events +# requires: dynamic_events "w[:[/][]] [r|w|rw]@[:= ]":README + +echo 0 > events/enable +echo > dynamic_events + +# Use jiffies as a variable that is frequently written to. +TARGET=3Djiffies + +echo "w:my_wprobe w@$TARGET" >> dynamic_events + +grep -q my_wprobe dynamic_events +if [ $? -ne 0 ]; then + echo "Failed to create wprobe event" + exit_fail +fi + +test -d events/wprobes/my_wprobe +if [ $? -ne 0 ]; then + echo "Failed to create wprobe event directory" + exit_fail +fi + +echo 1 > events/wprobes/my_wprobe/enable + +# Check if the event is enabled +cat events/wprobes/my_wprobe/enable | grep -q 1 +if [ $? -ne 0 ]; then + echo "Failed to enable wprobe event" + exit_fail +fi + +# Let some time pass to trigger the breakpoint +sleep 1 + +# Check if we got any trace output +if !grep -q my_wprobe trace; then + echo "wprobe event was not triggered" +fi + +echo 0 > events/wprobes/my_wprobe/enable + +# Check if the event is disabled +cat events/wprobes/my_wprobe/enable | grep -q 0 +if [ $? -ne 0 ]; then + echo "Failed to disable wprobe event" + exit_fail +fi + +echo "-:my_wprobe" >> dynamic_events + +! grep -q my_wprobe dynamic_events +if [ $? -ne 0 ]; then + echo "Failed to remove wprobe event" + exit_fail +fi + +! test -d events/wprobes/my_wprobe +if [ $? -ne 0 ]; then + echo "Failed to remove wprobe event directory" + exit_fail +fi + +clear_trace + +exit 0 From nobody Fri Oct 3 11:15:09 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BCA10312829; Mon, 1 Sep 2025 14:45:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737946; cv=none; b=VK2kBkegb1e9uHGuw18ImrZHrCc1GchPK84Ye+PgimTHkynVBMXQmwN4r3N81clDV7rDDIippaNWGKUnfwrtY4GtZ5THKsVlYzGSAwcJBqEXxFPqJ2VZj/YVxNH/lHwOBDw7X5KLeDwILtLMvtkH8VQp3Jz+lGv6oDkfBUvOhOc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756737946; c=relaxed/simple; bh=YmnJaLz0IT7+Cz6HPZlkjrX2bTrODXvTpLb48ZgINCg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=C+n755Gwe8boj41pS0hsmWOY0dCkOr7fpvFeEo7sOA47faXczYH1/YhlnuD8U/1I7WKJlKZm8KxZE7TiePiv9SH8AWSDbz792pG6gh8cHhMrG2AsV5Sxxkg1gCrHrvZW1Mmqkx3v7UhfPwO9ihv6A8+6p5zhlLbh5+lMtHTAzY8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=HCuGXSHv; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="HCuGXSHv" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B034CC4CEF0; Mon, 1 Sep 2025 14:45:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1756737946; bh=YmnJaLz0IT7+Cz6HPZlkjrX2bTrODXvTpLb48ZgINCg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HCuGXSHvYAzt6neAHx0pqfdu7w7Q4WQL8pzQ2hvP0sDDYklnt4SybkM9yzVsk/l83 4+R74o9ETGWaT8uuM36uWqlNb2hDlUnqtcLNXSahPYY81cUQMEyIRCXa5ky9KgswJ9 TZ0/fQ51320iZtgNF5R8nW3/DZgtmGnoMTseeHdlHul53S58B4IZh1V0tDsrBhS7iC 80YmAzRJoQjQDA1gbocpvkJWDsw7T3Mdr+a1W7edKtr1FmDLSJmw33YcrPdp4qWZMU gPIl0Wac6K+3Pi1LYAkoYaGykz8/vHIesyob5vRE2xuoNd14NUcLV/k5kzFeeRW2O5 EptlfmifWQmIg== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Peter Zijlstra , Ingo Molnar , x86@kernel.org Cc: Mathieu Desnoyers , Masami Hiramatsu , Thomas Gleixner , Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Alexander Shishkin , Ian Rogers , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org Subject: [RFC PATCH 6/6] selftests: tracing: Add syntax testcase for wprobe Date: Mon, 1 Sep 2025 23:45:40 +0900 Message-ID: <175673794034.478080.9788390554653757444.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <175673787502.478080.3342912952394010967.stgit@devnote2> References: <175673787502.478080.3342912952394010967.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable From: Masami Hiramatsu (Google) Add "wprobe_syntax_errors.tc" testcase for testing syntax errors of the watch probe events. Signed-off-by: Masami Hiramatsu (Google) --- .../test.d/dynevent/wprobes_syntax_errors.tc | 20 ++++++++++++++++= ++++ 1 file changed, 20 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/wprobes_= syntax_errors.tc diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_= errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_e= rrors.tc new file mode 100644 index 000000000000..82a273d9efb4 --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/dynevent/wprobes_syntax_errors.= tc @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# description: Watch probe event parser error log check +# requires: dynamic_events "w[:[/][]] [r|w|rw]@[:= ]":README + +check_error() { # command-with-error-pos-by-^ + ftrace_errlog_check 'event_probe' "$1" 'dynamic_events' +} + +check_error 'w ^symbol' # BAD_ACCESS_FMT +check_error 'w ^a@symbol' # BAD_ACCESS_TYPE +check_error 'w w@^symbol' # BAD_ACCESS_ADDR +check_error 'w w@^jiffies+offset' # BAD_ACCESS_ADDR +check_error 'w w@jiffies:^100' # BAD_ACCESS_LEN +check_error 'w w@jiffies ^$arg1' # BAD_VAR +check_error 'w w@jiffies ^$retval' # BAD_VAR +check_error 'w w@jiffies ^$stack' # BAD_VAR +check_error 'w w@jiffies ^%ax' # BAD_VAR + +exit 0