mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	tracing/events: add support for modules to TRACE_EVENT
Impact: allow modules to add TRACE_EVENTS on load This patch adds the final hooks to allow modules to use the TRACE_EVENT macro. A notifier and a data structure are used to link the TRACE_EVENTs defined in the module to connect them with the ftrace event tracing system. It also adds the necessary automated clean ups to the trace events when a module is removed. Cc: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
This commit is contained in:
		
							parent
							
								
									17c873ec28
								
							
						
					
					
						commit
						6d723736e4
					
				
					 6 changed files with 113 additions and 32 deletions
				
			
		|  | @ -7,6 +7,7 @@ | ||||||
| 
 | 
 | ||||||
| struct trace_array; | struct trace_array; | ||||||
| struct tracer; | struct tracer; | ||||||
|  | struct dentry; | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * The trace entry - the most basic unit of tracing. This is what |  * The trace entry - the most basic unit of tracing. This is what | ||||||
|  | @ -87,6 +88,7 @@ struct ftrace_event_call { | ||||||
| 	char			*name; | 	char			*name; | ||||||
| 	char			*system; | 	char			*system; | ||||||
| 	struct dentry		*dir; | 	struct dentry		*dir; | ||||||
|  | 	struct trace_event	*event; | ||||||
| 	int			enabled; | 	int			enabled; | ||||||
| 	int			(*regfunc)(void); | 	int			(*regfunc)(void); | ||||||
| 	void			(*unregfunc)(void); | 	void			(*unregfunc)(void); | ||||||
|  | @ -97,6 +99,7 @@ struct ftrace_event_call { | ||||||
| 	struct list_head	fields; | 	struct list_head	fields; | ||||||
| 	int			n_preds; | 	int			n_preds; | ||||||
| 	struct filter_pred	**preds; | 	struct filter_pred	**preds; | ||||||
|  | 	void			*mod; | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_EVENT_PROFILE | #ifdef CONFIG_EVENT_PROFILE | ||||||
| 	atomic_t	profile_count; | 	atomic_t	profile_count; | ||||||
|  |  | ||||||
|  | @ -337,6 +337,10 @@ struct module | ||||||
| 	const char **trace_bprintk_fmt_start; | 	const char **trace_bprintk_fmt_start; | ||||||
| 	unsigned int num_trace_bprintk_fmt; | 	unsigned int num_trace_bprintk_fmt; | ||||||
| #endif | #endif | ||||||
|  | #ifdef CONFIG_EVENT_TRACING | ||||||
|  | 	struct ftrace_event_call *trace_events; | ||||||
|  | 	unsigned int num_trace_events; | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_MODULE_UNLOAD | #ifdef CONFIG_MODULE_UNLOAD | ||||||
| 	/* What modules depend on me? */ | 	/* What modules depend on me? */ | ||||||
|  |  | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| #ifndef _LINUX_TRACE_SEQ_H | #ifndef _LINUX_TRACE_SEQ_H | ||||||
| #define _LINUX_TRACE_SEQ_H | #define _LINUX_TRACE_SEQ_H | ||||||
| 
 | 
 | ||||||
|  | #include <linux/fs.h> | ||||||
|  | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Trace sequences are used to allow a function to call several other functions |  * Trace sequences are used to allow a function to call several other functions | ||||||
|  * to create a string of data to use (up to a max of PAGE_SIZE. |  * to create a string of data to use (up to a max of PAGE_SIZE. | ||||||
|  |  | ||||||
|  | @ -477,6 +477,7 @@ __attribute__((__aligned__(4)))						\ | ||||||
| __attribute__((section("_ftrace_events"))) event_##call = {		\ | __attribute__((section("_ftrace_events"))) event_##call = {		\ | ||||||
| 	.name			= #call,				\ | 	.name			= #call,				\ | ||||||
| 	.system			= __stringify(TRACE_SYSTEM),		\ | 	.system			= __stringify(TRACE_SYSTEM),		\ | ||||||
|  | 	.event			= &ftrace_event_type_##call,		\ | ||||||
| 	.raw_init		= ftrace_raw_init_event_##call,		\ | 	.raw_init		= ftrace_raw_init_event_##call,		\ | ||||||
| 	.regfunc		= ftrace_raw_reg_event_##call,		\ | 	.regfunc		= ftrace_raw_reg_event_##call,		\ | ||||||
| 	.unregfunc		= ftrace_raw_unreg_event_##call,	\ | 	.unregfunc		= ftrace_raw_unreg_event_##call,	\ | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
| */ | */ | ||||||
| #include <linux/module.h> | #include <linux/module.h> | ||||||
| #include <linux/moduleloader.h> | #include <linux/moduleloader.h> | ||||||
|  | #include <linux/ftrace_event.h> | ||||||
| #include <linux/init.h> | #include <linux/init.h> | ||||||
| #include <linux/kallsyms.h> | #include <linux/kallsyms.h> | ||||||
| #include <linux/fs.h> | #include <linux/fs.h> | ||||||
|  | @ -2172,6 +2173,12 @@ static noinline struct module *load_module(void __user *umod, | ||||||
| 					sizeof(*mod->tracepoints), | 					sizeof(*mod->tracepoints), | ||||||
| 					&mod->num_tracepoints); | 					&mod->num_tracepoints); | ||||||
| #endif | #endif | ||||||
|  | #ifdef CONFIG_EVENT_TRACING | ||||||
|  | 	mod->trace_events = section_objs(hdr, sechdrs, secstrings, | ||||||
|  | 					 "_ftrace_events", | ||||||
|  | 					 sizeof(*mod->trace_events), | ||||||
|  | 					 &mod->num_trace_events); | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_MODVERSIONS | #ifdef CONFIG_MODVERSIONS | ||||||
| 	if ((mod->num_syms && !mod->crcs) | 	if ((mod->num_syms && !mod->crcs) | ||||||
|  |  | ||||||
|  | @ -713,7 +713,13 @@ event_subsystem_dir(const char *name, struct dentry *d_events) | ||||||
| 		return d_events; | 		return d_events; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	system->name = name; | 	system->name = kstrdup(name, GFP_KERNEL); | ||||||
|  | 	if (!system->name) { | ||||||
|  | 		debugfs_remove(system->entry); | ||||||
|  | 		kfree(system); | ||||||
|  | 		return d_events; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	list_add(&system->list, &event_subsystems); | 	list_add(&system->list, &event_subsystems); | ||||||
| 
 | 
 | ||||||
| 	system->preds = NULL; | 	system->preds = NULL; | ||||||
|  | @ -738,7 +744,7 @@ event_create_dir(struct ftrace_event_call *call, struct dentry *d_events) | ||||||
| 	 * If the trace point header did not define TRACE_SYSTEM | 	 * If the trace point header did not define TRACE_SYSTEM | ||||||
| 	 * then the system would be called "TRACE_SYSTEM". | 	 * then the system would be called "TRACE_SYSTEM". | ||||||
| 	 */ | 	 */ | ||||||
| 	if (strcmp(call->system, "TRACE_SYSTEM") != 0) | 	if (strcmp(call->system, TRACE_SYSTEM) != 0) | ||||||
| 		d_events = event_subsystem_dir(call->system, d_events); | 		d_events = event_subsystem_dir(call->system, d_events); | ||||||
| 
 | 
 | ||||||
| 	if (call->raw_init) { | 	if (call->raw_init) { | ||||||
|  | @ -757,21 +763,13 @@ event_create_dir(struct ftrace_event_call *call, struct dentry *d_events) | ||||||
| 		return -1; | 		return -1; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (call->regfunc) { | 	if (call->regfunc) | ||||||
| 		entry = debugfs_create_file("enable", 0644, call->dir, call, | 		entry = trace_create_file("enable", 0644, call->dir, call, | ||||||
| 					    &ftrace_enable_fops); | 					  &ftrace_enable_fops); | ||||||
| 		if (!entry) |  | ||||||
| 			pr_warning("Could not create debugfs " |  | ||||||
| 				   "'%s/enable' entry\n", call->name); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if (call->id) { | 	if (call->id) | ||||||
| 		entry = debugfs_create_file("id", 0444, call->dir, call, | 		entry = trace_create_file("id", 0444, call->dir, call, | ||||||
| 				&ftrace_event_id_fops); | 					  &ftrace_event_id_fops); | ||||||
| 		if (!entry) |  | ||||||
| 			pr_warning("Could not create debugfs '%s/id' entry\n", |  | ||||||
| 					call->name); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if (call->define_fields) { | 	if (call->define_fields) { | ||||||
| 		ret = call->define_fields(); | 		ret = call->define_fields(); | ||||||
|  | @ -780,40 +778,102 @@ event_create_dir(struct ftrace_event_call *call, struct dentry *d_events) | ||||||
| 				   " events/%s\n", call->name); | 				   " events/%s\n", call->name); | ||||||
| 			return ret; | 			return ret; | ||||||
| 		} | 		} | ||||||
| 		entry = debugfs_create_file("filter", 0644, call->dir, call, | 		entry = trace_create_file("filter", 0644, call->dir, call, | ||||||
| 					    &ftrace_event_filter_fops); | 					  &ftrace_event_filter_fops); | ||||||
| 		if (!entry) |  | ||||||
| 			pr_warning("Could not create debugfs " |  | ||||||
| 				   "'%s/filter' entry\n", call->name); |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/* A trace may not want to export its format */ | 	/* A trace may not want to export its format */ | ||||||
| 	if (!call->show_format) | 	if (!call->show_format) | ||||||
| 		return 0; | 		return 0; | ||||||
| 
 | 
 | ||||||
| 	entry = debugfs_create_file("format", 0444, call->dir, call, | 	entry = trace_create_file("format", 0444, call->dir, call, | ||||||
| 				    &ftrace_event_format_fops); | 				  &ftrace_event_format_fops); | ||||||
| 	if (!entry) |  | ||||||
| 		pr_warning("Could not create debugfs " |  | ||||||
| 			   "'%s/format' entry\n", call->name); |  | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #define for_each_event(event, start, end)			\ | ||||||
|  | 	for (event = start;					\ | ||||||
|  | 	     (unsigned long)event < (unsigned long)end;		\ | ||||||
|  | 	     event++) | ||||||
|  | 
 | ||||||
|  | static void trace_module_add_events(struct module *mod) | ||||||
|  | { | ||||||
|  | 	struct ftrace_event_call *call, *start, *end; | ||||||
|  | 	struct dentry *d_events; | ||||||
|  | 
 | ||||||
|  | 	start = mod->trace_events; | ||||||
|  | 	end = mod->trace_events + mod->num_trace_events; | ||||||
|  | 
 | ||||||
|  | 	if (start == end) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	d_events = event_trace_events_dir(); | ||||||
|  | 	if (!d_events) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	for_each_event(call, start, end) { | ||||||
|  | 		/* The linker may leave blanks */ | ||||||
|  | 		if (!call->name) | ||||||
|  | 			continue; | ||||||
|  | 		call->mod = mod; | ||||||
|  | 		list_add(&call->list, &ftrace_events); | ||||||
|  | 		event_create_dir(call, d_events); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void trace_module_remove_events(struct module *mod) | ||||||
|  | { | ||||||
|  | 	struct ftrace_event_call *call, *p; | ||||||
|  | 
 | ||||||
|  | 	list_for_each_entry_safe(call, p, &ftrace_events, list) { | ||||||
|  | 		if (call->mod == mod) { | ||||||
|  | 			if (call->enabled) { | ||||||
|  | 				call->enabled = 0; | ||||||
|  | 				call->unregfunc(); | ||||||
|  | 			} | ||||||
|  | 			if (call->event) | ||||||
|  | 				unregister_ftrace_event(call->event); | ||||||
|  | 			debugfs_remove_recursive(call->dir); | ||||||
|  | 			list_del(&call->list); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int trace_module_notify(struct notifier_block *self, | ||||||
|  | 			unsigned long val, void *data) | ||||||
|  | { | ||||||
|  | 	struct module *mod = data; | ||||||
|  | 
 | ||||||
|  | 	mutex_lock(&event_mutex); | ||||||
|  | 	switch (val) { | ||||||
|  | 	case MODULE_STATE_COMING: | ||||||
|  | 		trace_module_add_events(mod); | ||||||
|  | 		break; | ||||||
|  | 	case MODULE_STATE_GOING: | ||||||
|  | 		trace_module_remove_events(mod); | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | 	mutex_unlock(&event_mutex); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | struct notifier_block trace_module_nb = { | ||||||
|  | 	.notifier_call = trace_module_notify, | ||||||
|  | 	.priority = 0, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| extern struct ftrace_event_call __start_ftrace_events[]; | extern struct ftrace_event_call __start_ftrace_events[]; | ||||||
| extern struct ftrace_event_call __stop_ftrace_events[]; | extern struct ftrace_event_call __stop_ftrace_events[]; | ||||||
| 
 | 
 | ||||||
| #define for_each_event(event)						\ |  | ||||||
| 	for (event = __start_ftrace_events;				\ |  | ||||||
| 	     (unsigned long)event < (unsigned long)__stop_ftrace_events; \ |  | ||||||
| 	     event++) |  | ||||||
| 
 |  | ||||||
| static __init int event_trace_init(void) | static __init int event_trace_init(void) | ||||||
| { | { | ||||||
| 	struct ftrace_event_call *call; | 	struct ftrace_event_call *call; | ||||||
| 	struct dentry *d_tracer; | 	struct dentry *d_tracer; | ||||||
| 	struct dentry *entry; | 	struct dentry *entry; | ||||||
| 	struct dentry *d_events; | 	struct dentry *d_events; | ||||||
|  | 	int ret; | ||||||
| 
 | 
 | ||||||
| 	d_tracer = tracing_init_dentry(); | 	d_tracer = tracing_init_dentry(); | ||||||
| 	if (!d_tracer) | 	if (!d_tracer) | ||||||
|  | @ -837,7 +897,7 @@ static __init int event_trace_init(void) | ||||||
| 	if (!d_events) | 	if (!d_events) | ||||||
| 		return 0; | 		return 0; | ||||||
| 
 | 
 | ||||||
| 	for_each_event(call) { | 	for_each_event(call, __start_ftrace_events, __stop_ftrace_events) { | ||||||
| 		/* The linker may leave blanks */ | 		/* The linker may leave blanks */ | ||||||
| 		if (!call->name) | 		if (!call->name) | ||||||
| 			continue; | 			continue; | ||||||
|  | @ -845,6 +905,10 @@ static __init int event_trace_init(void) | ||||||
| 		event_create_dir(call, d_events); | 		event_create_dir(call, d_events); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	ret = register_module_notifier(&trace_module_nb); | ||||||
|  | 	if (!ret) | ||||||
|  | 		pr_warning("Failed to register trace events module notifier\n"); | ||||||
|  | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| fs_initcall(event_trace_init); | fs_initcall(event_trace_init); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Steven Rostedt
						Steven Rostedt