forked from Minki/linux
e4378f0cb9
bpf_prog_info contains information necessary to annotate bpf programs. This patch saves bpf_prog_info for bpf programs loaded in the system. Some big picture of the next few patches: To fully annotate BPF programs with source code mapping, 4 different informations are needed: 1) PERF_RECORD_KSYMBOL 2) PERF_RECORD_BPF_EVENT 3) bpf_prog_info 4) btf Before this set, 1) and 2) in the list are already saved to perf.data file. For BPF programs that are already loaded before perf run, 1) and 2) are synthesized by perf_event__synthesize_bpf_events(). For short living BPF programs, 1) and 2) are generated by kernel. This set handles 3) and 4) from the list. Again, it is necessary to handle existing BPF program and short living program separately. This patch handles 3) for exising BPF programs while synthesizing 1) and 2) in perf_event__synthesize_bpf_events(). These data are stored in perf_env. The next patch saves these data from perf_env to perf.data as headers. Similarly, the two patches after the next saves 4) of existing BPF programs to perf_env and perf.data. Another patch later will handle 3) and 4) for short living BPF programs by monitoring 1) and 2) in a dedicate thread. Signed-off-by: Song Liu <songliubraving@fb.com> Reviewed-by: Jiri Olsa <jolsa@kernel.org> Cc: Alexei Starovoitov <ast@kernel.org> Cc: Daniel Borkmann <daniel@iogearbox.net> Cc: Namhyung Kim <namhyung@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Stanislav Fomichev <sdf@google.com> Cc: kernel-team@fb.com Link: http://lkml.kernel.org/r/20190312053051.2690567-7-songliubraving@fb.com [ set env->bpf_progs.infos_cnt to zero in perf_env__purge_bpf() as noted by jolsa ] Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
256 lines
6.9 KiB
C
256 lines
6.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <bpf/bpf.h>
|
|
#include <bpf/btf.h>
|
|
#include <bpf/libbpf.h>
|
|
#include <linux/btf.h>
|
|
#include <linux/err.h>
|
|
#include "bpf-event.h"
|
|
#include "debug.h"
|
|
#include "symbol.h"
|
|
#include "machine.h"
|
|
#include "env.h"
|
|
#include "session.h"
|
|
|
|
#define ptr_to_u64(ptr) ((__u64)(unsigned long)(ptr))
|
|
|
|
static int snprintf_hex(char *buf, size_t size, unsigned char *data, size_t len)
|
|
{
|
|
int ret = 0;
|
|
size_t i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
ret += snprintf(buf + ret, size - ret, "%02x", data[i]);
|
|
return ret;
|
|
}
|
|
|
|
int machine__process_bpf_event(struct machine *machine __maybe_unused,
|
|
union perf_event *event,
|
|
struct perf_sample *sample __maybe_unused)
|
|
{
|
|
if (dump_trace)
|
|
perf_event__fprintf_bpf_event(event, stdout);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Synthesize PERF_RECORD_KSYMBOL and PERF_RECORD_BPF_EVENT for one bpf
|
|
* program. One PERF_RECORD_BPF_EVENT is generated for the program. And
|
|
* one PERF_RECORD_KSYMBOL is generated for each sub program.
|
|
*
|
|
* Returns:
|
|
* 0 for success;
|
|
* -1 for failures;
|
|
* -2 for lack of kernel support.
|
|
*/
|
|
static int perf_event__synthesize_one_bpf_prog(struct perf_session *session,
|
|
perf_event__handler_t process,
|
|
struct machine *machine,
|
|
int fd,
|
|
union perf_event *event,
|
|
struct record_opts *opts)
|
|
{
|
|
struct ksymbol_event *ksymbol_event = &event->ksymbol_event;
|
|
struct bpf_event *bpf_event = &event->bpf_event;
|
|
struct bpf_prog_info_linear *info_linear;
|
|
struct perf_tool *tool = session->tool;
|
|
struct bpf_prog_info_node *info_node;
|
|
struct bpf_prog_info *info;
|
|
struct btf *btf = NULL;
|
|
bool has_btf = false;
|
|
struct perf_env *env;
|
|
u32 sub_prog_cnt, i;
|
|
int err = 0;
|
|
u64 arrays;
|
|
|
|
/*
|
|
* for perf-record and perf-report use header.env;
|
|
* otherwise, use global perf_env.
|
|
*/
|
|
env = session->data ? &session->header.env : &perf_env;
|
|
|
|
arrays = 1UL << BPF_PROG_INFO_JITED_KSYMS;
|
|
arrays |= 1UL << BPF_PROG_INFO_JITED_FUNC_LENS;
|
|
arrays |= 1UL << BPF_PROG_INFO_FUNC_INFO;
|
|
arrays |= 1UL << BPF_PROG_INFO_PROG_TAGS;
|
|
arrays |= 1UL << BPF_PROG_INFO_JITED_INSNS;
|
|
arrays |= 1UL << BPF_PROG_INFO_LINE_INFO;
|
|
arrays |= 1UL << BPF_PROG_INFO_JITED_LINE_INFO;
|
|
|
|
info_linear = bpf_program__get_prog_info_linear(fd, arrays);
|
|
if (IS_ERR_OR_NULL(info_linear)) {
|
|
info_linear = NULL;
|
|
pr_debug("%s: failed to get BPF program info. aborting\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
if (info_linear->info_len < offsetof(struct bpf_prog_info, prog_tags)) {
|
|
pr_debug("%s: the kernel is too old, aborting\n", __func__);
|
|
return -2;
|
|
}
|
|
|
|
info = &info_linear->info;
|
|
|
|
/* number of ksyms, func_lengths, and tags should match */
|
|
sub_prog_cnt = info->nr_jited_ksyms;
|
|
if (sub_prog_cnt != info->nr_prog_tags ||
|
|
sub_prog_cnt != info->nr_jited_func_lens)
|
|
return -1;
|
|
|
|
/* check BTF func info support */
|
|
if (info->btf_id && info->nr_func_info && info->func_info_rec_size) {
|
|
/* btf func info number should be same as sub_prog_cnt */
|
|
if (sub_prog_cnt != info->nr_func_info) {
|
|
pr_debug("%s: mismatch in BPF sub program count and BTF function info count, aborting\n", __func__);
|
|
err = -1;
|
|
goto out;
|
|
}
|
|
if (btf__get_from_id(info->btf_id, &btf)) {
|
|
pr_debug("%s: failed to get BTF of id %u, aborting\n", __func__, info->btf_id);
|
|
err = -1;
|
|
btf = NULL;
|
|
goto out;
|
|
}
|
|
has_btf = true;
|
|
}
|
|
|
|
/* Synthesize PERF_RECORD_KSYMBOL */
|
|
for (i = 0; i < sub_prog_cnt; i++) {
|
|
u8 (*prog_tags)[BPF_TAG_SIZE] = (void *)(uintptr_t)(info->prog_tags);
|
|
__u32 *prog_lens = (__u32 *)(uintptr_t)(info->jited_func_lens);
|
|
__u64 *prog_addrs = (__u64 *)(uintptr_t)(info->jited_ksyms);
|
|
void *func_infos = (void *)(uintptr_t)(info->func_info);
|
|
const struct bpf_func_info *finfo;
|
|
const char *short_name = NULL;
|
|
const struct btf_type *t;
|
|
int name_len;
|
|
|
|
*ksymbol_event = (struct ksymbol_event){
|
|
.header = {
|
|
.type = PERF_RECORD_KSYMBOL,
|
|
.size = offsetof(struct ksymbol_event, name),
|
|
},
|
|
.addr = prog_addrs[i],
|
|
.len = prog_lens[i],
|
|
.ksym_type = PERF_RECORD_KSYMBOL_TYPE_BPF,
|
|
.flags = 0,
|
|
};
|
|
name_len = snprintf(ksymbol_event->name, KSYM_NAME_LEN,
|
|
"bpf_prog_");
|
|
name_len += snprintf_hex(ksymbol_event->name + name_len,
|
|
KSYM_NAME_LEN - name_len,
|
|
prog_tags[i], BPF_TAG_SIZE);
|
|
if (has_btf) {
|
|
finfo = func_infos + i * info->func_info_rec_size;
|
|
t = btf__type_by_id(btf, finfo->type_id);
|
|
short_name = btf__name_by_offset(btf, t->name_off);
|
|
} else if (i == 0 && sub_prog_cnt == 1) {
|
|
/* no subprog */
|
|
if (info->name[0])
|
|
short_name = info->name;
|
|
} else
|
|
short_name = "F";
|
|
if (short_name)
|
|
name_len += snprintf(ksymbol_event->name + name_len,
|
|
KSYM_NAME_LEN - name_len,
|
|
"_%s", short_name);
|
|
|
|
ksymbol_event->header.size += PERF_ALIGN(name_len + 1,
|
|
sizeof(u64));
|
|
|
|
memset((void *)event + event->header.size, 0, machine->id_hdr_size);
|
|
event->header.size += machine->id_hdr_size;
|
|
err = perf_tool__process_synth_event(tool, event,
|
|
machine, process);
|
|
}
|
|
|
|
if (!opts->no_bpf_event) {
|
|
/* Synthesize PERF_RECORD_BPF_EVENT */
|
|
*bpf_event = (struct bpf_event){
|
|
.header = {
|
|
.type = PERF_RECORD_BPF_EVENT,
|
|
.size = sizeof(struct bpf_event),
|
|
},
|
|
.type = PERF_BPF_EVENT_PROG_LOAD,
|
|
.flags = 0,
|
|
.id = info->id,
|
|
};
|
|
memcpy(bpf_event->tag, info->tag, BPF_TAG_SIZE);
|
|
memset((void *)event + event->header.size, 0, machine->id_hdr_size);
|
|
event->header.size += machine->id_hdr_size;
|
|
|
|
/* save bpf_prog_info to env */
|
|
info_node = malloc(sizeof(struct bpf_prog_info_node));
|
|
if (!info_node) {
|
|
err = -1;
|
|
goto out;
|
|
}
|
|
|
|
info_node->info_linear = info_linear;
|
|
perf_env__insert_bpf_prog_info(env, info_node);
|
|
info_linear = NULL;
|
|
|
|
/*
|
|
* process after saving bpf_prog_info to env, so that
|
|
* required information is ready for look up
|
|
*/
|
|
err = perf_tool__process_synth_event(tool, event,
|
|
machine, process);
|
|
}
|
|
|
|
out:
|
|
free(info_linear);
|
|
free(btf);
|
|
return err ? -1 : 0;
|
|
}
|
|
|
|
int perf_event__synthesize_bpf_events(struct perf_session *session,
|
|
perf_event__handler_t process,
|
|
struct machine *machine,
|
|
struct record_opts *opts)
|
|
{
|
|
union perf_event *event;
|
|
__u32 id = 0;
|
|
int err;
|
|
int fd;
|
|
|
|
event = malloc(sizeof(event->bpf_event) + KSYM_NAME_LEN + machine->id_hdr_size);
|
|
if (!event)
|
|
return -1;
|
|
while (true) {
|
|
err = bpf_prog_get_next_id(id, &id);
|
|
if (err) {
|
|
if (errno == ENOENT) {
|
|
err = 0;
|
|
break;
|
|
}
|
|
pr_debug("%s: can't get next program: %s%s\n",
|
|
__func__, strerror(errno),
|
|
errno == EINVAL ? " -- kernel too old?" : "");
|
|
/* don't report error on old kernel or EPERM */
|
|
err = (errno == EINVAL || errno == EPERM) ? 0 : -1;
|
|
break;
|
|
}
|
|
fd = bpf_prog_get_fd_by_id(id);
|
|
if (fd < 0) {
|
|
pr_debug("%s: failed to get fd for prog_id %u\n",
|
|
__func__, id);
|
|
continue;
|
|
}
|
|
|
|
err = perf_event__synthesize_one_bpf_prog(session, process,
|
|
machine, fd,
|
|
event, opts);
|
|
close(fd);
|
|
if (err) {
|
|
/* do not return error for old kernel */
|
|
if (err == -2)
|
|
err = 0;
|
|
break;
|
|
}
|
|
}
|
|
free(event);
|
|
return err;
|
|
}
|