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
}