perf/core improvements and fixes:
User visible: ------------- New features: - Add support for using symbols in address filters with Intel PT and ARM CoreSight (hardware assisted tracing facilities) (Adrian Hunter, Mathieu Poirier) Fixes: - Fix MMAP event synthesis for pre-existing threads when no hugetlbfs mount is in place (Adrian Hunter) - Don't ignore kernel idle symbols in 'perf script' (Adrian Hunter) - Assorted Intel PT fixes (Adrian Hunter) Improvements: - Fix handling of C++ symbols in 'perf probe' (Masami Hiramatsu) - Beautify sched_[gs]et_attr return value in 'perf trace' (Arnaldo Carvalho de Melo) Infrastructure: --------------- New features: - Add dwarf unwind 'perf test' for powerpc (Ravi Bangoria) Fixes: - Fix error paths in 'perf record' (Adrian Hunter) Documentation: - Update documentation info about quipper, a C++ parser for converting to/from perf.data/chromium profiling format (Simon Que) Build Fixes: Fix building in 32 bit platform with libbabeltrace (Wang Nan) Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJX7SNKAAoJENZQFvNTUqpAozcP/1HBFsdA2w7cqx2NbeBVo0Fa c6oirKVFix9IHvYRuxaOt6+MRFaUFhKR5mdeK+9Vg6gcVaQGSTR54dA6uecHUVhw ezf224ZdHgN3cc+wm4skwjLjJN4CtgW3VORIkyUZm71VFLkb6IpxHa1s7TigYcRf aSFdHHzVW+uN0c5kdG98kYgdP30i3V2+TXCOqxeeoODAlSuXVg8uB0Op6jUMW+n0 l/K/5xLVGNjlBpB7468EiHyNLzrmuCTwzjZ1g5yYI7PkM+SDRZ7DAz/erZ0dz0GM I9s9bVgkBhSriV2cq0BaXdtUW+oJmQQbgA+4R8xMb5RxwIDqjLA+k8hLpYrvSdUv Fry0jtmrAPMM31jr0k2dmTJ/zruCbKaoU+rmPhcOmn1odJTadTdcYwfUPczZzD20 IHdDAux+kY8C94+obJGijjqeHtM9A9ciCWAr3577DQASkMHsvR/Gnit3lUyFSwiU NLamIq+XhKPEX9KnqXGqSYm77VKHQjfyAm1GsN+kJLkz74F1n5IEnypoIO7RqLdn wToFtp3rS5Fp9Xy+JLSQgcXlDsyZeFCexbu5JB7SYDi5bXwfdgqoO7+8eKiOlOim nBZlcUIkymTkRpYM0b1PDtbGxVE2ajrZH204XTOQ39cyxNfpnd7JQl2b/laVZ6Vb IC28njgLUddVzqnUCpw8 =FBd2 -----END PGP SIGNATURE----- Merge tag 'perf-core-for-mingo-20160929' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux into perf/core Pull perf/core improvements and fixes from Arnaldo Carvalho de Melo: User visible changes: --------------------- New features: - Add support for using symbols in address filters with Intel PT and ARM CoreSight (hardware assisted tracing facilities) (Adrian Hunter, Mathieu Poirier) Fixes: - Fix MMAP event synthesis for pre-existing threads when no hugetlbfs mount is in place (Adrian Hunter) - Don't ignore kernel idle symbols in 'perf script' (Adrian Hunter) - Assorted Intel PT fixes (Adrian Hunter) Improvements: - Fix handling of C++ symbols in 'perf probe' (Masami Hiramatsu) - Beautify sched_[gs]et_attr return value in 'perf trace' (Arnaldo Carvalho de Melo) Infrastructure changes: ----------------------- New features: - Add dwarf unwind 'perf test' for powerpc (Ravi Bangoria) Fixes: - Fix error paths in 'perf record' (Adrian Hunter) Documentation: - Update documentation info about quipper, a C++ parser for converting to/from perf.data/chromium profiling format (Simon Que) Build Fixes: Fix building in 32 bit platform with libbabeltrace (Wang Nan) Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
Коммит
41aad2a6d4
|
@ -35,15 +35,15 @@ OPTIONS
|
|||
|
||||
- a symbolically formed PMU event like 'pmu/param1=0x3,param2/' where
|
||||
'param1', 'param2', etc are defined as formats for the PMU in
|
||||
/sys/bus/event_sources/devices/<pmu>/format/*.
|
||||
/sys/bus/event_source/devices/<pmu>/format/*.
|
||||
|
||||
- a symbolically formed event like 'pmu/config=M,config1=N,config3=K/'
|
||||
|
||||
where M, N, K are numbers (in decimal, hex, octal format). Acceptable
|
||||
values for each of 'config', 'config1' and 'config2' are defined by
|
||||
corresponding entries in /sys/bus/event_sources/devices/<pmu>/format/*
|
||||
corresponding entries in /sys/bus/event_source/devices/<pmu>/format/*
|
||||
param1 and param2 are defined as formats for the PMU in:
|
||||
/sys/bus/event_sources/devices/<pmu>/format/*
|
||||
/sys/bus/event_source/devices/<pmu>/format/*
|
||||
|
||||
There are also some params which are not defined in .../<pmu>/format/*.
|
||||
These params can be used to overload default config values per event.
|
||||
|
@ -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
|
||||
|
|
|
@ -437,6 +437,10 @@ in pmu-tools parser. This allows to read perf.data from python and dump it.
|
|||
quipper
|
||||
|
||||
The quipper C++ parser is available at
|
||||
https://chromium.googlesource.com/chromiumos/platform/chromiumos-wide-profiling/
|
||||
https://chromium.googlesource.com/chromiumos/platform2
|
||||
|
||||
It is under the chromiumos-wide-profiling/ subdirectory. This library can
|
||||
convert a perf data file to a protobuf and vice versa.
|
||||
|
||||
Unfortunately this parser tends to be many versions behind and may not be able
|
||||
to parse data files generated by recent perf.
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
libperf-y += util/
|
||||
libperf-y += tests/
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef ARCH_TESTS_H
|
||||
#define ARCH_TESTS_H
|
||||
|
||||
#ifdef HAVE_DWARF_UNWIND_SUPPORT
|
||||
struct thread;
|
||||
struct perf_sample;
|
||||
int test__arch_unwind_sample(struct perf_sample *sample,
|
||||
struct thread *thread);
|
||||
#endif
|
||||
|
||||
extern struct test arch_tests[];
|
||||
|
||||
#endif
|
|
@ -5,6 +5,8 @@
|
|||
#include <linux/types.h>
|
||||
#include <asm/perf_regs.h>
|
||||
|
||||
void perf_regs_load(u64 *regs);
|
||||
|
||||
#define PERF_REGS_MASK ((1ULL << PERF_REG_POWERPC_MAX) - 1)
|
||||
#define PERF_REGS_MAX PERF_REG_POWERPC_MAX
|
||||
#ifdef __powerpc64__
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
libperf-$(CONFIG_DWARF_UNWIND) += regs_load.o
|
||||
libperf-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
|
||||
|
||||
libperf-y += arch-tests.o
|
|
@ -0,0 +1,15 @@
|
|||
#include <string.h>
|
||||
#include "tests/tests.h"
|
||||
#include "arch-tests.h"
|
||||
|
||||
struct test arch_tests[] = {
|
||||
#ifdef HAVE_DWARF_UNWIND_SUPPORT
|
||||
{
|
||||
.desc = "Test dwarf unwind",
|
||||
.func = test__dwarf_unwind,
|
||||
},
|
||||
#endif
|
||||
{
|
||||
.func = NULL,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
#include <string.h>
|
||||
#include "perf_regs.h"
|
||||
#include "thread.h"
|
||||
#include "map.h"
|
||||
#include "event.h"
|
||||
#include "debug.h"
|
||||
#include "tests/tests.h"
|
||||
#include "arch-tests.h"
|
||||
|
||||
#define STACK_SIZE 8192
|
||||
|
||||
static int sample_ustack(struct perf_sample *sample,
|
||||
struct thread *thread, u64 *regs)
|
||||
{
|
||||
struct stack_dump *stack = &sample->user_stack;
|
||||
struct map *map;
|
||||
unsigned long sp;
|
||||
u64 stack_size, *buf;
|
||||
|
||||
buf = malloc(STACK_SIZE);
|
||||
if (!buf) {
|
||||
pr_debug("failed to allocate sample uregs data\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
sp = (unsigned long) regs[PERF_REG_POWERPC_R1];
|
||||
|
||||
map = map_groups__find(thread->mg, MAP__VARIABLE, (u64) sp);
|
||||
if (!map) {
|
||||
pr_debug("failed to get stack map\n");
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
stack_size = map->end - sp;
|
||||
stack_size = stack_size > STACK_SIZE ? STACK_SIZE : stack_size;
|
||||
|
||||
memcpy(buf, (void *) sp, stack_size);
|
||||
stack->data = (char *) buf;
|
||||
stack->size = stack_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test__arch_unwind_sample(struct perf_sample *sample,
|
||||
struct thread *thread)
|
||||
{
|
||||
struct regs_dump *regs = &sample->user_regs;
|
||||
u64 *buf;
|
||||
|
||||
buf = calloc(1, sizeof(u64) * PERF_REGS_MAX);
|
||||
if (!buf) {
|
||||
pr_debug("failed to allocate sample uregs data\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
perf_regs_load(buf);
|
||||
regs->abi = PERF_SAMPLE_REGS_ABI;
|
||||
regs->regs = buf;
|
||||
regs->mask = PERF_REGS_MASK;
|
||||
|
||||
return sample_ustack(sample, thread, buf);
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
#include <linux/linkage.h>
|
||||
|
||||
/* Offset is based on macros from arch/powerpc/include/uapi/asm/ptrace.h. */
|
||||
#define R0 0
|
||||
#define R1 1 * 8
|
||||
#define R2 2 * 8
|
||||
#define R3 3 * 8
|
||||
#define R4 4 * 8
|
||||
#define R5 5 * 8
|
||||
#define R6 6 * 8
|
||||
#define R7 7 * 8
|
||||
#define R8 8 * 8
|
||||
#define R9 9 * 8
|
||||
#define R10 10 * 8
|
||||
#define R11 11 * 8
|
||||
#define R12 12 * 8
|
||||
#define R13 13 * 8
|
||||
#define R14 14 * 8
|
||||
#define R15 15 * 8
|
||||
#define R16 16 * 8
|
||||
#define R17 17 * 8
|
||||
#define R18 18 * 8
|
||||
#define R19 19 * 8
|
||||
#define R20 20 * 8
|
||||
#define R21 21 * 8
|
||||
#define R22 22 * 8
|
||||
#define R23 23 * 8
|
||||
#define R24 24 * 8
|
||||
#define R25 25 * 8
|
||||
#define R26 26 * 8
|
||||
#define R27 27 * 8
|
||||
#define R28 28 * 8
|
||||
#define R29 29 * 8
|
||||
#define R30 30 * 8
|
||||
#define R31 31 * 8
|
||||
#define NIP 32 * 8
|
||||
#define CTR 35 * 8
|
||||
#define LINK 36 * 8
|
||||
#define XER 37 * 8
|
||||
|
||||
.globl perf_regs_load
|
||||
perf_regs_load:
|
||||
std 0, R0(3)
|
||||
std 1, R1(3)
|
||||
std 2, R2(3)
|
||||
std 3, R3(3)
|
||||
std 4, R4(3)
|
||||
std 5, R5(3)
|
||||
std 6, R6(3)
|
||||
std 7, R7(3)
|
||||
std 8, R8(3)
|
||||
std 9, R9(3)
|
||||
std 10, R10(3)
|
||||
std 11, R11(3)
|
||||
std 12, R12(3)
|
||||
std 13, R13(3)
|
||||
std 14, R14(3)
|
||||
std 15, R15(3)
|
||||
std 16, R16(3)
|
||||
std 17, R17(3)
|
||||
std 18, R18(3)
|
||||
std 19, R19(3)
|
||||
std 20, R20(3)
|
||||
std 21, R21(3)
|
||||
std 22, R22(3)
|
||||
std 23, R23(3)
|
||||
std 24, R24(3)
|
||||
std 25, R25(3)
|
||||
std 26, R26(3)
|
||||
std 27, R27(3)
|
||||
std 28, R28(3)
|
||||
std 29, R29(3)
|
||||
std 30, R30(3)
|
||||
std 31, R31(3)
|
||||
|
||||
/* store NIP */
|
||||
mflr 4
|
||||
std 4, NIP(3)
|
||||
|
||||
/* Store LR */
|
||||
std 4, LINK(3)
|
||||
|
||||
/* Store XER */
|
||||
mfxer 4
|
||||
std 4, XER(3)
|
||||
|
||||
/* Store CTR */
|
||||
mfctr 4
|
||||
std 4, CTR(3)
|
||||
|
||||
/* Restore original value of r4 */
|
||||
ld 4, R4(3)
|
||||
|
||||
blr
|
|
@ -62,6 +62,7 @@ struct intel_pt_recording {
|
|||
size_t snapshot_ref_buf_size;
|
||||
int snapshot_ref_cnt;
|
||||
struct intel_pt_snapshot_ref *snapshot_refs;
|
||||
size_t priv_size;
|
||||
};
|
||||
|
||||
static int intel_pt_parse_terms_with_default(struct list_head *formats,
|
||||
|
@ -273,11 +274,37 @@ intel_pt_pmu_default_config(struct perf_pmu *intel_pt_pmu)
|
|||
return attr;
|
||||
}
|
||||
|
||||
static size_t
|
||||
intel_pt_info_priv_size(struct auxtrace_record *itr __maybe_unused,
|
||||
struct perf_evlist *evlist __maybe_unused)
|
||||
static const char *intel_pt_find_filter(struct perf_evlist *evlist,
|
||||
struct perf_pmu *intel_pt_pmu)
|
||||
{
|
||||
return INTEL_PT_AUXTRACE_PRIV_SIZE;
|
||||
struct perf_evsel *evsel;
|
||||
|
||||
evlist__for_each_entry(evlist, evsel) {
|
||||
if (evsel->attr.type == intel_pt_pmu->type)
|
||||
return evsel->filter;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static size_t intel_pt_filter_bytes(const char *filter)
|
||||
{
|
||||
size_t len = filter ? strlen(filter) : 0;
|
||||
|
||||
return len ? roundup(len + 1, 8) : 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
intel_pt_info_priv_size(struct auxtrace_record *itr, struct perf_evlist *evlist)
|
||||
{
|
||||
struct intel_pt_recording *ptr =
|
||||
container_of(itr, struct intel_pt_recording, itr);
|
||||
const char *filter = intel_pt_find_filter(evlist, ptr->intel_pt_pmu);
|
||||
|
||||
ptr->priv_size = (INTEL_PT_AUXTRACE_PRIV_MAX * sizeof(u64)) +
|
||||
intel_pt_filter_bytes(filter);
|
||||
|
||||
return ptr->priv_size;
|
||||
}
|
||||
|
||||
static void intel_pt_tsc_ctc_ratio(u32 *n, u32 *d)
|
||||
|
@ -302,9 +329,13 @@ static int intel_pt_info_fill(struct auxtrace_record *itr,
|
|||
bool cap_user_time_zero = false, per_cpu_mmaps;
|
||||
u64 tsc_bit, mtc_bit, mtc_freq_bits, cyc_bit, noretcomp_bit;
|
||||
u32 tsc_ctc_ratio_n, tsc_ctc_ratio_d;
|
||||
unsigned long max_non_turbo_ratio;
|
||||
size_t filter_str_len;
|
||||
const char *filter;
|
||||
u64 *info;
|
||||
int err;
|
||||
|
||||
if (priv_size != INTEL_PT_AUXTRACE_PRIV_SIZE)
|
||||
if (priv_size != ptr->priv_size)
|
||||
return -EINVAL;
|
||||
|
||||
intel_pt_parse_terms(&intel_pt_pmu->format, "tsc", &tsc_bit);
|
||||
|
@ -317,6 +348,13 @@ static int intel_pt_info_fill(struct auxtrace_record *itr,
|
|||
|
||||
intel_pt_tsc_ctc_ratio(&tsc_ctc_ratio_n, &tsc_ctc_ratio_d);
|
||||
|
||||
if (perf_pmu__scan_file(intel_pt_pmu, "max_nonturbo_ratio",
|
||||
"%lu", &max_non_turbo_ratio) != 1)
|
||||
max_non_turbo_ratio = 0;
|
||||
|
||||
filter = intel_pt_find_filter(session->evlist, ptr->intel_pt_pmu);
|
||||
filter_str_len = filter ? strlen(filter) : 0;
|
||||
|
||||
if (!session->evlist->nr_mmaps)
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -351,6 +389,17 @@ static int intel_pt_info_fill(struct auxtrace_record *itr,
|
|||
auxtrace_info->priv[INTEL_PT_TSC_CTC_N] = tsc_ctc_ratio_n;
|
||||
auxtrace_info->priv[INTEL_PT_TSC_CTC_D] = tsc_ctc_ratio_d;
|
||||
auxtrace_info->priv[INTEL_PT_CYC_BIT] = cyc_bit;
|
||||
auxtrace_info->priv[INTEL_PT_MAX_NONTURBO_RATIO] = max_non_turbo_ratio;
|
||||
auxtrace_info->priv[INTEL_PT_FILTER_STR_LEN] = filter_str_len;
|
||||
|
||||
info = &auxtrace_info->priv[INTEL_PT_FILTER_STR_LEN] + 1;
|
||||
|
||||
if (filter_str_len) {
|
||||
size_t len = intel_pt_filter_bytes(filter);
|
||||
|
||||
strncpy((char *)info, filter, len);
|
||||
info += len >> 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1573,29 +1573,39 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)
|
|||
if (!rec->itr) {
|
||||
rec->itr = auxtrace_record__init(rec->evlist, &err);
|
||||
if (err)
|
||||
return err;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = auxtrace_parse_snapshot_options(rec->itr, &rec->opts,
|
||||
rec->opts.auxtrace_snapshot_opts);
|
||||
if (err)
|
||||
return 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)
|
||||
return 0;
|
||||
goto out;
|
||||
|
||||
err = bpf__setup_stdout(rec->evlist);
|
||||
if (err) {
|
||||
bpf__strerror_setup_stdout(rec->evlist, err, errbuf, sizeof(errbuf));
|
||||
pr_err("ERROR: Setup BPF stdout failed: %s\n",
|
||||
errbuf);
|
||||
return err;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = -ENOMEM;
|
||||
|
||||
symbol__init(NULL);
|
||||
|
||||
if (symbol_conf.kptr_restrict)
|
||||
pr_warning(
|
||||
"WARNING: Kernel address maps (/proc/{kallsyms,modules}) are restricted,\n"
|
||||
|
@ -1643,7 +1653,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)
|
|||
if (rec->evlist->nr_entries == 0 &&
|
||||
perf_evlist__add_default(rec->evlist) < 0) {
|
||||
pr_err("Not enough memory for event selector list\n");
|
||||
goto out_symbol_exit;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (rec->opts.target.tid && !rec->opts.no_inherit_set)
|
||||
|
@ -1663,7 +1673,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)
|
|||
ui__error("%s", errbuf);
|
||||
|
||||
err = -saved_errno;
|
||||
goto out_symbol_exit;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = -ENOMEM;
|
||||
|
@ -1672,7 +1682,7 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)
|
|||
|
||||
err = auxtrace_record__options(rec->itr, rec->evlist, &rec->opts);
|
||||
if (err)
|
||||
goto out_symbol_exit;
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* We take all buildids when the file contains
|
||||
|
@ -1684,11 +1694,11 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)
|
|||
|
||||
if (record_opts__config(&rec->opts)) {
|
||||
err = -EINVAL;
|
||||
goto out_symbol_exit;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = __cmd_record(&record, argc, argv);
|
||||
out_symbol_exit:
|
||||
out:
|
||||
perf_evlist__delete(rec->evlist);
|
||||
symbol__exit();
|
||||
auxtrace_record__free(rec->itr);
|
||||
|
|
|
@ -742,6 +742,8 @@ static struct syscall_fmt {
|
|||
.arg_scnprintf = { [1] = SCA_SIGNUM, /* sig */ }, },
|
||||
{ .name = "rt_tgsigqueueinfo", .errmsg = true,
|
||||
.arg_scnprintf = { [2] = SCA_SIGNUM, /* sig */ }, },
|
||||
{ .name = "sched_getattr", .errmsg = true, },
|
||||
{ .name = "sched_setattr", .errmsg = true, },
|
||||
{ .name = "sched_setscheduler", .errmsg = true,
|
||||
.arg_scnprintf = { [1] = SCA_SCHED_POLICY, /* policy */ }, },
|
||||
{ .name = "seccomp", .errmsg = true,
|
||||
|
@ -2141,6 +2143,7 @@ out_delete_sys_enter:
|
|||
static int trace__set_ev_qualifier_filter(struct trace *trace)
|
||||
{
|
||||
int err = -1;
|
||||
struct perf_evsel *sys_exit;
|
||||
char *filter = asprintf_expr_inout_ints("id", !trace->not_ev_qualifier,
|
||||
trace->ev_qualifier_ids.nr,
|
||||
trace->ev_qualifier_ids.entries);
|
||||
|
@ -2148,8 +2151,11 @@ static int trace__set_ev_qualifier_filter(struct trace *trace)
|
|||
if (filter == NULL)
|
||||
goto out_enomem;
|
||||
|
||||
if (!perf_evsel__append_filter(trace->syscalls.events.sys_enter, "&&", filter))
|
||||
err = perf_evsel__append_filter(trace->syscalls.events.sys_exit, "&&", filter);
|
||||
if (!perf_evsel__append_tp_filter(trace->syscalls.events.sys_enter,
|
||||
filter)) {
|
||||
sys_exit = trace->syscalls.events.sys_exit;
|
||||
err = perf_evsel__append_tp_filter(sys_exit, filter);
|
||||
}
|
||||
|
||||
free(filter);
|
||||
out:
|
||||
|
|
|
@ -71,7 +71,7 @@ $(OUTPUT)tests/llvm-src-relocation.c: tests/bpf-script-test-relocation.c tests/B
|
|||
$(Q)sed -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/g' $< >> $@
|
||||
$(Q)echo ';' >> $@
|
||||
|
||||
ifeq ($(ARCH),$(filter $(ARCH),x86 arm arm64))
|
||||
ifeq ($(ARCH),$(filter $(ARCH),x86 arm arm64 powerpc))
|
||||
perf-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
|
||||
endif
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "thread.h"
|
||||
#include "callchain.h"
|
||||
|
||||
#if defined (__x86_64__) || defined (__i386__)
|
||||
#if defined (__x86_64__) || defined (__i386__) || defined (__powerpc__)
|
||||
#include "arch-tests.h"
|
||||
#endif
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -620,7 +620,7 @@ static int build_id_cache__add_sdt_cache(const char *sbuild_id,
|
|||
|
||||
ret = probe_cache__scan_sdt(cache, realname);
|
||||
if (ret >= 0) {
|
||||
pr_debug("Found %d SDTs in %s\n", ret, realname);
|
||||
pr_debug4("Found %d SDTs in %s\n", ret, realname);
|
||||
if (probe_cache__commit(cache) < 0)
|
||||
ret = -1;
|
||||
}
|
||||
|
@ -691,7 +691,7 @@ int build_id_cache__add_s(const char *sbuild_id, const char *name,
|
|||
|
||||
/* Update SDT cache : error is just warned */
|
||||
if (build_id_cache__add_sdt_cache(sbuild_id, realname) < 0)
|
||||
pr_debug("Failed to update/scan SDT cache for %s\n", realname);
|
||||
pr_debug4("Failed to update/scan SDT cache for %s\n", realname);
|
||||
|
||||
out_free:
|
||||
if (!is_kallsyms)
|
||||
|
|
|
@ -437,7 +437,7 @@ add_bpf_output_values(struct bt_ctf_event_class *event_class,
|
|||
int ret;
|
||||
|
||||
if (nr_elements * sizeof(u32) != raw_size)
|
||||
pr_warning("Incorrect raw_size (%u) in bpf output event, skip %lu bytes\n",
|
||||
pr_warning("Incorrect raw_size (%u) in bpf output event, skip %zu bytes\n",
|
||||
raw_size, nr_elements * sizeof(u32) - raw_size);
|
||||
|
||||
len_type = bt_ctf_event_class_get_field_by_name(event_class, "raw_len");
|
||||
|
|
|
@ -129,6 +129,22 @@ int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* die_get_linkage_name - Get the linkage name of the object
|
||||
* @dw_die: A DIE of the object
|
||||
*
|
||||
* Get the linkage name attiribute of given @dw_die.
|
||||
* For C++ binary, the linkage name will be the mangled symbol.
|
||||
*/
|
||||
const char *die_get_linkage_name(Dwarf_Die *dw_die)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
|
||||
if (dwarf_attr_integrate(dw_die, DW_AT_linkage_name, &attr) == NULL)
|
||||
return NULL;
|
||||
return dwarf_formstring(&attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* die_compare_name - Compare diename and tname
|
||||
* @dw_die: a DIE
|
||||
|
@ -145,18 +161,26 @@ bool die_compare_name(Dwarf_Die *dw_die, const char *tname)
|
|||
}
|
||||
|
||||
/**
|
||||
* die_match_name - Match diename and glob
|
||||
* die_match_name - Match diename/linkage name and glob
|
||||
* @dw_die: a DIE
|
||||
* @glob: a string of target glob pattern
|
||||
*
|
||||
* Glob matching the name of @dw_die and @glob. Return false if matching fail.
|
||||
* This also match linkage name.
|
||||
*/
|
||||
bool die_match_name(Dwarf_Die *dw_die, const char *glob)
|
||||
{
|
||||
const char *name;
|
||||
|
||||
name = dwarf_diename(dw_die);
|
||||
return name ? strglobmatch(name, glob) : false;
|
||||
if (name && strglobmatch(name, glob))
|
||||
return true;
|
||||
/* fall back to check linkage name */
|
||||
name = die_get_linkage_name(dw_die);
|
||||
if (name && strglobmatch(name, glob))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,6 +38,9 @@ int cu_find_lineinfo(Dwarf_Die *cudie, unsigned long addr,
|
|||
int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
|
||||
int (*callback)(Dwarf_Die *, void *), void *data);
|
||||
|
||||
/* Get DW_AT_linkage_name (should be NULL for C binary) */
|
||||
const char *die_get_linkage_name(Dwarf_Die *dw_die);
|
||||
|
||||
/* Ensure that this DIE is a subprogram and definition (not declaration) */
|
||||
bool die_is_func_def(Dwarf_Die *dw_die);
|
||||
|
||||
|
|
|
@ -346,7 +346,8 @@ out:
|
|||
if (!strcmp(execname, ""))
|
||||
strcpy(execname, anonstr);
|
||||
|
||||
if (!strncmp(execname, hugetlbfs_mnt, hugetlbfs_mnt_len)) {
|
||||
if (hugetlbfs_mnt_len &&
|
||||
!strncmp(execname, hugetlbfs_mnt, hugetlbfs_mnt_len)) {
|
||||
strcpy(execname, anonstr);
|
||||
event->mmap2.flags |= MAP_HUGETLB;
|
||||
}
|
||||
|
|
|
@ -1045,15 +1045,15 @@ int perf_evsel__set_filter(struct perf_evsel *evsel, const char *filter)
|
|||
return -1;
|
||||
}
|
||||
|
||||
int perf_evsel__append_filter(struct perf_evsel *evsel,
|
||||
const char *op, const char *filter)
|
||||
static int perf_evsel__append_filter(struct perf_evsel *evsel,
|
||||
const char *fmt, const char *filter)
|
||||
{
|
||||
char *new_filter;
|
||||
|
||||
if (evsel->filter == NULL)
|
||||
return perf_evsel__set_filter(evsel, filter);
|
||||
|
||||
if (asprintf(&new_filter,"(%s) %s (%s)", evsel->filter, op, filter) > 0) {
|
||||
if (asprintf(&new_filter, fmt, evsel->filter, filter) > 0) {
|
||||
free(evsel->filter);
|
||||
evsel->filter = new_filter;
|
||||
return 0;
|
||||
|
@ -1062,6 +1062,16 @@ int perf_evsel__append_filter(struct perf_evsel *evsel,
|
|||
return -1;
|
||||
}
|
||||
|
||||
int perf_evsel__append_tp_filter(struct perf_evsel *evsel, const char *filter)
|
||||
{
|
||||
return perf_evsel__append_filter(evsel, "(%s) && (%s)", filter);
|
||||
}
|
||||
|
||||
int perf_evsel__append_addr_filter(struct perf_evsel *evsel, const char *filter)
|
||||
{
|
||||
return perf_evsel__append_filter(evsel, "%s,%s", filter);
|
||||
}
|
||||
|
||||
int perf_evsel__enable(struct perf_evsel *evsel)
|
||||
{
|
||||
int nthreads = thread_map__nr(evsel->threads);
|
||||
|
|
|
@ -235,8 +235,9 @@ void perf_evsel__set_sample_id(struct perf_evsel *evsel,
|
|||
bool use_sample_identifier);
|
||||
|
||||
int perf_evsel__set_filter(struct perf_evsel *evsel, const char *filter);
|
||||
int perf_evsel__append_filter(struct perf_evsel *evsel,
|
||||
const char *op, const char *filter);
|
||||
int perf_evsel__append_tp_filter(struct perf_evsel *evsel, const char *filter);
|
||||
int perf_evsel__append_addr_filter(struct perf_evsel *evsel,
|
||||
const char *filter);
|
||||
int perf_evsel__apply_filter(struct perf_evsel *evsel, int ncpus, int nthreads,
|
||||
const char *filter);
|
||||
int perf_evsel__enable(struct perf_evsel *evsel);
|
||||
|
|
|
@ -122,9 +122,6 @@ int sample__fprintf_callchain(struct perf_sample *sample, int left_alignment,
|
|||
if (!node)
|
||||
break;
|
||||
|
||||
if (node->sym && node->sym->idle)
|
||||
goto next;
|
||||
|
||||
printed += fprintf(fp, "%-*.*s", left_alignment, left_alignment, " ");
|
||||
|
||||
if (print_ip)
|
||||
|
@ -158,7 +155,7 @@ int sample__fprintf_callchain(struct perf_sample *sample, int left_alignment,
|
|||
|
||||
if (!print_oneline)
|
||||
printed += fprintf(fp, "\n");
|
||||
next:
|
||||
|
||||
callchain_cursor_advance(cursor);
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +178,7 @@ int sample__fprintf_sym(struct perf_sample *sample, struct addr_location *al,
|
|||
if (cursor != NULL) {
|
||||
printed += sample__fprintf_callchain(sample, left_alignment,
|
||||
print_opts, cursor, fp);
|
||||
} else if (!(al->sym && al->sym->idle)) {
|
||||
} else {
|
||||
printed += fprintf(fp, "%-*.*s", left_alignment, left_alignment, " ");
|
||||
|
||||
if (print_ip)
|
||||
|
|
|
@ -80,6 +80,7 @@ struct intel_pt_decoder {
|
|||
int (*walk_insn)(struct intel_pt_insn *intel_pt_insn,
|
||||
uint64_t *insn_cnt_ptr, uint64_t *ip, uint64_t to_ip,
|
||||
uint64_t max_insn_cnt, void *data);
|
||||
bool (*pgd_ip)(uint64_t ip, void *data);
|
||||
void *data;
|
||||
struct intel_pt_state state;
|
||||
const unsigned char *buf;
|
||||
|
@ -186,6 +187,7 @@ struct intel_pt_decoder *intel_pt_decoder_new(struct intel_pt_params *params)
|
|||
|
||||
decoder->get_trace = params->get_trace;
|
||||
decoder->walk_insn = params->walk_insn;
|
||||
decoder->pgd_ip = params->pgd_ip;
|
||||
decoder->data = params->data;
|
||||
decoder->return_compression = params->return_compression;
|
||||
|
||||
|
@ -1008,6 +1010,19 @@ static int intel_pt_walk_tip(struct intel_pt_decoder *decoder)
|
|||
int err;
|
||||
|
||||
err = intel_pt_walk_insn(decoder, &intel_pt_insn, 0);
|
||||
if (err == INTEL_PT_RETURN &&
|
||||
decoder->pgd_ip &&
|
||||
decoder->pkt_state == INTEL_PT_STATE_TIP_PGD &&
|
||||
(decoder->state.type & INTEL_PT_BRANCH) &&
|
||||
decoder->pgd_ip(decoder->state.to_ip, decoder->data)) {
|
||||
/* Unconditional branch leaving filter region */
|
||||
decoder->no_progress = 0;
|
||||
decoder->pge = false;
|
||||
decoder->continuous_period = false;
|
||||
decoder->pkt_state = INTEL_PT_STATE_IN_SYNC;
|
||||
decoder->state.to_ip = 0;
|
||||
return 0;
|
||||
}
|
||||
if (err == INTEL_PT_RETURN)
|
||||
return 0;
|
||||
if (err)
|
||||
|
@ -1036,6 +1051,21 @@ static int intel_pt_walk_tip(struct intel_pt_decoder *decoder)
|
|||
}
|
||||
|
||||
if (intel_pt_insn.branch == INTEL_PT_BR_CONDITIONAL) {
|
||||
uint64_t to_ip = decoder->ip + intel_pt_insn.length +
|
||||
intel_pt_insn.rel;
|
||||
|
||||
if (decoder->pgd_ip &&
|
||||
decoder->pkt_state == INTEL_PT_STATE_TIP_PGD &&
|
||||
decoder->pgd_ip(to_ip, decoder->data)) {
|
||||
/* Conditional branch leaving filter region */
|
||||
decoder->pge = false;
|
||||
decoder->continuous_period = false;
|
||||
decoder->pkt_state = INTEL_PT_STATE_IN_SYNC;
|
||||
decoder->ip = to_ip;
|
||||
decoder->state.from_ip = decoder->ip;
|
||||
decoder->state.to_ip = 0;
|
||||
return 0;
|
||||
}
|
||||
intel_pt_log_at("ERROR: Conditional branch when expecting indirect branch",
|
||||
decoder->ip);
|
||||
decoder->pkt_state = INTEL_PT_STATE_ERR_RESYNC;
|
||||
|
|
|
@ -83,6 +83,7 @@ struct intel_pt_params {
|
|||
int (*walk_insn)(struct intel_pt_insn *intel_pt_insn,
|
||||
uint64_t *insn_cnt_ptr, uint64_t *ip, uint64_t to_ip,
|
||||
uint64_t max_insn_cnt, void *data);
|
||||
bool (*pgd_ip)(uint64_t ip, void *data);
|
||||
void *data;
|
||||
bool return_compression;
|
||||
uint64_t period;
|
||||
|
|
|
@ -103,6 +103,9 @@ struct intel_pt {
|
|||
unsigned max_non_turbo_ratio;
|
||||
|
||||
unsigned long num_events;
|
||||
|
||||
char *filter;
|
||||
struct addr_filters filts;
|
||||
};
|
||||
|
||||
enum switch_state {
|
||||
|
@ -241,7 +244,7 @@ static int intel_pt_get_trace(struct intel_pt_buffer *b, void *data)
|
|||
}
|
||||
|
||||
queue = &ptq->pt->queues.queue_array[ptq->queue_nr];
|
||||
|
||||
next:
|
||||
buffer = auxtrace_buffer__next(queue, buffer);
|
||||
if (!buffer) {
|
||||
if (old_buffer)
|
||||
|
@ -264,9 +267,6 @@ static int intel_pt_get_trace(struct intel_pt_buffer *b, void *data)
|
|||
intel_pt_do_fix_overlap(ptq->pt, old_buffer, buffer))
|
||||
return -ENOMEM;
|
||||
|
||||
if (old_buffer)
|
||||
auxtrace_buffer__drop_data(old_buffer);
|
||||
|
||||
if (buffer->use_data) {
|
||||
b->len = buffer->use_size;
|
||||
b->buf = buffer->use_data;
|
||||
|
@ -276,6 +276,16 @@ static int intel_pt_get_trace(struct intel_pt_buffer *b, void *data)
|
|||
}
|
||||
b->ref_timestamp = buffer->reference;
|
||||
|
||||
/*
|
||||
* If in snapshot mode and the buffer has no usable data, get next
|
||||
* buffer and again check overlap against old_buffer.
|
||||
*/
|
||||
if (ptq->pt->snapshot_mode && !b->len)
|
||||
goto next;
|
||||
|
||||
if (old_buffer)
|
||||
auxtrace_buffer__drop_data(old_buffer);
|
||||
|
||||
if (!old_buffer || ptq->pt->sampling_mode || (ptq->pt->snapshot_mode &&
|
||||
!buffer->consecutive)) {
|
||||
b->consecutive = false;
|
||||
|
@ -541,6 +551,76 @@ out_no_cache:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool intel_pt_match_pgd_ip(struct intel_pt *pt, uint64_t ip,
|
||||
uint64_t offset, const char *filename)
|
||||
{
|
||||
struct addr_filter *filt;
|
||||
bool have_filter = false;
|
||||
bool hit_tracestop = false;
|
||||
bool hit_filter = false;
|
||||
|
||||
list_for_each_entry(filt, &pt->filts.head, list) {
|
||||
if (filt->start)
|
||||
have_filter = true;
|
||||
|
||||
if ((filename && !filt->filename) ||
|
||||
(!filename && filt->filename) ||
|
||||
(filename && strcmp(filename, filt->filename)))
|
||||
continue;
|
||||
|
||||
if (!(offset >= filt->addr && offset < filt->addr + filt->size))
|
||||
continue;
|
||||
|
||||
intel_pt_log("TIP.PGD ip %#"PRIx64" offset %#"PRIx64" in %s hit filter: %s offset %#"PRIx64" size %#"PRIx64"\n",
|
||||
ip, offset, filename ? filename : "[kernel]",
|
||||
filt->start ? "filter" : "stop",
|
||||
filt->addr, filt->size);
|
||||
|
||||
if (filt->start)
|
||||
hit_filter = true;
|
||||
else
|
||||
hit_tracestop = true;
|
||||
}
|
||||
|
||||
if (!hit_tracestop && !hit_filter)
|
||||
intel_pt_log("TIP.PGD ip %#"PRIx64" offset %#"PRIx64" in %s is not in a filter region\n",
|
||||
ip, offset, filename ? filename : "[kernel]");
|
||||
|
||||
return hit_tracestop || (have_filter && !hit_filter);
|
||||
}
|
||||
|
||||
static int __intel_pt_pgd_ip(uint64_t ip, void *data)
|
||||
{
|
||||
struct intel_pt_queue *ptq = data;
|
||||
struct thread *thread;
|
||||
struct addr_location al;
|
||||
u8 cpumode;
|
||||
u64 offset;
|
||||
|
||||
if (ip >= ptq->pt->kernel_start)
|
||||
return intel_pt_match_pgd_ip(ptq->pt, ip, ip, NULL);
|
||||
|
||||
cpumode = PERF_RECORD_MISC_USER;
|
||||
|
||||
thread = ptq->thread;
|
||||
if (!thread)
|
||||
return -EINVAL;
|
||||
|
||||
thread__find_addr_map(thread, cpumode, MAP__FUNCTION, ip, &al);
|
||||
if (!al.map || !al.map->dso)
|
||||
return -EINVAL;
|
||||
|
||||
offset = al.map->map_ip(al.map, ip);
|
||||
|
||||
return intel_pt_match_pgd_ip(ptq->pt, ip, offset,
|
||||
al.map->dso->long_name);
|
||||
}
|
||||
|
||||
static bool intel_pt_pgd_ip(uint64_t ip, void *data)
|
||||
{
|
||||
return __intel_pt_pgd_ip(ip, data) > 0;
|
||||
}
|
||||
|
||||
static bool intel_pt_get_config(struct intel_pt *pt,
|
||||
struct perf_event_attr *attr, u64 *config)
|
||||
{
|
||||
|
@ -717,6 +797,9 @@ static struct intel_pt_queue *intel_pt_alloc_queue(struct intel_pt *pt,
|
|||
params.tsc_ctc_ratio_n = pt->tsc_ctc_ratio_n;
|
||||
params.tsc_ctc_ratio_d = pt->tsc_ctc_ratio_d;
|
||||
|
||||
if (pt->filts.cnt > 0)
|
||||
params.pgd_ip = intel_pt_pgd_ip;
|
||||
|
||||
if (pt->synth_opts.instructions) {
|
||||
if (pt->synth_opts.period) {
|
||||
switch (pt->synth_opts.period_type) {
|
||||
|
@ -1767,6 +1850,8 @@ static void intel_pt_free(struct perf_session *session)
|
|||
intel_pt_free_events(session);
|
||||
session->auxtrace = NULL;
|
||||
thread__put(pt->unknown_thread);
|
||||
addr_filters__exit(&pt->filts);
|
||||
zfree(&pt->filter);
|
||||
free(pt);
|
||||
}
|
||||
|
||||
|
@ -2016,6 +2101,8 @@ static const char * const intel_pt_info_fmts[] = {
|
|||
[INTEL_PT_TSC_CTC_N] = " TSC:CTC numerator %"PRIu64"\n",
|
||||
[INTEL_PT_TSC_CTC_D] = " TSC:CTC denominator %"PRIu64"\n",
|
||||
[INTEL_PT_CYC_BIT] = " CYC bit %#"PRIx64"\n",
|
||||
[INTEL_PT_MAX_NONTURBO_RATIO] = " Max non-turbo ratio %"PRIu64"\n",
|
||||
[INTEL_PT_FILTER_STR_LEN] = " Filter string len. %"PRIu64"\n",
|
||||
};
|
||||
|
||||
static void intel_pt_print_info(u64 *arr, int start, int finish)
|
||||
|
@ -2029,12 +2116,28 @@ static void intel_pt_print_info(u64 *arr, int start, int finish)
|
|||
fprintf(stdout, intel_pt_info_fmts[i], arr[i]);
|
||||
}
|
||||
|
||||
static void intel_pt_print_info_str(const char *name, const char *str)
|
||||
{
|
||||
if (!dump_trace)
|
||||
return;
|
||||
|
||||
fprintf(stdout, " %-20s%s\n", name, str ? str : "");
|
||||
}
|
||||
|
||||
static bool intel_pt_has(struct auxtrace_info_event *auxtrace_info, int pos)
|
||||
{
|
||||
return auxtrace_info->header.size >=
|
||||
sizeof(struct auxtrace_info_event) + (sizeof(u64) * (pos + 1));
|
||||
}
|
||||
|
||||
int intel_pt_process_auxtrace_info(union perf_event *event,
|
||||
struct perf_session *session)
|
||||
{
|
||||
struct auxtrace_info_event *auxtrace_info = &event->auxtrace_info;
|
||||
size_t min_sz = sizeof(u64) * INTEL_PT_PER_CPU_MMAPS;
|
||||
struct intel_pt *pt;
|
||||
void *info_end;
|
||||
u64 *info;
|
||||
int err;
|
||||
|
||||
if (auxtrace_info->header.size < sizeof(struct auxtrace_info_event) +
|
||||
|
@ -2045,6 +2148,8 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
|
|||
if (!pt)
|
||||
return -ENOMEM;
|
||||
|
||||
addr_filters__init(&pt->filts);
|
||||
|
||||
perf_config(intel_pt_perf_config, pt);
|
||||
|
||||
err = auxtrace_queues__init(&pt->queues);
|
||||
|
@ -2069,8 +2174,7 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
|
|||
intel_pt_print_info(&auxtrace_info->priv[0], INTEL_PT_PMU_TYPE,
|
||||
INTEL_PT_PER_CPU_MMAPS);
|
||||
|
||||
if (auxtrace_info->header.size >= sizeof(struct auxtrace_info_event) +
|
||||
(sizeof(u64) * INTEL_PT_CYC_BIT)) {
|
||||
if (intel_pt_has(auxtrace_info, INTEL_PT_CYC_BIT)) {
|
||||
pt->mtc_bit = auxtrace_info->priv[INTEL_PT_MTC_BIT];
|
||||
pt->mtc_freq_bits = auxtrace_info->priv[INTEL_PT_MTC_FREQ_BITS];
|
||||
pt->tsc_ctc_ratio_n = auxtrace_info->priv[INTEL_PT_TSC_CTC_N];
|
||||
|
@ -2080,6 +2184,54 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
|
|||
INTEL_PT_CYC_BIT);
|
||||
}
|
||||
|
||||
if (intel_pt_has(auxtrace_info, INTEL_PT_MAX_NONTURBO_RATIO)) {
|
||||
pt->max_non_turbo_ratio =
|
||||
auxtrace_info->priv[INTEL_PT_MAX_NONTURBO_RATIO];
|
||||
intel_pt_print_info(&auxtrace_info->priv[0],
|
||||
INTEL_PT_MAX_NONTURBO_RATIO,
|
||||
INTEL_PT_MAX_NONTURBO_RATIO);
|
||||
}
|
||||
|
||||
info = &auxtrace_info->priv[INTEL_PT_FILTER_STR_LEN] + 1;
|
||||
info_end = (void *)info + auxtrace_info->header.size;
|
||||
|
||||
if (intel_pt_has(auxtrace_info, INTEL_PT_FILTER_STR_LEN)) {
|
||||
size_t len;
|
||||
|
||||
len = auxtrace_info->priv[INTEL_PT_FILTER_STR_LEN];
|
||||
intel_pt_print_info(&auxtrace_info->priv[0],
|
||||
INTEL_PT_FILTER_STR_LEN,
|
||||
INTEL_PT_FILTER_STR_LEN);
|
||||
if (len) {
|
||||
const char *filter = (const char *)info;
|
||||
|
||||
len = roundup(len + 1, 8);
|
||||
info += len >> 3;
|
||||
if ((void *)info > info_end) {
|
||||
pr_err("%s: bad filter string length\n", __func__);
|
||||
err = -EINVAL;
|
||||
goto err_free_queues;
|
||||
}
|
||||
pt->filter = memdup(filter, len);
|
||||
if (!pt->filter) {
|
||||
err = -ENOMEM;
|
||||
goto err_free_queues;
|
||||
}
|
||||
if (session->header.needs_swap)
|
||||
mem_bswap_64(pt->filter, len);
|
||||
if (pt->filter[len - 1]) {
|
||||
pr_err("%s: filter string not null terminated\n", __func__);
|
||||
err = -EINVAL;
|
||||
goto err_free_queues;
|
||||
}
|
||||
err = addr_filters__parse_bare_filter(&pt->filts,
|
||||
filter);
|
||||
if (err)
|
||||
goto err_free_queues;
|
||||
}
|
||||
intel_pt_print_info_str("Filter string", pt->filter);
|
||||
}
|
||||
|
||||
pt->timeless_decoding = intel_pt_timeless_decoding(pt);
|
||||
pt->have_tsc = intel_pt_have_tsc(pt);
|
||||
pt->sampling_mode = false;
|
||||
|
@ -2121,11 +2273,13 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
|
|||
pt->switch_evsel = intel_pt_find_sched_switch(session->evlist);
|
||||
if (!pt->switch_evsel) {
|
||||
pr_err("%s: missing sched_switch event\n", __func__);
|
||||
err = -EINVAL;
|
||||
goto err_delete_thread;
|
||||
}
|
||||
} else if (pt->have_sched_switch == 2 &&
|
||||
!intel_pt_find_switch(session->evlist)) {
|
||||
pr_err("%s: missing context_switch attribute flag\n", __func__);
|
||||
err = -EINVAL;
|
||||
goto err_delete_thread;
|
||||
}
|
||||
|
||||
|
@ -2149,7 +2303,9 @@ int intel_pt_process_auxtrace_info(union perf_event *event,
|
|||
if (pt->tc.time_mult) {
|
||||
u64 tsc_freq = intel_pt_ns_to_ticks(pt, 1000000000);
|
||||
|
||||
pt->max_non_turbo_ratio = (tsc_freq + 50000000) / 100000000;
|
||||
if (!pt->max_non_turbo_ratio)
|
||||
pt->max_non_turbo_ratio =
|
||||
(tsc_freq + 50000000) / 100000000;
|
||||
intel_pt_log("TSC frequency %"PRIu64"\n", tsc_freq);
|
||||
intel_pt_log("Maximum non-turbo ratio %u\n",
|
||||
pt->max_non_turbo_ratio);
|
||||
|
@ -2193,6 +2349,8 @@ err_free_queues:
|
|||
auxtrace_queues__free(&pt->queues);
|
||||
session->auxtrace = NULL;
|
||||
err_free:
|
||||
addr_filters__exit(&pt->filts);
|
||||
zfree(&pt->filter);
|
||||
free(pt);
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -34,11 +34,11 @@ enum {
|
|||
INTEL_PT_TSC_CTC_N,
|
||||
INTEL_PT_TSC_CTC_D,
|
||||
INTEL_PT_CYC_BIT,
|
||||
INTEL_PT_MAX_NONTURBO_RATIO,
|
||||
INTEL_PT_FILTER_STR_LEN,
|
||||
INTEL_PT_AUXTRACE_PRIV_MAX,
|
||||
};
|
||||
|
||||
#define INTEL_PT_AUXTRACE_PRIV_SIZE (INTEL_PT_AUXTRACE_PRIV_MAX * sizeof(u64))
|
||||
|
||||
struct auxtrace_record;
|
||||
struct perf_tool;
|
||||
union perf_event;
|
||||
|
|
|
@ -1760,20 +1760,49 @@ foreach_evsel_in_last_glob(struct perf_evlist *evlist,
|
|||
static int set_filter(struct perf_evsel *evsel, const void *arg)
|
||||
{
|
||||
const char *str = arg;
|
||||
bool found = false;
|
||||
int nr_addr_filters = 0;
|
||||
struct perf_pmu *pmu = NULL;
|
||||
|
||||
if (evsel == NULL || evsel->attr.type != PERF_TYPE_TRACEPOINT) {
|
||||
fprintf(stderr,
|
||||
"--filter option should follow a -e tracepoint option\n");
|
||||
return -1;
|
||||
if (evsel == NULL)
|
||||
goto err;
|
||||
|
||||
if (evsel->attr.type == PERF_TYPE_TRACEPOINT) {
|
||||
if (perf_evsel__append_tp_filter(evsel, str) < 0) {
|
||||
fprintf(stderr,
|
||||
"not enough memory to hold filter string\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (perf_evsel__append_filter(evsel, "&&", str) < 0) {
|
||||
while ((pmu = perf_pmu__scan(pmu)) != NULL)
|
||||
if (pmu->type == evsel->attr.type) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (found)
|
||||
perf_pmu__scan_file(pmu, "nr_addr_filters",
|
||||
"%d", &nr_addr_filters);
|
||||
|
||||
if (!nr_addr_filters)
|
||||
goto err;
|
||||
|
||||
if (perf_evsel__append_addr_filter(evsel, str) < 0) {
|
||||
fprintf(stderr,
|
||||
"not enough memory to hold filter string\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
fprintf(stderr,
|
||||
"--filter option should follow a -e tracepoint or HW tracer option\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int parse_filter(const struct option *opt, const char *str,
|
||||
|
@ -1798,7 +1827,7 @@ static int add_exclude_perf_filter(struct perf_evsel *evsel,
|
|||
|
||||
snprintf(new_filter, sizeof(new_filter), "common_pid != %d", getpid());
|
||||
|
||||
if (perf_evsel__append_filter(evsel, "&&", new_filter) < 0) {
|
||||
if (perf_evsel__append_tp_filter(evsel, new_filter) < 0) {
|
||||
fprintf(stderr,
|
||||
"not enough memory to hold filter string\n");
|
||||
return -1;
|
||||
|
|
|
@ -213,9 +213,13 @@ static int convert_exec_to_group(const char *exec, char **result)
|
|||
goto out;
|
||||
}
|
||||
|
||||
ptr2 = strpbrk(ptr1, "-._");
|
||||
if (ptr2)
|
||||
*ptr2 = '\0';
|
||||
for (ptr2 = ptr1; ptr2 != '\0'; ptr2++) {
|
||||
if (!isalnum(*ptr2) && *ptr2 != '_') {
|
||||
*ptr2 = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = e_snprintf(buf, 64, "%s_%s", PERFPROBE_GROUP, ptr1);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
|
|
@ -699,7 +699,7 @@ int probe_cache__scan_sdt(struct probe_cache *pcache, const char *pathname)
|
|||
INIT_LIST_HEAD(&sdtlist);
|
||||
ret = get_sdt_note_list(&sdtlist, pathname);
|
||||
if (ret < 0) {
|
||||
pr_debug("Failed to get sdt note: %d\n", ret);
|
||||
pr_debug4("Failed to get sdt note: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
list_for_each_entry(note, &sdtlist, note_list) {
|
||||
|
|
|
@ -955,6 +955,11 @@ static int probe_point_inline_cb(Dwarf_Die *in_die, void *data)
|
|||
dwarf_diename(in_die));
|
||||
return -ENOENT;
|
||||
}
|
||||
if (addr == 0) {
|
||||
pr_debug("%s has no valid entry address. skipped.\n",
|
||||
dwarf_diename(in_die));
|
||||
return -ENOENT;
|
||||
}
|
||||
pf->addr = addr;
|
||||
pf->addr += pp->offset;
|
||||
pr_debug("found inline addr: 0x%jx\n",
|
||||
|
@ -988,7 +993,8 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data)
|
|||
if (pp->file && strtailcmp(pp->file, dwarf_decl_file(sp_die)))
|
||||
return DWARF_CB_OK;
|
||||
|
||||
pr_debug("Matched function: %s\n", dwarf_diename(sp_die));
|
||||
pr_debug("Matched function: %s [%lx]\n", dwarf_diename(sp_die),
|
||||
(unsigned long)dwarf_dieoffset(sp_die));
|
||||
pf->fname = dwarf_decl_file(sp_die);
|
||||
if (pp->line) { /* Function relative line */
|
||||
dwarf_decl_line(sp_die, &pf->lno);
|
||||
|
@ -997,8 +1003,13 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data)
|
|||
} else if (die_is_func_instance(sp_die)) {
|
||||
/* Instances always have the entry address */
|
||||
dwarf_entrypc(sp_die, &pf->addr);
|
||||
/* But in some case the entry address is 0 */
|
||||
if (pf->addr == 0) {
|
||||
pr_debug("%s has no entry PC. Skipped\n",
|
||||
dwarf_diename(sp_die));
|
||||
param->retval = 0;
|
||||
/* Real function */
|
||||
if (pp->lazy_line)
|
||||
} else if (pp->lazy_line)
|
||||
param->retval = find_probe_point_lazy(sp_die, pf);
|
||||
else {
|
||||
skip_prologue(sp_die, pf);
|
||||
|
@ -1011,7 +1022,7 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data)
|
|||
param->retval = die_walk_instances(sp_die,
|
||||
probe_point_inline_cb, (void *)pf);
|
||||
/* This could be a non-existed inline definition */
|
||||
if (param->retval == -ENOENT && strisglob(pp->function))
|
||||
if (param->retval == -ENOENT)
|
||||
param->retval = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -345,6 +345,16 @@ static struct symbol *symbols__first(struct rb_root *symbols)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct symbol *symbols__last(struct rb_root *symbols)
|
||||
{
|
||||
struct rb_node *n = rb_last(symbols);
|
||||
|
||||
if (n)
|
||||
return rb_entry(n, struct symbol, rb_node);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct symbol *symbols__next(struct symbol *sym)
|
||||
{
|
||||
struct rb_node *n = rb_next(&sym->rb_node);
|
||||
|
@ -466,6 +476,11 @@ struct symbol *dso__first_symbol(struct dso *dso, enum map_type type)
|
|||
return symbols__first(&dso->symbols[type]);
|
||||
}
|
||||
|
||||
struct symbol *dso__last_symbol(struct dso *dso, enum map_type type)
|
||||
{
|
||||
return symbols__last(&dso->symbols[type]);
|
||||
}
|
||||
|
||||
struct symbol *dso__next_symbol(struct symbol *sym)
|
||||
{
|
||||
return symbols__next(sym);
|
||||
|
|
|
@ -259,6 +259,7 @@ struct symbol *dso__find_symbol_by_name(struct dso *dso, enum map_type type,
|
|||
struct symbol *symbol__next_by_name(struct symbol *sym);
|
||||
|
||||
struct symbol *dso__first_symbol(struct dso *dso, enum map_type type);
|
||||
struct symbol *dso__last_symbol(struct dso *dso, enum map_type type);
|
||||
struct symbol *dso__next_symbol(struct symbol *sym);
|
||||
|
||||
enum dso_type dso__type_fd(int fd);
|
||||
|
|
Загрузка…
Ссылка в новой задаче