github.com/cilium/ebpf@v0.16.0/btf/btf_test.go (about) 1 package btf 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "io" 9 "io/fs" 10 "os" 11 "runtime" 12 "sync" 13 "sync/atomic" 14 "testing" 15 16 "github.com/go-quicktest/qt" 17 18 "github.com/cilium/ebpf/internal" 19 "github.com/cilium/ebpf/internal/testutils" 20 ) 21 22 func vmlinuxSpec(tb testing.TB) *Spec { 23 tb.Helper() 24 25 // /sys/kernel/btf was introduced in 341dfcf8d78e ("btf: expose BTF info 26 // through sysfs"), which shipped in Linux 5.4. 27 if _, err := os.Stat("/sys/kernel/btf/vmlinux"); errors.Is(err, fs.ErrNotExist) { 28 tb.Skip("No /sys/kernel/btf/vmlinux") 29 } 30 31 spec, err := LoadKernelSpec() 32 if err != nil { 33 tb.Fatal(err) 34 } 35 return spec 36 } 37 38 type specAndRawBTF struct { 39 raw []byte 40 spec *Spec 41 } 42 43 var vmlinuxTestdata = sync.OnceValues(func() (specAndRawBTF, error) { 44 b, err := internal.ReadAllCompressed("testdata/vmlinux.btf.gz") 45 if err != nil { 46 return specAndRawBTF{}, err 47 } 48 49 spec, err := loadRawSpec(bytes.NewReader(b), binary.LittleEndian, nil) 50 if err != nil { 51 return specAndRawBTF{}, err 52 } 53 54 return specAndRawBTF{b, spec}, nil 55 }) 56 57 func vmlinuxTestdataReader(tb testing.TB) *bytes.Reader { 58 tb.Helper() 59 60 td, err := vmlinuxTestdata() 61 if err != nil { 62 tb.Fatal(err) 63 } 64 65 return bytes.NewReader(td.raw) 66 } 67 68 func vmlinuxTestdataSpec(tb testing.TB) *Spec { 69 tb.Helper() 70 71 td, err := vmlinuxTestdata() 72 if err != nil { 73 tb.Fatal(err) 74 } 75 76 return td.spec.Copy() 77 } 78 79 func parseELFBTF(tb testing.TB, file string) *Spec { 80 tb.Helper() 81 82 spec, err := LoadSpec(file) 83 if err != nil { 84 tb.Fatal("Can't load BTF:", err) 85 } 86 87 return spec 88 } 89 90 func TestAnyTypesByName(t *testing.T) { 91 testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) { 92 spec := parseELFBTF(t, file) 93 94 types, err := spec.AnyTypesByName("ambiguous") 95 if err != nil { 96 t.Fatal(err) 97 } 98 99 if len(types) != 1 { 100 t.Fatalf("expected to receive exactly 1 types from querying ambiguous type, got: %v", types) 101 } 102 103 types, err = spec.AnyTypesByName("ambiguous___flavour") 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 if len(types) != 1 { 109 t.Fatalf("expected to receive exactly 1 type from querying ambiguous flavour, got: %v", types) 110 } 111 }) 112 } 113 114 func TestTypeByNameAmbiguous(t *testing.T) { 115 testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) { 116 spec := parseELFBTF(t, file) 117 118 var typ *Struct 119 if err := spec.TypeByName("ambiguous", &typ); err != nil { 120 t.Fatal(err) 121 } 122 123 if name := typ.TypeName(); name != "ambiguous" { 124 t.Fatal("expected type name 'ambiguous', got:", name) 125 } 126 127 if err := spec.TypeByName("ambiguous___flavour", &typ); err != nil { 128 t.Fatal(err) 129 } 130 131 if name := typ.TypeName(); name != "ambiguous___flavour" { 132 t.Fatal("expected type name 'ambiguous___flavour', got:", name) 133 } 134 }) 135 } 136 137 func TestTypeByName(t *testing.T) { 138 spec := vmlinuxTestdataSpec(t) 139 140 for _, typ := range []interface{}{ 141 nil, 142 Struct{}, 143 &Struct{}, 144 []Struct{}, 145 &[]Struct{}, 146 map[int]Struct{}, 147 &map[int]Struct{}, 148 int(0), 149 new(int), 150 } { 151 t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) { 152 // spec.TypeByName MUST fail if typ is a nil btf.Type. 153 if err := spec.TypeByName("iphdr", typ); err == nil { 154 t.Fatalf("TypeByName does not fail with type %T", typ) 155 } 156 }) 157 } 158 159 // spec.TypeByName MUST return the same address for multiple calls with the same type name. 160 var iphdr1, iphdr2 *Struct 161 if err := spec.TypeByName("iphdr", &iphdr1); err != nil { 162 t.Fatal(err) 163 } 164 if err := spec.TypeByName("iphdr", &iphdr2); err != nil { 165 t.Fatal(err) 166 } 167 168 if iphdr1 != iphdr2 { 169 t.Fatal("multiple TypeByName calls for `iphdr` name do not return the same addresses") 170 } 171 172 // It's valid to pass a *Type to TypeByName. 173 typ := Type(iphdr2) 174 if err := spec.TypeByName("iphdr", &typ); err != nil { 175 t.Fatal("Can't look up using *Type:", err) 176 } 177 178 // Excerpt from linux/ip.h, https://elixir.bootlin.com/linux/latest/A/ident/iphdr 179 // 180 // struct iphdr { 181 // #if defined(__LITTLE_ENDIAN_BITFIELD) 182 // __u8 ihl:4, version:4; 183 // #elif defined (__BIG_ENDIAN_BITFIELD) 184 // __u8 version:4, ihl:4; 185 // #else 186 // ... 187 // } 188 // 189 // The BTF we test against is for little endian. 190 m := iphdr1.Members[1] 191 if m.Name != "version" { 192 t.Fatal("Expected version as the second member, got", m.Name) 193 } 194 td, ok := m.Type.(*Typedef) 195 if !ok { 196 t.Fatalf("version member of iphdr should be a __u8 typedef: actual: %T", m.Type) 197 } 198 u8, ok := td.Type.(*Int) 199 if !ok { 200 t.Fatalf("__u8 typedef should point to an Int type: actual: %T", td.Type) 201 } 202 if m.BitfieldSize != 4 { 203 t.Fatalf("incorrect bitfield size: expected: 4 actual: %d", m.BitfieldSize) 204 } 205 if u8.Encoding != 0 { 206 t.Fatalf("incorrect encoding of an __u8 int: expected: 0 actual: %x", u8.Encoding) 207 } 208 if m.Offset != 4 { 209 t.Fatalf("incorrect bitfield offset: expected: 4 actual: %d", m.Offset) 210 } 211 } 212 213 func BenchmarkParseVmlinux(b *testing.B) { 214 rd := vmlinuxTestdataReader(b) 215 b.ReportAllocs() 216 b.ResetTimer() 217 218 for n := 0; n < b.N; n++ { 219 if _, err := rd.Seek(0, io.SeekStart); err != nil { 220 b.Fatal(err) 221 } 222 223 if _, err := loadRawSpec(rd, binary.LittleEndian, nil); err != nil { 224 b.Fatal("Can't load BTF:", err) 225 } 226 } 227 } 228 229 func TestParseCurrentKernelBTF(t *testing.T) { 230 spec := vmlinuxSpec(t) 231 232 if len(spec.imm.namedTypes) == 0 { 233 t.Fatal("Empty kernel BTF") 234 } 235 236 totalBytes := 0 237 distinct := 0 238 seen := make(map[string]bool) 239 for _, str := range spec.strings.strings { 240 totalBytes += len(str) 241 if !seen[str] { 242 distinct++ 243 seen[str] = true 244 } 245 } 246 t.Logf("%d strings total, %d distinct", len(spec.strings.strings), distinct) 247 t.Logf("Average string size: %.0f", float64(totalBytes)/float64(len(spec.strings.strings))) 248 } 249 250 func TestFindVMLinux(t *testing.T) { 251 file, err := findVMLinux() 252 testutils.SkipIfNotSupported(t, err) 253 if err != nil { 254 t.Fatal("Can't find vmlinux:", err) 255 } 256 defer file.Close() 257 258 spec, err := LoadSpecFromReader(file) 259 if err != nil { 260 t.Fatal("Can't load BTF:", err) 261 } 262 263 if len(spec.imm.namedTypes) == 0 { 264 t.Fatal("Empty kernel BTF") 265 } 266 } 267 268 func TestLoadSpecFromElf(t *testing.T) { 269 testutils.Files(t, testutils.Glob(t, "../testdata/loader-e*.elf"), func(t *testing.T, file string) { 270 spec := parseELFBTF(t, file) 271 272 vt, err := spec.TypeByID(0) 273 if err != nil { 274 t.Error("Can't retrieve void type by ID:", err) 275 } 276 if _, ok := vt.(*Void); !ok { 277 t.Errorf("Expected Void for type id 0, but got: %T", vt) 278 } 279 280 var bpfMapDef *Struct 281 if err := spec.TypeByName("bpf_map_def", &bpfMapDef); err != nil { 282 t.Error("Can't find bpf_map_def:", err) 283 } 284 285 var tmp *Void 286 if err := spec.TypeByName("totally_bogus_type", &tmp); !errors.Is(err, ErrNotFound) { 287 t.Error("TypeByName doesn't return ErrNotFound:", err) 288 } 289 290 var fn *Func 291 if err := spec.TypeByName("global_fn", &fn); err != nil { 292 t.Error("Can't find global_fn():", err) 293 } else { 294 if fn.Linkage != GlobalFunc { 295 t.Error("Expected global linkage:", fn) 296 } 297 } 298 299 var v *Var 300 if err := spec.TypeByName("key3", &v); err != nil { 301 t.Error("Can't find key3:", err) 302 } else { 303 if v.Linkage != GlobalVar { 304 t.Error("Expected global linkage:", v) 305 } 306 } 307 }) 308 } 309 310 func TestVerifierError(t *testing.T) { 311 b, err := NewBuilder([]Type{&Int{Encoding: 255}}) 312 qt.Assert(t, qt.IsNil(err)) 313 _, err = NewHandle(b) 314 testutils.SkipIfNotSupported(t, err) 315 var ve *internal.VerifierError 316 if !errors.As(err, &ve) { 317 t.Fatalf("expected a VerifierError, got: %v", err) 318 } 319 } 320 321 func TestGuessBTFByteOrder(t *testing.T) { 322 bo := guessRawBTFByteOrder(vmlinuxTestdataReader(t)) 323 if bo != binary.LittleEndian { 324 t.Fatalf("Guessed %s instead of %s", bo, binary.LittleEndian) 325 } 326 } 327 328 func TestSpecCopy(t *testing.T) { 329 qt.Check(t, qt.IsNil((*Spec)(nil).Copy())) 330 331 spec := parseELFBTF(t, "../testdata/loader-el.elf") 332 cpy := spec.Copy() 333 334 have := typesFromSpec(spec) 335 qt.Assert(t, qt.IsTrue(len(have) > 0)) 336 337 want := typesFromSpec(cpy) 338 qt.Assert(t, qt.HasLen(want, len(have))) 339 340 for i := range want { 341 if _, ok := have[i].(*Void); ok { 342 // Since Void is an empty struct, a Type interface value containing 343 // &Void{} stores (*Void, nil). Since interface equality first compares 344 // the type and then the concrete value, Void is always equal. 345 continue 346 } 347 348 if have[i] == want[i] { 349 t.Fatalf("Type at index %d is not a copy: %T == %T", i, have[i], want[i]) 350 } 351 } 352 } 353 354 func TestSpecCopyModifications(t *testing.T) { 355 spec := specFromTypes(t, []Type{&Int{Name: "a", Size: 4}}) 356 357 typ, err := spec.TypeByID(1) 358 qt.Assert(t, qt.IsNil(err)) 359 360 i := typ.(*Int) 361 i.Name = "b" 362 i.Size = 2 363 364 cpy := spec.Copy() 365 typ2, err := cpy.TypeByID(1) 366 qt.Assert(t, qt.IsNil(err)) 367 i2 := typ2.(*Int) 368 369 qt.Assert(t, qt.Not(qt.Equals(i2, i)), qt.Commentf("Types are distinct")) 370 qt.Assert(t, qt.DeepEquals(i2, i), qt.Commentf("Modifications are preserved")) 371 372 i.Name = "bar" 373 qt.Assert(t, qt.Equals(i2.Name, "b")) 374 } 375 376 func TestSpecTypeByID(t *testing.T) { 377 spec := specFromTypes(t, nil) 378 379 _, err := spec.TypeByID(0) 380 qt.Assert(t, qt.IsNil(err)) 381 382 _, err = spec.TypeByID(1) 383 qt.Assert(t, qt.ErrorIs(err, ErrNotFound)) 384 } 385 386 func ExampleSpec_TypeByName() { 387 // Acquire a Spec via one of its constructors. 388 spec := new(Spec) 389 390 // Declare a variable of the desired type 391 var foo *Struct 392 393 if err := spec.TypeByName("foo", &foo); err != nil { 394 // There is no struct with name foo, or there 395 // are multiple possibilities. 396 } 397 398 // We've found struct foo 399 fmt.Println(foo.Name) 400 } 401 402 func TestTypesIterator(t *testing.T) { 403 types := []Type{(*Void)(nil), &Int{Size: 4}, &Int{Size: 2}} 404 405 b, err := NewBuilder(types[1:]) 406 if err != nil { 407 t.Fatal(err) 408 } 409 410 raw, err := b.Marshal(nil, nil) 411 if err != nil { 412 t.Fatal(err) 413 } 414 415 spec, err := LoadSpecFromReader(bytes.NewReader(raw)) 416 if err != nil { 417 t.Fatal(err) 418 } 419 420 iter := spec.Iterate() 421 422 for i, typ := range types { 423 if !iter.Next() { 424 t.Fatal("Iterator ended early at item", i) 425 } 426 427 qt.Assert(t, qt.DeepEquals(iter.Type, typ)) 428 } 429 430 if iter.Next() { 431 t.Fatalf("Iterator yielded too many items: %p (%[1]T)", iter.Type) 432 } 433 } 434 435 func TestLoadSplitSpecFromReader(t *testing.T) { 436 spec := vmlinuxTestdataSpec(t) 437 438 f, err := os.Open("testdata/btf_testmod.btf") 439 if err != nil { 440 t.Fatal(err) 441 } 442 defer f.Close() 443 444 splitSpec, err := LoadSplitSpecFromReader(f, spec) 445 if err != nil { 446 t.Fatal(err) 447 } 448 449 typ, err := splitSpec.AnyTypeByName("bpf_testmod_init") 450 if err != nil { 451 t.Fatal(err) 452 } 453 typeID, err := splitSpec.TypeID(typ) 454 if err != nil { 455 t.Fatal(err) 456 } 457 458 typeByID, err := splitSpec.TypeByID(typeID) 459 qt.Assert(t, qt.IsNil(err)) 460 qt.Assert(t, qt.Equals(typeByID, typ)) 461 462 fnType := typ.(*Func) 463 fnProto := fnType.Type.(*FuncProto) 464 465 // 'int' is defined in the base BTF... 466 intType, err := spec.AnyTypeByName("int") 467 if err != nil { 468 t.Fatal(err) 469 } 470 // ... but not in the split BTF 471 _, err = splitSpec.AnyTypeByName("int") 472 if err == nil { 473 t.Fatal("'int' is not supposed to be found in the split BTF") 474 } 475 476 qt.Assert(t, qt.Not(qt.Equals(fnProto.Return, intType)), 477 qt.Commentf("types found in base of split spec should be copies")) 478 479 // Check that copied split-BTF's spec has correct type indexing 480 splitSpecCopy := splitSpec.Copy() 481 copyType, err := splitSpecCopy.AnyTypeByName("bpf_testmod_init") 482 if err != nil { 483 t.Fatal(err) 484 } 485 copyTypeID, err := splitSpecCopy.TypeID(copyType) 486 if err != nil { 487 t.Fatal(err) 488 } 489 if copyTypeID != typeID { 490 t.Fatalf("'bpf_testmod_init` type ID (%d) does not match copied spec's (%d)", 491 typeID, copyTypeID) 492 } 493 } 494 495 func TestFixupDatasecLayout(t *testing.T) { 496 ds := &Datasec{ 497 Size: 0, // Populated by fixup. 498 Vars: []VarSecinfo{ 499 {Type: &Var{Type: &Int{Size: 4}}}, 500 {Type: &Var{Type: &Int{Size: 1}}}, 501 {Type: &Var{Type: &Int{Size: 1}}}, 502 {Type: &Var{Type: &Int{Size: 2}}}, 503 {Type: &Var{Type: &Int{Size: 16}}}, 504 {Type: &Var{Type: &Int{Size: 8}}}, 505 }, 506 } 507 508 qt.Assert(t, qt.IsNil(fixupDatasecLayout(ds))) 509 510 qt.Assert(t, qt.Equals(ds.Size, 40)) 511 qt.Assert(t, qt.Equals(ds.Vars[0].Offset, 0)) 512 qt.Assert(t, qt.Equals(ds.Vars[1].Offset, 4)) 513 qt.Assert(t, qt.Equals(ds.Vars[2].Offset, 5)) 514 qt.Assert(t, qt.Equals(ds.Vars[3].Offset, 6)) 515 qt.Assert(t, qt.Equals(ds.Vars[4].Offset, 16)) 516 qt.Assert(t, qt.Equals(ds.Vars[5].Offset, 32)) 517 } 518 519 func TestSpecConcurrentAccess(t *testing.T) { 520 spec := vmlinuxTestdataSpec(t) 521 522 maxprocs := runtime.GOMAXPROCS(0) 523 if maxprocs < 2 { 524 t.Error("GOMAXPROCS is lower than 2:", maxprocs) 525 } 526 527 var cond atomic.Int64 528 var wg sync.WaitGroup 529 for i := 0; i < maxprocs; i++ { 530 wg.Add(1) 531 go func() { 532 defer wg.Done() 533 534 n := cond.Add(1) 535 for cond.Load() != int64(maxprocs) { 536 // Spin to increase the chances of a race. 537 } 538 539 if n%2 == 0 { 540 _, _ = spec.AnyTypeByName("gov_update_cpu_data") 541 } else { 542 _ = spec.Copy() 543 } 544 }() 545 546 // Try to get the Goroutines scheduled and spinning. 547 runtime.Gosched() 548 } 549 wg.Wait() 550 } 551 552 func BenchmarkSpecCopy(b *testing.B) { 553 spec := vmlinuxTestdataSpec(b) 554 b.ResetTimer() 555 556 for i := 0; i < b.N; i++ { 557 spec.Copy() 558 } 559 } 560 561 func BenchmarkSpecTypeByID(b *testing.B) { 562 spec := vmlinuxTestdataSpec(b) 563 564 b.ReportAllocs() 565 b.ResetTimer() 566 for i := 0; i < b.N; i++ { 567 _, err := spec.TypeByID(1) 568 if err != nil { 569 b.Fatal(err) 570 } 571 } 572 }