Commit ab38e84b authored by Blake Jones's avatar Blake Jones Committed by Namhyung Kim
Browse files

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: default avatarBlake Jones <blakejones@google.com>
Link: https://lore.kernel.org/r/20250612194939.162730-3-blakejones@google.com


Signed-off-by: default avatarNamhyung Kim <namhyung@kernel.org>
parent 1d0654b7
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -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 */
+332 −0
Original line number Diff line number Diff line
// 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:
+12 −0
Original line number Diff line number Diff line
@@ -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