* [PATCH bpf-next v5 0/3] Normalize enum values in libbpf and bpftool
@ 2026-11-08 2:56 Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 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-08 2:56 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 in libbpf and bpftool data dumps
using the shared btf_enum_value() helper suggested by Alexei. It also
uses the helper for enum-backed map fields, where sign extension can
change the requested map_extra value.
Ordinary enums are interpreted according to kflag. When kflag is
clear, values are treated as unsigned, including BTF from producers
that did not record signedness.
The helper returns __u64, consistent with btf_enum64_value(). bpftool
casts to __s64 at signed numeric output. CO-RE relocation handling is
left for a separate change.
Changes in v5:
- Keep patches 1 and 2 and the map_extra test unchanged.
- Adopt the bpftool and libbpf tests posted by Alexei in the v4 review.
Tested on arm64 (Linux 6.12.68, GCC 13.3, Clang 18.1). libbpf and
bpftool build with -Werror under both compilers. With patch 3 alone,
the new bpftool_map_dump/enum and map_extra/enum32_high subtests and
the expanded enum_data subtest in btf_dump fail. With the full series,
all 25 subtests pass. The 22 unchanged existing subtests pass in both
cases.
v4: https://lore.kernel.org/bpf/20261107020725.36675-1-dennylin0707@gmail.com/
v3: https://lore.kernel.org/bpf/20261101103221.51534-1-dennylin0707@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 in data dumps and map fields
tools/bpf/bpftool/btf_dumper.c | 23 ++++--
tools/lib/bpf/btf.h | 6 ++
tools/lib/bpf/btf_dump.c | 2 +-
tools/lib/bpf/libbpf.c | 2 +-
.../bpf/prog_tests/bpftool_map_dump.c | 76 +++++++++++++++++++
.../selftests/bpf/prog_tests/btf_dump.c | 20 +++++
.../selftests/bpf/prog_tests/map_extra.c | 19 +++++
.../selftests/bpf/progs/test_map_extra.c | 12 +++
8 files changed, 150 insertions(+), 10 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/map_extra.c
create mode 100644 tools/testing/selftests/bpf/progs/test_map_extra.c
base-commit: 3e18218484126afb9da722080659c038cb2256ab
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH bpf-next v5 1/3] libbpf: Normalize enum values according to signedness
2026-11-08 2:56 [PATCH bpf-next v5 0/3] Normalize enum values in libbpf and bpftool Hungyu Lin
@ 2026-11-08 2:56 ` Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 2/3] bpftool: Preserve unsigned enum values in data dumps Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 3/3] selftests/bpf: Cover enum signedness in data dumps and map fields Hungyu Lin
2 siblings, 0 replies; 4+ messages in thread
From: Hungyu Lin @ 2026-11-08 2:56 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 for data-name matching and enum-backed map fields. Return __u64,
consistent with btf_enum64_value(), while sign-extending signed values.
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 | 2 +-
tools/lib/bpf/libbpf.c | 2 +-
3 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index 57f12630c5d1..d74c92056f0f 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 __u64 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 fcd97aa90589..602664a6adef 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -2378,7 +2378,7 @@ 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)
+ if ((__u64)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 0e2a93a42908..44134421bd7d 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -2650,7 +2650,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 v5 2/3] bpftool: Preserve unsigned enum values in data dumps
2026-11-08 2:56 [PATCH bpf-next v5 0/3] Normalize enum values in libbpf and bpftool Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 1/3] libbpf: Normalize enum values according to signedness Hungyu Lin
@ 2026-11-08 2:56 ` Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 3/3] selftests/bpf: Cover enum signedness in data dumps and map fields Hungyu Lin
2 siblings, 0 replies; 4+ messages in thread
From: Hungyu Lin @ 2026-11-08 2:56 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 ordinary enum data according to kflag and use btf_enum_value() for
name matching. Select the numeric writer according to kflag for both
enum kinds, casting the value to __s64 for signed output.
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>
Acked-by: Quentin Monnet <qmo@kernel.org>
Signed-off-by: Hungyu Lin <dennylin0707@gmail.com>
---
tools/bpf/bpftool/btf_dumper.c | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
index 3267163119dc..a7171622ce57 100644
--- a/tools/bpf/bpftool/btf_dumper.c
+++ b/tools/bpf/bpftool/btf_dumper.c
@@ -149,28 +149,29 @@ static int btf_dumper_enum(const struct btf_dumper *d,
const void *data)
{
const struct btf_enum *enums = btf_enum(t);
- __s64 value;
+ bool is_signed = btf_kflag(t);
+ __u64 value;
__u32 i;
switch (t->size) {
case 8:
- value = *(__s64 *)data;
+ value = *(__u64 *)data;
break;
case 4:
- value = *(__s32 *)data;
+ value = is_signed ? (__s64)*(__s32 *)data : *(__u32 *)data;
break;
case 2:
- value = *(__s16 *)data;
+ value = is_signed ? *(__s16 *)data : *(__u16 *)data;
break;
case 1:
- value = *(__s8 *)data;
+ value = is_signed ? *(__s8 *)data : *(__u8 *)data;
break;
default:
return -EINVAL;
}
for (i = 0; i < btf_vlen(t); i++) {
- if (value == enums[i].val) {
+ if (value == btf_enum_value(t, &enums[i])) {
jsonw_string(d->jw,
btf__name_by_offset(d->btf,
enums[i].name_off));
@@ -178,7 +179,10 @@ static int btf_dumper_enum(const struct btf_dumper *d,
}
}
- jsonw_int(d->jw, value);
+ if (is_signed)
+ jsonw_int(d->jw, (__s64)value);
+ else
+ jsonw_uint(d->jw, value);
return 0;
}
@@ -204,7 +208,10 @@ static int btf_dumper_enum64(const struct btf_dumper *d,
}
}
- jsonw_int(d->jw, value);
+ if (btf_kflag(t))
+ jsonw_int(d->jw, (__s64)value);
+ else
+ jsonw_uint(d->jw, value);
return 0;
}
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH bpf-next v5 3/3] selftests/bpf: Cover enum signedness in data dumps and map fields
2026-11-08 2:56 [PATCH bpf-next v5 0/3] Normalize enum values in libbpf and bpftool Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 1/3] libbpf: Normalize enum values according to signedness Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 2/3] bpftool: Preserve unsigned enum values in data dumps Hungyu Lin
@ 2026-11-08 2:56 ` Hungyu Lin
2 siblings, 0 replies; 4+ messages in thread
From: Hungyu Lin @ 2026-11-08 2:56 UTC (permalink / raw)
To: ast, bpf
Cc: andrii, qmo, daniel, yonghong.song, eddyz87, ihor.solodrai,
shuah, linux-kselftest, linux-kernel, Hungyu Lin
Check unsigned and signed enumerator names in libbpf's existing enum
data test. Create a new dumper after adding the test types, as the data
dumper does not resize its per-type state for newly added types.
Extend bpftool_map_dump with one BTF-backed array map containing four
enum fields. A single JSON dump checks unsigned and signed names and
unsigned numeric output for ordinary enums and ENUM64. Plain output
uses the same enum dumper and does not need another invocation.
Also use an open-only arena skeleton to check an enum-backed map_extra
value with bit 31 set. The object is not loaded into the kernel, so the
test needs neither arena support nor mmap.
Suggested-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Quentin Monnet <qmo@kernel.org>
Signed-off-by: Hungyu Lin <dennylin0707@gmail.com>
---
.../bpf/prog_tests/bpftool_map_dump.c | 76 +++++++++++++++++++
.../selftests/bpf/prog_tests/btf_dump.c | 20 +++++
| 19 +++++
| 12 +++
4 files changed, 127 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/map_extra.c
create mode 100644 tools/testing/selftests/bpf/progs/test_map_extra.c
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..a9584c83023c 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,85 @@ static void test_queue(void)
close(map_fd);
}
+struct enums {
+ __u8 u8_name;
+ __s8 s8_name;
+ __u32 u32_num;
+ __u64 u64_num;
+};
+
+/* An array map. Its value is struct enums with an enum behind every member. */
+static int create_enum_map(void)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+ struct btf *btf;
+ int fd = -1;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
+ return -1;
+
+ btf__add_int(btf, "int", 4, BTF_INT_SIGNED); /* [1] */
+ btf__add_enum(btf, "u8", 1); /* [2] */
+ btf__add_enum_value(btf, "U8_MAX", 0xff);
+ btf__add_enum(btf, "s8", 1); /* [3] */
+ btf__add_enum_value(btf, "S8_NEG", -1);
+ btf__add_enum(btf, "u32", 4); /* [4] */
+ btf__add_enum_value(btf, "U32_ONE", 1);
+ btf__add_enum64(btf, "u64", 8, false); /* [5] */
+ btf__add_enum64_value(btf, "U64_ONE", 1);
+ btf__add_struct(btf, "enums", sizeof(struct enums)); /* [6] */
+ btf__add_field(btf, "u8_name", 2, 0, 0);
+ btf__add_field(btf, "s8_name", 3, 8, 0);
+ btf__add_field(btf, "u32_num", 4, 32, 0);
+ btf__add_field(btf, "u64_num", 5, 64, 0);
+ 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 = 1;
+ opts.btf_value_type_id = 6;
+ fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "enums", sizeof(int),
+ sizeof(struct enums), 1, &opts);
+ ASSERT_OK_FD(fd, "bpf_map_create");
+out:
+ btf__free(btf);
+ return fd;
+}
+
+static void test_enum(void)
+{
+ struct enums value = { 0xff, -1, (__u32)-22, (1ULL << 63) | 1 };
+ char output[1024] = {};
+ int map_fd, key = 0, ret;
+
+ map_fd = create_enum_map();
+ if (map_fd < 0)
+ return;
+ if (!ASSERT_OK(bpf_map_update_elem(map_fd, &key, &value, BPF_ANY),
+ "update map"))
+ goto out;
+ if (!ASSERT_OK(bpf_obj_pin(map_fd, ENUM_PIN_PATH), "pin map"))
+ goto out;
+
+ ret = get_bpftool_command_output("-j map dump pinned " ENUM_PIN_PATH,
+ output, sizeof(output));
+ ASSERT_OK(ret, "json dump");
+ ASSERT_HAS_SUBSTR(output,
+ "{\"u8_name\":\"U8_MAX\",\"s8_name\":\"S8_NEG\","
+ "\"u32_num\":4294967274,\"u64_num\":9223372036854775809}",
+ "enum values");
+out:
+ unlink(ENUM_PIN_PATH);
+ close(map_fd);
+}
+
void test_bpftool_map_dump(void)
{
if (test__start_subtest("sk_storage"))
test_sk_storage();
if (test__start_subtest("queue"))
test_queue();
+ if (test__start_subtest("enum"))
+ test_enum();
}
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dump.c b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
index bfa74db17987..b5c90f729339 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_dump.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dump.c
@@ -944,6 +944,9 @@ static void test_btf_dump_typedef_data(struct btf *btf, struct btf_dump *d,
static void test_btf_dump_enum_data(struct btf *btf, struct btf_dump *d,
char *str)
{
+ __u32 high = 0x80000000;
+ __s32 neg = -1;
+
/* enum where enum value does (and does not) exist */
TEST_BTF_DUMP_DATA_C(btf, d, "enum", str, enum bpf_cmd, BTF_F_COMPACT,
BPF_MAP_CREATE);
@@ -975,6 +978,23 @@ static void test_btf_dump_enum_data(struct btf *btf, struct btf_dump *d,
TEST_BTF_DUMP_DATA_OVER(btf, d, "enum", str, enum bpf_cmd,
sizeof(enum bpf_cmd) - 1, "", BPF_MAP_CREATE);
+
+ /* enumerators with bit 31 set in unsigned and in signed enum */
+ ASSERT_GT(btf__add_enum(btf, "test_enum_u32", 4), 0, "add unsigned enum");
+ ASSERT_OK(btf__add_enum_value(btf, "TEST_ENUM_HIGH", 0x80000000),
+ "add TEST_ENUM_HIGH");
+ ASSERT_GT(btf__add_enum(btf, "test_enum_s32", 4), 0, "add signed enum");
+ ASSERT_OK(btf__add_enum_value(btf, "TEST_ENUM_NEG", -1), "add TEST_ENUM_NEG");
+
+ /* btf_dump__dump_type_data() doesn't resize 'd' for the new types */
+ d = btf_dump__new(btf, btf_dump_snprintf, str, NULL);
+ if (!ASSERT_OK_PTR(d, "btf_dump__new"))
+ return;
+ ASSERT_OK(btf_dump_data(btf, d, "test_enum_u32", NULL, 0, &high, 4, str,
+ "(enum test_enum_u32)TEST_ENUM_HIGH"), "dump unsigned enum");
+ ASSERT_OK(btf_dump_data(btf, d, "test_enum_s32", NULL, 0, &neg, 4, str,
+ "(enum test_enum_s32)TEST_ENUM_NEG"), "dump signed enum");
+ btf_dump__free(d);
}
static void test_btf_dump_struct_data(struct btf *btf, struct btf_dump *d,
--git a/tools/testing/selftests/bpf/prog_tests/map_extra.c b/tools/testing/selftests/bpf/prog_tests/map_extra.c
new file mode 100644
index 000000000000..cbb1881ba693
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/map_extra.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "test_map_extra.skel.h"
+
+void test_map_extra(void)
+{
+ struct test_map_extra *skel;
+
+ if (!test__start_subtest("enum32_high"))
+ return;
+
+ skel = test_map_extra__open();
+ if (!ASSERT_OK_PTR(skel, "test_map_extra__open"))
+ return;
+
+ ASSERT_EQ(bpf_map__map_extra(skel->maps.arena), 0x80000000ULL,
+ "map_extra");
+ test_map_extra__destroy(skel);
+}
--git a/tools/testing/selftests/bpf/progs/test_map_extra.c b/tools/testing/selftests/bpf/progs/test_map_extra.c
new file mode 100644
index 000000000000..b6aa2341c115
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_map_extra.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 1);
+ __ulong(map_extra, 0x80000000U);
+} arena SEC(".maps");
+
+char LICENSE[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-08 2:57 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-08 2:56 [PATCH bpf-next v5 0/3] Normalize enum values in libbpf and bpftool Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 1/3] libbpf: Normalize enum values according to signedness Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 2/3] bpftool: Preserve unsigned enum values in data dumps Hungyu Lin
2026-11-08 2:56 ` [PATCH bpf-next v5 3/3] selftests/bpf: Cover enum signedness in data dumps and map fields 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®