tracing/probe: Support user-space dereference
Support user-space dereference syntax for probe event arguments to dereference the data-structure or array in user-space. The syntax is just adding 'u' before an offset value. +|-u<OFFSET>(<FETCHARG>) e.g. +u8(%ax), +u0(+0(%si)) For example, if you probe do_sched_setscheduler(pid, policy, param) and record param->sched_priority, you can add new probe as below; p do_sched_setscheduler priority=+u0($arg3) Note that kprobe event provides this and it doesn't change the dereference method automatically because we do not know whether the given address is in userspace or kernel on some archs. So as same as "ustring", this is an option for user, who has to carefully choose the dereference method. Link: http://lkml.kernel.org/r/155789872187.26965.4468456816590888687.stgit@devnote2 Acked-by: Ingo Molnar <mingo@kernel.org> Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org> Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
This commit is contained in:

committed by
Steven Rostedt (VMware)

parent
88903c4643
commit
e65f7ae7f4
@@ -4842,10 +4842,11 @@ static const char readme_msg[] =
|
||||
"\t args: <name>=fetcharg[:type]\n"
|
||||
"\t fetcharg: %<register>, @<address>, @<symbol>[+|-<offset>],\n"
|
||||
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
|
||||
"\t $stack<index>, $stack, $retval, $comm, $arg<N>\n"
|
||||
"\t $stack<index>, $stack, $retval, $comm, $arg<N>,\n"
|
||||
#else
|
||||
"\t $stack<index>, $stack, $retval, $comm\n"
|
||||
"\t $stack<index>, $stack, $retval, $comm,\n"
|
||||
#endif
|
||||
"\t +|-[u]<offset>(<fetcharg>)\n"
|
||||
"\t type: s8/16/32/64, u8/16/32/64, x8/16/32/64, string, symbol,\n"
|
||||
"\t b<bit-width>@<bit-offset>/<container-size>, ustring,\n"
|
||||
"\t <type>\\[<array-size>\\]\n"
|
||||
|
@@ -952,6 +952,12 @@ probe_mem_read(void *dest, void *src, size_t size)
|
||||
return probe_kernel_read(dest, src, size);
|
||||
}
|
||||
|
||||
static nokprobe_inline int
|
||||
probe_mem_read_user(void *dest, void *src, size_t size)
|
||||
{
|
||||
return probe_user_read(dest, src, size);
|
||||
}
|
||||
|
||||
/* 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,
|
||||
|
@@ -324,6 +324,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
{
|
||||
struct fetch_insn *code = *pcode;
|
||||
unsigned long param;
|
||||
int deref = FETCH_OP_DEREF;
|
||||
long offset = 0;
|
||||
char *tmp;
|
||||
int ret = 0;
|
||||
@@ -396,9 +397,14 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
break;
|
||||
|
||||
case '+': /* deref memory */
|
||||
arg++; /* Skip '+', because kstrtol() rejects it. */
|
||||
/* fall through */
|
||||
case '-':
|
||||
if (arg[1] == 'u') {
|
||||
deref = FETCH_OP_UDEREF;
|
||||
arg[1] = arg[0];
|
||||
arg++;
|
||||
}
|
||||
if (arg[0] == '+')
|
||||
arg++; /* Skip '+', because kstrtol() rejects it. */
|
||||
tmp = strchr(arg, '(');
|
||||
if (!tmp) {
|
||||
trace_probe_log_err(offs, DEREF_NEED_BRACE);
|
||||
@@ -434,7 +440,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
|
||||
}
|
||||
*pcode = code;
|
||||
|
||||
code->op = FETCH_OP_DEREF;
|
||||
code->op = deref;
|
||||
code->offset = offset;
|
||||
}
|
||||
break;
|
||||
@@ -573,14 +579,15 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size,
|
||||
/* Store operation */
|
||||
if (!strcmp(parg->type->name, "string") ||
|
||||
!strcmp(parg->type->name, "ustring")) {
|
||||
if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_IMM &&
|
||||
code->op != FETCH_OP_COMM) {
|
||||
if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF &&
|
||||
code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM) {
|
||||
trace_probe_log_err(offset + (t ? (t - arg) : 0),
|
||||
BAD_STRING);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
if (code->op != FETCH_OP_DEREF || parg->count) {
|
||||
if ((code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM) ||
|
||||
parg->count) {
|
||||
/*
|
||||
* IMM and COMM is pointing actual address, those must
|
||||
* be kept, and if parg->count != 0, this is an array
|
||||
@@ -594,7 +601,8 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size,
|
||||
}
|
||||
}
|
||||
/* If op == DEREF, replace it with STRING */
|
||||
if (!strcmp(parg->type->name, "ustring"))
|
||||
if (!strcmp(parg->type->name, "ustring") ||
|
||||
code->op == FETCH_OP_UDEREF)
|
||||
code->op = FETCH_OP_ST_USTRING;
|
||||
else
|
||||
code->op = FETCH_OP_ST_STRING;
|
||||
@@ -603,6 +611,9 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size,
|
||||
} else if (code->op == FETCH_OP_DEREF) {
|
||||
code->op = FETCH_OP_ST_MEM;
|
||||
code->size = parg->type->size;
|
||||
} else if (code->op == FETCH_OP_UDEREF) {
|
||||
code->op = FETCH_OP_ST_UMEM;
|
||||
code->size = parg->type->size;
|
||||
} else {
|
||||
code++;
|
||||
if (code->op != FETCH_OP_NOP) {
|
||||
|
@@ -92,9 +92,11 @@ enum fetch_op {
|
||||
FETCH_OP_FOFFS, /* File offset: .immediate */
|
||||
// Stage 2 (dereference) op
|
||||
FETCH_OP_DEREF, /* Dereference: .offset */
|
||||
FETCH_OP_UDEREF, /* User-space Dereference: .offset */
|
||||
// Stage 3 (store) ops
|
||||
FETCH_OP_ST_RAW, /* Raw: .size */
|
||||
FETCH_OP_ST_MEM, /* Mem: .offset, .size */
|
||||
FETCH_OP_ST_UMEM, /* Mem: .offset, .size */
|
||||
FETCH_OP_ST_STRING, /* String: .offset, .size */
|
||||
FETCH_OP_ST_USTRING, /* User String: .offset, .size */
|
||||
// Stage 4 (modify) op
|
||||
|
@@ -64,6 +64,8 @@ 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
|
||||
@@ -77,14 +79,21 @@ process_fetch_insn_bottom(struct fetch_insn *code, unsigned long 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));
|
||||
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:
|
||||
@@ -109,6 +118,9 @@ stage3:
|
||||
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);
|
||||
|
@@ -140,6 +140,13 @@ probe_mem_read(void *dest, void *src, size_t size)
|
||||
|
||||
return copy_from_user(dest, vaddr, size) ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
static nokprobe_inline int
|
||||
probe_mem_read_user(void *dest, void *src, size_t size)
|
||||
{
|
||||
return probe_mem_read(dest, src, size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch a null-terminated string. Caller MUST set *(u32 *)dest with max
|
||||
* length and relative data location.
|
||||
|
Reference in New Issue
Block a user