mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-01 00:58:39 +02:00 
			
		
		
		
	tracing/kprobes: Use dyn_event framework for kprobe events
Use dyn_event framework for kprobe events. This shows kprobe events on "tracing/dynamic_events" file. User can also define new events via tracing/dynamic_events. Link: http://lkml.kernel.org/r/154140855646.17322.6619219995865980392.stgit@devbox Reviewed-by: Tom Zanussi <tom.zanussi@linux.intel.com> Tested-by: Tom Zanussi <tom.zanussi@linux.intel.com> Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org> Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
This commit is contained in:
		
							parent
							
								
									5448d44c38
								
							
						
					
					
						commit
						6212dd2968
					
				
					 5 changed files with 210 additions and 148 deletions
				
			
		|  | @ -20,6 +20,9 @@ current_tracer. Instead of that, add probe points via | ||||||
| /sys/kernel/debug/tracing/kprobe_events, and enable it via | /sys/kernel/debug/tracing/kprobe_events, and enable it via | ||||||
| /sys/kernel/debug/tracing/events/kprobes/<EVENT>/enable. | /sys/kernel/debug/tracing/events/kprobes/<EVENT>/enable. | ||||||
| 
 | 
 | ||||||
|  | You can also use /sys/kernel/debug/tracing/dynamic_events instead of | ||||||
|  | kprobe_events. That interface will provide unified access to other | ||||||
|  | dynamic events too. | ||||||
| 
 | 
 | ||||||
| Synopsis of kprobe_events | Synopsis of kprobe_events | ||||||
| ------------------------- | ------------------------- | ||||||
|  |  | ||||||
|  | @ -461,6 +461,7 @@ config KPROBE_EVENTS | ||||||
| 	bool "Enable kprobes-based dynamic events" | 	bool "Enable kprobes-based dynamic events" | ||||||
| 	select TRACING | 	select TRACING | ||||||
| 	select PROBE_EVENTS | 	select PROBE_EVENTS | ||||||
|  | 	select DYNAMIC_EVENTS | ||||||
| 	default y | 	default y | ||||||
| 	help | 	help | ||||||
| 	  This allows the user to add tracing events (similar to tracepoints) | 	  This allows the user to add tracing events (similar to tracepoints) | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ | ||||||
| #include <linux/rculist.h> | #include <linux/rculist.h> | ||||||
| #include <linux/error-injection.h> | #include <linux/error-injection.h> | ||||||
| 
 | 
 | ||||||
|  | #include "trace_dynevent.h" | ||||||
| #include "trace_kprobe_selftest.h" | #include "trace_kprobe_selftest.h" | ||||||
| #include "trace_probe.h" | #include "trace_probe.h" | ||||||
| #include "trace_probe_tmpl.h" | #include "trace_probe_tmpl.h" | ||||||
|  | @ -19,17 +20,51 @@ | ||||||
| #define KPROBE_EVENT_SYSTEM "kprobes" | #define KPROBE_EVENT_SYSTEM "kprobes" | ||||||
| #define KRETPROBE_MAXACTIVE_MAX 4096 | #define KRETPROBE_MAXACTIVE_MAX 4096 | ||||||
| 
 | 
 | ||||||
|  | static int trace_kprobe_create(int argc, const char **argv); | ||||||
|  | static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev); | ||||||
|  | static int trace_kprobe_release(struct dyn_event *ev); | ||||||
|  | static bool trace_kprobe_is_busy(struct dyn_event *ev); | ||||||
|  | static bool trace_kprobe_match(const char *system, const char *event, | ||||||
|  | 			       struct dyn_event *ev); | ||||||
|  | 
 | ||||||
|  | static struct dyn_event_operations trace_kprobe_ops = { | ||||||
|  | 	.create = trace_kprobe_create, | ||||||
|  | 	.show = trace_kprobe_show, | ||||||
|  | 	.is_busy = trace_kprobe_is_busy, | ||||||
|  | 	.free = trace_kprobe_release, | ||||||
|  | 	.match = trace_kprobe_match, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Kprobe event core functions |  * Kprobe event core functions | ||||||
|  */ |  */ | ||||||
| struct trace_kprobe { | struct trace_kprobe { | ||||||
| 	struct list_head	list; | 	struct dyn_event	devent; | ||||||
| 	struct kretprobe	rp;	/* Use rp.kp for kprobe use */ | 	struct kretprobe	rp;	/* Use rp.kp for kprobe use */ | ||||||
| 	unsigned long __percpu *nhit; | 	unsigned long __percpu *nhit; | ||||||
| 	const char		*symbol;	/* symbol name */ | 	const char		*symbol;	/* symbol name */ | ||||||
| 	struct trace_probe	tp; | 	struct trace_probe	tp; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | static bool is_trace_kprobe(struct dyn_event *ev) | ||||||
|  | { | ||||||
|  | 	return ev->ops == &trace_kprobe_ops; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct trace_kprobe *to_trace_kprobe(struct dyn_event *ev) | ||||||
|  | { | ||||||
|  | 	return container_of(ev, struct trace_kprobe, devent); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * for_each_trace_kprobe - iterate over the trace_kprobe list | ||||||
|  |  * @pos:	the struct trace_kprobe * for each entry | ||||||
|  |  * @dpos:	the struct dyn_event * to use as a loop cursor | ||||||
|  |  */ | ||||||
|  | #define for_each_trace_kprobe(pos, dpos)	\ | ||||||
|  | 	for_each_dyn_event(dpos)		\ | ||||||
|  | 		if (is_trace_kprobe(dpos) && (pos = to_trace_kprobe(dpos))) | ||||||
|  | 
 | ||||||
| #define SIZEOF_TRACE_KPROBE(n)				\ | #define SIZEOF_TRACE_KPROBE(n)				\ | ||||||
| 	(offsetof(struct trace_kprobe, tp.args) +	\ | 	(offsetof(struct trace_kprobe, tp.args) +	\ | ||||||
| 	(sizeof(struct probe_arg) * (n))) | 	(sizeof(struct probe_arg) * (n))) | ||||||
|  | @ -81,6 +116,22 @@ static nokprobe_inline bool trace_kprobe_module_exist(struct trace_kprobe *tk) | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static bool trace_kprobe_is_busy(struct dyn_event *ev) | ||||||
|  | { | ||||||
|  | 	struct trace_kprobe *tk = to_trace_kprobe(ev); | ||||||
|  | 
 | ||||||
|  | 	return trace_probe_is_enabled(&tk->tp); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool trace_kprobe_match(const char *system, const char *event, | ||||||
|  | 			       struct dyn_event *ev) | ||||||
|  | { | ||||||
|  | 	struct trace_kprobe *tk = to_trace_kprobe(ev); | ||||||
|  | 
 | ||||||
|  | 	return strcmp(trace_event_name(&tk->tp.call), event) == 0 && | ||||||
|  | 	    (!system || strcmp(tk->tp.call.class->system, system) == 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static nokprobe_inline unsigned long trace_kprobe_nhit(struct trace_kprobe *tk) | static nokprobe_inline unsigned long trace_kprobe_nhit(struct trace_kprobe *tk) | ||||||
| { | { | ||||||
| 	unsigned long nhit = 0; | 	unsigned long nhit = 0; | ||||||
|  | @ -128,9 +179,6 @@ bool trace_kprobe_error_injectable(struct trace_event_call *call) | ||||||
| static int register_kprobe_event(struct trace_kprobe *tk); | static int register_kprobe_event(struct trace_kprobe *tk); | ||||||
| static int unregister_kprobe_event(struct trace_kprobe *tk); | static int unregister_kprobe_event(struct trace_kprobe *tk); | ||||||
| 
 | 
 | ||||||
| static DEFINE_MUTEX(probe_lock); |  | ||||||
| static LIST_HEAD(probe_list); |  | ||||||
| 
 |  | ||||||
| static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs); | static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs); | ||||||
| static int kretprobe_dispatcher(struct kretprobe_instance *ri, | static int kretprobe_dispatcher(struct kretprobe_instance *ri, | ||||||
| 				struct pt_regs *regs); | 				struct pt_regs *regs); | ||||||
|  | @ -192,7 +240,7 @@ static struct trace_kprobe *alloc_trace_kprobe(const char *group, | ||||||
| 	if (!tk->tp.class.system) | 	if (!tk->tp.class.system) | ||||||
| 		goto error; | 		goto error; | ||||||
| 
 | 
 | ||||||
| 	INIT_LIST_HEAD(&tk->list); | 	dyn_event_init(&tk->devent, &trace_kprobe_ops); | ||||||
| 	INIT_LIST_HEAD(&tk->tp.files); | 	INIT_LIST_HEAD(&tk->tp.files); | ||||||
| 	return tk; | 	return tk; | ||||||
| error: | error: | ||||||
|  | @ -207,6 +255,9 @@ static void free_trace_kprobe(struct trace_kprobe *tk) | ||||||
| { | { | ||||||
| 	int i; | 	int i; | ||||||
| 
 | 
 | ||||||
|  | 	if (!tk) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
| 	for (i = 0; i < tk->tp.nr_args; i++) | 	for (i = 0; i < tk->tp.nr_args; i++) | ||||||
| 		traceprobe_free_probe_arg(&tk->tp.args[i]); | 		traceprobe_free_probe_arg(&tk->tp.args[i]); | ||||||
| 
 | 
 | ||||||
|  | @ -220,9 +271,10 @@ static void free_trace_kprobe(struct trace_kprobe *tk) | ||||||
| static struct trace_kprobe *find_trace_kprobe(const char *event, | static struct trace_kprobe *find_trace_kprobe(const char *event, | ||||||
| 					      const char *group) | 					      const char *group) | ||||||
| { | { | ||||||
|  | 	struct dyn_event *pos; | ||||||
| 	struct trace_kprobe *tk; | 	struct trace_kprobe *tk; | ||||||
| 
 | 
 | ||||||
| 	list_for_each_entry(tk, &probe_list, list) | 	for_each_trace_kprobe(tk, pos) | ||||||
| 		if (strcmp(trace_event_name(&tk->tp.call), event) == 0 && | 		if (strcmp(trace_event_name(&tk->tp.call), event) == 0 && | ||||||
| 		    strcmp(tk->tp.call.class->system, group) == 0) | 		    strcmp(tk->tp.call.class->system, group) == 0) | ||||||
| 			return tk; | 			return tk; | ||||||
|  | @ -321,7 +373,7 @@ disable_trace_kprobe(struct trace_kprobe *tk, struct trace_event_file *file) | ||||||
| 	 * created with perf_event_open. We don't need to wait for these | 	 * created with perf_event_open. We don't need to wait for these | ||||||
| 	 * trace_kprobes | 	 * trace_kprobes | ||||||
| 	 */ | 	 */ | ||||||
| 	if (list_empty(&tk->list)) | 	if (list_empty(&tk->devent.list)) | ||||||
| 		wait = 0; | 		wait = 0; | ||||||
|  out: |  out: | ||||||
| 	if (wait) { | 	if (wait) { | ||||||
|  | @ -419,7 +471,7 @@ static void __unregister_trace_kprobe(struct trace_kprobe *tk) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Unregister a trace_probe and probe_event: call with locking probe_lock */ | /* Unregister a trace_probe and probe_event */ | ||||||
| static int unregister_trace_kprobe(struct trace_kprobe *tk) | static int unregister_trace_kprobe(struct trace_kprobe *tk) | ||||||
| { | { | ||||||
| 	/* Enabled event can not be unregistered */ | 	/* Enabled event can not be unregistered */ | ||||||
|  | @ -431,7 +483,7 @@ static int unregister_trace_kprobe(struct trace_kprobe *tk) | ||||||
| 		return -EBUSY; | 		return -EBUSY; | ||||||
| 
 | 
 | ||||||
| 	__unregister_trace_kprobe(tk); | 	__unregister_trace_kprobe(tk); | ||||||
| 	list_del(&tk->list); | 	dyn_event_remove(&tk->devent); | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  | @ -442,7 +494,7 @@ static int register_trace_kprobe(struct trace_kprobe *tk) | ||||||
| 	struct trace_kprobe *old_tk; | 	struct trace_kprobe *old_tk; | ||||||
| 	int ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
| 	mutex_lock(&probe_lock); | 	mutex_lock(&event_mutex); | ||||||
| 
 | 
 | ||||||
| 	/* Delete old (same name) event if exist */ | 	/* Delete old (same name) event if exist */ | ||||||
| 	old_tk = find_trace_kprobe(trace_event_name(&tk->tp.call), | 	old_tk = find_trace_kprobe(trace_event_name(&tk->tp.call), | ||||||
|  | @ -471,10 +523,10 @@ static int register_trace_kprobe(struct trace_kprobe *tk) | ||||||
| 	if (ret < 0) | 	if (ret < 0) | ||||||
| 		unregister_kprobe_event(tk); | 		unregister_kprobe_event(tk); | ||||||
| 	else | 	else | ||||||
| 		list_add_tail(&tk->list, &probe_list); | 		dyn_event_add(&tk->devent); | ||||||
| 
 | 
 | ||||||
| end: | end: | ||||||
| 	mutex_unlock(&probe_lock); | 	mutex_unlock(&event_mutex); | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -483,6 +535,7 @@ static int trace_kprobe_module_callback(struct notifier_block *nb, | ||||||
| 				       unsigned long val, void *data) | 				       unsigned long val, void *data) | ||||||
| { | { | ||||||
| 	struct module *mod = data; | 	struct module *mod = data; | ||||||
|  | 	struct dyn_event *pos; | ||||||
| 	struct trace_kprobe *tk; | 	struct trace_kprobe *tk; | ||||||
| 	int ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
|  | @ -490,8 +543,8 @@ static int trace_kprobe_module_callback(struct notifier_block *nb, | ||||||
| 		return NOTIFY_DONE; | 		return NOTIFY_DONE; | ||||||
| 
 | 
 | ||||||
| 	/* Update probes on coming module */ | 	/* Update probes on coming module */ | ||||||
| 	mutex_lock(&probe_lock); | 	mutex_lock(&event_mutex); | ||||||
| 	list_for_each_entry(tk, &probe_list, list) { | 	for_each_trace_kprobe(tk, pos) { | ||||||
| 		if (trace_kprobe_within_module(tk, mod)) { | 		if (trace_kprobe_within_module(tk, mod)) { | ||||||
| 			/* Don't need to check busy - this should have gone. */ | 			/* Don't need to check busy - this should have gone. */ | ||||||
| 			__unregister_trace_kprobe(tk); | 			__unregister_trace_kprobe(tk); | ||||||
|  | @ -502,7 +555,7 @@ static int trace_kprobe_module_callback(struct notifier_block *nb, | ||||||
| 					mod->name, ret); | 					mod->name, ret); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	mutex_unlock(&probe_lock); | 	mutex_unlock(&event_mutex); | ||||||
| 
 | 
 | ||||||
| 	return NOTIFY_DONE; | 	return NOTIFY_DONE; | ||||||
| } | } | ||||||
|  | @ -520,7 +573,7 @@ static inline void sanitize_event_name(char *name) | ||||||
| 			*name = '_'; | 			*name = '_'; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int create_trace_kprobe(int argc, char **argv) | static int trace_kprobe_create(int argc, const char *argv[]) | ||||||
| { | { | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Argument syntax: | 	 * Argument syntax: | ||||||
|  | @ -544,9 +597,10 @@ static int create_trace_kprobe(int argc, char **argv) | ||||||
| 	 *  FETCHARG:TYPE : use TYPE instead of unsigned long. | 	 *  FETCHARG:TYPE : use TYPE instead of unsigned long. | ||||||
| 	 */ | 	 */ | ||||||
| 	struct trace_kprobe *tk; | 	struct trace_kprobe *tk; | ||||||
| 	int i, ret = 0; | 	int i, len, ret = 0; | ||||||
| 	bool is_return = false, is_delete = false; | 	bool is_return = false; | ||||||
| 	char *symbol = NULL, *event = NULL, *group = NULL; | 	char *symbol = NULL, *tmp = NULL; | ||||||
|  | 	const char *event = NULL, *group = KPROBE_EVENT_SYSTEM; | ||||||
| 	int maxactive = 0; | 	int maxactive = 0; | ||||||
| 	long offset = 0; | 	long offset = 0; | ||||||
| 	void *addr = NULL; | 	void *addr = NULL; | ||||||
|  | @ -554,26 +608,26 @@ static int create_trace_kprobe(int argc, char **argv) | ||||||
| 	unsigned int flags = TPARG_FL_KERNEL; | 	unsigned int flags = TPARG_FL_KERNEL; | ||||||
| 
 | 
 | ||||||
| 	/* argc must be >= 1 */ | 	/* argc must be >= 1 */ | ||||||
| 	if (argv[0][0] == 'p') | 	if (argv[0][0] == 'r') { | ||||||
| 		is_return = false; |  | ||||||
| 	else if (argv[0][0] == 'r') { |  | ||||||
| 		is_return = true; | 		is_return = true; | ||||||
| 		flags |= TPARG_FL_RETURN; | 		flags |= TPARG_FL_RETURN; | ||||||
| 	} else if (argv[0][0] == '-') | 	} else if (argv[0][0] != 'p' || argc < 2) | ||||||
| 		is_delete = true; | 		return -ECANCELED; | ||||||
| 	else { |  | ||||||
| 		pr_info("Probe definition must be started with 'p', 'r' or" |  | ||||||
| 			" '-'.\n"); |  | ||||||
| 		return -EINVAL; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	event = strchr(&argv[0][1], ':'); | 	event = strchr(&argv[0][1], ':'); | ||||||
| 	if (event) { | 	if (event) | ||||||
| 		event[0] = '\0'; |  | ||||||
| 		event++; | 		event++; | ||||||
| 	} | 
 | ||||||
| 	if (is_return && isdigit(argv[0][1])) { | 	if (is_return && isdigit(argv[0][1])) { | ||||||
| 		ret = kstrtouint(&argv[0][1], 0, &maxactive); | 		if (event) | ||||||
|  | 			len = event - &argv[0][1] - 1; | ||||||
|  | 		else | ||||||
|  | 			len = strlen(&argv[0][1]); | ||||||
|  | 		if (len > MAX_EVENT_NAME_LEN - 1) | ||||||
|  | 			return -E2BIG; | ||||||
|  | 		memcpy(buf, &argv[0][1], len); | ||||||
|  | 		buf[len] = '\0'; | ||||||
|  | 		ret = kstrtouint(buf, 0, &maxactive); | ||||||
| 		if (ret) { | 		if (ret) { | ||||||
| 			pr_info("Failed to parse maxactive.\n"); | 			pr_info("Failed to parse maxactive.\n"); | ||||||
| 			return ret; | 			return ret; | ||||||
|  | @ -588,74 +642,37 @@ static int create_trace_kprobe(int argc, char **argv) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (event) { |  | ||||||
| 		char *slash; |  | ||||||
| 
 |  | ||||||
| 		slash = strchr(event, '/'); |  | ||||||
| 		if (slash) { |  | ||||||
| 			group = event; |  | ||||||
| 			event = slash + 1; |  | ||||||
| 			slash[0] = '\0'; |  | ||||||
| 			if (strlen(group) == 0) { |  | ||||||
| 				pr_info("Group name is not specified\n"); |  | ||||||
| 				return -EINVAL; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if (strlen(event) == 0) { |  | ||||||
| 			pr_info("Event name is not specified\n"); |  | ||||||
| 			return -EINVAL; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if (!group) |  | ||||||
| 		group = KPROBE_EVENT_SYSTEM; |  | ||||||
| 
 |  | ||||||
| 	if (is_delete) { |  | ||||||
| 		if (!event) { |  | ||||||
| 			pr_info("Delete command needs an event name.\n"); |  | ||||||
| 			return -EINVAL; |  | ||||||
| 		} |  | ||||||
| 		mutex_lock(&probe_lock); |  | ||||||
| 		tk = find_trace_kprobe(event, group); |  | ||||||
| 		if (!tk) { |  | ||||||
| 			mutex_unlock(&probe_lock); |  | ||||||
| 			pr_info("Event %s/%s doesn't exist.\n", group, event); |  | ||||||
| 			return -ENOENT; |  | ||||||
| 		} |  | ||||||
| 		/* delete an event */ |  | ||||||
| 		ret = unregister_trace_kprobe(tk); |  | ||||||
| 		if (ret == 0) |  | ||||||
| 			free_trace_kprobe(tk); |  | ||||||
| 		mutex_unlock(&probe_lock); |  | ||||||
| 		return ret; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (argc < 2) { |  | ||||||
| 		pr_info("Probe point is not specified.\n"); |  | ||||||
| 		return -EINVAL; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/* try to parse an address. if that fails, try to read the
 | 	/* try to parse an address. if that fails, try to read the
 | ||||||
| 	 * input as a symbol. */ | 	 * input as a symbol. */ | ||||||
| 	if (kstrtoul(argv[1], 0, (unsigned long *)&addr)) { | 	if (kstrtoul(argv[1], 0, (unsigned long *)&addr)) { | ||||||
|  | 		/* Check whether uprobe event specified */ | ||||||
|  | 		if (strchr(argv[1], '/') && strchr(argv[1], ':')) | ||||||
|  | 			return -ECANCELED; | ||||||
| 		/* a symbol specified */ | 		/* a symbol specified */ | ||||||
| 		symbol = argv[1]; | 		symbol = kstrdup(argv[1], GFP_KERNEL); | ||||||
|  | 		if (!symbol) | ||||||
|  | 			return -ENOMEM; | ||||||
| 		/* TODO: support .init module functions */ | 		/* TODO: support .init module functions */ | ||||||
| 		ret = traceprobe_split_symbol_offset(symbol, &offset); | 		ret = traceprobe_split_symbol_offset(symbol, &offset); | ||||||
| 		if (ret || offset < 0 || offset > UINT_MAX) { | 		if (ret || offset < 0 || offset > UINT_MAX) { | ||||||
| 			pr_info("Failed to parse either an address or a symbol.\n"); | 			pr_info("Failed to parse either an address or a symbol.\n"); | ||||||
| 			return ret; | 			goto out; | ||||||
| 		} | 		} | ||||||
| 		if (kprobe_on_func_entry(NULL, symbol, offset)) | 		if (kprobe_on_func_entry(NULL, symbol, offset)) | ||||||
| 			flags |= TPARG_FL_FENTRY; | 			flags |= TPARG_FL_FENTRY; | ||||||
| 		if (offset && is_return && !(flags & TPARG_FL_FENTRY)) { | 		if (offset && is_return && !(flags & TPARG_FL_FENTRY)) { | ||||||
| 			pr_info("Given offset is not valid for return probe.\n"); | 			pr_info("Given offset is not valid for return probe.\n"); | ||||||
| 			return -EINVAL; | 			ret = -EINVAL; | ||||||
|  | 			goto out; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	argc -= 2; argv += 2; | 	argc -= 2; argv += 2; | ||||||
| 
 | 
 | ||||||
| 	/* setup a probe */ | 	if (event) { | ||||||
| 	if (!event) { | 		ret = traceprobe_parse_event_name(&event, &group, buf); | ||||||
|  | 		if (ret) | ||||||
|  | 			goto out; | ||||||
|  | 	} else { | ||||||
| 		/* Make a new event name */ | 		/* Make a new event name */ | ||||||
| 		if (symbol) | 		if (symbol) | ||||||
| 			snprintf(buf, MAX_EVENT_NAME_LEN, "%c_%s_%ld", | 			snprintf(buf, MAX_EVENT_NAME_LEN, "%c_%s_%ld", | ||||||
|  | @ -666,17 +683,27 @@ static int create_trace_kprobe(int argc, char **argv) | ||||||
| 		sanitize_event_name(buf); | 		sanitize_event_name(buf); | ||||||
| 		event = buf; | 		event = buf; | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	/* setup a probe */ | ||||||
| 	tk = alloc_trace_kprobe(group, event, addr, symbol, offset, maxactive, | 	tk = alloc_trace_kprobe(group, event, addr, symbol, offset, maxactive, | ||||||
| 			       argc, is_return); | 			       argc, is_return); | ||||||
| 	if (IS_ERR(tk)) { | 	if (IS_ERR(tk)) { | ||||||
| 		pr_info("Failed to allocate trace_probe.(%d)\n", | 		pr_info("Failed to allocate trace_probe.(%d)\n", | ||||||
| 			(int)PTR_ERR(tk)); | 			(int)PTR_ERR(tk)); | ||||||
| 		return PTR_ERR(tk); | 		ret = PTR_ERR(tk); | ||||||
|  | 		goto out; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/* parse arguments */ | 	/* parse arguments */ | ||||||
| 	for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) { | 	for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) { | ||||||
| 		ret = traceprobe_parse_probe_arg(&tk->tp, i, argv[i], flags); | 		tmp = kstrdup(argv[i], GFP_KERNEL); | ||||||
|  | 		if (!tmp) { | ||||||
|  | 			ret = -ENOMEM; | ||||||
|  | 			goto error; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		ret = traceprobe_parse_probe_arg(&tk->tp, i, tmp, flags); | ||||||
|  | 		kfree(tmp); | ||||||
| 		if (ret) | 		if (ret) | ||||||
| 			goto error; | 			goto error; | ||||||
| 	} | 	} | ||||||
|  | @ -684,60 +711,39 @@ static int create_trace_kprobe(int argc, char **argv) | ||||||
| 	ret = register_trace_kprobe(tk); | 	ret = register_trace_kprobe(tk); | ||||||
| 	if (ret) | 	if (ret) | ||||||
| 		goto error; | 		goto error; | ||||||
| 	return 0; | out: | ||||||
|  | 	kfree(symbol); | ||||||
|  | 	return ret; | ||||||
| 
 | 
 | ||||||
| error: | error: | ||||||
| 	free_trace_kprobe(tk); | 	free_trace_kprobe(tk); | ||||||
| 	return ret; | 	goto out; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int release_all_trace_kprobes(void) | static int create_or_delete_trace_kprobe(int argc, char **argv) | ||||||
| { | { | ||||||
| 	struct trace_kprobe *tk; | 	int ret; | ||||||
| 	int ret = 0; |  | ||||||
| 
 | 
 | ||||||
| 	mutex_lock(&probe_lock); | 	if (argv[0][0] == '-') | ||||||
| 	/* Ensure no probe is in use. */ | 		return dyn_event_release(argc, argv, &trace_kprobe_ops); | ||||||
| 	list_for_each_entry(tk, &probe_list, list) | 
 | ||||||
| 		if (trace_probe_is_enabled(&tk->tp)) { | 	ret = trace_kprobe_create(argc, (const char **)argv); | ||||||
| 			ret = -EBUSY; | 	return ret == -ECANCELED ? -EINVAL : ret; | ||||||
| 			goto end; | } | ||||||
| 		} | 
 | ||||||
| 	/* TODO: Use batch unregistration */ | static int trace_kprobe_release(struct dyn_event *ev) | ||||||
| 	while (!list_empty(&probe_list)) { | { | ||||||
| 		tk = list_entry(probe_list.next, struct trace_kprobe, list); | 	struct trace_kprobe *tk = to_trace_kprobe(ev); | ||||||
| 		ret = unregister_trace_kprobe(tk); | 	int ret = unregister_trace_kprobe(tk); | ||||||
| 		if (ret) | 
 | ||||||
| 			goto end; | 	if (!ret) | ||||||
| 		free_trace_kprobe(tk); | 		free_trace_kprobe(tk); | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| end: |  | ||||||
| 	mutex_unlock(&probe_lock); |  | ||||||
| 
 |  | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Probes listing interfaces */ | static int trace_kprobe_show(struct seq_file *m, struct dyn_event *ev) | ||||||
| static void *probes_seq_start(struct seq_file *m, loff_t *pos) |  | ||||||
| { | { | ||||||
| 	mutex_lock(&probe_lock); | 	struct trace_kprobe *tk = to_trace_kprobe(ev); | ||||||
| 	return seq_list_start(&probe_list, *pos); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void *probes_seq_next(struct seq_file *m, void *v, loff_t *pos) |  | ||||||
| { |  | ||||||
| 	return seq_list_next(v, &probe_list, pos); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void probes_seq_stop(struct seq_file *m, void *v) |  | ||||||
| { |  | ||||||
| 	mutex_unlock(&probe_lock); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static int probes_seq_show(struct seq_file *m, void *v) |  | ||||||
| { |  | ||||||
| 	struct trace_kprobe *tk = v; |  | ||||||
| 	int i; | 	int i; | ||||||
| 
 | 
 | ||||||
| 	seq_putc(m, trace_kprobe_is_return(tk) ? 'r' : 'p'); | 	seq_putc(m, trace_kprobe_is_return(tk) ? 'r' : 'p'); | ||||||
|  | @ -759,10 +765,20 @@ static int probes_seq_show(struct seq_file *m, void *v) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int probes_seq_show(struct seq_file *m, void *v) | ||||||
|  | { | ||||||
|  | 	struct dyn_event *ev = v; | ||||||
|  | 
 | ||||||
|  | 	if (!is_trace_kprobe(ev)) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	return trace_kprobe_show(m, ev); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static const struct seq_operations probes_seq_op = { | static const struct seq_operations probes_seq_op = { | ||||||
| 	.start  = probes_seq_start, | 	.start  = dyn_event_seq_start, | ||||||
| 	.next   = probes_seq_next, | 	.next   = dyn_event_seq_next, | ||||||
| 	.stop   = probes_seq_stop, | 	.stop   = dyn_event_seq_stop, | ||||||
| 	.show   = probes_seq_show | 	.show   = probes_seq_show | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -771,7 +787,7 @@ static int probes_open(struct inode *inode, struct file *file) | ||||||
| 	int ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
| 	if ((file->f_mode & FMODE_WRITE) && (file->f_flags & O_TRUNC)) { | 	if ((file->f_mode & FMODE_WRITE) && (file->f_flags & O_TRUNC)) { | ||||||
| 		ret = release_all_trace_kprobes(); | 		ret = dyn_events_release_all(&trace_kprobe_ops); | ||||||
| 		if (ret < 0) | 		if (ret < 0) | ||||||
| 			return ret; | 			return ret; | ||||||
| 	} | 	} | ||||||
|  | @ -783,7 +799,7 @@ static ssize_t probes_write(struct file *file, const char __user *buffer, | ||||||
| 			    size_t count, loff_t *ppos) | 			    size_t count, loff_t *ppos) | ||||||
| { | { | ||||||
| 	return trace_parse_run_command(file, buffer, count, ppos, | 	return trace_parse_run_command(file, buffer, count, ppos, | ||||||
| 				       create_trace_kprobe); | 				       create_or_delete_trace_kprobe); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static const struct file_operations kprobe_events_ops = { | static const struct file_operations kprobe_events_ops = { | ||||||
|  | @ -798,8 +814,13 @@ static const struct file_operations kprobe_events_ops = { | ||||||
| /* Probes profiling interfaces */ | /* Probes profiling interfaces */ | ||||||
| static int probes_profile_seq_show(struct seq_file *m, void *v) | static int probes_profile_seq_show(struct seq_file *m, void *v) | ||||||
| { | { | ||||||
| 	struct trace_kprobe *tk = v; | 	struct dyn_event *ev = v; | ||||||
|  | 	struct trace_kprobe *tk; | ||||||
| 
 | 
 | ||||||
|  | 	if (!is_trace_kprobe(ev)) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	tk = to_trace_kprobe(ev); | ||||||
| 	seq_printf(m, "  %-44s %15lu %15lu\n", | 	seq_printf(m, "  %-44s %15lu %15lu\n", | ||||||
| 		   trace_event_name(&tk->tp.call), | 		   trace_event_name(&tk->tp.call), | ||||||
| 		   trace_kprobe_nhit(tk), | 		   trace_kprobe_nhit(tk), | ||||||
|  | @ -809,9 +830,9 @@ static int probes_profile_seq_show(struct seq_file *m, void *v) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static const struct seq_operations profile_seq_op = { | static const struct seq_operations profile_seq_op = { | ||||||
| 	.start  = probes_seq_start, | 	.start  = dyn_event_seq_start, | ||||||
| 	.next   = probes_seq_next, | 	.next   = dyn_event_seq_next, | ||||||
| 	.stop   = probes_seq_stop, | 	.stop   = dyn_event_seq_stop, | ||||||
| 	.show   = probes_profile_seq_show | 	.show   = probes_profile_seq_show | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -1332,7 +1353,7 @@ static int register_kprobe_event(struct trace_kprobe *tk) | ||||||
| 		kfree(call->print_fmt); | 		kfree(call->print_fmt); | ||||||
| 		return -ENODEV; | 		return -ENODEV; | ||||||
| 	} | 	} | ||||||
| 	ret = trace_add_event_call(call); | 	ret = trace_add_event_call_nolock(call); | ||||||
| 	if (ret) { | 	if (ret) { | ||||||
| 		pr_info("Failed to register kprobe event: %s\n", | 		pr_info("Failed to register kprobe event: %s\n", | ||||||
| 			trace_event_name(call)); | 			trace_event_name(call)); | ||||||
|  | @ -1347,7 +1368,7 @@ static int unregister_kprobe_event(struct trace_kprobe *tk) | ||||||
| 	int ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
| 	/* tp->event is unregistered in trace_remove_event_call() */ | 	/* tp->event is unregistered in trace_remove_event_call() */ | ||||||
| 	ret = trace_remove_event_call(&tk->tp.call); | 	ret = trace_remove_event_call_nolock(&tk->tp.call); | ||||||
| 	if (!ret) | 	if (!ret) | ||||||
| 		kfree(tk->tp.call.print_fmt); | 		kfree(tk->tp.call.print_fmt); | ||||||
| 	return ret; | 	return ret; | ||||||
|  | @ -1364,7 +1385,7 @@ create_local_trace_kprobe(char *func, void *addr, unsigned long offs, | ||||||
| 	char *event; | 	char *event; | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * local trace_kprobes are not added to probe_list, so they are never | 	 * local trace_kprobes are not added to dyn_event, so they are never | ||||||
| 	 * searched in find_trace_kprobe(). Therefore, there is no concern of | 	 * searched in find_trace_kprobe(). Therefore, there is no concern of | ||||||
| 	 * duplicated name here. | 	 * duplicated name here. | ||||||
| 	 */ | 	 */ | ||||||
|  | @ -1422,6 +1443,11 @@ static __init int init_kprobe_trace(void) | ||||||
| { | { | ||||||
| 	struct dentry *d_tracer; | 	struct dentry *d_tracer; | ||||||
| 	struct dentry *entry; | 	struct dentry *entry; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	ret = dyn_event_register(&trace_kprobe_ops); | ||||||
|  | 	if (ret) | ||||||
|  | 		return ret; | ||||||
| 
 | 
 | ||||||
| 	if (register_module_notifier(&trace_kprobe_module_nb)) | 	if (register_module_notifier(&trace_kprobe_module_nb)) | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
|  | @ -1479,9 +1505,8 @@ static __init int kprobe_trace_self_tests_init(void) | ||||||
| 
 | 
 | ||||||
| 	pr_info("Testing kprobe tracing: "); | 	pr_info("Testing kprobe tracing: "); | ||||||
| 
 | 
 | ||||||
| 	ret = trace_run_command("p:testprobe kprobe_trace_selftest_target " | 	ret = trace_run_command("p:testprobe kprobe_trace_selftest_target $stack $stack0 +0($stack)", | ||||||
| 				"$stack $stack0 +0($stack)", | 				create_or_delete_trace_kprobe); | ||||||
| 				create_trace_kprobe); |  | ||||||
| 	if (WARN_ON_ONCE(ret)) { | 	if (WARN_ON_ONCE(ret)) { | ||||||
| 		pr_warn("error on probing function entry.\n"); | 		pr_warn("error on probing function entry.\n"); | ||||||
| 		warn++; | 		warn++; | ||||||
|  | @ -1501,8 +1526,8 @@ static __init int kprobe_trace_self_tests_init(void) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ret = trace_run_command("r:testprobe2 kprobe_trace_selftest_target " | 	ret = trace_run_command("r:testprobe2 kprobe_trace_selftest_target $retval", | ||||||
| 				"$retval", create_trace_kprobe); | 				create_or_delete_trace_kprobe); | ||||||
| 	if (WARN_ON_ONCE(ret)) { | 	if (WARN_ON_ONCE(ret)) { | ||||||
| 		pr_warn("error on probing function return.\n"); | 		pr_warn("error on probing function return.\n"); | ||||||
| 		warn++; | 		warn++; | ||||||
|  | @ -1572,20 +1597,24 @@ static __init int kprobe_trace_self_tests_init(void) | ||||||
| 			disable_trace_kprobe(tk, file); | 			disable_trace_kprobe(tk, file); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ret = trace_run_command("-:testprobe", create_trace_kprobe); | 	ret = trace_run_command("-:testprobe", create_or_delete_trace_kprobe); | ||||||
| 	if (WARN_ON_ONCE(ret)) { | 	if (WARN_ON_ONCE(ret)) { | ||||||
| 		pr_warn("error on deleting a probe.\n"); | 		pr_warn("error on deleting a probe.\n"); | ||||||
| 		warn++; | 		warn++; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ret = trace_run_command("-:testprobe2", create_trace_kprobe); | 	ret = trace_run_command("-:testprobe2", create_or_delete_trace_kprobe); | ||||||
| 	if (WARN_ON_ONCE(ret)) { | 	if (WARN_ON_ONCE(ret)) { | ||||||
| 		pr_warn("error on deleting a probe.\n"); | 		pr_warn("error on deleting a probe.\n"); | ||||||
| 		warn++; | 		warn++; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| end: | end: | ||||||
| 	release_all_trace_kprobes(); | 	ret = dyn_events_release_all(&trace_kprobe_ops); | ||||||
|  | 	if (WARN_ON_ONCE(ret)) { | ||||||
|  | 		pr_warn("error on cleaning up probes.\n"); | ||||||
|  | 		warn++; | ||||||
|  | 	} | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * Wait for the optimizer work to finish. Otherwise it might fiddle | 	 * Wait for the optimizer work to finish. Otherwise it might fiddle | ||||||
| 	 * with probes in already freed __init text. | 	 * with probes in already freed __init text. | ||||||
|  |  | ||||||
|  | @ -154,6 +154,33 @@ int traceprobe_split_symbol_offset(char *symbol, long *offset) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* @buf must has MAX_EVENT_NAME_LEN size */ | ||||||
|  | int traceprobe_parse_event_name(const char **pevent, const char **pgroup, | ||||||
|  | 				char *buf) | ||||||
|  | { | ||||||
|  | 	const char *slash, *event = *pevent; | ||||||
|  | 
 | ||||||
|  | 	slash = strchr(event, '/'); | ||||||
|  | 	if (slash) { | ||||||
|  | 		if (slash == event) { | ||||||
|  | 			pr_info("Group name is not specified\n"); | ||||||
|  | 			return -EINVAL; | ||||||
|  | 		} | ||||||
|  | 		if (slash - event + 1 > MAX_EVENT_NAME_LEN) { | ||||||
|  | 			pr_info("Group name is too long\n"); | ||||||
|  | 			return -E2BIG; | ||||||
|  | 		} | ||||||
|  | 		strlcpy(buf, event, slash - event + 1); | ||||||
|  | 		*pgroup = buf; | ||||||
|  | 		*pevent = slash + 1; | ||||||
|  | 	} | ||||||
|  | 	if (strlen(event) == 0) { | ||||||
|  | 		pr_info("Event name is not specified\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) | #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) | ||||||
| 
 | 
 | ||||||
| static int parse_probe_vars(char *arg, const struct fetch_type *t, | static int parse_probe_vars(char *arg, const struct fetch_type *t, | ||||||
|  |  | ||||||
|  | @ -279,6 +279,8 @@ extern int traceprobe_update_arg(struct probe_arg *arg); | ||||||
| extern void traceprobe_free_probe_arg(struct probe_arg *arg); | extern void traceprobe_free_probe_arg(struct probe_arg *arg); | ||||||
| 
 | 
 | ||||||
| extern int traceprobe_split_symbol_offset(char *symbol, long *offset); | extern int traceprobe_split_symbol_offset(char *symbol, long *offset); | ||||||
|  | extern int traceprobe_parse_event_name(const char **pevent, | ||||||
|  | 				       const char **pgroup, char *buf); | ||||||
| 
 | 
 | ||||||
| extern int traceprobe_set_print_fmt(struct trace_probe *tp, bool is_return); | extern int traceprobe_set_print_fmt(struct trace_probe *tp, bool is_return); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Masami Hiramatsu
						Masami Hiramatsu