Skip to content

Commit ec9c39e

Browse files
committed
elf_reader: add struct_ops support
This commit adds struct_ops support to the ELF reader: it classifies non-executable PROGBITS sections, parses their BTF Datasec to build MapSpecs, associates relocs with func-pointer members to set ps.AttachTo, and adds TestStructOps. Related: cilium#1845 Signed-off-by: shun159 <[email protected]>
1 parent 7c861fd commit ec9c39e

File tree

6 files changed

+320
-13
lines changed

6 files changed

+320
-13
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ TARGETS := \
5050
testdata/errors \
5151
testdata/variables \
5252
testdata/arena \
53+
testdata/struct_ops \
5354
btf/testdata/relocs \
5455
btf/testdata/relocs_read \
5556
btf/testdata/relocs_read_tgt \

elf_reader.go

Lines changed: 206 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,30 @@ type ksymMeta struct {
4141
Name string
4242
}
4343

44+
type structOpsSpec struct {
45+
name string
46+
// section index of .struct_ops / .struct_ops.link
47+
secIdx elf.SectionIndex
48+
// byte offset of the variable in that section
49+
userOff uint64
50+
userSize uint64
51+
}
52+
4453
// elfCode is a convenience to reduce the amount of arguments that have to
4554
// be passed around explicitly. You should treat its contents as immutable.
4655
type elfCode struct {
4756
*internal.SafeELFFile
48-
sections map[elf.SectionIndex]*elfSection
49-
license string
50-
version uint32
51-
btf *btf.Spec
52-
extInfo *btf.ExtInfos
53-
maps map[string]*MapSpec
54-
vars map[string]*VariableSpec
55-
kfuncs map[string]*btf.Func
56-
ksyms map[string]struct{}
57-
kconfig *MapSpec
57+
sections map[elf.SectionIndex]*elfSection
58+
license string
59+
version uint32
60+
btf *btf.Spec
61+
extInfo *btf.ExtInfos
62+
maps map[string]*MapSpec
63+
vars map[string]*VariableSpec
64+
kfuncs map[string]*btf.Func
65+
ksyms map[string]struct{}
66+
kconfig *MapSpec
67+
structOps map[string]*structOpsSpec
5868
}
5969

6070
// LoadCollectionSpec parses an ELF file into a CollectionSpec.
@@ -116,8 +126,15 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
116126
case sec.Type == elf.SHT_REL:
117127
// Store relocations under the section index of the target
118128
relSections[elf.SectionIndex(sec.Info)] = sec
119-
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
120-
sections[idx] = newElfSection(sec, programSection)
129+
case sec.Type == elf.SHT_PROGBITS:
130+
if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 {
131+
sections[idx] = newElfSection(sec, programSection)
132+
} else if sec.Name == ".struct_ops" || sec.Name == ".struct_ops.link" {
133+
//classification based on sec names so that struct_ops-specific
134+
// sections (.struct_ops, .struct_ops.link) are correctly recognized
135+
// as non-executable PROGBITS, allowing value placement and link metadata to be loaded.
136+
sections[idx] = newElfSection(sec, structOpsSection)
137+
}
121138
}
122139
}
123140

@@ -147,6 +164,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
147164
vars: make(map[string]*VariableSpec),
148165
kfuncs: make(map[string]*btf.Func),
149166
ksyms: make(map[string]struct{}),
167+
structOps: make(map[string]*structOpsSpec),
150168
}
151169

152170
symbols, err := f.Symbols()
@@ -164,6 +182,10 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
164182
return nil, fmt.Errorf("load maps: %w", err)
165183
}
166184

185+
if err := ec.loadStructOpsMaps(); err != nil {
186+
return nil, fmt.Errorf("struct_ops maps: %w", err)
187+
}
188+
167189
if err := ec.loadBTFMaps(); err != nil {
168190
return nil, fmt.Errorf("load BTF maps: %w", err)
169191
}
@@ -186,6 +208,15 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
186208
return nil, fmt.Errorf("load programs: %w", err)
187209
}
188210

211+
// assiociate members in structs with ProgramSpecs using relo
212+
if err := ec.associateStructOpsRelocs(
213+
progs,
214+
relSections,
215+
symbols,
216+
); err != nil {
217+
return nil, fmt.Errorf("load struct_ops: %w", err)
218+
}
219+
189220
return &CollectionSpec{
190221
ec.maps,
191222
progs,
@@ -239,6 +270,7 @@ const (
239270
btfMapSection
240271
programSection
241272
dataSection
273+
structOpsSection
242274
)
243275

244276
type elfSection struct {
@@ -349,6 +381,10 @@ func (ec *elfCode) loadProgramSections() (map[string]*ProgramSpec, error) {
349381
continue
350382
}
351383

384+
if !(sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0) {
385+
continue
386+
}
387+
352388
if len(sec.symbols) == 0 {
353389
return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
354390
}
@@ -564,7 +600,7 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err
564600
ins.Constant = int64(uint64(offset) << 32)
565601
ins.Src = asm.PseudoMapValue
566602

567-
case programSection:
603+
case programSection, structOpsSection:
568604
switch opCode := ins.OpCode; {
569605
case opCode.JumpOp() == asm.Call:
570606
if ins.Src != asm.PseudoCall {
@@ -1379,6 +1415,163 @@ func (ec *elfCode) loadKsymsSection() error {
13791415
return nil
13801416
}
13811417

1418+
// loadStructOpsMapsFromSections creates StructOps MapSpecs from DataSec sections
1419+
// ".struct_ops" and ".struct_ops.link" found in the object BTF.
1420+
func (ec *elfCode) loadStructOpsMaps() error {
1421+
for secIdx, sec := range ec.sections {
1422+
if sec.kind != structOpsSection {
1423+
continue
1424+
}
1425+
1426+
// Process the struct_ops section to create the map
1427+
dataType, err := ec.btf.AnyTypeByName(sec.Name)
1428+
if err != nil {
1429+
return fmt.Errorf("datasec %s: %w", sec.Name, err)
1430+
}
1431+
1432+
dataSec, ok := btf.As[*btf.Datasec](dataType)
1433+
if !ok {
1434+
return fmt.Errorf("%s BTF is not a Datasec", sec.Name)
1435+
}
1436+
1437+
for _, vsi := range dataSec.Vars {
1438+
varType, ok := btf.As[*btf.Var](vsi.Type)
1439+
if !ok {
1440+
return fmt.Errorf("var type in %s: want *btf.Var, got %T", sec.Name, btf.UnderlyingType(vsi.Type))
1441+
}
1442+
mapName := varType.Name
1443+
1444+
userType, ok := btf.UnderlyingType(varType.Type).(*btf.Struct)
1445+
if !ok {
1446+
return fmt.Errorf("var %s: expect struct, got %T", varType.Name, varType.Type)
1447+
}
1448+
1449+
// Retrieve raw data from the ELF section.
1450+
// This data contains the initial values for the struct_ops map.
1451+
userData, err := sec.Data()
1452+
if err != nil {
1453+
return fmt.Errorf("failed to read section data: %w", err)
1454+
}
1455+
1456+
flags := uint32(0)
1457+
if sec.Name == ".struct_ops.link" {
1458+
flags = sys.BPF_F_LINK
1459+
}
1460+
1461+
userSize := uint64(userType.Size)
1462+
userOff := uint64(vsi.Offset)
1463+
if userOff+userSize > uint64(len(userData)) {
1464+
return fmt.Errorf("%s exceeds section", mapName)
1465+
}
1466+
1467+
ec.structOps[mapName] =
1468+
&structOpsSpec{
1469+
name: mapName,
1470+
secIdx: secIdx,
1471+
userOff: userOff,
1472+
userSize: userSize,
1473+
}
1474+
1475+
ec.maps[mapName] =
1476+
&MapSpec{
1477+
Name: mapName,
1478+
Type: StructOpsMap,
1479+
Key: &btf.Int{Size: 4},
1480+
Value: userType,
1481+
Flags: flags,
1482+
MaxEntries: 1,
1483+
Contents: []MapKV{
1484+
{
1485+
Key: uint32(0),
1486+
Value: append([]byte(nil), userData[userOff:userOff+userSize]...),
1487+
},
1488+
},
1489+
}
1490+
}
1491+
}
1492+
1493+
return nil
1494+
}
1495+
1496+
// associateStructOpsRelocs handles `.struct_ops(.link)`
1497+
// and associates the target function with the correct struct member in the map.
1498+
func (ec *elfCode) associateStructOpsRelocs(
1499+
progs map[string]*ProgramSpec,
1500+
relSecs map[elf.SectionIndex]*elf.Section,
1501+
symbols []elf.Symbol,
1502+
) error {
1503+
for _, sec := range relSecs {
1504+
if !strings.HasPrefix(sec.Name, ".rel") {
1505+
continue
1506+
}
1507+
1508+
targetIdx := elf.SectionIndex(sec.Info)
1509+
targetSec, ok := ec.sections[targetIdx]
1510+
if !(ok && strings.HasPrefix(targetSec.Name, ".struct_ops")) {
1511+
continue
1512+
}
1513+
1514+
// Load the relocations from the relocation section
1515+
rels, err := ec.loadSectionRelocations(sec, symbols)
1516+
if err != nil {
1517+
return fmt.Errorf("failed to load relocations for section %s: %w", sec.Name, err)
1518+
}
1519+
1520+
for relOff, sym := range rels {
1521+
var ms *MapSpec
1522+
var meta *structOpsSpec
1523+
1524+
for _, mapSpec := range ec.maps {
1525+
if mapSpec.Type != StructOpsMap || len(mapSpec.Contents) == 0 {
1526+
continue
1527+
}
1528+
1529+
stOps, ok := ec.structOps[mapSpec.Name]
1530+
if !ok {
1531+
continue
1532+
}
1533+
1534+
if uint64(targetIdx) == uint64(stOps.secIdx) &&
1535+
stOps.userOff <= relOff &&
1536+
(relOff-stOps.userOff) < stOps.userSize {
1537+
meta = stOps
1538+
ms = mapSpec
1539+
}
1540+
}
1541+
1542+
if ms == nil {
1543+
return fmt.Errorf("no struct_ops map found for secIdx %d and relOffset %d", targetIdx, relOff)
1544+
}
1545+
1546+
moff := btf.Bits((relOff - meta.userOff) * 8)
1547+
1548+
userSt, ok := btf.As[*btf.Struct](ms.Value)
1549+
if !ok {
1550+
return fmt.Errorf("provided value is not a btf.Struct")
1551+
}
1552+
1553+
for _, m := range userSt.Members {
1554+
if m.Offset != moff {
1555+
continue
1556+
}
1557+
1558+
mType := btf.UnderlyingType(m.Type)
1559+
if mPtr, isPtr := btf.As[*btf.Pointer](mType); isPtr {
1560+
if _, isFuncProto := btf.As[*btf.FuncProto](mPtr.Target); isFuncProto {
1561+
p, ok := progs[sym.Name]
1562+
if !(ok && p.Type == StructOps) {
1563+
return fmt.Errorf("program %q not found or not StructOps", sym.Name)
1564+
}
1565+
p.AttachTo = userSt.Name + ":" + m.Name
1566+
}
1567+
}
1568+
}
1569+
}
1570+
}
1571+
1572+
return nil
1573+
}
1574+
13821575
type libbpfElfSectionDef struct {
13831576
pattern string
13841577
programType sys.ProgType

0 commit comments

Comments
 (0)