Skip to content

Commit

Permalink
Merge branch 'master' into feature/asm-metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
Dylan Reimerink committed Feb 15, 2022
2 parents 64386ba + 6981110 commit 5323356
Show file tree
Hide file tree
Showing 11 changed files with 440 additions and 85 deletions.
73 changes: 73 additions & 0 deletions asm/instruction.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const InstructionSize = 8
// RawInstructionOffset is an offset in units of raw BPF instructions.
type RawInstructionOffset uint64

var ErrUnsatisfiedMapReference = errors.New("unsatisfied map reference")
var ErrUnsatisfiedReference = errors.New("unsatisfied reference")

// Bytes returns the offset of an instruction in bytes.
func (rio RawInstructionOffset) Bytes() uint64 {
return uint64(rio) * InstructionSize
Expand Down Expand Up @@ -213,6 +216,17 @@ func (ins *Instruction) RewriteMapOffset(offset uint32) error {
return nil
}

// RewriteJumpOffset sets the offset for a jump operation.
//
// Returns an error if the instruction is not a jump operation.
func (ins *Instruction) RewriteJumpOffset(offset int16) error {
if ins.OpCode.JumpOp() == InvalidJumpOp {
return errors.New("not a jump operation")
}
ins.Offset = offset
return nil
}

func (ins *Instruction) mapOffset() uint32 {
return uint32(uint64(ins.Constant) >> 32)
}
Expand Down Expand Up @@ -786,6 +800,65 @@ func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) {
return hex.EncodeToString(h.Sum(nil)[:unix.BPF_TAG_SIZE]), nil
}

// FixupReferences updates all references to also take the symbol offset into account.
//
// Returns an error if a reference isn't used, see ErrUnsatisfiedMapReference and ErrUnsatisfiedReference.
func (insns Instructions) FixupReferences() error {
symbolOffsets := make(map[string]RawInstructionOffset)
iter := insns.Iterate()
for iter.Next() {
ins := iter.Ins

if ins.Symbol() == "" {
continue
}

if _, ok := symbolOffsets[ins.Symbol()]; ok {
return fmt.Errorf("duplicate symbol %s", ins.Symbol())
}

symbolOffsets[ins.Symbol()] = iter.Offset
}

iter = insns.Iterate()
for iter.Next() {
i := iter.Index
offset := iter.Offset
ins := iter.Ins

if ins.Reference() == "" {
continue
}

symOffset, ok := symbolOffsets[ins.Reference()]
switch {
case ins.IsFunctionReference() && ins.Constant == -1:
if !ok {
break
}

ins.Constant = int64(symOffset - offset - 1)
continue

case ins.OpCode.Class().IsJump() && ins.Offset == -1:
if !ok {
break
}

ins.Offset = int16(symOffset - offset - 1)
continue

case ins.IsLoadFromMap() && ins.MapPtr() == -1:
return fmt.Errorf("map %s: %w", ins.Reference(), ErrUnsatisfiedMapReference)
default:
// no fixup needed
continue
}
return fmt.Errorf("%s at insn %d: symbol %q: %w", ins.OpCode, i, ins.Reference(), ErrUnsatisfiedReference)
}
return nil
}

// Iterate allows iterating a BPF program while keeping track of
// various offsets.
//
Expand Down
232 changes: 232 additions & 0 deletions features/misc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package features

import (
"bytes"
"errors"
"fmt"
"os"
"sync"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)

func init() {
miscs.miscTypes = make(map[miscType]error, maxMiscType)
}

var (
miscs miscCache
)

type miscCache struct {
sync.Mutex
miscTypes map[miscType]error
}

type miscType uint32

// Max returns the latest supported MiscType.
func (_ miscType) max() miscType {
return maxMiscType - 1
}

const (
// largeInsn support introduced in
// commit c04c0d2b968ac45d6ef020316808ef6c82325a82
largeInsn miscType = iota
// boundedLoops support introduced in
// commit 2589726d12a1b12eaaa93c7f1ea64287e383c7a5
boundedLoops
// v2ISA support introduced in
// commit 92b31a9af73b3a3fc801899335d6c47966351830
v2ISA
// v3ISA support introduced in
// commit 092ed0968bb648cd18e8a0430cd0a8a71727315c
v3ISA
// maxMiscType - Bound enum of FeatureTypes, has to be last in enum.
maxMiscType
)

const (
maxInsns = 4096
)

// HaveLargeInstructions probes the running kernel if more than 4096 instructions
// per program are supported.
// Return values have the following semantics:
//
// err == nil: The feature is available.
// errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
// err != nil: Any errors encountered during probe execution, wrapped.
//
// Note that the latter case may include false negatives, and that program creation may
// succeed despite an error being returned. Some program types cannot reliably be probed and
// will also return error. Only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached and persist throughout any process capability changes.
func HaveLargeInstructions() error {
return probeMisc(largeInsn)
}

// HaveBoundedLoops probes the running kernel if bounded loops are supported.
// Return values have the following semantics:
//
// err == nil: The feature is available.
// errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
// err != nil: Any errors encountered during probe execution, wrapped.
//
// Note that the latter case may include false negatives, and that program creation may
// succeed despite an error being returned. Some program types cannot reliably be probed and
// will also return error. Only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached and persist throughout any process capability changes.
func HaveBoundedLoops() error {
return probeMisc(boundedLoops)
}

// HaveV2ISA probes the running kernel if instructions of the v2 ISA are supported.
// Return values have the following semantics:
//
// err == nil: The feature is available.
// errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
// err != nil: Any errors encountered during probe execution, wrapped.
//
// Note that the latter case may include false negatives, and that program creation may
// succeed despite an error being returned. Some program types cannot reliably be probed and
// will also return error. Only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached and persist throughout any process capability changes.
func HaveV2ISA() error {
return probeMisc(v2ISA)
}

// HaveV3ISA probes the running kernel if instructions of the v3 ISA are supported.
// Return values have the following semantics:
//
// err == nil: The feature is available.
// errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
// err != nil: Any errors encountered during probe execution, wrapped.
//
// Note that the latter case may include false negatives, and that program creation may
// succeed despite an error being returned. Some program types cannot reliably be probed and
// will also return error. Only `nil` and `ebpf.ErrNotSupported` are conclusive.
//
// Probe results are cached and persist throughout any process capability changes.
func HaveV3ISA() error {
return probeMisc(v3ISA)
}

// probeMisc checks the kernel for a given supported misc by creating
// a specialized program probe and loading it.
// Results are cached and persist throughout any process capability changes.
func probeMisc(mt miscType) error {
if mt > mt.max() {
return os.ErrInvalid
}
mc.Lock()
defer mc.Unlock()
err, ok := miscs.miscTypes[mt]
if ok {
return err
}

attr, err := createMiscProbeAttr(mt)
if err != nil {
return fmt.Errorf("couldn't create the attributes for the probe: %w", err)
}

fd, err := sys.ProgLoad(attr)

switch {
// EINVAL occurs when attempting to create a program with an unknown type.
// E2BIG occurs when ProgLoadAttr contains non-zero bytes past the end
// of the struct known by the running kernel, meaning the kernel is too old
// to support the given map type.
case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
err = ebpf.ErrNotSupported

// EPERM is kept as-is and is not converted or wrapped.
case errors.Is(err, unix.EPERM):
break

// Wrap unexpected errors.
case err != nil:
err = fmt.Errorf("unexpected error during feature probe: %w", err)

default:
fd.Close()
}

miscs.miscTypes[mt] = err

return err
}

func createMiscProbeAttr(mt miscType) (*sys.ProgLoadAttr, error) {
var (
insns asm.Instructions
label string
)

switch mt {
case largeInsn:
for i := 0; i < maxInsns; i++ {
insns = append(insns, asm.Mov.Imm(asm.R0, 1))
}
insns = append(insns, asm.Return())
case boundedLoops:
label = "boundedLoop"
insns = asm.Instructions{
asm.Mov.Imm(asm.R0, 10),
asm.Sub.Imm(asm.R0, 1).Sym(label),
asm.JNE.Imm(asm.R0, 0, label),
asm.Return(),
}
case v2ISA:
label = "v2isa"
insns = asm.Instructions{
asm.Mov.Imm(asm.R0, 0).Sym(label),
asm.JLT.Imm(asm.R0, 0, label),
asm.Mov.Imm(asm.R0, 1),
asm.Return(),
}
// To test the v2 ISA we need a dedicated jump offset other
// than the one we would get from Instruction.FixupReferences().
if err := insns[1].RewriteJumpOffset(1); err != nil {
return nil, err
}
case v3ISA:
label = "v3isa"
insns = asm.Instructions{
asm.Mov.Imm(asm.R0, 0).Sym(label),
asm.JLT.Imm32(asm.R0, 0, label),
asm.Mov.Imm(asm.R0, 1),
asm.Return(),
}
default:
return nil, fmt.Errorf("feature %d not yet implemented", mt)
}

if err := insns.FixupReferences(); err != nil {
return nil, err
}

buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
if err := insns.Marshal(buf, internal.NativeEndian); err != nil {
return nil, err
}

bytecode := buf.Bytes()
instructions := sys.NewSlicePointer(bytecode)

return &sys.ProgLoadAttr{
ProgType: sys.BPF_PROG_TYPE_SOCKET_FILTER,
Insns: instructions,
InsnCnt: uint32(len(bytecode) / asm.InstructionSize),
License: sys.NewStringPointer("MIT"),
}, nil
}
42 changes: 42 additions & 0 deletions features/misc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package features

import (
"errors"
"fmt"
"math"
"os"
"testing"

"github.com/cilium/ebpf/internal/testutils"
)

func TestInvalidMisc(t *testing.T) {
if err := probeMisc(miscType(math.MaxUint32)); !errors.Is(err, os.ErrInvalid) {
t.Fatalf("Expected os.ErrInvalid but was: %v", err)
}
}

func TestHaveMisc(t *testing.T) {
tests := map[miscType]struct {
probe func() error
minKernel string
}{
largeInsn: {probe: HaveLargeInstructions, minKernel: "5.1"},
boundedLoops: {probe: HaveBoundedLoops, minKernel: "5.2"},
v2ISA: {probe: HaveV2ISA, minKernel: "4.13"},
v3ISA: {probe: HaveV3ISA, minKernel: "5.0"},
}

for misc, test := range tests {
test := test
probe := fmt.Sprintf("misc-%d", misc)
t.Run(probe, func(t *testing.T) {
testutils.SkipOnOldKernel(t, test.minKernel, probe)

if err := test.probe(); err != nil {
t.Fatalf("Feature %s isn't supported even though kernel is at least %s: %v",
probe, test.minKernel, err)
}
})
}
}
Loading

0 comments on commit 5323356

Please sign in to comment.