tracing/probes: Add $arg* meta argument for all function args
authorMasami Hiramatsu (Google) <mhiramat@kernel.org>
Tue, 6 Jun 2023 12:39:56 +0000 (21:39 +0900)
committerMasami Hiramatsu (Google) <mhiramat@kernel.org>
Tue, 6 Jun 2023 12:39:56 +0000 (21:39 +0900)
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: Masami Hiramatsu (Google) <mhiramat@kernel.org>
kernel/trace/trace_fprobe.c
kernel/trace/trace_kprobe.c
kernel/trace/trace_probe.c
kernel/trace/trace_probe.h

index 2dd884609321821fdce391c93fee8d87b187974f..dfe2e546acdcd6dbea9996d4dd48f6fd6d9d44ae 100644 (file)
@@ -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;
 
index 7cc32da3e8e84af69cb92b077cbab6cc0d1341ae..74adb82331dd812b06f83988775e77fcbc57d7cf 100644 (file)
@@ -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;
 
index 08c18d9d4cf20fc5cadc3eb820225c35199b7408..7216435d6728090c0b4fade067a347243a37b6a9 100644 (file)
@@ -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;
index 7af121996ce9163663f84effe52de441fdd407c0..e7fa2f2ed01cdc7f342da52d32430b0e54c86c1a 100644 (file)
@@ -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