From: "Masami Hiramatsu (Google)" <mhiramat@kernel.org>
To: Steven Rostedt <rostedt@goodmis.org>,
Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@kernel.org>,
x86@kernel.org
Cc: Jinchao Wang <wangjinchao600@gmail.com>,
Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
Masami Hiramatsu <mhiramat@kernel.org>,
Thomas Gleixner <tglx@linutronix.de>,
Borislav Petkov <bp@alien8.de>,
Dave Hansen <dave.hansen@linux.intel.com>,
"H . Peter Anvin" <hpa@zytor.com>,
Alexander Shishkin <alexander.shishkin@linux.intel.com>,
Ian Rogers <irogers@google.com>,
linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
linux-doc@vger.kernel.org, linux-perf-users@vger.kernel.org
Subject: [PATCH v12 11/11] tracing/wprobe: Support BTF struct offset resolution in set_wprobe trigger
Date: Sat, 8 Aug 2026 00:35:21 +0900 [thread overview]
Message-ID: <178611692137.237811.11807565234101581407.stgit@devnote2> (raw)
In-Reply-To: <178611679753.237811.10190793347982785419.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Allow set_wprobe trigger to use BTF struct offset resolution to specify
the target address field.
Link: https://lore.kernel.org/all/59637b96946653393a7ad3c7de094094796b39c2.1785067572.git.wangjinchao600@gmail.com/
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v12:
- Refactor field parsing logic into wprobe_trigger_field_parse().
- Remove unused variable count_str in wprobe_trigger_cmd_parse().
---
kernel/trace/trace_wprobe.c | 228 +++++++++++++++++---
.../test.d/trigger/trigger-wprobe-btf-offset.tc | 74 ++++++
2 files changed, 267 insertions(+), 35 deletions(-)
create mode 100644 tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-offset.tc
diff --git a/kernel/trace/trace_wprobe.c b/kernel/trace/trace_wprobe.c
index c64bbdc90a40..565c106a9380 100644
--- a/kernel/trace/trace_wprobe.c
+++ b/kernel/trace/trace_wprobe.c
@@ -27,6 +27,7 @@
#include <asm/ptrace.h>
#include "trace.h"
+#include "trace_btf.h"
#include "trace_dynevent.h"
#include "trace_probe.h"
#include "trace_probe_kernel.h"
@@ -962,9 +963,10 @@ static int wprobe_trigger_print(struct seq_file *m,
seq_printf(m, ":count=%ld", data->count);
if (data->filter_str)
- seq_printf(m, " if %s\n", data->filter_str);
- else
- seq_putc(m, '\n');
+ seq_printf(m, " if %s", data->filter_str);
+
+ seq_printf(m, " # offset:%d adjust:%ld\n",
+ wprobe_data->offset, wprobe_data->adjust);
return 0;
}
@@ -1002,6 +1004,181 @@ static void wprobe_trigger_free(struct event_trigger_data *data)
}
}
+#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
+
+static int get_offset_of_field(struct btf *btf, const struct btf_type *type, char *field_name)
+{
+ const struct btf_member *field;
+ int bitoffs = 0;
+ u32 anon_offs;
+ char *next;
+
+ do {
+ next = strchr(field_name, '.');
+ if (next)
+ *next++ = '\0';
+
+ field = btf_find_struct_member(btf, type, field_name, &anon_offs);
+ if (IS_ERR_OR_NULL(field))
+ return -ENOENT;
+
+ if (btf_type_kflag(type)) {
+ /* Reject bitfield member access */
+ if (BTF_MEMBER_BITFIELD_SIZE(field->offset))
+ return -EINVAL;
+ bitoffs += anon_offs + BTF_MEMBER_BIT_OFFSET(field->offset);
+ } else {
+ bitoffs += anon_offs + field->offset;
+ }
+
+ field_name = next;
+ if (next) {
+ type = btf_type_skip_modifiers(btf, field->type, NULL);
+ if (!type)
+ return -ENOENT;
+ }
+ } while (next);
+ return bitoffs / BITS_PER_BYTE;
+}
+
+/* btf_put(NULL) is acceptable. */
+DEFINE_FREE(btf_put, struct btf *, btf_put(_T))
+
+/* parse typecast: (TYPE[,ASGN])EVENT_FIELD->FIELD[.SUBFIELD...] and set adjust. */
+static int wprobe_trigger_typecast_parse(char **field_str_ptr,
+ struct trace_event_file *file,
+ struct wprobe_trigger_data *wprobe_data,
+ const char *glob)
+{
+ struct btf *btf __free(btf_put) = NULL;
+ const struct btf_type *type;
+ char *assign_field;
+ char *event_field;
+ char *type_field;
+ char *type_name;
+ char *offs;
+ long val = 0;
+ int id;
+ int adjust;
+
+ type_name = *field_str_ptr + 1;
+ event_field = strchr(type_name, ')');
+ if (!event_field) {
+ wprobe_trigger_log_err(file, glob, type_name - glob, DEREF_OPEN_BRACE);
+ return -EINVAL;
+ }
+ *event_field++ = '\0';
+
+ /* Check the optional assign field. */
+ assign_field = strchr(type_name, ',');
+ if (assign_field)
+ *assign_field++ = '\0';
+
+ /* Get the type field name. */
+ type_field = strstr(event_field, "->");
+ if (!type_field) {
+ wprobe_trigger_log_err(file, glob, event_field - glob, TYPECAST_REQ_FIELD);
+ return -EINVAL;
+ }
+ *type_field = '\0';
+ type_field += 2;
+
+ offs = strpbrk(type_field, "+-");
+ if (offs) {
+ if (kstrtol(offs, 0, &val) < 0) {
+ wprobe_trigger_log_err(file, glob, offs - glob, BAD_DEREF_OFFS);
+ return -EINVAL;
+ }
+ *offs = '\0';
+ }
+
+ /* find type from BTF */
+ id = bpf_find_btf_id(type_name, BTF_KIND_STRUCT, &btf);
+ if (id < 0) {
+ wprobe_trigger_log_err(file, glob, type_name - glob, BAD_BTF_TID);
+ return id;
+ }
+
+ type = btf_type_by_id(btf, id);
+ if (!type) {
+ wprobe_trigger_log_err(file, glob, type_name - glob, BAD_BTF_TID);
+ return -EINVAL;
+ }
+
+ adjust = get_offset_of_field(btf, type, type_field);
+ if (adjust < 0) {
+ wprobe_trigger_log_err(file, glob, type_field - glob, NO_BTF_FIELD);
+ return adjust;
+ }
+ wprobe_data->adjust = adjust + val;
+
+ if (assign_field) {
+ /* assign_field should be a struct field */
+ adjust = get_offset_of_field(btf, type, assign_field);
+ if (adjust < 0) {
+ wprobe_trigger_log_err(file, glob, assign_field - glob, NO_BTF_FIELD);
+ return adjust;
+ }
+ wprobe_data->adjust -= adjust;
+ }
+
+ *field_str_ptr = event_field;
+ return 0;
+}
+#else
+static int wprobe_trigger_typecast_parse(char **field_str_ptr,
+ struct trace_event_file *file,
+ struct wprobe_trigger_data *wprobe_data,
+ const char *glob)
+{
+ wprobe_trigger_log_err(file, glob, *field_str_ptr - glob, NOSUP_BTFARG);
+ return -EOPNOTSUPP;
+}
+#endif /* CONFIG_PROBE_EVENTS_BTF_ARGS */
+
+static int wprobe_trigger_field_parse(char *field_str, struct trace_event_file *file,
+ struct wprobe_trigger_data *wprobe_data,
+ const char *glob)
+{
+ struct ftrace_event_field *field;
+ char *offs;
+
+ if (field_str[0] == '(') {
+ int ret = wprobe_trigger_typecast_parse(&field_str, file, wprobe_data, glob);
+
+ if (ret < 0)
+ return ret;
+ } else {
+ offs = strpbrk(field_str, "+-");
+ if (offs) {
+ long val;
+
+ if (kstrtol(offs, 0, &val) < 0) {
+ wprobe_trigger_log_err(file, glob, offs - glob, BAD_DEREF_OFFS);
+ return -EINVAL;
+ }
+ wprobe_data->adjust = val;
+ *offs = '\0';
+ }
+ }
+
+ field = trace_find_event_field(file->event_call, field_str);
+ if (!field) {
+ wprobe_trigger_log_err(file, glob, field_str - glob, NO_EVENT_FIELD);
+ return -ENOENT;
+ }
+ if (field->size != sizeof(void *)) {
+ wprobe_trigger_log_err(file, glob, field_str - glob, WPROBE_BAD_FIELD);
+ return -ENOEXEC;
+ }
+ wprobe_data->offset = field->offset;
+ wprobe_data->field = kstrdup(field_str, GFP_KERNEL);
+ if (!wprobe_data->field)
+ return -ENOMEM;
+
+ return 0;
+}
+
static int wprobe_trigger_cmd_parse(struct event_command *cmd_ops,
struct trace_event_file *file,
char *glob, char *cmd,
@@ -1013,10 +1190,9 @@ static int wprobe_trigger_cmd_parse(struct event_command *cmd_ops,
*/
struct wprobe_trigger_data *wprobe_data __free(free_wprobe_trigger_data) = NULL;
struct event_trigger_data *trigger_data __free(kfree) = NULL;
- char *event_str, *field_str, *count_str, *comment;
+ char *event_str, *comment;
struct trace_event_file *wprobe_file;
struct trace_array *tr = file->tr;
- struct trace_event_call *event;
bool remove, clear = false;
struct trace_wprobe *tw;
char *param, *filter;
@@ -1075,42 +1251,24 @@ static int wprobe_trigger_cmd_parse(struct event_command *cmd_ops,
/* clear_wprobe does not need field. */
if (!clear) {
- char *offs;
+ char *field_str = strsep(¶m, ":");
- /* Find target field, which must be equivarent to "void *" */
- field_str = strsep(¶m, ":");
if (!field_str) {
wprobe_trigger_log_err(file, glob, strlen(glob), WPROBE_NEED_FIELD);
return -EINVAL;
}
-
- offs = strpbrk(field_str, "+-");
- if (offs) {
- long val;
-
- if (kstrtol(offs, 0, &val) < 0) {
- wprobe_trigger_log_err(file, glob, offs - glob, BAD_DEREF_OFFS);
- return -EINVAL;
- }
- wprobe_data->adjust = val;
- *offs = '\0';
- }
-
- event = file->event_call;
- field = trace_find_event_field(event, field_str);
- if (!field) {
- wprobe_trigger_log_err(file, glob, field_str - glob, NO_EVENT_FIELD);
- return -ENOENT;
- }
-
- if (field->size != sizeof(void *)) {
- wprobe_trigger_log_err(file, glob, field_str - glob, WPROBE_BAD_FIELD);
- return -ENOEXEC;
+ ret = wprobe_trigger_field_parse(field_str, file, wprobe_data, glob);
+ if (ret < 0)
+ return ret;
+ } else if (param) {
+ char *orig_param = param;
+ char *field_str = strsep(¶m, ":");
+
+ ret = wprobe_trigger_field_parse(field_str, file, wprobe_data, glob);
+ if (ret < 0) {
+ /* field_str was not a field, so it must be count_str */
+ param = orig_param;
}
- wprobe_data->offset = field->offset;
- wprobe_data->field = kstrdup(field_str, GFP_KERNEL);
- if (!wprobe_data->field)
- return -ENOMEM;
}
trigger_data = trigger_data_alloc(cmd_ops, cmd, param, wprobe_data);
diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-offset.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-offset.tc
new file mode 100644
index 000000000000..dda179a23282
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-wprobe-btf-offset.tc
@@ -0,0 +1,74 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: event trigger - test set_wprobe trigger with BTF struct offset
+# requires: dynamic_events "w[:[<group>/][<event>]] [r|w|rw]@<addr>[:<len>]":README events/sched/sched_process_fork/trigger "[(structname[,field])]<argname>[->field[->field|.field...]]":README
+
+rmmod trace-events-sample ||:
+if ! modprobe trace-events-sample ; then
+ echo "No trace-events sample module - please make CONFIG_SAMPLE_TRACE_EVENTS=m"
+ exit_unresolved
+fi
+
+cleanup_wprobe_triggers() {
+ if [ -f events/fprobes/testevent/trigger ]; then
+ reset_trigger_file events/fprobes/testevent/trigger || true
+ fi
+ echo 0 > events/enable 2>/dev/null || true
+ echo > dynamic_events 2>/dev/null || true
+ sleep 1
+ rmmod trace-events-sample 2>/dev/null || true
+ return 0
+}
+
+trap cleanup_wprobe_triggers EXIT
+
+echo 0 > tracing_on
+
+# we will skip this test if fprobe is not supported.
+if ! grep -Fq "f[:[<group>/][<event>]] <func-name>[%return] [<args>]" README; then
+ echo "UNRESOLVED: fprobe is not supported"
+ exit_unresolved
+fi
+
+# we will skip this test if the target function does not exist.
+if ! grep -wq "sample_timer_cb" /proc/kallsyms; then
+ echo "UNRESOLVED: sample_timer_cb not found"
+ exit_unresolved
+fi
+
+:;: "Add a wprobe event watching 8 bytes" ;:
+echo 'w:watch rw@0:8 address=$addr value=$value' >> dynamic_events
+
+:;: "Add fprobe event for sample_timer_cb" ;:
+# sample_timer_cb(struct timer_list *t)
+# container_of(t, struct foo_timer_data, timer)
+echo 'f:fprobes/testevent sample_timer_cb timer=t' >> dynamic_events
+
+:;: "Enable all events before setting triggers" ;:
+echo 1 > tracing_on
+echo 1 >> events/fprobes/testevent/enable
+
+:;: "Set set_wprobe trigger using BTF struct offset resolution" ;:
+# Syntax: set_wprobe:WPEVENT:(STRUCT,FIELD)EVENT_FIELD->MEMBER
+# (foo_timer_data,timer) is the BTF struct type and field name
+# timer->expires is the struct member whose offset is resolved automatically via BTF
+echo 'set_wprobe:watch:(foo_timer_data,timer)timer->timer.expires' >> events/fprobes/testevent/trigger
+cat events/fprobes/testevent/trigger | grep ^set_wprobe
+
+# Wait for sample_timer_cb to fire and set_wprobe trigger to activate
+sleep 3
+
+:;: "Check set_wprobe trigger activated the watchpoint" ;:
+cat trace | grep watch
+
+:;: "Remove wprobe triggers" ;:
+# Since we don't know actual offset of timer->expires in foo_timer_data, we use reset_trigger_file
+reset_trigger_file events/fprobes/testevent/trigger
+! grep ^set_wprobe events/fprobes/testevent/trigger
+
+:;: "Disable events and remove dynamic events" ;:
+echo 0 > events/enable
+echo > dynamic_events
+clear_trace
+
+exit 0
prev parent reply other threads:[~2026-08-07 15:35 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-07 15:33 [PATCH v12 00/11] tracing: wprobe: x86: Add wprobe for watchpoint Masami Hiramatsu (Google)
2026-08-07 15:33 ` [PATCH v12 01/11] x86/hw_breakpoints: Make DR7 updates NMI safe Masami Hiramatsu (Google)
2026-08-07 15:33 ` [PATCH v12 02/11] x86/hw_breakpoints: Add arch_modify_local_hw_breakpoint_addr() API Masami Hiramatsu (Google)
2026-08-07 15:33 ` [PATCH v12 03/11] HWBP: Add modify_local_hw_breakpoint_addr() API Masami Hiramatsu (Google)
2026-08-07 15:34 ` [PATCH v12 04/11] tracing/wprobe: Add wprobe (watchpoint probe) trace event support Masami Hiramatsu (Google)
2026-08-07 15:34 ` [PATCH v12 05/11] x86: hw_breakpoint: Add a kconfig to clarify when a breakpoint fires Masami Hiramatsu (Google)
2026-08-07 15:34 ` [PATCH v12 06/11] selftests: tracing: Add a basic testcase for wprobe Masami Hiramatsu (Google)
2026-08-07 15:34 ` [PATCH v12 07/11] selftests: tracing: Add syntax " Masami Hiramatsu (Google)
2026-08-07 15:34 ` [PATCH v12 08/11] tracing/wprobe: Add set_wprobe and clear_wprobe event triggers Masami Hiramatsu (Google)
2026-08-07 15:34 ` [PATCH v12 09/11] selftests: ftrace: Add wprobe trigger testcase Masami Hiramatsu (Google)
2026-08-07 15:35 ` [PATCH v12 10/11] tracing/wprobe: Support BTF typecast in fetchargs Masami Hiramatsu (Google)
2026-08-07 15:35 ` Masami Hiramatsu (Google) [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178611692137.237811.11807565234101581407.stgit@devnote2 \
--to=mhiramat@kernel.org \
--cc=alexander.shishkin@linux.intel.com \
--cc=bp@alien8.de \
--cc=dave.hansen@linux.intel.com \
--cc=hpa@zytor.com \
--cc=irogers@google.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=mingo@kernel.org \
--cc=peterz@infradead.org \
--cc=rostedt@goodmis.org \
--cc=tglx@linutronix.de \
--cc=wangjinchao600@gmail.com \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox