From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (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 DFDCF3D34A4; Mon, 8 Jun 2026 14:24:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780928692; cv=none; b=hEAeOgImDtNd+zya1rkfXAmUz5/uXZFm19KZAAZFDahDGeHzdf6s4xdm7GSMWlcB6zkxymBjYH9uJ5rbxyxSOGTIBsxZUKL3Fi9YxosEcg/bw/lMv5S/8uJ0uPL5RNlrBfuocYZxJ+28S31ZX3HeSnJoS7k18ajtyjm/4kn0B1g= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780928692; c=relaxed/simple; bh=EFimOQdJA9cXFHekDnMJHoF8R6IirVT6k+ThEpOV0D8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=d60l66pV5yxUOvmna1LPqlpTsfDgNu56wG7GFYlYe7fzevRsQ3/Az3SNVoegpp1Hk5Q5EJ7FlMerrdWZ1AZeere6J/H57GdOiiEEbaGofjL/venw7EhuM5GLt85cSpyGEUlPqvhluN3fRKlAsBQ/Q1KXb40yJmVlzExFlFlU778= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=OwASHI6i; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="OwASHI6i" Received: by smtp.kernel.org (Postfix) with ESMTPSA id BC5051F00893; Mon, 8 Jun 2026 14:24:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1780928690; bh=fwiXlThn0od5ilLjGbcur1/XCtdOPyk7xKrb5RYE+bw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=OwASHI6iQaXHSRFBEFFWM07kiOxtVklAtzfaGh3FCN4ijNG+GyJ8lbvPEOvLJs/pu bjEk5YePgwwxb3cI8g0WAjvvryIu2Qm+yBhOKAwjJkO+aeub7RAldA3UT7P54hRrbn Q6CA7hulJ4BKov9FOw51HZi0lj/PnjbwR4FRBMpr40q5+SIabL1lq5xlr2+cni6WAs /qGinBknNVDwyMrCMwsIWF805Tt7CWKEC71a9zVrqMoPRQmK7SKImW7d3RhCJBuv6A D7MRD4hlvtVa4Y4b32xum+TQEZpwZ5qRU215Lxm1bxIG3pGtq3OgVAOZZDCWTymMHG Xr4AVO26v9kuQ== From: "Masami Hiramatsu (Google)" To: Steven Rostedt , Mathieu Desnoyers Cc: Jonathan Corbet , Shuah Khan , Masami Hiramatsu , linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org Subject: [RFC PATCH 3/7] tracing/probes: Support field specifier option for typecast Date: Mon, 8 Jun 2026 23:24:46 +0900 Message-ID: <178092868655.163648.6822850065797972881.stgit@devnote2> X-Mailer: git-send-email 2.43.0 In-Reply-To: <178092865666.163648.10457567771536160909.stgit@devnote2> References: <178092865666.163648.10457567771536160909.stgit@devnote2> User-Agent: StGit/0.19 Precedence: bulk X-Mailing-List: linux-trace-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit From: Masami Hiramatsu (Google) 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 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 | 4 + 6 files changed, 131 insertions(+), 67 deletions(-) diff --git a/Documentation/trace/eprobetrace.rst b/Documentation/trace/eprobetrace.rst index cd0b4aa7f896..680e0af43d5d 100644 --- a/Documentation/trace/eprobetrace.rst +++ b/Documentation/trace/eprobetrace.rst @@ -49,7 +49,10 @@ Synopsis of eprobe_events (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to a pointer to STRUCT and then derference the pointer defined by ->MEMBER. Note that when this is used, the FIELD name does not - need to be prefixed with a '$'. + need to be prefixed with a '$'. ASGN can be specified optionally. + If ASGN is specified, FIELD will be cast to the same offset + position as the ASGN member, rather than to the beginning of + the STRUCT. (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can also be used with another FETCHARG instead of FIELD. diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst index 6b8bb27bb62d..290a9e6f7491 100644 --- a/Documentation/trace/fprobetrace.rst +++ b/Documentation/trace/fprobetrace.rst @@ -57,10 +57,12 @@ Synopsis of fprobe-events (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types (x8/x16/x32/x64), "char", "string", "ustring", "symbol", "symstr" and bitfield are supported. - (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to + (STRUCT[,ASGN])FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to a pointer to STRUCT and then derference the pointer defined by - ->MEMBER. - (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can + ->MEMBER. ASGN can be specified optionally. If ASGN is specified, + FIELD will be cast to the same offset position as the ASGN member, + rather than to the beginning of the STRUCT. + (STRUCT[,ASGN])(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can also be used with another FETCHARG instead of FIELD. (\*1) This is available only when BTF is enabled. diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst index c4382765d5b2..a62707e6a9f2 100644 --- a/Documentation/trace/kprobetrace.rst +++ b/Documentation/trace/kprobetrace.rst @@ -61,11 +61,13 @@ Synopsis of kprobe_events (x8/x16/x32/x64), VFS layer common type(%pd/%pD), "char", "string", "ustring", "symbol", "symstr" and bitfield are supported. - (STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to + (STRUCT[,ASGN])FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to a pointer to STRUCT and then derference the pointer defined by ->MEMBER. Note that this is available only when the probe is - on function entry. - (STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can + on function entry. ASGN can be specified optionally. If ASGN + is specified, FIELD will be cast to the same offset position + as the ASGN member, rather than to the beginning of the STRUCT. + (STRUCT[,ASGN])(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can also be used with another FETCHARG instead of FIELD. (\*1) only for the probe on function entry (offs == 0). Note, this argument access diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 4f70318918c2..0e36af853199 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4325,8 +4325,8 @@ static const char readme_msg[] = #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API "\t $stack, $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 ddd9b1b63a17..ff0b619e9a90 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -574,6 +574,65 @@ static int split_next_field(char *varname, char **next_field, return ret; } +/* Inner loop for solving dot operator ('.'). Return bit-offset of the given field */ +static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **ptype, + struct traceprobe_parse_context *ctx) +{ + const struct btf_type *type = *ptype; + const struct btf_member *field; + struct btf *btf = ctx_btf(ctx); + char *fieldname = *pfieldname; + int bitoffs = 0; + u32 anon_offs; + char *next; + int is_ptr; + s32 tid; + + do { + next = NULL; + is_ptr = split_next_field(fieldname, &next, ctx); + if (is_ptr < 0) + return is_ptr; + + anon_offs = 0; + field = btf_find_struct_member(btf, type, fieldname, + &anon_offs); + if (IS_ERR(field)) { + trace_probe_log_err(ctx->offset, BAD_BTF_TID); + return PTR_ERR(field); + } + if (!field) { + trace_probe_log_err(ctx->offset, NO_BTF_FIELD); + return -ENOENT; + } + /* Add anonymous structure/union offset */ + bitoffs += anon_offs; + + /* Accumulate the bit-offsets of the dot-connected fields */ + if (btf_type_kflag(type)) { + bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset); + ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset); + } else { + bitoffs += field->offset; + ctx->last_bitsize = 0; + } + + type = btf_type_skip_modifiers(btf, field->type, &tid); + if (!type) { + trace_probe_log_err(ctx->offset, BAD_BTF_TID); + return -EINVAL; + } + + if (next) + ctx->offset += next - fieldname; + fieldname = next; + } while (!is_ptr && fieldname); + + *pfieldname = fieldname; + *ptype = type; + + return bitoffs; +} /* * Parse the field of data structure. The @type must be a pointer type * pointing the target data structure type. @@ -583,16 +642,14 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type, struct traceprobe_parse_context *ctx) { struct fetch_insn *code = *pcode; - const struct btf_member *field; - u32 bitoffs, anon_offs; - bool is_struct = ctx->struct_btf != NULL; struct btf *btf = ctx_btf(ctx); - char *next; - int is_ptr; + bool is_first_field = true; + int bitoffs; s32 tid; do { - if (!is_struct) { + /* For the first field of typecast, @type will be the target structure type. */ + if (!(is_first_field && ctx->struct_btf)) { /* Outer loop for solving arrow operator ('->') */ if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) { trace_probe_log_err(ctx->offset, NO_PTR_STRCT); @@ -606,60 +663,25 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type, return -EINVAL; } } - /* Only the first type can skip being a pointer */ - is_struct = false; - - bitoffs = 0; - do { - /* Inner loop for solving dot operator ('.') */ - next = NULL; - is_ptr = split_next_field(fieldname, &next, ctx); - if (is_ptr < 0) - return is_ptr; - - anon_offs = 0; - field = btf_find_struct_member(btf, type, fieldname, - &anon_offs); - if (IS_ERR(field)) { - trace_probe_log_err(ctx->offset, BAD_BTF_TID); - return PTR_ERR(field); - } - if (!field) { - trace_probe_log_err(ctx->offset, NO_BTF_FIELD); - return -ENOENT; - } - /* Add anonymous structure/union offset */ - bitoffs += anon_offs; - - /* Accumulate the bit-offsets of the dot-connected fields */ - if (btf_type_kflag(type)) { - bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset); - ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset); - } else { - bitoffs += field->offset; - ctx->last_bitsize = 0; - } - - type = btf_type_skip_modifiers(btf, field->type, &tid); - if (!type) { - trace_probe_log_err(ctx->offset, BAD_BTF_TID); - return -EINVAL; - } - - ctx->offset += next - fieldname; - fieldname = next; - } while (!is_ptr && fieldname); + bitoffs = get_bitoffset_of_field(&fieldname, &type, ctx); + if (bitoffs < 0) + return bitoffs; if (++code == end) { trace_probe_log_err(ctx->offset, TOO_MANY_OPS); return -EINVAL; } code->op = FETCH_OP_DEREF; /* TODO: user deref support */ + if (is_first_field && ctx->struct_btf) { + /* The first field can be typecasted with field option. */ + bitoffs -= ctx->prefix_bitoffs; + } code->offset = bitoffs / 8; *pcode = code; ctx->last_bitoffs = bitoffs % 8; ctx->last_type = type; + is_first_field = false; } while (fieldname); return 0; @@ -700,8 +722,7 @@ static int parse_btf_arg(char *varname, /* TEVENT is only here via a typecast */ if (WARN_ON_ONCE(ctx->struct_btf == NULL)) return -EINVAL; - type = ctx->last_struct; - goto found_type; + goto found; } if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) { @@ -763,7 +784,6 @@ static int parse_btf_arg(char *varname, type = ctx->last_struct; else type = btf_type_skip_modifiers(ctx->btf, tid, &tid); -found_type: if (!type) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); return -EINVAL; @@ -832,6 +852,41 @@ 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; + } + ctx->prefix_bitoffs = ret; + /* 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) { @@ -913,11 +968,10 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, nested = true; } - ret = query_btf_struct(arg + 1, ctx); - if (ret < 0) { - trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT); - return -EINVAL; - } + ctx->offset = orig_offset + 1; /* for the '(' */ + ret = parse_btf_casttype(arg + 1, ctx); + if (ret < 0) + return ret; ctx->offset = orig_offset + tmp - arg; /* If it is nested, tmp points to the field name. */ @@ -925,6 +979,7 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode, ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx); else ret = parse_btf_arg(tmp, pcode, end, ctx); + ctx->prefix_bitoffs = 0; return ret; } diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 8dcc65e4e1db..b1a54da3c761 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -431,6 +431,7 @@ struct traceprobe_parse_context { unsigned int flags; int offset; int nested_level; + int prefix_bitoffs; /* The bit offset of the prefix field of typecast */ }; #define TRACEPROBE_MAX_NESTED_LEVEL 3 @@ -571,7 +572,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_NOT_ALIGNED, "Typecast field option is not byte-aligned"), #undef C #define C(a, b) TP_ERR_##a