mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 22:07:35 -04:00
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:
committed by
Namhyung Kim
parent
1d0654b7fd
commit
ab38e84ba9
@@ -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 */
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user