Skip to content

Commit 5f4ad1f

Browse files
committed
program: perform CO-RE against all kmod
CO-RE relocations are currently performed against vmlinux types only. If a program attaches to a function in a kernel module we also relocate against types in that module. This has the downside that it's not possible to inspect a variable with a type defined in a kernel module when tracing a function in vmlinux itself. The historical reason for this is that our BTF parsing was quite expensive. Now that we have lazy loading we can afford to relocate against all types in the kernel. Below is the change in performance against the last release: core: 1 goos: linux goarch: amd64 pkg: github.com/cilium/ebpf cpu: 13th Gen Intel(R) Core(TM) i7-1365U │ base.txt │ all.txt │ │ sec/op │ sec/op vs base │ NewCollectionManyProgs 336.94m ± 205% 16.38m ± 2% -95.14% (p=0.002 n=6) │ base.txt │ all.txt │ │ B/op │ B/op vs base │ NewCollectionManyProgs 112.541Mi ± 45% 4.011Mi ± 0% -96.44% (p=0.002 n=6) │ base.txt │ all.txt │ │ allocs/op │ allocs/op vs base │ NewCollectionManyProgs 386.00k ± 202% 39.78k ± 0% -89.70% (p=0.002 n=6) Fixes: #1511 Signed-off-by: Lorenz Bauer <lmb@isovalent.com>
1 parent 38316ff commit 5f4ad1f

File tree

7 files changed

+83
-53
lines changed

7 files changed

+83
-53
lines changed

btf/core_reloc_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ func TestCORERelocationLoad(t *testing.T) {
3636
}
3737

3838
prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
39-
KernelTypes: spec.Types,
39+
KernelTypes: spec.Types,
40+
KernelModuleTypes: map[string]*btf.Spec{},
4041
})
4142
testutils.SkipIfNotSupported(t, err)
4243

@@ -83,7 +84,8 @@ func TestCORERelocationRead(t *testing.T) {
8384
for _, progSpec := range spec.Programs {
8485
t.Run(progSpec.Name, func(t *testing.T) {
8586
prog, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
86-
KernelTypes: targetSpec,
87+
KernelTypes: targetSpec,
88+
KernelModuleTypes: map[string]*btf.Spec{},
8789
})
8890
testutils.SkipIfNotSupported(t, err)
8991
if err != nil {
@@ -149,7 +151,8 @@ func TestCOREPoisonLineInfo(t *testing.T) {
149151
t.Run(test.name, func(t *testing.T) {
150152
t.Log(progSpec.Instructions)
151153
_, err := ebpf.NewProgramWithOptions(progSpec, ebpf.ProgramOptions{
152-
KernelTypes: empty,
154+
KernelTypes: empty,
155+
KernelModuleTypes: map[string]*btf.Spec{},
153156
})
154157
testutils.SkipIfNotSupported(t, err)
155158

btf/kernel.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"slices"
9+
"sort"
810
"sync"
911

1012
"github.com/cilium/ebpf/internal"
@@ -189,7 +191,8 @@ func findVMLinux() (*os.File, error) {
189191
// It is not safe for concurrent use.
190192
type Cache struct {
191193
KernelTypes *Spec
192-
KernelModules map[string]*Spec
194+
ModuleTypes map[string]*Spec
195+
LoadedModules []string
193196
}
194197

195198
// NewCache creates a new Cache.
@@ -214,7 +217,11 @@ func NewCache() *Cache {
214217
modules[name] = &Spec{decoder: decoder}
215218
}
216219

217-
return &Cache{kernel, modules}
220+
if len(modules) == 0 {
221+
return &Cache{kernel, nil, nil}
222+
}
223+
224+
return &Cache{kernel, modules, nil}
218225
}
219226

220227
// Kernel is equivalent to [LoadKernelSpec], except that repeated calls do
@@ -234,12 +241,12 @@ func (c *Cache) Kernel() (*Spec, error) {
234241
//
235242
// All modules also share the return value of [Kernel] as their base.
236243
func (c *Cache) Module(name string) (*Spec, error) {
237-
if spec := c.KernelModules[name]; spec != nil {
244+
if spec := c.ModuleTypes[name]; spec != nil {
238245
return spec, nil
239246
}
240247

241-
if c.KernelModules == nil {
242-
c.KernelModules = make(map[string]*Spec)
248+
if c.ModuleTypes == nil {
249+
c.ModuleTypes = make(map[string]*Spec)
243250
}
244251

245252
base, err := c.Kernel()
@@ -260,6 +267,32 @@ func (c *Cache) Module(name string) (*Spec, error) {
260267
}
261268

262269
spec = &Spec{decoder: decoder}
263-
c.KernelModules[name] = spec
270+
c.ModuleTypes[name] = spec
264271
return spec, err
265272
}
273+
274+
// Modules returns a sorted list of all loaded modules.
275+
func (c *Cache) Modules() ([]string, error) {
276+
if c.LoadedModules != nil {
277+
return c.LoadedModules, nil
278+
}
279+
280+
btfDir, err := os.Open("/sys/kernel/btf")
281+
if err != nil {
282+
return nil, err
283+
}
284+
defer btfDir.Close()
285+
286+
entries, err := btfDir.Readdirnames(-1)
287+
if err != nil {
288+
return nil, err
289+
}
290+
291+
entries = slices.DeleteFunc(entries, func(s string) bool {
292+
return s == "vmlinux"
293+
})
294+
295+
sort.Strings(entries)
296+
c.LoadedModules = entries
297+
return entries, nil
298+
}

btf/kernel_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ func TestCache(t *testing.T) {
3434
c := NewCache()
3535

3636
qt.Assert(t, qt.IsNil(c.KernelTypes))
37-
qt.Assert(t, qt.HasLen(c.KernelModules, 0))
37+
qt.Assert(t, qt.HasLen(c.ModuleTypes, 0))
38+
qt.Assert(t, qt.IsNil(c.LoadedModules))
3839

3940
// Test that Kernel() creates only one copy
4041
spec1, err := c.Kernel()
@@ -75,7 +76,12 @@ func TestCache(t *testing.T) {
7576
qt.Assert(t, qt.IsNotNil(c.KernelTypes))
7677
qt.Assert(t, qt.Not(qt.Equals(c.KernelTypes, vmlinux)))
7778
if testmod != nil {
78-
qt.Assert(t, qt.IsNotNil(c.KernelModules["bpf_testmod"]))
79-
qt.Assert(t, qt.Not(qt.Equals(c.KernelModules["bpf_testmod"], testmod)))
79+
qt.Assert(t, qt.IsNotNil(c.ModuleTypes["bpf_testmod"]))
80+
qt.Assert(t, qt.Not(qt.Equals(c.ModuleTypes["bpf_testmod"], testmod)))
8081
}
82+
83+
// Test that Modules only reads modules once.
84+
_, err = c.Modules()
85+
qt.Assert(t, qt.IsNil(err))
86+
qt.Assert(t, qt.IsNotNil(c.LoadedModules))
8187
}

btf/unmarshal.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,9 @@ func (d *decoder) TypesByName(name essentialName) ([]Type, error) {
269269
}
270270

271271
if len(types) == 0 {
272-
return nil, fmt.Errorf("type with name %s: %w", name, ErrNotFound)
272+
// Return an unwrapped error because this is on the hot path
273+
// for CO-RE.
274+
return nil, ErrNotFound
273275
}
274276

275277
return types, nil

linker.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"errors"
77
"fmt"
88
"io"
9-
"io/fs"
109
"math"
1110
"slices"
1211
"strings"
@@ -124,7 +123,7 @@ func hasFunctionReferences(insns asm.Instructions) bool {
124123
//
125124
// Passing a nil target will relocate against the running kernel. insns are
126125
// modified in place.
127-
func applyRelocations(insns asm.Instructions, kmodName string, bo binary.ByteOrder, b *btf.Builder, c *btf.Cache) error {
126+
func applyRelocations(insns asm.Instructions, bo binary.ByteOrder, b *btf.Builder, c *btf.Cache) error {
128127
var relos []*btf.CORERelocation
129128
var reloInsns []*asm.Instruction
130129
iter := insns.Iterate()
@@ -143,22 +142,26 @@ func applyRelocations(insns asm.Instructions, kmodName string, bo binary.ByteOrd
143142
bo = internal.NativeEndian
144143
}
145144

146-
var targets []*btf.Spec
147145
kernelTarget, err := c.Kernel()
148146
if err != nil {
149147
return fmt.Errorf("load kernel spec: %w", err)
150148
}
149+
150+
modules, err := c.Modules()
151+
if err != nil {
152+
return err
153+
}
154+
155+
targets := make([]*btf.Spec, 0, 1+len(modules))
151156
targets = append(targets, kernelTarget)
152157

153-
if kmodName != "" {
154-
kmodTarget, err := c.Module(kmodName)
155-
// Ignore ErrNotExists to cater to kernels which have CONFIG_DEBUG_INFO_BTF_MODULES disabled.
156-
if err != nil && !errors.Is(err, fs.ErrNotExist) {
157-
return fmt.Errorf("load kernel module spec: %w", err)
158-
}
159-
if err == nil {
160-
targets = append(targets, kmodTarget)
158+
for _, kmod := range modules {
159+
spec, err := c.Module(kmod)
160+
if err != nil {
161+
return fmt.Errorf("load BTF for kmod %s: %w", kmod, err)
161162
}
163+
164+
targets = append(targets, spec)
162165
}
163166

164167
fixups, err := btf.CORERelocate(relos, targets, bo, b.Add)

prog.go

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import (
88
"math"
99
"path/filepath"
1010
"runtime"
11+
"sort"
1112
"time"
1213

1314
"github.com/cilium/ebpf/asm"
1415
"github.com/cilium/ebpf/btf"
1516
"github.com/cilium/ebpf/internal"
16-
"github.com/cilium/ebpf/internal/kallsyms"
1717
"github.com/cilium/ebpf/internal/linux"
1818
"github.com/cilium/ebpf/internal/platform"
1919
"github.com/cilium/ebpf/internal/sys"
@@ -171,17 +171,6 @@ func (ps *ProgramSpec) Tag() (string, error) {
171171
return ps.Instructions.Tag(internal.NativeEndian)
172172
}
173173

174-
// kernelModule returns the kernel module providing the symbol in
175-
// ProgramSpec.AttachTo, if any. Returns an empty string if the symbol is not
176-
// present or not part of a kernel module.
177-
func (ps *ProgramSpec) kernelModule() (string, error) {
178-
if ps.targetsKernelModule() {
179-
return kallsyms.Module(ps.AttachTo)
180-
}
181-
182-
return "", nil
183-
}
184-
185174
// targetsKernelModule returns true if the program supports being attached to a
186175
// symbol provided by a kernel module.
187176
func (ps *ProgramSpec) targetsKernelModule() bool {
@@ -303,13 +292,8 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, c *btf.Cache)
303292
insns := make(asm.Instructions, len(spec.Instructions))
304293
copy(insns, spec.Instructions)
305294

306-
kmodName, err := spec.kernelModule()
307-
if err != nil {
308-
return nil, fmt.Errorf("kernel module search: %w", err)
309-
}
310-
311295
var b btf.Builder
312-
if err := applyRelocations(insns, kmodName, spec.ByteOrder, &b, c); err != nil {
296+
if err := applyRelocations(insns, spec.ByteOrder, &b, c); err != nil {
313297
return nil, fmt.Errorf("apply CO-RE relocations: %w", err)
314298
}
315299

@@ -1212,7 +1196,14 @@ func newBTFCache(opts *ProgramOptions) *btf.Cache {
12121196
c := btf.NewCache()
12131197
if opts.KernelTypes != nil {
12141198
c.KernelTypes = opts.KernelTypes
1215-
c.KernelModules = opts.KernelModuleTypes
1199+
c.ModuleTypes = opts.KernelModuleTypes
1200+
if opts.KernelModuleTypes != nil {
1201+
c.LoadedModules = []string{}
1202+
for name := range opts.KernelModuleTypes {
1203+
c.LoadedModules = append(c.LoadedModules, name)
1204+
}
1205+
sort.Strings(c.LoadedModules)
1206+
}
12161207
}
12171208
return c
12181209
}

prog_test.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,8 @@ func TestProgramLoadErrors(t *testing.T) {
881881
t.Run(test.name, func(t *testing.T) {
882882
t.Log(progSpec.Instructions)
883883
_, err := newProgram(t, progSpec, &ProgramOptions{
884-
KernelTypes: empty,
884+
KernelTypes: empty,
885+
KernelModuleTypes: map[string]*btf.Spec{},
885886
})
886887
testutils.SkipIfNotSupported(t, err)
887888

@@ -902,15 +903,6 @@ func TestProgramTargetsKernelModule(t *testing.T) {
902903
qt.Assert(t, qt.IsTrue(ps.targetsKernelModule()))
903904
}
904905

905-
func TestProgramAttachToKernelModule(t *testing.T) {
906-
requireTestmod(t)
907-
908-
ps := ProgramSpec{AttachTo: "bpf_testmod_test_read", Type: Tracing, AttachType: AttachTraceFEntry}
909-
mod, err := ps.kernelModule()
910-
qt.Assert(t, qt.IsNil(err))
911-
qt.Assert(t, qt.Equals(mod, "bpf_testmod"))
912-
}
913-
914906
func BenchmarkNewProgram(b *testing.B) {
915907
testutils.SkipOnOldKernel(b, "5.18", "kfunc support")
916908
spec, err := LoadCollectionSpec(testutils.NativeFile(b, "testdata/kfunc-%s.elf"))

0 commit comments

Comments
 (0)