tracing: Cache ":mod:" events for modules not loaded yet
authorSteven Rostedt <rostedt@goodmis.org>
Thu, 16 Jan 2025 14:33:36 +0000 (09:33 -0500)
committerSteven Rostedt (Google) <rostedt@goodmis.org>
Thu, 16 Jan 2025 14:41:08 +0000 (09:41 -0500)
When the :mod: command is written into /sys/kernel/tracing/set_event (or
that file within an instance), if the module specified after the ":mod:"
is not yet loaded, it will store that string internally. When the module
is loaded, it will enable the events as if the module was loaded when the
string was written into the set_event file.

This can also be useful to enable events that are in the init section of
the module, as the events are enabled before the init section is executed.

This also works on the kernel command line:

 trace_event=:mod:<module>

Will enable the events for <module> when it is loaded.

Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: https://lore.kernel.org/20250116143533.514730995@goodmis.org
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Documentation/admin-guide/kernel-parameters.txt
Documentation/trace/events.rst
kernel/trace/ftrace.c
kernel/trace/trace.c
kernel/trace/trace.h
kernel/trace/trace_events.c

index 3872bc6ec49d63772755504966ae70113f24a1db..4f563cb0ca0f63dc5e9b84ecba35d8ef6d781861 100644 (file)
                        comma-separated list of trace events to enable. See
                        also Documentation/trace/events.rst
 
+                       To enable modules, use :mod: keyword:
+
+                       trace_event=:mod:<module>
+
+                       The value before :mod: will only enable specific events
+                       that are part of the module. See the above mentioned
+                       document for more information.
+
        trace_instance=[instance-info]
                        [FTRACE] Create a ring buffer instance early in boot up.
                        This will be listed in:
index 3db57516eb861d41975145aeee4e329bfe84d483..2d88a2acacc0306227377286710325a92ea34065 100644 (file)
@@ -60,7 +60,9 @@ a specific module::
 
        # echo ':mod:<module>' > /sys/kernel/tracing/set_event
 
-Will enable all events in the module ``<module>``.
+Will enable all events in the module ``<module>``.  If the module is not yet
+loaded, the string will be saved and when a module is that matches ``<module>``
+is loaded, then it will apply the enabling of events then.
 
 The text before ``:mod:`` will be parsed to specify specific events that the
 module creates::
index 9b17efb1a87dd7053eee818a585403177c27fe16..cafcfc97ff2aaf1903ff6d7977dc4d92776d0f81 100644 (file)
@@ -4930,23 +4930,6 @@ static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops,
        return __ftrace_hash_move_and_update_ops(ops, orig_hash, hash, enable);
 }
 
-static bool module_exists(const char *module)
-{
-       /* All modules have the symbol __this_module */
-       static const char this_mod[] = "__this_module";
-       char modname[MAX_PARAM_PREFIX_LEN + sizeof(this_mod) + 2];
-       unsigned long val;
-       int n;
-
-       n = snprintf(modname, sizeof(modname), "%s:%s", module, this_mod);
-
-       if (n > sizeof(modname) - 1)
-               return false;
-
-       val = module_kallsyms_lookup_name(modname);
-       return val != 0;
-}
-
 static int cache_mod(struct trace_array *tr,
                     const char *func, char *module, int enable)
 {
index cb85ee4a88076b34d91fe2952990af1cc86d37c1..87402b6e8c58bc6d1ae62aacb9bd9df963280de5 100644 (file)
@@ -9407,6 +9407,10 @@ trace_array_create_systems(const char *name, const char *systems,
        INIT_LIST_HEAD(&tr->hist_vars);
        INIT_LIST_HEAD(&tr->err_log);
 
+#ifdef CONFIG_MODULES
+       INIT_LIST_HEAD(&tr->mod_events);
+#endif
+
        if (allocate_trace_buffers(tr, trace_buf_size) < 0)
                goto out_free_tr;
 
@@ -9823,6 +9827,24 @@ late_initcall_sync(trace_eval_sync);
 
 
 #ifdef CONFIG_MODULES
+
+bool module_exists(const char *module)
+{
+       /* All modules have the symbol __this_module */
+       static const char this_mod[] = "__this_module";
+       char modname[MAX_PARAM_PREFIX_LEN + sizeof(this_mod) + 2];
+       unsigned long val;
+       int n;
+
+       n = snprintf(modname, sizeof(modname), "%s:%s", module, this_mod);
+
+       if (n > sizeof(modname) - 1)
+               return false;
+
+       val = module_kallsyms_lookup_name(modname);
+       return val != 0;
+}
+
 static void trace_module_add_evals(struct module *mod)
 {
        if (!mod->num_trace_evals)
@@ -10535,6 +10557,10 @@ __init static int tracer_alloc_buffers(void)
 #endif
        ftrace_init_global_array_ops(&global_trace);
 
+#ifdef CONFIG_MODULES
+       INIT_LIST_HEAD(&global_trace.mod_events);
+#endif
+
        init_trace_flags_index(&global_trace);
 
        register_tracer(&nop_trace);
index 9691b47b5f3da297598f00736bab697487f9a201..05ea0ebf5ebad39dc1ea4aa93cbbb661e54ff804 100644 (file)
@@ -400,6 +400,9 @@ struct trace_array {
        cpumask_var_t           pipe_cpumask;
        int                     ref;
        int                     trace_ref;
+#ifdef CONFIG_MODULES
+       struct list_head        mod_events;
+#endif
 #ifdef CONFIG_FUNCTION_TRACER
        struct ftrace_ops       *ops;
        struct trace_pid_list   __rcu *function_pids;
@@ -434,6 +437,15 @@ enum {
        TRACE_ARRAY_FL_BOOT     = BIT(1),
 };
 
+#ifdef CONFIG_MODULES
+bool module_exists(const char *module);
+#else
+static inline bool module_exists(const char *module)
+{
+       return false;
+}
+#endif
+
 extern struct list_head ftrace_trace_arrays;
 
 extern struct mutex trace_types_lock;
index 5c7d0e07618d76ead8b3c5e0504bbd30a8fb12e2..f762e554fad42d8c51aaa6fc385de46c150deca9 100644 (file)
@@ -857,6 +857,120 @@ static int ftrace_event_enable_disable(struct trace_event_file *file,
        return __ftrace_event_enable_disable(file, enable, 0);
 }
 
+#if CONFIG_MODULES
+struct event_mod_load {
+       struct list_head        list;
+       char                    *module;
+       char                    *match;
+       char                    *system;
+       char                    *event;
+};
+
+static void free_event_mod(struct event_mod_load *event_mod)
+{
+       list_del(&event_mod->list);
+       kfree(event_mod->module);
+       kfree(event_mod->match);
+       kfree(event_mod->system);
+       kfree(event_mod->event);
+       kfree(event_mod);
+}
+
+static void clear_mod_events(struct trace_array *tr)
+{
+       struct event_mod_load *event_mod, *n;
+
+       list_for_each_entry_safe(event_mod, n, &tr->mod_events, list) {
+               free_event_mod(event_mod);
+       }
+}
+
+static int remove_cache_mod(struct trace_array *tr, const char *mod,
+                           const char *match, const char *system, const char *event)
+{
+       struct event_mod_load *event_mod, *n;
+       int ret = -EINVAL;
+
+       list_for_each_entry_safe(event_mod, n, &tr->mod_events, list) {
+               if (strcmp(event_mod->module, mod) != 0)
+                       continue;
+
+               if (match && strcmp(event_mod->match, match) != 0)
+                       continue;
+
+               if (system &&
+                   (!event_mod->system || strcmp(event_mod->system, system) != 0))
+                       continue;
+
+               if (event &&
+                   (!event_mod->event || strcmp(event_mod->event, event) != 0))
+                       continue;
+
+               free_event_mod(event_mod);
+               ret = 0;
+       }
+
+       return ret;
+}
+
+static int cache_mod(struct trace_array *tr, const char *mod, int set,
+                    const char *match, const char *system, const char *event)
+{
+       struct event_mod_load *event_mod;
+
+       /* If the module exists, then this just failed to find an event */
+       if (module_exists(mod))
+               return -EINVAL;
+
+       /* See if this is to remove a cached filter */
+       if (!set)
+               return remove_cache_mod(tr, mod, match, system, event);
+
+       event_mod = kzalloc(sizeof(*event_mod), GFP_KERNEL);
+       if (!event_mod)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&event_mod->list);
+       event_mod->module = kstrdup(mod, GFP_KERNEL);
+       if (!event_mod->module)
+               goto out_free;
+
+       if (match) {
+               event_mod->match = kstrdup(match, GFP_KERNEL);
+               if (!event_mod->match)
+                       goto out_free;
+       }
+
+       if (system) {
+               event_mod->system = kstrdup(system, GFP_KERNEL);
+               if (!event_mod->system)
+                       goto out_free;
+       }
+
+       if (event) {
+               event_mod->event = kstrdup(event, GFP_KERNEL);
+               if (!event_mod->event)
+                       goto out_free;
+       }
+
+       list_add(&event_mod->list, &tr->mod_events);
+
+       return 0;
+
+ out_free:
+       free_event_mod(event_mod);
+
+       return -ENOMEM;
+}
+#else /* CONFIG_MODULES */
+static inline void clear_mod_events(struct trace_array *tr) { }
+static int cache_mod(struct trace_array *tr, const char *mod, int set,
+                    const char *match, const char *system, const char *event)
+{
+       return -EINVAL;
+}
+#endif
+
 static void ftrace_clear_events(struct trace_array *tr)
 {
        struct trace_event_file *file;
@@ -865,6 +979,7 @@ static void ftrace_clear_events(struct trace_array *tr)
        list_for_each_entry(file, &tr->events, list) {
                ftrace_event_enable_disable(file, 0);
        }
+       clear_mod_events(tr);
        mutex_unlock(&event_mutex);
 }
 
@@ -1215,6 +1330,13 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match,
                ret = eret;
        }
 
+       /*
+        * If this is a module setting and nothing was found,
+        * check if the module was loaded. If it wasn't cache it.
+        */
+       if (module && ret == -EINVAL && !eret)
+               ret = cache_mod(tr, module, set, match, sub, event);
+
        return ret;
 }
 
@@ -1416,37 +1538,71 @@ static void *t_start(struct seq_file *m, loff_t *pos)
        return file;
 }
 
+enum set_event_iter_type {
+       SET_EVENT_FILE,
+       SET_EVENT_MOD,
+};
+
+struct set_event_iter {
+       enum set_event_iter_type        type;
+       union {
+               struct trace_event_file *file;
+               struct event_mod_load   *event_mod;
+       };
+};
+
 static void *
 s_next(struct seq_file *m, void *v, loff_t *pos)
 {
-       struct trace_event_file *file = v;
+       struct set_event_iter *iter = v;
+       struct trace_event_file *file;
        struct trace_array *tr = m->private;
 
        (*pos)++;
 
-       list_for_each_entry_continue(file, &tr->events, list) {
-               if (file->flags & EVENT_FILE_FL_ENABLED)
-                       return file;
+       if (iter->type == SET_EVENT_FILE) {
+               file = iter->file;
+               list_for_each_entry_continue(file, &tr->events, list) {
+                       if (file->flags & EVENT_FILE_FL_ENABLED) {
+                               iter->file = file;
+                               return iter;
+                       }
+               }
+#ifdef CONFIG_MODULES
+               iter->type = SET_EVENT_MOD;
+               iter->event_mod = list_entry(&tr->mod_events, struct event_mod_load, list);
+#endif
        }
 
+#ifdef CONFIG_MODULES
+       list_for_each_entry_continue(iter->event_mod, &tr->mod_events, list)
+               return iter;
+#endif
+
        return NULL;
 }
 
 static void *s_start(struct seq_file *m, loff_t *pos)
 {
-       struct trace_event_file *file;
        struct trace_array *tr = m->private;
+       struct set_event_iter *iter;
        loff_t l;
 
+       iter = kzalloc(sizeof(iter), GFP_KERNEL);
+       if (!iter)
+               return NULL;
+
        mutex_lock(&event_mutex);
 
-       file = list_entry(&tr->events, struct trace_event_file, list);
+       iter->type = SET_EVENT_FILE;
+       iter->file = list_entry(&tr->events, struct trace_event_file, list);
+
        for (l = 0; l <= *pos; ) {
-               file = s_next(m, file, &l);
-               if (!file)
+               iter = s_next(m, iter, &l);
+               if (!iter)
                        break;
        }
-       return file;
+       return iter;
 }
 
 static int t_show(struct seq_file *m, void *v)
@@ -1466,6 +1622,45 @@ static void t_stop(struct seq_file *m, void *p)
        mutex_unlock(&event_mutex);
 }
 
+#ifdef CONFIG_MODULES
+static int s_show(struct seq_file *m, void *v)
+{
+       struct set_event_iter *iter = v;
+       const char *system;
+       const char *event;
+
+       if (iter->type == SET_EVENT_FILE)
+               return t_show(m, iter->file);
+
+       /* When match is set, system and event are not */
+       if (iter->event_mod->match) {
+               seq_printf(m, "%s:mod:%s", iter->event_mod->match,
+                          iter->event_mod->module);
+               return 0;
+       }
+
+       system = iter->event_mod->system ? : "*";
+       event = iter->event_mod->event ? : "*";
+
+       seq_printf(m, "%s:%s:mod:%s\n", system, event, iter->event_mod->module);
+
+       return 0;
+}
+#else /* CONFIG_MODULES */
+static int s_show(struct seq_file *m, void *v)
+{
+       struct set_event_iter *iter = v;
+
+       return t_show(m, iter->file);
+}
+#endif
+
+static void s_stop(struct seq_file *m, void *p)
+{
+       kfree(p);
+       t_stop(m, NULL);
+}
+
 static void *
 __next(struct seq_file *m, void *v, loff_t *pos, int type)
 {
@@ -2253,8 +2448,8 @@ static const struct seq_operations show_event_seq_ops = {
 static const struct seq_operations show_set_event_seq_ops = {
        .start = s_start,
        .next = s_next,
-       .show = t_show,
-       .stop = t_stop,
+       .show = s_show,
+       .stop = s_stop,
 };
 
 static const struct seq_operations show_set_pid_seq_ops = {
@@ -3385,6 +3580,28 @@ EXPORT_SYMBOL_GPL(trace_remove_event_call);
             event++)
 
 #ifdef CONFIG_MODULES
+static void update_cache(struct trace_array *tr, struct module *mod)
+{
+       struct event_mod_load *event_mod, *n;
+
+       list_for_each_entry_safe(event_mod, n, &tr->mod_events, list) {
+               if (strcmp(event_mod->module, mod->name) != 0)
+                       continue;
+
+               __ftrace_set_clr_event_nolock(tr, event_mod->match,
+                                             event_mod->system,
+                                             event_mod->event, 1, mod->name);
+               free_event_mod(event_mod);
+       }
+}
+
+static void update_cache_events(struct module *mod)
+{
+       struct trace_array *tr;
+
+       list_for_each_entry(tr, &ftrace_trace_arrays, list)
+               update_cache(tr, mod);
+}
 
 static void trace_module_add_events(struct module *mod)
 {
@@ -3407,6 +3624,8 @@ static void trace_module_add_events(struct module *mod)
                __register_event(*call, mod);
                __add_event_to_tracers(*call);
        }
+
+       update_cache_events(mod);
 }
 
 static void trace_module_remove_events(struct module *mod)