summaryrefslogtreecommitdiff
path: root/vendor/github.com/cilium/ebpf/elf_reader.go
diff options
context:
space:
mode:
authorLibravatar dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>2023-04-03 11:16:17 +0200
committerLibravatar GitHub <noreply@github.com>2023-04-03 11:16:17 +0200
commit57dc742c76d7876a2457594715a7b5bc2c9a92bd (patch)
tree76be1ec744face5bf4f617d4c9fca084707e4268 /vendor/github.com/cilium/ebpf/elf_reader.go
parent[bugfix/frontend] Preload css styles (#1638) (diff)
downloadgotosocial-57dc742c76d7876a2457594715a7b5bc2c9a92bd.tar.xz
[chore]: Bump github.com/KimMachineGun/automemlimit from 0.2.4 to 0.2.5 (#1666)
Bumps [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) from 0.2.4 to 0.2.5. - [Release notes](https://github.com/KimMachineGun/automemlimit/releases) - [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.2.4...v0.2.5) --- updated-dependencies: - dependency-name: github.com/KimMachineGun/automemlimit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Diffstat (limited to 'vendor/github.com/cilium/ebpf/elf_reader.go')
-rw-r--r--vendor/github.com/cilium/ebpf/elf_reader.go829
1 files changed, 548 insertions, 281 deletions
diff --git a/vendor/github.com/cilium/ebpf/elf_reader.go b/vendor/github.com/cilium/ebpf/elf_reader.go
index 943142c49..df278895c 100644
--- a/vendor/github.com/cilium/ebpf/elf_reader.go
+++ b/vendor/github.com/cilium/ebpf/elf_reader.go
@@ -13,19 +13,20 @@ import (
"strings"
"github.com/cilium/ebpf/asm"
+ "github.com/cilium/ebpf/btf"
"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.
+// 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.
@@ -49,7 +50,6 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
if err != nil {
return nil, err
}
- defer f.Close()
var (
licenseSection *elf.Section
@@ -95,77 +95,29 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
return nil, fmt.Errorf("load version: %w", err)
}
- btfSpec, err := btf.LoadSpecFromReader(rd)
- if err != nil {
+ btfSpec, btfExtInfo, err := btf.LoadSpecAndExtInfosFromReader(rd)
+ if err != nil && !errors.Is(err, btf.ErrNotFound) {
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,
+ extInfo: btfExtInfo,
}
- // 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)
- }
+ symbols, err := f.Symbols()
+ if err != nil {
+ return nil, fmt.Errorf("load symbols: %v", err)
+ }
- 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)
- }
+ ec.assignSymbols(symbols)
- target.references++
- }
-
- section.relocations = rels
+ if err := ec.loadRelocations(relSections, symbols); err != nil {
+ return nil, fmt.Errorf("load relocations: %w", err)
}
// Collect all the various ways to define maps.
@@ -183,12 +135,12 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
}
// Finally, collect programs and link them.
- progs, err := ec.loadPrograms()
+ progs, err := ec.loadProgramSections()
if err != nil {
return nil, fmt.Errorf("load programs: %w", err)
}
- return &CollectionSpec{maps, progs}, nil
+ return &CollectionSpec{maps, progs, btfSpec, ec.ByteOrder}, nil
}
func loadLicense(sec *elf.Section) (string, error) {
@@ -247,100 +199,238 @@ func newElfSection(section *elf.Section, kind elfSectionKind) *elfSection {
}
}
-func (ec *elfCode) loadPrograms() (map[string]*ProgramSpec, error) {
- var (
- progs []*ProgramSpec
- libs []*ProgramSpec
- )
+// 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
+ }
- for _, sec := range ec.sections {
- if sec.kind != programSection {
+ // 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
}
- if len(sec.symbols) == 0 {
- return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
+ // 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
}
- funcSym, ok := sec.symbols[0]
- if !ok {
- return nil, fmt.Errorf("section %v: no label at start", sec.Name)
+ 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
}
- insns, length, err := ec.loadInstructions(sec)
+ rels, err := ec.loadSectionRelocations(relSection, symbols)
if err != nil {
- return nil, fmt.Errorf("program %s: %w", funcSym.Name, err)
+ return fmt.Errorf("relocation for section %q: %w", section.Name, err)
}
- progType, attachType, attachTo := getProgType(sec.Name)
+ 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)
+ }
- spec := &ProgramSpec{
- Name: funcSym.Name,
- Type: progType,
- AttachType: attachType,
- AttachTo: attachTo,
- License: ec.license,
- KernelVersion: ec.version,
- Instructions: insns,
- ByteOrder: ec.ByteOrder,
+ target.references++
}
- 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)
- }
+ 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 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)
+ if len(sec.symbols) == 0 {
+ return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
}
- }
- res := make(map[string]*ProgramSpec, len(progs))
- for _, prog := range progs {
- err := link(prog, libs)
+ funcs, err := ec.loadFunctions(sec)
if err != nil {
- return nil, fmt.Errorf("program %s: %w", prog.Name, err)
+ return nil, fmt.Errorf("section %v: %w", sec.Name, err)
}
- res[prog.Name] = prog
- }
- return res, nil
-}
+ 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,
+ }
-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
+ // 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)
+ }
}
- if err != nil {
- return nil, 0, fmt.Errorf("offset %d: %w", offset, err)
+ }
+
+ 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
+}
- ins.Symbol = section.symbols[offset].Name
+// 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, 0, fmt.Errorf("offset %d: relocate instruction: %w", offset, err)
+ 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
+ }
- insns = append(insns, ins)
- offset += n
+ 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 {
@@ -366,24 +456,35 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
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:
+ var offset uint32
switch typ {
case elf.STT_SECTION:
if bind != elf.STB_LOCAL {
- return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind)
+ 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:
- if bind != elf.STB_GLOBAL {
- return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind)
+ // 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)
}
@@ -393,57 +494,75 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
// 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
+ // The kernel expects the offset in the second basic BPF instruction.
+ ins.Constant = int64(uint64(offset) << 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)
- }
+ switch opCode := ins.OpCode; {
+ case opCode.JumpOp() == asm.Call:
+ if ins.Src != asm.PseudoCall {
+ return fmt.Errorf("call: %s: incorrect source register", name)
+ }
- 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)
+ }
- 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)
+ }
- 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)
+ }
- // 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)
+ // 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(offset)]
+ sym, ok := target.symbols[uint64(ins.Constant)]
if !ok {
- return fmt.Errorf("call: %s: no symbol at offset %d", name, offset)
+ return fmt.Errorf("load: no symbol at offset %d", ins.Constant)
}
- ins.Constant = -1
name = sym.Name
+ ins.Constant = -1
+ ins.Src = asm.PseudoFunc
default:
- return fmt.Errorf("call: %s: invalid symbol type %s", name, typ)
+ return fmt.Errorf("neither a call nor a load instruction: %v", ins)
}
case undefSection:
@@ -461,7 +580,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
return fmt.Errorf("relocation to %q: %w", target.Name, ErrNotSupported)
}
- ins.Reference = name
+ *ins = ins.WithReference(name)
return nil
}
@@ -490,39 +609,51 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec) error {
return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
}
- if maps[mapSym.Name] != nil {
+ 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(mapSym.Name, -1),
+ Name: SanitizeName(mapName, -1),
}
switch {
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
- return fmt.Errorf("map %v: missing type", mapSym)
+ return fmt.Errorf("map %s: missing type", mapName)
case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
- return fmt.Errorf("map %v: missing key size", mapSym)
+ return fmt.Errorf("map %s: missing key size", mapName)
case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
- return fmt.Errorf("map %v: missing value size", mapSym)
+ return fmt.Errorf("map %s: missing value size", mapName)
case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
- return fmt.Errorf("map %v: missing max entries", mapSym)
+ return fmt.Errorf("map %s: missing max entries", mapName)
case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
- return fmt.Errorf("map %v: missing flags", mapSym)
+ 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 := io.Copy(internal.DiscardZeroes{}, lr); err != nil {
- return fmt.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
+ if err := spec.clampPerfEventArraySize(); err != nil {
+ return fmt.Errorf("map %s: %w", mapName, err)
}
- maps[mapSym.Name] = &spec
+ 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 {
@@ -533,42 +664,66 @@ func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec) error {
return fmt.Errorf("missing BTF")
}
- if len(sec.symbols) == 0 {
- return fmt.Errorf("section %v: no symbols", sec.Name)
+ // 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)
}
- _, 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)
- }
+ // 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 _, sym := range sec.symbols {
- name := sym.Name
- if maps[name] != nil {
- return fmt.Errorf("section %v: map %v already exists", sec.Name, sym)
+ 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)
- // 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)
+ // 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(name, mapStruct, false, ec.btf)
+ 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
@@ -577,24 +732,26 @@ func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec) error {
// 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) {
-
+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, flags, maxEntries uint32
- pinType PinType
- innerMapSpec *MapSpec
- err error
+ 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":
- mapType, err = uintFromBTF(member.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)
@@ -704,7 +861,7 @@ func mapSpecFromBTF(name string, def *btf.Struct, inner bool, spec *btf.Spec) (*
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 {
+ if mapType != ArrayOfMaps && mapType != HashOfMaps {
return nil, errors.New("outer map needs to be an array or a hash of maps")
}
if inner {
@@ -718,21 +875,38 @@ func mapSpecFromBTF(name string, def *btf.Struct, inner bool, spec *btf.Spec) (*
// 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)
+ 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)
}
}
- bm := btf.NewMap(spec, key, value)
+ if key == nil {
+ key = &btf.Void{}
+ }
+ if value == nil {
+ value = &btf.Void{}
+ }
return &MapSpec{
Name: SanitizeName(name, -1),
@@ -741,9 +915,12 @@ func mapSpecFromBTF(name string, def *btf.Struct, inner bool, spec *btf.Spec) (*
ValueSize: valueSize,
MaxEntries: maxEntries,
Flags: flags,
- BTF: &bm,
+ Key: key,
+ Value: value,
+ BTF: spec,
Pinning: pinType,
InnerMap: innerMapSpec,
+ Contents: contents,
}, nil
}
@@ -780,6 +957,64 @@ func resolveBTFArrayMacro(typ btf.Type) (btf.Type, error) {
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 {
@@ -792,15 +1027,6 @@ func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error {
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)
@@ -817,14 +1043,25 @@ func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error {
ValueSize: uint32(len(data)),
MaxEntries: 1,
Contents: []MapKV{{uint32(0), data}},
- BTF: btfMap,
}
- switch sec.Name {
- case ".rodata":
+ // 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 ".bss":
+ case n == ".bss":
// The kernel already zero-initializes the map
mapSpec.Contents = nil
}
@@ -834,74 +1071,104 @@ func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error {
return nil
}
-func getProgType(sectionName string) (ProgramType, AttachType, string) {
- types := map[string]struct {
+func getProgType(sectionName string) (ProgramType, AttachType, uint32, string) {
+ types := []struct {
+ prefix string
progType ProgramType
attachType AttachType
+ progFlags uint32
}{
- // 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) {
+ // 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(prefix, "/") {
- return t.progType, t.attachType, ""
+ if !strings.HasSuffix(t.prefix, "/") {
+ return t.progType, t.attachType, t.progFlags, ""
}
- return t.progType, t.attachType, sectionName[len(prefix):]
+ return t.progType, t.attachType, t.progFlags, sectionName[len(t.prefix):]
}
- return UnspecifiedProgram, AttachNone, ""
+ return UnspecifiedProgram, AttachNone, 0, ""
}
-func (ec *elfCode) loadRelocations(sec *elf.Section, symbols []elf.Symbol) (map[uint64]elf.Symbol, error) {
+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 {