2433 lines · 57.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * trace_events_synth - synthetic trace events * * Copyright (C) 2015, 2020 Tom Zanussi <tom.zanussi@linux.intel.com> */ #include <linux/module.h>#include <linux/kallsyms.h>#include <linux/security.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/stacktrace.h>#include <linux/rculist.h>#include <linux/tracefs.h> /* for gfp flag names */#include <linux/trace_events.h>#include <trace/events/mmflags.h>#include "trace_probe.h"#include "trace_probe_kernel.h" #include "trace_synth.h" #undef ERRORS#define ERRORS	\	C(BAD_NAME,		"Illegal name"),		\	C(INVALID_CMD,		"Command must be of the form: <name> field[;field] ..."),\	C(INVALID_DYN_CMD,	"Command must be of the form: s or -:[synthetic/]<name> field[;field] ..."),\	C(EVENT_EXISTS,		"Event already exists"),	\	C(TOO_MANY_FIELDS,	"Too many fields"),		\	C(INCOMPLETE_TYPE,	"Incomplete type"),		\	C(INVALID_TYPE,		"Invalid type"),		\	C(INVALID_FIELD,        "Invalid field"),		\	C(INVALID_ARRAY_SPEC,	"Invalid array specification"), #undef C#define C(a, b)		SYNTH_ERR_##a enum { ERRORS }; #undef C#define C(a, b)		b static const char *err_text[] = { ERRORS }; static DEFINE_MUTEX(lastcmd_mutex);static char *last_cmd; static int errpos(const char *str){	guard(mutex)(&lastcmd_mutex);	if (!str || !last_cmd)		return 0; 	return err_pos(last_cmd, str);} static void last_cmd_set(const char *str){	if (!str)		return; 	mutex_lock(&lastcmd_mutex);	kfree(last_cmd);	last_cmd = kstrdup(str, GFP_KERNEL);	mutex_unlock(&lastcmd_mutex);} static void synth_err(u8 err_type, u16 err_pos){	guard(mutex)(&lastcmd_mutex);	if (!last_cmd)		return; 	tracing_log_err(NULL, "synthetic_events", last_cmd, err_text,			err_type, err_pos);} static int create_synth_event(const char *raw_command);static int synth_event_show(struct seq_file *m, struct dyn_event *ev);static int synth_event_release(struct dyn_event *ev);static bool synth_event_is_busy(struct dyn_event *ev);static bool synth_event_match(const char *system, const char *event,			int argc, const char **argv, struct dyn_event *ev); static struct dyn_event_operations synth_event_ops = {	.create = create_synth_event,	.show = synth_event_show,	.is_busy = synth_event_is_busy,	.free = synth_event_release,	.match = synth_event_match,}; static bool is_synth_event(struct dyn_event *ev){	return ev->ops == &synth_event_ops;} static struct synth_event *to_synth_event(struct dyn_event *ev){	return container_of(ev, struct synth_event, devent);} static bool synth_event_is_busy(struct dyn_event *ev){	struct synth_event *event = to_synth_event(ev); 	return event->ref != 0;} static bool synth_event_match(const char *system, const char *event,			int argc, const char **argv, struct dyn_event *ev){	struct synth_event *sev = to_synth_event(ev); 	return strcmp(sev->name, event) == 0 &&		(!system || strcmp(system, SYNTH_SYSTEM) == 0);} struct synth_trace_event {	struct trace_entry	ent;	union trace_synth_field	fields[];}; static int synth_event_define_fields(struct trace_event_call *call){	struct synth_trace_event trace;	int offset = offsetof(typeof(trace), fields);	struct synth_event *event = call->data;	unsigned int i, size, n_u64;	char *name, *type;	int filter_type;	bool is_signed;	bool is_stack;	int ret = 0; 	for (i = 0, n_u64 = 0; i < event->n_fields; i++) {		size = event->fields[i]->size;		is_signed = event->fields[i]->is_signed;		type = event->fields[i]->type;		name = event->fields[i]->name;		is_stack = event->fields[i]->is_stack; 		filter_type = is_stack ? FILTER_STACKTRACE : FILTER_OTHER; 		ret = trace_define_field(call, type, name, offset, size,					 is_signed, filter_type);		if (ret)			break; 		event->fields[i]->offset = n_u64; 		if (event->fields[i]->is_string && !event->fields[i]->is_dynamic) {			offset += STR_VAR_LEN_MAX;			n_u64 += STR_VAR_LEN_MAX / sizeof(u64);		} else {			offset += sizeof(u64);			n_u64++;		}	} 	event->n_u64 = n_u64; 	return ret;} static bool synth_field_signed(char *type){	if (str_has_prefix(type, "u"))		return false;	if (strcmp(type, "gfp_t") == 0)		return false; 	return true;} static int synth_field_is_string(char *type){	if (strstr(type, "char[") != NULL)		return true; 	return false;} static int synth_field_is_stack(char *type){	if (strstr(type, "long[") != NULL)		return true; 	return false;} static int synth_field_string_size(char *type){	char buf[4], *end, *start;	unsigned int len;	int size, err; 	start = strstr(type, "char[");	if (start == NULL)		return -EINVAL;	start += sizeof("char[") - 1; 	end = strchr(type, ']');	if (!end || end < start || type + strlen(type) > end + 1)		return -EINVAL; 	len = end - start;	if (len > 3)		return -EINVAL; 	if (len == 0)		return 0; /* variable-length string */ 	memcpy(buf, start, len);	buf[len] = '\0'; 	err = kstrtouint(buf, 0, &size);	if (err)		return err; 	if (size > STR_VAR_LEN_MAX)		return -EINVAL; 	return size;} static int synth_field_size(char *type){	int size = 0; 	if (strcmp(type, "s64") == 0)		size = sizeof(s64);	else if (strcmp(type, "u64") == 0)		size = sizeof(u64);	else if (strcmp(type, "s32") == 0)		size = sizeof(s32);	else if (strcmp(type, "u32") == 0)		size = sizeof(u32);	else if (strcmp(type, "s16") == 0)		size = sizeof(s16);	else if (strcmp(type, "u16") == 0)		size = sizeof(u16);	else if (strcmp(type, "s8") == 0)		size = sizeof(s8);	else if (strcmp(type, "u8") == 0)		size = sizeof(u8);	else if (strcmp(type, "char") == 0)		size = sizeof(char);	else if (strcmp(type, "unsigned char") == 0)		size = sizeof(unsigned char);	else if (strcmp(type, "int") == 0)		size = sizeof(int);	else if (strcmp(type, "unsigned int") == 0)		size = sizeof(unsigned int);	else if (strcmp(type, "long") == 0)		size = sizeof(long);	else if (strcmp(type, "unsigned long") == 0)		size = sizeof(unsigned long);	else if (strcmp(type, "bool") == 0)		size = sizeof(bool);	else if (strcmp(type, "pid_t") == 0)		size = sizeof(pid_t);	else if (strcmp(type, "gfp_t") == 0)		size = sizeof(gfp_t);	else if (synth_field_is_string(type))		size = synth_field_string_size(type);	else if (synth_field_is_stack(type))		size = 0; 	return size;} static const char *synth_field_fmt(char *type){	const char *fmt = "%llu"; 	if (strcmp(type, "s64") == 0)		fmt = "%lld";	else if (strcmp(type, "u64") == 0)		fmt = "%llu";	else if (strcmp(type, "s32") == 0)		fmt = "%d";	else if (strcmp(type, "u32") == 0)		fmt = "%u";	else if (strcmp(type, "s16") == 0)		fmt = "%d";	else if (strcmp(type, "u16") == 0)		fmt = "%u";	else if (strcmp(type, "s8") == 0)		fmt = "%d";	else if (strcmp(type, "u8") == 0)		fmt = "%u";	else if (strcmp(type, "char") == 0)		fmt = "%d";	else if (strcmp(type, "unsigned char") == 0)		fmt = "%u";	else if (strcmp(type, "int") == 0)		fmt = "%d";	else if (strcmp(type, "unsigned int") == 0)		fmt = "%u";	else if (strcmp(type, "long") == 0)		fmt = "%ld";	else if (strcmp(type, "unsigned long") == 0)		fmt = "%lu";	else if (strcmp(type, "bool") == 0)		fmt = "%d";	else if (strcmp(type, "pid_t") == 0)		fmt = "%d";	else if (strcmp(type, "gfp_t") == 0)		fmt = "%x";	else if (synth_field_is_string(type))		fmt = "%s";	else if (synth_field_is_stack(type))		fmt = "%s"; 	return fmt;} static void print_synth_event_num_val(struct trace_seq *s,				      char *print_fmt, char *name,				      int size, union trace_synth_field *val, char *space){	switch (size) {	case 1:		trace_seq_printf(s, print_fmt, name, val->as_u8, space);		break; 	case 2:		trace_seq_printf(s, print_fmt, name, val->as_u16, space);		break; 	case 4:		trace_seq_printf(s, print_fmt, name, val->as_u32, space);		break; 	default:		trace_seq_printf(s, print_fmt, name, val->as_u64, space);		break;	}} static enum print_line_t print_synth_event(struct trace_iterator *iter,					   int flags,					   struct trace_event *event){	struct trace_array *tr = iter->tr;	struct trace_seq *s = &iter->seq;	struct synth_trace_event *entry;	struct synth_event *se;	unsigned int i, j, n_u64;	char print_fmt[32];	const char *fmt; 	entry = (struct synth_trace_event *)iter->ent;	se = container_of(event, struct synth_event, call.event); 	trace_seq_printf(s, "%s: ", se->name); 	for (i = 0, n_u64 = 0; i < se->n_fields; i++) {		if (trace_seq_has_overflowed(s))			goto end; 		fmt = synth_field_fmt(se->fields[i]->type); 		/* parameter types */		if (tr && tr->trace_flags & TRACE_ITER(VERBOSE))			trace_seq_printf(s, "%s ", fmt); 		snprintf(print_fmt, sizeof(print_fmt), "%%s=%s%%s", fmt); 		/* parameter values */		if (se->fields[i]->is_string) {			if (se->fields[i]->is_dynamic) {				union trace_synth_field *data = &entry->fields[n_u64]; 				trace_seq_printf(s, print_fmt, se->fields[i]->name,						 (char *)entry + data->as_dynamic.offset,						 i == se->n_fields - 1 ? "" : " ");				n_u64++;			} else {				trace_seq_printf(s, print_fmt, se->fields[i]->name,						 (char *)&entry->fields[n_u64].as_u64,						 i == se->n_fields - 1 ? "" : " ");				n_u64 += STR_VAR_LEN_MAX / sizeof(u64);			}		} else if (se->fields[i]->is_stack) {			union trace_synth_field *data = &entry->fields[n_u64];			unsigned long *p = (void *)entry + data->as_dynamic.offset; 			trace_seq_printf(s, "%s=STACK:\n", se->fields[i]->name);			for (j = 1; j < data->as_dynamic.len / sizeof(long); j++)				trace_seq_printf(s, "=> %pS\n", (void *)p[j]);			n_u64++;		} else {			struct trace_print_flags __flags[] = {			    __def_gfpflag_names };			char *space = (i == se->n_fields - 1 ? "" : " "); 			print_synth_event_num_val(s, print_fmt,						  se->fields[i]->name,						  se->fields[i]->size,						  &entry->fields[n_u64],						  space); 			if (strcmp(se->fields[i]->type, "gfp_t") == 0) {				trace_seq_puts(s, " (");				trace_print_flags_seq(s, "|",						      entry->fields[n_u64].as_u64,						      __flags, ARRAY_SIZE(__flags));				trace_seq_putc(s, ')');			}			n_u64++;		}	}end:	trace_seq_putc(s, '\n'); 	return trace_handle_return(s);} static struct trace_event_functions synth_event_funcs = {	.trace		= print_synth_event}; static unsigned int trace_string(struct synth_trace_event *entry,				 struct synth_event *event,				 char *str_val,				 bool is_dynamic,				 unsigned int data_size,				 unsigned int *n_u64){	unsigned int len = 0;	char *str_field;	int ret; 	if (is_dynamic) {		union trace_synth_field *data = &entry->fields[*n_u64]; 		len = fetch_store_strlen((unsigned long)str_val);		data->as_dynamic.offset = struct_size(entry, fields, event->n_u64) + data_size;		data->as_dynamic.len = len; 		ret = fetch_store_string((unsigned long)str_val, &entry->fields[*n_u64], entry); 		(*n_u64)++;	} else {		str_field = (char *)&entry->fields[*n_u64].as_u64; #ifdef CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE		if ((unsigned long)str_val < TASK_SIZE)			ret = strncpy_from_user_nofault(str_field, (const void __user *)str_val, STR_VAR_LEN_MAX);		else#endif			ret = strncpy_from_kernel_nofault(str_field, str_val, STR_VAR_LEN_MAX); 		if (ret < 0)			strcpy(str_field, FAULT_STRING); 		(*n_u64) += STR_VAR_LEN_MAX / sizeof(u64);	} 	return len;} static unsigned int trace_stack(struct synth_trace_event *entry,				 struct synth_event *event,				 long *stack,				 unsigned int data_size,				 unsigned int *n_u64){	union trace_synth_field *data = &entry->fields[*n_u64];	unsigned int len;	u32 data_offset;	void *data_loc; 	data_offset = struct_size(entry, fields, event->n_u64);	data_offset += data_size; 	for (len = 0; len < HIST_STACKTRACE_DEPTH; len++) {		if (!stack[len])			break;	} 	len *= sizeof(long); 	/* Find the dynamic section to copy the stack into. */	data_loc = (void *)entry + data_offset;	memcpy(data_loc, stack, len); 	/* Fill in the field that holds the offset/len combo */ 	data->as_dynamic.offset = data_offset;	data->as_dynamic.len = len; 	(*n_u64)++; 	return len;}

Lines 1–500 of 2433.