Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] - wrappers for type-safe ebpf.Variable #1543

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 98 additions & 17 deletions btf/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,45 @@ import (
"errors"
"fmt"
"strings"
"text/template"

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

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

var tplVars = `

type {{ .Name }} ebpf.Variable

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

{{- if not .ReadOnly }}
func(v *{{ .Name }}) Set(val {{ .Type }}) error {
return (*ebpf.Variable)(v).Set(val)
}

{{ if .CanAtomic }}
func(v *{{ .Name }}) AtomicRef() *ebpf.{{ .CapitalizedType }} {
ret, _ := (*ebpf.Variable)(v).Atomic{{ .CapitalizedType }}()
return ret
}
{{ end }}
{{- end }}
`

type TplVarsData struct {
Name string
Type string
CapitalizedType string
ReadOnly bool
CanAtomic bool
}

// GoFormatter converts a Type to Go syntax.
//
// A zero GoFormatter is valid to use.
Expand Down Expand Up @@ -64,7 +99,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 +335,82 @@ 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)
}

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)
va := getSuppAtomicType(v.Type)
tplArgs := TplVarsData{
Name: id,
Type: id + "Type",
CapitalizedType: cases.Title(language.Und, cases.NoLower).String(va),
ReadOnly: strings.HasPrefix(ds.Name, ".ro"),
CanAtomic: va != "",
}

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)
}

gf.w.WriteString("; ")
if err := tmpl.Execute(&gf.w, tplArgs); err != nil {
return fmt.Errorf("failed to execute template for variable %s: %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
Loading
Loading