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