-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
selftests/bpf: Validate libbpf's auto-sizing of LD/ST/STX instructions
Add selftests validating libbpf's auto-resizing of load/store instructions when used with CO-RE relocations. An explicit and manual approach with using bpf_core_read() is also demonstrated and tested. Separate BPF program is supposed to fail due to using signed integers of sizes that differ from kernel's sizes. To reliably simulate 32-bit BTF (i.e., the one with sizeof(long) == sizeof(void *) == 4), selftest generates its own custom BTF and passes it as a replacement for real kernel BTF. This allows to test 32/64-bitness mix on all architectures. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/bpf/20201008001025.292064-5-andrii@kernel.org
- Loading branch information
Andrii Nakryiko
authored and
Alexei Starovoitov
committed
Oct 8, 2020
1 parent
2b7d88c
commit 888d83b
Showing
2 changed files
with
397 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* Copyright (c) 2020 Facebook */ | ||
|
||
#include <test_progs.h> | ||
#include <bpf/btf.h> | ||
|
||
/* real layout and sizes according to test's (32-bit) BTF | ||
* needs to be defined before skeleton is included */ | ||
struct test_struct___real { | ||
unsigned int ptr; /* can't use `void *`, it is always 8 byte in BPF target */ | ||
unsigned int val2; | ||
unsigned long long val1; | ||
unsigned short val3; | ||
unsigned char val4; | ||
unsigned char _pad; | ||
}; | ||
|
||
#include "test_core_autosize.skel.h" | ||
|
||
static int duration = 0; | ||
|
||
static struct { | ||
unsigned long long ptr_samesized; | ||
unsigned long long val1_samesized; | ||
unsigned long long val2_samesized; | ||
unsigned long long val3_samesized; | ||
unsigned long long val4_samesized; | ||
struct test_struct___real output_samesized; | ||
|
||
unsigned long long ptr_downsized; | ||
unsigned long long val1_downsized; | ||
unsigned long long val2_downsized; | ||
unsigned long long val3_downsized; | ||
unsigned long long val4_downsized; | ||
struct test_struct___real output_downsized; | ||
|
||
unsigned long long ptr_probed; | ||
unsigned long long val1_probed; | ||
unsigned long long val2_probed; | ||
unsigned long long val3_probed; | ||
unsigned long long val4_probed; | ||
|
||
unsigned long long ptr_signed; | ||
unsigned long long val1_signed; | ||
unsigned long long val2_signed; | ||
unsigned long long val3_signed; | ||
unsigned long long val4_signed; | ||
struct test_struct___real output_signed; | ||
} out; | ||
|
||
void test_core_autosize(void) | ||
{ | ||
char btf_file[] = "/tmp/core_autosize.btf.XXXXXX"; | ||
int err, fd = -1, zero = 0; | ||
int char_id, short_id, int_id, long_long_id, void_ptr_id, id; | ||
struct test_core_autosize* skel = NULL; | ||
struct bpf_object_load_attr load_attr = {}; | ||
struct bpf_program *prog; | ||
struct bpf_map *bss_map; | ||
struct btf *btf = NULL; | ||
size_t written; | ||
const void *raw_data; | ||
__u32 raw_sz; | ||
FILE *f = NULL; | ||
|
||
btf = btf__new_empty(); | ||
if (!ASSERT_OK_PTR(btf, "empty_btf")) | ||
return; | ||
/* Emit the following struct with 32-bit pointer size: | ||
* | ||
* struct test_struct { | ||
* void *ptr; | ||
* unsigned long val2; | ||
* unsigned long long val1; | ||
* unsigned short val3; | ||
* unsigned char val4; | ||
* char: 8; | ||
* }; | ||
* | ||
* This struct is going to be used as the "kernel BTF" for this test. | ||
* It's equivalent memory-layout-wise to test_struct__real above. | ||
*/ | ||
|
||
/* force 32-bit pointer size */ | ||
btf__set_pointer_size(btf, 4); | ||
|
||
char_id = btf__add_int(btf, "unsigned char", 1, 0); | ||
ASSERT_EQ(char_id, 1, "char_id"); | ||
short_id = btf__add_int(btf, "unsigned short", 2, 0); | ||
ASSERT_EQ(short_id, 2, "short_id"); | ||
/* "long unsigned int" of 4 byte size tells BTF that sizeof(void *) == 4 */ | ||
int_id = btf__add_int(btf, "long unsigned int", 4, 0); | ||
ASSERT_EQ(int_id, 3, "int_id"); | ||
long_long_id = btf__add_int(btf, "unsigned long long", 8, 0); | ||
ASSERT_EQ(long_long_id, 4, "long_long_id"); | ||
void_ptr_id = btf__add_ptr(btf, 0); | ||
ASSERT_EQ(void_ptr_id, 5, "void_ptr_id"); | ||
|
||
id = btf__add_struct(btf, "test_struct", 20 /* bytes */); | ||
ASSERT_EQ(id, 6, "struct_id"); | ||
err = btf__add_field(btf, "ptr", void_ptr_id, 0, 0); | ||
err = err ?: btf__add_field(btf, "val2", int_id, 32, 0); | ||
err = err ?: btf__add_field(btf, "val1", long_long_id, 64, 0); | ||
err = err ?: btf__add_field(btf, "val3", short_id, 128, 0); | ||
err = err ?: btf__add_field(btf, "val4", char_id, 144, 0); | ||
ASSERT_OK(err, "struct_fields"); | ||
|
||
fd = mkstemp(btf_file); | ||
if (CHECK(fd < 0, "btf_tmp", "failed to create file: %d\n", fd)) | ||
goto cleanup; | ||
f = fdopen(fd, "w"); | ||
if (!ASSERT_OK_PTR(f, "btf_fdopen")) | ||
goto cleanup; | ||
|
||
raw_data = btf__get_raw_data(btf, &raw_sz); | ||
if (!ASSERT_OK_PTR(raw_data, "raw_data")) | ||
goto cleanup; | ||
written = fwrite(raw_data, 1, raw_sz, f); | ||
if (CHECK(written != raw_sz, "btf_write", "written: %zu, errno: %d\n", written, errno)) | ||
goto cleanup; | ||
fflush(f); | ||
fclose(f); | ||
f = NULL; | ||
close(fd); | ||
fd = -1; | ||
|
||
/* open and load BPF program with custom BTF as the kernel BTF */ | ||
skel = test_core_autosize__open(); | ||
if (!ASSERT_OK_PTR(skel, "skel_open")) | ||
return; | ||
|
||
/* disable handle_signed() for now */ | ||
prog = bpf_object__find_program_by_name(skel->obj, "handle_signed"); | ||
if (!ASSERT_OK_PTR(prog, "prog_find")) | ||
goto cleanup; | ||
bpf_program__set_autoload(prog, false); | ||
|
||
load_attr.obj = skel->obj; | ||
load_attr.target_btf_path = btf_file; | ||
err = bpf_object__load_xattr(&load_attr); | ||
if (!ASSERT_OK(err, "prog_load")) | ||
goto cleanup; | ||
|
||
prog = bpf_object__find_program_by_name(skel->obj, "handle_samesize"); | ||
if (!ASSERT_OK_PTR(prog, "prog_find")) | ||
goto cleanup; | ||
skel->links.handle_samesize = bpf_program__attach(prog); | ||
if (!ASSERT_OK_PTR(skel->links.handle_samesize, "prog_attach")) | ||
goto cleanup; | ||
|
||
prog = bpf_object__find_program_by_name(skel->obj, "handle_downsize"); | ||
if (!ASSERT_OK_PTR(prog, "prog_find")) | ||
goto cleanup; | ||
skel->links.handle_downsize = bpf_program__attach(prog); | ||
if (!ASSERT_OK_PTR(skel->links.handle_downsize, "prog_attach")) | ||
goto cleanup; | ||
|
||
prog = bpf_object__find_program_by_name(skel->obj, "handle_probed"); | ||
if (!ASSERT_OK_PTR(prog, "prog_find")) | ||
goto cleanup; | ||
skel->links.handle_probed = bpf_program__attach(prog); | ||
if (!ASSERT_OK_PTR(skel->links.handle_probed, "prog_attach")) | ||
goto cleanup; | ||
|
||
usleep(1); | ||
|
||
bss_map = bpf_object__find_map_by_name(skel->obj, "test_cor.bss"); | ||
if (!ASSERT_OK_PTR(bss_map, "bss_map_find")) | ||
goto cleanup; | ||
|
||
err = bpf_map_lookup_elem(bpf_map__fd(bss_map), &zero, (void *)&out); | ||
if (!ASSERT_OK(err, "bss_lookup")) | ||
goto cleanup; | ||
|
||
ASSERT_EQ(out.ptr_samesized, 0x01020304, "ptr_samesized"); | ||
ASSERT_EQ(out.val1_samesized, 0x1020304050607080, "val1_samesized"); | ||
ASSERT_EQ(out.val2_samesized, 0x0a0b0c0d, "val2_samesized"); | ||
ASSERT_EQ(out.val3_samesized, 0xfeed, "val3_samesized"); | ||
ASSERT_EQ(out.val4_samesized, 0xb9, "val4_samesized"); | ||
ASSERT_EQ(out.output_samesized.ptr, 0x01020304, "ptr_samesized"); | ||
ASSERT_EQ(out.output_samesized.val1, 0x1020304050607080, "val1_samesized"); | ||
ASSERT_EQ(out.output_samesized.val2, 0x0a0b0c0d, "val2_samesized"); | ||
ASSERT_EQ(out.output_samesized.val3, 0xfeed, "val3_samesized"); | ||
ASSERT_EQ(out.output_samesized.val4, 0xb9, "val4_samesized"); | ||
|
||
ASSERT_EQ(out.ptr_downsized, 0x01020304, "ptr_downsized"); | ||
ASSERT_EQ(out.val1_downsized, 0x1020304050607080, "val1_downsized"); | ||
ASSERT_EQ(out.val2_downsized, 0x0a0b0c0d, "val2_downsized"); | ||
ASSERT_EQ(out.val3_downsized, 0xfeed, "val3_downsized"); | ||
ASSERT_EQ(out.val4_downsized, 0xb9, "val4_downsized"); | ||
ASSERT_EQ(out.output_downsized.ptr, 0x01020304, "ptr_downsized"); | ||
ASSERT_EQ(out.output_downsized.val1, 0x1020304050607080, "val1_downsized"); | ||
ASSERT_EQ(out.output_downsized.val2, 0x0a0b0c0d, "val2_downsized"); | ||
ASSERT_EQ(out.output_downsized.val3, 0xfeed, "val3_downsized"); | ||
ASSERT_EQ(out.output_downsized.val4, 0xb9, "val4_downsized"); | ||
|
||
ASSERT_EQ(out.ptr_probed, 0x01020304, "ptr_probed"); | ||
ASSERT_EQ(out.val1_probed, 0x1020304050607080, "val1_probed"); | ||
ASSERT_EQ(out.val2_probed, 0x0a0b0c0d, "val2_probed"); | ||
ASSERT_EQ(out.val3_probed, 0xfeed, "val3_probed"); | ||
ASSERT_EQ(out.val4_probed, 0xb9, "val4_probed"); | ||
|
||
test_core_autosize__destroy(skel); | ||
skel = NULL; | ||
|
||
/* now re-load with handle_signed() enabled, it should fail loading */ | ||
skel = test_core_autosize__open(); | ||
if (!ASSERT_OK_PTR(skel, "skel_open")) | ||
return; | ||
|
||
load_attr.obj = skel->obj; | ||
load_attr.target_btf_path = btf_file; | ||
err = bpf_object__load_xattr(&load_attr); | ||
if (!ASSERT_ERR(err, "bad_prog_load")) | ||
goto cleanup; | ||
|
||
cleanup: | ||
if (f) | ||
fclose(f); | ||
if (fd >= 0) | ||
close(fd); | ||
remove(btf_file); | ||
btf__free(btf); | ||
test_core_autosize__destroy(skel); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* Copyright (c) 2020 Facebook */ | ||
|
||
#include <linux/bpf.h> | ||
#include <stdint.h> | ||
#include <bpf/bpf_helpers.h> | ||
#include <bpf/bpf_core_read.h> | ||
|
||
char _license[] SEC("license") = "GPL"; | ||
|
||
/* fields of exactly the same size */ | ||
struct test_struct___samesize { | ||
void *ptr; | ||
unsigned long long val1; | ||
unsigned int val2; | ||
unsigned short val3; | ||
unsigned char val4; | ||
} __attribute((preserve_access_index)); | ||
|
||
/* unsigned fields that have to be downsized by libbpf */ | ||
struct test_struct___downsize { | ||
void *ptr; | ||
unsigned long val1; | ||
unsigned long val2; | ||
unsigned long val3; | ||
unsigned long val4; | ||
/* total sz: 40 */ | ||
} __attribute__((preserve_access_index)); | ||
|
||
/* fields with signed integers of wrong size, should be rejected */ | ||
struct test_struct___signed { | ||
void *ptr; | ||
long val1; | ||
long val2; | ||
long val3; | ||
long val4; | ||
} __attribute((preserve_access_index)); | ||
|
||
/* real layout and sizes according to test's (32-bit) BTF */ | ||
struct test_struct___real { | ||
unsigned int ptr; /* can't use `void *`, it is always 8 byte in BPF target */ | ||
unsigned int val2; | ||
unsigned long long val1; | ||
unsigned short val3; | ||
unsigned char val4; | ||
unsigned char _pad; | ||
/* total sz: 20 */ | ||
}; | ||
|
||
struct test_struct___real input = { | ||
.ptr = 0x01020304, | ||
.val1 = 0x1020304050607080, | ||
.val2 = 0x0a0b0c0d, | ||
.val3 = 0xfeed, | ||
.val4 = 0xb9, | ||
._pad = 0xff, /* make sure no accidental zeros are present */ | ||
}; | ||
|
||
unsigned long long ptr_samesized = 0; | ||
unsigned long long val1_samesized = 0; | ||
unsigned long long val2_samesized = 0; | ||
unsigned long long val3_samesized = 0; | ||
unsigned long long val4_samesized = 0; | ||
struct test_struct___real output_samesized = {}; | ||
|
||
unsigned long long ptr_downsized = 0; | ||
unsigned long long val1_downsized = 0; | ||
unsigned long long val2_downsized = 0; | ||
unsigned long long val3_downsized = 0; | ||
unsigned long long val4_downsized = 0; | ||
struct test_struct___real output_downsized = {}; | ||
|
||
unsigned long long ptr_probed = 0; | ||
unsigned long long val1_probed = 0; | ||
unsigned long long val2_probed = 0; | ||
unsigned long long val3_probed = 0; | ||
unsigned long long val4_probed = 0; | ||
|
||
unsigned long long ptr_signed = 0; | ||
unsigned long long val1_signed = 0; | ||
unsigned long long val2_signed = 0; | ||
unsigned long long val3_signed = 0; | ||
unsigned long long val4_signed = 0; | ||
struct test_struct___real output_signed = {}; | ||
|
||
SEC("raw_tp/sys_exit") | ||
int handle_samesize(void *ctx) | ||
{ | ||
struct test_struct___samesize *in = (void *)&input; | ||
struct test_struct___samesize *out = (void *)&output_samesized; | ||
|
||
ptr_samesized = (unsigned long long)in->ptr; | ||
val1_samesized = in->val1; | ||
val2_samesized = in->val2; | ||
val3_samesized = in->val3; | ||
val4_samesized = in->val4; | ||
|
||
out->ptr = in->ptr; | ||
out->val1 = in->val1; | ||
out->val2 = in->val2; | ||
out->val3 = in->val3; | ||
out->val4 = in->val4; | ||
|
||
return 0; | ||
} | ||
|
||
SEC("raw_tp/sys_exit") | ||
int handle_downsize(void *ctx) | ||
{ | ||
struct test_struct___downsize *in = (void *)&input; | ||
struct test_struct___downsize *out = (void *)&output_downsized; | ||
|
||
ptr_downsized = (unsigned long long)in->ptr; | ||
val1_downsized = in->val1; | ||
val2_downsized = in->val2; | ||
val3_downsized = in->val3; | ||
val4_downsized = in->val4; | ||
|
||
out->ptr = in->ptr; | ||
out->val1 = in->val1; | ||
out->val2 = in->val2; | ||
out->val3 = in->val3; | ||
out->val4 = in->val4; | ||
|
||
return 0; | ||
} | ||
|
||
SEC("raw_tp/sys_enter") | ||
int handle_probed(void *ctx) | ||
{ | ||
struct test_struct___downsize *in = (void *)&input; | ||
__u64 tmp; | ||
|
||
tmp = 0; | ||
bpf_core_read(&tmp, bpf_core_field_size(in->ptr), &in->ptr); | ||
ptr_probed = tmp; | ||
|
||
tmp = 0; | ||
bpf_core_read(&tmp, bpf_core_field_size(in->val1), &in->val1); | ||
val1_probed = tmp; | ||
|
||
tmp = 0; | ||
bpf_core_read(&tmp, bpf_core_field_size(in->val2), &in->val2); | ||
val2_probed = tmp; | ||
|
||
tmp = 0; | ||
bpf_core_read(&tmp, bpf_core_field_size(in->val3), &in->val3); | ||
val3_probed = tmp; | ||
|
||
tmp = 0; | ||
bpf_core_read(&tmp, bpf_core_field_size(in->val4), &in->val4); | ||
val4_probed = tmp; | ||
|
||
return 0; | ||
} | ||
|
||
SEC("raw_tp/sys_enter") | ||
int handle_signed(void *ctx) | ||
{ | ||
struct test_struct___signed *in = (void *)&input; | ||
struct test_struct___signed *out = (void *)&output_signed; | ||
|
||
val2_signed = in->val2; | ||
val3_signed = in->val3; | ||
val4_signed = in->val4; | ||
|
||
out->val2= in->val2; | ||
out->val3= in->val3; | ||
out->val4= in->val4; | ||
|
||
return 0; | ||
} |