...

Source file src/github.com/cilium/ebpf/btf/btf_test.go

Documentation: github.com/cilium/ebpf/btf

     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  			// spec.TypeByName MUST fail if typ is a nil btf.Type.
   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  	// spec.TypeByName MUST return the same address for multiple calls with the same type name.
   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  	// Excerpt from linux/ip.h, https://elixir.bootlin.com/linux/latest/A/ident/iphdr
   134  	//
   135  	// struct iphdr {
   136  	// #if defined(__LITTLE_ENDIAN_BITFIELD)
   137  	//     __u8 ihl:4, version:4;
   138  	// #elif defined (__BIG_ENDIAN_BITFIELD)
   139  	//     __u8 version:4, ihl:4;
   140  	// #else
   141  	//     ...
   142  	// }
   143  	//
   144  	// The BTF we test against is for little endian.
   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  			// Since Void is an empty struct, a Type interface value containing
   311  			// &Void{} stores (*Void, nil). Since interface equality first compares
   312  			// the type and then the concrete value, Void is always equal.
   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  	// Acquire a Spec via one of its constructors.
   332  	spec := new(Spec)
   333  
   334  	// Declare a variable of the desired type
   335  	var foo *Struct
   336  
   337  	if err := spec.TypeByName("foo", &foo); err != nil {
   338  		// There is no struct with name foo, or there
   339  		// are multiple possibilities.
   340  	}
   341  
   342  	// We've found struct foo
   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  	// Assertion that 'iphdr' type exists within the spec
   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  	// 'int' is defined in the base BTF...
   410  	intType, err := spec.AnyTypeByName("int")
   411  	if err != nil {
   412  		t.Fatal(err)
   413  	}
   414  	// ... but not in the split BTF
   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  	// Check that copied split-BTF's spec has correct type indexing
   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