feat(starry-kernel): add aarch64 eBPF JIT backend#890
Conversation
…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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) | ||
| } |
There was a problem hiding this comment.
这里遍历所有 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"); |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
LCG 伪随机数生成器,硬编码种子 12345,可预测。对于 eBPF trace/debug 场景可接受,但如果用户依赖随机性(如 hash map 分布),质量不足。建议至少使用 ax_hal::rand 或 Xoshiro 等更好的 PRNG。
| Err(AxError::BadFileDescriptor) | ||
| }; | ||
| if result.is_ok() { | ||
| self.free_fds.push(fd); |
There was a problem hiding this comment.
5474 行新代码没有测试覆盖。eBPF 子系统涉及安全敏感的内存操作(probe_read、ringbuf、JIT 代码执行),需要测试验证:
- map CRUD 正确性
- 解释器 ALU/JMP/MEM 指令
- verifier 拒绝非法程序
- JIT 编译正确性(至少在 riscv64/x86_64/aarch64 上与解释器结果对比)
- perf_event ring buffer 写入/读取
概述
在 PR-2 的基础上,添加完整的 AArch64 JIT 后端,替换原有的空 stub 实现。AArch64 后端利用 STP/LDP 成对保存/恢复 callee-saved 寄存器,并正确处理 I-Cache/D-Cache 一致性。
依赖 PR: #PR2编号(x86_64 后端)
主要变更
AArch64 后端 (
ebpf_jit/jit_aarch64.rs)验证
cargo xtask clippy --package starry-kernel全部 11 个 feature 配置通过cargo fmt格式化通过