diff --git a/btf/format.go b/btf/format.go index 5e581b4a8..3e0dedaa2 100644 --- a/btf/format.go +++ b/btf/format.go @@ -161,6 +161,9 @@ func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error { case *Datasec: err = gf.writeDatasecLit(v, depth) + case *Var: + err = gf.writeTypeLit(v.Type, depth) + default: return fmt.Errorf("type %T: %w", v, ErrNotSupported) } diff --git a/btf/format_test.go b/btf/format_test.go index c26e023df..4bf21c0fe 100644 --- a/btf/format_test.go +++ b/btf/format_test.go @@ -137,6 +137,7 @@ func TestGoTypeDeclaration(t *testing.T) { }, "type t struct { _ [4]byte; g uint32; _ [8]byte; }", }, + {&Var{Type: &Int{Size: 4}}, "type t uint32"}, } for _, test := range tests { diff --git a/btf/types.go b/btf/types.go index 44d393067..dbcdf9dd7 100644 --- a/btf/types.go +++ b/btf/types.go @@ -1291,6 +1291,20 @@ func UnderlyingType(typ Type) Type { return &cycle{typ} } +// QualifiedType returns the type with all qualifiers removed. +func QualifiedType(typ Type) Type { + result := typ + for depth := 0; depth <= maxResolveDepth; depth++ { + switch v := (result).(type) { + case qualifier: + result = v.qualify() + default: + return result + } + } + return &cycle{typ} +} + // As returns typ if is of type T. Otherwise it peels qualifiers and Typedefs // until it finds a T. // diff --git a/cmd/bpf2go/gen/types.go b/cmd/bpf2go/gen/types.go index 37dad0c76..115d73423 100644 --- a/cmd/bpf2go/gen/types.go +++ b/cmd/bpf2go/gen/types.go @@ -1,44 +1,95 @@ package gen import ( + "cmp" + "slices" + "github.com/cilium/ebpf" "github.com/cilium/ebpf/btf" ) // CollectGlobalTypes finds all types which are used in the global scope. // -// This currently includes the types of map keys and values. +// This currently includes the types of variables, map keys and values. func CollectGlobalTypes(spec *ebpf.CollectionSpec) []btf.Type { var types []btf.Type - for _, typ := range collectMapTypes(spec.Maps) { - switch btf.UnderlyingType(typ).(type) { - case *btf.Datasec: - // Avoid emitting .rodata, .bss, etc. for now. We might want to - // name these types differently, etc. - continue - case *btf.Int: - // Don't emit primitive types by default. - continue - } + types = collectMapTypes(types, spec.Maps) + types = collectVariableTypes(types, spec.Variables) - types = append(types, typ) - } + slices.SortStableFunc(types, func(a, b btf.Type) int { + return cmp.Compare(a.TypeName(), b.TypeName()) + }) return types } -// collectMapTypes returns a list of all types used as map keys or values. -func collectMapTypes(maps map[string]*ebpf.MapSpec) []btf.Type { - var result []btf.Type +// collectMapTypes collects all types used by MapSpecs. +func collectMapTypes(types []btf.Type, maps map[string]*ebpf.MapSpec) []btf.Type { for _, m := range maps { if m.Key != nil && m.Key.TypeName() != "" { - result = append(result, m.Key) + types = addType(types, m.Key) } if m.Value != nil && m.Value.TypeName() != "" { - result = append(result, m.Value) + types = addType(types, m.Value) } } - return result + + return types +} + +// collectVariableTypes collects all types used by VariableSpecs. +func collectVariableTypes(types []btf.Type, vars map[string]*ebpf.VariableSpec) []btf.Type { + for _, vs := range vars { + v := vs.Type() + if v == nil { + continue + } + + types = addType(types, v.Type) + } + + return types +} + +// addType adds a type to types if not already present. Types that don't need to +// be generated are not added to types. +func addType(types []btf.Type, incoming btf.Type) []btf.Type { + incoming = selectType(incoming) + if incoming == nil { + return types + } + + // Strip only the qualifiers (not typedefs) from the incoming type. Retain + // typedefs since they carry the name of the anonymous type they point to, + // without which we can't generate a named Go type. + incoming = btf.QualifiedType(incoming) + if incoming.TypeName() == "" { + return types + } + + exists := func(existing btf.Type) bool { + return existing.TypeName() == incoming.TypeName() + } + if !slices.ContainsFunc(types, exists) { + types = append(types, incoming) + } + return types +} + +func selectType(t btf.Type) btf.Type { + // Obtain a concrete type with qualifiers and typedefs stripped. + switch ut := btf.UnderlyingType(t).(type) { + case *btf.Struct, *btf.Union, *btf.Enum: + return t + + // Collect the array's element type. Note: qualifiers on array-type variables + // typically appear after the array, e.g. a const volatile int[4] is actually + // an array of const volatile ints. + case *btf.Array: + return selectType(ut.Type) + } + + return nil } diff --git a/cmd/bpf2go/gen/types_test.go b/cmd/bpf2go/gen/types_test.go index bb5866301..286705a98 100644 --- a/cmd/bpf2go/gen/types_test.go +++ b/cmd/bpf2go/gen/types_test.go @@ -8,19 +8,34 @@ import ( "github.com/cilium/ebpf/internal/testutils" "github.com/go-quicktest/qt" + "github.com/google/go-cmp/cmp" ) +func mustAnyTypeByName(t *testing.T, spec *ebpf.CollectionSpec, name string) btf.Type { + t.Helper() + + typ, err := spec.Types.AnyTypeByName(name) + qt.Assert(t, qt.IsNil(err)) + return typ +} + func TestCollectGlobalTypes(t *testing.T) { spec, err := ebpf.LoadCollectionSpec(testutils.NativeFile(t, "../testdata/minimal-%s.elf")) if err != nil { t.Fatal(err) } - map1 := spec.Maps["map1"] + bar := mustAnyTypeByName(t, spec, "bar") + barfoo := mustAnyTypeByName(t, spec, "barfoo") + baz := mustAnyTypeByName(t, spec, "baz") + e := mustAnyTypeByName(t, spec, "e") + ubar := mustAnyTypeByName(t, spec, "ubar") - types := CollectGlobalTypes(spec) - if err != nil { - t.Fatal(err) - } - qt.Assert(t, qt.CmpEquals(types, []btf.Type{map1.Key, map1.Value}, typesEqualComparer)) + got := CollectGlobalTypes(spec) + qt.Assert(t, qt.IsNil(err)) + + want := []btf.Type{bar, barfoo, baz, e, ubar} + qt.Assert(t, qt.CmpEquals(got, want, cmp.Comparer(func(a, b btf.Type) bool { + return a.TypeName() == b.TypeName() + }))) } diff --git a/cmd/bpf2go/test/test_bpfeb.go b/cmd/bpf2go/test/test_bpfeb.go index 28b1b3b2a..be2f2e68d 100644 --- a/cmd/bpf2go/test/test_bpfeb.go +++ b/cmd/bpf2go/test/test_bpfeb.go @@ -12,6 +12,12 @@ import ( "github.com/cilium/ebpf" ) +type testBar struct { + A uint64 + B uint32 + _ [4]byte +} + type testBarfoo struct { Bar int64 Baz bool @@ -19,6 +25,8 @@ type testBarfoo struct { Boo testE } +type testBaz struct{ A uint64 } + type testE uint32 const ( @@ -26,6 +34,11 @@ const ( testEFROOD testE = 1 ) +type testUbar struct { + A uint32 + _ [4]byte +} + // loadTest returns the embedded CollectionSpec for test. func loadTest() (*ebpf.CollectionSpec, error) { reader := bytes.NewReader(_TestBytes) @@ -82,8 +95,13 @@ type testMapSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type testVariableSpecs struct { + AnInt *ebpf.VariableSpec `ebpf:"an_int"` + IntArray *ebpf.VariableSpec `ebpf:"int_array"` MyConstant *ebpf.VariableSpec `ebpf:"my_constant"` + StructArray *ebpf.VariableSpec `ebpf:"struct_array"` StructConst *ebpf.VariableSpec `ebpf:"struct_const"` + StructVar *ebpf.VariableSpec `ebpf:"struct_var"` + UnionVar *ebpf.VariableSpec `ebpf:"union_var"` } // testObjects contains all objects after they have been loaded into the kernel. @@ -119,8 +137,13 @@ func (m *testMaps) Close() error { // // It can be passed to loadTestObjects or ebpf.CollectionSpec.LoadAndAssign. type testVariables struct { + AnInt *ebpf.Variable `ebpf:"an_int"` + IntArray *ebpf.Variable `ebpf:"int_array"` MyConstant *ebpf.Variable `ebpf:"my_constant"` + StructArray *ebpf.Variable `ebpf:"struct_array"` StructConst *ebpf.Variable `ebpf:"struct_const"` + StructVar *ebpf.Variable `ebpf:"struct_var"` + UnionVar *ebpf.Variable `ebpf:"union_var"` } // testPrograms contains all programs after they have been loaded into the kernel. diff --git a/cmd/bpf2go/test/test_bpfeb.o b/cmd/bpf2go/test/test_bpfeb.o index dffb8f859..a59e38c27 100644 Binary files a/cmd/bpf2go/test/test_bpfeb.o and b/cmd/bpf2go/test/test_bpfeb.o differ diff --git a/cmd/bpf2go/test/test_bpfel.go b/cmd/bpf2go/test/test_bpfel.go index a66809533..4fb0c2280 100644 --- a/cmd/bpf2go/test/test_bpfel.go +++ b/cmd/bpf2go/test/test_bpfel.go @@ -12,6 +12,12 @@ import ( "github.com/cilium/ebpf" ) +type testBar struct { + A uint64 + B uint32 + _ [4]byte +} + type testBarfoo struct { Bar int64 Baz bool @@ -19,6 +25,8 @@ type testBarfoo struct { Boo testE } +type testBaz struct{ A uint64 } + type testE uint32 const ( @@ -26,6 +34,11 @@ const ( testEFROOD testE = 1 ) +type testUbar struct { + A uint32 + _ [4]byte +} + // loadTest returns the embedded CollectionSpec for test. func loadTest() (*ebpf.CollectionSpec, error) { reader := bytes.NewReader(_TestBytes) @@ -82,8 +95,13 @@ type testMapSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type testVariableSpecs struct { + AnInt *ebpf.VariableSpec `ebpf:"an_int"` + IntArray *ebpf.VariableSpec `ebpf:"int_array"` MyConstant *ebpf.VariableSpec `ebpf:"my_constant"` + StructArray *ebpf.VariableSpec `ebpf:"struct_array"` StructConst *ebpf.VariableSpec `ebpf:"struct_const"` + StructVar *ebpf.VariableSpec `ebpf:"struct_var"` + UnionVar *ebpf.VariableSpec `ebpf:"union_var"` } // testObjects contains all objects after they have been loaded into the kernel. @@ -119,8 +137,13 @@ func (m *testMaps) Close() error { // // It can be passed to loadTestObjects or ebpf.CollectionSpec.LoadAndAssign. type testVariables struct { + AnInt *ebpf.Variable `ebpf:"an_int"` + IntArray *ebpf.Variable `ebpf:"int_array"` MyConstant *ebpf.Variable `ebpf:"my_constant"` + StructArray *ebpf.Variable `ebpf:"struct_array"` StructConst *ebpf.Variable `ebpf:"struct_const"` + StructVar *ebpf.Variable `ebpf:"struct_var"` + UnionVar *ebpf.Variable `ebpf:"union_var"` } // testPrograms contains all programs after they have been loaded into the kernel. diff --git a/cmd/bpf2go/test/test_bpfel.o b/cmd/bpf2go/test/test_bpfel.o index 1cc5b1e1b..9bc2674a3 100644 Binary files a/cmd/bpf2go/test/test_bpfel.o and b/cmd/bpf2go/test/test_bpfel.o differ diff --git a/cmd/bpf2go/testdata/minimal-eb.elf b/cmd/bpf2go/testdata/minimal-eb.elf index dafa0302f..8a45210ac 100644 Binary files a/cmd/bpf2go/testdata/minimal-eb.elf and b/cmd/bpf2go/testdata/minimal-eb.elf differ diff --git a/cmd/bpf2go/testdata/minimal-el.elf b/cmd/bpf2go/testdata/minimal-el.elf index 0acd3be7c..89b8e9d3a 100644 Binary files a/cmd/bpf2go/testdata/minimal-el.elf and b/cmd/bpf2go/testdata/minimal-el.elf differ diff --git a/cmd/bpf2go/testdata/minimal.c b/cmd/bpf2go/testdata/minimal.c index 1639d6ef0..d906d2408 100644 --- a/cmd/bpf2go/testdata/minimal.c +++ b/cmd/bpf2go/testdata/minimal.c @@ -12,6 +12,20 @@ typedef struct { enum e boo; } barfoo; +typedef struct { + uint64_t a; +} baz; + +struct bar { + uint64_t a; + uint32_t b; +}; + +union ubar { + uint32_t a; + uint64_t b; +}; + struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, enum e); @@ -19,9 +33,14 @@ struct { __uint(max_entries, 1); } map1 __section(".maps"); +volatile const int an_int; volatile const enum e my_constant = FROOD; - +volatile const int int_array[2]; volatile const barfoo struct_const; +volatile const baz struct_array[2]; + +volatile struct bar struct_var; +volatile union ubar union_var; __section("socket") int filter() { return my_constant + struct_const.bar;