[RFC PATCH v1 6/8] perf inject: Extend perf inject to support bid_offset conversion

Ian Rogers <[email protected]> Fri, 7 Aug 2026 00:18:16 -0700
Newsgroups org.kernel.vger.linux-perf-users,org.kernel.vger.bpf,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
---
 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