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:
parent
cd67f99fe9
commit
1b36c03e35
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue