mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-27 15:17:04 -04:00
tracing/probes: Support field specifier option for typecast
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:
<idle>-0 [002] d.h1. 3778.087272: hrtimer_expire_entry: hrtimer=00000000d63db328 f
unction=tick_nohz_handler now=3777450051040
<idle>-0 [002] d.h1. 3778.087281: tick_nohz_handler__entry: (tick_nohz_handler+0x4
/0x140) next_tick=3777450000000
Link: https://lore.kernel.org/all/178271365745.1176915.725923927180862257.stgit@devnote2/
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4322,8 +4322,8 @@ static const char readme_msg[] =
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
|
||||
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
|
||||
"\t [(structname)]<argname>[->field[->field|.field...]],\n"
|
||||
"\t [(structname)](fetcharg)->field[->field|.field...],\n"
|
||||
"\t [(structname[,field])]<argname>[->field[->field|.field...]],\n"
|
||||
"\t [(structname[,field])](fetcharg)->field[->field|.field...],\n"
|
||||
#endif
|
||||
#else
|
||||
"\t $stack<index>, $stack, $retval, $comm,\n"
|
||||
|
||||
@@ -568,6 +568,64 @@ 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;
|
||||
|
||||
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, NULL);
|
||||
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.
|
||||
@@ -577,15 +635,13 @@ 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;
|
||||
|
||||
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);
|
||||
@@ -599,60 +655,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, NULL);
|
||||
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 */
|
||||
code->offset = bitoffs / 8;
|
||||
if (is_first_field && ctx->struct_btf) {
|
||||
/* The first field can be typecasted with field option. */
|
||||
code->offset -= ctx->prefix_byteoffs;
|
||||
}
|
||||
*pcode = code;
|
||||
|
||||
ctx->last_bitoffs = bitoffs % 8;
|
||||
ctx->last_type = type;
|
||||
is_first_field = false;
|
||||
} while (fieldname);
|
||||
|
||||
return 0;
|
||||
@@ -808,6 +829,46 @@ 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;
|
||||
}
|
||||
if (field != NULL) {
|
||||
/* this means @field skips an arrow operator ("->"). */
|
||||
trace_probe_log_err(ctx->offset - 2, TYPECAST_BAD_ARROW);
|
||||
return -EINVAL;
|
||||
}
|
||||
ctx->prefix_byteoffs = ret / 8;
|
||||
/* 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)
|
||||
{
|
||||
@@ -940,14 +1001,14 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
|
||||
tmp = close + 2; /* Skip ">" after inner variable name */
|
||||
|
||||
/* resolve the typecast struct name */
|
||||
ret = query_btf_struct(arg + 1, ctx);
|
||||
if (ret < 0) {
|
||||
trace_probe_log_err(orig_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;
|
||||
ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx);
|
||||
ctx->prefix_byteoffs = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -451,6 +451,7 @@ struct traceprobe_parse_context {
|
||||
unsigned int flags;
|
||||
int offset;
|
||||
int nested_level;
|
||||
int prefix_byteoffs; /* The byte offset of the prefix field of typecast */
|
||||
};
|
||||
|
||||
/* Each typecast consumes nested level. So the max number of typecast is 3. */
|
||||
@@ -594,7 +595,9 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
|
||||
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(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses")
|
||||
C(TYPECAST_SYM_OFFSET, "@SYM+/-OFFSET with typecast needs parentheses"), \
|
||||
C(TYPECAST_NOT_ALIGNED, "Typecast field option is not byte-aligned"), \
|
||||
C(TYPECAST_BAD_ARROW, "Typecast field option does not support -> operator"),
|
||||
|
||||
#undef C
|
||||
#define C(a, b) TP_ERR_##a
|
||||
|
||||
Reference in New Issue
Block a user