diff options
Diffstat (limited to 'kernel/trace/trace_events.c')
| -rw-r--r-- | kernel/trace/trace_events.c | 198 | 
1 files changed, 150 insertions, 48 deletions
| diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 069e92856bda..9f3e9537417d 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -400,6 +400,20 @@ static bool process_string(const char *fmt, int len, struct trace_event_call *ca  	return true;  } +static void handle_dereference_arg(const char *arg_str, u64 string_flags, int len, +				   u64 *dereference_flags, int arg, +				   struct trace_event_call *call) +{ +	if (string_flags & (1ULL << arg)) { +		if (process_string(arg_str, len, call)) +			*dereference_flags &= ~(1ULL << arg); +	} else if (process_pointer(arg_str, len, call)) +		*dereference_flags &= ~(1ULL << arg); +	else +		pr_warn("TRACE EVENT ERROR: Bad dereference argument: '%.*s'\n", +			len, arg_str); +} +  /*   * Examine the print fmt of the event looking for unsafe dereference   * pointers using %p* that could be recorded in the trace event and @@ -563,11 +577,9 @@ static void test_event_printk(struct trace_event_call *call)  			}  			if (dereference_flags & (1ULL << arg)) { -				if (string_flags & (1ULL << arg)) { -					if (process_string(fmt + start_arg, e - start_arg, call)) -						dereference_flags &= ~(1ULL << arg); -				} else if (process_pointer(fmt + start_arg, e - start_arg, call)) -					dereference_flags &= ~(1ULL << arg); +				handle_dereference_arg(fmt + start_arg, string_flags, +						       e - start_arg, +						       &dereference_flags, arg, call);  			}  			start_arg = i; @@ -578,11 +590,9 @@ static void test_event_printk(struct trace_event_call *call)  	}  	if (dereference_flags & (1ULL << arg)) { -		if (string_flags & (1ULL << arg)) { -			if (process_string(fmt + start_arg, i - start_arg, call)) -				dereference_flags &= ~(1ULL << arg); -		} else if (process_pointer(fmt + start_arg, i - start_arg, call)) -			dereference_flags &= ~(1ULL << arg); +		handle_dereference_arg(fmt + start_arg, string_flags, +				       i - start_arg, +				       &dereference_flags, arg, call);  	}  	/* @@ -622,7 +632,6 @@ EXPORT_SYMBOL_GPL(trace_event_raw_init);  bool trace_event_ignore_this_pid(struct trace_event_file *trace_file)  {  	struct trace_array *tr = trace_file->tr; -	struct trace_array_cpu *data;  	struct trace_pid_list *no_pid_list;  	struct trace_pid_list *pid_list; @@ -632,9 +641,11 @@ bool trace_event_ignore_this_pid(struct trace_event_file *trace_file)  	if (!pid_list && !no_pid_list)  		return false; -	data = this_cpu_ptr(tr->array_buffer.data); - -	return data->ignore_pid; +	/* +	 * This is recorded at every sched_switch for this task. +	 * Thus, even if the task migrates the ignore value will be the same. +	 */ +	return this_cpu_read(tr->array_buffer.data->ignore_pid) != 0;  }  EXPORT_SYMBOL_GPL(trace_event_ignore_this_pid); @@ -757,6 +768,7 @@ static int __ftrace_event_enable_disable(struct trace_event_file *file,  {  	struct trace_event_call *call = file->event_call;  	struct trace_array *tr = file->tr; +	bool soft_mode = atomic_read(&file->sm_ref) != 0;  	int ret = 0;  	int disable; @@ -771,7 +783,7 @@ static int __ftrace_event_enable_disable(struct trace_event_file *file,  		 * is set we do not want the event to be enabled before we  		 * clear the bit.  		 * -		 * When soft_disable is not set but the SOFT_MODE flag is, +		 * When soft_disable is not set but the soft_mode is,  		 * we do nothing. Do not disable the tracepoint, otherwise  		 * "soft enable"s (clearing the SOFT_DISABLED bit) wont work.  		 */ @@ -779,11 +791,11 @@ static int __ftrace_event_enable_disable(struct trace_event_file *file,  			if (atomic_dec_return(&file->sm_ref) > 0)  				break;  			disable = file->flags & EVENT_FILE_FL_SOFT_DISABLED; -			clear_bit(EVENT_FILE_FL_SOFT_MODE_BIT, &file->flags); +			soft_mode = false;  			/* Disable use of trace_buffered_event */  			trace_buffered_event_disable();  		} else -			disable = !(file->flags & EVENT_FILE_FL_SOFT_MODE); +			disable = !soft_mode;  		if (disable && (file->flags & EVENT_FILE_FL_ENABLED)) {  			clear_bit(EVENT_FILE_FL_ENABLED_BIT, &file->flags); @@ -801,8 +813,8 @@ static int __ftrace_event_enable_disable(struct trace_event_file *file,  			WARN_ON_ONCE(ret);  		} -		/* If in SOFT_MODE, just set the SOFT_DISABLE_BIT, else clear it */ -		if (file->flags & EVENT_FILE_FL_SOFT_MODE) +		/* If in soft mode, just set the SOFT_DISABLE_BIT, else clear it */ +		if (soft_mode)  			set_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &file->flags);  		else  			clear_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &file->flags); @@ -812,7 +824,7 @@ static int __ftrace_event_enable_disable(struct trace_event_file *file,  		 * When soft_disable is set and enable is set, we want to  		 * register the tracepoint for the event, but leave the event  		 * as is. That means, if the event was already enabled, we do -		 * nothing (but set SOFT_MODE). If the event is disabled, we +		 * nothing (but set soft_mode). If the event is disabled, we  		 * set SOFT_DISABLED before enabling the event tracepoint, so  		 * it still seems to be disabled.  		 */ @@ -821,7 +833,7 @@ static int __ftrace_event_enable_disable(struct trace_event_file *file,  		else {  			if (atomic_inc_return(&file->sm_ref) > 1)  				break; -			set_bit(EVENT_FILE_FL_SOFT_MODE_BIT, &file->flags); +			soft_mode = true;  			/* Enable use of trace_buffered_event */  			trace_buffered_event_enable();  		} @@ -829,7 +841,7 @@ static int __ftrace_event_enable_disable(struct trace_event_file *file,  		if (!(file->flags & EVENT_FILE_FL_ENABLED)) {  			bool cmd = false, tgid = false; -			/* Keep the event disabled, when going to SOFT_MODE. */ +			/* Keep the event disabled, when going to soft mode. */  			if (soft_disable)  				set_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &file->flags); @@ -1781,8 +1793,7 @@ event_enable_read(struct file *filp, char __user *ubuf, size_t cnt,  	    !(flags & EVENT_FILE_FL_SOFT_DISABLED))  		strcpy(buf, "1"); -	if (flags & EVENT_FILE_FL_SOFT_DISABLED || -	    flags & EVENT_FILE_FL_SOFT_MODE) +	if (atomic_read(&file->sm_ref) != 0)  		strcat(buf, "*");  	strcat(buf, "\n"); @@ -3125,7 +3136,10 @@ __register_event(struct trace_event_call *call, struct module *mod)  	if (ret < 0)  		return ret; +	down_write(&trace_event_sem);  	list_add(&call->list, &ftrace_events); +	up_write(&trace_event_sem); +  	if (call->flags & TRACE_EVENT_FL_DYNAMIC)  		atomic_set(&call->refcnt, 0);  	else @@ -3253,43 +3267,120 @@ static void add_str_to_module(struct module *module, char *str)  	list_add(&modstr->next, &module_strings);  } +#define ATTRIBUTE_STR "__attribute__(" +#define ATTRIBUTE_STR_LEN (sizeof(ATTRIBUTE_STR) - 1) + +/* Remove all __attribute__() from @type. Return allocated string or @type. */ +static char *sanitize_field_type(const char *type) +{ +	char *attr, *tmp, *next, *ret = (char *)type; +	int depth; + +	next = (char *)type; +	while ((attr = strstr(next, ATTRIBUTE_STR))) { +		/* Retry if "__attribute__(" is a part of another word. */ +		if (attr != next && !isspace(attr[-1])) { +			next = attr + ATTRIBUTE_STR_LEN; +			continue; +		} + +		if (ret == type) { +			ret = kstrdup(type, GFP_KERNEL); +			if (WARN_ON_ONCE(!ret)) +				return NULL; +			attr = ret + (attr - type); +		} + +		/* the ATTRIBUTE_STR already has the first '(' */ +		depth = 1; +		next = attr + ATTRIBUTE_STR_LEN; +		do { +			tmp = strpbrk(next, "()"); +			/* There is unbalanced parentheses */ +			if (WARN_ON_ONCE(!tmp)) { +				kfree(ret); +				return (char *)type; +			} + +			if (*tmp == '(') +				depth++; +			else +				depth--; +			next = tmp + 1; +		} while (depth > 0); +		next = skip_spaces(next); +		strcpy(attr, next); +		next = attr; +	} +	return ret; +} + +static char *find_replacable_eval(const char *type, const char *eval_string, +				  int len) +{ +	char *ptr; + +	if (!eval_string) +		return NULL; + +	ptr = strchr(type, '['); +	if (!ptr) +		return NULL; +	ptr++; + +	if (!isalpha(*ptr) && *ptr != '_') +		return NULL; + +	if (strncmp(eval_string, ptr, len) != 0) +		return NULL; + +	return ptr; +} +  static void update_event_fields(struct trace_event_call *call,  				struct trace_eval_map *map)  {  	struct ftrace_event_field *field; +	const char *eval_string = NULL;  	struct list_head *head; +	int len = 0;  	char *ptr;  	char *str; -	int len = strlen(map->eval_string);  	/* Dynamic events should never have field maps */ -	if (WARN_ON_ONCE(call->flags & TRACE_EVENT_FL_DYNAMIC)) +	if (call->flags & TRACE_EVENT_FL_DYNAMIC)  		return; +	if (map) { +		eval_string = map->eval_string; +		len = strlen(map->eval_string); +	} +  	head = trace_get_fields(call);  	list_for_each_entry(field, head, link) { -		ptr = strchr(field->type, '['); -		if (!ptr) -			continue; -		ptr++; - -		if (!isalpha(*ptr) && *ptr != '_') -			continue; +		str = sanitize_field_type(field->type); +		if (!str) +			return; -		if (strncmp(map->eval_string, ptr, len) != 0) -			continue; +		ptr = find_replacable_eval(str, eval_string, len); +		if (ptr) { +			if (str == field->type) { +				str = kstrdup(field->type, GFP_KERNEL); +				if (WARN_ON_ONCE(!str)) +					return; +				ptr = str + (ptr - field->type); +			} -		str = kstrdup(field->type, GFP_KERNEL); -		if (WARN_ON_ONCE(!str)) -			return; -		ptr = str + (ptr - field->type); -		ptr = eval_replace(ptr, map, len); -		/* enum/sizeof string smaller than value */ -		if (WARN_ON_ONCE(!ptr)) { -			kfree(str); -			continue; +			ptr = eval_replace(ptr, map, len); +			/* enum/sizeof string smaller than value */ +			if (WARN_ON_ONCE(!ptr)) { +				kfree(str); +				continue; +			}  		} +		if (str == field->type) +			continue;  		/*  		 * If the event is part of a module, then we need to free the string  		 * when the module is removed. Otherwise, it will stay allocated @@ -3299,14 +3390,18 @@ static void update_event_fields(struct trace_event_call *call,  			add_str_to_module(call->module, str);  		field->type = str; +		if (field->filter_type == FILTER_OTHER) +			field->filter_type = filter_assign_type(field->type);  	}  } -void trace_event_eval_update(struct trace_eval_map **map, int len) +/* Update all events for replacing eval and sanitizing */ +void trace_event_update_all(struct trace_eval_map **map, int len)  {  	struct trace_event_call *call, *p;  	const char *last_system = NULL;  	bool first = false; +	bool updated;  	int last_i;  	int i; @@ -3319,6 +3414,7 @@ void trace_event_eval_update(struct trace_eval_map **map, int len)  			last_system = call->class->system;  		} +		updated = false;  		/*  		 * Since calls are grouped by systems, the likelihood that the  		 * next call in the iteration belongs to the same system as the @@ -3338,8 +3434,12 @@ void trace_event_eval_update(struct trace_eval_map **map, int len)  				}  				update_event_printk(call, map[i]);  				update_event_fields(call, map[i]); +				updated = true;  			}  		} +		/* If not updated yet, update field for sanitizing. */ +		if (!updated) +			update_event_fields(call, NULL);  		cond_resched();  	}  	up_write(&trace_event_sem); @@ -3573,7 +3673,7 @@ static int probe_remove_event_call(struct trace_event_call *call)  			continue;  		/*  		 * We can't rely on ftrace_event_enable_disable(enable => 0) -		 * we are going to do, EVENT_FILE_FL_SOFT_MODE can suppress +		 * we are going to do, soft mode can suppress  		 * TRACE_REG_UNREGISTER.  		 */  		if (file->flags & EVENT_FILE_FL_ENABLED) @@ -3684,7 +3784,7 @@ static void trace_module_remove_events(struct module *mod)  		if (call->module == mod)  			__trace_remove_event_call(call);  	} -	/* Check for any strings allocade for this module */ +	/* Check for any strings allocated for this module */  	list_for_each_entry_safe(modstr, m, &module_strings, next) {  		if (modstr->module != mod)  			continue; @@ -3739,6 +3839,8 @@ __trace_add_event_dirs(struct trace_array *tr)  	struct trace_event_call *call;  	int ret; +	lockdep_assert_held(&trace_event_sem); +  	list_for_each_entry(call, &ftrace_events, list) {  		ret = __trace_add_new_event(call, tr);  		if (ret < 0) @@ -3986,7 +4088,7 @@ static int free_probe_data(void *data)  	edata->ref--;  	if (!edata->ref) { -		/* Remove the SOFT_MODE flag */ +		/* Remove soft mode */  		__ftrace_event_enable_disable(edata->file, 0, 1);  		trace_event_put_ref(edata->file->event_call);  		kfree(edata); | 
