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_probe.h" | ||||
| #include "trace_probe_tmpl.h" | ||||
| 
 | ||||
| #define KPROBE_EVENT_SYSTEM "kprobes" | ||||
| #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, | ||||
| 				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 */ | ||||
| static const struct fetch_type kprobes_fetch_type_table[] = { | ||||
| 	/* Special types */ | ||||
|  | @ -529,7 +376,7 @@ static bool within_notrace_func(struct trace_kprobe *tk) | |||
| /* Internal register function - just handle k*probes and flags */ | ||||
| static int __register_trace_kprobe(struct trace_kprobe *tk) | ||||
| { | ||||
| 	int i, ret; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (trace_probe_is_registered(&tk->tp)) | ||||
| 		return -EINVAL; | ||||
|  | @ -540,9 +387,6 @@ static int __register_trace_kprobe(struct trace_kprobe *tk) | |||
| 		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 */ | ||||
| 	if (trace_probe_is_enabled(&tk->tp)) | ||||
| 		tk->rp.kp.flags &= ~KPROBE_FLAG_DISABLED; | ||||
|  | @ -876,8 +720,8 @@ static int create_trace_kprobe(int argc, char **argv) | |||
| 
 | ||||
| 		/* Parse fetch argument */ | ||||
| 		ret = traceprobe_parse_probe_arg(arg, &tk->tp.size, parg, | ||||
| 						is_return, true, | ||||
| 						kprobes_fetch_type_table); | ||||
| 						 is_return, true, | ||||
| 						 kprobes_fetch_type_table); | ||||
| 		if (ret) { | ||||
| 			pr_info("Parse error at argument[%d]. (%d)\n", i, ret); | ||||
| 			goto error; | ||||
|  | @ -1031,6 +875,133 @@ static const struct file_operations kprobe_profile_ops = { | |||
| 	.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 */ | ||||
| static nokprobe_inline void | ||||
| __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\\\""; | ||||
| 
 | ||||
| #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, | ||||
| 						const struct fetch_type *ftbl) | ||||
| { | ||||
|  | @ -272,37 +104,6 @@ static const struct fetch_type *find_fetch_type(const char *type, | |||
| 	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. */ | ||||
| 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)) | ||||
| 
 | ||||
| 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) | ||||
| { | ||||
| 	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 (is_return) | ||||
| 			f->fn = t->fetch[FETCH_MTD_retval]; | ||||
| 			code->op = FETCH_OP_RETVAL; | ||||
| 		else | ||||
| 			ret = -EINVAL; | ||||
| 	} else if (strncmp(arg, "stack", 5) == 0) { | ||||
| 		if (arg[5] == '\0') { | ||||
| 			if (strcmp(t->name, DEFAULT_FETCH_TYPE_STR)) | ||||
| 				return -EINVAL; | ||||
| 
 | ||||
| 			if (is_kprobe) | ||||
| 				f->fn = fetch_kernel_stack_address; | ||||
| 			else | ||||
| 				f->fn = fetch_user_stack_address; | ||||
| 			code->op = FETCH_OP_STACKP; | ||||
| 		} else if (isdigit(arg[5])) { | ||||
| 			ret = kstrtoul(arg + 5, 10, ¶m); | ||||
| 			if (ret || (is_kprobe && param > PARAM_MAX_STACK)) | ||||
| 				ret = -EINVAL; | ||||
| 			else { | ||||
| 				f->fn = t->fetch[FETCH_MTD_stack]; | ||||
| 				f->data = (void *)param; | ||||
| 				code->op = FETCH_OP_STACK; | ||||
| 				code->param = (unsigned int)param; | ||||
| 			} | ||||
| 		} else | ||||
| 			ret = -EINVAL; | ||||
| 	} else if (strcmp(arg, "comm") == 0) { | ||||
| 		if (strcmp(t->name, "string") != 0 && | ||||
| 		    strcmp(t->name, "string_size") != 0) | ||||
| 			return -EINVAL; | ||||
| 		f->fn = t->fetch[FETCH_MTD_comm]; | ||||
| 		code->op = FETCH_OP_COMM; | ||||
| 	} else | ||||
| 		ret = -EINVAL; | ||||
| 
 | ||||
|  | @ -369,10 +161,13 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t, | |||
| } | ||||
| 
 | ||||
| /* Recursive argument parser */ | ||||
| static int parse_probe_arg(char *arg, const struct fetch_type *t, | ||||
| 		     struct fetch_param *f, bool is_return, bool is_kprobe, | ||||
| 		     const struct fetch_type *ftbl) | ||||
| static int | ||||
| parse_probe_arg(char *arg, const struct fetch_type *type, | ||||
| 		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; | ||||
| 	long offset; | ||||
| 	char *tmp; | ||||
|  | @ -380,14 +175,15 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | |||
| 
 | ||||
| 	switch (arg[0]) { | ||||
| 	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; | ||||
| 
 | ||||
| 	case '%':	/* named register */ | ||||
| 		ret = regs_query_register_offset(arg + 1); | ||||
| 		if (ret >= 0) { | ||||
| 			f->fn = t->fetch[FETCH_MTD_reg]; | ||||
| 			f->data = (void *)(unsigned long)ret; | ||||
| 			code->op = FETCH_OP_REG; | ||||
| 			code->param = (unsigned int)ret; | ||||
| 			ret = 0; | ||||
| 		} | ||||
| 		break; | ||||
|  | @ -397,9 +193,9 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | |||
| 			ret = kstrtoul(arg + 1, 0, ¶m); | ||||
| 			if (ret) | ||||
| 				break; | ||||
| 
 | ||||
| 			f->fn = t->fetch[FETCH_MTD_memory]; | ||||
| 			f->data = (void *)param; | ||||
| 			/* load address */ | ||||
| 			code->op = FETCH_OP_IMM; | ||||
| 			code->immediate = param; | ||||
| 		} else if (arg[1] == '+') { | ||||
| 			/* kprobes don't support file offsets */ | ||||
| 			if (is_kprobe) | ||||
|  | @ -409,8 +205,8 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | |||
| 			if (ret) | ||||
| 				break; | ||||
| 
 | ||||
| 			f->fn = t->fetch[FETCH_MTD_file_offset]; | ||||
| 			f->data = (void *)offset; | ||||
| 			code->op = FETCH_OP_FOFFS; | ||||
| 			code->immediate = (unsigned long)offset;  // imm64?
 | ||||
| 		} else { | ||||
| 			/* uprobes don't support symbols */ | ||||
| 			if (!is_kprobe) | ||||
|  | @ -420,10 +216,19 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | |||
| 			if (ret) | ||||
| 				break; | ||||
| 
 | ||||
| 			f->data = alloc_symbol_cache(arg + 1, offset); | ||||
| 			if (f->data) | ||||
| 				f->fn = t->fetch[FETCH_MTD_symbol]; | ||||
| 			code->op = FETCH_OP_IMM; | ||||
| 			code->immediate = | ||||
| 				(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; | ||||
| 
 | ||||
| 	case '+':	/* deref memory */ | ||||
|  | @ -431,11 +236,10 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | |||
| 	case '-': | ||||
| 		tmp = strchr(arg, '('); | ||||
| 		if (!tmp) | ||||
| 			break; | ||||
| 			return -EINVAL; | ||||
| 
 | ||||
| 		*tmp = '\0'; | ||||
| 		ret = kstrtol(arg, 0, &offset); | ||||
| 
 | ||||
| 		if (ret) | ||||
| 			break; | ||||
| 
 | ||||
|  | @ -443,36 +247,29 @@ static int parse_probe_arg(char *arg, const struct fetch_type *t, | |||
| 		tmp = strrchr(arg, ')'); | ||||
| 
 | ||||
| 		if (tmp) { | ||||
| 			struct deref_fetch_param	*dprm; | ||||
| 			const struct fetch_type		*t2; | ||||
| 			const struct fetch_type *t2; | ||||
| 
 | ||||
| 			t2 = find_fetch_type(NULL, ftbl); | ||||
| 			*tmp = '\0'; | ||||
| 			dprm = kzalloc(sizeof(struct deref_fetch_param), GFP_KERNEL); | ||||
| 
 | ||||
| 			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); | ||||
| 			ret = parse_probe_arg(arg, t2, &code, end, is_return, | ||||
| 					      is_kprobe, ftbl); | ||||
| 			if (ret) | ||||
| 				kfree(dprm); | ||||
| 			else { | ||||
| 				f->fn = t->fetch[FETCH_MTD_deref]; | ||||
| 				f->data = (void *)dprm; | ||||
| 			} | ||||
| 				break; | ||||
| 			if (code->op == FETCH_OP_COMM) | ||||
| 				return -EINVAL; | ||||
| 			if (++code == end) | ||||
| 				return -E2BIG; | ||||
| 			*pcode = code; | ||||
| 
 | ||||
| 			code->op = FETCH_OP_DEREF; | ||||
| 			code->offset = offset; | ||||
| 		} | ||||
| 		break; | ||||
| 	} | ||||
| 	if (!ret && !f->fn) {	/* Parsed, but do not find fetch method */ | ||||
| 		pr_info("%s type has no corresponding fetch method.\n", t->name); | ||||
| 	if (!ret && code->op == FETCH_OP_NOP) { | ||||
| 		/* Parsed, but do not find fetch method */ | ||||
| 		ret = -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	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 */ | ||||
| static int __parse_bitfield_probe_arg(const char *bf, | ||||
| 				      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; | ||||
| 	char *tail; | ||||
| 
 | ||||
| 	if (*bf != 'b') | ||||
| 		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 */ | ||||
| 
 | ||||
| 	if (bw == 0 || *tail != '@') | ||||
|  | @ -507,9 +297,15 @@ static int __parse_bitfield_probe_arg(const char *bf, | |||
| 
 | ||||
| 	if (tail == bf || *tail != '/') | ||||
| 		return -EINVAL; | ||||
| 	code++; | ||||
| 	if (code->op != FETCH_OP_NOP) | ||||
| 		return -E2BIG; | ||||
| 	*pcode = code; | ||||
| 
 | ||||
| 	bprm->hi_shift = BYTES_TO_BITS(t->size) - (bw + bo); | ||||
| 	bprm->low_shift = bprm->hi_shift + bo; | ||||
| 	code->op = FETCH_OP_MOD_BF; | ||||
| 	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; | ||||
| } | ||||
|  | @ -519,6 +315,7 @@ int traceprobe_parse_probe_arg(char *arg, ssize_t *size, | |||
| 		struct probe_arg *parg, bool is_return, bool is_kprobe, | ||||
| 		const struct fetch_type *ftbl) | ||||
| { | ||||
| 	struct fetch_insn *code, *tmp = NULL; | ||||
| 	const char *t; | ||||
| 	int ret; | ||||
| 
 | ||||
|  | @ -549,18 +346,60 @@ int traceprobe_parse_probe_arg(char *arg, ssize_t *size, | |||
| 	} | ||||
| 	parg->offset = *size; | ||||
| 	*size += parg->type->size; | ||||
| 	ret = parse_probe_arg(arg, parg->type, &parg->fetch, is_return, | ||||
| 			      is_kprobe, ftbl); | ||||
| 
 | ||||
| 	if (ret >= 0 && t != NULL) | ||||
| 		ret = __parse_bitfield_probe_arg(t, parg->type, &parg->fetch); | ||||
| 	code = tmp = kzalloc(sizeof(*code) * FETCH_INSN_MAX, GFP_KERNEL); | ||||
| 	if (!code) | ||||
| 		return -ENOMEM; | ||||
| 	code[FETCH_INSN_MAX - 1].op = FETCH_OP_END; | ||||
| 
 | ||||
| 	if (ret >= 0) { | ||||
| 		parg->fetch_size.fn = get_fetch_size_function(parg->type, | ||||
| 							      parg->fetch.fn, | ||||
| 							      ftbl); | ||||
| 		parg->fetch_size.data = parg->fetch.data; | ||||
| 	ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1], | ||||
| 			      is_return, is_kprobe, ftbl); | ||||
| 	if (ret) | ||||
| 		goto fail; | ||||
| 
 | ||||
| 	/* 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; | ||||
| } | ||||
|  | @ -582,25 +421,9 @@ int traceprobe_conflict_field_name(const char *name, | |||
| 	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) | ||||
| { | ||||
| 	if (CHECK_FETCH_FUNCS(bitfield, arg->fetch.fn)) | ||||
| 		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->code); | ||||
| 	kfree(arg->name); | ||||
| 	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); | ||||
| } | ||||
| 
 | ||||
| /* Data fetch function type */ | ||||
| typedef	void (*fetch_func_t)(struct pt_regs *, void *, void *); | ||||
| /* Printing function type */ | ||||
| typedef int (*print_type_func_t)(struct trace_seq *, void *, void *); | ||||
| 
 | ||||
| /* Fetch types */ | ||||
| enum { | ||||
| 	FETCH_MTD_reg = 0, | ||||
| 	FETCH_MTD_stack, | ||||
| 	FETCH_MTD_retval, | ||||
| 	FETCH_MTD_comm, | ||||
| 	FETCH_MTD_memory, | ||||
| 	FETCH_MTD_symbol, | ||||
| 	FETCH_MTD_deref, | ||||
| 	FETCH_MTD_bitfield, | ||||
| 	FETCH_MTD_file_offset, | ||||
| 	FETCH_MTD_END, | ||||
| enum fetch_op { | ||||
| 	FETCH_OP_NOP = 0, | ||||
| 	// Stage 1 (load) ops
 | ||||
| 	FETCH_OP_REG,		/* Register : .param = offset */ | ||||
| 	FETCH_OP_STACK,		/* Stack : .param = index */ | ||||
| 	FETCH_OP_STACKP,	/* Stack pointer */ | ||||
| 	FETCH_OP_RETVAL,	/* Return value */ | ||||
| 	FETCH_OP_IMM,		/* Immediate : .immediate */ | ||||
| 	FETCH_OP_COMM,		/* Current comm */ | ||||
| 	FETCH_OP_FOFFS,		/* File offset: .immediate */ | ||||
| 	// Stage 2 (dereference) op
 | ||||
| 	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 */ | ||||
| struct fetch_type { | ||||
| 	const char		*name;		/* Name of type */ | ||||
|  | @ -106,13 +131,6 @@ struct fetch_type { | |||
| 	print_type_func_t	print;		/* Print functions */ | ||||
| 	const char		*fmt;		/* Fromat string */ | ||||
| 	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 */ | ||||
|  | @ -142,66 +160,12 @@ DECLARE_BASIC_PRINT_TYPE_FUNC(x64); | |||
| 
 | ||||
| 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 */ | ||||
| #define __DEFAULT_FETCH_TYPE(t) x##t | ||||
| #define _DEFAULT_FETCH_TYPE(t) __DEFAULT_FETCH_TYPE(t) | ||||
| #define DEFAULT_FETCH_TYPE _DEFAULT_FETCH_TYPE(BITS_PER_LONG) | ||||
| #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)	\ | ||||
| 	{.name = _name,				\ | ||||
| 	 .size = _size,					\ | ||||
|  | @ -209,17 +173,6 @@ DEFINE_FETCH_##method(u64) | |||
| 	 .print = PRINT_TYPE_FUNC_NAME(ptype),		\ | ||||
| 	 .fmt = PRINT_TYPE_FMT_NAME(ptype),		\ | ||||
| 	 .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)			\ | ||||
|  | @ -231,42 +184,13 @@ ASSIGN_FETCH_FUNC(file_offset, ftype),			\ | |||
| 
 | ||||
| #define ASSIGN_FETCH_TYPE_END {} | ||||
| 
 | ||||
| #define FETCH_TYPE_STRING	0 | ||||
| #define FETCH_TYPE_STRSIZE	1 | ||||
| #define FETCH_TYPE_STRING      0 | ||||
| #define FETCH_TYPE_STRSIZE     1 | ||||
| 
 | ||||
| #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_error_injectable(struct trace_event_call *call); | ||||
| #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) | ||||
| { | ||||
| 	return false; | ||||
|  | @ -279,8 +203,8 @@ static inline bool trace_kprobe_error_injectable(struct trace_event_call *call) | |||
| #endif /* CONFIG_KPROBE_EVENTS */ | ||||
| 
 | ||||
| struct probe_arg { | ||||
| 	struct fetch_param	fetch; | ||||
| 	struct fetch_param	fetch_size; | ||||
| 	struct fetch_insn	*code; | ||||
| 	bool			dynamic;/* Dynamic array (string) is used */ | ||||
| 	unsigned int		offset;	/* Offset from argument entry */ | ||||
| 	const char		*name;	/* Name 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); | ||||
| } | ||||
| 
 | ||||
| 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 */ | ||||
| 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); | ||||
| 
 | ||||
| /* 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); | ||||
| 
 | ||||
| #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 "trace_probe.h" | ||||
| #include "trace_probe_tmpl.h" | ||||
| 
 | ||||
| #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 | ||||
|  */ | ||||
| #define DEFINE_FETCH_stack(type)					\ | ||||
| static void FETCH_FUNC_NAME(stack, type)(struct pt_regs *regs,		\ | ||||
| 					 void *offset, void *dest)	\ | ||||
| {									\ | ||||
| 	*(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 | ||||
| static nokprobe_inline int | ||||
| probe_user_read(void *dest, void *src, size_t size) | ||||
| { | ||||
| 	void __user *vaddr = (void __force __user *)src; | ||||
| 
 | ||||
| #define DEFINE_FETCH_memory(type)					\ | ||||
| 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;				\ | ||||
| 	return copy_from_user(dest, vaddr, size); | ||||
| } | ||||
| 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) | ||||
| static nokprobe_inline void | ||||
| fetch_store_string(unsigned long addr, void *dest) | ||||
| { | ||||
| 	long ret; | ||||
| 	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, | ||||
| 						 void *addr, void *dest) | ||||
| /* Return the length of string -- including null terminal byte */ | ||||
| static nokprobe_inline void | ||||
| fetch_store_strlen(unsigned long addr, void *dest) | ||||
| { | ||||
| 	int len; | ||||
| 	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; | ||||
| } | ||||
| 
 | ||||
| static unsigned long translate_user_vaddr(void *file_offset) | ||||
| static unsigned long translate_user_vaddr(unsigned long file_offset) | ||||
| { | ||||
| 	unsigned long base_addr; | ||||
| 	struct uprobe_dispatch_data *udd; | ||||
|  | @ -174,21 +158,9 @@ static unsigned long translate_user_vaddr(void *file_offset) | |||
| 	udd = (void *) current->utask->vaddr; | ||||
| 
 | ||||
| 	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 */ | ||||
| static const struct fetch_type uprobes_fetch_type_table[] = { | ||||
| 	/* Special types */ | ||||
|  | @ -213,6 +185,77 @@ static const struct fetch_type uprobes_fetch_type_table[] = { | |||
| 	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) | ||||
| { | ||||
| 	rwlock_init(&filter->rwlock); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Masami Hiramatsu
						Masami Hiramatsu