Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7df2f329 authored by Masami Hiramatsu's avatar Masami Hiramatsu Committed by Ingo Molnar
Browse files

perf probe: Add data structure member access support



Support accessing members in the data structures. With this,
perf-probe accepts data-structure members(IOW, it now accepts
dot '.' and arrow '->' operators) as probe arguemnts.

e.g.

 ./perf probe --add 'schedule:44 rq->curr'

 ./perf probe --add 'vfs_read file->f_op->read file->f_path.dentry'

Note that '>' can be interpreted as redirection in command-line.

Signed-off-by: default avatarMasami Hiramatsu <mhiramat@redhat.com>
Cc: systemtap <systemtap@sources.redhat.com>
Cc: DLE <dle-develop@lists.sourceforge.net>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
LKML-Reference: <20100316220626.32050.57552.stgit@localhost6.localdomain6>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
parent fb1587d8
Loading
Loading
Loading
Loading
+86 −4
Original line number Original line Diff line number Diff line
@@ -262,6 +262,49 @@ static void parse_perf_probe_point(char *arg, struct perf_probe_event *pev)
		 pp->lazy_line);
		 pp->lazy_line);
}
}


/* Parse perf-probe event argument */
static void parse_perf_probe_arg(const char *str, struct perf_probe_arg *arg)
{
	const char *tmp;
	struct perf_probe_arg_field **fieldp;

	pr_debug("parsing arg: %s into ", str);

	tmp = strpbrk(str, "-.");
	if (!is_c_varname(str) || !tmp) {
		/* A variable, register, symbol or special value */
		arg->name = xstrdup(str);
		pr_debug("%s\n", arg->name);
		return;
	}

	/* Structure fields */
	arg->name = xstrndup(str, tmp - str);
	pr_debug("%s, ", arg->name);
	fieldp = &arg->field;

	do {
		*fieldp = xzalloc(sizeof(struct perf_probe_arg_field));
		if (*tmp == '.') {
			str = tmp + 1;
			(*fieldp)->ref = false;
		} else if (tmp[1] == '>') {
			str = tmp + 2;
			(*fieldp)->ref = true;
		} else
			semantic_error("Argument parse error: %s", str);

		tmp = strpbrk(str, "-.");
		if (tmp) {
			(*fieldp)->name = xstrndup(str, tmp - str);
			pr_debug("%s(%d), ", (*fieldp)->name, (*fieldp)->ref);
			fieldp = &(*fieldp)->next;
		}
	} while (tmp);
	(*fieldp)->name = xstrdup(str);
	pr_debug("%s(%d)\n", (*fieldp)->name, (*fieldp)->ref);
}

/* Parse perf-probe event command */
/* Parse perf-probe event command */
void parse_perf_probe_command(const char *cmd, struct perf_probe_event *pev)
void parse_perf_probe_command(const char *cmd, struct perf_probe_event *pev)
{
{
@@ -281,7 +324,7 @@ void parse_perf_probe_command(const char *cmd, struct perf_probe_event *pev)
	pev->nargs = argc - 1;
	pev->nargs = argc - 1;
	pev->args = xzalloc(sizeof(struct perf_probe_arg) * pev->nargs);
	pev->args = xzalloc(sizeof(struct perf_probe_arg) * pev->nargs);
	for (i = 0; i < pev->nargs; i++) {
	for (i = 0; i < pev->nargs; i++) {
		pev->args[i].name = xstrdup(argv[i + 1]);
		parse_perf_probe_arg(argv[i + 1], &pev->args[i]);
		if (is_c_varname(pev->args[i].name) && pev->point.retprobe)
		if (is_c_varname(pev->args[i].name) && pev->point.retprobe)
			semantic_error("You can't specify local variable for"
			semantic_error("You can't specify local variable for"
				       " kretprobe");
				       " kretprobe");
@@ -353,6 +396,33 @@ void parse_kprobe_trace_command(const char *cmd, struct kprobe_trace_event *tev)
	argv_free(argv);
	argv_free(argv);
}
}


/* Compose only probe arg */
int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, size_t len)
{
	struct perf_probe_arg_field *field = pa->field;
	int ret;
	char *tmp = buf;

	ret = e_snprintf(tmp, len, "%s", pa->name);
	if (ret <= 0)
		goto error;
	tmp += ret;
	len -= ret;

	while (field) {
		ret = e_snprintf(tmp, len, "%s%s", field->ref ? "->" : ".",
				 field->name);
		if (ret <= 0)
			goto error;
		tmp += ret;
		len -= ret;
		field = field->next;
	}
	return tmp - buf;
error:
	die("Failed to synthesize perf probe argument: %s", strerror(-ret));
}

/* Compose only probe point (not argument) */
/* Compose only probe point (not argument) */
static char *synthesize_perf_probe_point(struct perf_probe_point *pp)
static char *synthesize_perf_probe_point(struct perf_probe_point *pp)
{
{
@@ -563,6 +633,7 @@ void convert_to_perf_probe_event(struct kprobe_trace_event *tev,
void clear_perf_probe_event(struct perf_probe_event *pev)
void clear_perf_probe_event(struct perf_probe_event *pev)
{
{
	struct perf_probe_point *pp = &pev->point;
	struct perf_probe_point *pp = &pev->point;
	struct perf_probe_arg_field *field, *next;
	int i;
	int i;


	if (pev->event)
	if (pev->event)
@@ -575,9 +646,18 @@ void clear_perf_probe_event(struct perf_probe_event *pev)
		free(pp->function);
		free(pp->function);
	if (pp->lazy_line)
	if (pp->lazy_line)
		free(pp->lazy_line);
		free(pp->lazy_line);
	for (i = 0; i < pev->nargs; i++)
	for (i = 0; i < pev->nargs; i++) {
		if (pev->args[i].name)
		if (pev->args[i].name)
			free(pev->args[i].name);
			free(pev->args[i].name);
		field = pev->args[i].field;
		while (field) {
			next = field->next;
			if (field->name)
				free(field->name);
			free(field);
			field = next;
		}
	}
	if (pev->args)
	if (pev->args)
		free(pev->args);
		free(pev->args);
	memset(pev, 0, sizeof(*pev));
	memset(pev, 0, sizeof(*pev));
@@ -682,8 +762,10 @@ static void show_perf_probe_event(struct perf_probe_event *pev)


	if (pev->nargs > 0) {
	if (pev->nargs > 0) {
		printf(" with");
		printf(" with");
		for (i = 0; i < pev->nargs; i++)
		for (i = 0; i < pev->nargs; i++) {
			printf(" %s", pev->args[i].name);
			synthesize_perf_probe_arg(&pev->args[i], buf, 128);
			printf(" %s", buf);
		}
	}
	}
	printf(")\n");
	printf(")\n");
	free(place);
	free(place);
+11 −1
Original line number Original line Diff line number Diff line
@@ -45,9 +45,17 @@ struct perf_probe_point {
	bool		retprobe;	/* Return probe flag */
	bool		retprobe;	/* Return probe flag */
};
};


/* Perf probe probing argument field chain */
struct perf_probe_arg_field {
	struct perf_probe_arg_field	*next;	/* Next field */
	char				*name;	/* Name of the field */
	bool				ref;	/* Referencing flag */
};

/* Perf probe probing argument */
/* Perf probe probing argument */
struct perf_probe_arg {
struct perf_probe_arg {
	char				*name;	/* Argument name */
	char				*name;	/* Argument name */
	struct perf_probe_arg_field	*field;	/* Structure fields */
};
};


/* Perf probe probing event (point + arg) */
/* Perf probe probing event (point + arg) */
@@ -86,6 +94,8 @@ extern void parse_kprobe_trace_command(const char *cmd,
/* Events to command string */
/* Events to command string */
extern char *synthesize_perf_probe_command(struct perf_probe_event *pev);
extern char *synthesize_perf_probe_command(struct perf_probe_event *pev);
extern char *synthesize_kprobe_trace_command(struct kprobe_trace_event *tev);
extern char *synthesize_kprobe_trace_command(struct kprobe_trace_event *tev);
extern int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf,
				     size_t len);


/* Check the perf_probe_event needs debuginfo */
/* Check the perf_probe_event needs debuginfo */
extern bool perf_probe_event_need_dwarf(struct perf_probe_event *pev);
extern bool perf_probe_event_need_dwarf(struct perf_probe_event *pev);
+104 −1
Original line number Original line Diff line number Diff line
@@ -206,6 +206,28 @@ static Dwarf_Addr die_get_entrypc(Dwarf_Die *dw_die)
	return epc;
	return epc;
}
}


/* Get type die, but skip qualifiers and typedef */
static Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
{
	Dwarf_Attribute attr;
	int tag;

	do {
		if (dwarf_attr(vr_die, DW_AT_type, &attr) == NULL ||
		    dwarf_formref_die(&attr, die_mem) == NULL)
			return NULL;

		tag = dwarf_tag(die_mem);
		vr_die = die_mem;
	} while (tag == DW_TAG_const_type ||
		 tag == DW_TAG_restrict_type ||
		 tag == DW_TAG_volatile_type ||
		 tag == DW_TAG_shared_type ||
		 tag == DW_TAG_typedef);

	return die_mem;
}

/* Return values for die_find callbacks */
/* Return values for die_find callbacks */
enum {
enum {
	DIE_FIND_CB_FOUND = 0,		/* End of Search */
	DIE_FIND_CB_FOUND = 0,		/* End of Search */
@@ -314,6 +336,25 @@ static Dwarf_Die *die_find_variable(Dwarf_Die *sp_die, const char *name,
			      die_mem);
			      die_mem);
}
}


static int __die_find_member_cb(Dwarf_Die *die_mem, void *data)
{
	const char *name = data;

	if ((dwarf_tag(die_mem) == DW_TAG_member) &&
	    (die_compare_name(die_mem, name) == 0))
		return DIE_FIND_CB_FOUND;

	return DIE_FIND_CB_SIBLING;
}

/* Find a member called 'name' */
static Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
				  Dwarf_Die *die_mem)
{
	return die_find_child(st_die, __die_find_member_cb, (void *)name,
			      die_mem);
}

/*
/*
 * Probe finder related functions
 * Probe finder related functions
 */
 */
@@ -363,6 +404,62 @@ static void convert_location(Dwarf_Op *op, struct probe_finder *pf)
	}
	}
}
}


static void convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
				    struct perf_probe_arg_field *field,
				    struct kprobe_trace_arg_ref **ref_ptr)
{
	struct kprobe_trace_arg_ref *ref = *ref_ptr;
	Dwarf_Attribute attr;
	Dwarf_Die member;
	Dwarf_Die type;
	Dwarf_Word offs;

	pr_debug("converting %s in %s\n", field->name, varname);
	if (die_get_real_type(vr_die, &type) == NULL)
		die("Failed to get a type information of %s.", varname);

	/* Check the pointer and dereference */
	if (dwarf_tag(&type) == DW_TAG_pointer_type) {
		if (!field->ref)
			die("Semantic error: %s must be referred by '->'",
			    field->name);
		/* Get the type pointed by this pointer */
		if (die_get_real_type(&type, &type) == NULL)
			die("Failed to get a type information of %s.", varname);

		ref = xzalloc(sizeof(struct kprobe_trace_arg_ref));
		if (*ref_ptr)
			(*ref_ptr)->next = ref;
		else
			*ref_ptr = ref;
	} else {
		if (field->ref)
			die("Semantic error: %s must be referred by '.'",
			    field->name);
		if (!ref)
			die("Structure on a register is not supported yet.");
	}

	/* Verify it is a data structure  */
	if (dwarf_tag(&type) != DW_TAG_structure_type)
		die("%s is not a data structure.", varname);

	if (die_find_member(&type, field->name, &member) == NULL)
		die("%s(tyep:%s) has no member %s.", varname,
		    dwarf_diename(&type), field->name);

	/* Get the offset of the field */
	if (dwarf_attr(&member, DW_AT_data_member_location, &attr) == NULL ||
	    dwarf_formudata(&attr, &offs) != 0)
		die("Failed to get the offset of %s.", field->name);
	ref->offset += (long)offs;

	/* Converting next field */
	if (field->next)
		convert_variable_fields(&member, field->name, field->next,
					&ref);
}

/* Show a variables in kprobe event format */
/* Show a variables in kprobe event format */
static void convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
static void convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
{
{
@@ -379,6 +476,10 @@ static void convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
		goto error;
		goto error;


	convert_location(expr, pf);
	convert_location(expr, pf);

	if (pf->pvar->field)
		convert_variable_fields(vr_die, pf->pvar->name,
					pf->pvar->field, &pf->tvar->ref);
	/* *expr will be cached in libdw. Don't free it. */
	/* *expr will be cached in libdw. Don't free it. */
	return ;
	return ;
error:
error:
@@ -391,13 +492,15 @@ static void convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
static void find_variable(Dwarf_Die *sp_die, struct probe_finder *pf)
static void find_variable(Dwarf_Die *sp_die, struct probe_finder *pf)
{
{
	Dwarf_Die vr_die;
	Dwarf_Die vr_die;
	char buf[128];


	/* TODO: Support struct members and arrays */
	/* TODO: Support struct members and arrays */
	if (!is_c_varname(pf->pvar->name)) {
	if (!is_c_varname(pf->pvar->name)) {
		/* Copy raw parameters */
		/* Copy raw parameters */
		pf->tvar->value = xstrdup(pf->pvar->name);
		pf->tvar->value = xstrdup(pf->pvar->name);
	} else {
	} else {
		pf->tvar->name = xstrdup(pf->pvar->name);
		synthesize_perf_probe_arg(pf->pvar, buf, 128);
		pf->tvar->name = xstrdup(buf);
		pr_debug("Searching '%s' variable in context.\n",
		pr_debug("Searching '%s' variable in context.\n",
			 pf->pvar->name);
			 pf->pvar->name);
		/* Search child die for local variables and parameters. */
		/* Search child die for local variables and parameters. */