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 tracer; | ||||
| struct dentry; | ||||
| 
 | ||||
| /*
 | ||||
|  * The trace entry - the most basic unit of tracing. This is what | ||||
|  | @ -87,6 +88,7 @@ struct ftrace_event_call { | |||
| 	char			*name; | ||||
| 	char			*system; | ||||
| 	struct dentry		*dir; | ||||
| 	struct trace_event	*event; | ||||
| 	int			enabled; | ||||
| 	int			(*regfunc)(void); | ||||
| 	void			(*unregfunc)(void); | ||||
|  | @ -97,6 +99,7 @@ struct ftrace_event_call { | |||
| 	struct list_head	fields; | ||||
| 	int			n_preds; | ||||
| 	struct filter_pred	**preds; | ||||
| 	void			*mod; | ||||
| 
 | ||||
| #ifdef CONFIG_EVENT_PROFILE | ||||
| 	atomic_t	profile_count; | ||||
|  |  | |||
|  | @ -337,6 +337,10 @@ struct module | |||
| 	const char **trace_bprintk_fmt_start; | ||||
| 	unsigned int num_trace_bprintk_fmt; | ||||
| #endif | ||||
| #ifdef CONFIG_EVENT_TRACING | ||||
| 	struct ftrace_event_call *trace_events; | ||||
| 	unsigned int num_trace_events; | ||||
| #endif | ||||
| 
 | ||||
| #ifdef CONFIG_MODULE_UNLOAD | ||||
| 	/* What modules depend on me? */ | ||||
|  |  | |||
|  | @ -1,6 +1,8 @@ | |||
| #ifndef _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 | ||||
|  * 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 = {		\ | ||||
| 	.name			= #call,				\ | ||||
| 	.system			= __stringify(TRACE_SYSTEM),		\ | ||||
| 	.event			= &ftrace_event_type_##call,		\ | ||||
| 	.raw_init		= ftrace_raw_init_event_##call,		\ | ||||
| 	.regfunc		= ftrace_raw_reg_event_##call,		\ | ||||
| 	.unregfunc		= ftrace_raw_unreg_event_##call,	\ | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| */ | ||||
| #include <linux/module.h> | ||||
| #include <linux/moduleloader.h> | ||||
| #include <linux/ftrace_event.h> | ||||
| #include <linux/init.h> | ||||
| #include <linux/kallsyms.h> | ||||
| #include <linux/fs.h> | ||||
|  | @ -2172,6 +2173,12 @@ static noinline struct module *load_module(void __user *umod, | |||
| 					sizeof(*mod->tracepoints), | ||||
| 					&mod->num_tracepoints); | ||||
| #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 | ||||
| 	if ((mod->num_syms && !mod->crcs) | ||||
|  |  | |||
|  | @ -713,7 +713,13 @@ event_subsystem_dir(const char *name, struct dentry *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); | ||||
| 
 | ||||
| 	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 | ||||
| 	 * 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); | ||||
| 
 | ||||
| 	if (call->raw_init) { | ||||
|  | @ -757,21 +763,13 @@ event_create_dir(struct ftrace_event_call *call, struct dentry *d_events) | |||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (call->regfunc) { | ||||
| 		entry = debugfs_create_file("enable", 0644, call->dir, call, | ||||
| 	if (call->regfunc) | ||||
| 		entry = trace_create_file("enable", 0644, call->dir, call, | ||||
| 					  &ftrace_enable_fops); | ||||
| 		if (!entry) | ||||
| 			pr_warning("Could not create debugfs " | ||||
| 				   "'%s/enable' entry\n", call->name); | ||||
| 	} | ||||
| 
 | ||||
| 	if (call->id) { | ||||
| 		entry = debugfs_create_file("id", 0444, call->dir, call, | ||||
| 	if (call->id) | ||||
| 		entry = trace_create_file("id", 0444, call->dir, call, | ||||
| 					  &ftrace_event_id_fops); | ||||
| 		if (!entry) | ||||
| 			pr_warning("Could not create debugfs '%s/id' entry\n", | ||||
| 					call->name); | ||||
| 	} | ||||
| 
 | ||||
| 	if (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); | ||||
| 			return ret; | ||||
| 		} | ||||
| 		entry = debugfs_create_file("filter", 0644, call->dir, call, | ||||
| 		entry = trace_create_file("filter", 0644, call->dir, call, | ||||
| 					  &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 */ | ||||
| 	if (!call->show_format) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	entry = debugfs_create_file("format", 0444, call->dir, call, | ||||
| 	entry = trace_create_file("format", 0444, call->dir, call, | ||||
| 				  &ftrace_event_format_fops); | ||||
| 	if (!entry) | ||||
| 		pr_warning("Could not create debugfs " | ||||
| 			   "'%s/format' entry\n", call->name); | ||||
| 
 | ||||
| 	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 __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) | ||||
| { | ||||
| 	struct ftrace_event_call *call; | ||||
| 	struct dentry *d_tracer; | ||||
| 	struct dentry *entry; | ||||
| 	struct dentry *d_events; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	d_tracer = tracing_init_dentry(); | ||||
| 	if (!d_tracer) | ||||
|  | @ -837,7 +897,7 @@ static __init int event_trace_init(void) | |||
| 	if (!d_events) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	for_each_event(call) { | ||||
| 	for_each_event(call, __start_ftrace_events, __stop_ftrace_events) { | ||||
| 		/* The linker may leave blanks */ | ||||
| 		if (!call->name) | ||||
| 			continue; | ||||
|  | @ -845,6 +905,10 @@ static __init int event_trace_init(void) | |||
| 		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; | ||||
| } | ||||
| fs_initcall(event_trace_init); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Steven Rostedt
						Steven Rostedt