Skip to content

Conversation

@heeen
Copy link

@heeen heeen commented Jan 17, 2026

Summary

This PR adds support for BPF struct_ops programs and maps, enabling implementation of kernel callbacks like sched_ext_ops, hid_bpf_ops, and tcp_congestion_ops in Rust eBPF programs.

Features added:

  • struct_ops program type: New StructOps program type with load(struct_name, btf) method
  • struct_ops map support: StructOpsMap for registration and program FD filling
  • kfunc relocation: Resolution of external kernel function calls via BTF lookup
  • .ksyms section support: Parsing of kernel symbol references
  • BPF link support: For .struct_ops.link sections (kernel 5.13+)
  • Syscall program type: BPF_PROG_TYPE_SYSCALL support

Changes by crate:

  • aya-obj: Section parsing, BTF helpers, kfunc relocation, map parsing
  • aya: Program/map types, loader integration, syscall wrappers
  • aya-ebpf: StructOpsContext and #[struct_ops] macro
  • aya-ebpf-macros: Proc macro implementation

Test plan

  • Unit tests for section parsing
  • Integration tests for struct_ops loading (test/integration-test/src/tests/struct_ops.rs)
  • CI validation

AI disclosure

the code was generated by claude while working on a hid keyboard translation layer. We created a working C/libbpf version and first ported the loader and then the requirements in aya for the bpf itself


This change is Reviewable

heeen and others added 11 commits January 16, 2026 15:48
Add support for parsing `struct_ops/` and `struct_ops.s/` ELF section
names as program sections. This enables loading BPF struct_ops programs
such as those used by HID-BPF and sched_ext.

The `StructOps` variant includes a `sleepable` field to distinguish
between regular and sleepable struct_ops programs (the latter use
the `struct_ops.s/` section prefix).

Refs: aya-rs#967
Add the `StructOps` program type for loading BPF struct_ops programs.
Struct_ops programs implement kernel interfaces like `hid_bpf_ops` for
HID device handling or `sched_ext_ops` for custom schedulers.

The `load()` method takes the struct type name (e.g., "hid_bpf_ops") and
BTF information to resolve the struct type ID needed by the kernel.

Refs: aya-rs#967
Add `StructOpsContext` for struct_ops programs and the `#[struct_ops]`
attribute macro for marking functions as struct_ops callbacks.

The macro supports:
- `name` parameter to override the callback name (defaults to function name)
- `sleepable` flag for sleepable programs

Example usage:
```rust
#[struct_ops]
fn my_callback(ctx: StructOpsContext) -> i32 {
    0
}
```

Refs: aya-rs#967
Parse struct_ops maps from ELF with all necessary metadata:
- btf_vmlinux_value_type_id for kernel type resolution
- Kernel wrapper struct size for map creation
- data_offset for program data placement
- Member name extraction from section name
- expected_attach_type set to member index
- func_info population from relocations

This enables the loader to properly create struct_ops maps
that match the kernel's expectations for the BPF struct_ops
subsystem.

struct_ops: set expected_attach_type to member index

For struct_ops programs, expected_attach_type must be the member index
in the struct (e.g., 4 for hid_rdesc_fixup), not BPF_STRUCT_OPS (44).
The kernel uses this to know which callback slot the program implements.

- Add Btf::struct_member_index() to look up member index by name
- Update StructOps::load() to take member_name parameter and look up index
- Update Ebpf::load_struct_ops() to pass prog_name as member_name

struct_ops: populate func_info from relocations and add load_struct_ops

- Parse relocations in struct_ops section to find program function
  pointers and their offsets in the struct
- Add Ebpf::load_struct_ops() method that:
  1. Loads struct_ops programs with their struct name and BTF
  2. Fills program FDs into struct_ops map data at correct offsets
- This is required because libbpf skeleton fills prog FDs before
  calling bpf_map_update_elem for struct_ops registration
Add explicit handling for struct_ops maps in the map type matching.
Struct_ops maps are marked as unsupported for now since they require
special handling (btf_vmlinux_value_type_id, program FD substitution,
and BPF_MAP_UPDATE_ELEM for registration).

aya: add struct_ops map support and registration

Add StructOpsMap type for working with BPF_MAP_TYPE_STRUCT_OPS maps.

Changes:
- Add StructOpsMap type in aya/src/maps/struct_ops.rs with register() method
- Add Map::StructOps variant to the Map enum
- Add MapData::create_struct_ops() for creating maps with btf_vmlinux_value_type_id
- Modify EbpfLoader::load() to handle struct_ops maps specially:
  - Look up kernel BTF type bpf_struct_ops_<type_name>
  - Create map with correct btf_vmlinux_value_type_id
- Update parse_map() to return Map::StructOps instead of Unsupported

The struct_ops map type is used for registering BPF implementations of
kernel callbacks like hid_bpf_ops, sched_ext_ops, and tcp_congestion_ops.

aya: add struct_ops FD filling and skip finalize for struct_ops maps

- Add obj_mut() accessor to MapData for mutable access
- Add StructOpsMap methods:
  - func_info(): Get function pointer field information
  - type_name(): Get struct_ops type name
  - is_link(): Check if this is a link-based struct_ops
  - set_prog_fd(): Set program FD at a given offset
- Skip finalize() for struct_ops maps - they need FDs filled first

The workflow for struct_ops is:
1. Load ELF with EbpfLoader::load()
2. Load struct_ops programs
3. Get program FDs and fill into struct_ops map with set_prog_fd()
4. Call struct_ops.register() to register with kernel

Add btf_fd and btf_value_type_id to struct_ops map creation

struct_ops maps require not just btf_vmlinux_value_type_id but also:
- btf_fd: file descriptor of the program's BTF
- btf_value_type_id: type ID of the struct_ops type in program BTF

Also adds btf_value_type_id() accessor to Map enum.

Remove btf_fd/btf_value_type_id from struct_ops map creation

libbpf does NOT set these fields for struct_ops maps - only
btf_vmlinux_value_type_id is needed.

Fix struct_ops map creation to match libbpf

Based on BCC/libbpf source analysis:
1. Re-add btf_fd and btf_value_type_id (they ARE set for struct_ops)
2. Use program's struct size for value_size (not kernel wrapper size)
   - libbpf sets map->def.value_size = type->size (program BTF)
   - We were incorrectly using kernel bpf_struct_ops_<name> wrapper size

Sources:
- https://github.com/iovisor/bcc/blob/master/src/cc/libbpf.c

Fix struct_ops: btf_value_type_id=0, use kernel wrapper size

Based on actual libbpf trace:
- value_size=128 (kernel wrapper bpf_struct_ops_<name> size)
- btf_value_type_id=0 (NOT the program type ID!)
- btf_fd=program BTF fd
- btf_vmlinux_value_type_id=kernel BTF type ID
Add support for the .ksyms pseudo-section used for extern kernel symbol
references. The .ksyms section exists in BTF but not as a real ELF section -
the kernel resolves these extern symbols at BPF load time.

- Add EbpfSectionKind::Ksyms variant
- Add ".ksyms" section name matching
- Skip BTF DATASEC size fixup for .ksyms (it's a pseudo-section)
- Ignore .ksyms section during parse_section

aya-obj: sanitize BTF_FUNC_EXTERN for kfuncs

BTF_FUNC_EXTERN is used for kernel function (kfunc) declarations.
These functions are resolved from kernel BTF at load time, not from
the program's BTF. Replace extern FUNC entries with TYPEDEF to avoid
the kernel BTF verifier error "Invalid func linkage" while preserving
type information.

This fixes loading BPF programs that use kfuncs like hid_bpf_get_data.

aya-obj: remove .ksyms DATASEC from BTF entirely

The kernel rejects DATASEC entries with size=0 and non-empty vlen.
The .ksyms pseudo-section for extern kernel symbols has no real ELF
section, so it ends up with size=0 but contains VAR entries for kfuncs.

Replace the .ksyms DATASEC with an INT type to effectively remove it
from BTF while keeping type IDs valid.

aya-obj: fix BTF header when replacing .ksyms DATASEC

When replacing .ksyms DATASEC with INT, the type section size changes
but the header wasn't being updated. This caused a mismatch between
header.type_len and the actual serialized size.

Now properly calculate the size difference and update header.type_len
and header.str_off when replacing the DATASEC type.
Kfunc calls (like hid_bpf_get_data) need to be resolved by looking up
the function name in kernel BTF and setting:
- insn.imm = BTF type ID of the function
- insn.src_reg = BPF_PSEUDO_KFUNC_CALL (2)

Previously we were skipping kfuncs, leaving them with imm=-1 which
caused the verifier to fail with "processed 0 insns".

- Add kernel_btf parameter to relocate_calls() and FunctionLinker
- Look up kfuncs by name using id_by_type_name_kind(name, BtfKind::Func)
- Pass kernel BTF from EbpfLoader to relocate_calls

Skip kfunc relocations in function linker

Kfuncs (kernel functions like hid_bpf_get_data) are external symbols
with no section_index. Previously these relocations were filtered out
but then the code tried to resolve them as BPF-to-BPF calls, failing
with "function not found".

Now explicitly detect kfunc relocations (symbols with no section) and
skip them - the kernel will resolve them via BTF at load time.
Previously parse_programs only registered the program entry point,
missing static helper functions in the same section. This caused
"function not found" errors when struct_ops programs called static
helpers.
Now walk through section data like parse_text_section does, registering
all functions by their symbol addresses. The first function (offset 0)
becomes the program entry point.
Link-based struct_ops (from .struct_ops.link sections) require a
BPF_LINK_CREATE call after bpf_map_update_elem to activate. This is
different from regular struct_ops which activate immediately on update.

- Add bpf_struct_ops_link_create() syscall wrapper
- Add StructOpsMap::attach() to create the BPF link
- Link keeps struct_ops active; dropping it detaches

Add fallback section-based matching for struct_ops programs

When func_info is empty (no relocations in .struct_ops section),
fall back to matching programs by their member_name (from section
name) to struct members using BTF offsets.

This is needed for Rust BPF programs where function pointer fields
in struct_ops can't create relocations in const context.
Add test eBPF program and integration tests for struct_ops program type:
- struct_ops_test.rs: minimal eBPF callback program
- tests/struct_ops.rs: tests for parsing and program type verification

Note: Running these tests requires bpf-linker to be installed.
Add support for BPF_PROG_TYPE_SYSCALL programs which can be invoked
directly via the bpf() syscall. This is used for HID-BPF probe functions
that determine whether to attach to a HID device.

- Add ProgramSection::Syscall variant in aya-obj/obj.rs
- Add "syscall" section name parsing
- Add syscall.rs with Syscall program type
- Add Program::Syscall variant and all required method implementations
- Handle ProgramSection::Syscall in bpf.rs program creation
@netlify
Copy link

netlify bot commented Jan 17, 2026

Deploy Preview for aya-rs-docs ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit cd526fb
🔍 Latest deploy log https://app.netlify.com/projects/aya-rs-docs/deploys/696c14e9e62ceb000871a097
😎 Deploy Preview https://deploy-preview-1444--aya-rs-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

heeen added 6 commits January 17, 2026 08:53
- Fix uninlined_format_args in bpf.rs, struct_ops.rs, relocation.rs
- Fix ptr_cast_constness in struct_ops.rs (use .cast_mut())
- Fix missing_transmute_annotations in struct_ops.rs
- Fix unused_trait_names (use `as _` for method-only imports)
- Fix pre-existing uninlined_format_args in probe.rs
- Remove unused text_sections field from FunctionLinker
The previous implementation only registered the first function (at
offset 0) as a program, breaking test_parse_multiple_program_in_same_section.

Also fix test fake_sym to set is_definition: true to match real ELF
symbol behavior.
Updates public API definition files to include new struct_ops types:
- StructOpsMap and related methods in aya
- StructOps program and StructOpsContext in aya-ebpf
- StructOps map type in aya-obj
- #[struct_ops] macro in aya-ebpf-macros
Update the public API file to reflect the new libc type path for nlattr
introduced in libc 0.2.180.
The previous implementation walked through section data by address and
registered ALL functions as programs. This caused integration test
failures because helper functions like `get_ptr_mut` were incorrectly
being treated as programs and triggering relocation code paths that
hit unimplemented branches.

Revert to the upstream approach that uses symbols_by_section to find
only the actual program entry points, not internal helper functions.
@tamird tamird requested a review from Copilot January 17, 2026 12:30
@tamird
Copy link
Member

tamird commented Jan 17, 2026

@codex review

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces comprehensive support for BPF struct_ops programs, enabling Rust eBPF programs to implement kernel subsystem callbacks like sched_ext_ops, hid_bpf_ops, and tcp_congestion_ops.

Changes:

  • Adds StructOps program type and StructOpsMap for registering kernel callbacks
  • Implements kfunc relocation to resolve external kernel function calls via BTF
  • Adds Syscall program type for BPF_PROG_TYPE_SYSCALL support

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
xtask/public-api/aya.txt Public API additions for StructOps/Syscall programs and StructOpsMap
xtask/public-api/aya-obj.txt BTF helpers and struct_ops map parsing API
xtask/public-api/aya-ebpf.txt StructOpsContext and ebpf-side API
xtask/public-api/aya-ebpf-macros.txt New #[struct_ops] proc macro
test/integration-test/src/tests/struct_ops.rs Integration tests for struct_ops parsing
test/integration-test/src/tests/rbpf.rs Updated relocate_calls signature with kernel_btf parameter
test/integration-test/src/tests.rs Added struct_ops test module
test/integration-test/src/lib.rs Registered STRUCT_OPS_TEST binary
test/integration-ebpf/src/struct_ops_test.rs Test eBPF program using #[struct_ops] macro
test/integration-ebpf/Cargo.toml Added struct_ops_test binary configuration
ebpf/aya-ebpf/src/programs/struct_ops.rs StructOpsContext implementation
ebpf/aya-ebpf/src/programs/mod.rs Exported StructOpsContext
aya/src/sys/bpf.rs Added bpf_create_map_with_vmlinux_btf and bpf_struct_ops_link_create
aya/src/programs/syscall.rs New Syscall program type implementation
aya/src/programs/struct_ops.rs StructOps program type with load() method
aya/src/programs/probe.rs String formatting fix
aya/src/programs/mod.rs Integrated StructOps/Syscall into Program enum
aya/src/maps/struct_ops.rs StructOpsMap implementation with register/attach methods
aya/src/maps/mod.rs Added StructOps variant to Map enum
aya/src/bpf.rs load_struct_ops() method for filling program FDs
aya-obj/src/relocation.rs Kfunc relocation via kernel BTF lookup
aya-obj/src/obj.rs Section parsing for .struct_ops and .ksyms
aya-obj/src/maps.rs StructOpsMap and StructOpsFuncInfo types
aya-obj/src/lib.rs Updated example with kernel_btf parameter
aya-obj/src/btf/btf.rs BTF helper methods for struct member lookup
aya-ebpf-macros/src/struct_ops.rs #[struct_ops] macro implementation
aya-ebpf-macros/src/lib.rs Exported struct_ops macro

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f44731da0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The previous kfunc relocation changes inadvertently removed the filter
that only considers text relocations (data relocations are handled in
relocate_maps()). This caused non-text relocations to fall through and
hit the todo!() branch.

Restore the filter that checks for SymbolKind::Text or symbols whose
section_index is in text_sections.
- Fix struct_ops macro to propagate return value instead of discarding it
- Improve transmute safety documentation in struct_ops program loading
- Change set_prog_fd to pub(crate) visibility
- Revert unrelated probe.rs formatting change
- Clarify btf_value_type_id comment in bpf.rs
- Add comment explaining kfunc detection pattern
- Use constant for .ksyms string literal
- Fix cast_mut on immutable data by using mutable copy
- Update public API
Replace define_link_wrapper! macro with manual link type definitions
for StructOps. The macro generates docs referencing StructOps::attach,
but struct_ops links come from StructOpsMap::attach() instead.

This also removes the StructOps::detach() and StructOps::take_link()
methods which were incorrect - struct_ops links are managed through
the map, not the program type.
@heeen
Copy link
Author

heeen commented Jan 17, 2026

@codex second round of review please

@chatgpt-codex-connector
Copy link

To use Codex here, create a Codex account and connect to github.

@tamird
Copy link
Member

tamird commented Jan 17, 2026

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31e4225a63

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

heeen added 2 commits January 17, 2026 23:52
struct_ops maps require btf_fd to be set so the kernel can resolve
struct and function type references in the BPF object's BTF. Without
this, BPF_MAP_CREATE fails with EINVAL.

The previous code incorrectly left btf_fd=0 for struct_ops maps,
causing HID-BPF and other struct_ops programs to fail loading on
kernels 6.x.

Verified by comparing strace output between libbpf (which sets btf_fd)
and aya (which was missing it).
The .struct_ops section data represents the ops struct itself, but
it must be placed at data_offset within the kernel wrapper struct
(bpf_struct_ops_<name>). Previously we just resized the buffer,
leaving the original data at offset 0 instead of data_offset.

This could cause struct_ops with non-function fields (like hid_id
in hid_bpf_ops) to have their static initializers ignored.

Pass data_offset to create_struct_ops and copy the original section
data to the correct offset within the resized buffer.
@heeen
Copy link
Author

heeen commented Jan 18, 2026

Copy link
Member

@tamird tamird left a comment

Choose a reason for hiding this comment

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

@tamird made 3 comments.
Reviewable status: 0 of 27 files reviewed, 3 unresolved discussions (waiting on @heeen).


-- commits line 1 at r24:
This commit history is rather a mess. Some of these commits look as though they are meant to be preserved, but others ("address code review feedback") are noise. If the PR is to be reviewed as one big ball of code, it is 2k lines, which is quite a lot.

What's your intention here?


-- commits line 3 at r24:
in addition to the sheer size of this PR, it is also notably almost all new code. this is a hallmark of LLM-generated code, which tends to err on the side of not perturbing existing code because duplication is just easier that refactoring. unfortunately this increases complexity and makes things harder to maintain, and obscures opportunities for extracting shared logic or reshaping existing logic in light of new requirements.


aya/src/bpf.rs line 1253 at r24 (raw file):

    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn load_struct_ops(&mut self, btf: &Btf) -> Result<(), EbpfError> {

this is a huge function, its documentation implies it is quite important, and it has zero test coverage.

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.

2 participants