* [PATCH v4 3/7] tracing/probes: Support nested typecast
From: Masami Hiramatsu (Google) @ 2026-06-15 1:14 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178148603548.185520.3389196102475741865.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
When we hit an open parenthesis right after typecast closing
parenthesis, it means we have nested typecast. This allows us to
typecast a generic data member in a structure to a pointer to
another structure.
For example, to cast a DATA_MEMBER of VAR structure to STRUCT pointer
and get MEMBER value.
(STRUCT)(VAR->DATA_MEMBER)->MEMBER
Also, we can nest typecast.
(STRUCT1)((STRUCT2)$ARG->FIELD2)->FIELD1
Currently the max nest level is limited to 3.
This also allows user to use typecasting for registers or stacks on
kprobe events. e.g.
(STRUCT)(%ax)->MEMBER
(STRUCT)($stack0)->MEMBER
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v4:
- Use orig_offset for reporting NO_PTR_STRCT error.
Changes in v2:
- Fix to skip "->" after closing parenthetsis.
---
Documentation/trace/eprobetrace.rst | 2 +
Documentation/trace/fprobetrace.rst | 2 +
Documentation/trace/kprobetrace.rst | 2 +
kernel/trace/trace.c | 1
kernel/trace/trace_probe.c | 78 +++++++++++++++++++++++++++++++----
kernel/trace/trace_probe.h | 7 +++
6 files changed, 83 insertions(+), 9 deletions(-)
diff --git a/Documentation/trace/eprobetrace.rst b/Documentation/trace/eprobetrace.rst
index fe3602540569..cd0b4aa7f896 100644
--- a/Documentation/trace/eprobetrace.rst
+++ b/Documentation/trace/eprobetrace.rst
@@ -50,6 +50,8 @@ Synopsis of eprobe_events
a pointer to STRUCT and then derference the pointer defined by
->MEMBER. Note that when this is used, the FIELD name does not
need to be prefixed with a '$'.
+ (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+ also be used with another FETCHARG instead of FIELD.
Types
-----
diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst
index 7435ded2d66d..6b8bb27bb62d 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -60,6 +60,8 @@ Synopsis of fprobe-events
(STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
a pointer to STRUCT and then derference the pointer defined by
->MEMBER.
+ (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+ also be used with another FETCHARG instead of FIELD.
(\*1) This is available only when BTF is enabled.
(\*2) only for the probe on function entry (offs == 0). Note, this argument access
diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
index f73614997d52..c4382765d5b2 100644
--- a/Documentation/trace/kprobetrace.rst
+++ b/Documentation/trace/kprobetrace.rst
@@ -65,6 +65,8 @@ Synopsis of kprobe_events
a pointer to STRUCT and then derference the pointer defined by
->MEMBER. Note that this is available only when the probe is
on function entry.
+ (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+ also be used with another FETCHARG instead of FIELD.
(\*1) only for the probe on function entry (offs == 0). Note, this argument access
is best effort, because depending on the argument type, it may be passed on
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index aa93e7b01146..4f70318918c2 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4326,6 +4326,7 @@ static const char readme_msg[] =
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
"\t [(structname)]<argname>[->field[->field|.field...]],\n"
+ "\t [(structname)](fetcharg)->field[->field|.field...],\n"
#endif
#else
"\t $stack<index>, $stack, $retval, $comm,\n"
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index 9158f1f22a62..adcb5a19e72d 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -832,10 +832,35 @@ static int query_btf_struct(const char *sname, struct traceprobe_parse_context *
return 0;
}
+/* Find the matching closing parenthesis for a given opening parenthesis. */
+static char *find_matched_close_paren(char *s)
+{
+ char *p = s;
+ int count = 0;
+
+ while (*p) {
+ if (*p == '(')
+ count++;
+ else if (*p == ')') {
+ if (--count == 0)
+ return p;
+ }
+ p++;
+ }
+ return NULL;
+}
+
+static int
+parse_probe_arg(char *arg, const struct fetch_type *type,
+ struct fetch_insn **pcode, struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx);
+
static int handle_typecast(char *arg, struct fetch_insn **pcode,
struct fetch_insn *end,
struct traceprobe_parse_context *ctx)
{
+ int orig_offset = ctx->offset;
+ bool nested = false;
char *tmp;
int ret;
@@ -852,19 +877,56 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
DEREF_OPEN_BRACE);
return -EINVAL;
}
- *tmp = '\0';
- ret = query_btf_struct(arg + 1, ctx);
- *tmp = ')';
+ *tmp++ = '\0';
+
+ /* Handle the nested structure like (STRUCT)(VAR->FIELD)->... */
+ if (*tmp == '(') {
+ char *close = find_matched_close_paren(tmp);
+
+ ctx->offset += tmp - arg;
+ if (!close) {
+ trace_probe_log_err(ctx->offset, DEREF_OPEN_BRACE);
+ return -EINVAL;
+ }
+ /* We expect a field access for typecast */
+ if (close[1] != '-' || close[2] != '>') {
+ trace_probe_log_err(ctx->offset + close - tmp + 1,
+ TYPECAST_REQ_FIELD);
+ return -EINVAL;
+ }
+ ctx->nested_level++;
+ if (ctx->nested_level > TRACEPROBE_MAX_NESTED_LEVEL) {
+ trace_probe_log_err(ctx->offset, TOO_MANY_NESTED);
+ return -E2BIG;
+ }
+ *close = '\0';
+
+ ctx->offset += 1; /* for the '(' */
+ /* We need to parse the nested one */
+ ret = parse_probe_arg(tmp + 1, find_fetch_type(NULL, ctx->flags),
+ pcode, end, ctx);
+ if (ret < 0)
+ return ret;
+ ctx->nested_level--;
+ clear_struct_btf(ctx);
+
+ tmp = close + 3;/* Skip "->" after closing parenthesis */
+ nested = true;
+ }
+
+ ret = query_btf_struct(arg + 1, ctx);
if (ret < 0) {
- trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
+ trace_probe_log_err(orig_offset + 1, NO_PTR_STRCT);
return -EINVAL;
}
- tmp++;
-
- ctx->offset += tmp - arg;
- ret = parse_btf_arg(tmp, pcode, end, ctx);
+ ctx->offset = orig_offset + tmp - arg;
+ /* If it is nested, tmp points to the field name. */
+ if (nested)
+ ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx);
+ else
+ ret = parse_btf_arg(tmp, pcode, end, ctx);
return ret;
}
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 883938a74aee..982d32a5df8b 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -435,8 +435,11 @@ struct traceprobe_parse_context {
struct trace_probe *tp;
unsigned int flags;
int offset;
+ int nested_level;
};
+#define TRACEPROBE_MAX_NESTED_LEVEL 3
+
extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i,
const char *argv,
struct traceprobe_parse_context *ctx);
@@ -571,7 +574,9 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(TOO_MANY_ARGS, "Too many arguments are specified"), \
C(TOO_MANY_EARGS, "Too many entry arguments specified"), \
C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \
- C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"),
+ C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"), \
+ C(TYPECAST_REQ_FIELD, "Typecast requires a field access"), \
+ C(TOO_MANY_NESTED, "Too many nested typecasts/dereferences"),
#undef C
#define C(a, b) TP_ERR_##a
^ permalink raw reply related
* [PATCH v4 2/7] tracing/probes: Support typecast for various probe events
From: Masami Hiramatsu (Google) @ 2026-06-15 1:14 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178148603548.185520.3389196102475741865.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Support BTF typecast feature on other probe events, but only if it is
kernel function entry or return, and must use function parameter name
or $retval. This means you can do:
(STRUCT)PARAM->MEMBER
but you can not do (this should be enabled by nesting support)
(STRUCT)%reg->MEMBER
To support other probe events, we just need to use last_struct type
when we find a function parameter in parse_btf_arg().
This also update <tracefs>/README file to show struct typecast.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v3:
- Clarify the limitation.
Changes in v2:
- Fix to re-enable typecast on eprobe.
---
Documentation/trace/fprobetrace.rst | 3 +++
Documentation/trace/kprobetrace.rst | 4 ++++
kernel/trace/trace.c | 2 +-
kernel/trace/trace_probe.c | 14 +++++++++-----
kernel/trace/trace_probe.h | 5 +++++
5 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst
index b4c2ca3d02c1..7435ded2d66d 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -57,6 +57,9 @@ Synopsis of fprobe-events
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
(x8/x16/x32/x64), "char", "string", "ustring", "symbol", "symstr"
and bitfield are supported.
+ (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
+ a pointer to STRUCT and then derference the pointer defined by
+ ->MEMBER.
(\*1) This is available only when BTF is enabled.
(\*2) only for the probe on function entry (offs == 0). Note, this argument access
diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
index 3b6791c17e9b..f73614997d52 100644
--- a/Documentation/trace/kprobetrace.rst
+++ b/Documentation/trace/kprobetrace.rst
@@ -61,6 +61,10 @@ Synopsis of kprobe_events
(x8/x16/x32/x64), VFS layer common type(%pd/%pD), "char",
"string", "ustring", "symbol", "symstr" and bitfield are
supported.
+ (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
+ a pointer to STRUCT and then derference the pointer defined by
+ ->MEMBER. Note that this is available only when the probe is
+ on function entry.
(\*1) only for the probe on function entry (offs == 0). Note, this argument access
is best effort, because depending on the argument type, it may be passed on
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 6eb4d3097a4d..aa93e7b01146 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4325,7 +4325,7 @@ static const char readme_msg[] =
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
- "\t <argname>[->field[->field|.field...]],\n"
+ "\t [(structname)]<argname>[->field[->field|.field...]],\n"
#endif
#else
"\t $stack<index>, $stack, $retval, $comm,\n"
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index fd1caa1f9723..9158f1f22a62 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -759,7 +759,10 @@ static int parse_btf_arg(char *varname,
return -ENOENT;
found:
- type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
+ if (ctx->struct_btf)
+ type = ctx->last_struct;
+ else
+ type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
found_type:
if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
@@ -836,10 +839,11 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
char *tmp;
int ret;
- /* Currently this only works for eprobes */
- if (!(ctx->flags & TPARG_FL_TEVENT)) {
- trace_probe_log_err(ctx->offset, TYPECAST_NOT_EVENT);
- return -EINVAL;
+ if (!(tparg_is_event_probe(ctx->flags) ||
+ tparg_is_function_entry(ctx->flags) ||
+ tparg_is_function_return(ctx->flags))) {
+ trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
+ return -EOPNOTSUPP;
}
tmp = strchr(arg, ')');
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 15758cc11fc6..883938a74aee 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -414,6 +414,11 @@ static inline bool tparg_is_function_return(unsigned int flags)
return (flags & TPARG_FL_LOC_MASK) == (TPARG_FL_KERNEL | TPARG_FL_RETURN);
}
+static inline bool tparg_is_event_probe(unsigned int flags)
+{
+ return !!(flags & TPARG_FL_TEVENT);
+}
+
struct traceprobe_parse_context {
struct trace_event_call *event;
/* BTF related parameters */
^ permalink raw reply related
* [PATCH v4 1/7] tracing/events: Fix to check the simple_tsk_fn creation
From: Masami Hiramatsu (Google) @ 2026-06-15 1:14 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178148603548.185520.3389196102475741865.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Sashiko pointed that this sample code does not correctly handle the
failure of thread creation because kthread_run() can return -errno.
Check the simple_tsk_fn is correctly initialized (created) or not.
Link: https://sashiko.dev/#/patchset/178092865666.163648.10457567771536160909.stgit%40devnote2
Fixes: 9cfe06f8cd5c ("tracing/events: add trace-events-sample")
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v4:
- Fix to remove decrementing counter in error path, since foo_bar_reg() always returns 0.
- Add a newline to error message.
Changes in v3:
- Recover the usage counter.
---
samples/trace_events/trace-events-sample.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/samples/trace_events/trace-events-sample.c b/samples/trace_events/trace-events-sample.c
index ecc7db237f2e..0b7a6efdb247 100644
--- a/samples/trace_events/trace-events-sample.c
+++ b/samples/trace_events/trace-events-sample.c
@@ -107,6 +107,10 @@ int foo_bar_reg(void)
* for consistency sake, we still take the thread_mutex.
*/
simple_tsk_fn = kthread_run(simple_thread_fn, NULL, "event-sample-fn");
+ if (IS_ERR_OR_NULL(simple_tsk_fn)) {
+ pr_err("Failed to create simple_thread_fn\n");
+ simple_tsk_fn = NULL;
+ }
out:
mutex_unlock(&thread_mutex);
return 0;
^ permalink raw reply related
* [PATCH v4 0/7] tracing/probes: Add more typecast features
From: Masami Hiramatsu (Google) @ 2026-06-15 1:13 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
Hi,
Here is the 4th version of series to introduce more typecast features
to probe events. The previous version is here:
https://lore.kernel.org/all/178144880282.159464.16882854283219530040.stgit@devnote2/
In this version, I fixed some issues found by Sashiko reviews.
Steve introduced BTF typecast feature for eprobe[1].
This series extends it and add more options:
1. Expanding BTF typecast to kprobe and fprobe.
(currently only function entry/exit)
2. Introduce container_of like typecast. This adds a "assigned
member" option to the typecast.
(STRUCT,MEMBER)VAR->ANOTHER_MEMBER
This casts VAR to STRUCT type but the VAR is as the address
of STRUCT.MEMBER. In C, it is:
container_of(VAR, STRUCT, MEMBER)->ANOTHER_MEMBER
3. Support nested typecast, e.g.
(STRUCT)((STRUCT2)VAR->MEMBER2)->MEMBER
the nest level must be smaller than 3.
4. Add $current variable to point "current" task_struct.
This is useful with typecast, e.g.
(task_struct)$current->pid
5. per-cpu dereference support.
Intrdouce this_cpu_read(VAR) and this_cpu_ptr(VAR) to
access per-cpu data on the current CPU (accessing other CPU
data is not stable, because it can be changed.)
You can access the member of per-cpu data structure using
typecast like:
(STRUCT)this_cpu_ptr(VAR)->MEMBER
And added a test script to test part of them.
[1] https://lore.kernel.org/all/20260601130746.2139d926@gandalf.local.home/
---
Masami Hiramatsu (Google) (7):
tracing/events: Fix to check the simple_tsk_fn creation
tracing/probes: Support typecast for various probe events
tracing/probes: Support nested typecast
tracing/probes: Support field specifier option for typecast
tracing/probes: Add $current variable support
tracing/probes: Add this_cpu_read() and this_cpu_ptr() dereference method to fetcharg
tracing/probes: Add a new testcase for BTF typecasts
Documentation/trace/eprobetrace.rst | 9
Documentation/trace/fprobetrace.rst | 10
Documentation/trace/kprobetrace.rst | 11 +
kernel/trace/trace.c | 8
kernel/trace/trace_probe.c | 413 +++++++++++++++-----
kernel/trace/trace_probe.h | 19 +
kernel/trace/trace_probe_tmpl.h | 33 +-
samples/trace_events/trace-events-sample.c | 44 ++
samples/trace_events/trace-events-sample.h | 34 ++
.../ftrace/test.d/dynevent/btf_probe_event.tc | 51 ++
.../ftrace/test.d/dynevent/fprobe_syntax_errors.tc | 9
.../ftrace/test.d/kprobe/kprobe_syntax_errors.tc | 9
.../ftrace/test.d/kprobe/uprobe_syntax_errors.tc | 5
13 files changed, 540 insertions(+), 115 deletions(-)
create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/btf_probe_event.tc
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* [PATCH v3 7/7] tracing/probes: Add a new testcase for BTF typecasts
From: Masami Hiramatsu (Google) @ 2026-06-14 14:54 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178144880282.159464.16882854283219530040.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
With the introduction of container_of-style BTF typecasting and
per-CPU variable access support in trace probes, we need a way to
verify their functionality and prevent regressions.
Add a new ftrace kselftest and update the trace event sample module
to test and validate these features.
Specifically, update the trace-events-sample module to set up a
periodic timer whose callback accesses a per-CPU counter. Introduce
a new sample trace event, foo_timer_fn, to trace this callback
and log the current counter value.
Then, add a new test case, btf_probe_event.tc, which defines a
dynamic probe on the timer callback. The probe uses BTF typecasting
to recover the parent structure from the timer argument and
this_cpu_read() to fetch the per-CPU counter. The test verifies
the integrity of the implementation by ensuring the values
recorded by the dynamic probe match those from the static tracepoint.
Assisted-by: Antigravity:gemini-3.5-flash
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v3:
- Add syntax test case.
- Update testcase to use this_cpu_read()
Changes in v2:
- Use timer_shutdown_sync() instead of timer_delete_sync() for teardown.
---
samples/trace_events/trace-events-sample.c | 40 +++++++++++++++-
samples/trace_events/trace-events-sample.h | 34 ++++++++++++-
.../ftrace/test.d/dynevent/btf_probe_event.tc | 51 ++++++++++++++++++++
.../ftrace/test.d/dynevent/fprobe_syntax_errors.tc | 9 ++++
.../ftrace/test.d/kprobe/kprobe_syntax_errors.tc | 9 ++++
.../ftrace/test.d/kprobe/uprobe_syntax_errors.tc | 5 ++
6 files changed, 143 insertions(+), 5 deletions(-)
create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/btf_probe_event.tc
diff --git a/samples/trace_events/trace-events-sample.c b/samples/trace_events/trace-events-sample.c
index 82344a78e471..651f3e2138ab 100644
--- a/samples/trace_events/trace-events-sample.c
+++ b/samples/trace_events/trace-events-sample.c
@@ -94,6 +94,20 @@ static int simple_thread_fn(void *arg)
static DEFINE_MUTEX(thread_mutex);
static int simple_thread_cnt;
+static struct foo_timer_data *foo_timer_data;
+
+static void sample_timer_cb(struct timer_list *t)
+{
+ struct foo_timer_data *data = container_of(t, struct foo_timer_data, timer);
+
+ get_cpu();
+ trace_foo_timer_fn(data);
+ (*this_cpu_ptr(data->counter))++;
+ put_cpu();
+
+ mod_timer(t, jiffies + HZ);
+}
+
int foo_bar_reg(void)
{
mutex_lock(&thread_mutex);
@@ -133,9 +147,27 @@ void foo_bar_unreg(void)
static int __init trace_event_init(void)
{
+ foo_timer_data = kzalloc_obj(*foo_timer_data, GFP_KERNEL);
+ if (!foo_timer_data)
+ return -ENOMEM;
+
+ foo_timer_data->name = "sample_timer_counter";
+ foo_timer_data->counter = alloc_percpu(int);
+ if (!foo_timer_data->counter) {
+ kfree(foo_timer_data);
+ return -ENOMEM;
+ }
+
+ timer_setup(&foo_timer_data->timer, sample_timer_cb, 0);
+ mod_timer(&foo_timer_data->timer, jiffies + HZ);
+
simple_tsk = kthread_run(simple_thread, NULL, "event-sample");
- if (IS_ERR(simple_tsk))
- return -1;
+ if (IS_ERR(simple_tsk)) {
+ timer_shutdown_sync(&foo_timer_data->timer);
+ free_percpu(foo_timer_data->counter);
+ kfree(foo_timer_data);
+ return PTR_ERR(simple_tsk);
+ }
return 0;
}
@@ -148,6 +180,10 @@ static void __exit trace_event_exit(void)
kthread_stop(simple_tsk_fn);
simple_tsk_fn = NULL;
mutex_unlock(&thread_mutex);
+
+ timer_shutdown_sync(&foo_timer_data->timer);
+ free_percpu(foo_timer_data->counter);
+ kfree(foo_timer_data);
}
module_init(trace_event_init);
diff --git a/samples/trace_events/trace-events-sample.h b/samples/trace_events/trace-events-sample.h
index 1a05fc153353..816848a456a2 100644
--- a/samples/trace_events/trace-events-sample.h
+++ b/samples/trace_events/trace-events-sample.h
@@ -247,12 +247,14 @@
*/
/*
- * It is OK to have helper functions in the file, but they need to be protected
- * from being defined more than once. Remember, this file gets included more
- * than once.
+ * It is OK to have helper functions and data structures in the file, but they
+ * need to be protected from being defined more than once. Remember, this file
+ * gets included more than once.
*/
#ifndef __TRACE_EVENT_SAMPLE_HELPER_FUNCTIONS
#define __TRACE_EVENT_SAMPLE_HELPER_FUNCTIONS
+#include <linux/timer.h>
+
static inline int __length_of(const int *list)
{
int i;
@@ -270,6 +272,13 @@ enum {
TRACE_SAMPLE_BAR = 4,
TRACE_SAMPLE_ZOO = 8,
};
+
+struct foo_timer_data {
+ const char *name;
+ struct timer_list timer;
+ int __percpu *counter;
+};
+
#endif
/*
@@ -595,6 +604,25 @@ TRACE_EVENT(foo_rel_loc,
__get_rel_bitmask(bitmask),
__get_rel_cpumask(cpumask))
);
+
+TRACE_EVENT(foo_timer_fn,
+
+ TP_PROTO(struct foo_timer_data *data),
+
+ TP_ARGS(data),
+
+ TP_STRUCT__entry(
+ __string( name, data->name )
+ __field( int, count )
+ ),
+
+ TP_fast_assign(
+ __assign_str(name);
+ __entry->count = *this_cpu_ptr(data->counter);
+ ),
+
+ TP_printk("name=%s count=%d", __get_str(name), __entry->count)
+);
#endif
/***** NOTICE! The #if protection ends here. *****/
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/btf_probe_event.tc b/tools/testing/selftests/ftrace/test.d/dynevent/btf_probe_event.tc
new file mode 100644
index 000000000000..96791e120b7d
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/btf_probe_event.tc
@@ -0,0 +1,51 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: BTF event with typecast and percpu access
+# requires: dynamic_events "this_cpu_read(<fetcharg>)":README "[(structname[,field])]<argname>[->field[->field|.field...]]":README
+
+# Check if the sample module is loaded
+if ! lsmod | grep -q trace_events_sample; then
+ modprobe trace-events-sample || exit_unsupported
+fi
+
+echo 0 > events/enable
+echo > dynamic_events
+
+# The sample_timer_cb(struct timer_list *t) is called.
+# We want to check (STRUCT,FIELD)VAR typecast and this_cpu_read() access.
+# (foo_timer_data,timer)t converts t to struct foo_timer_data * using container_of.
+# data->counter is a per-cpu pointer to int.
+# this_cpu_read(data->counter) should give the value of the counter.
+
+echo 'f:mysample/myevent sample_timer_cb name=(foo_timer_data,timer)t->name:string count=this_cpu_read((foo_timer_data,timer)t->counter)' >> dynamic_events
+
+echo 1 > events/mysample/myevent/enable
+echo 1 > events/sample-trace/foo_timer_fn/enable
+
+sleep 2
+
+echo 0 > events/mysample/myevent/enable
+echo 0 > events/sample-trace/foo_timer_fn/enable
+
+# Compare the values.
+MATCH=0
+while read line; do
+ if echo $line | grep -q "foo_timer_fn:"; then
+ NAME=`echo $line | sed 's/.*name=\([^ ]*\) .*/\1/'`
+ COUNT=`echo $line | sed 's/.*count=\([^ ]*\).*/\1/'`
+ if grep -q "myevent:.*name=\"${NAME}\" count=$COUNT" trace; then
+ MATCH=$((MATCH+1))
+ fi
+ fi
+done < trace
+
+if [ $MATCH -eq 0 ]; then
+ echo "No matching events found"
+ exit_fail
+fi
+
+# Clean up
+echo 0 > events/mysample/myevent/enable
+echo 0 > events/sample-trace/foo_timer_fn/enable
+echo > dynamic_events
+clear_trace
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc
index fee479295e2f..b781ec07c0d0 100644
--- a/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc
@@ -112,6 +112,15 @@ check_error 'f vfs_read%return $retval->^foo' # NO_PTR_STRCT
check_error 'f vfs_read file->^foo' # NO_BTF_FIELD
check_error 'f vfs_read file^-.foo' # BAD_HYPHEN
check_error 'f vfs_read ^file:string' # BAD_TYPE4STR
+if grep -qF "[(structname" README ; then
+check_error 'f vfs_read arg1=(task_struct)file^' # TYPECAST_REQ_FIELD
+check_error 'f vfs_read arg1=(f)((f)((f)((f)^((f)file->f)->f)->f)->f)->f' # TOO_MANY_NESTED
+check_error 'f vfs_read arg1=(task_struct,^in_execve)file->comm' # TYPECAST_NOT_ALIGNED
+check_error 'f vfs_read arg1=(task_struct,se^->group_node)file->comm' # TYPECAST_BAD_ARROW
+check_error 'f vfs_read arg1=(task_struct,^->pid)file->comm' # TYPECAST_BAD_ARROW
+check_error 'f vfs_read arg1=(task_struct,^.pid)file->comm' # BTF_BAD_TID
+check_error 'f vfs_read arg1=(task_struct,^.)file->comm' # BTF_BAD_TID
+fi
fi
else
diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc
index 8f1c58f0c239..78f015c8c010 100644
--- a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc
+++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc
@@ -115,6 +115,15 @@ check_error 'p vfs_read+20 ^$arg*' # NOFENTRY_ARGS
check_error 'p vfs_read ^hoge' # NO_BTFARG
check_error 'p kfree ^$arg10' # NO_BTFARG (exceed the number of parameters)
check_error 'r kfree ^$retval' # NO_RETVAL
+if grep -qF "[(structname" README ; then
+check_error 'p vfs_read arg1=(task_struct)file^' # TYPECAST_REQ_FIELD
+check_error 'p vfs_read arg1=(f)((f)((f)((f)^((f)file->f)->f)->f)->f)->f' # TOO_MANY_NESTED
+check_error 'p vfs_read arg1=(task_struct,^in_execve)file->comm' # TYPECAST_NOT_ALIGNED
+check_error 'p vfs_read arg1=(task_struct,se^->group_node)file->comm' # TYPECAST_BAD_ARROW
+check_error 'p vfs_read arg1=(task_struct,^->pid)file->comm' # TYPECAST_BAD_ARROW
+check_error 'p vfs_read arg1=(task_struct,^.pid)file->comm' # BTF_BAD_TID
+check_error 'p vfs_read arg1=(task_struct,^.)file->comm' # BTF_BAD_TID
+fi
else
check_error 'p vfs_read ^$arg*' # NOSUP_BTFARG
fi
diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/uprobe_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/kprobe/uprobe_syntax_errors.tc
index c817158b99db..d5d5245bee6c 100644
--- a/tools/testing/selftests/ftrace/test.d/kprobe/uprobe_syntax_errors.tc
+++ b/tools/testing/selftests/ftrace/test.d/kprobe/uprobe_syntax_errors.tc
@@ -28,4 +28,9 @@ if grep -q ".*symstr.*" README; then
check_error 'p /bin/sh:10 $stack0:^symstr' # BAD_TYPE
fi
+# $current is not supported by uprobe
+if grep -q "\$current.*" README; then
+check_error 'p /bin/sh:10 $current:^u8' # BAD_VAR
+fi
+
exit 0
^ permalink raw reply related
* [PATCH v3 6/7] tracing/probes: Add this_cpu_read() and this_cpu_ptr() dereference method to fetcharg
From: Masami Hiramatsu (Google) @ 2026-06-14 14:54 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178144880282.159464.16882854283219530040.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
When tracing the kernel local variables, sometimes we need to get the
CPU local variables. To access it, current simple dereference is not
enough.
Thus, introduce a special this_cpu_read() dereference to access per-cpu
variable for the current CPU (accessing other CPU variable may race with
updates on other CPUs). Also this_cpu_ptr() is for accessing per-cpu
pointer.
Those are working as same as the kernel percpu macro.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v3:
- Remove NULL check for percpu var because it is just an offset, could be 0.
- Simplify process_fetch_insn_bottom() code.
- If the last operation is this_cpu_read(), read only memory of the specific
size (of type).
Changes in v2:
- Drop +CPU/+PCPU and introduce this_cpu_read() and this_cpu_ptr().
- Support these method with BTF typecast.
- Just check the base address is NOT NULL instead of is_kernel_percpu_address().
---
Documentation/trace/eprobetrace.rst | 2 +
Documentation/trace/fprobetrace.rst | 2 +
Documentation/trace/kprobetrace.rst | 2 +
kernel/trace/trace.c | 1
kernel/trace/trace_probe.c | 138 ++++++++++++++++++++++++-----------
kernel/trace/trace_probe.h | 3 +
kernel/trace/trace_probe_tmpl.h | 30 ++++++--
7 files changed, 129 insertions(+), 49 deletions(-)
diff --git a/Documentation/trace/eprobetrace.rst b/Documentation/trace/eprobetrace.rst
index 680e0af43d5d..279396951b34 100644
--- a/Documentation/trace/eprobetrace.rst
+++ b/Documentation/trace/eprobetrace.rst
@@ -39,6 +39,8 @@ Synopsis of eprobe_events
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol)
$comm : Fetch current task comm.
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*3)(\*4)
+ this_cpu_read(FETCHARG) : Read the value of the per-CPU variable FETCHARG on the current CPU.
+ this_cpu_ptr(FETCHARG) : Get the address of the per-CPU variable FETCHARG on the current CPU.
\IMM : Store an immediate value to the argument.
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst
index 3392cab016b3..3439bc9bd351 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -52,6 +52,8 @@ Synopsis of fprobe-events
$comm : Fetch current task comm.
$current : Fetch the address of the current task_struct.
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*4)(\*5)
+ this_cpu_read(FETCHARG) : Read the value of the per-CPU variable FETCHARG on the current CPU.
+ this_cpu_ptr(FETCHARG) : Get the address of the per-CPU variable FETCHARG on the current CPU.
\IMM : Store an immediate value to the argument.
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
index 81e4fe38791d..9ae330eb0a52 100644
--- a/Documentation/trace/kprobetrace.rst
+++ b/Documentation/trace/kprobetrace.rst
@@ -55,6 +55,8 @@ Synopsis of kprobe_events
$comm : Fetch current task comm.
$current : Fetch the address of the current task_struct.
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*3)(\*4)
+ this_cpu_read(FETCHARG) : Read the value of the per-CPU variable FETCHARG on the current CPU.
+ this_cpu_ptr(FETCHARG) : Get the address of the per-CPU variable FETCHARG on the current CPU.
\IMM : Store an immediate value to the argument.
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index e185a006cb08..1d5d6e46dc4d 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4332,6 +4332,7 @@ static const char readme_msg[] =
"\t $stack<index>, $stack, $retval, $comm, $current\n"
#endif
"\t +|-[u]<offset>(<fetcharg>), \\imm-value, \\\"imm-string\"\n"
+ "\t this_cpu_read(<fetcharg>), this_cpu_ptr(<fetcharg>)\n"
"\t kernel return probes support: $retval, $arg<N>, $comm\n"
"\t type: s8/16/32/64, u8/16/32/64, x8/16/32/64, char, string, symbol,\n"
"\t b<bit-width>@<bit-offset>/<container-size>, ustring,\n"
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index 017f30ae9def..61bd65575f64 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -349,6 +349,77 @@ static int parse_trace_event(char *arg, struct fetch_insn *code,
return -EINVAL;
}
+/* this_cpu_* parser */
+#define THIS_CPU_PTR_PREFIX "this_cpu_ptr("
+#define THIS_CPU_READ_PREFIX "this_cpu_read("
+#define THIS_CPU_PTR_LEN (sizeof(THIS_CPU_PTR_PREFIX) - 1)
+#define THIS_CPU_READ_LEN (sizeof(THIS_CPU_READ_PREFIX) - 1)
+
+static int
+parse_probe_arg(char *arg, const struct fetch_type *type,
+ struct fetch_insn **pcode, struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx);
+
+/* handle dereference nested call */
+static inline int handle_dereference(char *arg, struct fetch_insn **pcode,
+ struct fetch_insn *end, struct traceprobe_parse_context *ctx,
+ int deref, long offset)
+{
+ const struct fetch_type *type = find_fetch_type(NULL, ctx->flags);
+ struct fetch_insn *code = *pcode;
+ int cur_offs = ctx->offset;
+ char *tmp;
+ int ret;
+
+ tmp = strrchr(arg, ')');
+ if (!tmp) {
+ trace_probe_log_err(ctx->offset + strlen(arg),
+ DEREF_OPEN_BRACE);
+ return -EINVAL;
+ }
+
+ *tmp = '\0';
+ ret = parse_probe_arg(arg, type, &code, end, ctx);
+ if (ret)
+ return ret;
+ ctx->offset = cur_offs;
+ if (code->op == FETCH_OP_COMM || code->op == FETCH_OP_DATA) {
+ trace_probe_log_err(ctx->offset, COMM_CANT_DEREF);
+ return -EINVAL;
+ }
+ code++;
+ if (code == end) {
+ trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
+ return -EINVAL;
+ }
+ *pcode = code;
+
+ code->op = deref;
+ code->offset = offset;
+ /* Reset the last type if used */
+ ctx->last_type = NULL;
+ return 0;
+}
+
+static int parse_this_cpu(char *arg, struct fetch_insn **pcode,
+ struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx)
+{
+ int deref;
+
+ if (str_has_prefix(arg, THIS_CPU_PTR_PREFIX)) {
+ arg += THIS_CPU_PTR_LEN;
+ ctx->offset += THIS_CPU_PTR_LEN;
+ deref = FETCH_OP_CPU_PTR;
+ } else if (str_has_prefix(arg, THIS_CPU_READ_PREFIX)) {
+ arg += THIS_CPU_READ_LEN;
+ ctx->offset += THIS_CPU_READ_LEN;
+ deref = FETCH_OP_DEREF_CPU;
+ } else
+ return -EINVAL;
+ return handle_dereference(arg, pcode, end, ctx, deref, 0);
+}
+
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
static u32 btf_type_int(const struct btf_type *t)
@@ -929,11 +1000,6 @@ static char *find_matched_close_paren(char *s)
return NULL;
}
-static int
-parse_probe_arg(char *arg, const struct fetch_type *type,
- struct fetch_insn **pcode, struct fetch_insn *end,
- struct traceprobe_parse_context *ctx);
-
static int handle_typecast(char *arg, struct fetch_insn **pcode,
struct fetch_insn *end,
struct traceprobe_parse_context *ctx)
@@ -959,7 +1025,8 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
*tmp++ = '\0';
/* Handle the nested structure like (STRUCT)(VAR->FIELD)->... */
- if (*tmp == '(') {
+ if (*tmp == '(' || str_has_prefix(tmp, THIS_CPU_PTR_PREFIX) ||
+ str_has_prefix(tmp, THIS_CPU_READ_PREFIX)) {
char *close = find_matched_close_paren(tmp);
ctx->offset += tmp - arg;
@@ -979,12 +1046,18 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
trace_probe_log_err(ctx->offset, TOO_MANY_NESTED);
return -E2BIG;
}
- *close = '\0';
- ctx->offset += 1; /* for the '(' */
- /* We need to parse the nested one */
- ret = parse_probe_arg(tmp + 1, find_fetch_type(NULL, ctx->flags),
- pcode, end, ctx);
+ if (*tmp == '(') {
+ /* Extract the inner argument */
+ *close = '\0';
+ ctx->offset += 1;/* for the '(' */
+ /* Parse the nested one */
+ ret = parse_probe_arg(tmp + 1, find_fetch_type(NULL, ctx->flags),
+ pcode, end, ctx);
+ } else {
+ /* this_cpu_* will be parsed in parse_this_cpu() */
+ ret = parse_this_cpu(tmp, pcode, end, ctx);
+ }
if (ret < 0)
return ret;
ctx->nested_level--;
@@ -1454,36 +1527,9 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
}
ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
arg = tmp + 1;
- tmp = strrchr(arg, ')');
- if (!tmp) {
- trace_probe_log_err(ctx->offset + strlen(arg),
- DEREF_OPEN_BRACE);
- return -EINVAL;
- } else {
- const struct fetch_type *t2 = find_fetch_type(NULL, ctx->flags);
- int cur_offs = ctx->offset;
-
- *tmp = '\0';
- ret = parse_probe_arg(arg, t2, &code, end, ctx);
- if (ret)
- break;
- ctx->offset = cur_offs;
- if (code->op == FETCH_OP_COMM ||
- code->op == FETCH_OP_DATA) {
- trace_probe_log_err(ctx->offset, COMM_CANT_DEREF);
- return -EINVAL;
- }
- if (++code == end) {
- trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
- return -EINVAL;
- }
- *pcode = code;
-
- code->op = deref;
- code->offset = offset;
- /* Reset the last type if used */
- ctx->last_type = NULL;
- }
+ ret = handle_dereference(arg, pcode, end, ctx, deref, offset);
+ if (ret < 0)
+ return ret;
break;
case '\\': /* Immediate value */
if (arg[1] == '"') { /* Immediate string */
@@ -1504,15 +1550,18 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
ret = handle_typecast(arg, pcode, end, ctx);
break;
default:
- if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
+ if (str_has_prefix(arg, THIS_CPU_PTR_PREFIX) ||
+ str_has_prefix(arg, THIS_CPU_READ_PREFIX)) {
+ ret = parse_this_cpu(arg, pcode, end, ctx);
+ } else if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
if (!tparg_is_function_entry(ctx->flags) &&
!tparg_is_function_return(ctx->flags)) {
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
return -EINVAL;
}
ret = parse_btf_arg(arg, pcode, end, ctx);
- break;
}
+ break;
}
if (!ret && code->op == FETCH_OP_NOP) {
/* Parsed, but do not find fetch method */
@@ -1687,6 +1736,9 @@ static int finalize_fetch_insn(struct fetch_insn *code,
} else if (code->op == FETCH_OP_UDEREF) {
code->op = FETCH_OP_ST_UMEM;
code->size = parg->type->size;
+ } else if (code->op == FETCH_OP_DEREF_CPU) {
+ code->op = FETCH_OP_ST_CPUMEM;
+ code->size = parg->type->size;
} else {
code++;
if (code->op != FETCH_OP_NOP) {
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 62645e847bd1..523612023608 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -100,10 +100,13 @@ enum fetch_op {
// Stage 2 (dereference) op
FETCH_OP_DEREF, /* Dereference: .offset */
FETCH_OP_UDEREF, /* User-space Dereference: .offset */
+ FETCH_OP_DEREF_CPU, /* Per-CPU Dereference for this CPU */
+ FETCH_OP_CPU_PTR, /* Per-CPU pointer for this CPU */
// Stage 3 (store) ops
FETCH_OP_ST_RAW, /* Raw: .size */
FETCH_OP_ST_MEM, /* Mem: .offset, .size */
FETCH_OP_ST_UMEM, /* Mem: .offset, .size */
+ FETCH_OP_ST_CPUMEM, /* Per-CPU Mem: .size */
FETCH_OP_ST_STRING, /* String: .offset, .size */
FETCH_OP_ST_USTRING, /* User String: .offset, .size */
FETCH_OP_ST_SYMSTR, /* Kernel Symbol String: .offset, .size */
diff --git a/kernel/trace/trace_probe_tmpl.h b/kernel/trace/trace_probe_tmpl.h
index f630930288d2..83111c167b74 100644
--- a/kernel/trace/trace_probe_tmpl.h
+++ b/kernel/trace/trace_probe_tmpl.h
@@ -129,25 +129,39 @@ process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val,
struct fetch_insn *s3 = NULL;
int total = 0, ret = 0, i = 0;
u32 loc = 0;
- unsigned long lval = val;
+ unsigned long lval, llval = val;
stage2:
/* 2nd stage: dereference memory if needed */
do {
- if (code->op == FETCH_OP_DEREF) {
- lval = val;
+ lval = val;
+ switch (code->op) {
+ case FETCH_OP_DEREF:
ret = probe_mem_read(&val, (void *)val + code->offset,
sizeof(val));
- } else if (code->op == FETCH_OP_UDEREF) {
- lval = val;
+ break;
+ case FETCH_OP_UDEREF:
ret = probe_mem_read_user(&val,
(void *)val + code->offset, sizeof(val));
- } else
break;
+ case FETCH_OP_DEREF_CPU:
+ val = (unsigned long)this_cpu_ptr((void __percpu *)val);
+ ret = probe_mem_read(&val, (void *)val, sizeof(val));
+ break;
+ case FETCH_OP_CPU_PTR:
+ val = (unsigned long)this_cpu_ptr((void __percpu *)val);
+ ret = 0;
+ break;
+ default:
+ lval = llval;
+ goto out;
+ }
if (ret)
return ret;
+ llval = lval;
code++;
} while (1);
+out:
s3 = code;
stage3:
@@ -181,6 +195,10 @@ process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val,
case FETCH_OP_ST_UMEM:
probe_mem_read_user(dest, (void *)val + code->offset, code->size);
break;
+ case FETCH_OP_ST_CPUMEM:
+ val = (unsigned long)this_cpu_ptr((void __percpu *)val);
+ probe_mem_read(dest, (void *)val, code->size);
+ break;
case FETCH_OP_ST_STRING:
loc = *(u32 *)dest;
ret = fetch_store_string(val + code->offset, dest, base);
^ permalink raw reply related
* [PATCH v3 5/7] tracing/probes: Add $current variable support
From: Masami Hiramatsu (Google) @ 2026-06-14 14:54 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178144880282.159464.16882854283219530040.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Since we can use the BTF to cast value to a structure pointer type,
it is useful to introduce "$current" special variable support to
fetcharg.
User can define a fetcharg to access current task_struct properties
using BTF info. e.g.
$current->cpus_ptr
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v3:
- Remove $current support from eprobes (because eprobes is only for event)
- Prohibit uprobes to use $current.
Changes in v2:
- Support to parse $current in parse_btf_arg().
- If no typecast on $current, it automatically casted to task_struct.
- Check error case if $current follows something except for "-".
---
Documentation/trace/fprobetrace.rst | 1 +
Documentation/trace/kprobetrace.rst | 1 +
kernel/trace/trace.c | 2 +-
kernel/trace/trace_probe.c | 34 +++++++++++++++++++++++++++++++++-
kernel/trace/trace_probe.h | 1 +
kernel/trace/trace_probe_tmpl.h | 3 +++
6 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst
index 290a9e6f7491..3392cab016b3 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -50,6 +50,7 @@ Synopsis of fprobe-events
$argN : Fetch the Nth function argument. (N >= 1) (\*2)
$retval : Fetch return value.(\*3)
$comm : Fetch current task comm.
+ $current : Fetch the address of the current task_struct.
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*4)(\*5)
\IMM : Store an immediate value to the argument.
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
index a62707e6a9f2..81e4fe38791d 100644
--- a/Documentation/trace/kprobetrace.rst
+++ b/Documentation/trace/kprobetrace.rst
@@ -53,6 +53,7 @@ Synopsis of kprobe_events
$argN : Fetch the Nth function argument. (N >= 1) (\*1)
$retval : Fetch return value.(\*2)
$comm : Fetch current task comm.
+ $current : Fetch the address of the current task_struct.
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*3)(\*4)
\IMM : Store an immediate value to the argument.
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 0e36af853199..e185a006cb08 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4329,7 +4329,7 @@ static const char readme_msg[] =
"\t [(structname[,field])](fetcharg)->field[->field|.field...],\n"
#endif
#else
- "\t $stack<index>, $stack, $retval, $comm,\n"
+ "\t $stack<index>, $stack, $retval, $comm, $current\n"
#endif
"\t +|-[u]<offset>(<fetcharg>), \\imm-value, \\\"imm-string\"\n"
"\t kernel return probes support: $retval, $arg<N>, $comm\n"
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index 3ead93de2d93..017f30ae9def 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -718,6 +718,20 @@ static int parse_btf_arg(char *varname,
return -EOPNOTSUPP;
}
+ if (strcmp(varname, "$current") == 0) {
+ code->op = FETCH_OP_CURRENT;
+ /* If no typecast is specified for $current, use task_struct by default */
+ if (!ctx->struct_btf) {
+ tid = bpf_find_btf_id("task_struct", BTF_KIND_STRUCT, &ctx->struct_btf);
+ if (tid < 0) {
+ trace_probe_log_err(ctx->offset, NO_BTF_ENTRY);
+ return -ENOENT;
+ }
+ ctx->last_struct = btf_type_skip_modifiers(ctx->struct_btf, tid, &tid);
+ }
+ goto found;
+ }
+
if (ctx->flags & TPARG_FL_TEVENT) {
ret = parse_trace_event(varname, code, ctx);
if (ret < 0) {
@@ -756,8 +770,8 @@ static int parse_btf_arg(char *varname,
return -ENOENT;
}
}
- params = ctx->params;
+ params = ctx->params;
for (i = 0; i < ctx->nr_params; i++) {
const char *name = btf_name_by_offset(ctx->btf, params[i].name_off);
@@ -1247,6 +1261,24 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
return 0;
}
+ /* $current returns the address of the current task_struct. */
+ if (str_has_prefix(arg, "current")) {
+ /* $current is only supported by kernel probe. */
+ if (!(ctx->flags & TPARG_FL_KERNEL)) {
+ err = TP_ERR_BAD_VAR;
+ goto inval;
+ }
+ arg += strlen("current");
+ if (*arg == '-' && IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS))
+ return parse_btf_arg(orig_arg, pcode, end, ctx);
+
+ if (*arg != '\0')
+ goto inval;
+
+ code->op = FETCH_OP_CURRENT;
+ return 0;
+ }
+
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
len = str_has_prefix(arg, "arg");
if (len) {
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 44f113faae61..62645e847bd1 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -96,6 +96,7 @@ enum fetch_op {
FETCH_OP_FOFFS, /* File offset: .immediate */
FETCH_OP_DATA, /* Allocated data: .data */
FETCH_OP_EDATA, /* Entry data: .offset */
+ FETCH_OP_CURRENT, /* Current task_struct address */
// Stage 2 (dereference) op
FETCH_OP_DEREF, /* Dereference: .offset */
FETCH_OP_UDEREF, /* User-space Dereference: .offset */
diff --git a/kernel/trace/trace_probe_tmpl.h b/kernel/trace/trace_probe_tmpl.h
index f39b37fcdb3b..f630930288d2 100644
--- a/kernel/trace/trace_probe_tmpl.h
+++ b/kernel/trace/trace_probe_tmpl.h
@@ -112,6 +112,9 @@ process_common_fetch_insn(struct fetch_insn *code, unsigned long *val)
case FETCH_OP_DATA:
*val = (unsigned long)code->data;
break;
+ case FETCH_OP_CURRENT:
+ *val = (unsigned long)current;
+ break;
default:
return -EILSEQ;
}
^ permalink raw reply related
* [PATCH v3 4/7] tracing/probes: Support field specifier option for typecast
From: Masami Hiramatsu (Google) @ 2026-06-14 14:54 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178144880282.159464.16882854283219530040.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add a field specifier option for the typecast. This works like
container_of() macro.
(STRUCT[,FIELD[.FIELD2...]])VAR
This is equivalent to :
container_of(VAR, struct STRUCT, FIELD[.FIELD2...])
For example:
echo "f tick_nohz_handler next_tick=(tick_sched,sched_timer)timer->next_tick" >> dynamic_events
This will trace tick_nohz_handler() with its tick_sched::next_tick which
is converted from @timer by contianer_of(tick, struct tick_sched, sched_timer).
So, if you enabkle both fprobes:tick_nohz_handler__entry and
timer:hrtimer_expire_entry events, we will see something like:
<idle>-0 [002] d.h1. 3778.087272: hrtimer_expire_entry: hrtimer=00000000d63db328 f
unction=tick_nohz_handler now=3777450051040
<idle>-0 [002] d.h1. 3778.087281: tick_nohz_handler__entry: (tick_nohz_handler+0x4
/0x140) next_tick=3777450000000
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v3:
- Fix error caret position.
Changes in v2:
- Use byteoffset for typecast field offset instead of bitoffset. This fixes negative modulo calculation.
- Check whether a field is specified after typecast.
- Reject if typecast field option has arrow operator.
---
Documentation/trace/eprobetrace.rst | 5 +
Documentation/trace/fprobetrace.rst | 8 +-
Documentation/trace/kprobetrace.rst | 8 +-
kernel/trace/trace.c | 4 -
kernel/trace/trace_probe.c | 179 ++++++++++++++++++++++++-----------
kernel/trace/trace_probe.h | 5 +
6 files changed, 142 insertions(+), 67 deletions(-)
diff --git a/Documentation/trace/eprobetrace.rst b/Documentation/trace/eprobetrace.rst
index cd0b4aa7f896..680e0af43d5d 100644
--- a/Documentation/trace/eprobetrace.rst
+++ b/Documentation/trace/eprobetrace.rst
@@ -49,7 +49,10 @@ Synopsis of eprobe_events
(STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
a pointer to STRUCT and then derference the pointer defined by
->MEMBER. Note that when this is used, the FIELD name does not
- need to be prefixed with a '$'.
+ need to be prefixed with a '$'. ASGN can be specified optionally.
+ If ASGN is specified, FIELD will be cast to the same offset
+ position as the ASGN member, rather than to the beginning of
+ the STRUCT.
(STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
also be used with another FETCHARG instead of FIELD.
diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst
index 6b8bb27bb62d..290a9e6f7491 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -57,10 +57,12 @@ Synopsis of fprobe-events
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
(x8/x16/x32/x64), "char", "string", "ustring", "symbol", "symstr"
and bitfield are supported.
- (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
+ (STRUCT[,ASGN])FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
a pointer to STRUCT and then derference the pointer defined by
- ->MEMBER.
- (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+ ->MEMBER. ASGN can be specified optionally. If ASGN is specified,
+ FIELD will be cast to the same offset position as the ASGN member,
+ rather than to the beginning of the STRUCT.
+ (STRUCT[,ASGN])(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
also be used with another FETCHARG instead of FIELD.
(\*1) This is available only when BTF is enabled.
diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
index c4382765d5b2..a62707e6a9f2 100644
--- a/Documentation/trace/kprobetrace.rst
+++ b/Documentation/trace/kprobetrace.rst
@@ -61,11 +61,13 @@ Synopsis of kprobe_events
(x8/x16/x32/x64), VFS layer common type(%pd/%pD), "char",
"string", "ustring", "symbol", "symstr" and bitfield are
supported.
- (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
+ (STRUCT[,ASGN])FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
a pointer to STRUCT and then derference the pointer defined by
->MEMBER. Note that this is available only when the probe is
- on function entry.
- (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+ on function entry. ASGN can be specified optionally. If ASGN
+ is specified, FIELD will be cast to the same offset position
+ as the ASGN member, rather than to the beginning of the STRUCT.
+ (STRUCT[,ASGN])(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
also be used with another FETCHARG instead of FIELD.
(\*1) only for the probe on function entry (offs == 0). Note, this argument access
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 4f70318918c2..0e36af853199 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4325,8 +4325,8 @@ static const char readme_msg[] =
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
- "\t [(structname)]<argname>[->field[->field|.field...]],\n"
- "\t [(structname)](fetcharg)->field[->field|.field...],\n"
+ "\t [(structname[,field])]<argname>[->field[->field|.field...]],\n"
+ "\t [(structname[,field])](fetcharg)->field[->field|.field...],\n"
#endif
#else
"\t $stack<index>, $stack, $retval, $comm,\n"
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index dba73aaa8ade..3ead93de2d93 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -574,6 +574,65 @@ static int split_next_field(char *varname, char **next_field,
return ret;
}
+/* Inner loop for solving dot operator ('.'). Return bit-offset of the given field */
+static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **ptype,
+ struct traceprobe_parse_context *ctx)
+{
+ const struct btf_type *type = *ptype;
+ const struct btf_member *field;
+ struct btf *btf = ctx_btf(ctx);
+ char *fieldname = *pfieldname;
+ int bitoffs = 0;
+ u32 anon_offs;
+ char *next;
+ int is_ptr;
+ s32 tid;
+
+ do {
+ next = NULL;
+ is_ptr = split_next_field(fieldname, &next, ctx);
+ if (is_ptr < 0)
+ return is_ptr;
+
+ anon_offs = 0;
+ field = btf_find_struct_member(btf, type, fieldname,
+ &anon_offs);
+ if (IS_ERR(field)) {
+ trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+ return PTR_ERR(field);
+ }
+ if (!field) {
+ trace_probe_log_err(ctx->offset, NO_BTF_FIELD);
+ return -ENOENT;
+ }
+ /* Add anonymous structure/union offset */
+ bitoffs += anon_offs;
+
+ /* Accumulate the bit-offsets of the dot-connected fields */
+ if (btf_type_kflag(type)) {
+ bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
+ ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
+ } else {
+ bitoffs += field->offset;
+ ctx->last_bitsize = 0;
+ }
+
+ type = btf_type_skip_modifiers(btf, field->type, &tid);
+ if (!type) {
+ trace_probe_log_err(ctx->offset, BAD_BTF_TID);
+ return -EINVAL;
+ }
+
+ if (next)
+ ctx->offset += next - fieldname;
+ fieldname = next;
+ } while (!is_ptr && fieldname);
+
+ *pfieldname = fieldname;
+ *ptype = type;
+
+ return bitoffs;
+}
/*
* Parse the field of data structure. The @type must be a pointer type
* pointing the target data structure type.
@@ -583,16 +642,14 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
struct traceprobe_parse_context *ctx)
{
struct fetch_insn *code = *pcode;
- const struct btf_member *field;
- u32 bitoffs, anon_offs;
- bool is_struct = ctx->struct_btf != NULL;
struct btf *btf = ctx_btf(ctx);
- char *next;
- int is_ptr;
+ bool is_first_field = true;
+ int bitoffs;
s32 tid;
do {
- if (!is_struct) {
+ /* For the first field of typecast, @type will be the target structure type. */
+ if (!(is_first_field && ctx->struct_btf)) {
/* Outer loop for solving arrow operator ('->') */
if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
@@ -606,60 +663,25 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
return -EINVAL;
}
}
- /* Only the first type can skip being a pointer */
- is_struct = false;
-
- bitoffs = 0;
- do {
- /* Inner loop for solving dot operator ('.') */
- next = NULL;
- is_ptr = split_next_field(fieldname, &next, ctx);
- if (is_ptr < 0)
- return is_ptr;
-
- anon_offs = 0;
- field = btf_find_struct_member(btf, type, fieldname,
- &anon_offs);
- if (IS_ERR(field)) {
- trace_probe_log_err(ctx->offset, BAD_BTF_TID);
- return PTR_ERR(field);
- }
- if (!field) {
- trace_probe_log_err(ctx->offset, NO_BTF_FIELD);
- return -ENOENT;
- }
- /* Add anonymous structure/union offset */
- bitoffs += anon_offs;
-
- /* Accumulate the bit-offsets of the dot-connected fields */
- if (btf_type_kflag(type)) {
- bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
- ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
- } else {
- bitoffs += field->offset;
- ctx->last_bitsize = 0;
- }
-
- type = btf_type_skip_modifiers(btf, field->type, &tid);
- if (!type) {
- trace_probe_log_err(ctx->offset, BAD_BTF_TID);
- return -EINVAL;
- }
-
- ctx->offset += next - fieldname;
- fieldname = next;
- } while (!is_ptr && fieldname);
+ bitoffs = get_bitoffset_of_field(&fieldname, &type, ctx);
+ if (bitoffs < 0)
+ return bitoffs;
if (++code == end) {
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
return -EINVAL;
}
code->op = FETCH_OP_DEREF; /* TODO: user deref support */
code->offset = bitoffs / 8;
+ if (is_first_field && ctx->struct_btf) {
+ /* The first field can be typecasted with field option. */
+ code->offset -= ctx->prefix_byteoffs;
+ }
*pcode = code;
ctx->last_bitoffs = bitoffs % 8;
ctx->last_type = type;
+ is_first_field = false;
} while (fieldname);
return 0;
@@ -690,6 +712,11 @@ static int parse_btf_arg(char *varname,
NOSUP_DAT_ARG);
return -EOPNOTSUPP;
}
+ if (!field && ctx->struct_btf) {
+ /* Typecast without field option is not supported */
+ trace_probe_log_err(ctx->offset + strlen(varname), TYPECAST_REQ_FIELD);
+ return -EOPNOTSUPP;
+ }
if (ctx->flags & TPARG_FL_TEVENT) {
ret = parse_trace_event(varname, code, ctx);
@@ -700,8 +727,7 @@ static int parse_btf_arg(char *varname,
/* TEVENT is only here via a typecast */
if (WARN_ON_ONCE(ctx->struct_btf == NULL))
return -EINVAL;
- type = ctx->last_struct;
- goto found_type;
+ goto found;
}
if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) {
@@ -763,7 +789,6 @@ static int parse_btf_arg(char *varname,
type = ctx->last_struct;
else
type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
-found_type:
if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
return -EINVAL;
@@ -832,6 +857,46 @@ static int query_btf_struct(const char *sname, struct traceprobe_parse_context *
return 0;
}
+static int parse_btf_casttype(char *casttype, struct traceprobe_parse_context *ctx)
+{
+ char *field;
+ int ret;
+
+ /* Field option - evaluated later. */
+ field = strchr(casttype, ',');
+ if (field)
+ *field++ = '\0';
+
+ ret = query_btf_struct(casttype, ctx);
+ if (ret < 0) {
+ trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
+ return -EINVAL;
+ }
+
+ if (field) {
+ struct btf_type *type = (struct btf_type *)ctx->last_struct;
+
+ ctx->offset += field - casttype;
+ ret = get_bitoffset_of_field(&field, &ctx->last_struct, ctx);
+ if (ret < 0)
+ return ret;
+ if (ret % 8) {
+ trace_probe_log_err(ctx->offset, TYPECAST_NOT_ALIGNED);
+ return -EINVAL;
+ }
+ if (field != NULL) {
+ /* this means @field skips an arrow operator ("->"). */
+ trace_probe_log_err(ctx->offset - 2, TYPECAST_BAD_ARROW);
+ return -EINVAL;
+ }
+ ctx->prefix_byteoffs = ret / 8;
+ /* Restore the original struct type (overwritten by get_bitoffset_of_field) */
+ ctx->last_struct = type;
+ }
+
+ return ret;
+}
+
/* Find the matching closing parenthesis for a given opening parenthesis. */
static char *find_matched_close_paren(char *s)
{
@@ -915,11 +980,10 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
nested = true;
}
- ret = query_btf_struct(arg + 1, ctx);
- if (ret < 0) {
- trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
- return -EINVAL;
- }
+ ctx->offset = orig_offset + 1; /* for the '(' */
+ ret = parse_btf_casttype(arg + 1, ctx);
+ if (ret < 0)
+ return ret;
ctx->offset = orig_offset + tmp - arg;
/* If it is nested, tmp points to the field name. */
@@ -927,6 +991,7 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx);
else
ret = parse_btf_arg(tmp, pcode, end, ctx);
+ ctx->prefix_byteoffs = 0;
return ret;
}
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 982d32a5df8b..44f113faae61 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -436,6 +436,7 @@ struct traceprobe_parse_context {
unsigned int flags;
int offset;
int nested_level;
+ int prefix_byteoffs; /* The byte offset of the prefix field of typecast */
};
#define TRACEPROBE_MAX_NESTED_LEVEL 3
@@ -576,7 +577,9 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \
C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"), \
C(TYPECAST_REQ_FIELD, "Typecast requires a field access"), \
- C(TOO_MANY_NESTED, "Too many nested typecasts/dereferences"),
+ C(TOO_MANY_NESTED, "Too many nested typecasts/dereferences"), \
+ C(TYPECAST_NOT_ALIGNED, "Typecast field option is not byte-aligned"), \
+ C(TYPECAST_BAD_ARROW, "Typecast field option does not support -> operator"),
#undef C
#define C(a, b) TP_ERR_##a
^ permalink raw reply related
* [PATCH v3 3/7] tracing/probes: Support nested typecast
From: Masami Hiramatsu (Google) @ 2026-06-14 14:53 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178144880282.159464.16882854283219530040.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
When we hit an open parenthesis right after typecast closing
parenthesis, it means we have nested typecast. This allows us to
typecast a generic data member in a structure to a pointer to
another structure.
For example, to cast a DATA_MEMBER of VAR structure to STRUCT pointer
and get MEMBER value.
(STRUCT)(VAR->DATA_MEMBER)->MEMBER
Also, we can nest typecast.
(STRUCT1)((STRUCT2)$ARG->FIELD2)->FIELD1
Currently the max nest level is limited to 3.
This also allows user to use typecasting for registers or stacks on
kprobe events. e.g.
(STRUCT)(%ax)->MEMBER
(STRUCT)($stack0)->MEMBER
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v2:
- Fix to skip "->" after closing parenthetsis.
---
Documentation/trace/eprobetrace.rst | 2 +
Documentation/trace/fprobetrace.rst | 2 +
Documentation/trace/kprobetrace.rst | 2 +
kernel/trace/trace.c | 1
kernel/trace/trace_probe.c | 76 ++++++++++++++++++++++++++++++++---
kernel/trace/trace_probe.h | 7 +++
6 files changed, 82 insertions(+), 8 deletions(-)
diff --git a/Documentation/trace/eprobetrace.rst b/Documentation/trace/eprobetrace.rst
index fe3602540569..cd0b4aa7f896 100644
--- a/Documentation/trace/eprobetrace.rst
+++ b/Documentation/trace/eprobetrace.rst
@@ -50,6 +50,8 @@ Synopsis of eprobe_events
a pointer to STRUCT and then derference the pointer defined by
->MEMBER. Note that when this is used, the FIELD name does not
need to be prefixed with a '$'.
+ (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+ also be used with another FETCHARG instead of FIELD.
Types
-----
diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst
index 7435ded2d66d..6b8bb27bb62d 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -60,6 +60,8 @@ Synopsis of fprobe-events
(STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
a pointer to STRUCT and then derference the pointer defined by
->MEMBER.
+ (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+ also be used with another FETCHARG instead of FIELD.
(\*1) This is available only when BTF is enabled.
(\*2) only for the probe on function entry (offs == 0). Note, this argument access
diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
index f73614997d52..c4382765d5b2 100644
--- a/Documentation/trace/kprobetrace.rst
+++ b/Documentation/trace/kprobetrace.rst
@@ -65,6 +65,8 @@ Synopsis of kprobe_events
a pointer to STRUCT and then derference the pointer defined by
->MEMBER. Note that this is available only when the probe is
on function entry.
+ (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
+ also be used with another FETCHARG instead of FIELD.
(\*1) only for the probe on function entry (offs == 0). Note, this argument access
is best effort, because depending on the argument type, it may be passed on
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index aa93e7b01146..4f70318918c2 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4326,6 +4326,7 @@ static const char readme_msg[] =
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
"\t [(structname)]<argname>[->field[->field|.field...]],\n"
+ "\t [(structname)](fetcharg)->field[->field|.field...],\n"
#endif
#else
"\t $stack<index>, $stack, $retval, $comm,\n"
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index 9158f1f22a62..dba73aaa8ade 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -832,10 +832,35 @@ static int query_btf_struct(const char *sname, struct traceprobe_parse_context *
return 0;
}
+/* Find the matching closing parenthesis for a given opening parenthesis. */
+static char *find_matched_close_paren(char *s)
+{
+ char *p = s;
+ int count = 0;
+
+ while (*p) {
+ if (*p == '(')
+ count++;
+ else if (*p == ')') {
+ if (--count == 0)
+ return p;
+ }
+ p++;
+ }
+ return NULL;
+}
+
+static int
+parse_probe_arg(char *arg, const struct fetch_type *type,
+ struct fetch_insn **pcode, struct fetch_insn *end,
+ struct traceprobe_parse_context *ctx);
+
static int handle_typecast(char *arg, struct fetch_insn **pcode,
struct fetch_insn *end,
struct traceprobe_parse_context *ctx)
{
+ int orig_offset = ctx->offset;
+ bool nested = false;
char *tmp;
int ret;
@@ -852,19 +877,56 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
DEREF_OPEN_BRACE);
return -EINVAL;
}
- *tmp = '\0';
- ret = query_btf_struct(arg + 1, ctx);
- *tmp = ')';
+ *tmp++ = '\0';
+
+ /* Handle the nested structure like (STRUCT)(VAR->FIELD)->... */
+ if (*tmp == '(') {
+ char *close = find_matched_close_paren(tmp);
+
+ ctx->offset += tmp - arg;
+ if (!close) {
+ trace_probe_log_err(ctx->offset, DEREF_OPEN_BRACE);
+ return -EINVAL;
+ }
+ /* We expect a field access for typecast */
+ if (close[1] != '-' || close[2] != '>') {
+ trace_probe_log_err(ctx->offset + close - tmp + 1,
+ TYPECAST_REQ_FIELD);
+ return -EINVAL;
+ }
+ ctx->nested_level++;
+ if (ctx->nested_level > TRACEPROBE_MAX_NESTED_LEVEL) {
+ trace_probe_log_err(ctx->offset, TOO_MANY_NESTED);
+ return -E2BIG;
+ }
+ *close = '\0';
+
+ ctx->offset += 1; /* for the '(' */
+ /* We need to parse the nested one */
+ ret = parse_probe_arg(tmp + 1, find_fetch_type(NULL, ctx->flags),
+ pcode, end, ctx);
+ if (ret < 0)
+ return ret;
+ ctx->nested_level--;
+ clear_struct_btf(ctx);
+
+ tmp = close + 3;/* Skip "->" after closing parenthesis */
+ nested = true;
+ }
+
+ ret = query_btf_struct(arg + 1, ctx);
if (ret < 0) {
trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
return -EINVAL;
}
- tmp++;
-
- ctx->offset += tmp - arg;
- ret = parse_btf_arg(tmp, pcode, end, ctx);
+ ctx->offset = orig_offset + tmp - arg;
+ /* If it is nested, tmp points to the field name. */
+ if (nested)
+ ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx);
+ else
+ ret = parse_btf_arg(tmp, pcode, end, ctx);
return ret;
}
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 883938a74aee..982d32a5df8b 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -435,8 +435,11 @@ struct traceprobe_parse_context {
struct trace_probe *tp;
unsigned int flags;
int offset;
+ int nested_level;
};
+#define TRACEPROBE_MAX_NESTED_LEVEL 3
+
extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i,
const char *argv,
struct traceprobe_parse_context *ctx);
@@ -571,7 +574,9 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(TOO_MANY_ARGS, "Too many arguments are specified"), \
C(TOO_MANY_EARGS, "Too many entry arguments specified"), \
C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \
- C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"),
+ C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"), \
+ C(TYPECAST_REQ_FIELD, "Typecast requires a field access"), \
+ C(TOO_MANY_NESTED, "Too many nested typecasts/dereferences"),
#undef C
#define C(a, b) TP_ERR_##a
^ permalink raw reply related
* [PATCH v3 2/7] tracing/probes: Support typecast for various probe events
From: Masami Hiramatsu (Google) @ 2026-06-14 14:53 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178144880282.159464.16882854283219530040.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Support BTF typecast feature on other probe events, but only if it is
kernel function entry or return, and must use function parameter name
or $retval. This means you can do:
(STRUCT)PARAM->MEMBER
but you can not do (this should be enabled by nesting support)
(STRUCT)%reg->MEMBER
To support other probe events, we just need to use last_struct type
when we find a function parameter in parse_btf_arg().
This also update <tracefs>/README file to show struct typecast.
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v3:
- Clarify the limitation.
Changes in v2:
- Fix to re-enable typecast on eprobe.
---
Documentation/trace/fprobetrace.rst | 3 +++
Documentation/trace/kprobetrace.rst | 4 ++++
kernel/trace/trace.c | 2 +-
kernel/trace/trace_probe.c | 14 +++++++++-----
kernel/trace/trace_probe.h | 5 +++++
5 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst
index b4c2ca3d02c1..7435ded2d66d 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -57,6 +57,9 @@ Synopsis of fprobe-events
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
(x8/x16/x32/x64), "char", "string", "ustring", "symbol", "symstr"
and bitfield are supported.
+ (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
+ a pointer to STRUCT and then derference the pointer defined by
+ ->MEMBER.
(\*1) This is available only when BTF is enabled.
(\*2) only for the probe on function entry (offs == 0). Note, this argument access
diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
index 3b6791c17e9b..f73614997d52 100644
--- a/Documentation/trace/kprobetrace.rst
+++ b/Documentation/trace/kprobetrace.rst
@@ -61,6 +61,10 @@ Synopsis of kprobe_events
(x8/x16/x32/x64), VFS layer common type(%pd/%pD), "char",
"string", "ustring", "symbol", "symstr" and bitfield are
supported.
+ (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
+ a pointer to STRUCT and then derference the pointer defined by
+ ->MEMBER. Note that this is available only when the probe is
+ on function entry.
(\*1) only for the probe on function entry (offs == 0). Note, this argument access
is best effort, because depending on the argument type, it may be passed on
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 6eb4d3097a4d..aa93e7b01146 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4325,7 +4325,7 @@ static const char readme_msg[] =
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
- "\t <argname>[->field[->field|.field...]],\n"
+ "\t [(structname)]<argname>[->field[->field|.field...]],\n"
#endif
#else
"\t $stack<index>, $stack, $retval, $comm,\n"
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index fd1caa1f9723..9158f1f22a62 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -759,7 +759,10 @@ static int parse_btf_arg(char *varname,
return -ENOENT;
found:
- type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
+ if (ctx->struct_btf)
+ type = ctx->last_struct;
+ else
+ type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
found_type:
if (!type) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
@@ -836,10 +839,11 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
char *tmp;
int ret;
- /* Currently this only works for eprobes */
- if (!(ctx->flags & TPARG_FL_TEVENT)) {
- trace_probe_log_err(ctx->offset, TYPECAST_NOT_EVENT);
- return -EINVAL;
+ if (!(tparg_is_event_probe(ctx->flags) ||
+ tparg_is_function_entry(ctx->flags) ||
+ tparg_is_function_return(ctx->flags))) {
+ trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
+ return -EOPNOTSUPP;
}
tmp = strchr(arg, ')');
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 15758cc11fc6..883938a74aee 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -414,6 +414,11 @@ static inline bool tparg_is_function_return(unsigned int flags)
return (flags & TPARG_FL_LOC_MASK) == (TPARG_FL_KERNEL | TPARG_FL_RETURN);
}
+static inline bool tparg_is_event_probe(unsigned int flags)
+{
+ return !!(flags & TPARG_FL_TEVENT);
+}
+
struct traceprobe_parse_context {
struct trace_event_call *event;
/* BTF related parameters */
^ permalink raw reply related
* [PATCH v3 1/7] tracing/events: Fix to check the simple_tsk_fn creation
From: Masami Hiramatsu (Google) @ 2026-06-14 14:53 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
In-Reply-To: <178144880282.159464.16882854283219530040.stgit@devnote2>
From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Sashiko pointed that this sample code does not correctly handle the
failure of thread creation because kthread_run() can return -errno.
Check the simple_tsk_fn is correctly initialized (created) or not.
Link: https://sashiko.dev/#/patchset/178092865666.163648.10457567771536160909.stgit%40devnote2
Fixes: 9cfe06f8cd5c ("tracing/events: add trace-events-sample")
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v3:
- Recover the usage counter.
---
samples/trace_events/trace-events-sample.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/samples/trace_events/trace-events-sample.c b/samples/trace_events/trace-events-sample.c
index ecc7db237f2e..82344a78e471 100644
--- a/samples/trace_events/trace-events-sample.c
+++ b/samples/trace_events/trace-events-sample.c
@@ -107,6 +107,11 @@ int foo_bar_reg(void)
* for consistency sake, we still take the thread_mutex.
*/
simple_tsk_fn = kthread_run(simple_thread_fn, NULL, "event-sample-fn");
+ if (IS_ERR_OR_NULL(simple_tsk_fn)) {
+ pr_err("Failed to create simple_thread_fn");
+ simple_thread_cnt--;
+ simple_tsk_fn = NULL;
+ }
out:
mutex_unlock(&thread_mutex);
return 0;
^ permalink raw reply related
* [PATCH v3 0/7] tracing/probes: Add more typecast features
From: Masami Hiramatsu (Google) @ 2026-06-14 14:53 UTC (permalink / raw)
To: Steven Rostedt, Mathieu Desnoyers
Cc: Jonathan Corbet, Shuah Khan, Masami Hiramatsu, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest
Hi,
Here is the 3rd version of series to introduce more typecast features
to probe events. The previous version is here:
https://lore.kernel.org/all/178105268094.21760.13668249930524377840.stgit@devnote2/
In this version, I fixed various problems Sashiko reviewed and add new
test cases.
Steve introduced BTF typecast feature for eprobe[1].
This series extends it and add more options:
1. Expanding BTF typecast to kprobe and fprobe.
(currently only function entry/exit)
2. Introduce container_of like typecast. This adds a "assigned
member" option to the typecast.
(STRUCT,MEMBER)VAR->ANOTHER_MEMBER
This casts VAR to STRUCT type but the VAR is as the address
of STRUCT.MEMBER. In C, it is:
container_of(VAR, STRUCT, MEMBER)->ANOTHER_MEMBER
3. Support nested typecast, e.g.
(STRUCT)((STRUCT2)VAR->MEMBER2)->MEMBER
the nest level must be smaller than 3.
4. Add $current variable to point "current" task_struct.
This is useful with typecast, e.g.
(task_struct)$current->pid
5. per-cpu dereference support.
Intrdouce this_cpu_read(VAR) and this_cpu_ptr(VAR) to
access per-cpu data on the current CPU (accessing other CPU
data is not stable, because it can be changed.)
You can access the member of per-cpu data structure using
typecast like:
(STRUCT)this_cpu_ptr(VAR)->MEMBER
And added a test script to test part of them.
[1] https://lore.kernel.org/all/20260601130746.2139d926@gandalf.local.home/
---
Masami Hiramatsu (Google) (7):
tracing/events: Fix to check the simple_tsk_fn creation
tracing/probes: Support typecast for various probe events
tracing/probes: Support nested typecast
tracing/probes: Support field specifier option for typecast
tracing/probes: Add $current variable support
tracing/probes: Add this_cpu_read() and this_cpu_ptr() dereference method to fetcharg
tracing/probes: Add a new testcase for BTF typecasts
Documentation/trace/eprobetrace.rst | 9
Documentation/trace/fprobetrace.rst | 10
Documentation/trace/kprobetrace.rst | 11 +
kernel/trace/trace.c | 6
kernel/trace/trace_probe.c | 413 +++++++++++++++-----
kernel/trace/trace_probe.h | 19 +
kernel/trace/trace_probe_tmpl.h | 33 +-
samples/trace_events/trace-events-sample.c | 45 ++
samples/trace_events/trace-events-sample.h | 34 ++
.../ftrace/test.d/dynevent/btf_probe_event.tc | 51 ++
.../ftrace/test.d/dynevent/fprobe_syntax_errors.tc | 9
.../ftrace/test.d/kprobe/kprobe_syntax_errors.tc | 9
.../ftrace/test.d/kprobe/uprobe_syntax_errors.tc | 5
13 files changed, 540 insertions(+), 114 deletions(-)
create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/btf_probe_event.tc
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* [PATCH 1/1] tools/tracing/rtla: fix missing unistd include
From: Andreas Ziegler @ 2026-06-14 9:28 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: linux-trace-kernel, linux-kernel, Andreas Ziegler
Compiling RTLA 7.1-rc6 with GCC 16 and uClibc as standard library fails
with these errors:
src/common.c: In function ‘set_signals’:
src/common.c:40:17: error: implicit declaration of function ‘alarm’ [-Wimplicit-function-declaration]
40 | alarm(params->duration);
| ^~~~~
src/common.c: In function ‘common_apply_config’:
src/common.c:187:44: error: implicit declaration of function ‘getpid’; did you mean ‘getpt’? [-Wimplicit-function-declaration]
187 | retval = sched_setaffinity(getpid(), sizeof(params->hk_cpu_set),
| ^~~~~~
| getpt
In file included from src/common.c:9:
src/common.c: In function ‘run_tool’:
src/common.c:262:19: error: implicit declaration of function ‘sysconf’; did you mean ‘sscanf’? [-Wimplicit-function-declaration]
262 | nr_cpus = get_nprocs_conf();
| ^~~~~~~~~~~~~~~
src/common.c:262:19: error: ‘_SC_NPROCESSORS_CONF’ undeclared (first use in this function)
262 | nr_cpus = get_nprocs_conf();
| ^~~~~~~~~~~~~~~
src/common.c:262:19: note: each undeclared identifier is reported only once for each function it appears in
src/common.c:370:17: error: implicit declaration of function ‘sleep’ [-Wimplicit-function-declaration]
370 | sleep(1);
| ^~~~~
Restore the missing unistd.h include.
Fixes: <115b06a00875> (tools/rtla: Consolidate nr_cpus usage across all tools)
Signed-off-by: Andreas Ziegler <br025@umbiko.net>
---
tools/tracing/rtla/src/common.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c
index 35e3d3aa922e..5c5398d20f40 100644
--- a/tools/tracing/rtla/src/common.c
+++ b/tools/tracing/rtla/src/common.c
@@ -5,6 +5,7 @@
#include <signal.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
#include <getopt.h>
#include <sys/sysinfo.h>
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v3 0/9] rv/tlob: Add task latency over budget RV monitor
From: Wen Yang @ 2026-06-13 16:00 UTC (permalink / raw)
To: Gabriele Monaco; +Cc: Steven Rostedt, linux-trace-kernel, linux-kernel
In-Reply-To: <cover.1780847473.git.wen.yang@linux.dev>
Hi Gabriele,
Gentle ping on this series. Please let me know if there are any
concerns or if further changes are needed.
Thanks for your time,
Wen
On 6/8/26 00:13, wen.yang@linux.dev wrote:
> From: Wen Yang <wen.yang@linux.dev>
>
> This series introduces tlob (task latency over budget), a per-task
> hybrid automaton RV monitor that measures elapsed wall-clock time across
> a user-delimited code section and fires when the time exceeds a
> configurable budget.
>
> The series applies cleanly on top of:
> [1] git://git.kernel.org/pub/scm/linux/kernel/git/gmonaco/linux.git rv-fixes-7.1
> "rv fixes for v7.1"
>
> Background
> ----------
> The existing wwnr monitor uses a two-state DA to detect tasks that are
> woken but never run. tlob extends the RV framework to a three-state
> hybrid automaton:
>
> running (initial) -- on CPU
> waiting -- in the scheduler runqueue, not yet on CPU
> sleeping -- blocked on a lock, I/O, or similar resource
>
> A single HA clock invariant, clk_elapsed < BUDGET_NS(), is active in
> all states. The framework enforces it via a per-task hrtimer. On
> expiry, error_env_tlob is emitted, followed by detail_env_tlob which
> carries a per-state time breakdown (running_ns, waiting_ns, sleeping_ns)
> that pinpoints whether the overrun occurred in the running, waiting, or
> sleeping state.
>
> Userspace interface
> -------------------
> Tasks are registered for monitoring by writing to the tracefs monitor
> file:
>
> # echo "p /path/to/binary:START_OFFSET STOP_OFFSET threshold=NS" \
> > /sys/kernel/tracing/rv/monitors/tlob/monitor
>
> Two uprobes are registered at START_OFFSET (entry) and STOP_OFFSET
> (exit) of the delimited section. When a task executes the entry uprobe,
> the monitor starts; when the task reaches the exit uprobe or the budget
> expires, monitoring stops and the slot is returned to the pool.
>
> Multiple uprobe pairs can be registered for the same binary or different
> binaries. Each task can have at most one active monitoring session; if
> a task hits a start uprobe while already monitored, the prior session is
> cancelled and a new one begins.
>
> Series structure
> ----------------
> Patch 1: rv/da: introduce DA_MON_ALLOCATION_STRATEGY
> Consolidates per-object DA storage allocation under a compile-time
> selector with three strategies:
> DA_ALLOC_AUTO (default) - lock-free kmalloc_nolock; unbounded
> DA_ALLOC_POOL - pre-allocated fixed-size pool
> DA_ALLOC_MANUAL - caller pre-inserts storage
>
> da_handle_start_event() and da_handle_start_run_event() call
> da_prepare_storage() which resolves at compile time to the correct
> allocation function.
>
> This patch also includes critical correctness fixes for the pool
> implementation:
> - Add tracepoint_synchronize_unregister() in da_monitor_destroy_pool()
> to fix UAF where in-flight handlers access freed pool storage
> - Fix duplicate hash entry race in da_create_or_get_pool() via
> concurrent-insert detection under RCU
> - Add capacity field to fix build error (DA_MON_POOL_SIZE undeclared
> in da_pool_return_cb)
>
> Patch 2: rv: add generic uprobe infrastructure for RV monitors
> Introduces rv_uprobe, a thin wrapper around uprobe_consumer for RV
> monitors. Provides rv_uprobe_register(), rv_uprobe_unregister(),
> and rv_uprobe_sync() for safe teardown.
>
> Patch 3: rv/tlob: add tlob model DOT file
> The formal model used to generate tlob.h.
>
> Patch 4: rv/ha: fix ha_invariant_passed_ns silent bypass of invariant check
> Fixes a bug where ha_invariant_passed_ns() returned 0 early when
> env_store was invalid (U64_MAX), leaving it at U64_MAX and causing
> ha_check_invariant_ns() to always pass. The fix calls ha_reset_clk_ns()
> then ha_set_invariant_ns() on first use.
>
> Patch 5: rv/ha: make da_monitor_reset_hook and EVENT_NONE_LBL overridable
> Allows tlob to override EVENT_NONE_LBL for its start_tlob self-loop.
>
> Patch 6: rv/tlob: add tlob hybrid automaton monitor
> The main tlob implementation, including:
> - Three-state HA (running/waiting/sleeping)
> - Per-task hrtimer enforcement (HRTIMER_MODE_REL_HARD)
> - DA_ALLOC_POOL for allocation-free hot path
> - Uprobe registration via tracefs monitor file
> - Per-state time accumulation (running_ns, waiting_ns, sleeping_ns)
> - HIGH_RES_TIMERS dependency in Kconfig
>
> Patches 7-9: Tests
> - KUnit tests for tlob monitor
> - Selftest infrastructure fixes
> - tlob selftests (uprobe binding, state tracking, violation detection)
>
> Changes since v2
> ----------------
> All feedback from Gabriele Monaco has been addressed:
>
> -- Patch 02 (per-task slot ordering / ha_monitor_reset_env):
> Dropped from v3; rebased on top of Gabriele's series [1].
>
> -- Patch 03 (verificationtest-ktap):
> Changed to use realpath for robustness as suggested.
>
> -- Patch 04 (pre-allocated storage pool):
> Complete redesign as DA_MON_ALLOCATION_STRATEGY:
> - Three strategies (AUTO/POOL/MANUAL) via compile-time macro
> - da_monitor_init_prealloc() removed; da_monitor_init() selects
> internally
> - da_create_or_get_kmalloc() removed (no viable use case)
> - nomiss updated to use DA_ALLOC_MANUAL
> - da_extra_cleanup() hook added for per-entry teardown
>
> Critical bug fixes included in this patch:
> - tracepoint_synchronize_unregister() added to da_monitor_destroy_pool()
> to prevent UAF from in-flight handlers accessing freed pool storage
> - Duplicate hash entry race fixed in da_create_or_get_pool() via
> concurrent-insert detection and slot return under RCU
> - capacity field added to fix DA_MON_POOL_SIZE undeclared build error
>
> -- Patch 05 (generic uprobe infrastructure):
> Carried unchanged into v3.
>
> -- Patch 06 (rvgen __init arrow reset):
> Carried unchanged into v3.
>
> -- Patch 08 (tlob monitor):
> Split and refactored:
> - ioctl interface deferred to follow-up series (tracefs-only in v3)
> - Handler simplification: three inline helpers (tlob_acc_running/
> waiting/sleeping) with scoped_guard(rcu)
> - do_prev/do_next flags removed (da_handle_event skips unmonitored)
> - scoped_guard(rcu) and guard(mutex) applied throughout
> - tlob_stop_all() removed; da_extra_cleanup() hook used instead
> - start_tlob self-loop added to DOT model as suggested
> - ha_setup_invariants() guards against redundant timer restart
> - HIGH_RES_TIMERS dependency added to Kconfig
>
> Additional improvements in v3
> ------------------------------
> Beyond the v2 feedback, this version includes:
>
> 1. Simplified tlob monitor implementation:
> - Removed redundant tlob_num_monitored atomic counter
> (da_handle_event already handles unmonitored tasks via hash lookup)
> - Eliminated extra cacheline touch on every sched_switch/sched_wakeup
> - Several repeated pattern simplifications.
>
> 2. Extracted common accumulation logic:
> - __tlob_acc() using offsetof() replaces three nearly-identical functions
> - Reduces code duplication while maintaining type safety
>
> 3. Complete test coverage:
> - KUnit tests for core functionality
> - Comprehensive selftests for uprobe integration, state tracking,
> and violation detection
>
> Testing
> -------
> All patches have been tested on:
> - x86_64 with CONFIG_PREEMPT_RT
> - All KUnit tests pass
> - All selftests pass with verificationtest-ktap
>
>
> [1] git://git.kernel.org/pub/scm/linux/kernel/git/gmonaco/linux.git rv-fixes-7.1
> "rv fixes for v7.1"
>
>
> Wen Yang (9):
> rv/da: introduce DA_MON_ALLOCATION_STRATEGY
> rv: add generic uprobe infrastructure for RV monitors
> rv/tlob: add tlob model DOT file
> rv/ha: fix ha_invariant_passed_ns silent bypass of invariant check
> rv/ha: make da_monitor_reset_hook and EVENT_NONE_LBL overridable
> rv/tlob: add tlob hybrid automaton monitor
> rv/tlob: add KUnit tests for the tlob monitor
> selftests/verification: fix verificationtest-ktap for out-of-tree
> execution
> selftests/verification: add tlob selftests
>
> Documentation/trace/rv/index.rst | 1 +
> Documentation/trace/rv/monitor_tlob.rst | 177 ++++
> include/rv/da_monitor.h | 276 ++++-
> include/rv/ha_monitor.h | 22 +-
> include/rv/rv_uprobe.h | 119 +++
> kernel/trace/rv/Kconfig | 5 +
> kernel/trace/rv/Makefile | 3 +
> kernel/trace/rv/monitors/nomiss/nomiss.c | 6 +-
> kernel/trace/rv/monitors/tlob/.kunitconfig | 6 +
> kernel/trace/rv/monitors/tlob/Kconfig | 19 +
> kernel/trace/rv/monitors/tlob/tlob.c | 968 ++++++++++++++++++
> kernel/trace/rv/monitors/tlob/tlob.h | 148 +++
> kernel/trace/rv/monitors/tlob/tlob_kunit.c | 92 ++
> kernel/trace/rv/monitors/tlob/tlob_trace.h | 49 +
> kernel/trace/rv/rv_trace.h | 1 +
> kernel/trace/rv/rv_uprobe.c | 182 ++++
> .../testing/selftests/verification/.gitignore | 2 +
> tools/testing/selftests/verification/Makefile | 19 +-
> .../verification/test.d/tlob/Makefile | 20 +
> .../verification/test.d/tlob/test.d/functions | 1 +
> .../verification/test.d/tlob/tlob_sym.c | 189 ++++
> .../verification/test.d/tlob/tlob_target.c | 138 +++
> .../verification/test.d/tlob/uprobe_bind.tc | 37 +
> .../test.d/tlob/uprobe_detail_running.tc | 51 +
> .../test.d/tlob/uprobe_detail_sleeping.tc | 50 +
> .../test.d/tlob/uprobe_detail_waiting.tc | 66 ++
> .../verification/test.d/tlob/uprobe_multi.tc | 64 ++
> .../test.d/tlob/uprobe_no_event.tc | 19 +
> .../test.d/tlob/uprobe_violation.tc | 67 ++
> .../verification/verificationtest-ktap | 4 +-
> tools/verification/models/tlob.dot | 22 +
> 31 files changed, 2789 insertions(+), 34 deletions(-)
> create mode 100644 Documentation/trace/rv/monitor_tlob.rst
> create mode 100644 include/rv/rv_uprobe.h
> create mode 100644 kernel/trace/rv/monitors/tlob/.kunitconfig
> create mode 100644 kernel/trace/rv/monitors/tlob/Kconfig
> create mode 100644 kernel/trace/rv/monitors/tlob/tlob.c
> create mode 100644 kernel/trace/rv/monitors/tlob/tlob.h
> create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.c
> create mode 100644 kernel/trace/rv/monitors/tlob/tlob_trace.h
> create mode 100644 kernel/trace/rv/rv_uprobe.c
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/Makefile
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/test.d/functions
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/tlob_sym.c
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/tlob_target.c
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_running.tc
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_sleeping.tc
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_detail_waiting.tc
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc
> create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc
> create mode 100644 tools/verification/models/tlob.dot
>
^ permalink raw reply
* Re: [RESEND][PATCH v2] unwind: Add sframe_(un)register() system calls
From: Fangrui Song @ 2026-06-13 4:18 UTC (permalink / raw)
To: Steven Rostedt
Cc: LKML, Linux Trace Kernel, bpf, Masami Hiramatsu,
Mathieu Desnoyers, Jens Remus, Josh Poimboeuf, Peter Zijlstra,
Ingo Molnar, Jiri Olsa, Arnaldo Carvalho de Melo, Namhyung Kim,
Thomas Gleixner, Andrii Nakryiko, Indu Bhagat, Jose E. Marchesi,
Beau Belgrave, Linus Torvalds, Andrew Morton, Florian Weimer,
Kees Cook, Carlos O'Donell, Sam James, Dylan Hatch,
Borislav Petkov, Dave Hansen, David Hildenbrand, H. Peter Anvin,
Liam R. Howlett, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
Suren Baghdasaryan, Vlastimil Babka, Heiko Carstens,
Vasily Gorbik, Thomas Weißschuh
In-Reply-To: <20260611072249.2222fd9d@gandalf.local.home>
On Thu, Jun 11, 2026 at 4:23 AM Steven Rostedt <rostedt@kernel.org> wrote:
>
> On Thu, 11 Jun 2026 00:00:25 -0700
> Fangrui Song <i@maskray.me> wrote:
>
> > Hi Steven,
> >
> > This is not an objection to deferred userspace unwinding itself -- my
> > concern is narrower: these syscalls permanently encode the kernel's
> > commitment to the SFrame format family at exactly the moment the
> > format's size trajectory is heading the wrong way, and while arguably
> > superior formats exist.
> >
> > I raised related size concerns about SFrame's viability for userspace
> > stack walking earlier:
> > https://lore.kernel.org/all/3xd4fqvwflefvsjjoagytoi3y3sf7lxqjremhe2zo5tounihe4@3ftafgryadsr/
> > ("Concerns about SFrame viability for userspace stack walking")
> >
> > SFrame v3 is even larger than v2.
> >
> > For comparison: Microsoft is currently upstreaming its Windows x64
> > Unwind V3 implementation to LLVM, which will make a side-by-side reading
> > of the two formats straightforward. Unwind V3 provides correct
> > exception-handling unwind -- full prologue replay, SEH handlers,
> > funclets -- and supports Intel APX. SFrame v3 provides stack tracing
> > only, no EH, yet comes out larger than .eh_frame. A format revision that
> > adds capability without adding bulk is demonstrably achievable; SFrame
> > v3 went the other way.
>
> My main concern is simplicity in implementation on the kernel side. One
> thing we would like to avoid is any interpreter that becomes basically
> executing user space code to perform the stack tracing (i.e. DWARF). I
> haven't looked at the Windows x64 but will do so.
I recognize interpreter complexity as a real and valid concern (though
I believe the concern can be alleviated with a good use of LLM
auditing).
But the SFrame family's size problem is more pressing than it's being
given credit for, because of a point that applies specifically to
x86-64: SFrame cannot *replace* .eh_frame there, only add to it (to
keep debugging and C++ exception handling working).
.eh_frame+.eh_frame_hdr almost takes 9%; adding .sframe adds another 9% on top.
I don't think the perceived benefit on x86-64 justifies near-doubling
the unwind metadata footprint.
x86-64 experiments with x64 Unwind V3 for ELF:
unwind B VM B % of VM
sqlite3 -O0
.eh_frame(+hdr) 42,120 446,735 9.43%
Win64 v1 24,536 438,609 5.59%
Win64 v3 33,296 450,889 7.38%
SFrame v3 39,105 440,727 8.87%
sqlite3 -O1
.eh_frame(+hdr) 44,424 479,348 9.27%
Win64 v1 15,496 450,724 3.44%
Win64 v3 21,816 458,916 4.75%
SFrame v3 44,756 477,776 9.37%
CGExpr.cpp -O3
.eh_frame(+hdr) 27,040 372,416 7.26%
Win64 v1 10,720 356,080 3.01%
Win64 v3 15,320 360,185 4.25%
SFrame v3 27,628 370,912 7.45%
> >
> > I understand IBM is doubling down on SFrame for their s390x and ppc64,
>
> That's because this is currently the only way s390 can perform stack
> walking in user space.
Hmm, I think that DWARF .eh_frame already works on s390x, but I take
your word that .sframe might be appealing for some use cases.
Perhaps s390x and x86-64 shouldn't be decided by the same vote.
> > but I'm not convinced the size overhead of v3 will make it appealing on
> > x86-64. I have learned that the person driving their SFrame work at
> > Google had left and the SFrame at data center effort was being
> > reevaluated per a toolchain manager.
> I believe the person who left Google that was driving the SFrame work was
> me ;-)
>
> Thanks,
>
> -- Steve
Ha, I wasn't referring to you. I had a different toolchain engineer in mind.
But that was a secondhand aside and not the point I want to rest
anything on, so I'll set it aside and stick to the bytes.
-----
Separately, on maturity and sequencing:
On timing: the SFrame v3 binutils set was first posted 2025-12-09; the
2.46 branch was cut 2026-01-17 and released 2026-02-08 with v3
generation. That's a ~5-week window, and I'm not convinced there's
been time for rigorous review of the v3 design; if flaws surface we're
at a v4, and kernel adoption still wouldn't be a given.
^ permalink raw reply
* Re: [PATCH] rtla: Simplify osnoise tracer option setting code
From: Crystal Wood @ 2026-06-12 17:54 UTC (permalink / raw)
To: Tomas Glozar, Steven Rostedt
Cc: John Kacur, Luis Goncalves, Costa Shulyupin, Wander Lairson Costa,
LKML, linux-trace-kernel
In-Reply-To: <20260612115121.54862-1-tglozar@redhat.com>
On Fri, 2026-06-12 at 13:51 +0200, Tomas Glozar wrote:
> Each osnoise tracer option (in /sys/kernel/tracing/osnoise) used by RTLA
> requires four functions to be defined:
>
> - static osnoise_get_<opt>() - to get the current value of the option
> and save it into struct osnoise_context's orig_<opt> field,
> - osnoise_set_<opt>() - to set the value of the option requested by the
> user after reading and saving the original with osnoise_get_<opt>(),
> and save it into <opt> field of struct osnoise_context,
> - osnoise_restore_<opt>() - restore the value recorded in orig_<opt>,
> - static osnoise_put_<opt>() - restore the value recorded in orig_<opt>
> and update <opt> to reflect that.
>
> The logic is duplicated for all the options, except for cpus (which is
> the only string option) and period/runtime (which are handled together
> and feature extra checks).
Thanks for taking this on... this is one of the things that was bugging me
during consolidation work that I didn't get to.
> Deduplicate the logic using a set of macros featuring the X macro
> pattern, defined in src/common.h:
>
> - OSNOISE_LL_OPTIONS, which invokes OSNOISE_LL_OPTION macro for all
> "long long" options,
> - OSNOISE_FLAG_OPTIONS, which invokes OSNOISE_FLAG_OPTION macro for all
> flag (boolean values in osnoise/options file) options.
>
> The list macros are then invoked in four places:
>
> - for struct osnoise_context fields in src/common.h,
> - for function declarations, moved into src/common.h from
> src/osnoise.h,
> - for function definitions in src/osnoise.c,
> - for context initialization and restoration, in osnoise_context_alloc()
> and osnoise_put_context(), both in src/osnoise.c.
>
> OSNOISE_LL_OPTIONS takes three options: name - struct osnoise_context
> field name (written "<opt>" above), path - filename inside
> /sys/kernel/tracing/osnoise passed to libtracefs, and init_val - initial
> value of struct fields, corresponding to an otherwise invalid option
> (some options use OSNOISE_OPTION_INIT_VAL = -1, some use
> OSNOISE_TIME_INIT_VAL = 0).
Can we simplify by always using -1? Especially since that's already
treated as the universal "invalid" by osnoise_read_ll_config().
FWIW using "init val" to mean "invalid" rather than "default" is a bit
unintuitive.
> OSNOISE_FLAG_OPTION is similar, but instead of path, it takes the option
> string inside /sys/kernel/tracing/osnoise/options (opt_string), and no
> init_val, as it is purely boolean (0 or 1).
>
> Previously, for options timerlat_align and osnoise_workload, the return
> value of osnoise_set_<opt>() distinguished between -2 (option cannot be
> set) and -1 (option not present). This distinction is expanded for all
> options for consistency; for most options, it is currently not used,
> only osnoise_workload is implemented to avoid error on -1 on older RTLA
> versions.
"on -1 on"?
> The change overall has two main benefits: it makes it much simpler to
> add a new option, as well as to change existing logic consistently for
> all of them. It also makes the code shorter by a bit over 500 lines.
>
> There is no intentional user-visible change coming from the refactoring.
> osnoise_restore_<opt>() for flag options now sets <opt> instead of
> orig_<opt>. As the latter is also set by osnoise_put_<opt>(), plus long
> long options set <opt> in both the old and new implementation, the old
> behavior was likely a mistake, and should not matter for now, as the
> options are only restored once at the end of tracing and neither <opt>
> nor orig_<opt> field is read again.
>
> Assisted-by: Claude:claude-opus-4-6
> Signed-off-by: Tomas Glozar <tglozar@redhat.com>
> ---
> tools/tracing/rtla/src/common.h | 79 +--
> tools/tracing/rtla/src/osnoise.c | 836 ++++++-------------------------
> tools/tracing/rtla/src/osnoise.h | 22 -
> 3 files changed, 188 insertions(+), 749 deletions(-)
While we're at it, can we move this code to common.c, and drop
"osnoise" from the names, to move closer to using that only for the
actual osnoise mode?
Or if we really want to namespace things that are specific to the
osnoise subsystem (i.e. everything implemented in trace_osnoise.c) but
not specific with respect to the osnoise/timerlat split, I'd suggest
something different like "osn_".
> + * Long long option get/set/restore/put functions, generated from OSNOISE_LL_OPTIONS.
> + */
> +#define OSNOISE_LL_OPTION(name, path, init_val) \
> +static long long \
> +osnoise_get_##name(struct osnoise_context *context) \
> +{ \
> + long long name; \
> + \
> + if (context->name != (init_val)) \
> + return context->name; \
> + \
> + if (context->orig_##name != (init_val)) \
> + return context->orig_##name; \
> + \
> + name = osnoise_read_ll_config(path); \
> + if (name < 0) \
> + return (init_val); \
> + \
> + context->orig_##name = name; \
> + return name; \
> +} \
> + \
> +int osnoise_set_##name(struct osnoise_context *context, long long name) \
> +{ \
> + long long curr = osnoise_get_##name(context); \
> + int retval; \
> + \
> + if (curr == (init_val)) \
> + return -1; \
> + \
> + retval = osnoise_write_ll_config(path, name); \
> + if (retval < 0) \
> + return -2; \
> + \
> + context->name = name; \
> + return 0; \
> +} \
Using "name" for the value is confusing... "val" would be better.
> + \
> +void osnoise_restore_##name(struct osnoise_context *context) \
> +{ \
> + int retval; \
> + \
> + if (context->orig_##name == (init_val)) \
> + return; \
> + \
> + if (context->orig_##name == context->name) \
> + goto out_done_##name; \
> + \
> + retval = osnoise_write_ll_config(path, context->orig_##name); \
> + if (retval < 0) \
> + err_msg("Could not restore original " #name "\n"); \
> + \
> +out_done_##name: \
> + context->name = (init_val); \
> +} \
Why does the label need to have ##name in it?
> + \
> +static void osnoise_put_##name(struct osnoise_context *context) \
> +{ \
> + osnoise_restore_##name(context); \
> + \
> + if (context->orig_##name == (init_val)) \
> + return; \
> + \
> + context->orig_##name = (init_val); \
> +}
[snip]
> +/*
> + * Flag option get/set/restore/put functions, generated from OSNOISE_FLAG_OPTIONS.
> + */
> +#define OSNOISE_FLAG_OPTION(name, option_str) \
> +static int osnoise_get_##name(struct osnoise_context *context) \
> +{ \
> + if (context->opt_##name != OSNOISE_OPTION_INIT_VAL) \
> + return context->opt_##name; \
> + \
> + if (context->orig_opt_##name != OSNOISE_OPTION_INIT_VAL) \
> + return context->orig_opt_##name; \
> + \
> + context->orig_opt_##name = osnoise_options_get_option(option_str); \
> + return context->orig_opt_##name; \
> +} \
> + \
> +int osnoise_set_##name(struct osnoise_context *context, bool onoff) \
> +{ \
> + int val = osnoise_get_##name(context); \
> + int retval; \
> + \
> + if (val == OSNOISE_OPTION_INIT_VAL) \
> + return -1; \
> + \
> + if (val == onoff) \
> + return 0; \
> + \
> + retval = osnoise_options_set_option(option_str, onoff); \
> + if (retval < 0) \
> + return -2; \
> + \
> + context->opt_##name = onoff; \
> + return 0; \
> +} \
> + \
> +void osnoise_restore_##name(struct osnoise_context *context) \
> +{ \
> + int retval; \
> + \
> + if (context->orig_opt_##name == OSNOISE_OPTION_INIT_VAL) \
> + return; \
> + \
> + if (context->orig_opt_##name == context->opt_##name) \
> + goto out_done_##name; \
> + \
> + retval = osnoise_options_set_option(option_str, context->orig_opt_##name); \
> + if (retval < 0) \
> + err_msg("Could not restore original " option_str " option\n"); \
> + \
> +out_done_##name: \
> + context->opt_##name = OSNOISE_OPTION_INIT_VAL; \
> +} \
> + \
> +static void osnoise_put_##name(struct osnoise_context *context) \
> +{ \
> + osnoise_restore_##name(context); \
> + \
> + if (context->orig_opt_##name == OSNOISE_OPTION_INIT_VAL) \
> + return; \
> + \
> + context->orig_opt_##name = OSNOISE_OPTION_INIT_VAL; \
> +}
Can we reduce the amount of code we put in macros by moving some of the
logic to osnoise_read/write_ll_config() and osnoise_get/set_optino()?
Or a non-macro wrapper around them if there are other callers that need
the current behavior.
Something like (assuming universal -1 invalid):
static int osn_read_ll_config(const char *rel_path, long long *val, long long *orig)
static int osn_write_ll_config(const char *rel_path, long long *val, long long *orig)
static int osn_get_option(const char *name, int *val, int *orig)
static int osn_set_option(const char *name, int *val, int *orig)
-Crystal (who wishes we were using a modern language that didn't require all
this macro stuff)
^ permalink raw reply
* Re: [RFC PATCH 1/2] random: Expose boot ID to other subsystems
From: Jason A. Donenfeld @ 2026-06-12 17:04 UTC (permalink / raw)
To: Masami Hiramatsu (Google)
Cc: Theodore Ts'o, Steven Rostedt, Mathieu Desnoyers,
linux-kernel, linux-trace-kernel
In-Reply-To: <177937542892.2596845.4271730537688894501.stgit@mhiramat.tok.corp.google.com>
On Thu, May 21, 2026 at 11:57:09PM +0900, Masami Hiramatsu (Google) wrote:
> From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
>
> Add get_boot_id() to expose current boot ID to other kernel subsystems.
> Note that since this is only meaningful if user can access it via sysctl,
> it returns NULL if CONFIG_SYSCTL=n.
Wouldn't this be nice to have even on !SYSCTL systems? Why disable it for this
case?
> +/**
> + * get_boot_id - return the boot ID UUID
> + *
> + * This function returns a pointer to the boot ID UUID, which is generated on
> + * demand the first time this function is called. The boot ID is a UUID that
> + * is unique to each boot of the system.
> + */
> +const u8 *get_boot_id(void)
> +{
> + static DEFINE_SPINLOCK(bootid_spinlock);
> +
> + spin_lock(&bootid_spinlock);
> + if (!sysctl_bootid[8])
> + generate_random_uuid(sysctl_bootid);
> + spin_unlock(&bootid_spinlock);
> +
> + return sysctl_bootid;
> +}
> +
> /*
> * This function is used to return both the bootid UUID, and random
> * UUID. The difference is in whether table->data is NULL; if it is,
> @@ -1638,12 +1657,8 @@ static int proc_do_uuid(const struct ctl_table *table, int write, void *buf,
> uuid = tmp_uuid;
> generate_random_uuid(uuid);
> } else {
> - static DEFINE_SPINLOCK(bootid_spinlock);
> -
> - spin_lock(&bootid_spinlock);
> - if (!uuid[8])
> - generate_random_uuid(uuid);
> - spin_unlock(&bootid_spinlock);
> + /* Ensure that the boot ID is initialized. */
> + get_boot_id();
I find this a little odd, this implicit behavior now that sysctl_bootid ==
uuid. But perhaps that's the cleanest approach there is?
^ permalink raw reply
* Re: [PATCH] tracing: fprobe: Remove __packed from generic __fprobe_header
From: Steven Rostedt @ 2026-06-12 16:36 UTC (permalink / raw)
To: Markus Schneider-Pargmann
Cc: Mathieu Desnoyers, David Laight, Masami Hiramatsu (Google),
Heiko Carstens, linux-kernel, linux-trace-kernel
In-Reply-To: <DJ7328G40P9R.YB03MWLT8GQF@baylibre.com>
On Fri, 12 Jun 2026 14:51:58 +0200
"Markus Schneider-Pargmann" <msp@baylibre.com> wrote:
> fgraph_data = fgraph_reserve_data(gops->idx, reserved_words * sizeof(long));
>
> fgraph_reserve_data() returns a pointer into an unsigned long array
> ret_stack. ret_stack is allocated with
Correct. It is in fact a requirement that fgraph_reserve_data() returns
a long aligned pointer.
-- Steve
^ permalink raw reply
* Re: [LSF/MM/BPF TOPIC][RFC PATCH v4 00/27] Private Memory Nodes (w/ Compressed RAM)
From: Gregory Price @ 2026-06-12 15:29 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Balbir Singh, lsf-pc, linux-kernel, linux-cxl, cgroups, linux-mm,
linux-trace-kernel, damon, kernel-team, gregkh, rafael, dakr,
dave, jonathan.cameron, dave.jiang, alison.schofield,
vishal.l.verma, ira.weiny, dan.j.williams, longman, akpm,
lorenzo.stoakes, Liam.Howlett, vbabka, rppt, surenb, mhocko,
osalvador, ziy, matthew.brost, joshua.hahnjy, rakie.kim,
byungchul, ying.huang, apopple, axelrasmussen, yuanchu, weixugc,
yury.norov, linux, mhiramat, mathieu.desnoyers, tj, hannes,
mkoutny, jackmanb, sj, baolin.wang, npache, ryan.roberts,
dev.jain, baohua, lance.yang, muchun.song, xu.xin16,
chengming.zhou, jannh, linmiaohe, nao.horiguchi, pfalcato,
rientjes, shakeel.butt, riel, harry.yoo, cl, roman.gushchin,
chrisl, kasong, shikemeng, nphamcs, bhe, zhengqi.arch,
terry.bowman
In-Reply-To: <ainFROZ3WrGioyuY@gourry-fedora-PF4VCD3F>
On Wed, Jun 10, 2026 at 04:12:52PM -0400, Gregory Price wrote:
> On Wed, Jun 10, 2026 at 08:59:59PM +0200, David Hildenbrand (Arm) wrote:
> > >
> > > I understand this question in two ways:
> > >
> > > 1) Can we disallow PAGE allocation and limit this to FOLIO allocation
> >
> > Yes. Can we only allow folios to be allocated from private memory nodes. So let
> > me reply to that one below.
> >
> ... snip ...
> >
> > At LSF/MM we talked about how GFP flags are bad and how deriving stuff from the
> > context might be better. I think there was also talk about how the memalloc_*
> > interface might be a better way forward. Maybe we would start giving the
> > allocator more context ("we are allocating a folio").
> >
> > The following is incomplete (esp. hugetlb stuff I assume), just as some idea:
> >
>
> I will still probably send the next RFC version tomorrow or friday,
> as I want to get some eyes on the __GFP_PRIVATE-less pattern.
>
> Also, I made a new `anondax` driver which enables userland testing
> of this functionality without any specialty hardware.
>
(apologies for the length of this email: this will all be covered in
the coming cover letter, but I just wanted to share a bit of a preview)
===
Just another small update - I am planning to post the RFC today once i
get some mild cleanup done. It will be based on the dax atomic hotplug
https://lore.kernel.org/linux-mm/20260605211911.2160954-1-gourry@gourry.net/
But a couple specific details regarding the memalloc pieces that i've
learned the past couple of days playing with it.
1) memalloc_folio is required to ensure non-folio allocations don't land
on the private node, even if it happens within a memalloc_private
context. Since memalloc_folio may be useful in contexts outside of
private nodes, I kept this as a separate flag.
If we think there will *never* be additional users of memalloc_folio,
then we could fold _folio into _private to save the flag for now and
add it back when we actually need it.
2) memalloc_private is needed to unlock private nodes, but in the
original NOFALLBACK-only design, you also needed __GFP_THISNODE.
This is *highly* restrictive. I found when playing with mbind that
MPOL_BIND + __GFP_THISNODE generates a WARN (valid WARN, it normally
implies a bug).
That leads me to #3
3) If a private node is opted into something like Demotion (the node is
a demotion target) or mbind(), such that normal kernel operation can
place memory there - it's *pseudo-private*, and should actually land
in it's own FALLBACK list (reachable without __GFP_THISNODE, but not
reachable as a normal fallback allocation target).
I'm still playing with this, but I think we can even omit the
__GFP_THISNODE requirement (my initial feeling that __GFP_THISNODE
didn't buy us anything in particular seems to have panned out).
At the end of the day, this makes the whole memalloc_private_save()
pattern a heck of a lot cleaner than trying fiddle with GFP.
I think you will all enjoy how clean the code ends up, and how easily
testable it is.
As a testbed I've implement an anondax (we can discuss naming) that
adds some sample NODE_PRIVATE_OPT_* flags so you can do the following.
I'm including this in the next RFC - but we can hack the entire thing
off (including the OPT flags) if we prefer to just get the base set in
without a new driver as a start.
echo 1 > dax0.0/reclaim # kswapd and reclaim run normally on this node
echo 1 > dax0.0/demotion # it is a demotion target
echo 1 > dax0.0/mbind # mbind() can target this node for anon-vma's
echo 1 > dax0.0/madvise # allow madvise() to operate on its folios
echo 1 > dax0.0/numa_balance # allow numa balancing for this node
echo 1 > dax0.0/ltpin # allow GUP longterm pin to operate normally
echo * > dax0.0/adistance # set the adistance for hotplug time
echo * > dax0.0/hotplug # same as kmem/hotplug
This also means *existing hardware* can leverage private nodes if
they're capable of generating a dax device.
I've even gotten it such that you can put a private node above dram in
the adistance heirarchy - which means demotion flows downward from
device to CPU, but allocations don't default or fallback there.
This seems *immediately* useful for a variety of use cases.
~Gregory
^ permalink raw reply
* Re: [PATCHv7 bpf-next 03/29] ftrace: Add add_ftrace_hash_entry function
From: Steven Rostedt @ 2026-06-12 14:44 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Kumar Kartikeya Dwivedi, Jiri Olsa, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, bpf, linux-trace-kernel,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
Menglong Dong
In-Reply-To: <DJ6EGJ8S87HP.2WOTGYK374XKI@gmail.com>
On Thu, 11 Jun 2026 10:35:11 -0700
"Alexei Starovoitov" <alexei.starovoitov@gmail.com> wrote:
> AI finds things to consider, but when they're considered and postponned
> to future it doesn't understand that and keep reporting the same thing
> every revision. So it might look like that patches are landing with
> outstanding AI complains, but this is not the case.
Well, if someone just asked me to give an ack then I would have. But I
have other things to work on. Especially since everything I do at the
moment is 100% hobby related. The chores my wife gives me now have
priority ;-)
I'll start a new job come Monday.
>
> btw since patches touch ftrace from time to time should we add your
> ftrace testsuite to bpf CI ?
That's actually a good idea.
> How automated is it?
Very. In fact it's public. Although it's been a few years since I
updated the git repos.
I have two qemu images (currently private, but I can update them and
share). Where one is a 32bit x86 image and the other is a 64bit image.
The 64bit image hostname is called tracetest and the 32bit image's
hostname is tracetest-32. Both with root password of test0000.
The tests loaded on the image is here:
https://github.com/rostedt/ftrace-tests
And the ktests I run are here:
https://github.com/rostedt/ftrace-ktests
I would run the ktest like;
ktest.pl -DPATCH_CHECKOUT:=<SHA/BRANCH> -DPATCH_START:=<first-commit> tracetest-64.conf
And in another window
ktest.pl -DPATCH_CHECKOUT:=<SHA/BRANCH> -DPATCH_START:=<first-commit> tracetest-32.conf
For example:
ktest.pl -DPATCH_CHECKOUT:=trace/ftrace/core -DPATCH_START:=b5d6d3f73d0bac4a7e3a061372f6da166fc6ee5c tracetest-64.conf
And that will run 40 tests (I added some more since my last push, so
github doesn't have 40) and build, boot, install, test on the qemu
64bit image. It starts out testing commits from
b5d6d3f73d0bac4a7e3a061372f6da166fc6ee5c and going through to
trace/ftrace/core. Note, the PATCH_START needs to be in the history of
the PATCH_CHECKOUT otherwise the test will fail.
If you want to run these, let me know and I can help with the setup.
It's what I gave Masami to test as well.
>
> >
> >>
> >> While at it, please review Mykyta's set:
> >> https://patchwork.kernel.org/user/todo/netdevbpf/?series=1096695
> >>
> >> It's also been pending for almost a month now.
> >
> > Have a better link? I just get a blank page as "TODO" is set to what I have.
>
> Ohh. I meant this set:
> https://lore.kernel.org/bpf/CAEf4BzZFjsEv3aLktwdCZF6EXoCL+eefX+6xa3XGrhBmfO1SqA@mail.gmail.com/
> where you said that you'll think more about it after pto.
I came back on Tuesday and have yet to catch up on all the email I
ignored while away :-p
> Would be great to land it now for this merge window, so we have
> discoverability right now and if better approach comes in the future
> we can adjust to it later.
>
I'll take a look at it.
-- Steve
^ permalink raw reply
* Re: [GIT PULL] RTLA changes for 7.2
From: Tomas Glozar @ 2026-06-12 14:19 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Costa Shulyupin, Crystal Wood, LKML, linux-trace-kernel
In-Reply-To: <20260529130643.3080315-1-tglozar@redhat.com>
pá 29. 5. 2026 v 15:16 odesílatel Tomas Glozar <tglozar@redhat.com> napsal:
>
> ----------------------------------------------------------------
> Costa Shulyupin (1):
> tools/rtla: Fix --dump-tasks usage in timerlat
>
> Crystal Wood (1):
> rtla: Stop the record trace on interrupt
>
> Tomas Glozar (24):
> rtla/tests: Cover both top and hist tools where possible
> rtla/tests: Add get_workload_pids() helper
> rtla/tests: Check -c/--cpus thread affinity
> rtla/tests: Use negative match when testing --aa-only
> rtla/tests: Extend timerlat top --aa-only coverage
> rtla/tests: Cover all hist options in runtime tests
> rtla/tests: Add runtime test for -H/--house-keeping
> rtla/tests: Add runtime test for -k and -u options
> rtla/tests: Add runtime tests for -C/--cgroup
> rtla/tests: Add unit tests for actions module
> rtla/actions: Restore continue flag in actions_perform()
> rtla/tests: Add unit test for restoring continue flag
> rtla/tests: Run runtime tests in temporary directory
> rtla/tests: Add runtime tests for restoring continue flag
> rtla: Add libsubcmd dependency
> tools subcmd: support optarg as separate argument
> tools subcmd: allow parsing distinct --opt and --no-opt
> rtla: Parse cmdline using libsubcmd
This will create a conflict with a fix in master/7.1 [1] as it removes
the code that is fixed by the patch, as reported in linux-next already
[2]. The resolution is trivial (just remove the new, fixed code, as it
is entirely replaced by a new implementation that doesn't have the
bug) but a small note in the final pull request might be useful, so
that it's clear we know about it.
[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e9e41d3035032ed6053d8bad7b7077e1cb3a6540
[2] https://lore.kernel.org/linux-next/aimDwlNq_RRLjg6X@sirena.co.uk/T/#u
> rtla/tests: Add unit tests for _parse_args() functions
> rtla/tests: Add unit tests for CLI option callbacks
> rtla/timerlat: Add -A/--aligned CLI option
> rtla/tests: Add unit tests for -A/--aligned option
> Documentation/rtla: Add -A/--aligned option
> rtla: Document tests in README
>
Tomas
^ permalink raw reply
* Re: [PATCH] tracing: fprobe: Remove __packed from generic __fprobe_header
From: Markus Schneider-Pargmann @ 2026-06-12 12:51 UTC (permalink / raw)
To: Mathieu Desnoyers, Steven Rostedt, David Laight
Cc: Masami Hiramatsu (Google),
Markus Schneider-Pargmann (The Capable Hub), Heiko Carstens,
linux-kernel, linux-trace-kernel
In-Reply-To: <0ea2ae74-7452-4ba5-9549-59197c766c25@efficios.com>
[-- Attachment #1: Type: text/plain, Size: 3036 bytes --]
Hi,
On Wed Jun 10, 2026 at 10:05 PM CEST, Mathieu Desnoyers wrote:
> On 2026-06-10 15:51, Steven Rostedt wrote:
>> On Wed, 10 Jun 2026 12:06:59 +0100
>> David Laight <david.laight.linux@gmail.com> wrote:
>>
>>> So you only want __packed on structures that might be misaligned and those
>>> that contain misaligned members.
>>>
>>> If the structure is only guaranteed to be 32bit aligned then use __packed
>>> __aligned(4) so that two 32bit accesses get used instead of 8 8bit ones.
>>>
>>> -- David
>>>
>>>>
>>>> Thank you,
>>>>
>>>>> Signed-off-by: Markus Schneider-Pargmann (The Capable Hub) <msp@baylibre.com>
>>>>> ---
>>>>> kernel/trace/fprobe.c | 2 +-
>>>>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>>>>
>>>>> diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c
>>>>> index cc49ebd2a773..21751dcdb7b9 100644
>>>>> --- a/kernel/trace/fprobe.c
>>>>> +++ b/kernel/trace/fprobe.c
>>>>> @@ -181,7 +181,7 @@ static inline void read_fprobe_header(unsigned long *stack,
>>>>> struct __fprobe_header {
>>>>> struct fprobe *fp;
>>>>> unsigned long size_words;
>>>>> -} __packed;
>>>>> +};
>>>>>
>>
>> Does "__packed" really do anything between a pointer and a long?
>
> If that structure is allocated at a non-void-ptr-aligned address, the
> packed attribute will ensure that the compiler don't emit instructions
> that require aligned loads/stores when accessing those fields.
>
> It does not change the layout of the structure per se in this specific
> case, but it informs the compiler about the lack of guarantees about
> alignment for the entire structure.
>
> x86 32/64 cannot care less about this, but it's relevant on other
> architectures.
Thanks for your feedback. I checked this before submitting the patch.
The struct is always aligned to sizeof(long):
struct __fprobe_header is only ever accessed through
read_fprobe_header() and write_fprobe_header(). Since the read will only
read what we have previously written, only the write part is relevant
here. write_fprobe_header() is only called from fprobe_fgraph_entry():
if (write_fprobe_header(&fgraph_data[used], fp, size_words))
used += FPROBE_HEADER_SIZE_IN_LONG + size_words;
used is always kept aligned to sizeof(long), in fact the above snippet
is the only part where it is actually changed. fgraph_data is assigned
here:
fgraph_data = fgraph_reserve_data(gops->idx, reserved_words * sizeof(long));
fgraph_reserve_data() returns a pointer into an unsigned long array
ret_stack. ret_stack is allocated with
ret_stack = kmem_cache_alloc(fgraph_stack_cachep, GFP_KERNEL);
and fgraph_stack_cachep is allocated with
fgraph_stack_cachep = kmem_cache_create("fgraph_stack",
SHADOW_STACK_SIZE,
SHADOW_STACK_SIZE, 0, NULL);
So as far as I can see everything is sizeof(long) aligned here and it is
not allocated at a non-void-ptr-aligned address.
Best
Markus
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 289 bytes --]
^ permalink raw reply
* Re: [PATCH v4] rethook: Remove the running task check in rethook_find_ret_addr()
From: Masami Hiramatsu @ 2026-06-12 12:20 UTC (permalink / raw)
To: XIAO WU
Cc: sashiko-reviews, Petr Mladek, Peter Zijlstra, Tengda Wu,
Mathieu Desnoyers, Alexei Starovoitov, Steven Rostedt,
linux-kernel, linux-trace-kernel, live-patching
In-Reply-To: <tencent_3D17DC5BE32C8A51D938AF50F221321F6206@qq.com>
On Thu, 11 Jun 2026 23:53:36 +0800
XIAO WU <xiaowu.417@qq.com> wrote:
> Hi Tengda,
>
> Sashiko [1] reviewed this patch and found that removing the
> task_is_running() check exposes stack unwinders to real crashes — not
> just "invalid information." A PoC confirms this: a KASAN panic triggers
> within seconds when /proc/<pid>/stack reads the stack of a task that is
> concurrently running a kretprobe.
Hmm, why /proc/<pid>/stack unwind stack so unreliable way...
That should stop the target process, because it is exposed to
userspace. Thus it should work as safe as possible.
Anyway, thanks for reporting with the test program.
>
> [1]
> https://sashiko.dev/#/patchset/20260610013658.1837963-1-wutengda%40huaweicloud.com
>
> > diff --git a/kernel/trace/rethook.c b/kernel/trace/rethook.c
> > index 5a8bdf88999a..1e7fdebe3cd5 100644
> > --- a/kernel/trace/rethook.c
> > +++ b/kernel/trace/rethook.c
> > @@ -250,9 +251,6 @@ unsigned long rethook_find_ret_addr(struct
> task_struct *tsk, unsigned long frame
> > if (WARN_ON_ONCE(!cur))
> > return 0;
> >
> > - if (tsk != current && task_is_running(tsk))
> > - return 0;
> > -
> > do {
> > ret = __rethook_find_ret_addr(tsk, cur);
> > if (!ret)
>
> The commit message states:
>
> > The iteration is already safe from crashes because
> > unwind_next_frame() holds RCU and rethook_node structures are
> > RCU-freed; even if the iteration goes off the rails and returns
> > invalid information, it will not crash.
>
> There are two problems with this claim, both reproducible.
>
> **Problem 1: stack-out-of-bounds in unwind_next_frame itself**
>
> The PoC below reliably triggers the following KASAN panic — not in the
> rethook list traversal, but inside unwind_next_frame():
>
> [ 1833.494623] BUG: KASAN: stack-out-of-bounds in
> unwind_next_frame+0x861/0x2080
> [ 1833.494651] Read of size 2 at addr ffffc90003e6f5f0 by task poc/9854
> [ 1833.494707] Call Trace:
> [ 1833.494719] dump_stack_lvl+0x116/0x1f0
> [ 1833.494743] print_report+0xf4/0x600
> [ 1833.494788] kasan_report+0xe0/0x110
> [ 1833.494836] unwind_next_frame+0x861/0x2080
> [ 1833.494948] arch_stack_walk+0x99/0x100
> [ 1833.495000] stack_trace_save_tsk+0x16a/0x200
> [ 1833.495054] proc_pid_stack+0x173/0x2b0
> [ 1833.495103] seq_read_iter+0x519/0x12d0
> [ 1833.495166] seq_read+0x3b7/0x590
> [ 1833.495297] vfs_read+0x1f5/0xd20
> [ 1833.495497] ksys_read+0x135/0x250
> [ 1833.495549] do_syscall_64+0x129/0x850
> [ 1833.495566] entry_SYSCALL_64_after_hwframe+0x77/0x7f
> [ 1833.498894] Kernel panic - not syncing: KASAN: panic_on_warn set ...
>
> page last free pid 9737 tgid 9737 stack trace:
> do_sys_openat2+0xbf/0x260 <-- target task inside kretprobe
> __x64_sys_openat+0x179/0x210
>
> This crash has nothing to do with rethook_node lifetimes or RCU. It
> happens because the ORC unwinder reads stack memory while the target
> task concurrently executes a kretprobe trampoline that modifies return
> addresses. The unwinder follows corrupted frame data past valid stack
> boundaries. RCU protection of rethook_node structures is irrelevant —
> this crash occurs at the stack frame interpretation level, before any
> rethook list traversal.
OK, in that case, I think we should not allow list traversal.
I think without freezing the target task, accessing /proc/<pid>/stack
is potentially dangerous. Shouldn't we fix this at first?
>
> The old task_is_running() check prevented the unwinder from attempting
> to unwind a running task's stack in the first place.
>
> **Problem 2: use-after-free via rethook_node recycling**
>
> Even if the stack-out-of-bounds above were addressed, a second crash
> path exists in the rethook list traversal itself.
>
> rethook_recycle() immediately pushes nodes back to the objpool without
> an RCU grace period:
>
> kernel/trace/rethook.c:
> void rethook_recycle(struct rethook_node *node)
> {
> ...
> objpool_push(node, &node->rethook->pool);
> }
>
> Meanwhile, unwind_next_frame() in arch/x86/kernel/unwind_orc.c drops
> RCU between frames while the cursor (*cur) persists across iterations:
>
> arch/x86/kernel/unwind_orc.c:
> bool unwind_next_frame(...)
> {
> ...
> guard(rcu)(); // RCU held for one frame
> ...
> } // RCU dropped here
>
> When the unwinder calls __rethook_find_ret_addr() in the next frame
> iteration, it does:
>
> struct llist_node *first = tsk->rethooks.first;
> ...
> *cur = first;
> ...
> node = node->next; // node may have been recycled
>
> If the target task returns from a probed function between frames, its
> rethook_node is recycled and can be instantly reallocated to another
> task. The unwinder's stale cursor then dereferences a freed pointer,
> leading to use-after-free.
OK, this is still real problem. We should use call_rcu() to return
the object back to objpool.
Thanks!
>
> ## Reproducer
>
> The PoC sets up a kretprobe on do_sys_openat2, creates hot-loop threads
> calling open(), and concurrently reads /proc/<tid>/stack. The race
> triggers within seconds (Problem 1 above; Problem 2 may reproduce on
> kernels without KASAN or with different timing).
>
> Build: gcc -static -pthread -o poc poc.c
> Run: ./poc [runtime_seconds]
> Needs: root, CONFIG_KASAN=y
>
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <sys/types.h>
> #include <sys/stat.h>
> #include <sys/wait.h>
> #include <sys/syscall.h>
> #include <sched.h>
> #include <fcntl.h>
> #include <errno.h>
> #include <signal.h>
> #include <pthread.h>
> #include <dirent.h>
>
> #define TRACE "/sys/kernel/tracing"
>
> volatile int stop = 0;
>
> static int tfs(const char *f, const char *b)
> {
> char p[256]; int fd, r;
> snprintf(p, 256, "%s/%s", TRACE, f);
> fd = open(p, O_WRONLY | O_TRUNC);
> if (fd < 0) {
> system("mount -t tracefs tracefs /sys/kernel/tracing 2>/dev/null");
> usleep(50000);
> fd = open(p, O_WRONLY | O_TRUNC);
> }
> if (fd < 0) return -1;
> r = write(fd, b, strlen(b));
> close(fd);
> return r < 0 ? -1 : 0;
> }
>
> void *hot_thread(void *arg)
> {
> while (!__atomic_load_n(&stop, __ATOMIC_RELAXED)) {
> int fd = open("/dev/null", O_RDONLY);
> if (fd >= 0) close(fd);
> }
> return NULL;
> }
>
> void *reader_thread(void *arg)
> {
> pid_t target = *(pid_t *)arg;
> char path[64], buf[8192];
> snprintf(path, 64, "/proc/%d/stack", target);
> while (!__atomic_load_n(&stop, __ATOMIC_RELAXED)) {
> int fd = open(path, O_RDONLY);
> if (fd >= 0) { read(fd, buf, 8191); close(fd); }
> }
> return NULL;
> }
>
> void sigh(int s) { stop = 1; }
>
> int main(int argc, char *argv[])
> {
> int runtime = 120;
> if (argc > 1) runtime = atoi(argv[1]);
>
> printf("rethook race PoC\n");
> if (geteuid()) { printf("root needed\n"); return 1; }
> signal(SIGINT, sigh);
>
> pthread_t hot[4], rdr[4];
> pid_t hot_tids[4];
> int pairs = 4;
>
> for (int c = 0; c < runtime / 5 && !stop; c++) {
> tfs("events/kprobes/myretprobe/enable", "0");
> tfs("kprobe_events", "-:myretprobe");
> usleep(100);
> tfs("kprobe_events", "r:myretprobe do_sys_openat2 $retval");
> tfs("events/kprobes/myretprobe/enable", "1");
>
> pid_t main_tid = syscall(SYS_gettid);
>
> for (int i = 0; i < pairs; i++)
> pthread_create(&hot[i], NULL, hot_thread, NULL);
>
> usleep(300000);
>
> {
> DIR *d = opendir("/proc/self/task");
> int cnt = 0;
> if (d) {
> struct dirent *de;
> while ((de = readdir(d)) != NULL && cnt < pairs) {
> pid_t t = atoi(de->d_name);
> if (t > 0 && t != main_tid)
> hot_tids[cnt++] = t;
> }
> closedir(d);
> }
> for (int i = 0; i < cnt; i++)
> pthread_create(&rdr[i], NULL, reader_thread, &hot_tids[i]);
> }
>
> printf("round %d\n", c);
> sleep(5);
>
> stop = 1;
> usleep(100000);
>
> for (int i = 0; i < pairs; i++) pthread_join(hot[i], NULL);
> for (int i = 0; i < pairs; i++) pthread_join(rdr[i], NULL);
>
> stop = 0;
> usleep(1000);
> }
>
> tfs("events/kprobes/myretprobe/enable", "0");
> tfs("kprobe_events", "-:myretprobe");
> printf("Done\n");
> return 0;
> }
>
> ## Summary
>
> The v4 commit message claims the iteration "will not crash," but the PoC
> demonstrates a reproducible KASAN panic:
>
> 1. stack-out-of-bounds in unwind_next_frame (ORC unwinder reads
> concurrently-modified stack frames of a running task)
>
> 2. Potential use-after-free in __rethook_find_ret_addr (rethook nodes
> recycled without RCU grace period, cursor persists across RCU drops)
>
> The old task_is_running() check was racy but served as a practical
> safety net. Removing it without adding equivalent protection in the
> callers (proc_pid_stack, BPF stack walkers) exposes users to kernel
> panics via /proc/<pid>/stack on any task running a kretprobe.
>
> Thanks,
> Xiao
>
>
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* [PATCH] rtla: Simplify osnoise tracer option setting code
From: Tomas Glozar @ 2026-06-12 11:51 UTC (permalink / raw)
To: Steven Rostedt, Tomas Glozar
Cc: John Kacur, Luis Goncalves, Crystal Wood, Costa Shulyupin,
Wander Lairson Costa, LKML, linux-trace-kernel
Each osnoise tracer option (in /sys/kernel/tracing/osnoise) used by RTLA
requires four functions to be defined:
- static osnoise_get_<opt>() - to get the current value of the option
and save it into struct osnoise_context's orig_<opt> field,
- osnoise_set_<opt>() - to set the value of the option requested by the
user after reading and saving the original with osnoise_get_<opt>(),
and save it into <opt> field of struct osnoise_context,
- osnoise_restore_<opt>() - restore the value recorded in orig_<opt>,
- static osnoise_put_<opt>() - restore the value recorded in orig_<opt>
and update <opt> to reflect that.
The logic is duplicated for all the options, except for cpus (which is
the only string option) and period/runtime (which are handled together
and feature extra checks).
Deduplicate the logic using a set of macros featuring the X macro
pattern, defined in src/common.h:
- OSNOISE_LL_OPTIONS, which invokes OSNOISE_LL_OPTION macro for all
"long long" options,
- OSNOISE_FLAG_OPTIONS, which invokes OSNOISE_FLAG_OPTION macro for all
flag (boolean values in osnoise/options file) options.
The list macros are then invoked in four places:
- for struct osnoise_context fields in src/common.h,
- for function declarations, moved into src/common.h from
src/osnoise.h,
- for function definitions in src/osnoise.c,
- for context initialization and restoration, in osnoise_context_alloc()
and osnoise_put_context(), both in src/osnoise.c.
OSNOISE_LL_OPTIONS takes three options: name - struct osnoise_context
field name (written "<opt>" above), path - filename inside
/sys/kernel/tracing/osnoise passed to libtracefs, and init_val - initial
value of struct fields, corresponding to an otherwise invalid option
(some options use OSNOISE_OPTION_INIT_VAL = -1, some use
OSNOISE_TIME_INIT_VAL = 0).
OSNOISE_FLAG_OPTION is similar, but instead of path, it takes the option
string inside /sys/kernel/tracing/osnoise/options (opt_string), and no
init_val, as it is purely boolean (0 or 1).
Previously, for options timerlat_align and osnoise_workload, the return
value of osnoise_set_<opt>() distinguished between -2 (option cannot be
set) and -1 (option not present). This distinction is expanded for all
options for consistency; for most options, it is currently not used,
only osnoise_workload is implemented to avoid error on -1 on older RTLA
versions.
The change overall has two main benefits: it makes it much simpler to
add a new option, as well as to change existing logic consistently for
all of them. It also makes the code shorter by a bit over 500 lines.
There is no intentional user-visible change coming from the refactoring.
osnoise_restore_<opt>() for flag options now sets <opt> instead of
orig_<opt>. As the latter is also set by osnoise_put_<opt>(), plus long
long options set <opt> in both the old and new implementation, the old
behavior was likely a mistake, and should not matter for now, as the
options are only restored once at the end of tracing and neither <opt>
nor orig_<opt> field is read again.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
---
tools/tracing/rtla/src/common.h | 79 +--
tools/tracing/rtla/src/osnoise.c | 836 ++++++-------------------------
tools/tracing/rtla/src/osnoise.h | 22 -
3 files changed, 188 insertions(+), 749 deletions(-)
diff --git a/tools/tracing/rtla/src/common.h b/tools/tracing/rtla/src/common.h
index 04b287a03f6d..47233b0781c7 100644
--- a/tools/tracing/rtla/src/common.h
+++ b/tools/tracing/rtla/src/common.h
@@ -6,9 +6,35 @@
#include "trace.h"
#include "utils.h"
+/*
+ * OSNOISE_LL_OPTIONS - list of long long options backed by tracefs files.
+ * OSNOISE_LL_OPTION(field_name, tracefs_path, init_value)
+ *
+ * OSNOISE_FLAG_OPTIONS - list of boolean options backed by osnoise/options.
+ * OSNOISE_FLAG_OPTION(field_name, option_string)
+ */
+#define OSNOISE_LL_OPTIONS \
+ OSNOISE_LL_OPTION(stop_us, "osnoise/stop_tracing_us", OSNOISE_OPTION_INIT_VAL) \
+ OSNOISE_LL_OPTION(stop_total_us, "osnoise/stop_tracing_total_us", OSNOISE_OPTION_INIT_VAL) \
+ OSNOISE_LL_OPTION(print_stack, "osnoise/print_stack", OSNOISE_OPTION_INIT_VAL) \
+ OSNOISE_LL_OPTION(tracing_thresh, "tracing_thresh", OSNOISE_OPTION_INIT_VAL) \
+ OSNOISE_LL_OPTION(timerlat_period_us, "osnoise/timerlat_period_us", OSNOISE_TIME_INIT_VAL) \
+ OSNOISE_LL_OPTION(timerlat_align_us, "osnoise/timerlat_align_us", OSNOISE_OPTION_INIT_VAL)
+
+#define OSNOISE_FLAG_OPTIONS \
+ OSNOISE_FLAG_OPTION(irq_disable, "OSNOISE_IRQ_DISABLE") \
+ OSNOISE_FLAG_OPTION(workload, "OSNOISE_WORKLOAD") \
+ OSNOISE_FLAG_OPTION(timerlat_align, "TIMERLAT_ALIGN")
+
/*
* osnoise_context - read, store, write, restore osnoise configs.
*/
+#define OSNOISE_LL_OPTION(name, path, init_val) \
+ long long orig_##name; \
+ long long name;
+#define OSNOISE_FLAG_OPTION(name, option_str) \
+ int orig_opt_##name; \
+ int opt_##name;
struct osnoise_context {
int flags;
int ref;
@@ -24,42 +50,11 @@ struct osnoise_context {
unsigned long long orig_period_us;
unsigned long long period_us;
- /* 0 as init value */
- long long orig_timerlat_period_us;
- long long timerlat_period_us;
-
- /* 0 as init value */
- long long orig_tracing_thresh;
- long long tracing_thresh;
-
- /* -1 as init value because 0 is disabled */
- long long orig_stop_us;
- long long stop_us;
-
- /* -1 as init value because 0 is disabled */
- long long orig_stop_total_us;
- long long stop_total_us;
-
- /* -1 as init value because 0 is disabled */
- long long orig_print_stack;
- long long print_stack;
-
- /* -1 as init value because 0 is off */
- int orig_opt_irq_disable;
- int opt_irq_disable;
-
- /* -1 as init value because 0 is off */
- int orig_opt_workload;
- int opt_workload;
-
- /* -1 as init value because 0 is off */
- int orig_opt_timerlat_align;
- int opt_timerlat_align;
-
- /* 0 as init value */
- unsigned long long orig_timerlat_align_us;
- unsigned long long timerlat_align_us;
+ OSNOISE_LL_OPTIONS
+ OSNOISE_FLAG_OPTIONS
};
+#undef OSNOISE_LL_OPTION
+#undef OSNOISE_FLAG_OPTION
extern volatile int stop_tracing;
@@ -173,15 +168,21 @@ common_threshold_handler(const struct osnoise_tool *tool);
int osnoise_set_cpus(struct osnoise_context *context, char *cpus);
void osnoise_restore_cpus(struct osnoise_context *context);
-int osnoise_set_workload(struct osnoise_context *context, bool onoff);
+#define OSNOISE_LL_OPTION(name, path, init_val) \
+ int osnoise_set_##name(struct osnoise_context *context, long long name); \
+ void osnoise_restore_##name(struct osnoise_context *context);
+#define OSNOISE_FLAG_OPTION(name, option_str) \
+ int osnoise_set_##name(struct osnoise_context *context, bool onoff); \
+ void osnoise_restore_##name(struct osnoise_context *context);
+OSNOISE_LL_OPTIONS
+OSNOISE_FLAG_OPTIONS
+#undef OSNOISE_LL_OPTION
+#undef OSNOISE_FLAG_OPTION
void osnoise_destroy_tool(struct osnoise_tool *top);
struct osnoise_tool *osnoise_init_tool(char *tool_name);
struct osnoise_tool *osnoise_init_trace_tool(const char *tracer);
bool osnoise_trace_is_off(struct osnoise_tool *tool, struct osnoise_tool *record);
-int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us);
-int osnoise_set_stop_total_us(struct osnoise_context *context,
- long long stop_total_us);
int common_apply_config(struct osnoise_tool *tool, struct common_params *params);
int top_main_loop(struct osnoise_tool *tool);
diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c
index 4ff5dad013b1..7f15d00b431e 100644
--- a/tools/tracing/rtla/src/osnoise.c
+++ b/tools/tracing/rtla/src/osnoise.c
@@ -345,480 +345,73 @@ void osnoise_put_runtime_period(struct osnoise_context *context)
}
/*
- * osnoise_get_timerlat_period_us - read and save the original "timerlat_period_us"
- */
-static long long
-osnoise_get_timerlat_period_us(struct osnoise_context *context)
-{
- long long timerlat_period_us;
-
- if (context->timerlat_period_us != OSNOISE_TIME_INIT_VAL)
- return context->timerlat_period_us;
-
- if (context->orig_timerlat_period_us != OSNOISE_TIME_INIT_VAL)
- return context->orig_timerlat_period_us;
-
- timerlat_period_us = osnoise_read_ll_config("osnoise/timerlat_period_us");
- if (timerlat_period_us < 0)
- goto out_err;
-
- context->orig_timerlat_period_us = timerlat_period_us;
- return timerlat_period_us;
-
-out_err:
- return OSNOISE_TIME_INIT_VAL;
-}
-
-/*
- * osnoise_set_timerlat_period_us - set "timerlat_period_us"
- */
-int osnoise_set_timerlat_period_us(struct osnoise_context *context, long long timerlat_period_us)
-{
- long long curr_timerlat_period_us = osnoise_get_timerlat_period_us(context);
- int retval;
-
- if (curr_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
- return -1;
-
- retval = osnoise_write_ll_config("osnoise/timerlat_period_us", timerlat_period_us);
- if (retval < 0)
- return -1;
-
- context->timerlat_period_us = timerlat_period_us;
-
- return 0;
-}
-
-/*
- * osnoise_restore_timerlat_period_us - restore "timerlat_period_us"
- */
-void osnoise_restore_timerlat_period_us(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
- return;
-
- if (context->orig_timerlat_period_us == context->timerlat_period_us)
- goto out_done;
-
- retval = osnoise_write_ll_config("osnoise/timerlat_period_us", context->orig_timerlat_period_us);
- if (retval < 0)
- err_msg("Could not restore original osnoise timerlat_period_us\n");
-
-out_done:
- context->timerlat_period_us = OSNOISE_TIME_INIT_VAL;
-}
-
-/*
- * osnoise_put_timerlat_period_us - restore original values and cleanup data
- */
-void osnoise_put_timerlat_period_us(struct osnoise_context *context)
-{
- osnoise_restore_timerlat_period_us(context);
-
- if (context->orig_timerlat_period_us == OSNOISE_TIME_INIT_VAL)
- return;
-
- context->orig_timerlat_period_us = OSNOISE_TIME_INIT_VAL;
-}
-
-/*
- * osnoise_get_timerlat_align_us - read and save the original "timerlat_align_us"
- */
-static long long
-osnoise_get_timerlat_align_us(struct osnoise_context *context)
-{
- long long timerlat_align_us;
-
- if (context->timerlat_align_us != OSNOISE_OPTION_INIT_VAL)
- return context->timerlat_align_us;
-
- if (context->orig_timerlat_align_us != OSNOISE_OPTION_INIT_VAL)
- return context->orig_timerlat_align_us;
-
- timerlat_align_us = osnoise_read_ll_config("osnoise/timerlat_align_us");
- if (timerlat_align_us < 0)
- goto out_err;
-
- context->orig_timerlat_align_us = timerlat_align_us;
- return timerlat_align_us;
-
-out_err:
- return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_timerlat_align_us - set "timerlat_align_us"
- */
-int osnoise_set_timerlat_align_us(struct osnoise_context *context, long long timerlat_align_us)
-{
- long long curr_timerlat_align_us = osnoise_get_timerlat_align_us(context);
- int retval;
-
- if (curr_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
- return -1;
-
- retval = osnoise_write_ll_config("osnoise/timerlat_align_us", timerlat_align_us);
- if (retval < 0)
- return -1;
-
- context->timerlat_align_us = timerlat_align_us;
-
- return 0;
-}
-
-/*
- * osnoise_restore_timerlat_align_us - restore "timerlat_align_us"
- */
-void osnoise_restore_timerlat_align_us(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
- return;
-
- if (context->orig_timerlat_align_us == context->timerlat_align_us)
- goto out_done;
-
- retval = osnoise_write_ll_config("osnoise/timerlat_align_us",
- context->orig_timerlat_align_us);
- if (retval < 0)
- err_msg("Could not restore original osnoise timerlat_align_us\n");
-
-out_done:
- context->timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_timerlat_align_us - restore original values and cleanup data
- */
-void osnoise_put_timerlat_align_us(struct osnoise_context *context)
-{
- osnoise_restore_timerlat_align_us(context);
-
- if (context->orig_timerlat_align_us == OSNOISE_OPTION_INIT_VAL)
- return;
-
- context->orig_timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_get_stop_us - read and save the original "stop_tracing_us"
- */
-static long long
-osnoise_get_stop_us(struct osnoise_context *context)
-{
- long long stop_us;
-
- if (context->stop_us != OSNOISE_OPTION_INIT_VAL)
- return context->stop_us;
-
- if (context->orig_stop_us != OSNOISE_OPTION_INIT_VAL)
- return context->orig_stop_us;
-
- stop_us = osnoise_read_ll_config("osnoise/stop_tracing_us");
- if (stop_us < 0)
- goto out_err;
-
- context->orig_stop_us = stop_us;
- return stop_us;
-
-out_err:
- return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_stop_us - set "stop_tracing_us"
- */
-int osnoise_set_stop_us(struct osnoise_context *context, long long stop_us)
-{
- long long curr_stop_us = osnoise_get_stop_us(context);
- int retval;
-
- if (curr_stop_us == OSNOISE_OPTION_INIT_VAL)
- return -1;
-
- retval = osnoise_write_ll_config("osnoise/stop_tracing_us", stop_us);
- if (retval < 0)
- return -1;
-
- context->stop_us = stop_us;
-
- return 0;
-}
-
-/*
- * osnoise_restore_stop_us - restore the original "stop_tracing_us"
- */
-void osnoise_restore_stop_us(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_stop_us == OSNOISE_OPTION_INIT_VAL)
- return;
-
- if (context->orig_stop_us == context->stop_us)
- goto out_done;
-
- retval = osnoise_write_ll_config("osnoise/stop_tracing_us", context->orig_stop_us);
- if (retval < 0)
- err_msg("Could not restore original osnoise stop_us\n");
-
-out_done:
- context->stop_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_stop_us - restore original values and cleanup data
- */
-void osnoise_put_stop_us(struct osnoise_context *context)
-{
- osnoise_restore_stop_us(context);
-
- if (context->orig_stop_us == OSNOISE_OPTION_INIT_VAL)
- return;
-
- context->orig_stop_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_get_stop_total_us - read and save the original "stop_tracing_total_us"
- */
-static long long
-osnoise_get_stop_total_us(struct osnoise_context *context)
-{
- long long stop_total_us;
-
- if (context->stop_total_us != OSNOISE_OPTION_INIT_VAL)
- return context->stop_total_us;
-
- if (context->orig_stop_total_us != OSNOISE_OPTION_INIT_VAL)
- return context->orig_stop_total_us;
-
- stop_total_us = osnoise_read_ll_config("osnoise/stop_tracing_total_us");
- if (stop_total_us < 0)
- goto out_err;
-
- context->orig_stop_total_us = stop_total_us;
- return stop_total_us;
-
-out_err:
- return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_stop_total_us - set "stop_tracing_total_us"
- */
-int osnoise_set_stop_total_us(struct osnoise_context *context, long long stop_total_us)
-{
- long long curr_stop_total_us = osnoise_get_stop_total_us(context);
- int retval;
-
- if (curr_stop_total_us == OSNOISE_OPTION_INIT_VAL)
- return -1;
-
- retval = osnoise_write_ll_config("osnoise/stop_tracing_total_us", stop_total_us);
- if (retval < 0)
- return -1;
-
- context->stop_total_us = stop_total_us;
-
- return 0;
-}
-
-/*
- * osnoise_restore_stop_total_us - restore the original "stop_tracing_total_us"
- */
-void osnoise_restore_stop_total_us(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_stop_total_us == OSNOISE_OPTION_INIT_VAL)
- return;
-
- if (context->orig_stop_total_us == context->stop_total_us)
- goto out_done;
-
- retval = osnoise_write_ll_config("osnoise/stop_tracing_total_us",
- context->orig_stop_total_us);
- if (retval < 0)
- err_msg("Could not restore original osnoise stop_total_us\n");
-
-out_done:
- context->stop_total_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_stop_total_us - restore original values and cleanup data
- */
-void osnoise_put_stop_total_us(struct osnoise_context *context)
-{
- osnoise_restore_stop_total_us(context);
-
- if (context->orig_stop_total_us == OSNOISE_OPTION_INIT_VAL)
- return;
-
- context->orig_stop_total_us = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_get_print_stack - read and save the original "print_stack"
- */
-static long long
-osnoise_get_print_stack(struct osnoise_context *context)
-{
- long long print_stack;
-
- if (context->print_stack != OSNOISE_OPTION_INIT_VAL)
- return context->print_stack;
-
- if (context->orig_print_stack != OSNOISE_OPTION_INIT_VAL)
- return context->orig_print_stack;
-
- print_stack = osnoise_read_ll_config("osnoise/print_stack");
- if (print_stack < 0)
- goto out_err;
-
- context->orig_print_stack = print_stack;
- return print_stack;
-
-out_err:
- return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_print_stack - set "print_stack"
- */
-int osnoise_set_print_stack(struct osnoise_context *context, long long print_stack)
-{
- long long curr_print_stack = osnoise_get_print_stack(context);
- int retval;
-
- if (curr_print_stack == OSNOISE_OPTION_INIT_VAL)
- return -1;
-
- retval = osnoise_write_ll_config("osnoise/print_stack", print_stack);
- if (retval < 0)
- return -1;
-
- context->print_stack = print_stack;
-
- return 0;
-}
-
-/*
- * osnoise_restore_print_stack - restore the original "print_stack"
- */
-void osnoise_restore_print_stack(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_print_stack == OSNOISE_OPTION_INIT_VAL)
- return;
-
- if (context->orig_print_stack == context->print_stack)
- goto out_done;
-
- retval = osnoise_write_ll_config("osnoise/print_stack", context->orig_print_stack);
- if (retval < 0)
- err_msg("Could not restore original osnoise print_stack\n");
-
-out_done:
- context->print_stack = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_print_stack - restore original values and cleanup data
- */
-void osnoise_put_print_stack(struct osnoise_context *context)
-{
- osnoise_restore_print_stack(context);
-
- if (context->orig_print_stack == OSNOISE_OPTION_INIT_VAL)
- return;
-
- context->orig_print_stack = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_get_tracing_thresh - read and save the original "tracing_thresh"
- */
-static long long
-osnoise_get_tracing_thresh(struct osnoise_context *context)
-{
- long long tracing_thresh;
-
- if (context->tracing_thresh != OSNOISE_OPTION_INIT_VAL)
- return context->tracing_thresh;
-
- if (context->orig_tracing_thresh != OSNOISE_OPTION_INIT_VAL)
- return context->orig_tracing_thresh;
-
- tracing_thresh = osnoise_read_ll_config("tracing_thresh");
- if (tracing_thresh < 0)
- goto out_err;
-
- context->orig_tracing_thresh = tracing_thresh;
- return tracing_thresh;
-
-out_err:
- return OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_set_tracing_thresh - set "tracing_thresh"
- */
-int osnoise_set_tracing_thresh(struct osnoise_context *context, long long tracing_thresh)
-{
- long long curr_tracing_thresh = osnoise_get_tracing_thresh(context);
- int retval;
-
- if (curr_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
- return -1;
-
- retval = osnoise_write_ll_config("tracing_thresh", tracing_thresh);
- if (retval < 0)
- return -1;
-
- context->tracing_thresh = tracing_thresh;
-
- return 0;
-}
-
-/*
- * osnoise_restore_tracing_thresh - restore the original "tracing_thresh"
- */
-void osnoise_restore_tracing_thresh(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
- return;
-
- if (context->orig_tracing_thresh == context->tracing_thresh)
- goto out_done;
-
- retval = osnoise_write_ll_config("tracing_thresh", context->orig_tracing_thresh);
- if (retval < 0)
- err_msg("Could not restore original tracing_thresh\n");
-
-out_done:
- context->tracing_thresh = OSNOISE_OPTION_INIT_VAL;
-}
-
-/*
- * osnoise_put_tracing_thresh - restore original values and cleanup data
- */
-void osnoise_put_tracing_thresh(struct osnoise_context *context)
-{
- osnoise_restore_tracing_thresh(context);
-
- if (context->orig_tracing_thresh == OSNOISE_OPTION_INIT_VAL)
- return;
-
- context->orig_tracing_thresh = OSNOISE_OPTION_INIT_VAL;
-}
+ * Long long option get/set/restore/put functions, generated from OSNOISE_LL_OPTIONS.
+ */
+#define OSNOISE_LL_OPTION(name, path, init_val) \
+static long long \
+osnoise_get_##name(struct osnoise_context *context) \
+{ \
+ long long name; \
+ \
+ if (context->name != (init_val)) \
+ return context->name; \
+ \
+ if (context->orig_##name != (init_val)) \
+ return context->orig_##name; \
+ \
+ name = osnoise_read_ll_config(path); \
+ if (name < 0) \
+ return (init_val); \
+ \
+ context->orig_##name = name; \
+ return name; \
+} \
+ \
+int osnoise_set_##name(struct osnoise_context *context, long long name) \
+{ \
+ long long curr = osnoise_get_##name(context); \
+ int retval; \
+ \
+ if (curr == (init_val)) \
+ return -1; \
+ \
+ retval = osnoise_write_ll_config(path, name); \
+ if (retval < 0) \
+ return -2; \
+ \
+ context->name = name; \
+ return 0; \
+} \
+ \
+void osnoise_restore_##name(struct osnoise_context *context) \
+{ \
+ int retval; \
+ \
+ if (context->orig_##name == (init_val)) \
+ return; \
+ \
+ if (context->orig_##name == context->name) \
+ goto out_done_##name; \
+ \
+ retval = osnoise_write_ll_config(path, context->orig_##name); \
+ if (retval < 0) \
+ err_msg("Could not restore original " #name "\n"); \
+ \
+out_done_##name: \
+ context->name = (init_val); \
+} \
+ \
+static void osnoise_put_##name(struct osnoise_context *context) \
+{ \
+ osnoise_restore_##name(context); \
+ \
+ if (context->orig_##name == (init_val)) \
+ return; \
+ \
+ context->orig_##name = (init_val); \
+}
+OSNOISE_LL_OPTIONS
+#undef OSNOISE_LL_OPTION
static int osnoise_options_get_option(char *option)
{
@@ -866,188 +459,70 @@ static int osnoise_options_set_option(char *option, bool onoff)
return tracefs_instance_file_write(NULL, "osnoise/options", no_option);
}
-static int osnoise_get_irq_disable(struct osnoise_context *context)
-{
- if (context->opt_irq_disable != OSNOISE_OPTION_INIT_VAL)
- return context->opt_irq_disable;
-
- if (context->orig_opt_irq_disable != OSNOISE_OPTION_INIT_VAL)
- return context->orig_opt_irq_disable;
-
- context->orig_opt_irq_disable = osnoise_options_get_option("OSNOISE_IRQ_DISABLE");
-
- return context->orig_opt_irq_disable;
-}
-
-int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff)
-{
- int opt_irq_disable = osnoise_get_irq_disable(context);
- int retval;
-
- if (opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
- return -1;
-
- if (opt_irq_disable == onoff)
- return 0;
-
- retval = osnoise_options_set_option("OSNOISE_IRQ_DISABLE", onoff);
- if (retval < 0)
- return -1;
-
- context->opt_irq_disable = onoff;
-
- return 0;
-}
-
-static void osnoise_restore_irq_disable(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
- return;
-
- if (context->orig_opt_irq_disable == context->opt_irq_disable)
- goto out_done;
-
- retval = osnoise_options_set_option("OSNOISE_IRQ_DISABLE", context->orig_opt_irq_disable);
- if (retval < 0)
- err_msg("Could not restore original OSNOISE_IRQ_DISABLE option\n");
-
-out_done:
- context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL;
-}
-
-static void osnoise_put_irq_disable(struct osnoise_context *context)
-{
- osnoise_restore_irq_disable(context);
-
- if (context->orig_opt_irq_disable == OSNOISE_OPTION_INIT_VAL)
- return;
-
- context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL;
-}
-
-static int osnoise_get_workload(struct osnoise_context *context)
-{
- if (context->opt_workload != OSNOISE_OPTION_INIT_VAL)
- return context->opt_workload;
-
- if (context->orig_opt_workload != OSNOISE_OPTION_INIT_VAL)
- return context->orig_opt_workload;
-
- context->orig_opt_workload = osnoise_options_get_option("OSNOISE_WORKLOAD");
-
- return context->orig_opt_workload;
-}
-
-int osnoise_set_workload(struct osnoise_context *context, bool onoff)
-{
- int opt_workload = osnoise_get_workload(context);
- int retval;
-
- if (opt_workload == OSNOISE_OPTION_INIT_VAL)
- return -1;
-
- if (opt_workload == onoff)
- return 0;
-
- retval = osnoise_options_set_option("OSNOISE_WORKLOAD", onoff);
- if (retval < 0)
- return -2;
-
- context->opt_workload = onoff;
-
- return 0;
-}
-
-static void osnoise_restore_workload(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_opt_workload == OSNOISE_OPTION_INIT_VAL)
- return;
-
- if (context->orig_opt_workload == context->opt_workload)
- goto out_done;
-
- retval = osnoise_options_set_option("OSNOISE_WORKLOAD", context->orig_opt_workload);
- if (retval < 0)
- err_msg("Could not restore original OSNOISE_WORKLOAD option\n");
-
-out_done:
- context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
-}
-
-static void osnoise_put_workload(struct osnoise_context *context)
-{
- osnoise_restore_workload(context);
-
- if (context->orig_opt_workload == OSNOISE_OPTION_INIT_VAL)
- return;
-
- context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
-}
-
-static int osnoise_get_timerlat_align(struct osnoise_context *context)
-{
- if (context->opt_timerlat_align != OSNOISE_OPTION_INIT_VAL)
- return context->opt_timerlat_align;
-
- if (context->orig_opt_timerlat_align != OSNOISE_OPTION_INIT_VAL)
- return context->orig_opt_timerlat_align;
-
- context->orig_opt_timerlat_align = osnoise_options_get_option("TIMERLAT_ALIGN");
-
- return context->orig_opt_timerlat_align;
-}
-
-int osnoise_set_timerlat_align(struct osnoise_context *context, bool onoff)
-{
- int opt_timerlat_align = osnoise_get_timerlat_align(context);
- int retval;
-
- if (opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
- return -1;
-
- if (opt_timerlat_align == onoff)
- return 0;
-
- retval = osnoise_options_set_option("TIMERLAT_ALIGN", onoff);
- if (retval < 0)
- return -2;
-
- context->opt_timerlat_align = onoff;
-
- return 0;
-}
-
-static void osnoise_restore_timerlat_align(struct osnoise_context *context)
-{
- int retval;
-
- if (context->orig_opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
- return;
-
- if (context->orig_opt_timerlat_align == context->opt_timerlat_align)
- goto out_done;
-
- retval = osnoise_options_set_option("TIMERLAT_ALIGN", context->orig_opt_timerlat_align);
- if (retval < 0)
- err_msg("Could not restore original TIMERLAT_ALIGN option\n");
-
-out_done:
- context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
-}
-
-static void osnoise_put_timerlat_align(struct osnoise_context *context)
-{
- osnoise_restore_timerlat_align(context);
-
- if (context->orig_opt_timerlat_align == OSNOISE_OPTION_INIT_VAL)
- return;
-
- context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
-}
+/*
+ * Flag option get/set/restore/put functions, generated from OSNOISE_FLAG_OPTIONS.
+ */
+#define OSNOISE_FLAG_OPTION(name, option_str) \
+static int osnoise_get_##name(struct osnoise_context *context) \
+{ \
+ if (context->opt_##name != OSNOISE_OPTION_INIT_VAL) \
+ return context->opt_##name; \
+ \
+ if (context->orig_opt_##name != OSNOISE_OPTION_INIT_VAL) \
+ return context->orig_opt_##name; \
+ \
+ context->orig_opt_##name = osnoise_options_get_option(option_str); \
+ return context->orig_opt_##name; \
+} \
+ \
+int osnoise_set_##name(struct osnoise_context *context, bool onoff) \
+{ \
+ int val = osnoise_get_##name(context); \
+ int retval; \
+ \
+ if (val == OSNOISE_OPTION_INIT_VAL) \
+ return -1; \
+ \
+ if (val == onoff) \
+ return 0; \
+ \
+ retval = osnoise_options_set_option(option_str, onoff); \
+ if (retval < 0) \
+ return -2; \
+ \
+ context->opt_##name = onoff; \
+ return 0; \
+} \
+ \
+void osnoise_restore_##name(struct osnoise_context *context) \
+{ \
+ int retval; \
+ \
+ if (context->orig_opt_##name == OSNOISE_OPTION_INIT_VAL) \
+ return; \
+ \
+ if (context->orig_opt_##name == context->opt_##name) \
+ goto out_done_##name; \
+ \
+ retval = osnoise_options_set_option(option_str, context->orig_opt_##name); \
+ if (retval < 0) \
+ err_msg("Could not restore original " option_str " option\n"); \
+ \
+out_done_##name: \
+ context->opt_##name = OSNOISE_OPTION_INIT_VAL; \
+} \
+ \
+static void osnoise_put_##name(struct osnoise_context *context) \
+{ \
+ osnoise_restore_##name(context); \
+ \
+ if (context->orig_opt_##name == OSNOISE_OPTION_INIT_VAL) \
+ return; \
+ \
+ context->orig_opt_##name = OSNOISE_OPTION_INIT_VAL; \
+}
+OSNOISE_FLAG_OPTIONS
+#undef OSNOISE_FLAG_OPTION
enum {
FLAG_CONTEXT_NEWLY_CREATED = (1 << 0),
@@ -1083,29 +558,16 @@ struct osnoise_context *osnoise_context_alloc(void)
context = calloc_fatal(1, sizeof(*context));
- context->orig_stop_us = OSNOISE_OPTION_INIT_VAL;
- context->stop_us = OSNOISE_OPTION_INIT_VAL;
-
- context->orig_stop_total_us = OSNOISE_OPTION_INIT_VAL;
- context->stop_total_us = OSNOISE_OPTION_INIT_VAL;
-
- context->orig_print_stack = OSNOISE_OPTION_INIT_VAL;
- context->print_stack = OSNOISE_OPTION_INIT_VAL;
-
- context->orig_tracing_thresh = OSNOISE_OPTION_INIT_VAL;
- context->tracing_thresh = OSNOISE_OPTION_INIT_VAL;
-
- context->orig_opt_irq_disable = OSNOISE_OPTION_INIT_VAL;
- context->opt_irq_disable = OSNOISE_OPTION_INIT_VAL;
-
- context->orig_opt_workload = OSNOISE_OPTION_INIT_VAL;
- context->opt_workload = OSNOISE_OPTION_INIT_VAL;
-
- context->orig_opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
- context->opt_timerlat_align = OSNOISE_OPTION_INIT_VAL;
-
- context->orig_timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
- context->timerlat_align_us = OSNOISE_OPTION_INIT_VAL;
+#define OSNOISE_LL_OPTION(name, path, init_val) \
+ context->orig_##name = (init_val); \
+ context->name = (init_val);
+#define OSNOISE_FLAG_OPTION(name, option_str) \
+ context->orig_opt_##name = OSNOISE_OPTION_INIT_VAL; \
+ context->opt_##name = OSNOISE_OPTION_INIT_VAL;
+ OSNOISE_LL_OPTIONS
+ OSNOISE_FLAG_OPTIONS
+#undef OSNOISE_LL_OPTION
+#undef OSNOISE_FLAG_OPTION
osnoise_get_context(context);
@@ -1128,15 +590,13 @@ void osnoise_put_context(struct osnoise_context *context)
osnoise_put_cpus(context);
osnoise_put_runtime_period(context);
- osnoise_put_stop_us(context);
- osnoise_put_stop_total_us(context);
- osnoise_put_timerlat_period_us(context);
- osnoise_put_print_stack(context);
- osnoise_put_tracing_thresh(context);
- osnoise_put_irq_disable(context);
- osnoise_put_workload(context);
- osnoise_put_timerlat_align(context);
- osnoise_put_timerlat_align_us(context);
+
+#define OSNOISE_LL_OPTION(name, path, init_val) osnoise_put_##name(context);
+#define OSNOISE_FLAG_OPTION(name, option_str) osnoise_put_##name(context);
+ OSNOISE_LL_OPTIONS
+ OSNOISE_FLAG_OPTIONS
+#undef OSNOISE_LL_OPTION
+#undef OSNOISE_FLAG_OPTION
free(context);
}
diff --git a/tools/tracing/rtla/src/osnoise.h b/tools/tracing/rtla/src/osnoise.h
index 340ff5a64e6e..3d1852bffed8 100644
--- a/tools/tracing/rtla/src/osnoise.h
+++ b/tools/tracing/rtla/src/osnoise.h
@@ -34,28 +34,6 @@ int osnoise_set_runtime_period(struct osnoise_context *context,
unsigned long long period);
void osnoise_restore_runtime_period(struct osnoise_context *context);
-void osnoise_restore_stop_us(struct osnoise_context *context);
-void osnoise_restore_stop_total_us(struct osnoise_context *context);
-
-int osnoise_set_timerlat_period_us(struct osnoise_context *context,
- long long timerlat_period_us);
-void osnoise_restore_timerlat_period_us(struct osnoise_context *context);
-
-int osnoise_set_tracing_thresh(struct osnoise_context *context,
- long long tracing_thresh);
-void osnoise_restore_tracing_thresh(struct osnoise_context *context);
-
-void osnoise_restore_print_stack(struct osnoise_context *context);
-int osnoise_set_print_stack(struct osnoise_context *context,
- long long print_stack);
-
-int osnoise_set_timerlat_align_us(struct osnoise_context *context,
- long long timerlat_align_us);
-void osnoise_restore_timerlat_align_us(struct osnoise_context *context);
-
-int osnoise_set_timerlat_align(struct osnoise_context *context, bool onoff);
-
-int osnoise_set_irq_disable(struct osnoise_context *context, bool onoff);
void osnoise_report_missed_events(struct osnoise_tool *tool);
int osnoise_apply_config(struct osnoise_tool *tool, struct osnoise_params *params);
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] sparc64: uprobes: add missing break
From: Andreas Larsson @ 2026-06-12 9:13 UTC (permalink / raw)
To: Rosen Penev, linux-kernel
Cc: Masami Hiramatsu, Oleg Nesterov, Peter Zijlstra, David S. Miller,
open list:UPROBES, open list:SPARC + UltraSPARC (sparc/sparc64)
In-Reply-To: <20260506031815.779909-1-rosenp@gmail.com>
On 2026-05-06 05:18, Rosen Penev wrote:
> Missing fallthrough causes failure with newer compilers:
>
> arch/sparc/kernel/uprobes.c:284:2: error: unannotated fall-through between switch labels [-Werror,-Wimplicit-fallthrough]
> 284 | default:
> | ^
> arch/sparc/kernel/uprobes.c:284:2: note: insert 'break;' to avoid fall-through
> 284 | default:
> | ^
> | break;
>
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
> ---
> arch/sparc/kernel/uprobes.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/arch/sparc/kernel/uprobes.c b/arch/sparc/kernel/uprobes.c
> index 305017bec164..c8cac64e9988 100644
> --- a/arch/sparc/kernel/uprobes.c
> +++ b/arch/sparc/kernel/uprobes.c
> @@ -280,6 +280,7 @@ int arch_uprobe_exception_notify(struct notifier_block *self,
> case DIE_SSTEP:
> if (uprobe_post_sstep_notifier(args->regs))
> ret = NOTIFY_STOP;
> + break;
>
> default:
> break;
Reviewed-by: Andreas Larsson <andreas@gaisler.com>
Picking this up to my for-next.
Thanks,
Andreas
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox