1130 lines · 26.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Common code for probe-based Dynamic events. * * This code was copied from kernel/trace/trace_kprobe.c written by * Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> * * Updates to make this generic: * Copyright (C) IBM Corporation, 2010-2011 * Author:     Srikar Dronamraju */#define pr_fmt(fmt)	"trace_probe: " fmt #include "trace_probe.h" #undef C#define C(a, b)		b static const char *trace_probe_err_text[] = { ERRORS }; static const char *reserved_field_names[] = {	"common_type",	"common_flags",	"common_preempt_count",	"common_pid",	"common_tgid",	FIELD_STRING_IP,	FIELD_STRING_RETIP,	FIELD_STRING_FUNC,}; /* Printing  in basic type function template */#define DEFINE_BASIC_PRINT_TYPE_FUNC(tname, type, fmt)			\int PRINT_TYPE_FUNC_NAME(tname)(struct trace_seq *s, void *data, void *ent)\{									\	trace_seq_printf(s, fmt, *(type *)data);			\	return !trace_seq_has_overflowed(s);				\}									\const char PRINT_TYPE_FMT_NAME(tname)[] = fmt; DEFINE_BASIC_PRINT_TYPE_FUNC(u8,  u8,  "%u")DEFINE_BASIC_PRINT_TYPE_FUNC(u16, u16, "%u")DEFINE_BASIC_PRINT_TYPE_FUNC(u32, u32, "%u")DEFINE_BASIC_PRINT_TYPE_FUNC(u64, u64, "%Lu")DEFINE_BASIC_PRINT_TYPE_FUNC(s8,  s8,  "%d")DEFINE_BASIC_PRINT_TYPE_FUNC(s16, s16, "%d")DEFINE_BASIC_PRINT_TYPE_FUNC(s32, s32, "%d")DEFINE_BASIC_PRINT_TYPE_FUNC(s64, s64, "%Ld")DEFINE_BASIC_PRINT_TYPE_FUNC(x8,  u8,  "0x%x")DEFINE_BASIC_PRINT_TYPE_FUNC(x16, u16, "0x%x")DEFINE_BASIC_PRINT_TYPE_FUNC(x32, u32, "0x%x")DEFINE_BASIC_PRINT_TYPE_FUNC(x64, u64, "0x%Lx") int PRINT_TYPE_FUNC_NAME(symbol)(struct trace_seq *s, void *data, void *ent){	trace_seq_printf(s, "%pS", (void *)*(unsigned long *)data);	return !trace_seq_has_overflowed(s);}const char PRINT_TYPE_FMT_NAME(symbol)[] = "%pS"; /* Print type function for string type */int PRINT_TYPE_FUNC_NAME(string)(struct trace_seq *s, void *data, void *ent){	int len = *(u32 *)data >> 16; 	if (!len)		trace_seq_puts(s, "(fault)");	else		trace_seq_printf(s, "\"%s\"",				 (const char *)get_loc_data(data, ent));	return !trace_seq_has_overflowed(s);} const char PRINT_TYPE_FMT_NAME(string)[] = "\\\"%s\\\""; /* Fetch type information table */static const struct fetch_type probe_fetch_types[] = {	/* Special types */	__ASSIGN_FETCH_TYPE("string", string, string, sizeof(u32), 1,			    "__data_loc char[]"),	__ASSIGN_FETCH_TYPE("ustring", string, string, sizeof(u32), 1,			    "__data_loc char[]"),	/* Basic types */	ASSIGN_FETCH_TYPE(u8,  u8,  0),	ASSIGN_FETCH_TYPE(u16, u16, 0),	ASSIGN_FETCH_TYPE(u32, u32, 0),	ASSIGN_FETCH_TYPE(u64, u64, 0),	ASSIGN_FETCH_TYPE(s8,  u8,  1),	ASSIGN_FETCH_TYPE(s16, u16, 1),	ASSIGN_FETCH_TYPE(s32, u32, 1),	ASSIGN_FETCH_TYPE(s64, u64, 1),	ASSIGN_FETCH_TYPE_ALIAS(x8,  u8,  u8,  0),	ASSIGN_FETCH_TYPE_ALIAS(x16, u16, u16, 0),	ASSIGN_FETCH_TYPE_ALIAS(x32, u32, u32, 0),	ASSIGN_FETCH_TYPE_ALIAS(x64, u64, u64, 0),	ASSIGN_FETCH_TYPE_ALIAS(symbol, ADDR_FETCH_TYPE, ADDR_FETCH_TYPE, 0), 	ASSIGN_FETCH_TYPE_END}; static const struct fetch_type *find_fetch_type(const char *type){	int i; 	if (!type)		type = DEFAULT_FETCH_TYPE_STR; 	/* Special case: bitfield */	if (*type == 'b') {		unsigned long bs; 		type = strchr(type, '/');		if (!type)			goto fail; 		type++;		if (kstrtoul(type, 0, &bs))			goto fail; 		switch (bs) {		case 8:			return find_fetch_type("u8");		case 16:			return find_fetch_type("u16");		case 32:			return find_fetch_type("u32");		case 64:			return find_fetch_type("u64");		default:			goto fail;		}	} 	for (i = 0; probe_fetch_types[i].name; i++) {		if (strcmp(type, probe_fetch_types[i].name) == 0)			return &probe_fetch_types[i];	} fail:	return NULL;} static struct trace_probe_log trace_probe_log; void trace_probe_log_init(const char *subsystem, int argc, const char **argv){	trace_probe_log.subsystem = subsystem;	trace_probe_log.argc = argc;	trace_probe_log.argv = argv;	trace_probe_log.index = 0;} void trace_probe_log_clear(void){	memset(&trace_probe_log, 0, sizeof(trace_probe_log));} void trace_probe_log_set_index(int index){	trace_probe_log.index = index;} void __trace_probe_log_err(int offset, int err_type){	char *command, *p;	int i, len = 0, pos = 0; 	if (!trace_probe_log.argv)		return; 	/* Recalcurate the length and allocate buffer */	for (i = 0; i < trace_probe_log.argc; i++) {		if (i == trace_probe_log.index)			pos = len;		len += strlen(trace_probe_log.argv[i]) + 1;	}	command = kzalloc(len, GFP_KERNEL);	if (!command)		return; 	if (trace_probe_log.index >= trace_probe_log.argc) {		/**		 * Set the error position is next to the last arg + space.		 * Note that len includes the terminal null and the cursor		 * appaers at pos + 1.		 */		pos = len;		offset = 0;	} 	/* And make a command string from argv array */	p = command;	for (i = 0; i < trace_probe_log.argc; i++) {		len = strlen(trace_probe_log.argv[i]);		strcpy(p, trace_probe_log.argv[i]);		p[len] = ' ';		p += len + 1;	}	*(p - 1) = '\0'; 	tracing_log_err(NULL, trace_probe_log.subsystem, command,			trace_probe_err_text, err_type, pos + offset); 	kfree(command);} /* Split symbol and offset. */int traceprobe_split_symbol_offset(char *symbol, long *offset){	char *tmp;	int ret; 	if (!offset)		return -EINVAL; 	tmp = strpbrk(symbol, "+-");	if (tmp) {		ret = kstrtol(tmp, 0, offset);		if (ret)			return ret;		*tmp = '\0';	} else		*offset = 0; 	return 0;} /* @buf must has MAX_EVENT_NAME_LEN size */int traceprobe_parse_event_name(const char **pevent, const char **pgroup,				char *buf, int offset){	const char *slash, *event = *pevent;	int len; 	slash = strchr(event, '/');	if (slash) {		if (slash == event) {			trace_probe_log_err(offset, NO_GROUP_NAME);			return -EINVAL;		}		if (slash - event + 1 > MAX_EVENT_NAME_LEN) {			trace_probe_log_err(offset, GROUP_TOO_LONG);			return -EINVAL;		}		strlcpy(buf, event, slash - event + 1);		if (!is_good_name(buf)) {			trace_probe_log_err(offset, BAD_GROUP_NAME);			return -EINVAL;		}		*pgroup = buf;		*pevent = slash + 1;		offset += slash - event + 1;		event = *pevent;	}	len = strlen(event);	if (len == 0) {		trace_probe_log_err(offset, NO_EVENT_NAME);		return -EINVAL;	} else if (len > MAX_EVENT_NAME_LEN) {		trace_probe_log_err(offset, EVENT_TOO_LONG);		return -EINVAL;	}	if (!is_good_name(event)) {		trace_probe_log_err(offset, BAD_EVENT_NAME);		return -EINVAL;	}	return 0;} #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) static int parse_probe_vars(char *arg, const struct fetch_type *t,			struct fetch_insn *code, unsigned int flags, int offs){	unsigned long param;	int ret = 0;	int len; 	if (strcmp(arg, "retval") == 0) {		if (flags & TPARG_FL_RETURN) {			code->op = FETCH_OP_RETVAL;		} else {			trace_probe_log_err(offs, RETVAL_ON_PROBE);			ret = -EINVAL;		}	} else if ((len = str_has_prefix(arg, "stack"))) {		if (arg[len] == '\0') {			code->op = FETCH_OP_STACKP;		} else if (isdigit(arg[len])) {			ret = kstrtoul(arg + len, 10, &param);			if (ret) {				goto inval_var;			} else if ((flags & TPARG_FL_KERNEL) &&				    param > PARAM_MAX_STACK) {				trace_probe_log_err(offs, BAD_STACK_NUM);				ret = -EINVAL;			} else {				code->op = FETCH_OP_STACK;				code->param = (unsigned int)param;			}		} else			goto inval_var;	} else if (strcmp(arg, "comm") == 0) {		code->op = FETCH_OP_COMM;#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API	} else if (((flags & TPARG_FL_MASK) ==		    (TPARG_FL_KERNEL | TPARG_FL_FENTRY)) &&		   (len = str_has_prefix(arg, "arg"))) {		ret = kstrtoul(arg + len, 10, &param);		if (ret) {			goto inval_var;		} else if (!param || param > PARAM_MAX_STACK) {			trace_probe_log_err(offs, BAD_ARG_NUM);			return -EINVAL;		}		code->op = FETCH_OP_ARG;		code->param = (unsigned int)param - 1;#endif	} else		goto inval_var; 	return ret; inval_var:	trace_probe_log_err(offs, BAD_VAR);	return -EINVAL;} static int str_to_immediate(char *str, unsigned long *imm){	if (isdigit(str[0]))		return kstrtoul(str, 0, imm);	else if (str[0] == '-')		return kstrtol(str, 0, (long *)imm);	else if (str[0] == '+')		return kstrtol(str + 1, 0, (long *)imm);	return -EINVAL;} static int __parse_imm_string(char *str, char **pbuf, int offs){	size_t len = strlen(str); 	if (str[len - 1] != '"') {		trace_probe_log_err(offs + len, IMMSTR_NO_CLOSE);		return -EINVAL;	}	*pbuf = kstrndup(str, len - 1, GFP_KERNEL);	return 0;} /* Recursive argument parser */static intparse_probe_arg(char *arg, const struct fetch_type *type,		struct fetch_insn **pcode, struct fetch_insn *end,		unsigned int flags, int offs){	struct fetch_insn *code = *pcode;	unsigned long param;	int deref = FETCH_OP_DEREF;	long offset = 0;	char *tmp;	int ret = 0; 	switch (arg[0]) {	case '$':		ret = parse_probe_vars(arg + 1, type, code, flags, offs);		break; 	case '%':	/* named register */		ret = regs_query_register_offset(arg + 1);		if (ret >= 0) {			code->op = FETCH_OP_REG;			code->param = (unsigned int)ret;			ret = 0;		} else			trace_probe_log_err(offs, BAD_REG_NAME);		break; 	case '@':	/* memory, file-offset or symbol */		if (isdigit(arg[1])) {			ret = kstrtoul(arg + 1, 0, &param);			if (ret) {				trace_probe_log_err(offs, BAD_MEM_ADDR);				break;			}			/* load address */			code->op = FETCH_OP_IMM;			code->immediate = param;		} else if (arg[1] == '+') {			/* kprobes don't support file offsets */			if (flags & TPARG_FL_KERNEL) {				trace_probe_log_err(offs, FILE_ON_KPROBE);				return -EINVAL;			}			ret = kstrtol(arg + 2, 0, &offset);			if (ret) {				trace_probe_log_err(offs, BAD_FILE_OFFS);				break;			} 			code->op = FETCH_OP_FOFFS;			code->immediate = (unsigned long)offset;  // imm64?		} else {			/* uprobes don't support symbols */			if (!(flags & TPARG_FL_KERNEL)) {				trace_probe_log_err(offs, SYM_ON_UPROBE);				return -EINVAL;			}			/* Preserve symbol for updating */			code->op = FETCH_NOP_SYMBOL;			code->data = kstrdup(arg + 1, GFP_KERNEL);			if (!code->data)				return -ENOMEM;			if (++code == end) {				trace_probe_log_err(offs, TOO_MANY_OPS);				return -EINVAL;			}			code->op = FETCH_OP_IMM;			code->immediate = 0;		}		/* These are fetching from memory */		if (++code == end) {			trace_probe_log_err(offs, TOO_MANY_OPS);			return -EINVAL;		}		*pcode = code;		code->op = FETCH_OP_DEREF;		code->offset = offset;		break; 	case '+':	/* deref memory */	case '-':		if (arg[1] == 'u') {			deref = FETCH_OP_UDEREF;			arg[1] = arg[0];			arg++;		}		if (arg[0] == '+')			arg++;	/* Skip '+', because kstrtol() rejects it. */		tmp = strchr(arg, '(');		if (!tmp) {			trace_probe_log_err(offs, DEREF_NEED_BRACE);			return -EINVAL;		}		*tmp = '\0';		ret = kstrtol(arg, 0, &offset);		if (ret) {			trace_probe_log_err(offs, BAD_DEREF_OFFS);			break;		}		offs += (tmp + 1 - arg) + (arg[0] != '-' ? 1 : 0);		arg = tmp + 1;		tmp = strrchr(arg, ')');		if (!tmp) {			trace_probe_log_err(offs + strlen(arg),					    DEREF_OPEN_BRACE);			return -EINVAL;		} else {			const struct fetch_type *t2 = find_fetch_type(NULL); 			*tmp = '\0';			ret = parse_probe_arg(arg, t2, &code, end, flags, offs);			if (ret)				break;			if (code->op == FETCH_OP_COMM ||			    code->op == FETCH_OP_DATA) {				trace_probe_log_err(offs, COMM_CANT_DEREF);				return -EINVAL;			}			if (++code == end) {				trace_probe_log_err(offs, TOO_MANY_OPS);				return -EINVAL;			}			*pcode = code; 			code->op = deref;			code->offset = offset;		}		break;	case '\\':	/* Immediate value */		if (arg[1] == '"') {	/* Immediate string */			ret = __parse_imm_string(arg + 2, &tmp, offs + 2);			if (ret)				break;			code->op = FETCH_OP_DATA;			code->data = tmp;		} else {			ret = str_to_immediate(arg + 1, &code->immediate);			if (ret)				trace_probe_log_err(offs + 1, BAD_IMM);			else				code->op = FETCH_OP_IMM;		}		break;	}	if (!ret && code->op == FETCH_OP_NOP) {		/* Parsed, but do not find fetch method */		trace_probe_log_err(offs, BAD_FETCH_ARG);		ret = -EINVAL;

Lines 1–500 of 1130.