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 }