forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
5.6 KiB
242 lines
5.6 KiB
/* 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 fetch_store_strlen_user(unsigned long addr); |
|
static nokprobe_inline int |
|
fetch_store_string_user(unsigned long addr, void *dest, void *base); |
|
static nokprobe_inline int |
|
probe_mem_read(void *dest, void *src, size_t size); |
|
static nokprobe_inline int |
|
probe_mem_read_user(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 */ |
|
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; |
|
if (ret) |
|
return ret; |
|
code++; |
|
} while (1); |
|
|
|
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 if (code->op == FETCH_OP_ST_USTRING) { |
|
ret += fetch_store_strlen_user(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_UMEM: |
|
probe_mem_read_user(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; |
|
case FETCH_OP_ST_USTRING: |
|
loc = *(u32 *)dest; |
|
ret = fetch_store_string_user(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 && |
|
s3->op != FETCH_OP_ST_USTRING) { |
|
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; |
|
}
|
|
|