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 )
16
17 var vmlinux struct {
18 sync.Once
19 err error
20 raw []byte
21 }
22
23 func readVMLinux(tb testing.TB) *bytes.Reader {
24 tb.Helper()
25
26 vmlinux.Do(func() {
27 vmlinux.raw, vmlinux.err = internal.ReadAllCompressed("testdata/vmlinux.btf.gz")
28 })
29
30 if vmlinux.err != nil {
31 tb.Fatal(vmlinux.err)
32 }
33
34 return bytes.NewReader(vmlinux.raw)
35 }
36
37 func parseELFBTF(tb testing.TB, file string) *Spec {
38 tb.Helper()
39
40 spec, err := LoadSpec(file)
41 if err != nil {
42 tb.Fatal("Can't load BTF:", err)
43 }
44
45 return spec
46 }
47
48 func TestAnyTypesByName(t *testing.T) {
49 testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
50 spec := parseELFBTF(t, file)
51
52 types, err := spec.AnyTypesByName("ambiguous")
53 if err != nil {
54 t.Fatal(err)
55 }
56
57 if len(types) != 1 {
58 t.Fatalf("expected to receive exactly 1 types from querying ambiguous type, got: %v", types)
59 }
60
61 types, err = spec.AnyTypesByName("ambiguous___flavour")
62 if err != nil {
63 t.Fatal(err)
64 }
65
66 if len(types) != 1 {
67 t.Fatalf("expected to receive exactly 1 type from querying ambiguous flavour, got: %v", types)
68 }
69 })
70 }
71
72 func TestTypeByNameAmbiguous(t *testing.T) {
73 testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
74 spec := parseELFBTF(t, file)
75
76 var typ *Struct
77 if err := spec.TypeByName("ambiguous", &typ); err != nil {
78 t.Fatal(err)
79 }
80
81 if name := typ.TypeName(); name != "ambiguous" {
82 t.Fatal("expected type name 'ambiguous', got:", name)
83 }
84
85 if err := spec.TypeByName("ambiguous___flavour", &typ); err != nil {
86 t.Fatal(err)
87 }
88
89 if name := typ.TypeName(); name != "ambiguous___flavour" {
90 t.Fatal("expected type name 'ambiguous___flavour', got:", name)
91 }
92 })
93 }
94
95 func TestTypeByName(t *testing.T) {
96 spec, err := LoadSpecFromReader(readVMLinux(t))
97 if err != nil {
98 t.Fatal(err)
99 }
100
101 for _, typ := range []interface{}{
102 nil,
103 Struct{},
104 &Struct{},
105 []Struct{},
106 &[]Struct{},
107 map[int]Struct{},
108 &map[int]Struct{},
109 int(0),
110 new(int),
111 } {
112 t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) {
113
114 if err := spec.TypeByName("iphdr", typ); err == nil {
115 t.Fatalf("TypeByName does not fail with type %T", typ)
116 }
117 })
118 }
119
120
121 var iphdr1, iphdr2 *Struct
122 if err := spec.TypeByName("iphdr", &iphdr1); err != nil {
123 t.Fatal(err)
124 }
125 if err := spec.TypeByName("iphdr", &iphdr2); err != nil {
126 t.Fatal(err)
127 }
128
129 if iphdr1 != iphdr2 {
130 t.Fatal("multiple TypeByName calls for `iphdr` name do not return the same addresses")
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145 m := iphdr1.Members[1]
146 if m.Name != "version" {
147 t.Fatal("Expected version as the second member, got", m.Name)
148 }
149 td, ok := m.Type.(*Typedef)
150 if !ok {
151 t.Fatalf("version member of iphdr should be a __u8 typedef: actual: %T", m.Type)
152 }
153 u8, ok := td.Type.(*Int)
154 if !ok {
155 t.Fatalf("__u8 typedef should point to an Int type: actual: %T", td.Type)
156 }
157 if m.BitfieldSize != 4 {
158 t.Fatalf("incorrect bitfield size: expected: 4 actual: %d", m.BitfieldSize)
159 }
160 if u8.Encoding != 0 {
161 t.Fatalf("incorrect encoding of an __u8 int: expected: 0 actual: %x", u8.Encoding)
162 }
163 if m.Offset != 4 {
164 t.Fatalf("incorrect bitfield offset: expected: 4 actual: %d", m.Offset)
165 }
166 }
167
168 func BenchmarkParseVmlinux(b *testing.B) {
169 rd := readVMLinux(b)
170 b.ReportAllocs()
171 b.ResetTimer()
172
173 for n := 0; n < b.N; n++ {
174 if _, err := rd.Seek(0, io.SeekStart); err != nil {
175 b.Fatal(err)
176 }
177
178 if _, err := loadRawSpec(rd, binary.LittleEndian, nil, nil); err != nil {
179 b.Fatal("Can't load BTF:", err)
180 }
181 }
182 }
183
184 func TestParseCurrentKernelBTF(t *testing.T) {
185 spec, err := LoadKernelSpec()
186 testutils.SkipIfNotSupported(t, err)
187 if err != nil {
188 t.Fatal("Can't load BTF:", err)
189 }
190
191 if len(spec.namedTypes) == 0 {
192 t.Fatal("Empty kernel BTF")
193 }
194
195 totalBytes := 0
196 distinct := 0
197 seen := make(map[string]bool)
198 for _, str := range spec.strings.strings {
199 totalBytes += len(str)
200 if !seen[str] {
201 distinct++
202 seen[str] = true
203 }
204 }
205 t.Logf("%d strings total, %d distinct", len(spec.strings.strings), distinct)
206 t.Logf("Average string size: %.0f", float64(totalBytes)/float64(len(spec.strings.strings)))
207 }
208
209 func TestFindVMLinux(t *testing.T) {
210 file, err := findVMLinux()
211 testutils.SkipIfNotSupported(t, err)
212 if err != nil {
213 t.Fatal("Can't find vmlinux:", err)
214 }
215 defer file.Close()
216
217 spec, err := loadSpecFromELF(file)
218 if err != nil {
219 t.Fatal("Can't load BTF:", err)
220 }
221
222 if len(spec.namedTypes) == 0 {
223 t.Fatal("Empty kernel BTF")
224 }
225 }
226
227 func TestLoadSpecFromElf(t *testing.T) {
228 testutils.Files(t, testutils.Glob(t, "../testdata/loader-e*.elf"), func(t *testing.T, file string) {
229 spec := parseELFBTF(t, file)
230
231 vt, err := spec.TypeByID(0)
232 if err != nil {
233 t.Error("Can't retrieve void type by ID:", err)
234 }
235 if _, ok := vt.(*Void); !ok {
236 t.Errorf("Expected Void for type id 0, but got: %T", vt)
237 }
238
239 var bpfMapDef *Struct
240 if err := spec.TypeByName("bpf_map_def", &bpfMapDef); err != nil {
241 t.Error("Can't find bpf_map_def:", err)
242 }
243
244 var tmp *Void
245 if err := spec.TypeByName("totally_bogus_type", &tmp); !errors.Is(err, ErrNotFound) {
246 t.Error("TypeByName doesn't return ErrNotFound:", err)
247 }
248
249 var fn *Func
250 if err := spec.TypeByName("global_fn", &fn); err != nil {
251 t.Error("Can't find global_fn():", err)
252 } else {
253 if fn.Linkage != GlobalFunc {
254 t.Error("Expected global linkage:", fn)
255 }
256 }
257
258 var v *Var
259 if err := spec.TypeByName("key3", &v); err != nil {
260 t.Error("Cant find key3:", err)
261 } else {
262 if v.Linkage != GlobalVar {
263 t.Error("Expected global linkage:", v)
264 }
265 }
266
267 if spec.byteOrder != internal.NativeEndian {
268 return
269 }
270
271 t.Run("Handle", func(t *testing.T) {
272 btf, err := NewHandle(spec)
273 testutils.SkipIfNotSupported(t, err)
274 if err != nil {
275 t.Fatal("Can't load BTF:", err)
276 }
277 defer btf.Close()
278 })
279 })
280 }
281
282 func TestLoadKernelSpec(t *testing.T) {
283 if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) {
284 t.Skip("/sys/kernel/btf/vmlinux not present")
285 }
286
287 _, err := LoadKernelSpec()
288 if err != nil {
289 t.Fatal("Can't load kernel spec:", err)
290 }
291 }
292
293 func TestGuessBTFByteOrder(t *testing.T) {
294 bo := guessRawBTFByteOrder(readVMLinux(t))
295 if bo != binary.LittleEndian {
296 t.Fatalf("Guessed %s instead of %s", bo, binary.LittleEndian)
297 }
298 }
299
300 func TestSpecCopy(t *testing.T) {
301 spec := parseELFBTF(t, "../testdata/loader-el.elf")
302
303 if len(spec.types) < 1 {
304 t.Fatal("Not enough types")
305 }
306
307 cpy := spec.Copy()
308 for i := range cpy.types {
309 if _, ok := cpy.types[i].(*Void); ok {
310
311
312
313 continue
314 }
315
316 if cpy.types[i] == spec.types[i] {
317 t.Fatalf("Type at index %d is not a copy: %T == %T", i, cpy.types[i], spec.types[i])
318 }
319 }
320 }
321
322 func TestHaveBTF(t *testing.T) {
323 testutils.CheckFeatureTest(t, haveBTF)
324 }
325
326 func TestHaveFuncLinkage(t *testing.T) {
327 testutils.CheckFeatureTest(t, haveFuncLinkage)
328 }
329
330 func ExampleSpec_TypeByName() {
331
332 spec := new(Spec)
333
334
335 var foo *Struct
336
337 if err := spec.TypeByName("foo", &foo); err != nil {
338
339
340 }
341
342
343 fmt.Println(foo.Name)
344 }
345
346 func TestTypesIterator(t *testing.T) {
347 spec, err := LoadSpecFromReader(readVMLinux(t))
348 if err != nil {
349 t.Fatal(err)
350 }
351
352 if len(spec.types) < 1 {
353 t.Fatal("Not enough types")
354 }
355
356
357 _, err = spec.AnyTypeByName("iphdr")
358 if err != nil {
359 t.Fatalf("Failed to find 'iphdr' type by name: %s", err)
360 }
361
362 found := false
363 count := 0
364
365 iter := spec.Iterate()
366 for iter.Next() {
367 if !found && iter.Type.TypeName() == "iphdr" {
368 found = true
369 }
370 count += 1
371 }
372
373 if l := len(spec.types); l != count {
374 t.Fatalf("Failed to iterate over all types (%d vs %d)", l, count)
375 }
376 if !found {
377 t.Fatal("Cannot find 'iphdr' type")
378 }
379 }
380
381 func TestLoadSplitSpecFromReader(t *testing.T) {
382 spec, err := LoadSpecFromReader(readVMLinux(t))
383 if err != nil {
384 t.Fatal(err)
385 }
386
387 f, err := os.Open("testdata/btf_testmod.btf")
388 if err != nil {
389 t.Fatal(err)
390 }
391 defer f.Close()
392
393 splitSpec, err := LoadSplitSpecFromReader(f, spec)
394 if err != nil {
395 t.Fatal(err)
396 }
397
398 typ, err := splitSpec.AnyTypeByName("bpf_testmod_init")
399 if err != nil {
400 t.Fatal(err)
401 }
402 typeID, err := splitSpec.TypeID(typ)
403 if err != nil {
404 t.Fatal(err)
405 }
406 fnType := typ.(*Func)
407 fnProto := fnType.Type.(*FuncProto)
408
409
410 intType, err := spec.AnyTypeByName("int")
411 if err != nil {
412 t.Fatal(err)
413 }
414
415 _, err = splitSpec.AnyTypeByName("int")
416 if err == nil {
417 t.Fatal("'int' is not supposed to be found in the split BTF")
418 }
419
420 if fnProto.Return != intType {
421 t.Fatalf("Return type of 'bpf_testmod_init()' (%s) does not match 'int' type (%s)",
422 fnProto.Return, intType)
423 }
424
425
426 splitSpecCopy := splitSpec.Copy()
427 copyType, err := splitSpecCopy.AnyTypeByName("bpf_testmod_init")
428 if err != nil {
429 t.Fatal(err)
430 }
431 copyTypeID, err := splitSpecCopy.TypeID(copyType)
432 if err != nil {
433 t.Fatal(err)
434 }
435 if copyTypeID != typeID {
436 t.Fatalf("'bpf_testmod_init` type ID (%d) does not match copied spec's (%d)",
437 typeID, copyTypeID)
438 }
439
440 }
441
View as plain text