Skip to content

Commit fb6c1a7

Browse files
committed
struct_ops: add structOpsMeta to carry BTF hints for StructOpsMap creation
1. Verify creating a StructOpsMap from a hand-crafted MapSpec with metadata. 2. Resolve attr.BtfVmlinuxValueTypeId from vmlinux BTF before MapCreate. 3. Value population is deferred to follow-up PRs. see: #1502 Signed-off-by: shun159 <[email protected]>
1 parent f87d8e6 commit fb6c1a7

File tree

4 files changed

+148
-0
lines changed

4 files changed

+148
-0
lines changed

helpers_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,36 @@ func requireTestmod(tb testing.TB) {
4444
}
4545
}
4646

47+
var haveStructOpsDummy = sync.OnceValues(func() (bool, error) {
48+
if platform.IsWindows {
49+
return false, nil
50+
}
51+
kspec, err := btf.LoadKernelSpec()
52+
if err != nil {
53+
return false, nil
54+
}
55+
_, err = kspec.AnyTypeByName("bpf_struct_ops_bpf_dummy_ops")
56+
if err == nil {
57+
return true, nil
58+
}
59+
if errors.Is(err, btf.ErrNotFound) {
60+
return false, nil
61+
}
62+
return false, err
63+
})
64+
65+
func requireStructOpsDummy(tb testing.TB) {
66+
tb.Helper()
67+
68+
ok, err := haveStructOpsDummy()
69+
if err != nil {
70+
tb.Fatal(err)
71+
}
72+
if !ok {
73+
tb.Skip("struct_ops dummy_ops wrapper type not present in vmlinux BTF")
74+
}
75+
}
76+
4777
func newMap(tb testing.TB, spec *MapSpec, opts *MapOptions) (*Map, error) {
4878
tb.Helper()
4979

map.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,47 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) {
556556
}
557557
}
558558

559+
if spec.Type == StructOpsMap {
560+
meta, err := extractStructOpsMeta(spec.Contents)
561+
if err != nil {
562+
return nil, err
563+
}
564+
565+
// we need drop meta entry here
566+
if len(spec.Contents) > 0 {
567+
spec.Contents = spec.Contents[1:]
568+
}
569+
570+
var b btf.Builder
571+
h, err := btf.NewHandle(&b)
572+
if err != nil {
573+
return nil, err
574+
}
575+
defer h.Close()
576+
577+
s, err := btf.LoadKernelSpec()
578+
if err != nil {
579+
return nil, fmt.Errorf("open vmlinux BTF: %w", err)
580+
}
581+
582+
typeSpec, err := s.AnyTypeByName(meta.kernTypeName)
583+
if errors.Is(err, btf.ErrNotFound) {
584+
return nil, fmt.Errorf("struct_ops kernel type %q: %w", meta.kernTypeName, ErrNotSupported)
585+
}
586+
if err != nil {
587+
return nil, fmt.Errorf("lookup kernel type %q: %w", meta.kernTypeName, err)
588+
}
589+
590+
btfValueTypeId, err := s.TypeID(typeSpec)
591+
if err != nil {
592+
return nil, fmt.Errorf("lookup type_id of %s: %w", typeSpec.TypeName(), err)
593+
}
594+
595+
attr.ValueSize = spec.ValueSize
596+
attr.BtfVmlinuxValueTypeId = btfValueTypeId
597+
attr.BtfFd = uint32(h.FD())
598+
}
599+
559600
fd, err := sys.MapCreate(&attr)
560601

561602
// Some map types don't support BTF k/v in earlier kernel versions.

struct_ops.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package ebpf
2+
3+
import "fmt"
4+
5+
// structOpsMeta is a placeholder object inserted into MapSpec.Contents
6+
// so that later stages (loader, ELF parser) can recognise this map as
7+
// a struct‑ops map without adding public fields yet.
8+
type structOpsMeta struct {
9+
userTypeName string
10+
kernTypeName string
11+
members []struct {
12+
name string
13+
userOfs uint32
14+
size uint32
15+
kind uint8
16+
progName string
17+
}
18+
initUserBlob []byte
19+
}
20+
21+
// extractStructOpsMeta returns the *structops.Meta embedded in a MapSpec’s Contents
22+
// according to the struct-ops convention:
23+
//
24+
// contents[0].Key == uint32(0)
25+
// contents[0].Value == *structopsMeta
26+
func extractStructOpsMeta(contents []MapKV) (*structOpsMeta, error) {
27+
if len(contents) == 0 {
28+
return nil, fmt.Errorf("struct_ops: missing meta at Contents[0]")
29+
}
30+
31+
k, ok := contents[0].Key.(uint32)
32+
if !ok || k != 0 {
33+
return nil, fmt.Errorf("struct_ops: meta key must be 0")
34+
}
35+
36+
meta, ok := contents[0].Value.(structOpsMeta)
37+
if !ok {
38+
return nil, fmt.Errorf("struct_ops: meta value must be structOpsMeta")
39+
}
40+
41+
return &meta, nil
42+
}

struct_ops_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package ebpf
2+
3+
import (
4+
"testing"
5+
6+
"github.com/cilium/ebpf/internal/testutils"
7+
)
8+
9+
func TestCreateStructOpsMapSpecSimple(t *testing.T) {
10+
requireStructOpsDummy(t)
11+
12+
ms := &MapSpec{
13+
Name: "dummy_ops",
14+
Type: StructOpsMap,
15+
KeySize: 4,
16+
ValueSize: 128,
17+
MaxEntries: 1,
18+
Contents: []MapKV{
19+
{
20+
Key: uint32(0),
21+
Value: structOpsMeta{
22+
userTypeName: "bpf_dummy_ops",
23+
kernTypeName: "bpf_struct_ops_bpf_dummy_ops",
24+
},
25+
},
26+
},
27+
}
28+
29+
m, err := NewMap(ms)
30+
testutils.SkipIfNotSupported(t, err)
31+
if err != nil {
32+
t.Fatalf("creating struct_ops map failed: %v", err)
33+
}
34+
t.Cleanup(func() { _ = m.Close() })
35+
}

0 commit comments

Comments
 (0)