Skip to content

feat(starry-kernel): add aarch64 eBPF JIT backend#890

Closed
CN-TangLin wants to merge 4 commits into
rcore-os:devfrom
CN-TangLin:feat/ebpf-jit-riscv64
Closed

feat(starry-kernel): add aarch64 eBPF JIT backend#890
CN-TangLin wants to merge 4 commits into
rcore-os:devfrom
CN-TangLin:feat/ebpf-jit-riscv64

Conversation

@CN-TangLin

@CN-TangLin CN-TangLin commented May 23, 2026

Copy link
Copy Markdown
Contributor

概述

在 PR-2 的基础上,添加完整的 AArch64 JIT 后端,替换原有的空 stub 实现。AArch64 后端利用 STP/LDP 成对保存/恢复 callee-saved 寄存器,并正确处理 I-Cache/D-Cache 一致性。

依赖 PR: #PR2编号(x86_64 后端)

主要变更

AArch64 后端 (ebpf_jit/jit_aarch64.rs)

  • 完整 AArch64 编码:ADD/SUB/MUL/UDIV/AND/OR/XOR、MOVZ/MOVK、LDR/STR/LDP/STP
  • 寄存器映射:r0→x0, r1-r5→x1-x5, r6→x7, r7→x9, r8→x15, r9→x16, r10→x29(FP)
  • 13 种 ALU/ALU64 操作:32 位使用 W 寄存器变体
  • DIV 除零保护:cbz 跳过模式,除零时返回 0
  • MOD 实现:UDIV + MSUB(AArch64 无直接余数指令)
  • 11 种条件跳转:CMP + B.cond,JSET 使用 AND + CBNZ 模式
  • 内存操作:STRB/STRH/STRW/STR 和 LDRB/LDRH/LDRW/LDR 四种宽度
  • LD_IMM64:MOVZ + 3×MOVK(4 条指令序列加载 64 位常量)
  • Helper CALL:参数重排(x1-x5→x0-x4)+ BLR 间接调用
  • Prologue:SUB SP, STP x29/x7, STP x9/x15, STR x16, ADD x29=SP+frame
  • Epilogue:LDP/LDR, ADD SP, RET
  • 缓存维护:finalize 时 dcache clean to PoU + icache flush

验证

  • cargo xtask clippy --package starry-kernel 全部 11 个 feature 配置通过
  • cargo fmt 格式化通过

…ier and new map types

Add complete eBPF subsystem including:
- Enhanced verifier with DFS reachability analysis, register bounds
  checking, stack access validation, and division-by-zero detection
- 3 new helper functions: probe_read_user, probe_read_user_str,
  probe_read_kernel_str
- 4 new map types: RingBufferMap, ProgArrayMap (with tail call),
  StackTraceMap (with get_stackid), PerCpuArrayMap
- HashMap upgraded from BTreeMap to hashbrown::HashMap (O(1) lookup)
- perf_event ring buffer for data output to userspace
…kend

Add a two-pass JIT compiler for eBPF programs with full riscv64 backend:

- JitBuffer: page-aligned executable memory with W->X security model
  and architecture-aware cache flushing (dcache clean + icache flush
  on aarch64, icache flush on riscv64/x86_64)
- JitCompiler: two-pass compilation (sizing pass builds PC->offset
  mapping, emit pass generates native machine code)
- Riscv64Backend: complete BPF instruction translation including
  ALU/ALU64 (13 ops), JMP/JMP32 (11 conditions + JA + EXIT),
  ST/STX/LDX (4 widths), LD_IMM64, helper function calls
- Register mapping: BPF r0-r10 -> a0-a5, s1-s5 with proper
  callee-saved register preservation
- BpfProg integration: JIT compilation attempted on prog load,
  run_bpf_prog prioritizes JIT execution with interpreter fallback
- Stub backends for x86_64 and aarch64 (to be implemented)
Add complete x86_64 JIT backend for eBPF with:

- Full x86_64 instruction encoding: REX prefix, ModRM, SIB, displacement
- Register mapping: BPF r0-r10 -> RAX,RDI,RSI,RDX,RCX,R8,RBX,R13-R15,RBP
  (natural alignment with System V ABI for zero-overhead helper calls)
- All 13 ALU/ALU64 operations with 32-bit zero-extension via and r,r
- 11 conditional jumps mapped to x86 condition codes (JEQ->JE, JGT->JA, etc.)
- DIV/MOD with divide-by-zero protection using test+jmp skip pattern
- ST/STX/LDX for B/H/W/DW memory widths with ModRM displacement encoding
- LD_IMM64 via movabs (10-byte immediate-to-register)
- Helper call via movabs+callq (load function pointer into RAX, indirect call)
- Prologue: push RBP/RBX/R13-R15, sub rsp 512, lea rbp [rsp]
- Epilogue: add rsp 512, pop R15-R13/RBX/RBP, ret
Add complete AArch64 JIT backend for eBPF with:

- Full AArch64 instruction encoding: ADD/SUB/MUL/UDIV/AND/OR/XOR,
  shift immediates, MOVZ/MOVK for 64-bit constants, LDR/STR/LDP/STP
- Register mapping: BPF r0-r10 -> x0-x5,x7,x9,x15,x16,x29(FP)
- All 13 ALU/ALU64 operations with 32-bit (W-register) variants
- DIV with cbz skip pattern for divide-by-zero protection
- MOD using UDIV + MSUB (AArch64 has no direct remainder)
- 11 conditional jumps via CMP + B.cond with correct condition codes
- JSET using AND + CBNZ pattern
- ST/STX/LDX for B/H/W/DW using STRB/STRH/STR/STR (64-bit)
- LD_IMM64 via MOVZ + 3x MOVK (4-instruction sequence)
- Helper call with argument shuffle (x1-x5 -> x0-x4) + BLR
- Prologue: SUB SP, STP x29/x7, STP x9/x15, STR x16, ADD x29=SP+frame
- Epilogue: LDP/LDR, ADD SP, RET
- Cache maintenance: dcache clean to PoU + icache flush on finalize
@CN-TangLin CN-TangLin changed the title feat(starry-kernel): add eBPF JIT compiler with riscv64/x86_64/aarch64 backends feat(starry-kernel): add aarch64 eBPF JIT backend May 23, 2026
@CN-TangLin CN-TangLin closed this May 23, 2026

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #890 代码审查

⚠️ 本 PR 已关闭(未合并),本审查仅作为代码质量反馈,供后续 PR 参考。

概要

本 PR 包含 4 个 commit、5474 行新增代码,6 个新文件。虽然标题为「aarch64 eBPF JIT 后端」,实际包含了完整的 eBPF 子系统(2429 行解释器+验证器+map+helper)、JIT 编译框架(234 行)、riscv64 后端(824 行)、x86_64 后端(895 行)、aarch64 后端(662 行)以及 perf_event 数据通道(430 行)。建议拆分为独立 PR 分别审查。

🔴 阻塞问题

1. 无测试覆盖

5474 行新增代码没有任何测试变更。PR 描述仅声称 clippy 和 fmt 通过,没有可复现的功能验证。eBPF 子系统是安全敏感组件,解释器、JIT 编译器、map 操作、helper 函数、perf_event 通道均需要测试覆盖。

2. PR 范围与标题不匹配,提交粒度过大

  • 标题说「aarch64 JIT 后端」,但第一个 commit 就包含 2429 行完整 eBPF 子系统
  • 应拆分为:(a) eBPF 子系统核心 (b) JIT 框架 (c) 各架构后端独立 PR
  • 分支名 feat/ebpf-jit-riscv64 也与 aarch64 内容不对应

3. ProgArrayMap::update_elem 潜在死锁

在已持有 BPF_GLOBAL 锁的 guard.get_map() 返回可变借用后,又通过 BPF_GLOBAL.lock() 重新获取锁来检查 prog fd 是否存在。虽然在 SpinNoIrq 下单线程不会死锁,但这是一个设计错误,在 SMP 环境下会阻塞。

4. helper_ringbuf_submit / helper_ringbuf_discard 错误地遍历所有 map

当有多个 RingBufferMap 时,这两个 helper 遍历所有 map 查找 pending_reserve,会错误匹配非目标 ringbuf。应通过 sample_ptr 定位具体 map。

5. RingBufferMap::reserve 返回裸指针安全风险

reserve() 返回 *mut u8 并记录 pending_reserve 状态。如果 BPF 程序通过 JIT 执行路径写入该指针,但解释器路径又调用了另一个 reserve(),前一个指针将悬空。

6. PerCpuArrayMap::detect_cpu_count 实现不正确

使用 (this_cpu_id() + 1).clamp(2, 256) 估算 CPU 数量,这不反映实际 CPU 数量。如果 CPU 0 上运行时返回 2,CPU 1 上运行时返回 2,可能导致数据不一致。应使用系统实际的 CPU 数量。

🟡 建议改进

7. JitBuffer 缺少 W→X 保护

JitBuffer 使用 alloc_zeroed 分配页对齐内存,写入代码后仅做缓存刷新(dcache clean + icache flush),但没有将页面从可写改为可执行。在具有 W^X 硬件保护的平台(如 aarch64 PXN/PAN)上,代码页应先设为只读+可执行。

8. 除零验证不完整

verifier 仅检查 imm == 0 的立即数除法,但 BPF_X(寄存器源)的除法没有插入运行时检查。解释器和 JIT 后端做了运行时保护(返回 0),但 verifier 的静态检查是多余的且不完整。

9. 全局单锁性能瓶颈

所有 map/prog 操作共用 BPF_GLOBAL 一个 SpinNoIrq 锁。在高频场景(如 kprobe 每次触发都 lookup/update map)下,所有 CPU 争用同一把锁。建议 per-map 细粒度锁。

10. RingBufferMap::discard 存在死代码

hdr_bytes_to_clear 变量被计算但随后被 let _ = (hdr_bytes_to_clear, data_offset) 丢弃,没有实际效果。

11. get_prandom_u32 使用线性同余伪随机数

LCG 硬编码种子 12345,可预测。对于 eBPF 场景可能不需要密码学安全,但至少应使用更好的 PRNG 或硬件随机源。

重复与重叠分析

  • PR #888 (open):同一作者的 eBPF 子系统核心,与本 PR 第一个 commit 内容高度重复
  • PR #891 (open):同一作者的 JIT 框架 + riscv64 后端
  • PR #892 (open):同一作者的 x86_64 JIT 后端
  • PR #848 (open):同一作者更早版本的 eBPF 子系统(~1340 行简化版),与 #888 和本 PR重复
  • PR #850 (open, LorenzLorentz):基于 kbpf-basic + rbpf 的不同 eBPF 运行时实现,更接近 Linux 语义,有完整的 kprobe/tracepoint 集成

建议:PR #848#888#890 内容重叠度极高,建议作者整理为一个清晰的 PR 链。同时与 PR #850(更成熟的运行时方案)协调,避免并行开发两套 eBPF 实现。

总结

本 PR 包含大量有价值的 eBPF 工作,但存在以下阻塞问题:无测试、范围过大、潜在死锁、ringbuf helper 逻辑错误、裸指针安全风险、CPU 数量检测不正确。建议拆分并逐个解决后重新提交。

Powered by glm-5.1

return Err(bpf_error::EINVAL);
}
if map_type != map_type::RINGBUF && (key_size == 0 || value_size == 0) {
return Err(bpf_error::EINVAL);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ 潜在死锁:这里已通过外层 guard.get_map() 持有 BPF_GLOBAL 锁的可变借用,然后又通过 BPF_GLOBAL.lock() 重新获取锁。在 SpinNoIrq 下虽然不会自锁,但违反了锁的设计约定,在后续改为可重入或 Mutex 时会成为真正的死锁。建议先从 guard 中分离出 prog fd 验证逻辑,或者将 prog fd 存在性检查下放到 guard 释放后执行。

#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!("mov {0}, x29", out(reg) fp)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

这里遍历所有 map 查找 pending_reserve,当存在多个 RingBufferMap 时会错误匹配。应根据 sample_ptr 参数定位具体的 RingBufferMap,而非遍历。sample_ptr 参数目前被忽略了(_sample_ptr),但它应该用于确定目标 map。

)
};
if log_level > 0 {
warn!("bpf: BPF_PROG_LOAD verifier log requested but not implemented");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

reserve() 返回裸指针 *mut u8 给 BPF 程序写入。在 JIT 执行路径中,BPF 程序直接通过指针写入,但如果此时有另一个 reserve 调用发生(比如通过 tail call),前一个指针将指向无效位置。Linux 内核的 ringbuf_reserve 通过 per-CPU 上下文和 header 的 busy 标志位来保护,建议参考类似机制。

}
};
for i in start..self.prog_fds.len() {
if self.prog_fds[i].is_some() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

detect_cpu_count() 使用 (this_cpu_id() + 1).clamp(2, 256) 不反映实际 CPU 数量。如果系统有 4 个 CPU,但当前运行在 CPU 0 上,这里返回 2,导致 per-CPU 数组只有 2 个槽位,CPU 2 和 3 的数据会丢失。应使用 ax_hal::percpu::num_cpus() 或类似的系统级 API 获取实际 CPU 数量。

m.insert(helper_id::RINGBUF_OUTPUT, helper_ringbuf_output);
m.insert(helper_id::RINGBUF_RESERVE, helper_ringbuf_reserve);
m.insert(helper_id::RINGBUF_SUBMIT, helper_ringbuf_submit);
m.insert(helper_id::RINGBUF_DISCARD, helper_ringbuf_discard);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LCG 伪随机数生成器,硬编码种子 12345,可预测。对于 eBPF trace/debug 场景可接受,但如果用户依赖随机性(如 hash map 分布),质量不足。建议至少使用 ax_hal::rand 或 Xoshiro 等更好的 PRNG。

Err(AxError::BadFileDescriptor)
};
if result.is_ok() {
self.free_fds.push(fd);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

5474 行新代码没有测试覆盖。eBPF 子系统涉及安全敏感的内存操作(probe_read、ringbuf、JIT 代码执行),需要测试验证:

  1. map CRUD 正确性
  2. 解释器 ALU/JMP/MEM 指令
  3. verifier 拒绝非法程序
  4. JIT 编译正确性(至少在 riscv64/x86_64/aarch64 上与解释器结果对比)
  5. perf_event ring buffer 写入/读取

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.

1 participant