Skip to content

Conversation

shun159
Copy link
Contributor

@shun159 shun159 commented Sep 29, 2025

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: #1845

@shun159 shun159 requested a review from a team as a code owner September 29, 2025 16:01
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]>
```
Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .strtab           STRTAB          0000000000000000 00036b 000077 00      0   0  1
  [ 2] .text             PROGBITS        0000000000000000 000040 000000 00  AX  0   0  4
  [ 3] struct_ops/test_1 PROGBITS        0000000000000000 000040 000010 00  AX  0   0  8
  [ 4] license           PROGBITS        0000000000000000 000050 000004 00  WA  0   0  1
  [ 5] .struct_ops.link  PROGBITS        0000000000000000 000058 000018 00  WA  0   0  8
  [ 6] .rel.struct_ops.link REL          0000000000000000 000318 000010 10   I 12   5  8
  [ 7] .BTF              PROGBITS        0000000000000000 000070 0001e0 00      0   0  4
  [ 8] .rel.BTF          REL             0000000000000000 000328 000020 10   I 12   7  8
  [ 9] .BTF.ext          PROGBITS        0000000000000000 000250 000050 00      0   0  4
  [10] .rel.BTF.ext      REL             0000000000000000 000348 000020 10   I 12   9  8
  [11] .llvm_addrsig     LLVM_ADDRSIG    0000000000000000 000368 000003 00   E  0   0  1
  [12] .symtab           SYMTAB          0000000000000000 0002a0 000078 18      1   2  8
```

Signed-off-by: shun159 <[email protected]>
@shun159 shun159 force-pushed the feature/struct-ops-3 branch from 6e099b2 to ef7d704 Compare October 16, 2025 15:53
Copy link
Collaborator

@lmb lmb left a comment

Choose a reason for hiding this comment

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

Please refuse loading bare .struct_ops and re-enable the LibBPFCompat test of StructOps.

elf_reader.go Outdated
}

for _, vsi := range dataSec.Vars {
varType, ok := btf.As[*btf.Var](vsi.Type)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why mix btf.As and btf.UnderlyingType here?

elf_reader.go Outdated

// Retrieve raw data from the ELF section.
// This data contains the initial values for the struct_ops map.
userData, err := sec.Data()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do this for each iteration? Can be done once.

elf_reader.go Outdated
case sec.Type == elf.SHT_PROGBITS:
if (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0 {
sections[idx] = newElfSection(sec, programSection)
} else if sec.Name == ".struct_ops" || sec.Name == ".struct_ops.link" {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Refuse ".struct_ops" here.

Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to still allow .struct_ops parsing to Spec, but then not allow loading unless you manually add BPF_F_LINK to the map spec?

If you block it in ELF parsing, then you force users to modify the section, and take away the ability to patch this in userspace.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The reason for not using BPF_F_LINK is to get compat with older kernels, no? Adding BPF_F_LINK manually then kind of defeats the purpose?

I agree that it's probably nicer to refuse loading a StructOpsMap without BPF_F_LINK instead, then we can test more of the libbpf ELFs.

Copy link
Member

Choose a reason for hiding this comment

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

The reason for not using BPF_F_LINK is to get compat with older kernels, no? Adding BPF_F_LINK manually then kind of defeats the purpose?

Yea, BPF_F_LINK got added in 6.4, and struct ops originally got added in 5.6. In the end, its all about the attachment, there is not difference in the how you write the programs. Its only really tcp_congestion_ops that existed before BPF_F_LINK so its examples and selftests use .struct_ops. Schedext and HIDBPF both have macros for defining the struct which do use .struct_ops.link, since those features came after 6.4.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ack. let's refuse ".struct_ops" here.

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.

elf_reader.go Outdated
}

// Process the struct_ops section to create the map
dataType, err := ec.btf.AnyTypeByName(sec.Name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should use TypeByName.

elf_reader.go Outdated
Type: StructOpsMap,
Key: &btf.Int{Size: 4},
KeySize: 4,
Value: userType,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe also set ValueSize for completeness sake?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lmb At this point, the size of the value type is not yet determined, so my understanding is that it’s difficult to specify this field for now.

elf_reader.go Outdated
Name string
}

type structOpsSpec struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this is necessary. elfSection.relocations already carries this information in the form of an elf.Symbol.

elf_reader.go Outdated

// loadStructOpsMapsFromSections creates StructOps MapSpecs from DataSec sections
// ".struct_ops" and ".struct_ops.link" found in the object BTF.
func (ec *elfCode) loadStructOpsMaps() error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be merged with associateStructOpsRelocs unless there is a reason why they need to be separate which I don't understand.

elf_reader.go Outdated
relSecs map[elf.SectionIndex]*elf.Section,
symbols []elf.Symbol,
) error {
for _, sec := range relSecs {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This just duplicates the loop in loadRelocations? You can iterate sec.relocations instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

outdated, but I will take a look this.

@shun159
Copy link
Contributor Author

shun159 commented Oct 18, 2025

re-enable the LibBPFCompat test of StructOps

@lmb @dylandreimerink

It seems that in the test cases for programs that include "autocreate", the program fails when load because it cannot determine which map it belongs to.
I believe at this point such cases should be treated as NotSupoorted?

test case: https://github.com/cilium/ebpf/actions/runs/18612775255/job/53073227432?pr=1869#step:8:1224
test data : https://elixir.bootlin.com/linux/v6.17.1/source/tools/testing/selftests/bpf/progs/struct_ops_autocreate2.c#L25

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants