Skip to content

Commit

Permalink
elf_reader: initial __kconfig support for LINUX_KERNEL_VERSION
Browse files Browse the repository at this point in the history
This commit is a first step towards __kconfig support. When a .kconfig Datasec
is detected, a .kconfig map is created and emitted into the CollectionSpec.
Instructions referring to __kconfig variables will be rewritten to refer to
the .kconfig map during ELF load.

Its contents will only be initialized when loading the spec into the kernel.
All variables specified in the .kconfig Datasec are resolved based on their
names, and the results are written to .kconfig's Contents.

For the moment, it only enables using LINUX_KERNEL_VERSION.

Signed-off-by: Francis Laniel <[email protected]>
Co-authored-by: Timo Beckers <[email protected]>
  • Loading branch information
eiffel-fl and ti-mo committed Mar 3, 2023
1 parent 45dd723 commit b673899
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 2 deletions.
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/map_spin_lock \
testdata/subprog_reloc \
testdata/fwd_decl \
testdata/kconfig \
btf/testdata/relocs \
btf/testdata/relocs_read \
btf/testdata/relocs_read_tgt
Expand Down
50 changes: 50 additions & 0 deletions collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
)

// CollectionOptions control loading a collection into the kernel.
Expand Down Expand Up @@ -532,6 +533,12 @@ func (cl *collectionLoader) populateMaps() error {
return fmt.Errorf("missing map spec %s", mapName)
}

if mapName == kconfigMap {
if err := resolveKconfig(mapSpec); err != nil {
return fmt.Errorf("resolving kconfig: %w", err)
}
}

// MapSpecs that refer to inner maps or programs within the same
// CollectionSpec do so using strings. These strings are used as the key
// to look up the respective object in the Maps or Programs fields.
Expand Down Expand Up @@ -575,6 +582,49 @@ func (cl *collectionLoader) populateMaps() error {
return nil
}

// resolveKconfig resolves all variables declared in .kconfig and populates
// m.Contents. Does nothing if the given m.Contents is non-empty.
func resolveKconfig(m *MapSpec) error {
// Allow caller to manually populate .kconfig contents for testing purposes.
if len(m.Contents) != 0 {
return nil
}

ds, ok := m.Value.(*btf.Datasec)
if !ok {
return errors.New("map value is not a Datasec")
}

data := make([]byte, ds.Size)
for _, vsi := range ds.Vars {
v := vsi.Type.(*btf.Var)
s, err := btf.Sizeof(v.Type)
if err != nil {
return fmt.Errorf("variable %s: getting size: %w", v.Name, err)
}

switch n := v.TypeName(); n {
case "LINUX_KERNEL_VERSION":
if s != 4 {
return fmt.Errorf("variable %s must be u32, got %d", n, s)
}

kv, err := internal.KernelVersion()
if err != nil {
return fmt.Errorf("getting kernel version: %w", err)
}
internal.NativeEndian.PutUint32(data[vsi.Offset:], kv.Kernel())

default:
return fmt.Errorf("unsupported kconfig: %s", n)
}
}

m.Contents = []MapKV{{uint32(0), data}}

return nil
}

// LoadCollection reads an object file and creates and loads its declared
// resources into the kernel.
//
Expand Down
79 changes: 78 additions & 1 deletion elf_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/cilium/ebpf/internal/unix"
)

const kconfigMap = ".kconfig"

// 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 Down Expand Up @@ -140,6 +142,10 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
return nil, fmt.Errorf("load data sections: %w", err)
}

if err := ec.loadVirtualDataSections(); err != nil {
return nil, fmt.Errorf("load virtual data sections: %w", err)
}

// Finally, collect programs and link them.
progs, err := ec.loadProgramSections()
if err != nil {
Expand Down Expand Up @@ -566,6 +572,10 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
return fmt.Errorf("neither a call nor a load instruction: %v", ins)
}

// The Undefined section is used for 'virtual' symbols that aren't backed by
// an ELF section. This includes symbol references from inline asm, forward
// function declarations, as well as extern kfunc declarations using __ksym
// and extern kconfig variables declared using __kconfig.
case undefSection:
if bind != elf.STB_GLOBAL {
return fmt.Errorf("asm relocation: %s: unsupported binding: %s", name, bind)
Expand All @@ -575,7 +585,39 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
return fmt.Errorf("asm relocation: %s: unsupported type %s", name, typ)
}

// There is nothing to do here but set ins.Reference.
// 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
// remain standard symbol references.
kc := ec.maps[kconfigMap]
if kc == nil {
break
}

// extern __kconfig reads are represented as dword loads that need to be
// rewritten to pseudo map loads from .kconfig. If the map is present,
// require it to contain the symbol to disambiguate between inline asm
// relos and kconfigs.
if ins.OpCode.IsDWordLoad() {
var found bool
for _, vsi := range kc.Value.(*btf.Datasec).Vars {
if vsi.Type.(*btf.Var).Name != rel.Name {
continue
}

// Encode a map read at the offset of the var in the datasec.
ins.Constant = int64(uint64(vsi.Offset) << 32)
ins.Src = asm.PseudoMapValue
name = kconfigMap

found = true
break
}

if !found {
return fmt.Errorf("kconfig %s not found in %s", rel.Name, kconfigMap)
}
}

default:
return fmt.Errorf("relocation to %q: %w", target.Name, ErrNotSupported)
Expand Down Expand Up @@ -1067,6 +1109,41 @@ func (ec *elfCode) loadDataSections() error {

ec.maps[sec.Name] = mapSpec
}

return nil
}

// loadVirtualDataSections handles 'virtual' Datasecs like .kconfig that don't
// have corresponding ELF sections and exist purely in BTF.
func (ec *elfCode) loadVirtualDataSections() error {
if ec.btf == nil {
return nil
}

var ds *btf.Datasec
err := ec.btf.TypeByName(kconfigMap, &ds)
if errors.Is(err, btf.ErrNotFound) {
return nil
}
if err != nil {
return err
}

if ds.Size == 0 {
return errors.New("zero-length .kconfig")
}

ec.maps[kconfigMap] = &MapSpec{
Name: kconfigMap,
Type: Array,
KeySize: uint32(4),
ValueSize: ds.Size,
MaxEntries: 1,
Flags: unix.BPF_F_RDONLY_PROG | unix.BPF_F_MMAPABLE,
Key: &btf.Int{Size: 4},
Value: ds,
}

return nil
}

Expand Down
44 changes: 43 additions & 1 deletion elf_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func TestLoadCollectionSpec(t *testing.T) {
}

if ret != 7 {
t.Error("Expected return value to be 5, got", ret)
t.Error("Unexpected return value:", ret)
}
})
}
Expand Down Expand Up @@ -573,6 +573,46 @@ func TestTailCall(t *testing.T) {
})
}

func TestKconfig(t *testing.T) {
testutils.Files(t, testutils.Glob(t, "testdata/kconfig-*.elf"), func(t *testing.T, file string) {
spec, err := LoadCollectionSpec(file)
if err != nil {
t.Fatal(err)
}

if spec.ByteOrder != internal.NativeEndian {
return
}

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

err = spec.LoadAndAssign(&obj, nil)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}
defer obj.Main.Close()

ret, _, err := obj.Main.Test(internal.EmptyBPFContext)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}

v, err := internal.KernelVersion()
if err != nil {
t.Fatalf("getting kernel version: %s", err)
}

version := v.Kernel()
if ret != version {
t.Fatalf("Expected eBPF to return value %d, got %d", version, ret)
}
})
}

func TestSubprogRelocation(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.13", "bpf_for_each_map_elem")

Expand Down Expand Up @@ -774,6 +814,8 @@ func TestLibBPFCompat(t *testing.T) {
t.Skip("Skipping due to possible bug in upstream CO-RE generation")
case "test_usdt.o", "test_usdt.linked3.o", "test_urandom_usdt.o", "test_urandom_usdt.linked3.o", "test_usdt_multispec.o":
t.Skip("Skipping due to missing support for usdt.bpf.h")
case "bpf_cubic.o", "bpf_iter_tcp6.o", "bpf_iter_tcp6.linked3.o", "bpf_iter_tcp4.o", "bpf_iter_tcp4.linked3.o", "bpf_iter_ipv6_route.o", "bpf_iter_ipv6_route.linked3.o", "test_subskeleton_lib.o", "test_skeleton.o", "test_skeleton.linked3.o", "test_subskeleton_lib.linked3.o", "test_subskeleton.o", "test_subskeleton.linked3.o", "test_core_extern.linked3.o", "test_core_extern.o", "profiler1.linked3.o", "profiler3.o", "profiler3.linked3.o", "profiler2.o", "profiler2.linked3.o", "profiler1.o":
t.Skip("Skipping due to using CONFIG_* variable which are not supported at the moment")
}

t.Parallel()
Expand Down
2 changes: 2 additions & 0 deletions testdata/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ typedef unsigned long uint64_t;
#define __type(name, val) typeof(val) *name
#define __array(name, val) typeof(val) *name[]

#define __kconfig __attribute__((section(".kconfig")))

#define BPF_MAP_TYPE_HASH (1)
#define BPF_MAP_TYPE_ARRAY (2)
#define BPF_MAP_TYPE_PROG_ARRAY (3)
Expand Down
Binary file added testdata/kconfig-eb.elf
Binary file not shown.
Binary file added testdata/kconfig-el.elf
Binary file not shown.
9 changes: 9 additions & 0 deletions testdata/kconfig.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "common.h"

char __license[] __section("license") = "MIT";

extern int LINUX_KERNEL_VERSION __kconfig;

__section("socket") int kconfig() {
return LINUX_KERNEL_VERSION;
}

0 comments on commit b673899

Please sign in to comment.