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 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
244 {Name: "baz", Type: u32, Offset: 64, BitfieldSize: 3},
245
246 {Name: "quux", Type: u16, Offset: 96, BitfieldSize: 10},
247
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
564
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 }
620
View as plain text