Skip to content

Commit

Permalink
btf: store ExtInfos in instruction metadata
Browse files Browse the repository at this point in the history
Use per-instruction metadata to store func info, line info and CO-RE relocations.
As a result, simply concatenating two Instruction slices preserves the ext_info
without extra code to keep track of offsets. The simplest way to achieve is to
assign ext_infos per section, before we split into individual functions. This
also avoids having to split ext_infos in the first place.

Storing ext_infos in metadata in turn allows / requires removing code that relies
on stable offsets, the most notable being applying COREFixups. Instead of tracking
which offset a fixup should be applied to we change coreRelocate to instead return
results in the same order as CORERelocations are passed. In the caller we remember
which instruction originated the relocation and apply the fixup directly instead of
iterating all instructions once more.

Instead of storing ExtInfos in Spec we split it off into a separate exported type.
This makes more sense since every BTF ELF has a Spec, but ExtInfos are optional. It
turns out that only the ELF loader doesn't care about ExtInfos in the first place,
so we allow skipping ExtInfos altogether by introducing LoadSpecAndExtInfosFromReader.

Finally we can remove btf.Program since we don't need an intermediate type to hold
metadata anymore.

Fixes cilium#522
  • Loading branch information
lmb committed May 4, 2022
1 parent a4c5ff3 commit 4bd5f30
Show file tree
Hide file tree
Showing 11 changed files with 349 additions and 479 deletions.
2 changes: 1 addition & 1 deletion collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ func (cl *collectionLoader) loadProgram(progName string) (*Program, error) {
return nil, fmt.Errorf("cannot load program %s: program type is unspecified", progName)
}

if progSpec.BTF != nil && cl.coll.Types != progSpec.BTF.Spec() {
if progSpec.BTF != nil && cl.coll.Types != progSpec.BTF {
return nil, fmt.Errorf("program %s: BTF doesn't match collection", progName)
}

Expand Down
16 changes: 8 additions & 8 deletions elf_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type elfCode struct {
license string
version uint32
btf *btf.Spec
extInfo *btf.ExtInfos
}

// LoadCollectionSpec parses an ELF file into a CollectionSpec.
Expand Down Expand Up @@ -94,7 +95,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
return nil, fmt.Errorf("load version: %w", err)
}

btfSpec, err := btf.LoadSpecFromReader(rd)
btfSpec, btfExtInfo, err := btf.LoadSpecAndExtInfosFromReader(rd)
if err != nil && !errors.Is(err, btf.ErrNotFound) {
return nil, fmt.Errorf("load BTF: %w", err)
}
Expand All @@ -105,6 +106,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
license: license,
version: version,
btf: btfSpec,
extInfo: btfExtInfo,
}

symbols, err := f.Symbols()
Expand Down Expand Up @@ -309,13 +311,7 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) {
KernelVersion: ec.version,
Instructions: insns,
ByteOrder: ec.ByteOrder,
}

if ec.btf != nil {
spec.BTF, err = ec.btf.Program(name)
if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) {
return nil, fmt.Errorf("program %s: %w", name, err)
}
BTF: ec.btf,
}

// Function names must be unique within a single ELF blob.
Expand Down Expand Up @@ -383,6 +379,10 @@ func (ec *elfCode) loadFunctions(section *elfSection) (map[string]asm.Instructio
}
}

if ec.extInfo != nil {
ec.extInfo.Assign(insns, section.Name)
}

return splitSymbols(insns)
}

Expand Down
2 changes: 1 addition & 1 deletion elf_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func TestLoadCollectionSpec(t *testing.T) {
}
return false
}),
cmpopts.IgnoreTypes(new(btf.Map), new(btf.Program)),
cmpopts.IgnoreTypes(new(btf.Map), new(btf.Spec)),
cmpopts.IgnoreFields(CollectionSpec{}, "ByteOrder", "Types"),
cmpopts.IgnoreFields(ProgramSpec{}, "Instructions", "ByteOrder"),
cmpopts.IgnoreUnexported(ProgramSpec{}),
Expand Down
191 changes: 30 additions & 161 deletions internal/btf/btf.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"math"
"os"
"reflect"
"sort"
"sync"

"github.com/cilium/ebpf/internal"
Expand Down Expand Up @@ -44,11 +43,6 @@ type Spec struct {
// Includes all struct flavors and types with the same name.
namedTypes map[essentialName][]Type

// Data from .BTF.ext. indexed by function name.
funcInfos map[string]FuncInfo
lineInfos map[string]LineInfos
coreRelos map[string]CORERelos

byteOrder binary.ByteOrder
}

Expand Down Expand Up @@ -78,14 +72,16 @@ func (h *btfHeader) stringStart() int64 {

// LoadSpecFromReader reads from an ELF or a raw BTF blob.
//
// Returns ErrNotFound if reading from an ELF which contains no BTF.
// Returns ErrNotFound if reading from an ELF which contains no BTF. ExtInfos
// may be nil.
func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
file, err := internal.NewSafeELFFile(rd)
if err != nil {
if bo := guessRawBTFByteOrder(rd); bo != nil {
// Try to parse a naked BTF blob. This will return an error if
// we encounter a Datasec, since we can't fix it up.
return loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil, nil)
spec, err := loadRawSpec(io.NewSectionReader(rd, 0, math.MaxInt64), bo, nil, nil)
return spec, err
}

return nil, err
Expand All @@ -94,6 +90,29 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
return loadSpecFromELF(file)
}

// LoadSpecAndExtInfosFromReader reads from an ELF.
//
// ExtInfos may be nil if the ELF doesn't contain section metadta.
// Returns ErrNotFound if the ELF contains no BTF.
func LoadSpecAndExtInfosFromReader(rd io.ReaderAt) (*Spec, *ExtInfos, error) {
file, err := internal.NewSafeELFFile(rd)
if err != nil {
return nil, nil, err
}

spec, err := loadSpecFromELF(file)
if err != nil {
return nil, nil, err
}

extInfos, err := loadExtInfosFromELF(file, spec.types, spec.strings)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, nil, err
}

return spec, extInfos, nil
}

// variableOffsets extracts all symbols offsets from an ELF and indexes them by
// section and variable name.
//
Expand Down Expand Up @@ -132,17 +151,14 @@ func variableOffsets(file *internal.SafeELFFile) (map[variable]uint32, error) {

func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
var (
btfSection *elf.Section
btfExtSection *elf.Section
sectionSizes = make(map[string]uint32)
btfSection *elf.Section
sectionSizes = make(map[string]uint32)
)

for _, sec := range file.Sections {
switch sec.Name {
case ".BTF":
btfSection = sec
case ".BTF.ext":
btfExtSection = sec
default:
if sec.Type != elf.SHT_PROGBITS && sec.Type != elf.SHT_NOBITS {
break
Expand All @@ -169,118 +185,7 @@ func loadSpecFromELF(file *internal.SafeELFFile) (*Spec, error) {
return nil, fmt.Errorf("compressed BTF is not supported")
}

spec, err := loadRawSpec(btfSection.ReaderAt, file.ByteOrder, sectionSizes, vars)
if err != nil {
return nil, err
}

if btfExtSection == nil {
return spec, nil
}

if btfExtSection.ReaderAt == nil {
return nil, fmt.Errorf("compressed ext_info is not supported")
}

extInfo, err := loadExtInfos(btfExtSection, file.ByteOrder, spec.strings)
if err != nil {
return nil, fmt.Errorf("can't parse ext info: %w", err)
}

if err := spec.splitExtInfos(extInfo); err != nil {
return nil, fmt.Errorf("linking funcInfos and lineInfos: %w", err)
}

return spec, nil
}

// splitExtInfos takes FuncInfos, LineInfos and CORERelos indexed by section and
// transforms them to be indexed by function. Retrieves function names from
// the BTF spec.
func (spec *Spec) splitExtInfos(info *extInfo) error {
ofi := make(map[string]FuncInfo)
oli := make(map[string]LineInfos)
ocr := make(map[string]CORERelos)

for secName, secFuncInfos := range info.funcInfos {
// Collect functions from each section and organize them by name.
var funcs []*Func
for _, bfi := range secFuncInfos {
fi, err := newFuncInfo(bfi, spec.types)
if err != nil {
return err
}

ofi[fi.fn.Name] = *fi
funcs = append(funcs, fi.fn)
}

sort.Slice(secFuncInfos, func(i, j int) bool {
return secFuncInfos[i].InsnOff < secFuncInfos[j].InsnOff
})

// Consider an ELF section that contains 3 functions (a, b, c)
// at offsets 0, 10 and 15 respectively. Offset 5 will return function a,
// offset 12 will return b, offset >= 15 will return c, etc.
funcForInstruction := func(offset uint32) (fn *Func, fnOffset uint32) {
for i, fi := range secFuncInfos {
if fi.InsnOff > offset {
break
}
fn = funcs[i]
fnOffset = fi.InsnOff
}
return fn, fnOffset
}

// Attribute LineInfo records to their respective functions, if any.
if lines := info.lineInfos[secName]; lines != nil {
for _, bli := range lines {
fn, fnOffset := funcForInstruction(bli.InsnOff)
if fn == nil {
return fmt.Errorf("section %s: error looking up FuncInfo for offset %v", secName, bli.InsnOff)
}

li, err := newLineInfo(bli, spec.strings)
if err != nil {
return err
}

// Offsets are ELF section-scoped, make them function-scoped by
// subtracting the function's start offset.
li.insnOff -= fnOffset

oli[fn.Name] = append(oli[fn.Name], *li)
}
}

// Attribute CO-RE relocations to their respective functions, if any.
if relos := info.relos[secName]; relos != nil {
for _, r := range relos {
fn, fnOffset := funcForInstruction(r.InsnOff)
if fn == nil {
return fmt.Errorf("section %s: error looking up FuncInfo for offset %v", secName, r.InsnOff)
}

relo, err := newCORERelocation(r, spec.types, spec.strings)
if err != nil {
return err
}

// Offsets are ELF section-scoped, make them function-scoped by
// subtracting the function's start offset.
relo.insnOff -= fnOffset

ocr[fn.Name] = append(ocr[fn.Name], *relo)
}
}
}

spec.funcInfos = ofi
spec.lineInfos = oli
spec.coreRelos = ocr

return nil
return loadRawSpec(btfSection.ReaderAt, file.ByteOrder, sectionSizes, vars)
}

func loadRawSpec(btf io.ReaderAt, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
Expand Down Expand Up @@ -522,9 +427,6 @@ func (s *Spec) Copy() *Spec {
s.strings,
types,
namedTypes,
s.funcInfos,
s.lineInfos,
s.coreRelos,
s.byteOrder,
}
}
Expand Down Expand Up @@ -597,26 +499,6 @@ func (sw sliceWriter) Write(p []byte) (int, error) {
return copy(sw, p), nil
}

// Program finds the BTF for a specific function.
//
// Returns an error which may wrap ErrNoExtendedInfo if the Spec doesn't
// contain extended BTF info.
func (s *Spec) Program(name string) (*Program, error) {
if s.funcInfos == nil && s.lineInfos == nil && s.coreRelos == nil {
return nil, fmt.Errorf("BTF for function %s: %w", name, ErrNoExtendedInfo)
}

funcInfo, funcOK := s.funcInfos[name]
lineInfos, lineOK := s.lineInfos[name]
relos, coreOK := s.coreRelos[name]

if !funcOK && !lineOK && !coreOK {
return nil, fmt.Errorf("no extended BTF info for function %s", name)
}

return &Program{s, funcInfo, lineInfos, relos}, nil
}

// TypeByID returns the BTF Type with the given type ID.
//
// Returns an error wrapping ErrNotFound if a Type with the given ID
Expand Down Expand Up @@ -813,19 +695,6 @@ type Map struct {
Key, Value Type
}

// Program is the BTF information for a stream of instructions.
type Program struct {
spec *Spec
FuncInfo FuncInfo
LineInfos LineInfos
CORERelos CORERelos
}

// Spec returns the BTF spec of this program.
func (p *Program) Spec() *Spec {
return p.spec
}

func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
const minHeaderLength = 24

Expand Down
8 changes: 0 additions & 8 deletions internal/btf/btf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,6 @@ func TestLoadSpecFromElf(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "../../testdata/loader-e*.elf"), func(t *testing.T, file string) {
spec := parseELFBTF(t, file)

if prog, err := spec.Program("xdp_prog"); err != nil || prog == nil {
t.Error("Can't get BTF for program xdp_prog:", err)
}

if prog, err := spec.Program("no_relocation"); err != nil || prog == nil {
t.Error("Can't get BTF for program no_relocation:", err)
}

vt, err := spec.TypeByID(0)
if err != nil {
t.Error("Can't retrieve void type by ID:", err)
Expand Down
Loading

0 comments on commit 4bd5f30

Please sign in to comment.