summaryrefslogtreecommitdiff
path: root/tools/perf
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf')
-rw-r--r--tools/perf/Documentation/perf-bench.txt38
-rw-r--r--tools/perf/Documentation/perf-test.txt36
-rw-r--r--tools/perf/Makefile5
-rw-r--r--tools/perf/Makefile.config4
-rw-r--r--tools/perf/Makefile.perf3
-rw-r--r--tools/perf/bench/Build1
-rw-r--r--tools/perf/bench/atomic.c299
-rw-r--r--tools/perf/bench/bench.h2
-rw-r--r--tools/perf/builtin-bench.c9
-rw-r--r--tools/perf/builtin-c2c.c16
-rw-r--r--tools/perf/builtin-kvm.c2
-rw-r--r--tools/perf/builtin-lock.c61
-rwxr-xr-xtools/perf/pmu-events/jevents.py4
-rwxr-xr-xtools/perf/python/treport.py5
-rwxr-xr-xtools/perf/python/ttimechart.py5
-rwxr-xr-xtools/perf/scripts/sync-headers.sh1676
-rw-r--r--tools/perf/tests/Build1
-rw-r--r--tools/perf/tests/backward-ring-buffer.c2
-rw-r--r--tools/perf/tests/builtin-test.c276
-rw-r--r--tools/perf/tests/code-reading.c2
-rw-r--r--tools/perf/tests/dso-data.c7
-rw-r--r--tools/perf/tests/event_refresh.c162
-rw-r--r--tools/perf/tests/keep-tracking.c2
-rw-r--r--tools/perf/tests/mmap-basic.c189
-rw-r--r--tools/perf/tests/openat-syscall-tp-fields.c1
-rw-r--r--tools/perf/tests/pe-file-parsing.c22
-rw-r--r--tools/perf/tests/perf-record.c1
-rw-r--r--tools/perf/tests/perf-time-to-tsc.c1
-rw-r--r--tools/perf/tests/pmu-events.c5
-rw-r--r--tools/perf/tests/shell/attr/README10
-rw-r--r--tools/perf/tests/shell/attr/base-stat-group45
-rw-r--r--tools/perf/tests/shell/attr/test-record-graph-dwarf2
-rw-r--r--tools/perf/tests/shell/attr/test-record-group-sampling5
-rw-r--r--tools/perf/tests/shell/attr/test-record-group-sampling15
-rw-r--r--tools/perf/tests/shell/attr/test-record-group-sampling25
-rw-r--r--tools/perf/tests/shell/attr/test-record-group13
-rw-r--r--tools/perf/tests/shell/attr/test-record-group23
-rw-r--r--tools/perf/tests/shell/attr/test-record-group33
-rw-r--r--tools/perf/tests/shell/attr/test-stat-C01
-rw-r--r--tools/perf/tests/shell/attr/test-stat-basic1
-rw-r--r--tools/perf/tests/shell/attr/test-stat-default91
-rw-r--r--tools/perf/tests/shell/attr/test-stat-detailed-199
-rw-r--r--tools/perf/tests/shell/attr/test-stat-detailed-2111
-rw-r--r--tools/perf/tests/shell/attr/test-stat-detailed-3115
-rw-r--r--tools/perf/tests/shell/attr/test-stat-group13
-rw-r--r--tools/perf/tests/shell/attr/test-stat-no-inherit1
-rwxr-xr-xtools/perf/tests/shell/drm_pmu.sh2
-rwxr-xr-xtools/perf/tests/shell/ftrace.sh2
-rwxr-xr-xtools/perf/tests/shell/kallsyms.sh2
-rw-r--r--tools/perf/tests/shell/lib/attr.py461
-rwxr-xr-xtools/perf/tests/shell/list.sh2
-rwxr-xr-xtools/perf/tests/shell/perf_sched_stats.sh2
-rwxr-xr-xtools/perf/tests/shell/probe_vfs_getname.sh2
-rwxr-xr-xtools/perf/tests/shell/python-use.sh2
-rwxr-xr-xtools/perf/tests/shell/schedstat_snapshots.sh2
-rwxr-xr-xtools/perf/tests/shell/stat+csv_output.sh2
-rwxr-xr-xtools/perf/tests/shell/stat+csv_summary.sh2
-rwxr-xr-xtools/perf/tests/shell/stat+event_uniquifying.sh2
-rwxr-xr-xtools/perf/tests/shell/stat+json_output.sh2
-rwxr-xr-xtools/perf/tests/shell/stat+shadow_stat.sh2
-rwxr-xr-xtools/perf/tests/shell/stat+std_output.sh2
-rwxr-xr-xtools/perf/tests/shell/stat.sh2
-rwxr-xr-xtools/perf/tests/shell/stat_all_metricgroups.sh2
-rwxr-xr-xtools/perf/tests/shell/stat_all_metrics.sh2
-rwxr-xr-xtools/perf/tests/shell/stat_all_pfm.sh2
-rwxr-xr-xtools/perf/tests/shell/stat_bpf_counters_cgrp.sh2
-rwxr-xr-xtools/perf/tests/shell/stat_metrics_cgrp.sh2
-rwxr-xr-xtools/perf/tests/shell/stat_metrics_values.sh2
-rwxr-xr-xtools/perf/tests/shell/test_ilist_python.sh2
-rwxr-xr-xtools/perf/tests/shell/test_stat_cpi_python.sh2
-rwxr-xr-xtools/perf/tests/shell/test_test_junit_output.sh5
-rwxr-xr-xtools/perf/tests/shell/test_uprobe_from_different_cu.sh2
-rw-r--r--tools/perf/tests/sw-clock.c2
-rw-r--r--tools/perf/tests/task-exit.c1
-rw-r--r--tools/perf/tests/tests-scripts.c85
-rw-r--r--tools/perf/tests/tests.h18
-rw-r--r--tools/perf/trace/beauty/include/linux/socket.h11
-rw-r--r--tools/perf/trace/beauty/include/uapi/linux/fcntl.h1
-rw-r--r--tools/perf/trace/beauty/include/uapi/linux/fs.h1
-rw-r--r--tools/perf/trace/beauty/include/uapi/linux/mount.h1
-rw-r--r--tools/perf/trace/beauty/include/uapi/sound/asound.h200
-rw-r--r--tools/perf/util/addr2line.c28
-rw-r--r--tools/perf/util/annotate-arch/annotate-arm64.c774
-rw-r--r--tools/perf/util/annotate-arch/annotate-x86.c38
-rw-r--r--tools/perf/util/annotate-data.c209
-rw-r--r--tools/perf/util/annotate-data.h14
-rw-r--r--tools/perf/util/auxtrace.c2
-rw-r--r--tools/perf/util/auxtrace.h1
-rw-r--r--tools/perf/util/cs-etm-decoder/cs-etm-decoder.c12
-rw-r--r--tools/perf/util/cs-etm-decoder/cs-etm-decoder.h2
-rw-r--r--tools/perf/util/cs-etm.c184
-rw-r--r--tools/perf/util/debug.h5
-rw-r--r--tools/perf/util/disasm.c124
-rw-r--r--tools/perf/util/dso.c145
-rw-r--r--tools/perf/util/dso.h15
-rw-r--r--tools/perf/util/evsel.c42
-rw-r--r--tools/perf/util/evsel.h1
-rw-r--r--tools/perf/util/hist.c6
-rw-r--r--tools/perf/util/libbfd.c8
-rw-r--r--tools/perf/util/libdw.c25
-rw-r--r--tools/perf/util/libdw.h5
-rw-r--r--tools/perf/util/llvm-c-helpers.cpp66
-rw-r--r--tools/perf/util/llvm.c12
-rw-r--r--tools/perf/util/machine.c2
-rw-r--r--tools/perf/util/mmap.c3
-rw-r--r--tools/perf/util/mutex.c101
-rw-r--r--tools/perf/util/mutex.h6
-rw-r--r--tools/perf/util/parse-events.c2
-rw-r--r--tools/perf/util/pfm.c1
-rw-r--r--tools/perf/util/print-events.c1
-rw-r--r--tools/perf/util/print_insn.h1
-rw-r--r--tools/perf/util/python.c2
-rw-r--r--tools/perf/util/srcline.c21
-rw-r--r--tools/perf/util/symbol-elf.c3
-rw-r--r--tools/perf/util/symbol.c58
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, &ltime)) {
strftime(date, sizeof(date), "%H:%M:%S", &ltime);
- 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, &reg_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, &reg_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, &reg_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) {