github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/btf/marshal_test.go (about)

     1  package btf
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"math"
     7  	"testing"
     8  
     9  	"github.com/go-quicktest/qt"
    10  	"github.com/google/go-cmp/cmp"
    11  
    12  	"github.com/cilium/ebpf/internal"
    13  	"github.com/cilium/ebpf/internal/testutils"
    14  )
    15  
    16  func TestBuilderMarshal(t *testing.T) {
    17  	typ := &Int{
    18  		Name:     "foo",
    19  		Size:     2,
    20  		Encoding: Signed | Char,
    21  	}
    22  
    23  	want := []Type{
    24  		(*Void)(nil),
    25  		typ,
    26  		&Pointer{typ},
    27  		&Typedef{"baz", typ},
    28  	}
    29  
    30  	b, err := NewBuilder(want)
    31  	qt.Assert(t, qt.IsNil(err))
    32  
    33  	cpy := *b
    34  	buf, err := b.Marshal(nil, &MarshalOptions{Order: internal.NativeEndian})
    35  	qt.Assert(t, qt.IsNil(err))
    36  	qt.Assert(t, qt.CmpEquals(b, &cpy, cmp.AllowUnexported(*b)), qt.Commentf("Marshaling should not change Builder state"))
    37  
    38  	have, err := loadRawSpec(bytes.NewReader(buf), internal.NativeEndian, nil)
    39  	qt.Assert(t, qt.IsNil(err), qt.Commentf("Couldn't parse BTF"))
    40  	qt.Assert(t, qt.DeepEquals(have.imm.types, want))
    41  }
    42  
    43  func TestBuilderAdd(t *testing.T) {
    44  	i := &Int{
    45  		Name:     "foo",
    46  		Size:     2,
    47  		Encoding: Signed | Char,
    48  	}
    49  	pi := &Pointer{i}
    50  
    51  	var b Builder
    52  	id, err := b.Add(pi)
    53  	qt.Assert(t, qt.IsNil(err))
    54  	qt.Assert(t, qt.Equals(id, TypeID(1)), qt.Commentf("First non-void type doesn't get id 1"))
    55  
    56  	id, err = b.Add(pi)
    57  	qt.Assert(t, qt.IsNil(err))
    58  	qt.Assert(t, qt.Equals(id, TypeID(1)))
    59  
    60  	id, err = b.Add(i)
    61  	qt.Assert(t, qt.IsNil(err))
    62  	qt.Assert(t, qt.Equals(id, TypeID(2)), qt.Commentf("Second type doesn't get id 2"))
    63  
    64  	id, err = b.Add(i)
    65  	qt.Assert(t, qt.IsNil(err))
    66  	qt.Assert(t, qt.Equals(id, TypeID(2)), qt.Commentf("Adding a type twice returns different ids"))
    67  
    68  	id, err = b.Add(&Typedef{"baz", i})
    69  	qt.Assert(t, qt.IsNil(err))
    70  	qt.Assert(t, qt.Equals(id, TypeID(3)))
    71  }
    72  
    73  func TestRoundtripVMlinux(t *testing.T) {
    74  	types := typesFromSpec(vmlinuxSpec(t))
    75  
    76  	// Randomize the order to force different permutations of walking the type
    77  	// graph. Keep Void at index 0.
    78  	testutils.Rand(t).Shuffle(len(types[1:]), func(i, j int) {
    79  		types[i+1], types[j+1] = types[j+1], types[i+1]
    80  	})
    81  
    82  	visited := make(map[Type]struct{})
    83  limitTypes:
    84  	for i, typ := range types {
    85  		visitInPostorder(typ, visited, func(t Type) bool { return true })
    86  		if len(visited) >= math.MaxInt16 {
    87  			// IDs exceeding math.MaxUint16 can trigger a bug when loading BTF.
    88  			// This can be removed once the patch lands.
    89  			// See https://lore.kernel.org/bpf/20220909092107.3035-1-oss@lmb.io/
    90  			types = types[:i]
    91  			break limitTypes
    92  		}
    93  	}
    94  
    95  	b, err := NewBuilder(types)
    96  	qt.Assert(t, qt.IsNil(err))
    97  	buf, err := b.Marshal(nil, KernelMarshalOptions())
    98  	qt.Assert(t, qt.IsNil(err))
    99  
   100  	rebuilt, err := loadRawSpec(bytes.NewReader(buf), binary.LittleEndian, nil)
   101  	qt.Assert(t, qt.IsNil(err), qt.Commentf("round tripping BTF failed"))
   102  
   103  	if n := len(rebuilt.imm.types); n > math.MaxUint16 {
   104  		t.Logf("Rebuilt BTF contains %d types which exceeds uint16, test may fail on older kernels", n)
   105  	}
   106  
   107  	h, err := NewHandleFromRawBTF(buf)
   108  	testutils.SkipIfNotSupported(t, err)
   109  	qt.Assert(t, qt.IsNil(err), qt.Commentf("loading rebuilt BTF failed"))
   110  	h.Close()
   111  }
   112  
   113  func TestMarshalEnum64(t *testing.T) {
   114  	enum := &Enum{
   115  		Name:   "enum64",
   116  		Size:   8,
   117  		Signed: true,
   118  		Values: []EnumValue{
   119  			{"A", 0},
   120  			{"B", 1},
   121  		},
   122  	}
   123  
   124  	b, err := NewBuilder([]Type{enum})
   125  	qt.Assert(t, qt.IsNil(err))
   126  	buf, err := b.Marshal(nil, &MarshalOptions{
   127  		Order:         internal.NativeEndian,
   128  		ReplaceEnum64: true,
   129  	})
   130  	qt.Assert(t, qt.IsNil(err))
   131  
   132  	spec, err := loadRawSpec(bytes.NewReader(buf), internal.NativeEndian, nil)
   133  	qt.Assert(t, qt.IsNil(err))
   134  
   135  	var have *Union
   136  	err = spec.TypeByName("enum64", &have)
   137  	qt.Assert(t, qt.IsNil(err))
   138  
   139  	placeholder := &Int{Name: "enum64_placeholder", Size: 8, Encoding: Signed}
   140  	qt.Assert(t, qt.DeepEquals(have, &Union{
   141  		Name: "enum64",
   142  		Size: 8,
   143  		Members: []Member{
   144  			{Name: "A", Type: placeholder},
   145  			{Name: "B", Type: placeholder},
   146  		},
   147  	}))
   148  }
   149  
   150  func BenchmarkMarshaler(b *testing.B) {
   151  	types := typesFromSpec(vmlinuxTestdataSpec(b))[:100]
   152  
   153  	b.ReportAllocs()
   154  	b.ResetTimer()
   155  
   156  	for i := 0; i < b.N; i++ {
   157  		var b Builder
   158  		for _, typ := range types {
   159  			_, _ = b.Add(typ)
   160  		}
   161  		_, _ = b.Marshal(nil, nil)
   162  	}
   163  }
   164  
   165  func BenchmarkBuildVmlinux(b *testing.B) {
   166  	types := typesFromSpec(vmlinuxTestdataSpec(b))
   167  
   168  	b.ReportAllocs()
   169  	b.ResetTimer()
   170  
   171  	for i := 0; i < b.N; i++ {
   172  		var b Builder
   173  		for _, typ := range types {
   174  			_, _ = b.Add(typ)
   175  		}
   176  		_, _ = b.Marshal(nil, nil)
   177  	}
   178  }
   179  
   180  func marshalNativeEndian(tb testing.TB, types []Type) []byte {
   181  	tb.Helper()
   182  
   183  	b, err := NewBuilder(types)
   184  	qt.Assert(tb, qt.IsNil(err))
   185  	buf, err := b.Marshal(nil, nil)
   186  	qt.Assert(tb, qt.IsNil(err))
   187  	return buf
   188  }
   189  
   190  func specFromTypes(tb testing.TB, types []Type) *Spec {
   191  	tb.Helper()
   192  
   193  	btf := marshalNativeEndian(tb, types)
   194  	spec, err := loadRawSpec(bytes.NewReader(btf), internal.NativeEndian, nil)
   195  	qt.Assert(tb, qt.IsNil(err))
   196  
   197  	return spec
   198  }
   199  
   200  func typesFromSpec(spec *Spec) []Type {
   201  	var types []Type
   202  	iter := spec.Iterate()
   203  	for iter.Next() {
   204  		types = append(types, iter.Type)
   205  	}
   206  
   207  	return types
   208  }