[PATCH bpf-next v3 2/3] bpftool: Preserve unsigned enum values in data dumps

From: Hungyu Lin

Date: Sun Nov 01 2026 - 05:33:26 EST


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@xxxxxxxxxx>
Signed-off-by: Hungyu Lin <dennylin0707@xxxxxxxxx>
---
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