mirror of
https://github.com/tbsdtv/linux_media.git
synced 2025-07-22 20:30:58 +02:00
Merge tag 'probes-v6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace
Pull probes updates from Masami Hiramatsu: - fprobe: Pass return address to the fprobe entry/exit callbacks so that the callbacks don't need to analyze pt_regs/stack to find the function return address. - kprobe events: cleanup usage of TPARG_FL_FENTRY and TPARG_FL_RETURN flags so that those are not set at once. - fprobe events: - Add a new fprobe events for tracing arbitrary function entry and exit as a trace event. - Add a new tracepoint events for tracing raw tracepoint as a trace event. This allows user to trace non user-exposed tracepoints. - Move eprobe's event parser code into probe event common file. - Introduce BTF (BPF type format) support to kernel probe (kprobe, fprobe and tracepoint probe) events so that user can specify traced function arguments by name. This also applies the type of argument when fetching the argument. - Introduce '$arg*' wildcard support if BTF is available. This expands the '$arg*' meta argument to all function argument automatically. - Check the return value types by BTF. If the function returns 'void', '$retval' is rejected. - Add some selftest script for fprobe events, tracepoint events and BTF support. - Update documentation about the fprobe events. - Some fixes for above features, document and selftests. - selftests for ftrace (in addition to the new fprobe events): - Add a test case for multiple consecutive probes in a function which checks if ftrace based kprobe, optimized kprobe and normal kprobe can be defined in the same target function. - Add a test case for optimized probe, which checks whether kprobe can be optimized or not. * tag 'probes-v6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: tracing/probes: Fix tracepoint event with $arg* to fetch correct argument Documentation: Fix typo of reference file name tracing/probes: Fix to return NULL and keep using current argc selftests/ftrace: Add new test case which checks for optimized probes selftests/ftrace: Add new test case which adds multiple consecutive probes in a function Documentation: tracing/probes: Add fprobe event tracing document selftests/ftrace: Add BTF arguments test cases selftests/ftrace: Add tracepoint probe test case tracing/probes: Add BTF retval type support tracing/probes: Add $arg* meta argument for all function args tracing/probes: Support function parameters if BTF is available tracing/probes: Move event parameter fetching code to common parser tracing/probes: Add tracepoint support on fprobe_events selftests/ftrace: Add fprobe related testcases tracing/probes: Add fprobe events for tracing function entry and exit. tracing/probes: Avoid setting TPARG_FL_FENTRY and TPARG_FL_RETURN fprobe: Pass return address to the handlers
This commit is contained in:
@@ -2127,6 +2127,7 @@ static int pre_handler_kretprobe(struct kprobe *p, struct pt_regs *regs)
|
||||
NOKPROBE_SYMBOL(pre_handler_kretprobe);
|
||||
|
||||
static void kretprobe_rethook_handler(struct rethook_node *rh, void *data,
|
||||
unsigned long ret_addr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
struct kretprobe *rp = (struct kretprobe *)data;
|
||||
|
@@ -665,6 +665,32 @@ config BLK_DEV_IO_TRACE
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config FPROBE_EVENTS
|
||||
depends on FPROBE
|
||||
depends on HAVE_REGS_AND_STACK_ACCESS_API
|
||||
bool "Enable fprobe-based dynamic events"
|
||||
select TRACING
|
||||
select PROBE_EVENTS
|
||||
select DYNAMIC_EVENTS
|
||||
default y
|
||||
help
|
||||
This allows user to add tracing events on the function entry and
|
||||
exit via ftrace interface. The syntax is same as the kprobe events
|
||||
and the kprobe events on function entry and exit will be
|
||||
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
|
||||
depends on KPROBES
|
||||
depends on HAVE_REGS_AND_STACK_ACCESS_API
|
||||
|
@@ -104,6 +104,7 @@ obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o
|
||||
obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o
|
||||
obj-$(CONFIG_FPROBE) += fprobe.o
|
||||
obj-$(CONFIG_RETHOOK) += rethook.o
|
||||
obj-$(CONFIG_FPROBE_EVENTS) += trace_fprobe.o
|
||||
|
||||
obj-$(CONFIG_TRACEPOINT_BENCHMARK) += trace_benchmark.o
|
||||
obj-$(CONFIG_RV) += rv/
|
||||
|
@@ -2652,7 +2652,8 @@ kprobe_multi_link_prog_run(struct bpf_kprobe_multi_link *link,
|
||||
|
||||
static int
|
||||
kprobe_multi_link_handler(struct fprobe *fp, unsigned long fentry_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
unsigned long ret_ip, struct pt_regs *regs,
|
||||
void *data)
|
||||
{
|
||||
struct bpf_kprobe_multi_link *link;
|
||||
|
||||
@@ -2663,7 +2664,8 @@ kprobe_multi_link_handler(struct fprobe *fp, unsigned long fentry_ip,
|
||||
|
||||
static void
|
||||
kprobe_multi_link_exit_handler(struct fprobe *fp, unsigned long fentry_ip,
|
||||
struct pt_regs *regs, void *data)
|
||||
unsigned long ret_ip, struct pt_regs *regs,
|
||||
void *data)
|
||||
{
|
||||
struct bpf_kprobe_multi_link *link;
|
||||
|
||||
|
@@ -46,7 +46,7 @@ static inline void __fprobe_handler(unsigned long ip, unsigned long parent_ip,
|
||||
}
|
||||
|
||||
if (fp->entry_handler)
|
||||
ret = fp->entry_handler(fp, ip, ftrace_get_regs(fregs), entry_data);
|
||||
ret = fp->entry_handler(fp, ip, parent_ip, ftrace_get_regs(fregs), entry_data);
|
||||
|
||||
/* If entry_handler returns !0, nmissed is not counted. */
|
||||
if (rh) {
|
||||
@@ -112,7 +112,7 @@ static void fprobe_kprobe_handler(unsigned long ip, unsigned long parent_ip,
|
||||
}
|
||||
|
||||
static void fprobe_exit_handler(struct rethook_node *rh, void *data,
|
||||
struct pt_regs *regs)
|
||||
unsigned long ret_ip, struct pt_regs *regs)
|
||||
{
|
||||
struct fprobe *fp = (struct fprobe *)data;
|
||||
struct fprobe_rethook_node *fpr;
|
||||
@@ -133,7 +133,7 @@ static void fprobe_exit_handler(struct rethook_node *rh, void *data,
|
||||
return;
|
||||
}
|
||||
|
||||
fp->exit_handler(fp, fpr->entry_ip, regs,
|
||||
fp->exit_handler(fp, fpr->entry_ip, ret_ip, regs,
|
||||
fp->entry_data_size ? (void *)fpr->data : NULL);
|
||||
ftrace_test_recursion_unlock(bit);
|
||||
}
|
||||
@@ -348,6 +348,14 @@ int register_fprobe_syms(struct fprobe *fp, const char **syms, int num)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_fprobe_syms);
|
||||
|
||||
bool fprobe_is_registered(struct fprobe *fp)
|
||||
{
|
||||
if (!fp || (fp->ops.saved_func != fprobe_handler &&
|
||||
fp->ops.saved_func != fprobe_kprobe_handler))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* unregister_fprobe() - Unregister fprobe from ftrace
|
||||
* @fp: A fprobe data structure to be unregistered.
|
||||
@@ -360,8 +368,7 @@ int unregister_fprobe(struct fprobe *fp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!fp || (fp->ops.saved_func != fprobe_handler &&
|
||||
fp->ops.saved_func != fprobe_kprobe_handler))
|
||||
if (!fprobe_is_registered(fp))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
|
@@ -301,7 +301,8 @@ unsigned long rethook_trampoline_handler(struct pt_regs *regs,
|
||||
break;
|
||||
handler = READ_ONCE(rhn->rethook->handler);
|
||||
if (handler)
|
||||
handler(rhn, rhn->rethook->data, regs);
|
||||
handler(rhn, rhn->rethook->data,
|
||||
correct_ret_addr, regs);
|
||||
|
||||
if (first == node)
|
||||
break;
|
||||
|
@@ -5672,10 +5672,17 @@ static const char readme_msg[] =
|
||||
" uprobe_events\t\t- Create/append/remove/show the userspace dynamic events\n"
|
||||
"\t\t\t Write into this file to define/undefine new trace events.\n"
|
||||
#endif
|
||||
#if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS)
|
||||
#if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS) || \
|
||||
defined(CONFIG_FPROBE_EVENTS)
|
||||
"\t accepts: event-definitions (one definition per line)\n"
|
||||
#if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS)
|
||||
"\t Format: p[:[<group>/][<event>]] <place> [<args>]\n"
|
||||
"\t r[maxactive][:[<group>/][<event>]] <place> [<args>]\n"
|
||||
#endif
|
||||
#ifdef CONFIG_FPROBE_EVENTS
|
||||
"\t f[:[<group>/][<event>]] <func-name>[%return] [<args>]\n"
|
||||
"\t t[:[<group>/][<event>]] <tracepoint> [<args>]\n"
|
||||
#endif
|
||||
#ifdef CONFIG_HIST_TRIGGERS
|
||||
"\t s:[synthetic/]<event> <field> [<field>]\n"
|
||||
#endif
|
||||
@@ -5691,7 +5698,11 @@ static const char readme_msg[] =
|
||||
"\t args: <name>=fetcharg[:type]\n"
|
||||
"\t fetcharg: (%<register>|$<efield>), @<address>, @<symbol>[+|-<offset>],\n"
|
||||
#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"
|
||||
#endif
|
||||
#else
|
||||
"\t $stack<index>, $stack, $retval, $comm,\n"
|
||||
#endif
|
||||
|
@@ -148,6 +148,17 @@ struct kretprobe_trace_entry_head {
|
||||
unsigned long ret_ip;
|
||||
};
|
||||
|
||||
struct fentry_trace_entry_head {
|
||||
struct trace_entry ent;
|
||||
unsigned long ip;
|
||||
};
|
||||
|
||||
struct fexit_trace_entry_head {
|
||||
struct trace_entry ent;
|
||||
unsigned long func;
|
||||
unsigned long ret_ip;
|
||||
};
|
||||
|
||||
#define TRACE_BUF_SIZE 1024
|
||||
|
||||
struct trace_array;
|
||||
|
@@ -227,37 +227,6 @@ error:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int trace_eprobe_tp_arg_update(struct trace_eprobe *ep, int i)
|
||||
{
|
||||
struct probe_arg *parg = &ep->tp.args[i];
|
||||
struct ftrace_event_field *field;
|
||||
struct list_head *head;
|
||||
int ret = -ENOENT;
|
||||
|
||||
head = trace_get_fields(ep->event);
|
||||
list_for_each_entry(field, head, link) {
|
||||
if (!strcmp(parg->code->data, field->name)) {
|
||||
kfree(parg->code->data);
|
||||
parg->code->data = field;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Argument not found on event. But allow for comm and COMM
|
||||
* to be used to get the current->comm.
|
||||
*/
|
||||
if (strcmp(parg->code->data, "COMM") == 0 ||
|
||||
strcmp(parg->code->data, "comm") == 0) {
|
||||
parg->code->op = FETCH_OP_COMM;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
kfree(parg->code->data);
|
||||
parg->code->data = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int eprobe_event_define_fields(struct trace_event_call *event_call)
|
||||
{
|
||||
struct eprobe_trace_entry_head field;
|
||||
@@ -817,19 +786,16 @@ find_and_get_event(const char *system, const char *event_name)
|
||||
|
||||
static int trace_eprobe_tp_update_arg(struct trace_eprobe *ep, const char *argv[], int i)
|
||||
{
|
||||
unsigned int flags = TPARG_FL_KERNEL | TPARG_FL_TPOINT;
|
||||
struct traceprobe_parse_context ctx = {
|
||||
.event = ep->event,
|
||||
.flags = TPARG_FL_KERNEL | TPARG_FL_TEVENT,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], flags);
|
||||
ret = traceprobe_parse_probe_arg(&ep->tp, i, argv[i], &ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ep->tp.args[i].code->op == FETCH_OP_TP_ARG) {
|
||||
ret = trace_eprobe_tp_arg_update(ep, i);
|
||||
if (ret)
|
||||
trace_probe_log_err(0, BAD_ATTACH_ARG);
|
||||
}
|
||||
|
||||
/* Handle symbols "@" */
|
||||
if (!ret)
|
||||
ret = traceprobe_update_arg(&ep->tp.args[i]);
|
||||
|
1199
kernel/trace/trace_fprobe.c
Normal file
1199
kernel/trace/trace_fprobe.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -732,9 +732,10 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
* FETCHARG:TYPE : use TYPE instead of unsigned long.
|
||||
*/
|
||||
struct trace_kprobe *tk = NULL;
|
||||
int i, len, ret = 0;
|
||||
int i, len, new_argc = 0, ret = 0;
|
||||
bool is_return = false;
|
||||
char *symbol = NULL, *tmp = NULL;
|
||||
const char **new_argv = NULL;
|
||||
const char *event = NULL, *group = KPROBE_EVENT_SYSTEM;
|
||||
enum probe_print_type ptype;
|
||||
int maxactive = 0;
|
||||
@@ -742,7 +743,8 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
void *addr = NULL;
|
||||
char buf[MAX_EVENT_NAME_LEN];
|
||||
char gbuf[MAX_EVENT_NAME_LEN];
|
||||
unsigned int flags = TPARG_FL_KERNEL;
|
||||
char abuf[MAX_BTF_ARGS_LEN];
|
||||
struct traceprobe_parse_context ctx = { .flags = TPARG_FL_KERNEL };
|
||||
|
||||
switch (argv[0][0]) {
|
||||
case 'r':
|
||||
@@ -764,7 +766,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
|
||||
if (isdigit(argv[0][1])) {
|
||||
if (!is_return) {
|
||||
trace_probe_log_err(1, MAXACT_NO_KPROBE);
|
||||
trace_probe_log_err(1, BAD_MAXACT_TYPE);
|
||||
goto parse_error;
|
||||
}
|
||||
if (event)
|
||||
@@ -823,10 +825,10 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
goto parse_error;
|
||||
}
|
||||
if (is_return)
|
||||
flags |= TPARG_FL_RETURN;
|
||||
ctx.flags |= TPARG_FL_RETURN;
|
||||
ret = kprobe_on_func_entry(NULL, symbol, offset);
|
||||
if (ret == 0)
|
||||
flags |= TPARG_FL_FENTRY;
|
||||
if (ret == 0 && !is_return)
|
||||
ctx.flags |= TPARG_FL_FENTRY;
|
||||
/* Defer the ENOENT case until register kprobe */
|
||||
if (ret == -EINVAL && is_return) {
|
||||
trace_probe_log_err(0, BAD_RETPROBE);
|
||||
@@ -854,21 +856,35 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
event = buf;
|
||||
}
|
||||
|
||||
argc -= 2; argv += 2;
|
||||
ctx.funcname = symbol;
|
||||
new_argv = traceprobe_expand_meta_args(argc, argv, &new_argc,
|
||||
abuf, MAX_BTF_ARGS_LEN, &ctx);
|
||||
if (IS_ERR(new_argv)) {
|
||||
ret = PTR_ERR(new_argv);
|
||||
new_argv = NULL;
|
||||
goto out;
|
||||
}
|
||||
if (new_argv) {
|
||||
argc = new_argc;
|
||||
argv = new_argv;
|
||||
}
|
||||
|
||||
/* setup a probe */
|
||||
tk = alloc_trace_kprobe(group, event, addr, symbol, offset, maxactive,
|
||||
argc - 2, is_return);
|
||||
argc, is_return);
|
||||
if (IS_ERR(tk)) {
|
||||
ret = PTR_ERR(tk);
|
||||
/* This must return -ENOMEM, else there is a bug */
|
||||
WARN_ON_ONCE(ret != -ENOMEM);
|
||||
goto out; /* We know tk is not allocated */
|
||||
}
|
||||
argc -= 2; argv += 2;
|
||||
|
||||
/* 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);
|
||||
ctx.offset = 0;
|
||||
ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], &ctx);
|
||||
if (ret)
|
||||
goto error; /* This can be -ENOMEM */
|
||||
}
|
||||
@@ -892,6 +908,7 @@ static int __trace_kprobe_create(int argc, const char *argv[])
|
||||
|
||||
out:
|
||||
trace_probe_log_clear();
|
||||
kfree(new_argv);
|
||||
kfree(symbol);
|
||||
return ret;
|
||||
|
||||
|
@@ -11,6 +11,8 @@
|
||||
*/
|
||||
#define pr_fmt(fmt) "trace_probe: " fmt
|
||||
|
||||
#include <linux/bpf.h>
|
||||
|
||||
#include "trace_probe.h"
|
||||
|
||||
#undef C
|
||||
@@ -283,69 +285,354 @@ int traceprobe_parse_event_name(const char **pevent, const char **pgroup,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_trace_event_arg(char *arg, struct fetch_insn *code,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct ftrace_event_field *field;
|
||||
struct list_head *head;
|
||||
|
||||
head = trace_get_fields(ctx->event);
|
||||
list_for_each_entry(field, head, link) {
|
||||
if (!strcmp(arg, field->name)) {
|
||||
code->op = FETCH_OP_TP_ARG;
|
||||
code->data = field;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
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_type *find_btf_func_proto(const char *funcname)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const struct btf_type *t;
|
||||
s32 id;
|
||||
|
||||
if (!btf || !funcname)
|
||||
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);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
|
||||
bool tracepoint)
|
||||
{
|
||||
const struct btf_param *param;
|
||||
const struct btf_type *t;
|
||||
|
||||
if (!funcname || !nr)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
t = find_btf_func_proto(funcname);
|
||||
if (IS_ERR(t))
|
||||
return (const struct btf_param *)t;
|
||||
|
||||
*nr = btf_type_vlen(t);
|
||||
param = (const struct btf_param *)(t + 1);
|
||||
|
||||
/* Hide the first 'data' argument of tracepoint */
|
||||
if (tracepoint) {
|
||||
(*nr)--;
|
||||
param++;
|
||||
}
|
||||
|
||||
if (*nr > 0)
|
||||
return param;
|
||||
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,
|
||||
ctx->flags & TPARG_FL_TPOINT);
|
||||
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;
|
||||
if (ctx->flags & TPARG_FL_TPOINT)
|
||||
code->param = i + 1;
|
||||
else
|
||||
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) {
|
||||
if (ctx->flags & TPARG_FL_TPOINT)
|
||||
arg_idx--;
|
||||
typestr = type_from_btf_id(btf, ctx->params[arg_idx].type);
|
||||
}
|
||||
|
||||
return find_fetch_type(typestr, ctx->flags);
|
||||
}
|
||||
|
||||
static const struct fetch_type *parse_btf_retval_type(
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const char *typestr = NULL;
|
||||
const struct btf_type *t;
|
||||
|
||||
if (btf && ctx->funcname) {
|
||||
t = find_btf_func_proto(ctx->funcname);
|
||||
if (!IS_ERR(t))
|
||||
typestr = type_from_btf_id(btf, t->type);
|
||||
}
|
||||
|
||||
return find_fetch_type(typestr, ctx->flags);
|
||||
}
|
||||
|
||||
static bool is_btf_retval_void(const char *funcname)
|
||||
{
|
||||
const struct btf_type *t;
|
||||
|
||||
t = find_btf_func_proto(funcname);
|
||||
if (IS_ERR(t))
|
||||
return false;
|
||||
|
||||
return t->type == 0;
|
||||
}
|
||||
#else
|
||||
static struct btf *traceprobe_get_btf(void)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
|
||||
bool tracepoint)
|
||||
{
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
#define parse_btf_retval_type(ctx) \
|
||||
find_fetch_type(NULL, ctx->flags)
|
||||
|
||||
#define is_btf_retval_void(funcname) (false)
|
||||
|
||||
#endif
|
||||
|
||||
#define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
|
||||
|
||||
static int parse_probe_vars(char *arg, const struct fetch_type *t,
|
||||
struct fetch_insn *code, unsigned int flags, int offs)
|
||||
struct fetch_insn *code,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
unsigned long param;
|
||||
int err = TP_ERR_BAD_VAR;
|
||||
int ret = 0;
|
||||
int len;
|
||||
|
||||
if (flags & TPARG_FL_TPOINT) {
|
||||
if (ctx->flags & TPARG_FL_TEVENT) {
|
||||
if (code->data)
|
||||
return -EFAULT;
|
||||
code->data = kstrdup(arg, GFP_KERNEL);
|
||||
if (!code->data)
|
||||
return -ENOMEM;
|
||||
code->op = FETCH_OP_TP_ARG;
|
||||
} else if (strcmp(arg, "retval") == 0) {
|
||||
if (flags & TPARG_FL_RETURN) {
|
||||
code->op = FETCH_OP_RETVAL;
|
||||
} else {
|
||||
trace_probe_log_err(offs, RETVAL_ON_PROBE);
|
||||
ret = -EINVAL;
|
||||
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;
|
||||
}
|
||||
} else if ((len = str_has_prefix(arg, "stack"))) {
|
||||
/* backward compatibility */
|
||||
ctx->offset = 0;
|
||||
goto inval;
|
||||
}
|
||||
|
||||
if (strcmp(arg, "retval") == 0) {
|
||||
if (ctx->flags & TPARG_FL_RETURN) {
|
||||
if ((ctx->flags & TPARG_FL_KERNEL) &&
|
||||
is_btf_retval_void(ctx->funcname)) {
|
||||
err = TP_ERR_NO_RETVAL;
|
||||
goto inval;
|
||||
}
|
||||
code->op = FETCH_OP_RETVAL;
|
||||
return 0;
|
||||
}
|
||||
err = TP_ERR_RETVAL_ON_PROBE;
|
||||
goto inval;
|
||||
}
|
||||
|
||||
len = str_has_prefix(arg, "stack");
|
||||
if (len) {
|
||||
|
||||
if (arg[len] == '\0') {
|
||||
code->op = FETCH_OP_STACKP;
|
||||
} else if (isdigit(arg[len])) {
|
||||
ret = kstrtoul(arg + len, 10, ¶m);
|
||||
if (ret) {
|
||||
goto inval_var;
|
||||
} else if ((flags & TPARG_FL_KERNEL) &&
|
||||
param > PARAM_MAX_STACK) {
|
||||
trace_probe_log_err(offs, BAD_STACK_NUM);
|
||||
ret = -EINVAL;
|
||||
} else {
|
||||
code->op = FETCH_OP_STACK;
|
||||
code->param = (unsigned int)param;
|
||||
}
|
||||
} else
|
||||
goto inval_var;
|
||||
} else if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
|
||||
code->op = FETCH_OP_COMM;
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
} else if (((flags & TPARG_FL_MASK) ==
|
||||
(TPARG_FL_KERNEL | TPARG_FL_FENTRY)) &&
|
||||
(len = str_has_prefix(arg, "arg"))) {
|
||||
ret = kstrtoul(arg + len, 10, ¶m);
|
||||
if (ret) {
|
||||
goto inval_var;
|
||||
} else if (!param || param > PARAM_MAX_STACK) {
|
||||
trace_probe_log_err(offs, BAD_ARG_NUM);
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isdigit(arg[len])) {
|
||||
ret = kstrtoul(arg + len, 10, ¶m);
|
||||
if (ret)
|
||||
goto inval;
|
||||
|
||||
if ((ctx->flags & TPARG_FL_KERNEL) &&
|
||||
param > PARAM_MAX_STACK) {
|
||||
err = TP_ERR_BAD_STACK_NUM;
|
||||
goto inval;
|
||||
}
|
||||
code->op = FETCH_OP_STACK;
|
||||
code->param = (unsigned int)param;
|
||||
return 0;
|
||||
}
|
||||
goto inval;
|
||||
}
|
||||
|
||||
if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
|
||||
code->op = FETCH_OP_COMM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
len = str_has_prefix(arg, "arg");
|
||||
if (len && tparg_is_function_entry(ctx->flags)) {
|
||||
ret = kstrtoul(arg + len, 10, ¶m);
|
||||
if (ret)
|
||||
goto inval;
|
||||
|
||||
if (!param || param > PARAM_MAX_STACK) {
|
||||
err = TP_ERR_BAD_ARG_NUM;
|
||||
goto inval;
|
||||
}
|
||||
|
||||
code->op = FETCH_OP_ARG;
|
||||
code->param = (unsigned int)param - 1;
|
||||
/*
|
||||
* The tracepoint probe will probe a stub function, and the
|
||||
* first parameter of the stub is a dummy and should be ignored.
|
||||
*/
|
||||
if (ctx->flags & TPARG_FL_TPOINT)
|
||||
code->param++;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
} else
|
||||
goto inval_var;
|
||||
|
||||
return ret;
|
||||
|
||||
inval_var:
|
||||
trace_probe_log_err(offs, BAD_VAR);
|
||||
inval:
|
||||
__trace_probe_log_err(ctx->offset, err);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -378,7 +665,7 @@ static int __parse_imm_string(char *str, char **pbuf, int offs)
|
||||
static int
|
||||
parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
struct fetch_insn **pcode, struct fetch_insn *end,
|
||||
unsigned int flags, int offs)
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct fetch_insn *code = *pcode;
|
||||
unsigned long param;
|
||||
@@ -389,13 +676,13 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
|
||||
switch (arg[0]) {
|
||||
case '$':
|
||||
ret = parse_probe_vars(arg + 1, type, code, flags, offs);
|
||||
ret = parse_probe_vars(arg + 1, type, code, ctx);
|
||||
break;
|
||||
|
||||
case '%': /* named register */
|
||||
if (flags & TPARG_FL_TPOINT) {
|
||||
/* eprobes do not handle registers */
|
||||
trace_probe_log_err(offs, BAD_VAR);
|
||||
if (ctx->flags & (TPARG_FL_TEVENT | TPARG_FL_FPROBE)) {
|
||||
/* eprobe and fprobe do not handle registers */
|
||||
trace_probe_log_err(ctx->offset, BAD_VAR);
|
||||
break;
|
||||
}
|
||||
ret = regs_query_register_offset(arg + 1);
|
||||
@@ -404,14 +691,14 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
code->param = (unsigned int)ret;
|
||||
ret = 0;
|
||||
} else
|
||||
trace_probe_log_err(offs, BAD_REG_NAME);
|
||||
trace_probe_log_err(ctx->offset, BAD_REG_NAME);
|
||||
break;
|
||||
|
||||
case '@': /* memory, file-offset or symbol */
|
||||
if (isdigit(arg[1])) {
|
||||
ret = kstrtoul(arg + 1, 0, ¶m);
|
||||
if (ret) {
|
||||
trace_probe_log_err(offs, BAD_MEM_ADDR);
|
||||
trace_probe_log_err(ctx->offset, BAD_MEM_ADDR);
|
||||
break;
|
||||
}
|
||||
/* load address */
|
||||
@@ -419,13 +706,13 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
code->immediate = param;
|
||||
} else if (arg[1] == '+') {
|
||||
/* kprobes don't support file offsets */
|
||||
if (flags & TPARG_FL_KERNEL) {
|
||||
trace_probe_log_err(offs, FILE_ON_KPROBE);
|
||||
if (ctx->flags & TPARG_FL_KERNEL) {
|
||||
trace_probe_log_err(ctx->offset, FILE_ON_KPROBE);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = kstrtol(arg + 2, 0, &offset);
|
||||
if (ret) {
|
||||
trace_probe_log_err(offs, BAD_FILE_OFFS);
|
||||
trace_probe_log_err(ctx->offset, BAD_FILE_OFFS);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -433,8 +720,8 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
code->immediate = (unsigned long)offset; // imm64?
|
||||
} else {
|
||||
/* uprobes don't support symbols */
|
||||
if (!(flags & TPARG_FL_KERNEL)) {
|
||||
trace_probe_log_err(offs, SYM_ON_UPROBE);
|
||||
if (!(ctx->flags & TPARG_FL_KERNEL)) {
|
||||
trace_probe_log_err(ctx->offset, SYM_ON_UPROBE);
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Preserve symbol for updating */
|
||||
@@ -443,7 +730,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
if (!code->data)
|
||||
return -ENOMEM;
|
||||
if (++code == end) {
|
||||
trace_probe_log_err(offs, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -EINVAL;
|
||||
}
|
||||
code->op = FETCH_OP_IMM;
|
||||
@@ -451,7 +738,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
}
|
||||
/* These are fetching from memory */
|
||||
if (++code == end) {
|
||||
trace_probe_log_err(offs, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -EINVAL;
|
||||
}
|
||||
*pcode = code;
|
||||
@@ -470,36 +757,38 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
arg++; /* Skip '+', because kstrtol() rejects it. */
|
||||
tmp = strchr(arg, '(');
|
||||
if (!tmp) {
|
||||
trace_probe_log_err(offs, DEREF_NEED_BRACE);
|
||||
trace_probe_log_err(ctx->offset, DEREF_NEED_BRACE);
|
||||
return -EINVAL;
|
||||
}
|
||||
*tmp = '\0';
|
||||
ret = kstrtol(arg, 0, &offset);
|
||||
if (ret) {
|
||||
trace_probe_log_err(offs, BAD_DEREF_OFFS);
|
||||
trace_probe_log_err(ctx->offset, BAD_DEREF_OFFS);
|
||||
break;
|
||||
}
|
||||
offs += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
|
||||
ctx->offset += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);
|
||||
arg = tmp + 1;
|
||||
tmp = strrchr(arg, ')');
|
||||
if (!tmp) {
|
||||
trace_probe_log_err(offs + strlen(arg),
|
||||
trace_probe_log_err(ctx->offset + strlen(arg),
|
||||
DEREF_OPEN_BRACE);
|
||||
return -EINVAL;
|
||||
} else {
|
||||
const struct fetch_type *t2 = find_fetch_type(NULL, flags);
|
||||
const struct fetch_type *t2 = find_fetch_type(NULL, ctx->flags);
|
||||
int cur_offs = ctx->offset;
|
||||
|
||||
*tmp = '\0';
|
||||
ret = parse_probe_arg(arg, t2, &code, end, flags, offs);
|
||||
ret = parse_probe_arg(arg, t2, &code, end, ctx);
|
||||
if (ret)
|
||||
break;
|
||||
ctx->offset = cur_offs;
|
||||
if (code->op == FETCH_OP_COMM ||
|
||||
code->op == FETCH_OP_DATA) {
|
||||
trace_probe_log_err(offs, COMM_CANT_DEREF);
|
||||
trace_probe_log_err(ctx->offset, COMM_CANT_DEREF);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (++code == end) {
|
||||
trace_probe_log_err(offs, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
return -EINVAL;
|
||||
}
|
||||
*pcode = code;
|
||||
@@ -510,7 +799,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
break;
|
||||
case '\\': /* Immediate value */
|
||||
if (arg[1] == '"') { /* Immediate string */
|
||||
ret = __parse_imm_string(arg + 2, &tmp, offs + 2);
|
||||
ret = __parse_imm_string(arg + 2, &tmp, ctx->offset + 2);
|
||||
if (ret)
|
||||
break;
|
||||
code->op = FETCH_OP_DATA;
|
||||
@@ -518,15 +807,24 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
} else {
|
||||
ret = str_to_immediate(arg + 1, &code->immediate);
|
||||
if (ret)
|
||||
trace_probe_log_err(offs + 1, BAD_IMM);
|
||||
trace_probe_log_err(ctx->offset + 1, BAD_IMM);
|
||||
else
|
||||
code->op = FETCH_OP_IMM;
|
||||
}
|
||||
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) {
|
||||
/* Parsed, but do not find fetch method */
|
||||
trace_probe_log_err(offs, BAD_FETCH_ARG);
|
||||
trace_probe_log_err(ctx->offset, BAD_FETCH_ARG);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
return ret;
|
||||
@@ -571,12 +869,13 @@ static int __parse_bitfield_probe_arg(const char *bf,
|
||||
|
||||
/* String length checking wrapper */
|
||||
static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
struct probe_arg *parg, unsigned int flags, int offset)
|
||||
struct probe_arg *parg,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct fetch_insn *code, *scode, *tmp = NULL;
|
||||
char *t, *t2, *t3;
|
||||
char *arg;
|
||||
int ret, len;
|
||||
char *arg;
|
||||
|
||||
arg = kstrdup(argv, GFP_KERNEL);
|
||||
if (!arg)
|
||||
@@ -585,10 +884,10 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
ret = -EINVAL;
|
||||
len = strlen(arg);
|
||||
if (len > MAX_ARGSTR_LEN) {
|
||||
trace_probe_log_err(offset, ARG_TOO_LONG);
|
||||
trace_probe_log_err(ctx->offset, ARG_TOO_LONG);
|
||||
goto out;
|
||||
} else if (len == 0) {
|
||||
trace_probe_log_err(offset, NO_ARG_BODY);
|
||||
trace_probe_log_err(ctx->offset, NO_ARG_BODY);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -606,23 +905,24 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
*t2++ = '\0';
|
||||
t3 = strchr(t2, ']');
|
||||
if (!t3) {
|
||||
offset += t2 + strlen(t2) - arg;
|
||||
trace_probe_log_err(offset,
|
||||
int offs = t2 + strlen(t2) - arg;
|
||||
|
||||
trace_probe_log_err(ctx->offset + offs,
|
||||
ARRAY_NO_CLOSE);
|
||||
goto out;
|
||||
} else if (t3[1] != '\0') {
|
||||
trace_probe_log_err(offset + t3 + 1 - arg,
|
||||
trace_probe_log_err(ctx->offset + t3 + 1 - arg,
|
||||
BAD_ARRAY_SUFFIX);
|
||||
goto out;
|
||||
}
|
||||
*t3 = '\0';
|
||||
if (kstrtouint(t2, 0, &parg->count) || !parg->count) {
|
||||
trace_probe_log_err(offset + t2 - arg,
|
||||
trace_probe_log_err(ctx->offset + t2 - arg,
|
||||
BAD_ARRAY_NUM);
|
||||
goto out;
|
||||
}
|
||||
if (parg->count > MAX_ARRAY_LEN) {
|
||||
trace_probe_log_err(offset + t2 - arg,
|
||||
trace_probe_log_err(ctx->offset + t2 - arg,
|
||||
ARRAY_TOO_BIG);
|
||||
goto out;
|
||||
}
|
||||
@@ -633,17 +933,17 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
* Since $comm and immediate string can not be dereferenced,
|
||||
* we can find those by strcmp. But ignore for eprobes.
|
||||
*/
|
||||
if (!(flags & TPARG_FL_TPOINT) &&
|
||||
if (!(ctx->flags & TPARG_FL_TEVENT) &&
|
||||
(strcmp(arg, "$comm") == 0 || strcmp(arg, "$COMM") == 0 ||
|
||||
strncmp(arg, "\\\"", 2) == 0)) {
|
||||
/* The type of $comm must be "string", and not an array. */
|
||||
if (parg->count || (t && strcmp(t, "string")))
|
||||
goto out;
|
||||
parg->type = find_fetch_type("string", flags);
|
||||
parg->type = find_fetch_type("string", ctx->flags);
|
||||
} else
|
||||
parg->type = find_fetch_type(t, flags);
|
||||
parg->type = find_fetch_type(t, ctx->flags);
|
||||
if (!parg->type) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0), BAD_TYPE);
|
||||
trace_probe_log_err(ctx->offset + (t ? (t - arg) : 0), BAD_TYPE);
|
||||
goto out;
|
||||
}
|
||||
parg->offset = *size;
|
||||
@@ -665,10 +965,18 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
code[FETCH_INSN_MAX - 1].op = FETCH_OP_END;
|
||||
|
||||
ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
|
||||
flags, offset);
|
||||
ctx);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* Update storing type if BTF is available */
|
||||
if (IS_ENABLED(CONFIG_PROBE_EVENTS_BTF_ARGS) && !t) {
|
||||
if (code->op == FETCH_OP_ARG)
|
||||
parg->type = parse_btf_arg_type(code->param, ctx);
|
||||
else if (code->op == FETCH_OP_RETVAL)
|
||||
parg->type = parse_btf_retval_type(ctx);
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
/* Store operation */
|
||||
if (parg->type->is_string) {
|
||||
@@ -676,7 +984,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
if (code->op != FETCH_OP_REG && code->op != FETCH_OP_STACK &&
|
||||
code->op != FETCH_OP_RETVAL && code->op != FETCH_OP_ARG &&
|
||||
code->op != FETCH_OP_DEREF && code->op != FETCH_OP_TP_ARG) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0),
|
||||
trace_probe_log_err(ctx->offset + (t ? (t - arg) : 0),
|
||||
BAD_SYMSTRING);
|
||||
goto fail;
|
||||
}
|
||||
@@ -684,7 +992,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF &&
|
||||
code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM &&
|
||||
code->op != FETCH_OP_DATA && code->op != FETCH_OP_TP_ARG) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0),
|
||||
trace_probe_log_err(ctx->offset + (t ? (t - arg) : 0),
|
||||
BAD_STRING);
|
||||
goto fail;
|
||||
}
|
||||
@@ -703,7 +1011,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
*/
|
||||
code++;
|
||||
if (code->op != FETCH_OP_NOP) {
|
||||
trace_probe_log_err(offset, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
@@ -726,7 +1034,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
} else {
|
||||
code++;
|
||||
if (code->op != FETCH_OP_NOP) {
|
||||
trace_probe_log_err(offset, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
goto fail;
|
||||
}
|
||||
code->op = FETCH_OP_ST_RAW;
|
||||
@@ -737,7 +1045,7 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
if (t != NULL) {
|
||||
ret = __parse_bitfield_probe_arg(t, parg->type, &code);
|
||||
if (ret) {
|
||||
trace_probe_log_err(offset + t - arg, BAD_BITFIELD);
|
||||
trace_probe_log_err(ctx->offset + t - arg, BAD_BITFIELD);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
@@ -747,13 +1055,13 @@ static int traceprobe_parse_probe_arg_body(const char *argv, ssize_t *size,
|
||||
if (scode->op != FETCH_OP_ST_MEM &&
|
||||
scode->op != FETCH_OP_ST_STRING &&
|
||||
scode->op != FETCH_OP_ST_USTRING) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0),
|
||||
trace_probe_log_err(ctx->offset + (t ? (t - arg) : 0),
|
||||
BAD_STRING);
|
||||
goto fail;
|
||||
}
|
||||
code++;
|
||||
if (code->op != FETCH_OP_NOP) {
|
||||
trace_probe_log_err(offset, TOO_MANY_OPS);
|
||||
trace_probe_log_err(ctx->offset, TOO_MANY_OPS);
|
||||
goto fail;
|
||||
}
|
||||
code->op = FETCH_OP_LP_ARRAY;
|
||||
@@ -801,8 +1109,35 @@ static int traceprobe_conflict_field_name(const char *name,
|
||||
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,
|
||||
unsigned int flags)
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct probe_arg *parg = &tp->args[i];
|
||||
const char *body;
|
||||
@@ -822,8 +1157,7 @@ int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
|
||||
parg->name = kmemdup_nul(arg, body - arg, GFP_KERNEL);
|
||||
body++;
|
||||
} else {
|
||||
/* If argument name is omitted, set "argN" */
|
||||
parg->name = kasprintf(GFP_KERNEL, "arg%d", i + 1);
|
||||
parg->name = generate_probe_arg_name(arg, i);
|
||||
body = arg;
|
||||
}
|
||||
if (!parg->name)
|
||||
@@ -837,9 +1171,9 @@ int traceprobe_parse_probe_arg(struct trace_probe *tp, int i, const char *arg,
|
||||
trace_probe_log_err(0, USED_ARG_NAME);
|
||||
return -EINVAL;
|
||||
}
|
||||
ctx->offset = body - arg;
|
||||
/* Parse fetch argument */
|
||||
return traceprobe_parse_probe_arg_body(body, &tp->size, parg, flags,
|
||||
body - arg);
|
||||
return traceprobe_parse_probe_arg_body(body, &tp->size, parg, ctx);
|
||||
}
|
||||
|
||||
void traceprobe_free_probe_arg(struct probe_arg *arg)
|
||||
@@ -858,6 +1192,151 @@ void traceprobe_free_probe_arg(struct probe_arg *arg)
|
||||
kfree(arg->fmt);
|
||||
}
|
||||
|
||||
static int argv_has_var_arg(int argc, const char *argv[], int *args_idx,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
int i, found = 0;
|
||||
|
||||
for (i = 0; i < argc; i++)
|
||||
if (str_has_prefix(argv[i], "$arg")) {
|
||||
trace_probe_log_set_index(i + 2);
|
||||
|
||||
if (!tparg_is_function_entry(ctx->flags)) {
|
||||
trace_probe_log_err(0, NOFENTRY_ARGS);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (isdigit(argv[i][4])) {
|
||||
found = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argv[i][4] != '*') {
|
||||
trace_probe_log_err(0, BAD_VAR);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (*args_idx >= 0 && *args_idx < argc) {
|
||||
trace_probe_log_err(0, DOUBLE_ARGS);
|
||||
return -EINVAL;
|
||||
}
|
||||
found = 1;
|
||||
*args_idx = i;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static int sprint_nth_btf_arg(int idx, const char *type,
|
||||
char *buf, int bufsize,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
struct btf *btf = traceprobe_get_btf();
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
if (idx >= ctx->nr_params) {
|
||||
trace_probe_log_err(0, NO_BTFARG);
|
||||
return -ENOENT;
|
||||
}
|
||||
name = btf_name_by_offset(btf, ctx->params[idx].name_off);
|
||||
if (!name) {
|
||||
trace_probe_log_err(0, NO_BTF_ENTRY);
|
||||
return -ENOENT;
|
||||
}
|
||||
ret = snprintf(buf, bufsize, "%s%s", name, type);
|
||||
if (ret >= bufsize) {
|
||||
trace_probe_log_err(0, ARGS_2LONG);
|
||||
return -E2BIG;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Return new_argv which must be freed after use */
|
||||
const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
int *new_argc, char *buf, int bufsize,
|
||||
struct traceprobe_parse_context *ctx)
|
||||
{
|
||||
const struct btf_param *params = NULL;
|
||||
int i, j, n, used, ret, args_idx = -1;
|
||||
const char **new_argv = NULL;
|
||||
int nr_params;
|
||||
|
||||
ret = argv_has_var_arg(argc, argv, &args_idx, ctx);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
if (!ret) {
|
||||
*new_argc = argc;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
params = find_btf_func_param(ctx->funcname, &nr_params,
|
||||
ctx->flags & TPARG_FL_TPOINT);
|
||||
if (IS_ERR(params)) {
|
||||
if (args_idx != -1) {
|
||||
/* $arg* requires BTF info */
|
||||
trace_probe_log_err(0, NOSUP_BTFARG);
|
||||
return (const char **)params;
|
||||
}
|
||||
*new_argc = argc;
|
||||
return NULL;
|
||||
}
|
||||
ctx->params = params;
|
||||
ctx->nr_params = nr_params;
|
||||
|
||||
if (args_idx >= 0)
|
||||
*new_argc = argc + ctx->nr_params - 1;
|
||||
else
|
||||
*new_argc = argc;
|
||||
|
||||
new_argv = kcalloc(*new_argc, sizeof(char *), GFP_KERNEL);
|
||||
if (!new_argv)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
used = 0;
|
||||
for (i = 0, j = 0; i < argc; i++) {
|
||||
trace_probe_log_set_index(i + 2);
|
||||
if (i == args_idx) {
|
||||
for (n = 0; n < nr_params; n++) {
|
||||
ret = sprint_nth_btf_arg(n, "", buf + used,
|
||||
bufsize - used, ctx);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
new_argv[j++] = buf + used;
|
||||
used += ret + 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_has_prefix(argv[i], "$arg")) {
|
||||
char *type = NULL;
|
||||
|
||||
n = simple_strtoul(argv[i] + 4, &type, 10);
|
||||
if (type && !(*type == ':' || *type == '\0')) {
|
||||
trace_probe_log_err(0, BAD_VAR);
|
||||
ret = -ENOENT;
|
||||
goto error;
|
||||
}
|
||||
/* Note: $argN starts from $arg1 */
|
||||
ret = sprint_nth_btf_arg(n - 1, type, buf + used,
|
||||
bufsize - used, ctx);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
new_argv[j++] = buf + used;
|
||||
used += ret + 1;
|
||||
} else
|
||||
new_argv[j++] = argv[i];
|
||||
}
|
||||
|
||||
return new_argv;
|
||||
|
||||
error:
|
||||
kfree(new_argv);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
int traceprobe_update_arg(struct probe_arg *arg)
|
||||
{
|
||||
struct fetch_insn *code = arg->code;
|
||||
|
@@ -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"
|
||||
@@ -32,7 +33,9 @@
|
||||
#define MAX_ARGSTR_LEN 63
|
||||
#define MAX_ARRAY_LEN 64
|
||||
#define MAX_ARG_NAME_LEN 32
|
||||
#define MAX_BTF_ARGS_LEN 128
|
||||
#define MAX_STRING_SIZE PATH_MAX
|
||||
#define MAX_ARG_BUF_LEN (MAX_TRACE_ARGS * MAX_ARG_NAME_LEN)
|
||||
|
||||
/* Reserved field names */
|
||||
#define FIELD_STRING_IP "__probe_ip"
|
||||
@@ -357,15 +360,42 @@ int trace_probe_print_args(struct trace_seq *s, struct probe_arg *args, int nr_a
|
||||
#define trace_probe_for_each_link_rcu(pos, tp) \
|
||||
list_for_each_entry_rcu(pos, &(tp)->event->files, list)
|
||||
|
||||
/*
|
||||
* The flags used for parsing trace_probe arguments.
|
||||
* TPARG_FL_RETURN, TPARG_FL_FENTRY and TPARG_FL_TEVENT are mutually exclusive.
|
||||
* TPARG_FL_KERNEL and TPARG_FL_USER are also mutually exclusive.
|
||||
* TPARG_FL_FPROBE and TPARG_FL_TPOINT are optional but it should be with
|
||||
* TPARG_FL_KERNEL.
|
||||
*/
|
||||
#define TPARG_FL_RETURN BIT(0)
|
||||
#define TPARG_FL_KERNEL BIT(1)
|
||||
#define TPARG_FL_FENTRY BIT(2)
|
||||
#define TPARG_FL_TPOINT BIT(3)
|
||||
#define TPARG_FL_TEVENT BIT(3)
|
||||
#define TPARG_FL_USER BIT(4)
|
||||
#define TPARG_FL_MASK GENMASK(4, 0)
|
||||
#define TPARG_FL_FPROBE BIT(5)
|
||||
#define TPARG_FL_TPOINT BIT(6)
|
||||
#define TPARG_FL_LOC_MASK GENMASK(4, 0)
|
||||
|
||||
static inline bool tparg_is_function_entry(unsigned int flags)
|
||||
{
|
||||
return (flags & TPARG_FL_LOC_MASK) == (TPARG_FL_KERNEL | TPARG_FL_FENTRY);
|
||||
}
|
||||
|
||||
struct traceprobe_parse_context {
|
||||
struct trace_event_call *event;
|
||||
const struct btf_param *params;
|
||||
s32 nr_params;
|
||||
const char *funcname;
|
||||
unsigned int flags;
|
||||
int offset;
|
||||
};
|
||||
|
||||
extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i,
|
||||
const char *argv, unsigned int flags);
|
||||
const char *argv,
|
||||
struct traceprobe_parse_context *ctx);
|
||||
const char **traceprobe_expand_meta_args(int argc, const char *argv[],
|
||||
int *new_argc, char *buf, int bufsize,
|
||||
struct traceprobe_parse_context *ctx);
|
||||
|
||||
extern int traceprobe_update_arg(struct probe_arg *arg);
|
||||
extern void traceprobe_free_probe_arg(struct probe_arg *arg);
|
||||
@@ -404,11 +434,12 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
|
||||
C(REFCNT_OPEN_BRACE, "Reference counter brace is not closed"), \
|
||||
C(BAD_REFCNT_SUFFIX, "Reference counter has wrong suffix"), \
|
||||
C(BAD_UPROBE_OFFS, "Invalid uprobe offset"), \
|
||||
C(MAXACT_NO_KPROBE, "Maxactive is not for kprobe"), \
|
||||
C(BAD_MAXACT_TYPE, "Maxactive is only for function exit"), \
|
||||
C(BAD_MAXACT, "Invalid maxactive number"), \
|
||||
C(MAXACT_TOO_BIG, "Maxactive is too big"), \
|
||||
C(BAD_PROBE_ADDR, "Invalid probed address or symbol"), \
|
||||
C(BAD_RETPROBE, "Retprobe address must be an function entry"), \
|
||||
C(NO_TRACEPOINT, "Tracepoint is not found"), \
|
||||
C(BAD_ADDR_SUFFIX, "Invalid probed address suffix"), \
|
||||
C(NO_GROUP_NAME, "Group name is not specified"), \
|
||||
C(GROUP_TOO_LONG, "Group name is too long"), \
|
||||
@@ -418,6 +449,7 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
|
||||
C(BAD_EVENT_NAME, "Event name must follow the same rules as C identifiers"), \
|
||||
C(EVENT_EXIST, "Given group/event name is already used by another event"), \
|
||||
C(RETVAL_ON_PROBE, "$retval is not available on probe"), \
|
||||
C(NO_RETVAL, "This function returns 'void' type"), \
|
||||
C(BAD_STACK_NUM, "Invalid stack number"), \
|
||||
C(BAD_ARG_NUM, "Invalid argument number"), \
|
||||
C(BAD_VAR, "Invalid $-valiable specified"), \
|
||||
@@ -456,7 +488,14 @@ 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(BAD_ATTACH_EVENT, "Attached event does not exist"),\
|
||||
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"), \
|
||||
C(BAD_VAR_ARGS, "$arg* must be an independent parameter without name etc."),\
|
||||
C(NOFENTRY_ARGS, "$arg* can be used only on function entry"), \
|
||||
C(DOUBLE_ARGS, "$arg* can be used only once in the parameters"), \
|
||||
C(ARGS_2LONG, "$arg* failed because the argument list is too long"),
|
||||
|
||||
#undef C
|
||||
#define C(a, b) TP_ERR_##a
|
||||
|
@@ -686,10 +686,12 @@ static int __trace_uprobe_create(int argc, const char **argv)
|
||||
|
||||
/* parse arguments */
|
||||
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {
|
||||
struct traceprobe_parse_context ctx = {
|
||||
.flags = (is_return ? TPARG_FL_RETURN : 0) | TPARG_FL_USER,
|
||||
};
|
||||
|
||||
trace_probe_log_set_index(i + 2);
|
||||
ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i],
|
||||
(is_return ? TPARG_FL_RETURN : 0) |
|
||||
TPARG_FL_USER);
|
||||
ret = traceprobe_parse_probe_arg(&tu->tp, i, argv[i], &ctx);
|
||||
if (ret)
|
||||
goto error;
|
||||
}
|
||||
|
Reference in New Issue
Block a user