perf record: Add support for using symbols in address filters

Symbols come from either the DSO or /proc/kallsyms for the kernel.
Details of the functionality can be found in Documentation/perf-record.txt.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mathieu Poirier <mathieu.poirier@linaro.org>
Link: http://lkml.kernel.org/r/1474641528-18776-8-git-send-email-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Adrian Hunter 2016-09-23 17:38:39 +03:00 committed by Arnaldo Carvalho de Melo
parent cd67f99fe9
commit 1b36c03e35
4 changed files with 857 additions and 3 deletions

View File

@ -89,9 +89,62 @@ OPTIONS
--filter=<filter>::
Event filter. This option should follow a event selector (-e) which
selects tracepoint event(s). Multiple '--filter' options are combined
selects either tracepoint event(s) or a hardware trace PMU
(e.g. Intel PT or CoreSight).
- tracepoint filters
In the case of tracepoints, multiple '--filter' options are combined
using '&&'.
- address filters
A hardware trace PMU advertises its ability to accept a number of
address filters by specifying a non-zero value in
/sys/bus/event_source/devices/<pmu>/nr_addr_filters.
Address filters have the format:
filter|start|stop|tracestop <start> [/ <size>] [@<file name>]
Where:
- 'filter': defines a region that will be traced.
- 'start': defines an address at which tracing will begin.
- 'stop': defines an address at which tracing will stop.
- 'tracestop': defines a region in which tracing will stop.
<file name> is the name of the object file, <start> is the offset to the
code to trace in that file, and <size> is the size of the region to
trace. 'start' and 'stop' filters need not specify a <size>.
If no object file is specified then the kernel is assumed, in which case
the start address must be a current kernel memory address.
<start> can also be specified by providing the name of a symbol. If the
symbol name is not unique, it can be disambiguated by inserting #n where
'n' selects the n'th symbol in address order. Alternately #0, #g or #G
select only a global symbol. <size> can also be specified by providing
the name of a symbol, in which case the size is calculated to the end
of that symbol. For 'filter' and 'tracestop' filters, if <size> is
omitted and <start> is a symbol, then the size is calculated to the end
of that symbol.
If <size> is omitted and <start> is '*', then the start and size will
be calculated from the first and last symbols, i.e. to trace the whole
file.
If symbol names (or '*') are provided, they must be surrounded by white
space.
The filter passed to the kernel is not necessarily the same as entered.
To see the filter that is passed, use the -v option.
The kernel may not be able to configure a trace region if it is not
within a single mapping. MMAP events (or /proc/<pid>/maps) can be
examined to determine if that is a possibility.
Multiple filters can be separated with space or comma.
--exclude-perf::
Don't record events issued by perf itself. This option should follow
a event selector (-e) which selects tracepoint event(s). It adds a

View File

@ -1581,6 +1581,18 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)
if (err)
goto out;
/*
* Allow aliases to facilitate the lookup of symbols for address
* filters. Refer to auxtrace_parse_filters().
*/
symbol_conf.allow_aliases = true;
symbol__init(NULL);
err = auxtrace_parse_filters(rec->evlist);
if (err)
goto out;
if (dry_run)
goto out;
@ -1594,8 +1606,6 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)
err = -ENOMEM;
symbol__init(NULL);
if (symbol_conf.kptr_restrict)
pr_warning(
"WARNING: Kernel address maps (/proc/{kallsyms,modules}) are restricted,\n"

View File

@ -16,6 +16,10 @@
#include <sys/types.h>
#include <sys/mman.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <linux/kernel.h>
#include <linux/perf_event.h>
@ -35,9 +39,14 @@
#include "../perf.h"
#include "util.h"
#include "evlist.h"
#include "dso.h"
#include "map.h"
#include "pmu.h"
#include "evsel.h"
#include "cpumap.h"
#include "thread_map.h"
#include "asm/bug.h"
#include "symbol/kallsyms.h"
#include "auxtrace.h"
#include <linux/hash.h>
@ -1399,3 +1408,731 @@ void *auxtrace_cache__lookup(struct auxtrace_cache *c, u32 key)
return NULL;
}
static void addr_filter__free_str(struct addr_filter *filt)
{
free(filt->str);
filt->action = NULL;
filt->sym_from = NULL;
filt->sym_to = NULL;
filt->filename = NULL;
filt->str = NULL;
}
static struct addr_filter *addr_filter__new(void)
{
struct addr_filter *filt = zalloc(sizeof(*filt));
if (filt)
INIT_LIST_HEAD(&filt->list);
return filt;
}
static void addr_filter__free(struct addr_filter *filt)
{
if (filt)
addr_filter__free_str(filt);
free(filt);
}
static void addr_filters__add(struct addr_filters *filts,
struct addr_filter *filt)
{
list_add_tail(&filt->list, &filts->head);
filts->cnt += 1;
}
static void addr_filters__del(struct addr_filters *filts,
struct addr_filter *filt)
{
list_del_init(&filt->list);
filts->cnt -= 1;
}
void addr_filters__init(struct addr_filters *filts)
{
INIT_LIST_HEAD(&filts->head);
filts->cnt = 0;
}
void addr_filters__exit(struct addr_filters *filts)
{
struct addr_filter *filt, *n;
list_for_each_entry_safe(filt, n, &filts->head, list) {
addr_filters__del(filts, filt);
addr_filter__free(filt);
}
}
static int parse_num_or_str(char **inp, u64 *num, const char **str,
const char *str_delim)
{
*inp += strspn(*inp, " ");
if (isdigit(**inp)) {
char *endptr;
if (!num)
return -EINVAL;
errno = 0;
*num = strtoull(*inp, &endptr, 0);
if (errno)
return -errno;
if (endptr == *inp)
return -EINVAL;
*inp = endptr;
} else {
size_t n;
if (!str)
return -EINVAL;
*inp += strspn(*inp, " ");
*str = *inp;
n = strcspn(*inp, str_delim);
if (!n)
return -EINVAL;
*inp += n;
if (**inp) {
**inp = '\0';
*inp += 1;
}
}
return 0;
}
static int parse_action(struct addr_filter *filt)
{
if (!strcmp(filt->action, "filter")) {
filt->start = true;
filt->range = true;
} else if (!strcmp(filt->action, "start")) {
filt->start = true;
} else if (!strcmp(filt->action, "stop")) {
filt->start = false;
} else if (!strcmp(filt->action, "tracestop")) {
filt->start = false;
filt->range = true;
filt->action += 5; /* Change 'tracestop' to 'stop' */
} else {
return -EINVAL;
}
return 0;
}
static int parse_sym_idx(char **inp, int *idx)
{
*idx = -1;
*inp += strspn(*inp, " ");
if (**inp != '#')
return 0;
*inp += 1;
if (**inp == 'g' || **inp == 'G') {
*inp += 1;
*idx = 0;
} else {
unsigned long num;
char *endptr;
errno = 0;
num = strtoul(*inp, &endptr, 0);
if (errno)
return -errno;
if (endptr == *inp || num > INT_MAX)
return -EINVAL;
*inp = endptr;
*idx = num;
}
return 0;
}
static int parse_addr_size(char **inp, u64 *num, const char **str, int *idx)
{
int err = parse_num_or_str(inp, num, str, " ");
if (!err && *str)
err = parse_sym_idx(inp, idx);
return err;
}
static int parse_one_filter(struct addr_filter *filt, const char **filter_inp)
{
char *fstr;
int err;
filt->str = fstr = strdup(*filter_inp);
if (!fstr)
return -ENOMEM;
err = parse_num_or_str(&fstr, NULL, &filt->action, " ");
if (err)
goto out_err;
err = parse_action(filt);
if (err)
goto out_err;
err = parse_addr_size(&fstr, &filt->addr, &filt->sym_from,
&filt->sym_from_idx);
if (err)
goto out_err;
fstr += strspn(fstr, " ");
if (*fstr == '/') {
fstr += 1;
err = parse_addr_size(&fstr, &filt->size, &filt->sym_to,
&filt->sym_to_idx);
if (err)
goto out_err;
filt->range = true;
}
fstr += strspn(fstr, " ");
if (*fstr == '@') {
fstr += 1;
err = parse_num_or_str(&fstr, NULL, &filt->filename, " ,");
if (err)
goto out_err;
}
fstr += strspn(fstr, " ,");
*filter_inp += fstr - filt->str;
return 0;
out_err:
addr_filter__free_str(filt);
return err;
}
int addr_filters__parse_bare_filter(struct addr_filters *filts,
const char *filter)
{
struct addr_filter *filt;
const char *fstr = filter;
int err;
while (*fstr) {
filt = addr_filter__new();
err = parse_one_filter(filt, &fstr);
if (err) {
addr_filter__free(filt);
addr_filters__exit(filts);
return err;
}
addr_filters__add(filts, filt);
}
return 0;
}
struct sym_args {
const char *name;
u64 start;
u64 size;
int idx;
int cnt;
bool started;
bool global;
bool selected;
bool duplicate;
bool near;
};
static bool kern_sym_match(struct sym_args *args, const char *name, char type)
{
/* A function with the same name, and global or the n'th found or any */
return symbol_type__is_a(type, MAP__FUNCTION) &&
!strcmp(name, args->name) &&
((args->global && isupper(type)) ||
(args->selected && ++(args->cnt) == args->idx) ||
(!args->global && !args->selected));
}
static int find_kern_sym_cb(void *arg, const char *name, char type, u64 start)
{
struct sym_args *args = arg;
if (args->started) {
if (!args->size)
args->size = start - args->start;
if (args->selected) {
if (args->size)
return 1;
} else if (kern_sym_match(args, name, type)) {
args->duplicate = true;
return 1;
}
} else if (kern_sym_match(args, name, type)) {
args->started = true;
args->start = start;
}
return 0;
}
static int print_kern_sym_cb(void *arg, const char *name, char type, u64 start)
{
struct sym_args *args = arg;
if (kern_sym_match(args, name, type)) {
pr_err("#%d\t0x%"PRIx64"\t%c\t%s\n",
++args->cnt, start, type, name);
args->near = true;
} else if (args->near) {
args->near = false;
pr_err("\t\twhich is near\t\t%s\n", name);
}
return 0;
}
static int sym_not_found_error(const char *sym_name, int idx)
{
if (idx > 0) {
pr_err("N'th occurrence (N=%d) of symbol '%s' not found.\n",
idx, sym_name);
} else if (!idx) {
pr_err("Global symbol '%s' not found.\n", sym_name);
} else {
pr_err("Symbol '%s' not found.\n", sym_name);
}
pr_err("Note that symbols must be functions.\n");
return -EINVAL;
}
static int find_kern_sym(const char *sym_name, u64 *start, u64 *size, int idx)
{
struct sym_args args = {
.name = sym_name,
.idx = idx,
.global = !idx,
.selected = idx > 0,
};
int err;
*start = 0;
*size = 0;
err = kallsyms__parse("/proc/kallsyms", &args, find_kern_sym_cb);
if (err < 0) {
pr_err("Failed to parse /proc/kallsyms\n");
return err;
}
if (args.duplicate) {
pr_err("Multiple kernel symbols with name '%s'\n", sym_name);
args.cnt = 0;
kallsyms__parse("/proc/kallsyms", &args, print_kern_sym_cb);
pr_err("Disambiguate symbol name by inserting #n after the name e.g. %s #2\n",
sym_name);
pr_err("Or select a global symbol by inserting #0 or #g or #G\n");
return -EINVAL;
}
if (!args.started) {
pr_err("Kernel symbol lookup: ");
return sym_not_found_error(sym_name, idx);
}
*start = args.start;
*size = args.size;
return 0;
}
static int find_entire_kern_cb(void *arg, const char *name __maybe_unused,
char type, u64 start)
{
struct sym_args *args = arg;
if (!symbol_type__is_a(type, MAP__FUNCTION))
return 0;
if (!args->started) {
args->started = true;
args->start = start;
}
/* Don't know exactly where the kernel ends, so we add a page */
args->size = round_up(start, page_size) + page_size - args->start;
return 0;
}
static int addr_filter__entire_kernel(struct addr_filter *filt)
{
struct sym_args args = { .started = false };
int err;
err = kallsyms__parse("/proc/kallsyms", &args, find_entire_kern_cb);
if (err < 0 || !args.started) {
pr_err("Failed to parse /proc/kallsyms\n");
return err;
}
filt->addr = args.start;
filt->size = args.size;
return 0;
}
static int check_end_after_start(struct addr_filter *filt, u64 start, u64 size)
{
if (start + size >= filt->addr)
return 0;
if (filt->sym_from) {
pr_err("Symbol '%s' (0x%"PRIx64") comes before '%s' (0x%"PRIx64")\n",
filt->sym_to, start, filt->sym_from, filt->addr);
} else {
pr_err("Symbol '%s' (0x%"PRIx64") comes before address 0x%"PRIx64")\n",
filt->sym_to, start, filt->addr);
}
return -EINVAL;
}
static int addr_filter__resolve_kernel_syms(struct addr_filter *filt)
{
bool no_size = false;
u64 start, size;
int err;
if (symbol_conf.kptr_restrict) {
pr_err("Kernel addresses are restricted. Unable to resolve kernel symbols.\n");
return -EINVAL;
}
if (filt->sym_from && !strcmp(filt->sym_from, "*"))
return addr_filter__entire_kernel(filt);
if (filt->sym_from) {
err = find_kern_sym(filt->sym_from, &start, &size,
filt->sym_from_idx);
if (err)
return err;
filt->addr = start;
if (filt->range && !filt->size && !filt->sym_to) {
filt->size = size;
no_size = !!size;
}
}
if (filt->sym_to) {
err = find_kern_sym(filt->sym_to, &start, &size,
filt->sym_to_idx);
if (err)
return err;
err = check_end_after_start(filt, start, size);
if (err)
return err;
filt->size = start + size - filt->addr;
no_size = !!size;
}
/* The very last symbol in kallsyms does not imply a particular size */
if (no_size) {
pr_err("Cannot determine size of symbol '%s'\n",
filt->sym_to ? filt->sym_to : filt->sym_from);
return -EINVAL;
}
return 0;
}
static struct dso *load_dso(const char *name)
{
struct map *map;
struct dso *dso;
map = dso__new_map(name);
if (!map)
return NULL;
map__load(map);
dso = dso__get(map->dso);
map__put(map);
return dso;
}
static bool dso_sym_match(struct symbol *sym, const char *name, int *cnt,
int idx)
{
/* Same name, and global or the n'th found or any */
return !arch__compare_symbol_names(name, sym->name) &&
((!idx && sym->binding == STB_GLOBAL) ||
(idx > 0 && ++*cnt == idx) ||
idx < 0);
}
static void print_duplicate_syms(struct dso *dso, const char *sym_name)
{
struct symbol *sym;
bool near = false;
int cnt = 0;
pr_err("Multiple symbols with name '%s'\n", sym_name);
sym = dso__first_symbol(dso, MAP__FUNCTION);
while (sym) {
if (dso_sym_match(sym, sym_name, &cnt, -1)) {
pr_err("#%d\t0x%"PRIx64"\t%c\t%s\n",
++cnt, sym->start,
sym->binding == STB_GLOBAL ? 'g' :
sym->binding == STB_LOCAL ? 'l' : 'w',
sym->name);
near = true;
} else if (near) {
near = false;
pr_err("\t\twhich is near\t\t%s\n", sym->name);
}
sym = dso__next_symbol(sym);
}
pr_err("Disambiguate symbol name by inserting #n after the name e.g. %s #2\n",
sym_name);
pr_err("Or select a global symbol by inserting #0 or #g or #G\n");
}
static int find_dso_sym(struct dso *dso, const char *sym_name, u64 *start,
u64 *size, int idx)
{
struct symbol *sym;
int cnt = 0;
*start = 0;
*size = 0;
sym = dso__first_symbol(dso, MAP__FUNCTION);
while (sym) {
if (*start) {
if (!*size)
*size = sym->start - *start;
if (idx > 0) {
if (*size)
return 1;
} else if (dso_sym_match(sym, sym_name, &cnt, idx)) {
print_duplicate_syms(dso, sym_name);
return -EINVAL;
}
} else if (dso_sym_match(sym, sym_name, &cnt, idx)) {
*start = sym->start;
*size = sym->end - sym->start;
}
sym = dso__next_symbol(sym);
}
if (!*start)
return sym_not_found_error(sym_name, idx);
return 0;
}
static int addr_filter__entire_dso(struct addr_filter *filt, struct dso *dso)
{
struct symbol *first_sym = dso__first_symbol(dso, MAP__FUNCTION);
struct symbol *last_sym = dso__last_symbol(dso, MAP__FUNCTION);
if (!first_sym || !last_sym) {
pr_err("Failed to determine filter for %s\nNo symbols found.\n",
filt->filename);
return -EINVAL;
}
filt->addr = first_sym->start;
filt->size = last_sym->end - first_sym->start;
return 0;
}
static int addr_filter__resolve_syms(struct addr_filter *filt)
{
u64 start, size;
struct dso *dso;
int err = 0;
if (!filt->sym_from && !filt->sym_to)
return 0;
if (!filt->filename)
return addr_filter__resolve_kernel_syms(filt);
dso = load_dso(filt->filename);
if (!dso) {
pr_err("Failed to load symbols from: %s\n", filt->filename);
return -EINVAL;
}
if (filt->sym_from && !strcmp(filt->sym_from, "*")) {
err = addr_filter__entire_dso(filt, dso);
goto put_dso;
}
if (filt->sym_from) {
err = find_dso_sym(dso, filt->sym_from, &start, &size,
filt->sym_from_idx);
if (err)
goto put_dso;
filt->addr = start;
if (filt->range && !filt->size && !filt->sym_to)
filt->size = size;
}
if (filt->sym_to) {
err = find_dso_sym(dso, filt->sym_to, &start, &size,
filt->sym_to_idx);
if (err)
goto put_dso;
err = check_end_after_start(filt, start, size);
if (err)
return err;
filt->size = start + size - filt->addr;
}
put_dso:
dso__put(dso);
return err;
}
static char *addr_filter__to_str(struct addr_filter *filt)
{
char filename_buf[PATH_MAX];
const char *at = "";
const char *fn = "";
char *filter;
int err;
if (filt->filename) {
at = "@";
fn = realpath(filt->filename, filename_buf);
if (!fn)
return NULL;
}
if (filt->range) {
err = asprintf(&filter, "%s 0x%"PRIx64"/0x%"PRIx64"%s%s",
filt->action, filt->addr, filt->size, at, fn);
} else {
err = asprintf(&filter, "%s 0x%"PRIx64"%s%s",
filt->action, filt->addr, at, fn);
}
return err < 0 ? NULL : filter;
}
static int parse_addr_filter(struct perf_evsel *evsel, const char *filter,
int max_nr)
{
struct addr_filters filts;
struct addr_filter *filt;
int err;
addr_filters__init(&filts);
err = addr_filters__parse_bare_filter(&filts, filter);
if (err)
goto out_exit;
if (filts.cnt > max_nr) {
pr_err("Error: number of address filters (%d) exceeds maximum (%d)\n",
filts.cnt, max_nr);
err = -EINVAL;
goto out_exit;
}
list_for_each_entry(filt, &filts.head, list) {
char *new_filter;
err = addr_filter__resolve_syms(filt);
if (err)
goto out_exit;
new_filter = addr_filter__to_str(filt);
if (!new_filter) {
err = -ENOMEM;
goto out_exit;
}
if (perf_evsel__append_addr_filter(evsel, new_filter)) {
err = -ENOMEM;
goto out_exit;
}
}
out_exit:
addr_filters__exit(&filts);
if (err) {
pr_err("Failed to parse address filter: '%s'\n", filter);
pr_err("Filter format is: filter|start|stop|tracestop <start symbol or address> [/ <end symbol or size>] [@<file name>]\n");
pr_err("Where multiple filters are separated by space or comma.\n");
}
return err;
}
static struct perf_pmu *perf_evsel__find_pmu(struct perf_evsel *evsel)
{
struct perf_pmu *pmu = NULL;
while ((pmu = perf_pmu__scan(pmu)) != NULL) {
if (pmu->type == evsel->attr.type)
break;
}
return pmu;
}
static int perf_evsel__nr_addr_filter(struct perf_evsel *evsel)
{
struct perf_pmu *pmu = perf_evsel__find_pmu(evsel);
int nr_addr_filters = 0;
if (!pmu)
return 0;
perf_pmu__scan_file(pmu, "nr_addr_filters", "%d", &nr_addr_filters);
return nr_addr_filters;
}
int auxtrace_parse_filters(struct perf_evlist *evlist)
{
struct perf_evsel *evsel;
char *filter;
int err, max_nr;
evlist__for_each_entry(evlist, evsel) {
filter = evsel->filter;
max_nr = perf_evsel__nr_addr_filter(evsel);
if (!filter || !max_nr)
continue;
evsel->filter = NULL;
err = parse_addr_filter(evsel, filter, max_nr);
free(filter);
if (err)
return err;
pr_debug("Address filter: %s\n", evsel->filter);
}
return 0;
}

View File

@ -318,6 +318,48 @@ struct auxtrace_record {
unsigned int alignment;
};
/**
* struct addr_filter - address filter.
* @list: list node
* @range: true if it is a range filter
* @start: true if action is 'filter' or 'start'
* @action: 'filter', 'start' or 'stop' ('tracestop' is accepted but converted
* to 'stop')
* @sym_from: symbol name for the filter address
* @sym_to: symbol name that determines the filter size
* @sym_from_idx: selects n'th from symbols with the same name (0 means global
* and less than 0 means symbol must be unique)
* @sym_to_idx: same as @sym_from_idx but for @sym_to
* @addr: filter address
* @size: filter region size (for range filters)
* @filename: DSO file name or NULL for the kernel
* @str: allocated string that contains the other string members
*/
struct addr_filter {
struct list_head list;
bool range;
bool start;
const char *action;
const char *sym_from;
const char *sym_to;
int sym_from_idx;
int sym_to_idx;
u64 addr;
u64 size;
const char *filename;
char *str;
};
/**
* struct addr_filters - list of address filters.
* @head: list of address filters
* @cnt: number of address filters
*/
struct addr_filters {
struct list_head head;
int cnt;
};
#ifdef HAVE_AUXTRACE_SUPPORT
/*
@ -482,6 +524,12 @@ void perf_session__auxtrace_error_inc(struct perf_session *session,
union perf_event *event);
void events_stats__auxtrace_error_warn(const struct events_stats *stats);
void addr_filters__init(struct addr_filters *filts);
void addr_filters__exit(struct addr_filters *filts);
int addr_filters__parse_bare_filter(struct addr_filters *filts,
const char *filter);
int auxtrace_parse_filters(struct perf_evlist *evlist);
static inline int auxtrace__process_event(struct perf_session *session,
union perf_event *event,
struct perf_sample *sample,
@ -640,6 +688,12 @@ void auxtrace_index__free(struct list_head *head __maybe_unused)
{
}
static inline
int auxtrace_parse_filters(struct perf_evlist *evlist __maybe_unused)
{
return 0;
}
int auxtrace_mmap__mmap(struct auxtrace_mmap *mm,
struct auxtrace_mmap_params *mp,
void *userpg, int fd);