Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

btf: export API to create BTF from scratch #859

Merged
merged 4 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
233 changes: 105 additions & 128 deletions btf/btf.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package btf

import (
"bufio"
"bytes"
"debug/elf"
"encoding/binary"
"errors"
Expand Down Expand Up @@ -31,22 +30,27 @@ var (
// ID represents the unique ID of a BTF object.
type ID = sys.BTFID

// Spec represents decoded BTF.
// Spec allows querying a set of Types and loading the set into the
// kernel.
type Spec struct {
// Data from .BTF.
strings *stringTable

// All types contained by the spec, not including types from the base in
// case the spec was parsed from split BTF.
types []Type

// Type IDs indexed by type.
typeIDs map[Type]TypeID

// The last allocated type ID.
lastTypeID TypeID

// Types indexed by essential name.
// Includes all struct flavors and types with the same name.
namedTypes map[essentialName][]Type

// String table from ELF, may be nil.
strings *stringTable

// Byte order of the ELF we decoded the spec from, may be nil.
byteOrder binary.ByteOrder
}

Expand Down Expand Up @@ -76,6 +80,18 @@ func (h *btfHeader) stringStart() int64 {
return int64(h.HdrLen + h.StringOff)
}

// NewSpec creates a Spec containing only Void.
func NewSpec() *Spec {
return &Spec{
[]Type{(*Void)(nil)},
map[Type]TypeID{(*Void)(nil): 0},
0,
make(map[essentialName][]Type),
nil,
nil,
}
}

// LoadSpec opens file and calls LoadSpecFromReader on it.
func LoadSpec(file string) (*Spec, error) {
fh, err := os.Open(file)
Expand Down Expand Up @@ -214,7 +230,6 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {

func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder,
baseTypes types, baseStrings *stringTable) (*Spec, error) {

rawTypes, rawStrings, err := parseBTF(btf, bo, baseStrings)
if err != nil {
return nil, err
Expand All @@ -225,18 +240,19 @@ func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder,
return nil, err
}

typeIDs, typesByName := indexTypes(types, TypeID(len(baseTypes)))
typeIDs, typesByName, lastTypeID := indexTypes(types, TypeID(len(baseTypes)))

return &Spec{
namedTypes: typesByName,
typeIDs: typeIDs,
types: types,
lastTypeID: lastTypeID,
strings: rawStrings,
byteOrder: bo,
}, nil
}

func indexTypes(types []Type, typeIDOffset TypeID) (map[Type]TypeID, map[essentialName][]Type) {
func indexTypes(types []Type, typeIDOffset TypeID) (map[Type]TypeID, map[essentialName][]Type, TypeID) {
namedTypes := 0
for _, typ := range types {
if typ.TypeName() != "" {
Expand All @@ -250,14 +266,16 @@ func indexTypes(types []Type, typeIDOffset TypeID) (map[Type]TypeID, map[essenti
typeIDs := make(map[Type]TypeID, len(types))
typesByName := make(map[essentialName][]Type, namedTypes)

var lastTypeID TypeID
for i, typ := range types {
if name := newEssentialName(typ.TypeName()); name != "" {
typesByName[name] = append(typesByName[name], typ)
}
typeIDs[typ] = TypeID(i) + typeIDOffset
lastTypeID = TypeID(i) + typeIDOffset
typeIDs[typ] = lastTypeID
}

return typeIDs, typesByName
return typeIDs, typesByName, lastTypeID
}

// LoadKernelSpec returns the current kernel's BTF information.
Expand Down Expand Up @@ -469,15 +487,15 @@ func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symb
// Copy creates a copy of Spec.
func (s *Spec) Copy() *Spec {
types := copyTypes(s.types, nil)

typeIDs, typesByName := indexTypes(types, s.firstTypeID())
typeIDs, typesByName, lastTypeID := indexTypes(types, s.firstTypeID())

// NB: Other parts of spec are not copied since they are immutable.
return &Spec{
s.strings,
types,
typeIDs,
lastTypeID,
typesByName,
s.strings,
s.byteOrder,
}
}
Expand All @@ -492,6 +510,36 @@ func (sw sliceWriter) Write(p []byte) (int, error) {
return copy(sw, p), nil
}

// Add a Type to the Spec, making it queryable via [TypeByName], etc.
//
// Adding the identical Type multiple times is valid and will return a stable ID.
//
// See [Type] for details on identity.
func (s *Spec) Add(typ Type) (TypeID, error) {
ti-mo marked this conversation as resolved.
Show resolved Hide resolved
if typ == nil {
return 0, fmt.Errorf("can't add nil Type")
}

if id, err := s.TypeID(typ); err == nil {
return id, nil
}

id := s.lastTypeID + 1
if id < s.lastTypeID {
return 0, fmt.Errorf("type ID overflow")
}

s.typeIDs[typ] = id
s.types = append(s.types, typ)
s.lastTypeID = id

if name := newEssentialName(typ.TypeName()); name != "" {
s.namedTypes[name] = append(s.namedTypes[name], typ)
}

return id, nil
}

// TypeByID returns the BTF Type with the given type ID.
//
// Returns an error wrapping ErrNotFound if a Type with the given ID
Expand Down Expand Up @@ -638,6 +686,10 @@ func (s *Spec) firstTypeID() TypeID {
// Types from base are used to resolve references in the split BTF.
// The returned Spec only contains types from the split BTF, not from the base.
func LoadSplitSpecFromReader(r io.ReaderAt, base *Spec) (*Spec, error) {
if base.strings == nil {
return nil, fmt.Errorf("parse split BTF: base must be loaded from an ELF")
}

lmb marked this conversation as resolved.
Show resolved Hide resolved
return loadRawSpec(r, internal.NativeEndian, base.types, base.strings)
}

Expand Down Expand Up @@ -665,55 +717,15 @@ func (iter *TypesIterator) Next() bool {
return true
}

func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
const minHeaderLength = 24

typesLen := uint32(binary.Size(types))
header := btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: minHeaderLength,
TypeOff: 0,
TypeLen: typesLen,
StringOff: typesLen,
StringLen: uint32(len(strings)),
}

buf := new(bytes.Buffer)
_ = binary.Write(buf, bo, &header)
_ = binary.Write(buf, bo, types)
buf.Write(strings)

return buf.Bytes()
}

// haveBTF attempts to load a BTF blob containing an Int. It should pass on any
// kernel that supports BPF_BTF_LOAD.
var haveBTF = internal.NewFeatureTest("BTF", "4.18", func() error {
var (
types struct {
Integer btfType
btfInt
}
strings = []byte{0}
)
types.Integer.SetKind(kindInt) // 0-length anonymous integer

btf := marshalBTF(&types, strings, internal.NativeEndian)

fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
})
// 0-length anonymous integer
err := probeBTF(&Int{})
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
if err != nil {
return err
}

fd.Close()
return nil
return err
})

// haveMapBTF attempts to load a minimal BTF blob containing a Var. It is
Expand All @@ -724,37 +736,18 @@ var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", "5.2", func()
return err
}

var (
types struct {
Integer btfType
Var btfType
btfVariable
}
strings = []byte{0, 'a', 0}
)

types.Integer.SetKind(kindPointer)
types.Var.NameOff = 1
types.Var.SetKind(kindVar)
types.Var.SizeType = 1

btf := marshalBTF(&types, strings, internal.NativeEndian)
v := &Var{
Name: "a",
Type: &Pointer{(*Void)(nil)},
}

fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
})
err := probeBTF(v)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
// Treat both EINVAL and EPERM as not supported: creating the map may still
// succeed without Btf* attrs.
return internal.ErrNotSupported
}
if err != nil {
return err
}

fd.Close()
return nil
return err
})

// haveProgBTF attempts to load a BTF blob containing a Func and FuncProto. It
Expand All @@ -765,68 +758,52 @@ var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", "5.0",
return err
}

var (
types struct {
FuncProto btfType
Func btfType
}
strings = []byte{0, 'a', 0}
)

types.FuncProto.SetKind(kindFuncProto)
types.Func.SetKind(kindFunc)
types.Func.SizeType = 1 // aka FuncProto
types.Func.NameOff = 1

btf := marshalBTF(&types, strings, internal.NativeEndian)
fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
}

fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
})
err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
if err != nil {
return err
}

fd.Close()
return nil
return err
})

var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", "5.6", func() error {
if err := haveProgBTF(); err != nil {
return err
}

var (
types struct {
FuncProto btfType
Func btfType
}
strings = []byte{0, 'a', 0}
)

types.FuncProto.SetKind(kindFuncProto)
types.Func.SetKind(kindFunc)
types.Func.SizeType = 1 // aka FuncProto
types.Func.NameOff = 1
types.Func.SetLinkage(GlobalFunc)

btf := marshalBTF(&types, strings, internal.NativeEndian)
fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
Linkage: GlobalFunc,
}

fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
})
err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
if err != nil {
return err
})

func probeBTF(typ Type) error {
buf := getBuffer()
defer putBuffer(buf)

if err := marshalTypes(buf, []Type{&Void{}, typ}, nil, nil); err != nil {
return err
}

fd.Close()
return nil
})
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(buf.Bytes()),
BtfSize: uint32(buf.Len()),
})

if err == nil {
fd.Close()
}

return err
}