From nobody Mon Jun 8 05:25:46 2026 Received: from relay.hostedemail.com (smtprelay0016.hostedemail.com [216.40.44.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AB0D329A31C; Mon, 1 Jun 2026 17:07:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=216.40.44.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780333628; cv=none; b=jr8XH5p5Q09unPfP86p4uDdNL8JCxwqgq2Jbt0QNdjSDRhbc83hOmwN/psF4HZmcgK/3HDIRLbItdVPsyfoCNWCHVYeKXzqKmZRi+ZjCVdMnWphIp+d7x/JrkYbWlbAGxHP2lAkITk9Pg8ohJT9zdmIkit/Xfv/YJHhW3nN1G9Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780333628; c=relaxed/simple; bh=oY5r1z9LSiGTCvGT/9kMpOXNa9ymBW/Ki5su+UrrwU4=; h=Date:From:To:Cc:Subject:Message-ID:MIME-Version:Content-Type; b=a3wpBM6TeX2GzOOftkDEPnm6YtZEO8zUieFAdp7HPwnExqLck5h3CfyBtLl0xt5GpOY9GyDgErUbaDj+RxwNtQj+UvX1Ewl4hQZiywxkZsjUxZxHrdwpELbXTc3lIRTQvJA+aUg1HjJsfCGgIV8Tl7oyuKKNqzjWdtHwTVSTmo4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=goodmis.org; spf=pass smtp.mailfrom=goodmis.org; arc=none smtp.client-ip=216.40.44.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=goodmis.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=goodmis.org Received: from omf16.hostedemail.com (lb01a-stub [10.200.18.249]) by unirelay10.hostedemail.com (Postfix) with ESMTP id 8AF78C2204; Mon, 1 Jun 2026 17:07:04 +0000 (UTC) Received: from [HIDDEN] (Authenticated sender: rostedt@goodmis.org) by omf16.hostedemail.com (Postfix) with ESMTPA id 9724E20013; Mon, 1 Jun 2026 17:07:01 +0000 (UTC) Date: Mon, 1 Jun 2026 13:07:46 -0400 From: Steven Rostedt To: LKML , Linux Trace Kernel Cc: Masami Hiramatsu , Mathieu Desnoyers , Mark Rutland , Peter Zijlstra , Namhyung Kim , Takaya Saeki , Douglas Raillard , Tom Zanussi , Andrew Morton , Thomas Gleixner , Ian Rogers , Jiri Olsa Subject: [PATCH v8] tracing/eprobes: Allow use of BTF names to dereference pointers Message-ID: <20260601130746.2139d926@gandalf.local.home> X-Mailer: Claws Mail 3.20.0git84 (GTK+ 2.24.33; x86_64-pc-linux-gnu) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Stat-Signature: 5qk9qzitehwwudc8rji9f736wcjcnrtn X-Rspamd-Server: rspamout04 X-Rspamd-Queue-Id: 9724E20013 X-Session-Marker: 726F737465647440676F6F646D69732E6F7267 X-Session-ID: U2FsdGVkX18Rvc4Rb9UeARTeoJz5jU0kGQ0A1/kVvns= X-HE-Tag: 1780333621-742288 X-HE-Meta: U2FsdGVkX19Hw6BSvG+BCUoakM1NvwO7MtpI2YXcm9bMj7Ot3/TohT9mq8KkoRh1zX//RLL3VNzTumbOLGFgswumJg76Ungv+tfMTydvtaduHLjDx9lWeaCMx9DRrkVKz7mnTtDDH+4eT7vhdmzupeExL9o5hwaoxugr9DWKflilfQ7XGBMs1W7TFrMd0nctnMiKTWyfuw5lcIlcgwmMGogFSHoK41nesM1PW2YzgOQ/4kakrJDjEELab9YA7W65xt9Wyhq+bo0h6z+bcALLyqh+1gBM36n50YC/ysXoEjrzpMnGa6O7OCTFLG8I18+Jr+cQkBTHszy9BtRJ22Oun/WcVzG81wwCny9ji+FxBB5i0Y1c8Uq3t241i4n63Zjl1iGkGrstEwPymEa6QIJS8xSoWw0a2lc3276qU75i7/ZhpQNELJdUtqaxJDM77+CPtvIZOnOkBBGiXaLZgmwCyQ== Content-Type: text/plain; charset="utf-8" From: Steven Rostedt Add syntax to the parsing of eprobes to be able to typecast a trace event field that is a pointer to a structure. Currently, a dereference must be a number, where the user has to figure out manually the offset of a member of a structure that they want to dereference. But for event probes that records a field that happens to be a pointer to a structure, it cannot dereference these values with BTF naming, but must use numerical offsets. For example, to find out what device a sk_buff is pointing to in the net_dev_xmit trace event, one must first use gdb to find the offsets of the members of the structures: (gdb) p &((struct sk_buff *)0)->dev $1 =3D (struct net_device **) 0x10 (gdb) p &((struct net_device *)0)->name $2 =3D (char (*)[16]) 0x118 And then use the raw numbers to dereference: # echo 'e:xmit net.net_dev_xmit +0x118(+0x10($skbaddr)):string' >> dynami= c_events If BTF is in the kernel, then instead, the skbaddr can be typecast to sk_buff and use the normal dereference logic. # echo 'e:xmit net.net_dev_xmit (sk_buff)skbaddr->dev->name:string' >> dy= namic_events # echo 1 > events/eprobes/xmit/enable # cat trace [..] sshd-session-1022 [000] b..2. 860.249343: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.250061: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.250142: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.263553: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.283820: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.302716: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.322905: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.342828: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.362268: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.382335: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.400856: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" sshd-session-1022 [000] b..2. 860.419893: xmit: (net.net_dev_xmit)= arg1=3D"enp7s0" The syntax is simply: (STRUCT)(FIELD)->MEMBER[->MEMBER..] Also add comments around the #else and #endif of #ifdef CONFIG_PROBE_EVENTS= _BTF_ARGS to know what they are for. Signed-off-by: Steven Rostedt (Google) --- Changes since v7: https://patch.msgid.link/20260529110442.0967a64c@fedora - Add error message in parse_btf_args() for failed parsing of TEVENT. (Sashiko) - Remove TPARG_FL_TYPECAST and just use ctx->struct_btf instead. The flag was redundant and added unnecessary complexity. - Restructure to keep the lifetime of the TYPECAST to the end of traceprobe_parse_probe_arg_body(). This allows the last_type to stay around in case there's not a type parameter and then btf can still be used. (Sashiko and Masami Hiramatsu) Documentation/trace/eprobetrace.rst | 4 + kernel/trace/trace_probe.c | 173 +++++++++++++++++++++++----- kernel/trace/trace_probe.h | 5 +- 3 files changed, 154 insertions(+), 28 deletions(-) diff --git a/Documentation/trace/eprobetrace.rst b/Documentation/trace/epro= betrace.rst index 89b5157cfab8..fe3602540569 100644 --- a/Documentation/trace/eprobetrace.rst +++ b/Documentation/trace/eprobetrace.rst @@ -46,6 +46,10 @@ Synopsis of eprobe_events (x8/x16/x32/x64), VFS layer common type(%pd/%pD), "char", "string", "ustring", "symbol", "symstr" and "bitfield" a= re supported. + (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to + a pointer to STRUCT and then derference the pointer defi= ned by + ->MEMBER. Note that when this is used, the FIELD name do= es not + need to be prefixed with a '$'. =20 Types ----- diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 695310571b08..fd1caa1f9723 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -332,6 +332,23 @@ static int parse_trace_event_arg(char *arg, struct fet= ch_insn *code, return -ENOENT; } =20 +static int parse_trace_event(char *arg, struct fetch_insn *code, + struct traceprobe_parse_context *ctx) +{ + int ret; + + if (code->data) + return -EFAULT; + ret =3D parse_trace_event_arg(arg, code, ctx); + if (!ret) + return 0; + if (strcmp(arg, "comm") =3D=3D 0 || strcmp(arg, "COMM") =3D=3D 0) { + code->op =3D FETCH_OP_COMM; + return 0; + } + return -EINVAL; +} + #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS =20 static u32 btf_type_int(const struct btf_type *t) @@ -376,11 +393,16 @@ static bool btf_type_is_char_array(struct btf *btf, c= onst struct btf_type *type) && BTF_INT_BITS(intdata) =3D=3D 8; } =20 +static struct btf *ctx_btf(struct traceprobe_parse_context *ctx) +{ + return ctx->struct_btf ? : ctx->btf; +} + static int check_prepare_btf_string_fetch(char *typename, struct fetch_insn **pcode, struct traceprobe_parse_context *ctx) { - struct btf *btf =3D ctx->btf; + struct btf *btf =3D ctx_btf(ctx); =20 if (!btf || !ctx->last_type) return 0; @@ -506,6 +528,15 @@ static int query_btf_context(struct traceprobe_parse_c= ontext *ctx) return 0; } =20 +static void clear_struct_btf(struct traceprobe_parse_context *ctx) +{ + if (ctx->struct_btf) { + btf_put(ctx->struct_btf); + ctx->struct_btf =3D NULL; + ctx->last_struct =3D NULL; + } +} + static void clear_btf_context(struct traceprobe_parse_context *ctx) { if (ctx->btf) { @@ -554,22 +585,29 @@ static int parse_btf_field(char *fieldname, const str= uct btf_type *type, struct fetch_insn *code =3D *pcode; const struct btf_member *field; u32 bitoffs, anon_offs; + bool is_struct =3D ctx->struct_btf !=3D NULL; + struct btf *btf =3D ctx_btf(ctx); char *next; int is_ptr; s32 tid; =20 do { - /* Outer loop for solving arrow operator ('->') */ - if (BTF_INFO_KIND(type->info) !=3D BTF_KIND_PTR) { - trace_probe_log_err(ctx->offset, NO_PTR_STRCT); - return -EINVAL; - } - /* Convert a struct pointer type to a struct type */ - type =3D btf_type_skip_modifiers(ctx->btf, type->type, &tid); - if (!type) { - trace_probe_log_err(ctx->offset, BAD_BTF_TID); - return -EINVAL; + if (!is_struct) { + /* Outer loop for solving arrow operator ('->') */ + if (BTF_INFO_KIND(type->info) !=3D BTF_KIND_PTR) { + trace_probe_log_err(ctx->offset, NO_PTR_STRCT); + return -EINVAL; + } + + /* Convert a struct pointer type to a struct type */ + type =3D btf_type_skip_modifiers(btf, type->type, &tid); + if (!type) { + trace_probe_log_err(ctx->offset, BAD_BTF_TID); + return -EINVAL; + } } + /* Only the first type can skip being a pointer */ + is_struct =3D false; =20 bitoffs =3D 0; do { @@ -580,7 +618,7 @@ static int parse_btf_field(char *fieldname, const struc= t btf_type *type, return is_ptr; =20 anon_offs =3D 0; - field =3D btf_find_struct_member(ctx->btf, type, fieldname, + field =3D btf_find_struct_member(btf, type, fieldname, &anon_offs); if (IS_ERR(field)) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); @@ -602,7 +640,7 @@ static int parse_btf_field(char *fieldname, const struc= t btf_type *type, ctx->last_bitsize =3D 0; } =20 - type =3D btf_type_skip_modifiers(ctx->btf, field->type, &tid); + type =3D btf_type_skip_modifiers(btf, field->type, &tid); if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL; @@ -640,7 +678,7 @@ static int parse_btf_arg(char *varname, int i, is_ptr, ret; u32 tid; =20 - if (WARN_ON_ONCE(!ctx->funcname)) + if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT))) return -EINVAL; =20 is_ptr =3D split_next_field(varname, &field, ctx); @@ -653,6 +691,19 @@ static int parse_btf_arg(char *varname, return -EOPNOTSUPP; } =20 + if (ctx->flags & TPARG_FL_TEVENT) { + ret =3D 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 =3D=3D NULL)) + return -EINVAL; + type =3D ctx->last_struct; + goto found_type; + } + if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) { code->op =3D FETCH_OP_RETVAL; /* Check whether the function return type is not void */ @@ -709,6 +760,7 @@ static int parse_btf_arg(char *varname, =20 found: type =3D btf_type_skip_modifiers(ctx->btf, tid, &tid); +found_type: if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL; @@ -727,7 +779,7 @@ static int parse_btf_arg(char *varname, static const struct fetch_type *find_fetch_type_from_btf_type( struct traceprobe_parse_context *ctx) { - struct btf *btf =3D ctx->btf; + struct btf *btf =3D ctx_btf(ctx); const char *typestr =3D NULL; =20 if (btf && ctx->last_type) @@ -758,7 +810,67 @@ static int parse_btf_bitfield(struct fetch_insn **pcod= e, return 0; } =20 -#else +static int query_btf_struct(const char *sname, struct traceprobe_parse_con= text *ctx) +{ + struct btf *btf =3D NULL; + int id; + + /* A struct_btf should only be used by a single argument */ + if (WARN_ON_ONCE(ctx->struct_btf)) { + btf_put(ctx->struct_btf); + ctx->struct_btf =3D NULL; + } + + id =3D bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf); + if (id < 0) + return id; + ctx->struct_btf =3D btf; + ctx->last_struct =3D btf_type_by_id(ctx->struct_btf, id); + return 0; +} + +static int handle_typecast(char *arg, struct fetch_insn **pcode, + struct fetch_insn *end, + struct traceprobe_parse_context *ctx) +{ + 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; + } + + tmp =3D strchr(arg, ')'); + if (!tmp) { + trace_probe_log_err(ctx->offset + strlen(arg), + DEREF_OPEN_BRACE); + return -EINVAL; + } + *tmp =3D '\0'; + ret =3D query_btf_struct(arg + 1, ctx); + *tmp =3D ')'; + + if (ret < 0) { + trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT); + return -EINVAL; + } + + tmp++; + + ctx->offset +=3D tmp - arg; + ret =3D parse_btf_arg(tmp, pcode, end, ctx); + return ret; +} + +#else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */ + +static void clear_struct_btf(struct traceprobe_parse_context *ctx) +{ + ctx->struct_btf =3D NULL; +} + static void clear_btf_context(struct traceprobe_parse_context *ctx) { ctx->btf =3D NULL; @@ -794,7 +906,15 @@ static int check_prepare_btf_string_fetch(char *typena= me, return 0; } =20 -#endif +static int handle_typecast(char *arg, struct fetch_insn **pcode, + struct fetch_insn *end, + struct traceprobe_parse_context *ctx) +{ + trace_probe_log_err(ctx->offset, NOSUP_BTFARG); + return -EOPNOTSUPP; +} + +#endif /* CONFIG_PROBE_EVENTS_BTF_ARGS */ =20 #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API =20 @@ -948,16 +1068,9 @@ static int parse_probe_vars(char *orig_arg, const str= uct fetch_type *t, int len; =20 if (ctx->flags & TPARG_FL_TEVENT) { - if (code->data) - return -EFAULT; - ret =3D parse_trace_event_arg(arg, code, ctx); - if (!ret) - return 0; - if (strcmp(arg, "comm") =3D=3D 0 || strcmp(arg, "COMM") =3D=3D 0) { - code->op =3D FETCH_OP_COMM; - return 0; - } - goto inval; + if (parse_trace_event(arg, code, ctx) < 0) + goto inval; + return 0; } =20 if (str_has_prefix(arg, "retval")) { @@ -1224,6 +1337,9 @@ parse_probe_arg(char *arg, const struct fetch_type *t= ype, code->op =3D FETCH_OP_IMM; } break; + case '(': + ret =3D handle_typecast(arg, pcode, end, ctx); + break; default: if (isalpha(arg[0]) || arg[0] =3D=3D '_') { /* BTF variable */ if (!tparg_is_function_entry(ctx->flags) && @@ -1556,6 +1672,9 @@ static int traceprobe_parse_probe_arg_body(const char= *argv, ssize_t *size, } kfree(tmp); =20 + /* struct_btf should not be passed to other arguments */ + clear_struct_btf(ctx); + return ret; } =20 diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 1076f1df347b..15758cc11fc6 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -422,7 +422,9 @@ struct traceprobe_parse_context { const struct btf_param *params; /* Parameter of the function */ s32 nr_params; /* The number of the parameters */ struct btf *btf; /* The BTF to be used */ + struct btf *struct_btf; /* The BTF to be used for structs */ const struct btf_type *last_type; /* Saved type */ + const struct btf_type *last_struct; /* Saved structure */ u32 last_bitoffs; /* Saved bitoffs */ u32 last_bitsize; /* Saved bitsize */ struct trace_probe *tp; @@ -563,7 +565,8 @@ extern int traceprobe_define_arg_fields(struct trace_ev= ent_call *event_call, C(NEED_STRING_TYPE, "$comm and immediate-string only accepts string type"= ),\ C(TOO_MANY_ARGS, "Too many arguments are specified"), \ C(TOO_MANY_EARGS, "Too many entry arguments specified"), \ - C(EVENT_TOO_BIG, "Event too big (too many fields?)"), + C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \ + C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"), =20 #undef C #define C(a, b) TP_ERR_##a --=20 2.53.0