github.com/kubeshark/ebpf@v0.9.2/btf/btf_test.go (about) 1 package btf 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "sync" 11 "testing" 12 13 "github.com/kubeshark/ebpf/internal" 14 "github.com/kubeshark/ebpf/internal/testutils" 15 ) 16 17 var vmlinux struct { 18 sync.Once 19 err error 20 raw []byte 21 } 22 23 func readVMLinux(tb testing.TB) *bytes.Reader { 24 tb.Helper() 25 26 vmlinux.Do(func() { 27 vmlinux.raw, vmlinux.err = internal.ReadAllCompressed("testdata/vmlinux.btf.gz") 28 }) 29 30 if vmlinux.err != nil { 31 tb.Fatal(vmlinux.err) 32 } 33 34 return bytes.NewReader(vmlinux.raw) 35 } 36 37 func parseELFBTF(tb testing.TB, file string) *Spec { 38 tb.Helper() 39 40 spec, err := LoadSpec(file) 41 if err != nil { 42 tb.Fatal("Can't load BTF:", err) 43 } 44 45 return spec 46 } 47 48 func TestAnyTypesByName(t *testing.T) { 49 testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) { 50 spec := parseELFBTF(t, file) 51 52 types, err := spec.AnyTypesByName("ambiguous") 53 if err != nil { 54 t.Fatal(err) 55 } 56 57 if len(types) != 1 { 58 t.Fatalf("expected to receive exactly 1 types from querying ambiguous type, got: %v", types) 59 } 60 61 types, err = spec.AnyTypesByName("ambiguous___flavour") 62 if err != nil { 63 t.Fatal(err) 64 } 65 66 if len(types) != 1 { 67 t.Fatalf("expected to receive exactly 1 type from querying ambiguous flavour, got: %v", types) 68 } 69 }) 70 } 71 72 func TestTypeByNameAmbiguous(t *testing.T) { 73 testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) { 74 spec := parseELFBTF(t, file) 75 76 var typ *Struct 77 if err := spec.TypeByName("ambiguous", &typ); err != nil { 78 t.Fatal(err) 79 } 80 81 if name := typ.TypeName(); name != "ambiguous" { 82 t.Fatal("expected type name 'ambiguous', got:", name) 83 } 84 85 if err := spec.TypeByName("ambiguous___flavour", &typ); err != nil { 86 t.Fatal(err) 87 } 88 89 if name := typ.TypeName(); name != "ambiguous___flavour" { 90 t.Fatal("expected type name 'ambiguous___flavour', got:", name) 91 } 92 }) 93 } 94 95 func TestTypeByName(t *testing.T) { 96 spec, err := LoadSpecFromReader(readVMLinux(t)) 97 if err != nil { 98 t.Fatal(err) 99 } 100 101 for _, typ := range []interface{}{ 102 nil, 103 Struct{}, 104 &Struct{}, 105 []Struct{}, 106 &[]Struct{}, 107 map[int]Struct{}, 108 &map[int]Struct{}, 109 int(0), 110 new(int), 111 } { 112 t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) { 113 // spec.TypeByName MUST fail if typ is a nil btf.Type. 114 if err := spec.TypeByName("iphdr", typ); err == nil { 115 t.Fatalf("TypeByName does not fail with type %T", typ) 116 } 117 }) 118 } 119 120 // spec.TypeByName MUST return the same address for multiple calls with the same type name. 121 var iphdr1, iphdr2 *Struct 122 if err := spec.TypeByName("iphdr", &iphdr1); err != nil { 123 t.Fatal(err) 124 } 125 if err := spec.TypeByName("iphdr", &iphdr2); err != nil { 126 t.Fatal(err) 127 } 128 129 if iphdr1 != iphdr2 { 130 t.Fatal("multiple TypeByName calls for `iphdr` name do not return the same addresses") 131 } 132 133 // Excerpt from linux/ip.h, https://elixir.bootlin.com/linux/latest/A/ident/iphdr 134 // 135 // struct iphdr { 136 // #if defined(__LITTLE_ENDIAN_BITFIELD) 137 // __u8 ihl:4, version:4; 138 // #elif defined (__BIG_ENDIAN_BITFIELD) 139 // __u8 version:4, ihl:4; 140 // #else 141 // ... 142 // } 143 // 144 // The BTF we test against is for little endian. 145 m := iphdr1.Members[1] 146 if m.Name != "version" { 147 t.Fatal("Expected version as the second member, got", m.Name) 148 } 149 td, ok := m.Type.(*Typedef) 150 if !ok { 151 t.Fatalf("version member of iphdr should be a __u8 typedef: actual: %T", m.Type) 152 } 153 u8, ok := td.Type.(*Int) 154 if !ok { 155 t.Fatalf("__u8 typedef should point to an Int type: actual: %T", td.Type) 156 } 157 if m.BitfieldSize != 4 { 158 t.Fatalf("incorrect bitfield size: expected: 4 actual: %d", m.BitfieldSize) 159 } 160 if u8.Encoding != 0 { 161 t.Fatalf("incorrect encoding of an __u8 int: expected: 0 actual: %x", u8.Encoding) 162 } 163 if m.Offset != 4 { 164 t.Fatalf("incorrect bitfield offset: expected: 4 actual: %d", m.Offset) 165 } 166 } 167 168 func BenchmarkParseVmlinux(b *testing.B) { 169 rd := readVMLinux(b) 170 b.ReportAllocs() 171 b.ResetTimer() 172 173 for n := 0; n < b.N; n++ { 174 if _, err := rd.Seek(0, io.SeekStart); err != nil { 175 b.Fatal(err) 176 } 177 178 if _, err := loadRawSpec(rd, binary.LittleEndian, nil, nil); err != nil { 179 b.Fatal("Can't load BTF:", err) 180 } 181 } 182 } 183 184 func TestParseCurrentKernelBTF(t *testing.T) { 185 spec, err := LoadKernelSpec() 186 testutils.SkipIfNotSupported(t, err) 187 if err != nil { 188 t.Fatal("Can't load BTF:", err) 189 } 190 191 if len(spec.namedTypes) == 0 { 192 t.Fatal("Empty kernel BTF") 193 } 194 195 totalBytes := 0 196 distinct := 0 197 seen := make(map[string]bool) 198 for _, str := range spec.strings.strings { 199 totalBytes += len(str) 200 if !seen[str] { 201 distinct++ 202 seen[str] = true 203 } 204 } 205 t.Logf("%d strings total, %d distinct", len(spec.strings.strings), distinct) 206 t.Logf("Average string size: %.0f", float64(totalBytes)/float64(len(spec.strings.strings))) 207 } 208 209 func TestFindVMLinux(t *testing.T) { 210 file, err := findVMLinux() 211 testutils.SkipIfNotSupported(t, err) 212 if err != nil { 213 t.Fatal("Can't find vmlinux:", err) 214 } 215 defer file.Close() 216 217 spec, err := loadSpecFromELF(file) 218 if err != nil { 219 t.Fatal("Can't load BTF:", err) 220 } 221 222 if len(spec.namedTypes) == 0 { 223 t.Fatal("Empty kernel BTF") 224 } 225 } 226 227 func TestLoadSpecFromElf(t *testing.T) { 228 testutils.Files(t, testutils.Glob(t, "../testdata/loader-e*.elf"), func(t *testing.T, file string) { 229 spec := parseELFBTF(t, file) 230 231 vt, err := spec.TypeByID(0) 232 if err != nil { 233 t.Error("Can't retrieve void type by ID:", err) 234 } 235 if _, ok := vt.(*Void); !ok { 236 t.Errorf("Expected Void for type id 0, but got: %T", vt) 237 } 238 239 var bpfMapDef *Struct 240 if err := spec.TypeByName("bpf_map_def", &bpfMapDef); err != nil { 241 t.Error("Can't find bpf_map_def:", err) 242 } 243 244 var tmp *Void 245 if err := spec.TypeByName("totally_bogus_type", &tmp); !errors.Is(err, ErrNotFound) { 246 t.Error("TypeByName doesn't return ErrNotFound:", err) 247 } 248 249 var fn *Func 250 if err := spec.TypeByName("global_fn", &fn); err != nil { 251 t.Error("Can't find global_fn():", err) 252 } else { 253 if fn.Linkage != GlobalFunc { 254 t.Error("Expected global linkage:", fn) 255 } 256 } 257 258 var v *Var 259 if err := spec.TypeByName("key3", &v); err != nil { 260 t.Error("Cant find key3:", err) 261 } else { 262 if v.Linkage != GlobalVar { 263 t.Error("Expected global linkage:", v) 264 } 265 } 266 267 if spec.byteOrder != internal.NativeEndian { 268 return 269 } 270 271 t.Run("Handle", func(t *testing.T) { 272 btf, err := NewHandle(spec) 273 testutils.SkipIfNotSupported(t, err) 274 if err != nil { 275 t.Fatal("Can't load BTF:", err) 276 } 277 defer btf.Close() 278 }) 279 }) 280 } 281 282 func TestLoadKernelSpec(t *testing.T) { 283 if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) { 284 t.Skip("/sys/kernel/btf/vmlinux not present") 285 } 286 287 _, err := LoadKernelSpec() 288 if err != nil { 289 t.Fatal("Can't load kernel spec:", err) 290 } 291 } 292 293 func TestGuessBTFByteOrder(t *testing.T) { 294 bo := guessRawBTFByteOrder(readVMLinux(t)) 295 if bo != binary.LittleEndian { 296 t.Fatalf("Guessed %s instead of %s", bo, binary.LittleEndian) 297 } 298 } 299 300 func TestSpecCopy(t *testing.T) { 301 spec := parseELFBTF(t, "../testdata/loader-el.elf") 302 303 if len(spec.types) < 1 { 304 t.Fatal("Not enough types") 305 } 306 307 cpy := spec.Copy() 308 for i := range cpy.types { 309 if _, ok := cpy.types[i].(*Void); ok { 310 // Since Void is an empty struct, a Type interface value containing 311 // &Void{} stores (*Void, nil). Since interface equality first compares 312 // the type and then the concrete value, Void is always equal. 313 continue 314 } 315 316 if cpy.types[i] == spec.types[i] { 317 t.Fatalf("Type at index %d is not a copy: %T == %T", i, cpy.types[i], spec.types[i]) 318 } 319 } 320 } 321 322 func TestHaveBTF(t *testing.T) { 323 testutils.CheckFeatureTest(t, haveBTF) 324 } 325 326 func TestHaveFuncLinkage(t *testing.T) { 327 testutils.CheckFeatureTest(t, haveFuncLinkage) 328 } 329 330 func ExampleSpec_TypeByName() { 331 // Acquire a Spec via one of its constructors. 332 spec := new(Spec) 333 334 // Declare a variable of the desired type 335 var foo *Struct 336 337 if err := spec.TypeByName("foo", &foo); err != nil { 338 // There is no struct with name foo, or there 339 // are multiple possibilities. 340 } 341 342 // We've found struct foo 343 fmt.Println(foo.Name) 344 } 345 346 func TestTypesIterator(t *testing.T) { 347 spec, err := LoadSpecFromReader(readVMLinux(t)) 348 if err != nil { 349 t.Fatal(err) 350 } 351 352 if len(spec.types) < 1 { 353 t.Fatal("Not enough types") 354 } 355 356 // Assertion that 'iphdr' type exists within the spec 357 _, err = spec.AnyTypeByName("iphdr") 358 if err != nil { 359 t.Fatalf("Failed to find 'iphdr' type by name: %s", err) 360 } 361 362 found := false 363 count := 0 364 365 iter := spec.Iterate() 366 for iter.Next() { 367 if !found && iter.Type.TypeName() == "iphdr" { 368 found = true 369 } 370 count += 1 371 } 372 373 if l := len(spec.types); l != count { 374 t.Fatalf("Failed to iterate over all types (%d vs %d)", l, count) 375 } 376 if !found { 377 t.Fatal("Cannot find 'iphdr' type") 378 } 379 } 380 381 func TestLoadSplitSpecFromReader(t *testing.T) { 382 spec, err := LoadSpecFromReader(readVMLinux(t)) 383 if err != nil { 384 t.Fatal(err) 385 } 386 387 f, err := os.Open("testdata/btf_testmod.btf") 388 if err != nil { 389 t.Fatal(err) 390 } 391 defer f.Close() 392 393 splitSpec, err := LoadSplitSpecFromReader(f, spec) 394 if err != nil { 395 t.Fatal(err) 396 } 397 398 typ, err := splitSpec.AnyTypeByName("bpf_testmod_init") 399 if err != nil { 400 t.Fatal(err) 401 } 402 typeID, err := splitSpec.TypeID(typ) 403 if err != nil { 404 t.Fatal(err) 405 } 406 fnType := typ.(*Func) 407 fnProto := fnType.Type.(*FuncProto) 408 409 // 'int' is defined in the base BTF... 410 intType, err := spec.AnyTypeByName("int") 411 if err != nil { 412 t.Fatal(err) 413 } 414 // ... but not in the split BTF 415 _, err = splitSpec.AnyTypeByName("int") 416 if err == nil { 417 t.Fatal("'int' is not supposed to be found in the split BTF") 418 } 419 420 if fnProto.Return != intType { 421 t.Fatalf("Return type of 'bpf_testmod_init()' (%s) does not match 'int' type (%s)", 422 fnProto.Return, intType) 423 } 424 425 // Check that copied split-BTF's spec has correct type indexing 426 splitSpecCopy := splitSpec.Copy() 427 copyType, err := splitSpecCopy.AnyTypeByName("bpf_testmod_init") 428 if err != nil { 429 t.Fatal(err) 430 } 431 copyTypeID, err := splitSpecCopy.TypeID(copyType) 432 if err != nil { 433 t.Fatal(err) 434 } 435 if copyTypeID != typeID { 436 t.Fatalf("'bpf_testmod_init` type ID (%d) does not match copied spec's (%d)", 437 typeID, copyTypeID) 438 } 439 440 }