Update runc to v1.0.0-rc91

https://github.com/opencontainers/runc/releases/tag/v1.0.0-rc91

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
This commit is contained in:
Davanum Srinivas
2020-07-01 22:06:59 -04:00
parent c91c72c867
commit 963625d7bc
275 changed files with 9060 additions and 18508 deletions

16
vendor/github.com/cilium/ebpf/abi.go generated vendored
View File

@@ -3,14 +3,13 @@ package ebpf
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"syscall"
"github.com/cilium/ebpf/internal"
"github.com/pkg/errors"
)
// MapABI are the attributes of a Map which are available across all supported kernels.
@@ -35,7 +34,7 @@ func newMapABIFromSpec(spec *MapSpec) *MapABI {
func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) {
info, err := bpfGetMapInfoByFD(fd)
if err != nil {
if errors.Cause(err) == syscall.EINVAL {
if errors.Is(err, syscall.EINVAL) {
abi, err := newMapABIFromProc(fd)
return "", abi, err
}
@@ -98,7 +97,7 @@ func newProgramABIFromSpec(spec *ProgramSpec) *ProgramABI {
func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) {
info, err := bpfGetProgInfoByFD(fd)
if err != nil {
if errors.Cause(err) == syscall.EINVAL {
if errors.Is(err, syscall.EINVAL) {
return newProgramABIFromProc(fd)
}
@@ -127,7 +126,7 @@ func newProgramABIFromProc(fd *internal.FD) (string, *ProgramABI, error) {
"prog_type": &abi.Type,
"prog_tag": &name,
})
if errors.Cause(err) == errMissingFields {
if errors.Is(err, errMissingFields) {
return "", nil, &internal.UnsupportedFeatureError{
Name: "reading ABI from /proc/self/fdinfo",
MinimumVersion: internal.Version{4, 11, 0},
@@ -152,7 +151,10 @@ func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
}
defer fh.Close()
return errors.Wrap(scanFdInfoReader(fh, fields), fh.Name())
if err := scanFdInfoReader(fh, fields); err != nil {
return fmt.Errorf("%s: %w", fh.Name(), err)
}
return nil
}
var errMissingFields = errors.New("missing fields")
@@ -176,7 +178,7 @@ func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
}
if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 {
return errors.Wrapf(err, "can't parse field %s", name)
return fmt.Errorf("can't parse field %s: %v", name, err)
}
scanned++

View File

@@ -2,12 +2,11 @@ package asm
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"strings"
"github.com/pkg/errors"
)
// InstructionSize is the size of a BPF instruction in bytes
@@ -39,10 +38,12 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
}
ins.OpCode = bi.OpCode
ins.Dst = bi.Registers.Dst()
ins.Src = bi.Registers.Src()
ins.Offset = bi.Offset
ins.Constant = int64(bi.Constant)
ins.Dst, ins.Src, err = bi.Registers.Unmarshal(bo)
if err != nil {
return 0, fmt.Errorf("can't unmarshal registers: %s", err)
}
if !bi.OpCode.isDWordLoad() {
return InstructionSize, nil
@@ -75,9 +76,14 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
cons = int32(uint32(ins.Constant))
}
regs, err := newBPFRegisters(ins.Dst, ins.Src, bo)
if err != nil {
return 0, fmt.Errorf("can't marshal registers: %s", err)
}
bpfi := bpfInstruction{
ins.OpCode,
newBPFRegisters(ins.Dst, ins.Src),
regs,
ins.Offset,
cons,
}
@@ -103,22 +109,52 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
// RewriteMapPtr changes an instruction to use a new map fd.
//
// Returns an error if the fd is invalid, or the instruction
// is incorrect.
// Returns an error if the instruction doesn't load a map.
func (ins *Instruction) RewriteMapPtr(fd int) error {
if !ins.OpCode.isDWordLoad() {
return errors.Errorf("%s is not a 64 bit load", ins.OpCode)
return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
}
if fd < 0 {
return errors.New("invalid fd")
if ins.Src != PseudoMapFD && ins.Src != PseudoMapValue {
return errors.New("not a load from a map")
}
ins.Src = R1
ins.Constant = int64(fd)
// Preserve the offset value for direct map loads.
offset := uint64(ins.Constant) & (math.MaxUint32 << 32)
rawFd := uint64(uint32(fd))
ins.Constant = int64(offset | rawFd)
return nil
}
func (ins *Instruction) mapPtr() uint32 {
return uint32(uint64(ins.Constant) & math.MaxUint32)
}
// RewriteMapOffset changes the offset of a direct load from a map.
//
// Returns an error if the instruction is not a direct load.
func (ins *Instruction) RewriteMapOffset(offset uint32) error {
if !ins.OpCode.isDWordLoad() {
return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
}
if ins.Src != PseudoMapValue {
return errors.New("not a direct load from a map")
}
fd := uint64(ins.Constant) & math.MaxUint32
ins.Constant = int64(uint64(offset)<<32 | fd)
return nil
}
func (ins *Instruction) mapOffset() uint32 {
return uint32(uint64(ins.Constant) >> 32)
}
func (ins *Instruction) isLoadFromMap() bool {
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
}
// Format implements fmt.Formatter.
func (ins Instruction) Format(f fmt.State, c rune) {
if c != 'v' {
@@ -139,6 +175,19 @@ func (ins Instruction) Format(f fmt.State, c rune) {
return
}
if ins.isLoadFromMap() {
fd := int32(ins.mapPtr())
switch ins.Src {
case PseudoMapFD:
fmt.Fprintf(f, "LoadMapPtr dst: %s fd: %d", ins.Dst, fd)
case PseudoMapValue:
fmt.Fprintf(f, "LoadMapValue dst: %s, fd: %d off: %d", ins.Dst, fd, ins.mapOffset())
}
goto ref
}
fmt.Fprintf(f, "%v ", op)
switch cls := op.Class(); cls {
case LdClass, LdXClass, StClass, StXClass:
@@ -166,7 +215,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
case JumpClass:
switch jop := op.JumpOp(); jop {
case Call:
if ins.Src == R1 {
if ins.Src == PseudoCall {
// bpf-to-bpf call
fmt.Fprint(f, ins.Constant)
} else {
@@ -183,6 +232,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
}
}
ref:
if ins.Reference != "" {
fmt.Fprintf(f, " <%s>", ins.Reference)
}
@@ -235,7 +285,7 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
}
if _, ok := offsets[ins.Symbol]; ok {
return nil, errors.Errorf("duplicate symbol %s", ins.Symbol)
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
}
offsets[ins.Symbol] = i
@@ -273,7 +323,7 @@ func (insns Instructions) marshalledOffsets() (map[string]int, error) {
}
if _, ok := symbols[ins.Symbol]; ok {
return nil, errors.Errorf("duplicate symbol %s", ins.Symbol)
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
}
symbols[ins.Symbol] = currentPos
@@ -350,11 +400,11 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
num := 0
for i, ins := range insns {
switch {
case ins.OpCode.JumpOp() == Call && ins.Constant == -1:
case ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall && ins.Constant == -1:
// Rewrite bpf to bpf call
offset, ok := absoluteOffsets[ins.Reference]
if !ok {
return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
}
ins.Constant = int64(offset - num - 1)
@@ -363,7 +413,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
// Rewrite jump to label
offset, ok := absoluteOffsets[ins.Reference]
if !ok {
return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
}
ins.Offset = int16(offset - num - 1)
@@ -371,7 +421,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
n, err := ins.Marshal(w, bo)
if err != nil {
return errors.Wrapf(err, "instruction %d", i)
return fmt.Errorf("instruction %d: %w", i, err)
}
num += int(n / InstructionSize)
@@ -388,16 +438,26 @@ type bpfInstruction struct {
type bpfRegisters uint8
func newBPFRegisters(dst, src Register) bpfRegisters {
return bpfRegisters((src << 4) | (dst & 0xF))
func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, error) {
switch bo {
case binary.LittleEndian:
return bpfRegisters((src << 4) | (dst & 0xF)), nil
case binary.BigEndian:
return bpfRegisters((dst << 4) | (src & 0xF)), nil
default:
return 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
}
}
func (r bpfRegisters) Dst() Register {
return Register(r & 0xF)
}
func (r bpfRegisters) Src() Register {
return Register(r >> 4)
func (r bpfRegisters) Unmarshal(bo binary.ByteOrder) (dst, src Register, err error) {
switch bo {
case binary.LittleEndian:
return Register(r & 0xF), Register(r >> 4), nil
case binary.BigEndian:
return Register(r >> 4), Register(r & 0xf), nil
default:
return 0, 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
}
}
type unreferencedSymbolError struct {

View File

@@ -95,7 +95,7 @@ func (op JumpOp) Label(label string) Instruction {
if op == Call {
return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(Call),
Src: R1,
Src: PseudoCall,
Constant: -1,
Reference: label,
}

View File

@@ -110,11 +110,26 @@ func LoadMapPtr(dst Register, fd int) Instruction {
return Instruction{
OpCode: LoadImmOp(DWord),
Dst: dst,
Src: R1,
Src: PseudoMapFD,
Constant: int64(fd),
}
}
// LoadMapValue stores a pointer to the value at a certain offset of a map.
func LoadMapValue(dst Register, fd int, offset uint32) Instruction {
if fd < 0 {
return Instruction{OpCode: InvalidOpCode}
}
fdAndOffset := (uint64(offset) << 32) | uint64(uint32(fd))
return Instruction{
OpCode: LoadImmOp(DWord),
Dst: dst,
Src: PseudoMapValue,
Constant: int64(fdAndOffset),
}
}
// LoadIndOp returns the OpCode for loading a value of given size from an sk_buff.
func LoadIndOp(size Size) OpCode {
return OpCode(LdClass).SetMode(IndMode).SetSize(size)

View File

@@ -225,7 +225,7 @@ func (op OpCode) String() string {
}
default:
fmt.Fprintf(&f, "%#x", op)
fmt.Fprintf(&f, "OpCode(%#x)", uint8(op))
}
return f.String()

View File

@@ -33,6 +33,13 @@ const (
RFP = R10
)
// Pseudo registers used by 64bit loads and jumps
const (
PseudoMapFD = R1 // BPF_PSEUDO_MAP_FD
PseudoMapValue = R2 // BPF_PSEUDO_MAP_VALUE
PseudoCall = R1 // BPF_PSEUDO_CALL
)
func (r Register) String() string {
v := uint8(r)
if v == 10 {

View File

@@ -1,9 +1,13 @@
package ebpf
import (
"errors"
"fmt"
"math"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/pkg/errors"
)
// CollectionOptions control loading a collection into the kernel.
@@ -39,6 +43,89 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
return &cpy
}
// RewriteMaps replaces all references to specific maps.
//
// Use this function to use pre-existing maps instead of creating new ones
// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps.
//
// Returns an error if a named map isn't used in at least one program.
func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
for symbol, m := range maps {
// have we seen a program that uses this symbol / map
seen := false
fd := m.FD()
for progName, progSpec := range cs.Programs {
err := progSpec.Instructions.RewriteMapPtr(symbol, fd)
switch {
case err == nil:
seen = true
case asm.IsUnreferencedSymbol(err):
// Not all programs need to use the map
default:
return fmt.Errorf("program %s: %w", progName, err)
}
}
if !seen {
return fmt.Errorf("map %s not referenced by any programs", symbol)
}
// Prevent NewCollection from creating rewritten maps
delete(cs.Maps, symbol)
}
return nil
}
// RewriteConstants replaces the value of multiple constants.
//
// The constant must be defined like so in the C program:
//
// static volatile const type foobar;
// static volatile const type foobar = default;
//
// Replacement values must be of the same length as the C sizeof(type).
// If necessary, they are marshalled according to the same rules as
// map values.
//
// From Linux 5.5 the verifier will use constants to eliminate dead code.
//
// Returns an error if a constant doesn't exist.
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
rodata := cs.Maps[".rodata"]
if rodata == nil {
return errors.New("missing .rodata section")
}
if rodata.BTF == nil {
return errors.New(".rodata section has no BTF")
}
if n := len(rodata.Contents); n != 1 {
return fmt.Errorf("expected one key in .rodata, found %d", n)
}
kv := rodata.Contents[0]
value, ok := kv.Value.([]byte)
if !ok {
return fmt.Errorf("first value in .rodata is %T not []byte", kv.Value)
}
buf := make([]byte, len(value))
copy(buf, value)
err := patchValue(buf, btf.MapValue(rodata.BTF), consts)
if err != nil {
return err
}
rodata.Contents[0] = MapKV{kv.Key, buf}
return nil
}
// Collection is a collection of Programs and Maps associated
// with their symbols
type Collection struct {
@@ -99,14 +186,14 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col
var handle *btf.Handle
if mapSpec.BTF != nil {
handle, err = loadBTF(btf.MapSpec(mapSpec.BTF))
if err != nil && !btf.IsNotSupported(err) {
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, err
}
}
m, err := newMapWithBTF(mapSpec, handle)
if err != nil {
return nil, errors.Wrapf(err, "map %s", mapName)
return nil, fmt.Errorf("map %s: %w", mapName, err)
}
maps[mapName] = m
}
@@ -116,37 +203,43 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col
// Rewrite any reference to a valid map.
for i := range progSpec.Instructions {
var (
ins = &progSpec.Instructions[i]
m = maps[ins.Reference]
)
ins := &progSpec.Instructions[i]
if ins.Reference == "" || m == nil {
if ins.OpCode != asm.LoadImmOp(asm.DWord) || ins.Reference == "" {
continue
}
if ins.Src == asm.R1 {
if uint32(ins.Constant) != math.MaxUint32 {
// Don't overwrite maps already rewritten, users can
// rewrite programs in the spec themselves
continue
}
m := maps[ins.Reference]
if m == nil {
return nil, fmt.Errorf("program %s: missing map %s", progName, ins.Reference)
}
fd := m.FD()
if fd < 0 {
return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
}
if err := ins.RewriteMapPtr(m.FD()); err != nil {
return nil, errors.Wrapf(err, "progam %s: map %s", progName, ins.Reference)
return nil, fmt.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
}
}
var handle *btf.Handle
if progSpec.BTF != nil {
handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF))
if err != nil && !btf.IsNotSupported(err) {
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, err
}
}
prog, err := newProgramWithBTF(progSpec, handle, opts.Programs)
if err != nil {
return nil, errors.Wrapf(err, "program %s", progName)
return nil, fmt.Errorf("program %s: %w", progName, err)
}
progs[progName] = prog
}

View File

@@ -4,21 +4,23 @@ import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"strings"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/pkg/errors"
"github.com/cilium/ebpf/internal/unix"
)
type elfCode struct {
*elf.File
symbols []elf.Symbol
symbolsPerSection map[elf.SectionIndex]map[uint64]string
symbolsPerSection map[elf.SectionIndex]map[uint64]elf.Symbol
license string
version uint32
}
@@ -32,7 +34,10 @@ func LoadCollectionSpec(file string) (*CollectionSpec, error) {
defer f.Close()
spec, err := LoadCollectionSpecFromReader(f)
return spec, errors.Wrapf(err, "file %s", file)
if err != nil {
return nil, fmt.Errorf("file %s: %w", file, err)
}
return spec, nil
}
// LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec.
@@ -45,7 +50,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
symbols, err := f.Symbols()
if err != nil {
return nil, errors.Wrap(err, "load symbols")
return nil, fmt.Errorf("load symbols: %v", err)
}
ec := &elfCode{f, symbols, symbolsPerSection(symbols), "", 0}
@@ -57,6 +62,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
progSections = make(map[elf.SectionIndex]*elf.Section)
relSections = make(map[elf.SectionIndex]*elf.Section)
mapSections = make(map[elf.SectionIndex]*elf.Section)
dataSections = make(map[elf.SectionIndex]*elf.Section)
)
for i, sec := range ec.Sections {
@@ -69,15 +75,17 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
mapSections[elf.SectionIndex(i)] = sec
case sec.Name == ".maps":
btfMaps[elf.SectionIndex(i)] = sec
case sec.Name == ".bss" || sec.Name == ".rodata" || sec.Name == ".data":
dataSections[elf.SectionIndex(i)] = sec
case sec.Type == elf.SHT_REL:
if int(sec.Info) >= len(ec.Sections) {
return nil, errors.Errorf("found relocation section %v for missing section %v", i, sec.Info)
return nil, fmt.Errorf("found relocation section %v for missing section %v", i, sec.Info)
}
// Store relocations under the section index of the target
idx := elf.SectionIndex(sec.Info)
if relSections[idx] != nil {
return nil, errors.Errorf("section %d has multiple relocation sections", sec.Info)
return nil, fmt.Errorf("section %d has multiple relocation sections", sec.Info)
}
relSections[idx] = sec
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
@@ -87,34 +95,52 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
ec.license, err = loadLicense(licenseSection)
if err != nil {
return nil, errors.Wrap(err, "load license")
return nil, fmt.Errorf("load license: %w", err)
}
ec.version, err = loadVersion(versionSection, ec.ByteOrder)
if err != nil {
return nil, errors.Wrap(err, "load version")
return nil, fmt.Errorf("load version: %w", err)
}
btf, err := btf.LoadSpecFromReader(rd)
btfSpec, err := btf.LoadSpecFromReader(rd)
if err != nil {
return nil, errors.Wrap(err, "load BTF")
return nil, fmt.Errorf("load BTF: %w", err)
}
relocations, referencedSections, err := ec.loadRelocations(relSections)
if err != nil {
return nil, fmt.Errorf("load relocations: %w", err)
}
maps := make(map[string]*MapSpec)
if err := ec.loadMaps(maps, mapSections); err != nil {
return nil, errors.Wrap(err, "load maps")
return nil, fmt.Errorf("load maps: %w", err)
}
if len(btfMaps) > 0 {
if err := ec.loadBTFMaps(maps, btfMaps, btf); err != nil {
return nil, errors.Wrap(err, "load BTF maps")
if err := ec.loadBTFMaps(maps, btfMaps, btfSpec); err != nil {
return nil, fmt.Errorf("load BTF maps: %w", err)
}
}
progs, err := ec.loadPrograms(progSections, relSections, btf)
if len(dataSections) > 0 {
for idx := range dataSections {
if !referencedSections[idx] {
// Prune data sections which are not referenced by any
// instructions.
delete(dataSections, idx)
}
}
if err := ec.loadDataSections(maps, dataSections, btfSpec); err != nil {
return nil, fmt.Errorf("load data sections: %w", err)
}
}
progs, err := ec.loadPrograms(progSections, relocations, btfSpec)
if err != nil {
return nil, errors.Wrap(err, "load programs")
return nil, fmt.Errorf("load programs: %w", err)
}
return &CollectionSpec{maps, progs}, nil
@@ -122,11 +148,12 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
func loadLicense(sec *elf.Section) (string, error) {
if sec == nil {
return "", errors.Errorf("missing license section")
return "", nil
}
data, err := sec.Data()
if err != nil {
return "", errors.Wrapf(err, "section %s", sec.Name)
return "", fmt.Errorf("section %s: %v", sec.Name, err)
}
return string(bytes.TrimRight(data, "\000")), nil
}
@@ -137,52 +164,51 @@ func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) {
}
var version uint32
err := binary.Read(sec.Open(), bo, &version)
return version, errors.Wrapf(err, "section %s", sec.Name)
if err := binary.Read(sec.Open(), bo, &version); err != nil {
return 0, fmt.Errorf("section %s: %v", sec.Name, err)
}
return version, nil
}
func (ec *elfCode) loadPrograms(progSections, relSections map[elf.SectionIndex]*elf.Section, btf *btf.Spec) (map[string]*ProgramSpec, error) {
func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, relocations map[elf.SectionIndex]map[uint64]elf.Symbol, btfSpec *btf.Spec) (map[string]*ProgramSpec, error) {
var (
progs []*ProgramSpec
libs []*ProgramSpec
)
for idx, prog := range progSections {
for idx, sec := range progSections {
syms := ec.symbolsPerSection[idx]
if len(syms) == 0 {
return nil, errors.Errorf("section %v: missing symbols", prog.Name)
return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
}
funcSym := syms[0]
if funcSym == "" {
return nil, errors.Errorf("section %v: no label at start", prog.Name)
funcSym, ok := syms[0]
if !ok {
return nil, fmt.Errorf("section %v: no label at start", sec.Name)
}
rels, err := ec.loadRelocations(relSections[idx])
insns, length, err := ec.loadInstructions(sec, syms, relocations[idx])
if err != nil {
return nil, errors.Wrapf(err, "program %s: can't load relocations", funcSym)
return nil, fmt.Errorf("program %s: can't unmarshal instructions: %w", funcSym.Name, err)
}
insns, length, err := ec.loadInstructions(prog, syms, rels)
if err != nil {
return nil, errors.Wrapf(err, "program %s: can't unmarshal instructions", funcSym)
}
progType, attachType := getProgType(prog.Name)
progType, attachType, attachTo := getProgType(sec.Name)
spec := &ProgramSpec{
Name: funcSym,
Name: funcSym.Name,
Type: progType,
AttachType: attachType,
AttachTo: attachTo,
License: ec.license,
KernelVersion: ec.version,
Instructions: insns,
ByteOrder: ec.ByteOrder,
}
if btf != nil {
spec.BTF, err = btf.Program(prog.Name, length)
if err != nil {
return nil, errors.Wrapf(err, "BTF for section %s (program %s)", prog.Name, funcSym)
if btfSpec != nil {
spec.BTF, err = btfSpec.Program(sec.Name, length)
if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) {
return nil, fmt.Errorf("program %s: %w", funcSym.Name, err)
}
}
@@ -200,7 +226,7 @@ func (ec *elfCode) loadPrograms(progSections, relSections map[elf.SectionIndex]*
for _, prog := range progs {
err := link(prog, libs)
if err != nil {
return nil, errors.Wrapf(err, "program %s", prog.Name)
return nil, fmt.Errorf("program %s: %w", prog.Name, err)
}
res[prog.Name] = prog
}
@@ -208,39 +234,158 @@ func (ec *elfCode) loadPrograms(progSections, relSections map[elf.SectionIndex]*
return res, nil
}
func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]string) (asm.Instructions, uint64, error) {
func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]elf.Symbol) (asm.Instructions, uint64, error) {
var (
r = section.Open()
insns asm.Instructions
ins asm.Instruction
offset uint64
)
for {
var ins asm.Instruction
n, err := ins.Unmarshal(r, ec.ByteOrder)
if err == io.EOF {
return insns, offset, nil
}
if err != nil {
return nil, 0, errors.Wrapf(err, "offset %d", offset)
return nil, 0, fmt.Errorf("offset %d: %w", offset, err)
}
ins.Symbol = symbols[offset]
ins.Reference = relocations[offset]
ins.Symbol = symbols[offset].Name
if rel, ok := relocations[offset]; ok {
if err = ec.relocateInstruction(&ins, rel); err != nil {
return nil, 0, fmt.Errorf("offset %d: can't relocate instruction: %w", offset, err)
}
}
insns = append(insns, ins)
offset += n
}
}
func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) error {
var (
typ = elf.ST_TYPE(rel.Info)
bind = elf.ST_BIND(rel.Info)
name = rel.Name
)
if typ == elf.STT_SECTION {
// Symbols with section type do not have a name set. Get it
// from the section itself.
idx := int(rel.Section)
if idx > len(ec.Sections) {
return errors.New("out-of-bounds section index")
}
name = ec.Sections[idx].Name
}
outer:
switch {
case ins.OpCode == asm.LoadImmOp(asm.DWord):
// There are two distinct types of a load from a map:
// a direct one, where the value is extracted without
// a call to map_lookup_elem in eBPF, and an indirect one
// that goes via the helper. They are distinguished by
// different relocations.
switch typ {
case elf.STT_SECTION:
// This is a direct load since the referenced symbol is a
// section. Weirdly, the offset of the real symbol in the
// section is encoded in the instruction stream.
if bind != elf.STB_LOCAL {
return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind)
}
// For some reason, clang encodes the offset of the symbol its
// section in the first basic BPF instruction, while the kernel
// expects it in the second one.
ins.Constant <<= 32
ins.Src = asm.PseudoMapValue
case elf.STT_NOTYPE:
if bind == elf.STB_GLOBAL && rel.Section == elf.SHN_UNDEF {
// This is a relocation generated by inline assembly.
// We can't do more than assigning ins.Reference.
break outer
}
// This is an ELF generated on clang < 8, which doesn't tag
// relocations appropriately.
fallthrough
case elf.STT_OBJECT:
if bind != elf.STB_GLOBAL {
return fmt.Errorf("load: %s: unsupported binding: %s", name, bind)
}
ins.Src = asm.PseudoMapFD
default:
return fmt.Errorf("load: %s: unsupported relocation: %s", name, typ)
}
// Mark the instruction as needing an update when creating the
// collection.
if err := ins.RewriteMapPtr(-1); err != nil {
return err
}
case ins.OpCode.JumpOp() == asm.Call:
if ins.Src != asm.PseudoCall {
return fmt.Errorf("call: %s: incorrect source register", name)
}
switch typ {
case elf.STT_NOTYPE, elf.STT_FUNC:
if bind != elf.STB_GLOBAL {
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
}
case elf.STT_SECTION:
if bind != elf.STB_LOCAL {
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
}
// The function we want to call is in the indicated section,
// at the offset encoded in the instruction itself. Reverse
// the calculation to find the real function we're looking for.
// A value of -1 references the first instruction in the section.
offset := int64(int32(ins.Constant)+1) * asm.InstructionSize
if offset < 0 {
return fmt.Errorf("call: %s: invalid offset %d", name, offset)
}
sym, ok := ec.symbolsPerSection[rel.Section][uint64(offset)]
if !ok {
return fmt.Errorf("call: %s: no symbol at offset %d", name, offset)
}
ins.Constant = -1
name = sym.Name
default:
return fmt.Errorf("call: %s: invalid symbol type %s", name, typ)
}
default:
return fmt.Errorf("relocation for unsupported instruction: %s", ins.OpCode)
}
ins.Reference = name
return nil
}
func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section) error {
for idx, sec := range mapSections {
syms := ec.symbolsPerSection[idx]
if len(syms) == 0 {
return errors.Errorf("section %v: no symbols", sec.Name)
return fmt.Errorf("section %v: no symbols", sec.Name)
}
if sec.Size%uint64(len(syms)) != 0 {
return errors.Errorf("section %v: map descriptors are not of equal size", sec.Name)
return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name)
}
var (
@@ -248,36 +393,38 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
size = sec.Size / uint64(len(syms))
)
for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size {
mapSym := syms[offset]
if mapSym == "" {
return errors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
mapSym, ok := syms[offset]
if !ok {
return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
}
if maps[mapSym] != nil {
return errors.Errorf("section %v: map %v already exists", sec.Name, mapSym)
if maps[mapSym.Name] != nil {
return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym)
}
lr := io.LimitReader(r, int64(size))
var spec MapSpec
spec := MapSpec{
Name: SanitizeName(mapSym.Name, -1),
}
switch {
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
return errors.Errorf("map %v: missing type", mapSym)
return fmt.Errorf("map %v: missing type", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
return errors.Errorf("map %v: missing key size", mapSym)
return fmt.Errorf("map %v: missing key size", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
return errors.Errorf("map %v: missing value size", mapSym)
return fmt.Errorf("map %v: missing value size", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
return errors.Errorf("map %v: missing max entries", mapSym)
return fmt.Errorf("map %v: missing max entries", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
return errors.Errorf("map %v: missing flags", mapSym)
return fmt.Errorf("map %v: missing flags", mapSym)
}
if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil {
return errors.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
return fmt.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
}
maps[mapSym] = &spec
maps[mapSym.Name] = &spec
}
}
@@ -285,85 +432,117 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
}
func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
if spec == nil {
return errors.Errorf("missing BTF")
return fmt.Errorf("missing BTF")
}
for idx, sec := range mapSections {
syms := ec.symbolsPerSection[idx]
if len(syms) == 0 {
return errors.Errorf("section %v: no symbols", sec.Name)
return fmt.Errorf("section %v: no symbols", sec.Name)
}
for _, sym := range syms {
if maps[sym] != nil {
return errors.Errorf("section %v: map %v already exists", sec.Name, sym)
name := sym.Name
if maps[name] != nil {
return fmt.Errorf("section %v: map %v already exists", sec.Name, sym)
}
btfMap, err := spec.Map(sym)
mapSpec, err := mapSpecFromBTF(spec, name)
if err != nil {
return errors.Wrapf(err, "map %v: can't get BTF", sym)
return fmt.Errorf("map %v: %w", name, err)
}
spec, err := mapSpecFromBTF(btfMap)
if err != nil {
return errors.Wrapf(err, "map %v", sym)
}
maps[sym] = spec
maps[name] = mapSpec
}
}
return nil
}
func mapSpecFromBTF(btfMap *btf.Map) (*MapSpec, error) {
func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) {
btfMap, btfMapMembers, err := spec.Map(name)
if err != nil {
return nil, fmt.Errorf("can't get BTF: %w", err)
}
keyType := btf.MapKey(btfMap)
size, err := btf.Sizeof(keyType)
if err != nil {
return nil, fmt.Errorf("can't get size of BTF key: %w", err)
}
keySize := uint32(size)
valueType := btf.MapValue(btfMap)
size, err = btf.Sizeof(valueType)
if err != nil {
return nil, fmt.Errorf("can't get size of BTF value: %w", err)
}
valueSize := uint32(size)
var (
mapType, flags, maxEntries uint32
err error
)
for _, member := range btf.MapType(btfMap).Members {
for _, member := range btfMapMembers {
switch member.Name {
case "type":
mapType, err = uintFromBTF(member.Type)
if err != nil {
return nil, errors.Wrap(err, "can't get type")
return nil, fmt.Errorf("can't get type: %w", err)
}
case "map_flags":
flags, err = uintFromBTF(member.Type)
if err != nil {
return nil, errors.Wrap(err, "can't get BTF map flags")
return nil, fmt.Errorf("can't get BTF map flags: %w", err)
}
case "max_entries":
maxEntries, err = uintFromBTF(member.Type)
if err != nil {
return nil, errors.Wrap(err, "can't get BTF map max entries")
return nil, fmt.Errorf("can't get BTF map max entries: %w", err)
}
case "key":
case "value":
case "key_size":
if _, isVoid := keyType.(*btf.Void); !isVoid {
return nil, errors.New("both key and key_size given")
}
keySize, err = uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get BTF key size: %w", err)
}
case "value_size":
if _, isVoid := valueType.(*btf.Void); !isVoid {
return nil, errors.New("both value and value_size given")
}
valueSize, err = uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get BTF value size: %w", err)
}
case "pinning":
pinning, err := uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get pinning: %w", err)
}
if pinning != 0 {
return nil, fmt.Errorf("'pinning' attribute not supported: %w", ErrNotSupported)
}
case "key", "value":
default:
return nil, errors.Errorf("unrecognized field %s in BTF map definition", member.Name)
return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name)
}
}
keySize, err := btf.Sizeof(btf.MapKey(btfMap))
if err != nil {
return nil, errors.Wrap(err, "can't get size of BTF key")
}
valueSize, err := btf.Sizeof(btf.MapValue(btfMap))
if err != nil {
return nil, errors.Wrap(err, "can't get size of BTF value")
}
return &MapSpec{
Type: MapType(mapType),
KeySize: uint32(keySize),
ValueSize: uint32(valueSize),
KeySize: keySize,
ValueSize: valueSize,
MaxEntries: maxEntries,
Flags: flags,
BTF: btfMap,
@@ -375,127 +554,163 @@ func mapSpecFromBTF(btfMap *btf.Map) (*MapSpec, error) {
func uintFromBTF(typ btf.Type) (uint32, error) {
ptr, ok := typ.(*btf.Pointer)
if !ok {
return 0, errors.Errorf("not a pointer: %v", typ)
return 0, fmt.Errorf("not a pointer: %v", typ)
}
arr, ok := ptr.Target.(*btf.Array)
if !ok {
return 0, errors.Errorf("not a pointer to array: %v", typ)
return 0, fmt.Errorf("not a pointer to array: %v", typ)
}
return arr.Nelems, nil
}
func getProgType(v string) (ProgramType, AttachType) {
types := map[string]ProgramType{
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568
"socket": SocketFilter,
"seccomp": SocketFilter,
"kprobe/": Kprobe,
"uprobe/": Kprobe,
"kretprobe/": Kprobe,
"uretprobe/": Kprobe,
"tracepoint/": TracePoint,
"raw_tracepoint/": RawTracepoint,
"xdp": XDP,
"perf_event": PerfEvent,
"lwt_in": LWTIn,
"lwt_out": LWTOut,
"lwt_xmit": LWTXmit,
"lwt_seg6local": LWTSeg6Local,
"sockops": SockOps,
"sk_skb": SkSKB,
"sk_msg": SkMsg,
"lirc_mode2": LircMode2,
"flow_dissector": FlowDissector,
"cgroup_skb/": CGroupSKB,
"cgroup/dev": CGroupDevice,
"cgroup/skb": CGroupSKB,
"cgroup/sock": CGroupSock,
"cgroup/post_bind": CGroupSock,
"cgroup/bind": CGroupSockAddr,
"cgroup/connect": CGroupSockAddr,
"cgroup/sendmsg": CGroupSockAddr,
"cgroup/recvmsg": CGroupSockAddr,
"cgroup/sysctl": CGroupSysctl,
"cgroup/getsockopt": CGroupSockopt,
"cgroup/setsockopt": CGroupSockopt,
"classifier": SchedCLS,
"action": SchedACT,
}
attachTypes := map[string]AttachType{
"cgroup_skb/ingress": AttachCGroupInetIngress,
"cgroup_skb/egress": AttachCGroupInetEgress,
"cgroup/sock": AttachCGroupInetSockCreate,
"cgroup/post_bind4": AttachCGroupInet4PostBind,
"cgroup/post_bind6": AttachCGroupInet6PostBind,
"cgroup/dev": AttachCGroupDevice,
"sockops": AttachCGroupSockOps,
"sk_skb/stream_parser": AttachSkSKBStreamParser,
"sk_skb/stream_verdict": AttachSkSKBStreamVerdict,
"sk_msg": AttachSkSKBStreamVerdict,
"lirc_mode2": AttachLircMode2,
"flow_dissector": AttachFlowDissector,
"cgroup/bind4": AttachCGroupInet4Bind,
"cgroup/bind6": AttachCGroupInet6Bind,
"cgroup/connect4": AttachCGroupInet4Connect,
"cgroup/connect6": AttachCGroupInet6Connect,
"cgroup/sendmsg4": AttachCGroupUDP4Sendmsg,
"cgroup/sendmsg6": AttachCGroupUDP6Sendmsg,
"cgroup/recvmsg4": AttachCGroupUDP4Recvmsg,
"cgroup/recvmsg6": AttachCGroupUDP6Recvmsg,
"cgroup/sysctl": AttachCGroupSysctl,
"cgroup/getsockopt": AttachCGroupGetsockopt,
"cgroup/setsockopt": AttachCGroupSetsockopt,
}
attachType := AttachNone
for k, t := range attachTypes {
if strings.HasPrefix(v, k) {
attachType = t
}
func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
if spec == nil {
return errors.New("data sections require BTF, make sure all consts are marked as static")
}
for k, t := range types {
if strings.HasPrefix(v, k) {
return t, attachType
for _, sec := range dataSections {
btfMap, err := spec.Datasec(sec.Name)
if err != nil {
return err
}
data, err := sec.Data()
if err != nil {
return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
}
if uint64(len(data)) > math.MaxUint32 {
return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
}
mapSpec := &MapSpec{
Name: SanitizeName(sec.Name, -1),
Type: Array,
KeySize: 4,
ValueSize: uint32(len(data)),
MaxEntries: 1,
Contents: []MapKV{{uint32(0), data}},
BTF: btfMap,
}
switch sec.Name {
case ".rodata":
mapSpec.Flags = unix.BPF_F_RDONLY_PROG
mapSpec.Freeze = true
case ".bss":
// The kernel already zero-initializes the map
mapSpec.Contents = nil
}
maps[sec.Name] = mapSpec
}
return UnspecifiedProgram, AttachNone
return nil
}
func (ec *elfCode) loadRelocations(sec *elf.Section) (map[uint64]string, error) {
rels := make(map[uint64]string)
if sec == nil {
return rels, nil
func getProgType(sectionName string) (ProgramType, AttachType, string) {
types := map[string]struct {
progType ProgramType
attachType AttachType
}{
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c
"socket": {SocketFilter, AttachNone},
"seccomp": {SocketFilter, AttachNone},
"kprobe/": {Kprobe, AttachNone},
"uprobe/": {Kprobe, AttachNone},
"kretprobe/": {Kprobe, AttachNone},
"uretprobe/": {Kprobe, AttachNone},
"tracepoint/": {TracePoint, AttachNone},
"raw_tracepoint/": {RawTracepoint, AttachNone},
"xdp": {XDP, AttachNone},
"perf_event": {PerfEvent, AttachNone},
"lwt_in": {LWTIn, AttachNone},
"lwt_out": {LWTOut, AttachNone},
"lwt_xmit": {LWTXmit, AttachNone},
"lwt_seg6local": {LWTSeg6Local, AttachNone},
"sockops": {SockOps, AttachCGroupSockOps},
"sk_skb/stream_parser": {SkSKB, AttachSkSKBStreamParser},
"sk_skb/stream_verdict": {SkSKB, AttachSkSKBStreamParser},
"sk_msg": {SkMsg, AttachSkSKBStreamVerdict},
"lirc_mode2": {LircMode2, AttachLircMode2},
"flow_dissector": {FlowDissector, AttachFlowDissector},
"iter/": {Tracing, AttachTraceIter},
"cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress},
"cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress},
"cgroup/dev": {CGroupDevice, AttachCGroupDevice},
"cgroup/skb": {CGroupSKB, AttachNone},
"cgroup/sock": {CGroupSock, AttachCGroupInetSockCreate},
"cgroup/post_bind4": {CGroupSock, AttachCGroupInet4PostBind},
"cgroup/post_bind6": {CGroupSock, AttachCGroupInet6PostBind},
"cgroup/bind4": {CGroupSockAddr, AttachCGroupInet4Bind},
"cgroup/bind6": {CGroupSockAddr, AttachCGroupInet6Bind},
"cgroup/connect4": {CGroupSockAddr, AttachCGroupInet4Connect},
"cgroup/connect6": {CGroupSockAddr, AttachCGroupInet6Connect},
"cgroup/sendmsg4": {CGroupSockAddr, AttachCGroupUDP4Sendmsg},
"cgroup/sendmsg6": {CGroupSockAddr, AttachCGroupUDP6Sendmsg},
"cgroup/recvmsg4": {CGroupSockAddr, AttachCGroupUDP4Recvmsg},
"cgroup/recvmsg6": {CGroupSockAddr, AttachCGroupUDP6Recvmsg},
"cgroup/sysctl": {CGroupSysctl, AttachCGroupSysctl},
"cgroup/getsockopt": {CGroupSockopt, AttachCGroupGetsockopt},
"cgroup/setsockopt": {CGroupSockopt, AttachCGroupSetsockopt},
"classifier": {SchedCLS, AttachNone},
"action": {SchedACT, AttachNone},
}
if sec.Entsize < 16 {
return nil, errors.New("rels are less than 16 bytes")
}
r := sec.Open()
for off := uint64(0); off < sec.Size; off += sec.Entsize {
ent := io.LimitReader(r, int64(sec.Entsize))
var rel elf.Rel64
if binary.Read(ent, ec.ByteOrder, &rel) != nil {
return nil, errors.Errorf("can't parse relocation at offset %v", off)
for prefix, t := range types {
if !strings.HasPrefix(sectionName, prefix) {
continue
}
symNo := int(elf.R_SYM64(rel.Info) - 1)
if symNo >= len(ec.symbols) {
return nil, errors.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
if !strings.HasSuffix(prefix, "/") {
return t.progType, t.attachType, ""
}
rels[rel.Off] = ec.symbols[symNo].Name
return t.progType, t.attachType, sectionName[len(prefix):]
}
return rels, nil
return UnspecifiedProgram, AttachNone, ""
}
func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]string {
result := make(map[elf.SectionIndex]map[uint64]string)
for i, sym := range symbols {
func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, map[elf.SectionIndex]bool, error) {
result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
targets := make(map[elf.SectionIndex]bool)
for idx, sec := range sections {
rels := make(map[uint64]elf.Symbol)
if sec.Entsize < 16 {
return nil, nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name)
}
r := sec.Open()
for off := uint64(0); off < sec.Size; off += sec.Entsize {
ent := io.LimitReader(r, int64(sec.Entsize))
var rel elf.Rel64
if binary.Read(ent, ec.ByteOrder, &rel) != nil {
return nil, nil, fmt.Errorf("can't parse relocation at offset %v", off)
}
symNo := int(elf.R_SYM64(rel.Info) - 1)
if symNo >= len(ec.symbols) {
return nil, nil, fmt.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
}
symbol := ec.symbols[symNo]
targets[symbol.Section] = true
rels[rel.Off] = ec.symbols[symNo]
}
result[idx] = rels
}
return result, targets, nil
}
func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]elf.Symbol {
result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
for _, sym := range symbols {
switch elf.ST_TYPE(sym.Info) {
case elf.STT_NOTYPE:
// Older versions of LLVM doesn't tag
@@ -509,15 +724,19 @@ func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]str
continue
}
if sym.Section == elf.SHN_UNDEF || sym.Section >= elf.SHN_LORESERVE {
continue
}
if sym.Name == "" {
continue
}
idx := sym.Section
if _, ok := result[idx]; !ok {
result[idx] = make(map[uint64]string)
result[idx] = make(map[uint64]elf.Symbol)
}
result[idx][sym.Value] = symbols[i].Name
result[idx][sym.Value] = sym
}
return result
}

View File

@@ -1,8 +1,5 @@
module github.com/cilium/ebpf
go 1.12
go 1.13
require (
github.com/pkg/errors v0.8.1
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7
)
require golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9

View File

@@ -4,20 +4,29 @@ import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"reflect"
"sync"
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
)
const btfMagic = 0xeB9F
// Errors returned by BTF functions.
var (
ErrNotSupported = internal.ErrNotSupported
ErrNotFound = errors.New("not found")
ErrNoExtendedInfo = errors.New("no extended info")
)
// Spec represents decoded BTF.
type Spec struct {
rawTypes []rawType
@@ -25,6 +34,7 @@ type Spec struct {
types map[string][]Type
funcInfos map[string]extInfo
lineInfos map[string]extInfo
byteOrder binary.ByteOrder
}
type btfHeader struct {
@@ -52,6 +62,7 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
var (
btfSection *elf.Section
btfExtSection *elf.Section
sectionSizes = make(map[string]uint32)
)
for _, sec := range file.Sections {
@@ -60,6 +71,16 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
btfSection = sec
case ".BTF.ext":
btfExtSection = sec
default:
if sec.Type != elf.SHT_PROGBITS && sec.Type != elf.SHT_NOBITS {
break
}
if sec.Size > math.MaxUint32 {
return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
}
sectionSizes[sec.Name] = uint32(sec.Size)
}
}
@@ -67,74 +88,59 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
return nil, nil
}
spec, err := parseBTF(btfSection.Open(), file.ByteOrder)
symbols, err := file.Symbols()
if err != nil {
return nil, fmt.Errorf("can't read symbols: %v", err)
}
variableOffsets := make(map[variable]uint32)
for _, symbol := range symbols {
if idx := symbol.Section; idx >= elf.SHN_LORESERVE && idx <= elf.SHN_HIRESERVE {
// Ignore things like SHN_ABS
continue
}
secName := file.Sections[symbol.Section].Name
if _, ok := sectionSizes[secName]; !ok {
continue
}
if symbol.Value > math.MaxUint32 {
return nil, fmt.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name)
}
variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value)
}
spec, err := loadNakedSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
if err != nil {
return nil, err
}
if btfExtSection != nil {
spec.funcInfos, spec.lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
if err != nil {
return nil, errors.Wrap(err, "can't read ext info")
}
if btfExtSection == nil {
return spec, nil
}
spec.funcInfos, spec.lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
if err != nil {
return nil, fmt.Errorf("can't read ext info: %w", err)
}
return spec, nil
}
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) (*Spec, error) {
rawBTF, err := ioutil.ReadAll(btf)
func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
rawTypes, rawStrings, err := parseBTF(btf, bo)
if err != nil {
return nil, errors.Wrap(err, "can't read BTF")
return nil, err
}
rd := bytes.NewReader(rawBTF)
var header btfHeader
if err := binary.Read(rd, bo, &header); err != nil {
return nil, errors.Wrap(err, "can't read header")
}
if header.Magic != btfMagic {
return nil, errors.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, errors.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, errors.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, errors.New("header is too short")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
return nil, errors.Wrap(err, "header padding")
}
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
return nil, errors.Wrap(err, "can't seek to start of string section")
}
strings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
err = fixupDatasec(rawTypes, rawStrings, sectionSizes, variableOffsets)
if err != nil {
return nil, errors.Wrap(err, "can't read type names")
return nil, err
}
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil {
return nil, errors.Wrap(err, "can't seek to start of type section")
}
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
if err != nil {
return nil, errors.Wrap(err, "can't read types")
}
types, err := inflateRawTypes(rawTypes, strings)
types, err := inflateRawTypes(rawTypes, rawStrings)
if err != nil {
return nil, err
}
@@ -142,13 +148,158 @@ func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) (*Spec, error) {
return &Spec{
rawTypes: rawTypes,
types: types,
strings: strings,
funcInfos: make(map[string]extInfo),
lineInfos: make(map[string]extInfo),
strings: rawStrings,
byteOrder: bo,
}, nil
}
func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
var kernelBTF struct {
sync.Mutex
*Spec
}
// LoadKernelSpec returns the current kernel's BTF information.
//
// Requires a >= 5.5 kernel with CONFIG_DEBUG_INFO_BTF enabled. Returns
// ErrNotSupported if BTF is not enabled.
func LoadKernelSpec() (*Spec, error) {
kernelBTF.Lock()
defer kernelBTF.Unlock()
if kernelBTF.Spec != nil {
return kernelBTF.Spec, nil
}
var err error
kernelBTF.Spec, err = loadKernelSpec()
return kernelBTF.Spec, err
}
func loadKernelSpec() (*Spec, error) {
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if os.IsNotExist(err) {
return nil, fmt.Errorf("can't open kernel BTF at /sys/kernel/btf/vmlinux: %w", ErrNotFound)
}
if err != nil {
return nil, fmt.Errorf("can't read kernel BTF: %s", err)
}
defer fh.Close()
return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
}
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) {
rawBTF, err := ioutil.ReadAll(btf)
if err != nil {
return nil, nil, fmt.Errorf("can't read BTF: %v", err)
}
rd := bytes.NewReader(rawBTF)
var header btfHeader
if err := binary.Read(rd, bo, &header); err != nil {
return nil, nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, nil, errors.New("header is too short")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
return nil, nil, fmt.Errorf("header padding: %v", err)
}
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
return nil, nil, fmt.Errorf("can't seek to start of string section: %v", err)
}
rawStrings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
if err != nil {
return nil, nil, fmt.Errorf("can't read type names: %w", err)
}
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil {
return nil, nil, fmt.Errorf("can't seek to start of type section: %v", err)
}
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
if err != nil {
return nil, nil, fmt.Errorf("can't read types: %w", err)
}
return rawTypes, rawStrings, nil
}
type variable struct {
section string
name string
}
func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) error {
for i, rawType := range rawTypes {
if rawType.Kind() != kindDatasec {
continue
}
name, err := rawStrings.Lookup(rawType.NameOff)
if err != nil {
return err
}
if name == ".kconfig" || name == ".ksym" {
return fmt.Errorf("reference to %s: %w", name, ErrNotSupported)
}
size, ok := sectionSizes[name]
if !ok {
return fmt.Errorf("data section %s: missing size", name)
}
rawTypes[i].SizeType = size
secinfos := rawType.data.([]btfVarSecinfo)
for j, secInfo := range secinfos {
id := int(secInfo.Type - 1)
if id >= len(rawTypes) {
return fmt.Errorf("data section %s: invalid type id %d for variable %d", name, id, j)
}
varName, err := rawStrings.Lookup(rawTypes[id].NameOff)
if err != nil {
return fmt.Errorf("data section %s: can't get name for type %d: %w", name, id, err)
}
offset, ok := variableOffsets[variable{name, varName}]
if !ok {
return fmt.Errorf("data section %s: missing offset for variable %s", name, varName)
}
secinfos[j].Offset = offset
}
}
return nil
}
type marshalOpts struct {
ByteOrder binary.ByteOrder
StripFuncLinkage bool
}
func (s *Spec) marshal(opts marshalOpts) ([]byte, error) {
var (
buf bytes.Buffer
header = new(btfHeader)
@@ -160,17 +311,14 @@ func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
_, _ = buf.Write(make([]byte, headerLen))
// Write type section, just after the header.
for _, typ := range s.rawTypes {
if typ.Kind() == kindDatasec {
// Datasec requires patching with information from the ELF
// file. We don't support this at the moment, so patch
// out any Datasec by turning it into a void*.
typ = rawType{}
typ.SetKind(kindPointer)
for _, raw := range s.rawTypes {
switch {
case opts.StripFuncLinkage && raw.Kind() == kindFunc:
raw.SetLinkage(linkageStatic)
}
if err := typ.Marshal(&buf, bo); err != nil {
return nil, errors.Wrap(err, "can't marshal BTF")
if err := raw.Marshal(&buf, opts.ByteOrder); err != nil {
return nil, fmt.Errorf("can't marshal BTF: %w", err)
}
}
@@ -192,9 +340,9 @@ func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
}
raw := buf.Bytes()
err := binary.Write(sliceWriter(raw[:headerLen]), bo, header)
err := binary.Write(sliceWriter(raw[:headerLen]), opts.ByteOrder, header)
if err != nil {
return nil, errors.Wrap(err, "can't write header")
return nil, fmt.Errorf("can't write header: %v", err)
}
return raw, nil
@@ -214,17 +362,22 @@ func (sw sliceWriter) Write(p []byte) (int, error) {
//
// Length is the number of bytes in the raw BPF instruction stream.
//
// Returns an error if there is no BTF.
// Returns an error which may wrap ErrNoExtendedInfo if the Spec doesn't
// contain extended BTF info.
func (s *Spec) Program(name string, length uint64) (*Program, error) {
if length == 0 {
return nil, errors.New("length musn't be zero")
}
if s.funcInfos == nil && s.lineInfos == nil {
return nil, fmt.Errorf("BTF for section %s: %w", name, ErrNoExtendedInfo)
}
funcInfos, funcOK := s.funcInfos[name]
lineInfos, lineOK := s.lineInfos[name]
if !funcOK && !lineOK {
return nil, errors.Errorf("no BTF for program %s", name)
return nil, fmt.Errorf("no extended BTF info for section %s", name)
}
return &Program{s, length, funcInfos, lineInfos}, nil
@@ -233,15 +386,15 @@ func (s *Spec) Program(name string, length uint64) (*Program, error) {
// Map finds the BTF for a map.
//
// Returns an error if there is no BTF for the given name.
func (s *Spec) Map(name string) (*Map, error) {
func (s *Spec) Map(name string) (*Map, []Member, error) {
var mapVar Var
if err := s.FindType(name, &mapVar); err != nil {
return nil, err
return nil, nil, err
}
mapStruct, ok := mapVar.Type.(*Struct)
if !ok {
return nil, errors.Errorf("expected struct, have %s", mapVar.Type)
return nil, nil, fmt.Errorf("expected struct, have %s", mapVar.Type)
}
var key, value Type
@@ -256,23 +409,32 @@ func (s *Spec) Map(name string) (*Map, error) {
}
if key == nil {
return nil, errors.Errorf("map %s: missing 'key' in type", name)
key = (*Void)(nil)
}
if value == nil {
return nil, errors.Errorf("map %s: missing 'value' in type", name)
value = (*Void)(nil)
}
return &Map{mapStruct, s, key, value}, nil
return &Map{s, key, value}, mapStruct.Members, nil
}
var errNotFound = errors.New("not found")
// Datasec returns the BTF required to create maps which represent data sections.
func (s *Spec) Datasec(name string) (*Map, error) {
var datasec Datasec
if err := s.FindType(name, &datasec); err != nil {
return nil, fmt.Errorf("data section %s: can't get BTF: %w", name, err)
}
return &Map{s, &Void{}, &datasec}, nil
}
// FindType searches for a type with a specific name.
//
// hint determines the type of the returned Type.
//
// Returns an error if there is no or multiple matches.
// Returns an error wrapping ErrNotFound if no matching
// type exists in spec.
func (s *Spec) FindType(name string, typ Type) error {
var (
wanted = reflect.TypeOf(typ)
@@ -285,14 +447,14 @@ func (s *Spec) FindType(name string, typ Type) error {
}
if candidate != nil {
return errors.Errorf("type %s: multiple candidates for %T", name, typ)
return fmt.Errorf("type %s: multiple candidates for %T", name, typ)
}
candidate = typ
}
if candidate == nil {
return errors.WithMessagef(errNotFound, "type %s", name)
return fmt.Errorf("type %s: %w", name, ErrNotFound)
}
value := reflect.Indirect(reflect.ValueOf(copyType(candidate)))
@@ -307,16 +469,22 @@ type Handle struct {
// NewHandle loads BTF into the kernel.
//
// Returns an error if BTF is not supported, which can
// be checked by IsNotSupported.
// Returns ErrNotSupported if BTF is not supported.
func NewHandle(spec *Spec) (*Handle, error) {
if err := haveBTF(); err != nil {
return nil, err
}
btf, err := spec.marshal(internal.NativeEndian)
if spec.byteOrder != internal.NativeEndian {
return nil, fmt.Errorf("can't load %s BTF on %s", spec.byteOrder, internal.NativeEndian)
}
btf, err := spec.marshal(marshalOpts{
ByteOrder: internal.NativeEndian,
StripFuncLinkage: haveFuncLinkage() != nil,
})
if err != nil {
return nil, errors.Wrap(err, "can't marshal BTF")
return nil, fmt.Errorf("can't marshal BTF: %w", err)
}
if uint64(len(btf)) > math.MaxUint32 {
@@ -360,7 +528,6 @@ func (h *Handle) FD() int {
// Map is the BTF for a map.
type Map struct {
definition *Struct
spec *Spec
key, value Type
}
@@ -371,12 +538,6 @@ func MapSpec(m *Map) *Spec {
return m.spec
}
// MapType should be a method on Map, but is a free function
// to hide it from users of the ebpf package.
func MapType(m *Map) *Struct {
return m.definition
}
// MapKey should be a method on Map, but is a free function
// to hide it from users of the ebpf package.
func MapKey(m *Map) Type {
@@ -411,12 +572,12 @@ func ProgramSpec(s *Program) *Spec {
func ProgramAppend(s, other *Program) error {
funcInfos, err := s.funcInfos.append(other.funcInfos, s.length)
if err != nil {
return errors.Wrap(err, "func infos")
return fmt.Errorf("func infos: %w", err)
}
lineInfos, err := s.lineInfos.append(other.lineInfos, s.length)
if err != nil {
return errors.Wrap(err, "line infos")
return fmt.Errorf("line infos: %w", err)
}
s.length += other.length
@@ -451,13 +612,6 @@ func ProgramLineInfos(s *Program) (recordSize uint32, bytes []byte, err error) {
return s.lineInfos.recordSize, bytes, nil
}
// IsNotSupported returns true if the error indicates that the kernel
// doesn't support BTF.
func IsNotSupported(err error) bool {
ufe, ok := errors.Cause(err).(*internal.UnsupportedFeatureError)
return ok && ufe.Name == "BTF"
}
type bpfLoadBTFAttr struct {
btf internal.Pointer
logBuf internal.Pointer
@@ -477,26 +631,36 @@ func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
return internal.NewFD(uint32(fd)), nil
}
func minimalBTF(bo binary.ByteOrder) []byte {
func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
const minHeaderLength = 24
typesLen := uint32(binary.Size(types))
header := btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: minHeaderLength,
TypeOff: 0,
TypeLen: typesLen,
StringOff: typesLen,
StringLen: uint32(len(strings)),
}
buf := new(bytes.Buffer)
_ = binary.Write(buf, bo, &header)
_ = binary.Write(buf, bo, types)
buf.Write(strings)
return buf.Bytes()
}
var haveBTF = internal.FeatureTest("BTF", "5.1", func() (bool, error) {
var (
types struct {
Integer btfType
Var btfType
btfVar struct{ Linkage uint32 }
}
typLen = uint32(binary.Size(&types))
strings = []byte{0, 'a', 0}
header = btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: minHeaderLength,
TypeOff: 0,
TypeLen: typLen,
StringOff: typLen,
StringLen: uint32(len(strings)),
}
)
// We use a BTF_KIND_VAR here, to make sure that
@@ -507,16 +671,8 @@ func minimalBTF(bo binary.ByteOrder) []byte {
types.Var.SetKind(kindVar)
types.Var.SizeType = 1
buf := new(bytes.Buffer)
_ = binary.Write(buf, bo, &header)
_ = binary.Write(buf, bo, &types)
buf.Write(strings)
btf := marshalBTF(&types, strings, internal.NativeEndian)
return buf.Bytes()
}
var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool {
btf := minimalBTF(internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
@@ -526,5 +682,35 @@ var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool {
}
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return errors.Cause(err) != unix.EINVAL
return !errors.Is(err, unix.EINVAL), nil
})
var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() (bool, error) {
var (
types struct {
FuncProto btfType
Func btfType
}
strings = []byte{0, 'a', 0}
)
types.FuncProto.SetKind(kindFuncProto)
types.Func.SetKind(kindFunc)
types.Func.SizeType = 1 // aka FuncProto
types.Func.NameOff = 1
types.Func.SetLinkage(linkageGlobal)
btf := marshalBTF(&types, strings, internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
})
if err == nil {
fd.Close()
}
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return !errors.Is(err, unix.EINVAL), nil
})

View File

@@ -2,9 +2,8 @@ package btf
import (
"encoding/binary"
"fmt"
"io"
"github.com/pkg/errors"
)
// btfKind describes a Type.
@@ -32,6 +31,14 @@ const (
kindDatasec
)
type btfFuncLinkage uint8
const (
linkageStatic btfFuncLinkage = iota
linkageGlobal
linkageExtern
)
const (
btfTypeKindShift = 24
btfTypeKindLen = 4
@@ -43,7 +50,7 @@ const (
type btfType struct {
NameOff uint32
/* "info" bits arrangement
* bits 0-15: vlen (e.g. # of struct's members)
* bits 0-15: vlen (e.g. # of struct's members), linkage
* bits 16-23: unused
* bits 24-27: kind (e.g. int, ptr, array...etc)
* bits 28-30: unused
@@ -61,6 +68,45 @@ type btfType struct {
SizeType uint32
}
func (k btfKind) String() string {
switch k {
case kindUnknown:
return "Unknown"
case kindInt:
return "Integer"
case kindPointer:
return "Pointer"
case kindArray:
return "Array"
case kindStruct:
return "Struct"
case kindUnion:
return "Union"
case kindEnum:
return "Enumeration"
case kindForward:
return "Forward"
case kindTypedef:
return "Typedef"
case kindVolatile:
return "Volatile"
case kindConst:
return "Const"
case kindRestrict:
return "Restrict"
case kindFunc:
return "Function"
case kindFuncProto:
return "Function Proto"
case kindVar:
return "Variable"
case kindDatasec:
return "Section"
default:
return fmt.Sprintf("Unknown (%d)", k)
}
}
func mask(len uint32) uint32 {
return (1 << len) - 1
}
@@ -90,6 +136,14 @@ func (bt *btfType) SetVlen(vlen int) {
bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) Linkage() btfFuncLinkage {
return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
}
func (bt *btfType) SetLinkage(linkage btfFuncLinkage) {
bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) Type() TypeID {
// TODO: Panic here if wrong kind?
return TypeID(bt.SizeType)
@@ -129,6 +183,26 @@ type btfMember struct {
Offset uint32
}
type btfVarSecinfo struct {
Type TypeID
Offset uint32
Size uint32
}
type btfVariable struct {
Linkage uint32
}
type btfEnum struct {
NameOff uint32
Val int32
}
type btfParam struct {
NameOff uint32
Type TypeID
}
func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
var (
header btfType
@@ -139,14 +213,13 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
if err := binary.Read(r, bo, &header); err == io.EOF {
return types, nil
} else if err != nil {
return nil, errors.Wrapf(err, "can't read type info for id %v", id)
return nil, fmt.Errorf("can't read type info for id %v: %v", id, err)
}
var data interface{}
switch header.Kind() {
case kindInt:
// sizeof(uint32)
data = make([]byte, 4)
data = new(uint32)
case kindPointer:
case kindArray:
data = new(btfArray)
@@ -155,8 +228,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
case kindUnion:
data = make([]btfMember, header.Vlen())
case kindEnum:
// sizeof(struct btf_enum)
data = make([]byte, header.Vlen()*4*2)
data = make([]btfEnum, header.Vlen())
case kindForward:
case kindTypedef:
case kindVolatile:
@@ -164,16 +236,13 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
case kindRestrict:
case kindFunc:
case kindFuncProto:
// sizeof(struct btf_param)
data = make([]byte, header.Vlen()*4*2)
data = make([]btfParam, header.Vlen())
case kindVar:
// sizeof(struct btf_variable)
data = make([]byte, 4)
data = new(btfVariable)
case kindDatasec:
// sizeof(struct btf_var_secinfo)
data = make([]byte, header.Vlen()*4*3)
data = make([]btfVarSecinfo, header.Vlen())
default:
return nil, errors.Errorf("type id %v: unknown kind: %v", id, header.Kind())
return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind())
}
if data == nil {
@@ -182,7 +251,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
}
if err := binary.Read(r, bo, data); err != nil {
return nil, errors.Wrapf(err, "type id %d: kind %v: can't read %T", id, header.Kind(), data)
return nil, fmt.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err)
}
types = append(types, rawType{header, data})

View File

@@ -3,13 +3,13 @@ package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/pkg/errors"
)
type btfExtHeader struct {
@@ -25,23 +25,21 @@ type btfExtHeader struct {
}
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, err error) {
const expectedMagic = 0xeB9F
var header btfExtHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, nil, errors.Wrap(err, "can't read header")
return nil, nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != expectedMagic {
return nil, nil, errors.Errorf("incorrect magic value %v", header.Magic)
if header.Magic != btfMagic {
return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, errors.Errorf("unexpected version %v", header.Version)
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, errors.Errorf("unsupported flags %v", header.Flags)
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
@@ -53,25 +51,25 @@ func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (f
// .BTF ext header. We need to ignore non-null values.
_, err = io.CopyN(ioutil.Discard, r, remainder)
if err != nil {
return nil, nil, errors.Wrap(err, "header padding")
return nil, nil, fmt.Errorf("header padding: %v", err)
}
if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil {
return nil, nil, errors.Wrap(err, "can't seek to function info section")
return nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
}
funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings)
if err != nil {
return nil, nil, errors.Wrap(err, "function info")
return nil, nil, fmt.Errorf("function info: %w", err)
}
if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil {
return nil, nil, errors.Wrap(err, "can't seek to line info section")
return nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
}
lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings)
if err != nil {
return nil, nil, errors.Wrap(err, "line info")
return nil, nil, fmt.Errorf("line info: %w", err)
}
return funcInfo, lineInfo, nil
@@ -94,7 +92,7 @@ type extInfo struct {
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
if other.recordSize != ei.recordSize {
return extInfo{}, errors.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
}
records := make([]extInfoRecord, 0, len(ei.records)+len(other.records))
@@ -119,7 +117,7 @@ func (ei extInfo) MarshalBinary() ([]byte, error) {
// while the ELF tracks it in bytes.
insnOff := uint32(info.InsnOff / asm.InstructionSize)
if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil {
return nil, errors.Wrap(err, "can't write instruction offset")
return nil, fmt.Errorf("can't write instruction offset: %v", err)
}
buf.Write(info.Opaque)
@@ -131,7 +129,7 @@ func (ei extInfo) MarshalBinary() ([]byte, error) {
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return nil, errors.Wrap(err, "can't read record size")
return nil, fmt.Errorf("can't read record size: %v", err)
}
if recordSize < 4 {
@@ -145,32 +143,32 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st
if err := binary.Read(r, bo, &infoHeader); err == io.EOF {
return result, nil
} else if err != nil {
return nil, errors.Wrap(err, "can't read ext info header")
return nil, fmt.Errorf("can't read ext info header: %v", err)
}
secName, err := strings.Lookup(infoHeader.SecNameOff)
if err != nil {
return nil, errors.Wrap(err, "can't get section name")
return nil, fmt.Errorf("can't get section name: %w", err)
}
if infoHeader.NumInfo == 0 {
return nil, errors.Errorf("section %s has invalid number of records", secName)
return nil, fmt.Errorf("section %s has invalid number of records", secName)
}
var records []extInfoRecord
for i := uint32(0); i < infoHeader.NumInfo; i++ {
var byteOff uint32
if err := binary.Read(r, bo, &byteOff); err != nil {
return nil, errors.Wrapf(err, "section %v: can't read extended info offset", secName)
return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err)
}
buf := make([]byte, int(recordSize-4))
if _, err := io.ReadFull(r, buf); err != nil {
return nil, errors.Wrapf(err, "section %v: can't read record", secName)
return nil, fmt.Errorf("section %v: can't read record: %v", secName, err)
}
if byteOff%asm.InstructionSize != 0 {
return nil, errors.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
}
records = append(records, extInfoRecord{uint64(byteOff), buf})

View File

@@ -2,10 +2,10 @@ package btf
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"github.com/pkg/errors"
)
type stringTable []byte
@@ -13,7 +13,7 @@ type stringTable []byte
func readStringTable(r io.Reader) (stringTable, error) {
contents, err := ioutil.ReadAll(r)
if err != nil {
return nil, errors.Wrap(err, "can't read string table")
return nil, fmt.Errorf("can't read string table: %v", err)
}
if len(contents) < 1 {
@@ -33,22 +33,22 @@ func readStringTable(r io.Reader) (stringTable, error) {
func (st stringTable) Lookup(offset uint32) (string, error) {
if int64(offset) > int64(^uint(0)>>1) {
return "", errors.Errorf("offset %d overflows int", offset)
return "", fmt.Errorf("offset %d overflows int", offset)
}
pos := int(offset)
if pos >= len(st) {
return "", errors.Errorf("offset %d is out of bounds", offset)
return "", fmt.Errorf("offset %d is out of bounds", offset)
}
if pos > 0 && st[pos-1] != '\x00' {
return "", errors.Errorf("offset %d isn't start of a string", offset)
return "", fmt.Errorf("offset %d isn't start of a string", offset)
}
str := st[pos:]
end := bytes.IndexByte(str, '\x00')
if end == -1 {
return "", errors.Errorf("offset %d isn't null terminated", offset)
return "", fmt.Errorf("offset %d isn't null terminated", offset)
}
return string(str[:end]), nil

View File

@@ -1,9 +1,9 @@
package btf
import (
"errors"
"fmt"
"math"
"github.com/pkg/errors"
)
const maxTypeDepth = 32
@@ -38,9 +38,10 @@ func (n Name) name() string {
// Void is the unit type of BTF.
type Void struct{}
func (v Void) ID() TypeID { return 0 }
func (v Void) copy() Type { return Void{} }
func (v Void) walk(*copyStack) {}
func (v *Void) ID() TypeID { return 0 }
func (v *Void) size() uint32 { return 0 }
func (v *Void) copy() Type { return (*Void)(nil) }
func (v *Void) walk(*copyStack) {}
// Int is an integer of a given length.
type Int struct {
@@ -103,7 +104,8 @@ func (s *Struct) walk(cs *copyStack) {
func (s *Struct) copy() Type {
cpy := *s
cpy.Members = copyMembers(cpy.Members)
cpy.Members = make([]Member, len(s.Members))
copy(cpy.Members, s.Members)
return &cpy
}
@@ -126,7 +128,8 @@ func (u *Union) walk(cs *copyStack) {
func (u *Union) copy() Type {
cpy := *u
cpy.Members = copyMembers(cpy.Members)
cpy.Members = make([]Member, len(u.Members))
copy(cpy.Members, u.Members)
return &cpy
}
@@ -139,14 +142,6 @@ type Member struct {
Offset uint32
}
func copyMembers(in []Member) []Member {
cpy := make([]Member, 0, len(in))
for _, member := range in {
cpy = append(cpy, member)
}
return cpy
}
// Enum lists possible values.
type Enum struct {
TypeID
@@ -265,15 +260,31 @@ type Datasec struct {
TypeID
Name
Size uint32
Vars []VarSecinfo
}
func (ds *Datasec) size() uint32 { return ds.Size }
func (ds *Datasec) walk(cs *copyStack) {
for i := range ds.Vars {
cs.push(&ds.Vars[i].Type)
}
}
func (ds *Datasec) size() uint32 { return ds.Size }
func (ds *Datasec) walk(*copyStack) {}
func (ds *Datasec) copy() Type {
cpy := *ds
cpy.Vars = make([]VarSecinfo, len(ds.Vars))
copy(cpy.Vars, ds.Vars)
return &cpy
}
// VarSecinfo describes variable in a Datasec
type VarSecinfo struct {
Type Type
Offset uint32
Size uint32
}
type sizer interface {
size() uint32
}
@@ -326,7 +337,7 @@ func Sizeof(typ Type) (int, error) {
continue
default:
return 0, errors.Errorf("unrecognized type %T", typ)
return 0, fmt.Errorf("unrecognized type %T", typ)
}
if n > 0 && elem > math.MaxInt64/n {
@@ -405,12 +416,17 @@ var _ namer = Name("")
// compilation units, multiple types may share the same name. A Type may form a
// cyclic graph by pointing at itself.
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map[string][]Type, err error) {
type fixup struct {
id TypeID
typ *Type
type fixupDef struct {
id TypeID
expectedKind btfKind
typ *Type
}
var fixups []fixupDef
fixup := func(id TypeID, expectedKind btfKind, typ *Type) {
fixups = append(fixups, fixupDef{id, expectedKind, typ})
}
var fixups []fixup
convertMembers := func(raw []btfMember) ([]Member, error) {
// NB: The fixup below relies on pre-allocating this array to
// work, since otherwise append might re-allocate members.
@@ -418,7 +434,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
for i, btfMember := range raw {
name, err := rawStrings.LookupName(btfMember.NameOff)
if err != nil {
return nil, errors.Wrapf(err, "can't get name for member %d", i)
return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
}
members = append(members, Member{
Name: name,
@@ -426,13 +442,13 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
})
}
for i := range members {
fixups = append(fixups, fixup{raw[i].Type, &members[i].Type})
fixup(raw[i].Type, kindUnknown, &members[i].Type)
}
return members, nil
}
types := make([]Type, 0, len(rawTypes))
types = append(types, Void{})
types = append(types, (*Void)(nil))
namedTypes = make(map[string][]Type)
for i, raw := range rawTypes {
@@ -445,7 +461,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
name, err := rawStrings.LookupName(raw.NameOff)
if err != nil {
return nil, errors.Wrapf(err, "can't get name for type id %d", id)
return nil, fmt.Errorf("can't get name for type id %d: %w", id, err)
}
switch raw.Kind() {
@@ -454,7 +470,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
case kindPointer:
ptr := &Pointer{id, nil}
fixups = append(fixups, fixup{raw.Type(), &ptr.Target})
fixup(raw.Type(), kindUnknown, &ptr.Target)
typ = ptr
case kindArray:
@@ -463,20 +479,20 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
// IndexType is unused according to btf.rst.
// Don't make it available right now.
arr := &Array{id, nil, btfArr.Nelems}
fixups = append(fixups, fixup{btfArr.Type, &arr.Type})
fixup(btfArr.Type, kindUnknown, &arr.Type)
typ = arr
case kindStruct:
members, err := convertMembers(raw.data.([]btfMember))
if err != nil {
return nil, errors.Wrapf(err, "struct %s (id %d)", name, id)
return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
}
typ = &Struct{id, name, raw.Size(), members}
case kindUnion:
members, err := convertMembers(raw.data.([]btfMember))
if err != nil {
return nil, errors.Wrapf(err, "union %s (id %d)", name, id)
return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
}
typ = &Union{id, name, raw.Size(), members}
@@ -488,44 +504,55 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
case kindTypedef:
typedef := &Typedef{id, name, nil}
fixups = append(fixups, fixup{raw.Type(), &typedef.Type})
fixup(raw.Type(), kindUnknown, &typedef.Type)
typ = typedef
case kindVolatile:
volatile := &Volatile{id, nil}
fixups = append(fixups, fixup{raw.Type(), &volatile.Type})
fixup(raw.Type(), kindUnknown, &volatile.Type)
typ = volatile
case kindConst:
cnst := &Const{id, nil}
fixups = append(fixups, fixup{raw.Type(), &cnst.Type})
fixup(raw.Type(), kindUnknown, &cnst.Type)
typ = cnst
case kindRestrict:
restrict := &Restrict{id, nil}
fixups = append(fixups, fixup{raw.Type(), &restrict.Type})
fixup(raw.Type(), kindUnknown, &restrict.Type)
typ = restrict
case kindFunc:
fn := &Func{id, name, nil}
fixups = append(fixups, fixup{raw.Type(), &fn.Type})
fixup(raw.Type(), kindFuncProto, &fn.Type)
typ = fn
case kindFuncProto:
fp := &FuncProto{id, nil}
fixups = append(fixups, fixup{raw.Type(), &fp.Return})
fixup(raw.Type(), kindUnknown, &fp.Return)
typ = fp
case kindVar:
v := &Var{id, name, nil}
fixups = append(fixups, fixup{raw.Type(), &v.Type})
fixup(raw.Type(), kindUnknown, &v.Type)
typ = v
case kindDatasec:
typ = &Datasec{id, name, raw.SizeType}
btfVars := raw.data.([]btfVarSecinfo)
vars := make([]VarSecinfo, 0, len(btfVars))
for _, btfVar := range btfVars {
vars = append(vars, VarSecinfo{
Offset: btfVar.Offset,
Size: btfVar.Size,
})
}
for i := range vars {
fixup(btfVars[i].Type, kindVar, &vars[i].Type)
}
typ = &Datasec{id, name, raw.SizeType, vars}
default:
return nil, errors.Errorf("type id %d: unknown kind: %v", id, raw.Kind())
return nil, fmt.Errorf("type id %d: unknown kind: %v", id, raw.Kind())
}
types = append(types, typ)
@@ -540,7 +567,17 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
for _, fixup := range fixups {
i := int(fixup.id)
if i >= len(types) {
return nil, errors.Errorf("reference to invalid type id: %d", fixup.id)
return nil, fmt.Errorf("reference to invalid type id: %d", fixup.id)
}
// Default void (id 0) to unknown
rawKind := kindUnknown
if i > 0 {
rawKind = rawTypes[i-1].Kind()
}
if expected := fixup.expectedKind; expected != kindUnknown && rawKind != expected {
return nil, fmt.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind)
}
*fixup.typ = types[i]

View File

@@ -2,10 +2,9 @@ package internal
import (
"fmt"
"os"
"io/ioutil"
"strings"
"sync"
"github.com/pkg/errors"
)
var sysCPU struct {
@@ -18,45 +17,44 @@ var sysCPU struct {
// Logical CPU numbers must be of the form 0-n
func PossibleCPUs() (int, error) {
sysCPU.once.Do(func() {
sysCPU.num, sysCPU.err = parseCPUs("/sys/devices/system/cpu/possible")
sysCPU.num, sysCPU.err = parseCPUsFromFile("/sys/devices/system/cpu/possible")
})
return sysCPU.num, sysCPU.err
}
var onlineCPU struct {
once sync.Once
err error
num int
}
// OnlineCPUs returns the number of currently online CPUs
// Logical CPU numbers must be of the form 0-n
func OnlineCPUs() (int, error) {
onlineCPU.once.Do(func() {
onlineCPU.num, onlineCPU.err = parseCPUs("/sys/devices/system/cpu/online")
})
return onlineCPU.num, onlineCPU.err
}
// parseCPUs parses the number of cpus from sysfs,
// in the format of "/sys/devices/system/cpu/{possible,online,..}.
// Logical CPU numbers must be of the form 0-n
func parseCPUs(path string) (int, error) {
file, err := os.Open(path)
func parseCPUsFromFile(path string) (int, error) {
spec, err := ioutil.ReadFile(path)
if err != nil {
return 0, err
}
defer file.Close()
n, err := parseCPUs(string(spec))
if err != nil {
return 0, fmt.Errorf("can't parse %s: %v", path, err)
}
return n, nil
}
// parseCPUs parses the number of cpus from a string produced
// by bitmap_list_string() in the Linux kernel.
// Multiple ranges are rejected, since they can't be unified
// into a single number.
// This is the format of /sys/devices/system/cpu/possible, it
// is not suitable for /sys/devices/system/cpu/online, etc.
func parseCPUs(spec string) (int, error) {
if strings.Trim(spec, "\n") == "0" {
return 1, nil
}
var low, high int
n, _ := fmt.Fscanf(file, "%d-%d", &low, &high)
if n < 1 || low != 0 {
return 0, errors.Wrapf(err, "%s has unknown format", path)
n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high)
if n != 2 || err != nil {
return 0, fmt.Errorf("invalid format: %s", spec)
}
if n == 1 {
high = low
if low != 0 {
return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec)
}
// cpus is 0 indexed

View File

@@ -2,11 +2,11 @@ package internal
import (
"bytes"
"errors"
"fmt"
"strings"
"github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
)
// ErrorWithLog returns an error that includes logs from the
@@ -16,19 +16,20 @@ import (
// the log. It is used to check for truncation of the output.
func ErrorWithLog(err error, log []byte, logErr error) error {
logStr := strings.Trim(CString(log), "\t\r\n ")
if errors.Cause(logErr) == unix.ENOSPC {
if errors.Is(logErr, unix.ENOSPC) {
logStr += " (truncated...)"
}
return &loadError{err, logStr}
return &VerifierError{err, logStr}
}
type loadError struct {
// VerifierError includes information from the eBPF verifier.
type VerifierError struct {
cause error
log string
}
func (le *loadError) Error() string {
func (le *VerifierError) Error() string {
if le.log == "" {
return le.cause.Error()
}
@@ -36,10 +37,6 @@ func (le *loadError) Error() string {
return fmt.Sprintf("%s: %s", le.cause, le.log)
}
func (le *loadError) Cause() error {
return le.cause
}
// CString turns a NUL / zero terminated byte buffer into a string.
func CString(in []byte) string {
inLen := bytes.IndexByte(in, 0)

View File

@@ -1,12 +1,13 @@
package internal
import (
"errors"
"fmt"
"os"
"runtime"
"strconv"
"github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
)
var ErrClosedFd = errors.New("use of closed file descriptor")
@@ -56,8 +57,13 @@ func (fd *FD) Dup() (*FD, error) {
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0)
if err != nil {
return nil, errors.Wrap(err, "can't dup fd")
return nil, fmt.Errorf("can't dup fd: %v", err)
}
return NewFD(uint32(dup)), nil
}
func (fd *FD) File(name string) *os.File {
fd.Forget()
return os.NewFile(uintptr(fd.raw), name)
}

View File

@@ -1,12 +1,14 @@
package internal
import (
"errors"
"fmt"
"sync"
"github.com/pkg/errors"
)
// ErrNotSupported indicates that a feature is not supported by the current kernel.
var ErrNotSupported = errors.New("not supported")
// UnsupportedFeatureError is returned by FeatureTest() functions.
type UnsupportedFeatureError struct {
// The minimum Linux mainline version required for this feature.
@@ -21,33 +23,68 @@ func (ufe *UnsupportedFeatureError) Error() string {
return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion)
}
// Is indicates that UnsupportedFeatureError is ErrNotSupported.
func (ufe *UnsupportedFeatureError) Is(target error) bool {
return target == ErrNotSupported
}
type featureTest struct {
sync.Mutex
successful bool
result error
}
// FeatureTestFn is used to determine whether the kernel supports
// a certain feature.
//
// The return values have the following semantics:
//
// err != nil: the test couldn't be executed
// err == nil && available: the feature is available
// err == nil && !available: the feature isn't available
type FeatureTestFn func() (available bool, err error)
// FeatureTest wraps a function so that it is run at most once.
//
// name should identify the tested feature, while version must be in the
// form Major.Minor[.Patch].
//
// Returns a descriptive UnsupportedFeatureError if the feature is not available.
func FeatureTest(name, version string, fn func() bool) func() error {
// Returns an error wrapping ErrNotSupported if the feature is not supported.
func FeatureTest(name, version string, fn FeatureTestFn) func() error {
v, err := NewVersion(version)
if err != nil {
return func() error { return err }
}
var (
once sync.Once
result error
)
ft := new(featureTest)
return func() error {
once.Do(func() {
if !fn() {
result = &UnsupportedFeatureError{
MinimumVersion: v,
Name: name,
}
ft.Lock()
defer ft.Unlock()
if ft.successful {
return ft.result
}
available, err := fn()
if errors.Is(err, ErrNotSupported) {
// The feature test aborted because a dependent feature
// is missing, which we should cache.
available = false
} else if err != nil {
// We couldn't execute the feature test to a point
// where it could make a determination.
// Don't cache the result, just return it.
return fmt.Errorf("can't detect support for %s: %w", name, err)
}
ft.successful = true
if !available {
ft.result = &UnsupportedFeatureError{
MinimumVersion: v,
Name: name,
}
})
return result
}
return ft.result
}
}
@@ -61,7 +98,7 @@ func NewVersion(ver string) (Version, error) {
var major, minor, patch uint16
n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
if n < 2 {
return Version{}, errors.Errorf("invalid version: %s", ver)
return Version{}, fmt.Errorf("invalid version: %s", ver)
}
return Version{major, minor, patch}, nil
}

View File

@@ -1,6 +1,6 @@
package internal
import "github.com/pkg/errors"
import "errors"
// DiscardZeroes makes sure that all written bytes are zero
// before discarding them.

View File

@@ -22,5 +22,9 @@ func NewStringPointer(str string) Pointer {
return Pointer{}
}
return Pointer{ptr: unsafe.Pointer(&[]byte(str)[0])}
// The kernel expects strings to be zero terminated
buf := make([]byte, len(str)+1)
copy(buf, str)
return Pointer{ptr: unsafe.Pointer(&buf[0])}
}

View File

@@ -1,16 +1,61 @@
package internal
import (
"fmt"
"path/filepath"
"runtime"
"unsafe"
"github.com/cilium/ebpf/internal/unix"
)
//go:generate stringer -output syscall_string.go -type=BPFCmd
// BPFCmd identifies a subcommand of the bpf syscall.
type BPFCmd int
// Well known BPF commands.
const (
BPF_MAP_CREATE BPFCmd = iota
BPF_MAP_LOOKUP_ELEM
BPF_MAP_UPDATE_ELEM
BPF_MAP_DELETE_ELEM
BPF_MAP_GET_NEXT_KEY
BPF_PROG_LOAD
BPF_OBJ_PIN
BPF_OBJ_GET
BPF_PROG_ATTACH
BPF_PROG_DETACH
BPF_PROG_TEST_RUN
BPF_PROG_GET_NEXT_ID
BPF_MAP_GET_NEXT_ID
BPF_PROG_GET_FD_BY_ID
BPF_MAP_GET_FD_BY_ID
BPF_OBJ_GET_INFO_BY_FD
BPF_PROG_QUERY
BPF_RAW_TRACEPOINT_OPEN
BPF_BTF_LOAD
BPF_BTF_GET_FD_BY_ID
BPF_TASK_FD_QUERY
BPF_MAP_LOOKUP_AND_DELETE_ELEM
BPF_MAP_FREEZE
BPF_BTF_GET_NEXT_ID
BPF_MAP_LOOKUP_BATCH
BPF_MAP_LOOKUP_AND_DELETE_BATCH
BPF_MAP_UPDATE_BATCH
BPF_MAP_DELETE_BATCH
BPF_LINK_CREATE
BPF_LINK_UPDATE
BPF_LINK_GET_FD_BY_ID
BPF_LINK_GET_NEXT_ID
BPF_ENABLE_STATS
BPF_ITER_CREATE
)
// BPF wraps SYS_BPF.
//
// Any pointers contained in attr must use the Pointer type from this package.
func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) {
func BPF(cmd BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
runtime.KeepAlive(attr)
@@ -21,3 +66,74 @@ func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) {
return r1, err
}
type BPFProgAttachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
AttachFlags uint32
ReplaceBpfFd uint32
}
func BPFProgAttach(attr *BPFProgAttachAttr) error {
_, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type BPFProgDetachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
}
func BPFProgDetach(attr *BPFProgDetachAttr) error {
_, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type bpfObjAttr struct {
fileName Pointer
fd uint32
fileFlags uint32
}
const bpfFSType = 0xcafe4a11
// BPFObjPin wraps BPF_OBJ_PIN.
func BPFObjPin(fileName string, fd *FD) error {
dirName := filepath.Dir(fileName)
var statfs unix.Statfs_t
if err := unix.Statfs(dirName, &statfs); err != nil {
return err
}
if uint64(statfs.Type) != bpfFSType {
return fmt.Errorf("%s is not on a bpf filesystem", fileName)
}
value, err := fd.Value()
if err != nil {
return err
}
attr := bpfObjAttr{
fileName: NewStringPointer(fileName),
fd: value,
}
_, err = BPF(BPF_OBJ_PIN, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return fmt.Errorf("pin object %s: %w", fileName, err)
}
return nil
}
// BPFObjGet wraps BPF_OBJ_GET.
func BPFObjGet(fileName string) (*FD, error) {
attr := bpfObjAttr{
fileName: NewStringPointer(fileName),
}
ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return nil, fmt.Errorf("get object %s: %w", fileName, err)
}
return NewFD(uint32(ptr)), nil
}

View File

@@ -0,0 +1,56 @@
// Code generated by "stringer -output syscall_string.go -type=BPFCmd"; DO NOT EDIT.
package internal
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[BPF_MAP_CREATE-0]
_ = x[BPF_MAP_LOOKUP_ELEM-1]
_ = x[BPF_MAP_UPDATE_ELEM-2]
_ = x[BPF_MAP_DELETE_ELEM-3]
_ = x[BPF_MAP_GET_NEXT_KEY-4]
_ = x[BPF_PROG_LOAD-5]
_ = x[BPF_OBJ_PIN-6]
_ = x[BPF_OBJ_GET-7]
_ = x[BPF_PROG_ATTACH-8]
_ = x[BPF_PROG_DETACH-9]
_ = x[BPF_PROG_TEST_RUN-10]
_ = x[BPF_PROG_GET_NEXT_ID-11]
_ = x[BPF_MAP_GET_NEXT_ID-12]
_ = x[BPF_PROG_GET_FD_BY_ID-13]
_ = x[BPF_MAP_GET_FD_BY_ID-14]
_ = x[BPF_OBJ_GET_INFO_BY_FD-15]
_ = x[BPF_PROG_QUERY-16]
_ = x[BPF_RAW_TRACEPOINT_OPEN-17]
_ = x[BPF_BTF_LOAD-18]
_ = x[BPF_BTF_GET_FD_BY_ID-19]
_ = x[BPF_TASK_FD_QUERY-20]
_ = x[BPF_MAP_LOOKUP_AND_DELETE_ELEM-21]
_ = x[BPF_MAP_FREEZE-22]
_ = x[BPF_BTF_GET_NEXT_ID-23]
_ = x[BPF_MAP_LOOKUP_BATCH-24]
_ = x[BPF_MAP_LOOKUP_AND_DELETE_BATCH-25]
_ = x[BPF_MAP_UPDATE_BATCH-26]
_ = x[BPF_MAP_DELETE_BATCH-27]
_ = x[BPF_LINK_CREATE-28]
_ = x[BPF_LINK_UPDATE-29]
_ = x[BPF_LINK_GET_FD_BY_ID-30]
_ = x[BPF_LINK_GET_NEXT_ID-31]
_ = x[BPF_ENABLE_STATS-32]
_ = x[BPF_ITER_CREATE-33]
}
const _BPFCmd_name = "BPF_MAP_CREATEBPF_MAP_LOOKUP_ELEMBPF_MAP_UPDATE_ELEMBPF_MAP_DELETE_ELEMBPF_MAP_GET_NEXT_KEYBPF_PROG_LOADBPF_OBJ_PINBPF_OBJ_GETBPF_PROG_ATTACHBPF_PROG_DETACHBPF_PROG_TEST_RUNBPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_PROG_GET_FD_BY_IDBPF_MAP_GET_FD_BY_IDBPF_OBJ_GET_INFO_BY_FDBPF_PROG_QUERYBPF_RAW_TRACEPOINT_OPENBPF_BTF_LOADBPF_BTF_GET_FD_BY_IDBPF_TASK_FD_QUERYBPF_MAP_LOOKUP_AND_DELETE_ELEMBPF_MAP_FREEZEBPF_BTF_GET_NEXT_IDBPF_MAP_LOOKUP_BATCHBPF_MAP_LOOKUP_AND_DELETE_BATCHBPF_MAP_UPDATE_BATCHBPF_MAP_DELETE_BATCHBPF_LINK_CREATEBPF_LINK_UPDATEBPF_LINK_GET_FD_BY_IDBPF_LINK_GET_NEXT_IDBPF_ENABLE_STATSBPF_ITER_CREATE"
var _BPFCmd_index = [...]uint16{0, 14, 33, 52, 71, 91, 104, 115, 126, 141, 156, 173, 193, 212, 233, 253, 275, 289, 312, 324, 344, 361, 391, 405, 424, 444, 475, 495, 515, 530, 545, 566, 586, 602, 617}
func (i BPFCmd) String() string {
if i < 0 || i >= BPFCmd(len(_BPFCmd_index)-1) {
return "BPFCmd(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _BPFCmd_name[_BPFCmd_index[i]:_BPFCmd_index[i+1]]
}

View File

@@ -10,10 +10,17 @@ import (
const (
ENOENT = linux.ENOENT
EEXIST = linux.EEXIST
EAGAIN = linux.EAGAIN
ENOSPC = linux.ENOSPC
EINVAL = linux.EINVAL
EPOLLIN = linux.EPOLLIN
EINTR = linux.EINTR
EPERM = linux.EPERM
ESRCH = linux.ESRCH
ENODEV = linux.ENODEV
BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG
BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG
BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN
BPF_TAG_SIZE = linux.BPF_TAG_SIZE
SYS_BPF = linux.SYS_BPF
@@ -31,6 +38,7 @@ const (
PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW
PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC
RLIM_INFINITY = linux.RLIM_INFINITY
RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK
)
// Statfs_t is a wrapper
@@ -125,3 +133,18 @@ type Utsname = linux.Utsname
func Uname(buf *Utsname) (err error) {
return linux.Uname(buf)
}
// Getpid is a wrapper
func Getpid() int {
return linux.Getpid()
}
// Gettid is a wrapper
func Gettid() int {
return linux.Gettid()
}
// Tgkill is a wrapper
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return linux.Tgkill(tgid, tid, sig)
}

View File

@@ -12,9 +12,16 @@ var errNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime
const (
ENOENT = syscall.ENOENT
EEXIST = syscall.EEXIST
EAGAIN = syscall.EAGAIN
ENOSPC = syscall.ENOSPC
EINVAL = syscall.EINVAL
EINTR = syscall.EINTR
EPERM = syscall.EPERM
ESRCH = syscall.ESRCH
ENODEV = syscall.ENODEV
BPF_F_RDONLY_PROG = 0
BPF_F_WRONLY_PROG = 0
BPF_OBJ_NAME_LEN = 0x10
BPF_TAG_SIZE = 0x8
SYS_BPF = 321
@@ -32,6 +39,8 @@ const (
PerfBitWatermark = 0x4000
PERF_SAMPLE_RAW = 0x400
PERF_FLAG_FD_CLOEXEC = 0x8
RLIM_INFINITY = 0x7fffffffffffffff
RLIMIT_MEMLOCK = 8
)
// Statfs_t is a wrapper
@@ -184,10 +193,25 @@ func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int
// Utsname is a wrapper
type Utsname struct {
Release [65]byte
Release [65]byte
}
// Uname is a wrapper
func Uname(buf *Utsname) (err error) {
return errNonLinux
}
// Getpid is a wrapper
func Getpid() int {
return -1
}
// Gettid is a wrapper
func Gettid() int {
return -1
}
// Tgkill is a wrapper
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return errNonLinux
}

View File

@@ -1,43 +1,60 @@
package ebpf
import (
"fmt"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal/btf"
"github.com/pkg/errors"
)
// link resolves bpf-to-bpf calls.
//
// Each library may contain multiple functions / labels, and is only linked
// if the program being edited references one of these functions.
// if prog references one of these functions.
//
// Libraries must not require linking themselves.
// Libraries also linked.
func link(prog *ProgramSpec, libs []*ProgramSpec) error {
for _, lib := range libs {
insns, err := linkSection(prog.Instructions, lib.Instructions)
if err != nil {
return errors.Wrapf(err, "linking %s", lib.Name)
}
var (
linked = make(map[*ProgramSpec]bool)
pending = []asm.Instructions{prog.Instructions}
insns asm.Instructions
)
for len(pending) > 0 {
insns, pending = pending[0], pending[1:]
for _, lib := range libs {
if linked[lib] {
continue
}
if len(insns) == len(prog.Instructions) {
continue
}
needed, err := needSection(insns, lib.Instructions)
if err != nil {
return fmt.Errorf("linking %s: %w", lib.Name, err)
}
prog.Instructions = insns
if prog.BTF != nil && lib.BTF != nil {
if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil {
return errors.Wrapf(err, "linking BTF of %s", lib.Name)
if !needed {
continue
}
linked[lib] = true
prog.Instructions = append(prog.Instructions, lib.Instructions...)
pending = append(pending, lib.Instructions)
if prog.BTF != nil && lib.BTF != nil {
if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil {
return fmt.Errorf("linking BTF of %s: %w", lib.Name, err)
}
}
}
}
return nil
}
func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
func needSection(insns, section asm.Instructions) (bool, error) {
// A map of symbols to the libraries which contain them.
symbols, err := section.SymbolOffsets()
if err != nil {
return nil, err
return false, err
}
for _, ins := range insns {
@@ -45,7 +62,7 @@ func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
continue
}
if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.R1 {
if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.PseudoCall {
continue
}
@@ -60,11 +77,10 @@ func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
}
// At this point we know that at least one function in the
// library is called from insns. Merge the two sections.
// The rewrite of ins.Constant happens in asm.Instruction.Marshal.
return append(insns, section...), nil
// library is called from insns, so we have to link it.
return true, nil
}
// None of the functions in the section are called. Do nothing.
return insns, nil
// None of the functions in the section are called.
return false, nil
}

331
vendor/github.com/cilium/ebpf/map.go generated vendored
View File

@@ -1,15 +1,25 @@
package ebpf
import (
"errors"
"fmt"
"strings"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
)
// Errors returned by Map and MapIterator methods.
var (
ErrKeyNotExist = errors.New("key does not exist")
ErrKeyExist = errors.New("key already exists")
ErrIterationAborted = errors.New("iteration aborted")
)
// MapID represents the unique ID of an eBPF map
type MapID uint32
// MapSpec defines a Map.
type MapSpec struct {
// Name is passed to the kernel as a debug aid. Must only contain
@@ -21,6 +31,12 @@ type MapSpec struct {
MaxEntries uint32
Flags uint32
// The initial contents of the map. May be nil.
Contents []MapKV
// Whether to freeze a map after setting its initial contents.
Freeze bool
// InnerMap is used as a template for ArrayOfMaps and HashOfMaps
InnerMap *MapSpec
@@ -33,16 +49,26 @@ func (ms *MapSpec) String() string {
}
// Copy returns a copy of the spec.
//
// MapSpec.Contents is a shallow copy.
func (ms *MapSpec) Copy() *MapSpec {
if ms == nil {
return nil
}
cpy := *ms
cpy.Contents = make([]MapKV, len(ms.Contents))
copy(cpy.Contents, ms.Contents)
cpy.InnerMap = ms.InnerMap.Copy()
return &cpy
}
// MapKV is used to initialize the contents of a Map.
type MapKV struct {
Key interface{}
Value interface{}
}
// Map represents a Map file descriptor.
//
// It is not safe to close a map which is used by other goroutines.
@@ -81,14 +107,18 @@ func NewMapFromFD(fd int) (*Map, error) {
//
// Creating a map for the first time will perform feature detection
// by creating small, temporary maps.
//
// The caller is responsible for ensuring the process' rlimit is set
// sufficiently high for locking memory during map creation. This can be done
// by calling unix.Setrlimit with unix.RLIMIT_MEMLOCK prior to calling NewMap.
func NewMap(spec *MapSpec) (*Map, error) {
if spec.BTF == nil {
return newMapWithBTF(spec, nil)
}
handle, err := btf.NewHandle(btf.MapSpec(spec.BTF))
if err != nil && !btf.IsNotSupported(err) {
return nil, errors.Wrap(err, "can't load BTF")
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, fmt.Errorf("can't load BTF: %w", err)
}
return newMapWithBTF(spec, handle)
@@ -100,7 +130,7 @@ func newMapWithBTF(spec *MapSpec, handle *btf.Handle) (*Map, error) {
}
if spec.InnerMap == nil {
return nil, errors.Errorf("%s requires InnerMap", spec.Type)
return nil, fmt.Errorf("%s requires InnerMap", spec.Type)
}
template, err := createMap(spec.InnerMap, nil, handle)
@@ -113,7 +143,7 @@ func newMapWithBTF(spec *MapSpec, handle *btf.Handle) (*Map, error) {
}
func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, error) {
spec = spec.Copy()
abi := newMapABIFromSpec(spec)
switch spec.Type {
case ArrayOfMaps:
@@ -123,43 +153,50 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
return nil, err
}
if spec.ValueSize != 0 && spec.ValueSize != 4 {
return nil, errors.Errorf("ValueSize must be zero or four for map of map")
if abi.ValueSize != 0 && abi.ValueSize != 4 {
return nil, errors.New("ValueSize must be zero or four for map of map")
}
spec.ValueSize = 4
abi.ValueSize = 4
case PerfEventArray:
if spec.KeySize != 0 {
return nil, errors.Errorf("KeySize must be zero for perf event array")
}
if spec.ValueSize != 0 {
return nil, errors.Errorf("ValueSize must be zero for perf event array")
}
if spec.MaxEntries == 0 {
n, err := internal.OnlineCPUs()
if err != nil {
return nil, errors.Wrap(err, "perf event array")
}
spec.MaxEntries = uint32(n)
if abi.KeySize != 0 && abi.KeySize != 4 {
return nil, errors.New("KeySize must be zero or four for perf event array")
}
abi.KeySize = 4
spec.KeySize = 4
spec.ValueSize = 4
if abi.ValueSize != 0 && abi.ValueSize != 4 {
return nil, errors.New("ValueSize must be zero or four for perf event array")
}
abi.ValueSize = 4
if abi.MaxEntries == 0 {
n, err := internal.PossibleCPUs()
if err != nil {
return nil, fmt.Errorf("perf event array: %w", err)
}
abi.MaxEntries = uint32(n)
}
}
if abi.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze {
if err := haveMapMutabilityModifiers(); err != nil {
return nil, fmt.Errorf("map create: %w", err)
}
}
attr := bpfMapCreateAttr{
mapType: spec.Type,
keySize: spec.KeySize,
valueSize: spec.ValueSize,
maxEntries: spec.MaxEntries,
flags: spec.Flags,
mapType: abi.Type,
keySize: abi.KeySize,
valueSize: abi.ValueSize,
maxEntries: abi.MaxEntries,
flags: abi.Flags,
}
if inner != nil {
var err error
attr.innerMapFd, err = inner.Value()
if err != nil {
return nil, errors.Wrap(err, "map create")
return nil, fmt.Errorf("map create: %w", err)
}
}
@@ -169,21 +206,33 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
attr.btfValueTypeID = btf.MapValue(spec.BTF).ID()
}
name, err := newBPFObjName(spec.Name)
if err != nil {
return nil, errors.Wrap(err, "map create")
}
if haveObjName() == nil {
attr.mapName = name
attr.mapName = newBPFObjName(spec.Name)
}
fd, err := bpfMapCreate(&attr)
if err != nil {
return nil, errors.Wrap(err, "map create")
return nil, fmt.Errorf("map create: %w", err)
}
return newMap(fd, spec.Name, newMapABIFromSpec(spec))
m, err := newMap(fd, spec.Name, abi)
if err != nil {
return nil, err
}
if err := m.populate(spec.Contents); err != nil {
m.Close()
return nil, fmt.Errorf("map create: can't set initial contents: %w", err)
}
if spec.Freeze {
if err := m.Freeze(); err != nil {
m.Close()
return nil, fmt.Errorf("can't freeze map: %w", err)
}
}
return m, nil
}
func newMap(fd *internal.FD, name string, abi *MapABI) (*Map, error) {
@@ -251,9 +300,9 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
*value = m
return nil
case *Map:
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
case Map:
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
case **Program:
p, err := unmarshalProgram(valueBytes)
@@ -265,9 +314,9 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
*value = p
return nil
case *Program:
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
case Program:
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
default:
return unmarshalBytes(valueOut, valueBytes)
@@ -275,16 +324,18 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
}
// LookupAndDelete retrieves and deletes a value from a Map.
//
// Returns ErrKeyNotExist if the key doesn't exist.
func (m *Map) LookupAndDelete(key, valueOut interface{}) error {
valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return errors.WithMessage(err, "can't marshal key")
return fmt.Errorf("can't marshal key: %w", err)
}
if err := bpfMapLookupAndDelete(m.fd, keyPtr, valuePtr); err != nil {
return errors.WithMessage(err, "lookup and delete and delete failed")
return fmt.Errorf("lookup and delete failed: %w", err)
}
return unmarshalBytes(valueOut, valueBytes)
@@ -298,7 +349,7 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
valuePtr := internal.NewSlicePointer(valueBytes)
err := m.lookup(key, valuePtr)
if IsNotExist(err) {
if errors.Is(err, ErrKeyNotExist) {
return nil, nil
}
@@ -308,11 +359,13 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return errors.WithMessage(err, "can't marshal key")
return fmt.Errorf("can't marshal key: %w", err)
}
err = bpfMapLookupElem(m.fd, keyPtr, valueOut)
return errors.WithMessage(err, "lookup failed")
if err = bpfMapLookupElem(m.fd, keyPtr, valueOut); err != nil {
return fmt.Errorf("lookup failed: %w", err)
}
return nil
}
// MapUpdateFlags controls the behaviour of the Map.Update call.
@@ -340,7 +393,7 @@ func (m *Map) Put(key, value interface{}) error {
func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return errors.WithMessage(err, "can't marshal key")
return fmt.Errorf("can't marshal key: %w", err)
}
var valuePtr internal.Pointer
@@ -350,28 +403,36 @@ func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
valuePtr, err = marshalPtr(value, int(m.abi.ValueSize))
}
if err != nil {
return errors.WithMessage(err, "can't marshal value")
return fmt.Errorf("can't marshal value: %w", err)
}
return bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags))
if err = bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags)); err != nil {
return fmt.Errorf("update failed: %w", err)
}
return nil
}
// Delete removes a value.
//
// Returns an error if the key does not exist, see IsNotExist.
// Returns ErrKeyNotExist if the key does not exist.
func (m *Map) Delete(key interface{}) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return errors.WithMessage(err, "can't marshal key")
return fmt.Errorf("can't marshal key: %w", err)
}
err = bpfMapDeleteElem(m.fd, keyPtr)
return errors.WithMessage(err, "can't delete key")
if err = bpfMapDeleteElem(m.fd, keyPtr); err != nil {
return fmt.Errorf("delete failed: %w", err)
}
return nil
}
// NextKey finds the key following an initial key.
//
// See NextKeyBytes for details.
//
// Returns ErrKeyNotExist if there is no next key.
func (m *Map) NextKey(key, nextKeyOut interface{}) error {
nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.abi.KeySize))
@@ -383,8 +444,10 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
return nil
}
err := unmarshalBytes(nextKeyOut, nextKeyBytes)
return errors.WithMessage(err, "can't unmarshal next key")
if err := unmarshalBytes(nextKeyOut, nextKeyBytes); err != nil {
return fmt.Errorf("can't unmarshal next key: %w", err)
}
return nil
}
// NextKeyBytes returns the key following an initial key as a byte slice.
@@ -392,12 +455,14 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
// Passing nil will return the first key.
//
// Use Iterate if you want to traverse all entries in the map.
//
// Returns nil if there are no more keys.
func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
nextKey := make([]byte, m.abi.KeySize)
nextKeyPtr := internal.NewSlicePointer(nextKey)
err := m.nextKey(key, nextKeyPtr)
if IsNotExist(err) {
if errors.Is(err, ErrKeyNotExist) {
return nil, nil
}
@@ -413,12 +478,14 @@ func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
if key != nil {
keyPtr, err = marshalPtr(key, int(m.abi.KeySize))
if err != nil {
return errors.WithMessage(err, "can't marshal key")
return fmt.Errorf("can't marshal key: %w", err)
}
}
err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut)
return errors.WithMessage(err, "can't get next key")
if err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut); err != nil {
return fmt.Errorf("next key failed: %w", err)
}
return nil
}
// Iterate traverses a map.
@@ -469,7 +536,7 @@ func (m *Map) Clone() (*Map, error) {
dup, err := m.fd.Dup()
if err != nil {
return nil, errors.Wrap(err, "can't clone map")
return nil, fmt.Errorf("can't clone map: %w", err)
}
return newMap(dup, m.name, &m.abi)
@@ -479,7 +546,30 @@ func (m *Map) Clone() (*Map, error) {
//
// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
func (m *Map) Pin(fileName string) error {
return bpfPinObject(fileName, m.fd)
return internal.BPFObjPin(fileName, m.fd)
}
// Freeze prevents a map to be modified from user space.
//
// It makes no changes to kernel-side restrictions.
func (m *Map) Freeze() error {
if err := haveMapMutabilityModifiers(); err != nil {
return fmt.Errorf("can't freeze map: %w", err)
}
if err := bpfMapFreeze(m.fd); err != nil {
return fmt.Errorf("can't freeze map: %w", err)
}
return nil
}
func (m *Map) populate(contents []MapKV) error {
for _, kv := range contents {
if err := m.Put(kv.Key, kv.Value); err != nil {
return fmt.Errorf("key %v: %w", kv.Key, err)
}
}
return nil
}
// LoadPinnedMap load a Map from a BPF file.
@@ -487,7 +577,7 @@ func (m *Map) Pin(fileName string) error {
// The function is not compatible with nested maps.
// Use LoadPinnedMapExplicit in these situations.
func LoadPinnedMap(fileName string) (*Map, error) {
fd, err := bpfGetObject(fileName)
fd, err := internal.BPFObjGet(fileName)
if err != nil {
return nil, err
}
@@ -501,7 +591,7 @@ func LoadPinnedMap(fileName string) (*Map, error) {
// LoadPinnedMapExplicit loads a map with explicit parameters.
func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) {
fd, err := bpfGetObject(fileName)
fd, err := internal.BPFObjGet(fileName)
if err != nil {
return nil, err
}
@@ -516,18 +606,7 @@ func unmarshalMap(buf []byte) (*Map, error) {
// Looking up an entry in a nested map or prog array returns an id,
// not an fd.
id := internal.NativeEndian.Uint32(buf)
fd, err := bpfGetMapFDByID(id)
if err != nil {
return nil, err
}
name, abi, err := newMapABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return newMap(fd, name, abi)
return NewMapFromID(MapID(id))
}
// MarshalBinary implements BinaryMarshaler.
@@ -542,6 +621,60 @@ func (m *Map) MarshalBinary() ([]byte, error) {
return buf, nil
}
func patchValue(value []byte, typ btf.Type, replacements map[string]interface{}) error {
replaced := make(map[string]bool)
replace := func(name string, offset, size int, replacement interface{}) error {
if offset+size > len(value) {
return fmt.Errorf("%s: offset %d(+%d) is out of bounds", name, offset, size)
}
buf, err := marshalBytes(replacement, size)
if err != nil {
return fmt.Errorf("marshal %s: %w", name, err)
}
copy(value[offset:offset+size], buf)
replaced[name] = true
return nil
}
switch parent := typ.(type) {
case *btf.Datasec:
for _, secinfo := range parent.Vars {
name := string(secinfo.Type.(*btf.Var).Name)
replacement, ok := replacements[name]
if !ok {
continue
}
err := replace(name, int(secinfo.Offset), int(secinfo.Size), replacement)
if err != nil {
return err
}
}
default:
return fmt.Errorf("patching %T is not supported", typ)
}
if len(replaced) == len(replacements) {
return nil
}
var missing []string
for name := range replacements {
if !replaced[name] {
missing = append(missing, name)
}
}
if len(missing) == 1 {
return fmt.Errorf("unknown field: %s", missing[0])
}
return fmt.Errorf("unknown fields: %s", strings.Join(missing, ","))
}
// MapIterator iterates a Map.
//
// See Map.Iterate.
@@ -562,8 +695,6 @@ func newMapIterator(target *Map) *MapIterator {
}
}
var errIterationAborted = errors.New("iteration aborted")
// Next decodes the next key and value.
//
// Iterating a hash map from which keys are being deleted is not
@@ -599,7 +730,7 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool {
mi.prevKey = mi.prevBytes
mi.err = mi.target.Lookup(nextBytes, valueOut)
if IsNotExist(mi.err) {
if errors.Is(mi.err, ErrKeyNotExist) {
// Even though the key should be valid, we couldn't look up
// its value. If we're iterating a hash map this is probably
// because a concurrent delete removed the value before we
@@ -618,26 +749,50 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool {
return mi.err == nil
}
mi.err = errIterationAborted
mi.err = fmt.Errorf("%w", ErrIterationAborted)
return false
}
// Err returns any encountered error.
//
// The method must be called after Next returns nil.
//
// Returns ErrIterationAborted if it wasn't possible to do a full iteration.
func (mi *MapIterator) Err() error {
return mi.err
}
// IsNotExist returns true if the error indicates that a
// key doesn't exist.
func IsNotExist(err error) bool {
return errors.Cause(err) == unix.ENOENT
// MapGetNextID returns the ID of the next eBPF map.
//
// Returns ErrNotExist, if there is no next eBPF map.
func MapGetNextID(startID MapID) (MapID, error) {
id, err := objGetNextID(internal.BPF_MAP_GET_NEXT_ID, uint32(startID))
return MapID(id), err
}
// IsIterationAborted returns true if the iteration was aborted.
// NewMapFromID returns the map for a given id.
//
// This occurs when keys are deleted from a hash map during iteration.
func IsIterationAborted(err error) bool {
return errors.Cause(err) == errIterationAborted
// Returns ErrNotExist, if there is no eBPF map with the given id.
func NewMapFromID(id MapID) (*Map, error) {
fd, err := bpfObjGetFDByID(internal.BPF_MAP_GET_FD_BY_ID, uint32(id))
if err != nil {
return nil, err
}
name, abi, err := newMapABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return newMap(fd, name, abi)
}
// ID returns the systemwide unique ID of the map.
func (m *Map) ID() (MapID, error) {
info, err := bpfGetMapInfoByFD(m.fd)
if err != nil {
return MapID(0), err
}
return MapID(info.id), nil
}

View File

@@ -4,13 +4,13 @@ import (
"bytes"
"encoding"
"encoding/binary"
"errors"
"fmt"
"reflect"
"runtime"
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/pkg/errors"
)
func marshalPtr(data interface{}, length int) (internal.Pointer, error) {
@@ -46,7 +46,9 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
default:
var wr bytes.Buffer
err = binary.Write(&wr, internal.NativeEndian, value)
err = errors.Wrapf(err, "encoding %T", value)
if err != nil {
err = fmt.Errorf("encoding %T: %v", value, err)
}
buf = wr.Bytes()
}
if err != nil {
@@ -54,7 +56,7 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
}
if len(buf) != length {
return nil, errors.Errorf("%T doesn't marshal to %d bytes", data, length)
return nil, fmt.Errorf("%T doesn't marshal to %d bytes", data, length)
}
return buf, nil
}
@@ -95,8 +97,10 @@ func unmarshalBytes(data interface{}, buf []byte) error {
return errors.New("require pointer to []byte")
default:
rd := bytes.NewReader(buf)
err := binary.Read(rd, internal.NativeEndian, value)
return errors.Wrapf(err, "decoding %T", value)
if err := binary.Read(rd, internal.NativeEndian, value); err != nil {
return fmt.Errorf("decoding %T: %v", value, err)
}
return nil
}
}
@@ -120,7 +124,7 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, er
sliceValue := reflect.ValueOf(slice)
sliceLen := sliceValue.Len()
if sliceLen > possibleCPUs {
return internal.Pointer{}, errors.Errorf("per-CPU value exceeds number of CPUs")
return internal.Pointer{}, fmt.Errorf("per-CPU value exceeds number of CPUs")
}
alignedElemLength := align(elemLength, 8)
@@ -147,7 +151,7 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, er
func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) error {
slicePtrType := reflect.TypeOf(slicePtr)
if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice {
return errors.Errorf("per-cpu value requires pointer to slice")
return fmt.Errorf("per-cpu value requires pointer to slice")
}
possibleCPUs, err := internal.PossibleCPUs()
@@ -166,7 +170,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
step := len(buf) / possibleCPUs
if step < elemLength {
return errors.Errorf("per-cpu element length is larger than available data")
return fmt.Errorf("per-cpu element length is larger than available data")
}
for i := 0; i < possibleCPUs; i++ {
var elem interface{}
@@ -184,7 +188,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
err := unmarshalBytes(elem, elemBytes)
if err != nil {
return errors.Wrapf(err, "cpu %d", i)
return fmt.Errorf("cpu %d: %w", i, err)
}
buf = buf[step:]

262
vendor/github.com/cilium/ebpf/prog.go generated vendored
View File

@@ -2,20 +2,25 @@ package ebpf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"strings"
"time"
"unsafe"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
)
// ErrNotSupported is returned whenever the kernel doesn't support a feature.
var ErrNotSupported = internal.ErrNotSupported
// ProgramID represents the unique ID of an eBPF program
type ProgramID uint32
const (
// Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN.
// This is currently the maximum of spare space allocated for SKB
@@ -41,17 +46,33 @@ type ProgramOptions struct {
type ProgramSpec struct {
// Name is passed to the kernel as a debug aid. Must only contain
// alpha numeric and '_' characters.
Name string
Type ProgramType
AttachType AttachType
Instructions asm.Instructions
License string
Name string
// Type determines at which hook in the kernel a program will run.
Type ProgramType
AttachType AttachType
// Name of a kernel data structure to attach to. It's interpretation
// depends on Type and AttachType.
AttachTo string
Instructions asm.Instructions
// License of the program. Some helpers are only available if
// the license is deemed compatible with the GPL.
//
// See https://www.kernel.org/doc/html/latest/process/license-rules.html#id1
License string
// Version used by tracing programs.
//
// Deprecated: superseded by BTF.
KernelVersion uint32
// The BTF associated with this program. Changing Instructions
// will most likely invalidate the contained data, and may
// result in errors when attempting to load it into the kernel.
BTF *btf.Program
// The byte order this program was compiled for, may be nil.
ByteOrder binary.ByteOrder
}
// Copy returns a copy of the spec.
@@ -74,9 +95,10 @@ type Program struct {
// otherwise it is empty.
VerifierLog string
fd *internal.FD
name string
abi ProgramABI
fd *internal.FD
name string
abi ProgramABI
attachType AttachType
}
// NewProgram creates a new Program.
@@ -97,8 +119,8 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
}
handle, err := btf.NewHandle(btf.ProgramSpec(spec.BTF))
if err != nil && !btf.IsNotSupported(err) {
return nil, errors.Wrap(err, "can't load BTF")
if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, fmt.Errorf("can't load BTF: %w", err)
}
return newProgramWithBTF(spec, handle, opts)
@@ -130,6 +152,7 @@ func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions)
return prog, nil
}
logErr := err
if opts.LogLevel == 0 {
// Re-run with the verifier enabled to get better error messages.
logBuf = make([]byte, logSize)
@@ -137,11 +160,11 @@ func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions)
attr.logSize = uint32(len(logBuf))
attr.logBuf = internal.NewSlicePointer(logBuf)
_, logErr := bpfProgLoad(attr)
err = internal.ErrorWithLog(err, logBuf, logErr)
_, logErr = bpfProgLoad(attr)
}
return nil, errors.Wrap(err, "can't load program")
err = internal.ErrorWithLog(err, logBuf, logErr)
return nil, fmt.Errorf("can't load program: %w", err)
}
// NewProgramFromFD creates a program from a raw fd.
@@ -181,6 +204,10 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
return nil, errors.New("License cannot be empty")
}
if spec.ByteOrder != nil && spec.ByteOrder != internal.NativeEndian {
return nil, fmt.Errorf("can't load %s program on %s", spec.ByteOrder, internal.NativeEndian)
}
buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize))
err := spec.Instructions.Marshal(buf, internal.NativeEndian)
if err != nil {
@@ -195,15 +222,11 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
insCount: insCount,
instructions: internal.NewSlicePointer(bytecode),
license: internal.NewStringPointer(spec.License),
}
name, err := newBPFObjName(spec.Name)
if err != nil {
return nil, err
kernelVersion: spec.KernelVersion,
}
if haveObjName() == nil {
attr.progName = name
attr.progName = newBPFObjName(spec.Name)
}
if handle != nil && spec.BTF != nil {
@@ -211,7 +234,7 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
recSize, bytes, err := btf.ProgramLineInfos(spec.BTF)
if err != nil {
return nil, errors.Wrap(err, "can't get BTF line infos")
return nil, fmt.Errorf("can't get BTF line infos: %w", err)
}
attr.lineInfoRecSize = recSize
attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
@@ -219,13 +242,23 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF)
if err != nil {
return nil, errors.Wrap(err, "can't get BTF function infos")
return nil, fmt.Errorf("can't get BTF function infos: %w", err)
}
attr.funcInfoRecSize = recSize
attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
attr.funcInfo = internal.NewSlicePointer(bytes)
}
if spec.AttachTo != "" {
target, err := resolveBTFType(spec.AttachTo, spec.Type, spec.AttachType)
if err != nil {
return nil, err
}
if target != nil {
attr.attachBTFID = target.ID()
}
}
return attr, nil
}
@@ -267,7 +300,7 @@ func (p *Program) Clone() (*Program, error) {
dup, err := p.fd.Dup()
if err != nil {
return nil, errors.Wrap(err, "can't clone program")
return nil, fmt.Errorf("can't clone program: %w", err)
}
return newProgram(dup, p.name, &p.abi), nil
@@ -277,7 +310,10 @@ func (p *Program) Clone() (*Program, error) {
//
// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
func (p *Program) Pin(fileName string) error {
return errors.Wrap(bpfPinObject(fileName, p.fd), "can't pin program")
if err := internal.BPFObjPin(fileName, p.fd); err != nil {
return fmt.Errorf("can't pin program: %w", err)
}
return nil
}
// Close unloads the program from the kernel.
@@ -297,23 +333,33 @@ func (p *Program) Close() error {
//
// This function requires at least Linux 4.12.
func (p *Program) Test(in []byte) (uint32, []byte, error) {
ret, out, _, err := p.testRun(in, 1)
return ret, out, errors.Wrap(err, "can't test program")
ret, out, _, err := p.testRun(in, 1, nil)
if err != nil {
return ret, nil, fmt.Errorf("can't test program: %w", err)
}
return ret, out, nil
}
// Benchmark runs the Program with the given input for a number of times
// and returns the time taken per iteration.
//
// The returned value is the return value of the last execution of
// the program.
// Returns the result of the last execution of the program and the time per
// run or an error. reset is called whenever the benchmark syscall is
// interrupted, and should be set to testing.B.ResetTimer or similar.
//
// Note: profiling a call to this function will skew it's results, see
// https://github.com/cilium/ebpf/issues/24
//
// This function requires at least Linux 4.12.
func (p *Program) Benchmark(in []byte, repeat int) (uint32, time.Duration, error) {
ret, _, total, err := p.testRun(in, repeat)
return ret, total, errors.Wrap(err, "can't benchmark program")
func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.Duration, error) {
ret, _, total, err := p.testRun(in, repeat, reset)
if err != nil {
return ret, total, fmt.Errorf("can't benchmark program: %w", err)
}
return ret, total, nil
}
var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() bool {
var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() (bool, error) {
prog, err := NewProgram(&ProgramSpec{
Type: SocketFilter,
Instructions: asm.Instructions{
@@ -324,31 +370,26 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() b
})
if err != nil {
// This may be because we lack sufficient permissions, etc.
return false
return false, err
}
defer prog.Close()
fd, err := prog.fd.Value()
if err != nil {
return false
}
// Programs require at least 14 bytes input
in := make([]byte, 14)
attr := bpfProgTestRunAttr{
fd: fd,
fd: uint32(prog.FD()),
dataSizeIn: uint32(len(in)),
dataIn: internal.NewSlicePointer(in),
}
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
err = bpfProgTestRun(&attr)
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return errors.Cause(err) != unix.EINVAL
return !errors.Is(err, unix.EINVAL), nil
})
func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration, error) {
func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte, time.Duration, error) {
if uint(repeat) > math.MaxUint32 {
return 0, nil, 0, fmt.Errorf("repeat is too high")
}
@@ -386,9 +427,20 @@ func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration,
repeat: uint32(repeat),
}
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return 0, nil, 0, errors.Wrap(err, "can't run test")
for {
err = bpfProgTestRun(&attr)
if err == nil {
break
}
if errors.Is(err, unix.EINTR) {
if reset != nil {
reset()
}
continue
}
return 0, nil, 0, fmt.Errorf("can't run test: %w", err)
}
if int(attr.dataSizeOut) > cap(out) {
@@ -410,18 +462,7 @@ func unmarshalProgram(buf []byte) (*Program, error) {
// Looking up an entry in a nested map or prog array returns an id,
// not an fd.
id := internal.NativeEndian.Uint32(buf)
fd, err := bpfGetProgramFDByID(id)
if err != nil {
return nil, err
}
name, abi, err := newProgramABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return newProgram(fd, name, abi), nil
return NewProgramFromID(ProgramID(id))
}
// MarshalBinary implements BinaryMarshaler.
@@ -436,7 +477,9 @@ func (p *Program) MarshalBinary() ([]byte, error) {
return buf, nil
}
// Attach a Program to a container object fd
// Attach a Program.
//
// Deprecated: use link.RawAttachProgram instead.
func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
if fd < 0 {
return errors.New("invalid fd")
@@ -447,42 +490,47 @@ func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
return err
}
attr := bpfProgAlterAttr{
targetFd: uint32(fd),
attachBpfFd: pfd,
attachType: uint32(typ),
attachFlags: uint32(flags),
attr := internal.BPFProgAttachAttr{
TargetFd: uint32(fd),
AttachBpfFd: pfd,
AttachType: uint32(typ),
AttachFlags: uint32(flags),
}
return bpfProgAlter(_ProgAttach, &attr)
return internal.BPFProgAttach(&attr)
}
// Detach a Program from a container object fd
// Detach a Program.
//
// Deprecated: use link.RawDetachProgram instead.
func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
if fd < 0 {
return errors.New("invalid fd")
}
if flags != 0 {
return errors.New("flags must be zero")
}
pfd, err := p.fd.Value()
if err != nil {
return err
}
attr := bpfProgAlterAttr{
targetFd: uint32(fd),
attachBpfFd: pfd,
attachType: uint32(typ),
attachFlags: uint32(flags),
attr := internal.BPFProgDetachAttr{
TargetFd: uint32(fd),
AttachBpfFd: pfd,
AttachType: uint32(typ),
}
return bpfProgAlter(_ProgDetach, &attr)
return internal.BPFProgDetach(&attr)
}
// LoadPinnedProgram loads a Program from a BPF file.
//
// Requires at least Linux 4.11.
func LoadPinnedProgram(fileName string) (*Program, error) {
fd, err := bpfGetObject(fileName)
fd, err := internal.BPFObjGet(fileName)
if err != nil {
return nil, err
}
@@ -490,7 +538,7 @@ func LoadPinnedProgram(fileName string) (*Program, error) {
name, abi, err := newProgramABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, errors.Wrapf(err, "can't get ABI for %s", fileName)
return nil, fmt.Errorf("can't get ABI for %s: %w", fileName, err)
}
return newProgram(fd, name, abi), nil
@@ -512,9 +560,63 @@ func SanitizeName(name string, replacement rune) string {
}, name)
}
// IsNotSupported returns true if an error occurred because
// the kernel does not have support for a specific feature.
func IsNotSupported(err error) bool {
_, notSupported := errors.Cause(err).(*internal.UnsupportedFeatureError)
return notSupported
// ProgramGetNextID returns the ID of the next eBPF program.
//
// Returns ErrNotExist, if there is no next eBPF program.
func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
id, err := objGetNextID(internal.BPF_PROG_GET_NEXT_ID, uint32(startID))
return ProgramID(id), err
}
// NewProgramFromID returns the program for a given id.
//
// Returns ErrNotExist, if there is no eBPF program with the given id.
func NewProgramFromID(id ProgramID) (*Program, error) {
fd, err := bpfObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id))
if err != nil {
return nil, err
}
name, abi, err := newProgramABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return newProgram(fd, name, abi), nil
}
// ID returns the systemwide unique ID of the program.
func (p *Program) ID() (ProgramID, error) {
info, err := bpfGetProgInfoByFD(p.fd)
if err != nil {
return ProgramID(0), err
}
return ProgramID(info.id), nil
}
func resolveBTFType(name string, progType ProgramType, attachType AttachType) (btf.Type, error) {
kernel, err := btf.LoadKernelSpec()
if err != nil {
return nil, fmt.Errorf("can't resolve BTF type %s: %w", name, err)
}
type match struct {
p ProgramType
a AttachType
}
target := match{progType, attachType}
switch target {
case match{Tracing, AttachTraceIter}:
var target btf.Func
if err := kernel.FindType("bpf_iter_"+name, &target); err != nil {
return nil, fmt.Errorf("can't resolve BTF for iterator %s: %w", name, err)
}
return &target, nil
default:
return nil, nil
}
}

View File

@@ -13,6 +13,11 @@ The library is maintained by [Cloudflare](https://www.cloudflare.com) and [Ciliu
The package is production ready, but **the API is explicitly unstable
right now**. Expect to update your code if you want to follow along.
## Requirements
* A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy)
* Linux 4.9, 4.19 or 5.4 (versions in-between should work, but are not tested)
## Useful resources
* [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended)

View File

@@ -1,15 +1,19 @@
package ebpf
import (
"path/filepath"
"strings"
"errors"
"fmt"
"os"
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix"
)
"github.com/pkg/errors"
// Generic errors returned by BPF syscalls.
var (
ErrNotExist = errors.New("requested object does not exist")
)
// bpfObjName is a null-terminated string made up of
@@ -17,18 +21,15 @@ import (
type bpfObjName [unix.BPF_OBJ_NAME_LEN]byte
// newBPFObjName truncates the result if it is too long.
func newBPFObjName(name string) (bpfObjName, error) {
idx := strings.IndexFunc(name, invalidBPFObjNameChar)
if idx != -1 {
return bpfObjName{}, errors.Errorf("invalid character '%c' in name '%s'", name[idx], name)
}
func newBPFObjName(name string) bpfObjName {
var result bpfObjName
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
return result, nil
return result
}
func invalidBPFObjNameChar(char rune) bool {
dotAllowed := objNameAllowsDot() == nil
switch {
case char >= 'A' && char <= 'Z':
fallthrough
@@ -36,6 +37,8 @@ func invalidBPFObjNameChar(char rune) bool {
fallthrough
case char >= '0' && char <= '9':
fallthrough
case dotAllowed && char == '.':
fallthrough
case char == '_':
return false
default:
@@ -76,12 +79,6 @@ type bpfMapInfo struct {
mapName bpfObjName // since 4.15 ad5b177bd73f
}
type bpfPinObjAttr struct {
fileName internal.Pointer
fd uint32
padding uint32
}
type bpfProgLoadAttr struct {
progType ProgramType
insCount uint32
@@ -102,6 +99,8 @@ type bpfProgLoadAttr struct {
lineInfoRecSize uint32
lineInfo internal.Pointer
lineInfoCnt uint32
attachBTFID btf.TypeID
attachProgFd uint32
}
type bpfProgInfo struct {
@@ -130,13 +129,6 @@ type bpfProgTestRunAttr struct {
duration uint32
}
type bpfProgAlterAttr struct {
targetFd uint32
attachBpfFd uint32
attachType uint32
attachFlags uint32
}
type bpfObjGetInfoByFDAttr struct {
fd uint32
infoLen uint32
@@ -148,9 +140,19 @@ type bpfGetFDByIDAttr struct {
next uint32
}
type bpfMapFreezeAttr struct {
mapFd uint32
}
type bpfObjGetNextIDAttr struct {
startID uint32
nextID uint32
openFlags uint32
}
func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) {
for {
fd, err := internal.BPF(_ProgLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
fd, err := internal.BPF(internal.BPF_PROG_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
// As of ~4.20 the verifier can be interrupted by a signal,
// and returns EAGAIN in that case.
if err == unix.EAGAIN {
@@ -165,13 +167,17 @@ func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) {
}
}
func bpfProgAlter(cmd int, attr *bpfProgAlterAttr) error {
_, err := internal.BPF(cmd, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
func bpfProgTestRun(attr *bpfProgTestRunAttr) error {
_, err := internal.BPF(internal.BPF_PROG_TEST_RUN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) {
fd, err := internal.BPF(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
fd, err := internal.BPF(internal.BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if errors.Is(err, os.ErrPermission) {
return nil, errors.New("permission denied or insufficient rlimit to lock memory for map")
}
if err != nil {
return nil, err
}
@@ -179,7 +185,7 @@ func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) {
return internal.NewFD(uint32(fd)), nil
}
var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool {
var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() (bool, error) {
inner, err := bpfMapCreate(&bpfMapCreateAttr{
mapType: Array,
keySize: 4,
@@ -187,7 +193,7 @@ var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool {
maxEntries: 1,
})
if err != nil {
return false
return false, err
}
defer inner.Close()
@@ -200,11 +206,28 @@ var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool {
innerMapFd: innerFd,
})
if err != nil {
return false
return false, nil
}
_ = nested.Close()
return true
return true, nil
})
var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", func() (bool, error) {
// This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since
// BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check.
m, err := bpfMapCreate(&bpfMapCreateAttr{
mapType: Array,
keySize: 4,
valueSize: 4,
maxEntries: 1,
flags: unix.BPF_F_RDONLY_PROG,
})
if err != nil {
return false, nil
}
_ = m.Close()
return true, nil
})
func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error {
@@ -218,8 +241,8 @@ func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error {
key: key,
value: valueOut,
}
_, err = internal.BPF(_MapLookupElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err
_, err = internal.BPF(internal.BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error {
@@ -233,8 +256,8 @@ func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error
key: key,
value: valueOut,
}
_, err = internal.BPF(_MapLookupAndDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err
_, err = internal.BPF(internal.BPF_MAP_LOOKUP_AND_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func bpfMapUpdateElem(m *internal.FD, key, valueOut internal.Pointer, flags uint64) error {
@@ -249,8 +272,8 @@ func bpfMapUpdateElem(m *internal.FD, key, valueOut internal.Pointer, flags uint
value: valueOut,
flags: flags,
}
_, err = internal.BPF(_MapUpdateElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err
_, err = internal.BPF(internal.BPF_MAP_UPDATE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error {
@@ -263,8 +286,8 @@ func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error {
mapFd: fd,
key: key,
}
_, err = internal.BPF(_MapDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err
_, err = internal.BPF(internal.BPF_MAP_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error {
@@ -278,44 +301,58 @@ func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error {
key: key,
value: nextKeyOut,
}
_, err = internal.BPF(_MapGetNextKey, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
_, err = internal.BPF(internal.BPF_MAP_GET_NEXT_KEY, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return wrapMapError(err)
}
func objGetNextID(cmd internal.BPFCmd, start uint32) (uint32, error) {
attr := bpfObjGetNextIDAttr{
startID: start,
}
_, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return attr.nextID, wrapObjError(err)
}
func wrapObjError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, unix.ENOENT) {
return fmt.Errorf("%w", ErrNotExist)
}
return errors.New(err.Error())
}
func wrapMapError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, unix.ENOENT) {
return ErrKeyNotExist
}
if errors.Is(err, unix.EEXIST) {
return ErrKeyExist
}
return errors.New(err.Error())
}
func bpfMapFreeze(m *internal.FD) error {
fd, err := m.Value()
if err != nil {
return err
}
attr := bpfMapFreezeAttr{
mapFd: fd,
}
_, err = internal.BPF(internal.BPF_MAP_FREEZE, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err
}
const bpfFSType = 0xcafe4a11
func bpfPinObject(fileName string, fd *internal.FD) error {
dirName := filepath.Dir(fileName)
var statfs unix.Statfs_t
if err := unix.Statfs(dirName, &statfs); err != nil {
return err
}
if uint64(statfs.Type) != bpfFSType {
return errors.Errorf("%s is not on a bpf filesystem", fileName)
}
value, err := fd.Value()
if err != nil {
return err
}
_, err = internal.BPF(_ObjPin, unsafe.Pointer(&bpfPinObjAttr{
fileName: internal.NewStringPointer(fileName),
fd: value,
}), 16)
return errors.Wrapf(err, "pin object %s", fileName)
}
func bpfGetObject(fileName string) (*internal.FD, error) {
ptr, err := internal.BPF(_ObjGet, unsafe.Pointer(&bpfPinObjAttr{
fileName: internal.NewStringPointer(fileName),
}), 16)
if err != nil {
return nil, errors.Wrapf(err, "get object %s", fileName)
}
return internal.NewFD(uint32(ptr)), nil
}
func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error {
value, err := fd.Value()
if err != nil {
@@ -328,28 +365,51 @@ func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) er
infoLen: uint32(size),
info: internal.NewPointer(info),
}
_, err = internal.BPF(_ObjGetInfoByFD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return errors.Wrapf(err, "fd %d", fd)
_, err = internal.BPF(internal.BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return fmt.Errorf("fd %d: %w", fd, err)
}
return nil
}
func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) {
var info bpfProgInfo
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
return &info, errors.Wrap(err, "can't get program info")
if err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
return nil, fmt.Errorf("can't get program info: %w", err)
}
return &info, nil
}
func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) {
var info bpfMapInfo
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
return &info, errors.Wrap(err, "can't get map info")
if err != nil {
return nil, fmt.Errorf("can't get map info: %w", err)
}
return &info, nil
}
var haveObjName = internal.FeatureTest("object names", "4.15", func() bool {
name, err := newBPFObjName("feature_test")
var haveObjName = internal.FeatureTest("object names", "4.15", func() (bool, error) {
attr := bpfMapCreateAttr{
mapType: Array,
keySize: 4,
valueSize: 4,
maxEntries: 1,
mapName: newBPFObjName("feature_test"),
}
fd, err := bpfMapCreate(&attr)
if err != nil {
// This really is a fatal error, but it should be caught
// by the unit tests not working.
return false
return false, nil
}
_ = fd.Close()
return true, nil
})
var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() (bool, error) {
if err := haveObjName(); err != nil {
return false, err
}
attr := bpfMapCreateAttr{
@@ -357,38 +417,22 @@ var haveObjName = internal.FeatureTest("object names", "4.15", func() bool {
keySize: 4,
valueSize: 4,
maxEntries: 1,
mapName: name,
mapName: newBPFObjName(".test"),
}
fd, err := bpfMapCreate(&attr)
if err != nil {
return false
return false, nil
}
_ = fd.Close()
return true
return true, nil
})
func bpfGetMapFDByID(id uint32) (*internal.FD, error) {
// available from 4.13
func bpfObjGetFDByID(cmd internal.BPFCmd, id uint32) (*internal.FD, error) {
attr := bpfGetFDByIDAttr{
id: id,
}
ptr, err := internal.BPF(_MapGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return nil, errors.Wrapf(err, "can't get fd for map id %d", id)
}
return internal.NewFD(uint32(ptr)), nil
}
func bpfGetProgramFDByID(id uint32) (*internal.FD, error) {
// available from 4.13
attr := bpfGetFDByIDAttr{
id: id,
}
ptr, err := internal.BPF(_ProgGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return nil, errors.Wrapf(err, "can't get fd for program id %d", id)
}
return internal.NewFD(uint32(ptr)), nil
ptr, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return internal.NewFD(uint32(ptr)), wrapObjError(err)
}

View File

@@ -1,6 +1,6 @@
package ebpf
//go:generate stringer -output types_string.go -type=MapType,ProgramType
//go:generate stringer -output types_string.go -type=MapType,ProgramType,AttachType
// MapType indicates the type map structure
// that will be initialized in the kernel.
@@ -85,44 +85,12 @@ const (
// hasPerCPUValue returns true if the Map stores a value per CPU.
func (mt MapType) hasPerCPUValue() bool {
if mt == PerCPUHash || mt == PerCPUArray {
if mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash {
return true
}
return false
}
const (
_MapCreate = iota
_MapLookupElem
_MapUpdateElem
_MapDeleteElem
_MapGetNextKey
_ProgLoad
_ObjPin
_ObjGet
_ProgAttach
_ProgDetach
_ProgTestRun
_ProgGetNextID
_MapGetNextID
_ProgGetFDByID
_MapGetFDByID
_ObjGetInfoByFD
_ProgQuery
_RawTracepointOpen
_BTFLoad
_BTFGetFDByID
_TaskFDQuery
_MapLookupAndDeleteElem
_MapFreeze
)
const (
_Any = iota
_NoExist
_Exist
)
// ProgramType of the eBPF program
type ProgramType uint32
@@ -219,6 +187,9 @@ const (
AttachTraceRawTp
AttachTraceFEntry
AttachTraceFExit
AttachModifyReturn
AttachLSMMac
AttachTraceIter
)
// AttachFlags of the eBPF program used in BPF_PROG_ATTACH command

View File

@@ -1,4 +1,4 @@
// Code generated by "stringer -output types_string.go -type=MapType,ProgramType"; DO NOT EDIT.
// Code generated by "stringer -output types_string.go -type=MapType,ProgramType,AttachType"; DO NOT EDIT.
package ebpf
@@ -89,3 +89,49 @@ func (i ProgramType) String() string {
}
return _ProgramType_name[_ProgramType_index[i]:_ProgramType_index[i+1]]
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[AttachNone-0]
_ = x[AttachCGroupInetIngress-0]
_ = x[AttachCGroupInetEgress-1]
_ = x[AttachCGroupInetSockCreate-2]
_ = x[AttachCGroupSockOps-3]
_ = x[AttachSkSKBStreamParser-4]
_ = x[AttachSkSKBStreamVerdict-5]
_ = x[AttachCGroupDevice-6]
_ = x[AttachSkMsgVerdict-7]
_ = x[AttachCGroupInet4Bind-8]
_ = x[AttachCGroupInet6Bind-9]
_ = x[AttachCGroupInet4Connect-10]
_ = x[AttachCGroupInet6Connect-11]
_ = x[AttachCGroupInet4PostBind-12]
_ = x[AttachCGroupInet6PostBind-13]
_ = x[AttachCGroupUDP4Sendmsg-14]
_ = x[AttachCGroupUDP6Sendmsg-15]
_ = x[AttachLircMode2-16]
_ = x[AttachFlowDissector-17]
_ = x[AttachCGroupSysctl-18]
_ = x[AttachCGroupUDP4Recvmsg-19]
_ = x[AttachCGroupUDP6Recvmsg-20]
_ = x[AttachCGroupGetsockopt-21]
_ = x[AttachCGroupSetsockopt-22]
_ = x[AttachTraceRawTp-23]
_ = x[AttachTraceFEntry-24]
_ = x[AttachTraceFExit-25]
_ = x[AttachModifyReturn-26]
_ = x[AttachLSMMac-27]
_ = x[AttachTraceIter-28]
}
const _AttachType_name = "AttachNoneAttachCGroupInetEgressAttachCGroupInetSockCreateAttachCGroupSockOpsAttachSkSKBStreamParserAttachSkSKBStreamVerdictAttachCGroupDeviceAttachSkMsgVerdictAttachCGroupInet4BindAttachCGroupInet6BindAttachCGroupInet4ConnectAttachCGroupInet6ConnectAttachCGroupInet4PostBindAttachCGroupInet6PostBindAttachCGroupUDP4SendmsgAttachCGroupUDP6SendmsgAttachLircMode2AttachFlowDissectorAttachCGroupSysctlAttachCGroupUDP4RecvmsgAttachCGroupUDP6RecvmsgAttachCGroupGetsockoptAttachCGroupSetsockoptAttachTraceRawTpAttachTraceFEntryAttachTraceFExitAttachModifyReturnAttachLSMMacAttachTraceIter"
var _AttachType_index = [...]uint16{0, 10, 32, 58, 77, 100, 124, 142, 160, 181, 202, 226, 250, 275, 300, 323, 346, 361, 380, 398, 421, 444, 466, 488, 504, 521, 537, 555, 567, 582}
func (i AttachType) String() string {
if i >= AttachType(len(_AttachType_index)-1) {
return "AttachType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]]
}