tracing/probes: Add $arg* meta argument for all function args

Add the '$arg*' meta fetch argument for function-entry probe events. This
will be expanded to the all arguments of the function and the tracepoint
using BTF function argument information.

e.g.
 #  echo 'p vfs_read $arg*' >> dynamic_events
 #  echo 'f vfs_write $arg*' >> dynamic_events
 #  echo 't sched_overutilized_tp $arg*' >> dynamic_events
 # cat dynamic_events
p:kprobes/p_vfs_read_0 vfs_read file=file buf=buf count=count pos=pos
f:fprobes/vfs_write__entry vfs_write file=file buf=buf count=count pos=pos
t:tracepoints/sched_overutilized_tp sched_overutilized_tp rd=rd overutilized=overutilized

Also, single '$arg[0-9]*' will be converted to the BTF function argument.

NOTE: This seems like a wildcard, but a fake one at this moment. This
is just for telling user that this can be expanded to several arguments.
And it is not like other $-vars, you can not use this $arg* as a part of
fetch args, e.g. specifying name "foo=$arg*" and using it in dereferences
"+0($arg*)" will lead a parse error.

Link: https://lore.kernel.org/all/168507475126.913472.18329684401466211816.stgit@mhiramat.roam.corp.google.com/Signed-off-by: default avatarMasami Hiramatsu (Google) <mhiramat@kernel.org>
parent b576e097
......@@ -925,14 +925,16 @@ static int __trace_fprobe_create(int argc, const char *argv[])
* FETCHARG:TYPE : use TYPE instead of unsigned long.
*/
struct trace_fprobe *tf = 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 *event = NULL, *group = FPROBE_EVENT_SYSTEM;
const char **new_argv = NULL;
int maxactive = 0;
char buf[MAX_EVENT_NAME_LEN];
char gbuf[MAX_EVENT_NAME_LEN];
char sbuf[KSYM_NAME_LEN];
char abuf[MAX_BTF_ARGS_LEN];
bool is_tracepoint = false;
struct tracepoint *tpoint = NULL;
struct traceprobe_parse_context ctx = {
......@@ -1040,9 +1042,22 @@ static int __trace_fprobe_create(int argc, const char *argv[])
} else
ctx.funcname = symbol;
argc -= 2; argv += 2;
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 */
tf = alloc_trace_fprobe(group, event, symbol, tpoint, maxactive,
argc - 2, is_return);
argc, is_return);
if (IS_ERR(tf)) {
ret = PTR_ERR(tf);
/* This must return -ENOMEM, else there is a bug */
......@@ -1054,7 +1069,6 @@ static int __trace_fprobe_create(int argc, const char *argv[])
tf->mod = __module_text_address(
(unsigned long)tf->tpoint->probestub);
argc -= 2; argv += 2;
/* parse arguments */
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {
trace_probe_log_set_index(i + 2);
......@@ -1083,6 +1097,7 @@ static int __trace_fprobe_create(int argc, const char *argv[])
out:
trace_probe_log_clear();
kfree(new_argv);
kfree(symbol);
return ret;
......
......@@ -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,6 +743,7 @@ 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];
char abuf[MAX_BTF_ARGS_LEN];
struct traceprobe_parse_context ctx = { .flags = TPARG_FL_KERNEL };
switch (argv[0][0]) {
......@@ -854,19 +856,31 @@ 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 */
ctx.funcname = symbol;
for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {
trace_probe_log_set_index(i + 2);
ctx.offset = 0;
......@@ -894,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;
......
......@@ -371,9 +371,11 @@ static const char *type_from_btf_id(struct btf *btf, s32 id)
return NULL;
}
static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr)
static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr,
bool tracepoint)
{
struct btf *btf = traceprobe_get_btf();
const struct btf_param *param;
const struct btf_type *t;
s32 id;
......@@ -395,9 +397,16 @@ static const struct btf_param *find_btf_func_param(const char *funcname, s32 *nr
return ERR_PTR(-ENOENT);
*nr = btf_type_vlen(t);
param = (const struct btf_param *)(t + 1);
if (*nr)
return (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;
}
......@@ -418,7 +427,8 @@ static int parse_btf_arg(const char *varname, struct fetch_insn *code,
return -EINVAL;
if (!ctx->params) {
params = find_btf_func_param(ctx->funcname, &ctx->nr_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);
......@@ -451,12 +461,19 @@ static const struct fetch_type *parse_btf_arg_type(int arg_idx,
return find_fetch_type(typestr, ctx->flags);
}
#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)
{
......@@ -1114,6 +1131,150 @@ 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;
}
return 0;
}
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;
......
......@@ -33,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"
......@@ -391,6 +393,9 @@ struct traceprobe_parse_context {
extern int traceprobe_parse_probe_arg(struct trace_probe *tp, int i,
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);
......@@ -485,7 +490,11 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
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(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
......
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