Skip to content

CollectionSpec: add new Variables field to interact with constants and global variables #1564

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

Merged
merged 5 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 34 additions & 62 deletions collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kconfig"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/sysenc"
)

// CollectionOptions control loading a collection into the kernel.
Expand All @@ -39,6 +38,11 @@ type CollectionSpec struct {
Maps map[string]*MapSpec
Programs map[string]*ProgramSpec

// Variables refer to global variables declared in the ELF. They can be read
// and modified freely before loading the Collection. Modifying them after
// loading has no effect on a running eBPF program.
Variables map[string]*VariableSpec

// Types holds type information about Maps and Programs.
// Modifications to Types are currently undefined behaviour.
Types *btf.Spec
Expand All @@ -57,6 +61,7 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
cpy := CollectionSpec{
Maps: make(map[string]*MapSpec, len(cs.Maps)),
Programs: make(map[string]*ProgramSpec, len(cs.Programs)),
Variables: make(map[string]*VariableSpec, len(cs.Variables)),
ByteOrder: cs.ByteOrder,
Types: cs.Types.Copy(),
}
Expand All @@ -69,6 +74,10 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
cpy.Programs[name] = spec.Copy()
}

for name, spec := range cs.Variables {
cpy.Variables[name] = spec.copy(&cpy)
}

return &cpy
}

Expand Down Expand Up @@ -135,65 +144,24 @@ func (m *MissingConstantsError) Error() string {
// From Linux 5.5 the verifier will use constants to eliminate dead code.
//
// Returns an error wrapping [MissingConstantsError] if a constant doesn't exist.
//
// Deprecated: Use [CollectionSpec.Variables] to interact with constants instead.
// RewriteConstants is now a wrapper around the VariableSpec API.
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
replaced := make(map[string]bool)

for name, spec := range cs.Maps {
if !strings.HasPrefix(name, ".rodata") {
var missing []string
for n, c := range consts {
v, ok := cs.Variables[n]
if !ok {
missing = append(missing, n)
continue
}

b, ds, err := spec.dataSection()
if errors.Is(err, errMapNoBTFValue) {
// Data sections without a BTF Datasec are valid, but don't support
// constant replacements.
continue
if !v.Constant() {
return fmt.Errorf("variable %s is not a constant", n)
}
if err != nil {
return fmt.Errorf("map %s: %w", name, err)
}

// MapSpec.Copy() performs a shallow copy. Fully copy the byte slice
// to avoid any changes affecting other copies of the MapSpec.
cpy := make([]byte, len(b))
copy(cpy, b)

for _, v := range ds.Vars {
vname := v.Type.TypeName()
replacement, ok := consts[vname]
if !ok {
continue
}

if _, ok := v.Type.(*btf.Var); !ok {
return fmt.Errorf("section %s: unexpected type %T for variable %s", name, v.Type, vname)
}

if replaced[vname] {
return fmt.Errorf("section %s: duplicate variable %s", name, vname)
}

if int(v.Offset+v.Size) > len(cpy) {
return fmt.Errorf("section %s: offset %d(+%d) for variable %s is out of bounds", name, v.Offset, v.Size, vname)
}

b, err := sysenc.Marshal(replacement, int(v.Size))
if err != nil {
return fmt.Errorf("marshaling constant replacement %s: %w", vname, err)
}

b.CopyTo(cpy[v.Offset : v.Offset+v.Size])

replaced[vname] = true
}

spec.Contents[0] = MapKV{Key: uint32(0), Value: cpy}
}

var missing []string
for c := range consts {
if !replaced[c] {
missing = append(missing, c)
if err := v.Set(c); err != nil {
return fmt.Errorf("rewriting constant %s: %w", n, err)
}
}

Expand All @@ -211,25 +179,23 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
// if this sounds useful.
//
// 'to' must be a pointer to a struct. A field of the
// struct is updated with values from Programs or Maps if it
// has an `ebpf` tag and its type is *ProgramSpec or *MapSpec.
// struct is updated with values from Programs, Maps or Variables if it
// has an `ebpf` tag and its type is *ProgramSpec, *MapSpec or *VariableSpec.
// The tag's value specifies the name of the program or map as
// found in the CollectionSpec.
//
// struct {
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
// Bar *ebpf.MapSpec `ebpf:"bar_map"`
// Foo *ebpf.ProgramSpec `ebpf:"xdp_foo"`
// Bar *ebpf.MapSpec `ebpf:"bar_map"`
// Var *ebpf.VariableSpec `ebpf:"some_var"`
// Ignored int
// }
//
// Returns an error if any of the eBPF objects can't be found, or
// if the same MapSpec or ProgramSpec is assigned multiple times.
// if the same Spec is assigned multiple times.
func (cs *CollectionSpec) Assign(to interface{}) error {
// Assign() only supports assigning ProgramSpecs and MapSpecs,
// so doesn't load any resources into the kernel.
getValue := func(typ reflect.Type, name string) (interface{}, error) {
switch typ {

case reflect.TypeOf((*ProgramSpec)(nil)):
if p := cs.Programs[name]; p != nil {
return p, nil
Expand All @@ -242,6 +208,12 @@ func (cs *CollectionSpec) Assign(to interface{}) error {
}
return nil, fmt.Errorf("missing map %q", name)

case reflect.TypeOf((*VariableSpec)(nil)):
if v := cs.Variables[name]; v != nil {
return v, nil
}
return nil, fmt.Errorf("missing variable %q", name)

default:
return nil, fmt.Errorf("unsupported type %s", typ)
}
Expand Down
78 changes: 22 additions & 56 deletions collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ func TestCollectionSpecNotModified(t *testing.T) {
}

func TestCollectionSpecCopy(t *testing.T) {
ms := &MapSpec{
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
}

cs := &CollectionSpec{
map[string]*MapSpec{
"my-map": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
},
},
map[string]*MapSpec{"my-map": ms},
map[string]*ProgramSpec{
"test": {
Type: SocketFilter,
Expand All @@ -77,6 +77,14 @@ func TestCollectionSpecCopy(t *testing.T) {
License: "MIT",
},
},
map[string]*VariableSpec{
"my-var": {
name: "my-var",
offset: 0,
size: 4,
m: ms,
},
},
&btf.Spec{},
binary.LittleEndian,
}
Expand Down Expand Up @@ -325,52 +333,6 @@ func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) {
}
}

func TestCollectionRewriteConstants(t *testing.T) {
cs := &CollectionSpec{
Maps: map[string]*MapSpec{
".rodata": {
Type: Array,
KeySize: 4,
ValueSize: 4,
MaxEntries: 1,
Value: &btf.Datasec{
Vars: []btf.VarSecinfo{
{
Type: &btf.Var{
Name: "the_constant",
Type: &btf.Int{Size: 4},
},
Offset: 0,
Size: 4,
},
},
},
Contents: []MapKV{
{Key: uint32(0), Value: []byte{1, 1, 1, 1}},
},
},
},
}

err := cs.RewriteConstants(map[string]interface{}{
"fake_constant_one": uint32(1),
"fake_constant_two": uint32(2),
})
qt.Assert(t, qt.IsNotNil(err), qt.Commentf("RewriteConstants did not fail"))

var mErr *MissingConstantsError
if !errors.As(err, &mErr) {
t.Fatal("Error doesn't wrap MissingConstantsError:", err)
}
qt.Assert(t, qt.ContentEquals(mErr.Constants, []string{"fake_constant_one", "fake_constant_two"}))

err = cs.RewriteConstants(map[string]interface{}{
"the_constant": uint32(0x42424242),
})
qt.Assert(t, qt.IsNil(err))
qt.Assert(t, qt.ContentEquals(cs.Maps[".rodata"].Contents[0].Value.([]byte), []byte{0x42, 0x42, 0x42, 0x42}))
}

func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {
spec := &CollectionSpec{
Maps: map[string]*MapSpec{
Expand Down Expand Up @@ -427,8 +389,9 @@ func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) {

func TestCollectionSpecAssign(t *testing.T) {
var specs struct {
Program *ProgramSpec `ebpf:"prog1"`
Map *MapSpec `ebpf:"map1"`
Program *ProgramSpec `ebpf:"prog1"`
Map *MapSpec `ebpf:"map1"`
Variable *VariableSpec `ebpf:"var1"`
}

mapSpec := &MapSpec{
Expand All @@ -453,6 +416,9 @@ func TestCollectionSpecAssign(t *testing.T) {
Programs: map[string]*ProgramSpec{
"prog1": progSpec,
},
Variables: map[string]*VariableSpec{
"var1": {},
},
}

if err := cs.Assign(&specs); err != nil {
Expand Down
Loading