Support $$args fetch arg for function entry. This uses BTF for finding the function argument. Thus it depends on CONFIG_BPF_SYSCALL. /sys/kernel/tracing # echo 'p vfs_read $$args' >> kprobe_events /sys/kernel/tracing # cat kprobe_events p:kprobes/p_vfs_read_0 vfs_read file=$arg1:x64 buf=$arg2:x64 count=$arg3:u64 pos=$arg4:x64 Note that $$args must be used without argument name. Signed-off-by: Masami Hiramatsu <mhiramat@xxxxxxxxxx> --- kernel/trace/trace_kprobe.c | 60 ++++++++++++++++++++++++- kernel/trace/trace_probe.c | 105 +++++++++++++++++++++++++++++++++++++++++++ kernel/trace/trace_probe.h | 5 ++ 3 files changed, 168 insertions(+), 2 deletions(-) diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c index 3dd4fb719aa3..fe88ee8c8cd8 100644 --- a/kernel/trace/trace_kprobe.c +++ b/kernel/trace/trace_kprobe.c @@ -712,6 +712,58 @@ static int trace_kprobe_module_callback(struct notifier_block *nb, return NOTIFY_DONE; } +#ifdef CONFIG_BPF_SYSCALL + +static int trace_kprobe_parse_btf_args(struct trace_kprobe *tk, int i, + const char *arg, unsigned int flags) +{ + struct trace_probe *tp = &tk->tp; + static struct btf *btf; + const struct btf_type *t; + const struct btf_param *args; + s32 id, nargs; + int ret; + + if (!(flags & TPARG_FL_FENTRY)) + return -EINVAL; + if (!tk->symbol) + return -EINVAL; + + if (!btf) + btf = btf_parse_vmlinux(); + + id = btf_find_by_name_kind(btf, tk->symbol, BTF_KIND_FUNC); + if (id <= 0) + return -ENOENT; + + /* Get BTF_KIND_FUNC type */ + t = btf_type_by_id(btf, id); + if (!btf_type_is_func(t)) + return -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 -ENOENT; + + args = (const struct btf_param *)(t + 1); + nargs = btf_type_vlen(t); + for (i = 0; i < nargs; i++) { + ret = traceprobe_parse_btf_arg(tp, i, btf, &args[i]); + if (ret < 0) + break; + } + + return ret; +} +#else +static int trace_kprobe_parse_btf_args(struct trace_kprobe *tk, int i, + const char *arg, unsigned int flags) +{ + return -EOPNOTSUPP; +} +#endif + static struct notifier_block trace_kprobe_module_nb = { .notifier_call = trace_kprobe_module_callback, .priority = 1 /* Invoked after kprobe module callback */ @@ -733,12 +785,13 @@ static int __trace_kprobe_create(int argc, const char *argv[]) * $stack : fetch stack address * $stackN : fetch Nth of stack (N:0-) * $comm : fetch current task comm + * $$args : fetch parameters using BTF * @ADDR : fetch memory at ADDR (ADDR should be in kernel) * @SYM[+|-offs] : fetch memory at SYM +|- offs (SYM is a data symbol) * %REG : fetch register REG * Dereferencing memory fetch: * +|-offs(ARG) : fetch memory at ARG +|- offs address. - * Alias name of args: + * Alias name of args (except for $$args) : * NAME=FETCHARG : set NAME as alias of FETCHARG. * Type of args: * FETCHARG:TYPE : use TYPE instead of unsigned long. @@ -877,7 +930,10 @@ static int __trace_kprobe_create(int argc, const char *argv[]) /* parse arguments */ for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) { trace_probe_log_set_index(i + 2); - ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], flags); + if (strcmp(argv[i], "$$args") == 0) + ret = trace_kprobe_parse_btf_args(tk, i, argv[i], flags); + else + ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], flags); if (ret) goto error; /* This can be -ENOMEM */ } diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 2fe104109525..bbac261b1688 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -765,6 +765,111 @@ static int traceprobe_conflict_field_name(const char *name, return 0; } +#ifdef CONFIG_BPF_SYSCALL + +static u32 btf_type_int(const struct btf_type *t) +{ + return *(u32 *)(t + 1); +} + +static const char *traceprobe_type_from_btf(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_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"; + } + } + } + + /* Default type */ + if (IS_ENABLED(CONFIG_64BIT)) + return "x64"; + else + return "x32"; +} + +int traceprobe_parse_btf_arg(struct trace_probe *tp, int i, struct btf *btf, + const struct btf_param *arg) +{ + struct probe_arg *parg = &tp->args[i]; + const char *name, *tname; + char *body; + int ret; + + tp->nr_args++; + name = btf_name_by_offset(btf, arg->name_off); + parg->name = kstrdup(name, GFP_KERNEL); + if (!parg->name) + return -ENOMEM; + + if (!is_good_name(parg->name)) { + trace_probe_log_err(0, BAD_ARG_NAME); + return -EINVAL; + } + if (traceprobe_conflict_field_name(parg->name, tp->args, i)) { + trace_probe_log_err(0, USED_ARG_NAME); + return -EINVAL; + } + + /* + * Since probe event needs an appropriate command for dyn_event interface, + * convert BTF type to corresponding fetch-type string. + */ + tname = traceprobe_type_from_btf(btf, arg->type); + if (tname) + body = kasprintf(GFP_KERNEL, "$arg%d:%s", i + 1, tname); + else + body = kasprintf(GFP_KERNEL, "$arg%d", i + 1); + + if (!body) + return -ENOMEM; + /* Parse fetch argument */ + ret = traceprobe_parse_probe_arg_body(body, &tp->size, parg, + TPARG_FL_KERNEL | TPARG_FL_FENTRY, 0); + + kfree(body); + + return ret; +} +#endif + int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg, unsigned int flags) { diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index 355c78a930f8..857b946afe29 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -23,6 +23,7 @@ #include <linux/limits.h> #include <linux/uaccess.h> #include <linux/bitops.h> +#include <linux/btf.h> #include <asm/bitsperlong.h> #include "trace.h" @@ -359,6 +360,10 @@ int trace_probe_create(const char *raw_command, int (*createfn)(int, const char extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *argv, unsigned int flags); +#ifdef CONFIG_BPF_SYSCALL +int traceprobe_parse_btf_arg(struct trace_probe *tp, int i, struct btf *btf, + const struct btf_param *arg); +#endif extern int traceprobe_update_arg(struct probe_arg *arg); extern void traceprobe_free_probe_arg(struct probe_arg *arg);