Skip to content
Open
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 @@ -50,6 +50,7 @@ TARGETS := \
testdata/errors \
testdata/variables \
testdata/arena \
testdata/struct_ops \
btf/testdata/relocs \
btf/testdata/relocs_read \
btf/testdata/relocs_read_tgt \
Expand Down
156 changes: 154 additions & 2 deletions elf_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,17 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
case sec.Type == elf.SHT_REL:
// Store relocations under the section index of the target
relSections[elf.SectionIndex(sec.Info)] = sec
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
sections[idx] = newElfSection(sec, programSection)
case sec.Type == elf.SHT_PROGBITS && sec.Size > 0:
if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 {
sections[idx] = newElfSection(sec, programSection)
} else if sec.Name == structOpsLinkSec {
// classification based on sec names so that struct_ops-specific
// sections (.struct_ops.link) is correctly recognized
// as non-executable PROGBITS, allowing value placement and link metadata to be loaded.
sections[idx] = newElfSection(sec, structOpsSection)
} else if sec.Name == structOpsSec {
return nil, fmt.Errorf("section %q: got '.struct_ops' section: %w", sec.Name, ErrNotSupported)
}
}
}

Expand Down Expand Up @@ -186,6 +195,15 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
return nil, fmt.Errorf("load programs: %w", err)
}

// assiociate members in structs with ProgramSpecs using relo
if err := ec.associateStructOpsRelocs(
progs,
relSections,
symbols,
); err != nil {
return nil, fmt.Errorf("load struct_ops: %w", err)
}

return &CollectionSpec{
ec.maps,
progs,
Expand Down Expand Up @@ -239,6 +257,7 @@ const (
btfMapSection
programSection
dataSection
structOpsSection
)

type elfSection struct {
Expand Down Expand Up @@ -349,6 +368,10 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) {
continue
}

if !(sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? if kind == programSection then this should always be true.

continue
}

if len(sec.symbols) == 0 {
return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
}
Expand Down Expand Up @@ -1379,6 +1402,135 @@ func (ec *elfCode) loadKsymsSection() error {
return nil
}

// associateStructOpsRelocs handles `.struct_ops.link`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: need to be refactored.

// and associates the target function with the correct struct member in the map.
func (ec *elfCode) associateStructOpsRelocs(
progs map[string]*ProgramSpec,
relSecs map[elf.SectionIndex]*elf.Section,
symbols []elf.Symbol,
) error {
willAttachToMap := make(map[string]bool)

for secIdx, sec := range ec.sections {
if sec.kind != structOpsSection {
continue
}

userData, err := sec.Data()
if err != nil {
return fmt.Errorf("failed to read section data: %w", err)
}

// Resolve the BTF datasec describing variables in this section.
var ds *btf.Datasec
if err := ec.btf.TypeByName(sec.Name, &ds); err != nil {
return fmt.Errorf("datasec %s: %w", sec.Name, err)
}

// Set flags for .struct_ops.link (BPF_F_LINK).
flags := uint32(0)
if sec.Name == structOpsLinkSec {
flags = sys.BPF_F_LINK
}

type structOpsMapOfsSize struct {
userOff uint64
userSize uint64
}

ofsSizes := make(map[string]structOpsMapOfsSize)

for _, vsi := range ds.Vars {
varType := btf.UnderlyingType(vsi.Type).(*btf.Var)
mapName := varType.Name

userType, ok := btf.UnderlyingType(varType.Type).(*btf.Struct)
if !ok {
return fmt.Errorf("var %s: expect struct, got %T", varType.Name, varType.Type)
}

userSize := uint64(userType.Size)
userOff := uint64(vsi.Offset)
if userOff+userSize > uint64(len(userData)) {
return fmt.Errorf("%s exceeds section", mapName)
}

// Register the MapSpec for this struct_ops instance.
ec.maps[mapName] = &MapSpec{
Name: mapName,
Type: StructOpsMap,
Key: &btf.Int{Size: 4},
KeySize: 4,
Value: userType,
Flags: flags,
MaxEntries: 1,
Contents: []MapKV{
{
Key: uint32(0),
Value: append([]byte(nil), userData[userOff:userOff+userSize]...),
},
},
}
ofsSizes[mapName] =
structOpsMapOfsSize{userOff, userSize}
}

// Process relo sections that target this struct_ops section.
for relSecIdx, relSec := range relSecs {
if elf.SectionIndex(relSec.Info) != elf.SectionIndex(secIdx) {
continue
}

if !(relSec.Type == elf.SHT_REL) {
continue
}

// Load relocation entries (offset -> symbol).
rels, err := ec.loadSectionRelocations(relSec, symbols)
if err != nil {
return fmt.Errorf("failed to load relocations for section %s (relIdx=%d -> target=%d): %w",
relSec.Name, relSecIdx, secIdx, err)
}

for relOff, sym := range rels {
var ms *MapSpec
var msName string
var baseOff uint64

for mapName, ofsSz := range ofsSizes {
if relOff >= ofsSz.userOff && relOff < ofsSz.userOff+ofsSz.userSize {
baseOff = ofsSz.userOff
ms = ec.maps[mapName]
msName = mapName
break
}
}

if ms == nil || ms.Type != StructOpsMap {
return fmt.Errorf("struct_ops map %s not found or wrong type", msName)
}

userSt, ok := btf.As[*btf.Struct](ms.Value)
if !ok {
return fmt.Errorf("map %s value is not a btf.Struct", ms.Name)
}

moff := btf.Bits((relOff - baseOff) * 8)
if memberName, ok := structOpsFuncPtrMemberAtOffset(userSt, moff); ok {
p, ok := progs[sym.Name]
if !(ok && p.Type == StructOps) {
return fmt.Errorf("program %s not found or not StructOps", sym.Name)
}
p.AttachTo = userSt.Name + ":" + memberName
willAttachToMap[sym.Name] = true
}
}
}
}

return nil
}

type libbpfElfSectionDef struct {
pattern string
programType sys.ProgType
Expand Down
105 changes: 99 additions & 6 deletions elf_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"syscall"
"testing"

"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/kallsyms"
Expand Down Expand Up @@ -932,6 +933,101 @@ func TestArena(t *testing.T) {
mustNewCollection(t, coll, nil)
}

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

userData := []byte{
// test_1 func ptr (8B)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// test_2 func ptr (8B)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// data (4B) + padding (4B)
0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00,
}

want := &CollectionSpec{
Maps: map[string]*MapSpec{
"testmod_ops": {
Name: "testmod_ops",
Type: StructOpsMap,
MaxEntries: 1,
Flags: sys.BPF_F_LINK,
Key: &btf.Int{Size: 4},
KeySize: 4,
Value: &btf.Struct{
Name: "bpf_testmod_ops",
Size: 24,
Members: []btf.Member{
{
Name: "test_1",
Type: &btf.Pointer{
Target: &btf.FuncProto{
Params: []btf.FuncParam{},
Return: &btf.Int{Name: "int", Size: 4, Encoding: btf.Signed}}},
Offset: 0,
},
{
Name: "test_2",
Type: &btf.Pointer{
Target: &btf.FuncProto{
Params: []btf.FuncParam{
{Type: &btf.Int{Name: "int", Size: 4, Encoding: btf.Signed}},
{Type: &btf.Int{Name: "int", Size: 4, Encoding: btf.Signed}},
},
Return: (*btf.Void)(nil),
},
},
Offset: 64,
},
{
Name: "data",
Type: &btf.Int{Name: "int", Size: 4, Encoding: btf.Signed},
Offset: 128, // bits
},
},
},
Contents: []MapKV{
{
Key: uint32(0),
Value: userData,
},
},
},
},
Programs: map[string]*ProgramSpec{
"test_1": {
Name: "test_1",
Type: StructOps,
AttachTo: "bpf_testmod_ops:test_1",
License: "GPL",
SectionName: "struct_ops/test_1",
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
},
},
Variables: map[string]*VariableSpec{},
}

testModOps, ok := coll.Maps["testmod_ops"]
if !ok {
t.Fatalf("testmod_ops doesn't exist")
}

data, ok := testModOps.Contents[0].Value.([]byte)
if !ok {
t.Fatalf("Contents[0].Value should be an array of byte")
}

qt.Assert(t, qt.CmpEquals(coll.Programs, want.Programs, csCmpOpts))
qt.Assert(t, qt.CmpEquals(coll.Maps, want.Maps, csCmpOpts))
qt.Assert(t, qt.CmpEquals(testModOps.Value, want.Maps["testmod_ops"].Value, csCmpOpts))
qt.Assert(t, qt.CmpEquals(data, userData, csCmpOpts))
}

var (
elfPath = flag.String("elfs", os.Getenv("CI_KERNEL_SELFTESTS"), "`Path` containing libbpf-compatible ELFs (defaults to $CI_KERNEL_SELFTESTS)")
elfPattern = flag.String("elf-pattern", "*.o", "Glob `pattern` for object files that should be tested")
Expand Down Expand Up @@ -968,6 +1064,9 @@ func TestLibBPFCompat(t *testing.T) {
t.Fatal("Expected an error during load")
}
} else if err != nil {
if errors.Is(err, errUnknownStructOps) {
t.Skip("Skipping since the struct_ops target doesn't exist in kernel")
}
t.Fatal("Error during loading:", err)
}
}
Expand Down Expand Up @@ -1061,12 +1160,6 @@ func TestLibBPFCompat(t *testing.T) {
}
}

for _, ps := range spec.Programs {
if ps.Type == StructOps {
ps.AttachTo = ""
}
}

coreFiles := sourceOfBTF(t, path)
if len(coreFiles) == 0 {
// NB: test_core_reloc_kernel.o doesn't have dedicated BTF and
Expand Down
4 changes: 2 additions & 2 deletions elf_sections.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions prog.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ var errBadRelocation = errors.New("bad CO-RE relocation")
// This error is detected based on heuristics and therefore may not be reliable.
var errUnknownKfunc = errors.New("unknown kfunc")

// errUnknownStructOps is returned when the struct_ops target doesn't exist in kernel
var errUnknownStructOps = errors.New("unknown struct_ops target")

// ProgramID represents the unique ID of an eBPF program.
type ProgramID = sys.ProgramID

Expand Down
Loading
Loading