github.com/cilium/ebpf@v0.16.0/collection_test.go (about)

     1  package ebpf
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"reflect"
    10  	"testing"
    11  
    12  	"github.com/go-quicktest/qt"
    13  
    14  	"github.com/cilium/ebpf/asm"
    15  	"github.com/cilium/ebpf/btf"
    16  	"github.com/cilium/ebpf/internal"
    17  	"github.com/cilium/ebpf/internal/testutils"
    18  	"github.com/cilium/ebpf/internal/testutils/fdtrace"
    19  )
    20  
    21  func TestMain(m *testing.M) {
    22  	fdtrace.TestMain(m)
    23  }
    24  
    25  func TestCollectionSpecNotModified(t *testing.T) {
    26  	cs := CollectionSpec{
    27  		Maps: map[string]*MapSpec{
    28  			"my-map": {
    29  				Type:       Array,
    30  				KeySize:    4,
    31  				ValueSize:  4,
    32  				MaxEntries: 1,
    33  			},
    34  		},
    35  		Programs: map[string]*ProgramSpec{
    36  			"test": {
    37  				Type: SocketFilter,
    38  				Instructions: asm.Instructions{
    39  					asm.LoadImm(asm.R1, 0, asm.DWord).WithReference("my-map"),
    40  					asm.LoadImm(asm.R0, 0, asm.DWord),
    41  					asm.Return(),
    42  				},
    43  				License: "MIT",
    44  			},
    45  		},
    46  	}
    47  
    48  	coll, err := NewCollection(&cs)
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	coll.Close()
    53  
    54  	if cs.Programs["test"].Instructions[0].Constant != 0 {
    55  		t.Error("Creating a collection modifies input spec")
    56  	}
    57  }
    58  
    59  func TestCollectionSpecCopy(t *testing.T) {
    60  	cs := &CollectionSpec{
    61  		map[string]*MapSpec{
    62  			"my-map": {
    63  				Type:       Array,
    64  				KeySize:    4,
    65  				ValueSize:  4,
    66  				MaxEntries: 1,
    67  			},
    68  		},
    69  		map[string]*ProgramSpec{
    70  			"test": {
    71  				Type: SocketFilter,
    72  				Instructions: asm.Instructions{
    73  					asm.LoadMapPtr(asm.R1, 0),
    74  					asm.LoadImm(asm.R0, 0, asm.DWord),
    75  					asm.Return(),
    76  				},
    77  				License: "MIT",
    78  			},
    79  		},
    80  		&btf.Spec{},
    81  		binary.LittleEndian,
    82  	}
    83  
    84  	qt.Check(t, qt.IsNil((*CollectionSpec)(nil).Copy()))
    85  	qt.Assert(t, testutils.IsDeepCopy(cs.Copy(), cs))
    86  }
    87  
    88  func TestCollectionSpecLoadCopy(t *testing.T) {
    89  	file := testutils.NativeFile(t, "testdata/loader-%s.elf")
    90  	spec, err := LoadCollectionSpec(file)
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  
    95  	spec2 := spec.Copy()
    96  
    97  	var objs struct {
    98  		Prog *Program `ebpf:"xdp_prog"`
    99  	}
   100  
   101  	err = spec.LoadAndAssign(&objs, nil)
   102  	testutils.SkipIfNotSupported(t, err)
   103  	if err != nil {
   104  		t.Fatal("Loading original spec:", err)
   105  	}
   106  	defer objs.Prog.Close()
   107  
   108  	if err := spec2.LoadAndAssign(&objs, nil); err != nil {
   109  		t.Fatal("Loading copied spec:", err)
   110  	}
   111  	defer objs.Prog.Close()
   112  }
   113  
   114  func TestCollectionSpecRewriteMaps(t *testing.T) {
   115  	insns := asm.Instructions{
   116  		// R1 map
   117  		asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"),
   118  		// R2 key
   119  		asm.Mov.Reg(asm.R2, asm.R10),
   120  		asm.Add.Imm(asm.R2, -4),
   121  		asm.StoreImm(asm.R2, 0, 0, asm.Word),
   122  		// Lookup map[0]
   123  		asm.FnMapLookupElem.Call(),
   124  		asm.JEq.Imm(asm.R0, 0, "ret"),
   125  		asm.LoadMem(asm.R0, asm.R0, 0, asm.Word),
   126  		asm.Return().WithSymbol("ret"),
   127  	}
   128  
   129  	cs := &CollectionSpec{
   130  		Maps: map[string]*MapSpec{
   131  			"test-map": {
   132  				Type:       Array,
   133  				KeySize:    4,
   134  				ValueSize:  4,
   135  				MaxEntries: 1,
   136  			},
   137  		},
   138  		Programs: map[string]*ProgramSpec{
   139  			"test-prog": {
   140  				Type:         SocketFilter,
   141  				Instructions: insns,
   142  				License:      "MIT",
   143  			},
   144  		},
   145  	}
   146  
   147  	// Override the map with another one
   148  	newMap, err := NewMap(cs.Maps["test-map"])
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  	defer newMap.Close()
   153  
   154  	err = newMap.Put(uint32(0), uint32(2))
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  
   159  	err = cs.RewriteMaps(map[string]*Map{
   160  		"test-map": newMap,
   161  	})
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  
   166  	if cs.Maps["test-map"] != nil {
   167  		t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps")
   168  	}
   169  
   170  	coll, err := NewCollection(cs)
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	}
   174  	defer coll.Close()
   175  
   176  	ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext)
   177  	testutils.SkipIfNotSupported(t, err)
   178  	if err != nil {
   179  		t.Fatal(err)
   180  	}
   181  
   182  	if ret != 2 {
   183  		t.Fatal("new / override map not used")
   184  	}
   185  }
   186  
   187  func TestCollectionSpecMapReplacements(t *testing.T) {
   188  	insns := asm.Instructions{
   189  		// R1 map
   190  		asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"),
   191  		// R2 key
   192  		asm.Mov.Reg(asm.R2, asm.R10),
   193  		asm.Add.Imm(asm.R2, -4),
   194  		asm.StoreImm(asm.R2, 0, 0, asm.Word),
   195  		// Lookup map[0]
   196  		asm.FnMapLookupElem.Call(),
   197  		asm.JEq.Imm(asm.R0, 0, "ret"),
   198  		asm.LoadMem(asm.R0, asm.R0, 0, asm.Word),
   199  		asm.Return().WithSymbol("ret"),
   200  	}
   201  
   202  	cs := &CollectionSpec{
   203  		Maps: map[string]*MapSpec{
   204  			"test-map": {
   205  				Type:       Array,
   206  				KeySize:    4,
   207  				ValueSize:  4,
   208  				MaxEntries: 1,
   209  			},
   210  		},
   211  		Programs: map[string]*ProgramSpec{
   212  			"test-prog": {
   213  				Type:         SocketFilter,
   214  				Instructions: insns,
   215  				License:      "MIT",
   216  			},
   217  		},
   218  	}
   219  
   220  	// Replace the map with another one
   221  	newMap, err := NewMap(cs.Maps["test-map"])
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	defer newMap.Close()
   226  
   227  	err = newMap.Put(uint32(0), uint32(2))
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  
   232  	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
   233  		MapReplacements: map[string]*Map{
   234  			"test-map": newMap,
   235  		},
   236  	})
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	defer coll.Close()
   241  
   242  	ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext)
   243  	testutils.SkipIfNotSupported(t, err)
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  
   248  	if ret != 2 {
   249  		t.Fatal("new / override map not used")
   250  	}
   251  
   252  	// Check that newMap isn't closed when the collection is closed
   253  	coll.Close()
   254  	err = newMap.Put(uint32(0), uint32(3))
   255  	if err != nil {
   256  		t.Fatalf("failed to update replaced map: %s", err)
   257  	}
   258  }
   259  func TestCollectionSpecMapReplacements_NonExistingMap(t *testing.T) {
   260  	cs := &CollectionSpec{
   261  		Maps: map[string]*MapSpec{
   262  			"test-map": {
   263  				Type:       Array,
   264  				KeySize:    4,
   265  				ValueSize:  4,
   266  				MaxEntries: 1,
   267  			},
   268  		},
   269  	}
   270  
   271  	// Override non-existing map
   272  	newMap, err := NewMap(cs.Maps["test-map"])
   273  	if err != nil {
   274  		t.Fatal(err)
   275  	}
   276  	defer newMap.Close()
   277  
   278  	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
   279  		MapReplacements: map[string]*Map{
   280  			"non-existing-map": newMap,
   281  		},
   282  	})
   283  	if err == nil {
   284  		coll.Close()
   285  		t.Fatal("Overriding a non existing map did not fail")
   286  	}
   287  }
   288  
   289  func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) {
   290  	cs := &CollectionSpec{
   291  		Maps: map[string]*MapSpec{
   292  			"test-map": {
   293  				Type:       Array,
   294  				KeySize:    4,
   295  				ValueSize:  4,
   296  				MaxEntries: 1,
   297  			},
   298  		},
   299  	}
   300  
   301  	// Override map with mismatching spec
   302  	newMap, err := NewMap(&MapSpec{
   303  		Type:       Array,
   304  		KeySize:    4,
   305  		ValueSize:  8, // this is different
   306  		MaxEntries: 1,
   307  	})
   308  	if err != nil {
   309  		t.Fatal(err)
   310  	}
   311  	// Map fd is duplicated by MapReplacements, this one can be safely closed.
   312  	defer newMap.Close()
   313  
   314  	coll, err := NewCollectionWithOptions(cs, CollectionOptions{
   315  		MapReplacements: map[string]*Map{
   316  			"test-map": newMap,
   317  		},
   318  	})
   319  	if err == nil {
   320  		coll.Close()
   321  		t.Fatal("Overriding a map with a mismatching spec did not fail")
   322  	}
   323  	if !errors.Is(err, ErrMapIncompatible) {
   324  		t.Fatalf("Overriding a map with a mismatching spec failed with the wrong error")
   325  	}
   326  }
   327  
   328  func TestCollectionRewriteConstants(t *testing.T) {
   329  	cs := &CollectionSpec{
   330  		Maps: map[string]*MapSpec{
   331  			".rodata": {
   332  				Type:       Array,
   333  				KeySize:    4,
   334  				ValueSize:  4,
   335  				MaxEntries: 1,
   336  				Value: &btf.Datasec{
   337  					Vars: []btf.VarSecinfo{
   338  						{
   339  							Type: &btf.Var{
   340  								Name: "the_constant",
   341  								Type: &btf.Int{Size: 4},
   342  							},
   343  							Offset: 0,
   344  							Size:   4,
   345  						},
   346  					},
   347  				},
   348  				Contents: []MapKV{
   349  					{Key: uint32(0), Value: []byte{1, 1, 1, 1}},
   350  				},
   351  			},
   352  		},
   353  	}
   354  
   355  	err := cs.RewriteConstants(map[string]interface{}{
   356  		"fake_constant_one": uint32(1),
   357  		"fake_constant_two": uint32(2),
   358  	})
   359  	qt.Assert(t, qt.IsNotNil(err), qt.Commentf("RewriteConstants did not fail"))
   360  
   361  	var mErr *MissingConstantsError
   362  	if !errors.As(err, &mErr) {
   363  		t.Fatal("Error doesn't wrap MissingConstantsError:", err)
   364  	}
   365  	qt.Assert(t, qt.ContentEquals(mErr.Constants, []string{"fake_constant_one", "fake_constant_two"}))
   366  
   367  	err = cs.RewriteConstants(map[string]interface{}{
   368  		"the_constant": uint32(0x42424242),
   369  	})
   370  	qt.Assert(t, qt.IsNil(err))
   371  	qt.Assert(t, qt.ContentEquals(cs.Maps[".rodata"].Contents[0].Value.([]byte), []byte{0x42, 0x42, 0x42, 0x42}))
   372  }
   373  
   374  func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {
   375  	spec := &CollectionSpec{
   376  		Maps: map[string]*MapSpec{
   377  			"valid": {
   378  				Type:       Array,
   379  				KeySize:    4,
   380  				ValueSize:  4,
   381  				MaxEntries: 1,
   382  			},
   383  			"bogus": {
   384  				Type:       Array,
   385  				MaxEntries: 0,
   386  			},
   387  		},
   388  		Programs: map[string]*ProgramSpec{
   389  			"valid": {
   390  				Type: SocketFilter,
   391  				Instructions: asm.Instructions{
   392  					asm.LoadImm(asm.R0, 0, asm.DWord),
   393  					asm.Return(),
   394  				},
   395  				License: "MIT",
   396  			},
   397  			"bogus": {
   398  				Type: SocketFilter,
   399  				Instructions: asm.Instructions{
   400  					// Undefined return value is rejected
   401  					asm.Return(),
   402  				},
   403  				License: "MIT",
   404  			},
   405  		},
   406  	}
   407  
   408  	var objs struct {
   409  		Prog *Program `ebpf:"valid"`
   410  		Map  *Map     `ebpf:"valid"`
   411  	}
   412  
   413  	if err := spec.LoadAndAssign(&objs, nil); err != nil {
   414  		t.Fatal("Assign loads a map or program that isn't requested in the struct:", err)
   415  	}
   416  	defer objs.Prog.Close()
   417  	defer objs.Map.Close()
   418  
   419  	if objs.Prog == nil {
   420  		t.Error("Program is nil")
   421  	}
   422  
   423  	if objs.Map == nil {
   424  		t.Error("Map is nil")
   425  	}
   426  }
   427  
   428  func TestCollectionSpecAssign(t *testing.T) {
   429  	var specs struct {
   430  		Program *ProgramSpec `ebpf:"prog1"`
   431  		Map     *MapSpec     `ebpf:"map1"`
   432  	}
   433  
   434  	mapSpec := &MapSpec{
   435  		Type:       Array,
   436  		KeySize:    4,
   437  		ValueSize:  4,
   438  		MaxEntries: 1,
   439  	}
   440  	progSpec := &ProgramSpec{
   441  		Type: SocketFilter,
   442  		Instructions: asm.Instructions{
   443  			asm.LoadImm(asm.R0, 0, asm.DWord),
   444  			asm.Return(),
   445  		},
   446  		License: "MIT",
   447  	}
   448  
   449  	cs := &CollectionSpec{
   450  		Maps: map[string]*MapSpec{
   451  			"map1": mapSpec,
   452  		},
   453  		Programs: map[string]*ProgramSpec{
   454  			"prog1": progSpec,
   455  		},
   456  	}
   457  
   458  	if err := cs.Assign(&specs); err != nil {
   459  		t.Fatal("Can't assign spec:", err)
   460  	}
   461  
   462  	if specs.Program != progSpec {
   463  		t.Fatalf("Expected Program to be %p, got %p", progSpec, specs.Program)
   464  	}
   465  
   466  	if specs.Map != mapSpec {
   467  		t.Fatalf("Expected Map to be %p, got %p", mapSpec, specs.Map)
   468  	}
   469  
   470  	if err := cs.Assign(new(int)); err == nil {
   471  		t.Fatal("Assign allows to besides *struct")
   472  	}
   473  
   474  	if err := cs.Assign(new(struct{ Foo int })); err != nil {
   475  		t.Fatal("Assign doesn't ignore untagged fields")
   476  	}
   477  
   478  	unexported := new(struct {
   479  		foo *MapSpec `ebpf:"map1"`
   480  	})
   481  
   482  	if err := cs.Assign(unexported); err == nil {
   483  		t.Error("Assign should return an error on unexported fields")
   484  	}
   485  }
   486  
   487  func TestAssignValues(t *testing.T) {
   488  	zero := func(t reflect.Type, name string) (interface{}, error) {
   489  		return reflect.Zero(t).Interface(), nil
   490  	}
   491  
   492  	type t1 struct {
   493  		Bar int `ebpf:"bar"`
   494  	}
   495  
   496  	type t2 struct {
   497  		t1
   498  		Foo int `ebpf:"foo"`
   499  	}
   500  
   501  	type t2ptr struct {
   502  		*t1
   503  		Foo int `ebpf:"foo"`
   504  	}
   505  
   506  	invalid := []struct {
   507  		name string
   508  		to   interface{}
   509  	}{
   510  		{"non-struct", 1},
   511  		{"non-pointer struct", t1{}},
   512  		{"pointer to non-struct", new(int)},
   513  		{"embedded nil pointer", &t2ptr{}},
   514  		{"unexported field", new(struct {
   515  			foo int `ebpf:"foo"`
   516  		})},
   517  		{"identical tag", new(struct {
   518  			Foo1 int `ebpf:"foo"`
   519  			Foo2 int `ebpf:"foo"`
   520  		})},
   521  	}
   522  
   523  	for _, testcase := range invalid {
   524  		t.Run(testcase.name, func(t *testing.T) {
   525  			if err := assignValues(testcase.to, zero); err == nil {
   526  				t.Fatal("assignValues didn't return an error")
   527  			} else {
   528  				t.Log(err)
   529  			}
   530  		})
   531  	}
   532  
   533  	valid := []struct {
   534  		name string
   535  		to   interface{}
   536  	}{
   537  		{"pointer to struct", new(t1)},
   538  		{"embedded struct", new(t2)},
   539  		{"embedded struct pointer", &t2ptr{t1: new(t1)}},
   540  		{"untagged field", new(struct{ Foo int })},
   541  	}
   542  
   543  	for _, testcase := range valid {
   544  		t.Run(testcase.name, func(t *testing.T) {
   545  			if err := assignValues(testcase.to, zero); err != nil {
   546  				t.Fatal("assignValues returned", err)
   547  			}
   548  		})
   549  	}
   550  
   551  }
   552  
   553  func TestCollectionAssign(t *testing.T) {
   554  	var objs struct {
   555  		Program *Program `ebpf:"prog1"`
   556  		Map     *Map     `ebpf:"map1"`
   557  	}
   558  
   559  	cs := &CollectionSpec{
   560  		Maps: map[string]*MapSpec{
   561  			"map1": {
   562  				Type:       Array,
   563  				KeySize:    4,
   564  				ValueSize:  4,
   565  				MaxEntries: 1,
   566  			},
   567  		},
   568  		Programs: map[string]*ProgramSpec{
   569  			"prog1": {
   570  				Type: SocketFilter,
   571  				Instructions: asm.Instructions{
   572  					asm.LoadImm(asm.R0, 0, asm.DWord),
   573  					asm.Return(),
   574  				},
   575  				License: "MIT",
   576  			},
   577  		},
   578  	}
   579  
   580  	coll, err := NewCollection(cs)
   581  	qt.Assert(t, qt.IsNil(err))
   582  	defer coll.Close()
   583  
   584  	qt.Assert(t, qt.IsNil(coll.Assign(&objs)))
   585  	defer objs.Program.Close()
   586  	defer objs.Map.Close()
   587  
   588  	// Check that objs has received ownership of map and prog
   589  	qt.Assert(t, qt.IsTrue(objs.Program.FD() >= 0))
   590  	qt.Assert(t, qt.IsTrue(objs.Map.FD() >= 0))
   591  
   592  	// Check that the collection has lost ownership
   593  	qt.Assert(t, qt.IsNil(coll.Programs["prog1"]))
   594  	qt.Assert(t, qt.IsNil(coll.Maps["map1"]))
   595  }
   596  
   597  func TestCollectionAssignFail(t *testing.T) {
   598  	// `map2` does not exist
   599  	var objs struct {
   600  		Program *Program `ebpf:"prog1"`
   601  		Map     *Map     `ebpf:"map2"`
   602  	}
   603  
   604  	cs := &CollectionSpec{
   605  		Maps: map[string]*MapSpec{
   606  			"map1": {
   607  				Type:       Array,
   608  				KeySize:    4,
   609  				ValueSize:  4,
   610  				MaxEntries: 1,
   611  			},
   612  		},
   613  		Programs: map[string]*ProgramSpec{
   614  			"prog1": {
   615  				Type: SocketFilter,
   616  				Instructions: asm.Instructions{
   617  					asm.LoadImm(asm.R0, 0, asm.DWord),
   618  					asm.Return(),
   619  				},
   620  				License: "MIT",
   621  			},
   622  		},
   623  	}
   624  
   625  	coll, err := NewCollection(cs)
   626  	qt.Assert(t, qt.IsNil(err))
   627  	defer coll.Close()
   628  
   629  	qt.Assert(t, qt.IsNotNil(coll.Assign(&objs)))
   630  
   631  	// Check that the collection has retained ownership
   632  	qt.Assert(t, qt.IsNotNil(coll.Programs["prog1"]))
   633  	qt.Assert(t, qt.IsNotNil(coll.Maps["map1"]))
   634  }
   635  
   636  func TestIncompleteLoadAndAssign(t *testing.T) {
   637  	spec := &CollectionSpec{
   638  		Programs: map[string]*ProgramSpec{
   639  			"valid": {
   640  				Type: SocketFilter,
   641  				Instructions: asm.Instructions{
   642  					asm.LoadImm(asm.R0, 0, asm.DWord),
   643  					asm.Return(),
   644  				},
   645  				License: "MIT",
   646  			},
   647  			"invalid": {
   648  				Type: SocketFilter,
   649  				Instructions: asm.Instructions{
   650  					asm.Return(),
   651  				},
   652  				License: "MIT",
   653  			},
   654  		},
   655  	}
   656  
   657  	s := struct {
   658  		// Assignment to Valid should execute and succeed.
   659  		Valid *Program `ebpf:"valid"`
   660  		// Assignment to Invalid should fail and cause Valid's fd to be closed.
   661  		Invalid *Program `ebpf:"invalid"`
   662  	}{}
   663  
   664  	if err := spec.LoadAndAssign(&s, nil); err == nil {
   665  		t.Fatal("expected error loading invalid ProgramSpec")
   666  	}
   667  
   668  	if s.Valid == nil {
   669  		t.Fatal("expected valid prog to be non-nil")
   670  	}
   671  
   672  	if fd := s.Valid.FD(); fd != -1 {
   673  		t.Fatal("expected valid prog to have closed fd -1, got:", fd)
   674  	}
   675  
   676  	if s.Invalid != nil {
   677  		t.Fatal("expected invalid prog to be nil due to never being assigned")
   678  	}
   679  }
   680  
   681  func BenchmarkNewCollection(b *testing.B) {
   682  	file := testutils.NativeFile(b, "testdata/loader-%s.elf")
   683  	spec, err := LoadCollectionSpec(file)
   684  	if err != nil {
   685  		b.Fatal(err)
   686  	}
   687  
   688  	spec.Maps["array_of_hash_map"].InnerMap = spec.Maps["hash_map"]
   689  	for _, m := range spec.Maps {
   690  		m.Pinning = PinNone
   691  	}
   692  
   693  	b.ReportAllocs()
   694  	b.ResetTimer()
   695  
   696  	for i := 0; i < b.N; i++ {
   697  		coll, err := NewCollection(spec)
   698  		if err != nil {
   699  			b.Fatal(err)
   700  		}
   701  		coll.Close()
   702  	}
   703  }
   704  
   705  func BenchmarkNewCollectionManyProgs(b *testing.B) {
   706  	file := testutils.NativeFile(b, "testdata/manyprogs-%s.elf")
   707  	spec, err := LoadCollectionSpec(file)
   708  	if err != nil {
   709  		b.Fatal(err)
   710  	}
   711  
   712  	b.ReportAllocs()
   713  	b.ResetTimer()
   714  
   715  	for i := 0; i < b.N; i++ {
   716  		coll, err := NewCollection(spec)
   717  		if err != nil {
   718  			b.Fatal(err)
   719  		}
   720  		coll.Close()
   721  	}
   722  }
   723  
   724  func BenchmarkLoadCollectionManyProgs(b *testing.B) {
   725  	file, err := os.Open(testutils.NativeFile(b, "testdata/manyprogs-%s.elf"))
   726  	qt.Assert(b, qt.IsNil(err))
   727  	defer file.Close()
   728  
   729  	b.ReportAllocs()
   730  	b.ResetTimer()
   731  
   732  	for i := 0; i < b.N; i++ {
   733  		_, err := file.Seek(0, io.SeekStart)
   734  		if err != nil {
   735  			b.Fatal(err)
   736  		}
   737  
   738  		_, err = LoadCollectionSpecFromReader(file)
   739  		if err != nil {
   740  			b.Fatal(err)
   741  		}
   742  	}
   743  }
   744  
   745  func ExampleCollectionSpec_Assign() {
   746  	spec := &CollectionSpec{
   747  		Maps: map[string]*MapSpec{
   748  			"map1": {
   749  				Type:       Array,
   750  				KeySize:    4,
   751  				ValueSize:  4,
   752  				MaxEntries: 1,
   753  			},
   754  		},
   755  		Programs: map[string]*ProgramSpec{
   756  			"prog1": {
   757  				Type: SocketFilter,
   758  				Instructions: asm.Instructions{
   759  					asm.LoadImm(asm.R0, 0, asm.DWord),
   760  					asm.Return(),
   761  				},
   762  				License: "MIT",
   763  			},
   764  		},
   765  	}
   766  
   767  	type maps struct {
   768  		Map *MapSpec `ebpf:"map1"`
   769  	}
   770  
   771  	var specs struct {
   772  		maps
   773  		Program *ProgramSpec `ebpf:"prog1"`
   774  	}
   775  
   776  	if err := spec.Assign(&specs); err != nil {
   777  		panic(err)
   778  	}
   779  
   780  	fmt.Println(specs.Program.Type)
   781  	fmt.Println(specs.Map.Type)
   782  
   783  	// Output: SocketFilter
   784  	// Array
   785  }
   786  
   787  func ExampleCollectionSpec_LoadAndAssign() {
   788  	spec := &CollectionSpec{
   789  		Maps: map[string]*MapSpec{
   790  			"map1": {
   791  				Type:       Array,
   792  				KeySize:    4,
   793  				ValueSize:  4,
   794  				MaxEntries: 1,
   795  			},
   796  		},
   797  		Programs: map[string]*ProgramSpec{
   798  			"prog1": {
   799  				Type: SocketFilter,
   800  				Instructions: asm.Instructions{
   801  					asm.LoadImm(asm.R0, 0, asm.DWord),
   802  					asm.Return(),
   803  				},
   804  				License: "MIT",
   805  			},
   806  		},
   807  	}
   808  
   809  	var objs struct {
   810  		Program *Program `ebpf:"prog1"`
   811  		Map     *Map     `ebpf:"map1"`
   812  	}
   813  
   814  	if err := spec.LoadAndAssign(&objs, nil); err != nil {
   815  		panic(err)
   816  	}
   817  	defer objs.Program.Close()
   818  	defer objs.Map.Close()
   819  
   820  	fmt.Println(objs.Program.Type())
   821  	fmt.Println(objs.Map.Type())
   822  
   823  	// Output: SocketFilter
   824  	// Array
   825  }