* [PATCH v4 1/2] perf trace: Correct default cpumask formatting to hexadecimal
2026-07-22 15:50 [PATCH v4 0/2] perf trace: Correct cpumask formatting and add --bitmask-list Aaron Tomlin
@ 2026-07-22 15:50 ` Aaron Tomlin
2026-07-22 15:50 ` [PATCH v4 2/2] perf trace: Add --bitmask-list command-line option Aaron Tomlin
1 sibling, 0 replies; 3+ messages in thread
From: Aaron Tomlin @ 2026-07-22 15:50 UTC (permalink / raw)
To: peterz, mingo, acme, namhyung
Cc: mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter,
james.clark, howardchu95, atomlin, neelx, chjohnst, sean, steve,
linux-perf-users, linux-kernel
Currently, dynamic non-array fields such as 'cpumask_t' are mishandled in
'perf trace', causing the raw length and offset descriptors to be interpreted
and displayed as a literal integer (e.g., "cpumask: 524320" instead of the
actual mask data).
Correct the parsing of dynamic fields that do not have the
TEP_FIELD_IS_ARRAY flag set by introducing helper functions
format_field__get_raw_data() and format_field__get_cpumask().
Using these helpers, resolve the pointer to the raw bits within the
payload and format the cpumask as a zero-padded hexadecimal string by default.
Fixes: c5e006cdbd27 ("perf trace: Support tracepoint dynamic char arrays")
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
tools/perf/builtin-trace.c | 71 +++++++++++++++++++++++++----
tools/perf/util/evsel.c | 91 ++++++++++++++++++++++++++++++++++++++
tools/perf/util/evsel.h | 6 +++
3 files changed, 159 insertions(+), 9 deletions(-)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index ba0f8749fc7d..5808adad52ae 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -3207,6 +3207,22 @@ static void bpf_output__fprintf(struct trace *trace,
++trace->nr_events_printed;
}
+static unsigned char bitmap_byte(const unsigned long *mask, int byte_idx)
+{
+ unsigned char b_val = 0;
+ int bit_in_byte;
+
+ for (bit_in_byte = 0; bit_in_byte < 8; bit_in_byte++) {
+ int b_idx = byte_idx * 8 + bit_in_byte;
+ int host_w_idx = b_idx / BITS_PER_LONG;
+ int host_bit_in_word = b_idx % BITS_PER_LONG;
+
+ if (mask[host_w_idx] & (1UL << host_bit_in_word))
+ b_val |= (1 << bit_in_byte);
+ }
+ return b_val;
+}
+
static size_t trace__fprintf_tp_fields(struct trace *trace, struct perf_sample *sample,
struct thread *thread, void *augmented_args, int augmented_args_size)
{
@@ -3238,17 +3254,54 @@ static size_t trace__fprintf_tp_fields(struct trace *trace, struct perf_sample *
syscall_arg.len = 0;
syscall_arg.fmt = arg;
if (field->flags & TEP_FIELD_IS_ARRAY) {
- int offset = field->offset;
-
- if (field->flags & TEP_FIELD_IS_DYNAMIC) {
- offset = format_field__intval(field, sample, evsel->needs_swap);
- syscall_arg.len = offset >> 16;
- offset &= 0xffff;
- if (tep_field_is_relative(field->flags))
- offset += field->offset + field->size;
+ void *ptr = format_field__get_raw_data(field, sample,
+ evsel->needs_swap,
+ &syscall_arg.len);
+
+ if (!ptr) {
+ pr_err("Problem processing %s field, skipping...\n", field->name);
+ continue;
+ }
+ val = (uintptr_t)ptr;
+ } else if ((field->flags & TEP_FIELD_IS_DYNAMIC) &&
+ strstr(field->type, "cpumask")) {
+ unsigned long *mask = format_field__get_cpumask(field, sample,
+ evsel->needs_swap,
+ &syscall_arg.len);
+
+ if (!mask) {
+ pr_err("Problem processing %s field, skipping...\n", field->name);
+ continue;
}
- val = (uintptr_t)(sample->raw_data + offset);
+ printed += scnprintf(bf + printed, size - printed, "%s", printed ? ", " : "");
+ if (trace->show_arg_names)
+ printed += scnprintf(bf + printed, size - printed, "%s: ", field->name);
+
+ if (syscall_arg.len == 0) {
+ printed += scnprintf(bf + printed, size - printed, "0");
+ } else {
+ int i;
+ bool skip_zero = true;
+
+ printed += scnprintf(bf + printed, size - printed, "0x");
+ /* Print bytes from most significant to least significant */
+ for (i = syscall_arg.len - 1; i >= 0; i--) {
+ unsigned char b_val = bitmap_byte(mask, i);
+
+ if (skip_zero && b_val == 0 && i > 0)
+ continue;
+
+ if (skip_zero) {
+ printed += scnprintf(bf + printed, size - printed, "%x", b_val);
+ skip_zero = false;
+ } else {
+ printed += scnprintf(bf + printed, size - printed, "%02x", b_val);
+ }
+ }
+ }
+ free(mask);
+ continue;
} else
val = format_field__intval(field, sample, evsel->needs_swap);
/*
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index ea9fa04429f0..185673874aa0 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -16,9 +16,11 @@
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
+#include <string.h>
#include <dirent.h>
#include <linux/bitops.h>
+#include <linux/bitmap.h>
#include <linux/compiler.h>
#include <linux/ctype.h>
#include <linux/err.h>
@@ -3933,6 +3935,95 @@ void *perf_sample__rawptr(struct perf_sample *sample, const char *name)
return sample->raw_data + offset;
}
+void *format_field__get_raw_data(struct tep_format_field *field, struct
+ perf_sample *sample, bool needs_swap,
+ u16 *len_out)
+{
+ int offset = field->offset;
+ int size = field->size;
+
+ if (field->flags & TEP_FIELD_IS_DYNAMIC) {
+ unsigned int dynamic_data;
+
+ if (out_of_bounds(field, field->offset, field->size, sample->raw_size))
+ return NULL;
+
+ dynamic_data = format_field__intval(field, sample, needs_swap);
+
+ offset = dynamic_data & 0xffff;
+ size = (dynamic_data >> 16) & 0xffff;
+
+ if (tep_field_is_relative(field->flags))
+ offset += field->offset + field->size;
+ }
+
+ if (out_of_bounds(field, offset, size, sample->raw_size))
+ return NULL;
+
+ *len_out = size;
+ return sample->raw_data + offset;
+}
+
+unsigned long *format_field__get_cpumask(struct tep_format_field *field,
+ struct perf_sample *sample,
+ bool needs_swap, u16 *len_out)
+{
+ u16 len;
+ void *ptr = format_field__get_raw_data(field, sample, needs_swap, &len);
+ unsigned long *mask;
+ struct perf_env *env;
+ bool target_is_64;
+ int target_word_size;
+ int nr_words;
+ int bit_idx;
+ int nbits;
+
+ if (!ptr)
+ return NULL;
+
+ nbits = len * 8;
+ mask = bitmap_zalloc(nbits ?: 1);
+ if (!mask)
+ return NULL;
+
+ env = evsel__env(sample->evsel);
+ target_is_64 = env ? perf_env__kernel_is_64_bit(env) : (sizeof(void *) == 8);
+ target_word_size = target_is_64 ? 8 : 4;
+ nr_words = len / target_word_size;
+
+ for (bit_idx = 0; bit_idx < nbits; bit_idx++) {
+ int w_idx = bit_idx / (target_word_size * 8);
+ int bit_in_word = bit_idx % (target_word_size * 8);
+ bool set = false;
+
+ if (w_idx >= nr_words)
+ break;
+
+ if (target_is_64) {
+ u64 word;
+ memcpy(&word, (unsigned char *)ptr + w_idx * 8, 8);
+ if (needs_swap)
+ word = bswap_64(word);
+ set = (word & (1ULL << bit_in_word)) != 0;
+ } else {
+ u32 word32;
+ memcpy(&word32, (unsigned char *)ptr + w_idx * 4, 4);
+ if (needs_swap)
+ word32 = bswap_32(word32);
+ set = (word32 & (1U << bit_in_word)) != 0;
+ }
+
+ if (set) {
+ int host_w_idx = bit_idx / BITS_PER_LONG;
+ int host_bit_in_word = bit_idx % BITS_PER_LONG;
+ mask[host_w_idx] |= (1UL << host_bit_in_word);
+ }
+ }
+
+ *len_out = len;
+ return mask;
+}
+
u64 format_field__intval(struct tep_format_field *field, struct perf_sample *sample,
bool needs_swap)
{
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 163fc2b6a7ea..02129a022ea3 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -400,6 +400,12 @@ static inline char *perf_sample__strval(struct perf_sample *sample, const char *
struct tep_format_field;
+void *format_field__get_raw_data(struct tep_format_field *field,
+ struct perf_sample *sample,
+ bool needs_swap, u16 *len_out);
+unsigned long *format_field__get_cpumask(struct tep_format_field *field,
+ struct perf_sample *sample,
+ bool needs_swap, u16 *len_out);
u64 format_field__intval(struct tep_format_field *field, struct perf_sample *sample, bool needs_swap);
#ifdef HAVE_LIBTRACEEVENT
--
2.54.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH v4 2/2] perf trace: Add --bitmask-list command-line option
2026-07-22 15:50 [PATCH v4 0/2] perf trace: Correct cpumask formatting and add --bitmask-list Aaron Tomlin
2026-07-22 15:50 ` [PATCH v4 1/2] perf trace: Correct default cpumask formatting to hexadecimal Aaron Tomlin
@ 2026-07-22 15:50 ` Aaron Tomlin
1 sibling, 0 replies; 3+ messages in thread
From: Aaron Tomlin @ 2026-07-22 15:50 UTC (permalink / raw)
To: peterz, mingo, acme, namhyung
Cc: mark.rutland, alexander.shishkin, jolsa, irogers, adrian.hunter,
james.clark, howardchu95, atomlin, neelx, chjohnst, sean, steve,
linux-perf-users, linux-kernel
Introduce a new '--bitmask-list' command-line option for 'perf trace'.
When this option is specified, the formatting of cpumasks is delegated
to bitmap_scnprintf(), enabling cpumasks to be displayed as a condensed,
human-readable list (e.g., "0,2-5,7") instead of the default hexadecimal
representation. An example is provided below:
❯ sudo ./perf trace --show-cpu --bitmask-list --event ipi:ipi_send_cpumask --max-event 5
0.000 [000] Xorg/1434 ipi:ipi_send_cpumask(cpumask: 2-3,6, callsite: 0xffffffff9994f8e4, callback: 0xffffffff9994fdd0)
694.527 [002] chrome/2894 ipi:ipi_send_cpumask(cpumask: 1,3-5, callsite: 0xffffffff9994f8e4, callback: 0xffffffff9994fdd0)
2666.608 [003] Chrome_ChildIO/2948 ipi:ipi_send_cpumask(cpumask: 4,7, callsite: 0xffffffff9994f8e4, callback: 0xffffffff9994fdd0)
2673.638 [000] Chrome_IOThrea/2920 ipi:ipi_send_cpumask(cpumask: 2-5, callsite: 0xffffffff9994f8e4, callback: 0xffffffff9994fdd0)
2714.228 [005] chrome/3375 ipi:ipi_send_cpumask(cpumask: 0-4,6-7, callsite: 0xffffffff9994f8e4, callback: 0xffffffff9994fdd0)
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
tools/perf/Documentation/perf-trace.txt | 4 ++++
tools/perf/builtin-trace.c | 5 +++++
2 files changed, 9 insertions(+)
diff --git a/tools/perf/Documentation/perf-trace.txt b/tools/perf/Documentation/perf-trace.txt
index d0b6c771a1b9..d20b43ea3d37 100644
--- a/tools/perf/Documentation/perf-trace.txt
+++ b/tools/perf/Documentation/perf-trace.txt
@@ -247,6 +247,10 @@ the thread executes on the designated CPUs. Default is to monitor all CPUs.
pretty-printing serves as a fallback to hand-crafted pretty printers, as the latter can
better pretty-print integer flags and struct pointers.
+--bitmask-list::
+ Show bitmasks as a human-readable, condensed list (e.g. "0,2-5,7")
+ instead of the default hexadecimal representation.
+
--bpf-summary::
Collect system call statistics in BPF. This is only for live mode and
works well with -s/--summary option where no argument information is
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index 5808adad52ae..9307c2aae4ba 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -226,6 +226,7 @@ struct trace {
bool force;
bool vfs_getname;
bool force_btf;
+ bool bitmask_list;
bool summary_bpf;
int trace_pgfaults;
char *perfconfig_events;
@@ -3280,6 +3281,9 @@ static size_t trace__fprintf_tp_fields(struct trace *trace, struct perf_sample *
if (syscall_arg.len == 0) {
printed += scnprintf(bf + printed, size - printed, "0");
+ } else if (trace->bitmask_list) {
+ printed += bitmap_scnprintf(mask, syscall_arg.len * 8,
+ bf + printed, size - printed);
} else {
int i;
bool skip_zero = true;
@@ -5590,6 +5594,7 @@ int cmd_trace(int argc, const char **argv)
"start"),
OPT_BOOLEAN(0, "force-btf", &trace.force_btf, "Prefer btf_dump general pretty printer"
"to customized ones"),
+ OPT_BOOLEAN(0, "bitmask-list", &trace.bitmask_list, "Show bitmask as a human-readable list"),
OPT_BOOLEAN(0, "bpf-summary", &trace.summary_bpf, "Summary syscall stats in BPF"),
OPT_INTEGER(0, "max-summary", &trace.max_summary,
"Max number of entries in the summary."),
--
2.54.0
^ permalink raw reply related [flat|nested] 3+ messages in thread