package btf import ( "errors" "math/rand" "os" "strings" "testing" "github.com/cilium/ebpf/internal/testutils" "github.com/google/go-cmp/cmp" qt "github.com/frankban/quicktest" ) func TestCOREAreTypesCompatible(t *testing.T) { tests := []struct { a, b Type compatible bool }{ {&Void{}, &Void{}, true}, {&Struct{Name: "a"}, &Struct{Name: "b"}, true}, {&Union{Name: "a"}, &Union{Name: "b"}, true}, {&Union{Name: "a"}, &Struct{Name: "b"}, false}, {&Enum{Name: "a"}, &Enum{Name: "b"}, true}, {&Fwd{Name: "a"}, &Fwd{Name: "b"}, true}, {&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true}, {&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true}, {&Pointer{Target: &Void{}}, &Void{}, false}, {&Array{Index: &Void{}, Type: &Void{}}, &Array{Index: &Void{}, Type: &Void{}}, true}, {&Array{Index: &Void{}, Type: &Int{}}, &Array{Index: &Void{}, Type: &Void{}}, false}, {&FuncProto{Return: &Int{}}, &FuncProto{Return: &Void{}}, false}, { &FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "a", Type: &Void{}}}}, &FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "b", Type: &Void{}}}}, true, }, { &FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}}, &FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Int{}}}}, false, }, { &FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}, {Type: &Void{}}}}, &FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}}, false, }, } for _, test := range tests { err := coreAreTypesCompatible(test.a, test.b) if test.compatible { if err != nil { t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b) continue } } else { if !errors.Is(err, errImpossibleRelocation) { t.Errorf("Expected types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b) continue } } err = coreAreTypesCompatible(test.b, test.a) if test.compatible { if err != nil { t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b) } } else { if !errors.Is(err, errImpossibleRelocation) { t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b) } } } for _, invalid := range []Type{&Var{}, &Datasec{}} { err := coreAreTypesCompatible(invalid, invalid) if errors.Is(err, errImpossibleRelocation) { t.Errorf("Expected an error for %T, not errImpossibleRelocation", invalid) } else if err == nil { t.Errorf("Expected an error for %T", invalid) } } } func TestCOREAreMembersCompatible(t *testing.T) { tests := []struct { a, b Type compatible bool }{ {&Struct{Name: "a"}, &Struct{Name: "b"}, true}, {&Union{Name: "a"}, &Union{Name: "b"}, true}, {&Union{Name: "a"}, &Struct{Name: "b"}, true}, {&Enum{Name: "a"}, &Enum{Name: "b"}, false}, {&Enum{Name: "a"}, &Enum{Name: "a___foo"}, true}, {&Enum{Name: "a"}, &Enum{Name: ""}, true}, {&Fwd{Name: "a"}, &Fwd{Name: "b"}, false}, {&Fwd{Name: "a"}, &Fwd{Name: "a___foo"}, true}, {&Fwd{Name: "a"}, &Fwd{Name: ""}, true}, {&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true}, {&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true}, {&Pointer{Target: &Void{}}, &Void{}, false}, {&Array{Type: &Int{Size: 1}}, &Array{Type: &Int{Encoding: Signed}}, true}, {&Float{Size: 2}, &Float{Size: 4}, true}, } for _, test := range tests { err := coreAreMembersCompatible(test.a, test.b) if test.compatible { if err != nil { t.Errorf("Expected members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b) continue } } else { if !errors.Is(err, errImpossibleRelocation) { t.Errorf("Expected members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b) continue } } err = coreAreMembersCompatible(test.b, test.a) if test.compatible { if err != nil { t.Errorf("Expected reversed members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b) } } else { if !errors.Is(err, errImpossibleRelocation) { t.Errorf("Expected reversed members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b) } } } for _, invalid := range []Type{&Void{}, &FuncProto{}, &Var{}, &Datasec{}} { err := coreAreMembersCompatible(invalid, invalid) if errors.Is(err, errImpossibleRelocation) { t.Errorf("Expected an error for %T, not errImpossibleRelocation", invalid) } else if err == nil { t.Errorf("Expected an error for %T", invalid) } } } func TestCOREAccessor(t *testing.T) { for _, valid := range []string{ "0", "1:0", "1:0:3:34:10:1", } { _, err := parseCOREAccessor(valid) if err != nil { t.Errorf("Parse %q: %s", valid, err) } } for _, invalid := range []string{ "", "-1", ":", "0:", ":12", "4294967296", } { _, err := parseCOREAccessor(invalid) if err == nil { t.Errorf("Accepted invalid accessor %q", invalid) } } } func TestCOREFindEnumValue(t *testing.T) { a := &Enum{Values: []EnumValue{{"foo", 23}, {"bar", 42}}} b := &Enum{Values: []EnumValue{ {"foo___flavour", 0}, {"bar", 123}, {"garbage", 3}, }} invalid := []struct { name string local Type target Type acc coreAccessor err error }{ {"o-o-b accessor", a, b, coreAccessor{len(a.Values)}, nil}, {"long accessor", a, b, coreAccessor{0, 1}, nil}, {"wrong target", a, &Void{}, coreAccessor{0, 1}, nil}, { "no matching value", b, a, coreAccessor{2}, errImpossibleRelocation, }, } for _, test := range invalid { t.Run(test.name, func(t *testing.T) { _, _, err := coreFindEnumValue(test.local, test.acc, test.target) if test.err != nil && !errors.Is(err, test.err) { t.Fatalf("Expected %s, got %s", test.err, err) } if err == nil { t.Fatal("Accepted invalid case") } }) } valid := []struct { name string local, target Type acc coreAccessor localValue, targetValue int32 }{ {"a to b", a, b, coreAccessor{0}, 23, 0}, {"b to a", b, a, coreAccessor{1}, 123, 42}, } for _, test := range valid { t.Run(test.name, func(t *testing.T) { local, target, err := coreFindEnumValue(test.local, test.acc, test.target) qt.Assert(t, err, qt.IsNil) qt.Check(t, local.Value, qt.Equals, test.localValue) qt.Check(t, target.Value, qt.Equals, test.targetValue) }) } } func TestCOREFindField(t *testing.T) { ptr := &Pointer{} u16 := &Int{Size: 2} u32 := &Int{Size: 4} aFields := []Member{ {Name: "foo", Type: ptr, Offset: 8}, {Name: "bar", Type: u16, Offset: 16}, {Name: "baz", Type: u32, Offset: 32, BitfieldSize: 3}, {Name: "quux", Type: u32, Offset: 35, BitfieldSize: 10}, {Name: "quuz", Type: u32, Offset: 45, BitfieldSize: 8}, } bFields := []Member{ {Name: "foo", Type: ptr, Offset: 16}, {Name: "bar", Type: u32, Offset: 8}, {Name: "other", Offset: 4}, // baz is separated out from the other bitfields {Name: "baz", Type: u32, Offset: 64, BitfieldSize: 3}, // quux's type changes u32->u16 {Name: "quux", Type: u16, Offset: 96, BitfieldSize: 10}, // quuz becomes a normal field {Name: "quuz", Type: u16, Offset: 112}, } aStruct := &Struct{Members: aFields, Size: 48} bStruct := &Struct{Members: bFields, Size: 80} aArray := &Array{Nelems: 4, Type: u16} bArray := &Array{Nelems: 3, Type: u32} invalid := []struct { name string local, target Type acc coreAccessor err error }{ { "unsupported type", &Void{}, &Void{}, coreAccessor{0, 0}, ErrNotSupported, }, { "different types", &Union{}, &Array{Type: u16}, coreAccessor{0}, errImpossibleRelocation, }, { "invalid composite accessor", aStruct, aStruct, coreAccessor{0, len(aStruct.Members)}, nil, }, { "invalid array accessor", aArray, aArray, coreAccessor{0, int(aArray.Nelems)}, nil, }, { "o-o-b array accessor", aArray, bArray, coreAccessor{0, int(bArray.Nelems)}, errImpossibleRelocation, }, { "no match", bStruct, aStruct, coreAccessor{0, 2}, errImpossibleRelocation, }, { "incompatible match", &Union{Members: []Member{{Name: "foo", Type: &Pointer{}}}}, &Union{Members: []Member{{Name: "foo", Type: &Int{}}}}, coreAccessor{0, 0}, errImpossibleRelocation, }, } for _, test := range invalid { t.Run(test.name, func(t *testing.T) { _, _, err := coreFindField(test.local, test.acc, test.target) if test.err != nil && !errors.Is(err, test.err) { t.Fatalf("Expected %s, got %s", test.err, err) } if err == nil { t.Fatal("Accepted invalid case") } t.Log(err) }) } bytes := func(typ Type) uint32 { sz, err := Sizeof(typ) if err != nil { t.Fatal(err) } return uint32(sz) } anon := func(t Type, offset Bits) []Member { return []Member{{Type: t, Offset: offset}} } anonStruct := func(m ...Member) Member { return Member{Type: &Struct{Members: m}} } anonUnion := func(m ...Member) Member { return Member{Type: &Union{Members: m}} } valid := []struct { name string local Type target Type acc coreAccessor localField, targetField coreField }{ { "array[0]", aArray, bArray, coreAccessor{0, 0}, coreField{u16, 0, 0, 0}, coreField{u32, 0, 0, 0}, }, { "array[1]", aArray, bArray, coreAccessor{0, 1}, coreField{u16, bytes(aArray.Type), 0, 0}, coreField{u32, bytes(bArray.Type), 0, 0}, }, { "array[0] with base offset", aArray, bArray, coreAccessor{1, 0}, coreField{u16, bytes(aArray), 0, 0}, coreField{u32, bytes(bArray), 0, 0}, }, { "array[2] with base offset", aArray, bArray, coreAccessor{1, 2}, coreField{u16, bytes(aArray) + 2*bytes(aArray.Type), 0, 0}, coreField{u32, bytes(bArray) + 2*bytes(bArray.Type), 0, 0}, }, { "flex array", &Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u16}}}}, &Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u32}}}}, coreAccessor{0, 0, 9000}, coreField{u16, bytes(u16) * 9000, 0, 0}, coreField{u32, bytes(u32) * 9000, 0, 0}, }, { "struct.0", aStruct, bStruct, coreAccessor{0, 0}, coreField{ptr, 1, 0, 0}, coreField{ptr, 2, 0, 0}, }, { "struct.0 anon", aStruct, &Struct{Members: anon(bStruct, 24)}, coreAccessor{0, 0}, coreField{ptr, 1, 0, 0}, coreField{ptr, 3 + 2, 0, 0}, }, { "struct.0 with base offset", aStruct, bStruct, coreAccessor{3, 0}, coreField{ptr, 3*bytes(aStruct) + 1, 0, 0}, coreField{ptr, 3*bytes(bStruct) + 2, 0, 0}, }, { "struct.1", aStruct, bStruct, coreAccessor{0, 1}, coreField{u16, 2, 0, 0}, coreField{u32, 1, 0, 0}, }, { "struct.1 anon", aStruct, &Struct{Members: anon(bStruct, 24)}, coreAccessor{0, 1}, coreField{u16, 2, 0, 0}, coreField{u32, 3 + 1, 0, 0}, }, { "union.1", &Union{Members: aFields, Size: 32}, &Union{Members: bFields, Size: 32}, coreAccessor{0, 1}, coreField{u16, 2, 0, 0}, coreField{u32, 1, 0, 0}, }, { "interchangeable composites", &Struct{ Members: []Member{ anonStruct(anonUnion(Member{Name: "_1", Type: u16})), }, }, &Struct{ Members: []Member{ anonUnion(anonStruct(Member{Name: "_1", Type: u16})), }, }, coreAccessor{0, 0, 0, 0}, coreField{u16, 0, 0, 0}, coreField{u16, 0, 0, 0}, }, { "struct.2 (bitfield baz)", aStruct, bStruct, coreAccessor{0, 2}, coreField{u32, 4, 0, 3}, coreField{u32, 8, 0, 3}, }, { "struct.3 (bitfield quux)", aStruct, bStruct, coreAccessor{0, 3}, coreField{u32, 4, 3, 10}, coreField{u16, 12, 0, 10}, }, { "struct.4 (bitfield quuz)", aStruct, bStruct, coreAccessor{0, 4}, coreField{u32, 4, 13, 8}, coreField{u16, 14, 0, 0}, }, } allowCoreField := cmp.AllowUnexported(coreField{}) checkCOREField := func(t *testing.T, which string, got, want coreField) { t.Helper() if diff := cmp.Diff(want, got, allowCoreField); diff != "" { t.Errorf("%s mismatch (-want +got):\n%s", which, diff) } } for _, test := range valid { t.Run(test.name, func(t *testing.T) { localField, targetField, err := coreFindField(test.local, test.acc, test.target) qt.Assert(t, err, qt.IsNil) checkCOREField(t, "local", localField, test.localField) checkCOREField(t, "target", targetField, test.targetField) }) } } func TestCOREFindFieldCyclical(t *testing.T) { members := []Member{{Name: "foo", Type: &Pointer{}}} cyclicStruct := &Struct{} cyclicStruct.Members = []Member{{Type: cyclicStruct}} cyclicUnion := &Union{} cyclicUnion.Members = []Member{{Type: cyclicUnion}} cyclicArray := &Array{Nelems: 1} cyclicArray.Type = &Pointer{Target: cyclicArray} tests := []struct { name string local, cyclic Type }{ {"struct", &Struct{Members: members}, cyclicStruct}, {"union", &Union{Members: members}, cyclicUnion}, {"array", &Array{Nelems: 2, Type: &Int{}}, cyclicArray}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { _, _, err := coreFindField(test.local, coreAccessor{0, 0}, test.cyclic) if !errors.Is(err, errImpossibleRelocation) { t.Fatal("Should return errImpossibleRelocation, got", err) } }) } } func TestCORERelocation(t *testing.T) { testutils.Files(t, testutils.Glob(t, "testdata/*.elf"), func(t *testing.T, file string) { rd, err := os.Open(file) if err != nil { t.Fatal(err) } defer rd.Close() spec, extInfos, err := LoadSpecAndExtInfosFromReader(rd) if err != nil { t.Fatal(err) } if extInfos == nil { t.Skip("No ext_infos") } errs := map[string]error{ "err_ambiguous": errAmbiguousRelocation, "err_ambiguous_flavour": errAmbiguousRelocation, } for section := range extInfos.funcInfos { name := strings.TrimPrefix(section, "socket_filter/") t.Run(name, func(t *testing.T) { var relos []*CORERelocation for _, reloInfo := range extInfos.relocationInfos[section] { relos = append(relos, reloInfo.relo) } fixups, err := CORERelocate(spec, spec, relos) if want := errs[name]; want != nil { if !errors.Is(err, want) { t.Fatal("Expected", want, "got", err) } return } if err != nil { t.Fatal("Can't relocate against itself:", err) } for offset, fixup := range fixups { if want := fixup.local; !fixup.skipLocalValidation && want != fixup.target { // Since we're relocating against ourselves both values // should match. t.Errorf("offset %d: local %v doesn't match target %d (kind %s)", offset, fixup.local, fixup.target, fixup.kind) } } }) } }) } func TestCORECopyWithoutQualifiers(t *testing.T) { qualifiers := []struct { name string fn func(Type) Type }{ {"const", func(t Type) Type { return &Const{Type: t} }}, {"volatile", func(t Type) Type { return &Volatile{Type: t} }}, {"restrict", func(t Type) Type { return &Restrict{Type: t} }}, {"typedef", func(t Type) Type { return &Typedef{Type: t} }}, } for _, test := range qualifiers { t.Run(test.name+" cycle", func(t *testing.T) { root := &Volatile{} root.Type = test.fn(root) cycle, ok := Copy(root, UnderlyingType).(*cycle) qt.Assert(t, ok, qt.IsTrue) qt.Assert(t, cycle.root, qt.Equals, root) }) } for _, a := range qualifiers { for _, b := range qualifiers { t.Run(a.name+" "+b.name, func(t *testing.T) { v := a.fn(&Pointer{Target: b.fn(&Int{Name: "z"})}) want := &Pointer{Target: &Int{Name: "z"}} got := Copy(v, UnderlyingType) qt.Assert(t, got, qt.DeepEquals, want) }) } } t.Run("long chain", func(t *testing.T) { root := &Int{Name: "abc"} v := Type(root) for i := 0; i < maxTypeDepth; i++ { q := qualifiers[rand.Intn(len(qualifiers))] v = q.fn(v) t.Log(q.name) } got := Copy(v, UnderlyingType) qt.Assert(t, got, qt.DeepEquals, root) }) }