1628 lines · 34.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (c) 2021, Microsoft Corporation. * * Authors: *   Beau Belgrave <beaub@linux.microsoft.com> */ #include <linux/bitmap.h>#include <linux/cdev.h>#include <linux/hashtable.h>#include <linux/list.h>#include <linux/io.h>#include <linux/uio.h>#include <linux/ioctl.h>#include <linux/jhash.h>#include <linux/trace_events.h>#include <linux/tracefs.h>#include <linux/types.h>#include <linux/uaccess.h>/* Reminder to move to uapi when everything works */#ifdef CONFIG_COMPILE_TEST#include <linux/user_events.h>#else#include <uapi/linux/user_events.h>#endif#include "trace.h"#include "trace_dynevent.h" #define USER_EVENTS_PREFIX_LEN (sizeof(USER_EVENTS_PREFIX)-1) #define FIELD_DEPTH_TYPE 0#define FIELD_DEPTH_NAME 1#define FIELD_DEPTH_SIZE 2 /* * Limits how many trace_event calls user processes can create: * Must be a power of two of PAGE_SIZE. */#define MAX_PAGE_ORDER 0#define MAX_PAGES (1 << MAX_PAGE_ORDER)#define MAX_EVENTS (MAX_PAGES * PAGE_SIZE) /* Limit how long of an event name plus args within the subsystem. */#define MAX_EVENT_DESC 512#define EVENT_NAME(user_event) ((user_event)->tracepoint.name)#define MAX_FIELD_ARRAY_SIZE 1024#define MAX_FIELD_ARG_NAME 256 static char *register_page_data; static DEFINE_MUTEX(reg_mutex);static DEFINE_HASHTABLE(register_table, 4);static DECLARE_BITMAP(page_bitmap, MAX_EVENTS); /* * Stores per-event properties, as users register events * within a file a user_event might be created if it does not * already exist. These are globally used and their lifetime * is tied to the refcnt member. These cannot go away until the * refcnt reaches zero. */struct user_event {	struct tracepoint tracepoint;	struct trace_event_call call;	struct trace_event_class class;	struct dyn_event devent;	struct hlist_node node;	struct list_head fields;	struct list_head validators;	atomic_t refcnt;	int index;	int flags;	int min_size;}; /* * Stores per-file events references, as users register events * within a file this structure is modified and freed via RCU. * The lifetime of this struct is tied to the lifetime of the file. * These are not shared and only accessible by the file that created it. */struct user_event_refs {	struct rcu_head rcu;	int count;	struct user_event *events[];}; #define VALIDATOR_ENSURE_NULL (1 << 0)#define VALIDATOR_REL (1 << 1) struct user_event_validator {	struct list_head link;	int offset;	int flags;}; typedef void (*user_event_func_t) (struct user_event *user, struct iov_iter *i,				   void *tpdata, bool *faulted); static int user_event_parse(char *name, char *args, char *flags,			    struct user_event **newuser); static u32 user_event_key(char *name){	return jhash(name, strlen(name), 0);} static __always_inline __must_checksize_t copy_nofault(void *addr, size_t bytes, struct iov_iter *i){	size_t ret; 	pagefault_disable(); 	ret = copy_from_iter_nocache(addr, bytes, i); 	pagefault_enable(); 	return ret;} static struct list_head *user_event_get_fields(struct trace_event_call *call){	struct user_event *user = (struct user_event *)call->data; 	return &user->fields;} /* * Parses a register command for user_events * Format: event_name[:FLAG1[,FLAG2...]] [field1[;field2...]] * * Example event named 'test' with a 20 char 'msg' field with an unsigned int * 'id' field after: * test char[20] msg;unsigned int id * * NOTE: Offsets are from the user data perspective, they are not from the * trace_entry/buffer perspective. We automatically add the common properties * sizes to the offset for the user. * * Upon success user_event has its ref count increased by 1. */static int user_event_parse_cmd(char *raw_command, struct user_event **newuser){	char *name = raw_command;	char *args = strpbrk(name, " ");	char *flags; 	if (args)		*args++ = '\0'; 	flags = strpbrk(name, ":"); 	if (flags)		*flags++ = '\0'; 	return user_event_parse(name, args, flags, newuser);} static int user_field_array_size(const char *type){	const char *start = strchr(type, '[');	char val[8];	char *bracket;	int size = 0; 	if (start == NULL)		return -EINVAL; 	if (strscpy(val, start + 1, sizeof(val)) <= 0)		return -EINVAL; 	bracket = strchr(val, ']'); 	if (!bracket)		return -EINVAL; 	*bracket = '\0'; 	if (kstrtouint(val, 0, &size))		return -EINVAL; 	if (size > MAX_FIELD_ARRAY_SIZE)		return -EINVAL; 	return size;} static int user_field_size(const char *type){	/* long is not allowed from a user, since it's ambigious in size */	if (strcmp(type, "s64") == 0)		return sizeof(s64);	if (strcmp(type, "u64") == 0)		return sizeof(u64);	if (strcmp(type, "s32") == 0)		return sizeof(s32);	if (strcmp(type, "u32") == 0)		return sizeof(u32);	if (strcmp(type, "int") == 0)		return sizeof(int);	if (strcmp(type, "unsigned int") == 0)		return sizeof(unsigned int);	if (strcmp(type, "s16") == 0)		return sizeof(s16);	if (strcmp(type, "u16") == 0)		return sizeof(u16);	if (strcmp(type, "short") == 0)		return sizeof(short);	if (strcmp(type, "unsigned short") == 0)		return sizeof(unsigned short);	if (strcmp(type, "s8") == 0)		return sizeof(s8);	if (strcmp(type, "u8") == 0)		return sizeof(u8);	if (strcmp(type, "char") == 0)		return sizeof(char);	if (strcmp(type, "unsigned char") == 0)		return sizeof(unsigned char);	if (str_has_prefix(type, "char["))		return user_field_array_size(type);	if (str_has_prefix(type, "unsigned char["))		return user_field_array_size(type);	if (str_has_prefix(type, "__data_loc "))		return sizeof(u32);	if (str_has_prefix(type, "__rel_loc "))		return sizeof(u32); 	/* Uknown basic type, error */	return -EINVAL;} static void user_event_destroy_validators(struct user_event *user){	struct user_event_validator *validator, *next;	struct list_head *head = &user->validators; 	list_for_each_entry_safe(validator, next, head, link) {		list_del(&validator->link);		kfree(validator);	}} static void user_event_destroy_fields(struct user_event *user){	struct ftrace_event_field *field, *next;	struct list_head *head = &user->fields; 	list_for_each_entry_safe(field, next, head, link) {		list_del(&field->link);		kfree(field);	}} static int user_event_add_field(struct user_event *user, const char *type,				const char *name, int offset, int size,				int is_signed, int filter_type){	struct user_event_validator *validator;	struct ftrace_event_field *field;	int validator_flags = 0; 	field = kmalloc(sizeof(*field), GFP_KERNEL); 	if (!field)		return -ENOMEM; 	if (str_has_prefix(type, "__data_loc "))		goto add_validator; 	if (str_has_prefix(type, "__rel_loc ")) {		validator_flags |= VALIDATOR_REL;		goto add_validator;	} 	goto add_field; add_validator:	if (strstr(type, "char") != 0)		validator_flags |= VALIDATOR_ENSURE_NULL; 	validator = kmalloc(sizeof(*validator), GFP_KERNEL); 	if (!validator) {		kfree(field);		return -ENOMEM;	} 	validator->flags = validator_flags;	validator->offset = offset; 	/* Want sequential access when validating */	list_add_tail(&validator->link, &user->validators); add_field:	field->type = type;	field->name = name;	field->offset = offset;	field->size = size;	field->is_signed = is_signed;	field->filter_type = filter_type; 	list_add(&field->link, &user->fields); 	/*	 * Min size from user writes that are required, this does not include	 * the size of trace_entry (common fields).	 */	user->min_size = (offset + size) - sizeof(struct trace_entry); 	return 0;} /* * Parses the values of a field within the description * Format: type name [size] */static int user_event_parse_field(char *field, struct user_event *user,				  u32 *offset){	char *part, *type, *name;	u32 depth = 0, saved_offset = *offset;	int len, size = -EINVAL;	bool is_struct = false; 	field = skip_spaces(field); 	if (*field == '\0')		return 0; 	/* Handle types that have a space within */	len = str_has_prefix(field, "unsigned ");	if (len)		goto skip_next; 	len = str_has_prefix(field, "struct ");	if (len) {		is_struct = true;		goto skip_next;	} 	len = str_has_prefix(field, "__data_loc unsigned ");	if (len)		goto skip_next; 	len = str_has_prefix(field, "__data_loc ");	if (len)		goto skip_next; 	len = str_has_prefix(field, "__rel_loc unsigned ");	if (len)		goto skip_next; 	len = str_has_prefix(field, "__rel_loc ");	if (len)		goto skip_next; 	goto parse;skip_next:	type = field;	field = strpbrk(field + len, " "); 	if (field == NULL)		return -EINVAL; 	*field++ = '\0';	depth++;parse:	name = NULL; 	while ((part = strsep(&field, " ")) != NULL) {		switch (depth++) {		case FIELD_DEPTH_TYPE:			type = part;			break;		case FIELD_DEPTH_NAME:			name = part;			break;		case FIELD_DEPTH_SIZE:			if (!is_struct)				return -EINVAL; 			if (kstrtou32(part, 10, &size))				return -EINVAL;			break;		default:			return -EINVAL;		}	} 	if (depth < FIELD_DEPTH_SIZE || !name)		return -EINVAL; 	if (depth == FIELD_DEPTH_SIZE)		size = user_field_size(type); 	if (size == 0)		return -EINVAL; 	if (size < 0)		return size; 	*offset = saved_offset + size; 	return user_event_add_field(user, type, name, saved_offset, size,				    type[0] != 'u', FILTER_OTHER);} static int user_event_parse_fields(struct user_event *user, char *args){	char *field;	u32 offset = sizeof(struct trace_entry);	int ret = -EINVAL; 	if (args == NULL)		return 0; 	while ((field = strsep(&args, ";")) != NULL) {		ret = user_event_parse_field(field, user, &offset); 		if (ret)			break;	} 	return ret;} static struct trace_event_fields user_event_fields_array[1]; static const char *user_field_format(const char *type){	if (strcmp(type, "s64") == 0)		return "%lld";	if (strcmp(type, "u64") == 0)		return "%llu";	if (strcmp(type, "s32") == 0)		return "%d";	if (strcmp(type, "u32") == 0)		return "%u";	if (strcmp(type, "int") == 0)		return "%d";	if (strcmp(type, "unsigned int") == 0)		return "%u";	if (strcmp(type, "s16") == 0)		return "%d";	if (strcmp(type, "u16") == 0)		return "%u";	if (strcmp(type, "short") == 0)		return "%d";	if (strcmp(type, "unsigned short") == 0)		return "%u";	if (strcmp(type, "s8") == 0)		return "%d";	if (strcmp(type, "u8") == 0)		return "%u";	if (strcmp(type, "char") == 0)		return "%d";	if (strcmp(type, "unsigned char") == 0)		return "%u";	if (strstr(type, "char[") != 0)		return "%s"; 	/* Unknown, likely struct, allowed treat as 64-bit */	return "%llu";} static bool user_field_is_dyn_string(const char *type, const char **str_func){	if (str_has_prefix(type, "__data_loc ")) {		*str_func = "__get_str";		goto check;	} 	if (str_has_prefix(type, "__rel_loc ")) {		*str_func = "__get_rel_str";		goto check;	} 	return false;check:	return strstr(type, "char") != 0;} #define LEN_OR_ZERO (len ? len - pos : 0)static int user_event_set_print_fmt(struct user_event *user, char *buf, int len){	struct ftrace_event_field *field, *next;	struct list_head *head = &user->fields;	int pos = 0, depth = 0;	const char *str_func; 	pos += snprintf(buf + pos, LEN_OR_ZERO, "\""); 	list_for_each_entry_safe_reverse(field, next, head, link) {		if (depth != 0)			pos += snprintf(buf + pos, LEN_OR_ZERO, " "); 		pos += snprintf(buf + pos, LEN_OR_ZERO, "%s=%s",				field->name, user_field_format(field->type));

Lines 1–500 of 1628.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t