mirror of
https://github.com/torvalds/linux.git
synced 2026-09-14 16:10:02 +02:00
tracing/probes: Support nested typecast
When we hit an open parenthesis right after typecast closing parenthesis, it means we have nested typecast. This allows us to typecast a generic data member in a structure to a pointer to another structure. For example, to cast a DATA_MEMBER of VAR structure to STRUCT pointer and get MEMBER value. (STRUCT)(VAR->DATA_MEMBER)->MEMBER Also, we can nest typecast. (STRUCT1)((STRUCT2)$ARG->FIELD2)->FIELD1 Currently the max nest level is limited to 3. This also allows user to use typecasting for registers or stacks on kprobe events. e.g. (STRUCT)(%ax)->MEMBER (STRUCT)($stack0)->MEMBER Link: https://lore.kernel.org/all/178271363855.1176915.16793301788257446529.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
This commit is contained in:
parent
438a8d6a82
commit
60432df556
|
|
@ -50,6 +50,8 @@ Synopsis of eprobe_events
|
|||
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 '$'.
|
||||
(STRUCT)(FETCHARG)->MEMBER[->MEMBER] : typecast can nest, so the above can
|
||||
also be used with another FETCHARG instead of FIELD.
|
||||
|
||||
Types
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ Synopsis of fprobe-events
|
|||
(STRUCT)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
|
||||
also be used with another FETCHARG instead of FIELD.
|
||||
|
||||
(\*1) This is available only when BTF is enabled.
|
||||
(\*2) only for the probe on function entry (offs == 0). Note, this argument access
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ Synopsis of kprobe_events
|
|||
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
|
||||
also be used with another FETCHARG instead of FIELD.
|
||||
|
||||
(\*1) only for the probe on function entry (offs == 0). Note, this argument access
|
||||
is best effort, because depending on the argument type, it may be passed on
|
||||
|
|
|
|||
|
|
@ -4323,6 +4323,7 @@ static const char readme_msg[] =
|
|||
"\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"
|
||||
#endif
|
||||
#else
|
||||
"\t $stack<index>, $stack, $retval, $comm,\n"
|
||||
|
|
|
|||
|
|
@ -832,10 +832,35 @@ static int query_btf_struct(const char *sname, struct traceprobe_parse_context *
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Find the matching closing parenthesis for a given opening parenthesis. */
|
||||
static char *find_matched_close_paren(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
int count = 0;
|
||||
|
||||
while (*p) {
|
||||
if (*p == '(')
|
||||
count++;
|
||||
else if (*p == ')') {
|
||||
if (--count == 0)
|
||||
return p;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
struct fetch_insn **pcode, struct fetch_insn *end,
|
||||
struct traceprobe_parse_context *ctx);
|
||||
|
||||
static int handle_typecast(char *arg, struct fetch_insn **pcode,
|
||||
struct fetch_insn *end,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
int orig_offset = ctx->offset;
|
||||
bool nested = false;
|
||||
char *tmp;
|
||||
int ret;
|
||||
|
||||
|
|
@ -852,19 +877,56 @@ static int handle_typecast(char *arg, struct fetch_insn **pcode,
|
|||
DEREF_OPEN_BRACE);
|
||||
return -EINVAL;
|
||||
}
|
||||
*tmp = '\0';
|
||||
ret = query_btf_struct(arg + 1, ctx);
|
||||
*tmp = ')';
|
||||
*tmp++ = '\0';
|
||||
|
||||
/* Handle the nested structure like (STRUCT)(VAR->FIELD)->... */
|
||||
if (*tmp == '(') {
|
||||
char *close = find_matched_close_paren(tmp);
|
||||
|
||||
ctx->offset += tmp - arg;
|
||||
if (!close) {
|
||||
trace_probe_log_err(ctx->offset, DEREF_OPEN_BRACE);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* We expect a field access for typecast */
|
||||
if (close[1] != '-' || close[2] != '>') {
|
||||
trace_probe_log_err(ctx->offset + close - tmp + 1,
|
||||
TYPECAST_REQ_FIELD);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctx->nested_level++;
|
||||
if (ctx->nested_level > TRACEPROBE_MAX_NESTED_LEVEL) {
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_NESTED);
|
||||
return -E2BIG;
|
||||
}
|
||||
*close = '\0';
|
||||
|
||||
ctx->offset += 1; /* for the '(' */
|
||||
/* We need to parse the nested one */
|
||||
ret = parse_probe_arg(tmp + 1, find_fetch_type(NULL, ctx->flags),
|
||||
pcode, end, ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ctx->nested_level--;
|
||||
clear_struct_btf(ctx);
|
||||
|
||||
tmp = close + 3;/* Skip "->" after closing parenthesis */
|
||||
nested = true;
|
||||
}
|
||||
|
||||
ret = query_btf_struct(arg + 1, ctx);
|
||||
if (ret < 0) {
|
||||
trace_probe_log_err(ctx->offset + 1, NO_PTR_STRCT);
|
||||
trace_probe_log_err(orig_offset + 1, NO_PTR_STRCT);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tmp++;
|
||||
|
||||
ctx->offset += tmp - arg;
|
||||
ret = parse_btf_arg(tmp, pcode, end, ctx);
|
||||
ctx->offset = orig_offset + tmp - arg;
|
||||
/* If it is nested, tmp points to the field name. */
|
||||
if (nested)
|
||||
ret = parse_btf_field(tmp, ctx->last_struct, pcode, end, ctx);
|
||||
else
|
||||
ret = parse_btf_arg(tmp, pcode, end, ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -1638,6 +1700,11 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
|||
ctx);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
/* nested_level must be 0 here, otherwise there is a bug. */
|
||||
if (WARN_ON_ONCE(ctx->nested_level)) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Update storing type if BTF is available */
|
||||
if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
|
||||
|
|
|
|||
|
|
@ -450,8 +450,11 @@ struct traceprobe_parse_context {
|
|||
struct trace_probe *tp;
|
||||
unsigned int flags;
|
||||
int offset;
|
||||
int nested_level;
|
||||
};
|
||||
|
||||
#define TRACEPROBE_MAX_NESTED_LEVEL 3
|
||||
|
||||
extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i,
|
||||
const char *argv,
|
||||
struct traceprobe_parse_context *ctx);
|
||||
|
|
@ -587,7 +590,9 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
|
|||
C(TOO_MANY_ARGS, "Too many arguments are specified"), \
|
||||
C(TOO_MANY_EARGS, "Too many entry arguments specified"), \
|
||||
C(EVENT_TOO_BIG, "Event too big (too many fields?)"), \
|
||||
C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe 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"),
|
||||
|
||||
#undef C
|
||||
#define C(a, b) TP_ERR_##a
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user