diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-07 13:24:48 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-07 13:24:48 +0100 |
| commit | 6b2e352659c615bf909340d3c9d2c34c4be731a4 (patch) | |
| tree | 0897714d4d4052fe0a812bf52b8cee18a4b849bc /tools/perf | |
| parent | efcdb1714bf28590e8b192b212e385858ddcf0c3 (diff) | |
| parent | 02f6847e1822714a4201b87e42f92b0d43e8549d (diff) | |
| download | linux-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.txt | 3 | ||||
| -rw-r--r-- | tools/perf/Documentation/perf-record.txt | 8 | ||||
| -rw-r--r-- | tools/perf/Documentation/perf-report.txt | 6 | ||||
| -rw-r--r-- | tools/perf/Documentation/perf-top.txt | 6 | ||||
| -rw-r--r-- | tools/perf/arch/x86/util/pmu.c | 208 | ||||
| -rw-r--r-- | tools/perf/builtin-annotate.c | 3 | ||||
| -rw-r--r-- | tools/perf/builtin-report.c | 3 | ||||
| -rw-r--r-- | tools/perf/builtin-top.c | 2 | ||||
| -rw-r--r-- | tools/perf/builtin-trace.c | 248 | ||||
| -rw-r--r-- | tools/perf/tests/sample-parsing.c | 109 | ||||
| -rwxr-xr-x | tools/perf/tests/shell/annotate_weight.sh | 63 | ||||
| -rwxr-xr-x | tools/perf/tests/shell/trace_ksym_beautifier.sh | 38 | ||||
| -rw-r--r-- | tools/perf/trace/beauty/beauty.h | 3 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/annotate.c | 20 | ||||
| -rw-r--r-- | tools/perf/util/annotate.c | 121 | ||||
| -rw-r--r-- | tools/perf/util/annotate.h | 44 | ||||
| -rw-r--r-- | tools/perf/util/evsel.c | 20 | ||||
| -rw-r--r-- | tools/perf/util/genelf_debug.c | 28 | ||||
| -rw-r--r-- | tools/perf/util/jitdump.c | 204 | ||||
| -rw-r--r-- | tools/perf/util/symbol_conf.h | 13 |
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 = ¬es->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(¬es->src->histograms[pos->core.idx]); + pos = evsel__next(pos); + } + return mask; + } + + return sym_hist__weight_mask(¬es->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, |
