Repository: cilium/ebpf
Pull request #581 (2022-02-22 14:44:09)

collection: Rename CollectionSpec.BTF to .Types, copy in Copy()
In preparation of renaming the btf.Spec type itself down the line, once .BTF.ext data has been lifted into per-insn metadata, cement the interface that will be used to query the CollectionSpec for its type information.

A CollectionSpec now provides Maps, Programs and Types.

---

Also fix CollectionSpec.Copy() not copying Types.


Patch:
diff --git a/cmd/bpf2go/output.go b/cmd/bpf2go/output.go
index 6eeafc3..924946c 100644
--- a/cmd/bpf2go/output.go
+++ b/cmd/bpf2go/output.go
@@ -255,7 +255,7 @@ func output(args outputArgs) error {
 	}
 
 	// Collect any types which we've been asked for explicitly.
-	cTypes, err := collectCTypes(spec.BTF, args.cTypes)
+	cTypes, err := collectCTypes(spec.Types, args.cTypes)
 	if err != nil {
 		return err
 	}
diff --git a/collection.go b/collection.go
index 0e0dc53..6d5f7cd 100644
--- a/collection.go
+++ b/collection.go
@@ -27,8 +27,8 @@ type CollectionSpec struct {
 	Maps     map[string]*MapSpec
 	Programs map[string]*ProgramSpec
 
-	// The BTF used by maps and programs.
-	BTF *btf.Spec
+	// Types holds type information about Maps and Programs.
+	Types *btf.Spec
 
 	// ByteOrder specifies whether the ELF was compiled for
 	// big-endian or little-endian architectures.
@@ -45,6 +45,7 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
 		Maps:      make(map[string]*MapSpec, len(cs.Maps)),
 		Programs:  make(map[string]*ProgramSpec, len(cs.Programs)),
 		ByteOrder: cs.ByteOrder,
+		Types:     cs.Types.Copy(),
 	}
 
 	for name, spec := range cs.Maps {
@@ -412,7 +413,7 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
 		return nil, fmt.Errorf("missing map %s", mapName)
 	}
 
-	if mapSpec.BTF != nil && cl.coll.BTF != mapSpec.BTF.Spec {
+	if mapSpec.BTF != nil && cl.coll.Types != mapSpec.BTF.Spec {
 		return nil, fmt.Errorf("map %s: BTF doesn't match collection", mapName)
 	}
 
@@ -441,7 +442,7 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
 		return nil, fmt.Errorf("cannot load program %s: program type is unspecified", progName)
 	}
 
-	if progSpec.BTF != nil && cl.coll.BTF != progSpec.BTF.Spec() {
+	if progSpec.BTF != nil && cl.coll.Types != progSpec.BTF.Spec() {
 		return nil, fmt.Errorf("program %s: BTF doesn't match collection", progName)
 	}
 
diff --git a/internal/btf/btf.go b/internal/btf/btf.go
index 3d72be2..2eef409 100644
--- a/internal/btf/btf.go
+++ b/internal/btf/btf.go
@@ -10,6 +10,7 @@ import (
 	"math"
 	"os"
 	"reflect"
+	"sort"
 	"sync"
 
 	"github.com/cilium/ebpf/internal"
@@ -42,7 +43,7 @@ type Spec struct {
 	// Includes all struct flavors and types with the same name.
 	namedTypes map[essentialName][]Type
 
-	// Data from .BTF.ext.
+	// Data from .BTF.ext. indexed by function name.
 	funcInfos map[string]FuncInfo
 	lineInfos map[string]LineInfos
 	coreRelos map[string]CoreRelos
@@ -193,67 +194,99 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
 // transforms them to be indexed by function. Retrieves function names from
 // the BTF spec.
 func (spec *Spec) splitExtInfos(info *extInfo) error {
-
 	ofi := make(map[string]FuncInfo)
 	oli := make(map[string]LineInfos)
 	ocr := make(map[string]CoreRelos)
 
-	for secName, secFuncs := range info.funcInfos {
+	for secName, secFuncInfos := range info.funcInfos {
 		// Collect functions from each section and organize them by name.
-		for _, fi := range secFuncs {
-			name, err := fi.Name(spec)
+		var funcs []*Func
+		for _, fi := range secFuncInfos {
+			typ, err := spec.TypeByID(fi.TypeID)
 			if err != nil {
-				return fmt.Errorf("looking up function name: %w", err)
+				return err
+			}
+
+			fn, ok := typ.(*Func)
+			if !ok {
+				return fmt.Errorf("type ID %d is a %T, but expected a Func", fi.TypeID, typ)
 			}
 
-			// FuncInfo offsets are scoped to the ELF section. Zero them out
-			// since they are meaningless outside of that context. The linker
-			// will determine the offset of the function within the final
-			// instruction stream before handing it off to the kernel.
-			fi.InsnOff = 0
+			// C doesn't have anonymous functions, but check just in case.
+			if fn.Name == "" {
+				return fmt.Errorf("func with type ID %d doesn't have a name", fi.TypeID)
+			}
+
+			ofi[fn.Name] = FuncInfo{fn}
+			funcs = append(funcs, fn)
+		}
 
-			ofi[name] = fi
+		sort.Slice(secFuncInfos, func(i, j int) bool {
+			return secFuncInfos[i].InsnOff < secFuncInfos[j].InsnOff
+		})
+
+		// Consider an ELF section that contains 3 functions (a, b, c)
+		// at offsets 0, 10 and 15 respectively. Offset 5 will return function a,
+		// offset 12 will return b, offset >= 15 will return c, etc.
+		funcForInstruction := func(offset uint32) (fn *Func, fnOffset uint32) {
+			for i, fi := range secFuncInfos {
+				if fi.InsnOff > offset {
+					break
+				}
+				fn = funcs[i]
+				fnOffset = fi.InsnOff
+			}
+			return fn, fnOffset
 		}
 
 		// Attribute LineInfo records to their respective functions, if any.
 		if lines := info.lineInfos[secName]; lines != nil {
 			for _, li := range lines {
-				fi := secFuncs.funcForOffset(li.InsnOff)
-				if fi == nil {
+				fn, fnOffset := funcForInstruction(li.InsnOff)
+				if fn == nil {
 					return fmt.Errorf("section %s: error looking up FuncInfo for LineInfo %v", secName, li)
 				}
 
-				// Offsets are ELF section-scoped, make them function-scoped by
-				// subtracting the function's start offset.
-				li.InsnOff -= fi.InsnOff
+				line, err := spec.strings.Lookup(li.LineOff)
+				if err != nil {
+					return fmt.Errorf("lookup of line: %w", err)
+				}
 
-				name, err := fi.Name(spec)
+				fileName, err := spec.strings.Lookup(li.FileNameOff)
 				if err != nil {
-					return fmt.Errorf("looking up function name: %w", err)
+					return fmt.Errorf("lookup of filename: %w", err)
 				}
 
-				oli[name] = append(oli[name], li)
+				lineNumber := li.LineCol >> bpfLineShift
+				lineColumn := li.LineCol & bpfColumnMax
+
+				oli[fn.Name] = append(oli[fn.Name], LineInfo{
+					fileName,
+					line,
+					lineNumber,
+					lineColumn,
+					// Offsets are ELF section-scoped, make them function-scoped by
+					// subtracting the function's start offset.
+					li.InsnOff - fnOffset,
+					li.FileNameOff,
+					li.LineOff,
+				})
 			}
 		}
 
 		// Attribute CO-RE relocations to their respective functions, if any.
 		if relos := info.relos[secName]; relos != nil {
 			for _, r := range relos {
-				fi := secFuncs.funcForOffset(r.insnOff)
-				if fi == nil {
+				fn, fnOffset := funcForInstruction(r.insnOff)
+				if fn == nil {
 					return fmt.Errorf("section %s: error looking up FuncInfo for CO-RE relocation %v", secName, r)
 				}
 
 				// Offsets are ELF section-scoped, make them function-scoped by
 				// subtracting the function's start offset.
-				r.insnOff -= fi.InsnOff
-
-				name, err := fi.Name(spec)
-				if err != nil {
-					return fmt.Errorf("looking up function name: %w", err)
-				}
+				r.insnOff -= fnOffset
 
-				ocr[name] = append(ocr[name], r)
+				ocr[fn.Name] = append(ocr[fn.Name], r)
 			}
 		}
 	}
@@ -588,7 +621,7 @@ func (s *Spec) Program(name string) (*Program, error) {
 		return nil, fmt.Errorf("BTF for function %s: %w", name, ErrNoExtendedInfo)
 	}
 
-	funcInfos, funcOK := s.funcInfos[name]
+	funcInfo, funcOK := s.funcInfos[name]
 	lineInfos, lineOK := s.lineInfos[name]
 	relos, coreOK := s.coreRelos[name]
 
@@ -596,7 +629,7 @@ func (s *Spec) Program(name string) (*Program, error) {
 		return nil, fmt.Errorf("no extended BTF info for function %s", name)
 	}
 
-	return &Program{s, funcInfos, lineInfos, relos}, nil
+	return &Program{s, funcInfo, lineInfos, relos}, nil
 }
 
 // TypeByID returns the BTF Type with the given type ID.
diff --git a/internal/btf/ext_info.go b/internal/btf/ext_info.go
index c4da1e4..2be0622 100644
--- a/internal/btf/ext_info.go
+++ b/internal/btf/ext_info.go
@@ -15,8 +15,8 @@ import (
 //
 // It is indexed per section.
 type extInfo struct {
-	funcInfos map[string]FuncInfos
-	lineInfos map[string]LineInfos
+	funcInfos map[string][]bpfFuncInfo
+	lineInfos map[string][]bpfLineInfo
 	relos     map[string]CoreRelos
 }
 
@@ -197,80 +197,43 @@ func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
 	return recordSize, nil
 }
 
-// FuncInfo represents the location and type ID of a function in a BPF ELF.
+// The size of a FuncInfo in BTF wire format.
+var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))
+
 type FuncInfo struct {
+	fn *Func
+}
+
+type bpfFuncInfo struct {
 	// Instruction offset of the function within an ELF section.
-	// Always zero after parsing a funcinfo from an ELF, instruction streams
-	// are split on function boundaries.
 	InsnOff uint32
 	TypeID  TypeID
 }
 
-// Name looks up the FuncInfo's corresponding function name in the given spec.
-func (fi FuncInfo) Name(spec *Spec) (string, error) {
-	// Look up function name based on type ID.
-	typ, err := spec.TypeByID(fi.TypeID)
-	if err != nil {
-		return "", fmt.Errorf("looking up type by ID: %w", err)
-	}
-	if _, ok := typ.(*Func); !ok {
-		return "", fmt.Errorf("type ID %d is a %T, but expected a Func", fi.TypeID, typ)
-	}
-
-	// C doesn't have anonymous functions, but check just in case.
-	if name := typ.TypeName(); name != "" {
-		return name, nil
-	}
-
-	return "", fmt.Errorf("Func with type ID %d doesn't have a name", fi.TypeID)
-}
-
-// Marshal writes the binary representation of the FuncInfo to w.
-// The function offset is converted from bytes to instructions.
-func (fi FuncInfo) Marshal(w io.Writer, offset uint64) error {
-	fi.InsnOff += uint32(offset)
-	// The kernel expects offsets in number of raw bpf instructions,
-	// while the ELF tracks it in bytes.
-	fi.InsnOff /= asm.InstructionSize
-	return binary.Write(w, internal.NativeEndian, fi)
+func (fi *FuncInfo) Func() *Func {
+	return fi.fn
 }
 
-type FuncInfos []FuncInfo
-
-// funcForOffset returns the function that the instruction at the given
-// ELF section offset belongs to.
+// Marshal into the BTF wire format.
 //
-// For example, consider an ELF section that contains 3 functions (a, b, c)
-// at offsets 0, 10 and 15 respectively. Offset 5 will return function a,
-// offset 12 will return b, offset >= 15 will return c, etc.
-func (infos FuncInfos) funcForOffset(offset uint32) *FuncInfo {
-	for n, fi := range infos {
-		// Iterator went past the offset the caller is looking for,
-		// no point in continuing the search.
-		if offset < fi.InsnOff {
-			return nil
-		}
-
-		// If there is no next item in the list, or if the given offset
-		// is smaller than the next function, the offset belongs to
-		// the current function.
-		if n+1 >= len(infos) || offset < infos[n+1].InsnOff {
-			return &fi
-		}
+// The offset is converted from bytes to instructions.
+func (fi *FuncInfo) Marshal(w io.Writer, offset uint64) error {
+	bfi := bpfFuncInfo{
+		InsnOff: uint32(offset / asm.InstructionSize),
+		TypeID:  fi.fn.TypeID,
 	}
-
-	return nil
+	return binary.Write(w, internal.NativeEndian, &bfi)
 }
 
 // parseLineInfos parses a func_info sub-section within .BTF.ext ito a map of
 // func infos indexed by section name.
-func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]FuncInfos, error) {
+func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string][]bpfFuncInfo, error) {
 	recordSize, err := parseExtInfoRecordSize(r, bo)
 	if err != nil {
 		return nil, err
 	}
 
-	result := make(map[string]FuncInfos)
+	result := make(map[string][]bpfFuncInfo)
 	for {
 		secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
 		if errors.Is(err, io.EOF) {
@@ -292,11 +255,11 @@ func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[
 // parseFuncInfoRecords parses a stream of func_infos into a funcInfos.
 // These records appear after a btf_ext_info_sec header in the func_info
 // sub-section of .BTF.ext.
-func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) (FuncInfos, error) {
-	var out FuncInfos
-	var fi FuncInfo
+func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfFuncInfo, error) {
+	var out []bpfFuncInfo
+	var fi bpfFuncInfo
 
-	if exp, got := uint32(binary.Size(fi)), recordSize; exp != got {
+	if exp, got := FuncInfoSize, recordSize; exp != got {
 		// BTF blob's record size is longer than we know how to parse.
 		return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got)
 	}
@@ -310,44 +273,98 @@ func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
 			return nil, fmt.Errorf("offset %v is not aligned with instruction size", fi.InsnOff)
 		}
 
+		// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
+		// Convert as early as possible.
+		fi.InsnOff /= asm.InstructionSize
+
 		out = append(out, fi)
 	}
 
 	return out, nil
 }
 
+var LineInfoSize = uint32(binary.Size(bpfLineInfo{}))
+
 // LineInfo represents the location and contents of a single line of source
 // code a BPF ELF was compiled from.
 type LineInfo struct {
-	// Instruction offset of the function within an ELF section.
-	// After parsing a LineInfo from an ELF, this offset is relative to
-	// the function body instead of an ELF section.
+	fileName   string
+	line       string
+	lineNumber uint32
+	lineColumn uint32
+
+	// TODO: We should get rid of the fields below, but for that we need to be
+	// able to write BTF.
+
+	// Instruction offset of the line within its enclosing function, in instructions.
+	insnOff     uint32
+	fileNameOff uint32
+	lineOff     uint32
+}
+
+// Constants for the format of bpfLineInfo.LineCol.
+const (
+	bpfLineShift = 10
+	bpfLineMax   = (1 << (32 - bpfLineShift)) - 1
+	bpfColumnMax = (1 << bpfLineShift) - 1
+)
+
+type bpfLineInfo struct {
+	// Instruction offset of the line within the whole instruction stream, in instructions.
 	InsnOff     uint32
 	FileNameOff uint32
 	LineOff     uint32
 	LineCol     uint32
 }
 
+func (li *LineInfo) FileName() string {
+	return li.fileName
+}
+
+func (li *LineInfo) Line() string {
+	return li.line
+}
+
+func (li *LineInfo) LineNumber() uint32 {
+	return li.lineNumber
+}
+
+func (li *LineInfo) LineColumn() uint32 {
+	return li.lineColumn
+}
+
+func (li *LineInfo) String() string {
+	return li.line
+}
+
 // Marshal writes the binary representation of the LineInfo to w.
 // The instruction offset is converted from bytes to instructions.
-func (li LineInfo) Marshal(w io.Writer, offset uint64) error {
-	li.InsnOff += uint32(offset)
-	// The kernel expects offsets in number of raw bpf instructions,
-	// while the ELF tracks it in bytes.
-	li.InsnOff /= asm.InstructionSize
-	return binary.Write(w, internal.NativeEndian, li)
-}
+func (li *LineInfo) Marshal(w io.Writer, offset uint64) error {
+	if li.lineNumber > bpfLineMax {
+		return fmt.Errorf("line %d exceeds %d", li.lineNumber, bpfLineMax)
+	}
 
-type LineInfos []LineInfo
+	if li.lineColumn > bpfColumnMax {
+		return fmt.Errorf("column %d exceeds %d", li.lineColumn, bpfColumnMax)
+	}
 
-// Marshal writes the binary representation of the LineInfos to w.
-func (li LineInfos) Marshal(w io.Writer, off uint64) error {
-	if len(li) == 0 {
-		return nil
+	bli := bpfLineInfo{
+		li.insnOff + uint32(offset/asm.InstructionSize),
+		li.fileNameOff,
+		li.lineOff,
+		(li.lineNumber << bpfLineShift) | li.lineColumn,
 	}
+	return binary.Write(w, internal.NativeEndian, &bli)
+}
+
+type LineInfos []LineInfo
 
+// Marshal writes the BTF wire format of the LineInfos to w.
+//
+// offset is the start of the enclosing function in bytes.
+func (li LineInfos) Marshal(w io.Writer, offset uint64) error {
 	for _, info := range li {
-		if err := info.Marshal(w, off); err != nil {
+		if err := info.Marshal(w, offset); err != nil {
 			return err
 		}
 	}
@@ -357,13 +374,13 @@ func (li LineInfos) Marshal(w io.Writer, off uint64) error {
 
 // parseLineInfos parses a line_info sub-section within .BTF.ext ito a map of
 // line infos indexed by section name.
-func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]LineInfos, error) {
+func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string][]bpfLineInfo, error) {
 	recordSize, err := parseExtInfoRecordSize(r, bo)
 	if err != nil {
 		return nil, err
 	}
 
-	result := make(map[string]LineInfos)
+	result := make(map[string][]bpfLineInfo)
 	for {
 		secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
 		if errors.Is(err, io.EOF) {
@@ -385,9 +402,9 @@ func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[
 // parseLineInfoRecords parses a stream of line_infos into a lineInfos.
 // These records appear after a btf_ext_info_sec header in the line_info
 // sub-section of .BTF.ext.
-func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) (LineInfos, error) {
-	var out LineInfos
-	var li LineInfo
+func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) ([]bpfLineInfo, error) {
+	var out []bpfLineInfo
+	var li bpfLineInfo
 
 	if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
 		// BTF blob's record size is longer than we know how to parse.
@@ -403,6 +420,10 @@ func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, r
 			return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
 		}
 
+		// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
+		// Convert as early as possible.
+		li.InsnOff /= asm.InstructionSize
+
 		out = append(out, li)
 	}
 
diff --git a/linker.go b/linker.go
index f9cf84c..6f2357d 100644
--- a/linker.go
+++ b/linker.go
@@ -2,11 +2,9 @@ package ebpf
 
 import (
 	"bytes"
-	"encoding/binary"
 	"fmt"
 
 	"github.com/cilium/ebpf/asm"
-	"github.com/cilium/ebpf/internal/btf"
 )
 
 // The linker is responsible for resolving bpf-to-bpf calls between programs
@@ -88,9 +86,9 @@ func marshalFuncInfos(layout []reference) ([]byte, error) {
 		return nil, nil
 	}
 
-	buf := bytes.NewBuffer(make([]byte, 0, binary.Size(&btf.FuncInfo{})*len(layout)))
+	var buf bytes.Buffer
 	for _, sym := range layout {
-		if err := sym.spec.BTF.FuncInfo.Marshal(buf, sym.offset); err != nil {
+		if err := sym.spec.BTF.FuncInfo.Marshal(&buf, sym.offset); err != nil {
 			return nil, fmt.Errorf("marshaling prog %s func info: %w", sym.spec.Name, err)
 		}
 	}
@@ -104,9 +102,9 @@ func marshalLineInfos(layout []reference) ([]byte, error) {
 		return nil, nil
 	}
 
-	buf := bytes.NewBuffer(make([]byte, 0, binary.Size(&btf.LineInfo{})*len(layout)))
+	var buf bytes.Buffer
 	for _, sym := range layout {
-		if err := sym.spec.BTF.LineInfos.Marshal(buf, sym.offset); err != nil {
+		if err := sym.spec.BTF.LineInfos.Marshal(&buf, sym.offset); err != nil {
 			return nil, fmt.Errorf("marshaling prog %s line infos: %w", sym.spec.Name, err)
 		}
 	}
diff --git a/prog.go b/prog.go
index 7490b38..ccac3f2 100644
--- a/prog.go
+++ b/prog.go
@@ -311,16 +311,16 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, handles *hand
 			if err != nil {
 				return nil, err
 			}
-			attr.FuncInfoRecSize = uint32(binary.Size(btf.FuncInfo{}))
-			attr.FuncInfoCnt = uint32(len(fib)) / attr.FuncInfoRecSize
+			attr.FuncInfoRecSize = btf.FuncInfoSize
+			attr.FuncInfoCnt = uint32(len(fib)) / btf.FuncInfoSize
 			attr.FuncInfo = sys.NewSlicePointer(fib)
 
 			lib, err := marshalLineInfos(layout)
 			if err != nil {
 				return nil, err
 			}
-			attr.LineInfoRecSize = uint32(binary.Size(btf.LineInfo{}))
-			attr.LineInfoCnt = uint32(len(lib)) / attr.LineInfoRecSize
+			attr.LineInfoRecSize = btf.LineInfoSize
+			attr.LineInfoCnt = uint32(len(lib)) / btf.LineInfoSize
 			attr.LineInfo = sys.NewSlicePointer(lib)
 		}
 	}


Test patch:
diff --git a/collection_test.go b/collection_test.go
index 13ac84d..714e74a 100644
--- a/collection_test.go
+++ b/collection_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"github.com/cilium/ebpf/asm"
+	"github.com/cilium/ebpf/internal/btf"
 	"github.com/cilium/ebpf/internal/testutils"
 )
 
@@ -66,6 +67,7 @@ func TestCollectionSpecCopy(t *testing.T) {
 				License: "MIT",
 			},
 		},
+		Types: &btf.Spec{},
 	}
 	cpy := cs.Copy()
 
@@ -80,6 +82,10 @@ func TestCollectionSpecCopy(t *testing.T) {
 	if cpy.Programs["test"] == cs.Programs["test"] {
 		t.Error("Copy returned same Programs")
 	}
+
+	if cpy.Types == cs.Types {
+		t.Error("Copy returned same Types")
+	}
 }
 
 func TestCollectionSpecRewriteMaps(t *testing.T) {
diff --git a/elf_reader_test.go b/elf_reader_test.go
index 01a35df..aea7e58 100644
--- a/elf_reader_test.go
+++ b/elf_reader_test.go
@@ -143,7 +143,7 @@ func TestLoadCollectionSpec(t *testing.T) {
 			return false
 		}),
 		cmpopts.IgnoreTypes(new(btf.Map), new(btf.Program)),
-		cmpopts.IgnoreFields(CollectionSpec{}, "ByteOrder", "BTF"),
+		cmpopts.IgnoreFields(CollectionSpec{}, "ByteOrder", "Types"),
 		cmpopts.IgnoreFields(ProgramSpec{}, "Instructions", "ByteOrder"),
 		cmpopts.IgnoreUnexported(ProgramSpec{}),
 		cmpopts.IgnoreMapEntries(func(key string, _ *MapSpec) bool {