kernel-fxtec-pro1x/tools/perf/util/probe-event.c
Masami Hiramatsu 2a9c8c3609 perf probe: Add lazy line matching support
Add lazy line matching support for specifying new probes.
This also changes the syntax of perf probe a bit. Now
perf probe accepts one of below probe event definitions.

1) Define event based on function name
 [EVENT=]FUNC[@SRC][:RLN|+OFF|%return|;PTN] [ARG ...]

2) Define event based on source file with line number
 [EVENT=]SRC:ALN [ARG ...]

3) Define event based on source file with lazy pattern
 [EVENT=]SRC;PTN [ARG ...]

- New lazy matching pattern(PTN) follows ';' (semicolon). And it
  must be put the end of the definition.
- So, @SRC is no longer the part which must be put at the end
  of the definition.

Note that ';' (semicolon) can be interpreted as the end of
a command by the shell. This means that you need to quote it.
(anyway you will need to quote the lazy pattern itself too,
because it may contains other sensitive characters, like
'[',']' etc.).

Lazy matching
-------------
The lazy line matching is similar to glob matching except
ignoring spaces in both of pattern and target.

e.g.
'a=*' can matches 'a=b', 'a = b', 'a == b' and so on.

This provides some sort of flexibility and robustness to
probe point definitions against minor code changes.
(for example, actual 10th line of schedule() can be changed
 easily by modifying schedule(), but the same line matching
 'rq=cpu_rq*' may still exist.)

Changes in v3:
 - Cast Dwarf_Addr to uintmax_t for printf-formats.

Changes in v2:
 - Cast Dwarf_Addr to unsigned long long for printf-formats.

Signed-off-by: Masami 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: Peter Zijlstra <peterz@infradead.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: K.Prasad <prasad@linux.vnet.ibm.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ananth N Mavinakayanahalli <ananth@in.ibm.com>
LKML-Reference: <20100225133611.6725.45078.stgit@localhost6.localdomain6>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2010-02-25 17:49:30 +01:00

799 lines
19 KiB
C

/*
* probe-event.c : perf-probe definition to kprobe_events format converter
*
* Written by Masami Hiramatsu <mhiramat@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#define _GNU_SOURCE
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#undef _GNU_SOURCE
#include "event.h"
#include "string.h"
#include "strlist.h"
#include "debug.h"
#include "cache.h"
#include "color.h"
#include "parse-events.h" /* For debugfs_path */
#include "probe-event.h"
#define MAX_CMDLEN 256
#define MAX_PROBE_ARGS 128
#define PERFPROBE_GROUP "probe"
#define semantic_error(msg ...) die("Semantic error :" msg)
/* If there is no space to write, returns -E2BIG. */
static int e_snprintf(char *str, size_t size, const char *format, ...)
__attribute__((format(printf, 3, 4)));
static int e_snprintf(char *str, size_t size, const char *format, ...)
{
int ret;
va_list ap;
va_start(ap, format);
ret = vsnprintf(str, size, format, ap);
va_end(ap);
if (ret >= (int)size)
ret = -E2BIG;
return ret;
}
void parse_line_range_desc(const char *arg, struct line_range *lr)
{
const char *ptr;
char *tmp;
/*
* <Syntax>
* SRC:SLN[+NUM|-ELN]
* FUNC[:SLN[+NUM|-ELN]]
*/
ptr = strchr(arg, ':');
if (ptr) {
lr->start = (unsigned int)strtoul(ptr + 1, &tmp, 0);
if (*tmp == '+')
lr->end = lr->start + (unsigned int)strtoul(tmp + 1,
&tmp, 0);
else if (*tmp == '-')
lr->end = (unsigned int)strtoul(tmp + 1, &tmp, 0);
else
lr->end = 0;
pr_debug("Line range is %u to %u\n", lr->start, lr->end);
if (lr->end && lr->start > lr->end)
semantic_error("Start line must be smaller"
" than end line.");
if (*tmp != '\0')
semantic_error("Tailing with invalid character '%d'.",
*tmp);
tmp = strndup(arg, (ptr - arg));
} else
tmp = strdup(arg);
if (strchr(tmp, '.'))
lr->file = tmp;
else
lr->function = tmp;
}
/* Check the name is good for event/group */
static bool check_event_name(const char *name)
{
if (!isalpha(*name) && *name != '_')
return false;
while (*++name != '\0') {
if (!isalpha(*name) && !isdigit(*name) && *name != '_')
return false;
}
return true;
}
/* Parse probepoint definition. */
static void parse_perf_probe_probepoint(char *arg, struct probe_point *pp)
{
char *ptr, *tmp;
char c, nc = 0;
/*
* <Syntax>
* perf probe [EVENT=]SRC[:LN|;PTN]
* perf probe [EVENT=]FUNC[@SRC][+OFFS|%return|:LN|;PAT]
*
* TODO:Group name support
*/
ptr = strpbrk(arg, ";=@+%");
if (ptr && *ptr == '=') { /* Event name */
*ptr = '\0';
tmp = ptr + 1;
ptr = strchr(arg, ':');
if (ptr) /* Group name is not supported yet. */
semantic_error("Group name is not supported yet.");
if (!check_event_name(arg))
semantic_error("%s is bad for event name -it must "
"follow C symbol-naming rule.", arg);
pp->event = strdup(arg);
arg = tmp;
}
ptr = strpbrk(arg, ";:+@%");
if (ptr) {
nc = *ptr;
*ptr++ = '\0';
}
/* Check arg is function or file and copy it */
if (strchr(arg, '.')) /* File */
pp->file = strdup(arg);
else /* Function */
pp->function = strdup(arg);
DIE_IF(pp->file == NULL && pp->function == NULL);
/* Parse other options */
while (ptr) {
arg = ptr;
c = nc;
if (c == ';') { /* Lazy pattern must be the last part */
pp->lazy_line = strdup(arg);
break;
}
ptr = strpbrk(arg, ";:+@%");
if (ptr) {
nc = *ptr;
*ptr++ = '\0';
}
switch (c) {
case ':': /* Line number */
pp->line = strtoul(arg, &tmp, 0);
if (*tmp != '\0')
semantic_error("There is non-digit char"
" in line number.");
break;
case '+': /* Byte offset from a symbol */
pp->offset = strtoul(arg, &tmp, 0);
if (*tmp != '\0')
semantic_error("There is non-digit character"
" in offset.");
break;
case '@': /* File name */
if (pp->file)
semantic_error("SRC@SRC is not allowed.");
pp->file = strdup(arg);
DIE_IF(pp->file == NULL);
break;
case '%': /* Probe places */
if (strcmp(arg, "return") == 0) {
pp->retprobe = 1;
} else /* Others not supported yet */
semantic_error("%%%s is not supported.", arg);
break;
default:
DIE_IF("Program has a bug.");
break;
}
}
/* Exclusion check */
if (pp->lazy_line && pp->line)
semantic_error("Lazy pattern can't be used with line number.");
if (pp->lazy_line && pp->offset)
semantic_error("Lazy pattern can't be used with offset.");
if (pp->line && pp->offset)
semantic_error("Offset can't be used with line number.");
if (!pp->line && !pp->lazy_line && pp->file && !pp->function)
semantic_error("File always requires line number or "
"lazy pattern.");
if (pp->offset && !pp->function)
semantic_error("Offset requires an entry function.");
if (pp->retprobe && !pp->function)
semantic_error("Return probe requires an entry function.");
if ((pp->offset || pp->line || pp->lazy_line) && pp->retprobe)
semantic_error("Offset/Line/Lazy pattern can't be used with "
"return probe.");
pr_debug("symbol:%s file:%s line:%d offset:%d return:%d lazy:%s\n",
pp->function, pp->file, pp->line, pp->offset, pp->retprobe,
pp->lazy_line);
}
/* Parse perf-probe event definition */
void parse_perf_probe_event(const char *str, struct probe_point *pp,
bool *need_dwarf)
{
char **argv;
int argc, i;
*need_dwarf = false;
argv = argv_split(str, &argc);
if (!argv)
die("argv_split failed.");
if (argc > MAX_PROBE_ARGS + 1)
semantic_error("Too many arguments");
/* Parse probe point */
parse_perf_probe_probepoint(argv[0], pp);
if (pp->file || pp->line)
*need_dwarf = true;
/* Copy arguments and ensure return probe has no C argument */
pp->nr_args = argc - 1;
pp->args = zalloc(sizeof(char *) * pp->nr_args);
for (i = 0; i < pp->nr_args; i++) {
pp->args[i] = strdup(argv[i + 1]);
if (!pp->args[i])
die("Failed to copy argument.");
if (is_c_varname(pp->args[i])) {
if (pp->retprobe)
semantic_error("You can't specify local"
" variable for kretprobe");
*need_dwarf = true;
}
}
argv_free(argv);
}
/* Parse kprobe_events event into struct probe_point */
void parse_trace_kprobe_event(const char *str, struct probe_point *pp)
{
char pr;
char *p;
int ret, i, argc;
char **argv;
pr_debug("Parsing kprobe_events: %s\n", str);
argv = argv_split(str, &argc);
if (!argv)
die("argv_split failed.");
if (argc < 2)
semantic_error("Too less arguments.");
/* Scan event and group name. */
ret = sscanf(argv[0], "%c:%a[^/ \t]/%a[^ \t]",
&pr, (float *)(void *)&pp->group,
(float *)(void *)&pp->event);
if (ret != 3)
semantic_error("Failed to parse event name: %s", argv[0]);
pr_debug("Group:%s Event:%s probe:%c\n", pp->group, pp->event, pr);
pp->retprobe = (pr == 'r');
/* Scan function name and offset */
ret = sscanf(argv[1], "%a[^+]+%d", (float *)(void *)&pp->function,
&pp->offset);
if (ret == 1)
pp->offset = 0;
/* kprobe_events doesn't have this information */
pp->line = 0;
pp->file = NULL;
pp->nr_args = argc - 2;
pp->args = zalloc(sizeof(char *) * pp->nr_args);
for (i = 0; i < pp->nr_args; i++) {
p = strchr(argv[i + 2], '=');
if (p) /* We don't need which register is assigned. */
*p = '\0';
pp->args[i] = strdup(argv[i + 2]);
if (!pp->args[i])
die("Failed to copy argument.");
}
argv_free(argv);
}
/* Synthesize only probe point (not argument) */
int synthesize_perf_probe_point(struct probe_point *pp)
{
char *buf;
char offs[64] = "", line[64] = "";
int ret;
pp->probes[0] = buf = zalloc(MAX_CMDLEN);
if (!buf)
die("Failed to allocate memory by zalloc.");
if (pp->offset) {
ret = e_snprintf(offs, 64, "+%d", pp->offset);
if (ret <= 0)
goto error;
}
if (pp->line) {
ret = e_snprintf(line, 64, ":%d", pp->line);
if (ret <= 0)
goto error;
}
if (pp->function)
ret = e_snprintf(buf, MAX_CMDLEN, "%s%s%s%s", pp->function,
offs, pp->retprobe ? "%return" : "", line);
else
ret = e_snprintf(buf, MAX_CMDLEN, "%s%s", pp->file, line);
if (ret <= 0) {
error:
free(pp->probes[0]);
pp->probes[0] = NULL;
}
return ret;
}
int synthesize_perf_probe_event(struct probe_point *pp)
{
char *buf;
int i, len, ret;
len = synthesize_perf_probe_point(pp);
if (len < 0)
return 0;
buf = pp->probes[0];
for (i = 0; i < pp->nr_args; i++) {
ret = e_snprintf(&buf[len], MAX_CMDLEN - len, " %s",
pp->args[i]);
if (ret <= 0)
goto error;
len += ret;
}
pp->found = 1;
return pp->found;
error:
free(pp->probes[0]);
pp->probes[0] = NULL;
return ret;
}
int synthesize_trace_kprobe_event(struct probe_point *pp)
{
char *buf;
int i, len, ret;
pp->probes[0] = buf = zalloc(MAX_CMDLEN);
if (!buf)
die("Failed to allocate memory by zalloc.");
ret = e_snprintf(buf, MAX_CMDLEN, "%s+%d", pp->function, pp->offset);
if (ret <= 0)
goto error;
len = ret;
for (i = 0; i < pp->nr_args; i++) {
ret = e_snprintf(&buf[len], MAX_CMDLEN - len, " %s",
pp->args[i]);
if (ret <= 0)
goto error;
len += ret;
}
pp->found = 1;
return pp->found;
error:
free(pp->probes[0]);
pp->probes[0] = NULL;
return ret;
}
static int open_kprobe_events(int flags, int mode)
{
char buf[PATH_MAX];
int ret;
ret = e_snprintf(buf, PATH_MAX, "%s/../kprobe_events", debugfs_path);
if (ret < 0)
die("Failed to make kprobe_events path.");
ret = open(buf, flags, mode);
if (ret < 0) {
if (errno == ENOENT)
die("kprobe_events file does not exist -"
" please rebuild with CONFIG_KPROBE_EVENT.");
else
die("Could not open kprobe_events file: %s",
strerror(errno));
}
return ret;
}
/* Get raw string list of current kprobe_events */
static struct strlist *get_trace_kprobe_event_rawlist(int fd)
{
int ret, idx;
FILE *fp;
char buf[MAX_CMDLEN];
char *p;
struct strlist *sl;
sl = strlist__new(true, NULL);
fp = fdopen(dup(fd), "r");
while (!feof(fp)) {
p = fgets(buf, MAX_CMDLEN, fp);
if (!p)
break;
idx = strlen(p) - 1;
if (p[idx] == '\n')
p[idx] = '\0';
ret = strlist__add(sl, buf);
if (ret < 0)
die("strlist__add failed: %s", strerror(-ret));
}
fclose(fp);
return sl;
}
/* Free and zero clear probe_point */
static void clear_probe_point(struct probe_point *pp)
{
int i;
if (pp->event)
free(pp->event);
if (pp->group)
free(pp->group);
if (pp->function)
free(pp->function);
if (pp->file)
free(pp->file);
if (pp->lazy_line)
free(pp->lazy_line);
for (i = 0; i < pp->nr_args; i++)
free(pp->args[i]);
if (pp->args)
free(pp->args);
for (i = 0; i < pp->found; i++)
free(pp->probes[i]);
memset(pp, 0, sizeof(*pp));
}
/* Show an event */
static void show_perf_probe_event(const char *event, const char *place,
struct probe_point *pp)
{
int i, ret;
char buf[128];
ret = e_snprintf(buf, 128, "%s:%s", pp->group, event);
if (ret < 0)
die("Failed to copy event: %s", strerror(-ret));
printf(" %-40s (on %s", buf, place);
if (pp->nr_args > 0) {
printf(" with");
for (i = 0; i < pp->nr_args; i++)
printf(" %s", pp->args[i]);
}
printf(")\n");
}
/* List up current perf-probe events */
void show_perf_probe_events(void)
{
int fd;
struct probe_point pp;
struct strlist *rawlist;
struct str_node *ent;
setup_pager();
fd = open_kprobe_events(O_RDONLY, 0);
rawlist = get_trace_kprobe_event_rawlist(fd);
close(fd);
strlist__for_each(ent, rawlist) {
parse_trace_kprobe_event(ent->s, &pp);
/* Synthesize only event probe point */
synthesize_perf_probe_point(&pp);
/* Show an event */
show_perf_probe_event(pp.event, pp.probes[0], &pp);
clear_probe_point(&pp);
}
strlist__delete(rawlist);
}
/* Get current perf-probe event names */
static struct strlist *get_perf_event_names(int fd, bool include_group)
{
char buf[128];
struct strlist *sl, *rawlist;
struct str_node *ent;
struct probe_point pp;
memset(&pp, 0, sizeof(pp));
rawlist = get_trace_kprobe_event_rawlist(fd);
sl = strlist__new(true, NULL);
strlist__for_each(ent, rawlist) {
parse_trace_kprobe_event(ent->s, &pp);
if (include_group) {
if (e_snprintf(buf, 128, "%s:%s", pp.group,
pp.event) < 0)
die("Failed to copy group:event name.");
strlist__add(sl, buf);
} else
strlist__add(sl, pp.event);
clear_probe_point(&pp);
}
strlist__delete(rawlist);
return sl;
}
static void write_trace_kprobe_event(int fd, const char *buf)
{
int ret;
pr_debug("Writing event: %s\n", buf);
ret = write(fd, buf, strlen(buf));
if (ret <= 0)
die("Failed to write event: %s", strerror(errno));
}
static void get_new_event_name(char *buf, size_t len, const char *base,
struct strlist *namelist, bool allow_suffix)
{
int i, ret;
/* Try no suffix */
ret = e_snprintf(buf, len, "%s", base);
if (ret < 0)
die("snprintf() failed: %s", strerror(-ret));
if (!strlist__has_entry(namelist, buf))
return;
if (!allow_suffix) {
pr_warning("Error: event \"%s\" already exists. "
"(Use -f to force duplicates.)\n", base);
die("Can't add new event.");
}
/* Try to add suffix */
for (i = 1; i < MAX_EVENT_INDEX; i++) {
ret = e_snprintf(buf, len, "%s_%d", base, i);
if (ret < 0)
die("snprintf() failed: %s", strerror(-ret));
if (!strlist__has_entry(namelist, buf))
break;
}
if (i == MAX_EVENT_INDEX)
die("Too many events are on the same function.");
}
void add_trace_kprobe_events(struct probe_point *probes, int nr_probes,
bool force_add)
{
int i, j, fd;
struct probe_point *pp;
char buf[MAX_CMDLEN];
char event[64];
struct strlist *namelist;
bool allow_suffix;
fd = open_kprobe_events(O_RDWR, O_APPEND);
/* Get current event names */
namelist = get_perf_event_names(fd, false);
for (j = 0; j < nr_probes; j++) {
pp = probes + j;
if (!pp->event)
pp->event = strdup(pp->function);
if (!pp->group)
pp->group = strdup(PERFPROBE_GROUP);
DIE_IF(!pp->event || !pp->group);
/* If force_add is true, suffix search is allowed */
allow_suffix = force_add;
for (i = 0; i < pp->found; i++) {
/* Get an unused new event name */
get_new_event_name(event, 64, pp->event, namelist,
allow_suffix);
snprintf(buf, MAX_CMDLEN, "%c:%s/%s %s\n",
pp->retprobe ? 'r' : 'p',
pp->group, event,
pp->probes[i]);
write_trace_kprobe_event(fd, buf);
printf("Added new event:\n");
/* Get the first parameter (probe-point) */
sscanf(pp->probes[i], "%s", buf);
show_perf_probe_event(event, buf, pp);
/* Add added event name to namelist */
strlist__add(namelist, event);
/*
* Probes after the first probe which comes from same
* user input are always allowed to add suffix, because
* there might be several addresses corresponding to
* one code line.
*/
allow_suffix = true;
}
}
/* Show how to use the event. */
printf("\nYou can now use it on all perf tools, such as:\n\n");
printf("\tperf record -e %s:%s -a sleep 1\n\n", PERFPROBE_GROUP, event);
strlist__delete(namelist);
close(fd);
}
static void __del_trace_kprobe_event(int fd, struct str_node *ent)
{
char *p;
char buf[128];
/* Convert from perf-probe event to trace-kprobe event */
if (e_snprintf(buf, 128, "-:%s", ent->s) < 0)
die("Failed to copy event.");
p = strchr(buf + 2, ':');
if (!p)
die("Internal error: %s should have ':' but not.", ent->s);
*p = '/';
write_trace_kprobe_event(fd, buf);
printf("Remove event: %s\n", ent->s);
}
static void del_trace_kprobe_event(int fd, const char *group,
const char *event, struct strlist *namelist)
{
char buf[128];
struct str_node *ent, *n;
int found = 0;
if (e_snprintf(buf, 128, "%s:%s", group, event) < 0)
die("Failed to copy event.");
if (strpbrk(buf, "*?")) { /* Glob-exp */
strlist__for_each_safe(ent, n, namelist)
if (strglobmatch(ent->s, buf)) {
found++;
__del_trace_kprobe_event(fd, ent);
strlist__remove(namelist, ent);
}
} else {
ent = strlist__find(namelist, buf);
if (ent) {
found++;
__del_trace_kprobe_event(fd, ent);
strlist__remove(namelist, ent);
}
}
if (found == 0)
pr_info("Info: event \"%s\" does not exist, could not remove it.\n", buf);
}
void del_trace_kprobe_events(struct strlist *dellist)
{
int fd;
const char *group, *event;
char *p, *str;
struct str_node *ent;
struct strlist *namelist;
fd = open_kprobe_events(O_RDWR, O_APPEND);
/* Get current event names */
namelist = get_perf_event_names(fd, true);
strlist__for_each(ent, dellist) {
str = strdup(ent->s);
if (!str)
die("Failed to copy event.");
pr_debug("Parsing: %s\n", str);
p = strchr(str, ':');
if (p) {
group = str;
*p = '\0';
event = p + 1;
} else {
group = "*";
event = str;
}
pr_debug("Group: %s, Event: %s\n", group, event);
del_trace_kprobe_event(fd, group, event, namelist);
free(str);
}
strlist__delete(namelist);
close(fd);
}
#define LINEBUF_SIZE 256
#define NR_ADDITIONAL_LINES 2
static void show_one_line(FILE *fp, unsigned int l, bool skip, bool show_num)
{
char buf[LINEBUF_SIZE];
const char *color = PERF_COLOR_BLUE;
if (fgets(buf, LINEBUF_SIZE, fp) == NULL)
goto error;
if (!skip) {
if (show_num)
fprintf(stdout, "%7u %s", l, buf);
else
color_fprintf(stdout, color, " %s", buf);
}
while (strlen(buf) == LINEBUF_SIZE - 1 &&
buf[LINEBUF_SIZE - 2] != '\n') {
if (fgets(buf, LINEBUF_SIZE, fp) == NULL)
goto error;
if (!skip) {
if (show_num)
fprintf(stdout, "%s", buf);
else
color_fprintf(stdout, color, "%s", buf);
}
}
return;
error:
if (feof(fp))
die("Source file is shorter than expected.");
else
die("File read error: %s", strerror(errno));
}
void show_line_range(struct line_range *lr)
{
unsigned int l = 1;
struct line_node *ln;
FILE *fp;
setup_pager();
if (lr->function)
fprintf(stdout, "<%s:%d>\n", lr->function,
lr->start - lr->offset);
else
fprintf(stdout, "<%s:%d>\n", lr->file, lr->start);
fp = fopen(lr->path, "r");
if (fp == NULL)
die("Failed to open %s: %s", lr->path, strerror(errno));
/* Skip to starting line number */
while (l < lr->start)
show_one_line(fp, l++, true, false);
list_for_each_entry(ln, &lr->line_list, list) {
while (ln->line > l)
show_one_line(fp, (l++) - lr->offset, false, false);
show_one_line(fp, (l++) - lr->offset, false, true);
}
if (lr->end == INT_MAX)
lr->end = l + NR_ADDITIONAL_LINES;
while (l < lr->end && !feof(fp))
show_one_line(fp, (l++) - lr->offset, false, false);
fclose(fp);
}