Bump deps

```
* use go 1.17 semantics in go.mod
* pin tonistiigi/fsutil dep to match docker/docker's dep
go get -u ./...
go mod vendor
```
This commit is contained in:
Jason Hall
2022-01-21 16:58:24 +00:00
committed by GitHub
parent a59970f4ac
commit c19ffc26a2
3595 changed files with 429699 additions and 120494 deletions
+6
View File
@@ -0,0 +1,6 @@
package internal
// Align returns 'n' updated to 'alignment' boundary.
func Align(n, alignment int) int {
return (int(n) + alignment - 1) / alignment * alignment
}
+364 -264
View File
@@ -7,14 +7,13 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"reflect"
"sync"
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
@@ -27,16 +26,28 @@ var (
ErrNoExtendedInfo = errors.New("no extended info")
)
// ID represents the unique ID of a BTF object.
type ID uint32
// Spec represents decoded BTF.
type Spec struct {
rawTypes []rawType
strings stringTable
types []Type
namedTypes map[string][]namedType
funcInfos map[string]extInfo
lineInfos map[string]extInfo
coreRelos map[string]bpfCoreRelos
byteOrder binary.ByteOrder
// Data from .BTF.
rawTypes []rawType
strings stringTable
// Inflated Types.
types []Type
// Types indexed by essential name.
// Includes all struct flavors and types with the same name.
namedTypes map[essentialName][]Type
// Data from .BTF.ext.
funcInfos map[string]FuncInfo
lineInfos map[string]LineInfos
coreRelos map[string]CoreRelos
byteOrder binary.ByteOrder
}
type btfHeader struct {
@@ -51,25 +62,34 @@ type btfHeader struct {
StringLen uint32
}
// LoadSpecFromReader reads BTF sections from an ELF.
// typeStart returns the offset from the beginning of the .BTF section
// to the start of its type entries.
func (h *btfHeader) typeStart() int64 {
return int64(h.HdrLen + h.TypeOff)
}
// stringStart returns the offset from the beginning of the .BTF section
// to the start of its string table.
func (h *btfHeader) stringStart() int64 {
return int64(h.HdrLen + h.StringOff)
}
// LoadSpecFromReader reads from an ELF or a raw BTF blob.
//
// Returns a nil Spec and no error if no BTF was present.
// Returns ErrNotFound if reading from an ELF which contains no BTF.
func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
file, err := internal.NewSafeELFFile(rd)
if err != nil {
if bo := guessRawBTFByteOrder(rd); bo != nil {
// Try to parse a naked BTF blob. This will return an error if
// we encounter a Datasec, since we can't fix it up.
return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil, nil)
}
return nil, err
}
defer file.Close()
btfSection, btfExtSection, sectionSizes, err := findBtfSections(file)
if err != nil {
return nil, err
}
if btfSection == nil {
return nil, nil
}
symbols, err := file.Symbols()
if err != nil {
return nil, fmt.Errorf("can't read symbols: %v", err)
@@ -87,10 +107,6 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
}
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)
}
@@ -98,24 +114,10 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
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 {
return spec, nil
}
spec.funcInfos, spec.lineInfos, spec.coreRelos, 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
return loadSpecFromELF(file, variableOffsets)
}
func findBtfSections(file *internal.SafeELFFile) (*elf.Section, *elf.Section, map[string]uint32, error) {
func loadSpecFromELF(file *internal.SafeELFFile, variableOffsets map[variable]uint32) (*Spec, error) {
var (
btfSection *elf.Section
btfExtSection *elf.Section
@@ -134,33 +136,119 @@ func findBtfSections(file *internal.SafeELFFile) (*elf.Section, *elf.Section, ma
}
if sec.Size > math.MaxUint32 {
return nil, nil, nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
}
sectionSizes[sec.Name] = uint32(sec.Size)
}
}
return btfSection, btfExtSection, sectionSizes, nil
}
func loadSpecFromVmlinux(rd io.ReaderAt) (*Spec, error) {
file, err := internal.NewSafeELFFile(rd)
if btfSection == nil {
return nil, fmt.Errorf("btf: %w", ErrNotFound)
}
spec, err := loadRawSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
if err != nil {
return nil, err
}
defer file.Close()
btfSection, _, _, err := findBtfSections(file)
if btfExtSection == nil {
return spec, nil
}
if btfExtSection.ReaderAt == nil {
return nil, fmt.Errorf("compressed ext_info is not supported")
}
extInfo, err := loadExtInfos(btfExtSection, file.ByteOrder, spec.strings)
if err != nil {
return nil, fmt.Errorf(".BTF ELF section: %s", err)
return nil, fmt.Errorf("can't parse ext info: %w", err)
}
if btfSection == nil {
return nil, fmt.Errorf("unable to find .BTF ELF section")
if err := spec.splitExtInfos(extInfo); err != nil {
return nil, fmt.Errorf("linking funcInfos and lineInfos: %w", err)
}
return loadNakedSpec(btfSection.Open(), file.ByteOrder, nil, nil)
return spec, nil
}
func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
// splitExtInfos takes FuncInfos, LineInfos and CoreRelos indexed by section and
// transforms them to be indexed by function. Retrieves function names from
// the BTF spec.
func (spec *Spec) splitExtInfos(info *extInfo) error {
ofi := make(map[string]FuncInfo)
oli := make(map[string]LineInfos)
ocr := make(map[string]CoreRelos)
for secName, secFuncs := range info.funcInfos {
// Collect functions from each section and organize them by name.
for _, fi := range secFuncs {
name, err := fi.Name(spec)
if err != nil {
return fmt.Errorf("looking up function name: %w", err)
}
// FuncInfo offsets are scoped to the ELF section. Zero them out
// since they are meaningless outside of that context. The linker
// will determine the offset of the function within the final
// instruction stream before handing it off to the kernel.
fi.InsnOff = 0
ofi[name] = fi
}
// Attribute LineInfo records to their respective functions, if any.
if lines := info.lineInfos[secName]; lines != nil {
for _, li := range lines {
fi := secFuncs.funcForOffset(li.InsnOff)
if fi == nil {
return fmt.Errorf("section %s: error looking up FuncInfo for LineInfo %v", secName, li)
}
// Offsets are ELF section-scoped, make them function-scoped by
// subtracting the function's start offset.
li.InsnOff -= fi.InsnOff
name, err := fi.Name(spec)
if err != nil {
return fmt.Errorf("looking up function name: %w", err)
}
oli[name] = append(oli[name], li)
}
}
// Attribute CO-RE relocations to their respective functions, if any.
if relos := info.relos[secName]; relos != nil {
for _, r := range relos {
fi := secFuncs.funcForOffset(r.insnOff)
if fi == nil {
return fmt.Errorf("section %s: error looking up FuncInfo for CO-RE relocation %v", secName, r)
}
// Offsets are ELF section-scoped, make them function-scoped by
// subtracting the function's start offset.
r.insnOff -= fi.InsnOff
name, err := fi.Name(spec)
if err != nil {
return fmt.Errorf("looking up function name: %w", err)
}
ocr[name] = append(ocr[name], r)
}
}
}
spec.funcInfos = ofi
spec.lineInfos = oli
spec.coreRelos = ocr
return nil
}
func loadRawSpec(btf io.Reader, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
rawTypes, rawStrings, err := parseBTF(btf, bo)
if err != nil {
return nil, err
@@ -208,18 +296,21 @@ func LoadKernelSpec() (*Spec, error) {
}
func loadKernelSpec() (*Spec, error) {
release, err := unix.KernelRelease()
if err != nil {
return nil, fmt.Errorf("can't read kernel release number: %w", err)
}
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if err == nil {
defer fh.Close()
return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
return loadRawSpec(fh, internal.NativeEndian, nil, nil)
}
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return nil, fmt.Errorf("uname failed: %w", err)
}
end := bytes.IndexByte(uname.Release[:], 0)
release := string(uname.Release[:end])
// use same list of locations as libbpf
// https://github.com/libbpf/libbpf/blob/9a3a42608dbe3731256a5682a125ac1e23bced8f/src/btf.c#L3114-L3122
locations := []string{
@@ -241,60 +332,84 @@ func loadKernelSpec() (*Spec, error) {
}
defer fh.Close()
return loadSpecFromVmlinux(fh)
file, err := internal.NewSafeELFFile(fh)
if err != nil {
return nil, err
}
defer file.Close()
return loadSpecFromELF(file, nil)
}
return nil, fmt.Errorf("no BTF for kernel version %s: %w", release, internal.ErrNotSupported)
}
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)
// parseBTFHeader parses the header of the .BTF section.
func parseBTFHeader(r io.Reader, bo binary.ByteOrder) (*btfHeader, error) {
var header btfHeader
if err := binary.Read(rd, bo, &header); err != nil {
return nil, nil, fmt.Errorf("can't read header: %v", err)
if err := binary.Read(r, bo, &header); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
return nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
return 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")
return nil, errors.New("header length shorter than btfHeader size")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
return nil, nil, fmt.Errorf("header padding: %v", err)
if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil {
return 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)
return &header, nil
}
func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder {
for _, bo := range []binary.ByteOrder{
binary.LittleEndian,
binary.BigEndian,
} {
if _, err := parseBTFHeader(io.NewSectionReader(r, 0, math.MaxInt64), bo); err == nil {
return bo
}
}
rawStrings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
return nil
}
// parseBTF reads a .BTF section into memory and parses it into a list of
// raw types and a string table.
func parseBTF(btf io.Reader, bo binary.ByteOrder) ([]rawType, stringTable, error) {
rawBTF, err := io.ReadAll(btf)
if err != nil {
return nil, nil, fmt.Errorf("can't read BTF: %v", err)
}
rd := bytes.NewReader(rawBTF)
header, err := parseBTFHeader(rd, bo)
if err != nil {
return nil, nil, fmt.Errorf("parsing .BTF header: %v", err)
}
buf := io.NewSectionReader(rd, header.stringStart(), int64(header.StringLen))
rawStrings, err := readStringTable(buf)
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)
buf = io.NewSectionReader(rd, header.typeStart(), int64(header.TypeLen))
rawTypes, err := readTypes(buf, bo)
if err != nil {
return nil, nil, fmt.Errorf("can't read types: %w", err)
}
@@ -357,6 +472,31 @@ func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[s
return nil
}
// Copy creates a copy of Spec.
func (s *Spec) Copy() *Spec {
types, _ := copyTypes(s.types, nil)
namedTypes := make(map[essentialName][]Type)
for _, typ := range types {
if name := typ.TypeName(); name != "" {
en := newEssentialName(name)
namedTypes[en] = append(namedTypes[en], typ)
}
}
// NB: Other parts of spec are not copied since they are immutable.
return &Spec{
s.rawTypes,
s.strings,
types,
namedTypes,
s.funcInfos,
s.lineInfos,
s.coreRelos,
s.byteOrder,
}
}
type marshalOpts struct {
ByteOrder binary.ByteOrder
StripFuncLinkage bool
@@ -377,7 +517,7 @@ func (s *Spec) marshal(opts marshalOpts) ([]byte, error) {
for _, raw := range s.rawTypes {
switch {
case opts.StripFuncLinkage && raw.Kind() == kindFunc:
raw.SetLinkage(linkageStatic)
raw.SetLinkage(StaticFunc)
}
if err := raw.Marshal(&buf, opts.ByteOrder); err != nil {
@@ -421,65 +561,99 @@ func (sw sliceWriter) Write(p []byte) (int, error) {
return copy(sw, p), nil
}
// Program finds the BTF for a specific section.
//
// Length is the number of bytes in the raw BPF instruction stream.
// Program finds the BTF for a specific function.
//
// 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")
}
func (s *Spec) Program(name string) (*Program, error) {
if s.funcInfos == nil && s.lineInfos == nil && s.coreRelos == nil {
return nil, fmt.Errorf("BTF for section %s: %w", name, ErrNoExtendedInfo)
return nil, fmt.Errorf("BTF for function %s: %w", name, ErrNoExtendedInfo)
}
funcInfos, funcOK := s.funcInfos[name]
lineInfos, lineOK := s.lineInfos[name]
coreRelos, coreOK := s.coreRelos[name]
relos, coreOK := s.coreRelos[name]
if !funcOK && !lineOK && !coreOK {
return nil, fmt.Errorf("no extended BTF info for section %s", name)
return nil, fmt.Errorf("no extended BTF info for function %s", name)
}
return &Program{s, length, funcInfos, lineInfos, coreRelos}, nil
return &Program{s, funcInfos, lineInfos, relos}, nil
}
// 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)
}
m := NewMap(s, &Void{}, &datasec)
return &m, nil
}
// FindType searches for a type with a specific name.
// TypeByID returns the BTF Type with the given type ID.
//
// hint determines the type of the returned Type.
// Returns an error wrapping ErrNotFound if a Type with the given ID
// does not exist in the Spec.
func (s *Spec) TypeByID(id TypeID) (Type, error) {
if int(id) > len(s.types) {
return nil, fmt.Errorf("type ID %d: %w", id, ErrNotFound)
}
return s.types[id], nil
}
// AnyTypesByName returns a list of BTF Types with the given name.
//
// If the BTF blob describes multiple compilation units like vmlinux, multiple
// Types with the same name and kind can exist, but might not describe the same
// data structure.
//
// Returns an error wrapping ErrNotFound if no matching Type exists in the Spec.
func (s *Spec) AnyTypesByName(name string) ([]Type, error) {
types := s.namedTypes[newEssentialName(name)]
if len(types) == 0 {
return nil, fmt.Errorf("type name %s: %w", name, ErrNotFound)
}
// Return a copy to prevent changes to namedTypes.
result := make([]Type, 0, len(types))
for _, t := range types {
// Match against the full name, not just the essential one
// in case the type being looked up is a struct flavor.
if t.TypeName() == name {
result = append(result, t)
}
}
return result, nil
}
// TypeByName searches for a Type with a specific name. Since multiple
// Types with the same name can exist, the parameter typ is taken to
// narrow down the search in case of a clash.
//
// typ must be a non-nil pointer to an implementation of a Type.
// On success, the address of the found Type will be copied to typ.
//
// 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)
candidate Type
)
// Type exists in the Spec. If multiple candidates are found,
// an error is returned.
func (s *Spec) TypeByName(name string, typ interface{}) error {
typValue := reflect.ValueOf(typ)
if typValue.Kind() != reflect.Ptr {
return fmt.Errorf("%T is not a pointer", typ)
}
for _, typ := range s.namedTypes[essentialName(name)] {
typPtr := typValue.Elem()
if !typPtr.CanSet() {
return fmt.Errorf("%T cannot be set", typ)
}
wanted := typPtr.Type()
if !wanted.AssignableTo(reflect.TypeOf((*Type)(nil)).Elem()) {
return fmt.Errorf("%T does not satisfy Type interface", typ)
}
types, err := s.AnyTypesByName(name)
if err != nil {
return err
}
var candidate Type
for _, typ := range types {
if reflect.TypeOf(typ) != wanted {
continue
}
// Match against the full name, not just the essential one.
if typ.name() != name {
continue
}
if candidate != nil {
return fmt.Errorf("type %s: multiple candidates for %T", name, typ)
}
@@ -491,14 +665,15 @@ func (s *Spec) FindType(name string, typ Type) error {
return fmt.Errorf("type %s: %w", name, ErrNotFound)
}
value := reflect.Indirect(reflect.ValueOf(copyType(candidate)))
reflect.Indirect(reflect.ValueOf(typ)).Set(value)
typPtr.Set(reflect.ValueOf(candidate))
return nil
}
// Handle is a reference to BTF loaded into the kernel.
type Handle struct {
fd *internal.FD
spec *Spec
fd *sys.FD
}
// NewHandle loads BTF into the kernel.
@@ -525,22 +700,49 @@ func NewHandle(spec *Spec) (*Handle, error) {
return nil, errors.New("BTF exceeds the maximum size")
}
attr := &bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
attr := &sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
}
fd, err := bpfLoadBTF(attr)
fd, err := sys.BtfLoad(attr)
if err != nil {
logBuf := make([]byte, 64*1024)
attr.logBuf = internal.NewSlicePointer(logBuf)
attr.btfLogSize = uint32(len(logBuf))
attr.btfLogLevel = 1
_, logErr := bpfLoadBTF(attr)
attr.BtfLogBuf = sys.NewSlicePointer(logBuf)
attr.BtfLogSize = uint32(len(logBuf))
attr.BtfLogLevel = 1
_, logErr := sys.BtfLoad(attr)
return nil, internal.ErrorWithLog(err, logBuf, logErr)
}
return &Handle{fd}, nil
return &Handle{spec.Copy(), fd}, nil
}
// NewHandleFromID returns the BTF handle for a given id.
//
// Returns ErrNotExist, if there is no BTF with the given id.
//
// Requires CAP_SYS_ADMIN.
func NewHandleFromID(id ID) (*Handle, error) {
fd, err := sys.BtfGetFdById(&sys.BtfGetFdByIdAttr{
Id: uint32(id),
})
if err != nil {
return nil, fmt.Errorf("get BTF by id: %w", err)
}
info, err := newInfoFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, fmt.Errorf("get BTF spec for handle: %w", err)
}
return &Handle{info.BTF, fd}, nil
}
// Spec returns the Spec that defined the BTF loaded into the kernel.
func (h *Handle) Spec() *Spec {
return h.spec
}
// Close destroys the handle.
@@ -552,147 +754,45 @@ func (h *Handle) Close() error {
// FD returns the file descriptor for the handle.
func (h *Handle) FD() int {
value, err := h.fd.Value()
if err != nil {
return -1
}
return int(value)
return h.fd.Int()
}
// Map is the BTF for a map.
type Map struct {
spec *Spec
key, value Type
}
// NewMap returns a new Map containing the given values.
// The key and value arguments are initialized to Void if nil values are given.
func NewMap(spec *Spec, key Type, value Type) Map {
if key == nil {
key = &Void{}
}
if value == nil {
value = &Void{}
}
return Map{
spec: spec,
key: key,
value: value,
}
}
// MapSpec should be a method on Map, but is a free function
// to hide it from users of the ebpf package.
func MapSpec(m *Map) *Spec {
return m.spec
}
// 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 {
return m.key
}
// MapValue should be a method on Map, but is a free function
// to hide it from users of the ebpf package.
func MapValue(m *Map) Type {
return m.value
Spec *Spec
Key, Value Type
}
// Program is the BTF information for a stream of instructions.
type Program struct {
spec *Spec
length uint64
funcInfos, lineInfos extInfo
coreRelos bpfCoreRelos
spec *Spec
FuncInfo FuncInfo
LineInfos LineInfos
CoreRelos CoreRelos
}
// ProgramSpec returns the Spec needed for loading function and line infos into the kernel.
//
// This is a free function instead of a method to hide it from users
// of package ebpf.
func ProgramSpec(s *Program) *Spec {
return s.spec
// Spec returns the BTF spec of this program.
func (p *Program) Spec() *Spec {
return p.spec
}
// ProgramAppend the information from other to the Program.
// Fixups returns the changes required to adjust the program to the target.
//
// This is a free function instead of a method to hide it from users
// of package ebpf.
func ProgramAppend(s, other *Program) error {
funcInfos, err := s.funcInfos.append(other.funcInfos, s.length)
if err != nil {
return fmt.Errorf("func infos: %w", err)
}
lineInfos, err := s.lineInfos.append(other.lineInfos, s.length)
if err != nil {
return fmt.Errorf("line infos: %w", err)
}
s.funcInfos = funcInfos
s.lineInfos = lineInfos
s.coreRelos = s.coreRelos.append(other.coreRelos, s.length)
s.length += other.length
return nil
}
// ProgramFuncInfos returns the binary form of BTF function infos.
//
// This is a free function instead of a method to hide it from users
// of package ebpf.
func ProgramFuncInfos(s *Program) (recordSize uint32, bytes []byte, err error) {
bytes, err = s.funcInfos.MarshalBinary()
if err != nil {
return 0, nil, err
}
return s.funcInfos.recordSize, bytes, nil
}
// ProgramLineInfos returns the binary form of BTF line infos.
//
// This is a free function instead of a method to hide it from users
// of package ebpf.
func ProgramLineInfos(s *Program) (recordSize uint32, bytes []byte, err error) {
bytes, err = s.lineInfos.MarshalBinary()
if err != nil {
return 0, nil, err
}
return s.lineInfos.recordSize, bytes, nil
}
// ProgramRelocations returns the CO-RE relocations required to adjust the
// program to the target.
//
// This is a free function instead of a method to hide it from users
// of package ebpf.
func ProgramRelocations(s *Program, target *Spec) (map[uint64]Relocation, error) {
if len(s.coreRelos) == 0 {
// Passing a nil target will relocate against the running kernel.
func (p *Program) Fixups(target *Spec) (COREFixups, error) {
if len(p.CoreRelos) == 0 {
return nil, nil
}
return coreRelocate(s.spec, target, s.coreRelos)
}
type bpfLoadBTFAttr struct {
btf internal.Pointer
logBuf internal.Pointer
btfSize uint32
btfLogSize uint32
btfLogLevel uint32
}
func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
fd, err := internal.BPF(internal.BPF_BTF_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
if target == nil {
var err error
target, err = LoadKernelSpec()
if err != nil {
return nil, err
}
}
return internal.NewFD(uint32(fd)), nil
return coreRelocate(p.spec, target, p.CoreRelos)
}
func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
@@ -737,9 +837,9 @@ var haveBTF = internal.FeatureTest("BTF", "5.1", func() error {
btf := marshalBTF(&types, strings, internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
})
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
// Treat both EINVAL and EPERM as not supported: loading the program
@@ -771,13 +871,13 @@ var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() err
types.Func.SetKind(kindFunc)
types.Func.SizeType = 1 // aka FuncProto
types.Func.NameOff = 1
types.Func.SetLinkage(linkageGlobal)
types.Func.SetLinkage(GlobalFunc)
btf := marshalBTF(&types, strings, internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
})
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
+28 -10
View File
@@ -6,6 +6,8 @@ import (
"io"
)
//go:generate stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage
// btfKind describes a Type.
type btfKind uint8
@@ -29,19 +31,32 @@ const (
// Added ~5.1
kindVar
kindDatasec
// Added ~5.13
kindFloat
)
type btfFuncLinkage uint8
// FuncLinkage describes BTF function linkage metadata.
type FuncLinkage int
// Equivalent of enum btf_func_linkage.
const (
StaticFunc FuncLinkage = iota // static
GlobalFunc // global
ExternFunc // extern
)
// VarLinkage describes BTF variable linkage metadata.
type VarLinkage int
const (
linkageStatic btfFuncLinkage = iota
linkageGlobal
linkageExtern
StaticVar VarLinkage = iota // static
GlobalVar // global
ExternVar // extern
)
const (
btfTypeKindShift = 24
btfTypeKindLen = 4
btfTypeKindLen = 5
btfTypeVlenShift = 0
btfTypeVlenMask = 16
btfTypeKindFlagShift = 31
@@ -54,8 +69,8 @@ type btfType struct {
/* "info" bits arrangement
* 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
* bits 24-28: kind (e.g. int, ptr, array...etc)
* bits 29-30: unused
* bit 31: kind_flag, currently used by
* struct, union and fwd
*/
@@ -104,6 +119,8 @@ func (k btfKind) String() string {
return "Variable"
case kindDatasec:
return "Section"
case kindFloat:
return "Float"
default:
return fmt.Sprintf("Unknown (%d)", k)
}
@@ -142,11 +159,11 @@ func (bt *btfType) KindFlag() bool {
return bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift) == 1
}
func (bt *btfType) Linkage() btfFuncLinkage {
return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
func (bt *btfType) Linkage() FuncLinkage {
return FuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
}
func (bt *btfType) SetLinkage(linkage btfFuncLinkage) {
func (bt *btfType) SetLinkage(linkage FuncLinkage) {
bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift)
}
@@ -247,6 +264,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
data = new(btfVariable)
case kindDatasec:
data = make([]btfVarSecinfo, header.Vlen())
case kindFloat:
default:
return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind())
}
+44
View File
@@ -0,0 +1,44 @@
// Code generated by "stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage"; DO NOT EDIT.
package btf
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[StaticFunc-0]
_ = x[GlobalFunc-1]
_ = x[ExternFunc-2]
}
const _FuncLinkage_name = "staticglobalextern"
var _FuncLinkage_index = [...]uint8{0, 6, 12, 18}
func (i FuncLinkage) String() string {
if i < 0 || i >= FuncLinkage(len(_FuncLinkage_index)-1) {
return "FuncLinkage(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _FuncLinkage_name[_FuncLinkage_index[i]:_FuncLinkage_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[StaticVar-0]
_ = x[GlobalVar-1]
_ = x[ExternVar-2]
}
const _VarLinkage_name = "staticglobalextern"
var _VarLinkage_index = [...]uint8{0, 6, 12, 18}
func (i VarLinkage) String() string {
if i < 0 || i >= VarLinkage(len(_VarLinkage_index)-1) {
return "VarLinkage(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _VarLinkage_name[_VarLinkage_index[i]:_VarLinkage_index[i+1]]
}
+662 -149
View File
@@ -3,43 +3,160 @@ package btf
import (
"errors"
"fmt"
"math"
"reflect"
"sort"
"strconv"
"strings"
"github.com/cilium/ebpf/asm"
)
// Code in this file is derived from libbpf, which is available under a BSD
// 2-Clause license.
// Relocation describes a CO-RE relocation.
type Relocation struct {
Current uint32
New uint32
// COREFixup is the result of computing a CO-RE relocation for a target.
type COREFixup struct {
Kind COREKind
Local uint32
Target uint32
Poison bool
}
func (r Relocation) equal(other Relocation) bool {
return r.Current == other.Current && r.New == other.New
func (f COREFixup) equal(other COREFixup) bool {
return f.Local == other.Local && f.Target == other.Target
}
// coreReloKind is the type of CO-RE relocation
type coreReloKind uint32
func (f COREFixup) String() string {
if f.Poison {
return fmt.Sprintf("%s=poison", f.Kind)
}
return fmt.Sprintf("%s=%d->%d", f.Kind, f.Local, f.Target)
}
func (f COREFixup) apply(ins *asm.Instruction) error {
if f.Poison {
return errors.New("can't poison individual instruction")
}
switch class := ins.OpCode.Class(); class {
case asm.LdXClass, asm.StClass, asm.StXClass:
if want := int16(f.Local); want != ins.Offset {
return fmt.Errorf("invalid offset %d, expected %d", ins.Offset, want)
}
if f.Target > math.MaxInt16 {
return fmt.Errorf("offset %d exceeds MaxInt16", f.Target)
}
ins.Offset = int16(f.Target)
case asm.LdClass:
if !ins.IsConstantLoad(asm.DWord) {
return fmt.Errorf("not a dword-sized immediate load")
}
if want := int64(f.Local); want != ins.Constant {
return fmt.Errorf("invalid immediate %d, expected %d", ins.Constant, want)
}
ins.Constant = int64(f.Target)
case asm.ALUClass:
if ins.OpCode.ALUOp() == asm.Swap {
return fmt.Errorf("relocation against swap")
}
fallthrough
case asm.ALU64Class:
if src := ins.OpCode.Source(); src != asm.ImmSource {
return fmt.Errorf("invalid source %s", src)
}
if want := int64(f.Local); want != ins.Constant {
return fmt.Errorf("invalid immediate %d, expected %d", ins.Constant, want)
}
if f.Target > math.MaxInt32 {
return fmt.Errorf("immediate %d exceeds MaxInt32", f.Target)
}
ins.Constant = int64(f.Target)
default:
return fmt.Errorf("invalid class %s", class)
}
return nil
}
func (f COREFixup) isNonExistant() bool {
return f.Kind.checksForExistence() && f.Target == 0
}
type COREFixups map[uint64]COREFixup
// Apply returns a copy of insns with CO-RE relocations applied.
func (fs COREFixups) Apply(insns asm.Instructions) (asm.Instructions, error) {
if len(fs) == 0 {
cpy := make(asm.Instructions, len(insns))
copy(cpy, insns)
return insns, nil
}
cpy := make(asm.Instructions, 0, len(insns))
iter := insns.Iterate()
for iter.Next() {
fixup, ok := fs[iter.Offset.Bytes()]
if !ok {
cpy = append(cpy, *iter.Ins)
continue
}
ins := *iter.Ins
if fixup.Poison {
const badRelo = asm.BuiltinFunc(0xbad2310)
cpy = append(cpy, badRelo.Call())
if ins.OpCode.IsDWordLoad() {
// 64 bit constant loads occupy two raw bpf instructions, so
// we need to add another instruction as padding.
cpy = append(cpy, badRelo.Call())
}
continue
}
if err := fixup.apply(&ins); err != nil {
return nil, fmt.Errorf("instruction %d, offset %d: %s: %w", iter.Index, iter.Offset.Bytes(), fixup.Kind, err)
}
cpy = append(cpy, ins)
}
return cpy, nil
}
// COREKind is the type of CO-RE relocation
type COREKind uint32
const (
reloFieldByteOffset coreReloKind = iota /* field byte offset */
reloFieldByteSize /* field size in bytes */
reloFieldExists /* field existence in target kernel */
reloFieldSigned /* field signedness (0 - unsigned, 1 - signed) */
reloFieldLShiftU64 /* bitfield-specific left bitshift */
reloFieldRShiftU64 /* bitfield-specific right bitshift */
reloTypeIDLocal /* type ID in local BPF object */
reloTypeIDTarget /* type ID in target kernel */
reloTypeExists /* type existence in target kernel */
reloTypeSize /* type size in bytes */
reloEnumvalExists /* enum value existence in target kernel */
reloEnumvalValue /* enum value integer value */
reloFieldByteOffset COREKind = iota /* field byte offset */
reloFieldByteSize /* field size in bytes */
reloFieldExists /* field existence in target kernel */
reloFieldSigned /* field signedness (0 - unsigned, 1 - signed) */
reloFieldLShiftU64 /* bitfield-specific left bitshift */
reloFieldRShiftU64 /* bitfield-specific right bitshift */
reloTypeIDLocal /* type ID in local BPF object */
reloTypeIDTarget /* type ID in target kernel */
reloTypeExists /* type existence in target kernel */
reloTypeSize /* type size in bytes */
reloEnumvalExists /* enum value existence in target kernel */
reloEnumvalValue /* enum value integer value */
)
func (k coreReloKind) String() string {
func (k COREKind) String() string {
switch k {
case reloFieldByteOffset:
return "byte_off"
@@ -70,103 +187,249 @@ func (k coreReloKind) String() string {
}
}
func coreRelocate(local, target *Spec, coreRelos bpfCoreRelos) (map[uint64]Relocation, error) {
if target == nil {
var err error
target, err = loadKernelSpec()
if err != nil {
return nil, err
}
}
func (k COREKind) checksForExistence() bool {
return k == reloEnumvalExists || k == reloTypeExists || k == reloFieldExists
}
func coreRelocate(local, target *Spec, relos CoreRelos) (COREFixups, error) {
if local.byteOrder != target.byteOrder {
return nil, fmt.Errorf("can't relocate %s against %s", local.byteOrder, target.byteOrder)
}
relocations := make(map[uint64]Relocation, len(coreRelos))
for _, relo := range coreRelos {
accessorStr, err := local.strings.Lookup(relo.AccessStrOff)
if err != nil {
return nil, err
}
var ids []TypeID
relosByID := make(map[TypeID]CoreRelos)
result := make(COREFixups, len(relos))
for _, relo := range relos {
if relo.kind == reloTypeIDLocal {
// Filtering out reloTypeIDLocal here makes our lives a lot easier
// down the line, since it doesn't have a target at all.
if len(relo.accessor) > 1 || relo.accessor[0] != 0 {
return nil, fmt.Errorf("%s: unexpected accessor %v", relo.kind, relo.accessor)
}
accessor, err := parseCoreAccessor(accessorStr)
if err != nil {
return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
}
if int(relo.TypeID) >= len(local.types) {
return nil, fmt.Errorf("invalid type id %d", relo.TypeID)
}
typ := local.types[relo.TypeID]
if relo.ReloKind == reloTypeIDLocal {
relocations[uint64(relo.InsnOff)] = Relocation{
uint32(typ.ID()),
uint32(typ.ID()),
result[uint64(relo.insnOff)] = COREFixup{
relo.kind,
uint32(relo.typeID),
uint32(relo.typeID),
false,
}
continue
}
named, ok := typ.(namedType)
if !ok || named.name() == "" {
return nil, fmt.Errorf("relocate anonymous type %s: %w", typ.String(), ErrNotSupported)
relos, ok := relosByID[relo.typeID]
if !ok {
ids = append(ids, relo.typeID)
}
name := essentialName(named.name())
res, err := coreCalculateRelocation(typ, target.namedTypes[name], relo.ReloKind, accessor)
if err != nil {
return nil, fmt.Errorf("relocate %s: %w", name, err)
}
relocations[uint64(relo.InsnOff)] = res
relosByID[relo.typeID] = append(relos, relo)
}
return relocations, nil
// Ensure we work on relocations in a deterministic order.
sort.Slice(ids, func(i, j int) bool {
return ids[i] < ids[j]
})
for _, id := range ids {
if int(id) >= len(local.types) {
return nil, fmt.Errorf("invalid type id %d", id)
}
localType := local.types[id]
localTypeName := localType.TypeName()
if localTypeName == "" {
return nil, fmt.Errorf("relocate unnamed or anonymous type %s: %w", localType, ErrNotSupported)
}
relos := relosByID[id]
targets := target.namedTypes[newEssentialName(localTypeName)]
fixups, err := coreCalculateFixups(localType, targets, relos)
if err != nil {
return nil, fmt.Errorf("relocate %s: %w", localType, err)
}
for i, relo := range relos {
result[uint64(relo.insnOff)] = fixups[i]
}
}
return result, nil
}
var errAmbiguousRelocation = errors.New("ambiguous relocation")
var errImpossibleRelocation = errors.New("impossible relocation")
// coreCalculateFixups calculates the fixups for the given relocations using
// the "best" target.
//
// The best target is determined by scoring: the less poisoning we have to do
// the better the target is.
func coreCalculateFixups(local Type, targets []Type, relos CoreRelos) ([]COREFixup, error) {
localID := local.ID()
local, err := copyType(local, skipQualifiersAndTypedefs)
if err != nil {
return nil, err
}
bestScore := len(relos)
var bestFixups []COREFixup
for i := range targets {
targetID := targets[i].ID()
target, err := copyType(targets[i], skipQualifiersAndTypedefs)
if err != nil {
return nil, err
}
score := 0 // lower is better
fixups := make([]COREFixup, 0, len(relos))
for _, relo := range relos {
fixup, err := coreCalculateFixup(local, localID, target, targetID, relo)
if err != nil {
return nil, fmt.Errorf("target %s: %w", target, err)
}
if fixup.Poison || fixup.isNonExistant() {
score++
}
fixups = append(fixups, fixup)
}
if score > bestScore {
// We have a better target already, ignore this one.
continue
}
if score < bestScore {
// This is the best target yet, use it.
bestScore = score
bestFixups = fixups
continue
}
// Some other target has the same score as the current one. Make sure
// the fixups agree with each other.
for i, fixup := range bestFixups {
if !fixup.equal(fixups[i]) {
return nil, fmt.Errorf("%s: multiple types match: %w", fixup.Kind, errAmbiguousRelocation)
}
}
}
if bestFixups == nil {
// Nothing at all matched, probably because there are no suitable
// targets at all. Poison everything!
bestFixups = make([]COREFixup, len(relos))
for i, relo := range relos {
bestFixups[i] = COREFixup{Kind: relo.kind, Poison: true}
}
}
return bestFixups, nil
}
// coreCalculateFixup calculates the fixup for a single local type, target type
// and relocation.
func coreCalculateFixup(local Type, localID TypeID, target Type, targetID TypeID, relo CoreRelo) (COREFixup, error) {
fixup := func(local, target uint32) (COREFixup, error) {
return COREFixup{relo.kind, local, target, false}, nil
}
poison := func() (COREFixup, error) {
if relo.kind.checksForExistence() {
return fixup(1, 0)
}
return COREFixup{relo.kind, 0, 0, true}, nil
}
zero := COREFixup{}
switch relo.kind {
case reloTypeIDTarget, reloTypeSize, reloTypeExists:
if len(relo.accessor) > 1 || relo.accessor[0] != 0 {
return zero, fmt.Errorf("%s: unexpected accessor %v", relo.kind, relo.accessor)
}
err := coreAreTypesCompatible(local, target)
if errors.Is(err, errImpossibleRelocation) {
return poison()
}
if err != nil {
return zero, fmt.Errorf("relocation %s: %w", relo.kind, err)
}
switch relo.kind {
case reloTypeExists:
return fixup(1, 1)
func coreCalculateRelocation(local Type, targets []namedType, kind coreReloKind, localAccessor coreAccessor) (Relocation, error) {
var relos []Relocation
var matches []Type
for _, target := range targets {
switch kind {
case reloTypeIDTarget:
if localAccessor[0] != 0 {
return Relocation{}, fmt.Errorf("%s: unexpected non-zero accessor", kind)
return fixup(uint32(localID), uint32(targetID))
case reloTypeSize:
localSize, err := Sizeof(local)
if err != nil {
return zero, err
}
if compat, err := coreAreTypesCompatible(local, target); err != nil {
return Relocation{}, fmt.Errorf("%s: %s", kind, err)
} else if !compat {
continue
targetSize, err := Sizeof(target)
if err != nil {
return zero, err
}
relos = append(relos, Relocation{uint32(target.ID()), uint32(target.ID())})
default:
return Relocation{}, fmt.Errorf("relocation %s: %w", kind, ErrNotSupported)
return fixup(uint32(localSize), uint32(targetSize))
}
matches = append(matches, target)
}
if len(relos) == 0 {
// TODO: Add switch for existence checks like reloEnumvalExists here.
case reloEnumvalValue, reloEnumvalExists:
localValue, targetValue, err := coreFindEnumValue(local, relo.accessor, target)
if errors.Is(err, errImpossibleRelocation) {
return poison()
}
if err != nil {
return zero, fmt.Errorf("relocation %s: %w", relo.kind, err)
}
// TODO: This might have to be poisoned.
return Relocation{}, fmt.Errorf("no relocation found, tried %v", targets)
}
switch relo.kind {
case reloEnumvalExists:
return fixup(1, 1)
case reloEnumvalValue:
return fixup(uint32(localValue.Value), uint32(targetValue.Value))
}
case reloFieldByteOffset, reloFieldByteSize, reloFieldExists:
if _, ok := target.(*Fwd); ok {
// We can't relocate fields using a forward declaration, so
// skip it. If a non-forward declaration is present in the BTF
// we'll find it in one of the other iterations.
return poison()
}
localField, targetField, err := coreFindField(local, relo.accessor, target)
if errors.Is(err, errImpossibleRelocation) {
return poison()
}
if err != nil {
return zero, fmt.Errorf("target %s: %w", target, err)
}
switch relo.kind {
case reloFieldExists:
return fixup(1, 1)
case reloFieldByteOffset:
return fixup(localField.offset/8, targetField.offset/8)
case reloFieldByteSize:
localSize, err := Sizeof(localField.Type)
if err != nil {
return zero, err
}
targetSize, err := Sizeof(targetField.Type)
if err != nil {
return zero, err
}
return fixup(uint32(localSize), uint32(targetSize))
relo := relos[0]
for _, altRelo := range relos[1:] {
if !altRelo.equal(relo) {
return Relocation{}, fmt.Errorf("multiple types %v match: %w", matches, errAmbiguousRelocation)
}
}
return relo, nil
return zero, fmt.Errorf("relocation %s: %w", relo.kind, ErrNotSupported)
}
/* coreAccessor contains a path through a struct. It contains at least one index.
@@ -204,8 +467,8 @@ func parseCoreAccessor(accessor string) (coreAccessor, error) {
return nil, fmt.Errorf("empty accessor")
}
var result coreAccessor
parts := strings.Split(accessor, ":")
result := make(coreAccessor, 0, len(parts))
for _, part := range parts {
// 31 bits to avoid overflowing int on 32 bit platforms.
index, err := strconv.ParseUint(part, 10, 31)
@@ -219,6 +482,240 @@ func parseCoreAccessor(accessor string) (coreAccessor, error) {
return result, nil
}
func (ca coreAccessor) String() string {
strs := make([]string, 0, len(ca))
for _, i := range ca {
strs = append(strs, strconv.Itoa(i))
}
return strings.Join(strs, ":")
}
func (ca coreAccessor) enumValue(t Type) (*EnumValue, error) {
e, ok := t.(*Enum)
if !ok {
return nil, fmt.Errorf("not an enum: %s", t)
}
if len(ca) > 1 {
return nil, fmt.Errorf("invalid accessor %s for enum", ca)
}
i := ca[0]
if i >= len(e.Values) {
return nil, fmt.Errorf("invalid index %d for %s", i, e)
}
return &e.Values[i], nil
}
type coreField struct {
Type Type
offset uint32
}
func adjustOffset(base uint32, t Type, n int) (uint32, error) {
size, err := Sizeof(t)
if err != nil {
return 0, err
}
return base + (uint32(n) * uint32(size) * 8), nil
}
// coreFindField descends into the local type using the accessor and tries to
// find an equivalent field in target at each step.
//
// Returns the field and the offset of the field from the start of
// target in bits.
func coreFindField(local Type, localAcc coreAccessor, target Type) (_, _ coreField, _ error) {
// The first index is used to offset a pointer of the base type like
// when accessing an array.
localOffset, err := adjustOffset(0, local, localAcc[0])
if err != nil {
return coreField{}, coreField{}, err
}
targetOffset, err := adjustOffset(0, target, localAcc[0])
if err != nil {
return coreField{}, coreField{}, err
}
if err := coreAreMembersCompatible(local, target); err != nil {
return coreField{}, coreField{}, fmt.Errorf("fields: %w", err)
}
var localMaybeFlex, targetMaybeFlex bool
for _, acc := range localAcc[1:] {
switch localType := local.(type) {
case composite:
// For composite types acc is used to find the field in the local type,
// and then we try to find a field in target with the same name.
localMembers := localType.members()
if acc >= len(localMembers) {
return coreField{}, coreField{}, fmt.Errorf("invalid accessor %d for %s", acc, local)
}
localMember := localMembers[acc]
if localMember.Name == "" {
_, ok := localMember.Type.(composite)
if !ok {
return coreField{}, coreField{}, fmt.Errorf("unnamed field with type %s: %s", localMember.Type, ErrNotSupported)
}
// This is an anonymous struct or union, ignore it.
local = localMember.Type
localOffset += localMember.OffsetBits
localMaybeFlex = false
continue
}
targetType, ok := target.(composite)
if !ok {
return coreField{}, coreField{}, fmt.Errorf("target not composite: %w", errImpossibleRelocation)
}
targetMember, last, err := coreFindMember(targetType, localMember.Name)
if err != nil {
return coreField{}, coreField{}, err
}
if targetMember.BitfieldSize > 0 {
return coreField{}, coreField{}, fmt.Errorf("field %q is a bitfield: %w", targetMember.Name, ErrNotSupported)
}
local = localMember.Type
localMaybeFlex = acc == len(localMembers)-1
localOffset += localMember.OffsetBits
target = targetMember.Type
targetMaybeFlex = last
targetOffset += targetMember.OffsetBits
case *Array:
// For arrays, acc is the index in the target.
targetType, ok := target.(*Array)
if !ok {
return coreField{}, coreField{}, fmt.Errorf("target not array: %w", errImpossibleRelocation)
}
if localType.Nelems == 0 && !localMaybeFlex {
return coreField{}, coreField{}, fmt.Errorf("local type has invalid flexible array")
}
if targetType.Nelems == 0 && !targetMaybeFlex {
return coreField{}, coreField{}, fmt.Errorf("target type has invalid flexible array")
}
if localType.Nelems > 0 && acc >= int(localType.Nelems) {
return coreField{}, coreField{}, fmt.Errorf("invalid access of %s at index %d", localType, acc)
}
if targetType.Nelems > 0 && acc >= int(targetType.Nelems) {
return coreField{}, coreField{}, fmt.Errorf("out of bounds access of target: %w", errImpossibleRelocation)
}
local = localType.Type
localMaybeFlex = false
localOffset, err = adjustOffset(localOffset, local, acc)
if err != nil {
return coreField{}, coreField{}, err
}
target = targetType.Type
targetMaybeFlex = false
targetOffset, err = adjustOffset(targetOffset, target, acc)
if err != nil {
return coreField{}, coreField{}, err
}
default:
return coreField{}, coreField{}, fmt.Errorf("relocate field of %T: %w", localType, ErrNotSupported)
}
if err := coreAreMembersCompatible(local, target); err != nil {
return coreField{}, coreField{}, err
}
}
return coreField{local, localOffset}, coreField{target, targetOffset}, nil
}
// coreFindMember finds a member in a composite type while handling anonymous
// structs and unions.
func coreFindMember(typ composite, name string) (Member, bool, error) {
if name == "" {
return Member{}, false, errors.New("can't search for anonymous member")
}
type offsetTarget struct {
composite
offset uint32
}
targets := []offsetTarget{{typ, 0}}
visited := make(map[composite]bool)
for i := 0; i < len(targets); i++ {
target := targets[i]
// Only visit targets once to prevent infinite recursion.
if visited[target] {
continue
}
if len(visited) >= maxTypeDepth {
// This check is different than libbpf, which restricts the entire
// path to BPF_CORE_SPEC_MAX_LEN items.
return Member{}, false, fmt.Errorf("type is nested too deep")
}
visited[target] = true
members := target.members()
for j, member := range members {
if member.Name == name {
// NB: This is safe because member is a copy.
member.OffsetBits += target.offset
return member, j == len(members)-1, nil
}
// The names don't match, but this member could be an anonymous struct
// or union.
if member.Name != "" {
continue
}
comp, ok := member.Type.(composite)
if !ok {
return Member{}, false, fmt.Errorf("anonymous non-composite type %T not allowed", member.Type)
}
targets = append(targets, offsetTarget{comp, target.offset + member.OffsetBits})
}
}
return Member{}, false, fmt.Errorf("no matching member: %w", errImpossibleRelocation)
}
// coreFindEnumValue follows localAcc to find the equivalent enum value in target.
func coreFindEnumValue(local Type, localAcc coreAccessor, target Type) (localValue, targetValue *EnumValue, _ error) {
localValue, err := localAcc.enumValue(local)
if err != nil {
return nil, nil, err
}
targetEnum, ok := target.(*Enum)
if !ok {
return nil, nil, errImpossibleRelocation
}
localName := newEssentialName(localValue.Name)
for i, targetValue := range targetEnum.Values {
if newEssentialName(targetValue.Name) != localName {
continue
}
return localValue, &targetEnum.Values[i], nil
}
return nil, nil, errImpossibleRelocation
}
/* The comment below is from bpf_core_types_are_compat in libbpf.c:
*
* Check local and target types for compatibility. This check is used for
@@ -239,8 +736,10 @@ func parseCoreAccessor(accessor string) (coreAccessor, error) {
* number of input args and compatible return and argument types.
* These rules are not set in stone and probably will be adjusted as we get
* more experience with using BPF CO-RE relocations.
*
* Returns errImpossibleRelocation if types are not compatible.
*/
func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
func coreAreTypesCompatible(localType Type, targetType Type) error {
var (
localTs, targetTs typeDeque
l, t = &localType, &targetType
@@ -249,14 +748,14 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
for ; l != nil && t != nil; l, t = localTs.shift(), targetTs.shift() {
if depth >= maxTypeDepth {
return false, errors.New("types are nested too deep")
return errors.New("types are nested too deep")
}
localType = skipQualifierAndTypedef(*l)
targetType = skipQualifierAndTypedef(*t)
localType = *l
targetType = *t
if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
return false, nil
return fmt.Errorf("type mismatch: %w", errImpossibleRelocation)
}
switch lv := (localType).(type) {
@@ -266,7 +765,7 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
case *Int:
tv := targetType.(*Int)
if lv.isBitfield() || tv.isBitfield() {
return false, nil
return fmt.Errorf("bitfield: %w", errImpossibleRelocation)
}
case *Pointer, *Array:
@@ -277,7 +776,7 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
case *FuncProto:
tv := targetType.(*FuncProto)
if len(lv.Params) != len(tv.Params) {
return false, nil
return fmt.Errorf("function param mismatch: %w", errImpossibleRelocation)
}
depth++
@@ -285,22 +784,24 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
targetType.walk(&targetTs)
default:
return false, fmt.Errorf("unsupported type %T", localType)
return fmt.Errorf("unsupported type %T", localType)
}
}
if l != nil {
return false, fmt.Errorf("dangling local type %T", *l)
return fmt.Errorf("dangling local type %T", *l)
}
if t != nil {
return false, fmt.Errorf("dangling target type %T", *t)
return fmt.Errorf("dangling target type %T", *t)
}
return true, nil
return nil
}
/* The comment below is from bpf_core_fields_are_compat in libbpf.c:
/* coreAreMembersCompatible checks two types for field-based relocation compatibility.
*
* The comment below is from bpf_core_fields_are_compat in libbpf.c:
*
* Check two types for compatibility for the purpose of field access
* relocation. const/volatile/restrict and typedefs are skipped to ensure we
@@ -312,67 +813,66 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
* least one of enums should be anonymous;
* - for ENUMs, check sizes, names are ignored;
* - for INT, size and signedness are ignored;
* - any two FLOATs are always compatible;
* - for ARRAY, dimensionality is ignored, element types are checked for
* compatibility recursively;
* [ NB: coreAreMembersCompatible doesn't recurse, this check is done
* by coreFindField. ]
* - everything else shouldn't be ever a target of relocation.
* These rules are not set in stone and probably will be adjusted as we get
* more experience with using BPF CO-RE relocations.
*
* Returns errImpossibleRelocation if the members are not compatible.
*/
func coreAreMembersCompatible(localType Type, targetType Type) (bool, error) {
doNamesMatch := func(a, b string) bool {
func coreAreMembersCompatible(localType Type, targetType Type) error {
doNamesMatch := func(a, b string) error {
if a == "" || b == "" {
// allow anonymous and named type to match
return true
return nil
}
return essentialName(a) == essentialName(b)
if newEssentialName(a) == newEssentialName(b) {
return nil
}
return fmt.Errorf("names don't match: %w", errImpossibleRelocation)
}
for depth := 0; depth <= maxTypeDepth; depth++ {
localType = skipQualifierAndTypedef(localType)
targetType = skipQualifierAndTypedef(targetType)
_, lok := localType.(composite)
_, tok := targetType.(composite)
if lok && tok {
return true, nil
}
if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
return false, nil
}
switch lv := localType.(type) {
case *Pointer:
return true, nil
case *Enum:
tv := targetType.(*Enum)
return doNamesMatch(lv.name(), tv.name()), nil
case *Fwd:
tv := targetType.(*Fwd)
return doNamesMatch(lv.name(), tv.name()), nil
case *Int:
tv := targetType.(*Int)
return !lv.isBitfield() && !tv.isBitfield(), nil
case *Array:
tv := targetType.(*Array)
localType = lv.Type
targetType = tv.Type
default:
return false, fmt.Errorf("unsupported type %T", localType)
}
_, lok := localType.(composite)
_, tok := targetType.(composite)
if lok && tok {
return nil
}
return false, errors.New("types are nested too deep")
if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
return fmt.Errorf("type mismatch: %w", errImpossibleRelocation)
}
switch lv := localType.(type) {
case *Array, *Pointer, *Float:
return nil
case *Enum:
tv := targetType.(*Enum)
return doNamesMatch(lv.Name, tv.Name)
case *Fwd:
tv := targetType.(*Fwd)
return doNamesMatch(lv.Name, tv.Name)
case *Int:
tv := targetType.(*Int)
if lv.isBitfield() || tv.isBitfield() {
return fmt.Errorf("bitfield: %w", errImpossibleRelocation)
}
return nil
default:
return fmt.Errorf("type %s: %w", localType, ErrNotSupported)
}
}
func skipQualifierAndTypedef(typ Type) Type {
func skipQualifiersAndTypedefs(typ Type) (Type, error) {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
switch v := (result).(type) {
@@ -381,8 +881,21 @@ func skipQualifierAndTypedef(typ Type) Type {
case *Typedef:
result = v.Type
default:
return result
return result, nil
}
}
return typ
return nil, errors.New("exceeded type depth")
}
func skipQualifiers(typ Type) (Type, error) {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
switch v := (result).(type) {
case qualifier:
result = v.qualify()
default:
return result, nil
}
}
return nil, errors.New("exceeded type depth")
}
+443 -227
View File
@@ -1,23 +1,73 @@
package btf
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
)
// extInfo contains extended program metadata.
//
// It is indexed per section.
type extInfo struct {
funcInfos map[string]FuncInfos
lineInfos map[string]LineInfos
relos map[string]CoreRelos
}
// loadExtInfos parses the .BTF.ext section into its constituent parts.
func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, strings stringTable) (*extInfo, error) {
// Open unbuffered section reader. binary.Read() calls io.ReadFull on
// the header structs, resulting in one syscall per header.
headerRd := io.NewSectionReader(r, 0, math.MaxInt64)
extHeader, err := parseBTFExtHeader(headerRd, bo)
if err != nil {
return nil, fmt.Errorf("parsing BTF extension header: %w", err)
}
coreHeader, err := parseBTFExtCoreHeader(headerRd, bo, extHeader)
if err != nil {
return nil, fmt.Errorf("parsing BTF CO-RE header: %w", err)
}
buf := internal.NewBufferedSectionReader(r, extHeader.funcInfoStart(), int64(extHeader.FuncInfoLen))
funcInfos, err := parseFuncInfos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing BTF function info: %w", err)
}
buf = internal.NewBufferedSectionReader(r, extHeader.lineInfoStart(), int64(extHeader.LineInfoLen))
lineInfos, err := parseLineInfos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing BTF line info: %w", err)
}
relos := make(map[string]CoreRelos)
if coreHeader != nil && coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 {
buf = internal.NewBufferedSectionReader(r, extHeader.coreReloStart(coreHeader), int64(coreHeader.CoreReloLen))
relos, err = parseCoreRelos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
}
}
return &extInfo{funcInfos, lineInfos, relos}, nil
}
// btfExtHeader is found at the start of the .BTF.ext section.
type btfExtHeader struct {
Magic uint16
Version uint8
Flags uint8
HdrLen uint32
// HdrLen is larger than the size of struct btfExtHeader when it is
// immediately followed by a btfExtCoreHeader.
HdrLen uint32
FuncInfoOff uint32
FuncInfoLen uint32
@@ -25,82 +75,74 @@ type btfExtHeader struct {
LineInfoLen uint32
}
// parseBTFExtHeader parses the header of the .BTF.ext section.
func parseBTFExtHeader(r io.Reader, bo binary.ByteOrder) (*btfExtHeader, error) {
var header btfExtHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
if int64(header.HdrLen) < int64(binary.Size(&header)) {
return nil, fmt.Errorf("header length shorter than btfExtHeader size")
}
return &header, nil
}
// funcInfoStart returns the offset from the beginning of the .BTF.ext section
// to the start of its func_info entries.
func (h *btfExtHeader) funcInfoStart() int64 {
return int64(h.HdrLen + h.FuncInfoOff)
}
// lineInfoStart returns the offset from the beginning of the .BTF.ext section
// to the start of its line_info entries.
func (h *btfExtHeader) lineInfoStart() int64 {
return int64(h.HdrLen + h.LineInfoOff)
}
// coreReloStart returns the offset from the beginning of the .BTF.ext section
// to the start of its CO-RE relocation entries.
func (h *btfExtHeader) coreReloStart(ch *btfExtCoreHeader) int64 {
return int64(h.HdrLen + ch.CoreReloOff)
}
// btfExtCoreHeader is found right after the btfExtHeader when its HdrLen
// field is larger than its size.
type btfExtCoreHeader struct {
CoreReloOff uint32
CoreReloLen uint32
}
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, coreRelos map[string]bpfCoreRelos, err error) {
var header btfExtHeader
// parseBTFExtCoreHeader parses the tail of the .BTF.ext header. If additional
// header bytes are present, extHeader.HdrLen will be larger than the struct,
// indicating the presence of a CO-RE extension header.
func parseBTFExtCoreHeader(r io.Reader, bo binary.ByteOrder, extHeader *btfExtHeader) (*btfExtCoreHeader, error) {
extHdrSize := int64(binary.Size(&extHeader))
remainder := int64(extHeader.HdrLen) - extHdrSize
if remainder == 0 {
return nil, nil
}
var coreHeader btfExtCoreHeader
if err := binary.Read(r, bo, &header); err != nil {
return nil, nil, nil, fmt.Errorf("can't read header: %v", err)
if err := binary.Read(r, bo, &coreHeader); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, nil, nil, errors.New("header is too short")
}
coreHdrSize := int64(binary.Size(&coreHeader))
if remainder >= coreHdrSize {
if err := binary.Read(r, bo, &coreHeader); err != nil {
return nil, nil, nil, fmt.Errorf("can't read CO-RE relocation header: %v", err)
}
remainder -= coreHdrSize
}
// Of course, the .BTF.ext header has different semantics than the
// .BTF ext header. We need to ignore non-null values.
_, err = io.CopyN(ioutil.Discard, r, remainder)
if err != nil {
return nil, nil, nil, fmt.Errorf("header padding: %v", err)
}
if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil {
return nil, nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
}
buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen)))
funcInfo, err = parseExtInfo(buf, bo, strings)
if err != nil {
return nil, nil, nil, fmt.Errorf("function info: %w", err)
}
if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil {
return nil, nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
}
buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen)))
lineInfo, err = parseExtInfo(buf, bo, strings)
if err != nil {
return nil, nil, nil, fmt.Errorf("line info: %w", err)
}
if coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 {
if _, err := r.Seek(int64(header.HdrLen+coreHeader.CoreReloOff), io.SeekStart); err != nil {
return nil, nil, nil, fmt.Errorf("can't seek to CO-RE relocation section: %v", err)
}
coreRelos, err = parseExtInfoRelos(io.LimitReader(r, int64(coreHeader.CoreReloLen)), bo, strings)
if err != nil {
return nil, nil, nil, fmt.Errorf("CO-RE relocation info: %w", err)
}
}
return funcInfo, lineInfo, coreRelos, nil
return &coreHeader, nil
}
type btfExtInfoSec struct {
@@ -108,161 +150,11 @@ type btfExtInfoSec struct {
NumInfo uint32
}
type extInfoRecord struct {
InsnOff uint64
Opaque []byte
}
type extInfo struct {
recordSize uint32
records []extInfoRecord
}
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
if other.recordSize != ei.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))
records = append(records, ei.records...)
for _, info := range other.records {
records = append(records, extInfoRecord{
InsnOff: info.InsnOff + offset,
Opaque: info.Opaque,
})
}
return extInfo{ei.recordSize, records}, nil
}
func (ei extInfo) MarshalBinary() ([]byte, error) {
if len(ei.records) == 0 {
return nil, nil
}
buf := bytes.NewBuffer(make([]byte, 0, int(ei.recordSize)*len(ei.records)))
for _, info := range ei.records {
// The kernel expects offsets in number of raw bpf instructions,
// 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, fmt.Errorf("can't write instruction offset: %v", err)
}
buf.Write(info.Opaque)
}
return buf.Bytes(), nil
}
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
const maxRecordSize = 256
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return nil, fmt.Errorf("can't read record size: %v", err)
}
if recordSize < 4 {
// Need at least insnOff
return nil, errors.New("record size too short")
}
if recordSize > maxRecordSize {
return nil, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
}
result := make(map[string]extInfo)
for {
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
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, 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, fmt.Errorf("section %v: can't read record: %v", secName, err)
}
if byteOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
}
records = append(records, extInfoRecord{uint64(byteOff), buf})
}
result[secName] = extInfo{
recordSize,
records,
}
}
}
// bpfCoreRelo matches `struct bpf_core_relo` from the kernel
type bpfCoreRelo struct {
InsnOff uint32
TypeID TypeID
AccessStrOff uint32
ReloKind coreReloKind
}
type bpfCoreRelos []bpfCoreRelo
// append two slices of extInfoRelo to each other. The InsnOff of b are adjusted
// by offset.
func (r bpfCoreRelos) append(other bpfCoreRelos, offset uint64) bpfCoreRelos {
result := make([]bpfCoreRelo, 0, len(r)+len(other))
result = append(result, r...)
for _, relo := range other {
relo.InsnOff += uint32(offset)
result = append(result, relo)
}
return result
}
var extInfoReloSize = binary.Size(bpfCoreRelo{})
func parseExtInfoRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]bpfCoreRelos, error) {
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return nil, fmt.Errorf("read record size: %v", err)
}
if recordSize != uint32(extInfoReloSize) {
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
}
result := make(map[string]bpfCoreRelos)
for {
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
var relos []bpfCoreRelo
for i := uint32(0); i < infoHeader.NumInfo; i++ {
var relo bpfCoreRelo
if err := binary.Read(r, bo, &relo); err != nil {
return nil, fmt.Errorf("section %v: read record: %v", secName, err)
}
if relo.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, relo.InsnOff)
}
relos = append(relos, relo)
}
result[secName] = relos
}
}
func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (string, *btfExtInfoSec, error) {
// parseExtInfoSec parses a btf_ext_info_sec header within .BTF.ext,
// appearing within func_info and line_info sub-sections.
// These headers appear once for each program section in the ELF and are
// followed by one or more func/line_info records for the section.
func parseExtInfoSec(r io.Reader, bo binary.ByteOrder, strings stringTable) (string, *btfExtInfoSec, error) {
var infoHeader btfExtInfoSec
if err := binary.Read(r, bo, &infoHeader); err != nil {
return "", nil, fmt.Errorf("read ext info header: %w", err)
@@ -272,6 +164,9 @@ func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (
if err != nil {
return "", nil, fmt.Errorf("get section name: %w", err)
}
if secName == "" {
return "", nil, fmt.Errorf("extinfo header refers to empty section name")
}
if infoHeader.NumInfo == 0 {
return "", nil, fmt.Errorf("section %s has zero records", secName)
@@ -279,3 +174,324 @@ func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (
return secName, &infoHeader, nil
}
// parseExtInfoRecordSize parses the uint32 at the beginning of a func_infos
// or line_infos segment that describes the length of all extInfoRecords in
// that segment.
func parseExtInfoRecordSize(r io.Reader, bo binary.ByteOrder) (uint32, error) {
const maxRecordSize = 256
var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil {
return 0, fmt.Errorf("can't read record size: %v", err)
}
if recordSize < 4 {
// Need at least InsnOff worth of bytes per record.
return 0, errors.New("record size too short")
}
if recordSize > maxRecordSize {
return 0, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
}
return recordSize, nil
}
// FuncInfo represents the location and type ID of a function in a BPF ELF.
type FuncInfo struct {
// Instruction offset of the function within an ELF section.
// Always zero after parsing a funcinfo from an ELF, instruction streams
// are split on function boundaries.
InsnOff uint32
TypeID TypeID
}
// Name looks up the FuncInfo's corresponding function name in the given spec.
func (fi FuncInfo) Name(spec *Spec) (string, error) {
// Look up function name based on type ID.
typ, err := spec.TypeByID(fi.TypeID)
if err != nil {
return "", fmt.Errorf("looking up type by ID: %w", err)
}
if _, ok := typ.(*Func); !ok {
return "", fmt.Errorf("type ID %d is a %T, but expected a Func", fi.TypeID, typ)
}
// C doesn't have anonymous functions, but check just in case.
if name := typ.TypeName(); name != "" {
return name, nil
}
return "", fmt.Errorf("Func with type ID %d doesn't have a name", fi.TypeID)
}
// Marshal writes the binary representation of the FuncInfo to w.
// The function offset is converted from bytes to instructions.
func (fi FuncInfo) Marshal(w io.Writer, offset uint64) error {
fi.InsnOff += uint32(offset)
// The kernel expects offsets in number of raw bpf instructions,
// while the ELF tracks it in bytes.
fi.InsnOff /= asm.InstructionSize
return binary.Write(w, internal.NativeEndian, fi)
}
type FuncInfos []FuncInfo
// funcForOffset returns the function that the instruction at the given
// ELF section offset belongs to.
//
// For example, consider an ELF section that contains 3 functions (a, b, c)
// at offsets 0, 10 and 15 respectively. Offset 5 will return function a,
// offset 12 will return b, offset >= 15 will return c, etc.
func (infos FuncInfos) funcForOffset(offset uint32) *FuncInfo {
for n, fi := range infos {
// Iterator went past the offset the caller is looking for,
// no point in continuing the search.
if offset < fi.InsnOff {
return nil
}
// If there is no next item in the list, or if the given offset
// is smaller than the next function, the offset belongs to
// the current function.
if n+1 >= len(infos) || offset < infos[n+1].InsnOff {
return &fi
}
}
return nil
}
// parseLineInfos parses a func_info sub-section within .BTF.ext ito a map of
// func infos indexed by section name.
func parseFuncInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]FuncInfos, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
result := make(map[string]FuncInfos)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseFuncInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// parseFuncInfoRecords parses a stream of func_infos into a funcInfos.
// These records appear after a btf_ext_info_sec header in the func_info
// sub-section of .BTF.ext.
func parseFuncInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) (FuncInfos, error) {
var out FuncInfos
var fi FuncInfo
if exp, got := uint32(binary.Size(fi)), recordSize; exp != got {
// BTF blob's record size is longer than we know how to parse.
return nil, fmt.Errorf("expected FuncInfo record size %d, but BTF blob contains %d", exp, got)
}
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &fi); err != nil {
return nil, fmt.Errorf("can't read function info: %v", err)
}
if fi.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", fi.InsnOff)
}
out = append(out, fi)
}
return out, nil
}
// LineInfo represents the location and contents of a single line of source
// code a BPF ELF was compiled from.
type LineInfo struct {
// Instruction offset of the function within an ELF section.
// After parsing a LineInfo from an ELF, this offset is relative to
// the function body instead of an ELF section.
InsnOff uint32
FileNameOff uint32
LineOff uint32
LineCol uint32
}
// Marshal writes the binary representation of the LineInfo to w.
// The instruction offset is converted from bytes to instructions.
func (li LineInfo) Marshal(w io.Writer, offset uint64) error {
li.InsnOff += uint32(offset)
// The kernel expects offsets in number of raw bpf instructions,
// while the ELF tracks it in bytes.
li.InsnOff /= asm.InstructionSize
return binary.Write(w, internal.NativeEndian, li)
}
type LineInfos []LineInfo
// Marshal writes the binary representation of the LineInfos to w.
func (li LineInfos) Marshal(w io.Writer, off uint64) error {
if len(li) == 0 {
return nil
}
for _, info := range li {
if err := info.Marshal(w, off); err != nil {
return err
}
}
return nil
}
// parseLineInfos parses a line_info sub-section within .BTF.ext ito a map of
// line infos indexed by section name.
func parseLineInfos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]LineInfos, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
result := make(map[string]LineInfos)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseLineInfoRecords(r, bo, recordSize, infoHeader.NumInfo)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// parseLineInfoRecords parses a stream of line_infos into a lineInfos.
// These records appear after a btf_ext_info_sec header in the line_info
// sub-section of .BTF.ext.
func parseLineInfoRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32) (LineInfos, error) {
var out LineInfos
var li LineInfo
if exp, got := uint32(binary.Size(li)), recordSize; exp != got {
// BTF blob's record size is longer than we know how to parse.
return nil, fmt.Errorf("expected LineInfo record size %d, but BTF blob contains %d", exp, got)
}
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &li); err != nil {
return nil, fmt.Errorf("can't read line info: %v", err)
}
if li.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", li.InsnOff)
}
out = append(out, li)
}
return out, nil
}
// bpfCoreRelo matches the kernel's struct bpf_core_relo.
type bpfCoreRelo struct {
InsnOff uint32
TypeID TypeID
AccessStrOff uint32
Kind COREKind
}
type CoreRelo struct {
insnOff uint32
typeID TypeID
accessor coreAccessor
kind COREKind
}
type CoreRelos []CoreRelo
var extInfoReloSize = binary.Size(bpfCoreRelo{})
// parseCoreRelos parses a core_relos sub-section within .BTF.ext ito a map of
// CO-RE relocations indexed by section name.
func parseCoreRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]CoreRelos, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
if recordSize != uint32(extInfoReloSize) {
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
}
result := make(map[string]CoreRelos)
for {
secName, infoHeader, err := parseExtInfoSec(r, bo, strings)
if errors.Is(err, io.EOF) {
return result, nil
}
if err != nil {
return nil, err
}
records, err := parseCoreReloRecords(r, bo, recordSize, infoHeader.NumInfo, strings)
if err != nil {
return nil, fmt.Errorf("section %v: %w", secName, err)
}
result[secName] = records
}
}
// parseCoreReloRecords parses a stream of CO-RE relocation entries into a
// coreRelos. These records appear after a btf_ext_info_sec header in the
// core_relos sub-section of .BTF.ext.
func parseCoreReloRecords(r io.Reader, bo binary.ByteOrder, recordSize uint32, recordNum uint32, strings stringTable) (CoreRelos, error) {
var out CoreRelos
var relo bpfCoreRelo
for i := uint32(0); i < recordNum; i++ {
if err := binary.Read(r, bo, &relo); err != nil {
return nil, fmt.Errorf("can't read CO-RE relocation: %v", err)
}
if relo.InsnOff%asm.InstructionSize != 0 {
return nil, fmt.Errorf("offset %v is not aligned with instruction size", relo.InsnOff)
}
accessorStr, err := strings.Lookup(relo.AccessStrOff)
if err != nil {
return nil, err
}
accessor, err := parseCoreAccessor(accessorStr)
if err != nil {
return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
}
out = append(out, CoreRelo{
relo.InsnOff,
relo.TypeID,
accessor,
relo.Kind,
})
}
return out, nil
}
+304
View File
@@ -0,0 +1,304 @@
package btf
import (
"errors"
"fmt"
"strings"
)
var errNestedTooDeep = errors.New("nested too deep")
// GoFormatter converts a Type to Go syntax.
//
// A zero GoFormatter is valid to use.
type GoFormatter struct {
w strings.Builder
// Types present in this map are referred to using the given name if they
// are encountered when outputting another type.
Names map[Type]string
// Identifier is called for each field of struct-like types. By default the
// field name is used as is.
Identifier func(string) string
// EnumIdentifier is called for each element of an enum. By default the
// name of the enum type is concatenated with Identifier(element).
EnumIdentifier func(name, element string) string
}
// TypeDeclaration generates a Go type declaration for a BTF type.
func (gf *GoFormatter) TypeDeclaration(name string, typ Type) (string, error) {
gf.w.Reset()
if err := gf.writeTypeDecl(name, typ); err != nil {
return "", err
}
return gf.w.String(), nil
}
func (gf *GoFormatter) identifier(s string) string {
if gf.Identifier != nil {
return gf.Identifier(s)
}
return s
}
func (gf *GoFormatter) enumIdentifier(name, element string) string {
if gf.EnumIdentifier != nil {
return gf.EnumIdentifier(name, element)
}
return name + gf.identifier(element)
}
// writeTypeDecl outputs a declaration of the given type.
//
// It encodes https://golang.org/ref/spec#Type_declarations:
//
// type foo struct { bar uint32; }
// type bar int32
func (gf *GoFormatter) writeTypeDecl(name string, typ Type) error {
if name == "" {
return fmt.Errorf("need a name for type %s", typ)
}
typ, err := skipQualifiers(typ)
if err != nil {
return err
}
switch v := typ.(type) {
case *Enum:
fmt.Fprintf(&gf.w, "type %s int32", name)
if len(v.Values) == 0 {
return nil
}
gf.w.WriteString("; const ( ")
for _, ev := range v.Values {
id := gf.enumIdentifier(name, ev.Name)
fmt.Fprintf(&gf.w, "%s %s = %d; ", id, name, ev.Value)
}
gf.w.WriteString(")")
return nil
}
fmt.Fprintf(&gf.w, "type %s ", name)
return gf.writeTypeLit(typ, 0)
}
// writeType outputs the name of a named type or a literal describing the type.
//
// It encodes https://golang.org/ref/spec#Types.
//
// foo (if foo is a named type)
// uint32
func (gf *GoFormatter) writeType(typ Type, depth int) error {
typ, err := skipQualifiers(typ)
if err != nil {
return err
}
name := gf.Names[typ]
if name != "" {
gf.w.WriteString(name)
return nil
}
return gf.writeTypeLit(typ, depth)
}
// writeTypeLit outputs a literal describing the type.
//
// The function ignores named types.
//
// It encodes https://golang.org/ref/spec#TypeLit.
//
// struct { bar uint32; }
// uint32
func (gf *GoFormatter) writeTypeLit(typ Type, depth int) error {
depth++
if depth > maxTypeDepth {
return errNestedTooDeep
}
typ, err := skipQualifiers(typ)
if err != nil {
return err
}
switch v := typ.(type) {
case *Int:
gf.writeIntLit(v)
case *Enum:
gf.w.WriteString("int32")
case *Typedef:
err = gf.writeType(v.Type, depth)
case *Array:
fmt.Fprintf(&gf.w, "[%d]", v.Nelems)
err = gf.writeType(v.Type, depth)
case *Struct:
err = gf.writeStructLit(v.Size, v.Members, depth)
case *Union:
// Always choose the first member to represent the union in Go.
err = gf.writeStructLit(v.Size, v.Members[:1], depth)
case *Datasec:
err = gf.writeDatasecLit(v, depth)
default:
return fmt.Errorf("type %s: %w", typ, ErrNotSupported)
}
if err != nil {
return fmt.Errorf("%s: %w", typ, err)
}
return nil
}
func (gf *GoFormatter) writeIntLit(i *Int) {
// NB: Encoding.IsChar is ignored.
if i.Encoding.IsBool() && i.Size == 1 {
gf.w.WriteString("bool")
return
}
bits := i.Size * 8
if i.Encoding.IsSigned() {
fmt.Fprintf(&gf.w, "int%d", bits)
} else {
fmt.Fprintf(&gf.w, "uint%d", bits)
}
}
func (gf *GoFormatter) writeStructLit(size uint32, members []Member, depth int) error {
gf.w.WriteString("struct { ")
prevOffset := uint32(0)
skippedBitfield := false
for i, m := range members {
if m.BitfieldSize > 0 {
skippedBitfield = true
continue
}
offset := m.OffsetBits / 8
if n := offset - prevOffset; skippedBitfield && n > 0 {
fmt.Fprintf(&gf.w, "_ [%d]byte /* unsupported bitfield */; ", n)
} else {
gf.writePadding(n)
}
size, err := Sizeof(m.Type)
if err != nil {
return fmt.Errorf("field %d: %w", i, err)
}
prevOffset = offset + uint32(size)
if err := gf.writeStructField(m, depth); err != nil {
return fmt.Errorf("field %d: %w", i, err)
}
}
gf.writePadding(size - prevOffset)
gf.w.WriteString("}")
return nil
}
func (gf *GoFormatter) writeStructField(m Member, depth int) error {
if m.BitfieldSize > 0 {
return fmt.Errorf("bitfields are not supported")
}
if m.OffsetBits%8 != 0 {
return fmt.Errorf("unsupported offset %d", m.OffsetBits)
}
if m.Name == "" {
// Special case a nested anonymous union like
// struct foo { union { int bar; int baz }; }
// by replacing the whole union with its first member.
union, ok := m.Type.(*Union)
if !ok {
return fmt.Errorf("anonymous fields are not supported")
}
if len(union.Members) == 0 {
return errors.New("empty anonymous union")
}
depth++
if depth > maxTypeDepth {
return errNestedTooDeep
}
m := union.Members[0]
size, err := Sizeof(m.Type)
if err != nil {
return err
}
if err := gf.writeStructField(m, depth); err != nil {
return err
}
gf.writePadding(union.Size - uint32(size))
return nil
}
fmt.Fprintf(&gf.w, "%s ", gf.identifier(m.Name))
if err := gf.writeType(m.Type, depth); err != nil {
return err
}
gf.w.WriteString("; ")
return nil
}
func (gf *GoFormatter) writeDatasecLit(ds *Datasec, depth int) error {
gf.w.WriteString("struct { ")
prevOffset := uint32(0)
for i, vsi := range ds.Vars {
v := vsi.Type.(*Var)
if v.Linkage != GlobalVar {
// Ignore static, extern, etc. for now.
continue
}
if v.Name == "" {
return fmt.Errorf("variable %d: empty name", i)
}
gf.writePadding(vsi.Offset - prevOffset)
prevOffset = vsi.Offset + vsi.Size
fmt.Fprintf(&gf.w, "%s ", gf.identifier(v.Name))
if err := gf.writeType(v.Type, depth); err != nil {
return fmt.Errorf("variable %d: %w", i, err)
}
gf.w.WriteString("; ")
}
gf.writePadding(ds.Size - prevOffset)
gf.w.WriteString("}")
return nil
}
func (gf *GoFormatter) writePadding(bytes uint32) {
if bytes > 0 {
fmt.Fprintf(&gf.w, "_ [%d]byte; ", bytes)
}
}
-49
View File
@@ -1,49 +0,0 @@
// +build gofuzz
// Use with https://github.com/dvyukov/go-fuzz
package btf
import (
"bytes"
"encoding/binary"
"github.com/cilium/ebpf/internal"
)
func FuzzSpec(data []byte) int {
if len(data) < binary.Size(btfHeader{}) {
return -1
}
spec, err := loadNakedSpec(bytes.NewReader(data), internal.NativeEndian, nil, nil)
if err != nil {
if spec != nil {
panic("spec is not nil")
}
return 0
}
if spec == nil {
panic("spec is nil")
}
return 1
}
func FuzzExtInfo(data []byte) int {
if len(data) < binary.Size(btfExtHeader{}) {
return -1
}
table := stringTable("\x00foo\x00barfoo\x00")
info, err := parseExtInfo(bytes.NewReader(data), internal.NativeEndian, table)
if err != nil {
if info != nil {
panic("info is not nil")
}
return 0
}
if info == nil {
panic("info is nil")
}
return 1
}
+51
View File
@@ -0,0 +1,51 @@
package btf
import (
"bytes"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// info describes a BTF object.
type info struct {
BTF *Spec
ID ID
// Name is an identifying name for the BTF, currently only used by the
// kernel.
Name string
// KernelBTF is true if the BTf originated with the kernel and not
// userspace.
KernelBTF bool
}
func newInfoFromFd(fd *sys.FD) (*info, error) {
// We invoke the syscall once with a empty BTF and name buffers to get size
// information to allocate buffers. Then we invoke it a second time with
// buffers to receive the data.
var btfInfo sys.BtfInfo
if err := sys.ObjInfo(fd, &btfInfo); err != nil {
return nil, err
}
btfBuffer := make([]byte, btfInfo.BtfSize)
nameBuffer := make([]byte, btfInfo.NameLen)
btfInfo.Btf, btfInfo.BtfSize = sys.NewSlicePointerLen(btfBuffer)
btfInfo.Name, btfInfo.NameLen = sys.NewSlicePointerLen(nameBuffer)
if err := sys.ObjInfo(fd, &btfInfo); err != nil {
return nil, err
}
spec, err := loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, nil, nil)
if err != nil {
return nil, err
}
return &info{
BTF: spec,
ID: ID(btfInfo.Id),
Name: unix.ByteSliceToString(nameBuffer),
KernelBTF: btfInfo.KernelBtf != 0,
}, nil
}
+1 -7
View File
@@ -5,13 +5,12 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
)
type stringTable []byte
func readStringTable(r io.Reader) (stringTable, error) {
contents, err := ioutil.ReadAll(r)
contents, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("can't read string table: %v", err)
}
@@ -53,8 +52,3 @@ func (st stringTable) Lookup(offset uint32) (string, error) {
return string(str[:end]), nil
}
func (st stringTable) LookupName(offset uint32) (Name, error) {
str, err := st.Lookup(offset)
return Name(str), err
}
+215 -94
View File
@@ -1,7 +1,6 @@
package btf
import (
"errors"
"fmt"
"math"
"strings"
@@ -19,9 +18,12 @@ func (tid TypeID) ID() TypeID {
// Type represents a type described by BTF.
type Type interface {
// The type ID of the Type within this BTF spec.
ID() TypeID
String() string
// Name of the type, empty for anonymous types and types that cannot
// carry a name, like Void and Pointer.
TypeName() string
// Make a copy of the type, without copying Type members.
copy() Type
@@ -29,33 +31,32 @@ type Type interface {
// Enumerate all nested Types. Repeated calls must visit nested
// types in the same order.
walk(*typeDeque)
String() string
}
// namedType is a type with a name.
//
// Most named types simply embed Name.
type namedType interface {
Type
name() string
}
// Name identifies a type.
//
// Anonymous types have an empty name.
type Name string
func (n Name) name() string {
return string(n)
}
var (
_ Type = (*Int)(nil)
_ Type = (*Struct)(nil)
_ Type = (*Union)(nil)
_ Type = (*Enum)(nil)
_ Type = (*Fwd)(nil)
_ Type = (*Func)(nil)
_ Type = (*Typedef)(nil)
_ Type = (*Var)(nil)
_ Type = (*Datasec)(nil)
_ Type = (*Float)(nil)
)
// Void is the unit type of BTF.
type Void struct{}
func (v *Void) ID() TypeID { return 0 }
func (v *Void) String() string { return "void#0" }
func (v *Void) size() uint32 { return 0 }
func (v *Void) copy() Type { return (*Void)(nil) }
func (v *Void) walk(*typeDeque) {}
func (v *Void) ID() TypeID { return 0 }
func (v *Void) String() string { return "void#0" }
func (v *Void) TypeName() string { return "" }
func (v *Void) size() uint32 { return 0 }
func (v *Void) copy() Type { return (*Void)(nil) }
func (v *Void) walk(*typeDeque) {}
type IntEncoding byte
@@ -65,32 +66,43 @@ const (
Bool
)
func (ie IntEncoding) IsSigned() bool {
return ie&Signed != 0
}
func (ie IntEncoding) IsChar() bool {
return ie&Char != 0
}
func (ie IntEncoding) IsBool() bool {
return ie&Bool != 0
}
// Int is an integer of a given length.
type Int struct {
TypeID
Name
Name string
// The size of the integer in bytes.
Size uint32
Encoding IntEncoding
// Offset is the starting bit offset. Currently always 0.
// OffsetBits is the starting bit offset. Currently always 0.
// See https://www.kernel.org/doc/html/latest/bpf/btf.html#btf-kind-int
Offset uint32
Bits byte
OffsetBits uint32
Bits byte
}
var _ namedType = (*Int)(nil)
func (i *Int) String() string {
var s strings.Builder
switch {
case i.Encoding&Char != 0:
case i.Encoding.IsChar():
s.WriteString("char")
case i.Encoding&Bool != 0:
case i.Encoding.IsBool():
s.WriteString("bool")
default:
if i.Encoding&Signed == 0 {
if !i.Encoding.IsSigned() {
s.WriteRune('u')
}
s.WriteString("int")
@@ -106,15 +118,16 @@ func (i *Int) String() string {
return s.String()
}
func (i *Int) size() uint32 { return i.Size }
func (i *Int) walk(*typeDeque) {}
func (i *Int) TypeName() string { return i.Name }
func (i *Int) size() uint32 { return i.Size }
func (i *Int) walk(*typeDeque) {}
func (i *Int) copy() Type {
cpy := *i
return &cpy
}
func (i *Int) isBitfield() bool {
return i.Offset > 0
return i.OffsetBits > 0
}
// Pointer is a pointer to another type.
@@ -127,6 +140,7 @@ func (p *Pointer) String() string {
return fmt.Sprintf("pointer#%d[target=#%d]", p.TypeID, p.Target.ID())
}
func (p *Pointer) TypeName() string { return "" }
func (p *Pointer) size() uint32 { return 8 }
func (p *Pointer) walk(tdq *typeDeque) { tdq.push(&p.Target) }
func (p *Pointer) copy() Type {
@@ -145,6 +159,8 @@ func (arr *Array) String() string {
return fmt.Sprintf("array#%d[type=#%d n=%d]", arr.TypeID, arr.Type.ID(), arr.Nelems)
}
func (arr *Array) TypeName() string { return "" }
func (arr *Array) walk(tdq *typeDeque) { tdq.push(&arr.Type) }
func (arr *Array) copy() Type {
cpy := *arr
@@ -154,7 +170,7 @@ func (arr *Array) copy() Type {
// Struct is a compound type of consecutive members.
type Struct struct {
TypeID
Name
Name string
// The size of the struct including padding, in bytes
Size uint32
Members []Member
@@ -164,6 +180,8 @@ func (s *Struct) String() string {
return fmt.Sprintf("struct#%d[%q]", s.TypeID, s.Name)
}
func (s *Struct) TypeName() string { return s.Name }
func (s *Struct) size() uint32 { return s.Size }
func (s *Struct) walk(tdq *typeDeque) {
@@ -174,8 +192,7 @@ func (s *Struct) walk(tdq *typeDeque) {
func (s *Struct) copy() Type {
cpy := *s
cpy.Members = make([]Member, len(s.Members))
copy(cpy.Members, s.Members)
cpy.Members = copyMembers(s.Members)
return &cpy
}
@@ -186,7 +203,7 @@ func (s *Struct) members() []Member {
// Union is a compound type where members occupy the same memory.
type Union struct {
TypeID
Name
Name string
// The size of the union including padding, in bytes.
Size uint32
Members []Member
@@ -196,6 +213,8 @@ func (u *Union) String() string {
return fmt.Sprintf("union#%d[%q]", u.TypeID, u.Name)
}
func (u *Union) TypeName() string { return u.Name }
func (u *Union) size() uint32 { return u.Size }
func (u *Union) walk(tdq *typeDeque) {
@@ -206,8 +225,7 @@ func (u *Union) walk(tdq *typeDeque) {
func (u *Union) copy() Type {
cpy := *u
cpy.Members = make([]Member, len(u.Members))
copy(cpy.Members, u.Members)
cpy.Members = copyMembers(u.Members)
return &cpy
}
@@ -215,6 +233,12 @@ func (u *Union) members() []Member {
return u.Members
}
func copyMembers(orig []Member) []Member {
cpy := make([]Member, len(orig))
copy(cpy, orig)
return cpy
}
type composite interface {
members() []Member
}
@@ -228,17 +252,17 @@ var (
//
// It is not a valid Type.
type Member struct {
Name
Name string
Type Type
// Offset is the bit offset of this member
Offset uint32
// OffsetBits is the bit offset of this member.
OffsetBits uint32
BitfieldSize uint32
}
// Enum lists possible values.
type Enum struct {
TypeID
Name
Name string
Values []EnumValue
}
@@ -246,11 +270,13 @@ func (e *Enum) String() string {
return fmt.Sprintf("enum#%d[%q]", e.TypeID, e.Name)
}
func (e *Enum) TypeName() string { return e.Name }
// EnumValue is part of an Enum
//
// Is is not a valid Type
type EnumValue struct {
Name
Name string
Value int32
}
@@ -286,7 +312,7 @@ func (fk FwdKind) String() string {
// Fwd is a forward declaration of a Type.
type Fwd struct {
TypeID
Name
Name string
Kind FwdKind
}
@@ -294,6 +320,8 @@ func (f *Fwd) String() string {
return fmt.Sprintf("fwd#%d[%s %q]", f.TypeID, f.Kind, f.Name)
}
func (f *Fwd) TypeName() string { return f.Name }
func (f *Fwd) walk(*typeDeque) {}
func (f *Fwd) copy() Type {
cpy := *f
@@ -303,7 +331,7 @@ func (f *Fwd) copy() Type {
// Typedef is an alias of a Type.
type Typedef struct {
TypeID
Name
Name string
Type Type
}
@@ -311,6 +339,8 @@ func (td *Typedef) String() string {
return fmt.Sprintf("typedef#%d[%q #%d]", td.TypeID, td.Name, td.Type.ID())
}
func (td *Typedef) TypeName() string { return td.Name }
func (td *Typedef) walk(tdq *typeDeque) { tdq.push(&td.Type) }
func (td *Typedef) copy() Type {
cpy := *td
@@ -327,6 +357,8 @@ func (v *Volatile) String() string {
return fmt.Sprintf("volatile#%d[#%d]", v.TypeID, v.Type.ID())
}
func (v *Volatile) TypeName() string { return "" }
func (v *Volatile) qualify() Type { return v.Type }
func (v *Volatile) walk(tdq *typeDeque) { tdq.push(&v.Type) }
func (v *Volatile) copy() Type {
@@ -344,6 +376,8 @@ func (c *Const) String() string {
return fmt.Sprintf("const#%d[#%d]", c.TypeID, c.Type.ID())
}
func (c *Const) TypeName() string { return "" }
func (c *Const) qualify() Type { return c.Type }
func (c *Const) walk(tdq *typeDeque) { tdq.push(&c.Type) }
func (c *Const) copy() Type {
@@ -361,6 +395,8 @@ func (r *Restrict) String() string {
return fmt.Sprintf("restrict#%d[#%d]", r.TypeID, r.Type.ID())
}
func (r *Restrict) TypeName() string { return "" }
func (r *Restrict) qualify() Type { return r.Type }
func (r *Restrict) walk(tdq *typeDeque) { tdq.push(&r.Type) }
func (r *Restrict) copy() Type {
@@ -371,14 +407,17 @@ func (r *Restrict) copy() Type {
// Func is a function definition.
type Func struct {
TypeID
Name
Type Type
Name string
Type Type
Linkage FuncLinkage
}
func (f *Func) String() string {
return fmt.Sprintf("func#%d[%q proto=#%d]", f.TypeID, f.Name, f.Type.ID())
return fmt.Sprintf("func#%d[%s %q proto=#%d]", f.TypeID, f.Linkage, f.Name, f.Type.ID())
}
func (f *Func) TypeName() string { return f.Name }
func (f *Func) walk(tdq *typeDeque) { tdq.push(&f.Type) }
func (f *Func) copy() Type {
cpy := *f
@@ -402,6 +441,8 @@ func (fp *FuncProto) String() string {
return s.String()
}
func (fp *FuncProto) TypeName() string { return "" }
func (fp *FuncProto) walk(tdq *typeDeque) {
tdq.push(&fp.Return)
for i := range fp.Params {
@@ -417,22 +458,24 @@ func (fp *FuncProto) copy() Type {
}
type FuncParam struct {
Name
Name string
Type Type
}
// Var is a global variable.
type Var struct {
TypeID
Name
Type Type
Name string
Type Type
Linkage VarLinkage
}
func (v *Var) String() string {
// TODO: Linkage
return fmt.Sprintf("var#%d[%q]", v.TypeID, v.Name)
return fmt.Sprintf("var#%d[%s %q]", v.TypeID, v.Linkage, v.Name)
}
func (v *Var) TypeName() string { return v.Name }
func (v *Var) walk(tdq *typeDeque) { tdq.push(&v.Type) }
func (v *Var) copy() Type {
cpy := *v
@@ -442,7 +485,7 @@ func (v *Var) copy() Type {
// Datasec is a global program section containing data.
type Datasec struct {
TypeID
Name
Name string
Size uint32
Vars []VarSecinfo
}
@@ -451,6 +494,8 @@ func (ds *Datasec) String() string {
return fmt.Sprintf("section#%d[%q]", ds.TypeID, ds.Name)
}
func (ds *Datasec) TypeName() string { return ds.Name }
func (ds *Datasec) size() uint32 { return ds.Size }
func (ds *Datasec) walk(tdq *typeDeque) {
@@ -466,7 +511,7 @@ func (ds *Datasec) copy() Type {
return &cpy
}
// VarSecinfo describes variable in a Datasec
// VarSecinfo describes variable in a Datasec.
//
// It is not a valid Type.
type VarSecinfo struct {
@@ -475,6 +520,27 @@ type VarSecinfo struct {
Size uint32
}
// Float is a float of a given length.
type Float struct {
TypeID
Name string
// The size of the float in bytes.
Size uint32
}
func (f *Float) String() string {
return fmt.Sprintf("float%d#%d[%q]", f.Size*8, f.TypeID, f.Name)
}
func (f *Float) TypeName() string { return f.Name }
func (f *Float) size() uint32 { return f.Size }
func (f *Float) walk(*typeDeque) {}
func (f *Float) copy() Type {
cpy := *f
return &cpy
}
type sizer interface {
size() uint32
}
@@ -511,7 +577,7 @@ func Sizeof(typ Type) (int, error) {
switch v := typ.(type) {
case *Array:
if n > 0 && int64(v.Nelems) > math.MaxInt64/n {
return 0, errors.New("overflow")
return 0, fmt.Errorf("type %s: overflow", typ)
}
// Arrays may be of zero length, which allows
@@ -532,49 +598,89 @@ func Sizeof(typ Type) (int, error) {
continue
default:
return 0, fmt.Errorf("unrecognized type %T", typ)
return 0, fmt.Errorf("unsized type %T", typ)
}
if n > 0 && elem > math.MaxInt64/n {
return 0, errors.New("overflow")
return 0, fmt.Errorf("type %s: overflow", typ)
}
size := n * elem
if int64(int(size)) != size {
return 0, errors.New("overflow")
return 0, fmt.Errorf("type %s: overflow", typ)
}
return int(size), nil
}
return 0, errors.New("exceeded type depth")
return 0, fmt.Errorf("type %s: exceeded type depth", typ)
}
// Copy a Type recursively.
func Copy(typ Type) Type {
typ, _ = copyType(typ, nil)
return typ
}
// copy a Type recursively.
//
// typ may form a cycle.
func copyType(typ Type) Type {
var (
copies = make(map[Type]Type)
work typeDeque
)
//
// Returns any errors from transform verbatim.
func copyType(typ Type, transform func(Type) (Type, error)) (Type, error) {
copies := make(copier)
return typ, copies.copy(&typ, transform)
}
for t := &typ; t != nil; t = work.pop() {
// copy a slice of Types recursively.
//
// Types may form a cycle.
//
// Returns any errors from transform verbatim.
func copyTypes(types []Type, transform func(Type) (Type, error)) ([]Type, error) {
result := make([]Type, len(types))
copy(result, types)
copies := make(copier)
for i := range result {
if err := copies.copy(&result[i], transform); err != nil {
return nil, err
}
}
return result, nil
}
type copier map[Type]Type
func (c copier) copy(typ *Type, transform func(Type) (Type, error)) error {
var work typeDeque
for t := typ; t != nil; t = work.pop() {
// *t is the identity of the type.
if cpy := copies[*t]; cpy != nil {
if cpy := c[*t]; cpy != nil {
*t = cpy
continue
}
cpy := (*t).copy()
copies[*t] = cpy
var cpy Type
if transform != nil {
tf, err := transform(*t)
if err != nil {
return fmt.Errorf("copy %s: %w", *t, err)
}
cpy = tf.copy()
} else {
cpy = (*t).copy()
}
c[*t] = cpy
*t = cpy
// Mark any nested types for copying.
cpy.walk(&work)
}
return typ
return nil
}
// typeDeque keeps track of pointers to types which still
@@ -585,6 +691,10 @@ type typeDeque struct {
mask uint64
}
func (dq *typeDeque) empty() bool {
return dq.read == dq.write
}
// push adds a type to the stack.
func (dq *typeDeque) push(t *Type) {
if dq.write-dq.read < uint64(len(dq.types)) {
@@ -611,7 +721,7 @@ func (dq *typeDeque) push(t *Type) {
// shift returns the first element or null.
func (dq *typeDeque) shift() *Type {
if dq.read == dq.write {
if dq.empty() {
return nil
}
@@ -624,7 +734,7 @@ func (dq *typeDeque) shift() *Type {
// pop returns the last element or null.
func (dq *typeDeque) pop() *Type {
if dq.read == dq.write {
if dq.empty() {
return nil
}
@@ -653,7 +763,7 @@ func (dq *typeDeque) all() []*Type {
// Returns a map of named types (so, where NameOff is non-zero) and a slice of types
// indexed by TypeID. Since BTF ignores 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) (types []Type, namedTypes map[string][]namedType, err error) {
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type, namedTypes map[essentialName][]Type, err error) {
type fixupDef struct {
id TypeID
expectedKind btfKind
@@ -670,17 +780,17 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
// work, since otherwise append might re-allocate members.
members := make([]Member, 0, len(raw))
for i, btfMember := range raw {
name, err := rawStrings.LookupName(btfMember.NameOff)
name, err := rawStrings.Lookup(btfMember.NameOff)
if err != nil {
return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
}
m := Member{
Name: name,
Offset: btfMember.Offset,
Name: name,
OffsetBits: btfMember.Offset,
}
if kindFlag {
m.BitfieldSize = btfMember.Offset >> 24
m.Offset &= 0xffffff
m.OffsetBits &= 0xffffff
}
members = append(members, m)
}
@@ -692,7 +802,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
types = make([]Type, 0, len(rawTypes))
types = append(types, (*Void)(nil))
namedTypes = make(map[string][]namedType)
namedTypes = make(map[essentialName][]Type)
for i, raw := range rawTypes {
var (
@@ -702,7 +812,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
typ Type
)
name, err := rawStrings.LookupName(raw.NameOff)
name, err := rawStrings.Lookup(raw.NameOff)
if err != nil {
return nil, nil, fmt.Errorf("get name for type id %d: %w", id, err)
}
@@ -744,7 +854,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
rawvals := raw.data.([]btfEnum)
vals := make([]EnumValue, 0, len(rawvals))
for i, btfVal := range rawvals {
name, err := rawStrings.LookupName(btfVal.NameOff)
name, err := rawStrings.Lookup(btfVal.NameOff)
if err != nil {
return nil, nil, fmt.Errorf("get name for enum value %d: %s", i, err)
}
@@ -783,7 +893,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
typ = restrict
case kindFunc:
fn := &Func{id, name, nil}
fn := &Func{id, name, nil, raw.Linkage()}
fixup(raw.Type(), kindFuncProto, &fn.Type)
typ = fn
@@ -791,7 +901,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
rawparams := raw.data.([]btfParam)
params := make([]FuncParam, 0, len(rawparams))
for i, param := range rawparams {
name, err := rawStrings.LookupName(param.NameOff)
name, err := rawStrings.Lookup(param.NameOff)
if err != nil {
return nil, nil, fmt.Errorf("get name for func proto parameter %d: %s", i, err)
}
@@ -808,7 +918,8 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
typ = fp
case kindVar:
v := &Var{id, name, nil}
variable := raw.data.(*btfVariable)
v := &Var{id, name, nil, VarLinkage(variable.Linkage)}
fixup(raw.Type(), kindUnknown, &v.Type)
typ = v
@@ -826,16 +937,17 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
}
typ = &Datasec{id, name, raw.SizeType, vars}
case kindFloat:
typ = &Float{id, name, raw.Size()}
default:
return nil, nil, fmt.Errorf("type id %d: unknown kind: %v", id, raw.Kind())
}
types = append(types, typ)
if named, ok := typ.(namedType); ok {
if name := essentialName(named.name()); name != "" {
namedTypes[name] = append(namedTypes[name], named)
}
if name := newEssentialName(typ.TypeName()); name != "" {
namedTypes[name] = append(namedTypes[name], typ)
}
}
@@ -861,11 +973,20 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
return types, namedTypes, nil
}
// essentialName returns name without a ___ suffix.
func essentialName(name string) string {
// essentialName represents the name of a BTF type stripped of any flavor
// suffixes after a ___ delimiter.
type essentialName string
// newEssentialName returns name without a ___ suffix.
//
// CO-RE has the concept of 'struct flavors', which are used to deal with
// changes in kernel data structures. Anything after three underscores
// in a type name is ignored for the purpose of finding a candidate type
// in the kernel's BTF.
func newEssentialName(name string) essentialName {
lastIdx := strings.LastIndex(name, "___")
if lastIdx > 0 {
return name[:lastIdx]
return essentialName(name[:lastIdx])
}
return name
return essentialName(name)
}
+2 -2
View File
@@ -2,7 +2,7 @@ package internal
import (
"fmt"
"io/ioutil"
"os"
"strings"
"sync"
)
@@ -24,7 +24,7 @@ func PossibleCPUs() (int, error) {
}
func parseCPUsFromFile(path string) (int, error) {
spec, err := ioutil.ReadFile(path)
spec, err := os.ReadFile(path)
if err != nil {
return 0, err
}
+16
View File
@@ -50,3 +50,19 @@ func (se *SafeELFFile) Symbols() (syms []elf.Symbol, err error) {
syms, err = se.File.Symbols()
return
}
// DynamicSymbols is the safe version of elf.File.DynamicSymbols.
func (se *SafeELFFile) DynamicSymbols() (syms []elf.Symbol, err error) {
defer func() {
r := recover()
if r == nil {
return
}
syms = nil
err = fmt.Errorf("reading ELF dynamic symbols panicked: %s", r)
}()
syms, err = se.File.DynamicSymbols()
return
}
+5
View File
@@ -9,11 +9,16 @@ import (
// depending on the host's endianness.
var NativeEndian binary.ByteOrder
// Clang is set to either "el" or "eb" depending on the host's endianness.
var ClangEndian string
func init() {
if isBigEndian() {
NativeEndian = binary.BigEndian
ClangEndian = "eb"
} else {
NativeEndian = binary.LittleEndian
ClangEndian = "el"
}
}
+5 -11
View File
@@ -1,7 +1,6 @@
package internal
import (
"bytes"
"errors"
"fmt"
"strings"
@@ -15,7 +14,7 @@ import (
// logErr should be the error returned by the syscall that generated
// 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 ")
logStr := strings.Trim(unix.ByteSliceToString(log), "\t\r\n ")
if errors.Is(logErr, unix.ENOSPC) {
logStr += " (truncated...)"
}
@@ -29,6 +28,10 @@ type VerifierError struct {
log string
}
func (le *VerifierError) Unwrap() error {
return le.cause
}
func (le *VerifierError) Error() string {
if le.log == "" {
return le.cause.Error()
@@ -36,12 +39,3 @@ func (le *VerifierError) Error() string {
return fmt.Sprintf("%s: %s", le.cause, le.log)
}
// CString turns a NUL / zero terminated byte buffer into a string.
func CString(in []byte) string {
inLen := bytes.IndexByte(in, 0)
if inLen == -1 {
return ""
}
return string(in[:inLen])
}
-69
View File
@@ -1,69 +0,0 @@
package internal
import (
"errors"
"fmt"
"os"
"runtime"
"strconv"
"github.com/cilium/ebpf/internal/unix"
)
var ErrClosedFd = errors.New("use of closed file descriptor")
type FD struct {
raw int64
}
func NewFD(value uint32) *FD {
fd := &FD{int64(value)}
runtime.SetFinalizer(fd, (*FD).Close)
return fd
}
func (fd *FD) String() string {
return strconv.FormatInt(fd.raw, 10)
}
func (fd *FD) Value() (uint32, error) {
if fd.raw < 0 {
return 0, ErrClosedFd
}
return uint32(fd.raw), nil
}
func (fd *FD) Close() error {
if fd.raw < 0 {
return nil
}
value := int(fd.raw)
fd.raw = -1
fd.Forget()
return unix.Close(value)
}
func (fd *FD) Forget() {
runtime.SetFinalizer(fd, nil)
}
func (fd *FD) Dup() (*FD, error) {
if fd.raw < 0 {
return nil, ErrClosedFd
}
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0)
if err != nil {
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)
}
-38
View File
@@ -98,41 +98,3 @@ func FeatureTest(name, version string, fn FeatureTestFn) func() error {
return ft.result
}
}
// A Version in the form Major.Minor.Patch.
type Version [3]uint16
// NewVersion creates a version from a string like "Major.Minor.Patch".
//
// Patch is optional.
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{}, fmt.Errorf("invalid version: %s", ver)
}
return Version{major, minor, patch}, nil
}
func (v Version) String() string {
if v[2] == 0 {
return fmt.Sprintf("v%d.%d", v[0], v[1])
}
return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2])
}
// Less returns true if the version is less than another version.
func (v Version) Less(other Version) bool {
for i, a := range v {
if a == other[i] {
continue
}
return a < other[i]
}
return false
}
// Unspecified returns true if the version is all zero.
func (v Version) Unspecified() bool {
return v[0] == 0 && v[1] == 0 && v[2] == 0
}
+47 -1
View File
@@ -1,6 +1,35 @@
package internal
import "errors"
import (
"bufio"
"compress/gzip"
"errors"
"io"
"os"
)
// NewBufferedSectionReader wraps an io.ReaderAt in an appropriately-sized
// buffered reader. It is a convenience function for reading subsections of
// ELF sections while minimizing the amount of read() syscalls made.
//
// Syscall overhead is non-negligible in continuous integration context
// where ELFs might be accessed over virtual filesystems with poor random
// access performance. Buffering reads makes sense because (sub)sections
// end up being read completely anyway.
//
// Use instead of the r.Seek() + io.LimitReader() pattern.
func NewBufferedSectionReader(ra io.ReaderAt, off, n int64) io.Reader {
// Clamp the size of the buffer to one page to avoid slurping large parts
// of a file into memory. bufio.NewReader uses a hardcoded default buffer
// of 4096. Allow arches with larger pages to allocate more, but don't
// allocate a fixed 4k buffer if we only need to read a small segment.
buf := n
if ps := int64(os.Getpagesize()); n > ps {
buf = ps
}
return bufio.NewReaderSize(io.NewSectionReader(ra, off, n), int(buf))
}
// DiscardZeroes makes sure that all written bytes are zero
// before discarding them.
@@ -14,3 +43,20 @@ func (DiscardZeroes) Write(p []byte) (int, error) {
}
return len(p), nil
}
// ReadAllCompressed decompresses a gzipped file into memory.
func ReadAllCompressed(file string) ([]byte, error) {
fh, err := os.Open(file)
if err != nil {
return nil, err
}
defer fh.Close()
gz, err := gzip.NewReader(fh)
if err != nil {
return nil, err
}
defer gz.Close()
return io.ReadAll(gz)
}
+84
View File
@@ -0,0 +1,84 @@
package internal
import (
"bytes"
"errors"
"go/format"
"go/scanner"
"io"
"strings"
"unicode"
)
// Identifier turns a C style type or field name into an exportable Go equivalent.
func Identifier(str string) string {
prev := rune(-1)
return strings.Map(func(r rune) rune {
// See https://golang.org/ref/spec#Identifiers
switch {
case unicode.IsLetter(r):
if prev == -1 {
r = unicode.ToUpper(r)
}
case r == '_':
switch {
// The previous rune was deleted, or we are at the
// beginning of the string.
case prev == -1:
fallthrough
// The previous rune is a lower case letter or a digit.
case unicode.IsDigit(prev) || (unicode.IsLetter(prev) && unicode.IsLower(prev)):
// delete the current rune, and force the
// next character to be uppercased.
r = -1
}
case unicode.IsDigit(r):
default:
// Delete the current rune. prev is unchanged.
return -1
}
prev = r
return r
}, str)
}
// WriteFormatted outputs a formatted src into out.
//
// If formatting fails it returns an informative error message.
func WriteFormatted(src []byte, out io.Writer) error {
formatted, err := format.Source(src)
if err == nil {
_, err = out.Write(formatted)
return err
}
var el scanner.ErrorList
if !errors.As(err, &el) {
return err
}
var nel scanner.ErrorList
for _, err := range el {
if !err.Pos.IsValid() {
nel = append(nel, err)
continue
}
buf := src[err.Pos.Offset:]
nl := bytes.IndexRune(buf, '\n')
if nl == -1 {
nel = append(nel, err)
continue
}
err.Msg += ": " + string(buf[:nl])
nel = append(nel, err)
}
return nel
}
+66
View File
@@ -0,0 +1,66 @@
package internal
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
func Pin(currentPath, newPath string, fd *sys.FD) error {
const bpfFSType = 0xcafe4a11
if newPath == "" {
return errors.New("given pinning path cannot be empty")
}
if currentPath == newPath {
return nil
}
var statfs unix.Statfs_t
if err := unix.Statfs(filepath.Dir(newPath), &statfs); err != nil {
return err
} else if uint64(statfs.Type) != bpfFSType {
return fmt.Errorf("%s is not on a bpf filesystem", newPath)
}
defer runtime.KeepAlive(fd)
if currentPath == "" {
return sys.ObjPin(&sys.ObjPinAttr{
Pathname: sys.NewStringPointer(newPath),
BpfFd: fd.Uint(),
})
}
// Renameat2 is used instead of os.Rename to disallow the new path replacing
// an existing path.
err := unix.Renameat2(unix.AT_FDCWD, currentPath, unix.AT_FDCWD, newPath, unix.RENAME_NOREPLACE)
if err == nil {
// Object is now moved to the new pinning path.
return nil
}
if !os.IsNotExist(err) {
return fmt.Errorf("unable to move pinned object to new path %v: %w", newPath, err)
}
// Internal state not in sync with the file system so let's fix it.
return sys.ObjPin(&sys.ObjPinAttr{
Pathname: sys.NewStringPointer(newPath),
BpfFd: fd.Uint(),
})
}
func Unpin(pinnedPath string) error {
if pinnedPath == "" {
return nil
}
err := os.Remove(pinnedPath)
if err == nil || os.IsNotExist(err) {
return nil
}
return err
}
-14
View File
@@ -1,14 +0,0 @@
// +build !386,!amd64p32,!arm,!mipsle,!mips64p32le
// +build !armbe,!mips,!mips64p32
package internal
import (
"unsafe"
)
// Pointer wraps an unsafe.Pointer to be 64bit to
// conform to the syscall specification.
type Pointer struct {
ptr unsafe.Pointer
}
+4
View File
@@ -0,0 +1,4 @@
// Package sys contains bindings for the BPF syscall.
package sys
//go:generate go run github.com/cilium/ebpf/internal/cmd/gentypes ../btf/testdata/vmlinux-btf.gz
+96
View File
@@ -0,0 +1,96 @@
package sys
import (
"fmt"
"math"
"os"
"runtime"
"strconv"
"github.com/cilium/ebpf/internal/unix"
)
var ErrClosedFd = unix.EBADF
type FD struct {
raw int
}
func newFD(value int) *FD {
fd := &FD{value}
runtime.SetFinalizer(fd, (*FD).Close)
return fd
}
// NewFD wraps a raw fd with a finalizer.
//
// You must not use the raw fd after calling this function, since the underlying
// file descriptor number may change. This is because the BPF UAPI assumes that
// zero is not a valid fd value.
func NewFD(value int) (*FD, error) {
if value < 0 {
return nil, fmt.Errorf("invalid fd %d", value)
}
fd := newFD(value)
if value != 0 {
return fd, nil
}
dup, err := fd.Dup()
_ = fd.Close()
return dup, err
}
func (fd *FD) String() string {
return strconv.FormatInt(int64(fd.raw), 10)
}
func (fd *FD) Int() int {
return fd.raw
}
func (fd *FD) Uint() uint32 {
if fd.raw < 0 || int64(fd.raw) > math.MaxUint32 {
// Best effort: this is the number most likely to be an invalid file
// descriptor. It is equal to -1 (on two's complement arches).
return math.MaxUint32
}
return uint32(fd.raw)
}
func (fd *FD) Close() error {
if fd.raw < 0 {
return nil
}
value := int(fd.raw)
fd.raw = -1
fd.Forget()
return unix.Close(value)
}
func (fd *FD) Forget() {
runtime.SetFinalizer(fd, nil)
}
func (fd *FD) Dup() (*FD, error) {
if fd.raw < 0 {
return nil, ErrClosedFd
}
// Always require the fd to be larger than zero: the BPF API treats the value
// as "no argument provided".
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 1)
if err != nil {
return nil, fmt.Errorf("can't dup fd: %v", err)
}
return newFD(dup), nil
}
func (fd *FD) File(name string) *os.File {
fd.Forget()
return os.NewFile(uintptr(fd.raw), name)
}
@@ -1,6 +1,10 @@
package internal
package sys
import "unsafe"
import (
"unsafe"
"github.com/cilium/ebpf/internal/unix"
)
// NewPointer creates a 64-bit pointer from an unsafe Pointer.
func NewPointer(ptr unsafe.Pointer) Pointer {
@@ -16,15 +20,19 @@ func NewSlicePointer(buf []byte) Pointer {
return Pointer{ptr: unsafe.Pointer(&buf[0])}
}
// NewSlicePointer creates a 64-bit pointer from a byte slice.
//
// Useful to assign both the pointer and the length in one go.
func NewSlicePointerLen(buf []byte) (Pointer, uint32) {
return NewSlicePointer(buf), uint32(len(buf))
}
// NewStringPointer creates a 64-bit pointer from a string.
func NewStringPointer(str string) Pointer {
if str == "" {
p, err := unix.BytePtrFromString(str)
if err != nil {
return Pointer{}
}
// The kernel expects strings to be zero terminated
buf := make([]byte, len(str)+1)
copy(buf, str)
return Pointer{ptr: unsafe.Pointer(&buf[0])}
return Pointer{ptr: unsafe.Pointer(p)}
}
@@ -1,6 +1,7 @@
//go:build armbe || mips || mips64p32
// +build armbe mips mips64p32
package internal
package sys
import (
"unsafe"
@@ -1,6 +1,7 @@
//go:build 386 || amd64p32 || arm || mipsle || mips64p32le
// +build 386 amd64p32 arm mipsle mips64p32le
package internal
package sys
import (
"unsafe"
+14
View File
@@ -0,0 +1,14 @@
//go:build !386 && !amd64p32 && !arm && !mipsle && !mips64p32le && !armbe && !mips && !mips64p32
// +build !386,!amd64p32,!arm,!mipsle,!mips64p32le,!armbe,!mips,!mips64p32
package sys
import (
"unsafe"
)
// Pointer wraps an unsafe.Pointer to be 64bit to
// conform to the syscall specification.
type Pointer struct {
ptr unsafe.Pointer
}
+123
View File
@@ -0,0 +1,123 @@
package sys
import (
"runtime"
"syscall"
"unsafe"
"github.com/cilium/ebpf/internal/unix"
)
// BPF wraps SYS_BPF.
//
// Any pointers contained in attr must use the Pointer type from this package.
func BPF(cmd Cmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
for {
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
runtime.KeepAlive(attr)
// As of ~4.20 the verifier can be interrupted by a signal,
// and returns EAGAIN in that case.
if errNo == unix.EAGAIN && cmd == BPF_PROG_LOAD {
continue
}
var err error
if errNo != 0 {
err = wrappedErrno{errNo}
}
return r1, err
}
}
// Info is implemented by all structs that can be passed to the ObjInfo syscall.
//
// MapInfo
// ProgInfo
// LinkInfo
// BtfInfo
type Info interface {
info() (unsafe.Pointer, uint32)
}
var _ Info = (*MapInfo)(nil)
func (i *MapInfo) info() (unsafe.Pointer, uint32) {
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
}
var _ Info = (*ProgInfo)(nil)
func (i *ProgInfo) info() (unsafe.Pointer, uint32) {
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
}
var _ Info = (*LinkInfo)(nil)
func (i *LinkInfo) info() (unsafe.Pointer, uint32) {
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
}
var _ Info = (*BtfInfo)(nil)
func (i *BtfInfo) info() (unsafe.Pointer, uint32) {
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
}
// ObjInfo retrieves information about a BPF Fd.
//
// info may be one of MapInfo, ProgInfo, LinkInfo and BtfInfo.
func ObjInfo(fd *FD, info Info) error {
ptr, len := info.info()
err := ObjGetInfoByFd(&ObjGetInfoByFdAttr{
BpfFd: fd.Uint(),
InfoLen: len,
Info: NewPointer(ptr),
})
runtime.KeepAlive(fd)
return err
}
// BPFObjName is a null-terminated string made up of
// 'A-Za-z0-9_' characters.
type ObjName [unix.BPF_OBJ_NAME_LEN]byte
// NewObjName truncates the result if it is too long.
func NewObjName(name string) ObjName {
var result ObjName
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
return result
}
// LinkID uniquely identifies a bpf_link.
type LinkID uint32
// wrappedErrno wraps syscall.Errno to prevent direct comparisons with
// syscall.E* or unix.E* constants.
//
// You should never export an error of this type.
type wrappedErrno struct {
syscall.Errno
}
func (we wrappedErrno) Unwrap() error {
return we.Errno
}
type syscallError struct {
error
errno syscall.Errno
}
func Error(err error, errno syscall.Errno) error {
return &syscallError{err, errno}
}
func (se *syscallError) Is(target error) bool {
return target == se.error
}
func (se *syscallError) Unwrap() error {
return se.errno
}
+954
View File
@@ -0,0 +1,954 @@
// Code generated by internal/cmd/gentypes; DO NOT EDIT.
package sys
import (
"unsafe"
)
type AdjRoomMode int32
const (
BPF_ADJ_ROOM_NET AdjRoomMode = 0
BPF_ADJ_ROOM_MAC AdjRoomMode = 1
)
type AttachType int32
const (
BPF_CGROUP_INET_INGRESS AttachType = 0
BPF_CGROUP_INET_EGRESS AttachType = 1
BPF_CGROUP_INET_SOCK_CREATE AttachType = 2
BPF_CGROUP_SOCK_OPS AttachType = 3
BPF_SK_SKB_STREAM_PARSER AttachType = 4
BPF_SK_SKB_STREAM_VERDICT AttachType = 5
BPF_CGROUP_DEVICE AttachType = 6
BPF_SK_MSG_VERDICT AttachType = 7
BPF_CGROUP_INET4_BIND AttachType = 8
BPF_CGROUP_INET6_BIND AttachType = 9
BPF_CGROUP_INET4_CONNECT AttachType = 10
BPF_CGROUP_INET6_CONNECT AttachType = 11
BPF_CGROUP_INET4_POST_BIND AttachType = 12
BPF_CGROUP_INET6_POST_BIND AttachType = 13
BPF_CGROUP_UDP4_SENDMSG AttachType = 14
BPF_CGROUP_UDP6_SENDMSG AttachType = 15
BPF_LIRC_MODE2 AttachType = 16
BPF_FLOW_DISSECTOR AttachType = 17
BPF_CGROUP_SYSCTL AttachType = 18
BPF_CGROUP_UDP4_RECVMSG AttachType = 19
BPF_CGROUP_UDP6_RECVMSG AttachType = 20
BPF_CGROUP_GETSOCKOPT AttachType = 21
BPF_CGROUP_SETSOCKOPT AttachType = 22
BPF_TRACE_RAW_TP AttachType = 23
BPF_TRACE_FENTRY AttachType = 24
BPF_TRACE_FEXIT AttachType = 25
BPF_MODIFY_RETURN AttachType = 26
BPF_LSM_MAC AttachType = 27
BPF_TRACE_ITER AttachType = 28
BPF_CGROUP_INET4_GETPEERNAME AttachType = 29
BPF_CGROUP_INET6_GETPEERNAME AttachType = 30
BPF_CGROUP_INET4_GETSOCKNAME AttachType = 31
BPF_CGROUP_INET6_GETSOCKNAME AttachType = 32
BPF_XDP_DEVMAP AttachType = 33
BPF_CGROUP_INET_SOCK_RELEASE AttachType = 34
BPF_XDP_CPUMAP AttachType = 35
BPF_SK_LOOKUP AttachType = 36
BPF_XDP AttachType = 37
BPF_SK_SKB_VERDICT AttachType = 38
__MAX_BPF_ATTACH_TYPE AttachType = 39
)
type Cmd int32
const (
BPF_MAP_CREATE Cmd = 0
BPF_MAP_LOOKUP_ELEM Cmd = 1
BPF_MAP_UPDATE_ELEM Cmd = 2
BPF_MAP_DELETE_ELEM Cmd = 3
BPF_MAP_GET_NEXT_KEY Cmd = 4
BPF_PROG_LOAD Cmd = 5
BPF_OBJ_PIN Cmd = 6
BPF_OBJ_GET Cmd = 7
BPF_PROG_ATTACH Cmd = 8
BPF_PROG_DETACH Cmd = 9
BPF_PROG_TEST_RUN Cmd = 10
BPF_PROG_GET_NEXT_ID Cmd = 11
BPF_MAP_GET_NEXT_ID Cmd = 12
BPF_PROG_GET_FD_BY_ID Cmd = 13
BPF_MAP_GET_FD_BY_ID Cmd = 14
BPF_OBJ_GET_INFO_BY_FD Cmd = 15
BPF_PROG_QUERY Cmd = 16
BPF_RAW_TRACEPOINT_OPEN Cmd = 17
BPF_BTF_LOAD Cmd = 18
BPF_BTF_GET_FD_BY_ID Cmd = 19
BPF_TASK_FD_QUERY Cmd = 20
BPF_MAP_LOOKUP_AND_DELETE_ELEM Cmd = 21
BPF_MAP_FREEZE Cmd = 22
BPF_BTF_GET_NEXT_ID Cmd = 23
BPF_MAP_LOOKUP_BATCH Cmd = 24
BPF_MAP_LOOKUP_AND_DELETE_BATCH Cmd = 25
BPF_MAP_UPDATE_BATCH Cmd = 26
BPF_MAP_DELETE_BATCH Cmd = 27
BPF_LINK_CREATE Cmd = 28
BPF_LINK_UPDATE Cmd = 29
BPF_LINK_GET_FD_BY_ID Cmd = 30
BPF_LINK_GET_NEXT_ID Cmd = 31
BPF_ENABLE_STATS Cmd = 32
BPF_ITER_CREATE Cmd = 33
BPF_LINK_DETACH Cmd = 34
BPF_PROG_BIND_MAP Cmd = 35
)
type FunctionId int32
const (
BPF_FUNC_unspec FunctionId = 0
BPF_FUNC_map_lookup_elem FunctionId = 1
BPF_FUNC_map_update_elem FunctionId = 2
BPF_FUNC_map_delete_elem FunctionId = 3
BPF_FUNC_probe_read FunctionId = 4
BPF_FUNC_ktime_get_ns FunctionId = 5
BPF_FUNC_trace_printk FunctionId = 6
BPF_FUNC_get_prandom_u32 FunctionId = 7
BPF_FUNC_get_smp_processor_id FunctionId = 8
BPF_FUNC_skb_store_bytes FunctionId = 9
BPF_FUNC_l3_csum_replace FunctionId = 10
BPF_FUNC_l4_csum_replace FunctionId = 11
BPF_FUNC_tail_call FunctionId = 12
BPF_FUNC_clone_redirect FunctionId = 13
BPF_FUNC_get_current_pid_tgid FunctionId = 14
BPF_FUNC_get_current_uid_gid FunctionId = 15
BPF_FUNC_get_current_comm FunctionId = 16
BPF_FUNC_get_cgroup_classid FunctionId = 17
BPF_FUNC_skb_vlan_push FunctionId = 18
BPF_FUNC_skb_vlan_pop FunctionId = 19
BPF_FUNC_skb_get_tunnel_key FunctionId = 20
BPF_FUNC_skb_set_tunnel_key FunctionId = 21
BPF_FUNC_perf_event_read FunctionId = 22
BPF_FUNC_redirect FunctionId = 23
BPF_FUNC_get_route_realm FunctionId = 24
BPF_FUNC_perf_event_output FunctionId = 25
BPF_FUNC_skb_load_bytes FunctionId = 26
BPF_FUNC_get_stackid FunctionId = 27
BPF_FUNC_csum_diff FunctionId = 28
BPF_FUNC_skb_get_tunnel_opt FunctionId = 29
BPF_FUNC_skb_set_tunnel_opt FunctionId = 30
BPF_FUNC_skb_change_proto FunctionId = 31
BPF_FUNC_skb_change_type FunctionId = 32
BPF_FUNC_skb_under_cgroup FunctionId = 33
BPF_FUNC_get_hash_recalc FunctionId = 34
BPF_FUNC_get_current_task FunctionId = 35
BPF_FUNC_probe_write_user FunctionId = 36
BPF_FUNC_current_task_under_cgroup FunctionId = 37
BPF_FUNC_skb_change_tail FunctionId = 38
BPF_FUNC_skb_pull_data FunctionId = 39
BPF_FUNC_csum_update FunctionId = 40
BPF_FUNC_set_hash_invalid FunctionId = 41
BPF_FUNC_get_numa_node_id FunctionId = 42
BPF_FUNC_skb_change_head FunctionId = 43
BPF_FUNC_xdp_adjust_head FunctionId = 44
BPF_FUNC_probe_read_str FunctionId = 45
BPF_FUNC_get_socket_cookie FunctionId = 46
BPF_FUNC_get_socket_uid FunctionId = 47
BPF_FUNC_set_hash FunctionId = 48
BPF_FUNC_setsockopt FunctionId = 49
BPF_FUNC_skb_adjust_room FunctionId = 50
BPF_FUNC_redirect_map FunctionId = 51
BPF_FUNC_sk_redirect_map FunctionId = 52
BPF_FUNC_sock_map_update FunctionId = 53
BPF_FUNC_xdp_adjust_meta FunctionId = 54
BPF_FUNC_perf_event_read_value FunctionId = 55
BPF_FUNC_perf_prog_read_value FunctionId = 56
BPF_FUNC_getsockopt FunctionId = 57
BPF_FUNC_override_return FunctionId = 58
BPF_FUNC_sock_ops_cb_flags_set FunctionId = 59
BPF_FUNC_msg_redirect_map FunctionId = 60
BPF_FUNC_msg_apply_bytes FunctionId = 61
BPF_FUNC_msg_cork_bytes FunctionId = 62
BPF_FUNC_msg_pull_data FunctionId = 63
BPF_FUNC_bind FunctionId = 64
BPF_FUNC_xdp_adjust_tail FunctionId = 65
BPF_FUNC_skb_get_xfrm_state FunctionId = 66
BPF_FUNC_get_stack FunctionId = 67
BPF_FUNC_skb_load_bytes_relative FunctionId = 68
BPF_FUNC_fib_lookup FunctionId = 69
BPF_FUNC_sock_hash_update FunctionId = 70
BPF_FUNC_msg_redirect_hash FunctionId = 71
BPF_FUNC_sk_redirect_hash FunctionId = 72
BPF_FUNC_lwt_push_encap FunctionId = 73
BPF_FUNC_lwt_seg6_store_bytes FunctionId = 74
BPF_FUNC_lwt_seg6_adjust_srh FunctionId = 75
BPF_FUNC_lwt_seg6_action FunctionId = 76
BPF_FUNC_rc_repeat FunctionId = 77
BPF_FUNC_rc_keydown FunctionId = 78
BPF_FUNC_skb_cgroup_id FunctionId = 79
BPF_FUNC_get_current_cgroup_id FunctionId = 80
BPF_FUNC_get_local_storage FunctionId = 81
BPF_FUNC_sk_select_reuseport FunctionId = 82
BPF_FUNC_skb_ancestor_cgroup_id FunctionId = 83
BPF_FUNC_sk_lookup_tcp FunctionId = 84
BPF_FUNC_sk_lookup_udp FunctionId = 85
BPF_FUNC_sk_release FunctionId = 86
BPF_FUNC_map_push_elem FunctionId = 87
BPF_FUNC_map_pop_elem FunctionId = 88
BPF_FUNC_map_peek_elem FunctionId = 89
BPF_FUNC_msg_push_data FunctionId = 90
BPF_FUNC_msg_pop_data FunctionId = 91
BPF_FUNC_rc_pointer_rel FunctionId = 92
BPF_FUNC_spin_lock FunctionId = 93
BPF_FUNC_spin_unlock FunctionId = 94
BPF_FUNC_sk_fullsock FunctionId = 95
BPF_FUNC_tcp_sock FunctionId = 96
BPF_FUNC_skb_ecn_set_ce FunctionId = 97
BPF_FUNC_get_listener_sock FunctionId = 98
BPF_FUNC_skc_lookup_tcp FunctionId = 99
BPF_FUNC_tcp_check_syncookie FunctionId = 100
BPF_FUNC_sysctl_get_name FunctionId = 101
BPF_FUNC_sysctl_get_current_value FunctionId = 102
BPF_FUNC_sysctl_get_new_value FunctionId = 103
BPF_FUNC_sysctl_set_new_value FunctionId = 104
BPF_FUNC_strtol FunctionId = 105
BPF_FUNC_strtoul FunctionId = 106
BPF_FUNC_sk_storage_get FunctionId = 107
BPF_FUNC_sk_storage_delete FunctionId = 108
BPF_FUNC_send_signal FunctionId = 109
BPF_FUNC_tcp_gen_syncookie FunctionId = 110
BPF_FUNC_skb_output FunctionId = 111
BPF_FUNC_probe_read_user FunctionId = 112
BPF_FUNC_probe_read_kernel FunctionId = 113
BPF_FUNC_probe_read_user_str FunctionId = 114
BPF_FUNC_probe_read_kernel_str FunctionId = 115
BPF_FUNC_tcp_send_ack FunctionId = 116
BPF_FUNC_send_signal_thread FunctionId = 117
BPF_FUNC_jiffies64 FunctionId = 118
BPF_FUNC_read_branch_records FunctionId = 119
BPF_FUNC_get_ns_current_pid_tgid FunctionId = 120
BPF_FUNC_xdp_output FunctionId = 121
BPF_FUNC_get_netns_cookie FunctionId = 122
BPF_FUNC_get_current_ancestor_cgroup_id FunctionId = 123
BPF_FUNC_sk_assign FunctionId = 124
BPF_FUNC_ktime_get_boot_ns FunctionId = 125
BPF_FUNC_seq_printf FunctionId = 126
BPF_FUNC_seq_write FunctionId = 127
BPF_FUNC_sk_cgroup_id FunctionId = 128
BPF_FUNC_sk_ancestor_cgroup_id FunctionId = 129
BPF_FUNC_ringbuf_output FunctionId = 130
BPF_FUNC_ringbuf_reserve FunctionId = 131
BPF_FUNC_ringbuf_submit FunctionId = 132
BPF_FUNC_ringbuf_discard FunctionId = 133
BPF_FUNC_ringbuf_query FunctionId = 134
BPF_FUNC_csum_level FunctionId = 135
BPF_FUNC_skc_to_tcp6_sock FunctionId = 136
BPF_FUNC_skc_to_tcp_sock FunctionId = 137
BPF_FUNC_skc_to_tcp_timewait_sock FunctionId = 138
BPF_FUNC_skc_to_tcp_request_sock FunctionId = 139
BPF_FUNC_skc_to_udp6_sock FunctionId = 140
BPF_FUNC_get_task_stack FunctionId = 141
BPF_FUNC_load_hdr_opt FunctionId = 142
BPF_FUNC_store_hdr_opt FunctionId = 143
BPF_FUNC_reserve_hdr_opt FunctionId = 144
BPF_FUNC_inode_storage_get FunctionId = 145
BPF_FUNC_inode_storage_delete FunctionId = 146
BPF_FUNC_d_path FunctionId = 147
BPF_FUNC_copy_from_user FunctionId = 148
BPF_FUNC_snprintf_btf FunctionId = 149
BPF_FUNC_seq_printf_btf FunctionId = 150
BPF_FUNC_skb_cgroup_classid FunctionId = 151
BPF_FUNC_redirect_neigh FunctionId = 152
BPF_FUNC_per_cpu_ptr FunctionId = 153
BPF_FUNC_this_cpu_ptr FunctionId = 154
BPF_FUNC_redirect_peer FunctionId = 155
BPF_FUNC_task_storage_get FunctionId = 156
BPF_FUNC_task_storage_delete FunctionId = 157
BPF_FUNC_get_current_task_btf FunctionId = 158
BPF_FUNC_bprm_opts_set FunctionId = 159
BPF_FUNC_ktime_get_coarse_ns FunctionId = 160
BPF_FUNC_ima_inode_hash FunctionId = 161
BPF_FUNC_sock_from_file FunctionId = 162
BPF_FUNC_check_mtu FunctionId = 163
BPF_FUNC_for_each_map_elem FunctionId = 164
BPF_FUNC_snprintf FunctionId = 165
__BPF_FUNC_MAX_ID FunctionId = 166
)
type HdrStartOff int32
const (
BPF_HDR_START_MAC HdrStartOff = 0
BPF_HDR_START_NET HdrStartOff = 1
)
type LinkType int32
const (
BPF_LINK_TYPE_UNSPEC LinkType = 0
BPF_LINK_TYPE_RAW_TRACEPOINT LinkType = 1
BPF_LINK_TYPE_TRACING LinkType = 2
BPF_LINK_TYPE_CGROUP LinkType = 3
BPF_LINK_TYPE_ITER LinkType = 4
BPF_LINK_TYPE_NETNS LinkType = 5
BPF_LINK_TYPE_XDP LinkType = 6
MAX_BPF_LINK_TYPE LinkType = 7
)
type MapType int32
const (
BPF_MAP_TYPE_UNSPEC MapType = 0
BPF_MAP_TYPE_HASH MapType = 1
BPF_MAP_TYPE_ARRAY MapType = 2
BPF_MAP_TYPE_PROG_ARRAY MapType = 3
BPF_MAP_TYPE_PERF_EVENT_ARRAY MapType = 4
BPF_MAP_TYPE_PERCPU_HASH MapType = 5
BPF_MAP_TYPE_PERCPU_ARRAY MapType = 6
BPF_MAP_TYPE_STACK_TRACE MapType = 7
BPF_MAP_TYPE_CGROUP_ARRAY MapType = 8
BPF_MAP_TYPE_LRU_HASH MapType = 9
BPF_MAP_TYPE_LRU_PERCPU_HASH MapType = 10
BPF_MAP_TYPE_LPM_TRIE MapType = 11
BPF_MAP_TYPE_ARRAY_OF_MAPS MapType = 12
BPF_MAP_TYPE_HASH_OF_MAPS MapType = 13
BPF_MAP_TYPE_DEVMAP MapType = 14
BPF_MAP_TYPE_SOCKMAP MapType = 15
BPF_MAP_TYPE_CPUMAP MapType = 16
BPF_MAP_TYPE_XSKMAP MapType = 17
BPF_MAP_TYPE_SOCKHASH MapType = 18
BPF_MAP_TYPE_CGROUP_STORAGE MapType = 19
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY MapType = 20
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE MapType = 21
BPF_MAP_TYPE_QUEUE MapType = 22
BPF_MAP_TYPE_STACK MapType = 23
BPF_MAP_TYPE_SK_STORAGE MapType = 24
BPF_MAP_TYPE_DEVMAP_HASH MapType = 25
BPF_MAP_TYPE_STRUCT_OPS MapType = 26
BPF_MAP_TYPE_RINGBUF MapType = 27
BPF_MAP_TYPE_INODE_STORAGE MapType = 28
BPF_MAP_TYPE_TASK_STORAGE MapType = 29
)
type ProgType int32
const (
BPF_PROG_TYPE_UNSPEC ProgType = 0
BPF_PROG_TYPE_SOCKET_FILTER ProgType = 1
BPF_PROG_TYPE_KPROBE ProgType = 2
BPF_PROG_TYPE_SCHED_CLS ProgType = 3
BPF_PROG_TYPE_SCHED_ACT ProgType = 4
BPF_PROG_TYPE_TRACEPOINT ProgType = 5
BPF_PROG_TYPE_XDP ProgType = 6
BPF_PROG_TYPE_PERF_EVENT ProgType = 7
BPF_PROG_TYPE_CGROUP_SKB ProgType = 8
BPF_PROG_TYPE_CGROUP_SOCK ProgType = 9
BPF_PROG_TYPE_LWT_IN ProgType = 10
BPF_PROG_TYPE_LWT_OUT ProgType = 11
BPF_PROG_TYPE_LWT_XMIT ProgType = 12
BPF_PROG_TYPE_SOCK_OPS ProgType = 13
BPF_PROG_TYPE_SK_SKB ProgType = 14
BPF_PROG_TYPE_CGROUP_DEVICE ProgType = 15
BPF_PROG_TYPE_SK_MSG ProgType = 16
BPF_PROG_TYPE_RAW_TRACEPOINT ProgType = 17
BPF_PROG_TYPE_CGROUP_SOCK_ADDR ProgType = 18
BPF_PROG_TYPE_LWT_SEG6LOCAL ProgType = 19
BPF_PROG_TYPE_LIRC_MODE2 ProgType = 20
BPF_PROG_TYPE_SK_REUSEPORT ProgType = 21
BPF_PROG_TYPE_FLOW_DISSECTOR ProgType = 22
BPF_PROG_TYPE_CGROUP_SYSCTL ProgType = 23
BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE ProgType = 24
BPF_PROG_TYPE_CGROUP_SOCKOPT ProgType = 25
BPF_PROG_TYPE_TRACING ProgType = 26
BPF_PROG_TYPE_STRUCT_OPS ProgType = 27
BPF_PROG_TYPE_EXT ProgType = 28
BPF_PROG_TYPE_LSM ProgType = 29
BPF_PROG_TYPE_SK_LOOKUP ProgType = 30
)
type RetCode int32
const (
BPF_OK RetCode = 0
BPF_DROP RetCode = 2
BPF_REDIRECT RetCode = 7
BPF_LWT_REROUTE RetCode = 128
)
type SkAction int32
const (
SK_DROP SkAction = 0
SK_PASS SkAction = 1
)
type StackBuildIdStatus int32
const (
BPF_STACK_BUILD_ID_EMPTY StackBuildIdStatus = 0
BPF_STACK_BUILD_ID_VALID StackBuildIdStatus = 1
BPF_STACK_BUILD_ID_IP StackBuildIdStatus = 2
)
type StatsType int32
const (
BPF_STATS_RUN_TIME StatsType = 0
)
type XdpAction int32
const (
XDP_ABORTED XdpAction = 0
XDP_DROP XdpAction = 1
XDP_PASS XdpAction = 2
XDP_TX XdpAction = 3
XDP_REDIRECT XdpAction = 4
)
type BtfInfo struct {
Btf Pointer
BtfSize uint32
Id uint32
Name Pointer
NameLen uint32
KernelBtf uint32
}
type FuncInfo struct {
InsnOff uint32
TypeId uint32
}
type LineInfo struct {
InsnOff uint32
FileNameOff uint32
LineOff uint32
LineCol uint32
}
type LinkInfo struct {
Type LinkType
Id LinkID
ProgId uint32
_ [4]byte
Extra [16]uint8
}
type MapInfo struct {
Type uint32
Id uint32
KeySize uint32
ValueSize uint32
MaxEntries uint32
MapFlags uint32
Name ObjName
Ifindex uint32
BtfVmlinuxValueTypeId uint32
NetnsDev uint64
NetnsIno uint64
BtfId uint32
BtfKeyTypeId uint32
BtfValueTypeId uint32
_ [4]byte
}
type ProgInfo struct {
Type uint32
Id uint32
Tag [8]uint8
JitedProgLen uint32
XlatedProgLen uint32
JitedProgInsns uint64
XlatedProgInsns Pointer
LoadTime uint64
CreatedByUid uint32
NrMapIds uint32
MapIds Pointer
Name ObjName
Ifindex uint32
_ [4]byte /* unsupported bitfield */
NetnsDev uint64
NetnsIno uint64
NrJitedKsyms uint32
NrJitedFuncLens uint32
JitedKsyms uint64
JitedFuncLens uint64
BtfId uint32
FuncInfoRecSize uint32
FuncInfo uint64
NrFuncInfo uint32
NrLineInfo uint32
LineInfo uint64
JitedLineInfo uint64
NrJitedLineInfo uint32
LineInfoRecSize uint32
JitedLineInfoRecSize uint32
NrProgTags uint32
ProgTags uint64
RunTimeNs uint64
RunCnt uint64
RecursionMisses uint64
}
type BtfGetFdByIdAttr struct{ Id uint32 }
func BtfGetFdById(attr *BtfGetFdByIdAttr) (*FD, error) {
fd, err := BPF(BPF_BTF_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type BtfLoadAttr struct {
Btf Pointer
BtfLogBuf Pointer
BtfSize uint32
BtfLogSize uint32
BtfLogLevel uint32
_ [4]byte
}
func BtfLoad(attr *BtfLoadAttr) (*FD, error) {
fd, err := BPF(BPF_BTF_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type EnableStatsAttr struct{ Type uint32 }
func EnableStats(attr *EnableStatsAttr) (*FD, error) {
fd, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type IterCreateAttr struct {
LinkFd uint32
Flags uint32
}
func IterCreate(attr *IterCreateAttr) (*FD, error) {
fd, err := BPF(BPF_ITER_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type LinkCreateAttr struct {
ProgFd uint32
TargetFd uint32
AttachType AttachType
Flags uint32
TargetBtfId uint32
_ [12]byte
}
func LinkCreate(attr *LinkCreateAttr) (*FD, error) {
fd, err := BPF(BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type LinkCreateIterAttr struct {
ProgFd uint32
TargetFd uint32
AttachType AttachType
Flags uint32
IterInfo Pointer
IterInfoLen uint32
_ [4]byte
}
func LinkCreateIter(attr *LinkCreateIterAttr) (*FD, error) {
fd, err := BPF(BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type LinkUpdateAttr struct {
LinkFd uint32
NewProgFd uint32
Flags uint32
OldProgFd uint32
}
func LinkUpdate(attr *LinkUpdateAttr) error {
_, err := BPF(BPF_LINK_UPDATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapCreateAttr struct {
MapType MapType
KeySize uint32
ValueSize uint32
MaxEntries uint32
MapFlags uint32
InnerMapFd uint32
NumaNode uint32
MapName ObjName
MapIfindex uint32
BtfFd uint32
BtfKeyTypeId uint32
BtfValueTypeId uint32
BtfVmlinuxValueTypeId uint32
}
func MapCreate(attr *MapCreateAttr) (*FD, error) {
fd, err := BPF(BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type MapDeleteBatchAttr struct {
InBatch Pointer
OutBatch Pointer
Keys Pointer
Values Pointer
Count uint32
MapFd uint32
ElemFlags uint64
Flags uint64
}
func MapDeleteBatch(attr *MapDeleteBatchAttr) error {
_, err := BPF(BPF_MAP_DELETE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapDeleteElemAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
Value Pointer
Flags uint64
}
func MapDeleteElem(attr *MapDeleteElemAttr) error {
_, err := BPF(BPF_MAP_DELETE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapFreezeAttr struct{ MapFd uint32 }
func MapFreeze(attr *MapFreezeAttr) error {
_, err := BPF(BPF_MAP_FREEZE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapGetFdByIdAttr struct{ Id uint32 }
func MapGetFdById(attr *MapGetFdByIdAttr) (*FD, error) {
fd, err := BPF(BPF_MAP_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type MapGetNextIdAttr struct {
Id uint32
NextId uint32
}
func MapGetNextId(attr *MapGetNextIdAttr) error {
_, err := BPF(BPF_MAP_GET_NEXT_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapGetNextKeyAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
NextKey Pointer
}
func MapGetNextKey(attr *MapGetNextKeyAttr) error {
_, err := BPF(BPF_MAP_GET_NEXT_KEY, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapLookupAndDeleteBatchAttr struct {
InBatch Pointer
OutBatch Pointer
Keys Pointer
Values Pointer
Count uint32
MapFd uint32
ElemFlags uint64
Flags uint64
}
func MapLookupAndDeleteBatch(attr *MapLookupAndDeleteBatchAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_AND_DELETE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapLookupAndDeleteElemAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
Value Pointer
Flags uint64
}
func MapLookupAndDeleteElem(attr *MapLookupAndDeleteElemAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_AND_DELETE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapLookupBatchAttr struct {
InBatch Pointer
OutBatch Pointer
Keys Pointer
Values Pointer
Count uint32
MapFd uint32
ElemFlags uint64
Flags uint64
}
func MapLookupBatch(attr *MapLookupBatchAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapLookupElemAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
Value Pointer
Flags uint64
}
func MapLookupElem(attr *MapLookupElemAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapUpdateBatchAttr struct {
InBatch Pointer
OutBatch Pointer
Keys Pointer
Values Pointer
Count uint32
MapFd uint32
ElemFlags uint64
Flags uint64
}
func MapUpdateBatch(attr *MapUpdateBatchAttr) error {
_, err := BPF(BPF_MAP_UPDATE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type MapUpdateElemAttr struct {
MapFd uint32
_ [4]byte
Key Pointer
Value Pointer
Flags uint64
}
func MapUpdateElem(attr *MapUpdateElemAttr) error {
_, err := BPF(BPF_MAP_UPDATE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ObjGetAttr struct {
Pathname Pointer
BpfFd uint32
FileFlags uint32
}
func ObjGet(attr *ObjGetAttr) (*FD, error) {
fd, err := BPF(BPF_OBJ_GET, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type ObjGetInfoByFdAttr struct {
BpfFd uint32
InfoLen uint32
Info Pointer
}
func ObjGetInfoByFd(attr *ObjGetInfoByFdAttr) error {
_, err := BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ObjPinAttr struct {
Pathname Pointer
BpfFd uint32
FileFlags uint32
}
func ObjPin(attr *ObjPinAttr) error {
_, err := BPF(BPF_OBJ_PIN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgAttachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
AttachFlags uint32
ReplaceBpfFd uint32
}
func ProgAttach(attr *ProgAttachAttr) error {
_, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgBindMapAttr struct {
ProgFd uint32
MapFd uint32
Flags uint32
}
func ProgBindMap(attr *ProgBindMapAttr) error {
_, err := BPF(BPF_PROG_BIND_MAP, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgDetachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
}
func ProgDetach(attr *ProgDetachAttr) error {
_, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgGetFdByIdAttr struct{ Id uint32 }
func ProgGetFdById(attr *ProgGetFdByIdAttr) (*FD, error) {
fd, err := BPF(BPF_PROG_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type ProgGetNextIdAttr struct {
Id uint32
NextId uint32
}
func ProgGetNextId(attr *ProgGetNextIdAttr) error {
_, err := BPF(BPF_PROG_GET_NEXT_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type ProgLoadAttr struct {
ProgType ProgType
InsnCnt uint32
Insns Pointer
License Pointer
LogLevel uint32
LogSize uint32
LogBuf Pointer
KernVersion uint32
ProgFlags uint32
ProgName ObjName
ProgIfindex uint32
ExpectedAttachType AttachType
ProgBtfFd uint32
FuncInfoRecSize uint32
FuncInfo Pointer
FuncInfoCnt uint32
LineInfoRecSize uint32
LineInfo Pointer
LineInfoCnt uint32
AttachBtfId uint32
AttachProgFd uint32
_ [4]byte
}
func ProgLoad(attr *ProgLoadAttr) (*FD, error) {
fd, err := BPF(BPF_PROG_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type ProgRunAttr struct {
ProgFd uint32
Retval uint32
DataSizeIn uint32
DataSizeOut uint32
DataIn Pointer
DataOut Pointer
Repeat uint32
Duration uint32
CtxSizeIn uint32
CtxSizeOut uint32
CtxIn Pointer
CtxOut Pointer
Flags uint32
Cpu uint32
}
func ProgRun(attr *ProgRunAttr) error {
_, err := BPF(BPF_PROG_TEST_RUN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type RawTracepointOpenAttr struct {
Name Pointer
ProgFd uint32
_ [4]byte
}
func RawTracepointOpen(attr *RawTracepointOpenAttr) (*FD, error) {
fd, err := BPF(BPF_RAW_TRACEPOINT_OPEN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return NewFD(int(fd))
}
type CgroupLinkInfo struct {
CgroupId uint64
AttachType AttachType
_ [4]byte
}
type IterLinkInfo struct {
TargetName Pointer
TargetNameLen uint32
}
type NetNsLinkInfo struct {
NetnsIno uint32
AttachType AttachType
}
type RawTracepointLinkInfo struct {
TpName Pointer
TpNameLen uint32
_ [4]byte
}
type TracingLinkInfo struct {
AttachType AttachType
TargetObjId uint32
TargetBtfId uint32
}
type XDPLinkInfo struct{ Ifindex uint32 }
-179
View File
@@ -1,179 +0,0 @@
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 BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
runtime.KeepAlive(attr)
var err error
if errNo != 0 {
err = errNo
}
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 BPFEnableStatsAttr struct {
StatsType uint32
}
func BPFEnableStats(attr *BPFEnableStatsAttr) (*FD, error) {
ptr, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, fmt.Errorf("enable stats: %w", err)
}
return NewFD(uint32(ptr)), nil
}
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
}
type bpfObjGetInfoByFDAttr struct {
fd uint32
infoLen uint32
info Pointer
}
// BPFObjGetInfoByFD wraps BPF_OBJ_GET_INFO_BY_FD.
//
// Available from 4.13.
func BPFObjGetInfoByFD(fd *FD, info unsafe.Pointer, size uintptr) error {
value, err := fd.Value()
if err != nil {
return err
}
attr := bpfObjGetInfoByFDAttr{
fd: value,
infoLen: uint32(size),
info: NewPointer(info),
}
_, err = BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return fmt.Errorf("fd %v: %w", fd, err)
}
return nil
}
-56
View File
@@ -1,56 +0,0 @@
// 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]]
}
+56 -17
View File
@@ -1,9 +1,9 @@
//go:build linux
// +build linux
package unix
import (
"bytes"
"syscall"
linux "golang.org/x/sys/unix"
@@ -20,16 +20,26 @@ const (
EPERM = linux.EPERM
ESRCH = linux.ESRCH
ENODEV = linux.ENODEV
EBADF = linux.EBADF
E2BIG = linux.E2BIG
EFAULT = linux.EFAULT
// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
ENOTSUPP = syscall.Errno(0x20c)
EBADF = linux.EBADF
BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC
BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE
BPF_F_RDONLY = linux.BPF_F_RDONLY
BPF_F_WRONLY = linux.BPF_F_WRONLY
BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG
BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG
BPF_F_SLEEPABLE = linux.BPF_F_SLEEPABLE
BPF_F_MMAPABLE = linux.BPF_F_MMAPABLE
BPF_F_INNER_MAP = linux.BPF_F_INNER_MAP
BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN
BPF_TAG_SIZE = linux.BPF_TAG_SIZE
BPF_RINGBUF_BUSY_BIT = linux.BPF_RINGBUF_BUSY_BIT
BPF_RINGBUF_DISCARD_BIT = linux.BPF_RINGBUF_DISCARD_BIT
BPF_RINGBUF_HDR_SZ = linux.BPF_RINGBUF_HDR_SZ
SYS_BPF = linux.SYS_BPF
F_DUPFD_CLOEXEC = linux.F_DUPFD_CLOEXEC
EPOLL_CTL_ADD = linux.EPOLL_CTL_ADD
@@ -39,27 +49,36 @@ const (
PROT_READ = linux.PROT_READ
PROT_WRITE = linux.PROT_WRITE
MAP_SHARED = linux.MAP_SHARED
PERF_ATTR_SIZE_VER1 = linux.PERF_ATTR_SIZE_VER1
PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE
PERF_TYPE_TRACEPOINT = linux.PERF_TYPE_TRACEPOINT
PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT
PERF_EVENT_IOC_DISABLE = linux.PERF_EVENT_IOC_DISABLE
PERF_EVENT_IOC_ENABLE = linux.PERF_EVENT_IOC_ENABLE
PERF_EVENT_IOC_SET_BPF = linux.PERF_EVENT_IOC_SET_BPF
PerfBitWatermark = linux.PerfBitWatermark
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
BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME
PERF_RECORD_LOST = linux.PERF_RECORD_LOST
PERF_RECORD_SAMPLE = linux.PERF_RECORD_SAMPLE
AT_FDCWD = linux.AT_FDCWD
RENAME_NOREPLACE = linux.RENAME_NOREPLACE
SO_ATTACH_BPF = linux.SO_ATTACH_BPF
SO_DETACH_BPF = linux.SO_DETACH_BPF
SOL_SOCKET = linux.SOL_SOCKET
)
// Statfs_t is a wrapper
type Statfs_t = linux.Statfs_t
type Stat_t = linux.Stat_t
// Rlimit is a wrapper
type Rlimit = linux.Rlimit
// Setrlimit is a wrapper
func Setrlimit(resource int, rlim *Rlimit) (err error) {
return linux.Setrlimit(resource, rlim)
}
// Syscall is a wrapper
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
return linux.Syscall(trap, a1, a2, a3)
@@ -70,6 +89,11 @@ func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
return linux.FcntlInt(fd, cmd, arg)
}
// IoctlSetInt is a wrapper
func IoctlSetInt(fd int, req uint, value int) error {
return linux.IoctlSetInt(fd, req, value)
}
// Statfs is a wrapper
func Statfs(path string, buf *Statfs_t) (err error) {
return linux.Statfs(path, buf)
@@ -157,14 +181,29 @@ func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return linux.Tgkill(tgid, tid, sig)
}
func KernelRelease() (string, error) {
var uname Utsname
err := Uname(&uname)
if err != nil {
return "", err
}
end := bytes.IndexByte(uname.Release[:], 0)
release := string(uname.Release[:end])
return release, nil
// BytePtrFromString is a wrapper
func BytePtrFromString(s string) (*byte, error) {
return linux.BytePtrFromString(s)
}
// ByteSliceToString is a wrapper
func ByteSliceToString(s []byte) string {
return linux.ByteSliceToString(s)
}
// Renameat2 is a wrapper
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
return linux.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags)
}
func Prlimit(pid, resource int, new, old *Rlimit) error {
return linux.Prlimit(pid, resource, new, old)
}
func Open(path string, mode int, perm uint32) (int, error) {
return linux.Open(path, mode, perm)
}
func Fstat(fd int, stat *Stat_t) error {
return linux.Fstat(fd, stat)
}
+56 -7
View File
@@ -1,3 +1,4 @@
//go:build !linux
// +build !linux
package unix
@@ -21,15 +22,25 @@ const (
ESRCH = syscall.ESRCH
ENODEV = syscall.ENODEV
EBADF = syscall.Errno(0)
E2BIG = syscall.Errno(0)
EFAULT = syscall.EFAULT
// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
ENOTSUPP = syscall.Errno(0x20c)
BPF_F_NO_PREALLOC = 0
BPF_F_NUMA_NODE = 0
BPF_F_RDONLY = 0
BPF_F_WRONLY = 0
BPF_F_RDONLY_PROG = 0
BPF_F_WRONLY_PROG = 0
BPF_F_SLEEPABLE = 0
BPF_F_MMAPABLE = 0
BPF_F_INNER_MAP = 0
BPF_OBJ_NAME_LEN = 0x10
BPF_TAG_SIZE = 0x8
BPF_RINGBUF_BUSY_BIT = 0
BPF_RINGBUF_DISCARD_BIT = 0
BPF_RINGBUF_HDR_SZ = 0
SYS_BPF = 321
F_DUPFD_CLOEXEC = 0x406
EPOLLIN = 0x1
@@ -40,14 +51,26 @@ const (
PROT_READ = 0x1
PROT_WRITE = 0x2
MAP_SHARED = 0x1
PERF_ATTR_SIZE_VER1 = 0
PERF_TYPE_SOFTWARE = 0x1
PERF_TYPE_TRACEPOINT = 0
PERF_COUNT_SW_BPF_OUTPUT = 0xa
PERF_EVENT_IOC_DISABLE = 0
PERF_EVENT_IOC_ENABLE = 0
PERF_EVENT_IOC_SET_BPF = 0
PerfBitWatermark = 0x4000
PERF_SAMPLE_RAW = 0x400
PERF_FLAG_FD_CLOEXEC = 0x8
RLIM_INFINITY = 0x7fffffffffffffff
RLIMIT_MEMLOCK = 8
BPF_STATS_RUN_TIME = 0
PERF_RECORD_LOST = 2
PERF_RECORD_SAMPLE = 9
AT_FDCWD = -0x2
RENAME_NOREPLACE = 0x1
SO_ATTACH_BPF = 0x32
SO_DETACH_BPF = 0x1b
SOL_SOCKET = 0x1
)
// Statfs_t is a wrapper
@@ -66,17 +89,14 @@ type Statfs_t struct {
Spare [4]int64
}
type Stat_t struct{}
// Rlimit is a wrapper
type Rlimit struct {
Cur uint64
Max uint64
}
// Setrlimit is a wrapper
func Setrlimit(resource int, rlim *Rlimit) (err error) {
return errNonLinux
}
// Syscall is a wrapper
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
return 0, 0, syscall.Errno(1)
@@ -87,6 +107,11 @@ func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
return -1, errNonLinux
}
// IoctlSetInt is a wrapper
func IoctlSetInt(fd int, req uint, value int) error {
return errNonLinux
}
// Statfs is a wrapper
func Statfs(path string, buf *Statfs_t) error {
return errNonLinux
@@ -201,6 +226,7 @@ func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int
// Utsname is a wrapper
type Utsname struct {
Release [65]byte
Version [65]byte
}
// Uname is a wrapper
@@ -223,6 +249,29 @@ func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return errNonLinux
}
func KernelRelease() (string, error) {
return "", errNonLinux
// BytePtrFromString is a wrapper
func BytePtrFromString(s string) (*byte, error) {
return nil, errNonLinux
}
// ByteSliceToString is a wrapper
func ByteSliceToString(s []byte) string {
return ""
}
// Renameat2 is a wrapper
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
return errNonLinux
}
func Prlimit(pid, resource int, new, old *Rlimit) error {
return errNonLinux
}
func Open(path string, mode int, perm uint32) (int, error) {
return -1, errNonLinux
}
func Fstat(fd int, stat *Stat_t) error {
return errNonLinux
}
+145
View File
@@ -0,0 +1,145 @@
package internal
import (
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"github.com/cilium/ebpf/internal/unix"
)
var (
errAuxvNoVDSO = errors.New("no vdso address found in auxv")
)
// vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
// linked into the current process image.
func vdsoVersion() (uint32, error) {
// Read data from the auxiliary vector, which is normally passed directly
// to the process. Go does not expose that data, so we must read it from procfs.
// https://man7.org/linux/man-pages/man3/getauxval.3.html
av, err := os.Open("/proc/self/auxv")
if err != nil {
return 0, fmt.Errorf("opening auxv: %w", err)
}
defer av.Close()
vdsoAddr, err := vdsoMemoryAddress(av)
if err != nil {
return 0, fmt.Errorf("finding vDSO memory address: %w", err)
}
// Use /proc/self/mem rather than unsafe.Pointer tricks.
mem, err := os.Open("/proc/self/mem")
if err != nil {
return 0, fmt.Errorf("opening mem: %w", err)
}
defer mem.Close()
// Open ELF at provided memory address, as offset into /proc/self/mem.
c, err := vdsoLinuxVersionCode(io.NewSectionReader(mem, int64(vdsoAddr), math.MaxInt64))
if err != nil {
return 0, fmt.Errorf("reading linux version code: %w", err)
}
return c, nil
}
// vdsoMemoryAddress returns the memory address of the vDSO library
// linked into the current process image. r is an io.Reader into an auxv blob.
func vdsoMemoryAddress(r io.Reader) (uint64, error) {
const (
_AT_NULL = 0 // End of vector
_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
)
// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`,
// the address of a page containing the virtual Dynamic Shared Object (vDSO).
aux := struct{ Tag, Val uint64 }{}
for {
if err := binary.Read(r, NativeEndian, &aux); err != nil {
return 0, fmt.Errorf("reading auxv entry: %w", err)
}
switch aux.Tag {
case _AT_SYSINFO_EHDR:
if aux.Val != 0 {
return aux.Val, nil
}
return 0, fmt.Errorf("invalid vDSO address in auxv")
// _AT_NULL is always the last tag/val pair in the aux vector
// and can be treated like EOF.
case _AT_NULL:
return 0, errAuxvNoVDSO
}
}
}
// format described at https://www.man7.org/linux/man-pages/man5/elf.5.html in section 'Notes (Nhdr)'
type elfNoteHeader struct {
NameSize int32
DescSize int32
Type int32
}
// vdsoLinuxVersionCode returns the LINUX_VERSION_CODE embedded in
// the ELF notes section of the binary provided by the reader.
func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
hdr, err := NewSafeELFFile(r)
if err != nil {
return 0, fmt.Errorf("reading vDSO ELF: %w", err)
}
sec := hdr.SectionByType(elf.SHT_NOTE)
if sec == nil {
return 0, fmt.Errorf("no note section found in vDSO ELF")
}
sr := sec.Open()
var n elfNoteHeader
// Read notes until we find one named 'Linux'.
for {
if err := binary.Read(sr, hdr.ByteOrder, &n); err != nil {
if errors.Is(err, io.EOF) {
return 0, fmt.Errorf("no Linux note in ELF")
}
return 0, fmt.Errorf("reading note header: %w", err)
}
// If a note name is defined, it follows the note header.
var name string
if n.NameSize > 0 {
// Read the note name, aligned to 4 bytes.
buf := make([]byte, Align(int(n.NameSize), 4))
if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil {
return 0, fmt.Errorf("reading note name: %w", err)
}
// Read nul-terminated string.
name = unix.ByteSliceToString(buf[:n.NameSize])
}
// If a note descriptor is defined, it follows the name.
// It is possible for a note to have a descriptor but not a name.
if n.DescSize > 0 {
// LINUX_VERSION_CODE is a uint32 value.
if name == "Linux" && n.DescSize == 4 && n.Type == 0 {
var version uint32
if err := binary.Read(sr, hdr.ByteOrder, &version); err != nil {
return 0, fmt.Errorf("reading note descriptor: %w", err)
}
return version, nil
}
// Discard the note descriptor if it exists but we're not interested in it.
if _, err := io.CopyN(io.Discard, sr, int64(Align(int(n.DescSize), 4))); err != nil {
return 0, err
}
}
}
}
+107
View File
@@ -0,0 +1,107 @@
package internal
import (
"fmt"
"sync"
)
const (
// Version constant used in ELF binaries indicating that the loader needs to
// substitute the eBPF program's version with the value of the kernel's
// KERNEL_VERSION compile-time macro. Used for compatibility with BCC, gobpf
// and RedSift.
MagicKernelVersion = 0xFFFFFFFE
)
var (
kernelVersion = struct {
once sync.Once
version Version
err error
}{}
)
// A Version in the form Major.Minor.Patch.
type Version [3]uint16
// NewVersion creates a version from a string like "Major.Minor.Patch".
//
// Patch is optional.
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{}, fmt.Errorf("invalid version: %s", ver)
}
return Version{major, minor, patch}, nil
}
// NewVersionFromCode creates a version from a LINUX_VERSION_CODE.
func NewVersionFromCode(code uint32) Version {
return Version{
uint16(uint8(code >> 16)),
uint16(uint8(code >> 8)),
uint16(uint8(code)),
}
}
func (v Version) String() string {
if v[2] == 0 {
return fmt.Sprintf("v%d.%d", v[0], v[1])
}
return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2])
}
// Less returns true if the version is less than another version.
func (v Version) Less(other Version) bool {
for i, a := range v {
if a == other[i] {
continue
}
return a < other[i]
}
return false
}
// Unspecified returns true if the version is all zero.
func (v Version) Unspecified() bool {
return v[0] == 0 && v[1] == 0 && v[2] == 0
}
// Kernel implements the kernel's KERNEL_VERSION macro from linux/version.h.
// It represents the kernel version and patch level as a single value.
func (v Version) Kernel() uint32 {
// Kernels 4.4 and 4.9 have their SUBLEVEL clamped to 255 to avoid
// overflowing into PATCHLEVEL.
// See kernel commit 9b82f13e7ef3 ("kbuild: clamp SUBLEVEL to 255").
s := v[2]
if s > 255 {
s = 255
}
// Truncate members to uint8 to prevent them from spilling over into
// each other when overflowing 8 bits.
return uint32(uint8(v[0]))<<16 | uint32(uint8(v[1]))<<8 | uint32(uint8(s))
}
// KernelVersion returns the version of the currently running kernel.
func KernelVersion() (Version, error) {
kernelVersion.once.Do(func() {
kernelVersion.version, kernelVersion.err = detectKernelVersion()
})
if kernelVersion.err != nil {
return Version{}, kernelVersion.err
}
return kernelVersion.version, nil
}
// detectKernelVersion returns the version of the running kernel.
func detectKernelVersion() (Version, error) {
vc, err := vdsoVersion()
if err != nil {
return Version{}, err
}
return NewVersionFromCode(vc), nil
}