Skip to content

Commit

Permalink
add bpf2go support and wrappers for ebpf.Variable generation
Browse files Browse the repository at this point in the history
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;
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 VarMsgType = [16]int8
```

2. A new type definition (non-alias, need new methods) for the variables is performed:

```go
type PktCount ebpf.Variable

type VarMsg ebpf.Variable
```

3. The `.Get` and `.Set` methods for these types are generated:

```go
func (v *PktCount) Get() (PktCountType, error) {
	var ret PktCountType
	return ret, (*ebpf.Variable)(v).Get(&ret)
}

func (v *PktCount) Set(val PktCountType) error {
	return (*ebpf.Variable)(v).Set(val)
}

func (v *VarMsg) Get() (VarMsgType, error) {
	var ret VarMsgType
	return ret, (*ebpf.Variable)(v).Get(&ret)
}

func (v *VarMsg) Set(val VarMsgType) error {
	return (*ebpf.Variable)(v).Set(val)
}
```

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 to get a reference to the
	underlying value while also performing atomic operations.

```go
func (v *PktCount) AtomicRef() (*ebpf.Uint64, error) {
	return (*ebpf.Variable)(v).AtomicUint64()
}
```

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, err := objs.PktCount.AtomicRef()
err := aPktCount.Load()
aPktCount.Store(rand.Uint64())
...

varMsg, _ := objs.VarMsg.Get()
varMsg[0] = 'B'
err := objs.VarMsg.Set(varMsg)
```

Signed-off-by: Simone Magnani <[email protected]>
  • Loading branch information
smagnani96 committed Sep 6, 2024
1 parent 59808a1 commit 8486b44
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 32 deletions.
117 changes: 100 additions & 17 deletions btf/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,40 @@ import (
"errors"
"fmt"
"strings"
"text/template"

"golang.org/x/text/cases"
"golang.org/x/text/language"
)

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

const tplVars = `
type {{ .Name }} ebpf.Variable
func(v *{{ .Name }}) Get() ({{ .Type }}, error) {
var ret {{ .Type }}
return ret, (*ebpf.Variable)(v).Get(&ret)
}
func(v *{{ .Name }}) Set(val {{ .Type }}) error {
return (*ebpf.Variable)(v).Set(val)
}
`

const tplAtomicVars = `
func(v *{{ .Name }}) AtomicRef() (*ebpf.{{ .CapitalizedType }}, error) {
return (*ebpf.Variable)(v).Atomic{{ .CapitalizedType }}()
}
`

type TplVarsData struct {
Name string
Type string
CapitalizedType string
}

// GoFormatter converts a Type to Go syntax.
//
// A zero GoFormatter is valid to use.
Expand Down Expand Up @@ -64,7 +94,12 @@ func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
}

typ = skipQualifiers(typ)
fmt.Fprintf(&gf.w, "type %s ", name)
// custom handling Datasec types in writeDatasecLit
_, ok := typ.(*Datasec)
if !ok {
fmt.Fprintf(&gf.w, "type %s ", name)
}

if err := gf.writeTypeLit(typ, 0); err != nil {
return err
}
Expand Down Expand Up @@ -295,41 +330,89 @@ func (gf *GoFormatter) writeStructField(m Member, depth int) error {
}

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

tmplAtomic, err := template.New("varsAtomicHelpers").Parse(tplAtomicVars)
if err != nil {
return fmt.Errorf("failed to parse template: %w", err)
}

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

if v.Linkage != GlobalVar {
// Ignore static, extern, etc. for now.
continue
}

if v.Name == "" {
return fmt.Errorf("variable %d: empty name", i)
id := gf.identifier(v.Name)
tplArgs := TplVarsData{
Name: id,
Type: id + "Type",
}

gf.writePadding(vsi.Offset - prevOffset)
prevOffset = vsi.Offset + vsi.Size

fmt.Fprintf(&gf.w, "%s ", gf.identifier(v.Name))
fmt.Fprintf(&gf.w, "type %s =", tplArgs.Type)

if err := gf.writeType(v.Type, depth); err != nil {
return fmt.Errorf("variable %d: %w", i, err)
}
if err := tmpl.Execute(&gf.w, tplArgs); err != nil {
return fmt.Errorf("failed to execute template for variable %s: %w", tplArgs.Name, err)
}

gf.w.WriteString("; ")
if va := getSuppAtomicType(v.Type); va != "" {
tplArgs.CapitalizedType = cases.Title(language.Und, cases.NoLower).String(va)
if tmplAtomic.Execute(&gf.w, tplArgs) != nil {
return fmt.Errorf("failed to execute template for variable %s atomics: %w", tplArgs.Name, err)
}
}
}

gf.writePadding(ds.Size - prevOffset)
gf.w.WriteString("}")
return nil
}

// getSuppAtomicType returns the corresponding Go type for the
// provided argument if it supports package atomic primitives.
// Current support for int32, uint32, int64, uint64.
func getSuppAtomicType(t Type) string {
checkInt := func(t *Int) string {
ret := ""
switch t.Size {
// uint32/int32 and uint64/int64
case 4:
ret = "int32"
case 8:
ret = "int64"
default:
return ""
}
if t.Encoding == Unsigned {
ret = "u" + ret
}
return ret
}

switch v := skipQualifiers(t).(type) {
case *Int:
return checkInt(v)
case *Typedef:
if vv, ok := v.Type.(*Int); ok {
return checkInt(vv)
}
case *Enum:
i := &Int{
Name: v.Name,
Size: v.Size,
Encoding: Unsigned,
}
if v.Signed {
i.Encoding = Signed
}
return checkInt(i)
}
return ""
}
func (gf *GoFormatter) writePadding(bytes uint32) {
if bytes > 0 {
fmt.Fprintf(&gf.w, "_ [%d]byte; ", bytes)
Expand Down
25 changes: 17 additions & 8 deletions cmd/bpf2go/gen/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func (n templateName) MapSpecs() string {
return string(n) + "MapSpecs"
}

func (n templateName) VariableSpecs() string {
return string(n) + "VariableSpecs"
}

func (n templateName) Load() string {
return n.maybeExport("load" + toUpperFirst(string(n)))
}
Expand All @@ -65,6 +69,10 @@ func (n templateName) Maps() string {
return string(n) + "Maps"
}

func (n templateName) Variables() string {
return string(n) + "Variables"
}

func (n templateName) Programs() string {
return string(n) + "Programs"
}
Expand All @@ -82,6 +90,8 @@ type GenerateArgs struct {
Constraints constraint.Expr
// Maps to be emitted.
Maps []string
// Variables to be emitted.
Variables []string
// Programs to be emitted.
Programs []string
// Types to be emitted.
Expand All @@ -103,19 +113,16 @@ func Generate(args GenerateArgs) error {
return fmt.Errorf("file %q contains an invalid character", args.ObjectFile)
}

for _, typ := range args.Types {
if _, ok := btf.As[*btf.Datasec](typ); ok {
// Avoid emitting .rodata, .bss, etc. for now. We might want to
// name these types differently, etc.
return fmt.Errorf("can't output btf.Datasec: %s", typ)
}
}

maps := make(map[string]string)
for _, name := range args.Maps {
maps[name] = internal.Identifier(name)
}

vars := make(map[string]string)
for _, name := range args.Variables {
vars[name] = internal.Identifier(name)
}

programs := make(map[string]string)
for _, name := range args.Programs {
programs[name] = internal.Identifier(name)
Expand Down Expand Up @@ -146,6 +153,7 @@ func Generate(args GenerateArgs) error {
Constraints constraint.Expr
Name templateName
Maps map[string]string
Variables map[string]string
Programs map[string]string
Types []btf.Type
TypeNames map[btf.Type]string
Expand All @@ -157,6 +165,7 @@ func Generate(args GenerateArgs) error {
args.Constraints,
templateName(args.Stem),
maps,
vars,
programs,
types,
typeNames,
Expand Down
20 changes: 20 additions & 0 deletions cmd/bpf2go/gen/output.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func {{ .Name.LoadObjects }}(obj interface{}, opts *ebpf.CollectionOptions) (err
type {{ .Name.Specs }} struct {
{{ .Name.ProgramSpecs }}
{{ .Name.MapSpecs }}
{{ .Name.VariableSpecs }}
}

// {{ .Name.Specs }} contains programs before they are loaded into the kernel.
Expand All @@ -80,6 +81,7 @@ type {{ .Name.MapSpecs }} struct {
type {{ .Name.Objects }} struct {
{{ .Name.Programs }}
{{ .Name.Maps }}
{{ .Name.Variables }}
}

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

// {{ .Name.VariableSpecs }} contains variables before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type {{ .Name.VariableSpecs }} struct {
{{- range $name, $id := .Variables }}
{{ $id }} *ebpf.VariableSpec `ebpf:"{{ $name }}"`
{{- end }}
}

// {{ .Name.Variables }} contains all variables after they have been loaded into the kernel.
//
// It can be passed to {{ .Name.LoadObjects }} or ebpf.CollectionSpec.LoadAndAssign.
type {{ .Name.Variables }} struct {
{{- range $name, $id := .Variables }}
{{ $id }} *{{ $id }} `ebpf:"{{ $name }}"`
{{- end }}
}

func (m *{{ .Name.Maps }}) Close() error {
return {{ .Name.CloseHelper }}(
{{- range $id := .Maps }}
Expand Down
4 changes: 0 additions & 4 deletions cmd/bpf2go/gen/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ 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.
Expand Down
6 changes: 6 additions & 0 deletions cmd/bpf2go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,11 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) {
}
}

var vars []string
for name := range spec.Variables {
vars = append(vars, name)
}

var programs []string
for name := range spec.Programs {
programs = append(programs, name)
Expand Down Expand Up @@ -397,6 +402,7 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) {
Stem: b2g.identStem,
Constraints: constraints,
Maps: maps,
Variables: vars,
Programs: programs,
Types: types,
ObjectFile: filepath.Base(objFileName),
Expand Down
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ require (
github.com/go-quicktest/qt v1.101.0
github.com/google/go-cmp v0.6.0
github.com/jsimonetti/rtnetlink/v2 v2.0.1
github.com/stretchr/testify v1.9.0
golang.org/x/sys v0.20.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
github.com/josharian/native v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
Expand All @@ -17,5 +24,6 @@ require (
github.com/mdlayher/socket v0.4.1 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.18.0
)
16 changes: 14 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand All @@ -16,12 +18,22 @@ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 8486b44

Please sign in to comment.