summaryrefslogtreecommitdiff
path: root/vendor/github.com/cilium/ebpf/btf/btf.go
diff options
context:
space:
mode:
authorLibravatar dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>2023-04-03 11:16:17 +0200
committerLibravatar GitHub <noreply@github.com>2023-04-03 11:16:17 +0200
commit57dc742c76d7876a2457594715a7b5bc2c9a92bd (patch)
tree76be1ec744face5bf4f617d4c9fca084707e4268 /vendor/github.com/cilium/ebpf/btf/btf.go
parent[bugfix/frontend] Preload css styles (#1638) (diff)
downloadgotosocial-57dc742c76d7876a2457594715a7b5bc2c9a92bd.tar.xz
[chore]: Bump github.com/KimMachineGun/automemlimit from 0.2.4 to 0.2.5 (#1666)
Bumps [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) from 0.2.4 to 0.2.5. - [Release notes](https://github.com/KimMachineGun/automemlimit/releases) - [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.2.4...v0.2.5) --- updated-dependencies: - dependency-name: github.com/KimMachineGun/automemlimit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Diffstat (limited to 'vendor/github.com/cilium/ebpf/btf/btf.go')
-rw-r--r--vendor/github.com/cilium/ebpf/btf/btf.go897
1 files changed, 897 insertions, 0 deletions
diff --git a/vendor/github.com/cilium/ebpf/btf/btf.go b/vendor/github.com/cilium/ebpf/btf/btf.go
new file mode 100644
index 000000000..a5969332a
--- /dev/null
+++ b/vendor/github.com/cilium/ebpf/btf/btf.go
@@ -0,0 +1,897 @@
+package btf
+
+import (
+ "bufio"
+ "bytes"
+ "debug/elf"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "os"
+ "reflect"
+
+ "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")
+)
+
+// ID represents the unique ID of a BTF object.
+type ID = sys.BTFID
+
+// Spec represents decoded BTF.
+type Spec struct {
+ // Data from .BTF.
+ rawTypes []rawType
+ strings *stringTable
+
+ // All types contained by the spec. For the base type, the position of
+ // a type in the slice is its ID.
+ types types
+
+ // Type IDs indexed by type.
+ typeIDs map[Type]TypeID
+
+ // Types indexed by essential name.
+ // Includes all struct flavors and types with the same name.
+ namedTypes map[essentialName][]Type
+
+ byteOrder binary.ByteOrder
+}
+
+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)
+}
+
+// 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 {
+ // Try to parse a naked BTF blob. This will return an error if
+ // we encounter a Datasec, since we can't fix it up.
+ spec, err := loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil, nil)
+ return spec, err
+ }
+
+ return nil, err
+ }
+
+ return loadSpecFromELF(file)
+}
+
+// LoadSpecAndExtInfosFromReader reads from an ELF.
+//
+// ExtInfos may be nil if the ELF doesn't contain section metadta.
+// 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.types, spec.strings)
+ if err != nil && !errors.Is(err, ErrNotFound) {
+ return nil, nil, err
+ }
+
+ return spec, extInfos, nil
+}
+
+// variableOffsets 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 variableOffsets(file *internal.SafeELFFile) (map[variable]uint32, error) {
+ symbols, err := file.Symbols()
+ if err != nil {
+ return nil, fmt.Errorf("can't read symbols: %v", err)
+ }
+
+ variableOffsets := make(map[variable]uint32)
+ for _, symbol := range symbols {
+ if idx := symbol.Section; idx >= elf.SHN_LORESERVE && idx <= elf.SHN_HIRESERVE {
+ // Ignore things like SHN_ABS
+ continue
+ }
+
+ if symbol.Value > math.MaxUint32 {
+ // VarSecinfo offset is u32, cannot reference symbols in higher regions.
+ continue
+ }
+
+ if int(symbol.Section) >= len(file.Sections) {
+ return nil, fmt.Errorf("symbol %s: invalid section %d", symbol.Name, symbol.Section)
+ }
+
+ secName := file.Sections[symbol.Section].Name
+ variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value)
+ }
+
+ return variableOffsets, 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)
+ }
+
+ vars, err := variableOffsets(file)
+ if err != nil {
+ return nil, err
+ }
+
+ if btfSection.ReaderAt == nil {
+ return nil, fmt.Errorf("compressed BTF is not supported")
+ }
+
+ rawTypes, rawStrings, err := parseBTF(btfSection.ReaderAt, file.ByteOrder, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ err = fixupDatasec(rawTypes, rawStrings, sectionSizes, vars)
+ if err != nil {
+ return nil, err
+ }
+
+ return inflateSpec(rawTypes, rawStrings, file.ByteOrder, nil)
+}
+
+func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder,
+ baseTypes types, baseStrings *stringTable) (*Spec, error) {
+
+ rawTypes, rawStrings, err := parseBTF(btf, bo, baseStrings)
+ if err != nil {
+ return nil, err
+ }
+
+ return inflateSpec(rawTypes, rawStrings, bo, baseTypes)
+}
+
+func inflateSpec(rawTypes []rawType, rawStrings *stringTable, bo binary.ByteOrder,
+ baseTypes types) (*Spec, error) {
+
+ types, err := inflateRawTypes(rawTypes, baseTypes, rawStrings)
+ if err != nil {
+ return nil, err
+ }
+
+ typeIDs, typesByName := indexTypes(types, TypeID(len(baseTypes)))
+
+ return &Spec{
+ rawTypes: rawTypes,
+ namedTypes: typesByName,
+ typeIDs: typeIDs,
+ types: types,
+ strings: rawStrings,
+ byteOrder: bo,
+ }, nil
+}
+
+func indexTypes(types []Type, typeIDOffset 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] = TypeID(i) + typeIDOffset
+ }
+
+ 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) {
+ fh, err := os.Open("/sys/kernel/btf/vmlinux")
+ if err == nil {
+ defer fh.Close()
+
+ return loadRawSpec(fh, internal.NativeEndian, nil, nil)
+ }
+
+ file, err := findVMLinux()
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ return loadSpecFromELF(file)
+}
+
+// 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 variable struct {
+ section string
+ name string
+}
+
+func fixupDatasec(rawTypes []rawType, rawStrings *stringTable, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) error {
+ for i, rawType := range rawTypes {
+ if rawType.Kind() != kindDatasec {
+ continue
+ }
+
+ name, err := rawStrings.Lookup(rawType.NameOff)
+ if err != nil {
+ return err
+ }
+
+ if name == ".kconfig" || name == ".ksyms" {
+ return fmt.Errorf("reference to %s: %w", name, ErrNotSupported)
+ }
+
+ if rawTypes[i].SizeType != 0 {
+ continue
+ }
+
+ size, ok := sectionSizes[name]
+ if !ok {
+ return fmt.Errorf("data section %s: missing size", name)
+ }
+
+ rawTypes[i].SizeType = size
+
+ secinfos := rawType.data.([]btfVarSecinfo)
+ for j, secInfo := range secinfos {
+ id := int(secInfo.Type - 1)
+ if id >= len(rawTypes) {
+ return fmt.Errorf("data section %s: invalid type id %d for variable %d", name, id, j)
+ }
+
+ varName, err := rawStrings.Lookup(rawTypes[id].NameOff)
+ if err != nil {
+ return fmt.Errorf("data section %s: can't get name for type %d: %w", name, id, err)
+ }
+
+ offset, ok := variableOffsets[variable{name, varName}]
+ if !ok {
+ return fmt.Errorf("data section %s: missing offset for variable %s", name, varName)
+ }
+
+ secinfos[j].Offset = offset
+ }
+ }
+
+ return nil
+}
+
+// Copy creates a copy of Spec.
+func (s *Spec) Copy() *Spec {
+ types := copyTypes(s.types, nil)
+
+ typeIDOffset := TypeID(0)
+ if len(s.types) != 0 {
+ typeIDOffset = s.typeIDs[s.types[0]]
+ }
+ typeIDs, typesByName := indexTypes(types, typeIDOffset)
+
+ // NB: Other parts of spec are not copied since they are immutable.
+ return &Spec{
+ s.rawTypes,
+ s.strings,
+ types,
+ typeIDs,
+ typesByName,
+ s.byteOrder,
+ }
+}
+
+type marshalOpts struct {
+ ByteOrder binary.ByteOrder
+ StripFuncLinkage bool
+}
+
+func (s *Spec) marshal(opts marshalOpts) ([]byte, error) {
+ var (
+ buf bytes.Buffer
+ header = new(btfHeader)
+ headerLen = binary.Size(header)
+ )
+
+ // Reserve space for the header. We have to write it last since
+ // we don't know the size of the type section yet.
+ _, _ = buf.Write(make([]byte, headerLen))
+
+ // Write type section, just after the header.
+ for _, raw := range s.rawTypes {
+ switch {
+ case opts.StripFuncLinkage && raw.Kind() == kindFunc:
+ raw.SetLinkage(StaticFunc)
+ }
+
+ if err := raw.Marshal(&buf, opts.ByteOrder); err != nil {
+ return nil, fmt.Errorf("can't marshal BTF: %w", err)
+ }
+ }
+
+ typeLen := uint32(buf.Len() - headerLen)
+
+ // Write string section after type section.
+ stringsLen := s.strings.Length()
+ buf.Grow(stringsLen)
+ if err := s.strings.Marshal(&buf); err != nil {
+ return nil, err
+ }
+
+ // Fill out the header, and write it out.
+ header = &btfHeader{
+ Magic: btfMagic,
+ Version: 1,
+ Flags: 0,
+ HdrLen: uint32(headerLen),
+ TypeOff: 0,
+ TypeLen: typeLen,
+ StringOff: typeLen,
+ StringLen: uint32(stringsLen),
+ }
+
+ raw := buf.Bytes()
+ err := binary.Write(sliceWriter(raw[:headerLen]), opts.ByteOrder, header)
+ if err != nil {
+ return nil, fmt.Errorf("can't write header: %v", err)
+ }
+
+ return raw, nil
+}
+
+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
+}
+
+// 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) {
+ return s.types.ByID(id)
+}
+
+// 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. If multiple candidates are found,
+// an error is returned.
+func (s *Spec) TypeByName(name string, typ interface{}) error {
+ 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.AssignableTo(reflect.TypeOf((*Type)(nil)).Elem()) {
+ 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: multiple candidates for %T", name, typ)
+ }
+
+ candidate = typ
+ }
+
+ if candidate == nil {
+ return fmt.Errorf("type %s: %w", 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.types, base.strings)
+}
+
+// TypesIterator iterates over types of a given spec.
+type TypesIterator struct {
+ spec *Spec
+ index int
+ // The last visited type in the spec.
+ Type Type
+}
+
+// Iterate returns the types iterator.
+func (s *Spec) Iterate() *TypesIterator {
+ return &TypesIterator{spec: s, index: 0}
+}
+
+// Next returns true as long as there are any remaining types.
+func (iter *TypesIterator) Next() bool {
+ if len(iter.spec.types) <= iter.index {
+ return false
+ }
+
+ iter.Type = iter.spec.types[iter.index]
+ iter.index++
+ return true
+}
+
+// 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
+}
+
+// NewHandle loads BTF into the kernel.
+//
+// Returns ErrNotSupported if BTF is not supported.
+func NewHandle(spec *Spec) (*Handle, error) {
+ if err := haveBTF(); err != nil {
+ return nil, err
+ }
+
+ if spec.byteOrder != internal.NativeEndian {
+ return nil, fmt.Errorf("can't load %s BTF on %s", spec.byteOrder, internal.NativeEndian)
+ }
+
+ btf, err := spec.marshal(marshalOpts{
+ ByteOrder: internal.NativeEndian,
+ StripFuncLinkage: haveFuncLinkage() != nil,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("can't marshal BTF: %w", err)
+ }
+
+ 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 {
+ logBuf := make([]byte, 64*1024)
+ attr.BtfLogBuf = sys.NewSlicePointer(logBuf)
+ attr.BtfLogSize = uint32(len(logBuf))
+ attr.BtfLogLevel = 1
+ // NB: The syscall will never return ENOSPC as of 5.18-rc4.
+ _, _ = sys.BtfLoad(attr)
+ return nil, internal.ErrorWithLog(err, logBuf)
+ }
+
+ return &Handle{fd, attr.BtfSize}, nil
+}
+
+// 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}, nil
+}
+
+// Spec parses the kernel BTF into Go types.
+//
+// base is used to decode split BTF and may be nil.
+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
+ }
+
+ var baseTypes types
+ var baseStrings *stringTable
+ if base != nil {
+ baseTypes = base.types
+ baseStrings = base.strings
+ }
+
+ return loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, baseTypes, baseStrings)
+}
+
+// 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)
+}
+
+func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
+ const minHeaderLength = 24
+
+ typesLen := uint32(binary.Size(types))
+ header := btfHeader{
+ Magic: btfMagic,
+ Version: 1,
+ HdrLen: minHeaderLength,
+ TypeOff: 0,
+ TypeLen: typesLen,
+ StringOff: typesLen,
+ StringLen: uint32(len(strings)),
+ }
+
+ buf := new(bytes.Buffer)
+ _ = binary.Write(buf, bo, &header)
+ _ = binary.Write(buf, bo, types)
+ buf.Write(strings)
+
+ return buf.Bytes()
+}
+
+var haveBTF = internal.FeatureTest("BTF", "5.1", func() error {
+ var (
+ types struct {
+ Integer btfType
+ Var btfType
+ btfVar struct{ Linkage uint32 }
+ }
+ strings = []byte{0, 'a', 0}
+ )
+
+ // We use a BTF_KIND_VAR here, to make sure that
+ // the kernel understands BTF at least as well as we
+ // do. BTF_KIND_VAR was introduced ~5.1.
+ types.Integer.SetKind(kindPointer)
+ types.Var.NameOff = 1
+ types.Var.SetKind(kindVar)
+ types.Var.SizeType = 1
+
+ btf := marshalBTF(&types, strings, internal.NativeEndian)
+
+ fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
+ Btf: sys.NewSlicePointer(btf),
+ BtfSize: uint32(len(btf)),
+ })
+ if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) {
+ // Treat both EINVAL and EPERM as not supported: loading the program
+ // might still succeed without BTF.
+ return internal.ErrNotSupported
+ }
+ if err != nil {
+ return err
+ }
+
+ fd.Close()
+ return nil
+})
+
+var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() error {
+ if err := haveBTF(); err != nil {
+ return err
+ }
+
+ var (
+ types struct {
+ FuncProto btfType
+ Func btfType
+ }
+ strings = []byte{0, 'a', 0}
+ )
+
+ types.FuncProto.SetKind(kindFuncProto)
+ types.Func.SetKind(kindFunc)
+ types.Func.SizeType = 1 // aka FuncProto
+ types.Func.NameOff = 1
+ types.Func.SetLinkage(GlobalFunc)
+
+ btf := marshalBTF(&types, strings, internal.NativeEndian)
+
+ fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
+ Btf: sys.NewSlicePointer(btf),
+ BtfSize: uint32(len(btf)),
+ })
+ if errors.Is(err, unix.EINVAL) {
+ return internal.ErrNotSupported
+ }
+ if err != nil {
+ return err
+ }
+
+ fd.Close()
+ return nil
+})