whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,869 @@
package btf
import (
"bufio"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"reflect"
"sync"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
const btfMagic = 0xeB9F
// Errors returned by BTF functions.
var (
ErrNotSupported = internal.ErrNotSupported
ErrNotFound = errors.New("not found")
ErrNoExtendedInfo = errors.New("no extended info")
ErrMultipleMatches = errors.New("multiple matching types")
)
// ID represents the unique ID of a BTF object.
type ID = sys.BTFID
// Spec allows querying a set of Types and loading the set into the
// kernel.
type Spec struct {
// 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 ID of the first type in types.
firstTypeID 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
}
var btfHeaderLen = binary.Size(&btfHeader{})
type btfHeader struct {
Magic uint16
Version uint8
Flags uint8
HdrLen uint32
TypeOff uint32
TypeLen uint32
StringOff uint32
StringLen uint32
}
// 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)
}
// 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)
if err != nil {
return nil, err
}
defer fh.Close()
return LoadSpecFromReader(fh)
}
// LoadSpecFromReader reads from an ELF or a raw BTF blob.
//
// Returns ErrNotFound if reading from an ELF which contains no BTF. ExtInfos
// may be nil.
func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
file, err := internal.NewSafeELFFile(rd)
if err != nil {
if bo := guessRawBTFByteOrder(rd); bo != nil {
return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil)
}
return nil, err
}
return loadSpecFromELF(file)
}
// LoadSpecAndExtInfosFromReader reads from an ELF.
//
// ExtInfos may be nil if the ELF doesn't contain section metadata.
// Returns ErrNotFound if the ELF contains no BTF.
func LoadSpecAndExtInfosFromReader(rd io.ReaderAt) (*Spec, *ExtInfos, error) {
file, err := internal.NewSafeELFFile(rd)
if err != nil {
return nil, nil, err
}
spec, err := loadSpecFromELF(file)
if err != nil {
return nil, nil, err
}
extInfos, err := loadExtInfosFromELF(file, spec)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, nil, err
}
return spec, extInfos, nil
}
// symbolOffsets extracts all symbols offsets from an ELF and indexes them by
// section and variable name.
//
// References to variables in BTF data sections carry unsigned 32-bit offsets.
// Some ELF symbols (e.g. in vmlinux) may point to virtual memory that is well
// beyond this range. Since these symbols cannot be described by BTF info,
// ignore them here.
func symbolOffsets(file *internal.SafeELFFile) (map[symbol]uint32, error) {
symbols, err := file.Symbols()
if err != nil {
return nil, fmt.Errorf("can't read symbols: %v", err)
}
offsets := make(map[symbol]uint32)
for _, sym := range symbols {
if idx := sym.Section; idx >= elf.SHN_LORESERVE && idx <= elf.SHN_HIRESERVE {
// Ignore things like SHN_ABS
continue
}
if sym.Value > math.MaxUint32 {
// VarSecinfo offset is u32, cannot reference symbols in higher regions.
continue
}
if int(sym.Section) >= len(file.Sections) {
return nil, fmt.Errorf("symbol %s: invalid section %d", sym.Name, sym.Section)
}
secName := file.Sections[sym.Section].Name
offsets[symbol{secName, sym.Name}] = uint32(sym.Value)
}
return offsets, nil
}
func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
var (
btfSection *elf.Section
sectionSizes = make(map[string]uint32)
)
for _, sec := range file.Sections {
switch sec.Name {
case ".BTF":
btfSection = sec
default:
if sec.Type != elf.SHT_PROGBITS && sec.Type != elf.SHT_NOBITS {
break
}
if sec.Size > math.MaxUint32 {
return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
}
sectionSizes[sec.Name] = uint32(sec.Size)
}
}
if btfSection == nil {
return nil, fmt.Errorf("btf: %w", ErrNotFound)
}
offsets, err := symbolOffsets(file)
if err != nil {
return nil, err
}
if btfSection.ReaderAt == nil {
return nil, fmt.Errorf("compressed BTF is not supported")
}
spec, err := loadRawSpec(btfSection.ReaderAt, file.ByteOrder, nil)
if err != nil {
return nil, err
}
err = fixupDatasec(spec.types, sectionSizes, offsets)
if err != nil {
return nil, err
}
return spec, nil
}
func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, base *Spec) (*Spec, error) {
var (
baseStrings *stringTable
firstTypeID TypeID
err error
)
if base != nil {
if base.firstTypeID != 0 {
return nil, fmt.Errorf("can't use split BTF as base")
}
if base.strings == nil {
return nil, fmt.Errorf("parse split BTF: base must be loaded from an ELF")
}
baseStrings = base.strings
firstTypeID, err = base.nextTypeID()
if err != nil {
return nil, err
}
}
rawTypes, rawStrings, err := parseBTF(btf, bo, baseStrings)
if err != nil {
return nil, err
}
types, err := inflateRawTypes(rawTypes, rawStrings, base)
if err != nil {
return nil, err
}
typeIDs, typesByName := indexTypes(types, firstTypeID)
return &Spec{
namedTypes: typesByName,
typeIDs: typeIDs,
types: types,
firstTypeID: firstTypeID,
strings: rawStrings,
byteOrder: bo,
}, nil
}
func indexTypes(types []Type, firstTypeID TypeID) (map[Type]TypeID, map[essentialName][]Type) {
namedTypes := 0
for _, typ := range types {
if typ.TypeName() != "" {
// Do a pre-pass to figure out how big types by name has to be.
// Most types have unique names, so it's OK to ignore essentialName
// here.
namedTypes++
}
}
typeIDs := make(map[Type]TypeID, len(types))
typesByName := make(map[essentialName][]Type, namedTypes)
for i, typ := range types {
if name := newEssentialName(typ.TypeName()); name != "" {
typesByName[name] = append(typesByName[name], typ)
}
typeIDs[typ] = firstTypeID + TypeID(i)
}
return typeIDs, typesByName
}
// LoadKernelSpec returns the current kernel's BTF information.
//
// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file system
// for vmlinux ELFs. Returns an error wrapping ErrNotSupported if BTF is not enabled.
func LoadKernelSpec() (*Spec, error) {
spec, _, err := kernelSpec()
if err != nil {
return nil, err
}
return spec.Copy(), nil
}
var kernelBTF struct {
sync.RWMutex
spec *Spec
// True if the spec was read from an ELF instead of raw BTF in /sys.
fallback bool
}
// FlushKernelSpec removes any cached kernel type information.
func FlushKernelSpec() {
kernelBTF.Lock()
defer kernelBTF.Unlock()
kernelBTF.spec, kernelBTF.fallback = nil, false
}
func kernelSpec() (*Spec, bool, error) {
kernelBTF.RLock()
spec, fallback := kernelBTF.spec, kernelBTF.fallback
kernelBTF.RUnlock()
if spec == nil {
kernelBTF.Lock()
defer kernelBTF.Unlock()
spec, fallback = kernelBTF.spec, kernelBTF.fallback
}
if spec != nil {
return spec, fallback, nil
}
spec, fallback, err := loadKernelSpec()
if err != nil {
return nil, false, err
}
kernelBTF.spec, kernelBTF.fallback = spec, fallback
return spec, fallback, nil
}
func loadKernelSpec() (_ *Spec, fallback bool, _ error) {
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if err == nil {
defer fh.Close()
spec, err := loadRawSpec(fh, internal.NativeEndian, nil)
return spec, false, err
}
file, err := findVMLinux()
if err != nil {
return nil, false, err
}
defer file.Close()
spec, err := loadSpecFromELF(file)
return spec, true, err
}
// findVMLinux scans multiple well-known paths for vmlinux kernel images.
func findVMLinux() (*internal.SafeELFFile, error) {
release, err := internal.KernelRelease()
if err != nil {
return nil, err
}
// use same list of locations as libbpf
// https://github.com/libbpf/libbpf/blob/9a3a42608dbe3731256a5682a125ac1e23bced8f/src/btf.c#L3114-L3122
locations := []string{
"/boot/vmlinux-%s",
"/lib/modules/%s/vmlinux-%[1]s",
"/lib/modules/%s/build/vmlinux",
"/usr/lib/modules/%s/kernel/vmlinux",
"/usr/lib/debug/boot/vmlinux-%s",
"/usr/lib/debug/boot/vmlinux-%s.debug",
"/usr/lib/debug/lib/modules/%s/vmlinux",
}
for _, loc := range locations {
file, err := internal.OpenSafeELFFile(fmt.Sprintf(loc, release))
if errors.Is(err, os.ErrNotExist) {
continue
}
return file, err
}
return nil, fmt.Errorf("no BTF found for kernel version %s: %w", release, internal.ErrNotSupported)
}
// 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(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)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, errors.New("header length shorter than btfHeader size")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil {
return nil, fmt.Errorf("header padding: %v", err)
}
return &header, nil
}
func guessRawBTFByteOrder(r io.ReaderAt) binary.ByteOrder {
buf := new(bufio.Reader)
for _, bo := range []binary.ByteOrder{
binary.LittleEndian,
binary.BigEndian,
} {
buf.Reset(io.NewSectionReader(r, 0, math.MaxInt64))
if _, err := parseBTFHeader(buf, bo); err == nil {
return bo
}
}
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.ReaderAt, bo binary.ByteOrder, baseStrings *stringTable) ([]rawType, *stringTable, error) {
buf := internal.NewBufferedSectionReader(btf, 0, math.MaxInt64)
header, err := parseBTFHeader(buf, bo)
if err != nil {
return nil, nil, fmt.Errorf("parsing .BTF header: %v", err)
}
rawStrings, err := readStringTable(io.NewSectionReader(btf, header.stringStart(), int64(header.StringLen)),
baseStrings)
if err != nil {
return nil, nil, fmt.Errorf("can't read type names: %w", err)
}
buf.Reset(io.NewSectionReader(btf, header.typeStart(), int64(header.TypeLen)))
rawTypes, err := readTypes(buf, bo, header.TypeLen)
if err != nil {
return nil, nil, fmt.Errorf("can't read types: %w", err)
}
return rawTypes, rawStrings, nil
}
type symbol struct {
section string
name string
}
// fixupDatasec attempts to patch up missing info in Datasecs and its members by
// supplementing them with information from the ELF headers and symbol table.
func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symbol]uint32) error {
for _, typ := range types {
ds, ok := typ.(*Datasec)
if !ok {
continue
}
name := ds.Name
// Some Datasecs are virtual and don't have corresponding ELF sections.
switch name {
case ".ksyms":
// .ksyms describes forward declarations of kfunc signatures.
// Nothing to fix up, all sizes and offsets are 0.
for _, vsi := range ds.Vars {
_, ok := vsi.Type.(*Func)
if !ok {
// Only Funcs are supported in the .ksyms Datasec.
return fmt.Errorf("data section %s: expected *btf.Func, not %T: %w", name, vsi.Type, ErrNotSupported)
}
}
continue
case ".kconfig":
// .kconfig has a size of 0 and has all members' offsets set to 0.
// Fix up all offsets and set the Datasec's size.
if err := fixupDatasecLayout(ds); err != nil {
return err
}
// Fix up extern to global linkage to avoid a BTF verifier error.
for _, vsi := range ds.Vars {
vsi.Type.(*Var).Linkage = GlobalVar
}
continue
}
if ds.Size != 0 {
continue
}
ds.Size, ok = sectionSizes[name]
if !ok {
return fmt.Errorf("data section %s: missing size", name)
}
for i := range ds.Vars {
symName := ds.Vars[i].Type.TypeName()
ds.Vars[i].Offset, ok = offsets[symbol{name, symName}]
if !ok {
return fmt.Errorf("data section %s: missing offset for symbol %s", name, symName)
}
}
}
return nil
}
// fixupDatasecLayout populates ds.Vars[].Offset according to var sizes and
// alignment. Calculate and set ds.Size.
func fixupDatasecLayout(ds *Datasec) error {
var off uint32
for i, vsi := range ds.Vars {
v, ok := vsi.Type.(*Var)
if !ok {
return fmt.Errorf("member %d: unsupported type %T", i, vsi.Type)
}
size, err := Sizeof(v.Type)
if err != nil {
return fmt.Errorf("variable %s: getting size: %w", v.Name, err)
}
align, err := alignof(v.Type)
if err != nil {
return fmt.Errorf("variable %s: getting alignment: %w", v.Name, err)
}
// Align the current member based on the offset of the end of the previous
// member and the alignment of the current member.
off = internal.Align(off, uint32(align))
ds.Vars[i].Offset = off
off += uint32(size)
}
ds.Size = off
return nil
}
// Copy creates a copy of Spec.
func (s *Spec) Copy() *Spec {
types := copyTypes(s.types, nil)
typeIDs, typesByName := indexTypes(types, s.firstTypeID)
// NB: Other parts of spec are not copied since they are immutable.
return &Spec{
types,
typeIDs,
s.firstTypeID,
typesByName,
s.strings,
s.byteOrder,
}
}
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
}
// nextTypeID returns the next unallocated type ID or an error if there are no
// more type IDs.
func (s *Spec) nextTypeID() (TypeID, error) {
id := s.firstTypeID + TypeID(len(s.types))
if id < s.firstTypeID {
return 0, fmt.Errorf("no more type IDs")
}
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
// does not exist in the Spec.
func (s *Spec) TypeByID(id TypeID) (Type, error) {
if id < s.firstTypeID {
return nil, fmt.Errorf("look up type with ID %d (first ID is %d): %w", id, s.firstTypeID, ErrNotFound)
}
index := int(id - s.firstTypeID)
if index >= len(s.types) {
return nil, fmt.Errorf("look up type with ID %d: %w", id, ErrNotFound)
}
return s.types[index], nil
}
// TypeID returns the ID for a given Type.
//
// Returns an error wrapping ErrNoFound if the type isn't part of the Spec.
func (s *Spec) TypeID(typ Type) (TypeID, error) {
if _, ok := typ.(*Void); ok {
// Equality is weird for void, since it is a zero sized type.
return 0, nil
}
id, ok := s.typeIDs[typ]
if !ok {
return 0, fmt.Errorf("no ID for type %s: %w", typ, ErrNotFound)
}
return 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
}
// AnyTypeByName returns a Type with the given name.
//
// Returns an error if multiple types of that name exist.
func (s *Spec) AnyTypeByName(name string) (Type, error) {
types, err := s.AnyTypesByName(name)
if err != nil {
return nil, err
}
if len(types) > 1 {
return nil, fmt.Errorf("found multiple types: %v", types)
}
return types[0], 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 the Spec.
// Returns an error wrapping ErrMultipleTypes if multiple candidates are found.
func (s *Spec) TypeByName(name string, typ interface{}) error {
typeInterface := reflect.TypeOf((*Type)(nil)).Elem()
// typ may be **T or *Type
typValue := reflect.ValueOf(typ)
if typValue.Kind() != reflect.Ptr {
return fmt.Errorf("%T is not a pointer", typ)
}
typPtr := typValue.Elem()
if !typPtr.CanSet() {
return fmt.Errorf("%T cannot be set", typ)
}
wanted := typPtr.Type()
if wanted == typeInterface {
// This is *Type. Unwrap the value's type.
wanted = typPtr.Elem().Type()
}
if !wanted.AssignableTo(typeInterface) {
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
}
if candidate != nil {
return fmt.Errorf("type %s(%T): %w", name, typ, ErrMultipleMatches)
}
candidate = typ
}
if candidate == nil {
return fmt.Errorf("%s %s: %w", wanted, name, ErrNotFound)
}
typPtr.Set(reflect.ValueOf(candidate))
return nil
}
// LoadSplitSpecFromReader loads split BTF from a reader.
//
// 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) {
return loadRawSpec(r, internal.NativeEndian, base)
}
// TypesIterator iterates over types of a given spec.
type TypesIterator struct {
types []Type
index int
// The last visited type in the spec.
Type Type
}
// Iterate returns the types iterator.
func (s *Spec) Iterate() *TypesIterator {
// We share the backing array of types with the Spec. This is safe since
// we don't allow deletion or shuffling of types.
return &TypesIterator{types: s.types, index: 0}
}
// Next returns true as long as there are any remaining types.
func (iter *TypesIterator) Next() bool {
if len(iter.types) <= iter.index {
return false
}
iter.Type = iter.types[iter.index]
iter.index++
return true
}
// 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 {
// 0-length anonymous integer
err := probeBTF(&Int{})
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
})
// haveMapBTF attempts to load a minimal BTF blob containing a Var. It is
// used as a proxy for .bss, .data and .rodata map support, which generally
// come with a Var and Datasec. These were introduced in Linux 5.2.
var haveMapBTF = internal.NewFeatureTest("Map BTF (Var/Datasec)", "5.2", func() error {
if err := haveBTF(); err != nil {
return err
}
v := &Var{
Name: "a",
Type: &Pointer{(*Void)(nil)},
}
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
}
return err
})
// haveProgBTF attempts to load a BTF blob containing a Func and FuncProto. It
// is used as a proxy for ext_info (func_info) support, which depends on
// Func(Proto) by definition.
var haveProgBTF = internal.NewFeatureTest("Program BTF (func/line_info)", "5.0", func() error {
if err := haveBTF(); err != nil {
return err
}
fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
}
err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
return internal.ErrNotSupported
}
return err
})
var haveFuncLinkage = internal.NewFeatureTest("BTF func linkage", "5.6", func() error {
if err := haveProgBTF(); err != nil {
return err
}
fn := &Func{
Name: "a",
Type: &FuncProto{Return: (*Void)(nil)},
Linkage: GlobalFunc,
}
err := probeBTF(fn)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
return err
})
func probeBTF(typ Type) error {
b, err := NewBuilder([]Type{typ})
if err != nil {
return err
}
buf, err := b.Marshal(nil, nil)
if err != nil {
return err
}
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(buf),
BtfSize: uint32(len(buf)),
})
if err == nil {
fd.Close()
}
return err
}
@@ -0,0 +1,525 @@
package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"testing"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
qt "github.com/frankban/quicktest"
)
func vmlinuxSpec(tb testing.TB) *Spec {
tb.Helper()
// /sys/kernel/btf was introduced in 341dfcf8d78e ("btf: expose BTF info
// through sysfs"), which shipped in Linux 5.4.
testutils.SkipOnOldKernel(tb, "5.4", "vmlinux BTF in sysfs")
spec, fallback, err := kernelSpec()
if err != nil {
tb.Fatal(err)
}
if fallback {
tb.Fatal("/sys/kernel/btf/vmlinux is not available")
}
return spec.Copy()
}
type specAndRawBTF struct {
raw []byte
spec *Spec
}
var vmlinuxTestdata = internal.Memoize(func() (specAndRawBTF, error) {
b, err := internal.ReadAllCompressed("testdata/vmlinux.btf.gz")
if err != nil {
return specAndRawBTF{}, err
}
spec, err := loadRawSpec(bytes.NewReader(b), binary.LittleEndian, nil)
if err != nil {
return specAndRawBTF{}, err
}
return specAndRawBTF{b, spec}, nil
})
func vmlinuxTestdataReader(tb testing.TB) *bytes.Reader {
tb.Helper()
td, err := vmlinuxTestdata()
if err != nil {
tb.Fatal(err)
}
return bytes.NewReader(td.raw)
}
func vmlinuxTestdataSpec(tb testing.TB) *Spec {
tb.Helper()
td, err := vmlinuxTestdata()
if err != nil {
tb.Fatal(err)
}
return td.spec.Copy()
}
func parseELFBTF(tb testing.TB, file string) *Spec {
tb.Helper()
spec, err := LoadSpec(file)
if err != nil {
tb.Fatal("Can't load BTF:", err)
}
return spec
}
func TestAnyTypesByName(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
spec := parseELFBTF(t, file)
types, err := spec.AnyTypesByName("ambiguous")
if err != nil {
t.Fatal(err)
}
if len(types) != 1 {
t.Fatalf("expected to receive exactly 1 types from querying ambiguous type, got: %v", types)
}
types, err = spec.AnyTypesByName("ambiguous___flavour")
if err != nil {
t.Fatal(err)
}
if len(types) != 1 {
t.Fatalf("expected to receive exactly 1 type from querying ambiguous flavour, got: %v", types)
}
})
}
func TestTypeByNameAmbiguous(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
spec := parseELFBTF(t, file)
var typ *Struct
if err := spec.TypeByName("ambiguous", &typ); err != nil {
t.Fatal(err)
}
if name := typ.TypeName(); name != "ambiguous" {
t.Fatal("expected type name 'ambiguous', got:", name)
}
if err := spec.TypeByName("ambiguous___flavour", &typ); err != nil {
t.Fatal(err)
}
if name := typ.TypeName(); name != "ambiguous___flavour" {
t.Fatal("expected type name 'ambiguous___flavour', got:", name)
}
})
}
func TestTypeByName(t *testing.T) {
spec := vmlinuxTestdataSpec(t)
for _, typ := range []interface{}{
nil,
Struct{},
&Struct{},
[]Struct{},
&[]Struct{},
map[int]Struct{},
&map[int]Struct{},
int(0),
new(int),
} {
t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) {
// spec.TypeByName MUST fail if typ is a nil btf.Type.
if err := spec.TypeByName("iphdr", typ); err == nil {
t.Fatalf("TypeByName does not fail with type %T", typ)
}
})
}
// spec.TypeByName MUST return the same address for multiple calls with the same type name.
var iphdr1, iphdr2 *Struct
if err := spec.TypeByName("iphdr", &iphdr1); err != nil {
t.Fatal(err)
}
if err := spec.TypeByName("iphdr", &iphdr2); err != nil {
t.Fatal(err)
}
if iphdr1 != iphdr2 {
t.Fatal("multiple TypeByName calls for `iphdr` name do not return the same addresses")
}
// It's valid to pass a *Type to TypeByName.
typ := Type(iphdr2)
if err := spec.TypeByName("iphdr", &typ); err != nil {
t.Fatal("Can't look up using *Type:", err)
}
// Excerpt from linux/ip.h, https://elixir.bootlin.com/linux/latest/A/ident/iphdr
//
// struct iphdr {
// #if defined(__LITTLE_ENDIAN_BITFIELD)
// __u8 ihl:4, version:4;
// #elif defined (__BIG_ENDIAN_BITFIELD)
// __u8 version:4, ihl:4;
// #else
// ...
// }
//
// The BTF we test against is for little endian.
m := iphdr1.Members[1]
if m.Name != "version" {
t.Fatal("Expected version as the second member, got", m.Name)
}
td, ok := m.Type.(*Typedef)
if !ok {
t.Fatalf("version member of iphdr should be a __u8 typedef: actual: %T", m.Type)
}
u8, ok := td.Type.(*Int)
if !ok {
t.Fatalf("__u8 typedef should point to an Int type: actual: %T", td.Type)
}
if m.BitfieldSize != 4 {
t.Fatalf("incorrect bitfield size: expected: 4 actual: %d", m.BitfieldSize)
}
if u8.Encoding != 0 {
t.Fatalf("incorrect encoding of an __u8 int: expected: 0 actual: %x", u8.Encoding)
}
if m.Offset != 4 {
t.Fatalf("incorrect bitfield offset: expected: 4 actual: %d", m.Offset)
}
}
func BenchmarkParseVmlinux(b *testing.B) {
rd := vmlinuxTestdataReader(b)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
if _, err := rd.Seek(0, io.SeekStart); err != nil {
b.Fatal(err)
}
if _, err := loadRawSpec(rd, binary.LittleEndian, nil); err != nil {
b.Fatal("Can't load BTF:", err)
}
}
}
func TestParseCurrentKernelBTF(t *testing.T) {
spec := vmlinuxSpec(t)
if len(spec.namedTypes) == 0 {
t.Fatal("Empty kernel BTF")
}
totalBytes := 0
distinct := 0
seen := make(map[string]bool)
for _, str := range spec.strings.strings {
totalBytes += len(str)
if !seen[str] {
distinct++
seen[str] = true
}
}
t.Logf("%d strings total, %d distinct", len(spec.strings.strings), distinct)
t.Logf("Average string size: %.0f", float64(totalBytes)/float64(len(spec.strings.strings)))
}
func TestFindVMLinux(t *testing.T) {
file, err := findVMLinux()
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Can't find vmlinux:", err)
}
defer file.Close()
spec, err := loadSpecFromELF(file)
if err != nil {
t.Fatal("Can't load BTF:", err)
}
if len(spec.namedTypes) == 0 {
t.Fatal("Empty kernel BTF")
}
}
func TestLoadSpecFromElf(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "../testdata/loader-e*.elf"), func(t *testing.T, file string) {
spec := parseELFBTF(t, file)
vt, err := spec.TypeByID(0)
if err != nil {
t.Error("Can't retrieve void type by ID:", err)
}
if _, ok := vt.(*Void); !ok {
t.Errorf("Expected Void for type id 0, but got: %T", vt)
}
var bpfMapDef *Struct
if err := spec.TypeByName("bpf_map_def", &bpfMapDef); err != nil {
t.Error("Can't find bpf_map_def:", err)
}
var tmp *Void
if err := spec.TypeByName("totally_bogus_type", &tmp); !errors.Is(err, ErrNotFound) {
t.Error("TypeByName doesn't return ErrNotFound:", err)
}
var fn *Func
if err := spec.TypeByName("global_fn", &fn); err != nil {
t.Error("Can't find global_fn():", err)
} else {
if fn.Linkage != GlobalFunc {
t.Error("Expected global linkage:", fn)
}
}
var v *Var
if err := spec.TypeByName("key3", &v); err != nil {
t.Error("Cant find key3:", err)
} else {
if v.Linkage != GlobalVar {
t.Error("Expected global linkage:", v)
}
}
})
}
func TestVerifierError(t *testing.T) {
_, err := NewHandle(&Builder{})
testutils.SkipIfNotSupported(t, err)
var ve *internal.VerifierError
if !errors.As(err, &ve) {
t.Fatalf("expected a VerifierError, got: %v", err)
}
if ve.Truncated {
t.Fatalf("expected non-truncated verifier log: %v", err)
}
}
func TestLoadKernelSpec(t *testing.T) {
if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) {
t.Skip("/sys/kernel/btf/vmlinux not present")
}
_, err := LoadKernelSpec()
if err != nil {
t.Fatal("Can't load kernel spec:", err)
}
}
func TestGuessBTFByteOrder(t *testing.T) {
bo := guessRawBTFByteOrder(vmlinuxTestdataReader(t))
if bo != binary.LittleEndian {
t.Fatalf("Guessed %s instead of %s", bo, binary.LittleEndian)
}
}
func TestSpecCopy(t *testing.T) {
spec := parseELFBTF(t, "../testdata/loader-el.elf")
if len(spec.types) < 1 {
t.Fatal("Not enough types")
}
cpy := spec.Copy()
for i := range cpy.types {
if _, ok := cpy.types[i].(*Void); ok {
// Since Void is an empty struct, a Type interface value containing
// &Void{} stores (*Void, nil). Since interface equality first compares
// the type and then the concrete value, Void is always equal.
continue
}
if cpy.types[i] == spec.types[i] {
t.Fatalf("Type at index %d is not a copy: %T == %T", i, cpy.types[i], spec.types[i])
}
}
}
func TestSpecTypeByID(t *testing.T) {
_, err := newSpec().TypeByID(0)
qt.Assert(t, err, qt.IsNil)
_, err = newSpec().TypeByID(1)
qt.Assert(t, err, qt.ErrorIs, ErrNotFound)
}
func TestHaveBTF(t *testing.T) {
testutils.CheckFeatureTest(t, haveBTF)
}
func TestHaveMapBTF(t *testing.T) {
testutils.CheckFeatureTest(t, haveMapBTF)
}
func TestHaveProgBTF(t *testing.T) {
testutils.CheckFeatureTest(t, haveProgBTF)
}
func TestHaveFuncLinkage(t *testing.T) {
testutils.CheckFeatureTest(t, haveFuncLinkage)
}
func ExampleSpec_TypeByName() {
// Acquire a Spec via one of its constructors.
spec := new(Spec)
// Declare a variable of the desired type
var foo *Struct
if err := spec.TypeByName("foo", &foo); err != nil {
// There is no struct with name foo, or there
// are multiple possibilities.
}
// We've found struct foo
fmt.Println(foo.Name)
}
func TestTypesIterator(t *testing.T) {
types := []Type{(*Void)(nil), &Int{Size: 4}, &Int{Size: 2}}
b, err := NewBuilder(types[1:])
if err != nil {
t.Fatal(err)
}
raw, err := b.Marshal(nil, nil)
if err != nil {
t.Fatal(err)
}
spec, err := LoadSpecFromReader(bytes.NewReader(raw))
if err != nil {
t.Fatal(err)
}
iter := spec.Iterate()
for i, typ := range types {
if !iter.Next() {
t.Fatal("Iterator ended early at item", i)
}
qt.Assert(t, iter.Type, qt.DeepEquals, typ)
}
if iter.Next() {
t.Fatalf("Iterator yielded too many items: %p (%[1]T)", iter.Type)
}
}
func TestLoadSplitSpecFromReader(t *testing.T) {
spec := vmlinuxTestdataSpec(t)
f, err := os.Open("testdata/btf_testmod.btf")
if err != nil {
t.Fatal(err)
}
defer f.Close()
splitSpec, err := LoadSplitSpecFromReader(f, spec)
if err != nil {
t.Fatal(err)
}
typ, err := splitSpec.AnyTypeByName("bpf_testmod_init")
if err != nil {
t.Fatal(err)
}
typeID, err := splitSpec.TypeID(typ)
if err != nil {
t.Fatal(err)
}
typeByID, err := splitSpec.TypeByID(typeID)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, typeByID, qt.Equals, typ)
fnType := typ.(*Func)
fnProto := fnType.Type.(*FuncProto)
// 'int' is defined in the base BTF...
intType, err := spec.AnyTypeByName("int")
if err != nil {
t.Fatal(err)
}
// ... but not in the split BTF
_, err = splitSpec.AnyTypeByName("int")
if err == nil {
t.Fatal("'int' is not supposed to be found in the split BTF")
}
if fnProto.Return != intType {
t.Fatalf("Return type of 'bpf_testmod_init()' (%s) does not match 'int' type (%s)",
fnProto.Return, intType)
}
// Check that copied split-BTF's spec has correct type indexing
splitSpecCopy := splitSpec.Copy()
copyType, err := splitSpecCopy.AnyTypeByName("bpf_testmod_init")
if err != nil {
t.Fatal(err)
}
copyTypeID, err := splitSpecCopy.TypeID(copyType)
if err != nil {
t.Fatal(err)
}
if copyTypeID != typeID {
t.Fatalf("'bpf_testmod_init` type ID (%d) does not match copied spec's (%d)",
typeID, copyTypeID)
}
}
func TestFixupDatasecLayout(t *testing.T) {
ds := &Datasec{
Size: 0, // Populated by fixup.
Vars: []VarSecinfo{
{Type: &Var{Type: &Int{Size: 4}}},
{Type: &Var{Type: &Int{Size: 1}}},
{Type: &Var{Type: &Int{Size: 1}}},
{Type: &Var{Type: &Int{Size: 2}}},
{Type: &Var{Type: &Int{Size: 16}}},
{Type: &Var{Type: &Int{Size: 8}}},
},
}
qt.Assert(t, fixupDatasecLayout(ds), qt.IsNil)
qt.Assert(t, ds.Size, qt.Equals, uint32(40))
qt.Assert(t, ds.Vars[0].Offset, qt.Equals, uint32(0))
qt.Assert(t, ds.Vars[1].Offset, qt.Equals, uint32(4))
qt.Assert(t, ds.Vars[2].Offset, qt.Equals, uint32(5))
qt.Assert(t, ds.Vars[3].Offset, qt.Equals, uint32(6))
qt.Assert(t, ds.Vars[4].Offset, qt.Equals, uint32(16))
qt.Assert(t, ds.Vars[5].Offset, qt.Equals, uint32(32))
}
func BenchmarkSpecCopy(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
b.ResetTimer()
for i := 0; i < b.N; i++ {
spec.Copy()
}
}
@@ -0,0 +1,371 @@
package btf
import (
"encoding/binary"
"fmt"
"io"
"unsafe"
)
//go:generate stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind
// btfKind describes a Type.
type btfKind uint8
// Equivalents of the BTF_KIND_* constants.
const (
kindUnknown btfKind = iota // Unknown
kindInt // Int
kindPointer // Pointer
kindArray // Array
kindStruct // Struct
kindUnion // Union
kindEnum // Enum
kindForward // Forward
kindTypedef // Typedef
kindVolatile // Volatile
kindConst // Const
kindRestrict // Restrict
// Added ~4.20
kindFunc // Func
kindFuncProto // FuncProto
// Added ~5.1
kindVar // Var
kindDatasec // Datasec
// Added ~5.13
kindFloat // Float
// Added 5.16
kindDeclTag // DeclTag
kindTypeTag // TypeTag
// Added 6.0
kindEnum64 // Enum64
)
// 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 (
StaticVar VarLinkage = iota // static
GlobalVar // global
ExternVar // extern
)
const (
btfTypeKindShift = 24
btfTypeKindLen = 5
btfTypeVlenShift = 0
btfTypeVlenMask = 16
btfTypeKindFlagShift = 31
btfTypeKindFlagMask = 1
)
var btfTypeLen = binary.Size(btfType{})
// btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst.
type btfType struct {
NameOff uint32
/* "info" bits arrangement
* bits 0-15: vlen (e.g. # of struct's members), linkage
* bits 16-23: 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
*/
Info uint32
/* "size" is used by INT, ENUM, STRUCT and UNION.
* "size" tells the size of the type it is describing.
*
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
* FUNC and FUNC_PROTO.
* "type" is a type_id referring to another type.
*/
SizeType uint32
}
func mask(len uint32) uint32 {
return (1 << len) - 1
}
func readBits(value, len, shift uint32) uint32 {
return (value >> shift) & mask(len)
}
func writeBits(value, len, shift, new uint32) uint32 {
value &^= mask(len) << shift
value |= (new & mask(len)) << shift
return value
}
func (bt *btfType) info(len, shift uint32) uint32 {
return readBits(bt.Info, len, shift)
}
func (bt *btfType) setInfo(value, len, shift uint32) {
bt.Info = writeBits(bt.Info, len, shift, value)
}
func (bt *btfType) Kind() btfKind {
return btfKind(bt.info(btfTypeKindLen, btfTypeKindShift))
}
func (bt *btfType) SetKind(kind btfKind) {
bt.setInfo(uint32(kind), btfTypeKindLen, btfTypeKindShift)
}
func (bt *btfType) Vlen() int {
return int(bt.info(btfTypeVlenMask, btfTypeVlenShift))
}
func (bt *btfType) SetVlen(vlen int) {
bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) kindFlagBool() bool {
return bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift) == 1
}
func (bt *btfType) setKindFlagBool(set bool) {
var value uint32
if set {
value = 1
}
bt.setInfo(value, btfTypeKindFlagMask, btfTypeKindFlagShift)
}
// Bitfield returns true if the struct or union contain a bitfield.
func (bt *btfType) Bitfield() bool {
return bt.kindFlagBool()
}
func (bt *btfType) SetBitfield(isBitfield bool) {
bt.setKindFlagBool(isBitfield)
}
func (bt *btfType) FwdKind() FwdKind {
return FwdKind(bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift))
}
func (bt *btfType) SetFwdKind(kind FwdKind) {
bt.setInfo(uint32(kind), btfTypeKindFlagMask, btfTypeKindFlagShift)
}
func (bt *btfType) Signed() bool {
return bt.kindFlagBool()
}
func (bt *btfType) SetSigned(signed bool) {
bt.setKindFlagBool(signed)
}
func (bt *btfType) Linkage() FuncLinkage {
return FuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
}
func (bt *btfType) SetLinkage(linkage FuncLinkage) {
bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) Type() TypeID {
// TODO: Panic here if wrong kind?
return TypeID(bt.SizeType)
}
func (bt *btfType) SetType(id TypeID) {
bt.SizeType = uint32(id)
}
func (bt *btfType) Size() uint32 {
// TODO: Panic here if wrong kind?
return bt.SizeType
}
func (bt *btfType) SetSize(size uint32) {
bt.SizeType = size
}
func (bt *btfType) Marshal(w io.Writer, bo binary.ByteOrder) error {
buf := make([]byte, unsafe.Sizeof(*bt))
bo.PutUint32(buf[0:], bt.NameOff)
bo.PutUint32(buf[4:], bt.Info)
bo.PutUint32(buf[8:], bt.SizeType)
_, err := w.Write(buf)
return err
}
type rawType struct {
btfType
data interface{}
}
func (rt *rawType) Marshal(w io.Writer, bo binary.ByteOrder) error {
if err := rt.btfType.Marshal(w, bo); err != nil {
return err
}
if rt.data == nil {
return nil
}
return binary.Write(w, bo, rt.data)
}
// btfInt encodes additional data for integers.
//
// ? ? ? ? e e e e o o o o o o o o ? ? ? ? ? ? ? ? b b b b b b b b
// ? = undefined
// e = encoding
// o = offset (bitfields?)
// b = bits (bitfields)
type btfInt struct {
Raw uint32
}
const (
btfIntEncodingLen = 4
btfIntEncodingShift = 24
btfIntOffsetLen = 8
btfIntOffsetShift = 16
btfIntBitsLen = 8
btfIntBitsShift = 0
)
func (bi btfInt) Encoding() IntEncoding {
return IntEncoding(readBits(bi.Raw, btfIntEncodingLen, btfIntEncodingShift))
}
func (bi *btfInt) SetEncoding(e IntEncoding) {
bi.Raw = writeBits(uint32(bi.Raw), btfIntEncodingLen, btfIntEncodingShift, uint32(e))
}
func (bi btfInt) Offset() Bits {
return Bits(readBits(bi.Raw, btfIntOffsetLen, btfIntOffsetShift))
}
func (bi *btfInt) SetOffset(offset uint32) {
bi.Raw = writeBits(bi.Raw, btfIntOffsetLen, btfIntOffsetShift, offset)
}
func (bi btfInt) Bits() Bits {
return Bits(readBits(bi.Raw, btfIntBitsLen, btfIntBitsShift))
}
func (bi *btfInt) SetBits(bits byte) {
bi.Raw = writeBits(bi.Raw, btfIntBitsLen, btfIntBitsShift, uint32(bits))
}
type btfArray struct {
Type TypeID
IndexType TypeID
Nelems uint32
}
type btfMember struct {
NameOff uint32
Type TypeID
Offset uint32
}
type btfVarSecinfo struct {
Type TypeID
Offset uint32
Size uint32
}
type btfVariable struct {
Linkage uint32
}
type btfEnum struct {
NameOff uint32
Val uint32
}
type btfEnum64 struct {
NameOff uint32
ValLo32 uint32
ValHi32 uint32
}
type btfParam struct {
NameOff uint32
Type TypeID
}
type btfDeclTag struct {
ComponentIdx uint32
}
func readTypes(r io.Reader, bo binary.ByteOrder, typeLen uint32) ([]rawType, error) {
var header btfType
// because of the interleaving between types and struct members it is difficult to
// precompute the numbers of raw types this will parse
// this "guess" is a good first estimation
sizeOfbtfType := uintptr(btfTypeLen)
tyMaxCount := uintptr(typeLen) / sizeOfbtfType / 2
types := make([]rawType, 0, tyMaxCount)
for id := TypeID(1); ; id++ {
if err := binary.Read(r, bo, &header); err == io.EOF {
return types, nil
} else if err != nil {
return nil, fmt.Errorf("can't read type info for id %v: %v", id, err)
}
var data interface{}
switch header.Kind() {
case kindInt:
data = new(btfInt)
case kindPointer:
case kindArray:
data = new(btfArray)
case kindStruct:
fallthrough
case kindUnion:
data = make([]btfMember, header.Vlen())
case kindEnum:
data = make([]btfEnum, header.Vlen())
case kindForward:
case kindTypedef:
case kindVolatile:
case kindConst:
case kindRestrict:
case kindFunc:
case kindFuncProto:
data = make([]btfParam, header.Vlen())
case kindVar:
data = new(btfVariable)
case kindDatasec:
data = make([]btfVarSecinfo, header.Vlen())
case kindFloat:
case kindDeclTag:
data = new(btfDeclTag)
case kindTypeTag:
case kindEnum64:
data = make([]btfEnum64, header.Vlen())
default:
return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind())
}
if data == nil {
types = append(types, rawType{header, nil})
continue
}
if err := binary.Read(r, bo, data); err != nil {
return nil, fmt.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err)
}
types = append(types, rawType{header, data})
}
}
@@ -0,0 +1,80 @@
// Code generated by "stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage,btfKind"; 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]]
}
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[kindUnknown-0]
_ = x[kindInt-1]
_ = x[kindPointer-2]
_ = x[kindArray-3]
_ = x[kindStruct-4]
_ = x[kindUnion-5]
_ = x[kindEnum-6]
_ = x[kindForward-7]
_ = x[kindTypedef-8]
_ = x[kindVolatile-9]
_ = x[kindConst-10]
_ = x[kindRestrict-11]
_ = x[kindFunc-12]
_ = x[kindFuncProto-13]
_ = x[kindVar-14]
_ = x[kindDatasec-15]
_ = x[kindFloat-16]
_ = x[kindDeclTag-17]
_ = x[kindTypeTag-18]
_ = x[kindEnum64-19]
}
const _btfKind_name = "UnknownIntPointerArrayStructUnionEnumForwardTypedefVolatileConstRestrictFuncFuncProtoVarDatasecFloatDeclTagTypeTagEnum64"
var _btfKind_index = [...]uint8{0, 7, 10, 17, 22, 28, 33, 37, 44, 51, 59, 64, 72, 76, 85, 88, 95, 100, 107, 114, 120}
func (i btfKind) String() string {
if i >= btfKind(len(_btfKind_index)-1) {
return "btfKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _btfKind_name[_btfKind_index[i]:_btfKind_index[i+1]]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,111 @@
package btf_test
import (
"fmt"
"io"
"os"
"strings"
"testing"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
)
func TestCORERelocationLoad(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/relocs-*.elf"), func(t *testing.T, file string) {
fh, err := os.Open(file)
if err != nil {
t.Fatal(err)
}
defer fh.Close()
spec, err := ebpf.LoadCollectionSpecFromReader(fh)
if err != nil {
t.Fatal(err)
}
if spec.ByteOrder != internal.NativeEndian {
return
}
for _, progSpec := range spec.Programs {
t.Run(progSpec.Name, func(t *testing.T) {
if _, err := fh.Seek(0, io.SeekStart); err != nil {
t.Fatal(err)
}
prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
KernelTypes: spec.Types,
})
if strings.HasPrefix(progSpec.Name, "err_") {
if err == nil {
prog.Close()
t.Fatal("Expected an error")
}
t.Log("Got expected error:", err)
return
}
if err != nil {
t.Fatal("Load program:", err)
}
defer prog.Close()
ret, _, err := prog.Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Error when running:", err)
}
if ret != 0 {
t.Error("Assertion failed on line", ret)
}
})
}
})
}
func TestCORERelocationRead(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/relocs_read-*.elf"), func(t *testing.T, file string) {
spec, err := ebpf.LoadCollectionSpec(file)
if err != nil {
t.Fatal(err)
}
if spec.ByteOrder != internal.NativeEndian {
return
}
targetFile := fmt.Sprintf("testdata/relocs_read_tgt-%s.elf", internal.ClangEndian)
targetSpec, err := btf.LoadSpec(targetFile)
if err != nil {
t.Fatal(err)
}
for _, progSpec := range spec.Programs {
t.Run(progSpec.Name, func(t *testing.T) {
prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
KernelTypes: targetSpec,
})
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Load program:", err)
}
defer prog.Close()
ret, _, err := prog.Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Error when running:", err)
}
if ret != 0 {
t.Error("Assertion failed on line", ret)
}
})
}
})
}
@@ -0,0 +1,746 @@
package btf
import (
"errors"
"fmt"
"os"
"strings"
"testing"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
"github.com/google/go-cmp/cmp"
qt "github.com/frankban/quicktest"
)
func TestCheckTypeCompatibility(t *testing.T) {
tests := []struct {
a, b Type
compatible bool
}{
{&FuncProto{Return: &Typedef{Type: &Int{}}}, &FuncProto{Return: &Int{}}, true},
{&FuncProto{Return: &Typedef{Type: &Int{}}}, &FuncProto{Return: &Void{}}, false},
}
for _, test := range tests {
err := CheckTypeCompatibility(test.a, test.b)
if test.compatible {
if err != nil {
t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
} else {
if !errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
}
err = CheckTypeCompatibility(test.b, test.a)
if test.compatible {
if err != nil {
t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
} else {
if !errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
}
}
}
func TestCOREAreTypesCompatible(t *testing.T) {
tests := []struct {
a, b Type
compatible bool
}{
{&Void{}, &Void{}, true},
{&Struct{Name: "a"}, &Struct{Name: "b"}, true},
{&Union{Name: "a"}, &Union{Name: "b"}, true},
{&Union{Name: "a"}, &Struct{Name: "b"}, false},
{&Enum{Name: "a"}, &Enum{Name: "b"}, true},
{&Fwd{Name: "a"}, &Fwd{Name: "b"}, true},
{&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true},
{&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true},
{&Pointer{Target: &Void{}}, &Void{}, false},
{&Array{Index: &Void{}, Type: &Void{}}, &Array{Index: &Void{}, Type: &Void{}}, true},
{&Array{Index: &Void{}, Type: &Int{}}, &Array{Index: &Void{}, Type: &Void{}}, false},
{&FuncProto{Return: &Int{}}, &FuncProto{Return: &Void{}}, false},
{
&FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "a", Type: &Void{}}}},
&FuncProto{Return: &Void{}, Params: []FuncParam{{Name: "b", Type: &Void{}}}},
true,
},
{
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}},
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Int{}}}},
false,
},
{
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}, {Type: &Void{}}}},
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}},
false,
},
}
for _, test := range tests {
err := coreAreTypesCompatible(test.a, test.b)
if test.compatible {
if err != nil {
t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
} else {
if !errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
}
err = coreAreTypesCompatible(test.b, test.a)
if test.compatible {
if err != nil {
t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
} else {
if !errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
}
}
for _, invalid := range []Type{&Var{}, &Datasec{}} {
err := coreAreTypesCompatible(invalid, invalid)
if errors.Is(err, errIncompatibleTypes) {
t.Errorf("Expected an error for %T, not errIncompatibleTypes", invalid)
} else if err == nil {
t.Errorf("Expected an error for %T", invalid)
}
}
}
func TestCOREAreMembersCompatible(t *testing.T) {
tests := []struct {
a, b Type
compatible bool
}{
{&Struct{Name: "a"}, &Struct{Name: "b"}, true},
{&Union{Name: "a"}, &Union{Name: "b"}, true},
{&Union{Name: "a"}, &Struct{Name: "b"}, true},
{&Enum{Name: "a"}, &Enum{Name: "b"}, false},
{&Enum{Name: "a"}, &Enum{Name: "a___foo"}, true},
{&Enum{Name: "a"}, &Enum{Name: ""}, true},
{&Fwd{Name: "a"}, &Fwd{Name: "b"}, false},
{&Fwd{Name: "a"}, &Fwd{Name: "a___foo"}, true},
{&Fwd{Name: "a"}, &Fwd{Name: ""}, true},
{&Int{Name: "a", Size: 2}, &Int{Name: "b", Size: 4}, true},
{&Pointer{Target: &Void{}}, &Pointer{Target: &Void{}}, true},
{&Pointer{Target: &Void{}}, &Void{}, false},
{&Array{Type: &Int{Size: 1}}, &Array{Type: &Int{Encoding: Signed}}, true},
{&Float{Size: 2}, &Float{Size: 4}, true},
}
for _, test := range tests {
err := coreAreMembersCompatible(test.a, test.b)
if test.compatible {
if err != nil {
t.Errorf("Expected members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
} else {
if !errors.Is(err, errImpossibleRelocation) {
t.Errorf("Expected members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
continue
}
}
err = coreAreMembersCompatible(test.b, test.a)
if test.compatible {
if err != nil {
t.Errorf("Expected reversed members to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
} else {
if !errors.Is(err, errImpossibleRelocation) {
t.Errorf("Expected reversed members to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
}
}
}
for _, invalid := range []Type{&Void{}, &FuncProto{}, &Var{}, &Datasec{}} {
err := coreAreMembersCompatible(invalid, invalid)
if errors.Is(err, errImpossibleRelocation) {
t.Errorf("Expected an error for %T, not errImpossibleRelocation", invalid)
} else if err == nil {
t.Errorf("Expected an error for %T", invalid)
}
}
}
func TestCOREAccessor(t *testing.T) {
for _, valid := range []string{
"0",
"1:0",
"1:0:3:34:10:1",
} {
_, err := parseCOREAccessor(valid)
if err != nil {
t.Errorf("Parse %q: %s", valid, err)
}
}
for _, invalid := range []string{
"",
"-1",
":",
"0:",
":12",
"4294967296",
} {
_, err := parseCOREAccessor(invalid)
if err == nil {
t.Errorf("Accepted invalid accessor %q", invalid)
}
}
}
func TestCOREFindEnumValue(t *testing.T) {
a := &Enum{Values: []EnumValue{{"foo", 23}, {"bar", 42}}}
b := &Enum{Values: []EnumValue{
{"foo___flavour", 0},
{"bar", 123},
{"garbage", 3},
}}
invalid := []struct {
name string
local Type
target Type
acc coreAccessor
err error
}{
{"o-o-b accessor", a, b, coreAccessor{len(a.Values)}, nil},
{"long accessor", a, b, coreAccessor{0, 1}, nil},
{"wrong target", a, &Void{}, coreAccessor{0, 1}, nil},
{
"no matching value",
b, a,
coreAccessor{2},
errImpossibleRelocation,
},
}
for _, test := range invalid {
t.Run(test.name, func(t *testing.T) {
_, _, err := coreFindEnumValue(test.local, test.acc, test.target)
if test.err != nil && !errors.Is(err, test.err) {
t.Fatalf("Expected %s, got %s", test.err, err)
}
if err == nil {
t.Fatal("Accepted invalid case")
}
})
}
valid := []struct {
name string
local, target Type
acc coreAccessor
localValue, targetValue uint64
}{
{"a to b", a, b, coreAccessor{0}, 23, 0},
{"b to a", b, a, coreAccessor{1}, 123, 42},
}
for _, test := range valid {
t.Run(test.name, func(t *testing.T) {
local, target, err := coreFindEnumValue(test.local, test.acc, test.target)
qt.Assert(t, err, qt.IsNil)
qt.Check(t, local.Value, qt.Equals, test.localValue)
qt.Check(t, target.Value, qt.Equals, test.targetValue)
})
}
}
func TestCOREFindField(t *testing.T) {
ptr := &Pointer{}
u16 := &Int{Size: 2}
u32 := &Int{Size: 4}
aFields := []Member{
{Name: "foo", Type: ptr, Offset: 8},
{Name: "bar", Type: u16, Offset: 16},
{Name: "baz", Type: u32, Offset: 32, BitfieldSize: 3},
{Name: "quux", Type: u32, Offset: 35, BitfieldSize: 10},
{Name: "quuz", Type: u32, Offset: 45, BitfieldSize: 8},
}
bFields := []Member{
{Name: "foo", Type: ptr, Offset: 16},
{Name: "bar", Type: u32, Offset: 8},
{Name: "other", Offset: 4},
// baz is separated out from the other bitfields
{Name: "baz", Type: u32, Offset: 64, BitfieldSize: 3},
// quux's type changes u32->u16
{Name: "quux", Type: u16, Offset: 96, BitfieldSize: 10},
// quuz becomes a normal field
{Name: "quuz", Type: u16, Offset: 112},
}
aStruct := &Struct{Members: aFields, Size: 48}
bStruct := &Struct{Members: bFields, Size: 80}
aArray := &Array{Nelems: 4, Type: u16}
bArray := &Array{Nelems: 3, Type: u32}
invalid := []struct {
name string
local, target Type
acc coreAccessor
err error
}{
{
"unsupported type",
&Void{}, &Void{},
coreAccessor{0, 0},
ErrNotSupported,
},
{
"different types",
&Union{}, &Array{Type: u16},
coreAccessor{0},
errImpossibleRelocation,
},
{
"invalid composite accessor",
aStruct, aStruct,
coreAccessor{0, len(aStruct.Members)},
nil,
},
{
"invalid array accessor",
aArray, aArray,
coreAccessor{0, int(aArray.Nelems)},
nil,
},
{
"o-o-b array accessor",
aArray, bArray,
coreAccessor{0, int(bArray.Nelems)},
errImpossibleRelocation,
},
{
"no match",
bStruct, aStruct,
coreAccessor{0, 2},
errImpossibleRelocation,
},
{
"incompatible match",
&Union{Members: []Member{{Name: "foo", Type: &Pointer{}}}},
&Union{Members: []Member{{Name: "foo", Type: &Int{}}}},
coreAccessor{0, 0},
errImpossibleRelocation,
},
{
"unsized type",
bStruct, &Func{},
// non-zero accessor to force calculating the offset.
coreAccessor{1},
errImpossibleRelocation,
},
}
for _, test := range invalid {
t.Run(test.name, func(t *testing.T) {
_, _, err := coreFindField(test.local, test.acc, test.target)
if test.err != nil && !errors.Is(err, test.err) {
t.Fatalf("Expected %s, got %s", test.err, err)
}
if err == nil {
t.Fatal("Accepted invalid case")
}
t.Log(err)
})
}
bytes := func(typ Type) uint32 {
sz, err := Sizeof(typ)
if err != nil {
t.Fatal(err)
}
return uint32(sz)
}
anon := func(t Type, offset Bits) []Member {
return []Member{{Type: t, Offset: offset}}
}
anonStruct := func(m ...Member) Member {
return Member{Type: &Struct{Members: m}}
}
anonUnion := func(m ...Member) Member {
return Member{Type: &Union{Members: m}}
}
valid := []struct {
name string
local Type
target Type
acc coreAccessor
localField, targetField coreField
}{
{
"array[0]",
aArray,
bArray,
coreAccessor{0, 0},
coreField{u16, 0, 0, 0},
coreField{u32, 0, 0, 0},
},
{
"array[1]",
aArray,
bArray,
coreAccessor{0, 1},
coreField{u16, bytes(aArray.Type), 0, 0},
coreField{u32, bytes(bArray.Type), 0, 0},
},
{
"array[0] with base offset",
aArray,
bArray,
coreAccessor{1, 0},
coreField{u16, bytes(aArray), 0, 0},
coreField{u32, bytes(bArray), 0, 0},
},
{
"array[2] with base offset",
aArray,
bArray,
coreAccessor{1, 2},
coreField{u16, bytes(aArray) + 2*bytes(aArray.Type), 0, 0},
coreField{u32, bytes(bArray) + 2*bytes(bArray.Type), 0, 0},
},
{
"flex array",
&Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u16}}}},
&Struct{Members: []Member{{Name: "foo", Type: &Array{Nelems: 0, Type: u32}}}},
coreAccessor{0, 0, 9000},
coreField{u16, bytes(u16) * 9000, 0, 0},
coreField{u32, bytes(u32) * 9000, 0, 0},
},
{
"struct.0",
aStruct, bStruct,
coreAccessor{0, 0},
coreField{ptr, 1, 0, 0},
coreField{ptr, 2, 0, 0},
},
{
"struct.0 anon",
aStruct, &Struct{Members: anon(bStruct, 24)},
coreAccessor{0, 0},
coreField{ptr, 1, 0, 0},
coreField{ptr, 3 + 2, 0, 0},
},
{
"struct.0 with base offset",
aStruct, bStruct,
coreAccessor{3, 0},
coreField{ptr, 3*bytes(aStruct) + 1, 0, 0},
coreField{ptr, 3*bytes(bStruct) + 2, 0, 0},
},
{
"struct.1",
aStruct, bStruct,
coreAccessor{0, 1},
coreField{u16, 2, 0, 0},
coreField{u32, 1, 0, 0},
},
{
"struct.1 anon",
aStruct, &Struct{Members: anon(bStruct, 24)},
coreAccessor{0, 1},
coreField{u16, 2, 0, 0},
coreField{u32, 3 + 1, 0, 0},
},
{
"union.1",
&Union{Members: aFields, Size: 32},
&Union{Members: bFields, Size: 32},
coreAccessor{0, 1},
coreField{u16, 2, 0, 0},
coreField{u32, 1, 0, 0},
},
{
"interchangeable composites",
&Struct{
Members: []Member{
anonStruct(anonUnion(Member{Name: "_1", Type: u16})),
},
},
&Struct{
Members: []Member{
anonUnion(anonStruct(Member{Name: "_1", Type: u16})),
},
},
coreAccessor{0, 0, 0, 0},
coreField{u16, 0, 0, 0},
coreField{u16, 0, 0, 0},
},
{
"struct.2 (bitfield baz)",
aStruct, bStruct,
coreAccessor{0, 2},
coreField{u32, 4, 0, 3},
coreField{u32, 8, 0, 3},
},
{
"struct.3 (bitfield quux)",
aStruct, bStruct,
coreAccessor{0, 3},
coreField{u32, 4, 3, 10},
coreField{u16, 12, 0, 10},
},
{
"struct.4 (bitfield quuz)",
aStruct, bStruct,
coreAccessor{0, 4},
coreField{u32, 4, 13, 8},
coreField{u16, 14, 0, 0},
},
}
allowCoreField := cmp.AllowUnexported(coreField{})
checkCOREField := func(t *testing.T, which string, got, want coreField) {
t.Helper()
if diff := cmp.Diff(want, got, allowCoreField); diff != "" {
t.Errorf("%s mismatch (-want +got):\n%s", which, diff)
}
}
for _, test := range valid {
t.Run(test.name, func(t *testing.T) {
localField, targetField, err := coreFindField(test.local, test.acc, test.target)
qt.Assert(t, err, qt.IsNil)
checkCOREField(t, "local", localField, test.localField)
checkCOREField(t, "target", targetField, test.targetField)
})
}
}
func TestCOREFindFieldCyclical(t *testing.T) {
members := []Member{{Name: "foo", Type: &Pointer{}}}
cyclicStruct := &Struct{}
cyclicStruct.Members = []Member{{Type: cyclicStruct}}
cyclicUnion := &Union{}
cyclicUnion.Members = []Member{{Type: cyclicUnion}}
cyclicArray := &Array{Nelems: 1}
cyclicArray.Type = &Pointer{Target: cyclicArray}
tests := []struct {
name string
local, cyclic Type
}{
{"struct", &Struct{Members: members}, cyclicStruct},
{"union", &Union{Members: members}, cyclicUnion},
{"array", &Array{Nelems: 2, Type: &Int{}}, cyclicArray},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, _, err := coreFindField(test.local, coreAccessor{0, 0}, test.cyclic)
if !errors.Is(err, errImpossibleRelocation) {
t.Fatal("Should return errImpossibleRelocation, got", err)
}
})
}
}
func TestCORERelocation(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/*.elf"), func(t *testing.T, file string) {
rd, err := os.Open(file)
if err != nil {
t.Fatal(err)
}
defer rd.Close()
spec, extInfos, err := LoadSpecAndExtInfosFromReader(rd)
if err != nil {
t.Fatal(err)
}
if extInfos == nil {
t.Skip("No ext_infos")
}
errs := map[string]error{
"err_ambiguous": errAmbiguousRelocation,
"err_ambiguous_flavour": errAmbiguousRelocation,
}
for section := range extInfos.funcInfos {
name := strings.TrimPrefix(section, "socket_filter/")
t.Run(name, func(t *testing.T) {
var relos []*CORERelocation
for _, reloInfo := range extInfos.relocationInfos[section] {
relos = append(relos, reloInfo.relo)
}
fixups, err := CORERelocate(relos, spec, spec.byteOrder)
if want := errs[name]; want != nil {
if !errors.Is(err, want) {
t.Fatal("Expected", want, "got", err)
}
return
}
if err != nil {
t.Fatal("Can't relocate against itself:", err)
}
for offset, fixup := range fixups {
if want := fixup.local; !fixup.skipLocalValidation && want != fixup.target {
// Since we're relocating against ourselves both values
// should match.
t.Errorf("offset %d: local %v doesn't match target %d (kind %s)", offset, fixup.local, fixup.target, fixup.kind)
}
}
})
}
})
}
func TestCORECopyWithoutQualifiers(t *testing.T) {
qualifiers := []struct {
name string
fn func(Type) Type
}{
{"const", func(t Type) Type { return &Const{Type: t} }},
{"volatile", func(t Type) Type { return &Volatile{Type: t} }},
{"restrict", func(t Type) Type { return &Restrict{Type: t} }},
{"typedef", func(t Type) Type { return &Typedef{Type: t} }},
}
for _, test := range qualifiers {
t.Run(test.name+" cycle", func(t *testing.T) {
root := &Volatile{}
root.Type = test.fn(root)
cycle, ok := Copy(root, UnderlyingType).(*cycle)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, cycle.root, qt.Equals, root)
})
}
for _, a := range qualifiers {
for _, b := range qualifiers {
t.Run(a.name+" "+b.name, func(t *testing.T) {
v := a.fn(&Pointer{Target: b.fn(&Int{Name: "z"})})
want := &Pointer{Target: &Int{Name: "z"}}
got := Copy(v, UnderlyingType)
qt.Assert(t, got, qt.DeepEquals, want)
})
}
}
t.Run("long chain", func(t *testing.T) {
root := &Int{Name: "abc"}
v := Type(root)
for i := 0; i < maxTypeDepth; i++ {
q := qualifiers[testutils.Rand().Intn(len(qualifiers))]
v = q.fn(v)
t.Log(q.name)
}
got := Copy(v, UnderlyingType)
qt.Assert(t, got, qt.DeepEquals, root)
})
}
func TestCOREReloFieldSigned(t *testing.T) {
for _, typ := range []Type{&Int{}, &Enum{}} {
t.Run(fmt.Sprintf("%T with invalid target", typ), func(t *testing.T) {
relo := &CORERelocation{
typ, coreAccessor{0}, reloFieldSigned, 0,
}
fixup, err := coreCalculateFixup(relo, &Void{}, 0, internal.NativeEndian)
qt.Assert(t, fixup.poison, qt.IsTrue)
qt.Assert(t, err, qt.IsNil)
})
}
t.Run("type without signedness", func(t *testing.T) {
relo := &CORERelocation{
&Array{}, coreAccessor{0}, reloFieldSigned, 0,
}
_, err := coreCalculateFixup(relo, &Array{}, 0, internal.NativeEndian)
qt.Assert(t, err, qt.ErrorIs, errNoSignedness)
})
}
func TestCOREReloFieldShiftU64(t *testing.T) {
typ := &Struct{
Members: []Member{
{Name: "A", Type: &Fwd{}},
},
}
for _, relo := range []*CORERelocation{
{typ, coreAccessor{0, 0}, reloFieldRShiftU64, 1},
{typ, coreAccessor{0, 0}, reloFieldLShiftU64, 1},
} {
t.Run(relo.kind.String(), func(t *testing.T) {
_, err := coreCalculateFixup(relo, typ, 1, internal.NativeEndian)
qt.Assert(t, err, qt.ErrorIs, errUnsizedType)
})
}
}
func BenchmarkCORESkBuff(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
var skb *Struct
err := spec.TypeByName("sk_buff", &skb)
qt.Assert(b, err, qt.IsNil)
skbID, err := spec.TypeID(skb)
qt.Assert(b, err, qt.IsNil)
var pktHashTypes *Enum
err = spec.TypeByName("pkt_hash_types", &pktHashTypes)
qt.Assert(b, err, qt.IsNil)
pktHashTypesID, err := spec.TypeID(pktHashTypes)
qt.Assert(b, err, qt.IsNil)
for _, relo := range []*CORERelocation{
{skb, coreAccessor{0, 0}, reloFieldByteOffset, skbID},
{skb, coreAccessor{0, 0}, reloFieldByteSize, skbID},
{skb, coreAccessor{0, 0}, reloFieldExists, skbID},
{skb, coreAccessor{0, 0}, reloFieldSigned, skbID},
{skb, coreAccessor{0, 0}, reloFieldLShiftU64, skbID},
{skb, coreAccessor{0, 0}, reloFieldRShiftU64, skbID},
{skb, coreAccessor{0}, reloTypeIDLocal, skbID},
{skb, coreAccessor{0}, reloTypeIDTarget, skbID},
{skb, coreAccessor{0}, reloTypeExists, skbID},
{skb, coreAccessor{0}, reloTypeSize, skbID},
{pktHashTypes, coreAccessor{0}, reloEnumvalExists, pktHashTypesID},
{pktHashTypes, coreAccessor{0}, reloEnumvalValue, pktHashTypesID},
} {
b.Run(relo.kind.String(), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err = CORERelocate([]*CORERelocation{relo}, spec, spec.byteOrder)
if err != nil {
b.Fatal(err)
}
}
})
}
}
@@ -0,0 +1,5 @@
// Package btf handles data encoded according to the BPF Type Format.
//
// The canonical documentation lives in the Linux kernel repository and is
// available at https://www.kernel.org/doc/html/latest/bpf/btf.html
package btf
@@ -0,0 +1,768 @@
package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"sort"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
)
// ExtInfos contains ELF section metadata.
type ExtInfos struct {
// The slices are sorted by offset in ascending order.
funcInfos map[string][]funcInfo
lineInfos map[string][]lineInfo
relocationInfos map[string][]coreRelocationInfo
}
// loadExtInfosFromELF parses ext infos from the .BTF.ext section in an ELF.
//
// Returns an error wrapping ErrNotFound if no ext infos are present.
func loadExtInfosFromELF(file *internal.SafeELFFile, spec *Spec) (*ExtInfos, error) {
section := file.Section(".BTF.ext")
if section == nil {
return nil, fmt.Errorf("btf ext infos: %w", ErrNotFound)
}
if section.ReaderAt == nil {
return nil, fmt.Errorf("compressed ext_info is not supported")
}
return loadExtInfos(section.ReaderAt, file.ByteOrder, spec, spec.strings)
}
// loadExtInfos parses bare ext infos.
func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, spec *Spec, strings *stringTable) (*ExtInfos, 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))
btfFuncInfos, err := parseFuncInfos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing BTF function info: %w", err)
}
funcInfos := make(map[string][]funcInfo, len(btfFuncInfos))
for section, bfis := range btfFuncInfos {
funcInfos[section], err = newFuncInfos(bfis, spec)
if err != nil {
return nil, fmt.Errorf("section %s: func infos: %w", section, err)
}
}
buf = internal.NewBufferedSectionReader(r, extHeader.lineInfoStart(), int64(extHeader.LineInfoLen))
btfLineInfos, err := parseLineInfos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing BTF line info: %w", err)
}
lineInfos := make(map[string][]lineInfo, len(btfLineInfos))
for section, blis := range btfLineInfos {
lineInfos[section], err = newLineInfos(blis, strings)
if err != nil {
return nil, fmt.Errorf("section %s: line infos: %w", section, err)
}
}
if coreHeader == nil || coreHeader.COREReloLen == 0 {
return &ExtInfos{funcInfos, lineInfos, nil}, nil
}
var btfCORERelos map[string][]bpfCORERelo
buf = internal.NewBufferedSectionReader(r, extHeader.coreReloStart(coreHeader), int64(coreHeader.COREReloLen))
btfCORERelos, err = parseCORERelos(buf, bo, strings)
if err != nil {
return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
}
coreRelos := make(map[string][]coreRelocationInfo, len(btfCORERelos))
for section, brs := range btfCORERelos {
coreRelos[section], err = newRelocationInfos(brs, spec, strings)
if err != nil {
return nil, fmt.Errorf("section %s: CO-RE relocations: %w", section, err)
}
}
return &ExtInfos{funcInfos, lineInfos, coreRelos}, nil
}
type funcInfoMeta struct{}
type coreRelocationMeta struct{}
// Assign per-section metadata from BTF to a section's instructions.
func (ei *ExtInfos) Assign(insns asm.Instructions, section string) {
funcInfos := ei.funcInfos[section]
lineInfos := ei.lineInfos[section]
reloInfos := ei.relocationInfos[section]
iter := insns.Iterate()
for iter.Next() {
if len(funcInfos) > 0 && funcInfos[0].offset == iter.Offset {
*iter.Ins = WithFuncMetadata(*iter.Ins, funcInfos[0].fn)
funcInfos = funcInfos[1:]
}
if len(lineInfos) > 0 && lineInfos[0].offset == iter.Offset {
*iter.Ins = iter.Ins.WithSource(lineInfos[0].line)
lineInfos = lineInfos[1:]
}
if len(reloInfos) > 0 && reloInfos[0].offset == iter.Offset {
iter.Ins.Metadata.Set(coreRelocationMeta{}, reloInfos[0].relo)
reloInfos = reloInfos[1:]
}
}
}
// MarshalExtInfos encodes function and line info embedded in insns into kernel
// wire format.
//
// Returns ErrNotSupported if the kernel doesn't support BTF-associated programs.
func MarshalExtInfos(insns asm.Instructions) (_ *Handle, funcInfos, lineInfos []byte, _ error) {
// Bail out early if the kernel doesn't support Func(Proto). If this is the
// case, func_info will also be unsupported.
if err := haveProgBTF(); err != nil {
return nil, nil, nil, err
}
iter := insns.Iterate()
for iter.Next() {
_, ok := iter.Ins.Source().(*Line)
fn := FuncMetadata(iter.Ins)
if ok || fn != nil {
goto marshal
}
}
return nil, nil, nil, nil
marshal:
var b Builder
var fiBuf, liBuf bytes.Buffer
for {
if fn := FuncMetadata(iter.Ins); fn != nil {
fi := &funcInfo{
fn: fn,
offset: iter.Offset,
}
if err := fi.marshal(&fiBuf, &b); err != nil {
return nil, nil, nil, fmt.Errorf("write func info: %w", err)
}
}
if line, ok := iter.Ins.Source().(*Line); ok {
li := &lineInfo{
line: line,
offset: iter.Offset,
}
if err := li.marshal(&liBuf, &b); err != nil {
return nil, nil, nil, fmt.Errorf("write line info: %w", err)
}
}
if !iter.Next() {
break
}
}
handle, err := NewHandle(&b)
return handle, fiBuf.Bytes(), liBuf.Bytes(), err
}
// btfExtHeader is found at the start of the .BTF.ext section.
type btfExtHeader struct {
Magic uint16
Version uint8
Flags uint8
// HdrLen is larger than the size of struct btfExtHeader when it is
// immediately followed by a btfExtCOREHeader.
HdrLen uint32
FuncInfoOff uint32
FuncInfoLen uint32
LineInfoOff uint32
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
}
// 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, &coreHeader); err != nil {
return nil, fmt.Errorf("can't read header: %v", err)
}
return &coreHeader, nil
}
type btfExtInfoSec struct {
SecNameOff uint32
NumInfo uint32
}
// 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)
}
secName, err := strings.Lookup(infoHeader.SecNameOff)
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)
}
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
}
// The size of a FuncInfo in BTF wire format.
var FuncInfoSize = uint32(binary.Size(bpfFuncInfo{}))
type funcInfo struct {
fn *Func
offset asm.RawInstructionOffset
}
type bpfFuncInfo struct {
// Instruction offset of the function within an ELF section.
InsnOff uint32
TypeID TypeID
}
func newFuncInfo(fi bpfFuncInfo, spec *Spec) (*funcInfo, error) {
typ, err := spec.TypeByID(fi.TypeID)
if err != nil {
return nil, err
}
fn, ok := typ.(*Func)
if !ok {
return nil, 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 fn.Name == "" {
return nil, fmt.Errorf("func with type ID %d doesn't have a name", fi.TypeID)
}
return &funcInfo{
fn,
asm.RawInstructionOffset(fi.InsnOff),
}, nil
}
func newFuncInfos(bfis []bpfFuncInfo, spec *Spec) ([]funcInfo, error) {
fis := make([]funcInfo, 0, len(bfis))
for _, bfi := range bfis {
fi, err := newFuncInfo(bfi, spec)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", bfi.InsnOff, err)
}
fis = append(fis, *fi)
}
sort.Slice(fis, func(i, j int) bool {
return fis[i].offset <= fis[j].offset
})
return fis, nil
}
// marshal into the BTF wire format.
func (fi *funcInfo) marshal(w *bytes.Buffer, b *Builder) error {
id, err := b.Add(fi.fn)
if err != nil {
return err
}
bfi := bpfFuncInfo{
InsnOff: uint32(fi.offset),
TypeID: id,
}
buf := make([]byte, FuncInfoSize)
internal.NativeEndian.PutUint32(buf, bfi.InsnOff)
internal.NativeEndian.PutUint32(buf[4:], uint32(bfi.TypeID))
_, err = w.Write(buf)
return err
}
// parseFuncInfos 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][]bpfFuncInfo, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
result := make(map[string][]bpfFuncInfo)
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) ([]bpfFuncInfo, error) {
var out []bpfFuncInfo
var fi bpfFuncInfo
if exp, got := FuncInfoSize, 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)
}
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
fi.InsnOff /= asm.InstructionSize
out = append(out, fi)
}
return out, nil
}
var LineInfoSize = uint32(binary.Size(bpfLineInfo{}))
// Line represents the location and contents of a single line of source
// code a BPF ELF was compiled from.
type Line struct {
fileName string
line string
lineNumber uint32
lineColumn uint32
}
func (li *Line) FileName() string {
return li.fileName
}
func (li *Line) Line() string {
return li.line
}
func (li *Line) LineNumber() uint32 {
return li.lineNumber
}
func (li *Line) LineColumn() uint32 {
return li.lineColumn
}
func (li *Line) String() string {
return li.line
}
type lineInfo struct {
line *Line
offset asm.RawInstructionOffset
}
// Constants for the format of bpfLineInfo.LineCol.
const (
bpfLineShift = 10
bpfLineMax = (1 << (32 - bpfLineShift)) - 1
bpfColumnMax = (1 << bpfLineShift) - 1
)
type bpfLineInfo struct {
// Instruction offset of the line within the whole instruction stream, in instructions.
InsnOff uint32
FileNameOff uint32
LineOff uint32
LineCol uint32
}
func newLineInfo(li bpfLineInfo, strings *stringTable) (*lineInfo, error) {
line, err := strings.Lookup(li.LineOff)
if err != nil {
return nil, fmt.Errorf("lookup of line: %w", err)
}
fileName, err := strings.Lookup(li.FileNameOff)
if err != nil {
return nil, fmt.Errorf("lookup of filename: %w", err)
}
lineNumber := li.LineCol >> bpfLineShift
lineColumn := li.LineCol & bpfColumnMax
return &lineInfo{
&Line{
fileName,
line,
lineNumber,
lineColumn,
},
asm.RawInstructionOffset(li.InsnOff),
}, nil
}
func newLineInfos(blis []bpfLineInfo, strings *stringTable) ([]lineInfo, error) {
lis := make([]lineInfo, 0, len(blis))
for _, bli := range blis {
li, err := newLineInfo(bli, strings)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", bli.InsnOff, err)
}
lis = append(lis, *li)
}
sort.Slice(lis, func(i, j int) bool {
return lis[i].offset <= lis[j].offset
})
return lis, nil
}
// marshal writes the binary representation of the LineInfo to w.
func (li *lineInfo) marshal(w *bytes.Buffer, b *Builder) error {
line := li.line
if line.lineNumber > bpfLineMax {
return fmt.Errorf("line %d exceeds %d", line.lineNumber, bpfLineMax)
}
if line.lineColumn > bpfColumnMax {
return fmt.Errorf("column %d exceeds %d", line.lineColumn, bpfColumnMax)
}
fileNameOff, err := b.addString(line.fileName)
if err != nil {
return fmt.Errorf("file name %q: %w", line.fileName, err)
}
lineOff, err := b.addString(line.line)
if err != nil {
return fmt.Errorf("line %q: %w", line.line, err)
}
bli := bpfLineInfo{
uint32(li.offset),
fileNameOff,
lineOff,
(line.lineNumber << bpfLineShift) | line.lineColumn,
}
buf := make([]byte, LineInfoSize)
internal.NativeEndian.PutUint32(buf, bli.InsnOff)
internal.NativeEndian.PutUint32(buf[4:], bli.FileNameOff)
internal.NativeEndian.PutUint32(buf[8:], bli.LineOff)
internal.NativeEndian.PutUint32(buf[12:], bli.LineCol)
_, err = w.Write(buf)
return err
}
// 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][]bpfLineInfo, error) {
recordSize, err := parseExtInfoRecordSize(r, bo)
if err != nil {
return nil, err
}
result := make(map[string][]bpfLineInfo)
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) ([]bpfLineInfo, error) {
var out []bpfLineInfo
var li bpfLineInfo
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)
}
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
li.InsnOff /= asm.InstructionSize
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 CORERelocation struct {
// The local type of the relocation, stripped of typedefs and qualifiers.
typ Type
accessor coreAccessor
kind coreKind
// The ID of the local type in the source BTF.
id TypeID
}
func (cr *CORERelocation) String() string {
return fmt.Sprintf("CORERelocation(%s, %s[%s], local_id=%d)", cr.kind, cr.typ, cr.accessor, cr.id)
}
func CORERelocationMetadata(ins *asm.Instruction) *CORERelocation {
relo, _ := ins.Metadata.Get(coreRelocationMeta{}).(*CORERelocation)
return relo
}
type coreRelocationInfo struct {
relo *CORERelocation
offset asm.RawInstructionOffset
}
func newRelocationInfo(relo bpfCORERelo, spec *Spec, strings *stringTable) (*coreRelocationInfo, error) {
typ, err := spec.TypeByID(relo.TypeID)
if err != nil {
return nil, err
}
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)
}
return &coreRelocationInfo{
&CORERelocation{
typ,
accessor,
relo.Kind,
relo.TypeID,
},
asm.RawInstructionOffset(relo.InsnOff),
}, nil
}
func newRelocationInfos(brs []bpfCORERelo, spec *Spec, strings *stringTable) ([]coreRelocationInfo, error) {
rs := make([]coreRelocationInfo, 0, len(brs))
for _, br := range brs {
relo, err := newRelocationInfo(br, spec, strings)
if err != nil {
return nil, fmt.Errorf("offset %d: %w", br.InsnOff, err)
}
rs = append(rs, *relo)
}
sort.Slice(rs, func(i, j int) bool {
return rs[i].offset < rs[j].offset
})
return rs, nil
}
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][]bpfCORERelo, 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][]bpfCORERelo)
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)
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) ([]bpfCORERelo, error) {
var out []bpfCORERelo
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)
}
// ELF tracks offset in bytes, the kernel expects raw BPF instructions.
// Convert as early as possible.
relo.InsnOff /= asm.InstructionSize
out = append(out, relo)
}
return out, nil
}
@@ -0,0 +1,25 @@
package btf
import (
"bytes"
"strings"
"testing"
"github.com/cilium/ebpf/internal"
)
func TestParseExtInfoBigRecordSize(t *testing.T) {
rd := strings.NewReader("\xff\xff\xff\xff\x00\x00\x00\x000709171295166016")
table, err := readStringTable(bytes.NewReader([]byte{0}), nil)
if err != nil {
t.Fatal(err)
}
if _, err := parseFuncInfos(rd, internal.NativeEndian, table); err == nil {
t.Error("Parsing func info with large record size doesn't return an error")
}
if _, err := parseLineInfos(rd, internal.NativeEndian, table); err == nil {
t.Error("Parsing line info with large record size doesn't return an error")
}
}
@@ -0,0 +1,344 @@
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 = skipQualifiers(typ)
fmt.Fprintf(&gf.w, "type %s ", name)
if err := gf.writeTypeLit(typ, 0); err != nil {
return err
}
e, ok := typ.(*Enum)
if !ok || len(e.Values) == 0 {
return nil
}
gf.w.WriteString("; const ( ")
for _, ev := range e.Values {
id := gf.enumIdentifier(name, ev.Name)
fmt.Fprintf(&gf.w, "%s %s = %d; ", id, name, ev.Value)
}
gf.w.WriteString(")")
return nil
}
// 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 = skipQualifiers(typ)
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
}
var err error
switch v := skipQualifiers(typ).(type) {
case *Int:
err = gf.writeIntLit(v)
case *Enum:
if !v.Signed {
gf.w.WriteRune('u')
}
switch v.Size {
case 1:
gf.w.WriteString("int8")
case 2:
gf.w.WriteString("int16")
case 4:
gf.w.WriteString("int32")
case 8:
gf.w.WriteString("int64")
default:
err = fmt.Errorf("invalid enum size %d", v.Size)
}
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 %T: %w", v, ErrNotSupported)
}
if err != nil {
return fmt.Errorf("%s: %w", typ, err)
}
return nil
}
func (gf *GoFormatter) writeIntLit(i *Int) error {
bits := i.Size * 8
switch i.Encoding {
case Bool:
if i.Size != 1 {
return fmt.Errorf("bool with size %d", i.Size)
}
gf.w.WriteString("bool")
case Char:
if i.Size != 1 {
return fmt.Errorf("char with size %d", i.Size)
}
// BTF doesn't have a way to specify the signedness of a char. Assume
// we are dealing with unsigned, since this works nicely with []byte
// in Go code.
fallthrough
case Unsigned, Signed:
stem := "uint"
if i.Encoding == Signed {
stem = "int"
}
if i.Size > 8 {
fmt.Fprintf(&gf.w, "[%d]byte /* %s%d */", i.Size, stem, i.Size*8)
} else {
fmt.Fprintf(&gf.w, "%s%d", stem, bits)
}
default:
return fmt.Errorf("can't encode %s", i.Encoding)
}
return nil
}
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.Offset.Bytes()
if n := offset - prevOffset; skippedBitfield && n > 0 {
fmt.Fprintf(&gf.w, "_ [%d]byte /* unsupported bitfield */; ", n)
} else {
gf.writePadding(n)
}
fieldSize, err := Sizeof(m.Type)
if err != nil {
return fmt.Errorf("field %d: %w", i, err)
}
prevOffset = offset + uint32(fieldSize)
if prevOffset > size {
return fmt.Errorf("field %d of size %d exceeds type size %d", i, fieldSize, 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.Offset%8 != 0 {
return fmt.Errorf("unsupported offset %d", m.Offset)
}
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, ok := vsi.Type.(*Var)
if !ok {
return fmt.Errorf("can't format %s as part of data section", vsi.Type)
}
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)
}
}
func skipQualifiers(typ Type) Type {
result := typ
for depth := 0; depth <= maxTypeDepth; depth++ {
switch v := (result).(type) {
case qualifier:
result = v.qualify()
default:
return result
}
}
return &cycle{typ}
}
@@ -0,0 +1,267 @@
package btf
import (
"errors"
"fmt"
"go/format"
"strings"
"testing"
)
func TestGoTypeDeclaration(t *testing.T) {
tests := []struct {
typ Type
output string
}{
{&Int{Size: 1}, "type t uint8"},
{&Int{Size: 1, Encoding: Bool}, "type t bool"},
{&Int{Size: 1, Encoding: Char}, "type t uint8"},
{&Int{Size: 2, Encoding: Signed}, "type t int16"},
{&Int{Size: 4, Encoding: Signed}, "type t int32"},
{&Int{Size: 8}, "type t uint64"},
{&Typedef{Name: "frob", Type: &Int{Size: 8}}, "type t uint64"},
{&Int{Size: 16}, "type t [16]byte /* uint128 */"},
{&Enum{Values: []EnumValue{{"FOO", 32}}, Size: 4}, "type t uint32; const ( tFOO t = 32; )"},
{&Enum{Values: []EnumValue{{"BAR", 1}}, Size: 1, Signed: true}, "type t int8; const ( tBAR t = 1; )"},
{
&Struct{
Name: "enum literals",
Size: 2,
Members: []Member{
{Name: "enum", Type: &Enum{Values: []EnumValue{{"BAR", 1}}, Size: 2}, Offset: 0},
},
},
"type t struct { enum uint16; }",
},
{&Array{Nelems: 2, Type: &Int{Size: 1}}, "type t [2]uint8"},
{
&Union{
Size: 8,
Members: []Member{
{Name: "a", Type: &Int{Size: 4}},
{Name: "b", Type: &Int{Size: 8}},
},
},
"type t struct { a uint32; _ [4]byte; }",
},
{
&Struct{
Name: "field padding",
Size: 16,
Members: []Member{
{Name: "frob", Type: &Int{Size: 4}, Offset: 0},
{Name: "foo", Type: &Int{Size: 8}, Offset: 8 * 8},
},
},
"type t struct { frob uint32; _ [4]byte; foo uint64; }",
},
{
&Struct{
Name: "end padding",
Size: 16,
Members: []Member{
{Name: "foo", Type: &Int{Size: 8}, Offset: 0},
{Name: "frob", Type: &Int{Size: 4}, Offset: 8 * 8},
},
},
"type t struct { foo uint64; frob uint32; _ [4]byte; }",
},
{
&Struct{
Name: "bitfield",
Size: 8,
Members: []Member{
{Name: "foo", Type: &Int{Size: 4}, Offset: 0, BitfieldSize: 1},
{Name: "frob", Type: &Int{Size: 4}, Offset: 4 * 8},
},
},
"type t struct { _ [4]byte /* unsupported bitfield */; frob uint32; }",
},
{
&Struct{
Name: "nested",
Size: 8,
Members: []Member{
{
Name: "foo",
Type: &Struct{
Size: 4,
Members: []Member{
{Name: "bar", Type: &Int{Size: 4}, Offset: 0},
},
},
},
{Name: "frob", Type: &Int{Size: 4}, Offset: 4 * 8},
},
},
"type t struct { foo struct { bar uint32; }; frob uint32; }",
},
{
&Struct{
Name: "nested anon union",
Size: 8,
Members: []Member{
{
Name: "",
Type: &Union{
Size: 4,
Members: []Member{
{Name: "foo", Type: &Int{Size: 4}, Offset: 0},
{Name: "bar", Type: &Int{Size: 4}, Offset: 0},
},
},
},
},
},
"type t struct { foo uint32; _ [4]byte; }",
},
{
&Datasec{
Size: 16,
Vars: []VarSecinfo{
{&Var{Name: "s", Type: &Int{Size: 2}, Linkage: StaticVar}, 0, 2},
{&Var{Name: "g", Type: &Int{Size: 4}, Linkage: GlobalVar}, 4, 4},
{&Var{Name: "e", Type: &Int{Size: 8}, Linkage: ExternVar}, 8, 8},
},
},
"type t struct { _ [4]byte; g uint32; _ [8]byte; }",
},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
have := mustGoTypeDeclaration(t, test.typ, nil, nil)
if have != test.output {
t.Errorf("Unexpected output:\n\t-%s\n\t+%s", test.output, have)
}
})
}
}
func TestGoTypeDeclarationNamed(t *testing.T) {
e1 := &Enum{Name: "e1", Size: 4}
s1 := &Struct{
Name: "s1",
Size: 4,
Members: []Member{
{Name: "frob", Type: e1},
},
}
s2 := &Struct{
Name: "s2",
Size: 4,
Members: []Member{
{Name: "frood", Type: s1},
},
}
td := &Typedef{Name: "td", Type: e1}
arr := &Array{Nelems: 1, Type: td}
tests := []struct {
typ Type
named []Type
output string
}{
{e1, []Type{e1}, "type t uint32"},
{s1, []Type{e1, s1}, "type t struct { frob E1; }"},
{s2, []Type{e1}, "type t struct { frood struct { frob E1; }; }"},
{s2, []Type{e1, s1}, "type t struct { frood S1; }"},
{td, nil, "type t uint32"},
{td, []Type{td}, "type t uint32"},
{arr, []Type{td}, "type t [1]TD"},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
names := make(map[Type]string)
for _, t := range test.named {
names[t] = strings.ToUpper(t.TypeName())
}
have := mustGoTypeDeclaration(t, test.typ, names, nil)
if have != test.output {
t.Errorf("Unexpected output:\n\t-%s\n\t+%s", test.output, have)
}
})
}
}
func TestGoTypeDeclarationQualifiers(t *testing.T) {
i := &Int{Size: 4}
want := mustGoTypeDeclaration(t, i, nil, nil)
tests := []struct {
typ Type
}{
{&Volatile{Type: i}},
{&Const{Type: i}},
{&Restrict{Type: i}},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
have := mustGoTypeDeclaration(t, test.typ, nil, nil)
if have != want {
t.Errorf("Unexpected output:\n\t-%s\n\t+%s", want, have)
}
})
}
}
func TestGoTypeDeclarationCycle(t *testing.T) {
s := &Struct{Name: "cycle"}
s.Members = []Member{{Name: "f", Type: s}}
var gf GoFormatter
_, err := gf.TypeDeclaration("t", s)
if !errors.Is(err, errNestedTooDeep) {
t.Fatal("Expected errNestedTooDeep, got", err)
}
}
func TestRejectBogusTypes(t *testing.T) {
tests := []struct {
typ Type
}{
{&Struct{
Size: 1,
Members: []Member{
{Name: "foo", Type: &Int{Size: 2}, Offset: 0},
},
}},
{&Int{Size: 2, Encoding: Bool}},
{&Int{Size: 1, Encoding: Char | Signed}},
{&Int{Size: 2, Encoding: Char}},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
var gf GoFormatter
_, err := gf.TypeDeclaration("t", test.typ)
if err == nil {
t.Fatal("TypeDeclaration does not reject bogus type")
}
})
}
}
func mustGoTypeDeclaration(tb testing.TB, typ Type, names map[Type]string, id func(string) string) string {
tb.Helper()
gf := GoFormatter{
Names: names,
Identifier: id,
}
have, err := gf.TypeDeclaration("t", typ)
if err != nil {
tb.Fatal(err)
}
_, err = format.Source([]byte(have))
if err != nil {
tb.Fatalf("Output can't be formatted: %s\n%s", err, have)
}
return have
}
@@ -0,0 +1,80 @@
package btf
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"testing"
"github.com/cilium/ebpf/internal"
)
func FuzzSpec(f *testing.F) {
var buf bytes.Buffer
err := binary.Write(&buf, internal.NativeEndian, &btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: uint32(binary.Size(btfHeader{})),
})
if err != nil {
f.Fatal(err)
}
f.Add(buf.Bytes())
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) < binary.Size(btfHeader{}) {
t.Skip("data is too short")
}
spec, err := loadRawSpec(bytes.NewReader(data), internal.NativeEndian, nil)
if err != nil {
if spec != nil {
t.Fatal("spec is not nil")
}
return
}
if spec == nil {
t.Fatal("spec is nil")
}
for _, typ := range spec.types {
fmt.Fprintf(io.Discard, "%+10v", typ)
}
})
}
func FuzzExtInfo(f *testing.F) {
var buf bytes.Buffer
err := binary.Write(&buf, internal.NativeEndian, &btfExtHeader{
Magic: btfMagic,
Version: 1,
HdrLen: uint32(binary.Size(btfExtHeader{})),
})
if err != nil {
f.Fatal(err)
}
f.Add(buf.Bytes(), []byte("\x00foo\x00barfoo\x00"))
emptySpec := newSpec()
f.Fuzz(func(t *testing.T, data, strings []byte) {
if len(data) < binary.Size(btfExtHeader{}) {
t.Skip("data is too short")
}
table, err := readStringTable(bytes.NewReader(strings), nil)
if err != nil {
t.Skip("invalid string table")
}
info, err := loadExtInfos(bytes.NewReader(data), internal.NativeEndian, emptySpec, table)
if err != nil {
if info != nil {
t.Fatal("info is not nil")
}
} else if info == nil {
t.Fatal("info is nil")
}
})
}
@@ -0,0 +1,287 @@
package btf
import (
"bytes"
"errors"
"fmt"
"math"
"os"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)
// Handle is a reference to BTF loaded into the kernel.
type Handle struct {
fd *sys.FD
// Size of the raw BTF in bytes.
size uint32
needsKernelBase bool
}
// NewHandle loads the contents of a [Builder] into the kernel.
//
// Returns an error wrapping ErrNotSupported if the kernel doesn't support BTF.
func NewHandle(b *Builder) (*Handle, error) {
small := getByteSlice()
defer putByteSlice(small)
buf, err := b.Marshal(*small, KernelMarshalOptions())
if err != nil {
return nil, fmt.Errorf("marshal BTF: %w", err)
}
return NewHandleFromRawBTF(buf)
}
// NewHandleFromRawBTF loads raw BTF into the kernel.
//
// Returns an error wrapping ErrNotSupported if the kernel doesn't support BTF.
func NewHandleFromRawBTF(btf []byte) (*Handle, error) {
if uint64(len(btf)) > math.MaxUint32 {
return nil, errors.New("BTF exceeds the maximum size")
}
attr := &sys.BtfLoadAttr{
Btf: sys.NewSlicePointer(btf),
BtfSize: uint32(len(btf)),
}
fd, err := sys.BtfLoad(attr)
if err == nil {
return &Handle{fd, attr.BtfSize, false}, nil
}
if err := haveBTF(); err != nil {
return nil, err
}
logBuf := make([]byte, 64*1024)
attr.BtfLogBuf = sys.NewSlicePointer(logBuf)
attr.BtfLogSize = uint32(len(logBuf))
attr.BtfLogLevel = 1
// Up until at least kernel 6.0, the BTF verifier does not return ENOSPC
// if there are other verification errors. ENOSPC is only returned when
// the BTF blob is correct, a log was requested, and the provided buffer
// is too small.
_, ve := sys.BtfLoad(attr)
return nil, internal.ErrorWithLog("load btf", err, logBuf, errors.Is(ve, unix.ENOSPC))
}
// NewHandleFromID returns the BTF handle for a given id.
//
// Prefer calling [ebpf.Program.Handle] or [ebpf.Map.Handle] if possible.
//
// 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 FD for ID %d: %w", id, err)
}
info, err := newHandleInfoFromFD(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return &Handle{fd, info.size, info.IsModule()}, nil
}
// Spec parses the kernel BTF into Go types.
//
// base must contain type information for vmlinux if the handle is for
// a kernel module. It may be nil otherwise.
func (h *Handle) Spec(base *Spec) (*Spec, error) {
var btfInfo sys.BtfInfo
btfBuffer := make([]byte, h.size)
btfInfo.Btf, btfInfo.BtfSize = sys.NewSlicePointerLen(btfBuffer)
if err := sys.ObjInfo(h.fd, &btfInfo); err != nil {
return nil, err
}
if h.needsKernelBase && base == nil {
return nil, fmt.Errorf("missing base types")
}
return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, base)
}
// Close destroys the handle.
//
// Subsequent calls to FD will return an invalid value.
func (h *Handle) Close() error {
if h == nil {
return nil
}
return h.fd.Close()
}
// FD returns the file descriptor for the handle.
func (h *Handle) FD() int {
return h.fd.Int()
}
// Info returns metadata about the handle.
func (h *Handle) Info() (*HandleInfo, error) {
return newHandleInfoFromFD(h.fd)
}
// HandleInfo describes a Handle.
type HandleInfo struct {
// ID of this handle in the kernel. The ID is only valid as long as the
// associated handle is kept alive.
ID ID
// Name is an identifying name for the BTF, currently only used by the
// kernel.
Name string
// IsKernel is true if the BTF originated with the kernel and not
// userspace.
IsKernel bool
// Size of the raw BTF in bytes.
size uint32
}
func newHandleInfoFromFD(fd *sys.FD) (*HandleInfo, 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, fmt.Errorf("get BTF info for fd %s: %w", fd, err)
}
if btfInfo.NameLen > 0 {
// NameLen doesn't account for the terminating NUL.
btfInfo.NameLen++
}
// Don't pull raw BTF by default, since it may be quite large.
btfSize := btfInfo.BtfSize
btfInfo.BtfSize = 0
nameBuffer := make([]byte, btfInfo.NameLen)
btfInfo.Name, btfInfo.NameLen = sys.NewSlicePointerLen(nameBuffer)
if err := sys.ObjInfo(fd, &btfInfo); err != nil {
return nil, err
}
return &HandleInfo{
ID: ID(btfInfo.Id),
Name: unix.ByteSliceToString(nameBuffer),
IsKernel: btfInfo.KernelBtf != 0,
size: btfSize,
}, nil
}
// IsVmlinux returns true if the BTF is for the kernel itself.
func (i *HandleInfo) IsVmlinux() bool {
return i.IsKernel && i.Name == "vmlinux"
}
// IsModule returns true if the BTF is for a kernel module.
func (i *HandleInfo) IsModule() bool {
return i.IsKernel && i.Name != "vmlinux"
}
// HandleIterator allows enumerating BTF blobs loaded into the kernel.
type HandleIterator struct {
// The ID of the current handle. Only valid after a call to Next.
ID ID
// The current Handle. Only valid until a call to Next.
// See Take if you want to retain the handle.
Handle *Handle
err error
}
// Next retrieves a handle for the next BTF object.
//
// Returns true if another BTF object was found. Call [HandleIterator.Err] after
// the function returns false.
func (it *HandleIterator) Next() bool {
id := it.ID
for {
attr := &sys.BtfGetNextIdAttr{Id: id}
err := sys.BtfGetNextId(attr)
if errors.Is(err, os.ErrNotExist) {
// There are no more BTF objects.
break
} else if err != nil {
it.err = fmt.Errorf("get next BTF ID: %w", err)
break
}
id = attr.NextId
handle, err := NewHandleFromID(id)
if errors.Is(err, os.ErrNotExist) {
// Try again with the next ID.
continue
} else if err != nil {
it.err = fmt.Errorf("retrieve handle for ID %d: %w", id, err)
break
}
it.Handle.Close()
it.ID, it.Handle = id, handle
return true
}
// No more handles or we encountered an error.
it.Handle.Close()
it.Handle = nil
return false
}
// Take the ownership of the current handle.
//
// It's the callers responsibility to close the handle.
func (it *HandleIterator) Take() *Handle {
handle := it.Handle
it.Handle = nil
return handle
}
// Err returns an error if iteration failed for some reason.
func (it *HandleIterator) Err() error {
return it.err
}
// FindHandle returns the first handle for which predicate returns true.
//
// Requires CAP_SYS_ADMIN.
//
// Returns an error wrapping ErrNotFound if predicate never returns true or if
// there is no BTF loaded into the kernel.
func FindHandle(predicate func(info *HandleInfo) bool) (*Handle, error) {
it := new(HandleIterator)
defer it.Handle.Close()
for it.Next() {
info, err := it.Handle.Info()
if err != nil {
return nil, fmt.Errorf("info for ID %d: %w", it.ID, err)
}
if predicate(info) {
return it.Take(), nil
}
}
if err := it.Err(); err != nil {
return nil, fmt.Errorf("iterate handles: %w", err)
}
return nil, fmt.Errorf("find handle: %w", ErrNotFound)
}
@@ -0,0 +1,99 @@
package btf_test
import (
"fmt"
"testing"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal/testutils"
)
func TestHandleIterator(t *testing.T) {
// There is no guarantee that there is a BTF ID allocated, but loading a module
// triggers loading vmlinux.
// See https://github.com/torvalds/linux/commit/5329722057d41aebc31e391907a501feaa42f7d9
testutils.SkipOnOldKernel(t, "5.11", "vmlinux BTF ID")
it := new(btf.HandleIterator)
defer it.Handle.Close()
if !it.Next() {
t.Fatalf("No BTF loaded")
}
if it.Handle == nil {
t.Fatal("Next doesn't assign handle")
}
prev := it.ID
for it.Next() {
// Iterate all loaded BTF.
if it.Handle == nil {
t.Fatal("Next doesn't assign handle")
}
if it.ID == prev {
t.Fatal("Iterator doesn't advance ID")
}
prev = it.ID
}
if err := it.Err(); err != nil {
t.Fatal("Iteration returned an error:", err)
}
if it.Handle != nil {
t.Fatal("Next doesn't clean up handle on last iteration")
}
if prev != it.ID {
t.Fatal("Next changes ID on last iteration")
}
}
func TestParseModuleSplitSpec(t *testing.T) {
// See TestNewHandleFromID for reasoning.
testutils.SkipOnOldKernel(t, "5.11", "vmlinux BTF ID")
module, err := btf.FindHandle(func(info *btf.HandleInfo) bool {
if info.IsModule() {
t.Log("Using module", info.Name)
return true
}
return false
})
if err != nil {
t.Fatal(err)
}
defer module.Close()
vmlinux, err := btf.FindHandle(func(info *btf.HandleInfo) bool {
return info.IsVmlinux()
})
if err != nil {
t.Fatal(err)
}
defer vmlinux.Close()
base, err := vmlinux.Spec(nil)
if err != nil {
t.Fatal(err)
}
_, err = module.Spec(base)
if err != nil {
t.Fatal("Parse module BTF:", err)
}
}
func ExampleHandleIterator() {
it := new(btf.HandleIterator)
defer it.Handle.Close()
for it.Next() {
info, err := it.Handle.Info()
if err != nil {
panic(err)
}
fmt.Printf("Found handle with ID %d and name %s\n", it.ID, info.Name)
}
if err := it.Err(); err != nil {
panic(err)
}
}
@@ -0,0 +1,543 @@
package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"sync"
"github.com/cilium/ebpf/internal"
"golang.org/x/exp/slices"
)
type MarshalOptions struct {
// Target byte order. Defaults to the system's native endianness.
Order binary.ByteOrder
// Remove function linkage information for compatibility with <5.6 kernels.
StripFuncLinkage bool
}
// KernelMarshalOptions will generate BTF suitable for the current kernel.
func KernelMarshalOptions() *MarshalOptions {
return &MarshalOptions{
Order: internal.NativeEndian,
StripFuncLinkage: haveFuncLinkage() != nil,
}
}
// encoder turns Types into raw BTF.
type encoder struct {
MarshalOptions
pending internal.Deque[Type]
buf *bytes.Buffer
strings *stringTableBuilder
ids map[Type]TypeID
lastID TypeID
}
var bufferPool = sync.Pool{
New: func() any {
buf := make([]byte, btfHeaderLen+128)
return &buf
},
}
func getByteSlice() *[]byte {
return bufferPool.Get().(*[]byte)
}
func putByteSlice(buf *[]byte) {
*buf = (*buf)[:0]
bufferPool.Put(buf)
}
// Builder turns Types into raw BTF.
//
// The default value may be used and represents an empty BTF blob. Void is
// added implicitly if necessary.
type Builder struct {
// Explicitly added types.
types []Type
// IDs for all added types which the user knows about.
stableIDs map[Type]TypeID
// Explicitly added strings.
strings *stringTableBuilder
}
// NewBuilder creates a Builder from a list of types.
//
// It is more efficient than calling [Add] individually.
//
// Returns an error if adding any of the types fails.
func NewBuilder(types []Type) (*Builder, error) {
b := &Builder{
make([]Type, 0, len(types)),
make(map[Type]TypeID, len(types)),
nil,
}
for _, typ := range types {
_, err := b.Add(typ)
if err != nil {
return nil, fmt.Errorf("add %s: %w", typ, err)
}
}
return b, nil
}
// Add a Type and allocate a stable ID for it.
//
// Adding the identical Type multiple times is valid and will return the same ID.
//
// See [Type] for details on identity.
func (b *Builder) Add(typ Type) (TypeID, error) {
if b.stableIDs == nil {
b.stableIDs = make(map[Type]TypeID)
}
if _, ok := typ.(*Void); ok {
// Equality is weird for void, since it is a zero sized type.
return 0, nil
}
if ds, ok := typ.(*Datasec); ok {
if err := datasecResolveWorkaround(b, ds); err != nil {
return 0, err
}
}
id, ok := b.stableIDs[typ]
if ok {
return id, nil
}
b.types = append(b.types, typ)
id = TypeID(len(b.types))
if int(id) != len(b.types) {
return 0, fmt.Errorf("no more type IDs")
}
b.stableIDs[typ] = id
return id, nil
}
// Marshal encodes all types in the Marshaler into BTF wire format.
//
// opts may be nil.
func (b *Builder) Marshal(buf []byte, opts *MarshalOptions) ([]byte, error) {
stb := b.strings
if stb == nil {
// Assume that most types are named. This makes encoding large BTF like
// vmlinux a lot cheaper.
stb = newStringTableBuilder(len(b.types))
} else {
// Avoid modifying the Builder's string table.
stb = b.strings.Copy()
}
if opts == nil {
opts = &MarshalOptions{Order: internal.NativeEndian}
}
// Reserve space for the BTF header.
buf = slices.Grow(buf, btfHeaderLen)[:btfHeaderLen]
w := internal.NewBuffer(buf)
defer internal.PutBuffer(w)
e := encoder{
MarshalOptions: *opts,
buf: w,
strings: stb,
lastID: TypeID(len(b.types)),
ids: make(map[Type]TypeID, len(b.types)),
}
// Ensure that types are marshaled in the exact order they were Add()ed.
// Otherwise the ID returned from Add() won't match.
e.pending.Grow(len(b.types))
for _, typ := range b.types {
e.pending.Push(typ)
e.ids[typ] = b.stableIDs[typ]
}
if err := e.deflatePending(); err != nil {
return nil, err
}
length := e.buf.Len()
typeLen := uint32(length - btfHeaderLen)
stringLen := e.strings.Length()
buf = e.strings.AppendEncoded(e.buf.Bytes())
// Fill out the header, and write it out.
header := &btfHeader{
Magic: btfMagic,
Version: 1,
Flags: 0,
HdrLen: uint32(btfHeaderLen),
TypeOff: 0,
TypeLen: typeLen,
StringOff: typeLen,
StringLen: uint32(stringLen),
}
err := binary.Write(sliceWriter(buf[:btfHeaderLen]), e.Order, header)
if err != nil {
return nil, fmt.Errorf("write header: %v", err)
}
return buf, nil
}
// addString adds a string to the resulting BTF.
//
// Adding the same string multiple times will return the same result.
//
// Returns an identifier into the string table or an error if the string
// contains invalid characters.
func (b *Builder) addString(str string) (uint32, error) {
if b.strings == nil {
b.strings = newStringTableBuilder(0)
}
return b.strings.Add(str)
}
func (e *encoder) allocateID(typ Type) error {
id := e.lastID + 1
if id < e.lastID {
return errors.New("type ID overflow")
}
e.pending.Push(typ)
e.ids[typ] = id
e.lastID = id
return nil
}
// id returns the ID for the given type or panics with an error.
func (e *encoder) id(typ Type) TypeID {
if _, ok := typ.(*Void); ok {
return 0
}
id, ok := e.ids[typ]
if !ok {
panic(fmt.Errorf("no ID for type %v", typ))
}
return id
}
func (e *encoder) deflatePending() error {
// Declare root outside of the loop to avoid repeated heap allocations.
var root Type
skip := func(t Type) (skip bool) {
if t == root {
// Force descending into the current root type even if it already
// has an ID. Otherwise we miss children of types that have their
// ID pre-allocated via Add.
return false
}
_, isVoid := t.(*Void)
_, alreadyEncoded := e.ids[t]
return isVoid || alreadyEncoded
}
for !e.pending.Empty() {
root = e.pending.Shift()
// Allocate IDs for all children of typ, including transitive dependencies.
iter := postorderTraversal(root, skip)
for iter.Next() {
if iter.Type == root {
// The iterator yields root at the end, do not allocate another ID.
break
}
if err := e.allocateID(iter.Type); err != nil {
return err
}
}
if err := e.deflateType(root); err != nil {
id := e.ids[root]
return fmt.Errorf("deflate %v with ID %d: %w", root, id, err)
}
}
return nil
}
func (e *encoder) deflateType(typ Type) (err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
panic(r)
}
}
}()
var raw rawType
raw.NameOff, err = e.strings.Add(typ.TypeName())
if err != nil {
return err
}
switch v := typ.(type) {
case *Void:
return errors.New("Void is implicit in BTF wire format")
case *Int:
raw.SetKind(kindInt)
raw.SetSize(v.Size)
var bi btfInt
bi.SetEncoding(v.Encoding)
// We need to set bits in addition to size, since btf_type_int_is_regular
// otherwise flags this as a bitfield.
bi.SetBits(byte(v.Size) * 8)
raw.data = bi
case *Pointer:
raw.SetKind(kindPointer)
raw.SetType(e.id(v.Target))
case *Array:
raw.SetKind(kindArray)
raw.data = &btfArray{
e.id(v.Type),
e.id(v.Index),
v.Nelems,
}
case *Struct:
raw.SetKind(kindStruct)
raw.SetSize(v.Size)
raw.data, err = e.convertMembers(&raw.btfType, v.Members)
case *Union:
raw.SetKind(kindUnion)
raw.SetSize(v.Size)
raw.data, err = e.convertMembers(&raw.btfType, v.Members)
case *Enum:
raw.SetSize(v.size())
raw.SetVlen(len(v.Values))
raw.SetSigned(v.Signed)
if v.has64BitValues() {
raw.SetKind(kindEnum64)
raw.data, err = e.deflateEnum64Values(v.Values)
} else {
raw.SetKind(kindEnum)
raw.data, err = e.deflateEnumValues(v.Values)
}
case *Fwd:
raw.SetKind(kindForward)
raw.SetFwdKind(v.Kind)
case *Typedef:
raw.SetKind(kindTypedef)
raw.SetType(e.id(v.Type))
case *Volatile:
raw.SetKind(kindVolatile)
raw.SetType(e.id(v.Type))
case *Const:
raw.SetKind(kindConst)
raw.SetType(e.id(v.Type))
case *Restrict:
raw.SetKind(kindRestrict)
raw.SetType(e.id(v.Type))
case *Func:
raw.SetKind(kindFunc)
raw.SetType(e.id(v.Type))
if !e.StripFuncLinkage {
raw.SetLinkage(v.Linkage)
}
case *FuncProto:
raw.SetKind(kindFuncProto)
raw.SetType(e.id(v.Return))
raw.SetVlen(len(v.Params))
raw.data, err = e.deflateFuncParams(v.Params)
case *Var:
raw.SetKind(kindVar)
raw.SetType(e.id(v.Type))
raw.data = btfVariable{uint32(v.Linkage)}
case *Datasec:
raw.SetKind(kindDatasec)
raw.SetSize(v.Size)
raw.SetVlen(len(v.Vars))
raw.data = e.deflateVarSecinfos(v.Vars)
case *Float:
raw.SetKind(kindFloat)
raw.SetSize(v.Size)
case *declTag:
raw.SetKind(kindDeclTag)
raw.SetType(e.id(v.Type))
raw.data = &btfDeclTag{uint32(v.Index)}
raw.NameOff, err = e.strings.Add(v.Value)
case *typeTag:
raw.SetKind(kindTypeTag)
raw.SetType(e.id(v.Type))
raw.NameOff, err = e.strings.Add(v.Value)
default:
return fmt.Errorf("don't know how to deflate %T", v)
}
if err != nil {
return err
}
return raw.Marshal(e.buf, e.Order)
}
func (e *encoder) convertMembers(header *btfType, members []Member) ([]btfMember, error) {
bms := make([]btfMember, 0, len(members))
isBitfield := false
for _, member := range members {
isBitfield = isBitfield || member.BitfieldSize > 0
offset := member.Offset
if isBitfield {
offset = member.BitfieldSize<<24 | (member.Offset & 0xffffff)
}
nameOff, err := e.strings.Add(member.Name)
if err != nil {
return nil, err
}
bms = append(bms, btfMember{
nameOff,
e.id(member.Type),
uint32(offset),
})
}
header.SetVlen(len(members))
header.SetBitfield(isBitfield)
return bms, nil
}
func (e *encoder) deflateEnumValues(values []EnumValue) ([]btfEnum, error) {
bes := make([]btfEnum, 0, len(values))
for _, value := range values {
nameOff, err := e.strings.Add(value.Name)
if err != nil {
return nil, err
}
if value.Value > math.MaxUint32 {
return nil, fmt.Errorf("value of enum %q exceeds 32 bits", value.Name)
}
bes = append(bes, btfEnum{
nameOff,
uint32(value.Value),
})
}
return bes, nil
}
func (e *encoder) deflateEnum64Values(values []EnumValue) ([]btfEnum64, error) {
bes := make([]btfEnum64, 0, len(values))
for _, value := range values {
nameOff, err := e.strings.Add(value.Name)
if err != nil {
return nil, err
}
bes = append(bes, btfEnum64{
nameOff,
uint32(value.Value),
uint32(value.Value >> 32),
})
}
return bes, nil
}
func (e *encoder) deflateFuncParams(params []FuncParam) ([]btfParam, error) {
bps := make([]btfParam, 0, len(params))
for _, param := range params {
nameOff, err := e.strings.Add(param.Name)
if err != nil {
return nil, err
}
bps = append(bps, btfParam{
nameOff,
e.id(param.Type),
})
}
return bps, nil
}
func (e *encoder) deflateVarSecinfos(vars []VarSecinfo) []btfVarSecinfo {
vsis := make([]btfVarSecinfo, 0, len(vars))
for _, v := range vars {
vsis = append(vsis, btfVarSecinfo{
e.id(v.Type),
v.Offset,
v.Size,
})
}
return vsis
}
// MarshalMapKV creates a BTF object containing a map key and value.
//
// The function is intended for the use of the ebpf package and may be removed
// at any point in time.
func MarshalMapKV(key, value Type) (_ *Handle, keyID, valueID TypeID, err error) {
var b Builder
if key != nil {
keyID, err = b.Add(key)
if err != nil {
return nil, 0, 0, fmt.Errorf("add key type: %w", err)
}
}
if value != nil {
valueID, err = b.Add(value)
if err != nil {
return nil, 0, 0, fmt.Errorf("add value type: %w", err)
}
}
handle, err := NewHandle(&b)
if err != nil {
// Check for 'full' map BTF support, since kernels between 4.18 and 5.2
// already support BTF blobs for maps without Var or Datasec just fine.
if err := haveMapBTF(); err != nil {
return nil, 0, 0, err
}
}
return handle, keyID, valueID, err
}
@@ -0,0 +1,163 @@
package btf
import (
"bytes"
"encoding/binary"
"math"
"testing"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
"github.com/google/go-cmp/cmp"
qt "github.com/frankban/quicktest"
)
func TestBuilderMarshal(t *testing.T) {
typ := &Int{
Name: "foo",
Size: 2,
Encoding: Signed | Char,
}
want := []Type{
(*Void)(nil),
typ,
&Pointer{typ},
&Typedef{"baz", typ},
}
b, err := NewBuilder(want)
qt.Assert(t, err, qt.IsNil)
cpy := *b
buf, err := b.Marshal(nil, &MarshalOptions{Order: internal.NativeEndian})
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, b, qt.CmpEquals(cmp.AllowUnexported(*b)), &cpy, qt.Commentf("Marshaling should not change Builder state"))
have, err := loadRawSpec(bytes.NewReader(buf), internal.NativeEndian, nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("Couldn't parse BTF"))
qt.Assert(t, have.types, qt.DeepEquals, want)
}
func TestBuilderAdd(t *testing.T) {
i := &Int{
Name: "foo",
Size: 2,
Encoding: Signed | Char,
}
pi := &Pointer{i}
var b Builder
id, err := b.Add(pi)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(1), qt.Commentf("First non-void type doesn't get id 1"))
id, err = b.Add(pi)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(1))
id, err = b.Add(i)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(2), qt.Commentf("Second type doesn't get id 2"))
id, err = b.Add(i)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(2), qt.Commentf("Adding a type twice returns different ids"))
id, err = b.Add(&Typedef{"baz", i})
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, id, qt.Equals, TypeID(3))
}
func TestRoundtripVMlinux(t *testing.T) {
types := vmlinuxSpec(t).types
// Randomize the order to force different permutations of walking the type
// graph. Keep Void at index 0.
testutils.Rand().Shuffle(len(types[1:]), func(i, j int) {
types[i+1], types[j+1] = types[j+1], types[i+1]
})
// Skip per CPU datasec, see https://github.com/cilium/ebpf/issues/921
for i, typ := range types {
if ds, ok := typ.(*Datasec); ok && ds.Name == ".data..percpu" {
types[i] = types[len(types)-1]
types = types[:len(types)-1]
break
}
}
seen := make(map[Type]bool)
limitTypes:
for i, typ := range types {
iter := postorderTraversal(typ, func(t Type) (skip bool) {
return seen[t]
})
for iter.Next() {
seen[iter.Type] = true
}
if len(seen) >= math.MaxInt16 {
// IDs exceeding math.MaxUint16 can trigger a bug when loading BTF.
// This can be removed once the patch lands.
// See https://lore.kernel.org/bpf/20220909092107.3035-1-oss@lmb.io/
types = types[:i]
break limitTypes
}
}
buf := marshalNativeEndian(t, types)
rebuilt, err := loadRawSpec(bytes.NewReader(buf), binary.LittleEndian, nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("round tripping BTF failed"))
if n := len(rebuilt.types); n > math.MaxUint16 {
t.Logf("Rebuilt BTF contains %d types which exceeds uint16, test may fail on older kernels", n)
}
h, err := NewHandleFromRawBTF(buf)
testutils.SkipIfNotSupported(t, err)
qt.Assert(t, err, qt.IsNil, qt.Commentf("loading rebuilt BTF failed"))
h.Close()
}
func BenchmarkMarshaler(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
types := spec.types[:100]
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var b Builder
for _, typ := range types {
_, _ = b.Add(typ)
}
_, _ = b.Marshal(nil, nil)
}
}
func BenchmarkBuildVmlinux(b *testing.B) {
types := vmlinuxTestdataSpec(b).types
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var b Builder
for _, typ := range types {
_, _ = b.Add(typ)
}
_, _ = b.Marshal(nil, nil)
}
}
func marshalNativeEndian(tb testing.TB, types []Type) []byte {
tb.Helper()
b, err := NewBuilder(types)
qt.Assert(tb, err, qt.IsNil)
buf, err := b.Marshal(nil, nil)
qt.Assert(tb, err, qt.IsNil)
return buf
}
@@ -0,0 +1,214 @@
package btf
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"golang.org/x/exp/maps"
)
type stringTable struct {
base *stringTable
offsets []uint32
strings []string
}
// sizedReader is implemented by bytes.Reader, io.SectionReader, strings.Reader, etc.
type sizedReader interface {
io.Reader
Size() int64
}
func readStringTable(r sizedReader, base *stringTable) (*stringTable, error) {
// When parsing split BTF's string table, the first entry offset is derived
// from the last entry offset of the base BTF.
firstStringOffset := uint32(0)
if base != nil {
idx := len(base.offsets) - 1
firstStringOffset = base.offsets[idx] + uint32(len(base.strings[idx])) + 1
}
// Derived from vmlinux BTF.
const averageStringLength = 16
n := int(r.Size() / averageStringLength)
offsets := make([]uint32, 0, n)
strings := make([]string, 0, n)
offset := firstStringOffset
scanner := bufio.NewScanner(r)
scanner.Split(splitNull)
for scanner.Scan() {
str := scanner.Text()
offsets = append(offsets, offset)
strings = append(strings, str)
offset += uint32(len(str)) + 1
}
if err := scanner.Err(); err != nil {
return nil, err
}
if len(strings) == 0 {
return nil, errors.New("string table is empty")
}
if firstStringOffset == 0 && strings[0] != "" {
return nil, errors.New("first item in string table is non-empty")
}
return &stringTable{base, offsets, strings}, nil
}
func splitNull(data []byte, atEOF bool) (advance int, token []byte, err error) {
i := bytes.IndexByte(data, 0)
if i == -1 {
if atEOF && len(data) > 0 {
return 0, nil, errors.New("string table isn't null terminated")
}
return 0, nil, nil
}
return i + 1, data[:i], nil
}
func (st *stringTable) Lookup(offset uint32) (string, error) {
if st.base != nil && offset <= st.base.offsets[len(st.base.offsets)-1] {
return st.base.lookup(offset)
}
return st.lookup(offset)
}
func (st *stringTable) lookup(offset uint32) (string, error) {
i := search(st.offsets, offset)
if i == len(st.offsets) || st.offsets[i] != offset {
return "", fmt.Errorf("offset %d isn't start of a string", offset)
}
return st.strings[i], nil
}
func (st *stringTable) Marshal(w io.Writer) error {
for _, str := range st.strings {
_, err := io.WriteString(w, str)
if err != nil {
return err
}
_, err = w.Write([]byte{0})
if err != nil {
return err
}
}
return nil
}
// Num returns the number of strings in the table.
func (st *stringTable) Num() int {
return len(st.strings)
}
// search is a copy of sort.Search specialised for uint32.
//
// Licensed under https://go.dev/LICENSE
func search(ints []uint32, needle uint32) int {
// Define f(-1) == false and f(n) == true.
// Invariant: f(i-1) == false, f(j) == true.
i, j := 0, len(ints)
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if !(ints[h] >= needle) {
i = h + 1 // preserves f(i-1) == false
} else {
j = h // preserves f(j) == true
}
}
// i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
return i
}
// stringTableBuilder builds BTF string tables.
type stringTableBuilder struct {
length uint32
strings map[string]uint32
}
// newStringTableBuilder creates a builder with the given capacity.
//
// capacity may be zero.
func newStringTableBuilder(capacity int) *stringTableBuilder {
var stb stringTableBuilder
if capacity == 0 {
// Use the runtime's small default size.
stb.strings = make(map[string]uint32)
} else {
stb.strings = make(map[string]uint32, capacity)
}
// Ensure that the empty string is at index 0.
stb.append("")
return &stb
}
// Add a string to the table.
//
// Adding the same string multiple times will only store it once.
func (stb *stringTableBuilder) Add(str string) (uint32, error) {
if strings.IndexByte(str, 0) != -1 {
return 0, fmt.Errorf("string contains null: %q", str)
}
offset, ok := stb.strings[str]
if ok {
return offset, nil
}
return stb.append(str), nil
}
func (stb *stringTableBuilder) append(str string) uint32 {
offset := stb.length
stb.length += uint32(len(str)) + 1
stb.strings[str] = offset
return offset
}
// Lookup finds the offset of a string in the table.
//
// Returns an error if str hasn't been added yet.
func (stb *stringTableBuilder) Lookup(str string) (uint32, error) {
offset, ok := stb.strings[str]
if !ok {
return 0, fmt.Errorf("string %q is not in table", str)
}
return offset, nil
}
// Length returns the length in bytes.
func (stb *stringTableBuilder) Length() int {
return int(stb.length)
}
// AppendEncoded appends the string table to the end of the provided buffer.
func (stb *stringTableBuilder) AppendEncoded(buf []byte) []byte {
n := len(buf)
buf = append(buf, make([]byte, stb.Length())...)
strings := buf[n:]
for str, offset := range stb.strings {
copy(strings[offset:], str)
}
return buf
}
// Copy the string table builder.
func (stb *stringTableBuilder) Copy() *stringTableBuilder {
return &stringTableBuilder{
stb.length,
maps.Clone(stb.strings),
}
}
@@ -0,0 +1,110 @@
package btf
import (
"bytes"
"strings"
"testing"
qt "github.com/frankban/quicktest"
)
func TestStringTable(t *testing.T) {
const in = "\x00one\x00two\x00"
const splitIn = "three\x00four\x00"
st, err := readStringTable(strings.NewReader(in), nil)
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
if err := st.Marshal(&buf); err != nil {
t.Fatal("Can't marshal string table:", err)
}
if !bytes.Equal([]byte(in), buf.Bytes()) {
t.Error("String table doesn't match input")
}
// Parse string table of split BTF
split, err := readStringTable(strings.NewReader(splitIn), st)
if err != nil {
t.Fatal(err)
}
testcases := []struct {
offset uint32
want string
}{
{0, ""},
{1, "one"},
{5, "two"},
{9, "three"},
{15, "four"},
}
for _, tc := range testcases {
have, err := split.Lookup(tc.offset)
if err != nil {
t.Errorf("Offset %d: %s", tc.offset, err)
continue
}
if have != tc.want {
t.Errorf("Offset %d: want %s but have %s", tc.offset, tc.want, have)
}
}
if _, err := st.Lookup(2); err == nil {
t.Error("No error when using offset pointing into middle of string")
}
// Make sure we reject bogus tables
_, err = readStringTable(strings.NewReader("\x00one"), nil)
if err == nil {
t.Fatal("Accepted non-terminated string")
}
_, err = readStringTable(strings.NewReader("one\x00"), nil)
if err == nil {
t.Fatal("Accepted non-empty first item")
}
}
func TestStringTableBuilder(t *testing.T) {
stb := newStringTableBuilder(0)
_, err := readStringTable(bytes.NewReader(stb.AppendEncoded(nil)), nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("Can't parse string table"))
_, err = stb.Add("foo\x00bar")
qt.Assert(t, err, qt.IsNotNil)
empty, err := stb.Add("")
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, empty, qt.Equals, uint32(0), qt.Commentf("The empty string is not at index 0"))
foo1, _ := stb.Add("foo")
foo2, _ := stb.Add("foo")
qt.Assert(t, foo1, qt.Equals, foo2, qt.Commentf("Adding the same string returns different offsets"))
table := stb.AppendEncoded(nil)
if n := bytes.Count(table, []byte("foo")); n != 1 {
t.Fatalf("Marshalled string table contains foo %d times instead of once", n)
}
_, err = readStringTable(bytes.NewReader(table), nil)
qt.Assert(t, err, qt.IsNil, qt.Commentf("Can't parse string table"))
}
func newStringTable(strings ...string) *stringTable {
offsets := make([]uint32, len(strings))
var offset uint32
for i, str := range strings {
offsets[i] = offset
offset += uint32(len(str)) + 1 // account for NUL
}
return &stringTable{nil, offsets, strings}
}
@@ -0,0 +1,444 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_CORE_READ_H__
#define __BPF_CORE_READ_H__
/*
* enum bpf_field_info_kind is passed as a second argument into
* __builtin_preserve_field_info() built-in to get a specific aspect of
* a field, captured as a first argument. __builtin_preserve_field_info(field,
* info_kind) returns __u32 integer and produces BTF field relocation, which
* is understood and processed by libbpf during BPF object loading. See
* selftests/bpf for examples.
*/
enum bpf_field_info_kind {
BPF_FIELD_BYTE_OFFSET = 0, /* field byte offset */
BPF_FIELD_BYTE_SIZE = 1,
BPF_FIELD_EXISTS = 2, /* field existence in target kernel */
BPF_FIELD_SIGNED = 3,
BPF_FIELD_LSHIFT_U64 = 4,
BPF_FIELD_RSHIFT_U64 = 5,
};
/* second argument to __builtin_btf_type_id() built-in */
enum bpf_type_id_kind {
BPF_TYPE_ID_LOCAL = 0, /* BTF type ID in local program */
BPF_TYPE_ID_TARGET = 1, /* BTF type ID in target kernel */
};
/* second argument to __builtin_preserve_type_info() built-in */
enum bpf_type_info_kind {
BPF_TYPE_EXISTS = 0, /* type existence in target kernel */
BPF_TYPE_SIZE = 1, /* type size in target kernel */
};
/* second argument to __builtin_preserve_enum_value() built-in */
enum bpf_enum_value_kind {
BPF_ENUMVAL_EXISTS = 0, /* enum value existence in kernel */
BPF_ENUMVAL_VALUE = 1, /* enum value value relocation */
};
#define __CORE_RELO(src, field, info) \
__builtin_preserve_field_info((src)->field, BPF_FIELD_##info)
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \
bpf_probe_read_kernel( \
(void *)dst, \
__CORE_RELO(src, fld, BYTE_SIZE), \
(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#else
/* semantics of LSHIFT_64 assumes loading values into low-ordered bytes, so
* for big-endian we need to adjust destination pointer accordingly, based on
* field byte size
*/
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \
bpf_probe_read_kernel( \
(void *)dst + (8 - __CORE_RELO(src, fld, BYTE_SIZE)), \
__CORE_RELO(src, fld, BYTE_SIZE), \
(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#endif
/*
* Extract bitfield, identified by s->field, and return its value as u64.
* All this is done in relocatable manner, so bitfield changes such as
* signedness, bit size, offset changes, this will be handled automatically.
* This version of macro is using bpf_probe_read_kernel() to read underlying
* integer storage. Macro functions as an expression and its return type is
* bpf_probe_read_kernel()'s return value: 0, on success, <0 on error.
*/
#define BPF_CORE_READ_BITFIELD_PROBED(s, field) ({ \
unsigned long long val = 0; \
\
__CORE_BITFIELD_PROBE_READ(&val, s, field); \
val <<= __CORE_RELO(s, field, LSHIFT_U64); \
if (__CORE_RELO(s, field, SIGNED)) \
val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \
else \
val = val >> __CORE_RELO(s, field, RSHIFT_U64); \
val; \
})
/*
* Extract bitfield, identified by s->field, and return its value as u64.
* This version of macro is using direct memory reads and should be used from
* BPF program types that support such functionality (e.g., typed raw
* tracepoints).
*/
#define BPF_CORE_READ_BITFIELD(s, field) ({ \
const void *p = (const void *)s + __CORE_RELO(s, field, BYTE_OFFSET); \
unsigned long long val; \
\
/* This is a so-called barrier_var() operation that makes specified \
* variable "a black box" for optimizing compiler. \
* It forces compiler to perform BYTE_OFFSET relocation on p and use \
* its calculated value in the switch below, instead of applying \
* the same relocation 4 times for each individual memory load. \
*/ \
asm volatile("" : "=r"(p) : "0"(p)); \
\
switch (__CORE_RELO(s, field, BYTE_SIZE)) { \
case 1: val = *(const unsigned char *)p; break; \
case 2: val = *(const unsigned short *)p; break; \
case 4: val = *(const unsigned int *)p; break; \
case 8: val = *(const unsigned long long *)p; break; \
} \
val <<= __CORE_RELO(s, field, LSHIFT_U64); \
if (__CORE_RELO(s, field, SIGNED)) \
val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \
else \
val = val >> __CORE_RELO(s, field, RSHIFT_U64); \
val; \
})
/*
* Convenience macro to check that field actually exists in target kernel's.
* Returns:
* 1, if matching field is present in target kernel;
* 0, if no matching field found.
*/
#define bpf_core_field_exists(field) \
__builtin_preserve_field_info(field, BPF_FIELD_EXISTS)
/*
* Convenience macro to get the byte size of a field. Works for integers,
* struct/unions, pointers, arrays, and enums.
*/
#define bpf_core_field_size(field) \
__builtin_preserve_field_info(field, BPF_FIELD_BYTE_SIZE)
/*
* Convenience macro to get BTF type ID of a specified type, using a local BTF
* information. Return 32-bit unsigned integer with type ID from program's own
* BTF. Always succeeds.
*/
#define bpf_core_type_id_local(type) \
__builtin_btf_type_id(*(typeof(type) *)0, BPF_TYPE_ID_LOCAL)
/*
* Convenience macro to get BTF type ID of a target kernel's type that matches
* specified local type.
* Returns:
* - valid 32-bit unsigned type ID in kernel BTF;
* - 0, if no matching type was found in a target kernel BTF.
*/
#define bpf_core_type_id_kernel(type) \
__builtin_btf_type_id(*(typeof(type) *)0, BPF_TYPE_ID_TARGET)
/*
* Convenience macro to check that provided named type
* (struct/union/enum/typedef) exists in a target kernel.
* Returns:
* 1, if such type is present in target kernel's BTF;
* 0, if no matching type is found.
*/
#define bpf_core_type_exists(type) \
__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_EXISTS)
/*
* Convenience macro to get the byte size of a provided named type
* (struct/union/enum/typedef) in a target kernel.
* Returns:
* >= 0 size (in bytes), if type is present in target kernel's BTF;
* 0, if no matching type is found.
*/
#define bpf_core_type_size(type) \
__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_SIZE)
/*
* Convenience macro to check that provided enumerator value is defined in
* a target kernel.
* Returns:
* 1, if specified enum type and its enumerator value are present in target
* kernel's BTF;
* 0, if no matching enum and/or enum value within that enum is found.
*/
#define bpf_core_enum_value_exists(enum_type, enum_value) \
__builtin_preserve_enum_value(*(typeof(enum_type) *)enum_value, BPF_ENUMVAL_EXISTS)
/*
* Convenience macro to get the integer value of an enumerator value in
* a target kernel.
* Returns:
* 64-bit value, if specified enum type and its enumerator value are
* present in target kernel's BTF;
* 0, if no matching enum and/or enum value within that enum is found.
*/
#define bpf_core_enum_value(enum_type, enum_value) \
__builtin_preserve_enum_value(*(typeof(enum_type) *)enum_value, BPF_ENUMVAL_VALUE)
/*
* bpf_core_read() abstracts away bpf_probe_read_kernel() call and captures
* offset relocation for source address using __builtin_preserve_access_index()
* built-in, provided by Clang.
*
* __builtin_preserve_access_index() takes as an argument an expression of
* taking an address of a field within struct/union. It makes compiler emit
* a relocation, which records BTF type ID describing root struct/union and an
* accessor string which describes exact embedded field that was used to take
* an address. See detailed description of this relocation format and
* semantics in comments to struct bpf_field_reloc in libbpf_internal.h.
*
* This relocation allows libbpf to adjust BPF instruction to use correct
* actual field offset, based on target kernel BTF type that matches original
* (local) BTF, used to record relocation.
*/
#define bpf_core_read(dst, sz, src) \
bpf_probe_read_kernel(dst, sz, (const void *)__builtin_preserve_access_index(src))
/* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use. */
#define bpf_core_read_user(dst, sz, src) \
bpf_probe_read_user(dst, sz, (const void *)__builtin_preserve_access_index(src))
/*
* bpf_core_read_str() is a thin wrapper around bpf_probe_read_str()
* additionally emitting BPF CO-RE field relocation for specified source
* argument.
*/
#define bpf_core_read_str(dst, sz, src) \
bpf_probe_read_kernel_str(dst, sz, (const void *)__builtin_preserve_access_index(src))
/* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use. */
#define bpf_core_read_user_str(dst, sz, src) \
bpf_probe_read_user_str(dst, sz, (const void *)__builtin_preserve_access_index(src))
#define ___concat(a, b) a ## b
#define ___apply(fn, n) ___concat(fn, n)
#define ___nth(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, __11, N, ...) N
/*
* return number of provided arguments; used for switch-based variadic macro
* definitions (see ___last, ___arrow, etc below)
*/
#define ___narg(...) ___nth(_, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
/*
* return 0 if no arguments are passed, N - otherwise; used for
* recursively-defined macros to specify termination (0) case, and generic
* (N) case (e.g., ___read_ptrs, ___core_read)
*/
#define ___empty(...) ___nth(_, ##__VA_ARGS__, N, N, N, N, N, N, N, N, N, N, 0)
#define ___last1(x) x
#define ___last2(a, x) x
#define ___last3(a, b, x) x
#define ___last4(a, b, c, x) x
#define ___last5(a, b, c, d, x) x
#define ___last6(a, b, c, d, e, x) x
#define ___last7(a, b, c, d, e, f, x) x
#define ___last8(a, b, c, d, e, f, g, x) x
#define ___last9(a, b, c, d, e, f, g, h, x) x
#define ___last10(a, b, c, d, e, f, g, h, i, x) x
#define ___last(...) ___apply(___last, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___nolast2(a, _) a
#define ___nolast3(a, b, _) a, b
#define ___nolast4(a, b, c, _) a, b, c
#define ___nolast5(a, b, c, d, _) a, b, c, d
#define ___nolast6(a, b, c, d, e, _) a, b, c, d, e
#define ___nolast7(a, b, c, d, e, f, _) a, b, c, d, e, f
#define ___nolast8(a, b, c, d, e, f, g, _) a, b, c, d, e, f, g
#define ___nolast9(a, b, c, d, e, f, g, h, _) a, b, c, d, e, f, g, h
#define ___nolast10(a, b, c, d, e, f, g, h, i, _) a, b, c, d, e, f, g, h, i
#define ___nolast(...) ___apply(___nolast, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___arrow1(a) a
#define ___arrow2(a, b) a->b
#define ___arrow3(a, b, c) a->b->c
#define ___arrow4(a, b, c, d) a->b->c->d
#define ___arrow5(a, b, c, d, e) a->b->c->d->e
#define ___arrow6(a, b, c, d, e, f) a->b->c->d->e->f
#define ___arrow7(a, b, c, d, e, f, g) a->b->c->d->e->f->g
#define ___arrow8(a, b, c, d, e, f, g, h) a->b->c->d->e->f->g->h
#define ___arrow9(a, b, c, d, e, f, g, h, i) a->b->c->d->e->f->g->h->i
#define ___arrow10(a, b, c, d, e, f, g, h, i, j) a->b->c->d->e->f->g->h->i->j
#define ___arrow(...) ___apply(___arrow, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___type(...) typeof(___arrow(__VA_ARGS__))
#define ___read(read_fn, dst, src_type, src, accessor) \
read_fn((void *)(dst), sizeof(*(dst)), &((src_type)(src))->accessor)
/* "recursively" read a sequence of inner pointers using local __t var */
#define ___rd_first(fn, src, a) ___read(fn, &__t, ___type(src), src, a);
#define ___rd_last(fn, ...) \
___read(fn, &__t, ___type(___nolast(__VA_ARGS__)), __t, ___last(__VA_ARGS__));
#define ___rd_p1(fn, ...) const void *__t; ___rd_first(fn, __VA_ARGS__)
#define ___rd_p2(fn, ...) ___rd_p1(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p3(fn, ...) ___rd_p2(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p4(fn, ...) ___rd_p3(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p5(fn, ...) ___rd_p4(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p6(fn, ...) ___rd_p5(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p7(fn, ...) ___rd_p6(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p8(fn, ...) ___rd_p7(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p9(fn, ...) ___rd_p8(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___read_ptrs(fn, src, ...) \
___apply(___rd_p, ___narg(__VA_ARGS__))(fn, src, __VA_ARGS__)
#define ___core_read0(fn, fn_ptr, dst, src, a) \
___read(fn, dst, ___type(src), src, a);
#define ___core_readN(fn, fn_ptr, dst, src, ...) \
___read_ptrs(fn_ptr, src, ___nolast(__VA_ARGS__)) \
___read(fn, dst, ___type(src, ___nolast(__VA_ARGS__)), __t, \
___last(__VA_ARGS__));
#define ___core_read(fn, fn_ptr, dst, src, a, ...) \
___apply(___core_read, ___empty(__VA_ARGS__))(fn, fn_ptr, dst, \
src, a, ##__VA_ARGS__)
/*
* BPF_CORE_READ_INTO() is a more performance-conscious variant of
* BPF_CORE_READ(), in which final field is read into user-provided storage.
* See BPF_CORE_READ() below for more details on general usage.
*/
#define BPF_CORE_READ_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read, bpf_core_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Variant of BPF_CORE_READ_INTO() for reading from user-space memory.
*
* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use.
*/
#define BPF_CORE_READ_USER_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_user, bpf_core_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_INTO() */
#define BPF_PROBE_READ_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read, bpf_probe_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_USER_INTO().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_user, bpf_probe_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* BPF_CORE_READ_STR_INTO() does same "pointer chasing" as
* BPF_CORE_READ() for intermediate pointers, but then executes (and returns
* corresponding error code) bpf_core_read_str() for final string read.
*/
#define BPF_CORE_READ_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_str, bpf_core_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Variant of BPF_CORE_READ_STR_INTO() for reading from user-space memory.
*
* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use.
*/
#define BPF_CORE_READ_USER_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_user_str, bpf_core_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_STR_INTO() */
#define BPF_PROBE_READ_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_str, bpf_probe_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Non-CO-RE variant of BPF_CORE_READ_USER_STR_INTO().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_user_str, bpf_probe_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* BPF_CORE_READ() is used to simplify BPF CO-RE relocatable read, especially
* when there are few pointer chasing steps.
* E.g., what in non-BPF world (or in BPF w/ BCC) would be something like:
* int x = s->a.b.c->d.e->f->g;
* can be succinctly achieved using BPF_CORE_READ as:
* int x = BPF_CORE_READ(s, a.b.c, d.e, f, g);
*
* BPF_CORE_READ will decompose above statement into 4 bpf_core_read (BPF
* CO-RE relocatable bpf_probe_read_kernel() wrapper) calls, logically
* equivalent to:
* 1. const void *__t = s->a.b.c;
* 2. __t = __t->d.e;
* 3. __t = __t->f;
* 4. return __t->g;
*
* Equivalence is logical, because there is a heavy type casting/preservation
* involved, as well as all the reads are happening through
* bpf_probe_read_kernel() calls using __builtin_preserve_access_index() to
* emit CO-RE relocations.
*
* N.B. Only up to 9 "field accessors" are supported, which should be more
* than enough for any practical purpose.
*/
#define BPF_CORE_READ(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_CORE_READ_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/*
* Variant of BPF_CORE_READ() for reading from user-space memory.
*
* NOTE: all the source types involved are still *kernel types* and need to
* exist in kernel (or kernel module) BTF, otherwise CO-RE relocation will
* fail. Custom user types are not relocatable with CO-RE.
* The typical situation in which BPF_CORE_READ_USER() might be used is to
* read kernel UAPI types from the user-space memory passed in as a syscall
* input argument.
*/
#define BPF_CORE_READ_USER(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_CORE_READ_USER_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/* Non-CO-RE variant of BPF_CORE_READ() */
#define BPF_PROBE_READ(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_PROBE_READ_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/*
* Non-CO-RE variant of BPF_CORE_READ_USER().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_PROBE_READ_USER_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
#endif
@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("\x9f\xeb\x01\x00\x1c\x00\x00\x00\x00\x00\x00\x00000\x10\x00\x00\x00\x000000\xf3\xff\xff\xff0\x00\x00\x00")
[]byte("0")
@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("\x9f\xeb\x01\x00\x18\x00\x00\x00\x00\x00\x00\x000000000000000\x00\x00\x000000")
[]byte("0")
@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("\x9f\xeb\x01\x00\x1c\x00\x00\x00\x00\x00\x00\x000000\xe8\xff\xff\xff000000000\x00\x00\x00")
[]byte("0")
@@ -0,0 +1,268 @@
#include "../../testdata/common.h"
#include "bpf_core_read.h"
enum e {
// clang-12 doesn't allow enum relocations with zero value.
// See https://reviews.llvm.org/D97659
ONE = 1,
TWO,
};
typedef enum e e_t;
struct s {
int _1;
char _2;
unsigned int _3;
};
typedef struct s s_t;
union u {
int *_1;
char *_2;
unsigned int *_3;
};
typedef union u u_t;
#define local_id_zero(expr) \
({ \
if (bpf_core_type_id_local(expr) != 0) { \
return __LINE__; \
} \
})
#define local_id_not_zero(expr) \
({ \
if (bpf_core_type_id_local(expr) == 0) { \
return __LINE__; \
} \
})
#define target_and_local_id_match(expr) \
({ \
if (bpf_core_type_id_kernel(expr) != bpf_core_type_id_local(expr)) { \
return __LINE__; \
} \
})
__section("socket_filter/type_ids") int type_ids() {
local_id_not_zero(int);
local_id_not_zero(struct { int frob; });
local_id_not_zero(enum {FRAP});
local_id_not_zero(union { char bar; });
local_id_not_zero(struct s);
local_id_not_zero(s_t);
local_id_not_zero(const s_t);
local_id_not_zero(volatile s_t);
local_id_not_zero(enum e);
local_id_not_zero(e_t);
local_id_not_zero(const e_t);
local_id_not_zero(volatile e_t);
local_id_not_zero(union u);
local_id_not_zero(u_t);
local_id_not_zero(const u_t);
local_id_not_zero(volatile u_t);
// Qualifiers on types crash clang.
target_and_local_id_match(struct s);
target_and_local_id_match(s_t);
// target_and_local_id_match(const s_t);
// target_and_local_id_match(volatile s_t);
target_and_local_id_match(enum e);
target_and_local_id_match(e_t);
// target_and_local_id_match(const e_t);
// target_and_local_id_match(volatile e_t);
target_and_local_id_match(union u);
target_and_local_id_match(u_t);
// target_and_local_id_match(const u_t);
// target_and_local_id_match(volatile u_t);
return 0;
}
#define type_exists(expr) \
({ \
if (!bpf_core_type_exists(expr)) { \
return __LINE__; \
} \
})
#define type_size_matches(expr) \
({ \
if (bpf_core_type_size(expr) != sizeof(expr)) { \
return __LINE__; \
} \
})
__section("socket_filter/types") int types() {
type_exists(struct s);
type_exists(s_t);
type_exists(const s_t);
type_exists(volatile s_t);
type_exists(enum e);
type_exists(e_t);
type_exists(const e_t);
type_exists(volatile e_t);
type_exists(union u);
type_exists(u_t);
type_exists(const u_t);
type_exists(volatile u_t);
// TODO: Check non-existence.
type_size_matches(struct s);
type_size_matches(s_t);
type_size_matches(const s_t);
type_size_matches(volatile s_t);
type_size_matches(enum e);
type_size_matches(e_t);
type_size_matches(const e_t);
type_size_matches(volatile e_t);
type_size_matches(union u);
type_size_matches(u_t);
type_size_matches(const u_t);
type_size_matches(volatile u_t);
return 0;
}
#define enum_value_exists(t, v) \
({ \
if (!bpf_core_enum_value_exists(t, v)) { \
return __LINE__; \
} \
})
#define enum_value_matches(t, v) \
({ \
if (v != bpf_core_enum_value(t, v)) { \
return __LINE__; \
} \
})
__section("socket_filter/enums") int enums() {
enum_value_exists(enum e, ONE);
enum_value_exists(volatile enum e, ONE);
enum_value_exists(const enum e, ONE);
enum_value_exists(e_t, TWO);
// TODO: Check non-existence.
enum_value_matches(enum e, TWO);
enum_value_matches(e_t, ONE);
enum_value_matches(volatile e_t, ONE);
enum_value_matches(const e_t, ONE);
return 0;
}
#define field_exists(f) \
({ \
if (!bpf_core_field_exists(f)) { \
return __LINE__; \
} \
})
#define field_size_matches(f) \
({ \
if (sizeof(f) != bpf_core_field_size(f)) { \
return __LINE__; \
} \
})
#define field_offset_matches(t, f) \
({ \
if (__builtin_offsetof(t, f) != __builtin_preserve_field_info(((typeof(t) *)0)->f, BPF_FIELD_BYTE_OFFSET)) { \
return __LINE__; \
} \
})
#define field_is_signed(f) \
({ \
if (!__builtin_preserve_field_info(f, BPF_FIELD_SIGNED)) { \
return __LINE__; \
} \
})
#define field_is_unsigned(f) \
({ \
if (__builtin_preserve_field_info(f, BPF_FIELD_SIGNED)) { \
return __LINE__; \
} \
})
__section("socket_filter/fields") int fields() {
field_exists((struct s){}._1);
field_exists((s_t){}._2);
field_exists((union u){}._1);
field_exists((u_t){}._2);
field_is_signed((struct s){}._1);
field_is_unsigned((struct s){}._3);
// unions crash clang-14.
// field_is_signed((union u){}._1);
// field_is_unsigned((union u){}._3);
field_size_matches((struct s){}._1);
field_size_matches((s_t){}._2);
field_size_matches((union u){}._1);
field_size_matches((u_t){}._2);
field_offset_matches(struct s, _1);
field_offset_matches(s_t, _2);
field_offset_matches(union u, _1);
field_offset_matches(u_t, _2);
struct t {
union {
s_t s[10];
};
struct {
union u u;
};
} bar, *barp = &bar;
field_exists(bar.s[2]._1);
field_exists(bar.s[1]._2);
field_exists(bar.u._1);
field_exists(bar.u._2);
field_exists(barp[1].u._2);
field_is_signed(bar.s[2]._1);
field_is_unsigned(bar.s[2]._3);
// unions crash clang-14.
// field_is_signed(bar.u._1);
// field_is_signed(bar.u._3);
field_size_matches(bar.s[2]._1);
field_size_matches(bar.s[1]._2);
field_size_matches(bar.u._1);
field_size_matches(bar.u._2);
field_size_matches(barp[1].u._2);
field_offset_matches(struct t, s[2]._1);
field_offset_matches(struct t, s[1]._2);
field_offset_matches(struct t, u._1);
field_offset_matches(struct t, u._2);
return 0;
}
struct ambiguous {
int _1;
char _2;
};
struct ambiguous___flavour {
char _1;
int _2;
};
__section("socket_filter/err_ambiguous") int err_ambiguous() {
return bpf_core_type_id_kernel(struct ambiguous);
}
__section("socket_filter/err_ambiguous_flavour") int err_ambiguous_flavour() {
return bpf_core_type_id_kernel(struct ambiguous___flavour);
}
@@ -0,0 +1,116 @@
#include "../../testdata/common.h"
#include "bpf_core_read.h"
#define core_access __builtin_preserve_access_index
// Struct with the members declared in the wrong order. Accesses need
// a successful CO-RE relocation against the type in relocs_read_tgt.c
// for the test below to pass.
struct s {
char b;
char a;
};
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long u64;
// Struct with bitfields.
struct bits {
int x;
u8 a : 4, b : 2;
u16 c : 1;
unsigned int d : 2;
enum { ZERO = 0, ONE = 1 } e : 1;
u64 f : 16, g : 30;
};
struct nonexist {
int non_exist;
};
enum nonexist_enum { NON_EXIST = 1 };
// Perform a read from a subprog to ensure CO-RE relocations
// occurring there are tracked and executed in the final linked program.
__attribute__((noinline)) int read_subprog() {
struct s foo = {
.a = 0,
.b = 1,
};
if (core_access(foo.a) == 0)
return __LINE__;
if (core_access(foo.b) == 1)
return __LINE__;
struct bits bar;
char *p = (char *)&bar;
/* Target:
* [4] STRUCT 'bits' size=8 vlen=7
* 'b' type_id=5 bits_offset=0 bitfield_size=2
* 'a' type_id=5 bits_offset=2 bitfield_size=4
* 'd' type_id=7 bits_offset=6 bitfield_size=2
* 'c' type_id=9 bits_offset=8 bitfield_size=1
* 'e' type_id=11 bits_offset=9 bitfield_size=1
* 'f' type_id=9 bits_offset=16
* 'g' type_id=12 bits_offset=32 bitfield_size=30
*/
*p++ = 0xff; // a, b, d
*p++ = 0x00; // c, e
*p++ = 0x56; // f
*p++ = 0x56; // f
#ifdef __BIG_ENDIAN__
*p++ = 0x55; // g
*p++ = 0x44; // g
*p++ = 0x33; // g
*p++ = 0x22; // g
#else
*p++ = 0x22; // g
*p++ = 0x33; // g
*p++ = 0x44; // g
*p++ = 0x55; // g
#endif
if (BPF_CORE_READ_BITFIELD(&bar, a) != (1 << 4) - 1)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, b) != (1 << 2) - 1)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, d) != (1 << 2) - 1)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, c) != 0)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, e) != 0)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, f) != 0x5656)
return __LINE__;
if (BPF_CORE_READ_BITFIELD(&bar, g) != 0x15443322)
return __LINE__;
if (bpf_core_type_exists(struct nonexist) != 0)
return __LINE__;
if (bpf_core_field_exists(((struct nonexist *)0)->non_exist) != 0)
return __LINE__;
if (bpf_core_enum_value_exists(enum nonexist_enum, NON_EXIST) != 0)
return __LINE__;
return 0;
}
__section("socket") int reads() {
int ret = read_subprog();
if (ret)
return ret;
return 0;
}
@@ -0,0 +1,30 @@
/*
This file exists to emit ELFs with specific BTF types to use as target BTF
in tests. It can be made redundant when btf.Spec can be handcrafted and
passed as a CO-RE target in the future.
*/
struct s {
char a;
char b;
};
struct s *unused_s __attribute__((unused));
typedef unsigned int my_u32;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long u64;
struct bits {
/*int x;*/
u8 b : 2, a : 4; /* a was before b */
my_u32 d : 2; /* was 'unsigned int' */
u16 c : 1; /* was before d */
enum { ZERO = 0, ONE = 1 } e : 1;
u16 f; /* was: u64 f:16 */
u32 g : 30; /* was: u64 g:30 */
};
struct bits *unused_bits __attribute__((unused));
@@ -0,0 +1,141 @@
package btf
import (
"fmt"
"github.com/cilium/ebpf/internal"
)
// Functions to traverse a cyclic graph of types. The below was very useful:
// https://eli.thegreenplace.net/2015/directed-graph-traversal-orderings-and-applications-to-data-flow-analysis/#post-order-and-reverse-post-order
type postorderIterator struct {
// Iteration skips types for which this function returns true.
skip func(Type) bool
// The root type. May be nil if skip(root) is true.
root Type
// Contains types which need to be either walked or yielded.
types typeDeque
// Contains a boolean whether the type has been walked or not.
walked internal.Deque[bool]
// The set of types which has been pushed onto types.
pushed map[Type]struct{}
// The current type. Only valid after a call to Next().
Type Type
}
// postorderTraversal iterates all types reachable from root by visiting the
// leaves of the graph first.
//
// Types for which skip returns true are ignored. skip may be nil.
func postorderTraversal(root Type, skip func(Type) (skip bool)) postorderIterator {
// Avoid allocations for the common case of a skipped root.
if skip != nil && skip(root) {
return postorderIterator{}
}
po := postorderIterator{root: root, skip: skip}
walkType(root, po.push)
return po
}
func (po *postorderIterator) push(t *Type) {
if _, ok := po.pushed[*t]; ok || *t == po.root {
return
}
if po.skip != nil && po.skip(*t) {
return
}
if po.pushed == nil {
// Lazily allocate pushed to avoid an allocation for Types without children.
po.pushed = make(map[Type]struct{})
}
po.pushed[*t] = struct{}{}
po.types.Push(t)
po.walked.Push(false)
}
// Next returns true if there is another Type to traverse.
func (po *postorderIterator) Next() bool {
for !po.types.Empty() {
t := po.types.Pop()
if !po.walked.Pop() {
// Push the type again, so that we re-evaluate it in done state
// after all children have been handled.
po.types.Push(t)
po.walked.Push(true)
// Add all direct children to todo.
walkType(*t, po.push)
} else {
// We've walked this type previously, so we now know that all
// children have been handled.
po.Type = *t
return true
}
}
// Only return root once.
po.Type, po.root = po.root, nil
return po.Type != nil
}
// walkType calls fn on each child of typ.
func walkType(typ Type, fn func(*Type)) {
// Explicitly type switch on the most common types to allow the inliner to
// do its work. This avoids allocating intermediate slices from walk() on
// the heap.
switch v := typ.(type) {
case *Void, *Int, *Enum, *Fwd, *Float:
// No children to traverse.
case *Pointer:
fn(&v.Target)
case *Array:
fn(&v.Index)
fn(&v.Type)
case *Struct:
for i := range v.Members {
fn(&v.Members[i].Type)
}
case *Union:
for i := range v.Members {
fn(&v.Members[i].Type)
}
case *Typedef:
fn(&v.Type)
case *Volatile:
fn(&v.Type)
case *Const:
fn(&v.Type)
case *Restrict:
fn(&v.Type)
case *Func:
fn(&v.Type)
case *FuncProto:
fn(&v.Return)
for i := range v.Params {
fn(&v.Params[i].Type)
}
case *Var:
fn(&v.Type)
case *Datasec:
for i := range v.Vars {
fn(&v.Vars[i].Type)
}
case *declTag:
fn(&v.Type)
case *typeTag:
fn(&v.Type)
case *cycle:
// cycle has children, but we ignore them deliberately.
default:
panic(fmt.Sprintf("don't know how to walk Type %T", v))
}
}
@@ -0,0 +1,97 @@
package btf
import (
"fmt"
"testing"
qt "github.com/frankban/quicktest"
)
func TestPostorderTraversal(t *testing.T) {
ptr := newCyclicalType(2).(*Pointer)
cst := ptr.Target.(*Const)
str := cst.Type.(*Struct)
t.Logf("%3v", ptr)
pending := []Type{str, cst, ptr}
iter := postorderTraversal(ptr, nil)
for iter.Next() {
qt.Assert(t, iter.Type, qt.Equals, pending[0])
pending = pending[1:]
}
qt.Assert(t, pending, qt.HasLen, 0)
i := &Int{Name: "foo"}
// i appears twice at the same nesting depth.
arr := &Array{Index: i, Type: i}
seen := make(map[Type]bool)
iter = postorderTraversal(arr, nil)
for iter.Next() {
qt.Assert(t, seen[iter.Type], qt.IsFalse)
seen[iter.Type] = true
}
qt.Assert(t, seen[arr], qt.IsTrue)
qt.Assert(t, seen[i], qt.IsTrue)
}
func TestPostorderTraversalVmlinux(t *testing.T) {
spec := vmlinuxTestdataSpec(t)
typ, err := spec.AnyTypeByName("gov_update_cpu_data")
if err != nil {
t.Fatal(err)
}
for _, typ := range []Type{typ} {
t.Run(fmt.Sprintf("%s", typ), func(t *testing.T) {
seen := make(map[Type]bool)
var last Type
iter := postorderTraversal(typ, nil)
for iter.Next() {
if seen[iter.Type] {
t.Fatalf("%s visited twice", iter.Type)
}
seen[iter.Type] = true
last = iter.Type
}
if last != typ {
t.Fatalf("Expected %s got %s as last type", typ, last)
}
walkType(typ, func(child *Type) {
qt.Check(t, seen[*child], qt.IsTrue, qt.Commentf("missing child %s", *child))
})
})
}
}
func BenchmarkPostorderTraversal(b *testing.B) {
spec := vmlinuxTestdataSpec(b)
var fn *Func
err := spec.TypeByName("gov_update_cpu_data", &fn)
if err != nil {
b.Fatal(err)
}
for _, test := range []struct {
name string
typ Type
}{
{"single type", &Int{}},
{"cycle(1)", newCyclicalType(1)},
{"cycle(10)", newCyclicalType(10)},
{"gov_update_cpu_data", fn},
} {
b.Logf("%10v", test.typ)
b.Run(test.name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
iter := postorderTraversal(test.typ, nil)
for iter.Next() {
}
}
})
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,512 @@
package btf
import (
"bytes"
"fmt"
"reflect"
"testing"
"github.com/cilium/ebpf/internal"
qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp"
)
func TestSizeof(t *testing.T) {
testcases := []struct {
size int
typ Type
}{
{0, (*Void)(nil)},
{1, &Int{Size: 1}},
{8, &Enum{Size: 8}},
{0, &Array{Type: &Pointer{Target: (*Void)(nil)}, Nelems: 0}},
{12, &Array{Type: &Enum{Size: 4}, Nelems: 3}},
}
for _, tc := range testcases {
name := fmt.Sprint(tc.typ)
t.Run(name, func(t *testing.T) {
have, err := Sizeof(tc.typ)
if err != nil {
t.Fatal("Can't calculate size:", err)
}
if have != tc.size {
t.Errorf("Expected size %d, got %d", tc.size, have)
}
})
}
}
func TestPow(t *testing.T) {
tests := []struct {
n int
r bool
}{
{0, false},
{1, true},
{2, true},
{3, false},
{4, true},
{5, false},
{8, true},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%d", tt.n), func(t *testing.T) {
if want, got := tt.r, pow(tt.n); want != got {
t.Errorf("unexpected result for n %d; want: %v, got: %v", tt.n, want, got)
}
})
}
}
func TestCopy(t *testing.T) {
_ = Copy((*Void)(nil), nil)
in := &Int{Size: 4}
out := Copy(in, nil)
in.Size = 8
if size := out.(*Int).Size; size != 4 {
t.Error("Copy doesn't make a copy, expected size 4, got", size)
}
t.Run("cyclical", func(t *testing.T) {
_ = Copy(newCyclicalType(2), nil)
})
t.Run("identity", func(t *testing.T) {
u16 := &Int{Size: 2}
out := Copy(&Struct{
Members: []Member{
{Name: "a", Type: u16},
{Name: "b", Type: u16},
},
}, nil)
outStruct := out.(*Struct)
qt.Assert(t, outStruct.Members[0].Type, qt.Equals, outStruct.Members[1].Type)
})
}
func TestAs(t *testing.T) {
i := &Int{}
ptr := &Pointer{i}
td := &Typedef{Type: ptr}
cst := &Const{td}
vol := &Volatile{cst}
// It's possible to retrieve qualifiers and Typedefs.
haveVol, ok := as[*Volatile](vol)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, haveVol, qt.Equals, vol)
haveTd, ok := as[*Typedef](vol)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, haveTd, qt.Equals, td)
haveCst, ok := as[*Const](vol)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, haveCst, qt.Equals, cst)
// Make sure we don't skip Pointer.
haveI, ok := as[*Int](vol)
qt.Assert(t, ok, qt.IsFalse)
qt.Assert(t, haveI, qt.IsNil)
// Make sure we can always retrieve Pointer.
for _, typ := range []Type{
td, cst, vol, ptr,
} {
have, ok := as[*Pointer](typ)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, have, qt.Equals, ptr)
}
}
func BenchmarkCopy(b *testing.B) {
typ := newCyclicalType(10)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Copy(typ, nil)
}
}
// The following are valid Types.
//
// There currently is no better way to document which
// types implement an interface.
func ExampleType_validTypes() {
var _ Type = &Void{}
var _ Type = &Int{}
var _ Type = &Pointer{}
var _ Type = &Array{}
var _ Type = &Struct{}
var _ Type = &Union{}
var _ Type = &Enum{}
var _ Type = &Fwd{}
var _ Type = &Typedef{}
var _ Type = &Volatile{}
var _ Type = &Const{}
var _ Type = &Restrict{}
var _ Type = &Func{}
var _ Type = &FuncProto{}
var _ Type = &Var{}
var _ Type = &Datasec{}
var _ Type = &Float{}
}
func TestType(t *testing.T) {
types := []func() Type{
func() Type { return &Void{} },
func() Type { return &Int{Size: 2} },
func() Type { return &Pointer{Target: &Void{}} },
func() Type { return &Array{Type: &Int{}} },
func() Type {
return &Struct{
Members: []Member{{Type: &Void{}}},
}
},
func() Type {
return &Union{
Members: []Member{{Type: &Void{}}},
}
},
func() Type { return &Enum{} },
func() Type { return &Fwd{Name: "thunk"} },
func() Type { return &Typedef{Type: &Void{}} },
func() Type { return &Volatile{Type: &Void{}} },
func() Type { return &Const{Type: &Void{}} },
func() Type { return &Restrict{Type: &Void{}} },
func() Type { return &Func{Name: "foo", Type: &Void{}} },
func() Type {
return &FuncProto{
Params: []FuncParam{{Name: "bar", Type: &Void{}}},
Return: &Void{},
}
},
func() Type { return &Var{Type: &Void{}} },
func() Type {
return &Datasec{
Vars: []VarSecinfo{{Type: &Void{}}},
}
},
func() Type { return &Float{} },
func() Type { return &declTag{Type: &Void{}} },
func() Type { return &typeTag{Type: &Void{}} },
func() Type { return &cycle{&Void{}} },
}
compareTypes := cmp.Comparer(func(a, b *Type) bool {
return a == b
})
for _, fn := range types {
typ := fn()
t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) {
t.Logf("%v", typ)
if typ == typ.copy() {
t.Error("Copy doesn't copy")
}
var a []*Type
walkType(typ, func(t *Type) { a = append(a, t) })
if _, ok := typ.(*cycle); !ok {
if n := countChildren(t, reflect.TypeOf(typ)); len(a) < n {
t.Errorf("walkType visited %d children, expected at least %d", len(a), n)
}
}
var b []*Type
walkType(typ, func(t *Type) { b = append(b, t) })
if diff := cmp.Diff(a, b, compareTypes); diff != "" {
t.Errorf("Walk mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestTagMarshaling(t *testing.T) {
for _, typ := range []Type{
&declTag{&Struct{Members: []Member{}}, "foo", -1},
&typeTag{&Int{}, "foo"},
} {
t.Run(fmt.Sprint(typ), func(t *testing.T) {
buf := marshalNativeEndian(t, []Type{typ})
s, err := loadRawSpec(bytes.NewReader(buf), internal.NativeEndian, nil)
qt.Assert(t, err, qt.IsNil)
have, err := s.TypeByID(1)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, have, qt.DeepEquals, typ)
})
}
}
func countChildren(t *testing.T, typ reflect.Type) int {
if typ.Kind() != reflect.Pointer {
t.Fatal("Expected pointer, got", typ.Kind())
}
typ = typ.Elem()
if typ.Kind() != reflect.Struct {
t.Fatal("Expected struct, got", typ.Kind())
}
var n int
for i := 0; i < typ.NumField(); i++ {
if typ.Field(i).Type == reflect.TypeOf((*Type)(nil)).Elem() {
n++
}
}
return n
}
type testFormattableType struct {
name string
extra []interface{}
}
var _ formattableType = (*testFormattableType)(nil)
func (tft *testFormattableType) TypeName() string { return tft.name }
func (tft *testFormattableType) Format(fs fmt.State, verb rune) {
formatType(fs, verb, tft, tft.extra...)
}
func TestFormatType(t *testing.T) {
t1 := &testFormattableType{"", []interface{}{"extra"}}
t1Addr := fmt.Sprintf("%#p", t1)
goType := reflect.TypeOf(t1).Elem().Name()
t2 := &testFormattableType{"foo", []interface{}{t1}}
t3 := &testFormattableType{extra: []interface{}{""}}
tests := []struct {
t formattableType
fmt string
contains []string
omits []string
}{
// %s doesn't contain address or extra.
{t1, "%s", []string{goType}, []string{t1Addr, "extra"}},
// %+s doesn't contain extra.
{t1, "%+s", []string{goType, t1Addr}, []string{"extra"}},
// %v does contain extra.
{t1, "%v", []string{goType, "extra"}, []string{t1Addr}},
// %+v does contain address.
{t1, "%+v", []string{goType, "extra", t1Addr}, nil},
// %v doesn't print nested types' extra.
{t2, "%v", []string{goType, t2.name}, []string{"extra"}},
// %1v does print nested types' extra.
{t2, "%1v", []string{goType, t2.name, "extra"}, nil},
// empty strings in extra don't emit anything.
{t3, "%v", []string{"[]"}, nil},
}
for _, test := range tests {
t.Run(test.fmt, func(t *testing.T) {
str := fmt.Sprintf(test.fmt, test.t)
t.Log(str)
for _, want := range test.contains {
qt.Assert(t, str, qt.Contains, want)
}
for _, notWant := range test.omits {
qt.Assert(t, str, qt.Not(qt.Contains), notWant)
}
})
}
}
func newCyclicalType(n int) Type {
ptr := &Pointer{}
prev := Type(ptr)
for i := 0; i < n; i++ {
switch i % 5 {
case 0:
prev = &Struct{
Members: []Member{
{Type: prev},
},
}
case 1:
prev = &Const{Type: prev}
case 2:
prev = &Volatile{Type: prev}
case 3:
prev = &Typedef{Type: prev}
case 4:
prev = &Array{Type: prev, Index: &Int{Size: 1}}
}
}
ptr.Target = prev
return ptr
}
func TestUnderlyingType(t *testing.T) {
wrappers := []struct {
name string
fn func(Type) Type
}{
{"const", func(t Type) Type { return &Const{Type: t} }},
{"volatile", func(t Type) Type { return &Volatile{Type: t} }},
{"restrict", func(t Type) Type { return &Restrict{Type: t} }},
{"typedef", func(t Type) Type { return &Typedef{Type: t} }},
{"type tag", func(t Type) Type { return &typeTag{Type: t} }},
}
for _, test := range wrappers {
t.Run(test.name+" cycle", func(t *testing.T) {
root := &Volatile{}
root.Type = test.fn(root)
got, ok := UnderlyingType(root).(*cycle)
qt.Assert(t, ok, qt.IsTrue)
qt.Assert(t, got.root, qt.Equals, root)
})
}
for _, test := range wrappers {
t.Run(test.name, func(t *testing.T) {
want := &Int{}
got := UnderlyingType(test.fn(want))
qt.Assert(t, got, qt.Equals, want)
})
}
}
func TestInflateLegacyBitfield(t *testing.T) {
const offset = 3
const size = 5
var rawInt rawType
rawInt.SetKind(kindInt)
rawInt.SetSize(4)
var data btfInt
data.SetOffset(offset)
data.SetBits(size)
rawInt.data = &data
var beforeInt rawType
beforeInt.SetKind(kindStruct)
beforeInt.SetVlen(1)
beforeInt.data = []btfMember{{Type: 2}}
afterInt := beforeInt
afterInt.data = []btfMember{{Type: 1}}
emptyStrings := newStringTable("")
for _, test := range []struct {
name string
raw []rawType
}{
{"struct before int", []rawType{beforeInt, rawInt}},
{"struct after int", []rawType{rawInt, afterInt}},
} {
t.Run(test.name, func(t *testing.T) {
types, err := inflateRawTypes(test.raw, emptyStrings, nil)
if err != nil {
t.Fatal(err)
}
for _, typ := range types {
s, ok := typ.(*Struct)
if !ok {
continue
}
i := s.Members[0]
if i.BitfieldSize != size {
t.Errorf("Expected bitfield size %d, got %d", size, i.BitfieldSize)
}
if i.Offset != offset {
t.Errorf("Expected offset %d, got %d", offset, i.Offset)
}
return
}
t.Fatal("No Struct returned from inflateRawTypes")
})
}
}
func BenchmarkWalk(b *testing.B) {
types := []Type{
&Void{},
&Int{},
&Pointer{},
&Array{},
&Struct{Members: make([]Member, 2)},
&Union{Members: make([]Member, 2)},
&Enum{},
&Fwd{},
&Typedef{},
&Volatile{},
&Const{},
&Restrict{},
&Func{},
&FuncProto{Params: make([]FuncParam, 2)},
&Var{},
&Datasec{Vars: make([]VarSecinfo, 2)},
}
for _, typ := range types {
b.Run(fmt.Sprint(typ), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var dq typeDeque
walkType(typ, dq.Push)
}
})
}
}
func BenchmarkUnderlyingType(b *testing.B) {
b.Run("no unwrapping", func(b *testing.B) {
v := &Int{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
UnderlyingType(v)
}
})
b.Run("single unwrapping", func(b *testing.B) {
v := &Typedef{Type: &Int{}}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
UnderlyingType(v)
}
})
}
// Copy can be used with UnderlyingType to strip qualifiers from a type graph.
func ExampleCopy_stripQualifiers() {
a := &Volatile{Type: &Pointer{Target: &Typedef{Name: "foo", Type: &Int{Size: 2}}}}
b := Copy(a, UnderlyingType)
// b has Volatile and Typedef removed.
fmt.Printf("%3v\n", b)
// Output: Pointer[target=Int[unsigned size=16]]
}
@@ -0,0 +1,26 @@
package btf
// datasecResolveWorkaround ensures that certain vars in a Datasec are added
// to a Spec before the Datasec. This avoids a bug in kernel BTF validation.
//
// See https://lore.kernel.org/bpf/20230302123440.1193507-1-lmb@isovalent.com/
func datasecResolveWorkaround(b *Builder, ds *Datasec) error {
for _, vsi := range ds.Vars {
v, ok := vsi.Type.(*Var)
if !ok {
continue
}
switch v.Type.(type) {
case *Typedef, *Volatile, *Const, *Restrict, *typeTag:
// NB: We must never call Add on a Datasec, otherwise we risk
// infinite recursion.
_, err := b.Add(v.Type)
if err != nil {
return err
}
}
}
return nil
}
@@ -0,0 +1,66 @@
package btf
import (
"errors"
"fmt"
"testing"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/testutils"
)
func TestDatasecResolveWorkaround(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.2", "BTF_KIND_DATASEC")
i := &Int{Size: 1}
for _, typ := range []Type{
&Typedef{"foo", i},
&Volatile{i},
&Const{i},
&Restrict{i},
&typeTag{i, "foo"},
} {
t.Run(fmt.Sprint(typ), func(t *testing.T) {
if _, ok := typ.(*typeTag); ok {
testutils.SkipOnOldKernel(t, "5.17", "BTF_KIND_TYPE_TAG")
}
ds := &Datasec{
Name: "a",
Size: 2,
Vars: []VarSecinfo{
{
Size: 1,
Offset: 0,
// struct, union, pointer, array will trigger the bug.
Type: &Var{Name: "a", Type: &Pointer{i}},
},
{
Size: 1,
Offset: 1,
Type: &Var{
Name: "b",
Type: typ,
},
},
},
}
b, err := NewBuilder([]Type{ds})
if err != nil {
t.Fatal(err)
}
h, err := NewHandle(b)
var ve *internal.VerifierError
if errors.As(err, &ve) {
t.Fatalf("%+v\n", ve)
}
if err != nil {
t.Fatal(err)
}
h.Close()
})
}
}