perf record: collect BPF metadata from existing BPF programs

Look for .rodata maps, find ones with 'bpf_metadata_' variables, extract
their values as strings, and create a new PERF_RECORD_BPF_METADATA
synthetic event using that data. The code gets invoked from the existing
routine perf_event__synthesize_one_bpf_prog().

For example, a BPF program with the following variables:

    const char bpf_metadata_version[] SEC(".rodata") = "3.14159";
    int bpf_metadata_value[] SEC(".rodata") = 42;

would generate a PERF_RECORD_BPF_METADATA record with:

    .prog_name        = <BPF program name, e.g. "bpf_prog_a1b2c3_foo">
    .nr_entries       = 2
    .entries[0].key   = "version"
    .entries[0].value = "3.14159"
    .entries[1].key   = "value"
    .entries[1].value = "42"

Each of the BPF programs and subprograms that share those variables would
get a distinct PERF_RECORD_BPF_METADATA record, with the ".prog_name"
showing the name of each program or subprogram. The prog_name is
deliberately the same as the ".name" field in the corresponding
PERF_RECORD_KSYMBOL record.

This code only gets invoked if support for displaying BTF char arrays
as strings is detected.

Signed-off-by: Blake Jones <blakejones@google.com>
Link: https://lore.kernel.org/r/20250612194939.162730-3-blakejones@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
This commit is contained in:
Blake Jones
2025-06-12 12:49:36 -07:00
committed by Namhyung Kim
parent 1d0654b7fd
commit ab38e84ba9
3 changed files with 362 additions and 0 deletions

View File

@@ -467,6 +467,22 @@ struct perf_record_compressed2 {
char data[];
};
#define BPF_METADATA_KEY_LEN 64
#define BPF_METADATA_VALUE_LEN 256
#define BPF_PROG_NAME_LEN KSYM_NAME_LEN
struct perf_record_bpf_metadata_entry {
char key[BPF_METADATA_KEY_LEN];
char value[BPF_METADATA_VALUE_LEN];
};
struct perf_record_bpf_metadata {
struct perf_event_header header;
char prog_name[BPF_PROG_NAME_LEN];
__u64 nr_entries;
struct perf_record_bpf_metadata_entry entries[];
};
enum perf_user_event_type { /* above any possible kernel type */
PERF_RECORD_USER_TYPE_START = 64,
PERF_RECORD_HEADER_ATTR = 64,
@@ -489,6 +505,7 @@ enum perf_user_event_type { /* above any possible kernel type */
PERF_RECORD_COMPRESSED = 81,
PERF_RECORD_FINISHED_INIT = 82,
PERF_RECORD_COMPRESSED2 = 83,
PERF_RECORD_BPF_METADATA = 84,
PERF_RECORD_HEADER_MAX
};
@@ -530,6 +547,7 @@ union perf_event {
struct perf_record_header_feature feat;
struct perf_record_compressed pack;
struct perf_record_compressed2 pack2;
struct perf_record_bpf_metadata bpf_metadata;
};
#endif /* __LIBPERF_EVENT_H */

View File

@@ -1,13 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bpf/bpf.h>
#include <bpf/btf.h>
#include <bpf/libbpf.h>
#include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/err.h>
#include <linux/perf_event.h>
#include <linux/string.h>
#include <linux/zalloc.h>
#include <internal/lib.h>
#include <perf/event.h>
#include <symbol/kallsyms.h>
#include "bpf-event.h"
#include "bpf-utils.h"
@@ -151,6 +159,319 @@ static int synthesize_bpf_prog_name(char *buf, int size,
return name_len;
}
#ifdef HAVE_LIBBPF_STRINGS_SUPPORT
#define BPF_METADATA_PREFIX "bpf_metadata_"
#define BPF_METADATA_PREFIX_LEN (sizeof(BPF_METADATA_PREFIX) - 1)
static bool name_has_bpf_metadata_prefix(const char **s)
{
if (strncmp(*s, BPF_METADATA_PREFIX, BPF_METADATA_PREFIX_LEN) != 0)
return false;
*s += BPF_METADATA_PREFIX_LEN;
return true;
}
struct bpf_metadata_map {
struct btf *btf;
const struct btf_type *datasec;
void *rodata;
size_t rodata_size;
unsigned int num_vars;
};
static int bpf_metadata_read_map_data(__u32 map_id, struct bpf_metadata_map *map)
{
int map_fd;
struct bpf_map_info map_info;
__u32 map_info_len;
int key;
struct btf *btf;
const struct btf_type *datasec;
struct btf_var_secinfo *vsi;
unsigned int vlen, vars;
void *rodata;
map_fd = bpf_map_get_fd_by_id(map_id);
if (map_fd < 0)
return -1;
memset(&map_info, 0, sizeof(map_info));
map_info_len = sizeof(map_info);
if (bpf_obj_get_info_by_fd(map_fd, &map_info, &map_info_len) < 0)
goto out_close;
/* If it's not an .rodata map, don't bother. */
if (map_info.type != BPF_MAP_TYPE_ARRAY ||
map_info.key_size != sizeof(int) ||
map_info.max_entries != 1 ||
!map_info.btf_value_type_id ||
!strstr(map_info.name, ".rodata")) {
goto out_close;
}
btf = btf__load_from_kernel_by_id(map_info.btf_id);
if (!btf)
goto out_close;
datasec = btf__type_by_id(btf, map_info.btf_value_type_id);
if (!btf_is_datasec(datasec))
goto out_free_btf;
/*
* If there aren't any variables with the "bpf_metadata_" prefix,
* don't bother.
*/
vlen = btf_vlen(datasec);
vsi = btf_var_secinfos(datasec);
vars = 0;
for (unsigned int i = 0; i < vlen; i++, vsi++) {
const struct btf_type *t_var = btf__type_by_id(btf, vsi->type);
const char *name = btf__name_by_offset(btf, t_var->name_off);
if (name_has_bpf_metadata_prefix(&name))
vars++;
}
if (vars == 0)
goto out_free_btf;
rodata = zalloc(map_info.value_size);
if (!rodata)
goto out_free_btf;
key = 0;
if (bpf_map_lookup_elem(map_fd, &key, rodata)) {
free(rodata);
goto out_free_btf;
}
close(map_fd);
map->btf = btf;
map->datasec = datasec;
map->rodata = rodata;
map->rodata_size = map_info.value_size;
map->num_vars = vars;
return 0;
out_free_btf:
btf__free(btf);
out_close:
close(map_fd);
return -1;
}
struct format_btf_ctx {
char *buf;
size_t buf_size;
size_t buf_idx;
};
static void format_btf_cb(void *arg, const char *fmt, va_list ap)
{
int n;
struct format_btf_ctx *ctx = (struct format_btf_ctx *)arg;
n = vsnprintf(ctx->buf + ctx->buf_idx, ctx->buf_size - ctx->buf_idx,
fmt, ap);
ctx->buf_idx += n;
if (ctx->buf_idx >= ctx->buf_size)
ctx->buf_idx = ctx->buf_size;
}
static void format_btf_variable(struct btf *btf, char *buf, size_t buf_size,
const struct btf_type *t, const void *btf_data)
{
struct format_btf_ctx ctx = {
.buf = buf,
.buf_idx = 0,
.buf_size = buf_size,
};
const struct btf_dump_type_data_opts opts = {
.sz = sizeof(struct btf_dump_type_data_opts),
.skip_names = 1,
.compact = 1,
.emit_strings = 1,
};
struct btf_dump *d;
size_t btf_size;
d = btf_dump__new(btf, format_btf_cb, &ctx, NULL);
btf_size = btf__resolve_size(btf, t->type);
btf_dump__dump_type_data(d, t->type, btf_data, btf_size, &opts);
btf_dump__free(d);
}
static void bpf_metadata_fill_event(struct bpf_metadata_map *map,
struct perf_record_bpf_metadata *bpf_metadata_event)
{
struct btf_var_secinfo *vsi;
unsigned int i, vlen;
memset(bpf_metadata_event->prog_name, 0, BPF_PROG_NAME_LEN);
vlen = btf_vlen(map->datasec);
vsi = btf_var_secinfos(map->datasec);
for (i = 0; i < vlen; i++, vsi++) {
const struct btf_type *t_var = btf__type_by_id(map->btf,
vsi->type);
const char *name = btf__name_by_offset(map->btf,
t_var->name_off);
const __u64 nr_entries = bpf_metadata_event->nr_entries;
struct perf_record_bpf_metadata_entry *entry;
if (!name_has_bpf_metadata_prefix(&name))
continue;
if (nr_entries >= (__u64)map->num_vars)
break;
entry = &bpf_metadata_event->entries[nr_entries];
memset(entry, 0, sizeof(*entry));
snprintf(entry->key, BPF_METADATA_KEY_LEN, "%s", name);
format_btf_variable(map->btf, entry->value,
BPF_METADATA_VALUE_LEN, t_var,
map->rodata + vsi->offset);
bpf_metadata_event->nr_entries++;
}
}
static void bpf_metadata_free_map_data(struct bpf_metadata_map *map)
{
btf__free(map->btf);
free(map->rodata);
}
static struct bpf_metadata *bpf_metadata_alloc(__u32 nr_prog_tags,
__u32 nr_variables)
{
struct bpf_metadata *metadata;
size_t event_size;
metadata = zalloc(sizeof(struct bpf_metadata));
if (!metadata)
return NULL;
metadata->prog_names = zalloc(nr_prog_tags * sizeof(char *));
if (!metadata->prog_names) {
bpf_metadata_free(metadata);
return NULL;
}
for (__u32 prog_index = 0; prog_index < nr_prog_tags; prog_index++) {
metadata->prog_names[prog_index] = zalloc(BPF_PROG_NAME_LEN);
if (!metadata->prog_names[prog_index]) {
bpf_metadata_free(metadata);
return NULL;
}
metadata->nr_prog_names++;
}
event_size = sizeof(metadata->event->bpf_metadata) +
nr_variables * sizeof(metadata->event->bpf_metadata.entries[0]);
metadata->event = zalloc(event_size);
if (!metadata->event) {
bpf_metadata_free(metadata);
return NULL;
}
metadata->event->bpf_metadata = (struct perf_record_bpf_metadata) {
.header = {
.type = PERF_RECORD_BPF_METADATA,
.size = event_size,
},
.nr_entries = 0,
};
return metadata;
}
static struct bpf_metadata *bpf_metadata_create(struct bpf_prog_info *info)
{
struct bpf_metadata *metadata;
const __u32 *map_ids = (__u32 *)(uintptr_t)info->map_ids;
for (__u32 map_index = 0; map_index < info->nr_map_ids; map_index++) {
struct bpf_metadata_map map;
if (bpf_metadata_read_map_data(map_ids[map_index], &map) != 0)
continue;
metadata = bpf_metadata_alloc(info->nr_prog_tags, map.num_vars);
if (!metadata)
continue;
bpf_metadata_fill_event(&map, &metadata->event->bpf_metadata);
for (__u32 index = 0; index < info->nr_prog_tags; index++) {
synthesize_bpf_prog_name(metadata->prog_names[index],
BPF_PROG_NAME_LEN, info,
map.btf, index);
}
bpf_metadata_free_map_data(&map);
return metadata;
}
return NULL;
}
static int synthesize_perf_record_bpf_metadata(const struct bpf_metadata *metadata,
const struct perf_tool *tool,
perf_event__handler_t process,
struct machine *machine)
{
const size_t event_size = metadata->event->header.size;
union perf_event *event;
int err = 0;
event = zalloc(event_size + machine->id_hdr_size);
if (!event)
return -1;
memcpy(event, metadata->event, event_size);
memset((void *)event + event->header.size, 0, machine->id_hdr_size);
event->header.size += machine->id_hdr_size;
for (__u32 index = 0; index < metadata->nr_prog_names; index++) {
memcpy(event->bpf_metadata.prog_name,
metadata->prog_names[index], BPF_PROG_NAME_LEN);
err = perf_tool__process_synth_event(tool, event, machine,
process);
if (err != 0)
break;
}
free(event);
return err;
}
void bpf_metadata_free(struct bpf_metadata *metadata)
{
if (metadata == NULL)
return;
for (__u32 index = 0; index < metadata->nr_prog_names; index++)
free(metadata->prog_names[index]);
free(metadata->prog_names);
free(metadata->event);
free(metadata);
}
#else /* HAVE_LIBBPF_STRINGS_SUPPORT */
static struct bpf_metadata *bpf_metadata_create(struct bpf_prog_info *info __maybe_unused)
{
return NULL;
}
static int synthesize_perf_record_bpf_metadata(const struct bpf_metadata *metadata __maybe_unused,
const struct perf_tool *tool __maybe_unused,
perf_event__handler_t process __maybe_unused,
struct machine *machine __maybe_unused)
{
return 0;
}
void bpf_metadata_free(struct bpf_metadata *metadata __maybe_unused)
{
}
#endif /* HAVE_LIBBPF_STRINGS_SUPPORT */
/*
* Synthesize PERF_RECORD_KSYMBOL and PERF_RECORD_BPF_EVENT for one bpf
* program. One PERF_RECORD_BPF_EVENT is generated for the program. And
@@ -173,6 +494,7 @@ static int perf_event__synthesize_one_bpf_prog(struct perf_session *session,
const struct perf_tool *tool = session->tool;
struct bpf_prog_info_node *info_node;
struct perf_bpil *info_linear;
struct bpf_metadata *metadata;
struct bpf_prog_info *info;
struct btf *btf = NULL;
struct perf_env *env;
@@ -193,6 +515,7 @@ static int perf_event__synthesize_one_bpf_prog(struct perf_session *session,
arrays |= 1UL << PERF_BPIL_JITED_INSNS;
arrays |= 1UL << PERF_BPIL_LINE_INFO;
arrays |= 1UL << PERF_BPIL_JITED_LINE_INFO;
arrays |= 1UL << PERF_BPIL_MAP_IDS;
info_linear = get_bpf_prog_info_linear(fd, arrays);
if (IS_ERR_OR_NULL(info_linear)) {
@@ -301,6 +624,15 @@ static int perf_event__synthesize_one_bpf_prog(struct perf_session *session,
*/
err = perf_tool__process_synth_event(tool, event,
machine, process);
/* Synthesize PERF_RECORD_BPF_METADATA */
metadata = bpf_metadata_create(info);
if (metadata != NULL) {
err = synthesize_perf_record_bpf_metadata(metadata,
tool, process,
machine);
bpf_metadata_free(metadata);
}
}
out:

View File

@@ -17,6 +17,12 @@ struct record_opts;
struct evlist;
struct target;
struct bpf_metadata {
union perf_event *event;
char **prog_names;
__u64 nr_prog_names;
};
struct bpf_prog_info_node {
struct perf_bpil *info_linear;
struct rb_node rb_node;
@@ -36,6 +42,7 @@ int evlist__add_bpf_sb_event(struct evlist *evlist, struct perf_env *env);
void __bpf_event__print_bpf_prog_info(struct bpf_prog_info *info,
struct perf_env *env,
FILE *fp);
void bpf_metadata_free(struct bpf_metadata *metadata);
#else
static inline int machine__process_bpf(struct machine *machine __maybe_unused,
union perf_event *event __maybe_unused,
@@ -55,6 +62,11 @@ static inline void __bpf_event__print_bpf_prog_info(struct bpf_prog_info *info _
FILE *fp __maybe_unused)
{
}
static inline void bpf_metadata_free(struct bpf_metadata *metadata __maybe_unused)
{
}
#endif // HAVE_LIBBPF_SUPPORT
#endif