tracing/probes: Support function parameters if BTF is available

Support function or tracepoint parameters by name if BTF support is enabled
and the event is for function entry (this feature can be used with kprobe-
events, fprobe-events and tracepoint probe events.)

Note that the BTF variable syntax does not require a prefix. If it starts
with an alphabetic character or an underscore ('_') without a prefix like
'$' and '%', it is considered as a BTF variable.
If you specify only the BTF variable name, the argument name will also
be the same name instead of 'arg*'.

 # echo 'p vfs_read count pos' >> dynamic_events
 # echo 'f vfs_write count pos' >> dynamic_events
 # echo 't sched_overutilized_tp rd overutilized' >> dynamic_events
 # cat dynamic_events
p:kprobes/p_vfs_read_0 vfs_read count=count pos=pos
f:fprobes/vfs_write__entry vfs_write count=count pos=pos
t:tracepoints/sched_overutilized_tp sched_overutilized_tp rd=rd overutilized=overutilized

Link: https://lore.kernel.org/all/168507474014.913472.16963996883278039183.stgit@mhiramat.roam.corp.google.com/Signed-off-by: default avatarMasami Hiramatsu (Google) <mhiramat@kernel.org>
Reviewed-by: default avatarAlan Maguire <alan.maguire@oracle.com>
Tested-by: default avatarAlan Maguire <alan.maguire@oracle.com>
parent 1b8b0cd7
...@@ -664,6 +664,18 @@ config FPROBE_EVENTS ...@@ -664,6 +664,18 @@ config FPROBE_EVENTS
and the kprobe events on function entry and exit will be and the kprobe events on function entry and exit will be
transparently converted to this fprobe events. transparently converted to this fprobe events.
config PROBE_EVENTS_BTF_ARGS
depends on HAVE_FUNCTION_ARG_ACCESS_API
depends on FPROBE_EVENTS || KPROBE_EVENTS
depends on DEBUG_INFO_BTF && BPF_SYSCALL
bool "Support BTF function arguments for probe events"
default y
help
The user can specify the arguments of the probe event using the names
of the arguments of the probed function, when the probe location is a
kernel function entry or a tracepoint.
This is available only if BTF (BPF Type Format) support is enabled.
config KPROBE_EVENTS config KPROBE_EVENTS
depends on KPROBES depends on KPROBES
depends on HAVE_REGS_AND_STACK_ACCESS_API depends on HAVE_REGS_AND_STACK_ACCESS_API
......
...@@ -5698,7 +5698,11 @@ static const char readme_msg[] = ...@@ -5698,7 +5698,11 @@ static const char readme_msg[] =
"\t args: <name>=fetcharg[:type]\n" "\t args: <name>=fetcharg[:type]\n"
"\t fetcharg: (%<register>|$<efield>), @<address>, @<symbol>[+|-<offset>],\n" "\t fetcharg: (%<register>|$<efield>), @<address>, @<symbol>[+|-<offset>],\n"
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API #ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
"\t $stack<index>, $stack, $retval, $comm, $arg<N>, <argname>\n"
#else
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n" "\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
#endif
#else #else
"\t $stack<index>, $stack, $retval, $comm,\n" "\t $stack<index>, $stack, $retval, $comm,\n"
#endif #endif
......
...@@ -366,6 +366,7 @@ static void free_trace_fprobe(struct trace_fprobe *tf) ...@@ -366,6 +366,7 @@ static void free_trace_fprobe(struct trace_fprobe *tf)
static struct trace_fprobe *alloc_trace_fprobe(const char *group, static struct trace_fprobe *alloc_trace_fprobe(const char *group,
const char *event, const char *event,
const char *symbol, const char *symbol,
struct tracepoint *tpoint,
int maxactive, int maxactive,
int nargs, bool is_return) int nargs, bool is_return)
{ {
...@@ -385,6 +386,7 @@ static struct trace_fprobe *alloc_trace_fprobe(const char *group, ...@@ -385,6 +386,7 @@ static struct trace_fprobe *alloc_trace_fprobe(const char *group,
else else
tf->fp.entry_handler = fentry_dispatcher; tf->fp.entry_handler = fentry_dispatcher;
tf->tpoint = tpoint;
tf->fp.nr_maxactive = maxactive; tf->fp.nr_maxactive = maxactive;
ret = trace_probe_init(&tf->tp, event, group, false); ret = trace_probe_init(&tf->tp, event, group, false);
...@@ -930,8 +932,12 @@ static int __trace_fprobe_create(int argc, const char *argv[]) ...@@ -930,8 +932,12 @@ static int __trace_fprobe_create(int argc, const char *argv[])
int maxactive = 0; int maxactive = 0;
char buf[MAX_EVENT_NAME_LEN]; char buf[MAX_EVENT_NAME_LEN];
char gbuf[MAX_EVENT_NAME_LEN]; char gbuf[MAX_EVENT_NAME_LEN];
unsigned int flags = TPARG_FL_KERNEL | TPARG_FL_FPROBE; char sbuf[KSYM_NAME_LEN];
bool is_tracepoint = false; bool is_tracepoint = false;
struct tracepoint *tpoint = NULL;
struct traceprobe_parse_context ctx = {
.flags = TPARG_FL_KERNEL | TPARG_FL_FPROBE,
};
if ((argv[0][0] != 'f' && argv[0][0] != 't') || argc < 2) if ((argv[0][0] != 'f' && argv[0][0] != 't') || argc < 2)
return -ECANCELED; return -ECANCELED;
...@@ -995,14 +1001,6 @@ static int __trace_fprobe_create(int argc, const char *argv[]) ...@@ -995,14 +1001,6 @@ static int __trace_fprobe_create(int argc, const char *argv[])
goto parse_error; goto parse_error;
} }
if (is_return)
flags |= TPARG_FL_RETURN;
else
flags |= TPARG_FL_FENTRY;
if (is_tracepoint)
flags |= TPARG_FL_TPOINT;
trace_probe_log_set_index(0); trace_probe_log_set_index(0);
if (event) { if (event) {
ret = traceprobe_parse_event_name(&event, &group, gbuf, ret = traceprobe_parse_event_name(&event, &group, gbuf,
...@@ -1014,7 +1012,8 @@ static int __trace_fprobe_create(int argc, const char *argv[]) ...@@ -1014,7 +1012,8 @@ static int __trace_fprobe_create(int argc, const char *argv[])
if (!event) { if (!event) {
/* Make a new event name */ /* Make a new event name */
if (is_tracepoint) if (is_tracepoint)
strscpy(buf, symbol, MAX_EVENT_NAME_LEN); snprintf(buf, MAX_EVENT_NAME_LEN, "%s%s",
isdigit(*symbol) ? "_" : "", symbol);
else else
snprintf(buf, MAX_EVENT_NAME_LEN, "%s__%s", symbol, snprintf(buf, MAX_EVENT_NAME_LEN, "%s__%s", symbol,
is_return ? "exit" : "entry"); is_return ? "exit" : "entry");
...@@ -1022,8 +1021,27 @@ static int __trace_fprobe_create(int argc, const char *argv[]) ...@@ -1022,8 +1021,27 @@ static int __trace_fprobe_create(int argc, const char *argv[])
event = buf; event = buf;
} }
if (is_return)
ctx.flags |= TPARG_FL_RETURN;
else
ctx.flags |= TPARG_FL_FENTRY;
if (is_tracepoint) {
ctx.flags |= TPARG_FL_TPOINT;
tpoint = find_tracepoint(symbol);
if (!tpoint) {
trace_probe_log_set_index(1);
trace_probe_log_err(0, NO_TRACEPOINT);
goto parse_error;
}
ctx.funcname = kallsyms_lookup(
(unsigned long)tpoint->probestub,
NULL, NULL, NULL, sbuf);
} else
ctx.funcname = symbol;
/* setup a probe */ /* setup a probe */
tf = alloc_trace_fprobe(group, event, symbol, maxactive, tf = alloc_trace_fprobe(group, event, symbol, tpoint, maxactive,
argc - 2, is_return); argc - 2, is_return);
if (IS_ERR(tf)) { if (IS_ERR(tf)) {
ret = PTR_ERR(tf); ret = PTR_ERR(tf);
...@@ -1032,24 +1050,15 @@ static int __trace_fprobe_create(int argc, const char *argv[]) ...@@ -1032,24 +1050,15 @@ static int __trace_fprobe_create(int argc, const char *argv[])
goto out; /* We know tf is not allocated */ goto out; /* We know tf is not allocated */
} }
if (is_tracepoint) { if (is_tracepoint)
tf->tpoint = find_tracepoint(tf->symbol);
if (!tf->tpoint) {
trace_probe_log_set_index(1);
trace_probe_log_err(0, NO_TRACEPOINT);
goto parse_error;
}
tf->mod = __module_text_address( tf->mod = __module_text_address(
(unsigned long)tf->tpoint->probestub); (unsigned long)tf->tpoint->probestub);
}
argc -= 2; argv += 2; argc -= 2; argv += 2;
/* parse arguments */ /* parse arguments */
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) { for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {
struct traceprobe_parse_context ctx = { .flags = flags };
trace_probe_log_set_index(i + 2); trace_probe_log_set_index(i + 2);
ctx.offset = 0;
ret = traceprobe_parse_probe_arg(&tf->tp, i, argv[i], &ctx); ret = traceprobe_parse_probe_arg(&tf->tp, i, argv[i], &ctx);
if (ret) if (ret)
goto error; /* This can be -ENOMEM */ goto error; /* This can be -ENOMEM */
......
...@@ -742,7 +742,7 @@ static int __trace_kprobe_create(int argc, const char *argv[]) ...@@ -742,7 +742,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
void *addr = NULL; void *addr = NULL;
char buf[MAX_EVENT_NAME_LEN]; char buf[MAX_EVENT_NAME_LEN];
char gbuf[MAX_EVENT_NAME_LEN]; char gbuf[MAX_EVENT_NAME_LEN];
unsigned int flags = TPARG_FL_KERNEL; struct traceprobe_parse_context ctx = { .flags = TPARG_FL_KERNEL };
switch (argv[0][0]) { switch (argv[0][0]) {
case 'r': case 'r':
...@@ -823,10 +823,10 @@ static int __trace_kprobe_create(int argc, const char *argv[]) ...@@ -823,10 +823,10 @@ static int __trace_kprobe_create(int argc, const char *argv[])
goto parse_error; goto parse_error;
} }
if (is_return) if (is_return)
flags |= TPARG_FL_RETURN; ctx.flags |= TPARG_FL_RETURN;
ret = kprobe_on_func_entry(NULL, symbol, offset); ret = kprobe_on_func_entry(NULL, symbol, offset);
if (ret == 0 && !is_return) if (ret == 0 && !is_return)
flags |= TPARG_FL_FENTRY; ctx.flags |= TPARG_FL_FENTRY;
/* Defer the ENOENT case until register kprobe */ /* Defer the ENOENT case until register kprobe */
if (ret == -EINVAL && is_return) { if (ret == -EINVAL && is_return) {
trace_probe_log_err(0, BAD_RETPROBE); trace_probe_log_err(0, BAD_RETPROBE);
...@@ -856,7 +856,7 @@ static int __trace_kprobe_create(int argc, const char *argv[]) ...@@ -856,7 +856,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
/* setup a probe */ /* setup a probe */
tk = alloc_trace_kprobe(group, event, addr, symbol, offset, maxactive, tk = alloc_trace_kprobe(group, event, addr, symbol, offset, maxactive,
argc - 2, is_return); argc - 2, is_return);
if (IS_ERR(tk)) { if (IS_ERR(tk)) {
ret = PTR_ERR(tk); ret = PTR_ERR(tk);
/* This must return -ENOMEM, else there is a bug */ /* This must return -ENOMEM, else there is a bug */
...@@ -866,10 +866,10 @@ static int __trace_kprobe_create(int argc, const char *argv[]) ...@@ -866,10 +866,10 @@ static int __trace_kprobe_create(int argc, const char *argv[])
argc -= 2; argv += 2; argc -= 2; argv += 2;
/* parse arguments */ /* parse arguments */
ctx.funcname = symbol;
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) { for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {
struct traceprobe_parse_context ctx = { .flags = flags };
trace_probe_log_set_index(i + 2); trace_probe_log_set_index(i + 2);
ctx.offset = 0;
ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], &ctx); ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], &ctx);
if (ret) if (ret)
goto error; /* This can be -ENOMEM */ goto error; /* This can be -ENOMEM */
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
*/ */
#define pr_fmt(fmt) "trace_probe: " fmt #define pr_fmt(fmt) "trace_probe: " fmt
#include <linux/bpf.h>
#include "trace_probe.h" #include "trace_probe.h"
#undef C #undef C
...@@ -300,6 +302,171 @@ static int parse_trace_event_arg(char *arg, struct fetch_insn *code, ...@@ -300,6 +302,171 @@ static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
return -ENOENT; return -ENOENT;
} }
#ifdef CONFIG_PROBE_EVENTS_BTF_ARGS
static struct btf *traceprobe_get_btf(void)
{
struct btf *btf = bpf_get_btf_vmlinux();
if (IS_ERR_OR_NULL(btf))
return NULL;
return btf;
}
static u32 btf_type_int(const struct btf_type *t)
{
return *(u32 *)(t + 1);
}
static const char *type_from_btf_id(struct btf *btf, s32 id)
{
const struct btf_type *t;
u32 intdata;
s32 tid;
/* TODO: const char * could be converted as a string */
t = btf_type_skip_modifiers(btf, id, &tid);
switch (BTF_INFO_KIND(t->info)) {
case BTF_KIND_ENUM:
/* enum is "int", so convert to "s32" */
return "s32";
case BTF_KIND_ENUM64:
return "s64";
case BTF_KIND_PTR:
/* pointer will be converted to "x??" */
if (IS_ENABLED(CONFIG_64BIT))
return "x64";
else
return "x32";
case BTF_KIND_INT:
intdata = btf_type_int(t);
if (BTF_INT_ENCODING(intdata) & BTF_INT_SIGNED) {
switch (BTF_INT_BITS(intdata)) {
case 8:
return "s8";
case 16:
return "s16";
case 32:
return "s32";
case 64:
return "s64";
}
} else { /* unsigned */
switch (BTF_INT_BITS(intdata)) {
case 8:
return "u8";
case 16:
return "u16";
case 32:
return "u32";
case 64:
return "u64";
}
}
}
/* TODO: support other types */
return NULL;
}
static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr)
{
struct btf *btf = traceprobe_get_btf();
const struct btf_type *t;
s32 id;
if (!btf || !funcname || !nr)
return ERR_PTR(-EINVAL);
id = btf_find_by_name_kind(btf, funcname, BTF_KIND_FUNC);
if (id <= 0)
return ERR_PTR(-ENOENT);
/* Get BTF_KIND_FUNC type */
t = btf_type_by_id(btf, id);
if (!btf_type_is_func(t))
return ERR_PTR(-ENOENT);
/* The type of BTF_KIND_FUNC is BTF_KIND_FUNC_PROTO */
t = btf_type_by_id(btf, t->type);
if (!btf_type_is_func_proto(t))
return ERR_PTR(-ENOENT);
*nr = btf_type_vlen(t);
if (*nr)
return (const struct btf_param *)(t + 1);
else
return NULL;
}
static int parse_btf_arg(const char *varname, struct fetch_insn *code,
struct traceprobe_parse_context *ctx)
{
struct btf *btf = traceprobe_get_btf();
const struct btf_param *params;
int i;
if (!btf) {
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
return -EOPNOTSUPP;
}
if (WARN_ON_ONCE(!ctx->funcname))
return -EINVAL;
if (!ctx->params) {
params = find_btf_func_param(ctx->funcname, &ctx->nr_params);
if (IS_ERR(params)) {
trace_probe_log_err(ctx->offset, NO_BTF_ENTRY);
return PTR_ERR(params);
}
ctx->params = params;
} else
params = ctx->params;
for (i = 0; i < ctx->nr_params; i++) {
const char *name = btf_name_by_offset(btf, params[i].name_off);
if (name && !strcmp(name, varname)) {
code->op = FETCH_OP_ARG;
code->param = i;
return 0;
}
}
trace_probe_log_err(ctx->offset, NO_BTFARG);
return -ENOENT;
}
static const struct fetch_type *parse_btf_arg_type(int arg_idx,
struct traceprobe_parse_context *ctx)
{
struct btf *btf = traceprobe_get_btf();
const char *typestr = NULL;
if (btf && ctx->params)
typestr = type_from_btf_id(btf, ctx->params[arg_idx].type);
return find_fetch_type(typestr, ctx->flags);
}
#else
static struct btf *traceprobe_get_btf(void)
{
return NULL;
}
static int parse_btf_arg(const char *varname, struct fetch_insn *code,
struct traceprobe_parse_context *ctx)
{
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
return -EOPNOTSUPP;
}
#define parse_btf_arg_type(idx, ctx) \
find_fetch_type(NULL, ctx->flags)
#endif
#define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
static int parse_probe_vars(char *arg, const struct fetch_type *t, static int parse_probe_vars(char *arg, const struct fetch_type *t,
...@@ -570,6 +737,15 @@ parse_probe_arg(char *arg, const struct fetch_type *type, ...@@ -570,6 +737,15 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
code->op = FETCH_OP_IMM; code->op = FETCH_OP_IMM;
} }
break; break;
default:
if (isalpha(arg[0]) || arg[0] == '_') { /* BTF variable */
if (!tparg_is_function_entry(ctx->flags)) {
trace_probe_log_err(ctx->offset, NOSUP_BTFARG);
return -EINVAL;
}
ret = parse_btf_arg(arg, code, ctx);
break;
}
} }
if (!ret && code->op == FETCH_OP_NOP) { if (!ret && code->op == FETCH_OP_NOP) {
/* Parsed, but do not find fetch method */ /* Parsed, but do not find fetch method */
...@@ -718,6 +894,11 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size, ...@@ -718,6 +894,11 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
if (ret) if (ret)
goto fail; goto fail;
/* Update storing type if BTF is available */
if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) &&
!t && code->op == FETCH_OP_ARG)
parg->type = parse_btf_arg_type(code->param, ctx);
ret = -EINVAL; ret = -EINVAL;
/* Store operation */ /* Store operation */
if (parg->type->is_string) { if (parg->type->is_string) {
...@@ -850,6 +1031,33 @@ static int traceprobe_conflict_field_name(const char *name, ...@@ -850,6 +1031,33 @@ static int traceprobe_conflict_field_name(const char *name,
return 0; return 0;
} }
static char *generate_probe_arg_name(const char *arg, int idx)
{
char *name = NULL;
const char *end;
/*
* If argument name is omitted, try arg as a name (BTF variable)
* or "argN".
*/
if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS)) {
end = strchr(arg, ':');
if (!end)
end = arg + strlen(arg);
name = kmemdup_nul(arg, end - arg, GFP_KERNEL);
if (!name || !is_good_name(name)) {
kfree(name);
name = NULL;
}
}
if (!name)
name = kasprintf(GFP_KERNEL, "arg%d", idx + 1);
return name;
}
int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg, int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
struct traceprobe_parse_context *ctx) struct traceprobe_parse_context *ctx)
{ {
...@@ -871,8 +1079,7 @@ int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg, ...@@ -871,8 +1079,7 @@ int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
parg->name = kmemdup_nul(arg, body - arg, GFP_KERNEL); parg->name = kmemdup_nul(arg, body - arg, GFP_KERNEL);
body++; body++;
} else { } else {
/* If argument name is omitted, set "argN" */ parg->name = generate_probe_arg_name(arg, i);
parg->name = kasprintf(GFP_KERNEL, "arg%d", i + 1);
body = arg; body = arg;
} }
if (!parg->name) if (!parg->name)
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <linux/limits.h> #include <linux/limits.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/bitops.h> #include <linux/bitops.h>
#include <linux/btf.h>
#include <asm/bitsperlong.h> #include <asm/bitsperlong.h>
#include "trace.h" #include "trace.h"
...@@ -380,6 +381,9 @@ static inline bool tparg_is_function_entry(unsigned int flags) ...@@ -380,6 +381,9 @@ static inline bool tparg_is_function_entry(unsigned int flags)
struct traceprobe_parse_context { struct traceprobe_parse_context {
struct trace_event_call *event; struct trace_event_call *event;
const struct btf_param *params;
s32 nr_params;
const char *funcname;
unsigned int flags; unsigned int flags;
int offset; int offset;
}; };
...@@ -478,7 +482,10 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, ...@@ -478,7 +482,10 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(NO_EVENT_INFO, "This requires both group and event name to attach"),\ C(NO_EVENT_INFO, "This requires both group and event name to attach"),\
C(BAD_ATTACH_EVENT, "Attached event does not exist"),\ C(BAD_ATTACH_EVENT, "Attached event does not exist"),\
C(BAD_ATTACH_ARG, "Attached event does not have this field"),\ C(BAD_ATTACH_ARG, "Attached event does not have this field"),\
C(NO_EP_FILTER, "No filter rule after 'if'"), C(NO_EP_FILTER, "No filter rule after 'if'"), \
C(NOSUP_BTFARG, "BTF is not available or not supported"), \
C(NO_BTFARG, "This variable is not found at this probe point"),\
C(NO_BTF_ENTRY, "No BTF entry for this probe point"),
#undef C #undef C
#define C(a, b) TP_ERR_##a #define C(a, b) TP_ERR_##a
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment