mirror of
https://github.com/torvalds/linux.git
synced 2026-11-09 13:44:03 +01:00
selftests/bpf: Add tests for bpftool btf dump format c
"bpftool btf dump format c" generates the vmlinux.h that BPF programs are built against, and it has no test coverage at all. The only in-tree consumers are build systems. Building against the header only catches what a compiler rejects, which is insufficient. Introduce a bpftool_btf_dump selftest. Dump a small hand-built BTF and compare the output against committed expectations. The BTF is assembled with btf__add_*() rather than compiled from BPF C because the fixture needs a 4-byte "long int". That is what makes bpftool render the hole in struct holey as a pair of "long: 32;" bitfields. A BPF target is always 64-bit, so a compiled fixture could not supply it. Comparing the whole dump makes every change to a generated header show up in a patch. Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev> Link: https://lore.kernel.org/r/20260828215207.3105313-6-ihor.solodrai@linux.dev Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
4f8a4c76f6
commit
438147f7f6
|
|
@ -119,7 +119,9 @@ TEST_GEN_PROGS += test_progs-cpuv4
|
|||
TEST_INST_SUBDIRS += cpuv4
|
||||
endif
|
||||
|
||||
TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c)
|
||||
TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c) \
|
||||
bpftool_btf_dump_sorted.expected \
|
||||
bpftool_btf_dump_unsorted.expected
|
||||
|
||||
# Order correspond to 'make run_tests' order
|
||||
TEST_PROGS := test_kmod.sh \
|
||||
|
|
@ -887,7 +889,9 @@ TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read \
|
|||
ima_setup.sh \
|
||||
$(VERIFY_SIG_SETUP) \
|
||||
$(wildcard progs/btf_dump_test_case_*.c) \
|
||||
$(wildcard progs/*.bpf.o)
|
||||
$(wildcard progs/*.bpf.o) \
|
||||
bpftool_btf_dump_sorted.expected \
|
||||
bpftool_btf_dump_unsorted.expected
|
||||
TRUNNER_BPF_BUILD_RULE := CLANG_BPF_BUILD_RULE
|
||||
TRUNNER_BPF_CFLAGS := $(BPF_CFLAGS) $(CLANG_CFLAGS) -DENABLE_ATOMICS_TESTS
|
||||
$(eval $(call DEFINE_TEST_RUNNER,test_progs))
|
||||
|
|
|
|||
44
tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
Normal file
44
tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef __VMLINUX_H__
|
||||
#define __VMLINUX_H__
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
|
||||
#endif
|
||||
|
||||
#ifndef __ksym
|
||||
#define __ksym __attribute__((section(".ksyms")))
|
||||
#endif
|
||||
|
||||
#ifndef __weak
|
||||
#define __weak __attribute__((weak))
|
||||
#endif
|
||||
|
||||
#ifndef __bpf_fastcall
|
||||
#if __has_attribute(bpf_fastcall)
|
||||
#define __bpf_fastcall __attribute__((bpf_fastcall))
|
||||
#else
|
||||
#define __bpf_fastcall
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct holey {
|
||||
int c;
|
||||
long: 32;
|
||||
long: 32;
|
||||
int tail;
|
||||
};
|
||||
|
||||
struct s {
|
||||
int f;
|
||||
};
|
||||
|
||||
|
||||
/* BPF kfuncs */
|
||||
#ifndef BPF_NO_KFUNC_PROTOTYPES
|
||||
#endif
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute pop
|
||||
#endif
|
||||
|
||||
#endif /* __VMLINUX_H__ */
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef __VMLINUX_H__
|
||||
#define __VMLINUX_H__
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
|
||||
#endif
|
||||
|
||||
#ifndef __ksym
|
||||
#define __ksym __attribute__((section(".ksyms")))
|
||||
#endif
|
||||
|
||||
#ifndef __weak
|
||||
#define __weak __attribute__((weak))
|
||||
#endif
|
||||
|
||||
#ifndef __bpf_fastcall
|
||||
#if __has_attribute(bpf_fastcall)
|
||||
#define __bpf_fastcall __attribute__((bpf_fastcall))
|
||||
#else
|
||||
#define __bpf_fastcall
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct holey {
|
||||
int c;
|
||||
long: 32;
|
||||
long: 32;
|
||||
int tail;
|
||||
};
|
||||
|
||||
enum {
|
||||
E0 = 1,
|
||||
};
|
||||
|
||||
struct s {
|
||||
int f;
|
||||
};
|
||||
|
||||
|
||||
/* BPF kfuncs */
|
||||
#ifndef BPF_NO_KFUNC_PROTOTYPES
|
||||
#endif
|
||||
|
||||
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
|
||||
#pragma clang attribute pop
|
||||
#endif
|
||||
|
||||
#endif /* __VMLINUX_H__ */
|
||||
178
tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
Normal file
178
tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <test_progs.h>
|
||||
#include <bpftool_helpers.h>
|
||||
#include <bpf/btf.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include "btf_helpers.h"
|
||||
#include "testing_helpers.h"
|
||||
|
||||
#define DUMP_BUF_SZ (16 * 1024)
|
||||
|
||||
#define EXPECTED_SORTED "bpftool_btf_dump_sorted.expected"
|
||||
#define EXPECTED_UNSORTED "bpftool_btf_dump_unsorted.expected"
|
||||
|
||||
/*
|
||||
* struct holey {
|
||||
* int c;
|
||||
* <64-bit hole>
|
||||
* int tail;
|
||||
* };
|
||||
* enum { E0 = 1 };
|
||||
* struct s { int f; };
|
||||
*/
|
||||
static struct btf *mk_btf(void)
|
||||
{
|
||||
struct btf *btf;
|
||||
|
||||
btf = btf__new_empty();
|
||||
if (!ASSERT_OK_PTR(btf, "new_empty"))
|
||||
return NULL;
|
||||
|
||||
btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
|
||||
btf__add_int(btf, "long int", 4, BTF_INT_SIGNED);
|
||||
|
||||
btf__add_struct(btf, "holey", 16);
|
||||
btf__add_field(btf, "c", 1, 0, 0);
|
||||
btf__add_field(btf, "tail", 1, 96, 0);
|
||||
|
||||
btf__add_enum(btf, NULL, 4);
|
||||
btf__add_enum_value(btf, "E0", 1);
|
||||
|
||||
btf__add_struct(btf, "s", 4);
|
||||
btf__add_field(btf, "f", 1, 0, 0);
|
||||
|
||||
VALIDATE_RAW_BTF(
|
||||
btf,
|
||||
"[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
|
||||
"[2] INT 'long int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
|
||||
"[3] STRUCT 'holey' size=16 vlen=2\n"
|
||||
"\t'c' type_id=1 bits_offset=0\n"
|
||||
"\t'tail' type_id=1 bits_offset=96",
|
||||
"[4] ENUM '(anon)' encoding=UNSIGNED size=4 vlen=1\n"
|
||||
"\t'E0' val=1",
|
||||
"[5] STRUCT 's' size=4 vlen=1\n"
|
||||
"\t'f' type_id=1 bits_offset=0");
|
||||
|
||||
return btf;
|
||||
}
|
||||
|
||||
static int btf_to_tmpfile(const struct btf *btf, char *path)
|
||||
{
|
||||
ssize_t written;
|
||||
const void *raw;
|
||||
__u32 sz;
|
||||
int fd;
|
||||
|
||||
raw = btf__raw_data(btf, &sz);
|
||||
if (!ASSERT_OK_PTR(raw, "raw_data"))
|
||||
return -1;
|
||||
|
||||
snprintf(path, PATH_MAX, "/tmp/bpftool_btf_dump.XXXXXX");
|
||||
fd = mkstemp(path);
|
||||
if (!ASSERT_OK_FD(fd, "mkstemp_btf"))
|
||||
return -1;
|
||||
|
||||
written = write(fd, raw, sz);
|
||||
close(fd);
|
||||
if (!ASSERT_EQ(written, sz, "write_btf")) {
|
||||
unlink(path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *dump_c(const char *btf_path, bool sorted)
|
||||
{
|
||||
char args[MAX_BPFTOOL_CMD_LEN];
|
||||
char *buf;
|
||||
int err;
|
||||
|
||||
buf = malloc(DUMP_BUF_SZ);
|
||||
if (!ASSERT_OK_PTR(buf, "alloc_dump"))
|
||||
return NULL;
|
||||
|
||||
snprintf(args, sizeof(args), "btf dump file %s format c%s",
|
||||
btf_path, sorted ? "" : " unsorted");
|
||||
|
||||
err = get_bpftool_command_output(args, buf, DUMP_BUF_SZ);
|
||||
if (!ASSERT_OK(err, "btf_dump_format_c")) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *read_expected(const char *path)
|
||||
{
|
||||
char *buf = NULL;
|
||||
size_t cap = 0;
|
||||
FILE *f;
|
||||
int err;
|
||||
|
||||
f = fopen(path, "r");
|
||||
if (!f) {
|
||||
err = errno;
|
||||
PRINT_FAIL("can't open expected output '%s': errno %d\n", path, err);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* no NUL in a generated header, so this reads to the end */
|
||||
if (getdelim(&buf, &cap, '\0', f) < 0) {
|
||||
err = errno;
|
||||
PRINT_FAIL("can't read expected output '%s': errno %d\n", path, err);
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void test_dump(const char *btf_path, bool sorted)
|
||||
{
|
||||
const char *exp_path;
|
||||
char *dump, *exp;
|
||||
int err;
|
||||
|
||||
exp_path = sorted ? EXPECTED_SORTED : EXPECTED_UNSORTED;
|
||||
|
||||
dump = dump_c(btf_path, sorted);
|
||||
if (!dump)
|
||||
return;
|
||||
|
||||
exp = read_expected(exp_path);
|
||||
if (!exp)
|
||||
goto out_dump;
|
||||
|
||||
err = compare_text_to_expected(dump, exp);
|
||||
ASSERT_OK(err, sorted ? "cmp_sorted" : "cmp_unsorted");
|
||||
|
||||
free(exp);
|
||||
out_dump:
|
||||
free(dump);
|
||||
}
|
||||
|
||||
void test_bpftool_btf_dump(void)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
struct btf *btf;
|
||||
|
||||
btf = mk_btf();
|
||||
if (!btf)
|
||||
return;
|
||||
|
||||
if (btf_to_tmpfile(btf, path))
|
||||
goto out_btf;
|
||||
|
||||
if (test__start_subtest("c_sorted"))
|
||||
test_dump(path, true);
|
||||
if (test__start_subtest("c_unsorted"))
|
||||
test_dump(path, false);
|
||||
|
||||
unlink(path);
|
||||
out_btf:
|
||||
btf__free(btf);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user