whatcanGOwrong
This commit is contained in:
@@ -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
|
||||
|
||||
Binary file not shown.
+3
@@ -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")
|
||||
+3
@@ -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")
|
||||
+3
@@ -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")
|
||||
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -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;
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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));
|
||||
Binary file not shown.
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user