mirror of
https://codeberg.org/superseriousbusiness/gotosocial.git
synced 2024-12-26 02:48:16 +03:00
931 lines
26 KiB
Go
931 lines
26 KiB
Go
|
package ebpf
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"debug/elf"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"math"
|
||
|
"os"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/cilium/ebpf/asm"
|
||
|
"github.com/cilium/ebpf/internal"
|
||
|
"github.com/cilium/ebpf/internal/btf"
|
||
|
"github.com/cilium/ebpf/internal/unix"
|
||
|
)
|
||
|
|
||
|
// elfCode is a convenience to reduce the amount of arguments that have to
|
||
|
// be passed around explicitly. You should treat it's contents as immutable.
|
||
|
type elfCode struct {
|
||
|
*internal.SafeELFFile
|
||
|
sections map[elf.SectionIndex]*elfSection
|
||
|
license string
|
||
|
version uint32
|
||
|
btf *btf.Spec
|
||
|
}
|
||
|
|
||
|
// LoadCollectionSpec parses an ELF file into a CollectionSpec.
|
||
|
func LoadCollectionSpec(file string) (*CollectionSpec, error) {
|
||
|
f, err := os.Open(file)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
|
||
|
spec, err := LoadCollectionSpecFromReader(f)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("file %s: %w", file, err)
|
||
|
}
|
||
|
return spec, nil
|
||
|
}
|
||
|
|
||
|
// LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec.
|
||
|
func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
|
||
|
f, err := internal.NewSafeELFFile(rd)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
|
||
|
var (
|
||
|
licenseSection *elf.Section
|
||
|
versionSection *elf.Section
|
||
|
sections = make(map[elf.SectionIndex]*elfSection)
|
||
|
relSections = make(map[elf.SectionIndex]*elf.Section)
|
||
|
)
|
||
|
|
||
|
// This is the target of relocations generated by inline assembly.
|
||
|
sections[elf.SHN_UNDEF] = newElfSection(new(elf.Section), undefSection)
|
||
|
|
||
|
// Collect all the sections we're interested in. This includes relocations
|
||
|
// which we parse later.
|
||
|
for i, sec := range f.Sections {
|
||
|
idx := elf.SectionIndex(i)
|
||
|
|
||
|
switch {
|
||
|
case strings.HasPrefix(sec.Name, "license"):
|
||
|
licenseSection = sec
|
||
|
case strings.HasPrefix(sec.Name, "version"):
|
||
|
versionSection = sec
|
||
|
case strings.HasPrefix(sec.Name, "maps"):
|
||
|
sections[idx] = newElfSection(sec, mapSection)
|
||
|
case sec.Name == ".maps":
|
||
|
sections[idx] = newElfSection(sec, btfMapSection)
|
||
|
case sec.Name == ".bss" || sec.Name == ".data" || strings.HasPrefix(sec.Name, ".rodata"):
|
||
|
sections[idx] = newElfSection(sec, dataSection)
|
||
|
case sec.Type == elf.SHT_REL:
|
||
|
// Store relocations under the section index of the target
|
||
|
relSections[elf.SectionIndex(sec.Info)] = sec
|
||
|
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
|
||
|
sections[idx] = newElfSection(sec, programSection)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
license, err := loadLicense(licenseSection)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("load license: %w", err)
|
||
|
}
|
||
|
|
||
|
version, err := loadVersion(versionSection, f.ByteOrder)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("load version: %w", err)
|
||
|
}
|
||
|
|
||
|
btfSpec, err := btf.LoadSpecFromReader(rd)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("load BTF: %w", err)
|
||
|
}
|
||
|
|
||
|
// Assign symbols to all the sections we're interested in.
|
||
|
symbols, err := f.Symbols()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("load symbols: %v", err)
|
||
|
}
|
||
|
|
||
|
for _, symbol := range symbols {
|
||
|
idx := symbol.Section
|
||
|
symType := elf.ST_TYPE(symbol.Info)
|
||
|
|
||
|
section := sections[idx]
|
||
|
if section == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Older versions of LLVM don't tag symbols correctly, so keep
|
||
|
// all NOTYPE ones.
|
||
|
keep := symType == elf.STT_NOTYPE
|
||
|
switch section.kind {
|
||
|
case mapSection, btfMapSection, dataSection:
|
||
|
keep = keep || symType == elf.STT_OBJECT
|
||
|
case programSection:
|
||
|
keep = keep || symType == elf.STT_FUNC
|
||
|
}
|
||
|
if !keep || symbol.Name == "" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
section.symbols[symbol.Value] = symbol
|
||
|
}
|
||
|
|
||
|
ec := &elfCode{
|
||
|
SafeELFFile: f,
|
||
|
sections: sections,
|
||
|
license: license,
|
||
|
version: version,
|
||
|
btf: btfSpec,
|
||
|
}
|
||
|
|
||
|
// Go through relocation sections, and parse the ones for sections we're
|
||
|
// interested in. Make sure that relocations point at valid sections.
|
||
|
for idx, relSection := range relSections {
|
||
|
section := sections[idx]
|
||
|
if section == nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
rels, err := ec.loadRelocations(relSection, symbols)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("relocation for section %q: %w", section.Name, err)
|
||
|
}
|
||
|
|
||
|
for _, rel := range rels {
|
||
|
target := sections[rel.Section]
|
||
|
if target == nil {
|
||
|
return nil, fmt.Errorf("section %q: reference to %q in section %s: %w", section.Name, rel.Name, rel.Section, ErrNotSupported)
|
||
|
}
|
||
|
|
||
|
if target.Flags&elf.SHF_STRINGS > 0 {
|
||
|
return nil, fmt.Errorf("section %q: string %q is not stack allocated: %w", section.Name, rel.Name, ErrNotSupported)
|
||
|
}
|
||
|
|
||
|
target.references++
|
||
|
}
|
||
|
|
||
|
section.relocations = rels
|
||
|
}
|
||
|
|
||
|
// Collect all the various ways to define maps.
|
||
|
maps := make(map[string]*MapSpec)
|
||
|
if err := ec.loadMaps(maps); err != nil {
|
||
|
return nil, fmt.Errorf("load maps: %w", err)
|
||
|
}
|
||
|
|
||
|
if err := ec.loadBTFMaps(maps); err != nil {
|
||
|
return nil, fmt.Errorf("load BTF maps: %w", err)
|
||
|
}
|
||
|
|
||
|
if err := ec.loadDataSections(maps); err != nil {
|
||
|
return nil, fmt.Errorf("load data sections: %w", err)
|
||
|
}
|
||
|
|
||
|
// Finally, collect programs and link them.
|
||
|
progs, err := ec.loadPrograms()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("load programs: %w", err)
|
||
|
}
|
||
|
|
||
|
return &CollectionSpec{maps, progs}, nil
|
||
|
}
|
||
|
|
||
|
func loadLicense(sec *elf.Section) (string, error) {
|
||
|
if sec == nil {
|
||
|
return "", nil
|
||
|
}
|
||
|
|
||
|
data, err := sec.Data()
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("section %s: %v", sec.Name, err)
|
||
|
}
|
||
|
return string(bytes.TrimRight(data, "\000")), nil
|
||
|
}
|
||
|
|
||
|
func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) {
|
||
|
if sec == nil {
|
||
|
return 0, nil
|
||
|
}
|
||
|
|
||
|
var version uint32
|
||
|
if err := binary.Read(sec.Open(), bo, &version); err != nil {
|
||
|
return 0, fmt.Errorf("section %s: %v", sec.Name, err)
|
||
|
}
|
||
|
return version, nil
|
||
|
}
|
||
|
|
||
|
type elfSectionKind int
|
||
|
|
||
|
const (
|
||
|
undefSection elfSectionKind = iota
|
||
|
mapSection
|
||
|
btfMapSection
|
||
|
programSection
|
||
|
dataSection
|
||
|
)
|
||
|
|
||
|
type elfSection struct {
|
||
|
*elf.Section
|
||
|
kind elfSectionKind
|
||
|
// Offset from the start of the section to a symbol
|
||
|
symbols map[uint64]elf.Symbol
|
||
|
// Offset from the start of the section to a relocation, which points at
|
||
|
// a symbol in another section.
|
||
|
relocations map[uint64]elf.Symbol
|
||
|
// The number of relocations pointing at this section.
|
||
|
references int
|
||
|
}
|
||
|
|
||
|
func newElfSection(section *elf.Section, kind elfSectionKind) *elfSection {
|
||
|
return &elfSection{
|
||
|
section,
|
||
|
kind,
|
||
|
make(map[uint64]elf.Symbol),
|
||
|
make(map[uint64]elf.Symbol),
|
||
|
0,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ec *elfCode) loadPrograms() (map[string]*ProgramSpec, error) {
|
||
|
var (
|
||
|
progs []*ProgramSpec
|
||
|
libs []*ProgramSpec
|
||
|
)
|
||
|
|
||
|
for _, sec := range ec.sections {
|
||
|
if sec.kind != programSection {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if len(sec.symbols) == 0 {
|
||
|
return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
|
||
|
}
|
||
|
|
||
|
funcSym, ok := sec.symbols[0]
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("section %v: no label at start", sec.Name)
|
||
|
}
|
||
|
|
||
|
insns, length, err := ec.loadInstructions(sec)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("program %s: %w", funcSym.Name, err)
|
||
|
}
|
||
|
|
||
|
progType, attachType, attachTo := getProgType(sec.Name)
|
||
|
|
||
|
spec := &ProgramSpec{
|
||
|
Name: funcSym.Name,
|
||
|
Type: progType,
|
||
|
AttachType: attachType,
|
||
|
AttachTo: attachTo,
|
||
|
License: ec.license,
|
||
|
KernelVersion: ec.version,
|
||
|
Instructions: insns,
|
||
|
ByteOrder: ec.ByteOrder,
|
||
|
}
|
||
|
|
||
|
if ec.btf != nil {
|
||
|
spec.BTF, err = ec.btf.Program(sec.Name, length)
|
||
|
if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) {
|
||
|
return nil, fmt.Errorf("program %s: %w", funcSym.Name, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if spec.Type == UnspecifiedProgram {
|
||
|
// There is no single name we can use for "library" sections,
|
||
|
// since they may contain multiple functions. We'll decode the
|
||
|
// labels they contain later on, and then link sections that way.
|
||
|
libs = append(libs, spec)
|
||
|
} else {
|
||
|
progs = append(progs, spec)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
res := make(map[string]*ProgramSpec, len(progs))
|
||
|
for _, prog := range progs {
|
||
|
err := link(prog, libs)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("program %s: %w", prog.Name, err)
|
||
|
}
|
||
|
res[prog.Name] = prog
|
||
|
}
|
||
|
|
||
|
return res, nil
|
||
|
}
|
||
|
|
||
|
func (ec *elfCode) loadInstructions(section *elfSection) (asm.Instructions, uint64, error) {
|
||
|
var (
|
||
|
r = bufio.NewReader(section.Open())
|
||
|
insns asm.Instructions
|
||
|
offset uint64
|
||
|
)
|
||
|
for {
|
||
|
var ins asm.Instruction
|
||
|
n, err := ins.Unmarshal(r, ec.ByteOrder)
|
||
|
if err == io.EOF {
|
||
|
return insns, offset, nil
|
||
|
}
|
||
|
if err != nil {
|
||
|
return nil, 0, fmt.Errorf("offset %d: %w", offset, err)
|
||
|
}
|
||
|
|
||
|
ins.Symbol = section.symbols[offset].Name
|
||
|
|
||
|
if rel, ok := section.relocations[offset]; ok {
|
||
|
if err = ec.relocateInstruction(&ins, rel); err != nil {
|
||
|
return nil, 0, fmt.Errorf("offset %d: relocate instruction: %w", offset, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
insns = append(insns, ins)
|
||
|
offset += n
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) error {
|
||
|
var (
|
||
|
typ = elf.ST_TYPE(rel.Info)
|
||
|
bind = elf.ST_BIND(rel.Info)
|
||
|
name = rel.Name
|
||
|
)
|
||
|
|
||
|
target := ec.sections[rel.Section]
|
||
|
|
||
|
switch target.kind {
|
||
|
case mapSection, btfMapSection:
|
||
|
if bind != elf.STB_GLOBAL {
|
||
|
return fmt.Errorf("possible erroneous static qualifier on map definition: found reference to %q", name)
|
||
|
}
|
||
|
|
||
|
if typ != elf.STT_OBJECT && typ != elf.STT_NOTYPE {
|
||
|
// STT_NOTYPE is generated on clang < 8 which doesn't tag
|
||
|
// relocations appropriately.
|
||
|
return fmt.Errorf("map load: incorrect relocation type %v", typ)
|
||
|
}
|
||
|
|
||
|
ins.Src = asm.PseudoMapFD
|
||
|
|
||
|
// Mark the instruction as needing an update when creating the
|
||
|
// collection.
|
||
|
if err := ins.RewriteMapPtr(-1); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
case dataSection:
|
||
|
switch typ {
|
||
|
case elf.STT_SECTION:
|
||
|
if bind != elf.STB_LOCAL {
|
||
|
return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind)
|
||
|
}
|
||
|
|
||
|
case elf.STT_OBJECT:
|
||
|
if bind != elf.STB_GLOBAL {
|
||
|
return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind)
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return fmt.Errorf("incorrect relocation type %v for direct map load", typ)
|
||
|
}
|
||
|
|
||
|
// We rely on using the name of the data section as the reference. It
|
||
|
// would be nicer to keep the real name in case of an STT_OBJECT, but
|
||
|
// it's not clear how to encode that into Instruction.
|
||
|
name = target.Name
|
||
|
|
||
|
// For some reason, clang encodes the offset of the symbol its
|
||
|
// section in the first basic BPF instruction, while the kernel
|
||
|
// expects it in the second one.
|
||
|
ins.Constant <<= 32
|
||
|
ins.Src = asm.PseudoMapValue
|
||
|
|
||
|
// Mark the instruction as needing an update when creating the
|
||
|
// collection.
|
||
|
if err := ins.RewriteMapPtr(-1); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
case programSection:
|
||
|
if ins.OpCode.JumpOp() != asm.Call {
|
||
|
return fmt.Errorf("not a call instruction: %s", ins)
|
||
|
}
|
||
|
|
||
|
if ins.Src != asm.PseudoCall {
|
||
|
return fmt.Errorf("call: %s: incorrect source register", name)
|
||
|
}
|
||
|
|
||
|
switch typ {
|
||
|
case elf.STT_NOTYPE, elf.STT_FUNC:
|
||
|
if bind != elf.STB_GLOBAL {
|
||
|
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
|
||
|
}
|
||
|
|
||
|
case elf.STT_SECTION:
|
||
|
if bind != elf.STB_LOCAL {
|
||
|
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
|
||
|
}
|
||
|
|
||
|
// The function we want to call is in the indicated section,
|
||
|
// at the offset encoded in the instruction itself. Reverse
|
||
|
// the calculation to find the real function we're looking for.
|
||
|
// A value of -1 references the first instruction in the section.
|
||
|
offset := int64(int32(ins.Constant)+1) * asm.InstructionSize
|
||
|
if offset < 0 {
|
||
|
return fmt.Errorf("call: %s: invalid offset %d", name, offset)
|
||
|
}
|
||
|
|
||
|
sym, ok := target.symbols[uint64(offset)]
|
||
|
if !ok {
|
||
|
return fmt.Errorf("call: %s: no symbol at offset %d", name, offset)
|
||
|
}
|
||
|
|
||
|
ins.Constant = -1
|
||
|
name = sym.Name
|
||
|
|
||
|
default:
|
||
|
return fmt.Errorf("call: %s: invalid symbol type %s", name, typ)
|
||
|
}
|
||
|
|
||
|
case undefSection:
|
||
|
if bind != elf.STB_GLOBAL {
|
||
|
return fmt.Errorf("asm relocation: %s: unsupported binding: %s", name, bind)
|
||
|
}
|
||
|
|
||
|
if typ != elf.STT_NOTYPE {
|
||
|
return fmt.Errorf("asm relocation: %s: unsupported type %s", name, typ)
|
||
|
}
|
||
|
|
||
|
// There is nothing to do here but set ins.Reference.
|
||
|
|
||
|
default:
|
||
|
return fmt.Errorf("relocation to %q: %w", target.Name, ErrNotSupported)
|
||
|
}
|
||
|
|
||
|
ins.Reference = name
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (ec *elfCode) loadMaps(maps map[string]*MapSpec) error {
|
||
|
for _, sec := range ec.sections {
|
||
|
if sec.kind != mapSection {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
nSym := len(sec.symbols)
|
||
|
if nSym == 0 {
|
||
|
return fmt.Errorf("section %v: no symbols", sec.Name)
|
||
|
}
|
||
|
|
||
|
if sec.Size%uint64(nSym) != 0 {
|
||
|
return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name)
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
r = bufio.NewReader(sec.Open())
|
||
|
size = sec.Size / uint64(nSym)
|
||
|
)
|
||
|
for i, offset := 0, uint64(0); i < nSym; i, offset = i+1, offset+size {
|
||
|
mapSym, ok := sec.symbols[offset]
|
||
|
if !ok {
|
||
|
return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
|
||
|
}
|
||
|
|
||
|
if maps[mapSym.Name] != nil {
|
||
|
return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym)
|
||
|
}
|
||
|
|
||
|
lr := io.LimitReader(r, int64(size))
|
||
|
|
||
|
spec := MapSpec{
|
||
|
Name: SanitizeName(mapSym.Name, -1),
|
||
|
}
|
||
|
switch {
|
||
|
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
|
||
|
return fmt.Errorf("map %v: missing type", mapSym)
|
||
|
case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
|
||
|
return fmt.Errorf("map %v: missing key size", mapSym)
|
||
|
case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
|
||
|
return fmt.Errorf("map %v: missing value size", mapSym)
|
||
|
case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
|
||
|
return fmt.Errorf("map %v: missing max entries", mapSym)
|
||
|
case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
|
||
|
return fmt.Errorf("map %v: missing flags", mapSym)
|
||
|
}
|
||
|
|
||
|
if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil {
|
||
|
return fmt.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
|
||
|
}
|
||
|
|
||
|
maps[mapSym.Name] = &spec
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec) error {
|
||
|
for _, sec := range ec.sections {
|
||
|
if sec.kind != btfMapSection {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if ec.btf == nil {
|
||
|
return fmt.Errorf("missing BTF")
|
||
|
}
|
||
|
|
||
|
if len(sec.symbols) == 0 {
|
||
|
return fmt.Errorf("section %v: no symbols", sec.Name)
|
||
|
}
|
||
|
|
||
|
_, err := io.Copy(internal.DiscardZeroes{}, bufio.NewReader(sec.Open()))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("section %v: initializing BTF map definitions: %w", sec.Name, internal.ErrNotSupported)
|
||
|
}
|
||
|
|
||
|
for _, sym := range sec.symbols {
|
||
|
name := sym.Name
|
||
|
if maps[name] != nil {
|
||
|
return fmt.Errorf("section %v: map %v already exists", sec.Name, sym)
|
||
|
}
|
||
|
|
||
|
// A global Var is created by declaring a struct with a 'structure variable',
|
||
|
// as is common in eBPF C to declare eBPF maps. For example,
|
||
|
// `struct { ... } map_name ...;` emits a global variable `map_name`
|
||
|
// with the type of said struct (which can be anonymous).
|
||
|
var v btf.Var
|
||
|
if err := ec.btf.FindType(name, &v); err != nil {
|
||
|
return fmt.Errorf("cannot find global variable '%s' in BTF: %w", name, err)
|
||
|
}
|
||
|
|
||
|
mapStruct, ok := v.Type.(*btf.Struct)
|
||
|
if !ok {
|
||
|
return fmt.Errorf("expected struct, got %s", v.Type)
|
||
|
}
|
||
|
|
||
|
mapSpec, err := mapSpecFromBTF(name, mapStruct, false, ec.btf)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("map %v: %w", name, err)
|
||
|
}
|
||
|
|
||
|
maps[name] = mapSpec
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// mapSpecFromBTF produces a MapSpec based on a btf.Struct def representing
|
||
|
// a BTF map definition. The name and spec arguments will be copied to the
|
||
|
// resulting MapSpec, and inner must be true on any resursive invocations.
|
||
|
func mapSpecFromBTF(name string, def *btf.Struct, inner bool, spec *btf.Spec) (*MapSpec, error) {
|
||
|
|
||
|
var (
|
||
|
key, value btf.Type
|
||
|
keySize, valueSize uint32
|
||
|
mapType, flags, maxEntries uint32
|
||
|
pinType PinType
|
||
|
innerMapSpec *MapSpec
|
||
|
err error
|
||
|
)
|
||
|
|
||
|
for i, member := range def.Members {
|
||
|
switch member.Name {
|
||
|
case "type":
|
||
|
mapType, err = uintFromBTF(member.Type)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't get type: %w", err)
|
||
|
}
|
||
|
|
||
|
case "map_flags":
|
||
|
flags, err = uintFromBTF(member.Type)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't get BTF map flags: %w", err)
|
||
|
}
|
||
|
|
||
|
case "max_entries":
|
||
|
maxEntries, err = uintFromBTF(member.Type)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't get BTF map max entries: %w", err)
|
||
|
}
|
||
|
|
||
|
case "key":
|
||
|
if keySize != 0 {
|
||
|
return nil, errors.New("both key and key_size given")
|
||
|
}
|
||
|
|
||
|
pk, ok := member.Type.(*btf.Pointer)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("key type is not a pointer: %T", member.Type)
|
||
|
}
|
||
|
|
||
|
key = pk.Target
|
||
|
|
||
|
size, err := btf.Sizeof(pk.Target)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't get size of BTF key: %w", err)
|
||
|
}
|
||
|
|
||
|
keySize = uint32(size)
|
||
|
|
||
|
case "value":
|
||
|
if valueSize != 0 {
|
||
|
return nil, errors.New("both value and value_size given")
|
||
|
}
|
||
|
|
||
|
vk, ok := member.Type.(*btf.Pointer)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("value type is not a pointer: %T", member.Type)
|
||
|
}
|
||
|
|
||
|
value = vk.Target
|
||
|
|
||
|
size, err := btf.Sizeof(vk.Target)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't get size of BTF value: %w", err)
|
||
|
}
|
||
|
|
||
|
valueSize = uint32(size)
|
||
|
|
||
|
case "key_size":
|
||
|
// Key needs to be nil and keySize needs to be 0 for key_size to be
|
||
|
// considered a valid member.
|
||
|
if key != nil || keySize != 0 {
|
||
|
return nil, errors.New("both key and key_size given")
|
||
|
}
|
||
|
|
||
|
keySize, err = uintFromBTF(member.Type)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't get BTF key size: %w", err)
|
||
|
}
|
||
|
|
||
|
case "value_size":
|
||
|
// Value needs to be nil and valueSize needs to be 0 for value_size to be
|
||
|
// considered a valid member.
|
||
|
if value != nil || valueSize != 0 {
|
||
|
return nil, errors.New("both value and value_size given")
|
||
|
}
|
||
|
|
||
|
valueSize, err = uintFromBTF(member.Type)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't get BTF value size: %w", err)
|
||
|
}
|
||
|
|
||
|
case "pinning":
|
||
|
if inner {
|
||
|
return nil, errors.New("inner maps can't be pinned")
|
||
|
}
|
||
|
|
||
|
pinning, err := uintFromBTF(member.Type)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't get pinning: %w", err)
|
||
|
}
|
||
|
|
||
|
pinType = PinType(pinning)
|
||
|
|
||
|
case "values":
|
||
|
// The 'values' field in BTF map definitions is used for declaring map
|
||
|
// value types that are references to other BPF objects, like other maps
|
||
|
// or programs. It is always expected to be an array of pointers.
|
||
|
if i != len(def.Members)-1 {
|
||
|
return nil, errors.New("'values' must be the last member in a BTF map definition")
|
||
|
}
|
||
|
|
||
|
if valueSize != 0 && valueSize != 4 {
|
||
|
return nil, errors.New("value_size must be 0 or 4")
|
||
|
}
|
||
|
valueSize = 4
|
||
|
|
||
|
valueType, err := resolveBTFArrayMacro(member.Type)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't resolve type of member 'values': %w", err)
|
||
|
}
|
||
|
|
||
|
switch t := valueType.(type) {
|
||
|
case *btf.Struct:
|
||
|
// The values member pointing to an array of structs means we're expecting
|
||
|
// a map-in-map declaration.
|
||
|
if MapType(mapType) != ArrayOfMaps && MapType(mapType) != HashOfMaps {
|
||
|
return nil, errors.New("outer map needs to be an array or a hash of maps")
|
||
|
}
|
||
|
if inner {
|
||
|
return nil, fmt.Errorf("nested inner maps are not supported")
|
||
|
}
|
||
|
|
||
|
// This inner map spec is used as a map template, but it needs to be
|
||
|
// created as a traditional map before it can be used to do so.
|
||
|
// libbpf names the inner map template '<outer_name>.inner', but we
|
||
|
// opted for _inner to simplify validation logic. (dots only supported
|
||
|
// on kernels 5.2 and up)
|
||
|
// Pass the BTF spec from the parent object, since both parent and
|
||
|
// child must be created from the same BTF blob (on kernels that support BTF).
|
||
|
innerMapSpec, err = mapSpecFromBTF(name+"_inner", t, true, spec)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("can't parse BTF map definition of inner map: %w", err)
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return nil, fmt.Errorf("unsupported value type %q in 'values' field", t)
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bm := btf.NewMap(spec, key, value)
|
||
|
|
||
|
return &MapSpec{
|
||
|
Name: SanitizeName(name, -1),
|
||
|
Type: MapType(mapType),
|
||
|
KeySize: keySize,
|
||
|
ValueSize: valueSize,
|
||
|
MaxEntries: maxEntries,
|
||
|
Flags: flags,
|
||
|
BTF: &bm,
|
||
|
Pinning: pinType,
|
||
|
InnerMap: innerMapSpec,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// uintFromBTF resolves the __uint macro, which is a pointer to a sized
|
||
|
// array, e.g. for int (*foo)[10], this function will return 10.
|
||
|
func uintFromBTF(typ btf.Type) (uint32, error) {
|
||
|
ptr, ok := typ.(*btf.Pointer)
|
||
|
if !ok {
|
||
|
return 0, fmt.Errorf("not a pointer: %v", typ)
|
||
|
}
|
||
|
|
||
|
arr, ok := ptr.Target.(*btf.Array)
|
||
|
if !ok {
|
||
|
return 0, fmt.Errorf("not a pointer to array: %v", typ)
|
||
|
}
|
||
|
|
||
|
return arr.Nelems, nil
|
||
|
}
|
||
|
|
||
|
// resolveBTFArrayMacro resolves the __array macro, which declares an array
|
||
|
// of pointers to a given type. This function returns the target Type of
|
||
|
// the pointers in the array.
|
||
|
func resolveBTFArrayMacro(typ btf.Type) (btf.Type, error) {
|
||
|
arr, ok := typ.(*btf.Array)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("not an array: %v", typ)
|
||
|
}
|
||
|
|
||
|
ptr, ok := arr.Type.(*btf.Pointer)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("not an array of pointers: %v", typ)
|
||
|
}
|
||
|
|
||
|
return ptr.Target, nil
|
||
|
}
|
||
|
|
||
|
func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error {
|
||
|
for _, sec := range ec.sections {
|
||
|
if sec.kind != dataSection {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if sec.references == 0 {
|
||
|
// Prune data sections which are not referenced by any
|
||
|
// instructions.
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if ec.btf == nil {
|
||
|
return errors.New("data sections require BTF, make sure all consts are marked as static")
|
||
|
}
|
||
|
|
||
|
btfMap, err := ec.btf.Datasec(sec.Name)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
data, err := sec.Data()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
|
||
|
}
|
||
|
|
||
|
if uint64(len(data)) > math.MaxUint32 {
|
||
|
return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
|
||
|
}
|
||
|
|
||
|
mapSpec := &MapSpec{
|
||
|
Name: SanitizeName(sec.Name, -1),
|
||
|
Type: Array,
|
||
|
KeySize: 4,
|
||
|
ValueSize: uint32(len(data)),
|
||
|
MaxEntries: 1,
|
||
|
Contents: []MapKV{{uint32(0), data}},
|
||
|
BTF: btfMap,
|
||
|
}
|
||
|
|
||
|
switch sec.Name {
|
||
|
case ".rodata":
|
||
|
mapSpec.Flags = unix.BPF_F_RDONLY_PROG
|
||
|
mapSpec.Freeze = true
|
||
|
case ".bss":
|
||
|
// The kernel already zero-initializes the map
|
||
|
mapSpec.Contents = nil
|
||
|
}
|
||
|
|
||
|
maps[sec.Name] = mapSpec
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func getProgType(sectionName string) (ProgramType, AttachType, string) {
|
||
|
types := map[string]struct {
|
||
|
progType ProgramType
|
||
|
attachType AttachType
|
||
|
}{
|
||
|
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c
|
||
|
"socket": {SocketFilter, AttachNone},
|
||
|
"seccomp": {SocketFilter, AttachNone},
|
||
|
"kprobe/": {Kprobe, AttachNone},
|
||
|
"uprobe/": {Kprobe, AttachNone},
|
||
|
"kretprobe/": {Kprobe, AttachNone},
|
||
|
"uretprobe/": {Kprobe, AttachNone},
|
||
|
"tracepoint/": {TracePoint, AttachNone},
|
||
|
"raw_tracepoint/": {RawTracepoint, AttachNone},
|
||
|
"xdp": {XDP, AttachNone},
|
||
|
"perf_event": {PerfEvent, AttachNone},
|
||
|
"lwt_in": {LWTIn, AttachNone},
|
||
|
"lwt_out": {LWTOut, AttachNone},
|
||
|
"lwt_xmit": {LWTXmit, AttachNone},
|
||
|
"lwt_seg6local": {LWTSeg6Local, AttachNone},
|
||
|
"sockops": {SockOps, AttachCGroupSockOps},
|
||
|
"sk_skb/stream_parser": {SkSKB, AttachSkSKBStreamParser},
|
||
|
"sk_skb/stream_verdict": {SkSKB, AttachSkSKBStreamParser},
|
||
|
"sk_msg": {SkMsg, AttachSkSKBStreamVerdict},
|
||
|
"lirc_mode2": {LircMode2, AttachLircMode2},
|
||
|
"flow_dissector": {FlowDissector, AttachFlowDissector},
|
||
|
"iter/": {Tracing, AttachTraceIter},
|
||
|
"sk_lookup/": {SkLookup, AttachSkLookup},
|
||
|
"lsm/": {LSM, AttachLSMMac},
|
||
|
|
||
|
"cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress},
|
||
|
"cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress},
|
||
|
"cgroup/dev": {CGroupDevice, AttachCGroupDevice},
|
||
|
"cgroup/skb": {CGroupSKB, AttachNone},
|
||
|
"cgroup/sock": {CGroupSock, AttachCGroupInetSockCreate},
|
||
|
"cgroup/post_bind4": {CGroupSock, AttachCGroupInet4PostBind},
|
||
|
"cgroup/post_bind6": {CGroupSock, AttachCGroupInet6PostBind},
|
||
|
"cgroup/bind4": {CGroupSockAddr, AttachCGroupInet4Bind},
|
||
|
"cgroup/bind6": {CGroupSockAddr, AttachCGroupInet6Bind},
|
||
|
"cgroup/connect4": {CGroupSockAddr, AttachCGroupInet4Connect},
|
||
|
"cgroup/connect6": {CGroupSockAddr, AttachCGroupInet6Connect},
|
||
|
"cgroup/sendmsg4": {CGroupSockAddr, AttachCGroupUDP4Sendmsg},
|
||
|
"cgroup/sendmsg6": {CGroupSockAddr, AttachCGroupUDP6Sendmsg},
|
||
|
"cgroup/recvmsg4": {CGroupSockAddr, AttachCGroupUDP4Recvmsg},
|
||
|
"cgroup/recvmsg6": {CGroupSockAddr, AttachCGroupUDP6Recvmsg},
|
||
|
"cgroup/sysctl": {CGroupSysctl, AttachCGroupSysctl},
|
||
|
"cgroup/getsockopt": {CGroupSockopt, AttachCGroupGetsockopt},
|
||
|
"cgroup/setsockopt": {CGroupSockopt, AttachCGroupSetsockopt},
|
||
|
"classifier": {SchedCLS, AttachNone},
|
||
|
"action": {SchedACT, AttachNone},
|
||
|
}
|
||
|
|
||
|
for prefix, t := range types {
|
||
|
if !strings.HasPrefix(sectionName, prefix) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if !strings.HasSuffix(prefix, "/") {
|
||
|
return t.progType, t.attachType, ""
|
||
|
}
|
||
|
|
||
|
return t.progType, t.attachType, sectionName[len(prefix):]
|
||
|
}
|
||
|
|
||
|
return UnspecifiedProgram, AttachNone, ""
|
||
|
}
|
||
|
|
||
|
func (ec *elfCode) loadRelocations(sec *elf.Section, symbols []elf.Symbol) (map[uint64]elf.Symbol, error) {
|
||
|
rels := make(map[uint64]elf.Symbol)
|
||
|
|
||
|
if sec.Entsize < 16 {
|
||
|
return nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name)
|
||
|
}
|
||
|
|
||
|
r := bufio.NewReader(sec.Open())
|
||
|
for off := uint64(0); off < sec.Size; off += sec.Entsize {
|
||
|
ent := io.LimitReader(r, int64(sec.Entsize))
|
||
|
|
||
|
var rel elf.Rel64
|
||
|
if binary.Read(ent, ec.ByteOrder, &rel) != nil {
|
||
|
return nil, fmt.Errorf("can't parse relocation at offset %v", off)
|
||
|
}
|
||
|
|
||
|
symNo := int(elf.R_SYM64(rel.Info) - 1)
|
||
|
if symNo >= len(symbols) {
|
||
|
return nil, fmt.Errorf("offset %d: symbol %d doesn't exist", off, symNo)
|
||
|
}
|
||
|
|
||
|
symbol := symbols[symNo]
|
||
|
rels[rel.Off] = symbol
|
||
|
}
|
||
|
|
||
|
return rels, nil
|
||
|
}
|