// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <test_progs.h>
#include "progs/core_reloc_types.h"
#include "test_kmods/bpf_testmod.h"
#include <linux/limits.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)
#define MODULES_CASE(name, pg_name, tp_name) { \
.case_name = name, \
.bpf_obj_file = "test_core_reloc_module.bpf.o" , \
.btf_src_file = NULL, /* find in kernel module BTFs */ \
.input = "" , \
.input_len = 0 , \
.output = STRUCT_TO_CHAR_PTR(core_reloc_module_output) { \
.read_ctx_sz = sizeof (struct bpf_testmod_test_read_ctx),\
.read_ctx_exists = true , \
.buf_exists = true , \
.len_exists = true , \
.off_exists = true , \
.len = 123 , \
.off = 0 , \
.comm = "test_progs" , \
.comm_len = sizeof ("test_progs" ), \
}, \
.output_len = sizeof (struct core_reloc_module_output), \
.prog_name = pg_name, \
.raw_tp_name = tp_name, \
.trigger = __trigger_module_test_read, \
.needs_testmod = true , \
}
#define FLAVORS_DATA(struct_name) STRUCT_TO_CHAR_PTR(struct_name) { \
.a = 42 , \
.b = 0 xc001, \
.c = 0 xbeef, \
}
#define FLAVORS_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_flavors.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_flavors" \
#define FLAVORS_CASE(name) { \
FLAVORS_CASE_COMMON(name), \
.input = FLAVORS_DATA(core_reloc_## name), \
.input_len = sizeof (struct core_reloc_## name), \
.output = FLAVORS_DATA(core_reloc_flavors), \
.output_len = sizeof (struct core_reloc_flavors), \
}
#define FLAVORS_ERR_CASE(name) { \
FLAVORS_CASE_COMMON(name), \
.fails = true , \
}
#define NESTING_DATA(struct_name) STRUCT_TO_CHAR_PTR(struct_name) { \
.a = { .a = { .a = 42 } }, \
.b = { .b = { .b = 0 xc001 } }, \
}
#define NESTING_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_nesting.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_nesting" \
#define NESTING_CASE(name) { \
NESTING_CASE_COMMON(name), \
.input = NESTING_DATA(core_reloc_## name), \
.input_len = sizeof (struct core_reloc_## name), \
.output = NESTING_DATA(core_reloc_nesting), \
.output_len = sizeof (struct core_reloc_nesting) \
}
#define NESTING_ERR_CASE(name) { \
NESTING_CASE_COMMON(name), \
.fails = true , \
.run_btfgen_fails = true , \
}
#define ARRAYS_DATA(struct_name) STRUCT_TO_CHAR_PTR(struct_name) { \
.a = { [2 ] = 1 , [3 ] = 11 }, \
.b = { [1 ] = { [2 ] = { [3 ] = 2 } } }, \
.c = { [1 ] = { .c = 3 } }, \
.d = { [0 ] = { [0 ] = { .d = 4 } } }, \
}
#define ARRAYS_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_arrays.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_arrays" \
#define ARRAYS_CASE(name) { \
ARRAYS_CASE_COMMON(name), \
.input = ARRAYS_DATA(core_reloc_## name), \
.input_len = sizeof (struct core_reloc_## name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_arrays_output) { \
.a2 = 1 , \
.a3 = 12 , \
.b123 = 2 , \
.c1c = 3 , \
.d00d = 4 , \
.f10c = 0 , \
}, \
.output_len = sizeof (struct core_reloc_arrays_output) \
}
#define ARRAYS_ERR_CASE(name) { \
ARRAYS_CASE_COMMON(name), \
.fails = true , \
}
#define PRIMITIVES_DATA(struct_name) STRUCT_TO_CHAR_PTR(struct_name) { \
.a = 1 , \
.b = 2 , \
.c = 3 , \
.d = (void *)4 , \
.f = (void *)5 , \
}
#define PRIMITIVES_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_primitives.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_primitives" \
#define PRIMITIVES_CASE(name) { \
PRIMITIVES_CASE_COMMON(name), \
.input = PRIMITIVES_DATA(core_reloc_## name), \
.input_len = sizeof (struct core_reloc_## name), \
.output = PRIMITIVES_DATA(core_reloc_primitives), \
.output_len = sizeof (struct core_reloc_primitives), \
}
#define PRIMITIVES_ERR_CASE(name) { \
PRIMITIVES_CASE_COMMON(name), \
.fails = true , \
}
#define MODS_CASE(name) { \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_mods.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.input = STRUCT_TO_CHAR_PTR(core_reloc_## name) { \
.a = 1 , \
.b = 2 , \
.c = (void *)3 , \
.d = (void *)4 , \
.e = { [2 ] = 5 }, \
.f = { [1 ] = 6 }, \
.g = { .x = 7 }, \
.h = { .y = 8 }, \
}, \
.input_len = sizeof (struct core_reloc_## name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_mods_output) { \
.a = 1 , .b = 2 , .c = 3 , .d = 4 , \
.e = 5 , .f = 6 , .g = 7 , .h = 8 , \
}, \
.output_len = sizeof (struct core_reloc_mods_output), \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_mods" , \
}
#define PTR_AS_ARR_CASE(name) { \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_ptr_as_arr.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.input = (const char *)&(struct core_reloc_## name []){ \
{ .a = 1 }, \
{ .a = 2 }, \
{ .a = 3 }, \
}, \
.input_len = 3 * sizeof (struct core_reloc_## name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_ptr_as_arr) { \
.a = 3 , \
}, \
.output_len = sizeof (struct core_reloc_ptr_as_arr), \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_ptr_as_arr" , \
}
#define INTS_DATA(struct_name) STRUCT_TO_CHAR_PTR(struct_name) { \
.u8_field = 1 , \
.s8_field = 2 , \
.u16_field = 3 , \
.s16_field = 4 , \
.u32_field = 5 , \
.s32_field = 6 , \
.u64_field = 7 , \
.s64_field = 8 , \
}
#define INTS_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_ints.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_ints"
#define INTS_CASE(name) { \
INTS_CASE_COMMON(name), \
.input = INTS_DATA(core_reloc_## name), \
.input_len = sizeof (struct core_reloc_## name), \
.output = INTS_DATA(core_reloc_ints), \
.output_len = sizeof (struct core_reloc_ints), \
}
#define INTS_ERR_CASE(name) { \
INTS_CASE_COMMON(name), \
.fails = true , \
}
#define FIELD_EXISTS_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_existence.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_existence"
#define BITFIELDS_CASE_COMMON(objfile, test_name_prefix, name) \
.case_name = test_name_prefix#name , \
.bpf_obj_file = objfile, \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o"
#define BITFIELDS_CASE(name, ...) { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_probed.bpf.o" , \
"probed:" , name), \
.input = STRUCT_TO_CHAR_PTR(core_reloc_## name) __VA_ARGS__, \
.input_len = sizeof (struct core_reloc_## name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_bitfields_output) \
__VA_ARGS__, \
.output_len = sizeof (struct core_reloc_bitfields_output), \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_bitfields" , \
}, { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_direct.bpf.o" , \
"direct:" , name), \
.input = STRUCT_TO_CHAR_PTR(core_reloc_## name) __VA_ARGS__, \
.input_len = sizeof (struct core_reloc_## name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_bitfields_output) \
__VA_ARGS__, \
.output_len = sizeof (struct core_reloc_bitfields_output), \
.prog_name = "test_core_bitfields_direct" , \
}
#define BITFIELDS_ERR_CASE(name) { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_probed.bpf.o" , \
"probed:" , name), \
.fails = true , \
.run_btfgen_fails = true , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_bitfields" , \
}, { \
BITFIELDS_CASE_COMMON("test_core_reloc_bitfields_direct.bpf.o" , \
"direct:" , name), \
.fails = true , \
.run_btfgen_fails = true , \
.prog_name = "test_core_bitfields_direct" , \
}
#define SIZE_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_size.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_size"
#define SIZE_OUTPUT_DATA(type) \
STRUCT_TO_CHAR_PTR(core_reloc_size_output) { \
.int_sz = sizeof (((type *)0 )->int_field), \
.int_off = offsetof(type, int_field), \
.struct_sz = sizeof (((type *)0 )->struct_field), \
.struct_off = offsetof(type, struct_field), \
.union_sz = sizeof (((type *)0 )->union_field), \
.union_off = offsetof(type, union_field), \
.arr_sz = sizeof (((type *)0 )->arr_field), \
.arr_off = offsetof(type, arr_field), \
.arr_elem_sz = sizeof (((type *)0 )->arr_field[1 ]), \
.arr_elem_off = offsetof(type, arr_field[1 ]), \
.ptr_sz = 8 , /* always 8-byte pointer for BPF */ \
.ptr_off = offsetof(type, ptr_field), \
.enum_sz = sizeof (((type *)0 )->enum_field), \
.enum_off = offsetof(type, enum_field), \
.float_sz = sizeof (((type *)0 )->float_field), \
.float_off = offsetof(type, float_field), \
}
#define SIZE_CASE(name) { \
SIZE_CASE_COMMON(name), \
.input_len = 0 , \
.output = SIZE_OUTPUT_DATA(struct core_reloc_## name), \
.output_len = sizeof (struct core_reloc_size_output), \
}
#define SIZE_ERR_CASE(name) { \
SIZE_CASE_COMMON(name), \
.fails = true , \
.run_btfgen_fails = true , \
}
#define TYPE_BASED_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_type_based.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_type_based"
#define TYPE_BASED_CASE(name, ...) { \
TYPE_BASED_CASE_COMMON(name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_type_based_output) \
__VA_ARGS__, \
.output_len = sizeof (struct core_reloc_type_based_output), \
}
#define TYPE_BASED_ERR_CASE(name) { \
TYPE_BASED_CASE_COMMON(name), \
.fails = true , \
}
#define TYPE_ID_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_type_id.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_type_id"
#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 , \
}
#define ENUMVAL_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_enumval.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_enumval"
#define ENUMVAL_CASE(name, ...) { \
ENUMVAL_CASE_COMMON(name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_enumval_output) \
__VA_ARGS__, \
.output_len = sizeof (struct core_reloc_enumval_output), \
}
#define ENUMVAL_ERR_CASE(name) { \
ENUMVAL_CASE_COMMON(name), \
.fails = true , \
}
#define ENUM64VAL_CASE_COMMON(name) \
.case_name = #name , \
.bpf_obj_file = "test_core_reloc_enum64val.bpf.o" , \
.btf_src_file = "btf__core_reloc_" #name ".bpf.o" , \
.raw_tp_name = "sys_enter" , \
.prog_name = "test_core_enum64val"
#define ENUM64VAL_CASE(name, ...) { \
ENUM64VAL_CASE_COMMON(name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_enum64val_output) \
__VA_ARGS__, \
.output_len = sizeof (struct core_reloc_enum64val_output), \
}
#define ENUM64VAL_ERR_CASE(name) { \
ENUM64VAL_CASE_COMMON(name), \
.fails = true , \
}
struct core_reloc_test_case;
typedef int (*setup_test_fn)(struct core_reloc_test_case *test);
typedef int (*trigger_test_fn)(const struct core_reloc_test_case *test);
struct core_reloc_test_case {
const char *case_name;
const char *bpf_obj_file;
const char *btf_src_file;
const char *input;
int input_len;
const char *output;
int output_len;
bool fails;
bool run_btfgen_fails;
bool needs_testmod;
bool relaxed_core_relocs;
const char *prog_name;
const char *raw_tp_name;
setup_test_fn setup;
trigger_test_fn trigger;
};
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 (!ASSERT_OK_PTR(local_btf, "local_btf" ) || !ASSERT_OK_PTR(targ_btf, "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__type_cnt(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;
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 int __trigger_module_test_read(const struct core_reloc_test_case *test)
{
struct core_reloc_module_output *exp = (void *)test->output;
trigger_module_test_read(exp->len);
return 0 ;
}
static const struct core_reloc_test_case test_cases[] = {
/* validate we can find kernel image and use its BTF for relocs */
{
.case_name = "kernel" ,
.bpf_obj_file = "test_core_reloc_kernel.bpf.o" ,
.btf_src_file = NULL, /* load from /lib/modules/$(uname -r) */
.input = "" ,
.input_len = 0 ,
.output = STRUCT_TO_CHAR_PTR(core_reloc_kernel_output) {
.valid = { 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , },
.comm = "test_progs" ,
.comm_len = sizeof ("test_progs" ),
.local_task_struct_matches = true ,
},
.output_len = sizeof (struct core_reloc_kernel_output),
.raw_tp_name = "sys_enter" ,
.prog_name = "test_core_kernel" ,
},
/* validate we can find kernel module BTF types for relocs/attach */
MODULES_CASE("module_probed" , "test_core_module_probed" , "bpf_testmod_test_read" ),
MODULES_CASE("module_direct" , "test_core_module_direct" , NULL),
/* validate BPF program can use multiple flavors to match against
* single target BTF type
*/
FLAVORS_CASE(flavors),
FLAVORS_ERR_CASE(flavors__err_wrong_name),
/* various struct/enum nesting and resolution scenarios */
NESTING_CASE(nesting),
NESTING_CASE(nesting___anon_embed),
NESTING_CASE(nesting___struct_union_mixup),
NESTING_CASE(nesting___extra_nesting),
NESTING_CASE(nesting___dup_compat_types),
NESTING_ERR_CASE(nesting___err_missing_field),
NESTING_ERR_CASE(nesting___err_array_field),
NESTING_ERR_CASE(nesting___err_missing_container),
NESTING_ERR_CASE(nesting___err_nonstruct_container),
NESTING_ERR_CASE(nesting___err_array_container),
NESTING_ERR_CASE(nesting___err_dup_incompat_types),
NESTING_ERR_CASE(nesting___err_partial_match_dups),
NESTING_ERR_CASE(nesting___err_too_deep),
/* various array access relocation scenarios */
ARRAYS_CASE(arrays),
ARRAYS_CASE(arrays___diff_arr_dim),
ARRAYS_CASE(arrays___diff_arr_val_sz),
ARRAYS_CASE(arrays___equiv_zero_sz_arr),
ARRAYS_CASE(arrays___fixed_arr),
ARRAYS_ERR_CASE(arrays___err_too_small),
ARRAYS_ERR_CASE(arrays___err_too_shallow),
ARRAYS_ERR_CASE(arrays___err_non_array),
ARRAYS_ERR_CASE(arrays___err_wrong_val_type),
ARRAYS_ERR_CASE(arrays___err_bad_zero_sz_arr),
ARRAYS_ERR_CASE(arrays___err_bad_signed_arr_elem_sz),
/* enum/ptr/int handling scenarios */
PRIMITIVES_CASE(primitives),
PRIMITIVES_CASE(primitives___diff_enum_def),
PRIMITIVES_CASE(primitives___diff_func_proto),
PRIMITIVES_CASE(primitives___diff_ptr_type),
PRIMITIVES_ERR_CASE(primitives___err_non_enum),
PRIMITIVES_ERR_CASE(primitives___err_non_int),
PRIMITIVES_ERR_CASE(primitives___err_non_ptr),
/* const/volatile/restrict and typedefs scenarios */
MODS_CASE(mods),
MODS_CASE(mods___mod_swap),
MODS_CASE(mods___typedefs),
/* handling "ptr is an array" semantics */
PTR_AS_ARR_CASE(ptr_as_arr),
PTR_AS_ARR_CASE(ptr_as_arr___diff_sz),
/* int signedness/sizing/bitfield handling */
INTS_CASE(ints),
INTS_CASE(ints___bool),
INTS_CASE(ints___reverse_sign),
/* validate edge cases of capturing relocations */
{
.case_name = "misc" ,
.bpf_obj_file = "test_core_reloc_misc.bpf.o" ,
.btf_src_file = "btf__core_reloc_misc.bpf.o" ,
.input = (const char *)&(struct core_reloc_misc_extensible[]){
{ .a = 1 },
{ .a = 2 }, /* not read */
{ .a = 3 },
},
.input_len = 4 * sizeof (int ),
.output = STRUCT_TO_CHAR_PTR(core_reloc_misc_output) {
.a = 1 ,
.b = 1 ,
.c = 0 , /* BUG in clang, should be 3 */
},
.output_len = sizeof (struct core_reloc_misc_output),
.raw_tp_name = "sys_enter" ,
.prog_name = "test_core_misc" ,
},
/* validate field existence checks */
{
FIELD_EXISTS_CASE_COMMON(existence),
.input = STRUCT_TO_CHAR_PTR(core_reloc_existence) {
.a = 1 ,
.b = 2 ,
.c = 3 ,
.arr = { 4 },
.s = { .x = 5 },
},
.input_len = sizeof (struct core_reloc_existence),
.output = STRUCT_TO_CHAR_PTR(core_reloc_existence_output) {
.a_exists = 1 ,
.b_exists = 1 ,
.c_exists = 1 ,
.arr_exists = 1 ,
.s_exists = 1 ,
.a_value = 1 ,
.b_value = 2 ,
.c_value = 3 ,
.arr_value = 4 ,
.s_value = 5 ,
},
.output_len = sizeof (struct core_reloc_existence_output),
},
{
FIELD_EXISTS_CASE_COMMON(existence___minimal),
.input = STRUCT_TO_CHAR_PTR(core_reloc_existence___minimal) {
.a = 42 ,
},
.input_len = sizeof (struct core_reloc_existence___minimal),
.output = STRUCT_TO_CHAR_PTR(core_reloc_existence_output) {
.a_exists = 1 ,
.b_exists = 0 ,
.c_exists = 0 ,
.arr_exists = 0 ,
.s_exists = 0 ,
.a_value = 42 ,
.b_value = 0 xff000002u,
.c_value = 0 xff000003u,
.arr_value = 0 xff000004u,
.s_value = 0 xff000005u,
},
.output_len = sizeof (struct core_reloc_existence_output),
},
{
FIELD_EXISTS_CASE_COMMON(existence___wrong_field_defs),
.input = STRUCT_TO_CHAR_PTR(core_reloc_existence___wrong_field_defs) {
},
.input_len = sizeof (struct core_reloc_existence___wrong_field_defs),
.output = STRUCT_TO_CHAR_PTR(core_reloc_existence_output) {
.a_exists = 0 ,
.b_exists = 0 ,
.c_exists = 0 ,
.arr_exists = 0 ,
.s_exists = 0 ,
.a_value = 0 xff000001u,
.b_value = 0 xff000002u,
.c_value = 0 xff000003u,
.arr_value = 0 xff000004u,
.s_value = 0 xff000005u,
},
.output_len = sizeof (struct core_reloc_existence_output),
},
/* bitfield relocation checks */
BITFIELDS_CASE(bitfields, {
.ub1 = 1 ,
.ub2 = 2 ,
.ub7 = 96 ,
.sb4 = -7 ,
.sb20 = -0 x76543,
.u32 = 0 x80000000,
.s32 = -0 x76543210,
}),
BITFIELDS_CASE(bitfields___bit_sz_change, {
.ub1 = 6 ,
.ub2 = 0 xABCDE,
.ub7 = 1 ,
.sb4 = -1 ,
.sb20 = -0 x17654321,
.u32 = 0 xBEEF,
.s32 = -0 x3FEDCBA987654321LL,
}),
BITFIELDS_CASE(bitfields___bitfield_vs_int, {
.ub1 = 0 xFEDCBA9876543210LL,
.ub2 = 0 xA6,
.ub7 = -0 x7EDCBA987654321LL,
.sb4 = -0 x6123456789ABCDELL,
.sb20 = 0 xD00DLL,
.u32 = -0 x76543,
.s32 = 0 x0ADEADBEEFBADB0BLL,
}),
BITFIELDS_CASE(bitfields___just_big_enough, {
.ub1 = 0 xFLL,
.ub2 = 0 x0812345678FEDCBALL,
}),
BITFIELDS_ERR_CASE(bitfields___err_too_big_bitfield),
/* field size and offset relocation checks */
SIZE_CASE(size),
SIZE_CASE(size___diff_sz),
SIZE_CASE(size___diff_offs),
SIZE_ERR_CASE(size___err_ambiguous),
/* validate type existence, match, and size relocations */
TYPE_BASED_CASE(type_based, {
.struct_exists = 1 ,
.complex_struct_exists = 1 ,
.union_exists = 1 ,
.enum_exists = 1 ,
.typedef_named_struct_exists = 1 ,
.typedef_anon_struct_exists = 1 ,
.typedef_struct_ptr_exists = 1 ,
.typedef_int_exists = 1 ,
.typedef_enum_exists = 1 ,
.typedef_void_ptr_exists = 1 ,
.typedef_restrict_ptr_exists = 1 ,
.typedef_func_proto_exists = 1 ,
.typedef_arr_exists = 1 ,
.struct_matches = 1 ,
.complex_struct_matches = 1 ,
.union_matches = 1 ,
.enum_matches = 1 ,
.typedef_named_struct_matches = 1 ,
.typedef_anon_struct_matches = 1 ,
.typedef_struct_ptr_matches = 1 ,
.typedef_int_matches = 1 ,
.typedef_enum_matches = 1 ,
.typedef_void_ptr_matches = 1 ,
.typedef_restrict_ptr_matches = 1 ,
.typedef_func_proto_matches = 1 ,
.typedef_arr_matches = 1 ,
.struct_sz = sizeof (struct a_struct),
.union_sz = sizeof (union a_union),
.enum_sz = sizeof (enum an_enum),
.typedef_named_struct_sz = sizeof (named_struct_typedef),
.typedef_anon_struct_sz = sizeof (anon_struct_typedef),
.typedef_struct_ptr_sz = sizeof (struct_ptr_typedef),
.typedef_int_sz = sizeof (int_typedef),
.typedef_enum_sz = sizeof (enum_typedef),
.typedef_void_ptr_sz = sizeof (void_ptr_typedef),
.typedef_func_proto_sz = sizeof (func_proto_typedef),
.typedef_arr_sz = sizeof (arr_typedef),
}),
TYPE_BASED_CASE(type_based___all_missing, {
/* all zeros */
}),
TYPE_BASED_CASE(type_based___diff, {
.struct_exists = 1 ,
.complex_struct_exists = 1 ,
.union_exists = 1 ,
.enum_exists = 1 ,
.typedef_named_struct_exists = 1 ,
.typedef_anon_struct_exists = 1 ,
.typedef_struct_ptr_exists = 1 ,
.typedef_int_exists = 1 ,
.typedef_enum_exists = 1 ,
.typedef_void_ptr_exists = 1 ,
.typedef_func_proto_exists = 1 ,
.typedef_arr_exists = 1 ,
.struct_matches = 1 ,
.complex_struct_matches = 1 ,
.union_matches = 1 ,
.enum_matches = 1 ,
.typedef_named_struct_matches = 1 ,
.typedef_anon_struct_matches = 1 ,
.typedef_struct_ptr_matches = 1 ,
.typedef_int_matches = 0 ,
.typedef_enum_matches = 1 ,
.typedef_void_ptr_matches = 1 ,
.typedef_func_proto_matches = 0 ,
.typedef_arr_matches = 0 ,
.struct_sz = sizeof (struct a_struct___diff),
.union_sz = sizeof (union a_union___diff),
.enum_sz = sizeof (enum an_enum___diff),
.typedef_named_struct_sz = sizeof (named_struct_typedef___diff),
.typedef_anon_struct_sz = sizeof (anon_struct_typedef___diff),
.typedef_struct_ptr_sz = sizeof (struct_ptr_typedef___diff),
.typedef_int_sz = sizeof (int_typedef___diff),
.typedef_enum_sz = sizeof (enum_typedef___diff),
.typedef_void_ptr_sz = sizeof (void_ptr_typedef___diff),
.typedef_func_proto_sz = sizeof (func_proto_typedef___diff),
.typedef_arr_sz = sizeof (arr_typedef___diff),
}),
TYPE_BASED_CASE(type_based___diff_sz, {
.struct_exists = 1 ,
.union_exists = 1 ,
.enum_exists = 1 ,
.typedef_named_struct_exists = 1 ,
.typedef_anon_struct_exists = 1 ,
.typedef_struct_ptr_exists = 1 ,
.typedef_int_exists = 1 ,
.typedef_enum_exists = 1 ,
.typedef_void_ptr_exists = 1 ,
.typedef_func_proto_exists = 1 ,
.typedef_arr_exists = 1 ,
.struct_matches = 0 ,
.union_matches = 0 ,
.enum_matches = 0 ,
.typedef_named_struct_matches = 0 ,
.typedef_anon_struct_matches = 0 ,
.typedef_struct_ptr_matches = 1 ,
.typedef_int_matches = 0 ,
.typedef_enum_matches = 0 ,
.typedef_void_ptr_matches = 1 ,
.typedef_func_proto_matches = 0 ,
.typedef_arr_matches = 0 ,
.struct_sz = sizeof (struct a_struct___diff_sz),
.union_sz = sizeof (union a_union___diff_sz),
.enum_sz = sizeof (enum an_enum___diff_sz),
.typedef_named_struct_sz = sizeof (named_struct_typedef___diff_sz),
.typedef_anon_struct_sz = sizeof (anon_struct_typedef___diff_sz),
.typedef_struct_ptr_sz = sizeof (struct_ptr_typedef___diff_sz),
.typedef_int_sz = sizeof (int_typedef___diff_sz),
.typedef_enum_sz = sizeof (enum_typedef___diff_sz),
.typedef_void_ptr_sz = sizeof (void_ptr_typedef___diff_sz),
.typedef_func_proto_sz = sizeof (func_proto_typedef___diff_sz),
.typedef_arr_sz = sizeof (arr_typedef___diff_sz),
}),
TYPE_BASED_CASE(type_based___incompat, {
.enum_exists = 1 ,
.enum_matches = 1 ,
.enum_sz = sizeof (enum an_enum),
}),
TYPE_BASED_CASE(type_based___fn_wrong_args, {
.struct_exists = 1 ,
.struct_matches = 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),
/* Enumerator value existence and value relocations */
ENUMVAL_CASE(enumval, {
.named_val1_exists = true ,
.named_val2_exists = true ,
.named_val3_exists = true ,
.anon_val1_exists = true ,
.anon_val2_exists = true ,
.anon_val3_exists = true ,
.named_val1 = 1 ,
.named_val2 = 2 ,
.anon_val1 = 0 x10,
.anon_val2 = 0 x20,
}),
ENUMVAL_CASE(enumval___diff, {
.named_val1_exists = true ,
.named_val2_exists = true ,
.named_val3_exists = true ,
.anon_val1_exists = true ,
.anon_val2_exists = true ,
.anon_val3_exists = true ,
.named_val1 = 101 ,
.named_val2 = 202 ,
.anon_val1 = 0 x11,
.anon_val2 = 0 x22,
}),
ENUMVAL_CASE(enumval___val3_missing, {
.named_val1_exists = true ,
.named_val2_exists = true ,
.named_val3_exists = false ,
.anon_val1_exists = true ,
.anon_val2_exists = true ,
.anon_val3_exists = false ,
.named_val1 = 111 ,
.named_val2 = 222 ,
.anon_val1 = 0 x111,
.anon_val2 = 0 x222,
}),
ENUMVAL_ERR_CASE(enumval___err_missing),
/* 64bit enumerator value existence and value relocations */
ENUM64VAL_CASE(enum64val, {
.unsigned_val1_exists = true ,
.unsigned_val2_exists = true ,
.unsigned_val3_exists = true ,
.signed_val1_exists = true ,
.signed_val2_exists = true ,
.signed_val3_exists = true ,
.unsigned_val1 = 0 x1ffffffffULL,
.unsigned_val2 = 0 x2,
.signed_val1 = 0 x1ffffffffLL,
.signed_val2 = -2 ,
}),
ENUM64VAL_CASE(enum64val___diff, {
.unsigned_val1_exists = true ,
.unsigned_val2_exists = true ,
.unsigned_val3_exists = true ,
.signed_val1_exists = true ,
.signed_val2_exists = true ,
.signed_val3_exists = true ,
.unsigned_val1 = 0 x101ffffffffULL,
.unsigned_val2 = 0 x202ffffffffULL,
.signed_val1 = -101 ,
.signed_val2 = -202 ,
}),
ENUM64VAL_CASE(enum64val___val3_missing, {
.unsigned_val1_exists = true ,
.unsigned_val2_exists = true ,
.unsigned_val3_exists = false ,
.signed_val1_exists = true ,
.signed_val2_exists = true ,
.signed_val3_exists = false ,
.unsigned_val1 = 0 x111ffffffffULL,
.unsigned_val2 = 0 x222,
.signed_val1 = 0 x111ffffffffLL,
.signed_val2 = -222 ,
}),
ENUM64VAL_ERR_CASE(enum64val___err_missing),
};
struct data {
char in[256 ];
char out[256 ];
bool skip;
uint64_t my_pid_tgid;
};
static size_t roundup_page(size_t sz)
{
long page_size = sysconf(_SC_PAGE_SIZE);
return (sz + page_size - 1 ) / page_size * page_size;
}
static int run_btfgen(const char *src_btf, const char *dst_btf, const char *objpath)
{
char command[4096 ];
int n;
n = snprintf(command, sizeof (command),
"./bpftool gen min_core_btf %s %s %s" ,
src_btf, dst_btf, objpath);
if (n < 0 || n >= sizeof (command))
return -1 ;
return system(command);
}
static void run_core_reloc_tests(bool use_btfgen)
{
const size_t mmap_sz = roundup_page(sizeof (struct data));
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, open_opts);
struct core_reloc_test_case *test_case, test_case_copy;
const char *tp_name, *probe_name;
int err, i, equal, fd;
struct bpf_link *link = NULL;
struct bpf_map *data_map;
struct bpf_program *prog;
struct bpf_object *obj;
uint64_t my_pid_tgid;
struct data *data;
void *mmap_data = NULL;
my_pid_tgid = getpid() | ((uint64_t)sys_gettid() << 32 );
for (i = 0 ; i < ARRAY_SIZE(test_cases); i++) {
char btf_file[] = "/tmp/core_reloc.btf.XXXXXX" ;
test_case_copy = test_cases[i];
test_case = &test_case_copy;
if (!test__start_subtest(test_case->case_name))
continue ;
if (test_case->needs_testmod && !env.has_testmod) {
test__skip();
continue ;
}
/* generate a "minimal" BTF file and use it as source */
if (use_btfgen) {
if (!test_case->btf_src_file || test_case->run_btfgen_fails) {
test__skip();
continue ;
}
fd = mkstemp(btf_file);
if (!ASSERT_GE(fd, 0 , "btf_tmp" ))
continue ;
close(fd); /* we only need the path */
err = run_btfgen(test_case->btf_src_file, btf_file,
test_case->bpf_obj_file);
if (!ASSERT_OK(err, "run_btfgen" ))
continue ;
test_case->btf_src_file = btf_file;
}
if (test_case->setup) {
err = test_case->setup(test_case);
if (CHECK(err, "test_setup" , "test #%d setup failed: %d\n" , i, err))
continue ;
}
if (test_case->btf_src_file) {
err = access(test_case->btf_src_file, R_OK);
if (!ASSERT_OK(err, "btf_src_file" ))
continue ;
}
open_opts.btf_custom_path = test_case->btf_src_file;
obj = bpf_object__open_file(test_case->bpf_obj_file, &open_opts);
if (!ASSERT_OK_PTR(obj, "obj_open" ))
goto cleanup;
probe_name = test_case->prog_name;
tp_name = test_case->raw_tp_name; /* NULL for tp_btf */
prog = bpf_object__find_program_by_name(obj, probe_name);
if (CHECK(!prog, "find_probe" ,
"prog '%s' not found\n" , probe_name))
goto cleanup;
err = bpf_object__load(obj);
if (err) {
if (!test_case->fails)
ASSERT_OK(err, "obj_load" );
goto cleanup;
}
data_map = bpf_object__find_map_by_name(obj, ".bss" );
if (CHECK(!data_map, "find_data_map" , "data map not found\n" ))
goto cleanup;
mmap_data = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE,
MAP_SHARED, bpf_map__fd(data_map), 0 );
if (CHECK(mmap_data == MAP_FAILED, "mmap" ,
".bss mmap failed: %d" , errno)) {
mmap_data = NULL;
goto cleanup;
}
data = mmap_data;
memset(mmap_data, 0 , sizeof (*data));
if (test_case->input_len)
memcpy(data->in, test_case->input, test_case->input_len);
data->my_pid_tgid = my_pid_tgid;
link = bpf_program__attach_raw_tracepoint(prog, tp_name);
if (!ASSERT_OK_PTR(link, "attach_raw_tp" ))
goto cleanup;
/* trigger test run */
if (test_case->trigger) {
if (!ASSERT_OK(test_case->trigger(test_case), "test_trigger" ))
goto cleanup;
} else {
usleep(1 );
}
if (data->skip) {
test__skip();
goto cleanup;
}
if (!ASSERT_FALSE(test_case->fails, "obj_load_should_fail" ))
goto cleanup;
equal = memcmp(data->out, test_case->output,
test_case->output_len) == 0 ;
if (CHECK(!equal, "check_result" ,
"input/output data don't match\n" )) {
int j;
for (j = 0 ; j < test_case->input_len; j++) {
printf("input byte #%d: 0x%02hhx\n" ,
j, test_case->input[j]);
}
for (j = 0 ; j < test_case->output_len; j++) {
printf("output byte #%d: EXP 0x%02hhx GOT 0x%02hhx\n" ,
j, test_case->output[j], data->out[j]);
}
goto cleanup;
}
cleanup:
if (mmap_data) {
CHECK_FAIL(munmap(mmap_data, mmap_sz));
mmap_data = NULL;
}
if (use_btfgen)
remove(test_case->btf_src_file);
bpf_link__destroy(link);
link = NULL;
bpf_object__close(obj);
}
}
void test_core_reloc(void )
{
run_core_reloc_tests(false );
}
void test_core_reloc_btfgen(void )
{
run_core_reloc_tests(true );
}
Messung V0.5 in Prozent C=93 H=95 G=93
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-06-08)
¤
*© Formatika GbR, Deutschland