linux-perf-users.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/6] perf: Add support for memory region/range reporting
@ 2026-08-03 20:45 Thomas Falcon
  2026-08-03 20:45 ` [PATCH v3 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-08-03 20:45 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: linux-perf-users, linux-kernel, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, 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.

v3:
-- included missing ff-size check in process_memory_ranges
-- make memory region reporting in perf c2c-and perf-script conditional
   on feature bit, requiring some patch reordering (Namhyung Kim)
-- used open/openat to read memory range sysfs files in memory_range__read()
   (Namhyung Kim)
-- Removed path name and file name buffers in memory_range__read(),
   instead pass path name from as a parameter from memory_range__parse()
   (Namhyung Kim)
-- use calloc instead of zalloc in memory_range__parse() and
   process_memory_ranges() (Namhyung Kim)
-- Removed redundant check for existance of memory range directory in
   memory_range__read() (Namhyung Kim)
-- Included output examples of memory ranges in perf header and perf-c2c
   in commit message (Namhyung Kim)

v2:
-- Added missing newline in WARN_ONCE message in c2c_he__set_mem_region()
-- increased buffers passed to perf_script__meminfo_scnprintf() from 150
   to 200 bytes to avoid silent truncation
-- Added check for NULL return of sysfs__mountpoint() when parsing
   memory ranges in sysfs
-- increased MAX_MEMORY_RANGES sanity check from 64 to 256 based on
   ACPI MRRM table implementation in Linux kernel
-- added comment for MAX_MEMORY_RANGES to clarify that it is a sanity check
   for malformed perf.data files
-- removed a line of code was removed from util/env.h but was added back in
   v1 due to bad rebase

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                      | 170 ++++++++++++---
 tools/perf/builtin-inject.c                   |   1 +
 tools/perf/builtin-script.c                   |  11 +-
 tools/perf/util/bpf-filter.l                  |   1 +
 tools/perf/util/env.c                         |   1 +
 tools/perf/util/env.h                         |  10 +
 tools/perf/util/header.c                      | 203 ++++++++++++++++++
 tools/perf/util/header.h                      |   1 +
 tools/perf/util/mem-events.c                  |  84 +++++++-
 tools/perf/util/mem-events.h                  |   2 +
 .../scripting-engines/trace-event-python.c    |   4 +-
 13 files changed, 460 insertions(+), 43 deletions(-)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v3 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf()
  2026-08-03 20:45 [PATCH v3 0/6] perf: Add support for memory region/range reporting Thomas Falcon
@ 2026-08-03 20:45 ` Thomas Falcon
  2026-08-03 20:54   ` sashiko-bot
  2026-08-03 20:45 ` [PATCH v3 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-08-03 20:45 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: linux-perf-users, linux-kernel, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, 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>
Reviewed-by: Dapeng Mi <dapeng1.mi@linux.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.55.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v3 2/6] perf mem: Add support for printing PERF_MEM_LVLNUM_L0
  2026-08-03 20:45 [PATCH v3 0/6] perf: Add support for memory region/range reporting Thomas Falcon
  2026-08-03 20:45 ` [PATCH v3 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
@ 2026-08-03 20:45 ` Thomas Falcon
  2026-08-03 20:45 ` [PATCH v3 3/6] perf header: Support memory ranges Thomas Falcon
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Thomas Falcon @ 2026-08-03 20:45 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: linux-perf-users, linux-kernel, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, 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.

The L0 cache is newly added a small piece of cache which is
the closest, lowest-latency memory cache tied directly to the
execution pipeline.

The table "Table 9-4. Data Source Field Encodings for Panther
Cove and Coyote Cove Microarchitectures" in the ISE doc chapter
"9.2.1 Panther Cove and Coyote Cove Microarchitectures Memory
Auxiliary Field Layout"[1] indicates the code "01H" means the
"L0 Hit - Minimal latency core cache hit. This request was
satisfied by the L0 data cache."

[1]: https://www.intel.com/content/www/us/en/content-details/922690/intel-architecture-instruction-set-extensions-programming-reference.html

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>
Reviewed-by: Dapeng Mi <dapeng1.mi@linux.intel.com>
---
v3: Included more information about the L0 cache in the commit message.
---
 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.55.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v3 3/6] perf header: Support memory ranges
  2026-08-03 20:45 [PATCH v3 0/6] perf: Add support for memory region/range reporting Thomas Falcon
  2026-08-03 20:45 ` [PATCH v3 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
  2026-08-03 20:45 ` [PATCH v3 2/6] perf mem: Add support for printing PERF_MEM_LVLNUM_L0 Thomas Falcon
@ 2026-08-03 20:45 ` Thomas Falcon
  2026-08-03 21:00   ` sashiko-bot
  2026-08-03 20:45 ` [PATCH v3 4/6] perf tools: Show memory region in perf-c2c subcommand Thomas Falcon
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 11+ messages in thread
From: Thomas Falcon @ 2026-08-03 20:45 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: linux-perf-users, linux-kernel, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, 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.

Example output:
 # memory ranges (nr 5):
 # range0: [0x0000000000000000-0x00000000bfffffff], node = 0, local_region_id = 0, remote_region_id = 255
 # range1: [0x0000000100000000-0x000000203fffffff], node = 0, local_region_id = 0, remote_region_id = 255
 # range2: [0x0000008000000000-0x0000027fffffffff], node = -2, local_region_id = 1, remote_region_id = 255
 # range3: [0x0000028000000000-0x0000047fffffffff], node = -2, local_region_id = 1, remote_region_id = 255
 # range4: [0x0000048000000000-0x000004ffffffffff], node = -2, local_region_id = 1, remote_region_id = 255

[1]: https://lore.kernel.org/lkml/20250505173819.419271-1-tony.luck@intel.com/

Assisted-by: Sashiko:gemini-3.1-pro-preview
Assisted-by: GitHub-Copilot:claude-opus-4-8
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
---
v3:
-- Added missing ff->size check in process_memory_ranges()
-- used open/openat to read memory range sysfs files in memory_range__read()
-- Removed path name and file name buffers in memory_range__read(),
   instead pass path name from as a parameter from memory_range__parse()
-- use calloc instead of zalloc in memory_range__parse() and
   process_memory_ranges()
-- Removed redundant check for existance of memory range directory in
   memory_range__read()
-- Included examples of memory range output in commit message

v2:
-- Added check for NULL return of sysfs__mountpoint() when parsing
   memory ranges in sysfs
-- increased MAX_MEMORY_RANGES sanity check from 64 to 256 based on
   ACPI MRRM table implementation in Linux kernel
-- added comment for MAX_MEMORY_RANGES to clarify that it is a sanity check
   for malformed perf.data files
-- removed a line of code was removed from util/env.h but was added back in
   v1 due to bad rebase
---
 .../Documentation/perf.data-file-format.txt   |  13 ++
 tools/perf/builtin-inject.c                   |   1 +
 tools/perf/util/env.c                         |   1 +
 tools/perf/util/env.h                         |  10 +
 tools/perf/util/header.c                      | 203 ++++++++++++++++++
 tools/perf/util/header.h                      |   1 +
 6 files changed, 229 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..23d35425efe3 100644
--- a/tools/perf/util/env.h
+++ b/tools/perf/util/env.h
@@ -68,6 +68,14 @@ 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;
+};
+
 struct perf_env {
 	char			*hostname;
 	char			*os_release;
@@ -100,6 +108,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 +131,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
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index e90e541f546b..18d3869c6aa2 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	256
 
 /*
  * magic2 = "PERFILE2"
@@ -1891,6 +1892,132 @@ static int write_cpu_domain_info(struct feat_fd *ff,
 	return ret;
 }
 
+static int memory_range__read(struct memory_range *range, const char *path)
+{
+	char buf[32];
+	ssize_t n;
+	int dfd, fd, tmp, ret = -1;
+
+	dfd = open(path, O_RDONLY | O_DIRECTORY);
+	if (dfd < 0)
+		return -1;
+
+#define _R(name, dst, conv)			\
+	fd = openat(dfd, name, O_RDONLY);	\
+	if (fd < 0)				\
+		goto out;			\
+	n = read(fd, buf, sizeof(buf) - 1);	\
+	close(fd);				\
+	if (n <= 0)				\
+		goto out;			\
+	buf[n] = '\0';				\
+	dst = conv(buf, NULL, 0);
+
+	_R("base", range->base, strtoull);
+	_R("length", range->length, strtoull);
+	_R("node", range->node, strtol);
+	_R("local_region_id", tmp, strtol);
+	if (tmp < 0 || tmp >= UINT8_MAX)
+		goto out;
+	range->local_region_id = tmp;
+	_R("remote_region_id", tmp, strtol);
+	if (tmp < 0 || tmp >= UINT8_MAX)
+		goto out;
+	range->remote_region_id = tmp;
+#undef _R
+
+	ret = 0;
+out:
+	close(dfd);
+	return ret;
+}
+
+static int memory_range__parse(struct memory_range **ranges)
+{
+	const char *sysfs = sysfs__mountpoint();
+	int i, err, nr_memory_ranges = 0;
+	char path[PATH_MAX];
+	struct stat st;
+
+	if (!sysfs)
+		return 0;
+
+	scnprintf(path, PATH_MAX, "%s/firmware/acpi/memory_ranges", sysfs);
+	if (stat(path, &st))
+		return 0;
+
+	while (1) {
+		scnprintf(path, PATH_MAX,
+			  "%s/firmware/acpi/memory_ranges/range%d",
+			  sysfs, nr_memory_ranges);
+		if (stat(path, &st))
+			break;
+
+		nr_memory_ranges++;
+	}
+
+	if (nr_memory_ranges == 0)
+		return 0;
+
+	*ranges = calloc(nr_memory_ranges, sizeof(struct memory_range));
+	if (!(*ranges))
+		return -ENOMEM;
+
+	for (i = 0; i < nr_memory_ranges; i++) {
+		struct memory_range range;
+
+		scnprintf(path, PATH_MAX,
+			  "%s/firmware/acpi/memory_ranges/range%d", sysfs, i);
+		err = memory_range__read(&range, path);
+		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 +2755,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%016" PRIx64 "-0x%016" 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 +4345,64 @@ 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;
+	}
+
+	/* According to version 1.1 of the ACPI MRRM table, the maximum
+	 * number of memory regions can be at most 255. Do a sanity check
+	 * here to guard against a malformed perf.data file.
+	 */
+	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;
+	}
+
+	if (ff->size < sizeof(u32) + nr_memory_ranges * (2 * sizeof(u64) + sizeof(u32) + 2 * sizeof(u8))) {
+		pr_err("Invalid HEADER_MEMORY_RANGES: section too small (%zu) for %u range entries\n",
+		       ff->size, nr_memory_ranges);
+		return -1;
+	}
+
+	ranges = calloc(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 +4467,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 {
diff --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.55.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v3 4/6] perf tools: Show memory region in perf-c2c subcommand
  2026-08-03 20:45 [PATCH v3 0/6] perf: Add support for memory region/range reporting Thomas Falcon
                   ` (2 preceding siblings ...)
  2026-08-03 20:45 ` [PATCH v3 3/6] perf header: Support memory ranges Thomas Falcon
@ 2026-08-03 20:45 ` Thomas Falcon
  2026-08-03 20:57   ` sashiko-bot
  2026-08-03 20:45 ` [PATCH v3 5/6] perf tools: Show memory region in perf-script subcommand Thomas Falcon
  2026-08-03 20:45 ` [PATCH v3 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-08-03 20:45 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: linux-perf-users, linux-kernel, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, 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
Assisted-by: GitHub-Copilot:claude-opus-4-8
Signed-off-by: Dapeng Mi <dapeng1.mi@linux.intel.com>
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
Link: https://lore.kernel.org/all/20260114011750.350569-1-dapeng1.mi@linux.intel.com/
---
v3: make memory region reporting conditional on feature bit
---
 tools/perf/builtin-c2c.c | 140 +++++++++++++++++++++++++++++----------
 1 file changed, 105 insertions(+), 35 deletions(-)

diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index c9584dbedf77..6954a48c60e4 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\n"))
+		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,30 @@ 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 - PERF_MEM_REGION_MEM0);
+
+	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 +1466,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 +1481,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 +1920,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 +2948,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;
 
@@ -3151,8 +3202,11 @@ static int perf_c2c__report(int argc, const char **argv)
 	OPT_END()
 	};
 	int err = 0;
-	const char *output_str, *sort_str = NULL;
+	const char *sort_str = NULL;
+	char *output_str = NULL;
 	struct perf_env *env;
+	bool has_mem_regions;
+	int ret;
 
 	annotation_options__init();
 
@@ -3278,7 +3332,7 @@ static int perf_c2c__report(int argc, const char **argv)
 	 * implementation.
 	 */
 	if (perf_c2c__has_annotation(NULL)) {
-		int ret = symbol__annotation_init();
+		ret = symbol__annotation_init();
 
 		if (ret < 0)
 			goto out_mem2node;
@@ -3316,36 +3370,50 @@ static int perf_c2c__report(int argc, const char **argv)
 		goto out_mem2node;
 	}
 
-	if (c2c.display != DISPLAY_SNP_PEER)
-		output_str = "cl_idx,"
-			     "dcacheline,"
-			     "dcacheline_node,"
-			     "dcacheline_count,"
-			     "percent_costly_snoop,"
-			     "tot_hitm,lcl_hitm,rmt_hitm,"
-			     "tot_recs,"
-			     "tot_loads,"
-			     "tot_stores,"
-			     "stores_l1hit,stores_l1miss,stores_na,"
-			     "ld_fbhit,ld_l1hit,ld_l2hit,"
-			     "ld_lclhit,lcl_hitm,"
-			     "ld_rmthit,rmt_hitm,"
-			     "dram_lcl,dram_rmt";
-	else
-		output_str = "cl_idx,"
-			     "dcacheline,"
-			     "dcacheline_node,"
-			     "dcacheline_count,"
-			     "percent_costly_snoop,"
-			     "tot_peer,lcl_peer,rmt_peer,"
-			     "tot_recs,"
-			     "tot_loads,"
-			     "tot_stores,"
-			     "stores_l1hit,stores_l1miss,stores_na,"
-			     "ld_fbhit,ld_l1hit,ld_l2hit,"
-			     "ld_lclhit,lcl_hitm,"
-			     "ld_rmthit,rmt_hitm,"
-			     "dram_lcl,dram_rmt";
+	has_mem_regions = perf_header__has_feat(&session->header,
+						HEADER_MEMORY_RANGES);
+
+	if (c2c.display != DISPLAY_SNP_PEER) {
+		ret = asprintf(&output_str,
+			       "cl_idx,"
+			       "dcacheline,"
+			       "%s"
+			       "dcacheline_node,"
+			       "dcacheline_count,"
+			       "percent_costly_snoop,"
+			       "tot_hitm,lcl_hitm,rmt_hitm,"
+			       "tot_recs,"
+			       "tot_loads,"
+			       "tot_stores,"
+			       "stores_l1hit,stores_l1miss,stores_na,"
+			       "ld_fbhit,ld_l1hit,ld_l2hit,"
+			       "ld_lclhit,lcl_hitm,"
+			       "ld_rmthit,rmt_hitm,"
+			       "dram_lcl,dram_rmt",
+			       has_mem_regions ? "dcacheline_mem_region," : "");
+		if (ret < 0)
+			goto out_mem2node;
+	} else {
+		ret = asprintf(&output_str,
+			       "cl_idx,"
+			       "dcacheline,"
+			       "%s"
+			       "dcacheline_node,"
+			       "dcacheline_count,"
+			       "percent_costly_snoop,"
+			       "tot_peer,lcl_peer,rmt_peer,"
+			       "tot_recs,"
+			       "tot_loads,"
+			       "tot_stores,"
+			       "stores_l1hit,stores_l1miss,stores_na,"
+			       "ld_fbhit,ld_l1hit,ld_l2hit,"
+			       "ld_lclhit,lcl_hitm,"
+			       "ld_rmthit,rmt_hitm,"
+			       "dram_lcl,dram_rmt",
+			       has_mem_regions ? "dcacheline_mem_region," : "");
+		if (ret < 0)
+			goto out_mem2node;
+	}
 
 	if (c2c.display == DISPLAY_TOT_HITM)
 		sort_str = "tot_hitm";
@@ -3368,11 +3436,13 @@ static int perf_c2c__report(int argc, const char **argv)
 
 	if (ui_quirks()) {
 		pr_err("failed to setup UI\n");
-		goto out_mem2node;
+		goto out_str;
 	}
 
 	perf_c2c_display(session);
 
+out_str:
+	free(output_str);
 out_mem2node:
 	mem2node__exit(&c2c.mem2node);
 out_session:
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v3 5/6] perf tools: Show memory region in perf-script subcommand
  2026-08-03 20:45 [PATCH v3 0/6] perf: Add support for memory region/range reporting Thomas Falcon
                   ` (3 preceding siblings ...)
  2026-08-03 20:45 ` [PATCH v3 4/6] perf tools: Show memory region in perf-c2c subcommand Thomas Falcon
@ 2026-08-03 20:45 ` Thomas Falcon
  2026-08-03 21:01   ` sashiko-bot
  2026-08-03 20:45 ` [PATCH v3 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-08-03 20:45 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: linux-perf-users, linux-kernel, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, 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/

Assisted-by: Sashiko:gemini-3.1-pro-preview
Assisted-by: GitHub-Copilot:claude-opus-4-8
Signed-off-by: Dapeng Mi <dapeng1.mi@linux.intel.com>
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
Link: https://lore.kernel.org/all/20260114011750.350569-1-dapeng1.mi@linux.intel.com/
---
v3: make memory region reporting conditional on feature bit
---
 tools/perf/builtin-script.c                   | 11 ++-
 tools/perf/util/mem-events.c                  | 77 +++++++++++++++++++
 tools/perf/util/mem-events.h                  |  1 +
 .../scripting-engines/trace-event-python.c    |  4 +-
 4 files changed, 87 insertions(+), 6 deletions(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index f91d8b1fbd01..9ebf8dfe01c9 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[200];
+	char out[200];
 	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, 200, mi);
 	mem_info__put(mi);
 
-	len = scnprintf(out, 100, "%16" PRIx64 " %s", data_src, decode);
+	len = scnprintf(out, 200, "%16" PRIx64 " %s", data_src, decode);
 	if (maxlen < len)
 		maxlen = len;
 
@@ -4470,6 +4470,9 @@ int cmd_script(int argc, const char **argv)
 	if (show_full_info)
 		script.tool.show_feat_hdr = SHOW_FEAT_HEADER_FULL_INFO;
 
+	perf_mem__set_show_region(perf_header__has_feat(&session->header,
+							HEADER_MEMORY_RANGES));
+
 	if (symbol__init(env) < 0)
 		goto out_delete;
 
diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
index 4fd48fd20055..61f203508916 100644
--- a/tools/perf/util/mem-events.c
+++ b/tools/perf/util/mem-events.c
@@ -31,6 +31,13 @@ struct perf_mem_event perf_mem_events[PERF_MEM_EVENTS__MAX] = {
 
 bool perf_mem_record[PERF_MEM_EVENTS__MAX] = { 0 };
 
+static bool perf_mem__show_region = false;
+
+void perf_mem__set_show_region(bool val)
+{
+	perf_mem__show_region = val;
+}
+
 struct perf_mem_event *perf_pmu__mem_events_ptr(struct perf_pmu *pmu, int i)
 {
 	if (i >= PERF_MEM_EVENTS__MAX || !pmu)
@@ -604,6 +611,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 +693,10 @@ 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);
+	if (perf_mem__show_region) {
+		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/mem-events.h b/tools/perf/util/mem-events.h
index daa22748f9fe..91fe57ce1859 100644
--- a/tools/perf/util/mem-events.h
+++ b/tools/perf/util/mem-events.h
@@ -146,5 +146,6 @@ enum mem_stat_dtlb {
 
 int mem_stat_index(const enum mem_stat_type mst, const u64 data_src);
 const char *mem_stat_name(const enum mem_stat_type mst, const int idx);
+void perf_mem__set_show_region(bool val);
 
 #endif /* __PERF_MEM_EVENTS_H */
diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c
index 8f832ae316ca..57ec259897e4 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[200];
 
 	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, 200, mi);
 	mem_info__put(mi);
 
 	pydict_set_item_string_decref(dict, "datasrc_decode",
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v3 6/6] perf c2c: print memory region data with stdio output
  2026-08-03 20:45 [PATCH v3 0/6] perf: Add support for memory region/range reporting Thomas Falcon
                   ` (4 preceding siblings ...)
  2026-08-03 20:45 ` [PATCH v3 5/6] perf tools: Show memory region in perf-script subcommand Thomas Falcon
@ 2026-08-03 20:45 ` Thomas Falcon
  5 siblings, 0 replies; 11+ messages in thread
From: Thomas Falcon @ 2026-08-03 20:45 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: linux-perf-users, linux-kernel, Peter Zijlstra, Ingo Molnar,
	Arnaldo Carvalho de Melo, 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.

Example output of perf c2c report --stdio:

=================================================
                  Memory Ranges
=================================================
Range 0: [0x0000000000000000-0x00000000bfffffff] Node 0, local region id 0, remote region id 255
Range 1: [0x0000000100000000-0x000000203fffffff] Node 0, local region id 0, remote region id 255
Range 2: [0x0000008000000000-0x0000027fffffffff] Node -2, local region id 1, remote region id 255
Range 3: [0x0000028000000000-0x0000047fffffffff] Node -2, local region id 1, remote region id 255
Range 4: [0x0000048000000000-0x000004ffffffffff] Node -2, local region id 1, remote region id 255

Assisted-by: Sashiko:gemini-3.1-pro-preview
Signed-off-by: Thomas Falcon <thomas.falcon@intel.com>
---
v3: Included examples of memory range output in commit message
---
 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 6954a48c60e4..ce825fe6dfc6 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -2635,6 +2635,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();
@@ -2648,6 +2676,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.55.0


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf()
  2026-08-03 20:45 ` [PATCH v3 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
@ 2026-08-03 20:54   ` sashiko-bot
  0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-03 20:54 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] Insufficient stack buffer size and unsafe string operations in memory event formatting lead to severe integer underflows and stack buffer overflows.
--

--- Patch [1]: [PATCH v3 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() ---
commit ac4418d3c38063a8fbcd5e19348324171261d3d5
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
> index 0b49fce251fcc..4e490f9cd3488 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);

[Severity: Critical]
This is a pre-existing issue, but by passing sz - i here, does it make it
more likely to hit buffer overflows and integer underflows in the helper
functions?

In tools/perf/builtin-script.c:data_src__fprintf(), a fixed 100-byte stack
buffer is used:

    char decode[100];
    ...
    perf_script__meminfo_scnprintf(decode, 100, mi);

As perf_script__meminfo_scnprintf() accumulates the printed length into i
without bounding it to sz, if the total length exceeds 100 bytes (e.g., with
multiple mem_lvl or snoop flags), i will exceed sz. This causes sz - i to
underflow to a massive size_t value, bypassing bounds checks in subsequent
helper calls.

Additionally, if the remaining size becomes exactly 0 or very small, helpers
like perf_mem__lvl_scnprintf() and perf_mem__snp_scnprintf() do:

    sz -= 1; /* -1 for null termination */
    ...
    strcat(out, "Remote ");
    l += 7;

If passed a size of 0, sz -= 1 underflows to a massive unsigned value. Unchecked
strcat() calls will then write out-of-bounds on the stack.

>  	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 ");

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260803204540.100348-1-thomas.falcon@intel.com?part=1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 4/6] perf tools: Show memory region in perf-c2c subcommand
  2026-08-03 20:45 ` [PATCH v3 4/6] perf tools: Show memory region in perf-c2c subcommand Thomas Falcon
@ 2026-08-03 20:57   ` sashiko-bot
  0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-03 20:57 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:
- [Medium] Unconditional header span and width changes break the perf c2c UI layout for older or unsupported perf.data files.
- [Medium] perf_c2c__report incorrectly returns 0 (success) when asprintf memory allocation fails.
--

--- Patch [4]: [PATCH v3 4/6] perf tools: Show memory region in perf-c2c subcommand ---
commit a204da119426634f593eeb8162efcca17a1e1e85
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 cacheline list view in the
perf c2c subcommand output. It parses the memory region from memory info
and dynamically adds the column if the HEADER_MEMORY_RANGES feature is
present in the perf.data file.

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..6954a48c60e46 100644
> --- a/tools/perf/builtin-c2c.c
> +++ b/tools/perf/builtin-c2c.c
> @@ -1425,7 +1466,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),

[Severity: Medium]
Will this unconditional header span break the UI layout when the memory region
column is excluded?

Because has_mem_regions conditionally adds the column, if the perf.data file
lacks the HEADER_MEMORY_RANGES feature, this static span of 3 might swallow
the headers of subsequent columns.

>  	.name		= "dcacheline",
>  	.cmp		= dcacheline_cmp,
>  	.entry		= dcacheline_entry,

[ ... ]

> @@ -2898,8 +2948,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);

[Severity: Medium]
Could this width calculation also cause visual artifacts when the memory
region column is absent?

Similar to the header span, the zero-line text width unconditionally includes
the new column's width and spacing, which could pad the zero line excessively
when the column is not rendered.

>  	if (!buf)
>  		return -ENOMEM;

[ ... ]

> @@ -3370,50 +3370,50 @@ static int perf_c2c__report(int argc, const char **argv)
>  
>  	has_mem_regions = perf_header__has_feat(&session->header,
>  						HEADER_MEMORY_RANGES);
>  
>  	if (c2c.display != DISPLAY_SNP_PEER) {
>  		ret = asprintf(&output_str,
>  			       "cl_idx,"
>  			       "dcacheline,"

[ ... ]

>  			       has_mem_regions ? "dcacheline_mem_region," : "");
>  		if (ret < 0)
>  			goto out_mem2node;

[Severity: Medium]
Does this error path accidentally return success when asprintf() fails?

The original code sets err = 0 at the beginning of perf_c2c__report(),
and this goto statement jumps to cleanup without setting err = ret or similar,
so the function will return 0 even though memory allocation failed.

>  	} else {
>  		ret = asprintf(&output_str,
>  			       "cl_idx,"
>  			       "dcacheline,"

[ ... ]

>  			       has_mem_regions ? "dcacheline_mem_region," : "");
>  		if (ret < 0)
>  			goto out_mem2node;

[Severity: Medium]
Does this alternative display layout's error path also accidentally return
success when asprintf() fails?

Similar to the previous block, err is not updated before jumping to cleanup.

>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260803204540.100348-1-thomas.falcon@intel.com?part=4

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 3/6] perf header: Support memory ranges
  2026-08-03 20:45 ` [PATCH v3 3/6] perf header: Support memory ranges Thomas Falcon
@ 2026-08-03 21:00   ` sashiko-bot
  0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-03 21:00 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:
- [High] The bounds check for `local_region_id` and `remote_region_id` incorrectly rejects the valid value of 255 (UINT8_MAX).
- [Medium] Missing explicit inclusion of `<fcntl.h>` for `open()`, `openat()`, `O_RDONLY`, and `O_DIRECTORY`, causing musl build failures.
- [Medium] Repeated parsing of HEADER_MEMORY_RANGES sections leaks previously allocated memory.
--

--- Patch [3]: [PATCH v3 3/6] perf header: Support memory ranges ---
commit 0b006df2ed3ab47f9865f5a75df7ab210c86b2a2
Author: Thomas Falcon <thomas.falcon@intel.com>

perf header: Support memory ranges

This commit adds support for parsing memory range data from the ACPI MRRM
table exposed via sysfs. It saves this data in the perf data file header
under a new feature bit, HEADER_MEMORY_RANGES, and adds support for
displaying it.

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..a9dedbe390243 100644
> --- a/tools/perf/util/header.c
> +++ b/tools/perf/util/header.c
[ ... ]
> @@ -1891,6 +1892,132 @@ static int write_cpu_domain_info(struct feat_fd *ff,
>  	return ret;
>  }
>  
> +static int memory_range__read(struct memory_range *range, const char *path)
> +{
> +	char buf[32];
> +	ssize_t n;
> +	int dfd, fd, tmp, ret = -1;
> +
> +	dfd = open(path, O_RDONLY | O_DIRECTORY);

[Severity: Medium]
Since this function uses open(), openat(), O_RDONLY, and O_DIRECTORY, should
<fcntl.h> be explicitly included at the top of the file to prevent build
failures on musl libc?

> +	if (dfd < 0)
> +		return -1;
> +
> +#define _R(name, dst, conv)			\
> +	fd = openat(dfd, name, O_RDONLY);	\
[ ... ]
> +	_R("local_region_id", tmp, strtol);
> +	if (tmp < 0 || tmp >= UINT8_MAX)
> +		goto out;
> +	range->local_region_id = tmp;
> +	_R("remote_region_id", tmp, strtol);
> +	if (tmp < 0 || tmp >= UINT8_MAX)
> +		goto out;

[Severity: High]
Does this bounds check incorrectly reject the valid value of 255? 

UINT8_MAX evaluates to 255, so using >= will cause the function to reject
255 and jump to the error path. However, the example output in the commit
message shows remote_region_id = 255, which suggests 255 is an expected and
valid value.

> +	range->remote_region_id = tmp;
> +#undef _R
> +
> +	ret = 0;
> +out:
> +	close(dfd);
> +	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;
[ ... ]
> +	ranges = calloc(nr_memory_ranges, sizeof(*ranges));
> +	if (!ranges)
> +		return -1;
[ ... ]
> +	env->memory_ranges = ranges;
> +	env->nr_memory_ranges = nr_memory_ranges;
> +
> +	return 0;

[Severity: Medium]
If a perf.data file contains multiple HEADER_MEMORY_RANGES sections, could 
this leak the previously allocated memory? 

This assignment blindly overwrites env->memory_ranges with the newly allocated
ranges array without first checking for or freeing any existing allocation.

> +error:
> +	zfree(&ranges);
> +	return -1;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260803204540.100348-1-thomas.falcon@intel.com?part=3

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 5/6] perf tools: Show memory region in perf-script subcommand
  2026-08-03 20:45 ` [PATCH v3 5/6] perf tools: Show memory region in perf-script subcommand Thomas Falcon
@ 2026-08-03 21:01   ` sashiko-bot
  0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-03 21:01 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:

New issues:
- [Medium] The `perf_mem__show_region` flag is evaluated before feature events are processed in pipe mode, permanently hiding the memory region field for piped inputs.

Pre-existing issues:
- [High] Stack buffer overflow in memory event formatting due to missing bounds checks, unbounded `strcat`, and unsigned integer underflow.
--

--- Patch [5]: [PATCH v3 5/6] perf tools: Show memory region in perf-script subcommand ---
commit 453eaf4e1cef12c5307d93d17b31ce0722004b14
Author: Dapeng Mi <dapeng1.mi@linux.intel.com>

perf tools: Show memory region in perf-script subcommand

This commit adds support for showing the memory region in the perf-script
subcommand. It maps the mem_region field of the memory information data
source into a new Region field in the output.

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
> --- a/tools/perf/builtin-script.c
> +++ b/tools/perf/builtin-script.c
> @@ -4468,6 +4468,9 @@ int cmd_script(int argc, const char **argv)
>  	if (show_full_info)
>  		script.tool.show_feat_hdr = SHOW_FEAT_HEADER_FULL_INFO;
>  
> +	perf_mem__set_show_region(perf_header__has_feat(&session->header,
> +							HEADER_MEMORY_RANGES));
> +

[Severity: Medium]
Will this correctly identify the memory region feature when reading from a
pipe?

In pipe mode, the session header features are not fully populated until
PERF_RECORD_HEADER_FEATURE events are processed dynamically during
perf_session__process_events(). Evaluating perf_header__has_feat() prematurely
here causes it to evaluate to false and permanently hides the memory region
field.

>  	if (symbol__init(env) < 0)
>  		goto out_delete;
>  
> 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
[ ... ]
> +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 is a pre-existing issue, but does this code safely handle the case where
sz is already 0?

If this function is called with sz = 0 due to earlier buffer exhaustion,
sz -= 1 will underflow to SIZE_MAX and blindly write a null byte out of bounds.

[ ... ]
> @@ -620,6 +693,10 @@ 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);
> +	if (perf_mem__show_region) {
> +		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 can this sequence of formatting operations
cause a stack buffer overflow?

If a crafted perf.data file has an event with all memory levels, snoops,
and TLB bits set, the combined string might exceed the fixed 200-byte stack
arrays in data_src__fprintf() and set_sample_datasrc_in_dict().

Because i is accumulated without checking if i < sz, the expression sz - i
passed to subsequent functions can underflow to SIZE_MAX, disabling all
remaining bounds checks. 

Furthermore, pre-existing helper functions like perf_mem__lvl_scnprintf()
unconditionally use strcat() regardless of remaining buffer capacity:

tools/perf/util/mem-events.c:perf_mem__lvl_scnprintf() {
    ...
		if (printed++) {
			strcat(out, " or ");
			l += 4;
		}
    ...
}

Will this cause memory corruption if the buffer limit is exceeded?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260803204540.100348-1-thomas.falcon@intel.com?part=5

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-08-03 21:01 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 20:45 [PATCH v3 0/6] perf: Add support for memory region/range reporting Thomas Falcon
2026-08-03 20:45 ` [PATCH v3 1/6] perf mem: Fix size tracking for mem_lvl's in perf_script__meminfo_scnprintf() Thomas Falcon
2026-08-03 20:54   ` sashiko-bot
2026-08-03 20:45 ` [PATCH v3 2/6] perf mem: Add support for printing PERF_MEM_LVLNUM_L0 Thomas Falcon
2026-08-03 20:45 ` [PATCH v3 3/6] perf header: Support memory ranges Thomas Falcon
2026-08-03 21:00   ` sashiko-bot
2026-08-03 20:45 ` [PATCH v3 4/6] perf tools: Show memory region in perf-c2c subcommand Thomas Falcon
2026-08-03 20:57   ` sashiko-bot
2026-08-03 20:45 ` [PATCH v3 5/6] perf tools: Show memory region in perf-script subcommand Thomas Falcon
2026-08-03 21:01   ` sashiko-bot
2026-08-03 20:45 ` [PATCH v3 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;
as well as URLs for NNTP newsgroup(s).