package btf import ( "bytes" "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") ) // ID represents the unique ID of a BTF object. type ID uint32 // Spec represents decoded BTF. type Spec struct { // Data from .BTF. rawTypes []rawType strings stringTable // Inflated Types. types []Type // Types indexed by essential name. // Includes all struct flavors and types with the same name. namedTypes map[essentialName][]Type // Data from .BTF.ext. funcInfos map[string]FuncInfo lineInfos map[string]LineInfos coreRelos map[string]CoreRelos 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) } // LoadSpecFromReader reads from an ELF or a raw BTF blob. // // Returns ErrNotFound if reading from an ELF which contains no BTF. 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. return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil, nil) } return nil, err } defer file.Close() 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 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 if symbol.Value > math.MaxUint32 { return nil, fmt.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name) } variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value) } return loadSpecFromELF(file, variableOffsets) } func loadSpecFromELF(file *internal.SafeELFFile, variableOffsets map[variable]uint32) (*Spec, error) { var ( btfSection *elf.Section btfExtSection *elf.Section sectionSizes = make(map[string]uint32) ) for _, sec := range file.Sections { switch sec.Name { case ".BTF": btfSection = sec case ".BTF.ext": btfExtSection = 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) } spec, err := loadRawSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets) if err != nil { return nil, err } if btfExtSection == nil { return spec, nil } if btfExtSection.ReaderAt == nil { return nil, fmt.Errorf("compressed ext_info is not supported") } extInfo, err := loadExtInfos(btfExtSection, file.ByteOrder, spec.strings) if err != nil { return nil, fmt.Errorf("can't parse ext info: %w", err) } if err := spec.splitExtInfos(extInfo); err != nil { return nil, fmt.Errorf("linking funcInfos and lineInfos: %w", err) } return spec, nil } // splitExtInfos takes FuncInfos, LineInfos and CoreRelos indexed by section and // transforms them to be indexed by function. Retrieves function names from // the BTF spec. func (spec *Spec) splitExtInfos(info *extInfo) error { ofi := make(map[string]FuncInfo) oli := make(map[string]LineInfos) ocr := make(map[string]CoreRelos) for secName, secFuncs := range info.funcInfos { // Collect functions from each section and organize them by name. for _, fi := range secFuncs { name, err := fi.Name(spec) if err != nil { return fmt.Errorf("looking up function name: %w", err) } // FuncInfo offsets are scoped to the ELF section. Zero them out // since they are meaningless outside of that context. The linker // will determine the offset of the function within the final // instruction stream before handing it off to the kernel. fi.InsnOff = 0 ofi[name] = fi } // Attribute LineInfo records to their respective functions, if any. if lines := info.lineInfos[secName]; lines != nil { for _, li := range lines { fi := secFuncs.funcForOffset(li.InsnOff) if fi == nil { return fmt.Errorf("section %s: error looking up FuncInfo for LineInfo %v", secName, li) } // Offsets are ELF section-scoped, make them function-scoped by // subtracting the function's start offset. li.InsnOff -= fi.InsnOff name, err := fi.Name(spec) if err != nil { return fmt.Errorf("looking up function name: %w", err) } oli[name] = append(oli[name], li) } } // Attribute CO-RE relocations to their respective functions, if any. if relos := info.relos[secName]; relos != nil { for _, r := range relos { fi := secFuncs.funcForOffset(r.insnOff) if fi == nil { return fmt.Errorf("section %s: error looking up FuncInfo for CO-RE relocation %v", secName, r) } // Offsets are ELF section-scoped, make them function-scoped by // subtracting the function's start offset. r.insnOff -= fi.InsnOff name, err := fi.Name(spec) if err != nil { return fmt.Errorf("looking up function name: %w", err) } ocr[name] = append(ocr[name], r) } } } spec.funcInfos = ofi spec.lineInfos = oli spec.coreRelos = ocr return nil } func loadRawSpec(btf io.Reader, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) { rawTypes, rawStrings, err := parseBTF(btf, bo) if err != nil { return nil, err } err = fixupDatasec(rawTypes, rawStrings, sectionSizes, variableOffsets) if err != nil { return nil, err } types, typesByName, err := inflateRawTypes(rawTypes, rawStrings) if err != nil { return nil, err } return &Spec{ rawTypes: rawTypes, namedTypes: typesByName, types: types, strings: rawStrings, byteOrder: bo, }, nil } var kernelBTF struct { sync.Mutex *Spec } // LoadKernelSpec returns the current kernel's BTF information. // // Requires a >= 5.5 kernel with CONFIG_DEBUG_INFO_BTF enabled. Returns // ErrNotSupported if BTF is not enabled. func LoadKernelSpec() (*Spec, error) { kernelBTF.Lock() defer kernelBTF.Unlock() if kernelBTF.Spec != nil { return kernelBTF.Spec, nil } var err error kernelBTF.Spec, err = loadKernelSpec() return kernelBTF.Spec, err } 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) } var uname unix.Utsname if err := unix.Uname(&uname); err != nil { return nil, fmt.Errorf("uname failed: %w", err) } end := bytes.IndexByte(uname.Release[:], 0) release := string(uname.Release[:end]) // 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 { path := fmt.Sprintf(loc, release) fh, err := os.Open(path) if err != nil { continue } defer fh.Close() file, err := internal.NewSafeELFFile(fh) if err != nil { return nil, err } defer file.Close() return loadSpecFromELF(file, nil) } return nil, fmt.Errorf("no BTF 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 { for _, bo := range []binary.ByteOrder{ binary.LittleEndian, binary.BigEndian, } { if _, err := parseBTFHeader(io.NewSectionReader(r, 0, math.MaxInt64), 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.Reader, bo binary.ByteOrder) ([]rawType, stringTable, error) { rawBTF, err := io.ReadAll(btf) if err != nil { return nil, nil, fmt.Errorf("can't read BTF: %v", err) } rd := bytes.NewReader(rawBTF) header, err := parseBTFHeader(rd, bo) if err != nil { return nil, nil, fmt.Errorf("parsing .BTF header: %v", err) } buf := io.NewSectionReader(rd, header.stringStart(), int64(header.StringLen)) rawStrings, err := readStringTable(buf) if err != nil { return nil, nil, fmt.Errorf("can't read type names: %w", err) } buf = io.NewSectionReader(rd, header.typeStart(), int64(header.TypeLen)) rawTypes, err := readTypes(buf, bo) 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) namedTypes := make(map[essentialName][]Type) for _, typ := range types { if name := typ.TypeName(); name != "" { en := newEssentialName(name) namedTypes[en] = append(namedTypes[en], typ) } } // NB: Other parts of spec are not copied since they are immutable. return &Spec{ s.rawTypes, s.strings, types, namedTypes, s.funcInfos, s.lineInfos, s.coreRelos, 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. _, _ = buf.Write(s.strings) // 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(len(s.strings)), } 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 } // Program finds the BTF for a specific function. // // Returns an error which may wrap ErrNoExtendedInfo if the Spec doesn't // contain extended BTF info. func (s *Spec) Program(name string) (*Program, error) { if s.funcInfos == nil && s.lineInfos == nil && s.coreRelos == nil { return nil, fmt.Errorf("BTF for function %s: %w", name, ErrNoExtendedInfo) } funcInfos, funcOK := s.funcInfos[name] lineInfos, lineOK := s.lineInfos[name] relos, coreOK := s.coreRelos[name] if !funcOK && !lineOK && !coreOK { return nil, fmt.Errorf("no extended BTF info for function %s", name) } return &Program{s, funcInfos, lineInfos, relos}, 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 int(id) > len(s.types) { return nil, fmt.Errorf("type ID %d: %w", id, ErrNotFound) } return s.types[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 } // 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 } // Handle is a reference to BTF loaded into the kernel. type Handle struct { spec *Spec fd *sys.FD } // 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 _, logErr := sys.BtfLoad(attr) return nil, internal.ErrorWithLog(err, logBuf, logErr) } return &Handle{spec.Copy(), fd}, nil } // NewHandleFromID returns the BTF handle for a given id. // // 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 BTF by id: %w", err) } info, err := newInfoFromFd(fd) if err != nil { _ = fd.Close() return nil, fmt.Errorf("get BTF spec for handle: %w", err) } return &Handle{info.BTF, fd}, nil } // Spec returns the Spec that defined the BTF loaded into the kernel. func (h *Handle) Spec() *Spec { return h.spec } // Close destroys the handle. // // Subsequent calls to FD will return an invalid value. func (h *Handle) Close() error { return h.fd.Close() } // FD returns the file descriptor for the handle. func (h *Handle) FD() int { return h.fd.Int() } // Map is the BTF for a map. type Map struct { Spec *Spec Key, Value Type } // Program is the BTF information for a stream of instructions. type Program struct { spec *Spec FuncInfo FuncInfo LineInfos LineInfos CoreRelos CoreRelos } // Spec returns the BTF spec of this program. func (p *Program) Spec() *Spec { return p.spec } // Fixups returns the changes required to adjust the program to the target. // // Passing a nil target will relocate against the running kernel. func (p *Program) Fixups(target *Spec) (COREFixups, error) { if len(p.CoreRelos) == 0 { return nil, nil } if target == nil { var err error target, err = LoadKernelSpec() if err != nil { return nil, err } } return coreRelocate(p.spec, target, p.CoreRelos) } 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 })