tracing: probeevent: Add $argN for accessing function args

Add $argN special fetch variable for accessing function
arguments. This allows user to trace the Nth argument easily
at the function entry.

Note that this returns most probably assignment of registers
and stacks. In some case, it may not work well. If you need
to access correct registers or stacks you should use perf-probe.

Link: http://lkml.kernel.org/r/152465888632.26224.3412465701570253696.stgit@devbox

Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
This commit is contained in:
Masami Hiramatsu 2018-04-25 21:21:26 +09:00 коммит произвёл Steven Rostedt (VMware)
Родитель 3c88ee194c
Коммит a1303af5d7
6 изменённых файлов: 55 добавлений и 24 удалений

Просмотреть файл

@ -45,16 +45,18 @@ Synopsis of kprobe_events
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol) @SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol)
$stackN : Fetch Nth entry of stack (N >= 0) $stackN : Fetch Nth entry of stack (N >= 0)
$stack : Fetch stack address. $stack : Fetch stack address.
$retval : Fetch return value.(*) $argN : Fetch the Nth function argument. (N >= 1) (\*1)
$retval : Fetch return value.(\*2)
$comm : Fetch current task comm. $comm : Fetch current task comm.
+|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**) +|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(\*3)
NAME=FETCHARG : Set NAME as the argument name of FETCHARG. NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
(x8/x16/x32/x64), "string" and bitfield are supported. (x8/x16/x32/x64), "string" and bitfield are supported.
(*) only for return probe. (\*1) only for the probe on function entry (offs == 0).
(**) this is useful for fetching a field of data structures. (\*2) only for return probe.
(\*3) this is useful for fetching a field of data structures.
Types Types
----- -----

Просмотреть файл

@ -4625,7 +4625,11 @@ static const char readme_msg[] =
#endif #endif
"\t args: <name>=fetcharg[:type]\n" "\t args: <name>=fetcharg[:type]\n"
"\t fetcharg: %<register>, @<address>, @<symbol>[+|-<offset>],\n" "\t fetcharg: %<register>, @<address>, @<symbol>[+|-<offset>],\n"
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
"\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 type: s8/16/32/64, u8/16/32/64, x8/16/32/64, string, symbol,\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>,\n" "\t b<bit-width>@<bit-offset>/<container-size>,\n"
"\t <type>\\[<array-size>\\]\n" "\t <type>\\[<array-size>\\]\n"

Просмотреть файл

@ -533,13 +533,15 @@ static int create_trace_kprobe(int argc, char **argv)
long offset = 0; long offset = 0;
void *addr = NULL; void *addr = NULL;
char buf[MAX_EVENT_NAME_LEN]; char buf[MAX_EVENT_NAME_LEN];
unsigned int flags = TPARG_FL_KERNEL;
/* argc must be >= 1 */ /* argc must be >= 1 */
if (argv[0][0] == 'p') if (argv[0][0] == 'p')
is_return = false; is_return = false;
else if (argv[0][0] == 'r') else if (argv[0][0] == 'r') {
is_return = true; is_return = true;
else if (argv[0][0] == '-') flags |= TPARG_FL_RETURN;
} else if (argv[0][0] == '-')
is_delete = true; is_delete = true;
else { else {
pr_info("Probe definition must be started with 'p', 'r' or" pr_info("Probe definition must be started with 'p', 'r' or"
@ -625,8 +627,9 @@ static int create_trace_kprobe(int argc, char **argv)
pr_info("Failed to parse either an address or a symbol.\n"); pr_info("Failed to parse either an address or a symbol.\n");
return ret; return ret;
} }
if (offset && is_return && if (kprobe_on_func_entry(NULL, symbol, offset))
!kprobe_on_func_entry(NULL, symbol, offset)) { flags |= TPARG_FL_FENTRY;
if (offset && is_return && !(flags & TPARG_FL_FENTRY)) {
pr_info("Given offset is not valid for return probe.\n"); pr_info("Given offset is not valid for return probe.\n");
return -EINVAL; return -EINVAL;
} }
@ -696,7 +699,7 @@ static int create_trace_kprobe(int argc, char **argv)
/* Parse fetch argument */ /* Parse fetch argument */
ret = traceprobe_parse_probe_arg(arg, &tk->tp.size, parg, ret = traceprobe_parse_probe_arg(arg, &tk->tp.size, parg,
is_return, true); flags);
if (ret) { if (ret) {
pr_info("Parse error at argument[%d]. (%d)\n", i, ret); pr_info("Parse error at argument[%d]. (%d)\n", i, ret);
goto error; goto error;
@ -932,6 +935,11 @@ process_fetch_insn(struct fetch_insn *code, struct pt_regs *regs, void *dest,
case FETCH_OP_COMM: case FETCH_OP_COMM:
val = (unsigned long)current->comm; val = (unsigned long)current->comm;
break; break;
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
case FETCH_OP_ARG:
val = regs_get_kernel_argument(regs, code->param);
break;
#endif
default: default:
return -EILSEQ; return -EILSEQ;
} }

Просмотреть файл

@ -157,14 +157,13 @@ int traceprobe_split_symbol_offset(char *symbol, long *offset)
#define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) #define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long))
static int parse_probe_vars(char *arg, const struct fetch_type *t, static int parse_probe_vars(char *arg, const struct fetch_type *t,
struct fetch_insn *code, bool is_return, struct fetch_insn *code, unsigned int flags)
bool is_kprobe)
{ {
int ret = 0; int ret = 0;
unsigned long param; unsigned long param;
if (strcmp(arg, "retval") == 0) { if (strcmp(arg, "retval") == 0) {
if (is_return) if (flags & TPARG_FL_RETURN)
code->op = FETCH_OP_RETVAL; code->op = FETCH_OP_RETVAL;
else else
ret = -EINVAL; ret = -EINVAL;
@ -173,7 +172,8 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t,
code->op = FETCH_OP_STACKP; code->op = FETCH_OP_STACKP;
} else if (isdigit(arg[5])) { } else if (isdigit(arg[5])) {
ret = kstrtoul(arg + 5, 10, &param); ret = kstrtoul(arg + 5, 10, &param);
if (ret || (is_kprobe && param > PARAM_MAX_STACK)) if (ret || ((flags & TPARG_FL_KERNEL) &&
param > PARAM_MAX_STACK))
ret = -EINVAL; ret = -EINVAL;
else { else {
code->op = FETCH_OP_STACK; code->op = FETCH_OP_STACK;
@ -183,6 +183,18 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t,
ret = -EINVAL; ret = -EINVAL;
} else if (strcmp(arg, "comm") == 0) { } else if (strcmp(arg, "comm") == 0) {
code->op = FETCH_OP_COMM; code->op = FETCH_OP_COMM;
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
} else if (((flags & TPARG_FL_MASK) ==
(TPARG_FL_KERNEL | TPARG_FL_FENTRY)) &&
strncmp(arg, "arg", 3) == 0) {
if (!isdigit(arg[3]))
return -EINVAL;
ret = kstrtoul(arg + 3, 10, &param);
if (ret || !param || param > PARAM_MAX_STACK)
return -EINVAL;
code->op = FETCH_OP_ARG;
code->param = (unsigned int)param - 1;
#endif
} else } else
ret = -EINVAL; ret = -EINVAL;
@ -193,7 +205,7 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t,
static int static int
parse_probe_arg(char *arg, const struct fetch_type *type, parse_probe_arg(char *arg, const struct fetch_type *type,
struct fetch_insn **pcode, struct fetch_insn *end, struct fetch_insn **pcode, struct fetch_insn *end,
bool is_return, bool is_kprobe) unsigned int flags)
{ {
struct fetch_insn *code = *pcode; struct fetch_insn *code = *pcode;
unsigned long param; unsigned long param;
@ -203,8 +215,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
switch (arg[0]) { switch (arg[0]) {
case '$': case '$':
ret = parse_probe_vars(arg + 1, type, code, ret = parse_probe_vars(arg + 1, type, code, flags);
is_return, is_kprobe);
break; break;
case '%': /* named register */ case '%': /* named register */
@ -226,7 +237,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
code->immediate = param; code->immediate = param;
} else if (arg[1] == '+') { } else if (arg[1] == '+') {
/* kprobes don't support file offsets */ /* kprobes don't support file offsets */
if (is_kprobe) if (flags & TPARG_FL_KERNEL)
return -EINVAL; return -EINVAL;
ret = kstrtol(arg + 2, 0, &offset); ret = kstrtol(arg + 2, 0, &offset);
@ -237,7 +248,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
code->immediate = (unsigned long)offset; // imm64? code->immediate = (unsigned long)offset; // imm64?
} else { } else {
/* uprobes don't support symbols */ /* uprobes don't support symbols */
if (!is_kprobe) if (!(flags & TPARG_FL_KERNEL))
return -EINVAL; return -EINVAL;
ret = traceprobe_split_symbol_offset(arg + 1, &offset); ret = traceprobe_split_symbol_offset(arg + 1, &offset);
@ -278,8 +289,7 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
const struct fetch_type *t2 = find_fetch_type(NULL); const struct fetch_type *t2 = find_fetch_type(NULL);
*tmp = '\0'; *tmp = '\0';
ret = parse_probe_arg(arg, t2, &code, end, is_return, ret = parse_probe_arg(arg, t2, &code, end, flags);
is_kprobe);
if (ret) if (ret)
break; break;
if (code->op == FETCH_OP_COMM) if (code->op == FETCH_OP_COMM)
@ -339,7 +349,7 @@ static int __parse_bitfield_probe_arg(const char *bf,
/* String length checking wrapper */ /* String length checking wrapper */
int traceprobe_parse_probe_arg(char *arg, ssize_t *size, int traceprobe_parse_probe_arg(char *arg, ssize_t *size,
struct probe_arg *parg, bool is_return, bool is_kprobe) struct probe_arg *parg, unsigned int flags)
{ {
struct fetch_insn *code, *scode, *tmp = NULL; struct fetch_insn *code, *scode, *tmp = NULL;
char *t, *t2; char *t, *t2;
@ -397,7 +407,7 @@ int traceprobe_parse_probe_arg(char *arg, ssize_t *size,
code[FETCH_INSN_MAX - 1].op = FETCH_OP_END; code[FETCH_INSN_MAX - 1].op = FETCH_OP_END;
ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1], ret = parse_probe_arg(arg, parg->type, &code, &code[FETCH_INSN_MAX - 1],
is_return, is_kprobe); flags);
if (ret) if (ret)
goto fail; goto fail;

Просмотреть файл

@ -23,6 +23,7 @@
#include <linux/stringify.h> #include <linux/stringify.h>
#include <linux/limits.h> #include <linux/limits.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/bitops.h>
#include <asm/bitsperlong.h> #include <asm/bitsperlong.h>
#include "trace.h" #include "trace.h"
@ -86,6 +87,7 @@ enum fetch_op {
FETCH_OP_RETVAL, /* Return value */ FETCH_OP_RETVAL, /* Return value */
FETCH_OP_IMM, /* Immediate : .immediate */ FETCH_OP_IMM, /* Immediate : .immediate */
FETCH_OP_COMM, /* Current comm */ FETCH_OP_COMM, /* Current comm */
FETCH_OP_ARG, /* Function argument : .param */
FETCH_OP_FOFFS, /* File offset: .immediate */ FETCH_OP_FOFFS, /* File offset: .immediate */
// Stage 2 (dereference) op // Stage 2 (dereference) op
FETCH_OP_DEREF, /* Dereference: .offset */ FETCH_OP_DEREF, /* Dereference: .offset */
@ -263,8 +265,13 @@ find_event_file_link(struct trace_probe *tp, struct trace_event_file *file)
return NULL; return NULL;
} }
#define TPARG_FL_RETURN BIT(0)
#define TPARG_FL_KERNEL BIT(1)
#define TPARG_FL_FENTRY BIT(2)
#define TPARG_FL_MASK GENMASK(2, 0)
extern int traceprobe_parse_probe_arg(char *arg, ssize_t *size, extern int traceprobe_parse_probe_arg(char *arg, ssize_t *size,
struct probe_arg *parg, bool is_return, bool is_kprobe); struct probe_arg *parg, unsigned int flags);
extern int traceprobe_conflict_field_name(const char *name, extern int traceprobe_conflict_field_name(const char *name,
struct probe_arg *args, int narg); struct probe_arg *args, int narg);

Просмотреть файл

@ -557,7 +557,7 @@ static int create_trace_uprobe(int argc, char **argv)
/* Parse fetch argument */ /* Parse fetch argument */
ret = traceprobe_parse_probe_arg(arg, &tu->tp.size, parg, ret = traceprobe_parse_probe_arg(arg, &tu->tp.size, parg,
is_return, false); is_return ? TPARG_FL_RETURN : 0);
if (ret) { if (ret) {
pr_info("Parse error at argument[%d]. (%d)\n", i, ret); pr_info("Parse error at argument[%d]. (%d)\n", i, ret);
goto error; goto error;