selftests/bpf: Add CO-RE relo test for TYPE_ID_LOCAL/TYPE_ID_TARGET

Add tests for BTF type ID relocations. To allow testing this, enhance
core_relo.c test runner to allow dynamic initialization of test inputs.
If Clang doesn't have necessary support for new functionality, test is
skipped.

Signed-off-by: Andrii Nakryiko <andriin@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Yonghong Song <yhs@fb.com>
Link: https://lore.kernel.org/bpf/20200819194519.3375898-4-andriin@fb.com
This commit is contained in:
Andrii Nakryiko 2020-08-19 12:45:17 -07:00 committed by Alexei Starovoitov
parent 124a892d1c
commit 4836bf5e2e
6 changed files with 323 additions and 19 deletions

View File

@ -3,6 +3,9 @@
#include "progs/core_reloc_types.h"
#include <sys/mman.h>
#include <sys/syscall.h>
#include <bpf/btf.h>
static int duration = 0;
#define STRUCT_TO_CHAR_PTR(struct_name) (const char *)&(struct struct_name)
@ -269,6 +272,27 @@
.fails = true, \
}
#define TYPE_ID_CASE_COMMON(name) \
.case_name = #name, \
.bpf_obj_file = "test_core_reloc_type_id.o", \
.btf_src_file = "btf__core_reloc_" #name ".o" \
#define TYPE_ID_CASE(name, setup_fn) { \
TYPE_ID_CASE_COMMON(name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_type_id_output) {}, \
.output_len = sizeof(struct core_reloc_type_id_output), \
.setup = setup_fn, \
}
#define TYPE_ID_ERR_CASE(name) { \
TYPE_ID_CASE_COMMON(name), \
.fails = true, \
}
struct core_reloc_test_case;
typedef int (*setup_test_fn)(struct core_reloc_test_case *test);
struct core_reloc_test_case {
const char *case_name;
const char *bpf_obj_file;
@ -280,8 +304,136 @@ struct core_reloc_test_case {
bool fails;
bool relaxed_core_relocs;
bool direct_raw_tp;
setup_test_fn setup;
};
static int find_btf_type(const struct btf *btf, const char *name, __u32 kind)
{
int id;
id = btf__find_by_name_kind(btf, name, kind);
if (CHECK(id <= 0, "find_type_id", "failed to find '%s', kind %d: %d\n", name, kind, id))
return -1;
return id;
}
static int setup_type_id_case_local(struct core_reloc_test_case *test)
{
struct core_reloc_type_id_output *exp = (void *)test->output;
struct btf *local_btf = btf__parse(test->bpf_obj_file, NULL);
struct btf *targ_btf = btf__parse(test->btf_src_file, NULL);
const struct btf_type *t;
const char *name;
int i;
if (CHECK(IS_ERR(local_btf), "local_btf", "failed: %ld\n", PTR_ERR(local_btf)) ||
CHECK(IS_ERR(targ_btf), "targ_btf", "failed: %ld\n", PTR_ERR(targ_btf))) {
btf__free(local_btf);
btf__free(targ_btf);
return -EINVAL;
}
exp->local_anon_struct = -1;
exp->local_anon_union = -1;
exp->local_anon_enum = -1;
exp->local_anon_func_proto_ptr = -1;
exp->local_anon_void_ptr = -1;
exp->local_anon_arr = -1;
for (i = 1; i <= btf__get_nr_types(local_btf); i++)
{
t = btf__type_by_id(local_btf, i);
/* we are interested only in anonymous types */
if (t->name_off)
continue;
if (btf_is_struct(t) && btf_vlen(t) &&
(name = btf__name_by_offset(local_btf, btf_members(t)[0].name_off)) &&
strcmp(name, "marker_field") == 0) {
exp->local_anon_struct = i;
} else if (btf_is_union(t) && btf_vlen(t) &&
(name = btf__name_by_offset(local_btf, btf_members(t)[0].name_off)) &&
strcmp(name, "marker_field") == 0) {
exp->local_anon_union = i;
} else if (btf_is_enum(t) && btf_vlen(t) &&
(name = btf__name_by_offset(local_btf, btf_enum(t)[0].name_off)) &&
strcmp(name, "MARKER_ENUM_VAL") == 0) {
exp->local_anon_enum = i;
} else if (btf_is_ptr(t) && (t = btf__type_by_id(local_btf, t->type))) {
if (btf_is_func_proto(t) && (t = btf__type_by_id(local_btf, t->type)) &&
btf_is_int(t) && (name = btf__name_by_offset(local_btf, t->name_off)) &&
strcmp(name, "_Bool") == 0) {
/* ptr -> func_proto -> _Bool */
exp->local_anon_func_proto_ptr = i;
} else if (btf_is_void(t)) {
/* ptr -> void */
exp->local_anon_void_ptr = i;
}
} else if (btf_is_array(t) && (t = btf__type_by_id(local_btf, btf_array(t)->type)) &&
btf_is_int(t) && (name = btf__name_by_offset(local_btf, t->name_off)) &&
strcmp(name, "_Bool") == 0) {
/* _Bool[] */
exp->local_anon_arr = i;
}
}
exp->local_struct = find_btf_type(local_btf, "a_struct", BTF_KIND_STRUCT);
exp->local_union = find_btf_type(local_btf, "a_union", BTF_KIND_UNION);
exp->local_enum = find_btf_type(local_btf, "an_enum", BTF_KIND_ENUM);
exp->local_int = find_btf_type(local_btf, "int", BTF_KIND_INT);
exp->local_struct_typedef = find_btf_type(local_btf, "named_struct_typedef", BTF_KIND_TYPEDEF);
exp->local_func_proto_typedef = find_btf_type(local_btf, "func_proto_typedef", BTF_KIND_TYPEDEF);
exp->local_arr_typedef = find_btf_type(local_btf, "arr_typedef", BTF_KIND_TYPEDEF);
btf__free(local_btf);
btf__free(targ_btf);
return 0;
}
static int setup_type_id_case_success(struct core_reloc_test_case *test) {
struct core_reloc_type_id_output *exp = (void *)test->output;
struct btf *targ_btf = btf__parse(test->btf_src_file, NULL);
int err;
err = setup_type_id_case_local(test);
if (err)
return err;
targ_btf = btf__parse(test->btf_src_file, NULL);
exp->targ_struct = find_btf_type(targ_btf, "a_struct", BTF_KIND_STRUCT);
exp->targ_union = find_btf_type(targ_btf, "a_union", BTF_KIND_UNION);
exp->targ_enum = find_btf_type(targ_btf, "an_enum", BTF_KIND_ENUM);
exp->targ_int = find_btf_type(targ_btf, "int", BTF_KIND_INT);
exp->targ_struct_typedef = find_btf_type(targ_btf, "named_struct_typedef", BTF_KIND_TYPEDEF);
exp->targ_func_proto_typedef = find_btf_type(targ_btf, "func_proto_typedef", BTF_KIND_TYPEDEF);
exp->targ_arr_typedef = find_btf_type(targ_btf, "arr_typedef", BTF_KIND_TYPEDEF);
btf__free(targ_btf);
return 0;
}
static int setup_type_id_case_failure(struct core_reloc_test_case *test)
{
struct core_reloc_type_id_output *exp = (void *)test->output;
int err;
err = setup_type_id_case_local(test);
if (err)
return err;
exp->targ_struct = 0;
exp->targ_union = 0;
exp->targ_enum = 0;
exp->targ_int = 0;
exp->targ_struct_typedef = 0;
exp->targ_func_proto_typedef = 0;
exp->targ_arr_typedef = 0;
return 0;
}
static struct core_reloc_test_case test_cases[] = {
/* validate we can find kernel image and use its BTF for relocs */
{
@ -530,6 +682,10 @@ static struct core_reloc_test_case test_cases[] = {
.struct_exists = 1,
.struct_sz = sizeof(struct a_struct),
}),
/* BTF_TYPE_ID_LOCAL/BTF_TYPE_ID_TARGET tests */
TYPE_ID_CASE(type_id, setup_type_id_case_success),
TYPE_ID_CASE(type_id___missing_targets, setup_type_id_case_failure),
};
struct data {
@ -551,7 +707,7 @@ void test_core_reloc(void)
struct bpf_object_load_attr load_attr = {};
struct core_reloc_test_case *test_case;
const char *tp_name, *probe_name;
int err, duration = 0, i, equal;
int err, i, equal;
struct bpf_link *link = NULL;
struct bpf_map *data_map;
struct bpf_program *prog;
@ -567,11 +723,13 @@ void test_core_reloc(void)
if (!test__start_subtest(test_case->case_name))
continue;
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
.relaxed_core_relocs = test_case->relaxed_core_relocs,
);
if (test_case->setup) {
err = test_case->setup(test_case);
if (CHECK(err, "test_setup", "test #%d setup failed: %d\n", i, err))
continue;
}
obj = bpf_object__open_file(test_case->bpf_obj_file, &opts);
obj = bpf_object__open_file(test_case->bpf_obj_file, NULL);
if (CHECK(IS_ERR(obj), "obj_open", "failed to open '%s': %ld\n",
test_case->bpf_obj_file, PTR_ERR(obj)))
continue;

View File

@ -0,0 +1,3 @@
#include "core_reloc_types.h"
void f(struct core_reloc_type_id x) {}

View File

@ -0,0 +1,3 @@
#include "core_reloc_types.h"
void f(struct core_reloc_type_id___missing_targets x) {}

View File

@ -1034,3 +1034,44 @@ struct core_reloc_type_based___fn_wrong_args {
func_proto_typedef___fn_wrong_arg_cnt1 f6;
func_proto_typedef___fn_wrong_arg_cnt2 f7;
};
/*
* TYPE ID MAPPING (LOCAL AND TARGET)
*/
struct core_reloc_type_id_output {
int local_anon_struct;
int local_anon_union;
int local_anon_enum;
int local_anon_func_proto_ptr;
int local_anon_void_ptr;
int local_anon_arr;
int local_struct;
int local_union;
int local_enum;
int local_int;
int local_struct_typedef;
int local_func_proto_typedef;
int local_arr_typedef;
int targ_struct;
int targ_union;
int targ_enum;
int targ_int;
int targ_struct_typedef;
int targ_func_proto_typedef;
int targ_arr_typedef;
};
struct core_reloc_type_id {
struct a_struct f1;
union a_union f2;
enum an_enum f3;
named_struct_typedef f4;
func_proto_typedef f5;
arr_typedef f6;
};
struct core_reloc_type_id___missing_targets {
/* nothing */
};

View File

@ -48,20 +48,6 @@ typedef int (*func_proto_typedef)(long);
typedef char arr_typedef[20];
struct core_reloc_type_based {
struct a_struct f1;
union a_union f2;
enum an_enum f3;
named_struct_typedef f4;
anon_struct_typedef f5;
struct_ptr_typedef f6;
int_typedef f7;
enum_typedef f8;
void_ptr_typedef f9;
func_proto_typedef f10;
arr_typedef f11;
};
struct core_reloc_type_based_output {
bool struct_exists;
bool union_exists;

View File

@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2020 Facebook
#include <linux/bpf.h>
#include <stdint.h>
#include <stdbool.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
char _license[] SEC("license") = "GPL";
struct {
char in[256];
char out[256];
bool skip;
} data = {};
/* some types are shared with test_core_reloc_type_based.c */
struct a_struct {
int x;
};
union a_union {
int y;
int z;
};
enum an_enum {
AN_ENUM_VAL1 = 1,
AN_ENUM_VAL2 = 2,
AN_ENUM_VAL3 = 3,
};
typedef struct a_struct named_struct_typedef;
typedef int (*func_proto_typedef)(long);
typedef char arr_typedef[20];
struct core_reloc_type_id_output {
int local_anon_struct;
int local_anon_union;
int local_anon_enum;
int local_anon_func_proto_ptr;
int local_anon_void_ptr;
int local_anon_arr;
int local_struct;
int local_union;
int local_enum;
int local_int;
int local_struct_typedef;
int local_func_proto_typedef;
int local_arr_typedef;
int targ_struct;
int targ_union;
int targ_enum;
int targ_int;
int targ_struct_typedef;
int targ_func_proto_typedef;
int targ_arr_typedef;
};
/* preserve types even if Clang doesn't support built-in */
struct a_struct t1 = {};
union a_union t2 = {};
enum an_enum t3 = 0;
named_struct_typedef t4 = {};
func_proto_typedef t5 = 0;
arr_typedef t6 = {};
SEC("raw_tracepoint/sys_enter")
int test_core_type_id(void *ctx)
{
/* We use __builtin_btf_type_id() in this tests, but up until the time
* __builtin_preserve_type_info() was added it contained a bug that
* would make this test fail. The bug was fixed with addition of
* __builtin_preserve_type_info(), though, so that's what we are using
* to detect whether this test has to be executed, however strange
* that might look like.
*/
#if __has_builtin(__builtin_preserve_type_info)
struct core_reloc_type_id_output *out = (void *)&data.out;
out->local_anon_struct = bpf_core_type_id_local(struct { int marker_field; });
out->local_anon_union = bpf_core_type_id_local(union { int marker_field; });
out->local_anon_enum = bpf_core_type_id_local(enum { MARKER_ENUM_VAL = 123 });
out->local_anon_func_proto_ptr = bpf_core_type_id_local(_Bool(*)(int));
out->local_anon_void_ptr = bpf_core_type_id_local(void *);
out->local_anon_arr = bpf_core_type_id_local(_Bool[47]);
out->local_struct = bpf_core_type_id_local(struct a_struct);
out->local_union = bpf_core_type_id_local(union a_union);
out->local_enum = bpf_core_type_id_local(enum an_enum);
out->local_int = bpf_core_type_id_local(int);
out->local_struct_typedef = bpf_core_type_id_local(named_struct_typedef);
out->local_func_proto_typedef = bpf_core_type_id_local(func_proto_typedef);
out->local_arr_typedef = bpf_core_type_id_local(arr_typedef);
out->targ_struct = bpf_core_type_id_kernel(struct a_struct);
out->targ_union = bpf_core_type_id_kernel(union a_union);
out->targ_enum = bpf_core_type_id_kernel(enum an_enum);
out->targ_int = bpf_core_type_id_kernel(int);
out->targ_struct_typedef = bpf_core_type_id_kernel(named_struct_typedef);
out->targ_func_proto_typedef = bpf_core_type_id_kernel(func_proto_typedef);
out->targ_arr_typedef = bpf_core_type_id_kernel(arr_typedef);
#else
data.skip = true;
#endif
return 0;
}