mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-18 18:58:32 +02:00
Bump deps
``` * use go 1.17 semantics in go.mod * pin tonistiigi/fsutil dep to match docker/docker's dep go get -u ./... go mod vendor ```
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
package internal
|
||||
|
||||
// Align returns 'n' updated to 'alignment' boundary.
|
||||
func Align(n, alignment int) int {
|
||||
return (int(n) + alignment - 1) / alignment * alignment
|
||||
}
|
||||
+364
-264
@@ -7,14 +7,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/sys"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
@@ -27,16 +26,28 @@ var (
|
||||
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 {
|
||||
rawTypes []rawType
|
||||
strings stringTable
|
||||
types []Type
|
||||
namedTypes map[string][]namedType
|
||||
funcInfos map[string]extInfo
|
||||
lineInfos map[string]extInfo
|
||||
coreRelos map[string]bpfCoreRelos
|
||||
byteOrder binary.ByteOrder
|
||||
// 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 {
|
||||
@@ -51,25 +62,34 @@ type btfHeader struct {
|
||||
StringLen uint32
|
||||
}
|
||||
|
||||
// LoadSpecFromReader reads BTF sections from an ELF.
|
||||
// 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 a nil Spec and no error if no BTF was present.
|
||||
// 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()
|
||||
|
||||
btfSection, btfExtSection, sectionSizes, err := findBtfSections(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if btfSection == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
symbols, err := file.Symbols()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read symbols: %v", err)
|
||||
@@ -87,10 +107,6 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
|
||||
}
|
||||
|
||||
secName := file.Sections[symbol.Section].Name
|
||||
if _, ok := sectionSizes[secName]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if symbol.Value > math.MaxUint32 {
|
||||
return nil, fmt.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name)
|
||||
}
|
||||
@@ -98,24 +114,10 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
|
||||
variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value)
|
||||
}
|
||||
|
||||
spec, err := loadNakedSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if btfExtSection == nil {
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
spec.funcInfos, spec.lineInfos, spec.coreRelos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read ext info: %w", err)
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
return loadSpecFromELF(file, variableOffsets)
|
||||
}
|
||||
|
||||
func findBtfSections(file *internal.SafeELFFile) (*elf.Section, *elf.Section, map[string]uint32, error) {
|
||||
func loadSpecFromELF(file *internal.SafeELFFile, variableOffsets map[variable]uint32) (*Spec, error) {
|
||||
var (
|
||||
btfSection *elf.Section
|
||||
btfExtSection *elf.Section
|
||||
@@ -134,33 +136,119 @@ func findBtfSections(file *internal.SafeELFFile) (*elf.Section, *elf.Section, ma
|
||||
}
|
||||
|
||||
if sec.Size > math.MaxUint32 {
|
||||
return nil, nil, nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
|
||||
return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
|
||||
}
|
||||
|
||||
sectionSizes[sec.Name] = uint32(sec.Size)
|
||||
}
|
||||
}
|
||||
return btfSection, btfExtSection, sectionSizes, nil
|
||||
}
|
||||
|
||||
func loadSpecFromVmlinux(rd io.ReaderAt) (*Spec, error) {
|
||||
file, err := internal.NewSafeELFFile(rd)
|
||||
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
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
btfSection, _, _, err := findBtfSections(file)
|
||||
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(".BTF ELF section: %s", err)
|
||||
return nil, fmt.Errorf("can't parse ext info: %w", err)
|
||||
}
|
||||
if btfSection == nil {
|
||||
return nil, fmt.Errorf("unable to find .BTF ELF section")
|
||||
|
||||
if err := spec.splitExtInfos(extInfo); err != nil {
|
||||
return nil, fmt.Errorf("linking funcInfos and lineInfos: %w", err)
|
||||
}
|
||||
return loadNakedSpec(btfSection.Open(), file.ByteOrder, nil, nil)
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
|
||||
// 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
|
||||
@@ -208,18 +296,21 @@ func LoadKernelSpec() (*Spec, error) {
|
||||
}
|
||||
|
||||
func loadKernelSpec() (*Spec, error) {
|
||||
release, err := unix.KernelRelease()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read kernel release number: %w", err)
|
||||
}
|
||||
|
||||
fh, err := os.Open("/sys/kernel/btf/vmlinux")
|
||||
if err == nil {
|
||||
defer fh.Close()
|
||||
|
||||
return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
|
||||
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{
|
||||
@@ -241,60 +332,84 @@ func loadKernelSpec() (*Spec, error) {
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
return loadSpecFromVmlinux(fh)
|
||||
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)
|
||||
}
|
||||
|
||||
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) {
|
||||
rawBTF, err := ioutil.ReadAll(btf)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("can't read BTF: %v", err)
|
||||
}
|
||||
|
||||
rd := bytes.NewReader(rawBTF)
|
||||
|
||||
// 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(rd, bo, &header); err != nil {
|
||||
return nil, nil, fmt.Errorf("can't read header: %v", err)
|
||||
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, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
|
||||
return nil, fmt.Errorf("incorrect magic value %v", header.Magic)
|
||||
}
|
||||
|
||||
if header.Version != 1 {
|
||||
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
|
||||
return nil, fmt.Errorf("unexpected version %v", header.Version)
|
||||
}
|
||||
|
||||
if header.Flags != 0 {
|
||||
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
|
||||
return nil, fmt.Errorf("unsupported flags %v", header.Flags)
|
||||
}
|
||||
|
||||
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
|
||||
if remainder < 0 {
|
||||
return nil, nil, errors.New("header is too short")
|
||||
return nil, errors.New("header length shorter than btfHeader size")
|
||||
}
|
||||
|
||||
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
|
||||
return nil, nil, fmt.Errorf("header padding: %v", err)
|
||||
if _, err := io.CopyN(internal.DiscardZeroes{}, r, remainder); err != nil {
|
||||
return nil, fmt.Errorf("header padding: %v", err)
|
||||
}
|
||||
|
||||
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
|
||||
return nil, nil, fmt.Errorf("can't seek to start of string section: %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
|
||||
}
|
||||
}
|
||||
|
||||
rawStrings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
|
||||
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)
|
||||
}
|
||||
|
||||
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil {
|
||||
return nil, nil, fmt.Errorf("can't seek to start of type section: %v", err)
|
||||
}
|
||||
|
||||
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
|
||||
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)
|
||||
}
|
||||
@@ -357,6 +472,31 @@ func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[s
|
||||
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
|
||||
@@ -377,7 +517,7 @@ func (s *Spec) marshal(opts marshalOpts) ([]byte, error) {
|
||||
for _, raw := range s.rawTypes {
|
||||
switch {
|
||||
case opts.StripFuncLinkage && raw.Kind() == kindFunc:
|
||||
raw.SetLinkage(linkageStatic)
|
||||
raw.SetLinkage(StaticFunc)
|
||||
}
|
||||
|
||||
if err := raw.Marshal(&buf, opts.ByteOrder); err != nil {
|
||||
@@ -421,65 +561,99 @@ func (sw sliceWriter) Write(p []byte) (int, error) {
|
||||
return copy(sw, p), nil
|
||||
}
|
||||
|
||||
// Program finds the BTF for a specific section.
|
||||
//
|
||||
// Length is the number of bytes in the raw BPF instruction stream.
|
||||
// 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, length uint64) (*Program, error) {
|
||||
if length == 0 {
|
||||
return nil, errors.New("length musn't be zero")
|
||||
}
|
||||
|
||||
func (s *Spec) Program(name string) (*Program, error) {
|
||||
if s.funcInfos == nil && s.lineInfos == nil && s.coreRelos == nil {
|
||||
return nil, fmt.Errorf("BTF for section %s: %w", name, ErrNoExtendedInfo)
|
||||
return nil, fmt.Errorf("BTF for function %s: %w", name, ErrNoExtendedInfo)
|
||||
}
|
||||
|
||||
funcInfos, funcOK := s.funcInfos[name]
|
||||
lineInfos, lineOK := s.lineInfos[name]
|
||||
coreRelos, coreOK := s.coreRelos[name]
|
||||
relos, coreOK := s.coreRelos[name]
|
||||
|
||||
if !funcOK && !lineOK && !coreOK {
|
||||
return nil, fmt.Errorf("no extended BTF info for section %s", name)
|
||||
return nil, fmt.Errorf("no extended BTF info for function %s", name)
|
||||
}
|
||||
|
||||
return &Program{s, length, funcInfos, lineInfos, coreRelos}, nil
|
||||
return &Program{s, funcInfos, lineInfos, relos}, nil
|
||||
}
|
||||
|
||||
// Datasec returns the BTF required to create maps which represent data sections.
|
||||
func (s *Spec) Datasec(name string) (*Map, error) {
|
||||
var datasec Datasec
|
||||
if err := s.FindType(name, &datasec); err != nil {
|
||||
return nil, fmt.Errorf("data section %s: can't get BTF: %w", name, err)
|
||||
}
|
||||
|
||||
m := NewMap(s, &Void{}, &datasec)
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// FindType searches for a type with a specific name.
|
||||
// TypeByID returns the BTF Type with the given type ID.
|
||||
//
|
||||
// hint determines the type of the returned Type.
|
||||
// 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 spec.
|
||||
func (s *Spec) FindType(name string, typ Type) error {
|
||||
var (
|
||||
wanted = reflect.TypeOf(typ)
|
||||
candidate Type
|
||||
)
|
||||
// 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)
|
||||
}
|
||||
|
||||
for _, typ := range s.namedTypes[essentialName(name)] {
|
||||
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
|
||||
}
|
||||
|
||||
// Match against the full name, not just the essential one.
|
||||
if typ.name() != name {
|
||||
continue
|
||||
}
|
||||
|
||||
if candidate != nil {
|
||||
return fmt.Errorf("type %s: multiple candidates for %T", name, typ)
|
||||
}
|
||||
@@ -491,14 +665,15 @@ func (s *Spec) FindType(name string, typ Type) error {
|
||||
return fmt.Errorf("type %s: %w", name, ErrNotFound)
|
||||
}
|
||||
|
||||
value := reflect.Indirect(reflect.ValueOf(copyType(candidate)))
|
||||
reflect.Indirect(reflect.ValueOf(typ)).Set(value)
|
||||
typPtr.Set(reflect.ValueOf(candidate))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle is a reference to BTF loaded into the kernel.
|
||||
type Handle struct {
|
||||
fd *internal.FD
|
||||
spec *Spec
|
||||
fd *sys.FD
|
||||
}
|
||||
|
||||
// NewHandle loads BTF into the kernel.
|
||||
@@ -525,22 +700,49 @@ func NewHandle(spec *Spec) (*Handle, error) {
|
||||
return nil, errors.New("BTF exceeds the maximum size")
|
||||
}
|
||||
|
||||
attr := &bpfLoadBTFAttr{
|
||||
btf: internal.NewSlicePointer(btf),
|
||||
btfSize: uint32(len(btf)),
|
||||
attr := &sys.BtfLoadAttr{
|
||||
Btf: sys.NewSlicePointer(btf),
|
||||
BtfSize: uint32(len(btf)),
|
||||
}
|
||||
|
||||
fd, err := bpfLoadBTF(attr)
|
||||
fd, err := sys.BtfLoad(attr)
|
||||
if err != nil {
|
||||
logBuf := make([]byte, 64*1024)
|
||||
attr.logBuf = internal.NewSlicePointer(logBuf)
|
||||
attr.btfLogSize = uint32(len(logBuf))
|
||||
attr.btfLogLevel = 1
|
||||
_, logErr := bpfLoadBTF(attr)
|
||||
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{fd}, nil
|
||||
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.
|
||||
@@ -552,147 +754,45 @@ func (h *Handle) Close() error {
|
||||
|
||||
// FD returns the file descriptor for the handle.
|
||||
func (h *Handle) FD() int {
|
||||
value, err := h.fd.Value()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return int(value)
|
||||
return h.fd.Int()
|
||||
}
|
||||
|
||||
// Map is the BTF for a map.
|
||||
type Map struct {
|
||||
spec *Spec
|
||||
key, value Type
|
||||
}
|
||||
|
||||
// NewMap returns a new Map containing the given values.
|
||||
// The key and value arguments are initialized to Void if nil values are given.
|
||||
func NewMap(spec *Spec, key Type, value Type) Map {
|
||||
if key == nil {
|
||||
key = &Void{}
|
||||
}
|
||||
if value == nil {
|
||||
value = &Void{}
|
||||
}
|
||||
|
||||
return Map{
|
||||
spec: spec,
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// MapSpec should be a method on Map, but is a free function
|
||||
// to hide it from users of the ebpf package.
|
||||
func MapSpec(m *Map) *Spec {
|
||||
return m.spec
|
||||
}
|
||||
|
||||
// MapKey should be a method on Map, but is a free function
|
||||
// to hide it from users of the ebpf package.
|
||||
func MapKey(m *Map) Type {
|
||||
return m.key
|
||||
}
|
||||
|
||||
// MapValue should be a method on Map, but is a free function
|
||||
// to hide it from users of the ebpf package.
|
||||
func MapValue(m *Map) Type {
|
||||
return m.value
|
||||
Spec *Spec
|
||||
Key, Value Type
|
||||
}
|
||||
|
||||
// Program is the BTF information for a stream of instructions.
|
||||
type Program struct {
|
||||
spec *Spec
|
||||
length uint64
|
||||
funcInfos, lineInfos extInfo
|
||||
coreRelos bpfCoreRelos
|
||||
spec *Spec
|
||||
FuncInfo FuncInfo
|
||||
LineInfos LineInfos
|
||||
CoreRelos CoreRelos
|
||||
}
|
||||
|
||||
// ProgramSpec returns the Spec needed for loading function and line infos into the kernel.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramSpec(s *Program) *Spec {
|
||||
return s.spec
|
||||
// Spec returns the BTF spec of this program.
|
||||
func (p *Program) Spec() *Spec {
|
||||
return p.spec
|
||||
}
|
||||
|
||||
// ProgramAppend the information from other to the Program.
|
||||
// Fixups returns the changes required to adjust the program to the target.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramAppend(s, other *Program) error {
|
||||
funcInfos, err := s.funcInfos.append(other.funcInfos, s.length)
|
||||
if err != nil {
|
||||
return fmt.Errorf("func infos: %w", err)
|
||||
}
|
||||
|
||||
lineInfos, err := s.lineInfos.append(other.lineInfos, s.length)
|
||||
if err != nil {
|
||||
return fmt.Errorf("line infos: %w", err)
|
||||
}
|
||||
|
||||
s.funcInfos = funcInfos
|
||||
s.lineInfos = lineInfos
|
||||
s.coreRelos = s.coreRelos.append(other.coreRelos, s.length)
|
||||
s.length += other.length
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProgramFuncInfos returns the binary form of BTF function infos.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramFuncInfos(s *Program) (recordSize uint32, bytes []byte, err error) {
|
||||
bytes, err = s.funcInfos.MarshalBinary()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return s.funcInfos.recordSize, bytes, nil
|
||||
}
|
||||
|
||||
// ProgramLineInfos returns the binary form of BTF line infos.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramLineInfos(s *Program) (recordSize uint32, bytes []byte, err error) {
|
||||
bytes, err = s.lineInfos.MarshalBinary()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return s.lineInfos.recordSize, bytes, nil
|
||||
}
|
||||
|
||||
// ProgramRelocations returns the CO-RE relocations required to adjust the
|
||||
// program to the target.
|
||||
//
|
||||
// This is a free function instead of a method to hide it from users
|
||||
// of package ebpf.
|
||||
func ProgramRelocations(s *Program, target *Spec) (map[uint64]Relocation, error) {
|
||||
if len(s.coreRelos) == 0 {
|
||||
// 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
|
||||
}
|
||||
|
||||
return coreRelocate(s.spec, target, s.coreRelos)
|
||||
}
|
||||
|
||||
type bpfLoadBTFAttr struct {
|
||||
btf internal.Pointer
|
||||
logBuf internal.Pointer
|
||||
btfSize uint32
|
||||
btfLogSize uint32
|
||||
btfLogLevel uint32
|
||||
}
|
||||
|
||||
func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
|
||||
fd, err := internal.BPF(internal.BPF_BTF_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if target == nil {
|
||||
var err error
|
||||
target, err = LoadKernelSpec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return internal.NewFD(uint32(fd)), nil
|
||||
return coreRelocate(p.spec, target, p.CoreRelos)
|
||||
}
|
||||
|
||||
func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
|
||||
@@ -737,9 +837,9 @@ var haveBTF = internal.FeatureTest("BTF", "5.1", func() error {
|
||||
|
||||
btf := marshalBTF(&types, strings, internal.NativeEndian)
|
||||
|
||||
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
|
||||
btf: internal.NewSlicePointer(btf),
|
||||
btfSize: uint32(len(btf)),
|
||||
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
|
||||
@@ -771,13 +871,13 @@ var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() err
|
||||
types.Func.SetKind(kindFunc)
|
||||
types.Func.SizeType = 1 // aka FuncProto
|
||||
types.Func.NameOff = 1
|
||||
types.Func.SetLinkage(linkageGlobal)
|
||||
types.Func.SetLinkage(GlobalFunc)
|
||||
|
||||
btf := marshalBTF(&types, strings, internal.NativeEndian)
|
||||
|
||||
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
|
||||
btf: internal.NewSlicePointer(btf),
|
||||
btfSize: uint32(len(btf)),
|
||||
fd, err := sys.BtfLoad(&sys.BtfLoadAttr{
|
||||
Btf: sys.NewSlicePointer(btf),
|
||||
BtfSize: uint32(len(btf)),
|
||||
})
|
||||
if errors.Is(err, unix.EINVAL) {
|
||||
return internal.ErrNotSupported
|
||||
|
||||
+28
-10
@@ -6,6 +6,8 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
//go:generate stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage
|
||||
|
||||
// btfKind describes a Type.
|
||||
type btfKind uint8
|
||||
|
||||
@@ -29,19 +31,32 @@ const (
|
||||
// Added ~5.1
|
||||
kindVar
|
||||
kindDatasec
|
||||
// Added ~5.13
|
||||
kindFloat
|
||||
)
|
||||
|
||||
type btfFuncLinkage uint8
|
||||
// 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 (
|
||||
linkageStatic btfFuncLinkage = iota
|
||||
linkageGlobal
|
||||
linkageExtern
|
||||
StaticVar VarLinkage = iota // static
|
||||
GlobalVar // global
|
||||
ExternVar // extern
|
||||
)
|
||||
|
||||
const (
|
||||
btfTypeKindShift = 24
|
||||
btfTypeKindLen = 4
|
||||
btfTypeKindLen = 5
|
||||
btfTypeVlenShift = 0
|
||||
btfTypeVlenMask = 16
|
||||
btfTypeKindFlagShift = 31
|
||||
@@ -54,8 +69,8 @@ type btfType struct {
|
||||
/* "info" bits arrangement
|
||||
* bits 0-15: vlen (e.g. # of struct's members), linkage
|
||||
* bits 16-23: unused
|
||||
* bits 24-27: kind (e.g. int, ptr, array...etc)
|
||||
* bits 28-30: 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
|
||||
*/
|
||||
@@ -104,6 +119,8 @@ func (k btfKind) String() string {
|
||||
return "Variable"
|
||||
case kindDatasec:
|
||||
return "Section"
|
||||
case kindFloat:
|
||||
return "Float"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown (%d)", k)
|
||||
}
|
||||
@@ -142,11 +159,11 @@ func (bt *btfType) KindFlag() bool {
|
||||
return bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift) == 1
|
||||
}
|
||||
|
||||
func (bt *btfType) Linkage() btfFuncLinkage {
|
||||
return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
|
||||
func (bt *btfType) Linkage() FuncLinkage {
|
||||
return FuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
|
||||
}
|
||||
|
||||
func (bt *btfType) SetLinkage(linkage btfFuncLinkage) {
|
||||
func (bt *btfType) SetLinkage(linkage FuncLinkage) {
|
||||
bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift)
|
||||
}
|
||||
|
||||
@@ -247,6 +264,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
|
||||
data = new(btfVariable)
|
||||
case kindDatasec:
|
||||
data = make([]btfVarSecinfo, header.Vlen())
|
||||
case kindFloat:
|
||||
default:
|
||||
return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind())
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// Code generated by "stringer -linecomment -output=btf_types_string.go -type=FuncLinkage,VarLinkage"; 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]]
|
||||
}
|
||||
+662
-149
@@ -3,43 +3,160 @@ package btf
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cilium/ebpf/asm"
|
||||
)
|
||||
|
||||
// Code in this file is derived from libbpf, which is available under a BSD
|
||||
// 2-Clause license.
|
||||
|
||||
// Relocation describes a CO-RE relocation.
|
||||
type Relocation struct {
|
||||
Current uint32
|
||||
New uint32
|
||||
// COREFixup is the result of computing a CO-RE relocation for a target.
|
||||
type COREFixup struct {
|
||||
Kind COREKind
|
||||
Local uint32
|
||||
Target uint32
|
||||
Poison bool
|
||||
}
|
||||
|
||||
func (r Relocation) equal(other Relocation) bool {
|
||||
return r.Current == other.Current && r.New == other.New
|
||||
func (f COREFixup) equal(other COREFixup) bool {
|
||||
return f.Local == other.Local && f.Target == other.Target
|
||||
}
|
||||
|
||||
// coreReloKind is the type of CO-RE relocation
|
||||
type coreReloKind uint32
|
||||
func (f COREFixup) String() string {
|
||||
if f.Poison {
|
||||
return fmt.Sprintf("%s=poison", f.Kind)
|
||||
}
|
||||
return fmt.Sprintf("%s=%d->%d", f.Kind, f.Local, f.Target)
|
||||
}
|
||||
|
||||
func (f COREFixup) apply(ins *asm.Instruction) error {
|
||||
if f.Poison {
|
||||
return errors.New("can't poison individual instruction")
|
||||
}
|
||||
|
||||
switch class := ins.OpCode.Class(); class {
|
||||
case asm.LdXClass, asm.StClass, asm.StXClass:
|
||||
if want := int16(f.Local); want != ins.Offset {
|
||||
return fmt.Errorf("invalid offset %d, expected %d", ins.Offset, want)
|
||||
}
|
||||
|
||||
if f.Target > math.MaxInt16 {
|
||||
return fmt.Errorf("offset %d exceeds MaxInt16", f.Target)
|
||||
}
|
||||
|
||||
ins.Offset = int16(f.Target)
|
||||
|
||||
case asm.LdClass:
|
||||
if !ins.IsConstantLoad(asm.DWord) {
|
||||
return fmt.Errorf("not a dword-sized immediate load")
|
||||
}
|
||||
|
||||
if want := int64(f.Local); want != ins.Constant {
|
||||
return fmt.Errorf("invalid immediate %d, expected %d", ins.Constant, want)
|
||||
}
|
||||
|
||||
ins.Constant = int64(f.Target)
|
||||
|
||||
case asm.ALUClass:
|
||||
if ins.OpCode.ALUOp() == asm.Swap {
|
||||
return fmt.Errorf("relocation against swap")
|
||||
}
|
||||
|
||||
fallthrough
|
||||
|
||||
case asm.ALU64Class:
|
||||
if src := ins.OpCode.Source(); src != asm.ImmSource {
|
||||
return fmt.Errorf("invalid source %s", src)
|
||||
}
|
||||
|
||||
if want := int64(f.Local); want != ins.Constant {
|
||||
return fmt.Errorf("invalid immediate %d, expected %d", ins.Constant, want)
|
||||
}
|
||||
|
||||
if f.Target > math.MaxInt32 {
|
||||
return fmt.Errorf("immediate %d exceeds MaxInt32", f.Target)
|
||||
}
|
||||
|
||||
ins.Constant = int64(f.Target)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid class %s", class)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f COREFixup) isNonExistant() bool {
|
||||
return f.Kind.checksForExistence() && f.Target == 0
|
||||
}
|
||||
|
||||
type COREFixups map[uint64]COREFixup
|
||||
|
||||
// Apply returns a copy of insns with CO-RE relocations applied.
|
||||
func (fs COREFixups) Apply(insns asm.Instructions) (asm.Instructions, error) {
|
||||
if len(fs) == 0 {
|
||||
cpy := make(asm.Instructions, len(insns))
|
||||
copy(cpy, insns)
|
||||
return insns, nil
|
||||
}
|
||||
|
||||
cpy := make(asm.Instructions, 0, len(insns))
|
||||
iter := insns.Iterate()
|
||||
for iter.Next() {
|
||||
fixup, ok := fs[iter.Offset.Bytes()]
|
||||
if !ok {
|
||||
cpy = append(cpy, *iter.Ins)
|
||||
continue
|
||||
}
|
||||
|
||||
ins := *iter.Ins
|
||||
if fixup.Poison {
|
||||
const badRelo = asm.BuiltinFunc(0xbad2310)
|
||||
|
||||
cpy = append(cpy, badRelo.Call())
|
||||
if ins.OpCode.IsDWordLoad() {
|
||||
// 64 bit constant loads occupy two raw bpf instructions, so
|
||||
// we need to add another instruction as padding.
|
||||
cpy = append(cpy, badRelo.Call())
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := fixup.apply(&ins); err != nil {
|
||||
return nil, fmt.Errorf("instruction %d, offset %d: %s: %w", iter.Index, iter.Offset.Bytes(), fixup.Kind, err)
|
||||
}
|
||||
|
||||
cpy = append(cpy, ins)
|
||||
}
|
||||
|
||||
return cpy, nil
|
||||
}
|
||||
|
||||
// COREKind is the type of CO-RE relocation
|
||||
type COREKind uint32
|
||||
|
||||
const (
|
||||
reloFieldByteOffset coreReloKind = iota /* field byte offset */
|
||||
reloFieldByteSize /* field size in bytes */
|
||||
reloFieldExists /* field existence in target kernel */
|
||||
reloFieldSigned /* field signedness (0 - unsigned, 1 - signed) */
|
||||
reloFieldLShiftU64 /* bitfield-specific left bitshift */
|
||||
reloFieldRShiftU64 /* bitfield-specific right bitshift */
|
||||
reloTypeIDLocal /* type ID in local BPF object */
|
||||
reloTypeIDTarget /* type ID in target kernel */
|
||||
reloTypeExists /* type existence in target kernel */
|
||||
reloTypeSize /* type size in bytes */
|
||||
reloEnumvalExists /* enum value existence in target kernel */
|
||||
reloEnumvalValue /* enum value integer value */
|
||||
reloFieldByteOffset COREKind = iota /* field byte offset */
|
||||
reloFieldByteSize /* field size in bytes */
|
||||
reloFieldExists /* field existence in target kernel */
|
||||
reloFieldSigned /* field signedness (0 - unsigned, 1 - signed) */
|
||||
reloFieldLShiftU64 /* bitfield-specific left bitshift */
|
||||
reloFieldRShiftU64 /* bitfield-specific right bitshift */
|
||||
reloTypeIDLocal /* type ID in local BPF object */
|
||||
reloTypeIDTarget /* type ID in target kernel */
|
||||
reloTypeExists /* type existence in target kernel */
|
||||
reloTypeSize /* type size in bytes */
|
||||
reloEnumvalExists /* enum value existence in target kernel */
|
||||
reloEnumvalValue /* enum value integer value */
|
||||
)
|
||||
|
||||
func (k coreReloKind) String() string {
|
||||
func (k COREKind) String() string {
|
||||
switch k {
|
||||
case reloFieldByteOffset:
|
||||
return "byte_off"
|
||||
@@ -70,103 +187,249 @@ func (k coreReloKind) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func coreRelocate(local, target *Spec, coreRelos bpfCoreRelos) (map[uint64]Relocation, error) {
|
||||
if target == nil {
|
||||
var err error
|
||||
target, err = loadKernelSpec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
func (k COREKind) checksForExistence() bool {
|
||||
return k == reloEnumvalExists || k == reloTypeExists || k == reloFieldExists
|
||||
}
|
||||
|
||||
func coreRelocate(local, target *Spec, relos CoreRelos) (COREFixups, error) {
|
||||
if local.byteOrder != target.byteOrder {
|
||||
return nil, fmt.Errorf("can't relocate %s against %s", local.byteOrder, target.byteOrder)
|
||||
}
|
||||
|
||||
relocations := make(map[uint64]Relocation, len(coreRelos))
|
||||
for _, relo := range coreRelos {
|
||||
accessorStr, err := local.strings.Lookup(relo.AccessStrOff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ids []TypeID
|
||||
relosByID := make(map[TypeID]CoreRelos)
|
||||
result := make(COREFixups, len(relos))
|
||||
for _, relo := range relos {
|
||||
if relo.kind == reloTypeIDLocal {
|
||||
// Filtering out reloTypeIDLocal here makes our lives a lot easier
|
||||
// down the line, since it doesn't have a target at all.
|
||||
if len(relo.accessor) > 1 || relo.accessor[0] != 0 {
|
||||
return nil, fmt.Errorf("%s: unexpected accessor %v", relo.kind, relo.accessor)
|
||||
}
|
||||
|
||||
accessor, err := parseCoreAccessor(accessorStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("accessor %q: %s", accessorStr, err)
|
||||
}
|
||||
|
||||
if int(relo.TypeID) >= len(local.types) {
|
||||
return nil, fmt.Errorf("invalid type id %d", relo.TypeID)
|
||||
}
|
||||
|
||||
typ := local.types[relo.TypeID]
|
||||
|
||||
if relo.ReloKind == reloTypeIDLocal {
|
||||
relocations[uint64(relo.InsnOff)] = Relocation{
|
||||
uint32(typ.ID()),
|
||||
uint32(typ.ID()),
|
||||
result[uint64(relo.insnOff)] = COREFixup{
|
||||
relo.kind,
|
||||
uint32(relo.typeID),
|
||||
uint32(relo.typeID),
|
||||
false,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
named, ok := typ.(namedType)
|
||||
if !ok || named.name() == "" {
|
||||
return nil, fmt.Errorf("relocate anonymous type %s: %w", typ.String(), ErrNotSupported)
|
||||
relos, ok := relosByID[relo.typeID]
|
||||
if !ok {
|
||||
ids = append(ids, relo.typeID)
|
||||
}
|
||||
|
||||
name := essentialName(named.name())
|
||||
res, err := coreCalculateRelocation(typ, target.namedTypes[name], relo.ReloKind, accessor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("relocate %s: %w", name, err)
|
||||
}
|
||||
|
||||
relocations[uint64(relo.InsnOff)] = res
|
||||
relosByID[relo.typeID] = append(relos, relo)
|
||||
}
|
||||
|
||||
return relocations, nil
|
||||
// Ensure we work on relocations in a deterministic order.
|
||||
sort.Slice(ids, func(i, j int) bool {
|
||||
return ids[i] < ids[j]
|
||||
})
|
||||
|
||||
for _, id := range ids {
|
||||
if int(id) >= len(local.types) {
|
||||
return nil, fmt.Errorf("invalid type id %d", id)
|
||||
}
|
||||
|
||||
localType := local.types[id]
|
||||
localTypeName := localType.TypeName()
|
||||
if localTypeName == "" {
|
||||
return nil, fmt.Errorf("relocate unnamed or anonymous type %s: %w", localType, ErrNotSupported)
|
||||
}
|
||||
|
||||
relos := relosByID[id]
|
||||
targets := target.namedTypes[newEssentialName(localTypeName)]
|
||||
fixups, err := coreCalculateFixups(localType, targets, relos)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("relocate %s: %w", localType, err)
|
||||
}
|
||||
|
||||
for i, relo := range relos {
|
||||
result[uint64(relo.insnOff)] = fixups[i]
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var errAmbiguousRelocation = errors.New("ambiguous relocation")
|
||||
var errImpossibleRelocation = errors.New("impossible relocation")
|
||||
|
||||
// coreCalculateFixups calculates the fixups for the given relocations using
|
||||
// the "best" target.
|
||||
//
|
||||
// The best target is determined by scoring: the less poisoning we have to do
|
||||
// the better the target is.
|
||||
func coreCalculateFixups(local Type, targets []Type, relos CoreRelos) ([]COREFixup, error) {
|
||||
localID := local.ID()
|
||||
local, err := copyType(local, skipQualifiersAndTypedefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bestScore := len(relos)
|
||||
var bestFixups []COREFixup
|
||||
for i := range targets {
|
||||
targetID := targets[i].ID()
|
||||
target, err := copyType(targets[i], skipQualifiersAndTypedefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
score := 0 // lower is better
|
||||
fixups := make([]COREFixup, 0, len(relos))
|
||||
for _, relo := range relos {
|
||||
fixup, err := coreCalculateFixup(local, localID, target, targetID, relo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("target %s: %w", target, err)
|
||||
}
|
||||
if fixup.Poison || fixup.isNonExistant() {
|
||||
score++
|
||||
}
|
||||
fixups = append(fixups, fixup)
|
||||
}
|
||||
|
||||
if score > bestScore {
|
||||
// We have a better target already, ignore this one.
|
||||
continue
|
||||
}
|
||||
|
||||
if score < bestScore {
|
||||
// This is the best target yet, use it.
|
||||
bestScore = score
|
||||
bestFixups = fixups
|
||||
continue
|
||||
}
|
||||
|
||||
// Some other target has the same score as the current one. Make sure
|
||||
// the fixups agree with each other.
|
||||
for i, fixup := range bestFixups {
|
||||
if !fixup.equal(fixups[i]) {
|
||||
return nil, fmt.Errorf("%s: multiple types match: %w", fixup.Kind, errAmbiguousRelocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bestFixups == nil {
|
||||
// Nothing at all matched, probably because there are no suitable
|
||||
// targets at all. Poison everything!
|
||||
bestFixups = make([]COREFixup, len(relos))
|
||||
for i, relo := range relos {
|
||||
bestFixups[i] = COREFixup{Kind: relo.kind, Poison: true}
|
||||
}
|
||||
}
|
||||
|
||||
return bestFixups, nil
|
||||
}
|
||||
|
||||
// coreCalculateFixup calculates the fixup for a single local type, target type
|
||||
// and relocation.
|
||||
func coreCalculateFixup(local Type, localID TypeID, target Type, targetID TypeID, relo CoreRelo) (COREFixup, error) {
|
||||
fixup := func(local, target uint32) (COREFixup, error) {
|
||||
return COREFixup{relo.kind, local, target, false}, nil
|
||||
}
|
||||
poison := func() (COREFixup, error) {
|
||||
if relo.kind.checksForExistence() {
|
||||
return fixup(1, 0)
|
||||
}
|
||||
return COREFixup{relo.kind, 0, 0, true}, nil
|
||||
}
|
||||
zero := COREFixup{}
|
||||
|
||||
switch relo.kind {
|
||||
case reloTypeIDTarget, reloTypeSize, reloTypeExists:
|
||||
if len(relo.accessor) > 1 || relo.accessor[0] != 0 {
|
||||
return zero, fmt.Errorf("%s: unexpected accessor %v", relo.kind, relo.accessor)
|
||||
}
|
||||
|
||||
err := coreAreTypesCompatible(local, target)
|
||||
if errors.Is(err, errImpossibleRelocation) {
|
||||
return poison()
|
||||
}
|
||||
if err != nil {
|
||||
return zero, fmt.Errorf("relocation %s: %w", relo.kind, err)
|
||||
}
|
||||
|
||||
switch relo.kind {
|
||||
case reloTypeExists:
|
||||
return fixup(1, 1)
|
||||
|
||||
func coreCalculateRelocation(local Type, targets []namedType, kind coreReloKind, localAccessor coreAccessor) (Relocation, error) {
|
||||
var relos []Relocation
|
||||
var matches []Type
|
||||
for _, target := range targets {
|
||||
switch kind {
|
||||
case reloTypeIDTarget:
|
||||
if localAccessor[0] != 0 {
|
||||
return Relocation{}, fmt.Errorf("%s: unexpected non-zero accessor", kind)
|
||||
return fixup(uint32(localID), uint32(targetID))
|
||||
|
||||
case reloTypeSize:
|
||||
localSize, err := Sizeof(local)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
if compat, err := coreAreTypesCompatible(local, target); err != nil {
|
||||
return Relocation{}, fmt.Errorf("%s: %s", kind, err)
|
||||
} else if !compat {
|
||||
continue
|
||||
targetSize, err := Sizeof(target)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
relos = append(relos, Relocation{uint32(target.ID()), uint32(target.ID())})
|
||||
|
||||
default:
|
||||
return Relocation{}, fmt.Errorf("relocation %s: %w", kind, ErrNotSupported)
|
||||
return fixup(uint32(localSize), uint32(targetSize))
|
||||
}
|
||||
matches = append(matches, target)
|
||||
}
|
||||
|
||||
if len(relos) == 0 {
|
||||
// TODO: Add switch for existence checks like reloEnumvalExists here.
|
||||
case reloEnumvalValue, reloEnumvalExists:
|
||||
localValue, targetValue, err := coreFindEnumValue(local, relo.accessor, target)
|
||||
if errors.Is(err, errImpossibleRelocation) {
|
||||
return poison()
|
||||
}
|
||||
if err != nil {
|
||||
return zero, fmt.Errorf("relocation %s: %w", relo.kind, err)
|
||||
}
|
||||
|
||||
// TODO: This might have to be poisoned.
|
||||
return Relocation{}, fmt.Errorf("no relocation found, tried %v", targets)
|
||||
}
|
||||
switch relo.kind {
|
||||
case reloEnumvalExists:
|
||||
return fixup(1, 1)
|
||||
|
||||
case reloEnumvalValue:
|
||||
return fixup(uint32(localValue.Value), uint32(targetValue.Value))
|
||||
}
|
||||
|
||||
case reloFieldByteOffset, reloFieldByteSize, reloFieldExists:
|
||||
if _, ok := target.(*Fwd); ok {
|
||||
// We can't relocate fields using a forward declaration, so
|
||||
// skip it. If a non-forward declaration is present in the BTF
|
||||
// we'll find it in one of the other iterations.
|
||||
return poison()
|
||||
}
|
||||
|
||||
localField, targetField, err := coreFindField(local, relo.accessor, target)
|
||||
if errors.Is(err, errImpossibleRelocation) {
|
||||
return poison()
|
||||
}
|
||||
if err != nil {
|
||||
return zero, fmt.Errorf("target %s: %w", target, err)
|
||||
}
|
||||
|
||||
switch relo.kind {
|
||||
case reloFieldExists:
|
||||
return fixup(1, 1)
|
||||
|
||||
case reloFieldByteOffset:
|
||||
return fixup(localField.offset/8, targetField.offset/8)
|
||||
|
||||
case reloFieldByteSize:
|
||||
localSize, err := Sizeof(localField.Type)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
targetSize, err := Sizeof(targetField.Type)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
return fixup(uint32(localSize), uint32(targetSize))
|
||||
|
||||
relo := relos[0]
|
||||
for _, altRelo := range relos[1:] {
|
||||
if !altRelo.equal(relo) {
|
||||
return Relocation{}, fmt.Errorf("multiple types %v match: %w", matches, errAmbiguousRelocation)
|
||||
}
|
||||
}
|
||||
|
||||
return relo, nil
|
||||
return zero, fmt.Errorf("relocation %s: %w", relo.kind, ErrNotSupported)
|
||||
}
|
||||
|
||||
/* coreAccessor contains a path through a struct. It contains at least one index.
|
||||
@@ -204,8 +467,8 @@ func parseCoreAccessor(accessor string) (coreAccessor, error) {
|
||||
return nil, fmt.Errorf("empty accessor")
|
||||
}
|
||||
|
||||
var result coreAccessor
|
||||
parts := strings.Split(accessor, ":")
|
||||
result := make(coreAccessor, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
// 31 bits to avoid overflowing int on 32 bit platforms.
|
||||
index, err := strconv.ParseUint(part, 10, 31)
|
||||
@@ -219,6 +482,240 @@ func parseCoreAccessor(accessor string) (coreAccessor, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (ca coreAccessor) String() string {
|
||||
strs := make([]string, 0, len(ca))
|
||||
for _, i := range ca {
|
||||
strs = append(strs, strconv.Itoa(i))
|
||||
}
|
||||
return strings.Join(strs, ":")
|
||||
}
|
||||
|
||||
func (ca coreAccessor) enumValue(t Type) (*EnumValue, error) {
|
||||
e, ok := t.(*Enum)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not an enum: %s", t)
|
||||
}
|
||||
|
||||
if len(ca) > 1 {
|
||||
return nil, fmt.Errorf("invalid accessor %s for enum", ca)
|
||||
}
|
||||
|
||||
i := ca[0]
|
||||
if i >= len(e.Values) {
|
||||
return nil, fmt.Errorf("invalid index %d for %s", i, e)
|
||||
}
|
||||
|
||||
return &e.Values[i], nil
|
||||
}
|
||||
|
||||
type coreField struct {
|
||||
Type Type
|
||||
offset uint32
|
||||
}
|
||||
|
||||
func adjustOffset(base uint32, t Type, n int) (uint32, error) {
|
||||
size, err := Sizeof(t)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return base + (uint32(n) * uint32(size) * 8), nil
|
||||
}
|
||||
|
||||
// coreFindField descends into the local type using the accessor and tries to
|
||||
// find an equivalent field in target at each step.
|
||||
//
|
||||
// Returns the field and the offset of the field from the start of
|
||||
// target in bits.
|
||||
func coreFindField(local Type, localAcc coreAccessor, target Type) (_, _ coreField, _ error) {
|
||||
// The first index is used to offset a pointer of the base type like
|
||||
// when accessing an array.
|
||||
localOffset, err := adjustOffset(0, local, localAcc[0])
|
||||
if err != nil {
|
||||
return coreField{}, coreField{}, err
|
||||
}
|
||||
|
||||
targetOffset, err := adjustOffset(0, target, localAcc[0])
|
||||
if err != nil {
|
||||
return coreField{}, coreField{}, err
|
||||
}
|
||||
|
||||
if err := coreAreMembersCompatible(local, target); err != nil {
|
||||
return coreField{}, coreField{}, fmt.Errorf("fields: %w", err)
|
||||
}
|
||||
|
||||
var localMaybeFlex, targetMaybeFlex bool
|
||||
for _, acc := range localAcc[1:] {
|
||||
switch localType := local.(type) {
|
||||
case composite:
|
||||
// For composite types acc is used to find the field in the local type,
|
||||
// and then we try to find a field in target with the same name.
|
||||
localMembers := localType.members()
|
||||
if acc >= len(localMembers) {
|
||||
return coreField{}, coreField{}, fmt.Errorf("invalid accessor %d for %s", acc, local)
|
||||
}
|
||||
|
||||
localMember := localMembers[acc]
|
||||
if localMember.Name == "" {
|
||||
_, ok := localMember.Type.(composite)
|
||||
if !ok {
|
||||
return coreField{}, coreField{}, fmt.Errorf("unnamed field with type %s: %s", localMember.Type, ErrNotSupported)
|
||||
}
|
||||
|
||||
// This is an anonymous struct or union, ignore it.
|
||||
local = localMember.Type
|
||||
localOffset += localMember.OffsetBits
|
||||
localMaybeFlex = false
|
||||
continue
|
||||
}
|
||||
|
||||
targetType, ok := target.(composite)
|
||||
if !ok {
|
||||
return coreField{}, coreField{}, fmt.Errorf("target not composite: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
targetMember, last, err := coreFindMember(targetType, localMember.Name)
|
||||
if err != nil {
|
||||
return coreField{}, coreField{}, err
|
||||
}
|
||||
|
||||
if targetMember.BitfieldSize > 0 {
|
||||
return coreField{}, coreField{}, fmt.Errorf("field %q is a bitfield: %w", targetMember.Name, ErrNotSupported)
|
||||
}
|
||||
|
||||
local = localMember.Type
|
||||
localMaybeFlex = acc == len(localMembers)-1
|
||||
localOffset += localMember.OffsetBits
|
||||
target = targetMember.Type
|
||||
targetMaybeFlex = last
|
||||
targetOffset += targetMember.OffsetBits
|
||||
|
||||
case *Array:
|
||||
// For arrays, acc is the index in the target.
|
||||
targetType, ok := target.(*Array)
|
||||
if !ok {
|
||||
return coreField{}, coreField{}, fmt.Errorf("target not array: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
if localType.Nelems == 0 && !localMaybeFlex {
|
||||
return coreField{}, coreField{}, fmt.Errorf("local type has invalid flexible array")
|
||||
}
|
||||
if targetType.Nelems == 0 && !targetMaybeFlex {
|
||||
return coreField{}, coreField{}, fmt.Errorf("target type has invalid flexible array")
|
||||
}
|
||||
|
||||
if localType.Nelems > 0 && acc >= int(localType.Nelems) {
|
||||
return coreField{}, coreField{}, fmt.Errorf("invalid access of %s at index %d", localType, acc)
|
||||
}
|
||||
if targetType.Nelems > 0 && acc >= int(targetType.Nelems) {
|
||||
return coreField{}, coreField{}, fmt.Errorf("out of bounds access of target: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
local = localType.Type
|
||||
localMaybeFlex = false
|
||||
localOffset, err = adjustOffset(localOffset, local, acc)
|
||||
if err != nil {
|
||||
return coreField{}, coreField{}, err
|
||||
}
|
||||
|
||||
target = targetType.Type
|
||||
targetMaybeFlex = false
|
||||
targetOffset, err = adjustOffset(targetOffset, target, acc)
|
||||
if err != nil {
|
||||
return coreField{}, coreField{}, err
|
||||
}
|
||||
|
||||
default:
|
||||
return coreField{}, coreField{}, fmt.Errorf("relocate field of %T: %w", localType, ErrNotSupported)
|
||||
}
|
||||
|
||||
if err := coreAreMembersCompatible(local, target); err != nil {
|
||||
return coreField{}, coreField{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return coreField{local, localOffset}, coreField{target, targetOffset}, nil
|
||||
}
|
||||
|
||||
// coreFindMember finds a member in a composite type while handling anonymous
|
||||
// structs and unions.
|
||||
func coreFindMember(typ composite, name string) (Member, bool, error) {
|
||||
if name == "" {
|
||||
return Member{}, false, errors.New("can't search for anonymous member")
|
||||
}
|
||||
|
||||
type offsetTarget struct {
|
||||
composite
|
||||
offset uint32
|
||||
}
|
||||
|
||||
targets := []offsetTarget{{typ, 0}}
|
||||
visited := make(map[composite]bool)
|
||||
|
||||
for i := 0; i < len(targets); i++ {
|
||||
target := targets[i]
|
||||
|
||||
// Only visit targets once to prevent infinite recursion.
|
||||
if visited[target] {
|
||||
continue
|
||||
}
|
||||
if len(visited) >= maxTypeDepth {
|
||||
// This check is different than libbpf, which restricts the entire
|
||||
// path to BPF_CORE_SPEC_MAX_LEN items.
|
||||
return Member{}, false, fmt.Errorf("type is nested too deep")
|
||||
}
|
||||
visited[target] = true
|
||||
|
||||
members := target.members()
|
||||
for j, member := range members {
|
||||
if member.Name == name {
|
||||
// NB: This is safe because member is a copy.
|
||||
member.OffsetBits += target.offset
|
||||
return member, j == len(members)-1, nil
|
||||
}
|
||||
|
||||
// The names don't match, but this member could be an anonymous struct
|
||||
// or union.
|
||||
if member.Name != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
comp, ok := member.Type.(composite)
|
||||
if !ok {
|
||||
return Member{}, false, fmt.Errorf("anonymous non-composite type %T not allowed", member.Type)
|
||||
}
|
||||
|
||||
targets = append(targets, offsetTarget{comp, target.offset + member.OffsetBits})
|
||||
}
|
||||
}
|
||||
|
||||
return Member{}, false, fmt.Errorf("no matching member: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
// coreFindEnumValue follows localAcc to find the equivalent enum value in target.
|
||||
func coreFindEnumValue(local Type, localAcc coreAccessor, target Type) (localValue, targetValue *EnumValue, _ error) {
|
||||
localValue, err := localAcc.enumValue(local)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
targetEnum, ok := target.(*Enum)
|
||||
if !ok {
|
||||
return nil, nil, errImpossibleRelocation
|
||||
}
|
||||
|
||||
localName := newEssentialName(localValue.Name)
|
||||
for i, targetValue := range targetEnum.Values {
|
||||
if newEssentialName(targetValue.Name) != localName {
|
||||
continue
|
||||
}
|
||||
|
||||
return localValue, &targetEnum.Values[i], nil
|
||||
}
|
||||
|
||||
return nil, nil, errImpossibleRelocation
|
||||
}
|
||||
|
||||
/* The comment below is from bpf_core_types_are_compat in libbpf.c:
|
||||
*
|
||||
* Check local and target types for compatibility. This check is used for
|
||||
@@ -239,8 +736,10 @@ func parseCoreAccessor(accessor string) (coreAccessor, error) {
|
||||
* number of input args and compatible return and argument types.
|
||||
* These rules are not set in stone and probably will be adjusted as we get
|
||||
* more experience with using BPF CO-RE relocations.
|
||||
*
|
||||
* Returns errImpossibleRelocation if types are not compatible.
|
||||
*/
|
||||
func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
|
||||
func coreAreTypesCompatible(localType Type, targetType Type) error {
|
||||
var (
|
||||
localTs, targetTs typeDeque
|
||||
l, t = &localType, &targetType
|
||||
@@ -249,14 +748,14 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
|
||||
|
||||
for ; l != nil && t != nil; l, t = localTs.shift(), targetTs.shift() {
|
||||
if depth >= maxTypeDepth {
|
||||
return false, errors.New("types are nested too deep")
|
||||
return errors.New("types are nested too deep")
|
||||
}
|
||||
|
||||
localType = skipQualifierAndTypedef(*l)
|
||||
targetType = skipQualifierAndTypedef(*t)
|
||||
localType = *l
|
||||
targetType = *t
|
||||
|
||||
if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
|
||||
return false, nil
|
||||
return fmt.Errorf("type mismatch: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
switch lv := (localType).(type) {
|
||||
@@ -266,7 +765,7 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
|
||||
case *Int:
|
||||
tv := targetType.(*Int)
|
||||
if lv.isBitfield() || tv.isBitfield() {
|
||||
return false, nil
|
||||
return fmt.Errorf("bitfield: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
case *Pointer, *Array:
|
||||
@@ -277,7 +776,7 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
|
||||
case *FuncProto:
|
||||
tv := targetType.(*FuncProto)
|
||||
if len(lv.Params) != len(tv.Params) {
|
||||
return false, nil
|
||||
return fmt.Errorf("function param mismatch: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
depth++
|
||||
@@ -285,22 +784,24 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
|
||||
targetType.walk(&targetTs)
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported type %T", localType)
|
||||
return fmt.Errorf("unsupported type %T", localType)
|
||||
}
|
||||
}
|
||||
|
||||
if l != nil {
|
||||
return false, fmt.Errorf("dangling local type %T", *l)
|
||||
return fmt.Errorf("dangling local type %T", *l)
|
||||
}
|
||||
|
||||
if t != nil {
|
||||
return false, fmt.Errorf("dangling target type %T", *t)
|
||||
return fmt.Errorf("dangling target type %T", *t)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
/* The comment below is from bpf_core_fields_are_compat in libbpf.c:
|
||||
/* coreAreMembersCompatible checks two types for field-based relocation compatibility.
|
||||
*
|
||||
* The comment below is from bpf_core_fields_are_compat in libbpf.c:
|
||||
*
|
||||
* Check two types for compatibility for the purpose of field access
|
||||
* relocation. const/volatile/restrict and typedefs are skipped to ensure we
|
||||
@@ -312,67 +813,66 @@ func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) {
|
||||
* least one of enums should be anonymous;
|
||||
* - for ENUMs, check sizes, names are ignored;
|
||||
* - for INT, size and signedness are ignored;
|
||||
* - any two FLOATs are always compatible;
|
||||
* - for ARRAY, dimensionality is ignored, element types are checked for
|
||||
* compatibility recursively;
|
||||
* [ NB: coreAreMembersCompatible doesn't recurse, this check is done
|
||||
* by coreFindField. ]
|
||||
* - everything else shouldn't be ever a target of relocation.
|
||||
* These rules are not set in stone and probably will be adjusted as we get
|
||||
* more experience with using BPF CO-RE relocations.
|
||||
*
|
||||
* Returns errImpossibleRelocation if the members are not compatible.
|
||||
*/
|
||||
func coreAreMembersCompatible(localType Type, targetType Type) (bool, error) {
|
||||
doNamesMatch := func(a, b string) bool {
|
||||
func coreAreMembersCompatible(localType Type, targetType Type) error {
|
||||
doNamesMatch := func(a, b string) error {
|
||||
if a == "" || b == "" {
|
||||
// allow anonymous and named type to match
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
return essentialName(a) == essentialName(b)
|
||||
if newEssentialName(a) == newEssentialName(b) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("names don't match: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
for depth := 0; depth <= maxTypeDepth; depth++ {
|
||||
localType = skipQualifierAndTypedef(localType)
|
||||
targetType = skipQualifierAndTypedef(targetType)
|
||||
|
||||
_, lok := localType.(composite)
|
||||
_, tok := targetType.(composite)
|
||||
if lok && tok {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch lv := localType.(type) {
|
||||
case *Pointer:
|
||||
return true, nil
|
||||
|
||||
case *Enum:
|
||||
tv := targetType.(*Enum)
|
||||
return doNamesMatch(lv.name(), tv.name()), nil
|
||||
|
||||
case *Fwd:
|
||||
tv := targetType.(*Fwd)
|
||||
return doNamesMatch(lv.name(), tv.name()), nil
|
||||
|
||||
case *Int:
|
||||
tv := targetType.(*Int)
|
||||
return !lv.isBitfield() && !tv.isBitfield(), nil
|
||||
|
||||
case *Array:
|
||||
tv := targetType.(*Array)
|
||||
|
||||
localType = lv.Type
|
||||
targetType = tv.Type
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported type %T", localType)
|
||||
}
|
||||
_, lok := localType.(composite)
|
||||
_, tok := targetType.(composite)
|
||||
if lok && tok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return false, errors.New("types are nested too deep")
|
||||
if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
|
||||
return fmt.Errorf("type mismatch: %w", errImpossibleRelocation)
|
||||
}
|
||||
|
||||
switch lv := localType.(type) {
|
||||
case *Array, *Pointer, *Float:
|
||||
return nil
|
||||
|
||||
case *Enum:
|
||||
tv := targetType.(*Enum)
|
||||
return doNamesMatch(lv.Name, tv.Name)
|
||||
|
||||
case *Fwd:
|
||||
tv := targetType.(*Fwd)
|
||||
return doNamesMatch(lv.Name, tv.Name)
|
||||
|
||||
case *Int:
|
||||
tv := targetType.(*Int)
|
||||
if lv.isBitfield() || tv.isBitfield() {
|
||||
return fmt.Errorf("bitfield: %w", errImpossibleRelocation)
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("type %s: %w", localType, ErrNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
func skipQualifierAndTypedef(typ Type) Type {
|
||||
func skipQualifiersAndTypedefs(typ Type) (Type, error) {
|
||||
result := typ
|
||||
for depth := 0; depth <= maxTypeDepth; depth++ {
|
||||
switch v := (result).(type) {
|
||||
@@ -381,8 +881,21 @@ func skipQualifierAndTypedef(typ Type) Type {
|
||||
case *Typedef:
|
||||
result = v.Type
|
||||
default:
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
return typ
|
||||
return nil, errors.New("exceeded type depth")
|
||||
}
|
||||
|
||||
func skipQualifiers(typ Type) (Type, error) {
|
||||
result := typ
|
||||
for depth := 0; depth <= maxTypeDepth; depth++ {
|
||||
switch v := (result).(type) {
|
||||
case qualifier:
|
||||
result = v.qualify()
|
||||
default:
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("exceeded type depth")
|
||||
}
|
||||
|
||||
+443
-227
@@ -1,23 +1,73 @@
|
||||
package btf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
|
||||
"github.com/cilium/ebpf/asm"
|
||||
"github.com/cilium/ebpf/internal"
|
||||
)
|
||||
|
||||
// extInfo contains extended program metadata.
|
||||
//
|
||||
// It is indexed per section.
|
||||
type extInfo struct {
|
||||
funcInfos map[string]FuncInfos
|
||||
lineInfos map[string]LineInfos
|
||||
relos map[string]CoreRelos
|
||||
}
|
||||
|
||||
// loadExtInfos parses the .BTF.ext section into its constituent parts.
|
||||
func loadExtInfos(r io.ReaderAt, bo binary.ByteOrder, strings stringTable) (*extInfo, 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))
|
||||
funcInfos, err := parseFuncInfos(buf, bo, strings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing BTF function info: %w", err)
|
||||
}
|
||||
|
||||
buf = internal.NewBufferedSectionReader(r, extHeader.lineInfoStart(), int64(extHeader.LineInfoLen))
|
||||
lineInfos, err := parseLineInfos(buf, bo, strings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing BTF line info: %w", err)
|
||||
}
|
||||
|
||||
relos := make(map[string]CoreRelos)
|
||||
if coreHeader != nil && coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 {
|
||||
buf = internal.NewBufferedSectionReader(r, extHeader.coreReloStart(coreHeader), int64(coreHeader.CoreReloLen))
|
||||
relos, err = parseCoreRelos(buf, bo, strings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing CO-RE relocation info: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &extInfo{funcInfos, lineInfos, relos}, nil
|
||||
}
|
||||
|
||||
// btfExtHeader is found at the start of the .BTF.ext section.
|
||||
type btfExtHeader struct {
|
||||
Magic uint16
|
||||
Version uint8
|
||||
Flags uint8
|
||||
HdrLen uint32
|
||||
|
||||
// HdrLen is larger than the size of struct btfExtHeader when it is
|
||||
// immediately followed by a btfExtCoreHeader.
|
||||
HdrLen uint32
|
||||
|
||||
FuncInfoOff uint32
|
||||
FuncInfoLen uint32
|
||||
@@ -25,82 +75,74 @@ type btfExtHeader struct {
|
||||
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
|
||||
}
|
||||
|
||||
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, coreRelos map[string]bpfCoreRelos, err error) {
|
||||
var header btfExtHeader
|
||||
// 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, &header); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("can't read header: %v", err)
|
||||
if err := binary.Read(r, bo, &coreHeader); err != nil {
|
||||
return nil, fmt.Errorf("can't read header: %v", err)
|
||||
}
|
||||
|
||||
if header.Magic != btfMagic {
|
||||
return nil, nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
|
||||
}
|
||||
|
||||
if header.Version != 1 {
|
||||
return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version)
|
||||
}
|
||||
|
||||
if header.Flags != 0 {
|
||||
return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
|
||||
}
|
||||
|
||||
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
|
||||
if remainder < 0 {
|
||||
return nil, nil, nil, errors.New("header is too short")
|
||||
}
|
||||
|
||||
coreHdrSize := int64(binary.Size(&coreHeader))
|
||||
if remainder >= coreHdrSize {
|
||||
if err := binary.Read(r, bo, &coreHeader); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("can't read CO-RE relocation header: %v", err)
|
||||
}
|
||||
remainder -= coreHdrSize
|
||||
}
|
||||
|
||||
// Of course, the .BTF.ext header has different semantics than the
|
||||
// .BTF ext header. We need to ignore non-null values.
|
||||
_, err = io.CopyN(ioutil.Discard, r, remainder)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("header padding: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
|
||||
}
|
||||
|
||||
buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen)))
|
||||
funcInfo, err = parseExtInfo(buf, bo, strings)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("function info: %w", err)
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
|
||||
}
|
||||
|
||||
buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen)))
|
||||
lineInfo, err = parseExtInfo(buf, bo, strings)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("line info: %w", err)
|
||||
}
|
||||
|
||||
if coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 {
|
||||
if _, err := r.Seek(int64(header.HdrLen+coreHeader.CoreReloOff), io.SeekStart); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("can't seek to CO-RE relocation section: %v", err)
|
||||
}
|
||||
|
||||
coreRelos, err = parseExtInfoRelos(io.LimitReader(r, int64(coreHeader.CoreReloLen)), bo, strings)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("CO-RE relocation info: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return funcInfo, lineInfo, coreRelos, nil
|
||||
return &coreHeader, nil
|
||||
}
|
||||
|
||||
type btfExtInfoSec struct {
|
||||
@@ -108,161 +150,11 @@ type btfExtInfoSec struct {
|
||||
NumInfo uint32
|
||||
}
|
||||
|
||||
type extInfoRecord struct {
|
||||
InsnOff uint64
|
||||
Opaque []byte
|
||||
}
|
||||
|
||||
type extInfo struct {
|
||||
recordSize uint32
|
||||
records []extInfoRecord
|
||||
}
|
||||
|
||||
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
|
||||
if other.recordSize != ei.recordSize {
|
||||
return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
|
||||
}
|
||||
|
||||
records := make([]extInfoRecord, 0, len(ei.records)+len(other.records))
|
||||
records = append(records, ei.records...)
|
||||
for _, info := range other.records {
|
||||
records = append(records, extInfoRecord{
|
||||
InsnOff: info.InsnOff + offset,
|
||||
Opaque: info.Opaque,
|
||||
})
|
||||
}
|
||||
return extInfo{ei.recordSize, records}, nil
|
||||
}
|
||||
|
||||
func (ei extInfo) MarshalBinary() ([]byte, error) {
|
||||
if len(ei.records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, int(ei.recordSize)*len(ei.records)))
|
||||
for _, info := range ei.records {
|
||||
// The kernel expects offsets in number of raw bpf instructions,
|
||||
// while the ELF tracks it in bytes.
|
||||
insnOff := uint32(info.InsnOff / asm.InstructionSize)
|
||||
if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil {
|
||||
return nil, fmt.Errorf("can't write instruction offset: %v", err)
|
||||
}
|
||||
|
||||
buf.Write(info.Opaque)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
|
||||
const maxRecordSize = 256
|
||||
|
||||
var recordSize uint32
|
||||
if err := binary.Read(r, bo, &recordSize); err != nil {
|
||||
return nil, fmt.Errorf("can't read record size: %v", err)
|
||||
}
|
||||
|
||||
if recordSize < 4 {
|
||||
// Need at least insnOff
|
||||
return nil, errors.New("record size too short")
|
||||
}
|
||||
if recordSize > maxRecordSize {
|
||||
return nil, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
|
||||
}
|
||||
|
||||
result := make(map[string]extInfo)
|
||||
for {
|
||||
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
|
||||
if errors.Is(err, io.EOF) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var records []extInfoRecord
|
||||
for i := uint32(0); i < infoHeader.NumInfo; i++ {
|
||||
var byteOff uint32
|
||||
if err := binary.Read(r, bo, &byteOff); err != nil {
|
||||
return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err)
|
||||
}
|
||||
|
||||
buf := make([]byte, int(recordSize-4))
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return nil, fmt.Errorf("section %v: can't read record: %v", secName, err)
|
||||
}
|
||||
|
||||
if byteOff%asm.InstructionSize != 0 {
|
||||
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
|
||||
}
|
||||
|
||||
records = append(records, extInfoRecord{uint64(byteOff), buf})
|
||||
}
|
||||
|
||||
result[secName] = extInfo{
|
||||
recordSize,
|
||||
records,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bpfCoreRelo matches `struct bpf_core_relo` from the kernel
|
||||
type bpfCoreRelo struct {
|
||||
InsnOff uint32
|
||||
TypeID TypeID
|
||||
AccessStrOff uint32
|
||||
ReloKind coreReloKind
|
||||
}
|
||||
|
||||
type bpfCoreRelos []bpfCoreRelo
|
||||
|
||||
// append two slices of extInfoRelo to each other. The InsnOff of b are adjusted
|
||||
// by offset.
|
||||
func (r bpfCoreRelos) append(other bpfCoreRelos, offset uint64) bpfCoreRelos {
|
||||
result := make([]bpfCoreRelo, 0, len(r)+len(other))
|
||||
result = append(result, r...)
|
||||
for _, relo := range other {
|
||||
relo.InsnOff += uint32(offset)
|
||||
result = append(result, relo)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var extInfoReloSize = binary.Size(bpfCoreRelo{})
|
||||
|
||||
func parseExtInfoRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]bpfCoreRelos, error) {
|
||||
var recordSize uint32
|
||||
if err := binary.Read(r, bo, &recordSize); err != nil {
|
||||
return nil, fmt.Errorf("read record size: %v", err)
|
||||
}
|
||||
|
||||
if recordSize != uint32(extInfoReloSize) {
|
||||
return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize)
|
||||
}
|
||||
|
||||
result := make(map[string]bpfCoreRelos)
|
||||
for {
|
||||
secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
|
||||
if errors.Is(err, io.EOF) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var relos []bpfCoreRelo
|
||||
for i := uint32(0); i < infoHeader.NumInfo; i++ {
|
||||
var relo bpfCoreRelo
|
||||
if err := binary.Read(r, bo, &relo); err != nil {
|
||||
return nil, fmt.Errorf("section %v: read record: %v", secName, err)
|
||||
}
|
||||
|
||||
if relo.InsnOff%asm.InstructionSize != 0 {
|
||||
return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, relo.InsnOff)
|
||||
}
|
||||
|
||||
relos = append(relos, relo)
|
||||
}
|
||||
|
||||
result[secName] = relos
|
||||
}
|
||||
}
|
||||
|
||||
func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (string, *btfExtInfoSec, error) {
|
||||
// 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)
|
||||
@@ -272,6 +164,9 @@ func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (
|
||||
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)
|
||||
@@ -279,3 +174,324 @@ func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// FuncInfo represents the location and type ID of a function in a BPF ELF.
|
||||
type FuncInfo struct {
|
||||
// Instruction offset of the function within an ELF section.
|
||||
// Always zero after parsing a funcinfo from an ELF, instruction streams
|
||||
// are split on function boundaries.
|
||||
InsnOff uint32
|
||||
TypeID TypeID
|
||||
}
|
||||
|
||||
// Name looks up the FuncInfo's corresponding function name in the given spec.
|
||||
func (fi FuncInfo) Name(spec *Spec) (string, error) {
|
||||
// Look up function name based on type ID.
|
||||
typ, err := spec.TypeByID(fi.TypeID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("looking up type by ID: %w", err)
|
||||
}
|
||||
if _, ok := typ.(*Func); !ok {
|
||||
return "", 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 name := typ.TypeName(); name != "" {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Func with type ID %d doesn't have a name", fi.TypeID)
|
||||
}
|
||||
|
||||
// Marshal writes the binary representation of the FuncInfo to w.
|
||||
// The function offset is converted from bytes to instructions.
|
||||
func (fi FuncInfo) Marshal(w io.Writer, offset uint64) error {
|
||||
fi.InsnOff += uint32(offset)
|
||||
// The kernel expects offsets in number of raw bpf instructions,
|
||||
// while the ELF tracks it in bytes.
|
||||
fi.InsnOff /= asm.InstructionSize
|
||||
return binary.Write(w, internal.NativeEndian, fi)
|
||||
}
|
||||
|
||||
type FuncInfos []FuncInfo
|
||||
|
||||
// funcForOffset returns the function that the instruction at the given
|
||||
// ELF section offset belongs to.
|
||||
//
|
||||
// For example, consider an ELF section that contains 3 functions (a, b, c)
|
||||
// at offsets 0, 10 and 15 respectively. Offset 5 will return function a,
|
||||
// offset 12 will return b, offset >= 15 will return c, etc.
|
||||
func (infos FuncInfos) funcForOffset(offset uint32) *FuncInfo {
|
||||
for n, fi := range infos {
|
||||
// Iterator went past the offset the caller is looking for,
|
||||
// no point in continuing the search.
|
||||
if offset < fi.InsnOff {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If there is no next item in the list, or if the given offset
|
||||
// is smaller than the next function, the offset belongs to
|
||||
// the current function.
|
||||
if n+1 >= len(infos) || offset < infos[n+1].InsnOff {
|
||||
return &fi
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseLineInfos 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]FuncInfos, error) {
|
||||
recordSize, err := parseExtInfoRecordSize(r, bo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]FuncInfos)
|
||||
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) (FuncInfos, error) {
|
||||
var out FuncInfos
|
||||
var fi FuncInfo
|
||||
|
||||
if exp, got := uint32(binary.Size(fi)), 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)
|
||||
}
|
||||
|
||||
out = append(out, fi)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// LineInfo represents the location and contents of a single line of source
|
||||
// code a BPF ELF was compiled from.
|
||||
type LineInfo struct {
|
||||
// Instruction offset of the function within an ELF section.
|
||||
// After parsing a LineInfo from an ELF, this offset is relative to
|
||||
// the function body instead of an ELF section.
|
||||
InsnOff uint32
|
||||
FileNameOff uint32
|
||||
LineOff uint32
|
||||
LineCol uint32
|
||||
}
|
||||
|
||||
// Marshal writes the binary representation of the LineInfo to w.
|
||||
// The instruction offset is converted from bytes to instructions.
|
||||
func (li LineInfo) Marshal(w io.Writer, offset uint64) error {
|
||||
li.InsnOff += uint32(offset)
|
||||
// The kernel expects offsets in number of raw bpf instructions,
|
||||
// while the ELF tracks it in bytes.
|
||||
li.InsnOff /= asm.InstructionSize
|
||||
return binary.Write(w, internal.NativeEndian, li)
|
||||
}
|
||||
|
||||
type LineInfos []LineInfo
|
||||
|
||||
// Marshal writes the binary representation of the LineInfos to w.
|
||||
func (li LineInfos) Marshal(w io.Writer, off uint64) error {
|
||||
if len(li) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, info := range li {
|
||||
if err := info.Marshal(w, off); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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]LineInfos, error) {
|
||||
recordSize, err := parseExtInfoRecordSize(r, bo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]LineInfos)
|
||||
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) (LineInfos, error) {
|
||||
var out LineInfos
|
||||
var li LineInfo
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 CoreRelo struct {
|
||||
insnOff uint32
|
||||
typeID TypeID
|
||||
accessor coreAccessor
|
||||
kind COREKind
|
||||
}
|
||||
|
||||
type CoreRelos []CoreRelo
|
||||
|
||||
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]CoreRelos, 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]CoreRelos)
|
||||
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, strings)
|
||||
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, strings stringTable) (CoreRelos, error) {
|
||||
var out CoreRelos
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
out = append(out, CoreRelo{
|
||||
relo.InsnOff,
|
||||
relo.TypeID,
|
||||
accessor,
|
||||
relo.Kind,
|
||||
})
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
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, err := skipQualifiers(typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := typ.(type) {
|
||||
case *Enum:
|
||||
fmt.Fprintf(&gf.w, "type %s int32", name)
|
||||
if len(v.Values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
gf.w.WriteString("; const ( ")
|
||||
for _, ev := range v.Values {
|
||||
id := gf.enumIdentifier(name, ev.Name)
|
||||
fmt.Fprintf(&gf.w, "%s %s = %d; ", id, name, ev.Value)
|
||||
}
|
||||
gf.w.WriteString(")")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(&gf.w, "type %s ", name)
|
||||
return gf.writeTypeLit(typ, 0)
|
||||
}
|
||||
|
||||
// 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, err := skipQualifiers(typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
typ, err := skipQualifiers(typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := typ.(type) {
|
||||
case *Int:
|
||||
gf.writeIntLit(v)
|
||||
|
||||
case *Enum:
|
||||
gf.w.WriteString("int32")
|
||||
|
||||
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 %s: %w", typ, ErrNotSupported)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", typ, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gf *GoFormatter) writeIntLit(i *Int) {
|
||||
// NB: Encoding.IsChar is ignored.
|
||||
if i.Encoding.IsBool() && i.Size == 1 {
|
||||
gf.w.WriteString("bool")
|
||||
return
|
||||
}
|
||||
|
||||
bits := i.Size * 8
|
||||
if i.Encoding.IsSigned() {
|
||||
fmt.Fprintf(&gf.w, "int%d", bits)
|
||||
} else {
|
||||
fmt.Fprintf(&gf.w, "uint%d", bits)
|
||||
}
|
||||
}
|
||||
|
||||
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.OffsetBits / 8
|
||||
if n := offset - prevOffset; skippedBitfield && n > 0 {
|
||||
fmt.Fprintf(&gf.w, "_ [%d]byte /* unsupported bitfield */; ", n)
|
||||
} else {
|
||||
gf.writePadding(n)
|
||||
}
|
||||
|
||||
size, err := Sizeof(m.Type)
|
||||
if err != nil {
|
||||
return fmt.Errorf("field %d: %w", i, err)
|
||||
}
|
||||
prevOffset = offset + uint32(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.OffsetBits%8 != 0 {
|
||||
return fmt.Errorf("unsupported offset %d", m.OffsetBits)
|
||||
}
|
||||
|
||||
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 := vsi.Type.(*Var)
|
||||
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)
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
// +build gofuzz
|
||||
|
||||
// Use with https://github.com/dvyukov/go-fuzz
|
||||
|
||||
package btf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
)
|
||||
|
||||
func FuzzSpec(data []byte) int {
|
||||
if len(data) < binary.Size(btfHeader{}) {
|
||||
return -1
|
||||
}
|
||||
|
||||
spec, err := loadNakedSpec(bytes.NewReader(data), internal.NativeEndian, nil, nil)
|
||||
if err != nil {
|
||||
if spec != nil {
|
||||
panic("spec is not nil")
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if spec == nil {
|
||||
panic("spec is nil")
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func FuzzExtInfo(data []byte) int {
|
||||
if len(data) < binary.Size(btfExtHeader{}) {
|
||||
return -1
|
||||
}
|
||||
|
||||
table := stringTable("\x00foo\x00barfoo\x00")
|
||||
info, err := parseExtInfo(bytes.NewReader(data), internal.NativeEndian, table)
|
||||
if err != nil {
|
||||
if info != nil {
|
||||
panic("info is not nil")
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if info == nil {
|
||||
panic("info is nil")
|
||||
}
|
||||
return 1
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package btf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/cilium/ebpf/internal"
|
||||
"github.com/cilium/ebpf/internal/sys"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
// info describes a BTF object.
|
||||
type info struct {
|
||||
BTF *Spec
|
||||
ID ID
|
||||
// Name is an identifying name for the BTF, currently only used by the
|
||||
// kernel.
|
||||
Name string
|
||||
// KernelBTF is true if the BTf originated with the kernel and not
|
||||
// userspace.
|
||||
KernelBTF bool
|
||||
}
|
||||
|
||||
func newInfoFromFd(fd *sys.FD) (*info, 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, err
|
||||
}
|
||||
|
||||
btfBuffer := make([]byte, btfInfo.BtfSize)
|
||||
nameBuffer := make([]byte, btfInfo.NameLen)
|
||||
btfInfo.Btf, btfInfo.BtfSize = sys.NewSlicePointerLen(btfBuffer)
|
||||
btfInfo.Name, btfInfo.NameLen = sys.NewSlicePointerLen(nameBuffer)
|
||||
if err := sys.ObjInfo(fd, &btfInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec, err := loadRawSpec(bytes.NewReader(btfBuffer), internal.NativeEndian, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &info{
|
||||
BTF: spec,
|
||||
ID: ID(btfInfo.Id),
|
||||
Name: unix.ByteSliceToString(nameBuffer),
|
||||
KernelBTF: btfInfo.KernelBtf != 0,
|
||||
}, nil
|
||||
}
|
||||
+1
-7
@@ -5,13 +5,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type stringTable []byte
|
||||
|
||||
func readStringTable(r io.Reader) (stringTable, error) {
|
||||
contents, err := ioutil.ReadAll(r)
|
||||
contents, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read string table: %v", err)
|
||||
}
|
||||
@@ -53,8 +52,3 @@ func (st stringTable) Lookup(offset uint32) (string, error) {
|
||||
|
||||
return string(str[:end]), nil
|
||||
}
|
||||
|
||||
func (st stringTable) LookupName(offset uint32) (Name, error) {
|
||||
str, err := st.Lookup(offset)
|
||||
return Name(str), err
|
||||
}
|
||||
|
||||
+215
-94
@@ -1,7 +1,6 @@
|
||||
package btf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
@@ -19,9 +18,12 @@ func (tid TypeID) ID() TypeID {
|
||||
|
||||
// Type represents a type described by BTF.
|
||||
type Type interface {
|
||||
// The type ID of the Type within this BTF spec.
|
||||
ID() TypeID
|
||||
|
||||
String() string
|
||||
// Name of the type, empty for anonymous types and types that cannot
|
||||
// carry a name, like Void and Pointer.
|
||||
TypeName() string
|
||||
|
||||
// Make a copy of the type, without copying Type members.
|
||||
copy() Type
|
||||
@@ -29,33 +31,32 @@ type Type interface {
|
||||
// Enumerate all nested Types. Repeated calls must visit nested
|
||||
// types in the same order.
|
||||
walk(*typeDeque)
|
||||
|
||||
String() string
|
||||
}
|
||||
|
||||
// namedType is a type with a name.
|
||||
//
|
||||
// Most named types simply embed Name.
|
||||
type namedType interface {
|
||||
Type
|
||||
name() string
|
||||
}
|
||||
|
||||
// Name identifies a type.
|
||||
//
|
||||
// Anonymous types have an empty name.
|
||||
type Name string
|
||||
|
||||
func (n Name) name() string {
|
||||
return string(n)
|
||||
}
|
||||
var (
|
||||
_ Type = (*Int)(nil)
|
||||
_ Type = (*Struct)(nil)
|
||||
_ Type = (*Union)(nil)
|
||||
_ Type = (*Enum)(nil)
|
||||
_ Type = (*Fwd)(nil)
|
||||
_ Type = (*Func)(nil)
|
||||
_ Type = (*Typedef)(nil)
|
||||
_ Type = (*Var)(nil)
|
||||
_ Type = (*Datasec)(nil)
|
||||
_ Type = (*Float)(nil)
|
||||
)
|
||||
|
||||
// Void is the unit type of BTF.
|
||||
type Void struct{}
|
||||
|
||||
func (v *Void) ID() TypeID { return 0 }
|
||||
func (v *Void) String() string { return "void#0" }
|
||||
func (v *Void) size() uint32 { return 0 }
|
||||
func (v *Void) copy() Type { return (*Void)(nil) }
|
||||
func (v *Void) walk(*typeDeque) {}
|
||||
func (v *Void) ID() TypeID { return 0 }
|
||||
func (v *Void) String() string { return "void#0" }
|
||||
func (v *Void) TypeName() string { return "" }
|
||||
func (v *Void) size() uint32 { return 0 }
|
||||
func (v *Void) copy() Type { return (*Void)(nil) }
|
||||
func (v *Void) walk(*typeDeque) {}
|
||||
|
||||
type IntEncoding byte
|
||||
|
||||
@@ -65,32 +66,43 @@ const (
|
||||
Bool
|
||||
)
|
||||
|
||||
func (ie IntEncoding) IsSigned() bool {
|
||||
return ie&Signed != 0
|
||||
}
|
||||
|
||||
func (ie IntEncoding) IsChar() bool {
|
||||
return ie&Char != 0
|
||||
}
|
||||
|
||||
func (ie IntEncoding) IsBool() bool {
|
||||
return ie&Bool != 0
|
||||
}
|
||||
|
||||
// Int is an integer of a given length.
|
||||
type Int struct {
|
||||
TypeID
|
||||
Name
|
||||
|
||||
Name string
|
||||
|
||||
// The size of the integer in bytes.
|
||||
Size uint32
|
||||
Encoding IntEncoding
|
||||
// Offset is the starting bit offset. Currently always 0.
|
||||
// OffsetBits is the starting bit offset. Currently always 0.
|
||||
// See https://www.kernel.org/doc/html/latest/bpf/btf.html#btf-kind-int
|
||||
Offset uint32
|
||||
Bits byte
|
||||
OffsetBits uint32
|
||||
Bits byte
|
||||
}
|
||||
|
||||
var _ namedType = (*Int)(nil)
|
||||
|
||||
func (i *Int) String() string {
|
||||
var s strings.Builder
|
||||
|
||||
switch {
|
||||
case i.Encoding&Char != 0:
|
||||
case i.Encoding.IsChar():
|
||||
s.WriteString("char")
|
||||
case i.Encoding&Bool != 0:
|
||||
case i.Encoding.IsBool():
|
||||
s.WriteString("bool")
|
||||
default:
|
||||
if i.Encoding&Signed == 0 {
|
||||
if !i.Encoding.IsSigned() {
|
||||
s.WriteRune('u')
|
||||
}
|
||||
s.WriteString("int")
|
||||
@@ -106,15 +118,16 @@ func (i *Int) String() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (i *Int) size() uint32 { return i.Size }
|
||||
func (i *Int) walk(*typeDeque) {}
|
||||
func (i *Int) TypeName() string { return i.Name }
|
||||
func (i *Int) size() uint32 { return i.Size }
|
||||
func (i *Int) walk(*typeDeque) {}
|
||||
func (i *Int) copy() Type {
|
||||
cpy := *i
|
||||
return &cpy
|
||||
}
|
||||
|
||||
func (i *Int) isBitfield() bool {
|
||||
return i.Offset > 0
|
||||
return i.OffsetBits > 0
|
||||
}
|
||||
|
||||
// Pointer is a pointer to another type.
|
||||
@@ -127,6 +140,7 @@ func (p *Pointer) String() string {
|
||||
return fmt.Sprintf("pointer#%d[target=#%d]", p.TypeID, p.Target.ID())
|
||||
}
|
||||
|
||||
func (p *Pointer) TypeName() string { return "" }
|
||||
func (p *Pointer) size() uint32 { return 8 }
|
||||
func (p *Pointer) walk(tdq *typeDeque) { tdq.push(&p.Target) }
|
||||
func (p *Pointer) copy() Type {
|
||||
@@ -145,6 +159,8 @@ func (arr *Array) String() string {
|
||||
return fmt.Sprintf("array#%d[type=#%d n=%d]", arr.TypeID, arr.Type.ID(), arr.Nelems)
|
||||
}
|
||||
|
||||
func (arr *Array) TypeName() string { return "" }
|
||||
|
||||
func (arr *Array) walk(tdq *typeDeque) { tdq.push(&arr.Type) }
|
||||
func (arr *Array) copy() Type {
|
||||
cpy := *arr
|
||||
@@ -154,7 +170,7 @@ func (arr *Array) copy() Type {
|
||||
// Struct is a compound type of consecutive members.
|
||||
type Struct struct {
|
||||
TypeID
|
||||
Name
|
||||
Name string
|
||||
// The size of the struct including padding, in bytes
|
||||
Size uint32
|
||||
Members []Member
|
||||
@@ -164,6 +180,8 @@ func (s *Struct) String() string {
|
||||
return fmt.Sprintf("struct#%d[%q]", s.TypeID, s.Name)
|
||||
}
|
||||
|
||||
func (s *Struct) TypeName() string { return s.Name }
|
||||
|
||||
func (s *Struct) size() uint32 { return s.Size }
|
||||
|
||||
func (s *Struct) walk(tdq *typeDeque) {
|
||||
@@ -174,8 +192,7 @@ func (s *Struct) walk(tdq *typeDeque) {
|
||||
|
||||
func (s *Struct) copy() Type {
|
||||
cpy := *s
|
||||
cpy.Members = make([]Member, len(s.Members))
|
||||
copy(cpy.Members, s.Members)
|
||||
cpy.Members = copyMembers(s.Members)
|
||||
return &cpy
|
||||
}
|
||||
|
||||
@@ -186,7 +203,7 @@ func (s *Struct) members() []Member {
|
||||
// Union is a compound type where members occupy the same memory.
|
||||
type Union struct {
|
||||
TypeID
|
||||
Name
|
||||
Name string
|
||||
// The size of the union including padding, in bytes.
|
||||
Size uint32
|
||||
Members []Member
|
||||
@@ -196,6 +213,8 @@ func (u *Union) String() string {
|
||||
return fmt.Sprintf("union#%d[%q]", u.TypeID, u.Name)
|
||||
}
|
||||
|
||||
func (u *Union) TypeName() string { return u.Name }
|
||||
|
||||
func (u *Union) size() uint32 { return u.Size }
|
||||
|
||||
func (u *Union) walk(tdq *typeDeque) {
|
||||
@@ -206,8 +225,7 @@ func (u *Union) walk(tdq *typeDeque) {
|
||||
|
||||
func (u *Union) copy() Type {
|
||||
cpy := *u
|
||||
cpy.Members = make([]Member, len(u.Members))
|
||||
copy(cpy.Members, u.Members)
|
||||
cpy.Members = copyMembers(u.Members)
|
||||
return &cpy
|
||||
}
|
||||
|
||||
@@ -215,6 +233,12 @@ func (u *Union) members() []Member {
|
||||
return u.Members
|
||||
}
|
||||
|
||||
func copyMembers(orig []Member) []Member {
|
||||
cpy := make([]Member, len(orig))
|
||||
copy(cpy, orig)
|
||||
return cpy
|
||||
}
|
||||
|
||||
type composite interface {
|
||||
members() []Member
|
||||
}
|
||||
@@ -228,17 +252,17 @@ var (
|
||||
//
|
||||
// It is not a valid Type.
|
||||
type Member struct {
|
||||
Name
|
||||
Name string
|
||||
Type Type
|
||||
// Offset is the bit offset of this member
|
||||
Offset uint32
|
||||
// OffsetBits is the bit offset of this member.
|
||||
OffsetBits uint32
|
||||
BitfieldSize uint32
|
||||
}
|
||||
|
||||
// Enum lists possible values.
|
||||
type Enum struct {
|
||||
TypeID
|
||||
Name
|
||||
Name string
|
||||
Values []EnumValue
|
||||
}
|
||||
|
||||
@@ -246,11 +270,13 @@ func (e *Enum) String() string {
|
||||
return fmt.Sprintf("enum#%d[%q]", e.TypeID, e.Name)
|
||||
}
|
||||
|
||||
func (e *Enum) TypeName() string { return e.Name }
|
||||
|
||||
// EnumValue is part of an Enum
|
||||
//
|
||||
// Is is not a valid Type
|
||||
type EnumValue struct {
|
||||
Name
|
||||
Name string
|
||||
Value int32
|
||||
}
|
||||
|
||||
@@ -286,7 +312,7 @@ func (fk FwdKind) String() string {
|
||||
// Fwd is a forward declaration of a Type.
|
||||
type Fwd struct {
|
||||
TypeID
|
||||
Name
|
||||
Name string
|
||||
Kind FwdKind
|
||||
}
|
||||
|
||||
@@ -294,6 +320,8 @@ func (f *Fwd) String() string {
|
||||
return fmt.Sprintf("fwd#%d[%s %q]", f.TypeID, f.Kind, f.Name)
|
||||
}
|
||||
|
||||
func (f *Fwd) TypeName() string { return f.Name }
|
||||
|
||||
func (f *Fwd) walk(*typeDeque) {}
|
||||
func (f *Fwd) copy() Type {
|
||||
cpy := *f
|
||||
@@ -303,7 +331,7 @@ func (f *Fwd) copy() Type {
|
||||
// Typedef is an alias of a Type.
|
||||
type Typedef struct {
|
||||
TypeID
|
||||
Name
|
||||
Name string
|
||||
Type Type
|
||||
}
|
||||
|
||||
@@ -311,6 +339,8 @@ func (td *Typedef) String() string {
|
||||
return fmt.Sprintf("typedef#%d[%q #%d]", td.TypeID, td.Name, td.Type.ID())
|
||||
}
|
||||
|
||||
func (td *Typedef) TypeName() string { return td.Name }
|
||||
|
||||
func (td *Typedef) walk(tdq *typeDeque) { tdq.push(&td.Type) }
|
||||
func (td *Typedef) copy() Type {
|
||||
cpy := *td
|
||||
@@ -327,6 +357,8 @@ func (v *Volatile) String() string {
|
||||
return fmt.Sprintf("volatile#%d[#%d]", v.TypeID, v.Type.ID())
|
||||
}
|
||||
|
||||
func (v *Volatile) TypeName() string { return "" }
|
||||
|
||||
func (v *Volatile) qualify() Type { return v.Type }
|
||||
func (v *Volatile) walk(tdq *typeDeque) { tdq.push(&v.Type) }
|
||||
func (v *Volatile) copy() Type {
|
||||
@@ -344,6 +376,8 @@ func (c *Const) String() string {
|
||||
return fmt.Sprintf("const#%d[#%d]", c.TypeID, c.Type.ID())
|
||||
}
|
||||
|
||||
func (c *Const) TypeName() string { return "" }
|
||||
|
||||
func (c *Const) qualify() Type { return c.Type }
|
||||
func (c *Const) walk(tdq *typeDeque) { tdq.push(&c.Type) }
|
||||
func (c *Const) copy() Type {
|
||||
@@ -361,6 +395,8 @@ func (r *Restrict) String() string {
|
||||
return fmt.Sprintf("restrict#%d[#%d]", r.TypeID, r.Type.ID())
|
||||
}
|
||||
|
||||
func (r *Restrict) TypeName() string { return "" }
|
||||
|
||||
func (r *Restrict) qualify() Type { return r.Type }
|
||||
func (r *Restrict) walk(tdq *typeDeque) { tdq.push(&r.Type) }
|
||||
func (r *Restrict) copy() Type {
|
||||
@@ -371,14 +407,17 @@ func (r *Restrict) copy() Type {
|
||||
// Func is a function definition.
|
||||
type Func struct {
|
||||
TypeID
|
||||
Name
|
||||
Type Type
|
||||
Name string
|
||||
Type Type
|
||||
Linkage FuncLinkage
|
||||
}
|
||||
|
||||
func (f *Func) String() string {
|
||||
return fmt.Sprintf("func#%d[%q proto=#%d]", f.TypeID, f.Name, f.Type.ID())
|
||||
return fmt.Sprintf("func#%d[%s %q proto=#%d]", f.TypeID, f.Linkage, f.Name, f.Type.ID())
|
||||
}
|
||||
|
||||
func (f *Func) TypeName() string { return f.Name }
|
||||
|
||||
func (f *Func) walk(tdq *typeDeque) { tdq.push(&f.Type) }
|
||||
func (f *Func) copy() Type {
|
||||
cpy := *f
|
||||
@@ -402,6 +441,8 @@ func (fp *FuncProto) String() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (fp *FuncProto) TypeName() string { return "" }
|
||||
|
||||
func (fp *FuncProto) walk(tdq *typeDeque) {
|
||||
tdq.push(&fp.Return)
|
||||
for i := range fp.Params {
|
||||
@@ -417,22 +458,24 @@ func (fp *FuncProto) copy() Type {
|
||||
}
|
||||
|
||||
type FuncParam struct {
|
||||
Name
|
||||
Name string
|
||||
Type Type
|
||||
}
|
||||
|
||||
// Var is a global variable.
|
||||
type Var struct {
|
||||
TypeID
|
||||
Name
|
||||
Type Type
|
||||
Name string
|
||||
Type Type
|
||||
Linkage VarLinkage
|
||||
}
|
||||
|
||||
func (v *Var) String() string {
|
||||
// TODO: Linkage
|
||||
return fmt.Sprintf("var#%d[%q]", v.TypeID, v.Name)
|
||||
return fmt.Sprintf("var#%d[%s %q]", v.TypeID, v.Linkage, v.Name)
|
||||
}
|
||||
|
||||
func (v *Var) TypeName() string { return v.Name }
|
||||
|
||||
func (v *Var) walk(tdq *typeDeque) { tdq.push(&v.Type) }
|
||||
func (v *Var) copy() Type {
|
||||
cpy := *v
|
||||
@@ -442,7 +485,7 @@ func (v *Var) copy() Type {
|
||||
// Datasec is a global program section containing data.
|
||||
type Datasec struct {
|
||||
TypeID
|
||||
Name
|
||||
Name string
|
||||
Size uint32
|
||||
Vars []VarSecinfo
|
||||
}
|
||||
@@ -451,6 +494,8 @@ func (ds *Datasec) String() string {
|
||||
return fmt.Sprintf("section#%d[%q]", ds.TypeID, ds.Name)
|
||||
}
|
||||
|
||||
func (ds *Datasec) TypeName() string { return ds.Name }
|
||||
|
||||
func (ds *Datasec) size() uint32 { return ds.Size }
|
||||
|
||||
func (ds *Datasec) walk(tdq *typeDeque) {
|
||||
@@ -466,7 +511,7 @@ func (ds *Datasec) copy() Type {
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// VarSecinfo describes variable in a Datasec
|
||||
// VarSecinfo describes variable in a Datasec.
|
||||
//
|
||||
// It is not a valid Type.
|
||||
type VarSecinfo struct {
|
||||
@@ -475,6 +520,27 @@ type VarSecinfo struct {
|
||||
Size uint32
|
||||
}
|
||||
|
||||
// Float is a float of a given length.
|
||||
type Float struct {
|
||||
TypeID
|
||||
Name string
|
||||
|
||||
// The size of the float in bytes.
|
||||
Size uint32
|
||||
}
|
||||
|
||||
func (f *Float) String() string {
|
||||
return fmt.Sprintf("float%d#%d[%q]", f.Size*8, f.TypeID, f.Name)
|
||||
}
|
||||
|
||||
func (f *Float) TypeName() string { return f.Name }
|
||||
func (f *Float) size() uint32 { return f.Size }
|
||||
func (f *Float) walk(*typeDeque) {}
|
||||
func (f *Float) copy() Type {
|
||||
cpy := *f
|
||||
return &cpy
|
||||
}
|
||||
|
||||
type sizer interface {
|
||||
size() uint32
|
||||
}
|
||||
@@ -511,7 +577,7 @@ func Sizeof(typ Type) (int, error) {
|
||||
switch v := typ.(type) {
|
||||
case *Array:
|
||||
if n > 0 && int64(v.Nelems) > math.MaxInt64/n {
|
||||
return 0, errors.New("overflow")
|
||||
return 0, fmt.Errorf("type %s: overflow", typ)
|
||||
}
|
||||
|
||||
// Arrays may be of zero length, which allows
|
||||
@@ -532,49 +598,89 @@ func Sizeof(typ Type) (int, error) {
|
||||
continue
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("unrecognized type %T", typ)
|
||||
return 0, fmt.Errorf("unsized type %T", typ)
|
||||
}
|
||||
|
||||
if n > 0 && elem > math.MaxInt64/n {
|
||||
return 0, errors.New("overflow")
|
||||
return 0, fmt.Errorf("type %s: overflow", typ)
|
||||
}
|
||||
|
||||
size := n * elem
|
||||
if int64(int(size)) != size {
|
||||
return 0, errors.New("overflow")
|
||||
return 0, fmt.Errorf("type %s: overflow", typ)
|
||||
}
|
||||
|
||||
return int(size), nil
|
||||
}
|
||||
|
||||
return 0, errors.New("exceeded type depth")
|
||||
return 0, fmt.Errorf("type %s: exceeded type depth", typ)
|
||||
}
|
||||
|
||||
// Copy a Type recursively.
|
||||
func Copy(typ Type) Type {
|
||||
typ, _ = copyType(typ, nil)
|
||||
return typ
|
||||
}
|
||||
|
||||
// copy a Type recursively.
|
||||
//
|
||||
// typ may form a cycle.
|
||||
func copyType(typ Type) Type {
|
||||
var (
|
||||
copies = make(map[Type]Type)
|
||||
work typeDeque
|
||||
)
|
||||
//
|
||||
// Returns any errors from transform verbatim.
|
||||
func copyType(typ Type, transform func(Type) (Type, error)) (Type, error) {
|
||||
copies := make(copier)
|
||||
return typ, copies.copy(&typ, transform)
|
||||
}
|
||||
|
||||
for t := &typ; t != nil; t = work.pop() {
|
||||
// copy a slice of Types recursively.
|
||||
//
|
||||
// Types may form a cycle.
|
||||
//
|
||||
// Returns any errors from transform verbatim.
|
||||
func copyTypes(types []Type, transform func(Type) (Type, error)) ([]Type, error) {
|
||||
result := make([]Type, len(types))
|
||||
copy(result, types)
|
||||
|
||||
copies := make(copier)
|
||||
for i := range result {
|
||||
if err := copies.copy(&result[i], transform); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type copier map[Type]Type
|
||||
|
||||
func (c copier) copy(typ *Type, transform func(Type) (Type, error)) error {
|
||||
var work typeDeque
|
||||
for t := typ; t != nil; t = work.pop() {
|
||||
// *t is the identity of the type.
|
||||
if cpy := copies[*t]; cpy != nil {
|
||||
if cpy := c[*t]; cpy != nil {
|
||||
*t = cpy
|
||||
continue
|
||||
}
|
||||
|
||||
cpy := (*t).copy()
|
||||
copies[*t] = cpy
|
||||
var cpy Type
|
||||
if transform != nil {
|
||||
tf, err := transform(*t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copy %s: %w", *t, err)
|
||||
}
|
||||
cpy = tf.copy()
|
||||
} else {
|
||||
cpy = (*t).copy()
|
||||
}
|
||||
|
||||
c[*t] = cpy
|
||||
*t = cpy
|
||||
|
||||
// Mark any nested types for copying.
|
||||
cpy.walk(&work)
|
||||
}
|
||||
|
||||
return typ
|
||||
return nil
|
||||
}
|
||||
|
||||
// typeDeque keeps track of pointers to types which still
|
||||
@@ -585,6 +691,10 @@ type typeDeque struct {
|
||||
mask uint64
|
||||
}
|
||||
|
||||
func (dq *typeDeque) empty() bool {
|
||||
return dq.read == dq.write
|
||||
}
|
||||
|
||||
// push adds a type to the stack.
|
||||
func (dq *typeDeque) push(t *Type) {
|
||||
if dq.write-dq.read < uint64(len(dq.types)) {
|
||||
@@ -611,7 +721,7 @@ func (dq *typeDeque) push(t *Type) {
|
||||
|
||||
// shift returns the first element or null.
|
||||
func (dq *typeDeque) shift() *Type {
|
||||
if dq.read == dq.write {
|
||||
if dq.empty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -624,7 +734,7 @@ func (dq *typeDeque) shift() *Type {
|
||||
|
||||
// pop returns the last element or null.
|
||||
func (dq *typeDeque) pop() *Type {
|
||||
if dq.read == dq.write {
|
||||
if dq.empty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -653,7 +763,7 @@ func (dq *typeDeque) all() []*Type {
|
||||
// Returns a map of named types (so, where NameOff is non-zero) and a slice of types
|
||||
// indexed by TypeID. Since BTF ignores compilation units, multiple types may share
|
||||
// the same name. A Type may form a cyclic graph by pointing at itself.
|
||||
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type, namedTypes map[string][]namedType, err error) {
|
||||
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type, namedTypes map[essentialName][]Type, err error) {
|
||||
type fixupDef struct {
|
||||
id TypeID
|
||||
expectedKind btfKind
|
||||
@@ -670,17 +780,17 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
// work, since otherwise append might re-allocate members.
|
||||
members := make([]Member, 0, len(raw))
|
||||
for i, btfMember := range raw {
|
||||
name, err := rawStrings.LookupName(btfMember.NameOff)
|
||||
name, err := rawStrings.Lookup(btfMember.NameOff)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
|
||||
}
|
||||
m := Member{
|
||||
Name: name,
|
||||
Offset: btfMember.Offset,
|
||||
Name: name,
|
||||
OffsetBits: btfMember.Offset,
|
||||
}
|
||||
if kindFlag {
|
||||
m.BitfieldSize = btfMember.Offset >> 24
|
||||
m.Offset &= 0xffffff
|
||||
m.OffsetBits &= 0xffffff
|
||||
}
|
||||
members = append(members, m)
|
||||
}
|
||||
@@ -692,7 +802,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
|
||||
types = make([]Type, 0, len(rawTypes))
|
||||
types = append(types, (*Void)(nil))
|
||||
namedTypes = make(map[string][]namedType)
|
||||
namedTypes = make(map[essentialName][]Type)
|
||||
|
||||
for i, raw := range rawTypes {
|
||||
var (
|
||||
@@ -702,7 +812,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
typ Type
|
||||
)
|
||||
|
||||
name, err := rawStrings.LookupName(raw.NameOff)
|
||||
name, err := rawStrings.Lookup(raw.NameOff)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("get name for type id %d: %w", id, err)
|
||||
}
|
||||
@@ -744,7 +854,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
rawvals := raw.data.([]btfEnum)
|
||||
vals := make([]EnumValue, 0, len(rawvals))
|
||||
for i, btfVal := range rawvals {
|
||||
name, err := rawStrings.LookupName(btfVal.NameOff)
|
||||
name, err := rawStrings.Lookup(btfVal.NameOff)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("get name for enum value %d: %s", i, err)
|
||||
}
|
||||
@@ -783,7 +893,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
typ = restrict
|
||||
|
||||
case kindFunc:
|
||||
fn := &Func{id, name, nil}
|
||||
fn := &Func{id, name, nil, raw.Linkage()}
|
||||
fixup(raw.Type(), kindFuncProto, &fn.Type)
|
||||
typ = fn
|
||||
|
||||
@@ -791,7 +901,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
rawparams := raw.data.([]btfParam)
|
||||
params := make([]FuncParam, 0, len(rawparams))
|
||||
for i, param := range rawparams {
|
||||
name, err := rawStrings.LookupName(param.NameOff)
|
||||
name, err := rawStrings.Lookup(param.NameOff)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("get name for func proto parameter %d: %s", i, err)
|
||||
}
|
||||
@@ -808,7 +918,8 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
typ = fp
|
||||
|
||||
case kindVar:
|
||||
v := &Var{id, name, nil}
|
||||
variable := raw.data.(*btfVariable)
|
||||
v := &Var{id, name, nil, VarLinkage(variable.Linkage)}
|
||||
fixup(raw.Type(), kindUnknown, &v.Type)
|
||||
typ = v
|
||||
|
||||
@@ -826,16 +937,17 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
}
|
||||
typ = &Datasec{id, name, raw.SizeType, vars}
|
||||
|
||||
case kindFloat:
|
||||
typ = &Float{id, name, raw.Size()}
|
||||
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("type id %d: unknown kind: %v", id, raw.Kind())
|
||||
}
|
||||
|
||||
types = append(types, typ)
|
||||
|
||||
if named, ok := typ.(namedType); ok {
|
||||
if name := essentialName(named.name()); name != "" {
|
||||
namedTypes[name] = append(namedTypes[name], named)
|
||||
}
|
||||
if name := newEssentialName(typ.TypeName()); name != "" {
|
||||
namedTypes[name] = append(namedTypes[name], typ)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -861,11 +973,20 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type,
|
||||
return types, namedTypes, nil
|
||||
}
|
||||
|
||||
// essentialName returns name without a ___ suffix.
|
||||
func essentialName(name string) string {
|
||||
// essentialName represents the name of a BTF type stripped of any flavor
|
||||
// suffixes after a ___ delimiter.
|
||||
type essentialName string
|
||||
|
||||
// newEssentialName returns name without a ___ suffix.
|
||||
//
|
||||
// CO-RE has the concept of 'struct flavors', which are used to deal with
|
||||
// changes in kernel data structures. Anything after three underscores
|
||||
// in a type name is ignored for the purpose of finding a candidate type
|
||||
// in the kernel's BTF.
|
||||
func newEssentialName(name string) essentialName {
|
||||
lastIdx := strings.LastIndex(name, "___")
|
||||
if lastIdx > 0 {
|
||||
return name[:lastIdx]
|
||||
return essentialName(name[:lastIdx])
|
||||
}
|
||||
return name
|
||||
return essentialName(name)
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -24,7 +24,7 @@ func PossibleCPUs() (int, error) {
|
||||
}
|
||||
|
||||
func parseCPUsFromFile(path string) (int, error) {
|
||||
spec, err := ioutil.ReadFile(path)
|
||||
spec, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
+16
@@ -50,3 +50,19 @@ func (se *SafeELFFile) Symbols() (syms []elf.Symbol, err error) {
|
||||
syms, err = se.File.Symbols()
|
||||
return
|
||||
}
|
||||
|
||||
// DynamicSymbols is the safe version of elf.File.DynamicSymbols.
|
||||
func (se *SafeELFFile) DynamicSymbols() (syms []elf.Symbol, err error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
syms = nil
|
||||
err = fmt.Errorf("reading ELF dynamic symbols panicked: %s", r)
|
||||
}()
|
||||
|
||||
syms, err = se.File.DynamicSymbols()
|
||||
return
|
||||
}
|
||||
|
||||
+5
@@ -9,11 +9,16 @@ import (
|
||||
// depending on the host's endianness.
|
||||
var NativeEndian binary.ByteOrder
|
||||
|
||||
// Clang is set to either "el" or "eb" depending on the host's endianness.
|
||||
var ClangEndian string
|
||||
|
||||
func init() {
|
||||
if isBigEndian() {
|
||||
NativeEndian = binary.BigEndian
|
||||
ClangEndian = "eb"
|
||||
} else {
|
||||
NativeEndian = binary.LittleEndian
|
||||
ClangEndian = "el"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-11
@@ -1,7 +1,6 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -15,7 +14,7 @@ import (
|
||||
// logErr should be the error returned by the syscall that generated
|
||||
// the log. It is used to check for truncation of the output.
|
||||
func ErrorWithLog(err error, log []byte, logErr error) error {
|
||||
logStr := strings.Trim(CString(log), "\t\r\n ")
|
||||
logStr := strings.Trim(unix.ByteSliceToString(log), "\t\r\n ")
|
||||
if errors.Is(logErr, unix.ENOSPC) {
|
||||
logStr += " (truncated...)"
|
||||
}
|
||||
@@ -29,6 +28,10 @@ type VerifierError struct {
|
||||
log string
|
||||
}
|
||||
|
||||
func (le *VerifierError) Unwrap() error {
|
||||
return le.cause
|
||||
}
|
||||
|
||||
func (le *VerifierError) Error() string {
|
||||
if le.log == "" {
|
||||
return le.cause.Error()
|
||||
@@ -36,12 +39,3 @@ func (le *VerifierError) Error() string {
|
||||
|
||||
return fmt.Sprintf("%s: %s", le.cause, le.log)
|
||||
}
|
||||
|
||||
// CString turns a NUL / zero terminated byte buffer into a string.
|
||||
func CString(in []byte) string {
|
||||
inLen := bytes.IndexByte(in, 0)
|
||||
if inLen == -1 {
|
||||
return ""
|
||||
}
|
||||
return string(in[:inLen])
|
||||
}
|
||||
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
var ErrClosedFd = errors.New("use of closed file descriptor")
|
||||
|
||||
type FD struct {
|
||||
raw int64
|
||||
}
|
||||
|
||||
func NewFD(value uint32) *FD {
|
||||
fd := &FD{int64(value)}
|
||||
runtime.SetFinalizer(fd, (*FD).Close)
|
||||
return fd
|
||||
}
|
||||
|
||||
func (fd *FD) String() string {
|
||||
return strconv.FormatInt(fd.raw, 10)
|
||||
}
|
||||
|
||||
func (fd *FD) Value() (uint32, error) {
|
||||
if fd.raw < 0 {
|
||||
return 0, ErrClosedFd
|
||||
}
|
||||
|
||||
return uint32(fd.raw), nil
|
||||
}
|
||||
|
||||
func (fd *FD) Close() error {
|
||||
if fd.raw < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := int(fd.raw)
|
||||
fd.raw = -1
|
||||
|
||||
fd.Forget()
|
||||
return unix.Close(value)
|
||||
}
|
||||
|
||||
func (fd *FD) Forget() {
|
||||
runtime.SetFinalizer(fd, nil)
|
||||
}
|
||||
|
||||
func (fd *FD) Dup() (*FD, error) {
|
||||
if fd.raw < 0 {
|
||||
return nil, ErrClosedFd
|
||||
}
|
||||
|
||||
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't dup fd: %v", err)
|
||||
}
|
||||
|
||||
return NewFD(uint32(dup)), nil
|
||||
}
|
||||
|
||||
func (fd *FD) File(name string) *os.File {
|
||||
fd.Forget()
|
||||
return os.NewFile(uintptr(fd.raw), name)
|
||||
}
|
||||
-38
@@ -98,41 +98,3 @@ func FeatureTest(name, version string, fn FeatureTestFn) func() error {
|
||||
return ft.result
|
||||
}
|
||||
}
|
||||
|
||||
// A Version in the form Major.Minor.Patch.
|
||||
type Version [3]uint16
|
||||
|
||||
// NewVersion creates a version from a string like "Major.Minor.Patch".
|
||||
//
|
||||
// Patch is optional.
|
||||
func NewVersion(ver string) (Version, error) {
|
||||
var major, minor, patch uint16
|
||||
n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
|
||||
if n < 2 {
|
||||
return Version{}, fmt.Errorf("invalid version: %s", ver)
|
||||
}
|
||||
return Version{major, minor, patch}, nil
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v[2] == 0 {
|
||||
return fmt.Sprintf("v%d.%d", v[0], v[1])
|
||||
}
|
||||
return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2])
|
||||
}
|
||||
|
||||
// Less returns true if the version is less than another version.
|
||||
func (v Version) Less(other Version) bool {
|
||||
for i, a := range v {
|
||||
if a == other[i] {
|
||||
continue
|
||||
}
|
||||
return a < other[i]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Unspecified returns true if the version is all zero.
|
||||
func (v Version) Unspecified() bool {
|
||||
return v[0] == 0 && v[1] == 0 && v[2] == 0
|
||||
}
|
||||
|
||||
+47
-1
@@ -1,6 +1,35 @@
|
||||
package internal
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// NewBufferedSectionReader wraps an io.ReaderAt in an appropriately-sized
|
||||
// buffered reader. It is a convenience function for reading subsections of
|
||||
// ELF sections while minimizing the amount of read() syscalls made.
|
||||
//
|
||||
// Syscall overhead is non-negligible in continuous integration context
|
||||
// where ELFs might be accessed over virtual filesystems with poor random
|
||||
// access performance. Buffering reads makes sense because (sub)sections
|
||||
// end up being read completely anyway.
|
||||
//
|
||||
// Use instead of the r.Seek() + io.LimitReader() pattern.
|
||||
func NewBufferedSectionReader(ra io.ReaderAt, off, n int64) io.Reader {
|
||||
// Clamp the size of the buffer to one page to avoid slurping large parts
|
||||
// of a file into memory. bufio.NewReader uses a hardcoded default buffer
|
||||
// of 4096. Allow arches with larger pages to allocate more, but don't
|
||||
// allocate a fixed 4k buffer if we only need to read a small segment.
|
||||
buf := n
|
||||
if ps := int64(os.Getpagesize()); n > ps {
|
||||
buf = ps
|
||||
}
|
||||
|
||||
return bufio.NewReaderSize(io.NewSectionReader(ra, off, n), int(buf))
|
||||
}
|
||||
|
||||
// DiscardZeroes makes sure that all written bytes are zero
|
||||
// before discarding them.
|
||||
@@ -14,3 +43,20 @@ func (DiscardZeroes) Write(p []byte) (int, error) {
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// ReadAllCompressed decompresses a gzipped file into memory.
|
||||
func ReadAllCompressed(file string) ([]byte, error) {
|
||||
fh, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
gz, err := gzip.NewReader(fh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gz.Close()
|
||||
|
||||
return io.ReadAll(gz)
|
||||
}
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"go/format"
|
||||
"go/scanner"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Identifier turns a C style type or field name into an exportable Go equivalent.
|
||||
func Identifier(str string) string {
|
||||
prev := rune(-1)
|
||||
return strings.Map(func(r rune) rune {
|
||||
// See https://golang.org/ref/spec#Identifiers
|
||||
switch {
|
||||
case unicode.IsLetter(r):
|
||||
if prev == -1 {
|
||||
r = unicode.ToUpper(r)
|
||||
}
|
||||
|
||||
case r == '_':
|
||||
switch {
|
||||
// The previous rune was deleted, or we are at the
|
||||
// beginning of the string.
|
||||
case prev == -1:
|
||||
fallthrough
|
||||
|
||||
// The previous rune is a lower case letter or a digit.
|
||||
case unicode.IsDigit(prev) || (unicode.IsLetter(prev) && unicode.IsLower(prev)):
|
||||
// delete the current rune, and force the
|
||||
// next character to be uppercased.
|
||||
r = -1
|
||||
}
|
||||
|
||||
case unicode.IsDigit(r):
|
||||
|
||||
default:
|
||||
// Delete the current rune. prev is unchanged.
|
||||
return -1
|
||||
}
|
||||
|
||||
prev = r
|
||||
return r
|
||||
}, str)
|
||||
}
|
||||
|
||||
// WriteFormatted outputs a formatted src into out.
|
||||
//
|
||||
// If formatting fails it returns an informative error message.
|
||||
func WriteFormatted(src []byte, out io.Writer) error {
|
||||
formatted, err := format.Source(src)
|
||||
if err == nil {
|
||||
_, err = out.Write(formatted)
|
||||
return err
|
||||
}
|
||||
|
||||
var el scanner.ErrorList
|
||||
if !errors.As(err, &el) {
|
||||
return err
|
||||
}
|
||||
|
||||
var nel scanner.ErrorList
|
||||
for _, err := range el {
|
||||
if !err.Pos.IsValid() {
|
||||
nel = append(nel, err)
|
||||
continue
|
||||
}
|
||||
|
||||
buf := src[err.Pos.Offset:]
|
||||
nl := bytes.IndexRune(buf, '\n')
|
||||
if nl == -1 {
|
||||
nel = append(nel, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err.Msg += ": " + string(buf[:nl])
|
||||
nel = append(nel, err)
|
||||
}
|
||||
|
||||
return nel
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/cilium/ebpf/internal/sys"
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
func Pin(currentPath, newPath string, fd *sys.FD) error {
|
||||
const bpfFSType = 0xcafe4a11
|
||||
|
||||
if newPath == "" {
|
||||
return errors.New("given pinning path cannot be empty")
|
||||
}
|
||||
if currentPath == newPath {
|
||||
return nil
|
||||
}
|
||||
|
||||
var statfs unix.Statfs_t
|
||||
if err := unix.Statfs(filepath.Dir(newPath), &statfs); err != nil {
|
||||
return err
|
||||
} else if uint64(statfs.Type) != bpfFSType {
|
||||
return fmt.Errorf("%s is not on a bpf filesystem", newPath)
|
||||
}
|
||||
|
||||
defer runtime.KeepAlive(fd)
|
||||
|
||||
if currentPath == "" {
|
||||
return sys.ObjPin(&sys.ObjPinAttr{
|
||||
Pathname: sys.NewStringPointer(newPath),
|
||||
BpfFd: fd.Uint(),
|
||||
})
|
||||
}
|
||||
|
||||
// Renameat2 is used instead of os.Rename to disallow the new path replacing
|
||||
// an existing path.
|
||||
err := unix.Renameat2(unix.AT_FDCWD, currentPath, unix.AT_FDCWD, newPath, unix.RENAME_NOREPLACE)
|
||||
if err == nil {
|
||||
// Object is now moved to the new pinning path.
|
||||
return nil
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("unable to move pinned object to new path %v: %w", newPath, err)
|
||||
}
|
||||
// Internal state not in sync with the file system so let's fix it.
|
||||
return sys.ObjPin(&sys.ObjPinAttr{
|
||||
Pathname: sys.NewStringPointer(newPath),
|
||||
BpfFd: fd.Uint(),
|
||||
})
|
||||
}
|
||||
|
||||
func Unpin(pinnedPath string) error {
|
||||
if pinnedPath == "" {
|
||||
return nil
|
||||
}
|
||||
err := os.Remove(pinnedPath)
|
||||
if err == nil || os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
// +build !386,!amd64p32,!arm,!mipsle,!mips64p32le
|
||||
// +build !armbe,!mips,!mips64p32
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Pointer wraps an unsafe.Pointer to be 64bit to
|
||||
// conform to the syscall specification.
|
||||
type Pointer struct {
|
||||
ptr unsafe.Pointer
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// Package sys contains bindings for the BPF syscall.
|
||||
package sys
|
||||
|
||||
//go:generate go run github.com/cilium/ebpf/internal/cmd/gentypes ../btf/testdata/vmlinux-btf.gz
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
var ErrClosedFd = unix.EBADF
|
||||
|
||||
type FD struct {
|
||||
raw int
|
||||
}
|
||||
|
||||
func newFD(value int) *FD {
|
||||
fd := &FD{value}
|
||||
runtime.SetFinalizer(fd, (*FD).Close)
|
||||
return fd
|
||||
}
|
||||
|
||||
// NewFD wraps a raw fd with a finalizer.
|
||||
//
|
||||
// You must not use the raw fd after calling this function, since the underlying
|
||||
// file descriptor number may change. This is because the BPF UAPI assumes that
|
||||
// zero is not a valid fd value.
|
||||
func NewFD(value int) (*FD, error) {
|
||||
if value < 0 {
|
||||
return nil, fmt.Errorf("invalid fd %d", value)
|
||||
}
|
||||
|
||||
fd := newFD(value)
|
||||
if value != 0 {
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
dup, err := fd.Dup()
|
||||
_ = fd.Close()
|
||||
return dup, err
|
||||
}
|
||||
|
||||
func (fd *FD) String() string {
|
||||
return strconv.FormatInt(int64(fd.raw), 10)
|
||||
}
|
||||
|
||||
func (fd *FD) Int() int {
|
||||
return fd.raw
|
||||
}
|
||||
|
||||
func (fd *FD) Uint() uint32 {
|
||||
if fd.raw < 0 || int64(fd.raw) > math.MaxUint32 {
|
||||
// Best effort: this is the number most likely to be an invalid file
|
||||
// descriptor. It is equal to -1 (on two's complement arches).
|
||||
return math.MaxUint32
|
||||
}
|
||||
return uint32(fd.raw)
|
||||
}
|
||||
|
||||
func (fd *FD) Close() error {
|
||||
if fd.raw < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := int(fd.raw)
|
||||
fd.raw = -1
|
||||
|
||||
fd.Forget()
|
||||
return unix.Close(value)
|
||||
}
|
||||
|
||||
func (fd *FD) Forget() {
|
||||
runtime.SetFinalizer(fd, nil)
|
||||
}
|
||||
|
||||
func (fd *FD) Dup() (*FD, error) {
|
||||
if fd.raw < 0 {
|
||||
return nil, ErrClosedFd
|
||||
}
|
||||
|
||||
// Always require the fd to be larger than zero: the BPF API treats the value
|
||||
// as "no argument provided".
|
||||
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't dup fd: %v", err)
|
||||
}
|
||||
|
||||
return newFD(dup), nil
|
||||
}
|
||||
|
||||
func (fd *FD) File(name string) *os.File {
|
||||
fd.Forget()
|
||||
return os.NewFile(uintptr(fd.raw), name)
|
||||
}
|
||||
Generated
Vendored
+16
-8
@@ -1,6 +1,10 @@
|
||||
package internal
|
||||
package sys
|
||||
|
||||
import "unsafe"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
// NewPointer creates a 64-bit pointer from an unsafe Pointer.
|
||||
func NewPointer(ptr unsafe.Pointer) Pointer {
|
||||
@@ -16,15 +20,19 @@ func NewSlicePointer(buf []byte) Pointer {
|
||||
return Pointer{ptr: unsafe.Pointer(&buf[0])}
|
||||
}
|
||||
|
||||
// NewSlicePointer creates a 64-bit pointer from a byte slice.
|
||||
//
|
||||
// Useful to assign both the pointer and the length in one go.
|
||||
func NewSlicePointerLen(buf []byte) (Pointer, uint32) {
|
||||
return NewSlicePointer(buf), uint32(len(buf))
|
||||
}
|
||||
|
||||
// NewStringPointer creates a 64-bit pointer from a string.
|
||||
func NewStringPointer(str string) Pointer {
|
||||
if str == "" {
|
||||
p, err := unix.BytePtrFromString(str)
|
||||
if err != nil {
|
||||
return Pointer{}
|
||||
}
|
||||
|
||||
// The kernel expects strings to be zero terminated
|
||||
buf := make([]byte, len(str)+1)
|
||||
copy(buf, str)
|
||||
|
||||
return Pointer{ptr: unsafe.Pointer(&buf[0])}
|
||||
return Pointer{ptr: unsafe.Pointer(p)}
|
||||
}
|
||||
Generated
Vendored
+2
-1
@@ -1,6 +1,7 @@
|
||||
//go:build armbe || mips || mips64p32
|
||||
// +build armbe mips mips64p32
|
||||
|
||||
package internal
|
||||
package sys
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
Generated
Vendored
+2
-1
@@ -1,6 +1,7 @@
|
||||
//go:build 386 || amd64p32 || arm || mipsle || mips64p32le
|
||||
// +build 386 amd64p32 arm mipsle mips64p32le
|
||||
|
||||
package internal
|
||||
package sys
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//go:build !386 && !amd64p32 && !arm && !mipsle && !mips64p32le && !armbe && !mips && !mips64p32
|
||||
// +build !386,!amd64p32,!arm,!mipsle,!mips64p32le,!armbe,!mips,!mips64p32
|
||||
|
||||
package sys
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Pointer wraps an unsafe.Pointer to be 64bit to
|
||||
// conform to the syscall specification.
|
||||
type Pointer struct {
|
||||
ptr unsafe.Pointer
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
// BPF wraps SYS_BPF.
|
||||
//
|
||||
// Any pointers contained in attr must use the Pointer type from this package.
|
||||
func BPF(cmd Cmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
|
||||
for {
|
||||
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
|
||||
runtime.KeepAlive(attr)
|
||||
|
||||
// As of ~4.20 the verifier can be interrupted by a signal,
|
||||
// and returns EAGAIN in that case.
|
||||
if errNo == unix.EAGAIN && cmd == BPF_PROG_LOAD {
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
if errNo != 0 {
|
||||
err = wrappedErrno{errNo}
|
||||
}
|
||||
|
||||
return r1, err
|
||||
}
|
||||
}
|
||||
|
||||
// Info is implemented by all structs that can be passed to the ObjInfo syscall.
|
||||
//
|
||||
// MapInfo
|
||||
// ProgInfo
|
||||
// LinkInfo
|
||||
// BtfInfo
|
||||
type Info interface {
|
||||
info() (unsafe.Pointer, uint32)
|
||||
}
|
||||
|
||||
var _ Info = (*MapInfo)(nil)
|
||||
|
||||
func (i *MapInfo) info() (unsafe.Pointer, uint32) {
|
||||
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
|
||||
}
|
||||
|
||||
var _ Info = (*ProgInfo)(nil)
|
||||
|
||||
func (i *ProgInfo) info() (unsafe.Pointer, uint32) {
|
||||
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
|
||||
}
|
||||
|
||||
var _ Info = (*LinkInfo)(nil)
|
||||
|
||||
func (i *LinkInfo) info() (unsafe.Pointer, uint32) {
|
||||
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
|
||||
}
|
||||
|
||||
var _ Info = (*BtfInfo)(nil)
|
||||
|
||||
func (i *BtfInfo) info() (unsafe.Pointer, uint32) {
|
||||
return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
|
||||
}
|
||||
|
||||
// ObjInfo retrieves information about a BPF Fd.
|
||||
//
|
||||
// info may be one of MapInfo, ProgInfo, LinkInfo and BtfInfo.
|
||||
func ObjInfo(fd *FD, info Info) error {
|
||||
ptr, len := info.info()
|
||||
err := ObjGetInfoByFd(&ObjGetInfoByFdAttr{
|
||||
BpfFd: fd.Uint(),
|
||||
InfoLen: len,
|
||||
Info: NewPointer(ptr),
|
||||
})
|
||||
runtime.KeepAlive(fd)
|
||||
return err
|
||||
}
|
||||
|
||||
// BPFObjName is a null-terminated string made up of
|
||||
// 'A-Za-z0-9_' characters.
|
||||
type ObjName [unix.BPF_OBJ_NAME_LEN]byte
|
||||
|
||||
// NewObjName truncates the result if it is too long.
|
||||
func NewObjName(name string) ObjName {
|
||||
var result ObjName
|
||||
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
|
||||
return result
|
||||
}
|
||||
|
||||
// LinkID uniquely identifies a bpf_link.
|
||||
type LinkID uint32
|
||||
|
||||
// wrappedErrno wraps syscall.Errno to prevent direct comparisons with
|
||||
// syscall.E* or unix.E* constants.
|
||||
//
|
||||
// You should never export an error of this type.
|
||||
type wrappedErrno struct {
|
||||
syscall.Errno
|
||||
}
|
||||
|
||||
func (we wrappedErrno) Unwrap() error {
|
||||
return we.Errno
|
||||
}
|
||||
|
||||
type syscallError struct {
|
||||
error
|
||||
errno syscall.Errno
|
||||
}
|
||||
|
||||
func Error(err error, errno syscall.Errno) error {
|
||||
return &syscallError{err, errno}
|
||||
}
|
||||
|
||||
func (se *syscallError) Is(target error) bool {
|
||||
return target == se.error
|
||||
}
|
||||
|
||||
func (se *syscallError) Unwrap() error {
|
||||
return se.errno
|
||||
}
|
||||
+954
@@ -0,0 +1,954 @@
|
||||
// Code generated by internal/cmd/gentypes; DO NOT EDIT.
|
||||
|
||||
package sys
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type AdjRoomMode int32
|
||||
|
||||
const (
|
||||
BPF_ADJ_ROOM_NET AdjRoomMode = 0
|
||||
BPF_ADJ_ROOM_MAC AdjRoomMode = 1
|
||||
)
|
||||
|
||||
type AttachType int32
|
||||
|
||||
const (
|
||||
BPF_CGROUP_INET_INGRESS AttachType = 0
|
||||
BPF_CGROUP_INET_EGRESS AttachType = 1
|
||||
BPF_CGROUP_INET_SOCK_CREATE AttachType = 2
|
||||
BPF_CGROUP_SOCK_OPS AttachType = 3
|
||||
BPF_SK_SKB_STREAM_PARSER AttachType = 4
|
||||
BPF_SK_SKB_STREAM_VERDICT AttachType = 5
|
||||
BPF_CGROUP_DEVICE AttachType = 6
|
||||
BPF_SK_MSG_VERDICT AttachType = 7
|
||||
BPF_CGROUP_INET4_BIND AttachType = 8
|
||||
BPF_CGROUP_INET6_BIND AttachType = 9
|
||||
BPF_CGROUP_INET4_CONNECT AttachType = 10
|
||||
BPF_CGROUP_INET6_CONNECT AttachType = 11
|
||||
BPF_CGROUP_INET4_POST_BIND AttachType = 12
|
||||
BPF_CGROUP_INET6_POST_BIND AttachType = 13
|
||||
BPF_CGROUP_UDP4_SENDMSG AttachType = 14
|
||||
BPF_CGROUP_UDP6_SENDMSG AttachType = 15
|
||||
BPF_LIRC_MODE2 AttachType = 16
|
||||
BPF_FLOW_DISSECTOR AttachType = 17
|
||||
BPF_CGROUP_SYSCTL AttachType = 18
|
||||
BPF_CGROUP_UDP4_RECVMSG AttachType = 19
|
||||
BPF_CGROUP_UDP6_RECVMSG AttachType = 20
|
||||
BPF_CGROUP_GETSOCKOPT AttachType = 21
|
||||
BPF_CGROUP_SETSOCKOPT AttachType = 22
|
||||
BPF_TRACE_RAW_TP AttachType = 23
|
||||
BPF_TRACE_FENTRY AttachType = 24
|
||||
BPF_TRACE_FEXIT AttachType = 25
|
||||
BPF_MODIFY_RETURN AttachType = 26
|
||||
BPF_LSM_MAC AttachType = 27
|
||||
BPF_TRACE_ITER AttachType = 28
|
||||
BPF_CGROUP_INET4_GETPEERNAME AttachType = 29
|
||||
BPF_CGROUP_INET6_GETPEERNAME AttachType = 30
|
||||
BPF_CGROUP_INET4_GETSOCKNAME AttachType = 31
|
||||
BPF_CGROUP_INET6_GETSOCKNAME AttachType = 32
|
||||
BPF_XDP_DEVMAP AttachType = 33
|
||||
BPF_CGROUP_INET_SOCK_RELEASE AttachType = 34
|
||||
BPF_XDP_CPUMAP AttachType = 35
|
||||
BPF_SK_LOOKUP AttachType = 36
|
||||
BPF_XDP AttachType = 37
|
||||
BPF_SK_SKB_VERDICT AttachType = 38
|
||||
__MAX_BPF_ATTACH_TYPE AttachType = 39
|
||||
)
|
||||
|
||||
type Cmd int32
|
||||
|
||||
const (
|
||||
BPF_MAP_CREATE Cmd = 0
|
||||
BPF_MAP_LOOKUP_ELEM Cmd = 1
|
||||
BPF_MAP_UPDATE_ELEM Cmd = 2
|
||||
BPF_MAP_DELETE_ELEM Cmd = 3
|
||||
BPF_MAP_GET_NEXT_KEY Cmd = 4
|
||||
BPF_PROG_LOAD Cmd = 5
|
||||
BPF_OBJ_PIN Cmd = 6
|
||||
BPF_OBJ_GET Cmd = 7
|
||||
BPF_PROG_ATTACH Cmd = 8
|
||||
BPF_PROG_DETACH Cmd = 9
|
||||
BPF_PROG_TEST_RUN Cmd = 10
|
||||
BPF_PROG_GET_NEXT_ID Cmd = 11
|
||||
BPF_MAP_GET_NEXT_ID Cmd = 12
|
||||
BPF_PROG_GET_FD_BY_ID Cmd = 13
|
||||
BPF_MAP_GET_FD_BY_ID Cmd = 14
|
||||
BPF_OBJ_GET_INFO_BY_FD Cmd = 15
|
||||
BPF_PROG_QUERY Cmd = 16
|
||||
BPF_RAW_TRACEPOINT_OPEN Cmd = 17
|
||||
BPF_BTF_LOAD Cmd = 18
|
||||
BPF_BTF_GET_FD_BY_ID Cmd = 19
|
||||
BPF_TASK_FD_QUERY Cmd = 20
|
||||
BPF_MAP_LOOKUP_AND_DELETE_ELEM Cmd = 21
|
||||
BPF_MAP_FREEZE Cmd = 22
|
||||
BPF_BTF_GET_NEXT_ID Cmd = 23
|
||||
BPF_MAP_LOOKUP_BATCH Cmd = 24
|
||||
BPF_MAP_LOOKUP_AND_DELETE_BATCH Cmd = 25
|
||||
BPF_MAP_UPDATE_BATCH Cmd = 26
|
||||
BPF_MAP_DELETE_BATCH Cmd = 27
|
||||
BPF_LINK_CREATE Cmd = 28
|
||||
BPF_LINK_UPDATE Cmd = 29
|
||||
BPF_LINK_GET_FD_BY_ID Cmd = 30
|
||||
BPF_LINK_GET_NEXT_ID Cmd = 31
|
||||
BPF_ENABLE_STATS Cmd = 32
|
||||
BPF_ITER_CREATE Cmd = 33
|
||||
BPF_LINK_DETACH Cmd = 34
|
||||
BPF_PROG_BIND_MAP Cmd = 35
|
||||
)
|
||||
|
||||
type FunctionId int32
|
||||
|
||||
const (
|
||||
BPF_FUNC_unspec FunctionId = 0
|
||||
BPF_FUNC_map_lookup_elem FunctionId = 1
|
||||
BPF_FUNC_map_update_elem FunctionId = 2
|
||||
BPF_FUNC_map_delete_elem FunctionId = 3
|
||||
BPF_FUNC_probe_read FunctionId = 4
|
||||
BPF_FUNC_ktime_get_ns FunctionId = 5
|
||||
BPF_FUNC_trace_printk FunctionId = 6
|
||||
BPF_FUNC_get_prandom_u32 FunctionId = 7
|
||||
BPF_FUNC_get_smp_processor_id FunctionId = 8
|
||||
BPF_FUNC_skb_store_bytes FunctionId = 9
|
||||
BPF_FUNC_l3_csum_replace FunctionId = 10
|
||||
BPF_FUNC_l4_csum_replace FunctionId = 11
|
||||
BPF_FUNC_tail_call FunctionId = 12
|
||||
BPF_FUNC_clone_redirect FunctionId = 13
|
||||
BPF_FUNC_get_current_pid_tgid FunctionId = 14
|
||||
BPF_FUNC_get_current_uid_gid FunctionId = 15
|
||||
BPF_FUNC_get_current_comm FunctionId = 16
|
||||
BPF_FUNC_get_cgroup_classid FunctionId = 17
|
||||
BPF_FUNC_skb_vlan_push FunctionId = 18
|
||||
BPF_FUNC_skb_vlan_pop FunctionId = 19
|
||||
BPF_FUNC_skb_get_tunnel_key FunctionId = 20
|
||||
BPF_FUNC_skb_set_tunnel_key FunctionId = 21
|
||||
BPF_FUNC_perf_event_read FunctionId = 22
|
||||
BPF_FUNC_redirect FunctionId = 23
|
||||
BPF_FUNC_get_route_realm FunctionId = 24
|
||||
BPF_FUNC_perf_event_output FunctionId = 25
|
||||
BPF_FUNC_skb_load_bytes FunctionId = 26
|
||||
BPF_FUNC_get_stackid FunctionId = 27
|
||||
BPF_FUNC_csum_diff FunctionId = 28
|
||||
BPF_FUNC_skb_get_tunnel_opt FunctionId = 29
|
||||
BPF_FUNC_skb_set_tunnel_opt FunctionId = 30
|
||||
BPF_FUNC_skb_change_proto FunctionId = 31
|
||||
BPF_FUNC_skb_change_type FunctionId = 32
|
||||
BPF_FUNC_skb_under_cgroup FunctionId = 33
|
||||
BPF_FUNC_get_hash_recalc FunctionId = 34
|
||||
BPF_FUNC_get_current_task FunctionId = 35
|
||||
BPF_FUNC_probe_write_user FunctionId = 36
|
||||
BPF_FUNC_current_task_under_cgroup FunctionId = 37
|
||||
BPF_FUNC_skb_change_tail FunctionId = 38
|
||||
BPF_FUNC_skb_pull_data FunctionId = 39
|
||||
BPF_FUNC_csum_update FunctionId = 40
|
||||
BPF_FUNC_set_hash_invalid FunctionId = 41
|
||||
BPF_FUNC_get_numa_node_id FunctionId = 42
|
||||
BPF_FUNC_skb_change_head FunctionId = 43
|
||||
BPF_FUNC_xdp_adjust_head FunctionId = 44
|
||||
BPF_FUNC_probe_read_str FunctionId = 45
|
||||
BPF_FUNC_get_socket_cookie FunctionId = 46
|
||||
BPF_FUNC_get_socket_uid FunctionId = 47
|
||||
BPF_FUNC_set_hash FunctionId = 48
|
||||
BPF_FUNC_setsockopt FunctionId = 49
|
||||
BPF_FUNC_skb_adjust_room FunctionId = 50
|
||||
BPF_FUNC_redirect_map FunctionId = 51
|
||||
BPF_FUNC_sk_redirect_map FunctionId = 52
|
||||
BPF_FUNC_sock_map_update FunctionId = 53
|
||||
BPF_FUNC_xdp_adjust_meta FunctionId = 54
|
||||
BPF_FUNC_perf_event_read_value FunctionId = 55
|
||||
BPF_FUNC_perf_prog_read_value FunctionId = 56
|
||||
BPF_FUNC_getsockopt FunctionId = 57
|
||||
BPF_FUNC_override_return FunctionId = 58
|
||||
BPF_FUNC_sock_ops_cb_flags_set FunctionId = 59
|
||||
BPF_FUNC_msg_redirect_map FunctionId = 60
|
||||
BPF_FUNC_msg_apply_bytes FunctionId = 61
|
||||
BPF_FUNC_msg_cork_bytes FunctionId = 62
|
||||
BPF_FUNC_msg_pull_data FunctionId = 63
|
||||
BPF_FUNC_bind FunctionId = 64
|
||||
BPF_FUNC_xdp_adjust_tail FunctionId = 65
|
||||
BPF_FUNC_skb_get_xfrm_state FunctionId = 66
|
||||
BPF_FUNC_get_stack FunctionId = 67
|
||||
BPF_FUNC_skb_load_bytes_relative FunctionId = 68
|
||||
BPF_FUNC_fib_lookup FunctionId = 69
|
||||
BPF_FUNC_sock_hash_update FunctionId = 70
|
||||
BPF_FUNC_msg_redirect_hash FunctionId = 71
|
||||
BPF_FUNC_sk_redirect_hash FunctionId = 72
|
||||
BPF_FUNC_lwt_push_encap FunctionId = 73
|
||||
BPF_FUNC_lwt_seg6_store_bytes FunctionId = 74
|
||||
BPF_FUNC_lwt_seg6_adjust_srh FunctionId = 75
|
||||
BPF_FUNC_lwt_seg6_action FunctionId = 76
|
||||
BPF_FUNC_rc_repeat FunctionId = 77
|
||||
BPF_FUNC_rc_keydown FunctionId = 78
|
||||
BPF_FUNC_skb_cgroup_id FunctionId = 79
|
||||
BPF_FUNC_get_current_cgroup_id FunctionId = 80
|
||||
BPF_FUNC_get_local_storage FunctionId = 81
|
||||
BPF_FUNC_sk_select_reuseport FunctionId = 82
|
||||
BPF_FUNC_skb_ancestor_cgroup_id FunctionId = 83
|
||||
BPF_FUNC_sk_lookup_tcp FunctionId = 84
|
||||
BPF_FUNC_sk_lookup_udp FunctionId = 85
|
||||
BPF_FUNC_sk_release FunctionId = 86
|
||||
BPF_FUNC_map_push_elem FunctionId = 87
|
||||
BPF_FUNC_map_pop_elem FunctionId = 88
|
||||
BPF_FUNC_map_peek_elem FunctionId = 89
|
||||
BPF_FUNC_msg_push_data FunctionId = 90
|
||||
BPF_FUNC_msg_pop_data FunctionId = 91
|
||||
BPF_FUNC_rc_pointer_rel FunctionId = 92
|
||||
BPF_FUNC_spin_lock FunctionId = 93
|
||||
BPF_FUNC_spin_unlock FunctionId = 94
|
||||
BPF_FUNC_sk_fullsock FunctionId = 95
|
||||
BPF_FUNC_tcp_sock FunctionId = 96
|
||||
BPF_FUNC_skb_ecn_set_ce FunctionId = 97
|
||||
BPF_FUNC_get_listener_sock FunctionId = 98
|
||||
BPF_FUNC_skc_lookup_tcp FunctionId = 99
|
||||
BPF_FUNC_tcp_check_syncookie FunctionId = 100
|
||||
BPF_FUNC_sysctl_get_name FunctionId = 101
|
||||
BPF_FUNC_sysctl_get_current_value FunctionId = 102
|
||||
BPF_FUNC_sysctl_get_new_value FunctionId = 103
|
||||
BPF_FUNC_sysctl_set_new_value FunctionId = 104
|
||||
BPF_FUNC_strtol FunctionId = 105
|
||||
BPF_FUNC_strtoul FunctionId = 106
|
||||
BPF_FUNC_sk_storage_get FunctionId = 107
|
||||
BPF_FUNC_sk_storage_delete FunctionId = 108
|
||||
BPF_FUNC_send_signal FunctionId = 109
|
||||
BPF_FUNC_tcp_gen_syncookie FunctionId = 110
|
||||
BPF_FUNC_skb_output FunctionId = 111
|
||||
BPF_FUNC_probe_read_user FunctionId = 112
|
||||
BPF_FUNC_probe_read_kernel FunctionId = 113
|
||||
BPF_FUNC_probe_read_user_str FunctionId = 114
|
||||
BPF_FUNC_probe_read_kernel_str FunctionId = 115
|
||||
BPF_FUNC_tcp_send_ack FunctionId = 116
|
||||
BPF_FUNC_send_signal_thread FunctionId = 117
|
||||
BPF_FUNC_jiffies64 FunctionId = 118
|
||||
BPF_FUNC_read_branch_records FunctionId = 119
|
||||
BPF_FUNC_get_ns_current_pid_tgid FunctionId = 120
|
||||
BPF_FUNC_xdp_output FunctionId = 121
|
||||
BPF_FUNC_get_netns_cookie FunctionId = 122
|
||||
BPF_FUNC_get_current_ancestor_cgroup_id FunctionId = 123
|
||||
BPF_FUNC_sk_assign FunctionId = 124
|
||||
BPF_FUNC_ktime_get_boot_ns FunctionId = 125
|
||||
BPF_FUNC_seq_printf FunctionId = 126
|
||||
BPF_FUNC_seq_write FunctionId = 127
|
||||
BPF_FUNC_sk_cgroup_id FunctionId = 128
|
||||
BPF_FUNC_sk_ancestor_cgroup_id FunctionId = 129
|
||||
BPF_FUNC_ringbuf_output FunctionId = 130
|
||||
BPF_FUNC_ringbuf_reserve FunctionId = 131
|
||||
BPF_FUNC_ringbuf_submit FunctionId = 132
|
||||
BPF_FUNC_ringbuf_discard FunctionId = 133
|
||||
BPF_FUNC_ringbuf_query FunctionId = 134
|
||||
BPF_FUNC_csum_level FunctionId = 135
|
||||
BPF_FUNC_skc_to_tcp6_sock FunctionId = 136
|
||||
BPF_FUNC_skc_to_tcp_sock FunctionId = 137
|
||||
BPF_FUNC_skc_to_tcp_timewait_sock FunctionId = 138
|
||||
BPF_FUNC_skc_to_tcp_request_sock FunctionId = 139
|
||||
BPF_FUNC_skc_to_udp6_sock FunctionId = 140
|
||||
BPF_FUNC_get_task_stack FunctionId = 141
|
||||
BPF_FUNC_load_hdr_opt FunctionId = 142
|
||||
BPF_FUNC_store_hdr_opt FunctionId = 143
|
||||
BPF_FUNC_reserve_hdr_opt FunctionId = 144
|
||||
BPF_FUNC_inode_storage_get FunctionId = 145
|
||||
BPF_FUNC_inode_storage_delete FunctionId = 146
|
||||
BPF_FUNC_d_path FunctionId = 147
|
||||
BPF_FUNC_copy_from_user FunctionId = 148
|
||||
BPF_FUNC_snprintf_btf FunctionId = 149
|
||||
BPF_FUNC_seq_printf_btf FunctionId = 150
|
||||
BPF_FUNC_skb_cgroup_classid FunctionId = 151
|
||||
BPF_FUNC_redirect_neigh FunctionId = 152
|
||||
BPF_FUNC_per_cpu_ptr FunctionId = 153
|
||||
BPF_FUNC_this_cpu_ptr FunctionId = 154
|
||||
BPF_FUNC_redirect_peer FunctionId = 155
|
||||
BPF_FUNC_task_storage_get FunctionId = 156
|
||||
BPF_FUNC_task_storage_delete FunctionId = 157
|
||||
BPF_FUNC_get_current_task_btf FunctionId = 158
|
||||
BPF_FUNC_bprm_opts_set FunctionId = 159
|
||||
BPF_FUNC_ktime_get_coarse_ns FunctionId = 160
|
||||
BPF_FUNC_ima_inode_hash FunctionId = 161
|
||||
BPF_FUNC_sock_from_file FunctionId = 162
|
||||
BPF_FUNC_check_mtu FunctionId = 163
|
||||
BPF_FUNC_for_each_map_elem FunctionId = 164
|
||||
BPF_FUNC_snprintf FunctionId = 165
|
||||
__BPF_FUNC_MAX_ID FunctionId = 166
|
||||
)
|
||||
|
||||
type HdrStartOff int32
|
||||
|
||||
const (
|
||||
BPF_HDR_START_MAC HdrStartOff = 0
|
||||
BPF_HDR_START_NET HdrStartOff = 1
|
||||
)
|
||||
|
||||
type LinkType int32
|
||||
|
||||
const (
|
||||
BPF_LINK_TYPE_UNSPEC LinkType = 0
|
||||
BPF_LINK_TYPE_RAW_TRACEPOINT LinkType = 1
|
||||
BPF_LINK_TYPE_TRACING LinkType = 2
|
||||
BPF_LINK_TYPE_CGROUP LinkType = 3
|
||||
BPF_LINK_TYPE_ITER LinkType = 4
|
||||
BPF_LINK_TYPE_NETNS LinkType = 5
|
||||
BPF_LINK_TYPE_XDP LinkType = 6
|
||||
MAX_BPF_LINK_TYPE LinkType = 7
|
||||
)
|
||||
|
||||
type MapType int32
|
||||
|
||||
const (
|
||||
BPF_MAP_TYPE_UNSPEC MapType = 0
|
||||
BPF_MAP_TYPE_HASH MapType = 1
|
||||
BPF_MAP_TYPE_ARRAY MapType = 2
|
||||
BPF_MAP_TYPE_PROG_ARRAY MapType = 3
|
||||
BPF_MAP_TYPE_PERF_EVENT_ARRAY MapType = 4
|
||||
BPF_MAP_TYPE_PERCPU_HASH MapType = 5
|
||||
BPF_MAP_TYPE_PERCPU_ARRAY MapType = 6
|
||||
BPF_MAP_TYPE_STACK_TRACE MapType = 7
|
||||
BPF_MAP_TYPE_CGROUP_ARRAY MapType = 8
|
||||
BPF_MAP_TYPE_LRU_HASH MapType = 9
|
||||
BPF_MAP_TYPE_LRU_PERCPU_HASH MapType = 10
|
||||
BPF_MAP_TYPE_LPM_TRIE MapType = 11
|
||||
BPF_MAP_TYPE_ARRAY_OF_MAPS MapType = 12
|
||||
BPF_MAP_TYPE_HASH_OF_MAPS MapType = 13
|
||||
BPF_MAP_TYPE_DEVMAP MapType = 14
|
||||
BPF_MAP_TYPE_SOCKMAP MapType = 15
|
||||
BPF_MAP_TYPE_CPUMAP MapType = 16
|
||||
BPF_MAP_TYPE_XSKMAP MapType = 17
|
||||
BPF_MAP_TYPE_SOCKHASH MapType = 18
|
||||
BPF_MAP_TYPE_CGROUP_STORAGE MapType = 19
|
||||
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY MapType = 20
|
||||
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE MapType = 21
|
||||
BPF_MAP_TYPE_QUEUE MapType = 22
|
||||
BPF_MAP_TYPE_STACK MapType = 23
|
||||
BPF_MAP_TYPE_SK_STORAGE MapType = 24
|
||||
BPF_MAP_TYPE_DEVMAP_HASH MapType = 25
|
||||
BPF_MAP_TYPE_STRUCT_OPS MapType = 26
|
||||
BPF_MAP_TYPE_RINGBUF MapType = 27
|
||||
BPF_MAP_TYPE_INODE_STORAGE MapType = 28
|
||||
BPF_MAP_TYPE_TASK_STORAGE MapType = 29
|
||||
)
|
||||
|
||||
type ProgType int32
|
||||
|
||||
const (
|
||||
BPF_PROG_TYPE_UNSPEC ProgType = 0
|
||||
BPF_PROG_TYPE_SOCKET_FILTER ProgType = 1
|
||||
BPF_PROG_TYPE_KPROBE ProgType = 2
|
||||
BPF_PROG_TYPE_SCHED_CLS ProgType = 3
|
||||
BPF_PROG_TYPE_SCHED_ACT ProgType = 4
|
||||
BPF_PROG_TYPE_TRACEPOINT ProgType = 5
|
||||
BPF_PROG_TYPE_XDP ProgType = 6
|
||||
BPF_PROG_TYPE_PERF_EVENT ProgType = 7
|
||||
BPF_PROG_TYPE_CGROUP_SKB ProgType = 8
|
||||
BPF_PROG_TYPE_CGROUP_SOCK ProgType = 9
|
||||
BPF_PROG_TYPE_LWT_IN ProgType = 10
|
||||
BPF_PROG_TYPE_LWT_OUT ProgType = 11
|
||||
BPF_PROG_TYPE_LWT_XMIT ProgType = 12
|
||||
BPF_PROG_TYPE_SOCK_OPS ProgType = 13
|
||||
BPF_PROG_TYPE_SK_SKB ProgType = 14
|
||||
BPF_PROG_TYPE_CGROUP_DEVICE ProgType = 15
|
||||
BPF_PROG_TYPE_SK_MSG ProgType = 16
|
||||
BPF_PROG_TYPE_RAW_TRACEPOINT ProgType = 17
|
||||
BPF_PROG_TYPE_CGROUP_SOCK_ADDR ProgType = 18
|
||||
BPF_PROG_TYPE_LWT_SEG6LOCAL ProgType = 19
|
||||
BPF_PROG_TYPE_LIRC_MODE2 ProgType = 20
|
||||
BPF_PROG_TYPE_SK_REUSEPORT ProgType = 21
|
||||
BPF_PROG_TYPE_FLOW_DISSECTOR ProgType = 22
|
||||
BPF_PROG_TYPE_CGROUP_SYSCTL ProgType = 23
|
||||
BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE ProgType = 24
|
||||
BPF_PROG_TYPE_CGROUP_SOCKOPT ProgType = 25
|
||||
BPF_PROG_TYPE_TRACING ProgType = 26
|
||||
BPF_PROG_TYPE_STRUCT_OPS ProgType = 27
|
||||
BPF_PROG_TYPE_EXT ProgType = 28
|
||||
BPF_PROG_TYPE_LSM ProgType = 29
|
||||
BPF_PROG_TYPE_SK_LOOKUP ProgType = 30
|
||||
)
|
||||
|
||||
type RetCode int32
|
||||
|
||||
const (
|
||||
BPF_OK RetCode = 0
|
||||
BPF_DROP RetCode = 2
|
||||
BPF_REDIRECT RetCode = 7
|
||||
BPF_LWT_REROUTE RetCode = 128
|
||||
)
|
||||
|
||||
type SkAction int32
|
||||
|
||||
const (
|
||||
SK_DROP SkAction = 0
|
||||
SK_PASS SkAction = 1
|
||||
)
|
||||
|
||||
type StackBuildIdStatus int32
|
||||
|
||||
const (
|
||||
BPF_STACK_BUILD_ID_EMPTY StackBuildIdStatus = 0
|
||||
BPF_STACK_BUILD_ID_VALID StackBuildIdStatus = 1
|
||||
BPF_STACK_BUILD_ID_IP StackBuildIdStatus = 2
|
||||
)
|
||||
|
||||
type StatsType int32
|
||||
|
||||
const (
|
||||
BPF_STATS_RUN_TIME StatsType = 0
|
||||
)
|
||||
|
||||
type XdpAction int32
|
||||
|
||||
const (
|
||||
XDP_ABORTED XdpAction = 0
|
||||
XDP_DROP XdpAction = 1
|
||||
XDP_PASS XdpAction = 2
|
||||
XDP_TX XdpAction = 3
|
||||
XDP_REDIRECT XdpAction = 4
|
||||
)
|
||||
|
||||
type BtfInfo struct {
|
||||
Btf Pointer
|
||||
BtfSize uint32
|
||||
Id uint32
|
||||
Name Pointer
|
||||
NameLen uint32
|
||||
KernelBtf uint32
|
||||
}
|
||||
|
||||
type FuncInfo struct {
|
||||
InsnOff uint32
|
||||
TypeId uint32
|
||||
}
|
||||
|
||||
type LineInfo struct {
|
||||
InsnOff uint32
|
||||
FileNameOff uint32
|
||||
LineOff uint32
|
||||
LineCol uint32
|
||||
}
|
||||
|
||||
type LinkInfo struct {
|
||||
Type LinkType
|
||||
Id LinkID
|
||||
ProgId uint32
|
||||
_ [4]byte
|
||||
Extra [16]uint8
|
||||
}
|
||||
|
||||
type MapInfo struct {
|
||||
Type uint32
|
||||
Id uint32
|
||||
KeySize uint32
|
||||
ValueSize uint32
|
||||
MaxEntries uint32
|
||||
MapFlags uint32
|
||||
Name ObjName
|
||||
Ifindex uint32
|
||||
BtfVmlinuxValueTypeId uint32
|
||||
NetnsDev uint64
|
||||
NetnsIno uint64
|
||||
BtfId uint32
|
||||
BtfKeyTypeId uint32
|
||||
BtfValueTypeId uint32
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
type ProgInfo struct {
|
||||
Type uint32
|
||||
Id uint32
|
||||
Tag [8]uint8
|
||||
JitedProgLen uint32
|
||||
XlatedProgLen uint32
|
||||
JitedProgInsns uint64
|
||||
XlatedProgInsns Pointer
|
||||
LoadTime uint64
|
||||
CreatedByUid uint32
|
||||
NrMapIds uint32
|
||||
MapIds Pointer
|
||||
Name ObjName
|
||||
Ifindex uint32
|
||||
_ [4]byte /* unsupported bitfield */
|
||||
NetnsDev uint64
|
||||
NetnsIno uint64
|
||||
NrJitedKsyms uint32
|
||||
NrJitedFuncLens uint32
|
||||
JitedKsyms uint64
|
||||
JitedFuncLens uint64
|
||||
BtfId uint32
|
||||
FuncInfoRecSize uint32
|
||||
FuncInfo uint64
|
||||
NrFuncInfo uint32
|
||||
NrLineInfo uint32
|
||||
LineInfo uint64
|
||||
JitedLineInfo uint64
|
||||
NrJitedLineInfo uint32
|
||||
LineInfoRecSize uint32
|
||||
JitedLineInfoRecSize uint32
|
||||
NrProgTags uint32
|
||||
ProgTags uint64
|
||||
RunTimeNs uint64
|
||||
RunCnt uint64
|
||||
RecursionMisses uint64
|
||||
}
|
||||
|
||||
type BtfGetFdByIdAttr struct{ Id uint32 }
|
||||
|
||||
func BtfGetFdById(attr *BtfGetFdByIdAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_BTF_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type BtfLoadAttr struct {
|
||||
Btf Pointer
|
||||
BtfLogBuf Pointer
|
||||
BtfSize uint32
|
||||
BtfLogSize uint32
|
||||
BtfLogLevel uint32
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
func BtfLoad(attr *BtfLoadAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_BTF_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type EnableStatsAttr struct{ Type uint32 }
|
||||
|
||||
func EnableStats(attr *EnableStatsAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type IterCreateAttr struct {
|
||||
LinkFd uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
func IterCreate(attr *IterCreateAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_ITER_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type LinkCreateAttr struct {
|
||||
ProgFd uint32
|
||||
TargetFd uint32
|
||||
AttachType AttachType
|
||||
Flags uint32
|
||||
TargetBtfId uint32
|
||||
_ [12]byte
|
||||
}
|
||||
|
||||
func LinkCreate(attr *LinkCreateAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type LinkCreateIterAttr struct {
|
||||
ProgFd uint32
|
||||
TargetFd uint32
|
||||
AttachType AttachType
|
||||
Flags uint32
|
||||
IterInfo Pointer
|
||||
IterInfoLen uint32
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
func LinkCreateIter(attr *LinkCreateIterAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type LinkUpdateAttr struct {
|
||||
LinkFd uint32
|
||||
NewProgFd uint32
|
||||
Flags uint32
|
||||
OldProgFd uint32
|
||||
}
|
||||
|
||||
func LinkUpdate(attr *LinkUpdateAttr) error {
|
||||
_, err := BPF(BPF_LINK_UPDATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapCreateAttr struct {
|
||||
MapType MapType
|
||||
KeySize uint32
|
||||
ValueSize uint32
|
||||
MaxEntries uint32
|
||||
MapFlags uint32
|
||||
InnerMapFd uint32
|
||||
NumaNode uint32
|
||||
MapName ObjName
|
||||
MapIfindex uint32
|
||||
BtfFd uint32
|
||||
BtfKeyTypeId uint32
|
||||
BtfValueTypeId uint32
|
||||
BtfVmlinuxValueTypeId uint32
|
||||
}
|
||||
|
||||
func MapCreate(attr *MapCreateAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type MapDeleteBatchAttr struct {
|
||||
InBatch Pointer
|
||||
OutBatch Pointer
|
||||
Keys Pointer
|
||||
Values Pointer
|
||||
Count uint32
|
||||
MapFd uint32
|
||||
ElemFlags uint64
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func MapDeleteBatch(attr *MapDeleteBatchAttr) error {
|
||||
_, err := BPF(BPF_MAP_DELETE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapDeleteElemAttr struct {
|
||||
MapFd uint32
|
||||
_ [4]byte
|
||||
Key Pointer
|
||||
Value Pointer
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func MapDeleteElem(attr *MapDeleteElemAttr) error {
|
||||
_, err := BPF(BPF_MAP_DELETE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapFreezeAttr struct{ MapFd uint32 }
|
||||
|
||||
func MapFreeze(attr *MapFreezeAttr) error {
|
||||
_, err := BPF(BPF_MAP_FREEZE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapGetFdByIdAttr struct{ Id uint32 }
|
||||
|
||||
func MapGetFdById(attr *MapGetFdByIdAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_MAP_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type MapGetNextIdAttr struct {
|
||||
Id uint32
|
||||
NextId uint32
|
||||
}
|
||||
|
||||
func MapGetNextId(attr *MapGetNextIdAttr) error {
|
||||
_, err := BPF(BPF_MAP_GET_NEXT_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapGetNextKeyAttr struct {
|
||||
MapFd uint32
|
||||
_ [4]byte
|
||||
Key Pointer
|
||||
NextKey Pointer
|
||||
}
|
||||
|
||||
func MapGetNextKey(attr *MapGetNextKeyAttr) error {
|
||||
_, err := BPF(BPF_MAP_GET_NEXT_KEY, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapLookupAndDeleteBatchAttr struct {
|
||||
InBatch Pointer
|
||||
OutBatch Pointer
|
||||
Keys Pointer
|
||||
Values Pointer
|
||||
Count uint32
|
||||
MapFd uint32
|
||||
ElemFlags uint64
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func MapLookupAndDeleteBatch(attr *MapLookupAndDeleteBatchAttr) error {
|
||||
_, err := BPF(BPF_MAP_LOOKUP_AND_DELETE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapLookupAndDeleteElemAttr struct {
|
||||
MapFd uint32
|
||||
_ [4]byte
|
||||
Key Pointer
|
||||
Value Pointer
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func MapLookupAndDeleteElem(attr *MapLookupAndDeleteElemAttr) error {
|
||||
_, err := BPF(BPF_MAP_LOOKUP_AND_DELETE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapLookupBatchAttr struct {
|
||||
InBatch Pointer
|
||||
OutBatch Pointer
|
||||
Keys Pointer
|
||||
Values Pointer
|
||||
Count uint32
|
||||
MapFd uint32
|
||||
ElemFlags uint64
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func MapLookupBatch(attr *MapLookupBatchAttr) error {
|
||||
_, err := BPF(BPF_MAP_LOOKUP_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapLookupElemAttr struct {
|
||||
MapFd uint32
|
||||
_ [4]byte
|
||||
Key Pointer
|
||||
Value Pointer
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func MapLookupElem(attr *MapLookupElemAttr) error {
|
||||
_, err := BPF(BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapUpdateBatchAttr struct {
|
||||
InBatch Pointer
|
||||
OutBatch Pointer
|
||||
Keys Pointer
|
||||
Values Pointer
|
||||
Count uint32
|
||||
MapFd uint32
|
||||
ElemFlags uint64
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func MapUpdateBatch(attr *MapUpdateBatchAttr) error {
|
||||
_, err := BPF(BPF_MAP_UPDATE_BATCH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type MapUpdateElemAttr struct {
|
||||
MapFd uint32
|
||||
_ [4]byte
|
||||
Key Pointer
|
||||
Value Pointer
|
||||
Flags uint64
|
||||
}
|
||||
|
||||
func MapUpdateElem(attr *MapUpdateElemAttr) error {
|
||||
_, err := BPF(BPF_MAP_UPDATE_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type ObjGetAttr struct {
|
||||
Pathname Pointer
|
||||
BpfFd uint32
|
||||
FileFlags uint32
|
||||
}
|
||||
|
||||
func ObjGet(attr *ObjGetAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_OBJ_GET, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type ObjGetInfoByFdAttr struct {
|
||||
BpfFd uint32
|
||||
InfoLen uint32
|
||||
Info Pointer
|
||||
}
|
||||
|
||||
func ObjGetInfoByFd(attr *ObjGetInfoByFdAttr) error {
|
||||
_, err := BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type ObjPinAttr struct {
|
||||
Pathname Pointer
|
||||
BpfFd uint32
|
||||
FileFlags uint32
|
||||
}
|
||||
|
||||
func ObjPin(attr *ObjPinAttr) error {
|
||||
_, err := BPF(BPF_OBJ_PIN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type ProgAttachAttr struct {
|
||||
TargetFd uint32
|
||||
AttachBpfFd uint32
|
||||
AttachType uint32
|
||||
AttachFlags uint32
|
||||
ReplaceBpfFd uint32
|
||||
}
|
||||
|
||||
func ProgAttach(attr *ProgAttachAttr) error {
|
||||
_, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type ProgBindMapAttr struct {
|
||||
ProgFd uint32
|
||||
MapFd uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
func ProgBindMap(attr *ProgBindMapAttr) error {
|
||||
_, err := BPF(BPF_PROG_BIND_MAP, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type ProgDetachAttr struct {
|
||||
TargetFd uint32
|
||||
AttachBpfFd uint32
|
||||
AttachType uint32
|
||||
}
|
||||
|
||||
func ProgDetach(attr *ProgDetachAttr) error {
|
||||
_, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type ProgGetFdByIdAttr struct{ Id uint32 }
|
||||
|
||||
func ProgGetFdById(attr *ProgGetFdByIdAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_PROG_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type ProgGetNextIdAttr struct {
|
||||
Id uint32
|
||||
NextId uint32
|
||||
}
|
||||
|
||||
func ProgGetNextId(attr *ProgGetNextIdAttr) error {
|
||||
_, err := BPF(BPF_PROG_GET_NEXT_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type ProgLoadAttr struct {
|
||||
ProgType ProgType
|
||||
InsnCnt uint32
|
||||
Insns Pointer
|
||||
License Pointer
|
||||
LogLevel uint32
|
||||
LogSize uint32
|
||||
LogBuf Pointer
|
||||
KernVersion uint32
|
||||
ProgFlags uint32
|
||||
ProgName ObjName
|
||||
ProgIfindex uint32
|
||||
ExpectedAttachType AttachType
|
||||
ProgBtfFd uint32
|
||||
FuncInfoRecSize uint32
|
||||
FuncInfo Pointer
|
||||
FuncInfoCnt uint32
|
||||
LineInfoRecSize uint32
|
||||
LineInfo Pointer
|
||||
LineInfoCnt uint32
|
||||
AttachBtfId uint32
|
||||
AttachProgFd uint32
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
func ProgLoad(attr *ProgLoadAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_PROG_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type ProgRunAttr struct {
|
||||
ProgFd uint32
|
||||
Retval uint32
|
||||
DataSizeIn uint32
|
||||
DataSizeOut uint32
|
||||
DataIn Pointer
|
||||
DataOut Pointer
|
||||
Repeat uint32
|
||||
Duration uint32
|
||||
CtxSizeIn uint32
|
||||
CtxSizeOut uint32
|
||||
CtxIn Pointer
|
||||
CtxOut Pointer
|
||||
Flags uint32
|
||||
Cpu uint32
|
||||
}
|
||||
|
||||
func ProgRun(attr *ProgRunAttr) error {
|
||||
_, err := BPF(BPF_PROG_TEST_RUN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type RawTracepointOpenAttr struct {
|
||||
Name Pointer
|
||||
ProgFd uint32
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
func RawTracepointOpen(attr *RawTracepointOpenAttr) (*FD, error) {
|
||||
fd, err := BPF(BPF_RAW_TRACEPOINT_OPEN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFD(int(fd))
|
||||
}
|
||||
|
||||
type CgroupLinkInfo struct {
|
||||
CgroupId uint64
|
||||
AttachType AttachType
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
type IterLinkInfo struct {
|
||||
TargetName Pointer
|
||||
TargetNameLen uint32
|
||||
}
|
||||
|
||||
type NetNsLinkInfo struct {
|
||||
NetnsIno uint32
|
||||
AttachType AttachType
|
||||
}
|
||||
|
||||
type RawTracepointLinkInfo struct {
|
||||
TpName Pointer
|
||||
TpNameLen uint32
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
type TracingLinkInfo struct {
|
||||
AttachType AttachType
|
||||
TargetObjId uint32
|
||||
TargetBtfId uint32
|
||||
}
|
||||
|
||||
type XDPLinkInfo struct{ Ifindex uint32 }
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
//go:generate stringer -output syscall_string.go -type=BPFCmd
|
||||
|
||||
// BPFCmd identifies a subcommand of the bpf syscall.
|
||||
type BPFCmd int
|
||||
|
||||
// Well known BPF commands.
|
||||
const (
|
||||
BPF_MAP_CREATE BPFCmd = iota
|
||||
BPF_MAP_LOOKUP_ELEM
|
||||
BPF_MAP_UPDATE_ELEM
|
||||
BPF_MAP_DELETE_ELEM
|
||||
BPF_MAP_GET_NEXT_KEY
|
||||
BPF_PROG_LOAD
|
||||
BPF_OBJ_PIN
|
||||
BPF_OBJ_GET
|
||||
BPF_PROG_ATTACH
|
||||
BPF_PROG_DETACH
|
||||
BPF_PROG_TEST_RUN
|
||||
BPF_PROG_GET_NEXT_ID
|
||||
BPF_MAP_GET_NEXT_ID
|
||||
BPF_PROG_GET_FD_BY_ID
|
||||
BPF_MAP_GET_FD_BY_ID
|
||||
BPF_OBJ_GET_INFO_BY_FD
|
||||
BPF_PROG_QUERY
|
||||
BPF_RAW_TRACEPOINT_OPEN
|
||||
BPF_BTF_LOAD
|
||||
BPF_BTF_GET_FD_BY_ID
|
||||
BPF_TASK_FD_QUERY
|
||||
BPF_MAP_LOOKUP_AND_DELETE_ELEM
|
||||
BPF_MAP_FREEZE
|
||||
BPF_BTF_GET_NEXT_ID
|
||||
BPF_MAP_LOOKUP_BATCH
|
||||
BPF_MAP_LOOKUP_AND_DELETE_BATCH
|
||||
BPF_MAP_UPDATE_BATCH
|
||||
BPF_MAP_DELETE_BATCH
|
||||
BPF_LINK_CREATE
|
||||
BPF_LINK_UPDATE
|
||||
BPF_LINK_GET_FD_BY_ID
|
||||
BPF_LINK_GET_NEXT_ID
|
||||
BPF_ENABLE_STATS
|
||||
BPF_ITER_CREATE
|
||||
)
|
||||
|
||||
// BPF wraps SYS_BPF.
|
||||
//
|
||||
// Any pointers contained in attr must use the Pointer type from this package.
|
||||
func BPF(cmd BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
|
||||
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
|
||||
runtime.KeepAlive(attr)
|
||||
|
||||
var err error
|
||||
if errNo != 0 {
|
||||
err = errNo
|
||||
}
|
||||
|
||||
return r1, err
|
||||
}
|
||||
|
||||
type BPFProgAttachAttr struct {
|
||||
TargetFd uint32
|
||||
AttachBpfFd uint32
|
||||
AttachType uint32
|
||||
AttachFlags uint32
|
||||
ReplaceBpfFd uint32
|
||||
}
|
||||
|
||||
func BPFProgAttach(attr *BPFProgAttachAttr) error {
|
||||
_, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type BPFProgDetachAttr struct {
|
||||
TargetFd uint32
|
||||
AttachBpfFd uint32
|
||||
AttachType uint32
|
||||
}
|
||||
|
||||
func BPFProgDetach(attr *BPFProgDetachAttr) error {
|
||||
_, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
return err
|
||||
}
|
||||
|
||||
type BPFEnableStatsAttr struct {
|
||||
StatsType uint32
|
||||
}
|
||||
|
||||
func BPFEnableStats(attr *BPFEnableStatsAttr) (*FD, error) {
|
||||
ptr, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("enable stats: %w", err)
|
||||
}
|
||||
return NewFD(uint32(ptr)), nil
|
||||
|
||||
}
|
||||
|
||||
type bpfObjAttr struct {
|
||||
fileName Pointer
|
||||
fd uint32
|
||||
fileFlags uint32
|
||||
}
|
||||
|
||||
const bpfFSType = 0xcafe4a11
|
||||
|
||||
// BPFObjPin wraps BPF_OBJ_PIN.
|
||||
func BPFObjPin(fileName string, fd *FD) error {
|
||||
dirName := filepath.Dir(fileName)
|
||||
var statfs unix.Statfs_t
|
||||
if err := unix.Statfs(dirName, &statfs); err != nil {
|
||||
return err
|
||||
}
|
||||
if uint64(statfs.Type) != bpfFSType {
|
||||
return fmt.Errorf("%s is not on a bpf filesystem", fileName)
|
||||
}
|
||||
|
||||
value, err := fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attr := bpfObjAttr{
|
||||
fileName: NewStringPointer(fileName),
|
||||
fd: value,
|
||||
}
|
||||
_, err = BPF(BPF_OBJ_PIN, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("pin object %s: %w", fileName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BPFObjGet wraps BPF_OBJ_GET.
|
||||
func BPFObjGet(fileName string) (*FD, error) {
|
||||
attr := bpfObjAttr{
|
||||
fileName: NewStringPointer(fileName),
|
||||
}
|
||||
ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get object %s: %w", fileName, err)
|
||||
}
|
||||
return NewFD(uint32(ptr)), nil
|
||||
}
|
||||
|
||||
type bpfObjGetInfoByFDAttr struct {
|
||||
fd uint32
|
||||
infoLen uint32
|
||||
info Pointer
|
||||
}
|
||||
|
||||
// BPFObjGetInfoByFD wraps BPF_OBJ_GET_INFO_BY_FD.
|
||||
//
|
||||
// Available from 4.13.
|
||||
func BPFObjGetInfoByFD(fd *FD, info unsafe.Pointer, size uintptr) error {
|
||||
value, err := fd.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attr := bpfObjGetInfoByFDAttr{
|
||||
fd: value,
|
||||
infoLen: uint32(size),
|
||||
info: NewPointer(info),
|
||||
}
|
||||
_, err = BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("fd %v: %w", fd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
// Code generated by "stringer -output syscall_string.go -type=BPFCmd"; DO NOT EDIT.
|
||||
|
||||
package internal
|
||||
|
||||
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[BPF_MAP_CREATE-0]
|
||||
_ = x[BPF_MAP_LOOKUP_ELEM-1]
|
||||
_ = x[BPF_MAP_UPDATE_ELEM-2]
|
||||
_ = x[BPF_MAP_DELETE_ELEM-3]
|
||||
_ = x[BPF_MAP_GET_NEXT_KEY-4]
|
||||
_ = x[BPF_PROG_LOAD-5]
|
||||
_ = x[BPF_OBJ_PIN-6]
|
||||
_ = x[BPF_OBJ_GET-7]
|
||||
_ = x[BPF_PROG_ATTACH-8]
|
||||
_ = x[BPF_PROG_DETACH-9]
|
||||
_ = x[BPF_PROG_TEST_RUN-10]
|
||||
_ = x[BPF_PROG_GET_NEXT_ID-11]
|
||||
_ = x[BPF_MAP_GET_NEXT_ID-12]
|
||||
_ = x[BPF_PROG_GET_FD_BY_ID-13]
|
||||
_ = x[BPF_MAP_GET_FD_BY_ID-14]
|
||||
_ = x[BPF_OBJ_GET_INFO_BY_FD-15]
|
||||
_ = x[BPF_PROG_QUERY-16]
|
||||
_ = x[BPF_RAW_TRACEPOINT_OPEN-17]
|
||||
_ = x[BPF_BTF_LOAD-18]
|
||||
_ = x[BPF_BTF_GET_FD_BY_ID-19]
|
||||
_ = x[BPF_TASK_FD_QUERY-20]
|
||||
_ = x[BPF_MAP_LOOKUP_AND_DELETE_ELEM-21]
|
||||
_ = x[BPF_MAP_FREEZE-22]
|
||||
_ = x[BPF_BTF_GET_NEXT_ID-23]
|
||||
_ = x[BPF_MAP_LOOKUP_BATCH-24]
|
||||
_ = x[BPF_MAP_LOOKUP_AND_DELETE_BATCH-25]
|
||||
_ = x[BPF_MAP_UPDATE_BATCH-26]
|
||||
_ = x[BPF_MAP_DELETE_BATCH-27]
|
||||
_ = x[BPF_LINK_CREATE-28]
|
||||
_ = x[BPF_LINK_UPDATE-29]
|
||||
_ = x[BPF_LINK_GET_FD_BY_ID-30]
|
||||
_ = x[BPF_LINK_GET_NEXT_ID-31]
|
||||
_ = x[BPF_ENABLE_STATS-32]
|
||||
_ = x[BPF_ITER_CREATE-33]
|
||||
}
|
||||
|
||||
const _BPFCmd_name = "BPF_MAP_CREATEBPF_MAP_LOOKUP_ELEMBPF_MAP_UPDATE_ELEMBPF_MAP_DELETE_ELEMBPF_MAP_GET_NEXT_KEYBPF_PROG_LOADBPF_OBJ_PINBPF_OBJ_GETBPF_PROG_ATTACHBPF_PROG_DETACHBPF_PROG_TEST_RUNBPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_PROG_GET_FD_BY_IDBPF_MAP_GET_FD_BY_IDBPF_OBJ_GET_INFO_BY_FDBPF_PROG_QUERYBPF_RAW_TRACEPOINT_OPENBPF_BTF_LOADBPF_BTF_GET_FD_BY_IDBPF_TASK_FD_QUERYBPF_MAP_LOOKUP_AND_DELETE_ELEMBPF_MAP_FREEZEBPF_BTF_GET_NEXT_IDBPF_MAP_LOOKUP_BATCHBPF_MAP_LOOKUP_AND_DELETE_BATCHBPF_MAP_UPDATE_BATCHBPF_MAP_DELETE_BATCHBPF_LINK_CREATEBPF_LINK_UPDATEBPF_LINK_GET_FD_BY_IDBPF_LINK_GET_NEXT_IDBPF_ENABLE_STATSBPF_ITER_CREATE"
|
||||
|
||||
var _BPFCmd_index = [...]uint16{0, 14, 33, 52, 71, 91, 104, 115, 126, 141, 156, 173, 193, 212, 233, 253, 275, 289, 312, 324, 344, 361, 391, 405, 424, 444, 475, 495, 515, 530, 545, 566, 586, 602, 617}
|
||||
|
||||
func (i BPFCmd) String() string {
|
||||
if i < 0 || i >= BPFCmd(len(_BPFCmd_index)-1) {
|
||||
return "BPFCmd(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _BPFCmd_name[_BPFCmd_index[i]:_BPFCmd_index[i+1]]
|
||||
}
|
||||
+56
-17
@@ -1,9 +1,9 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package unix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"syscall"
|
||||
|
||||
linux "golang.org/x/sys/unix"
|
||||
@@ -20,16 +20,26 @@ const (
|
||||
EPERM = linux.EPERM
|
||||
ESRCH = linux.ESRCH
|
||||
ENODEV = linux.ENODEV
|
||||
EBADF = linux.EBADF
|
||||
E2BIG = linux.E2BIG
|
||||
EFAULT = linux.EFAULT
|
||||
// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
|
||||
ENOTSUPP = syscall.Errno(0x20c)
|
||||
|
||||
EBADF = linux.EBADF
|
||||
BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC
|
||||
BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE
|
||||
BPF_F_RDONLY = linux.BPF_F_RDONLY
|
||||
BPF_F_WRONLY = linux.BPF_F_WRONLY
|
||||
BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG
|
||||
BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG
|
||||
BPF_F_SLEEPABLE = linux.BPF_F_SLEEPABLE
|
||||
BPF_F_MMAPABLE = linux.BPF_F_MMAPABLE
|
||||
BPF_F_INNER_MAP = linux.BPF_F_INNER_MAP
|
||||
BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN
|
||||
BPF_TAG_SIZE = linux.BPF_TAG_SIZE
|
||||
BPF_RINGBUF_BUSY_BIT = linux.BPF_RINGBUF_BUSY_BIT
|
||||
BPF_RINGBUF_DISCARD_BIT = linux.BPF_RINGBUF_DISCARD_BIT
|
||||
BPF_RINGBUF_HDR_SZ = linux.BPF_RINGBUF_HDR_SZ
|
||||
SYS_BPF = linux.SYS_BPF
|
||||
F_DUPFD_CLOEXEC = linux.F_DUPFD_CLOEXEC
|
||||
EPOLL_CTL_ADD = linux.EPOLL_CTL_ADD
|
||||
@@ -39,27 +49,36 @@ const (
|
||||
PROT_READ = linux.PROT_READ
|
||||
PROT_WRITE = linux.PROT_WRITE
|
||||
MAP_SHARED = linux.MAP_SHARED
|
||||
PERF_ATTR_SIZE_VER1 = linux.PERF_ATTR_SIZE_VER1
|
||||
PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE
|
||||
PERF_TYPE_TRACEPOINT = linux.PERF_TYPE_TRACEPOINT
|
||||
PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT
|
||||
PERF_EVENT_IOC_DISABLE = linux.PERF_EVENT_IOC_DISABLE
|
||||
PERF_EVENT_IOC_ENABLE = linux.PERF_EVENT_IOC_ENABLE
|
||||
PERF_EVENT_IOC_SET_BPF = linux.PERF_EVENT_IOC_SET_BPF
|
||||
PerfBitWatermark = linux.PerfBitWatermark
|
||||
PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW
|
||||
PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC
|
||||
RLIM_INFINITY = linux.RLIM_INFINITY
|
||||
RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK
|
||||
BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME
|
||||
PERF_RECORD_LOST = linux.PERF_RECORD_LOST
|
||||
PERF_RECORD_SAMPLE = linux.PERF_RECORD_SAMPLE
|
||||
AT_FDCWD = linux.AT_FDCWD
|
||||
RENAME_NOREPLACE = linux.RENAME_NOREPLACE
|
||||
SO_ATTACH_BPF = linux.SO_ATTACH_BPF
|
||||
SO_DETACH_BPF = linux.SO_DETACH_BPF
|
||||
SOL_SOCKET = linux.SOL_SOCKET
|
||||
)
|
||||
|
||||
// Statfs_t is a wrapper
|
||||
type Statfs_t = linux.Statfs_t
|
||||
|
||||
type Stat_t = linux.Stat_t
|
||||
|
||||
// Rlimit is a wrapper
|
||||
type Rlimit = linux.Rlimit
|
||||
|
||||
// Setrlimit is a wrapper
|
||||
func Setrlimit(resource int, rlim *Rlimit) (err error) {
|
||||
return linux.Setrlimit(resource, rlim)
|
||||
}
|
||||
|
||||
// Syscall is a wrapper
|
||||
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
|
||||
return linux.Syscall(trap, a1, a2, a3)
|
||||
@@ -70,6 +89,11 @@ func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
|
||||
return linux.FcntlInt(fd, cmd, arg)
|
||||
}
|
||||
|
||||
// IoctlSetInt is a wrapper
|
||||
func IoctlSetInt(fd int, req uint, value int) error {
|
||||
return linux.IoctlSetInt(fd, req, value)
|
||||
}
|
||||
|
||||
// Statfs is a wrapper
|
||||
func Statfs(path string, buf *Statfs_t) (err error) {
|
||||
return linux.Statfs(path, buf)
|
||||
@@ -157,14 +181,29 @@ func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
|
||||
return linux.Tgkill(tgid, tid, sig)
|
||||
}
|
||||
|
||||
func KernelRelease() (string, error) {
|
||||
var uname Utsname
|
||||
err := Uname(&uname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
end := bytes.IndexByte(uname.Release[:], 0)
|
||||
release := string(uname.Release[:end])
|
||||
return release, nil
|
||||
// BytePtrFromString is a wrapper
|
||||
func BytePtrFromString(s string) (*byte, error) {
|
||||
return linux.BytePtrFromString(s)
|
||||
}
|
||||
|
||||
// ByteSliceToString is a wrapper
|
||||
func ByteSliceToString(s []byte) string {
|
||||
return linux.ByteSliceToString(s)
|
||||
}
|
||||
|
||||
// Renameat2 is a wrapper
|
||||
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
|
||||
return linux.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags)
|
||||
}
|
||||
|
||||
func Prlimit(pid, resource int, new, old *Rlimit) error {
|
||||
return linux.Prlimit(pid, resource, new, old)
|
||||
}
|
||||
|
||||
func Open(path string, mode int, perm uint32) (int, error) {
|
||||
return linux.Open(path, mode, perm)
|
||||
}
|
||||
|
||||
func Fstat(fd int, stat *Stat_t) error {
|
||||
return linux.Fstat(fd, stat)
|
||||
}
|
||||
|
||||
+56
-7
@@ -1,3 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package unix
|
||||
@@ -21,15 +22,25 @@ const (
|
||||
ESRCH = syscall.ESRCH
|
||||
ENODEV = syscall.ENODEV
|
||||
EBADF = syscall.Errno(0)
|
||||
E2BIG = syscall.Errno(0)
|
||||
EFAULT = syscall.EFAULT
|
||||
// ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP
|
||||
ENOTSUPP = syscall.Errno(0x20c)
|
||||
|
||||
BPF_F_NO_PREALLOC = 0
|
||||
BPF_F_NUMA_NODE = 0
|
||||
BPF_F_RDONLY = 0
|
||||
BPF_F_WRONLY = 0
|
||||
BPF_F_RDONLY_PROG = 0
|
||||
BPF_F_WRONLY_PROG = 0
|
||||
BPF_F_SLEEPABLE = 0
|
||||
BPF_F_MMAPABLE = 0
|
||||
BPF_F_INNER_MAP = 0
|
||||
BPF_OBJ_NAME_LEN = 0x10
|
||||
BPF_TAG_SIZE = 0x8
|
||||
BPF_RINGBUF_BUSY_BIT = 0
|
||||
BPF_RINGBUF_DISCARD_BIT = 0
|
||||
BPF_RINGBUF_HDR_SZ = 0
|
||||
SYS_BPF = 321
|
||||
F_DUPFD_CLOEXEC = 0x406
|
||||
EPOLLIN = 0x1
|
||||
@@ -40,14 +51,26 @@ const (
|
||||
PROT_READ = 0x1
|
||||
PROT_WRITE = 0x2
|
||||
MAP_SHARED = 0x1
|
||||
PERF_ATTR_SIZE_VER1 = 0
|
||||
PERF_TYPE_SOFTWARE = 0x1
|
||||
PERF_TYPE_TRACEPOINT = 0
|
||||
PERF_COUNT_SW_BPF_OUTPUT = 0xa
|
||||
PERF_EVENT_IOC_DISABLE = 0
|
||||
PERF_EVENT_IOC_ENABLE = 0
|
||||
PERF_EVENT_IOC_SET_BPF = 0
|
||||
PerfBitWatermark = 0x4000
|
||||
PERF_SAMPLE_RAW = 0x400
|
||||
PERF_FLAG_FD_CLOEXEC = 0x8
|
||||
RLIM_INFINITY = 0x7fffffffffffffff
|
||||
RLIMIT_MEMLOCK = 8
|
||||
BPF_STATS_RUN_TIME = 0
|
||||
PERF_RECORD_LOST = 2
|
||||
PERF_RECORD_SAMPLE = 9
|
||||
AT_FDCWD = -0x2
|
||||
RENAME_NOREPLACE = 0x1
|
||||
SO_ATTACH_BPF = 0x32
|
||||
SO_DETACH_BPF = 0x1b
|
||||
SOL_SOCKET = 0x1
|
||||
)
|
||||
|
||||
// Statfs_t is a wrapper
|
||||
@@ -66,17 +89,14 @@ type Statfs_t struct {
|
||||
Spare [4]int64
|
||||
}
|
||||
|
||||
type Stat_t struct{}
|
||||
|
||||
// Rlimit is a wrapper
|
||||
type Rlimit struct {
|
||||
Cur uint64
|
||||
Max uint64
|
||||
}
|
||||
|
||||
// Setrlimit is a wrapper
|
||||
func Setrlimit(resource int, rlim *Rlimit) (err error) {
|
||||
return errNonLinux
|
||||
}
|
||||
|
||||
// Syscall is a wrapper
|
||||
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
|
||||
return 0, 0, syscall.Errno(1)
|
||||
@@ -87,6 +107,11 @@ func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
|
||||
return -1, errNonLinux
|
||||
}
|
||||
|
||||
// IoctlSetInt is a wrapper
|
||||
func IoctlSetInt(fd int, req uint, value int) error {
|
||||
return errNonLinux
|
||||
}
|
||||
|
||||
// Statfs is a wrapper
|
||||
func Statfs(path string, buf *Statfs_t) error {
|
||||
return errNonLinux
|
||||
@@ -201,6 +226,7 @@ func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int
|
||||
// Utsname is a wrapper
|
||||
type Utsname struct {
|
||||
Release [65]byte
|
||||
Version [65]byte
|
||||
}
|
||||
|
||||
// Uname is a wrapper
|
||||
@@ -223,6 +249,29 @@ func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
|
||||
return errNonLinux
|
||||
}
|
||||
|
||||
func KernelRelease() (string, error) {
|
||||
return "", errNonLinux
|
||||
// BytePtrFromString is a wrapper
|
||||
func BytePtrFromString(s string) (*byte, error) {
|
||||
return nil, errNonLinux
|
||||
}
|
||||
|
||||
// ByteSliceToString is a wrapper
|
||||
func ByteSliceToString(s []byte) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Renameat2 is a wrapper
|
||||
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
|
||||
return errNonLinux
|
||||
}
|
||||
|
||||
func Prlimit(pid, resource int, new, old *Rlimit) error {
|
||||
return errNonLinux
|
||||
}
|
||||
|
||||
func Open(path string, mode int, perm uint32) (int, error) {
|
||||
return -1, errNonLinux
|
||||
}
|
||||
|
||||
func Fstat(fd int, stat *Stat_t) error {
|
||||
return errNonLinux
|
||||
}
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"github.com/cilium/ebpf/internal/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
errAuxvNoVDSO = errors.New("no vdso address found in auxv")
|
||||
)
|
||||
|
||||
// vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
|
||||
// linked into the current process image.
|
||||
func vdsoVersion() (uint32, error) {
|
||||
// Read data from the auxiliary vector, which is normally passed directly
|
||||
// to the process. Go does not expose that data, so we must read it from procfs.
|
||||
// https://man7.org/linux/man-pages/man3/getauxval.3.html
|
||||
av, err := os.Open("/proc/self/auxv")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("opening auxv: %w", err)
|
||||
}
|
||||
defer av.Close()
|
||||
|
||||
vdsoAddr, err := vdsoMemoryAddress(av)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("finding vDSO memory address: %w", err)
|
||||
}
|
||||
|
||||
// Use /proc/self/mem rather than unsafe.Pointer tricks.
|
||||
mem, err := os.Open("/proc/self/mem")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("opening mem: %w", err)
|
||||
}
|
||||
defer mem.Close()
|
||||
|
||||
// Open ELF at provided memory address, as offset into /proc/self/mem.
|
||||
c, err := vdsoLinuxVersionCode(io.NewSectionReader(mem, int64(vdsoAddr), math.MaxInt64))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("reading linux version code: %w", err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// vdsoMemoryAddress returns the memory address of the vDSO library
|
||||
// linked into the current process image. r is an io.Reader into an auxv blob.
|
||||
func vdsoMemoryAddress(r io.Reader) (uint64, error) {
|
||||
const (
|
||||
_AT_NULL = 0 // End of vector
|
||||
_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
|
||||
)
|
||||
|
||||
// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`,
|
||||
// the address of a page containing the virtual Dynamic Shared Object (vDSO).
|
||||
aux := struct{ Tag, Val uint64 }{}
|
||||
for {
|
||||
if err := binary.Read(r, NativeEndian, &aux); err != nil {
|
||||
return 0, fmt.Errorf("reading auxv entry: %w", err)
|
||||
}
|
||||
|
||||
switch aux.Tag {
|
||||
case _AT_SYSINFO_EHDR:
|
||||
if aux.Val != 0 {
|
||||
return aux.Val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("invalid vDSO address in auxv")
|
||||
// _AT_NULL is always the last tag/val pair in the aux vector
|
||||
// and can be treated like EOF.
|
||||
case _AT_NULL:
|
||||
return 0, errAuxvNoVDSO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// format described at https://www.man7.org/linux/man-pages/man5/elf.5.html in section 'Notes (Nhdr)'
|
||||
type elfNoteHeader struct {
|
||||
NameSize int32
|
||||
DescSize int32
|
||||
Type int32
|
||||
}
|
||||
|
||||
// vdsoLinuxVersionCode returns the LINUX_VERSION_CODE embedded in
|
||||
// the ELF notes section of the binary provided by the reader.
|
||||
func vdsoLinuxVersionCode(r io.ReaderAt) (uint32, error) {
|
||||
hdr, err := NewSafeELFFile(r)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("reading vDSO ELF: %w", err)
|
||||
}
|
||||
|
||||
sec := hdr.SectionByType(elf.SHT_NOTE)
|
||||
if sec == nil {
|
||||
return 0, fmt.Errorf("no note section found in vDSO ELF")
|
||||
}
|
||||
|
||||
sr := sec.Open()
|
||||
var n elfNoteHeader
|
||||
|
||||
// Read notes until we find one named 'Linux'.
|
||||
for {
|
||||
if err := binary.Read(sr, hdr.ByteOrder, &n); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return 0, fmt.Errorf("no Linux note in ELF")
|
||||
}
|
||||
return 0, fmt.Errorf("reading note header: %w", err)
|
||||
}
|
||||
|
||||
// If a note name is defined, it follows the note header.
|
||||
var name string
|
||||
if n.NameSize > 0 {
|
||||
// Read the note name, aligned to 4 bytes.
|
||||
buf := make([]byte, Align(int(n.NameSize), 4))
|
||||
if err := binary.Read(sr, hdr.ByteOrder, &buf); err != nil {
|
||||
return 0, fmt.Errorf("reading note name: %w", err)
|
||||
}
|
||||
|
||||
// Read nul-terminated string.
|
||||
name = unix.ByteSliceToString(buf[:n.NameSize])
|
||||
}
|
||||
|
||||
// If a note descriptor is defined, it follows the name.
|
||||
// It is possible for a note to have a descriptor but not a name.
|
||||
if n.DescSize > 0 {
|
||||
// LINUX_VERSION_CODE is a uint32 value.
|
||||
if name == "Linux" && n.DescSize == 4 && n.Type == 0 {
|
||||
var version uint32
|
||||
if err := binary.Read(sr, hdr.ByteOrder, &version); err != nil {
|
||||
return 0, fmt.Errorf("reading note descriptor: %w", err)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// Discard the note descriptor if it exists but we're not interested in it.
|
||||
if _, err := io.CopyN(io.Discard, sr, int64(Align(int(n.DescSize), 4))); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Version constant used in ELF binaries indicating that the loader needs to
|
||||
// substitute the eBPF program's version with the value of the kernel's
|
||||
// KERNEL_VERSION compile-time macro. Used for compatibility with BCC, gobpf
|
||||
// and RedSift.
|
||||
MagicKernelVersion = 0xFFFFFFFE
|
||||
)
|
||||
|
||||
var (
|
||||
kernelVersion = struct {
|
||||
once sync.Once
|
||||
version Version
|
||||
err error
|
||||
}{}
|
||||
)
|
||||
|
||||
// A Version in the form Major.Minor.Patch.
|
||||
type Version [3]uint16
|
||||
|
||||
// NewVersion creates a version from a string like "Major.Minor.Patch".
|
||||
//
|
||||
// Patch is optional.
|
||||
func NewVersion(ver string) (Version, error) {
|
||||
var major, minor, patch uint16
|
||||
n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
|
||||
if n < 2 {
|
||||
return Version{}, fmt.Errorf("invalid version: %s", ver)
|
||||
}
|
||||
return Version{major, minor, patch}, nil
|
||||
}
|
||||
|
||||
// NewVersionFromCode creates a version from a LINUX_VERSION_CODE.
|
||||
func NewVersionFromCode(code uint32) Version {
|
||||
return Version{
|
||||
uint16(uint8(code >> 16)),
|
||||
uint16(uint8(code >> 8)),
|
||||
uint16(uint8(code)),
|
||||
}
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v[2] == 0 {
|
||||
return fmt.Sprintf("v%d.%d", v[0], v[1])
|
||||
}
|
||||
return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2])
|
||||
}
|
||||
|
||||
// Less returns true if the version is less than another version.
|
||||
func (v Version) Less(other Version) bool {
|
||||
for i, a := range v {
|
||||
if a == other[i] {
|
||||
continue
|
||||
}
|
||||
return a < other[i]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Unspecified returns true if the version is all zero.
|
||||
func (v Version) Unspecified() bool {
|
||||
return v[0] == 0 && v[1] == 0 && v[2] == 0
|
||||
}
|
||||
|
||||
// Kernel implements the kernel's KERNEL_VERSION macro from linux/version.h.
|
||||
// It represents the kernel version and patch level as a single value.
|
||||
func (v Version) Kernel() uint32 {
|
||||
|
||||
// Kernels 4.4 and 4.9 have their SUBLEVEL clamped to 255 to avoid
|
||||
// overflowing into PATCHLEVEL.
|
||||
// See kernel commit 9b82f13e7ef3 ("kbuild: clamp SUBLEVEL to 255").
|
||||
s := v[2]
|
||||
if s > 255 {
|
||||
s = 255
|
||||
}
|
||||
|
||||
// Truncate members to uint8 to prevent them from spilling over into
|
||||
// each other when overflowing 8 bits.
|
||||
return uint32(uint8(v[0]))<<16 | uint32(uint8(v[1]))<<8 | uint32(uint8(s))
|
||||
}
|
||||
|
||||
// KernelVersion returns the version of the currently running kernel.
|
||||
func KernelVersion() (Version, error) {
|
||||
kernelVersion.once.Do(func() {
|
||||
kernelVersion.version, kernelVersion.err = detectKernelVersion()
|
||||
})
|
||||
|
||||
if kernelVersion.err != nil {
|
||||
return Version{}, kernelVersion.err
|
||||
}
|
||||
return kernelVersion.version, nil
|
||||
}
|
||||
|
||||
// detectKernelVersion returns the version of the running kernel.
|
||||
func detectKernelVersion() (Version, error) {
|
||||
vc, err := vdsoVersion()
|
||||
if err != nil {
|
||||
return Version{}, err
|
||||
}
|
||||
return NewVersionFromCode(vc), nil
|
||||
}
|
||||
Reference in New Issue
Block a user