mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH bpf-next v3 0/3] Normalize enum values in libbpf and bpftool
@ 2026-11-01 10:32 Hungyu Lin
  2026-11-01 10:32 ` [PATCH bpf-next v3 1/3] libbpf: Normalize enum values according to signedness Hungyu Lin
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Hungyu Lin @ 2026-11-01 10:32 UTC (permalink / raw)
  To: ast, bpf
  Cc: andrii, qmo, daniel, yonghong.song, eddyz87, ihor.solodrai,
	shuah, linux-kselftest, linux-kernel, Hungyu Lin

This series fixes enum signedness problems in libbpf and bpftool data
dumps using the shared btf_enum_value() helper suggested by Alexei.
It also uses the helper when parsing enum-backed map fields, where sign
extension can change the requested map_extra value.

Keep existing enum name matches before considering normalized values.
This matters for eight-byte BTF_KIND_ENUM values in both dumpers and
narrow negative enums in bpftool. libbpf already zero-extends narrow
data without kflag; that behavior is unchanged. bpftool preserves the
original name's priority in one scan, even if another enumerator matches
the normalized value.

For an eight-byte ordinary enum with kflag clear, a stored negative
enumerator can match both its sign- and zero-extended values. This is
a compatibility choice: identical metadata can describe either a legacy
signed enum or an unsigned enum.

Unknown bpftool values follow kflag. With kflag clear, numeric output is
unsigned, including BTF sanitized by libbpf for kernels without enum64
support. This changes unknown negative values whose original signedness
is no longer recorded. libbpf's numeric fallback is unchanged.

CO-RE is left unchanged. Clang 14 and 18 emit a zero-extended immediate
for 0x80000000U, which the current sign-extended orig_val rejects. But
using the helper directly would reject negative enum immediates from
Clang 14 objects without kflag, and would zero-extend negative values
from target BTF without kflag. Those compatibility issues need a
separate change.

This supersedes the pending libbpf patch [1] and bpftool data-dump v2 [2],
as requested in [3]. The already applied bpftool type-JSON fix,
eaae576c34d7, is in the base and is not resubmitted.

Changes in v3:
- Combine libbpf, bpftool and regression tests into one series.
- Add btf_enum_value() and use it for enum-backed map fields and data
  name matching.
- Preserve existing legacy names, including eight-byte ordinary enums
  and ambiguous narrow names, addressing the review of bpftool v2.
- Add libbpf data-dump tests and kernel-backed bpftool map-dump tests.

Validation on ARM64, GCC 13.3.0, Clang 18.1.3, Linux 6.12.68:
- Complete libbpf and bpftool builds pass, including the libbpf ABI
  checks and bpftool built with -Werror.
- With the new tests on the baseline: 38 subtests pass, 8 fail.
- With the fixes: all 46 subtests pass, none fail or skip.
  These are the btf_dump and bpftool_map_dump test groups. The bpftool
  cases load BTF and maps into the kernel and check both CLI formats.
  Run these groups with ./test_progs -t btf_dump,bpftool_map_dump.
- map_extra parsing was checked with a separate local object-open
  harness; this series does not include a selftest for that path.
  Arena allocation and mmap were not tested.

[1] https://lore.kernel.org/bpf/20261031233219.9362-1-dennylin0707@gmail.com/
[2] https://lore.kernel.org/bpf/20261101001024.9994-1-dennylin0707@gmail.com/
[3] https://lore.kernel.org/bpf/DMJG1R2YIJRK.16CZLV9S5U7Z7@gmail.com/

Thanks,
Hungyu

Hungyu Lin (3):
  libbpf: Normalize enum values according to signedness
  bpftool: Preserve unsigned enum values in data dumps
  selftests/bpf: Cover enum signedness and legacy name matching

 tools/bpf/bpftool/btf_dumper.c                |  42 ++--
 tools/lib/bpf/btf.h                           |   6 +
 tools/lib/bpf/btf_dump.c                      |   3 +-
 tools/lib/bpf/libbpf.c                        |   2 +-
 .../bpf/prog_tests/bpftool_map_dump.c         | 180 ++++++++++++++++++
 .../selftests/bpf/prog_tests/btf_dump.c       |  84 ++++++++
 6 files changed, 302 insertions(+), 15 deletions(-)


base-commit: a30c772627b2ef4bc715332b1b04938a914556cd
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH bpf-next v3 1/3] libbpf: Normalize enum values according to signedness
  2026-11-01 10:32 [PATCH bpf-next v3 0/3] Normalize enum values in libbpf and bpftool Hungyu Lin
@ 2026-11-01 10:32 ` Hungyu Lin
  2026-11-01 10:32 ` [PATCH bpf-next v3 2/3] bpftool: Preserve unsigned enum values in data dumps Hungyu Lin
  2026-11-01 10:32 ` [PATCH bpf-next v3 3/3] selftests/bpf: Cover enum signedness and legacy name matching Hungyu Lin
  2 siblings, 0 replies; 4+ messages in thread
From: Hungyu Lin @ 2026-11-01 10:32 UTC (permalink / raw)
  To: ast, bpf
  Cc: andrii, qmo, daniel, yonghong.song, eddyz87, ihor.solodrai,
	shuah, linux-kselftest, linux-kernel, Hungyu Lin

BTF_KIND_ENUM stores enumerators in a signed 32-bit field even for
unsigned enums. btf_dump_enum_data() compares unsigned data against that
signed field, so values with bit 31 set fail to match their names.
get_map_field_long() also sign-extends the field: for example,
__ulong(map_extra, 0x80000000) becomes 0xffffffff80000000 instead of the
requested arena mapping address.

Add btf_enum_value() to extend an enumerator according to kflag and use
it in both places.

For data dumps, keep the original comparison as well. Older BTF producers
did not record enum signedness; an eight-byte BTF_KIND_ENUM can contain
signed data such as -1 with kflag clear. Using only the helper would lose
an existing name match. This compatibility comparison does not truncate
the data value or change the numeric fallback.

For an eight-byte BTF_KIND_ENUM with kflag clear, a negative stored
enumerator can therefore match both its sign- and zero-extended values.
The record cannot distinguish legacy signed metadata from unsigned
metadata with the same bits.

Fixes: d90ec262b35b ("libbpf: Add enum64 support for btf_dump")
Fixes: d147357e2e59 ("libbpf: Allow specifying 64-bit integers in map BTF.")
Suggested-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/DMJG1R2YIJRK.16CZLV9S5U7Z7@gmail.com/
Signed-off-by: Hungyu Lin <dennylin0707@gmail.com>
---
 tools/lib/bpf/btf.h      | 6 ++++++
 tools/lib/bpf/btf_dump.c | 3 ++-
 tools/lib/bpf/libbpf.c   | 2 +-
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index 57f12630c5d1..eeb4984d6c57 100644
--- a/tools/lib/bpf/btf.h
+++ b/tools/lib/bpf/btf.h
@@ -636,6 +636,12 @@ static inline struct btf_enum *btf_enum(const struct btf_type *t)
 	return (struct btf_enum *)(t + 1);
 }
 
+/* btf_enum->val is signed even for unsigned enums; kflag records the sign. */
+static inline __s64 btf_enum_value(const struct btf_type *t, const struct btf_enum *e)
+{
+	return btf_kflag(t) ? (__s64)e->val : (__u32)e->val;
+}
+
 struct btf_enum64;
 
 static inline struct btf_enum64 *btf_enum64(const struct btf_type *t)
diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index ea9a34db4c00..b636dc4185a0 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -2373,7 +2373,8 @@ static int btf_dump_enum_data(struct btf_dump *d,
 		const struct btf_enum *e;
 
 		for (i = 0, e = btf_enum(t); i < btf_vlen(t); i++, e++) {
-			if (value != e->val)
+			/* Keep names from legacy signed enums without kflag. */
+			if (value != e->val && value != btf_enum_value(t, e))
 				continue;
 			btf_dump_type_values(d, "%s", btf_name_of(d, e->name_off));
 			return 0;
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 02e75b183ed0..cecd6b5f27de 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -2627,7 +2627,7 @@ static bool get_map_field_long(const char *map_name, const struct btf *btf,
 	if (btf_is_enum(t)) {
 		const struct btf_enum *e = btf_enum(t);
 
-		*res = e->val;
+		*res = btf_enum_value(t, e);
 	} else {
 		const struct btf_enum64 *e = btf_enum64(t);
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH bpf-next v3 2/3] bpftool: Preserve unsigned enum values in data dumps
  2026-11-01 10:32 [PATCH bpf-next v3 0/3] Normalize enum values in libbpf and bpftool Hungyu Lin
  2026-11-01 10:32 ` [PATCH bpf-next v3 1/3] libbpf: Normalize enum values according to signedness Hungyu Lin
@ 2026-11-01 10:32 ` Hungyu Lin
  2026-11-01 10:32 ` [PATCH bpf-next v3 3/3] selftests/bpf: Cover enum signedness and legacy name matching Hungyu Lin
  2 siblings, 0 replies; 4+ messages in thread
From: Hungyu Lin @ 2026-11-01 10:32 UTC (permalink / raw)
  To: ast, bpf
  Cc: andrii, qmo, daniel, yonghong.song, eddyz87, ihor.solodrai,
	shuah, linux-kselftest, linux-kernel, Hungyu Lin

btf_dumper_enum() loads ordinary enum data as signed and uses signed
numeric output. Unsigned values with the high bit set can therefore be
printed as negative numbers. Narrow unsigned values also fail to match
their enumerators: an eight-bit 0xff is loaded as -1, while its named
value is stored as 255.

btf_dumper_enum64() likewise uses signed numeric output for unmatched
values. For example, 0x8000000000000001 is printed as
-9223372036854775807 instead of 9223372036854775809.

Load the data according to the enum's signedness, use btf_enum_value()
for name matching, and select the numeric writer according to kflag.

Retain the original signed-width comparison and give it priority over
new matches. This preserves names from older BTF without signedness,
including eight-byte ordinary enums, and keeps the selected name when
multiple enumerators could match. Remember the first normalized match
while looking for an original match so only one scan is needed.

For an eight-byte BTF_KIND_ENUM with kflag clear, a negative stored
enumerator can therefore match both its sign- and zero-extended values.
The record cannot distinguish legacy signed metadata from unsigned
metadata with the same bits.

Unmatched values are printed as unsigned when kflag is clear. This
includes BTF read back from kernels without enum64 support, where libbpf
clears kflag before loading. The original signedness cannot be recovered
from that flag.

Fixes: ca7e6e45d108 ("bpftool: Print the enum's name instead of value")
Fixes: 58a53978fdf6 ("bpftool: Add btf enum64 support")
Suggested-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Hungyu Lin <dennylin0707@gmail.com>
---
 tools/bpf/bpftool/btf_dumper.c | 42 +++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 13 deletions(-)

diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
index 3267163119dc..792e210cb503 100644
--- a/tools/bpf/bpftool/btf_dumper.c
+++ b/tools/bpf/bpftool/btf_dumper.c
@@ -148,37 +148,50 @@ static int btf_dumper_enum(const struct btf_dumper *d,
 			    const struct btf_type *t,
 			    const void *data)
 {
-	const struct btf_enum *enums = btf_enum(t);
-	__s64 value;
+	const struct btf_enum *enums = btf_enum(t), *match = NULL;
+	bool is_signed = btf_kflag(t);
+	__s64 value, legacy_value;
 	__u32 i;
 
 	switch (t->size) {
 	case 8:
-		value = *(__s64 *)data;
+		legacy_value = *(__s64 *)data;
+		value = legacy_value;
 		break;
 	case 4:
-		value = *(__s32 *)data;
+		legacy_value = *(__s32 *)data;
+		value = is_signed ? legacy_value : *(__u32 *)data;
 		break;
 	case 2:
-		value = *(__s16 *)data;
+		legacy_value = *(__s16 *)data;
+		value = is_signed ? legacy_value : *(__u16 *)data;
 		break;
 	case 1:
-		value = *(__s8 *)data;
+		legacy_value = *(__s8 *)data;
+		value = is_signed ? legacy_value : *(__u8 *)data;
 		break;
 	default:
 		return -EINVAL;
 	}
 
+	/* Prefer existing name matches, including signed enums without kflag. */
 	for (i = 0; i < btf_vlen(t); i++) {
-		if (value == enums[i].val) {
-			jsonw_string(d->jw,
-				     btf__name_by_offset(d->btf,
-							 enums[i].name_off));
-			return 0;
+		if (legacy_value == enums[i].val) {
+			match = &enums[i];
+			break;
 		}
+		if (!match && value == btf_enum_value(t, &enums[i]))
+			match = &enums[i];
+	}
+	if (match) {
+		jsonw_string(d->jw, btf__name_by_offset(d->btf, match->name_off));
+		return 0;
 	}
 
-	jsonw_int(d->jw, value);
+	if (is_signed)
+		jsonw_int(d->jw, value);
+	else
+		jsonw_uint(d->jw, value);
 	return 0;
 }
 
@@ -204,7 +217,10 @@ static int btf_dumper_enum64(const struct btf_dumper *d,
 		}
 	}
 
-	jsonw_int(d->jw, value);
+	if (btf_kflag(t))
+		jsonw_int(d->jw, value);
+	else
+		jsonw_uint(d->jw, value);
 	return 0;
 }
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH bpf-next v3 3/3] selftests/bpf: Cover enum signedness and legacy name matching
  2026-11-01 10:32 [PATCH bpf-next v3 0/3] Normalize enum values in libbpf and bpftool Hungyu Lin
  2026-11-01 10:32 ` [PATCH bpf-next v3 1/3] libbpf: Normalize enum values according to signedness Hungyu Lin
  2026-11-01 10:32 ` [PATCH bpf-next v3 2/3] bpftool: Preserve unsigned enum values in data dumps Hungyu Lin
@ 2026-11-01 10:32 ` Hungyu Lin
  2 siblings, 0 replies; 4+ messages in thread
From: Hungyu Lin @ 2026-11-01 10:32 UTC (permalink / raw)
  To: ast, bpf
  Cc: andrii, qmo, daniel, yonghong.song, eddyz87, ihor.solodrai,
	shuah, linux-kselftest, linux-kernel, Hungyu Lin

Exercise unsigned enum values with their high bit set and signed
controls through libbpf's data dumper. Include ordinary enums stored in
eight bytes and legacy BTF with kflag clear so normalization does not
lose existing names or match only the low 32 bits of wider data.

Extend bpftool_map_dump with BTF-backed array maps and check both JSON
and plain output. Cover narrow unsigned names, signed and unsigned
numeric fallbacks, legacy enum names, both enumerator orders for
ambiguous narrow names, and unmatched ENUM64 values.

Build legacy enum records through the public BTF API and load them into
the kernel before invoking bpftool. Initialize map data at its actual
width so the tests also work on big-endian hosts.

Suggested-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Hungyu Lin <dennylin0707@gmail.com>
---
 .../bpf/prog_tests/bpftool_map_dump.c         | 180 ++++++++++++++++++
 .../selftests/bpf/prog_tests/btf_dump.c       |  84 ++++++++
 2 files changed, 264 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c
index 187b3f784307..926d0f324a5f 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump.c
@@ -7,6 +7,7 @@
 
 #define PIN_PATH	"/sys/fs/bpf/test_bpftool_map_dump_sk_storage"
 #define QUEUE_PIN_PATH	"/sys/fs/bpf/test_bpftool_map_dump_queue"
+#define ENUM_PIN_PATH	"/sys/fs/bpf/test_bpftool_map_dump_enum"
 #define JSON_ERROR	"{\"error\":\"can't dump sk_storage map"
 
 /* An empty socket storage map with an int value. */
@@ -124,10 +125,189 @@ static void test_queue(void)
 	close(map_fd);
 }
 
+enum enum_dump_kind {
+	DUMP_ENUM_UNSIGNED,
+	DUMP_ENUM_SIGNED,
+	DUMP_ENUM_LEGACY,
+	DUMP_ENUM_ALIAS_FIRST,
+	DUMP_ENUM_ALIAS_LAST,
+	DUMP_ENUM64_UNSIGNED,
+	DUMP_ENUM64_SIGNED,
+};
+
+struct enum_dump_test {
+	const char *name;
+	enum enum_dump_kind kind;
+	__u32 size;
+	__u64 value;
+	const char *expected;
+};
+
+static const struct enum_dump_test enum_dump_tests[] = {
+	{ "enum_u8_name", DUMP_ENUM_UNSIGNED, 1, 0xff, "\"MAX\"" },
+	{ "enum_u8_unknown", DUMP_ENUM_UNSIGNED, 1, (__u64)-22, "234" },
+	{ "enum_u16_name", DUMP_ENUM_UNSIGNED, 2, 0xffff, "\"MAX\"" },
+	{ "enum_u16_unknown", DUMP_ENUM_UNSIGNED, 2, (__u64)-22, "65514" },
+	{ "enum_u32_high", DUMP_ENUM_UNSIGNED, 4, 0x80000000, "\"HIGH\"" },
+	{ "enum_u32_max", DUMP_ENUM_UNSIGNED, 4, 0xffffffff, "\"MAX\"" },
+	{ "enum_u32_unknown", DUMP_ENUM_UNSIGNED, 4, (__u64)-22, "4294967274" },
+	{ "enum_s8_name", DUMP_ENUM_SIGNED, 1, (__u64)-1, "\"NEG\"" },
+	{ "enum_s8_unknown", DUMP_ENUM_SIGNED, 1, (__u64)-22, "-22" },
+	{ "enum_s16_name", DUMP_ENUM_SIGNED, 2, (__u64)-1, "\"NEG\"" },
+	{ "enum_s32_name", DUMP_ENUM_SIGNED, 4, (__u64)-1, "\"NEG\"" },
+	{ "enum_s32_unknown", DUMP_ENUM_SIGNED, 4, (__u64)-22, "-22" },
+	{ "enum_legacy8", DUMP_ENUM_LEGACY, 1, (__u64)-1, "\"NEG\"" },
+	{ "enum_legacy16", DUMP_ENUM_LEGACY, 2, (__u64)-1, "\"NEG\"" },
+	{ "enum_legacy32", DUMP_ENUM_LEGACY, 4, (__u64)-1, "\"NEG\"" },
+	{ "enum_legacy_wide", DUMP_ENUM_LEGACY, 8, (__u64)-1, "\"NEG\"" },
+	{ "enum_alias_first", DUMP_ENUM_ALIAS_FIRST, 1, 0xff, "\"NEG\"" },
+	{ "enum_alias_last", DUMP_ENUM_ALIAS_LAST, 1, 0xff, "\"NEG\"" },
+	{ "enum_wide_name", DUMP_ENUM_UNSIGNED, 8, 0xffffffff, "\"MAX\"" },
+	{ "enum_wide_unknown", DUMP_ENUM_UNSIGNED, 8, 0x1ffffffffULL, "8589934591" },
+	{ "enum64_unsigned", DUMP_ENUM64_UNSIGNED, 8, (1ULL << 63) | 1,
+	  "9223372036854775809" },
+	{ "enum64_signed", DUMP_ENUM64_SIGNED, 8, (__u64)-22, "-22" },
+};
+
+static int add_enum_dump_values(struct btf *btf, const struct enum_dump_test *test)
+{
+	__u64 max = test->size == 1 ? 0xff : test->size == 2 ? 0xffff : 0xffffffff;
+	int err;
+
+	switch (test->kind) {
+	case DUMP_ENUM_UNSIGNED:
+		err = btf__add_enum_value(btf, "HIGH", (max >> 1) + 1);
+		return err ?: btf__add_enum_value(btf, "MAX", max);
+	case DUMP_ENUM_SIGNED:
+		return btf__add_enum_value(btf, "NEG", -1);
+	case DUMP_ENUM_LEGACY:
+		/* Model old BTF: raw signed -1, but no signedness kflag. */
+		return btf__add_enum_value(btf, "NEG", (__u32)-1);
+	case DUMP_ENUM_ALIAS_FIRST:
+		err = btf__add_enum_value(btf, "U255", 0xff);
+		return err ?: btf__add_enum_value(btf, "NEG", (__u32)-1);
+	case DUMP_ENUM_ALIAS_LAST:
+		err = btf__add_enum_value(btf, "NEG", (__u32)-1);
+		return err ?: btf__add_enum_value(btf, "U255", 0xff);
+	case DUMP_ENUM64_UNSIGNED:
+		err = btf__add_enum64_value(btf, "HIGH", 1ULL << 63);
+		return err ?: btf__add_enum64_value(btf, "LOW", 1);
+	case DUMP_ENUM64_SIGNED:
+		return btf__add_enum64_value(btf, "NEG", (__u64)-1);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int create_enum_dump_map(const struct enum_dump_test *test)
+{
+	LIBBPF_OPTS(bpf_map_create_opts, opts);
+	bool enum64 = test->kind == DUMP_ENUM64_UNSIGNED ||
+		      test->kind == DUMP_ENUM64_SIGNED;
+	struct btf *btf;
+	int key_id, value_id, fd = -1;
+
+	btf = btf__new_empty();
+	if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
+		return -1;
+
+	key_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+	if (!ASSERT_GT(key_id, 0, "btf__add_int"))
+		goto out;
+	if (enum64)
+		value_id = btf__add_enum64(btf, "enum_value", test->size,
+					   test->kind == DUMP_ENUM64_SIGNED);
+	else
+		value_id = btf__add_enum(btf, "enum_value", test->size);
+	if (!ASSERT_GT(value_id, 0, "add enum"))
+		goto out;
+	if (!ASSERT_OK(add_enum_dump_values(btf, test), "add enum values"))
+		goto out;
+	if (!ASSERT_OK(btf__load_into_kernel(btf), "btf__load_into_kernel"))
+		goto out;
+
+	opts.btf_fd = btf__fd(btf);
+	opts.btf_key_type_id = key_id;
+	opts.btf_value_type_id = value_id;
+	fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "enum_dump", sizeof(__u32),
+			    test->size, 1, &opts);
+	ASSERT_OK_FD(fd, "bpf_map_create");
+out:
+	btf__free(btf);
+	return fd;
+}
+
+static void test_enum_dump(const struct enum_dump_test *test)
+{
+	union {
+		__u8 val8;
+		__u16 val16;
+		__u32 val32;
+		__u64 val64;
+	} value;
+	char output[1024] = {}, expected[128];
+	__u32 key = 0;
+	int map_fd, ret;
+
+	/* Store the value at its native width, including on big-endian hosts. */
+	switch (test->size) {
+	case 1:
+		value.val8 = (__u8)test->value;
+		break;
+	case 2:
+		value.val16 = (__u16)test->value;
+		break;
+	case 4:
+		value.val32 = (__u32)test->value;
+		break;
+	case 8:
+		value.val64 = test->value;
+		break;
+	default:
+		PRINT_FAIL("unexpected enum size %u (expected 1, 2, 4 or 8)\n", test->size);
+		return;
+	}
+
+	map_fd = create_enum_dump_map(test);
+	if (map_fd < 0)
+		return;
+	if (!ASSERT_OK(bpf_map_update_elem(map_fd, &key, &value, BPF_ANY),
+		       "update enum value"))
+		goto out;
+	if (!ASSERT_OK(bpf_obj_pin(map_fd, ENUM_PIN_PATH), "pin enum map"))
+		goto out;
+
+	ret = get_bpftool_command_output("-j map dump pinned " ENUM_PIN_PATH,
+					 output, sizeof(output));
+	if (!ASSERT_OK(ret, "json enum dump"))
+		goto out;
+	snprintf(expected, sizeof(expected), "\"formatted\":{\"key\":0,\"value\":%s}",
+		 test->expected);
+	ASSERT_HAS_SUBSTR(output, expected, "json enum value");
+
+	memset(output, 0, sizeof(output));
+	ret = get_bpftool_command_output("map dump pinned " ENUM_PIN_PATH,
+					 output, sizeof(output));
+	if (!ASSERT_OK(ret, "plain enum dump"))
+		goto out;
+	snprintf(expected, sizeof(expected), "\"value\": %s\n", test->expected);
+	ASSERT_HAS_SUBSTR(output, expected, "plain enum value");
+out:
+	unlink(ENUM_PIN_PATH);
+	close(map_fd);
+}
+
 void test_bpftool_map_dump(void)
 {
+	size_t i;
+
 	if (test__start_subtest("sk_storage"))
 		test_sk_storage();
 	if (test__start_subtest("queue"))
 		test_queue();
+
+	for (i = 0; i < ARRAY_SIZE(enum_dump_tests); i++) {
+		if (test__start_subtest(enum_dump_tests[i].name))
+			test_enum_dump(&enum_dump_tests[i]);
+	}
 }
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
index dbef014f75ac..20a169095e9c 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
@@ -952,6 +952,87 @@ static void test_btf_dump_enum_data(struct btf *btf, struct btf_dump *d,
 				sizeof(enum bpf_cmd) - 1, "", BPF_MAP_CREATE);
 }
 
+static void test_btf_dump_enum_values(void)
+{
+	LIBBPF_OPTS(btf_dump_type_data_opts, opts,
+		    .compact = true,
+		    .skip_names = true);
+	struct {
+		const char *name;
+		__u32 size;
+		__s64 enum_value;
+		union {
+			__u8 u8;
+			__u16 u16;
+			__u32 u32;
+			__u64 u64;
+		} data;
+		const char *expected;
+	} cases[] = {
+		{ "unsigned_byte", 1, 0xff, { .u8 = 0xff }, "VALUE" },
+		{ "unsigned_short", 2, 0xffff, { .u16 = 0xffff }, "VALUE" },
+		{ "unsigned_high", 4, 0x80000000LL,
+		  { .u32 = 0x80000000U }, "VALUE" },
+		{ "unsigned_max", 4, 0xffffffffLL,
+		  { .u32 = 0xffffffffU }, "VALUE" },
+		{ "signed_value", 4, -1, { .u32 = (__u32)-1 }, "VALUE" },
+		{ "signed_unknown", 4, -1, { .u32 = (__u32)-22 }, "-22" },
+		/* Old BTF stores negative enum values without setting kflag. */
+		{ "legacy_wide", 8, (__u32)-1, { .u64 = (__u64)-1 }, "VALUE" },
+		{ "unsigned_wide", 8, 0xffffffffLL,
+		  { .u64 = 0xffffffffULL }, "VALUE" },
+		/*
+		 * An unrelated 64-bit value must not match an ENUM32 by its
+		 * low 32 bits. Do not constrain the numeric fallback's width.
+		 */
+		{ "wide_unknown", 8, (__u32)-1,
+		  { .u64 = 0x1ffffffffULL }, NULL },
+		{ "unknown_byte", 1, (__u32)-1, { .u8 = (__u8)-22 }, "234" },
+		{ "unknown_short", 2, (__u32)-1, { .u16 = (__u16)-22 }, "65514" },
+		{ "unknown_word", 4, (__u32)-1,
+		  { .u32 = (__u32)-22 }, "4294967274" },
+	};
+	struct btf_dump *d;
+	struct btf *btf;
+	int ids[ARRAY_SIZE(cases)];
+	char str[STRSIZE];
+	int err, i;
+
+	btf = btf__new_empty();
+	if (!ASSERT_OK_PTR(btf, "new_empty"))
+		return;
+	for (i = 0; i < ARRAY_SIZE(cases); i++) {
+		ids[i] = btf__add_enum(btf, cases[i].name, cases[i].size);
+		if (!ASSERT_GT(ids[i], 0, "add_enum"))
+			goto free_btf;
+		err = btf__add_enum_value(btf, "VALUE", cases[i].enum_value);
+		if (!ASSERT_OK(err, "add_enum_value"))
+			goto free_btf;
+	}
+
+	d = btf_dump__new(btf, btf_dump_snprintf, str, NULL);
+	if (!ASSERT_OK_PTR(d, "btf_dump__new"))
+		goto free_btf;
+
+	for (i = 0; i < ARRAY_SIZE(cases); i++) {
+		str[0] = '\0';
+		err = btf_dump__dump_type_data(d, ids[i], &cases[i].data,
+					       cases[i].size, &opts);
+		ASSERT_EQ(err, cases[i].size, cases[i].name);
+		if (cases[i].expected) {
+			ASSERT_STREQ(str, cases[i].expected, cases[i].name);
+		} else {
+			ASSERT_NEQ(str[0], '\0', cases[i].name);
+			ASSERT_EQ(strspn(str, "0123456789"), strlen(str),
+				  cases[i].name);
+		}
+	}
+
+	btf_dump__free(d);
+free_btf:
+	btf__free(btf);
+}
+
 static void test_btf_dump_struct_data(struct btf *btf, struct btf_dump *d,
 				      char *str)
 {
@@ -1396,6 +1477,9 @@ void test_btf_dump() {
 	if (test__start_subtest("btf_dump: type_tags"))
 		test_btf_dump_type_tags();
 
+	if (test__start_subtest("btf_dump: enum_values"))
+		test_btf_dump_enum_values();
+
 	btf = libbpf_find_kernel_btf();
 	if (!ASSERT_OK_PTR(btf, "no kernel BTF found"))
 		return;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-11-01 10:32 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-01 10:32 [PATCH bpf-next v3 0/3] Normalize enum values in libbpf and bpftool Hungyu Lin
2026-11-01 10:32 ` [PATCH bpf-next v3 1/3] libbpf: Normalize enum values according to signedness Hungyu Lin
2026-11-01 10:32 ` [PATCH bpf-next v3 2/3] bpftool: Preserve unsigned enum values in data dumps Hungyu Lin
2026-11-01 10:32 ` [PATCH bpf-next v3 3/3] selftests/bpf: Cover enum signedness and legacy name matching Hungyu Lin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®