mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	 489fe0096b
			
		
	
	
		489fe0096b
		
	
	
	
	
		
			
			Do not accumulate strlen result on "ret" local variable, because
it is accumulated on "total" local variable for array case.
Link: http://lkml.kernel.org/r/155723735237.9149.3192150444705457531.stgit@devnote2
Fixes: 40b53b7718 ("tracing: probeevent: Add array type support")
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
		
	
			
		
			
				
	
	
		
			218 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * These functions must be defined for each callsite.
 | |
|  * Return consumed dynamic data size (>= 0), or error (< 0).
 | |
|  * If dest is NULL, don't store result and return required dynamic data size.
 | |
|  */
 | |
| static int
 | |
| process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs,
 | |
| 		   void *dest, void *base);
 | |
| static nokprobe_inline int fetch_store_strlen(unsigned long addr);
 | |
| static nokprobe_inline int
 | |
| fetch_store_string(unsigned long addr, void *dest, void *base);
 | |
| static nokprobe_inline int
 | |
| probe_mem_read(void *dest, void *src, size_t size);
 | |
| 
 | |
| /* 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)
 | |
| {
 | |
| 	struct fetch_insn *s3 = NULL;
 | |
| 	int total = 0, ret = 0, i = 0;
 | |
| 	u32 loc = 0;
 | |
| 	unsigned long lval = val;
 | |
| 
 | |
| stage2:
 | |
| 	/* 2nd stage: dereference memory if needed */
 | |
| 	while (code->op == FETCH_OP_DEREF) {
 | |
| 		lval = val;
 | |
| 		ret = probe_mem_read(&val, (void *)val + code->offset,
 | |
| 					sizeof(val));
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 		code++;
 | |
| 	}
 | |
| 
 | |
| 	s3 = code;
 | |
| stage3:
 | |
| 	/* 3rd stage: store value to buffer */
 | |
| 	if (unlikely(!dest)) {
 | |
| 		if (code->op == FETCH_OP_ST_STRING) {
 | |
| 			ret = fetch_store_strlen(val + code->offset);
 | |
| 			code++;
 | |
| 			goto array;
 | |
| 		} else
 | |
| 			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;
 | |
| 	case FETCH_OP_ST_STRING:
 | |
| 		loc = *(u32 *)dest;
 | |
| 		ret = fetch_store_string(val + code->offset, dest, base);
 | |
| 		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++;
 | |
| 	}
 | |
| 
 | |
| array:
 | |
| 	/* the last stage: Loop on array */
 | |
| 	if (code->op == FETCH_OP_LP_ARRAY) {
 | |
| 		total += ret;
 | |
| 		if (++i < code->param) {
 | |
| 			code = s3;
 | |
| 			if (s3->op != FETCH_OP_ST_STRING) {
 | |
| 				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;
 | |
| 	}
 | |
| 
 | |
| 	return code->op == FETCH_OP_END ? ret : -EILSEQ;
 | |
| }
 | |
| 
 | |
| /* 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, len, ret = 0;
 | |
| 
 | |
| 	for (i = 0; i < tp->nr_args; i++) {
 | |
| 		arg = tp->args + i;
 | |
| 		if (unlikely(arg->dynamic)) {
 | |
| 			len = process_fetch_insn(arg->code, regs, NULL, NULL);
 | |
| 			if (len > 0)
 | |
| 				ret += len;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /* Store the value of each argument */
 | |
| static nokprobe_inline void
 | |
| store_trace_args(void *data, struct trace_probe *tp, struct pt_regs *regs,
 | |
| 		 int header_size, int maxlen)
 | |
| {
 | |
| 	struct probe_arg *arg;
 | |
| 	void *base = data - header_size;
 | |
| 	void *dyndata = data + tp->size;
 | |
| 	u32 *dl;	/* Data location */
 | |
| 	int ret, i;
 | |
| 
 | |
| 	for (i = 0; i < tp->nr_args; i++) {
 | |
| 		arg = tp->args + i;
 | |
| 		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);
 | |
| 		if (unlikely(ret < 0 && arg->dynamic)) {
 | |
| 			*dl = make_data_loc(0, dyndata - base);
 | |
| 		} else {
 | |
| 			dyndata += ret;
 | |
| 			maxlen -= ret;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| print_probe_args(struct trace_seq *s, struct probe_arg *args, int nr_args,
 | |
| 		 u8 *data, void *field)
 | |
| {
 | |
| 	void *p;
 | |
| 	int i, j;
 | |
| 
 | |
| 	for (i = 0; i < nr_args; i++) {
 | |
| 		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;
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 |