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
7 changes: 1 addition & 6 deletions cmd/bpf2go/gen/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@ func collectMapTypes(types []btf.Type, maps map[string]*ebpf.MapSpec) []btf.Type
// collectVariableTypes collects all types used by VariableSpecs.
func collectVariableTypes(types []btf.Type, vars map[string]*ebpf.VariableSpec) []btf.Type {
for _, vs := range vars {
v := vs.Type()
if v == nil {
continue
}

types = addType(types, v.Type)
types = addType(types, vs.Type.Type)
}

return types
Expand Down
47 changes: 24 additions & 23 deletions collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
}

for name, spec := range cs.Variables {
cpy.Variables[name] = spec.copy(&cpy)
cpy.Variables[name] = spec.Copy()
}
if cs.Variables == nil {
cpy.Variables = nil
Expand Down Expand Up @@ -170,7 +170,7 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error
continue
}

if !v.Constant() {
if !strings.HasPrefix(v.MapName, ".rodata") {
return fmt.Errorf("variable %s is not a constant", n)
}

Expand Down Expand Up @@ -514,6 +514,19 @@ func (cl *collectionLoader) loadMap(mapName string) (*Map, error) {
return m, nil
}

var vars []*VariableSpec
for _, vs := range cl.coll.Variables {
if vs.MapName != mapName {
continue
}

vars = append(vars, vs)
}

if err := mapSpec.updateDataSection(vars); err != nil {
return nil, fmt.Errorf("assembling contents: %w", err)
}

m, err := newMapWithOptions(mapSpec, cl.opts.Maps, cl.types)
if err != nil {
return nil, fmt.Errorf("map %s: %w", mapName, err)
Expand Down Expand Up @@ -597,19 +610,7 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) {
return nil, fmt.Errorf("unknown variable %s", varName)
}

// Get the key of the VariableSpec's MapSpec in the CollectionSpec.
var mapName string
for n, ms := range cl.coll.Maps {
if ms == varSpec.m {
mapName = n
break
}
}
if mapName == "" {
return nil, fmt.Errorf("variable %s: underlying MapSpec %s was removed from CollectionSpec", varName, varSpec.m.Name)
}

m, err := cl.loadMap(mapName)
m, err := cl.loadMap(varSpec.MapName)
if err != nil {
return nil, fmt.Errorf("variable %s: %w", varName, err)
}
Expand All @@ -626,14 +627,14 @@ func (cl *collectionLoader) loadVariable(varName string) (*Variable, error) {
mm, err = m.Memory()
}
if err != nil && !errors.Is(err, ErrNotSupported) {
return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, mapName, err)
return nil, fmt.Errorf("variable %s: getting memory for map %s: %w", varName, varSpec.MapName, err)
}

v, err := newVariable(
varSpec.name,
varSpec.offset,
varSpec.size,
varSpec.t,
varSpec.Name,
varSpec.Offset,
len(varSpec.Value),
varSpec.Type,
mm,
)
if err != nil {
Expand Down Expand Up @@ -715,9 +716,9 @@ func (cl *collectionLoader) populateStructOps(m *Map, mapSpec *MapSpec) error {
return fmt.Errorf("value should be a *Struct")
}

userData, ok := mapSpec.Contents[0].Value.([]byte)
if !ok {
return fmt.Errorf("value should be an array of byte")
userData, err := mapSpec.dataSection()
if err != nil {
return fmt.Errorf("getting data section: %w", err)
}
if len(userData) < int(userType.Size) {
return fmt.Errorf("user data too short: have %d, need at least %d", len(userData), userType.Size)
Expand Down
7 changes: 3 additions & 4 deletions collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,9 @@ func TestCollectionSpecCopy(t *testing.T) {
},
map[string]*VariableSpec{
"my-var": {
name: "my-var",
offset: 0,
size: 4,
m: ms,
Name: "my-var",
MapName: "my-map",
Offset: 0,
},
},
&btf.Spec{},
Expand Down
35 changes: 23 additions & 12 deletions elf_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -1210,27 +1210,30 @@ func (ec *elfCode) loadDataSections() error {
mapSpec.Flags = sys.BPF_F_RDONLY_PROG
}

var data []byte
switch sec.Type {
// Only open the section if we know there's actual data to be read.
case elf.SHT_PROGBITS:
data, err := sec.Data()
var err error
data, err = sec.Data()
if err != nil {
return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
}
mapSpec.Contents = []MapKV{{uint32(0), data}}

case elf.SHT_NOBITS:
// NOBITS sections like .bss contain only zeroes and are not allocated in
// the ELF. Since data sections are Arrays, the kernel can preallocate
// them. Don't attempt reading zeroes from the ELF, instead allocate the
// zeroed memory to support getting and setting VariableSpecs for sections
// like .bss.
mapSpec.Contents = []MapKV{{uint32(0), make([]byte, sec.Size)}}
data = make([]byte, sec.Size)

default:
return fmt.Errorf("data section %s: unknown section type %s", sec.Name, sec.Type)
}

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

for off, sym := range sec.symbols {
// Skip symbols marked with the 'hidden' attribute.
if elf.ST_VISIBILITY(sym.Other) == elf.STV_HIDDEN ||
Expand Down Expand Up @@ -1259,11 +1262,19 @@ func (ec *elfCode) loadDataSections() error {
continue
}

if off+sym.Size > uint64(len(data)) {
return fmt.Errorf("data section %s: variable %s exceeds section bounds", sec.Name, sym.Name)
}

if off > math.MaxInt {
return fmt.Errorf("data section %s: variable %s offset %d exceeds maximum", sec.Name, sym.Name, off)
}

ec.vars[sym.Name] = &VariableSpec{
name: sym.Name,
offset: off,
size: sym.Size,
m: mapSpec,
MapName: sec.Name,
Name: sym.Name,
Offset: int(off),
Value: slices.Clone(data[off : off+sym.Size]),
}
}

Expand Down Expand Up @@ -1294,17 +1305,17 @@ func (ec *elfCode) loadDataSections() error {
continue
}

if uint64(v.Offset) != ev.offset {
return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.offset)
if v.Offset != uint32(ev.Offset) {
return fmt.Errorf("data section %s: variable %s datasec offset (%d) doesn't match ELF symbol offset (%d)", sec.Name, name, v.Offset, ev.Offset)
}

if uint64(v.Size) != ev.size {
return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, ev.size)
if v.Size != uint32(len(ev.Value)) {
return fmt.Errorf("data section %s: variable %s size in datasec (%d) doesn't match ELF symbol size (%d)", sec.Name, name, v.Size, len(ev.Value))
}

// Decouple the Var in the VariableSpec from the underlying DataSec in
// the MapSpec to avoid modifications from affecting map loads later on.
ev.t = btf.Copy(vt).(*btf.Var)
ev.Type = btf.Copy(vt).(*btf.Var)
}
}
}
Expand Down
23 changes: 9 additions & 14 deletions elf_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,11 @@ var csCmpOpts = cmp.Options{
}
return false
}),
cmp.Comparer(func(a, b *VariableSpec) bool {
if a.name != b.name || a.offset != b.offset || a.size != b.size {
return false
}
return true
}),
cmpopts.IgnoreTypes(btf.Spec{}),
cmpopts.IgnoreFields(CollectionSpec{}, "ByteOrder", "Types"),
cmpopts.IgnoreFields(ProgramSpec{}, "Instructions", "ByteOrder"),
cmpopts.IgnoreFields(MapSpec{}, "Key", "Value", "Contents"),
cmpopts.IgnoreFields(VariableSpec{}, "Type", "Value"),
cmpopts.IgnoreUnexported(ProgramSpec{}),
}

Expand Down Expand Up @@ -224,14 +219,14 @@ func TestLoadCollectionSpec(t *testing.T) {
},
},
Variables: map[string]*VariableSpec{
"arg": {name: "arg", offset: 4, size: 4},
"arg2": {name: "arg2", offset: 0, size: 4},
"arg3": {name: "arg3", offset: 0, size: 4},
"key1": {name: "key1", offset: 0, size: 4},
"key2": {name: "key2", offset: 0, size: 4},
"key3": {name: "key3", offset: 0, size: 4},
"neg": {name: "neg", offset: 12, size: 4},
"uneg": {name: "uneg", offset: 8, size: 4},
"arg": {Name: "arg", MapName: ".rodata", Offset: 4},
"arg2": {Name: "arg2", MapName: ".rodata.test", Offset: 0},
"arg3": {Name: "arg3", MapName: ".data.test", Offset: 0},
"key1": {Name: "key1", MapName: ".bss", Offset: 0},
"key2": {Name: "key2", MapName: ".data", Offset: 0},
"key3": {Name: "key3", MapName: ".rodata", Offset: 0},
"neg": {Name: "neg", MapName: ".rodata", Offset: 12},
"uneg": {Name: "uneg", MapName: ".rodata", Offset: 8},
},
}

Expand Down
4 changes: 0 additions & 4 deletions helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,5 @@ func fixupCollectionSpec(spec *CollectionSpec) *CollectionSpec {
spec.Programs[name] = fixupProgramSpec(spec.Programs[name])
}

for _, varSpec := range spec.Variables {
varSpec.m = spec.Maps[varSpec.m.Name]
}

return spec
}
60 changes: 47 additions & 13 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"path/filepath"
"reflect"
"slices"
"sort"
"strings"
"sync"
"time"
Expand All @@ -29,7 +30,6 @@ var (
ErrKeyExist = errors.New("key already exists")
ErrIterationAborted = errors.New("iteration aborted")
ErrMapIncompatible = errors.New("map spec is incompatible with existing map")
errMapNoBTFValue = errors.New("map spec does not contain a BTF Value")

// pre-allocating these errors here since they may get called in hot code paths
// and cause unnecessary memory allocations
Expand Down Expand Up @@ -188,27 +188,61 @@ func (spec *MapSpec) fixupMagicFields() (*MapSpec, error) {
}

// dataSection returns the contents and BTF Datasec descriptor of the spec.
func (ms *MapSpec) dataSection() ([]byte, *btf.Datasec, error) {
if ms.Value == nil {
return nil, nil, errMapNoBTFValue
func (ms *MapSpec) dataSection() ([]byte, error) {
if n := len(ms.Contents); n != 1 {
return nil, fmt.Errorf("expected one key, found %d", n)
}

kv := ms.Contents[0]
if key, ok := ms.Contents[0].Key.(uint32); !ok || key != 0 {
return nil, fmt.Errorf("expected contents to have key 0")
}

ds, ok := ms.Value.(*btf.Datasec)
value, ok := kv.Value.([]byte)
if !ok {
return nil, nil, fmt.Errorf("map value BTF is a %T, not a *btf.Datasec", ms.Value)
return nil, fmt.Errorf("value at first map key is %T, not []byte", kv.Value)
}

if n := len(ms.Contents); n != 1 {
return nil, nil, fmt.Errorf("expected one key, found %d", n)
return value, nil
}

// updateDataSection copies the values of vars into MapSpec.Contents[0].Value.
//
// vars is sorted in place by offset.
func (ms *MapSpec) updateDataSection(vars []*VariableSpec) error {
if len(vars) == 0 {
return nil
}

kv := ms.Contents[0]
value, ok := kv.Value.([]byte)
if !ok {
return nil, nil, fmt.Errorf("value at first map key is %T, not []byte", kv.Value)
data, err := ms.dataSection()
if err != nil {
return err
}

// Do not modify the original data slice, ms.Contents is a shallow copy.
data = slices.Clone(data)

sort.Slice(vars, func(i, j int) bool {
return vars[i].Offset < vars[j].Offset
})

offset := 0
for _, v := range vars {
if v.Offset < offset {
return fmt.Errorf("variable %s overlaps with previous variable", v.Name)
}

end := v.Offset + len(v.Value)
if end > len(data) {
return fmt.Errorf("variable %s exceeds map size", v.Name)
}

copy(data[v.Offset:end], v.Value)
offset = end
}

return value, ds, nil
ms.Contents = []MapKV{{Key: uint32(0), Value: data}}
return nil
}

func (ms *MapSpec) readOnly() bool {
Expand Down
Loading
Loading