Skip to content

Commit f6a94c5

Browse files
committed
add bpf2go support and wrappers for ebpf.Variable generation
This commit introduces support and wrappers around ebpf.Variable. An ebpf.Variable does not define per-se wrappers method to automatically infer the underlying data type and handle typed data in Go. In fact, it relies on parameters passed to the `Variable.Get` and `Variable.Set` APIs. This commit allows `bpf2go` to emit `VariableSpec` and `Variable` in the generated code. In addition, a new set of wrapper methods are created to provide support for typed method around variables. To do so, this commit enables emitting `Dataspec`, which so far was not emitted. However, it modifies the current implementation so that for each variable in `Dataspec` the needed support is provided. Supposing to have an ebpf program that defines the following two global variables: ```c __u64 pkt_count = 0; const __u32 ro_variable = 1; char var_msg[] = "Hello World!"; ``` The steps performed during the Go code generation are as follows: 1. An alias with the variable name is generated in the Go file: ```go type PktCountType = uint64 type RoVariable = uint32 type VarMsgType = [16]int8 ``` 2. A new type definition (non-alias, need new methods) for the variables is performed: ```go type PktCount ebpf.Variable type RoVariable ebpf.Variable type VarMsg ebpf.Variable ``` 3. The `.Get` methods for all types are generated: ```go func (v *PktCount) Get() (PktCountType, error) { var ret PktCountType return ret, (*ebpf.Variable)(v).Get(&ret) } func (v *RoVariable) Get() (RoVariableType, error) { var ret RoVariableType return ret, (*ebpf.Variable)(v).Get(&ret) } func (v *VarMsg) Get() (VarMsgType, error) { var ret VarMsgType return ret, (*ebpf.Variable)(v).Get(&ret) } ``` 4. The `.Set` methods for the non read-only variables are generated: ```go func (v *PktCount) Set(val PktCountType) error { return (*ebpf.Variable)(v).Set(val) } func (v *VarMsg) set(val VarMsgType) error { return (*ebpf.Variable)(v).Set(ret) } ``` 4. In case the type alias is supported by the atomic package, and then also supported by `ebpf.Variable` (int32, uint32, int64, uint64), then an additional `AtomicRef` method is created for the non read-only variables to get an reference to the underlying data. ```go func (v *PktCount) AtomicRef() (*ebpf.Uint64, error) { ret, _ := (*ebpf.Variable)(v).AtomicUint64() return ret } ``` From the user perspective, ignoring the error catching, the following operations can be performed: ```go ... objs := bpfObjects{} err := loadBpfObjects(&objs, nil) ... // ref kept and can be used multiple times aPktCount := objs.PktCount.AtomicRef() err := aPktCount.Load() aPktCount.Store(rand.Uint64()) ... vRoVariable, _ := objs.RoVariable.Get() ... varMsg, _ := objs.VarMsg.Get() varMsg[0] = 'B' err := objs.VarMsg.Set(varMsg) ``` Signed-off-by: Simone Magnani <[email protected]>
1 parent 59808a1 commit f6a94c5

File tree

7 files changed

+164
-32
lines changed

7 files changed

+164
-32
lines changed

Diff for: btf/format.go

+98-17
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,45 @@ import (
44
"errors"
55
"fmt"
66
"strings"
7+
"text/template"
8+
9+
"golang.org/x/text/cases"
10+
"golang.org/x/text/language"
711
)
812

913
var errNestedTooDeep = errors.New("nested too deep")
1014

15+
var tplVars = `
16+
17+
type {{ .Name }} ebpf.Variable
18+
19+
func(v *{{ .Name }}) Get() ({{ .Type }}, error) {
20+
var ret {{ .Type }}
21+
return ret, (*ebpf.Variable)(v).Get(&ret)
22+
}
23+
24+
{{- if not .ReadOnly }}
25+
func(v *{{ .Name }}) Set(val {{ .Type }}) error {
26+
return (*ebpf.Variable)(v).Set(val)
27+
}
28+
29+
{{ if .CanAtomic }}
30+
func(v *{{ .Name }}) AtomicRef() *ebpf.{{ .CapitalizedType }} {
31+
ret, _ := (*ebpf.Variable)(v).Atomic{{ .CapitalizedType }}()
32+
return ret
33+
}
34+
{{ end }}
35+
{{- end }}
36+
`
37+
38+
type TplVarsData struct {
39+
Name string
40+
Type string
41+
CapitalizedType string
42+
ReadOnly bool
43+
CanAtomic bool
44+
}
45+
1146
// GoFormatter converts a Type to Go syntax.
1247
//
1348
// A zero GoFormatter is valid to use.
@@ -64,7 +99,12 @@ func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
6499
}
65100

66101
typ = skipQualifiers(typ)
67-
fmt.Fprintf(&gf.w, "type %s ", name)
102+
// custom handling Datasec types in writeDatasecLit
103+
_, ok := typ.(*Datasec)
104+
if !ok {
105+
fmt.Fprintf(&gf.w, "type %s ", name)
106+
}
107+
68108
if err := gf.writeTypeLit(typ, 0); err != nil {
69109
return err
70110
}
@@ -295,41 +335,82 @@ func (gf *GoFormatter) writeStructField(m Member, depth int) error {
295335
}
296336

297337
func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error {
298-
gf.w.WriteString("struct { ")
338+
tmpl, err := template.New("varsHelpers").Parse(tplVars)
339+
if err != nil {
340+
return fmt.Errorf("failed to parse template: %w", err)
341+
}
299342

300-
prevOffset := uint32(0)
301343
for i, vsi := range ds.Vars {
302344
v, ok := vsi.Type.(*Var)
303345
if !ok {
304346
return fmt.Errorf("can't format %s as part of data section", vsi.Type)
305347
}
306348

307-
if v.Linkage != GlobalVar {
308-
// Ignore static, extern, etc. for now.
309-
continue
310-
}
311-
312-
if v.Name == "" {
313-
return fmt.Errorf("variable %d: empty name", i)
349+
id := gf.identifier(v.Name)
350+
va := getSuppAtomicType(v.Type)
351+
tplArgs := TplVarsData{
352+
Name: id,
353+
Type: id + "Type",
354+
CapitalizedType: cases.Title(language.Und, cases.NoLower).String(va),
355+
ReadOnly: strings.HasPrefix(ds.Name, ".ro"),
356+
CanAtomic: va != "",
314357
}
315358

316-
gf.writePadding(vsi.Offset - prevOffset)
317-
prevOffset = vsi.Offset + vsi.Size
318-
319-
fmt.Fprintf(&gf.w, "%s ", gf.identifier(v.Name))
359+
fmt.Fprintf(&gf.w, "type %s =", tplArgs.Type)
320360

321361
if err := gf.writeType(v.Type, depth); err != nil {
322362
return fmt.Errorf("variable %d: %w", i, err)
323363
}
324364

325-
gf.w.WriteString("; ")
365+
if err := tmpl.Execute(&gf.w, tplArgs); err != nil {
366+
return fmt.Errorf("failed to execute template for variable %s: %w", tplArgs.Name, err)
367+
}
326368
}
327369

328-
gf.writePadding(ds.Size - prevOffset)
329-
gf.w.WriteString("}")
330370
return nil
331371
}
332372

373+
// getSuppAtomicType returns the corresponding Go type for the
374+
// provided argument if it supports package atomic primitives.
375+
// Current support for int32, uint32, int64, uint64.
376+
func getSuppAtomicType(t Type) string {
377+
checkInt := func(t *Int) string {
378+
ret := ""
379+
switch t.Size {
380+
// uint32/int32 and uint64/int64
381+
case 4:
382+
ret = "int32"
383+
case 8:
384+
ret = "int64"
385+
default:
386+
return ""
387+
}
388+
if t.Encoding == Unsigned {
389+
ret = "u" + ret
390+
}
391+
return ret
392+
}
393+
394+
switch v := skipQualifiers(t).(type) {
395+
case *Int:
396+
return checkInt(v)
397+
case *Typedef:
398+
if vv, ok := v.Type.(*Int); ok {
399+
return checkInt(vv)
400+
}
401+
case *Enum:
402+
i := &Int{
403+
Name: v.Name,
404+
Size: v.Size,
405+
Encoding: Unsigned,
406+
}
407+
if v.Signed {
408+
i.Encoding = Signed
409+
}
410+
return checkInt(i)
411+
}
412+
return ""
413+
}
333414
func (gf *GoFormatter) writePadding(bytes uint32) {
334415
if bytes > 0 {
335416
fmt.Fprintf(&gf.w, "_ [%d]byte; ", bytes)

Diff for: cmd/bpf2go/gen/output.go

+17-8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func (n templateName) MapSpecs() string {
4949
return string(n) + "MapSpecs"
5050
}
5151

52+
func (n templateName) VariableSpecs() string {
53+
return string(n) + "VariableSpecs"
54+
}
55+
5256
func (n templateName) Load() string {
5357
return n.maybeExport("load" + toUpperFirst(string(n)))
5458
}
@@ -65,6 +69,10 @@ func (n templateName) Maps() string {
6569
return string(n) + "Maps"
6670
}
6771

72+
func (n templateName) Variables() string {
73+
return string(n) + "Variables"
74+
}
75+
6876
func (n templateName) Programs() string {
6977
return string(n) + "Programs"
7078
}
@@ -82,6 +90,8 @@ type GenerateArgs struct {
8290
Constraints constraint.Expr
8391
// Maps to be emitted.
8492
Maps []string
93+
// Variables to be emitted.
94+
Variables []string
8595
// Programs to be emitted.
8696
Programs []string
8797
// Types to be emitted.
@@ -103,19 +113,16 @@ func Generate(args GenerateArgs) error {
103113
return fmt.Errorf("file %q contains an invalid character", args.ObjectFile)
104114
}
105115

106-
for _, typ := range args.Types {
107-
if _, ok := btf.As[*btf.Datasec](typ); ok {
108-
// Avoid emitting .rodata, .bss, etc. for now. We might want to
109-
// name these types differently, etc.
110-
return fmt.Errorf("can't output btf.Datasec: %s", typ)
111-
}
112-
}
113-
114116
maps := make(map[string]string)
115117
for _, name := range args.Maps {
116118
maps[name] = internal.Identifier(name)
117119
}
118120

121+
vars := make(map[string]string)
122+
for _, name := range args.Variables {
123+
vars[name] = internal.Identifier(name)
124+
}
125+
119126
programs := make(map[string]string)
120127
for _, name := range args.Programs {
121128
programs[name] = internal.Identifier(name)
@@ -146,6 +153,7 @@ func Generate(args GenerateArgs) error {
146153
Constraints constraint.Expr
147154
Name templateName
148155
Maps map[string]string
156+
Variables map[string]string
149157
Programs map[string]string
150158
Types []btf.Type
151159
TypeNames map[btf.Type]string
@@ -157,6 +165,7 @@ func Generate(args GenerateArgs) error {
157165
args.Constraints,
158166
templateName(args.Stem),
159167
maps,
168+
vars,
160169
programs,
161170
types,
162171
typeNames,

Diff for: cmd/bpf2go/gen/output.tpl

+20
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func {{ .Name.LoadObjects }}(obj interface{}, opts *ebpf.CollectionOptions) (err
5454
type {{ .Name.Specs }} struct {
5555
{{ .Name.ProgramSpecs }}
5656
{{ .Name.MapSpecs }}
57+
{{ .Name.VariableSpecs }}
5758
}
5859

5960
// {{ .Name.Specs }} contains programs before they are loaded into the kernel.
@@ -80,6 +81,7 @@ type {{ .Name.MapSpecs }} struct {
8081
type {{ .Name.Objects }} struct {
8182
{{ .Name.Programs }}
8283
{{ .Name.Maps }}
84+
{{ .Name.Variables }}
8385
}
8486

8587
func (o *{{ .Name.Objects }}) Close() error {
@@ -98,6 +100,24 @@ type {{ .Name.Maps }} struct {
98100
{{- end }}
99101
}
100102

103+
// {{ .Name.VariableSpecs }} contains variables before they are loaded into the kernel.
104+
//
105+
// It can be passed ebpf.CollectionSpec.Assign.
106+
type {{ .Name.VariableSpecs }} struct {
107+
{{- range $name, $id := .Variables }}
108+
{{ $id }} *ebpf.VariableSpec `ebpf:"{{ $name }}"`
109+
{{- end }}
110+
}
111+
112+
// {{ .Name.Variables }} contains all variables after they have been loaded into the kernel.
113+
//
114+
// It can be passed to {{ .Name.LoadObjects }} or ebpf.CollectionSpec.LoadAndAssign.
115+
type {{ .Name.Variables }} struct {
116+
{{- range $name, $id := .Variables }}
117+
{{ $id }} *{{ $id }} `ebpf:"{{ $name }}"`
118+
{{- end }}
119+
}
120+
101121
func (m *{{ .Name.Maps }}) Close() error {
102122
return {{ .Name.CloseHelper }}(
103123
{{- range $id := .Maps }}

Diff for: cmd/bpf2go/gen/types.go

-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ func CollectGlobalTypes(spec *ebpf.CollectionSpec) []btf.Type {
1212
var types []btf.Type
1313
for _, typ := range collectMapTypes(spec.Maps) {
1414
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
1915

2016
case *btf.Int:
2117
// Don't emit primitive types by default.

Diff for: cmd/bpf2go/main.go

+6
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,11 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) {
370370
}
371371
}
372372

373+
var vars []string
374+
for name := range spec.Variables {
375+
vars = append(vars, name)
376+
}
377+
373378
var programs []string
374379
for name := range spec.Programs {
375380
programs = append(programs, name)
@@ -397,6 +402,7 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) {
397402
Stem: b2g.identStem,
398403
Constraints: constraints,
399404
Maps: maps,
405+
Variables: vars,
400406
Programs: programs,
401407
Types: types,
402408
ObjectFile: filepath.Base(objFileName),

Diff for: go.mod

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@ require (
66
github.com/go-quicktest/qt v1.101.0
77
github.com/google/go-cmp v0.6.0
88
github.com/jsimonetti/rtnetlink/v2 v2.0.1
9+
github.com/stretchr/testify v1.9.0
910
golang.org/x/sys v0.20.0
1011
)
1112

13+
require (
14+
github.com/davecgh/go-spew v1.1.1 // indirect
15+
github.com/pmezard/go-difflib v1.0.0 // indirect
16+
gopkg.in/yaml.v3 v3.0.1 // indirect
17+
)
18+
1219
require (
1320
github.com/josharian/native v1.1.0 // indirect
1421
github.com/kr/pretty v0.3.1 // indirect
@@ -17,5 +24,6 @@ require (
1724
github.com/mdlayher/socket v0.4.1 // indirect
1825
github.com/rogpeppe/go-internal v1.11.0 // indirect
1926
golang.org/x/net v0.23.0 // indirect
20-
golang.org/x/sync v0.1.0 // indirect
27+
golang.org/x/sync v0.8.0 // indirect
28+
golang.org/x/text v0.18.0
2129
)

Diff for: go.sum

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
35
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
46
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -16,12 +18,22 @@ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU
1618
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
1719
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
1820
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
21+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
22+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1923
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
2024
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
2125
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
26+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
27+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
2228
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
2329
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
24-
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
25-
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
30+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
31+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
2632
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
2733
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
34+
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
35+
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
36+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
37+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
38+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
39+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)