BPF List
 help / color / mirror / Atom feed
From: Ihor Solodrai <ihor.solodrai@linux.dev>
To: Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Kumar Kartikeya Dwivedi <memxor@gmail.com>,
	Quentin Monnet <qmo@kernel.org>
Cc: bpf@vger.kernel.org
Subject: [PATCH bpf-next v2 5/6] selftests/bpf: Add tests for bpftool btf dump format c
Date: Fri, 28 Aug 2026 14:52:06 -0700	[thread overview]
Message-ID: <20260828215207.3105313-6-ihor.solodrai@linux.dev> (raw)
In-Reply-To: <20260828215207.3105313-1-ihor.solodrai@linux.dev>

"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>
---
 tools/testing/selftests/bpf/Makefile          |   8 +-
 .../bpf/bpftool_btf_dump_sorted.expected      |  44 +++++
 .../bpf/bpftool_btf_dump_unsorted.expected    |  48 +++++
 .../bpf/prog_tests/bpftool_btf_dump.c         | 178 ++++++++++++++++++
 4 files changed, 276 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
 create mode 100644 tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
 create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c

diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 5f1a3bfc0569..d99581257e54 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -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))
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
new file mode 100644
index 000000000000..a9c6b688ffd7
--- /dev/null
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_sorted.expected
@@ -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__ */
diff --git a/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
new file mode 100644
index 000000000000..4310adc2ed51
--- /dev/null
+++ b/tools/testing/selftests/bpf/bpftool_btf_dump_unsorted.expected
@@ -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__ */
diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
new file mode 100644
index 000000000000..d5b25302b0c8
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_btf_dump.c
@@ -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);
+}
-- 
2.55.0


  parent reply	other threads:[~2026-08-28 21:52 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-28 21:52 [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 1/6] selftests/bpf: Add compare_text_to_expected() helper Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 2/6] selftests/bpf: Modernize btf_dump test scaffolding Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 3/6] selftests/bpf: Compare btf_dump expected output in-process Ihor Solodrai
2026-08-28 21:52 ` [PATCH bpf-next v2 4/6] selftests/bpf: NUL-terminate bpftool command output Ihor Solodrai
2026-08-28 21:52 ` Ihor Solodrai [this message]
2026-08-28 21:52 ` [PATCH bpf-next v2 6/6] bpftool: Don't drop a type in the sorted C dump Ihor Solodrai
2026-08-30  1:30 ` [PATCH bpf-next v2 0/6] bpftool, selftests: Add tests for C dump and fix a dropped type patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260828215207.3105313-6-ihor.solodrai@linux.dev \
    --to=ihor.solodrai@linux.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=memxor@gmail.com \
    --cc=qmo@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox