Skip to content

Commit 4c67552

Browse files
committed
btf: lazily decode Types
Use the lazy decoder to avoid decoding all types upfront. This requires adding an index from essentialName to TypeID to be able to implement AnyTypeByName. decoder has to be safe for concurrent use because we want to share the raw BTF blob and so on when copying a Spec. Signed-off-by: Lorenz Bauer <lmb@isovalent.com>
1 parent 4644d1a commit 4c67552

File tree

6 files changed

+117
-246
lines changed

6 files changed

+117
-246
lines changed

btf/btf.go

Lines changed: 12 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"math"
1313
"os"
1414
"reflect"
15-
"sync"
1615

1716
"github.com/cilium/ebpf/internal"
1817
"github.com/cilium/ebpf/internal/sys"
@@ -31,128 +30,6 @@ var (
3130
// ID represents the unique ID of a BTF object.
3231
type ID = sys.BTFID
3332

34-
// immutableTypes is a set of types which musn't be changed.
35-
type immutableTypes struct {
36-
// All types contained by the spec, not including types from the base in
37-
// case the spec was parsed from split BTF.
38-
types []Type
39-
40-
// Type IDs indexed by type.
41-
typeIDs map[Type]TypeID
42-
43-
// The ID of the first type in types.
44-
firstTypeID TypeID
45-
46-
// Types indexed by essential name.
47-
// Includes all struct flavors and types with the same name.
48-
namedTypes map[essentialName][]TypeID
49-
50-
// Byte order of the types. This affects things like struct member order
51-
// when using bitfields.
52-
byteOrder binary.ByteOrder
53-
}
54-
55-
func (s *immutableTypes) typeByID(id TypeID) (Type, bool) {
56-
if id < s.firstTypeID {
57-
return nil, false
58-
}
59-
60-
index := int(id - s.firstTypeID)
61-
if index >= len(s.types) {
62-
return nil, false
63-
}
64-
65-
return s.types[index], true
66-
}
67-
68-
// mutableTypes is a set of types which may be changed.
69-
type mutableTypes struct {
70-
imm immutableTypes
71-
mu sync.RWMutex // protects copies below
72-
copies map[Type]Type // map[orig]copy
73-
copiedTypeIDs map[Type]TypeID // map[copy]origID
74-
}
75-
76-
// add a type to the set of mutable types.
77-
//
78-
// Copies type and all of its children once. Repeated calls with the same type
79-
// do not copy again.
80-
func (mt *mutableTypes) add(typ Type, typeIDs map[Type]TypeID) Type {
81-
mt.mu.RLock()
82-
cpy, ok := mt.copies[typ]
83-
mt.mu.RUnlock()
84-
85-
if ok {
86-
// Fast path: the type has been copied before.
87-
return cpy
88-
}
89-
90-
// modifyGraphPreorder copies the type graph node by node, so we can't drop
91-
// the lock in between.
92-
mt.mu.Lock()
93-
defer mt.mu.Unlock()
94-
95-
return copyType(typ, typeIDs, mt.copies, mt.copiedTypeIDs)
96-
}
97-
98-
// copy a set of mutable types.
99-
func (mt *mutableTypes) copy() *mutableTypes {
100-
if mt == nil {
101-
return nil
102-
}
103-
104-
// Prevent concurrent modification of mt.copiedTypeIDs.
105-
mt.mu.RLock()
106-
defer mt.mu.RUnlock()
107-
108-
mtCopy := &mutableTypes{
109-
mt.imm,
110-
sync.RWMutex{},
111-
make(map[Type]Type, len(mt.copies)),
112-
make(map[Type]TypeID, len(mt.copiedTypeIDs)),
113-
}
114-
115-
copiesOfCopies := make(map[Type]Type, len(mt.copies))
116-
for orig, copy := range mt.copies {
117-
// NB: We make a copy of copy, not orig, so that changes to mutable types
118-
// are preserved.
119-
copyOfCopy := copyType(copy, mt.copiedTypeIDs, copiesOfCopies, mtCopy.copiedTypeIDs)
120-
mtCopy.copies[orig] = copyOfCopy
121-
}
122-
123-
return mtCopy
124-
}
125-
126-
func (mt *mutableTypes) typeID(typ Type) (TypeID, error) {
127-
if _, ok := typ.(*Void); ok {
128-
// Equality is weird for void, since it is a zero sized type.
129-
return 0, nil
130-
}
131-
132-
mt.mu.RLock()
133-
defer mt.mu.RUnlock()
134-
135-
id, ok := mt.copiedTypeIDs[typ]
136-
if !ok {
137-
return 0, fmt.Errorf("no ID for type %s: %w", typ, ErrNotFound)
138-
}
139-
140-
return id, nil
141-
}
142-
143-
func (mt *mutableTypes) typeByID(id TypeID) (Type, bool) {
144-
immT, ok := mt.imm.typeByID(id)
145-
if !ok {
146-
return nil, false
147-
}
148-
149-
return mt.add(immT, mt.imm.typeIDs), true
150-
}
151-
152-
func (mt *mutableTypes) typeIDsByName(name essentialName) []TypeID {
153-
return mt.imm.namedTypes[name]
154-
}
155-
15633
type elfData struct {
15734
sectionSizes map[string]uint32
15835
symbolOffsets map[elfSymbol]uint32
@@ -167,10 +44,7 @@ type elfSymbol struct {
16744
// Spec allows querying a set of Types and loading the set into the
16845
// kernel.
16946
type Spec struct {
170-
*mutableTypes
171-
172-
// String table from ELF.
173-
strings *stringTable
47+
*decoder
17448

17549
// Additional data from ELF, may be nil.
17650
elf *elfData
@@ -315,22 +189,14 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
315189

316190
func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error) {
317191
var (
192+
baseDecoder *decoder
318193
baseStrings *stringTable
319-
firstTypeID TypeID
320194
err error
321195
)
322196

323197
if base != nil {
324-
if base.imm.firstTypeID != 0 {
325-
return nil, fmt.Errorf("can't use split BTF as base")
326-
}
327-
198+
baseDecoder = base.decoder
328199
baseStrings = base.strings
329-
330-
firstTypeID, err = base.nextTypeID()
331-
if err != nil {
332-
return nil, err
333-
}
334200
}
335201

336202
buf := internal.NewBufferedSectionReader(btf, 0, math.MaxInt64)
@@ -351,55 +217,12 @@ func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error
351217
return nil, fmt.Errorf("read type section: %w", err)
352218
}
353219

354-
types, err := readAndInflateTypes(rawTypes, bo, header.TypeLen, rawStrings, base)
220+
decoder, err := newDecoder(rawTypes, bo, rawStrings, baseDecoder)
355221
if err != nil {
356222
return nil, err
357223
}
358224

359-
typeIDs, typesByName := indexTypes(types, firstTypeID)
360-
361-
return &Spec{
362-
&mutableTypes{
363-
immutableTypes{
364-
types,
365-
typeIDs,
366-
firstTypeID,
367-
typesByName,
368-
bo,
369-
},
370-
sync.RWMutex{},
371-
make(map[Type]Type),
372-
make(map[Type]TypeID),
373-
},
374-
rawStrings,
375-
nil,
376-
}, nil
377-
}
378-
379-
func indexTypes(types []Type, firstTypeID TypeID) (map[Type]TypeID, map[essentialName][]TypeID) {
380-
namedTypes := 0
381-
for _, typ := range types {
382-
if typ.TypeName() != "" {
383-
// Do a pre-pass to figure out how big types by name has to be.
384-
// Most types have unique names, so it's OK to ignore essentialName
385-
// here.
386-
namedTypes++
387-
}
388-
}
389-
390-
typeIDs := make(map[Type]TypeID, len(types))
391-
typesByName := make(map[essentialName][]TypeID, namedTypes)
392-
393-
for i, typ := range types {
394-
id := firstTypeID + TypeID(i)
395-
typeIDs[typ] = id
396-
397-
if name := newEssentialName(typ.TypeName()); name != "" {
398-
typesByName[name] = append(typesByName[name], id)
399-
}
400-
}
401-
402-
return typeIDs, typesByName
225+
return &Spec{decoder, nil}, nil
403226
}
404227

405228
func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder {
@@ -525,8 +348,7 @@ func (s *Spec) Copy() *Spec {
525348
}
526349

527350
cpy := &Spec{
528-
s.copy(),
529-
s.strings,
351+
s.decoder.Copy(),
530352
nil,
531353
}
532354

@@ -541,24 +363,14 @@ func (s *Spec) Copy() *Spec {
541363
return cpy
542364
}
543365

544-
// nextTypeID returns the next unallocated type ID or an error if there are no
545-
// more type IDs.
546-
func (s *Spec) nextTypeID() (TypeID, error) {
547-
id := s.imm.firstTypeID + TypeID(len(s.imm.types))
548-
if id < s.imm.firstTypeID {
549-
return 0, fmt.Errorf("no more type IDs")
550-
}
551-
return id, nil
552-
}
553-
554366
// TypeByID returns the BTF Type with the given type ID.
555367
//
556368
// Returns an error wrapping ErrNotFound if a Type with the given ID
557369
// does not exist in the Spec.
558370
func (s *Spec) TypeByID(id TypeID) (Type, error) {
559-
typ, ok := s.typeByID(id)
560-
if !ok {
561-
return nil, fmt.Errorf("look up type with ID %d (first ID is %d): %w", id, s.imm.firstTypeID, ErrNotFound)
371+
typ, err := s.decoder.TypeByID(id)
372+
if err != nil {
373+
return nil, fmt.Errorf("inflate type: %w", err)
562374
}
563375

564376
if s.elf == nil {
@@ -576,7 +388,7 @@ func (s *Spec) TypeByID(id TypeID) (Type, error) {
576388
//
577389
// Returns an error wrapping [ErrNotFound] if the type isn't part of the Spec.
578390
func (s *Spec) TypeID(typ Type) (TypeID, error) {
579-
return s.typeID(typ)
391+
return s.decoder.TypeID(typ)
580392
}
581393

582394
// AnyTypesByName returns a list of BTF Types with the given name.
@@ -587,7 +399,7 @@ func (s *Spec) TypeID(typ Type) (TypeID, error) {
587399
//
588400
// Returns an error wrapping ErrNotFound if no matching Type exists in the Spec.
589401
func (s *Spec) AnyTypesByName(name string) ([]Type, error) {
590-
typeIDs := s.typeIDsByName(newEssentialName(name))
402+
typeIDs := s.TypeIDsByName(newEssentialName(name))
591403
if len(typeIDs) == 0 {
592404
return nil, fmt.Errorf("type name %s: %w", name, ErrNotFound)
593405
}
@@ -698,7 +510,7 @@ func LoadSplitSpecFromReader(r io.ReaderAt, base *Spec) (*Spec, error) {
698510
// All iterates over all types.
699511
func (s *Spec) All() iter.Seq2[Type, error] {
700512
return func(yield func(Type, error) bool) {
701-
for id := s.imm.firstTypeID; ; id++ {
513+
for id := s.firstTypeID; ; id++ {
702514
typ, err := s.TypeByID(id)
703515
if errors.Is(err, ErrNotFound) {
704516
return

btf/btf_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func BenchmarkIterateVmlinux(b *testing.B) {
249249
func TestParseCurrentKernelBTF(t *testing.T) {
250250
spec := vmlinuxSpec(t)
251251

252-
if len(spec.imm.namedTypes) == 0 {
252+
if len(spec.namedTypes) == 0 {
253253
t.Fatal("Empty kernel BTF")
254254
}
255255

@@ -280,7 +280,7 @@ func TestFindVMLinux(t *testing.T) {
280280
t.Fatal("Can't load BTF:", err)
281281
}
282282

283-
if len(spec.imm.namedTypes) == 0 {
283+
if len(spec.namedTypes) == 0 {
284284
t.Fatal("Empty kernel BTF")
285285
}
286286
}

btf/core.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,8 @@ func CORERelocate(relos []*CORERelocation, targets []*Spec, bo binary.ByteOrder,
211211
resolveTargetTypeID := targets[0].TypeID
212212

213213
for _, target := range targets {
214-
if bo != target.imm.byteOrder {
215-
return nil, fmt.Errorf("can't relocate %s against %s", bo, target.imm.byteOrder)
214+
if bo != target.byteOrder {
215+
return nil, fmt.Errorf("can't relocate %s against %s", bo, target.byteOrder)
216216
}
217217
}
218218

@@ -265,7 +265,7 @@ func CORERelocate(relos []*CORERelocation, targets []*Spec, bo binary.ByteOrder,
265265

266266
var targetTypes []Type
267267
for _, target := range targets {
268-
namedTypeIDs := target.imm.namedTypes[essentialName]
268+
namedTypeIDs := target.TypeIDsByName(essentialName)
269269
targetTypes = slices.Grow(targetTypes, len(namedTypeIDs))
270270
for _, id := range namedTypeIDs {
271271
typ, err := target.TypeByID(id)

btf/core_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ func TestCORERelocation(t *testing.T) {
558558
relos = append(relos, reloInfo.relo)
559559
}
560560

561-
fixups, err := CORERelocate(relos, []*Spec{spec}, spec.imm.byteOrder, spec.TypeID)
561+
fixups, err := CORERelocate(relos, []*Spec{spec}, spec.byteOrder, spec.TypeID)
562562
if want := errs[name]; want != nil {
563563
if !errors.Is(err, want) {
564564
t.Fatal("Expected", want, "got", err)
@@ -696,7 +696,7 @@ func BenchmarkCORESkBuff(b *testing.B) {
696696
b.ReportAllocs()
697697

698698
for i := 0; i < b.N; i++ {
699-
_, err = CORERelocate([]*CORERelocation{relo}, []*Spec{spec}, spec.imm.byteOrder, spec.TypeID)
699+
_, err = CORERelocate([]*CORERelocation{relo}, []*Spec{spec}, spec.byteOrder, spec.TypeID)
700700
if err != nil {
701701
b.Fatal(err)
702702
}

btf/marshal_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ limitTypes:
101101
rebuilt, err := loadRawSpec(bytes.NewReader(buf), binary.LittleEndian, nil)
102102
qt.Assert(t, qt.IsNil(err), qt.Commentf("round tripping BTF failed"))
103103

104-
if n := len(rebuilt.imm.types); n > math.MaxUint16 {
104+
if n := len(rebuilt.offsets); n > math.MaxUint16 {
105105
t.Logf("Rebuilt BTF contains %d types which exceeds uint16, test may fail on older kernels", n)
106106
}
107107

@@ -261,7 +261,7 @@ func specFromTypes(tb testing.TB, types []Type) *Spec {
261261
func typesFromSpec(tb testing.TB, spec *Spec) []Type {
262262
tb.Helper()
263263

264-
types := make([]Type, 0, len(spec.imm.types))
264+
types := make([]Type, 0, len(spec.offsets))
265265

266266
for typ, err := range spec.All() {
267267
qt.Assert(tb, qt.IsNil(err))

0 commit comments

Comments
 (0)