mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 08:38:45 +02:00 
			
		
		
		
	tracing: probeevent: Introduce new argument fetching code
Replace {k,u}probe event argument fetching framework with switch-case based.
Currently that is implemented with structures, macros and chain of
function-pointers, which is more complicated than necessary and may get a
performance penalty by retpoline.
This simplify that with an array of "fetch_insn" (opcode and oprands), and
make process_fetch_insn() just interprets it. No function pointers are used.
Link: http://lkml.kernel.org/r/152465868340.26224.2551120475197839464.stgit@devbox
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
			
			
This commit is contained in:
		
							parent
							
								
									7bfbc63eda
								
							
						
					
					
						commit
						533059281e
					
				
					 5 changed files with 489 additions and 676 deletions
				
			
		|  | @ -14,6 +14,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "trace_kprobe_selftest.h" | #include "trace_kprobe_selftest.h" | ||||||
| #include "trace_probe.h" | #include "trace_probe.h" | ||||||
|  | #include "trace_probe_tmpl.h" | ||||||
| 
 | 
 | ||||||
| #define KPROBE_EVENT_SYSTEM "kprobes" | #define KPROBE_EVENT_SYSTEM "kprobes" | ||||||
| #define KRETPROBE_MAXACTIVE_MAX 4096 | #define KRETPROBE_MAXACTIVE_MAX 4096 | ||||||
|  | @ -120,160 +121,6 @@ static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs); | ||||||
| static int kretprobe_dispatcher(struct kretprobe_instance *ri, | static int kretprobe_dispatcher(struct kretprobe_instance *ri, | ||||||
| 				struct pt_regs *regs); | 				struct pt_regs *regs); | ||||||
| 
 | 
 | ||||||
| /* Memory fetching by symbol */ |  | ||||||
| struct symbol_cache { |  | ||||||
| 	char		*symbol; |  | ||||||
| 	long		offset; |  | ||||||
| 	unsigned long	addr; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| unsigned long update_symbol_cache(struct symbol_cache *sc) |  | ||||||
| { |  | ||||||
| 	sc->addr = (unsigned long)kallsyms_lookup_name(sc->symbol); |  | ||||||
| 
 |  | ||||||
| 	if (sc->addr) |  | ||||||
| 		sc->addr += sc->offset; |  | ||||||
| 
 |  | ||||||
| 	return sc->addr; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void free_symbol_cache(struct symbol_cache *sc) |  | ||||||
| { |  | ||||||
| 	kfree(sc->symbol); |  | ||||||
| 	kfree(sc); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| struct symbol_cache *alloc_symbol_cache(const char *sym, long offset) |  | ||||||
| { |  | ||||||
| 	struct symbol_cache *sc; |  | ||||||
| 
 |  | ||||||
| 	if (!sym || strlen(sym) == 0) |  | ||||||
| 		return NULL; |  | ||||||
| 
 |  | ||||||
| 	sc = kzalloc(sizeof(struct symbol_cache), GFP_KERNEL); |  | ||||||
| 	if (!sc) |  | ||||||
| 		return NULL; |  | ||||||
| 
 |  | ||||||
| 	sc->symbol = kstrdup(sym, GFP_KERNEL); |  | ||||||
| 	if (!sc->symbol) { |  | ||||||
| 		kfree(sc); |  | ||||||
| 		return NULL; |  | ||||||
| 	} |  | ||||||
| 	sc->offset = offset; |  | ||||||
| 	update_symbol_cache(sc); |  | ||||||
| 
 |  | ||||||
| 	return sc; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /*
 |  | ||||||
|  * Kprobes-specific fetch functions |  | ||||||
|  */ |  | ||||||
| #define DEFINE_FETCH_stack(type)					\ |  | ||||||
| static void FETCH_FUNC_NAME(stack, type)(struct pt_regs *regs,		\ |  | ||||||
| 					  void *offset, void *dest)	\ |  | ||||||
| {									\ |  | ||||||
| 	*(type *)dest = (type)regs_get_kernel_stack_nth(regs,		\ |  | ||||||
| 				(unsigned int)((unsigned long)offset));	\ |  | ||||||
| }									\ |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(stack, type)); |  | ||||||
| 
 |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(stack) |  | ||||||
| /* No string on the stack entry */ |  | ||||||
| #define fetch_stack_string	NULL |  | ||||||
| #define fetch_stack_string_size	NULL |  | ||||||
| 
 |  | ||||||
| #define DEFINE_FETCH_memory(type)					\ |  | ||||||
| static void FETCH_FUNC_NAME(memory, type)(struct pt_regs *regs,		\ |  | ||||||
| 					  void *addr, void *dest)	\ |  | ||||||
| {									\ |  | ||||||
| 	type retval;							\ |  | ||||||
| 	if (probe_kernel_address(addr, retval))				\ |  | ||||||
| 		*(type *)dest = 0;					\ |  | ||||||
| 	else								\ |  | ||||||
| 		*(type *)dest = retval;					\ |  | ||||||
| }									\ |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(memory, type)); |  | ||||||
| 
 |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(memory) |  | ||||||
| /*
 |  | ||||||
|  * Fetch a null-terminated string. Caller MUST set *(u32 *)dest with max |  | ||||||
|  * length and relative data location. |  | ||||||
|  */ |  | ||||||
| static void FETCH_FUNC_NAME(memory, string)(struct pt_regs *regs, |  | ||||||
| 					    void *addr, void *dest) |  | ||||||
| { |  | ||||||
| 	int maxlen = get_rloc_len(*(u32 *)dest); |  | ||||||
| 	u8 *dst = get_rloc_data(dest); |  | ||||||
| 	long ret; |  | ||||||
| 
 |  | ||||||
| 	if (!maxlen) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	/*
 |  | ||||||
| 	 * Try to get string again, since the string can be changed while |  | ||||||
| 	 * probing. |  | ||||||
| 	 */ |  | ||||||
| 	ret = strncpy_from_unsafe(dst, addr, maxlen); |  | ||||||
| 
 |  | ||||||
| 	if (ret < 0) {	/* Failed to fetch string */ |  | ||||||
| 		dst[0] = '\0'; |  | ||||||
| 		*(u32 *)dest = make_data_rloc(0, get_rloc_offs(*(u32 *)dest)); |  | ||||||
| 	} else { |  | ||||||
| 		*(u32 *)dest = make_data_rloc(ret, get_rloc_offs(*(u32 *)dest)); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(memory, string)); |  | ||||||
| 
 |  | ||||||
| /* Return the length of string -- including null terminal byte */ |  | ||||||
| static void FETCH_FUNC_NAME(memory, string_size)(struct pt_regs *regs, |  | ||||||
| 						 void *addr, void *dest) |  | ||||||
| { |  | ||||||
| 	mm_segment_t old_fs; |  | ||||||
| 	int ret, len = 0; |  | ||||||
| 	u8 c; |  | ||||||
| 
 |  | ||||||
| 	old_fs = get_fs(); |  | ||||||
| 	set_fs(KERNEL_DS); |  | ||||||
| 	pagefault_disable(); |  | ||||||
| 
 |  | ||||||
| 	do { |  | ||||||
| 		ret = __copy_from_user_inatomic(&c, (u8 *)addr + len, 1); |  | ||||||
| 		len++; |  | ||||||
| 	} while (c && ret == 0 && len < MAX_STRING_SIZE); |  | ||||||
| 
 |  | ||||||
| 	pagefault_enable(); |  | ||||||
| 	set_fs(old_fs); |  | ||||||
| 
 |  | ||||||
| 	if (ret < 0)	/* Failed to check the length */ |  | ||||||
| 		*(u32 *)dest = 0; |  | ||||||
| 	else |  | ||||||
| 		*(u32 *)dest = len; |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(memory, string_size)); |  | ||||||
| 
 |  | ||||||
| #define DEFINE_FETCH_symbol(type)					\ |  | ||||||
| void FETCH_FUNC_NAME(symbol, type)(struct pt_regs *regs, void *data, void *dest)\ |  | ||||||
| {									\ |  | ||||||
| 	struct symbol_cache *sc = data;					\ |  | ||||||
| 	if (sc->addr)							\ |  | ||||||
| 		fetch_memory_##type(regs, (void *)sc->addr, dest);	\ |  | ||||||
| 	else								\ |  | ||||||
| 		*(type *)dest = 0;					\ |  | ||||||
| }									\ |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(symbol, type)); |  | ||||||
| 
 |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(symbol) |  | ||||||
| DEFINE_FETCH_symbol(string) |  | ||||||
| DEFINE_FETCH_symbol(string_size) |  | ||||||
| 
 |  | ||||||
| /* kprobes don't support file_offset fetch methods */ |  | ||||||
| #define fetch_file_offset_u8		NULL |  | ||||||
| #define fetch_file_offset_u16		NULL |  | ||||||
| #define fetch_file_offset_u32		NULL |  | ||||||
| #define fetch_file_offset_u64		NULL |  | ||||||
| #define fetch_file_offset_string	NULL |  | ||||||
| #define fetch_file_offset_string_size	NULL |  | ||||||
| 
 |  | ||||||
| /* Fetch type information table */ | /* Fetch type information table */ | ||||||
| static const struct fetch_type kprobes_fetch_type_table[] = { | static const struct fetch_type kprobes_fetch_type_table[] = { | ||||||
| 	/* Special types */ | 	/* Special types */ | ||||||
|  | @ -529,7 +376,7 @@ static bool within_notrace_func(struct trace_kprobe *tk) | ||||||
| /* Internal register function - just handle k*probes and flags */ | /* Internal register function - just handle k*probes and flags */ | ||||||
| static int __register_trace_kprobe(struct trace_kprobe *tk) | static int __register_trace_kprobe(struct trace_kprobe *tk) | ||||||
| { | { | ||||||
| 	int i, ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
| 	if (trace_probe_is_registered(&tk->tp)) | 	if (trace_probe_is_registered(&tk->tp)) | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
|  | @ -540,9 +387,6 @@ static int __register_trace_kprobe(struct trace_kprobe *tk) | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for (i = 0; i < tk->tp.nr_args; i++) |  | ||||||
| 		traceprobe_update_arg(&tk->tp.args[i]); |  | ||||||
| 
 |  | ||||||
| 	/* Set/clear disabled flag according to tp->flag */ | 	/* Set/clear disabled flag according to tp->flag */ | ||||||
| 	if (trace_probe_is_enabled(&tk->tp)) | 	if (trace_probe_is_enabled(&tk->tp)) | ||||||
| 		tk->rp.kp.flags &= ~KPROBE_FLAG_DISABLED; | 		tk->rp.kp.flags &= ~KPROBE_FLAG_DISABLED; | ||||||
|  | @ -876,8 +720,8 @@ static int create_trace_kprobe(int argc, char **argv) | ||||||
| 
 | 
 | ||||||
| 		/* Parse fetch argument */ | 		/* Parse fetch argument */ | ||||||
| 		ret = traceprobe_parse_probe_arg(arg, &tk->tp.size, parg, | 		ret = traceprobe_parse_probe_arg(arg, &tk->tp.size, parg, | ||||||
| 						is_return, true, | 						 is_return, true, | ||||||
| 						kprobes_fetch_type_table); | 						 kprobes_fetch_type_table); | ||||||
| 		if (ret) { | 		if (ret) { | ||||||
| 			pr_info("Parse error at argument[%d]. (%d)\n", i, ret); | 			pr_info("Parse error at argument[%d]. (%d)\n", i, ret); | ||||||
| 			goto error; | 			goto error; | ||||||
|  | @ -1031,6 +875,133 @@ static const struct file_operations kprobe_profile_ops = { | ||||||
| 	.release        = seq_release, | 	.release        = seq_release, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* Kprobe specific fetch functions */ | ||||||
|  | 
 | ||||||
|  | /* Return the length of string -- including null terminal byte */ | ||||||
|  | static nokprobe_inline void | ||||||
|  | fetch_store_strlen(unsigned long addr, void *dest) | ||||||
|  | { | ||||||
|  | 	mm_segment_t old_fs; | ||||||
|  | 	int ret, len = 0; | ||||||
|  | 	u8 c; | ||||||
|  | 
 | ||||||
|  | 	old_fs = get_fs(); | ||||||
|  | 	set_fs(KERNEL_DS); | ||||||
|  | 	pagefault_disable(); | ||||||
|  | 
 | ||||||
|  | 	do { | ||||||
|  | 		ret = __copy_from_user_inatomic(&c, (u8 *)addr + len, 1); | ||||||
|  | 		len++; | ||||||
|  | 	} while (c && ret == 0 && len < MAX_STRING_SIZE); | ||||||
|  | 
 | ||||||
|  | 	pagefault_enable(); | ||||||
|  | 	set_fs(old_fs); | ||||||
|  | 
 | ||||||
|  | 	if (ret < 0)	/* Failed to check the length */ | ||||||
|  | 		*(u32 *)dest = 0; | ||||||
|  | 	else | ||||||
|  | 		*(u32 *)dest = len; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Fetch a null-terminated string. Caller MUST set *(u32 *)buf with max | ||||||
|  |  * length and relative data location. | ||||||
|  |  */ | ||||||
|  | static nokprobe_inline void | ||||||
|  | fetch_store_string(unsigned long addr, void *dest) | ||||||
|  | { | ||||||
|  | 	int maxlen = get_rloc_len(*(u32 *)dest); | ||||||
|  | 	u8 *dst = get_rloc_data(dest); | ||||||
|  | 	long ret; | ||||||
|  | 
 | ||||||
|  | 	if (!maxlen) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * Try to get string again, since the string can be changed while | ||||||
|  | 	 * probing. | ||||||
|  | 	 */ | ||||||
|  | 	ret = strncpy_from_unsafe(dst, (void *)addr, maxlen); | ||||||
|  | 
 | ||||||
|  | 	if (ret < 0) {	/* Failed to fetch string */ | ||||||
|  | 		dst[0] = '\0'; | ||||||
|  | 		*(u32 *)dest = make_data_rloc(0, get_rloc_offs(*(u32 *)dest)); | ||||||
|  | 	} else { | ||||||
|  | 		*(u32 *)dest = make_data_rloc(ret, get_rloc_offs(*(u32 *)dest)); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Note that we don't verify it, since the code does not come from user space */ | ||||||
|  | static int | ||||||
|  | process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, void *dest, | ||||||
|  | 		   bool pre) | ||||||
|  | { | ||||||
|  | 	unsigned long val; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	/* 1st stage: get value from context */ | ||||||
|  | 	switch (code->op) { | ||||||
|  | 	case FETCH_OP_REG: | ||||||
|  | 		val = regs_get_register(regs, code->param); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_STACK: | ||||||
|  | 		val = regs_get_kernel_stack_nth(regs, code->param); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_STACKP: | ||||||
|  | 		val = kernel_stack_pointer(regs); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_RETVAL: | ||||||
|  | 		val = regs_return_value(regs); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_IMM: | ||||||
|  | 		val = code->immediate; | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_COMM: | ||||||
|  | 		val = (unsigned long)current->comm; | ||||||
|  | 		break; | ||||||
|  | 	default: | ||||||
|  | 		return -EILSEQ; | ||||||
|  | 	} | ||||||
|  | 	code++; | ||||||
|  | 
 | ||||||
|  | 	/* 2nd stage: dereference memory if needed */ | ||||||
|  | 	while (code->op == FETCH_OP_DEREF) { | ||||||
|  | 		ret = probe_kernel_read(&val, (void *)val + code->offset, | ||||||
|  | 					sizeof(val)); | ||||||
|  | 		if (ret) | ||||||
|  | 			return ret; | ||||||
|  | 		code++; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* 3rd stage: store value to buffer */ | ||||||
|  | 	switch (code->op) { | ||||||
|  | 	case FETCH_OP_ST_RAW: | ||||||
|  | 		fetch_store_raw(val, code, dest); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_ST_MEM: | ||||||
|  | 		probe_kernel_read(dest, (void *)val + code->offset, code->size); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_ST_STRING: | ||||||
|  | 		if (pre) | ||||||
|  | 			fetch_store_strlen(val + code->offset, dest); | ||||||
|  | 		else | ||||||
|  | 			fetch_store_string(val + code->offset, dest); | ||||||
|  | 		break; | ||||||
|  | 	default: | ||||||
|  | 		return -EILSEQ; | ||||||
|  | 	} | ||||||
|  | 	code++; | ||||||
|  | 
 | ||||||
|  | 	/* 4th stage: modify stored value if needed */ | ||||||
|  | 	if (code->op == FETCH_OP_MOD_BF) { | ||||||
|  | 		fetch_apply_bitfield(code, dest); | ||||||
|  | 		code++; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return code->op == FETCH_OP_END ? 0 : -EILSEQ; | ||||||
|  | } | ||||||
|  | NOKPROBE_SYMBOL(process_fetch_insn) | ||||||
|  | 
 | ||||||
| /* Kprobe handler */ | /* Kprobe handler */ | ||||||
| static nokprobe_inline void | static nokprobe_inline void | ||||||
| __kprobe_trace_func(struct trace_kprobe *tk, struct pt_regs *regs, | __kprobe_trace_func(struct trace_kprobe *tk, struct pt_regs *regs, | ||||||
|  |  | ||||||
|  | @ -61,174 +61,6 @@ int PRINT_TYPE_FUNC_NAME(string)(struct trace_seq *s, void *data, void *ent) | ||||||
| 
 | 
 | ||||||
| const char PRINT_TYPE_FMT_NAME(string)[] = "\\\"%s\\\""; | const char PRINT_TYPE_FMT_NAME(string)[] = "\\\"%s\\\""; | ||||||
| 
 | 
 | ||||||
| #define CHECK_FETCH_FUNCS(method, fn)			\ |  | ||||||
| 	(((FETCH_FUNC_NAME(method, u8) == fn) ||	\ |  | ||||||
| 	  (FETCH_FUNC_NAME(method, u16) == fn) ||	\ |  | ||||||
| 	  (FETCH_FUNC_NAME(method, u32) == fn) ||	\ |  | ||||||
| 	  (FETCH_FUNC_NAME(method, u64) == fn) ||	\ |  | ||||||
| 	  (FETCH_FUNC_NAME(method, string) == fn) ||	\ |  | ||||||
| 	  (FETCH_FUNC_NAME(method, string_size) == fn)) \ |  | ||||||
| 	 && (fn != NULL)) |  | ||||||
| 
 |  | ||||||
| /* Data fetch function templates */ |  | ||||||
| #define DEFINE_FETCH_reg(type)						\ |  | ||||||
| void FETCH_FUNC_NAME(reg, type)(struct pt_regs *regs, void *offset, void *dest)	\ |  | ||||||
| {									\ |  | ||||||
| 	*(type *)dest = (type)regs_get_register(regs,			\ |  | ||||||
| 				(unsigned int)((unsigned long)offset));	\ |  | ||||||
| }									\ |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(reg, type)); |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(reg) |  | ||||||
| /* No string on the register */ |  | ||||||
| #define fetch_reg_string	NULL |  | ||||||
| #define fetch_reg_string_size	NULL |  | ||||||
| 
 |  | ||||||
| #define DEFINE_FETCH_retval(type)					\ |  | ||||||
| void FETCH_FUNC_NAME(retval, type)(struct pt_regs *regs,		\ |  | ||||||
| 				   void *dummy, void *dest)		\ |  | ||||||
| {									\ |  | ||||||
| 	*(type *)dest = (type)regs_return_value(regs);			\ |  | ||||||
| }									\ |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(retval, type)); |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(retval) |  | ||||||
| /* No string on the retval */ |  | ||||||
| #define fetch_retval_string		NULL |  | ||||||
| #define fetch_retval_string_size	NULL |  | ||||||
| 
 |  | ||||||
| /* Dereference memory access function */ |  | ||||||
| struct deref_fetch_param { |  | ||||||
| 	struct fetch_param	orig; |  | ||||||
| 	long			offset; |  | ||||||
| 	fetch_func_t		fetch; |  | ||||||
| 	fetch_func_t		fetch_size; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| #define DEFINE_FETCH_deref(type)					\ |  | ||||||
| void FETCH_FUNC_NAME(deref, type)(struct pt_regs *regs,			\ |  | ||||||
| 				  void *data, void *dest)		\ |  | ||||||
| {									\ |  | ||||||
| 	struct deref_fetch_param *dprm = data;				\ |  | ||||||
| 	unsigned long addr;						\ |  | ||||||
| 	call_fetch(&dprm->orig, regs, &addr);				\ |  | ||||||
| 	if (addr) {							\ |  | ||||||
| 		addr += dprm->offset;					\ |  | ||||||
| 		dprm->fetch(regs, (void *)addr, dest);			\ |  | ||||||
| 	} else								\ |  | ||||||
| 		*(type *)dest = 0;					\ |  | ||||||
| }									\ |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(deref, type)); |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(deref) |  | ||||||
| DEFINE_FETCH_deref(string) |  | ||||||
| 
 |  | ||||||
| void FETCH_FUNC_NAME(deref, string_size)(struct pt_regs *regs, |  | ||||||
| 					 void *data, void *dest) |  | ||||||
| { |  | ||||||
| 	struct deref_fetch_param *dprm = data; |  | ||||||
| 	unsigned long addr; |  | ||||||
| 
 |  | ||||||
| 	call_fetch(&dprm->orig, regs, &addr); |  | ||||||
| 	if (addr && dprm->fetch_size) { |  | ||||||
| 		addr += dprm->offset; |  | ||||||
| 		dprm->fetch_size(regs, (void *)addr, dest); |  | ||||||
| 	} else |  | ||||||
| 		*(string_size *)dest = 0; |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(deref, string_size)); |  | ||||||
| 
 |  | ||||||
| static void update_deref_fetch_param(struct deref_fetch_param *data) |  | ||||||
| { |  | ||||||
| 	if (CHECK_FETCH_FUNCS(deref, data->orig.fn)) |  | ||||||
| 		update_deref_fetch_param(data->orig.data); |  | ||||||
| 	else if (CHECK_FETCH_FUNCS(symbol, data->orig.fn)) |  | ||||||
| 		update_symbol_cache(data->orig.data); |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(update_deref_fetch_param); |  | ||||||
| 
 |  | ||||||
| static void free_deref_fetch_param(struct deref_fetch_param *data) |  | ||||||
| { |  | ||||||
| 	if (CHECK_FETCH_FUNCS(deref, data->orig.fn)) |  | ||||||
| 		free_deref_fetch_param(data->orig.data); |  | ||||||
| 	else if (CHECK_FETCH_FUNCS(symbol, data->orig.fn)) |  | ||||||
| 		free_symbol_cache(data->orig.data); |  | ||||||
| 	kfree(data); |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(free_deref_fetch_param); |  | ||||||
| 
 |  | ||||||
| /* Bitfield fetch function */ |  | ||||||
| struct bitfield_fetch_param { |  | ||||||
| 	struct fetch_param	orig; |  | ||||||
| 	unsigned char		hi_shift; |  | ||||||
| 	unsigned char		low_shift; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| #define DEFINE_FETCH_bitfield(type)					\ |  | ||||||
| void FETCH_FUNC_NAME(bitfield, type)(struct pt_regs *regs,		\ |  | ||||||
| 				     void *data, void *dest)		\ |  | ||||||
| {									\ |  | ||||||
| 	struct bitfield_fetch_param *bprm = data;			\ |  | ||||||
| 	type buf = 0;							\ |  | ||||||
| 	call_fetch(&bprm->orig, regs, &buf);				\ |  | ||||||
| 	if (buf) {							\ |  | ||||||
| 		buf <<= bprm->hi_shift;					\ |  | ||||||
| 		buf >>= bprm->low_shift;				\ |  | ||||||
| 	}								\ |  | ||||||
| 	*(type *)dest = buf;						\ |  | ||||||
| }									\ |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(bitfield, type)); |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(bitfield) |  | ||||||
| #define fetch_bitfield_string		NULL |  | ||||||
| #define fetch_bitfield_string_size	NULL |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| update_bitfield_fetch_param(struct bitfield_fetch_param *data) |  | ||||||
| { |  | ||||||
| 	/*
 |  | ||||||
| 	 * Don't check the bitfield itself, because this must be the |  | ||||||
| 	 * last fetch function. |  | ||||||
| 	 */ |  | ||||||
| 	if (CHECK_FETCH_FUNCS(deref, data->orig.fn)) |  | ||||||
| 		update_deref_fetch_param(data->orig.data); |  | ||||||
| 	else if (CHECK_FETCH_FUNCS(symbol, data->orig.fn)) |  | ||||||
| 		update_symbol_cache(data->orig.data); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| free_bitfield_fetch_param(struct bitfield_fetch_param *data) |  | ||||||
| { |  | ||||||
| 	/*
 |  | ||||||
| 	 * Don't check the bitfield itself, because this must be the |  | ||||||
| 	 * last fetch function. |  | ||||||
| 	 */ |  | ||||||
| 	if (CHECK_FETCH_FUNCS(deref, data->orig.fn)) |  | ||||||
| 		free_deref_fetch_param(data->orig.data); |  | ||||||
| 	else if (CHECK_FETCH_FUNCS(symbol, data->orig.fn)) |  | ||||||
| 		free_symbol_cache(data->orig.data); |  | ||||||
| 
 |  | ||||||
| 	kfree(data); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void FETCH_FUNC_NAME(comm, string)(struct pt_regs *regs, |  | ||||||
| 					  void *data, void *dest) |  | ||||||
| { |  | ||||||
| 	int maxlen = get_rloc_len(*(u32 *)dest); |  | ||||||
| 	u8 *dst = get_rloc_data(dest); |  | ||||||
| 	long ret; |  | ||||||
| 
 |  | ||||||
| 	if (!maxlen) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	ret = strlcpy(dst, current->comm, maxlen); |  | ||||||
| 	*(u32 *)dest = make_data_rloc(ret, get_rloc_offs(*(u32 *)dest)); |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(comm, string)); |  | ||||||
| 
 |  | ||||||
| void FETCH_FUNC_NAME(comm, string_size)(struct pt_regs *regs, |  | ||||||
| 					       void *data, void *dest) |  | ||||||
| { |  | ||||||
| 	*(u32 *)dest = strlen(current->comm) + 1; |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(FETCH_FUNC_NAME(comm, string_size)); |  | ||||||
| 
 |  | ||||||
| static const struct fetch_type *find_fetch_type(const char *type, | static const struct fetch_type *find_fetch_type(const char *type, | ||||||
| 						const struct fetch_type *ftbl) | 						const struct fetch_type *ftbl) | ||||||
| { | { | ||||||
|  | @ -272,37 +104,6 @@ static const struct fetch_type *find_fetch_type(const char *type, | ||||||
| 	return NULL; | 	return NULL; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Special function : only accept unsigned long */ |  | ||||||
| static void fetch_kernel_stack_address(struct pt_regs *regs, void *dummy, void *dest) |  | ||||||
| { |  | ||||||
| 	*(unsigned long *)dest = kernel_stack_pointer(regs); |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(fetch_kernel_stack_address); |  | ||||||
| 
 |  | ||||||
| static void fetch_user_stack_address(struct pt_regs *regs, void *dummy, void *dest) |  | ||||||
| { |  | ||||||
| 	*(unsigned long *)dest = user_stack_pointer(regs); |  | ||||||
| } |  | ||||||
| NOKPROBE_SYMBOL(fetch_user_stack_address); |  | ||||||
| 
 |  | ||||||
| static fetch_func_t get_fetch_size_function(const struct fetch_type *type, |  | ||||||
| 					    fetch_func_t orig_fn, |  | ||||||
| 					    const struct fetch_type *ftbl) |  | ||||||
| { |  | ||||||
| 	int i; |  | ||||||
| 
 |  | ||||||
| 	if (type != &ftbl[FETCH_TYPE_STRING]) |  | ||||||
| 		return NULL;	/* Only string type needs size function */ |  | ||||||
| 
 |  | ||||||
| 	for (i = 0; i < FETCH_MTD_END; i++) |  | ||||||
| 		if (type->fetch[i] == orig_fn) |  | ||||||
| 			return ftbl[FETCH_TYPE_STRSIZE].fetch[i]; |  | ||||||
| 
 |  | ||||||
| 	WARN_ON(1);	/* This should not happen */ |  | ||||||
| 
 |  | ||||||
| 	return NULL; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Split symbol and offset. */ | /* Split symbol and offset. */ | ||||||
| int traceprobe_split_symbol_offset(char *symbol, long *offset) | int traceprobe_split_symbol_offset(char *symbol, long *offset) | ||||||
| { | { | ||||||
|  | @ -327,7 +128,7 @@ int traceprobe_split_symbol_offset(char *symbol, long *offset) | ||||||
| #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) | #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) | ||||||
| 
 | 
 | ||||||
| static int parse_probe_vars(char *arg, const struct fetch_type *t, | static int parse_probe_vars(char *arg, const struct fetch_type *t, | ||||||
| 			    struct fetch_param *f, bool is_return, | 			    struct fetch_insn *code, bool is_return, | ||||||
| 			    bool is_kprobe) | 			    bool is_kprobe) | ||||||
| { | { | ||||||
| 	int ret = 0; | 	int ret = 0; | ||||||
|  | @ -335,33 +136,24 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t, | ||||||
| 
 | 
 | ||||||
| 	if (strcmp(arg, "retval") == 0) { | 	if (strcmp(arg, "retval") == 0) { | ||||||
| 		if (is_return) | 		if (is_return) | ||||||
| 			f->fn = t->fetch[FETCH_MTD_retval]; | 			code->op = FETCH_OP_RETVAL; | ||||||
| 		else | 		else | ||||||
| 			ret = -EINVAL; | 			ret = -EINVAL; | ||||||
| 	} else if (strncmp(arg, "stack", 5) == 0) { | 	} else if (strncmp(arg, "stack", 5) == 0) { | ||||||
| 		if (arg[5] == '\0') { | 		if (arg[5] == '\0') { | ||||||
| 			if (strcmp(t->name, DEFAULT_FETCH_TYPE_STR)) | 			code->op = FETCH_OP_STACKP; | ||||||
| 				return -EINVAL; |  | ||||||
| 
 |  | ||||||
| 			if (is_kprobe) |  | ||||||
| 				f->fn = fetch_kernel_stack_address; |  | ||||||
| 			else |  | ||||||
| 				f->fn = fetch_user_stack_address; |  | ||||||
| 		} else if (isdigit(arg[5])) { | 		} else if (isdigit(arg[5])) { | ||||||
| 			ret = kstrtoul(arg + 5, 10, ¶m); | 			ret = kstrtoul(arg + 5, 10, ¶m); | ||||||
| 			if (ret || (is_kprobe && param > PARAM_MAX_STACK)) | 			if (ret || (is_kprobe && param > PARAM_MAX_STACK)) | ||||||
| 				ret = -EINVAL; | 				ret = -EINVAL; | ||||||
| 			else { | 			else { | ||||||
| 				f->fn = t->fetch[FETCH_MTD_stack]; | 				code->op = FETCH_OP_STACK; | ||||||
| 				f->data = (void *)param; | 				code->param = (unsigned int)param; | ||||||
| 			} | 			} | ||||||
| 		} else | 		} else | ||||||
| 			ret = -EINVAL; | 			ret = -EINVAL; | ||||||
| 	} else if (strcmp(arg, "comm") == 0) { | 	} else if (strcmp(arg, "comm") == 0) { | ||||||
| 		if (strcmp(t->name, "string") != 0 && | 		code->op = FETCH_OP_COMM; | ||||||
| 		    strcmp(t->name, "string_size") != 0) |  | ||||||
| 			return -EINVAL; |  | ||||||
| 		f->fn = t->fetch[FETCH_MTD_comm]; |  | ||||||
| 	} else | 	} else | ||||||
| 		ret = -EINVAL; | 		ret = -EINVAL; | ||||||
| 
 | 
 | ||||||
|  | @ -369,10 +161,13 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Recursive argument parser */ | /* Recursive argument parser */ | ||||||
| static int parse_probe_arg(char *arg, const struct fetch_type *t, | static int | ||||||
| 		     struct fetch_param *f, bool is_return, bool is_kprobe, | parse_probe_arg(char *arg, const struct fetch_type *type, | ||||||
| 		     const struct fetch_type *ftbl) | 		struct fetch_insn **pcode, struct fetch_insn *end, | ||||||
|  | 		bool is_return, bool is_kprobe, | ||||||
|  | 		const struct fetch_type *ftbl) | ||||||
| { | { | ||||||
|  | 	struct fetch_insn *code = *pcode; | ||||||
| 	unsigned long param; | 	unsigned long param; | ||||||
| 	long offset; | 	long offset; | ||||||
| 	char *tmp; | 	char *tmp; | ||||||
|  | @ -380,14 +175,15 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | ||||||
| 
 | 
 | ||||||
| 	switch (arg[0]) { | 	switch (arg[0]) { | ||||||
| 	case '$': | 	case '$': | ||||||
| 		ret = parse_probe_vars(arg + 1, t, f, is_return, is_kprobe); | 		ret = parse_probe_vars(arg + 1, type, code, | ||||||
|  | 					is_return, is_kprobe); | ||||||
| 		break; | 		break; | ||||||
| 
 | 
 | ||||||
| 	case '%':	/* named register */ | 	case '%':	/* named register */ | ||||||
| 		ret = regs_query_register_offset(arg + 1); | 		ret = regs_query_register_offset(arg + 1); | ||||||
| 		if (ret >= 0) { | 		if (ret >= 0) { | ||||||
| 			f->fn = t->fetch[FETCH_MTD_reg]; | 			code->op = FETCH_OP_REG; | ||||||
| 			f->data = (void *)(unsigned long)ret; | 			code->param = (unsigned int)ret; | ||||||
| 			ret = 0; | 			ret = 0; | ||||||
| 		} | 		} | ||||||
| 		break; | 		break; | ||||||
|  | @ -397,9 +193,9 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | ||||||
| 			ret = kstrtoul(arg + 1, 0, ¶m); | 			ret = kstrtoul(arg + 1, 0, ¶m); | ||||||
| 			if (ret) | 			if (ret) | ||||||
| 				break; | 				break; | ||||||
| 
 | 			/* load address */ | ||||||
| 			f->fn = t->fetch[FETCH_MTD_memory]; | 			code->op = FETCH_OP_IMM; | ||||||
| 			f->data = (void *)param; | 			code->immediate = param; | ||||||
| 		} else if (arg[1] == '+') { | 		} else if (arg[1] == '+') { | ||||||
| 			/* kprobes don't support file offsets */ | 			/* kprobes don't support file offsets */ | ||||||
| 			if (is_kprobe) | 			if (is_kprobe) | ||||||
|  | @ -409,8 +205,8 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | ||||||
| 			if (ret) | 			if (ret) | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			f->fn = t->fetch[FETCH_MTD_file_offset]; | 			code->op = FETCH_OP_FOFFS; | ||||||
| 			f->data = (void *)offset; | 			code->immediate = (unsigned long)offset;  // imm64?
 | ||||||
| 		} else { | 		} else { | ||||||
| 			/* uprobes don't support symbols */ | 			/* uprobes don't support symbols */ | ||||||
| 			if (!is_kprobe) | 			if (!is_kprobe) | ||||||
|  | @ -420,10 +216,19 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | ||||||
| 			if (ret) | 			if (ret) | ||||||
| 				break; | 				break; | ||||||
| 
 | 
 | ||||||
| 			f->data = alloc_symbol_cache(arg + 1, offset); | 			code->op = FETCH_OP_IMM; | ||||||
| 			if (f->data) | 			code->immediate = | ||||||
| 				f->fn = t->fetch[FETCH_MTD_symbol]; | 				(unsigned long)kallsyms_lookup_name(arg + 1); | ||||||
|  | 			if (!code->immediate) | ||||||
|  | 				return -ENOENT; | ||||||
|  | 			code->immediate += offset; | ||||||
| 		} | 		} | ||||||
|  | 		/* These are fetching from memory */ | ||||||
|  | 		if (++code == end) | ||||||
|  | 			return -E2BIG; | ||||||
|  | 		*pcode = code; | ||||||
|  | 		code->op = FETCH_OP_DEREF; | ||||||
|  | 		code->offset = offset; | ||||||
| 		break; | 		break; | ||||||
| 
 | 
 | ||||||
| 	case '+':	/* deref memory */ | 	case '+':	/* deref memory */ | ||||||
|  | @ -431,11 +236,10 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | ||||||
| 	case '-': | 	case '-': | ||||||
| 		tmp = strchr(arg, '('); | 		tmp = strchr(arg, '('); | ||||||
| 		if (!tmp) | 		if (!tmp) | ||||||
| 			break; | 			return -EINVAL; | ||||||
| 
 | 
 | ||||||
| 		*tmp = '\0'; | 		*tmp = '\0'; | ||||||
| 		ret = kstrtol(arg, 0, &offset); | 		ret = kstrtol(arg, 0, &offset); | ||||||
| 
 |  | ||||||
| 		if (ret) | 		if (ret) | ||||||
| 			break; | 			break; | ||||||
| 
 | 
 | ||||||
|  | @ -443,36 +247,29 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | ||||||
| 		tmp = strrchr(arg, ')'); | 		tmp = strrchr(arg, ')'); | ||||||
| 
 | 
 | ||||||
| 		if (tmp) { | 		if (tmp) { | ||||||
| 			struct deref_fetch_param	*dprm; | 			const struct fetch_type *t2; | ||||||
| 			const struct fetch_type		*t2; |  | ||||||
| 
 | 
 | ||||||
| 			t2 = find_fetch_type(NULL, ftbl); | 			t2 = find_fetch_type(NULL, ftbl); | ||||||
| 			*tmp = '\0'; | 			*tmp = '\0'; | ||||||
| 			dprm = kzalloc(sizeof(struct deref_fetch_param), GFP_KERNEL); | 			ret = parse_probe_arg(arg, t2, &code, end, is_return, | ||||||
| 
 | 					      is_kprobe, ftbl); | ||||||
| 			if (!dprm) |  | ||||||
| 				return -ENOMEM; |  | ||||||
| 
 |  | ||||||
| 			dprm->offset = offset; |  | ||||||
| 			dprm->fetch = t->fetch[FETCH_MTD_memory]; |  | ||||||
| 			dprm->fetch_size = get_fetch_size_function(t, |  | ||||||
| 							dprm->fetch, ftbl); |  | ||||||
| 			ret = parse_probe_arg(arg, t2, &dprm->orig, is_return, |  | ||||||
| 							is_kprobe, ftbl); |  | ||||||
| 			if (ret) | 			if (ret) | ||||||
| 				kfree(dprm); | 				break; | ||||||
| 			else { | 			if (code->op == FETCH_OP_COMM) | ||||||
| 				f->fn = t->fetch[FETCH_MTD_deref]; | 				return -EINVAL; | ||||||
| 				f->data = (void *)dprm; | 			if (++code == end) | ||||||
| 			} | 				return -E2BIG; | ||||||
|  | 			*pcode = code; | ||||||
|  | 
 | ||||||
|  | 			code->op = FETCH_OP_DEREF; | ||||||
|  | 			code->offset = offset; | ||||||
| 		} | 		} | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| 	if (!ret && !f->fn) {	/* Parsed, but do not find fetch method */ | 	if (!ret && code->op == FETCH_OP_NOP) { | ||||||
| 		pr_info("%s type has no corresponding fetch method.\n", t->name); | 		/* Parsed, but do not find fetch method */ | ||||||
| 		ret = -EINVAL; | 		ret = -EINVAL; | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -481,22 +278,15 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | ||||||
| /* Bitfield type needs to be parsed into a fetch function */ | /* Bitfield type needs to be parsed into a fetch function */ | ||||||
| static int __parse_bitfield_probe_arg(const char *bf, | static int __parse_bitfield_probe_arg(const char *bf, | ||||||
| 				      const struct fetch_type *t, | 				      const struct fetch_type *t, | ||||||
| 				      struct fetch_param *f) | 				      struct fetch_insn **pcode) | ||||||
| { | { | ||||||
| 	struct bitfield_fetch_param *bprm; | 	struct fetch_insn *code = *pcode; | ||||||
| 	unsigned long bw, bo; | 	unsigned long bw, bo; | ||||||
| 	char *tail; | 	char *tail; | ||||||
| 
 | 
 | ||||||
| 	if (*bf != 'b') | 	if (*bf != 'b') | ||||||
| 		return 0; | 		return 0; | ||||||
| 
 | 
 | ||||||
| 	bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); |  | ||||||
| 	if (!bprm) |  | ||||||
| 		return -ENOMEM; |  | ||||||
| 
 |  | ||||||
| 	bprm->orig = *f; |  | ||||||
| 	f->fn = t->fetch[FETCH_MTD_bitfield]; |  | ||||||
| 	f->data = (void *)bprm; |  | ||||||
| 	bw = simple_strtoul(bf + 1, &tail, 0);	/* Use simple one */ | 	bw = simple_strtoul(bf + 1, &tail, 0);	/* Use simple one */ | ||||||
| 
 | 
 | ||||||
| 	if (bw == 0 || *tail != '@') | 	if (bw == 0 || *tail != '@') | ||||||
|  | @ -507,9 +297,15 @@ static int __parse_bitfield_probe_arg(const char *bf, | ||||||
| 
 | 
 | ||||||
| 	if (tail == bf || *tail != '/') | 	if (tail == bf || *tail != '/') | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
|  | 	code++; | ||||||
|  | 	if (code->op != FETCH_OP_NOP) | ||||||
|  | 		return -E2BIG; | ||||||
|  | 	*pcode = code; | ||||||
| 
 | 
 | ||||||
| 	bprm->hi_shift = BYTES_TO_BITS(t->size) - (bw + bo); | 	code->op = FETCH_OP_MOD_BF; | ||||||
| 	bprm->low_shift = bprm->hi_shift + bo; | 	code->lshift = BYTES_TO_BITS(t->size) - (bw + bo); | ||||||
|  | 	code->rshift = BYTES_TO_BITS(t->size) - bw; | ||||||
|  | 	code->basesize = t->size; | ||||||
| 
 | 
 | ||||||
| 	return (BYTES_TO_BITS(t->size) < (bw + bo)) ? -EINVAL : 0; | 	return (BYTES_TO_BITS(t->size) < (bw + bo)) ? -EINVAL : 0; | ||||||
| } | } | ||||||
|  | @ -519,6 +315,7 @@ int traceprobe_parse_probe_arg(char *arg, ssize_t *size, | ||||||
| 		struct probe_arg *parg, bool is_return, bool is_kprobe, | 		struct probe_arg *parg, bool is_return, bool is_kprobe, | ||||||
| 		const struct fetch_type *ftbl) | 		const struct fetch_type *ftbl) | ||||||
| { | { | ||||||
|  | 	struct fetch_insn *code, *tmp = NULL; | ||||||
| 	const char *t; | 	const char *t; | ||||||
| 	int ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
|  | @ -549,18 +346,60 @@ int traceprobe_parse_probe_arg(char *arg, ssize_t *size, | ||||||
| 	} | 	} | ||||||
| 	parg->offset = *size; | 	parg->offset = *size; | ||||||
| 	*size += parg->type->size; | 	*size += parg->type->size; | ||||||
| 	ret = parse_probe_arg(arg, parg->type, &parg->fetch, is_return, |  | ||||||
| 			      is_kprobe, ftbl); |  | ||||||
| 
 | 
 | ||||||
| 	if (ret >= 0 && t != NULL) | 	code = tmp = kzalloc(sizeof(*code) * FETCH_INSN_MAX, GFP_KERNEL); | ||||||
| 		ret = __parse_bitfield_probe_arg(t, parg->type, &parg->fetch); | 	if (!code) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 	code[FETCH_INSN_MAX - 1].op = FETCH_OP_END; | ||||||
| 
 | 
 | ||||||
| 	if (ret >= 0) { | 	ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1], | ||||||
| 		parg->fetch_size.fn = get_fetch_size_function(parg->type, | 			      is_return, is_kprobe, ftbl); | ||||||
| 							      parg->fetch.fn, | 	if (ret) | ||||||
| 							      ftbl); | 		goto fail; | ||||||
| 		parg->fetch_size.data = parg->fetch.data; | 
 | ||||||
|  | 	/* Store operation */ | ||||||
|  | 	if (!strcmp(parg->type->name, "string")) { | ||||||
|  | 		if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_IMM && | ||||||
|  | 		    code->op != FETCH_OP_COMM) { | ||||||
|  | 			pr_info("string only accepts memory or address.\n"); | ||||||
|  | 			ret = -EINVAL; | ||||||
|  | 			goto fail; | ||||||
|  | 		} | ||||||
|  | 		/* Since IMM or COMM must be the 1st insn, this is safe */ | ||||||
|  | 		if (code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM) | ||||||
|  | 			code++; | ||||||
|  | 		code->op = FETCH_OP_ST_STRING;	/* In DEREF case, replace it */ | ||||||
|  | 		parg->dynamic = true; | ||||||
|  | 	} else if (code->op == FETCH_OP_DEREF) { | ||||||
|  | 		code->op = FETCH_OP_ST_MEM; | ||||||
|  | 		code->size = parg->type->size; | ||||||
|  | 	} else { | ||||||
|  | 		code++; | ||||||
|  | 		if (code->op != FETCH_OP_NOP) { | ||||||
|  | 			ret = -E2BIG; | ||||||
|  | 			goto fail; | ||||||
|  | 		} | ||||||
|  | 		code->op = FETCH_OP_ST_RAW; | ||||||
|  | 		code->size = parg->type->size; | ||||||
| 	} | 	} | ||||||
|  | 	/* Modify operation */ | ||||||
|  | 	if (t != NULL) { | ||||||
|  | 		ret = __parse_bitfield_probe_arg(t, parg->type, &code); | ||||||
|  | 		if (ret) | ||||||
|  | 			goto fail; | ||||||
|  | 	} | ||||||
|  | 	code++; | ||||||
|  | 	code->op = FETCH_OP_END; | ||||||
|  | 
 | ||||||
|  | 	/* Shrink down the code buffer */ | ||||||
|  | 	parg->code = kzalloc(sizeof(*code) * (code - tmp + 1), GFP_KERNEL); | ||||||
|  | 	if (!parg->code) | ||||||
|  | 		ret = -ENOMEM; | ||||||
|  | 	else | ||||||
|  | 		memcpy(parg->code, tmp, sizeof(*code) * (code - tmp + 1)); | ||||||
|  | 
 | ||||||
|  | fail: | ||||||
|  | 	kfree(tmp); | ||||||
| 
 | 
 | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  | @ -582,25 +421,9 @@ int traceprobe_conflict_field_name(const char *name, | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void traceprobe_update_arg(struct probe_arg *arg) |  | ||||||
| { |  | ||||||
| 	if (CHECK_FETCH_FUNCS(bitfield, arg->fetch.fn)) |  | ||||||
| 		update_bitfield_fetch_param(arg->fetch.data); |  | ||||||
| 	else if (CHECK_FETCH_FUNCS(deref, arg->fetch.fn)) |  | ||||||
| 		update_deref_fetch_param(arg->fetch.data); |  | ||||||
| 	else if (CHECK_FETCH_FUNCS(symbol, arg->fetch.fn)) |  | ||||||
| 		update_symbol_cache(arg->fetch.data); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void traceprobe_free_probe_arg(struct probe_arg *arg) | void traceprobe_free_probe_arg(struct probe_arg *arg) | ||||||
| { | { | ||||||
| 	if (CHECK_FETCH_FUNCS(bitfield, arg->fetch.fn)) | 	kfree(arg->code); | ||||||
| 		free_bitfield_fetch_param(arg->fetch.data); |  | ||||||
| 	else if (CHECK_FETCH_FUNCS(deref, arg->fetch.fn)) |  | ||||||
| 		free_deref_fetch_param(arg->fetch.data); |  | ||||||
| 	else if (CHECK_FETCH_FUNCS(symbol, arg->fetch.fn)) |  | ||||||
| 		free_symbol_cache(arg->fetch.data); |  | ||||||
| 
 |  | ||||||
| 	kfree(arg->name); | 	kfree(arg->name); | ||||||
| 	kfree(arg->comm); | 	kfree(arg->comm); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -79,25 +79,50 @@ static nokprobe_inline void *get_loc_data(u32 *dl, void *ent) | ||||||
| 	return (u8 *)ent + get_rloc_offs(*dl); | 	return (u8 *)ent + get_rloc_offs(*dl); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Data fetch function type */ |  | ||||||
| typedef	void (*fetch_func_t)(struct pt_regs *, void *, void *); |  | ||||||
| /* Printing function type */ | /* Printing function type */ | ||||||
| typedef int (*print_type_func_t)(struct trace_seq *, void *, void *); | typedef int (*print_type_func_t)(struct trace_seq *, void *, void *); | ||||||
| 
 | 
 | ||||||
| /* Fetch types */ | enum fetch_op { | ||||||
| enum { | 	FETCH_OP_NOP = 0, | ||||||
| 	FETCH_MTD_reg = 0, | 	// Stage 1 (load) ops
 | ||||||
| 	FETCH_MTD_stack, | 	FETCH_OP_REG,		/* Register : .param = offset */ | ||||||
| 	FETCH_MTD_retval, | 	FETCH_OP_STACK,		/* Stack : .param = index */ | ||||||
| 	FETCH_MTD_comm, | 	FETCH_OP_STACKP,	/* Stack pointer */ | ||||||
| 	FETCH_MTD_memory, | 	FETCH_OP_RETVAL,	/* Return value */ | ||||||
| 	FETCH_MTD_symbol, | 	FETCH_OP_IMM,		/* Immediate : .immediate */ | ||||||
| 	FETCH_MTD_deref, | 	FETCH_OP_COMM,		/* Current comm */ | ||||||
| 	FETCH_MTD_bitfield, | 	FETCH_OP_FOFFS,		/* File offset: .immediate */ | ||||||
| 	FETCH_MTD_file_offset, | 	// Stage 2 (dereference) op
 | ||||||
| 	FETCH_MTD_END, | 	FETCH_OP_DEREF,		/* Dereference: .offset */ | ||||||
|  | 	// Stage 3 (store) ops
 | ||||||
|  | 	FETCH_OP_ST_RAW,	/* Raw: .size */ | ||||||
|  | 	FETCH_OP_ST_MEM,	/* Mem: .offset, .size */ | ||||||
|  | 	FETCH_OP_ST_STRING,	/* String: .offset, .size */ | ||||||
|  | 	// Stage 4 (modify) op
 | ||||||
|  | 	FETCH_OP_MOD_BF,	/* Bitfield: .basesize, .lshift, .rshift */ | ||||||
|  | 	FETCH_OP_END, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct fetch_insn { | ||||||
|  | 	enum fetch_op op; | ||||||
|  | 	union { | ||||||
|  | 		unsigned int param; | ||||||
|  | 		struct { | ||||||
|  | 			unsigned int size; | ||||||
|  | 			int offset; | ||||||
|  | 		}; | ||||||
|  | 		struct { | ||||||
|  | 			unsigned char basesize; | ||||||
|  | 			unsigned char lshift; | ||||||
|  | 			unsigned char rshift; | ||||||
|  | 		}; | ||||||
|  | 		unsigned long immediate; | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* fetch + deref*N + store + mod + end <= 16, this allows N=12, enough */ | ||||||
|  | #define FETCH_INSN_MAX	16 | ||||||
|  | 
 | ||||||
| /* Fetch type information table */ | /* Fetch type information table */ | ||||||
| struct fetch_type { | struct fetch_type { | ||||||
| 	const char		*name;		/* Name of type */ | 	const char		*name;		/* Name of type */ | ||||||
|  | @ -106,13 +131,6 @@ struct fetch_type { | ||||||
| 	print_type_func_t	print;		/* Print functions */ | 	print_type_func_t	print;		/* Print functions */ | ||||||
| 	const char		*fmt;		/* Fromat string */ | 	const char		*fmt;		/* Fromat string */ | ||||||
| 	const char		*fmttype;	/* Name in format file */ | 	const char		*fmttype;	/* Name in format file */ | ||||||
| 	/* Fetch functions */ |  | ||||||
| 	fetch_func_t		fetch[FETCH_MTD_END]; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| struct fetch_param { |  | ||||||
| 	fetch_func_t		fn; |  | ||||||
| 	void 			*data; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /* For defining macros, define string/string_size types */ | /* For defining macros, define string/string_size types */ | ||||||
|  | @ -142,66 +160,12 @@ DECLARE_BASIC_PRINT_TYPE_FUNC(x64); | ||||||
| 
 | 
 | ||||||
| DECLARE_BASIC_PRINT_TYPE_FUNC(string); | DECLARE_BASIC_PRINT_TYPE_FUNC(string); | ||||||
| 
 | 
 | ||||||
| #define FETCH_FUNC_NAME(method, type)	fetch_##method##_##type |  | ||||||
| 
 |  | ||||||
| /* Declare macro for basic types */ |  | ||||||
| #define DECLARE_FETCH_FUNC(method, type)				\ |  | ||||||
| extern void FETCH_FUNC_NAME(method, type)(struct pt_regs *regs, 	\ |  | ||||||
| 					  void *data, void *dest) |  | ||||||
| 
 |  | ||||||
| #define DECLARE_BASIC_FETCH_FUNCS(method) 	\ |  | ||||||
| DECLARE_FETCH_FUNC(method, u8);			\ |  | ||||||
| DECLARE_FETCH_FUNC(method, u16);		\ |  | ||||||
| DECLARE_FETCH_FUNC(method, u32);		\ |  | ||||||
| DECLARE_FETCH_FUNC(method, u64) |  | ||||||
| 
 |  | ||||||
| DECLARE_BASIC_FETCH_FUNCS(reg); |  | ||||||
| #define fetch_reg_string			NULL |  | ||||||
| #define fetch_reg_string_size			NULL |  | ||||||
| 
 |  | ||||||
| DECLARE_BASIC_FETCH_FUNCS(retval); |  | ||||||
| #define fetch_retval_string			NULL |  | ||||||
| #define fetch_retval_string_size		NULL |  | ||||||
| 
 |  | ||||||
| DECLARE_BASIC_FETCH_FUNCS(symbol); |  | ||||||
| DECLARE_FETCH_FUNC(symbol, string); |  | ||||||
| DECLARE_FETCH_FUNC(symbol, string_size); |  | ||||||
| 
 |  | ||||||
| DECLARE_BASIC_FETCH_FUNCS(deref); |  | ||||||
| DECLARE_FETCH_FUNC(deref, string); |  | ||||||
| DECLARE_FETCH_FUNC(deref, string_size); |  | ||||||
| 
 |  | ||||||
| DECLARE_BASIC_FETCH_FUNCS(bitfield); |  | ||||||
| #define fetch_bitfield_string			NULL |  | ||||||
| #define fetch_bitfield_string_size		NULL |  | ||||||
| 
 |  | ||||||
| /* comm only makes sense as a string */ |  | ||||||
| #define fetch_comm_u8		NULL |  | ||||||
| #define fetch_comm_u16		NULL |  | ||||||
| #define fetch_comm_u32		NULL |  | ||||||
| #define fetch_comm_u64		NULL |  | ||||||
| DECLARE_FETCH_FUNC(comm, string); |  | ||||||
| DECLARE_FETCH_FUNC(comm, string_size); |  | ||||||
| 
 |  | ||||||
| /*
 |  | ||||||
|  * Define macro for basic types - we don't need to define s* types, because |  | ||||||
|  * we have to care only about bitwidth at recording time. |  | ||||||
|  */ |  | ||||||
| #define DEFINE_BASIC_FETCH_FUNCS(method) \ |  | ||||||
| DEFINE_FETCH_##method(u8)		\ |  | ||||||
| DEFINE_FETCH_##method(u16)		\ |  | ||||||
| DEFINE_FETCH_##method(u32)		\ |  | ||||||
| DEFINE_FETCH_##method(u64) |  | ||||||
| 
 |  | ||||||
| /* Default (unsigned long) fetch type */ | /* Default (unsigned long) fetch type */ | ||||||
| #define __DEFAULT_FETCH_TYPE(t) x##t | #define __DEFAULT_FETCH_TYPE(t) x##t | ||||||
| #define _DEFAULT_FETCH_TYPE(t) __DEFAULT_FETCH_TYPE(t) | #define _DEFAULT_FETCH_TYPE(t) __DEFAULT_FETCH_TYPE(t) | ||||||
| #define DEFAULT_FETCH_TYPE _DEFAULT_FETCH_TYPE(BITS_PER_LONG) | #define DEFAULT_FETCH_TYPE _DEFAULT_FETCH_TYPE(BITS_PER_LONG) | ||||||
| #define DEFAULT_FETCH_TYPE_STR __stringify(DEFAULT_FETCH_TYPE) | #define DEFAULT_FETCH_TYPE_STR __stringify(DEFAULT_FETCH_TYPE) | ||||||
| 
 | 
 | ||||||
| #define ASSIGN_FETCH_FUNC(method, type)	\ |  | ||||||
| 	[FETCH_MTD_##method] = FETCH_FUNC_NAME(method, type) |  | ||||||
| 
 |  | ||||||
| #define __ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, _fmttype)	\ | #define __ASSIGN_FETCH_TYPE(_name, ptype, ftype, _size, sign, _fmttype)	\ | ||||||
| 	{.name = _name,				\ | 	{.name = _name,				\ | ||||||
| 	 .size = _size,					\ | 	 .size = _size,					\ | ||||||
|  | @ -209,17 +173,6 @@ DEFINE_FETCH_##method(u64) | ||||||
| 	 .print = PRINT_TYPE_FUNC_NAME(ptype),		\ | 	 .print = PRINT_TYPE_FUNC_NAME(ptype),		\ | ||||||
| 	 .fmt = PRINT_TYPE_FMT_NAME(ptype),		\ | 	 .fmt = PRINT_TYPE_FMT_NAME(ptype),		\ | ||||||
| 	 .fmttype = _fmttype,				\ | 	 .fmttype = _fmttype,				\ | ||||||
| 	 .fetch = {					\ |  | ||||||
| ASSIGN_FETCH_FUNC(reg, ftype),				\ |  | ||||||
| ASSIGN_FETCH_FUNC(stack, ftype),			\ |  | ||||||
| ASSIGN_FETCH_FUNC(retval, ftype),			\ |  | ||||||
| ASSIGN_FETCH_FUNC(comm, ftype),				\ |  | ||||||
| ASSIGN_FETCH_FUNC(memory, ftype),			\ |  | ||||||
| ASSIGN_FETCH_FUNC(symbol, ftype),			\ |  | ||||||
| ASSIGN_FETCH_FUNC(deref, ftype),			\ |  | ||||||
| ASSIGN_FETCH_FUNC(bitfield, ftype),			\ |  | ||||||
| ASSIGN_FETCH_FUNC(file_offset, ftype),			\ |  | ||||||
| 	  }						\ |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| #define ASSIGN_FETCH_TYPE(ptype, ftype, sign)			\ | #define ASSIGN_FETCH_TYPE(ptype, ftype, sign)			\ | ||||||
|  | @ -231,42 +184,13 @@ ASSIGN_FETCH_FUNC(file_offset, ftype),			\ | ||||||
| 
 | 
 | ||||||
| #define ASSIGN_FETCH_TYPE_END {} | #define ASSIGN_FETCH_TYPE_END {} | ||||||
| 
 | 
 | ||||||
| #define FETCH_TYPE_STRING	0 | #define FETCH_TYPE_STRING      0 | ||||||
| #define FETCH_TYPE_STRSIZE	1 | #define FETCH_TYPE_STRSIZE     1 | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_KPROBE_EVENTS | #ifdef CONFIG_KPROBE_EVENTS | ||||||
| struct symbol_cache; |  | ||||||
| unsigned long update_symbol_cache(struct symbol_cache *sc); |  | ||||||
| void free_symbol_cache(struct symbol_cache *sc); |  | ||||||
| struct symbol_cache *alloc_symbol_cache(const char *sym, long offset); |  | ||||||
| bool trace_kprobe_on_func_entry(struct trace_event_call *call); | bool trace_kprobe_on_func_entry(struct trace_event_call *call); | ||||||
| bool trace_kprobe_error_injectable(struct trace_event_call *call); | bool trace_kprobe_error_injectable(struct trace_event_call *call); | ||||||
| #else | #else | ||||||
| /* uprobes do not support symbol fetch methods */ |  | ||||||
| #define fetch_symbol_u8			NULL |  | ||||||
| #define fetch_symbol_u16		NULL |  | ||||||
| #define fetch_symbol_u32		NULL |  | ||||||
| #define fetch_symbol_u64		NULL |  | ||||||
| #define fetch_symbol_string		NULL |  | ||||||
| #define fetch_symbol_string_size	NULL |  | ||||||
| 
 |  | ||||||
| struct symbol_cache { |  | ||||||
| }; |  | ||||||
| static inline unsigned long __used update_symbol_cache(struct symbol_cache *sc) |  | ||||||
| { |  | ||||||
| 	return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static inline void __used free_symbol_cache(struct symbol_cache *sc) |  | ||||||
| { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static inline struct symbol_cache * __used |  | ||||||
| alloc_symbol_cache(const char *sym, long offset) |  | ||||||
| { |  | ||||||
| 	return NULL; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static inline bool trace_kprobe_on_func_entry(struct trace_event_call *call) | static inline bool trace_kprobe_on_func_entry(struct trace_event_call *call) | ||||||
| { | { | ||||||
| 	return false; | 	return false; | ||||||
|  | @ -279,8 +203,8 @@ static inline bool trace_kprobe_error_injectable(struct trace_event_call *call) | ||||||
| #endif /* CONFIG_KPROBE_EVENTS */ | #endif /* CONFIG_KPROBE_EVENTS */ | ||||||
| 
 | 
 | ||||||
| struct probe_arg { | struct probe_arg { | ||||||
| 	struct fetch_param	fetch; | 	struct fetch_insn	*code; | ||||||
| 	struct fetch_param	fetch_size; | 	bool			dynamic;/* Dynamic array (string) is used */ | ||||||
| 	unsigned int		offset;	/* Offset from argument entry */ | 	unsigned int		offset;	/* Offset from argument entry */ | ||||||
| 	const char		*name;	/* Name of this argument */ | 	const char		*name;	/* Name of this argument */ | ||||||
| 	const char		*comm;	/* Command of this argument */ | 	const char		*comm;	/* Command of this argument */ | ||||||
|  | @ -312,12 +236,6 @@ static inline bool trace_probe_is_registered(struct trace_probe *tp) | ||||||
| 	return !!(tp->flags & TP_FLAG_REGISTERED); | 	return !!(tp->flags & TP_FLAG_REGISTERED); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static nokprobe_inline void call_fetch(struct fetch_param *fprm, |  | ||||||
| 				 struct pt_regs *regs, void *dest) |  | ||||||
| { |  | ||||||
| 	return fprm->fn(regs, fprm->data, dest); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Check the name is good for event/group/fields */ | /* Check the name is good for event/group/fields */ | ||||||
| static inline bool is_good_name(const char *name) | static inline bool is_good_name(const char *name) | ||||||
| { | { | ||||||
|  | @ -354,68 +272,6 @@ extern void traceprobe_free_probe_arg(struct probe_arg *arg); | ||||||
| 
 | 
 | ||||||
| extern int traceprobe_split_symbol_offset(char *symbol, long *offset); | extern int traceprobe_split_symbol_offset(char *symbol, long *offset); | ||||||
| 
 | 
 | ||||||
| /* Sum up total data length for dynamic arraies (strings) */ |  | ||||||
| static nokprobe_inline int |  | ||||||
| __get_data_size(struct trace_probe *tp, struct pt_regs *regs) |  | ||||||
| { |  | ||||||
| 	int i, ret = 0; |  | ||||||
| 	u32 len; |  | ||||||
| 
 |  | ||||||
| 	for (i = 0; i < tp->nr_args; i++) |  | ||||||
| 		if (unlikely(tp->args[i].fetch_size.fn)) { |  | ||||||
| 			call_fetch(&tp->args[i].fetch_size, regs, &len); |  | ||||||
| 			ret += len; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* Store the value of each argument */ |  | ||||||
| static nokprobe_inline void |  | ||||||
| store_trace_args(int ent_size, struct trace_probe *tp, struct pt_regs *regs, |  | ||||||
| 		 u8 *data, int maxlen) |  | ||||||
| { |  | ||||||
| 	int i; |  | ||||||
| 	u32 end = tp->size; |  | ||||||
| 	u32 *dl;	/* Data (relative) location */ |  | ||||||
| 
 |  | ||||||
| 	for (i = 0; i < tp->nr_args; i++) { |  | ||||||
| 		if (unlikely(tp->args[i].fetch_size.fn)) { |  | ||||||
| 			/*
 |  | ||||||
| 			 * First, we set the relative location and |  | ||||||
| 			 * maximum data length to *dl |  | ||||||
| 			 */ |  | ||||||
| 			dl = (u32 *)(data + tp->args[i].offset); |  | ||||||
| 			*dl = make_data_rloc(maxlen, end - tp->args[i].offset); |  | ||||||
| 			/* Then try to fetch string or dynamic array data */ |  | ||||||
| 			call_fetch(&tp->args[i].fetch, regs, dl); |  | ||||||
| 			/* Reduce maximum length */ |  | ||||||
| 			end += get_rloc_len(*dl); |  | ||||||
| 			maxlen -= get_rloc_len(*dl); |  | ||||||
| 			/* Trick here, convert data_rloc to data_loc */ |  | ||||||
| 			*dl = convert_rloc_to_loc(*dl, |  | ||||||
| 				 ent_size + tp->args[i].offset); |  | ||||||
| 		} else |  | ||||||
| 			/* Just fetching data normally */ |  | ||||||
| 			call_fetch(&tp->args[i].fetch, regs, |  | ||||||
| 				   data + tp->args[i].offset); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static inline int |  | ||||||
| print_probe_args(struct trace_seq *s, struct probe_arg *args, int nr_args, |  | ||||||
| 		 u8 *data, void *field) |  | ||||||
| { |  | ||||||
| 	int i; |  | ||||||
| 
 |  | ||||||
| 	for (i = 0; i < nr_args; i++) { |  | ||||||
| 		trace_seq_printf(s, " %s=", args[i].name); |  | ||||||
| 		if (!args[i].type->print(s, data + args[i].offset, field)) |  | ||||||
| 			return -ENOMEM; |  | ||||||
| 	} |  | ||||||
| 	return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| extern int set_print_fmt(struct trace_probe *tp, bool is_return); | extern int set_print_fmt(struct trace_probe *tp, bool is_return); | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_PERF_EVENTS | #ifdef CONFIG_PERF_EVENTS | ||||||
|  |  | ||||||
							
								
								
									
										120
									
								
								kernel/trace/trace_probe_tmpl.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								kernel/trace/trace_probe_tmpl.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,120 @@ | ||||||
|  | /* SPDX-License-Identifier: GPL-2.0 */ | ||||||
|  | /*
 | ||||||
|  |  * Traceprobe fetch helper inlines | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | static nokprobe_inline void | ||||||
|  | fetch_store_raw(unsigned long val, struct fetch_insn *code, void *buf) | ||||||
|  | { | ||||||
|  | 	switch (code->size) { | ||||||
|  | 	case 1: | ||||||
|  | 		*(u8 *)buf = (u8)val; | ||||||
|  | 		break; | ||||||
|  | 	case 2: | ||||||
|  | 		*(u16 *)buf = (u16)val; | ||||||
|  | 		break; | ||||||
|  | 	case 4: | ||||||
|  | 		*(u32 *)buf = (u32)val; | ||||||
|  | 		break; | ||||||
|  | 	case 8: | ||||||
|  | 		//TBD: 32bit signed
 | ||||||
|  | 		*(u64 *)buf = (u64)val; | ||||||
|  | 		break; | ||||||
|  | 	default: | ||||||
|  | 		*(unsigned long *)buf = val; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static nokprobe_inline void | ||||||
|  | fetch_apply_bitfield(struct fetch_insn *code, void *buf) | ||||||
|  | { | ||||||
|  | 	switch (code->basesize) { | ||||||
|  | 	case 1: | ||||||
|  | 		*(u8 *)buf <<= code->lshift; | ||||||
|  | 		*(u8 *)buf >>= code->rshift; | ||||||
|  | 		break; | ||||||
|  | 	case 2: | ||||||
|  | 		*(u16 *)buf <<= code->lshift; | ||||||
|  | 		*(u16 *)buf >>= code->rshift; | ||||||
|  | 		break; | ||||||
|  | 	case 4: | ||||||
|  | 		*(u32 *)buf <<= code->lshift; | ||||||
|  | 		*(u32 *)buf >>= code->rshift; | ||||||
|  | 		break; | ||||||
|  | 	case 8: | ||||||
|  | 		*(u64 *)buf <<= code->lshift; | ||||||
|  | 		*(u64 *)buf >>= code->rshift; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Define this for each callsite */ | ||||||
|  | static int | ||||||
|  | process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, | ||||||
|  | 		   void *dest, bool pre); | ||||||
|  | 
 | ||||||
|  | /* Sum up total data length for dynamic arraies (strings) */ | ||||||
|  | static nokprobe_inline int | ||||||
|  | __get_data_size(struct trace_probe *tp, struct pt_regs *regs) | ||||||
|  | { | ||||||
|  | 	struct probe_arg *arg; | ||||||
|  | 	int i, ret = 0; | ||||||
|  | 	u32 len; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < tp->nr_args; i++) { | ||||||
|  | 		arg = tp->args + i; | ||||||
|  | 		if (unlikely(arg->dynamic)) { | ||||||
|  | 			process_fetch_insn(arg->code, regs, &len, true); | ||||||
|  | 			ret += len; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Store the value of each argument */ | ||||||
|  | static nokprobe_inline void | ||||||
|  | store_trace_args(int ent_size, struct trace_probe *tp, struct pt_regs *regs, | ||||||
|  | 		 u8 *data, int maxlen) | ||||||
|  | { | ||||||
|  | 	struct probe_arg *arg; | ||||||
|  | 	u32 end = tp->size; | ||||||
|  | 	u32 *dl;	/* Data (relative) location */ | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < tp->nr_args; i++) { | ||||||
|  | 		arg = tp->args + i; | ||||||
|  | 		if (unlikely(arg->dynamic)) { | ||||||
|  | 			/*
 | ||||||
|  | 			 * First, we set the relative location and | ||||||
|  | 			 * maximum data length to *dl | ||||||
|  | 			 */ | ||||||
|  | 			dl = (u32 *)(data + arg->offset); | ||||||
|  | 			*dl = make_data_rloc(maxlen, end - arg->offset); | ||||||
|  | 			/* Then try to fetch string or dynamic array data */ | ||||||
|  | 			process_fetch_insn(arg->code, regs, dl, false); | ||||||
|  | 			/* Reduce maximum length */ | ||||||
|  | 			end += get_rloc_len(*dl); | ||||||
|  | 			maxlen -= get_rloc_len(*dl); | ||||||
|  | 			/* Trick here, convert data_rloc to data_loc */ | ||||||
|  | 			*dl = convert_rloc_to_loc(*dl, ent_size + arg->offset); | ||||||
|  | 		} else | ||||||
|  | 			/* Just fetching data normally */ | ||||||
|  | 			process_fetch_insn(arg->code, regs, data + arg->offset, | ||||||
|  | 					   false); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline int | ||||||
|  | print_probe_args(struct trace_seq *s, struct probe_arg *args, int nr_args, | ||||||
|  | 		 u8 *data, void *field) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < nr_args; i++) { | ||||||
|  | 		trace_seq_printf(s, " %s=", args[i].name); | ||||||
|  | 		if (!args[i].type->print(s, data + args[i].offset, field)) | ||||||
|  | 			return -ENOMEM; | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | @ -15,6 +15,7 @@ | ||||||
| #include <linux/rculist.h> | #include <linux/rculist.h> | ||||||
| 
 | 
 | ||||||
| #include "trace_probe.h" | #include "trace_probe.h" | ||||||
|  | #include "trace_probe_tmpl.h" | ||||||
| 
 | 
 | ||||||
| #define UPROBE_EVENT_SYSTEM	"uprobes" | #define UPROBE_EVENT_SYSTEM	"uprobes" | ||||||
| 
 | 
 | ||||||
|  | @ -99,37 +100,19 @@ static unsigned long get_user_stack_nth(struct pt_regs *regs, unsigned int n) | ||||||
| /*
 | /*
 | ||||||
|  * Uprobes-specific fetch functions |  * Uprobes-specific fetch functions | ||||||
|  */ |  */ | ||||||
| #define DEFINE_FETCH_stack(type)					\ | static nokprobe_inline int | ||||||
| static void FETCH_FUNC_NAME(stack, type)(struct pt_regs *regs,		\ | probe_user_read(void *dest, void *src, size_t size) | ||||||
| 					 void *offset, void *dest)	\ | { | ||||||
| {									\ | 	void __user *vaddr = (void __force __user *)src; | ||||||
| 	*(type *)dest = (type)get_user_stack_nth(regs,			\ |  | ||||||
| 					      ((unsigned long)offset)); \ |  | ||||||
| } |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(stack) |  | ||||||
| /* No string on the stack entry */ |  | ||||||
| #define fetch_stack_string	NULL |  | ||||||
| #define fetch_stack_string_size	NULL |  | ||||||
| 
 | 
 | ||||||
| #define DEFINE_FETCH_memory(type)					\ | 	return copy_from_user(dest, vaddr, size); | ||||||
| static void FETCH_FUNC_NAME(memory, type)(struct pt_regs *regs,		\ |  | ||||||
| 					  void *addr, void *dest)	\ |  | ||||||
| {									\ |  | ||||||
| 	type retval;							\ |  | ||||||
| 	void __user *vaddr = (void __force __user *) addr;		\ |  | ||||||
| 									\ |  | ||||||
| 	if (copy_from_user(&retval, vaddr, sizeof(type)))		\ |  | ||||||
| 		*(type *)dest = 0;					\ |  | ||||||
| 	else								\ |  | ||||||
| 		*(type *) dest = retval;				\ |  | ||||||
| } | } | ||||||
| DEFINE_BASIC_FETCH_FUNCS(memory) |  | ||||||
| /*
 | /*
 | ||||||
|  * Fetch a null-terminated string. Caller MUST set *(u32 *)dest with max |  * Fetch a null-terminated string. Caller MUST set *(u32 *)dest with max | ||||||
|  * length and relative data location. |  * length and relative data location. | ||||||
|  */ |  */ | ||||||
| static void FETCH_FUNC_NAME(memory, string)(struct pt_regs *regs, | static nokprobe_inline void | ||||||
| 					    void *addr, void *dest) | fetch_store_string(unsigned long addr, void *dest) | ||||||
| { | { | ||||||
| 	long ret; | 	long ret; | ||||||
| 	u32 rloc = *(u32 *)dest; | 	u32 rloc = *(u32 *)dest; | ||||||
|  | @ -152,8 +135,9 @@ static void FETCH_FUNC_NAME(memory, string)(struct pt_regs *regs, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void FETCH_FUNC_NAME(memory, string_size)(struct pt_regs *regs, | /* Return the length of string -- including null terminal byte */ | ||||||
| 						 void *addr, void *dest) | static nokprobe_inline void | ||||||
|  | fetch_store_strlen(unsigned long addr, void *dest) | ||||||
| { | { | ||||||
| 	int len; | 	int len; | ||||||
| 	void __user *vaddr = (void __force __user *) addr; | 	void __user *vaddr = (void __force __user *) addr; | ||||||
|  | @ -166,7 +150,7 @@ static void FETCH_FUNC_NAME(memory, string_size)(struct pt_regs *regs, | ||||||
| 		*(u32 *)dest = len; | 		*(u32 *)dest = len; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static unsigned long translate_user_vaddr(void *file_offset) | static unsigned long translate_user_vaddr(unsigned long file_offset) | ||||||
| { | { | ||||||
| 	unsigned long base_addr; | 	unsigned long base_addr; | ||||||
| 	struct uprobe_dispatch_data *udd; | 	struct uprobe_dispatch_data *udd; | ||||||
|  | @ -174,21 +158,9 @@ static unsigned long translate_user_vaddr(void *file_offset) | ||||||
| 	udd = (void *) current->utask->vaddr; | 	udd = (void *) current->utask->vaddr; | ||||||
| 
 | 
 | ||||||
| 	base_addr = udd->bp_addr - udd->tu->offset; | 	base_addr = udd->bp_addr - udd->tu->offset; | ||||||
| 	return base_addr + (unsigned long)file_offset; | 	return base_addr + file_offset; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #define DEFINE_FETCH_file_offset(type)					\ |  | ||||||
| static void FETCH_FUNC_NAME(file_offset, type)(struct pt_regs *regs,	\ |  | ||||||
| 					       void *offset, void *dest)\ |  | ||||||
| {									\ |  | ||||||
| 	void *vaddr = (void *)translate_user_vaddr(offset);		\ |  | ||||||
| 									\ |  | ||||||
| 	FETCH_FUNC_NAME(memory, type)(regs, vaddr, dest);		\ |  | ||||||
| } |  | ||||||
| DEFINE_BASIC_FETCH_FUNCS(file_offset) |  | ||||||
| DEFINE_FETCH_file_offset(string) |  | ||||||
| DEFINE_FETCH_file_offset(string_size) |  | ||||||
| 
 |  | ||||||
| /* Fetch type information table */ | /* Fetch type information table */ | ||||||
| static const struct fetch_type uprobes_fetch_type_table[] = { | static const struct fetch_type uprobes_fetch_type_table[] = { | ||||||
| 	/* Special types */ | 	/* Special types */ | ||||||
|  | @ -213,6 +185,77 @@ static const struct fetch_type uprobes_fetch_type_table[] = { | ||||||
| 	ASSIGN_FETCH_TYPE_END | 	ASSIGN_FETCH_TYPE_END | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* Note that we don't verify it, since the code does not come from user space */ | ||||||
|  | static int | ||||||
|  | process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, void *dest, | ||||||
|  | 		   bool pre) | ||||||
|  | { | ||||||
|  | 	unsigned long val; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	/* 1st stage: get value from context */ | ||||||
|  | 	switch (code->op) { | ||||||
|  | 	case FETCH_OP_REG: | ||||||
|  | 		val = regs_get_register(regs, code->param); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_STACK: | ||||||
|  | 		val = get_user_stack_nth(regs, code->param); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_STACKP: | ||||||
|  | 		val = user_stack_pointer(regs); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_RETVAL: | ||||||
|  | 		val = regs_return_value(regs); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_IMM: | ||||||
|  | 		val = code->immediate; | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_FOFFS: | ||||||
|  | 		val = translate_user_vaddr(code->immediate); | ||||||
|  | 		break; | ||||||
|  | 	default: | ||||||
|  | 		return -EILSEQ; | ||||||
|  | 	} | ||||||
|  | 	code++; | ||||||
|  | 
 | ||||||
|  | 	/* 2nd stage: dereference memory if needed */ | ||||||
|  | 	while (code->op == FETCH_OP_DEREF) { | ||||||
|  | 		ret = probe_user_read(&val, (void *)val + code->offset, | ||||||
|  | 				      sizeof(val)); | ||||||
|  | 		if (ret) | ||||||
|  | 			return ret; | ||||||
|  | 		code++; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* 3rd stage: store value to buffer */ | ||||||
|  | 	switch (code->op) { | ||||||
|  | 	case FETCH_OP_ST_RAW: | ||||||
|  | 		fetch_store_raw(val, code, dest); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_ST_MEM: | ||||||
|  | 		probe_user_read(dest, (void *)val + code->offset, code->size); | ||||||
|  | 		break; | ||||||
|  | 	case FETCH_OP_ST_STRING: | ||||||
|  | 		if (pre) | ||||||
|  | 			fetch_store_strlen(val + code->offset, dest); | ||||||
|  | 		else | ||||||
|  | 			fetch_store_string(val + code->offset, dest); | ||||||
|  | 		break; | ||||||
|  | 	default: | ||||||
|  | 		return -EILSEQ; | ||||||
|  | 	} | ||||||
|  | 	code++; | ||||||
|  | 
 | ||||||
|  | 	/* 4th stage: modify stored value if needed */ | ||||||
|  | 	if (code->op == FETCH_OP_MOD_BF) { | ||||||
|  | 		fetch_apply_bitfield(code, dest); | ||||||
|  | 		code++; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return code->op == FETCH_OP_END ? 0 : -EILSEQ; | ||||||
|  | } | ||||||
|  | NOKPROBE_SYMBOL(process_fetch_insn) | ||||||
|  | 
 | ||||||
| static inline void init_trace_uprobe_filter(struct trace_uprobe_filter *filter) | static inline void init_trace_uprobe_filter(struct trace_uprobe_filter *filter) | ||||||
| { | { | ||||||
| 	rwlock_init(&filter->rwlock); | 	rwlock_init(&filter->rwlock); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Masami Hiramatsu
						Masami Hiramatsu