| 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 |
| } |