github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/btf/btf_test.go (about)

     1  package btf
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/fs"
    10  	"os"
    11  	"runtime"
    12  	"sync"
    13  	"sync/atomic"
    14  	"testing"
    15  
    16  	"github.com/go-quicktest/qt"
    17  
    18  	"github.com/cilium/ebpf/internal"
    19  	"github.com/cilium/ebpf/internal/testutils"
    20  )
    21  
    22  func vmlinuxSpec(tb testing.TB) *Spec {
    23  	tb.Helper()
    24  
    25  	// /sys/kernel/btf was introduced in 341dfcf8d78e ("btf: expose BTF info
    26  	// through sysfs"), which shipped in Linux 5.4.
    27  	if _, err := os.Stat("/sys/kernel/btf/vmlinux"); errors.Is(err, fs.ErrNotExist) {
    28  		tb.Skip("No /sys/kernel/btf/vmlinux")
    29  	}
    30  
    31  	spec, err := LoadKernelSpec()
    32  	if err != nil {
    33  		tb.Fatal(err)
    34  	}
    35  	return spec
    36  }
    37  
    38  type specAndRawBTF struct {
    39  	raw  []byte
    40  	spec *Spec
    41  }
    42  
    43  var vmlinuxTestdata = sync.OnceValues(func() (specAndRawBTF, error) {
    44  	b, err := internal.ReadAllCompressed("testdata/vmlinux.btf.gz")
    45  	if err != nil {
    46  		return specAndRawBTF{}, err
    47  	}
    48  
    49  	spec, err := loadRawSpec(bytes.NewReader(b), binary.LittleEndian, nil)
    50  	if err != nil {
    51  		return specAndRawBTF{}, err
    52  	}
    53  
    54  	return specAndRawBTF{b, spec}, nil
    55  })
    56  
    57  func vmlinuxTestdataReader(tb testing.TB) *bytes.Reader {
    58  	tb.Helper()
    59  
    60  	td, err := vmlinuxTestdata()
    61  	if err != nil {
    62  		tb.Fatal(err)
    63  	}
    64  
    65  	return bytes.NewReader(td.raw)
    66  }
    67  
    68  func vmlinuxTestdataSpec(tb testing.TB) *Spec {
    69  	tb.Helper()
    70  
    71  	td, err := vmlinuxTestdata()
    72  	if err != nil {
    73  		tb.Fatal(err)
    74  	}
    75  
    76  	return td.spec.Copy()
    77  }
    78  
    79  func parseELFBTF(tb testing.TB, file string) *Spec {
    80  	tb.Helper()
    81  
    82  	spec, err := LoadSpec(file)
    83  	if err != nil {
    84  		tb.Fatal("Can't load BTF:", err)
    85  	}
    86  
    87  	return spec
    88  }
    89  
    90  func TestAnyTypesByName(t *testing.T) {
    91  	testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
    92  		spec := parseELFBTF(t, file)
    93  
    94  		types, err := spec.AnyTypesByName("ambiguous")
    95  		if err != nil {
    96  			t.Fatal(err)
    97  		}
    98  
    99  		if len(types) != 1 {
   100  			t.Fatalf("expected to receive exactly 1 types from querying ambiguous type, got: %v", types)
   101  		}
   102  
   103  		types, err = spec.AnyTypesByName("ambiguous___flavour")
   104  		if err != nil {
   105  			t.Fatal(err)
   106  		}
   107  
   108  		if len(types) != 1 {
   109  			t.Fatalf("expected to receive exactly 1 type from querying ambiguous flavour, got: %v", types)
   110  		}
   111  	})
   112  }
   113  
   114  func TestTypeByNameAmbiguous(t *testing.T) {
   115  	testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
   116  		spec := parseELFBTF(t, file)
   117  
   118  		var typ *Struct
   119  		if err := spec.TypeByName("ambiguous", &typ); err != nil {
   120  			t.Fatal(err)
   121  		}
   122  
   123  		if name := typ.TypeName(); name != "ambiguous" {
   124  			t.Fatal("expected type name 'ambiguous', got:", name)
   125  		}
   126  
   127  		if err := spec.TypeByName("ambiguous___flavour", &typ); err != nil {
   128  			t.Fatal(err)
   129  		}
   130  
   131  		if name := typ.TypeName(); name != "ambiguous___flavour" {
   132  			t.Fatal("expected type name 'ambiguous___flavour', got:", name)
   133  		}
   134  	})
   135  }
   136  
   137  func TestTypeByName(t *testing.T) {
   138  	spec := vmlinuxTestdataSpec(t)
   139  
   140  	for _, typ := range []interface{}{
   141  		nil,
   142  		Struct{},
   143  		&Struct{},
   144  		[]Struct{},
   145  		&[]Struct{},
   146  		map[int]Struct{},
   147  		&map[int]Struct{},
   148  		int(0),
   149  		new(int),
   150  	} {
   151  		t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) {
   152  			// spec.TypeByName MUST fail if typ is a nil btf.Type.
   153  			if err := spec.TypeByName("iphdr", typ); err == nil {
   154  				t.Fatalf("TypeByName does not fail with type %T", typ)
   155  			}
   156  		})
   157  	}
   158  
   159  	// spec.TypeByName MUST return the same address for multiple calls with the same type name.
   160  	var iphdr1, iphdr2 *Struct
   161  	if err := spec.TypeByName("iphdr", &iphdr1); err != nil {
   162  		t.Fatal(err)
   163  	}
   164  	if err := spec.TypeByName("iphdr", &iphdr2); err != nil {
   165  		t.Fatal(err)
   166  	}
   167  
   168  	if iphdr1 != iphdr2 {
   169  		t.Fatal("multiple TypeByName calls for `iphdr` name do not return the same addresses")
   170  	}
   171  
   172  	// It's valid to pass a *Type to TypeByName.
   173  	typ := Type(iphdr2)
   174  	if err := spec.TypeByName("iphdr", &typ); err != nil {
   175  		t.Fatal("Can't look up using *Type:", err)
   176  	}
   177  
   178  	// Excerpt from linux/ip.h, https://elixir.bootlin.com/linux/latest/A/ident/iphdr
   179  	//
   180  	// struct iphdr {
   181  	// #if defined(__LITTLE_ENDIAN_BITFIELD)
   182  	//     __u8 ihl:4, version:4;
   183  	// #elif defined (__BIG_ENDIAN_BITFIELD)
   184  	//     __u8 version:4, ihl:4;
   185  	// #else
   186  	//     ...
   187  	// }
   188  	//
   189  	// The BTF we test against is for little endian.
   190  	m := iphdr1.Members[1]
   191  	if m.Name != "version" {
   192  		t.Fatal("Expected version as the second member, got", m.Name)
   193  	}
   194  	td, ok := m.Type.(*Typedef)
   195  	if !ok {
   196  		t.Fatalf("version member of iphdr should be a __u8 typedef: actual: %T", m.Type)
   197  	}
   198  	u8, ok := td.Type.(*Int)
   199  	if !ok {
   200  		t.Fatalf("__u8 typedef should point to an Int type: actual: %T", td.Type)
   201  	}
   202  	if m.BitfieldSize != 4 {
   203  		t.Fatalf("incorrect bitfield size: expected: 4 actual: %d", m.BitfieldSize)
   204  	}
   205  	if u8.Encoding != 0 {
   206  		t.Fatalf("incorrect encoding of an __u8 int: expected: 0 actual: %x", u8.Encoding)
   207  	}
   208  	if m.Offset != 4 {
   209  		t.Fatalf("incorrect bitfield offset: expected: 4 actual: %d", m.Offset)
   210  	}
   211  }
   212  
   213  func BenchmarkParseVmlinux(b *testing.B) {
   214  	rd := vmlinuxTestdataReader(b)
   215  	b.ReportAllocs()
   216  	b.ResetTimer()
   217  
   218  	for n := 0; n < b.N; n++ {
   219  		if _, err := rd.Seek(0, io.SeekStart); err != nil {
   220  			b.Fatal(err)
   221  		}
   222  
   223  		if _, err := loadRawSpec(rd, binary.LittleEndian, nil); err != nil {
   224  			b.Fatal("Can't load BTF:", err)
   225  		}
   226  	}
   227  }
   228  
   229  func TestParseCurrentKernelBTF(t *testing.T) {
   230  	spec := vmlinuxSpec(t)
   231  
   232  	if len(spec.imm.namedTypes) == 0 {
   233  		t.Fatal("Empty kernel BTF")
   234  	}
   235  
   236  	totalBytes := 0
   237  	distinct := 0
   238  	seen := make(map[string]bool)
   239  	for _, str := range spec.strings.strings {
   240  		totalBytes += len(str)
   241  		if !seen[str] {
   242  			distinct++
   243  			seen[str] = true
   244  		}
   245  	}
   246  	t.Logf("%d strings total, %d distinct", len(spec.strings.strings), distinct)
   247  	t.Logf("Average string size: %.0f", float64(totalBytes)/float64(len(spec.strings.strings)))
   248  }
   249  
   250  func TestFindVMLinux(t *testing.T) {
   251  	file, err := findVMLinux()
   252  	testutils.SkipIfNotSupported(t, err)
   253  	if err != nil {
   254  		t.Fatal("Can't find vmlinux:", err)
   255  	}
   256  	defer file.Close()
   257  
   258  	spec, err := LoadSpecFromReader(file)
   259  	if err != nil {
   260  		t.Fatal("Can't load BTF:", err)
   261  	}
   262  
   263  	if len(spec.imm.namedTypes) == 0 {
   264  		t.Fatal("Empty kernel BTF")
   265  	}
   266  }
   267  
   268  func TestLoadSpecFromElf(t *testing.T) {
   269  	testutils.Files(t, testutils.Glob(t, "../testdata/loader-e*.elf"), func(t *testing.T, file string) {
   270  		spec := parseELFBTF(t, file)
   271  
   272  		vt, err := spec.TypeByID(0)
   273  		if err != nil {
   274  			t.Error("Can't retrieve void type by ID:", err)
   275  		}
   276  		if _, ok := vt.(*Void); !ok {
   277  			t.Errorf("Expected Void for type id 0, but got: %T", vt)
   278  		}
   279  
   280  		var bpfMapDef *Struct
   281  		if err := spec.TypeByName("bpf_map_def", &bpfMapDef); err != nil {
   282  			t.Error("Can't find bpf_map_def:", err)
   283  		}
   284  
   285  		var tmp *Void
   286  		if err := spec.TypeByName("totally_bogus_type", &tmp); !errors.Is(err, ErrNotFound) {
   287  			t.Error("TypeByName doesn't return ErrNotFound:", err)
   288  		}
   289  
   290  		var fn *Func
   291  		if err := spec.TypeByName("global_fn", &fn); err != nil {
   292  			t.Error("Can't find global_fn():", err)
   293  		} else {
   294  			if fn.Linkage != GlobalFunc {
   295  				t.Error("Expected global linkage:", fn)
   296  			}
   297  		}
   298  
   299  		var v *Var
   300  		if err := spec.TypeByName("key3", &v); err != nil {
   301  			t.Error("Can't find key3:", err)
   302  		} else {
   303  			if v.Linkage != GlobalVar {
   304  				t.Error("Expected global linkage:", v)
   305  			}
   306  		}
   307  	})
   308  }
   309  
   310  func TestVerifierError(t *testing.T) {
   311  	b, err := NewBuilder([]Type{&Int{Encoding: 255}})
   312  	qt.Assert(t, qt.IsNil(err))
   313  	_, err = NewHandle(b)
   314  	testutils.SkipIfNotSupported(t, err)
   315  	var ve *internal.VerifierError
   316  	if !errors.As(err, &ve) {
   317  		t.Fatalf("expected a VerifierError, got: %v", err)
   318  	}
   319  
   320  	if ve.Truncated {
   321  		t.Fatalf("expected non-truncated verifier log: %v", err)
   322  	}
   323  }
   324  
   325  func TestGuessBTFByteOrder(t *testing.T) {
   326  	bo := guessRawBTFByteOrder(vmlinuxTestdataReader(t))
   327  	if bo != binary.LittleEndian {
   328  		t.Fatalf("Guessed %s instead of %s", bo, binary.LittleEndian)
   329  	}
   330  }
   331  
   332  func TestSpecCopy(t *testing.T) {
   333  	spec := parseELFBTF(t, "../testdata/loader-el.elf")
   334  	cpy := spec.Copy()
   335  
   336  	have := typesFromSpec(spec)
   337  	qt.Assert(t, qt.IsTrue(len(have) > 0))
   338  
   339  	want := typesFromSpec(cpy)
   340  	qt.Assert(t, qt.HasLen(want, len(have)))
   341  
   342  	for i := range want {
   343  		if _, ok := have[i].(*Void); ok {
   344  			// Since Void is an empty struct, a Type interface value containing
   345  			// &Void{} stores (*Void, nil). Since interface equality first compares
   346  			// the type and then the concrete value, Void is always equal.
   347  			continue
   348  		}
   349  
   350  		if have[i] == want[i] {
   351  			t.Fatalf("Type at index %d is not a copy: %T == %T", i, have[i], want[i])
   352  		}
   353  	}
   354  }
   355  
   356  func TestSpecCopyModifications(t *testing.T) {
   357  	spec := specFromTypes(t, []Type{&Int{Name: "a", Size: 4}})
   358  
   359  	typ, err := spec.TypeByID(1)
   360  	qt.Assert(t, qt.IsNil(err))
   361  
   362  	i := typ.(*Int)
   363  	i.Name = "b"
   364  	i.Size = 2
   365  
   366  	cpy := spec.Copy()
   367  	typ2, err := cpy.TypeByID(1)
   368  	qt.Assert(t, qt.IsNil(err))
   369  	i2 := typ2.(*Int)
   370  
   371  	qt.Assert(t, qt.Not(qt.Equals(i2, i)), qt.Commentf("Types are distinct"))
   372  	qt.Assert(t, qt.DeepEquals(i2, i), qt.Commentf("Modifications are preserved"))
   373  
   374  	i.Name = "bar"
   375  	qt.Assert(t, qt.Equals(i2.Name, "b"))
   376  }
   377  
   378  func TestSpecTypeByID(t *testing.T) {
   379  	spec := specFromTypes(t, nil)
   380  
   381  	_, err := spec.TypeByID(0)
   382  	qt.Assert(t, qt.IsNil(err))
   383  
   384  	_, err = spec.TypeByID(1)
   385  	qt.Assert(t, qt.ErrorIs(err, ErrNotFound))
   386  }
   387  
   388  func ExampleSpec_TypeByName() {
   389  	// Acquire a Spec via one of its constructors.
   390  	spec := new(Spec)
   391  
   392  	// Declare a variable of the desired type
   393  	var foo *Struct
   394  
   395  	if err := spec.TypeByName("foo", &foo); err != nil {
   396  		// There is no struct with name foo, or there
   397  		// are multiple possibilities.
   398  	}
   399  
   400  	// We've found struct foo
   401  	fmt.Println(foo.Name)
   402  }
   403  
   404  func TestTypesIterator(t *testing.T) {
   405  	types := []Type{(*Void)(nil), &Int{Size: 4}, &Int{Size: 2}}
   406  
   407  	b, err := NewBuilder(types[1:])
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  
   412  	raw, err := b.Marshal(nil, nil)
   413  	if err != nil {
   414  		t.Fatal(err)
   415  	}
   416  
   417  	spec, err := LoadSpecFromReader(bytes.NewReader(raw))
   418  	if err != nil {
   419  		t.Fatal(err)
   420  	}
   421  
   422  	iter := spec.Iterate()
   423  
   424  	for i, typ := range types {
   425  		if !iter.Next() {
   426  			t.Fatal("Iterator ended early at item", i)
   427  		}
   428  
   429  		qt.Assert(t, qt.DeepEquals(iter.Type, typ))
   430  	}
   431  
   432  	if iter.Next() {
   433  		t.Fatalf("Iterator yielded too many items: %p (%[1]T)", iter.Type)
   434  	}
   435  }
   436  
   437  func TestLoadSplitSpecFromReader(t *testing.T) {
   438  	spec := vmlinuxTestdataSpec(t)
   439  
   440  	f, err := os.Open("testdata/btf_testmod.btf")
   441  	if err != nil {
   442  		t.Fatal(err)
   443  	}
   444  	defer f.Close()
   445  
   446  	splitSpec, err := LoadSplitSpecFromReader(f, spec)
   447  	if err != nil {
   448  		t.Fatal(err)
   449  	}
   450  
   451  	typ, err := splitSpec.AnyTypeByName("bpf_testmod_init")
   452  	if err != nil {
   453  		t.Fatal(err)
   454  	}
   455  	typeID, err := splitSpec.TypeID(typ)
   456  	if err != nil {
   457  		t.Fatal(err)
   458  	}
   459  
   460  	typeByID, err := splitSpec.TypeByID(typeID)
   461  	qt.Assert(t, qt.IsNil(err))
   462  	qt.Assert(t, qt.Equals(typeByID, typ))
   463  
   464  	fnType := typ.(*Func)
   465  	fnProto := fnType.Type.(*FuncProto)
   466  
   467  	// 'int' is defined in the base BTF...
   468  	intType, err := spec.AnyTypeByName("int")
   469  	if err != nil {
   470  		t.Fatal(err)
   471  	}
   472  	// ... but not in the split BTF
   473  	_, err = splitSpec.AnyTypeByName("int")
   474  	if err == nil {
   475  		t.Fatal("'int' is not supposed to be found in the split BTF")
   476  	}
   477  
   478  	qt.Assert(t, qt.Not(qt.Equals(fnProto.Return, intType)),
   479  		qt.Commentf("types found in base of split spec should be copies"))
   480  
   481  	// Check that copied split-BTF's spec has correct type indexing
   482  	splitSpecCopy := splitSpec.Copy()
   483  	copyType, err := splitSpecCopy.AnyTypeByName("bpf_testmod_init")
   484  	if err != nil {
   485  		t.Fatal(err)
   486  	}
   487  	copyTypeID, err := splitSpecCopy.TypeID(copyType)
   488  	if err != nil {
   489  		t.Fatal(err)
   490  	}
   491  	if copyTypeID != typeID {
   492  		t.Fatalf("'bpf_testmod_init` type ID (%d) does not match copied spec's (%d)",
   493  			typeID, copyTypeID)
   494  	}
   495  }
   496  
   497  func TestFixupDatasecLayout(t *testing.T) {
   498  	ds := &Datasec{
   499  		Size: 0, // Populated by fixup.
   500  		Vars: []VarSecinfo{
   501  			{Type: &Var{Type: &Int{Size: 4}}},
   502  			{Type: &Var{Type: &Int{Size: 1}}},
   503  			{Type: &Var{Type: &Int{Size: 1}}},
   504  			{Type: &Var{Type: &Int{Size: 2}}},
   505  			{Type: &Var{Type: &Int{Size: 16}}},
   506  			{Type: &Var{Type: &Int{Size: 8}}},
   507  		},
   508  	}
   509  
   510  	qt.Assert(t, qt.IsNil(fixupDatasecLayout(ds)))
   511  
   512  	qt.Assert(t, qt.Equals(ds.Size, 40))
   513  	qt.Assert(t, qt.Equals(ds.Vars[0].Offset, 0))
   514  	qt.Assert(t, qt.Equals(ds.Vars[1].Offset, 4))
   515  	qt.Assert(t, qt.Equals(ds.Vars[2].Offset, 5))
   516  	qt.Assert(t, qt.Equals(ds.Vars[3].Offset, 6))
   517  	qt.Assert(t, qt.Equals(ds.Vars[4].Offset, 16))
   518  	qt.Assert(t, qt.Equals(ds.Vars[5].Offset, 32))
   519  }
   520  
   521  func TestSpecConcurrentAccess(t *testing.T) {
   522  	spec := vmlinuxTestdataSpec(t)
   523  
   524  	maxprocs := runtime.GOMAXPROCS(0)
   525  	if maxprocs < 2 {
   526  		t.Error("GOMAXPROCS is lower than 2:", maxprocs)
   527  	}
   528  
   529  	var cond atomic.Int64
   530  	var wg sync.WaitGroup
   531  	for i := 0; i < maxprocs; i++ {
   532  		wg.Add(1)
   533  		go func() {
   534  			defer wg.Done()
   535  
   536  			n := cond.Add(1)
   537  			for cond.Load() != int64(maxprocs) {
   538  				// Spin to increase the chances of a race.
   539  			}
   540  
   541  			if n%2 == 0 {
   542  				_, _ = spec.AnyTypeByName("gov_update_cpu_data")
   543  			} else {
   544  				_ = spec.Copy()
   545  			}
   546  		}()
   547  
   548  		// Try to get the Goroutines scheduled and spinning.
   549  		runtime.Gosched()
   550  	}
   551  	wg.Wait()
   552  }
   553  
   554  func BenchmarkSpecCopy(b *testing.B) {
   555  	spec := vmlinuxTestdataSpec(b)
   556  	b.ResetTimer()
   557  
   558  	for i := 0; i < b.N; i++ {
   559  		spec.Copy()
   560  	}
   561  }
   562  
   563  func BenchmarkSpecTypeByID(b *testing.B) {
   564  	spec := vmlinuxTestdataSpec(b)
   565  
   566  	b.ReportAllocs()
   567  	b.ResetTimer()
   568  	for i := 0; i < b.N; i++ {
   569  		_, err := spec.TypeByID(1)
   570  		if err != nil {
   571  			b.Fatal(err)
   572  		}
   573  	}
   574  }