From fbafa1810aa805016dbe44e6d83f39343f65ecdb Mon Sep 17 00:00:00 2001 From: shun159 Date: Sat, 28 Mar 2026 18:30:04 +0900 Subject: [PATCH 1/4] bpf2go/struct_ops: implement StructOps support Implement code generation and library support for declarative initialization of struct_ops maps. This allows users to initialize struct_ops member, using standard Go structs during the LoadAndAssign process. bpf2go changes: - Scans for ebpf.StructOpsMap in the CollectionSpec and generates corresponding Go struct types. - Automatically maps kernel function pointers to *ebpf.Program and scalar members to appropriate Go types. Library changes: - Modify assignValues and LoadAndAssign to pass the reflect.Value of target fields, enabling the library to read pre-instantiated data from the user-provided objects struct. - Introduce loadStructOps to synchronize Go struct fields with the MapSpec before the map is created in the kernel. - Implement a "partial override" strategy in structOpsPatchValue: only non-zero fields in the Go struct are patched into the ELF section data, preserving ELF-defined defaults for uninitialized fields. - Support early relocation of struct_ops function pointers by injecting ebpf.Program FDs into the section data buffer during the patch process. Ref: https://github.com/cilium/ebpf/issues/1957 Signed-off-by: shun159 --- cmd/bpf2go/gen/output.go | 78 +++++++++++++++++++++++++ cmd/bpf2go/gen/output.tpl | 8 +++ cmd/bpf2go/gen/types.go | 4 ++ cmd/bpf2go/main.go | 9 +++ collection.go | 54 +++++++++++++++-- collection_test.go | 2 +- examples/sched_ext/bpf_bpfeb.go | 16 ++++++ examples/sched_ext/bpf_bpfeb.o | Bin 1288 -> 1592 bytes examples/sched_ext/bpf_bpfel.go | 16 ++++++ examples/sched_ext/bpf_bpfel.o | Bin 1288 -> 1592 bytes examples/sched_ext/main.go | 6 +- examples/sched_ext/sched_ext.c | 8 ++- struct_ops.go | 99 ++++++++++++++++++++++++++++++-- 13 files changed, 286 insertions(+), 14 deletions(-) diff --git a/cmd/bpf2go/gen/output.go b/cmd/bpf2go/gen/output.go index 56ab312c7..de88c02fa 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,62 @@ 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(fmt.Sprintf("\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 + if ptr, ok := btf.As[*btf.Pointer](btf.UnderlyingType(m.Type)); ok { + if _, ok := btf.As[*btf.FuncProto](btf.UnderlyingType(ptr.Target)); ok { + fieldType = "*ebpf.Program" + } + } + + if fieldType == "" { + decl, err := gf.TypeDeclaration("T", m.Type) + if err != nil { + return "", fmt.Errorf("field %s: %w", m.Name, err) + } + fieldType = strings.TrimPrefix(decl, "type T ") + fieldType = strings.Split(fieldType, ";")[0] + } + + 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 +209,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 +240,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 +261,7 @@ func Generate(args GenerateArgs) error { TypeDeclarations []string File string NeedsStructsPkg bool + StructOps map[string]string }{ b2gInt.CurrentModule, args.Package, @@ -196,6 +273,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/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/examples/sched_ext/bpf_bpfeb.go b/examples/sched_ext/bpf_bpfeb.go index b327f8b7b..d3599b4aa 100644 --- a/examples/sched_ext/bpf_bpfeb.go +++ b/examples/sched_ext/bpf_bpfeb.go @@ -8,10 +8,20 @@ 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"` + Name [128]int8 `ebpf:"name"` +} + // loadBpf returns the embedded CollectionSpec for bpf. func loadBpf() (*ebpf.CollectionSpec, error) { reader := bytes.NewReader(_BpfBytes) @@ -76,6 +86,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 07b7c9f0eec5ed8cbf78be84e6c8064ffd1d321a..f5040daa4f85a87ecf46dcd746ce468c37fc597b 100644 GIT binary patch delta 523 zcmX|;u}i~H5XLVrPi6v1jVJOlY>I67;B(Sg(T2HXim_yUi9{b4E2f0-R9X-p#Uur)EfAzNz-Uei{q`@ODd*F`00x<%(|Aa*b6}Uw- zlcwY*Ue&F!edPIGNN3G@BcRZ0x$P))S^@d>mP^4(nH-1J^FxX%tEgBbKkyp9dulh^ zej^czlB2BuLp-Ctjji}XsyVS#$k{U|;7qVbVZvX%Q delta 217 zcmdnN)4?@CkBRBTL<3p&+|0bp+{B!Th2;|)SSP;V<6_vszyQW^lNkL48G)PzAT|PG zHXvr0EWjwP+W=&k^MSYw46Hy5Qsv4Ip+REoAOZ*)fH-V&0+Sm{USe+QWF}_U$==L5 zk|1Fa*Z{;#K+FQ-0KtjL6PS-NF=b7TVA;>&3lx}qfK_C2J`2xeYgQS~3t%-2jCzw3 aSk0K2JSNXzwPRw+n0y07F-(?VQvd+=IwrCJ diff --git a/examples/sched_ext/bpf_bpfel.go b/examples/sched_ext/bpf_bpfel.go index 6a5d9e4ed..009ae4811 100644 --- a/examples/sched_ext/bpf_bpfel.go +++ b/examples/sched_ext/bpf_bpfel.go @@ -8,10 +8,20 @@ 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"` + Name [128]int8 `ebpf:"name"` +} + // loadBpf returns the embedded CollectionSpec for bpf. func loadBpf() (*ebpf.CollectionSpec, error) { reader := bytes.NewReader(_BpfBytes) @@ -76,6 +86,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 d018a4ec5d866c7bdaa108f79282db9d5c94aeb4..1f7d863ffec2182b2c69466508b7d8098812a94b 100644 GIT binary patch delta 526 zcmX|-y-EW?6ov23th$Lw{7Iu$K|w<*i4rR%q*74SN>E`*a19H|M#wIt5E3LstaO9xj04sNm;`082{NDuw!sW|0I|eG9563!jaK1%I$H_&VVsljBb^Q0 zOF5#haiSNj6x@z;8t&+9@X#{o=-!%wU&eVFex>thpzeqy2RteYJ@^fPGGi#bejj<3 zcplAif%s26scU2B)b(7S&KuQwn|!zFv;yC5w#lnD9cr%?$+o$=o=-t(9Ua%eYrA#N zIm@k delta 222 zcmdnN)4?@CgXsj*L@j0Z+|0bp+{B!Th2;|)SSP;V;oQN%zyQK=6aV^)FfuT(Gypk9 zK+FcjATU{gQDm|K6N{!fgwMwcq=5<na+0#rngadH%^ Z8IuRo 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) +} From 067c0daafadb50939f73856bee9883c4b6723123 Mon Sep 17 00:00:00 2001 From: shun159 Date: Sun, 29 Mar 2026 18:27:34 +0900 Subject: [PATCH 2/4] bpf2go: remove unnecessary fmt.Sprint call Signed-off-by: shun159 --- cmd/bpf2go/gen/output.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/bpf2go/gen/output.go b/cmd/bpf2go/gen/output.go index de88c02fa..e0f92cd44 100644 --- a/cmd/bpf2go/gen/output.go +++ b/cmd/bpf2go/gen/output.go @@ -124,7 +124,7 @@ func generateStructOpsShadowType(goTypeName string, st *btf.Struct, gf *btf.GoFo 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(fmt.Sprintf("\t_ structs.HostLayout\n")) + sb.WriteString("\t_ structs.HostLayout\n") prevOffset := uint32(0) for _, m := range st.Members { From 60b3842b1473469115e949670ad434e21d061205 Mon Sep 17 00:00:00 2001 From: shun159 Date: Sun, 29 Mar 2026 18:38:10 +0900 Subject: [PATCH 3/4] run make to format code Signed-off-by: shun159 --- cmd/bpf2go/test/test_bpfeb.go | 5 +++++ cmd/bpf2go/test/test_bpfel.go | 5 +++++ docs/examples/getting_started/counter_bpfeb.go | 5 +++++ docs/examples/getting_started/counter_bpfel.go | 5 +++++ docs/examples/variables/variables_bpfeb.go | 5 +++++ docs/examples/variables/variables_bpfel.go | 5 +++++ examples/cgroup_skb/bpf_bpfeb.go | 5 +++++ examples/cgroup_skb/bpf_bpfel.go | 5 +++++ examples/fentry/bpf_bpfeb.go | 5 +++++ examples/fentry/bpf_bpfel.go | 5 +++++ examples/kprobe/bpf_bpfeb.go | 5 +++++ examples/kprobe/bpf_bpfel.go | 5 +++++ examples/kprobe_percpu/bpf_bpfeb.go | 5 +++++ examples/kprobe_percpu/bpf_bpfel.go | 5 +++++ examples/kprobepin/bpf_bpfeb.go | 5 +++++ examples/kprobepin/bpf_bpfel.go | 5 +++++ examples/ringbuffer/bpf_bpfeb.go | 5 +++++ examples/ringbuffer/bpf_bpfel.go | 5 +++++ examples/sched_ext/sched_ext.c | 12 ++++++------ examples/tcprtt/bpf_bpfeb.go | 5 +++++ examples/tcprtt/bpf_bpfel.go | 5 +++++ examples/tcprtt_sockops/bpf_bpfeb.go | 5 +++++ examples/tcprtt_sockops/bpf_bpfel.go | 5 +++++ examples/tcx/bpf_bpfeb.go | 5 +++++ examples/tcx/bpf_bpfel.go | 5 +++++ examples/tracepoint_in_c/bpf_bpfeb.go | 5 +++++ examples/tracepoint_in_c/bpf_bpfel.go | 5 +++++ examples/uretprobe/bpf_x86_bpfel.go | 5 +++++ examples/xdp/bpf_bpfeb.go | 5 +++++ examples/xdp/bpf_bpfel.go | 5 +++++ examples/xdp_live_frame/bpf_bpfeb.go | 5 +++++ examples/xdp_live_frame/bpf_bpfel.go | 5 +++++ 32 files changed, 161 insertions(+), 6 deletions(-) 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/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/sched_ext.c b/examples/sched_ext/sched_ext.c index 9255d524f..b295bd252 100644 --- a/examples/sched_ext/sched_ext.c +++ b/examples/sched_ext/sched_ext.c @@ -6,14 +6,14 @@ char __license[] SEC("license") = "Dual MIT/GPL"; struct sched_ext_ops { - s32 (*init)(); - u64 flags; - u32 timeout_ms; - char name[128]; + s32 (*init)(); + u64 flags; + u32 timeout_ms; + char name[128]; }; SEC(".struct_ops.link") struct sched_ext_ops minimal_sched = { - .name = "minimal", - .timeout_ms = 5000, + .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 { From a1281197bed96e89a6e6bfa1f0a806314627860a Mon Sep 17 00:00:00 2001 From: shun159 Date: Mon, 30 Mar 2026 00:52:46 +0900 Subject: [PATCH 4/4] cmd/bpf2go: represent nested structs in struct_ops as bytes Signed-off-by: shun159 --- cmd/bpf2go/gen/output.go | 22 ++++++++++++++++++---- examples/sched_ext/bpf_bpfeb.go | 5 ++++- examples/sched_ext/bpf_bpfeb.o | Bin 1592 -> 1760 bytes examples/sched_ext/bpf_bpfel.go | 5 ++++- examples/sched_ext/bpf_bpfel.o | Bin 1592 -> 1760 bytes examples/sched_ext/sched_ext.c | 10 ++++++++++ 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/cmd/bpf2go/gen/output.go b/cmd/bpf2go/gen/output.go index e0f92cd44..6e4f8601c 100644 --- a/cmd/bpf2go/gen/output.go +++ b/cmd/bpf2go/gen/output.go @@ -139,19 +139,33 @@ func generateStructOpsShadowType(goTypeName string, st *btf.Struct, gf *btf.GoFo } var fieldType string - if ptr, ok := btf.As[*btf.Pointer](btf.UnderlyingType(m.Type)); ok { + 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 { - return "", fmt.Errorf("field %s: %w", m.Name, err) + size, _ := btf.Sizeof(m.Type) + fieldType = fmt.Sprintf("[%d]byte", size) + } else { + fieldType = strings.TrimPrefix(decl, "type T ") + fieldType = strings.TrimRight(fieldType, " \n\t;") } - fieldType = strings.TrimPrefix(decl, "type T ") - fieldType = strings.Split(fieldType, ";")[0] } sb.WriteString(fmt.Sprintf("\t%s %s `ebpf:\"%s\"`\n", fieldName, fieldType, m.Name)) diff --git a/examples/sched_ext/bpf_bpfeb.go b/examples/sched_ext/bpf_bpfeb.go index d3599b4aa..a04e362ec 100644 --- a/examples/sched_ext/bpf_bpfeb.go +++ b/examples/sched_ext/bpf_bpfeb.go @@ -19,7 +19,10 @@ type bpfStructOpsMinimalSched struct { Init *ebpf.Program `ebpf:"init"` Flags uint64 `ebpf:"flags"` TimeoutMs uint32 `ebpf:"timeout_ms"` - Name [128]int8 `ebpf:"name"` + _ [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. diff --git a/examples/sched_ext/bpf_bpfeb.o b/examples/sched_ext/bpf_bpfeb.o index f5040daa4f85a87ecf46dcd746ce468c37fc597b..a487371b44dd4c66910feb6be8aa1c6c75de8139 100644 GIT binary patch delta 464 zcmXwzy-UMD7>D1xUTUkYwtk=mhk&^G3DU6!2N48a6huT@Y@mTA+BEfJvV#szHs0dk z;N;?xQP4pgba8M{=%^rq|AoK9^Mxn(-2L9XcekmJbZdf(t8i*2tPOX#j_0_gYFOJ3 zzq83VA_nN^oGlx1NF-O-WbOgB1o{wLSXV(myeW^_$kBQNTT~KkYVU-XnK~sBSWT=I zou%Ma?P<8L`_z#IPIT4-Kh>UrpZ(8+o8VFqaiRjL@EY_2RX0Gn1yqNsEQs)fnxSUi zgDyZa!v@EjkjHMdB{G2Odd2|PCPDQw2!SMkUx#bgIIJ0|8ClN;FV84V^MU{e49^7JgU diff --git a/examples/sched_ext/bpf_bpfel.go b/examples/sched_ext/bpf_bpfel.go index 009ae4811..c53409516 100644 --- a/examples/sched_ext/bpf_bpfel.go +++ b/examples/sched_ext/bpf_bpfel.go @@ -19,7 +19,10 @@ type bpfStructOpsMinimalSched struct { Init *ebpf.Program `ebpf:"init"` Flags uint64 `ebpf:"flags"` TimeoutMs uint32 `ebpf:"timeout_ms"` - Name [128]int8 `ebpf:"name"` + _ [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. diff --git a/examples/sched_ext/bpf_bpfel.o b/examples/sched_ext/bpf_bpfel.o index 1f7d863ffec2182b2c69466508b7d8098812a94b..8fddf7a0dc940b9906284e4905d3b33b32d86435 100644 GIT binary patch delta 471 zcmXw#y-Nc@5XEOVdwS6%CK`)~MG(YjqgDZnaB7oEQLs?Z5X?aiazQT_KPpAANTuht z7FISkDFrRWLK_Pa1xp1H{38V4X3v2)zcJGjQKZdMm5}o#m0_ju;n~?)ZI{rrBo72m jlNs5}nJSnkTd~=(&R_