mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-16 17:57:38 -04:00
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>
This commit is contained in:
committed by
Masami Hiramatsu (Google)
parent
585abc02be
commit
69efd863a7
@@ -46,6 +46,10 @@ Synopsis of eprobe_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
|
||||
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 '$'.
|
||||
|
||||
Types
|
||||
-----
|
||||
|
||||
@@ -332,6 +332,23 @@ static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int parse_trace_event(char *arg, struct fetch_insn *code,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (code->data)
|
||||
return -EFAULT;
|
||||
ret = parse_trace_event_arg(arg, code, ctx);
|
||||
if (!ret)
|
||||
return 0;
|
||||
if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
|
||||
code->op = FETCH_OP_COMM;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
|
||||
|
||||
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)
|
||||
&& BTF_INT_BITS(intdata) == 8;
|
||||
}
|
||||
|
||||
static struct btf *ctx_btf(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
return ctx->struct_btf ? : ctx->btf;
|
||||
}
|
||||
|
||||
static int check_prepare_btf_string_fetch(char *typename,
|
||||
struct fetch_insn **pcode,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = ctx->btf;
|
||||
struct btf *btf = ctx_btf(ctx);
|
||||
|
||||
if (!btf || !ctx->last_type)
|
||||
return 0;
|
||||
@@ -506,6 +528,15 @@ static int query_btf_context(struct traceprobe_parse_context *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void clear_struct_btf(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
if (ctx->struct_btf) {
|
||||
btf_put(ctx->struct_btf);
|
||||
ctx->struct_btf = NULL;
|
||||
ctx->last_struct = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_btf_context(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
if (ctx->btf) {
|
||||
@@ -554,22 +585,29 @@ 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->struct_btf != NULL;
|
||||
struct btf *btf = ctx_btf(ctx);
|
||||
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(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 {
|
||||
@@ -580,7 +618,7 @@ static int parse_btf_field(char *fieldname, const struct btf_type *type,
|
||||
return is_ptr;
|
||||
|
||||
anon_offs = 0;
|
||||
field = btf_find_struct_member(ctx->btf, type, fieldname,
|
||||
field = btf_find_struct_member(btf, type, fieldname,
|
||||
&anon_offs);
|
||||
if (IS_ERR(field)) {
|
||||
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,
|
||||
ctx->last_bitsize = 0;
|
||||
}
|
||||
|
||||
type = btf_type_skip_modifiers(ctx->btf, field->type, &tid);
|
||||
type = btf_type_skip_modifiers(btf, field->type, &tid);
|
||||
if (!type) {
|
||||
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
|
||||
return -EINVAL;
|
||||
@@ -640,7 +678,7 @@ static int parse_btf_arg(char *varname,
|
||||
int i, is_ptr, ret;
|
||||
u32 tid;
|
||||
|
||||
if (WARN_ON_ONCE(!ctx->funcname))
|
||||
if (WARN_ON_ONCE(!ctx->funcname && !(ctx->flags & TPARG_FL_TEVENT)))
|
||||
return -EINVAL;
|
||||
|
||||
is_ptr = split_next_field(varname, &field, ctx);
|
||||
@@ -653,6 +691,19 @@ static int parse_btf_arg(char *varname,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (ctx->flags & TPARG_FL_TEVENT) {
|
||||
ret = parse_trace_event(varname, code, ctx);
|
||||
if (ret < 0) {
|
||||
trace_probe_log_err(ctx->offset, BAD_ATTACH_ARG);
|
||||
return ret;
|
||||
}
|
||||
/* TEVENT is only here via a typecast */
|
||||
if (WARN_ON_ONCE(ctx->struct_btf == NULL))
|
||||
return -EINVAL;
|
||||
type = ctx->last_struct;
|
||||
goto found_type;
|
||||
}
|
||||
|
||||
if (ctx->flags & TPARG_FL_RETURN && !strcmp(varname, "$retval")) {
|
||||
code->op = FETCH_OP_RETVAL;
|
||||
/* Check whether the function return type is not void */
|
||||
@@ -709,6 +760,7 @@ static int parse_btf_arg(char *varname,
|
||||
|
||||
found:
|
||||
type = btf_type_skip_modifiers(ctx->btf, tid, &tid);
|
||||
found_type:
|
||||
if (!type) {
|
||||
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
|
||||
return -EINVAL;
|
||||
@@ -727,7 +779,7 @@ static int parse_btf_arg(char *varname,
|
||||
static const struct fetch_type *find_fetch_type_from_btf_type(
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = ctx->btf;
|
||||
struct btf *btf = ctx_btf(ctx);
|
||||
const char *typestr = NULL;
|
||||
|
||||
if (btf && ctx->last_type)
|
||||
@@ -758,7 +810,67 @@ static int parse_btf_bitfield(struct fetch_insn **pcode,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
static int query_btf_struct(const char *sname, struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = NULL;
|
||||
int id;
|
||||
|
||||
/* A struct_btf should only be used by a single argument */
|
||||
if (WARN_ON_ONCE(ctx->struct_btf)) {
|
||||
btf_put(ctx->struct_btf);
|
||||
ctx->struct_btf = NULL;
|
||||
}
|
||||
|
||||
id = bpf_find_btf_id(sname, BTF_KIND_STRUCT, &btf);
|
||||
if (id < 0)
|
||||
return id;
|
||||
ctx->struct_btf = btf;
|
||||
ctx->last_struct = btf_type_by_id(ctx->struct_btf, id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_typecast(char *arg, struct fetch_insn **pcode,
|
||||
struct fetch_insn *end,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
char *tmp;
|
||||
int ret;
|
||||
|
||||
/* Currently this only works for eprobes */
|
||||
if (!(ctx->flags & TPARG_FL_TEVENT)) {
|
||||
trace_probe_log_err(ctx->offset, TYPECAST_NOT_EVENT);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tmp = strchr(arg, ')');
|
||||
if (!tmp) {
|
||||
trace_probe_log_err(ctx->offset + strlen(arg),
|
||||
DEREF_OPEN_BRACE);
|
||||
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;
|
||||
}
|
||||
|
||||
tmp++;
|
||||
|
||||
ctx->offset += tmp - arg;
|
||||
ret = parse_btf_arg(tmp, pcode, end, ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#else /* !CONFIG_PROBE_EVENTS_BTF_ARGS */
|
||||
|
||||
static void clear_struct_btf(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
ctx->struct_btf = NULL;
|
||||
}
|
||||
|
||||
static void clear_btf_context(struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
ctx->btf = NULL;
|
||||
@@ -794,7 +906,15 @@ static int check_prepare_btf_string_fetch(char *typename,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
static int handle_typecast(char *arg, struct fetch_insn **pcode,
|
||||
struct fetch_insn *end,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PROBE_EVENTS_BTF_ARGS */
|
||||
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
|
||||
@@ -948,16 +1068,9 @@ static int parse_probe_vars(char *orig_arg, const struct fetch_type *t,
|
||||
int len;
|
||||
|
||||
if (ctx->flags & TPARG_FL_TEVENT) {
|
||||
if (code->data)
|
||||
return -EFAULT;
|
||||
ret = parse_trace_event_arg(arg, code, ctx);
|
||||
if (!ret)
|
||||
return 0;
|
||||
if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
|
||||
code->op = FETCH_OP_COMM;
|
||||
return 0;
|
||||
}
|
||||
goto inval;
|
||||
if (parse_trace_event(arg, code, ctx) < 0)
|
||||
goto inval;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (str_has_prefix(arg, "retval")) {
|
||||
@@ -1224,6 +1337,9 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
code->op = FETCH_OP_IMM;
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
ret = handle_typecast(arg, pcode, end, ctx);
|
||||
break;
|
||||
default:
|
||||
if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
|
||||
if (!tparg_is_function_entry(ctx->flags) &&
|
||||
@@ -1556,6 +1672,9 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
}
|
||||
kfree(tmp);
|
||||
|
||||
/* struct_btf should not be passed to other arguments */
|
||||
clear_struct_btf(ctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -422,7 +422,9 @@ struct traceprobe_parse_context {
|
||||
const struct btf_param *params; /* Parameter of the function */
|
||||
s32 nr_params; /* The number of the parameters */
|
||||
struct btf *btf; /* The BTF to be used */
|
||||
struct btf *struct_btf; /* The BTF to be used for structs */
|
||||
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;
|
||||
@@ -563,7 +565,8 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
|
||||
C(NEED_STRING_TYPE, "$comm and immediate-string only accepts string type"),\
|
||||
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(EVENT_TOO_BIG, "Event too big (too many fields?)"), \
|
||||
C(TYPECAST_NOT_EVENT, "Typecasts are only for eprobe fields"),
|
||||
|
||||
#undef C
|
||||
#define C(a, b) TP_ERR_##a
|
||||
|
||||
Reference in New Issue
Block a user