Skip to content

Commit 3a0cbac

Browse files
shun159rgo3
authored andcommitted
elf: support kernel module kfunc calls
This commit adds support for kernel module function call support. The fdArray parameter is used during BPF program load to pass module BTFs referenced by the program. ins.Offset is set to index into this array and ins.Constant is assigned a BTFID in the BTF (of module or vmlinux). cf: https://lore.kernel.org/bpf/[email protected]/ Signed-off-by: shun159 <[email protected]>
1 parent 88883cf commit 3a0cbac

9 files changed

+183
-33
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ TARGETS := \
4242
testdata/fwd_decl \
4343
testdata/kconfig \
4444
testdata/kfunc \
45+
testdata/kfunc-kmod \
4546
btf/testdata/relocs \
4647
btf/testdata/relocs_read \
4748
btf/testdata/relocs_read_tgt \

elf_reader_test.go

+40-1
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ func TestKconfigSyscallWrapper(t *testing.T) {
656656

657657
func TestKfunc(t *testing.T) {
658658
testutils.SkipOnOldKernel(t, "5.18", "bpf_kfunc_call_test_mem_len_pass1")
659-
testutils.Files(t, testutils.Glob(t, "testdata/kfunc-*.elf"), func(t *testing.T, file string) {
659+
testutils.Files(t, testutils.Glob(t, "testdata/kfunc-e*.elf"), func(t *testing.T, file string) {
660660
spec, err := LoadCollectionSpec(file)
661661
if err != nil {
662662
t.Fatal(err)
@@ -686,7 +686,46 @@ func TestKfunc(t *testing.T) {
686686
if ret != 1 {
687687
t.Fatalf("Expected kfunc to return value 1, got %d", ret)
688688
}
689+
})
690+
}
691+
692+
func TestKfuncKmod(t *testing.T) {
693+
testutils.SkipOnOldKernel(t, "5.18", "Kernel module function calls")
694+
695+
if !haveTestmod(t) {
696+
t.Skip("bpf_testmod not loaded")
697+
}
698+
699+
testutils.Files(t, testutils.Glob(t, "testdata/kfunc-kmod-*.elf"), func(t *testing.T, file string) {
700+
spec, err := LoadCollectionSpec(file)
701+
if err != nil {
702+
t.Fatal(err)
703+
}
704+
705+
if spec.ByteOrder != internal.NativeEndian {
706+
return
707+
}
708+
709+
var obj struct {
710+
Main *Program `ebpf:"call_kfunc"`
711+
}
689712

713+
err = spec.LoadAndAssign(&obj, nil)
714+
testutils.SkipIfNotSupported(t, err)
715+
if err != nil {
716+
t.Fatalf("%v+", err)
717+
}
718+
defer obj.Main.Close()
719+
720+
ret, _, err := obj.Main.Test(internal.EmptyBPFContext)
721+
testutils.SkipIfNotSupported(t, err)
722+
if err != nil {
723+
t.Fatal(err)
724+
}
725+
726+
if ret != 1 {
727+
t.Fatalf("Expected kfunc to return value 1, got %d", ret)
728+
}
690729
})
691730
}
692731

helpers_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package ebpf
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/cilium/ebpf/btf"
8+
"github.com/cilium/ebpf/internal/testutils"
9+
)
10+
11+
func haveTestmod(tb testing.TB) bool {
12+
haveTestmod := false
13+
if !testutils.IsKernelLessThan(tb, "5.11") {
14+
// See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744
15+
testmod, err := btf.FindHandle(func(info *btf.HandleInfo) bool {
16+
return info.IsModule() && info.Name == "bpf_testmod"
17+
})
18+
if err != nil && !errors.Is(err, btf.ErrNotFound) {
19+
tb.Fatal(err)
20+
}
21+
haveTestmod = testmod != nil
22+
testmod.Close()
23+
}
24+
25+
return haveTestmod
26+
}

linker.go

+65-20
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,47 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"math"
89

910
"github.com/cilium/ebpf/asm"
1011
"github.com/cilium/ebpf/btf"
1112
"github.com/cilium/ebpf/internal"
1213
)
1314

15+
// handles stores handle objects to avoid gc cleanup
16+
type handles []*btf.Handle
17+
18+
func (hs *handles) add(h *btf.Handle) (int, error) {
19+
if h == nil {
20+
return 0, nil
21+
}
22+
23+
if len(*hs) == math.MaxInt16 {
24+
return 0, fmt.Errorf("can't add more than %d module FDs to fdArray", math.MaxInt16)
25+
}
26+
27+
*hs = append(*hs, h)
28+
29+
// return length of slice so that indexes start at 1
30+
return len(*hs), nil
31+
}
32+
33+
func (hs handles) fdArray() []int32 {
34+
// first element of fda is reserved as no module can be indexed with 0
35+
fda := []int32{0}
36+
for _, h := range hs {
37+
fda = append(fda, int32(h.FD()))
38+
}
39+
40+
return fda
41+
}
42+
43+
func (hs handles) close() {
44+
for _, h := range hs {
45+
h.Close()
46+
}
47+
}
48+
1449
// splitSymbols splits insns into subsections delimited by Symbol Instructions.
1550
// insns cannot be empty and must start with a Symbol Instruction.
1651
//
@@ -190,17 +225,13 @@ func fixupAndValidate(insns asm.Instructions) error {
190225
fixupProbeReadKernel(ins)
191226
}
192227

193-
if err := fixupKfuncs(insns); err != nil {
194-
return fmt.Errorf("fixing up kfuncs: %w", err)
195-
}
196-
197228
return nil
198229
}
199230

200231
// fixupKfuncs loops over all instructions in search for kfunc calls.
201-
// If at least one is found, the current kernels BTF is loaded to set Instruction.Constant
202-
// to the running kernels btf id of the btf.Func in the instructions Metadata for all kfunc call instructions.
203-
func fixupKfuncs(insns asm.Instructions) error {
232+
// If at least one is found, the current kernels BTF and module BTFis are searched to set Instruction.Constant
233+
// and Instruction.Offset to the correct values.
234+
func fixupKfuncs(insns asm.Instructions) (handles, error) {
204235
iter := insns.Iterate()
205236
for iter.Next() {
206237
ins := iter.Ins
@@ -209,15 +240,16 @@ func fixupKfuncs(insns asm.Instructions) error {
209240
}
210241
}
211242

212-
return nil
243+
return nil, nil
213244

214245
fixups:
215246
// only load the kernel spec if we found at least one kfunc call
216-
s, err := btf.LoadKernelSpec()
247+
kernelSpec, err := btf.LoadKernelSpec()
217248
if err != nil {
218-
return err
249+
return nil, err
219250
}
220251

252+
fdArray := make(handles, 0)
221253
for {
222254
ins := iter.Ins
223255

@@ -232,32 +264,45 @@ fixups:
232264
// check meta, if no meta return err
233265
kfm, _ := ins.Metadata.Get(kfuncMeta{}).(*btf.Func)
234266
if kfm == nil {
235-
return fmt.Errorf("kfunc call has no kfuncMeta")
267+
return nil, fmt.Errorf("kfunc call has no kfuncMeta")
236268
}
237269

238-
var fn *btf.Func
239-
if err := s.TypeByName(kfm.Name, &fn); err != nil {
240-
return fmt.Errorf("couldn't resolve %s in kernel spec: %v: %w", kfm.Name, err, ErrNotSupported)
270+
kfuncHandle, id, err := findKfuncInKernel(kernelSpec, kfm.Name)
271+
if err != nil {
272+
return nil, err
241273
}
242274

243-
if err := btf.CheckTypeCompatibility(kfm.Type, fn.Type); err != nil {
244-
return err
275+
// to avoid doing compatibility checks in findKfuncInKernel
276+
// we need to retrieve the btf.Type again by its ID.
277+
spec := kernelSpec
278+
if kfuncHandle != nil {
279+
spec, err = kfuncHandle.Spec(kernelSpec)
280+
if err != nil {
281+
return nil, err
282+
}
283+
}
284+
typ, err := spec.TypeByID(id)
285+
if err != nil {
286+
return nil, err
287+
}
288+
if err := btf.CheckTypeCompatibility(kfm.Type, typ.(*btf.Func).Type); err != nil {
289+
return nil, err
245290
}
246291

247-
id, err := s.TypeID(fn)
292+
idx, err := fdArray.add(kfuncHandle)
248293
if err != nil {
249-
return err
294+
return nil, err
250295
}
251296

252297
ins.Constant = int64(id)
253-
ins.Offset = int16(0) // currently always 0, no support for kmods
298+
ins.Offset = int16(idx)
254299

255300
if !iter.Next() {
256301
break
257302
}
258303
}
259304

260-
return nil
305+
return fdArray, nil
261306
}
262307

263308
// fixupProbeReadKernel replaces calls to bpf_probe_read_{kernel,user}(_str)

prog.go

+40
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"runtime"
1111
"strings"
1212
"time"
13+
"unsafe"
1314

1415
"github.com/cilium/ebpf/asm"
1516
"github.com/cilium/ebpf/btf"
@@ -268,6 +269,17 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
268269
return nil, err
269270
}
270271

272+
handles, err := fixupKfuncs(insns)
273+
if err != nil {
274+
return nil, fmt.Errorf("fixing up kfuncs: %w", err)
275+
}
276+
277+
if len(handles) > 0 {
278+
fdArray := handles.fdArray()
279+
attr.FdArray = sys.NewPointer(unsafe.Pointer(&fdArray[0]))
280+
defer handles.close()
281+
}
282+
271283
buf := bytes.NewBuffer(make([]byte, 0, insns.Size()))
272284
err = insns.Marshal(buf, internal.NativeEndian)
273285
if err != nil {
@@ -1000,3 +1012,31 @@ func findTargetInProgram(prog *Program, name string, progType ProgramType, attac
10001012

10011013
return spec.TypeID(targetFunc)
10021014
}
1015+
1016+
// find a kfunc in a kernel module or vmlinux.
1017+
func findKfuncInKernel(kernelSpec *btf.Spec, name string) (*btf.Handle, btf.TypeID, error) {
1018+
var fn *btf.Func
1019+
err := kernelSpec.TypeByName(name, &fn)
1020+
if errors.Is(err, btf.ErrNotFound) {
1021+
// couldn't find kfunc in vmlinux, searching modules.
1022+
module, id, err := findTargetInModule(kernelSpec, name, fn)
1023+
if errors.Is(err, btf.ErrNotFound) {
1024+
return nil, 0, fmt.Errorf("find kfunc '%s' in vmlinux or modules: %v: %w", name, err, ErrNotSupported)
1025+
}
1026+
if err != nil {
1027+
return nil, 0, fmt.Errorf("find kfunc '%s' in modules: %v", name, err)
1028+
}
1029+
1030+
return module, id, nil
1031+
}
1032+
if err != nil {
1033+
return nil, 0, fmt.Errorf("find kfunc '%s' in kernel: %v", name, err)
1034+
}
1035+
1036+
id, err := kernelSpec.TypeID(fn)
1037+
if err != nil {
1038+
return nil, 0, err
1039+
}
1040+
1041+
return nil, id, nil
1042+
}

prog_test.go

+1-12
Original file line numberDiff line numberDiff line change
@@ -758,18 +758,7 @@ func TestProgramAttachToKernel(t *testing.T) {
758758
// See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744
759759
testutils.SkipOnOldKernel(t, "5.5", "attach_btf_id")
760760

761-
haveTestmod := false
762-
if !testutils.IsKernelLessThan(t, "5.11") {
763-
// See https://github.com/torvalds/linux/commit/290248a5b7d829871b3ea3c62578613a580a1744
764-
testmod, err := btf.FindHandle(func(info *btf.HandleInfo) bool {
765-
return info.IsModule() && info.Name == "bpf_testmod"
766-
})
767-
if err != nil && !errors.Is(err, btf.ErrNotFound) {
768-
t.Fatal(err)
769-
}
770-
haveTestmod = testmod != nil
771-
testmod.Close()
772-
}
761+
haveTestmod := haveTestmod(t)
773762

774763
tests := []struct {
775764
attachTo string

testdata/kfunc-kmod-eb.elf

1.66 KB
Binary file not shown.

testdata/kfunc-kmod-el.elf

1.66 KB
Binary file not shown.

testdata/kfunc-kmod.c

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include "common.h"
2+
3+
char __license[] __section("license") = "Dual MIT/GPL";
4+
5+
extern void bpf_testmod_test_mod_kfunc(int) __ksym;
6+
7+
__section("tc") int call_kfunc() {
8+
bpf_testmod_test_mod_kfunc(0);
9+
return 1;
10+
}

0 commit comments

Comments
 (0)