| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | /* 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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  |  * These functions must be defined for each callsite. | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  |  * Return consumed dynamic data size (>= 0), or error (< 0). | 
					
						
							|  |  |  |  * If dest is NULL, don't store result and return required dynamic data size. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | static int | 
					
						
							|  |  |  | process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | 		   void *dest, void *base); | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | static nokprobe_inline int fetch_store_strlen(unsigned long addr); | 
					
						
							|  |  |  | static nokprobe_inline int | 
					
						
							|  |  |  | fetch_store_string(unsigned long addr, void *dest, void *base); | 
					
						
							| 
									
										
										
										
											2019-05-15 14:38:30 +09:00
										 |  |  | static nokprobe_inline int fetch_store_strlen_user(unsigned long addr); | 
					
						
							|  |  |  | static nokprobe_inline int | 
					
						
							|  |  |  | fetch_store_string_user(unsigned long addr, void *dest, void *base); | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | static nokprobe_inline int | 
					
						
							|  |  |  | probe_mem_read(void *dest, void *src, size_t size); | 
					
						
							| 
									
										
										
										
											2019-05-15 14:38:42 +09:00
										 |  |  | static nokprobe_inline int | 
					
						
							|  |  |  | probe_mem_read_user(void *dest, void *src, size_t size); | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | /* From the 2nd stage, routine is same */ | 
					
						
							|  |  |  | static nokprobe_inline int | 
					
						
							|  |  |  | process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val, | 
					
						
							|  |  |  | 			   void *dest, void *base) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 	struct fetch_insn *s3 = NULL; | 
					
						
							|  |  |  | 	int total = 0, ret = 0, i = 0; | 
					
						
							|  |  |  | 	u32 loc = 0; | 
					
						
							|  |  |  | 	unsigned long lval = val; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | stage2: | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 	/* 2nd stage: dereference memory if needed */ | 
					
						
							| 
									
										
										
										
											2019-05-15 14:38:42 +09:00
										 |  |  | 	do { | 
					
						
							|  |  |  | 		if (code->op == FETCH_OP_DEREF) { | 
					
						
							|  |  |  | 			lval = val; | 
					
						
							|  |  |  | 			ret = probe_mem_read(&val, (void *)val + code->offset, | 
					
						
							|  |  |  | 					     sizeof(val)); | 
					
						
							|  |  |  | 		} else if (code->op == FETCH_OP_UDEREF) { | 
					
						
							|  |  |  | 			lval = val; | 
					
						
							|  |  |  | 			ret = probe_mem_read_user(&val, | 
					
						
							|  |  |  | 				 (void *)val + code->offset, sizeof(val)); | 
					
						
							|  |  |  | 		} else | 
					
						
							|  |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 		if (ret) | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 		code++; | 
					
						
							| 
									
										
										
										
											2019-05-15 14:38:42 +09:00
										 |  |  | 	} while (1); | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 	s3 = code; | 
					
						
							|  |  |  | stage3: | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 	/* 3rd stage: store value to buffer */ | 
					
						
							|  |  |  | 	if (unlikely(!dest)) { | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 		if (code->op == FETCH_OP_ST_STRING) { | 
					
						
							| 
									
										
										
										
											2019-05-07 22:55:52 +09:00
										 |  |  | 			ret = fetch_store_strlen(val + code->offset); | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 			code++; | 
					
						
							|  |  |  | 			goto array; | 
					
						
							| 
									
										
										
										
											2019-05-15 14:38:30 +09:00
										 |  |  | 		} else if (code->op == FETCH_OP_ST_USTRING) { | 
					
						
							|  |  |  | 			ret += fetch_store_strlen_user(val + code->offset); | 
					
						
							|  |  |  | 			code++; | 
					
						
							|  |  |  | 			goto array; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 		} else | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 			return -EILSEQ; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (code->op) { | 
					
						
							|  |  |  | 	case FETCH_OP_ST_RAW: | 
					
						
							|  |  |  | 		fetch_store_raw(val, code, dest); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case FETCH_OP_ST_MEM: | 
					
						
							|  |  |  | 		probe_mem_read(dest, (void *)val + code->offset, code->size); | 
					
						
							|  |  |  | 		break; | 
					
						
							| 
									
										
										
										
											2019-05-15 14:38:42 +09:00
										 |  |  | 	case FETCH_OP_ST_UMEM: | 
					
						
							|  |  |  | 		probe_mem_read_user(dest, (void *)val + code->offset, code->size); | 
					
						
							|  |  |  | 		break; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 	case FETCH_OP_ST_STRING: | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 		loc = *(u32 *)dest; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 		ret = fetch_store_string(val + code->offset, dest, base); | 
					
						
							|  |  |  | 		break; | 
					
						
							| 
									
										
										
										
											2019-05-15 14:38:30 +09:00
										 |  |  | 	case FETCH_OP_ST_USTRING: | 
					
						
							|  |  |  | 		loc = *(u32 *)dest; | 
					
						
							|  |  |  | 		ret = fetch_store_string_user(val + code->offset, dest, base); | 
					
						
							|  |  |  | 		break; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		return -EILSEQ; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	code++; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* 4th stage: modify stored value if needed */ | 
					
						
							|  |  |  | 	if (code->op == FETCH_OP_MOD_BF) { | 
					
						
							|  |  |  | 		fetch_apply_bitfield(code, dest); | 
					
						
							|  |  |  | 		code++; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | array: | 
					
						
							|  |  |  | 	/* the last stage: Loop on array */ | 
					
						
							|  |  |  | 	if (code->op == FETCH_OP_LP_ARRAY) { | 
					
						
							|  |  |  | 		total += ret; | 
					
						
							|  |  |  | 		if (++i < code->param) { | 
					
						
							|  |  |  | 			code = s3; | 
					
						
							| 
									
										
										
										
											2019-05-15 14:38:30 +09:00
										 |  |  | 			if (s3->op != FETCH_OP_ST_STRING && | 
					
						
							|  |  |  | 			    s3->op != FETCH_OP_ST_USTRING) { | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 				dest += s3->size; | 
					
						
							|  |  |  | 				val += s3->size; | 
					
						
							|  |  |  | 				goto stage3; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			code--; | 
					
						
							|  |  |  | 			val = lval + sizeof(char *); | 
					
						
							|  |  |  | 			if (dest) { | 
					
						
							|  |  |  | 				dest += sizeof(u32); | 
					
						
							|  |  |  | 				*(u32 *)dest = update_data_loc(loc, ret); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			goto stage2; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		code++; | 
					
						
							|  |  |  | 		ret = total; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:59 +09:00
										 |  |  | 	return code->op == FETCH_OP_END ? ret : -EILSEQ; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | /* 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; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | 	int i, len, ret = 0; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < tp->nr_args; i++) { | 
					
						
							|  |  |  | 		arg = tp->args + i; | 
					
						
							|  |  |  | 		if (unlikely(arg->dynamic)) { | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | 			len = process_fetch_insn(arg->code, regs, NULL, NULL); | 
					
						
							|  |  |  | 			if (len > 0) | 
					
						
							|  |  |  | 				ret += len; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Store the value of each argument */ | 
					
						
							|  |  |  | static nokprobe_inline void | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | store_trace_args(void *data, struct trace_probe *tp, struct pt_regs *regs, | 
					
						
							|  |  |  | 		 int header_size, int maxlen) | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct probe_arg *arg; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | 	void *base = data - header_size; | 
					
						
							|  |  |  | 	void *dyndata = data + tp->size; | 
					
						
							|  |  |  | 	u32 *dl;	/* Data location */ | 
					
						
							|  |  |  | 	int ret, i; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < tp->nr_args; i++) { | 
					
						
							|  |  |  | 		arg = tp->args + i; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | 		dl = data + arg->offset; | 
					
						
							|  |  |  | 		/* Point the dynamic data area if needed */ | 
					
						
							|  |  |  | 		if (unlikely(arg->dynamic)) | 
					
						
							|  |  |  | 			*dl = make_data_loc(maxlen, dyndata - base); | 
					
						
							|  |  |  | 		ret = process_fetch_insn(arg->code, regs, dl, base); | 
					
						
							| 
									
										
										
										
											2019-02-06 20:00:13 +01:00
										 |  |  | 		if (unlikely(ret < 0 && arg->dynamic)) { | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | 			*dl = make_data_loc(0, dyndata - base); | 
					
						
							| 
									
										
										
										
											2019-02-06 20:00:13 +01:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2018-04-25 21:19:01 +09:00
										 |  |  | 			dyndata += ret; | 
					
						
							| 
									
										
										
										
											2019-02-06 20:00:13 +01:00
										 |  |  | 			maxlen -= ret; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline int | 
					
						
							|  |  |  | print_probe_args(struct trace_seq *s, struct probe_arg *args, int nr_args, | 
					
						
							|  |  |  | 		 u8 *data, void *field) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 	void *p; | 
					
						
							|  |  |  | 	int i, j; | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < nr_args; i++) { | 
					
						
							| 
									
										
										
										
											2018-04-25 21:21:55 +09:00
										 |  |  | 		struct probe_arg *a = args + i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		trace_seq_printf(s, " %s=", a->name); | 
					
						
							|  |  |  | 		if (likely(!a->count)) { | 
					
						
							|  |  |  | 			if (!a->type->print(s, data + a->offset, field)) | 
					
						
							|  |  |  | 				return -ENOMEM; | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		trace_seq_putc(s, '{'); | 
					
						
							|  |  |  | 		p = data + a->offset; | 
					
						
							|  |  |  | 		for (j = 0; j < a->count; j++) { | 
					
						
							|  |  |  | 			if (!a->type->print(s, p, field)) | 
					
						
							|  |  |  | 				return -ENOMEM; | 
					
						
							|  |  |  | 			trace_seq_putc(s, j == a->count - 1 ? '}' : ','); | 
					
						
							|  |  |  | 			p += a->type->size; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-04-25 21:18:03 +09:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } |