github.com/cilium/ebpf@v0.10.0/btf/core_test.go (about) 1 package btf 2 3 import ( 4 "errors" 5 "math/rand" 6 "os" 7 "strings" 8 "testing" 9 10 "github.com/cilium/ebpf/internal/testutils" 11 "github.com/google/go-cmp/cmp" 12 13 qt "github.com/frankban/quicktest" 14 ) 15 16 func TestCOREAreTypesCompatible(t *testing.T) { 17 tests := []struct { 18 a, b Type 19 compatible bool 20 }{ 21 {&Void{}, &Void{}, true}, 22 {&Struct{Name: "a"}, &Struct{Name: "b"}, true}, 23 {&Union{Name: "a"}, &Union{Name: "b"}, true}, 24 {&Union{Name: "a"}, &Struct{Name: "b"}, false}, 25 {&Enum{Name: "a"}, &Enum{Name: "b"}, true}, 26 {&Fwd{Name: "a"}, &Fwd{Name: "b"}, true}, 27 {&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true}, 28 {&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true}, 29 {&Pointer{Target: &Void{}}, &Void{}, false}, 30 {&Array{Index: &Void{}, Type: &Void{}}, &Array{Index: &Void{}, Type: &Void{}}, true}, 31 {&Array{Index: &Void{}, Type: &Int{}}, &Array{Index: &Void{}, Type: &Void{}}, false}, 32 {&FuncProto{Return: &Int{}}, &FuncProto{Return: &Void{}}, false}, 33 { 34 &FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "a", Type: &Void{}}}}, 35 &FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "b", Type: &Void{}}}}, 36 true, 37 }, 38 { 39 &FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}}, 40 &FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Int{}}}}, 41 false, 42 }, 43 { 44 &FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}, {Type: &Void{}}}}, 45 &FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}}, 46 false, 47 }, 48 } 49 50 for _, test := range tests { 51 err := coreAreTypesCompatible(test.a, test.b) 52 if test.compatible { 53 if err != nil { 54 t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b) 55 continue 56 } 57 } else { 58 if !errors.Is(err, errImpossibleRelocation) { 59 t.Errorf("Expected types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b) 60 continue 61 } 62 } 63 64 err = coreAreTypesCompatible(test.b, test.a) 65 if test.compatible { 66 if err != nil { 67 t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b) 68 } 69 } else { 70 if !errors.Is(err, errImpossibleRelocation) { 71 t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b) 72 } 73 } 74 } 75 76 for _, invalid := range []Type{&Var{}, &Datasec{}} { 77 err := coreAreTypesCompatible(invalid, invalid) 78 if errors.Is(err, errImpossibleRelocation) { 79 t.Errorf("Expected an error for %T, not errImpossibleRelocation", invalid) 80 } else if err == nil { 81 t.Errorf("Expected an error for %T", invalid) 82 } 83 } 84 } 85 86 func TestCOREAreMembersCompatible(t *testing.T) { 87 tests := []struct { 88 a, b Type 89 compatible bool 90 }{ 91 {&Struct{Name: "a"}, &Struct{Name: "b"}, true}, 92 {&Union{Name: "a"}, &Union{Name: "b"}, true}, 93 {&Union{Name: "a"}, &Struct{Name: "b"}, true}, 94 {&Enum{Name: "a"}, &Enum{Name: "b"}, false}, 95 {&Enum{Name: "a"}, &Enum{Name: "a___foo"}, true}, 96 {&Enum{Name: "a"}, &Enum{Name: ""}, true}, 97 {&Fwd{Name: "a"}, &Fwd{Name: "b"}, false}, 98 {&Fwd{Name: "a"}, &Fwd{Name: "a___foo"}, true}, 99 {&Fwd{Name: "a"}, &Fwd{Name: ""}, true}, 100 {&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true}, 101 {&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true}, 102 {&Pointer{Target: &Void{}}, &Void{}, false}, 103 {&Array{Type: &Int{Size: 1}}, &Array{Type: &Int{Encoding: Signed}}, true}, 104 {&Float{Size: 2}, &Float{Size: 4}, true}, 105 } 106 107 for _, test := range tests { 108 err := coreAreMembersCompatible(test.a, test.b) 109 if test.compatible { 110 if err != nil { 111 t.Errorf("Expected members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b) 112 continue 113 } 114 } else { 115 if !errors.Is(err, errImpossibleRelocation) { 116 t.Errorf("Expected members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b) 117 continue 118 } 119 } 120 121 err = coreAreMembersCompatible(test.b, test.a) 122 if test.compatible { 123 if err != nil { 124 t.Errorf("Expected reversed members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b) 125 } 126 } else { 127 if !errors.Is(err, errImpossibleRelocation) { 128 t.Errorf("Expected reversed members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b) 129 } 130 } 131 } 132 133 for _, invalid := range []Type{&Void{}, &FuncProto{}, &Var{}, &Datasec{}} { 134 err := coreAreMembersCompatible(invalid, invalid) 135 if errors.Is(err, errImpossibleRelocation) { 136 t.Errorf("Expected an error for %T, not errImpossibleRelocation", invalid) 137 } else if err == nil { 138 t.Errorf("Expected an error for %T", invalid) 139 } 140 } 141 } 142 143 func TestCOREAccessor(t *testing.T) { 144 for _, valid := range []string{ 145 "0", 146 "1:0", 147 "1:0:3:34:10:1", 148 } { 149 _, err := parseCOREAccessor(valid) 150 if err != nil { 151 t.Errorf("Parse %q: %s", valid, err) 152 } 153 } 154 155 for _, invalid := range []string{ 156 "", 157 "-1", 158 ":", 159 "0:", 160 ":12", 161 "4294967296", 162 } { 163 _, err := parseCOREAccessor(invalid) 164 if err == nil { 165 t.Errorf("Accepted invalid accessor %q", invalid) 166 } 167 } 168 } 169 170 func TestCOREFindEnumValue(t *testing.T) { 171 a := &Enum{Values: []EnumValue{{"foo", 23}, {"bar", 42}}} 172 b := &Enum{Values: []EnumValue{ 173 {"foo___flavour", 0}, 174 {"bar", 123}, 175 {"garbage", 3}, 176 }} 177 178 invalid := []struct { 179 name string 180 local Type 181 target Type 182 acc coreAccessor 183 err error 184 }{ 185 {"o-o-b accessor", a, b, coreAccessor{len(a.Values)}, nil}, 186 {"long accessor", a, b, coreAccessor{0, 1}, nil}, 187 {"wrong target", a, &Void{}, coreAccessor{0, 1}, nil}, 188 { 189 "no matching value", 190 b, a, 191 coreAccessor{2}, 192 errImpossibleRelocation, 193 }, 194 } 195 196 for _, test := range invalid { 197 t.Run(test.name, func(t *testing.T) { 198 _, _, err := coreFindEnumValue(test.local, test.acc, test.target) 199 if test.err != nil && !errors.Is(err, test.err) { 200 t.Fatalf("Expected %s, got %s", test.err, err) 201 } 202 if err == nil { 203 t.Fatal("Accepted invalid case") 204 } 205 }) 206 } 207 208 valid := []struct { 209 name string 210 local, target Type 211 acc coreAccessor 212 localValue, targetValue uint64 213 }{ 214 {"a to b", a, b, coreAccessor{0}, 23, 0}, 215 {"b to a", b, a, coreAccessor{1}, 123, 42}, 216 } 217 218 for _, test := range valid { 219 t.Run(test.name, func(t *testing.T) { 220 local, target, err := coreFindEnumValue(test.local, test.acc, test.target) 221 qt.Assert(t, err, qt.IsNil) 222 qt.Check(t, local.Value, qt.Equals, test.localValue) 223 qt.Check(t, target.Value, qt.Equals, test.targetValue) 224 }) 225 } 226 } 227 228 func TestCOREFindField(t *testing.T) { 229 ptr := &Pointer{} 230 u16 := &Int{Size: 2} 231 u32 := &Int{Size: 4} 232 aFields := []Member{ 233 {Name: "foo", Type: ptr, Offset: 8}, 234 {Name: "bar", Type: u16, Offset: 16}, 235 {Name: "baz", Type: u32, Offset: 32, BitfieldSize: 3}, 236 {Name: "quux", Type: u32, Offset: 35, BitfieldSize: 10}, 237 {Name: "quuz", Type: u32, Offset: 45, BitfieldSize: 8}, 238 } 239 bFields := []Member{ 240 {Name: "foo", Type: ptr, Offset: 16}, 241 {Name: "bar", Type: u32, Offset: 8}, 242 {Name: "other", Offset: 4}, 243 // baz is separated out from the other bitfields 244 {Name: "baz", Type: u32, Offset: 64, BitfieldSize: 3}, 245 // quux's type changes u32->u16 246 {Name: "quux", Type: u16, Offset: 96, BitfieldSize: 10}, 247 // quuz becomes a normal field 248 {Name: "quuz", Type: u16, Offset: 112}, 249 } 250 251 aStruct := &Struct{Members: aFields, Size: 48} 252 bStruct := &Struct{Members: bFields, Size: 80} 253 aArray := &Array{Nelems: 4, Type: u16} 254 bArray := &Array{Nelems: 3, Type: u32} 255 256 invalid := []struct { 257 name string 258 local, target Type 259 acc coreAccessor 260 err error 261 }{ 262 { 263 "unsupported type", 264 &Void{}, &Void{}, 265 coreAccessor{0, 0}, 266 ErrNotSupported, 267 }, 268 { 269 "different types", 270 &Union{}, &Array{Type: u16}, 271 coreAccessor{0}, 272 errImpossibleRelocation, 273 }, 274 { 275 "invalid composite accessor", 276 aStruct, aStruct, 277 coreAccessor{0, len(aStruct.Members)}, 278 nil, 279 }, 280 { 281 "invalid array accessor", 282 aArray, aArray, 283 coreAccessor{0, int(aArray.Nelems)}, 284 nil, 285 }, 286 { 287 "o-o-b array accessor", 288 aArray, bArray, 289 coreAccessor{0, int(bArray.Nelems)}, 290 errImpossibleRelocation, 291 }, 292 { 293 "no match", 294 bStruct, aStruct, 295 coreAccessor{0, 2}, 296 errImpossibleRelocation, 297 }, 298 { 299 "incompatible match", 300 &Union{Members: []Member{{Name: "foo", Type: &Pointer{}}}}, 301 &Union{Members: []Member{{Name: "foo", Type: &Int{}}}}, 302 coreAccessor{0, 0}, 303 errImpossibleRelocation, 304 }, 305 { 306 "unsized type", 307 bStruct, &Func{}, 308 // non-zero accessor to force calculating the offset. 309 coreAccessor{1}, 310 errImpossibleRelocation, 311 }, 312 } 313 314 for _, test := range invalid { 315 t.Run(test.name, func(t *testing.T) { 316 _, _, err := coreFindField(test.local, test.acc, test.target) 317 if test.err != nil && !errors.Is(err, test.err) { 318 t.Fatalf("Expected %s, got %s", test.err, err) 319 } 320 if err == nil { 321 t.Fatal("Accepted invalid case") 322 } 323 t.Log(err) 324 }) 325 } 326 327 bytes := func(typ Type) uint32 { 328 sz, err := Sizeof(typ) 329 if err != nil { 330 t.Fatal(err) 331 } 332 return uint32(sz) 333 } 334 335 anon := func(t Type, offset Bits) []Member { 336 return []Member{{Type: t, Offset: offset}} 337 } 338 339 anonStruct := func(m ...Member) Member { 340 return Member{Type: &Struct{Members: m}} 341 } 342 343 anonUnion := func(m ...Member) Member { 344 return Member{Type: &Union{Members: m}} 345 } 346 347 valid := []struct { 348 name string 349 local Type 350 target Type 351 acc coreAccessor 352 localField, targetField coreField 353 }{ 354 { 355 "array[0]", 356 aArray, 357 bArray, 358 coreAccessor{0, 0}, 359 coreField{u16, 0, 0, 0}, 360 coreField{u32, 0, 0, 0}, 361 }, 362 { 363 "array[1]", 364 aArray, 365 bArray, 366 coreAccessor{0, 1}, 367 coreField{u16, bytes(aArray.Type), 0, 0}, 368 coreField{u32, bytes(bArray.Type), 0, 0}, 369 }, 370 { 371 "array[0] with base offset", 372 aArray, 373 bArray, 374 coreAccessor{1, 0}, 375 coreField{u16, bytes(aArray), 0, 0}, 376 coreField{u32, bytes(bArray), 0, 0}, 377 }, 378 { 379 "array[2] with base offset", 380 aArray, 381 bArray, 382 coreAccessor{1, 2}, 383 coreField{u16, bytes(aArray) + 2*bytes(aArray.Type), 0, 0}, 384 coreField{u32, bytes(bArray) + 2*bytes(bArray.Type), 0, 0}, 385 }, 386 { 387 "flex array", 388 &Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u16}}}}, 389 &Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u32}}}}, 390 coreAccessor{0, 0, 9000}, 391 coreField{u16, bytes(u16) * 9000, 0, 0}, 392 coreField{u32, bytes(u32) * 9000, 0, 0}, 393 }, 394 { 395 "struct.0", 396 aStruct, bStruct, 397 coreAccessor{0, 0}, 398 coreField{ptr, 1, 0, 0}, 399 coreField{ptr, 2, 0, 0}, 400 }, 401 { 402 "struct.0 anon", 403 aStruct, &Struct{Members: anon(bStruct, 24)}, 404 coreAccessor{0, 0}, 405 coreField{ptr, 1, 0, 0}, 406 coreField{ptr, 3 + 2, 0, 0}, 407 }, 408 { 409 "struct.0 with base offset", 410 aStruct, bStruct, 411 coreAccessor{3, 0}, 412 coreField{ptr, 3*bytes(aStruct) + 1, 0, 0}, 413 coreField{ptr, 3*bytes(bStruct) + 2, 0, 0}, 414 }, 415 { 416 "struct.1", 417 aStruct, bStruct, 418 coreAccessor{0, 1}, 419 coreField{u16, 2, 0, 0}, 420 coreField{u32, 1, 0, 0}, 421 }, 422 { 423 "struct.1 anon", 424 aStruct, &Struct{Members: anon(bStruct, 24)}, 425 coreAccessor{0, 1}, 426 coreField{u16, 2, 0, 0}, 427 coreField{u32, 3 + 1, 0, 0}, 428 }, 429 { 430 "union.1", 431 &Union{Members: aFields, Size: 32}, 432 &Union{Members: bFields, Size: 32}, 433 coreAccessor{0, 1}, 434 coreField{u16, 2, 0, 0}, 435 coreField{u32, 1, 0, 0}, 436 }, 437 { 438 "interchangeable composites", 439 &Struct{ 440 Members: []Member{ 441 anonStruct(anonUnion(Member{Name: "_1", Type: u16})), 442 }, 443 }, 444 &Struct{ 445 Members: []Member{ 446 anonUnion(anonStruct(Member{Name: "_1", Type: u16})), 447 }, 448 }, 449 coreAccessor{0, 0, 0, 0}, 450 coreField{u16, 0, 0, 0}, 451 coreField{u16, 0, 0, 0}, 452 }, 453 { 454 "struct.2 (bitfield baz)", 455 aStruct, bStruct, 456 coreAccessor{0, 2}, 457 coreField{u32, 4, 0, 3}, 458 coreField{u32, 8, 0, 3}, 459 }, 460 { 461 "struct.3 (bitfield quux)", 462 aStruct, bStruct, 463 coreAccessor{0, 3}, 464 coreField{u32, 4, 3, 10}, 465 coreField{u16, 12, 0, 10}, 466 }, 467 { 468 "struct.4 (bitfield quuz)", 469 aStruct, bStruct, 470 coreAccessor{0, 4}, 471 coreField{u32, 4, 13, 8}, 472 coreField{u16, 14, 0, 0}, 473 }, 474 } 475 476 allowCoreField := cmp.AllowUnexported(coreField{}) 477 478 checkCOREField := func(t *testing.T, which string, got, want coreField) { 479 t.Helper() 480 if diff := cmp.Diff(want, got, allowCoreField); diff != "" { 481 t.Errorf("%s mismatch (-want +got):\n%s", which, diff) 482 } 483 } 484 485 for _, test := range valid { 486 t.Run(test.name, func(t *testing.T) { 487 localField, targetField, err := coreFindField(test.local, test.acc, test.target) 488 qt.Assert(t, err, qt.IsNil) 489 checkCOREField(t, "local", localField, test.localField) 490 checkCOREField(t, "target", targetField, test.targetField) 491 }) 492 } 493 } 494 495 func TestCOREFindFieldCyclical(t *testing.T) { 496 members := []Member{{Name: "foo", Type: &Pointer{}}} 497 498 cyclicStruct := &Struct{} 499 cyclicStruct.Members = []Member{{Type: cyclicStruct}} 500 501 cyclicUnion := &Union{} 502 cyclicUnion.Members = []Member{{Type: cyclicUnion}} 503 504 cyclicArray := &Array{Nelems: 1} 505 cyclicArray.Type = &Pointer{Target: cyclicArray} 506 507 tests := []struct { 508 name string 509 local, cyclic Type 510 }{ 511 {"struct", &Struct{Members: members}, cyclicStruct}, 512 {"union", &Union{Members: members}, cyclicUnion}, 513 {"array", &Array{Nelems: 2, Type: &Int{}}, cyclicArray}, 514 } 515 516 for _, test := range tests { 517 t.Run(test.name, func(t *testing.T) { 518 _, _, err := coreFindField(test.local, coreAccessor{0, 0}, test.cyclic) 519 if !errors.Is(err, errImpossibleRelocation) { 520 t.Fatal("Should return errImpossibleRelocation, got", err) 521 } 522 }) 523 } 524 } 525 526 func TestCORERelocation(t *testing.T) { 527 testutils.Files(t, testutils.Glob(t, "testdata/*.elf"), func(t *testing.T, file string) { 528 rd, err := os.Open(file) 529 if err != nil { 530 t.Fatal(err) 531 } 532 defer rd.Close() 533 534 spec, extInfos, err := LoadSpecAndExtInfosFromReader(rd) 535 if err != nil { 536 t.Fatal(err) 537 } 538 539 if extInfos == nil { 540 t.Skip("No ext_infos") 541 } 542 543 errs := map[string]error{ 544 "err_ambiguous": errAmbiguousRelocation, 545 "err_ambiguous_flavour": errAmbiguousRelocation, 546 } 547 548 for section := range extInfos.funcInfos { 549 name := strings.TrimPrefix(section, "socket_filter/") 550 t.Run(name, func(t *testing.T) { 551 var relos []*CORERelocation 552 for _, reloInfo := range extInfos.relocationInfos[section] { 553 relos = append(relos, reloInfo.relo) 554 } 555 556 fixups, err := CORERelocate(relos, spec, spec.byteOrder) 557 if want := errs[name]; want != nil { 558 if !errors.Is(err, want) { 559 t.Fatal("Expected", want, "got", err) 560 } 561 return 562 } 563 564 if err != nil { 565 t.Fatal("Can't relocate against itself:", err) 566 } 567 568 for offset, fixup := range fixups { 569 if want := fixup.local; !fixup.skipLocalValidation && want != fixup.target { 570 // Since we're relocating against ourselves both values 571 // should match. 572 t.Errorf("offset %d: local %v doesn't match target %d (kind %s)", offset, fixup.local, fixup.target, fixup.kind) 573 } 574 } 575 }) 576 } 577 }) 578 } 579 580 func TestCORECopyWithoutQualifiers(t *testing.T) { 581 qualifiers := []struct { 582 name string 583 fn func(Type) Type 584 }{ 585 {"const", func(t Type) Type { return &Const{Type: t} }}, 586 {"volatile", func(t Type) Type { return &Volatile{Type: t} }}, 587 {"restrict", func(t Type) Type { return &Restrict{Type: t} }}, 588 {"typedef", func(t Type) Type { return &Typedef{Type: t} }}, 589 } 590 591 for _, test := range qualifiers { 592 t.Run(test.name+" cycle", func(t *testing.T) { 593 root := &Volatile{} 594 root.Type = test.fn(root) 595 596 cycle, ok := Copy(root, UnderlyingType).(*cycle) 597 qt.Assert(t, ok, qt.IsTrue) 598 qt.Assert(t, cycle.root, qt.Equals, root) 599 }) 600 } 601 602 for _, a := range qualifiers { 603 for _, b := range qualifiers { 604 t.Run(a.name+" "+b.name, func(t *testing.T) { 605 v := a.fn(&Pointer{Target: b.fn(&Int{Name: "z"})}) 606 want := &Pointer{Target: &Int{Name: "z"}} 607 608 got := Copy(v, UnderlyingType) 609 qt.Assert(t, got, qt.DeepEquals, want) 610 }) 611 } 612 } 613 614 t.Run("long chain", func(t *testing.T) { 615 root := &Int{Name: "abc"} 616 v := Type(root) 617 for i := 0; i < maxTypeDepth; i++ { 618 q := qualifiers[rand.Intn(len(qualifiers))] 619 v = q.fn(v) 620 t.Log(q.name) 621 } 622 623 got := Copy(v, UnderlyingType) 624 qt.Assert(t, got, qt.DeepEquals, root) 625 }) 626 }