Linux Perf Users
 help / color / mirror / Atom feed
* [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support
@ 2026-08-07  7:18 Ian Rogers
  2026-08-07  7:18 ` [RFC PATCH v1 1/8] perf event: Factor build_id out into its own top-level struct Ian Rogers
                   ` (8 more replies)
  0 siblings, 9 replies; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

This patch series introduces PERF_SAMPLE_BUILD_ID_OFFSET to the
perf_event UAPI and implements full support across both the kernel
and perf tools.

Background & Motivation:

In order for perf to translate virtual addresses of samples into
symbols a file and offset within the file are needed. During event
synthesis perf will create mmap events to facilitate the translation
of a virtual address to a file and offset by modelling the address
space of a process. By directly recording in a sample the Build ID of
a file and the offset within it, no synthesis is necessary. The Build
ID and offset as a pair are much larger than a virtual address, so
there is a trade-off between synthesis cost and extra size for
samples. These changes just facilitate Build ID and offset as a choice
for perf samples and the user can have the choice to use it when they
believe it is advantageous.

In practice perf still needs to map a build ID to a file, so by
default this change keeps synthesis to allow this. It is expected a
user that knows their build IDs, say through debuginfod, will disable
this option with say --synth=no.

The kernel support uses the existing build ID and offset support used
by BPF stack traces.

The Build ID and offset samples inherently don't leak ASLR
information. As with the ASLR remapping work in perf inject, support
is added to migrate virtual addresses to Build ID and offset for the
purposes of testing without kernel support.

Series Overview:

- Patch 1: Factor struct perf_build_id into top-level header structure.
- Patch 2: Add PERF_SAMPLE_BUILD_ID_OFFSET to UAPI.
- Patch 3: Implement kernel-side sampling support in
           perf_output_sample() using stack_map_get_build_id_offset().
- Patch 4: Refactor perf tools thread__find_map and symbol lookup APIs
           to accept struct perf_sample.
- Patch 5: Add perf tools internal parsing and symbol resolution via
           Build ID + offset.
- Patch 6: Extend perf inject with --buildid-offset to convert sample
           files.
- Patch 7: Add --buildid-offset option to perf record.
- Patch 8: Add unit and shell test coverage (bid-offset test and
           inject_bid_offset.sh).

Testing & Verification:

The series was verified using QEMU hypervisor testing with kernel
builds and perf test.

Linux Plumbers Tracing Miniconference:

I submitted a proposal/abstract to talk about these changes to the
Tracing Micro Conference at Linux Plumbers Conference 2026 in Prague.

Ian Rogers (8):
  perf event: Factor build_id out into its own top-level struct
  perf/core: Add BUILD_ID_OFFSET to UAPI
  perf/core: Implement BUILD_ID_OFFSET sample type
  perf: Refactor thread map and symbol APIs to take perf_sample
  perf tools: Internal support for BUILD_ID_OFFSET
  perf inject: Extend perf inject to support bid_offset conversion
  perf record: Add --buildid-offset option
  perf tests: Add build_id_offset test coverage

 include/linux/bpf.h                           |  13 +
 include/uapi/linux/perf_event.h               |   4 +-
 kernel/bpf/stackmap.c                         |   2 +-
 kernel/events/core.c                          |  85 ++-
 tools/include/uapi/linux/perf_event.h         |  30 +-
 tools/lib/perf/include/perf/event.h           |  18 +-
 .../arch/powerpc/util/skip-callchain-idx.c    |   4 +-
 tools/perf/builtin-inject.c                   |  23 +-
 tools/perf/builtin-record.c                   |   7 +
 tools/perf/builtin-script.c                   |  49 +-
 tools/perf/builtin-timechart.c                |   4 +-
 tools/perf/builtin-trace.c                    |  12 +-
 tools/perf/tests/Build                        |   1 +
 tools/perf/tests/bid-offset.c                 |  60 +++
 tools/perf/tests/builtin-test.c               |   1 +
 tools/perf/tests/code-reading.c               |   4 +-
 tools/perf/tests/mmap-thread-lookup.c         |  45 +-
 tools/perf/tests/sample-parsing.c             |  37 +-
 tools/perf/tests/shell/inject_bid_offset.sh   | 170 ++++++
 tools/perf/tests/shell/record.sh              |  69 +++
 tools/perf/tests/tests.h                      |   1 +
 tools/perf/util/Build                         |   1 +
 tools/perf/util/annotate-data.c               |   5 +-
 tools/perf/util/aslr.c                        |  16 +-
 tools/perf/util/build-id.c                    |   5 +-
 tools/perf/util/capstone.c                    |   4 +-
 tools/perf/util/cs-etm.c                      |   4 +-
 tools/perf/util/data-convert-json.c           |   4 +-
 tools/perf/util/debug.c                       |   4 +-
 tools/perf/util/dlfilter.c                    |   8 +-
 tools/perf/util/event.c                       |  93 ++--
 tools/perf/util/evsel.c                       |  35 ++
 tools/perf/util/evsel.h                       |   3 +-
 tools/perf/util/evsel_fprintf.c               |   2 +-
 tools/perf/util/inject_bid_offset.c           | 504 ++++++++++++++++++
 tools/perf/util/inject_bid_offset.h           |  21 +
 tools/perf/util/intel-pt.c                    |  12 +-
 tools/perf/util/machine.c                     | 106 ++--
 tools/perf/util/maps.c                        |  29 +
 tools/perf/util/maps.h                        |   2 +
 tools/perf/util/perf_event_attr_fprintf.c     |   3 +-
 tools/perf/util/python.c                      |  13 +-
 tools/perf/util/record.h                      |   1 +
 tools/perf/util/sample.h                      |  13 +
 .../scripting-engines/trace-event-python.c    |  20 +-
 tools/perf/util/synthetic-events.c            |  60 ++-
 tools/perf/util/thread.c                      |  11 +-
 tools/perf/util/thread.h                      |  14 +-
 tools/perf/util/unwind-libdw.c                |   7 +-
 tools/perf/util/unwind-libunwind.c            |   8 +-
 50 files changed, 1473 insertions(+), 174 deletions(-)
 create mode 100644 tools/perf/tests/bid-offset.c
 create mode 100755 tools/perf/tests/shell/inject_bid_offset.sh
 create mode 100644 tools/perf/util/inject_bid_offset.c
 create mode 100644 tools/perf/util/inject_bid_offset.h

-- 
2.55.0.679.g6767b8d81c-goog


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

* [RFC PATCH v1 1/8] perf event: Factor build_id out into its own top-level struct
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
@ 2026-08-07  7:18 ` Ian Rogers
  2026-08-07  7:33   ` sashiko-bot
  2026-08-07  7:18 ` [RFC PATCH v1 2/8] perf/core: Add BUILD_ID_OFFSET to UAPI Ian Rogers
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

Future changes will use build_id in more contexts. For consistency make
the struct its own type and update usage to be of this type.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/include/uapi/linux/perf_event.h | 19 +++++++++----
 tools/lib/perf/include/perf/event.h   | 18 +++++++-----
 tools/perf/builtin-inject.c           |  3 +-
 tools/perf/util/event.c               |  4 +--
 tools/perf/util/machine.c             |  3 +-
 tools/perf/util/python.c              |  8 +++---
 tools/perf/util/synthetic-events.c    | 40 +++++++++++++++------------
 7 files changed, 57 insertions(+), 38 deletions(-)

diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h
index fd10aa8d697f..2479a38883d0 100644
--- a/tools/include/uapi/linux/perf_event.h
+++ b/tools/include/uapi/linux/perf_event.h
@@ -1058,6 +1058,18 @@ enum perf_event_type {
 	 */
 	PERF_RECORD_SAMPLE			= 9,
 
+	/*
+	 * Build IDs may be present in a number of events. They have a
+	 * consistent encoding of:
+	 *
+	 * struct build_id {
+	 *	u8		size;
+	 *	u8		__reserved_1;
+	 *	u16		__reserved_2;
+	 *	u8		data[20];
+	 * };
+	 */
+
 	/*
 	 * The MMAP2 records are an augmented version of MMAP, they add
 	 * maj, min, ino numbers to be used to uniquely identify each mapping
@@ -1076,12 +1088,7 @@ enum perf_event_type {
 	 *			u64		ino;
 	 *			u64		ino_generation;
 	 *		};
-	 *		struct {
-	 *			u8		build_id_size;
-	 *			u8		__reserved_1;
-	 *			u16		__reserved_2;
-	 *			u8		build_id[20];
-	 *		};
+	 *		struct build_id		build_id;
 	 *	};
 	 *	u32				prot, flags;
 	 *	char				filename[];
diff --git a/tools/lib/perf/include/perf/event.h b/tools/lib/perf/include/perf/event.h
index fdced574c889..173eab43c148 100644
--- a/tools/lib/perf/include/perf/event.h
+++ b/tools/lib/perf/include/perf/event.h
@@ -26,6 +26,15 @@ struct perf_record_mmap {
 	char			 filename[PATH_MAX];
 };
 
+#define PERF_BUILD_ID_SIZE 20
+
+struct perf_build_id {
+	__u8	 size;
+	__u8	 __reserved_1;
+	__u16	 __reserved_2;
+	__u8	 data[PERF_BUILD_ID_SIZE];
+};
+
 struct perf_record_mmap2 {
 	struct perf_event_header header;
 	__u32			 pid, tid;
@@ -39,12 +48,7 @@ struct perf_record_mmap2 {
 			__u64	 ino;
 			__u64	 ino_generation;
 		};
-		struct {
-			__u8	 build_id_size;
-			__u8	 __reserved_1;
-			__u16	 __reserved_2;
-			__u8	 build_id[20];
-		};
+		struct perf_build_id build_id;
 	};
 	__u32			 prot;
 	__u32			 flags;
@@ -321,7 +325,7 @@ struct perf_record_header_build_id {
 	union {
 		__u8		 build_id[24];
 		struct {
-			__u8	 data[20];
+			__u8	 data[PERF_BUILD_ID_SIZE];
 			__u8	 size;
 			__u8	 reserved1__;
 			__u16	 reserved2__;
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 70bbfad5653e..8559c0eab5f1 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -811,7 +811,8 @@ static int perf_event__repipe_mmap2(const struct perf_tool *tool,
 	struct dso_id id = dso_id_empty;
 
 	if (event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) {
-		build_id__init(&id.build_id, event->mmap2.build_id, event->mmap2.build_id_size);
+		build_id__init(&id.build_id, event->mmap2.build_id.data,
+			       event->mmap2.build_id.size);
 	} else {
 		id.maj = event->mmap2.maj;
 		id.min = event->mmap2.min;
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
index ea75816d126a..c69ae57ce679 100644
--- a/tools/perf/util/event.c
+++ b/tools/perf/util/event.c
@@ -335,8 +335,8 @@ size_t perf_event__fprintf_mmap2(union perf_event *event, FILE *fp)
 		char sbuild_id[SBUILD_ID_SIZE];
 		struct build_id bid;
 
-		build_id__init(&bid, event->mmap2.build_id,
-			       event->mmap2.build_id_size);
+		build_id__init(&bid, event->mmap2.build_id.data,
+			       event->mmap2.build_id.size);
 		build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
 
 		return fprintf(fp, " %d/%d: [%#" PRI_lx64 "(%#" PRI_lx64 ") @ %#" PRI_lx64
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index f86b3b7df742..21d54ebc866b 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -1797,7 +1797,8 @@ int machine__process_mmap2_event(struct machine *machine,
 		perf_event__fprintf_mmap2(event, stdout);
 
 	if (event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) {
-		build_id__init(&dso_id.build_id, event->mmap2.build_id, event->mmap2.build_id_size);
+		build_id__init(&dso_id.build_id, event->mmap2.build_id.data,
+			       event->mmap2.build_id.size);
 	} else {
 		dso_id.maj = event->mmap2.maj;
 		dso_id.min = event->mmap2.min;
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index af85c0771c44..d35190052d97 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -233,12 +233,12 @@ static PyObject *pyrf_mmap2_event__get_build_id(PyObject *self, void *closure __
 	if (!(pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID))
 		Py_RETURN_NONE;
 
-	int size = pevent->event.mmap2.build_id_size;
+	size_t size = pevent->event.mmap2.build_id.size;
 
-	if (size > 20)
-		size = 20;
+	if (size > sizeof(pevent->event.mmap2.build_id.data))
+		size = sizeof(pevent->event.mmap2.build_id.data);
 
-	return PyBytes_FromStringAndSize((const char *)pevent->event.mmap2.build_id, size);
+	return PyBytes_FromStringAndSize((const char *)pevent->event.mmap2.build_id.data, size);
 }
 
 static PyGetSetDef pyrf_mmap2_event__getset[] = {
diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic-events.c
index 75a32ae8ef62..f7dedfb6bab8 100644
--- a/tools/perf/util/synthetic-events.c
+++ b/tools/perf/util/synthetic-events.c
@@ -452,7 +452,7 @@ static void perf_record_mmap2__read_build_id(struct perf_record_mmap2 *event,
 	}
 
 	if (event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) {
-		build_id__init(&dso_id.build_id, event->build_id, event->build_id_size);
+		build_id__init(&dso_id.build_id, event->build_id.data, event->build_id.size);
 	} else {
 		dso_id.maj = event->maj;
 		dso_id.min = event->min;
@@ -478,16 +478,20 @@ static void perf_record_mmap2__read_build_id(struct perf_record_mmap2 *event,
 	nsinfo__put(nsi);
 
 out:
+	event->build_id.__reserved_1 = 0;
+	event->build_id.__reserved_2 = 0;
 	if (rc == 0) {
-		memcpy(event->build_id, bid.data, sizeof(bid.data));
-		event->build_id_size = (u8) bid.size;
+		memcpy(event->build_id.data, bid.data, sizeof(bid.data));
+		event->build_id.size = (u8) bid.size;
 		event->header.misc |= PERF_RECORD_MISC_MMAP_BUILD_ID;
-		event->__reserved_1 = 0;
-		event->__reserved_2 = 0;
 
 		if (dso && !dso__has_build_id(dso))
 			dso__set_build_id(dso, &bid);
 	} else {
+		memset(&event->build_id, 0, sizeof(event->build_id));
+		event->build_id.size = 0;
+		event->header.misc &= ~PERF_RECORD_MISC_MMAP_BUILD_ID;
+
 		if (event->filename[0] == '/') {
 			pr_debug2("Failed to read build ID for %s\n",
 				  event->filename);
@@ -611,8 +615,10 @@ int perf_event__synthesize_mmap_events(const struct perf_tool *tool,
 		event->mmap2.prot = prot;
 		event->mmap2.flags = flags;
 
-		if (!symbol_conf.no_buildid_mmap2)
-			perf_record_mmap2__read_build_id(&event->mmap2, machine, false);
+		if (!symbol_conf.no_buildid_mmap2) {
+			perf_record_mmap2__read_build_id(&event->mmap2, machine,
+							 /*is_kernel=*/false);
+		}
 
 		if (perf_tool__process_synth_event(tool, event, machine, process) != 0) {
 			rc = -1;
@@ -807,12 +813,12 @@ static int perf_event__synthesize_modules_maps_cb(struct map *map, void *data)
 
 		/* Clear stale build ID and entire union from previous module iteration */
 		event->mmap2.header.misc &= ~PERF_RECORD_MISC_MMAP_BUILD_ID;
-		memset(event->mmap2.build_id, 0, sizeof(event->mmap2.build_id));
-		event->mmap2.build_id_size = 0;
-		event->mmap2.__reserved_1 = 0;
-		event->mmap2.__reserved_2 = 0;
+		memset(event->mmap2.build_id.data, 0, sizeof(event->mmap2.build_id.data));
+		event->mmap2.build_id.size = 0;
+		event->mmap2.build_id.__reserved_1 = 0;
+		event->mmap2.build_id.__reserved_2 = 0;
 
-		perf_record_mmap2__read_build_id(&event->mmap2, args->machine, false);
+		perf_record_mmap2__read_build_id(&event->mmap2, args->machine, /*is_kernel=*/false);
 	} else {
 		const char *long_name = dso__long_name(dso);
 
@@ -1293,7 +1299,7 @@ static int __perf_event__synthesize_kernel_mmap(const struct perf_tool *tool,
 		event->mmap2.len   = map__end(map) - event->mmap.start;
 		event->mmap2.pid   = machine->pid;
 
-		perf_record_mmap2__read_build_id(&event->mmap2, machine, true);
+		perf_record_mmap2__read_build_id(&event->mmap2, machine, /*is_kernel=*/true);
 	} else {
 		size = snprintf(event->mmap.filename, sizeof(event->mmap.filename),
 				"%s%s", machine->mmap_name, kmap->ref_reloc_sym->name) + 1;
@@ -2486,10 +2492,10 @@ int perf_event__synthesize_mmap2_build_id(const struct perf_tool *tool,
 	ev.mmap2.len = len;
 	ev.mmap2.pgoff = pgoff;
 
-	ev.mmap2.build_id_size = bid->size;
-	if (ev.mmap2.build_id_size > sizeof(ev.mmap2.build_id))
-		ev.mmap2.build_id_size = sizeof(ev.mmap2.build_id);
-	memcpy(ev.mmap2.build_id, bid->data, ev.mmap2.build_id_size);
+	ev.mmap2.build_id.size = bid->size;
+	if (ev.mmap2.build_id.size > sizeof(ev.mmap2.build_id.data))
+		ev.build_id.size = sizeof(ev.mmap2.build_id.data);
+	memcpy(ev.mmap2.build_id.data, bid->data, ev.mmap2.build_id.size);
 
 	ev.mmap2.prot = prot;
 	ev.mmap2.flags = flags;
-- 
2.55.0.679.g6767b8d81c-goog


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

* [RFC PATCH v1 2/8] perf/core: Add BUILD_ID_OFFSET to UAPI
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
  2026-08-07  7:18 ` [RFC PATCH v1 1/8] perf event: Factor build_id out into its own top-level struct Ian Rogers
@ 2026-08-07  7:18 ` Ian Rogers
  2026-08-07  7:42   ` sashiko-bot
  2026-08-07  7:18 ` [RFC PATCH v1 3/8] perf/core: Implement BUILD_ID_OFFSET sample type Ian Rogers
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

Introduce PERF_SAMPLE_BUILD_ID_OFFSET and
PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET

Signed-off-by: Ian Rogers <irogers@google.com>
---
 include/uapi/linux/perf_event.h       |  4 +++-
 tools/include/uapi/linux/perf_event.h | 11 ++++++++++-
 2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
index fd10aa8d697f..f0712c1471d3 100644
--- a/include/uapi/linux/perf_event.h
+++ b/include/uapi/linux/perf_event.h
@@ -165,8 +165,10 @@ enum perf_event_sample_format {
 	PERF_SAMPLE_DATA_PAGE_SIZE		= 1U << 22,
 	PERF_SAMPLE_CODE_PAGE_SIZE		= 1U << 23,
 	PERF_SAMPLE_WEIGHT_STRUCT		= 1U << 24,
+	PERF_SAMPLE_BUILD_ID_OFFSET		= 1U << 25,
+	PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET	= 1U << 26,
 
-	PERF_SAMPLE_MAX = 1U << 25,		/* non-ABI */
+	PERF_SAMPLE_MAX = 1U << 27,		/* non-ABI */
 };
 
 #define PERF_SAMPLE_WEIGHT_TYPE	(PERF_SAMPLE_WEIGHT | PERF_SAMPLE_WEIGHT_STRUCT)
diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h
index 2479a38883d0..d5660e2d6b60 100644
--- a/tools/include/uapi/linux/perf_event.h
+++ b/tools/include/uapi/linux/perf_event.h
@@ -165,8 +165,10 @@ enum perf_event_sample_format {
 	PERF_SAMPLE_DATA_PAGE_SIZE		= 1U << 22,
 	PERF_SAMPLE_CODE_PAGE_SIZE		= 1U << 23,
 	PERF_SAMPLE_WEIGHT_STRUCT		= 1U << 24,
+	PERF_SAMPLE_BUILD_ID_OFFSET		= 1U << 25,
+	PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET	= 1U << 26,
 
-	PERF_SAMPLE_MAX = 1U << 25,		/* non-ABI */
+	PERF_SAMPLE_MAX = 1U << 27,		/* non-ABI */
 };
 
 #define PERF_SAMPLE_WEIGHT_TYPE	(PERF_SAMPLE_WEIGHT | PERF_SAMPLE_WEIGHT_STRUCT)
@@ -1054,6 +1056,13 @@ enum perf_event_type {
 	 *	{ u64			code_page_size;} && PERF_SAMPLE_CODE_PAGE_SIZE
 	 *	{ u64			size;
 	 *	  char			data[size]; } && PERF_SAMPLE_AUX
+	 *	{ struct build_id	bid;
+	 *	  u64			offset; } && PERF_SAMPLE_BUILD_ID_OFFSET
+	 *	{ u64			nr;
+	 *	  struct {
+	 *		struct build_id	bid;
+	 *		u64 offset;
+	 *	  }[nr]; } && PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET
 	 * };
 	 */
 	PERF_RECORD_SAMPLE			= 9,
-- 
2.55.0.679.g6767b8d81c-goog


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

* [RFC PATCH v1 3/8] perf/core: Implement BUILD_ID_OFFSET sample type
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
  2026-08-07  7:18 ` [RFC PATCH v1 1/8] perf event: Factor build_id out into its own top-level struct Ian Rogers
  2026-08-07  7:18 ` [RFC PATCH v1 2/8] perf/core: Add BUILD_ID_OFFSET to UAPI Ian Rogers
@ 2026-08-07  7:18 ` Ian Rogers
  2026-08-07  7:54   ` sashiko-bot
  2026-08-07  7:18 ` [RFC PATCH v1 4/8] perf: Refactor thread map and symbol APIs to take perf_sample Ian Rogers
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

Expose stack_map_get_build_id_offset for perf_events to use when generating
these samples, and implement the payload writing in the perf event core.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 include/linux/bpf.h   | 13 +++++++
 kernel/bpf/stackmap.c |  2 +-
 kernel/events/core.c  | 85 ++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 98 insertions(+), 2 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 7719f6528445..fd76900a8e47 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -4209,4 +4209,17 @@ static inline int bpf_map_check_op_flags(struct bpf_map *map, u64 flags, u64 all
 	return 0;
 }
 
+struct bpf_stack_build_id;
+#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PERF_EVENTS)
+void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
+				    u32 trace_nr, bool user, bool may_fault);
+#else
+static inline void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
+						  u32 trace_nr, bool user, bool may_fault)
+{
+	if (id_offs)
+		id_offs->status = 1; /* BPF_STACK_BUILD_ID_EMPTY */
+}
+#endif
+
 #endif /* _LINUX_BPF_H */
diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c
index 41fe87d7302f..889ceb7cf29b 100644
--- a/kernel/bpf/stackmap.c
+++ b/kernel/bpf/stackmap.c
@@ -411,7 +411,7 @@ static void stack_map_get_build_id_offset_sleepable(struct bpf_stack_build_id *i
  *     id_offs[i].build_id is zeroed out and id_offs[i].status is set to
  *     BPF_STACK_BUILD_ID_IP.
  */
-static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
+void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
 					  u32 trace_nr, bool user, bool may_fault)
 {
 	struct mmap_unlock_irq_work *work = NULL;
diff --git a/kernel/events/core.c b/kernel/events/core.c
index ba5bd6a78fe7..97ac70441602 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -2048,6 +2048,15 @@ static int __perf_event_read_size(u64 read_format, int nr_siblings)
 	return size + nr * entry;
 }
 
+
+struct perf_sample_build_id_offset {
+	u8	size;
+	u8	res1;
+	u16	res2;
+	u8	build_id[BPF_BUILD_ID_SIZE];
+	u64	offset;
+};
+
 static void __perf_event_header_size(struct perf_event *event, u64 sample_type)
 {
 	struct perf_sample_data *data;
@@ -2086,6 +2095,11 @@ static void __perf_event_header_size(struct perf_event *event, u64 sample_type)
 	if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE)
 		size += sizeof(data->code_page_size);
 
+	if (sample_type & PERF_SAMPLE_BUILD_ID_OFFSET)
+		size += sizeof(struct perf_sample_build_id_offset);
+
+
+
 	event->header_size = size;
 }
 
@@ -8287,6 +8301,62 @@ void perf_output_sample(struct perf_output_handle *handle,
 	if (sample_type & PERF_SAMPLE_READ)
 		perf_output_read(handle, event);
 
+	if (sample_type & PERF_SAMPLE_BUILD_ID_OFFSET) {
+		struct bpf_stack_build_id bpf_bid = { .ip = data->ip };
+		struct perf_sample_build_id_offset bid_offset = { 0 };
+
+		bool is_user = (header->misc & PERF_RECORD_MISC_CPUMODE_MASK) ==
+			       PERF_RECORD_MISC_USER;
+
+		stack_map_get_build_id_offset(
+			&bpf_bid, /*trace_nr=*/1,
+			is_user, /*may_fault=*/false);
+		if (bpf_bid.status == BPF_STACK_BUILD_ID_VALID) {
+			bid_offset.size = BPF_BUILD_ID_SIZE;
+			memcpy(bid_offset.build_id, bpf_bid.build_id,
+				       BPF_BUILD_ID_SIZE);
+			bid_offset.offset = bpf_bid.offset;
+		} else {
+			bid_offset.offset = bpf_bid.ip;
+		}
+		perf_output_put(handle, bid_offset);
+	}
+
+	if (sample_type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET) {
+		u64 nr = data->callchain ? data->callchain->nr : 0;
+		struct perf_sample_build_id_offset bid_offset;
+		struct bpf_stack_build_id bpf_bid;
+		u64 i;
+
+		perf_output_put(handle, nr);
+
+		for (i = 0; i < nr; i++) {
+			memset(&bid_offset, /*c=*/0, sizeof(bid_offset));
+			memset(&bpf_bid, /*c=*/0, sizeof(bpf_bid));
+			bpf_bid.ip = data->callchain->ip[i];
+
+			if (data->callchain->ip[i] == PERF_CONTEXT_USER) {
+				bpf_bid.status = BPF_STACK_BUILD_ID_EMPTY;
+			} else {
+				bool is_user = data->callchain->ip[i] < PERF_CONTEXT_MAX;
+
+				stack_map_get_build_id_offset(&bpf_bid, /*trace_nr=*/1,
+					      is_user, /*may_fault=*/false);
+			}
+
+			if (bpf_bid.status == BPF_STACK_BUILD_ID_VALID) {
+				bid_offset.size = BPF_BUILD_ID_SIZE;
+				memcpy(bid_offset.build_id, bpf_bid.build_id,
+			       BPF_BUILD_ID_SIZE);
+				bid_offset.offset = bpf_bid.offset;
+			} else {
+				bid_offset.offset = bpf_bid.ip;
+			}
+			perf_output_put(handle, bid_offset);
+		}
+	}
+
+
 	if (sample_type & PERF_SAMPLE_CALLCHAIN) {
 		int size = 1;
 
@@ -8632,7 +8702,7 @@ void perf_prepare_sample(struct perf_sample_data *data,
 
 	__perf_event_header__init_id(data, event, filtered_sample_type);
 
-	if (filtered_sample_type & PERF_SAMPLE_IP) {
+	if (filtered_sample_type & (PERF_SAMPLE_IP | PERF_SAMPLE_BUILD_ID_OFFSET)) {
 		data->ip = perf_instruction_pointer(event, regs);
 		data->sample_flags |= PERF_SAMPLE_IP;
 	}
@@ -8640,6 +8710,19 @@ void perf_prepare_sample(struct perf_sample_data *data,
 	if (filtered_sample_type & PERF_SAMPLE_CALLCHAIN)
 		perf_sample_save_callchain(data, event, regs);
 
+	if (filtered_sample_type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET) {
+		int size = 1;
+
+		if (!data->callchain)
+			data->callchain = perf_callchain(event, regs);
+
+		size += data->callchain->nr *
+			(sizeof(struct perf_sample_build_id_offset) / sizeof(u64));
+		data->dyn_size += size * sizeof(u64);
+		data->sample_flags |= PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET;
+	}
+
+
 	if (filtered_sample_type & PERF_SAMPLE_RAW) {
 		data->raw = NULL;
 		data->dyn_size += sizeof(u64);
-- 
2.55.0.679.g6767b8d81c-goog


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

* [RFC PATCH v1 4/8] perf: Refactor thread map and symbol APIs to take perf_sample
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
                   ` (2 preceding siblings ...)
  2026-08-07  7:18 ` [RFC PATCH v1 3/8] perf/core: Implement BUILD_ID_OFFSET sample type Ian Rogers
@ 2026-08-07  7:18 ` Ian Rogers
  2026-08-07  8:20   ` sashiko-bot
  2026-08-07  7:18 ` [RFC PATCH v1 5/8] perf tools: Internal support for BUILD_ID_OFFSET Ian Rogers
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

Refactor thread__find_map and thread__find_symbol family of APIs to
take a struct perf_sample * instead of cpumode and addr. This prepares
for looking up symbols using build ID and offset, which is contained in
the sample payload instead of the address.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 .../arch/powerpc/util/skip-callchain-idx.c    |  4 +-
 tools/perf/builtin-inject.c                   |  6 +-
 tools/perf/builtin-script.c                   | 33 +++++++----
 tools/perf/builtin-timechart.c                |  4 +-
 tools/perf/builtin-trace.c                    | 12 +++-
 tools/perf/tests/code-reading.c               |  4 +-
 tools/perf/tests/mmap-thread-lookup.c         |  9 ++-
 tools/perf/util/annotate-data.c               |  5 +-
 tools/perf/util/aslr.c                        | 16 +++--
 tools/perf/util/build-id.c                    |  5 +-
 tools/perf/util/capstone.c                    |  4 +-
 tools/perf/util/cs-etm.c                      |  4 +-
 tools/perf/util/data-convert-json.c           |  4 +-
 tools/perf/util/debug.c                       |  4 +-
 tools/perf/util/dlfilter.c                    |  8 ++-
 tools/perf/util/event.c                       | 59 +++++++++++--------
 tools/perf/util/intel-pt.c                    | 12 +++-
 tools/perf/util/machine.c                     | 18 ++++--
 tools/perf/util/python.c                      |  5 +-
 .../scripting-engines/trace-event-python.c    | 20 ++++---
 tools/perf/util/thread.c                      | 11 ++--
 tools/perf/util/thread.h                      | 14 ++---
 tools/perf/util/unwind-libdw.c                |  7 ++-
 tools/perf/util/unwind-libunwind.c            |  8 ++-
 24 files changed, 188 insertions(+), 88 deletions(-)

diff --git a/tools/perf/arch/powerpc/util/skip-callchain-idx.c b/tools/perf/arch/powerpc/util/skip-callchain-idx.c
index e57f10798fa6..7a15efa0a699 100644
--- a/tools/perf/arch/powerpc/util/skip-callchain-idx.c
+++ b/tools/perf/arch/powerpc/util/skip-callchain-idx.c
@@ -223,7 +223,9 @@ int arch_skip_callchain_idx(struct thread *thread, struct ip_callchain *chain)
 	addr_location__init(&al);
 	ip = chain->ips[1];
 
-	thread__find_symbol(thread, PERF_RECORD_MISC_USER, ip, &al);
+	thread__find_symbol(thread,
+			    &(struct perf_sample){.cpumode = PERF_RECORD_MISC_USER, .ip = ip},
+			    &al);
 
 	if (al.map)
 		dso = map__dso(al.map);
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 8559c0eab5f1..6aa9e3eea438 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -1167,7 +1167,11 @@ static int perf_event__inject_buildid(const struct perf_tool *tool, union perf_e
 		goto repipe;
 	}
 
-	if (thread__find_map(thread, sample->cpumode, sample->ip, &al)) {
+	if (thread__find_map(thread,
+			     &(struct perf_sample){.cpumode = sample->cpumode,
+						   .ip = sample->ip,
+						   .bid = sample->bid},
+			     &al)) {
 		mark_dso_hit(inject, tool, sample, machine, args.mmap_evsel, al.map,
 			     /*sample_in_dso=*/true);
 	}
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index f91d8b1fbd01..93cffafb1af7 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -1000,8 +1000,10 @@ static int perf_sample__fprintf_brstack(struct perf_sample *sample,
 
 			addr_location__init(&alf);
 			addr_location__init(&alt);
-			thread__find_map_fb(thread, sample->cpumode, from, &alf);
-			thread__find_map_fb(thread, sample->cpumode, to, &alt);
+			thread__find_map_fb(thread,
+			    &(struct perf_sample){.cpumode = sample->cpumode, .ip = from}, &alf);
+			thread__find_map_fb(thread,
+			    &(struct perf_sample){.cpumode = sample->cpumode, .ip = to}, &alt);
 
 			printed += map__fprintf_dsoname_dsoff(alf.map, PRINT_FIELD(DSOFF), alf.addr, fp);
 			printed += fprintf(fp, "/0x%"PRIx64, to);
@@ -1037,8 +1039,10 @@ static int perf_sample__fprintf_brstacksym(struct perf_sample *sample,
 		from = entries[i].from;
 		to   = entries[i].to;
 
-		thread__find_symbol_fb(thread, sample->cpumode, from, &alf);
-		thread__find_symbol_fb(thread, sample->cpumode, to, &alt);
+		thread__find_symbol_fb(thread,
+			       &(struct perf_sample){.cpumode = sample->cpumode, .ip = from}, &alf);
+		thread__find_symbol_fb(thread,
+			       &(struct perf_sample){.cpumode = sample->cpumode, .ip = to}, &alt);
 
 		printed += symbol__fprintf_symname_offs(alf.sym, &alf, fp);
 		if (PRINT_FIELD(DSO))
@@ -1075,11 +1079,15 @@ static int perf_sample__fprintf_brstackoff(struct perf_sample *sample,
 		from = entries[i].from;
 		to   = entries[i].to;
 
-		if (thread__find_map_fb(thread, sample->cpumode, from, &alf) &&
+		if (thread__find_map_fb(thread,
+				&(struct perf_sample){.cpumode = sample->cpumode,
+						      .ip = from}, &alf) &&
 		    !dso__adjust_symbols(map__dso(alf.map)))
 			from = map__dso_map_ip(alf.map, from);
 
-		if (thread__find_map_fb(thread, sample->cpumode, to, &alt) &&
+		if (thread__find_map_fb(thread,
+				&(struct perf_sample){.cpumode = sample->cpumode,
+						      .ip = to}, &alt) &&
 		    !dso__adjust_symbols(map__dso(alt.map)))
 			to = map__dso_map_ip(alt.map, to);
 
@@ -1137,7 +1145,10 @@ static int grab_bb(u8 *buffer, u64 start, u64 end,
 	}
 
 	addr_location__init(&al);
-	if (!thread__find_map(thread, *cpumode, start, &al) || (dso = map__dso(al.map)) == NULL) {
+	if (!thread__find_map(thread,
+			      &(struct perf_sample){.cpumode = *cpumode,
+						    .ip = start}, &al) ||
+	    (dso = map__dso(al.map)) == NULL) {
 		pr_debug("\tcannot resolve %" PRIx64 "-%" PRIx64 "\n", start, end);
 		goto out;
 	}
@@ -1212,7 +1223,7 @@ static int print_srccode(struct thread *thread, u8 cpumode, uint64_t addr)
 	int ret = 0;
 
 	addr_location__init(&al);
-	thread__find_map(thread, cpumode, addr, &al);
+	thread__find_map(thread, &(struct perf_sample){.cpumode = cpumode, .ip = addr}, &al);
 	if (!al.map)
 		goto out;
 	ret = map__fprintf_srccode(al.map, al.addr, stdout,
@@ -1267,7 +1278,9 @@ static int ip__fprintf_jump(uint64_t ip, struct branch_entry *en,
 		struct addr_location al;
 
 		addr_location__init(&al);
-		thread__find_map(thread, x->cpumode, ip, &al);
+		thread__find_map(thread,
+			 &(struct perf_sample){.cpumode = x->cpumode,
+					       .ip = ip}, &al);
 		printed += map__fprintf_srcline(al.map, al.addr, " srcline: ", fp);
 		printed += fprintf(fp, "\t");
 		addr_location__exit(&al);
@@ -1327,7 +1340,7 @@ static int ip__fprintf_sym(uint64_t addr, struct thread *thread,
 	int off, printed = 0, ret = 0;
 
 	addr_location__init(&al);
-	thread__find_map(thread, cpumode, addr, &al);
+	thread__find_map(thread, &(struct perf_sample){.cpumode = cpumode, .ip = addr}, &al);
 
 	if ((*lastsym) && al.addr >= (*lastsym)->start && al.addr < (*lastsym)->end)
 		goto out;
diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
index 3f9153d5ecfb..77b79188afe6 100644
--- a/tools/perf/builtin-timechart.c
+++ b/tools/perf/builtin-timechart.c
@@ -564,7 +564,9 @@ static char *cat_backtrace(struct perf_sample *sample,
 
 		addr_location__init(&tal);
 		tal.filtered = 0;
-		if (thread__find_symbol(al.thread, cpumode, ip, &tal))
+		if (thread__find_symbol(al.thread,
+				&(struct perf_sample){.cpumode = cpumode,
+						      .ip = ip}, &tal))
 			fprintf(f, "..... %016" PRIx64 " %s\n", ip, tal.sym->name);
 		else
 			fprintf(f, "..... %016" PRIx64 "\n", ip);
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index c3c7f1f85c53..efe2ff239480 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -3565,7 +3565,9 @@ static int trace__pgfault(struct trace *trace,
 	if (trace->summary_only)
 		goto out;
 
-	thread__find_symbol(thread, sample->cpumode, sample->ip, &al);
+	thread__find_symbol(thread,
+			    &(struct perf_sample){.cpumode = sample->cpumode,
+						  .ip = sample->ip}, &al);
 
 	trace__fprintf_entry_head(trace, thread, 0, true, sample->time,
 				  sample->cpu, trace->output);
@@ -3578,10 +3580,14 @@ static int trace__pgfault(struct trace *trace,
 
 	fprintf(trace->output, "] => ");
 
-	thread__find_symbol(thread, sample->cpumode, sample->addr, &al);
+	thread__find_symbol(thread,
+			    &(struct perf_sample){.cpumode = sample->cpumode,
+						  .ip = sample->addr}, &al);
 
 	if (!al.map) {
-		thread__find_symbol(thread, sample->cpumode, sample->addr, &al);
+		thread__find_symbol(thread,
+			    &(struct perf_sample){.cpumode = sample->cpumode,
+						  .ip = sample->addr}, &al);
 
 		if (al.map)
 			map_type = 'x';
diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c
index f0e8ea8754ef..ca118d0262ad 100644
--- a/tools/perf/tests/code-reading.c
+++ b/tools/perf/tests/code-reading.c
@@ -394,7 +394,9 @@ static int read_object_code(u64 addr, size_t len, u8 cpumode,
 	pr_debug("Reading object code for memory address: %#"PRIx64"\n", addr);
 
 	addr_location__init(&al);
-	if (!thread__find_map(thread, cpumode, addr, &al) || !map__dso(al.map)) {
+	if (!thread__find_map(thread,
+			      &(struct perf_sample){.cpumode = cpumode,
+						    .ip = addr}, &al) || !map__dso(al.map)) {
 		if (cpumode == PERF_RECORD_MISC_HYPERVISOR) {
 			pr_debug("Hypervisor address can not be resolved - skipping\n");
 			goto out;
diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c
index 0c5619c6e6e9..dca8bbfe780a 100644
--- a/tools/perf/tests/mmap-thread-lookup.c
+++ b/tools/perf/tests/mmap-thread-lookup.c
@@ -195,8 +195,10 @@ static int mmap_events(synth_cb synth)
 
 		pr_debug("looking for map %p\n", td->map);
 
-		thread__find_map(thread, PERF_RECORD_MISC_USER,
-				 (unsigned long) (td->map + 1), &al);
+		thread__find_map(thread, &(struct perf_sample){
+					.cpumode = PERF_RECORD_MISC_USER,
+					.ip = (unsigned long) (td->map + 1),
+				 }, &al);
 
 		thread__put(thread);
 
@@ -208,7 +210,10 @@ static int mmap_events(synth_cb synth)
 		}
 
 		pr_debug("map %p, addr %" PRIx64 "\n", al.map, map__start(al.map));
+
 		addr_location__exit(&al);
+		if (err)
+			break;
 	}
 
 	machine__delete(machine);
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index 4e4c58764082..e3fc9c68ebe0 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -734,8 +734,9 @@ bool get_global_var_info(struct data_loc_info *dloc, u64 addr,
 	mem_addr = addr + map__reloc(dloc->ms->map);
 
 	addr_location__init(&al);
-	sym = thread__find_symbol_fb(dloc->thread, dloc->cpumode,
-				     mem_addr, &al);
+	sym = thread__find_symbol_fb(dloc->thread,
+				     &(struct perf_sample){.cpumode = dloc->cpumode,
+							   .ip = mem_addr}, &al);
 	if (sym) {
 		*var_name = sym->name;
 		/* Calculate type offset from the start of variable */
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index 027695d96779..cb8e3ca437a1 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -154,7 +154,9 @@ static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
 		return 0; /* No thread. */
 
 	addr_location__init(&al);
-	if (!thread__find_map(aslr_thread, cpumode, addr, &al)) {
+	if (!thread__find_map(aslr_thread,
+			      &(struct perf_sample){.cpumode = cpumode, .ip = addr},
+			      &al)) {
 		/*
 		 * If lookup fails with specified cpumode, try fallback to the other space
 		 * to be robust against bad cpumode in samples.
@@ -168,7 +170,9 @@ static u64 aslr_tool__remap_address(struct aslr_tool *aslr,
 		else if (cpumode == PERF_RECORD_MISC_GUEST_USER)
 			effective_cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
 
-		if (!thread__find_map(aslr_thread, effective_cpumode, addr, &al)) {
+		if (!thread__find_map(aslr_thread,
+				      &(struct perf_sample){.cpumode = effective_cpumode,
+							    .ip = addr}, &al)) {
 			addr_location__exit(&al);
 			return 0; /* No mmap. */
 		}
@@ -285,7 +289,9 @@ static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
 	remap_key.pid = (cpumode == PERF_RECORD_MISC_KERNEL ||
 			 cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ?
 			kernel_pid : thread__pid(aslr_thread);
-	if (thread__find_map(aslr_thread, cpumode, start, &al)) {
+	if (thread__find_map(aslr_thread,
+			     &(struct perf_sample){.cpumode = cpumode, .ip = start},
+			     &al)) {
 		struct dso *dso = map__dso(al.map);
 		const char *dso_name = dso ? dso__long_name(dso) : NULL;
 
@@ -342,7 +348,9 @@ static u64 aslr_tool__findnew_mapping(struct aslr_tool *aslr,
 		remap_addr = top->remapped_max;
 
 		addr_location__init(&prev_al);
-		if (thread__find_map(aslr_thread, cpumode, start - 1, &prev_al)) {
+		if (thread__find_map(aslr_thread,
+				     &(struct perf_sample){.cpumode = cpumode,
+							   .ip = start - 1}, &prev_al)) {
 			if (map__end(prev_al.map) == start)
 				is_contiguous = true;
 		}
diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c
index eb95ab90f974..39e69f2299ab 100644
--- a/tools/perf/util/build-id.c
+++ b/tools/perf/util/build-id.c
@@ -69,7 +69,10 @@ int build_id__mark_dso_hit(const struct perf_tool *tool __maybe_unused,
 	}
 
 	addr_location__init(&al);
-	if (thread__find_map(thread, sample->cpumode, sample->ip, &al))
+	if (thread__find_map(thread,
+			     &(struct perf_sample){.cpumode = sample->cpumode,
+						   .ip = sample->ip,
+						   .bid = sample->bid}, &al))
 		dso__set_hit(map__dso(al.map));
 
 	addr_location__exit(&al);
diff --git a/tools/perf/util/capstone.c b/tools/perf/util/capstone.c
index 74213daf8786..dc31047149e3 100644
--- a/tools/perf/util/capstone.c
+++ b/tools/perf/util/capstone.c
@@ -241,7 +241,9 @@ static size_t print_insn_x86(struct thread *thread, u8 cpumode, struct cs_insn *
 
 		addr_location__init(&al);
 		if (op->type == X86_OP_IMM &&
-		    thread__find_symbol(thread, cpumode, op->imm, &al)) {
+		    thread__find_symbol(thread,
+					&(struct perf_sample){.cpumode = cpumode,
+							      .ip = op->imm}, &al)) {
 			printed += fprintf(fp, "%s ", insn[0].mnemonic);
 			printed += symbol__fprintf_symname_offs(al.sym, &al, fp);
 			if (print_opts & PRINT_INSN_IMM_HEX)
diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 114b3cd2da49..a0bfd9aa6dbe 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -1152,7 +1152,9 @@ static u32 __cs_etm__mem_access(struct cs_etm_queue *etmq,
 
 	cpumode = cs_etm__cpu_mode(etmq, address, el);
 
-	if (!thread__find_map(thread, cpumode, address, &al))
+	if (!thread__find_map(thread,
+			      &(struct perf_sample){.cpumode = cpumode,
+						    .ip = address}, &al))
 		goto out;
 
 	dso = map__dso(al.map);
diff --git a/tools/perf/util/data-convert-json.c b/tools/perf/util/data-convert-json.c
index 40888b7c4467..8fa7bfdaadff 100644
--- a/tools/perf/util/data-convert-json.c
+++ b/tools/perf/util/data-convert-json.c
@@ -235,7 +235,9 @@ static int process_sample_event(const struct perf_tool *tool,
 				fputc(',', out);
 
 			addr_location__init(&tal);
-			ok = thread__find_symbol(al.thread, cpumode, ip, &tal);
+			ok = thread__find_symbol(al.thread,
+						 &(struct perf_sample){.cpumode = cpumode,
+								       .ip = ip}, &tal);
 			output_sample_callchain_entry(tool, ip, ok ? &tal : NULL);
 			addr_location__exit(&tal);
 		}
diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c
index 6b5ffe81f141..ea88c4942ccb 100644
--- a/tools/perf/util/debug.c
+++ b/tools/perf/util/debug.c
@@ -339,7 +339,9 @@ void __dump_stack(FILE *file, void **stackdump, size_t stackdump_size)
 		bool printed = false;
 
 		addr_location__init(&al);
-		if (thread && thread__find_map(thread, PERF_RECORD_MISC_USER, addr, &al)) {
+		if (thread && thread__find_map(thread,
+				       &(struct perf_sample){.cpumode = PERF_RECORD_MISC_USER,
+							     .ip = addr}, &al)) {
 			al.sym = map__find_symbol(al.map, al.addr);
 			if (al.sym) {
 				fprintf(file, "    #%zd %p in %s ", i, stackdump[i], al.sym->name);
diff --git a/tools/perf/util/dlfilter.c b/tools/perf/util/dlfilter.c
index e11e144af62b..8a33bfe50939 100644
--- a/tools/perf/util/dlfilter.c
+++ b/tools/perf/util/dlfilter.c
@@ -177,7 +177,9 @@ static __s32 dlfilter__resolve_address(void *ctx, __u64 address, struct perf_dlf
 		return -1;
 
 	addr_location__init(&al);
-	thread__find_symbol_fb(thread, d->sample->cpumode, address, &al);
+	thread__find_symbol_fb(thread,
+			       &(struct perf_sample){.cpumode = d->sample->cpumode,
+						     .ip = address}, &al);
 
 	al_to_d_al(&al, &d_al);
 
@@ -314,7 +316,9 @@ static __s32 dlfilter__object_code(void *ctx, __u64 ip, void *buf, __u32 len)
 
 	addr_location__init(&a);
 
-	thread__find_map_fb(al->thread, d->sample->cpumode, ip, &a);
+	thread__find_map_fb(al->thread,
+			    &(struct perf_sample){.cpumode = d->sample->cpumode,
+						  .ip = ip}, &a);
 	ret = a.map ? code_read(ip, a.map, d->machine, buf, len) : -1;
 
 	addr_location__exit(&a);
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
index c69ae57ce679..982fb9ed049f 100644
--- a/tools/perf/util/event.c
+++ b/tools/perf/util/event.c
@@ -691,7 +691,7 @@ int perf_event__process(const struct perf_tool *tool __maybe_unused,
 	return machine__process_event(machine, event, sample);
 }
 
-struct map *thread__find_map(struct thread *thread, u8 cpumode, u64 addr,
+struct map *thread__find_map(struct thread *thread, struct perf_sample *sample,
 			     struct addr_location *al)
 {
 	struct maps *maps = thread__maps(thread);
@@ -702,34 +702,39 @@ struct map *thread__find_map(struct thread *thread, u8 cpumode, u64 addr,
 	thread__zput(al->thread);
 	al->thread = thread__get(thread);
 
-	al->addr = addr;
-	al->cpumode = cpumode;
+	if (sample->ip) {
+		al->addr = sample->ip;
+	} else {
+		pr_debug("Sample missing IP data\n");
+		return NULL;
+	}
+	al->cpumode = sample->cpumode;
 	al->filtered = 0;
 
 	if (machine == NULL)
 		return NULL;
 
-	if (cpumode == PERF_RECORD_MISC_KERNEL && perf_host) {
+	if (sample->cpumode == PERF_RECORD_MISC_KERNEL && perf_host) {
 		al->level = 'k';
 		maps = machine__kernel_maps(machine);
 		load_map = !symbol_conf.lazy_load_kernel_maps;
-	} else if (cpumode == PERF_RECORD_MISC_USER && perf_host) {
+	} else if (sample->cpumode == PERF_RECORD_MISC_USER && perf_host) {
 		al->level = '.';
-	} else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL && perf_guest) {
+	} else if (sample->cpumode == PERF_RECORD_MISC_GUEST_KERNEL && perf_guest) {
 		al->level = 'g';
 		maps = machine__kernel_maps(machine);
 		load_map = !symbol_conf.lazy_load_kernel_maps;
-	} else if (cpumode == PERF_RECORD_MISC_GUEST_USER && perf_guest) {
+	} else if (sample->cpumode == PERF_RECORD_MISC_GUEST_USER && perf_guest) {
 		al->level = 'u';
 	} else {
 		al->level = 'H';
 
-		if ((cpumode == PERF_RECORD_MISC_GUEST_USER ||
-			cpumode == PERF_RECORD_MISC_GUEST_KERNEL) &&
+		if ((sample->cpumode == PERF_RECORD_MISC_GUEST_USER ||
+			sample->cpumode == PERF_RECORD_MISC_GUEST_KERNEL) &&
 			!perf_guest)
 			al->filtered |= (1 << HIST_FILTER__GUEST);
-		if ((cpumode == PERF_RECORD_MISC_USER ||
-			cpumode == PERF_RECORD_MISC_KERNEL) &&
+		if ((sample->cpumode == PERF_RECORD_MISC_USER ||
+			sample->cpumode == PERF_RECORD_MISC_KERNEL) &&
 			!perf_host)
 			al->filtered |= (1 << HIST_FILTER__HOST);
 
@@ -754,33 +759,34 @@ struct map *thread__find_map(struct thread *thread, u8 cpumode, u64 addr,
  * because it applies only to the sample 'ip' and not necessary to 'addr' or
  * branch stack addresses. If possible, use a fallback to deal with those cases.
  */
-struct map *thread__find_map_fb(struct thread *thread, u8 cpumode, u64 addr,
+struct map *thread__find_map_fb(struct thread *thread, struct perf_sample *sample,
 				struct addr_location *al)
 {
-	struct map *map = thread__find_map(thread, cpumode, addr, al);
+	struct map *map = thread__find_map(thread, sample, al);
 	struct machine *machine = maps__machine(thread__maps(thread));
-	u8 addr_cpumode = machine__addr_cpumode(machine, cpumode, addr);
+	u8 addr_cpumode = machine__addr_cpumode(machine, sample->cpumode, sample->ip);
 
-	if (map || addr_cpumode == cpumode)
+	if (map || addr_cpumode == sample->cpumode)
 		return map;
 
-	return thread__find_map(thread, addr_cpumode, addr, al);
+	sample->cpumode = addr_cpumode;
+	return thread__find_map(thread, sample, al);
 }
 
-struct symbol *thread__find_symbol(struct thread *thread, u8 cpumode,
-				   u64 addr, struct addr_location *al)
+struct symbol *thread__find_symbol(struct thread *thread, struct perf_sample *sample,
+				   struct addr_location *al)
 {
 	al->sym = NULL;
-	if (thread__find_map(thread, cpumode, addr, al))
+	if (thread__find_map(thread, sample, al))
 		al->sym = map__find_symbol(al->map, al->addr);
 	return al->sym;
 }
 
-struct symbol *thread__find_symbol_fb(struct thread *thread, u8 cpumode,
-				      u64 addr, struct addr_location *al)
+struct symbol *thread__find_symbol_fb(struct thread *thread, struct perf_sample *sample,
+				      struct addr_location *al)
 {
 	al->sym = NULL;
-	if (thread__find_map_fb(thread, cpumode, addr, al))
+	if (thread__find_map_fb(thread, sample, al))
 		al->sym = map__find_symbol(al->map, al->addr);
 	return al->sym;
 }
@@ -816,7 +822,10 @@ int machine__resolve(struct machine *machine, struct addr_location *al,
 		return -1;
 
 	dump_printf(" ... thread: %s:%d\n", thread__comm_str(thread), thread__tid(thread));
-	thread__find_map(thread, sample->cpumode, sample->ip, al);
+	thread__find_map(thread,
+			 &(struct perf_sample){.cpumode = sample->cpumode,
+					       .ip = sample->ip,
+					       .bid = sample->bid}, al);
 	dso = al->map ? map__dso(al->map) : NULL;
 	dump_printf(" ...... dso: %s\n",
 		dso
@@ -934,7 +943,9 @@ bool sample_addr_correlates_sym(struct perf_event_attr *attr)
 void thread__resolve(struct thread *thread, struct addr_location *al,
 		     struct perf_sample *sample)
 {
-	thread__find_map_fb(thread, sample->cpumode, sample->addr, al);
+	thread__find_map_fb(thread,
+			    &(struct perf_sample){.cpumode = sample->cpumode,
+						  .ip = sample->addr}, al);
 
 	al->cpu = sample->cpu;
 	al->sym = NULL;
diff --git a/tools/perf/util/intel-pt.c b/tools/perf/util/intel-pt.c
index 9d6628169fd9..228af524e9ce 100644
--- a/tools/perf/util/intel-pt.c
+++ b/tools/perf/util/intel-pt.c
@@ -812,7 +812,9 @@ static int intel_pt_walk_next_insn(struct intel_pt_insn *intel_pt_insn,
 	while (1) {
 		struct dso *dso;
 
-		if (!thread__find_map(thread, cpumode, *ip, &al) || !map__dso(al.map)) {
+		if (!thread__find_map(thread,
+				      &(struct perf_sample){.cpumode = cpumode,
+							    .ip = *ip}, &al) || !map__dso(al.map)) {
 			if (al.map)
 				intel_pt_log("ERROR: thread has no dso for %#" PRIx64 "\n", *ip);
 			else
@@ -1007,7 +1009,9 @@ static int __intel_pt_pgd_ip(uint64_t ip, void *data)
 		return -EINVAL;
 
 	addr_location__init(&al);
-	if (!thread__find_map(thread, cpumode, ip, &al) || !map__dso(al.map))
+	if (!thread__find_map(thread,
+			      &(struct perf_sample){.cpumode = cpumode,
+						    .ip = ip}, &al) || !map__dso(al.map))
 		return -EINVAL;
 
 	offset = map__map_ip(al.map, ip);
@@ -3656,7 +3660,9 @@ static int intel_pt_find_map(struct thread *thread, u8 cpumode, u64 addr,
 			     struct addr_location *al)
 {
 	if (!al->map || addr < map__start(al->map) || addr >= map__end(al->map)) {
-		if (!thread__find_map(thread, cpumode, addr, al))
+		if (!thread__find_map(thread,
+				      &(struct perf_sample){.cpumode = cpumode,
+							    .ip = addr}, al))
 			return -1;
 	}
 
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index 21d54ebc866b..aeace492b4e7 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -2072,7 +2072,9 @@ static void ip__resolve_ams(struct thread *thread,
 	 * Thus, we have to try consecutively until we find a match
 	 * or else, the symbol is unknown
 	 */
-	thread__find_cpumode_addr_location(thread, ip, /*symbols=*/true, &al);
+	thread__find_cpumode_addr_location(thread,
+					   &(struct perf_sample){.ip = ip},
+					   /*symbols=*/true, &al);
 
 	ams->addr = ip;
 	ams->al_addr = al.addr;
@@ -2093,7 +2095,7 @@ static void ip__resolve_data(struct thread *thread,
 
 	addr_location__init(&al);
 
-	thread__find_symbol(thread, m, addr, &al);
+	thread__find_symbol(thread, &(struct perf_sample){.cpumode = m, .ip = addr}, &al);
 
 	ams->addr = addr;
 	ams->al_addr = al.addr;
@@ -2227,7 +2229,9 @@ static int add_callchain_ip(struct thread *thread,
 	al.sym = NULL;
 	al.srcline = NULL;
 	if (!cpumode) {
-		thread__find_cpumode_addr_location(thread, ip, symbols, &al);
+		thread__find_cpumode_addr_location(thread,
+					   &(struct perf_sample){.ip = ip},
+					   symbols, &al);
 	} else {
 		if (ip >= PERF_CONTEXT_MAX) {
 			switch (ip) {
@@ -2255,9 +2259,13 @@ static int add_callchain_ip(struct thread *thread,
 			goto out;
 		}
 		if (symbols)
-			thread__find_symbol(thread, *cpumode, ip, &al);
+			thread__find_symbol(thread,
+				    &(struct perf_sample){.cpumode = *cpumode, .ip = ip},
+				    &al);
 		else
-			thread__find_map(thread, *cpumode, ip, &al);
+			thread__find_map(thread,
+				 &(struct perf_sample){.cpumode = *cpumode, .ip = ip},
+				 &al);
 	}
 
 	if (al.sym != NULL) {
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index d35190052d97..8857faf445cb 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -808,7 +808,10 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args)
 
 	if (addr != pevent->sample.ip) {
 		addr_location__init(&al);
-		thread__find_symbol_fb(pevent->al.thread, pevent->sample.cpumode, addr, &al);
+		thread__find_symbol_fb(pevent->al.thread,
+			       &(struct perf_sample){
+				       .cpumode = pevent->sample.cpumode,
+				       .ip = addr}, &al);
 	} else {
 		addr_location__init(&al);
 		al.thread = thread__get(pevent->al.thread);
diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c
index 8f832ae316ca..8f7512c889a7 100644
--- a/tools/perf/util/scripting-engines/trace-event-python.c
+++ b/tools/perf/util/scripting-engines/trace-event-python.c
@@ -520,14 +520,16 @@ static PyObject *python_process_brstack(struct perf_sample *sample,
 		    PyLong_FromUnsignedLongLong(entries[i].flags.cycles));
 
 		addr_location__init(&al);
-		thread__find_map_fb(thread, sample->cpumode,
-				    entries[i].from, &al);
+		thread__find_map_fb(thread,
+				    &(struct perf_sample){.cpumode = sample->cpumode,
+							  .ip = entries[i].from}, &al);
 		dsoname = get_dsoname(al.map);
 		pydict_set_item_string_decref(pyelem, "from_dsoname",
 					      _PyUnicode_FromString(dsoname));
 
-		thread__find_map_fb(thread, sample->cpumode,
-				    entries[i].to, &al);
+		thread__find_map_fb(thread,
+				    &(struct perf_sample){.cpumode = sample->cpumode,
+							  .ip = entries[i].to}, &al);
 		dsoname = get_dsoname(al.map);
 		pydict_set_item_string_decref(pyelem, "to_dsoname",
 					      _PyUnicode_FromString(dsoname));
@@ -593,14 +595,16 @@ static PyObject *python_process_brstacksym(struct perf_sample *sample,
 		if (!pyelem)
 			Py_FatalError("couldn't create Python dictionary");
 
-		thread__find_symbol_fb(thread, sample->cpumode,
-				       entries[i].from, &al);
+		thread__find_symbol_fb(thread,
+				       &(struct perf_sample){.cpumode = sample->cpumode,
+							     .ip = entries[i].from}, &al);
 		get_symoff(al.sym, &al, true, bf, sizeof(bf));
 		pydict_set_item_string_decref(pyelem, "from",
 					      _PyUnicode_FromString(bf));
 
-		thread__find_symbol_fb(thread, sample->cpumode,
-				       entries[i].to, &al);
+		thread__find_symbol_fb(thread,
+				       &(struct perf_sample){.cpumode = sample->cpumode,
+							     .ip = entries[i].to}, &al);
 		get_symoff(al.sym, &al, true, bf, sizeof(bf));
 		pydict_set_item_string_decref(pyelem, "to",
 					      _PyUnicode_FromString(bf));
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
index e483ffcb5d93..e79b2c0dd5d5 100644
--- a/tools/perf/util/thread.c
+++ b/tools/perf/util/thread.c
@@ -413,7 +413,7 @@ int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp, bo
 	return thread__clone_maps(thread, parent, do_maps_clone);
 }
 
-void thread__find_cpumode_addr_location(struct thread *thread, u64 addr,
+void thread__find_cpumode_addr_location(struct thread *thread, struct perf_sample *sample,
 					bool symbols, struct addr_location *al)
 {
 	size_t i;
@@ -425,10 +425,11 @@ void thread__find_cpumode_addr_location(struct thread *thread, u64 addr,
 	};
 
 	for (i = 0; i < ARRAY_SIZE(cpumodes); i++) {
+		sample->cpumode = cpumodes[i];
 		if (symbols)
-			thread__find_symbol(thread, cpumodes[i], addr, al);
+			thread__find_symbol(thread, sample, al);
 		else
-			thread__find_map(thread, cpumodes[i], addr, al);
+			thread__find_map(thread, sample, al);
 
 		if (al->map)
 			break;
@@ -581,6 +582,7 @@ int thread__memcpy(struct thread *thread, struct machine *machine,
 		   void *buf, u64 ip, int len, bool *is64bit)
 {
 	u8 cpumode = PERF_RECORD_MISC_USER;
+	struct perf_sample sample = { .ip = ip };
 	struct addr_location al;
 	struct dso *dso;
 	long offset;
@@ -589,7 +591,8 @@ int thread__memcpy(struct thread *thread, struct machine *machine,
 		cpumode = PERF_RECORD_MISC_KERNEL;
 
 	addr_location__init(&al);
-	if (!thread__find_map(thread, cpumode, ip, &al)) {
+	sample.cpumode = cpumode;
+	if (!thread__find_map(thread, &sample, &al)) {
 		addr_location__exit(&al);
 		return -1;
 	}
diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h
index d82fce8173ae..f0c50eafeee5 100644
--- a/tools/perf/util/thread.h
+++ b/tools/perf/util/thread.h
@@ -124,17 +124,17 @@ size_t thread__fprintf(struct thread *thread, FILE *fp);
 
 struct thread *thread__main_thread(struct machine *machine, struct thread *thread);
 
-struct map *thread__find_map(struct thread *thread, u8 cpumode, u64 addr,
+struct map *thread__find_map(struct thread *thread, struct perf_sample *sample,
 			     struct addr_location *al);
-struct map *thread__find_map_fb(struct thread *thread, u8 cpumode, u64 addr,
+struct map *thread__find_map_fb(struct thread *thread, struct perf_sample *sample,
 				struct addr_location *al);
 
-struct symbol *thread__find_symbol(struct thread *thread, u8 cpumode,
-				   u64 addr, struct addr_location *al);
-struct symbol *thread__find_symbol_fb(struct thread *thread, u8 cpumode,
-				      u64 addr, struct addr_location *al);
+struct symbol *thread__find_symbol(struct thread *thread, struct perf_sample *sample,
+				   struct addr_location *al);
+struct symbol *thread__find_symbol_fb(struct thread *thread, struct perf_sample *sample,
+				      struct addr_location *al);
 
-void thread__find_cpumode_addr_location(struct thread *thread, u64 addr,
+void thread__find_cpumode_addr_location(struct thread *thread, struct perf_sample *sample,
 					bool symbols, struct addr_location *al);
 
 int thread__memcpy(struct thread *thread, struct machine *machine,
diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c
index 63a5c2253174..10779a0c793e 100644
--- a/tools/perf/util/unwind-libdw.c
+++ b/tools/perf/util/unwind-libdw.c
@@ -77,7 +77,9 @@ static int __report_module(struct addr_location *al, u64 ip,
 	 * Some callers will use al->sym, so we can't just use the
 	 * cheaper thread__find_map() here.
 	 */
-	thread__find_symbol(ui->thread, PERF_RECORD_MISC_USER, ip, al);
+	thread__find_symbol(ui->thread,
+			    &(struct perf_sample){.cpumode = PERF_RECORD_MISC_USER, .ip = ip},
+			    al);
 
 	if (al->map)
 		dso = map__dso(al->map);
@@ -203,12 +205,13 @@ static bool get_thread(Dwfl *dwfl __maybe_unused, pid_t tid, void *arg,
 static int access_dso_mem(struct unwind_info *ui, Dwarf_Addr addr,
 			  Dwarf_Word *data)
 {
+	struct perf_sample sample = { .cpumode = PERF_RECORD_MISC_USER, .ip = addr };
 	struct addr_location al;
 	ssize_t size;
 	struct dso *dso;
 
 	addr_location__init(&al);
-	if (!thread__find_map(ui->thread, PERF_RECORD_MISC_USER, addr, &al)) {
+	if (!thread__find_map(ui->thread, &sample, &al)) {
 		pr_debug("unwind: no map for %lx\n", (unsigned long)addr);
 		goto out_fail;
 	}
diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c
index 73d191ce51a5..5d9f9002710a 100644
--- a/tools/perf/util/unwind-libunwind.c
+++ b/tools/perf/util/unwind-libunwind.c
@@ -351,7 +351,9 @@ static struct map *find_map(uint64_t ip, struct unwind_info *ui)
 	struct map *ret;
 
 	addr_location__init(&al);
-	thread__find_map(ui->thread, PERF_RECORD_MISC_USER, ip, &al);
+	thread__find_map(ui->thread,
+			 &(struct perf_sample){.cpumode = PERF_RECORD_MISC_USER, .ip = ip},
+			 &al);
 	ret = map__get(al.map);
 	addr_location__exit(&al);
 	return ret;
@@ -596,7 +598,9 @@ static int entry(uint64_t ip, struct thread *thread, unwind_entry_cb_t cb, void
 	int ret;
 
 	addr_location__init(&al);
-	e.ms.sym = thread__find_symbol(thread, PERF_RECORD_MISC_USER, ip, &al);
+	e.ms.sym = thread__find_symbol(thread,
+				       &(struct perf_sample){.cpumode = PERF_RECORD_MISC_USER,
+							     .ip = ip}, &al);
 	e.ip     = ip;
 	e.ms.map = al.map;
 	e.ms.thread = thread__get(al.thread);
-- 
2.55.0.679.g6767b8d81c-goog


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

* [RFC PATCH v1 5/8] perf tools: Internal support for BUILD_ID_OFFSET
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
                   ` (3 preceding siblings ...)
  2026-08-07  7:18 ` [RFC PATCH v1 4/8] perf: Refactor thread map and symbol APIs to take perf_sample Ian Rogers
@ 2026-08-07  7:18 ` Ian Rogers
  2026-08-07  8:41   ` sashiko-bot
  2026-08-07  7:18 ` [RFC PATCH v1 6/8] perf inject: Extend perf inject to support bid_offset conversion Ian Rogers
                   ` (3 subsequent siblings)
  8 siblings, 1 reply; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

Implement user-space parsing for the new sample types in evsel,
update perf_event_attr_fprintf to display the new bits
in 'perf report -D', and update synthetic event parameter names.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/builtin-script.c               | 16 +++--
 tools/perf/util/event.c                   | 42 +++++++----
 tools/perf/util/evsel.c                   | 24 +++++++
 tools/perf/util/evsel.h                   |  3 +-
 tools/perf/util/evsel_fprintf.c           |  2 +-
 tools/perf/util/machine.c                 | 87 ++++++++++++++++-------
 tools/perf/util/maps.c                    | 29 ++++++++
 tools/perf/util/maps.h                    |  2 +
 tools/perf/util/perf_event_attr_fprintf.c |  3 +-
 tools/perf/util/sample.h                  | 13 ++++
 tools/perf/util/synthetic-events.c        | 20 ++++++
 11 files changed, 192 insertions(+), 49 deletions(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index 93cffafb1af7..65229e9952a8 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -494,7 +494,8 @@ static int evsel__check_attr(struct evsel *evsel, struct perf_session *session)
 		return -EINVAL;
 
 	if (PRINT_FIELD(IP)) {
-		if (evsel__check_stype(evsel, PERF_SAMPLE_IP, "IP", PERF_OUTPUT_IP))
+		if (evsel__check_stype(evsel, PERF_SAMPLE_IP | PERF_SAMPLE_BUILD_ID_OFFSET,
+			       "IP", PERF_OUTPUT_IP))
 			return -EINVAL;
 	}
 
@@ -511,7 +512,8 @@ static int evsel__check_attr(struct evsel *evsel, struct perf_session *session)
 		return -EINVAL;
 
 	if (PRINT_FIELD(SYM) &&
-	    !(evsel->core.attr.sample_type & (PERF_SAMPLE_IP|PERF_SAMPLE_ADDR))) {
+	    !(evsel->core.attr.sample_type & (PERF_SAMPLE_IP | PERF_SAMPLE_ADDR |
+					      PERF_SAMPLE_BUILD_ID_OFFSET))) {
 		pr_err("Display of symbols requested but neither sample IP nor "
 			   "sample address\navailable. Hence, no addresses to convert "
 		       "to symbols.\n");
@@ -523,7 +525,8 @@ static int evsel__check_attr(struct evsel *evsel, struct perf_session *session)
 		return -EINVAL;
 	}
 	if (PRINT_FIELD(DSO) &&
-	    !(evsel->core.attr.sample_type & (PERF_SAMPLE_IP|PERF_SAMPLE_ADDR))) {
+	    !(evsel->core.attr.sample_type & (PERF_SAMPLE_IP | PERF_SAMPLE_ADDR |
+					      PERF_SAMPLE_BUILD_ID_OFFSET))) {
 		pr_err("Display of DSO requested but no address to convert.\n");
 		return -EINVAL;
 	}
@@ -1702,7 +1705,7 @@ static int perf_sample__fprintf_bts(struct perf_sample *sample,
 		unsigned int print_opts = output[type].print_ip_opts;
 		struct callchain_cursor *cursor = NULL;
 
-		if (symbol_conf.use_callchain && sample->callchain) {
+		if (symbol_conf.use_callchain && (sample->callchain || sample->callchain_bids)) {
 			cursor = get_tls_callchain_cursor();
 			if (thread__resolve_callchain(al->thread, cursor,
 						      sample, NULL, NULL,
@@ -2528,7 +2531,7 @@ static void process_event(struct perf_script *script,
 		if (script->stitch_lbr)
 			thread__set_lbr_stitch_enable(al->thread, true);
 
-		if (symbol_conf.use_callchain && sample->callchain) {
+		if (symbol_conf.use_callchain && (sample->callchain || sample->callchain_bids)) {
 			cursor = get_tls_callchain_cursor();
 			if (thread__resolve_callchain(al->thread, cursor,
 						      sample, NULL, NULL,
@@ -2815,7 +2818,7 @@ static int process_deferred_sample_event(const struct perf_tool *tool,
 	if (PRINT_FIELD(IP)) {
 		struct callchain_cursor *cursor = NULL;
 
-		if (symbol_conf.use_callchain && sample->callchain) {
+		if (symbol_conf.use_callchain && (sample->callchain || sample->callchain_bids)) {
 			cursor = get_tls_callchain_cursor();
 			if (thread__resolve_callchain(al.thread, cursor,
 						      sample, NULL, NULL,
@@ -2897,6 +2900,7 @@ static int process_attr(const struct perf_tool *tool, union perf_event *event,
 	/* Enable fields for callchain entries */
 	if (symbol_conf.use_callchain &&
 	    (sample_type & PERF_SAMPLE_CALLCHAIN ||
+	     sample_type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET ||
 	     sample_type & PERF_SAMPLE_BRANCH_STACK ||
 	     (sample_type & PERF_SAMPLE_REGS_USER &&
 	      sample_type & PERF_SAMPLE_STACK_USER))) {
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
index 982fb9ed049f..86df3edd1bfa 100644
--- a/tools/perf/util/event.c
+++ b/tools/perf/util/event.c
@@ -702,12 +702,6 @@ struct map *thread__find_map(struct thread *thread, struct perf_sample *sample,
 	thread__zput(al->thread);
 	al->thread = thread__get(thread);
 
-	if (sample->ip) {
-		al->addr = sample->ip;
-	} else {
-		pr_debug("Sample missing IP data\n");
-		return NULL;
-	}
 	al->cpumode = sample->cpumode;
 	al->filtered = 0;
 
@@ -740,15 +734,33 @@ struct map *thread__find_map(struct thread *thread, struct perf_sample *sample,
 
 		return NULL;
 	}
-	al->map = maps__find(maps, al->addr);
-	if (al->map != NULL) {
-		/*
-		 * Kernel maps might be changed when loading symbols so loading
-		 * must be done prior to using kernel maps.
-		 */
-		if (load_map)
-			map__load(al->map);
-		al->addr = map__map_ip(al->map, al->addr);
+
+	if (sample->ip) {
+		al->addr = sample->ip;
+		al->map = maps__find(maps, al->addr);
+		if (al->map != NULL) {
+			/*
+			 * Kernel maps might be changed when loading symbols so loading
+			 * must be done prior to using kernel maps.
+			 */
+			if (load_map)
+				map__load(al->map);
+			al->addr = map__map_ip(al->map, al->addr);
+		}
+	} else if (sample->bid.bid && sample->bid.bid->size > 0) {
+		struct build_id bid;
+
+		build_id__init(&bid, sample->bid.bid->data, sample->bid.bid->size);
+		al->addr = sample->bid.offset;
+		al->map = maps__find_by_build_id(maps, &bid);
+		if (al->map != NULL) {
+			if (load_map)
+				map__load(al->map);
+			/* al->addr is already a file offset */
+		}
+	} else {
+		pr_debug("Sample missing IP or build id + offset data\n");
+		return NULL;
 	}
 
 	return al->map;
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index d4cb455f4a7d..d9a07f5825e8 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3778,6 +3778,30 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
 		array = (void *)array + sz;
 	}
 
+	if (type & PERF_SAMPLE_BUILD_ID_OFFSET) {
+		data->bid.bid = (struct perf_build_id *) array;
+		array += sizeof(struct perf_build_id) / sizeof(u64);
+		data->bid.offset = *array;
+		array++;
+	} else {
+		data->bid.bid = NULL;
+		data->bid.offset = 0;
+	}
+
+
+	if (type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET) {
+		OVERFLOW_CHECK_u64(array);
+		sz = *array++;
+
+		OVERFLOW_CHECK(array, sz * (sizeof(struct perf_build_id) + sizeof(u64)), max_size);
+		data->callchain_bids = (struct perf_build_id *) array;
+		data->callchain_bids_nr = sz;
+		array += sz * (sizeof(struct perf_build_id) + sizeof(u64)) / sizeof(u64);
+	} else {
+		data->callchain_bids = NULL;
+		data->callchain_bids_nr = 0;
+	}
+
 	if (evsel__is_offcpu_event(evsel)) {
 		if (__set_offcpu_sample(data))
 			goto out_efault;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index d9ecc6628217..e082e6e6af66 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -567,7 +567,8 @@ static inline bool evsel__has_callchain(const struct evsel *evsel)
 	 * For reporting purposes, an evsel sample can have a recorded callchain
 	 * or a callchain synthesized from AUX area data.
 	 */
-	return evsel->core.attr.sample_type & PERF_SAMPLE_CALLCHAIN ||
+	return evsel->core.attr.sample_type & (PERF_SAMPLE_CALLCHAIN |
+				       PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET) ||
 	       evsel->synth_sample_type & PERF_SAMPLE_CALLCHAIN;
 }
 
diff --git a/tools/perf/util/evsel_fprintf.c b/tools/perf/util/evsel_fprintf.c
index 0f7a25500a44..9f7054410a2c 100644
--- a/tools/perf/util/evsel_fprintf.c
+++ b/tools/perf/util/evsel_fprintf.c
@@ -131,7 +131,7 @@ int sample__fprintf_callchain(struct perf_sample *sample, int left_alignment,
 	if (cursor == NULL)
 		return fprintf(fp, "<not enough memory for the callchain cursor>%s", print_oneline ? "" : "\n");
 
-	if (sample->callchain) {
+	if (sample->callchain || sample->callchain_bids) {
 		callchain_cursor_commit(cursor);
 
 		while (1) {
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index aeace492b4e7..82de7bc7eba1 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -2216,7 +2216,9 @@ static int add_callchain_ip(struct thread *thread,
 			    struct branch_flags *flags,
 			    struct iterations *iter,
 			    u64 branch_from,
-			    bool symbols)
+			    bool symbols,
+			      struct perf_build_id *bid,
+			      u64 offset)
 {
 	struct map_symbol ms = {};
 	struct addr_location al;
@@ -2230,7 +2232,9 @@ static int add_callchain_ip(struct thread *thread,
 	al.srcline = NULL;
 	if (!cpumode) {
 		thread__find_cpumode_addr_location(thread,
-					   &(struct perf_sample){.ip = ip},
+					   &(struct perf_sample){.ip = ip,
+								 .bid = { .bid = bid,
+									  .offset = offset }},
 					   symbols, &al);
 	} else {
 		if (ip >= PERF_CONTEXT_MAX) {
@@ -2411,7 +2415,7 @@ static int lbr_callchain_add_kernel_ip(struct thread *thread,
 			err = add_callchain_ip(thread, cursor, parent,
 					       root_al, &cpumode, chain->ips[i],
 					       false, NULL, NULL, branch_from,
-					       symbols);
+					       symbols, /*bid=*/NULL, /*offset=*/0);
 			if (err)
 				return err;
 		}
@@ -2422,7 +2426,7 @@ static int lbr_callchain_add_kernel_ip(struct thread *thread,
 		err = add_callchain_ip(thread, cursor, parent,
 				       root_al, &cpumode, chain->ips[i],
 				       false, NULL, NULL, branch_from,
-				       symbols);
+				       symbols, /*bid=*/NULL, /*offset=*/0);
 		if (err)
 			return err;
 	}
@@ -2505,7 +2509,7 @@ static int lbr_callchain_add_lbr_ip(struct thread *thread,
 		err = add_callchain_ip(thread, cursor, parent,
 				       root_al, &cpumode, ip,
 				       true, flags, NULL,
-				       *branch_from, symbols);
+				       *branch_from, symbols, /*bid=*/NULL, /*offset=*/0);
 		if (err)
 			return err;
 
@@ -2530,7 +2534,7 @@ static int lbr_callchain_add_lbr_ip(struct thread *thread,
 			err = add_callchain_ip(thread, cursor, parent,
 					       root_al, &cpumode, ip,
 					       true, flags, NULL,
-					       *branch_from, symbols);
+					       *branch_from, symbols, /*bid=*/NULL, /*offset=*/0);
 			if (err)
 				return err;
 			save_lbr_cursor_node(thread, cursor, i);
@@ -2545,7 +2549,7 @@ static int lbr_callchain_add_lbr_ip(struct thread *thread,
 		err = add_callchain_ip(thread, cursor, parent,
 				       root_al, &cpumode, ip,
 				       true, flags, NULL,
-				       *branch_from, symbols);
+				       *branch_from, symbols, /*bid=*/NULL, /*offset=*/0);
 		if (err)
 			return err;
 		save_lbr_cursor_node(thread, cursor, i);
@@ -2565,7 +2569,7 @@ static int lbr_callchain_add_lbr_ip(struct thread *thread,
 		err = add_callchain_ip(thread, cursor, parent,
 				root_al, &cpumode, ip,
 				true, flags, NULL,
-				*branch_from, symbols);
+				*branch_from, symbols, /*bid=*/NULL, /*offset=*/0);
 		if (err)
 			return err;
 	}
@@ -2829,7 +2833,8 @@ static int find_prev_cpumode(struct ip_callchain *chain, struct thread *thread,
 		if (ip >= PERF_CONTEXT_MAX) {
 			err = add_callchain_ip(thread, cursor, parent,
 					       root_al, cpumode, ip,
-					       false, NULL, NULL, 0, symbols);
+					       false, NULL, NULL, 0, symbols,
+					       /*bid=*/NULL, /*offset=*/0);
 			break;
 		}
 	}
@@ -2857,6 +2862,7 @@ static int thread__resolve_callchain_sample(struct thread *thread,
 	struct branch_stack *branch = sample->branch_stack;
 	struct branch_entry *entries = perf_sample__branch_entries(sample);
 	struct ip_callchain *chain = sample->callchain;
+	struct ip_callchain *alloc_chain = NULL;
 	int chain_nr = 0;
 	u8 cpumode = PERF_RECORD_MISC_USER;
 	int i, j, err, nr_entries, usr_idx;
@@ -2864,6 +2870,16 @@ static int thread__resolve_callchain_sample(struct thread *thread,
 	int first_call = 0;
 	u64 leaf_frame_caller;
 
+	if (!chain && sample->callchain_bids_nr > 0) {
+		alloc_chain = malloc(sizeof(*chain) + sample->callchain_bids_nr * sizeof(u64));
+		if (alloc_chain) {
+			alloc_chain->nr = sample->callchain_bids_nr;
+			for (i = 0; i < (int)sample->callchain_bids_nr; i++)
+				alloc_chain->ips[i] = ((u64 *)sample->callchain_bids)[i * 4 + 3];
+			chain = alloc_chain;
+		}
+	}
+
 	if (chain)
 		chain_nr = chain->nr;
 
@@ -2874,8 +2890,10 @@ static int thread__resolve_callchain_sample(struct thread *thread,
 						   root_al, max_stack,
 						   !env ? 0 : env->max_branches,
 						   symbols);
-		if (err)
-			return (err < 0) ? err : 0;
+		if (err) {
+			err = (err < 0) ? err : 0;
+			goto out;
+		}
 	}
 
 	/*
@@ -2938,18 +2956,20 @@ static int thread__resolve_callchain_sample(struct thread *thread,
 					       root_al,
 					       NULL, be[i].to,
 					       true, &be[i].flags,
-					       NULL, be[i].from, symbols);
+					       NULL, be[i].from, symbols,
+					       /*bid=*/NULL, /*offset=*/0);
 
 			if (!err) {
 				err = add_callchain_ip(thread, cursor, parent, root_al,
 						       NULL, be[i].from,
 						       true, &be[i].flags,
-						       &iter[i], 0, symbols);
+						       &iter[i], 0, symbols,
+						       /*bid=*/NULL, /*offset=*/0);
 			}
 			if (err == -EINVAL)
 				break;
 			if (err)
-				return err;
+				goto out;
 		}
 
 		if (chain_nr == 0)
@@ -2962,12 +2982,16 @@ static int thread__resolve_callchain_sample(struct thread *thread,
 	if (chain && callchain_param.order != ORDER_CALLEE) {
 		err = find_prev_cpumode(chain, thread, cursor, parent, root_al,
 					&cpumode, chain->nr - first_call, symbols);
-		if (err)
-			return (err < 0) ? err : 0;
+		if (err) {
+			err = (err < 0) ? err : 0;
+			goto out;
+		}
 	}
 	for (i = first_call, nr_entries = 0;
 	     i < chain_nr && nr_entries < max_stack; i++) {
 		u64 ip;
+		struct perf_build_id *bid = NULL;
+		u64 offset = 0;
 
 		if (callchain_param.order == ORDER_CALLEE)
 			j = i;
@@ -2979,13 +3003,19 @@ static int thread__resolve_callchain_sample(struct thread *thread,
 			continue;
 #endif
 		ip = chain->ips[j];
+		if (sample->callchain_bids) {
+			bid = (struct perf_build_id *)&((u64 *)sample->callchain_bids)[j * 4];
+			offset = ((u64 *)sample->callchain_bids)[j * 4 + 3];
+		}
 		if (ip < PERF_CONTEXT_MAX)
                        ++nr_entries;
 		else if (callchain_param.order != ORDER_CALLEE) {
 			err = find_prev_cpumode(chain, thread, cursor, parent,
 						root_al, &cpumode, j, symbols);
-			if (err)
-				return (err < 0) ? err : 0;
+			if (err) {
+				err = (err < 0) ? err : 0;
+				goto out;
+			}
 			continue;
 		}
 
@@ -3011,21 +3041,28 @@ static int thread__resolve_callchain_sample(struct thread *thread,
 
 				err = add_callchain_ip(thread, cursor, parent,
 						root_al, &cpumode, leaf_frame_caller,
-						false, NULL, NULL, 0, symbols);
-				if (err)
-					return (err < 0) ? err : 0;
+						false, NULL, NULL, 0, symbols, bid, offset);
+				if (err) {
+					err = (err < 0) ? err : 0;
+					goto out;
+				}
 			}
 		}
 
 		err = add_callchain_ip(thread, cursor, parent,
 				       root_al, &cpumode, ip,
-				       false, NULL, NULL, 0, symbols);
+				       false, NULL, NULL, 0, symbols, bid, offset);
 
-		if (err)
-			return (err < 0) ? err : 0;
+		if (err) {
+			err = (err < 0) ? err : 0;
+			goto out;
+		}
 	}
 
-	return 0;
+	err = 0;
+out:
+	free(alloc_chain);
+	return err;
 }
 
 static int unwind_entry(struct unwind_entry *entry, void *arg)
diff --git a/tools/perf/util/maps.c b/tools/perf/util/maps.c
index f808df2fe77b..7884720481d8 100644
--- a/tools/perf/util/maps.c
+++ b/tools/perf/util/maps.c
@@ -1251,6 +1251,35 @@ static int map__strcmp_name(const void *name, const void *b)
 	return strcmp(name, dso__short_name(dso));
 }
 
+struct map *maps__find_by_build_id(struct maps *maps, struct build_id *bid)
+{
+	struct map *result = NULL;
+	bool done = false;
+
+	/* See locking/sorting note. */
+	while (!done) {
+		down_read(maps__lock(maps));
+		if (maps__maps_by_address_sorted(maps)) {
+			struct map **maps_by_address = maps__maps_by_address(maps);
+			unsigned int nr_maps = maps__nr_maps(maps);
+
+			for (unsigned int i = 0; i < nr_maps; i++) {
+				struct map *map = maps_by_address[i];
+
+				if (dso__build_id_equal(map__dso(map), bid)) {
+					result = map__get(map);
+					break;
+				}
+			}
+			done = true;
+		}
+		up_read(maps__lock(maps));
+		if (!done)
+			maps__sort_by_address(maps);
+	}
+	return result;
+}
+
 struct map *maps__find_by_name(struct maps *maps, const char *name)
 {
 	struct map *result = NULL;
diff --git a/tools/perf/util/maps.h b/tools/perf/util/maps.h
index 4ec9b7453a3b..6f1791457068 100644
--- a/tools/perf/util/maps.h
+++ b/tools/perf/util/maps.h
@@ -11,6 +11,7 @@ struct ref_reloc_sym;
 struct machine;
 struct map;
 struct maps;
+struct build_id;
 
 #define KMAP_NAME_LEN 256
 
@@ -76,6 +77,7 @@ int maps__find_ams(struct maps *maps, struct addr_map_symbol *ams);
 int maps__fixup_overlap_and_insert(struct maps *maps, struct map *new);
 
 struct map *maps__find_by_name(struct maps *maps, const char *name);
+struct map *maps__find_by_build_id(struct maps *maps, struct build_id *bid);
 
 struct map *maps__find_next_entry(struct maps *maps, struct map *map);
 
diff --git a/tools/perf/util/perf_event_attr_fprintf.c b/tools/perf/util/perf_event_attr_fprintf.c
index 3933639d76c5..41ccb45eca86 100644
--- a/tools/perf/util/perf_event_attr_fprintf.c
+++ b/tools/perf/util/perf_event_attr_fprintf.c
@@ -40,7 +40,8 @@ static void __p_sample_type(char *buf, size_t size, u64 value)
 		bit_name(IDENTIFIER), bit_name(REGS_INTR), bit_name(DATA_SRC),
 		bit_name(WEIGHT), bit_name(PHYS_ADDR), bit_name(AUX),
 		bit_name(CGROUP), bit_name(DATA_PAGE_SIZE), bit_name(CODE_PAGE_SIZE),
-		bit_name(WEIGHT_STRUCT),
+		bit_name(WEIGHT_STRUCT), bit_name(BUILD_ID_OFFSET),
+		bit_name(CALLCHAIN_BUILD_ID_OFFSET),
 		{ .name = NULL, }
 	};
 #undef bit_name
diff --git a/tools/perf/util/sample.h b/tools/perf/util/sample.h
index c4eae8b2fd06..5041c193a953 100644
--- a/tools/perf/util/sample.h
+++ b/tools/perf/util/sample.h
@@ -8,6 +8,7 @@
 struct evsel;
 struct machine;
 struct thread;
+struct perf_build_id;
 
 /* number of register is bound by the number of bits in regs_dump::mask (64) */
 #define PERF_SAMPLE_REGS_CACHE_SIZE (8 * sizeof(u64))
@@ -158,6 +159,18 @@ struct perf_sample {
 	u64 code_page_size;
 	/** @cgroup: The sample event PERF_SAMPLE_CGROUP value. */
 	u64 cgroup;
+	/** @bid: The sample event PERF_SAMPLE_BUILD_ID_OFFSET value. */
+	struct {
+		struct perf_build_id *bid;
+		u64 offset;
+	} bid;
+	/**
+	 * @callchain_bids: Build IDs for the callchain when
+	 * PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET is set.
+	 */
+	struct perf_build_id *callchain_bids;
+	/** @callchain_bids_nr: Number of entries in callchain_bids. */
+	u32 callchain_bids_nr;
 	/** @file_offset: Byte offset of this event in the perf.data file. */
 	u64 file_offset;
 	/** @flags: Extra flag data from auxiliary events like intel-pt. */
diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic-events.c
index f7dedfb6bab8..a7b2b2a5f6bd 100644
--- a/tools/perf/util/synthetic-events.c
+++ b/tools/perf/util/synthetic-events.c
@@ -1701,6 +1701,13 @@ size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type,
 		result += sample->aux_sample.size;
 	}
 
+	if (type & PERF_SAMPLE_BUILD_ID_OFFSET)
+		result += sizeof(struct perf_build_id) + sizeof(u64);
+
+	if (type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET)
+		result += sizeof(u64) + sample->callchain->nr *
+			  (sizeof(struct perf_build_id) + sizeof(u64));
+
 	return result;
 }
 
@@ -1933,6 +1940,19 @@ int perf_event__synthesize_sample(union perf_event *event, u64 type, u64 read_fo
 		array = (void *)array + sz;
 	}
 
+	if (type & PERF_SAMPLE_BUILD_ID_OFFSET) {
+		memcpy(array, sample->bid.bid, sizeof(struct perf_build_id));
+		array += sizeof(struct perf_build_id) / sizeof(u64);
+		*array++ = sample->bid.offset;
+	}
+
+	if (type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET) {
+		*array++ = sample->callchain->nr;
+		sz = sample->callchain->nr * (sizeof(struct perf_build_id) + sizeof(u64));
+		memcpy(array, sample->callchain_bids, sz);
+		array += sz / sizeof(u64);
+	}
+
 	return 0;
 }
 
-- 
2.55.0.679.g6767b8d81c-goog


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

* [RFC PATCH v1 6/8] perf inject: Extend perf inject to support bid_offset conversion
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
                   ` (4 preceding siblings ...)
  2026-08-07  7:18 ` [RFC PATCH v1 5/8] perf tools: Internal support for BUILD_ID_OFFSET Ian Rogers
@ 2026-08-07  7:18 ` Ian Rogers
  2026-08-07  9:18   ` sashiko-bot
  2026-08-07  7:18 ` [RFC PATCH v1 7/8] perf record: Add --buildid-offset option Ian Rogers
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

This adds the --sample-buildids option to perf inject, allowing it to
drop MMAP events and rewrite samples to use build IDs and offsets
instead of virtual addresses.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/builtin-inject.c         |  14 +-
 tools/perf/util/Build               |   1 +
 tools/perf/util/inject_bid_offset.c | 504 ++++++++++++++++++++++++++++
 tools/perf/util/inject_bid_offset.h |  21 ++
 4 files changed, 539 insertions(+), 1 deletion(-)
 create mode 100644 tools/perf/util/inject_bid_offset.c
 create mode 100644 tools/perf/util/inject_bid_offset.h

diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index 6aa9e3eea438..8f8d5267a37e 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -9,6 +9,7 @@
 #include "builtin.h"
 
 #include "util/aslr.h"
+#include "util/inject_bid_offset.h"
 #include "util/color.h"
 #include "util/dso.h"
 #include "util/vdso.h"
@@ -112,6 +113,7 @@ enum build_id_rewrite_style {
 	BID_RWS__INJECT_HEADER_ALL,
 	BID_RWS__MMAP2_BUILDID_ALL,
 	BID_RWS__MMAP2_BUILDID_LAZY,
+	BID_RWS__SAMPLE_BUILDID,
 };
 
 struct perf_inject {
@@ -371,7 +373,7 @@ static s64 perf_event__repipe_auxtrace(const struct perf_tool *tool,
 	return event->auxtrace.size;
 }
 
-static int perf_event__repipe(const struct perf_tool *tool,
+int perf_event__repipe(const struct perf_tool *tool,
 			      union perf_event *event,
 			      struct perf_sample *sample __maybe_unused,
 			      struct machine *machine __maybe_unused)
@@ -2687,6 +2689,7 @@ int cmd_inject(int argc, const char **argv)
 	bool build_id_all = false;
 	bool mmap2_build_ids = false;
 	bool mmap2_build_id_all = false;
+	bool build_id_sample = false;
 
 	struct option options[] = {
 		OPT_BOOLEAN('b', "build-ids", &build_ids,
@@ -2695,8 +2698,11 @@ int cmd_inject(int argc, const char **argv)
 			    "Inject build-ids of all DSOs into the output stream"),
 		OPT_BOOLEAN('B', "mmap2-buildids", &mmap2_build_ids,
 			    "Drop unused mmap events, make others mmap2 with build IDs"),
+
 		OPT_BOOLEAN(0, "mmap2-buildid-all", &mmap2_build_id_all,
 			    "Rewrite all mmap events as mmap2 events with build IDs"),
+		OPT_BOOLEAN('S', "sample-buildids", &build_id_sample,
+			    "Drop all mmap events and rewrite samples to use build ID + offset"),
 		OPT_STRING(0, "known-build-ids", &known_build_ids,
 			   "buildid path [,buildid path...]",
 			   "build-ids to use for given paths"),
@@ -2815,8 +2821,11 @@ int cmd_inject(int argc, const char **argv)
 		inject.build_id_style = BID_RWS__MMAP2_BUILDID_ALL;
 	if (build_ids)
 		inject.build_id_style = BID_RWS__INJECT_HEADER_LAZY;
+
 	if (build_id_all)
 		inject.build_id_style = BID_RWS__INJECT_HEADER_ALL;
+	if (build_id_sample)
+		inject.build_id_style = BID_RWS__SAMPLE_BUILDID;
 
 	data.path = inject.input_name;
 
@@ -2880,8 +2889,11 @@ int cmd_inject(int argc, const char **argv)
 
 	if (IS_ERR(inject.session)) {
 		ret = PTR_ERR(inject.session);
+
 		if (inject.aslr)
 			aslr_tool__delete(tool);
+		if (inject.build_id_style == BID_RWS__SAMPLE_BUILDID)
+			inject_bid_offset_tool__delete(tool);
 		goto out_close_output;
 	}
 
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 330311cac550..cefbf2ef3fac 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -7,6 +7,7 @@ perf-util-y += addr2line.o
 perf-util-y += addr_location.o
 perf-util-y += annotate.o
 perf-util-y += aslr.o
+perf-util-y += inject_bid_offset.o
 perf-util-y += blake2s.o
 perf-util-y += block-info.o
 perf-util-y += block-range.o
diff --git a/tools/perf/util/inject_bid_offset.c b/tools/perf/util/inject_bid_offset.c
new file mode 100644
index 000000000000..f7b5fb05fac7
--- /dev/null
+++ b/tools/perf/util/inject_bid_offset.c
@@ -0,0 +1,504 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "inject_bid_offset.h"
+
+#include <stdlib.h>
+
+#include <linux/compiler.h>
+#include <linux/string.h>
+#include <linux/zalloc.h>
+
+#include "addr_location.h"
+#include "debug.h"
+#include "dso.h"
+#include "event.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "machine.h"
+#include "map.h"
+#include "session.h"
+#include "synthetic-events.h"
+#include "thread.h"
+#include "tool.h"
+
+struct inject_bid_offset_tool {
+	struct delegate_tool tool;
+	char event_copy[PERF_SAMPLE_MAX_SIZE] __aligned(8);
+};
+
+int perf_event__rewrite_attr_for_build_id_offset(struct perf_event_attr *attr)
+{
+	if (attr->sample_type & (PERF_SAMPLE_BUILD_ID_OFFSET |
+				 PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET)) {
+		/*
+		 * Expect to add build ID information from virtual address, if
+		 * it is already present then things would be confused so fail.
+		 */
+		return -1;
+	}
+	if (attr->sample_type & PERF_SAMPLE_IP) {
+		attr->sample_type &= ~PERF_SAMPLE_IP;
+		attr->sample_type |= PERF_SAMPLE_BUILD_ID_OFFSET;
+	}
+	if (attr->sample_type & PERF_SAMPLE_CALLCHAIN) {
+		attr->sample_type &= ~PERF_SAMPLE_CALLCHAIN;
+		attr->sample_type |= PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET;
+	}
+	return 0;
+}
+
+int perf_event__rewrite_attr_for_sample_ip(struct perf_event_attr *attr)
+{
+	if (attr->sample_type & (PERF_SAMPLE_IP | PERF_SAMPLE_CALLCHAIN)) {
+		/*
+		 * Expect to remove build ID information for virtual address, if
+		 * it is already present then things would be confused so fail.
+		 */
+		return -1;
+	}
+	if (attr->sample_type & PERF_SAMPLE_BUILD_ID_OFFSET) {
+		attr->sample_type &= ~PERF_SAMPLE_BUILD_ID_OFFSET;
+		attr->sample_type |= PERF_SAMPLE_IP;
+	}
+	if (attr->sample_type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET) {
+		attr->sample_type &= ~PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET;
+		attr->sample_type |= PERF_SAMPLE_CALLCHAIN;
+	}
+	return 0;
+}
+
+static void perf_event__inject_sample_buildid_array(struct thread *thread,
+						    u64 ip, u8 cpumode,
+						    __u64 *array)
+{
+	struct perf_build_id bid = { .size = 0 };
+	u64 offset = ip;
+	struct addr_location al;
+	struct dso *dso;
+	const struct build_id *dso_bid;
+
+	struct perf_sample ps = { .ip = ip, .cpumode = cpumode };
+
+	addr_location__init(&al);
+
+	if (!thread)
+		goto write_bid;
+
+	if (!thread__find_map(thread, &ps, &al))
+		goto write_bid;
+
+	dso = al.map ? dso__get(map__dso(al.map)) : NULL;
+	if (!dso)
+		goto write_bid;
+	dso_bid = dso__bid(dso);
+	if (!dso_bid) {
+		dso__put(dso);
+		goto write_bid;
+	}
+
+	bid.size = dso_bid->size;
+	if (bid.size > sizeof(bid.data))
+		bid.size = sizeof(bid.data);
+	memcpy(bid.data, &dso_bid->data, bid.size);
+	offset = map__dso_map_ip(al.map, offset);
+	dso__put(dso);
+
+write_bid:
+	compiletime_assert(sizeof(struct perf_build_id) == 3 * sizeof(u64),
+			   "Unexpected perf_build_id size");
+	memcpy(&array[0], &bid, 3 * sizeof(u64));
+	array[3] = offset;
+	addr_location__exit(&al);
+}
+
+static void mark_dso_hit(const struct perf_tool *tool,
+			 struct perf_sample *sample, struct machine *machine,
+			 struct thread *thread, u64 ip, u8 cpumode)
+{
+	struct addr_location al;
+	struct dso *dso;
+
+	struct perf_sample ps = { .ip = ip, .cpumode = cpumode };
+
+	addr_location__init(&al);
+
+	if (thread__find_map(thread, &ps, &al)) {
+		dso = al.map ? dso__get(map__dso(al.map)) : NULL;
+		if (dso) {
+			if (!dso__hit(dso)) {
+				const struct build_id *bid = dso__bid(dso);
+
+				dso__set_hit(dso);
+				if (bid) {
+					perf_event__synthesize_build_id(
+						tool, sample, machine,
+						perf_event__repipe,
+						dso__kernel(dso) ?
+							PERF_RECORD_MISC_KERNEL :
+							PERF_RECORD_MISC_USER,
+						bid, dso->long_name);
+				}
+			}
+			dso__put(dso);
+		}
+	}
+	addr_location__exit(&al);
+}
+
+static int inject_bid_offset_tool__sample(const struct perf_tool *tool,
+					  union perf_event *event,
+					  struct perf_sample *sample,
+					  struct machine *machine)
+{
+	struct delegate_tool *dt =
+		container_of(tool, struct delegate_tool, tool);
+	struct inject_bid_offset_tool *ibo =
+		container_of(dt, struct inject_bid_offset_tool, tool);
+	union perf_event *ev;
+	struct evsel *evsel = sample->evsel;
+	__u64 i = 0, j = 0;
+	__u64 *in_array, *out_array;
+	__u64 sample_type = evsel->core.attr.sample_type;
+	const __u64 max_i = event->header.size / sizeof(__u64);
+	struct thread *thread;
+	u16 max_size = event->header.size;
+
+	if ((sample_type & (PERF_SAMPLE_IP | PERF_SAMPLE_CALLCHAIN)) == 0)
+		return ibo->tool.delegate->sample(ibo->tool.delegate, event,
+						  sample, machine);
+
+	if (symbol_conf.guest_code && !machine__is_host(machine))
+		thread = machine__findnew_guest_code(machine, sample->pid);
+	else
+		thread = machine__findnew_thread(machine, sample->pid,
+						 sample->tid);
+
+	if (sample_type & PERF_SAMPLE_IP)
+		max_size += sizeof(struct perf_build_id) + sizeof(u64) -
+			    sizeof(u64);
+
+	if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+		max_size +=
+			sample->callchain->nr * (sizeof(struct perf_build_id) +
+						 sizeof(u64) - sizeof(u64));
+	}
+
+	if (max_size > PERF_SAMPLE_MAX_SIZE) {
+		pr_debug("Insufficient space to copy event\n");
+		thread__put(thread);
+		return -E2BIG;
+	}
+
+	ev = (union perf_event *)ibo->event_copy;
+	ev->sample.header =
+		(struct perf_event_header){ .type = event->header.type,
+					    .misc = event->header.misc,
+					    .size = max_size };
+
+	in_array = &event->sample.array[0];
+	out_array = &ev->sample.array[0];
+
+	if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_IP) {
+		i++;
+		if (evsel && thread)
+			mark_dso_hit(ibo->tool.delegate, sample, machine,
+				     thread, sample->ip, sample->cpumode);
+	}
+	if (sample_type & PERF_SAMPLE_TID) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_TIME) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_ADDR) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_ID) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_STREAM_ID) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_CPU) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_PERIOD) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_READ) {
+		if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
+			if (i > max_i)
+				goto err;
+			out_array[j++] = in_array[i++];
+			if (evsel->core.attr.read_format &
+			    PERF_FORMAT_TOTAL_TIME_ENABLED)
+				if (i > max_i)
+					goto err;
+			out_array[j++] = in_array[i++];
+			if (evsel->core.attr.read_format &
+			    PERF_FORMAT_TOTAL_TIME_RUNNING)
+				if (i > max_i)
+					goto err;
+			out_array[j++] = in_array[i++];
+			if (evsel->core.attr.read_format & PERF_FORMAT_ID)
+				if (i > max_i)
+					goto err;
+			out_array[j++] = in_array[i++];
+			if (evsel->core.attr.read_format & PERF_FORMAT_LOST)
+				if (i > max_i)
+					goto err;
+			out_array[j++] = in_array[i++];
+		} else {
+			u64 nr;
+
+			if (i > max_i)
+				goto err;
+			nr = out_array[j++] = in_array[i++];
+			if (evsel->core.attr.read_format &
+			    PERF_FORMAT_TOTAL_TIME_ENABLED)
+				if (i > max_i)
+					goto err;
+			out_array[j++] = in_array[i++];
+			if (evsel->core.attr.read_format &
+			    PERF_FORMAT_TOTAL_TIME_RUNNING)
+				if (i > max_i)
+					goto err;
+			out_array[j++] = in_array[i++];
+			for (u64 cntr = 0; cntr < nr; cntr++) {
+				if (i > max_i)
+					goto err;
+				out_array[j++] = in_array[i++];
+				if (evsel->core.attr.read_format &
+				    PERF_FORMAT_ID)
+					if (i > max_i)
+						goto err;
+				out_array[j++] = in_array[i++];
+				if (evsel->core.attr.read_format &
+				    PERF_FORMAT_LOST)
+					if (i > max_i)
+						goto err;
+				out_array[j++] = in_array[i++];
+			}
+		}
+	}
+	if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+		i++;
+		if (evsel && thread) {
+			for (u64 x = 0; x < sample->callchain->nr; x++)
+				mark_dso_hit(ibo->tool.delegate, sample,
+					     machine, thread,
+					     sample->callchain->ips[x],
+					     sample->cpumode);
+		}
+		i += sample->callchain->nr;
+	}
+	if (sample_type & PERF_SAMPLE_RAW) {
+		size_t bytes = sizeof(u32) + sample->raw_size;
+
+		if ((i + (bytes / sizeof(u64))) > max_i)
+			goto err;
+		memcpy(&out_array[j], &in_array[i], bytes);
+		i += bytes / sizeof(u64);
+		j += bytes / sizeof(u64);
+	}
+	if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+		if (sample_type & PERF_SAMPLE_BRANCH_HW_INDEX) {
+			if (i > max_i)
+				goto err;
+			out_array[j++] = in_array[i++];
+		}
+		if (i + (sample->branch_stack->nr * 3) > max_i)
+			goto err;
+		memcpy(&out_array[j], &in_array[i],
+		       sample->branch_stack->nr * 3 * sizeof(u64));
+		i += sample->branch_stack->nr * 3;
+		j += sample->branch_stack->nr * 3;
+		if (sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {
+			if (i + sample->branch_stack->nr > max_i)
+				goto err;
+			memcpy(&out_array[j], &in_array[i],
+			       sample->branch_stack->nr * sizeof(u64));
+			i += sample->branch_stack->nr;
+			j += sample->branch_stack->nr;
+		}
+	}
+	if (sample_type & PERF_SAMPLE_REGS_USER) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+		if (sample->user_regs->abi != PERF_SAMPLE_REGS_ABI_NONE) {
+			u64 nr = hweight64(evsel->core.attr.sample_regs_user);
+
+			if (i + nr > max_i)
+				goto err;
+			memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+			i += nr;
+			j += nr;
+		}
+	}
+	if (sample_type & PERF_SAMPLE_STACK_USER) {
+		u64 size;
+
+		if (i > max_i)
+			goto err;
+		size = out_array[j++] = in_array[i++];
+		if (size > 0) {
+			memcpy(&out_array[j], &in_array[i], size);
+			i += size / sizeof(u64);
+			j += size / sizeof(u64);
+			if (i > max_i)
+				goto err;
+			out_array[j++] = in_array[i++];
+		}
+	}
+	if (sample_type & PERF_SAMPLE_WEIGHT_TYPE) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_DATA_SRC) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_TRANSACTION) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_REGS_INTR) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+		if (sample->intr_regs->abi != PERF_SAMPLE_REGS_ABI_NONE) {
+			u64 nr = hweight64(evsel->core.attr.sample_regs_intr);
+
+			if (i + nr > max_i)
+				goto err;
+			memcpy(&out_array[j], &in_array[i], nr * sizeof(u64));
+			i += nr;
+			j += nr;
+		}
+	}
+	if (sample_type & PERF_SAMPLE_PHYS_ADDR) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_CGROUP) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_DATA_PAGE_SIZE) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_CODE_PAGE_SIZE) {
+		if (i > max_i)
+			goto err;
+		out_array[j++] = in_array[i++];
+	}
+	if (sample_type & PERF_SAMPLE_AUX) {
+		u64 size;
+
+		if (i > max_i)
+			goto err;
+		size = out_array[j++] = in_array[i++];
+		if (i + (size / sizeof(u64)) > max_i)
+			goto err;
+		memcpy(&out_array[j], &in_array[i], size);
+		i += size / sizeof(u64);
+		j += size / sizeof(u64);
+	}
+
+	if (sample_type & PERF_SAMPLE_IP) {
+		perf_event__inject_sample_buildid_array(
+			thread, sample->ip, sample->cpumode, &out_array[j]);
+		j += 4;
+	}
+	if (sample_type & PERF_SAMPLE_CALLCHAIN) {
+		out_array[j++] = sample->callchain->nr;
+		for (u64 x = 0; x < sample->callchain->nr; x++) {
+			perf_event__inject_sample_buildid_array(
+				thread, sample->callchain->ips[x],
+				sample->cpumode, &out_array[j]);
+			j += 4;
+		}
+	}
+
+	thread__put(thread);
+
+	return ibo->tool.delegate->sample(ibo->tool.delegate, ev, sample,
+					  machine);
+
+err:
+	thread__put(thread);
+	return -EFAULT;
+}
+
+static int
+inject_bid_offset_tool__mmap(const struct perf_tool *tool __maybe_unused,
+			     union perf_event *event,
+			     struct perf_sample *sample,
+			     struct machine *machine)
+{
+	perf_event__process_mmap(tool, event, sample, machine);
+	return 0; // Drop mmap events from output stream
+}
+static int
+inject_bid_offset_tool__mmap2(const struct perf_tool *tool __maybe_unused,
+			      union perf_event *event,
+			      struct perf_sample *sample,
+			      struct machine *machine)
+{
+	perf_event__process_mmap2(tool, event, sample, machine);
+	return 0; // Drop mmap2 events from output stream
+}
+
+struct perf_tool *inject_bid_offset_tool__new(struct perf_tool *delegate)
+{
+	struct inject_bid_offset_tool *ibo = zalloc(sizeof(*ibo));
+
+	if (!ibo)
+		return NULL;
+
+	delegate_tool__init(&ibo->tool, delegate);
+	ibo->tool.tool.sample = inject_bid_offset_tool__sample;
+	ibo->tool.tool.mmap = inject_bid_offset_tool__mmap;
+	ibo->tool.tool.mmap2 = inject_bid_offset_tool__mmap2;
+
+	return &ibo->tool.tool;
+}
+
+void inject_bid_offset_tool__delete(struct perf_tool *tool)
+{
+	struct delegate_tool *dt;
+
+	if (!tool)
+		return;
+	dt = container_of(tool, struct delegate_tool, tool);
+	free(container_of(dt, struct inject_bid_offset_tool, tool));
+}
diff --git a/tools/perf/util/inject_bid_offset.h b/tools/perf/util/inject_bid_offset.h
new file mode 100644
index 000000000000..e4cfea255f6b
--- /dev/null
+++ b/tools/perf/util/inject_bid_offset.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_INJECT_BID_OFFSET_H
+#define __PERF_INJECT_BID_OFFSET_H
+
+#include <linux/perf_event.h>
+
+struct perf_tool;
+struct evlist;
+
+int perf_event__rewrite_attr_for_build_id_offset(struct perf_event_attr *attr);
+int perf_event__rewrite_attr_for_sample_ip(struct perf_event_attr *attr);
+union perf_event;
+struct perf_sample;
+struct machine;
+
+int perf_event__repipe(const struct perf_tool *tool, union perf_event *event,
+			struct perf_sample *sample, struct machine *machine);
+struct perf_tool *inject_bid_offset_tool__new(struct perf_tool *delegate);
+void inject_bid_offset_tool__delete(struct perf_tool *tool);
+
+#endif /* __PERF_INJECT_BID_OFFSET_H */
-- 
2.55.0.679.g6767b8d81c-goog


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

* [RFC PATCH v1 7/8] perf record: Add --buildid-offset option
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
                   ` (5 preceding siblings ...)
  2026-08-07  7:18 ` [RFC PATCH v1 6/8] perf inject: Extend perf inject to support bid_offset conversion Ian Rogers
@ 2026-08-07  7:18 ` Ian Rogers
  2026-08-07  9:47   ` sashiko-bot
  2026-08-07  7:18 ` [RFC PATCH v1 8/8] perf tests: Add build_id_offset test coverage Ian Rogers
  2026-08-07 11:18 ` [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Peter Zijlstra
  8 siblings, 1 reply; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

Add the --buildid-offset flag to perf record to natively request
PERF_SAMPLE_BUILD_ID_OFFSET and PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET
from the kernel. This configures the evsel attributes by replacing
the standard IP and CALLCHAIN bits with the build ID offset equivalents.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/builtin-record.c |  7 +++++++
 tools/perf/util/evsel.c     | 11 +++++++++++
 tools/perf/util/record.h    |  1 +
 3 files changed, 19 insertions(+)

diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index f58d7e3c7879..9ff9cfc06278 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -3608,6 +3608,8 @@ static struct option __record_options[] = {
 		   "file", "vmlinux pathname"),
 	OPT_BOOLEAN(0, "buildid-all", &record.buildid_all,
 		    "Record build-id of all DSOs regardless of hits"),
+	OPT_BOOLEAN(0, "buildid-offset", &record.opts.buildid_offset,
+		    "Record build id offset instead of IP"),
 	OPT_BOOLEAN_SET(0, "buildid-mmap", &record.buildid_mmap, &record.buildid_mmap_set,
 			"Record build-id in mmap events and skip build-id processing."),
 	OPT_BOOLEAN(0, "timestamp-filename", &record.timestamp_filename,
@@ -4166,6 +4168,11 @@ int cmd_record(int argc, const char **argv)
 		rec->no_buildid_cache = true;
 	}
 
+	if (rec->opts.buildid_offset) {
+		/* Do not drop mmap events here so we can generate build ids later */
+		/* rec->opts.synth &= ~PERF_SYNTH_MMAP; */
+	}
+
 	if (rec->opts.record_cgroup && !perf_can_record_cgroup()) {
 		pr_err("Kernel has no cgroup sampling support.\n");
 		err = -EINVAL;
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index d9a07f5825e8..587f0fa41cce 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -1744,6 +1744,17 @@ void evsel__config(struct evsel *evsel, const struct record_opts *opts,
 	if (opts->sample_transaction)
 		evsel__set_sample_bit(evsel, TRANSACTION);
 
+	if (opts->buildid_offset) {
+		if (evsel->core.attr.sample_type & PERF_SAMPLE_IP) {
+			evsel__reset_sample_bit(evsel, IP);
+			evsel__set_sample_bit(evsel, BUILD_ID_OFFSET);
+		}
+		if (evsel->core.attr.sample_type & PERF_SAMPLE_CALLCHAIN) {
+			evsel__reset_sample_bit(evsel, CALLCHAIN);
+			evsel__set_sample_bit(evsel, CALLCHAIN_BUILD_ID_OFFSET);
+		}
+	}
+
 	if (opts->running_time) {
 		evsel->core.attr.read_format |=
 			PERF_FORMAT_TOTAL_TIME_ENABLED |
diff --git a/tools/perf/util/record.h b/tools/perf/util/record.h
index 93627c9a7338..a8cec7db150e 100644
--- a/tools/perf/util/record.h
+++ b/tools/perf/util/record.h
@@ -55,6 +55,7 @@ struct record_opts {
 	bool	      kcore;
 	bool	      text_poke;
 	bool	      build_id;
+	bool	      buildid_offset;
 	unsigned int  freq;
 	unsigned int  mmap_pages;
 	unsigned int  auxtrace_mmap_pages;
-- 
2.55.0.679.g6767b8d81c-goog


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

* [RFC PATCH v1 8/8] perf tests: Add build_id_offset test coverage
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
                   ` (6 preceding siblings ...)
  2026-08-07  7:18 ` [RFC PATCH v1 7/8] perf record: Add --buildid-offset option Ian Rogers
@ 2026-08-07  7:18 ` Ian Rogers
  2026-08-07  9:58   ` sashiko-bot
  2026-08-07 11:18 ` [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Peter Zijlstra
  8 siblings, 1 reply; 18+ messages in thread
From: Ian Rogers @ 2026-08-07  7:18 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
	Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Mark Rutland, Alexander Shishkin, Ian Rogers,
	Adrian Hunter, James Clark, Suzuki K Poulose, Mike Leach, Leo Yan,
	John Garry, Will Deacon, Thomas Gleixner, Dapeng Mi,
	Ravi Bangoria, Swapnil Sapkal, Thomas Falcon, Thomas Richter,
	Dmitrii Dolgov, Eric Biggers, Zecheng Li, Gabriel Marin,
	Tengda Wu, Derek Foreman, Tanushree Shah, Ankur Arora,
	Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever, Shimin Guo,
	Alessio Podda, linux-kernel, bpf, linux-perf-users, coresight,
	linux-arm-kernel

Test the kernel interface using sys_perf_event_open, test perf inject
--sample-buildids with pipe mode and callchains, and test perf record
--buildid-offset across multiple sampling modes.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/Build                      |   1 +
 tools/perf/tests/bid-offset.c               |  60 +++++++
 tools/perf/tests/builtin-test.c             |   1 +
 tools/perf/tests/mmap-thread-lookup.c       |  36 +++++
 tools/perf/tests/sample-parsing.c           |  37 ++++-
 tools/perf/tests/shell/inject_bid_offset.sh | 170 ++++++++++++++++++++
 tools/perf/tests/shell/record.sh            |  69 ++++++++
 tools/perf/tests/tests.h                    |   1 +
 8 files changed, 374 insertions(+), 1 deletion(-)
 create mode 100644 tools/perf/tests/bid-offset.c
 create mode 100755 tools/perf/tests/shell/inject_bid_offset.sh

diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build
index 66944a4f4968..f7f8c4deba30 100644
--- a/tools/perf/tests/Build
+++ b/tools/perf/tests/Build
@@ -64,6 +64,7 @@ perf-test-y += expand-cgroup.o
 perf-test-y += perf-time-to-tsc.o
 perf-test-y += dlfilter-test.o
 perf-test-y += sigtrap.o
+perf-test-y += bid-offset.o
 perf-test-y += event_groups.o
 perf-test-y += symbols.o
 perf-test-y += util.o
diff --git a/tools/perf/tests/bid-offset.c b/tools/perf/tests/bid-offset.c
new file mode 100644
index 000000000000..f7d85491832f
--- /dev/null
+++ b/tools/perf/tests/bid-offset.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "tests.h"
+#include "debug.h"
+#include "evlist.h"
+#include "evsel.h"
+#include <linux/hw_breakpoint.h>
+#include <linux/perf_event.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <errno.h>
+#include "../perf-sys.h"
+
+static int test__bid_offset(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
+{
+	struct perf_event_attr attr = {
+		.type		= PERF_TYPE_SOFTWARE,
+		.config		= PERF_COUNT_SW_DUMMY,
+		.size		= sizeof(attr),
+		.sample_type	= PERF_SAMPLE_BUILD_ID_OFFSET,
+		.exclude_kernel = 1,
+		.exclude_hv     = 1,
+	};
+	int fd;
+
+	fd = sys_perf_event_open(&attr, /*pid=*/0, /*cpu=*/-1, /*group_fd=*/-1, /*flags=*/0);
+	if (fd < 0) {
+		pr_debug("sys_perf_event_open failed with %d\n", errno);
+		if (errno == EINVAL) {
+			pr_debug("Kernel does not support PERF_SAMPLE_BUILD_ID_OFFSET\n");
+			return TEST_SKIP;
+		}
+		return TEST_FAIL;
+	}
+	close(fd);
+
+	attr.sample_type = PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET;
+	fd = sys_perf_event_open(&attr, /*pid=*/0, /*cpu=*/-1, /*group_fd=*/-1, /*flags=*/0);
+	if (fd < 0) {
+		pr_debug("sys_perf_event_open with callchain failed with %d\n", errno);
+		if (errno == EINVAL) {
+			pr_debug("Kernel does not support PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET\n");
+			return TEST_SKIP;
+		}
+		return TEST_FAIL;
+	}
+	close(fd);
+
+	return TEST_OK;
+}
+
+static struct test_case bid_offset_tests[] = {
+	TEST_CASE_REASON("Test PERF_SAMPLE_BUILD_ID_OFFSET", bid_offset,
+			 "not supported by kernel"),
+	{ .name = NULL, }
+};
+
+struct test_suite suite__bid_offset = {
+	.desc = "Test PERF_SAMPLE_BUILD_ID_OFFSET",
+	.test_cases = bid_offset_tests,
+};
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index 41dcfbf874f0..afc9c7efe009 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -148,6 +148,7 @@ static struct test_suite *generic_tests[] = {
 	&suite__perf_time_to_tsc,
 	&suite__dlfilter,
 	&suite__sigtrap,
+	&suite__bid_offset,
 	&suite__event_groups,
 	&suite__symbols,
 	&suite__util,
diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c
index dca8bbfe780a..65c72ed896ac 100644
--- a/tools/perf/tests/mmap-thread-lookup.c
+++ b/tools/perf/tests/mmap-thread-lookup.c
@@ -17,6 +17,8 @@
 #include "symbol.h"
 #include "util/synthetic-events.h"
 #include "thread.h"
+#include "dso.h"
+#include "build-id.h"
 #include <internal/lib.h> // page_size
 
 #define THREADS 4
@@ -211,6 +213,40 @@ static int mmap_events(synth_cb synth)
 
 		pr_debug("map %p, addr %" PRIx64 "\n", al.map, map__start(al.map));
 
+		/* Verify lookup by build ID and offset */
+		{
+			struct dso *dso = dso__get(map__dso(al.map));
+
+			if (dso) {
+				const struct build_id *bid = dso__bid(dso);
+
+				if (bid && bid->size > 0) {
+					struct addr_location al2;
+					struct perf_build_id pbid = { .size = bid->size };
+					u64 offset = map__dso_map_ip(al.map,
+								     (unsigned long)(td->map + 1));
+
+					memcpy(pbid.data, bid->data, bid->size);
+					addr_location__init(&al2);
+					thread__find_map(thread, &(struct perf_sample){
+								.cpumode = PERF_RECORD_MISC_USER,
+								.ip = 0,
+								.bid = {
+									.bid = &pbid,
+									.offset = offset,
+								},
+							 }, &al2);
+
+					if (al2.map != al.map) {
+						pr_debug("failed, couldn't find map by build id and offset\n");
+						err = -1;
+					}
+					addr_location__exit(&al2);
+				}
+				dso__put(dso);
+			}
+		}
+
 		addr_location__exit(&al);
 		if (err)
 			break;
diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c
index 20cab91ceaeb..b49b6dcf10cd 100644
--- a/tools/perf/tests/sample-parsing.c
+++ b/tools/perf/tests/sample-parsing.c
@@ -229,6 +229,36 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format)
 	const u32 raw_data[] = {0x12345678, 0x0a0b0c0d, 0x11020304, 0x05060708, 0 };
 	const u64 data[] = {0x2211443366558877ULL, 0, 0xaabbccddeeff4321ULL};
 	const u64 aux_data[] = {0xa55a, 0, 0xeeddee, 0x0282028202820282};
+	const struct perf_build_id bid = {
+		.size = 20,
+		.data = {
+			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+		}
+	};
+	const struct perf_build_id callchain_bids[] = {
+		{
+			.size = 20,
+			.data = {
+				'9', '8', '7', '6', '5', '4', '3', '2', '1', '0',
+				'9', '8', '7', '6', '5', '4', '3', '2', '1', '0',
+			}
+		},
+		{
+			.size = 20,
+			.data = {
+				'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd',
+				'e', 'f', 'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b',
+			}
+		},
+		{
+			.size = 20,
+			.data = {
+				'f', 'e', 'd', 'c', 'b', 'a', 'f', 'e', 'd', 'c',
+				'b', 'a', 'f', 'e', 'd', 'c', 'b', 'a', 'f', 'e',
+			}
+		},
+	};
 	struct regs_dump user_regs = {
 		.abi	= PERF_SAMPLE_REGS_ABI_64,
 		.mask	= sample_regs,
@@ -277,6 +307,11 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format)
 			.size	= sizeof(aux_data),
 			.data	= (void *)aux_data,
 		},
+		.bid		= {
+			.bid = (struct perf_build_id *)&bid,
+			.offset = 0,
+		},
+		.callchain_bids = (struct perf_build_id *)callchain_bids,
 	};
 	struct sample_read_value values[] = {{1, 5, 0}, {9, 3, 0}, {2, 7, 0}, {6, 4, 1},};
 	struct perf_sample sample_out, sample_out_endian;
@@ -409,7 +444,7 @@ static int test__sample_parsing(struct test_suite *test __maybe_unused, int subt
 	 * were added.  Please actually update the test rather than just change
 	 * the condition below.
 	 */
-	if (PERF_SAMPLE_MAX > PERF_SAMPLE_WEIGHT_STRUCT << 1) {
+	if (PERF_SAMPLE_WEIGHT_STRUCT << 3 < PERF_SAMPLE_MAX) {
 		pr_debug("sample format has changed, some new PERF_SAMPLE_ bit was introduced - test needs updating\n");
 		return -1;
 	}
diff --git a/tools/perf/tests/shell/inject_bid_offset.sh b/tools/perf/tests/shell/inject_bid_offset.sh
new file mode 100755
index 000000000000..eac0a19f24e1
--- /dev/null
+++ b/tools/perf/tests/shell/inject_bid_offset.sh
@@ -0,0 +1,170 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --sample-buildids test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+temp_dir=$(mktemp -d /tmp/perf-test-bid-offset.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+
+cleanup() {
+	local exit_code=${1:-$?}
+	trap - EXIT TERM INT
+	if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+		echo "Test failed! Preserving temp directory: ${temp_dir}"
+		exit 1
+	fi
+	if [[ "${temp_dir}" =~ ^/tmp/perf-test-bid-offset\. ]]; then
+		rm -rf "${temp_dir}"
+	fi
+	exit 0
+}
+
+trap_cleanup() {
+	local exit_code=$?
+	echo "Unexpected signal in ${FUNCNAME[1]}"
+	cleanup ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+compare_script() {
+	local orig=$1
+	local bid=$2
+	local msg=$3
+
+	perf script -F -ip -i "${orig}" > "${temp_dir}/script_orig.txt"
+	perf script -F -ip -i "${bid}" > "${temp_dir}/script_bid.txt"
+
+	if ! diff -q "${temp_dir}/script_orig.txt" "${temp_dir}/script_bid.txt"; then
+		echo "${msg} [Failed - perf script output differs]"
+		err=1
+	else
+		echo "${msg} [Success]"
+	fi
+}
+
+compare_report() {
+	local orig=$1
+	local bid=$2
+	local msg=$3
+
+	perf report -i "${orig}" --stdio | grep -v '^#' > "${temp_dir}/report_orig.txt"
+	perf report -i "${bid}" --stdio | grep -v '^#' > "${temp_dir}/report_bid.txt"
+
+	if ! diff -q "${temp_dir}/report_orig.txt" "${temp_dir}/report_bid.txt"; then
+		echo "${msg} [Failed - perf report output differs]"
+		err=1
+	else
+		echo "${msg} [Success]"
+	fi
+}
+
+test_basic() {
+	echo "Test basic build id offset injection"
+	local data="${temp_dir}/perf.data.basic"
+	local data2="${temp_dir}/perf.data2.basic"
+
+	perf record -e task-clock:u -o "${data}" ${prog} >/dev/null 2>&1
+	perf inject --sample-buildids -i "${data}" -o "${data2}" >/dev/null 2>&1
+
+	compare_script "${data}" "${data2}" "Basic injection"
+}
+
+test_callchain() {
+	echo "Test Callchain build id offset injection"
+	local data="${temp_dir}/perf.data.callchain"
+	local data2="${temp_dir}/perf.data2.callchain"
+
+	perf record -g -e task-clock:u -o "${data}" ${prog} >/dev/null 2>&1
+	perf inject --sample-buildids -i "${data}" -o "${data2}" >/dev/null 2>&1
+
+	compare_script "${data}" "${data2}" "Callchain injection"
+}
+
+test_pipe() {
+	echo "Test pipe mode build id offset injection"
+	local data="${temp_dir}/perf.data.pipe"
+	local data2="${temp_dir}/perf.data2.pipe"
+
+	perf record -e task-clock:u -o - ${prog} 2>/dev/null | tee "${data}" | \
+		perf inject --sample-buildids -o "${data2}" >/dev/null 2>&1
+
+	compare_script "${data}" "${data2}" "Pipe mode injection"
+}
+
+test_report() {
+	echo "Test perf report consistency"
+	local data="${temp_dir}/perf.data.report"
+	local data2="${temp_dir}/perf.data2.report"
+
+	perf record -e task-clock:u -o "${data}" ${prog} >/dev/null 2>&1
+	perf inject --sample-buildids -i "${data}" -o "${data2}" >/dev/null 2>&1
+
+	compare_report "${data}" "${data2}" "Report consistency"
+}
+
+test_pipe_report() {
+	echo "Test pipe mode perf report consistency"
+	local data="${temp_dir}/perf.data.pipe_report"
+	local data2="${temp_dir}/perf.data2.pipe_report"
+
+	perf record -e task-clock:u -o - ${prog} 2>/dev/null | tee "${data}" | \
+		perf inject --sample-buildids -o "${data2}" >/dev/null 2>&1
+
+	compare_report "${data}" "${data2}" "Pipe Report consistency"
+}
+
+test_kernel() {
+	echo "Test kernel build id offset injection"
+	local data="${temp_dir}/perf.data.kernel"
+	local data2="${temp_dir}/perf.data2.kernel"
+
+	# Not all systems allow kernel profiling, so we check if it succeeds first
+	if ! perf record -e task-clock:k -o "${data}" ${prog} >/dev/null 2>&1; then
+		echo "Kernel injection [Skipped - permissions or support missing]"
+		return
+	fi
+	perf inject --sample-buildids -i "${data}" -o "${data2}" >/dev/null 2>&1
+
+	compare_script "${data}" "${data2}" "Kernel injection"
+}
+
+test_record_buildid_offset() {
+	echo "Test perf record --buildid-offset consistency"
+	local data="${temp_dir}/perf.data.record_orig"
+	local data2="${temp_dir}/perf.data2.record_bid"
+
+	perf record -e task-clock:u -o "${data}" ${prog} >/dev/null 2>&1
+	# We must have the binaries in .build-id cache, which the first record does natively.
+	# Now record with --buildid-offset.
+	if ! perf record --buildid-offset -e task-clock:u -o "${data2}" \
+	    ${prog} >/dev/null 2>&1; then
+		echo "Record --buildid-offset consistency [Skipped - not supported by kernel]"
+		return
+	fi
+
+	# Compare symbolization
+	compare_report "${data}" "${data2}" "Record --buildid-offset consistency"
+}
+
+test_basic
+test_callchain
+test_pipe
+test_report
+test_pipe_report
+test_kernel
+test_record_buildid_offset
+
+cleanup ${err}
diff --git a/tools/perf/tests/shell/record.sh b/tools/perf/tests/shell/record.sh
index 625240809fce..9346741b19c9 100755
--- a/tools/perf/tests/shell/record.sh
+++ b/tools/perf/tests/shell/record.sh
@@ -484,6 +484,74 @@ test_ratio_to_prev() {
   echo "Basic ratio-to-prev record test [Success]"
 }
 
+check_buildid_offset() {
+  perf report -D -i "${perfdata}" -q 2>/dev/null | grep -q "BUILD_ID_OFFSET"
+}
+
+test_buildid_offset() {
+  echo "buildid-offset test"
+  local ret=0
+  perf_record_with_retry "${perfdata}" "check_buildid_offset" "perf test -w thloop" \
+    --buildid-offset || ret=$?
+  if [ $ret -eq 2 ]; then
+    echo "buildid-offset test [Skipped not supported]"
+    return
+  elif [ $ret -eq 1 ]; then
+    echo "buildid-offset test [Failed missing output]"
+    err=1
+    return
+  fi
+
+  # Expand test coverage
+  perf_record_with_retry "${perfdata}" "check_per_thread" "perf test -w thloop" \
+    --buildid-offset --per-thread || ret=$?
+  if [ $ret -ne 0 ]; then
+    echo "buildid-offset test [Failed per-thread]"
+    err=1
+    return
+  fi
+
+  perf_record_with_retry "${perfdata}" "check_system_wide" "perf test -w thloop" \
+    --buildid-offset -aB --synth=no || ret=$?
+  if [ $ret -ne 0 ]; then
+    echo "buildid-offset test [Failed system-wide]"
+    err=1
+    return
+  fi
+
+  perf_record_with_retry "${perfdata}" "check_workload" "perf test -w thloop" \
+    --buildid-offset || ret=$?
+  if [ $ret -ne 0 ]; then
+    echo "buildid-offset test [Failed workload]"
+    err=1
+    return
+  fi
+
+  case $(uname -m)
+  in s390x)
+       cmd_flags="--call-graph dwarf -e cpu-clock";;
+     *)
+       cmd_flags="-g";;
+  esac
+
+  if ! perf record --buildid-offset -o "${perfdata}" $cmd_flags \
+    perf test -w brstack > /dev/null 2>&1
+  then
+    echo "buildid-offset test [Failed callgraph record]"
+    err=1
+    return
+  fi
+
+  if ! perf report -i "${perfdata}" 2>&1 | grep -q "${testsym2}"
+  then
+    echo "buildid-offset test [Failed callgraph missing symbol]"
+    err=1
+    return
+  fi
+
+  echo "buildid-offset test [Success]"
+}
+
 # raise the limit of file descriptors to minimum
 if [[ $default_fd_limit -lt $min_fd_limit ]]; then
        ulimit -Sn $min_fd_limit
@@ -502,6 +570,7 @@ test_precise_max
 test_callgraph
 test_acr_sampling
 test_ratio_to_prev
+test_buildid_offset
 
 # restore the default value
 ulimit -Sn $default_fd_limit
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index cee9e6b62dcc..b718ae148245 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -176,6 +176,7 @@ DECLARE_SUITE(expand_cgroup_events);
 DECLARE_SUITE(perf_time_to_tsc);
 DECLARE_SUITE(dlfilter);
 DECLARE_SUITE(sigtrap);
+DECLARE_SUITE(bid_offset);
 DECLARE_SUITE(event_groups);
 DECLARE_SUITE(symbols);
 DECLARE_SUITE(util);
-- 
2.55.0.679.g6767b8d81c-goog


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

* Re: [RFC PATCH v1 1/8] perf event: Factor build_id out into its own top-level struct
  2026-08-07  7:18 ` [RFC PATCH v1 1/8] perf event: Factor build_id out into its own top-level struct Ian Rogers
@ 2026-08-07  7:33   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-07  7:33 UTC (permalink / raw)
  To: Ian Rogers; +Cc: bpf, linux-perf-users

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Unconditional zeroing of build ID fields clobbers valid fallback identifiers (`maj`, `min`, `ino`), breaking symbol resolution.
- [High] Typo in bounds check writes to the wrong union member, corrupting `pgoff` and failing to clamp the build ID size.
- [Low] Modifying a tooling UAPI header without syncing the main kernel UAPI header breaks build checks.
--

--- Patch [1]: [RFC PATCH v1 1/8] perf event: Factor build_id out into its own top-level struct ---
commit 52b0cf61920b265e87e7e98a009860673a5711f3
Author: Ian Rogers <irogers@google.com>

perf event: Factor build_id out into its own top-level struct

This commit factors out the inline build_id struct into its own type,
struct perf_build_id, and updates various contexts to use this new
consistent type.

> diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h
> index fd10aa8d697f2..2479a38883d00 100644
> --- a/tools/include/uapi/linux/perf_event.h
> +++ b/tools/include/uapi/linux/perf_event.h
> @@ -1058,6 +1058,18 @@ enum perf_event_type {
>  	 */
>  	PERF_RECORD_SAMPLE			= 9,
>  

[Severity: Low]
Does modifying this tooling UAPI header without also updating the main kernel
UAPI header in include/uapi/linux/perf_event.h break the check-headers.sh
build scripts?

> +	/*
> +	 * Build IDs may be present in a number of events. They have a
> +	 * consistent encoding of:
> +	 *
> +	 * struct build_id {
> +	 *	u8		size;
> +	 *	u8		__reserved_1;
> +	 *	u16		__reserved_2;
> +	 *	u8		data[20];
> +	 * };
> +	 */

[ ... ]

> diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic-events.c
> index 75a32ae8ef623..f7dedfb6bab87 100644
> --- a/tools/perf/util/synthetic-events.c
> +++ b/tools/perf/util/synthetic-events.c
[ ... ]
> @@ -478,16 +478,20 @@ static void perf_record_mmap2__read_build_id(struct perf_record_mmap2 *event,
>  	nsinfo__put(nsi);
>  
>  out:
> +	event->build_id.__reserved_1 = 0;
> +	event->build_id.__reserved_2 = 0;

[Severity: High]
Since struct perf_build_id is part of an anonymous union that overlaps with
the maj, min, and ino fields in struct perf_record_mmap2, do these
unconditional assignments corrupt the previously parsed fallback metadata
for binaries lacking a build ID?

>  	if (rc == 0) {
> -		memcpy(event->build_id, bid.data, sizeof(bid.data));
> -		event->build_id_size = (u8) bid.size;
> +		memcpy(event->build_id.data, bid.data, sizeof(bid.data));
> +		event->build_id.size = (u8) bid.size;
>  		event->header.misc |= PERF_RECORD_MISC_MMAP_BUILD_ID;
> -		event->__reserved_1 = 0;
> -		event->__reserved_2 = 0;
>  
>  		if (dso && !dso__has_build_id(dso))
>  			dso__set_build_id(dso, &bid);
>  	} else {
> +		memset(&event->build_id, 0, sizeof(event->build_id));

[Severity: High]
Can this memset destroy the valid maj, min, and ino identifiers when
perf_record_mmap2__read_build_id() fails to read a build ID?

> +		event->build_id.size = 0;
> +		event->header.misc &= ~PERF_RECORD_MISC_MMAP_BUILD_ID;
> +
>  		if (event->filename[0] == '/') {
>  			pr_debug2("Failed to read build ID for %s\n",
>  				  event->filename);

[ ... ]

> @@ -2486,10 +2492,10 @@ int perf_event__synthesize_mmap2_build_id(const struct perf_tool *tool,
>  	ev.mmap2.len = len;
>  	ev.mmap2.pgoff = pgoff;
>  
> -	ev.mmap2.build_id_size = bid->size;
> -	if (ev.mmap2.build_id_size > sizeof(ev.mmap2.build_id))
> -		ev.mmap2.build_id_size = sizeof(ev.mmap2.build_id);
> -	memcpy(ev.mmap2.build_id, bid->data, ev.mmap2.build_id_size);
> +	ev.mmap2.build_id.size = bid->size;
> +	if (ev.mmap2.build_id.size > sizeof(ev.mmap2.build_id.data))
> +		ev.build_id.size = sizeof(ev.mmap2.build_id.data);

[Severity: High]
Is it possible this writes to ev.build_id.size instead of
ev.mmap2.build_id.size, accidentally modifying the pgoff field of
struct perf_record_mmap2 while leaving ev.mmap2.build_id.size unclamped
for the memcpy below?

> +	memcpy(ev.mmap2.build_id.data, bid->data, ev.mmap2.build_id.size);
>  
>  	ev.mmap2.prot = prot;
>  	ev.mmap2.flags = flags;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807071818.718751-1-irogers@google.com?part=1

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

* Re: [RFC PATCH v1 2/8] perf/core: Add BUILD_ID_OFFSET to UAPI
  2026-08-07  7:18 ` [RFC PATCH v1 2/8] perf/core: Add BUILD_ID_OFFSET to UAPI Ian Rogers
@ 2026-08-07  7:42   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-07  7:42 UTC (permalink / raw)
  To: Ian Rogers; +Cc: bpf, linux-perf-users

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The commit introduces `PERF_SAMPLE_BUILD_ID_OFFSET` and `PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET` to the UAPI and bumps `PERF_SAMPLE_MAX`, but fails to implement the corresponding sample generation logic in the core kernel.
- [Medium] The UAPI header update is applied inconsistently between `include/` and `tools/include/`, causing divergence and leaving the canonical kernel header undocumented.
- [Medium] The ABI documentation comment references `struct build_id`, an undeclared structure in the canonical UAPI headers, resulting in an opaque and confusing binary layout.
--

--- Patch [2]: [RFC PATCH v1 2/8] perf/core: Add BUILD_ID_OFFSET to UAPI ---
commit 16454ab98fa038eb527938712933e7e488db3d27
Author: Ian Rogers <irogers@google.com>

perf/core: Add BUILD_ID_OFFSET to UAPI
    
This commit introduces PERF_SAMPLE_BUILD_ID_OFFSET and
PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET to the UAPI and bumps the internal
validation limit.

> diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h
> index fd10aa8d697f2..f0712c1471d3d 100644
> --- a/include/uapi/linux/perf_event.h
> +++ b/include/uapi/linux/perf_event.h
> @@ -165,8 +165,10 @@ enum perf_event_sample_format {
>  	PERF_SAMPLE_DATA_PAGE_SIZE		= 1U << 22,
>  	PERF_SAMPLE_CODE_PAGE_SIZE		= 1U << 23,
>  	PERF_SAMPLE_WEIGHT_STRUCT		= 1U << 24,
> +	PERF_SAMPLE_BUILD_ID_OFFSET		= 1U << 25,
> +	PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET	= 1U << 26,
>  
> -	PERF_SAMPLE_MAX = 1U << 25,		/* non-ABI */
> +	PERF_SAMPLE_MAX = 1U << 27,		/* non-ABI */
>  };

[Severity: High]
Since PERF_SAMPLE_MAX is increased, perf_copy_attr() will now accept these
new sample types from userspace. However, is there missing core kernel logic
to actually process these bits?

Looking at the core kernel code, perf_output_sample() and
__perf_event_header_size() appear to lack handlers for the new bits. If a
userspace application requests these fields, it seems the kernel will not
allocate space for them or write them to the ring buffer.

When the userspace parser subsequently decodes the stream based on the
attr.sample_type bitmask, won't it read past the end of the truncated sample
and misinterpret the next event header as data?

> diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h
> index 2479a38883d00..d5660e2d6b603 100644
> --- a/tools/include/uapi/linux/perf_event.h
> +++ b/tools/include/uapi/linux/perf_event.h

[ ... ]

> @@ -1054,6 +1056,13 @@ enum perf_event_type {
>  	 *	{ u64			code_page_size;} && PERF_SAMPLE_CODE_PAGE_SIZE
>  	 *	{ u64			size;
>  	 *	  char			data[size]; } && PERF_SAMPLE_AUX
> +	 *	{ struct build_id	bid;
> +	 *	  u64			offset; } && PERF_SAMPLE_BUILD_ID_OFFSET

[Severity: Medium]
It looks like this payload documentation block was only added to the tools
side header (tools/include/uapi/linux/perf_event.h). Was it intended to be
added to the canonical include/uapi/linux/perf_event.h as well?

Missing the canonical header causes divergence between the kernel and tools
headers, which might trigger check-headers.sh warnings and leave users relying
on the kernel headers without documentation for the new ABI.

[Severity: Medium]
Also, regarding the struct build_id payload documentation here, this structure
appears to be undeclared in the canonical kernel UAPI header.

Does the use of struct build_id here collide with the internal tools structure
of the same name? The internal tools/perf/util/build-id.h structure has a
layout of:

  u8 data[20];
  u8 size;

Whereas the ABI typically uses a different layout (e.g., u8 size; u8 res1;
u16 res2; u8 data[20];). Could this cause layout confusion for tool developers
attempting to parse the ABI?

> +	 *	{ u64			nr;
> +	 *	  struct {
> +	 *		struct build_id	bid;
> +	 *		u64 offset;
> +	 *	  }[nr]; } && PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET
>  	 * };
>  	 */
>  	PERF_RECORD_SAMPLE			= 9,

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807071818.718751-1-irogers@google.com?part=2

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

* Re: [RFC PATCH v1 3/8] perf/core: Implement BUILD_ID_OFFSET sample type
  2026-08-07  7:18 ` [RFC PATCH v1 3/8] perf/core: Implement BUILD_ID_OFFSET sample type Ian Rogers
@ 2026-08-07  7:54   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-07  7:54 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users, bpf

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] New perf sample fields are inserted into the middle of the sample output, violating ABI append-only structure expectations.
- [High] Kernel IPs are incorrectly classified as user IPs in callchains, resulting in severe lock contention and expensive VMA tree lookups in NMI context.
- [High] Iterating `stack_map_get_build_id_offset` across callchain IPs in a loop breaks its `irq_work` mechanism, preventing resolution of all but the first IP.
- [High] The fallback stub for `stack_map_get_build_id_offset` incorrectly sets the status to `BPF_STACK_BUILD_ID_VALID`, causing perf to output zeroed build IDs.
- [Low] Conflicting definitions of `stack_map_get_build_id_offset` cause a build failure when `CONFIG_BPF_SYSCALL` is enabled but `CONFIG_PERF_EVENTS` is disabled.
--

--- Patch [3]: [RFC PATCH v1 3/8] perf/core: Implement BUILD_ID_OFFSET sample type ---
commit 230345e68f9a75e56416ae7796d505b712c39458
Author: Ian Rogers <irogers@google.com>

perf/core: Implement BUILD_ID_OFFSET sample type

Expose stack_map_get_build_id_offset for perf_events to use when generating
these samples, and implement the payload writing in the perf event core.

> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 7719f65284456..fd76900a8e476 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -4209,4 +4209,17 @@ static inline int bpf_map_check_op_flags(struct bpf_map *map, u64 flags, u64 all
>  	return 0;
>  }
>  
> +struct bpf_stack_build_id;
> +#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_PERF_EVENTS)
> +void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
> +				    u32 trace_nr, bool user, bool may_fault);
> +#else
> +static inline void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
> +						  u32 trace_nr, bool user, bool may_fault)

[Severity: Low]
Can this conditionally defined static inline stub cause a build failure?

When the kernel is compiled with CONFIG_BPF_SYSCALL=y but
CONFIG_PERF_EVENTS=n, this header provides a static inline stub. However,
kernel/bpf/stackmap.c unconditionally defines the non-static version of
stack_map_get_build_id_offset() when CONFIG_BPF_SYSCALL=y, leading to a
redefinition error.

> +{
> +	if (id_offs)
> +		id_offs->status = 1; /* BPF_STACK_BUILD_ID_EMPTY */

[Severity: High]
Does this fallback stub incorrectly set the status to
BPF_STACK_BUILD_ID_VALID?

The value 1 corresponds to BPF_STACK_BUILD_ID_VALID, not
BPF_STACK_BUILD_ID_EMPTY (which is 0).

Because the status is marked as valid, when CONFIG_PERF_EVENTS is
disabled or in other fallback scenarios, perf_output_sample() will copy
the zero-initialized build_id buffer into the perf event instead of
properly reporting the raw IP.

> +}
> +#endif
> +
>  #endif /* _LINUX_BPF_H */

[ ... ]

> diff --git a/kernel/events/core.c b/kernel/events/core.c
> index ba5bd6a78fe7b..97ac70441602d 100644
> --- a/kernel/events/core.c
> +++ b/kernel/events/core.c
> @@ -8287,6 +8301,62 @@ void perf_output_sample(struct perf_output_handle *handle,
>  	if (sample_type & PERF_SAMPLE_READ)
>  		perf_output_read(handle, event);
>  
> +	if (sample_type & PERF_SAMPLE_BUILD_ID_OFFSET) {

[Severity: Critical]
Does placing this output logic before PERF_SAMPLE_CALLCHAIN violate the ABI
append-only structure expectations?

The Linux perf sample ABI relies on fields being ordered exactly
corresponding to the bit-order of the PERF_SAMPLE_* flags. New flags with
higher bit indices must have their payloads appended to the end of the
sample structure.

By inserting the payload for PERF_SAMPLE_BUILD_ID_OFFSET and
PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET here in perf_output_sample() before
PERF_SAMPLE_CALLCHAIN, parsers reading sequentially will misalign their
offsets. They may treat build ID data as callchain metadata, causing memory
corruption or crashes in user space.

> +		struct bpf_stack_build_id bpf_bid = { .ip = data->ip };
> +		struct perf_sample_build_id_offset bid_offset = { 0 };

[ ... ]

> +	if (sample_type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET) {
> +		u64 nr = data->callchain ? data->callchain->nr : 0;
> +		struct perf_sample_build_id_offset bid_offset;
> +		struct bpf_stack_build_id bpf_bid;
> +		u64 i;
> +
> +		perf_output_put(handle, nr);
> +
> +		for (i = 0; i < nr; i++) {
> +			memset(&bid_offset, /*c=*/0, sizeof(bid_offset));
> +			memset(&bpf_bid, /*c=*/0, sizeof(bpf_bid));
> +			bpf_bid.ip = data->callchain->ip[i];
> +
> +			if (data->callchain->ip[i] == PERF_CONTEXT_USER) {
> +				bpf_bid.status = BPF_STACK_BUILD_ID_EMPTY;
> +			} else {
> +				bool is_user = data->callchain->ip[i] < PERF_CONTEXT_MAX;

[Severity: High]
Does this logic incorrectly classify standard kernel IPs as user IPs?

In perf_output_sample(), when processing
PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET, this checks if the IP is less than
PERF_CONTEXT_MAX. Since standard kernel instruction pointers are less than
the PERF_CONTEXT_MAX marker (which is used for context boundary markers
like PERF_CONTEXT_USER), this evaluates to true for all kernel IPs.

Calling stack_map_get_build_id_offset() with user=true for kernel IPs
forces an mmap_read_trylock(current->mm) and a VMA tree traversal inside an
NMI for every kernel IP. Can this cause severe lock contention and
expensive VMA tree lookups?

> +
> +				stack_map_get_build_id_offset(&bpf_bid, /*trace_nr=*/1,
> +					      is_user, /*may_fault=*/false);

[Severity: High]
Does iterating stack_map_get_build_id_offset() across callchain IPs in a
loop break its irq_work mechanism?

Inside perf_output_sample() running in NMI context,
stack_map_get_build_id_offset() acquires a per-CPU irq_work to defer
mmap_read_unlock(). On the first iteration, the lock is acquired and the
irq_work is queued.

On the second iteration, bpf_mmap_unlock_get_irq_work() detects that the
per-CPU irq_work is already busy (since the NMI has not exited to process
it). This prevents resolution of all but the first user IP in a callchain,
silently falling back to raw IPs for the rest.

> +			}
> +
> +			if (bpf_bid.status == BPF_STACK_BUILD_ID_VALID) {
> +				bid_offset.size = BPF_BUILD_ID_SIZE;
> +				memcpy(bid_offset.build_id, bpf_bid.build_id,
> +			       BPF_BUILD_ID_SIZE);
> +				bid_offset.offset = bpf_bid.offset;
> +			} else {
> +				bid_offset.offset = bpf_bid.ip;
> +			}
> +			perf_output_put(handle, bid_offset);
> +		}
> +	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807071818.718751-1-irogers@google.com?part=3

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

* Re: [RFC PATCH v1 4/8] perf: Refactor thread map and symbol APIs to take perf_sample
  2026-08-07  7:18 ` [RFC PATCH v1 4/8] perf: Refactor thread map and symbol APIs to take perf_sample Ian Rogers
@ 2026-08-07  8:20   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-07  8:20 UTC (permalink / raw)
  To: Ian Rogers; +Cc: bpf, linux-perf-users

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The updated `thread__find_map` function incorrectly treats an address of `0` as missing IP data, breaking lookups for address `0` and skipping required `addr_location` field initialization.
- [Medium] Excessive and redundant zero-initialization of the large `struct perf_sample` in hot paths causes severe performance overhead.
--

--- Patch [4]: [RFC PATCH v1 4/8] perf: Refactor thread map and symbol APIs to take perf_sample ---
commit 1099356ec0523851e160af69676be71cd35a2dc7
Author: Ian Rogers <irogers@google.com>

perf: Refactor thread map and symbol APIs to take perf_sample

This commit refactors the thread__find_map and thread__find_symbol
family of APIs in the perf tools to accept a pointer to a perf_sample
structure instead of individual cpumode and addr parameters. This change
prepares the APIs to support symbol lookups using a build ID and offset,
which are contained in the sample payload rather than just the address.

> 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
> @@ -1000,8 +1000,10 @@ static int perf_sample__fprintf_brstack(struct perf_sample *sample,
>  
>  			addr_location__init(&alf);
>  			addr_location__init(&alt);
> -			thread__find_map_fb(thread, sample->cpumode, from, &alf);
> -			thread__find_map_fb(thread, sample->cpumode, to, &alt);
> +			thread__find_map_fb(thread,
> +			    &(struct perf_sample){.cpumode = sample->cpumode, .ip = from}, &alf);
> +			thread__find_map_fb(thread,
> +			    &(struct perf_sample){.cpumode = sample->cpumode, .ip = to}, &alt);

[Severity: Medium]
Will these compound literals cause a significant performance regression in
this loop?

Because struct perf_sample is a large structure (~320 bytes), omitting the
remaining fields in the compound literal forces the compiler to
zero-initialize the rest of the struct for every branch stack entry.
Creating and zeroing these large structs inside a tight loop could
introduce severe CPU overhead when processing traces.

[ ... ]

> diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
> --- a/tools/perf/util/event.c
> +++ b/tools/perf/util/event.c
> @@ -702,34 +702,39 @@ struct map *thread__find_map(struct thread *thread, u8 cpumode, u64 addr,
>  	thread__zput(al->thread);
>  	al->thread = thread__get(thread);
>  
> -	al->addr = addr;
> -	al->cpumode = cpumode;
> +	if (sample->ip) {
> +		al->addr = sample->ip;
> +	} else {
> +		pr_debug("Sample missing IP data\n");
> +		return NULL;
> +	}
> +	al->cpumode = sample->cpumode;

[Severity: Medium]
Could this fail when resolving legitimate 0 addresses?

If a page fault or branch event occurs exactly at address 0 (for example,
a NULL pointer dereference), tools like trace__pgfault pass a sample->addr
of 0 as the IP. This condition evaluates to false for address 0, causing
the function to return NULL early.

When the function returns early here, it skips the initialization of
al->cpumode and al->level, which can leave those fields uninitialized
and cause trace output corruption (like printing null bytes).

[ ... ]

> @@ -816,7 +822,10 @@ int machine__resolve(struct machine *machine, struct addr_location *al,
>  		return -1;
>  
>  	dump_printf(" ... thread: %s:%d\n", thread__comm_str(thread), thread__tid(thread));
> -	thread__find_map(thread, sample->cpumode, sample->ip, al);
> +	thread__find_map(thread,
> +			 &(struct perf_sample){.cpumode = sample->cpumode,
> +					       .ip = sample->ip,
> +					       .bid = sample->bid}, al);

[Severity: Medium]
Could this compound literal also introduce overhead in the hot path?

Similar to the loop in perf_sample__fprintf_brstack, machine__resolve is
called for every single sample event. Creating a new, mostly
zero-initialized struct perf_sample here forces the compiler to write
zeroes to hundreds of bytes per event parsed, which can drastically slow
down trace processing.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807071818.718751-1-irogers@google.com?part=4

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

* Re: [RFC PATCH v1 5/8] perf tools: Internal support for BUILD_ID_OFFSET
  2026-08-07  7:18 ` [RFC PATCH v1 5/8] perf tools: Internal support for BUILD_ID_OFFSET Ian Rogers
@ 2026-08-07  8:41   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-07  8:41 UTC (permalink / raw)
  To: Ian Rogers; +Cc: bpf, linux-perf-users

Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] Synthesizing an event with PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET but without PERF_SAMPLE_CALLCHAIN causes a NULL pointer dereference.
- [High] Copying `callchain_bids` during event synthesis uses the potentially unrelated length `sample->callchain->nr`, leading to a heap buffer over-read.
- [High] Array access on `sample->callchain_bids` uses the unverified index `j` derived from `chain->nr`, leading to a heap buffer over-read.
- [High] `add_callchain_ip` ignores the `bid` and `offset` arguments when `cpumode` is set, breaking callchain resolution for `PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET`.
- [High] Missing `OVERFLOW_CHECK` when parsing `PERF_SAMPLE_BUILD_ID_OFFSET` allows out-of-bounds reads on malformed events.
- [Medium] `__evsel__parse_sample` fails to unswap the byte layout of `struct perf_build_id` when `swapped` is true, corrupting cross-endian perf.data analysis.
- [Medium] The test suite in `sample-parsing.c` is not updated for the new flags, causing immediate failure.
- [Medium] `thread__find_map` fails to resolve build IDs because `sample->ip` takes precedence over `sample->bid.bid`, misinterpreting the offset as a virtual address.
- [Medium] `leaf_frame_caller` incorrectly inherits the build ID and offset of its callee in `thread__resolve_callchain_sample`.
--

--- Patch [5]: [RFC PATCH v1 5/8] perf tools: Internal support for BUILD_ID_OFFSET ---
commit fbd06af754237e67a7a1661b582808d4407ddb3e
Author: Ian Rogers <irogers@google.com>
Subject: perf tools: Internal support for BUILD_ID_OFFSET

This patch implements user-space parsing for the new sample types in evsel.
It updates perf_event_attr_fprintf to display the new bits in 'perf report -D'
and updates synthetic event parameter names.

[Severity: Medium]
There appears to be an omission outside of the files modified by this patch:
Has test__sample_parsing() in tools/perf/tests/sample-parsing.c been
updated for the new flags? If PERF_SAMPLE_MAX was updated, the hardcoded
safety check:

    if (PERF_SAMPLE_MAX > PERF_SAMPLE_WEIGHT_STRUCT << 1)

might cause the test suite to unconditionally abort.

> diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
> --- a/tools/perf/util/event.c
> +++ b/tools/perf/util/event.c
> @@ -740,15 +734,33 @@ struct map *thread__find_map(struct thread *thread, struct perf_sample *sample,
>  
>  		return NULL;
>  	}
> -	al->map = maps__find(maps, al->addr);
> -	if (al->map != NULL) {
> -		/*
> -		 * Kernel maps might be changed when loading symbols so loading
> -		 * must be done prior to using kernel maps.
> -		 */
> -		if (load_map)
> -			map__load(al->map);
> -		al->addr = map__map_ip(al->map, al->addr);
> +
> +	if (sample->ip) {
> +		al->addr = sample->ip;
> +		al->map = maps__find(maps, al->addr);

[Severity: Medium]
When resolving callchains, sample->ip is typically populated with the file
offset. Because sample->ip takes precedence in this if/else chain, will the
offset be incorrectly evaluated as a virtual address via maps__find(),
causing the resolution to skip maps__find_by_build_id() entirely?

> +		if (al->map != NULL) {

[ ... ]

> diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
> --- a/tools/perf/util/evsel.c
> +++ b/tools/perf/util/evsel.c
> @@ -3778,6 +3778,30 @@ int __evsel__parse_sample(struct evsel *evsel, union perf_event *event,
>  		array = (void *)array + sz;
>  	}
>  
> +	if (type & PERF_SAMPLE_BUILD_ID_OFFSET) {
> +		data->bid.bid = (struct perf_build_id *) array;

[Severity: Medium]
During cross-platform analysis of perf.data files from a different
endianness, the event array is 64-bit byte-swapped prior to parsing. Since
struct perf_build_id contains mixed-size fields (8-bit, 16-bit, 32-bit,
and a byte array), does this code need to unswap the layout similar to how
PERF_SAMPLE_AUX uses mem_bswap_64() to prevent data corruption?

> +		array += sizeof(struct perf_build_id) / sizeof(u64);
> +		data->bid.offset = *array;
> +		array++;

[Severity: High]
Should there be an OVERFLOW_CHECK_u64(array) before dereferencing array
for the offset? Since PERF_SAMPLE_BUILD_ID_OFFSET is not part of
PERF_SAMPLE_MASK, its size isn't included in evsel->sample_size,
which could allow an out-of-bounds read on truncated events.

> +	} else {
> +		data->bid.bid = NULL;
> +		data->bid.offset = 0;
> +	}

[ ... ]

> diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
> --- a/tools/perf/util/machine.c
> +++ b/tools/perf/util/machine.c
> @@ -2216,7 +2216,9 @@ static int add_callchain_ip(struct thread *thread,
>  			    struct branch_flags *flags,
>  			    struct iterations *iter,
>  			    u64 branch_from,
> -			    bool symbols)
> +			    bool symbols,
> +			      struct perf_build_id *bid,
> +			      u64 offset)
>  {
>  	struct map_symbol ms = {};
>  	struct addr_location al;
> @@ -2230,7 +2232,9 @@ static int add_callchain_ip(struct thread *thread,
>  	al.srcline = NULL;
>  	if (!cpumode) {
>  		thread__find_cpumode_addr_location(thread,
> -					   &(struct perf_sample){.ip = ip},
> +					   &(struct perf_sample){.ip = ip,
> +								 .bid = { .bid = bid,
> +									  .offset = offset }},
>  					   symbols, &al);
>  	} else {

[Severity: High]
In thread__resolve_callchain_sample(), cpumode is initialized locally and
its address (&cpumode) is passed to add_callchain_ip(), meaning cpumode is
never NULL. Doesn't this force execution into the else branch here?

The else branch constructs a temporary struct perf_sample that omits the
new bid and offset parameters, which silently drops the data before
map/symbol resolution.

[ ... ]

> @@ -2979,13 +3003,19 @@ static int thread__resolve_callchain_sample(struct thread *thread,
>  			continue;
>  #endif
>  		ip = chain->ips[j];
> +		if (sample->callchain_bids) {
> +			bid = (struct perf_build_id *)&((u64 *)sample->callchain_bids)[j * 4];
> +			offset = ((u64 *)sample->callchain_bids)[j * 4 + 3];
> +		}

[Severity: High]
Since the loop index j is bounded by chain->nr rather than
sample->callchain_bids_nr, could a malformed event where chain->nr exceeds
the actual length of callchain_bids cause a heap buffer over-read here?

>  		if (ip < PERF_CONTEXT_MAX)
>                         ++nr_entries;

[ ... ]

> @@ -3011,21 +3041,28 @@ static int thread__resolve_callchain_sample(struct thread *thread,
>  
>  				err = add_callchain_ip(thread, cursor, parent,
>  						root_al, &cpumode, leaf_frame_caller,
> -						false, NULL, NULL, 0, symbols);
> -				if (err)
> -					return (err < 0) ? err : 0;
> +						false, NULL, NULL, 0, symbols, bid, offset);

[Severity: Medium]
When synthesizing a missing caller frame (leaf_frame_caller), this passes
the bid and offset variables belonging to the current frame (j, the callee)
into add_callchain_ip(). Does this incorrectly associate the synthesized
caller's IP with the callee's build ID?

> +				if (err) {
> +					err = (err < 0) ? err : 0;
> +					goto out;
> +				}

[ ... ]

> diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic-events.c
> --- a/tools/perf/util/synthetic-events.c
> +++ b/tools/perf/util/synthetic-events.c
> @@ -1701,6 +1701,13 @@ size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type,
>  		result += sample->aux_sample.size;
>  	}
>  
> +	if (type & PERF_SAMPLE_BUILD_ID_OFFSET)
> +		result += sizeof(struct perf_build_id) + sizeof(u64);
> +
> +	if (type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET)
> +		result += sizeof(u64) + sample->callchain->nr *
> +			  (sizeof(struct perf_build_id) + sizeof(u64));
> +
>  	return result;
>  }

[Severity: High]
If an event contains PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET but not
PERF_SAMPLE_CALLCHAIN, sample->callchain will be NULL. Does this sequence
unconditionally dereference sample->callchain->nr and cause a crash?

[ ... ]

> @@ -1933,6 +1940,19 @@ int perf_event__synthesize_sample(union perf_event *event, u64 type, u64 read_fo
>  		array = (void *)array + sz;
>  	}
>  
> +	if (type & PERF_SAMPLE_BUILD_ID_OFFSET) {
> +		memcpy(array, sample->bid.bid, sizeof(struct perf_build_id));
> +		array += sizeof(struct perf_build_id) / sizeof(u64);
> +		*array++ = sample->bid.offset;
> +	}
> +
> +	if (type & PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET) {
> +		*array++ = sample->callchain->nr;
> +		sz = sample->callchain->nr * (sizeof(struct perf_build_id) + sizeof(u64));
> +		memcpy(array, sample->callchain_bids, sz);

[Severity: High]
Should this use sample->callchain_bids_nr instead of sample->callchain->nr
for the copy size calculation? If a malformed event provides a chain->nr
that is larger than the true bounds of callchain_bids, could this
copy out-of-bounds heap memory into the synthesized event stream?

> +		array += sz / sizeof(u64);
> +	}
> +
>  	return 0;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807071818.718751-1-irogers@google.com?part=5

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

* Re: [RFC PATCH v1 6/8] perf inject: Extend perf inject to support bid_offset conversion
  2026-08-07  7:18 ` [RFC PATCH v1 6/8] perf inject: Extend perf inject to support bid_offset conversion Ian Rogers
@ 2026-08-07  9:18   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-07  9:18 UTC (permalink / raw)
  To: Ian Rogers; +Cc: bpf, linux-perf-users

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Integer overflow in `u16 max_size` calculation bypasses bounds check, leading to a massive heap buffer overflow.
- [High] Missing `inject_bid_offset_tool__new` instantiation and fatal invalid pointer free on error path.
- [High] Unused attribute rewriting functions lead to complete data corruption in output `perf.data`.
- [High] Raw event copying incorrectly checks `branch_sample_type` flags against the `sample_type` bitmask, aliasing with other flags.
- [High] Incorrect event boundary calculations allow the tool to read out-of-bounds memory up to 16 bytes past the event buffer.
- [High] Missing curly braces around `PERF_SAMPLE_READ` format condition checks leads to unconditional array execution, corrupting the sample payload.
- [Medium] Missing explicit inclusion of POSIX standard libc headers `<errno.h>` and `<string.h>` breaks musl compatibility.
--

--- Patch [6]: [RFC PATCH v1 6/8] perf inject: Extend perf inject to support bid_offset conversion ---
commit 00dac7cb64934c318662c48215a38d4a8f9ec625
Author: Ian Rogers <irogers@google.com>

perf inject: Extend perf inject to support bid_offset conversion

This patch adds a --sample-buildids option to perf inject. It drops MMAP
events and rewrites samples to use build IDs and offsets instead of virtual
addresses.

> diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
> index 6aa9e3eea4383..8f8d5267a37e2 100644
> --- a/tools/perf/builtin-inject.c
> +++ b/tools/perf/builtin-inject.c
[ ... ]
> @@ -2880,8 +2889,11 @@ int cmd_inject(int argc, const char **argv)
>  
>  	if (IS_ERR(inject.session)) {
>  		ret = PTR_ERR(inject.session);
> +
>  		if (inject.aslr)
>  			aslr_tool__delete(tool);
> +		if (inject.build_id_style == BID_RWS__SAMPLE_BUILDID)
> +			inject_bid_offset_tool__delete(tool);

[Severity: High]
Does this code call inject_bid_offset_tool__delete() on an invalid pointer
if __perf_session__new() fails? It appears inject_bid_offset_tool__new() is
never called to instantiate the tool during setup, so when running with
--sample-buildids, this error path will use container_of() and free() on an
uninitialized or incorrect pointer, crashing the tool.

>  		goto out_close_output;
>  	}
>  
> diff --git a/tools/perf/util/inject_bid_offset.c b/tools/perf/util/inject_bid_offset.c
> new file mode 100644
> index 0000000000000..f7b5fb05fac79
> --- /dev/null
> +++ b/tools/perf/util/inject_bid_offset.c
> @@ -0,0 +1,504 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include "inject_bid_offset.h"
> +
> +#include <stdlib.h>
> +
> +#include <linux/compiler.h>
> +#include <linux/string.h>

[Severity: Medium]
Will this file fail to build against musl libc? According to the POSIX
standard, files using POSIX constants like -E2BIG, -EFAULT and functions
like memcpy() need to explicitly include <errno.h> and <string.h>.

> +#include <linux/zalloc.h>
[ ... ]
> +int perf_event__rewrite_attr_for_build_id_offset(struct perf_event_attr *attr)
> +{

[Severity: High]
Is this attribute rewriting function dead code? It seems to be defined but
never called. Since inject_bid_offset_tool__sample() strips PERF_SAMPLE_IP
and appends PERF_SAMPLE_BUILD_ID_OFFSET, leaving the event attributes
unmodified will cause downstream tools like perf report to parse the
rewritten build-id payloads using the old schema, corrupting the data.

> +	if (attr->sample_type & (PERF_SAMPLE_BUILD_ID_OFFSET |
> +				 PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET)) {
[ ... ]
> +static int inject_bid_offset_tool__sample(const struct perf_tool *tool,
> +					  union perf_event *event,
> +					  struct perf_sample *sample,
> +					  struct machine *machine)
> +{
[ ... ]
> +	__u64 i = 0, j = 0;
> +	__u64 *in_array, *out_array;
> +	__u64 sample_type = evsel->core.attr.sample_type;
> +	const __u64 max_i = event->header.size / sizeof(__u64);
> +	struct thread *thread;
> +	u16 max_size = event->header.size;

[Severity: Critical]
Can this variable overflow when adding the callchain size below? Because
max_size is typed as u16, when processing an event with a large
sample->callchain->nr, adding the size could cause it to wrap around.

> +
> +	if ((sample_type & (PERF_SAMPLE_IP | PERF_SAMPLE_CALLCHAIN)) == 0)
[ ... ]
> +	if (sample_type & PERF_SAMPLE_CALLCHAIN) {
> +		max_size +=
> +			sample->callchain->nr * (sizeof(struct perf_build_id) +
> +						 sizeof(u64) - sizeof(u64));
> +	}
> +
> +	if (max_size > PERF_SAMPLE_MAX_SIZE) {

[Severity: Critical]
If max_size wraps around, will this bounds check (against 65536) bypass the
protection, leading to a massive out-of-bounds write later in the loop when
writing the unvalidated payload?

> +		pr_debug("Insufficient space to copy event\n");
> +		thread__put(thread);
> +		return -E2BIG;
> +	}
[ ... ]
> +	in_array = &event->sample.array[0];
> +	out_array = &ev->sample.array[0];
> +
> +	if (sample_type & PERF_SAMPLE_IDENTIFIER) {
> +		if (i > max_i)
> +			goto err;

[Severity: High]
Does this boundary calculation allow reading past the event buffer? max_i
represents the total number of 64-bit words including the 8-byte header,
but in_array points to the array skipping the header. Checking against
max_i loosely allows index i to read out of bounds.

> +		out_array[j++] = in_array[i++];
> +	}
[ ... ]
> +	if (sample_type & PERF_SAMPLE_READ) {
> +		if ((evsel->core.attr.read_format & PERF_FORMAT_GROUP) == 0) {
> +			if (i > max_i)
> +				goto err;
> +			out_array[j++] = in_array[i++];
> +			if (evsel->core.attr.read_format &
> +			    PERF_FORMAT_TOTAL_TIME_ENABLED)
> +				if (i > max_i)
> +					goto err;
> +			out_array[j++] = in_array[i++];

[Severity: High]
Will this execute unconditionally? Because of the missing curly braces
around the condition check, it looks like only the if (i > max_i) goto
err; check is conditional on PERF_FORMAT_TOTAL_TIME_ENABLED, causing the
out_array assignment to always execute and misalign all subsequent fields
if the flag is absent.

> +			if (evsel->core.attr.read_format &
> +			    PERF_FORMAT_TOTAL_TIME_RUNNING)
> +				if (i > max_i)
> +					goto err;
> +			out_array[j++] = in_array[i++];
[ ... ]
> +		} else {
> +			u64 nr;
[ ... ]
> +			for (u64 cntr = 0; cntr < nr; cntr++) {
> +				if (i > max_i)
> +					goto err;
> +				out_array[j++] = in_array[i++];
> +				if (evsel->core.attr.read_format &
> +				    PERF_FORMAT_ID)
> +					if (i > max_i)
> +						goto err;
> +				out_array[j++] = in_array[i++];

[Severity: High]
Are there missing curly braces here as well? The array copy appears to
execute unconditionally, potentially corrupting the event if
PERF_FORMAT_ID is absent.

[ ... ]
> +	if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
> +		if (i > max_i)
> +			goto err;
> +		out_array[j++] = in_array[i++];
> +		if (sample_type & PERF_SAMPLE_BRANCH_HW_INDEX) {

[Severity: High]
Does this code check the wrong bitmask? PERF_SAMPLE_BRANCH_HW_INDEX belongs
to branch_sample_type, not sample_type. Checking it against sample_type
means it aliases with PERF_SAMPLE_TRANSACTION, which might incorrectly
consume fields and corrupt the payload for unrelated events.

> +			if (i > max_i)
> +				goto err;
> +			out_array[j++] = in_array[i++];
> +		}
> +		if (i + (sample->branch_stack->nr * 3) > max_i)
> +			goto err;
> +		memcpy(&out_array[j], &in_array[i],
> +		       sample->branch_stack->nr * 3 * sizeof(u64));
> +		i += sample->branch_stack->nr * 3;
> +		j += sample->branch_stack->nr * 3;
> +		if (sample_type & PERF_SAMPLE_BRANCH_COUNTERS) {

[Severity: High]
Similarly, does this alias with PERF_SAMPLE_PHYS_ADDR since
PERF_SAMPLE_BRANCH_COUNTERS is a branch_sample_type flag, rather than a
sample_type flag?

> +			if (i + sample->branch_stack->nr > max_i)
> +				goto err;
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807071818.718751-1-irogers@google.com?part=6

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

* Re: [RFC PATCH v1 7/8] perf record: Add --buildid-offset option
  2026-08-07  7:18 ` [RFC PATCH v1 7/8] perf record: Add --buildid-offset option Ian Rogers
@ 2026-08-07  9:47   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-07  9:47 UTC (permalink / raw)
  To: Ian Rogers; +Cc: bpf, linux-perf-users

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] evsel__parse_sample() is vulnerable to an out-of-bounds read when parsing PERF_SAMPLE_BUILD_ID_OFFSET due to missing bounds checks and under-counted sample_size.
- [High] --buildid-offset silently produces unusable recordings if --buildid-mmap is disabled or unsupported.
- [High] Integer overflow in OVERFLOW_CHECK calculation for PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET bypasses bounds verification on 32-bit hosts.
- [Medium] perf report natively handles callchain build ID offsets but its front-end argument validation incorrectly rejects them.
- [Low] An empty if block with commented-out code was added to cmd_record.
--

--- Patch [7]: [RFC PATCH v1 7/8] perf record: Add --buildid-offset option ---
commit 1f8464ef5aa5b3d651d1ae57de1909d39b5f085c
Author: Ian Rogers <irogers@google.com>
Date:   Fri Aug 7 07:18:17 2026 +0000

perf record: Add --buildid-offset option
    
Add the --buildid-offset flag to perf record to natively request
PERF_SAMPLE_BUILD_ID_OFFSET and PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET
from the kernel. This configures the evsel attributes by replacing
the standard IP and CALLCHAIN bits with the build ID offset equivalents.

> diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
> index f58d7e3c7879e..9ff9cfc062788 100644
> --- a/tools/perf/builtin-record.c
> +++ b/tools/perf/builtin-record.c

[ ... ]

> @@ -4166,6 +4168,11 @@ int cmd_record(int argc, const char **argv)
>  		rec->no_buildid_cache = true;
>  	}
>  
> +	if (rec->opts.buildid_offset) {
> +		/* Do not drop mmap events here so we can generate build ids later */
> +		/* rec->opts.synth &= ~PERF_SYNTH_MMAP; */
> +	}
> +

[Severity: Low]
Is this commented-out code still needed, or can this empty block be removed?

[Severity: High]
Can sample resolution silently fail if --buildid-mmap is unsupported or
disabled?

If a user explicitly requests --buildid-offset but disables --buildid-mmap,
or the kernel does not support MMAP build IDs (which causes perf to forcefully
disable --buildid-mmap), the MMAP events will lack build IDs.

This causes thread__find_map() and maps__find_by_build_id() to silently fail,
resulting in broken profiles with unknown symbols and bypassing the final
post-processing step to read build IDs from disk.

Should we enforce the dependency on rec->buildid_mmap here?

>  	if (rec->opts.record_cgroup && !perf_can_record_cgroup()) {

[ ... ]

> diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
> index d9a07f5825e82..587f0fa41cce8 100644
> --- a/tools/perf/util/evsel.c
> +++ b/tools/perf/util/evsel.c

[ ... ]

> @@ -1744,6 +1744,17 @@ void evsel__config(struct evsel *evsel, const struct record_opts *opts,
>  	if (opts->sample_transaction)
>  		evsel__set_sample_bit(evsel, TRANSACTION);
>  
> +	if (opts->buildid_offset) {
> +		if (evsel->core.attr.sample_type & PERF_SAMPLE_IP) {
> +			evsel__reset_sample_bit(evsel, IP);
> +			evsel__set_sample_bit(evsel, BUILD_ID_OFFSET);

[Severity: High]
Does this allow an out-of-bounds read in evsel__parse_sample()?

When evsel__set_sample_bit() is called, __evsel__set_sample_bit() blindly adds
sizeof(u64) (8 bytes) to evsel->sample_size. However, the actual payload for
PERF_SAMPLE_BUILD_ID_OFFSET is 32 bytes (24 bytes for build_id + 8 for offset).

Because of this under-counting, a crafted or truncated perf.data file can
pass the initial perf_event__check_size() validation. Then, in
evsel__parse_sample(), PERF_SAMPLE_BUILD_ID_OFFSET is parsed without any
explicit OVERFLOW_CHECK, allowing the pointer to read out-of-bounds.

> +		}
> +		if (evsel->core.attr.sample_type & PERF_SAMPLE_CALLCHAIN) {
> +			evsel__reset_sample_bit(evsel, CALLCHAIN);
> +			evsel__set_sample_bit(evsel, CALLCHAIN_BUILD_ID_OFFSET);

[Severity: High]
Does this cause an integer overflow on 32-bit hosts?

When parsing PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET in evsel__parse_sample(),
a maliciously crafted file could provide a massive size (e.g., 0x08000001).

The macro OVERFLOW_CHECK performs sz * (sizeof(struct perf_build_id) +
sizeof(u64)), which is sz * 32. This results in 0x100000020 in 64-bit
arithmetic, but on 32-bit hosts, the pointer arithmetic inside OVERFLOW_CHECK
truncates the 64-bit size to 32 bits (0x20).

This trick effectively bypasses the bounds verification, while the raw
size is assigned to callchain_bids_nr, causing an out-of-bounds read during
iteration.

[Severity: Medium]
Will this replacement break perf report when users run it with -g?

If a file is natively recorded with --buildid-offset, the CALLCHAIN bit is
dynamically replaced by PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET.

However, in builtin-report.c:cmd_report():

    if (!is_pipe && !(sample_type & PERF_SAMPLE_CALLCHAIN)) {
        if (perf_hpp_list.parent) {
            ui__error("Selected --sort parent, but no "
                      "callchain data. Did you call "
                      "'perf record' without -g?\n");

This strict check seems to completely ignore that the bit was replaced by
CALLCHAIN_BUILD_ID_OFFSET, causing perf report to incorrectly reject these
files.

> +		}
> +	}
> +
>  	if (opts->running_time) {

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807071818.718751-1-irogers@google.com?part=7

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

* Re: [RFC PATCH v1 8/8] perf tests: Add build_id_offset test coverage
  2026-08-07  7:18 ` [RFC PATCH v1 8/8] perf tests: Add build_id_offset test coverage Ian Rogers
@ 2026-08-07  9:58   ` sashiko-bot
  0 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-08-07  9:58 UTC (permalink / raw)
  To: Ian Rogers; +Cc: bpf, linux-perf-users

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Medium] Heap Use-After-Free of `thread` reference handle in `mmap_events`.
- [Medium] Raw pointer comparison of reference-counted `struct map` handles causes spurious test failures.
- [Medium] Silent test bypass due to missing validation of `PERF_SAMPLE_BUILD_ID_OFFSET` fields.
- [Medium] Stack Out-Of-Bounds Read when synthesizing `PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET`.
- [Low] Missing explicit `<string.h>` inclusion causes compilation failures on musl libc.
--

--- Patch [8]: [RFC PATCH v1 8/8] perf tests: Add build_id_offset test coverage ---
commit 2b1a4526386e7ebd56cc41f452ac5f172c32e199
Author: Ian Rogers <irogers@google.com>

perf tests: Add build_id_offset test coverage

Test the kernel interface using sys_perf_event_open, test perf inject
--sample-buildids with pipe mode and callchains, and test perf record
--buildid-offset across multiple sampling modes.

> diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c
> index dca8bbfe780a7..65c72ed896ac2 100644
> --- a/tools/perf/tests/mmap-thread-lookup.c
> +++ b/tools/perf/tests/mmap-thread-lookup.c
[ ... ]
> @@ -211,6 +213,40 @@ static int mmap_events(synth_cb synth)
>  
>  		pr_debug("map %p, addr %" PRIx64 "\n", al.map, map__start(al.map));
>  
> +		/* Verify lookup by build ID and offset */
> +		{
> +			struct dso *dso = dso__get(map__dso(al.map));
> +
> +			if (dso) {
> +				const struct build_id *bid = dso__bid(dso);
> +
> +				if (bid && bid->size > 0) {
> +					struct addr_location al2;
> +					struct perf_build_id pbid = { .size = bid->size };
> +					u64 offset = map__dso_map_ip(al.map,
> +								     (unsigned long)(td->map + 1));
> +
> +					memcpy(pbid.data, bid->data, bid->size);

[Severity: Low]
Does this code need to explicitly include <string.h>?

Without the explicit include, calling memcpy() causes compilation failures on
systems using musl libc.

> +					addr_location__init(&al2);
> +					thread__find_map(thread, &(struct perf_sample){

[Severity: Medium]
Can this use-after-free the thread reference handle?

Looking at mmap_events(), thread__put(thread) is called right after the first
thread__find_map() call.

This means the newly added test code dereferences the freed thread pointer
here. Under REFCNT_CHECKING (ASAN), this invalidates the handle and poisons
the memory.

> +								.cpumode = PERF_RECORD_MISC_USER,
> +								.ip = 0,
> +								.bid = {
> +									.bid = &pbid,
> +									.offset = offset,
> +								},
> +							 }, &al2);
> +
> +					if (al2.map != al.map) {

[Severity: Medium]
Will this raw pointer comparison of reference-counted struct map handles cause
spurious test failures?

When perf is compiled with REFCNT_CHECKING, map__get() creates a newly
allocated wrapper struct for each reference, meaning two handles to the same
underlying map will have different pointer addresses.

Should this use RC_CHK_EQUAL(al2.map, al.map)?

> +						pr_debug("failed, couldn't find map by build id and offset\n");
> +						err = -1;
> +					}
> +					addr_location__exit(&al2);
> +				}
> +				dso__put(dso);
> +			}
> +		}
> +
>  		addr_location__exit(&al);
>  		if (err)
>  			break;

> diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c
> index 20cab91ceaeb2..b49b6dcf10cdd 100644
> --- a/tools/perf/tests/sample-parsing.c
> +++ b/tools/perf/tests/sample-parsing.c
[ ... ]
> @@ -229,6 +229,36 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format)
>  	const u32 raw_data[] = {0x12345678, 0x0a0b0c0d, 0x11020304, 0x05060708, 0 };
>  	const u64 data[] = {0x2211443366558877ULL, 0, 0xaabbccddeeff4321ULL};
>  	const u64 aux_data[] = {0xa55a, 0, 0xeeddee, 0x0282028202820282};
> +	const struct perf_build_id bid = {
> +		.size = 20,
> +		.data = {
> +			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
> +			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
> +		}
> +	};
> +	const struct perf_build_id callchain_bids[] = {

[Severity: Medium]
Will this result in a stack out-of-bounds read when synthesizing
PERF_SAMPLE_CALLCHAIN_BUILD_ID_OFFSET?

callchain_bids is declared as an array of 3 struct perf_build_id elements
(3 * 24 = 72 bytes).

However, perf_event__synthesize_sample() expects this array to contain
elements of size sizeof(struct perf_build_id) + sizeof(u64) (32 bytes).
It calculates the size as 96 bytes and calls memcpy(), reading past the end
of the stack array.

> +		{
> +			.size = 20,
> +			.data = {
> +				'9', '8', '7', '6', '5', '4', '3', '2', '1', '0',
> +				'9', '8', '7', '6', '5', '4', '3', '2', '1', '0',
> +			}
> +		},
[ ... ]
> @@ -277,6 +307,11 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format)
>  			.size	= sizeof(aux_data),
>  			.data	= (void *)aux_data,
>  		},
> +		.bid		= {
> +			.bid = (struct perf_build_id *)&bid,
> +			.offset = 0,
> +		},
> +		.callchain_bids = (struct perf_build_id *)callchain_bids,

[Severity: Medium]
Are the new PERF_SAMPLE_BUILD_ID_OFFSET fields actually being validated?

do_test() synthesizes events with bid and callchain_bids data here, but
the samples_same() validation function completely omits the COMP() checks
for these new flags.

Does this mean the parsing is never actually verified?

>  	};
>  	struct sample_read_value values[] = {{1, 5, 0}, {9, 3, 0}, {2, 7, 0}, {6, 4, 1},};
>  	struct perf_sample sample_out, sample_out_endian;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807071818.718751-1-irogers@google.com?part=8

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

* Re: [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support
  2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
                   ` (7 preceding siblings ...)
  2026-08-07  7:18 ` [RFC PATCH v1 8/8] perf tests: Add build_id_offset test coverage Ian Rogers
@ 2026-08-07 11:18 ` Peter Zijlstra
  8 siblings, 0 replies; 18+ messages in thread
From: Peter Zijlstra @ 2026-08-07 11:18 UTC (permalink / raw)
  To: Ian Rogers
  Cc: Steven Rostedt, Masami Hiramatsu, Alexei Starovoitov,
	Daniel Borkmann, John Fastabend, Andrii Nakryiko,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
	Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ingo Molnar,
	Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
	Alexander Shishkin, Adrian Hunter, James Clark, Suzuki K Poulose,
	Mike Leach, Leo Yan, John Garry, Will Deacon, Thomas Gleixner,
	Dapeng Mi, Ravi Bangoria, Swapnil Sapkal, Thomas Falcon,
	Thomas Richter, Dmitrii Dolgov, Eric Biggers, Zecheng Li,
	Gabriel Marin, Tengda Wu, Derek Foreman, Tanushree Shah,
	Ankur Arora, Aaron Tomlin, tanze, Rui Qi, Howard Chu, Chuck Lever,
	Shimin Guo, Alessio Podda, linux-kernel, bpf, linux-perf-users,
	coresight, linux-arm-kernel

On Fri, Aug 07, 2026 at 12:18:10AM -0700, Ian Rogers wrote:
> This patch series introduces PERF_SAMPLE_BUILD_ID_OFFSET to the
> perf_event UAPI and implements full support across both the kernel
> and perf tools.
> 
> Background & Motivation:
> 
> In order for perf to translate virtual addresses of samples into
> symbols a file and offset within the file are needed. During event
> synthesis perf will create mmap events to facilitate the translation
> of a virtual address to a file and offset by modelling the address
> space of a process. By directly recording in a sample the Build ID of
> a file and the offset within it, no synthesis is necessary. The Build
> ID and offset as a pair are much larger than a virtual address, so
> there is a trade-off between synthesis cost and extra size for
> samples. These changes just facilitate Build ID and offset as a choice
> for perf samples and the user can have the choice to use it when they
> believe it is advantageous.
> 
> In practice perf still needs to map a build ID to a file, so by
> default this change keeps synthesis to allow this. It is expected a
> user that knows their build IDs, say through debuginfod, will disable
> this option with say --synth=no.
> 
> The kernel support uses the existing build ID and offset support used
> by BPF stack traces.

That is still a giant stinking mess that needs to cleaned up.

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

end of thread, other threads:[~2026-08-07 11:18 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07  7:18 [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Ian Rogers
2026-08-07  7:18 ` [RFC PATCH v1 1/8] perf event: Factor build_id out into its own top-level struct Ian Rogers
2026-08-07  7:33   ` sashiko-bot
2026-08-07  7:18 ` [RFC PATCH v1 2/8] perf/core: Add BUILD_ID_OFFSET to UAPI Ian Rogers
2026-08-07  7:42   ` sashiko-bot
2026-08-07  7:18 ` [RFC PATCH v1 3/8] perf/core: Implement BUILD_ID_OFFSET sample type Ian Rogers
2026-08-07  7:54   ` sashiko-bot
2026-08-07  7:18 ` [RFC PATCH v1 4/8] perf: Refactor thread map and symbol APIs to take perf_sample Ian Rogers
2026-08-07  8:20   ` sashiko-bot
2026-08-07  7:18 ` [RFC PATCH v1 5/8] perf tools: Internal support for BUILD_ID_OFFSET Ian Rogers
2026-08-07  8:41   ` sashiko-bot
2026-08-07  7:18 ` [RFC PATCH v1 6/8] perf inject: Extend perf inject to support bid_offset conversion Ian Rogers
2026-08-07  9:18   ` sashiko-bot
2026-08-07  7:18 ` [RFC PATCH v1 7/8] perf record: Add --buildid-offset option Ian Rogers
2026-08-07  9:47   ` sashiko-bot
2026-08-07  7:18 ` [RFC PATCH v1 8/8] perf tests: Add build_id_offset test coverage Ian Rogers
2026-08-07  9:58   ` sashiko-bot
2026-08-07 11:18 ` [RFC PATCH v1 0/8] perf/core, perf/tools: Add PERF_SAMPLE_BUILD_ID_OFFSET support Peter Zijlstra

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox