summaryrefslogtreecommitdiff
path: root/tools/perf
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-07 13:24:48 +0100
committerMark Brown <broonie@kernel.org>2026-09-07 13:24:48 +0100
commit6b2e352659c615bf909340d3c9d2c34c4be731a4 (patch)
tree0897714d4d4052fe0a812bf52b8cee18a4b849bc /tools/perf
parentefcdb1714bf28590e8b192b212e385858ddcf0c3 (diff)
parent02f6847e1822714a4201b87e42f92b0d43e8549d (diff)
downloadlinux-next-6b2e352659c615bf909340d3c9d2c34c4be731a4.tar.gz
linux-next-6b2e352659c615bf909340d3c9d2c34c4be731a4.zip
Merge branch 'perf-tools-next' of https://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools-next.git
Diffstat (limited to 'tools/perf')
-rw-r--r--tools/perf/Documentation/perf-annotate.txt3
-rw-r--r--tools/perf/Documentation/perf-record.txt8
-rw-r--r--tools/perf/Documentation/perf-report.txt6
-rw-r--r--tools/perf/Documentation/perf-top.txt6
-rw-r--r--tools/perf/arch/x86/util/pmu.c208
-rw-r--r--tools/perf/builtin-annotate.c3
-rw-r--r--tools/perf/builtin-report.c3
-rw-r--r--tools/perf/builtin-top.c2
-rw-r--r--tools/perf/builtin-trace.c248
-rw-r--r--tools/perf/tests/sample-parsing.c109
-rwxr-xr-xtools/perf/tests/shell/annotate_weight.sh63
-rwxr-xr-xtools/perf/tests/shell/trace_ksym_beautifier.sh38
-rw-r--r--tools/perf/trace/beauty/beauty.h3
-rw-r--r--tools/perf/ui/browsers/annotate.c20
-rw-r--r--tools/perf/util/annotate.c121
-rw-r--r--tools/perf/util/annotate.h44
-rw-r--r--tools/perf/util/evsel.c20
-rw-r--r--tools/perf/util/genelf_debug.c28
-rw-r--r--tools/perf/util/jitdump.c204
-rw-r--r--tools/perf/util/symbol_conf.h13
20 files changed, 939 insertions, 211 deletions
diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt
index a688738809c4..1a90b09a12d5 100644
--- a/tools/perf/Documentation/perf-annotate.txt
+++ b/tools/perf/Documentation/perf-annotate.txt
@@ -95,6 +95,9 @@ include::itrace.txt[]
--gtk:: Use the GTK interface.
+--weights:: Show or hide (with `--no-weights`) weight columns in annotation output.
+ By default, weight columns are shown when samples contain non-zero weights.
+
-C::
--cpu=<cpu>:: Only report samples for the list of CPUs provided. Multiple CPUs can
be provided as a comma-separated list with no space: 0,1. Ranges of
diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt
index 178f483140ed..2c6510c5a87b 100644
--- a/tools/perf/Documentation/perf-record.txt
+++ b/tools/perf/Documentation/perf-record.txt
@@ -480,9 +480,11 @@ Note that this feature may not be available on all processors.
-W::
--weight::
-Enable weightened sampling. An additional weight is recorded per sample and can be
-displayed with the weight and local_weight sort keys. This currently works for TSX
-abort events and some memory events in precise mode on modern Intel CPUs.
+Enable weighted sampling. One or more additional weights can be recorded per sample
+and can be displayed with the weight, weight2, weight3 and local_weight sort keys.
+Possible uses for weights are memory or instruction retirement latencies.
+Support depends on the PMU and the event, as well as its configuration. Typically
+it requires use of 'p'.
--namespaces::
Record events of type PERF_RECORD_NAMESPACES. This enables 'cgroup_id' sort key.
diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt
index 22f87eaa3279..ae68ca402d0b 100644
--- a/tools/perf/Documentation/perf-report.txt
+++ b/tools/perf/Documentation/perf-report.txt
@@ -64,6 +64,9 @@ OPTIONS
--symbol-filter=::
Only show symbols that match (partially) with this filter.
+--weights:: Show or hide (with `--no-weights`) weight columns in annotation output.
+ By default, weight columns are shown when samples contain non-zero weights.
+
-U::
--hide-unresolved::
Only display entries resolved to a symbol.
@@ -353,6 +356,9 @@ OPTIONS
--gtk:: Use the GTK2 interface.
+--weights:: Show or hide (with `--no-weights`) weight columns in annotation output.
+ By default, weight columns are shown when samples contain non-zero weights.
+
-k::
--vmlinux=<file>::
vmlinux pathname
diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt
index af3e4230c72f..2da2a16bbf26 100644
--- a/tools/perf/Documentation/perf-top.txt
+++ b/tools/perf/Documentation/perf-top.txt
@@ -25,6 +25,12 @@ OPTIONS
--count=<count>::
Event period to sample.
+-W::
+--weight::
+Enable weighted sampling. One or more additional weights can be recorded per sample
+and can be displayed with the weight, weight2, weight3 and local_weight sort keys.
+Possible uses for weights are memory or instruction retirement latencies.
+
-C <cpu-list>::
--cpu=<cpu>::
Monitor only on the list of CPUs provided. Multiple CPUs can be provided as a
diff --git a/tools/perf/arch/x86/util/pmu.c b/tools/perf/arch/x86/util/pmu.c
index 7c9d238922a6..2c24ef3140da 100644
--- a/tools/perf/arch/x86/util/pmu.c
+++ b/tools/perf/arch/x86/util/pmu.c
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
+#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
@@ -22,20 +23,35 @@
#include "util/env.h"
#include "util/header.h"
-static bool x86__is_intel_graniterapids(void)
-{
- static bool checked_if_graniterapids;
- static bool is_graniterapids;
+#define GENUINE_INTEL_SPR "GenuineIntel-6-8F"
+#define GENUINE_INTEL_EMR "GenuineIntel-6-CF"
+#define GENUINE_INTEL_GNR "GenuineIntel-6-A[DE]"
- if (!checked_if_graniterapids) {
- const char *graniterapids_cpuid = "GenuineIntel-6-A[DE]";
- char *cpuid = get_cpuid_str((struct perf_cpu){0});
+static bool cached_snc_supported;
+static pthread_once_t snc_support_once = PTHREAD_ONCE_INIT;
- is_graniterapids = cpuid && strcmp_cpuid_str(graniterapids_cpuid, cpuid) == 0;
- free(cpuid);
- checked_if_graniterapids = true;
+static void init_snc_support(void)
+{
+ /* Sapphirerapids Emeraldrapids Graniterapids support SNC configuration. */
+ static const char *const supported_cpuids[] = {
+ GENUINE_INTEL_SPR, /* Sapphirerapids */
+ GENUINE_INTEL_EMR, /* Emeraldrapids */
+ GENUINE_INTEL_GNR, /* Graniterapids */
+ };
+ char *cpuid = get_cpuid_str((struct perf_cpu){0});
+
+ for (size_t i = 0; i < ARRAY_SIZE(supported_cpuids); i++) {
+ cached_snc_supported = cpuid && strcmp_cpuid_str(supported_cpuids[i], cpuid) == 0;
+ if (cached_snc_supported)
+ break;
}
- return is_graniterapids;
+ free(cpuid);
+}
+
+static bool x86__is_snc_supported(void)
+{
+ pthread_once(&snc_support_once, init_snc_support);
+ return cached_snc_supported;
}
static struct perf_cpu_map *read_sysfs_cpu_map(const char *sysfs_path)
@@ -52,49 +68,58 @@ static struct perf_cpu_map *read_sysfs_cpu_map(const char *sysfs_path)
return cpus;
}
-static int snc_nodes_per_l3_cache(void)
+static int cached_snc_nodes;
+static pthread_once_t snc_nodes_once = PTHREAD_ONCE_INIT;
+
+static void init_snc_nodes(void)
{
- static bool checked_snc;
- static int snc_nodes;
-
- if (!checked_snc) {
- struct perf_cpu_map *node_cpus =
- read_sysfs_cpu_map("devices/system/node/node0/cpulist");
- struct perf_cpu_map *cache_cpus =
- read_sysfs_cpu_map("devices/system/cpu/cpu0/cache/index3/shared_cpu_list");
-
- snc_nodes = perf_cpu_map__nr(cache_cpus) / perf_cpu_map__nr(node_cpus);
- perf_cpu_map__put(cache_cpus);
- perf_cpu_map__put(node_cpus);
- checked_snc = true;
- }
- return snc_nodes;
+ struct perf_cpu_map *node_cpus =
+ read_sysfs_cpu_map("devices/system/node/node0/cpulist");
+ struct perf_cpu_map *cache_cpus =
+ read_sysfs_cpu_map("devices/system/cpu/cpu0/cache/index3/shared_cpu_list");
+
+ if (node_cpus && cache_cpus)
+ cached_snc_nodes = perf_cpu_map__nr(cache_cpus) / perf_cpu_map__nr(node_cpus);
+ else
+ cached_snc_nodes = 0;
+ perf_cpu_map__put(cache_cpus);
+ perf_cpu_map__put(node_cpus);
}
-static int num_chas(void)
+static int snc_nodes_per_l3_cache(void)
{
- static bool checked_chas;
- static int num_chas;
+ pthread_once(&snc_nodes_once, init_snc_nodes);
+ return cached_snc_nodes;
+}
- if (!checked_chas) {
- int fd = perf_pmu__event_source_devices_fd();
- struct io_dir dir;
- struct io_dirent64 *dent;
+static int cached_num_chas;
+static pthread_once_t num_chas_once = PTHREAD_ONCE_INIT;
- if (fd < 0)
- return -1;
+static void init_num_chas(void)
+{
+ int fd = perf_pmu__event_source_devices_fd();
+ struct io_dir dir;
+ struct io_dirent64 *dent;
- io_dir__init(&dir, fd);
+ if (fd < 0) {
+ cached_num_chas = -1;
+ return;
+ }
- while ((dent = io_dir__readdir(&dir)) != NULL) {
- /* Note, dent->d_type will be DT_LNK and so isn't a useful filter. */
- if (strstarts(dent->d_name, "uncore_cha_"))
- num_chas++;
- }
- close(fd);
- checked_chas = true;
+ io_dir__init(&dir, fd);
+
+ while ((dent = io_dir__readdir(&dir)) != NULL) {
+ /* Note, dent->d_type will be DT_LNK and so isn't a useful filter. */
+ if (strstarts(dent->d_name, "uncore_cha_"))
+ cached_num_chas++;
}
- return num_chas;
+ close(fd);
+}
+
+static int num_chas(void)
+{
+ pthread_once(&num_chas_once, init_num_chas);
+ return cached_num_chas;
}
#define MAX_SNCS 6
@@ -121,46 +146,78 @@ static int uncore_cha_snc(struct perf_pmu *pmu)
return 0;
}
chas_per_node = num_cha / snc_nodes;
+ if (chas_per_node == 0) {
+ pr_warning("Unexpected: chas_per_node is 0 (num_cha=%d, snc_nodes=%d)\n",
+ num_cha, snc_nodes);
+ return 0;
+ }
cha_snc = cha_num / chas_per_node;
/* Range check cha_snc. for unexpected out of bounds. */
return cha_snc >= MAX_SNCS ? 0 : cha_snc;
}
-static int uncore_imc_snc(struct perf_pmu *pmu)
+static const u8 *cached_imc_snc_map;
+static size_t cached_imc_snc_map_len;
+static pthread_once_t imc_snc_map_once = PTHREAD_ONCE_INIT;
+
+static void init_snc_map(void)
{
- // Compute the IMC SNC using lookup tables.
- unsigned int imc_num;
int snc_nodes = snc_nodes_per_l3_cache();
- const u8 snc2_map[] = {1, 1, 0, 0, 1, 1, 0, 0};
- const u8 snc3_map[] = {1, 1, 0, 0, 2, 2, 1, 1, 0, 0, 2, 2};
- const u8 *snc_map;
- size_t snc_map_len;
+ char *cpuid;
+ static const u8 spr_emr_snc2_map[] = { 0, 0, 1, 1 };
+ static const u8 gnr_snc2_map[] = { 1, 1, 0, 0 };
+ static const u8 snc3_map[] = { 1, 1, 0, 0, 2, 2 };
switch (snc_nodes) {
case 2:
- snc_map = snc2_map;
- snc_map_len = ARRAY_SIZE(snc2_map);
+ cpuid = get_cpuid_str((struct perf_cpu){ 0 });
+ if (cpuid) {
+ if (strcmp_cpuid_str(GENUINE_INTEL_SPR, cpuid) == 0 ||
+ strcmp_cpuid_str(GENUINE_INTEL_EMR, cpuid) == 0) {
+ cached_imc_snc_map = spr_emr_snc2_map;
+ cached_imc_snc_map_len = ARRAY_SIZE(spr_emr_snc2_map);
+ } else if (strcmp_cpuid_str(GENUINE_INTEL_GNR, cpuid) == 0) {
+ cached_imc_snc_map = gnr_snc2_map;
+ cached_imc_snc_map_len = ARRAY_SIZE(gnr_snc2_map);
+ }
+ free(cpuid);
+ }
break;
case 3:
- snc_map = snc3_map;
- snc_map_len = ARRAY_SIZE(snc3_map);
+ cached_imc_snc_map = snc3_map;
+ cached_imc_snc_map_len = ARRAY_SIZE(snc3_map);
break;
default:
/* Error or no lookup support for SNC with >3 nodes. */
- return 0;
+ break;
}
+ if (!cached_imc_snc_map)
+ pr_warning("Unexpected: can not find snc map config\n");
+}
+
+static int uncore_imc_snc(struct perf_pmu *pmu)
+{
+ // Compute the IMC SNC using lookup tables.
+ unsigned int imc_num;
+ int snc_nodes = snc_nodes_per_l3_cache();
+
+ if (snc_nodes <= 1)
+ return 0;
+
+ pthread_once(&imc_snc_map_once, init_snc_map);
+
/* Compute SNC for PMU. */
if (sscanf(pmu->name, "uncore_imc_%u", &imc_num) != 1) {
pr_warning("Unexpected: unable to compute IMC number '%s'\n", pmu->name);
return 0;
}
- if (imc_num >= snc_map_len) {
- pr_warning("Unexpected IMC %d for SNC%d mapping\n", imc_num, snc_nodes);
+
+ if (!cached_imc_snc_map)
return 0;
- }
- return snc_map[imc_num];
+
+ return cached_imc_snc_map[imc_num % cached_imc_snc_map_len];
}
static int uncore_cha_imc_compute_cpu_adjust(int pmu_snc)
@@ -200,7 +257,9 @@ static int uncore_cha_imc_compute_cpu_adjust(int pmu_snc)
return cpu_adjust[pmu_snc];
}
-static void gnr_uncore_cha_imc_adjust_cpumask_for_snc(struct perf_pmu *pmu, bool cha)
+static pthread_mutex_t pmu_adjust_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void uncore_cha_imc_adjust_cpumask_for_snc(struct perf_pmu *pmu, bool cha)
{
// With sub-NUMA clustering (SNC) there is a NUMA node per SNC in the
// topology. For example, a two socket graniterapids machine may be set
@@ -231,9 +290,11 @@ static void gnr_uncore_cha_imc_adjust_cpumask_for_snc(struct perf_pmu *pmu, bool
return;
}
+ pthread_mutex_lock(&pmu_adjust_mutex);
+
pmu_snc = cha ? uncore_cha_snc(pmu) : uncore_imc_snc(pmu);
if (pmu_snc == 0) {
- // No adjustment necessary for the first SNC.
+ pthread_mutex_unlock(&pmu_adjust_mutex);
return;
}
@@ -242,8 +303,10 @@ static void gnr_uncore_cha_imc_adjust_cpumask_for_snc(struct perf_pmu *pmu, bool
// Hold onto the perf_cpu_map globally to avoid recomputation.
cpu_adjust = uncore_cha_imc_compute_cpu_adjust(pmu_snc);
adjusted[pmu_snc] = perf_cpu_map__empty_new(perf_cpu_map__nr(pmu->cpus));
- if (!adjusted[pmu_snc])
+ if (!adjusted[pmu_snc]) {
+ pthread_mutex_unlock(&pmu_adjust_mutex);
return;
+ }
}
perf_cpu_map__for_each_cpu(cpu, idx, pmu->cpus) {
@@ -263,6 +326,8 @@ static void gnr_uncore_cha_imc_adjust_cpumask_for_snc(struct perf_pmu *pmu, bool
perf_cpu_map__put(pmu->cpus);
pmu->cpus = perf_cpu_map__get(adjusted[pmu_snc]);
+
+ pthread_mutex_unlock(&pmu_adjust_mutex);
}
void perf_pmu__arch_init(struct perf_pmu *pmu)
@@ -300,11 +365,16 @@ void perf_pmu__arch_init(struct perf_pmu *pmu)
pmu->mem_events = perf_mem_events_intel_aux;
else
pmu->mem_events = perf_mem_events_intel;
- } else if (x86__is_intel_graniterapids()) {
- if (strstarts(pmu->name, "uncore_cha_"))
- gnr_uncore_cha_imc_adjust_cpumask_for_snc(pmu, /*cha=*/true);
- else if (strstarts(pmu->name, "uncore_imc_"))
- gnr_uncore_cha_imc_adjust_cpumask_for_snc(pmu, /*cha=*/false);
+ } else if (x86__is_snc_supported()) {
+ int snc_nodes = snc_nodes_per_l3_cache();
+
+ if (snc_nodes == 2 || snc_nodes == 3) {
+ if (strstarts(pmu->name, "uncore_cha_"))
+ uncore_cha_imc_adjust_cpumask_for_snc(pmu, /*cha=*/true);
+ else if (strstarts(pmu->name, "uncore_imc_") &&
+ !strstarts(pmu->name, "uncore_imc_free_running"))
+ uncore_cha_imc_adjust_cpumask_for_snc(pmu, /*cha=*/false);
+ }
}
}
}
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 69cb72b2082a..9e0b704c31f4 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -719,6 +719,8 @@ int cmd_annotate(int argc, const char **argv)
OPT_BOOLEAN(0, "tui", &annotate.use_tui, "Use the TUI interface"),
#endif
OPT_BOOLEAN(0, "stdio", &annotate.use_stdio, "Use the stdio interface"),
+ OPT_BOOLEAN(0, "weights", &symbol_conf.annotate_weight,
+ "Show or hide weight columns in annotation. Default show if non zero."),
OPT_BOOLEAN(0, "stdio2", &annotate.use_stdio2, "Use the stdio interface"),
OPT_BOOLEAN(0, "ignore-vmlinux", &symbol_conf.ignore_vmlinux,
"don't load vmlinux even if found"),
@@ -788,6 +790,7 @@ int cmd_annotate(int argc, const char **argv)
set_option_flag(options, 0, "show-total-period", PARSE_OPT_EXCLUSIVE);
set_option_flag(options, 0, "show-nr-samples", PARSE_OPT_EXCLUSIVE);
+ symbol_conf.annotate_weight = true;
annotation_options__init();
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index 60d1f166629e..b55c01bb45b4 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -1364,6 +1364,8 @@ int cmd_report(int argc, const char **argv)
#endif
OPT_BOOLEAN(0, "stdio", &report.use_stdio,
"Use the stdio interface"),
+ OPT_BOOLEAN(0, "weights", &symbol_conf.annotate_weight,
+ "Show or hide weight columns in annotation. Default show if non-zero."),
OPT_BOOLEAN(0, "header", &report.header, "Show data header."),
OPT_BOOLEAN(0, "header-only", &report.header_only,
"Show only data header."),
@@ -1525,6 +1527,7 @@ int cmd_report(int argc, const char **argv)
* reference exited threads.
*/
symbol_conf.keep_exited_threads = true;
+ symbol_conf.annotate_weight = true;
annotation_options__init();
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index 570410599f1b..2888ac3b7b92 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -1493,6 +1493,8 @@ int cmd_top(int argc, const char **argv)
OPT_CALLBACK(0, "filter", &top.evlist, "filter",
"event filter", parse_filter),
OPT_U64('c', "count", &opts->user_interval, "event period to sample"),
+ OPT_BOOLEAN('W', "weight", &opts->sample_weight,
+ "sample by weight (on special events only)"),
OPT_STRING('p', "pid", &target->pid, "pid",
"profile events on existing process id"),
OPT_STRING('t', "tid", &target->tid, "tid",
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index c3c7f1f85c53..be19d70eba09 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -122,6 +122,7 @@ struct syscall_arg_fmt {
#ifdef HAVE_LIBBPF_SUPPORT
const struct btf_type *type;
int type_id; /* used in btf_dump */
+ bool btf_type_cached;
#endif
};
@@ -750,6 +751,35 @@ size_t syscall_arg__scnprintf_ptr(char *bf, size_t size, struct syscall_arg *arg
return syscall_arg__scnprintf_hex(bf, size, arg);
}
+size_t syscall_arg__scnprintf_ksym(char *bf, size_t size, struct syscall_arg *arg)
+{
+ if (arg->val == 0)
+ return scnprintf(bf, size, "NULL");
+
+ if (arg->trace && arg->trace->host) {
+ struct map *map = NULL;
+ struct symbol *sym = machine__find_kernel_symbol(arg->trace->host,
+ arg->val, &map);
+
+ if (sym) {
+ u64 start = map__unmap_ip(map, sym->start);
+ u64 offset = arg->val - start;
+ size_t printed;
+
+ if (offset == 0)
+ printed = scnprintf(bf, size, "%s", sym->name);
+ else
+ printed = scnprintf(bf, size, "%s+0x%" PRIx64,
+ sym->name, offset);
+ map__put(map);
+ return printed;
+ }
+ map__put(map);
+ }
+
+ return syscall_arg__scnprintf_hex(bf, size, arg);
+}
+
size_t syscall_arg__scnprintf_int(char *bf, size_t size, struct syscall_arg *arg)
{
return scnprintf(bf, size, "%d", arg->val);
@@ -950,21 +980,68 @@ static size_t syscall_arg__scnprintf_getrandom_flags(char *bf, size_t size,
#define SCA_GETRANDOM_FLAGS syscall_arg__scnprintf_getrandom_flags
#ifdef HAVE_LIBBPF_SUPPORT
-static void syscall_arg_fmt__cache_btf_enum(struct syscall_arg_fmt *arg_fmt, struct btf *btf, char *type)
+static bool btf_is_func_ptr(const struct btf *btf, const struct btf_type *type)
{
+ int nr_ptrs = 0;
+
+ while (type) {
+ if (btf_is_ptr(type)) {
+ if (++nr_ptrs > 1)
+ return false;
+ type = btf__type_by_id(btf, type->type);
+ } else if (btf_is_typedef(type) || btf_is_mod(type)) {
+ type = btf__type_by_id(btf, type->type);
+ } else {
+ break;
+ }
+ }
+ return nr_ptrs == 1 && type && btf_is_func_proto(type);
+}
+
+static void syscall_arg_fmt__cache_btf_type(struct syscall_arg_fmt *arg_fmt,
+ struct btf *btf, const char *type)
+{
+ char name[128];
+ const char *pos;
+ size_t len = 0;
int id;
- type = strstr(type, "enum ");
+ arg_fmt->btf_type_cached = true;
+
if (type == NULL)
return;
- type += 5; // skip "enum " to get the enumeration name
+ /* Pointers to enums are memory addresses, not scalar enums */
+ if (strstr(type, "enum ") && strchr(type, '*'))
+ return;
+
+ if ((pos = strstr(type, "enum ")) != NULL)
+ pos += 5;
+ else if ((pos = strstr(type, "struct ")) != NULL)
+ pos += 7;
+ else if ((pos = strstr(type, "union ")) != NULL)
+ pos += 6;
+ else
+ pos = type;
+
+ while (isspace(*pos))
+ pos++;
+
+ while ((isalnum(pos[len]) || pos[len] == '_') && len < sizeof(name) - 1) {
+ name[len] = pos[len];
+ len++;
+ }
+ name[len] = '\0';
- id = btf__find_by_name(btf, type);
+ if (len == 0)
+ return;
+
+ id = btf__find_by_name(btf, name);
if (id < 0)
return;
arg_fmt->type = btf__type_by_id(btf, id);
+ arg_fmt->type_id = id;
}
static bool syscall_arg__strtoul_btf_enum(char *bf, size_t size, struct syscall_arg *arg, u64 *val)
@@ -998,10 +1075,8 @@ static bool syscall_arg__strtoul_btf_type(char *bf, size_t size, struct syscall_
if (btf == NULL)
return false;
- if (arg->fmt->type == NULL) {
- // See if this is an enum
- syscall_arg_fmt__cache_btf_enum(arg->fmt, btf, type);
- }
+ if (!arg->fmt->btf_type_cached)
+ syscall_arg_fmt__cache_btf_type(arg->fmt, btf, type);
// Now let's see if we have a BTF type resolved
bt = arg->fmt->type;
@@ -1009,19 +1084,22 @@ static bool syscall_arg__strtoul_btf_type(char *bf, size_t size, struct syscall_
return false;
// If it is an enum:
- if (btf_is_enum(arg->fmt->type))
+ if (btf_is_enum(arg->fmt->type)) {
+ if (type && strchr(type, '*'))
+ return false;
return syscall_arg__strtoul_btf_enum(bf, size, arg, val);
+ }
return false;
}
-static size_t btf_enum_scnprintf(const struct btf_type *type, struct btf *btf, char *bf, size_t size, int val)
+static size_t btf_enum_scnprintf(const struct btf_type *type, struct btf *btf, char *bf, size_t size, unsigned long val)
{
struct btf_enum *be = btf_enum(type);
const unsigned int nr_entries = btf_vlen(type);
for (unsigned int i = 0; i < nr_entries; ++i, ++be) {
- if (be->val == val) {
+ if ((unsigned long)(__u32)be->val == val || (unsigned long)be->val == val) {
return scnprintf(bf, size, "%s",
btf__name_by_offset(btf, be->name_off));
}
@@ -1048,14 +1126,19 @@ static size_t btf_struct_scnprintf(const struct btf_type *type, struct btf *btf,
.bf = bf,
.size = size,
};
- struct augmented_arg *augmented_arg = arg->augmented.args;
+ struct augmented_arg *augmented_arg;
int type_id = arg->fmt->type_id, consumed;
struct btf_dump *btf_dump;
LIBBPF_OPTS(btf_dump_opts, dump_opts);
LIBBPF_OPTS(btf_dump_type_data_opts, dump_data_opts);
- if (arg == NULL || arg->augmented.args == NULL)
+ if (arg == NULL || arg->augmented.args == NULL || arg->augmented.size <= 0 ||
+ arg->fmt == NULL || !arg->fmt->from_user)
+ return 0;
+
+ augmented_arg = arg->augmented.args;
+ if (augmented_arg->size <= 0)
return 0;
dump_data_opts.compact = true;
@@ -1066,8 +1149,10 @@ static size_t btf_struct_scnprintf(const struct btf_type *type, struct btf *btf,
return 0;
/* pretty print the struct data here */
- if (btf_dump__dump_type_data(btf_dump, type_id, arg->augmented.args->value, type->size, &dump_data_opts) == 0)
+ if (btf_dump__dump_type_data(btf_dump, type_id, arg->augmented.args->value, type->size, &dump_data_opts) <= 0) {
+ btf_dump__free(btf_dump);
return 0;
+ }
consumed = sizeof(*augmented_arg) + augmented_arg->size;
arg->augmented.args = ((void *)arg->augmented.args) + consumed;
@@ -1079,33 +1164,40 @@ static size_t btf_struct_scnprintf(const struct btf_type *type, struct btf *btf,
}
static size_t trace__btf_scnprintf(struct trace *trace, struct syscall_arg *arg, char *bf,
- size_t size, int val, char *type)
+ size_t size, unsigned long val, char *type)
{
struct syscall_arg_fmt *arg_fmt = arg->fmt;
if (trace->btf == NULL)
return 0;
- if (arg_fmt->type == NULL) {
- // Check if this is an enum and if we have the BTF type for it.
- syscall_arg_fmt__cache_btf_enum(arg_fmt, trace->btf, type);
- }
+ if (!arg_fmt->btf_type_cached)
+ syscall_arg_fmt__cache_btf_type(arg_fmt, trace->btf, type);
// Did we manage to find a BTF type for the syscall/tracepoint argument?
if (arg_fmt->type == NULL)
return 0;
+ if (type && strchr(type, '*')) {
+ if (btf_is_enum(arg_fmt->type) || btf_is_func_ptr(trace->btf, arg_fmt->type))
+ return 0;
+ }
+
if (btf_is_enum(arg_fmt->type))
return btf_enum_scnprintf(arg_fmt->type, trace->btf, bf, size, val);
else if (btf_is_struct(arg_fmt->type) || btf_is_union(arg_fmt->type))
return btf_struct_scnprintf(arg_fmt->type, trace->btf, bf, size, arg);
+ else if (btf_is_func_ptr(trace->btf, arg_fmt->type)) {
+ arg->val = val;
+ return syscall_arg__scnprintf_ksym(bf, size, arg);
+ }
return 0;
}
#else // HAVE_LIBBPF_SUPPORT
static size_t trace__btf_scnprintf(struct trace *trace __maybe_unused, struct syscall_arg *arg __maybe_unused,
- char *bf __maybe_unused, size_t size __maybe_unused, int val __maybe_unused,
+ char *bf __maybe_unused, size_t size __maybe_unused, unsigned long val __maybe_unused,
char *type __maybe_unused)
{
return 0;
@@ -2067,6 +2159,18 @@ static int syscall__alloc_arg_fmts(struct syscall *sc, int nr_args)
}
static const struct syscall_arg_fmt syscall_arg_fmts__by_name[] = {
+ { .name = "action", .scnprintf = SCA_KSYM, },
+ { .name = "call_site", .scnprintf = SCA_KSYM, },
+ { .name = "callback", .scnprintf = SCA_KSYM, },
+ { .name = "caller", .scnprintf = SCA_KSYM, },
+ { .name = "caller_ip", .scnprintf = SCA_KSYM, },
+ { .name = "callsite", .scnprintf = SCA_KSYM, },
+ { .name = "cb", .scnprintf = SCA_KSYM, },
+ { .name = "fn", .scnprintf = SCA_KSYM, },
+ { .name = "func", .scnprintf = SCA_KSYM, },
+ { .name = "function", .scnprintf = SCA_KSYM, },
+ { .name = "handler", .scnprintf = SCA_KSYM, },
+ { .name = "location", .scnprintf = SCA_KSYM, },
{ .name = "msr", .scnprintf = SCA_X86_MSR, .strtoul = STUL_X86_MSR, },
{ .name = "vector", .scnprintf = SCA_X86_IRQ_VECTORS, .strtoul = STUL_X86_IRQ_VECTORS, },
};
@@ -2142,6 +2246,29 @@ static bool field_has_hex_fmt(struct tep_format_field *field, int len)
return false;
}
+static bool field_is_enum(const struct tep_format_field *field)
+{
+ return field->type && strstr(field->type, "enum") != NULL;
+}
+
+static bool field_is_plain_int(const struct tep_format_field *field)
+{
+ if (!field->type)
+ return false;
+
+ return !strcmp(field->type, "int") ||
+ !strcmp(field->type, "unsigned int") ||
+ !strcmp(field->type, "u32") ||
+ !strcmp(field->type, "s32");
+}
+
+static bool field_is_ptr_sized(const struct tep_format_field *field)
+{
+ int ptr_size = tep_get_long_size(field->event->tep);
+
+ return field->size == ptr_size || field->size == sizeof(u64);
+}
+
static struct tep_format_field *
syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field *field,
bool *use_btf)
@@ -2169,38 +2296,49 @@ syscall_arg_fmt__init_array(struct syscall_arg_fmt *arg, struct tep_format_field
((len >= 4 && strcmp(field->name + len - 4, "name") == 0) ||
strstr(field->name, "path") != NULL)) {
arg->scnprintf = SCA_FILENAME;
- } else if ((field->flags & TEP_FIELD_IS_POINTER) || strstr(field->name, "addr") ||
- field_has_hex_fmt(field, len))
- arg->scnprintf = SCA_PTR;
- else if (strcmp(field->type, "pid_t") == 0)
- arg->scnprintf = SCA_PID;
- else if (strcmp(field->type, "umode_t") == 0)
- arg->scnprintf = SCA_MODE_T;
- else if ((field->flags & TEP_FIELD_IS_ARRAY) && strstr(field->type, "char")) {
- arg->scnprintf = SCA_CHAR_ARRAY;
- arg->nr_entries = field->arraylen;
- } else if ((strcmp(field->type, "int") == 0 ||
- strcmp(field->type, "unsigned int") == 0 ||
- strcmp(field->type, "long") == 0) &&
- len >= 2 && strcmp(field->name + len - 2, "fd") == 0) {
- /*
- * /sys/kernel/tracing/events/syscalls/sys_enter*
- * grep -E 'field:.*fd;' .../format|sed -r 's/.*field:([a-z ]+) [a-z_]*fd.+/\1/g'|sort|uniq -c
- * 65 int
- * 23 unsigned int
- * 7 unsigned long
- */
- arg->scnprintf = SCA_FD;
- } else if (strstr(field->type, "enum") && use_btf != NULL) {
- *use_btf = true;
- arg->strtoul = STUL_BTF_TYPE;
+ } else if (field->type && !(field->flags & TEP_FIELD_IS_ARRAY) &&
+ (strstr(field->type, "(*)") != NULL ||
+ strstr(field->type, "_func_t") != NULL ||
+ strstr(field->type, "_fn") != NULL)) {
+ arg->scnprintf = SCA_KSYM;
} else {
const struct syscall_arg_fmt *fmt =
syscall_arg_fmt__find_by_name(field->name);
if (fmt) {
- arg->scnprintf = fmt->scnprintf;
- arg->strtoul = fmt->strtoul;
+ if (fmt->scnprintf == SCA_KSYM) {
+ if ((field->flags & TEP_FIELD_IS_POINTER) ||
+ (!field_is_enum(field) && !field_is_plain_int(field) &&
+ field_is_ptr_sized(field) && !(field->flags & TEP_FIELD_IS_ARRAY))) {
+ arg->scnprintf = fmt->scnprintf;
+ arg->strtoul = fmt->strtoul;
+ }
+ } else {
+ arg->scnprintf = fmt->scnprintf;
+ arg->strtoul = fmt->strtoul;
+ }
+ }
+
+ if (arg->scnprintf == NULL) {
+ if ((field->flags & TEP_FIELD_IS_POINTER) || strstr(field->name, "addr") ||
+ field_has_hex_fmt(field, len)) {
+ arg->scnprintf = SCA_PTR;
+ } else if (strcmp(field->type, "pid_t") == 0) {
+ arg->scnprintf = SCA_PID;
+ } else if (strcmp(field->type, "umode_t") == 0) {
+ arg->scnprintf = SCA_MODE_T;
+ } else if ((field->flags & TEP_FIELD_IS_ARRAY) && strstr(field->type, "char")) {
+ arg->scnprintf = SCA_CHAR_ARRAY;
+ arg->nr_entries = field->arraylen;
+ } else if ((strcmp(field->type, "int") == 0 ||
+ strcmp(field->type, "unsigned int") == 0 ||
+ strcmp(field->type, "long") == 0) &&
+ len >= 2 && strcmp(field->name + len - 2, "fd") == 0) {
+ arg->scnprintf = SCA_FD;
+ } else if (field_is_enum(field) && use_btf != NULL) {
+ *use_btf = true;
+ arg->strtoul = STUL_BTF_TYPE;
+ }
}
}
}
@@ -2529,7 +2667,8 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size,
default_scnprintf = sc->arg_fmt[arg.idx].scnprintf;
- if (trace->force_btf || default_scnprintf == NULL || default_scnprintf == SCA_PTR) {
+ if (trace->force_btf || default_scnprintf == NULL ||
+ default_scnprintf == SCA_PTR || default_scnprintf == SCA_KSYM) {
btf_printed = trace__btf_scnprintf(trace, &arg, bf + printed,
size - printed, val, field->type);
if (btf_printed) {
@@ -3268,12 +3407,6 @@ static unsigned char bitmap_byte(const unsigned long *mask, int byte_idx)
return b_val;
}
-static bool trace__field_is_ip(const char *name)
-{
- return !strcmp(name, "__probe_ip") ||
- !strcmp(name, "caller_ip") ||
- !strcmp(name, "call_site");
-}
static size_t trace__fprintf_tp_fields(struct trace *trace, struct perf_sample *sample,
struct thread *thread, void *augmented_args, int augmented_args_size)
@@ -3375,14 +3508,11 @@ static size_t trace__fprintf_tp_fields(struct trace *trace, struct perf_sample *
* Suppress it by default to avoid cluttering the output.
* If verbose mode is enabled, ensure it is formatted as a
* hexadecimal memory address rather than a signed integer.
- *
- * caller_ip and call_site are also expected to be instruction
- * pointers and should always be represented in hexadecimal.
*/
is_probe_ip = evsel__is_probe(evsel) && !strcmp(field->name, "__probe_ip");
- if (is_probe_ip || trace__field_is_ip(field->name)) {
- if (is_probe_ip && !verbose)
+ if (is_probe_ip) {
+ if (!verbose)
continue;
printed += scnprintf(bf + printed, size - printed,
diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c
index 32dbc484487a..bd30f6d4c31b 100644
--- a/tools/perf/tests/sample-parsing.c
+++ b/tools/perf/tests/sample-parsing.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdbool.h>
+#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
@@ -417,6 +418,98 @@ out_free:
return ret;
}
+static int test_truncated_branch_stack(void)
+{
+ struct perf_event_attr attr = {
+ .sample_type = PERF_SAMPLE_BRANCH_STACK,
+ };
+ struct {
+ struct perf_event_header header;
+ u64 nr;
+ struct branch_entry entry;
+ } input = {
+ .header = {
+ .type = PERF_RECORD_SAMPLE,
+ .size = sizeof(input.header) + sizeof(input.nr),
+ },
+ .nr = 1,
+ };
+ struct perf_sample sample;
+ struct evsel *evsel;
+ u64 flags = 1;
+ int err;
+
+ input.entry.flags.value = flags;
+ evsel = evsel__new(&attr);
+ if (!evsel)
+ return -1;
+
+ evsel->sample_size = __evsel__sample_size(attr.sample_type);
+ err = __evsel__parse_sample(evsel, (union perf_event *)&input,
+ &sample, /*needs_swap=*/true);
+ perf_sample__exit(&sample);
+ evsel__put(evsel);
+
+ if (err != -EFAULT) {
+ pr_debug("truncated branch stack returned %d, expected -EFAULT\n", err);
+ return -1;
+ }
+ if (input.entry.flags.value != flags) {
+ pr_debug("truncated branch stack modified data past the event\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int test_truncated_swapped_raw(u16 event_size, u32 raw_size)
+{
+ struct perf_event_attr attr = {
+ .sample_type = PERF_SAMPLE_RAW,
+ };
+ struct {
+ struct perf_event_header header;
+ union {
+ u64 value;
+ u32 words[2];
+ } raw;
+ u64 canary;
+ } input = {
+ .header = {
+ .type = PERF_RECORD_SAMPLE,
+ .size = event_size,
+ },
+ /* Parsing a pre-swapped word exchanges these two u32 values. */
+ .raw.words = { 0x12345678, raw_size },
+ .canary = 0x8877665544332211ULL,
+ };
+ struct perf_sample sample;
+ struct evsel *evsel;
+ u64 raw = input.raw.value;
+ u64 canary = input.canary;
+ int err;
+
+ evsel = evsel__new(&attr);
+ if (!evsel)
+ return -1;
+
+ evsel->sample_size = __evsel__sample_size(attr.sample_type);
+ err = __evsel__parse_sample(evsel, (union perf_event *)&input,
+ &sample, /*needs_swap=*/true);
+ perf_sample__exit(&sample);
+ evsel__put(evsel);
+
+ if (err != -EFAULT) {
+ pr_debug("truncated swapped RAW sample (size %u, raw %u) returned %d, expected -EFAULT\n",
+ event_size, raw_size, err);
+ return -1;
+ }
+ if (input.raw.value != raw || input.canary != canary) {
+ pr_debug("truncated swapped RAW sample modified data before validation\n");
+ return -1;
+ }
+ return 0;
+}
+
/**
* test__sample_parsing - test sample parsing.
*
@@ -433,6 +526,22 @@ static int test__sample_parsing(struct test_suite *test __maybe_unused, int subt
size_t i;
int err;
+ err = test_truncated_branch_stack();
+ if (err)
+ return err;
+
+ /* The declared RAW payload extends past an otherwise aligned event. */
+ err = test_truncated_swapped_raw(sizeof(struct perf_event_header) +
+ sizeof(u64), 16);
+ if (err)
+ return err;
+
+ /* The final complete word touched by mem_bswap_64() extends past it. */
+ err = test_truncated_swapped_raw(sizeof(struct perf_event_header) +
+ sizeof(u32) + 9, 9);
+ if (err)
+ return err;
+
/*
* Fail the test if it has not been updated when new sample format bits
* were added. Please actually update the test rather than just change
diff --git a/tools/perf/tests/shell/annotate_weight.sh b/tools/perf/tests/shell/annotate_weight.sh
new file mode 100755
index 000000000000..6b8c105c048b
--- /dev/null
+++ b/tools/perf/tests/shell/annotate_weight.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+# perf annotate weight regression test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+shelldir=$(dirname "$0")
+# shellcheck source=tools/perf/tests/shell/lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+testsym="test_loop"
+skip_test_missing_symbol "${testsym}"
+
+perfdata=$(mktemp /tmp/__perf_test.annotate_weight.XXXXX)
+record_log=$(mktemp /tmp/__perf_test.annotate_weight.XXXXX.log)
+report_out=$(mktemp /tmp/__perf_test.annotate_weight.XXXXX.report)
+annotate_out=$(mktemp /tmp/__perf_test.annotate_weight.XXXXX.annotate)
+
+cleanup() {
+ rm -f "${perfdata}" "${record_log}" "${report_out}" "${annotate_out}"
+ trap - EXIT TERM INT
+}
+
+trap 'cleanup; exit 1' TERM INT
+trap cleanup EXIT
+
+# mem-loads:pu requests a precise user PEBS event whose sample weight should
+# be populated by -W. Unsupported PEBS/weight PMUs are skipped below.
+if ! perf record -W -e mem-loads:pu -o "${perfdata}" -- perf test -w thloop \
+ > /dev/null 2> "${record_log}"; then
+ echo "[SKIP] precise PEBS weight sampling is unavailable"
+ exit 2
+fi
+
+# Confirm the PMU actually produced nonzero weights. A successful record alone
+# is insufficient: some PMUs accept the event but provide no weight payload.
+if ! perf report --stdio -i "${perfdata}" --fields=weight1,weight2,weight3,symbol --percent-limit 0 \
+ > "${report_out}" 2> "${record_log}"; then
+ echo "[SKIP] weighted samples cannot be decoded"
+ exit 2
+fi
+
+if ! awk '$1 ~ /^[0-9]/ && ($1 + 0) > 0 { found = 1 } END { exit !found }' \
+ "${report_out}"; then
+ echo "[SKIP] PEBS weight sampling produced no nonzero weights"
+ exit 2
+fi
+
+perf annotate --stdio -i "${perfdata}" --symbol "${testsym}" \
+ > "${annotate_out}" 2> "${record_log}"
+
+grep -q 'Percent Weight' "${annotate_out}"
+
+# The second numeric column is the rendered average weight. This assertion
+# fails on the original regression because calc_percent() left it at zero.
+if ! awk '$1 ~ /^[0-9]/ && $2 ~ /^[0-9]/ && ($2 + 0) > 0 { found = 1 } END { exit !found }' \
+ "${annotate_out}"; then
+ echo "Annotation output contained no nonzero weight"
+ cat "${annotate_out}"
+ exit 1
+fi
+
+echo "PEBS annotation weights: PASS"
diff --git a/tools/perf/tests/shell/trace_ksym_beautifier.sh b/tools/perf/tests/shell/trace_ksym_beautifier.sh
new file mode 100755
index 000000000000..7c441f8551a2
--- /dev/null
+++ b/tools/perf/tests/shell/trace_ksym_beautifier.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# perf trace kernel symbol beautifier tests
+# SPDX-License-Identifier: GPL-2.0
+
+err=0
+
+# shellcheck source=lib/probe.sh
+. "$(dirname "$0")"/lib/probe.sh
+skip_if_no_perf_trace || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+test_ksym_call_site() {
+ echo "Testing perf trace kernel symbol beautifier (call_site)"
+ output="$(perf trace -e kmem:kmalloc --max-events=1 -- true 2>&1)"
+ if ! echo "$output" | grep -q -E "call_site: [a-zA-Z_][a-zA-Z0-9_]*" || echo "$output" | grep -q -E "call_site: 0x[0-9a-fA-F]+"
+ then
+ printf "Call site kernel symbol beautification failed, output:\n%s\n" "$output"
+ err=1
+ fi
+}
+
+test_ksym_function_ptr() {
+ echo "Testing perf trace kernel symbol beautifier (function pointer)"
+ output="$(perf trace -e timer:hrtimer_start --max-events=1 -- sleep 0.01 2>&1)"
+ if ! echo "$output" | grep -q -E "function: [a-zA-Z_][a-zA-Z0-9_]*" || echo "$output" | grep -q -E "function: 0x[0-9a-fA-F]+"
+ then
+ printf "Function pointer kernel symbol beautification failed, output:\n%s\n" "$output"
+ err=1
+ fi
+}
+
+test_ksym_call_site
+
+if [ $err = 0 ]; then
+ test_ksym_function_ptr
+fi
+
+exit $err
diff --git a/tools/perf/trace/beauty/beauty.h b/tools/perf/trace/beauty/beauty.h
index 58a3206481ae..0f4801c61a5b 100644
--- a/tools/perf/trace/beauty/beauty.h
+++ b/tools/perf/trace/beauty/beauty.h
@@ -160,6 +160,9 @@ size_t syscall_arg__scnprintf_hex(char *bf, size_t size, struct syscall_arg *arg
size_t syscall_arg__scnprintf_ptr(char *bf, size_t size, struct syscall_arg *arg);
#define SCA_PTR syscall_arg__scnprintf_ptr
+size_t syscall_arg__scnprintf_ksym(char *bf, size_t size, struct syscall_arg *arg);
+#define SCA_KSYM syscall_arg__scnprintf_ksym
+
size_t syscall_arg__scnprintf_int(char *bf, size_t size, struct syscall_arg *arg);
#define SCA_INT syscall_arg__scnprintf_int
diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c
index e47a46775089..e8de644c0e9d 100644
--- a/tools/perf/ui/browsers/annotate.c
+++ b/tools/perf/ui/browsers/annotate.c
@@ -189,7 +189,7 @@ static void annotate_browser__draw_current_jump(struct ui_browser *browser)
struct map_symbol *ms = ab->b.priv;
struct symbol *sym = ms->sym;
struct annotation *notes = symbol__annotation(sym);
- u8 pcnt_width = annotation__pcnt_width(notes);
+ u8 pcnt_width = annotation__pcnt_width(notes, ab->evsel);
u8 cntr_width = annotation__br_cntr_width();
int width;
int diff = 0;
@@ -255,7 +255,8 @@ static unsigned int annotate_browser__refresh(struct ui_browser *browser)
{
struct annotation *notes = browser__annotation(browser);
int ret = ui_browser__list_head_refresh(browser);
- int pcnt_width = annotation__pcnt_width(notes);
+ int pcnt_width = annotation__pcnt_width(notes,
+ container_of(browser, struct annotate_browser, b)->evsel);
if (annotate_opts.jump_arrows)
annotate_browser__draw_current_jump(browser);
@@ -972,6 +973,7 @@ static int annotate_browser__run(struct annotate_browser *browser,
"O Bump offset level (jump targets -> +call -> all -> cycle thru)\n"
"s Toggle source code view\n"
"t Circulate percent, total period, samples view\n"
+ "w Toggle weight columns\n"
"c Show min/max cycle\n"
"/ Search string\n"
"k Toggle line numbers\n"
@@ -1091,6 +1093,14 @@ show_sup_ins:
symbol_conf.show_total_period = true;
annotation__update_column_widths(notes);
continue;
+ case 'w':
+ symbol_conf.annotate_weight = !symbol_conf.annotate_weight;
+ browser->b.width = notes->src->widths.max_line_len +
+ annotation__pcnt_width(notes, evsel) +
+ annotation__cycles_width(notes) +
+ annotation__br_cntr_width();
+ ui_browser__refresh_dimensions(&browser->b);
+ continue;
case 'c':
if (annotate_opts.show_minmax_cycle)
annotate_opts.show_minmax_cycle = false;
@@ -1225,10 +1235,12 @@ int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms,
browser.type_hash = hashmap__new(type_hash, type_equal, /*ctx=*/NULL);
}
- browser.b.width = notes->src->widths.max_line_len;
+ browser.b.width = notes->src->widths.max_line_len +
+ annotation__pcnt_width(notes, evsel) +
+ annotation__cycles_width(notes) +
+ annotation__br_cntr_width();
browser.b.nr_entries = notes->src->nr_entries;
browser.b.entries = &notes->src->source;
- browser.b.width += 18; /* Percentage */
if (annotate_opts.hide_src_code)
ui_browser__init_asm_mode(&browser.b);
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index df70e95a8470..9304c21c686a 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -222,6 +222,7 @@ static int __symbol__inc_addr_samples(struct map_symbol *ms,
u64 offset;
struct sym_hist *h;
struct sym_hist_entry *entry;
+ u64 weight = sample->weight ?: sample->ins_lat ?: sample->weight3;
pr_debug3("%s: addr=%#" PRIx64 "\n", __func__, map__unmap_ip(ms->map, addr));
@@ -256,10 +257,28 @@ static int __symbol__inc_addr_samples(struct map_symbol *ms,
entry->nr_samples++;
entry->period += sample->period;
+ if (sample->evsel->core.attr.sample_type & PERF_SAMPLE_WEIGHT_TYPE) {
+ entry->weight_sum[WEIGHT_WEIGHT] += sample->weight;
+ entry->weight_num[WEIGHT_WEIGHT]++;
+ if (sample->weight)
+ sym_hist__set_weight_mask(h, BIT(WEIGHT_WEIGHT));
+ }
+ if (sample->evsel->core.attr.sample_type & PERF_SAMPLE_WEIGHT_STRUCT) {
+ entry->weight_sum[WEIGHT_INSNLAT] += sample->ins_lat;
+ entry->weight_num[WEIGHT_INSNLAT]++;
+ entry->weight_sum[WEIGHT_WEIGHT3] += sample->weight3;
+ entry->weight_num[WEIGHT_WEIGHT3]++;
+ if (sample->ins_lat)
+ sym_hist__set_weight_mask(h, BIT(WEIGHT_INSNLAT));
+ if (sample->weight3)
+ sym_hist__set_weight_mask(h, BIT(WEIGHT_WEIGHT3));
+ }
+
pr_debug3("%#" PRIx64 " %s: period++ [addr: %#" PRIx64 ", %#" PRIx64
- ", evidx=%d] => nr_samples: %" PRIu64 ", period: %" PRIu64 "\n",
+ ", evidx=%d] => nr_samples: %" PRIu64 ", period: %" PRIu64
+ " weight %" PRIu64 "\n",
sym->start, sym->name, addr, addr - sym->start, evsel->core.idx,
- entry->nr_samples, entry->period);
+ entry->nr_samples, entry->period, weight);
return 0;
}
@@ -778,6 +797,50 @@ static bool needs_type_info(struct annotated_data_type *data_type)
return (data_type != &stackop_type) && (data_type != &canary_type);
}
+static const char *annotation__weight_mode_str(enum symbol__weight_mode mode,
+ const struct evsel *evsel)
+{
+ switch (mode) {
+ case WEIGHT_NONE:
+ return "";
+ case WEIGHT_WEIGHT:
+ return "Weight";
+ case WEIGHT_INSNLAT:
+ return "InsnLat";
+ case WEIGHT_WEIGHT3:
+ switch (evsel__e_machine((struct evsel *)evsel, NULL)) {
+ case EM_PPC:
+ case EM_PPC64:
+ return "PCycleLat";
+ case EM_X86_64:
+ return "RetireLat";
+ default:
+ return "Weight3";
+ }
+ default:
+ return "";
+ }
+}
+
+static void annotation__column_title(char *buf, size_t size,
+ struct annotation *notes,
+ const struct evsel *evsel)
+{
+ const char *base = symbol_conf.show_total_period ? "Period" :
+ symbol_conf.show_nr_samples ? "Samples" : "Percent";
+ int weight;
+ u8 weight_mask = annotation__weight_mask(notes, evsel);
+
+ scnprintf(buf, size, "%s", base);
+ for_each_weight(weight) {
+ if (weight_mask & BIT(weight))
+ scnprintf(buf + strlen(buf),
+ size - strlen(buf),
+ " %s",
+ annotation__weight_mode_str(weight, evsel));
+ }
+}
+
static int
annotation_line__print(struct annotation_line *al, struct annotation_print_data *apd,
struct annotation_options *opts, int printed,
@@ -833,6 +896,8 @@ annotation_line__print(struct annotation_line *al, struct annotation_print_data
for (i = 0; i < nr_percent; i++) {
struct annotation_data *data = &al->data[i];
double percent;
+ int weight;
+ u8 weight_mask = annotation__weight_mask(notes, apd->evsel);
percent = annotation_data__percent(data, percent_type);
color = get_percent_color(percent);
@@ -845,6 +910,14 @@ annotation_line__print(struct annotation_line *al, struct annotation_print_data
data->he.nr_samples);
else
color_fprintf(stdout, color, " %7.2f", percent);
+ for_each_weight(weight) {
+ if (weight_mask & BIT(weight))
+ color_fprintf(stdout, color, " %7" PRIu64,
+ data->he.weight_num[weight] ?
+ data->he.weight_sum[weight] /
+ data->he.weight_num[weight]
+ : 0);
+ }
}
printf(" : ");
@@ -891,7 +964,7 @@ annotation_line__print(struct annotation_line *al, struct annotation_print_data
} else if (max_lines && printed >= max_lines)
return 1;
else {
- int width = annotation__pcnt_width(notes);
+ int width = annotation__pcnt_width(notes, apd->evsel);
if (queue)
return -1;
@@ -915,6 +988,9 @@ static void calc_percent(struct annotation *notes,
struct sym_hist *sym_hist = annotation__histogram(notes, evsel);
unsigned int hits = 0;
u64 period = 0;
+ int i;
+ u64 weight_sum[WEIGHT_WEIGHT3 + 1] = { 0 };
+ u64 weight_num[WEIGHT_WEIGHT3 + 1] = { 0 };
while (offset < end) {
struct sym_hist_entry *entry;
@@ -923,6 +999,10 @@ static void calc_percent(struct annotation *notes,
if (entry) {
hits += entry->nr_samples;
period += entry->period;
+ for_each_weight(i) {
+ weight_sum[i] += entry->weight_sum[i];
+ weight_num[i] += entry->weight_num[i];
+ }
}
++offset;
}
@@ -930,6 +1010,10 @@ static void calc_percent(struct annotation *notes,
if (sym_hist->nr_samples) {
data->he.period = period;
data->he.nr_samples = hits;
+ for_each_weight(i) {
+ data->he.weight_sum[i] = weight_sum[i];
+ data->he.weight_num[i] = weight_num[i];
+ }
data->percent[PERCENT_HITS_LOCAL] = 100.0 * hits / sym_hist->nr_samples;
}
@@ -1252,8 +1336,9 @@ int hist_entry__annotate_printf(struct hist_entry *he, struct evsel *evsel)
int printed = 2, queue_len = 0;
int more = 0;
bool context = opts->context;
- int width = annotation__pcnt_width(notes);
+ int width = annotation__pcnt_width(notes, evsel);
int graph_dotted_len;
+ char title[64];
char buf[512];
filename = strdup(dso__long_name(dso));
@@ -1275,10 +1360,10 @@ int hist_entry__annotate_printf(struct hist_entry *he, struct evsel *evsel)
return ENOTSUP;
}
- graph_dotted_len = printf(" %-*.*s| Source code & Disassembly of %s for %s (%" PRIu64 " samples, "
+ annotation__column_title(title, sizeof(title), notes, evsel);
+ graph_dotted_len = printf(" %-*.*s|\tSource code & Disassembly of %s for %s (%" PRIu64 " samples, "
"percent: %s)\n",
- width, width, symbol_conf.show_total_period ? "Period" :
- symbol_conf.show_nr_samples ? "Samples" : "Percent",
+ width, width, title,
d_filename, evsel_name, h->nr_samples,
percent_type_str(opts->percent_type));
@@ -2029,7 +2114,8 @@ static int disasm_line__snprint_type_info(struct disasm_line *dl,
return printed;
}
-void annotation_line__write(struct annotation_line *al, struct annotation *notes,
+void annotation_line__write(struct annotation_line *al,
+ struct annotation *notes,
const struct annotation_write_ops *wops,
struct annotation_print_data *apd)
{
@@ -2037,7 +2123,8 @@ void annotation_line__write(struct annotation_line *al, struct annotation *notes
bool change_color = wops->change_color;
double percent_max = annotation_line__max_percent(al, annotate_opts.percent_type);
int width = wops->width;
- int pcnt_width = annotation__pcnt_width(notes);
+ int pcnt_width = annotation__pcnt_width(notes, apd->evsel);
+ u8 weight_mask = annotation__weight_mask(notes, apd->evsel);
int cycles_width = annotation__cycles_width(notes);
bool show_title = false;
char bf[256];
@@ -2062,6 +2149,7 @@ void annotation_line__write(struct annotation_line *al, struct annotation *notes
for (i = 0; i < al->data_nr; i++) {
double percent;
+ int weight;
percent = annotation_data__percent(&al->data[i],
annotate_opts.percent_type);
@@ -2075,6 +2163,15 @@ void annotation_line__write(struct annotation_line *al, struct annotation *notes
} else {
obj__printf(obj, "%7.2f ", percent);
}
+
+ for_each_weight(weight) {
+ if (weight_mask & BIT(weight))
+ obj__printf(obj, "%7" PRIu64 " ",
+ al->data[i].he.weight_num[weight] ?
+ al->data[i].he.weight_sum[weight] /
+ al->data[i].he.weight_num[weight] :
+ 0);
+ }
}
} else {
obj__set_percent_color(obj, 0, current_entry);
@@ -2082,9 +2179,9 @@ void annotation_line__write(struct annotation_line *al, struct annotation *notes
if (!show_title)
obj__printf(obj, "%-*s", pcnt_width, " ");
else {
- obj__printf(obj, "%-*s", pcnt_width,
- symbol_conf.show_total_period ? "Period" :
- symbol_conf.show_nr_samples ? "Samples" : "Percent");
+ char buf[64];
+ annotation__column_title(buf, sizeof(buf), notes, apd->evsel);
+ obj__printf(obj, "%-*s", pcnt_width, buf);
}
}
width -= pcnt_width;
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
index fa08d09b80f7..d7807df6667f 100644
--- a/tools/perf/util/annotate.h
+++ b/tools/perf/util/annotate.h
@@ -6,6 +6,7 @@
#include <stdint.h>
#include <stdio.h>
#include <linux/types.h>
+#include <linux/bitops.h>
#include <linux/list.h>
#include <linux/rbtree.h>
#include <asm/bug.h>
@@ -86,6 +87,8 @@ struct annotation;
struct sym_hist_entry {
u64 nr_samples;
u64 period;
+ u64 weight_sum[WEIGHT_WEIGHT3 + 1];
+ u64 weight_num[WEIGHT_WEIGHT3 + 1];
};
enum {
@@ -231,8 +234,20 @@ void symbol__calc_percent(struct symbol *sym, struct evsel *evsel);
struct sym_hist {
u64 nr_samples;
u64 period;
+ u8 weight_mask;
};
+/* Can be set asynchronously by top. */
+static inline u8 sym_hist__weight_mask(const struct sym_hist *hist)
+{
+ return __atomic_load_n(&hist->weight_mask, __ATOMIC_RELAXED);
+}
+
+static inline void sym_hist__set_weight_mask(struct sym_hist *hist, u8 mask)
+{
+ __atomic_fetch_or(&hist->weight_mask, mask, __ATOMIC_RELAXED);
+}
+
/**
* struct cyc_hist - (CPU) cycle histogram for a basic block
*
@@ -376,9 +391,34 @@ static inline int annotation__cycles_width(struct annotation *notes)
return notes->branch ? ANNOTATION__IPC_WIDTH + ANNOTATION__CYCLES_WIDTH : 0;
}
-static inline int annotation__pcnt_width(struct annotation *notes)
+static inline u8 annotation__weight_mask(struct annotation *notes,
+ const struct evsel *evsel)
+{
+ u8 mask = 0;
+ struct evsel *pos;
+ int i;
+
+ if (!symbol_conf.annotate_weight)
+ return 0;
+
+ if (evsel__is_group_event((struct evsel *)evsel)) {
+ pos = (struct evsel *)evsel;
+ for (i = 0; i < evsel->core.nr_members; i++) {
+ mask |= sym_hist__weight_mask(&notes->src->histograms[pos->core.idx]);
+ pos = evsel__next(pos);
+ }
+ return mask;
+ }
+
+ return sym_hist__weight_mask(&notes->src->histograms[evsel->core.idx]);
+}
+
+static inline int annotation__pcnt_width(struct annotation *notes,
+ const struct evsel *evsel)
{
- return (symbol_conf.show_total_period ? 12 : 8) * notes->src->nr_events;
+ int extra = hweight8(annotation__weight_mask(notes, evsel)) * 8;
+ return ((symbol_conf.show_total_period ? 12 : 8) + extra) *
+ notes->src->nr_events;
}
static inline bool annotation_line__filter(struct annotation_line *al)
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index d4cb455f4a7d..ce429eb247b6 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3584,7 +3584,10 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
}
if (type & PERF_SAMPLE_RAW) {
+ const __u64 *raw;
+
OVERFLOW_CHECK_u64(array);
+ raw = array;
u.val64 = *array;
/*
@@ -3600,16 +3603,14 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
}
data->raw_size = u.val32[0];
- /*
- * The raw data is aligned on 64bits including the
- * u32 size, so it's safe to use mem_bswap_64.
- */
- if (swapped)
- mem_bswap_64((void *) array, data->raw_size);
-
array = (void *)array + sizeof(u32);
-
OVERFLOW_CHECK(array, data->raw_size, max_size);
+ if (swapped) {
+ /* mem_bswap_64() accesses complete 64-bit words. */
+ sz = roundup((u64)data->raw_size, sizeof(u64));
+ OVERFLOW_CHECK(raw, sz, max_size);
+ mem_bswap_64((void *)raw, data->raw_size);
+ }
data->raw_data = (void *)array;
array = (void *)array + data->raw_size;
}
@@ -3639,6 +3640,8 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
e = (struct branch_entry *)&data->branch_stack->hw_idx;
}
+ OVERFLOW_CHECK(array, sz, max_size);
+
if (swapped) {
/*
* struct branch_flag does not have endian
@@ -3654,7 +3657,6 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
e->flags.value = evsel__bitfield_swap_branch_flags(e->flags.value);
}
- OVERFLOW_CHECK(array, sz, max_size);
array = (void *)array + sz;
if (evsel__has_branch_counters(evsel)) {
diff --git a/tools/perf/util/genelf_debug.c b/tools/perf/util/genelf_debug.c
index 8588b3e35e00..7d9ef43aa6ac 100644
--- a/tools/perf/util/genelf_debug.c
+++ b/tools/perf/util/genelf_debug.c
@@ -12,6 +12,8 @@
*/
#include <linux/compiler.h>
#include <linux/zalloc.h>
+#include <linux/kernel.h>
+#include <linux/unaligned.h>
#include <sys/types.h>
#include <stdio.h>
#include <getopt.h>
@@ -303,11 +305,13 @@ static ubyte get_special_opcode(struct debug_entry *ent,
{
unsigned int temp;
unsigned long delta_addr;
+ int lineno = get_unaligned(&ent->lineno);
+ uint64_t addr = get_unaligned(&ent->addr);
/*
* delta from line_base
*/
- temp = (ent->lineno - last_line) - default_debug_line_header.line_base;
+ temp = (lineno - last_line) - default_debug_line_header.line_base;
if (temp >= default_debug_line_header.line_range)
return 0;
@@ -315,7 +319,7 @@ static ubyte get_special_opcode(struct debug_entry *ent,
/*
* delta of addresses
*/
- delta_addr = (ent->addr - last_vma) / default_debug_line_header.minimum_instruction_length;
+ delta_addr = (addr - last_vma) / default_debug_line_header.minimum_instruction_length;
/* This is not sufficient to ensure opcode will be in [0-256] but
* sufficient to ensure when summing with the delta lineno we will
@@ -362,6 +366,8 @@ static void emit_lineno_info(struct buffer_ext *be,
for (i = 0; i < nr_entry; i++, ent = debug_entry_next(ent)) {
int need_copy = 0;
ubyte special_opcode;
+ int lineno = get_unaligned(&ent->lineno);
+ uint64_t addr = get_unaligned(&ent->addr);
/*
* check if filename changed, if so add it
@@ -376,24 +382,24 @@ static void emit_lineno_info(struct buffer_ext *be,
special_opcode = get_special_opcode(ent, last_line, last_vma);
if (special_opcode != 0) {
- last_line = ent->lineno;
- last_vma = ent->addr;
+ last_line = lineno;
+ last_vma = addr;
emit_opcode(be, special_opcode);
} else {
/*
* lines differ, emit line delta
*/
- if (last_line != ent->lineno) {
- emit_advance_lineno(be, ent->lineno - last_line);
- last_line = ent->lineno;
+ if (last_line != lineno) {
+ emit_advance_lineno(be, lineno - last_line);
+ last_line = lineno;
need_copy = 1;
}
/*
* addresses differ, emit address delta
*/
- if (last_vma != ent->addr) {
- emit_advance_pc(be, ent->addr - last_vma);
- last_vma = ent->addr;
+ if (last_vma != addr) {
+ emit_advance_pc(be, addr - last_vma);
+ last_vma = addr;
need_copy = 1;
}
/*
@@ -480,7 +486,7 @@ jit_process_debug_info(uint64_t code_addr,
int i;
for (i = 0; i < nr_debug_entries; i++) {
- ent->addr = ent->addr - code_addr;
+ put_unaligned(get_unaligned(&ent->addr) - code_addr, &ent->addr);
ent = debug_entry_next(ent);
}
add_compilation_unit(di, buffer_ext_size(dl));
diff --git a/tools/perf/util/jitdump.c b/tools/perf/util/jitdump.c
index 83005b30b9bf..45a05316b3ca 100644
--- a/tools/perf/util/jitdump.c
+++ b/tools/perf/util/jitdump.c
@@ -9,10 +9,13 @@
#include <fcntl.h>
#include <unistd.h>
#include <inttypes.h>
+#include <limits.h>
#include <byteswap.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <linux/stringify.h>
+#include <linux/kernel.h>
+#include <linux/unaligned.h>
#include "event.h"
#include "debug.h"
@@ -117,6 +120,8 @@ jit_close(struct jit_buf_desc *jd)
funlockfile(jd->in);
fclose(jd->in);
jd->in = NULL;
+ zfree(&jd->debug_data);
+ zfree(&jd->unwinding_data);
}
static int
@@ -143,6 +148,7 @@ jit_open(struct jit_buf_desc *jd, const char *name)
ssize_t bs, bsz = 0;
void *n, *buf = NULL;
int ret, retval = -1;
+ char *dname;
nsinfo__mountns_enter(jd->nsi, &nsc);
jd->in = fopen(name, "r");
@@ -154,7 +160,7 @@ jit_open(struct jit_buf_desc *jd, const char *name)
buf = malloc(bsz);
if (!buf)
- goto error;
+ goto error_noflock;
/*
* protect from writer modifying the file while we are reading it
@@ -224,10 +230,12 @@ jit_open(struct jit_buf_desc *jd, const char *name)
n = realloc(buf, bs);
if (!n)
goto error;
- bsz = bs;
buf = n;
- /* read extra we do not know about */
- ret = fread(buf, bs - bsz, 1, jd->in);
+ bsz = bs;
+ }
+ if (bs > 0) {
+ /* consume extended header bytes from the stream */
+ ret = fread(buf, bs, 1, jd->in);
if (ret != 1)
goto error;
}
@@ -236,13 +244,16 @@ jit_open(struct jit_buf_desc *jd, const char *name)
*/
strncpy(jd->dir, name, PATH_MAX - 1);
jd->dir[PATH_MAX - 1] = '\0';
- dirname(jd->dir);
+ dname = dirname(jd->dir);
+ if (dname != jd->dir)
+ strlcpy(jd->dir, dname, sizeof(jd->dir));
free(buf);
return 0;
error:
- free(buf);
funlockfile(jd->in);
+error_noflock:
+ free(buf);
fclose(jd->in);
return retval;
}
@@ -315,14 +326,37 @@ jit_get_next_entry(struct jit_buf_desc *jd)
switch(id) {
case JIT_CODE_DEBUG_INFO:
if (jd->needs_bswap) {
+ void *end = (void *)jr + jr->prefix.total_size;
+ struct debug_entry *ent;
uint64_t n;
+
jr->info.code_addr = bswap_64(jr->info.code_addr);
jr->info.nr_entry = bswap_64(jr->info.nr_entry);
- for (n = 0 ; n < jr->info.nr_entry; n++) {
- jr->info.entries[n].addr = bswap_64(jr->info.entries[n].addr);
- jr->info.entries[n].lineno = bswap_32(jr->info.entries[n].lineno);
- jr->info.entries[n].discrim = bswap_32(jr->info.entries[n].discrim);
+
+ /*
+ * debug_entry has a variable-length name[], so array
+ * indexing would compute wrong offsets — use
+ * debug_entry_next() and bounds-check each entry.
+ */
+ ent = &jr->info.entries[0];
+ for (n = 0; n < jr->info.nr_entry; n++) {
+ if ((void *)ent + sizeof(*ent) > end)
+ break;
+ /* name must be NUL-terminated within the record */
+ if (!memchr(ent->name, '\0', (char *)end - ent->name))
+ break;
+ /*
+ * debug entries are packed with a variable-length
+ * name[], so entries after the first may be
+ * unaligned: byte-swap via unaligned-safe accessors.
+ */
+ put_unaligned(bswap_64(get_unaligned(&ent->addr)), &ent->addr);
+ put_unaligned(bswap_32(get_unaligned(&ent->lineno)), &ent->lineno);
+ put_unaligned(bswap_32(get_unaligned(&ent->discrim)), &ent->discrim);
+ ent = debug_entry_next(ent);
}
+ /* clamp so downstream consumers don't overrun */
+ jr->info.nr_entry = n;
}
break;
case JIT_CODE_UNWINDING_INFO:
@@ -435,12 +469,11 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr)
u16 idr_size;
const char *sym;
uint64_t count;
- int ret, csize, usize;
+ int ret, csize;
+ uint64_t usize;
pid_t nspid, pid, tid;
- struct {
- u32 pid, tid;
- u64 time;
- } *id;
+ uint64_t timestamp = 0;
+ unsigned long id;
nspid = jr->load.pid;
pid = jr_entry_pid(jd, jr);
@@ -448,8 +481,25 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr)
csize = jr->load.code_size;
usize = jd->unwinding_mapped_size;
addr = jr->load.code_addr;
+
+ /* code blob lives at the end of the record, validate it fits */
+ if (jr->load.p.total_size < sizeof(jr->load) ||
+ jr->load.code_size > jr->load.p.total_size - sizeof(jr->load) ||
+ jr->load.code_size > INT_MAX) {
+ pr_warning("jitdump: invalid code_size %" PRIu64 " (total_size=%u) in code_load record\n",
+ (uint64_t)jr->load.code_size, jr->load.p.total_size);
+ return -1;
+ }
+
sym = (void *)((unsigned long)jr + sizeof(jr->load));
code = (unsigned long)jr + jr->load.p.total_size - csize;
+
+ /* sym string lives between the load header and the code blob */
+ if (!memchr(sym, '\0', code - (unsigned long)sym)) {
+ pr_warning("jitdump: unterminated symbol name in code_load record\n");
+ return -1;
+ }
+
count = jr->load.code_index;
idr_size = jd->machine->id_hdr_size;
@@ -462,6 +512,9 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr)
jd->dir,
nspid,
count);
+ /* snprintf returns would-be length on truncation, clamp to buffer */
+ if (size >= sizeof(event->mmap2.filename))
+ size = sizeof(event->mmap2.filename) - 1;
size++; /* for \0 */
@@ -475,7 +528,7 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr)
jd->nr_debug_entries = 0;
}
- if (jd->unwinding_data && jd->eh_frame_hdr_size) {
+ if (jd->unwinding_data) {
zfree(&jd->unwinding_data);
jd->eh_frame_hdr_size = 0;
jd->unwinding_mapped_size = 0;
@@ -496,7 +549,7 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr)
event->mmap2.pgoff = GEN_ELF_TEXT_OFFSET;
event->mmap2.start = addr;
- event->mmap2.len = usize ? ALIGN_8(csize) + usize : csize;
+ event->mmap2.len = usize ? ALIGN_8((uint64_t)csize) + usize : (uint64_t)csize;
event->mmap2.pid = pid;
event->mmap2.tid = tid;
event->mmap2.ino = st.st_ino;
@@ -506,13 +559,27 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr)
event->mmap2.flags = MAP_SHARED;
event->mmap2.ino_generation = 1;
- id = (void *)((unsigned long)event + event->mmap.header.size - idr_size);
+ /*
+ * The sample id fields are appended in the order accounted for by
+ * evsel__id_hdr_size(), skipping the ones not requested in
+ * sample_type, so they cannot be written through a fixed struct:
+ * with PERF_SAMPLE_TID unset, PERF_SAMPLE_TIME starts at offset 0
+ * and idr_size is 8, so storing it at offset 8 runs past the end of
+ * the event allocation.
+ */
+ id = (unsigned long)event + event->mmap.header.size - idr_size;
if (jd->sample_type & PERF_SAMPLE_TID) {
- id->pid = pid;
- id->tid = tid;
+ struct { u32 pid, tid; } *id_tid = (void *)id;
+
+ id_tid->pid = pid;
+ id_tid->tid = tid;
+ id += sizeof(u64);
+ }
+ if (jd->sample_type & PERF_SAMPLE_TIME) {
+ timestamp = convert_timestamp(jd, jr->load.p.timestamp);
+ *(u64 *)id = timestamp;
+ id += sizeof(u64);
}
- if (jd->sample_type & PERF_SAMPLE_TIME)
- id->time = convert_timestamp(jd, jr->load.p.timestamp);
/*
* create pseudo sample to induce dso hit increment
@@ -522,7 +589,7 @@ static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr)
sample.cpumode = PERF_RECORD_MISC_USER;
sample.pid = pid;
sample.tid = tid;
- sample.time = id->time;
+ sample.time = timestamp;
sample.ip = addr;
ret = perf_event__process_mmap2(tool, event, &sample, jd->machine);
@@ -565,14 +632,12 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr)
char *filename;
size_t size;
struct stat st;
- int usize;
+ uint64_t usize;
u16 idr_size;
int ret;
pid_t nspid, pid, tid;
- struct {
- u32 pid, tid;
- u64 time;
- } *id;
+ uint64_t timestamp = 0;
+ unsigned long id;
nspid = jr->load.pid;
pid = jr_entry_pid(jd, jr);
@@ -581,9 +646,10 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr)
idr_size = jd->machine->id_hdr_size;
/*
- * +16 to account for sample_id_all (hack)
+ * Sample ID is written past the end of the mmap2 record; size
+ * the allocation to account for it instead of a hardcoded +16.
*/
- event = calloc(1, sizeof(*event) + 16);
+ event = calloc(1, sizeof(*event) + idr_size);
if (!event)
return -1;
@@ -592,6 +658,9 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr)
jd->dir,
nspid,
jr->move.code_index);
+ /* snprintf returns would-be length on truncation, clamp to buffer */
+ if (size >= sizeof(event->mmap2.filename))
+ size = sizeof(event->mmap2.filename) - 1;
size++; /* for \0 */
@@ -617,13 +686,27 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr)
event->mmap2.flags = MAP_SHARED;
event->mmap2.ino_generation = 1;
- id = (void *)((unsigned long)event + event->mmap.header.size - idr_size);
+ /*
+ * The sample id fields are appended in the order accounted for by
+ * evsel__id_hdr_size(), skipping the ones not requested in
+ * sample_type, so they cannot be written through a fixed struct:
+ * with PERF_SAMPLE_TID unset, PERF_SAMPLE_TIME starts at offset 0
+ * and idr_size is 8, so storing it at offset 8 runs past the end of
+ * the event allocation.
+ */
+ id = (unsigned long)event + event->mmap.header.size - idr_size;
if (jd->sample_type & PERF_SAMPLE_TID) {
- id->pid = pid;
- id->tid = tid;
+ struct { u32 pid, tid; } *id_tid = (void *)id;
+
+ id_tid->pid = pid;
+ id_tid->tid = tid;
+ id += sizeof(u64);
+ }
+ if (jd->sample_type & PERF_SAMPLE_TIME) {
+ timestamp = convert_timestamp(jd, jr->load.p.timestamp);
+ *(u64 *)id = timestamp;
+ id += sizeof(u64);
}
- if (jd->sample_type & PERF_SAMPLE_TIME)
- id->time = convert_timestamp(jd, jr->load.p.timestamp);
/*
* create pseudo sample to induce dso hit increment
@@ -633,7 +716,7 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr)
sample.cpumode = PERF_RECORD_MISC_USER;
sample.pid = pid;
sample.tid = tid;
- sample.time = id->time;
+ sample.time = timestamp;
sample.ip = jr->move.new_code_addr;
ret = perf_event__process_mmap2(tool, event, &sample, jd->machine);
@@ -645,17 +728,24 @@ static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr)
build_id__mark_dso_hit(tool, event, &sample, jd->machine);
out:
perf_sample__exit(&sample);
+ free(event);
return ret;
}
static int jit_repipe_debug_info(struct jit_buf_desc *jd, union jr_entry *jr)
{
- void *data;
- size_t sz;
+ struct debug_entry *ent;
+ void *data, *end;
+ size_t sz, valid;
+ uint64_t i;
if (!(jd && jr))
return -1;
+ /* total_size must cover at least the fixed header */
+ if (jr->prefix.total_size < sizeof(jr->info))
+ return -1;
+
sz = jr->prefix.total_size - sizeof(jr->info);
data = malloc(sz);
if (!data)
@@ -663,13 +753,29 @@ static int jit_repipe_debug_info(struct jit_buf_desc *jd, union jr_entry *jr)
memcpy(data, &jr->info.entries, sz);
+ zfree(&jd->debug_data);
jd->debug_data = data;
/*
- * we must use nr_entry instead of size here because
- * we cannot distinguish actual entry from padding otherwise
+ * Clamp nr_debug_entries to entries that actually fit in the
+ * payload. The byte-swap path already does this for cross-endian
+ * files; validate on the native path too, since downstream
+ * jit_process_debug_info() iterates via debug_entry_next() which
+ * calls strlen() on each entry's name field.
*/
- jd->nr_debug_entries = jr->info.nr_entry;
+ end = data + sz;
+ ent = data;
+ valid = 0;
+ for (i = 0; i < jr->info.nr_entry; i++) {
+ if ((void *)ent + sizeof(*ent) > end)
+ break;
+ /* name must be NUL-terminated within the payload */
+ if (!memchr(ent->name, '\0', (char *)end - ent->name))
+ break;
+ ent = debug_entry_next(ent);
+ valid++;
+ }
+ jd->nr_debug_entries = valid;
return 0;
}
@@ -683,7 +789,23 @@ jit_repipe_unwinding_info(struct jit_buf_desc *jd, union jr_entry *jr)
if (!(jd && jr))
return -1;
+ /* total_size must cover at least the fixed header */
+ if (jr->prefix.total_size < sizeof(jr->unwinding))
+ return -1;
+
unwinding_data_size = jr->prefix.total_size - sizeof(jr->unwinding);
+
+ /*
+ * Validate sizes before allocating — jit_add_eh_frame_info()
+ * computes unwinding_size - eh_frame_hdr_size and uses the
+ * result as a buffer length for libelf.
+ */
+ if (jr->unwinding.unwinding_size > unwinding_data_size ||
+ jr->unwinding.eh_frame_hdr_size > jr->unwinding.unwinding_size) {
+ pr_warning("jitdump: invalid unwinding sizes in unwinding_info record\n");
+ return -1;
+ }
+
unwinding_data = malloc(unwinding_data_size);
if (!unwinding_data)
return -1;
diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h
index 0dee5aa6a534..71f60081a85b 100644
--- a/tools/perf/util/symbol_conf.h
+++ b/tools/perf/util/symbol_conf.h
@@ -27,6 +27,16 @@ enum a2l_style {
};
#define MAX_A2L_STYLE (A2L_STYLE_CMD + 1)
+enum symbol__weight_mode {
+ WEIGHT_NONE = 0,
+ WEIGHT_WEIGHT,
+ WEIGHT_INSNLAT,
+ WEIGHT_WEIGHT3,
+};
+
+#define for_each_weight(_weight) \
+ for ((_weight) = WEIGHT_WEIGHT; (_weight) <= WEIGHT_WEIGHT3; (_weight)++)
+
struct symbol_conf {
bool nanosecs;
unsigned short priv_size;
@@ -69,7 +79,8 @@ struct symbol_conf {
annotate_data_sample,
skip_empty,
enable_latency,
- prefer_latency;
+ prefer_latency,
+ annotate_weight;
const char *vmlinux_name,
*kallsyms_name,
*source_prefix,