Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for untyped extern __ksym variables #1578

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ TARGETS := \
testdata/fwd_decl \
testdata/kconfig \
testdata/kconfig_config \
testdata/ksym \
testdata/kfunc \
testdata/invalid-kfunc \
testdata/kfunc-kmod \
Expand Down
16 changes: 11 additions & 5 deletions btf/btf.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,19 @@ func fixupDatasec(types []Type, sectionSizes map[string]uint32, offsets map[symb
// Some Datasecs are virtual and don't have corresponding ELF sections.
switch name {
case ".ksyms":
// .ksyms describes forward declarations of kfunc signatures.
// .ksyms describes forward declarations of kfunc signatures, as well as
// references to kernel symbols.
// Nothing to fix up, all sizes and offsets are 0.
for _, vsi := range ds.Vars {
_, ok := vsi.Type.(*Func)
if !ok {
// Only Funcs are supported in the .ksyms Datasec.
return fmt.Errorf("data section %s: expected *btf.Func, not %T: %w", name, vsi.Type, ErrNotSupported)
switch t := vsi.Type.(type) {
case *Func:
ti-mo marked this conversation as resolved.
Show resolved Hide resolved
continue
case *Var:
ti-mo marked this conversation as resolved.
Show resolved Hide resolved
if _, ok := t.Type.(*Void); !ok {
return fmt.Errorf("data section %s: expected %s to be *Void, not %T: %w", name, vsi.Type.TypeName(), vsi.Type, ErrNotSupported)
}
default:
return fmt.Errorf("data section %s: expected to be either *btf.Func or *btf.Var, not %T: %w", name, vsi.Type, ErrNotSupported)
}
}

Expand Down
37 changes: 35 additions & 2 deletions elf_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kallsyms"
"github.com/cilium/ebpf/internal/sys"
)

Expand All @@ -32,6 +33,14 @@ type kfuncMeta struct {
Func *btf.Func
}

type ksymMetaKey struct{}

type ksymMeta struct {
Binding elf.SymBind
Addr uint64
Name string
}

// elfCode is a convenience to reduce the amount of arguments that have to
// be passed around explicitly. You should treat its contents as immutable.
type elfCode struct {
Expand All @@ -44,6 +53,7 @@ type elfCode struct {
maps map[string]*MapSpec
vars map[string]*VariableSpec
kfuncs map[string]*btf.Func
ksyms map[string]uint64
kconfig *MapSpec
}

Expand Down Expand Up @@ -136,6 +146,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
maps: make(map[string]*MapSpec),
vars: make(map[string]*VariableSpec),
kfuncs: make(map[string]*btf.Func),
ksyms: make(map[string]uint64),
}

symbols, err := f.Symbols()
Expand Down Expand Up @@ -627,6 +638,8 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
}

kf := ec.kfuncs[name]
ksymAddr := ec.ksyms[name]

switch {
// If a Call / DWordLoad instruction is found and the datasec has a btf.Func with a Name
// that matches the symbol name we mark the instruction as a referencing a kfunc.
Expand All @@ -647,6 +660,16 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err

ins.Constant = 0

case ksymAddr != 0 && ins.OpCode.IsDWordLoad():
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
return fmt.Errorf("asm relocation: %s: %w: %s", name, errUnsupportedBinding, bind)
}
ins.Metadata.Set(ksymMetaKey{}, &ksymMeta{
Binding: bind,
Name: name,
Addr: ksymAddr,
})

// If no kconfig map is found, this must be a symbol reference from inline
// asm (see testdata/loader.c:asm_relocation()) or a call to a forward
// function declaration (see testdata/fwd_decl.c). Don't interfere, These
Expand Down Expand Up @@ -1280,8 +1303,18 @@ func (ec *elfCode) loadKsymsSection() error {
}

for _, v := range ds.Vars {
// we have already checked the .ksyms Datasec to only contain Func Vars.
ec.kfuncs[v.Type.TypeName()] = v.Type.(*btf.Func)
switch t := v.Type.(type) {
case *btf.Func:
ec.kfuncs[t.TypeName()] = t
case *btf.Var:
ec.ksyms[t.TypeName()] = 0
default:
return fmt.Errorf("unexpected variable type in .ksysm: %T", v)
}
}

if err := kallsyms.LoadSymbolAddresses(ec.ksyms); err != nil {
return fmt.Errorf("error while loading ksym addresses: %w", err)
}

return nil
Expand Down
57 changes: 57 additions & 0 deletions elf_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kallsyms"
"github.com/cilium/ebpf/internal/linux"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/testutils"
Expand Down Expand Up @@ -772,6 +773,62 @@ func TestKconfigConfig(t *testing.T) {
qt.Assert(t, qt.Not(qt.Equals(value, 0)))
}

func TestKsym(t *testing.T) {
file := testutils.NativeFile(t, "testdata/ksym-%s.elf")
spec, err := LoadCollectionSpec(file)
qt.Assert(t, qt.IsNil(err))

var obj struct {
Main *Program `ebpf:"ksym_test"`
ArrayMap *Map `ebpf:"array_map"`
}

err = spec.LoadAndAssign(&obj, nil)
testutils.SkipIfNotSupported(t, err)
qt.Assert(t, qt.IsNil(err))
defer obj.Main.Close()
defer obj.ArrayMap.Close()

_, _, err = obj.Main.Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
qt.Assert(t, qt.IsNil(err))

ksyms := map[string]uint64{
"socket_file_ops": 0,
"tty_fops": 0,
}

qt.Assert(t, qt.IsNil(kallsyms.LoadSymbolAddresses(ksyms)))

var value uint64
qt.Assert(t, qt.IsNil(obj.ArrayMap.Lookup(uint32(0), &value)))
qt.Assert(t, qt.Equals(value, ksyms["socket_file_ops"]))

err = obj.ArrayMap.Lookup(uint32(1), &value)
qt.Assert(t, qt.IsNil(err))
qt.Assert(t, qt.Equals(value, ksyms["tty_fops"]))
}

func TestKsymWeakMissing(t *testing.T) {
file := testutils.NativeFile(t, "testdata/ksym-%s.elf")
spec, err := LoadCollectionSpec(file)
qt.Assert(t, qt.IsNil(err))

var obj struct {
Main *Program `ebpf:"ksym_missing_test"`
}

err = spec.LoadAndAssign(&obj, nil)
testutils.SkipIfNotSupported(t, err)
qt.Assert(t, qt.IsNil(err))
defer obj.Main.Close()

res, _, err := obj.Main.Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
qt.Assert(t, qt.IsNil(err))
qt.Assert(t, qt.Equals(res, 1))
}

func TestKfunc(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.18", "kfunc support")
file := testutils.NativeFile(t, "testdata/kfunc-%s.elf")
Expand Down
53 changes: 53 additions & 0 deletions internal/kallsyms/kallsyms.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package kallsyms
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"sync"
Expand Down Expand Up @@ -52,6 +54,8 @@ func FlushKernelModuleCache() {
kernelModules.kmods = nil
}

var errKsymIsAmbiguous = errors.New("ksym is ambiguous")

func loadKernelModuleMapping(f io.Reader) (map[string]string, error) {
mods := make(map[string]string)
scanner := bufio.NewScanner(f)
Expand All @@ -72,3 +76,52 @@ func loadKernelModuleMapping(f io.Reader) (map[string]string, error) {
}
return mods, nil
}

func LoadSymbolAddresses(symbols map[string]uint64) error {
if len(symbols) == 0 {
return nil
}

f, err := os.Open("/proc/kallsyms")
if err != nil {
return err
}

if err := loadSymbolAddresses(f, symbols); err != nil {
return fmt.Errorf("error loading symbol addresses: %w", err)
}

return nil
}

func loadSymbolAddresses(f io.Reader, symbols map[string]uint64) error {
scan := bufio.NewScanner(f)
ti-mo marked this conversation as resolved.
Show resolved Hide resolved
for scan.Scan() {
var (
addr uint64
t rune
symbol string
)

line := scan.Text()

_, err := fmt.Sscanf(line, "%x %c %s", &addr, &t, &symbol)
if err != nil {
return err
}
// Multiple addresses for a symbol have been found. Lets return an error to not confuse any
// users and handle it the same as libbpf.
if existingAddr, found := symbols[symbol]; existingAddr != 0 {
return fmt.Errorf("symbol %s(0x%x): duplicate found at address 0x%x %w",
symbol, existingAddr, addr, errKsymIsAmbiguous)
} else if found {
symbols[symbol] = addr
}
}

if scan.Err() != nil {
return scan.Err()
}

return nil
}
47 changes: 36 additions & 11 deletions internal/kallsyms/kallsyms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ import (
"github.com/go-quicktest/qt"
)

var kallsyms = []byte(`0000000000000000 t hid_generic_probe [hid_generic]
00000000000000EA t writenote
00000000000000A0 T tcp_connect
00000000000000B0 B empty_zero_page
00000000000000C0 D kimage_vaddr
00000000000000D0 R __start_pci_fixups_early
00000000000000E0 V hv_root_partition
00000000000000F0 W calibrate_delay_is_known
A0000000000000AA a nft_counter_seq [nft_counter]
A0000000000000BA b bootconfig_found
A0000000000000CA d __func__.10
A0000000000000DA r __ksymtab_LZ4_decompress_fast
A0000000000000EA t writenote`)
ti-mo marked this conversation as resolved.
Show resolved Hide resolved

func TestKernelModule(t *testing.T) {
kallsyms := []byte(`0000000000000000 t hid_generic_probe [hid_generic]
0000000000000000 T tcp_connect
0000000000000000 B empty_zero_page
0000000000000000 D kimage_vaddr
0000000000000000 R __start_pci_fixups_early
0000000000000000 V hv_root_partition
0000000000000000 W calibrate_delay_is_known
0000000000000000 a nft_counter_seq [nft_counter]
0000000000000000 b bootconfig_found
0000000000000000 d __func__.10
0000000000000000 r __ksymtab_LZ4_decompress_fast`)
krdr := bytes.NewBuffer(kallsyms)
kmods, err := loadKernelModuleMapping(krdr)
qt.Assert(t, qt.IsNil(err))
Expand All @@ -37,3 +40,25 @@ func TestKernelModule(t *testing.T) {

qt.Assert(t, qt.Equals(kmods["nft_counter_seq"], ""))
}

func TestLoadSymbolAddresses(t *testing.T) {
b := bytes.NewBuffer(kallsyms)
ksyms := map[string]uint64{
"hid_generic_probe": 0,
"tcp_connect": 0,
"bootconfig_found": 0,
}
qt.Assert(t, qt.IsNil(loadSymbolAddresses(b, ksyms)))

qt.Assert(t, qt.Equals(ksyms["hid_generic_probe"], 0))
qt.Assert(t, qt.Equals(ksyms["tcp_connect"], 0xA0))
qt.Assert(t, qt.Equals(ksyms["bootconfig_found"], 0xA0000000000000BA))

b = bytes.NewBuffer(kallsyms)
ksyms = map[string]uint64{
"hid_generic_probe": 0,
"writenote": 0,
}
err := loadSymbolAddresses(b, ksyms)
qt.Assert(t, qt.ErrorIs(err, errKsymIsAmbiguous))
}
36 changes: 36 additions & 0 deletions linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io/fs"
"math"
"slices"
"strings"

"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
Expand Down Expand Up @@ -457,3 +458,38 @@ func resolveKconfigReferences(insns asm.Instructions) (_ *Map, err error) {

return kconfig, nil
}

func resolveKsymReferences(insns asm.Instructions) error {
ti-mo marked this conversation as resolved.
Show resolved Hide resolved
var missing []string

iter := insns.Iterate()
for iter.Next() {
ins := iter.Ins
meta, _ := ins.Metadata.Get(ksymMetaKey{}).(*ksymMeta)
if meta == nil {
continue
}

if meta.Addr != 0 {
ins.Constant = int64(meta.Addr)
continue
}

if meta.Binding == elf.STB_WEAK {
ti-mo marked this conversation as resolved.
Show resolved Hide resolved
// A weak ksym variable in eBPF C means its resolution is optional.
// Set a zero constant explicitly for clarity.
ins.Constant = 0
continue
}

if !slices.Contains(missing, meta.Name) {
missing = append(missing, meta.Name)
}
}

if len(missing) > 0 {
return fmt.Errorf("missing ksyms: %s", strings.Join(missing, ","))
}

return nil
}
4 changes: 4 additions & 0 deletions prog.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
}
defer kconfig.Close()

if err := resolveKsymReferences(insns); err != nil {
return nil, fmt.Errorf("resolve .ksyms: %w", err)
}

if err := fixupAndValidate(insns); err != nil {
return nil, err
}
Expand Down
Binary file added testdata/ksym-eb.elf
Binary file not shown.
Binary file added testdata/ksym-el.elf
Binary file not shown.
Loading