Skip to content

Commit

Permalink
variable: add accessors for values represented by a Variable
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Beckers <[email protected]>
  • Loading branch information
ti-mo committed Feb 27, 2025
1 parent 52f6f90 commit ea5f31d
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 0 deletions.
Binary file modified testdata/variables-eb.elf
Binary file not shown.
Binary file modified testdata/variables-el.elf
Binary file not shown.
6 changes: 6 additions & 0 deletions testdata/variables.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ __section("socket") int check_struct() {
// Variable aligned on page boundary to ensure all bytes in the mapping can be
// accessed through the Variable API.
volatile char var_array[8192] __section(".data.array");

volatile uint32_t var_atomic __section(".data.atomic");
__section("socket") int add_atomic() {
__sync_fetch_and_add(&var_atomic, 1);
return 0;
}
34 changes: 34 additions & 0 deletions variable.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ebpf

import (
"encoding/binary"
"fmt"
"io"

Expand Down Expand Up @@ -232,3 +233,36 @@ func (v *Variable) Get(out any) error {

return nil
}

func checkVariable[T any](v *Variable) error {
if v.ReadOnly() {
return ErrReadOnly
}

var t T
size := binary.Size(t)
if size < 0 {
return fmt.Errorf("can't determine size of type %T: %w", t, ErrInvalidType)
}

if v.size != uint64(size) {
return fmt.Errorf("can't create %d-byte accessor to %d-byte variable", size, v.size)
}
return nil
}

// VariablePointer returns a pointer to a value of type T that has its
// underlying memory mapped to the memory representing the Variable.
//
// Taking a pointer to a read-only Variable is not supported. T must be a
// fixed-size type according to [binary.Size]. Types containing Go pointers are
// not valid.
//
// When accessing structs, embedding [structs.HostLayout] may help ensure the
// layout of the Go struct matches the one in the BPF C program.
func VariablePointer[T any](v *Variable) (*T, error) {
if err := checkVariable[T](v); err != nil {
return nil, fmt.Errorf("variable pointer %s: %w", v.name, err)
}
return MemoryPointer[T](v.mm, v.offset)
}
48 changes: 48 additions & 0 deletions variable_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ebpf

import (
"sync/atomic"
"testing"

"github.com/go-quicktest/qt"
Expand Down Expand Up @@ -182,3 +183,50 @@ func TestVariableFallback(t *testing.T) {
qt.Assert(t, qt.ErrorIs(err, ErrNotSupported))
}
}

func TestVariablePointer(t *testing.T) {
testutils.SkipIfNotSupported(t, haveMmapableMaps())

file := testutils.NativeFile(t, "testdata/variables-%s.elf")
spec, err := LoadCollectionSpec(file)
qt.Assert(t, qt.IsNil(err))

obj := struct {
AddAtomic *Program `ebpf:"add_atomic"`
CheckStruct *Program `ebpf:"check_struct"`

Atomic *Variable `ebpf:"var_atomic"`
Struct *Variable `ebpf:"var_struct"`
}{}

qt.Assert(t, qt.IsNil(spec.LoadAndAssign(&obj, nil)))
t.Cleanup(func() {
obj.AddAtomic.Close()
obj.CheckStruct.Close()
})

// Bump the value by 1 using a bpf program.
want := uint32(1338)
a32, err := VariablePointer[atomic.Uint32](obj.Atomic)
qt.Assert(t, qt.IsNil(err))
a32.Store(want - 1)

mustReturn(t, obj.AddAtomic, 0)
qt.Assert(t, qt.Equals(a32.Load(), want))

_, err = VariablePointer[*uint32](obj.Atomic)
qt.Assert(t, qt.ErrorIs(err, ErrInvalidType))

// TODO: When moving to Go 1.23, embed structs.HostLayout here.
type S struct {
A, B uint64
}

s, err := VariablePointer[S](obj.Struct)
qt.Assert(t, qt.IsNil(err))
*s = S{0xa, 0xb}
mustReturn(t, obj.CheckStruct, 1)

_, err = VariablePointer[struct{ A, B *uint64 }](obj.Struct)
qt.Assert(t, qt.ErrorIs(err, ErrInvalidType))
}

0 comments on commit ea5f31d

Please sign in to comment.