* [PATCH 0/6] perf: Add support for memory region/range reporting
@ 2026-07-09 22:17 Thomas Falcon
2026-07-09 22:17 ` [PATCH 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Thomas Falcon @ 2026-07-09 22:17 UTC (permalink / raw)
To: linux-perf-users
Cc: linux-kernel, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Dapeng Mi
Add support for two memory-related reporting features in the perf tool:
1. Memory region reporting in perf-c2c and perf-script subcommands.
2. Memory range data in perf.data file header and perf-c2c subcommand.
Memory region reporting was introduced as part of support for the
Off-module Response facility (OMR) [1], which provides a new data
source encoding supporting "up to 8 fine-grained memory regions in
addition to the cache region, offering more detailed insights into
memory access regions."
Memory Range support was introduced with the addition of the ACPI Memory
Range and Region Mapping (MRRM) [2] table. It provides a base address plus
a length value for each memory range, as well as NUMA node and local and
remote "region ID's" so that "platform firmware can indicate the type of
memory for each range."[3]
Include a change allowing PERF_MEM_LVLNUM_L0 to be printed in
perf-mem output as well.
[1]: https://lore.kernel.org/all/20260114011750.350569-1-dapeng1.mi@linux.intel.com/
[2]: https://lore.kernel.org/lkml/20250505173819.419271-1-tony.luck@intel.com/
[3]: MRRM definition allow for future expansion for the OS to assign
these region IDs.
Dapeng Mi (3):
perf mem: Add support for printing PERF_MEM_LVLNUM_L0
perf tools: Show memory region in perf-c2c subcommand
perf tools: Show memory region in perf-script subcommand
Thomas Falcon (3):
perf mem: Fix size tracking for mem_lvl's in
perf_script__meminfo_scnprintf()
perf header: Support memory ranges
perf c2c: print memory region data with stdio output
tools/perf/Documentation/perf-record.txt | 2 +-
.../Documentation/perf.data-file-format.txt | 13 ++
tools/perf/builtin-c2c.c | 86 +++++++-
tools/perf/builtin-inject.c | 1 +
tools/perf/builtin-script.c | 8 +-
tools/perf/util/bpf-filter.l | 1 +
tools/perf/util/env.c | 1 +
tools/perf/util/env.h | 12 ++
tools/perf/util/header.c | 187 ++++++++++++++++++
tools/perf/util/header.h | 1 +
tools/perf/util/mem-events.c | 75 ++++++-
tools/perf/util/mem-events.h | 1 +
.../scripting-engines/trace-event-python.c | 4 +-
13 files changed, 382 insertions(+), 10 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf()
2026-07-09 22:17 [PATCH 0/6] perf: Add support for memory region/range reporting Thomas Falcon
@ 2026-07-09 22:17 ` Thomas Falcon
2026-07-09 22:28 ` sashiko-bot
2026-07-09 22:17 ` [PATCH 2/6] perf mem: Add support for printing PERF_MEM_LVLNUM_L0 Thomas Falcon
` (4 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Thomas Falcon @ 2026-07-09 22:17 UTC (permalink / raw)
To: linux-perf-users
Cc: linux-kernel, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Dapeng Mi
When printing memory info in perf script, the entire size of the
buffer is passed to perf_mem__lvl_scnprintf() instead of the remaining
size. Pass the remaining buffer size instead.
This issue was detected by sashiko during an internal code review.
Assisted-by: Sashiko:gemini-3.1-pro-preview
Fixes: fdefc3750e847 ("perf mem: Print memory operation type")
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
---
tools/perf/util/mem-events.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
index 0b49fce251fc..4e490f9cd348 100644
--- a/tools/perf/util/mem-events.c
+++ b/tools/perf/util/mem-events.c
@@ -610,7 +610,7 @@ int perf_script__meminfo_scnprintf(char *out, size_t sz, const struct mem_info *
i += scnprintf(out, sz, "|OP ");
i += perf_mem__op_scnprintf(out + i, sz - i, mem_info);
i += scnprintf(out + i, sz - i, "|LVL ");
- i += perf_mem__lvl_scnprintf(out + i, sz, mem_info);
+ i += perf_mem__lvl_scnprintf(out + i, sz - i, mem_info);
i += scnprintf(out + i, sz - i, "|SNP ");
i += perf_mem__snp_scnprintf(out + i, sz - i, mem_info);
i += scnprintf(out + i, sz - i, "|TLB ");
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 2/6] perf mem: Add support for printing PERF_MEM_LVLNUM_L0
2026-07-09 22:17 [PATCH 0/6] perf: Add support for memory region/range reporting Thomas Falcon
2026-07-09 22:17 ` [PATCH 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
@ 2026-07-09 22:17 ` Thomas Falcon
2026-07-09 22:17 ` [PATCH 3/6] perf tools: Show memory region in perf-c2c subcommand Thomas Falcon
` (3 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Thomas Falcon @ 2026-07-09 22:17 UTC (permalink / raw)
To: linux-perf-users
Cc: linux-kernel, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Dapeng Mi
From: Dapeng Mi <dapeng1.mi@linux.intel.com>
Add support for printing PERF_MEM_LVLNUM_L0 in perf mem report.
Assisted-by: Sashiko:gemini-3.1-pro-preview
Signed-off-by: Dapeng Mi <dapeng1.mi@linux.intel.com>
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
---
tools/perf/Documentation/perf-record.txt | 2 +-
tools/perf/util/bpf-filter.l | 1 +
tools/perf/util/mem-events.c | 5 +++++
tools/perf/util/mem-events.h | 1 +
4 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt
index 178f483140ed..b54032efe41c 100644
--- a/tools/perf/Documentation/perf-record.txt
+++ b/tools/perf/Documentation/perf-record.txt
@@ -212,7 +212,7 @@ OPTIONS
The <value> can be one of:
<number> (for any term)
na, load, store, pfetch, exec (for mem_op)
- l1, l2, l3, l4, cxl, io, any_cache, lfb, ram, pmem (for mem_lvl)
+ l0, l1, l2, l3, l4, cxl, io, any_cache, lfb, ram, pmem (for mem_lvl)
na, none, hit, miss, hitm, fwd, peer (for mem_snoop)
remote (for mem_remote)
na, locked (for mem_locked)
diff --git a/tools/perf/util/bpf-filter.l b/tools/perf/util/bpf-filter.l
index 6aa65ade3385..1be9df6550fc 100644
--- a/tools/perf/util/bpf-filter.l
+++ b/tools/perf/util/bpf-filter.l
@@ -131,6 +131,7 @@ store { return constant(PERF_MEM_OP_STORE); }
pfetch { return constant(PERF_MEM_OP_PFETCH); }
exec { return constant(PERF_MEM_OP_EXEC); }
+l0 { return constant(PERF_MEM_LVLNUM_L0); }
l1 { return constant(PERF_MEM_LVLNUM_L1); }
l2 { return constant(PERF_MEM_LVLNUM_L2); }
l3 { return constant(PERF_MEM_LVLNUM_L3); }
diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
index 4e490f9cd348..4fd48fd20055 100644
--- a/tools/perf/util/mem-events.c
+++ b/tools/perf/util/mem-events.c
@@ -391,6 +391,7 @@ static const char * const mem_lvlnum[] = {
[PERF_MEM_LVLNUM_L4] = "L4",
[PERF_MEM_LVLNUM_L2_MHB] = "L2 MHB",
[PERF_MEM_LVLNUM_MSC] = "Memory-side Cache",
+ [PERF_MEM_LVLNUM_L0] = "L0",
[PERF_MEM_LVLNUM_UNC] = "Uncached",
[PERF_MEM_LVLNUM_CXL] = "CXL",
[PERF_MEM_LVLNUM_IO] = "I/O",
@@ -831,6 +832,8 @@ int mem_stat_index(const enum mem_stat_type mst, const u64 val)
}
case PERF_MEM_STAT_CACHE:
switch (src.mem_lvl_num) {
+ case PERF_MEM_LVLNUM_L0:
+ return MEM_STAT_CACHE_L0;
case PERF_MEM_LVLNUM_L1:
return MEM_STAT_CACHE_L1;
case PERF_MEM_LVLNUM_L2:
@@ -915,6 +918,8 @@ const char *mem_stat_name(const enum mem_stat_type mst, const int idx)
}
case PERF_MEM_STAT_CACHE:
switch (idx) {
+ case MEM_STAT_CACHE_L0:
+ return "L0";
case MEM_STAT_CACHE_L1:
return "L1";
case MEM_STAT_CACHE_L2:
diff --git a/tools/perf/util/mem-events.h b/tools/perf/util/mem-events.h
index 5b98076904b0..daa22748f9fe 100644
--- a/tools/perf/util/mem-events.h
+++ b/tools/perf/util/mem-events.h
@@ -109,6 +109,7 @@ enum mem_stat_op {
};
enum mem_stat_cache {
+ MEM_STAT_CACHE_L0,
MEM_STAT_CACHE_L1,
MEM_STAT_CACHE_L2,
MEM_STAT_CACHE_L3,
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 3/6] perf tools: Show memory region in perf-c2c subcommand
2026-07-09 22:17 [PATCH 0/6] perf: Add support for memory region/range reporting Thomas Falcon
2026-07-09 22:17 ` [PATCH 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
2026-07-09 22:17 ` [PATCH 2/6] perf mem: Add support for printing PERF_MEM_LVLNUM_L0 Thomas Falcon
@ 2026-07-09 22:17 ` Thomas Falcon
2026-07-09 22:29 ` sashiko-bot
2026-07-09 22:17 ` [PATCH 4/6] perf tools: Show memory region in perf-script subcommand Thomas Falcon
` (2 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Thomas Falcon @ 2026-07-09 22:17 UTC (permalink / raw)
To: linux-perf-users
Cc: linux-kernel, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Dapeng Mi
From: Dapeng Mi <dapeng1.mi@linux.intel.com>
Add memory region field to the cacheline list view to help users
identify the memory region to which the cacheline belongs. The memory
region field was included with the introduction of support for the
Off-module Response facility (OMR) [1] in Intel's Diamond Rapids and
Nova Lake architectures.
An example of the new perf c2c output including the memory region
field is shown below:
Shared Data Cache Line Table (112 entries, sorted on Total HITMs)
--------------- Cacheline -------------- Tot ------- Load Hitm ------- Total Total Total
Index Address Region Node PA cnt Hitm Total LclHitm RmtHitm records Loads Stores
0 0xff1b7032a1e8c5c0 N/A 1 47 2.25% 50 44 6 1024 1023 1
1 0xff1b6ff3255bb880 N/A 0 11 1.62% 36 34 2 96 93 4
2 0xff1b70328e3b9880 N/A 1 13 1.49% 33 33 0 100 91 9
3 0xff1b70328b023800 N/A 1 1 1.40% 31 14 17 52 48 4
4 0xff1b70328e3b9c00 N/A 1 1 1.31% 29 27 2 67 33 34
5 0xff1b70328b0237c0 N/A 1 1 1.26% 28 10 18 154 150 4
6 0xff1b6ff3255bbc00 N/A 0 1 1.13% 25 25 0 48 25 23
7 0xff1b6ff3255bba40 N/A 0 1 0.99% 22 22 0 46 23 23
8 0xff3ab9ba50255c80 N/A N/A 0 0.77% 17 15 2 35 35 0
9 0xff3ab9ba503e3040 N/A N/A 0 0.77% 17 11 6 37 37 1
10 0xff1b703289e88f40 N/A 1 33 0.72% 16 9 7 69 63 6
11 0xff1b70328e3b9a40 N/A 1 1 0.68% 15 15 0 43 17 26
12 0xff1b7032c9fd6a40 N/A 1 15 0.68% 15 15 0 57 54 4
13 0xff1b7032a1e8c980 N/A 1 27 0.54% 12 11 1 761 761 0
14 0xff1b70727ffd57c0 N/A 1 7 0.54% 12 12 0 219 218 1
15 0xffffffffaefe2380 N/A 1 1 0.50% 11 8 3 14 14 0
Note: DMR simics does not support memory regions. Since the output is
captured on SPR, the memory region field shows "N/A" for all cachelines.
[1]: https://lore.kernel.org/all/20260114011750.350569-1-dapeng1.mi@linux.intel.com/
Assisted-by: Sashiko:gemini-3.1-pro-preview
Signed-off-by: Dapeng Mi <dapeng1.mi@linux.intel.com>
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
---
tools/perf/builtin-c2c.c | 56 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 54 insertions(+), 2 deletions(-)
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index c9584dbedf77..57e822dbd2d4 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -74,6 +74,7 @@ struct c2c_hist_entry {
unsigned long *nodeset;
struct c2c_stats *node_stats;
unsigned int cacheline_idx;
+ unsigned int mem_region;
struct compute_stats cstats;
@@ -281,6 +282,18 @@ static void c2c_he__set_node(struct c2c_hist_entry *c2c_he,
}
}
+static void c2c_he__set_mem_region(struct c2c_hist_entry *c2c_he,
+ unsigned int mem_region)
+{
+ if (WARN_ONCE(mem_region > PERF_MEM_REGION_MEM7,
+ "WARNING: invalid memory region ID"))
+ return;
+
+ /* Update mem_region only if it really accesses memory */
+ if (mem_region >= PERF_MEM_REGION_MMIO)
+ c2c_he->mem_region = mem_region;
+}
+
static void compute_stats(struct c2c_hist_entry *c2c_he,
struct c2c_stats *stats,
u64 weight)
@@ -339,6 +352,7 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
struct addr_location al;
struct mem_info *mi = NULL;
struct callchain_cursor *cursor;
+ unsigned int mem_region;
int ret;
addr_location__init(&al);
@@ -366,6 +380,7 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
}
c2c_decode_stats(&stats, mi);
+ mem_region = mem_info__data_src(mi)->mem_region;
he = hists__add_entry_ops(&c2c_hists->hists, &c2c_entry_ops,
&al, NULL, NULL, mi, NULL,
@@ -382,6 +397,7 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
c2c_he__set_cpu(c2c_he, sample);
c2c_he__set_node(c2c_he, sample);
c2c_he__set_evsel(c2c_he, evsel);
+ c2c_he__set_mem_region(c2c_he, mem_region);
hists__inc_nr_samples(&c2c_hists->hists, he->filtered);
@@ -435,6 +451,7 @@ static int process_sample_event(const struct perf_tool *tool __maybe_unused,
c2c_he__set_cpu(c2c_he, sample);
c2c_he__set_node(c2c_he, sample);
c2c_he__set_evsel(c2c_he, evsel);
+ c2c_he__set_mem_region(c2c_he, mem_region);
hists__inc_nr_samples(&c2c_hists->hists, he->filtered);
ret = hist_entry__append_callchain(he, sample);
@@ -603,6 +620,29 @@ dcacheline_node_count(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
return scnprintf(hpp->buf, hpp->size, "%*lu", width, c2c_he->paddr_cnt);
}
+static int
+dcacheline_node_mem_region(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he)
+{
+ int width = c2c_width(fmt, hpp, he->hists);
+ struct c2c_hist_entry *c2c_he;
+ unsigned int mem_region;
+ char buf[20];
+
+ c2c_he = container_of(he, struct c2c_hist_entry, he);
+ mem_region = c2c_he->mem_region;
+
+ if (mem_region == PERF_MEM_REGION_NA)
+ scnprintf(buf, sizeof(buf), "N/A");
+ /* mem_region could only be >= PERF_MEM_REGION_MMIO */
+ else if (mem_region == PERF_MEM_REGION_MMIO)
+ scnprintf(buf, sizeof(buf), "MMIO");
+ else
+ scnprintf(buf, sizeof(buf), "0x%x", mem_region - 0x8);
+
+ return scnprintf(hpp->buf, hpp->size, "%*s", width, buf);
+}
+
static int offset_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
struct hist_entry *he)
{
@@ -1425,7 +1465,7 @@ cl_idx_empty_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
}
static struct c2c_dimension dim_dcacheline = {
- .header = HEADER_SPAN("--- Cacheline ----", "Address", 2),
+ .header = HEADER_SPAN("--- Cacheline ----", "Address", 3),
.name = "dcacheline",
.cmp = dcacheline_cmp,
.entry = dcacheline_entry,
@@ -1440,6 +1480,14 @@ static struct c2c_dimension dim_dcacheline_node = {
.width = 4,
};
+static struct c2c_dimension dim_dcacheline_mem_region = {
+ .header = HEADER_LOW("Region"),
+ .name = "dcacheline_mem_region",
+ .cmp = empty_cmp,
+ .entry = dcacheline_node_mem_region,
+ .width = 6,
+};
+
static struct c2c_dimension dim_dcacheline_count = {
.header = HEADER_LOW("PA cnt"),
.name = "dcacheline_count",
@@ -1871,6 +1919,7 @@ static struct c2c_dimension dim_dcacheline_num_empty = {
static struct c2c_dimension *dimensions[] = {
&dim_dcacheline,
+ &dim_dcacheline_mem_region,
&dim_dcacheline_node,
&dim_dcacheline_count,
&dim_offset,
@@ -2898,8 +2947,9 @@ static int ui_quirks(void)
/* Fix the zero line for dcacheline column. */
buf = fill_line(chk_double_cl ? "Double-Cacheline" : "Cacheline",
dim_dcacheline.width +
+ dim_dcacheline_mem_region.width +
dim_dcacheline_node.width +
- dim_dcacheline_count.width + 4);
+ dim_dcacheline_count.width + 6);
if (!buf)
return -ENOMEM;
@@ -3319,6 +3369,7 @@ static int perf_c2c__report(int argc, const char **argv)
if (c2c.display != DISPLAY_SNP_PEER)
output_str = "cl_idx,"
"dcacheline,"
+ "dcacheline_mem_region,"
"dcacheline_node,"
"dcacheline_count,"
"percent_costly_snoop,"
@@ -3334,6 +3385,7 @@ static int perf_c2c__report(int argc, const char **argv)
else
output_str = "cl_idx,"
"dcacheline,"
+ "dcacheline_mem_region,"
"dcacheline_node,"
"dcacheline_count,"
"percent_costly_snoop,"
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 4/6] perf tools: Show memory region in perf-script subcommand
2026-07-09 22:17 [PATCH 0/6] perf: Add support for memory region/range reporting Thomas Falcon
` (2 preceding siblings ...)
2026-07-09 22:17 ` [PATCH 3/6] perf tools: Show memory region in perf-c2c subcommand Thomas Falcon
@ 2026-07-09 22:17 ` Thomas Falcon
2026-07-09 22:28 ` sashiko-bot
2026-07-09 22:17 ` [PATCH 5/6] perf header: Support memory ranges Thomas Falcon
2026-07-09 22:17 ` [PATCH 6/6] perf c2c: print memory region data with stdio output Thomas Falcon
5 siblings, 1 reply; 11+ messages in thread
From: Thomas Falcon @ 2026-07-09 22:17 UTC (permalink / raw)
To: linux-perf-users
Cc: linux-kernel, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Dapeng Mi
From: Dapeng Mi <dapeng1.mi@linux.intel.com>
Show the memory region in perf-script subcommand. Memory region is found
in the mem_region field of the memory information data source. This
field was included with the introduction of support for the Off-module
Response facility (OMR) [1] in Intel's Diamond Rapids and Nova Lake
Architectures.
An example of perf-script output with the new memory region field is shown
below:
random 10e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK N/A|Region N/A 7f32ae64a2ec
random 2411a68201042 |OP LOAD|LVL RAM hit|SNP Hit|TLB L1 or L2 hit|LCK No|BLK N/A|Region Mem-1 561de7f8910a
random 10e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK N/A|Region N/A 7f32ae64a2d0
random 20e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK Data|Region N/A 7f32ae64a2d0
random 20e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK Data|Region N/A 7f32ae64a2d0
random 10e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK N/A|Region N/A 7f32ae64a2ec
random 10e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK N/A|Region N/A 7f32ae64a2d0
random 10e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK N/A|Region N/A 7f32ae64a2d0
random 2411a68201042 |OP LOAD|LVL RAM hit|SNP Hit|TLB L1 or L2 hit|LCK No|BLK N/A|Region Mem-1 561de7f8910a
random 10e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK N/A|Region N/A 7f32ae64a2ec
random 10e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK N/A|Region N/A 7f32ae64a2d0
[1]: https://lore.kernel.org/all/20260114011750.350569-1-dapeng1.mi@linux.intel.com/
Signed-off-by: Dapeng Mi <dapeng1.mi@linux.intel.com>
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
---
tools/perf/builtin-script.c | 8 +--
tools/perf/util/mem-events.c | 68 +++++++++++++++++++
.../scripting-engines/trace-event-python.c | 4 +-
3 files changed, 74 insertions(+), 6 deletions(-)
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index f91d8b1fbd01..7ed13cf261f8 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2081,8 +2081,8 @@ static int evlist__max_name_len(struct evlist *evlist)
static int data_src__fprintf(u64 data_src, FILE *fp)
{
struct mem_info *mi = mem_info__new();
- char decode[100];
- char out[100];
+ char decode[150];
+ char out[150];
static int maxlen;
int len;
@@ -2090,10 +2090,10 @@ static int data_src__fprintf(u64 data_src, FILE *fp)
return -ENOMEM;
mem_info__data_src(mi)->val = data_src;
- perf_script__meminfo_scnprintf(decode, 100, mi);
+ perf_script__meminfo_scnprintf(decode, 150, mi);
mem_info__put(mi);
- len = scnprintf(out, 100, "%16" PRIx64 " %s", data_src, decode);
+ len = scnprintf(out, 150, "%16" PRIx64 " %s", data_src, decode);
if (maxlen < len)
maxlen = len;
diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
index 4fd48fd20055..ff08f1fee0e3 100644
--- a/tools/perf/util/mem-events.c
+++ b/tools/perf/util/mem-events.c
@@ -604,6 +604,72 @@ int perf_mem__blk_scnprintf(char *out, size_t sz, const struct mem_info *mem_inf
return l;
}
+static int perf_mem__region_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
+{
+ size_t l = 0;
+ u64 mem = PERF_MEM_REGION_NA;
+
+ sz -= 1; /* -1 for null termination */
+ out[0] = '\0';
+
+ if (mem_info)
+ mem = mem_info__const_data_src(mem_info)->mem_region;
+
+ switch (mem) {
+ case PERF_MEM_REGION_NA:
+ case PERF_MEM_REGION_RSVD:
+ l += scnprintf(out + l, sz - l, " N/A");
+ break;
+ case PERF_MEM_REGION_L_SHARE:
+ l += scnprintf(out + l, sz - l, " Local-shared-cache");
+ break;
+ case PERF_MEM_REGION_L_NON_SHARE:
+ l += scnprintf(out + l, sz - l, " Local-non-shared-cache");
+ break;
+ case PERF_MEM_REGION_O_IO:
+ l += scnprintf(out + l, sz - l, " Other-IO");
+ break;
+ case PERF_MEM_REGION_O_SHARE:
+ l += scnprintf(out + l, sz - l, " Other-shared-cache");
+ break;
+ case PERF_MEM_REGION_O_NON_SHARE:
+ l += scnprintf(out + l, sz - l, " Other-non-shared-cache");
+ break;
+ case PERF_MEM_REGION_MMIO:
+ l += scnprintf(out + l, sz - l, " MMIO");
+ break;
+ case PERF_MEM_REGION_MEM0:
+ l += scnprintf(out + l, sz - l, " Mem-0");
+ break;
+ case PERF_MEM_REGION_MEM1:
+ l += scnprintf(out + l, sz - l, " Mem-1");
+ break;
+ case PERF_MEM_REGION_MEM2:
+ l += scnprintf(out + l, sz - l, " Mem-2");
+ break;
+ case PERF_MEM_REGION_MEM3:
+ l += scnprintf(out + l, sz - l, " Mem-3");
+ break;
+ case PERF_MEM_REGION_MEM4:
+ l += scnprintf(out + l, sz - l, " Mem-4");
+ break;
+ case PERF_MEM_REGION_MEM5:
+ l += scnprintf(out + l, sz - l, " Mem-5");
+ break;
+ case PERF_MEM_REGION_MEM6:
+ l += scnprintf(out + l, sz - l, " Mem-6");
+ break;
+ case PERF_MEM_REGION_MEM7:
+ l += scnprintf(out + l, sz - l, " Mem-7");
+ break;
+ default:
+ l += scnprintf(out + l, sz - l, " N/A");
+ break;
+ }
+
+ return l;
+}
+
int perf_script__meminfo_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
{
int i = 0;
@@ -620,6 +686,8 @@ int perf_script__meminfo_scnprintf(char *out, size_t sz, const struct mem_info *
i += perf_mem__lck_scnprintf(out + i, sz - i, mem_info);
i += scnprintf(out + i, sz - i, "|BLK ");
i += perf_mem__blk_scnprintf(out + i, sz - i, mem_info);
+ i += scnprintf(out + i, sz - i, "|Region ");
+ i += perf_mem__region_scnprintf(out + i, sz - i, mem_info);
return i;
}
diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c
index 8f832ae316ca..4e93f9d2dc8e 100644
--- a/tools/perf/util/scripting-engines/trace-event-python.c
+++ b/tools/perf/util/scripting-engines/trace-event-python.c
@@ -695,7 +695,7 @@ static void set_sample_datasrc_in_dict(PyObject *dict,
struct perf_sample *sample)
{
struct mem_info *mi = mem_info__new();
- char decode[100];
+ char decode[150];
if (!mi)
Py_FatalError("couldn't create mem-info");
@@ -704,7 +704,7 @@ static void set_sample_datasrc_in_dict(PyObject *dict,
PyLong_FromUnsignedLongLong(sample->data_src));
mem_info__data_src(mi)->val = sample->data_src;
- perf_script__meminfo_scnprintf(decode, 100, mi);
+ perf_script__meminfo_scnprintf(decode, 150, mi);
mem_info__put(mi);
pydict_set_item_string_decref(dict, "datasrc_decode",
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 5/6] perf header: Support memory ranges
2026-07-09 22:17 [PATCH 0/6] perf: Add support for memory region/range reporting Thomas Falcon
` (3 preceding siblings ...)
2026-07-09 22:17 ` [PATCH 4/6] perf tools: Show memory region in perf-script subcommand Thomas Falcon
@ 2026-07-09 22:17 ` Thomas Falcon
2026-07-09 22:31 ` sashiko-bot
2026-07-09 22:17 ` [PATCH 6/6] perf c2c: print memory region data with stdio output Thomas Falcon
5 siblings, 1 reply; 11+ messages in thread
From: Thomas Falcon @ 2026-07-09 22:17 UTC (permalink / raw)
To: linux-perf-users
Cc: linux-kernel, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Dapeng Mi
Memory ranges were created to track different types of memory,
such as persistent, high bandwidth, or CXL-attached, that may
be present on a system for performance monitoring and resource
control purposes.
Memory range data are parsed from the ACPI MRRM table and exposed
to userspace tools via sysfs [1]. Memory range data is read from:
/sys/firmware/acpi/memory_ranges/rangeX
With the following attributes:
u64 base;
u64 length;
int node;
u8 local_region_id;
u8 remote_region_id;
Read memory range data from sysfs if present and save it in
the header of the perf data file under a new feature bit,
HEADER_MEMORY_RANGES (35). Memory range data can be viewed with the
--header or --header-only options of perf-report and perf-script.
[1]: https://lore.kernel.org/lkml/20250505173819.419271-1-tony.luck@intel.com/
Assisted-by: Sashiko:gemini-3.1-pro-preview
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
---
.../Documentation/perf.data-file-format.txt | 13 ++
tools/perf/builtin-inject.c | 1 +
tools/perf/util/env.c | 1 +
tools/perf/util/env.h | 12 ++
| 187 ++++++++++++++++++
| 1 +
6 files changed, 215 insertions(+)
diff --git a/tools/perf/Documentation/perf.data-file-format.txt b/tools/perf/Documentation/perf.data-file-format.txt
index b90cba9168f8..1f8e7d8e9ae2 100644
--- a/tools/perf/Documentation/perf.data-file-format.txt
+++ b/tools/perf/Documentation/perf.data-file-format.txt
@@ -478,6 +478,19 @@ The size of the cacheline in bytes. Format:
unsigned int cln_size;
+ HEADER_MEMORY_RANGES = 35,
+
+List of memory ranges. The format of the data is as below.
+
+u32 nr_memory_ranges;
+struct memory_range {
+ u64 base;
+ u64 length;
+ int node;
+ u8 local_region_id;
+ u8 remote_region_id;
+}; [nr_memory_ranges]
+
other bits are reserved and should be ignored for now
HEADER_FEAT_BITS = 256,
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 70bbfad5653e..169617a884d3 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -2306,6 +2306,7 @@ static bool keep_feat(struct perf_inject *inject, int feat)
case HEADER_PMU_CAPS:
case HEADER_CPU_DOMAIN_INFO:
case HEADER_CLN_SIZE:
+ case HEADER_MEMORY_RANGES:
return true;
/* Information that can be updated */
case HEADER_BUILD_ID:
diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c
index c0e2b9d5f0b2..50993b0dedc5 100644
--- a/tools/perf/util/env.c
+++ b/tools/perf/util/env.c
@@ -297,6 +297,7 @@ void perf_env__exit(struct perf_env *env)
zfree(&env->pmu_caps[i].pmu_name);
}
zfree(&env->pmu_caps);
+ zfree(&env->memory_ranges);
free_cpu_domain_info(env->cpu_domain, env->schedstat_version, env->nr_cpus_avail);
}
diff --git a/tools/perf/util/env.h b/tools/perf/util/env.h
index 7acca39b42ff..6f0869a9747a 100644
--- a/tools/perf/util/env.h
+++ b/tools/perf/util/env.h
@@ -68,6 +68,16 @@ struct cpu_domain_map {
struct domain_info **domains;
};
+struct memory_range {
+ u64 base;
+ u64 length;
+ int node;
+ u8 local_region_id;
+ u8 remote_region_id;
+};
+
+typedef const char *(arch_syscalls__strerrno_t)(int err);
+
struct perf_env {
char *hostname;
char *os_release;
@@ -100,6 +110,7 @@ struct perf_env {
int nr_cpu_pmu_caps;
int nr_hybrid_nodes;
int nr_pmus_with_caps;
+ int nr_memory_ranges;
char *cmdline;
const char **cmdline_argv;
char *sibling_cores;
@@ -122,6 +133,7 @@ struct perf_env {
unsigned long long memory_bsize;
struct hybrid_node *hybrid_nodes;
struct pmu_caps *pmu_caps;
+ struct memory_range *memory_ranges;
#ifdef HAVE_LIBBPF_SUPPORT
/*
* bpf_info_lock protects bpf rbtrees. This is needed because the
--git a/tools/perf/util/header.c b/tools/perf/util/header.c
index e90e541f546b..f99a129d3cff 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -92,6 +92,7 @@
#define MAX_PMU_CAPS 512
#define MAX_PMU_MAPPINGS 4096
#define MAX_SCHED_DOMAINS 64
+#define MAX_MEMORY_RANGES 64
/*
* magic2 = "PERFILE2"
@@ -1891,6 +1892,126 @@ static int write_cpu_domain_info(struct feat_fd *ff,
return ret;
}
+static int memory_range__read(struct memory_range *range, int idx)
+{
+ char path[PATH_MAX], file[PATH_MAX];
+ struct stat st;
+ int tmp;
+
+ scnprintf(path, PATH_MAX, "firmware/acpi/memory_ranges/range%d", idx);
+ scnprintf(file, PATH_MAX, "%s/%s", sysfs__mountpoint(), path);
+ if (stat(file, &st))
+ return -1;
+
+ scnprintf(file, PATH_MAX, "%s/base", path);
+ if (sysfs__read_xll(file, (unsigned long long *) &range->base))
+ return -1;
+
+ scnprintf(file, PATH_MAX, "%s/length", path);
+ if (sysfs__read_xll(file, (unsigned long long *) &range->length))
+ return -1;
+
+ scnprintf(file, PATH_MAX, "%s/node", path);
+ if (sysfs__read_int(file, (int *) &range->node))
+ return -1;
+
+ scnprintf(file, PATH_MAX, "%s/local_region_id", path);
+ if (sysfs__read_int(file, &tmp))
+ return -1;
+
+ if (tmp < 0 || tmp > 255)
+ return -1;
+ range->local_region_id = tmp;
+
+ scnprintf(file, PATH_MAX, "%s/remote_region_id", path);
+ if (sysfs__read_int(file, &tmp))
+ return -1;
+
+ if (tmp < 0 || tmp > 255)
+ return -1;
+ range->remote_region_id = tmp;
+
+ return 0;
+}
+
+static int memory_range__parse(struct memory_range **ranges)
+{
+ int i, err, nr_memory_ranges = 0;
+ char path[PATH_MAX];
+ struct stat st;
+
+ scnprintf(path, PATH_MAX, "%s/firmware/acpi/memory_ranges", sysfs__mountpoint());
+ if (stat(path, &st))
+ return 0;
+
+ while (1) {
+ scnprintf(path, PATH_MAX, "%s/firmware/acpi/memory_ranges/range%d", sysfs__mountpoint(), nr_memory_ranges);
+ if (stat(path, &st))
+ break;
+
+ nr_memory_ranges++;
+ }
+
+ if (nr_memory_ranges == 0)
+ return 0;
+
+ *ranges = zalloc(nr_memory_ranges * sizeof(struct memory_range));
+ if (!(*ranges))
+ return -ENOMEM;
+
+ for (i = 0; i < nr_memory_ranges; i++) {
+ struct memory_range range;
+
+ err = memory_range__read(&range, i);
+ if (err < 0)
+ goto out_error;
+
+ (*ranges)[i] = range;
+ }
+
+ return nr_memory_ranges;
+
+out_error:
+ zfree(ranges);
+ return -1;
+}
+
+static int write_memory_ranges(struct feat_fd *ff,
+ struct evlist *evlist __maybe_unused)
+{
+ struct memory_range *ranges = NULL;
+ int nr_memory_ranges = 0, ret;
+
+ nr_memory_ranges = memory_range__parse(&ranges);
+ if (nr_memory_ranges < 0)
+ return nr_memory_ranges;
+
+ ret = do_write(ff, &nr_memory_ranges, sizeof(nr_memory_ranges));
+ if (ret < 0)
+ goto out;
+
+ for (int i = 0; i < nr_memory_ranges; i++) {
+ ret = do_write(ff, &ranges[i].base, sizeof(u64));
+ if (ret < 0)
+ goto out;
+ ret = do_write(ff, &ranges[i].length, sizeof(u64));
+ if (ret < 0)
+ goto out;
+ ret = do_write(ff, &ranges[i].node, sizeof(u32));
+ if (ret < 0)
+ goto out;
+ ret = do_write(ff, &ranges[i].local_region_id, sizeof(u8));
+ if (ret < 0)
+ goto out;
+ ret = do_write(ff, &ranges[i].remote_region_id, sizeof(u8));
+ if (ret < 0)
+ goto out;
+ }
+out:
+ zfree(&ranges);
+ return ret;
+}
+
static void print_hostname(struct feat_fd *ff, FILE *fp)
{
fprintf(fp, "# hostname : %s\n", ff->ph->env.hostname);
@@ -2628,6 +2749,23 @@ static void print_cpu_domain_info(struct feat_fd *ff, FILE *fp)
}
}
+static void print_memory_ranges(struct feat_fd *ff, FILE *fp)
+{
+ struct memory_range *ranges = ff->ph->env.memory_ranges;
+ int nr_memory_ranges = ff->ph->env.nr_memory_ranges;
+ int i;
+
+ fprintf(fp, "# memory ranges (nr %d):\n", nr_memory_ranges);
+
+ for (i = 0; i < nr_memory_ranges; i++) {
+ fprintf(fp, "# range%u: 0x%16" PRIx64 "-0x%16" PRIx64,
+ i, ranges[i].base, ranges[i].base + ranges[i].length - 1);
+ fprintf(fp, ", node = %d, local_region_id = %d, remote_region_id = %d\n",
+ ranges[i].node, ranges[i].local_region_id,
+ ranges[i].remote_region_id);
+ }
+}
+
static int __event_process_build_id(struct perf_record_header_build_id *bev,
char *filename,
struct perf_session *session)
@@ -4201,6 +4339,54 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused
return ret;
}
+static int process_memory_ranges(struct feat_fd *ff, void *data __maybe_unused)
+{
+ struct perf_env *env = &ff->ph->env;
+ struct memory_range *ranges, *r;
+ u32 nr_memory_ranges, i;
+
+ if (do_read_u32(ff, &nr_memory_ranges))
+ return -1;
+
+ if (!nr_memory_ranges) {
+ pr_debug("memory ranges not available\n");
+ return 0;
+ }
+
+ if (nr_memory_ranges > MAX_MEMORY_RANGES) {
+ pr_err("Invalid memory_ranges: nr_memory_ranges (%u) > %u\n",
+ nr_memory_ranges, MAX_MEMORY_RANGES);
+ return -1;
+ }
+
+ ranges = zalloc(nr_memory_ranges * sizeof(*ranges));
+ if (!ranges)
+ return -1;
+
+ for (i = 0; i < nr_memory_ranges; i++) {
+ r = &ranges[i];
+
+ if (do_read_u64(ff, &r->base))
+ goto error;
+ if (do_read_u64(ff, &r->length))
+ goto error;
+ if (do_read_u32(ff, (u32 *) &r->node))
+ goto error;
+ if (__do_read(ff, &r->local_region_id, sizeof(u8)))
+ goto error;
+ if (__do_read(ff, &r->remote_region_id, sizeof(u8)))
+ goto error;
+ }
+
+ env->memory_ranges = ranges;
+ env->nr_memory_ranges = nr_memory_ranges;
+
+ return 0;
+error:
+ zfree(&ranges);
+ return -1;
+}
+
#define FEAT_OPR(n, func, __full_only) \
[HEADER_##n] = { \
.name = __stringify(n), \
@@ -4265,6 +4451,7 @@ const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE] = {
FEAT_OPR(CPU_DOMAIN_INFO, cpu_domain_info, true),
FEAT_OPR(E_MACHINE, e_machine, false),
FEAT_OPR(CLN_SIZE, cln_size, false),
+ FEAT_OPR(MEMORY_RANGES, memory_ranges, false),
};
struct header_print_data {
--git a/tools/perf/util/header.h b/tools/perf/util/header.h
index 5e03f884b7cc..765037762758 100644
--- a/tools/perf/util/header.h
+++ b/tools/perf/util/header.h
@@ -56,6 +56,7 @@ enum {
HEADER_CPU_DOMAIN_INFO,
HEADER_E_MACHINE,
HEADER_CLN_SIZE,
+ HEADER_MEMORY_RANGES,
HEADER_LAST_FEATURE,
HEADER_FEAT_BITS = 256,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 6/6] perf c2c: print memory region data with stdio output
2026-07-09 22:17 [PATCH 0/6] perf: Add support for memory region/range reporting Thomas Falcon
` (4 preceding siblings ...)
2026-07-09 22:17 ` [PATCH 5/6] perf header: Support memory ranges Thomas Falcon
@ 2026-07-09 22:17 ` Thomas Falcon
5 siblings, 0 replies; 11+ messages in thread
From: Thomas Falcon @ 2026-07-09 22:17 UTC (permalink / raw)
To: linux-perf-users
Cc: linux-kernel, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Dapeng Mi
Print memory range data in perf-c2c subcommand. Only available in stdio
mode currently. If memory ranges are not supported or present, print
nothing.
Assisted-by: Sashiko:gemini-3.1-pro-preview
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
---
tools/perf/builtin-c2c.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index 57e822dbd2d4..3fb8afba25be 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -2634,6 +2634,34 @@ static void print_c2c_info(FILE *out, struct perf_session *session)
fprintf(out, " Cacheline data grouping : %s\n", c2c.cl_sort);
}
+
+static void print_memory_ranges_info(FILE *out, struct perf_session *session)
+{
+ struct perf_env *env = perf_session__env(session);
+ int nr_ranges = 0;
+
+ if (!perf_header__has_feat(&session->header, HEADER_MEMORY_RANGES))
+ return;
+ nr_ranges = env->nr_memory_ranges;
+ if (nr_ranges == 0) {
+ pr_debug("No memory ranges found, skipping\n");
+ return;
+ }
+
+ fprintf(out, "\n");
+ fprintf(out, "=================================================\n");
+ fprintf(out, " Memory Ranges \n");
+ fprintf(out, "=================================================\n");
+
+ for (int i = 0; i < nr_ranges; i++) {
+ struct memory_range *r = &env->memory_ranges[i];
+
+ fprintf(out, "Range %d: [0x%016" PRIx64 "-0x%016" PRIx64 "] Node %d, local region id %u, remote region id %u\n",
+ i, r->base, r->base + r->length - 1, r->node,
+ r->local_region_id, r->remote_region_id);
+ }
+}
+
static void perf_c2c__hists_fprintf(FILE *out, struct perf_session *session)
{
setup_pager();
@@ -2647,6 +2675,8 @@ static void perf_c2c__hists_fprintf(FILE *out, struct perf_session *session)
if (c2c.stats_only)
return;
+ print_memory_ranges_info(out, session);
+
fprintf(out, "\n");
fprintf(out, "=================================================\n");
fprintf(out, " Shared Data Cache Line Table \n");
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 4/6] perf tools: Show memory region in perf-script subcommand
2026-07-09 22:17 ` [PATCH 4/6] perf tools: Show memory region in perf-script subcommand Thomas Falcon
@ 2026-07-09 22:28 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-09 22:28 UTC (permalink / raw)
To: Thomas Falcon; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Processing a crafted perf.data file triggers a stack buffer overflow due to integer underflow in size tracking and unbounded string operations, allowing potential arbitrary code execution.
- [Medium] The destination buffer `out` is too small to hold the formatted memory information string along with its prefix, leading to silent truncation.
--
--- Patch [4]: [PATCH 4/6] perf tools: Show memory region in perf-script subcommand ---
commit 39c0417fbcfb9d114a4a74dd75932d9174d65104
Author: Dapeng Mi <dapeng1.mi@linux.intel.com>
perf tools: Show memory region in perf-script subcommand
This commit adds the memory region field to the perf-script subcommand
output. It utilizes the mem_region field introduced for Intel's OMR facility
and increases the output buffer sizes to accommodate the new region string.
Link: https://lore.kernel.org/all/20260114011750.350569-1-dapeng1.mi@linux.intel.com/
> diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
> index 9ac29bdc3cd54..e82ab5cb842e2 100644
> --- a/tools/perf/builtin-script.c
> +++ b/tools/perf/builtin-script.c
> @@ -2081,8 +2081,8 @@ static int evlist__max_name_len(struct evlist *evlist)
> static int data_src__fprintf(u64 data_src, FILE *fp)
> {
> struct mem_info *mi = mem_info__new();
> - char decode[100];
> - char out[100];
> + char decode[150];
> + char out[150];
> static int maxlen;
> int len;
>
> @@ -2090,10 +2090,10 @@ static int data_src__fprintf(u64 data_src, FILE *fp)
> return -ENOMEM;
>
> mem_info__data_src(mi)->val = data_src;
> - perf_script__meminfo_scnprintf(decode, 100, mi);
> + perf_script__meminfo_scnprintf(decode, 150, mi);
> mem_info__put(mi);
>
> - len = scnprintf(out, 100, "%16" PRIx64 " %s", data_src, decode);
> + len = scnprintf(out, 150, "%16" PRIx64 " %s", data_src, decode);
[Severity: Medium]
This is a pre-existing issue, but I am wondering if the out buffer is large
enough here in data_src__fprintf()? After the 17-byte prefix, only 132 bytes
remain for the decode string. Valid combinations of memory bits can easily
exceed this limit, especially with the addition of the new Region field.
Could this lead to silent truncation?
> if (maxlen < len)
> maxlen = len;
[ ... ]
> diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
> index 4fd48fd200552..ff08f1fee0e33 100644
> --- a/tools/perf/util/mem-events.c
> +++ b/tools/perf/util/mem-events.c
> @@ -604,6 +604,72 @@ int perf_mem__blk_scnprintf(char *out, size_t sz, const struct mem_info *mem_inf
> return l;
> }
>
> +static int perf_mem__region_scnprintf(char *out, size_t sz, const struct mem_info *mem_info)
> +{
> + size_t l = 0;
> + u64 mem = PERF_MEM_REGION_NA;
> +
> + sz -= 1; /* -1 for null termination */
> + out[0] = '\0';
[Severity: High]
This isn't a bug introduced by this patch, but this newly added function
replicates a pre-existing pattern that seems unsafe. If
perf_mem__region_scnprintf() is called with sz equal to 0, sz -= 1 will
underflow to a large unsigned value, and out[0] = '\0' will write out of
bounds.
> +
> + if (mem_info)
> + mem = mem_info__const_data_src(mem_info)->mem_region;
> +
> + switch (mem) {
> + case PERF_MEM_REGION_NA:
[ ... ]
> @@ -620,6 +686,8 @@ int perf_script__meminfo_scnprintf(char *out, size_t sz, const struct mem_info *
> i += perf_mem__lck_scnprintf(out + i, sz - i, mem_info);
> i += scnprintf(out + i, sz - i, "|BLK ");
> i += perf_mem__blk_scnprintf(out + i, sz - i, mem_info);
> + i += scnprintf(out + i, sz - i, "|Region ");
> + i += perf_mem__region_scnprintf(out + i, sz - i, mem_info);
>
> return i;
> }
[Severity: High]
This is a pre-existing issue, but could this sequence of calls in
perf_script__meminfo_scnprintf() lead to a stack buffer overflow?
If any previous formatter returns a length that exceeds the available space,
i will become greater than sz. If that happens, the sz - i calculation
passed to subsequent formatters will underflow to a large unsigned value,
completely disabling bounds checking.
For example, perf_mem__lvl_scnprintf() uses strcat in some cases:
if (printed++) {
strcat(out, " or ");
l += 4;
}
This unbounded strcat can push the return length beyond the allocation,
guaranteeing i exceeds sz in the caller. Should the bounds tracking in
these functions be updated to prevent this underflow from propagating?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260709221736.33446-1-thomas.falcon@intel.com?part=4
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf()
2026-07-09 22:17 ` [PATCH 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
@ 2026-07-09 22:28 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-09 22:28 UTC (permalink / raw)
To: Thomas Falcon; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Cascading integer underflows and stack buffer overflows in `perf_script__meminfo_scnprintf` and its helper functions due to unbounded `strcat` and unchecked size arithmetic.
--
--- Patch [1]: [PATCH 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() ---
commit e57d4daab871fa57aef1ac0569d81f8d93753398
Author: Thomas Falcon <thomas.falcon@intel.com>
perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf()
When printing memory info in perf script, the entire size of the
buffer is passed to perf_mem__lvl_scnprintf() instead of the remaining
size. Pass the remaining buffer size instead.
> diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
> --- a/tools/perf/util/mem-events.c
> +++ b/tools/perf/util/mem-events.c
> @@ -610,7 +610,7 @@ int perf_script__meminfo_scnprintf(char *out, size_t sz, const struct mem_info *
> i += scnprintf(out, sz, "|OP ");
> i += perf_mem__op_scnprintf(out + i, sz - i, mem_info);
> i += scnprintf(out + i, sz - i, "|LVL ");
> - i += perf_mem__lvl_scnprintf(out + i, sz, mem_info);
> + i += perf_mem__lvl_scnprintf(out + i, sz - i, mem_info);
> i += scnprintf(out + i, sz - i, "|SNP ");
[Severity: Critical]
This is a pre-existing issue, but does sz - i risk underflowing here if i
exceeds sz?
Looking at the helper functions called here, they use strcat() without bounds
checking and advance the length manually:
tools/perf/util/mem-events.c:perf_mem__lvl_scnprintf() {
...
if (printed++) {
strcat(out, " or ");
l += 4;
}
l += scnprintf(out + l, sz - l, mem_lvl[i]);
...
}
If multiple bits are set and l exceeds sz due to the manual l += 4 increments,
could sz - l underflow to SIZE_MAX? This would cause subsequent scnprintf()
calls to write past the buffer boundaries. The same pattern appears to exist
in perf_mem__snp_scnprintf() and perf_mem__tlb_scnprintf().
Since these helpers can return a length greater than sz, the accumulated size i
in perf_script__meminfo_scnprintf() could also grow larger than sz. This would
cause cascading underflows when subtracting i from sz in subsequent calls.
Should we ensure the accumulated length is checked against sz before
subtraction, and consider replacing strcat() with a bounded string operation
in the helper functions?
> i += perf_mem__snp_scnprintf(out + i, sz - i, mem_info);
> i += scnprintf(out + i, sz - i, "|TLB ");
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260709221736.33446-1-thomas.falcon@intel.com?part=1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 3/6] perf tools: Show memory region in perf-c2c subcommand
2026-07-09 22:17 ` [PATCH 3/6] perf tools: Show memory region in perf-c2c subcommand Thomas Falcon
@ 2026-07-09 22:29 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-09 22:29 UTC (permalink / raw)
To: Thomas Falcon; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing newline character in WARN_ONCE format string.
--
--- Patch [3]: [PATCH 3/6] perf tools: Show memory region in perf-c2c subcommand ---
commit 13662f60c6b5c5739b7e7fe17ff89755ab8e651d
Author: Dapeng Mi <dapeng1.mi@linux.intel.com>
perf tools: Show memory region in perf-c2c subcommand
This commit adds a memory region field to the perf-c2c cacheline list view
to help users identify the region a cacheline belongs to. It updates the
internal structures, stats calculations, and display logic to show this
new field.
Link: https://lore.kernel.org/all/20260114011750.350569-1-dapeng1.mi@linux.intel.com/
> diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
> index c9584dbedf77a..57e822dbd2d4a 100644
> --- a/tools/perf/builtin-c2c.c
> +++ b/tools/perf/builtin-c2c.c
[ ... ]
> @@ -281,6 +282,18 @@ static void c2c_he__set_node(struct c2c_hist_entry *c2c_he,
> }
> }
>
> +static void c2c_he__set_mem_region(struct c2c_hist_entry *c2c_he,
> + unsigned int mem_region)
> +{
> + if (WARN_ONCE(mem_region > PERF_MEM_REGION_MEM7,
> + "WARNING: invalid memory region ID"))
[Severity: Low]
Does this WARN_ONCE format string need a trailing newline character?
In the perf tools environment, WARN_ONCE uses __WARN_printf, which maps
directly to fprintf(stderr, ...) without appending a newline. As a result,
when this warning is triggered, the subsequent output will be printed on the
same line, resulting in malformed terminal output.
> + return;
> +
> + /* Update mem_region only if it really accesses memory */
> + if (mem_region >= PERF_MEM_REGION_MMIO)
> + c2c_he->mem_region = mem_region;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260709221736.33446-1-thomas.falcon@intel.com?part=3
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 5/6] perf header: Support memory ranges
2026-07-09 22:17 ` [PATCH 5/6] perf header: Support memory ranges Thomas Falcon
@ 2026-07-09 22:31 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-07-09 22:31 UTC (permalink / raw)
To: Thomas Falcon; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] Asymmetric enforcement of `MAX_MEMORY_RANGES` allows `perf record` to create perfectly valid files that `perf report` will fatally reject.
- [Medium] TOCTOU race during sysfs parsing can cause `perf record` header generation to abort.
- [Medium] Unchecked return value of `sysfs__mountpoint()` is passed directly to `scnprintf` as a `%s` argument.
--
--- Patch [5]: [PATCH 5/6] perf header: Support memory ranges ---
commit 42917cf527bf0744d5a38079c4d626fee56bf5e9
Author: Thomas Falcon <thomas.falcon@intel.com>
perf header: Support memory ranges
Memory ranges are parsed from the ACPI MRRM table via sysfs and saved in the
perf data file under HEADER_MEMORY_RANGES. This data can be viewed with
perf-report and perf-script.
Link: https://lore.kernel.org/lkml/20250505173819.419271-1-tony.luck@intel.com/
> diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
> index 091d8f7f6bd2c..5c1f767dad159 100644
> --- a/tools/perf/util/header.c
> +++ b/tools/perf/util/header.c
> @@ -92,6 +92,7 @@
> #define MAX_PMU_CAPS 512
> #define MAX_PMU_MAPPINGS 4096
> #define MAX_SCHED_DOMAINS 64
> +#define MAX_MEMORY_RANGES 64
>
> /*
> * magic2 = "PERFILE2"
> @@ -1891,6 +1892,126 @@ static int write_cpu_domain_info(struct feat_fd *ff,
> return ret;
> }
>
> +static int memory_range__read(struct memory_range *range, int idx)
> +{
> + char path[PATH_MAX], file[PATH_MAX];
> + struct stat st;
> + int tmp;
> +
> + scnprintf(path, PATH_MAX, "firmware/acpi/memory_ranges/range%d", idx);
> + scnprintf(file, PATH_MAX, "%s/%s", sysfs__mountpoint(), path);
[Severity: Medium]
Can sysfs__mountpoint() return NULL here if sysfs is not mounted?
If it does return NULL, would passing it directly to the %s format specifier in
scnprintf() cause a segfault on environments using strictly compliant C
libraries like musl?
> + if (stat(file, &st))
> + return -1;
> +
[ ... ]
> +static int memory_range__parse(struct memory_range **ranges)
> +{
> + int i, err, nr_memory_ranges = 0;
> + char path[PATH_MAX];
> + struct stat st;
> +
> + scnprintf(path, PATH_MAX, "%s/firmware/acpi/memory_ranges", sysfs__mountpoint());
[Severity: Medium]
Is there a similar risk here of sysfs__mountpoint() returning NULL when
evaluating the initial base path?
> + if (stat(path, &st))
> + return 0;
> +
> + while (1) {
> + scnprintf(path, PATH_MAX, "%s/firmware/acpi/memory_ranges/range%d", sysfs__mountpoint(), nr_memory_ranges);
> + if (stat(path, &st))
> + break;
> +
> + nr_memory_ranges++;
> + }
> +
> + if (nr_memory_ranges == 0)
> + return 0;
> +
> + *ranges = zalloc(nr_memory_ranges * sizeof(struct memory_range));
> + if (!(*ranges))
> + return -ENOMEM;
> +
> + for (i = 0; i < nr_memory_ranges; i++) {
> + struct memory_range range;
> +
> + err = memory_range__read(&range, i);
[Severity: Medium]
Could there be a race condition if a memory range is dynamically removed from
sysfs between the initial counting loop above and this read loop?
If a range is removed, memory_range__read() will return -1, which propagates up
and aborts feature writing. Should the reader gracefully handle missing entries
that were counted moments before instead of failing the entire operation?
> + if (err < 0)
> + goto out_error;
> +
> + (*ranges)[i] = range;
> + }
> +
> + return nr_memory_ranges;
> +
> +out_error:
> + zfree(ranges);
> + return -1;
> +}
> +
> +static int write_memory_ranges(struct feat_fd *ff,
> + struct evlist *evlist __maybe_unused)
> +{
> + struct memory_range *ranges = NULL;
> + int nr_memory_ranges = 0, ret;
> +
> + nr_memory_ranges = memory_range__parse(&ranges);
> + if (nr_memory_ranges < 0)
> + return nr_memory_ranges;
> +
> + ret = do_write(ff, &nr_memory_ranges, sizeof(nr_memory_ranges));
> + if (ret < 0)
> + goto out;
> +
> + for (int i = 0; i < nr_memory_ranges; i++) {
[Severity: Medium]
Does this write loop allow writing more than MAX_MEMORY_RANGES (64) entries
into the perf data file if the system has more than 64 ranges?
> + ret = do_write(ff, &ranges[i].base, sizeof(u64));
> + if (ret < 0)
> + goto out;
[ ... ]
> @@ -4201,6 +4339,54 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused
> return ret;
> }
>
> +static int process_memory_ranges(struct feat_fd *ff, void *data __maybe_unused)
> +{
> + struct perf_env *env = &ff->ph->env;
> + struct memory_range *ranges, *r;
> + u32 nr_memory_ranges, i;
> +
> + if (do_read_u32(ff, &nr_memory_ranges))
> + return -1;
> +
> + if (!nr_memory_ranges) {
> + pr_debug("memory ranges not available\n");
> + return 0;
> + }
> +
> + if (nr_memory_ranges > MAX_MEMORY_RANGES) {
> + pr_err("Invalid memory_ranges: nr_memory_ranges (%u) > %u\n",
> + nr_memory_ranges, MAX_MEMORY_RANGES);
> + return -1;
> + }
[Severity: Medium]
If the writer successfully records more than MAX_MEMORY_RANGES (64) entries on
a large system, won't this strict limit in the reader cause perfectly valid
perf.data files to be rejected?
Since the writer dynamically discovers all ranges without limits, should the
reader also process all recorded ranges instead of imposing a static 64 limit?
> +
> + ranges = zalloc(nr_memory_ranges * sizeof(*ranges));
> + if (!ranges)
> + return -1;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260709221736.33446-1-thomas.falcon@intel.com?part=5
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-07-09 22:31 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-09 22:17 [PATCH 0/6] perf: Add support for memory region/range reporting Thomas Falcon
2026-07-09 22:17 ` [PATCH 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
2026-07-09 22:28 ` sashiko-bot
2026-07-09 22:17 ` [PATCH 2/6] perf mem: Add support for printing PERF_MEM_LVLNUM_L0 Thomas Falcon
2026-07-09 22:17 ` [PATCH 3/6] perf tools: Show memory region in perf-c2c subcommand Thomas Falcon
2026-07-09 22:29 ` sashiko-bot
2026-07-09 22:17 ` [PATCH 4/6] perf tools: Show memory region in perf-script subcommand Thomas Falcon
2026-07-09 22:28 ` sashiko-bot
2026-07-09 22:17 ` [PATCH 5/6] perf header: Support memory ranges Thomas Falcon
2026-07-09 22:31 ` sashiko-bot
2026-07-09 22:17 ` [PATCH 6/6] perf c2c: print memory region data with stdio output Thomas Falcon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox