Merge branch 'bpf-libbpf-relo-fix-and-tests'
Jesper Dangaard Brouer says: ==================== While playing with using libbpf for the Suricata project, we had issues LLVM >= 4.0.1 generating ELF files that could not be loaded with libbpf (tools/lib/bpf/). During the troubleshooting phase, I wrote a test program and improved the debugging output in libbpf. I turned this into a selftests program, and it also serves as a code example for libbpf in itself. I discovered that there are at least three ELF load issues with libbpf. I left them as TODO comments in (tools/testing/selftests/bpf) test_libbpf.sh. I've only fixed the load issue with eh_frames, and other types of relo-section that does not have exec flags. We can work on the other issues later. ==================== Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
This commit is contained in:
commit
d977ae593b
@ -15,9 +15,10 @@
|
|||||||
|
|
||||||
/* ld/ldx fields */
|
/* ld/ldx fields */
|
||||||
#define BPF_SIZE(code) ((code) & 0x18)
|
#define BPF_SIZE(code) ((code) & 0x18)
|
||||||
#define BPF_W 0x00
|
#define BPF_W 0x00 /* 32-bit */
|
||||||
#define BPF_H 0x08
|
#define BPF_H 0x08 /* 16-bit */
|
||||||
#define BPF_B 0x10
|
#define BPF_B 0x10 /* 8-bit */
|
||||||
|
/* eBPF BPF_DW 0x18 64-bit */
|
||||||
#define BPF_MODE(code) ((code) & 0xe0)
|
#define BPF_MODE(code) ((code) & 0xe0)
|
||||||
#define BPF_IMM 0x00
|
#define BPF_IMM 0x00
|
||||||
#define BPF_ABS 0x20
|
#define BPF_ABS 0x20
|
||||||
|
@ -319,8 +319,8 @@ bpf_program__init(void *data, size_t size, char *section_name, int idx,
|
|||||||
|
|
||||||
prog->section_name = strdup(section_name);
|
prog->section_name = strdup(section_name);
|
||||||
if (!prog->section_name) {
|
if (!prog->section_name) {
|
||||||
pr_warning("failed to alloc name for prog under section %s\n",
|
pr_warning("failed to alloc name for prog under section(%d) %s\n",
|
||||||
section_name);
|
idx, section_name);
|
||||||
goto errout;
|
goto errout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -742,6 +742,24 @@ bpf_object__init_maps(struct bpf_object *obj)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool section_have_execinstr(struct bpf_object *obj, int idx)
|
||||||
|
{
|
||||||
|
Elf_Scn *scn;
|
||||||
|
GElf_Shdr sh;
|
||||||
|
|
||||||
|
scn = elf_getscn(obj->efile.elf, idx);
|
||||||
|
if (!scn)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (gelf_getshdr(scn, &sh) != &sh)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (sh.sh_flags & SHF_EXECINSTR)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int bpf_object__elf_collect(struct bpf_object *obj)
|
static int bpf_object__elf_collect(struct bpf_object *obj)
|
||||||
{
|
{
|
||||||
Elf *elf = obj->efile.elf;
|
Elf *elf = obj->efile.elf;
|
||||||
@ -763,29 +781,29 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
|
|||||||
|
|
||||||
idx++;
|
idx++;
|
||||||
if (gelf_getshdr(scn, &sh) != &sh) {
|
if (gelf_getshdr(scn, &sh) != &sh) {
|
||||||
pr_warning("failed to get section header from %s\n",
|
pr_warning("failed to get section(%d) header from %s\n",
|
||||||
obj->path);
|
idx, obj->path);
|
||||||
err = -LIBBPF_ERRNO__FORMAT;
|
err = -LIBBPF_ERRNO__FORMAT;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = elf_strptr(elf, ep->e_shstrndx, sh.sh_name);
|
name = elf_strptr(elf, ep->e_shstrndx, sh.sh_name);
|
||||||
if (!name) {
|
if (!name) {
|
||||||
pr_warning("failed to get section name from %s\n",
|
pr_warning("failed to get section(%d) name from %s\n",
|
||||||
obj->path);
|
idx, obj->path);
|
||||||
err = -LIBBPF_ERRNO__FORMAT;
|
err = -LIBBPF_ERRNO__FORMAT;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = elf_getdata(scn, 0);
|
data = elf_getdata(scn, 0);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
pr_warning("failed to get section data from %s(%s)\n",
|
pr_warning("failed to get section(%d) data from %s(%s)\n",
|
||||||
name, obj->path);
|
idx, name, obj->path);
|
||||||
err = -LIBBPF_ERRNO__FORMAT;
|
err = -LIBBPF_ERRNO__FORMAT;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
pr_debug("section %s, size %ld, link %d, flags %lx, type=%d\n",
|
pr_debug("section(%d) %s, size %ld, link %d, flags %lx, type=%d\n",
|
||||||
name, (unsigned long)data->d_size,
|
idx, name, (unsigned long)data->d_size,
|
||||||
(int)sh.sh_link, (unsigned long)sh.sh_flags,
|
(int)sh.sh_link, (unsigned long)sh.sh_flags,
|
||||||
(int)sh.sh_type);
|
(int)sh.sh_type);
|
||||||
|
|
||||||
@ -825,6 +843,14 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
|
|||||||
} else if (sh.sh_type == SHT_REL) {
|
} else if (sh.sh_type == SHT_REL) {
|
||||||
void *reloc = obj->efile.reloc;
|
void *reloc = obj->efile.reloc;
|
||||||
int nr_reloc = obj->efile.nr_reloc + 1;
|
int nr_reloc = obj->efile.nr_reloc + 1;
|
||||||
|
int sec = sh.sh_info; /* points to other section */
|
||||||
|
|
||||||
|
/* Only do relo for section with exec instructions */
|
||||||
|
if (!section_have_execinstr(obj, sec)) {
|
||||||
|
pr_debug("skip relo %s(%d) for section(%d)\n",
|
||||||
|
name, idx, sec);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
reloc = realloc(reloc,
|
reloc = realloc(reloc,
|
||||||
sizeof(*obj->efile.reloc) * nr_reloc);
|
sizeof(*obj->efile.reloc) * nr_reloc);
|
||||||
@ -840,6 +866,8 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
|
|||||||
obj->efile.reloc[n].shdr = sh;
|
obj->efile.reloc[n].shdr = sh;
|
||||||
obj->efile.reloc[n].data = data;
|
obj->efile.reloc[n].data = data;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
pr_debug("skip section(%d) %s\n", idx, name);
|
||||||
}
|
}
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
@ -1119,8 +1147,7 @@ static int bpf_object__collect_reloc(struct bpf_object *obj)
|
|||||||
|
|
||||||
prog = bpf_object__find_prog_by_idx(obj, idx);
|
prog = bpf_object__find_prog_by_idx(obj, idx);
|
||||||
if (!prog) {
|
if (!prog) {
|
||||||
pr_warning("relocation failed: no %d section\n",
|
pr_warning("relocation failed: no section(%d)\n", idx);
|
||||||
idx);
|
|
||||||
return -LIBBPF_ERRNO__RELOC;
|
return -LIBBPF_ERRNO__RELOC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ endif
|
|||||||
CFLAGS += -Wall -O2 -I$(APIDIR) -I$(LIBDIR) -I$(GENDIR) $(GENFLAGS) -I../../../include
|
CFLAGS += -Wall -O2 -I$(APIDIR) -I$(LIBDIR) -I$(GENDIR) $(GENFLAGS) -I../../../include
|
||||||
LDLIBS += -lcap -lelf -lrt -lpthread
|
LDLIBS += -lcap -lelf -lrt -lpthread
|
||||||
|
|
||||||
|
# Order correspond to 'make run_tests' order
|
||||||
TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \
|
TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \
|
||||||
test_align test_verifier_log test_dev_cgroup test_tcpbpf_user
|
test_align test_verifier_log test_dev_cgroup test_tcpbpf_user
|
||||||
|
|
||||||
@ -22,15 +23,24 @@ TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test
|
|||||||
test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o \
|
test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o \
|
||||||
sample_map_ret0.o test_tcpbpf_kern.o
|
sample_map_ret0.o test_tcpbpf_kern.o
|
||||||
|
|
||||||
TEST_PROGS := test_kmod.sh test_xdp_redirect.sh test_xdp_meta.sh \
|
# Order correspond to 'make run_tests' order
|
||||||
|
TEST_PROGS := test_kmod.sh \
|
||||||
|
test_libbpf.sh \
|
||||||
|
test_xdp_redirect.sh \
|
||||||
|
test_xdp_meta.sh \
|
||||||
test_offload.py
|
test_offload.py
|
||||||
|
|
||||||
|
# Compile but not part of 'make run_tests'
|
||||||
|
TEST_GEN_PROGS_EXTENDED = test_libbpf_open
|
||||||
|
|
||||||
include ../lib.mk
|
include ../lib.mk
|
||||||
|
|
||||||
BPFOBJ := $(OUTPUT)/libbpf.a cgroup_helpers.c
|
BPFOBJ := $(OUTPUT)/libbpf.a cgroup_helpers.c
|
||||||
|
|
||||||
$(TEST_GEN_PROGS): $(BPFOBJ)
|
$(TEST_GEN_PROGS): $(BPFOBJ)
|
||||||
|
|
||||||
|
$(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/libbpf.a
|
||||||
|
|
||||||
.PHONY: force
|
.PHONY: force
|
||||||
|
|
||||||
# force a rebuild of BPFOBJ when its dependencies are updated
|
# force a rebuild of BPFOBJ when its dependencies are updated
|
||||||
|
49
tools/testing/selftests/bpf/test_libbpf.sh
Executable file
49
tools/testing/selftests/bpf/test_libbpf.sh
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
export TESTNAME=test_libbpf
|
||||||
|
|
||||||
|
# Determine selftest success via shell exit code
|
||||||
|
exit_handler()
|
||||||
|
{
|
||||||
|
if (( $? == 0 )); then
|
||||||
|
echo "selftests: $TESTNAME [PASS]";
|
||||||
|
else
|
||||||
|
echo "$TESTNAME: failed at file $LAST_LOADED" 1>&2
|
||||||
|
echo "selftests: $TESTNAME [FAILED]";
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
libbpf_open_file()
|
||||||
|
{
|
||||||
|
LAST_LOADED=$1
|
||||||
|
if [ -n "$VERBOSE" ]; then
|
||||||
|
./test_libbpf_open $1
|
||||||
|
else
|
||||||
|
./test_libbpf_open --quiet $1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exit script immediately (well catched by trap handler) if any
|
||||||
|
# program/thing exits with a non-zero status.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# (Use 'trap -l' to list meaning of numbers)
|
||||||
|
trap exit_handler 0 2 3 6 9
|
||||||
|
|
||||||
|
libbpf_open_file test_l4lb.o
|
||||||
|
|
||||||
|
# TODO: fix libbpf to load noinline functions
|
||||||
|
# [warning] libbpf: incorrect bpf_call opcode
|
||||||
|
#libbpf_open_file test_l4lb_noinline.o
|
||||||
|
|
||||||
|
# TODO: fix test_xdp_meta.c to load with libbpf
|
||||||
|
# [warning] libbpf: test_xdp_meta.o doesn't provide kernel version
|
||||||
|
#libbpf_open_file test_xdp_meta.o
|
||||||
|
|
||||||
|
# TODO: fix libbpf to handle .eh_frame
|
||||||
|
# [warning] libbpf: relocation failed: no section(10)
|
||||||
|
#libbpf_open_file ../../../../samples/bpf/tracex3_kern.o
|
||||||
|
|
||||||
|
# Success
|
||||||
|
exit 0
|
150
tools/testing/selftests/bpf/test_libbpf_open.c
Normal file
150
tools/testing/selftests/bpf/test_libbpf_open.c
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0
|
||||||
|
* Copyright (c) 2018 Jesper Dangaard Brouer, Red Hat Inc.
|
||||||
|
*/
|
||||||
|
static const char *__doc__ =
|
||||||
|
"Libbpf test program for loading BPF ELF object files";
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <bpf/libbpf.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, NULL, 'h' },
|
||||||
|
{"debug", no_argument, NULL, 'D' },
|
||||||
|
{"quiet", no_argument, NULL, 'q' },
|
||||||
|
{0, 0, NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static void usage(char *argv[])
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
printf("\nDOCUMENTATION:\n%s\n\n", __doc__);
|
||||||
|
printf(" Usage: %s (options-see-below) BPF_FILE\n", argv[0]);
|
||||||
|
printf(" Listing options:\n");
|
||||||
|
for (i = 0; long_options[i].name != 0; i++) {
|
||||||
|
printf(" --%-12s", long_options[i].name);
|
||||||
|
printf(" short-option: -%c",
|
||||||
|
long_options[i].val);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEFINE_PRINT_FN(name, enabled) \
|
||||||
|
static int libbpf_##name(const char *fmt, ...) \
|
||||||
|
{ \
|
||||||
|
va_list args; \
|
||||||
|
int ret; \
|
||||||
|
\
|
||||||
|
va_start(args, fmt); \
|
||||||
|
if (enabled) { \
|
||||||
|
fprintf(stderr, "[" #name "] "); \
|
||||||
|
ret = vfprintf(stderr, fmt, args); \
|
||||||
|
} \
|
||||||
|
va_end(args); \
|
||||||
|
return ret; \
|
||||||
|
}
|
||||||
|
DEFINE_PRINT_FN(warning, 1)
|
||||||
|
DEFINE_PRINT_FN(info, 1)
|
||||||
|
DEFINE_PRINT_FN(debug, 1)
|
||||||
|
|
||||||
|
#define EXIT_FAIL_LIBBPF EXIT_FAILURE
|
||||||
|
#define EXIT_FAIL_OPTION 2
|
||||||
|
|
||||||
|
int test_walk_progs(struct bpf_object *obj, bool verbose)
|
||||||
|
{
|
||||||
|
struct bpf_program *prog;
|
||||||
|
int cnt = 0;
|
||||||
|
|
||||||
|
bpf_object__for_each_program(prog, obj) {
|
||||||
|
cnt++;
|
||||||
|
if (verbose)
|
||||||
|
printf("Prog (count:%d) section_name: %s\n", cnt,
|
||||||
|
bpf_program__title(prog, false));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_walk_maps(struct bpf_object *obj, bool verbose)
|
||||||
|
{
|
||||||
|
struct bpf_map *map;
|
||||||
|
int cnt = 0;
|
||||||
|
|
||||||
|
bpf_map__for_each(map, obj) {
|
||||||
|
cnt++;
|
||||||
|
if (verbose)
|
||||||
|
printf("Map (count:%d) name: %s\n", cnt,
|
||||||
|
bpf_map__name(map));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_open_file(char *filename, bool verbose)
|
||||||
|
{
|
||||||
|
struct bpf_object *bpfobj = NULL;
|
||||||
|
long err;
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
printf("Open BPF ELF-file with libbpf: %s\n", filename);
|
||||||
|
|
||||||
|
/* Load BPF ELF object file and check for errors */
|
||||||
|
bpfobj = bpf_object__open(filename);
|
||||||
|
err = libbpf_get_error(bpfobj);
|
||||||
|
if (err) {
|
||||||
|
char err_buf[128];
|
||||||
|
libbpf_strerror(err, err_buf, sizeof(err_buf));
|
||||||
|
if (verbose)
|
||||||
|
printf("Unable to load eBPF objects in file '%s': %s\n",
|
||||||
|
filename, err_buf);
|
||||||
|
return EXIT_FAIL_LIBBPF;
|
||||||
|
}
|
||||||
|
test_walk_progs(bpfobj, verbose);
|
||||||
|
test_walk_maps(bpfobj, verbose);
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
printf("Close BPF ELF-file with libbpf: %s\n",
|
||||||
|
bpf_object__name(bpfobj));
|
||||||
|
bpf_object__close(bpfobj);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char filename[1024] = { 0 };
|
||||||
|
bool verbose = 1;
|
||||||
|
int longindex = 0;
|
||||||
|
int opt;
|
||||||
|
|
||||||
|
libbpf_set_print(libbpf_warning, libbpf_info, NULL);
|
||||||
|
|
||||||
|
/* Parse commands line args */
|
||||||
|
while ((opt = getopt_long(argc, argv, "hDq",
|
||||||
|
long_options, &longindex)) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'D':
|
||||||
|
libbpf_set_print(libbpf_warning, libbpf_info,
|
||||||
|
libbpf_debug);
|
||||||
|
break;
|
||||||
|
case 'q': /* Use in scripting mode */
|
||||||
|
verbose = 0;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
default:
|
||||||
|
usage(argv);
|
||||||
|
return EXIT_FAIL_OPTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (optind >= argc) {
|
||||||
|
usage(argv);
|
||||||
|
printf("ERROR: Expected BPF_FILE argument after options\n");
|
||||||
|
return EXIT_FAIL_OPTION;
|
||||||
|
}
|
||||||
|
snprintf(filename, sizeof(filename), "%s", argv[optind]);
|
||||||
|
|
||||||
|
return test_open_file(filename, verbose);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user