Skip to content

Commit db31073

Browse files
ti-mosmagnani96
andcommitted
bpf2go: generate Go types used in global variables
This commit generates Go type declarations for global variables declared in a C BPF program. Some improvements to CollectGlobalTypes were due: - types are now deduplicated by name - types are now selected based on allow-list instead of explicitly rejecting Datasec and Int - the output is sorted by type name - arrays' inner types are now emitted to interact with Variable{Spec} Also added a new btf.QualifiedType(), a subset of btf.UnderlyingType() to remove all qualifiers up to a Typedef, since Typedefs are named, and typically directly point to the anonymous type they alias. Typedefs should only be stripped for evaluating the concrete underlying type, and they should be included when yielding types to the Go renderer. Signed-off-by: Timo Beckers <[email protected]> Co-authored-by: Simone Magnani <[email protected]>
1 parent fabe147 commit db31073

12 files changed

+175
-26
lines changed

btf/format.go

+3
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
161161
case *Datasec:
162162
err = gf.writeDatasecLit(v, depth)
163163

164+
case *Var:
165+
err = gf.writeTypeLit(v.Type, depth)
166+
164167
default:
165168
return fmt.Errorf("type %T: %w", v, ErrNotSupported)
166169
}

btf/format_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ func TestGoTypeDeclaration(t *testing.T) {
137137
},
138138
"type t struct { _ [4]byte; g uint32; _ [8]byte; }",
139139
},
140+
{&Var{Type: &Int{Size: 4}}, "type t uint32"},
140141
}
141142

142143
for _, test := range tests {

btf/types.go

+14
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,20 @@ func UnderlyingType(typ Type) Type {
12911291
return &cycle{typ}
12921292
}
12931293

1294+
// QualifiedType returns the type with all qualifiers removed.
1295+
func QualifiedType(typ Type) Type {
1296+
result := typ
1297+
for depth := 0; depth <= maxResolveDepth; depth++ {
1298+
switch v := (result).(type) {
1299+
case qualifier:
1300+
result = v.qualify()
1301+
default:
1302+
return result
1303+
}
1304+
}
1305+
return &cycle{typ}
1306+
}
1307+
12941308
// As returns typ if is of type T. Otherwise it peels qualifiers and Typedefs
12951309
// until it finds a T.
12961310
//

cmd/bpf2go/gen/types.go

+70-19
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,95 @@
11
package gen
22

33
import (
4+
"cmp"
5+
"slices"
6+
47
"github.com/cilium/ebpf"
58
"github.com/cilium/ebpf/btf"
69
)
710

811
// CollectGlobalTypes finds all types which are used in the global scope.
912
//
10-
// This currently includes the types of map keys and values.
13+
// This currently includes the types of variables, map keys and values.
1114
func CollectGlobalTypes(spec *ebpf.CollectionSpec) []btf.Type {
1215
var types []btf.Type
13-
for _, typ := range collectMapTypes(spec.Maps) {
14-
switch btf.UnderlyingType(typ).(type) {
15-
case *btf.Datasec:
16-
// Avoid emitting .rodata, .bss, etc. for now. We might want to
17-
// name these types differently, etc.
18-
continue
1916

20-
case *btf.Int:
21-
// Don't emit primitive types by default.
22-
continue
23-
}
17+
types = collectMapTypes(types, spec.Maps)
18+
types = collectVariableTypes(types, spec.Variables)
2419

25-
types = append(types, typ)
26-
}
20+
slices.SortStableFunc(types, func(a, b btf.Type) int {
21+
return cmp.Compare(a.TypeName(), b.TypeName())
22+
})
2723

2824
return types
2925
}
3026

31-
// collectMapTypes returns a list of all types used as map keys or values.
32-
func collectMapTypes(maps map[string]*ebpf.MapSpec) []btf.Type {
33-
var result []btf.Type
27+
// collectMapTypes collects all types used by MapSpecs.
28+
func collectMapTypes(types []btf.Type, maps map[string]*ebpf.MapSpec) []btf.Type {
3429
for _, m := range maps {
3530
if m.Key != nil && m.Key.TypeName() != "" {
36-
result = append(result, m.Key)
31+
types = addType(types, m.Key)
3732
}
3833

3934
if m.Value != nil && m.Value.TypeName() != "" {
40-
result = append(result, m.Value)
35+
types = addType(types, m.Value)
4136
}
4237
}
43-
return result
38+
39+
return types
40+
}
41+
42+
// collectVariableTypes collects all types used by VariableSpecs.
43+
func collectVariableTypes(types []btf.Type, vars map[string]*ebpf.VariableSpec) []btf.Type {
44+
for _, vs := range vars {
45+
v := vs.Type()
46+
if v == nil {
47+
continue
48+
}
49+
50+
types = addType(types, v.Type)
51+
}
52+
53+
return types
54+
}
55+
56+
// addType adds a type to types if not already present. Types that don't need to
57+
// be generated are not added to types.
58+
func addType(types []btf.Type, incoming btf.Type) []btf.Type {
59+
incoming = selectType(incoming)
60+
if incoming == nil {
61+
return types
62+
}
63+
64+
// Strip only the qualifiers (not typedefs) from the incoming type. Retain
65+
// typedefs since they carry the name of the anonymous type they point to,
66+
// without which we can't generate a named Go type.
67+
incoming = btf.QualifiedType(incoming)
68+
if incoming.TypeName() == "" {
69+
return types
70+
}
71+
72+
exists := func(existing btf.Type) bool {
73+
return existing.TypeName() == incoming.TypeName()
74+
}
75+
if !slices.ContainsFunc(types, exists) {
76+
types = append(types, incoming)
77+
}
78+
return types
79+
}
80+
81+
func selectType(t btf.Type) btf.Type {
82+
// Obtain a concrete type with qualifiers and typedefs stripped.
83+
switch ut := btf.UnderlyingType(t).(type) {
84+
case *btf.Struct, *btf.Union, *btf.Enum:
85+
return t
86+
87+
// Collect the array's element type. Note: qualifiers on array-type variables
88+
// typically appear after the array, e.g. a const volatile int[4] is actually
89+
// an array of const volatile ints.
90+
case *btf.Array:
91+
return selectType(ut.Type)
92+
}
93+
94+
return nil
4495
}

cmd/bpf2go/gen/types_test.go

+21-6
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,34 @@ import (
88
"github.com/cilium/ebpf/internal/testutils"
99

1010
"github.com/go-quicktest/qt"
11+
"github.com/google/go-cmp/cmp"
1112
)
1213

14+
func mustAnyTypeByName(t *testing.T, spec *ebpf.CollectionSpec, name string) btf.Type {
15+
t.Helper()
16+
17+
typ, err := spec.Types.AnyTypeByName(name)
18+
qt.Assert(t, qt.IsNil(err))
19+
return typ
20+
}
21+
1322
func TestCollectGlobalTypes(t *testing.T) {
1423
spec, err := ebpf.LoadCollectionSpec(testutils.NativeFile(t, "../testdata/minimal-%s.elf"))
1524
if err != nil {
1625
t.Fatal(err)
1726
}
1827

19-
map1 := spec.Maps["map1"]
28+
bar := mustAnyTypeByName(t, spec, "bar")
29+
barfoo := mustAnyTypeByName(t, spec, "barfoo")
30+
baz := mustAnyTypeByName(t, spec, "baz")
31+
e := mustAnyTypeByName(t, spec, "e")
32+
ubar := mustAnyTypeByName(t, spec, "ubar")
2033

21-
types := CollectGlobalTypes(spec)
22-
if err != nil {
23-
t.Fatal(err)
24-
}
25-
qt.Assert(t, qt.CmpEquals(types, []btf.Type{map1.Key, map1.Value}, typesEqualComparer))
34+
got := CollectGlobalTypes(spec)
35+
qt.Assert(t, qt.IsNil(err))
36+
37+
want := []btf.Type{bar, barfoo, baz, e, ubar}
38+
qt.Assert(t, qt.CmpEquals(got, want, cmp.Comparer(func(a, b btf.Type) bool {
39+
return a.TypeName() == b.TypeName()
40+
})))
2641
}

cmd/bpf2go/test/test_bpfeb.go

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/bpf2go/test/test_bpfeb.o

896 Bytes
Binary file not shown.

cmd/bpf2go/test/test_bpfel.go

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/bpf2go/test/test_bpfel.o

896 Bytes
Binary file not shown.

cmd/bpf2go/testdata/minimal-eb.elf

896 Bytes
Binary file not shown.

cmd/bpf2go/testdata/minimal-el.elf

896 Bytes
Binary file not shown.

cmd/bpf2go/testdata/minimal.c

+20-1
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,35 @@ typedef struct {
1212
enum e boo;
1313
} barfoo;
1414

15+
typedef struct {
16+
uint64_t a;
17+
} baz;
18+
19+
struct bar {
20+
uint64_t a;
21+
uint32_t b;
22+
};
23+
24+
union ubar {
25+
uint32_t a;
26+
uint64_t b;
27+
};
28+
1529
struct {
1630
__uint(type, BPF_MAP_TYPE_HASH);
1731
__type(key, enum e);
1832
__type(value, barfoo);
1933
__uint(max_entries, 1);
2034
} map1 __section(".maps");
2135

36+
volatile const int an_int;
2237
volatile const enum e my_constant = FROOD;
23-
38+
volatile const int int_array[2];
2439
volatile const barfoo struct_const;
40+
volatile const baz struct_array[2];
41+
42+
volatile struct bar struct_var;
43+
volatile union ubar union_var;
2544

2645
__section("socket") int filter() {
2746
return my_constant + struct_const.bar;

0 commit comments

Comments
 (0)