Signed-off-by: Ryan Chung <[email protected]>
---
 kernel/trace/trace_fprobe.c | 247 ++++++++++++++++++++++++++++--------
 1 file changed, 192 insertions(+), 55 deletions(-)

diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c
index b36ade43d4b3..ec5b6e1c1a1b 100644
--- a/kernel/trace/trace_fprobe.c
+++ b/kernel/trace/trace_fprobe.c
@@ -191,6 +191,9 @@ struct trace_fprobe {
        bool                    tprobe;
        struct tracepoint_user  *tuser;
        struct trace_probe      tp;
+       char                    *filter;
+       char                    *nofilter;
+       bool                    list_mode;
 };
 
 static bool is_trace_fprobe(struct dyn_event *ev)
@@ -203,14 +206,10 @@ static struct trace_fprobe *to_trace_fprobe(struct 
dyn_event *ev)
        return container_of(ev, struct trace_fprobe, devent);
 }
 
-/**
- * for_each_trace_fprobe - iterate over the trace_fprobe list
- * @pos:       the struct trace_fprobe * for each entry
- * @dpos:      the struct dyn_event * to use as a loop cursor
- */
-#define for_each_trace_fprobe(pos, dpos)       \
-       for_each_dyn_event(dpos)                \
-               if (is_trace_fprobe(dpos) && (pos = to_trace_fprobe(dpos)))
+static struct trace_fprobe *trace_fprobe_from_dyn(struct dyn_event *ev)
+{
+       return is_trace_fprobe(ev) ? to_trace_fprobe(ev) : NULL;
+}
 
 static bool trace_fprobe_is_return(struct trace_fprobe *tf)
 {
@@ -227,6 +226,109 @@ static const char *trace_fprobe_symbol(struct 
trace_fprobe *tf)
        return tf->symbol ? tf->symbol : "unknown";
 }
 
+static bool has_wildcard(const char *s)
+{
+       return s && (strchr(s, '*') || strchr(s, '?'));
+}
+
+static int parse_fprobe_spec(const char *in, bool is_tracepoint,
+               char **base, bool *is_return, bool *list_mode,
+               char **filter, char **nofilter)
+{
+       const char *p;
+       char *work = NULL;
+       char *b = NULL, *f = NULL, *nf = NULL;
+       bool legacy_ret = false;
+       bool list = false;
+       int ret = 0;
+
+       if (!in || !base || !is_return || !list_mode || !filter || !nofilter)
+               return -EINVAL;
+
+       *base = NULL; *filter = NULL; *nofilter = NULL;
+       *is_return = false; *list_mode = false;
+
+       if (is_tracepoint) {
+               if (strchr(in, ',') || strchr(in, ':'))
+                       return -EINVAL;
+               if (strstr(in, "%return"))
+                       return -EINVAL;
+               for (p = in; *p; p++)
+                       if (!isalnum(*p) && *p != '_')
+                               return -EINVAL;
+               b = kstrdup(in, GFP_KERNEL);
+               if (!b)
+                       return -ENOMEM;
+               *base = b;
+               return 0;
+       }
+
+       work = kstrdup(in, GFP_KERNEL);
+       if (!work)
+               return -ENOMEM;
+
+       p = strstr(work, "%return");
+       if (p) {
+               if (!strcmp(p, ":exit")) {
+                       *is_return = true;
+                       *p = '\0';
+               } else if (!strcmp(p, ":entry")) {
+                       *p = '\0';
+               } else {
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+
+       list = !!strchr(work, ',') || has_wildcard(work);
+       if (legacy_ret)
+               *is_return = true;
+
+       b = kstrdup(work, GFP_KERNEL);
+       if (!b) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       if (list) {
+               char *tmp = b, *tok;
+               size_t fsz = strlen(b) + 1, nfsz = strlen(b) + 1;
+
+               f = kzalloc(fsz, GFP_KERNEL);
+               nf = kzalloc(nfsz, GFP_KERNEL);
+               if (!f || !nf) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+
+               while ((tok = strsep(&tmp, ",")) != NULL) {
+                       char *dst;
+                       bool neg = (*tok == '!');
+
+                       if (*tok == '\0')
+                               continue;
+                       if (neg)
+                               tok++;
+                       dst = neg ? nf : f;
+                       if (dst[0] != '\0')
+                               strcat(dst, ",");
+                       strcat(dst, tok);
+               }
+               *list_mode = true;
+       }
+
+       *base = b; b = NULL;
+       *filter = f; f = NULL;
+       *nofilter = nf; nf = NULL;
+
+out:
+       kfree(work);
+       kfree(b);
+       kfree(f);
+       kfree(nf);
+       return ret;
+}
+
 static bool trace_fprobe_is_busy(struct dyn_event *ev)
 {
        struct trace_fprobe *tf = to_trace_fprobe(ev);
@@ -556,13 +658,17 @@ static void free_trace_fprobe(struct trace_fprobe *tf)
                trace_probe_cleanup(&tf->tp);
                if (tf->tuser)
                        tracepoint_user_put(tf->tuser);
+               kfree(tf->filter);
+               kfree(tf->nofilter);
                kfree(tf->symbol);
                kfree(tf);
        }
 }
 
 /* Since alloc_trace_fprobe() can return error, check the pointer is ERR too. 
*/
-DEFINE_FREE(free_trace_fprobe, struct trace_fprobe *, if (!IS_ERR_OR_NULL(_T)) 
free_trace_fprobe(_T))
+DEFINE_FREE(free_trace_fprobe, struct trace_fprobe *,
+       if (!IS_ERR_OR_NULL(_T))
+               free_trace_fprobe(_T))
 
 /*
  * Allocate new trace_probe and initialize it (including fprobe).
@@ -605,10 +711,16 @@ static struct trace_fprobe *find_trace_fprobe(const char 
*event,
        struct dyn_event *pos;
        struct trace_fprobe *tf;
 
-       for_each_trace_fprobe(tf, pos)
+       list_for_each_entry(pos, &dyn_event_list, list) {
+               tf = trace_fprobe_from_dyn(pos);
+               if (!tf)
+                       continue;
+
                if (strcmp(trace_probe_name(&tf->tp), event) == 0 &&
                    strcmp(trace_probe_group_name(&tf->tp), group) == 0)
                        return tf;
+       }
+
        return NULL;
 }
 
@@ -835,7 +947,12 @@ static int __register_trace_fprobe(struct trace_fprobe *tf)
        if (trace_fprobe_is_tracepoint(tf))
                return __regsiter_tracepoint_fprobe(tf);
 
-       /* TODO: handle filter, nofilter or symbol list */
+       /* Registration path:
+        *  - list_mode: pass filter/nofilter
+        *  - single: pass symbol only (legacy)
+        */
+       if (tf->list_mode)
+               return register_fprobe(&tf->fp, tf->filter, tf->nofilter);
        return register_fprobe(&tf->fp, tf->symbol, NULL);
 }
 
@@ -1114,7 +1231,11 @@ static int __tprobe_event_module_cb(struct 
notifier_block *self,
                return NOTIFY_DONE;
 
        mutex_lock(&event_mutex);
-       for_each_trace_fprobe(tf, pos) {
+       list_for_each_entry(pos, &dyn_event_list, list) {
+               tf = trace_fprobe_from_dyn(pos);
+               if (!tf)
+                       continue;
+
                /* Skip fprobe and disabled tprobe events. */
                if (!trace_fprobe_is_tracepoint(tf) || !tf->tuser)
                        continue;
@@ -1155,55 +1276,35 @@ static int parse_symbol_and_return(int argc, const char 
*argv[],
                                   char **symbol, bool *is_return,
                                   bool is_tracepoint)
 {
-       char *tmp = strchr(argv[1], '%');
-       int i;
-
-       if (tmp) {
-               int len = tmp - argv[1];
-
-               if (!is_tracepoint && !strcmp(tmp, "%return")) {
-                       *is_return = true;
-               } else {
-                       trace_probe_log_err(len, BAD_ADDR_SUFFIX);
-                       return -EINVAL;
-               }
-               *symbol = kmemdup_nul(argv[1], len, GFP_KERNEL);
-       } else
-               *symbol = kstrdup(argv[1], GFP_KERNEL);
-       if (!*symbol)
-               return -ENOMEM;
-
-       if (*is_return)
-               return 0;
+       int i, ret;
+       bool list_mode = false;
+       char *filter = NULL; *nofilter = NULL;
 
-       if (is_tracepoint) {
-               tmp = *symbol;
-               while (*tmp && (isalnum(*tmp) || *tmp == '_'))
-                       tmp++;
-               if (*tmp) {
-                       /* find a wrong character. */
-                       trace_probe_log_err(tmp - *symbol, BAD_TP_NAME);
-                       kfree(*symbol);
-                       *symbol = NULL;
-                       return -EINVAL;
-               }
-       }
+       ret = parse_fprobe_spec(argv[1], is_tracepoint, symbol, is_return,
+                       &list_mode, &filter, &nofilter);
+       if (ret)
+               return ret;
 
-       /* If there is $retval, this should be a return fprobe. */
        for (i = 2; i < argc; i++) {
-               tmp = strstr(argv[i], "$retval");
+               char *tmp = strstr(argv[i], "$retval");
+
                if (tmp && !isalnum(tmp[7]) && tmp[7] != '_') {
                        if (is_tracepoint) {
                                trace_probe_log_set_index(i);
                                trace_probe_log_err(tmp - argv[i], 
RETVAL_ON_PROBE);
                                kfree(*symbol);
                                *symbol = NULL;
+                               kfree(filter);
+                               kfree(nofilter);
                                return -EINVAL;
                        }
                        *is_return = true;
                        break;
                }
        }
+
+       kfree(filter);
+       kfree(nofilter);
        return 0;
 }
 
@@ -1247,6 +1348,11 @@ static int trace_fprobe_create_internal(int argc, const 
char *argv[],
        int i, new_argc = 0, ret = 0;
        bool is_tracepoint = false;
        bool is_return = false;
+       bool list_mode = false;
+
+       char *parsed_filter __free(kfree) = NULL;
+       char *parsed_nofilter __free(kfree) = NULL;
+       bool has_wild = false;
 
        if ((argv[0][0] != 'f' && argv[0][0] != 't') || argc < 2)
                return -ECANCELED;
@@ -1267,8 +1373,9 @@ static int trace_fprobe_create_internal(int argc, const 
char *argv[],
 
        trace_probe_log_set_index(1);
 
-       /* a symbol(or tracepoint) must be specified */
-       ret = parse_symbol_and_return(argc, argv, &symbol, &is_return, 
is_tracepoint);
+       /* Parse spec early (single vs list, suffix, base symbol) */
+       ret = parse_fprobe_spec(argv[1], is_tracepoint, &symbol, &is_return,
+                       &list_mode, &parsed_filter, &parsed_nofilter);
        if (ret < 0)
                return -EINVAL;
 
@@ -1283,10 +1390,16 @@ static int trace_fprobe_create_internal(int argc, const 
char *argv[],
                        return -EINVAL;
        }
 
-       if (!event) {
-               ebuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
-               if (!ebuf)
-                       return -ENOMEM;
+               if (!event) {
+               /*
+                * Event name rules:
+                * - For list/wildcard: require explicit [GROUP/]EVENT
+                * - For single literal: autogenerate symbol__entry/symbol__exit
+                */
+                       if (list_mode || has_wildcard(symbol)) {
+                               trace_probe_log_err(0, NO_GROUP_NAME);
+                       return -EINVAL;
+               }
                /* Make a new event name */
                if (is_tracepoint)
                        snprintf(ebuf, MAX_EVENT_NAME_LEN, "%s%s",
@@ -1319,7 +1432,8 @@ static int trace_fprobe_create_internal(int argc, const 
char *argv[],
                                                        NULL, NULL, NULL, sbuf);
                }
        }
-       if (!ctx->funcname)
+
+       if (!list_mode && !has_wildcard(symbol) && !is_tracepoint)
                ctx->funcname = symbol;
 
        abuf = kmalloc(MAX_BTF_ARGS_LEN, GFP_KERNEL);
@@ -1353,6 +1467,21 @@ static int trace_fprobe_create_internal(int argc, const 
char *argv[],
                return ret;
        }
 
+       /* carry list parsing result into tf */
+       if (!is_tracepoint) {
+               tf->list_mode = list_mode;
+                       if (parsed_filter) {
+                               tf->filter = kstrdup(parsed_filter, GFP_KERNEL);
+                               if (!tf->filter)
+                                       return -ENOMEM;
+                       }
+                       if (parsed_nofilter) {
+                               tf->nofilter = kstrdup(parsed_nofilter, 
GFP_KERNEL);
+                               if (!tf->nofilter)
+                                       return -ENOMEM;
+                       }
+               }
+
        /* parse arguments */
        for (i = 0; i < argc; i++) {
                trace_probe_log_set_index(i + 2);
@@ -1439,8 +1568,16 @@ static int trace_fprobe_show(struct seq_file *m, struct 
dyn_event *ev)
        seq_printf(m, ":%s/%s", trace_probe_group_name(&tf->tp),
                                trace_probe_name(&tf->tp));
 
-       seq_printf(m, " %s%s", trace_fprobe_symbol(tf),
-                              trace_fprobe_is_return(tf) ? "%return" : "");
+       seq_printf(m, "%s", trace_fprobe_symbol(tf));
+       if (!trace_fprobe_is_tracepoint(tf)) {
+               if (tf->list_mode) {
+                       if (trace_fprobe_is_return(tf))
+                               seq_puts(m, ":exit");
+               } else {
+                       if (trace_fprobe_is_return(tf))
+                               seq_puts(m, "%return");
+               }
+       }
 
        for (i = 0; i < tf->tp.nr_args; i++)
                seq_printf(m, " %s=%s", tf->tp.args[i].name, 
tf->tp.args[i].comm);
-- 
2.43.0


Reply via email to