diff --git a/btf/format.go b/btf/format.go index 5e581b4a8..6b07c3444 100644 --- a/btf/format.go +++ b/btf/format.go @@ -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. @@ -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 } @@ -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) diff --git a/cmd/bpf2go/gen/output.go b/cmd/bpf2go/gen/output.go index a054fd2f1..2ade96375 100644 --- a/cmd/bpf2go/gen/output.go +++ b/cmd/bpf2go/gen/output.go @@ -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))) } @@ -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" } @@ -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. @@ -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) @@ -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 @@ -157,6 +165,7 @@ func Generate(args GenerateArgs) error { args.Constraints, templateName(args.Stem), maps, + vars, programs, types, typeNames, diff --git a/cmd/bpf2go/gen/output.tpl b/cmd/bpf2go/gen/output.tpl index 8d8047066..d5e2c2e8a 100644 --- a/cmd/bpf2go/gen/output.tpl +++ b/cmd/bpf2go/gen/output.tpl @@ -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. @@ -80,6 +81,7 @@ type {{ .Name.MapSpecs }} struct { type {{ .Name.Objects }} struct { {{ .Name.Programs }} {{ .Name.Maps }} + {{ .Name.Variables }} } func (o *{{ .Name.Objects }}) Close() error { @@ -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 }} diff --git a/cmd/bpf2go/gen/types.go b/cmd/bpf2go/gen/types.go index 37dad0c76..2973c17a7 100644 --- a/cmd/bpf2go/gen/types.go +++ b/cmd/bpf2go/gen/types.go @@ -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. diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go index fb077e139..a5164ff5e 100644 --- a/cmd/bpf2go/main.go +++ b/cmd/bpf2go/main.go @@ -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) @@ -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), diff --git a/go.mod b/go.mod index aca8492f9..c3cb55371 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 ) diff --git a/go.sum b/go.sum index e0fee7777..c443c7e57 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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=