PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers

Steven Rostedt posted 1 patch 1 week, 2 days ago
Documentation/trace/eprobetrace.rst |   4 +
kernel/trace/trace_probe.c          | 168 +++++++++++++++++++++++-----
kernel/trace/trace_probe.h          |   7 +-
3 files changed, 149 insertions(+), 30 deletions(-)
PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
Posted by Steven Rostedt 1 week, 2 days ago
From: Steven Rostedt <rostedt@goodmis.org>

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 = (struct net_device **) 0x10
 (gdb) p &((struct net_device *)0)->name
 $2 = (char (*)[16]) 0x118

And then use the raw numbers to dereference:

  # echo 'e:xmit net.net_dev_xmit +0x118(+0x10($skbaddr)):string' >> dynamic_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' >> dynamic_events
  # echo 1 > events/eprobes/xmit/enable
  # cat trace
[..]
    sshd-session-1022    [000] b..2.   860.249343: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.250061: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.250142: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.263553: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.283820: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.302716: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.322905: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.342828: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.362268: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.382335: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.400856: xmit: (net.net_dev_xmit) arg1="enp7s0"
    sshd-session-1022    [000] b..2.   860.419893: xmit: (net.net_dev_xmit) arg1="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) <rostedt@goodmis.org>
---
Changes since v6: https://patch.msgid.link/20260521225033.56458336@fedora

- Set ctx->struct_btf to NULL when finished with it in handle_typecast()
  (Sashiko)

- Remove extra unneeded "ret" declaration (Masami Hiramatsu)

- Add a WARN_ON_ONCE() in parse_btf_arg for TEVENT being called without
  TYPECAST being set. (Masami Hiramatsu)

 Documentation/trace/eprobetrace.rst |   4 +
 kernel/trace/trace_probe.c          | 168 +++++++++++++++++++++++-----
 kernel/trace/trace_probe.h          |   7 +-
 3 files changed, 149 insertions(+), 30 deletions(-)

diff --git a/Documentation/trace/eprobetrace.rst b/Documentation/trace/eprobetrace.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" 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 when this is used, the FIELD name does not
+                  need to be prefixed with a '$'.
 
 Types
 -----
diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index e0d3a0da26af..9246e9c3d066 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -332,6 +332,25 @@ static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
 	return -ENOENT;
 }
 
+static int parse_trace_event(char *arg, struct fetch_insn *code,
+			     struct traceprobe_parse_context *ctx)
+{
+	int ret;
+
+	if (code->data)
+		return -EFAULT;
+	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;
+	}
+	/* backward compatibility */
+	ctx->offset = 0;
+	return -EINVAL;
+}
+
 #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
 
 static u32 btf_type_int(const struct btf_type *t)
@@ -376,11 +395,17 @@ static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type)
 		&& BTF_INT_BITS(intdata) == 8;
 }
 
+static struct btf *ctx_btf(struct traceprobe_parse_context *ctx)
+{
+	return ctx->flags & TPARG_FL_TYPECAST ?
+		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 = ctx->btf;
+	struct btf *btf = ctx_btf(ctx);
 
 	if (!btf || !ctx->last_type)
 		return 0;
@@ -554,22 +579,29 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
 	struct fetch_insn *code = *pcode;
 	const struct btf_member *field;
 	u32 bitoffs, anon_offs;
+	bool is_struct = ctx->flags & TPARG_FL_TYPECAST;
+	struct btf *btf = ctx_btf(ctx);
 	char *next;
 	int is_ptr;
 	s32 tid;
 
 	do {
-		/* Outer loop for solving arrow operator ('->') */
-		if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
-			trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
-			return -EINVAL;
-		}
-		/* Convert a struct pointer type to a struct type */
-		type = 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) != BTF_KIND_PTR) {
+				trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
+				return -EINVAL;
+			}
+
+			/* Convert a struct pointer type to a struct type */
+			type = 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 = false;
 
 		bitoffs = 0;
 		do {
@@ -580,7 +612,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
 				return is_ptr;
 
 			anon_offs = 0;
-			field = btf_find_struct_member(ctx->btf, type, fieldname,
+			field = btf_find_struct_member(btf, type, fieldname,
 						       &anon_offs);
 			if (IS_ERR(field)) {
 				trace_probe_log_err(ctx->offset, BAD_BTF_TID);
@@ -602,7 +634,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
 				ctx->last_bitsize = 0;
 			}
 
-			type = btf_type_skip_modifiers(ctx->btf, field->type, &tid);
+			type = btf_type_skip_modifiers(btf, field->type, &tid);
 			if (!type) {
 				trace_probe_log_err(ctx->offset, BAD_BTF_TID);
 				return -EINVAL;
@@ -627,6 +659,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
 	return 0;
 }
 
+
 static int __store_entry_arg(struct trace_probe *tp, int argnum);
 
 static int parse_btf_arg(char *varname,
@@ -640,7 +673,7 @@ static int parse_btf_arg(char *varname,
 	int i, is_ptr, ret;
 	u32 tid;
 
-	if (WARN_ON_ONCE(!ctx->funcname))
+	if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)))
 		return -EINVAL;
 
 	is_ptr = split_next_field(varname, &field, ctx);
@@ -653,6 +686,16 @@ 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)
+			return ret;
+		if (WARN_ON_ONCE(!(ctx->flags & TPARG_FL_TYPECAST)))
+			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 */
@@ -709,6 +752,7 @@ static int parse_btf_arg(char *varname,
 
 found:
 	type = 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 +771,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 = ctx->btf;
+	struct btf *btf = ctx_btf(ctx);
 	const char *typestr = NULL;
 
 	if (btf && ctx->last_type)
@@ -758,7 +802,71 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
 	return 0;
 }
 
-#else
+static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
+{
+	int id;
+
+	if (!ctx->struct_btf) {
+		struct btf *btf;
+
+		id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
+		if (id < 0)
+			return id;
+		ctx->struct_btf = btf;
+	} else {
+		id = btf_find_by_name_kind(ctx->struct_btf, sname, BTF_KIND_STRUCT);
+		if (id < 0)
+			return id;
+	}
+
+	ctx->last_struct = 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 = strchr(arg, ')');
+	if (!tmp) {
+		trace_probe_log_err(ctx->offset + strlen(arg),
+				    DEREF_OPEN_BRACE);
+		return -EINVAL;
+	}
+	*tmp = '\0';
+	ret = query_btf_struct(arg + 1, ctx);
+	*tmp = ')';
+
+	if (ret < 0) {
+		trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
+		ret = -EINVAL;
+		goto out_put;
+	}
+
+	ctx->flags |= TPARG_FL_TYPECAST;
+	tmp++;
+
+	ctx->offset += tmp - arg;
+	ret = parse_btf_arg(tmp, pcode, end, ctx);
+	ctx->flags &= ~TPARG_FL_TYPECAST;
+	ctx->last_struct = NULL;
+out_put:
+	btf_put(ctx->struct_btf);
+	ctx->struct_btf = NULL;
+	return ret;
+}
+
+#else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */
+
 static void clear_btf_context(struct traceprobe_parse_context *ctx)
 {
 	ctx->btf = NULL;
@@ -794,7 +902,15 @@ static int check_prepare_btf_string_fetch(char *typename,
 	return 0;
 }
 
-#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 */
 
 #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
 
@@ -953,18 +1069,9 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
 	int len;
 
 	if (ctx->flags & TPARG_FL_TEVENT) {
-		if (code->data)
-			return -EFAULT;
-		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;
-		}
-		/* backward compatibility */
-		ctx->offset = 0;
-		goto inval;
+		if (parse_trace_event(arg, code, ctx) < 0)
+			goto inval;
+		return 0;
 	}
 
 	if (str_has_prefix(arg, "retval")) {
@@ -1231,6 +1338,9 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
 				code->op = FETCH_OP_IMM;
 		}
 		break;
+	case '(':
+		ret = handle_typecast(arg, pcode, end, ctx);
+		break;
 	default:
 		if (isalpha(arg[0]) || arg[0] == '_') {	/* BTF variable */
 			if (!tparg_is_function_entry(ctx->flags) &&
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 262d8707a3df..952e3d7582b8 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -394,6 +394,7 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)
  * TPARG_FL_KERNEL and TPARG_FL_USER are also mutually exclusive.
  * TPARG_FL_FPROBE and TPARG_FL_TPOINT are optional but it should be with
  * TPARG_FL_KERNEL.
+ * TPARG_FL_TYPECAST is set if an argument was typecast to a structure.
  */
 #define TPARG_FL_RETURN BIT(0)
 #define TPARG_FL_KERNEL BIT(1)
@@ -402,6 +403,7 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)
 #define TPARG_FL_USER   BIT(4)
 #define TPARG_FL_FPROBE BIT(5)
 #define TPARG_FL_TPOINT BIT(6)
+#define TPARG_FL_TYPECAST BIT(7)
 #define TPARG_FL_LOC_MASK	GENMASK(4, 0)
 
 static inline bool tparg_is_function_entry(unsigned int flags)
@@ -422,7 +424,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 +567,8 @@ extern int traceprobe_define_arg_fields(struct trace_event_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"),
 
 #undef C
 #define C(a, b)		TP_ERR_##a
-- 
2.53.0
Re: PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
Posted by Masami Hiramatsu (Google) 1 week, 1 day ago
On Fri, 29 May 2026 11:04:42 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:

> From: Steven Rostedt <rostedt@goodmis.org>
> 
> 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 = (struct net_device **) 0x10
>  (gdb) p &((struct net_device *)0)->name
>  $2 = (char (*)[16]) 0x118
> 
> And then use the raw numbers to dereference:
> 
>   # echo 'e:xmit net.net_dev_xmit +0x118(+0x10($skbaddr)):string' >> dynamic_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' >> dynamic_events
>   # echo 1 > events/eprobes/xmit/enable
>   # cat trace
> [..]
>     sshd-session-1022    [000] b..2.   860.249343: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.250061: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.250142: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.263553: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.283820: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.302716: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.322905: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.342828: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.362268: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.382335: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.400856: xmit: (net.net_dev_xmit) arg1="enp7s0"
>     sshd-session-1022    [000] b..2.   860.419893: xmit: (net.net_dev_xmit) arg1="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.

Thanks, and Sashiko reviewed this. 

https://sashiko.dev/#/patchset/20260529110442.0967a64c%40fedora

I think both comments are valid, especially, second one is important.

BTW, I updated probes/fixes. Could you also update this and rebase
on probes/fixes branch?

I'm working on the nesting and container_of support patches which
are on top of this.

Thank you,

> 
> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
> ---
> Changes since v6: https://patch.msgid.link/20260521225033.56458336@fedora
> 
> - Set ctx->struct_btf to NULL when finished with it in handle_typecast()
>   (Sashiko)
> 
> - Remove extra unneeded "ret" declaration (Masami Hiramatsu)
> 
> - Add a WARN_ON_ONCE() in parse_btf_arg for TEVENT being called without
>   TYPECAST being set. (Masami Hiramatsu)
> 
>  Documentation/trace/eprobetrace.rst |   4 +
>  kernel/trace/trace_probe.c          | 168 +++++++++++++++++++++++-----
>  kernel/trace/trace_probe.h          |   7 +-
>  3 files changed, 149 insertions(+), 30 deletions(-)
> 
> diff --git a/Documentation/trace/eprobetrace.rst b/Documentation/trace/eprobetrace.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" 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 when this is used, the FIELD name does not
> +                  need to be prefixed with a '$'.
>  
>  Types
>  -----
> diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> index e0d3a0da26af..9246e9c3d066 100644
> --- a/kernel/trace/trace_probe.c
> +++ b/kernel/trace/trace_probe.c
> @@ -332,6 +332,25 @@ static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
>  	return -ENOENT;
>  }
>  
> +static int parse_trace_event(char *arg, struct fetch_insn *code,
> +			     struct traceprobe_parse_context *ctx)
> +{
> +	int ret;
> +
> +	if (code->data)
> +		return -EFAULT;
> +	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;
> +	}
> +	/* backward compatibility */
> +	ctx->offset = 0;
> +	return -EINVAL;
> +}
> +
>  #ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
>  
>  static u32 btf_type_int(const struct btf_type *t)
> @@ -376,11 +395,17 @@ static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type)
>  		&& BTF_INT_BITS(intdata) == 8;
>  }
>  
> +static struct btf *ctx_btf(struct traceprobe_parse_context *ctx)
> +{
> +	return ctx->flags & TPARG_FL_TYPECAST ?
> +		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 = ctx->btf;
> +	struct btf *btf = ctx_btf(ctx);
>  
>  	if (!btf || !ctx->last_type)
>  		return 0;
> @@ -554,22 +579,29 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
>  	struct fetch_insn *code = *pcode;
>  	const struct btf_member *field;
>  	u32 bitoffs, anon_offs;
> +	bool is_struct = ctx->flags & TPARG_FL_TYPECAST;
> +	struct btf *btf = ctx_btf(ctx);
>  	char *next;
>  	int is_ptr;
>  	s32 tid;
>  
>  	do {
> -		/* Outer loop for solving arrow operator ('->') */
> -		if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
> -			trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
> -			return -EINVAL;
> -		}
> -		/* Convert a struct pointer type to a struct type */
> -		type = 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) != BTF_KIND_PTR) {
> +				trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
> +				return -EINVAL;
> +			}
> +
> +			/* Convert a struct pointer type to a struct type */
> +			type = 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 = false;
>  
>  		bitoffs = 0;
>  		do {
> @@ -580,7 +612,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
>  				return is_ptr;
>  
>  			anon_offs = 0;
> -			field = btf_find_struct_member(ctx->btf, type, fieldname,
> +			field = btf_find_struct_member(btf, type, fieldname,
>  						       &anon_offs);
>  			if (IS_ERR(field)) {
>  				trace_probe_log_err(ctx->offset, BAD_BTF_TID);
> @@ -602,7 +634,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
>  				ctx->last_bitsize = 0;
>  			}
>  
> -			type = btf_type_skip_modifiers(ctx->btf, field->type, &tid);
> +			type = btf_type_skip_modifiers(btf, field->type, &tid);
>  			if (!type) {
>  				trace_probe_log_err(ctx->offset, BAD_BTF_TID);
>  				return -EINVAL;
> @@ -627,6 +659,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
>  	return 0;
>  }
>  
> +
>  static int __store_entry_arg(struct trace_probe *tp, int argnum);
>  
>  static int parse_btf_arg(char *varname,
> @@ -640,7 +673,7 @@ static int parse_btf_arg(char *varname,
>  	int i, is_ptr, ret;
>  	u32 tid;
>  
> -	if (WARN_ON_ONCE(!ctx->funcname))
> +	if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)))
>  		return -EINVAL;
>  
>  	is_ptr = split_next_field(varname, &field, ctx);
> @@ -653,6 +686,16 @@ 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)
> +			return ret;
> +		if (WARN_ON_ONCE(!(ctx->flags & TPARG_FL_TYPECAST)))
> +			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 */
> @@ -709,6 +752,7 @@ static int parse_btf_arg(char *varname,
>  
>  found:
>  	type = 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 +771,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 = ctx->btf;
> +	struct btf *btf = ctx_btf(ctx);
>  	const char *typestr = NULL;
>  
>  	if (btf && ctx->last_type)
> @@ -758,7 +802,71 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
>  	return 0;
>  }
>  
> -#else
> +static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
> +{
> +	int id;
> +
> +	if (!ctx->struct_btf) {
> +		struct btf *btf;
> +
> +		id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
> +		if (id < 0)
> +			return id;
> +		ctx->struct_btf = btf;
> +	} else {
> +		id = btf_find_by_name_kind(ctx->struct_btf, sname, BTF_KIND_STRUCT);
> +		if (id < 0)
> +			return id;
> +	}
> +
> +	ctx->last_struct = 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 = strchr(arg, ')');
> +	if (!tmp) {
> +		trace_probe_log_err(ctx->offset + strlen(arg),
> +				    DEREF_OPEN_BRACE);
> +		return -EINVAL;
> +	}
> +	*tmp = '\0';
> +	ret = query_btf_struct(arg + 1, ctx);
> +	*tmp = ')';
> +
> +	if (ret < 0) {
> +		trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
> +		ret = -EINVAL;
> +		goto out_put;
> +	}
> +
> +	ctx->flags |= TPARG_FL_TYPECAST;
> +	tmp++;
> +
> +	ctx->offset += tmp - arg;
> +	ret = parse_btf_arg(tmp, pcode, end, ctx);
> +	ctx->flags &= ~TPARG_FL_TYPECAST;
> +	ctx->last_struct = NULL;
> +out_put:
> +	btf_put(ctx->struct_btf);
> +	ctx->struct_btf = NULL;
> +	return ret;
> +}
> +
> +#else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */
> +
>  static void clear_btf_context(struct traceprobe_parse_context *ctx)
>  {
>  	ctx->btf = NULL;
> @@ -794,7 +902,15 @@ static int check_prepare_btf_string_fetch(char *typename,
>  	return 0;
>  }
>  
> -#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 */
>  
>  #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
>  
> @@ -953,18 +1069,9 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
>  	int len;
>  
>  	if (ctx->flags & TPARG_FL_TEVENT) {
> -		if (code->data)
> -			return -EFAULT;
> -		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;
> -		}
> -		/* backward compatibility */
> -		ctx->offset = 0;
> -		goto inval;
> +		if (parse_trace_event(arg, code, ctx) < 0)
> +			goto inval;
> +		return 0;
>  	}
>  
>  	if (str_has_prefix(arg, "retval")) {
> @@ -1231,6 +1338,9 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
>  				code->op = FETCH_OP_IMM;
>  		}
>  		break;
> +	case '(':
> +		ret = handle_typecast(arg, pcode, end, ctx);
> +		break;
>  	default:
>  		if (isalpha(arg[0]) || arg[0] == '_') {	/* BTF variable */
>  			if (!tparg_is_function_entry(ctx->flags) &&
> diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
> index 262d8707a3df..952e3d7582b8 100644
> --- a/kernel/trace/trace_probe.h
> +++ b/kernel/trace/trace_probe.h
> @@ -394,6 +394,7 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)
>   * TPARG_FL_KERNEL and TPARG_FL_USER are also mutually exclusive.
>   * TPARG_FL_FPROBE and TPARG_FL_TPOINT are optional but it should be with
>   * TPARG_FL_KERNEL.
> + * TPARG_FL_TYPECAST is set if an argument was typecast to a structure.
>   */
>  #define TPARG_FL_RETURN BIT(0)
>  #define TPARG_FL_KERNEL BIT(1)
> @@ -402,6 +403,7 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)
>  #define TPARG_FL_USER   BIT(4)
>  #define TPARG_FL_FPROBE BIT(5)
>  #define TPARG_FL_TPOINT BIT(6)
> +#define TPARG_FL_TYPECAST BIT(7)
>  #define TPARG_FL_LOC_MASK	GENMASK(4, 0)
>  
>  static inline bool tparg_is_function_entry(unsigned int flags)
> @@ -422,7 +424,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 +567,8 @@ extern int traceprobe_define_arg_fields(struct trace_event_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"),
>  
>  #undef C
>  #define C(a, b)		TP_ERR_##a
> -- 
> 2.53.0
> 


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>
Re: PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
Posted by Steven Rostedt 1 week, 1 day ago
On Sat, 30 May 2026 23:14:27 +0900
Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:

> Thanks, and Sashiko reviewed this. 
> 
> https://sashiko.dev/#/patchset/20260529110442.0967a64c%40fedora
> 
> I think both comments are valid, especially, second one is important.

Hmm, comment below.

> 
> BTW, I updated probes/fixes. Could you also update this and rebase
> on probes/fixes branch?
> 
> I'm working on the nesting and container_of support patches which
> are on top of this.

OK, I'll make sure to use your probes branch. Which one should I use? your
probes/for-next or probes/urgent?

As for sashiko's comments:

> commit c8393ea9936934c16ba2881d9e7b3cce86c678ff
> Author: Steven Rostedt <rostedt@goodmis.org>
> [PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
> This commit introduces syntax to allow event probes to typecast pointer
> fields to a structure using BTF. This enables dereferencing structures
> using BTF names rather than having to manually calculate offsets.
> > diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> > index 39f040c863e85..0e87409a44033 100644
> > --- a/kernel/trace/trace_probe.c
> > +++ b/kernel/trace/trace_probe.c
> [ ... ]
> > @@ -640,7 +673,7 @@ static int parse_btf_arg(char *varname,
> >  	int i, is_ptr, ret;
> >  	u32 tid;
> >  
> > -	if (WARN_ON_ONCE(!ctx->funcname))
> > +	if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)))
> >  		return -EINVAL;
> >  
> >  	is_ptr = split_next_field(varname, &field, ctx);
> > @@ -653,6 +686,16 @@ 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)
> > +			return ret;

> Does this silently drop the error on parsing failure?
> If parse_trace_event() fails and returns -EINVAL, it appears this path
> returns the error without logging it.
> Unlike parse_probe_vars(), which explicitly logs the failure when
> parse_trace_event() fails, this path bypasses the log. Could this make
> it difficult to debug invalid typecast expressions since nothing is written
> to tracing/error_log?

Hmm, I guess I can put an error log here, as this is a unique path.

> > +		if (WARN_ON_ONCE(!(ctx->flags & TPARG_FL_TYPECAST)))
> > +			return -EINVAL;
> > +		type = ctx->last_struct;
> > +		goto found_type;
> > +	}
> > +
> >  	if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) {
> >  		code->op = FETCH_OP_RETVAL;
> [ ... ]
> > @@ -758,7 +802,71 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
> >  	return 0;
> >  }
> >  
> > -#else
> > +static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
> [ ... ]
> > +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 = strchr(arg, ')');
> > +	if (!tmp) {
> > +		trace_probe_log_err(ctx->offset + strlen(arg),
> > +				    DEREF_OPEN_BRACE);
> > +		return -EINVAL;
> > +	}
> > +	*tmp = '\0';
> > +	ret = query_btf_struct(arg + 1, ctx);
> > +	*tmp = ')';
> > +
> > +	if (ret < 0) {
> > +		trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
> > +		ret = -EINVAL;
> > +		goto out_put;
> > +	}
> > +
> > +	ctx->flags |= TPARG_FL_TYPECAST;
> > +	tmp++;
> > +
> > +	ctx->offset += tmp - arg;
> > +	ret = parse_btf_arg(tmp, pcode, end, ctx);
> > +	ctx->flags &= ~TPARG_FL_TYPECAST;
> > +	ctx->last_struct = NULL;
> > +out_put:
> > +	btf_put(ctx->struct_btf);

> Does this prematurely release the BTF struct reference?
> If TPARG_FL_TYPECAST is unset here and ctx->struct_btf is put, won't
> later steps in traceprobe_parse_probe_arg_body() (like
> find_fetch_type_from_btf_type()) fail to properly infer struct field sizes?
> When ctx_btf(ctx) is called later without TPARG_FL_TYPECAST set, it
> will evaluate to ctx->btf (which is NULL for eprobes).
> Could this potentially lead to silent defaults, such as 64-bit reads for
> smaller fields, or fail to inject pointer dereferences for string fields,
> while also leaving ctx->last_type pointing to a prematurely released BTF
> object?

Does this mean we need to set ctx->last_type to NULL here too?

Because everything above is pretty much the expected behavior. The put is
*not* premature. The last_struct and struct_btf are both set to NULL. I
guess the only thing missing is to reset last_type as well.

-- Steve


> > +	ctx->struct_btf = NULL;
> > +	return ret;
> > +}
> > +
> > +#else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */
Re: PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
Posted by Masami Hiramatsu (Google) 6 days, 19 hours ago
On Sat, 30 May 2026 11:07:54 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:

> On Sat, 30 May 2026 23:14:27 +0900
> Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:
> 
> > Thanks, and Sashiko reviewed this. 
> > 
> > https://sashiko.dev/#/patchset/20260529110442.0967a64c%40fedora
> > 
> > I think both comments are valid, especially, second one is important.
> 
> Hmm, comment below.
> 
> > 
> > BTW, I updated probes/fixes. Could you also update this and rebase
> > on probes/fixes branch?
> > 
> > I'm working on the nesting and container_of support patches which
> > are on top of this.
> 
> OK, I'll make sure to use your probes branch. Which one should I use? your
> probes/for-next or probes/urgent?

I pushed forced-updated probes/for-next on probes/fixes.
So you can use  probes/for-next now.

Thanks,

-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>
Re: PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
Posted by Masami Hiramatsu (Google) 1 week, 1 day ago
On Sat, 30 May 2026 11:07:54 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:

> On Sat, 30 May 2026 23:14:27 +0900
> Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:
> 
> > Thanks, and Sashiko reviewed this. 
> > 
> > https://sashiko.dev/#/patchset/20260529110442.0967a64c%40fedora
> > 
> > I think both comments are valid, especially, second one is important.
> 
> Hmm, comment below.
> 
> > 
> > BTW, I updated probes/fixes. Could you also update this and rebase
> > on probes/fixes branch?
> > 
> > I'm working on the nesting and container_of support patches which
> > are on top of this.
> 
> OK, I'll make sure to use your probes branch. Which one should I use? your
> probes/for-next or probes/urgent?

Ah, probes/fixes. (I think I need to update probes/for-next to rebase on it)

> 
> As for sashiko's comments:
> 
> > commit c8393ea9936934c16ba2881d9e7b3cce86c678ff
> > Author: Steven Rostedt <rostedt@goodmis.org>
> > [PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
> > This commit introduces syntax to allow event probes to typecast pointer
> > fields to a structure using BTF. This enables dereferencing structures
> > using BTF names rather than having to manually calculate offsets.
> > > diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> > > index 39f040c863e85..0e87409a44033 100644
> > > --- a/kernel/trace/trace_probe.c
> > > +++ b/kernel/trace/trace_probe.c
> > [ ... ]
> > > @@ -640,7 +673,7 @@ static int parse_btf_arg(char *varname,
> > >  	int i, is_ptr, ret;
> > >  	u32 tid;
> > >  
> > > -	if (WARN_ON_ONCE(!ctx->funcname))
> > > +	if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)))
> > >  		return -EINVAL;
> > >  
> > >  	is_ptr = split_next_field(varname, &field, ctx);
> > > @@ -653,6 +686,16 @@ 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)
> > > +			return ret;
> 
> > Does this silently drop the error on parsing failure?
> > If parse_trace_event() fails and returns -EINVAL, it appears this path
> > returns the error without logging it.
> > Unlike parse_probe_vars(), which explicitly logs the failure when
> > parse_trace_event() fails, this path bypasses the log. Could this make
> > it difficult to debug invalid typecast expressions since nothing is written
> > to tracing/error_log?
> 
> Hmm, I guess I can put an error log here, as this is a unique path.

Yeah, just adding trace_probe_log_err(ctx->offset, BAD_VAR) seems OK.

> 
> > > +		if (WARN_ON_ONCE(!(ctx->flags & TPARG_FL_TYPECAST)))
> > > +			return -EINVAL;
> > > +		type = ctx->last_struct;
> > > +		goto found_type;
> > > +	}
> > > +
> > >  	if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) {
> > >  		code->op = FETCH_OP_RETVAL;
> > [ ... ]
> > > @@ -758,7 +802,71 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
> > >  	return 0;
> > >  }
> > >  
> > > -#else
> > > +static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
> > [ ... ]
> > > +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 = strchr(arg, ')');
> > > +	if (!tmp) {
> > > +		trace_probe_log_err(ctx->offset + strlen(arg),
> > > +				    DEREF_OPEN_BRACE);
> > > +		return -EINVAL;
> > > +	}
> > > +	*tmp = '\0';
> > > +	ret = query_btf_struct(arg + 1, ctx);
> > > +	*tmp = ')';
> > > +
> > > +	if (ret < 0) {
> > > +		trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
> > > +		ret = -EINVAL;
> > > +		goto out_put;
> > > +	}
> > > +
> > > +	ctx->flags |= TPARG_FL_TYPECAST;
> > > +	tmp++;
> > > +
> > > +	ctx->offset += tmp - arg;
> > > +	ret = parse_btf_arg(tmp, pcode, end, ctx);
> > > +	ctx->flags &= ~TPARG_FL_TYPECAST;
> > > +	ctx->last_struct = NULL;
> > > +out_put:
> > > +	btf_put(ctx->struct_btf);
> 
> > Does this prematurely release the BTF struct reference?
> > If TPARG_FL_TYPECAST is unset here and ctx->struct_btf is put, won't
> > later steps in traceprobe_parse_probe_arg_body() (like
> > find_fetch_type_from_btf_type()) fail to properly infer struct field sizes?
> > When ctx_btf(ctx) is called later without TPARG_FL_TYPECAST set, it
> > will evaluate to ctx->btf (which is NULL for eprobes).
> > Could this potentially lead to silent defaults, such as 64-bit reads for
> > smaller fields, or fail to inject pointer dereferences for string fields,
> > while also leaving ctx->last_type pointing to a prematurely released BTF
> > object?
> 
> Does this mean we need to set ctx->last_type to NULL here too?

No, since the member we refer can be different from unsigned long.
When we don't have ":type" suffix, we use BTF type information to
decide appropriate type.

> 
> Because everything above is pretty much the expected behavior. The put is
> *not* premature. The last_struct and struct_btf are both set to NULL. I
> guess the only thing missing is to reset last_type as well.

No, as I explained, the last_type is used to determine the member type
when user does not specify the ":type" suffix.

So, what we need to do is deferring the btf_put(struct_btf) as below:
(no build test yet.)

diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index adaa1e4fbdd6..9a73c49e22df 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -538,6 +538,10 @@ static void clear_btf_context(struct traceprobe_parse_context *ctx)
 		ctx->params = NULL;
 		ctx->nr_params = 0;
 	}
+	if (ctx->struct_btf) {
+		btf_put(ctx->struct_btf);
+		ctx->struct_btf = NULL;
+	}
 }
 
 /* Return 1 if the field separator is arrow operator ('->') */
@@ -802,22 +806,18 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
 
 static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
 {
+	struct btf *btf = NULL;
 	int id;
 
-	if (!ctx->struct_btf) {
-		struct btf *btf;
-
-		id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
-		if (id < 0)
-			return id;
-		ctx->struct_btf = btf;
-	} else {
-		id = btf_find_by_name_kind(ctx->struct_btf, sname, BTF_KIND_STRUCT);
-		if (id < 0)
-			return id;
+	if (ctx->struct_btf) {
+		btf_put(ctx->struct_btf);
+		ctx->struct_btf = NULL;
 	}
 
-	ctx->last_struct = btf_type_by_id(ctx->struct_btf, id);
+	id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
+	if (id < 0)
+		return id;
+	ctx->struct_btf = btf;
 	return 0;
 }
 
@@ -846,8 +846,7 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
 
 	if (ret < 0) {
 		trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
-		ret = -EINVAL;
-		goto out_put;
+		return ret;
 	}
 
 	ctx->flags |= TPARG_FL_TYPECAST;
@@ -857,9 +856,6 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
 	ret = parse_btf_arg(tmp, pcode, end, ctx);
 	ctx->flags &= ~TPARG_FL_TYPECAST;
 	ctx->last_struct = NULL;
-out_put:
-	btf_put(ctx->struct_btf);
-	ctx->struct_btf = NULL;
 	return ret;
 }
 

Thanks!

-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>
Re: PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
Posted by Steven Rostedt 6 days, 17 hours ago
On Sun, 31 May 2026 10:14:58 +0900
Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:

> > > Does this prematurely release the BTF struct reference?
> > > If TPARG_FL_TYPECAST is unset here and ctx->struct_btf is put, won't
> > > later steps in traceprobe_parse_probe_arg_body() (like
> > > find_fetch_type_from_btf_type()) fail to properly infer struct field sizes?
> > > When ctx_btf(ctx) is called later without TPARG_FL_TYPECAST set, it
> > > will evaluate to ctx->btf (which is NULL for eprobes).
> > > Could this potentially lead to silent defaults, such as 64-bit reads for
> > > smaller fields, or fail to inject pointer dereferences for string fields,
> > > while also leaving ctx->last_type pointing to a prematurely released BTF
> > > object?  
> > 
> > Does this mean we need to set ctx->last_type to NULL here too?  
> 
> No, since the member we refer can be different from unsigned long.
> When we don't have ":type" suffix, we use BTF type information to
> decide appropriate type.
> 
> > 
> > Because everything above is pretty much the expected behavior. The put is
> > *not* premature. The last_struct and struct_btf are both set to NULL. I
> > guess the only thing missing is to reset last_type as well.  
> 
> No, as I explained, the last_type is used to determine the member type
> when user does not specify the ":type" suffix.
> 
> So, what we need to do is deferring the btf_put(struct_btf) as below:
> (no build test yet.)

OK, but I don't think we want the struct_btf to exist beyond a single
arg like the btf descriptor does. How about this (on top of this change),
where it clears the struct_btf at the end of traceprobe_parse_probe_arg_body()?

Also, I see the flag as being redundant and use the existence of
struct_btf to denote that it's parsing a typedef struct.

-- Steve

diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
index 9246e9c3d066..56b7dc406ca1 100644
--- a/kernel/trace/trace_probe.c
+++ b/kernel/trace/trace_probe.c
@@ -397,8 +397,7 @@ static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type)
 
 static struct btf *ctx_btf(struct traceprobe_parse_context *ctx)
 {
-	return ctx->flags & TPARG_FL_TYPECAST ?
-		ctx->struct_btf : ctx->btf;
+	return ctx->struct_btf ? : ctx->btf;
 }
 
 static int check_prepare_btf_string_fetch(char *typename,
@@ -531,6 +530,15 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
 	return 0;
 }
 
+static void clear_struct_btf(struct traceprobe_parse_context *ctx)
+{
+	if (ctx->struct_btf) {
+		btf_put(ctx->struct_btf);
+		ctx->struct_btf = NULL;
+		ctx->last_struct = NULL;
+	}
+}
+
 static void clear_btf_context(struct traceprobe_parse_context *ctx)
 {
 	if (ctx->btf) {
@@ -579,7 +587,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
 	struct fetch_insn *code = *pcode;
 	const struct btf_member *field;
 	u32 bitoffs, anon_offs;
-	bool is_struct = ctx->flags & TPARG_FL_TYPECAST;
+	bool is_struct = ctx->struct_btf != NULL;
 	struct btf *btf = ctx_btf(ctx);
 	char *next;
 	int is_ptr;
@@ -690,7 +698,7 @@ static int parse_btf_arg(char *varname,
 		ret = parse_trace_event(varname, code, ctx);
 		if (ret < 0)
 			return ret;
-		if (WARN_ON_ONCE(!(ctx->flags & TPARG_FL_TYPECAST)))
+		if (WARN_ON_ONCE(ctx->struct_btf == NULL))
 			return -EINVAL;
 		type = ctx->last_struct;
 		goto found_type;
@@ -804,21 +812,19 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
 
 static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
 {
+	struct btf *btf = NULL;
 	int id;
 
-	if (!ctx->struct_btf) {
-		struct btf *btf;
-
-		id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
-		if (id < 0)
-			return id;
-		ctx->struct_btf = btf;
-	} else {
-		id = btf_find_by_name_kind(ctx->struct_btf, sname, BTF_KIND_STRUCT);
-		if (id < 0)
-			return id;
+	/* Could be a for a structure in a different module */
+	if (ctx->struct_btf) {
+		btf_put(ctx->struct_btf);
+		ctx->struct_btf = NULL;
 	}
 
+	id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
+	if (id < 0)
+		return id;
+	ctx->struct_btf = btf;
 	ctx->last_struct = btf_type_by_id(ctx->struct_btf, id);
 	return 0;
 }
@@ -848,25 +854,23 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
 
 	if (ret < 0) {
 		trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
-		ret = -EINVAL;
-		goto out_put;
+		return -EINVAL;
 	}
 
-	ctx->flags |= TPARG_FL_TYPECAST;
 	tmp++;
 
 	ctx->offset += tmp - arg;
 	ret = parse_btf_arg(tmp, pcode, end, ctx);
-	ctx->flags &= ~TPARG_FL_TYPECAST;
-	ctx->last_struct = NULL;
-out_put:
-	btf_put(ctx->struct_btf);
-	ctx->struct_btf = NULL;
 	return ret;
 }
 
 #else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */
 
+static void clear_struct_btf(struct traceprobe_parse_context *ctx)
+{
+	ctx->struct_btf = NULL;
+}
+
 static void clear_btf_context(struct traceprobe_parse_context *ctx)
 {
 	ctx->btf = NULL;
@@ -1673,6 +1677,9 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
 	}
 	kfree(tmp);
 
+	/* struct_btf should not be passed to other arguments */
+	clear_struct_btf(ctx);
+
 	return ret;
 }
 
diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
index 952e3d7582b8..83565f1634db 100644
--- a/kernel/trace/trace_probe.h
+++ b/kernel/trace/trace_probe.h
@@ -394,7 +394,6 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)
  * TPARG_FL_KERNEL and TPARG_FL_USER are also mutually exclusive.
  * TPARG_FL_FPROBE and TPARG_FL_TPOINT are optional but it should be with
  * TPARG_FL_KERNEL.
- * TPARG_FL_TYPECAST is set if an argument was typecast to a structure.
  */
 #define TPARG_FL_RETURN BIT(0)
 #define TPARG_FL_KERNEL BIT(1)
@@ -403,7 +402,6 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)
 #define TPARG_FL_USER   BIT(4)
 #define TPARG_FL_FPROBE BIT(5)
 #define TPARG_FL_TPOINT BIT(6)
-#define TPARG_FL_TYPECAST BIT(7)
 #define TPARG_FL_LOC_MASK	GENMASK(4, 0)
 
 static inline bool tparg_is_function_entry(unsigned int flags)
Re: PATCH v7] tracing/eprobes: Allow use of BTF names to dereference pointers
Posted by Masami Hiramatsu (Google) 6 days, 9 hours ago
On Mon, 1 Jun 2026 12:21:26 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:

> On Sun, 31 May 2026 10:14:58 +0900
> Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:
> 
> > > > Does this prematurely release the BTF struct reference?
> > > > If TPARG_FL_TYPECAST is unset here and ctx->struct_btf is put, won't
> > > > later steps in traceprobe_parse_probe_arg_body() (like
> > > > find_fetch_type_from_btf_type()) fail to properly infer struct field sizes?
> > > > When ctx_btf(ctx) is called later without TPARG_FL_TYPECAST set, it
> > > > will evaluate to ctx->btf (which is NULL for eprobes).
> > > > Could this potentially lead to silent defaults, such as 64-bit reads for
> > > > smaller fields, or fail to inject pointer dereferences for string fields,
> > > > while also leaving ctx->last_type pointing to a prematurely released BTF
> > > > object?  
> > > 
> > > Does this mean we need to set ctx->last_type to NULL here too?  
> > 
> > No, since the member we refer can be different from unsigned long.
> > When we don't have ":type" suffix, we use BTF type information to
> > decide appropriate type.
> > 
> > > 
> > > Because everything above is pretty much the expected behavior. The put is
> > > *not* premature. The last_struct and struct_btf are both set to NULL. I
> > > guess the only thing missing is to reset last_type as well.  
> > 
> > No, as I explained, the last_type is used to determine the member type
> > when user does not specify the ":type" suffix.
> > 
> > So, what we need to do is deferring the btf_put(struct_btf) as below:
> > (no build test yet.)
> 
> OK, but I don't think we want the struct_btf to exist beyond a single
> arg like the btf descriptor does. How about this (on top of this change),
> where it clears the struct_btf at the end of traceprobe_parse_probe_arg_body()?
> 
> Also, I see the flag as being redundant and use the existence of
> struct_btf to denote that it's parsing a typedef struct.

Ah, indeed. OK, let me check v8 patch.

Thanks!

> 
> -- Steve
> 
> diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> index 9246e9c3d066..56b7dc406ca1 100644
> --- a/kernel/trace/trace_probe.c
> +++ b/kernel/trace/trace_probe.c
> @@ -397,8 +397,7 @@ static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type)
>  
>  static struct btf *ctx_btf(struct traceprobe_parse_context *ctx)
>  {
> -	return ctx->flags & TPARG_FL_TYPECAST ?
> -		ctx->struct_btf : ctx->btf;
> +	return ctx->struct_btf ? : ctx->btf;
>  }
>  
>  static int check_prepare_btf_string_fetch(char *typename,
> @@ -531,6 +530,15 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
>  	return 0;
>  }
>  
> +static void clear_struct_btf(struct traceprobe_parse_context *ctx)
> +{
> +	if (ctx->struct_btf) {
> +		btf_put(ctx->struct_btf);
> +		ctx->struct_btf = NULL;
> +		ctx->last_struct = NULL;
> +	}
> +}
> +
>  static void clear_btf_context(struct traceprobe_parse_context *ctx)
>  {
>  	if (ctx->btf) {
> @@ -579,7 +587,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
>  	struct fetch_insn *code = *pcode;
>  	const struct btf_member *field;
>  	u32 bitoffs, anon_offs;
> -	bool is_struct = ctx->flags & TPARG_FL_TYPECAST;
> +	bool is_struct = ctx->struct_btf != NULL;
>  	struct btf *btf = ctx_btf(ctx);
>  	char *next;
>  	int is_ptr;
> @@ -690,7 +698,7 @@ static int parse_btf_arg(char *varname,
>  		ret = parse_trace_event(varname, code, ctx);
>  		if (ret < 0)
>  			return ret;
> -		if (WARN_ON_ONCE(!(ctx->flags & TPARG_FL_TYPECAST)))
> +		if (WARN_ON_ONCE(ctx->struct_btf == NULL))
>  			return -EINVAL;
>  		type = ctx->last_struct;
>  		goto found_type;
> @@ -804,21 +812,19 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
>  
>  static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
>  {
> +	struct btf *btf = NULL;
>  	int id;
>  
> -	if (!ctx->struct_btf) {
> -		struct btf *btf;
> -
> -		id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
> -		if (id < 0)
> -			return id;
> -		ctx->struct_btf = btf;
> -	} else {
> -		id = btf_find_by_name_kind(ctx->struct_btf, sname, BTF_KIND_STRUCT);
> -		if (id < 0)
> -			return id;
> +	/* Could be a for a structure in a different module */
> +	if (ctx->struct_btf) {
> +		btf_put(ctx->struct_btf);
> +		ctx->struct_btf = NULL;
>  	}
>  
> +	id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
> +	if (id < 0)
> +		return id;
> +	ctx->struct_btf = btf;
>  	ctx->last_struct = btf_type_by_id(ctx->struct_btf, id);
>  	return 0;
>  }
> @@ -848,25 +854,23 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
>  
>  	if (ret < 0) {
>  		trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
> -		ret = -EINVAL;
> -		goto out_put;
> +		return -EINVAL;
>  	}
>  
> -	ctx->flags |= TPARG_FL_TYPECAST;
>  	tmp++;
>  
>  	ctx->offset += tmp - arg;
>  	ret = parse_btf_arg(tmp, pcode, end, ctx);
> -	ctx->flags &= ~TPARG_FL_TYPECAST;
> -	ctx->last_struct = NULL;
> -out_put:
> -	btf_put(ctx->struct_btf);
> -	ctx->struct_btf = NULL;
>  	return ret;
>  }
>  
>  #else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */
>  
> +static void clear_struct_btf(struct traceprobe_parse_context *ctx)
> +{
> +	ctx->struct_btf = NULL;
> +}
> +
>  static void clear_btf_context(struct traceprobe_parse_context *ctx)
>  {
>  	ctx->btf = NULL;
> @@ -1673,6 +1677,9 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
>  	}
>  	kfree(tmp);
>  
> +	/* struct_btf should not be passed to other arguments */
> +	clear_struct_btf(ctx);
> +
>  	return ret;
>  }
>  
> diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
> index 952e3d7582b8..83565f1634db 100644
> --- a/kernel/trace/trace_probe.h
> +++ b/kernel/trace/trace_probe.h
> @@ -394,7 +394,6 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)
>   * TPARG_FL_KERNEL and TPARG_FL_USER are also mutually exclusive.
>   * TPARG_FL_FPROBE and TPARG_FL_TPOINT are optional but it should be with
>   * TPARG_FL_KERNEL.
> - * TPARG_FL_TYPECAST is set if an argument was typecast to a structure.
>   */
>  #define TPARG_FL_RETURN BIT(0)
>  #define TPARG_FL_KERNEL BIT(1)
> @@ -403,7 +402,6 @@ static inline int traceprobe_get_entry_data_size(struct trace_probe *tp)
>  #define TPARG_FL_USER   BIT(4)
>  #define TPARG_FL_FPROBE BIT(5)
>  #define TPARG_FL_TPOINT BIT(6)
> -#define TPARG_FL_TYPECAST BIT(7)
>  #define TPARG_FL_LOC_MASK	GENMASK(4, 0)
>  
>  static inline bool tparg_is_function_entry(unsigned int flags)


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>