diff --git a/cmd/bpf2go/gen/output.go b/cmd/bpf2go/gen/output.go index 56ab312c7..6e4f8601c 100644 --- a/cmd/bpf2go/gen/output.go +++ b/cmd/bpf2go/gen/output.go @@ -79,6 +79,10 @@ func (n templateName) Programs() string { return string(n) + "Programs" } +func (n templateName) StructOps() string { + return string(n) + "StructOps" +} + func (n templateName) CloseHelper() string { return "_" + toUpperFirst(string(n)) + "Close" } @@ -104,6 +108,76 @@ type GenerateArgs struct { Output io.Writer // Function which transforms the input into a valid go identifier. Uses the default behaviour if nil Identifier func(string) string + // StructOps to be emitted + StructOps []StructOpsSpec +} + +type StructOpsSpec struct { + Name string + Type *btf.Struct +} + +// generateStructOpsShadowType produces a Go struct definition that mirrors the memory +// layout of a BTF struct, specifically for use with struct_ops maps. +func generateStructOpsShadowType(goTypeName string, st *btf.Struct, gf *btf.GoFormatter) (string, error) { + var sb strings.Builder + + sb.WriteString(fmt.Sprintf("// %s is a struct type for the struct_ops map.\n", goTypeName)) + sb.WriteString(fmt.Sprintf("type %s struct {\n", goTypeName)) + sb.WriteString("\t_ structs.HostLayout\n") + + prevOffset := uint32(0) + for _, m := range st.Members { + offset := m.Offset.Bytes() + if padding := offset - prevOffset; padding > 0 { + sb.WriteString(fmt.Sprintf("\t_ [%d]byte\n", padding)) + } + + fieldName := gf.Identifier(m.Name) + if fieldName == "" { + continue + } + + var fieldType string + underlying := btf.UnderlyingType(m.Type) + + if ptr, ok := btf.As[*btf.Pointer](underlying); ok { + if _, ok := btf.As[*btf.FuncProto](btf.UnderlyingType(ptr.Target)); ok { + fieldType = "*ebpf.Program" + } + } + + if fieldType == "" { + if _, ok := btf.As[*btf.Struct](underlying); ok { + size, _ := btf.Sizeof(m.Type) + fieldType = fmt.Sprintf("[%d]byte", size) + } else if _, ok := btf.As[*btf.Union](underlying); ok { + size, _ := btf.Sizeof(m.Type) + fieldType = fmt.Sprintf("[%d]byte", size) + } + } + + if fieldType == "" { + decl, err := gf.TypeDeclaration("T", m.Type) + if err != nil { + size, _ := btf.Sizeof(m.Type) + fieldType = fmt.Sprintf("[%d]byte", size) + } else { + fieldType = strings.TrimPrefix(decl, "type T ") + fieldType = strings.TrimRight(fieldType, " \n\t;") + } + } + + sb.WriteString(fmt.Sprintf("\t%s %s `ebpf:\"%s\"`\n", fieldName, fieldType, m.Name)) + size, err := btf.Sizeof(m.Type) + if err != nil { + return "", fmt.Errorf("field %s: %w", m.Name, err) + } + prevOffset = offset + uint32(size) + } + + sb.WriteString("}\n") + return sb.String(), nil } // Generate bindings for a BPF ELF file. @@ -149,6 +223,12 @@ func Generate(args GenerateArgs) error { typeNames[typ] = args.Stem + args.Identifier(typ.TypeName()) } + structOps := make(map[string]string) + for _, stOps := range args.StructOps { + goTypeName := "StructOps" + args.Identifier(stOps.Name) + structOps[stOps.Name] = goTypeName + } + // Ensure we don't have conflicting names and generate a sorted list of // named types so that the output is stable. types, err := sortTypes(typeNames) @@ -174,6 +254,16 @@ func Generate(args GenerateArgs) error { typeDecls = append(typeDecls, decl) } + for _, st := range args.StructOps { + goTypeName := args.Stem + "StructOps" + args.Identifier(st.Name) + decl, err := generateStructOpsShadowType(goTypeName, st.Type, gf) + if err != nil { + return err + } + typeDecls = append(typeDecls, decl) + needsStructsPkg = true + } + ctx := struct { Module string Package string @@ -185,6 +275,7 @@ func Generate(args GenerateArgs) error { TypeDeclarations []string File string NeedsStructsPkg bool + StructOps map[string]string }{ b2gInt.CurrentModule, args.Package, @@ -196,6 +287,7 @@ func Generate(args GenerateArgs) error { typeDecls, args.ObjectFile, needsStructsPkg, + structOps, } var buf bytes.Buffer diff --git a/cmd/bpf2go/gen/output.tpl b/cmd/bpf2go/gen/output.tpl index 350b0cab7..eff028e5b 100644 --- a/cmd/bpf2go/gen/output.tpl +++ b/cmd/bpf2go/gen/output.tpl @@ -94,6 +94,14 @@ type {{ .Name.Objects }} struct { {{ .Name.Programs }} {{ .Name.Maps }} {{ .Name.Variables }} + {{ .Name.StructOps }} +} + +// {{ .Name.StructOps }} contains all struct_ops types. +type {{ .Name.StructOps }} struct { +{{- range $name, $id := .StructOps }} + {{ $id }} {{ $.Name }}{{ $id }} `ebpf:"{{ $name }}"` +{{- end }} } func (o *{{ .Name.Objects }}) Close() error { diff --git a/cmd/bpf2go/gen/types.go b/cmd/bpf2go/gen/types.go index 93409e20d..b7e9d0f7b 100644 --- a/cmd/bpf2go/gen/types.go +++ b/cmd/bpf2go/gen/types.go @@ -29,6 +29,10 @@ func CollectGlobalTypes(spec *ebpf.CollectionSpec) []btf.Type { // collectMapTypes collects all types used by MapSpecs. func collectMapTypes(types []btf.Type, maps map[string]*ebpf.MapSpec) []btf.Type { for _, m := range maps { + if m.Type == ebpf.StructOpsMap { + continue + } + if m.Key != nil && m.Key.TypeName() != "" { types = addType(types, m.Key) } diff --git a/cmd/bpf2go/main.go b/cmd/bpf2go/main.go index a0da0838e..286136ca3 100644 --- a/cmd/bpf2go/main.go +++ b/cmd/bpf2go/main.go @@ -404,11 +404,19 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { } var maps []string + var structOps []gen.StructOpsSpec for name := range spec.Maps { // Skip .rodata, .data, .bss, etc. sections if !strings.HasPrefix(name, ".") { maps = append(maps, name) } + + ms := spec.Maps[name] + if ms.Type == ebpf.StructOpsMap { + userSt, _ := btf.As[*btf.Struct](ms.Value) + stOpsSpec := gen.StructOpsSpec{Name: name, Type: userSt} + structOps = append(structOps, stOpsSpec) + } } var variables []string @@ -448,6 +456,7 @@ func (b2g *bpf2go) convert(tgt gen.Target, goarches gen.GoArches) (err error) { Types: types, ObjectFile: filepath.Base(objFileName), Output: goFile, + StructOps: structOps, }) if err != nil { return fmt.Errorf("can't write %s: %s", goFileName, err) diff --git a/cmd/bpf2go/test/test_bpfeb.go b/cmd/bpf2go/test/test_bpfeb.go index 85885e88b..22415fda2 100644 --- a/cmd/bpf2go/test/test_bpfeb.go +++ b/cmd/bpf2go/test/test_bpfeb.go @@ -118,6 +118,11 @@ type testObjects struct { testPrograms testMaps testVariables + testStructOps +} + +// testStructOps contains all struct_ops types. +type testStructOps struct { } func (o *testObjects) Close() error { diff --git a/cmd/bpf2go/test/test_bpfel.go b/cmd/bpf2go/test/test_bpfel.go index 5034af12a..990ee0edf 100644 --- a/cmd/bpf2go/test/test_bpfel.go +++ b/cmd/bpf2go/test/test_bpfel.go @@ -118,6 +118,11 @@ type testObjects struct { testPrograms testMaps testVariables + testStructOps +} + +// testStructOps contains all struct_ops types. +type testStructOps struct { } func (o *testObjects) Close() error { diff --git a/collection.go b/collection.go index 3e5b05a47..542fd5467 100644 --- a/collection.go +++ b/collection.go @@ -116,7 +116,7 @@ func copyMapOfSpecs[T interface{ Copy() T }](m map[string]T) map[string]T { // Returns an error if any of the eBPF objects can't be found, or // if the same Spec is assigned multiple times. func (cs *CollectionSpec) Assign(to interface{}) error { - getValue := func(typ reflect.Type, name string) (interface{}, error) { + getValue := func(typ reflect.Type, name string, fieldVal reflect.Value) (interface{}, error) { switch typ { case reflect.TypeOf((*ProgramSpec)(nil)): if p := cs.Programs[name]; p != nil { @@ -183,7 +183,19 @@ func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) assignedProgs := make(map[string]bool) assignedVars := make(map[string]bool) - getValue := func(typ reflect.Type, name string) (interface{}, error) { + getValue := func(typ reflect.Type, name string, val reflect.Value) (interface{}, error) { + ms := cs.Maps[name] + if ms != nil && ms.Type == StructOpsMap { + vType := typ + if vType.Kind() == reflect.Ptr { + vType = vType.Elem() + } + + if vType.Kind() == reflect.Struct && typ != reflect.TypeOf((*Map)(nil)) { + return loader.loadStructOps(name, val) + } + } + switch typ { case reflect.TypeOf((*Program)(nil)): @@ -544,6 +556,38 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) { return v, nil } +// loadStructOps synchronizes a user-provided shadow struct with its MapSpec +// before the map is created in the kernel. +// +// If the field is already instantiated by the user, its values are patched +// into the MapSpec's raw data buffer. This allows setting initial flags, +// parameters, and function pointers before the kernel performs +// struct_ops validation. +func (cl *collectionLoader) loadStructOps(name string, field reflect.Value) (interface{}, error) { + ms := cl.coll.Maps[name] + if ms == nil { + return nil, fmt.Errorf("map %s: not found in loader", name) + } + + if field.IsValid() && field.Kind() == reflect.Struct { + userType, ok := btf.As[*btf.Struct](ms.Value) + if !ok { + return nil, fmt.Errorf("map %s: value type is not a Struct", name) + } + + rawData, ok := ms.Contents[0].Value.([]byte) + if !ok { + return nil, fmt.Errorf("map %s: spec contents are not a byte slice", name) + } + + if err := structOpsPatchValue(field, userType, rawData); err != nil { + return nil, fmt.Errorf("patching spec from field for %s: %q", name, err) + } + } + + return field.Interface(), nil +} + // populateDeferredMaps iterates maps holding programs or other maps and loads // any dependencies. Populates all maps in cl and freezes them if specified. func (cl *collectionLoader) populateDeferredMaps() error { @@ -845,7 +889,7 @@ func (coll *Collection) Assign(to interface{}) error { // Assign() only transfers already-loaded Maps and Programs. No extra // loading is done. - getValue := func(typ reflect.Type, name string) (interface{}, error) { + getValue := func(typ reflect.Type, name string, fieldVal reflect.Value) (interface{}, error) { switch typ { case reflect.TypeOf((*Program)(nil)): @@ -999,7 +1043,7 @@ func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) ([]struc // getValue is called for every tagged field of 'to' and must return the value // to be assigned to the field with the given typ and name. func assignValues(to interface{}, - getValue func(typ reflect.Type, name string) (interface{}, error)) error { + getValue func(typ reflect.Type, name string, val reflect.Value) (interface{}, error)) error { toValue := reflect.ValueOf(to) if toValue.Type().Kind() != reflect.Ptr { @@ -1037,7 +1081,7 @@ func assignValues(to interface{}, } // Get the eBPF object referred to by the tag. - value, err := getValue(field.Type, tag) + value, err := getValue(field.Type, tag, field.value) if err != nil { return fmt.Errorf("field %s: %w", field.Name, err) } diff --git a/collection_test.go b/collection_test.go index 398c2f96a..4af62e349 100644 --- a/collection_test.go +++ b/collection_test.go @@ -388,7 +388,7 @@ func TestNewCollectionFdLeak(t *testing.T) { } func TestAssignValues(t *testing.T) { - zero := func(t reflect.Type, name string) (interface{}, error) { + zero := func(t reflect.Type, name string, val reflect.Value) (interface{}, error) { return reflect.Zero(t).Interface(), nil } diff --git a/docs/examples/getting_started/counter_bpfeb.go b/docs/examples/getting_started/counter_bpfeb.go index 7fed9297c..2858aecbb 100644 --- a/docs/examples/getting_started/counter_bpfeb.go +++ b/docs/examples/getting_started/counter_bpfeb.go @@ -77,6 +77,11 @@ type counterObjects struct { counterPrograms counterMaps counterVariables + counterStructOps +} + +// counterStructOps contains all struct_ops types. +type counterStructOps struct { } func (o *counterObjects) Close() error { diff --git a/docs/examples/getting_started/counter_bpfel.go b/docs/examples/getting_started/counter_bpfel.go index 745d63920..5d85bfa4c 100644 --- a/docs/examples/getting_started/counter_bpfel.go +++ b/docs/examples/getting_started/counter_bpfel.go @@ -77,6 +77,11 @@ type counterObjects struct { counterPrograms counterMaps counterVariables + counterStructOps +} + +// counterStructOps contains all struct_ops types. +type counterStructOps struct { } func (o *counterObjects) Close() error { diff --git a/docs/examples/variables/variables_bpfeb.go b/docs/examples/variables/variables_bpfeb.go index 4fd793af9..8bfbda341 100644 --- a/docs/examples/variables/variables_bpfeb.go +++ b/docs/examples/variables/variables_bpfeb.go @@ -80,6 +80,11 @@ type variablesObjects struct { variablesPrograms variablesMaps variablesVariables + variablesStructOps +} + +// variablesStructOps contains all struct_ops types. +type variablesStructOps struct { } func (o *variablesObjects) Close() error { diff --git a/docs/examples/variables/variables_bpfel.go b/docs/examples/variables/variables_bpfel.go index 469e6f7ba..7fbd0f252 100644 --- a/docs/examples/variables/variables_bpfel.go +++ b/docs/examples/variables/variables_bpfel.go @@ -80,6 +80,11 @@ type variablesObjects struct { variablesPrograms variablesMaps variablesVariables + variablesStructOps +} + +// variablesStructOps contains all struct_ops types. +type variablesStructOps struct { } func (o *variablesObjects) Close() error { diff --git a/examples/cgroup_skb/bpf_bpfeb.go b/examples/cgroup_skb/bpf_bpfeb.go index d2b4b6fc9..861f7c278 100644 --- a/examples/cgroup_skb/bpf_bpfeb.go +++ b/examples/cgroup_skb/bpf_bpfeb.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/cgroup_skb/bpf_bpfel.go b/examples/cgroup_skb/bpf_bpfel.go index ace40f270..e4d9ebbb3 100644 --- a/examples/cgroup_skb/bpf_bpfel.go +++ b/examples/cgroup_skb/bpf_bpfel.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/fentry/bpf_bpfeb.go b/examples/fentry/bpf_bpfeb.go index db8b37b9b..39b1863d7 100644 --- a/examples/fentry/bpf_bpfeb.go +++ b/examples/fentry/bpf_bpfeb.go @@ -87,6 +87,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/fentry/bpf_bpfel.go b/examples/fentry/bpf_bpfel.go index 69a89fa07..c0f64690e 100644 --- a/examples/fentry/bpf_bpfel.go +++ b/examples/fentry/bpf_bpfel.go @@ -87,6 +87,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/kprobe/bpf_bpfeb.go b/examples/kprobe/bpf_bpfeb.go index f2c6ed870..fd460fdaa 100644 --- a/examples/kprobe/bpf_bpfeb.go +++ b/examples/kprobe/bpf_bpfeb.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/kprobe/bpf_bpfel.go b/examples/kprobe/bpf_bpfel.go index ec2bd068e..b1b3d8bc9 100644 --- a/examples/kprobe/bpf_bpfel.go +++ b/examples/kprobe/bpf_bpfel.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/kprobe_percpu/bpf_bpfeb.go b/examples/kprobe_percpu/bpf_bpfeb.go index f2c6ed870..fd460fdaa 100644 --- a/examples/kprobe_percpu/bpf_bpfeb.go +++ b/examples/kprobe_percpu/bpf_bpfeb.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/kprobe_percpu/bpf_bpfel.go b/examples/kprobe_percpu/bpf_bpfel.go index ec2bd068e..b1b3d8bc9 100644 --- a/examples/kprobe_percpu/bpf_bpfel.go +++ b/examples/kprobe_percpu/bpf_bpfel.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/kprobepin/bpf_bpfeb.go b/examples/kprobepin/bpf_bpfeb.go index f2c6ed870..fd460fdaa 100644 --- a/examples/kprobepin/bpf_bpfeb.go +++ b/examples/kprobepin/bpf_bpfeb.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/kprobepin/bpf_bpfel.go b/examples/kprobepin/bpf_bpfel.go index ec2bd068e..b1b3d8bc9 100644 --- a/examples/kprobepin/bpf_bpfel.go +++ b/examples/kprobepin/bpf_bpfel.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/ringbuffer/bpf_bpfeb.go b/examples/ringbuffer/bpf_bpfeb.go index 8ef32847a..fee24f65e 100644 --- a/examples/ringbuffer/bpf_bpfeb.go +++ b/examples/ringbuffer/bpf_bpfeb.go @@ -84,6 +84,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/ringbuffer/bpf_bpfel.go b/examples/ringbuffer/bpf_bpfel.go index fbb1c4aba..3ad47d40a 100644 --- a/examples/ringbuffer/bpf_bpfel.go +++ b/examples/ringbuffer/bpf_bpfel.go @@ -84,6 +84,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/sched_ext/bpf_bpfeb.go b/examples/sched_ext/bpf_bpfeb.go index b327f8b7b..a04e362ec 100644 --- a/examples/sched_ext/bpf_bpfeb.go +++ b/examples/sched_ext/bpf_bpfeb.go @@ -8,10 +8,23 @@ import ( _ "embed" "fmt" "io" + "structs" "github.com/cilium/ebpf" ) +// bpfStructOpsMinimalSched is a struct type for the struct_ops map. +type bpfStructOpsMinimalSched struct { + _ structs.HostLayout + Init *ebpf.Program `ebpf:"init"` + Flags uint64 `ebpf:"flags"` + TimeoutMs uint32 `ebpf:"timeout_ms"` + _ [4]byte + Inner [16]byte `ebpf:"inner"` + Test1 [8]byte `ebpf:"test_1"` + Name [128]int8 `ebpf:"name"` +} + // loadBpf returns the embedded CollectionSpec for bpf. func loadBpf() (*ebpf.CollectionSpec, error) { reader := bytes.NewReader(_BpfBytes) @@ -76,6 +89,12 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { + StructOpsMinimalSched bpfStructOpsMinimalSched `ebpf:"minimal_sched"` } func (o *bpfObjects) Close() error { diff --git a/examples/sched_ext/bpf_bpfeb.o b/examples/sched_ext/bpf_bpfeb.o index 07b7c9f0e..a487371b4 100644 Binary files a/examples/sched_ext/bpf_bpfeb.o and b/examples/sched_ext/bpf_bpfeb.o differ diff --git a/examples/sched_ext/bpf_bpfel.go b/examples/sched_ext/bpf_bpfel.go index 6a5d9e4ed..c53409516 100644 --- a/examples/sched_ext/bpf_bpfel.go +++ b/examples/sched_ext/bpf_bpfel.go @@ -8,10 +8,23 @@ import ( _ "embed" "fmt" "io" + "structs" "github.com/cilium/ebpf" ) +// bpfStructOpsMinimalSched is a struct type for the struct_ops map. +type bpfStructOpsMinimalSched struct { + _ structs.HostLayout + Init *ebpf.Program `ebpf:"init"` + Flags uint64 `ebpf:"flags"` + TimeoutMs uint32 `ebpf:"timeout_ms"` + _ [4]byte + Inner [16]byte `ebpf:"inner"` + Test1 [8]byte `ebpf:"test_1"` + Name [128]int8 `ebpf:"name"` +} + // loadBpf returns the embedded CollectionSpec for bpf. func loadBpf() (*ebpf.CollectionSpec, error) { reader := bytes.NewReader(_BpfBytes) @@ -76,6 +89,12 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { + StructOpsMinimalSched bpfStructOpsMinimalSched `ebpf:"minimal_sched"` } func (o *bpfObjects) Close() error { diff --git a/examples/sched_ext/bpf_bpfel.o b/examples/sched_ext/bpf_bpfel.o index d018a4ec5..8fddf7a0d 100644 Binary files a/examples/sched_ext/bpf_bpfel.o and b/examples/sched_ext/bpf_bpfel.o differ diff --git a/examples/sched_ext/main.go b/examples/sched_ext/main.go index 7f3af4731..e13ac62bc 100644 --- a/examples/sched_ext/main.go +++ b/examples/sched_ext/main.go @@ -12,7 +12,9 @@ import ( "github.com/cilium/ebpf/rlimit" ) -//go:generate go tool bpf2go -no-global-types -tags linux bpf sched_ext.c -- -I../headers/ +const ScxOpsSwitchPartial = 1 << 3 + +//go:generate go tool bpf2go -tags linux bpf sched_ext.c -- -I../headers/ // Load a minimal defining sched_ext_ops map // @@ -28,6 +30,8 @@ func main() { } objs := bpfObjects{} + objs.bpfStructOps.StructOpsMinimalSched.Flags = ScxOpsSwitchPartial + objs.bpfStructOps.StructOpsMinimalSched.TimeoutMs = 1000 if err := loadBpfObjects(&objs, nil); err != nil { log.Fatalf("loading objects: %v", err) } diff --git a/examples/sched_ext/sched_ext.c b/examples/sched_ext/sched_ext.c index 7ab5a7815..e09fbfded 100644 --- a/examples/sched_ext/sched_ext.c +++ b/examples/sched_ext/sched_ext.c @@ -5,11 +5,25 @@ char __license[] SEC("license") = "Dual MIT/GPL"; +struct list_head { + struct list_head *next, *prev; +}; + +struct test_struct { + u64 dummy; +}; + struct sched_ext_ops { + s32 (*init)(); + u64 flags; + u32 timeout_ms; + struct list_head inner; + struct test_struct test_1; char name[128]; }; SEC(".struct_ops.link") struct sched_ext_ops minimal_sched = { - .name = "minimal", + .name = "minimal", + .timeout_ms = 5000, }; diff --git a/examples/tcprtt/bpf_bpfeb.go b/examples/tcprtt/bpf_bpfeb.go index 95f71bd2d..9d45da9b0 100644 --- a/examples/tcprtt/bpf_bpfeb.go +++ b/examples/tcprtt/bpf_bpfeb.go @@ -87,6 +87,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/tcprtt/bpf_bpfel.go b/examples/tcprtt/bpf_bpfel.go index 3112677ba..d83761d11 100644 --- a/examples/tcprtt/bpf_bpfel.go +++ b/examples/tcprtt/bpf_bpfel.go @@ -87,6 +87,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/tcprtt_sockops/bpf_bpfeb.go b/examples/tcprtt_sockops/bpf_bpfeb.go index 361a967ee..8a3a53268 100644 --- a/examples/tcprtt_sockops/bpf_bpfeb.go +++ b/examples/tcprtt_sockops/bpf_bpfeb.go @@ -103,6 +103,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/tcprtt_sockops/bpf_bpfel.go b/examples/tcprtt_sockops/bpf_bpfel.go index d5e9d8614..db2a54984 100644 --- a/examples/tcprtt_sockops/bpf_bpfel.go +++ b/examples/tcprtt_sockops/bpf_bpfel.go @@ -103,6 +103,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/tcx/bpf_bpfeb.go b/examples/tcx/bpf_bpfeb.go index 45f404510..311e89950 100644 --- a/examples/tcx/bpf_bpfeb.go +++ b/examples/tcx/bpf_bpfeb.go @@ -79,6 +79,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/tcx/bpf_bpfel.go b/examples/tcx/bpf_bpfel.go index 09261fb24..b3b1b9117 100644 --- a/examples/tcx/bpf_bpfel.go +++ b/examples/tcx/bpf_bpfel.go @@ -79,6 +79,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/tracepoint_in_c/bpf_bpfeb.go b/examples/tracepoint_in_c/bpf_bpfeb.go index 2129bc735..ece15b6d6 100644 --- a/examples/tracepoint_in_c/bpf_bpfeb.go +++ b/examples/tracepoint_in_c/bpf_bpfeb.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/tracepoint_in_c/bpf_bpfel.go b/examples/tracepoint_in_c/bpf_bpfel.go index b461b4d10..b29860068 100644 --- a/examples/tracepoint_in_c/bpf_bpfel.go +++ b/examples/tracepoint_in_c/bpf_bpfel.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/uretprobe/bpf_x86_bpfel.go b/examples/uretprobe/bpf_x86_bpfel.go index cbdd704f2..b560ad5b5 100644 --- a/examples/uretprobe/bpf_x86_bpfel.go +++ b/examples/uretprobe/bpf_x86_bpfel.go @@ -84,6 +84,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/xdp/bpf_bpfeb.go b/examples/xdp/bpf_bpfeb.go index 9b5420dd9..627678a8c 100644 --- a/examples/xdp/bpf_bpfeb.go +++ b/examples/xdp/bpf_bpfeb.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/xdp/bpf_bpfel.go b/examples/xdp/bpf_bpfel.go index 21bf4d3e0..b976523aa 100644 --- a/examples/xdp/bpf_bpfel.go +++ b/examples/xdp/bpf_bpfel.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/xdp_live_frame/bpf_bpfeb.go b/examples/xdp_live_frame/bpf_bpfeb.go index a826a6255..3b02b352a 100644 --- a/examples/xdp_live_frame/bpf_bpfeb.go +++ b/examples/xdp_live_frame/bpf_bpfeb.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/examples/xdp_live_frame/bpf_bpfel.go b/examples/xdp_live_frame/bpf_bpfel.go index 207192648..1ecef87d2 100644 --- a/examples/xdp_live_frame/bpf_bpfel.go +++ b/examples/xdp_live_frame/bpf_bpfel.go @@ -77,6 +77,11 @@ type bpfObjects struct { bpfPrograms bpfMaps bpfVariables + bpfStructOps +} + +// bpfStructOps contains all struct_ops types. +type bpfStructOps struct { } func (o *bpfObjects) Close() error { diff --git a/struct_ops.go b/struct_ops.go index 3b70d56d2..c5c6a0b03 100644 --- a/struct_ops.go +++ b/struct_ops.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "strings" + "unsafe" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/internal" @@ -107,6 +108,7 @@ func structOpsCopyMember(m, km btf.Member, data []byte, kernVData []byte) error dstOff := int(km.Offset.Bytes()) if srcOff < 0 || srcOff+mSize > len(data) { + fmt.Println(srcOff, srcOff+mSize, len(data)) return fmt.Errorf("member %q: userdata is too small", m.Name) } @@ -145,16 +147,54 @@ func structOpsIsMemZeroed(data []byte) bool { return true } +// structOpsCopyMemberFromStructValue updates a single member in the ELF section data +// using a value from a generated Go struct. +// +// It implements a "partial override" strategy: if the Go field is a zero value, +// the operation is skipped to preserve the original default value defined in the ELF. +// Currently, nested structs or unions are not supported for non-zero updates to +// avoid complex recursive patching. +func structOpsCopyMemberFromStructValue(fieldVal reflect.Value, m btf.Member, dstOff int, secData []byte) error { + kSize, err := btf.Sizeof(m.Type) + if err != nil { + return fmt.Errorf("btf sizeof: %w", err) + } + + srcSize := int(fieldVal.Type().Size()) + if srcSize != kSize { + return fmt.Errorf("size mismatch (Go:%d, Kernel:%d)", srcSize, kSize) + } + + if dstOff+kSize > len(secData) { + return fmt.Errorf("kernel buffer overflow: dstOff: %v kSize: %v, kernVdata: %v", dstOff, kSize, len(secData)) + } + + srcPtr := unsafe.Pointer(fieldVal.UnsafeAddr()) + srcData := unsafe.Slice((*byte)(srcPtr), kSize) + + if structOpsIsMemZeroed(srcData) { + return nil + } + + underlying := btf.UnderlyingType(m.Type) + switch underlying.(type) { + case *btf.Struct, *btf.Union: + if !structOpsIsMemZeroed(srcData) { + return fmt.Errorf("non-zero nested struct is not supported") + } + return nil + } + + copy(secData[dstOff:dstOff+kSize], srcData) + return nil +} + // structOpsSetAttachTo sets p.AttachTo in the expected "struct_name:memberName" format // based on the struct definition. // // this relies on the assumption that each member in the // `.struct_ops` section has a relocation at its starting byte offset. -func structOpsSetAttachTo( - sec *elfSection, - baseOff uint32, - userSt *btf.Struct, - progs map[string]*ProgramSpec) error { +func structOpsSetAttachTo(sec *elfSection, baseOff uint32, userSt *btf.Struct, progs map[string]*ProgramSpec) error { for _, m := range userSt.Members { memberOff := m.Offset sym, ok := sec.relocations[uint64(baseOff+memberOff.Bytes())] @@ -173,3 +213,52 @@ func structOpsSetAttachTo( } return nil } + +// structOpsPatchValue synchronizes the values of a Go shadow struct into the +// underlying ELF section data before the map is created. +// +// It uses "ebpf" tags to map Go fields to their corresponding BTF members. +// Function pointers (*Program) are handled separately to perform early +// relocation by injecting program FDs into the section buffer. +func structOpsPatchValue(v reflect.Value, userType *btf.Struct, secData []byte) error { + t := v.Type() + + for i := range t.NumField() { + f := t.Field(i) + tag := f.Tag.Get("ebpf") + if tag == "" { + continue + } + + m, err := structOpsFindMemberByName(userType, tag) + if err != nil { + continue + } + + dstOff := int(m.Offset.Bytes()) + fieldVal := v.Field(i) + + if prog, ok := fieldVal.Interface().(*Program); ok { + if prog != nil { + if err := structOpsPopulateValue(*m, secData, prog); err != nil { + return fmt.Errorf("member %s: %w", tag, err) + } + } + continue + } + + if err := structOpsCopyMemberFromStructValue(fieldVal, *m, dstOff, secData); err != nil { + return fmt.Errorf("member %s: %w", tag, err) + } + } + return nil +} + +func structOpsFindMemberByName(st *btf.Struct, name string) (*btf.Member, error) { + for _, m := range st.Members { + if m.Name == name { + return &m, nil + } + } + return nil, fmt.Errorf("member %q not found", name) +}