-
Notifications
You must be signed in to change notification settings - Fork 848
[POC] bpf2go/struct_ops: implement StructOps support #1973
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
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 }} | ||
|
Comment on lines
+100
to
+104
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would consider surrounding this with an |
||
| } | ||
|
|
||
| func (o *{{ .Name.Objects }}) Close() error { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+187
to
+197
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code should go into |
||||||||||||||
|
|
||||||||||||||
| 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 { | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you invert this there is far less code indented.
Suggested change
|
||||||||||||||
| 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) | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||||||||||||||
| 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) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic of emitting a
*ebpf.Programfor pointers to functions seems to be the only reason for having this logic here. I think it would be better to adjust theGoFormatterin btf/format.go to do this instead, so you can reuse the rest of the existing logic for writing go types.