github.com/kubeshark/ebpf@v0.9.2/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/google/go-cmp/cmp" 11 "github.com/kubeshark/ebpf/internal/testutils" 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 int32 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 307 for _, test := range invalid { 308 t.Run(test.name, func(t *testing.T) { 309 _, _, err := coreFindField(test.local, test.acc, test.target) 310 if test.err != nil && !errors.Is(err, test.err) { 311 t.Fatalf("Expected %s, got %s", test.err, err) 312 } 313 if err == nil { 314 t.Fatal("Accepted invalid case") 315 } 316 t.Log(err) 317 }) 318 } 319 320 bytes := func(typ Type) uint32 { 321 sz, err := Sizeof(typ) 322 if err != nil { 323 t.Fatal(err) 324 } 325 return uint32(sz) 326 } 327 328 anon := func(t Type, offset Bits) []Member { 329 return []Member{{Type: t, Offset: offset}} 330 } 331 332 anonStruct := func(m ...Member) Member { 333 return Member{Type: &Struct{Members: m}} 334 } 335 336 anonUnion := func(m ...Member) Member { 337 return Member{Type: &Union{Members: m}} 338 } 339 340 valid := []struct { 341 name string 342 local Type 343 target Type 344 acc coreAccessor 345 localField, targetField coreField 346 }{ 347 { 348 "array[0]", 349 aArray, 350 bArray, 351 coreAccessor{0, 0}, 352 coreField{u16, 0, 0, 0}, 353 coreField{u32, 0, 0, 0}, 354 }, 355 { 356 "array[1]", 357 aArray, 358 bArray, 359 coreAccessor{0, 1}, 360 coreField{u16, bytes(aArray.Type), 0, 0}, 361 coreField{u32, bytes(bArray.Type), 0, 0}, 362 }, 363 { 364 "array[0] with base offset", 365 aArray, 366 bArray, 367 coreAccessor{1, 0}, 368 coreField{u16, bytes(aArray), 0, 0}, 369 coreField{u32, bytes(bArray), 0, 0}, 370 }, 371 { 372 "array[2] with base offset", 373 aArray, 374 bArray, 375 coreAccessor{1, 2}, 376 coreField{u16, bytes(aArray) + 2*bytes(aArray.Type), 0, 0}, 377 coreField{u32, bytes(bArray) + 2*bytes(bArray.Type), 0, 0}, 378 }, 379 { 380 "flex array", 381 &Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u16}}}}, 382 &Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u32}}}}, 383 coreAccessor{0, 0, 9000}, 384 coreField{u16, bytes(u16) * 9000, 0, 0}, 385 coreField{u32, bytes(u32) * 9000, 0, 0}, 386 }, 387 { 388 "struct.0", 389 aStruct, bStruct, 390 coreAccessor{0, 0}, 391 coreField{ptr, 1, 0, 0}, 392 coreField{ptr, 2, 0, 0}, 393 }, 394 { 395 "struct.0 anon", 396 aStruct, &Struct{Members: anon(bStruct, 24)}, 397 coreAccessor{0, 0}, 398 coreField{ptr, 1, 0, 0}, 399 coreField{ptr, 3 + 2, 0, 0}, 400 }, 401 { 402 "struct.0 with base offset", 403 aStruct, bStruct, 404 coreAccessor{3, 0}, 405 coreField{ptr, 3*bytes(aStruct) + 1, 0, 0}, 406 coreField{ptr, 3*bytes(bStruct) + 2, 0, 0}, 407 }, 408 { 409 "struct.1", 410 aStruct, bStruct, 411 coreAccessor{0, 1}, 412 coreField{u16, 2, 0, 0}, 413 coreField{u32, 1, 0, 0}, 414 }, 415 { 416 "struct.1 anon", 417 aStruct, &Struct{Members: anon(bStruct, 24)}, 418 coreAccessor{0, 1}, 419 coreField{u16, 2, 0, 0}, 420 coreField{u32, 3 + 1, 0, 0}, 421 }, 422 { 423 "union.1", 424 &Union{Members: aFields, Size: 32}, 425 &Union{Members: bFields, Size: 32}, 426 coreAccessor{0, 1}, 427 coreField{u16, 2, 0, 0}, 428 coreField{u32, 1, 0, 0}, 429 }, 430 { 431 "interchangeable composites", 432 &Struct{ 433 Members: []Member{ 434 anonStruct(anonUnion(Member{Name: "_1", Type: u16})), 435 }, 436 }, 437 &Struct{ 438 Members: []Member{ 439 anonUnion(anonStruct(Member{Name: "_1", Type: u16})), 440 }, 441 }, 442 coreAccessor{0, 0, 0, 0}, 443 coreField{u16, 0, 0, 0}, 444 coreField{u16, 0, 0, 0}, 445 }, 446 { 447 "struct.2 (bitfield baz)", 448 aStruct, bStruct, 449 coreAccessor{0, 2}, 450 coreField{u32, 4, 0, 3}, 451 coreField{u32, 8, 0, 3}, 452 }, 453 { 454 "struct.3 (bitfield quux)", 455 aStruct, bStruct, 456 coreAccessor{0, 3}, 457 coreField{u32, 4, 3, 10}, 458 coreField{u16, 12, 0, 10}, 459 }, 460 { 461 "struct.4 (bitfield quuz)", 462 aStruct, bStruct, 463 coreAccessor{0, 4}, 464 coreField{u32, 4, 13, 8}, 465 coreField{u16, 14, 0, 0}, 466 }, 467 } 468 469 allowCoreField := cmp.AllowUnexported(coreField{}) 470 471 checkCOREField := func(t *testing.T, which string, got, want coreField) { 472 t.Helper() 473 if diff := cmp.Diff(want, got, allowCoreField); diff != "" { 474 t.Errorf("%s mismatch (-want +got):\n%s", which, diff) 475 } 476 } 477 478 for _, test := range valid { 479 t.Run(test.name, func(t *testing.T) { 480 localField, targetField, err := coreFindField(test.local, test.acc, test.target) 481 qt.Assert(t, err, qt.IsNil) 482 checkCOREField(t, "local", localField, test.localField) 483 checkCOREField(t, "target", targetField, test.targetField) 484 }) 485 } 486 } 487 488 func TestCOREFindFieldCyclical(t *testing.T) { 489 members := []Member{{Name: "foo", Type: &Pointer{}}} 490 491 cyclicStruct := &Struct{} 492 cyclicStruct.Members = []Member{{Type: cyclicStruct}} 493 494 cyclicUnion := &Union{} 495 cyclicUnion.Members = []Member{{Type: cyclicUnion}} 496 497 cyclicArray := &Array{Nelems: 1} 498 cyclicArray.Type = &Pointer{Target: cyclicArray} 499 500 tests := []struct { 501 name string 502 local, cyclic Type 503 }{ 504 {"struct", &Struct{Members: members}, cyclicStruct}, 505 {"union", &Union{Members: members}, cyclicUnion}, 506 {"array", &Array{Nelems: 2, Type: &Int{}}, cyclicArray}, 507 } 508 509 for _, test := range tests { 510 t.Run(test.name, func(t *testing.T) { 511 _, _, err := coreFindField(test.local, coreAccessor{0, 0}, test.cyclic) 512 if !errors.Is(err, errImpossibleRelocation) { 513 t.Fatal("Should return errImpossibleRelocation, got", err) 514 } 515 }) 516 } 517 } 518 519 func TestCORERelocation(t *testing.T) { 520 testutils.Files(t, testutils.Glob(t, "testdata/*.elf"), func(t *testing.T, file string) { 521 rd, err := os.Open(file) 522 if err != nil { 523 t.Fatal(err) 524 } 525 defer rd.Close() 526 527 spec, extInfos, err := LoadSpecAndExtInfosFromReader(rd) 528 if err != nil { 529 t.Fatal(err) 530 } 531 532 if extInfos == nil { 533 t.Skip("No ext_infos") 534 } 535 536 errs := map[string]error{ 537 "err_ambiguous": errAmbiguousRelocation, 538 "err_ambiguous_flavour": errAmbiguousRelocation, 539 } 540 541 for section := range extInfos.funcInfos { 542 name := strings.TrimPrefix(section, "socket_filter/") 543 t.Run(name, func(t *testing.T) { 544 var relos []*CORERelocation 545 for _, reloInfo := range extInfos.relocationInfos[section] { 546 relos = append(relos, reloInfo.relo) 547 } 548 549 fixups, err := CORERelocate(spec, spec, relos) 550 if want := errs[name]; want != nil { 551 if !errors.Is(err, want) { 552 t.Fatal("Expected", want, "got", err) 553 } 554 return 555 } 556 557 if err != nil { 558 t.Fatal("Can't relocate against itself:", err) 559 } 560 561 for offset, fixup := range fixups { 562 if want := fixup.local; !fixup.skipLocalValidation && want != fixup.target { 563 // Since we're relocating against ourselves both values 564 // should match. 565 t.Errorf("offset %d: local %v doesn't match target %d (kind %s)", offset, fixup.local, fixup.target, fixup.kind) 566 } 567 } 568 }) 569 } 570 }) 571 } 572 573 func TestCORECopyWithoutQualifiers(t *testing.T) { 574 qualifiers := []struct { 575 name string 576 fn func(Type) Type 577 }{ 578 {"const", func(t Type) Type { return &Const{Type: t} }}, 579 {"volatile", func(t Type) Type { return &Volatile{Type: t} }}, 580 {"restrict", func(t Type) Type { return &Restrict{Type: t} }}, 581 {"typedef", func(t Type) Type { return &Typedef{Type: t} }}, 582 } 583 584 for _, test := range qualifiers { 585 t.Run(test.name+" cycle", func(t *testing.T) { 586 root := &Volatile{} 587 root.Type = test.fn(root) 588 589 cycle, ok := Copy(root, UnderlyingType).(*cycle) 590 qt.Assert(t, ok, qt.IsTrue) 591 qt.Assert(t, cycle.root, qt.Equals, root) 592 }) 593 } 594 595 for _, a := range qualifiers { 596 for _, b := range qualifiers { 597 t.Run(a.name+" "+b.name, func(t *testing.T) { 598 v := a.fn(&Pointer{Target: b.fn(&Int{Name: "z"})}) 599 want := &Pointer{Target: &Int{Name: "z"}} 600 601 got := Copy(v, UnderlyingType) 602 qt.Assert(t, got, qt.DeepEquals, want) 603 }) 604 } 605 } 606 607 t.Run("long chain", func(t *testing.T) { 608 root := &Int{Name: "abc"} 609 v := Type(root) 610 for i := 0; i < maxTypeDepth; i++ { 611 q := qualifiers[rand.Intn(len(qualifiers))] 612 v = q.fn(v) 613 t.Log(q.name) 614 } 615 616 got := Copy(v, UnderlyingType) 617 qt.Assert(t, got, qt.DeepEquals, root) 618 }) 619 }