Skip to content

Commit fd88693

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 80e6a9d commit fd88693

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
@@ -562,6 +562,47 @@ func (spec *MapSpec) createMap(inner *sys.FD) (_ *Map, err error) {
562562
}
563563
}
564564

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

567608
// 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)