* Re: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
2026-05-19 3:23 [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers Steven Rostedt
@ 2026-05-19 9:34 ` kernel test robot
2026-05-19 9:53 ` Masami Hiramatsu
` (2 subsequent siblings)
3 siblings, 0 replies; 9+ messages in thread
From: kernel test robot @ 2026-05-19 9:34 UTC (permalink / raw)
To: Steven Rostedt, LKML, Linux Trace Kernel, bpf
Cc: oe-kbuild-all, Masami Hiramatsu, Mathieu Desnoyers, Mark Rutland,
Peter Zijlstra, Namhyung Kim, Takaya Saeki, Douglas Raillard,
Tom Zanussi, Andrew Morton, Linux Memory Management List,
Thomas Gleixner, Ian Rogers, Jiri Olsa, Subject:[PATCH v2]
Hi Steven,
kernel test robot noticed the following build errors:
[auto build test ERROR on trace/for-next]
[also build test ERROR on linus/master v7.1-rc4 next-20260518]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Steven-Rostedt/tracing-probes-Allow-use-of-BTF-names-to-dereference-pointers/20260519-121930
base: https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace for-next
patch link: https://lore.kernel.org/r/20260518232312.0c78f055%40gandalf.local.home
patch subject: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
config: sh-defconfig (https://download.01.org/0day-ci/archive/20260519/202605191710.jVjifK67-lkp@intel.com/config)
compiler: sh4-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260519/202605191710.jVjifK67-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605191710.jVjifK67-lkp@intel.com/
All errors (new ones prefixed by >>):
kernel/trace/trace_probe.c: In function 'parse_probe_arg':
>> kernel/trace/trace_probe.c:1289:23: error: implicit declaration of function 'query_btf_struct' [-Wimplicit-function-declaration]
1289 | ret = query_btf_struct(arg + 1, ctx);
| ^~~~~~~~~~~~~~~~
vim +/query_btf_struct +1289 kernel/trace/trace_probe.c
1120
1121 /* Recursive argument parser */
1122 static int
1123 parse_probe_arg(char *arg, const struct fetch_type *type,
1124 struct fetch_insn **pcode, struct fetch_insn *end,
1125 struct traceprobe_parse_context *ctx)
1126 {
1127 struct fetch_insn *code = *pcode;
1128 unsigned long param;
1129 int deref = FETCH_OP_DEREF;
1130 long offset = 0;
1131 char *tmp;
1132 int ret = 0;
1133
1134 switch (arg[0]) {
1135 case '$':
1136 ret = parse_probe_vars(arg, type, pcode, end, ctx);
1137 break;
1138
1139 case '%': /* named register */
1140 if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) {
1141 /* eprobe and fprobe do not handle registers */
1142 trace_probe_log_err(ctx->offset, BAD_VAR);
1143 break;
1144 }
1145 ret = regs_query_register_offset(arg + 1);
1146 if (ret >= 0) {
1147 code->op = FETCH_OP_REG;
1148 code->param = (unsigned int)ret;
1149 ret = 0;
1150 } else
1151 trace_probe_log_err(ctx->offset, BAD_REG_NAME);
1152 break;
1153
1154 case '@': /* memory, file-offset or symbol */
1155 if (isdigit(arg[1])) {
1156 ret = kstrtoul(arg + 1, 0, ¶m);
1157 if (ret) {
1158 trace_probe_log_err(ctx->offset, BAD_MEM_ADDR);
1159 break;
1160 }
1161 /* load address */
1162 code->op = FETCH_OP_IMM;
1163 code->immediate = param;
1164 } else if (arg[1] == '+') {
1165 /* kprobes don't support file offsets */
1166 if (ctx->flags & TPARG_FL_KERNEL) {
1167 trace_probe_log_err(ctx->offset, FILE_ON_KPROBE);
1168 return -EINVAL;
1169 }
1170 ret = kstrtol(arg + 2, 0, &offset);
1171 if (ret) {
1172 trace_probe_log_err(ctx->offset, BAD_FILE_OFFS);
1173 break;
1174 }
1175
1176 code->op = FETCH_OP_FOFFS;
1177 code->immediate = (unsigned long)offset; // imm64?
1178 } else {
1179 /* uprobes don't support symbols */
1180 if (!(ctx->flags & TPARG_FL_KERNEL)) {
1181 trace_probe_log_err(ctx->offset, SYM_ON_UPROBE);
1182 return -EINVAL;
1183 }
1184 /* Preserve symbol for updating */
1185 code->op = FETCH_NOP_SYMBOL;
1186 code->data = kstrdup(arg + 1, GFP_KERNEL);
1187 if (!code->data)
1188 return -ENOMEM;
1189 if (++code == end) {
1190 trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
1191 return -EINVAL;
1192 }
1193 code->op = FETCH_OP_IMM;
1194 code->immediate = 0;
1195 }
1196 /* These are fetching from memory */
1197 if (++code == end) {
1198 trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
1199 return -EINVAL;
1200 }
1201 *pcode = code;
1202 code->op = FETCH_OP_DEREF;
1203 code->offset = offset;
1204 break;
1205
1206 case '+': /* deref memory */
1207 case '-':
1208 if (arg[1] == 'u') {
1209 deref = FETCH_OP_UDEREF;
1210 arg[1] = arg[0];
1211 arg++;
1212 }
1213 if (arg[0] == '+')
1214 arg++; /* Skip '+', because kstrtol() rejects it. */
1215 tmp = strchr(arg, '(');
1216 if (!tmp) {
1217 trace_probe_log_err(ctx->offset, DEREF_NEED_BRACE);
1218 return -EINVAL;
1219 }
1220 *tmp = '\0';
1221 ret = kstrtol(arg, 0, &offset);
1222 if (ret) {
1223 trace_probe_log_err(ctx->offset, BAD_DEREF_OFFS);
1224 break;
1225 }
1226 ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
1227 arg = tmp + 1;
1228 tmp = strrchr(arg, ')');
1229 if (!tmp) {
1230 trace_probe_log_err(ctx->offset + strlen(arg),
1231 DEREF_OPEN_BRACE);
1232 return -EINVAL;
1233 } else {
1234 const struct fetch_type *t2 = find_fetch_type(NULL, ctx->flags);
1235 int cur_offs = ctx->offset;
1236
1237 *tmp = '\0';
1238 ret = parse_probe_arg(arg, t2, &code, end, ctx);
1239 if (ret)
1240 break;
1241 ctx->offset = cur_offs;
1242 if (code->op == FETCH_OP_COMM ||
1243 code->op == FETCH_OP_DATA) {
1244 trace_probe_log_err(ctx->offset, COMM_CANT_DEREF);
1245 return -EINVAL;
1246 }
1247 if (++code == end) {
1248 trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
1249 return -EINVAL;
1250 }
1251 *pcode = code;
1252
1253 code->op = deref;
1254 code->offset = offset;
1255 /* Reset the last type if used */
1256 ctx->last_type = NULL;
1257 }
1258 break;
1259 case '\\': /* Immediate value */
1260 if (arg[1] == '"') { /* Immediate string */
1261 ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2);
1262 if (ret)
1263 break;
1264 code->op = FETCH_OP_DATA;
1265 code->data = tmp;
1266 } else {
1267 ret = str_to_immediate(arg + 1, &code->immediate);
1268 if (ret)
1269 trace_probe_log_err(ctx->offset + 1, BAD_IMM);
1270 else
1271 code->op = FETCH_OP_IMM;
1272 }
1273 break;
1274 case '(':
1275 tmp = strrchr(arg, ')');
1276 if (!tmp) {
1277 trace_probe_log_err(ctx->offset + strlen(arg),
1278 DEREF_OPEN_BRACE);
1279 return -EINVAL;
1280 }
1281
1282 tmp--;
1283 if (*tmp != '*') {
1284 trace_probe_log_err(ctx->offset + (tmp - arg),
1285 NO_PTR_STRCT);
1286 return -EINVAL;
1287 }
1288 *tmp = '\0';
> 1289 ret = query_btf_struct(arg + 1, ctx);
1290 *tmp = '*';
1291
1292 if (ret < 0) {
1293 trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
1294 return -EINVAL;
1295 }
1296
1297 ctx->flags |= TPARG_FL_STRUCT;
1298 tmp += 2;
1299
1300 if (*tmp != '$') {
1301 trace_probe_log_err(ctx->offset + (tmp - arg),
1302 BAD_VAR);
1303 return -EINVAL;
1304 }
1305
1306 ctx->offset += tmp - arg;
1307 ret = parse_probe_vars(tmp, type, pcode, end, ctx);
1308 ctx->flags &= ~TPARG_FL_STRUCT;
1309 ctx->last_struct = NULL;
1310 break;
1311 default:
1312 if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
1313 if (!tparg_is_function_entry(ctx->flags) &&
1314 !tparg_is_function_return(ctx->flags)) {
1315 trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
1316 return -EINVAL;
1317 }
1318 ret = parse_btf_arg(arg, pcode, end, ctx);
1319 break;
1320 }
1321 }
1322 if (!ret && code->op == FETCH_OP_NOP) {
1323 /* Parsed, but do not find fetch method */
1324 trace_probe_log_err(ctx->offset, BAD_FETCH_ARG);
1325 ret = -EINVAL;
1326 }
1327 return ret;
1328 }
1329
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
2026-05-19 3:23 [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers Steven Rostedt
2026-05-19 9:34 ` kernel test robot
@ 2026-05-19 9:53 ` Masami Hiramatsu
2026-05-19 12:31 ` Steven Rostedt
2026-05-19 10:10 ` kernel test robot
[not found] ` <20260519040928.458C4C2BCB3@smtp.kernel.org>
3 siblings, 1 reply; 9+ messages in thread
From: Masami Hiramatsu @ 2026-05-19 9:53 UTC (permalink / raw)
To: Steven Rostedt
Cc: LKML, Linux Trace Kernel, bpf, 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 v2]", tracing/pr
On Mon, 18 May 2026 23:23:12 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> From: Steven Rostedt <rostedt@goodmis.org>
>
> Add syntax to the FETCHARGS parsing of probes to be able to typecast a
> value to 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, unless the member is a function parameter that BTF already has
> information about what structure the argument is pointing to.
>
> But for event probes, or generic kprobes that records a register that
> happens to be a pointer to a structure, they cannot dereference these
> values with BTF naming, but must use numerical offsets.
Thanks for updating!
>
> 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
Ah, eprobes supports "$PARAM" to access its parameter by name.
That is a bit complicated. Should we allow user to access
parameter without '$' prefix for eprobes?
> # 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"
Looks very nice!
>
> The syntax is simply: ([STRUCT]*)(VAR)->FIELD[->FIELD..]
Is the STRUCT optional?? (because [] means optional.) I guess no.
I think we maybe possible to skip '*' (Or, make it optional)
because this is not C-like typecasting, we don't support "struct"
reserved word, and it does not support white-spaces in each
fetcharg. In this case, (STRUCT)VAR->FIELD should work.
BTW, I'm also considering to support new cast syntax, which allows
us to derefer a pointer with "container_of". This is typically
used in the kernel.
We usually see this pattern:
struct {
unsigned long data;
struct list_head list;
} foo;
void callback(struct list_head *foo_list)
{
unsigned long data = container_of(foo_list, struct foo, list)->data;
...
}
To access @data, simple casting does not work. Thus we need a
new syntax:
(STRUCT)(PTR,ASSIGN)->FIELD
So the above case, we can do:
data=(foo)(foo_list,list)->data
This is naturally extend the type casting to support container_of()
equivalent casting.
>
> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
> ---
> Changes since v3: https://patch.msgid.link/20260518095832.52659a3a@gandalf.local.home
>
> *** COMPLETE REWRITE FROM V3 ***
>
> - Rewrote it to use typecasting instead of simply replacing BTF names with
> offsets.
>
> Documentation/trace/kprobetrace.rst | 3 +
> kernel/trace/trace_probe.c | 110 ++++++++++++++++++++++++----
> kernel/trace/trace_probe.h | 3 +
> 3 files changed, 100 insertions(+), 16 deletions(-)
>
> diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
> index 3b6791c17e9b..450ac646fe4c 100644
> --- a/Documentation/trace/kprobetrace.rst
> +++ b/Documentation/trace/kprobetrace.rst
> @@ -54,6 +54,9 @@ Synopsis of kprobe_events
> $retval : Fetch return value.(\*2)
> $comm : Fetch current task comm.
> +|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*3)(\*4)
> + (STRUCT*)FETCHARG->FIELD[->FIELD] : If BTF is supported, typecast FETCHARG to
> + a pointer to STRUCT and then derference the pointer defined by
> + ->FIELD.
> \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_probe.c b/kernel/trace/trace_probe.c
> index e0d3a0da26af..b0829eb1cb52 100644
> --- a/kernel/trace/trace_probe.c
> +++ b/kernel/trace/trace_probe.c
> @@ -464,6 +464,26 @@ static const char *fetch_type_from_btf_type(struct btf *btf,
> return NULL;
> }
>
> +static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
> +{
> + int id;
> +
> + if (!ctx->btf) {
> + struct btf *btf;
This needs an empty line here.
> + id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
> + if (id < 0)
> + return -EINVAL;
Why don't you return id (it has corresponding errno)?
> + ctx->btf = btf;
> + } else {
> + id = btf_find_by_name_kind(ctx->btf, sname, BTF_KIND_STRUCT);
> + if (id < 0)
> + return -EINVAL;
Ditto.
> + }
> +
> + ctx->last_struct = btf_type_by_id(ctx->btf, id);
> + return 0;
> +}
> +
> static int query_btf_context(struct traceprobe_parse_context *ctx)
> {
> const struct btf_param *param;
> @@ -471,12 +491,12 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
> struct btf *btf;
> s32 nr;
>
> - if (ctx->btf)
> - return 0;
> -
> if (!ctx->funcname)
> return -EINVAL;
>
> + if (ctx->btf)
> + return 0;
> +
Could you tell me why this order is changed?
I think this type casting will allow us to skip checking funcname
because btf context is already specified.
Ah, BTW, we may need to use a special struct btf* for type
casting. If the target function is in a module and the
casting type is defined in vmlinux, those are stored in
the different places...
for example,
p funcA (foo)$arg1->bar buz
In this case, buz needs to use BTF including funcA.
Maybe we need to introduce ctx->func_btf, which resets ctx->btf
in traceprobe_parse_probe_arg_body() where parse_probe_arg()
is calling, e.g.
ctx->last_type = NULL;
+ if (ctx->btf)
+ btf_put(ctx->btf);
+ ctx->btf = ctx->func_btf;
ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
ctx);
> type = btf_find_func_proto(ctx->funcname, &btf);
> if (!type)
> return -ENOENT;
> @@ -514,6 +534,7 @@ static void clear_btf_context(struct traceprobe_parse_context *ctx)
> ctx->proto = NULL;
> ctx->params = NULL;
> ctx->nr_params = 0;
> + ctx->last_struct = NULL;
> }
> }
>
> @@ -554,22 +575,28 @@ 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_STRUCT;
> 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(ctx->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 {
> @@ -635,12 +662,12 @@ static int parse_btf_arg(char *varname,
> {
> struct fetch_insn *code = *pcode;
> const struct btf_param *params;
> - const struct btf_type *type;
> + const struct btf_type *type = NULL;
> char *field = NULL;
> int i, is_ptr, ret;
> u32 tid;
>
> - if (WARN_ON_ONCE(!ctx->funcname))
> + if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_STRUCT)))
> return -EINVAL;
>
> is_ptr = split_next_field(varname, &field, ctx);
> @@ -704,11 +731,18 @@ static int parse_btf_arg(char *varname,
> goto found;
> }
> }
> +
> + if (ctx->flags & TPARG_FL_STRUCT) {
> + type = ctx->last_struct;
> + goto found;
I rather like to jump type_found: label instead of
checking !type. (Or, save tid instead of type)
> + }
> +
> trace_probe_log_err(ctx->offset, NO_BTFARG);
> return -ENOENT;
>
> found:
> - type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
> + if (!type)
> + type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
type_found:
> if (!type) {
> trace_probe_log_err(ctx->offset, BAD_BTF_TID);
> return -EINVAL;
> @@ -952,6 +986,12 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
> int ret = 0;
> int len;
>
> + if (ctx->flags & TPARG_FL_STRUCT) {
> + ret = parse_btf_arg(orig_arg, pcode, end, ctx);
> + if (ret < 0)
> + return ret;
> + }
> +
> if (ctx->flags & TPARG_FL_TEVENT) {
> if (code->data)
> return -EFAULT;
> @@ -1231,6 +1271,43 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
> code->op = FETCH_OP_IMM;
> }
> break;
> + case '(':
> + tmp = strrchr(arg, ')');
OK, in this step, we don't support nested cast etc. so this works.
> + if (!tmp) {
> + trace_probe_log_err(ctx->offset + strlen(arg),
> + DEREF_OPEN_BRACE);
> + return -EINVAL;
> + }
> +
> + tmp--;
> + if (*tmp != '*') {
> + trace_probe_log_err(ctx->offset + (tmp - arg),
> + NO_PTR_STRCT);
> + return -EINVAL;
> + }
So I think this can be optional, not an error.
> + *tmp = '\0';
> + ret = query_btf_struct(arg + 1, ctx);
> + *tmp = '*';
> +
> + if (ret < 0) {
> + trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
> + return -EINVAL;
> + }
> +
> + ctx->flags |= TPARG_FL_STRUCT;
> + tmp += 2;
> +
> + if (*tmp != '$') {
> + trace_probe_log_err(ctx->offset + (tmp - arg),
> + BAD_VAR);
> + return -EINVAL;
> + }
Ok, this limitation will be removed afterwards.
Thanks,
> +
> + ctx->offset += tmp - arg;
> + ret = parse_probe_vars(tmp, type, pcode, end, ctx);
> + ctx->flags &= ~TPARG_FL_STRUCT;
> + ctx->last_struct = NULL;
> + break;
> default:
> if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
> if (!tparg_is_function_entry(ctx->flags) &&
> @@ -1504,6 +1581,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
> code[FETCH_INSN_MAX - 1].op = FETCH_OP_END;
>
> ctx->last_type = NULL;
> + ctx->last_struct = NULL;
> ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
> ctx);
> if (ret < 0)
> diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h
> index 262d8707a3df..88ab9f6da591 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_STRUCT 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_STRUCT BIT(7)
> #define TPARG_FL_LOC_MASK GENMASK(4, 0)
>
> static inline bool tparg_is_function_entry(unsigned int flags)
> @@ -423,6 +425,7 @@ struct traceprobe_parse_context {
> s32 nr_params; /* The number of the parameters */
> struct btf *btf; /* The BTF to be used */
> 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;
> --
> 2.53.0
>
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
2026-05-19 9:53 ` Masami Hiramatsu
@ 2026-05-19 12:31 ` Steven Rostedt
2026-05-19 15:26 ` Masami Hiramatsu
0 siblings, 1 reply; 9+ messages in thread
From: Steven Rostedt @ 2026-05-19 12:31 UTC (permalink / raw)
To: Masami Hiramatsu (Google)
Cc: LKML, Linux Trace Kernel, bpf, Mathieu Desnoyers, Mark Rutland,
Peter Zijlstra, Namhyung Kim, Takaya Saeki, Douglas Raillard,
Tom Zanussi, Andrew Morton, Thomas Gleixner, Ian Rogers,
Jiri Olsa
On Tue, 19 May 2026 18:53:02 +0900
Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:
> > 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
>
> Ah, eprobes supports "$PARAM" to access its parameter by name.
> That is a bit complicated. Should we allow user to access
> parameter without '$' prefix for eprobes?
I guess.
>
> > # 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"
>
> Looks very nice!
>
> >
> > The syntax is simply: ([STRUCT]*)(VAR)->FIELD[->FIELD..]
>
> Is the STRUCT optional?? (because [] means optional.) I guess no.
Oops, no, I was tired when I wrote this, and just put '[' and ']' to make
it a variable. But I wasn't consistent. I'll fix that to be:
The syntax is simply: (STRUCT*)(VAR)->FIELD[->FIELD..]
>
> I think we maybe possible to skip '*' (Or, make it optional)
> because this is not C-like typecasting, we don't support "struct"
> reserved word, and it does not support white-spaces in each
> fetcharg. In this case, (STRUCT)VAR->FIELD should work.
I could remove the '*' as it doesn't support the "struct" C word.
>
> BTW, I'm also considering to support new cast syntax, which allows
> us to derefer a pointer with "container_of". This is typically
> used in the kernel.
>
> We usually see this pattern:
>
> struct {
> unsigned long data;
> struct list_head list;
> } foo;
>
> void callback(struct list_head *foo_list)
> {
> unsigned long data = container_of(foo_list, struct foo, list)->data;
> ...
> }
>
> To access @data, simple casting does not work. Thus we need a
> new syntax:
>
> (STRUCT)(PTR,ASSIGN)->FIELD
>
> So the above case, we can do:
>
> data=(foo)(foo_list,list)->data
Hmm, it may be better to make it one parenthesis?
(STRUCT,PTR,ASSIGN)->FIELD
data=(foo,foo_list,list)->data
That would make it easier to differentiate between a simple "typecast" and
a container_of() by checking if the content between the parenthesis has a
comma.
Maybe even reorder it to:
(PTR,STRUCT,ASSIGN)->FIELD
data=(foo_list,foo,list)->data
to match the order of container_of():
data = container_of(foo_list, struct foo, list)->data;
?
>
> This is naturally extend the type casting to support container_of()
> equivalent casting.
>
> >
> > Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
> > ---
> > Changes since v3: https://patch.msgid.link/20260518095832.52659a3a@gandalf.local.home
> >
> > *** COMPLETE REWRITE FROM V3 ***
> >
> > - Rewrote it to use typecasting instead of simply replacing BTF names with
> > offsets.
> >
> > Documentation/trace/kprobetrace.rst | 3 +
> > kernel/trace/trace_probe.c | 110 ++++++++++++++++++++++++----
> > kernel/trace/trace_probe.h | 3 +
> > 3 files changed, 100 insertions(+), 16 deletions(-)
> >
> > diff --git a/Documentation/trace/kprobetrace.rst b/Documentation/trace/kprobetrace.rst
> > index 3b6791c17e9b..450ac646fe4c 100644
> > --- a/Documentation/trace/kprobetrace.rst
> > +++ b/Documentation/trace/kprobetrace.rst
> > @@ -54,6 +54,9 @@ Synopsis of kprobe_events
> > $retval : Fetch return value.(\*2)
> > $comm : Fetch current task comm.
> > +|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*3)(\*4)
> > + (STRUCT*)FETCHARG->FIELD[->FIELD] : If BTF is supported, typecast FETCHARG to
> > + a pointer to STRUCT and then derference the pointer defined by
> > + ->FIELD.
> > \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_probe.c b/kernel/trace/trace_probe.c
> > index e0d3a0da26af..b0829eb1cb52 100644
> > --- a/kernel/trace/trace_probe.c
> > +++ b/kernel/trace/trace_probe.c
> > @@ -464,6 +464,26 @@ static const char *fetch_type_from_btf_type(struct btf *btf,
> > return NULL;
> > }
> >
> > +static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
> > +{
> > + int id;
> > +
> > + if (!ctx->btf) {
> > + struct btf *btf;
>
> This needs an empty line here.
Sure.
For conditional blocks, I don't always add a newline, but this is your code
and I'll follow your suggestions.
>
> > + id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
> > + if (id < 0)
> > + return -EINVAL;
>
> Why don't you return id (it has corresponding errno)?
Because I forgot to ;-)
>
> > + ctx->btf = btf;
> > + } else {
> > + id = btf_find_by_name_kind(ctx->btf, sname, BTF_KIND_STRUCT);
> > + if (id < 0)
> > + return -EINVAL;
>
> Ditto.
>
> > + }
> > +
> > + ctx->last_struct = btf_type_by_id(ctx->btf, id);
> > + return 0;
> > +}
> > +
> > static int query_btf_context(struct traceprobe_parse_context *ctx)
> > {
> > const struct btf_param *param;
> > @@ -471,12 +491,12 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
> > struct btf *btf;
> > s32 nr;
> >
> > - if (ctx->btf)
> > - return 0;
> > -
> > if (!ctx->funcname)
> > return -EINVAL;
> >
> > + if (ctx->btf)
> > + return 0;
> > +
>
> Could you tell me why this order is changed?
> I think this type casting will allow us to skip checking funcname
> because btf context is already specified.
I wanted this to fail if btf was already set but funcname wasn't, because
this should only be called for functions.
>
> Ah, BTW, we may need to use a special struct btf* for type
> casting. If the target function is in a module and the
> casting type is defined in vmlinux, those are stored in
> the different places...
OK, I'll make a separate btf for it then. I'll have to make sure the btf
used for parsing knows which one to use. Shouldn't be too hard if we check
for the STRUCT flag in the ctx->flags.
>
>
> for example,
>
> p funcA (foo)$arg1->bar buz
>
> In this case, buz needs to use BTF including funcA.
> Maybe we need to introduce ctx->func_btf, which resets ctx->btf
> in traceprobe_parse_probe_arg_body() where parse_probe_arg()
> is calling, e.g.
>
> ctx->last_type = NULL;
> + if (ctx->btf)
> + btf_put(ctx->btf);
> + ctx->btf = ctx->func_btf;
> ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
> ctx);
>
>
> > type = btf_find_func_proto(ctx->funcname, &btf);
> > if (!type)
> > return -ENOENT;
> > @@ -514,6 +534,7 @@ static void clear_btf_context(struct traceprobe_parse_context *ctx)
> > ctx->proto = NULL;
> > ctx->params = NULL;
> > ctx->nr_params = 0;
> > + ctx->last_struct = NULL;
> > }
> > }
> >
> > @@ -554,22 +575,28 @@ 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_STRUCT;
> > 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(ctx->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 {
> > @@ -635,12 +662,12 @@ static int parse_btf_arg(char *varname,
> > {
> > struct fetch_insn *code = *pcode;
> > const struct btf_param *params;
> > - const struct btf_type *type;
> > + const struct btf_type *type = NULL;
> > char *field = NULL;
> > int i, is_ptr, ret;
> > u32 tid;
> >
> > - if (WARN_ON_ONCE(!ctx->funcname))
> > + if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_STRUCT)))
> > return -EINVAL;
> >
> > is_ptr = split_next_field(varname, &field, ctx);
> > @@ -704,11 +731,18 @@ static int parse_btf_arg(char *varname,
> > goto found;
> > }
> > }
> > +
> > + if (ctx->flags & TPARG_FL_STRUCT) {
> > + type = ctx->last_struct;
> > + goto found;
>
> I rather like to jump type_found: label instead of
> checking !type. (Or, save tid instead of type)
>
OK.
> > + }
> > +
> > trace_probe_log_err(ctx->offset, NO_BTFARG);
> > return -ENOENT;
> >
> > found:
> > - type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
> > + if (!type)
> > + type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
>
> type_found:
>
> > if (!type) {
> > trace_probe_log_err(ctx->offset, BAD_BTF_TID);
> > return -EINVAL;
> > @@ -952,6 +986,12 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
> > int ret = 0;
> > int len;
> >
> > + if (ctx->flags & TPARG_FL_STRUCT) {
> > + ret = parse_btf_arg(orig_arg, pcode, end, ctx);
> > + if (ret < 0)
> > + return ret;
> > + }
> > +
> > if (ctx->flags & TPARG_FL_TEVENT) {
> > if (code->data)
> > return -EFAULT;
> > @@ -1231,6 +1271,43 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
> > code->op = FETCH_OP_IMM;
> > }
> > break;
> > + case '(':
> > + tmp = strrchr(arg, ')');
>
> OK, in this step, we don't support nested cast etc. so this works.
>
> > + if (!tmp) {
> > + trace_probe_log_err(ctx->offset + strlen(arg),
> > + DEREF_OPEN_BRACE);
> > + return -EINVAL;
> > + }
> > +
> > + tmp--;
> > + if (*tmp != '*') {
> > + trace_probe_log_err(ctx->offset + (tmp - arg),
> > + NO_PTR_STRCT);
> > + return -EINVAL;
> > + }
>
> So I think this can be optional, not an error.
I'll just remove it.
>
> > + *tmp = '\0';
> > + ret = query_btf_struct(arg + 1, ctx);
> > + *tmp = '*';
> > +
> > + if (ret < 0) {
> > + trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
> > + return -EINVAL;
> > + }
> > +
> > + ctx->flags |= TPARG_FL_STRUCT;
> > + tmp += 2;
> > +
> > + if (*tmp != '$') {
> > + trace_probe_log_err(ctx->offset + (tmp - arg),
> > + BAD_VAR);
> > + return -EINVAL;
> > + }
>
> Ok, this limitation will be removed afterwards.
Yeah.
Thanks for reviewing.
-- Steve
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
2026-05-19 12:31 ` Steven Rostedt
@ 2026-05-19 15:26 ` Masami Hiramatsu
2026-05-19 16:28 ` Steven Rostedt
0 siblings, 1 reply; 9+ messages in thread
From: Masami Hiramatsu @ 2026-05-19 15:26 UTC (permalink / raw)
To: Steven Rostedt
Cc: LKML, Linux Trace Kernel, bpf, Mathieu Desnoyers, Mark Rutland,
Peter Zijlstra, Namhyung Kim, Takaya Saeki, Douglas Raillard,
Tom Zanussi, Andrew Morton, Thomas Gleixner, Ian Rogers,
Jiri Olsa
On Tue, 19 May 2026 08:31:28 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> >
> > To access @data, simple casting does not work. Thus we need a
> > new syntax:
> >
> > (STRUCT)(PTR,ASSIGN)->FIELD
> >
> > So the above case, we can do:
> >
> > data=(foo)(foo_list,list)->data
>
> Hmm, it may be better to make it one parenthesis?
>
> (STRUCT,PTR,ASSIGN)->FIELD
>
> data=(foo,foo_list,list)->data
OK, but I don't like this 3 parameters conversion. I want to
make it a kind of type casting with an option.
(STRUCT,ASSIGN)PTR->FIELD
data=(foo,list)foo_list->data
The second parenthesis will be eventually needed for nested casting,
for example, in above case, if the data is a pointer to another data
structure:
struct bar {
int value;
...
};
value=(bar)((foo,list)foo_list->data)->value
>
> That would make it easier to differentiate between a simple "typecast" and
> a container_of() by checking if the content between the parenthesis has a
> comma.
>
> Maybe even reorder it to:
>
> (PTR,STRUCT,ASSIGN)->FIELD
>
> data=(foo_list,foo,list)->data
>
> to match the order of container_of():
>
> data = container_of(foo_list, struct foo, list)->data;
>
> ?
This doesn't seem to conform to the rule here of using parentheses for
type casting, so I personally don't like it.
> > > diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> > > index e0d3a0da26af..b0829eb1cb52 100644
> > > --- a/kernel/trace/trace_probe.c
> > > +++ b/kernel/trace/trace_probe.c
> > > @@ -464,6 +464,26 @@ static const char *fetch_type_from_btf_type(struct btf *btf,
> > > return NULL;
> > > }
> > >
> > > +static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
> > > +{
> > > + int id;
> > > +
> > > + if (!ctx->btf) {
> > > + struct btf *btf;
> >
> > This needs an empty line here.
>
> Sure.
>
> For conditional blocks, I don't always add a newline, but this is your code
> and I'll follow your suggestions.
Ah, this is just for fixing checkpatch.pl warning :-)
>
> >
> > > + id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
> > > + if (id < 0)
> > > + return -EINVAL;
> >
> > Why don't you return id (it has corresponding errno)?
>
> Because I forgot to ;-)
>
> >
> > > + ctx->btf = btf;
> > > + } else {
> > > + id = btf_find_by_name_kind(ctx->btf, sname, BTF_KIND_STRUCT);
> > > + if (id < 0)
> > > + return -EINVAL;
> >
> > Ditto.
> >
> > > + }
> > > +
> > > + ctx->last_struct = btf_type_by_id(ctx->btf, id);
> > > + return 0;
> > > +}
> > > +
> > > static int query_btf_context(struct traceprobe_parse_context *ctx)
> > > {
> > > const struct btf_param *param;
> > > @@ -471,12 +491,12 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
> > > struct btf *btf;
> > > s32 nr;
> > >
> > > - if (ctx->btf)
> > > - return 0;
> > > -
> > > if (!ctx->funcname)
> > > return -EINVAL;
> > >
> > > + if (ctx->btf)
> > > + return 0;
> > > +
> >
> > Could you tell me why this order is changed?
> > I think this type casting will allow us to skip checking funcname
> > because btf context is already specified.
>
> I wanted this to fail if btf was already set but funcname wasn't, because
> this should only be called for functions.
Hmm, OK. Then, can you make a separate patch for this?
>
> >
> > Ah, BTW, we may need to use a special struct btf* for type
> > casting. If the target function is in a module and the
> > casting type is defined in vmlinux, those are stored in
> > the different places...
>
> OK, I'll make a separate btf for it then. I'll have to make sure the btf
> used for parsing knows which one to use. Shouldn't be too hard if we check
> for the STRUCT flag in the ctx->flags.
Yeah, and personally, I think that flag should be the TYPECAST flag.
Thank you!
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
2026-05-19 15:26 ` Masami Hiramatsu
@ 2026-05-19 16:28 ` Steven Rostedt
2026-05-19 16:38 ` Steven Rostedt
0 siblings, 1 reply; 9+ messages in thread
From: Steven Rostedt @ 2026-05-19 16:28 UTC (permalink / raw)
To: Masami Hiramatsu (Google)
Cc: LKML, Linux Trace Kernel, bpf, Mathieu Desnoyers, Mark Rutland,
Peter Zijlstra, Namhyung Kim, Takaya Saeki, Douglas Raillard,
Tom Zanussi, Andrew Morton, Thomas Gleixner, Ian Rogers,
Jiri Olsa
On Wed, 20 May 2026 00:26:40 +0900
Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:
> > Hmm, it may be better to make it one parenthesis?
> >
> > (STRUCT,PTR,ASSIGN)->FIELD
> >
> > data=(foo,foo_list,list)->data
>
> OK, but I don't like this 3 parameters conversion. I want to
> make it a kind of type casting with an option.
>
> (STRUCT,ASSIGN)PTR->FIELD
>
> data=(foo,list)foo_list->data
>
> The second parenthesis will be eventually needed for nested casting,
> for example, in above case, if the data is a pointer to another data
> structure:
>
> struct bar {
> int value;
> ...
> };
>
> value=(bar)((foo,list)foo_list->data)->value
Have fun with the parenthesis parsing ;-)
>
>
> >
> > That would make it easier to differentiate between a simple "typecast" and
> > a container_of() by checking if the content between the parenthesis has a
> > comma.
> >
> > Maybe even reorder it to:
> >
> > (PTR,STRUCT,ASSIGN)->FIELD
> >
> > data=(foo_list,foo,list)->data
> >
> > to match the order of container_of():
> >
> > data = container_of(foo_list, struct foo, list)->data;
> >
> > ?
>
> This doesn't seem to conform to the rule here of using parentheses for
> type casting, so I personally don't like it.
OK, as long as it's intuitive and is easy to remember. I hate having to
look up the documents every time I have to do some probe argument
parsing :-(
>
>
> > > > diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> > > > index e0d3a0da26af..b0829eb1cb52 100644
> > > > --- a/kernel/trace/trace_probe.c
> > > > +++ b/kernel/trace/trace_probe.c
> > > > @@ -464,6 +464,26 @@ static const char *fetch_type_from_btf_type(struct btf *btf,
> > > > return NULL;
> > > > }
> > > >
> > > > +static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
> > > > +{
> > > > + int id;
> > > > +
> > > > + if (!ctx->btf) {
> > > > + struct btf *btf;
> > >
> > > This needs an empty line here.
> >
> > Sure.
> >
> > For conditional blocks, I don't always add a newline, but this is your code
> > and I'll follow your suggestions.
>
> Ah, this is just for fixing checkpatch.pl warning :-)
I added it to keep your checkpatch happy.
> > > > @@ -471,12 +491,12 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
> > > > struct btf *btf;
> > > > s32 nr;
> > > >
> > > > - if (ctx->btf)
> > > > - return 0;
> > > > -
> > > > if (!ctx->funcname)
> > > > return -EINVAL;
> > > >
> > > > + if (ctx->btf)
> > > > + return 0;
> > > > +
> > >
> > > Could you tell me why this order is changed?
> > > I think this type casting will allow us to skip checking funcname
> > > because btf context is already specified.
> >
> > I wanted this to fail if btf was already set but funcname wasn't, because
> > this should only be called for functions.
>
> Hmm, OK. Then, can you make a separate patch for this?
I added a struct_btf (I can change it to typecast_btf) and have a
helper function that is:
static struct btf *ctx_btf(struct traceprobe_parse_context *ctx)
{
return ctx->flags & TPARG_FL_STRUCT ?
ctx->struct_btf : ctx->btf;
}
And have all functions get their btf from that. This way we can keep
the two allocations separate.
>
> >
> > >
> > > Ah, BTW, we may need to use a special struct btf* for type
> > > casting. If the target function is in a module and the
> > > casting type is defined in vmlinux, those are stored in
> > > the different places...
> >
> > OK, I'll make a separate btf for it then. I'll have to make sure the btf
> > used for parsing knows which one to use. Shouldn't be too hard if we check
> > for the STRUCT flag in the ctx->flags.
>
> Yeah, and personally, I think that flag should be the TYPECAST flag.
I'll update it.
Thanks,
-- Steve
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
2026-05-19 16:28 ` Steven Rostedt
@ 2026-05-19 16:38 ` Steven Rostedt
0 siblings, 0 replies; 9+ messages in thread
From: Steven Rostedt @ 2026-05-19 16:38 UTC (permalink / raw)
To: Masami Hiramatsu (Google)
Cc: LKML, Linux Trace Kernel, bpf, Mathieu Desnoyers, Mark Rutland,
Peter Zijlstra, Namhyung Kim, Takaya Saeki, Douglas Raillard,
Tom Zanussi, Andrew Morton, Thomas Gleixner, Ian Rogers,
Jiri Olsa
On Tue, 19 May 2026 12:28:36 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> I added a struct_btf (I can change it to typecast_btf) and have a
> helper function that is:
Actually, I'm going to keep it as struct_btf as it is a btf for a
structure. I did change the flag to be TYPECAST though.
-- Steve
>
> static struct btf *ctx_btf(struct traceprobe_parse_context *ctx)
> {
> return ctx->flags & TPARG_FL_STRUCT ?
> ctx->struct_btf : ctx->btf;
> }
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
2026-05-19 3:23 [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers Steven Rostedt
2026-05-19 9:34 ` kernel test robot
2026-05-19 9:53 ` Masami Hiramatsu
@ 2026-05-19 10:10 ` kernel test robot
[not found] ` <20260519040928.458C4C2BCB3@smtp.kernel.org>
3 siblings, 0 replies; 9+ messages in thread
From: kernel test robot @ 2026-05-19 10:10 UTC (permalink / raw)
To: Steven Rostedt, LKML, Linux Trace Kernel, bpf
Cc: llvm, oe-kbuild-all, Masami Hiramatsu, Mathieu Desnoyers,
Mark Rutland, Peter Zijlstra, Namhyung Kim, Takaya Saeki,
Douglas Raillard, Tom Zanussi, Andrew Morton,
Linux Memory Management List, Thomas Gleixner, Ian Rogers,
Jiri Olsa, Subject:[PATCH v2]
Hi Steven,
kernel test robot noticed the following build errors:
[auto build test ERROR on trace/for-next]
[also build test ERROR on linus/master v7.1-rc4 next-20260518]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Steven-Rostedt/tracing-probes-Allow-use-of-BTF-names-to-dereference-pointers/20260519-121930
base: https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace for-next
patch link: https://lore.kernel.org/r/20260518232312.0c78f055%40gandalf.local.home
patch subject: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
config: sparc64-defconfig (https://download.01.org/0day-ci/archive/20260519/202605191828.Y3E73pH1-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260519/202605191828.Y3E73pH1-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605191828.Y3E73pH1-lkp@intel.com/
All errors (new ones prefixed by >>):
>> kernel/trace/trace_probe.c:1289:9: error: call to undeclared function 'query_btf_struct'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
1289 | ret = query_btf_struct(arg + 1, ctx);
| ^
1 error generated.
vim +/query_btf_struct +1289 kernel/trace/trace_probe.c
1120
1121 /* Recursive argument parser */
1122 static int
1123 parse_probe_arg(char *arg, const struct fetch_type *type,
1124 struct fetch_insn **pcode, struct fetch_insn *end,
1125 struct traceprobe_parse_context *ctx)
1126 {
1127 struct fetch_insn *code = *pcode;
1128 unsigned long param;
1129 int deref = FETCH_OP_DEREF;
1130 long offset = 0;
1131 char *tmp;
1132 int ret = 0;
1133
1134 switch (arg[0]) {
1135 case '$':
1136 ret = parse_probe_vars(arg, type, pcode, end, ctx);
1137 break;
1138
1139 case '%': /* named register */
1140 if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) {
1141 /* eprobe and fprobe do not handle registers */
1142 trace_probe_log_err(ctx->offset, BAD_VAR);
1143 break;
1144 }
1145 ret = regs_query_register_offset(arg + 1);
1146 if (ret >= 0) {
1147 code->op = FETCH_OP_REG;
1148 code->param = (unsigned int)ret;
1149 ret = 0;
1150 } else
1151 trace_probe_log_err(ctx->offset, BAD_REG_NAME);
1152 break;
1153
1154 case '@': /* memory, file-offset or symbol */
1155 if (isdigit(arg[1])) {
1156 ret = kstrtoul(arg + 1, 0, ¶m);
1157 if (ret) {
1158 trace_probe_log_err(ctx->offset, BAD_MEM_ADDR);
1159 break;
1160 }
1161 /* load address */
1162 code->op = FETCH_OP_IMM;
1163 code->immediate = param;
1164 } else if (arg[1] == '+') {
1165 /* kprobes don't support file offsets */
1166 if (ctx->flags & TPARG_FL_KERNEL) {
1167 trace_probe_log_err(ctx->offset, FILE_ON_KPROBE);
1168 return -EINVAL;
1169 }
1170 ret = kstrtol(arg + 2, 0, &offset);
1171 if (ret) {
1172 trace_probe_log_err(ctx->offset, BAD_FILE_OFFS);
1173 break;
1174 }
1175
1176 code->op = FETCH_OP_FOFFS;
1177 code->immediate = (unsigned long)offset; // imm64?
1178 } else {
1179 /* uprobes don't support symbols */
1180 if (!(ctx->flags & TPARG_FL_KERNEL)) {
1181 trace_probe_log_err(ctx->offset, SYM_ON_UPROBE);
1182 return -EINVAL;
1183 }
1184 /* Preserve symbol for updating */
1185 code->op = FETCH_NOP_SYMBOL;
1186 code->data = kstrdup(arg + 1, GFP_KERNEL);
1187 if (!code->data)
1188 return -ENOMEM;
1189 if (++code == end) {
1190 trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
1191 return -EINVAL;
1192 }
1193 code->op = FETCH_OP_IMM;
1194 code->immediate = 0;
1195 }
1196 /* These are fetching from memory */
1197 if (++code == end) {
1198 trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
1199 return -EINVAL;
1200 }
1201 *pcode = code;
1202 code->op = FETCH_OP_DEREF;
1203 code->offset = offset;
1204 break;
1205
1206 case '+': /* deref memory */
1207 case '-':
1208 if (arg[1] == 'u') {
1209 deref = FETCH_OP_UDEREF;
1210 arg[1] = arg[0];
1211 arg++;
1212 }
1213 if (arg[0] == '+')
1214 arg++; /* Skip '+', because kstrtol() rejects it. */
1215 tmp = strchr(arg, '(');
1216 if (!tmp) {
1217 trace_probe_log_err(ctx->offset, DEREF_NEED_BRACE);
1218 return -EINVAL;
1219 }
1220 *tmp = '\0';
1221 ret = kstrtol(arg, 0, &offset);
1222 if (ret) {
1223 trace_probe_log_err(ctx->offset, BAD_DEREF_OFFS);
1224 break;
1225 }
1226 ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
1227 arg = tmp + 1;
1228 tmp = strrchr(arg, ')');
1229 if (!tmp) {
1230 trace_probe_log_err(ctx->offset + strlen(arg),
1231 DEREF_OPEN_BRACE);
1232 return -EINVAL;
1233 } else {
1234 const struct fetch_type *t2 = find_fetch_type(NULL, ctx->flags);
1235 int cur_offs = ctx->offset;
1236
1237 *tmp = '\0';
1238 ret = parse_probe_arg(arg, t2, &code, end, ctx);
1239 if (ret)
1240 break;
1241 ctx->offset = cur_offs;
1242 if (code->op == FETCH_OP_COMM ||
1243 code->op == FETCH_OP_DATA) {
1244 trace_probe_log_err(ctx->offset, COMM_CANT_DEREF);
1245 return -EINVAL;
1246 }
1247 if (++code == end) {
1248 trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
1249 return -EINVAL;
1250 }
1251 *pcode = code;
1252
1253 code->op = deref;
1254 code->offset = offset;
1255 /* Reset the last type if used */
1256 ctx->last_type = NULL;
1257 }
1258 break;
1259 case '\\': /* Immediate value */
1260 if (arg[1] == '"') { /* Immediate string */
1261 ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2);
1262 if (ret)
1263 break;
1264 code->op = FETCH_OP_DATA;
1265 code->data = tmp;
1266 } else {
1267 ret = str_to_immediate(arg + 1, &code->immediate);
1268 if (ret)
1269 trace_probe_log_err(ctx->offset + 1, BAD_IMM);
1270 else
1271 code->op = FETCH_OP_IMM;
1272 }
1273 break;
1274 case '(':
1275 tmp = strrchr(arg, ')');
1276 if (!tmp) {
1277 trace_probe_log_err(ctx->offset + strlen(arg),
1278 DEREF_OPEN_BRACE);
1279 return -EINVAL;
1280 }
1281
1282 tmp--;
1283 if (*tmp != '*') {
1284 trace_probe_log_err(ctx->offset + (tmp - arg),
1285 NO_PTR_STRCT);
1286 return -EINVAL;
1287 }
1288 *tmp = '\0';
> 1289 ret = query_btf_struct(arg + 1, ctx);
1290 *tmp = '*';
1291
1292 if (ret < 0) {
1293 trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
1294 return -EINVAL;
1295 }
1296
1297 ctx->flags |= TPARG_FL_STRUCT;
1298 tmp += 2;
1299
1300 if (*tmp != '$') {
1301 trace_probe_log_err(ctx->offset + (tmp - arg),
1302 BAD_VAR);
1303 return -EINVAL;
1304 }
1305
1306 ctx->offset += tmp - arg;
1307 ret = parse_probe_vars(tmp, type, pcode, end, ctx);
1308 ctx->flags &= ~TPARG_FL_STRUCT;
1309 ctx->last_struct = NULL;
1310 break;
1311 default:
1312 if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
1313 if (!tparg_is_function_entry(ctx->flags) &&
1314 !tparg_is_function_return(ctx->flags)) {
1315 trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
1316 return -EINVAL;
1317 }
1318 ret = parse_btf_arg(arg, pcode, end, ctx);
1319 break;
1320 }
1321 }
1322 if (!ret && code->op == FETCH_OP_NOP) {
1323 /* Parsed, but do not find fetch method */
1324 trace_probe_log_err(ctx->offset, BAD_FETCH_ARG);
1325 ret = -EINVAL;
1326 }
1327 return ret;
1328 }
1329
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 9+ messages in thread[parent not found: <20260519040928.458C4C2BCB3@smtp.kernel.org>]
* Re: [PATCH v4] tracing/probes: Allow use of BTF names to dereference pointers
[not found] ` <20260519040928.458C4C2BCB3@smtp.kernel.org>
@ 2026-05-19 12:36 ` Steven Rostedt
0 siblings, 0 replies; 9+ messages in thread
From: Steven Rostedt @ 2026-05-19 12:36 UTC (permalink / raw)
To: sashiko-bot
Cc: sashiko-reviews, bpf, LKML, Linux Trace Kernel, Masami Hiramatsu
On Tue, 19 May 2026 04:09:27 +0000
sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [High] NULL pointer dereference in `parse_btf_arg()` when typecasting `$retval`
> - [High] Instruction stream corruption when typecasting BTF variables with field dereferences
> - [High] Parser state corruption breaks multi-argument probes and standard BTF parameter resolution
> - [Medium] Hardcoded syntax check strictly prohibits typecasting hardware registers and standard BTF arguments
> - [Medium] Using `strrchr` causes parsing failures for memory dereferences containing parentheses
> --
>
> commit 8ce6119a14206ed21706a773d9694dc24e53436e
> Author: Steven Rostedt <rostedt@goodmis.org>
>
> tracing/probes: Allow use of BTF names to dereference pointers
>
> This commit adds syntax to FETCHARG parsing to allow typecasting a value
> to a pointer to a structure. This allows users to rely on BTF to figure out
> member offsets instead of manually finding and hardcoding numerical offsets.
>
> > But for event probes, or generic kprobes that records a register that
> > happens to be a pointer to a structure, they cannot dereference these
> > values with BTF naming, but must use numerical offsets.
>
> Does the code actually support dereferencing a register here? Hardware
> registers (like %rdi) and standard BTF arguments (like skb) do not begin
> with a $ character, but the parser explicitly enforces that the cast is
> followed by a $.
Heh, it doesn't support it yet. But I plan on doing so. I'll either add the
support or fix the change log.
>
> > diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c
> > index e1c73065dae51..29b5a99074d76 100644
> > --- a/kernel/trace/trace_probe.c
> > +++ b/kernel/trace/trace_probe.c
> [ ... ]
> > @@ -471,12 +491,12 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
> > struct btf *btf;
> > s32 nr;
> >
> > - if (ctx->btf)
> > - return 0;
> > -
> > if (!ctx->funcname)
> > return -EINVAL;
> >
> > + if (ctx->btf)
> > + return 0;
> > +
>
> If a user creates a probe with a struct cast for the return value (like
> (struct task_struct*)$retval), query_btf_struct() resolves the struct and
> sets ctx->btf but leaves ctx->proto as NULL.
>
> When parse_btf_arg() later handles $retval, it calls query_btf_context(),
> which will return 0 here because ctx->btf is already set. Does this lead
> to parse_btf_arg() unconditionally dereferencing a NULL ctx->proto?
>
> Additionally, for multi-argument probes where the first argument is a cast
> (like arg1=(struct foo*)$arg1 arg2=skb), setting ctx->btf for the first
> argument causes this check to return early for the second argument. Could
> this skip populating ctx->params and ctx->nr_params, causing valid BTF
> arguments to be rejected?
Yeah, this will be fixed by Masami's suggestion of using a different btf
for the structure.
>
> [ ... ]
> > @@ -952,6 +986,12 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
> > int ret = 0;
> > int len;
> >
> > + if (ctx->flags & TPARG_FL_STRUCT) {
> > + ret = parse_btf_arg(orig_arg, pcode, end, ctx);
> > + if (ret < 0)
> > + return ret;
> > + }
> > +
>
> When parse_btf_arg() successfully parses a typecast with field dereferences,
> it emits a FETCH_OP_DEREF instruction and advances the *pcode pointer.
>
> Since this block doesn't return upon success, execution continues and matches
> the base variable name later in parse_probe_vars(). The local code pointer
> is not updated to reflect the advanced *pcode, so it writes a new instruction
> (like FETCH_OP_ARG) into the same slot.
>
> Will this overwrite the FETCH_OP_DEREF instruction and corrupt the trace probe
> bytecode?
Good catch. I need to move the code = *pcode; to after that if block.
>
> [ ... ]
> > @@ -1231,6 +1271,43 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
> > code->op = FETCH_OP_IMM;
> > }
> > break;
> > + case '(':
> > + tmp = strrchr(arg, ')');
>
> If a user attempts to cast a standard memory dereference that itself
> contains parentheses (such as (struct foo*)+0($arg1)), strrchr() will
> find the final parenthesis of $arg1).
Ah, I should have used strchr(). Thanks.
>
> Will this cause the pointer validation logic below to evaluate the wrong
> segment of the string and incorrectly fail with NO_PTR_STRCT?
>
> > + if (!tmp) {
> > + trace_probe_log_err(ctx->offset + strlen(arg),
> > + DEREF_OPEN_BRACE);
> > + return -EINVAL;
> > + }
> > +
> > + tmp--;
> > + if (*tmp != '*') {
> > + trace_probe_log_err(ctx->offset + (tmp - arg),
> > + NO_PTR_STRCT);
> > + 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);
> > + return -EINVAL;
> > + }
> > +
> > + ctx->flags |= TPARG_FL_STRUCT;
> > + tmp += 2;
> > +
> > + if (*tmp != '$') {
>
> Is this strict check for '$' intentional?
>
> As mentioned regarding the commit message, this prevents typecasting hardware
> registers and standard BTF arguments since they do not start with a '$',
> effectively restricting the feature to $retval, $stack, and $argN.
It was the only thing I tested. I'll remove it and test other options.
Thanks,
-- Steve
>
> > + trace_probe_log_err(ctx->offset + (tmp - arg),
> > + BAD_VAR);
> > + return -EINVAL;
> > + }
> > +
> > + ctx->offset += tmp - arg;
> > + ret = parse_probe_vars(tmp, type, pcode, end, ctx);
> > + ctx->flags &= ~TPARG_FL_STRUCT;
> > + ctx->last_struct = NULL;
> > + break;
>
^ permalink raw reply [flat|nested] 9+ messages in thread