diff options
Diffstat (limited to 'kernel/trace/trace_eprobe.c')
| -rw-r--r-- | kernel/trace/trace_eprobe.c | 167 | 
1 files changed, 102 insertions, 65 deletions
diff --git a/kernel/trace/trace_eprobe.c b/kernel/trace/trace_eprobe.c index 1783e3478912..5dd0617e5df6 100644 --- a/kernel/trace/trace_eprobe.c +++ b/kernel/trace/trace_eprobe.c @@ -16,6 +16,7 @@  #include "trace_dynevent.h"  #include "trace_probe.h"  #include "trace_probe_tmpl.h" +#include "trace_probe_kernel.h"  #define EPROBE_EVENT_SYSTEM "eprobes" @@ -26,6 +27,9 @@ struct trace_eprobe {  	/* tracepoint event */  	const char *event_name; +	/* filter string for the tracepoint */ +	char *filter_str; +  	struct trace_event_call *event;  	struct dyn_event	devent; @@ -453,29 +457,14 @@ NOKPROBE_SYMBOL(process_fetch_insn)  static nokprobe_inline int  fetch_store_strlen_user(unsigned long addr)  { -	const void __user *uaddr =  (__force const void __user *)addr; - -	return strnlen_user_nofault(uaddr, MAX_STRING_SIZE); +	return kern_fetch_store_strlen_user(addr);  }  /* Return the length of string -- including null terminal byte */  static nokprobe_inline int  fetch_store_strlen(unsigned long addr)  { -	int ret, len = 0; -	u8 c; - -#ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE -	if (addr < TASK_SIZE) -		return fetch_store_strlen_user(addr); -#endif - -	do { -		ret = copy_from_kernel_nofault(&c, (u8 *)addr + len, 1); -		len++; -	} while (c && ret == 0 && len < MAX_STRING_SIZE); - -	return (ret < 0) ? ret : len; +	return kern_fetch_store_strlen(addr);  }  /* @@ -485,21 +474,7 @@ fetch_store_strlen(unsigned long addr)  static nokprobe_inline int  fetch_store_string_user(unsigned long addr, void *dest, void *base)  { -	const void __user *uaddr =  (__force const void __user *)addr; -	int maxlen = get_loc_len(*(u32 *)dest); -	void *__dest; -	long ret; - -	if (unlikely(!maxlen)) -		return -ENOMEM; - -	__dest = get_loc_data(dest, base); - -	ret = strncpy_from_user_nofault(__dest, uaddr, maxlen); -	if (ret >= 0) -		*(u32 *)dest = make_data_loc(ret, __dest - base); - -	return ret; +	return kern_fetch_store_string_user(addr, dest, base);  }  /* @@ -509,29 +484,7 @@ fetch_store_string_user(unsigned long addr, void *dest, void *base)  static nokprobe_inline int  fetch_store_string(unsigned long addr, void *dest, void *base)  { -	int maxlen = get_loc_len(*(u32 *)dest); -	void *__dest; -	long ret; - -#ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE -	if ((unsigned long)addr < TASK_SIZE) -		return fetch_store_string_user(addr, dest, base); -#endif - -	if (unlikely(!maxlen)) -		return -ENOMEM; - -	__dest = get_loc_data(dest, base); - -	/* -	 * Try to get string again, since the string can be changed while -	 * probing. -	 */ -	ret = strncpy_from_kernel_nofault(__dest, (void *)addr, maxlen); -	if (ret >= 0) -		*(u32 *)dest = make_data_loc(ret, __dest - base); - -	return ret; +	return kern_fetch_store_string(addr, dest, base);  }  static nokprobe_inline int @@ -664,14 +617,15 @@ static struct event_trigger_data *  new_eprobe_trigger(struct trace_eprobe *ep, struct trace_event_file *file)  {  	struct event_trigger_data *trigger; +	struct event_filter *filter = NULL;  	struct eprobe_data *edata; +	int ret;  	edata = kzalloc(sizeof(*edata), GFP_KERNEL);  	trigger = kzalloc(sizeof(*trigger), GFP_KERNEL);  	if (!trigger || !edata) { -		kfree(edata); -		kfree(trigger); -		return ERR_PTR(-ENOMEM); +		ret = -ENOMEM; +		goto error;  	}  	trigger->flags = EVENT_TRIGGER_FL_PROBE; @@ -686,13 +640,25 @@ new_eprobe_trigger(struct trace_eprobe *ep, struct trace_event_file *file)  	trigger->cmd_ops = &event_trigger_cmd;  	INIT_LIST_HEAD(&trigger->list); -	RCU_INIT_POINTER(trigger->filter, NULL); + +	if (ep->filter_str) { +		ret = create_event_filter(file->tr, file->event_call, +					ep->filter_str, false, &filter); +		if (ret) +			goto error; +	} +	RCU_INIT_POINTER(trigger->filter, filter);  	edata->file = file;  	edata->ep = ep;  	trigger->private_data = edata;  	return trigger; +error: +	free_event_filter(filter); +	kfree(edata); +	kfree(trigger); +	return ERR_PTR(ret);  }  static int enable_eprobe(struct trace_eprobe *ep, @@ -726,6 +692,7 @@ static int disable_eprobe(struct trace_eprobe *ep,  {  	struct event_trigger_data *trigger = NULL, *iter;  	struct trace_event_file *file; +	struct event_filter *filter;  	struct eprobe_data *edata;  	file = find_event_file(tr, ep->event_system, ep->event_name); @@ -752,6 +719,10 @@ static int disable_eprobe(struct trace_eprobe *ep,  	/* Make sure nothing is using the edata or trigger */  	tracepoint_synchronize_unregister(); +	filter = rcu_access_pointer(trigger->filter); + +	if (filter) +		free_event_filter(filter);  	kfree(edata);  	kfree(trigger); @@ -927,12 +898,62 @@ static int trace_eprobe_tp_update_arg(struct trace_eprobe *ep, const char *argv[  	return ret;  } +static int trace_eprobe_parse_filter(struct trace_eprobe *ep, int argc, const char *argv[]) +{ +	struct event_filter *dummy; +	int i, ret, len = 0; +	char *p; + +	if (argc == 0) { +		trace_probe_log_err(0, NO_EP_FILTER); +		return -EINVAL; +	} + +	/* Recover the filter string */ +	for (i = 0; i < argc; i++) +		len += strlen(argv[i]) + 1; + +	ep->filter_str = kzalloc(len, GFP_KERNEL); +	if (!ep->filter_str) +		return -ENOMEM; + +	p = ep->filter_str; +	for (i = 0; i < argc; i++) { +		ret = snprintf(p, len, "%s ", argv[i]); +		if (ret < 0) +			goto error; +		if (ret > len) { +			ret = -E2BIG; +			goto error; +		} +		p += ret; +		len -= ret; +	} +	p[-1] = '\0'; + +	/* +	 * Ensure the filter string can be parsed correctly. Note, this +	 * filter string is for the original event, not for the eprobe. +	 */ +	ret = create_event_filter(top_trace_array(), ep->event, ep->filter_str, +				  true, &dummy); +	free_event_filter(dummy); +	if (ret) +		goto error; + +	return 0; +error: +	kfree(ep->filter_str); +	ep->filter_str = NULL; +	return ret; +} +  static int __trace_eprobe_create(int argc, const char *argv[])  {  	/*  	 * Argument syntax: -	 *      e[:[GRP/][ENAME]] SYSTEM.EVENT [FETCHARGS] -	 * Fetch args: +	 *      e[:[GRP/][ENAME]] SYSTEM.EVENT [FETCHARGS] [if FILTER] +	 * Fetch args (no space):  	 *  <name>=$<field>[:TYPE]  	 */  	const char *event = NULL, *group = EPROBE_EVENT_SYSTEM; @@ -942,8 +963,8 @@ static int __trace_eprobe_create(int argc, const char *argv[])  	char buf1[MAX_EVENT_NAME_LEN];  	char buf2[MAX_EVENT_NAME_LEN];  	char gbuf[MAX_EVENT_NAME_LEN]; -	int ret = 0; -	int i; +	int ret = 0, filter_idx = 0; +	int i, filter_cnt;  	if (argc < 2 || argv[0][0] != 'e')  		return -ECANCELED; @@ -968,11 +989,19 @@ static int __trace_eprobe_create(int argc, const char *argv[])  	}  	if (!event) { -		strscpy(buf1, argv[1], MAX_EVENT_NAME_LEN); -		sanitize_event_name(buf1); +		strscpy(buf1, sys_event, MAX_EVENT_NAME_LEN);  		event = buf1;  	} +	for (i = 2; i < argc; i++) { +		if (!strcmp(argv[i], "if")) { +			filter_idx = i + 1; +			filter_cnt = argc - filter_idx; +			argc = i; +			break; +		} +	} +  	mutex_lock(&event_mutex);  	event_call = find_and_get_event(sys_name, sys_event);  	ep = alloc_event_probe(group, event, event_call, argc - 2); @@ -988,6 +1017,14 @@ static int __trace_eprobe_create(int argc, const char *argv[])  		goto error;  	} +	if (filter_idx) { +		trace_probe_log_set_index(filter_idx); +		ret = trace_eprobe_parse_filter(ep, filter_cnt, argv + filter_idx); +		if (ret) +			goto parse_error; +	} else +		ep->filter_str = NULL; +  	argc -= 2; argv += 2;  	/* parse arguments */  	for (i = 0; i < argc && i < MAX_TRACE_ARGS; i++) {  |