github.com/cilium/ebpf@v0.10.0/btf/btf_test.go (about)

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