[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