123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197 |
- package ebpf
- import (
- "bufio"
- "bytes"
- "debug/elf"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "math"
- "os"
- "strings"
- "github.com/cilium/ebpf/asm"
- "github.com/cilium/ebpf/btf"
- "github.com/cilium/ebpf/internal"
- "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 its contents as immutable.
- type elfCode struct {
- *internal.SafeELFFile
- sections map[elf.SectionIndex]*elfSection
- license string
- version uint32
- btf *btf.Spec
- extInfo *btf.ExtInfos
- }
- // 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
- }
- 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, btfExtInfo, err := btf.LoadSpecAndExtInfosFromReader(rd)
- if err != nil && !errors.Is(err, btf.ErrNotFound) {
- return nil, fmt.Errorf("load BTF: %w", err)
- }
- ec := &elfCode{
- SafeELFFile: f,
- sections: sections,
- license: license,
- version: version,
- btf: btfSpec,
- extInfo: btfExtInfo,
- }
- symbols, err := f.Symbols()
- if err != nil {
- return nil, fmt.Errorf("load symbols: %v", err)
- }
- ec.assignSymbols(symbols)
- if err := ec.loadRelocations(relSections, symbols); err != nil {
- return nil, fmt.Errorf("load relocations: %w", err)
- }
- // 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.loadProgramSections()
- if err != nil {
- return nil, fmt.Errorf("load programs: %w", err)
- }
- return &CollectionSpec{maps, progs, btfSpec, ec.ByteOrder}, 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,
- }
- }
- // assignSymbols takes a list of symbols and assigns them to their
- // respective sections, indexed by name.
- func (ec *elfCode) assignSymbols(symbols []elf.Symbol) {
- for _, symbol := range symbols {
- symType := elf.ST_TYPE(symbol.Info)
- symSection := ec.sections[symbol.Section]
- if symSection == nil {
- continue
- }
- // Anonymous symbols only occur in debug sections which we don't process
- // relocations for. Anonymous symbols are not referenced from other sections.
- if symbol.Name == "" {
- continue
- }
- // Older versions of LLVM don't tag symbols correctly, so keep
- // all NOTYPE ones.
- switch symSection.kind {
- case mapSection, btfMapSection, dataSection:
- if symType != elf.STT_NOTYPE && symType != elf.STT_OBJECT {
- continue
- }
- case programSection:
- if symType != elf.STT_NOTYPE && symType != elf.STT_FUNC {
- continue
- }
- // LLVM emits LBB_ (Local Basic Block) symbols that seem to be jump
- // targets within sections, but BPF has no use for them.
- if symType == elf.STT_NOTYPE && elf.ST_BIND(symbol.Info) == elf.STB_LOCAL &&
- strings.HasPrefix(symbol.Name, "LBB") {
- continue
- }
- // Only collect symbols that occur in program/maps/data sections.
- default:
- continue
- }
- symSection.symbols[symbol.Value] = symbol
- }
- }
- // loadRelocations iterates .rel* sections and extracts relocation entries for
- // sections of interest. Makes sure relocations point at valid sections.
- func (ec *elfCode) loadRelocations(relSections map[elf.SectionIndex]*elf.Section, symbols []elf.Symbol) error {
- for idx, relSection := range relSections {
- section := ec.sections[idx]
- if section == nil {
- continue
- }
- rels, err := ec.loadSectionRelocations(relSection, symbols)
- if err != nil {
- return fmt.Errorf("relocation for section %q: %w", section.Name, err)
- }
- for _, rel := range rels {
- target := ec.sections[rel.Section]
- if target == nil {
- return 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 fmt.Errorf("section %q: string is not stack allocated: %w", section.Name, ErrNotSupported)
- }
- target.references++
- }
- section.relocations = rels
- }
- return nil
- }
- // loadProgramSections iterates ec's sections and emits a ProgramSpec
- // for each function it finds.
- //
- // The resulting map is indexed by function name.
- func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) {
- progs := make(map[string]*ProgramSpec)
- // Generate a ProgramSpec for each function found in each program section.
- var export []string
- 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)
- }
- funcs, err := ec.loadFunctions(sec)
- if err != nil {
- return nil, fmt.Errorf("section %v: %w", sec.Name, err)
- }
- progType, attachType, progFlags, attachTo := getProgType(sec.Name)
- for name, insns := range funcs {
- spec := &ProgramSpec{
- Name: name,
- Type: progType,
- Flags: progFlags,
- AttachType: attachType,
- AttachTo: attachTo,
- SectionName: sec.Name,
- License: ec.license,
- KernelVersion: ec.version,
- Instructions: insns,
- ByteOrder: ec.ByteOrder,
- BTF: ec.btf,
- }
- // Function names must be unique within a single ELF blob.
- if progs[name] != nil {
- return nil, fmt.Errorf("duplicate program name %s", name)
- }
- progs[name] = spec
- if spec.SectionName != ".text" {
- export = append(export, name)
- }
- }
- }
- flattenPrograms(progs, export)
- // Hide programs (e.g. library functions) that were not explicitly emitted
- // to an ELF section. These could be exposed in a separate CollectionSpec
- // field later to allow them to be modified.
- for n, p := range progs {
- if p.SectionName == ".text" {
- delete(progs, n)
- }
- }
- return progs, nil
- }
- // loadFunctions extracts instruction streams from the given program section
- // starting at each symbol in the section. The section's symbols must already
- // be narrowed down to STT_NOTYPE (emitted by clang <8) or STT_FUNC.
- //
- // The resulting map is indexed by function name.
- func (ec *elfCode) loadFunctions(section *elfSection) (map[string]asm.Instructions, error) {
- r := bufio.NewReader(section.Open())
- // Decode the section's instruction stream.
- var insns asm.Instructions
- if err := insns.Unmarshal(r, ec.ByteOrder); err != nil {
- return nil, fmt.Errorf("decoding instructions for section %s: %w", section.Name, err)
- }
- if len(insns) == 0 {
- return nil, fmt.Errorf("no instructions found in section %s", section.Name)
- }
- iter := insns.Iterate()
- for iter.Next() {
- ins := iter.Ins
- offset := iter.Offset.Bytes()
- // Tag Symbol Instructions.
- if sym, ok := section.symbols[offset]; ok {
- *ins = ins.WithSymbol(sym.Name)
- }
- // Apply any relocations for the current instruction.
- // If no relocation is present, resolve any section-relative function calls.
- if rel, ok := section.relocations[offset]; ok {
- if err := ec.relocateInstruction(ins, rel); err != nil {
- return nil, fmt.Errorf("offset %d: relocating instruction: %w", offset, err)
- }
- } else {
- if err := referenceRelativeJump(ins, offset, section.symbols); err != nil {
- return nil, fmt.Errorf("offset %d: resolving relative jump: %w", offset, err)
- }
- }
- }
- if ec.extInfo != nil {
- ec.extInfo.Assign(insns, section.Name)
- }
- return splitSymbols(insns)
- }
- // referenceRelativeJump turns a relative jump to another bpf subprogram within
- // the same ELF section into a Reference Instruction.
- //
- // Up to LLVM 9, calls to subprograms within the same ELF section are sometimes
- // encoded using relative jumps instead of relocation entries. These jumps go
- // out of bounds of the current program, so their targets must be memoized
- // before the section's instruction stream is split.
- //
- // The relative jump Constant is blinded to -1 and the target Symbol is set as
- // the Instruction's Reference so it can be resolved by the linker.
- func referenceRelativeJump(ins *asm.Instruction, offset uint64, symbols map[uint64]elf.Symbol) error {
- if !ins.IsFunctionReference() || ins.Constant == -1 {
- return nil
- }
- tgt := jumpTarget(offset, *ins)
- sym := symbols[tgt].Name
- if sym == "" {
- return fmt.Errorf("no jump target found at offset %d", tgt)
- }
- *ins = ins.WithReference(sym)
- ins.Constant = -1
- return nil
- }
- // jumpTarget takes ins' offset within an instruction stream (in bytes)
- // and returns its absolute jump destination (in bytes) within the
- // instruction stream.
- func jumpTarget(offset uint64, ins asm.Instruction) uint64 {
- // A relative jump instruction describes the amount of raw BPF instructions
- // to jump, convert the offset into bytes.
- dest := ins.Constant * asm.InstructionSize
- // The starting point of the jump is the end of the current instruction.
- dest += int64(offset + asm.InstructionSize)
- if dest < 0 {
- return 0
- }
- return uint64(dest)
- }
- 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
- case dataSection:
- var offset uint32
- switch typ {
- case elf.STT_SECTION:
- if bind != elf.STB_LOCAL {
- return fmt.Errorf("direct load: %s: unsupported section relocation %s", name, bind)
- }
- // This is really a reference to a static symbol, which clang doesn't
- // emit a symbol table entry for. Instead it encodes the offset in
- // the instruction itself.
- offset = uint32(uint64(ins.Constant))
- case elf.STT_OBJECT:
- // LLVM 9 emits OBJECT-LOCAL symbols for anonymous constants.
- if bind != elf.STB_GLOBAL && bind != elf.STB_LOCAL {
- return fmt.Errorf("direct load: %s: unsupported object relocation %s", name, bind)
- }
- offset = uint32(rel.Value)
- case elf.STT_NOTYPE:
- // LLVM 7 emits NOTYPE-LOCAL symbols for anonymous constants.
- if bind != elf.STB_LOCAL {
- return fmt.Errorf("direct load: %s: unsupported untyped relocation %s", name, bind)
- }
- offset = uint32(rel.Value)
- 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
- // The kernel expects the offset in the second basic BPF instruction.
- ins.Constant = int64(uint64(offset) << 32)
- ins.Src = asm.PseudoMapValue
- case programSection:
- switch opCode := ins.OpCode; {
- case opCode.JumpOp() == asm.Call:
- 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
- sym, ok := target.symbols[uint64(offset)]
- if !ok {
- return fmt.Errorf("call: no symbol at offset %d", offset)
- }
- name = sym.Name
- ins.Constant = -1
- default:
- return fmt.Errorf("call: %s: invalid symbol type %s", name, typ)
- }
- case opCode.IsDWordLoad():
- switch typ {
- case elf.STT_FUNC:
- if bind != elf.STB_GLOBAL {
- return fmt.Errorf("load: %s: unsupported binding: %s", name, bind)
- }
- case elf.STT_SECTION:
- if bind != elf.STB_LOCAL {
- return fmt.Errorf("load: %s: unsupported binding: %s", name, bind)
- }
- // ins.Constant already contains the offset in bytes from the
- // start of the section. This is different than a call to a
- // static function.
- default:
- return fmt.Errorf("load: %s: invalid symbol type %s", name, typ)
- }
- sym, ok := target.symbols[uint64(ins.Constant)]
- if !ok {
- return fmt.Errorf("load: no symbol at offset %d", ins.Constant)
- }
- name = sym.Name
- ins.Constant = -1
- ins.Src = asm.PseudoFunc
- default:
- return fmt.Errorf("neither a call nor a load instruction: %v", ins)
- }
- 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 = ins.WithReference(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)
- }
- mapName := mapSym.Name
- if maps[mapName] != nil {
- return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym)
- }
- lr := io.LimitReader(r, int64(size))
- spec := MapSpec{
- Name: SanitizeName(mapName, -1),
- }
- switch {
- case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
- return fmt.Errorf("map %s: missing type", mapName)
- case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
- return fmt.Errorf("map %s: missing key size", mapName)
- case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
- return fmt.Errorf("map %s: missing value size", mapName)
- case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
- return fmt.Errorf("map %s: missing max entries", mapName)
- case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
- return fmt.Errorf("map %s: missing flags", mapName)
- }
- extra, err := io.ReadAll(lr)
- if err != nil {
- return fmt.Errorf("map %s: reading map tail: %w", mapName, err)
- }
- if len(extra) > 0 {
- spec.Extra = bytes.NewReader(extra)
- }
- if err := spec.clampPerfEventArraySize(); err != nil {
- return fmt.Errorf("map %s: %w", mapName, err)
- }
- maps[mapName] = &spec
- }
- }
- return nil
- }
- // loadBTFMaps iterates over all ELF sections marked as BTF map sections
- // (like .maps) and parses them into MapSpecs. Dump the .maps section and
- // any relocations with `readelf -x .maps -r <elf_file>`.
- 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")
- }
- // Each section must appear as a DataSec in the ELF's BTF blob.
- var ds *btf.Datasec
- if err := ec.btf.TypeByName(sec.Name, &ds); err != nil {
- return fmt.Errorf("cannot find section '%s' in BTF: %w", sec.Name, err)
- }
- // Open a Reader to the ELF's raw section bytes so we can assert that all
- // of them are zero on a per-map (per-Var) basis. For now, the section's
- // sole purpose is to receive relocations, so all must be zero.
- rs := sec.Open()
- for _, vs := range ds.Vars {
- // BPF maps are declared as and assigned to global variables,
- // so iterate over each Var in the DataSec and validate their types.
- v, ok := vs.Type.(*btf.Var)
- if !ok {
- return fmt.Errorf("section %v: unexpected type %s", sec.Name, vs.Type)
- }
- name := string(v.Name)
- // The BTF metadata for each Var contains the full length of the map
- // declaration, so read the corresponding amount of bytes from the ELF.
- // This way, we can pinpoint which map declaration contains unexpected
- // (and therefore unsupported) data.
- _, err := io.Copy(internal.DiscardZeroes{}, io.LimitReader(rs, int64(vs.Size)))
- if err != nil {
- return fmt.Errorf("section %v: map %s: initializing BTF map definitions: %w", sec.Name, name, internal.ErrNotSupported)
- }
- if maps[name] != nil {
- return fmt.Errorf("section %v: map %s already exists", sec.Name, name)
- }
- // Each Var representing a BTF map definition contains a Struct.
- mapStruct, ok := v.Type.(*btf.Struct)
- if !ok {
- return fmt.Errorf("expected struct, got %s", v.Type)
- }
- mapSpec, err := mapSpecFromBTF(sec, &vs, mapStruct, ec.btf, name, false)
- if err != nil {
- return fmt.Errorf("map %v: %w", name, err)
- }
- if err := mapSpec.clampPerfEventArraySize(); err != nil {
- return fmt.Errorf("map %v: %w", name, err)
- }
- maps[name] = mapSpec
- }
- // Drain the ELF section reader to make sure all bytes are accounted for
- // with BTF metadata.
- i, err := io.Copy(io.Discard, rs)
- if err != nil {
- return fmt.Errorf("section %v: unexpected error reading remainder of ELF section: %w", sec.Name, err)
- }
- if i > 0 {
- return fmt.Errorf("section %v: %d unexpected remaining bytes in ELF section, invalid BTF?", sec.Name, i)
- }
- }
- 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(es *elfSection, vs *btf.VarSecinfo, def *btf.Struct, spec *btf.Spec, name string, inner bool) (*MapSpec, error) {
- var (
- key, value btf.Type
- keySize, valueSize uint32
- mapType MapType
- flags, maxEntries uint32
- pinType PinType
- innerMapSpec *MapSpec
- contents []MapKV
- err error
- )
- for i, member := range def.Members {
- switch member.Name {
- case "type":
- mt, err := uintFromBTF(member.Type)
- if err != nil {
- return nil, fmt.Errorf("can't get type: %w", err)
- }
- mapType = MapType(mt)
- 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 != ArrayOfMaps && 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(es, vs, t, spec, name+"_inner", true)
- if err != nil {
- return nil, fmt.Errorf("can't parse BTF map definition of inner map: %w", err)
- }
- case *btf.FuncProto:
- // The values member contains an array of function pointers, meaning an
- // autopopulated PROG_ARRAY.
- if mapType != ProgramArray {
- return nil, errors.New("map needs to be a program array")
- }
- default:
- return nil, fmt.Errorf("unsupported value type %q in 'values' field", t)
- }
- contents, err = resolveBTFValuesContents(es, vs, member)
- if err != nil {
- return nil, fmt.Errorf("resolving values contents: %w", err)
- }
- default:
- return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name)
- }
- }
- if key == nil {
- key = &btf.Void{}
- }
- if value == nil {
- value = &btf.Void{}
- }
- return &MapSpec{
- Name: SanitizeName(name, -1),
- Type: MapType(mapType),
- KeySize: keySize,
- ValueSize: valueSize,
- MaxEntries: maxEntries,
- Flags: flags,
- Key: key,
- Value: value,
- BTF: spec,
- Pinning: pinType,
- InnerMap: innerMapSpec,
- Contents: contents,
- }, 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
- }
- // resolveBTFValuesContents resolves relocations into ELF sections belonging
- // to btf.VarSecinfo's. This can be used on the 'values' member in BTF map
- // definitions to extract static declarations of map contents.
- func resolveBTFValuesContents(es *elfSection, vs *btf.VarSecinfo, member btf.Member) ([]MapKV, error) {
- // The elements of a .values pointer array are not encoded in BTF.
- // Instead, relocations are generated into each array index.
- // However, it's possible to leave certain array indices empty, so all
- // indices' offsets need to be checked for emitted relocations.
- // The offset of the 'values' member within the _struct_ (in bits)
- // is the starting point of the array. Convert to bytes. Add VarSecinfo
- // offset to get the absolute position in the ELF blob.
- start := member.Offset.Bytes() + vs.Offset
- // 'values' is encoded in BTF as a zero (variable) length struct
- // member, and its contents run until the end of the VarSecinfo.
- // Add VarSecinfo offset to get the absolute position in the ELF blob.
- end := vs.Size + vs.Offset
- // The size of an address in this section. This determines the width of
- // an index in the array.
- align := uint32(es.SectionHeader.Addralign)
- // Check if variable-length section is aligned.
- if (end-start)%align != 0 {
- return nil, errors.New("unaligned static values section")
- }
- elems := (end - start) / align
- if elems == 0 {
- return nil, nil
- }
- contents := make([]MapKV, 0, elems)
- // k is the array index, off is its corresponding ELF section offset.
- for k, off := uint32(0), start; k < elems; k, off = k+1, off+align {
- r, ok := es.relocations[uint64(off)]
- if !ok {
- continue
- }
- // Relocation exists for the current offset in the ELF section.
- // Emit a value stub based on the type of relocation to be replaced by
- // a real fd later in the pipeline before populating the map.
- // Map keys are encoded in MapKV entries, so empty array indices are
- // skipped here.
- switch t := elf.ST_TYPE(r.Info); t {
- case elf.STT_FUNC:
- contents = append(contents, MapKV{uint32(k), r.Name})
- case elf.STT_OBJECT:
- contents = append(contents, MapKV{uint32(k), r.Name})
- default:
- return nil, fmt.Errorf("unknown relocation type %v", t)
- }
- }
- return contents, 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
- }
- 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}},
- }
- // It is possible for a data section to exist without a corresponding BTF Datasec
- // if it only contains anonymous values like macro-defined arrays.
- if ec.btf != nil {
- var ds *btf.Datasec
- if ec.btf.TypeByName(sec.Name, &ds) == nil {
- // Assign the spec's key and BTF only if the Datasec lookup was successful.
- mapSpec.BTF = ec.btf
- mapSpec.Key = &btf.Void{}
- mapSpec.Value = ds
- }
- }
- switch n := sec.Name; {
- case strings.HasPrefix(n, ".rodata"):
- mapSpec.Flags = unix.BPF_F_RDONLY_PROG
- mapSpec.Freeze = true
- case n == ".bss":
- // The kernel already zero-initializes the map
- mapSpec.Contents = nil
- }
- maps[sec.Name] = mapSpec
- }
- return nil
- }
- func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) {
- types := []struct {
- prefix string
- progType ProgramType
- attachType AttachType
- progFlags uint32
- }{
- // Please update the types from libbpf.c and follow the order of it.
- // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c
- {"socket", SocketFilter, AttachNone, 0},
- {"sk_reuseport/migrate", SkReuseport, AttachSkReuseportSelectOrMigrate, 0},
- {"sk_reuseport", SkReuseport, AttachSkReuseportSelect, 0},
- {"kprobe/", Kprobe, AttachNone, 0},
- {"uprobe/", Kprobe, AttachNone, 0},
- {"kretprobe/", Kprobe, AttachNone, 0},
- {"uretprobe/", Kprobe, AttachNone, 0},
- {"tc", SchedCLS, AttachNone, 0},
- {"classifier", SchedCLS, AttachNone, 0},
- {"action", SchedACT, AttachNone, 0},
- {"tracepoint/", TracePoint, AttachNone, 0},
- {"tp/", TracePoint, AttachNone, 0},
- {"raw_tracepoint/", RawTracepoint, AttachNone, 0},
- {"raw_tp/", RawTracepoint, AttachNone, 0},
- {"raw_tracepoint.w/", RawTracepointWritable, AttachNone, 0},
- {"raw_tp.w/", RawTracepointWritable, AttachNone, 0},
- {"tp_btf/", Tracing, AttachTraceRawTp, 0},
- {"fentry/", Tracing, AttachTraceFEntry, 0},
- {"fmod_ret/", Tracing, AttachModifyReturn, 0},
- {"fexit/", Tracing, AttachTraceFExit, 0},
- {"fentry.s/", Tracing, AttachTraceFEntry, unix.BPF_F_SLEEPABLE},
- {"fmod_ret.s/", Tracing, AttachModifyReturn, unix.BPF_F_SLEEPABLE},
- {"fexit.s/", Tracing, AttachTraceFExit, unix.BPF_F_SLEEPABLE},
- {"freplace/", Extension, AttachNone, 0},
- {"lsm/", LSM, AttachLSMMac, 0},
- {"lsm.s/", LSM, AttachLSMMac, unix.BPF_F_SLEEPABLE},
- {"iter/", Tracing, AttachTraceIter, 0},
- {"syscall", Syscall, AttachNone, 0},
- {"xdp_devmap/", XDP, AttachXDPDevMap, 0},
- {"xdp_cpumap/", XDP, AttachXDPCPUMap, 0},
- {"xdp", XDP, AttachNone, 0},
- {"perf_event", PerfEvent, AttachNone, 0},
- {"lwt_in", LWTIn, AttachNone, 0},
- {"lwt_out", LWTOut, AttachNone, 0},
- {"lwt_xmit", LWTXmit, AttachNone, 0},
- {"lwt_seg6local", LWTSeg6Local, AttachNone, 0},
- {"cgroup_skb/ingress", CGroupSKB, AttachCGroupInetIngress, 0},
- {"cgroup_skb/egress", CGroupSKB, AttachCGroupInetEgress, 0},
- {"cgroup/skb", CGroupSKB, AttachNone, 0},
- {"cgroup/sock_create", CGroupSock, AttachCGroupInetSockCreate, 0},
- {"cgroup/sock_release", CGroupSock, AttachCgroupInetSockRelease, 0},
- {"cgroup/sock", CGroupSock, AttachCGroupInetSockCreate, 0},
- {"cgroup/post_bind4", CGroupSock, AttachCGroupInet4PostBind, 0},
- {"cgroup/post_bind6", CGroupSock, AttachCGroupInet6PostBind, 0},
- {"cgroup/dev", CGroupDevice, AttachCGroupDevice, 0},
- {"sockops", SockOps, AttachCGroupSockOps, 0},
- {"sk_skb/stream_parser", SkSKB, AttachSkSKBStreamParser, 0},
- {"sk_skb/stream_verdict", SkSKB, AttachSkSKBStreamVerdict, 0},
- {"sk_skb", SkSKB, AttachNone, 0},
- {"sk_msg", SkMsg, AttachSkMsgVerdict, 0},
- {"lirc_mode2", LircMode2, AttachLircMode2, 0},
- {"flow_dissector", FlowDissector, AttachFlowDissector, 0},
- {"cgroup/bind4", CGroupSockAddr, AttachCGroupInet4Bind, 0},
- {"cgroup/bind6", CGroupSockAddr, AttachCGroupInet6Bind, 0},
- {"cgroup/connect4", CGroupSockAddr, AttachCGroupInet4Connect, 0},
- {"cgroup/connect6", CGroupSockAddr, AttachCGroupInet6Connect, 0},
- {"cgroup/sendmsg4", CGroupSockAddr, AttachCGroupUDP4Sendmsg, 0},
- {"cgroup/sendmsg6", CGroupSockAddr, AttachCGroupUDP6Sendmsg, 0},
- {"cgroup/recvmsg4", CGroupSockAddr, AttachCGroupUDP4Recvmsg, 0},
- {"cgroup/recvmsg6", CGroupSockAddr, AttachCGroupUDP6Recvmsg, 0},
- {"cgroup/getpeername4", CGroupSockAddr, AttachCgroupInet4GetPeername, 0},
- {"cgroup/getpeername6", CGroupSockAddr, AttachCgroupInet6GetPeername, 0},
- {"cgroup/getsockname4", CGroupSockAddr, AttachCgroupInet4GetSockname, 0},
- {"cgroup/getsockname6", CGroupSockAddr, AttachCgroupInet6GetSockname, 0},
- {"cgroup/sysctl", CGroupSysctl, AttachCGroupSysctl, 0},
- {"cgroup/getsockopt", CGroupSockopt, AttachCGroupGetsockopt, 0},
- {"cgroup/setsockopt", CGroupSockopt, AttachCGroupSetsockopt, 0},
- {"struct_ops+", StructOps, AttachNone, 0},
- {"sk_lookup/", SkLookup, AttachSkLookup, 0},
- {"seccomp", SocketFilter, AttachNone, 0},
- }
- for _, t := range types {
- if !strings.HasPrefix(sectionName, t.prefix) {
- continue
- }
- if !strings.HasSuffix(t.prefix, "/") {
- return t.progType, t.attachType, t.progFlags, ""
- }
- return t.progType, t.attachType, t.progFlags, sectionName[len(t.prefix):]
- }
- return UnspecifiedProgram, AttachNone, 0, ""
- }
- func (ec *elfCode) loadSectionRelocations(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
- }
|