From d29bed7fb1b0c217f0827775c2b93437a87c28af Mon Sep 17 00:00:00 2001 From: "nikos.nikolakakis" Date: Wed, 20 Aug 2025 08:54:20 +0300 Subject: [PATCH 1/2] map: parse map_extra field and expose in MapSpec Bloom filter maps were introduced in Linux 5.16 and use the map_extra field to specify the number of hash functions (lower 4 bits, 1-15 range). Parse this field from the ELF and pass it through to the kernel. Fixes #669 Signed-off-by: nikos.nikolakakis Co-authored-by: Timo Beckers --- elf_reader.go | 7 ++++++- map.go | 6 ++++++ map_test.go | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/elf_reader.go b/elf_reader.go index e2b21fa57..1e02ef8ee 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -877,6 +877,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b mapType MapType flags, maxEntries uint32 pinType PinType + mapExtra uint64 innerMapSpec *MapSpec contents []MapKV err error @@ -1035,7 +1036,10 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b } case "map_extra": - return nil, fmt.Errorf("BTF map definition: field %s: %w", member.Name, ErrNotSupported) + mapExtra, err = uintFromBTF(member.Type) + if err != nil { + return nil, fmt.Errorf("resolving map_extra: %w", err) + } default: return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name) @@ -1067,6 +1071,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b InnerMap: innerMapSpec, Contents: contents, Tags: slices.Clone(v.Tags), + MapExtra: mapExtra, }, nil } diff --git a/map.go b/map.go index e8720c407..384d71f37 100644 --- a/map.go +++ b/map.go @@ -78,6 +78,11 @@ type MapSpec struct { // InnerMap is used as a template for ArrayOfMaps and HashOfMaps InnerMap *MapSpec + // MapExtra is an opaque field whose meaning is map-specific. + // + // Available from 5.16. + MapExtra uint64 + // Extra trailing bytes found in the ELF map definition when using structs // larger than libbpf's bpf_map_def. nil if no trailing bytes were present. // Must be nil or empty before instantiating the MapSpec into a Map. @@ -534,6 +539,7 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) { MaxEntries: spec.MaxEntries, MapFlags: spec.Flags, NumaNode: spec.NumaNode, + MapExtra: spec.MapExtra, } if inner != nil { diff --git a/map_test.go b/map_test.go index ece61230b..0b613634d 100644 --- a/map_test.go +++ b/map_test.go @@ -93,6 +93,7 @@ func TestMapSpecCopy(t *testing.T) { 1, []MapKV{{1, 2}}, // Can't copy Contents, use value types nil, // InnerMap + 0, // MapExtra bytes.NewReader(nil), &btf.Int{}, &btf.Int{}, From 6c09c280e2adba2c75581aa4e9cbbfeb66d6affa Mon Sep 17 00:00:00 2001 From: Timo Beckers Date: Tue, 9 Sep 2025 14:12:33 +0200 Subject: [PATCH 2/2] map: preliminary support for arenas This commit teaches ebpf-go to understand the __ulong BTF map definition macro and uses it to specify a 64-bit value for map_extra in an arena map, defining the start of the mmap region for the Collection's arena. Signed-off-by: Timo Beckers --- Makefile | 1 + elf_reader.go | 51 +++++++++++++++++++++++++----------------- elf_reader_test.go | 26 +++++++++++++++++++-- testdata/arena-eb.elf | Bin 0 -> 1120 bytes testdata/arena-el.elf | Bin 0 -> 1120 bytes testdata/arena.c | 10 +++++++++ testdata/common.h | 6 +++++ 7 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 testdata/arena-eb.elf create mode 100644 testdata/arena-el.elf create mode 100644 testdata/arena.c diff --git a/Makefile b/Makefile index 45462e8d5..0ffc844eb 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ TARGETS := \ testdata/constants \ testdata/errors \ testdata/variables \ + testdata/arena \ btf/testdata/relocs \ btf/testdata/relocs_read \ btf/testdata/relocs_read_tgt \ diff --git a/elf_reader.go b/elf_reader.go index 1e02ef8ee..f2c9196b7 100644 --- a/elf_reader.go +++ b/elf_reader.go @@ -873,9 +873,9 @@ func (ec *elfCode) loadBTFMaps() error { func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *btf.Spec, name string, inner bool) (*MapSpec, error) { var ( key, value btf.Type - keySize, valueSize uint32 + keySize, valueSize uint64 mapType MapType - flags, maxEntries uint32 + flags, maxEntries uint64 pinType PinType mapExtra uint64 innerMapSpec *MapSpec @@ -921,7 +921,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("can't get size of BTF key: %w", err) } - keySize = uint32(size) + keySize = uint64(size) case "value": if valueSize != 0 { @@ -940,7 +940,7 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return nil, fmt.Errorf("can't get size of BTF value: %w", err) } - valueSize = uint32(size) + valueSize = uint64(size) case "key_size": // Key needs to be nil and keySize needs to be 0 for key_size to be @@ -1061,10 +1061,10 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b return &MapSpec{ Name: sanitizeName(name, -1), Type: MapType(mapType), - KeySize: keySize, - ValueSize: valueSize, - MaxEntries: maxEntries, - Flags: flags, + KeySize: uint32(keySize), + ValueSize: uint32(valueSize), + MaxEntries: uint32(maxEntries), + Flags: uint32(flags), Key: key, Value: value, Pinning: pinType, @@ -1075,20 +1075,31 @@ func mapSpecFromBTF(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *b }, nil } -// uintFromBTF resolves the __uint macro, which is a pointer to a sized -// array, e.g. for int (*foo)[10], this function will return 10. -func uintFromBTF(typ btf.Type) (uint32, error) { - ptr, ok := typ.(*btf.Pointer) - if !ok { - return 0, fmt.Errorf("not a pointer: %v", typ) - } +// uintFromBTF resolves the __uint and __ulong macros. +// +// __uint emits a pointer to a sized array. For int (*foo)[10], this function +// will return 10. +// +// __ulong emits an enum with a single value that can represent a 64-bit +// integer. The first (and only) enum value is returned. +func uintFromBTF(typ btf.Type) (uint64, error) { + switch t := typ.(type) { + case *btf.Pointer: + arr, ok := t.Target.(*btf.Array) + if !ok { + return 0, fmt.Errorf("not a pointer to array: %v", typ) + } + return uint64(arr.Nelems), nil - arr, ok := ptr.Target.(*btf.Array) - if !ok { - return 0, fmt.Errorf("not a pointer to array: %v", typ) - } + case *btf.Enum: + if len(t.Values) == 0 { + return 0, errors.New("enum has no values") + } + return t.Values[0].Value, nil - return arr.Nelems, nil + default: + return 0, fmt.Errorf("not a pointer or enum: %v", typ) + } } // resolveBTFArrayMacro resolves the __array macro, which declares an array diff --git a/elf_reader_test.go b/elf_reader_test.go index 1d0a52aaf..d7909c864 100644 --- a/elf_reader_test.go +++ b/elf_reader_test.go @@ -943,6 +943,30 @@ func TestIPRoute2Compat(t *testing.T) { coll.Close() } +func TestArena(t *testing.T) { + file := testutils.NativeFile(t, "testdata/arena-%s.elf") + coll, err := LoadCollectionSpec(file) + qt.Assert(t, qt.IsNil(err)) + + want := &CollectionSpec{ + Maps: map[string]*MapSpec{ + "arena": { + Name: "arena", + Type: Arena, + MaxEntries: 100, + Flags: sys.BPF_F_MMAPABLE, + MapExtra: 1 << 44, + }, + }, + Programs: map[string]*ProgramSpec{}, + Variables: map[string]*VariableSpec{}, + } + qt.Assert(t, qt.CmpEquals(coll, want, csCmpOpts)) + + testutils.SkipOnOldKernel(t, "6.9", "arena maps") + mustNewCollection(t, coll, nil) +} + var ( elfPath = flag.String("elfs", os.Getenv("CI_KERNEL_SELFTESTS"), "`Path` containing libbpf-compatible ELFs (defaults to $CI_KERNEL_SELFTESTS)") elfPattern = flag.String("elf-pattern", "*.o", "Glob `pattern` for object files that should be tested") @@ -997,8 +1021,6 @@ func TestLibBPFCompat(t *testing.T) { t.Skip("Skipping since the test generates dynamic BTF") case "test_static_linked": t.Skip("Skipping since .text contains 'subprog' twice") - case "bloom_filter_map", "bloom_filter_bench": - t.Skip("Skipping due to missing MapExtra field in MapSpec") case "netif_receive_skb", "local_kptr_stash", "local_kptr_stash_fail", diff --git a/testdata/arena-eb.elf b/testdata/arena-eb.elf new file mode 100644 index 0000000000000000000000000000000000000000..e6e05220744fd20ac4c6ee8d9f024780ad749bbf GIT binary patch literal 1120 zcmb_b%}&BV5Z+c$)c6-K7%wHBj7ftB6XU5EAu%DwAQ!kSHB>@U5KAH8Mc%|$@lm|{ z03P+5-JMzyJvqrY^UeRlZlB84ifvmW5lg&-rvga^#BM3iGdlNMoysfq_N+$bb;QbH zG$5Mf+d8-eW0{2+Sck$CG(u$=pa5jK4%`8F#xn7rJ=B)e(B1_#qD}E#{hO&~k9;Av zmBs`b;sA$$Sub4hEZ_mBzzQ$}E`U|w1-ROTf~*15SKs>TU#A4b5#4?)q%75HrG~t@ zY?fucab1=Yu~EPKFbd?@4@W^!#M40_dj3G(g??uUHjzO;j=BNyfea>b7$)*e}^h{bc03rMQ6_Bgd1bFndye%xF`K~I~sO7!X3s@?BBvpdt}Y_CNOGm-!>@39DSMP)quwII%Dcv~>-7WXepTjdnjJ@3Sg$5ngry$t%2qlQ3YvtAb&g_~a)+?30T? zkMI6Nwvk)OelE>&gu5n>#Jly}WpY#8EqA(6H0UV5-A;PpExElkN&RcshYnlmo&=G5 z*f^F8|7kW*uNR?kPgVADzYsUy$b|XK$nA(ud0I~@es^B@+j4zz22V(iSYvW~rbD~X z#hEy9#R~FRNEZ9=fWtfG0ePv&`(;h_4sxyOZ{mxHO`U$;!8`*aaiqS`eqz>5R0wrc R%g&n=)+EYDhUstq^Is*BL#hA( literal 0 HcmV?d00001 diff --git a/testdata/arena.c b/testdata/arena.c new file mode 100644 index 000000000..85d6555f8 --- /dev/null +++ b/testdata/arena.c @@ -0,0 +1,10 @@ +/* This file excercises the ELF loader. It is not a valid BPF program. */ + +#include "common.h" + +struct { + __uint(type, BPF_MAP_TYPE_ARENA); + __uint(map_flags, BPF_F_MMAPABLE); + __uint(max_entries, 100); /* number of pages */ + __ulong(map_extra, 0x1ull << 44); /* start of mmap region */ +} arena __section(".maps"); diff --git a/testdata/common.h b/testdata/common.h index a8c81a0cb..ffe55c115 100644 --- a/testdata/common.h +++ b/testdata/common.h @@ -13,10 +13,14 @@ enum libbpf_tristate { TRI_MODULE = 2, }; +#define ___bpf_concat(a, b) ____bpf_concat(a, b) +#define ____bpf_concat(a, b) a ## b + #define __section(NAME) __attribute__((section(NAME), used)) #define __uint(name, val) int(*name)[val] #define __type(name, val) typeof(val) *name #define __array(name, val) typeof(val) *name[] +#define __ulong(name, val) enum { ___bpf_concat(__unique_value, __COUNTER__) = val } name #define __kconfig __attribute__((section(".kconfig"))) #define __ksym __attribute__((section(".ksyms"))) @@ -40,8 +44,10 @@ enum libbpf_tristate { #define BPF_MAP_TYPE_PERF_EVENT_ARRAY (4) #define BPF_MAP_TYPE_ARRAY_OF_MAPS (12) #define BPF_MAP_TYPE_HASH_OF_MAPS (13) +#define BPF_MAP_TYPE_ARENA (33) #define BPF_F_NO_PREALLOC (1U << 0) +#define BPF_F_MMAPABLE (1U << 10) #define BPF_F_CURRENT_CPU (0xffffffffULL) /* From tools/lib/bpf/libbpf.h */