-
Notifications
You must be signed in to change notification settings - Fork 389
aya: add struct_ops support #1444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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
✅ Deploy Preview for aya-rs-docs ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
- 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.
|
@codex review |
There was a problem hiding this 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
StructOpsprogram type andStructOpsMapfor registering kernel callbacks - Implements kfunc relocation to resolve external kernel function calls via BTF
- Adds
Syscallprogram 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.
There was a problem hiding this 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.
|
@codex second round of review please |
|
To use Codex here, create a Codex account and connect to github. |
|
@codex review |
There was a problem hiding this 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".
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.
|
https://github.com/echtzeit-solutions/monsgeek-akko-linux/tree/master/akko-hid-bpf this is my use case btw |
tamird
left a comment
There was a problem hiding this 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.
Summary
This PR adds support for BPF struct_ops programs and maps, enabling implementation of kernel callbacks like
sched_ext_ops,hid_bpf_ops, andtcp_congestion_opsin Rust eBPF programs.Features added:
StructOpsprogram type withload(struct_name, btf)methodStructOpsMapfor registration and program FD filling.struct_ops.linksections (kernel 5.13+)BPF_PROG_TYPE_SYSCALLsupportChanges by crate:
aya-obj: Section parsing, BTF helpers, kfunc relocation, map parsingaya: Program/map types, loader integration, syscall wrappersaya-ebpf:StructOpsContextand#[struct_ops]macroaya-ebpf-macros: Proc macro implementationTest plan
test/integration-test/src/tests/struct_ops.rs)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