[RFC perf,bpf 4/5] perf util: introduce bpf_prog_info_event

From: Song Liu
Date: Tue Nov 06 2018 - 15:54:19 EST


This patch introduces struct bpf_prog_info_event to union perf_event.

struct bpf_prog_info_event {
struct perf_event_header header;
u32 prog_info_len;
u32 ksym_table_len;
u64 ksym_table;
struct bpf_prog_info prog_info;
char data[];
};

struct bpf_prog_info_event contains information about a bpf program.
These events are written to perf.data by perf-record, and processed by
perf-report.

struct bpf_prog_info_event uses arrays for some data (ksym_table, and
arrays in struct bpf_prog_info). To make these arrays easy to serialize,
we allocate continuous memory (data). These array pointers are translated
to offset in bpf_prog_info_event before written to file. And vice-versa
when the event is read from file.

This patch enables synthesizing these events at the beginning of
perf-record run. Next patch will process short living bpf programs that
are created during perf-record.

Signed-off-by: Song Liu <songliubraving@xxxxxx>
---
tools/perf/builtin-record.c | 5 +
tools/perf/builtin-report.c | 2 +
tools/perf/util/Build | 2 +
tools/perf/util/bpf-info.c | 287 ++++++++++++++++++++++++++++++++++++
tools/perf/util/bpf-info.h | 29 ++++
tools/perf/util/event.c | 1 +
tools/perf/util/event.h | 14 ++
tools/perf/util/session.c | 4 +
tools/perf/util/tool.h | 3 +-
9 files changed, 346 insertions(+), 1 deletion(-)
create mode 100644 tools/perf/util/bpf-info.c
create mode 100644 tools/perf/util/bpf-info.h

diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index 0980dfe3396b..73b02bde1ebc 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -41,6 +41,7 @@
#include "util/perf-hooks.h"
#include "util/time-utils.h"
#include "util/units.h"
+#include "util/bpf-info.h"
#include "asm/bug.h"

#include <errno.h>
@@ -850,6 +851,9 @@ static int record__synthesize(struct record *rec, bool tail)
err = __machine__synthesize_threads(machine, tool, &opts->target, rec->evlist->threads,
process_synthesized_event, opts->sample_address,
opts->proc_map_timeout, 1);
+
+ err = perf_event__synthesize_bpf_prog_info(
+ &rec->tool, process_synthesized_event, machine);
out:
return err;
}
@@ -1531,6 +1535,7 @@ static struct record record = {
.namespaces = perf_event__process_namespaces,
.mmap = perf_event__process_mmap,
.mmap2 = perf_event__process_mmap2,
+ .bpf_prog_info = perf_event__process_bpf_prog_info,
.ordered_events = true,
},
};
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index c0703979c51d..4a9a3e8da4e0 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -41,6 +41,7 @@
#include "util/auxtrace.h"
#include "util/units.h"
#include "util/branch.h"
+#include "util/bpf-info.h"

#include <dlfcn.h>
#include <errno.h>
@@ -981,6 +982,7 @@ int cmd_report(int argc, const char **argv)
.auxtrace_info = perf_event__process_auxtrace_info,
.auxtrace = perf_event__process_auxtrace,
.feature = process_feature_event,
+ .bpf_prog_info = perf_event__process_bpf_prog_info,
.ordered_events = true,
.ordering_requires_timestamps = true,
},
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index ecd9f9ceda77..624c7281217c 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -150,6 +150,8 @@ endif

libperf-y += perf-hooks.o

+libperf-$(CONFIG_LIBBPF) += bpf-info.o
+
libperf-$(CONFIG_CXX) += c++/

CFLAGS_config.o += -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))"
diff --git a/tools/perf/util/bpf-info.c b/tools/perf/util/bpf-info.c
new file mode 100644
index 000000000000..fa598c4328be
--- /dev/null
+++ b/tools/perf/util/bpf-info.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Facebook
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <bpf/bpf.h>
+#include "bpf-info.h"
+#include "debug.h"
+#include "session.h"
+
+#define KSYM_NAME_LEN 128
+#define BPF_PROG_INFO_MIN_SIZE 128 /* minimal require jited_func_lens */
+
+static inline __u64 ptr_to_u64(const void *ptr)
+{
+ return (__u64) (unsigned long) ptr;
+}
+
+/* fetch information of the bpf program via bpf syscall. */
+struct bpf_prog_info_event *perf_bpf_info__get_bpf_prog_info_event(u32 prog_id)
+{
+ struct bpf_prog_info_event *prog_info_event = NULL;
+ struct bpf_prog_info info = {};
+ u32 info_len = sizeof(info);
+ u32 event_len, i;
+ int fd, err;
+ void *ptr;
+
+ fd = bpf_prog_get_fd_by_id(prog_id);
+ if (fd < 0) {
+ pr_debug("Failed to get fd for prog_id %u\n", prog_id);
+ return NULL;
+ }
+
+ err = bpf_obj_get_info_by_fd(fd, &info, &info_len);
+ if (err) {
+ pr_debug("can't get prog info: %s", strerror(errno));
+ goto close_fd;
+ }
+ if (info_len < BPF_PROG_INFO_MIN_SIZE) {
+ pr_debug("kernel is too old to support proper prog info\n");
+ goto close_fd;
+ }
+
+ /* calculate size of bpf_prog_info_event */
+ event_len = sizeof(struct bpf_prog_info_event);
+ event_len += info_len;
+ event_len -= sizeof(info);
+ event_len += info.jited_prog_len;
+ event_len += info.xlated_prog_len;
+ event_len += info.nr_map_ids * sizeof(u32);
+ event_len += info.nr_jited_ksyms * sizeof(u64);
+ event_len += info.nr_jited_func_lens * sizeof(u32);
+ event_len += info.nr_jited_ksyms * KSYM_NAME_LEN;
+
+ prog_info_event = (struct bpf_prog_info_event *) malloc(event_len);
+ if (!prog_info_event)
+ goto close_fd;
+
+ /* assign pointers for map_ids, jited_prog_insns, etc. */
+ ptr = prog_info_event->data;
+ info.map_ids = ptr_to_u64(ptr);
+ ptr += info.nr_map_ids * sizeof(u32);
+ info.jited_prog_insns = ptr_to_u64(ptr);
+ ptr += info.jited_prog_len;
+ info.xlated_prog_insns = ptr_to_u64(ptr);
+ ptr += info.xlated_prog_len;
+ info.jited_ksyms = ptr_to_u64(ptr);
+ ptr += info.nr_jited_ksyms * sizeof(u64);
+ info.jited_func_lens = ptr_to_u64(ptr);
+ ptr += info.nr_jited_func_lens * sizeof(u32);
+
+ err = bpf_obj_get_info_by_fd(fd, &info, &info_len);
+ if (err) {
+ pr_err("can't get prog info: %s\n", strerror(errno));
+ free(prog_info_event);
+ prog_info_event = NULL;
+ goto close_fd;
+ }
+
+ /* fill data in prog_info_event */
+ prog_info_event->header.type = PERF_RECORD_BPF_PROG_INFO;
+ prog_info_event->header.misc = 0;
+ prog_info_event->prog_info_len = info_len;
+
+ memcpy(&prog_info_event->prog_info, &info, info_len);
+
+ prog_info_event->ksym_table_len = 0;
+ prog_info_event->ksym_table = ptr_to_u64(ptr);
+
+ /* fill in fake symbol name for now, add real name after BTF */
+ if (info.nr_jited_func_lens == 1 && info.name) { /* only main prog */
+ size_t l;
+
+ assert(info.nr_jited_ksyms == 1);
+ l = snprintf(ptr, KSYM_NAME_LEN, "bpf_prog_%s", info.name);
+ prog_info_event->ksym_table_len += l + 1;
+ ptr += l + 1;
+
+ } else {
+ assert(info.nr_jited_ksyms == info.nr_jited_func_lens);
+
+ for (i = 0; i < info.nr_jited_ksyms; i++) {
+ size_t l;
+
+ l = snprintf(ptr, KSYM_NAME_LEN, "bpf_prog_%d_%d",
+ info.id, i);
+ prog_info_event->ksym_table_len += l + 1;
+ ptr += l + 1;
+ }
+ }
+
+ prog_info_event->header.size = ptr - (void *)prog_info_event;
+
+close_fd:
+ close(fd);
+ return prog_info_event;
+}
+
+static size_t fprintf_bpf_prog_info(
+ struct bpf_prog_info_event *prog_info_event, FILE *fp)
+{
+ struct bpf_prog_info *info = &prog_info_event->prog_info;
+ unsigned long *jited_ksyms = (unsigned long *)(info->jited_ksyms);
+ char *name_ptr = (char *) prog_info_event->ksym_table;
+ unsigned int i;
+ size_t ret;
+
+ ret = fprintf(fp, "bpf_prog: type: %u id: %u ", info->type, info->id);
+ ret += fprintf(fp, "nr_jited_ksyms: %u\n", info->nr_jited_ksyms);
+
+ for (i = 0; i < info->nr_jited_ksyms; i++) {
+ ret += fprintf(fp, "jited_ksyms[%u]: %lx %s\n",
+ i, jited_ksyms[i], name_ptr);
+ name_ptr += strlen(name_ptr);
+ }
+ return ret;
+}
+
+size_t perf_event__fprintf_bpf_prog_info(union perf_event *event, FILE *fp)
+{
+ return fprintf_bpf_prog_info(&event->bpf_prog_info, fp);
+}
+
+/*
+ * translate all array ptr to offset from base address, called before
+ * writing the event to file
+ */
+void perf_bpf_info__ptr_to_offset(
+ struct bpf_prog_info_event *prog_info_event)
+{
+ u64 base = ptr_to_u64(prog_info_event);
+
+ prog_info_event->ksym_table -= base;
+ prog_info_event->prog_info.jited_prog_insns -= base;
+ prog_info_event->prog_info.xlated_prog_insns -= base;
+ prog_info_event->prog_info.map_ids -= base;
+ prog_info_event->prog_info.jited_ksyms -= base;
+ prog_info_event->prog_info.jited_func_lens -= base;
+}
+
+/*
+ * translate offset from base address to array pointer, called after
+ * reading the event from file
+ */
+void perf_bpf_info__offset_to_ptr(
+ struct bpf_prog_info_event *prog_info_event)
+{
+ u64 base = ptr_to_u64(prog_info_event);
+
+ prog_info_event->ksym_table += base;
+ prog_info_event->prog_info.jited_prog_insns += base;
+ prog_info_event->prog_info.xlated_prog_insns += base;
+ prog_info_event->prog_info.map_ids += base;
+ prog_info_event->prog_info.jited_ksyms += base;
+ prog_info_event->prog_info.jited_func_lens += base;
+}
+
+int perf_event__synthesize_one_bpf_prog_info(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine,
+ __u32 id)
+{
+ struct bpf_prog_info_event *prog_info_event;
+
+ prog_info_event = perf_bpf_info__get_bpf_prog_info_event(id);
+
+ if (!prog_info_event) {
+ pr_err("Failed to get prog_info_event\n");
+ return -1;
+ }
+ perf_bpf_info__ptr_to_offset(prog_info_event);
+
+ if (perf_tool__process_synth_event(
+ tool, (union perf_event *)prog_info_event,
+ machine, process) != 0) {
+ free(prog_info_event);
+ return -1;
+ }
+
+ free(prog_info_event);
+ return 0;
+}
+
+int perf_event__synthesize_bpf_prog_info(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine)
+{
+ __u32 id = 0;
+ int err = 0;
+
+ while (true) {
+ err = bpf_prog_get_next_id(id, &id);
+ if (err) {
+ if (errno == ENOENT) {
+ err = 0;
+ break;
+ }
+ fprintf(stderr, "can't get next program: %s%s",
+ strerror(errno),
+ errno == EINVAL ? " -- kernel too old?" : "");
+ err = -1;
+ break;
+ }
+ err = perf_event__synthesize_one_bpf_prog_info(
+ tool, process, machine, id);
+ }
+ return err;
+}
+
+int perf_event__process_bpf_prog_info(struct perf_session *session,
+ union perf_event *event)
+{
+ struct machine *machine = &session->machines.host;
+ struct bpf_prog_info_event *prog_info_event;
+ struct bpf_prog_info *info;
+ struct symbol *sym;
+ struct map *map;
+ char *name_ptr;
+ int ret = 0;
+ u64 *addrs;
+ u32 *lens;
+ u32 i;
+
+ prog_info_event = (struct bpf_prog_info_event *)
+ malloc(event->header.size);
+ if (!prog_info_event)
+ return -ENOMEM;
+
+ /* copy the data to rw memeory so we can modify it */
+ memcpy(prog_info_event, &event->bpf_prog_info, event->header.size);
+ info = &prog_info_event->prog_info;
+
+ perf_bpf_info__offset_to_ptr(prog_info_event);
+ name_ptr = (char *) prog_info_event->ksym_table;
+ addrs = (u64 *)info->jited_ksyms;
+ lens = (u32 *)info->jited_func_lens;
+ for (i = 0; i < info->nr_jited_ksyms; i++) {
+ u32 len = info->nr_jited_func_lens == 1 ?
+ len = info->jited_prog_len : lens[i];
+
+ map = map_groups__find(&machine->kmaps, addrs[i]);
+ if (!map) {
+ map = dso__new_map("bpf_prog");
+ if (!map) {
+ ret = -ENOMEM;
+ break;
+ }
+ map->start = addrs[i];
+ map->pgoff = map->start;
+ map->end = map->start + len;
+ map_groups__insert(&machine->kmaps, map);
+ }
+
+ sym = symbol__new(addrs[i], len, 0, 0, name_ptr);
+ if (!sym) {
+ ret = -ENOMEM;
+ break;
+ }
+ dso__insert_symbol(map->dso, sym);
+ name_ptr += strlen(name_ptr) + 1;
+ }
+
+ free(prog_info_event);
+ return ret;
+}
diff --git a/tools/perf/util/bpf-info.h b/tools/perf/util/bpf-info.h
new file mode 100644
index 000000000000..813cad07bacb
--- /dev/null
+++ b/tools/perf/util/bpf-info.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_BPF_INFO_H
+#define __PERF_BPF_INFO_H
+
+#include "event.h"
+#include "machine.h"
+#include "tool.h"
+#include "symbol.h"
+
+struct bpf_prog_info_event *perf_bpf_info__get_bpf_prog_info_event(u32 prog_id);
+
+size_t perf_event__fprintf_bpf_prog_info(union perf_event *event, FILE *fp);
+
+int perf_event__synthesize_one_bpf_prog_info(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine,
+ __u32 id);
+
+int perf_event__synthesize_bpf_prog_info(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine);
+
+void perf_bpf_info__ptr_to_offset(struct bpf_prog_info_event *prog_info_event);
+void perf_bpf_info__offset_to_ptr(struct bpf_prog_info_event *prog_info_event);
+
+int perf_event__process_bpf_prog_info(struct perf_session *session,
+ union perf_event *event);
+
+#endif /* __PERF_BPF_INFO_H */
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
index 601432afbfb2..33b1c168b83e 100644
--- a/tools/perf/util/event.c
+++ b/tools/perf/util/event.c
@@ -61,6 +61,7 @@ static const char *perf_event__names[] = {
[PERF_RECORD_EVENT_UPDATE] = "EVENT_UPDATE",
[PERF_RECORD_TIME_CONV] = "TIME_CONV",
[PERF_RECORD_HEADER_FEATURE] = "FEATURE",
+ [PERF_RECORD_BPF_PROG_INFO] = "BPF_PROG_INFO",
};

static const char *perf_ns__names[] = {
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h
index 13a0c64dd0ed..dc64d800eaa6 100644
--- a/tools/perf/util/event.h
+++ b/tools/perf/util/event.h
@@ -5,6 +5,7 @@
#include <limits.h>
#include <stdio.h>
#include <linux/kernel.h>
+#include <linux/bpf.h>

#include "../perf.h"
#include "build-id.h"
@@ -258,6 +259,7 @@ enum perf_user_event_type { /* above any possible kernel type */
PERF_RECORD_EVENT_UPDATE = 78,
PERF_RECORD_TIME_CONV = 79,
PERF_RECORD_HEADER_FEATURE = 80,
+ PERF_RECORD_BPF_PROG_INFO = 81,
PERF_RECORD_HEADER_MAX
};

@@ -629,6 +631,17 @@ struct feature_event {
char data[];
};

+#define KSYM_NAME_LEN 128
+
+struct bpf_prog_info_event {
+ struct perf_event_header header;
+ u32 prog_info_len;
+ u32 ksym_table_len;
+ u64 ksym_table;
+ struct bpf_prog_info prog_info;
+ char data[];
+};
+
union perf_event {
struct perf_event_header header;
struct mmap_event mmap;
@@ -661,6 +674,7 @@ union perf_event {
struct time_conv_event time_conv;
struct feature_event feat;
struct bpf_event bpf_event;
+ struct bpf_prog_info_event bpf_prog_info;
};

void perf_event__print_totals(void);
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index dffe5120d2d3..5365ee1dfbec 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -415,6 +415,8 @@ void perf_tool__fill_defaults(struct perf_tool *tool)
tool->time_conv = process_event_op2_stub;
if (tool->feature == NULL)
tool->feature = process_event_op2_stub;
+ if (tool->bpf_prog_info == NULL)
+ tool->bpf_prog_info = process_event_op2_stub;
}

static void swap_sample_id_all(union perf_event *event, void *data)
@@ -1397,6 +1399,8 @@ static s64 perf_session__process_user_event(struct perf_session *session,
return tool->time_conv(session, event);
case PERF_RECORD_HEADER_FEATURE:
return tool->feature(session, event);
+ case PERF_RECORD_BPF_PROG_INFO:
+ return tool->bpf_prog_info(session, event);
default:
return -EINVAL;
}
diff --git a/tools/perf/util/tool.h b/tools/perf/util/tool.h
index 69ae898ca024..739a4b1188f7 100644
--- a/tools/perf/util/tool.h
+++ b/tools/perf/util/tool.h
@@ -70,7 +70,8 @@ struct perf_tool {
stat_config,
stat,
stat_round,
- feature;
+ feature,
+ bpf_prog_info;
event_op3 auxtrace;
bool ordered_events;
bool ordering_requires_timestamps;
--
2.17.1