containerd/vendor/github.com/cilium/ebpf/internal/btf/btf.go
Sebastiaan van Stijn 004ff8b995
vendor: cilium/ebbf 4032b1d8aae306b7bb94a2a11002932caf88c644
full diff: 60c3aa43f4...4032b1d8aa

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-05-09 15:22:12 +02:00

531 lines
12 KiB
Go

package btf
import (
"bytes"
"debug/elf"
"encoding/binary"
"io"
"io/ioutil"
"math"
"reflect"
"unsafe"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
)
const btfMagic = 0xeB9F
// Spec represents decoded BTF.
type Spec struct {
rawTypes []rawType
strings stringTable
types map[string][]Type
funcInfos map[string]extInfo
lineInfos map[string]extInfo
}
type btfHeader struct {
Magic uint16
Version uint8
Flags uint8
HdrLen uint32
TypeOff uint32
TypeLen uint32
StringOff uint32
StringLen uint32
}
// LoadSpecFromReader reads BTF sections from an ELF.
//
// Returns a nil Spec and no error if no BTF was present.
func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
file, err := elf.NewFile(rd)
if err != nil {
return nil, err
}
defer file.Close()
var (
btfSection *elf.Section
btfExtSection *elf.Section
)
for _, sec := range file.Sections {
switch sec.Name {
case ".BTF":
btfSection = sec
case ".BTF.ext":
btfExtSection = sec
}
}
if btfSection == nil {
return nil, nil
}
spec, err := parseBTF(btfSection.Open(), file.ByteOrder)
if err != nil {
return nil, err
}
if btfExtSection != nil {
spec.funcInfos, spec.lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
if err != nil {
return nil, errors.Wrap(err, "can't read ext info")
}
}
return spec, nil
}
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) (*Spec, error) {
rawBTF, err := ioutil.ReadAll(btf)
if err != nil {
return nil, errors.Wrap(err, "can't read BTF")
}
rd := bytes.NewReader(rawBTF)
var header btfHeader
if err := binary.Read(rd, bo, &header); err != nil {
return nil, errors.Wrap(err, "can't read header")
}
if header.Magic != btfMagic {
return nil, errors.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, errors.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, errors.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, errors.New("header is too short")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
return nil, errors.Wrap(err, "header padding")
}
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
return nil, errors.Wrap(err, "can't seek to start of string section")
}
strings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
if err != nil {
return nil, errors.Wrap(err, "can't read type names")
}
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil {
return nil, errors.Wrap(err, "can't seek to start of type section")
}
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
if err != nil {
return nil, errors.Wrap(err, "can't read types")
}
types, err := inflateRawTypes(rawTypes, strings)
if err != nil {
return nil, err
}
return &Spec{
rawTypes: rawTypes,
types: types,
strings: strings,
funcInfos: make(map[string]extInfo),
lineInfos: make(map[string]extInfo),
}, nil
}
func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
var (
buf bytes.Buffer
header = new(btfHeader)
headerLen = binary.Size(header)
)
// Reserve space for the header. We have to write it last since
// we don't know the size of the type section yet.
_, _ = buf.Write(make([]byte, headerLen))
// Write type section, just after the header.
for _, typ := range s.rawTypes {
if typ.Kind() == kindDatasec {
// Datasec requires patching with information from the ELF
// file. We don't support this at the moment, so patch
// out any Datasec by turning it into a void*.
typ = rawType{}
typ.SetKind(kindPointer)
}
if err := typ.Marshal(&buf, bo); err != nil {
return nil, errors.Wrap(err, "can't marshal BTF")
}
}
typeLen := uint32(buf.Len() - headerLen)
// Write string section after type section.
_, _ = buf.Write(s.strings)
// Fill out the header, and write it out.
header = &btfHeader{
Magic: btfMagic,
Version: 1,
Flags: 0,
HdrLen: uint32(headerLen),
TypeOff: 0,
TypeLen: typeLen,
StringOff: typeLen,
StringLen: uint32(len(s.strings)),
}
raw := buf.Bytes()
err := binary.Write(sliceWriter(raw[:headerLen]), bo, header)
if err != nil {
return nil, errors.Wrap(err, "can't write header")
}
return raw, nil
}
type sliceWriter []byte
func (sw sliceWriter) Write(p []byte) (int, error) {
if len(p) != len(sw) {
return 0, errors.New("size doesn't match")
}
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.
//
// Returns an error if there is no BTF.
func (s *Spec) Program(name string, length uint64) (*Program, error) {
if length == 0 {
return nil, errors.New("length musn't be zero")
}
funcInfos, funcOK := s.funcInfos[name]
lineInfos, lineOK := s.lineInfos[name]
if !funcOK && !lineOK {
return nil, errors.Errorf("no BTF for program %s", name)
}
return &Program{s, length, funcInfos, lineInfos}, nil
}
// Map finds the BTF for a map.
//
// Returns an error if there is no BTF for the given name.
func (s *Spec) Map(name string) (*Map, error) {
var mapVar Var
if err := s.FindType(name, &mapVar); err != nil {
return nil, err
}
mapStruct, ok := mapVar.Type.(*Struct)
if !ok {
return nil, errors.Errorf("expected struct, have %s", mapVar.Type)
}
var key, value Type
for _, member := range mapStruct.Members {
switch member.Name {
case "key":
key = member.Type
case "value":
value = member.Type
}
}
if key == nil {
return nil, errors.Errorf("map %s: missing 'key' in type", name)
}
if value == nil {
return nil, errors.Errorf("map %s: missing 'value' in type", name)
}
return &Map{mapStruct, s, key, value}, nil
}
var errNotFound = errors.New("not found")
// FindType searches for a type with a specific name.
//
// hint determines the type of the returned Type.
//
// Returns an error if there is no or multiple matches.
func (s *Spec) FindType(name string, typ Type) error {
var (
wanted = reflect.TypeOf(typ)
candidate Type
)
for _, typ := range s.types[name] {
if reflect.TypeOf(typ) != wanted {
continue
}
if candidate != nil {
return errors.Errorf("type %s: multiple candidates for %T", name, typ)
}
candidate = typ
}
if candidate == nil {
return errors.WithMessagef(errNotFound, "type %s", name)
}
value := reflect.Indirect(reflect.ValueOf(copyType(candidate)))
reflect.Indirect(reflect.ValueOf(typ)).Set(value)
return nil
}
// Handle is a reference to BTF loaded into the kernel.
type Handle struct {
fd *internal.FD
}
// NewHandle loads BTF into the kernel.
//
// Returns an error if BTF is not supported, which can
// be checked by IsNotSupported.
func NewHandle(spec *Spec) (*Handle, error) {
if err := haveBTF(); err != nil {
return nil, err
}
btf, err := spec.marshal(internal.NativeEndian)
if err != nil {
return nil, errors.Wrap(err, "can't marshal BTF")
}
if uint64(len(btf)) > math.MaxUint32 {
return nil, errors.New("BTF exceeds the maximum size")
}
attr := &bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
}
fd, err := bpfLoadBTF(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)
return nil, internal.ErrorWithLog(err, logBuf, logErr)
}
return &Handle{fd}, nil
}
// Close destroys the handle.
//
// Subsequent calls to FD will return an invalid value.
func (h *Handle) Close() error {
return h.fd.Close()
}
// 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)
}
// Map is the BTF for a map.
type Map struct {
definition *Struct
spec *Spec
key, value Type
}
// 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
}
// MapType should be a method on Map, but is a free function
// to hide it from users of the ebpf package.
func MapType(m *Map) *Struct {
return m.definition
}
// MapKey should be a method on Map, but is a free function
// to hide it from users of the ebpf package.
func MapKey(m *Map) Type {
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
}
// Program is the BTF information for a stream of instructions.
type Program struct {
spec *Spec
length uint64
funcInfos, lineInfos extInfo
}
// 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
}
// ProgramAppend the information from other to the Program.
//
// 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 errors.Wrap(err, "func infos")
}
lineInfos, err := s.lineInfos.append(other.lineInfos, s.length)
if err != nil {
return errors.Wrap(err, "line infos")
}
s.length += other.length
s.funcInfos = funcInfos
s.lineInfos = lineInfos
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
}
// IsNotSupported returns true if the error indicates that the kernel
// doesn't support BTF.
func IsNotSupported(err error) bool {
ufe, ok := errors.Cause(err).(*internal.UnsupportedFeatureError)
return ok && ufe.Name == "BTF"
}
type bpfLoadBTFAttr struct {
btf internal.Pointer
logBuf internal.Pointer
btfSize uint32
btfLogSize uint32
btfLogLevel uint32
}
func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
const _BTFLoad = 18
fd, err := internal.BPF(_BTFLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return internal.NewFD(uint32(fd)), nil
}
func minimalBTF(bo binary.ByteOrder) []byte {
const minHeaderLength = 24
var (
types struct {
Integer btfType
Var btfType
btfVar struct{ Linkage uint32 }
}
typLen = uint32(binary.Size(&types))
strings = []byte{0, 'a', 0}
header = btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: minHeaderLength,
TypeOff: 0,
TypeLen: typLen,
StringOff: typLen,
StringLen: uint32(len(strings)),
}
)
// We use a BTF_KIND_VAR here, to make sure that
// the kernel understands BTF at least as well as we
// do. BTF_KIND_VAR was introduced ~5.1.
types.Integer.SetKind(kindPointer)
types.Var.NameOff = 1
types.Var.SetKind(kindVar)
types.Var.SizeType = 1
buf := new(bytes.Buffer)
_ = binary.Write(buf, bo, &header)
_ = binary.Write(buf, bo, &types)
buf.Write(strings)
return buf.Bytes()
}
var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool {
btf := minimalBTF(internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
})
if err == nil {
fd.Close()
}
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return errors.Cause(err) != unix.EINVAL
})