From d5dc200c3a3f217de072af269dd90adddf90e48d Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Tue, 16 Jun 2026 10:30:56 +0200 Subject: bpf: Add missing access_ok call to copy_user_syms As reported by sashiko we use __get_user without prior access_ok call on the user space pointer. Adding the missing call for the whole pointer array. Plus removing the err check in the error path, because it's not needed and also we can return -ENOMEM directly from the first kvmalloc_array fail path. Cc: stable@vger.kernel.org [1] https://lore.kernel.org/bpf/20260611115503.AC16D1F00893@smtp.kernel.org/ Fixes: 0236fec57a15 ("bpf: Resolve symbols with ftrace_lookup_symbols for kprobe multi link") Reported-by: Sashiko Closes: https://lore.kernel.org/bpf/20260611115503.AC16D1F00893@smtp.kernel.org/ Signed-off-by: Jiri Olsa Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260616083056.405652-1-jolsa@kernel.org Signed-off-by: Alexei Starovoitov --- kernel/trace/bpf_trace.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 82f8feea6931..75495a5c3507 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -2376,9 +2376,12 @@ static int copy_user_syms(struct user_syms *us, unsigned long __user *usyms, u32 int err = -ENOMEM; unsigned int i; + if (!access_ok(usyms, cnt * sizeof(*usyms))) + return -EFAULT; + syms = kvmalloc_array(cnt, sizeof(*syms), GFP_KERNEL); if (!syms) - goto error; + return -ENOMEM; buf = kvmalloc_array(cnt, KSYM_NAME_LEN, GFP_KERNEL); if (!buf) @@ -2403,10 +2406,8 @@ static int copy_user_syms(struct user_syms *us, unsigned long __user *usyms, u32 return 0; error: - if (err) { - kvfree(syms); - kvfree(buf); - } + kvfree(syms); + kvfree(buf); return err; } -- cgit v1.2.3 From 72c8646956ffc8050bb8be5988a0f28fc37e1ac4 Mon Sep 17 00:00:00 2001 From: Martin Kaiser Date: Thu, 25 Jun 2026 08:34:45 +0900 Subject: tracing: probes: fix typo in a log message Fix a typo ("Invalid $-variable") in a log message. Link: https://lore.kernel.org/all/20260507081041.885781-4-martin@kaiser.cx/ Fixes: ab105a4fb894 ("tracing: Use tracing error_log with probe events") Signed-off-by: Martin Kaiser Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 15758cc11fc6..0f09f7aaf93f 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -511,7 +511,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, C(NO_RETVAL, "This function returns 'void' type"), \ C(BAD_STACK_NUM, "Invalid stack number"), \ C(BAD_ARG_NUM, "Invalid argument number"), \ - C(BAD_VAR, "Invalid $-valiable specified"), \ + C(BAD_VAR, "Invalid $-variable specified"), \ C(BAD_REG_NAME, "Invalid register name"), \ C(BAD_MEM_ADDR, "Invalid memory address"), \ C(BAD_IMM, "Invalid immediate value"), \ -- cgit v1.2.3 From 251a8fe1b9aedccd298b77bc28426d564c5a923f Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 25 Jun 2026 08:34:46 +0900 Subject: tracing/probes: Remove WARN_ON_ONCE from parse_btf_arg Sashiko found that user can cause this WARN_ON_ONCE() easily with adding a kprobe event based on a raw address with BTF parameter. Since this is not an unexpected condition, remove the WARN_ON_ONCE(). Link: https://lore.kernel.org/all/178177265367.2059927.13789953014706792126.stgit@mhiramat.tok.corp.google.com/ Link: https://sashiko.dev/#/patchset/178165816303.269421.7302603996990753309.stgit%40devnote2 Reported-by: Sashiko Fixes: b576e09701c7 ("tracing/probes: Support function parameters if BTF is available") Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index fd1caa1f9723..98532c503d02 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -678,7 +678,7 @@ static int parse_btf_arg(char *varname, int i, is_ptr, ret; u32 tid; - if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT))) + if (!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)) return -EINVAL; is_ptr = split_next_field(varname, &field, ctx); -- cgit v1.2.3 From 206b25c09080cc20fd4c2bea12d59df4b7ba2121 Mon Sep 17 00:00:00 2001 From: Martin Kaiser Date: Thu, 25 Jun 2026 08:34:46 +0900 Subject: tracing: eprobe: read the complete FILTER_PTR_STRING pointer For a char * element in an event, the FILTER_PTR_STRING filter type is used. When the event occurs, a pointer is stored in the ringbuffer. If an eprobe references such a char * element of a "base event", the stored pointer is truncated when it's read from the ringbuffer. $ cd /sys/kernel/tracing $ echo 'e rcu.rcu_utilization $s:x64 $s:string' > dynamic_events $ echo 1 > tracing_on $ echo 1 > events/eprobes/enable $ sleep 1 $ echo 0 > events/eprobes/enable $ cat trace -0 ...: (rcu.rcu_utilization) arg1=0x4f arg2=(fault) -0 ...: (rcu.rcu_utilization) arg1=0x2 arg2=(fault) The problem is in get_event_field val = (unsigned long)(*(char *)addr); addr points to the position in the ringbuffer where the pointer was stored. The assignment reads only the lowest byte of the pointer. Fix the cast to read the whole pointer. The output of the test above is now -0 ... arg1=0xffffffff81c7d3f3 arg2="Start scheduler-tick" -0 ... arg1=0xffffffff81c57340 arg2="End scheduler-tick" Link: https://lore.kernel.org/all/20260620145339.3234726-1-martin@kaiser.cx/ Fixes: f04dec93466a ("tracing/eprobes: Fix reading of string fields") Signed-off-by: Martin Kaiser Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_eprobe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_eprobe.c b/kernel/trace/trace_eprobe.c index b66d6196338d..50518b071414 100644 --- a/kernel/trace/trace_eprobe.c +++ b/kernel/trace/trace_eprobe.c @@ -315,7 +315,7 @@ get_event_field(struct fetch_insn *code, void *rec) val = (unsigned long)addr; break; case FILTER_PTR_STRING: - val = (unsigned long)(*(char *)addr); + val = *(unsigned long *)addr; break; default: WARN_ON_ONCE(1); -- cgit v1.2.3 From 9a667b7750dda88cbf1cca96a53a2163b2ee71f7 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 25 Jun 2026 08:34:47 +0900 Subject: tracing/probes: Fix double addition of offset for @+FOFFSET Since commit 533059281ee5 ("tracing: probeevent: Introduce new argument fetching code") wrongly use @offset local variable during the parsing, the offset value is added twice when dereferencing. Reset the @offset after setting it in FETCH_OP_FOFFS. Link: https://lore.kernel.org/all/178217905962.643090.1978577464942171332.stgit@devnote2/ Fixes: 533059281ee5 ("tracing: probeevent: Introduce new argument fetching code") Signed-off-by: Masami Hiramatsu (Google) Cc: stable@vger.kernel.org --- kernel/trace/trace_probe.c | 1 + 1 file changed, 1 insertion(+) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 98532c503d02..502fa6da5949 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1241,6 +1241,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type, code->op = FETCH_OP_FOFFS; code->immediate = (unsigned long)offset; // imm64? + offset = 0; } else { /* uprobes don't support symbols */ if (!(ctx->flags & TPARG_FL_KERNEL)) { -- cgit v1.2.3 From 367c49d6e283c17b56a31e7a8d964a079244264c Mon Sep 17 00:00:00 2001 From: Sechang Lim Date: Thu, 25 Jun 2026 08:34:48 +0900 Subject: tracing/fprobe: Fix NULL pointer dereference in fprobe_fgraph_entry() fprobe_fgraph_entry() sizes a shadow-stack reservation in one walk of the per-ip fprobe list and fills it in a second walk, both under rcu_read_lock() only. A fprobe registered on an already-live ip can become visible between the two walks, so the fill walk processes an exit_handler the sizing walk did not count and used runs past reserved_words. If the sizing walk counted nothing, fgraph_data is NULL and the first write_fprobe_header() faults: Oops: general protection fault, probably for non-canonical address ... KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:fprobe_fgraph_entry+0xa38/0xf10 kernel/trace/fprobe.c:167 Call Trace: function_graph_enter_regs+0x44c/0xa10 kernel/trace/fgraph.c:677 ftrace_graph_func+0xc5/0x140 arch/x86/kernel/ftrace.c:671 __kernel_text_address+0x9/0x40 kernel/extable.c:78 arch_stack_walk+0x117/0x170 arch/x86/kernel/stacktrace.c:26 kmem_cache_free+0x188/0x580 mm/slub.c:6378 tcp_data_queue+0x18d/0x6550 net/ipv4/tcp_input.c:5590 [...] The list cannot be frozen across the two walks, so skip a node that does not fit the reservation and count it as missed. Link: https://lore.kernel.org/all/20260619184425.3824774-1-rhkrqnwk98@gmail.com/ Fixes: 4346ba160409 ("fprobe: Rewrite fprobe on function-graph tracer") Signed-off-by: Sechang Lim Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/fprobe.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'kernel/trace') diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index f378613ad120..f215990b9061 100644 --- a/kernel/trace/fprobe.c +++ b/kernel/trace/fprobe.c @@ -613,6 +613,16 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops continue; data_size = fp->entry_data_size; + /* + * The list may have grown since it was sized, so this node + * may not fit. Skip it as missed rather than overrun the + * reservation. + */ + if (fp->exit_handler && + used + FPROBE_HEADER_SIZE_IN_LONG + SIZE_IN_LONG(data_size) > reserved_words) { + fp->nmissed++; + continue; + } if (data_size && fp->exit_handler) data = fgraph_data + used + FPROBE_HEADER_SIZE_IN_LONG; else -- cgit v1.2.3 From a369299c3f785cf556bbef2de2db0aa2d294c4c9 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 25 Jun 2026 08:34:48 +0900 Subject: tracing/probes: Make the $ prefix mandatory for comm access Since $comm or $COMM are not event field but special fetcharg variables to access current->comm, It should not be accessed without '$' prefix even with typecast. Link: https://lore.kernel.org/all/178231209724.732967.12049805699091810641.stgit@devnote2/ Fixes: 69efd863a785 ("tracing/eprobes: Allow use of BTF names to dereference pointers") Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 502fa6da5949..d17cfee77d9c 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -342,10 +342,6 @@ static int parse_trace_event(char *arg, struct fetch_insn *code, ret = parse_trace_event_arg(arg, code, ctx); if (!ret) return 0; - if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) { - code->op = FETCH_OP_COMM; - return 0; - } return -EINVAL; } @@ -1068,8 +1064,14 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t, int len; if (ctx->flags & TPARG_FL_TEVENT) { - if (parse_trace_event(arg, code, ctx) < 0) + if (parse_trace_event(arg, code, ctx) < 0) { + /* 'comm' should be checked after field parsing. */ + if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) { + code->op = FETCH_OP_COMM; + return 0; + } goto inval; + } return 0; } -- cgit v1.2.3 From 859055e07697c46f6964109981aa1cd23d6bde47 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Mon, 29 Jun 2026 23:22:06 +0200 Subject: bpf: Add tracing_multi link info support Adding BPF_OBJ_GET_INFO_BY_FD support for tracing_multi links. We expose following tracing_multi link data: - attach_type of the program - number of ids - array of BTF ids - array of its related kernel addresses - array of cookies The change follows the kprobe_multi and uprobe_multi link-info convention of optional output arrays with an in/out count, On top of standard tracing link data we also expose addresses, because they are useful info for user (especially when the attachment was done via pattern). This data is hidden when kallsyms does not allow exposing kernel pointer values. Assisted-by: Codex:GPT-5 Signed-off-by: Jiri Olsa Signed-off-by: Andrii Nakryiko Acked-by: Leon Hwang Acked-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20260629212208.895962-2-jolsa@kernel.org --- include/uapi/linux/bpf.h | 9 +++++++ kernel/trace/bpf_trace.c | 55 ++++++++++++++++++++++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 9 +++++++ 3 files changed, 73 insertions(+) (limited to 'kernel/trace') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index c91b5a4bda03..2f1d24fef857 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -6842,6 +6842,15 @@ struct bpf_link_info { __u32 flags; __u32 pid; } uprobe_multi; + struct { + __u32 attach_type; + __u32 count; /* in/out: tracing_multi target count */ + __u32 btf_obj_id; + __u32 :32; + __aligned_u64 ids; + __aligned_u64 addrs; + __aligned_u64 cookies; + } tracing_multi; struct { __u32 type; /* enum bpf_perf_event_type */ __u32 :32; diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 75495a5c3507..76ab51deaa6b 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -3700,6 +3700,60 @@ static void bpf_tracing_multi_link_dealloc(struct bpf_link *link) kvfree(tr_link); } +static int bpf_tracing_multi_link_fill_link_info(const struct bpf_link *link, + struct bpf_link_info *info) +{ + u64 __user *ucookies = u64_to_user_ptr(info->tracing_multi.cookies); + u64 __user *uaddrs = u64_to_user_ptr(info->tracing_multi.addrs); + u32 __user *uids = u64_to_user_ptr(info->tracing_multi.ids); + struct bpf_tracing_multi_link *tr_link; + u32 ucount = info->tracing_multi.count; + bool has_cookies, show_addrs; + int err = 0; + + if ((uids || ucookies || uaddrs) && !ucount) + return -EINVAL; + + tr_link = container_of(link, struct bpf_tracing_multi_link, link); + + info->tracing_multi.attach_type = tr_link->link.attach_type; + info->tracing_multi.count = tr_link->nodes_cnt; + info->tracing_multi.btf_obj_id = btf_obj_id(tr_link->link.prog->aux->attach_btf); + + if (!uids && !ucookies && !uaddrs) + return 0; + + if (ucount < tr_link->nodes_cnt) + err = -ENOSPC; + else + ucount = tr_link->nodes_cnt; + + has_cookies = !!tr_link->cookies; + show_addrs = kallsyms_show_value(current_cred()); + + for (int i = 0; i < ucount; i++) { + struct bpf_tracing_multi_node *mnode = &tr_link->nodes[i]; + u64 addr, cookie; + u32 id; + + bpf_trampoline_unpack_key(mnode->trampoline->key, NULL, &id); + + addr = show_addrs ? mnode->trampoline->ip : 0; + cookie = has_cookies ? tr_link->cookies[i] : 0; + + if (uids && put_user(id, uids + i)) + return -EFAULT; + if (uaddrs && put_user(addr, uaddrs + i)) + return -EFAULT; + if (ucookies && put_user(cookie, ucookies + i)) + return -EFAULT; + + cond_resched(); + } + + return err; +} + #ifdef CONFIG_PROC_FS static void bpf_tracing_multi_show_fdinfo(const struct bpf_link *link, struct seq_file *seq) @@ -3730,6 +3784,7 @@ static void bpf_tracing_multi_show_fdinfo(const struct bpf_link *link, static const struct bpf_link_ops bpf_tracing_multi_link_lops = { .release = bpf_tracing_multi_link_release, .dealloc_deferred = bpf_tracing_multi_link_dealloc, + .fill_link_info = bpf_tracing_multi_link_fill_link_info, #ifdef CONFIG_PROC_FS .show_fdinfo = bpf_tracing_multi_show_fdinfo, #endif diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index c91b5a4bda03..2f1d24fef857 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -6842,6 +6842,15 @@ struct bpf_link_info { __u32 flags; __u32 pid; } uprobe_multi; + struct { + __u32 attach_type; + __u32 count; /* in/out: tracing_multi target count */ + __u32 btf_obj_id; + __u32 :32; + __aligned_u64 ids; + __aligned_u64 addrs; + __aligned_u64 cookies; + } tracing_multi; struct { __u32 type; /* enum bpf_perf_event_type */ __u32 :32; -- cgit v1.2.3 From b6c4dd07150dfd2d984dc306bada8f2dd4a2a73e Mon Sep 17 00:00:00 2001 From: Martin Kaiser Date: Thu, 2 Jul 2026 21:12:39 +0900 Subject: tracing: trace_fprobe: fix typo in function name The function name should be __register_tracepoint_fprobe. Link: https://lore.kernel.org/all/20260507081041.885781-5-martin@kaiser.cx/ Signed-off-by: Martin Kaiser Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_fprobe.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c index 9f5f08c0e7c2..4d1abbf66229 100644 --- a/kernel/trace/trace_fprobe.c +++ b/kernel/trace/trace_fprobe.c @@ -764,7 +764,7 @@ static int unregister_fprobe_event(struct trace_fprobe *tf) return trace_probe_unregister_event_call(&tf->tp); } -static int __regsiter_tracepoint_fprobe(struct trace_fprobe *tf) +static int __register_tracepoint_fprobe(struct trace_fprobe *tf) { struct tracepoint_user *tuser __free(tuser_put) = NULL; struct module *mod __free(module_put) = NULL; @@ -836,7 +836,7 @@ static int __register_trace_fprobe(struct trace_fprobe *tf) tf->fp.flags &= ~FPROBE_FL_DISABLED; if (trace_fprobe_is_tracepoint(tf)) - return __regsiter_tracepoint_fprobe(tf); + return __register_tracepoint_fprobe(tf); /* TODO: handle filter, nofilter or symbol list */ return register_fprobe(&tf->fp, tf->symbol, NULL); -- cgit v1.2.3 From 50133489c33764219a505ddb7e9b42cc6d8bfafa Mon Sep 17 00:00:00 2001 From: Yudistira Putra Date: Thu, 2 Jul 2026 21:12:39 +0900 Subject: tracing/probes: make file offset error message probe-agnostic The shared probe argument parser rejects file offsets for kernel probes. This path is used outside the kprobe event parser too, but the diagnostic currently says "with kprobe" even when emitted from another probe path. Make the diagnostic probe-agnostic. Link: https://lore.kernel.org/all/20260622160032.99834-1-pyudistira519@gmail.com/ Signed-off-by: Yudistira Putra Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 2 +- kernel/trace/trace_probe.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index d17cfee77d9c..9a58c0789ac5 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1230,7 +1230,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type, code->op = FETCH_OP_IMM; code->immediate = param; } else if (arg[1] == '+') { - /* kprobes don't support file offsets */ + /* Kernel probes do not support file offsets */ if (ctx->flags & TPARG_FL_KERNEL) { trace_probe_log_err(ctx->offset, FILE_ON_KPROBE); return -EINVAL; diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 0f09f7aaf93f..43e6954c14c5 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -516,7 +516,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, C(BAD_MEM_ADDR, "Invalid memory address"), \ C(BAD_IMM, "Invalid immediate value"), \ C(IMMSTR_NO_CLOSE, "String is not closed with '\"'"), \ - C(FILE_ON_KPROBE, "File offset is not available with kprobe"), \ + C(FILE_ON_KPROBE, "File offset is not available for kernel probes"), \ C(BAD_FILE_OFFS, "Invalid file offset value"), \ C(SYM_ON_UPROBE, "Symbol is not available with uprobe"), \ C(TOO_MANY_OPS, "Dereference is too much nested"), \ -- cgit v1.2.3 From 7e07baaa9e41c4140924a90f1c5cf8feeb9879fb Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:39 +0900 Subject: tracing/probes: Rename FETCH_OP_DATA to FETCH_OP_IMMSTR Since FETCH_OP_DATA is used solely to store immediate string values, rename it to the more specific FETCH_OP_IMMSTR. No behavior change, just rename it. Link: https://lore.kernel.org/all/178217906914.643090.2958799486090467704.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 12 ++++++------ kernel/trace/trace_probe.h | 2 +- kernel/trace/trace_probe_tmpl.h | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 9a58c0789ac5..348a7f97d6c7 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1309,7 +1309,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type, break; ctx->offset = cur_offs; if (code->op == FETCH_OP_COMM || - code->op == FETCH_OP_DATA) { + code->op == FETCH_OP_IMMSTR) { trace_probe_log_err(ctx->offset, COMM_CANT_DEREF); return -EINVAL; } @@ -1330,7 +1330,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type, ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2); if (ret) break; - code->op = FETCH_OP_DATA; + code->op = FETCH_OP_IMMSTR; code->data = tmp; } else { ret = str_to_immediate(arg + 1, &code->immediate); @@ -1485,7 +1485,7 @@ static int finalize_fetch_insn(struct fetch_insn *code, } else { if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF && code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM && - code->op != FETCH_OP_DATA && code->op != FETCH_OP_TP_ARG) { + code->op != FETCH_OP_IMMSTR && code->op != FETCH_OP_TP_ARG) { trace_probe_log_err(ctx->offset + type_offset, BAD_STRING); return -EINVAL; @@ -1494,7 +1494,7 @@ static int finalize_fetch_insn(struct fetch_insn *code, if (!strcmp(parg->type->name, "symstr") || (code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM || - code->op == FETCH_OP_DATA) || code->op == FETCH_OP_TP_ARG || + code->op == FETCH_OP_IMMSTR) || code->op == FETCH_OP_TP_ARG || parg->count) { /* * IMM, DATA and COMM is pointing actual address, those @@ -1670,7 +1670,7 @@ fail: if (ret < 0) { for (code = tmp; code < tmp + FETCH_INSN_MAX; code++) if (code->op == FETCH_NOP_SYMBOL || - code->op == FETCH_OP_DATA) + code->op == FETCH_OP_IMMSTR) kfree(code->data); } kfree(tmp); @@ -1769,7 +1769,7 @@ void traceprobe_free_probe_arg(struct probe_arg *arg) while (code && code->op != FETCH_OP_END) { if (code->op == FETCH_NOP_SYMBOL || - code->op == FETCH_OP_DATA) + code->op == FETCH_OP_IMMSTR) kfree(code->data); code++; } diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 43e6954c14c5..40b53b5b58a9 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -94,7 +94,7 @@ enum fetch_op { FETCH_OP_COMM, /* Current comm */ FETCH_OP_ARG, /* Function argument : .param */ FETCH_OP_FOFFS, /* File offset: .immediate */ - FETCH_OP_DATA, /* Allocated data: .data */ + FETCH_OP_IMMSTR, /* Allocated string: .data */ FETCH_OP_EDATA, /* Entry data: .offset */ // Stage 2 (dereference) op FETCH_OP_DEREF, /* Dereference: .offset */ diff --git a/kernel/trace/trace_probe_tmpl.h b/kernel/trace/trace_probe_tmpl.h index f39b37fcdb3b..51436f19083b 100644 --- a/kernel/trace/trace_probe_tmpl.h +++ b/kernel/trace/trace_probe_tmpl.h @@ -109,7 +109,7 @@ process_common_fetch_insn(struct fetch_insn *code, unsigned long *val) case FETCH_OP_COMM: *val = (unsigned long)current->comm; break; - case FETCH_OP_DATA: + case FETCH_OP_IMMSTR: *val = (unsigned long)code->data; break; default: -- cgit v1.2.3 From 823b37855829bc328d46102a56e4d0b2f7a3d0d1 Mon Sep 17 00:00:00 2001 From: Martin Kaiser Date: Thu, 2 Jul 2026 21:12:40 +0900 Subject: tracing/probes: ignore id update from btf_type_skip_modifiers We can pass NULL as id pointer to btf_type_skip_modifiers if we do not need the id of the returned btf_type. Link: https://lore.kernel.org/all/20260623132937.3494895-1-martin@kaiser.cx/ Signed-off-by: Martin Kaiser Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 348a7f97d6c7..0da7c0b53ba7 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -356,9 +356,8 @@ static bool btf_type_is_char_ptr(struct btf *btf, const struct btf_type *type) { const struct btf_type *real_type; u32 intdata; - s32 tid; - real_type = btf_type_skip_modifiers(btf, type->type, &tid); + real_type = btf_type_skip_modifiers(btf, type->type, NULL); if (!real_type) return false; @@ -375,14 +374,13 @@ static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type) const struct btf_type *real_type; const struct btf_array *array; u32 intdata; - s32 tid; if (BTF_INFO_KIND(type->info) != BTF_KIND_ARRAY) return false; array = (const struct btf_array *)(type + 1); - real_type = btf_type_skip_modifiers(btf, array->type, &tid); + real_type = btf_type_skip_modifiers(btf, array->type, NULL); intdata = btf_type_int(real_type); return !(BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED) @@ -585,7 +583,6 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type, struct btf *btf = ctx_btf(ctx); char *next; int is_ptr; - s32 tid; do { if (!is_struct) { @@ -596,7 +593,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type, } /* Convert a struct pointer type to a struct type */ - type = btf_type_skip_modifiers(btf, type->type, &tid); + type = btf_type_skip_modifiers(btf, type->type, NULL); if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL; @@ -636,7 +633,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type, ctx->last_bitsize = 0; } - type = btf_type_skip_modifiers(btf, field->type, &tid); + type = btf_type_skip_modifiers(btf, field->type, NULL); if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL; @@ -755,7 +752,7 @@ static int parse_btf_arg(char *varname, return -ENOENT; found: - type = btf_type_skip_modifiers(ctx->btf, tid, &tid); + type = btf_type_skip_modifiers(ctx->btf, tid, NULL); found_type: if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); -- cgit v1.2.3 From 427c240a027fca465e4c339ae08584632b525bfa Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:40 +0900 Subject: tracing/probes: Allow eprobe to use variable without $ prefix The commit 69efd863a785 ("tracing/eprobes: Allow use of BTF names to dereference pointers") allows eprobe to use event field without "$" prefix when it is used with typecast, it is natual to allow it without typecast. Link: https://lore.kernel.org/all/178271358117.1176915.13520279547897961254.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 12 +++++++++++- kernel/trace/trace_probe.h | 1 + .../ftrace/test.d/dynevent/eprobes_syntax_errors.tc | 3 +-- 3 files changed, 13 insertions(+), 3 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 0da7c0b53ba7..2ce7d62471cb 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1341,7 +1341,17 @@ 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 (isalpha(arg[0]) || arg[0] == '_') { + /* BTF variable or event field*/ + if (ctx->flags & TPARG_FL_TEVENT) { + ret = parse_trace_event(arg, *pcode, ctx); + if (ret < 0) { + trace_probe_log_err(ctx->offset, + NO_EVENT_FIELD); + return -EINVAL; + } + break; + } if (!tparg_is_function_entry(ctx->flags) && !tparg_is_function_return(ctx->flags)) { trace_probe_log_err(ctx->offset, NOSUP_BTFARG); diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 40b53b5b58a9..2e0d8384ee5c 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -559,6 +559,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, C(NO_PTR_STRCT, "This is not a pointer to union/structure."), \ C(NOSUP_DAT_ARG, "Non pointer structure/union argument is not supported."),\ C(BAD_HYPHEN, "Failed to parse single hyphen. Forgot '>'?"), \ + C(NO_EVENT_FIELD, "This event field is not found."), \ C(NO_BTF_FIELD, "This field is not found."), \ C(BAD_BTF_TID, "Failed to get BTF type info."),\ C(BAD_TYPE4STR, "This type does not fit for string."),\ diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/eprobes_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/eprobes_syntax_errors.tc index 2a680c086047..0e65e787e426 100644 --- a/tools/testing/selftests/ftrace/test.d/dynevent/eprobes_syntax_errors.tc +++ b/tools/testing/selftests/ftrace/test.d/dynevent/eprobes_syntax_errors.tc @@ -10,7 +10,7 @@ check_error() { # command-with-error-pos-by-^ check_error 'e ^a.' # NO_EVENT_INFO check_error 'e ^.b' # NO_EVENT_INFO check_error 'e ^a.b' # BAD_ATTACH_EVENT -check_error 'e syscalls/sys_enter_openat ^foo' # BAD_ATTACH_ARG +check_error 'e syscalls/sys_enter_openat ^foo' # NO_EVENT_FIELD check_error 'e:^/bar syscalls/sys_enter_openat' # NO_GROUP_NAME check_error 'e:^12345678901234567890123456789012345678901234567890123456789012345/bar syscalls/sys_enter_openat' # GROUP_TOO_LONG @@ -19,7 +19,6 @@ check_error 'e:^ syscalls/sys_enter_openat' # NO_EVENT_NAME check_error 'e:foo/^12345678901234567890123456789012345678901234567890123456789012345 syscalls/sys_enter_openat' # EVENT_TOO_LONG check_error 'e:foo/^bar.1 syscalls/sys_enter_openat' # BAD_EVENT_NAME -check_error 'e:foo/bar syscalls/sys_enter_openat arg=^dfd' # BAD_FETCH_ARG check_error 'e:foo/bar syscalls/sys_enter_openat arg=^$foo' # BAD_ATTACH_ARG if grep -q '\..*\[if \]' README; then -- cgit v1.2.3 From dccddc39aa7d0167e16e9fb94942fd37a30d581b Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:40 +0900 Subject: tracing/probes: Support dumping fetcharg program for debugging dynamic events For debugging probe events, it is helpful to verify the compiled fetch instructions for each probe argument. This introduces a new kernel config CONFIG_PROBE_EVENTS_DUMP_FETCHARG to decode the instruction sequence of each argument and display it under a commented line starting with '#' immediately following the dynamic event definition (such as in dynamic_events, kprobe_events, uprobe_events, etc.). For example: /sys/kernel/tracing # cat dynamic_events p:kprobes/p_vfs_read_0 vfs_read arg1=+0(file):ustring arg2=%ax:x16 # arg1: ARG(0) -> ST_USTRING(offset=0,size=4) -> END # arg2: REG(80) -> ST_RAW(size=2) -> END Link: https://lore.kernel.org/all/178271359508.1176915.11895926894608440183.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/Kconfig | 12 ++++++ kernel/trace/trace_eprobe.c | 2 + kernel/trace/trace_fprobe.c | 2 + kernel/trace/trace_kprobe.c | 2 + kernel/trace/trace_probe.c | 96 +++++++++++++++++++++++++++++++++++++++++++++ kernel/trace/trace_probe.h | 79 ++++++++++++++++++++++--------------- kernel/trace/trace_uprobe.c | 3 ++ 7 files changed, 164 insertions(+), 32 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index 084f34dc6c9f..0ab5916575a9 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -779,6 +779,18 @@ config PROBE_EVENTS_BTF_ARGS kernel function entry or a tracepoint. This is available only if BTF (BPF Type Format) support is enabled. +config PROBE_EVENTS_DUMP_FETCHARG + bool "Dump of dynamic probe event fetch-arguments" + depends on PROBE_EVENTS + default n + help + This shows the dump of fetch-arguments of dynamic probe events + alongside their event definitions in the dynamic_events file + as comment lines. This is useful to debug the probe events. + Since this exposes the raw values in the dynamic_events file, + it might be a security risk. Only enable it if you need to debug + probe events themselves. + config KPROBE_EVENTS depends on KPROBES depends on HAVE_REGS_AND_STACK_ACCESS_API diff --git a/kernel/trace/trace_eprobe.c b/kernel/trace/trace_eprobe.c index 50518b071414..462c31145733 100644 --- a/kernel/trace/trace_eprobe.c +++ b/kernel/trace/trace_eprobe.c @@ -87,6 +87,8 @@ static int eprobe_dyn_event_show(struct seq_file *m, struct dyn_event *ev) seq_printf(m, " %s=%s", ep->tp.args[i].name, ep->tp.args[i].comm); seq_putc(m, '\n'); + trace_probe_dump_args(m, &ep->tp); + return 0; } diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c index 4d1abbf66229..536781cd4c47 100644 --- a/kernel/trace/trace_fprobe.c +++ b/kernel/trace/trace_fprobe.c @@ -1449,6 +1449,8 @@ static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev) seq_printf(m, " %s=%s", tf->tp.args[i].name, tf->tp.args[i].comm); seq_putc(m, '\n'); + trace_probe_dump_args(m, &tf->tp); + return 0; } diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c index a8420e6abb56..cfa807d8e760 100644 --- a/kernel/trace/trace_kprobe.c +++ b/kernel/trace/trace_kprobe.c @@ -1320,6 +1320,8 @@ static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev) seq_printf(m, " %s=%s", tk->tp.args[i].name, tk->tp.args[i].comm); seq_putc(m, '\n'); + trace_probe_dump_args(m, &tk->tp); + return 0; } diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 2ce7d62471cb..0908019aea12 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -2403,3 +2403,99 @@ int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_a } return 0; } + +#ifdef CONFIG_PROBE_EVENTS_DUMP_FETCHARG + +struct fetch_op_decode { + const char *name; + void (*decode)(struct seq_file *m, struct fetch_insn *insn); +}; + +static const struct fetch_op_decode fetch_op_decode[]; + +static void fetcharg_decode_none(struct seq_file *m, struct fetch_insn *insn) +{ + seq_puts(m, fetch_op_decode[insn->op].name); +} + +static void fetcharg_decode_param(struct seq_file *m, struct fetch_insn *insn) +{ + seq_printf(m, "%s(%u)", fetch_op_decode[insn->op].name, insn->param); +} + +static void fetcharg_decode_imm(struct seq_file *m, struct fetch_insn *insn) +{ + seq_printf(m, "%s(0x%lx)", fetch_op_decode[insn->op].name, insn->immediate); +} + +static void fetcharg_decode_string(struct seq_file *m, struct fetch_insn *insn) +{ + seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, (char *)insn->data); +} + +static void fetcharg_decode_symbol(struct seq_file *m, struct fetch_insn *insn) +{ + seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, (char *)insn->data); +} + +static void fetcharg_decode_offset(struct seq_file *m, struct fetch_insn *insn) +{ + seq_printf(m, "%s(offset=%d)", fetch_op_decode[insn->op].name, insn->offset); +} + +static void fetcharg_decode_store(struct seq_file *m, struct fetch_insn *insn) +{ + if (insn->op == FETCH_OP_ST_RAW) + seq_printf(m, "%s(size=%u)", fetch_op_decode[insn->op].name, insn->size); + else + seq_printf(m, "%s(offset=%d,size=%u)", fetch_op_decode[insn->op].name, + insn->offset, insn->size); +} + +static void fetcharg_decode_bf(struct seq_file *m, struct fetch_insn *insn) +{ + seq_printf(m, "%s(basesize=%u,lshift=%u,rshift=%u)", + fetch_op_decode[insn->op].name, insn->basesize, insn->lshift, insn->rshift); +} + +static void fetcharg_decode_tp_arg(struct seq_file *m, struct fetch_insn *insn) +{ + struct ftrace_event_field *field = insn->data; + + seq_printf(m, "%s(%s)", fetch_op_decode[insn->op].name, field->name); +} + +#define FETCH_OP(opname, decode_fn) \ + [FETCH_OP_##opname] = { .name = #opname, .decode = fetcharg_decode_##decode_fn } + +static const struct fetch_op_decode fetch_op_decode[] = FETCH_OP_LIST; +#undef FETCH_OP + +static void trace_probe_dump_arg(struct seq_file *m, struct probe_arg *parg) +{ + int i; + + seq_printf(m, "# %s: ", parg->name); + for (i = 0; i < FETCH_INSN_MAX; i++) { + struct fetch_insn *insn = parg->code + i; + + if (insn->op >= ARRAY_SIZE(fetch_op_decode) || !fetch_op_decode[insn->op].decode) + seq_printf(m, "unknown(%d)", insn->op); + else + fetch_op_decode[insn->op].decode(m, insn); + + if (insn->op == FETCH_OP_END) + break; + seq_puts(m, " -> "); + } + seq_putc(m, '\n'); +} + +void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp) +{ + int i; + + for (i = 0; i < tp->nr_args; i++) + trace_probe_dump_arg(m, &tp->args[i]); +} +#endif /* CONFIG_PROBE_EVENTS_DUMP_FETCHARG */ diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 2e0d8384ee5c..e36cfe39e9a8 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -83,38 +83,46 @@ static nokprobe_inline u32 update_data_loc(u32 loc, int consumed) /* Printing function type */ typedef int (*print_type_func_t)(struct trace_seq *, void *, void *); -enum fetch_op { - FETCH_OP_NOP = 0, - // Stage 1 (load) ops - FETCH_OP_REG, /* Register : .param = offset */ - FETCH_OP_STACK, /* Stack : .param = index */ - FETCH_OP_STACKP, /* Stack pointer */ - FETCH_OP_RETVAL, /* Return value */ - FETCH_OP_IMM, /* Immediate : .immediate */ - FETCH_OP_COMM, /* Current comm */ - FETCH_OP_ARG, /* Function argument : .param */ - FETCH_OP_FOFFS, /* File offset: .immediate */ - FETCH_OP_IMMSTR, /* Allocated string: .data */ - FETCH_OP_EDATA, /* Entry data: .offset */ - // Stage 2 (dereference) op - FETCH_OP_DEREF, /* Dereference: .offset */ - FETCH_OP_UDEREF, /* User-space Dereference: .offset */ - // 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_STRING, /* String: .offset, .size */ - FETCH_OP_ST_USTRING, /* User String: .offset, .size */ - FETCH_OP_ST_SYMSTR, /* Kernel Symbol String: .offset, .size */ - FETCH_OP_ST_EDATA, /* Store Entry Data: .offset */ - // Stage 4 (modify) op - FETCH_OP_MOD_BF, /* Bitfield: .basesize, .lshift, .rshift */ - // Stage 5 (loop) op - FETCH_OP_LP_ARRAY, /* Array: .param = loop count */ - FETCH_OP_TP_ARG, /* Trace Point argument */ - FETCH_OP_END, - FETCH_NOP_SYMBOL, /* Unresolved Symbol holder */ -}; +#define FETCH_OP_LIST { \ + /* Stage 1 (load) ops */ \ + FETCH_OP(NOP, none), /* NOP */ \ + FETCH_OP(REG, param), /* Register: .param = offset */ \ + FETCH_OP(STACK, param), /* Stack: .param = index */ \ + FETCH_OP(STACKP, none), /* Stack pointer */ \ + FETCH_OP(RETVAL, none), /* Return value */ \ + FETCH_OP(IMM, imm), /* Immediate: .immediate */ \ + FETCH_OP(COMM, none), /* Current comm */ \ + FETCH_OP(ARG, param), /* Argument: .param = index */ \ + FETCH_OP(FOFFS, imm), /* File offset: .immediate */ \ + FETCH_OP(IMMSTR, string), /* Allocated string: .data */ \ + FETCH_OP(EDATA, offset), /* Entry data: .offset */ \ + FETCH_OP(TP_ARG, tp_arg), /* Tracepoint argument: .data */\ + /* Stage 2 (dereference) ops */ \ + FETCH_OP(DEREF, offset), /* Dereference: .offset */ \ + FETCH_OP(UDEREF, offset), /* User-space dereference: .offset */\ + /* Stage 3 (store) ops */ \ + FETCH_OP(ST_RAW, store), /* Raw value: .size */ \ + FETCH_OP(ST_MEM, store), /* Memory: .offset, .size */ \ + FETCH_OP(ST_UMEM, store), /* User memory: .offset, .size */\ + FETCH_OP(ST_STRING, store), /* String: .offset, .size */ \ + FETCH_OP(ST_USTRING, store), /* User string: .offset, .size */\ + FETCH_OP(ST_SYMSTR, store), /* Symbol name: .offset, .size */\ + FETCH_OP(ST_EDATA, offset), /* Entry data: .offset */ \ + /* Stage 4 (modify) op */ \ + FETCH_OP(MOD_BF, bf), /* Bitfield: .basesize, .lshift, .rshift*/\ + /* Stage 5 (loop) op */ \ + FETCH_OP(LP_ARRAY, param), /* Loop array: .param = count */\ + /* End */ \ + FETCH_OP(END, none), \ + /* Unresolved Symbol holder */ \ + FETCH_OP(NOP_SYMBOL, symbol), /* Non loaded symbol: .data = symbol name */\ +} + +#define FETCH_OP(opname, decode_fn) FETCH_OP_##opname +enum fetch_op FETCH_OP_LIST; +#undef FETCH_OP + +#define FETCH_NOP_SYMBOL FETCH_OP_NOP_SYMBOL struct fetch_insn { enum fetch_op op; @@ -370,6 +378,13 @@ bool trace_probe_match_command_args(struct trace_probe *tp, int trace_probe_create(const char *raw_command, int (*createfn)(int, const char **)); int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_args, u8 *data, void *field); +#ifdef CONFIG_PROBE_EVENTS_DUMP_FETCHARG +void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp); +#else +static inline void trace_probe_dump_args(struct seq_file *m, struct trace_probe *tp) +{ +} +#endif #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API int traceprobe_get_entry_data_size(struct trace_probe *tp); diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c index c274346853d1..b2e264a4b96c 100644 --- a/kernel/trace/trace_uprobe.c +++ b/kernel/trace/trace_uprobe.c @@ -765,6 +765,9 @@ static int trace_uprobe_show(struct seq_file *m, struct dyn_event *ev) seq_printf(m, " %s=%s", tu->tp.args[i].name, tu->tp.args[i].comm); seq_putc(m, '\n'); + + trace_probe_dump_args(m, &tu->tp); + return 0; } -- cgit v1.2.3 From 15d9968b68e848a1a38ca875ba553b05dadae03d Mon Sep 17 00:00:00 2001 From: Yu Peng Date: Wed, 3 Jun 2026 14:25:32 +0800 Subject: tracing/synthetic: Free pending field on error path Some __create_synth_event() error paths run after parse_synth_field() succeeds but before the field is stored in fields[]. The common cleanup then misses the field. Free it before freeing argv. Link: https://patch.msgid.link/20260603062533.1096320-1-pengyu@kylinos.cn Signed-off-by: Yu Peng Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_synth.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c index e6871230bde9..cdd5b9332835 100644 --- a/kernel/trace/trace_events_synth.c +++ b/kernel/trace/trace_events_synth.c @@ -1446,13 +1446,13 @@ static int __create_synth_event(const char *name, const char *raw_fields) if (cmd_version > 1 && n_fields_this_loop >= 1) { synth_err(SYNTH_ERR_INVALID_CMD, errpos(field_str)); ret = -EINVAL; - goto err_free_arg; + goto err_free_field; } if (n_fields == SYNTH_FIELDS_MAX) { synth_err(SYNTH_ERR_TOO_MANY_FIELDS, 0); ret = -EINVAL; - goto err_free_arg; + goto err_free_field; } fields[n_fields++] = field; @@ -1491,6 +1491,8 @@ static int __create_synth_event(const char *name, const char *raw_fields) kfree(saved_fields); return ret; + err_free_field: + free_synth_field(field); err_free_arg: argv_free(argv); err: -- cgit v1.2.3 From c37e0a4b79a6bbb96ce5ffe279d7c001e20529e0 Mon Sep 17 00:00:00 2001 From: Hui Wang Date: Sun, 7 Jun 2026 15:24:30 +0800 Subject: ring-buffer: Fix event length with forced 8-byte alignment When RB_FORCE_8BYTE_ALIGNMENT is true, rb_calculate_event_length() reserves the space of event->array[0] for placing the data length and rb_update_event() stores the data length in event->array[0] accordingly. As a result the whole event length will add extra 4 bytes for sizeof(event.array[0]) unconditionally. But ring_buffer_event_length() only subtracts the sizeof(event->array[0]) for events larger than RB_MAX_SMALL_DATA + sizeof(event->array[0]). As a result, small events on architectures with RB_FORCE_8BYTE_ALIGNMENT=true report a data length that is 4 bytes larger than expected. To fix it, add the RB_FORCE_8BYTE_ALIGNMENT as a condition to subtract the size of that length field whenever RB_FORCE_8BYTE_ALIGNMENT is true. This issue is observed in a riscv64 kernel with CONFIG_HAVE_64BIT_ALIGNED_ACCESS set to y, when we run ftrace selftest trace_marker_raw.tc, we get the weird log: for cases where the id is 1..100, the number of data field is 8*N, but once id exceeds 100, the number of data field becomes 8*N+4: # 1 buf: 58 00 00 00 80 5e d1 63 (number of data field is 8*1) ... # a buf: 58 ... (number of data field is 8*2) ... # 64 buf: 58 ... (number of data field is 8*13) # 65 buf: 58 ... (number of data field is 8*13+4) After applying this change, the number of data field keeps being 8*N+4 consistently. Link: https://patch.msgid.link/20260607072431.125633-2-hui.wang@canonical.com Fixes: 2271048d1b3b ("ring-buffer: Do 8 byte alignment for 64 bit that can not handle 4 byte align") Signed-off-by: Hui Wang Reviewed-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 56a328e94395..d9af2bbaf9c0 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -270,7 +270,8 @@ unsigned ring_buffer_event_length(struct ring_buffer_event *event) if (event->type_len > RINGBUF_TYPE_DATA_TYPE_LEN_MAX) return length; length -= RB_EVNT_HDR_SIZE; - if (length > RB_MAX_SMALL_DATA + sizeof(event->array[0])) + if (length > RB_MAX_SMALL_DATA + sizeof(event->array[0]) || + RB_FORCE_8BYTE_ALIGNMENT) length -= sizeof(event->array[0]); return length; } -- cgit v1.2.3 From fe58f457ad8d0a2bef4e053cfecca4b5cd266b1a Mon Sep 17 00:00:00 2001 From: Crystal Wood Date: Mon, 8 Jun 2026 23:54:30 -0500 Subject: tracing/osnoise: Call synchronize_rcu() when unregistering This ensures that any RCU readers traversing the instance list have finished, before releasing the reference on the tracer that the instance points to. Cc: stable@vger.kernel.org Fixes: a6ed2aee54644 ("tracing: Switch to kvfree_rcu() API") Link: https://patch.msgid.link/20260609045430.1589786-1-crwood@redhat.com Suggested-by: Steven Rostedt Signed-off-by: Crystal Wood Signed-off-by: Steven Rostedt --- kernel/trace/trace_osnoise.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c index 5e83c4f6f2b4..0e1265acd1cc 100644 --- a/kernel/trace/trace_osnoise.c +++ b/kernel/trace/trace_osnoise.c @@ -179,7 +179,9 @@ static void osnoise_unregister_instance(struct trace_array *tr) if (!found) return; - kvfree_rcu_mightsleep(inst); + /* Do a full sync to ensure that tr remains valid, not just inst */ + synchronize_rcu(); + kvfree(inst); } /* -- cgit v1.2.3 From a20b08de862c2983eac183b7560ad678acb58608 Mon Sep 17 00:00:00 2001 From: "Wayen.Yan" Date: Thu, 11 Jun 2026 11:52:57 +0800 Subject: tracing: Remove unused ret assignment in tracing_set_tracer() In tracing_set_tracer(), the assignment 'ret = 0' following the __tracing_resize_ring_buffer() error check is a dead store. After this point, all subsequent code paths either return with a constant value (-EINVAL, 0, -EBUSY) or reassign ret before reading it (tracing_arm_snapshot_locked, tracer_init). Remove the unnecessary assignment. No functional change. Link: https://patch.msgid.link/6a2a37c4.f0a9eb5a.2fc603.7724@mx.google.com Signed-off-by: Wayen.Yan Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 1 - 1 file changed, 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 1146b83b711a..299f5ab630b9 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -5015,7 +5015,6 @@ int tracing_set_tracer(struct trace_array *tr, const char *buf) RING_BUFFER_ALL_CPUS); if (ret < 0) return ret; - ret = 0; } list_for_each_entry(t, &tr->tracers, list) { -- cgit v1.2.3 From af05b4e06279bbe34f8044b1e82f0725b5da91b7 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 16 Jun 2026 18:55:38 +0100 Subject: ring-buffer: Fix ring_buffer_read_page() copying only one event per page Commit 8928e4a3be34 ("ring-buffer: Show persistent buffer dropped events in trace_pipe file") split the "commit" variable in ring_buffer_read_page() into "commit" (raw) and "size" (masked page size), but the inner copy loop's terminator was changed to compare rpos against "event_size" instead of "size". rpos is the cumulative read offset within the page; event_size is the length of the single event just copied. The loop thus breaks after the first event, so only one event is copied per call. This regresses the per-event memcpy path (partial reads, the active commit page, and mapped/remote buffers) used by splice/trace_pipe_raw and mmap consumers into a one-event-at-a-time read. Compare rpos against the page size as the original code did. Link: https://patch.msgid.link/20260616175538.111628-1-devnexen@gmail.com Fixes: 8928e4a3be34 ("ring-buffer: Show persistent buffer dropped events in trace_pipe file") Signed-off-by: David Carlier Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index d9af2bbaf9c0..12a83d9ffd0a 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -7175,7 +7175,7 @@ int ring_buffer_read_page(struct trace_buffer *buffer, rpos = reader->read; pos += event_size; - if (rpos >= event_size) + if (rpos >= size) break; event = rb_reader_event(cpu_buffer); -- cgit v1.2.3 From d5c6785f94ead7cd76b38af301ac63cb673485bc Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Wed, 17 Jun 2026 11:58:22 +0100 Subject: tracing: Make tracepoint_printk static as not exported The tracepoint_printk symbol is not exported, so make it static to remove the following sparse warning: kernel/trace/trace.c:90:5: warning: symbol 'tracepoint_printk' was not declared. Should it be static? Fixes: dd293df6395a2 ("tracing: Move trace sysctls into trace.c") Link: https://patch.msgid.link/20260617105822.904164-1-ben.dooks@codethink.co.uk Signed-off-by: Ben Dooks Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 299f5ab630b9..18710c190c92 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -87,7 +87,7 @@ void __init disable_tracing_selftest(const char *reason) /* Pipe tracepoints to printk */ static struct trace_iterator *tracepoint_print_iter; -int tracepoint_printk; +static int tracepoint_printk; static bool tracepoint_printk_stop_on_boot __initdata; static bool traceoff_after_boot __initdata; static DEFINE_STATIC_KEY_FALSE(tracepoint_printk_key); -- cgit v1.2.3 From c3e94604675e3db186111b8942650d86577df9b0 Mon Sep 17 00:00:00 2001 From: Yuanhe Shu Date: Wed, 24 Jun 2026 14:17:15 +0800 Subject: tracing: Fix NULL pointer dereference in func_set_flag() func_set_flag() dereferences tr->current_trace_flags before verifying that the current tracer is actually the function tracer. When the active tracer has been switched away from "function" (e.g., to "wakeup_rt"), tr->current_trace_flags can be NULL, leading to a NULL pointer dereference and kernel crash. The call chain that triggers this is: trace_options_write() -> __set_tracer_option() -> trace->set_flag() /* func_set_flag */ In func_set_flag(), the first operation is: if (!!set == !!(tr->current_trace_flags->val & bit)) This dereferences tr->current_trace_flags unconditionally. The safety check that guards against a non-function tracer: if (tr->current_trace != &function_trace) return 0; is placed *after* the dereference, which is too late. This was observed with the following crash dump: BUG: unable to handle page fault at 0000000000000000 RIP: func_set_flag+0xd Call Trace: __set_tracer_option+0x27 trace_options_write+0x75 vfs_write+0x12a ksys_write+0x66 do_syscall_64+0x5b RIP: ffffffff914c973d RSP: ff67ec88b01dfdf0 RFLAGS: 00010202 RAX: 0000000000000000 RBX: ff3a826e80354580 RCX: 0000000000000001 RDX: 0000000000000001 RSI: 0000000000000000 RDI: ffffffff93918080 The disassembly confirms the fault: func_set_flag+0: mov 0x1f08(%rdi), %rax ; RAX = tr->current_trace_flags = NULL func_set_flag+13: mov (%rax), %eax ; page fault: dereference NULL At the time of the crash: tr->current_trace_flags = 0x0 (NULL) tr->current_trace = wakeup_rt_tracer (not function_trace) The scenario is that a process opens a function tracer option file (such as "func_stack_trace"), then the current tracer is switched to another tracer (e.g., "wakeup_rt"), which sets current_trace_flags to NULL. When the process subsequently writes to the option file, func_set_flag() is invoked and crashes on the NULL dereference. Fix this by moving the current_trace check before the current_trace_flags dereference, so that func_set_flag() returns early when the function tracer is not active. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260624061715.1445655-1-xiangzao@linux.alibaba.com Fixes: 76680d0d2825 ("tracing: Have function tracer define options per instance") Signed-off-by: Yuanhe Shu Signed-off-by: Steven Rostedt --- kernel/trace/trace_functions.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_functions.c b/kernel/trace/trace_functions.c index f283391a4dc8..cd37f2013758 100644 --- a/kernel/trace/trace_functions.c +++ b/kernel/trace/trace_functions.c @@ -458,12 +458,12 @@ func_set_flag(struct trace_array *tr, u32 old_flags, u32 bit, int set) ftrace_func_t func; u32 new_flags; - /* Do nothing if already set. */ - if (!!set == !!(tr->current_trace_flags->val & bit)) + /* We can change this flag only when current tracer is function. */ + if (tr->current_trace != &function_trace) return 0; - /* We can change this flag only when not running. */ - if (tr->current_trace != &function_trace) + /* Do nothing if already set. */ + if (!!set == !!(tr->current_trace_flags->val & bit)) return 0; new_flags = (tr->current_trace_flags->val & ~bit) | (set ? bit : 0); -- cgit v1.2.3 From 0a6070839b1ef276d5b05bedfb787743e140fb17 Mon Sep 17 00:00:00 2001 From: Huihui Huang Date: Wed, 1 Jul 2026 18:28:46 +0800 Subject: tracing: Prevent out-of-bounds read in glob matching String event fields are not necessarily NUL-terminated, so the filter predicate functions (filter_pred_string(), filter_pred_strloc() and filter_pred_strrelloc()) pass the field length to the regex match callbacks, and the length-aware matchers honour it. regex_match_glob() was the exception: it ignored the length and called glob_match(), which scans the string until it hits a NUL byte. Some string fields are not NUL-terminated. One example is the dynamic char array of the xfs_* namespace tracepoints, which is copied without a trailing NUL. For such a field, glob matching reads past the end of the event field, causing a KASAN slab-out-of-bounds read in glob_match(), reached via regex_match_glob() and filter_match_preds() from the xfs_lookup tracepoint. Add a length-bounded glob_match_len() and use it from regex_match_glob() so glob matching always stops at the field boundary. The matching loop is factored into a shared helper so glob_match() keeps its behaviour. Fixes: 60f1d5e3bac4 ("ftrace: Support full glob matching") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/da1aaf125fc3b63320b0c540fd6afa7c3d5b4f1a.1782836943.git.hhhuang@smu.edu.sg Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Zhengchuan Liang Reported-by: Xin Liu Assisted-by: Codex:GPT-5.4 Signed-off-by: Huihui Huang Signed-off-by: Ren Wei Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- include/linux/glob.h | 1 + kernel/trace/trace_events_filter.c | 6 ++---- lib/glob.c | 31 +++++++++++++++++++++++++++++-- 3 files changed, 32 insertions(+), 6 deletions(-) (limited to 'kernel/trace') diff --git a/include/linux/glob.h b/include/linux/glob.h index 861327b33e41..91595e750936 100644 --- a/include/linux/glob.h +++ b/include/linux/glob.h @@ -6,5 +6,6 @@ #include /* For __pure */ bool __pure glob_match(char const *pat, char const *str); +bool __pure glob_match_len(char const *pat, char const *str, size_t len); #endif /* _LINUX_GLOB_H */ diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c index 609325f57942..6385cd662d8d 100644 --- a/kernel/trace/trace_events_filter.c +++ b/kernel/trace/trace_events_filter.c @@ -1056,11 +1056,9 @@ static int regex_match_end(char *str, struct regex *r, int len) return 0; } -static int regex_match_glob(char *str, struct regex *r, int len __maybe_unused) +static int regex_match_glob(char *str, struct regex *r, int len) { - if (glob_match(r->pattern, str)) - return 1; - return 0; + return glob_match_len(r->pattern, str, len) ? 1 : 0; } /** diff --git a/lib/glob.c b/lib/glob.c index 7aca76c25bcb..c80d9dd736b4 100644 --- a/lib/glob.c +++ b/lib/glob.c @@ -11,6 +11,9 @@ MODULE_DESCRIPTION("glob(7) matching"); MODULE_LICENSE("Dual MIT/GPL"); +static bool __pure glob_match_str(char const *pat, char const *str, + char const *str_end); + /** * glob_match - Shell-style pattern matching, like !fnmatch(pat, str, 0) * @pat: Shell-style pattern to match, e.g. "*.[ch]". @@ -40,6 +43,29 @@ MODULE_LICENSE("Dual MIT/GPL"); * An opening bracket without a matching close is matched literally. */ bool __pure glob_match(char const *pat, char const *str) +{ + return glob_match_str(pat, str, NULL); +} +EXPORT_SYMBOL(glob_match); + +/** + * glob_match_len - glob match against a length-bounded string + * @pat: Shell-style pattern to match. + * @str: String to match. Need not be NUL-terminated. + * @len: Number of bytes of @str that may be read. + * + * Like glob_match(), but @str is only read up to @len bytes, so it can be + * used on buffers that are not NUL-terminated (e.g. trace event fields). + * A NUL byte within @len still terminates the string. + */ +bool __pure glob_match_len(char const *pat, char const *str, size_t len) +{ + return glob_match_str(pat, str, str + len); +} +EXPORT_SYMBOL(glob_match_len); + +static bool __pure glob_match_str(char const *pat, char const *str, + char const *str_end) { /* * Backtrack to previous * on mismatch and retry starting one @@ -55,9 +81,11 @@ bool __pure glob_match(char const *pat, char const *str) * on mismatch, or true after matching the trailing nul bytes. */ for (;;) { - unsigned char c = *str++; + unsigned char c = (str_end && str >= str_end) ? '\0' : *str; unsigned char d = *pat++; + str++; + switch (d) { case '?': /* Wildcard: anything but nul */ if (c == '\0') @@ -125,4 +153,3 @@ backtrack: } } } -EXPORT_SYMBOL(glob_match); -- cgit v1.2.3 From 1e67bb60bb03518d3bdd59a48c65d69e6d773bb8 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 1 Jul 2026 13:27:44 -0400 Subject: tracing: Add a no-rcu-check version of trace_##event##_enabled() Tracepoints require that RCU is watching. To prevent them from being used in places that RCU is not watching, the trace_##event() macro always calls rcu_is_watching() even when the event is not enabled and warns if RCU is not watching. This is to make sure a warning is triggered even if the tracepoint is never enabled (as it is only a bug when it is). It was noticed that tracepoints could be hidden within trace_#event#_enabled() calls, which are used to do extra work for the tracepoint only if the tracepoint is enabled. But this also can hide the fact that a tracepoint is placed in a location that can be called when RCU is not watching. Commit 9764e731ef6ab ("tracepoint: Add lockdep rcu_is_watching() check to trace_##name##_enabled()") added a check to the trace_##event##_enabled() macro to make sure RCU is watching when it is called to make sure not to hide the bug of a tracepoint being called when RCU is not watching. There is one case in the irq_disable tracepoint where it is within a trace_irq_disable_enabled() block, but it checks if RCU is watching, and if it isn't, it makes a call to ct_irq_enter() that makes RCU watch again. But because trace_irq_disable_enabled() now checks if RCU is watching and will trigger if it isn't. This is a false warning as the code within the block handles this case. Add a new internal macro __trace_##event##_enabled() that doesn't check if RCU is watching, and convert the irq_enable/disable tracepoints over to it. Link: https://patch.msgid.link/20260701132744.6a7fc68b@robin Reported-by: Geert Uytterhoeven Closes: https://lore.kernel.org/all/CAMuHMdXud_RpWag_hFqa2ByBGRxg6KnxGL1ObCWZrpTsk3TfAw@mail.gmail.com/ Fixes: 9764e731ef6ab ("tracepoint: Add lockdep rcu_is_watching() check to trace_##name##_enabled()") Tested-by: Geert Uytterhoeven Signed-off-by: Steven Rostedt --- include/linux/tracepoint.h | 12 +++++++++++- kernel/trace/trace_preemptirq.c | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'kernel/trace') diff --git a/include/linux/tracepoint.h b/include/linux/tracepoint.h index 4a0c36f40fe2..e0d838c9ce93 100644 --- a/include/linux/tracepoint.h +++ b/include/linux/tracepoint.h @@ -292,13 +292,18 @@ static inline struct tracepoint *tracepoint_ptr_deref(tracepoint_ptr_t *p) { \ } \ static inline bool \ + __trace_##name##_enabled(void) \ + { \ + return static_branch_unlikely(&__tracepoint_##name.key);\ + } \ + static inline bool \ trace_##name##_enabled(void) \ { \ if (IS_ENABLED(CONFIG_LOCKDEP)) { \ WARN_ONCE(!rcu_is_watching(), \ "RCU not watching for tracepoint"); \ } \ - return static_branch_unlikely(&__tracepoint_##name.key);\ + return __trace_##name##_enabled(); \ } #define __DECLARE_TRACE(name, proto, args, cond, data_proto) \ @@ -457,6 +462,11 @@ static inline struct tracepoint *tracepoint_ptr_deref(tracepoint_ptr_t *p) { \ } \ static inline bool \ + __trace_##name##_enabled(void) \ + { \ + return false; \ + } \ + static inline bool \ trace_##name##_enabled(void) \ { \ return false; \ diff --git a/kernel/trace/trace_preemptirq.c b/kernel/trace/trace_preemptirq.c index 0c42b15c3800..b63e3558948f 100644 --- a/kernel/trace/trace_preemptirq.c +++ b/kernel/trace/trace_preemptirq.c @@ -30,7 +30,7 @@ #else #define trace(point, args) \ do { \ - if (trace_##point##_enabled()) { \ + if (__trace_##point##_enabled()) { \ bool exit_rcu = false; \ if (in_nmi()) \ break; \ -- cgit v1.2.3 From 50fd6dd755c6e48a38af2fa4621167eea56829c2 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 7 Jul 2026 12:59:11 -0400 Subject: tracing/user_events: Fix use-after-free in user_event_mm_dup() user_event_mm_dup() walks the parent mm's enabler list locklessly under rcu_read_lock() during fork() (from copy_process()); it does not take event_mutex: rcu_read_lock(); list_for_each_entry_rcu(enabler, &old_mm->enablers, mm_enablers_link) enabler->event = user_event_get(orig->event); user_event_enabler_destroy() removes an enabler from that list with list_del_rcu() and then, without waiting for a grace period, drops the enabler's user_event reference with user_event_put() and frees the enabler with kfree(). A reader that loaded the enabler before the list_del_rcu() can still be walking it, which leads to two use-after-frees: - kfree(enabler) frees the enabler while that reader dereferences enabler->event. - user_event_put() may drop the last reference to the user_event, which is then freed (via delayed_destroy_user_event() on a work queue), while the same reader does user_event_get(orig->event) on it. Both are reachable by an unprivileged task that can open user_events_data: one multithreaded process that registers an enabler and then concurrently unregisters it and calls fork() triggers the race. KASAN reports a slab-use-after-free in user_event_mm_dup() during clone(), with a "refcount_t: addition on 0" warning when the user_event is freed. The enabler use-after-free was found first; the user_event one was reported by XIAO WU, and the earlier enabler-only fix did not address it. Defer both the user_event_put() and the kfree(enabler) to a work item queued with queue_rcu_work(), so they run only after an RCU grace period, once all readers walking the enabler list have finished. The put must run in process context because user_event_put() takes event_mutex on the last reference, so a work queue is used rather than call_rcu(). The now-unlocked put lets the locked argument of user_event_enabler_destroy() be removed; all callers are updated. Fixes: 7235759084a4 ("tracing/user_events: Use remote writes for event enablement") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260707165912.2560537-2-michael.bommarito@gmail.com Reported-by: XIAO WU Closes: https://lore.kernel.org/all/tencent_89647CE40DC452B891C65C94D1B271DE8E07@qq.com/ Suggested-by: Beau Belgrave Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_user.c | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index c4ba484f7b38..8c82ecb735f4 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -109,6 +109,9 @@ struct user_event_enabler { /* Track enable bit, flags, etc. Aligned for bitops. */ unsigned long values; + + /* Defer the event put and enabler free past an RCU grace period. */ + struct rcu_work put_rwork; }; /* Bits 0-5 are for the bit to update upon enable/disable (0-63 allowed) */ @@ -396,17 +399,39 @@ error: return NULL; }; -static void user_event_enabler_destroy(struct user_event_enabler *enabler, - bool locked) +static void delayed_user_event_enabler_put(struct work_struct *work) { - list_del_rcu(&enabler->mm_enablers_link); + struct user_event_enabler *enabler = container_of(to_rcu_work(work), + struct user_event_enabler, put_rwork); /* No longer tracking the event via the enabler */ - user_event_put(enabler->event, locked); + user_event_put(enabler->event, false); + /* Run from queue_rcu_work(), the RCU grace period has elapsed */ kfree(enabler); } +static void user_event_enabler_destroy(struct user_event_enabler *enabler) +{ + list_del_rcu(&enabler->mm_enablers_link); + + /* + * The enabler is removed from an RCU-traversed list + * (user_event_mm_dup() walks mm->enablers under rcu_read_lock() only), + * and readers there dereference enabler->event and take a new ref on + * it. Both the put of that event reference and the free of the enabler + * therefore have to wait for a grace period so no reader can be looking + * at the enabler or racing the last put of its event. + * + * The put itself must not run in RCU context: when it drops the last + * reference user_event_put() takes event_mutex, which cannot be taken + * from a softirq/RCU callback. Defer both to a work item scheduled + * after a grace period via queue_rcu_work(). + */ + INIT_RCU_WORK(&enabler->put_rwork, delayed_user_event_enabler_put); + queue_rcu_work(system_percpu_wq, &enabler->put_rwork); +} + static int user_event_mm_fault_in(struct user_event_mm *mm, unsigned long uaddr, int attempt) { @@ -464,7 +489,7 @@ static void user_event_enabler_fault_fixup(struct work_struct *work) /* User asked for enabler to be removed during fault */ if (test_bit(ENABLE_VAL_FREEING_BIT, ENABLE_BITOPS(enabler))) { - user_event_enabler_destroy(enabler, true); + user_event_enabler_destroy(enabler); goto out; } @@ -764,7 +789,7 @@ static void user_event_mm_destroy(struct user_event_mm *mm) struct user_event_enabler *enabler, *next; list_for_each_entry_safe(enabler, next, &mm->enablers, mm_enablers_link) - user_event_enabler_destroy(enabler, false); + user_event_enabler_destroy(enabler); mmdrop(mm->mm); kfree(mm); @@ -2645,7 +2670,7 @@ static long user_events_ioctl_unreg(unsigned long uarg) flags |= enabler->values & ENABLE_VAL_COMPAT_MASK; if (!test_bit(ENABLE_VAL_FAULTING_BIT, ENABLE_BITOPS(enabler))) - user_event_enabler_destroy(enabler, true); + user_event_enabler_destroy(enabler); /* Removed at least one */ ret = 0; -- cgit v1.2.3 From 05074bb90af94f2acbbc8f8ffaa507f914c273c8 Mon Sep 17 00:00:00 2001 From: Yu Peng Date: Wed, 3 Jun 2026 14:25:33 +0800 Subject: tracing/synthetic: Free type string on error path parse_synth_field() builds a "__data_loc ..." type string before assigning it to field->type. If the seq_buf check fails, the common cleanup cannot free the temporary string. Free it before leaving. Link: https://patch.msgid.link/20260603062533.1096320-2-pengyu@kylinos.cn Signed-off-by: Yu Peng Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_synth.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c index cdd5b9332835..dc15658a887c 100644 --- a/kernel/trace/trace_events_synth.c +++ b/kernel/trace/trace_events_synth.c @@ -839,8 +839,10 @@ static struct synth_field *parse_synth_field(int argc, char **argv, seq_buf_puts(&s, "__data_loc "); seq_buf_puts(&s, field->type); - if (WARN_ON_ONCE(!seq_buf_buffer_left(&s))) + if (WARN_ON_ONCE(!seq_buf_buffer_left(&s))) { + kfree(type); goto free; + } s.buffer[s.len] = '\0'; kfree(field->type); -- cgit v1.2.3 From ec082d0b978b5fb4c11205ccce63587ac94c74e1 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Thu, 9 Jul 2026 17:00:15 +0100 Subject: tracing/remotes: Fix leak in trace_remote_alloc_buffer() error path If page allocation fails in trace_remote_alloc_buffer(), desc->nr_cpus is not yet incremented for the current CPU. As a consequence, on error, half-allocated rb_desc will not be freed in trace_remote_free_buffer(). Increment desc->nr_cpus as soon as the first allocation for the current CPU has succeeded. Link: https://patch.msgid.link/20260709160017.1729517-2-vdonnefort@google.com Fixes: 96e43537af54 ("tracing: Introduce trace remotes") Reported-by: Sashiko Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace_remote.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index 2a6cc000ec98..d48042239d58 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -1006,6 +1006,8 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, if (!rb_desc->meta_va) goto err; + desc->nr_cpus++; + for (id = 0; id < nr_pages; id++) { rb_desc->page_va[id] = (unsigned long)__get_free_page(GFP_KERNEL); if (!rb_desc->page_va[id]) @@ -1013,7 +1015,6 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, rb_desc->nr_page_va++; } - desc->nr_cpus++; desc->struct_len += offsetof(struct ring_buffer_desc, page_va); desc->struct_len += struct_size(rb_desc, page_va, rb_desc->nr_page_va); rb_desc = __next_ring_buffer_desc(rb_desc); -- cgit v1.2.3 From d471d4f86e8689ad15309e49da8e5e4e9bb9ce87 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Thu, 9 Jul 2026 17:00:16 +0100 Subject: tracing/remotes: Fix struct_len in trace_remote_alloc_buffer() Pre-calculate desc->struct_len up-front in trace_remote_alloc_buffer() with trace_buffer_desc_size() to fix double-counting. While at it, use the accessor __first_ring_buffer_desc(). Link: https://patch.msgid.link/20260709160017.1729517-3-vdonnefort@google.com Fixes: 96e43537af54 ("tracing: Introduce trace remotes") Reported-by: Sashiko Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace_remote.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index d48042239d58..0f6ef5c36d84 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -979,27 +979,22 @@ EXPORT_SYMBOL_GPL(trace_remote_free_buffer); int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, size_t buffer_size, const struct cpumask *cpumask) { + size_t min_desc_size = trace_buffer_desc_size(buffer_size, cpumask_weight(cpumask)); unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1; - void *desc_end = desc + desc_size; struct ring_buffer_desc *rb_desc; int cpu, ret = -ENOMEM; - if (desc_size < struct_size(desc, __data, 0)) + if (desc_size < min_desc_size) return -EINVAL; desc->nr_cpus = 0; - desc->struct_len = struct_size(desc, __data, 0); + desc->struct_len = min_desc_size; - rb_desc = (struct ring_buffer_desc *)&desc->__data[0]; + rb_desc = __first_ring_buffer_desc(desc); for_each_cpu(cpu, cpumask) { unsigned int id; - if ((void *)rb_desc + struct_size(rb_desc, page_va, nr_pages) > desc_end) { - ret = -EINVAL; - goto err; - } - rb_desc->cpu = cpu; rb_desc->nr_page_va = 0; rb_desc->meta_va = (unsigned long)__get_free_page(GFP_KERNEL); @@ -1015,8 +1010,6 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, rb_desc->nr_page_va++; } - desc->struct_len += offsetof(struct ring_buffer_desc, page_va); - desc->struct_len += struct_size(rb_desc, page_va, rb_desc->nr_page_va); rb_desc = __next_ring_buffer_desc(rb_desc); } -- cgit v1.2.3 From 601ddaceb861be7eb557278109966320a6f3478c Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Thu, 9 Jul 2026 17:00:17 +0100 Subject: ring-buffer: Allow sparse CPU masks in ring_buffer_desc() No user currently relies on sparse CPU masks, but the descriptor logic already supports them via linear fallback. Remove the arbitrary limitation. Link: https://patch.msgid.link/20260709160017.1729517-4-vdonnefort@google.com Fixes: 2e67fabd8b77 ("ring-buffer: Introduce ring-buffer remotes") Reported-by: Sashiko Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 12a83d9ffd0a..804ccae694d2 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -2330,10 +2330,7 @@ static struct ring_buffer_desc *ring_buffer_desc(struct trace_buffer_desc *trace size_t len; int i; - if (!trace_desc) - return NULL; - - if (cpu >= trace_desc->nr_cpus) + if (!trace_desc || !trace_desc->nr_cpus) return NULL; end = (struct ring_buffer_desc *)((void *)trace_desc + trace_desc->struct_len); -- cgit v1.2.3 From 438a8d6a82931f12305f80bc3e7ef067c2ac0551 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:40 +0900 Subject: tracing/probes: Support typecast for various probe events 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 Note: you can not use other variables like $stackN, %reg etc. That needs nesting support. 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 updates /README file to show struct typecast. Link: https://lore.kernel.org/all/178271362928.1176915.12235759508786922490.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) --- Documentation/trace/fprobetrace.rst | 3 +++ Documentation/trace/kprobetrace.rst | 4 ++++ kernel/trace/trace.c | 2 +- kernel/trace/trace_probe.c | 23 +++++++++++++++++------ kernel/trace/trace_probe.h | 5 +++++ 5 files changed, 30 insertions(+), 7 deletions(-) (limited to 'kernel/trace') 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 1146b83b711a..280a3dccd13f 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4322,7 +4322,7 @@ static const char readme_msg[] = #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API "\t $stack, $stack, $retval, $comm, $arg,\n" #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS - "\t [->field[->field|.field...]],\n" + "\t [(structname)][->field[->field|.field...]],\n" #endif #else "\t $stack, $stack, $retval, $comm,\n" diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 0908019aea12..e6cc9f3d6c8b 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -699,7 +699,7 @@ static int parse_btf_arg(char *varname, if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) { code->op = FETCH_OP_RETVAL; - /* Check whether the function return type is not void */ + /* Check whether the function return type is not void, even with typecast. */ if (query_btf_context(ctx) == 0) { if (ctx->proto->type == 0) { trace_probe_log_err(ctx->offset, NO_RETVAL); @@ -708,6 +708,13 @@ static int parse_btf_arg(char *varname, tid = ctx->proto->type; goto found; } + /* + * Even if we can not find appropriate BTF info, we can still access + * the field via typecast. + */ + if (ctx->struct_btf) + goto found; + if (field) { trace_probe_log_err(ctx->offset + field - varname, NO_BTF_ENTRY); @@ -752,7 +759,10 @@ static int parse_btf_arg(char *varname, return -ENOENT; found: - type = btf_type_skip_modifiers(ctx->btf, tid, NULL); + if (ctx->struct_btf) + type = ctx->last_struct; + else + type = btf_type_skip_modifiers(ctx->btf, tid, NULL); found_type: if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); @@ -829,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 e36cfe39e9a8..aa72e2ffdd93 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -429,6 +429,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 */ -- cgit v1.2.3 From 60432df556d5876d0c6451a9a4c5c01c513f19e1 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:41 +0900 Subject: tracing/probes: Support nested typecast 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 Link: https://lore.kernel.org/all/178271363855.1176915.16793301788257446529.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) --- 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 | 83 +++++++++++++++++++++++++++++++++---- kernel/trace/trace_probe.h | 7 +++- 6 files changed, 88 insertions(+), 9 deletions(-) (limited to 'kernel/trace') 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 280a3dccd13f..e56ee034c486 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4323,6 +4323,7 @@ static const char readme_msg[] = "\t $stack, $stack, $retval, $comm, $arg,\n" #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS "\t [(structname)][->field[->field|.field...]],\n" + "\t [(structname)](fetcharg)->field[->field|.field...],\n" #endif #else "\t $stack, $stack, $retval, $comm,\n" diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index e6cc9f3d6c8b..827ae04f6351 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; } @@ -1638,6 +1700,11 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size, ctx); if (ret < 0) goto fail; + /* nested_level must be 0 here, otherwise there is a bug. */ + if (WARN_ON_ONCE(ctx->nested_level)) { + ret = -EINVAL; + goto fail; + } /* Update storing type if BTF is available */ if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) && diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index aa72e2ffdd93..7d71925244e8 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -450,8 +450,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); @@ -587,7 +590,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 -- cgit v1.2.3 From 52c76ec18ebacdcf7e227c06f4215bc49e5d284c Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:41 +0900 Subject: tracing/probes: Type casting always involves nested calls This allows type casting to various fetchargs without parentheses by recursively calling parse_probe_arg on the target when type casting is used. For example, this allows the following expressions: - (STRUCT)%REG->FIELD - (STRUCT)$stackN->FIELD - (STRUCT)@SYM->FIELD Note that @SYM+/-OFFSET with typecast needs parentheses like: - (STRUCT)(@SYM-8)->FIELD Link: https://lore.kernel.org/all/178271364804.1176915.17600229399446264813.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 123 ++++++++++++++++++++++++++------------------- kernel/trace/trace_probe.h | 4 +- 2 files changed, 75 insertions(+), 52 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 827ae04f6351..1b97b125e9cb 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -684,19 +684,6 @@ static int parse_btf_arg(char *varname, return -EOPNOTSUPP; } - if (ctx->flags & TPARG_FL_TEVENT) { - ret = parse_trace_event(varname, code, ctx); - if (ret < 0) { - trace_probe_log_err(ctx->offset, BAD_ATTACH_ARG); - return ret; - } - /* TEVENT is only here via a typecast */ - if (WARN_ON_ONCE(ctx->struct_btf == NULL)) - return -EINVAL; - type = ctx->last_struct; - goto found_type; - } - if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) { code->op = FETCH_OP_RETVAL; /* Check whether the function return type is not void, even with typecast. */ @@ -708,13 +695,6 @@ static int parse_btf_arg(char *varname, tid = ctx->proto->type; goto found; } - /* - * Even if we can not find appropriate BTF info, we can still access - * the field via typecast. - */ - if (ctx->struct_btf) - goto found; - if (field) { trace_probe_log_err(ctx->offset + field - varname, NO_BTF_ENTRY); @@ -759,11 +739,7 @@ static int parse_btf_arg(char *varname, return -ENOENT; found: - if (ctx->struct_btf) - type = ctx->last_struct; - else - type = btf_type_skip_modifiers(ctx->btf, tid, NULL); -found_type: + type = btf_type_skip_modifiers(ctx->btf, tid, NULL); if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL; @@ -860,7 +836,7 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, struct traceprobe_parse_context *ctx) { int orig_offset = ctx->offset; - bool nested = false; + char *close; char *tmp; int ret; @@ -871,6 +847,17 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, return -EOPNOTSUPP; } + /* + * Always consider the token after typecast as a nested call + * For example: (STRUCT)VAR->FIELD and (STRUCT)(VAR)->FIELD are same. + * VAR is solved in the nested call. + */ + ctx->nested_level++; + if (ctx->nested_level > TRACEPROBE_MAX_NESTED_LEVEL) { + trace_probe_log_err(ctx->offset, TOO_MANY_NESTED); + return -E2BIG; + } + tmp = strchr(arg, ')'); if (!tmp) { trace_probe_log_err(ctx->offset + strlen(arg), @@ -879,11 +866,10 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, } *tmp++ = '\0'; - /* Handle the nested structure like (STRUCT)(VAR->FIELD)->... */ + ctx->offset += tmp - arg; if (*tmp == '(') { - char *close = find_matched_close_paren(tmp); + close = find_matched_close_paren(tmp); - ctx->offset += tmp - arg; if (!close) { trace_probe_log_err(ctx->offset, DEREF_OPEN_BRACE); return -EINVAL; @@ -894,27 +880,66 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, 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; + /* Skip '(' */ + ctx->offset += 1; + tmp++; + } else if (*tmp == '+' || *tmp == '-') { + /* Dereference can have another field access inside it. */ + char *open = strchr(tmp + 1, '('); + + if (!open) { + trace_probe_log_err(ctx->offset, + DEREF_NEED_BRACE); + return -EINVAL; + } + close = find_matched_close_paren(open); + if (!close) { + trace_probe_log_err(ctx->offset + strlen(tmp), + DEREF_OPEN_BRACE); + return -EINVAL; + } + close++; + /* We expect a field access for typecast */ + if (close[0] != '-' || close[1] != '>') { + trace_probe_log_err(ctx->offset + close - tmp, + TYPECAST_REQ_FIELD); + return -EINVAL; + } + } else { + if (tmp[0] == '@') { + /* @sym+offset is not allowed without parenthesized */ + close = strpbrk(tmp, "+-"); + if (close && isdigit(close[1])) { + trace_probe_log_err(ctx->offset, + TYPECAST_SYM_OFFSET); + return -EINVAL; + } } - *close = '\0'; + /* Inner variable name */ + close = strchr(tmp, '-'); + if (!close || close[1] != '>') { + trace_probe_log_err(ctx->offset + strlen(tmp), + TYPECAST_REQ_FIELD); + return -EINVAL; + } + } + *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); + /* We need to parse the nested one */ + ret = parse_probe_arg(tmp, 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; - } + /* Let tmp point the field name. */ + if (close[1] == '-') + tmp = close + 3; /* Skip "->" after closing parenthesis */ + else + tmp = close + 2; /* Skip ">" after inner variable name */ + /* resolve the typecast struct name */ ret = query_btf_struct(arg + 1, ctx); if (ret < 0) { trace_probe_log_err(orig_offset + 1, NO_PTR_STRCT); @@ -922,11 +947,7 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, } 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); + ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx); return ret; } diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 7d71925244e8..f4fbe3010978 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -453,6 +453,7 @@ struct traceprobe_parse_context { int nested_level; }; +/* Each typecast consumes nested level. So the max number of typecast is 3. */ #define TRACEPROBE_MAX_NESTED_LEVEL 3 extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, @@ -592,7 +593,8 @@ 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_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses") #undef C #define C(a, b) TP_ERR_##a -- cgit v1.2.3 From f2183452402508ad68ea01eed18ae8eecfcd8f0a Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:41 +0900 Subject: tracing/probes: Support field specifier option for typecast 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: -0 [002] d.h1. 3778.087272: hrtimer_expire_entry: hrtimer=00000000d63db328 f unction=tick_nohz_handler now=3777450051040 -0 [002] d.h1. 3778.087281: tick_nohz_handler__entry: (tick_nohz_handler+0x4 /0x140) next_tick=3777450000000 Link: https://lore.kernel.org/all/178271365745.1176915.725923927180862257.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) --- 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 | 169 ++++++++++++++++++++++++------------ kernel/trace/trace_probe.h | 5 +- 6 files changed, 135 insertions(+), 64 deletions(-) (limited to 'kernel/trace') 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 e56ee034c486..5670c4b91dc0 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4322,8 +4322,8 @@ static const char readme_msg[] = #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API "\t $stack, $stack, $retval, $comm, $arg,\n" #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS - "\t [(structname)][->field[->field|.field...]],\n" - "\t [(structname)](fetcharg)->field[->field|.field...],\n" + "\t [(structname[,field])][->field[->field|.field...]],\n" + "\t [(structname[,field])](fetcharg)->field[->field|.field...],\n" #endif #else "\t $stack, $stack, $retval, $comm,\n" diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 1b97b125e9cb..fd006b415c68 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -568,6 +568,64 @@ 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; + + 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, NULL); + 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. @@ -577,15 +635,13 @@ 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; 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); @@ -599,60 +655,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, NULL); - 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; @@ -808,6 +829,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) { @@ -940,14 +1001,14 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, tmp = close + 2; /* Skip ">" after inner variable name */ /* resolve the typecast struct name */ - ret = query_btf_struct(arg + 1, ctx); - if (ret < 0) { - trace_probe_log_err(orig_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; ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx); + ctx->prefix_byteoffs = 0; return ret; } diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index f4fbe3010978..6a0859e00bf4 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -451,6 +451,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 */ }; /* Each typecast consumes nested level. So the max number of typecast is 3. */ @@ -594,7 +595,9 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, 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(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses") + C(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses"), \ + 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 -- cgit v1.2.3 From 738b78ee4bfa5e290f155be1e946bc2ae5660dfd Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:41 +0900 Subject: tracing/probes: Add $current variable support 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 Link: https://lore.kernel.org/all/178271366709.1176915.15320906169981578568.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) --- Documentation/trace/fprobetrace.rst | 1 + Documentation/trace/kprobetrace.rst | 1 + kernel/trace/trace.c | 4 ++-- kernel/trace/trace_probe.c | 37 ++++++++++++++++++++++++++++++++++++- kernel/trace/trace_probe.h | 1 + kernel/trace/trace_probe_tmpl.h | 3 +++ 6 files changed, 44 insertions(+), 3 deletions(-) (limited to 'kernel/trace') 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 5670c4b91dc0..2b0b4f9acb2e 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4320,13 +4320,13 @@ static const char readme_msg[] = "\t args: =fetcharg[:type]\n" "\t fetcharg: (%|$), @
, @[+|-],\n" #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API - "\t $stack, $stack, $retval, $comm, $arg,\n" + "\t $stack, $stack, $retval, $comm, $arg, $current\n" #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS "\t [(structname[,field])][->field[->field|.field...]],\n" "\t [(structname[,field])](fetcharg)->field[->field|.field...],\n" #endif #else - "\t $stack, $stack, $retval, $comm,\n" + "\t $stack, $stack, $retval, $comm, $current\n" #endif "\t +|-[u](), \\imm-value, \\\"imm-string\"\n" "\t kernel return probes support: $retval, $arg, $comm\n" diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index fd006b415c68..999dec84275d 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -692,7 +692,9 @@ static int parse_btf_arg(char *varname, int i, is_ptr, ret; u32 tid; - if (!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)) + /* Note: field is not separated at this point, so check prefix. */ + if (!str_has_prefix(varname, "$current") && + !ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)) return -EINVAL; is_ptr = split_next_field(varname, &field, ctx); @@ -705,6 +707,20 @@ static int parse_btf_arg(char *varname, return -EOPNOTSUPP; } + if (!strcmp(varname, "$current")) { + code->op = FETCH_OP_CURRENT; + /* If no typecast is specified for $current, use task_struct by default */ + ret = bpf_find_btf_id("task_struct", BTF_KIND_STRUCT, &ctx->struct_btf); + if (ret < 0) { + trace_probe_log_err(ctx->offset, NO_BTF_ENTRY); + return -ENOENT; + } + tid = (u32)ret; + type = ctx->last_struct = + btf_type_skip_modifiers(ctx->struct_btf, tid, NULL); + goto found_type; + } + if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) { code->op = FETCH_OP_RETVAL; /* Check whether the function return type is not void, even with typecast. */ @@ -761,6 +777,7 @@ static int parse_btf_arg(char *varname, found: type = btf_type_skip_modifiers(ctx->btf, tid, NULL); +found_type: if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL; @@ -1270,6 +1287,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 6a0859e00bf4..195096f5b8b1 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -92,6 +92,7 @@ typedef int (*print_type_func_t)(struct trace_seq *, void *, void *); FETCH_OP(RETVAL, none), /* Return value */ \ FETCH_OP(IMM, imm), /* Immediate: .immediate */ \ FETCH_OP(COMM, none), /* Current comm */ \ + FETCH_OP(CURRENT, none), /* Current task_struct address */\ FETCH_OP(ARG, param), /* Argument: .param = index */ \ FETCH_OP(FOFFS, imm), /* File offset: .immediate */ \ FETCH_OP(IMMSTR, string), /* Allocated string: .data */ \ diff --git a/kernel/trace/trace_probe_tmpl.h b/kernel/trace/trace_probe_tmpl.h index 51436f19083b..d0e9662cde00 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_IMMSTR: *val = (unsigned long)code->data; break; + case FETCH_OP_CURRENT: + *val = (unsigned long)current; + break; default: return -EILSEQ; } -- cgit v1.2.3 From 49b0a3f6052975d5c25721ec75dce557cc933d90 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Thu, 2 Jul 2026 21:12:42 +0900 Subject: tracing/probes: Add this_cpu_read() and this_cpu_ptr() dereference method to fetcharg 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. Link: https://lore.kernel.org/all/178271367680.1176915.4711734074448973989.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) --- Documentation/trace/fprobetrace.rst | 2 + Documentation/trace/kprobetrace.rst | 2 + kernel/trace/trace.c | 1 + kernel/trace/trace_probe.c | 152 +++++++++++++++++++++++++++--------- kernel/trace/trace_probe.h | 4 +- kernel/trace/trace_probe_tmpl.h | 22 ++++-- 6 files changed, 138 insertions(+), 45 deletions(-) (limited to 'kernel/trace') 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 2b0b4f9acb2e..c9e182d40059 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4329,6 +4329,7 @@ static const char readme_msg[] = "\t $stack, $stack, $retval, $comm, $current\n" #endif "\t +|-[u](), \\imm-value, \\\"imm-string\"\n" + "\t this_cpu_read(), this_cpu_ptr()\n" "\t kernel return probes support: $retval, $arg, $comm\n" "\t type: s8/16/32/64, u8/16/32/64, x8/16/32/64, char, string, symbol,\n" "\t b@/, ustring,\n" diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 999dec84275d..18c212122344 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -345,6 +345,109 @@ 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_IMMSTR) { + trace_probe_log_err(ctx->offset, COMM_CANT_DEREF); + return -EINVAL; + } + + /* + * this_cpu_ptr(@SYM) does not use SYM value, but use SYM address. + * So we overwrite the last FETCH_OP_DEREF with FETCH_OP_CPU_PTR. + */ + if (!(deref == FETCH_OP_CPU_PTR && *arg == '@')) { + 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) +{ + struct fetch_insn *code; + bool is_ptr = false; + int ret; + + /* + * This is only for kernel probes, excluding eprobe, because per-cpu + * pointer should not be recorded by events. + */ + if (!(ctx->flags & TPARG_FL_KERNEL) || + (ctx->flags & TPARG_FL_TEVENT)) { + trace_probe_log_err(ctx->offset, NOSUP_PERCPU); + return -EINVAL; + } + if (str_has_prefix(arg, THIS_CPU_PTR_PREFIX)) { + arg += THIS_CPU_PTR_LEN; + ctx->offset += THIS_CPU_PTR_LEN; + is_ptr = true; + } else if (str_has_prefix(arg, THIS_CPU_READ_PREFIX)) { + arg += THIS_CPU_READ_LEN; + ctx->offset += THIS_CPU_READ_LEN; + } else + return -EINVAL; + + ret = handle_dereference(arg, pcode, end, ctx, FETCH_OP_CPU_PTR, 0); + if (ret || is_ptr) + return ret; + + /* this_cpu_read(VAR) -> +0(this_cpu_ptr(VAR)) */ + code = *pcode; + code++; + if (code == end) { + trace_probe_log_err(ctx->offset, TOO_MANY_OPS); + return -EINVAL; + } + code->op = FETCH_OP_DEREF; + code->offset = 0; + *pcode = code; + return 0; +} + #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS static u32 btf_type_int(const struct btf_type *t) @@ -904,11 +1007,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) @@ -961,7 +1059,9 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, /* Skip '(' */ ctx->offset += 1; tmp++; - } else if (*tmp == '+' || *tmp == '-') { + } else if (*tmp == '+' || *tmp == '-' || + str_has_prefix(tmp, THIS_CPU_PTR_PREFIX) || + str_has_prefix(tmp, THIS_CPU_READ_PREFIX)) { /* Dereference can have another field access inside it. */ char *open = strchr(tmp + 1, '('); @@ -1481,36 +1581,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_IMMSTR) { - 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 */ @@ -1531,7 +1604,10 @@ 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] == '_') { + 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 or event field*/ if (ctx->flags & TPARG_FL_TEVENT) { ret = parse_trace_event(arg, *pcode, ctx); @@ -1548,8 +1624,8 @@ parse_probe_arg(char *arg, const struct fetch_type *type, 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 */ diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 195096f5b8b1..e6268a8dc378 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -101,6 +101,7 @@ typedef int (*print_type_func_t)(struct trace_seq *, void *, void *); /* Stage 2 (dereference) ops */ \ FETCH_OP(DEREF, offset), /* Dereference: .offset */ \ FETCH_OP(UDEREF, offset), /* User-space dereference: .offset */\ + FETCH_OP(CPU_PTR, none), /* Per-CPU pointer: .offset */ \ /* Stage 3 (store) ops */ \ FETCH_OP(ST_RAW, store), /* Raw value: .size */ \ FETCH_OP(ST_MEM, store), /* Memory: .offset, .size */ \ @@ -598,7 +599,8 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, C(TOO_MANY_NESTED, "Too many nested typecasts/dereferences"), \ C(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses"), \ C(TYPECAST_NOT_ALIGNED, "Typecast field option is not byte-aligned"), \ - C(TYPECAST_BAD_ARROW, "Typecast field option does not support -> operator"), + C(TYPECAST_BAD_ARROW, "Typecast field option does not support -> operator"), \ + C(NOSUP_PERCPU, "Per-cpu variable access is only for kernel probes"), #undef C #define C(a, b) TP_ERR_##a diff --git a/kernel/trace/trace_probe_tmpl.h b/kernel/trace/trace_probe_tmpl.h index d0e9662cde00..8db12f758fda 100644 --- a/kernel/trace/trace_probe_tmpl.h +++ b/kernel/trace/trace_probe_tmpl.h @@ -129,25 +129,35 @@ 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_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: -- cgit v1.2.3 From b20c8177092f3ff4804e0271e3fd338d34e09097 Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (The Capable Hub)" Date: Tue, 28 Apr 2026 10:30:29 +0200 Subject: tracing: fprobe: Remove __packed from generic __fprobe_header fp pointer and unsigned long have the same size on all relevant architectures that build Linux. Furthermore this struct is only used in architectures that do not set ARCH_DEFINE_ENCODE_FPROBE_HEADER which is set only for 64bit architectures (apart from LoongArch). Both fields are aligned on these architectures so the struct with __packed and without it are the same. Remove the __packed as it is unnecessary. [Masami: Removed Fixes tag because this is not fixing any problem.] Link: https://lore.kernel.org/all/20260428-topic-fprobe-packed-v7-1-v1-1-9abc9b866b4c@baylibre.com/ Signed-off-by: Markus Schneider-Pargmann (The Capable Hub) Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/fprobe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index f215990b9061..2727f8861fa3 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; +}; #define FPROBE_HEADER_SIZE_IN_LONG SIZE_IN_LONG(sizeof(struct __fprobe_header)) -- cgit v1.2.3 From 2590d67a1ce85a8d26980f4ff512ab7bcc1b8d89 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Tue, 14 Jul 2026 10:09:36 +0900 Subject: tracing/probes: Refactor parse_probe_vars() Decompose parse_probe_vars() by extracting retval, stack, current task struct, and function argument parsing logic into dedicated static helper functions (parse_probe_var_retval, parse_probe_var_stack, parse_probe_var_current, and parse_probe_var_arg). This simplifies parse_probe_vars() and improves its readability. Link: https://lore.kernel.org/all/178399137612.27810.14869178899276413372.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 237 ++++++++++++++++++++++++++------------------- 1 file changed, 140 insertions(+), 97 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 18c212122344..8af97e29f096 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1319,132 +1319,175 @@ NOKPROBE_SYMBOL(store_trace_entry_data) #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) -/* Parse $vars. @orig_arg points '$', which syncs to @ctx->offset */ -static int parse_probe_vars(char *orig_arg, const struct fetch_type *t, - struct fetch_insn **pcode, - struct fetch_insn *end, - struct traceprobe_parse_context *ctx) +static int parse_probe_var_retval(char *orig_arg, char *arg, + struct fetch_insn **pcode, + struct fetch_insn *end, + struct traceprobe_parse_context *ctx) { struct fetch_insn *code = *pcode; - int err = TP_ERR_BAD_VAR; - char *arg = orig_arg + 1; + + if (!(ctx->flags & TPARG_FL_RETURN)) { + trace_probe_log_err(ctx->offset, RETVAL_ON_PROBE); + return -EINVAL; + } + if (!(ctx->flags & TPARG_FL_KERNEL) || + !IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) { + code->op = FETCH_OP_RETVAL; + return 0; + } + return parse_btf_arg(orig_arg, pcode, end, ctx); +} + +static int parse_probe_var_stack(char *arg, int len, struct fetch_insn *code, + struct traceprobe_parse_context *ctx) +{ unsigned long param; - int ret = 0; - int len; + int ret; - if (ctx->flags & TPARG_FL_TEVENT) { - if (parse_trace_event(arg, code, ctx) < 0) { - /* 'comm' should be checked after field parsing. */ - if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) { - code->op = FETCH_OP_COMM; - return 0; - } - goto inval; - } + if (arg[len] == '\0') { + code->op = FETCH_OP_STACKP; return 0; } - if (str_has_prefix(arg, "retval")) { - if (!(ctx->flags & TPARG_FL_RETURN)) { - err = TP_ERR_RETVAL_ON_PROBE; - goto inval; + if (isdigit(arg[len])) { + ret = kstrtoul(arg + len, 10, ¶m); + if (ret) { + trace_probe_log_err(ctx->offset, BAD_VAR); + return ret; } - if (!(ctx->flags & TPARG_FL_KERNEL) || - !IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) { - code->op = FETCH_OP_RETVAL; - return 0; + + if ((ctx->flags & TPARG_FL_KERNEL) && + param > PARAM_MAX_STACK) { + trace_probe_log_err(ctx->offset, BAD_STACK_NUM); + return -EINVAL; } + code->op = FETCH_OP_STACK; + code->param = (unsigned int)param; + return 0; + } + + trace_probe_log_err(ctx->offset, BAD_VAR); + return -EINVAL; +} + +static int parse_probe_var_current(char *orig_arg, char *arg, + struct fetch_insn **pcode, + struct fetch_insn *end, + struct traceprobe_parse_context *ctx) +{ + struct fetch_insn *code = *pcode; + + /* $current is only supported by kernel probe. */ + if (!(ctx->flags & TPARG_FL_KERNEL)) { + trace_probe_log_err(ctx->offset, BAD_VAR); + return -EINVAL; + } + arg += strlen("current"); + if (*arg == '-' && IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) return parse_btf_arg(orig_arg, pcode, end, ctx); + + if (*arg != '\0') { + trace_probe_log_err(ctx->offset, BAD_VAR); + return -EINVAL; } - len = str_has_prefix(arg, "stack"); - if (len) { + code->op = FETCH_OP_CURRENT; + return 0; +} - if (arg[len] == '\0') { - code->op = FETCH_OP_STACKP; - return 0; - } +#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API +static int parse_probe_var_arg(char *arg, int len, struct fetch_insn *code, + struct traceprobe_parse_context *ctx) +{ + unsigned long param; + int ret; - if (isdigit(arg[len])) { - ret = kstrtoul(arg + len, 10, ¶m); - if (ret) - goto inval; + ret = kstrtoul(arg + len, 10, ¶m); + if (ret) { + trace_probe_log_err(ctx->offset, BAD_VAR); + return ret; + } - if ((ctx->flags & TPARG_FL_KERNEL) && - param > PARAM_MAX_STACK) { - err = TP_ERR_BAD_STACK_NUM; - goto inval; - } - code->op = FETCH_OP_STACK; - code->param = (unsigned int)param; - return 0; - } - goto inval; + if (!param || param > PARAM_MAX_STACK) { + trace_probe_log_err(ctx->offset, BAD_ARG_NUM); + return -EINVAL; } + param--; /* argN starts from 1, but internal arg[N] starts from 0 */ - if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) { - code->op = FETCH_OP_COMM; - return 0; + if (tparg_is_function_entry(ctx->flags)) { + code->op = FETCH_OP_ARG; + code->param = (unsigned int)param; + /* + * The tracepoint probe will probe a stub function, and the + * first parameter of the stub is a dummy and should be ignored. + */ + if (ctx->flags & TPARG_FL_TPOINT) + code->param++; + } else if (tparg_is_function_return(ctx->flags)) { + /* function entry argument access from return probe */ + ret = __store_entry_arg(ctx->tp, param); + if (ret < 0) /* This error should be an internal error */ + return ret; + + code->op = FETCH_OP_EDATA; + code->offset = ret; + } else { + trace_probe_log_err(ctx->offset, NOFENTRY_ARGS); + return -EINVAL; } + return 0; +} +#else +static int parse_probe_var_arg(char *arg, int len, struct fetch_insn *code, + struct traceprobe_parse_context *ctx) +{ + trace_probe_log_err(ctx->offset, BAD_VAR); + return -EINVAL; +} +#endif - /* $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); +/* Parse $vars. @orig_arg points '$', which syncs to @ctx->offset */ +static int parse_probe_vars(char *orig_arg, const struct fetch_type *t, + struct fetch_insn **pcode, + struct fetch_insn *end, + struct traceprobe_parse_context *ctx) +{ + struct fetch_insn *code = *pcode; + char *arg = orig_arg + 1; + int len, ret; - if (*arg != '\0') - goto inval; + if (ctx->flags & TPARG_FL_TEVENT) { + ret = parse_trace_event(arg, code, ctx); + if (!ret) + return 0; + } - code->op = FETCH_OP_CURRENT; + if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) { + code->op = FETCH_OP_COMM; return 0; } -#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API - len = str_has_prefix(arg, "arg"); - if (len) { - ret = kstrtoul(arg + len, 10, ¶m); - if (ret) - goto inval; + /* eprobe only support event fields or '$comm'. */ + if (ctx->flags & TPARG_FL_TEVENT) + goto inval; - if (!param || param > PARAM_MAX_STACK) { - err = TP_ERR_BAD_ARG_NUM; - goto inval; - } - param--; /* argN starts from 1, but internal arg[N] starts from 0 */ + if (str_has_prefix(arg, "retval")) + return parse_probe_var_retval(orig_arg, arg, pcode, end, ctx); - if (tparg_is_function_entry(ctx->flags)) { - code->op = FETCH_OP_ARG; - code->param = (unsigned int)param; - /* - * The tracepoint probe will probe a stub function, and the - * first parameter of the stub is a dummy and should be ignored. - */ - if (ctx->flags & TPARG_FL_TPOINT) - code->param++; - } else if (tparg_is_function_return(ctx->flags)) { - /* function entry argument access from return probe */ - ret = __store_entry_arg(ctx->tp, param); - if (ret < 0) /* This error should be an internal error */ - return ret; + len = str_has_prefix(arg, "stack"); + if (len) + return parse_probe_var_stack(arg, len, code, ctx); - code->op = FETCH_OP_EDATA; - code->offset = ret; - } else { - err = TP_ERR_NOFENTRY_ARGS; - goto inval; - } - return 0; - } -#endif + /* $current returns the address of the current task_struct. */ + if (str_has_prefix(arg, "current")) + return parse_probe_var_current(orig_arg, arg, pcode, end, ctx); + + len = str_has_prefix(arg, "arg"); + if (len) + return parse_probe_var_arg(arg, len, code, ctx); inval: - __trace_probe_log_err(ctx->offset, err); + trace_probe_log_err(ctx->offset, BAD_VAR); return -EINVAL; } -- cgit v1.2.3 From 7e79d7fbaf1fe30a341ae128ad88f4586efb94f1 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Tue, 14 Jul 2026 10:09:45 +0900 Subject: tracing/probes: Refactor parse_probe_arg() Decompose parse_probe_arg() by extracting register, memory/symbol, dereference, immediate, and default BTF/CPU parsing handlers into dedicated static helper functions (parse_probe_arg_register, parse_probe_arg_mem_symbol, parse_probe_arg_deref, parse_probe_arg_imm, and parse_probe_arg_default). This modularizes the recursive argument parser and improves readability. Link: https://lore.kernel.org/all/178399138581.27810.9471730417467607229.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 300 ++++++++++++++++++++++++++------------------- 1 file changed, 174 insertions(+), 126 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 8af97e29f096..eb9f7bd13c19 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1516,158 +1516,206 @@ static int __parse_imm_string(char *str, char **pbuf, int offs) return 0; } -/* Recursive argument parser */ -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 parse_probe_arg_register(char *arg, struct fetch_insn *code, + struct traceprobe_parse_context *ctx) +{ + int ret; + + if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) { + /* eprobe and fprobe do not handle registers */ + trace_probe_log_err(ctx->offset, BAD_VAR); + return -EINVAL; + } + ret = regs_query_register_offset(arg + 1); + if (ret >= 0) { + code->op = FETCH_OP_REG; + code->param = (unsigned int)ret; + return 0; + } + trace_probe_log_err(ctx->offset, BAD_REG_NAME); + return -EINVAL; +} + +static int parse_probe_arg_mem_symbol(char *arg, struct fetch_insn **pcode, + struct fetch_insn *end, + struct traceprobe_parse_context *ctx) { struct fetch_insn *code = *pcode; unsigned long param; + long offset = 0; + int ret; + + if (isdigit(arg[1])) { + ret = kstrtoul(arg + 1, 0, ¶m); + if (ret) { + trace_probe_log_err(ctx->offset, BAD_MEM_ADDR); + return ret; + } + /* load address */ + code->op = FETCH_OP_IMM; + code->immediate = param; + } else if (arg[1] == '+') { + /* Kernel probes do not support file offsets */ + if (ctx->flags & TPARG_FL_KERNEL) { + trace_probe_log_err(ctx->offset, FILE_ON_KPROBE); + return -EINVAL; + } + ret = kstrtol(arg + 2, 0, &offset); + if (ret) { + trace_probe_log_err(ctx->offset, BAD_FILE_OFFS); + return ret; + } + + code->op = FETCH_OP_FOFFS; + code->immediate = (unsigned long)offset; + offset = 0; + } else { + /* uprobes don't support symbols */ + if (!(ctx->flags & TPARG_FL_KERNEL)) { + trace_probe_log_err(ctx->offset, SYM_ON_UPROBE); + return -EINVAL; + } + /* Preserve symbol for updating */ + code->op = FETCH_NOP_SYMBOL; + code->data = kstrdup(arg + 1, GFP_KERNEL); + if (!code->data) + return -ENOMEM; + if (++code == end) { + trace_probe_log_err(ctx->offset, TOO_MANY_OPS); + return -EINVAL; + } + code->op = FETCH_OP_IMM; + code->immediate = 0; + } + /* These are fetching from memory */ + if (++code == end) { + trace_probe_log_err(ctx->offset, TOO_MANY_OPS); + return -EINVAL; + } + *pcode = code; + code->op = FETCH_OP_DEREF; + code->offset = offset; + return 0; +} + +static int parse_probe_arg_deref(char *arg, struct fetch_insn **pcode, + struct fetch_insn *end, + struct traceprobe_parse_context *ctx) +{ int deref = FETCH_OP_DEREF; long offset = 0; char *tmp; - int ret = 0; + int ret; - switch (arg[0]) { - case '$': - ret = parse_probe_vars(arg, type, pcode, end, ctx); - break; + if (arg[1] == 'u') { + deref = FETCH_OP_UDEREF; + arg[1] = arg[0]; + arg++; + } + if (arg[0] == '+') + arg++; /* Skip '+', because kstrtol() rejects it. */ + tmp = strchr(arg, '('); + if (!tmp) { + trace_probe_log_err(ctx->offset, DEREF_NEED_BRACE); + return -EINVAL; + } + *tmp = '\0'; + ret = kstrtol(arg, 0, &offset); + if (ret) { + trace_probe_log_err(ctx->offset, BAD_DEREF_OFFS); + return ret; + } + ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0); + arg = tmp + 1; + return handle_dereference(arg, pcode, end, ctx, deref, offset); +} - case '%': /* named register */ - if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) { - /* eprobe and fprobe do not handle registers */ - trace_probe_log_err(ctx->offset, BAD_VAR); - break; +static int parse_probe_arg_imm(char *arg, struct fetch_insn *code, + struct traceprobe_parse_context *ctx) +{ + char *tmp; + int ret; + + if (arg[1] == '"') { /* Immediate string */ + ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2); + if (ret) + return ret; + code->op = FETCH_OP_IMMSTR; + code->data = tmp; + } else { + ret = str_to_immediate(arg + 1, &code->immediate); + if (ret) { + trace_probe_log_err(ctx->offset + 1, BAD_IMM); + return ret; } - ret = regs_query_register_offset(arg + 1); - if (ret >= 0) { - code->op = FETCH_OP_REG; - code->param = (unsigned int)ret; - ret = 0; - } else - trace_probe_log_err(ctx->offset, BAD_REG_NAME); - break; + code->op = FETCH_OP_IMM; + } + return 0; +} - case '@': /* memory, file-offset or symbol */ - if (isdigit(arg[1])) { - ret = kstrtoul(arg + 1, 0, ¶m); - if (ret) { - trace_probe_log_err(ctx->offset, BAD_MEM_ADDR); - break; - } - /* load address */ - code->op = FETCH_OP_IMM; - code->immediate = param; - } else if (arg[1] == '+') { - /* Kernel probes do not support file offsets */ - if (ctx->flags & TPARG_FL_KERNEL) { - trace_probe_log_err(ctx->offset, FILE_ON_KPROBE); - return -EINVAL; - } - ret = kstrtol(arg + 2, 0, &offset); - if (ret) { - trace_probe_log_err(ctx->offset, BAD_FILE_OFFS); - break; - } +static int parse_probe_arg_default(char *arg, struct fetch_insn **pcode, + struct fetch_insn *end, + struct traceprobe_parse_context *ctx) +{ + int ret; - code->op = FETCH_OP_FOFFS; - code->immediate = (unsigned long)offset; // imm64? - offset = 0; - } else { - /* uprobes don't support symbols */ - if (!(ctx->flags & TPARG_FL_KERNEL)) { - trace_probe_log_err(ctx->offset, SYM_ON_UPROBE); - return -EINVAL; - } - /* Preserve symbol for updating */ - code->op = FETCH_NOP_SYMBOL; - code->data = kstrdup(arg + 1, GFP_KERNEL); - if (!code->data) - return -ENOMEM; - if (++code == end) { - trace_probe_log_err(ctx->offset, TOO_MANY_OPS); + if (str_has_prefix(arg, THIS_CPU_PTR_PREFIX) || + str_has_prefix(arg, THIS_CPU_READ_PREFIX)) { + return parse_this_cpu(arg, pcode, end, ctx); + } + + if (isalpha(arg[0]) || arg[0] == '_') { + /* BTF variable or event field */ + if (ctx->flags & TPARG_FL_TEVENT) { + ret = parse_trace_event(arg, *pcode, ctx); + if (ret < 0) { + trace_probe_log_err(ctx->offset, NO_EVENT_FIELD); return -EINVAL; } - code->op = FETCH_OP_IMM; - code->immediate = 0; + return 0; } - /* These are fetching from memory */ - if (++code == end) { - trace_probe_log_err(ctx->offset, TOO_MANY_OPS); + if (!tparg_is_function_entry(ctx->flags) && + !tparg_is_function_return(ctx->flags)) { + trace_probe_log_err(ctx->offset, NOSUP_BTFARG); return -EINVAL; } - *pcode = code; - code->op = FETCH_OP_DEREF; - code->offset = offset; - break; + return parse_btf_arg(arg, pcode, end, ctx); + } + return 0; +} + +/* Recursive argument parser */ +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) +{ + struct fetch_insn *code = *pcode; + int ret; + + switch (arg[0]) { + case '$': + ret = parse_probe_vars(arg, type, pcode, end, ctx); + break; + case '%': /* named register */ + ret = parse_probe_arg_register(arg, code, ctx); + break; + case '@': /* memory, file-offset or symbol */ + ret = parse_probe_arg_mem_symbol(arg, pcode, end, ctx); + break; case '+': /* deref memory */ case '-': - if (arg[1] == 'u') { - deref = FETCH_OP_UDEREF; - arg[1] = arg[0]; - arg++; - } - if (arg[0] == '+') - arg++; /* Skip '+', because kstrtol() rejects it. */ - tmp = strchr(arg, '('); - if (!tmp) { - trace_probe_log_err(ctx->offset, DEREF_NEED_BRACE); - return -EINVAL; - } - *tmp = '\0'; - ret = kstrtol(arg, 0, &offset); - if (ret) { - trace_probe_log_err(ctx->offset, BAD_DEREF_OFFS); - break; - } - ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0); - arg = tmp + 1; - ret = handle_dereference(arg, pcode, end, ctx, deref, offset); - if (ret < 0) - return ret; + ret = parse_probe_arg_deref(arg, pcode, end, ctx); break; case '\\': /* Immediate value */ - if (arg[1] == '"') { /* Immediate string */ - ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2); - if (ret) - break; - code->op = FETCH_OP_IMMSTR; - code->data = tmp; - } else { - ret = str_to_immediate(arg + 1, &code->immediate); - if (ret) - trace_probe_log_err(ctx->offset + 1, BAD_IMM); - else - code->op = FETCH_OP_IMM; - } + ret = parse_probe_arg_imm(arg, code, ctx); break; case '(': ret = handle_typecast(arg, pcode, end, ctx); break; default: - 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 or event field*/ - if (ctx->flags & TPARG_FL_TEVENT) { - ret = parse_trace_event(arg, *pcode, ctx); - if (ret < 0) { - trace_probe_log_err(ctx->offset, - NO_EVENT_FIELD); - return -EINVAL; - } - break; - } - 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); - } + ret = parse_probe_arg_default(arg, pcode, end, ctx); break; } if (!ret && code->op == FETCH_OP_NOP) { -- cgit v1.2.3 From 0b420da72e45bc71fc2a8c9b230cd37585452188 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Tue, 14 Jul 2026 10:09:55 +0900 Subject: tracing/probes: Sort ERRORS list in trace_probe.h alphabetically Sort the C-macro ERRORS list alphabetically in trace_probe.h to make it easier to find and maintain error entries. Link: https://lore.kernel.org/all/178399139516.27810.14930549550788208142.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.h | 160 ++++++++++++++++++++++----------------------- 1 file changed, 80 insertions(+), 80 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index e6268a8dc378..e64e323244a5 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -512,95 +512,95 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, #undef ERRORS #define ERRORS \ - C(FILE_NOT_FOUND, "Failed to find the given file"), \ - C(NO_REGULAR_FILE, "Not a regular file"), \ - C(BAD_REFCNT, "Invalid reference counter offset"), \ - C(REFCNT_OPEN_BRACE, "Reference counter brace is not closed"), \ - C(BAD_REFCNT_SUFFIX, "Reference counter has wrong suffix"), \ - C(BAD_UPROBE_OFFS, "Invalid uprobe offset"), \ - C(BAD_MAXACT_TYPE, "Maxactive is only for function exit"), \ - C(BAD_MAXACT, "Invalid maxactive number"), \ - C(MAXACT_TOO_BIG, "Maxactive is too big"), \ - C(BAD_PROBE_ADDR, "Invalid probed address or symbol"), \ - C(NON_UNIQ_SYMBOL, "The symbol is not unique"), \ - C(BAD_RETPROBE, "Retprobe address must be an function entry"), \ - C(NO_TRACEPOINT, "Tracepoint is not found"), \ - C(BAD_TP_NAME, "Invalid character in tracepoint name"),\ - C(BAD_ADDR_SUFFIX, "Invalid probed address suffix"), \ - C(NO_GROUP_NAME, "Group name is not specified"), \ - C(GROUP_TOO_LONG, "Group name is too long"), \ - C(BAD_GROUP_NAME, "Group name must follow the same rules as C identifiers"), \ - C(NO_EVENT_NAME, "Event name is not specified"), \ - C(EVENT_TOO_LONG, "Event name is too long"), \ - C(BAD_EVENT_NAME, "Event name must follow the same rules as C identifiers"), \ - C(EVENT_EXIST, "Given group/event name is already used by another event"), \ - C(RETVAL_ON_PROBE, "$retval is not available on probe"), \ - C(NO_RETVAL, "This function returns 'void' type"), \ - C(BAD_STACK_NUM, "Invalid stack number"), \ + C(ARGIDX_2BIG, "$argN index is too big"), \ + C(ARGS_2LONG, "$arg* failed because the argument list is too long"), \ + C(ARG_NAME_TOO_LONG, "Argument name is too long"), \ + C(ARG_TOO_LONG, "Argument expression is too long"), \ + C(ARRAY_NO_CLOSE, "Array is not closed"), \ + C(ARRAY_TOO_BIG, "Array number is too big"), \ + C(BAD_ADDR_SUFFIX, "Invalid probed address suffix"), \ + C(BAD_ARG_NAME, "Argument name must follow the same rules as C identifiers"), \ C(BAD_ARG_NUM, "Invalid argument number"), \ - C(BAD_VAR, "Invalid $-variable specified"), \ - C(BAD_REG_NAME, "Invalid register name"), \ - C(BAD_MEM_ADDR, "Invalid memory address"), \ - C(BAD_IMM, "Invalid immediate value"), \ - C(IMMSTR_NO_CLOSE, "String is not closed with '\"'"), \ - C(FILE_ON_KPROBE, "File offset is not available for kernel probes"), \ - C(BAD_FILE_OFFS, "Invalid file offset value"), \ - C(SYM_ON_UPROBE, "Symbol is not available with uprobe"), \ - C(TOO_MANY_OPS, "Dereference is too much nested"), \ - C(DEREF_NEED_BRACE, "Dereference needs a brace"), \ + C(BAD_ARRAY_NUM, "Invalid array size"), \ + C(BAD_ARRAY_SUFFIX, "Array has wrong suffix"), \ + C(BAD_ATTACH_ARG, "Attached event does not have this field"), \ + C(BAD_ATTACH_EVENT, "Attached event does not exist"), \ + C(BAD_BITFIELD, "Invalid bitfield"), \ + C(BAD_BTF_TID, "Failed to get BTF type info."), \ C(BAD_DEREF_OFFS, "Invalid dereference offset"), \ - C(DEREF_OPEN_BRACE, "Dereference brace is not closed"), \ - C(COMM_CANT_DEREF, "$comm can not be dereferenced"), \ + C(BAD_EVENT_NAME, "Event name must follow the same rules as C identifiers"), \ C(BAD_FETCH_ARG, "Invalid fetch argument"), \ - C(ARRAY_NO_CLOSE, "Array is not closed"), \ - C(BAD_ARRAY_SUFFIX, "Array has wrong suffix"), \ - C(BAD_ARRAY_NUM, "Invalid array size"), \ - C(ARRAY_TOO_BIG, "Array number is too big"), \ - C(BAD_TYPE, "Unknown type is specified"), \ - C(BAD_STRING, "String accepts only memory argument"), \ + C(BAD_FILE_OFFS, "Invalid file offset value"), \ + C(BAD_GROUP_NAME, "Group name must follow the same rules as C identifiers"), \ + C(BAD_HYPHEN, "Failed to parse single hyphen. Forgot '>'?"), \ + C(BAD_IMM, "Invalid immediate value"), \ + C(BAD_INSN_BNDRY, "Probe point is not an instruction boundary"), \ + C(BAD_MAXACT, "Invalid maxactive number"), \ + C(BAD_MAXACT_TYPE, "Maxactive is only for function exit"), \ + C(BAD_MEM_ADDR, "Invalid memory address"), \ + C(BAD_PROBE_ADDR, "Invalid probed address or symbol"), \ + C(BAD_REFCNT, "Invalid reference counter offset"), \ + C(BAD_REFCNT_SUFFIX, "Reference counter has wrong suffix"), \ + C(BAD_REG_NAME, "Invalid register name"), \ + C(BAD_RETPROBE, "Retprobe address must be an function entry"), \ + C(BAD_STACK_NUM, "Invalid stack number"), \ + C(BAD_STRING, "String accepts only memory argument"), \ C(BAD_SYMSTRING, "Symbol String doesn't accept data/userdata"), \ - C(BAD_BITFIELD, "Invalid bitfield"), \ - C(ARG_NAME_TOO_LONG, "Argument name is too long"), \ - C(NO_ARG_NAME, "Argument name is not specified"), \ - C(BAD_ARG_NAME, "Argument name must follow the same rules as C identifiers"), \ - C(USED_ARG_NAME, "This argument name is already used"), \ - C(ARG_TOO_LONG, "Argument expression is too long"), \ + C(BAD_TP_NAME, "Invalid character in tracepoint name"), \ + C(BAD_TYPE, "Unknown type is specified"), \ + C(BAD_TYPE4STR, "This type does not fit for string."), \ + C(BAD_UPROBE_OFFS, "Invalid uprobe offset"), \ + C(BAD_VAR, "Invalid $-variable specified"), \ + C(BAD_VAR_ARGS, "$arg* must be an independent parameter without name etc."), \ + C(COMM_CANT_DEREF, "$comm can not be dereferenced"), \ + C(DEREF_NEED_BRACE, "Dereference needs a brace"), \ + C(DEREF_OPEN_BRACE, "Dereference brace is not closed"), \ + C(DIFF_ARG_TYPE, "Argument type or name is different from existing probe"), \ + C(DIFF_PROBE_TYPE, "Probe type is different from existing probe"), \ + C(DOUBLE_ARGS, "$arg* can be used only once in the parameters"), \ + C(EVENT_EXIST, "Given group/event name is already used by another event"), \ + C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \ + C(EVENT_TOO_LONG, "Event name is too long"), \ + C(FAIL_REG_PROBE, "Failed to register probe event"), \ + C(FILE_NOT_FOUND, "Failed to find the given file"), \ + C(FILE_ON_KPROBE, "File offset is not available for kernel probes"), \ + C(GROUP_TOO_LONG, "Group name is too long"), \ + C(IMMSTR_NO_CLOSE, "String is not closed with '\"'"), \ + C(MAXACT_TOO_BIG, "Maxactive is too big"), \ + C(NEED_STRING_TYPE, "$comm and immediate-string only accepts string type"), \ + C(NOFENTRY_ARGS, "$arg* can be used only on function entry or exit"), \ + C(NON_UNIQ_SYMBOL, "The symbol is not unique"), \ + C(NOSUP_BTFARG, "BTF is not available or not supported"), \ + C(NOSUP_DAT_ARG, "Non pointer structure/union argument is not supported."), \ + C(NOSUP_PERCPU, "Per-cpu variable access is only for kernel probes"), \ C(NO_ARG_BODY, "No argument expression"), \ - C(BAD_INSN_BNDRY, "Probe point is not an instruction boundary"),\ - C(FAIL_REG_PROBE, "Failed to register probe event"),\ - C(DIFF_PROBE_TYPE, "Probe type is different from existing probe"),\ - C(DIFF_ARG_TYPE, "Argument type or name is different from existing probe"),\ - C(SAME_PROBE, "There is already the exact same probe event"),\ - C(NO_EVENT_INFO, "This requires both group and event name to attach"),\ - C(BAD_ATTACH_EVENT, "Attached event does not exist"),\ - C(BAD_ATTACH_ARG, "Attached event does not have this field"),\ + C(NO_ARG_NAME, "Argument name is not specified"), \ + C(NO_BTFARG, "This variable is not found at this probe point"), \ + C(NO_BTF_ENTRY, "No BTF entry for this probe point"), \ + C(NO_BTF_FIELD, "This field is not found."), \ C(NO_EP_FILTER, "No filter rule after 'if'"), \ - C(NOSUP_BTFARG, "BTF is not available or not supported"), \ - C(NO_BTFARG, "This variable is not found at this probe point"),\ - C(NO_BTF_ENTRY, "No BTF entry for this probe point"), \ - C(BAD_VAR_ARGS, "$arg* must be an independent parameter without name etc."),\ - C(NOFENTRY_ARGS, "$arg* can be used only on function entry or exit"), \ - C(DOUBLE_ARGS, "$arg* can be used only once in the parameters"), \ - C(ARGS_2LONG, "$arg* failed because the argument list is too long"), \ - C(ARGIDX_2BIG, "$argN index is too big"), \ + C(NO_EVENT_FIELD, "This event field is not found."), \ + C(NO_EVENT_INFO, "This requires both group and event name to attach"), \ + C(NO_EVENT_NAME, "Event name is not specified"), \ + C(NO_GROUP_NAME, "Group name is not specified"), \ C(NO_PTR_STRCT, "This is not a pointer to union/structure."), \ - C(NOSUP_DAT_ARG, "Non pointer structure/union argument is not supported."),\ - C(BAD_HYPHEN, "Failed to parse single hyphen. Forgot '>'?"), \ - C(NO_EVENT_FIELD, "This event field is not found."), \ - C(NO_BTF_FIELD, "This field is not found."), \ - C(BAD_BTF_TID, "Failed to get BTF type info."),\ - C(BAD_TYPE4STR, "This type does not fit for string."),\ - C(NEED_STRING_TYPE, "$comm and immediate-string only accepts string type"),\ - C(TOO_MANY_ARGS, "Too many arguments are specified"), \ + C(NO_REGULAR_FILE, "Not a regular file"), \ + C(NO_RETVAL, "This function returns 'void' type"), \ + C(NO_TRACEPOINT, "Tracepoint is not found"), \ + C(REFCNT_OPEN_BRACE, "Reference counter brace is not closed"), \ + C(RETVAL_ON_PROBE, "$retval is not available on probe"), \ + C(SAME_PROBE, "There is already the exact same probe event"), \ + C(SYM_ON_UPROBE, "Symbol is not available with uprobe"), \ + 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(TOO_MANY_NESTED, "Too many nested typecasts/dereferences"), \ + C(TOO_MANY_OPS, "Dereference is too much nested"), \ + C(TYPECAST_BAD_ARROW, "Typecast field option does not support -> operator"), \ + C(TYPECAST_NOT_ALIGNED, "Typecast field option is not byte-aligned"), \ + 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(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses"), \ - C(TYPECAST_NOT_ALIGNED, "Typecast field option is not byte-aligned"), \ - C(TYPECAST_BAD_ARROW, "Typecast field option does not support -> operator"), \ - C(NOSUP_PERCPU, "Per-cpu variable access is only for kernel probes"), + C(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses"), \ + C(USED_ARG_NAME, "This argument name is already used"), #undef C #define C(a, b) TP_ERR_##a -- cgit v1.2.3 From 47e4ec68716624642f7108535960835bae7eff13 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Tue, 14 Jul 2026 10:10:04 +0900 Subject: tracing/probes: Extend max length of argument string To support BTF argument parsing (such as accessing fields within nested structures via typecasting), the maximum argument string length needs to be extended. Extend MAX_ARGSTR_LEN from 63 to 255. Since MAX_ARGSTR_LEN was previously reused to format command heads in trace_*probe_match_command_head() functions, introduce a dedicated MAX_COMMON_HEAD_LEN (63) macro for matching command heads and switch these functions to use the new macro. Link: https://lore.kernel.org/all/178399140457.27810.11387684872148824707.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_fprobe.c | 2 +- kernel/trace/trace_kprobe.c | 2 +- kernel/trace/trace_probe.h | 3 ++- kernel/trace/trace_uprobe.c | 2 +- tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc | 2 +- tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc | 2 +- tools/testing/selftests/ftrace/test.d/kprobe/kprobe_syntax_errors.tc | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c index 536781cd4c47..5638a90e61cc 100644 --- a/kernel/trace/trace_fprobe.c +++ b/kernel/trace/trace_fprobe.c @@ -238,7 +238,7 @@ static bool trace_fprobe_is_busy(struct dyn_event *ev) static bool trace_fprobe_match_command_head(struct trace_fprobe *tf, int argc, const char **argv) { - char buf[MAX_ARGSTR_LEN + 1]; + char buf[MAX_COMMON_HEAD_LEN + 1]; if (!argc) return true; diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c index cfa807d8e760..cc24e992732c 100644 --- a/kernel/trace/trace_kprobe.c +++ b/kernel/trace/trace_kprobe.c @@ -149,7 +149,7 @@ static bool trace_kprobe_is_busy(struct dyn_event *ev) static bool trace_kprobe_match_command_head(struct trace_kprobe *tk, int argc, const char **argv) { - char buf[MAX_ARGSTR_LEN + 1]; + char buf[MAX_COMMON_HEAD_LEN + 1]; if (!argc) return true; diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index e64e323244a5..e6d427910c4f 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -32,7 +32,8 @@ #include "trace_output.h" #define MAX_TRACE_ARGS 128 -#define MAX_ARGSTR_LEN 63 +#define MAX_ARGSTR_LEN 255 +#define MAX_COMMON_HEAD_LEN 63 #define MAX_ARRAY_LEN 64 #define MAX_ARG_NAME_LEN 32 #define MAX_BTF_ARGS_LEN 128 diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c index b2e264a4b96c..67bd8fd91e3e 100644 --- a/kernel/trace/trace_uprobe.c +++ b/kernel/trace/trace_uprobe.c @@ -281,7 +281,7 @@ static bool trace_uprobe_is_busy(struct dyn_event *ev) static bool trace_uprobe_match_command_head(struct trace_uprobe *tu, int argc, const char **argv) { - char buf[MAX_ARGSTR_LEN + 1]; + char buf[MAX_COMMON_HEAD_LEN + 1]; int len; if (!argc) 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 e9d7e6919c7f..984ab94df213 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 @@ -75,7 +75,7 @@ check_error 'f vfs_read ^arg123456789012345678901234567890=@11' # ARG_NAME_TOO_L check_error 'f vfs_read ^=@11' # NO_ARG_NAME check_error 'f vfs_read ^var.1=@11' # BAD_ARG_NAME check_error 'f vfs_read var1=@11 ^var1=@12' # USED_ARG_NAME -check_error 'f vfs_read ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))' # ARG_TOO_LONG +check_error 'f vfs_read ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))))))))))))))))))))))' # ARG_TOO_LONG check_error 'f vfs_read arg1=^' # NO_ARG_BODY diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc index ffe8ffef4027..2d0905b2c8b7 100644 --- a/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc +++ b/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc @@ -61,7 +61,7 @@ check_error 't kfree ^arg123456789012345678901234567890=@11' # ARG_NAME_TOO_LOG check_error 't kfree ^=@11' # NO_ARG_NAME check_error 't kfree ^var.1=@11' # BAD_ARG_NAME check_error 't kfree var1=@11 ^var1=@12' # USED_ARG_NAME -check_error 't kfree ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))' # ARG_TOO_LONG +check_error 't kfree ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))))))))))))))))))))))' # ARG_TOO_LONG check_error 't kfree arg1=^' # NO_ARG_BODY 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 21ce8414459f..d28f63b7e8a9 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 @@ -71,7 +71,7 @@ check_error 'p vfs_read ^arg123456789012345678901234567890=@11' # ARG_NAME_TOO_L check_error 'p vfs_read ^=@11' # NO_ARG_NAME check_error 'p vfs_read ^var.1=@11' # BAD_ARG_NAME check_error 'p vfs_read var1=@11 ^var1=@12' # USED_ARG_NAME -check_error 'p vfs_read ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))' # ARG_TOO_LONG +check_error 'p vfs_read ^+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(+1234567(@1234))))))))))))))))))))))))))' # ARG_TOO_LONG check_error 'p vfs_read arg1=^' # NO_ARG_BODY # instruction boundary check is valid on x86 (at this moment) -- cgit v1.2.3 From 1a0ffe10bfb91efc730f2d34c6875c1084b0a462 Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Tue, 14 Jul 2026 10:10:14 +0900 Subject: tracing/probes: Eliminate recursion in parse_probe_arg() To avoid potential stack overflows on limited kernel stacks, convert parse_probe_arg() from a recursive function into a loop-based implementation with a simple local state stack. Since recursion is eliminated using a loop with a fixed-size stack in the context, this restricts the dereference nesting depth. The maximum nesting depth of dereferences is now restricted to the same limit as typecasts (TRACEPROBE_MAX_NESTED_LEVEL, which is 8) and reports the same TOO_MANY_NESTED error. Update ftrace selftests to reflect this restriction and simplify the checks. Note that this change slightly alters the behavior of nested dereferencing in fetcharg. Previously, dereferencing without BTF allowed for up to 14 levels of nesting, whereas dereferencing with BTF was limited to 3 levels. With this change, the nesting depth is now limited to 8 levels in both cases. Link: https://lore.kernel.org/all/178399141396.27810.5390060618628718661.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 338 +++++++++++++-------- kernel/trace/trace_probe.h | 33 +- .../ftrace/test.d/dynevent/fprobe_syntax_errors.tc | 4 +- .../ftrace/test.d/dynevent/tprobe_syntax_errors.tc | 2 +- .../ftrace/test.d/kprobe/kprobe_syntax_errors.tc | 4 +- 5 files changed, 242 insertions(+), 139 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index eb9f7bd13c19..7568f5e68de7 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -356,61 +356,10 @@ 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) +static int parse_this_cpu(char *arg, struct traceprobe_parse_context *ctx) { - const struct fetch_type *type = find_fetch_type(NULL, ctx->flags); - struct fetch_insn *code = *pcode; - int cur_offs = ctx->offset; + bool is_read = false; 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_IMMSTR) { - trace_probe_log_err(ctx->offset, COMM_CANT_DEREF); - return -EINVAL; - } - - /* - * this_cpu_ptr(@SYM) does not use SYM value, but use SYM address. - * So we overwrite the last FETCH_OP_DEREF with FETCH_OP_CPU_PTR. - */ - if (!(deref == FETCH_OP_CPU_PTR && *arg == '@')) { - 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) -{ - struct fetch_insn *code; - bool is_ptr = false; - int ret; /* * This is only for kernel probes, excluding eprobe, because per-cpu @@ -424,27 +373,29 @@ static int parse_this_cpu(char *arg, struct fetch_insn **pcode, if (str_has_prefix(arg, THIS_CPU_PTR_PREFIX)) { arg += THIS_CPU_PTR_LEN; ctx->offset += THIS_CPU_PTR_LEN; - is_ptr = true; } else if (str_has_prefix(arg, THIS_CPU_READ_PREFIX)) { arg += THIS_CPU_READ_LEN; ctx->offset += THIS_CPU_READ_LEN; - } else + is_read = true; + } else { + trace_probe_log_err(ctx->offset, BAD_FETCH_ARG); return -EINVAL; + } - ret = handle_dereference(arg, pcode, end, ctx, FETCH_OP_CPU_PTR, 0); - if (ret || is_ptr) - return ret; - - /* this_cpu_read(VAR) -> +0(this_cpu_ptr(VAR)) */ - code = *pcode; - code++; - if (code == end) { - trace_probe_log_err(ctx->offset, TOO_MANY_OPS); + tmp = strrchr(arg, ')'); + if (!tmp) { + trace_probe_log_err(ctx->offset + strlen(arg), + DEREF_OPEN_BRACE); return -EINVAL; } - code->op = FETCH_OP_DEREF; - code->offset = 0; - *pcode = code; + *tmp = '\0'; + + ctx->stack[ctx->depth].type = STATE_DEREF; + ctx->stack[ctx->depth].deref.deref = FETCH_OP_CPU_PTR; + ctx->stack[ctx->depth].deref.offset = 0; + ctx->stack[ctx->depth].deref.cur_offs = ctx->offset; + ctx->stack[ctx->depth].deref.inner_arg = arg; + ctx->stack[ctx->depth].deref.is_cpu_read = is_read; return 0; } @@ -1007,14 +958,12 @@ static char *find_matched_close_paren(char *s) return NULL; } -static int handle_typecast(char *arg, struct fetch_insn **pcode, - struct fetch_insn *end, - struct traceprobe_parse_context *ctx) +static int handle_typecast(char *arg, struct traceprobe_parse_context *ctx) { int orig_offset = ctx->offset; char *close; char *tmp; - int ret; + char *fieldname; if (!(tparg_is_event_probe(ctx->flags) || tparg_is_function_entry(ctx->flags) || @@ -1028,12 +977,6 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, * For example: (STRUCT)VAR->FIELD and (STRUCT)(VAR)->FIELD are same. * VAR is solved in the nested call. */ - ctx->nested_level++; - if (ctx->nested_level > TRACEPROBE_MAX_NESTED_LEVEL) { - trace_probe_log_err(ctx->offset, TOO_MANY_NESTED); - return -E2BIG; - } - tmp = strchr(arg, ')'); if (!tmp) { trace_probe_log_err(ctx->offset + strlen(arg), @@ -1103,30 +1046,19 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, } *close = '\0'; - /* We need to parse the nested one */ - ret = parse_probe_arg(tmp, find_fetch_type(NULL, ctx->flags), - pcode, end, ctx); - if (ret < 0) - return ret; - ctx->nested_level--; - clear_struct_btf(ctx); - - /* Let tmp point the field name. */ + /* Let fieldname point the field name. */ if (close[1] == '-') - tmp = close + 3; /* Skip "->" after closing parenthesis */ + fieldname = close + 3; /* Skip "->" after closing parenthesis */ else - tmp = close + 2; /* Skip ">" after inner variable name */ - - /* resolve the typecast struct name */ - 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; - ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx); - ctx->prefix_byteoffs = 0; - return ret; + fieldname = close + 2; /* Skip ">" after inner variable name */ + + ctx->stack[ctx->depth].type = STATE_TYPECAST; + ctx->stack[ctx->depth].typecast.casttype = arg + 1; + ctx->stack[ctx->depth].typecast.fieldname = fieldname; + ctx->stack[ctx->depth].typecast.orig_offset = orig_offset; + ctx->stack[ctx->depth].typecast.field_offset_diff = fieldname - arg; + ctx->stack[ctx->depth].typecast.inner_arg = tmp; + return 0; } #else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */ @@ -1171,9 +1103,20 @@ static int check_prepare_btf_string_fetch(char *typename, return 0; } -static int handle_typecast(char *arg, struct fetch_insn **pcode, - struct fetch_insn *end, +static int parse_btf_casttype(char *casttype, + struct traceprobe_parse_context *ctx) +{ + return -EOPNOTSUPP; +} + +static int parse_btf_field(char *fieldname, const struct btf_type *type, + struct fetch_insn **pcode, struct fetch_insn *end, struct traceprobe_parse_context *ctx) +{ + return -EOPNOTSUPP; +} + +static int handle_typecast(char *arg, struct traceprobe_parse_context *ctx) { trace_probe_log_err(ctx->offset, NOSUP_BTFARG); return -EOPNOTSUPP; @@ -1598,9 +1541,7 @@ static int parse_probe_arg_mem_symbol(char *arg, struct fetch_insn **pcode, return 0; } -static int parse_probe_arg_deref(char *arg, struct fetch_insn **pcode, - struct fetch_insn *end, - struct traceprobe_parse_context *ctx) +static int parse_probe_arg_deref(char *arg, struct traceprobe_parse_context *ctx) { int deref = FETCH_OP_DEREF; long offset = 0; @@ -1627,7 +1568,22 @@ static int parse_probe_arg_deref(char *arg, struct fetch_insn **pcode, } ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0); arg = tmp + 1; - return handle_dereference(arg, pcode, end, ctx, deref, offset); + + tmp = strrchr(arg, ')'); + if (!tmp) { + trace_probe_log_err(ctx->offset + strlen(arg), + DEREF_OPEN_BRACE); + return -EINVAL; + } + *tmp = '\0'; + + ctx->stack[ctx->depth].type = STATE_DEREF; + ctx->stack[ctx->depth].deref.deref = deref; + ctx->stack[ctx->depth].deref.offset = offset; + ctx->stack[ctx->depth].deref.cur_offs = ctx->offset; + ctx->stack[ctx->depth].deref.inner_arg = arg; + ctx->stack[ctx->depth].deref.is_cpu_read = false; + return 0; } static int parse_probe_arg_imm(char *arg, struct fetch_insn *code, @@ -1659,11 +1615,6 @@ static int parse_probe_arg_default(char *arg, struct fetch_insn **pcode, { int ret; - if (str_has_prefix(arg, THIS_CPU_PTR_PREFIX) || - str_has_prefix(arg, THIS_CPU_READ_PREFIX)) { - return parse_this_cpu(arg, pcode, end, ctx); - } - if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable or event field */ if (ctx->flags & TPARG_FL_TEVENT) { @@ -1685,11 +1636,56 @@ static int parse_probe_arg_default(char *arg, struct fetch_insn **pcode, return 0; } -/* Recursive argument parser */ -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 parse_probe_arg_nested(char **parg, struct traceprobe_parse_context *ctx) +{ + char *arg = *parg; + int ret; + + while (true) { + /* Determine if this is a nested argument */ + if (arg[0] != '+' && arg[0] != '-' && arg[0] != '(' && + !str_has_prefix(arg, THIS_CPU_PTR_PREFIX) && + !str_has_prefix(arg, THIS_CPU_READ_PREFIX)) + break; + + /* If nested, check the maximum depth limit */ + if (ctx->depth >= TRACEPROBE_MAX_NESTED_LEVEL) { + trace_probe_log_err(ctx->offset, TOO_MANY_NESTED); + return -E2BIG; + } + + /* Perform the actual parsing subroutine calls */ + switch (arg[0]) { + case '+': + case '-': + ret = parse_probe_arg_deref(arg, ctx); + if (ret) + return ret; + arg = ctx->stack[ctx->depth].deref.inner_arg; + break; + case '(': + ret = handle_typecast(arg, ctx); + if (ret) + return ret; + arg = ctx->stack[ctx->depth].typecast.inner_arg; + break; + default: + ret = parse_this_cpu(arg, ctx); + if (ret) + return ret; + arg = ctx->stack[ctx->depth].deref.inner_arg; + break; + } + ctx->depth++; + } + + *parg = arg; + return 0; +} + +static int parse_probe_arg_leaf(char *arg, const struct fetch_type *type, + struct fetch_insn **pcode, struct fetch_insn *end, + struct traceprobe_parse_context *ctx) { struct fetch_insn *code = *pcode; int ret; @@ -1704,26 +1700,112 @@ parse_probe_arg(char *arg, const struct fetch_type *type, case '@': /* memory, file-offset or symbol */ ret = parse_probe_arg_mem_symbol(arg, pcode, end, ctx); break; - case '+': /* deref memory */ - case '-': - ret = parse_probe_arg_deref(arg, pcode, end, ctx); - break; case '\\': /* Immediate value */ ret = parse_probe_arg_imm(arg, code, ctx); break; - case '(': - ret = handle_typecast(arg, pcode, end, ctx); - break; default: ret = parse_probe_arg_default(arg, pcode, end, ctx); break; } - if (!ret && code->op == FETCH_OP_NOP) { + + if (ret) + return ret; + + if (code->op == FETCH_OP_NOP) { /* Parsed, but do not find fetch method */ trace_probe_log_err(ctx->offset, BAD_FETCH_ARG); - ret = -EINVAL; + return -EINVAL; } - return ret; + + return 0; +} + +static int unwind_parse_states(struct fetch_insn **pcode, struct fetch_insn *end, + struct traceprobe_parse_context *ctx) +{ + struct parse_state *state; + struct fetch_insn *code; + int ret; + + while (ctx->depth > 0) { + ctx->depth--; + state = &ctx->stack[ctx->depth]; + + if (state->type == STATE_DEREF) { + code = *pcode; + ctx->offset = state->deref.cur_offs; + if (code->op == FETCH_OP_COMM || code->op == FETCH_OP_IMMSTR) { + trace_probe_log_err(ctx->offset, COMM_CANT_DEREF); + return -EINVAL; + } + + if (!(state->deref.deref == FETCH_OP_CPU_PTR && + *state->deref.inner_arg == '@')) { + code++; + if (code == end) { + trace_probe_log_err(ctx->offset, TOO_MANY_OPS); + return -EINVAL; + } + } + *pcode = code; + + code->op = state->deref.deref; + code->offset = state->deref.offset; + ctx->last_type = NULL; + + if (state->deref.is_cpu_read) { + code = *pcode; + code++; + if (code == end) { + trace_probe_log_err(ctx->offset, TOO_MANY_OPS); + return -EINVAL; + } + code->op = FETCH_OP_DEREF; + code->offset = 0; + *pcode = code; + } + } else if (state->type == STATE_TYPECAST) { + clear_struct_btf(ctx); + + /* resolve the typecast struct name */ + ctx->offset = state->typecast.orig_offset + 1; /* for the '(' */ + ret = parse_btf_casttype(state->typecast.casttype, ctx); + if (ret < 0) + return ret; + + ctx->offset = state->typecast.orig_offset + + state->typecast.field_offset_diff; + ret = parse_btf_field(state->typecast.fieldname, + ctx->last_struct, pcode, + end, ctx); + ctx->prefix_byteoffs = 0; + if (ret < 0) + return ret; + } + } + + return 0; +} + +/* Loop-based (non-recursive) argument parser */ +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) +{ + int ret; + + ctx->depth = 0; + + ret = parse_probe_arg_nested(&arg, ctx); + if (ret) + return ret; + + ret = parse_probe_arg_leaf(arg, type, pcode, end, ctx); + if (ret) + return ret; + + return unwind_parse_states(pcode, end, ctx); } /* Bitfield type needs to be parsed into a fetch function */ @@ -1984,12 +2066,6 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size, ctx); if (ret < 0) goto fail; - /* nested_level must be 0 here, otherwise there is a bug. */ - if (WARN_ON_ONCE(ctx->nested_level)) { - ret = -EINVAL; - goto fail; - } - /* Update storing type if BTF is available */ if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) && ctx->last_type) { diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index e6d427910c4f..ebdc706e7cb6 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -437,6 +437,34 @@ static inline bool tparg_is_event_probe(unsigned int flags) return !!(flags & TPARG_FL_TEVENT); } +/* Each typecast consumes nested level. So the max number of typecast is 8. */ +#define TRACEPROBE_MAX_NESTED_LEVEL 8 + +enum parse_state_type { + STATE_DEREF, + STATE_TYPECAST, +}; + +struct parse_state { + int type; + union { + struct { + int deref; + long offset; + int cur_offs; + char *inner_arg; + bool is_cpu_read; + } deref; + struct { + char *casttype; + char *fieldname; + int orig_offset; + int field_offset_diff; + char *inner_arg; + } typecast; + }; +}; + struct traceprobe_parse_context { struct trace_event_call *event; /* BTF related parameters */ @@ -453,12 +481,11 @@ struct traceprobe_parse_context { struct trace_probe *tp; unsigned int flags; int offset; - int nested_level; int prefix_byteoffs; /* The byte offset of the prefix field of typecast */ + struct parse_state stack[TRACEPROBE_MAX_NESTED_LEVEL + 1]; + int depth; }; -/* Each typecast consumes nested level. So the max number of typecast is 3. */ -#define TRACEPROBE_MAX_NESTED_LEVEL 3 extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *argv, 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 984ab94df213..384209968325 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 @@ -60,7 +60,7 @@ check_error 'f vfs_read ^&1' # BAD_FETCH_ARG # We've introduced this limitation with array support if grep -q ' \\\[\\\]' README; then -check_error 'f vfs_read +0(^+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(@0))))))))))))))' # TOO_MANY_OPS? +check_error 'f vfs_read +0(+0(+0(+0(+0(+0(+0(+0(^+0(@0)))))))))' # TOO_MANY_NESTED check_error 'f vfs_read +0(@11):u8[10^' # ARRAY_NO_CLOSE check_error 'f vfs_read +0(@11):u8[10]^a' # BAD_ARRAY_SUFFIX check_error 'f vfs_read +0(@11):u8[^10a]' # BAD_ARRAY_NUM @@ -114,7 +114,7 @@ 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=(a)((b)((c)(^(d)file->d)->c)->b)->a' # TOO_MANY_NESTED +check_error 'f vfs_read arg1=(a)((b)((c)((d)((e)((f)((g)((h)(^(i)file->i)->h)->g)->f)->e)->d)->c)->b)->a' # 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,^foo_bar)file->pid' # NO_BTF_FIELD check_error 'f vfs_read arg1=(^task_struct1234)file->pid' # NO_PTR_STRCT diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc b/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc index 2d0905b2c8b7..72b8652df9ba 100644 --- a/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc +++ b/tools/testing/selftests/ftrace/test.d/dynevent/tprobe_syntax_errors.tc @@ -46,7 +46,7 @@ check_error 't kfree ^&1' # BAD_FETCH_ARG # We've introduced this limitation with array support if grep -q ' \\\[\\\]' README; then -check_error 't kfree +0(^+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(@0))))))))))))))' # TOO_MANY_OPS? +check_error 't kfree +0(+0(+0(+0(+0(+0(+0(+0(^+0(@0)))))))))' # TOO_MANY_NESTED check_error 't kfree +0(@11):u8[10^' # ARRAY_NO_CLOSE check_error 't kfree +0(@11):u8[10]^a' # BAD_ARRAY_SUFFIX check_error 't kfree +0(@11):u8[^10a]' # BAD_ARRAY_NUM 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 d28f63b7e8a9..b0e6b80ccb01 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 @@ -56,7 +56,7 @@ check_error 'p vfs_read ^&1' # BAD_FETCH_ARG # We've introduced this limitation with array support if grep -q ' \\\[\\\]' README; then -check_error 'p vfs_read +0(^+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(+0(@0))))))))))))))' # TOO_MANY_OPS? +check_error 'p vfs_read +0(+0(+0(+0(+0(+0(+0(+0(^+0(@0)))))))))' # TOO_MANY_NESTED check_error 'p vfs_read +0(@11):u8[10^' # ARRAY_NO_CLOSE check_error 'p vfs_read +0(@11):u8[10]^a' # BAD_ARRAY_SUFFIX check_error 'p vfs_read +0(@11):u8[^10a]' # BAD_ARRAY_NUM @@ -117,7 +117,7 @@ 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=(a)((b)((c)(^(d)file->d)->c)->b)->a' # TOO_MANY_NESTED +check_error 'p vfs_read arg1=(a)((b)((c)((d)((e)((f)((g)((h)(^(i)file->i)->h)->g)->f)->e)->d)->c)->b)->a' # 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,^foo_bar)file->pid' # NO_BTF_FIELD check_error 'p vfs_read arg1=(^task_struct1234)file->pid' # NO_PTR_STRCT -- cgit v1.2.3 From ab2900ae252b2a3dc2641bea627d1cb14a5d1bcb Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:10 +0200 Subject: rv: Simplify hybrid automata monitors's clock variables Hybrid automata monitors's clock variables have two different representations: - The invariant representation, which is the timestamp when the invariant expires - The guard representation, which is the timestamp when the clock is last reset This dual representation makes the logic quite difficult to follow (well, at least for me). It also complicates the monitors and the generation tool, as it requires conversion back and forth between the representation. Simplify by using the clock variables for a single purpose: storing the time stamp since the clock is last reset. This also allows simplifying rvgen, which will be done in a follow-up commit. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/c0f600dcbf3d8b487c944406851a39146f4d91fa.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- include/rv/ha_monitor.h | 64 ++++++++------------------------ kernel/trace/rv/monitors/nomiss/nomiss.c | 18 +-------- kernel/trace/rv/monitors/stall/stall.c | 2 +- 3 files changed, 19 insertions(+), 65 deletions(-) (limited to 'kernel/trace') diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h index 28d3c74cabfc..9144b4c06f3f 100644 --- a/include/rv/ha_monitor.h +++ b/include/rv/ha_monitor.h @@ -327,19 +327,8 @@ static inline void __ha_monitor_timer_callback(struct ha_monitor *ha_mon) } /* - * The clock variables have 2 different representations in the env_store: - * - The guard representation is the timestamp of the last reset - * - The invariant representation is the timestamp when the invariant expires - * As the representations are incompatible, care must be taken when switching - * between them: the invariant representation can only be used when starting a - * timer when the previous representation was guard (e.g. no other invariant - * started since the last reset operation). - * Likewise, switching from invariant to guard representation without a reset - * can be done only by subtracting the exact value used to start the invariant. - * - * Reading the environment variable (ha_get_clk) also reflects this difference - * any reads in states that have an invariant return the (possibly negative) - * time since expiration, other reads return the time since last reset. + * The clock variables store the time epoch - the timestamp when the clock was last reset. + * They are read by subtracting the time epoch from the current time. */ /* @@ -353,31 +342,21 @@ static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64 { WRITE_ONCE(ha_mon->env_store[env], time_ns); } -static inline void ha_set_invariant_ns(struct ha_monitor *ha_mon, enum envs env, - u64 value, u64 time_ns) -{ - WRITE_ONCE(ha_mon->env_store[env], time_ns + value); -} -static inline bool ha_check_invariant_ns(struct ha_monitor *ha_mon, - enum envs env, u64 time_ns) +static inline bool ha_check_invariant_ns(struct ha_monitor *ha_mon, enum envs env, + u64 time_ns, u64 expire_ns) { - return READ_ONCE(ha_mon->env_store[env]) >= time_ns; + return READ_ONCE(ha_mon->env_store[env]) >= time_ns - expire_ns; } /* * ha_invariant_passed_ns - prepare the invariant and return the time since reset */ -static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs env, - u64 expire, u64 time_ns) +static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs env, u64 time_ns) { - u64 passed = 0; - if (env < 0 || env >= ENV_MAX_STORED) return 0; if (ha_monitor_env_invalid(ha_mon, env)) return 0; - passed = ha_get_env(ha_mon, env, time_ns); - ha_set_invariant_ns(ha_mon, env, expire - passed, time_ns); - return passed; + return ha_get_env(ha_mon, env, time_ns); } /* @@ -391,32 +370,21 @@ static inline void ha_reset_clk_jiffy(struct ha_monitor *ha_mon, enum envs env) { WRITE_ONCE(ha_mon->env_store[env], get_jiffies_64()); } -static inline void ha_set_invariant_jiffy(struct ha_monitor *ha_mon, - enum envs env, u64 value) +static inline bool ha_check_invariant_jiffy(struct ha_monitor *ha_mon, enum envs env, + u64 time_ns, u64 expire_jiffy) { - WRITE_ONCE(ha_mon->env_store[env], get_jiffies_64() + value); -} -static inline bool ha_check_invariant_jiffy(struct ha_monitor *ha_mon, - enum envs env, u64 time_ns) -{ - return time_after64(READ_ONCE(ha_mon->env_store[env]), get_jiffies_64()); - + return time_after64(READ_ONCE(ha_mon->env_store[env]), get_jiffies_64() - expire_jiffy); } /* * ha_invariant_passed_jiffy - prepare the invariant and return the time since reset */ -static inline u64 ha_invariant_passed_jiffy(struct ha_monitor *ha_mon, enum envs env, - u64 expire, u64 time_ns) +static inline u64 ha_invariant_passed_jiffy(struct ha_monitor *ha_mon, enum envs env, u64 time_ns) { - u64 passed = 0; - if (env < 0 || env >= ENV_MAX_STORED) return 0; if (ha_monitor_env_invalid(ha_mon, env)) return 0; - passed = ha_get_env(ha_mon, env, time_ns); - ha_set_invariant_jiffy(ha_mon, env, expire - passed); - return passed; + return ha_get_env(ha_mon, env, time_ns); } /* @@ -463,14 +431,14 @@ static inline void ha_setup_timer(struct ha_monitor *ha_mon) static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env, u64 expire, u64 time_ns) { - u64 passed = ha_invariant_passed_jiffy(ha_mon, env, expire, time_ns); + u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns); mod_timer(&ha_mon->timer, get_jiffies_64() + expire - passed); } static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env, u64 expire, u64 time_ns) { - u64 passed = ha_invariant_passed_ns(ha_mon, env, expire, time_ns); + u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns); ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED, nsecs_to_jiffies(expire - passed + TICK_NSEC - 1), time_ns); @@ -516,7 +484,7 @@ static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env, u64 expire, u64 time_ns) { int mode = HRTIMER_MODE_REL_HARD; - u64 passed = ha_invariant_passed_ns(ha_mon, env, expire, time_ns); + u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns); if (RV_MON_TYPE == RV_MON_PER_CPU) mode |= HRTIMER_MODE_PINNED; @@ -525,7 +493,7 @@ static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env, static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env, u64 expire, u64 time_ns) { - u64 passed = ha_invariant_passed_jiffy(ha_mon, env, expire, time_ns); + u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns); ha_start_timer_ns(ha_mon, ENV_MAX_STORED, jiffies_to_nsecs(expire - passed), time_ns); diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c index 8ead8783c29f..515ece5ce0ca 100644 --- a/kernel/trace/rv/monitors/nomiss/nomiss.c +++ b/kernel/trace/rv/monitors/nomiss/nomiss.c @@ -57,24 +57,12 @@ static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, enum states next_state, u64 time_ns) { if (curr_state == ready_nomiss) - return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns); + return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns, DEADLINE_NS(ha_mon)); else if (curr_state == running_nomiss) - return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns); + return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns, DEADLINE_NS(ha_mon)); return true; } -static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon, - enum states curr_state, enum events event, - enum states next_state, u64 time_ns) -{ - if (curr_state == next_state) - return; - if (curr_state == ready_nomiss) - ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns); - else if (curr_state == running_nomiss) - ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns); -} - static inline bool ha_verify_guards(struct ha_monitor *ha_mon, enum states curr_state, enum events event, enum states next_state, u64 time_ns) @@ -122,8 +110,6 @@ static bool ha_verify_constraint(struct ha_monitor *ha_mon, if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns)) return false; - ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns); - if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns)) return false; diff --git a/kernel/trace/rv/monitors/stall/stall.c b/kernel/trace/rv/monitors/stall/stall.c index 3c38fb1a0159..b265578f845c 100644 --- a/kernel/trace/rv/monitors/stall/stall.c +++ b/kernel/trace/rv/monitors/stall/stall.c @@ -38,7 +38,7 @@ static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, enum states next_state, u64 time_ns) { if (curr_state == enqueued_stall) - return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns); + return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns, threshold_jiffies); return true; } -- cgit v1.2.3 From 15f197856d68882af9416fc97516bb55079b7677 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:12:10 +0900 Subject: tracing/probes: Avoid temporary buffer truncation in trace_probe_match_command_args() In trace_probe_match_command_args(), a stack buffer buf[MAX_ARGSTR_LEN + 1] (256 bytes) is used to format "=". However, since name can be up to 32 bytes (MAX_ARG_NAME_LEN) and comm up to 255 bytes (MAX_ARGSTR_LEN), the formatted string can exceed 256 bytes and get truncated by snprintf(), causing spurious argument matching failures. Instead of formatting into a temporary buffer on stack, compare the argument name, the '=' delimiter, and the comm expression directly. Link: https://lore.kernel.org/all/178454233010.290363.10428767141343428804.stgit@devnote2/ Fixes: eb5bf81330a7 ("tracing/kprobe: Add per-probe delete from event") Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index d17cfee77d9c..95e3d072321f 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -2338,16 +2338,17 @@ int trace_probe_compare_arg_type(struct trace_probe *a, struct trace_probe *b) bool trace_probe_match_command_args(struct trace_probe *tp, int argc, const char **argv) { - char buf[MAX_ARGSTR_LEN + 1]; int i; if (tp->nr_args < argc) return false; for (i = 0; i < argc; i++) { - snprintf(buf, sizeof(buf), "%s=%s", - tp->args[i].name, tp->args[i].comm); - if (strcmp(buf, argv[i])) + int len = strlen(tp->args[i].name); + + if (strncmp(argv[i], tp->args[i].name, len) || + argv[i][len] != '=' || + strcmp(argv[i] + len + 1, tp->args[i].comm)) return false; } return true; -- cgit v1.2.3 From a9d6fb284039a5d3858a1d9f9a0d7e46cfb7c2d4 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:12:20 +0900 Subject: tracing/probes: Prevent out-of-bounds write in __trace_probe_log_err() If trace_probe_log.argc is 0 in __trace_probe_log_err(), the loop constructing the command string will not execute and p will remain equal to command. Writing to *(p - 1) will cause an out-of-bounds access before command. This should not happen, but better to be treated. Reject if trace_probe_log.argc is 0. Link: https://lore.kernel.org/all/178454233992.290363.18323091580600697731.stgit@devnote2/ Fixes: ab105a4fb894 ("tracing: Use tracing error_log with probe events") Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 95e3d072321f..49daa3cc2a45 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -188,7 +188,7 @@ void __trace_probe_log_err(int offset, int err_type) lockdep_assert_held(&dyn_event_ops_mutex); - if (!trace_probe_log.argv) + if (!trace_probe_log.argv || !trace_probe_log.argc) return; /* Recalculate the length and allocate buffer */ -- cgit v1.2.3 From 8ce20bfba48902e1382187cd1a852f7cf3a1e739 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:12:29 +0900 Subject: tracing/probes: Fix potential underflow in LEN_OR_ZERO macro In __set_print_fmt(), LEN_OR_ZERO is defined as (len ? len - pos : 0). If len is non-zero but smaller than pos, len - pos evaluates to a negative integer. When passed as a size argument to snprintf(), this negative value is cast to a large unsigned size_t, bypassing buffer size limits. Ensure len > pos before subtracting to avoid integer underflow. Link: https://lore.kernel.org/all/178454234934.290363.15247317871499514139.stgit@devnote2/ Fixes: 5bf652aaf46c ("tracing/probes: Integrate duplicate set_print_fmt()") Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 49daa3cc2a45..506e6037e163 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -2013,7 +2013,7 @@ int traceprobe_update_arg(struct probe_arg *arg) } /* When len=0, we just calculate the needed length */ -#define LEN_OR_ZERO (len ? len - pos : 0) +#define LEN_OR_ZERO (len > pos ? len - pos : 0) static int __set_print_fmt(struct trace_probe *tp, char *buf, int len, enum probe_print_type ptype) { -- cgit v1.2.3 From f418d68d71fd4a0a9cef92377bc8c4c3334b5b53 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:12:38 +0900 Subject: tracing/eprobe: Fix exact system name matching in eprobe_dyn_event_match() eprobe_dyn_event_match() checks if the target event system in argv[0] matches ep->event_system using strncmp(ep->event_system, argv[0], len). However, if ep->event_system is longer than len (e.g. "eprobes" vs "ep/event"), strncmp() still returns 0 because the first len characters match. Check that ep->event_system[len] is '\0' to ensure exact system name matching. Link: https://lore.kernel.org/all/178454235856.290363.14872590900774231133.stgit@devnote2/ Fixes: 7d5fda1c841f ("tracing: Fix event probe removal from dynamic events") Cc: stable@vger.kernel.org Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_eprobe.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_eprobe.c b/kernel/trace/trace_eprobe.c index 50518b071414..bcd97cb24ac9 100644 --- a/kernel/trace/trace_eprobe.c +++ b/kernel/trace/trace_eprobe.c @@ -172,7 +172,8 @@ static bool eprobe_dyn_event_match(const char *system, const char *event, if (!slash) return false; - if (strncmp(ep->event_system, argv[0], slash - argv[0])) + if (strncmp(ep->event_system, argv[0], slash - argv[0]) || + ep->event_system[slash - argv[0]] != '\0') return false; if (strcmp(ep->event_name, slash + 1)) return false; -- cgit v1.2.3 From 323853e2d125ec1a99df61893fb546728f4d738e Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:43:39 +0900 Subject: tracing/probes: Remove duplicate MAX_ARRAY_LEN macro definition MAX_ARRAY_LEN is defined twice in trace_probe.h. Remove the redundant definition. Link: https://lore.kernel.org/all/178454421926.296567.6649983666070787570.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.h | 1 - 1 file changed, 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index ebdc706e7cb6..b3571c85abb1 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -220,7 +220,6 @@ DECLARE_BASIC_PRINT_TYPE_FUNC(symbol); _ASSIGN_FETCH_TYPE(#ptype, ptype, ftype, sizeof(ftype), sign, atype) #define ASSIGN_FETCH_TYPE_END {} -#define MAX_ARRAY_LEN 64 #ifdef CONFIG_KPROBE_EVENTS bool trace_kprobe_on_func_entry(struct trace_event_call *call); -- cgit v1.2.3 From 794b5640aa7b295623c1d6b6fc2fe229a30aad0d Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:43:48 +0900 Subject: tracing/probes: Remove redundant boolean conversion in trace_probe_has_single_file() list_is_singular() returns a boolean value, so the double negation (!!) in trace_probe_has_single_file() is redundant. Remove it. Link: https://lore.kernel.org/all/178454422826.296567.4382363406595169533.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index b3571c85abb1..c0f05763811c 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -360,7 +360,7 @@ static inline int trace_probe_unregister_event_call(struct trace_probe *tp) static inline bool trace_probe_has_single_file(struct trace_probe *tp) { - return !!list_is_singular(&tp->event->files); + return list_is_singular(&tp->event->files); } int trace_probe_init(struct trace_probe *tp, const char *event, -- cgit v1.2.3 From 6e9328ed75afc2f2564514df53ff66259f805a2a Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:43:57 +0900 Subject: tracing/probes: Remove redundant bounds check in trace_probe_compare_arg_type() In trace_probe_compare_arg_type(), prior to entering the comparison loop, a->nr_args and b->nr_args are checked for equality. Since the loop condition is i < a->nr_args, i is guaranteed to be less than b->nr_args inside the loop. Remove the redundant (b->nr_args <= i) check. Link: https://lore.kernel.org/all/178454423769.296567.6694636865644203423.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 7568f5e68de7..91ba4490937b 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -2770,10 +2770,9 @@ int trace_probe_compare_arg_type(struct trace_probe *a, struct trace_probe *b) return b->nr_args + 1; for (i = 0; i < a->nr_args; i++) { - if ((b->nr_args <= i) || - ((a->args[i].type != b->args[i].type) || - (a->args[i].count != b->args[i].count) || - strcmp(a->args[i].name, b->args[i].name))) + if ((a->args[i].type != b->args[i].type) || + (a->args[i].count != b->args[i].count) || + strcmp(a->args[i].name, b->args[i].name)) return i + 1; } -- cgit v1.2.3 From 5428fca0cd542d4fb7beb781c1b44292aedb7915 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:44:07 +0900 Subject: tracing/probes: Remove unused parameter from parse_probe_var_retval() The parameter 'arg' in parse_probe_var_retval() is unused. Remove it and update its caller accordingly. Link: https://lore.kernel.org/all/178454424711.296567.15352344258939682112.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 91ba4490937b..eb7be3725e85 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1262,7 +1262,7 @@ NOKPROBE_SYMBOL(store_trace_entry_data) #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) -static int parse_probe_var_retval(char *orig_arg, char *arg, +static int parse_probe_var_retval(char *orig_arg, struct fetch_insn **pcode, struct fetch_insn *end, struct traceprobe_parse_context *ctx) @@ -1415,7 +1415,7 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t, goto inval; if (str_has_prefix(arg, "retval")) - return parse_probe_var_retval(orig_arg, arg, pcode, end, ctx); + return parse_probe_var_retval(orig_arg, pcode, end, ctx); len = str_has_prefix(arg, "stack"); if (len) -- cgit v1.2.3 From 78d252c935c9c54742443e871b7b39204ecb7a01 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:44:16 +0900 Subject: tracing/probes: Cleanup pointer arithmetic in store_trace_entry_data() In store_trace_entry_data(), edata is cast to unsigned long for pointer offset arithmetic before being cast back to unsigned long *. Cast edata to u8 * instead. Link: https://lore.kernel.org/all/178454425622.296567.16952341966130280432.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index eb7be3725e85..4899aa5f9230 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1246,7 +1246,7 @@ void store_trace_entry_data(void *edata, struct trace_probe *tp, struct pt_regs val = regs_get_kernel_argument(regs, code->param); break; case FETCH_OP_ST_EDATA: - *(unsigned long *)((unsigned long)edata + code->offset) = val; + *(unsigned long *)((u8 *)edata + code->offset) = val; break; case FETCH_OP_END: goto end; -- cgit v1.2.3 From 0fb6fd1eb3e7c8649473bd0ea6d897c835740157 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:44:25 +0900 Subject: tracing/probes: Simplify BTF_KIND_PTR case in fetch_type_from_btf_type() Use a ternary operator for checking IS_ENABLED(CONFIG_64BIT) in the BTF_KIND_PTR case of fetch_type_from_btf_type() to simplify the code. Link: https://lore.kernel.org/all/178454426554.296567.17700307233923830044.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 4899aa5f9230..fc62d09ea535 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -495,10 +495,7 @@ static const char *fetch_type_from_btf_type(struct btf *btf, return "s64"; case BTF_KIND_PTR: /* pointer will be converted to "x??" */ - if (IS_ENABLED(CONFIG_64BIT)) - return "x64"; - else - return "x32"; + return IS_ENABLED(CONFIG_64BIT) ? "x64" : "x32"; case BTF_KIND_INT: intdata = btf_type_int(type); if (BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED) { -- cgit v1.2.3 From 36db65d0e5de923909e4d2b13524375b139568ac Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:44:34 +0900 Subject: tracing/fprobe: Remove redundant snprintf in trace_fprobe_match_command_head() trace_fprobe_match_command_head() copies trace_fprobe_symbol(tf) into a local buffer 'buf' of size MAX_COMMON_HEAD_LEN + 1 using snprintf before comparing with argv[0]. Since trace_fprobe_symbol(tf) already returns a null-terminated string, comparing it directly with argv[0] via strcmp() avoids stack buffer usage and potential symbol truncation at MAX_COMMON_HEAD_LEN. Link: https://lore.kernel.org/all/178454427449.296567.12336315661120939938.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_fprobe.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c index 5638a90e61cc..566b64853c61 100644 --- a/kernel/trace/trace_fprobe.c +++ b/kernel/trace/trace_fprobe.c @@ -238,13 +238,10 @@ static bool trace_fprobe_is_busy(struct dyn_event *ev) static bool trace_fprobe_match_command_head(struct trace_fprobe *tf, int argc, const char **argv) { - char buf[MAX_COMMON_HEAD_LEN + 1]; - if (!argc) return true; - snprintf(buf, sizeof(buf), "%s", trace_fprobe_symbol(tf)); - if (strcmp(buf, argv[0])) + if (strcmp(trace_fprobe_symbol(tf), argv[0])) return false; argc--; argv++; -- cgit v1.2.3 From 3d785646cc488d3f743e0cb67491e80b69200cfb Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:44:43 +0900 Subject: tracing/fprobe: Remove redundant memset in fentry_perf_func() fentry_perf_func() calls memset(&entry[1], 0, dsize) prior to calling store_trace_args(). store_trace_args() populates the entry buffer and handles dynamic data fields. Furthermore, passing dsize (the dynamic data byte length) to memset at &entry[1] (the start of fixed trace arguments) is inaccurate as it zeroes from the fixed args area rather than the dynamic data region. Remove this redundant memset call to align with fexit_perf_func() and other probe perf functions. Link: https://lore.kernel.org/all/178454428366.296567.16465331441301815980.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_fprobe.c | 1 - 1 file changed, 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c index 566b64853c61..ec9105fca4a6 100644 --- a/kernel/trace/trace_fprobe.c +++ b/kernel/trace/trace_fprobe.c @@ -471,7 +471,6 @@ static int fentry_perf_func(struct trace_fprobe *tf, unsigned long entry_ip, regs = ftrace_fill_perf_regs(fregs, regs); entry->ip = entry_ip; - memset(&entry[1], 0, dsize); store_trace_args(&entry[1], &tf->tp, fregs, NULL, sizeof(*entry), dsize); perf_trace_buf_submit(entry, size, rctx, call->event.type, 1, regs, head, NULL); -- cgit v1.2.3 From 5b385fd434c75b6288a36722d606f04444db34eb Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:44:53 +0900 Subject: tracing/kprobe: Remove redundant memset in kprobe_perf_func() kprobe_perf_func() calls memset(&entry[1], 0, dsize) prior to calling store_trace_args(). store_trace_args() populates the entry buffer and handles dynamic data fields. Remove this redundant memset call to align with kretprobe_perf_func() and other probe perf functions. Link: https://lore.kernel.org/all/178454429309.296567.17379087988750549247.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_kprobe.c | 1 - 1 file changed, 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c index cc24e992732c..c25f902aa1a6 100644 --- a/kernel/trace/trace_kprobe.c +++ b/kernel/trace/trace_kprobe.c @@ -1721,7 +1721,6 @@ kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs) return 0; entry->ip = (unsigned long)tk->rp.kp.addr; - memset(&entry[1], 0, dsize); store_trace_args(&entry[1], &tk->tp, regs, NULL, sizeof(*entry), dsize); perf_trace_buf_submit(entry, size, rctx, call->event.type, 1, regs, head, NULL); -- cgit v1.2.3 From 5be3a9db600853578559681b2dd20a9bc7dd4fc5 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Mon, 20 Jul 2026 19:45:02 +0900 Subject: tracing/probes: Fix extra whitespace in trace_probe_kernel.h Clean up extra space after '=' in fetch_store_strlen_user(). Link: https://lore.kernel.org/all/178454430235.296567.9486503882045148585.stgit@devnote2/ Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe_kernel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_probe_kernel.h b/kernel/trace/trace_probe_kernel.h index bb723eefd7b7..4eb4acfa09ba 100644 --- a/kernel/trace/trace_probe_kernel.h +++ b/kernel/trace/trace_probe_kernel.h @@ -12,7 +12,7 @@ static nokprobe_inline int fetch_store_strlen_user(unsigned long addr) { - const void __user *uaddr = (__force const void __user *)addr; + const void __user *uaddr = (__force const void __user *)addr; return strnlen_user_nofault(uaddr, MAX_STRING_SIZE); } -- cgit v1.2.3 From 94b83ff0c0a69e42f403b59918529fbca2a89daf Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Mon, 13 Jul 2026 08:28:23 +0100 Subject: tracing/remotes: Fix page_va[] access before counter update in trace_remote_alloc_buffer() page_va[] is annotated __counted_by(nr_page_va), so nr_page_va must cover an index before that element is accessed. The allocation loop writes page_va[id] while nr_page_va is still id and increments it only afterwards, so every write is one element past the declared count. The store is out of bounds with respect to the annotation: a build with CONFIG_UBSAN_BOUNDS on a toolchain that honours __counted_by (clang >= 20.1, gcc >= 15.1) flags it as an array-index overflow. Increment nr_page_va before writing the element it now covers. A failed allocation then leaves the slot counted but NULL; the error path frees it with free_page(0), which is a no-op. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260713072823.2668323-1-fuad.tabba@linux.dev Fixes: 96e43537af546 ("tracing: Introduce trace remotes") Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace_remote.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index 0f6ef5c36d84..ef42d9c38b37 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -1004,11 +1004,10 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, desc->nr_cpus++; for (id = 0; id < nr_pages; id++) { + rb_desc->nr_page_va++; rb_desc->page_va[id] = (unsigned long)__get_free_page(GFP_KERNEL); if (!rb_desc->page_va[id]) goto err; - - rb_desc->nr_page_va++; } rb_desc = __next_ring_buffer_desc(rb_desc); } -- cgit v1.2.3 From b255fc56f4e85fb34a491be9aa31bece3f4f3958 Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Wed, 15 Jul 2026 09:58:24 +0800 Subject: rv: Simplify task monitor slot management The slot array already tracks allocation and task_monitor_count duplicates that state. On an invalid second release, the old code warns but still decrements the counter, corrupting later allocations. Use the slot array as the sole source of truth. Return after warning about an unused slot, and return -EBUSY when no slot is free. Reviewed-by: Gabriele Monaco Signed-off-by: Li Qiang Link: https://lore.kernel.org/r/20260715015825.1413822-1-liqiang01@kylinos.cn Signed-off-by: Gabriele Monaco --- kernel/trace/rv/rv.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c index ee4e68102f17..187d87d5991c 100644 --- a/kernel/trace/rv/rv.c +++ b/kernel/trace/rv/rv.c @@ -164,7 +164,6 @@ struct dentry *get_monitors_root(void) */ LIST_HEAD(rv_monitors_list); -static int task_monitor_count; static bool task_monitor_slots[CONFIG_RV_PER_TASK_MONITORS]; int rv_get_task_monitor_slot(void) @@ -173,21 +172,14 @@ int rv_get_task_monitor_slot(void) lockdep_assert_held(&rv_interface_lock); - if (task_monitor_count == CONFIG_RV_PER_TASK_MONITORS) - return -EBUSY; - - task_monitor_count++; - for (i = 0; i < CONFIG_RV_PER_TASK_MONITORS; i++) { - if (task_monitor_slots[i] == false) { + if (!task_monitor_slots[i]) { task_monitor_slots[i] = true; return i; } } - WARN_ONCE(1, "RV task_monitor_count and slots are out of sync\n"); - - return -EINVAL; + return -EBUSY; } void rv_put_task_monitor_slot(int slot) @@ -199,10 +191,10 @@ void rv_put_task_monitor_slot(int slot) return; } - WARN_ONCE(!task_monitor_slots[slot], "RV releasing unused task_monitor_slots: %d\n", - slot); + if (WARN_ONCE(!task_monitor_slots[slot], + "RV releasing unused task monitor slot: %d\n", slot)) + return; - task_monitor_count--; task_monitor_slots[slot] = false; } -- cgit v1.2.3 From 655111f878a455f724e20122929cf2afa52b76e4 Mon Sep 17 00:00:00 2001 From: Jackie Liu Date: Wed, 15 Jul 2026 15:44:55 +0800 Subject: tracing: Propagate errors from remote event bulk updates remote_events_dir_enable_write() ignores the return value from trace_remote_enable_event(). If a remote rejects an event state change, the write therefore reports success even though the affected event remains in its previous state. Keep trying all events, but retain and return the first error. This matches __ftrace_set_clr_event_nolock(), which permits partial updates while notifying userspace when an operation fails. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260715074455.3897-1-liu.yun@linux.dev Fixes: 775cb093bc50 ("tracing: Add events/ root files to trace remotes") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Jackie Liu Reviewed-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace_remote.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index ef42d9c38b37..e6724f947170 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -1149,10 +1149,21 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us for (i = 0; i < remote->nr_events; i++) { struct remote_event *evt = &remote->events[i]; + int eret; - trace_remote_enable_event(remote, evt, enable); + eret = trace_remote_enable_event(remote, evt, enable); + /* + * Save the first error and return that. Some events + * may still have been enabled, but let the user + * know that something went wrong. + */ + if (!ret && eret) + ret = eret; } + if (ret) + return ret; + return count; } -- cgit v1.2.3 From c1d87e724ae55e781b7cc7ccafb34d9e668582b2 Mon Sep 17 00:00:00 2001 From: deepakraog Date: Wed, 15 Jul 2026 20:06:04 +0530 Subject: tracing: Fix resource leak on mmiotrace trace_pipe close The mmiotrace tracer was added May 12th 2008. At that time, resources created in pipe_open() could not be freed because there was not pipe_close function pointer of the tracer. The pipe_close function pointer was added in December 7th, 2009, but the mmiotrace tracer was not updated. mmio_pipe_open() allocates a header_iter and takes a pci_dev reference when trace_pipe is opened. mmio_close() frees them, but it was only wired to the tracer's .close callback. tracing_release_pipe() invokes .pipe_close, not .close, when the trace_pipe file is released. As a result, closing trace_pipe with the mmiotrace tracer active leaked the header_iter allocation and left a stale pci_dev reference. Set .pipe_close to mmio_close, matching how function_graph wires both callbacks to the same handler. Note, if the trace_pipe is read to completion, it will clean up the resources, but if one were to run: # head -n 1 /sys/kernel/tracing/trace_pipe VERSION 20070824 Over and over again, it would trigger a massive leak. Cc: stable@vger.kernel.org Fixes: c521efd1700a8 ("tracing: Add pipe_close interface) Link: https://patch.msgid.link/20260715143604.14481-1-gaikwad.dcg@gmail.com Signed-off-by: deepakraog Signed-off-by: Steven Rostedt --- kernel/trace/trace_mmiotrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_mmiotrace.c b/kernel/trace/trace_mmiotrace.c index 226cf66e0d68..20812e7f911f 100644 --- a/kernel/trace/trace_mmiotrace.c +++ b/kernel/trace/trace_mmiotrace.c @@ -109,7 +109,6 @@ static void mmio_pipe_open(struct trace_iterator *iter) iter->private = hiter; } -/* XXX: This is not called when the pipe is closed! */ static void mmio_close(struct trace_iterator *iter) { struct header_iter *hiter = iter->private; @@ -279,6 +278,7 @@ static struct tracer mmio_tracer __read_mostly = .start = mmio_trace_start, .pipe_open = mmio_pipe_open, .close = mmio_close, + .pipe_close = mmio_close, .read = mmio_read, .print_line = mmio_print_line, .noboot = true, -- cgit v1.2.3 From 42545589e36390e74a848c517a62eafaa6b8526c Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 09:21:19 +0200 Subject: rv/rtapp/sleep: Make the error more informative for user The rtapp/sleep monitor detects real-time tasks which go to sleep in an real-time-unsafe manner. If this happen, the monitor triggers a trace event in the sched_wakeup tracepoint's handler. However, the invoking context of that trace event is not the most informative, because of the stack trace of that event is the wakeup's code path which is not very helpful: 74.669317: rv:error_sleep: condvar[254]: violation detected ltl_validate+0x345 ([kernel.kallsyms]) handle_sched_wakeup+0x34 ([kernel.kallsyms]) ttwu_do_activate+0xff ([kernel.kallsyms]) sched_ttwu_pending+0x104 ([kernel.kallsyms]) __flush_smp_call_function_queue+0x15b ([kernel.kallsyms]) __sysvec_call_function_single+0x18 ([kernel.kallsyms]) sysvec_call_function_single+0x66 ([kernel.kallsyms]) asm_sysvec_call_function_single+0x1a ([kernel.kallsyms]) pv_native_safe_halt+0xf ([kernel.kallsyms]) default_idle+0x9 ([kernel.kallsyms]) default_idle_call+0x33 ([kernel.kallsyms]) do_idle+0x234 ([kernel.kallsyms]) cpu_startup_entry+0x24 ([kernel.kallsyms]) start_secondary+0xf8 ([kernel.kallsyms]) common_startup_64+0x13e ([kernel.kallsyms]) What would be much more valuable is the stack trace of the task itself. Instead of using the sched_wakeup tracepoint, use the sched_exit tracepoint. This makes the event happen in the task's context, making the stack trace far more informative for user: rv:error_sleep: condvar[254]: violation detected ltl_validate+0x345 ([kernel.kallsyms]) handle_sched_exit+0x39 ([kernel.kallsyms]) __schedule+0x80f ([kernel.kallsyms]) schedule+0x22 ([kernel.kallsyms]) futex_do_wait+0x33 ([kernel.kallsyms]) __futex_wait+0x8c ([kernel.kallsyms]) futex_wait+0x73 ([kernel.kallsyms]) do_futex+0xc6 ([kernel.kallsyms]) __x64_sys_futex+0x121 ([kernel.kallsyms]) do_syscall_64+0xf3 ([kernel.kallsyms]) entry_SYSCALL_64_after_hwframe+0x77 ([kernel.kallsyms]) __futex_abstimed_wait_common64+0xc6 (inlined) __futex_abstimed_wait_common+0xc6 (/usr/lib/x86_64-linux-gnu/libc.so.6) Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/d97b4b5c476e5792b6875ec9bbf8dc214f999516.1781852967.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 2 +- kernel/trace/rv/monitors/sleep/sleep.c | 10 +++++----- kernel/trace/rv/monitors/sleep/sleep.h | 14 +++++++------- tools/verification/models/rtapp/sleep.ltl | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) (limited to 'kernel/trace') diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index c8104eda924a..01656bf7080a 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -95,7 +95,7 @@ The monitor's specification is:: RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD) - and ((not WAKE) until RT_FRIENDLY_WAKE) + and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE) RT_VALID_SLEEP_REASON = FUTEX_WAIT or RT_FRIENDLY_NANOSLEEP diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index 8dfe5ec13e19..d6b677fab8f8 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -36,7 +36,7 @@ static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon) static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation) { ltl_atom_set(mon, LTL_SLEEP, false); - ltl_atom_set(mon, LTL_WAKE, false); + ltl_atom_set(mon, LTL_SCHEDULE_IN, false); ltl_atom_set(mon, LTL_ABORT_SLEEP, false); ltl_atom_set(mon, LTL_WOKEN_BY_HARDIRQ, false); ltl_atom_set(mon, LTL_WOKEN_BY_NMI, false); @@ -92,9 +92,9 @@ static void handle_sched_set_state(void *data, struct task_struct *task, int sta ltl_atom_pulse(task, LTL_ABORT_SLEEP, true); } -static void handle_sched_wakeup(void *data, struct task_struct *task) +static void handle_sched_exit(void *data, bool is_switch) { - ltl_atom_pulse(task, LTL_WAKE, true); + ltl_atom_pulse(current, LTL_SCHEDULE_IN, true); } static void handle_sched_waking(void *data, struct task_struct *task) @@ -200,7 +200,7 @@ static int enable_sleep(void) return retval; rv_attach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking); - rv_attach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup); + rv_attach_trace_probe("rtapp_sleep", sched_exit_tp, handle_sched_exit); rv_attach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state); rv_attach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin); rv_attach_trace_probe("rtapp_sleep", contention_end, handle_contention_end); @@ -213,7 +213,7 @@ static int enable_sleep(void) static void disable_sleep(void) { rv_detach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking); - rv_detach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup); + rv_detach_trace_probe("rtapp_sleep", sched_exit_tp, handle_sched_exit); rv_detach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state); rv_detach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin); rv_detach_trace_probe("rtapp_sleep", contention_end, handle_contention_end); diff --git a/kernel/trace/rv/monitors/sleep/sleep.h b/kernel/trace/rv/monitors/sleep/sleep.h index 95dc2727c059..403dc2852c52 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.h +++ b/kernel/trace/rv/monitors/sleep/sleep.h @@ -24,10 +24,10 @@ enum ltl_atom { LTL_NANOSLEEP_CLOCK_TAI, LTL_NANOSLEEP_TIMER_ABSTIME, LTL_RT, + LTL_SCHEDULE_IN, LTL_SLEEP, LTL_TASK_IS_MIGRATION, LTL_TASK_IS_RCU, - LTL_WAKE, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, LTL_WOKEN_BY_HARDIRQ, LTL_WOKEN_BY_NMI, @@ -50,10 +50,10 @@ static const char *ltl_atom_str(enum ltl_atom atom) "na_cl_ta", "na_ti_ab", "rt", - "sl", + "sch_in", + "sle", "ta_mi", "ta_rc", - "wak", "wo_eq_hi_pr", "wo_ha", "wo_nm", @@ -81,10 +81,10 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms); bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, mon->atoms); - bool wake = test_bit(LTL_WAKE, mon->atoms); bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms); bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms); bool sleep = test_bit(LTL_SLEEP, mon->atoms); + bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms); @@ -104,7 +104,7 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool val35 = woken_by_nmi || val34; bool val36 = woken_by_hardirq || val35; bool val14 = woken_by_equal_or_higher_prio || val36; - bool val13 = !wake; + bool val13 = !schedule_in; bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai; bool val27 = nanosleep_timer_abstime && val26; bool val18 = clock_nanosleep && val27; @@ -132,10 +132,10 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms); bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, mon->atoms); - bool wake = test_bit(LTL_WAKE, mon->atoms); bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms); bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms); bool sleep = test_bit(LTL_SLEEP, mon->atoms); + bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms); @@ -155,7 +155,7 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool val35 = woken_by_nmi || val34; bool val36 = woken_by_hardirq || val35; bool val14 = woken_by_equal_or_higher_prio || val36; - bool val13 = !wake; + bool val13 = !schedule_in; bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai; bool val27 = nanosleep_timer_abstime && val26; bool val18 = clock_nanosleep && val27; diff --git a/tools/verification/models/rtapp/sleep.ltl b/tools/verification/models/rtapp/sleep.ltl index 6f26c4810f78..464c84b9df87 100644 --- a/tools/verification/models/rtapp/sleep.ltl +++ b/tools/verification/models/rtapp/sleep.ltl @@ -1,7 +1,7 @@ RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD) - and ((not WAKE) until RT_FRIENDLY_WAKE) + and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE) RT_VALID_SLEEP_REASON = FUTEX_WAIT or RT_FRIENDLY_NANOSLEEP -- cgit v1.2.3 From 8fc4e16c75c012c93721a5b87f35d9f7e198dd01 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 09:21:20 +0200 Subject: rv/rtapp/sleep: Update nanosleep rule CLOCK_REALTIME is the only clock that often is misused in real-time applications. The other clocks either are safe for real-time uses (CLOCK_TAI, CLOCK_MONOTONIC, CLOCK_BOOTTIME) or are unlikely to be misused (CLOCK_AUX, CLOCK_PROCESS_CPUTIME_ID). Update the monitor to only warn about CLOCK_REALTIME. While at it, update the out-of-sync documentation. Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/c7ceb5c6263ee8f43a2676acae669cf486b0d903.1781852967.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 17 +++++----- kernel/trace/rv/monitors/sleep/sleep.c | 12 +++---- kernel/trace/rv/monitors/sleep/sleep.h | 52 ++++++++++++++----------------- tools/verification/models/rtapp/sleep.ltl | 2 +- 4 files changed, 39 insertions(+), 44 deletions(-) (limited to 'kernel/trace') diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index 01656bf7080a..570be67a8f3b 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -51,12 +51,13 @@ The `sleep` monitor reports real-time threads sleeping in a manner that may cause undesirable latency. Real-time applications should only put a real-time thread to sleep for one of the following reasons: - - Cyclic work: real-time thread sleeps waiting for the next cycle. For this - case, only the `clock_nanosleep` syscall should be used with `TIMER_ABSTIME` - (to avoid time drift) and `CLOCK_MONOTONIC` (to avoid the clock being - changed). No other method is safe for real-time. For example, threads - waiting for timerfd can be woken by softirq which provides no real-time - guarantee. + - Cyclic work: real-time thread sleeps waiting for the next + cycle. For this case, only the `clock_nanosleep` syscall should be + used with `TIMER_ABSTIME` (to avoid time drift). Additionally, + `CLOCK_REALTIME` should not be used (to avoid the clock being + changed). No other method is safe for real-time. For example, + threads waiting for timerfd can be woken by softirq which provides + no real-time guarantee. - Real-time thread waiting for something to happen (e.g. another thread releasing shared resources, or a completion signal from another thread). In this case, only futexes (FUTEX_LOCK_PI, FUTEX_LOCK_PI2 or one of @@ -99,14 +100,16 @@ The monitor's specification is:: RT_VALID_SLEEP_REASON = FUTEX_WAIT or RT_FRIENDLY_NANOSLEEP + or EPOLL_WAIT RT_FRIENDLY_NANOSLEEP = CLOCK_NANOSLEEP and NANOSLEEP_TIMER_ABSTIME - and NANOSLEEP_CLOCK_MONOTONIC + and not NANOSLEEP_CLOCK_REALTIME RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO or WOKEN_BY_HARDIRQ or WOKEN_BY_NMI + or ABORT_SLEEP or KTHREAD_SHOULD_STOP ALLOWLIST = BLOCK_ON_RT_MUTEX diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index d6b677fab8f8..638be7d8747f 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -44,8 +44,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo if (task_creation) { ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false); + ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false); ltl_atom_set(mon, LTL_FUTEX_WAIT, false); @@ -60,8 +59,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo /* kernel tasks do not do syscall */ ltl_atom_set(mon, LTL_FUTEX_WAIT, false); ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false); + ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false); ltl_atom_set(mon, LTL_EPOLL_WAIT, false); @@ -136,8 +134,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id) case __NR_clock_nanosleep_time64: #endif syscall_get_arguments(current, regs, args); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, args[0] == CLOCK_MONOTONIC); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, args[0] == CLOCK_TAI); + ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, args[0] == CLOCK_REALTIME); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, args[1] == TIMER_ABSTIME); ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, true); break; @@ -178,8 +175,7 @@ static void handle_sys_exit(void *data, struct pt_regs *regs, long ret) ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); ltl_atom_set(mon, LTL_FUTEX_WAIT, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false); + ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_EPOLL_WAIT, false); ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false); diff --git a/kernel/trace/rv/monitors/sleep/sleep.h b/kernel/trace/rv/monitors/sleep/sleep.h index 403dc2852c52..2fe2ec7edae8 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.h +++ b/kernel/trace/rv/monitors/sleep/sleep.h @@ -20,8 +20,7 @@ enum ltl_atom { LTL_FUTEX_WAIT, LTL_KERNEL_THREAD, LTL_KTHREAD_SHOULD_STOP, - LTL_NANOSLEEP_CLOCK_MONOTONIC, - LTL_NANOSLEEP_CLOCK_TAI, + LTL_NANOSLEEP_CLOCK_REALTIME, LTL_NANOSLEEP_TIMER_ABSTIME, LTL_RT, LTL_SCHEDULE_IN, @@ -46,8 +45,7 @@ static const char *ltl_atom_str(enum ltl_atom atom) "fu_wa", "ker_th", "kth_sh_st", - "na_cl_mo", - "na_cl_ta", + "na_cl_re", "na_ti_ab", "rt", "sch_in", @@ -87,8 +85,7 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); - bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms); - bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms); + bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms); bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms); bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms); bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms); @@ -97,17 +94,17 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms); bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms); - bool val42 = task_is_rcu || task_is_migration; - bool val43 = futex_lock_pi || val42; - bool val5 = block_on_rt_mutex || val43; - bool val34 = abort_sleep || kthread_should_stop; - bool val35 = woken_by_nmi || val34; - bool val36 = woken_by_hardirq || val35; - bool val14 = woken_by_equal_or_higher_prio || val36; + bool val41 = task_is_rcu || task_is_migration; + bool val42 = futex_lock_pi || val41; + bool val5 = block_on_rt_mutex || val42; + bool val33 = abort_sleep || kthread_should_stop; + bool val34 = woken_by_nmi || val33; + bool val35 = woken_by_hardirq || val34; + bool val14 = woken_by_equal_or_higher_prio || val35; bool val13 = !schedule_in; - bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai; - bool val27 = nanosleep_timer_abstime && val26; - bool val18 = clock_nanosleep && val27; + bool val25 = !nanosleep_clock_realtime; + bool val26 = nanosleep_timer_abstime && val25; + bool val18 = clock_nanosleep && val26; bool val20 = val18 || epoll_wait; bool val9 = futex_wait || val20; bool val11 = val9 || kernel_thread; @@ -138,8 +135,7 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); - bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms); - bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms); + bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms); bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms); bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms); bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms); @@ -148,17 +144,17 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms); bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms); - bool val42 = task_is_rcu || task_is_migration; - bool val43 = futex_lock_pi || val42; - bool val5 = block_on_rt_mutex || val43; - bool val34 = abort_sleep || kthread_should_stop; - bool val35 = woken_by_nmi || val34; - bool val36 = woken_by_hardirq || val35; - bool val14 = woken_by_equal_or_higher_prio || val36; + bool val41 = task_is_rcu || task_is_migration; + bool val42 = futex_lock_pi || val41; + bool val5 = block_on_rt_mutex || val42; + bool val33 = abort_sleep || kthread_should_stop; + bool val34 = woken_by_nmi || val33; + bool val35 = woken_by_hardirq || val34; + bool val14 = woken_by_equal_or_higher_prio || val35; bool val13 = !schedule_in; - bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai; - bool val27 = nanosleep_timer_abstime && val26; - bool val18 = clock_nanosleep && val27; + bool val25 = !nanosleep_clock_realtime; + bool val26 = nanosleep_timer_abstime && val25; + bool val18 = clock_nanosleep && val26; bool val20 = val18 || epoll_wait; bool val9 = futex_wait || val20; bool val11 = val9 || kernel_thread; diff --git a/tools/verification/models/rtapp/sleep.ltl b/tools/verification/models/rtapp/sleep.ltl index 464c84b9df87..5923e58d7810 100644 --- a/tools/verification/models/rtapp/sleep.ltl +++ b/tools/verification/models/rtapp/sleep.ltl @@ -9,7 +9,7 @@ RT_VALID_SLEEP_REASON = FUTEX_WAIT RT_FRIENDLY_NANOSLEEP = CLOCK_NANOSLEEP and NANOSLEEP_TIMER_ABSTIME - and (NANOSLEEP_CLOCK_MONOTONIC or NANOSLEEP_CLOCK_TAI) + and not NANOSLEEP_CLOCK_REALTIME RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO or WOKEN_BY_HARDIRQ -- cgit v1.2.3 From 28e68d3cdc8adf6215e6334222b710d07fb1c1c5 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 09:21:21 +0200 Subject: rv/rtapp/sleep: Stop monitoring kernel threads The rtapp/sleep monitor's primary purpose is detecting common mistakes with user-space real-time design. Monitoring real-time issues with kernel threads is a bonus. However, accomodating kernel threads complicates the monitor due to the edge cases which is seen by the monitor as lower-priority task waking higher-priority task: - kthread_stop() wakes up the task in order to stop it. - The rcu thread and migration thread can be woken by any task. - The ktimerd thread is woken near the end of irq_exit_rcu(), where the preempt counter is "broken" and falsely says this is task context. This requires the monitor to use the hardirq_context flag instead of the preempt counter. Beside complicating the monitor, the final case also requires enabling CONFIG_TRACE_IRQFLAGS (so that "hardirq_context" can be used). This adds overhead to the kernel even when the monitor is not active. This may be an obstacle to enabling this monitor in distros' kernels. Furthermore, kernel threads usually are started before the monitor is enabled. Consequently, the threads' states (i.o.w. the monitor's atomic propositions for the threads) are not fully known to the monitor. As a result, the kernel threads mostly cannot be monitored. Overall, the downsides of accomodating kernel threads outweights the benefits. Thus, exclude kernel threads to simplify the monitor. Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/eec2ca5224bcdacc45b8e1eb2f0e68109e1cae7a.1781852967.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 22 ++----- kernel/trace/rv/monitors/sleep/Kconfig | 1 - kernel/trace/rv/monitors/sleep/sleep.c | 39 +---------- kernel/trace/rv/monitors/sleep/sleep.h | 104 +++++++++++++----------------- tools/verification/models/rtapp/sleep.ltl | 7 +- 5 files changed, 54 insertions(+), 119 deletions(-) (limited to 'kernel/trace') diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index 570be67a8f3b..502d3ea412eb 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -93,9 +93,9 @@ assessment. The monitor's specification is:: - RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) + RULE = always ((RT and SLEEP and USER_THREAD) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) - RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD) + RT_FRIENDLY_SLEEP = RT_VALID_SLEEP_REASON and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE) RT_VALID_SLEEP_REASON = FUTEX_WAIT @@ -110,23 +110,13 @@ The monitor's specification is:: or WOKEN_BY_HARDIRQ or WOKEN_BY_NMI or ABORT_SLEEP - or KTHREAD_SHOULD_STOP ALLOWLIST = BLOCK_ON_RT_MUTEX or FUTEX_LOCK_PI - or TASK_IS_RCU - or TASK_IS_MIGRATION - -Beside the scenarios described above, this specification also handle some -special cases: - - - `KERNEL_THREAD`: kernel tasks do not have any pattern that can be recognized - as valid real-time sleeping reasons. Therefore sleeping reason is not - checked for kernel tasks. - - `KTHREAD_SHOULD_STOP`: a non-real-time thread may stop a real-time kernel - thread by waking it and waiting for it to exit (`kthread_stop()`). This - wakeup is safe for real-time. - - `ALLOWLIST`: to handle known false positives with the kernel. + +Beside the scenarios described above, this specification also defines an allow list +to handle some special cases: + - `BLOCK_ON_RT_MUTEX` is included in the allowlist due to its implementation. In the release path of rt_mutex, a boosted task is de-boosted before waking the rt_mutex's waiter. Consequently, the monitor may see a real-time-unsafe diff --git a/kernel/trace/rv/monitors/sleep/Kconfig b/kernel/trace/rv/monitors/sleep/Kconfig index 6b7a122e7b47..d6ec3e9a91b6 100644 --- a/kernel/trace/rv/monitors/sleep/Kconfig +++ b/kernel/trace/rv/monitors/sleep/Kconfig @@ -5,7 +5,6 @@ config RV_MON_SLEEP select RV_LTL_MONITOR depends on HAVE_SYSCALL_TRACEPOINTS depends on RV_MON_RTAPP - select TRACE_IRQFLAGS default y select LTL_MON_EVENTS_ID bool "sleep monitor" diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index 638be7d8747f..aa5a984853b5 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -43,7 +43,6 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo ltl_atom_set(mon, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, false); if (task_creation) { - ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false); ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false); @@ -53,33 +52,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false); } - if (task->flags & PF_KTHREAD) { - ltl_atom_set(mon, LTL_KERNEL_THREAD, true); - - /* kernel tasks do not do syscall */ - ltl_atom_set(mon, LTL_FUTEX_WAIT, false); - ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); - ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); - ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false); - ltl_atom_set(mon, LTL_EPOLL_WAIT, false); - - if (strstarts(task->comm, "migration/")) - ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, true); - else - ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false); - - if (strstarts(task->comm, "rcu")) - ltl_atom_set(mon, LTL_TASK_IS_RCU, true); - else - ltl_atom_set(mon, LTL_TASK_IS_RCU, false); - } else { - ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false); - ltl_atom_set(mon, LTL_KERNEL_THREAD, false); - ltl_atom_set(mon, LTL_TASK_IS_RCU, false); - ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false); - } - + ltl_atom_set(mon, LTL_USER_THREAD, !(task->flags & PF_KTHREAD)); } static void handle_sched_set_state(void *data, struct task_struct *task, int state) @@ -97,7 +70,7 @@ static void handle_sched_exit(void *data, bool is_switch) static void handle_sched_waking(void *data, struct task_struct *task) { - if (this_cpu_read(hardirq_context)) { + if (in_hardirq()) { ltl_atom_pulse(task, LTL_WOKEN_BY_HARDIRQ, true); } else if (in_task()) { if (current->prio <= task->prio) @@ -181,12 +154,6 @@ static void handle_sys_exit(void *data, struct pt_regs *regs, long ret) ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false); } -static void handle_kthread_stop(void *data, struct task_struct *task) -{ - /* FIXME: this could race with other tracepoint handlers */ - ltl_atom_update(task, LTL_KTHREAD_SHOULD_STOP, true); -} - static int enable_sleep(void) { int retval; @@ -200,7 +167,6 @@ static int enable_sleep(void) rv_attach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state); rv_attach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin); rv_attach_trace_probe("rtapp_sleep", contention_end, handle_contention_end); - rv_attach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop); rv_attach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter); rv_attach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit); return 0; @@ -213,7 +179,6 @@ static void disable_sleep(void) rv_detach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state); rv_detach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin); rv_detach_trace_probe("rtapp_sleep", contention_end, handle_contention_end); - rv_detach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop); rv_detach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter); rv_detach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit); diff --git a/kernel/trace/rv/monitors/sleep/sleep.h b/kernel/trace/rv/monitors/sleep/sleep.h index 2fe2ec7edae8..44e593f41e6a 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.h +++ b/kernel/trace/rv/monitors/sleep/sleep.h @@ -18,15 +18,12 @@ enum ltl_atom { LTL_EPOLL_WAIT, LTL_FUTEX_LOCK_PI, LTL_FUTEX_WAIT, - LTL_KERNEL_THREAD, - LTL_KTHREAD_SHOULD_STOP, LTL_NANOSLEEP_CLOCK_REALTIME, LTL_NANOSLEEP_TIMER_ABSTIME, LTL_RT, LTL_SCHEDULE_IN, LTL_SLEEP, - LTL_TASK_IS_MIGRATION, - LTL_TASK_IS_RCU, + LTL_USER_THREAD, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, LTL_WOKEN_BY_HARDIRQ, LTL_WOKEN_BY_NMI, @@ -43,15 +40,12 @@ static const char *ltl_atom_str(enum ltl_atom atom) "ep_wa", "fu_lo_pi", "fu_wa", - "ker_th", - "kth_sh_st", "na_cl_re", "na_ti_ab", "rt", "sch_in", "sle", - "ta_mi", - "ta_rc", + "us_th", "wo_eq_hi_pr", "wo_ha", "wo_nm", @@ -79,46 +73,41 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms); bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, mon->atoms); - bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms); - bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms); + bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms); bool sleep = test_bit(LTL_SLEEP, mon->atoms); bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms); - bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms); - bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms); bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms); bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms); bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms); bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms); bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms); - bool val41 = task_is_rcu || task_is_migration; - bool val42 = futex_lock_pi || val41; - bool val5 = block_on_rt_mutex || val42; - bool val33 = abort_sleep || kthread_should_stop; - bool val34 = woken_by_nmi || val33; - bool val35 = woken_by_hardirq || val34; - bool val14 = woken_by_equal_or_higher_prio || val35; + bool val7 = block_on_rt_mutex || futex_lock_pi; + bool val32 = woken_by_nmi || abort_sleep; + bool val33 = woken_by_hardirq || val32; + bool val14 = woken_by_equal_or_higher_prio || val33; bool val13 = !schedule_in; bool val25 = !nanosleep_clock_realtime; bool val26 = nanosleep_timer_abstime && val25; bool val18 = clock_nanosleep && val26; bool val20 = val18 || epoll_wait; - bool val9 = futex_wait || val20; - bool val11 = val9 || kernel_thread; + bool val11 = futex_wait || val20; + bool val3 = !user_thread; bool val2 = !sleep; + bool val4 = val2 || val3; bool val1 = !rt; - bool val3 = val1 || val2; + bool val5 = val1 || val4; - if (val3) + if (val5) __set_bit(S0, mon->states); if (val11 && val13) __set_bit(S1, mon->states); if (val11 && val14) __set_bit(S4, mon->states); - if (val5) + if (val7) __set_bit(S5, mon->states); } @@ -129,130 +118,125 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms); bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, mon->atoms); - bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms); - bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms); + bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms); bool sleep = test_bit(LTL_SLEEP, mon->atoms); bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms); - bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms); - bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms); bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms); bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms); bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms); bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms); bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms); - bool val41 = task_is_rcu || task_is_migration; - bool val42 = futex_lock_pi || val41; - bool val5 = block_on_rt_mutex || val42; - bool val33 = abort_sleep || kthread_should_stop; - bool val34 = woken_by_nmi || val33; - bool val35 = woken_by_hardirq || val34; - bool val14 = woken_by_equal_or_higher_prio || val35; + bool val7 = block_on_rt_mutex || futex_lock_pi; + bool val32 = woken_by_nmi || abort_sleep; + bool val33 = woken_by_hardirq || val32; + bool val14 = woken_by_equal_or_higher_prio || val33; bool val13 = !schedule_in; bool val25 = !nanosleep_clock_realtime; bool val26 = nanosleep_timer_abstime && val25; bool val18 = clock_nanosleep && val26; bool val20 = val18 || epoll_wait; - bool val9 = futex_wait || val20; - bool val11 = val9 || kernel_thread; + bool val11 = futex_wait || val20; + bool val3 = !user_thread; bool val2 = !sleep; + bool val4 = val2 || val3; bool val1 = !rt; - bool val3 = val1 || val2; + bool val5 = val1 || val4; switch (state) { case S0: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; case S1: if (val11 && val13) __set_bit(S1, next); - if (val13 && val3) + if (val13 && val5) __set_bit(S2, next); - if (val14 && val3) + if (val14 && val5) __set_bit(S3, next); if (val11 && val14) __set_bit(S4, next); - if (val13 && val5) + if (val13 && val7) __set_bit(S6, next); - if (val14 && val5) + if (val14 && val7) __set_bit(S7, next); break; case S2: if (val11 && val13) __set_bit(S1, next); - if (val13 && val3) + if (val13 && val5) __set_bit(S2, next); - if (val14 && val3) + if (val14 && val5) __set_bit(S3, next); if (val11 && val14) __set_bit(S4, next); - if (val13 && val5) + if (val13 && val7) __set_bit(S6, next); - if (val14 && val5) + if (val14 && val7) __set_bit(S7, next); break; case S3: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; case S4: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; case S5: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; case S6: if (val11 && val13) __set_bit(S1, next); - if (val13 && val3) + if (val13 && val5) __set_bit(S2, next); - if (val14 && val3) + if (val14 && val5) __set_bit(S3, next); if (val11 && val14) __set_bit(S4, next); - if (val13 && val5) + if (val13 && val7) __set_bit(S6, next); - if (val14 && val5) + if (val14 && val7) __set_bit(S7, next); break; case S7: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; } diff --git a/tools/verification/models/rtapp/sleep.ltl b/tools/verification/models/rtapp/sleep.ltl index 5923e58d7810..4d78fdd204c0 100644 --- a/tools/verification/models/rtapp/sleep.ltl +++ b/tools/verification/models/rtapp/sleep.ltl @@ -1,6 +1,6 @@ -RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) +RULE = always ((RT and SLEEP and USER_THREAD) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) -RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD) +RT_FRIENDLY_SLEEP = RT_VALID_SLEEP_REASON and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE) RT_VALID_SLEEP_REASON = FUTEX_WAIT @@ -15,9 +15,6 @@ RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO or WOKEN_BY_HARDIRQ or WOKEN_BY_NMI or ABORT_SLEEP - or KTHREAD_SHOULD_STOP ALLOWLIST = BLOCK_ON_RT_MUTEX or FUTEX_LOCK_PI - or TASK_IS_RCU - or TASK_IS_MIGRATION -- cgit v1.2.3 From 6fdaab4e163609772bcc617b052a62435e89d77c Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 09:21:22 +0200 Subject: rv/rtapp: Add wakeup monitor Add a wakeup monitor to detect a lower-priority task waking up a higher-priority task. The rtapp/sleep monitor already detects this. However, that monitor triggers an error in the context of the wakee task and user only gets the stacktrace of that task. It is also extremely useful to get the stacktrace of the waker task, which this monitor offers. In other words, this monitor complements the rtapp/sleep monitor. Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/ba5658fa13e49ada466b84a2c211f233037180b5.1781852967.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 20 ++++ kernel/trace/rv/Kconfig | 1 + kernel/trace/rv/Makefile | 1 + kernel/trace/rv/monitors/rtapp/Kconfig | 2 +- kernel/trace/rv/monitors/wakeup/Kconfig | 16 +++ kernel/trace/rv/monitors/wakeup/wakeup.c | 153 +++++++++++++++++++++++++ kernel/trace/rv/monitors/wakeup/wakeup.h | 92 +++++++++++++++ kernel/trace/rv/monitors/wakeup/wakeup_trace.h | 14 +++ kernel/trace/rv/rv_trace.h | 1 + tools/verification/models/rtapp/wakeup.ltl | 5 + 10 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 kernel/trace/rv/monitors/wakeup/Kconfig create mode 100644 kernel/trace/rv/monitors/wakeup/wakeup.c create mode 100644 kernel/trace/rv/monitors/wakeup/wakeup.h create mode 100644 kernel/trace/rv/monitors/wakeup/wakeup_trace.h create mode 100644 tools/verification/models/rtapp/wakeup.ltl (limited to 'kernel/trace') diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index 502d3ea412eb..238b59395ff5 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -124,3 +124,23 @@ to handle some special cases: real-time-safe because preemption is disabled for the duration. - `FUTEX_LOCK_PI` is included in the allowlist for the same reason as `BLOCK_ON_RT_MUTEX`. + +Monitor wakeup +++++++++++++++ + +The `wakeup` monitor reports real-time threads being woken by lower-priority threads, +which is a hint of priority inversion. Its specification is:: + + RULE = always (((RT and USER_THREAD) imply + (not (WOKEN_BY_LOWER_PRIO or WOKEN_BY_SOFTIRQ)) or ALLOWLIST)) + + ALLOWLIST = BLOCK_ON_RT_MUTEX + or FUTEX_LOCK_PI + +The `sleep` monitor already reports this type of problem. The difference is the +context in which the problem is reported. While the `sleep` monitor reports the problem +in the context of the wakee, this `wakeup` monitor reports the problem in the context of +the waker. This monitor complement the `sleep` monitor, giving user better +understanding of the issue. For instance, to debug a lower-priority task waking a +higher-priority task scenario, user can enable both `wakeup` monitor and `sleep` +monitor to get the stack traces of both tasks. diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig index 3884b14df375..4d3a14a0bac2 100644 --- a/kernel/trace/rv/Kconfig +++ b/kernel/trace/rv/Kconfig @@ -76,6 +76,7 @@ source "kernel/trace/rv/monitors/opid/Kconfig" source "kernel/trace/rv/monitors/rtapp/Kconfig" source "kernel/trace/rv/monitors/pagefault/Kconfig" source "kernel/trace/rv/monitors/sleep/Kconfig" +source "kernel/trace/rv/monitors/wakeup/Kconfig" # Add new rtapp monitors here source "kernel/trace/rv/monitors/stall/Kconfig" diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile index 94498da35b37..c2c0e4142eb4 100644 --- a/kernel/trace/rv/Makefile +++ b/kernel/trace/rv/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_RV_MON_OPID) += monitors/opid/opid.o obj-$(CONFIG_RV_MON_STALL) += monitors/stall/stall.o obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o +obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o # Add new monitors here obj-$(CONFIG_RV_REACTORS) += rv_reactors.o obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o diff --git a/kernel/trace/rv/monitors/rtapp/Kconfig b/kernel/trace/rv/monitors/rtapp/Kconfig index 1ce9370a9ba8..1fcd7a400ded 100644 --- a/kernel/trace/rv/monitors/rtapp/Kconfig +++ b/kernel/trace/rv/monitors/rtapp/Kconfig @@ -1,6 +1,6 @@ config RV_MON_RTAPP depends on RV - depends on RV_PER_TASK_MONITORS >= 2 + depends on RV_PER_TASK_MONITORS >= 3 bool "rtapp monitor" help Collection of monitors to check for common problems with real-time diff --git a/kernel/trace/rv/monitors/wakeup/Kconfig b/kernel/trace/rv/monitors/wakeup/Kconfig new file mode 100644 index 000000000000..98f618f0e01d --- /dev/null +++ b/kernel/trace/rv/monitors/wakeup/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_WAKEUP + depends on RV + depends on RV_MON_RTAPP + depends on HAVE_SYSCALL_TRACEPOINTS + default y + select LTL_MON_EVENTS_ID + bool "wakeup monitor" + help + This monitor detects a lower-priority task waking up a + higher-priority task. The RV_MON_SLEEP monitor already + detects this case, but this monitor detects in the context + of the waker task instead. This and RV_MON_SLEEP can be + enabled together to get the stacktrace of both the waker + task and the wakee task. diff --git a/kernel/trace/rv/monitors/wakeup/wakeup.c b/kernel/trace/rv/monitors/wakeup/wakeup.c new file mode 100644 index 000000000000..01b47416f24e --- /dev/null +++ b/kernel/trace/rv/monitors/wakeup/wakeup.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "wakeup" + +#include +#include +#include +#include + +#include +#include + + +#ifndef __NR_futex +#define __NR_futex (-__COUNTER__) +#endif +#ifndef __NR_futex_time64 +#define __NR_futex_time64 (-__COUNTER__) +#endif + +#include "wakeup.h" +#include + +static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon) +{ + /* + * This includes "actual" real-time tasks and also PI-boosted + * tasks. A task being PI-boosted means it is blocking an "actual" + * real-task, therefore it should also obey the monitor's rule, + * otherwise the "actual" real-task may be delayed. + */ + ltl_atom_set(mon, LTL_RT, rt_or_dl_task(task)); +} + +static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation) +{ + ltl_atom_set(mon, LTL_WOKEN_BY_LOWER_PRIO, false); + ltl_atom_set(mon, LTL_WOKEN_BY_SOFTIRQ, false); + + if (task_creation) { + ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false); + ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); + } + + ltl_atom_set(mon, LTL_USER_THREAD, !(task->flags & PF_KTHREAD)); +} + +static void handle_sched_waking(void *data, struct task_struct *task) +{ + if (in_task()) { + if (current->prio > task->prio) + ltl_atom_pulse(task, LTL_WOKEN_BY_LOWER_PRIO, true); + } else if (in_serving_softirq()) { + ltl_atom_pulse(task, LTL_WOKEN_BY_SOFTIRQ, true); + } +} + +static void handle_contention_begin(void *data, void *lock, unsigned int flags) +{ + if (flags & LCB_F_RT) + ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true); +} + +static void handle_contention_end(void *data, void *lock, int ret) +{ + ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false); +} + +static void handle_sys_enter(void *data, struct pt_regs *regs, long id) +{ + unsigned long args[6]; + int op, cmd; + + switch (id) { + case __NR_futex: + case __NR_futex_time64: + syscall_get_arguments(current, regs, args); + op = args[1]; + cmd = op & FUTEX_CMD_MASK; + + switch (cmd) { + case FUTEX_LOCK_PI: + case FUTEX_LOCK_PI2: + ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true); + break; + } + break; + } +} + +static void handle_sys_exit(void *data, struct pt_regs *regs, long ret) +{ + ltl_atom_update(current, LTL_FUTEX_LOCK_PI, false); +} + +static int enable_wakeup(void) +{ + int retval; + + retval = ltl_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("rtapp_wakeup", sched_waking, handle_sched_waking); + rv_attach_trace_probe("rtapp_wakeup", contention_begin, handle_contention_begin); + rv_attach_trace_probe("rtapp_wakeup", contention_end, handle_contention_end); + rv_attach_trace_probe("rtapp_wakeup", sys_enter, handle_sys_enter); + rv_attach_trace_probe("rtapp_wakeup", sys_exit, handle_sys_exit); + + return 0; +} + +static void disable_wakeup(void) +{ + rv_detach_trace_probe("rtapp_wakeup", sched_waking, handle_sched_waking); + rv_detach_trace_probe("rtapp_wakeup", contention_begin, handle_contention_begin); + rv_detach_trace_probe("rtapp_wakeup", contention_end, handle_contention_end); + rv_detach_trace_probe("rtapp_wakeup", sys_enter, handle_sys_enter); + rv_detach_trace_probe("rtapp_wakeup", sys_exit, handle_sys_exit); + + ltl_monitor_destroy(); +} + +static struct rv_monitor rv_wakeup = { + .name = "wakeup", + .description = "Monitor that real-time tasks are not woken by lower-priority tasks", + .enable = enable_wakeup, + .disable = disable_wakeup, +}; + +static int __init register_wakeup(void) +{ + return rv_register_monitor(&rv_wakeup, &rv_rtapp); +} + +static void __exit unregister_wakeup(void) +{ + rv_unregister_monitor(&rv_wakeup); +} + +module_init(register_wakeup); +module_exit(unregister_wakeup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nam Cao "); +MODULE_DESCRIPTION("Monitor that real-time tasks are not woken by lower-priority tasks"); diff --git a/kernel/trace/rv/monitors/wakeup/wakeup.h b/kernel/trace/rv/monitors/wakeup/wakeup.h new file mode 100644 index 000000000000..6f80da64e0e1 --- /dev/null +++ b/kernel/trace/rv/monitors/wakeup/wakeup.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * C implementation of Buchi automaton, automatically generated by + * tools/verification/rvgen from the linear temporal logic specification. + * For further information, see kernel documentation: + * Documentation/trace/rv/linear_temporal_logic.rst + */ + +#include + +#define MONITOR_NAME wakeup + +enum ltl_atom { + LTL_BLOCK_ON_RT_MUTEX, + LTL_FUTEX_LOCK_PI, + LTL_RT, + LTL_USER_THREAD, + LTL_WOKEN_BY_LOWER_PRIO, + LTL_WOKEN_BY_SOFTIRQ, + LTL_NUM_ATOM +}; +static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM); + +static const char *ltl_atom_str(enum ltl_atom atom) +{ + static const char *const names[] = { + "bl_on_rt_mu", + "fu_lo_pi", + "rt", + "us_th", + "wo_lo_pr", + "wo_so", + }; + + return names[atom]; +} + +enum ltl_buchi_state { + S0, + RV_NUM_BA_STATES +}; +static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES); + +static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) +{ + bool woken_by_softirq = test_bit(LTL_WOKEN_BY_SOFTIRQ, mon->atoms); + bool woken_by_lower_prio = test_bit(LTL_WOKEN_BY_LOWER_PRIO, mon->atoms); + bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms); + bool rt = test_bit(LTL_RT, mon->atoms); + bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms); + bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); + bool val9 = block_on_rt_mutex || futex_lock_pi; + bool val6 = !woken_by_softirq; + bool val5 = !woken_by_lower_prio; + bool val8 = val5 && val6; + bool val10 = val8 || val9; + bool val3 = !user_thread; + bool val2 = !rt; + bool val4 = val2 || val3; + bool val11 = val4 || val10; + + if (val11) + __set_bit(S0, mon->states); +} + +static void +ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next) +{ + bool woken_by_softirq = test_bit(LTL_WOKEN_BY_SOFTIRQ, mon->atoms); + bool woken_by_lower_prio = test_bit(LTL_WOKEN_BY_LOWER_PRIO, mon->atoms); + bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms); + bool rt = test_bit(LTL_RT, mon->atoms); + bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms); + bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); + bool val9 = block_on_rt_mutex || futex_lock_pi; + bool val6 = !woken_by_softirq; + bool val5 = !woken_by_lower_prio; + bool val8 = val5 && val6; + bool val10 = val8 || val9; + bool val3 = !user_thread; + bool val2 = !rt; + bool val4 = val2 || val3; + bool val11 = val4 || val10; + + switch (state) { + case S0: + if (val11) + __set_bit(S0, next); + break; + } +} diff --git a/kernel/trace/rv/monitors/wakeup/wakeup_trace.h b/kernel/trace/rv/monitors/wakeup/wakeup_trace.h new file mode 100644 index 000000000000..7e056183f920 --- /dev/null +++ b/kernel/trace/rv/monitors/wakeup/wakeup_trace.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_WAKEUP +DEFINE_EVENT(event_ltl_monitor_id, event_wakeup, + TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next), + TP_ARGS(task, states, atoms, next)); +DEFINE_EVENT(error_ltl_monitor_id, error_wakeup, + TP_PROTO(struct task_struct *task), + TP_ARGS(task)); +#endif /* CONFIG_RV_MON_WAKEUP */ diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h index 9622c269789c..2f8a932432c9 100644 --- a/kernel/trace/rv/rv_trace.h +++ b/kernel/trace/rv/rv_trace.h @@ -241,6 +241,7 @@ DECLARE_EVENT_CLASS(error_ltl_monitor_id, ); #include #include +#include // Add new monitors based on CONFIG_LTL_MON_EVENTS_ID here #endif /* CONFIG_LTL_MON_EVENTS_ID */ diff --git a/tools/verification/models/rtapp/wakeup.ltl b/tools/verification/models/rtapp/wakeup.ltl new file mode 100644 index 000000000000..a5d63ca0811a --- /dev/null +++ b/tools/verification/models/rtapp/wakeup.ltl @@ -0,0 +1,5 @@ +RULE = always (((RT and USER_THREAD) imply + (not (WOKEN_BY_LOWER_PRIO or WOKEN_BY_SOFTIRQ)) or ALLOWLIST)) + +ALLOWLIST = BLOCK_ON_RT_MUTEX + or FUTEX_LOCK_PI -- cgit v1.2.3 From 144f29e85702234b23d2a62abf723e6a17eb5427 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Tue, 21 Jul 2026 21:11:43 -0400 Subject: tracing: Fix mmiotrace possible NULL dereferencing of hiter->dev If the mmio_pipe_open() fails to find a PCI device, the hiter->dev will be assigned to NULL. The mmiotrace read() function dereferences the hiter->dev if hiter exists. Change the test of the read to not only check hiter being NULL, but also the hiter->dev before dereferencing it. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260721211143.36dbd559@gandalf.local.home Fixes: f984b51e0779 ("ftrace: add mmiotrace plugin") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260715143604.14481-1-gaikwad.dcg%40gmail.com Signed-off-by: Steven Rostedt --- kernel/trace/trace_mmiotrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_mmiotrace.c b/kernel/trace/trace_mmiotrace.c index 20812e7f911f..b88b8d9923ad 100644 --- a/kernel/trace/trace_mmiotrace.c +++ b/kernel/trace/trace_mmiotrace.c @@ -145,7 +145,7 @@ static ssize_t mmio_read(struct trace_iterator *iter, struct file *filp, goto print_out; } - if (!hiter) + if (!hiter || !hiter->dev) return 0; mmio_print_pcidev(s, hiter->dev); -- cgit v1.2.3 From b4eb07bde606c2096b24252be589e735eff6d413 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 17 Jul 2026 11:51:49 +0900 Subject: tracing: Fix union collision of module and refcnt for dynamic events In 'struct trace_event_call', the 'module' pointer and the 'refcnt' atomic variable share the same memory space in a union. For dynamic events, the union member is 'refcnt', which acts as an active reference counter. When a dynamic event (such as kprobe, uprobe, fprobe, eprobe, or wprobe) has a non-zero reference count (e.g. due to active event triggers or perf attachments), its 'call->module' evaluates to a small non-zero integer instead of NULL. When filtering or setting events for a specific module (e.g., writing ':mod:' to 'set_event'), the code in '__ftrace_set_clr_event_nolock()' and 'update_event_fields()' reads 'call->module' directly without checking whether the event is dynamic. This causes the kernel to treat the small integer (refcnt) as a 'struct module' pointer, leading to a NULL/invalid pointer dereference (Oops) when dereferencing the module name. Fix this by ensuring that the 'TRACE_EVENT_FL_DYNAMIC' flag is checked before treating 'call->module' as a valid pointer in these code paths. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/178425670947.84440.11344393611899824907.stgit@devnote2 Fixes: 4c86bc531e60 ("tracing: Add :mod: command to enabled module events") Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index c46e623e7e0d..956692856fa8 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -1350,7 +1350,9 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match, call = file->event_call; /* If a module is specified, skip events that are not that module */ - if (module && (!call->module || strcmp(module_name(call->module), module))) + if (module && + ((call->flags & TRACE_EVENT_FL_DYNAMIC) || + !call->module || strcmp(module_name(call->module), module))) continue; name = trace_event_name(call); -- cgit v1.2.3 From 8f76afb9b114bee1c1251e0e52553e9dc7c59f20 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Fri, 17 Jul 2026 10:32:52 -0700 Subject: tracing: Fix context switch counter truncation trace_user_fault_read() samples nr_context_switches_cpu() before enabling preemption and retries the user copy if the counter changes. The helper returns unsigned long long because rq->nr_switches is u64, but the saved value is unsigned int. Once a CPU has performed 2^32 context switches, assigning the counter to cnt discards its upper bits. The comparison after the copy promotes cnt back to unsigned long long, but the lost bits remain zero, so it reports a change even when the task was never scheduled out. Every retry then fails the same way until the 100-try guard warns and the user copy is abandoned. This affects long-running systems and workloads with high context-switch rates. A CPU switching 1,000 times per second takes about 50 days. Store the sampled count in unsigned long long so the full value is preserved. Cc: stable@vger.kernel.org Fixes: 64cf7d058a00 ("tracing: Have trace_marker use per-cpu data to read user space") Link: https://patch.msgid.link/20260717173252.3431565-1-usama.arif@linux.dev Reported-by: Breno Leitao Signed-off-by: Usama Arif Acked-by: Masami Hiramatsu (Google) Reviewed-by: Breno Leitao Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 18710c190c92..01a5e87af299 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -6187,7 +6187,7 @@ char *trace_user_fault_read(struct trace_user_buf_info *tinfo, { int cpu = smp_processor_id(); char *buffer = per_cpu_ptr(tinfo->tbuf, cpu)->buf; - unsigned int cnt; + unsigned long long cnt; int trys = 0; int ret; -- cgit v1.2.3 From 79097812153b826fc156a2930ec8a90ed9edf4a2 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 24 Jul 2026 04:05:17 +0100 Subject: tracing: Fix use-after-free freeing trigger private data Commit 61d445af0a7c ("tracing: Add bulk garbage collection of freeing event_trigger_data") moved the kfree() of event_trigger_data to a kthread that runs tracepoint_synchronize_unregister() before freeing. That removed the synchronization the trigger .free callbacks used to get implicitly and inline from trigger_data_free(). event_hist_trigger_free(), event_hist_trigger_named_free() and event_enable_trigger_free() free their satellite data (hist_data, cmd_ops, enable_data) right after trigger_data_free() returns. With the synchronization now deferred to the kthread, a concurrent tracepoint handler can still reach that data through the list_del_rcu()'d trigger, causing a use-after-free. The histogram teardown must stay synchronous: remove_hist_vars() and unregister_field_var_hists() have to detach a synthetic event from the histogram before the trigger-removal write returns, otherwise a following command races in and the synthetic-event removal fails with -EBUSY, as the trigger-synthetic-eprobe.tc selftest catches. Make those callbacks wait with the correct barrier - tracepoint_synchronize_unregister(), matching the free kthread - before freeing. The enable trigger has no such synchronous requirement, and a blocking synchronize there would re-serialize the path that commit deliberately deferred. Give it an optional private_data_free() callback that the free kthread runs after its grace period, and free enable_data from there. Link: https://patch.msgid.link/20260724030523.19081-1-devnexen@gmail.com Suggested-by: Masami Hiramatsu (Google) Suggested-by: Steven Rostedt Fixes: 61d445af0a7c ("tracing: Add bulk garbage collection of freeing event_trigger_data") Signed-off-by: David Carlier Signed-off-by: Steven Rostedt --- kernel/trace/trace.h | 1 + kernel/trace/trace_events_hist.c | 2 ++ kernel/trace/trace_events_trigger.c | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 80fe152af1dd..bf77331f56a4 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -1941,6 +1941,7 @@ struct event_trigger_data { struct list_head named_list; struct event_trigger_data *named_data; struct llist_node llist; + void (*private_data_free)(struct event_trigger_data *data); }; /* Avoid typos */ diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 82ce492ab268..58d28cd1afa3 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6349,6 +6349,7 @@ static void event_hist_trigger_free(struct event_trigger_data *data) trigger_data_free(data); + tracepoint_synchronize_unregister(); remove_hist_vars(hist_data); unregister_field_var_hists(hist_data); @@ -6388,6 +6389,7 @@ static void event_hist_trigger_named_free(struct event_trigger_data *data) del_named_trigger(data); trigger_data_free(data); + tracepoint_synchronize_unregister(); kfree(cmd_ops); } } diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c index 655db2e82513..46e60b70a4bb 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c @@ -38,6 +38,13 @@ static void trigger_create_kthread_locked(void) } } +static void trigger_data_free_one(struct event_trigger_data *data) +{ + if (data->private_data_free) + data->private_data_free(data); + kfree(data); +} + static void trigger_data_free_queued_locked(void) { struct event_trigger_data *data, *tmp; @@ -52,7 +59,7 @@ static void trigger_data_free_queued_locked(void) tracepoint_synchronize_unregister(); llist_for_each_entry_safe(data, tmp, llnodes, llist) - kfree(data); + trigger_data_free_one(data); } /* Bulk garbage collection of event_trigger_data elements */ @@ -75,7 +82,7 @@ static int trigger_kthread_fn(void *ignore) tracepoint_synchronize_unregister(); llist_for_each_entry_safe(data, tmp, llnodes, llist) - kfree(data); + trigger_data_free_one(data); } return 0; @@ -1717,6 +1724,11 @@ int event_enable_trigger_print(struct seq_file *m, return 0; } +static void enable_trigger_private_data_free(struct event_trigger_data *data) +{ + kfree(data->private_data); +} + void event_enable_trigger_free(struct event_trigger_data *data) { struct enable_trigger_data *enable_data = data->private_data; @@ -1728,9 +1740,9 @@ void event_enable_trigger_free(struct event_trigger_data *data) if (!data->ref) { /* Remove the SOFT_MODE flag */ trace_event_enable_disable(enable_data->file, 0, 1); + data->private_data_free = enable_trigger_private_data_free; trace_event_put_ref(enable_data->file->event_call); trigger_data_free(data); - kfree(enable_data); } } -- cgit v1.2.3 From e091351b38818ef620d27f44f4bfd625f13afbff Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 24 Jul 2026 13:24:15 -0400 Subject: tracing: Delay module ref count for "enable_event" trigger Triggers are now delayed from freeing, but can still be triggered until after the RCU grace period has ended. The freeing of the enable_event data is put into the private_data_free() callback, but the put of the module refcount is done immediately. It is possible that if a module is removed that has an event that would enable (or disable) it is still active, it can read the data of the module after it is removed causing a use-after-free bug. Move the trace_event_put_ref() that releases the module into the delayed callback so that the module can not be removed until any reference to its events are finished. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260724132415.1b5005db@gandalf.local.home Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724030523.19081-1-devnexen%40gmail.com Fixes: 61d445af0a7c ("tracing: Add bulk garbage collection of freeing event_trigger_data") Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_trigger.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c index 46e60b70a4bb..ad83419cb420 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c @@ -1726,7 +1726,10 @@ int event_enable_trigger_print(struct seq_file *m, static void enable_trigger_private_data_free(struct event_trigger_data *data) { - kfree(data->private_data); + struct enable_trigger_data *enable_data = data->private_data; + + trace_event_put_ref(enable_data->file->event_call); + kfree(enable_data); } void event_enable_trigger_free(struct event_trigger_data *data) @@ -1741,7 +1744,6 @@ void event_enable_trigger_free(struct event_trigger_data *data) /* Remove the SOFT_MODE flag */ trace_event_enable_disable(enable_data->file, 0, 1); data->private_data_free = enable_trigger_private_data_free; - trace_event_put_ref(enable_data->file->event_call); trigger_data_free(data); } } -- cgit v1.2.3 From 7720b63bcef3f54c7fe288774b720a227d54a306 Mon Sep 17 00:00:00 2001 From: Tengda Wu Date: Sat, 25 Jul 2026 02:47:21 +0000 Subject: ftrace: Add global mutex to serialize trace_parser access In ftrace, the trace_parser structure is allocated and initialized when a trace file is opened, and is subsequently used across write and release handlers to parse user input. The affected handler paths and their specific functions are: - Open paths: ftrace_regex_open(), ftrace_graph_open() - Write paths: ftrace_regex_write(), ftrace_graph_write() - Release paths: ftrace_regex_release(), ftrace_graph_release() If userspace opens a trace file descriptor and shares it across multiple threads, concurrent write calls will race on the parser's internal state, specifically the 'idx', 'cont', and 'buffer' fields, leading to corrupted input or undefined behavior. Fix this by adding a global mutex, parser_lock, to serialize all access to trace_parser across write and release paths, preventing concurrent corruption of parser state. Fixes: e704eff3ff51 ("ftrace: Have set_graph_function handle multiple functions in one write") Fixes: 689fd8b65d66 ("tracing: trace parser support for function and graph") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260725024721.1983675-1-wutengda@huaweicloud.com Signed-off-by: Tengda Wu Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'kernel/trace') diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index f93e34dd2328..6c47a94f5924 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -1097,6 +1097,12 @@ struct ftrace_ops global_ops = { FTRACE_OPS_FL_PID, }; +/* + * parser_lock - Protects trace_parser state against concurrent operations. + * Held across trace_get_user() and subsequent buffer parsing to prevent races. + */ +static DEFINE_MUTEX(parser_lock); + /* * Used by the stack unwinder to know about dynamic ftrace trampolines. */ @@ -5842,6 +5848,8 @@ ftrace_regex_write(struct file *file, const char __user *ubuf, /* iter->hash is a local copy, so we don't need regex_lock */ parser = &iter->parser; + + guard(mutex)(&parser_lock); read = trace_get_user(parser, ubuf, cnt, ppos); if (read >= 0 && trace_parser_loaded(parser) && @@ -6984,12 +6992,14 @@ int ftrace_regex_release(struct inode *inode, struct file *file) iter = file->private_data; parser = &iter->parser; + mutex_lock(&parser_lock); if (trace_parser_loaded(parser)) { int enable = !(iter->flags & FTRACE_ITER_NOTRACE); ftrace_process_regex(iter, parser->buffer, parser->idx, enable); } + mutex_unlock(&parser_lock); trace_parser_put(parser); @@ -7321,10 +7331,12 @@ ftrace_graph_release(struct inode *inode, struct file *file) parser = &fgd->parser; + mutex_lock(&parser_lock); if (trace_parser_loaded((parser))) { ret = ftrace_graph_set_hash(fgd->new_hash, parser->buffer); } + mutex_unlock(&parser_lock); trace_parser_put(parser); @@ -7437,6 +7449,7 @@ ftrace_graph_write(struct file *file, const char __user *ubuf, parser = &fgd->parser; + guard(mutex)(&parser_lock); read = trace_get_user(parser, ubuf, cnt, ppos); if (read >= 0 && trace_parser_loaded(parser) && -- cgit v1.2.3 From 2c2b322acdcc78575b8d6afa64a085cf92e03c12 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 24 Jul 2026 19:32:10 -0400 Subject: tracing: perf: Fix stale head for perf syscall tracing The code that can read the user space parameters of a system call may enable preemption and migrate. The head of the per CPU perf events list may be pointing to the wrong CPU event if the code migrates the task. Reassign the head pointer if the system call event called the code that may have caused a migration. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260724193210.03fae1d6@gandalf.local.home Reported-by: Sashiko <> Link: https://sashiko.dev/#/patchset/20260717173252.3431565-1-usama.arif%40linux.dev Fixes: edca33a56297d ("tracing: Fix failure to read user space from system call trace events") Signed-off-by: Steven Rostedt --- kernel/trace/trace_syscalls.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'kernel/trace') diff --git a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c index e98ee7e1e66f..8a4f3c75e39f 100644 --- a/kernel/trace/trace_syscalls.c +++ b/kernel/trace/trace_syscalls.c @@ -1451,6 +1451,11 @@ static void perf_syscall_enter(void *ignore, struct pt_regs *regs, long id) if (syscall_get_data(sys_data, args, &user_ptr, &size, user_sizes, &uargs, buf_size) < 0) return; + + /* The above may have caused a migration */ + head = this_cpu_ptr(sys_data->enter_event->perf_events); + if (hlist_empty(head)) + return; } /* get the size after alignment with the u32 buffer size field */ -- cgit v1.2.3 From eadc0725ab8d38c05148fce49f8d5c6a0e01f062 Mon Sep 17 00:00:00 2001 From: Mykyta Yatsenko Date: Mon, 18 May 2026 08:23:16 -0700 Subject: tracing: Expose tracepoint BTF ids via tracefs Add events///btf_ids, a per-template file that exposes the BTF ids resolve_btfids fills in for each tracepoint: btf_obj_id BTF object owning the ids below raw_btf_id FUNC_PROTO of __bpf_trace_ (named args), consumed by raw_tp / tp_btf BPF programs tp_btf_id trace_event_raw_ ring-buffer record, consumed by classic BPF_PROG_TYPE_TRACEPOINT programs DECLARE_EVENT_CLASS now emits a 2-entry BTF_ID_LIST (FUNC __bpf_trace_* and STRUCT trace_event_raw_*) and stores the pointer in trace_event_class. Per-syscall events under syscalls/ share the handcrafted classes event_class_syscall_{enter,exit} instead of going through DECLARE_EVENT_CLASS. Wire those classes to the BTF id lists generated for sys_enter / sys_exit so all ~700 per-syscall events expose the shared dispatcher prototype and record. The per-syscall events do not own their own tracepoint (they share sys_enter/sys_exit), so raw_btf_id is reported as 0 on those events; the meaningful raw_btf_id is exposed on raw_syscalls/sys_{enter,exit}/btf_ids where raw_tp / tp_btf programs can actually attach. Link: https://patch.msgid.link/20260518-generic_tracepoint-v2-2-b755a5cf67bb@meta.com Signed-off-by: Mykyta Yatsenko Signed-off-by: Steven Rostedt --- include/linux/trace_events.h | 9 +++++ include/trace/trace_events.h | 24 +++++++++++++ kernel/trace/trace_events.c | 80 ++++++++++++++++++++++++++++++++++++++++++- kernel/trace/trace_syscalls.c | 17 +++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) (limited to 'kernel/trace') diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 308c76b57d13..5cbd09c8be8d 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -298,6 +298,15 @@ struct trace_event_class { struct list_head *(*get_fields)(struct trace_event_call *); struct list_head fields; int (*raw_init)(struct trace_event_call *); +#ifdef CONFIG_BPF_EVENTS + /* + * Per-template BTF ids set by DECLARE_EVENT_CLASS via BTF_ID() and + * patched by resolve_btfids at link time. NULL for handcrafted classes. + * [0] FUNC __bpf_trace_