Skip to content

Commit

Permalink
Revert "btf: avoid copies of vmlinux BTF for internal callers"
Browse files Browse the repository at this point in the history
This reverts commit c3a89fc. I've come
to the conclusion that a separate package isn't the right approach.
Parsing vmlinux is still too expensive and will stay this way as long
as we unmarshal the entire type graph when parsing BTF.

I think that the real solution will involve breaking API once more,
probably in the next release. I'm backing out the change to avoid
this churn. The downside is that kfunc, CO-RE and attaching programs
like fentry get slower again. Since they were already slow in the
last release I'm hoping that this doesn't cause too many problems.

Signed-off-by: Lorenz Bauer <lmb@isovalent.com>
  • Loading branch information
lmb authored and ti-mo committed Jul 6, 2023
1 parent 4267cbc commit 624a8b7
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 179 deletions.
102 changes: 102 additions & 0 deletions btf/btf.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"math"
"os"
"reflect"
"sync"

"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/sys"
Expand Down Expand Up @@ -297,6 +298,107 @@ func indexTypes(types []Type, firstTypeID TypeID) (map[Type]TypeID, map[essentia
return typeIDs, typesByName
}

// LoadKernelSpec returns the current kernel's BTF information.
//
// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file system
// for vmlinux ELFs. Returns an error wrapping ErrNotSupported if BTF is not enabled.
func LoadKernelSpec() (*Spec, error) {
spec, _, err := kernelSpec()
if err != nil {
return nil, err
}
return spec.Copy(), nil
}

var kernelBTF struct {
sync.RWMutex
spec *Spec
// True if the spec was read from an ELF instead of raw BTF in /sys.
fallback bool
}

// FlushKernelSpec removes any cached kernel type information.
func FlushKernelSpec() {
kernelBTF.Lock()
defer kernelBTF.Unlock()

kernelBTF.spec, kernelBTF.fallback = nil, false
}

func kernelSpec() (*Spec, bool, error) {
kernelBTF.RLock()
spec, fallback := kernelBTF.spec, kernelBTF.fallback
kernelBTF.RUnlock()

if spec == nil {
kernelBTF.Lock()
defer kernelBTF.Unlock()

spec, fallback = kernelBTF.spec, kernelBTF.fallback
}

if spec != nil {
return spec, fallback, nil
}

spec, fallback, err := loadKernelSpec()
if err != nil {
return nil, false, err
}

kernelBTF.spec, kernelBTF.fallback = spec, fallback
return spec, fallback, nil
}

func loadKernelSpec() (_ *Spec, fallback bool, _ error) {
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if err == nil {
defer fh.Close()

spec, err := loadRawSpec(fh, internal.NativeEndian, nil)
return spec, false, err
}

file, err := findVMLinux()
if err != nil {
return nil, false, err
}
defer file.Close()

spec, err := loadSpecFromELF(file)
return spec, true, err
}

// findVMLinux scans multiple well-known paths for vmlinux kernel images.
func findVMLinux() (*internal.SafeELFFile, error) {
release, err := internal.KernelRelease()
if err != nil {
return nil, err
}

// use same list of locations as libbpf
// https://github.com/libbpf/libbpf/blob/9a3a42608dbe3731256a5682a125ac1e23bced8f/src/btf.c#L3114-L3122
locations := []string{
"/boot/vmlinux-%s",
"/lib/modules/%s/vmlinux-%[1]s",
"/lib/modules/%s/build/vmlinux",
"/usr/lib/modules/%s/kernel/vmlinux",
"/usr/lib/debug/boot/vmlinux-%s",
"/usr/lib/debug/boot/vmlinux-%s.debug",
"/usr/lib/debug/lib/modules/%s/vmlinux",
}

for _, loc := range locations {
file, err := internal.OpenSafeELFFile(fmt.Sprintf(loc, release))
if errors.Is(err, os.ErrNotExist) {
continue
}
return file, err
}

return nil, fmt.Errorf("no BTF found for kernel version %s: %w", release, internal.ErrNotSupported)
}

// parseBTFHeader parses the header of the .BTF section.
func parseBTFHeader(r io.Reader, bo binary.ByteOrder) (*btfHeader, error) {
var header btfHeader
Expand Down
34 changes: 33 additions & 1 deletion btf/btf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ func vmlinuxSpec(tb testing.TB) *Spec {
// through sysfs"), which shipped in Linux 5.4.
testutils.SkipOnOldKernel(tb, "5.4", "vmlinux BTF in sysfs")

spec, err := LoadSpec("/sys/kernel/btf/vmlinux")
spec, fallback, err := kernelSpec()
if err != nil {
tb.Fatal(err)
}
if fallback {
tb.Fatal("/sys/kernel/btf/vmlinux is not available")
}
return spec.Copy()
}

Expand Down Expand Up @@ -240,6 +243,24 @@ func TestParseCurrentKernelBTF(t *testing.T) {
t.Logf("Average string size: %.0f", float64(totalBytes)/float64(len(spec.strings.strings)))
}

func TestFindVMLinux(t *testing.T) {
file, err := findVMLinux()
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal("Can't find vmlinux:", err)
}
defer file.Close()

spec, err := loadSpecFromELF(file)
if err != nil {
t.Fatal("Can't load BTF:", err)
}

if len(spec.namedTypes) == 0 {
t.Fatal("Empty kernel BTF")
}
}

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)
Expand Down Expand Up @@ -295,6 +316,17 @@ func TestVerifierError(t *testing.T) {
}
}

func TestLoadKernelSpec(t *testing.T) {
if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) {
t.Skip("/sys/kernel/btf/vmlinux not present")
}

_, err := LoadKernelSpec()
if err != nil {
t.Fatal("Can't load kernel spec:", err)
}
}

func TestGuessBTFByteOrder(t *testing.T) {
bo := guessRawBTFByteOrder(vmlinuxTestdataReader(t))
if bo != binary.LittleEndian {
Expand Down
7 changes: 5 additions & 2 deletions btf/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,11 @@ func (k coreKind) String() string {
// for relos[i].
func CORERelocate(relos []*CORERelocation, target *Spec, bo binary.ByteOrder) ([]COREFixup, error) {
if target == nil {
// Explicitly check for nil here since the argument used to be optional.
return nil, fmt.Errorf("target must be provided")
var err error
target, _, err = kernelSpec()
if err != nil {
return nil, fmt.Errorf("load kernel spec: %w", err)
}
}

if bo != target.byteOrder {
Expand Down
107 changes: 0 additions & 107 deletions internal/linux/types.go

This file was deleted.

33 changes: 0 additions & 33 deletions internal/linux/types_test.go

This file was deleted.

11 changes: 1 addition & 10 deletions linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/linux"
)

// handles stores handle objects to avoid gc cleanup
Expand Down Expand Up @@ -124,14 +123,6 @@ func applyRelocations(insns asm.Instructions, target *btf.Spec, bo binary.ByteOr
bo = internal.NativeEndian
}

if target == nil {
var err error
target, err = linux.TypesNoCopy()
if err != nil {
return err
}
}

fixups, err := btf.CORERelocate(relos, target, bo)
if err != nil {
return err
Expand Down Expand Up @@ -253,7 +244,7 @@ func fixupKfuncs(insns asm.Instructions) (handles, error) {

fixups:
// only load the kernel spec if we found at least one kfunc call
kernelSpec, err := linux.TypesNoCopy()
kernelSpec, err := btf.LoadKernelSpec()
if err != nil {
return nil, err
}
Expand Down
24 changes: 0 additions & 24 deletions linux/types.go

This file was deleted.

Loading

0 comments on commit 624a8b7

Please sign in to comment.