diff options
author | Ingo Molnar <mingo@kernel.org> | 2016-09-29 19:09:58 +0200 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2016-09-29 19:09:58 +0200 |
commit | 41aad2a6d4fcdda8d73c9739daf7a9f3f49499d6 (patch) | |
tree | d679538c1fa9111ba5d604ad5ef016176c71f278 | |
parent | 6b652de2b27c0a4020ce0e8f277e782b6af76096 (diff) | |
parent | d18019a53a07e009899ff6b8dc5ec30f249360d9 (diff) | |
download | linux-41aad2a6d4fcdda8d73c9739daf7a9f3f49499d6.tar.bz2 |
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>
34 files changed, 1451 insertions, 62 deletions
diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt index 1a24f4d64328..92335193dc33 100644 --- a/tools/perf/Documentation/perf-record.txt +++ b/tools/perf/Documentation/perf-record.txt @@ -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 diff --git a/tools/perf/Documentation/perf.data-file-format.txt b/tools/perf/Documentation/perf.data-file-format.txt index fdc99fe6bbc3..b664b18d3991 100644 --- a/tools/perf/Documentation/perf.data-file-format.txt +++ b/tools/perf/Documentation/perf.data-file-format.txt @@ -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. diff --git a/tools/perf/arch/powerpc/Build b/tools/perf/arch/powerpc/Build index 54afe4a467e7..db52fa22d3a1 100644 --- a/tools/perf/arch/powerpc/Build +++ b/tools/perf/arch/powerpc/Build @@ -1 +1,2 @@ libperf-y += util/ +libperf-y += tests/ diff --git a/tools/perf/arch/powerpc/include/arch-tests.h b/tools/perf/arch/powerpc/include/arch-tests.h new file mode 100644 index 000000000000..84d8dedef2ed --- /dev/null +++ b/tools/perf/arch/powerpc/include/arch-tests.h @@ -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 diff --git a/tools/perf/arch/powerpc/include/perf_regs.h b/tools/perf/arch/powerpc/include/perf_regs.h index 75de0e92e71e..c12f4e804f66 100644 --- a/tools/perf/arch/powerpc/include/perf_regs.h +++ b/tools/perf/arch/powerpc/include/perf_regs.h @@ -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__ diff --git a/tools/perf/arch/powerpc/tests/Build b/tools/perf/arch/powerpc/tests/Build new file mode 100644 index 000000000000..d827ef384b33 --- /dev/null +++ b/tools/perf/arch/powerpc/tests/Build @@ -0,0 +1,4 @@ +libperf-$(CONFIG_DWARF_UNWIND) += regs_load.o +libperf-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o + +libperf-y += arch-tests.o diff --git a/tools/perf/arch/powerpc/tests/arch-tests.c b/tools/perf/arch/powerpc/tests/arch-tests.c new file mode 100644 index 000000000000..e24f46241f40 --- /dev/null +++ b/tools/perf/arch/powerpc/tests/arch-tests.c @@ -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, + }, +}; diff --git a/tools/perf/arch/powerpc/tests/dwarf-unwind.c b/tools/perf/arch/powerpc/tests/dwarf-unwind.c new file mode 100644 index 000000000000..0bac3137ccbd --- /dev/null +++ b/tools/perf/arch/powerpc/tests/dwarf-unwind.c @@ -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); +} diff --git a/tools/perf/arch/powerpc/tests/regs_load.S b/tools/perf/arch/powerpc/tests/regs_load.S new file mode 100644 index 000000000000..d76c9a32f327 --- /dev/null +++ b/tools/perf/arch/powerpc/tests/regs_load.S @@ -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 diff --git a/tools/perf/arch/x86/util/intel-pt.c b/tools/perf/arch/x86/util/intel-pt.c index a2412e9d883b..90fa2286edcf 100644 --- a/tools/perf/arch/x86/util/intel-pt.c +++ b/tools/perf/arch/x86/util/intel-pt.c @@ -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 const char *intel_pt_find_filter(struct perf_evlist *evlist, + struct perf_pmu *intel_pt_pmu) +{ + 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 __maybe_unused, - struct perf_evlist *evlist __maybe_unused) +intel_pt_info_priv_size(struct auxtrace_record *itr, struct perf_evlist *evlist) { - return INTEL_PT_AUXTRACE_PRIV_SIZE; + 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; } diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 2d0d69be3bf8..67d2a9003294 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -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); diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index b4fc1ab3d2a7..c298bd3e1d90 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -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: diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build index dc51bc570e51..8a4ce492f7b2 100644 --- a/tools/perf/tests/Build +++ b/tools/perf/tests/Build @@ -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 diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c index 8f6eb853aaf7..1046491de4b2 100644 --- a/tools/perf/tests/dwarf-unwind.c +++ b/tools/perf/tests/dwarf-unwind.c @@ -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 diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c index c0aba8e839aa..c5a6e0b12452 100644 --- a/tools/perf/util/auxtrace.c +++ b/tools/perf/util/auxtrace.c @@ -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; +} diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h index 09286f193532..26fb1ee5746a 100644 --- a/tools/perf/util/auxtrace.h +++ b/tools/perf/util/auxtrace.h @@ -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); diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index 5651f3c12f93..e528c40739cc 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c @@ -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) diff --git a/tools/perf/util/data-convert-bt.c b/tools/perf/util/data-convert-bt.c index 4f979bb27b6c..7123f4de32cc 100644 --- a/tools/perf/util/data-convert-bt.c +++ b/tools/perf/util/data-convert-bt.c @@ -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"); diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c index faec899435f2..41e068e94349 100644 --- a/tools/perf/util/dwarf-aux.c +++ b/tools/perf/util/dwarf-aux.c @@ -130,6 +130,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 * @tname: a string of target name @@ -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; } /** diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h index 8b6d2f83af02..8ac53bf1ec4e 100644 --- a/tools/perf/util/dwarf-aux.h +++ b/tools/perf/util/dwarf-aux.h @@ -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); diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 2880e2226fdb..8ab0d7da956b 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -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; } diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index f3225a2e6eee..380e84c3af3d 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -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); diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 323806082c58..b1503b0ecdff 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -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); diff --git a/tools/perf/util/evsel_fprintf.c b/tools/perf/util/evsel_fprintf.c index 9111e0666950..662a0a6182e7 100644 --- a/tools/perf/util/evsel_fprintf.c +++ b/tools/perf/util/evsel_fprintf.c @@ -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) diff --git a/tools/perf/util/intel-pt-decoder/intel-pt-decoder.c b/tools/perf/util/intel-pt-decoder/intel-pt-decoder.c index 8ff6c6a61291..7591a0c37473 100644 --- a/tools/perf/util/intel-pt-decoder/intel-pt-decoder.c +++ b/tools/perf/util/intel-pt-decoder/intel-pt-decoder.c @@ -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; diff --git a/tools/perf/util/intel-pt-decoder/intel-pt-decoder.h b/tools/perf/util/intel-pt-decoder/intel-pt-decoder.h index 02c38fec1c37..89399985fa4d 100644 --- a/tools/perf/util/intel-pt-decoder/intel-pt-decoder.h +++ b/tools/perf/util/intel-pt-decoder/intel-pt-decoder.h @@ -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; diff --git a/tools/perf/util/intel-pt.c b/tools/perf/util/intel-pt.c index b9cc353cace2..dc041d4368c8 100644 --- a/tools/perf/util/intel-pt.c +++ b/tools/perf/util/intel-pt.c @@ -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; } diff --git a/tools/perf/util/intel-pt.h b/tools/perf/util/intel-pt.h index 0065949df693..e13b14e5a37b 100644 --- a/tools/perf/util/intel-pt.h +++ b/tools/perf/util/intel-pt.h @@ -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; diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 2eb8b1ed4cc8..33546c3ac1fe 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -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; diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index bc60ce49720b..fcfbef07b92d 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -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; diff --git a/tools/perf/util/probe-file.c b/tools/perf/util/probe-file.c index 6f931e442f14..436b64731f65 100644 --- a/tools/perf/util/probe-file.c +++ b/tools/perf/util/probe-file.c @@ -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) { diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index 8daca4fc1f8d..df4debe564da 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -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; } diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 19c9c558454f..aecff69a510d 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -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); diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 0dacfb7d5b67..d964844eb314 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -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); |