github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/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 if ve.Truncated { 321 t.Fatalf("expected non-truncated verifier log: %v", err) 322 } 323 } 324 325 func TestGuessBTFByteOrder(t *testing.T) { 326 bo := guessRawBTFByteOrder(vmlinuxTestdataReader(t)) 327 if bo != binary.LittleEndian { 328 t.Fatalf("Guessed %s instead of %s", bo, binary.LittleEndian) 329 } 330 } 331 332 func TestSpecCopy(t *testing.T) { 333 spec := parseELFBTF(t, "../testdata/loader-el.elf") 334 cpy := spec.Copy() 335 336 have := typesFromSpec(spec) 337 qt.Assert(t, qt.IsTrue(len(have) > 0)) 338 339 want := typesFromSpec(cpy) 340 qt.Assert(t, qt.HasLen(want, len(have))) 341 342 for i := range want { 343 if _, ok := have[i].(*Void); ok { 344 // Since Void is an empty struct, a Type interface value containing 345 // &Void{} stores (*Void, nil). Since interface equality first compares 346 // the type and then the concrete value, Void is always equal. 347 continue 348 } 349 350 if have[i] == want[i] { 351 t.Fatalf("Type at index %d is not a copy: %T == %T", i, have[i], want[i]) 352 } 353 } 354 } 355 356 func TestSpecCopyModifications(t *testing.T) { 357 spec := specFromTypes(t, []Type{&Int{Name: "a", Size: 4}}) 358 359 typ, err := spec.TypeByID(1) 360 qt.Assert(t, qt.IsNil(err)) 361 362 i := typ.(*Int) 363 i.Name = "b" 364 i.Size = 2 365 366 cpy := spec.Copy() 367 typ2, err := cpy.TypeByID(1) 368 qt.Assert(t, qt.IsNil(err)) 369 i2 := typ2.(*Int) 370 371 qt.Assert(t, qt.Not(qt.Equals(i2, i)), qt.Commentf("Types are distinct")) 372 qt.Assert(t, qt.DeepEquals(i2, i), qt.Commentf("Modifications are preserved")) 373 374 i.Name = "bar" 375 qt.Assert(t, qt.Equals(i2.Name, "b")) 376 } 377 378 func TestSpecTypeByID(t *testing.T) { 379 spec := specFromTypes(t, nil) 380 381 _, err := spec.TypeByID(0) 382 qt.Assert(t, qt.IsNil(err)) 383 384 _, err = spec.TypeByID(1) 385 qt.Assert(t, qt.ErrorIs(err, ErrNotFound)) 386 } 387 388 func ExampleSpec_TypeByName() { 389 // Acquire a Spec via one of its constructors. 390 spec := new(Spec) 391 392 // Declare a variable of the desired type 393 var foo *Struct 394 395 if err := spec.TypeByName("foo", &foo); err != nil { 396 // There is no struct with name foo, or there 397 // are multiple possibilities. 398 } 399 400 // We've found struct foo 401 fmt.Println(foo.Name) 402 } 403 404 func TestTypesIterator(t *testing.T) { 405 types := []Type{(*Void)(nil), &Int{Size: 4}, &Int{Size: 2}} 406 407 b, err := NewBuilder(types[1:]) 408 if err != nil { 409 t.Fatal(err) 410 } 411 412 raw, err := b.Marshal(nil, nil) 413 if err != nil { 414 t.Fatal(err) 415 } 416 417 spec, err := LoadSpecFromReader(bytes.NewReader(raw)) 418 if err != nil { 419 t.Fatal(err) 420 } 421 422 iter := spec.Iterate() 423 424 for i, typ := range types { 425 if !iter.Next() { 426 t.Fatal("Iterator ended early at item", i) 427 } 428 429 qt.Assert(t, qt.DeepEquals(iter.Type, typ)) 430 } 431 432 if iter.Next() { 433 t.Fatalf("Iterator yielded too many items: %p (%[1]T)", iter.Type) 434 } 435 } 436 437 func TestLoadSplitSpecFromReader(t *testing.T) { 438 spec := vmlinuxTestdataSpec(t) 439 440 f, err := os.Open("testdata/btf_testmod.btf") 441 if err != nil { 442 t.Fatal(err) 443 } 444 defer f.Close() 445 446 splitSpec, err := LoadSplitSpecFromReader(f, spec) 447 if err != nil { 448 t.Fatal(err) 449 } 450 451 typ, err := splitSpec.AnyTypeByName("bpf_testmod_init") 452 if err != nil { 453 t.Fatal(err) 454 } 455 typeID, err := splitSpec.TypeID(typ) 456 if err != nil { 457 t.Fatal(err) 458 } 459 460 typeByID, err := splitSpec.TypeByID(typeID) 461 qt.Assert(t, qt.IsNil(err)) 462 qt.Assert(t, qt.Equals(typeByID, typ)) 463 464 fnType := typ.(*Func) 465 fnProto := fnType.Type.(*FuncProto) 466 467 // 'int' is defined in the base BTF... 468 intType, err := spec.AnyTypeByName("int") 469 if err != nil { 470 t.Fatal(err) 471 } 472 // ... but not in the split BTF 473 _, err = splitSpec.AnyTypeByName("int") 474 if err == nil { 475 t.Fatal("'int' is not supposed to be found in the split BTF") 476 } 477 478 qt.Assert(t, qt.Not(qt.Equals(fnProto.Return, intType)), 479 qt.Commentf("types found in base of split spec should be copies")) 480 481 // Check that copied split-BTF's spec has correct type indexing 482 splitSpecCopy := splitSpec.Copy() 483 copyType, err := splitSpecCopy.AnyTypeByName("bpf_testmod_init") 484 if err != nil { 485 t.Fatal(err) 486 } 487 copyTypeID, err := splitSpecCopy.TypeID(copyType) 488 if err != nil { 489 t.Fatal(err) 490 } 491 if copyTypeID != typeID { 492 t.Fatalf("'bpf_testmod_init` type ID (%d) does not match copied spec's (%d)", 493 typeID, copyTypeID) 494 } 495 } 496 497 func TestFixupDatasecLayout(t *testing.T) { 498 ds := &Datasec{ 499 Size: 0, // Populated by fixup. 500 Vars: []VarSecinfo{ 501 {Type: &Var{Type: &Int{Size: 4}}}, 502 {Type: &Var{Type: &Int{Size: 1}}}, 503 {Type: &Var{Type: &Int{Size: 1}}}, 504 {Type: &Var{Type: &Int{Size: 2}}}, 505 {Type: &Var{Type: &Int{Size: 16}}}, 506 {Type: &Var{Type: &Int{Size: 8}}}, 507 }, 508 } 509 510 qt.Assert(t, qt.IsNil(fixupDatasecLayout(ds))) 511 512 qt.Assert(t, qt.Equals(ds.Size, 40)) 513 qt.Assert(t, qt.Equals(ds.Vars[0].Offset, 0)) 514 qt.Assert(t, qt.Equals(ds.Vars[1].Offset, 4)) 515 qt.Assert(t, qt.Equals(ds.Vars[2].Offset, 5)) 516 qt.Assert(t, qt.Equals(ds.Vars[3].Offset, 6)) 517 qt.Assert(t, qt.Equals(ds.Vars[4].Offset, 16)) 518 qt.Assert(t, qt.Equals(ds.Vars[5].Offset, 32)) 519 } 520 521 func TestSpecConcurrentAccess(t *testing.T) { 522 spec := vmlinuxTestdataSpec(t) 523 524 maxprocs := runtime.GOMAXPROCS(0) 525 if maxprocs < 2 { 526 t.Error("GOMAXPROCS is lower than 2:", maxprocs) 527 } 528 529 var cond atomic.Int64 530 var wg sync.WaitGroup 531 for i := 0; i < maxprocs; i++ { 532 wg.Add(1) 533 go func() { 534 defer wg.Done() 535 536 n := cond.Add(1) 537 for cond.Load() != int64(maxprocs) { 538 // Spin to increase the chances of a race. 539 } 540 541 if n%2 == 0 { 542 _, _ = spec.AnyTypeByName("gov_update_cpu_data") 543 } else { 544 _ = spec.Copy() 545 } 546 }() 547 548 // Try to get the Goroutines scheduled and spinning. 549 runtime.Gosched() 550 } 551 wg.Wait() 552 } 553 554 func BenchmarkSpecCopy(b *testing.B) { 555 spec := vmlinuxTestdataSpec(b) 556 b.ResetTimer() 557 558 for i := 0; i < b.N; i++ { 559 spec.Copy() 560 } 561 } 562 563 func BenchmarkSpecTypeByID(b *testing.B) { 564 spec := vmlinuxTestdataSpec(b) 565 566 b.ReportAllocs() 567 b.ResetTimer() 568 for i := 0; i < b.N; i++ { 569 _, err := spec.TypeByID(1) 570 if err != nil { 571 b.Fatal(err) 572 } 573 } 574 }