diff options
Diffstat (limited to 'tools/perf')
115 files changed, 5243 insertions, 850 deletions
diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt index c5913cf59c98..7f820f69740f 100644 --- a/tools/perf/Documentation/perf-bench.txt +++ b/tools/perf/Documentation/perf-bench.txt @@ -58,6 +58,9 @@ SUBSYSTEM 'numa':: NUMA scheduling and MM benchmarks. +'atomic':: + Atomic operation benchmarks. + 'futex':: Futex stressing benchmarks. @@ -283,6 +286,41 @@ SUITES FOR 'numa' *mem*:: Suite for evaluating NUMA workloads. +SUITES FOR 'atomic' +~~~~~~~~~~~~~~~~~~~ +*cas*:: +Suite for evaluating compare-and-swap (CAS) atomic operations under +multi-threaded contention. + +*inc*:: +Suite for evaluating atomic fetch-add (increment) operations under +multi-threaded contention. + +Options of *cas* and *inc* +^^^^^^^^^^^^^^^^^^^^^^^^^^ +-T:: +--threads=:: +Number of threads contending for the shared counter (default: 2, or +1 on a single-CPU machine). + +-R:: +--runtime=:: +Runtime of each repeat in seconds (default: 2). + +The per-thread fastest/slowest/avg ops summary is only printed when +running with more than one thread. + +The average throughput is annotated with its relative deviation +("+- x%"), i.e. the standard error of the mean over the measured +repeats. A small value means the average is reproducible; it does not +reflect per-thread fairness, which the per-thread ops summary shows. + +Note that the cas benchmark counts successful compare-and-swap +operations only; retries and the loads between them are additional +atomic instructions not included in the ops/sec count, so cas and +inc throughputs are not directly comparable at the instruction +level. + SUITES FOR 'futex' ~~~~~~~~~~~~~~~~~~ *hash*:: diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt index 859df74e62ef..f66ccd77bfe7 100644 --- a/tools/perf/Documentation/perf-test.txt +++ b/tools/perf/Documentation/perf-test.txt @@ -8,7 +8,7 @@ perf-test - Runs sanity tests. SYNOPSIS -------- [verse] -'perf test [<options>] [{list <test-name-fragment>|[<test-name-fragments>|<test-numbers>]}]' +'perf test [<options>] [{list|requisites} [<test-name-fragment>]|[<test-name-fragments>|<test-numbers>]]' DESCRIPTION ----------- @@ -18,9 +18,39 @@ also will look for a directory with more tests in the form of scripts. To get a list of available tests use 'perf test list', specifying a test name fragment will show all tests that have it. +To get a list of what the tests need, i.e. of their requisites (see below), use +'perf test requisites', that takes the same test name fragments and numbers as +'list'. + To run just specific tests, inform test name fragments or the numbers obtained from 'perf test list'. +REQUISITES +---------- +Shell tests run perf, that maps ring buffers, so they are taken to need room +in the per user budget set in kernel.perf_event_mlock_kb, shared by the tests +running at the same time, that take turns using it. Tests state what they +need, or waive, in a comma separated list in the parens ending their +description line, e.g.: + + # perf stat tests (no_mlock) + +Each requisite is named in that list, "mlock" being room in the budget set in +kernel.perf_event_mlock_kb, and takes a "no_" prefix to waive it: "(mlock)" +states the default, "(no_mlock)" waives it, for tests that don't map ring +buffers. Leaving a requisite out states nothing about it, so what happens +then is up to each kind of test: shell ones run perf, that maps ring buffers, +so they are taken to need room in the budget unless they waive it, while C +suites say which ones map, see DEFINE_SUITE_NEEDS_MLOCK() in tests.h. +"exclusive" is a catch-all: the test can't run alongside any other one, so +it runs alone, in sequence, and implies every other requisite; it can't be +waived. More requisites are to come, each stating what a test needs, till +the catch-all isn't needed anymore. + +Use 'perf test requisites' to list what each test needs. That listing states +the needs, not the requisites form used in the scripts: a test listed as +needing "mlock" is one whose script doesn't say "(no_mlock)". + OPTIONS ------- -s:: @@ -36,8 +66,8 @@ OPTIONS -S:: --sequential:: - Run all tests one after the other. By default "exclusive" - tests are run sequentially, but other tests are run in + Run all tests one after the other. By default the tests asking to be + "exclusive" (see REQUISITES) run sequentially, but other tests run in parallel to speed execution. -r:: diff --git a/tools/perf/Makefile b/tools/perf/Makefile index c1818bd5f287..00baf90e5bda 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -137,6 +137,9 @@ build-test-tarball: check-headers: @./check-headers.sh +sync-headers: + @./scripts/sync-headers.sh $(SYNC_HEADERS_ARGS) + # # All other targets get passed through: # @@ -144,4 +147,4 @@ check-headers: $(print_msg) $(make) -.PHONY: all clean tags TAGS FORCE Makefile build-test build-test-tarball check-headers +.PHONY: all clean tags TAGS FORCE Makefile build-test build-test-tarball check-headers sync-headers diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index cafc7be79643..b4764f69f47f 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -417,6 +417,10 @@ ifeq ($(feature-pthread-barrier), 1) CFLAGS += -DHAVE_PTHREAD_BARRIER endif +ifeq ($(feature-pthread-mutex-robust), 1) + CFLAGS += -DHAVE_PTHREAD_MUTEX_ROBUST +endif + ifndef NO_BIONIC $(call feature_check,bionic) ifeq ($(feature-bionic), 1) diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index 2d9379fea267..b74b534e1153 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -870,6 +870,9 @@ help: @echo '' @echo 'Perf maintainer targets:' @echo ' clean - clean all binary objects and build output' + @echo ' check-headers - check which header copies are out of sync with the kernel' + @echo ' sync-headers - sync those header copies with the kernel, creating commits' + @echo ' (SYNC_HEADERS_ARGS="..." passes options to the script)' DOC_TARGETS := doc man html info pdf diff --git a/tools/perf/bench/Build b/tools/perf/bench/Build index 67b76fe20ba6..c64c52468d3d 100644 --- a/tools/perf/bench/Build +++ b/tools/perf/bench/Build @@ -3,6 +3,7 @@ perf-bench-y += sched-pipe.o perf-bench-y += sched-seccomp-notify.o perf-bench-y += syscall.o perf-bench-y += mem-functions.o +perf-bench-y += atomic.o perf-bench-y += futex.o perf-bench-y += futex-hash.o perf-bench-y += futex-wake.o diff --git a/tools/perf/bench/atomic.c b/tools/perf/bench/atomic.c new file mode 100644 index 000000000000..8cad20c7e802 --- /dev/null +++ b/tools/perf/bench/atomic.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Changbin Du <changbin.du@gmail.com> + * + * Benchmarks for atomic read-modify-write operations. + * + * Measures throughput (ops/sec) of contended atomic operations on a + * single shared counter across multiple threads. Each thread performs + * a hot loop of the selected operation -- compare-and-swap (cas) or + * fetch-add (inc) -- competing with all other threads for the same + * cache line. Threads run for a fixed runtime, so a system that + * cannot make progress under heavy contention reports a low (or + * zero) throughput instead of hanging. + */ +#include "bench.h" +#include <linux/compiler.h> +#include "../util/debug.h" + +#ifndef HAVE_PTHREAD_BARRIER +int bench_atomic_cas(int argc __maybe_unused, const char **argv __maybe_unused) +{ + pr_err("%s: pthread_barrier_t unavailable, disabling this test...\n", __func__); + return 0; +} + +int bench_atomic_inc(int argc __maybe_unused, const char **argv __maybe_unused) +{ + pr_err("%s: pthread_barrier_t unavailable, disabling this test...\n", __func__); + return 0; +} +#else /* HAVE_PTHREAD_BARRIER */ +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> +#include <unistd.h> +#include <pthread.h> +#include <time.h> +#include <inttypes.h> +#include <err.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/time64.h> +#include <perf/cpumap.h> +#include <subcmd/parse-options.h> +#include "../util/stat.h" + +static unsigned int threads; /* 0: default to min(2, online CPUs) */ +static unsigned int runtime = 2; /* seconds per repeat */ + +static const struct option options[] = { + OPT_UINTEGER('T', "threads", &threads, + "Number of contending threads (default: 2, or 1 on single CPU)"), + OPT_UINTEGER('R', "runtime", &runtime, + "Runtime of each repeat in seconds (default: 2)"), + OPT_END() +}; + +static const char * const cas_usage[] = { + "perf bench atomic cas <options>", + NULL +}; + +static const char * const inc_usage[] = { + "perf bench atomic inc <options>", + NULL +}; + +enum atomic_op { + OP_CAS, + OP_INC, +}; + +static int bench_op = OP_CAS; + +/* + * The termination flag is polled between batches only, so that the + * check does not perturb the measured loop. + */ +#define BATCH_OPS 1024 + +static unsigned long shared_counter __aligned(64); +static pthread_barrier_t start_barrier __aligned(64); +static bool done; + +struct worker_stats { + u64 runtime_ns; + u64 ops; +}; + +static void cas_batch(void) +{ + unsigned long i; + + /* + * Each iteration counts as one successful compare-and-swap; + * retries and the loads between them are not counted. + */ + for (i = 0; i < BATCH_OPS; i++) { + unsigned long old_val, new_val; + + old_val = __atomic_load_n(&shared_counter, __ATOMIC_RELAXED); + do { + new_val = old_val + 1; + } while (!__atomic_compare_exchange_n(&shared_counter, &old_val, + new_val, false, + __ATOMIC_RELAXED, + __ATOMIC_RELAXED)); + } +} + +static void inc_batch(void) +{ + unsigned long i; + + for (i = 0; i < BATCH_OPS; i++) + __atomic_fetch_add(&shared_counter, 1, __ATOMIC_RELAXED); +} + +static void *worker(void *arg) +{ + struct worker_stats *ws = (struct worker_stats *)arg; + struct timespec tstart, tend; + u64 ops = 0; + + pthread_barrier_wait(&start_barrier); + + clock_gettime(CLOCK_MONOTONIC, &tstart); + while (!__atomic_load_n(&done, __ATOMIC_RELAXED)) { + switch (bench_op) { + case OP_CAS: + cas_batch(); + break; + case OP_INC: + inc_batch(); + break; + default: + BUG_ON(1); + break; + } + ops += BATCH_OPS; + } + clock_gettime(CLOCK_MONOTONIC, &tend); + + ws->runtime_ns = (u64)(tend.tv_sec - tstart.tv_sec) * NSEC_PER_SEC + + (tend.tv_nsec - tstart.tv_nsec); + ws->ops = ops; + + return NULL; +} + +static void print_default_format(struct worker_stats *wstats, + struct stats *throughput_stats) +{ + double thru_avg, thru_stddev; + + printf(" Threads: %u, runtime: %u sec, repeats: %u (warmup: 1)\n", + threads, runtime, bench_repeat); + + thru_avg = avg_stats(throughput_stats); + thru_stddev = stddev_stats(throughput_stats); + + printf(" Avg throughput: %'.0f ops/sec (+- %.2f%%)\n", + thru_avg, rel_stddev_stats(thru_stddev, thru_avg)); + + /* + * Per-thread operation counts from the last measured repeat show + * whether any thread failed to make progress under contention. + */ + if (threads > 1) { + u64 min_ops = UINT64_MAX, max_ops = 0, total_ops = 0; + + for (unsigned int t = 0; t < threads; t++) { + if (wstats[t].ops < min_ops) + min_ops = wstats[t].ops; + if (wstats[t].ops > max_ops) + max_ops = wstats[t].ops; + total_ops += wstats[t].ops; + } + + printf(" Per-thread ops (last repeat):\n"); + printf(" fastest: %'" PRIu64 " ops\n", max_ops); + printf(" slowest: %'" PRIu64 " ops\n", min_ops); + printf(" avg: %'" PRIu64 " ops\n", total_ops / threads); + } +} + +static int run_benchmark(void) +{ + pthread_t *thread_ids; + struct worker_stats *wstats; + struct stats throughput_stats; + unsigned int r, t; + + thread_ids = calloc(threads, sizeof(pthread_t)); + if (!thread_ids) + err(EXIT_FAILURE, "calloc"); + + wstats = calloc(threads, sizeof(struct worker_stats)); + if (!wstats) + err(EXIT_FAILURE, "calloc"); + + init_stats(&throughput_stats); + + for (r = 0; r < bench_repeat + 1; r++) { + u64 max_runtime_ns = 0, total_ops = 0; + double throughput; + + shared_counter = 0; + __atomic_store_n(&done, false, __ATOMIC_RELAXED); + pthread_barrier_init(&start_barrier, NULL, threads); + + for (t = 0; t < threads; t++) { + if (pthread_create(&thread_ids[t], NULL, + worker, &wstats[t])) + err(EXIT_FAILURE, "pthread_create"); + } + + sleep(runtime); + __atomic_store_n(&done, true, __ATOMIC_RELAXED); + + for (t = 0; t < threads; t++) + pthread_join(thread_ids[t], NULL); + + pthread_barrier_destroy(&start_barrier); + + for (t = 0; t < threads; t++) { + total_ops += wstats[t].ops; + if (wstats[t].runtime_ns > max_runtime_ns) + max_runtime_ns = wstats[t].runtime_ns; + } + + /* + * Exclude the first repeat (warmup) from statistics so + * that cache-cold and lazy-init effects are not counted. + */ + if (r > 0 && max_runtime_ns) { + throughput = (double)total_ops / + ((double)max_runtime_ns / NSEC_PER_SEC); + update_stats(&throughput_stats, (u64)throughput); + } + } + + switch (bench_format) { + case BENCH_FORMAT_DEFAULT: + print_default_format(wstats, &throughput_stats); + break; + + case BENCH_FORMAT_SIMPLE: + printf("%.0f\n", avg_stats(&throughput_stats)); + break; + + default: + fprintf(stderr, "Unknown format: %d\n", bench_format); + exit(EXIT_FAILURE); + } + + free(thread_ids); + free(wstats); + + return 0; +} + +static int run_atomic_bench(int argc, const char **argv, + const char * const *usage) +{ + struct perf_cpu_map *cpu; + + if (parse_options(argc, argv, options, usage, 0)) { + usage_with_options(usage, options); + exit(EXIT_FAILURE); + } + + if (!threads) { /* default to 2 threads (1 on a single-CPU machine) */ + cpu = perf_cpu_map__new_online_cpus(); + threads = (cpu && perf_cpu_map__nr(cpu) > 1) ? 2 : 1; + perf_cpu_map__put(cpu); + } + + if (runtime < 1) { + fprintf(stderr, "Invalid runtime: %u\n", runtime); + return 1; + } + + return run_benchmark(); +} + +int bench_atomic_cas(int argc, const char **argv) +{ + bench_op = OP_CAS; + return run_atomic_bench(argc, argv, cas_usage); +} + +int bench_atomic_inc(int argc, const char **argv) +{ + bench_op = OP_INC; + return run_atomic_bench(argc, argv, inc_usage); +} +#endif /* HAVE_PTHREAD_BARRIER */ diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h index 8519eb5a42fa..85e3ac471875 100644 --- a/tools/perf/bench/bench.h +++ b/tools/perf/bench/bench.h @@ -30,6 +30,8 @@ int bench_mem_memcpy(int argc, const char **argv); int bench_mem_memset(int argc, const char **argv); int bench_mem_mmap(int argc, const char **argv); int bench_mem_find_bit(int argc, const char **argv); +int bench_atomic_cas(int argc, const char **argv); +int bench_atomic_inc(int argc, const char **argv); int bench_futex_hash(int argc, const char **argv); int bench_futex_wake(int argc, const char **argv); int bench_futex_wake_parallel(int argc, const char **argv); diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c index 02d47913cc6a..62bebdedd3b8 100644 --- a/tools/perf/builtin-bench.c +++ b/tools/perf/builtin-bench.c @@ -14,6 +14,7 @@ * syscall ... System call performance * mem ... memory access performance * numa ... NUMA scheduling and MM performance + * atomic ... Atomic operation performance * futex ... Futex performance * epoll ... Event poll performance */ @@ -115,6 +116,13 @@ static const struct bench uprobe_benchmarks[] = { { NULL, NULL, NULL }, }; +static const struct bench atomic_benchmarks[] = { + { "cas", "Benchmark CAS (compare-and-swap) operations", bench_atomic_cas }, + { "inc", "Benchmark atomic fetch-add operations", bench_atomic_inc }, + { "all", "Run all atomic benchmarks", NULL }, + { NULL, NULL, NULL } +}; + struct collection { const char *name; const char *summary; @@ -128,6 +136,7 @@ static const struct collection collections[] = { #ifdef HAVE_LIBNUMA_SUPPORT { "numa", "NUMA scheduling and MM benchmarks", numa_benchmarks }, #endif + { "atomic", "Atomic operation benchmarks", atomic_benchmarks }, {"futex", "Futex stressing benchmarks", futex_benchmarks }, #ifdef HAVE_EVENTFD_SUPPORT {"epoll", "Epoll stressing benchmarks", epoll_benchmarks }, diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c index 4222ac784fab..f51fa23f3bf1 100644 --- a/tools/perf/builtin-c2c.c +++ b/tools/perf/builtin-c2c.c @@ -2155,6 +2155,11 @@ static int c2c_hists__reinit(struct c2c_hists *c2c_hists, return ret; } +static void c2c_hists__exit(struct c2c_hists *c2c_hists) +{ + perf_hpp__reset_output_field(&c2c_hists->list); +} + #define DISPLAY_LINE_LIMIT 0.001 static u8 filter_display(u32 val, u32 sum) @@ -2563,7 +2568,7 @@ static void print_pareto(FILE *out, struct perf_env *env) ret = hpp_list__parse(&hpp_list, cl_output, /*evlist=*/NULL, env); if (WARN_ONCE(ret, "failed to setup sort entries\n")) - return; + goto out; nd = rb_first_cached(&c2c.hists.hists.entries); @@ -2577,6 +2582,9 @@ static void print_pareto(FILE *out, struct perf_env *env) c2c_he = container_of(he, struct c2c_hist_entry, he); print_cacheline(c2c_he->hists, he, &hpp_list, out); } + +out: + perf_hpp__reset_output_field(&hpp_list); } static void print_c2c_info(FILE *out, struct perf_session *session) @@ -3378,12 +3386,12 @@ static int perf_c2c__report(int argc, const char **argv) err = setup_nodes(session); if (err) { pr_err("Failed setup nodes\n"); - goto out_session; + goto out_hists; } err = mem2node__init(&c2c.mem2node, env); if (err) - goto out_session; + goto out_hists; err = setup_callchain(session->evlist); if (err) @@ -3525,6 +3533,8 @@ out_str: free(output_str); out_mem2node: mem2node__exit(&c2c.mem2node); +out_hists: + c2c_hists__exit(&c2c.hists); out_session: perf_session__delete(session); out: diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c index c5e82c078848..67eb3f667c99 100644 --- a/tools/perf/builtin-kvm.c +++ b/tools/perf/builtin-kvm.c @@ -1044,7 +1044,7 @@ static void show_timeofday(void) gettimeofday(&tv, NULL); if (localtime_r(&tv.tv_sec, <ime)) { strftime(date, sizeof(date), "%H:%M:%S", <ime); - pr_info("%s.%06ld", date, tv.tv_usec); + pr_info("%s.%06ld", date, (long)tv.tv_usec); } else pr_info("00:00:00.000000"); diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index d5c0d55cb82d..778923c92f42 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -975,6 +975,7 @@ static int report_lock_contention_begin_event(struct perf_sample *sample) struct machine *machine = &session->machines.host; struct map *kmap; struct symbol *sym; + u64 *callstack = NULL; ret = get_key_by_aggr_mode(&key, addr, sample); if (ret < 0) @@ -1010,33 +1011,6 @@ static int report_lock_contention_begin_event(struct perf_sample *sample) } } - ls = lock_stat_find(key); - if (!ls) { - char buf[128]; - const char *name = ""; - - switch (aggr_mode) { - case LOCK_AGGR_ADDR: - sym = machine__find_kernel_symbol(machine, key, &kmap); - if (sym) - name = sym->name; - break; - case LOCK_AGGR_CALLER: - name = buf; - if (lock_contention_caller(sample, buf, sizeof(buf)) < 0) - name = "Unknown"; - break; - case LOCK_AGGR_CGROUP: - case LOCK_AGGR_TASK: - default: - break; - } - - ls = lock_stat_findnew(key, name, flags); - if (!ls) - return -ENOMEM; - } - if (filters.nr_types) { bool found = false; @@ -1066,7 +1040,7 @@ static int report_lock_contention_begin_event(struct perf_sample *sample) } if (needs_callstack()) { - u64 *callstack = get_callstack(sample, max_stack_depth); + callstack = get_callstack(sample, max_stack_depth); if (callstack == NULL) return 0; @@ -1074,7 +1048,38 @@ static int report_lock_contention_begin_event(struct perf_sample *sample) free(callstack); return 0; } + } + + ls = lock_stat_find(key); + if (!ls) { + char buf[128]; + const char *name = ""; + + switch (aggr_mode) { + case LOCK_AGGR_ADDR: + sym = machine__find_kernel_symbol(machine, key, &kmap); + if (sym) + name = sym->name; + break; + case LOCK_AGGR_CALLER: + name = buf; + if (lock_contention_caller(sample, buf, sizeof(buf)) < 0) + name = "Unknown"; + break; + case LOCK_AGGR_CGROUP: + case LOCK_AGGR_TASK: + default: + break; + } + + ls = lock_stat_findnew(key, name, flags); + if (!ls) { + free(callstack); + return -ENOMEM; + } + } + if (callstack) { if (ls->callstack == NULL) ls->callstack = callstack; else diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py index cc143f7e7cdf..080e04b51b8c 100755 --- a/tools/perf/pmu-events/jevents.py +++ b/tools/perf/pmu-events/jevents.py @@ -35,7 +35,7 @@ _pending_metrics: list["JsonEvent"] = [] # Name of metrics table to be written out _pending_metrics_tblname: Optional[str] = None # Global BigCString shared by all structures. -_bcs = None +_bcs: "BigCString" # Map from the name of a metric group to a description of the group. _metricgroups: Dict[str, str] = {} # Order specific JsonEvent attributes will be visited. @@ -262,7 +262,7 @@ class JsonEvent: return fixed[name.lower()] return event - def unit_to_pmu(unit: Optional[str]) -> Optional[str]: + def unit_to_pmu(unit: Optional[str]) -> str: """Convert a JSON Unit to Linux PMU name.""" if not unit: return 'default_core' diff --git a/tools/perf/python/treport.py b/tools/perf/python/treport.py index e02121475370..fb583786b17a 100755 --- a/tools/perf/python/treport.py +++ b/tools/perf/python/treport.py @@ -638,8 +638,9 @@ class ReportApp(App): @work(thread=True, exclusive=True) def load_profile(self) -> None: """Build the profile in a thread so the UI stays responsive.""" - profile = self.loading - assert profile is not None + loading = self.loading + assert loading is not None + profile = loading def progress() -> None: try: diff --git a/tools/perf/python/ttimechart.py b/tools/perf/python/ttimechart.py index 34ecf84e68c1..d7ba0276a9fb 100755 --- a/tools/perf/python/ttimechart.py +++ b/tools/perf/python/ttimechart.py @@ -1676,14 +1676,13 @@ class TimechartApp(App): def goto_task(self, tid: int, time: Optional[float]) -> None: """Select the task in the tasks or I/O view, optionally moving the cursor.""" - for tab_id, view_id, row_type in (("tasks", "#tasks_view", TaskRow), - ("io", "#io_view", IoRow)): + for tab_id, view_id in (("tasks", "#tasks_view"), ("io", "#io_view")): views = self.query(view_id) if not views: continue view = views.first(TimelineView) for idx, row in enumerate(view.rows): - if isinstance(row, row_type) and row.task.tid == tid: + if isinstance(row, (TaskRow, IoRow)) and row.task.tid == tid: self.query_one(TabbedContent).active = tab_id if time is not None: self.window = self.window.with_cursor(time) diff --git a/tools/perf/scripts/sync-headers.sh b/tools/perf/scripts/sync-headers.sh new file mode 100755 index 000000000000..01e2d108432f --- /dev/null +++ b/tools/perf/scripts/sync-headers.sh @@ -0,0 +1,1676 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2026, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com> +# +# Sync tools/ header copies with the kernel sources, generate beauty +# string table diffs, verify perf still builds, and create git commits. + +set -euo pipefail + +YELLOW='\033[0;33m' +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +PERF_DIR=$(cd "$(dirname "$0")/.." && pwd) +KERNEL_DIR=$(cd "$PERF_DIR/../.." && pwd) +DRY_RUN=0 +BUILD=1 +COMMIT=1 +FORCE=0 +OUTPUT_DIR="${OUTPUT_DIR:-${TMPDIR:-/tmp}/build/perf-tools-$(id -u)}" +SIGN_OFF_NAME="${SIGN_OFF_NAME:-}" +SIGN_OFF_EMAIL="${SIGN_OFF_EMAIL:-}" +HEADER_FILTER="" +VERBOSE=0 + +# Track build successes and failures for end-of-run summary +declare -a SYNCED_OK=() +declare -a BUILD_FAILED=() +declare -a COPY_FAILED=() +declare -a COMMIT_FAILED=() +declare -a TOOLS_AHEAD=() +declare -a HAND_SYNC=() +declare -a MANUAL=() +declare -a FORCED=() + +info() { echo -e "${GREEN}==>${NC} $*"; } +warn() { echo -e "${YELLOW}Warning:${NC} $*"; } +err() { echo -e "${RED}Error:${NC} $*" >&2; } +detail(){ [[ $VERBOSE -eq 1 ]] && echo -e " $*"; return 0; } + +usage() { + local status="${1:-0}" + + cat <<EOF +Usage: $(basename "$0") [OPTIONS] + +Sync tools/ header copies with the kernel sources and create commits. + +Options: + -n, --dry-run Show what would be done without making changes + -N, --no-build Skip the perf build verification step + -C, --no-commit Copy files but do not create git commits + -f, --filter PAT Only process headers matching PAT (substring match + against the kernel-side path) + -F, --force Sync also when both copies have content the other + lacks, after manual investigation; tools/ additions + dropped are recorded in the commit + -o, --output DIR Build output directory (default: $OUTPUT_DIR) + -v, --verbose Show more details + -h, --help Show this help + +Environment: + SIGN_OFF_NAME Override git user.name for Signed-off-by + SIGN_OFF_EMAIL Override git user.email for Signed-off-by + OUTPUT_DIR Build output directory + +Through make: + SYNC_HEADERS_ARGS Options to pass to this script, e.g.: + make -C tools/perf sync-headers SYNC_HEADERS_ARGS='-n' + +The script: + 1. Runs 'make -C tools/perf check-headers' to find stale copies. + 2. For each stale header, identifies the kernel commits that changed it, + including the merges that brought content of their own. + 3. Copies the kernel header into the tools/ tree, skipping it when only + the tools/ copy changed (it is ahead) or when it carries intentional + edits, and stopping for manual investigation when both sides have + content the other lacks. + 4. For beauty headers, runs the relevant beauty script(s) before and + after, showing new/changed string table entries. + 5. Builds perf and the other consumers of the header to verify nothing + breaks. + 6. Creates a git commit per header (or group) with the standard commit + message format. + +EOF + exit "$status" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + -n|--dry-run) DRY_RUN=1; shift ;; + -N|--no-build) BUILD=0; shift ;; + -C|--no-commit) COMMIT=0; shift ;; + -f|--filter) + [[ $# -ge 2 ]] || { err "$1 needs an argument"; exit 1; } + HEADER_FILTER="$2"; shift 2 ;; + -F|--force) FORCE=1; shift ;; + -o|--output) + [[ $# -ge 2 ]] || { err "$1 needs an argument"; exit 1; } + OUTPUT_DIR="$2"; shift 2 ;; + -v|--verbose) VERBOSE=1; shift ;; + -h|--help) usage ;; + *) err "Unknown option: $1"; usage 1 ;; + esac +done + +# make -C resolves a relative O= from the directory it changes to, so pin +# the build directory down before main() moves into the kernel tree. +case "$OUTPUT_DIR" in +/*) ;; +*) OUTPUT_DIR="$PWD/$OUTPUT_DIR" ;; +esac + +sign_off() { + local name="${SIGN_OFF_NAME:-$(git config user.name)}" + local email="${SIGN_OFF_EMAIL:-$(git config user.email)}" + echo "Signed-off-by: $name <$email>" +} + +# --- Detect stale headers ------------------------------------------------ + +detect_stale_headers() { + local tmpfile + tmpfile=$(mktemp) + + # check-headers.sh must be run from tools/perf/ (it does cd ../.. internally) + (cd "$PERF_DIR" && bash check-headers.sh 2>/dev/null) | \ + grep '^ *diff -u ' | \ + sed 's/^ *diff -u //' > "$tmpfile" + + if [[ ! -s "$tmpfile" ]]; then + rm -f "$tmpfile" + return 1 + fi + + cat "$tmpfile" + rm -f "$tmpfile" + return 0 +} + +# --- Classify a header --------------------------------------------------- +# +# Returns via globals: +# HDR_CLASS: "files" | "beauty" +classify_header() { + local tools_path="$1" + + if [[ "$tools_path" == tools/perf/trace/beauty/* ]]; then + HDR_CLASS="beauty" + else + HDR_CLASS="files" + fi +} + +# --- Find kernel commits that changed a header since our copy ------------ + +# Copies checked with -I or hunk-ignore rules in check-headers.sh carry +# intentional edits, so a sync commit never leaves the two blobs equal. +has_ignore_rules() { + local kernel_path="$1" + local check_headers="$PERF_DIR/check-headers.sh" + + grep -Eq "^[[:space:]]*(check[[:space:]]+${kernel_path}[[:space:]]+.*-I|check_ignore_some_hunks[[:space:]]+${kernel_path}([[:space:]]|$))" \ + "$check_headers" 2>/dev/null && return 0 + sed -n '/^declare -a SYNC_CHECK_FILES=/,/)/p' "$check_headers" 2>/dev/null | \ + grep -Fq "\"$kernel_path\"" +} + +# The last commit that left the two copies equal: an in-tree kernel commit +# can update both files while an earlier kernel change is still missing. +find_sync_point() { + local kernel_path="$1" + local tools_path="$2" + local -a candidates blobs + local i + + # Files with ignore rules carry intentional edits and will never have + # identical blobs; fall back to the last commit touching tools/. + if ! has_ignore_rules "$kernel_path"; then + mapfile -t candidates < <(git log --no-merges --format='%H' -- "$kernel_path" "$tools_path" 2>/dev/null) + mapfile -t blobs < <( + printf '%s\n' "${candidates[@]}" | \ + awk -v k="$kernel_path" -v t="$tools_path" '{ print $1 ":" k ORS $1 ":" t }' | \ + git cat-file --batch-check) + + for ((i = 0; i < ${#candidates[@]}; i++)); do + # "<spec> missing" keeps the ':' of the spec as its first field, + # which no object id has. + if [[ "${blobs[2*i]%% *}" == "${blobs[2*i+1]%% *}" && \ + "${blobs[2*i]%% *}" != *:* ]]; then + echo "${candidates[i]}" + return + fi + done + fi + + git log --no-merges --format='%H' -1 -- "$tools_path" 2>/dev/null +} + +# A kernel commit that changed both the header and the tools copy already +# carried its change into tools/, so it has nothing left to pick up here. +find_kernel_commits() { + local kernel_path="$1" + local tools_path="$2" + local sync_point="$3" + local copied sha + + copied=$(git log --no-merges --format='%H' \ + "${sync_point}..HEAD" -- "$tools_path" 2>/dev/null) + [[ -n "$copied" ]] && copied+=$'\n' + + while IFS= read -r sha; do + [[ "$copied" == *"$sha"$'\n'* ]] && continue + echo "$sha" + done < <(git log --no-merges --format='%H' \ + "${sync_point}..HEAD" -- "$kernel_path" 2>/dev/null) +} + +# The lines the kernel copy has and the tools/ copy lacks, as "start,end" +# ranges on the kernel side. +added_line_ranges() { + local kernel_path="$1" + local tools_path="$2" + local out + + out=$(diff -U0 "$tools_path" "$kernel_path" 2>/dev/null) || true + printf '%s\n' "$out" | awk ' + /^@@/ { + split($3, a, ","); + start = substr(a[1], 2); + count = (a[2] == "" ? 1 : a[2]); + if (count > 0) + print start "," start + count - 1; + }' +} + +# A merge that resolves a conflict brings content of its own, e.g. a +# capability renumbered to avoid clashing with another one; git log +# --no-merges does not see those, so credit the merge with the lines it +# introduced. +merge_pickup_commits() { + local kernel_path="$1" + local tools_path="$2" + local sync_point="$3" + local range sha + local -a blame_args=() + local -a shas=() + + [[ -n "$sync_point" ]] || return 0 + + while read -r range; do + [[ -z "$range" ]] && continue + blame_args+=(-L "$range") + done < <(added_line_ranges "$kernel_path" "$tools_path") + + if [[ ${#blame_args[@]} -eq 0 ]]; then + return 0 + fi + + # Blame only what changed since the last sync, the lines older than + # it come out as the boundary commit and are not to be picked up. + while read -r sha; do + [[ -z "$sha" ]] && continue + shas+=("$sha") + done < <(git blame --porcelain "${sync_point}..HEAD" \ + "${blame_args[@]}" -- "$kernel_path" 2>/dev/null | + awk ' + /^[0-9a-f]{40} / { + sha = $1; + if (sha !~ /^0+$/) + order[++n] = sha; + next; + } + /^boundary$/ { + skip[sha] = 1; + next; + } + END { + for (i = 1; i <= n; i++) + if (!(order[i] in skip)) + print order[i]; + }') + + if [[ ${#shas[@]} -eq 0 ]]; then + return 0 + fi + + printf '%s\n' "${shas[@]}" | awk 'NF && !seen[$0]++' | + git rev-list --no-walk=unsorted --stdin --min-parents=2 2>/dev/null +} + +# Render commit ids with FORMAT at the usual 12 chars, keeping their order. +format_commits() { + local format="$1" + local ids="$2" + + [[ -n "$ids" ]] || return 0 + printf '%s\n' "$ids" | git log --no-walk=unsorted --abbrev=12 \ + --format="$format" --stdin 2>/dev/null +} + +# "add" when only the kernel changed since the sync point, "delete" when +# only the tools/ copy did, "both" when each side did; the sync point is +# where the blobs were equal, so comparing them is exact. +sync_direction() { + local kernel_path="$1" + local tools_path="$2" + local sync_point="$3" + local tools_base kernel_base tools_now kernel_now + + [[ -n "$sync_point" ]] || { echo add; return; } + tools_base=$(git -C "$KERNEL_DIR" rev-parse -q --verify "$sync_point:$tools_path") || { + echo add + return + } + kernel_base=$(git -C "$KERNEL_DIR" rev-parse -q --verify "$sync_point:$kernel_path") || { + echo add + return + } + tools_now=$(git -C "$KERNEL_DIR" hash-object --path="$tools_path" "$tools_path") || { + echo add + return + } + kernel_now=$(git -C "$KERNEL_DIR" hash-object --path="$kernel_path" "$kernel_path") || { + echo add + return + } + + if [[ "$tools_now" == "$tools_base" ]]; then + echo add # only the kernel moved + elif [[ "$kernel_now" == "$kernel_base" ]]; then + echo delete # only tools/ moved, it is ahead + else + echo both + fi +} + +# A clean merge that matches the kernel means the tools/ side changed only +# what the kernel already has, so a copy drops nothing unique. +merge_matches_kernel() { + local kernel_path="$1" + local tools_path="$2" + local sync_point="$3" + local base merged status + + [[ -n "$sync_point" ]] || return 1 + base=$(mktemp) || return 1 + merged=$(mktemp) || { rm -f "$base"; return 1; } + if ! git -C "$KERNEL_DIR" show "$sync_point:$tools_path" > "$base" 2>/dev/null; then + rm -f "$base" "$merged" + return 1 + fi + if ! git merge-file -p "$KERNEL_DIR/$tools_path" "$base" \ + "$KERNEL_DIR/$kernel_path" > "$merged" 2>/dev/null; then + rm -f "$base" "$merged" + return 1 + fi + cmp -s "$merged" "$KERNEL_DIR/$kernel_path" + status=$? + rm -f "$base" "$merged" + return $status +} + +# The additions only the tools/ copy added since the sync point, what a +# forced sync drops; diffing against the sync-point blob gives it exactly. +tools_only_lines() { + local tools_path="$1" + local sync_point="$2" + local line + + [[ -n "$sync_point" ]] || return 0 + git -C "$KERNEL_DIR" rev-parse -q --verify "$sync_point:$tools_path" \ + >/dev/null || return 0 + + while IFS= read -r line; do + case "$line" in + ---*|+++*) continue ;; + -*) echo " $line" ;; + esac + done < <(git -C "$KERNEL_DIR" cat-file blob "$sync_point:$tools_path" | \ + diff -u "$KERNEL_DIR/$tools_path" - 2>/dev/null) +} + +# --- Map a kernel header path to its beauty script(s) -------------------- +# +# The mapping comes from the beauty Build files: a script reads the +# headers listed as its prerequisites and the directories passed as its +# arguments. The header name still has to appear in the script so that +# one reading a whole tree, like syscalltbl.sh, doesn't match everything. + +declare -A BEAUTY_MAKE_DIRS=() +declare -A BEAUTY_MAKE_SCRIPTS=() +declare -A BEAUTY_SCRIPT_ARGS=() +declare -A BEAUTY_SCRIPT_PATH=() +declare -A BEAUTY_SCRIPT_ROOTS=() +declare -A BEAUTY_SCRIPT_FILES=() +declare -a BEAUTY_SCRIPTS=() +BEAUTY_ARCH="" + +beauty_add_script() { + local script="$1" + + [[ -n "$script" ]] || return 0 + [[ -n "${BEAUTY_SCRIPT_FILES[$script]+x}" ]] && return 0 + BEAUTY_SCRIPT_FILES["$script"]="" + BEAUTY_SCRIPT_ROOTS["$script"]="" + BEAUTY_SCRIPTS+=("$script") +} + +find_beauty_scripts() { + local kernel_path="$1" + local tools_path="$2" + local header_basename script script_path root matched + + header_basename=$(basename "$kernel_path") + + for script in "${BEAUTY_SCRIPTS[@]+"${BEAUTY_SCRIPTS[@]}"}"; do + script_path="${BEAUTY_SCRIPT_PATH[$script]:-$script}" + matched=0 + case $'\n'"${BEAUTY_SCRIPT_FILES[$script]:-}"$'\n' in + *$'\n'"$tools_path"$'\n'*) matched=1 ;; + esac + if [[ $matched -eq 0 ]]; then + while IFS= read -r root; do + [[ -n "$root" ]] || continue + [[ "$tools_path" == "$root" || \ + "$tools_path" == "$root"/* ]] || continue + if grep -F -q -e "/$header_basename" \ + -e "\"$header_basename\"" \ + "$KERNEL_DIR/$script_path" 2>/dev/null; then + matched=1 + break + fi + done <<< "${BEAUTY_SCRIPT_ROOTS[$script]:-}" + fi + [[ $matched -eq 1 ]] && echo "$script_path" + done +} + +# --- Find buildable targets that consume a header ------------------------- +# +# Given a tools/ header path, grep tools/ source files to find which +# buildable directories include it, then return them as build commands. +# This is discovered dynamically so new consumers are picked up automatically. + +# C sources include tools/ headers by their include-relative path, e.g. +# <linux/socket.h> for tools/include/uapi/linux/socket.h. +include_relative_path() { + local tools_path="$1" + + case "$tools_path" in + tools/include/uapi/*) + echo "${tools_path#tools/include/uapi/}" + ;; + tools/include/*) + echo "${tools_path#tools/include/}" + ;; + tools/arch/*/include/uapi/*) + echo "${tools_path#tools/arch/*/include/uapi/}" + ;; + tools/arch/*/include/*) + echo "${tools_path#tools/arch/*/include/}" + ;; + *) + basename "$tools_path" + ;; + esac +} + +# Map from a source file path to its buildable target directory and make command. +# Returns empty if the file doesn't belong to a known buildable target. +buildable_target() { + local src="$1" + case "$src" in + tools/perf/*) + echo "perf:$PERF_DIR" + ;; + tools/lib/bpf/*) + echo "libbpf:$KERNEL_DIR/tools/lib/bpf" + ;; + tools/bpf/bpftool/*) + echo "bpftool:$KERNEL_DIR/tools/bpf/bpftool" + ;; + tools/objtool/*) + echo "objtool:$KERNEL_DIR/tools/objtool" + ;; + tools/lib/subcmd/*) + echo "libsubcmd:$KERNEL_DIR/tools/lib/subcmd" + ;; + tools/lib/api/*) + echo "libapi:$KERNEL_DIR/tools/lib/api" + ;; + tools/lib/perf/*) + echo "libperf:$KERNEL_DIR/tools/lib/perf" + ;; + tools/lib/symbol/*) + echo "libsymbol:$KERNEL_DIR/tools/lib/symbol" + ;; + tools/tracing/*) + echo "tracing:$KERNEL_DIR/tools/tracing" + ;; + esac +} + +find_build_targets() { + local tools_path="$1" + local header_class="$2" + local include_path + local targets=() + local seen=() + + # Beauty copies are only on the include path of the beauty scripts. + if [[ "$header_class" == "beauty" ]]; then + echo "perf:$PERF_DIR" + return + fi + + include_path=$(include_relative_path "$tools_path") + + while IFS= read -r src; do + [[ -z "$src" ]] && continue + local target + target=$(buildable_target "$src") + [[ -z "$target" ]] && continue + + # Deduplicate + local already=0 + for s in "${seen[@]+"${seen[@]}"}"; do + if [[ "$s" == "$target" ]]; then + already=1 + break + fi + done + if [[ $already -eq 0 ]]; then + seen+=("$target") + targets+=("$target") + fi + done < <(grep -rlF -e "<$include_path>" -e "\"$include_path\"" -e "/$include_path" \ + --include='*.c' --include='*.h' --include='*.S' \ + "$KERNEL_DIR/tools/" 2>/dev/null | \ + sed "s|^$KERNEL_DIR/||" | grep -v '^tools/include/') + + # Always include perf + local has_perf=0 + for t in "${targets[@]+"${targets[@]}"}"; do + [[ "$t" == perf:* ]] && has_perf=1 + done + if [[ $has_perf -eq 0 ]]; then + targets+=("perf:$PERF_DIR") + fi + + printf '%s\n' "${targets[@]}" +} + +# Print the consumers that will be built for a header or group. +show_build_targets() { + local what="$1" + local target_list="$2" + + info "Build-testing consumers of $what:" + while IFS=: read -r name dir; do + [[ -z "$name" ]] && continue + echo " $name ($dir)" + done <<< "$target_list" +} + +# Keep build output out of the source tree; perf, tracing and objtool take +# O=, the libraries and bpftool take OUTPUT=. +build_target() { + local name="$1" + local dir="$2" + + case "$name" in + perf) + make -C "$dir" O="$OUTPUT_DIR" -j"$(nproc)" + ;; + tracing|objtool) + (umask 077 && mkdir -p "$OUTPUT_DIR/$name") + make -C "$dir" O="$OUTPUT_DIR/$name" -j"$(nproc)" + ;; + *) + (umask 077 && mkdir -p "$OUTPUT_DIR/$name") + make -C "$dir" OUTPUT="$OUTPUT_DIR/$name/" -j"$(nproc)" + ;; + esac +} + +# Run build tests for all targets that consume a header. +# Returns 0 if all builds pass, 1 if any fail. +build_test_targets() { + local tools_path="$1" + local header_class="$2" + local header_basename log_name + local target_list failed=0 + + header_basename=$(basename "$tools_path") + # Key the log by tools path, same-basename headers in different + # directories would otherwise share a log. + log_name="${tools_path//\//-}" + target_list=$(find_build_targets "$tools_path" "$header_class") + show_build_targets "$header_basename" "$target_list" + + while IFS=: read -r name dir; do + [[ -z "$name" ]] && continue + local log="$OUTPUT_DIR/$name.$log_name.log" + if ! build_target "$name" "$dir" > "$log" 2>&1; then + err " $name build failed, see $log" + failed=1 + fi + done <<< "$target_list" + + return $failed +} + +# --- Run a beauty script capturing output -------------------------------- +# +# The arguments come from the same Build files the build uses, so a new +# script or a changed directory needs no edit here; run bare, +# arch_errno_names.sh misses the header copies. + +# Resolve a Build variable token, $(srctree)/path or $(dir)/suffix, to a +# tree-relative path. +beauty_resolve_token() { + local token="$1" + local name suffix + + case "$token" in + '$(srctree)'*) + suffix="${token//\$(srctree)/}" + echo "${suffix#/}" + ;; + '$('*')'*) + name="${token#\$(}" + name="${name%%)*}" + suffix="${token#*\)}" + [[ -n "${BEAUTY_MAKE_DIRS[$name]:-}" ]] || return 1 + printf '%s\n' "${BEAUTY_MAKE_DIRS[$name]}$suffix" | \ + sed "s/\$(SRCARCH)/$BEAUTY_ARCH/g" + ;; + *) + return 1 + ;; + esac +} + +# Collect, from one Build file, the script each rule runs with its +# arguments and the headers it takes as prerequisites. +parse_beauty_build() { + local build="$1" + local -a rule_lines=() + local line last_rule="" var script args token value roots files needle + local name path re_dir re_tbl + + [[ -r "$build" ]] || return 0 + + # _dir :=, _tbl := assignments, and the $(SHELL) rules using them. + re_dir='^([A-Za-z0-9_]+_(dir|dirs))[[:space:]]*(:=|\?=)[[:space:]]*' + re_dir+='\$\(srctree\)/(.*)$' + re_tbl='^([A-Za-z0-9_]+_(tbl|tbls))[[:space:]]*(:=|\?=)[[:space:]]*' + re_tbl+='\$\(srctree\)/((.*\.sh))[[:space:]]*$' + + while IFS= read -r line; do + if [[ "$line" =~ $re_dir ]]; then + BEAUTY_MAKE_DIRS["${BASH_REMATCH[1]}"]=$(printf '%s' \ + "${BASH_REMATCH[4]%/}" | \ + sed "s/\$(SRCARCH)/$BEAUTY_ARCH/g") + elif [[ "$line" =~ $re_tbl ]]; then + BEAUTY_MAKE_SCRIPTS["${BASH_REMATCH[1]}"]="${BASH_REMATCH[4]}" + elif [[ "$line" == *'$(SHELL)'* && -n "$last_rule" ]]; then + rule_lines+=("$last_rule"$'\n'"$line") + elif [[ "$line" != '#'* && "$line" != $'\t'* && \ + "$line" == *:* && "$line" != *':='* && \ + "$line" != *'?='* && "$line" != *'+='* ]]; then + last_rule="$line" + fi + done < "$build" + + local rule prereqs command + for rule in "${rule_lines[@]+"${rule_lines[@]}"}"; do + prereqs="${rule%%$'\n'*}" + command="${rule#*$'\n'}" + + script="" + for var in "${!BEAUTY_MAKE_SCRIPTS[@]}"; do + needle="'\$($var)'" + if [[ "$command" == *"$needle"* ]]; then + script="${BEAUTY_MAKE_SCRIPTS[$var]##*/}" + BEAUTY_SCRIPT_PATH["$script"]="${BEAUTY_MAKE_SCRIPTS[$var]}" + args="${command#*"$needle"}" + break + fi + done + [[ -n "$script" ]] || continue + beauty_add_script "$script" + + args="${args//\'}" + args=$(printf '%s\n' "$args" | \ + sed 's/\$(patsubst -%,,\$(CC))/gcc/g') + args="${args%' > $@'}" + value="" + roots="" + for token in $args; do + case "$token" in + '$@') + token='@OUTFILE@' + ;; + '$(srctree)'*|'$('*')'*) + path=$(beauty_resolve_token "$token") || { + value="" + break + } + case "$token" in + '$(srctree)'*) + roots+="${path}"$'\n' + ;; + *) + name="${token#\$(}" + name="${name%%)*}" + roots+="${BEAUTY_MAKE_DIRS[$name]:-}"$'\n' + ;; + esac + token="$KERNEL_DIR/$path" + ;; + *) ;; + esac + token=$(printf '%s' "$token" | sed "s/\$(SRCARCH)/$BEAUTY_ARCH/g") + if [[ "$token" == *'$('* ]]; then + value="" + break + fi + value+="${value:+ }$token" + done + BEAUTY_SCRIPT_ROOTS["$script"]+="$roots" + [[ -n "$value" ]] && BEAUTY_SCRIPT_ARGS["$script"]="$value" + + prereqs="${prereqs#*:}" + files="" + for token in $prereqs; do + path=$(beauty_resolve_token "$token") || continue + [[ "$path" == *'*'* ]] && continue + [[ -n "$path" ]] && files+="$path"$'\n' + done + BEAUTY_SCRIPT_FILES["$script"]+="$files" + done +} + +setup_beauty_args() { + local build found=0 + + BEAUTY_ARCH=$(uname -m) + case "$BEAUTY_ARCH" in + x86_64|i[3-6]86) BEAUTY_ARCH=x86 ;; + aarch64|arm64) BEAUTY_ARCH=arm64 ;; + s390*) BEAUTY_ARCH=s390 ;; + riscv*) BEAUTY_ARCH=riscv ;; + loongarch*) BEAUTY_ARCH=loongarch ;; + ppc*) BEAUTY_ARCH=powerpc ;; + sparc*) BEAUTY_ARCH=sparc ;; + mips*) BEAUTY_ARCH=mips ;; + esac + + for build in "$PERF_DIR/trace/beauty/Build" \ + "$PERF_DIR/trace/beauty/tracepoints/Build"; do + [[ -r "$build" ]] || continue + parse_beauty_build "$build" + found=1 + done + + [[ $found -eq 1 ]] || \ + detail "No beauty Build file, running the scripts without arguments" +} + +run_beauty_script() { + local script="$1" + local -a cmd=() + local outfile="" i + + read -ra cmd <<< "${BEAUTY_SCRIPT_ARGS["${script##*/}"]:-}" + for ((i = 0; i < ${#cmd[@]}; i++)); do + if [[ "${cmd[$i]}" == '@OUTFILE@' ]]; then + outfile=$(mktemp) || continue + cmd[$i]="$outfile" + fi + done + bash "$script" "${cmd[@]+"${cmd[@]}"}" 2>/dev/null || true + if [[ -n "$outfile" ]]; then + cat "$outfile" 2>/dev/null || true + rm -f "$outfile" + fi +} + +# Run one beauty script against the kernel content swapped into the tools +# copy, capturing its output before and after; the saved copy restores the +# tools copy, also when the script is interrupted. +run_beauty_pass() { + local script="$1" + local kernel_path="$2" + local tools_path="$3" + local tmpdir="$4" + local idx="$5" + local before="$tmpdir/beauty.$idx.before" + local after="$tmpdir/beauty.$idx.after" + local saved="$tmpdir/beauty.$idx.saved" + local restore + + if ! cp -p "$KERNEL_DIR/$tools_path" "$saved"; then + err "Failed to save $tools_path" + return 1 + fi + restore=$(printf 'cp -p %q %q' "$saved" "$KERNEL_DIR/$tools_path") + # shellcheck disable=SC2064 # expand now, locals may be gone + trap "$restore" EXIT + # shellcheck disable=SC2064 # expand now, locals may be gone + trap "$restore; trap - EXIT; exit 130" INT + # shellcheck disable=SC2064 # expand now, locals may be gone + trap "$restore; trap - EXIT; exit 143" TERM + (cd "$KERNEL_DIR" && run_beauty_script "$script") > "$before" + if ! cp "$KERNEL_DIR/$kernel_path" "$KERNEL_DIR/$tools_path"; then + err "Failed to copy $kernel_path -> $tools_path" + cp -p "$saved" "$KERNEL_DIR/$tools_path" || \ + err "Failed to restore $tools_path" + trap - INT TERM EXIT + return 1 + fi + (cd "$KERNEL_DIR" && run_beauty_script "$script") > "$after" + if ! cp -p "$saved" "$KERNEL_DIR/$tools_path"; then + err "Failed to restore $tools_path" + trap - INT TERM EXIT + return 1 + fi + trap - INT TERM EXIT +} + +# The tools/perf/trace/beauty/ relative path of a beauty script, as shown +# in the commit messages. +beauty_script_relpath() { + echo "tools/perf/trace/beauty/${1##*/trace/beauty/}" +} + +# The commit message fragment for a beauty script whose table changed: +# the recipe the pre-pass ran and the difference between its two runs. +beauty_body_fragment() { + local script="$1" + local kernel_path="$2" + local tools_path="$3" + local before="$4" + local after="$5" + local rel_script args + + rel_script=$(beauty_script_relpath "$script") + # The runs are from the tree root, show the dirs relative to it. + args="${BEAUTY_SCRIPT_ARGS["${script##*/}"]:-}" + args="${args//"$KERNEL_DIR/"/}" + local before_args="$args" after_args="$args" + local before_tail=" > before.txt" after_tail=" > after.txt" + + # Scripts taking the output file as an argument write it themselves. + if [[ "$args" == *@OUTFILE@* ]]; then + before_args="${args//@OUTFILE@/out.before}" + after_args="${args//@OUTFILE@/out.after}" + before_tail="" + after_tail="" + fi + + printf '\n' + printf ' $ %s%s%s\n' "$rel_script" "${before_args:+ $before_args}" "$before_tail" + printf ' $ cp %s %s\n' "$kernel_path" "$tools_path" + printf ' $ %s%s%s\n' "$rel_script" "${after_args:+ $after_args}" "$after_tail" + if [[ "$args" == *@OUTFILE@* ]]; then + printf ' $ diff -u out.before out.after\n' + else + printf ' $ diff -u before.txt after.txt\n' + fi + printf '\n' + diff -u "$before" "$after" | tail -n +3 | sed 's/^/ /' + printf '\n' +} + +cleanup_tmpdir() { + [[ -n "$1" ]] || return 0 + rm -rf "$1" +} + +# --- Copy header and create commit --------------------------------------- + +process_header() { + local tools_path="$1" + local kernel_path="$2" + + classify_header "$tools_path" + + if [[ -n "$HEADER_FILTER" ]] && [[ "$kernel_path" != *"$HEADER_FILTER"* ]]; then + detail "Skipping $kernel_path (does not match filter)" + return + fi + + info "Processing: $kernel_path" + detail " Class: $HDR_CLASS" + detail " Tools: $tools_path" + detail " Kernel: $kernel_path" + + # Find kernel commits that changed this file + local sync_point pickup_commits="" + sync_point=$(find_sync_point "$kernel_path" "$tools_path") + if [[ -n "$sync_point" ]]; then + pickup_commits=$(find_kernel_commits "$kernel_path" \ + "$tools_path" "$sync_point") + else + warn "No previous sync commit found for $tools_path" + fi + + local kernel_commits="" + if [[ -n "$pickup_commits" ]]; then + kernel_commits=$(format_commits ' %h ("%s")' "$pickup_commits") + fi + + # A merge can bring content of its own; list it too, it is not the + # author of a change, so it gets no Cc. + local merge_commits="" + merge_commits=$(merge_pickup_commits "$kernel_path" "$tools_path" \ + "$sync_point") + if [[ -n "$merge_commits" ]]; then + if [[ -n "$kernel_commits" ]]; then + kernel_commits+=$'\n' + fi + kernel_commits+=$(format_commits ' %h ("%s")' "$merge_commits") + fi + + if [[ -n "$kernel_commits" ]]; then + echo "" + echo " Kernel commits since last sync:" + echo "$kernel_commits" + fi + + # Copies with ignore rules carry deliberate edits that a plain copy + # would drop; leave them for a hand sync, printing the Cc list for them. + if has_ignore_rules "$kernel_path"; then + local cc_lines="" + if [[ -n "$pickup_commits" ]]; then + cc_lines=$(format_commits 'Cc: %an <%ae>' "$pickup_commits" | sort -u) + if [[ -n "$cc_lines" ]]; then + echo "" + echo "$cc_lines" | sed 's/^/ /' + fi + echo "" + fi + warn "$tools_path carries intentional edits, sync by hand" + HAND_SYNC+=("$tools_path") + return + fi + + # The kernel is the reference, but only copy when that doesn't drop + # content only the tools/ copy has. + local direction dropped="" forced=0 + direction=$(sync_direction "$kernel_path" "$tools_path" "$sync_point") + case "$direction" in + delete) + warn "$tools_path is ahead of the kernel sources, leaving it alone" + TOOLS_AHEAD+=("$tools_path") + return + ;; + both) + if merge_matches_kernel "$kernel_path" "$tools_path" "$sync_point"; then + detail "Tools/ changes are already in the kernel" + elif [[ $FORCE -eq 0 ]]; then + err "$tools_path and $kernel_path both have content the other lacks, needs manual investigation" + MANUAL+=("$tools_path") + return + else + warn "Both copies have unique content, dropping the tools/ one (--force)" + dropped=$(tools_only_lines "$tools_path" "$sync_point") + forced=1 + fi + ;; + esac + + # For beauty files, capture the string table changes and keep the + # before/after files for the commit message. + local beauty_changed=0 + local beauty_tmpdir="" + local beauty_scripts + local -a beauty_script_list=() + beauty_scripts=$(find_beauty_scripts "$kernel_path" "$tools_path") + while IFS= read -r script; do + [[ -z "$script" ]] && continue + beauty_script_list+=("$script") + done <<< "$beauty_scripts" + if [[ ${#beauty_script_list[@]} -gt 0 ]]; then + beauty_tmpdir=$(mktemp -d) + + local idx=0 + for script in "${beauty_script_list[@]}"; do + local script_name + script_name=$(basename "$script") + local before="$beauty_tmpdir/beauty.$idx.before" + local after="$beauty_tmpdir/beauty.$idx.after" + + # The script reads the tools/ copy, so swap in the kernel one + # and restore the saved copy, also in a dry run. + if ! run_beauty_pass "$script" "$kernel_path" "$tools_path" \ + "$beauty_tmpdir" "$idx"; then + COPY_FAILED+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + return 1 + fi + + if ! diff -q "$before" "$after" > /dev/null 2>&1; then + echo "" + info "Beauty string changes from $script_name:" + diff -u "$before" "$after" | head -60 || true + beauty_changed=1 + fi + idx=$((idx + 1)) + done + fi + + if [[ $DRY_RUN -eq 1 ]]; then + [[ $forced -eq 1 ]] && FORCED+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + info "(dry run) Would copy $kernel_path -> $tools_path" + return + fi + + # Do the actual copy + if ! cp "$KERNEL_DIR/$kernel_path" "$KERNEL_DIR/$tools_path"; then + err "Failed to copy $kernel_path -> $tools_path" + COPY_FAILED+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + return 1 + fi + info "Copied $kernel_path -> $tools_path" + + # Verify the consumers still build + if [[ $BUILD -eq 1 ]]; then + if ! build_test_targets "$tools_path" "$HDR_CLASS"; then + err "Build failed after copying $kernel_path — reverting." + git -C "$KERNEL_DIR" checkout -- "$tools_path" || \ + err "Failed to revert $tools_path" + BUILD_FAILED+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + return 1 + fi + info "Build OK." + fi + + [[ $forced -eq 1 ]] && FORCED+=("$tools_path") + + if [[ $COMMIT -eq 0 ]]; then + SYNCED_OK+=("$tools_path") + cleanup_tmpdir "$beauty_tmpdir" + return + fi + + # Compose commit message + local header_short + header_short=$(basename "$kernel_path") + + # The subject follows the path of the copy, one wording per family. + local subject arch uapi_rel + case "$kernel_path" in + include/uapi/*) + uapi_rel="${kernel_path#include/uapi/}" + if [[ "$HDR_CLASS" == "beauty" ]]; then + subject="tools headers: Sync UAPI $uapi_rel with the kernel sources" + else + subject="tools headers UAPI: Sync $uapi_rel with the kernel sources" + fi + ;; + include/*) + subject="tools headers: Sync ${kernel_path#include/} with the kernel sources" + ;; + arch/*/include/uapi/*) + arch="${kernel_path#arch/}" + arch="${arch%%/*}" + subject="tools headers UAPI: Sync $arch/asm/${header_short} with the kernel sources" + ;; + arch/*) + arch="${kernel_path#arch/}" + arch="${arch%%/*}" + subject="tools arch $arch: Sync the $header_short copy with the kernel sources" + ;; + *) + subject="tools headers: Sync $kernel_path with the kernel sources" + ;; + esac + + # Build commit body + local body="" + + if [[ -n "$kernel_commits" ]]; then + body+="To pick up the changes in:" + body+=$'\n' + body+=$'\n' + body+="$kernel_commits" + body+=$'\n' + fi + + if [[ $beauty_changed -eq 1 ]]; then + # Show the beauty diff in the commit message. + body+=$'\n' + body+="That introduce these new entries in the string tables:" + body+=$'\n' + + local idx=0 + for script in "${beauty_script_list[@]}"; do + local before_msg="$beauty_tmpdir/beauty.$idx.before" + local after_msg="$beauty_tmpdir/beauty.$idx.after" + + if ! diff -q "$before_msg" "$after_msg" > /dev/null 2>&1; then + body+=$(beauty_body_fragment "$script" "$kernel_path" \ + "$tools_path" "$before_msg" "$after_msg") + body+=$'\n' + fi + idx=$((idx + 1)) + done + elif [[ ${#beauty_script_list[@]} -gt 0 ]]; then + body+=$'\n' + body+="That don't result in changes to the string tables generated" + body+=" from this header." + else + body+=$'\n' + body+="That just rebuilds perf, silencing this build warning." + fi + + if [[ -n "$dropped" ]]; then + body+=$'\n' + body+=$'\n' + body+="The tools/ copy had content not present in the kernel sources," + body+=$'\n' + body+="dropped here after manual review:" + body+=$'\n' + body+=$'\n' + body+="$dropped" + fi + + body+=$'\n' + body+=$'\n' + body+="This addresses this perf build warning:" + body+=$'\n' + body+=$'\n' + body+=" Warning: Kernel ABI header differences:" + body+=$'\n' + body+=" diff -u $tools_path $kernel_path" + body+=$'\n' + body+=$'\n' + body+="Please see tools/include/uapi/README for further details." + + # Add Cc for kernel commit authors + local cc_lines="" + if [[ -n "$pickup_commits" ]]; then + cc_lines=$(format_commits 'Cc: %an <%ae>' "$pickup_commits" | sort -u) + fi + + body+=$'\n' + body+=$'\n' + if [[ -n "$cc_lines" ]]; then + body+="$cc_lines"$'\n' + fi + body+="$(sign_off)" + + cleanup_tmpdir "$beauty_tmpdir" + + if ! git -C "$KERNEL_DIR" add "$tools_path"; then + err "Failed to stage $tools_path" + COMMIT_FAILED+=("$tools_path") + return 1 + fi + if ! git -C "$KERNEL_DIR" commit "$tools_path" \ + -m "$(printf '%s\n\n%s\n' "$subject" "$body")"; then + err "Commit failed for $tools_path" + COMMIT_FAILED+=("$tools_path") + return 1 + fi + + SYNCED_OK+=("$tools_path") + info "Committed: $subject" + echo "" +} + +# --- Group related headers ----------------------------------------------- +# +# Some headers should be committed together (e.g. all KVM arch headers, +# all syscall tables). This function processes groups. + +process_group() { + local group_name="$1" + shift + local pairs=("$@") + + if [[ ${#pairs[@]} -eq 0 ]]; then + return + fi + + local file_count + file_count=$((${#pairs[@]} / 2)) + info "Processing group: $group_name ($file_count files)" + + local group_slug="${group_name// /-}" + + local all_kernel_commits="" + local all_cc_lines="" + local all_warnings="" + local group_beauty_tmpdir="" + local group_beauty_body="" + local group_beauty_scripts=0 + local group_dropped="" + local -a group_forced=() + local files_to_add=() + + # Collect info for all files in the group + local i=0 + while [[ $i -lt ${#pairs[@]} ]]; do + local tools_path="${pairs[$i]}" + local kernel_path="${pairs[$((i+1))]}" + local fidx=$i + i=$((i + 2)) + + if [[ -n "$HEADER_FILTER" ]] && [[ "$kernel_path" != *"$HEADER_FILTER"* ]]; then + continue + fi + + if has_ignore_rules "$kernel_path"; then + local sync_point pickup_commits="" merge_commits="" + sync_point=$(find_sync_point "$kernel_path" "$tools_path") + if [[ -n "$sync_point" ]]; then + pickup_commits=$(find_kernel_commits "$kernel_path" \ + "$tools_path" "$sync_point") + merge_commits=$(merge_pickup_commits "$kernel_path" \ + "$tools_path" "$sync_point") + fi + if [[ -n "$pickup_commits" || -n "$merge_commits" ]]; then + echo "" + echo " Kernel commits since last sync ($tools_path):" + if [[ -n "$pickup_commits" ]]; then + format_commits ' %h ("%s")' "$pickup_commits" + fi + if [[ -n "$merge_commits" ]]; then + format_commits ' %h ("%s")' "$merge_commits" + fi + local ccs + ccs=$(format_commits 'Cc: %an <%ae>' "$pickup_commits" | sort -u) + if [[ -n "$ccs" ]]; then + echo "" + echo "$ccs" | sed 's/^/ /' + fi + echo "" + fi + warn "$tools_path carries intentional edits, sync by hand" + HAND_SYNC+=("$tools_path") + continue + fi + + local sync_point direction + sync_point=$(find_sync_point "$kernel_path" "$tools_path") + direction=$(sync_direction "$kernel_path" "$tools_path" \ + "$sync_point") + case "$direction" in + delete) + warn "$tools_path is ahead of the kernel sources, leaving it alone" + TOOLS_AHEAD+=("$tools_path") + continue + ;; + both) + if merge_matches_kernel "$kernel_path" "$tools_path" "$sync_point"; then + detail "Tools/ changes are already in the kernel" + elif [[ $FORCE -eq 0 ]]; then + err "$tools_path and $kernel_path both have content the other lacks, needs manual investigation" + MANUAL+=("$tools_path") + continue + else + warn "Both copies have unique content, dropping the tools/ one (--force)" + local file_dropped + file_dropped=$(tools_only_lines "$tools_path" "$sync_point") + if [[ -n "$file_dropped" ]]; then + group_dropped+="$file_dropped"$'\n' + fi + group_forced+=("$tools_path") + fi + ;; + esac + + local pickup_commits="" + if [[ -n "$sync_point" ]]; then + pickup_commits=$(find_kernel_commits "$kernel_path" \ + "$tools_path" "$sync_point") + else + warn "No previous sync commit found for $tools_path" + fi + if [[ -n "$pickup_commits" ]]; then + all_kernel_commits+=$(format_commits ' %h ("%s")' "$pickup_commits") + all_kernel_commits+=$'\n' + + local ccs + ccs=$(format_commits 'Cc: %an <%ae>' "$pickup_commits") + if [[ -n "$ccs" ]]; then + all_cc_lines+="$ccs"$'\n' + fi + fi + + # A merge can bring content of its own; list it too, it is not the + # author of a change, so it gets no Cc. + local merge_commits="" + merge_commits=$(merge_pickup_commits "$kernel_path" "$tools_path" \ + "$sync_point") + if [[ -n "$merge_commits" ]]; then + all_kernel_commits+=$(format_commits ' %h ("%s")' "$merge_commits") + all_kernel_commits+=$'\n' + fi + + all_warnings+=" diff -u $tools_path $kernel_path"$'\n' + + # Beauty string tables; the script reads the tools/ copy, so this + # runs before the copy too, also in a dry run. + local -a group_script_list=() + while IFS= read -r script; do + [[ -z "$script" ]] && continue + group_script_list+=("$script") + done < <(find_beauty_scripts "$kernel_path" "$tools_path") + if [[ ${#group_script_list[@]} -gt 0 ]]; then + group_beauty_scripts=1 + [[ -z "$group_beauty_tmpdir" ]] && group_beauty_tmpdir=$(mktemp -d) + local sidx=0 + for script in "${group_script_list[@]}"; do + local script_name + script_name=$(basename "$script") + local before="$group_beauty_tmpdir/beauty.$fidx.$sidx.before" + local after="$group_beauty_tmpdir/beauty.$fidx.$sidx.after" + + if ! run_beauty_pass "$script" "$kernel_path" "$tools_path" \ + "$group_beauty_tmpdir" "$fidx.$sidx"; then + COPY_FAILED+=("$tools_path") + for f in "${files_to_add[@]}"; do + git -C "$KERNEL_DIR" checkout -- "$f" || \ + err "Failed to revert $f" + done + cleanup_tmpdir "$group_beauty_tmpdir" + return 1 + fi + + if ! diff -q "$before" "$after" > /dev/null 2>&1; then + echo "" + info "Beauty string changes from $script_name:" + diff -u "$before" "$after" | head -60 || true + group_beauty_body+=$(beauty_body_fragment "$script" \ + "$kernel_path" "$tools_path" "$before" "$after") + group_beauty_body+=$'\n' + fi + sidx=$((sidx + 1)) + done + fi + + if [[ $DRY_RUN -eq 0 ]]; then + if ! cp "$KERNEL_DIR/$kernel_path" "$KERNEL_DIR/$tools_path"; then + err "Failed to copy $kernel_path -> $tools_path" + COPY_FAILED+=("$tools_path") + for f in "${files_to_add[@]}"; do + git -C "$KERNEL_DIR" checkout -- "$f" || \ + err "Failed to revert $f" + done + cleanup_tmpdir "$group_beauty_tmpdir" + return 1 + fi + files_to_add+=("$tools_path") + info "Copied $kernel_path -> $tools_path" + else + info "(dry run) Would copy $kernel_path -> $tools_path" + fi + done + + # The beauty before/after files are captured into the fragments above. + cleanup_tmpdir "$group_beauty_tmpdir" + + if [[ ${#files_to_add[@]} -eq 0 ]]; then + FORCED+=("${group_forced[@]+"${group_forced[@]}"}") + return + fi + + if [[ $BUILD -eq 1 ]]; then + # Collect all unique build targets for the group + local group_targets="" + for f in "${files_to_add[@]}"; do + classify_header "$f" + group_targets+=$(find_build_targets "$f" "$HDR_CLASS") + group_targets+=$'\n' + done + group_targets=$(echo "$group_targets" | sort -u | grep -v '^$') + + show_build_targets "group $group_name" "$group_targets" + local build_failed=0 + while IFS=: read -r name dir; do + [[ -z "$name" ]] && continue + detail " Build-testing $name ($dir)..." + local log="$OUTPUT_DIR/$name.$group_slug.log" + if ! build_target "$name" "$dir" > "$log" 2>&1; then + err " $name build failed, see $log" + build_failed=1 + fi + done <<< "$group_targets" + + if [[ $build_failed -eq 1 ]]; then + err "Build failed after copying group $group_name — reverting." + for f in "${files_to_add[@]}"; do + git -C "$KERNEL_DIR" checkout -- "$f" || \ + err "Failed to revert $f" + BUILD_FAILED+=("$f (group: $group_name)") + done + return 1 + fi + info "Build OK." + fi + + FORCED+=("${group_forced[@]+"${group_forced[@]}"}") + + if [[ $DRY_RUN -eq 1 ]]; then + return + fi + + if [[ $COMMIT -eq 0 ]]; then + for f in "${files_to_add[@]}"; do + SYNCED_OK+=("$f") + done + return + fi + + # Deduplicate kernel commits and Cc lines; the commits keep the git log + # order they came in, the Cc lines stay sorted. + all_kernel_commits=$(printf '%s\n' "$all_kernel_commits" | \ + awk 'NF && !seen[$0]++') + all_cc_lines=$(echo "$all_cc_lines" | sort -u | grep -v '^$' || true) + + local subject="tools headers: Sync $group_name with the kernel sources" + local body="" + + if [[ -n "$all_kernel_commits" ]]; then + body+="To pick up the changes in:" + body+=$'\n' + body+=$'\n' + body+="$all_kernel_commits" + body+=$'\n' + fi + + if [[ -n "$group_beauty_body" ]]; then + body+=$'\n' + body+="That introduce these new entries in the string tables:" + body+=$'\n' + body+="$group_beauty_body" + elif [[ $group_beauty_scripts -eq 1 ]]; then + body+=$'\n' + body+="That don't result in changes to the string tables generated" + body+=" from these headers." + fi + + if [[ -n "$group_dropped" ]]; then + body+=$'\n' + body+=$'\n' + body+="The tools/ copies had content not present in the kernel" + body+=" sources, dropped here after manual review:" + body+=$'\n' + body+=$'\n' + body+="${group_dropped%$'\n'}" + fi + + body+=$'\n' + body+=$'\n' + body+="This addresses these perf build warnings:" + body+=$'\n' + body+=$'\n' + body+=" Warning: Kernel ABI header differences:" + body+=$'\n' + body+="$all_warnings" + body+=$'\n' + body+="Please see tools/include/uapi/README for further details." + body+=$'\n' + body+=$'\n' + if [[ -n "$all_cc_lines" ]]; then + body+="$all_cc_lines"$'\n' + fi + body+="$(sign_off)" + + for f in "${files_to_add[@]}"; do + if ! git -C "$KERNEL_DIR" add "$f"; then + err "Failed to stage $f" + for g in "${files_to_add[@]}"; do + COMMIT_FAILED+=("$g") + done + return 1 + fi + done + + if ! git -C "$KERNEL_DIR" commit "${files_to_add[@]}" \ + -m "$(printf '%s\n\n%s\n' "$subject" "$body")"; then + err "Commit failed for group $group_name" + for f in "${files_to_add[@]}"; do + COMMIT_FAILED+=("$f") + done + return 1 + fi + + for f in "${files_to_add[@]}"; do + SYNCED_OK+=("$f") + done + + info "Committed: $subject" + echo "" +} + +# --- Main ----------------------------------------------------------------- + +main() { + cd "$KERNEL_DIR" + + if [[ ! -d include/uapi ]]; then + err "Must be run from a kernel tree root (or tools/perf/)" + exit 1 + fi + + if [[ ! -d tools/perf ]]; then + err "Cannot find tools/perf/ in kernel tree" + exit 1 + fi + + setup_beauty_args || true + + if [[ $BUILD -eq 1 ]]; then + # umask covers every level created, -m with -p only the last. + (umask 077 && mkdir -p "$OUTPUT_DIR") + fi + + # Check for uncommitted changes in tools/ + if ! git diff --quiet -- tools/include/ tools/perf/trace/beauty/ tools/perf/util/ tools/perf/arch/*/entry/syscalls/ tools/scripts/ tools/lib/ tools/arch/; then + warn "You have uncommitted changes in tools/. Commit or stash them first." + if [[ $DRY_RUN -eq 0 ]]; then + exit 1 + fi + fi + + info "Running check-headers to detect stale copies..." + echo "" + + local stale + if ! stale=$(detect_stale_headers); then + info "All headers are up to date." + exit 0 + fi + + echo "Stale headers found:" + echo "$stale" + echo "" + + # Parse the stale header pairs and group them + # Each line is: tools_path kernel_path + local kvm_group=() + local syscall_group=() + local singles=() + + while IFS=' ' read -r tools_path kernel_path; do + case "$kernel_path" in + *kvm.h|*svm.h|*vmx.h|*sie.h) + kvm_group+=("$tools_path" "$kernel_path") + ;; + *syscall*.tbl|*unistd.h) + syscall_group+=("$tools_path" "$kernel_path") + ;; + *) + singles+=("$tools_path" "$kernel_path") + ;; + esac + done <<< "$stale" + + # Process individual headers first (beauty and simple copies). + # || true: continue on failure; failures are tracked in BUILD_FAILED[], + # COPY_FAILED[] and COMMIT_FAILED[] and reported in the summary. + local i=0 + while [[ $i -lt ${#singles[@]} ]]; do + process_header "${singles[$i]}" "${singles[$((i+1))]}" || true + i=$((i + 2)) + done + + # Process grouped headers + if [[ ${#kvm_group[@]} -gt 0 ]]; then + process_group "KVM headers" "${kvm_group[@]}" || true + fi + + if [[ ${#syscall_group[@]} -gt 0 ]]; then + process_group "syscall tables and unistd.h" "${syscall_group[@]}" || true + fi + + # Summary + echo "" + echo "=========================================" + echo " Summary" + echo "=========================================" + + if [[ ${#SYNCED_OK[@]} -gt 0 ]]; then + info "Synced successfully (${#SYNCED_OK[@]}):" + for f in "${SYNCED_OK[@]}"; do + echo " $f" + done + fi + + if [[ ${#BUILD_FAILED[@]} -gt 0 ]]; then + echo "" + err "Build failures (${#BUILD_FAILED[@]}) — reverted, need manual attention:" + for f in "${BUILD_FAILED[@]}"; do + echo " $f" + done + fi + + if [[ ${#COPY_FAILED[@]} -gt 0 ]]; then + echo "" + err "Copy failures (${#COPY_FAILED[@]}) — need manual attention:" + for f in "${COPY_FAILED[@]}"; do + echo " $f" + done + fi + + if [[ ${#COMMIT_FAILED[@]} -gt 0 ]]; then + echo "" + err "Commit failures (${#COMMIT_FAILED[@]}) — need manual attention:" + for f in "${COMMIT_FAILED[@]}"; do + echo " $f" + done + fi + + if [[ ${#TOOLS_AHEAD[@]} -gt 0 ]]; then + echo "" + warn "tools/ copies ahead of the kernel, left alone (${#TOOLS_AHEAD[@]}):" + for f in "${TOOLS_AHEAD[@]}"; do + echo " $f" + done + fi + + if [[ ${#HAND_SYNC[@]} -gt 0 ]]; then + echo "" + warn "Carry intentional edits, need manual sync (${#HAND_SYNC[@]}):" + for f in "${HAND_SYNC[@]}"; do + echo " $f" + done + fi + + if [[ ${#MANUAL[@]} -gt 0 ]]; then + echo "" + err "Left for manual investigation (${#MANUAL[@]}):" + for f in "${MANUAL[@]}"; do + echo " $f" + done + fi + + if [[ ${#FORCED[@]} -gt 0 ]]; then + echo "" + info "Synced with --force, tools/ additions dropped (${#FORCED[@]}):" + for f in "${FORCED[@]}"; do + echo " $f" + done + fi + + # Final check-headers run to see what's still out of sync + echo "" + local remaining + if remaining=$(detect_stale_headers); then + warn "Headers still out of sync:" + echo "$remaining" + else + info "All headers are now in sync." + fi + + if [[ ${#BUILD_FAILED[@]} -gt 0 || ${#COPY_FAILED[@]} -gt 0 || \ + ${#COMMIT_FAILED[@]} -gt 0 || ${#MANUAL[@]} -gt 0 ]]; then + exit 1 + fi +} + +main "$@" diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build index 395f2866094a..58858f3d2bf9 100644 --- a/tools/perf/tests/Build +++ b/tools/perf/tests/Build @@ -23,6 +23,7 @@ perf-test-y += hists_output.o perf-test-y += hists_cumulate.o perf-test-y += bp_signal.o perf-test-y += bp_signal_overflow.o +perf-test-y += event_refresh.o perf-test-y += bp_account.o perf-test-y += wp.o perf-test-y += task-exit.o diff --git a/tools/perf/tests/backward-ring-buffer.c b/tools/perf/tests/backward-ring-buffer.c index 2735cc26d7ee..9314696858ff 100644 --- a/tools/perf/tests/backward-ring-buffer.c +++ b/tools/perf/tests/backward-ring-buffer.c @@ -167,4 +167,4 @@ out_put_evlist: return ret; } -DEFINE_SUITE("Read backward ring buffer", backward_ring_buffer); +DEFINE_SUITE_NEEDS_MLOCK("Read backward ring buffer", backward_ring_buffer); diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 98134b1c74cf..0b8897fd42ca 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -11,6 +11,7 @@ #include <execinfo.h> #endif #include <setjmp.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> @@ -32,6 +33,8 @@ #include <subcmd/parse-options.h> #include <subcmd/run-command.h> +#include <sys/mman.h> + #include "builtin.h" #include "color.h" #include "config.h" @@ -42,6 +45,9 @@ #include "symbol.h" #include "tests-scripts.h" #include "tests.h" +#include "util/cap.h" +#include "util/event.h" +#include "util/mutex.h" #include "util/rlimit.h" #include "util/strbuf.h" #include "util/term.h" @@ -56,10 +62,23 @@ static struct strbuf junit_xml_buf = STRBUF_INIT; static bool dont_fork; /* Fork the tests in parallel and wait for their completion. */ static bool sequential; +/* List what each test needs, set by the "requisites" subcommand. */ +static bool list_requisites; /* Number of times each test is run. */ static unsigned int runs_per_test = 1; /* Number of lines to include in failure snippet. */ static unsigned int failure_snippet_lines = 10; +/* + * The tests mapping ring buffers take turns using their share of + * kernel.perf_event_mlock_kb via this mutex, shared with the forked test + * processes and robust so that a test killed while holding it doesn't leave + * the budget locked, see mutex_init_pshared_robust(). + */ +struct mlock_budget { + struct mutex lock; +}; + +static struct mlock_budget *mlock_budget; const char *dso_to_test; const char *test_objdump_path = "objdump"; static const char *workload_control; @@ -101,6 +120,7 @@ static struct test_suite *generic_tests[] = { &suite__hists_link, &suite__bp_signal, &suite__bp_signal_overflow, + &suite__event_refresh, &suite__bp_accounting, &suite__wp, &suite__task_exit, @@ -283,6 +303,30 @@ static const char *test_description(const struct test_suite *t, int test_case) return t->desc; } +static void requisites__fprintf(FILE *fp, const struct test_suite *t, int test_case) +{ + bool needs_mlock = t->needs_mlock, exclusive; + + if (test_case < 0) + test_case = test_suite__num_test_cases(t) == 1 ? 0 : -1; + + if (test_case >= 0) { + exclusive = t->test_cases[test_case].exclusive; + /* The catch-all implies every other requisite. */ + needs_mlock = needs_mlock || exclusive; + } else { + exclusive = false; + } + + if (!exclusive && !needs_mlock) + return; + + fprintf(fp, " ("); + if (exclusive) + fprintf(fp, "exclusive, "); + fprintf(fp, "mlock)"); +} + static test_fnptr test_function(const struct test_suite *t, int test_case) { if (test_case <= 0) @@ -352,12 +396,30 @@ static void child_test_sig_handler(int sig) siglongjmp(run_test_jmp_buf, sig); } +static int run_test_case(struct child_test *child) +{ + return test_function(child->test, child->test_case_num)(child->test, child->test_case_num); +} + +/* The thread safety analysis can't express a lock held conditionally. */ +static int run_test_case_mlocked(struct child_test *child) +{ + int err; + + mutex_lock(&mlock_budget->lock); + err = run_test_case(child); + mutex_unlock(&mlock_budget->lock); + + return err; +} + static int run_test_child(struct child_process *process) { const int signals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, }; struct child_test *child = container_of(process, struct child_test, process); + bool needs_mlock = mlock_budget && child->test->needs_mlock; int err; close_parent_fds(); @@ -374,7 +436,12 @@ static int run_test_child(struct child_process *process) pr_debug("---- start ----\n"); pr_debug("test child forked, pid %d\n", getpid()); - err = test_function(child->test, child->test_case_num)(child->test, child->test_case_num); + + if (needs_mlock) + err = run_test_case_mlocked(child); + else + err = run_test_case(child); + pr_debug("---- end(%d) ----\n", err); check_leaks(); @@ -628,6 +695,47 @@ static void print_line_highlighted(FILE *fp, const char *line, size_t len) } +static bool is_blank_line(const char *line, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (!isspace((unsigned char)line[i])) + return false; + } + return true; +} + +static bool is_stack_line(const char *line, size_t len) +{ + size_t i = 0; + + while (i < len && isspace((unsigned char)line[i])) + i++; + return i + 1 < len && line[i] == '#' && isdigit((unsigned char)line[i + 1]); +} + +static bool is_ignorable_snippet_line(const char *line, size_t len) +{ + return is_blank_line(line, len) || is_stack_line(line, len); +} + +static bool has_non_blank_between(const char **lines, const size_t *line_lens, + size_t start, size_t end) +{ + for (size_t j = start + 1; j < end; j++) { + if (!is_blank_line(lines[j], line_lens[j])) + return true; + } + return false; +} + +static void pick_snippet_line(bool *pick, size_t idx, unsigned int *picked_count) +{ + if (!pick[idx] && *picked_count < failure_snippet_lines) { + pick[idx] = true; + (*picked_count)++; + } +} + static void print_test_failure_snippet(FILE *fp, const char *buf) { size_t num_lines = 0; @@ -698,43 +806,63 @@ static void print_test_failure_snippet(FILE *fp, const char *buf) } /* Pass 0: Always pick the very first line */ - if (num_lines > 0 && picked_count < failure_snippet_lines) { - pick[0] = true; - picked_count++; - } + if (num_lines > 0) + pick_snippet_line(pick, 0, &picked_count); /* Pass 1: Pick lines with failure keywords from start (Highest Priority) */ for (i = 0; i < num_lines && picked_count < failure_snippet_lines; i++) { size_t dummy; - if (find_next_keyword(lines[i], line_lens[i], &dummy)) { - if (!pick[i]) { - pick[i] = true; - picked_count++; - } - /* Prioritize getting the immediate next line for context */ - if (i + 1 < num_lines && !pick[i + 1] && - picked_count < failure_snippet_lines) { - pick[i + 1] = true; - picked_count++; + if (is_ignorable_snippet_line(lines[i], line_lens[i])) + continue; + if (!find_next_keyword(lines[i], line_lens[i], &dummy)) + continue; + + pick_snippet_line(pick, i, &picked_count); + /* + * For unexpected signals, prioritize the preceding non-blank + * line showing where the test had run to before the signal. + */ + if (!strncmp(lines[i], "---- unexpected signal", 22)) { + size_t j = i; + + while (j > 0) { + j--; + if (!is_ignorable_snippet_line(lines[j], line_lens[j])) { + pick_snippet_line(pick, j, &picked_count); + break; + } } } + /* Prioritize getting the immediate next line for context */ + if (i + 1 < num_lines && + !is_ignorable_snippet_line(lines[i + 1], line_lens[i + 1])) + pick_snippet_line(pick, i + 1, &picked_count); } - /* Pass 2: Fill remaining quota from the end backwards */ + /* + * Pass 2: Fill remaining quota from the end backwards, skipping blank + * and stack trace lines so trailing test progress is shown instead of + * outermost stack frames like main/_start. + */ i = num_lines; while (i > 0 && picked_count < failure_snippet_lines) { i--; - if (!pick[i]) { - pick[i] = true; - picked_count++; - } + if (!is_ignorable_snippet_line(lines[i], line_lens[i])) + pick_snippet_line(pick, i, &picked_count); + } + + /* Pass 3: Fallback to top stack frames if quota remains */ + for (i = 0; i < num_lines && picked_count < failure_snippet_lines; i++) { + if (!is_blank_line(lines[i], line_lens[i])) + pick_snippet_line(pick, i, &picked_count); } for (i = 0; i < num_lines; i++) { if (!pick[i]) continue; - if (last_printed != -1 && (int)i > last_printed + 1) { + if (last_printed != -1 && + has_non_blank_between(lines, line_lens, last_printed, i)) { if (perf_use_color_default) fprintf(fp, "%s...%s\n", PERF_COLOR_BLUE, PERF_COLOR_RESET); else @@ -1450,14 +1578,20 @@ static int perf_test__list(FILE *fp, struct test_suite **suites, int argc, const if (!perf_test__matches(test_description(*t, -1), curr_suite, argc, argv)) continue; - fprintf(fp, "%3d: %s\n", curr_suite + 1, test_description(*t, -1)); + fprintf(fp, "%3d: %s", curr_suite + 1, test_description(*t, -1)); + if (list_requisites) + requisites__fprintf(fp, *t, /*test_case=*/-1); + fprintf(fp, "\n"); if (test_suite__num_test_cases(*t) <= 1) continue; test_suite__for_each_test_case(*t, curr_test_case) { - fprintf(fp, "%3d.%1d: %s\n", curr_suite + 1, curr_test_case + 1, + fprintf(fp, "%3d.%1d: %s", curr_suite + 1, curr_test_case + 1, test_description(*t, curr_test_case)); + if (list_requisites) + requisites__fprintf(fp, *t, curr_test_case); + fprintf(fp, "\n"); } } return 0; @@ -1744,10 +1878,97 @@ static struct test_suite **build_suites(void) #undef for_each_suite } +static bool perf_test__shares_mlock_budget(struct test_suite **suites, int argc, + const char *argv[], struct intlist *skiplist) +{ + int sharing = 0; + + /* The selection rules mirrored here are __cmd_test()'s: when they change, change both. */ + for (int curr_suite = 0; suites[curr_suite]; curr_suite++) { + struct test_suite *t = suites[curr_suite]; + int curr_test_case; + bool suite_matched; + + if (!t->needs_mlock || intlist__find(skiplist, curr_suite + 1)) + continue; + + suite_matched = perf_test__matches(test_description(t, /*test_case=*/-1), + curr_suite, argc, argv); + test_suite__for_each_test_case(t, curr_test_case) { + if (test_exclusive(t, curr_test_case)) + continue; + + if (suite_matched || + perf_test__matches(test_description(t, curr_test_case), + curr_suite, argc, argv)) + sharing += runs_per_test; + } + if (sharing > 1) + return true; + } + return false; +} + +/* + * Once the per user budget set in kernel.perf_event_mlock_kb is spent, what + * maps on top of it is charged against RLIMIT_MEMLOCK and the mapping is + * refused with EPERM when that runs out too, but only for the processes the + * kernel is paranoid about and that have no CAP_IPC_LOCK to lift the check, + * see perf_mmap_calc_limits() in the kernel: used by + * perf_test__setup_mlock_budget() below, that has nothing to take turns on + * when neither applies. + */ +static bool perf_test__mlock_budget_exhaustible(void) +{ + if (perf_cap__capable(CAP_IPC_LOCK)) + return false; + + return perf_event_paranoid() > -1; +} + +static void perf_test__setup_mlock_budget(struct test_suite **suites, int argc, + const char *argv[], struct intlist *skiplist) +{ + /* Nothing can be denied the budget, nothing to take turns for. */ + if (!perf_test__mlock_budget_exhaustible()) { + pr_debug("Not taking turns using the perf mlock budget: nothing can exhaust it\n"); + return; + } + + /* Nothing runs at the same time, nothing to share the budget with. */ + if (sequential || !perf_test__shares_mlock_budget(suites, argc, argv, skiplist)) + return; + + mlock_budget = mmap(NULL, sizeof(*mlock_budget), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, /*fd=*/-1, /*offset=*/0); + if (mlock_budget == MAP_FAILED) { + mlock_budget = NULL; + pr_err("Failed to map the shared area used to take turns using the perf mlock budget\n"); + return; + } + + if (!mutex_init_pshared_robust(&mlock_budget->lock)) { + munmap(mlock_budget, sizeof(*mlock_budget)); + mlock_budget = NULL; + pr_debug("Not taking turns using the perf mlock budget\n"); + } +} + +static void perf_test__teardown_mlock_budget(void) +{ + if (!mlock_budget) + return; + + mutex_destroy(&mlock_budget->lock); + munmap(mlock_budget, sizeof(*mlock_budget)); + mlock_budget = NULL; +} + int cmd_test(int argc, const char **argv) { const char *test_usage[] = { - "perf test [<options>] [{list <test-name-fragment>|[<test-name-fragments>|<test-numbers>]}]", + "perf test [<options>] [{list|requisites} [<test-name-fragment>]|" + "[<test-name-fragments>|<test-numbers>]]", NULL, }; const char *skip = NULL; @@ -1776,7 +1997,7 @@ int cmd_test(int argc, const char **argv) "Generate junit XML output, default test.xml", "test.xml"), OPT_END() }; - const char * const test_subcommands[] = { "list", NULL }; + const char * const test_subcommands[] = { "list", "requisites", NULL }; struct intlist *skiplist = NULL; struct test_suite **suites; int ret; @@ -1808,10 +2029,11 @@ int cmd_test(int argc, const char **argv) setvbuf(stdout, NULL, _IONBF, 0); argc = parse_options_subcommand(argc, argv, test_options, test_subcommands, test_usage, 0); - if (argc >= 1 && !strcmp(argv[0], "list")) { + if (argc >= 1 && (!strcmp(argv[0], "list") || !strcmp(argv[0], "requisites"))) { suites = build_suites(); if (!suites) return errno ? -errno : -ENOMEM; + list_requisites = !strcmp(argv[0], "requisites"); ret = perf_test__list(stdout, suites, argc - 1, argv + 1); free(suites); return ret; @@ -1850,7 +2072,9 @@ int cmd_test(int argc, const char **argv) intlist__delete(skiplist); return err ? -err : -ENOMEM; } + perf_test__setup_mlock_budget(suites, argc, argv, skiplist); ret = __cmd_test(suites, argc, argv, skiplist); + perf_test__teardown_mlock_budget(); free(suites); intlist__delete(skiplist); return ret; diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c index c50b526f44fb..130cd6021f93 100644 --- a/tools/perf/tests/code-reading.c +++ b/tools/perf/tests/code-reading.c @@ -885,4 +885,4 @@ static int test__code_reading(struct test_suite *test __maybe_unused, int subtes }; } -DEFINE_SUITE("Object code reading", code_reading); +DEFINE_SUITE_NEEDS_MLOCK("Object code reading", code_reading); diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 46bc3f597260..18b7052a2dc5 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -138,6 +138,10 @@ static int test__dso_data(struct test_suite *test __maybe_unused, int subtest __ dsos__init(&machine.dsos); dso = dso__new(file); + TEST_ASSERT_VAL("Failed to get dso", dso); + /* override the binary type for dso data access */ + dso__set_binary_type(dso, DSO_BINARY_TYPE__SYSTEM_PATH_DSO); + dso__set_bintype_searched(dso); TEST_ASSERT_VAL("Failed to add dso", !dsos__add(&machine.dsos, dso)); TEST_ASSERT_VAL("Failed to access to dso", dso__data_fd(dso, &machine) >= 0); @@ -226,6 +230,9 @@ static int dsos__create(int cnt, int size, struct dsos *dsos) TEST_ASSERT_VAL("failed to get dso file", file); dso = dso__new(file); TEST_ASSERT_VAL("failed to get dso", dso); + /* override the binary type for dso data access */ + dso__set_binary_type(dso, DSO_BINARY_TYPE__SYSTEM_PATH_DSO); + dso__set_bintype_searched(dso); TEST_ASSERT_VAL("failed to add dso", !dsos__add(dsos, dso)); dso__put(dso); } diff --git a/tools/perf/tests/event_refresh.c b/tools/perf/tests/event_refresh.c new file mode 100644 index 000000000000..726e5de4c795 --- /dev/null +++ b/tools/perf/tests/event_refresh.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Re-arm a sampling event from its overflow signal handler with + * PERF_EVENT_IOC_REFRESH and check that every overflow is delivered. + */ +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <linux/perf_event.h> + +#include "cloexec.h" +#include "debug.h" +#include "tests.h" +#include "tp_pmu.h" +#include <internal/lib.h> // page_size +#include "../perf-sys.h" + +#define NR_EVENTS 1000 +/* A real-time signal is queued, so no overflow can be coalesced away. */ +#define REFRESH_SIGNAL (SIGRTMIN + 1) + +static volatile sig_atomic_t overflows, hups, refresh_errno; + +static void sig_handler(int signum __maybe_unused, siginfo_t *info, + void *uc __maybe_unused) +{ + int saved_errno = errno; + + overflows++; + if (info->si_code == POLL_HUP) + hups++; + if (ioctl(info->si_fd, PERF_EVENT_IOC_REFRESH, 1) < 0) + refresh_errno = errno; + errno = saved_errno; +} + +/* Count overflow signals for NR_EVENTS calls of trigger(), period 1. */ +static int count_overflows(struct perf_event_attr *attr, void (*trigger)(int i)) +{ + struct sigaction sa, old_sa; + uint64_t count = 0; + int fd, i, err = TEST_FAIL; + + attr->size = sizeof(*attr); + attr->sample_period = 1; + attr->disabled = 1; + + fd = sys_perf_event_open(attr, 0, -1, -1, perf_event_open_cloexec_flag()); + if (fd < 0) { + int open_errno = errno; + + pr_debug("failed opening event: %s\n", strerror(open_errno)); + return open_errno == EACCES || open_errno == EPERM ? TEST_SKIP : TEST_FAIL; + } + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = sig_handler; + sa.sa_flags = SA_SIGINFO; + if (sigaction(REFRESH_SIGNAL, &sa, &old_sa) < 0) { + pr_debug("failed setting up signal handler\n"); + close(fd); + return TEST_FAIL; + } + + if (fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK | O_ASYNC) < 0 || + fcntl(fd, F_SETSIG, REFRESH_SIGNAL) < 0 || + fcntl(fd, F_SETOWN, getpid()) < 0) { + pr_debug("failed setting up signal delivery: %s\n", strerror(errno)); + goto out; + } + + overflows = 0; + hups = 0; + refresh_errno = 0; + if (ioctl(fd, PERF_EVENT_IOC_REFRESH, 1) < 0) { + pr_debug("PERF_EVENT_IOC_REFRESH failed: %s\n", strerror(errno)); + goto out; + } + for (i = 0; i < NR_EVENTS; i++) + trigger(i); + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + if (read(fd, &count, sizeof(count)) != sizeof(count)) + pr_debug("failed to read the event count\n"); + pr_debug("%d events, count %" PRIu64 ", %d overflows (%d POLL_HUP), re-arm errno %d\n", + NR_EVENTS, count, overflows, hups, refresh_errno); + /* Unrelated page faults can only add overflows, never hide one. */ + if (overflows >= NR_EVENTS) + err = TEST_OK; +out: + /* Close first: a late signal must still find our handler. */ + close(fd); + sigaction(REFRESH_SIGNAL, &old_sa, NULL); + return err; +} + +static char *pages; + +static void touch_page(int i) +{ + pages[(long)i * page_size] = 1; +} + +static int test__event_refresh_sw(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_SOFTWARE, + .config = PERF_COUNT_SW_PAGE_FAULTS, + }; + int err; + + pages = mmap(NULL, (long)NR_EVENTS * page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pages == MAP_FAILED) + return TEST_FAIL; + /* One fault per touched page: a huge page would cover many of them. */ + madvise(pages, (long)NR_EVENTS * page_size, MADV_NOHUGEPAGE); + + err = count_overflows(&attr, touch_page); + munmap(pages, (long)NR_EVENTS * page_size); + return err; +} + +static void call_getppid(int i __maybe_unused) +{ + syscall(SYS_getppid); +} + +static int test__event_refresh_tp(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_TRACEPOINT, + }; + int id = tp_pmu__id("syscalls", "sys_enter_getppid"); + + if (id < 0) { + pr_debug("no syscalls:sys_enter_getppid tracepoint\n"); + return TEST_SKIP; + } + attr.config = id; + + return count_overflows(&attr, call_getppid); +} + +static struct test_case tests__event_refresh[] = { + TEST_CASE_REASON("Software event", event_refresh_sw, "permissions"), + TEST_CASE_REASON("Tracepoint event", event_refresh_tp, "permissions"), + { .name = NULL, } +}; + +struct test_suite suite__event_refresh = { + .desc = "Event re-armed with PERF_EVENT_IOC_REFRESH", + .test_cases = tests__event_refresh, +}; diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c index b760041bed30..6d8d8cd57200 100644 --- a/tools/perf/tests/keep-tracking.c +++ b/tools/perf/tests/keep-tracking.c @@ -161,4 +161,4 @@ out_err: return err; } -DEFINE_SUITE("Use a dummy software event to keep tracking", keep_tracking); +DEFINE_SUITE_NEEDS_MLOCK("Use a dummy software event to keep tracking", keep_tracking); diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c index 5cec7644952c..602a78d48b10 100644 --- a/tools/perf/tests/mmap-basic.c +++ b/tools/perf/tests/mmap-basic.c @@ -2,6 +2,7 @@ #include <errno.h> #include <inttypes.h> #include <stdlib.h> +#include <unistd.h> #include <fcntl.h> #include <linux/err.h> @@ -12,6 +13,8 @@ #include <perf/evlist.h> #include <perf/mmap.h> +#include <tools/config.h> + #include "cpumap.h" #include "debug.h" #include "event.h" @@ -182,50 +185,83 @@ out_free_threads: } enum user_read_state { - USER_READ_ENABLED, - USER_READ_DISABLED, - USER_READ_UNKNOWN, + USER_READ_ERR_PERM = -2, + USER_READ_ERR_NO_FILE = -1, + + USER_READ_DISABLED = '0', + USER_READ_ENABLED = '1', +#if defined(__i386__) || defined(__x86_64__) || (defined(__riscv) && __riscv_xlen == 64) + /* Unrestricted access on x86, legacy access on RISC-V. */ + USER_READ_LEGACY = '2', +#endif + USER_READ_MAX }; -static enum user_read_state set_user_read(struct perf_pmu *pmu, enum user_read_state enabled) +static enum user_read_state user_read_file_op(int fd, enum user_read_state enabled) { char buf[2] = {0, '\n'}; - ssize_t len; - int events_fd, rdpmc_fd; - enum user_read_state old_user_read = USER_READ_UNKNOWN; - - if (enabled == USER_READ_UNKNOWN) - return USER_READ_UNKNOWN; - - events_fd = perf_pmu__event_source_devices_fd(); - if (events_fd < 0) - return USER_READ_UNKNOWN; - - rdpmc_fd = perf_pmu__pathname_fd(events_fd, pmu->name, "rdpmc", O_RDWR); - if (rdpmc_fd < 0) { - close(events_fd); - return USER_READ_UNKNOWN; - } + ssize_t len = read(fd, buf, 1); + enum user_read_state old_user_read = USER_READ_ERR_NO_FILE; - len = read(rdpmc_fd, buf, sizeof(buf)); - if (len != sizeof(buf)) + if (len <= 0) { pr_debug("%s read failed\n", __func__); + goto out; + } - // Note, on Intel hybrid disabling on 1 PMU will implicitly disable on - // all the core PMUs. - old_user_read = (buf[0] == '1') ? USER_READ_ENABLED : USER_READ_DISABLED; + old_user_read = buf[0]; if (enabled != old_user_read) { - buf[0] = (enabled == USER_READ_ENABLED) ? '1' : '0'; - len = write(rdpmc_fd, buf, sizeof(buf)); + buf[0] = enabled; + lseek(fd, 0, SEEK_SET); + len = write(fd, buf, sizeof(buf)); if (len != sizeof(buf)) pr_debug("%s write failed\n", __func__); } - close(rdpmc_fd); - close(events_fd); + +out: + close(fd); return old_user_read; } +static enum user_read_state set_user_read(struct perf_pmu *pmu, + enum user_read_state enabled) +{ + int events_fd, fd; + enum user_read_state ret = USER_READ_ERR_NO_FILE; + + if (enabled == USER_READ_ERR_NO_FILE || enabled == USER_READ_ERR_PERM) + return enabled; + + events_fd = perf_pmu__event_source_devices_fd(); + if (events_fd >= 0) { + fd = perf_pmu__pathname_fd(events_fd, pmu->name, "rdpmc", O_RDWR); + if (fd >= 0) { + /* + * Note, on Intel hybrid disabling on 1 PMU will implicitly + * disable on all the core PMUs. + */ + ret = user_read_file_op(fd, enabled); + close(events_fd); + return ret; + } else if (errno == EACCES || errno == EROFS) { + /* Permissions failure, flag the failure for a skip. */ + close(events_fd); + return USER_READ_ERR_PERM; + } + close(events_fd); + } + + /* Fallback: perf_user_access interface (arm64, riscv, or similar) */ + fd = open("/proc/sys/kernel/perf_user_access", O_RDWR); + if (fd >= 0) + ret = user_read_file_op(fd, enabled); + else if (errno == EACCES || errno == EROFS) + ret = USER_READ_ERR_PERM; + + return ret; +} + + static int test_stat_user_read(u64 event, enum user_read_state enabled) { struct perf_pmu *pmu = NULL; @@ -246,9 +282,6 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) .config = perf_pmus__supports_extended_type() ? event | ((u64)pmu->type << PERF_PMU_TYPE_SHIFT) : event, -#ifdef __aarch64__ - .config1 = 0x2, /* Request user access */ -#endif }; struct perf_evsel *evsel = NULL; int err; @@ -256,8 +289,28 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) bool mapped = false, opened = false, rdpmc_supported; struct perf_counts_values counts = { .val = 0 }; + if (__is_defined(__aarch64__) && enabled == USER_READ_ENABLED) + attr.config1 = 0x2; /* Request user access */ pr_debug("User space counter reading for PMU %s\n", pmu->name); + + if (saved_user_read_state == USER_READ_ERR_PERM) { + pr_debug("User space counter reading for PMU %s [Failed to get user read state]\n", + pmu->name); + ret = TEST_SKIP; + goto cleanup; + } + + /* Check value is in the expected range */ + if (saved_user_read_state != USER_READ_ERR_NO_FILE && + (saved_user_read_state < USER_READ_DISABLED || + saved_user_read_state >= USER_READ_MAX)) { + pr_err("User space counter reading for PMU %s [Unexpected current state %d]\n", + pmu->name, saved_user_read_state); + ret = TEST_FAIL; + goto cleanup; + } + /* * Restrict scheduling to only use the rdpmc on the CPUs the * event can be on. If the test doesn't run on the CPU of the @@ -301,21 +354,18 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled) goto cleanup; } - if (saved_user_read_state == USER_READ_UNKNOWN) - rdpmc_supported = pc->cap_user_rdpmc && pc->index; - else - rdpmc_supported = (enabled == USER_READ_ENABLED); + rdpmc_supported = (enabled == USER_READ_ENABLED); - if (rdpmc_supported && (!pc->cap_user_rdpmc || !pc->index)) { - pr_err("User space counter reading for PMU %s [Failed unexpected supported counter access %d %d]\n", - pmu->name, pc->cap_user_rdpmc, pc->index); + if (rdpmc_supported && !pc->index) { + pr_err("User space counter reading for PMU %s [index must be set when enabled %d]\n", + pmu->name, pc->index); ret = TEST_FAIL; goto cleanup; } - if (!rdpmc_supported && pc->cap_user_rdpmc) { - pr_err("User space counter reading for PMU %s [Failed unexpected unsupported counter access %d]\n", - pmu->name, pc->cap_user_rdpmc); + if (rdpmc_supported != pc->cap_user_rdpmc) { + pr_err("User space counter reading for PMU %s [cap should match requested state %d %d]\n", + pmu->name, pc->cap_user_rdpmc, rdpmc_supported); ret = TEST_FAIL; goto cleanup; } @@ -363,7 +413,8 @@ cleanup: perf_evsel__munmap(evsel); if (opened) perf_evsel__close(evsel); - perf_evsel__delete(evsel); + if (evsel) + perf_evsel__delete(evsel); /* If the affinity was changed, then put it back to all CPUs. */ if (pmu->cpus != NULL) { @@ -378,13 +429,13 @@ cleanup: return ret; } -static int test__mmap_user_read_instr(struct test_suite *test __maybe_unused, +static int __maybe_unused test__mmap_user_read_instr(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { return test_stat_user_read(PERF_COUNT_HW_INSTRUCTIONS, USER_READ_ENABLED); } -static int test__mmap_user_read_cycles(struct test_suite *test __maybe_unused, +static int __maybe_unused test__mmap_user_read_cycles(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { return test_stat_user_read(PERF_COUNT_HW_CPU_CYCLES, USER_READ_ENABLED); @@ -406,46 +457,34 @@ static struct test_case tests__basic_mmap[] = { TEST_CASE_REASON("Read samples using the mmap interface", basic_mmap, "permissions"), - TEST_CASE_REASON_EXCLUSIVE("User space counter reading of instructions", - mmap_user_read_instr, + #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ (defined(__riscv) && __riscv_xlen == 64) - "permissions" -#else - "unsupported" -#endif - ), + /* + * libperf only supports userspace read for these platforms, see + * tools/lib/perf/mmap.c + */ + TEST_CASE_REASON_EXCLUSIVE("User space counter reading of instructions", + mmap_user_read_instr, + "permissions"), TEST_CASE_REASON_EXCLUSIVE("User space counter reading of cycles", - mmap_user_read_cycles, -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__riscv) && __riscv_xlen == 64) - "permissions" -#else - "unsupported" + mmap_user_read_cycles, + "permissions"), #endif - ), + + /* Counter read via userpage fallback is supported on all platforms */ TEST_CASE_REASON_EXCLUSIVE("User space counter disabling instructions", - mmap_user_read_instr_disabled, -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__riscv) && __riscv_xlen == 64) - "permissions" -#else - "unsupported" -#endif - ), + mmap_user_read_instr_disabled, + "permissions"), TEST_CASE_REASON_EXCLUSIVE("User space counter disabling cycles", - mmap_user_read_cycles_disabled, -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ - (defined(__riscv) && __riscv_xlen == 64) - "permissions" -#else - "unsupported" -#endif - ), + mmap_user_read_cycles_disabled, + "permissions"), + { .name = NULL, } }; struct test_suite suite__basic_mmap = { .desc = "mmap interface tests", .test_cases = tests__basic_mmap, + .needs_mlock = true, }; diff --git a/tools/perf/tests/openat-syscall-tp-fields.c b/tools/perf/tests/openat-syscall-tp-fields.c index 5365889d326f..1ea336fbc7e7 100644 --- a/tools/perf/tests/openat-syscall-tp-fields.c +++ b/tools/perf/tests/openat-syscall-tp-fields.c @@ -160,4 +160,5 @@ static struct test_case tests__syscall_openat_tp_fields[] = { struct test_suite suite__syscall_openat_tp_fields = { .desc = "syscalls:sys_enter_openat event fields", .test_cases = tests__syscall_openat_tp_fields, + .needs_mlock = true, }; diff --git a/tools/perf/tests/pe-file-parsing.c b/tools/perf/tests/pe-file-parsing.c index 30c7da79e109..190520ffc3ad 100644 --- a/tools/perf/tests/pe-file-parsing.c +++ b/tools/perf/tests/pe-file-parsing.c @@ -59,7 +59,7 @@ static int run_dir(const char *d) TEST_ASSERT_VAL("Failed to get dso", dso); ret = dso__load_bfd_symbols(dso, debugfile); - TEST_ASSERT_VAL("Failed to load symbols", ret == 0); + TEST_ASSERT_VAL("Failed to load symbols", ret > 0); dso__sort_by_name(dso); sym = dso__find_symbol_by_name(dso, "main", &idx); @@ -74,17 +74,20 @@ static int test__pe_file_parsing(struct test_suite *test __maybe_unused, { struct stat st; char path_dir[PATH_MAX]; - - /* First try development tree tests. */ - if (!lstat("./tests", &st)) - return run_dir("./tests"); - - /* Then installed path. */ + const char * const tests_dirs[] = { + "./tools/perf/tests", + "./tests", + "./source/tests", + path_dir, + }; snprintf(path_dir, PATH_MAX, "%s/tests", get_argv_exec_path()); - if (!lstat(path_dir, &st)) - return run_dir(path_dir); + for (size_t i = 0; i < ARRAY_SIZE(tests_dirs); ++i) { + if (!lstat(tests_dirs[i], &st)) + return run_dir(tests_dirs[i]); + } + pr_debug("cannot find the tests directory\n"); return TEST_SKIP; } @@ -93,6 +96,7 @@ static int test__pe_file_parsing(struct test_suite *test __maybe_unused, static int test__pe_file_parsing(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { + pr_debug("libbfd is not available\n"); return TEST_SKIP; } diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c index 0cac6ae1a1fc..370dbde08eac 100644 --- a/tools/perf/tests/perf-record.c +++ b/tools/perf/tests/perf-record.c @@ -372,4 +372,5 @@ static struct test_case tests__PERF_RECORD[] = { struct test_suite suite__PERF_RECORD = { .desc = "PERF_RECORD_* events & perf_sample fields", .test_cases = tests__PERF_RECORD, + .needs_mlock = true, }; diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c index f8f71fdd32b1..0cefa2df2db7 100644 --- a/tools/perf/tests/perf-time-to-tsc.c +++ b/tools/perf/tests/perf-time-to-tsc.c @@ -218,4 +218,5 @@ static struct test_case time_to_tsc_tests[] = { struct test_suite suite__perf_time_to_tsc = { .desc = "Convert perf time to TSC", .test_cases = time_to_tsc_tests, + .needs_mlock = true, }; diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c index f507ce39439a..e6e4f4cf1fa2 100644 --- a/tools/perf/tests/pmu-events.c +++ b/tools/perf/tests/pmu-events.c @@ -906,9 +906,12 @@ static int test__parsing_callback(const struct pmu_metric *pm, struct metric_expr *mexp; list_for_each_entry (mexp, &me->head, nd) { + double result; + if (strcmp(mexp->metric_name, pm->metric_name)) continue; - pr_debug("Result %f\n", test_generic_metric(mexp, 0)); + result = test_generic_metric(mexp, 0); + pr_debug("Result %f\n", result); err = 0; (*failures)--; goto out_err; diff --git a/tools/perf/tests/shell/attr/README b/tools/perf/tests/shell/attr/README index 67c4ca76b85d..c7b49cdf8729 100644 --- a/tools/perf/tests/shell/attr/README +++ b/tools/perf/tests/shell/attr/README @@ -49,10 +49,10 @@ Following tests are defined (with perf commands): perf record --call-graph dwarf kill (test-record-graph-dwarf) perf record --call-graph fp kill (test-record-graph-fp) perf record --call-graph fp kill (test-record-graph-fp-aarch64) - perf record -e '{cycles,instructions}' kill (test-record-group1) - perf record -e '{cycles/period=1/,instructions/period=2/}:S' kill (test-record-group2) - perf record -e '{cycles,cache-misses}:S' kill (test-record-group-sampling1) - perf record -c 10000 -e '{cycles,cache-misses}:S' kill (test-record-group-sampling2) + perf record -e '{cycles,default_core/legacy-hardware-config=1/}' kill (test-record-group1) + perf record -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill (test-record-group2) + perf record -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill (test-record-group-sampling1) + perf record -c 10000 -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill (test-record-group-sampling2) perf record -D kill (test-record-no-delay) perf record -i kill (test-record-no-inherit) perf record -n kill (test-record-no-samples) @@ -67,5 +67,5 @@ Following tests are defined (with perf commands): perf stat -d kill (test-stat-detailed-1) perf stat -dd kill (test-stat-detailed-2) perf stat -ddd kill (test-stat-detailed-3) - perf stat -e '{cycles,instructions}' kill (test-stat-group1) + perf stat -e '{cycles,default_core/legacy-hardware-config=1/}' kill (test-stat-group1) perf stat -i -e cycles kill (test-stat-no-inherit) diff --git a/tools/perf/tests/shell/attr/base-stat-group b/tools/perf/tests/shell/attr/base-stat-group new file mode 100644 index 000000000000..9bcb8ab3687f --- /dev/null +++ b/tools/perf/tests/shell/attr/base-stat-group @@ -0,0 +1,45 @@ +[event] +fd=1 +group_fd=-1 +# 0 or PERF_FLAG_FD_CLOEXEC flag +flags=0|8 +cpu=* +type=0 +size=176 +config=0 +sample_period=0 +sample_type=65536 +# The default and -d/-dd/-ddd metrics open part of their events in groups: +# the members get read_format=15 and are enabled right away, the standalone +# ones keep read_format=3 and wait for the exec, and the leaders get 15 when +# their group has members, 3 when alone, as create_perf_stat_counter() goes. +read_format=3|15 +disabled=0|1 +inherit=1 +pinned=0 +exclusive=0 +exclude_user=0 +exclude_kernel=0|1 +exclude_hv=0|1 +exclude_idle=0 +mmap=0 +comm=0 +freq=0 +inherit_stat=0 +enable_on_exec=0|1 +task=0 +watermark=0 +precise_ip=0 +mmap_data=0 +sample_id_all=0 +exclude_host=0|1 +exclude_guest=0|1 +exclude_callchain_kernel=0 +exclude_callchain_user=0 +wakeup_events=0 +bp_type=0 +config1=0 +config2=0 +branch_sample_type=0 +sample_regs_user=0 +sample_stack_user=0 diff --git a/tools/perf/tests/shell/attr/test-record-graph-dwarf b/tools/perf/tests/shell/attr/test-record-graph-dwarf index ae92061d611d..fbe0faa8cdb6 100644 --- a/tools/perf/tests/shell/attr/test-record-graph-dwarf +++ b/tools/perf/tests/shell/attr/test-record-graph-dwarf @@ -4,7 +4,7 @@ args = --no-bpf-event --call-graph dwarf -- kill >/dev/null 2>&1 ret = 1 [event:base-record] -sample_type=45359 +sample_type=12583 exclude_callchain_user=1 sample_stack_user=8192 # TODO different for each arch, no support for that now diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling b/tools/perf/tests/shell/attr/test-record-group-sampling index 86a940d7895d..ed47dd1e5984 100644 --- a/tools/perf/tests/shell/attr/test-record-group-sampling +++ b/tools/perf/tests/shell/attr/test-record-group-sampling @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1 ret = 1 kernel_until = 6.12 +requires = core-pmu [event-1:base-record] fd=1 @@ -15,7 +16,7 @@ inherit=0 fd=2 group_fd=1 -# cache-misses +# cache-misses, explicitly requested with the legacy hardware encoding type=0 config=3 diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling1 b/tools/perf/tests/shell/attr/test-record-group-sampling1 index 4748ab7bf684..9cf013dc03dd 100644 --- a/tools/perf/tests/shell/attr/test-record-group-sampling1 +++ b/tools/perf/tests/shell/attr/test-record-group-sampling1 @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1 ret = 1 kernel_since = 6.12 +requires = core-pmu [event-1:base-record] fd=1 @@ -30,7 +31,7 @@ inherit=1 fd=2 group_fd=1 -# cache-misses +# cache-misses, explicitly requested with the legacy hardware encoding type=0 config=3 diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling2 b/tools/perf/tests/shell/attr/test-record-group-sampling2 index e0432244a0eb..bd1eb4161e4a 100644 --- a/tools/perf/tests/shell/attr/test-record-group-sampling2 +++ b/tools/perf/tests/shell/attr/test-record-group-sampling2 @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -c 10000 -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -c 10000 -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1 ret = 1 kernel_since = 6.12 +requires = core-pmu [event-1:base-record] fd=1 @@ -36,7 +37,7 @@ write_backward=0 fd=2 group_fd=1 -# cache-misses +# cache-misses, explicitly requested with the legacy hardware encoding type=0 config=3 diff --git a/tools/perf/tests/shell/attr/test-record-group1 b/tools/perf/tests/shell/attr/test-record-group1 index eeb1db392bc9..5fdd5288555f 100644 --- a/tools/perf/tests/shell/attr/test-record-group1 +++ b/tools/perf/tests/shell/attr/test-record-group1 @@ -1,7 +1,8 @@ [config] command = record -args = --no-bpf-event -e '{cycles,instructions}' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=1/}' kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event-1:base-record] fd=1 diff --git a/tools/perf/tests/shell/attr/test-record-group2 b/tools/perf/tests/shell/attr/test-record-group2 index 891d41a7bddf..e7274108f5fd 100644 --- a/tools/perf/tests/shell/attr/test-record-group2 +++ b/tools/perf/tests/shell/attr/test-record-group2 @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill >/dev/null 2>&1 ret = 1 kernel_until = 6.12 +requires = core-pmu [event-1:base-record] fd=1 diff --git a/tools/perf/tests/shell/attr/test-record-group3 b/tools/perf/tests/shell/attr/test-record-group3 index 249be884959e..e74fd3a48ac9 100644 --- a/tools/perf/tests/shell/attr/test-record-group3 +++ b/tools/perf/tests/shell/attr/test-record-group3 @@ -1,8 +1,9 @@ [config] command = record -args = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1 +args = --no-bpf-event -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill >/dev/null 2>&1 ret = 1 kernel_since = 6.12 +requires = core-pmu [event-1:base-record] fd=1 diff --git a/tools/perf/tests/shell/attr/test-stat-C0 b/tools/perf/tests/shell/attr/test-stat-C0 index a2c76d10b2bb..9d242e93e3a9 100644 --- a/tools/perf/tests/shell/attr/test-stat-C0 +++ b/tools/perf/tests/shell/attr/test-stat-C0 @@ -2,6 +2,7 @@ command = stat args = -e cycles -C 0 kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event:base-stat] # events are disabled by default when attached to cpu diff --git a/tools/perf/tests/shell/attr/test-stat-basic b/tools/perf/tests/shell/attr/test-stat-basic index 69867d049fda..277897bd2626 100644 --- a/tools/perf/tests/shell/attr/test-stat-basic +++ b/tools/perf/tests/shell/attr/test-stat-basic @@ -2,6 +2,7 @@ command = stat args = -e cycles kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event:base-stat] optional=1 diff --git a/tools/perf/tests/shell/attr/test-stat-default b/tools/perf/tests/shell/attr/test-stat-default index 8dd27c1fb661..6a0c0c43f233 100644 --- a/tools/perf/tests/shell/attr/test-stat-default +++ b/tools/perf/tests/shell/attr/test-stat-default @@ -2,74 +2,75 @@ command = stat args = kill >/dev/null 2>&1 ret = 1 +requires = core-pmu # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] +[event1:base-stat-group] fd=1 type=1 config=1 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] +[event2:base-stat-group] fd=2 type=1 config=3 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] +[event3:base-stat-group] fd=3 type=1 config=4 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] +[event4:base-stat-group] fd=4 type=1 config=2 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] +[event5:base-stat-group] fd=5 type=0 config=0 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] +[event6:base-stat-group] fd=6 type=0 config=7 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] +[event7:base-stat-group] fd=7 type=0 config=8 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] +[event8:base-stat-group] fd=8 type=0 config=1 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] +[event9:base-stat-group] fd=9 type=0 config=4 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] +[event10:base-stat-group] fd=10 type=0 config=5 optional=1 # PERF_TYPE_RAW / slots (0x400) -[event11:base-stat] +[event11:base-stat-group] fd=11 group_fd=-1 type=4 @@ -78,7 +79,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-retiring (0x8000) -[event12:base-stat] +[event12:base-stat-group] fd=12 group_fd=11 type=4 @@ -89,7 +90,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-bad-spec (0x8100) -[event13:base-stat] +[event13:base-stat-group] fd=13 group_fd=11 type=4 @@ -100,7 +101,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fe-bound (0x8200) -[event14:base-stat] +[event14:base-stat-group] fd=14 group_fd=11 type=4 @@ -111,7 +112,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-be-bound (0x8300) -[event15:base-stat] +[event15:base-stat-group] fd=15 group_fd=11 type=4 @@ -122,7 +123,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400) -[event16:base-stat] +[event16:base-stat-group] fd=16 group_fd=11 type=4 @@ -133,7 +134,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500) -[event17:base-stat] +[event17:base-stat-group] fd=17 group_fd=11 type=4 @@ -144,7 +145,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600) -[event18:base-stat] +[event18:base-stat-group] fd=18 group_fd=11 type=4 @@ -155,7 +156,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-mem-bound (0x8700) -[event19:base-stat] +[event19:base-stat-group] fd=19 group_fd=11 type=4 @@ -166,71 +167,99 @@ read_format=15 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event20:base-stat] +[event20:base-stat-group] fd=20 type=4 config=4109 optional=1 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ -[event21:base-stat] +[event21:base-stat-group] fd=21 type=4 config=17039629 optional=1 -# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD -[event22:base-stat] +# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76) +[event22:base-stat-group] fd=22 type=4 -config=60 +config=60|118 optional=1 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY -[event23:base-stat] +[event23:base-stat-group] fd=23 type=4 config=2097421 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK -[event24:base-stat] +[event24:base-stat-group] fd=24 type=4 config=316 optional=1 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE -[event25:base-stat] +[event25:base-stat-group] fd=25 type=4 config=412 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE -[event26:base-stat] +[event26:base-stat-group] fd=26 type=4 config=572 optional=1 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS -[event27:base-stat] +[event27:base-stat-group] fd=27 type=4 config=706 optional=1 # PERF_TYPE_RAW / UOPS_ISSUED.ANY -[event28:base-stat] +[event28:base-stat-group] fd=28 type=4 config=270 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event29:base-stat] +[event29:base-stat-group] fd=29 type=4 config=4269 +optional=1 + +# PERF_TYPE_RAW / instructions (core PMU sysfs event) +[event30:base-stat-group] +fd=30 +type=4 +config=192 +optional=1 + +# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3) +[event31:base-stat-group] +fd=31 +type=4 +config=197|195 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9) +[event32:base-stat-group] +fd=32 +type=4 +config=25166094|208|647|169 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187) +[event33:base-stat-group] +fd=33 +type=4 +config=25166257|25182129|209|391 optional=1
\ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-1 b/tools/perf/tests/shell/attr/test-stat-detailed-1 index 12a2ebf4e64a..3d47dc6f815f 100644 --- a/tools/perf/tests/shell/attr/test-stat-detailed-1 +++ b/tools/perf/tests/shell/attr/test-stat-detailed-1 @@ -2,76 +2,77 @@ command = stat args = -d kill >/dev/null 2>&1 ret = 1 +requires = core-pmu # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] +[event1:base-stat-group] fd=1 type=1 config=1 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] +[event2:base-stat-group] fd=2 type=1 config=3 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] +[event3:base-stat-group] fd=3 type=1 config=4 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] +[event4:base-stat-group] fd=4 type=1 config=2 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] +[event5:base-stat-group] fd=5 type=0 config=0 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] +[event6:base-stat-group] fd=6 type=0 config=7 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] +[event7:base-stat-group] fd=7 type=0 config=8 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] +[event8:base-stat-group] fd=8 type=0 config=1 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] +[event9:base-stat-group] fd=9 type=0 config=4 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] +[event10:base-stat-group] fd=10 type=0 config=5 optional=1 # PERF_TYPE_RAW / slots (0x400) -[event11:base-stat] +[event11:base-stat-group] fd=11 group_fd=-1 type=4 @@ -80,7 +81,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-retiring (0x8000) -[event12:base-stat] +[event12:base-stat-group] fd=12 group_fd=11 type=4 @@ -91,7 +92,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-bad-spec (0x8100) -[event13:base-stat] +[event13:base-stat-group] fd=13 group_fd=11 type=4 @@ -102,7 +103,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fe-bound (0x8200) -[event14:base-stat] +[event14:base-stat-group] fd=14 group_fd=11 type=4 @@ -113,7 +114,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-be-bound (0x8300) -[event15:base-stat] +[event15:base-stat-group] fd=15 group_fd=11 type=4 @@ -124,7 +125,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400) -[event16:base-stat] +[event16:base-stat-group] fd=16 group_fd=11 type=4 @@ -135,7 +136,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500) -[event17:base-stat] +[event17:base-stat-group] fd=17 group_fd=11 type=4 @@ -146,7 +147,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600) -[event18:base-stat] +[event18:base-stat-group] fd=18 group_fd=11 type=4 @@ -157,7 +158,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-mem-bound (0x8700) -[event19:base-stat] +[event19:base-stat-group] fd=19 group_fd=11 type=4 @@ -168,63 +169,63 @@ read_format=15 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event20:base-stat] +[event20:base-stat-group] fd=20 type=4 config=4109 optional=1 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ -[event21:base-stat] +[event21:base-stat-group] fd=21 type=4 config=17039629 optional=1 -# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD -[event22:base-stat] +# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76) +[event22:base-stat-group] fd=22 type=4 -config=60 +config=60|118 optional=1 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY -[event23:base-stat] +[event23:base-stat-group] fd=23 type=4 config=2097421 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK -[event24:base-stat] +[event24:base-stat-group] fd=24 type=4 config=316 optional=1 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE -[event25:base-stat] +[event25:base-stat-group] fd=25 type=4 config=412 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE -[event26:base-stat] +[event26:base-stat-group] fd=26 type=4 config=572 optional=1 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS -[event27:base-stat] +[event27:base-stat-group] fd=27 type=4 config=706 optional=1 # PERF_TYPE_RAW / UOPS_ISSUED.ANY -[event28:base-stat] +[event28:base-stat-group] fd=28 type=4 config=270 @@ -234,7 +235,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event29:base-stat] +[event29:base-stat-group] fd=29 type=3 config=0 @@ -244,7 +245,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event30:base-stat] +[event30:base-stat-group] fd=30 type=3 config=65536 @@ -254,7 +255,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event31:base-stat] +[event31:base-stat-group] fd=31 type=3 config=2 @@ -264,15 +265,43 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event32:base-stat] +[event32:base-stat-group] fd=32 type=3 config=65538 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event33:base-stat] +[event33:base-stat-group] fd=33 type=4 config=4269 +optional=1 + +# PERF_TYPE_RAW / instructions (core PMU sysfs event) +[event34:base-stat-group] +fd=34 +type=4 +config=192 +optional=1 + +# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3) +[event35:base-stat-group] +fd=35 +type=4 +config=197|195 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9) +[event36:base-stat-group] +fd=36 +type=4 +config=25166094|208|647|169 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187) +[event37:base-stat-group] +fd=37 +type=4 +config=25166257|25182129|209|391 optional=1
\ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-2 b/tools/perf/tests/shell/attr/test-stat-detailed-2 index 66ea25b7d38f..ebe1f9e2736d 100644 --- a/tools/perf/tests/shell/attr/test-stat-detailed-2 +++ b/tools/perf/tests/shell/attr/test-stat-detailed-2 @@ -2,76 +2,77 @@ command = stat args = -dd kill >/dev/null 2>&1 ret = 1 +requires = core-pmu # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] +[event1:base-stat-group] fd=1 type=1 config=1 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] +[event2:base-stat-group] fd=2 type=1 config=3 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] +[event3:base-stat-group] fd=3 type=1 config=4 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] +[event4:base-stat-group] fd=4 type=1 config=2 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] +[event5:base-stat-group] fd=5 type=0 config=0 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] +[event6:base-stat-group] fd=6 type=0 config=7 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] +[event7:base-stat-group] fd=7 type=0 config=8 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] +[event8:base-stat-group] fd=8 type=0 config=1 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] +[event9:base-stat-group] fd=9 type=0 config=4 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] +[event10:base-stat-group] fd=10 type=0 config=5 optional=1 # PERF_TYPE_RAW / slots (0x400) -[event11:base-stat] +[event11:base-stat-group] fd=11 group_fd=-1 type=4 @@ -80,7 +81,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-retiring (0x8000) -[event12:base-stat] +[event12:base-stat-group] fd=12 group_fd=11 type=4 @@ -91,7 +92,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-bad-spec (0x8100) -[event13:base-stat] +[event13:base-stat-group] fd=13 group_fd=11 type=4 @@ -102,7 +103,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fe-bound (0x8200) -[event14:base-stat] +[event14:base-stat-group] fd=14 group_fd=11 type=4 @@ -113,7 +114,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-be-bound (0x8300) -[event15:base-stat] +[event15:base-stat-group] fd=15 group_fd=11 type=4 @@ -124,7 +125,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400) -[event16:base-stat] +[event16:base-stat-group] fd=16 group_fd=11 type=4 @@ -135,7 +136,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500) -[event17:base-stat] +[event17:base-stat-group] fd=17 group_fd=11 type=4 @@ -146,7 +147,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600) -[event18:base-stat] +[event18:base-stat-group] fd=18 group_fd=11 type=4 @@ -157,7 +158,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-mem-bound (0x8700) -[event19:base-stat] +[event19:base-stat-group] fd=19 group_fd=11 type=4 @@ -168,63 +169,63 @@ read_format=15 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event20:base-stat] +[event20:base-stat-group] fd=20 type=4 config=4109 optional=1 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ -[event21:base-stat] +[event21:base-stat-group] fd=21 type=4 config=17039629 optional=1 -# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD -[event22:base-stat] +# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76) +[event22:base-stat-group] fd=22 type=4 -config=60 +config=60|118 optional=1 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY -[event23:base-stat] +[event23:base-stat-group] fd=23 type=4 config=2097421 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK -[event24:base-stat] +[event24:base-stat-group] fd=24 type=4 config=316 optional=1 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE -[event25:base-stat] +[event25:base-stat-group] fd=25 type=4 config=412 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE -[event26:base-stat] +[event26:base-stat-group] fd=26 type=4 config=572 optional=1 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS -[event27:base-stat] +[event27:base-stat-group] fd=27 type=4 config=706 optional=1 # PERF_TYPE_RAW / UOPS_ISSUED.ANY -[event28:base-stat] +[event28:base-stat-group] fd=28 type=4 config=270 @@ -234,7 +235,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event29:base-stat] +[event29:base-stat-group] fd=29 type=3 config=0 @@ -244,7 +245,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event30:base-stat] +[event30:base-stat-group] fd=30 type=3 config=65536 @@ -254,7 +255,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event31:base-stat] +[event31:base-stat-group] fd=31 type=3 config=2 @@ -264,7 +265,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event32:base-stat] +[event32:base-stat-group] fd=32 type=3 config=65538 @@ -274,7 +275,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event33:base-stat] +[event33:base-stat-group] fd=33 type=3 config=1 @@ -284,7 +285,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event34:base-stat] +[event34:base-stat-group] fd=34 type=3 config=65537 @@ -294,7 +295,7 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event35:base-stat] +[event35:base-stat-group] fd=35 type=3 config=3 @@ -304,7 +305,7 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event36:base-stat] +[event36:base-stat-group] fd=36 type=3 config=65539 @@ -314,7 +315,7 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event37:base-stat] +[event37:base-stat-group] fd=37 type=3 config=4 @@ -324,15 +325,43 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event38:base-stat] +[event38:base-stat-group] fd=38 type=3 config=65540 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event39:base-stat] +[event39:base-stat-group] fd=39 type=4 config=4269 +optional=1 + +# PERF_TYPE_RAW / instructions (core PMU sysfs event) +[event40:base-stat-group] +fd=40 +type=4 +config=192 +optional=1 + +# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3) +[event41:base-stat-group] +fd=41 +type=4 +config=197|195 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9) +[event42:base-stat-group] +fd=42 +type=4 +config=25166094|208|647|169 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187) +[event43:base-stat-group] +fd=43 +type=4 +config=25166257|25182129|209|391 optional=1
\ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-3 b/tools/perf/tests/shell/attr/test-stat-detailed-3 index 4a27bbfb9f87..e7eef828245c 100644 --- a/tools/perf/tests/shell/attr/test-stat-detailed-3 +++ b/tools/perf/tests/shell/attr/test-stat-detailed-3 @@ -2,76 +2,77 @@ command = stat args = -ddd kill >/dev/null 2>&1 ret = 1 +requires = core-pmu # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] +[event1:base-stat-group] fd=1 type=1 config=1 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] +[event2:base-stat-group] fd=2 type=1 config=3 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] +[event3:base-stat-group] fd=3 type=1 config=4 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] +[event4:base-stat-group] fd=4 type=1 config=2 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] +[event5:base-stat-group] fd=5 type=0 config=0 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] +[event6:base-stat-group] fd=6 type=0 config=7 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] +[event7:base-stat-group] fd=7 type=0 config=8 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] +[event8:base-stat-group] fd=8 type=0 config=1 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] +[event9:base-stat-group] fd=9 type=0 config=4 optional=1 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] +[event10:base-stat-group] fd=10 type=0 config=5 optional=1 # PERF_TYPE_RAW / slots (0x400) -[event11:base-stat] +[event11:base-stat-group] fd=11 group_fd=-1 type=4 @@ -80,7 +81,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-retiring (0x8000) -[event12:base-stat] +[event12:base-stat-group] fd=12 group_fd=11 type=4 @@ -91,7 +92,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-bad-spec (0x8100) -[event13:base-stat] +[event13:base-stat-group] fd=13 group_fd=11 type=4 @@ -102,7 +103,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fe-bound (0x8200) -[event14:base-stat] +[event14:base-stat-group] fd=14 group_fd=11 type=4 @@ -113,7 +114,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-be-bound (0x8300) -[event15:base-stat] +[event15:base-stat-group] fd=15 group_fd=11 type=4 @@ -124,7 +125,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400) -[event16:base-stat] +[event16:base-stat-group] fd=16 group_fd=11 type=4 @@ -135,7 +136,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500) -[event17:base-stat] +[event17:base-stat-group] fd=17 group_fd=11 type=4 @@ -146,7 +147,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600) -[event18:base-stat] +[event18:base-stat-group] fd=18 group_fd=11 type=4 @@ -157,7 +158,7 @@ read_format=15 optional=1 # PERF_TYPE_RAW / topdown-mem-bound (0x8700) -[event19:base-stat] +[event19:base-stat-group] fd=19 group_fd=11 type=4 @@ -168,63 +169,63 @@ read_format=15 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event20:base-stat] +[event20:base-stat-group] fd=20 type=4 config=4109 optional=1 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ -[event21:base-stat] +[event21:base-stat-group] fd=21 type=4 config=17039629 optional=1 -# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD -[event22:base-stat] +# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76) +[event22:base-stat-group] fd=22 type=4 -config=60 +config=60|118 optional=1 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY -[event23:base-stat] +[event23:base-stat-group] fd=23 type=4 config=2097421 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK -[event24:base-stat] +[event24:base-stat-group] fd=24 type=4 config=316 optional=1 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE -[event25:base-stat] +[event25:base-stat-group] fd=25 type=4 config=412 optional=1 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE -[event26:base-stat] +[event26:base-stat-group] fd=26 type=4 config=572 optional=1 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS -[event27:base-stat] +[event27:base-stat-group] fd=27 type=4 config=706 optional=1 # PERF_TYPE_RAW / UOPS_ISSUED.ANY -[event28:base-stat] +[event28:base-stat-group] fd=28 type=4 config=270 @@ -234,7 +235,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event29:base-stat] +[event29:base-stat-group] fd=29 type=3 config=0 @@ -244,7 +245,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event30:base-stat] +[event30:base-stat-group] fd=30 type=3 config=65536 @@ -254,7 +255,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event31:base-stat] +[event31:base-stat-group] fd=31 type=3 config=2 @@ -264,7 +265,7 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event32:base-stat] +[event32:base-stat-group] fd=32 type=3 config=65538 @@ -274,7 +275,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event33:base-stat] +[event33:base-stat-group] fd=33 type=3 config=1 @@ -284,7 +285,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event34:base-stat] +[event34:base-stat-group] fd=34 type=3 config=65537 @@ -294,7 +295,7 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event35:base-stat] +[event35:base-stat-group] fd=35 type=3 config=3 @@ -304,7 +305,7 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event36:base-stat] +[event36:base-stat-group] fd=36 type=3 config=65539 @@ -314,7 +315,7 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event37:base-stat] +[event37:base-stat-group] fd=37 type=3 config=4 @@ -324,7 +325,7 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event38:base-stat] +[event38:base-stat-group] fd=38 type=3 config=65540 @@ -334,7 +335,7 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event39:base-stat] +[event39:base-stat-group] fd=39 type=3 config=512 @@ -344,15 +345,43 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event40:base-stat] +[event40:base-stat-group] fd=40 type=3 config=66048 optional=1 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING -[event41:base-stat] +[event41:base-stat-group] fd=41 type=4 config=4269 +optional=1 + +# PERF_TYPE_RAW / instructions (core PMU sysfs event) +[event42:base-stat-group] +fd=42 +type=4 +config=192 +optional=1 + +# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3) +[event43:base-stat-group] +fd=43 +type=4 +config=197|195 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9) +[event44:base-stat-group] +fd=44 +type=4 +config=25166094|208|647|169 +optional=1 + +# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187) +[event45:base-stat-group] +fd=45 +type=4 +config=25166257|25182129|209|391 optional=1
\ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-stat-group1 b/tools/perf/tests/shell/attr/test-stat-group1 index 1746751123dc..7a31b27933f1 100644 --- a/tools/perf/tests/shell/attr/test-stat-group1 +++ b/tools/perf/tests/shell/attr/test-stat-group1 @@ -1,7 +1,8 @@ [config] command = stat -args = -e '{cycles,instructions}' kill >/dev/null 2>&1 +args = -e '{cycles,default_core/legacy-hardware-config=1/}' kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event-1:base-stat] fd=1 diff --git a/tools/perf/tests/shell/attr/test-stat-no-inherit b/tools/perf/tests/shell/attr/test-stat-no-inherit index 924fbb9300d1..38f746855b1b 100644 --- a/tools/perf/tests/shell/attr/test-stat-no-inherit +++ b/tools/perf/tests/shell/attr/test-stat-no-inherit @@ -2,6 +2,7 @@ command = stat args = -i -e cycles kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event:base-stat] inherit=0 diff --git a/tools/perf/tests/shell/drm_pmu.sh b/tools/perf/tests/shell/drm_pmu.sh index e629fe0e8463..c827e613fc66 100755 --- a/tools/perf/tests/shell/drm_pmu.sh +++ b/tools/perf/tests/shell/drm_pmu.sh @@ -1,5 +1,5 @@ #!/bin/bash -# DRM PMU +# DRM PMU (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/ftrace.sh b/tools/perf/tests/shell/ftrace.sh index 9f6e590f6437..84d6126bf94d 100755 --- a/tools/perf/tests/shell/ftrace.sh +++ b/tools/perf/tests/shell/ftrace.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf ftrace tests +# perf ftrace tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/kallsyms.sh b/tools/perf/tests/shell/kallsyms.sh index d0eb99753f47..e3674cc52b30 100755 --- a/tools/perf/tests/shell/kallsyms.sh +++ b/tools/perf/tests/shell/kallsyms.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf kallsyms tests +# perf kallsyms tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 err=0 diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py index 68f06df9565a..df1993883199 100644 --- a/tools/perf/tests/shell/lib/attr.py +++ b/tools/perf/tests/shell/lib/attr.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 import configparser +import errno import os import sys import glob @@ -28,6 +29,192 @@ def data_equal(a, b): return False +CORE_PMU_AVAILABLE = None +CORE_PMU_COUNT = None + +def core_pmu_available(): + """ + Is there a core PMU on this machine? Without one hardware events aren't + supported and get replaced by the software fallback, so there's no way to + check the expectations that pin their legacy encodings. The answer can't + change while the tests run, so probe the binding only once. + """ + core_pmu_probe() + return CORE_PMU_AVAILABLE + +def core_pmu_probe(): + """ + Ask perf itself, through its python binding, to parse and open 'cycles' + once: a parse failure or an ENOENT means no PMU can count it, any other + error means we can't tell whether there is one, and the number of events + it expanded to is the number of core PMUs, see + parse_events_multi_pmu_add(), which is what core_pmu_count() asks for. + """ + global CORE_PMU_AVAILABLE, CORE_PMU_COUNT + + if CORE_PMU_AVAILABLE is not None: + return + + try: + import perf + parse_events = perf.parse_events + except (ImportError, AttributeError): + # Nothing to ask about it, let the expectations speak. + CORE_PMU_AVAILABLE = True + return + + try: + evlist = parse_events('cycles') + except OSError: + # No PMU that can count it, the same as the ENOENT below. + CORE_PMU_AVAILABLE = False + return + + CORE_PMU_COUNT = len(evlist) + try: + evlist.open() + except OSError as err: + # Anything else is a permission problem or alike, not our business here. + CORE_PMU_AVAILABLE = err.errno != errno.ENOENT + return + evlist.close() + + CORE_PMU_AVAILABLE = True + +PERF_TYPE_HARDWARE = 0 +PERF_TYPE_HW_CACHE = 3 +PERF_SAMPLE_ID = 1 << 6 +PERF_SAMPLE_IDENTIFIER = 1 << 16 +PERF_TYPE_SOFTWARE = 1 +PERF_COUNT_SW_DUMMY = 9 + +def is_dummy_tracking_event(event): + """ + Is this the dummy event perf opens to carry the tracking events (mmap, + comm, task) when the requested ones can't, like when there is more than one + core PMU and each core PMU's events run on only part of the CPUs? + """ + return (event.get('type') == str(PERF_TYPE_SOFTWARE) and + event.get('config') == str(PERF_COUNT_SW_DUMMY)) + +# With more than one core PMU the tracking terms go to the dummy tracking event, +# leaving the requested events without them, so they can't be matched there. +TRACKING_TERMS = ['mmap', 'comm', 'task', 'mmap_data'] + +# The legacy event types, where perf may qualify 'config' with the core PMU +# type in its top 32 bits when there is more than one core PMU. +LEGACY_TYPES = (str(PERF_TYPE_HARDWARE), str(PERF_TYPE_HW_CACHE)) + +def mask_bits(value, bits): + """ + Take the bits in 'bits' out of each of the '|'-separated alternatives in + 'value', keeping the rest of the expression, wildcards included. + """ + masked = [] + + for alternative in value.split('|'): + try: + alternative = str(int(alternative, 0) & ~bits) + except ValueError: + pass + masked.append(alternative) + + return '|'.join(masked) + +def hide_core_pmu_type(value): + """ + The kernel can be told which core PMU a legacy type event is meant for by + putting its type in the top 32 bits of 'config', which perf only does when + there is more than one core PMU, so take those bits out to keep the + expectations written for the single core PMU case matching on + heterogeneous machines. + """ + return mask_bits(value, 0xffffffff00000000) + +# The two sample id bits mean the same thing here: how the samples of the +# events in the evlist get associated with the event that produced them. +SAMPLE_ID_BITS = PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER + +# No real sample_type bit is this high, so it can stand for "either of the +# sample id bits" when comparing the two sides. +SAMPLE_ID_EITHER = 1 << 40 + +PERF_FORMAT_GROUP = 1 << 3 + +def core_pmu_count(): + """ + How many core PMUs does perf see? An event with no PMU named in it + becomes one event per core PMU, see parse_events_multi_pmu_add(), and + that is the expansion that leaves the clones for the other core PMUs + without any group members and that makes perf qualify 'config' with the + type of the PMU it is for, so this is the number of events perf makes of + 'cycles', from the probe core_pmu_available() shares. + """ + core_pmu_probe() + return CORE_PMU_COUNT + +def single_core_pmu(): + """ + Does perf see only one core PMU? Asked for by the comparisons that need + it, not at import time, as a run may never get to one. + """ + return core_pmu_count() == 1 + +def hide_group_format(expected, recorded): + """ + read_format's PERF_FORMAT_GROUP bit asks for the group members to be read + along with the leader, which aside from this bit is verified with the + fd/group_fd based group checking, and with more than one core PMU a group + whose members are pinned to one of them, say 'default_core', leaves the + clones for the other core PMUs without any members, so that bit isn't + there: whether it is there is a consequence of how the evlist got + expanded to more than one core PMU, not of what the expectations are + about, so take it out on both sides. On a machine with a single core PMU + there is nothing to expand, so the expectations keep verifying it. + """ + if single_core_pmu(): + return expected, recorded + + return (mask_bits(expected, PERF_FORMAT_GROUP), + mask_bits(recorded, PERF_FORMAT_GROUP)) + +def hide_sample_id(expected, recorded): + """ + perf asks for a way to tell the samples apart, PERF_SAMPLE_IDENTIFIER where + available, PERF_SAMPLE_ID otherwise, see evlist__config(): whenever the + evlist has more than one event, and when asked to outright, with + --sample-identifier, by an auxtrace session, or by record__config() for a + pipe run, none of which depends on the sample types, as those only choose + between the two bits: + with more than one core PMU the dummy tracking events make the sample types + differ, so IDENTIFIER is asked for even for events whose expectations ask + for ID, written on a machine with a single core PMU. + + So treat the two as the same thing: when the expectation asks for either + of them, ask for "an id of some sort" on both sides, and when the + expectation doesn't ask for any, take the bits out of what got recorded. + """ + try: + asked = int(expected, 0) & SAMPLE_ID_BITS + except ValueError: + # The expectation is a wildcard or a set of alternatives. + return expected, recorded + + try: + recorded_id_bits = int(recorded, 0) & SAMPLE_ID_BITS + except ValueError: + # The recorded side is a wildcard or a set of alternatives. + return expected, mask_bits(recorded, SAMPLE_ID_BITS) + + if not asked: + if single_core_pmu(): + return expected, recorded + return expected, mask_bits(recorded, SAMPLE_ID_BITS) + + return (str(int(expected, 0) & ~SAMPLE_ID_BITS | SAMPLE_ID_EITHER), + str(int(recorded, 0) & ~SAMPLE_ID_BITS | + (SAMPLE_ID_EITHER if recorded_id_bits else 0))) + class Fail(Exception): def __init__(self, test, msg): self.msg = msg @@ -36,17 +223,20 @@ class Fail(Exception): return '\'%s\' - %s' % (self.test.path, self.msg) class Notest(Exception): - def __init__(self, test, arch): - self.arch = arch + def __init__(self, test, reason): + self.reason = reason self.test = test def getMsg(self): - return '[%s] \'%s\'' % (self.arch, self.test.path) + return '[%s] \'%s\'' % (self.reason, self.test.path) class Unsup(Exception): - def __init__(self, test): + def __init__(self, test, ret, expected): self.test = test + self.ret = ret + self.expected = expected def getMsg(self): - return '\'%s\'' % self.test.path + return '\'%s\' exit %d, expected %s, no attrs recorded to verify' % ( + self.test.path, self.ret, self.expected) class Event(dict): terms = [ @@ -101,12 +291,76 @@ class Event(dict): self.add(base) self.add(data) + def hide_core_pmu_type_terms(self, other): + """ + Is this a legacy type event? Then the core PMU type that perf may have + put in the top 32 bits of 'config' to tell the kernel which core PMU it + wants isn't part of what we asked for, see hide_core_pmu_type(). + """ + for event in (self, other): + for type in event.get('type', '').split('|'): + if type in LEGACY_TYPES: + return True + + return False + + def hide_core_pmu_type(self, term, value, other): + if term == 'config' and self.hide_core_pmu_type_terms(other): + return hide_core_pmu_type(value) + + return value + + def hide_event_count_dependent_terms(self, term, expected, recorded): + if term == 'sample_type': + return hide_sample_id(expected, recorded) + if term == 'read_format': + return hide_group_format(expected, recorded) + + return expected, recorded + + def recorded_side(self, other): + """ + Which one is what perf recorded? Both directions get compared, see + Test.compare(), so this can't be taken from the arguments' order. + """ + if getattr(other, 'recorded', False): + return other + + return self if getattr(self, 'recorded', False) else None + + def hide_tracking_via_dummy(self, term, other): + """ + The tracking terms are set on the tracking event, which is the dummy + event when there is more than one core PMU, leaving the requested + events without them, nothing to match on then. + """ + if term not in TRACKING_TERMS: + return False + + for event in (self, other): + if term in getattr(event, 'tracking_moved_to_dummy', ()): + return True + + return False + def equal(self, other): for t in Event.terms: log.debug(" [%s] %s %s", t, self[t], other[t]) if t not in self or t not in other: return False - if not data_equal(self[t], other[t]): + if self.hide_tracking_via_dummy(t, other): + continue + + recorded = self.recorded_side(other) + if recorded is self: + expected, got_event = other, self + else: + expected, got_event = self, other + exp = self.hide_core_pmu_type(t, expected[t], other) + got = self.hide_core_pmu_type(t, got_event[t], other) + if recorded is not None: + exp, got = self.hide_event_count_dependent_terms(t, exp, got) + if not data_equal(exp, got): return False return True @@ -119,14 +373,30 @@ class Event(dict): for t in Event.terms: if t not in self or t not in other: continue - if not data_equal(self[t], other[t]): - log.warning("expected %s=%s, got %s", t, self[t], other[t]) + if self.hide_tracking_via_dummy(t, other): + continue + + recorded = self.recorded_side(other) + if recorded is self: + expected, got_event = other, self + else: + expected, got_event = self, other + exp = self.hide_core_pmu_type(t, expected[t], other) + got = self.hide_core_pmu_type(t, got_event[t], other) + if recorded is not None: + exp, got = self.hide_event_count_dependent_terms(t, exp, got) + if not data_equal(exp, got): + log.warning("expected %s=%s, got %s", t, expected[t], + got_event[t]) def parse_version(version): if not version: return None return [int(v) for v in version.split(".")[0:2]] +def format_version(version): + return ".".join(str(v) for v in version) + # Test file description needs to have following sections: # [config] # - just single instance in file @@ -138,6 +408,9 @@ def parse_version(version): # 'arch' - architecture specific test (optional) # comma separated list, ! at the beginning # negates it. +# 'requires' - Comma separated list of things the test needs, it is +# skipped when any of them isn't there, currently: +# core-pmu - the machine can count hardware events. # 'auxv' - Truthy statement that is evaled in the scope of the auxv map. When false, # the test is skipped. For example 'auxv["AT_HWCAP"] == 10'. (optional) # 'kernel_since' - Inclusive kernel version from which the test will start running. Only the @@ -173,6 +446,7 @@ class Test(object): self.arch = '' self.auxv = parser.get('config', 'auxv', fallback=None) + self.requires = parser.get('config', 'requires', fallback='') self.kernel_since = parse_version(parser.get('config', 'kernel_since', fallback=None)) self.kernel_until = parse_version(parser.get('config', 'kernel_until', fallback=None)) self.expect = {} @@ -188,13 +462,17 @@ class Test(object): def skip_test_kernel_since(self): if not self.kernel_since: - return False - return not self.kernel_since <= parse_version(platform.release()) + return None + if self.kernel_since <= parse_version(platform.release()): + return None + return "kernel older than %s" % format_version(self.kernel_since) def skip_test_kernel_until(self): if not self.kernel_until: - return False - return not parse_version(platform.release()) < self.kernel_until + return None + if parse_version(platform.release()) < self.kernel_until: + return None + return "kernel newer than %s" % format_version(self.kernel_until) def skip_test_auxv(self): def new_auxv(a, pattern): @@ -210,18 +488,20 @@ class Test(object): return (items[0], value) if not self.auxv: - return False + return None auxv = subprocess.check_output("LD_SHOW_AUXV=1 sleep 0", shell=True) \ .decode(sys.stdout.encoding) pattern = re.compile(r"[: ]+") auxv = dict([new_auxv(a, pattern) for a in auxv.splitlines()]) - return not eval(self.auxv) + if eval(self.auxv): + return None + + return "the auxv condition doesn't hold here" def skip_test_arch(self, myarch): # If architecture not set always run test if self.arch == '': - # log.warning("test for arch %s is ok" % myarch) - return False + return None # Allow multiple values in assignment separated by ',' arch_list = self.arch.split(',') @@ -231,16 +511,26 @@ class Test(object): arch_list[0] = arch_list[0][1:] log.warning("excluded architecture list %s", arch_list) for arch_item in arch_list: - # log.warning("test for %s arch is %s" % (arch_item, myarch)) if arch_item == myarch: - return True - return False + return "arch = %s, machine = %s" % (self.arch, myarch) + return None for arch_item in arch_list: - # log.warning("test for architecture '%s' current '%s'" % (arch_item, myarch)) if arch_item == myarch: - return False - return True + return None + + return "arch = %s, machine = %s" % (self.arch, myarch) + + def skip_test_requires(self): + for requirement in filter(None, map(str.strip, self.requires.split(','))): + if requirement == 'core-pmu': + if not core_pmu_available(): + log.warning("test limitation 'requires %s'", requirement) + return "needs a core PMU, none here" + else: + log.warning("unknown requirement '%s', ignored", requirement) + + return None def restore_sample_rate(self, value=10000): try: @@ -284,17 +574,36 @@ class Test(object): def run_cmd(self, tempdir): _junk1, _junk2, _junk3, _junk4, myarch = (os.uname()) - if self.skip_test_arch(myarch): - raise Notest(self, myarch) - - if self.skip_test_auxv(): - raise Notest(self, "auxv skip") - - if self.skip_test_kernel_since(): - raise Notest(self, "old kernel skip") - - if self.skip_test_kernel_until(): - raise Notest(self, "new kernel skip") + arch = self.skip_test_arch(myarch) + if arch: + log.debug(" detail: the test file has arch = %s, the machine reports %s", + self.arch, myarch) + raise Notest(self, arch) + + auxv = self.skip_test_auxv() + if auxv: + log.debug(" detail: the auxv condition %s doesn't hold, the bits come " + "from the LD_SHOW_AUXV=1 output", self.auxv) + raise Notest(self, auxv) + + kernel = self.skip_test_kernel_since() + if kernel: + log.debug(" detail: the test file's kernel_since = %s, this kernel is %s", + format_version(self.kernel_since), platform.release()) + raise Notest(self, kernel) + + kernel = self.skip_test_kernel_until() + if kernel: + log.debug(" detail: the test file's kernel_until = %s, this kernel is %s", + format_version(self.kernel_until), platform.release()) + raise Notest(self, kernel) + + requirement = self.skip_test_requires() + if requirement: + log.debug(" detail: requires core-pmu, nothing countable here, check " + "kernel.perf_event_paranoid and the PMU nodes under " + "/sys/bus/event_source/") + raise Notest(self, requirement) self.restore_sample_rate() cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir, @@ -304,12 +613,80 @@ class Test(object): log.info(" '%s' ret '%s', expected '%s'", cmd, str(ret), str(self.ret)) if not data_equal(str(ret), str(self.ret)): + plain_cmd = "%s %s -o /dev/null %s" % ( + self.perf, self.command, + self.args.split('>/dev/null')[0].rstrip()) if self.test_ret: raise Fail(self, "Perf exit code failure") else: - raise Unsup(self) + log.debug(" detail: '%s' exited with %d, the expectations want the " + "workload's %s exit, so there are no attrs recorded to " + "verify; this is normally a capability problem, run '%s' " + "to see why", plain_cmd, ret, self.ret, plain_cmd) + raise Unsup(self, ret, str(self.ret)) + + def drop_dummy_tracking_events(self): + """ + Leave out the dummy event perf itself opened, see + is_dummy_tracking_event(), unless the expectations ask for one. + """ + if any(is_dummy_tracking_event(e) for e in self.expect.values()): + return + + if not any(is_dummy_tracking_event(e) for e in self.result.values()): + return + + moved = set() + for name, event in list(self.result.items()): + if is_dummy_tracking_event(event): + log.debug(" dropping dummy tracking event [%s]", name) + moved.update(t for t in TRACKING_TERMS if event.get(t) == '1') + del self.result[name] + + # The tracking terms the dropped dummies carried, flag the remaining + # events so that the match leaves only those out. + for event in self.result.values(): + event.tracking_moved_to_dummy = moved + + def drop_other_core_pmu_events(self, devices='/sys/bus/event_source/devices'): + """ + Leave out the events of the other core PMUs, which have their own + encodings, when the expectations name one of the core PMUs. + """ + asked = set() + for event in self.expect.values(): + types = event.get('type', '').split('|') + if '*' in types: + return + asked.update(types) - def compare(self, expect, result): + try: + names = os.listdir(devices) + except OSError: + return + + core = set() + for name in names: + path = os.path.join(devices, name) + has_cpus = os.path.exists(os.path.join(path, 'cpus')) + if name not in ('cpu', 'cpum_cf') and not has_cpus: + continue + try: + with open(os.path.join(path, 'type'), encoding='utf-8') as f: + core.add(f.read().strip()) + except OSError: + pass + + if not core & asked: + return + + others = core - asked + for name, event in list(self.result.items()): + if event.get('type') in others: + log.debug(" dropping other core PMU event [%s]", name) + del self.result[name] + + def compare(self, expect, result, strict_groups=True): match = {} log.debug(" compare") @@ -343,8 +720,9 @@ class Test(object): match[exp_name] = exp_list - # For each defined group in the expected events - # check we match the same group in the result. + # Check that each expected group is matched in the result; an event + # opened in a group may match an expectation that doesn't declare + # one, as those only verify the event attributes. for exp_name, exp_event in expect.items(): group = exp_event.group @@ -353,6 +731,8 @@ class Test(object): for res_name in match[exp_name]: res_group = result[res_name].group + if res_group == '' and not strict_groups: + continue if res_group not in match[group]: raise Fail(self, 'group failure') @@ -384,6 +764,11 @@ class Test(object): log.debug(" loading result events") for f in glob.glob(tempdir + '/event*'): self.load_events(f, self.result) + for event in self.result.values(): + event.recorded = True + + self.drop_dummy_tracking_events() + self.drop_other_core_pmu_events() # resolve group_fd to event names self.resolve_groups(self.expect) @@ -391,7 +776,7 @@ class Test(object): # do the expectation - results matching - both ways self.compare(self.expect, self.result) - self.compare(self.result, self.expect) + self.compare(self.result, self.expect, strict_groups=False) finally: # cleanup diff --git a/tools/perf/tests/shell/list.sh b/tools/perf/tests/shell/list.sh index 14a03e0290ed..d4a0b04154d6 100755 --- a/tools/perf/tests/shell/list.sh +++ b/tools/perf/tests/shell/list.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf list tests +# perf list tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/perf_sched_stats.sh b/tools/perf/tests/shell/perf_sched_stats.sh index f13eb0a75b76..4ede9d701630 100755 --- a/tools/perf/tests/shell/perf_sched_stats.sh +++ b/tools/perf/tests/shell/perf_sched_stats.sh @@ -1,5 +1,5 @@ #!/bin/sh -# perf sched stats tests +# perf sched stats tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/probe_vfs_getname.sh b/tools/perf/tests/shell/probe_vfs_getname.sh index c40e5f3f0825..43bdd693076b 100755 --- a/tools/perf/tests/shell/probe_vfs_getname.sh +++ b/tools/perf/tests/shell/probe_vfs_getname.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Add vfs_getname probe to get syscall args filenames +# Add vfs_getname probe to get syscall args filenames (no_mlock) # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017 diff --git a/tools/perf/tests/shell/python-use.sh b/tools/perf/tests/shell/python-use.sh index 2d6017d5dd9d..ece4d306869a 100755 --- a/tools/perf/tests/shell/python-use.sh +++ b/tools/perf/tests/shell/python-use.sh @@ -1,5 +1,5 @@ #!/bin/bash -# 'import perf' in python +# 'import perf' in python (no_mlock) # SPDX-License-Identifier: GPL-2.0 # Just test if we can load the python binding. set -e diff --git a/tools/perf/tests/shell/schedstat_snapshots.sh b/tools/perf/tests/shell/schedstat_snapshots.sh index a4bdf2673d43..2050478e3f1a 100755 --- a/tools/perf/tests/shell/schedstat_snapshots.sh +++ b/tools/perf/tests/shell/schedstat_snapshots.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Validate CPU and domain pairing in perf sched stats snapshots +# Validate CPU and domain pairing in perf sched stats snapshots (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/shell/stat+csv_output.sh index a5b34c42e8d7..8beb6c674d05 100755 --- a/tools/perf/tests/shell/stat+csv_output.sh +++ b/tools/perf/tests/shell/stat+csv_output.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat CSV output linter +# perf stat CSV output linter (no_mlock) # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) # Tests various perf stat CSV output commands for the # correct number of fields and the CSV separator set to ','. diff --git a/tools/perf/tests/shell/stat+csv_summary.sh b/tools/perf/tests/shell/stat+csv_summary.sh index 9a4353db3825..6d8beb88fb6f 100755 --- a/tools/perf/tests/shell/stat+csv_summary.sh +++ b/tools/perf/tests/shell/stat+csv_summary.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat csv summary test +# perf stat csv summary test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat+event_uniquifying.sh b/tools/perf/tests/shell/stat+event_uniquifying.sh index b5dec6b6da36..f5c6fa30d8db 100755 --- a/tools/perf/tests/shell/stat+event_uniquifying.sh +++ b/tools/perf/tests/shell/stat+event_uniquifying.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat events uniquifying +# perf stat events uniquifying (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/shell/stat+json_output.sh index 4969ad70b67b..cbf7a4ffd7e8 100755 --- a/tools/perf/tests/shell/stat+json_output.sh +++ b/tools/perf/tests/shell/stat+json_output.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat JSON output linter +# perf stat JSON output linter (no_mlock) # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) # Checks various perf stat JSON output commands for the # correct number of fields. diff --git a/tools/perf/tests/shell/stat+shadow_stat.sh b/tools/perf/tests/shell/stat+shadow_stat.sh index cabbbf17c662..7a58e1507ce2 100755 --- a/tools/perf/tests/shell/stat+shadow_stat.sh +++ b/tools/perf/tests/shell/stat+shadow_stat.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat metrics (shadow stat) test +# perf stat metrics (shadow stat) test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/shell/stat+std_output.sh index 64c2b81f02de..b915656a20f6 100755 --- a/tools/perf/tests/shell/stat+std_output.sh +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat STD output linter +# perf stat STD output linter (no_mlock) # SPDX-License-Identifier: GPL-2.0 # Tests various perf stat STD output commands for # default event and metricgroup diff --git a/tools/perf/tests/shell/stat.sh b/tools/perf/tests/shell/stat.sh index 1f5d4cc05807..ac9e031831da 100755 --- a/tools/perf/tests/shell/stat.sh +++ b/tools/perf/tests/shell/stat.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat tests +# perf stat tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat_all_metricgroups.sh b/tools/perf/tests/shell/stat_all_metricgroups.sh index 81bc7070b5ab..074a437da0ae 100755 --- a/tools/perf/tests/shell/stat_all_metricgroups.sh +++ b/tools/perf/tests/shell/stat_all_metricgroups.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf all metricgroups test +# perf all metricgroups test (no_mlock) # SPDX-License-Identifier: GPL-2.0 ParanoidAndNotRoot() diff --git a/tools/perf/tests/shell/stat_all_metrics.sh b/tools/perf/tests/shell/stat_all_metrics.sh index df5fa27b21ae..9d0d4b8a217e 100755 --- a/tools/perf/tests/shell/stat_all_metrics.sh +++ b/tools/perf/tests/shell/stat_all_metrics.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf all metrics test +# perf all metrics test (no_mlock) # SPDX-License-Identifier: GPL-2.0 ParanoidAndNotRoot() diff --git a/tools/perf/tests/shell/stat_all_pfm.sh b/tools/perf/tests/shell/stat_all_pfm.sh index c08c186af2c4..78d8006fb729 100755 --- a/tools/perf/tests/shell/stat_all_pfm.sh +++ b/tools/perf/tests/shell/stat_all_pfm.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf all libpfm4 events test +# perf all libpfm4 events test (no_mlock) # SPDX-License-Identifier: GPL-2.0 if perf version --build-options | grep HAVE_LIBPFM | grep -q OFF diff --git a/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh index febe3af06441..d1b4632c17a3 100755 --- a/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh +++ b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat --bpf-counters --for-each-cgroup test +# perf stat --bpf-counters --for-each-cgroup test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat_metrics_cgrp.sh b/tools/perf/tests/shell/stat_metrics_cgrp.sh index 0728f48f21fa..75b8e01d11cc 100755 --- a/tools/perf/tests/shell/stat_metrics_cgrp.sh +++ b/tools/perf/tests/shell/stat_metrics_cgrp.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf stat metrics --for-each-cgroup test +# perf stat metrics --for-each-cgroup test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/stat_metrics_values.sh b/tools/perf/tests/shell/stat_metrics_values.sh index 0982f6b9737a..3e073a0e5b9f 100755 --- a/tools/perf/tests/shell/stat_metrics_values.sh +++ b/tools/perf/tests/shell/stat_metrics_values.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf metrics value validation +# perf metrics value validation (no_mlock) # SPDX-License-Identifier: GPL-2.0 shelldir=$(dirname "$0") diff --git a/tools/perf/tests/shell/test_ilist_python.sh b/tools/perf/tests/shell/test_ilist_python.sh index 83048474710d..a235539c0176 100755 --- a/tools/perf/tests/shell/test_ilist_python.sh +++ b/tools/perf/tests/shell/test_ilist_python.sh @@ -1,5 +1,5 @@ #!/bin/bash -# perf script ilist and perf list --tui test +# perf script ilist and perf list --tui test (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/shell/test_stat_cpi_python.sh b/tools/perf/tests/shell/test_stat_cpi_python.sh index 6cb376c92e2f..d1571a20f468 100755 --- a/tools/perf/tests/shell/test_stat_cpi_python.sh +++ b/tools/perf/tests/shell/test_stat_cpi_python.sh @@ -1,6 +1,6 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 -# stat-cpi python test +# stat-cpi python test (no_mlock) set -e diff --git a/tools/perf/tests/shell/test_test_junit_output.sh b/tools/perf/tests/shell/test_test_junit_output.sh index 38a29e646906..8fc92e083c34 100755 --- a/tools/perf/tests/shell/test_test_junit_output.sh +++ b/tools/perf/tests/shell/test_test_junit_output.sh @@ -1,6 +1,9 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 -# perf test junit XML output validation +# perf test junit XML output validation (no_mlock) +# +# Waiving the mlock requisite this way holds only while the 'perf test' +# runs below keep picking tests that don't map ring buffers. set -e diff --git a/tools/perf/tests/shell/test_uprobe_from_different_cu.sh b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh index 33bf36c4037e..0a02ff9b86d6 100755 --- a/tools/perf/tests/shell/test_uprobe_from_different_cu.sh +++ b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh @@ -1,5 +1,5 @@ #!/bin/bash -# test perf probe of function from different CU +# test perf probe of function from different CU (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e diff --git a/tools/perf/tests/sw-clock.c b/tools/perf/tests/sw-clock.c index d18185881635..1b62dae55f07 100644 --- a/tools/perf/tests/sw-clock.c +++ b/tools/perf/tests/sw-clock.c @@ -149,4 +149,4 @@ static int test__sw_clock_freq(struct test_suite *test __maybe_unused, int subte return ret; } -DEFINE_SUITE("Software clock events period values", sw_clock_freq); +DEFINE_SUITE_NEEDS_MLOCK("Software clock events period values", sw_clock_freq); diff --git a/tools/perf/tests/task-exit.c b/tools/perf/tests/task-exit.c index 95393edbfe36..9fac426ac2f9 100644 --- a/tools/perf/tests/task-exit.c +++ b/tools/perf/tests/task-exit.c @@ -158,4 +158,5 @@ struct test_case tests__task_exit[] = { struct test_suite suite__task_exit = { .desc = "Number of exit events of a simple workload", .test_cases = tests__task_exit, + .needs_mlock = true, }; diff --git a/tools/perf/tests/tests-scripts.c b/tools/perf/tests/tests-scripts.c index 0370bc701373..a86b5289f09b 100644 --- a/tools/perf/tests/tests-scripts.c +++ b/tools/perf/tests/tests-scripts.c @@ -2,6 +2,7 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> +#include <stdio.h> #include <linux/ctype.h> #include <linux/kernel.h> #include <linux/string.h> @@ -179,6 +180,79 @@ static int shell_test__run(struct test_suite *test, int subtest __maybe_unused) return WEXITSTATUS(err) == 2 ? TEST_SKIP : TEST_FAIL; } +/* + * The requisites are a comma separated list in the parens ending the + * description line, e.g. "perf stat tests (no_mlock)": "exclusive" is the + * catch-all, implying every other requisite, "no_mlock" waives the default of + * needing room in the per user budget set in kernel.perf_event_mlock_kb. + */ +static void shell_test__requisites(char *desc, bool *exclusive, bool *needs_mlock) +{ + char *lparen, *rparen, *requisites, *token, *tokp; + bool known = false; + + /* Only a list closing the description line is taken for requisites. */ + rparen = strrchr(desc, ')'); + if (rparen == NULL || rparen[1] != '\0') + return; + + lparen = strrchr(desc, '('); + if (lparen == NULL || lparen > rparen) + return; + + /* Tokenize a copy: a list with nothing recognized leaves desc alone. */ + requisites = strndup(lparen + 1, rparen - lparen - 1); + if (requisites == NULL) { + pr_err("Failed to copy the requisites of '%s'\n", desc); + return; + } + + for (token = strtok_r(requisites, ",", &tokp); token; + token = strtok_r(NULL, ",", &tokp)) { + char *end = token + strlen(token); + const char *requisite = token; + bool waived; + + while (isspace(*token)) + token++; + while (end > token && isspace(end[-1])) + end--; + *end = '\0'; + + /* A "no_" prefix waives the requisite named after it. */ + waived = strncmp(token, "no_", sizeof("no_") - 1) == 0; + if (waived) + token += sizeof("no_") - 1; + + if (!strcmp(token, "mlock")) { + *needs_mlock = !waived; + } else if (!strcmp(token, "exclusive") && !waived) { + *exclusive = true; + } else { + pr_debug("Ignoring %s requisite '%s' in '%s'\n", + strcmp(token, "exclusive") == 0 ? "a waived" : "an unknown", + requisite, desc); + continue; + } + + known = true; + } + + zfree(&requisites); + + if (!known) + return; + + /* Drop the list and the space before it from the description. */ + *lparen = '\0'; + if (lparen > desc && isspace(lparen[-1])) + lparen[-1] = '\0'; + + /* The catch-all implies every other requisite, whatever the order. */ + if (*exclusive) + *needs_mlock = true; +} + static void append_script(int dir_fd, const char *name, char *desc, struct test_suite ***result, size_t *result_sz) @@ -187,7 +261,8 @@ static void append_script(int dir_fd, const char *name, char *desc, struct test_suite *test_suite, **result_tmp; struct test_case *tests; ssize_t len; - char *exclusive; + /* Shell tests run perf, that maps ring buffers, so they need the budget. */ + bool exclusive = false, needs_mlock = true; snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd); len = readlink(link, filename, sizeof(filename) - 1); @@ -204,11 +279,8 @@ static void append_script(int dir_fd, const char *name, char *desc, return; } tests[0].name = strdup_check(name); - exclusive = strstr(desc, " (exclusive)"); - if (exclusive != NULL) { - tests[0].exclusive = true; - exclusive[0] = '\0'; - } + shell_test__requisites(desc, &exclusive, &needs_mlock); + tests[0].exclusive = exclusive; tests[0].desc = strdup_check(desc); tests[0].run_case = shell_test__run; test_suite = zalloc(sizeof(*test_suite)); @@ -220,6 +292,7 @@ static void append_script(int dir_fd, const char *name, char *desc, test_suite->desc = desc; test_suite->test_cases = tests; test_suite->priv = strdup_check(filename); + test_suite->needs_mlock = needs_mlock; /* Realloc is good enough, though we could realloc by chunks, not that * anyone will ever measure performance here */ result_tmp = realloc(*result, (*result_sz + 1) * sizeof(*result_tmp)); diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index f379620069d3..20ae3609892b 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -46,6 +46,12 @@ struct test_suite { struct test_case *test_cases; void *priv; int (*setup)(struct test_suite *suite); + /* + * Set for the suites mapping perf ring buffers, that need room in the + * per user budget set in kernel.perf_event_mlock_kb, see + * shell_test__requisites() for the shell suites. + */ + bool needs_mlock; }; #define DECLARE_SUITE(name) \ @@ -103,6 +109,17 @@ struct test_suite { .test_cases = tests__##_name, \ } +#define DEFINE_SUITE_NEEDS_MLOCK(description, _name) \ + struct test_case tests__##_name[] = { \ + TEST_CASE(description, _name), \ + { .name = NULL, } \ + }; \ + struct test_suite suite__##_name = { \ + .desc = description, \ + .test_cases = tests__##_name, \ + .needs_mlock = true, \ + } + /* Tests */ DECLARE_SUITE(vmlinux_matches_kallsyms); DECLARE_SUITE(openat_syscall_event); @@ -124,6 +141,7 @@ DECLARE_SUITE(parse_events); DECLARE_SUITE(hists_link); DECLARE_SUITE(bp_signal); DECLARE_SUITE(bp_signal_overflow); +DECLARE_SUITE(event_refresh); DECLARE_SUITE(bp_accounting); DECLARE_SUITE(wp); DECLARE_SUITE(task_exit); diff --git a/tools/perf/trace/beauty/include/linux/socket.h b/tools/perf/trace/beauty/include/linux/socket.h index 2a8d7b14f1d1..b17fdb7d38a7 100644 --- a/tools/perf/trace/beauty/include/linux/socket.h +++ b/tools/perf/trace/beauty/include/linux/socket.h @@ -189,10 +189,11 @@ static inline size_t msg_data_left(const struct msghdr *msg) /* "Socket"-level control message types: */ -#define SCM_RIGHTS 0x01 /* rw: access rights (array of int) */ -#define SCM_CREDENTIALS 0x02 /* rw: struct ucred */ -#define SCM_SECURITY 0x03 /* rw: security label */ -#define SCM_PIDFD 0x04 /* ro: pidfd (int) */ +#define SCM_RIGHTS 0x01 /* rw: access rights (array of int) */ +#define SCM_CREDENTIALS 0x02 /* rw: struct ucred */ +#define SCM_SECURITY 0x03 /* rw: security label */ +#define SCM_PIDFD 0x04 /* ro: pidfd (int) */ +#define SCM_PIDFD_THREAD 0x05 /* ro: thread pidfd (int) */ struct ucred { __u32 pid; @@ -461,6 +462,8 @@ extern struct file *__sys_socket_file(int family, int type, int protocol); extern int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen); extern int __sys_bind_socket(struct socket *sock, struct sockaddr_storage *address, int addrlen); +int connect_socket(struct socket *sock, struct sockaddr_storage *addr, + int addrlen, int flags); extern int __sys_connect_file(struct file *file, struct sockaddr_storage *addr, int addrlen, int file_flags); extern int __sys_connect(int fd, struct sockaddr __user *uservaddr, diff --git a/tools/perf/trace/beauty/include/uapi/linux/fcntl.h b/tools/perf/trace/beauty/include/uapi/linux/fcntl.h index aadfbf6e0cb3..e43e3de3e9ee 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/fcntl.h +++ b/tools/perf/trace/beauty/include/uapi/linux/fcntl.h @@ -124,6 +124,7 @@ struct delegation { #define FD_PIDFS_ROOT -10002 /* Root of the pidfs filesystem */ #define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */ +#define FD_FAILFS_ROOT -10004 /* Root of the failfs filesystem */ #define FD_INVALID -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */ /* Generic flags for the *at(2) family of syscalls. */ diff --git a/tools/perf/trace/beauty/include/uapi/linux/fs.h b/tools/perf/trace/beauty/include/uapi/linux/fs.h index ee2203d60643..a46c33692aa2 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/fs.h +++ b/tools/perf/trace/beauty/include/uapi/linux/fs.h @@ -471,6 +471,7 @@ typedef int __bitwise __kernel_rwf_t; #define PAGE_IS_HUGE (1 << 6) #define PAGE_IS_SOFT_DIRTY (1 << 7) #define PAGE_IS_GUARD (1 << 8) +#define PAGE_IS_ACCESSED (1 << 9) /* * struct page_region - Page region with flags diff --git a/tools/perf/trace/beauty/include/uapi/linux/mount.h b/tools/perf/trace/beauty/include/uapi/linux/mount.h index 2204708dbf7a..ac865edac517 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/mount.h +++ b/tools/perf/trace/beauty/include/uapi/linux/mount.h @@ -63,6 +63,7 @@ */ #define OPEN_TREE_CLONE (1 << 0) /* Clone the target tree and attach the clone */ #define OPEN_TREE_NAMESPACE (1 << 1) /* Clone the target tree into a new mount namespace */ +#define OPEN_TREE_DROP_MNTNS_MOUNTS (1 << 2) /* Drop mntns mounts from the clone */ #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ /* diff --git a/tools/perf/trace/beauty/include/uapi/sound/asound.h b/tools/perf/trace/beauty/include/uapi/sound/asound.h index d3ce75ba938a..c11da9656e38 100644 --- a/tools/perf/trace/beauty/include/uapi/sound/asound.h +++ b/tools/perf/trace/beauty/include/uapi/sound/asound.h @@ -169,72 +169,72 @@ enum { SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, }; -typedef int __bitwise snd_pcm_access_t; -#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */ -#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */ -#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */ -#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */ -#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */ +typedef int snd_pcm_access_t; +#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED 0 /* interleaved mmap */ +#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED 1 /* noninterleaved mmap */ +#define SNDRV_PCM_ACCESS_MMAP_COMPLEX 2 /* complex mmap */ +#define SNDRV_PCM_ACCESS_RW_INTERLEAVED 3 /* readi/writei */ +#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED 4 /* readn/writen */ #define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED -typedef int __bitwise snd_pcm_format_t; -#define SNDRV_PCM_FORMAT_S8 ((__force snd_pcm_format_t) 0) -#define SNDRV_PCM_FORMAT_U8 ((__force snd_pcm_format_t) 1) -#define SNDRV_PCM_FORMAT_S16_LE ((__force snd_pcm_format_t) 2) -#define SNDRV_PCM_FORMAT_S16_BE ((__force snd_pcm_format_t) 3) -#define SNDRV_PCM_FORMAT_U16_LE ((__force snd_pcm_format_t) 4) -#define SNDRV_PCM_FORMAT_U16_BE ((__force snd_pcm_format_t) 5) -#define SNDRV_PCM_FORMAT_S24_LE ((__force snd_pcm_format_t) 6) /* low three bytes */ -#define SNDRV_PCM_FORMAT_S24_BE ((__force snd_pcm_format_t) 7) /* low three bytes */ -#define SNDRV_PCM_FORMAT_U24_LE ((__force snd_pcm_format_t) 8) /* low three bytes */ -#define SNDRV_PCM_FORMAT_U24_BE ((__force snd_pcm_format_t) 9) /* low three bytes */ +typedef int snd_pcm_format_t; +#define SNDRV_PCM_FORMAT_S8 0 +#define SNDRV_PCM_FORMAT_U8 1 +#define SNDRV_PCM_FORMAT_S16_LE 2 +#define SNDRV_PCM_FORMAT_S16_BE 3 +#define SNDRV_PCM_FORMAT_U16_LE 4 +#define SNDRV_PCM_FORMAT_U16_BE 5 +#define SNDRV_PCM_FORMAT_S24_LE 6 /* low three bytes */ +#define SNDRV_PCM_FORMAT_S24_BE 7 /* low three bytes */ +#define SNDRV_PCM_FORMAT_U24_LE 8 /* low three bytes */ +#define SNDRV_PCM_FORMAT_U24_BE 9 /* low three bytes */ /* * For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the * available bit count in most significant bit. It's for the case of so-called 'left-justified' or * `right-padding` sample which has less width than 32 bit. */ -#define SNDRV_PCM_FORMAT_S32_LE ((__force snd_pcm_format_t) 10) -#define SNDRV_PCM_FORMAT_S32_BE ((__force snd_pcm_format_t) 11) -#define SNDRV_PCM_FORMAT_U32_LE ((__force snd_pcm_format_t) 12) -#define SNDRV_PCM_FORMAT_U32_BE ((__force snd_pcm_format_t) 13) -#define SNDRV_PCM_FORMAT_FLOAT_LE ((__force snd_pcm_format_t) 14) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT_BE ((__force snd_pcm_format_t) 15) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT64_LE ((__force snd_pcm_format_t) 16) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_FLOAT64_BE ((__force snd_pcm_format_t) 17) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ -#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((__force snd_pcm_format_t) 18) /* IEC-958 subframe, Little Endian */ -#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((__force snd_pcm_format_t) 19) /* IEC-958 subframe, Big Endian */ -#define SNDRV_PCM_FORMAT_MU_LAW ((__force snd_pcm_format_t) 20) -#define SNDRV_PCM_FORMAT_A_LAW ((__force snd_pcm_format_t) 21) -#define SNDRV_PCM_FORMAT_IMA_ADPCM ((__force snd_pcm_format_t) 22) -#define SNDRV_PCM_FORMAT_MPEG ((__force snd_pcm_format_t) 23) -#define SNDRV_PCM_FORMAT_GSM ((__force snd_pcm_format_t) 24) -#define SNDRV_PCM_FORMAT_S20_LE ((__force snd_pcm_format_t) 25) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_S20_BE ((__force snd_pcm_format_t) 26) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_U20_LE ((__force snd_pcm_format_t) 27) /* in four bytes, LSB justified */ -#define SNDRV_PCM_FORMAT_U20_BE ((__force snd_pcm_format_t) 28) /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_S32_LE 10 +#define SNDRV_PCM_FORMAT_S32_BE 11 +#define SNDRV_PCM_FORMAT_U32_LE 12 +#define SNDRV_PCM_FORMAT_U32_BE 13 +#define SNDRV_PCM_FORMAT_FLOAT_LE 14 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT_BE 15 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT64_LE 16 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_FLOAT64_BE 17 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE 18 /* IEC-958 subframe, Little Endian */ +#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE 19 /* IEC-958 subframe, Big Endian */ +#define SNDRV_PCM_FORMAT_MU_LAW 20 +#define SNDRV_PCM_FORMAT_A_LAW 21 +#define SNDRV_PCM_FORMAT_IMA_ADPCM 22 +#define SNDRV_PCM_FORMAT_MPEG 23 +#define SNDRV_PCM_FORMAT_GSM 24 +#define SNDRV_PCM_FORMAT_S20_LE 25 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_S20_BE 26 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_U20_LE 27 /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_U20_BE 28 /* in four bytes, LSB justified */ /* gap in the numbering for a future standard linear format */ -#define SNDRV_PCM_FORMAT_SPECIAL ((__force snd_pcm_format_t) 31) -#define SNDRV_PCM_FORMAT_S24_3LE ((__force snd_pcm_format_t) 32) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S24_3BE ((__force snd_pcm_format_t) 33) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U24_3LE ((__force snd_pcm_format_t) 34) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U24_3BE ((__force snd_pcm_format_t) 35) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S20_3LE ((__force snd_pcm_format_t) 36) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S20_3BE ((__force snd_pcm_format_t) 37) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U20_3LE ((__force snd_pcm_format_t) 38) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U20_3BE ((__force snd_pcm_format_t) 39) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S18_3LE ((__force snd_pcm_format_t) 40) /* in three bytes */ -#define SNDRV_PCM_FORMAT_S18_3BE ((__force snd_pcm_format_t) 41) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U18_3LE ((__force snd_pcm_format_t) 42) /* in three bytes */ -#define SNDRV_PCM_FORMAT_U18_3BE ((__force snd_pcm_format_t) 43) /* in three bytes */ -#define SNDRV_PCM_FORMAT_G723_24 ((__force snd_pcm_format_t) 44) /* 8 samples in 3 bytes */ -#define SNDRV_PCM_FORMAT_G723_24_1B ((__force snd_pcm_format_t) 45) /* 1 sample in 1 byte */ -#define SNDRV_PCM_FORMAT_G723_40 ((__force snd_pcm_format_t) 46) /* 8 Samples in 5 bytes */ -#define SNDRV_PCM_FORMAT_G723_40_1B ((__force snd_pcm_format_t) 47) /* 1 sample in 1 byte */ -#define SNDRV_PCM_FORMAT_DSD_U8 ((__force snd_pcm_format_t) 48) /* DSD, 1-byte samples DSD (x8) */ -#define SNDRV_PCM_FORMAT_DSD_U16_LE ((__force snd_pcm_format_t) 49) /* DSD, 2-byte samples DSD (x16), little endian */ -#define SNDRV_PCM_FORMAT_DSD_U32_LE ((__force snd_pcm_format_t) 50) /* DSD, 4-byte samples DSD (x32), little endian */ -#define SNDRV_PCM_FORMAT_DSD_U16_BE ((__force snd_pcm_format_t) 51) /* DSD, 2-byte samples DSD (x16), big endian */ -#define SNDRV_PCM_FORMAT_DSD_U32_BE ((__force snd_pcm_format_t) 52) /* DSD, 4-byte samples DSD (x32), big endian */ +#define SNDRV_PCM_FORMAT_SPECIAL 31 +#define SNDRV_PCM_FORMAT_S24_3LE 32 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S24_3BE 33 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U24_3LE 34 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U24_3BE 35 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S20_3LE 36 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S20_3BE 37 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U20_3LE 38 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U20_3BE 39 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S18_3LE 40 /* in three bytes */ +#define SNDRV_PCM_FORMAT_S18_3BE 41 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U18_3LE 42 /* in three bytes */ +#define SNDRV_PCM_FORMAT_U18_3BE 43 /* in three bytes */ +#define SNDRV_PCM_FORMAT_G723_24 44 /* 8 samples in 3 bytes */ +#define SNDRV_PCM_FORMAT_G723_24_1B 45 /* 1 sample in 1 byte */ +#define SNDRV_PCM_FORMAT_G723_40 46 /* 8 Samples in 5 bytes */ +#define SNDRV_PCM_FORMAT_G723_40_1B 47 /* 1 sample in 1 byte */ +#define SNDRV_PCM_FORMAT_DSD_U8 48 /* DSD, 1-byte samples DSD (x8) */ +#define SNDRV_PCM_FORMAT_DSD_U16_LE 49 /* DSD, 2-byte samples DSD (x16), little endian */ +#define SNDRV_PCM_FORMAT_DSD_U32_LE 50 /* DSD, 4-byte samples DSD (x32), little endian */ +#define SNDRV_PCM_FORMAT_DSD_U16_BE 51 /* DSD, 2-byte samples DSD (x16), big endian */ +#define SNDRV_PCM_FORMAT_DSD_U32_BE 52 /* DSD, 4-byte samples DSD (x32), big endian */ #define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE #define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 @@ -265,11 +265,11 @@ typedef int __bitwise snd_pcm_format_t; #define SNDRV_PCM_FORMAT_U20 SNDRV_PCM_FORMAT_U20_BE #endif -typedef int __bitwise snd_pcm_subformat_t; -#define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) -#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) -#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) -#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) +typedef int snd_pcm_subformat_t; +#define SNDRV_PCM_SUBFORMAT_STD 0 +#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX 1 +#define SNDRV_PCM_SUBFORMAT_MSBITS_20 2 +#define SNDRV_PCM_SUBFORMAT_MSBITS_24 3 #define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ @@ -303,16 +303,16 @@ typedef int __bitwise snd_pcm_subformat_t; #define __SND_STRUCT_TIME64 #endif -typedef int __bitwise snd_pcm_state_t; -#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */ -#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */ -#define SNDRV_PCM_STATE_PREPARED ((__force snd_pcm_state_t) 2) /* stream is ready to start */ -#define SNDRV_PCM_STATE_RUNNING ((__force snd_pcm_state_t) 3) /* stream is running */ -#define SNDRV_PCM_STATE_XRUN ((__force snd_pcm_state_t) 4) /* stream reached an xrun */ -#define SNDRV_PCM_STATE_DRAINING ((__force snd_pcm_state_t) 5) /* stream is draining */ -#define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ -#define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ -#define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ +typedef int snd_pcm_state_t; +#define SNDRV_PCM_STATE_OPEN 0 /* stream is open */ +#define SNDRV_PCM_STATE_SETUP 1 /* stream has a setup */ +#define SNDRV_PCM_STATE_PREPARED 2 /* stream is ready to start */ +#define SNDRV_PCM_STATE_RUNNING 3 /* stream is running */ +#define SNDRV_PCM_STATE_XRUN 4 /* stream reached an xrun */ +#define SNDRV_PCM_STATE_DRAINING 5 /* stream is draining */ +#define SNDRV_PCM_STATE_PAUSED 6 /* stream is paused */ +#define SNDRV_PCM_STATE_SUSPENDED 7 /* hardware is suspended */ +#define SNDRV_PCM_STATE_DISCONNECTED 8 /* hardware is disconnected */ #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED enum { @@ -1058,7 +1058,7 @@ struct snd_timer_tread { * * ****************************************************************************/ -#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) +#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) struct snd_ctl_card_info { int card; /* card number */ @@ -1072,24 +1072,43 @@ struct snd_ctl_card_info { unsigned char components[128]; /* card components / fine identification, delimited with one space (AC97 etc..) */ }; -typedef int __bitwise snd_ctl_elem_type_t; -#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */ -#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */ -#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */ -#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */ -#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */ -#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */ -#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */ +/* + * Card components can exceed the fixed 128 bytes in snd_ctl_card_info. + * Use SNDRV_CTL_IOCTL_CARD_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS + * to retrieve the full string. + */ + +/* Type values for struct snd_ctl_card_bytes::type */ +enum { + SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */ +}; + +struct snd_ctl_card_bytes { + __u32 type; /* SND_CTL_CARD_BTYPE_* */ + __u32 data_allocated; /* size of @data buffer in bytes */ + __u32 data_len; /* in/out: actual data length in bytes */ + __u32 reserved; /* explicit pad */ + __u64 data; /* user buffer (pointer stored as __u64) */ +}; + +typedef int snd_ctl_elem_type_t; +#define SNDRV_CTL_ELEM_TYPE_NONE 0 /* invalid */ +#define SNDRV_CTL_ELEM_TYPE_BOOLEAN 1 /* boolean type */ +#define SNDRV_CTL_ELEM_TYPE_INTEGER 2 /* integer type */ +#define SNDRV_CTL_ELEM_TYPE_ENUMERATED 3 /* enumerated type */ +#define SNDRV_CTL_ELEM_TYPE_BYTES 4 /* byte array */ +#define SNDRV_CTL_ELEM_TYPE_IEC958 5 /* IEC958 (S/PDIF) setup */ +#define SNDRV_CTL_ELEM_TYPE_INTEGER64 6 /* 64-bit integer type */ #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64 -typedef int __bitwise snd_ctl_elem_iface_t; -#define SNDRV_CTL_ELEM_IFACE_CARD ((__force snd_ctl_elem_iface_t) 0) /* global control */ -#define SNDRV_CTL_ELEM_IFACE_HWDEP ((__force snd_ctl_elem_iface_t) 1) /* hardware dependent device */ -#define SNDRV_CTL_ELEM_IFACE_MIXER ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */ -#define SNDRV_CTL_ELEM_IFACE_PCM ((__force snd_ctl_elem_iface_t) 3) /* PCM device */ -#define SNDRV_CTL_ELEM_IFACE_RAWMIDI ((__force snd_ctl_elem_iface_t) 4) /* RawMidi device */ -#define SNDRV_CTL_ELEM_IFACE_TIMER ((__force snd_ctl_elem_iface_t) 5) /* timer device */ -#define SNDRV_CTL_ELEM_IFACE_SEQUENCER ((__force snd_ctl_elem_iface_t) 6) /* sequencer client */ +typedef int snd_ctl_elem_iface_t; +#define SNDRV_CTL_ELEM_IFACE_CARD 0 /* global control */ +#define SNDRV_CTL_ELEM_IFACE_HWDEP 1 /* hardware dependent device */ +#define SNDRV_CTL_ELEM_IFACE_MIXER 2 /* virtual mixer device */ +#define SNDRV_CTL_ELEM_IFACE_PCM 3 /* PCM device */ +#define SNDRV_CTL_ELEM_IFACE_RAWMIDI 4 /* RawMidi device */ +#define SNDRV_CTL_ELEM_IFACE_TIMER 5 /* timer device */ +#define SNDRV_CTL_ELEM_IFACE_SEQUENCER 6 /* sequencer client */ #define SNDRV_CTL_ELEM_IFACE_LAST SNDRV_CTL_ELEM_IFACE_SEQUENCER #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) @@ -1198,6 +1217,7 @@ struct snd_ctl_tlv { #define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) #define SNDRV_CTL_IOCTL_CARD_INFO _IOR('U', 0x01, struct snd_ctl_card_info) +#define SNDRV_CTL_IOCTL_CARD_BYTES _IOWR('U', 0x02, struct snd_ctl_card_bytes) #define SNDRV_CTL_IOCTL_ELEM_LIST _IOWR('U', 0x10, struct snd_ctl_elem_list) #define SNDRV_CTL_IOCTL_ELEM_INFO _IOWR('U', 0x11, struct snd_ctl_elem_info) #define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value) diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c index 6f64f6b42442..960dd48701bb 100644 --- a/tools/perf/util/addr2line.c +++ b/tools/perf/util/addr2line.c @@ -298,6 +298,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, struct io io = { .eof = false }; enum cmd_a2l_style cmd_a2l_style; const char *current_dso_name; + bool a2l_synced = false; mutex_lock(dso__lock(dso)); current_dso_name = dso__symsrc_filename(dso) ?: dso_name; @@ -316,11 +317,14 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (a2l == NULL) { if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name); + ret = -1; goto out_unlock; } cmd_a2l_style = cmd_addr2line_configure(a2l, dso_name); - if (cmd_a2l_style == BROKEN) + if (cmd_a2l_style == BROKEN) { + ret = -1; goto out_unlock; + } /* * Take ownership of the a2l subprocess so we can safely perform @@ -344,6 +348,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (written != len) { if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not send request\n", __func__, dso_name); + ret = -1; goto out; } io__init(&io, a2l->out, buf, sizeof(buf)); @@ -353,6 +358,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, case -1: if (!symbol_conf.addr2line_disable_warn) pr_warning("%s %s: could not read first record\n", __func__, dso_name); + ret = -1; goto out; case 0: /* @@ -373,6 +379,7 @@ int cmd__addr2line(const char *dso_name, u64 addr, break; case 0: /* The sentinel as expected. */ + a2l_synced = true; break; default: if (!symbol_conf.addr2line_disable_warn) @@ -388,17 +395,23 @@ int cmd__addr2line(const char *dso_name, u64 addr, if (file) { *file = strdup(record_filename); - ret = 1; + if (!*file) { + ret = -ENOMEM; + goto out; + } } if (line_nr) *line_nr = record_line_nr; + ret = 1; if (unwind_inlines) { if (node && inline_list__append_record(dso, node, sym, record_function, record_filename, record_line_nr)) { - ret = 0; + if (file && *file) + zfree(file); + ret = -ENOMEM; goto out; } } @@ -421,12 +434,17 @@ int cmd__addr2line(const char *dso_name, u64 addr, record_function, record_filename, record_line_nr)) { - ret = 0; + if (file && *file) + zfree(file); + inline_node__clear_frames(node); + ret = -ENOMEM; goto out; } ret = 1; /* found at least one inline frame */ } } + if (record_status == 0) + a2l_synced = true; out: mutex_lock(dso__lock(dso)); @@ -434,7 +452,7 @@ out: free(record_filename); current_dso_name = dso__symsrc_filename(dso) ?: dso__long_name(dso); - if (!io.eof && dso__a2l(dso) == NULL && current_dso_name && + if (a2l_synced && !io.eof && dso__a2l(dso) == NULL && current_dso_name && !strcmp(current_dso_name, dso_name)) dso__set_a2l(dso, a2l); else diff --git a/tools/perf/util/annotate-arch/annotate-arm64.c b/tools/perf/util/annotate-arch/annotate-arm64.c index 6549535961a9..81369ae47290 100644 --- a/tools/perf/util/annotate-arch/annotate-arm64.c +++ b/tools/perf/util/annotate-arch/annotate-arm64.c @@ -1,14 +1,22 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/compiler.h> #include <errno.h> +#include <inttypes.h> #include <stdlib.h> #include <string.h> +#include <strings.h> #include <linux/ctype.h> #include <linux/string.h> #include <linux/zalloc.h> #include <regex.h> #include "../annotate.h" #include "../disasm.h" +#include "../annotate-data.h" +#include "../debug.h" +#include "../dso.h" +#include "../map.h" +#include "../symbol.h" +#include "../strbuf.h" struct arch_arm64 { struct arch arch; @@ -448,6 +456,769 @@ check_modifiers: } } +#ifdef HAVE_LIBDW_SUPPORT +/* Check if the instruction is read-only (does not modify register state). */ +static bool is_readonly_insn(const char *name) +{ + return !strncmp(name, "cb", 2) || !strncmp(name, "tb", 2) || + !strcmp(name, "tst") || !strcmp(name, "teq") || + !strcmp(name, "cmp") || !strcmp(name, "cmn") || + !strcmp(name, "ccmp") || !strcmp(name, "ccmn"); +} + +static void update_call_insn_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl) +{ + struct symbol *func = dl->ops.target.sym; + const char *call_name; + u64 call_addr; + struct type_state_reg *tsr; + Dwarf_Die type_die; + u32 insn_offset = dl->al.offset; + + call_name = func ? func->name : dl->ops.target.name; + pr_debug_dtp("call [%x] %s\n", insn_offset, call_name ?: "<unknown>"); + + /* Invalidate caller-saved registers after call */ + call_addr = map__rip_2objdump(dloc->ms->map, + dloc->ms->sym->start + dl->al.offset); + for (unsigned int i = 0; i < ARRAY_SIZE(state->regs); i++) { + struct type_state_reg *reg = &state->regs[i]; + + if (!reg->caller_saved) + continue; + /* Keep register valid within DWARF location lifetime */ + if (reg->lifetime_active && call_addr < reg->lifetime_end) + continue; + invalidate_reg_state(reg); + } + + /* Update register with the return type (if any) */ + if (call_name && die_find_func_rettype(cu_die, call_name, &type_die)) { + tsr = &state->regs[state->ret_reg]; + invalidate_reg_state(tsr); + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->ok = true; + + pr_debug_dtp("call [%x] return -> reg%d", insn_offset, state->ret_reg); + pr_debug_type_name(&type_die, tsr->kind); + } +} + +/* Invalidate registers explicitly written by an unhandled instruction. */ +static void invalidate_unhandled_insn_regs(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + u32 insn_offset = dl->al.offset; + + /* Invalidate destination registers */ + if (has_reg_type(state, dst->reg1) && !dst->mem_ref) { + pr_debug_dtp("%s [%x] invalidate reg%d", + dl->ins.name, insn_offset, dst->reg1); + invalidate_reg_state(&state->regs[dst->reg1]); + + if (dst->multi_regs && has_reg_type(state, dst->reg2)) { + pr_debug_dtp(" and reg%d", dst->reg2); + invalidate_reg_state(&state->regs[dst->reg2]); + } + pr_debug_dtp("\n"); + } + + /* + * Exclusive store instructions (stxr, stxp, stlxr, stlxp, etc.) + * write their execution status (0 or 1) into the status register + * (parsed into src->reg1). Invalidate this status register to prevent + * stale type propagation. + */ + if (!strncmp(dl->ins.name, "stx", 3) || /* stxr, stxp, stxrb, stxrh */ + !strncmp(dl->ins.name, "stlx", 4)) { /* stlxr, stlxp, stlxrb, stlxrh */ + if (has_reg_type(state, src->reg1)) { + pr_debug_dtp("%s [%x] invalidate reg%d\n", + dl->ins.name, insn_offset, src->reg1); + invalidate_reg_state(&state->regs[src->reg1]); + } + } + + /* TODO: Extend invalidation support to more special instructions. */ +} + +static int arm64__reg_size(const char *reg) +{ + if (!reg || !*reg || !arm64__is_reg(reg)) + return -1; + + if (reg[0] == 'w') + return 4; + + if (reg[0] == 'x' || !strncmp(reg, "sp", 2)) + return 8; + + return -1; +} + +/* Apply addressing mode (pre-index, post-index) to register state */ +static void adjust_reg_index_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *op_loc) +{ + struct type_state_reg *tsr; + int reg = op_loc->reg1; + int offset; + + if (op_loc->addr_mode != PERF_AAM_PRE_INDEX && + op_loc->addr_mode != PERF_AAM_POST_INDEX) + return; + + if (!has_reg_type(state, reg) || !state->regs[reg].ok) + return; + + tsr = &state->regs[reg]; + tsr->copied_from = -1; + + if (arch_get_reg_offset(dloc->arch, op_loc, reg, state, true, &offset)) { + invalidate_reg_state(tsr); + return; + } + + tsr->offset += offset; + + pr_debug_dtp("%s [%x] %s-index %#x(reg%d) -> reg%d", dl->ins.name, + (u32) dl->al.offset, op_loc->addr_mode == PERF_AAM_PRE_INDEX ? + "pre" : "post", offset, reg, reg); + pr_debug_type_name(&tsr->type, tsr->kind); +} + +/* + * Match standard load variants (ldr, ldur, ldar, ldp) that follow + * straightforward load semantics: memory source on the right + * and target registers on the left (written). Excludes exclusive loads + * (ldxr, ldxp, etc.) and acquire/exclusive combination variants. + */ +static bool is_standard_load_insn(const char *name) +{ + return !strncmp(name, "ldr", 3) || /* ldr, ldrb, ldrh, ldrsb, ldrsh, ldrsw */ + !strncmp(name, "ldur", 4) || /* ldur, ldurb, ldurh, ldursb, ldursh, ldursw */ + !strncmp(name, "ldar", 4) || /* ldar, ldarb, ldarh */ + !strncmp(name, "ldp", 3); /* ldp, ldpsw */ +} + +/* + * For load insns: propagate type from source reg state @src_states to @dreg. + * + * @mem_spacing accounts for the additional in-memory spacing between the two + * loads when handling multi-reg loads (e.g. the second target reg in 'ldp'), + * which is added to the reg offset. + */ +static int propagate_load_reg_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl, int dreg, + struct annotated_op_loc *src, + struct type_state_reg **src_states, + int mem_spacing) +{ + struct type_state_reg *tsr; + struct type_state_reg *src_tsr = src_states[0]; + Dwarf_Die type_die; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int reg_offset; + int fbreg = dloc->fbreg; + int fboff = 0; + + if (!has_reg_type(state, dreg)) + return -1; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + + if (dloc->fb_cfa) { + u64 ip = dloc->ms->sym->start + dl->al.offset; + u64 pc = map__rip_2objdump(dloc->ms->map, ip); + + if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0) + fbreg = -1; + } + +retry: + if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, ®_offset)) + goto load_non_regoff; + + reg_offset += mem_spacing; + + /* Check stack variables with offset */ + if (sreg != -1 && (sreg == fbreg || sreg == state->stack_reg)) { + struct type_state_stack *stack; + int offset = sreg == fbreg ? reg_offset - fboff : reg_offset; + + stack = find_stack_state(state, offset); + if (stack == NULL) { + return -1; + } else if (!stack->compound) { + tsr->type = stack->type; + tsr->kind = stack->kind; + tsr->offset = stack->ptr_offset; + tsr->imm_value = stack->imm_value; + tsr->ok = true; + } else if (die_get_member_type(&stack->type, + offset - stack->offset, + &type_die)) { + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + } else { + return -1; + } + + if (sreg == fbreg) { + pr_debug_dtp("%s [%x] -%#x(stack) -> reg%d", + dl->ins.name, insn_offset, -offset, dreg); + } else { + pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, offset, sreg, dreg); + } + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + if (!src_tsr || !src_tsr->ok) + return -1; + + /* Dereference the pointer if it has one */ + if (src_tsr->kind == TSR_KIND_TYPE && + die_deref_ptr_type(&src_tsr->type, + src_tsr->offset + reg_offset, &type_die)) { + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + + pr_debug_dtp("%s [%x] %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* Handle dereference of TSR_KIND_POINTER registers */ + if (src_tsr->kind == TSR_KIND_POINTER && + die_get_member_type(&src_tsr->type, + src_tsr->offset + reg_offset, &type_die)) { + tsr->type = src_tsr->type; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = src_tsr->offset + reg_offset; + tsr->imm_value = 0; + tsr->ok = true; + + pr_debug_dtp("%s [%x] addr %#x(reg%d) -> reg%d", + dl->ins.name, insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* Or check if it's a global variable */ + if (src_tsr->kind == TSR_KIND_GLOBAL_ADDR) { + u64 ip = dloc->ms->sym->start + dl->al.offset; + u64 addr = src_tsr->imm_value + reg_offset; + int offset; + u8 kind; + const char *var_name = NULL; + + /* it might be per-cpu offset */ + if (dso__kernel(map__dso(dloc->ms->map)) && + get_global_var_info(dloc, addr, &var_name, &offset) && + !strcmp(var_name, "__per_cpu_offset")) + kind = TSR_KIND_PERCPU_BASE; + else + kind = TSR_KIND_TYPE; + + if (!get_global_var_type(cu_die, dloc, ip, addr, &offset, &type_die) || + !die_get_member_type(&type_die, offset, &type_die)) + return -1; + + tsr->type = type_die; + tsr->kind = kind; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->ok = true; + + if (src->multi_regs) { + pr_debug_dtp("%s [%x] global (reg%d, reg%d) -> reg%d", + dl->ins.name, insn_offset, src->reg1, src->reg2, dreg); + } else { + pr_debug_dtp("%s [%x] global (reg%d) -> reg%d", + dl->ins.name, insn_offset, sreg, dreg); + } + pr_debug_type_name(&tsr->type, tsr->kind); + return 0; + } + + /* + * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array. + * No special handling is needed here; just fall through to retry + * and resolve the type from the second register. + */ + +load_non_regoff: + /* Try another register if any */ + if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 && + !(src->extend_type || src->shift_type)) { + sreg = src->reg2; + src_tsr = src_states[1]; + goto retry; + } + + return -1; +} + +static void update_load_insn_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg snapshots[2]; + struct type_state_reg *src_states[2] = {}; + int mem_spacing = 0; + + if (!src->mem_ref || /* exclude PC-relative loads */ + !has_reg_type(state, src->reg1)) { + /* Invalidate tracked destination registers */ + if (has_reg_type(state, dst->reg1)) + invalidate_reg_state(&state->regs[dst->reg1]); + if (dst->multi_regs && has_reg_type(state, dst->reg2)) + invalidate_reg_state(&state->regs[dst->reg2]); + goto out_adjust; + } + + /* + * Snapshot source register states before updating destination registers. + * This preserves the original source state for aliased loads (e.g., + * ldp x0, x1, [x0]), where updating the first destination may overwrite + * the source register. + */ + snapshots[0] = state->regs[src->reg1]; + src_states[0] = &snapshots[0]; + if (src->multi_regs && has_reg_type(state, src->reg2)) { + snapshots[1] = state->regs[src->reg2]; + src_states[1] = &snapshots[1]; + } + + /* Handle the first destination register */ + if (has_reg_type(state, dst->reg1)) { + if (propagate_load_reg_state(state, dloc, cu_die, dl, dst->reg1, + src, src_states, mem_spacing)) + invalidate_reg_state(&state->regs[dst->reg1]); + } + + if (!dst->multi_regs || !has_reg_type(state, dst->reg2)) + goto out_adjust; + + /* Handle the second destination register */ + if (!strcmp(dl->ins.name, "ldpsw")) + mem_spacing = 4; /* ldpsw loads two 32-bit signed words (4-byte spacing) */ + else + mem_spacing = arm64__reg_size(dl->ops.target.raw); + + if (mem_spacing < 0 || + propagate_load_reg_state(state, dloc, cu_die, dl, dst->reg2, + src, src_states, mem_spacing)) + invalidate_reg_state(&state->regs[dst->reg2]); + +out_adjust: + adjust_reg_index_state(state, dloc, dl, src); +} + +/* + * Match standard store variants (str, stur, stlr, stp) that follow + * straightforward store semantics: source registers on the left (read-only) + * and memory target on the right. Excludes exclusive stores (stxr, stxp, etc.) + * which have status/result register side effects. + */ +static bool is_standard_store_insn(const char *name) +{ + return !strncmp(name, "str", 3) || /* str, strb, strh */ + !strncmp(name, "stur", 4) || /* stur, sturb, sturh */ + !strncmp(name, "stlr", 4) || /* stlr, stlrb, stlrh */ + !strncmp(name, "stp", 3); /* stp */ +} + +/* + * For store insns: propagate type from @sreg to the memory location referenced + * by @dst, applying @mem_spacing to account for multi-regs stores (e.g. stp). + */ +static void propagate_store_reg_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, int sreg, + struct annotated_op_loc *dst, + int mem_spacing) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int dreg = dst->reg1; + int reg_offset; + int fbreg = dloc->fbreg; + int fboff = 0; + + if (arch_get_reg_offset(dloc->arch, dst, dreg, state, false, ®_offset)) + return; + + reg_offset += mem_spacing; + + if (dloc->fb_cfa) { + u64 ip = dloc->ms->sym->start + dl->al.offset; + u64 pc = map__rip_2objdump(dloc->ms->map, ip); + + if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0) + fbreg = -1; + } + + /* Check stack variables with offset */ + if (dreg != -1 && (dreg == fbreg || dreg == state->stack_reg)) { + struct type_state_stack *stack; + int offset = dreg == fbreg ? reg_offset - fboff : reg_offset; + + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + stack = find_stack_state(state, offset); + /* Preserve compound states when only a member is overwritten. */ + if (stack && !stack->compound) + delete_stack_state(state, offset); + + return; + } + + tsr = &state->regs[sreg]; + + stack = find_stack_state(state, offset); + if (stack) { + if (!stack->compound) + set_stack_state(stack, offset, tsr->kind, &tsr->type, + tsr->offset, tsr->imm_value); + /* + * If it's a compound type, it means attempting to + * write to a member value of the compound type without + * changing the compound type itself, so do nothing. + */ + } else { + findnew_stack_state(state, offset, tsr->kind, &tsr->type, + tsr->offset, tsr->imm_value); + } + + if (dreg == fbreg) { + pr_debug_dtp("%s [%x] reg%d -> -%#x(stack)", + dl->ins.name, insn_offset, sreg, -offset); + } else { + pr_debug_dtp("%s [%x] reg%d -> %#x(reg%d)", + dl->ins.name, insn_offset, sreg, offset, dreg); + } + if (tsr->offset != 0) + pr_debug_dtp(" reg%d offset %#x ->", sreg, tsr->offset); + + pr_debug_type_name(&tsr->type, tsr->kind); + } + /* + * Ignore other transfers since it'd set a value in a struct + * and won't change the type. + */ +} + +static void update_store_insn_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + int mem_spacing = 0; + + if (!has_reg_type(state, dst->reg1)) + return; + + /* Handle the first source register */ + propagate_store_reg_state(state, dloc, dl, src->reg1, dst, mem_spacing); + + /* Handle the second source register (if any) */ + if (src->multi_regs) { + mem_spacing = arm64__reg_size(dl->ops.source.raw); + + if (mem_spacing >= 0) + propagate_store_reg_state(state, dloc, dl, src->reg2, + dst, mem_spacing); + } + + adjust_reg_index_state(state, dloc, dl, dst); +} + +static void update_mov_insn_state(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int dreg = dst->reg1; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + + if (src->imm) { + invalidate_reg_state(tsr); + tsr->kind = TSR_KIND_CONST; + tsr->imm_value = (s64)src->offset; + tsr->offset = 0; + tsr->ok = true; + + pr_debug_dtp("mov [%x] imm=%#"PRIx64" -> reg%d\n", + insn_offset, tsr->imm_value, dreg); + return; + } + + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + invalidate_reg_state(tsr); + return; + } + + tsr->type = state->regs[sreg].type; + tsr->kind = state->regs[sreg].kind; + tsr->imm_value = state->regs[sreg].imm_value; + tsr->offset = state->regs[sreg].offset; + tsr->lifetime_active = state->regs[sreg].lifetime_active; + tsr->lifetime_end = state->regs[sreg].lifetime_end; + tsr->ok = state->regs[sreg].ok; + + if (tsr->kind == TSR_KIND_TYPE || tsr->kind == TSR_KIND_POINTER) + tsr->copied_from = sreg; + + pr_debug_dtp("mov [%x] reg%d -> reg%d", insn_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); +} + +static void update_add_insn_state(struct type_state *state, + struct data_loc_info *dloc, + struct disasm_line *dl, + struct annotated_op_loc *src, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + struct type_state_reg src_tsr; + u32 insn_offset = dl->al.offset; + int sreg = src->reg1; + int dreg = dst->reg1; + int reg_offset; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + +retry: + if (!has_reg_type(state, sreg) || !state->regs[sreg].ok) { + invalidate_reg_state(tsr); + return; + } + + src_tsr = state->regs[sreg]; + + if (arch_get_reg_offset(dloc->arch, src, sreg, state, false, ®_offset)) + goto add_non_regoff; + + if (src_tsr.kind == TSR_KIND_CONST || src_tsr.kind == TSR_KIND_GLOBAL_ADDR) { + tsr->kind = src_tsr.kind; + /* For 'adrp + add' pair: resolve the full global variable address. */ + tsr->imm_value = src_tsr.imm_value + reg_offset; + tsr->offset = 0; + tsr->ok = src_tsr.ok; + + pr_debug_dtp("add [%x] %s %#x(reg%d) -> reg%d\n", + insn_offset, src_tsr.kind == TSR_KIND_CONST ? + "imm" : "global", reg_offset, sreg, dreg); + return; + } + + if (src_tsr.kind == TSR_KIND_POINTER || + (src_tsr.kind == TSR_KIND_TYPE && + dwarf_tag(&src_tsr.type) == DW_TAG_pointer_type)) { + tsr->type = src_tsr.type; + tsr->kind = src_tsr.kind; + tsr->imm_value = src_tsr.imm_value; + tsr->offset = src_tsr.offset + reg_offset; + tsr->ok = src_tsr.ok; + + pr_debug_dtp("add [%x] address of %#x(reg%d) -> reg%d", + insn_offset, reg_offset, sreg, dreg); + pr_debug_type_name(&tsr->type, tsr->kind); + return; + } + + /* + * If src_tsr->kind is TSR_KIND_PERCPU_BASE, treat it as an array. + * No special handling is needed here; just fall through to retry + * and resolve the type from the second register. + */ + +add_non_regoff: + /* Try another register if any */ + if (src->multi_regs && src->reg1 != src->reg2 && sreg != src->reg2 && + !(src->extend_type || src->shift_type)) { + sreg = src->reg2; + goto retry; + } + invalidate_reg_state(tsr); +} + +static void update_adrp_insn_state(struct type_state *state, + struct disasm_line *dl, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + u32 insn_offset = dl->al.offset; + int dreg = dst->reg1; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + + if (!dl->ops.source.addr) { + invalidate_reg_state(tsr); + return; + } + + tsr->kind = TSR_KIND_GLOBAL_ADDR; + /* + * Stores a partial page-relative address. The full absolute address + * of the global variable will be resolved when a subsequent 'add' or + * 'ldr' instruction consumes this register. + */ + tsr->imm_value = dl->ops.source.addr; + tsr->offset = 0; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + tsr->ok = true; + + pr_debug_dtp("adrp [%x] global addr=%#"PRIx64" -> reg%d\n", + insn_offset, tsr->imm_value, dreg); +} + +static void update_mrs_insn_state(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl, + struct annotated_op_loc *dst) +{ + struct type_state_reg *tsr; + Dwarf_Die type_die; + u32 insn_offset = dl->al.offset; + int dreg = dst->reg1; + Dwarf_Die func_die; + Dwarf_Attribute attr; + u64 ip, pc; + struct strbuf sb; + char *type_name; + + if (!has_reg_type(state, dreg)) + return; + + tsr = &state->regs[dreg]; + tsr->copied_from = -1; + + /* Handle case difference: LLVM (SP_EL0) vs objdump (sp_el0) */ + if (!dso__kernel(map__dso(dloc->ms->map)) || + strcasecmp(dl->ops.source.raw, "sp_el0")) { + invalidate_reg_state(tsr); + return; + } + + ip = dloc->ms->sym->start + dl->al.offset; + pc = map__rip_2objdump(dloc->ms->map, ip); + + /* + * Find the inline function 'get_current()' Dwarf_Die and obtain its + * return value data type, which should be 'struct task_struct*'. + */ + if (!die_find_inlinefunc(cu_die, pc, &func_die) || + !die_compare_name(&func_die, "get_current") || + !dwarf_attr_integrate(&func_die, DW_AT_type, &attr) || + !dwarf_formref_die(&attr, &type_die)) { + invalidate_reg_state(tsr); + return; + } + + strbuf_init(&sb, 32); + die_get_typename_from_type(&type_die, &sb); + type_name = strbuf_detach(&sb, NULL); + + if (!type_name || strcmp(type_name, "struct task_struct*")) { + invalidate_reg_state(tsr); + goto out_free; + } + + tsr->type = type_die; + tsr->kind = TSR_KIND_TYPE; + tsr->offset = 0; + tsr->imm_value = 0; + tsr->lifetime_active = false; + tsr->lifetime_end = 0; + tsr->ok = true; + + pr_debug_dtp("mrs [%x] sp_el0 -> reg%d", insn_offset, dreg); + pr_debug_type_name(&type_die, tsr->kind); + +out_free: + free(type_name); +} + +static void update_insn_state_arm64(struct type_state *state, + struct data_loc_info *dloc, Dwarf_Die *cu_die, + struct disasm_line *dl) +{ + struct annotated_insn_loc loc; + struct annotated_op_loc *src = &loc.ops[INSN_OP_SOURCE]; + struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET]; + + if (is_readonly_insn(dl->ins.name)) + return; + + if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0) + return; + + if (ins__is_call(&dl->ins)) + update_call_insn_state(state, dloc, cu_die, dl); + else if (!strcmp(dl->ins.name, "mrs")) + update_mrs_insn_state(state, dloc, cu_die, dl, dst); + else if (!strcmp(dl->ins.name, "adrp")) + update_adrp_insn_state(state, dl, dst); + else if (!strcmp(dl->ins.name, "add")) + update_add_insn_state(state, dloc, dl, src, dst); + /* Register to register or imm value to register transfers */ + else if (!strcmp(dl->ins.name, "mov")) + update_mov_insn_state(state, dl, src, dst); + /* Memory to register transfers */ + else if (is_standard_load_insn(dl->ins.name)) + update_load_insn_state(state, dloc, cu_die, dl, src, dst); + /* Register to memory transfers */ + else if (is_standard_store_insn(dl->ins.name)) + update_store_insn_state(state, dloc, dl, src, dst); + else + invalidate_unhandled_insn_regs(state, dl, src, dst); +} +#endif + const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, const char *cpuid __maybe_unused) { @@ -467,6 +1238,9 @@ const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, arch->objdump.imm_char = '#'; arch->associate_instruction_ops = arm64__associate_instruction_ops; arch->extract_op_location = extract_op_location_arm64; +#ifdef HAVE_LIBDW_SUPPORT + arch->update_insn_state = update_insn_state_arm64; +#endif /* bl, blr */ err = regcomp(&arm->call_insn, "^blr?$", REG_EXTENDED); diff --git a/tools/perf/util/annotate-arch/annotate-x86.c b/tools/perf/util/annotate-arch/annotate-x86.c index 1acf31a2c759..3a6ea7e978e0 100644 --- a/tools/perf/util/annotate-arch/annotate-x86.c +++ b/tools/perf/util/annotate-arch/annotate-x86.c @@ -207,15 +207,6 @@ static int x86__cpuid_parse(struct arch *arch, const char *cpuid) } #ifdef HAVE_LIBDW_SUPPORT -static void invalidate_reg_state(struct type_state_reg *reg) -{ - reg->kind = TSR_KIND_INVALID; - reg->ok = false; - reg->lifetime_active = false; - reg->lifetime_end = 0; - reg->copied_from = -1; -} - static void update_insn_state_x86(struct type_state *state, struct data_loc_info *dloc, Dwarf_Die *cu_die, struct disasm_line *dl) @@ -545,11 +536,11 @@ static void update_insn_state_x86(struct type_state *state, if (src->imm) { tsr->kind = TSR_KIND_CONST; - tsr->imm_value = src->offset; + tsr->imm_value = (s64)src->offset; tsr->offset = 0; tsr->ok = true; - pr_debug_dtp("mov [%x] imm=%#x -> reg%d\n", + pr_debug_dtp("mov [%x] imm=%#"PRIx64" -> reg%d\n", insn_offset, tsr->imm_value, dst->reg1); return; } @@ -600,6 +591,7 @@ retry: tsr->type = stack->type; tsr->kind = stack->kind; tsr->offset = stack->ptr_offset; + tsr->imm_value = stack->imm_value; tsr->ok = true; } else if (die_get_member_type(&stack->type, offset - stack->offset, @@ -607,6 +599,7 @@ retry: tsr->type = type_die; tsr->kind = TSR_KIND_TYPE; tsr->offset = 0; + tsr->imm_value = 0; tsr->ok = true; } else { invalidate_reg_state(tsr); @@ -760,15 +753,24 @@ retry: } /* Case 3. register to memory transfers */ if (!src->mem_ref && dst->mem_ref) { - if (!has_reg_type(state, src->reg1) || - !state->regs[src->reg1].ok) - return; - /* Check stack variables with offset */ if (dst->reg1 == fbreg || dst->reg1 == state->stack_reg) { struct type_state_stack *stack; int offset = dst->offset - fboff; + if (!has_reg_type(state, src->reg1) || + !state->regs[src->reg1].ok) { + stack = find_stack_state(state, offset); + /* + * Preserve compound states when only a member + * is overwritten. + */ + if (stack && !stack->compound) + delete_stack_state(state, offset); + + return; + } + tsr = &state->regs[src->reg1]; stack = find_stack_state(state, offset); @@ -782,10 +784,12 @@ retry: */ if (!stack->compound) set_stack_state(stack, offset, tsr->kind, - &tsr->type, tsr->offset); + &tsr->type, tsr->offset, + tsr->imm_value); } else { findnew_stack_state(state, offset, tsr->kind, - &tsr->type, tsr->offset); + &tsr->type, tsr->offset, + tsr->imm_value); } if (dst->reg1 == fbreg) { diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c index 19a6ecd67f28..b3de1ca688a2 100644 --- a/tools/perf/util/annotate-data.c +++ b/tools/perf/util/annotate-data.c @@ -29,6 +29,7 @@ /* register number of the stack pointer */ #define X86_REG_SP 7 +#define ARM64_REG_SP 31 static void delete_var_types(struct die_var_type *var_types); @@ -70,6 +71,9 @@ void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind) case TSR_KIND_CANARY: pr_info(" stack canary\n"); return; + case TSR_KIND_GLOBAL_ADDR: + pr_info(" global address\n"); + return; case TSR_KIND_TYPE: default: break; @@ -179,6 +183,17 @@ static void init_type_state(struct type_state *state, const struct arch *arch) state->regs[11].caller_saved = true; state->ret_reg = 0; state->stack_reg = X86_REG_SP; + } else if (arch__is_arm64(arch)) { + /* + * Mark x0..x18 as caller-saved. x30 (LR) is intentionally + * omitted from the caller_saved set: it is clobbered by + * calls, but holds only the return address and is not used + * to carry data type information. + */ + for (int i = 0; i < 19; i++) + state->regs[i].caller_saved = true; + state->ret_reg = 0; + state->stack_reg = ARM64_REG_SP; } } @@ -692,7 +707,7 @@ struct type_state_stack *find_stack_state(struct type_state *state, } void set_stack_state(struct type_state_stack *stack, int offset, u8 kind, - Dwarf_Die *type_die, int ptr_offset) + Dwarf_Die *type_die, int ptr_offset, u64 imm_value) { int tag; Dwarf_Word size; @@ -709,8 +724,10 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind, stack->offset = offset; stack->ptr_offset = ptr_offset; stack->kind = kind; + stack->imm_value = imm_value; - if (kind == TSR_KIND_POINTER) { + if (kind == TSR_KIND_POINTER || kind == TSR_KIND_CONST || + kind == TSR_KIND_GLOBAL_ADDR) { stack->compound = false; return; } @@ -731,23 +748,42 @@ void set_stack_state(struct type_state_stack *stack, int offset, u8 kind, struct type_state_stack *findnew_stack_state(struct type_state *state, int offset, u8 kind, Dwarf_Die *type_die, - int ptr_offset) + int ptr_offset, u64 imm_value) { struct type_state_stack *stack = find_stack_state(state, offset); if (stack) { - set_stack_state(stack, offset, kind, type_die, ptr_offset); + set_stack_state(stack, offset, kind, type_die, ptr_offset, imm_value); return stack; } stack = malloc(sizeof(*stack)); if (stack) { - set_stack_state(stack, offset, kind, type_die, ptr_offset); + set_stack_state(stack, offset, kind, type_die, ptr_offset, imm_value); list_add(&stack->list, &state->stack_vars); } return stack; } +void delete_stack_state(struct type_state *state, int offset) +{ + struct type_state_stack *stack = find_stack_state(state, offset); + + if (stack) { + list_del(&stack->list); + free(stack); + } +} + +void invalidate_reg_state(struct type_state_reg *reg) +{ + reg->kind = TSR_KIND_INVALID; + reg->ok = false; + reg->lifetime_active = false; + reg->lifetime_end = 0; + reg->copied_from = -1; +} + /* Maintain a cache for quick global variable lookup */ struct global_var_entry { struct rb_node node; @@ -1037,7 +1073,7 @@ static void update_var_state(struct type_state *state, struct data_loc_info *dlo continue; findnew_stack_state(state, offset, TSR_KIND_TYPE, - &mem_die, /*ptr_offset=*/0); + &mem_die, /*ptr_offset=*/0, /*imm_value=*/0); if (var->reg == state->stack_reg) { pr_debug_dtp("var [%"PRIx64"] %#x(reg%d)", @@ -1220,6 +1256,78 @@ static void setup_stack_canary(struct data_loc_info *dloc) } } +int arch_get_reg_offset(const struct arch *arch, struct annotated_op_loc *op_loc, + int breg, struct type_state *state, bool apply_index, + int *offset) +{ + if (arch__is_arm64(arch)) { + int reg2; + + /* Post-indexed addressing: offset is 0 when not applying index */ + if (op_loc->addr_mode == PERF_AAM_POST_INDEX && !apply_index) { + *offset = 0; + return 0; + } + + /* Single-register addressing: return the stored offset directly */ + if (!op_loc->multi_regs) { + *offset = op_loc->offset; + return 0; + } + + /* + * SIB (Scale-Index-Base) style indexed addressing, e.g.: + * Array element load: ldr dst, [base, index, lsl #3] + * Array element load: ldr dst, [base, index, uxtw #3] + * Array address calc: add dst, base, index lsl #3 + * + * The index reg only computes element offset without modifying + * the underlying data type; the dst type is inherited from base. + * Therefore, the base reg offset can be treated as 0. + */ + if (op_loc->extend_type || op_loc->shift_type) { + /* A memory reference implies breg is a pointer. */ + if (op_loc->mem_ref) { + *offset = 0; + return 0; + } + /* + * Otherwise, require breg to be a known pointer to avoid + * treating integer arithmetic as indexed address calculation. + */ + if (has_reg_type(state, breg) && state->regs[breg].ok && + (state->regs[breg].kind == TSR_KIND_POINTER || + (state->regs[breg].kind == TSR_KIND_TYPE && + dwarf_tag(&state->regs[breg].type) == DW_TAG_pointer_type))) { + *offset = 0; + return 0; + } + return -1; + } + + /* + * For register-based addressing without shift/extension, + * derive the offset from the type of reg2. + */ + reg2 = op_loc->reg1 == breg ? op_loc->reg2 : op_loc->reg1; + if (has_reg_type(state, reg2) && state->regs[reg2].ok) { + if (state->regs[reg2].kind == TSR_KIND_CONST) + *offset = (s64)state->regs[reg2].imm_value; + else if (state->regs[reg2].kind == TSR_KIND_PERCPU_BASE) + *offset = 0; /* it does not contribute to the offset */ + else + return -1; + + return 0; + } + + return -1; + } + + *offset = op_loc->offset; + return 0; +} + /* * It's at the target address, check if it has a matching type. * It returns PERF_TMR_BAIL_OUT when it looks up per-cpu variables which @@ -1233,18 +1341,25 @@ static enum type_match_result check_matching_type(struct type_state *state, { u32 insn_offset = dl->al.offset; int reg = dloc->op->reg1; - int offset = dloc->op->offset; - const char *offset_sign = ""; + int offset; + const char *offset_sign; bool retry = true; - if (offset < 0) { - offset = -offset; - offset_sign = "-"; +again: + if (arch_get_reg_offset(dloc->arch, dloc->op, reg, state, false, &offset)) { + /* + * Fall back to the instruction's offset. This prevents the + * register type of the instruction from being completely lost. + * Compared to dropping this type inference entirely, providing + * a rough type hint is likely more useful. + */ + offset = dloc->op->offset; } -again: + offset_sign = offset < 0 ? "-" : ""; + pr_debug_dtp("chk [%x] reg%d offset=%s%#x ok=%d kind=%d ", - insn_offset, reg, offset_sign, offset, + insn_offset, reg, offset_sign, abs(offset), state->regs[reg].ok, state->regs[reg].kind); if (!state->regs[reg].ok) @@ -1267,7 +1382,7 @@ again: */ ptr_type = die_get_pointer_type(&state->regs[reg].type, &ptr_die); if (!ptr_type) { - if (dloc->op->offset < 0 && reg != state->stack_reg) + if (offset < 0 && reg != state->stack_reg) goto check_kernel; return PERF_TMR_NO_POINTER; @@ -1277,7 +1392,7 @@ again: if (__die_get_real_type(ptr_type, type_die) == NULL) return PERF_TMR_NO_POINTER; - dloc->type_offset = dloc->op->offset + state->regs[reg].offset; + dloc->type_offset = offset + state->regs[reg].offset; if (dwarf_tag(type_die) == DW_TAG_typedef) die_get_real_type(type_die, &sized_type); @@ -1301,7 +1416,7 @@ again: */ *type_die = state->regs[reg].type; - dloc->type_offset = dloc->op->offset + state->regs[reg].offset; + dloc->type_offset = offset + state->regs[reg].offset; return check_type_offset(type_die, dloc->type_offset); } @@ -1315,7 +1430,7 @@ again: */ *type_die = state->regs[reg].type; - dloc->type_offset = dloc->op->offset; + dloc->type_offset = offset; if (check_type_offset(type_die, dloc->type_offset) != PERF_TMR_OK) return PERF_TMR_BAIL_OUT; @@ -1337,11 +1452,31 @@ again: } if (state->regs[reg].kind == TSR_KIND_PERCPU_BASE) { - u64 var_addr = dloc->op->offset; + u64 var_addr; int var_offset; pr_debug_dtp("percpu var"); + if (arch__is_arm64(dloc->arch)) { + int reg2; + + if (!retry || !dloc->op->multi_regs || + dloc->op->reg1 == dloc->op->reg2 || + dloc->op->extend_type || dloc->op->shift_type) + return PERF_TMR_BAIL_OUT; + + reg2 = dloc->op->reg2; + if (!has_reg_type(state, reg2) || !state->regs[reg2].ok) + return PERF_TMR_NO_TYPE; + + pr_debug_dtp(" : retry\n"); + retry = false; + reg = reg2; + goto again; + } + + var_addr = (s64) offset; + if (dloc->op->multi_regs) { int reg2 = dloc->op->reg2; @@ -1364,16 +1499,35 @@ again: if (state->regs[reg].kind == TSR_KIND_CONST && dso__kernel(map__dso(dloc->ms->map))) { - if (dloc->op->offset < 0 && reg != state->stack_reg && reg != dloc->fbreg) + if (offset < 0 && reg != state->stack_reg && reg != dloc->fbreg) goto check_kernel; } + + if (state->regs[reg].kind == TSR_KIND_GLOBAL_ADDR) { + u64 var_addr = state->regs[reg].imm_value + offset; + int var_offset; + + pr_debug_dtp("global addr"); + + /* + * The register holds the address of a global variable. Try to + * find the variable by the address and get its type. + */ + if (get_global_var_type(cu_die, dloc, dloc->ip, var_addr, + &var_offset, type_die)) { + dloc->type_offset = var_offset; + return PERF_TMR_OK; + } + /* No need to retry global variables */ + return PERF_TMR_BAIL_OUT; + } check_non_register: if (reg == dloc->fbreg || reg == state->stack_reg) { struct type_state_stack *stack; pr_debug_dtp("%s", reg == dloc->fbreg ? "fbreg" : "stack"); - stack = find_stack_state(state, dloc->type_offset); + stack = find_stack_state(state, offset); if (stack == NULL) { if (retry) { pr_debug_dtp(" : retry\n"); @@ -1396,7 +1550,7 @@ check_non_register: *type_die = stack->type; /* Update the type offset from the start of slot */ - dloc->type_offset -= stack->offset; + dloc->type_offset = offset - stack->offset; return PERF_TMR_OK; } @@ -1414,7 +1568,7 @@ check_non_register: if (reg != fbreg) return PERF_TMR_NO_TYPE; - stack = find_stack_state(state, dloc->type_offset - fboff); + stack = find_stack_state(state, offset - fboff); if (stack == NULL) { if (retry) { pr_debug_dtp(" : retry\n"); @@ -1437,7 +1591,7 @@ check_non_register: *type_die = stack->type; /* Update the type offset from the start of slot */ - dloc->type_offset -= fboff + stack->offset; + dloc->type_offset = offset - fboff - stack->offset; return PERF_TMR_OK; } @@ -1451,7 +1605,7 @@ check_kernel: arch__is_x86(dloc->arch)) { pr_debug_dtp("this-cpu var"); - addr = dloc->op->offset; + addr = (s64) offset; if (get_global_var_type(cu_die, dloc, dloc->ip, addr, &offset, type_die)) { @@ -1462,8 +1616,8 @@ check_kernel: } /* Access to global variable like "-0x7dcf0500(,%rdx,8)" */ - if (dloc->op->offset < 0 && reg != state->stack_reg) { - addr = (s64) dloc->op->offset; + if (offset < 0 && reg != state->stack_reg) { + addr = (s64) offset; if (get_global_var_type(cu_die, dloc, dloc->ip, addr, &offset, type_die)) { @@ -1534,7 +1688,8 @@ out: static int arch_supports_insn_tracking(struct data_loc_info *dloc) { - if ((arch__is_x86(dloc->arch)) || (arch__is_powerpc(dloc->arch))) + if (arch__is_x86(dloc->arch) || arch__is_powerpc(dloc->arch) || + arch__is_arm64(dloc->arch)) return 1; return 0; } diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h index bbf4fd35c1d7..eee1f8dae3dc 100644 --- a/tools/perf/util/annotate-data.h +++ b/tools/perf/util/annotate-data.h @@ -37,6 +37,7 @@ enum type_state_kind { TSR_KIND_PERCPU_POINTER, TSR_KIND_POINTER, TSR_KIND_CANARY, + TSR_KIND_GLOBAL_ADDR, }; /** @@ -182,7 +183,7 @@ extern struct annotated_data_stat ann_data_stat; */ struct type_state_reg { Dwarf_Die type; - u32 imm_value; + u64 imm_value; /* * The offset within the struct that the register points to. * A value of 0 means the register points to the beginning. @@ -208,6 +209,7 @@ struct type_state_stack { int size; bool compound; u8 kind; + u64 imm_value; }; /* @@ -262,18 +264,22 @@ bool has_reg_type(struct type_state *state, int reg); struct type_state_stack *findnew_stack_state(struct type_state *state, int offset, u8 kind, Dwarf_Die *type_die, - int ptr_offset); + int ptr_offset, u64 imm_value); void set_stack_state(struct type_state_stack *stack, int offset, u8 kind, - Dwarf_Die *type_die, int ptr_offset); + Dwarf_Die *type_die, int ptr_offset, u64 imm_value); struct type_state_stack *find_stack_state(struct type_state *state, int offset); +void delete_stack_state(struct type_state *state, int offset); +void invalidate_reg_state(struct type_state_reg *reg); bool get_global_var_type(Dwarf_Die *cu_die, struct data_loc_info *dloc, u64 ip, u64 var_addr, int *var_offset, Dwarf_Die *type_die); bool get_global_var_info(struct data_loc_info *dloc, u64 addr, const char **var_name, int *var_offset); void pr_debug_type_name(Dwarf_Die *die, enum type_state_kind kind); - +int arch_get_reg_offset(const struct arch *arch, struct annotated_op_loc *op_loc, + int breg, struct type_state *state, bool apply_index, + int *offset); #else /* HAVE_LIBDW_SUPPORT */ static inline struct annotated_data_type * diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c index f30f86156a3a..8bb491d389a2 100644 --- a/tools/perf/util/auxtrace.c +++ b/tools/perf/util/auxtrace.c @@ -123,7 +123,7 @@ static int evlist__regroup(struct evlist *evlist, struct evsel *leader, struct e return 0; } -static bool auxtrace__dont_decode(struct perf_session *session) +bool auxtrace__dont_decode(struct perf_session *session) { return !session->itrace_synth_opts || session->itrace_synth_opts->dont_decode; diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h index 5012bead1b92..c29a8444f08e 100644 --- a/tools/perf/util/auxtrace.h +++ b/tools/perf/util/auxtrace.h @@ -650,6 +650,7 @@ void auxtrace__free(struct perf_session *session); bool auxtrace__evsel_is_auxtrace(struct perf_session *session, struct evsel *evsel); u64 auxtrace_synth_id_range_start(struct evsel *evsel); +bool auxtrace__dont_decode(struct perf_session *session); #define ITRACE_HELP \ " i[period]: synthesize instructions events\n" \ diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c index 26940f1f1b0b..b56cf57a68c8 100644 --- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c +++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c @@ -741,7 +741,7 @@ cs_etm_decoder__new(int decoders, struct cs_etm_decoder_params *d_params, return decoder; err_free_decoder: - cs_etm_decoder__free(decoder); + cs_etm_decoder__zfree(&decoder); return NULL; } @@ -793,14 +793,14 @@ int cs_etm_decoder__process_data_block(struct cs_etm_decoder *decoder, return ret; } -void cs_etm_decoder__free(struct cs_etm_decoder *decoder) +void cs_etm_decoder__zfree(struct cs_etm_decoder **decoder) { - if (!decoder) + if (!(*decoder)) return; - ocsd_destroy_dcd_tree(decoder->dcd_tree); - decoder->dcd_tree = NULL; - free(decoder); + ocsd_destroy_dcd_tree((*decoder)->dcd_tree); + (*decoder)->dcd_tree = NULL; + zfree(decoder); } const char *cs_etm_decoder__get_name(struct cs_etm_decoder *decoder) diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h index 12c782fa6db2..2a8214f17958 100644 --- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h +++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.h @@ -96,7 +96,7 @@ cs_etm_decoder__new(int num_cpu, struct cs_etm_decoder_params *d_params, struct cs_etm_trace_params t_params[]); -void cs_etm_decoder__free(struct cs_etm_decoder *decoder); +void cs_etm_decoder__zfree(struct cs_etm_decoder **decoder); int cs_etm_decoder__add_mem_access_cb(struct cs_etm_decoder *decoder, u64 start, u64 end, diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 2d1ab34f7b6b..bdcf94813129 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -243,8 +243,12 @@ enum cs_etm_pid_fmt cs_etm__get_pid_fmt(struct cs_etm_queue *etmq) return etmq->etm->pid_fmt; } -static int cs_etm__insert_trace_id_node(struct cs_etm_queue *etmq, - u8 trace_chan_id, u64 *cpu_metadata) +/* + * Add a traceid_list entry that maps a trace ID to a CPU metadata for this etmq + * and update the trace ID in the cpu_metadata. + */ +static int cs_etm__map_trace_id(struct cs_etm_queue *etmq, u8 trace_chan_id, + u64 *cpu_metadata) { /* Get an RB node for this CPU */ struct int_node *inode = intlist__findnew(etmq->traceid_list, trace_chan_id); @@ -289,8 +293,7 @@ static int cs_etm__insert_trace_id_node(struct cs_etm_queue *etmq, /* Not one we've seen before, associate the traceID with the metadata pointer */ inode->priv = cpu_metadata; - - return 0; + return cs_etm__metadata_set_trace_id(trace_chan_id, cpu_metadata); } static struct cs_etm_queue *cs_etm__get_queue(struct cs_etm_auxtrace *etm, int cpu) @@ -309,31 +312,29 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id { struct cs_etm_queue *etmq; - /* - * If the queue is unformatted then only save one mapping in the - * queue associated with that CPU so only one decoder is made. - */ etmq = cs_etm__get_queue(etm, cpu_metadata[CS_ETM_CPU]); if (!etmq) return -EINVAL; + /* + * Ignore HW_IDs for unformatted, a decoder will be made + * unconditionally later. + */ if (etmq->format == UNFORMATTED) - return cs_etm__insert_trace_id_node(etmq, trace_chan_id, - cpu_metadata); + return 0; /* * Otherwise, version 0 trace IDs are global so save them into every - * queue. + * formatted queue. */ for (unsigned int i = 0; i < etm->queues.nr_queues; ++i) { int ret; etmq = etm->queues.queue_array[i].priv; - if (!etmq) + if (!etmq || etmq->format == UNFORMATTED) continue; - ret = cs_etm__insert_trace_id_node(etmq, trace_chan_id, - cpu_metadata); + ret = cs_etm__map_trace_id(etmq, trace_chan_id, cpu_metadata); if (ret) return ret; } @@ -344,7 +345,6 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id static int cs_etm__process_trace_id_v0(struct cs_etm_auxtrace *etm, int cpu, u64 hw_id) { - int err; u64 *cpu_data; u8 trace_chan_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id); @@ -352,22 +352,13 @@ static int cs_etm__process_trace_id_v0(struct cs_etm_auxtrace *etm, int cpu, if (cpu_data == NULL) return -EINVAL; - err = cs_etm__map_trace_id_v0(etm, trace_chan_id, cpu_data); - if (err) - return err; - - /* - * if we are picking up the association from the packet, need to plug - * the correct trace ID into the metadata for setting up decoders later. - */ - return cs_etm__metadata_set_trace_id(trace_chan_id, cpu_data); + return cs_etm__map_trace_id_v0(etm, trace_chan_id, cpu_data); } static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, u64 hw_id) { struct cs_etm_queue *etmq = cs_etm__get_queue(etm, cpu); - int ret; u64 *cpu_data; u32 sink_id = FIELD_GET(CS_AUX_HW_ID_SINK_ID_MASK, hw_id); u8 trace_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id); @@ -376,6 +367,13 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, return -EINVAL; /* + * Ignore HW_IDs for unformatted, a decoder will be made + * unconditionally later. + */ + if (etmq->format == UNFORMATTED) + return 0; + + /* * Check sink id hasn't changed in per-cpu mode. In per-thread mode, * let it pass for now until an actual overlapping trace ID is hit. In * most cases IDs won't overlap even if the sink changes. @@ -419,13 +417,41 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu, if (!cpu_data) return -EINVAL; - ret = cs_etm__insert_trace_id_node(etmq, trace_id, cpu_data); - if (ret) - return ret; + return cs_etm__map_trace_id(etmq, trace_id, cpu_data); +} - ret = cs_etm__metadata_set_trace_id(trace_id, cpu_data); - if (ret) - return ret; +static int cs_etm__map_unformatted_trace_ids(struct cs_etm_auxtrace *etm) +{ + struct auxtrace_queues *queues = &etm->queues; + + for (unsigned int i = 0; i < queues->nr_queues; i++) { + struct auxtrace_queue *queue = &queues->queue_array[i]; + struct cs_etm_queue *etmq = queue->priv; + u64 *cpu_data; + int ret; + + if (list_empty(&queue->head) || !etmq || etmq->format != UNFORMATTED) + continue; + + if (!intlist__empty(etmq->traceid_list)) { + pr_err("CS_ETM: Unformatted queue should't have trace ID\n"); + return -EINVAL; + } + + if (etm->per_thread_decoding) + cpu_data = etm->metadata[0]; + else + cpu_data = get_cpu_data(etm, queue->cpu); + + if (!cpu_data) { + pr_err("CS_ETM: Unformatted queue missing CPU metadata\n"); + return -EINVAL; + } + + ret = cs_etm__map_trace_id(etmq, 1, cpu_data); + if (ret) + return ret; + } return 0; } @@ -939,7 +965,7 @@ static int cs_etm__flush_events(struct perf_session *session, struct cs_etm_auxtrace *etm = container_of(session->auxtrace, struct cs_etm_auxtrace, auxtrace); - if (dump_trace) + if (dump_trace || auxtrace__dont_decode(session)) return 0; if (!tool->ordered_events) @@ -1002,7 +1028,7 @@ static void cs_etm__free_queue(void *priv) if (!etmq) return; - cs_etm_decoder__free(etmq->decoder); + cs_etm_decoder__zfree(&etmq->decoder); cs_etm__free_traceid_queues(etmq); if (etmq->own_traceid_list) { @@ -1017,13 +1043,10 @@ static void cs_etm__free_queue(void *priv) free(etmq); } -static void cs_etm__free_events(struct perf_session *session) +static void cs_etm__free_queues(struct cs_etm_auxtrace *etm) { unsigned int i; - struct cs_etm_auxtrace *aux = container_of(session->auxtrace, - struct cs_etm_auxtrace, - auxtrace); - struct auxtrace_queues *queues = &aux->queues; + struct auxtrace_queues *queues = &etm->queues; for (i = 0; i < queues->nr_queues; i++) { cs_etm__free_queue(queues->queue_array[i].priv); @@ -1033,13 +1056,23 @@ static void cs_etm__free_events(struct perf_session *session) auxtrace_queues__free(queues); } +static void cs_etm__free_events(struct perf_session *session) +{ + struct cs_etm_auxtrace *aux = container_of(session->auxtrace, + struct cs_etm_auxtrace, + auxtrace); + + cs_etm__free_queues(aux); +} + static void cs_etm__free(struct perf_session *session) { int i; struct cs_etm_auxtrace *aux = container_of(session->auxtrace, struct cs_etm_auxtrace, auxtrace); - cs_etm__free_events(session); + + cs_etm__free_queues(aux); session->auxtrace = NULL; for (i = 0; i < aux->num_cpu; i++) @@ -2969,7 +3002,7 @@ static int cs_etm__process_event(struct perf_session *session, struct cs_etm_auxtrace, auxtrace); - if (dump_trace) + if (dump_trace || auxtrace__dont_decode(session)) return 0; if (!tool->ordered_events) { @@ -3476,7 +3509,7 @@ static int cs_etm__create_queue_decoders(struct cs_etm_queue *etmq) return 0; out_free_decoder: - cs_etm_decoder__free(etmq->decoder); + cs_etm_decoder__zfree(&etmq->decoder); out_free: zfree(&t_params); return -EINVAL; @@ -3519,27 +3552,49 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, int err = 0; int aux_hw_id_found; int i; - u64 *ptr = NULL; + u64 *ptr = (u64 *) auxtrace_info->priv; u64 **metadata = NULL; - /* First the global part */ - ptr = (u64 *) auxtrace_info->priv; - num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff; + etm = zalloc(sizeof(*etm)); + if (!etm) + return -ENOMEM; + + session->auxtrace = &etm->auxtrace; + etm->auxtrace.free = cs_etm__free; + etm->auxtrace.evsel_is_auxtrace = cs_etm__evsel_is_auxtrace; + etm->auxtrace.process_event = cs_etm__process_event; + etm->auxtrace.process_auxtrace_event = cs_etm__process_auxtrace_event; + etm->auxtrace.flush_events = cs_etm__flush_events; + etm->auxtrace.free_events = cs_etm__free_events; + etm->pmu_type = (unsigned int) ((ptr[CS_PMU_TYPE_CPUS] >> 32) & 0xffffffff); + + /* + * Don't go further than the minimum required to identify this event as + * auxtrace with cs_etm__evsel_is_auxtrace() so unleader_auxtrace() + * works. + */ + if (auxtrace__dont_decode(session)) + return 0; /* * Bound num_cpu by the event size: the global header consumes * CS_ETM_HEADER_SIZE bytes, and each CPU needs at least one u64 * metadata entry after that. */ + num_cpu = ptr[CS_PMU_TYPE_CPUS] & 0xffffffff; priv_size = total_size - event_header_size - INFO_HEADER_SIZE - CS_ETM_HEADER_SIZE; if (num_cpu <= 0 || priv_size <= 0 || - num_cpu > priv_size / (int)sizeof(u64)) - return -EINVAL; + num_cpu > priv_size / (int)sizeof(u64)) { + err = -EINVAL; + goto err_free_etm; + } metadata = zalloc(sizeof(*metadata) * num_cpu); - if (!metadata) - return -ENOMEM; + if (!metadata) { + err = -ENOMEM; + goto err_free_etm; + } /* Start parsing after the common part of the header */ i = CS_HEADER_VERSION_MAX; @@ -3598,13 +3653,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, goto err_free_metadata; } - etm = zalloc(sizeof(*etm)); - - if (!etm) { - err = -ENOMEM; - goto err_free_metadata; - } - /* * As all the ETMs run at the same exception level, the system should * have the same PID format crossing CPUs. So cache the PID format @@ -3614,7 +3662,7 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, err = auxtrace_queues__init_nr(&etm->queues, max_cpu + 1); if (err) - goto err_free_etm; + goto err_free_metadata; for (unsigned int j = 0; j < etm->queues.nr_queues; ++j) { err = cs_etm__setup_queue(etm, &etm->queues.queue_array[j], j); @@ -3652,7 +3700,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, etm->session = session; etm->num_cpu = num_cpu; - etm->pmu_type = (unsigned int) ((ptr[CS_PMU_TYPE_CPUS] >> 32) & 0xffffffff); etm->snapshot_mode = (ptr[CS_ETM_SNAPSHOT] != 0); etm->metadata = metadata; etm->auxtrace_type = auxtrace_info->type; @@ -3679,14 +3726,6 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, "you can specify the itrace option 'T' for timestamp decoding\n" "if the Coresight timestamp on the platform is same with the kernel time.\n\n"); - etm->auxtrace.process_event = cs_etm__process_event; - etm->auxtrace.process_auxtrace_event = cs_etm__process_auxtrace_event; - etm->auxtrace.flush_events = cs_etm__flush_events; - etm->auxtrace.free_events = cs_etm__free_events; - etm->auxtrace.free = cs_etm__free; - etm->auxtrace.evsel_is_auxtrace = cs_etm__evsel_is_auxtrace; - session->auxtrace = &etm->auxtrace; - cs_etm__setup_timeless_decoding(etm); etm->tc.time_shift = tc->time_shift; @@ -3750,6 +3789,11 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, goto err_free_queues; } + /* Create IDs for TRBE sinks so that a decoder is made for each one. */ + err = cs_etm__map_unformatted_trace_ids(etm); + if (err) + goto err_free_queues; + err = cs_etm__create_decoders(etm); if (err) goto err_free_queues; @@ -3758,14 +3802,14 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, return 0; err_free_queues: - auxtrace_queues__free(&etm->queues); - session->auxtrace = NULL; -err_free_etm: - zfree(&etm); + cs_etm__free_queues(etm); err_free_metadata: /* No need to check @metadata[j], free(NULL) is supported */ for (int j = 0; j < num_cpu; j++) zfree(&metadata[j]); zfree(&metadata); +err_free_etm: + session->auxtrace = NULL; + zfree(&etm); return err; } diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 6b737e195ce1..7552c9171d90 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -33,10 +33,9 @@ extern int debug_type_profile; }) #define pr_info(fmt, ...) \ eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__) -#define pr_debug(fmt, ...) \ - eprintf(1, verbose, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debugN(n, fmt, ...) \ - eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__) + (unlikely(verbose >= (n)) ? eprintf(n, verbose, pr_fmt(fmt), ##__VA_ARGS__) : 0) +#define pr_debug(fmt, ...) pr_debugN(1, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug2(fmt, ...) pr_debugN(2, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug3(fmt, ...) pr_debugN(3, pr_fmt(fmt), ##__VA_ARGS__) #define pr_debug4(fmt, ...) pr_debugN(4, pr_fmt(fmt), ##__VA_ARGS__) diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c index 5478c134e7e3..7c289b67b5bd 100644 --- a/tools/perf/util/disasm.c +++ b/tools/perf/util/disasm.c @@ -1176,84 +1176,6 @@ int symbol__strerror_disassemble(struct map_symbol *ms, int errnum, char *buf, s return 0; } -static int dso__disassemble_filename(struct dso *dso, char *filename, size_t filename_size) -{ - char linkname[PATH_MAX]; - char *build_id_filename; - char *build_id_path = NULL; - char *pos; - int len; - - if (dso__symtab_type(dso) == DSO_BINARY_TYPE__KALLSYMS && - !dso__is_kcore(dso)) - return SYMBOL_ANNOTATE_ERRNO__NO_VMLINUX; - - build_id_filename = dso__build_id_filename(dso, NULL, 0, false); - if (build_id_filename) { - /* - * This is a path in perf's own build id cache, not a path on - * the profiled system, so the symfs layout does not apply. - */ - path__join(filename, filename_size, symbol_conf.symfs, - build_id_filename); - free(build_id_filename); - } else { - if (dso__has_build_id(dso)) - return ENOMEM; - goto fallback; - } - - build_id_path = strdup(filename); - if (!build_id_path) - return ENOMEM; - - /* - * old style build-id cache has name of XX/XXXXXXX.. while - * new style has XX/XXXXXXX../{elf,kallsyms,vdso}. - * extract the build-id part of dirname in the new style only. - */ - pos = strrchr(build_id_path, '/'); - if (pos && strlen(pos) < SBUILD_ID_SIZE - 2) - dirname(build_id_path); - - if (dso__is_kcore(dso)) - goto fallback; - - len = readlink(build_id_path, linkname, sizeof(linkname) - 1); - if (len < 0) - goto fallback; - - linkname[len] = '\0'; - if (strstr(linkname, DSO__NAME_KALLSYMS) || - access(filename, R_OK)) { -fallback: - /* - * If we don't have build-ids or the build-id file isn't in the - * cache, or is just a kallsyms file, well, lets hope that this - * DSO is the same as when 'perf record' ran. - */ - if (dso__kernel(dso) && dso__long_name(dso)[0] == '/') - snprintf(filename, filename_size, "%s", dso__long_name(dso)); - else - __symbol__join_symfs(filename, filename_size, dso__long_name(dso)); - - mutex_lock(dso__lock(dso)); - if (access(filename, R_OK) && errno == ENOENT && dso__nsinfo(dso)) { - char *new_name = dso__filename_with_chroot(dso, filename); - if (new_name) { - strlcpy(filename, new_name, filename_size); - free(new_name); - } - } - mutex_unlock(dso__lock(dso)); - } else if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND) { - dso__set_binary_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); - } - - free(build_id_path); - return 0; -} - static int symbol__disassemble_raw(char *filename, struct symbol *sym, struct annotate_args *args) { @@ -1566,14 +1488,29 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) struct annotation_options *options = args->options; struct map *map = args->ms->map; struct dso *dso = map__dso(map); - char symfs_filename[PATH_MAX]; + char *symfs_filename; bool delete_extract = false; struct kcore_extract kce; + enum dso_binary_type dbt; bool decomp = false; - int err = dso__disassemble_filename(dso, symfs_filename, sizeof(symfs_filename)); + int err; - if (err) - return err; + dbt = dso__find_binary_type(dso); + + if (dbt == DSO_BINARY_TYPE__KALLSYMS) + return SYMBOL_ANNOTATE_ERRNO__NO_VMLINUX; + + mutex_lock(dso__lock(dso)); + symfs_filename = dso__get_filename(dso, "", &decomp, dbt); + mutex_unlock(dso__lock(dso)); + + if (symfs_filename == NULL) { + if (dbt == DSO_BINARY_TYPE__BPF_PROG_INFO || + dbt == DSO_BINARY_TYPE__BPF_IMAGE) + symfs_filename = (char *)dso__long_name(dso); + else + return SYMBOL_ANNOTATE_ERRNO__COULDNT_DETERMINE_FILE_TYPE; + } pr_debug("%s: filename=%s, sym=%s, start=%#" PRIx64 ", end=%#" PRIx64 "\n", __func__, symfs_filename, sym->name, map__unmap_ip(map, sym->start), @@ -1581,9 +1518,7 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) pr_debug("annotating [%p] %30s : [%p] %30s\n", dso, dso__long_name(dso), sym, sym->name); - if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND) { - return SYMBOL_ANNOTATE_ERRNO__COULDNT_DETERMINE_FILE_TYPE; - } else if (dso__is_kcore(dso)) { + if (dso__is_kcore(dso)) { kce.addr = map__rip_2objdump(map, sym->start); kce.kcore_filename = symfs_filename; kce.len = sym->end - sym->start; @@ -1591,18 +1526,8 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) if (!kcore_extract__create(&kce)) { delete_extract = true; - strlcpy(symfs_filename, kce.extract_filename, sizeof(symfs_filename)); - } - } else if (dso__needs_decompress(dso)) { - char tmp[KMOD_DECOMP_LEN]; - - if (dso__decompress_kmodule_path(dso, symfs_filename, tmp, sizeof(tmp)) < 0) - return -1; - - /* empty pathname means file wasn't actually compressed */ - if (tmp[0] != '\0') { - decomp = true; - strcpy(symfs_filename, tmp); + dso__put_filename(dso, symfs_filename, decomp); + symfs_filename = kce.extract_filename; } } @@ -1660,11 +1585,10 @@ int symbol__disassemble(struct symbol *sym, struct annotate_args *args) pr_debug("Disassembled with %s\n", perf_disassembler__strs[dis]); } out_remove_tmp: - if (decomp) - unlink(symfs_filename); - if (delete_extract) kcore_extract__delete(&kce); + else if (dbt != DSO_BINARY_TYPE__BPF_PROG_INFO && dbt != DSO_BINARY_TYPE__BPF_IMAGE) + dso__put_filename(dso, symfs_filename, decomp); return err; } diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index d3017c82ffb5..ce98613868e7 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -833,34 +833,149 @@ void dso__data_close(struct dso *dso) mutex_unlock(dso__data_open_lock()); } -static void try_to_open_dso(struct dso *dso, struct machine *machine) - EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +static int dso__find_binary_type_locked(struct dso *dso) + EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso)) { enum dso_binary_type binary_type_data[] = { DSO_BINARY_TYPE__BUILD_ID_CACHE, DSO_BINARY_TYPE__SYSTEM_PATH_DSO, + dso__symtab_type(dso), DSO_BINARY_TYPE__NOT_FOUND, }; int i = 0; - struct dso_data *dso_data = dso__data(dso); + char *path; + bool found = false; + bool decomp = false; + int saved_errno; - if (dso_data->fd >= 0) - return; + if (dso__bintype_searched(dso)) + return dso__binary_type(dso); + dso__set_bintype_searched(dso); - if (dso__binary_type(dso) != DSO_BINARY_TYPE__NOT_FOUND) { - dso_data->fd = open_dso(dso, machine); - goto out; + switch (dso__binary_type(dso)) { + case DSO_BINARY_TYPE__KALLSYMS: + case DSO_BINARY_TYPE__GUEST_KALLSYMS: + case DSO_BINARY_TYPE__VMLINUX: + case DSO_BINARY_TYPE__GUEST_VMLINUX: + case DSO_BINARY_TYPE__KCORE: + case DSO_BINARY_TYPE__GUEST_KCORE: + /* Nothing to do with kernel images */ + WARN_ON(!dso__kernel(dso)); + return dso__binary_type(dso); + case DSO_BINARY_TYPE__BPF_PROG_INFO: + case DSO_BINARY_TYPE__BPF_IMAGE: + case DSO_BINARY_TYPE__OOL: + case DSO_BINARY_TYPE__JAVA_JIT: + /* Same for the special DSOs */ + return dso__binary_type(dso); + case DSO_BINARY_TYPE__GUEST_KMODULE: + case DSO_BINARY_TYPE__GUEST_KMODULE_COMP: + case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE: + case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE_COMP: + case DSO_BINARY_TYPE__SYSTEM_PATH_DSO: + case DSO_BINARY_TYPE__BUILD_ID_CACHE: + /* We don't expect these are set; fall through */ + case DSO_BINARY_TYPE__NOT_FOUND: + /* Let's find it out (for user DSOs or kernel modules) */ + break; + case DSO_BINARY_TYPE__FEDORA_DEBUGINFO: + case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO: + case DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO: + case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO: + case DSO_BINARY_TYPE__BUILD_ID_CACHE_DEBUGINFO: + case DSO_BINARY_TYPE__BUILDID_DEBUGINFO: + case DSO_BINARY_TYPE__GNU_DEBUGDATA: + case DSO_BINARY_TYPE__DEBUGLINK: + default: + /* Unexpected binary types (usually debug only) */ + break; } do { dso__set_binary_type(dso, binary_type_data[i++]); - dso_data->fd = open_dso(dso, machine); - if (dso_data->fd >= 0) - goto out; + errno = 0; + path = dso__get_filename(dso, "", &decomp, dso__binary_type(dso)); + if (path == NULL) { + if (errno == EMFILE) + goto open_err; + continue; + } + + errno = 0; + found = filename__has_section(path, ".text"); + if (!found) + saved_errno = errno; + dso__put_filename(dso, path, decomp); + + if (found) + return dso__binary_type(dso); + else if (saved_errno == EMFILE) + goto open_err; } while (dso__binary_type(dso) != DSO_BINARY_TYPE__NOT_FOUND); -out: + + return dso__binary_type(dso); + +open_err: + /* Too many open files. Allow retry after closing one. */ + RC_CHK_ACCESS(dso)->bintype_searched = 0; + dso__set_binary_type(dso, DSO_BINARY_TYPE__NOT_FOUND); + return -1; +} + +#define FIND_MAX_RETRY 3 +static int __dso__find_binary_type(struct dso *dso) + EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +{ + int ret; + int retry = 0; + +again: + mutex_lock(dso__lock(dso)); + ret = dso__find_binary_type_locked(dso); + mutex_unlock(dso__lock(dso)); + + if (ret == -1 && retry++ < FIND_MAX_RETRY) { + if (dso__data_open_cnt) { + close_first_dso(); + goto again; + } + } + + if (ret == -1) { + mutex_lock(dso__lock(dso)); + ret = dso__binary_type(dso); + dso__set_bintype_searched(dso); + mutex_unlock(dso__lock(dso)); + } + + return ret; +} +#undef FIND_MAX_RETRY + +enum dso_binary_type dso__find_binary_type(struct dso *dso) +{ + int ret; + + mutex_lock(dso__data_open_lock()); + ret = __dso__find_binary_type(dso); + mutex_unlock(dso__data_open_lock()); + + return ret; +} + +static void try_to_open_dso(struct dso *dso, struct machine *machine) + EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock) +{ + struct dso_data *dso_data = dso__data(dso); + + if (dso_data->fd >= 0) + return; + + __dso__find_binary_type(dso); + dso_data->fd = open_dso(dso, machine); + if (dso_data->fd >= 0) dso_data->status = DSO_DATA_STATUS_OK; else @@ -1377,9 +1492,10 @@ uint16_t dso__e_machine_endian(struct dso *dso, struct machine *machine, uint32_ bool *is_big_endian) { uint16_t e_machine = EM_NONE; + enum dso_binary_type dbt = dso__find_binary_type(dso); int fd; - switch (dso__binary_type(dso)) { + switch (dbt) { case DSO_BINARY_TYPE__KALLSYMS: case DSO_BINARY_TYPE__GUEST_KALLSYMS: case DSO_BINARY_TYPE__VMLINUX: @@ -1722,8 +1838,6 @@ struct dso *dso__new_id(const char *name, const struct dso_id *id) dso->srclines = RB_ROOT_CACHED; dso->data_types = RB_ROOT; dso->global_vars = RB_ROOT; - dso->data.fd = -1; - dso->data.status = DSO_DATA_STATUS_UNKNOWN; dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; dso->dbginfo_type = DSO_BINARY_TYPE__NOT_FOUND; @@ -2113,6 +2227,7 @@ void dso__find_dbginfo_type(struct dso *dso) DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO, DSO_BINARY_TYPE__BUILDID_DEBUGINFO, DSO_BINARY_TYPE__SYSTEM_PATH_DSO, + DSO_BINARY_TYPE__DEBUGLINK, DSO_BINARY_TYPE__NOT_FOUND, }; const enum dso_binary_type *type; diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index 3f08d45e7f53..d5f7382d4e13 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -349,6 +349,7 @@ DECLARE_RC_STRUCT(dso) { u8 short_name_allocated:1; u8 long_name_allocated:1; u8 is_64_bit:1; + u8 bintype_searched:1; u8 debuginfo_searched:1; bool sorted_by_name; bool loaded; @@ -759,7 +760,7 @@ static inline const char *dso__symsrc_filename(const struct dso *dso) return RC_CHK_ACCESS(dso)->symsrc_filename; } -void dso__set_symsrc_filename(struct dso *dso, char *val); +void dso__set_symsrc_filename(struct dso *dso, char *val) EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso)); static inline void dso__free_symsrc_filename(struct dso *dso) { @@ -957,6 +958,8 @@ void dso__reset_find_symbol_cache(struct dso *dso); size_t dso__fprintf_symbols_by_name(struct dso *dso, FILE *fp); size_t dso__fprintf(struct dso *dso, FILE *fp); +enum dso_binary_type dso__find_binary_type(struct dso *dso); + static inline enum dso_binary_type dso__binary_type(const struct dso *dso) { return RC_CHK_ACCESS(dso)->binary_type; @@ -967,6 +970,16 @@ static inline void dso__set_binary_type(struct dso *dso, enum dso_binary_type bt RC_CHK_ACCESS(dso)->binary_type = bt; } +static inline bool dso__bintype_searched(const struct dso *dso) +{ + return RC_CHK_ACCESS(dso)->bintype_searched; +} + +static inline void dso__set_bintype_searched(struct dso *dso) +{ + RC_CHK_ACCESS(dso)->bintype_searched = 1; +} + static inline bool dso__is_vmlinux(const struct dso *dso) { enum dso_binary_type bt = dso__binary_type(dso); diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 9c5e7510f0c0..d060fca8090f 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -3005,6 +3005,19 @@ check: return false; } +static void evsel__close_fds(struct evsel *evsel, int start_cpu_map_idx, + int cpu_map_idx, int thread, int nthreads) +{ + do { + while (--thread >= 0) { + if (FD(evsel, cpu_map_idx, thread) >= 0) + close(FD(evsel, cpu_map_idx, thread)); + FD(evsel, cpu_map_idx, thread) = -1; + } + thread = nthreads; + } while (--cpu_map_idx >= start_cpu_map_idx); +} + static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, struct perf_thread_map *threads, int start_cpu_map_idx, int end_cpu_map_idx) @@ -3014,6 +3027,12 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus, enum rlimit_action set_rlimit = NO_CHANGE; struct perf_cpu cpu; + /* + * A failed attempt being retried after evsel__fallback() would + * otherwise keep group members out of their group in get_group_fd(). + */ + evsel->supported = true; + if (evsel__is_retire_lat(evsel)) { err = evsel__tpebs_open(evsel); goto out; @@ -3098,7 +3117,11 @@ retry_open: bpf_counter__install_pe(evsel, idx, fd); - if (unlikely(test_attr__enabled())) { + /* + * Support probes aren't what was asked for, leave them + * out of the PERF_TEST_ATTR dump. + */ + if (unlikely(test_attr__enabled() && !evsel->support_probe)) { test_attr__open(&evsel->core.attr, pid, cpu, fd, group_fd, evsel->open_flags); } @@ -3140,8 +3163,14 @@ try_fallback: if (err == -EMFILE && rlimit__increase_nofile(&set_rlimit)) goto retry_open; - if (err == -EINVAL && evsel__detect_missing_features(evsel, cpu)) + if (err == -EINVAL && evsel__detect_missing_features(evsel, cpu)) { + /* + * The restart opens the events again, so close what this + * attempt opened before its entries are overwritten. + */ + evsel__close_fds(evsel, start_cpu_map_idx, idx, thread, nthreads); goto fallback_missing_features; + } if (evsel__precise_ip_fallback(evsel)) goto retry_open; @@ -3151,14 +3180,7 @@ out_close: threads->err_thread = thread; old_errno = errno; - do { - while (--thread >= 0) { - if (FD(evsel, idx, thread) >= 0) - close(FD(evsel, idx, thread)); - FD(evsel, idx, thread) = -1; - } - thread = nthreads; - } while (--idx >= start_cpu_map_idx); + evsel__close_fds(evsel, start_cpu_map_idx, idx, thread, nthreads); errno = old_errno; out: if (err) diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 174f3414fd3c..53f3641b66e1 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -120,6 +120,7 @@ struct evsel { u64 db_id; bool uniquified_name; bool supported; + bool support_probe; /* Opened just to check for support. */ bool needs_swap; bool disabled; bool no_aux_samples; diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 443694926f1a..e75a4fcce975 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -409,7 +409,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) u64 prev_period = he->stat.period; u64 prev_latency = he->stat.latency; - if (prev_period == 0) + if (prev_period == 0 && + (!symbol_conf.cumulate_callchain || he->stat_acc->period == 0)) return true; he_stat__decay(&he->stat); @@ -442,7 +443,8 @@ static bool hists__decay_entry(struct hists *hists, struct hist_entry *he) } } - return he->stat.period == 0 && he->stat.latency == 0; + return he->stat.period == 0 && he->stat.latency == 0 && + (!symbol_conf.cumulate_callchain || he->stat_acc->period == 0); } static void hists__delete_entry(struct hists *hists, struct hist_entry *he) diff --git a/tools/perf/util/libbfd.c b/tools/perf/util/libbfd.c index a907d7c1ac94..992e2c2a7198 100644 --- a/tools/perf/util/libbfd.c +++ b/tools/perf/util/libbfd.c @@ -288,7 +288,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (node && inline_list__append_dso_a2l(dso, node, sym)) { inline_node__clear_frames(node); - ret = 0; + ret = -ENOMEM; goto out; } @@ -302,7 +302,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (node != NULL) { if (inline_list__append_dso_a2l(dso, node, sym)) { inline_node__clear_frames(node); - ret = 0; + ret = -ENOMEM; goto out; } } @@ -312,7 +312,7 @@ int libbfd__addr2line(const char *dso_name, u64 addr, if (file) { *file = a2l->filename ? strdup(a2l->filename) : NULL; if (!*file) { - /* Leave ret as 0 so that another addr2line is tried. */ + ret = a2l->filename ? -ENOMEM : 0; goto out; } } @@ -463,7 +463,7 @@ int dso__load_bfd_symbols(struct dso *dso, const char *debugfile) symbols__fixup_duplicate(dso__symbols(dso)); dso__set_adjust_symbols(dso, true); - err = 0; + err = dso__has_symbols(dso); out_free: free(symbols); out_close: diff --git a/tools/perf/util/libdw.c b/tools/perf/util/libdw.c index c4504cceb313..e660623338d5 100644 --- a/tools/perf/util/libdw.c +++ b/tools/perf/util/libdw.c @@ -7,6 +7,7 @@ #include "callchain.h" #include <fcntl.h> #include <unistd.h> +#include <string.h> #include <elfutils/libdwfl.h> static const Dwfl_Callbacks offline_callbacks = { @@ -175,10 +176,15 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, int lineno = 0; int ret = 0; + if (!dso__has_srcline(dso)) + return 0; + mutex_lock(dso__lock(dso)); dwfl = dso__libdw_dwfl(dso); - if (!dwfl) + if (!dwfl) { + ret = -1; goto out; + } mod = dwfl_addrmodule(dwfl, addr); if (!mod) { @@ -192,14 +198,23 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, * libdw. */ if (!dwfl_module_getdwarf(mod, &bias)) { - ret = -1; + /* + * Free the Dwfl session so the mmap'd ELF binary is unmapped + * immediately, and mark the DSO as lacking srcline/DWARF info + * so neither libdw nor slower fallback backends (addr2line + * subprocess, libbfd) are repeatedly invoked on stripped DSOs. + */ + dso__free_libdw(dso); + dso__set_has_srcline(dso, false); + ret = 0; goto out; } /* Find source line information for the address. */ dwline = dwfl_module_getsrc(mod, addr + bias); if (!dwline) { - ret = -1; + /* conclusively missing information -> abort fallbacks */ + ret = 0; goto out; } @@ -228,7 +243,7 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, free(*file); *file = NULL; } - ret = 0; + ret = -ENOMEM; goto out; } @@ -245,7 +260,7 @@ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, *file = NULL; } inline_node__clear_frames(node); - ret = 0; + ret = args.err; goto out; } } diff --git a/tools/perf/util/libdw.h b/tools/perf/util/libdw.h index b12094737415..c8f319ce35df 100644 --- a/tools/perf/util/libdw.h +++ b/tools/perf/util/libdw.h @@ -23,7 +23,8 @@ struct symbol; * finds the source line information for the given address, and optionally * resolves inline function call chains. * - * Returns 1 on success (found), 0 on failure (not found). + * Returns 1 on success (found), 0 when DWARF/line info is conclusively + * missing, and < 0 on initialization or uncompiled error. */ int libdw__addr2line(u64 addr, char **file, unsigned int *line_nr, struct dso *dso, @@ -47,7 +48,7 @@ static inline int libdw__addr2line(u64 addr __maybe_unused, char **file __maybe_ struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused) { - return 0; + return -1; } static inline void dso__free_libdw(struct dso *dso __maybe_unused) diff --git a/tools/perf/util/llvm-c-helpers.cpp b/tools/perf/util/llvm-c-helpers.cpp index 004081bd12c9..d990166100f0 100644 --- a/tools/perf/util/llvm-c-helpers.cpp +++ b/tools/perf/util/llvm-c-helpers.cpp @@ -50,20 +50,20 @@ static LLVMSymbolizer *get_symbolizer() return instance; } -/* Returns 0 on error, 1 on success. */ +/* Returns < 0 on error, 0 when line info is missing, 1 on success. */ static int extract_file_and_line(const DILineInfo &line_info, char **file, unsigned int *line) { - if (file) { - if (line_info.FileName == "<invalid>") { - /* Match the convention of libbfd. */ + if (line_info.FileName == "<invalid>") { + if (file) *file = nullptr; - } else { - /* The caller expects to get something it can free(). */ - *file = strdup(line_info.FileName.c_str()); - if (*file == nullptr) - return 0; - } + return 0; + } + if (file) { + /* The caller expects to get something it can free(). */ + *file = strdup(line_info.FileName.c_str()); + if (*file == nullptr) + return -ENOMEM; } if (line) *line = line_info.Line; @@ -86,30 +86,42 @@ int llvm_addr2line(const char *dso_name, u64 addr, Expected<DIInliningInfo> res_or_err = symbolizer->symbolizeInlinedCode(dso_name, sectioned_addr); - if (!res_or_err) - return 0; + if (!res_or_err) { + consumeError(res_or_err.takeError()); + return -1; + } unsigned num_frames = res_or_err->getNumberOfFrames(); if (num_frames == 0) return 0; - if (extract_file_and_line(res_or_err->getFrame(0), - file, line) == 0) - return 0; + int ret = extract_file_and_line(res_or_err->getFrame(0), + file, line); + if (ret <= 0) + return ret; *inline_frames = (llvm_a2l_frame *)calloc( num_frames, sizeof(**inline_frames)); - if (*inline_frames == nullptr) - return 0; + if (*inline_frames == nullptr) { + if (file) + zfree(file); + return -ENOMEM; + } for (unsigned i = 0; i < num_frames; ++i) { const DILineInfo &src = res_or_err->getFrame(i); llvm_a2l_frame &dst = (*inline_frames)[i]; - if (src.FileName == "<invalid>") - /* Match the convention of libbfd. */ - dst.filename = nullptr; - else - dst.filename = strdup(src.FileName.c_str()); + if (src.FileName == "<invalid>") { + for (unsigned j = 0; j < i; ++j) { + zfree(&(*inline_frames)[j].filename); + zfree(&(*inline_frames)[j].funcname); + } + zfree(inline_frames); + if (file) + zfree(file); + return 0; + } + dst.filename = strdup(src.FileName.c_str()); dst.funcname = strdup(src.FunctionName.c_str()); dst.line = src.Line; @@ -120,7 +132,9 @@ int llvm_addr2line(const char *dso_name, u64 addr, zfree(&(*inline_frames)[j].funcname); } zfree(inline_frames); - return 0; + if (file) + zfree(file); + return -ENOMEM; } } @@ -131,8 +145,10 @@ int llvm_addr2line(const char *dso_name, u64 addr, Expected<DILineInfo> res_or_err = symbolizer->symbolizeCode(dso_name, sectioned_addr); - if (!res_or_err) - return 0; + if (!res_or_err) { + consumeError(res_or_err.takeError()); + return -1; + } return extract_file_and_line(*res_or_err, file, line); } } diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c index 722e71aab100..0ecb1a53359e 100644 --- a/tools/perf/util/llvm.c +++ b/tools/perf/util/llvm.c @@ -44,8 +44,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused int num_frames = llvm_addr2line(dso_name, addr, file, line, node && unwind_inlines, &inline_frames); - if (num_frames == 0 || !inline_frames) { - /* Error, or we didn't want inlines. */ + if (num_frames <= 0 || !inline_frames) { + /* + * Error (< 0 -> iterate fallbacks), missing line info (0), or + * we resolved lines without wanting inlines (1). + */ return num_frames; } @@ -60,8 +63,11 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused inline_frames[i].line); } if (inline_list__append(inline_sym, srcline, node) != 0) { + if (file) + zfree(file); + inline_node__clear_frames(node); free_llvm_inline_frames(inline_frames, num_frames); - return 0; + return -1; } } free_llvm_inline_frames(inline_frames, num_frames); diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 1d9d3bf57720..076d167380f2 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -2860,7 +2860,7 @@ static int thread__resolve_callchain_sample(struct thread *thread, if (chain) chain_nr = chain->nr; - if (evsel__has_branch_callstack(evsel)) { + if (chain && evsel__has_branch_callstack(evsel)) { struct perf_env *env = evsel__env(evsel); err = resolve_lbr_callchain_sample(thread, cursor, sample, parent, diff --git a/tools/perf/util/mmap.c b/tools/perf/util/mmap.c index 358e70c4f3ed..6dcc4a9304b9 100644 --- a/tools/perf/util/mmap.c +++ b/tools/perf/util/mmap.c @@ -330,7 +330,7 @@ int mmap__mmap(struct mmap *map, struct mmap_params *mp, int fd, struct perf_cpu int perf_mmap__push(struct mmap *md, void *to, int push(struct mmap *map, void *to, void *buf, size_t size)) { - u64 head = perf_mmap__read_head(&md->core); + u64 head; unsigned char *data = md->core.base + page_size; unsigned long size; void *buf; @@ -340,6 +340,7 @@ int perf_mmap__push(struct mmap *md, void *to, if (rc < 0) return (rc == -EAGAIN) ? 1 : -1; + head = md->core.overwrite ? md->core.start : md->core.end; size = md->core.end - md->core.start; if ((md->core.start & md->core.mask) + size != (md->core.end & md->core.mask)) { diff --git a/tools/perf/util/mutex.c b/tools/perf/util/mutex.c index 7aa1f3f55a7d..df3b7cce3d5a 100644 --- a/tools/perf/util/mutex.c +++ b/tools/perf/util/mutex.c @@ -17,37 +17,106 @@ static void check_err(const char *fn, int err) #define CHECK_ERR(err) check_err(__func__, err) -static void __mutex_init(struct mutex *mtx, bool pshared, bool recursive) +#ifdef HAVE_PTHREAD_MUTEX_ROBUST +/* + * A robust mutex whose owner died while holding it comes back as EOWNERDEAD, + * with the caller holding it: make it consistent, which takes it over; failing + * that, a give_up caller unlocks it, leaving it ENOTRECOVERABLE, not leaked. + */ +static int mutex_take_over(struct mutex *mtx, int err, bool give_up) +{ + if (err != EOWNERDEAD) + return err; + + pr_debug("mutex: taking over one left by a dead owner\n"); + err = pthread_mutex_consistent(&mtx->lock); + if (err && give_up) + CHECK_ERR(pthread_mutex_unlock(&mtx->lock)); + + return err; +} +#else +#define mutex_take_over(mtx, err, give_up) ((void)(mtx), (void)(give_up), (err)) +#endif + +static bool __mutex_init(struct mutex *mtx, bool pshared, bool recursive, bool robust) { pthread_mutexattr_t attr; + bool ok = false, robust_unavailable = false; + int err; - CHECK_ERR(pthread_mutexattr_init(&attr)); + err = pthread_mutexattr_init(&attr); + if (err) + goto out; #ifndef NDEBUG /* In normal builds enable error checking, such as recursive usage. */ - CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK)); + err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if (err) + goto out_destroy; #endif - if (recursive) - CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)); - if (pshared) - CHECK_ERR(pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)); - CHECK_ERR(pthread_mutex_init(&mtx->lock, &attr)); + if (recursive) { + err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (err) + goto out_destroy; + } + if (pshared) { + err = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + if (err) + goto out_destroy; + } + if (robust) { +#ifdef HAVE_PTHREAD_MUTEX_ROBUST + err = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); + if (err) + robust_unavailable = true; +#else + robust_unavailable = true; +#endif + if (robust_unavailable) + goto out_destroy; + } + + err = pthread_mutex_init(&mtx->lock, &attr); + if (!err) + ok = true; + else if (robust && pshared && err == ENOTSUP) + /* + * glibc refuses a robust process shared mutex with ENOTSUP when + * set_robust_list(2) isn't usable, i.e. when the machine provides + * no robust mutexes for the process shared case: an unavailable + * API, not a bad request. + */ + robust_unavailable = true; +out_destroy: CHECK_ERR(pthread_mutexattr_destroy(&attr)); +out: + if (robust_unavailable) + pr_debug("mutex: robust mutexes aren't available\n"); + else if (err) + CHECK_ERR(err); + + return ok; } void mutex_init(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/false, /*recursive=*/false); + __mutex_init(mtx, /*pshared=*/false, /*recursive=*/false, /*robust=*/false); } void mutex_init_pshared(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false); + __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/false); } void mutex_init_recursive(struct mutex *mtx) { - __mutex_init(mtx, /*pshared=*/false, /*recursive=*/true); + __mutex_init(mtx, /*pshared=*/false, /*recursive=*/true, /*robust=*/false); +} + +bool mutex_init_pshared_robust(struct mutex *mtx) +{ + return __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/true); } void mutex_destroy(struct mutex *mtx) @@ -58,7 +127,8 @@ void mutex_destroy(struct mutex *mtx) void mutex_lock(struct mutex *mtx) NO_THREAD_SAFETY_ANALYSIS { - CHECK_ERR(pthread_mutex_lock(&mtx->lock)); + CHECK_ERR(mutex_take_over(mtx, pthread_mutex_lock(&mtx->lock), + /*give_up=*/false)); } void mutex_unlock(struct mutex *mtx) @@ -69,7 +139,8 @@ void mutex_unlock(struct mutex *mtx) bool mutex_trylock(struct mutex *mtx) { - int ret = pthread_mutex_trylock(&mtx->lock); + int ret = mutex_take_over(mtx, pthread_mutex_trylock(&mtx->lock), + /*give_up=*/true); if (ret == 0) return true; /* Lock acquired. */ @@ -111,7 +182,9 @@ void cond_destroy(struct cond *cnd) void cond_wait(struct cond *cnd, struct mutex *mtx) { - CHECK_ERR(pthread_cond_wait(&cnd->cond, &mtx->lock)); + CHECK_ERR(mutex_take_over(mtx, + pthread_cond_wait(&cnd->cond, &mtx->lock), + /*give_up=*/false)); } void cond_signal(struct cond *cnd) diff --git a/tools/perf/util/mutex.h b/tools/perf/util/mutex.h index 555493767588..5de1065ea2b9 100644 --- a/tools/perf/util/mutex.h +++ b/tools/perf/util/mutex.h @@ -142,6 +142,12 @@ void mutex_init(struct mutex *mtx); void mutex_init_pshared(struct mutex *mtx); /* Initializes a mutex that may be recursively held on the same thread. */ void mutex_init_recursive(struct mutex *mtx); +/* + * As mutex_init_pshared(), plus the robust attribute: a process dying while + * holding it doesn't leave it locked, but the takeover isn't reported and the + * data it protects may be mid update. Returns false when not available. + */ +bool mutex_init_pshared_robust(struct mutex *mtx); void mutex_destroy(struct mutex *mtx); void mutex_lock(struct mutex *mtx) EXCLUSIVE_LOCK_FUNCTION(*mtx); diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 2f3317ce17f0..f3ba70d4f76b 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 +#include <errno.h> #include <linux/hw_breakpoint.h> #include <linux/err.h> #include <linux/list_sort.h> #include <linux/zalloc.h> #include <dirent.h> -#include <errno.h> #include <sys/ioctl.h> #include <sys/param.h> #include "cpumap.h" diff --git a/tools/perf/util/pfm.c b/tools/perf/util/pfm.c index f80d6b0df47a..ad9bc481da70 100644 --- a/tools/perf/util/pfm.c +++ b/tools/perf/util/pfm.c @@ -141,6 +141,7 @@ static bool is_libpfm_event_supported(const char *name, struct perf_cpu_map *cpu return false; evsel->is_libpfm_event = true; + evsel->support_probe = true; ret = evsel__open(evsel, cpus, threads); if (ret == -EACCES) { diff --git a/tools/perf/util/print-events.c b/tools/perf/util/print-events.c index 0242243681b6..77428d9e73c4 100644 --- a/tools/perf/util/print-events.c +++ b/tools/perf/util/print-events.c @@ -147,6 +147,7 @@ bool is_event_supported(u8 type, u64 config) evsel = evsel__new(&attr); if (evsel) { + evsel->support_probe = true; ret = evsel__open(evsel, NULL, tmap) >= 0; if (!ret) { diff --git a/tools/perf/util/print_insn.h b/tools/perf/util/print_insn.h index cefa5c5f246e..3de9438af9cd 100644 --- a/tools/perf/util/print_insn.h +++ b/tools/perf/util/print_insn.h @@ -4,6 +4,7 @@ #include <stddef.h> #include <stdio.h> +#include <sys/types.h> #include <linux/types.h> diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 4eb17bdc5fd1..60dbe2dad5eb 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -3824,10 +3824,12 @@ static PyObject *pyrf__parse_events(PyObject *self, PyObject *args) perf_evlist__set_maps(evlist__core(evlist), cpus, threads); if (parse_events(evlist, input, &err)) { parse_events_error__print(&err, input); + parse_events_error__exit(&err); PyErr_SetFromErrno(PyExc_OSError); evlist__put(evlist); return NULL; } + parse_events_error__exit(&err); result = pyrf_evlist__from_evlist(evlist); evlist__put(evlist); return result; diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index a8d16106f619..23951c35fb66 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -181,9 +181,9 @@ static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int * break; case A2L_STYLE_UNKNOWN: default: - break; + return 0; } - if (ret > 0) + if (ret >= 0) return ret; } @@ -250,6 +250,10 @@ static struct inline_node *addr2inlines(const char *dso_name, u64 addr, addr2line(dso_name, addr, /*file=*/NULL, /*line_nr=*/NULL, dso, /*unwind_inlines=*/true, node, sym); + if (!dso__has_srcline(dso) && list_empty(&node->val)) { + inline_node__delete(node); + return NULL; + } return node; } @@ -276,8 +280,8 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, &line, dso, - unwind_inlines, /*node=*/NULL, sym)) { + if (addr2line(dso_name, addr, &file, &line, dso, + unwind_inlines, /*node=*/NULL, sym) <= 0) { free(dso_name); goto out_err; } @@ -302,6 +306,7 @@ out_err: dso__set_has_srcline(dso, false); dso__free_a2l(dso); dso__free_a2l_libbfd(dso); + dso__free_libdw(dso); } mutex_unlock(dso__lock(dso)); out: @@ -331,8 +336,8 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, - /*node=*/NULL, /*sym=*/NULL)) { + if (addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, + /*node=*/NULL, /*sym=*/NULL) <= 0) { free(dso_name); goto out_err; } @@ -350,6 +355,7 @@ out_err: dso__set_has_srcline(dso, false); dso__free_a2l(dso); dso__free_a2l_libbfd(dso); + dso__free_libdw(dso); } mutex_unlock(dso__lock(dso)); @@ -448,6 +454,9 @@ struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr, char *dso_name; struct inline_node *node; + if (!dso__has_srcline(dso)) + return NULL; + dso_name = srcline_dso_name(dso); if (dso_name == NULL) return NULL; diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index e955c3feddcd..1e2716c6af45 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -228,7 +228,8 @@ bool filename__has_section(const char *filename, const char *sec) if (gelf_getehdr(elf, &ehdr) == NULL) goto elf_out; - found = !!elf_section_by_name(elf, &ehdr, &shdr, sec, NULL); + found = !!elf_section_by_name(elf, &ehdr, &shdr, sec, NULL) && + shdr.sh_type == SHT_PROGBITS; elf_out: elf_end(elf); diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 5d98888d068c..b61a574ce9a3 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -967,23 +967,6 @@ static int maps__split_kallsyms_for_kcore(struct maps *kmaps, struct dso *dso) return count; } -static uint16_t machine_or_dso_e_machine(struct machine *machine, struct dso *dso) -{ - uint16_t e_machine = EM_NONE; - /* DSO should be most accurate */ - if (dso) - e_machine = dso__e_machine(dso, machine, /*e_flags=*/NULL); - - if (e_machine != EM_NONE) - return e_machine; - - /* Check the global environment next. */ - if (machine && machine->env && machine->env->e_machine != EM_NONE) - return machine->env->e_machine; - - return perf_env__e_machine(machine ? machine->env : NULL, /*e_flags=*/NULL); -} - /* * Split the symbols into maps, making sure there are no overlaps, i.e. the * kernel range is broken in several maps, named [kernel].N, as we don't have @@ -1005,7 +988,7 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta, return -1; machine = maps__machine(kmaps); - e_machine = machine_or_dso_e_machine(machine, dso); + e_machine = perf_env__e_machine(machine->env, /*e_flags=*/NULL); while (next) { char *module; @@ -1099,6 +1082,13 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta, return -1; dso__set_kernel(ndso, dso__kernel(dso)); + if (dso__kernel(dso) == DSO_SPACE__KERNEL_GUEST) { + dso__set_binary_type(ndso, DSO_BINARY_TYPE__GUEST_KALLSYMS); + dso__set_symtab_type(ndso, DSO_BINARY_TYPE__GUEST_KALLSYMS); + } else { + dso__set_binary_type(ndso, DSO_BINARY_TYPE__KALLSYMS); + dso__set_symtab_type(ndso, DSO_BINARY_TYPE__KALLSYMS); + } dso__set_loaded(ndso); curr_map = map__new2(pos->start, ndso); @@ -1560,7 +1550,7 @@ static int dso__load_kcore(struct dso *dso, struct map *map, free(new_node); } - if (machine_or_dso_e_machine(machine, dso) == EM_X86_64) { + if (perf_env__e_machine(machine->env, /*e_flags=*/NULL) == EM_X86_64) { u64 addr; /* @@ -1848,7 +1838,7 @@ int dso__load(struct dso *dso, struct map *map) ret = dso__load_guest_kernel_sym(dso, map); machine = maps__machine(map__kmaps(map)); - if (machine && machine_or_dso_e_machine(machine, dso) == EM_X86_64) + if (perf_env__e_machine(machine->env, /*e_flags=*/NULL) == EM_X86_64) machine__map_x86_64_entry_trampolines(machine, dso); goto out; } @@ -1857,6 +1847,7 @@ int dso__load(struct dso *dso, struct map *map) if (perfmap) { ret = dso__load_perf_map(map_path, dso); + dso__set_binary_type(dso, DSO_BINARY_TYPE__JAVA_JIT); dso__set_symtab_type(dso, ret > 0 ? DSO_BINARY_TYPE__JAVA_JIT : DSO_BINARY_TYPE__NOT_FOUND); @@ -1930,9 +1921,10 @@ int dso__load(struct dso *dso, struct map *map) if (nsexit) nsinfo__mountns_enter(dso__nsinfo(dso), &nsc); - if (bfdrc == 0) { - ret = 0; - break; + if (bfdrc > 0) { + dso__set_symtab_type(dso, symtab_type); + ret = 1; + goto out_destroy; } if (!is_reg || sirc < 0) @@ -1952,19 +1944,6 @@ int dso__load(struct dso *dso, struct map *map) if (next_slot) { ss_pos++; - - /* - * The binary type is used to find the file containing - * the executed instructions, so prefer the types that - * refer to the actual object over debug-only files such - * as DSO_BINARY_TYPE__DEBUGLINK. - */ - if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND || - symtab_type == DSO_BINARY_TYPE__BUILD_ID_CACHE || - (symtab_type == DSO_BINARY_TYPE__SYSTEM_PATH_DSO && - dso__binary_type(dso) != DSO_BINARY_TYPE__BUILD_ID_CACHE)) - dso__set_binary_type(dso, symtab_type); - if (syms_ss && runtime_ss) break; } else { @@ -1997,6 +1976,7 @@ int dso__load(struct dso *dso, struct map *map) ret += nr_plt; } +out_destroy: for (; ss_pos > 0; ss_pos--) symsrc__destroy(&ss_[ss_pos - 1]); out_free: @@ -2079,7 +2059,7 @@ int dso__load_vmlinux_path(struct dso *dso, struct map *map) if (filename != NULL) { err = dso__load_vmlinux(dso, map, filename, true); if (err > 0) - goto out; + dso__set_symtab_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); } out: return err; @@ -2235,8 +2215,10 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map) filename = __dso__build_id_filename(dso, NULL, 0, false, false); if (filename != NULL) { err = dso__load_vmlinux(dso, map, filename, true); - if (err > 0) + if (err > 0) { + dso__set_symtab_type(dso, DSO_BINARY_TYPE__BUILD_ID_CACHE); return err; + } } if (!symbol_conf.ignore_vmlinux && vmlinux_path != NULL) { |
