Skip to content

Commit 69efd86

Browse files
rostedtmhiramat
authored andcommitted
tracing/eprobes: Allow use of BTF names to dereference pointers
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. Link: https://lore.kernel.org/all/20260601130746.2139d926@gandalf.local.home/ Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org> Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
1 parent 585abc0 commit 69efd86

3 files changed

Lines changed: 154 additions & 28 deletions

File tree

Documentation/trace/eprobetrace.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Synopsis of eprobe_events
4646
(x8/x16/x32/x64), VFS layer common type(%pd/%pD), "char",
4747
"string", "ustring", "symbol", "symstr" and "bitfield" are
4848
supported.
49+
(STRUCT)FIELD->MEMBER[->MEMBER] : If BTF is supported, typecast FIELD to
50+
a pointer to STRUCT and then derference the pointer defined by
51+
->MEMBER. Note that when this is used, the FIELD name does not
52+
need to be prefixed with a '$'.
4953

5054
Types
5155
-----

kernel/trace/trace_probe.c

Lines changed: 146 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,23 @@ static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
332332
return -ENOENT;
333333
}
334334

335+
static int parse_trace_event(char *arg, struct fetch_insn *code,
336+
struct traceprobe_parse_context *ctx)
337+
{
338+
int ret;
339+
340+
if (code->data)
341+
return -EFAULT;
342+
ret = parse_trace_event_arg(arg, code, ctx);
343+
if (!ret)
344+
return 0;
345+
if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
346+
code->op = FETCH_OP_COMM;
347+
return 0;
348+
}
349+
return -EINVAL;
350+
}
351+
335352
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
336353

337354
static u32 btf_type_int(const struct btf_type *t)
@@ -376,11 +393,16 @@ static bool btf_type_is_char_array(struct btf *btf, const struct btf_type *type)
376393
&& BTF_INT_BITS(intdata) == 8;
377394
}
378395

396+
static struct btf *ctx_btf(struct traceprobe_parse_context *ctx)
397+
{
398+
return ctx->struct_btf ? : ctx->btf;
399+
}
400+
379401
static int check_prepare_btf_string_fetch(char *typename,
380402
struct fetch_insn **pcode,
381403
struct traceprobe_parse_context *ctx)
382404
{
383-
struct btf *btf = ctx->btf;
405+
struct btf *btf = ctx_btf(ctx);
384406

385407
if (!btf || !ctx->last_type)
386408
return 0;
@@ -506,6 +528,15 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
506528
return 0;
507529
}
508530

531+
static void clear_struct_btf(struct traceprobe_parse_context *ctx)
532+
{
533+
if (ctx->struct_btf) {
534+
btf_put(ctx->struct_btf);
535+
ctx->struct_btf = NULL;
536+
ctx->last_struct = NULL;
537+
}
538+
}
539+
509540
static void clear_btf_context(struct traceprobe_parse_context *ctx)
510541
{
511542
if (ctx->btf) {
@@ -554,22 +585,29 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
554585
struct fetch_insn *code = *pcode;
555586
const struct btf_member *field;
556587
u32 bitoffs, anon_offs;
588+
bool is_struct = ctx->struct_btf != NULL;
589+
struct btf *btf = ctx_btf(ctx);
557590
char *next;
558591
int is_ptr;
559592
s32 tid;
560593

561594
do {
562-
/* Outer loop for solving arrow operator ('->') */
563-
if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
564-
trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
565-
return -EINVAL;
566-
}
567-
/* Convert a struct pointer type to a struct type */
568-
type = btf_type_skip_modifiers(ctx->btf, type->type, &tid);
569-
if (!type) {
570-
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
571-
return -EINVAL;
595+
if (!is_struct) {
596+
/* Outer loop for solving arrow operator ('->') */
597+
if (BTF_INFO_KIND(type->info) != BTF_KIND_PTR) {
598+
trace_probe_log_err(ctx->offset, NO_PTR_STRCT);
599+
return -EINVAL;
600+
}
601+
602+
/* Convert a struct pointer type to a struct type */
603+
type = btf_type_skip_modifiers(btf, type->type, &tid);
604+
if (!type) {
605+
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
606+
return -EINVAL;
607+
}
572608
}
609+
/* Only the first type can skip being a pointer */
610+
is_struct = false;
573611

574612
bitoffs = 0;
575613
do {
@@ -580,7 +618,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
580618
return is_ptr;
581619

582620
anon_offs = 0;
583-
field = btf_find_struct_member(ctx->btf, type, fieldname,
621+
field = btf_find_struct_member(btf, type, fieldname,
584622
&anon_offs);
585623
if (IS_ERR(field)) {
586624
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
@@ -602,7 +640,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
602640
ctx->last_bitsize = 0;
603641
}
604642

605-
type = btf_type_skip_modifiers(ctx->btf, field->type, &tid);
643+
type = btf_type_skip_modifiers(btf, field->type, &tid);
606644
if (!type) {
607645
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
608646
return -EINVAL;
@@ -640,7 +678,7 @@ static int parse_btf_arg(char *varname,
640678
int i, is_ptr, ret;
641679
u32 tid;
642680

643-
if (WARN_ON_ONCE(!ctx->funcname))
681+
if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)))
644682
return -EINVAL;
645683

646684
is_ptr = split_next_field(varname, &field, ctx);
@@ -653,6 +691,19 @@ static int parse_btf_arg(char *varname,
653691
return -EOPNOTSUPP;
654692
}
655693

694+
if (ctx->flags & TPARG_FL_TEVENT) {
695+
ret = parse_trace_event(varname, code, ctx);
696+
if (ret < 0) {
697+
trace_probe_log_err(ctx->offset, BAD_ATTACH_ARG);
698+
return ret;
699+
}
700+
/* TEVENT is only here via a typecast */
701+
if (WARN_ON_ONCE(ctx->struct_btf == NULL))
702+
return -EINVAL;
703+
type = ctx->last_struct;
704+
goto found_type;
705+
}
706+
656707
if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) {
657708
code->op = FETCH_OP_RETVAL;
658709
/* Check whether the function return type is not void */
@@ -709,6 +760,7 @@ static int parse_btf_arg(char *varname,
709760

710761
found:
711762
type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
763+
found_type:
712764
if (!type) {
713765
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
714766
return -EINVAL;
@@ -727,7 +779,7 @@ static int parse_btf_arg(char *varname,
727779
static const struct fetch_type *find_fetch_type_from_btf_type(
728780
struct traceprobe_parse_context *ctx)
729781
{
730-
struct btf *btf = ctx->btf;
782+
struct btf *btf = ctx_btf(ctx);
731783
const char *typestr = NULL;
732784

733785
if (btf && ctx->last_type)
@@ -758,7 +810,67 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
758810
return 0;
759811
}
760812

761-
#else
813+
static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
814+
{
815+
struct btf *btf = NULL;
816+
int id;
817+
818+
/* A struct_btf should only be used by a single argument */
819+
if (WARN_ON_ONCE(ctx->struct_btf)) {
820+
btf_put(ctx->struct_btf);
821+
ctx->struct_btf = NULL;
822+
}
823+
824+
id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
825+
if (id < 0)
826+
return id;
827+
ctx->struct_btf = btf;
828+
ctx->last_struct = btf_type_by_id(ctx->struct_btf, id);
829+
return 0;
830+
}
831+
832+
static int handle_typecast(char *arg, struct fetch_insn **pcode,
833+
struct fetch_insn *end,
834+
struct traceprobe_parse_context *ctx)
835+
{
836+
char *tmp;
837+
int ret;
838+
839+
/* Currently this only works for eprobes */
840+
if (!(ctx->flags & TPARG_FL_TEVENT)) {
841+
trace_probe_log_err(ctx->offset, TYPECAST_NOT_EVENT);
842+
return -EINVAL;
843+
}
844+
845+
tmp = strchr(arg, ')');
846+
if (!tmp) {
847+
trace_probe_log_err(ctx->offset + strlen(arg),
848+
DEREF_OPEN_BRACE);
849+
return -EINVAL;
850+
}
851+
*tmp = '\0';
852+
ret = query_btf_struct(arg + 1, ctx);
853+
*tmp = ')';
854+
855+
if (ret < 0) {
856+
trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
857+
return -EINVAL;
858+
}
859+
860+
tmp++;
861+
862+
ctx->offset += tmp - arg;
863+
ret = parse_btf_arg(tmp, pcode, end, ctx);
864+
return ret;
865+
}
866+
867+
#else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */
868+
869+
static void clear_struct_btf(struct traceprobe_parse_context *ctx)
870+
{
871+
ctx->struct_btf = NULL;
872+
}
873+
762874
static void clear_btf_context(struct traceprobe_parse_context *ctx)
763875
{
764876
ctx->btf = NULL;
@@ -794,7 +906,15 @@ static int check_prepare_btf_string_fetch(char *typename,
794906
return 0;
795907
}
796908

797-
#endif
909+
static int handle_typecast(char *arg, struct fetch_insn **pcode,
910+
struct fetch_insn *end,
911+
struct traceprobe_parse_context *ctx)
912+
{
913+
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
914+
return -EOPNOTSUPP;
915+
}
916+
917+
#endif /* CONFIG_PROBE_EVENTS_BTF_ARGS */
798918

799919
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
800920

@@ -948,16 +1068,9 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
9481068
int len;
9491069

9501070
if (ctx->flags & TPARG_FL_TEVENT) {
951-
if (code->data)
952-
return -EFAULT;
953-
ret = parse_trace_event_arg(arg, code, ctx);
954-
if (!ret)
955-
return 0;
956-
if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
957-
code->op = FETCH_OP_COMM;
958-
return 0;
959-
}
960-
goto inval;
1071+
if (parse_trace_event(arg, code, ctx) < 0)
1072+
goto inval;
1073+
return 0;
9611074
}
9621075

9631076
if (str_has_prefix(arg, "retval")) {
@@ -1224,6 +1337,9 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
12241337
code->op = FETCH_OP_IMM;
12251338
}
12261339
break;
1340+
case '(':
1341+
ret = handle_typecast(arg, pcode, end, ctx);
1342+
break;
12271343
default:
12281344
if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
12291345
if (!tparg_is_function_entry(ctx->flags) &&
@@ -1556,6 +1672,9 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
15561672
}
15571673
kfree(tmp);
15581674

1675+
/* struct_btf should not be passed to other arguments */
1676+
clear_struct_btf(ctx);
1677+
15591678
return ret;
15601679
}
15611680

kernel/trace/trace_probe.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,9 @@ struct traceprobe_parse_context {
422422
const struct btf_param *params; /* Parameter of the function */
423423
s32 nr_params; /* The number of the parameters */
424424
struct btf *btf; /* The BTF to be used */
425+
struct btf *struct_btf; /* The BTF to be used for structs */
425426
const struct btf_type *last_type; /* Saved type */
427+
const struct btf_type *last_struct; /* Saved structure */
426428
u32 last_bitoffs; /* Saved bitoffs */
427429
u32 last_bitsize; /* Saved bitsize */
428430
struct trace_probe *tp;
@@ -563,7 +565,8 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
563565
C(NEED_STRING_TYPE, "$comm and immediate-string only accepts string type"),\
564566
C(TOO_MANY_ARGS, "Too many arguments are specified"), \
565567
C(TOO_MANY_EARGS, "Too many entry arguments specified"), \
566-
C(EVENT_TOO_BIG, "Event too big (too many fields?)"),
568+
C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \
569+
C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"),
567570

568571
#undef C
569572
#define C(a, b) TP_ERR_##a

0 commit comments

Comments
 (0)