Skip to content

Commit ce7b452

Browse files
committed
support for kmod CO-RE
Signed-off-by: Bryce Kahle <bryce.kahle@datadoghq.com>
1 parent ff37506 commit ce7b452

File tree

8 files changed

+295
-15
lines changed

8 files changed

+295
-15
lines changed

btf/btf.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import (
99
"io"
1010
"math"
1111
"os"
12+
"path/filepath"
1213
"reflect"
1314
"sync"
1415

1516
"github.com/cilium/ebpf/internal"
17+
"github.com/cilium/ebpf/internal/kallsyms"
1618
"github.com/cilium/ebpf/internal/sys"
1719
)
1820

@@ -171,6 +173,10 @@ type Spec struct {
171173
strings *stringTable
172174
}
173175

176+
func init() {
177+
kernelModuleBTF.spec = make(map[string]*Spec)
178+
}
179+
174180
// LoadSpec opens file and calls LoadSpecFromReader on it.
175181
func LoadSpec(file string) (*Spec, error) {
176182
fh, err := os.Open(file)
@@ -388,19 +394,45 @@ func LoadKernelSpec() (*Spec, error) {
388394
return spec.Copy(), nil
389395
}
390396

397+
// LoadKernelModuleSpec returns the BTF information for the named kernel module.
398+
//
399+
// Defaults to /sys/kernel/btf/<module>.
400+
// Returns an error wrapping ErrNotSupported if BTF is not enabled.
401+
func LoadKernelModuleSpec(module string) (*Spec, error) {
402+
dir, file := filepath.Split(module)
403+
if dir != "" || filepath.Ext(file) != "" {
404+
return nil, fmt.Errorf("invalid module name %q", module)
405+
}
406+
spec, err := kernelModuleSpec(module)
407+
if err != nil {
408+
return nil, err
409+
}
410+
return spec.Copy(), nil
411+
}
412+
391413
var kernelBTF struct {
392414
sync.RWMutex
393415
spec *Spec
394416
// True if the spec was read from an ELF instead of raw BTF in /sys.
395417
fallback bool
396418
}
397419

420+
var kernelModuleBTF struct {
421+
sync.RWMutex
422+
spec map[string]*Spec
423+
}
424+
398425
// FlushKernelSpec removes any cached kernel type information.
399426
func FlushKernelSpec() {
427+
kernelModuleBTF.Lock()
428+
defer kernelModuleBTF.Unlock()
400429
kernelBTF.Lock()
401430
defer kernelBTF.Unlock()
402431

403432
kernelBTF.spec, kernelBTF.fallback = nil, false
433+
kernelModuleBTF.spec = make(map[string]*Spec)
434+
435+
kallsyms.FlushKernelModuleCache()
404436
}
405437

406438
func kernelSpec() (*Spec, bool, error) {
@@ -428,6 +460,31 @@ func kernelSpec() (*Spec, bool, error) {
428460
return spec, fallback, nil
429461
}
430462

463+
func kernelModuleSpec(module string) (*Spec, error) {
464+
kernelModuleBTF.RLock()
465+
spec := kernelModuleBTF.spec[module]
466+
kernelModuleBTF.RUnlock()
467+
468+
if spec == nil {
469+
kernelModuleBTF.Lock()
470+
defer kernelModuleBTF.Unlock()
471+
472+
spec = kernelModuleBTF.spec[module]
473+
}
474+
475+
if spec != nil {
476+
return spec, nil
477+
}
478+
479+
spec, err := loadKernelModuleSpec(module)
480+
if err != nil {
481+
return nil, err
482+
}
483+
484+
kernelModuleBTF.spec[module] = spec
485+
return spec, nil
486+
}
487+
431488
func loadKernelSpec() (_ *Spec, fallback bool, _ error) {
432489
fh, err := os.Open("/sys/kernel/btf/vmlinux")
433490
if err == nil {
@@ -447,6 +504,21 @@ func loadKernelSpec() (_ *Spec, fallback bool, _ error) {
447504
return spec, true, err
448505
}
449506

507+
func loadKernelModuleSpec(module string) (*Spec, error) {
508+
base, _, err := kernelSpec()
509+
if err != nil {
510+
return nil, err
511+
}
512+
513+
fh, err := os.Open(filepath.Join("/sys/kernel/btf", module))
514+
if err != nil {
515+
return nil, err
516+
}
517+
defer fh.Close()
518+
519+
return loadRawSpec(fh, internal.NativeEndian, base)
520+
}
521+
450522
// findVMLinux scans multiple well-known paths for vmlinux kernel images.
451523
func findVMLinux() (*os.File, error) {
452524
release, err := internal.KernelRelease()

btf/core.go

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,33 @@ func (k coreKind) String() string {
156156
}
157157
}
158158

159+
type mergedSpec []*Spec
160+
161+
func (s mergedSpec) TypeByID(id TypeID) (Type, error) {
162+
for _, sp := range s {
163+
t, err := sp.TypeByID(id)
164+
if err != nil {
165+
if errors.Is(err, ErrNotFound) {
166+
continue
167+
}
168+
return nil, err
169+
}
170+
return t, nil
171+
}
172+
return nil, fmt.Errorf("look up type with ID %d (first ID is %d): %w", id, s[0].imm.firstTypeID, ErrNotFound)
173+
}
174+
175+
func (s mergedSpec) NamedTypes(name essentialName) []TypeID {
176+
var typeIDs []TypeID
177+
for _, sp := range s {
178+
namedTypes := sp.imm.namedTypes[name]
179+
if len(namedTypes) > 0 {
180+
typeIDs = append(typeIDs, namedTypes...)
181+
}
182+
}
183+
return typeIDs
184+
}
185+
159186
// CORERelocate calculates changes needed to adjust eBPF instructions for differences
160187
// in types.
161188
//
@@ -169,17 +196,26 @@ func (k coreKind) String() string {
169196
//
170197
// Fixups are returned in the order of relos, e.g. fixup[i] is the solution
171198
// for relos[i].
172-
func CORERelocate(relos []*CORERelocation, target *Spec, bo binary.ByteOrder, resolveLocalTypeID func(Type) (TypeID, error)) ([]COREFixup, error) {
173-
if target == nil {
174-
var err error
175-
target, _, err = kernelSpec()
199+
func CORERelocate(relos []*CORERelocation, targets []*Spec, kmodName string, bo binary.ByteOrder, resolveLocalTypeID func(Type) (TypeID, error)) ([]COREFixup, error) {
200+
if len(targets) == 0 {
201+
kernelTarget, _, err := kernelSpec()
176202
if err != nil {
177203
return nil, fmt.Errorf("load kernel spec: %w", err)
178204
}
179-
}
205+
targets = append(targets, kernelTarget)
180206

181-
if bo != target.imm.byteOrder {
182-
return nil, fmt.Errorf("can't relocate %s against %s", bo, target.imm.byteOrder)
207+
if kmodName != "" {
208+
kmodTarget, err := kernelModuleSpec(kmodName)
209+
if err != nil {
210+
return nil, fmt.Errorf("load kernel module spec: %w", err)
211+
}
212+
targets = append(targets, kmodTarget)
213+
}
214+
}
215+
for _, target := range targets {
216+
if bo != target.imm.byteOrder {
217+
return nil, fmt.Errorf("can't relocate %s against %s", bo, target.imm.byteOrder)
218+
}
183219
}
184220

185221
type reloGroup struct {
@@ -221,14 +257,15 @@ func CORERelocate(relos []*CORERelocation, target *Spec, bo binary.ByteOrder, re
221257
group.indices = append(group.indices, i)
222258
}
223259

260+
mergeTarget := mergedSpec(targets)
224261
for localType, group := range relosByType {
225262
localTypeName := localType.TypeName()
226263
if localTypeName == "" {
227264
return nil, fmt.Errorf("relocate unnamed or anonymous type %s: %w", localType, ErrNotSupported)
228265
}
229266

230-
targets := target.imm.namedTypes[newEssentialName(localTypeName)]
231-
fixups, err := coreCalculateFixups(group.relos, target, targets, bo)
267+
targets := mergeTarget.NamedTypes(newEssentialName(localTypeName))
268+
fixups, err := coreCalculateFixups(group.relos, &mergeTarget, targets, bo)
232269
if err != nil {
233270
return nil, fmt.Errorf("relocate %s: %w", localType, err)
234271
}
@@ -251,7 +288,7 @@ var errIncompatibleTypes = errors.New("incompatible types")
251288
//
252289
// The best target is determined by scoring: the less poisoning we have to do
253290
// the better the target is.
254-
func coreCalculateFixups(relos []*CORERelocation, targetSpec *Spec, targets []TypeID, bo binary.ByteOrder) ([]COREFixup, error) {
291+
func coreCalculateFixups(relos []*CORERelocation, targetSpec *mergedSpec, targets []TypeID, bo binary.ByteOrder) ([]COREFixup, error) {
255292
bestScore := len(relos)
256293
var bestFixups []COREFixup
257294
for _, targetID := range targets {

btf/core_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ func TestCORERelocation(t *testing.T) {
592592
relos = append(relos, reloInfo.relo)
593593
}
594594

595-
fixups, err := CORERelocate(relos, spec, spec.imm.byteOrder, spec.TypeID)
595+
fixups, err := CORERelocate(relos, []*Spec{spec}, "", spec.imm.byteOrder, spec.TypeID)
596596
if want := errs[name]; want != nil {
597597
if !errors.Is(err, want) {
598598
t.Fatal("Expected", want, "got", err)
@@ -744,7 +744,7 @@ func BenchmarkCORESkBuff(b *testing.B) {
744744
b.ReportAllocs()
745745

746746
for i := 0; i < b.N; i++ {
747-
_, err = CORERelocate([]*CORERelocation{relo}, spec, spec.imm.byteOrder, spec.TypeID)
747+
_, err = CORERelocate([]*CORERelocation{relo}, []*Spec{spec}, "", spec.imm.byteOrder, spec.TypeID)
748748
if err != nil {
749749
b.Fatal(err)
750750
}

internal/kallsyms/kallsyms.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package kallsyms
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"io"
7+
"os"
8+
"sync"
9+
)
10+
11+
var kernelModules struct {
12+
sync.RWMutex
13+
// function to kernel module mapping
14+
kmods map[string]string
15+
}
16+
17+
// KernelModule returns the kernel module, if any, a probe-able function is contained in.
18+
func KernelModule(fn string) (string, error) {
19+
kernelModules.RLock()
20+
kmods := kernelModules.kmods
21+
kernelModules.RUnlock()
22+
23+
if kmods == nil {
24+
kernelModules.Lock()
25+
defer kernelModules.Unlock()
26+
kmods = kernelModules.kmods
27+
}
28+
29+
if kmods != nil {
30+
return kmods[fn], nil
31+
}
32+
33+
f, err := os.Open("/proc/kallsyms")
34+
if err != nil {
35+
return "", err
36+
}
37+
defer f.Close()
38+
kmods, err = loadKernelModuleMapping(f)
39+
if err != nil {
40+
return "", err
41+
}
42+
43+
kernelModules.kmods = kmods
44+
return kmods[fn], nil
45+
}
46+
47+
// FlushKernelModuleCache removes any cached information about function to kernel module mapping.
48+
func FlushKernelModuleCache() {
49+
kernelModules.Lock()
50+
defer kernelModules.Unlock()
51+
52+
kernelModules.kmods = nil
53+
}
54+
55+
func loadKernelModuleMapping(f io.Reader) (map[string]string, error) {
56+
mods := make(map[string]string)
57+
scanner := bufio.NewScanner(f)
58+
for scanner.Scan() {
59+
fields := bytes.Fields(scanner.Bytes())
60+
if len(fields) < 4 {
61+
continue
62+
}
63+
switch string(fields[1]) {
64+
case "t", "T":
65+
mods[string(fields[2])] = string(bytes.Trim(fields[3], "[]"))
66+
default:
67+
continue
68+
}
69+
}
70+
if scanner.Err() != nil {
71+
return nil, scanner.Err()
72+
}
73+
return mods, nil
74+
}

internal/kallsyms/kallsyms_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package kallsyms
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/go-quicktest/qt"
8+
)
9+
10+
func TestKernelModule(t *testing.T) {
11+
kallsyms := []byte(`0000000000000000 t hid_generic_probe [hid_generic]
12+
0000000000000000 T tcp_connect
13+
0000000000000000 B empty_zero_page
14+
0000000000000000 D kimage_vaddr
15+
0000000000000000 R __start_pci_fixups_early
16+
0000000000000000 V hv_root_partition
17+
0000000000000000 W calibrate_delay_is_known
18+
0000000000000000 a nft_counter_seq [nft_counter]
19+
0000000000000000 b bootconfig_found
20+
0000000000000000 d __func__.10
21+
0000000000000000 r __ksymtab_LZ4_decompress_fast`)
22+
krdr := bytes.NewBuffer(kallsyms)
23+
kmods, err := loadKernelModuleMapping(krdr)
24+
qt.Assert(t, qt.IsNil(err))
25+
26+
// present and in module
27+
kmod := kmods["hid_generic_probe"]
28+
if kmod != "hid_generic" {
29+
t.Errorf("expected %q got %q", "hid_generic", kmod)
30+
}
31+
32+
// present but not kernel module
33+
kmod = kmods["tcp_connect"]
34+
if kmod != "" {
35+
t.Errorf("expected %q got %q", "", kmod)
36+
}
37+
38+
// not present
39+
kmod = kmods["asdfasdf"]
40+
if kmod != "" {
41+
t.Errorf("expected %q got %q", "", kmod)
42+
}
43+
}

kallsyms.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ebpf
2+
3+
import "github.com/cilium/ebpf/internal/kallsyms"
4+
5+
// FlushKernelModuleCache removes any cached information about function to kernel module mapping.
6+
func FlushKernelModuleCache() {
7+
kallsyms.FlushKernelModuleCache()
8+
}

linker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func hasFunctionReferences(insns asm.Instructions) bool {
119119
//
120120
// Passing a nil target will relocate against the running kernel. insns are
121121
// modified in place.
122-
func applyRelocations(insns asm.Instructions, target *btf.Spec, bo binary.ByteOrder, b *btf.Builder) error {
122+
func applyRelocations(insns asm.Instructions, targets []*btf.Spec, kmodName string, bo binary.ByteOrder, b *btf.Builder) error {
123123
var relos []*btf.CORERelocation
124124
var reloInsns []*asm.Instruction
125125
iter := insns.Iterate()
@@ -138,7 +138,7 @@ func applyRelocations(insns asm.Instructions, target *btf.Spec, bo binary.ByteOr
138138
bo = internal.NativeEndian
139139
}
140140

141-
fixups, err := btf.CORERelocate(relos, target, bo, b.Add)
141+
fixups, err := btf.CORERelocate(relos, targets, kmodName, bo, b.Add)
142142
if err != nil {
143143
return err
144144
}

0 commit comments

Comments
 (0)