第一期月报(2026.01.20 - 2026.02.22)
开发分支:axvisor/feature/ebpf | axebpf
执行摘要
| 项目 | 内容 |
|---|---|
| 本月目标 | 构建 AxVisor eBPF 追踪系统,覆盖静态追踪点、VMM 动态探针(hprobe)、Guest 内核探针(guest-kprobe)三条链路 |
| 完成度 | Phase 1–4 基础目标全部完成;guest-kprobe M1(异常接管闭环)和 M2a/M2b(地址翻译基础链路)里程碑达成 |
| 项目价值 | AxVisor 获得运行在 EL2 的 eBPF 动态追踪能力,可在不重新编译内核的情况下观测 VMM 内部行为及 guest 内核执行路径 |
| 主要风险 | guest-kprobe 端到端验证受 guest 生命周期窗口限制:arceos guest 退出过快,linux guest 存在早期 panic,暂无稳定可持续运行的验证基线 |
| 下月里程碑 | guest-kprobe Stage-2 恢复闭环;接入真实 guest 寄存器上下文;guest 符号表加载 |
术语说明
本报告首次出现的缩写定义如下:
| 术语 | 定义 |
|---|---|
| hprobe | Hypervisor Probe:探测 VMM 自身(EL2)函数的动态探针,基于 BRK 断点 + 指令槽软件单步实现 |
| guest-kprobe | 从 VMM(EL2)向下探测 guest 内核(EL1)函数的跨特权级动态探针 |
| M1 | guest-kprobe 里程碑 1:fault/BRK 匹配路径接管并返回 handled = true,结束"命中但未接管"状态 |
| M2a | guest-kprobe 里程碑 2a:实现 GVA→GPA 页表 walker(gva_to_gpa_with,含 L3 page/L2 block) |
| M2b | guest-kprobe 里程碑 2b:VMM 完成 Stage-2 回调注册(guest_pt_read/vm_ttbr1/gpa_to_hpa/stage2_exec) |
| Stage-2 单步 | 利用 AArch64 MDSCR_EL1.SS 位触发硬件单步异常(EC::SingleStepLowerEL),用于 guest 执行原指令后恢复 XN 权限的机制 |
| GVA/GPA/HPA/HVA | Guest Virtual/Physical Address,Host Physical/Virtual Address;四级地址翻译链见附录 A |
一、提交历史
| 日期 | Commit | 内容 |
|---|---|---|
| 2026-01-31 | 9a586fc 0e7f87e 73a305d 83c6d2d 2925de2 |
eBPF 构建与符号支持;trace 命令组;定时器模块;eBPF 追踪集成 |
| 2026-02-05 | 9a9b311 0370bb5 |
axebpf 改为 git submodule;kprobe 追踪集成 |
| 2026-02-06 | fd86cf6 989494a e50eeb5 6f7ddd3 |
kprobe eBPF 程序注册;ksym 命令;trace kprobe 强化;axcpu patch |
| 2026-02-09 | cfc9e8b eb61c44 9b24702 2898252 |
子模块更新;移除独立 ebpf-programs;简化 tracepoint;kprobe→hprobe 重命名 |
| 2026-02-10 | 96abd6a |
kallsyms:include_bytes! → linker section 注入 |
| 2026-02-21 | 662b3e6 708a828 916b8e7 |
trace stream/dump/stat;启用 guest-kprobe;kallsyms ELF 原地写入 |
命令命名历史(重要):2026-02-09 之前,
trace kprobe指 VMM 自身(EL2)探针。2898252提交后完成重命名:
trace hprobe / hretprobe / unhprobe→ VMM 探针(EL2,原trace kprobe)trace kprobe / unkprobe→ Guest 内核探针(EL1,新增)旧命令
trace kprobe <symbol>已废弃,等效替换为trace hprobe <symbol>。
二、已完成工作
2.1 阶段一(1.20 - 1.31):axebpf 模块基础建设
核心成果:从零构建 modules/axebpf,集成 5 个上游库,实现 eBPF 运行时 + Map + 静态追踪点 + Shell 命令全栈。
可验证证据:
- eBPF 程序加载并执行:
trace enable shell:shell_command --prog printk && trace stat,输出 COUNTER_MAP 命中计数 - 上游 PR(已合并):Starry-OS/kbpf-basic#1 — 修复
printf-compatVaList API 在 nightly 2025-12 上的编译失败 - 相关提交:
9a586fc(eBPF 集成)、83c6d2d(eBPF 构建与符号)
追踪点口径说明:
- 框架中定义:25+ 个(含 vcpu_run/hypercall 等规划态事件,详见附录 B)
- 已插桩且可触发:8 个(
vmm_init / config_load / image_load / vm_destroy / timer_tick / timer_event / shell_init / shell_command)
2.2 阶段二(2.1 - 2.7):EL2 动态探针
核心成果:适配 EL2 异常处理差异,实现 hprobe 双 BRK 软件单步机制,集成内核符号表。
可验证证据:
trace hprobe execute_command hprobe_entry→trace list显示 hits 递增(需目标函数标注#[inline(never)],见问题 4.1)ksym lookup <symbol>返回正确地址- 相关提交:
0370bb5(kprobe 集成)、989494a(ksym 命令)
2.3 阶段三(2.8 - 2.21):架构重构与 Guest-Kprobe
核心成果:三项架构级改造 + guest-kprobe M1/M2 里程碑。
2.3.1 统一事件管线
axebpf 内部提交 9a76fd8:新增 event.rs,64B TraceEvent,所有探针来源统一通过 emit_event() 上报。
可验证证据:trace stream -n 5 输出包含 probe_type 字段区分 tracepoint/hprobe/kprobe 来源。
2.3.2 ELF 加载器切换至 aya-obj
axebpf 内部提交 ab494b8:map 重定位和函数调用重定位与主流 eBPF 工具链对齐。
2.3.3 kallsyms 注入修复
可验证证据:修复前,cargo build 无变更也会清空符号,ksym lookup 返回空;修复后(916b8e7),多次 cargo build 后符号仍然有效。
2.3.4 Guest-Kprobe M1/M2(受限可用)
- M1(
2898252+ axebpf 内部 handler 修改):fault/BRK 路径返回handled = true,不再触发默认异常升级 - M2a/M2b(axebpf
addr_translate.rs+kernel/src/vmm/mod.rs):GVA→GPA→HVA 翻译链路 + VMM hook 注册
可验证证据(受限):
axvisor:/$ trace kprobe vm1:0xffff800080012340 hprobe_entry
# 预期:进入 manager 注册,翻译失败时自动回滚,无 panic
# 当前限制:arceos guest 退出过快,常见 "VM is not in Running state"
三、遇到的问题与解决方案
3.1 LTO 导致被探测函数内联,hprobe 命中数为 0
- 现象:
trace hprobe run_cmd_bytes hprobe_entryhits 始终为 0 - 根因:
lto = true使 LTO 跨 crate 内联目标函数,其符号地址处无有效指令,BRK 永远不触发 - 解法:被探测函数添加
#[inline(never)],已应用至run_cmd_bytes / execute_command / handle_builtin_commands
3.2 rbpf 内存边界检查导致 Map lookup 失败
- 现象:
bpf_map_lookup_elem返回指针后解引用触发out of bounds memory load - 解法:静态
LOOKUP_BUFFER(512B,Mutex 保护)+vm.register_allowed_memory()注册
3.3 Rust nightly VaList API 变更
- 现象:nightly >= 2025-12 编译报
cannot find type VaListImpl - 解法:Fork kbpf-basic,适配新 API,PR 已上游合并(#1)
3.4 cargo hardlink 破坏 kallsyms 注入
- 根因:
rust-objcopy --update-section产生新 inode,cargo 下次构建从 deps 硬链接恢复旧文件,覆盖注入的符号 - 解法:
inject_kallsyms(xtask/src/symbols.rs)改为 ELF 原地写入(OpenOptions::write() + seek()),保持 inode 不变(916b8e7)
3.5 Guest-kprobe attach 窗口问题(已知限制)
- 现象:常见
VM is not in Running state或翻译阶段回滚 - 根因:arceos guest 退出过快;linux guest 在当前分支存在早期
not yet implementedpanic - 当前状态:属于 guest 生命周期窗口限制,非 handler/manager 语义缺陷;下一步通过延长 guest 生命周期或使用长期运行 guest 建立验证基线
四、工作量统计
统一口径:以 git diff 计算提交行数(新增/删除/净增分开列示)。
| 时间段 | 新增行 | 删除行 | 净增行 | 主要模块数 |
|---|---|---|---|---|
| 1.20 - 1.31 | ~3,000 | ~0 | ~3,000 | 12(symbols/tracepoint/runtime/maps/helpers/attach 等) |
| 2.1 - 2.7 | ~500 | ~0 | ~500 | 3 子模块(arm_vcpu/axvm/axplat-aarch64-dyn) |
| 2.8 - 2.21 | ~3,050(axebpf 2117 + axvisor 932) | ~2,610(axebpf 1394 + axvisor 1212) | ~440 | axebpf 8 提交 / axvisor 9 提交 |
| 合计 | ~6,550 行新增 | ~2,610 行删除 | ~3,940 行净增 | — |
2.8-2.21 删除量较大(~2600 行)是因为移除了手写 ELF 解析器和独立 ebpf-programs 构建流程,由 aya-obj 和子模块方式替代,属于有计划的代码整理。
五、当前功能状态(截至 2026-02-22)
| 能力 | 状态 | 限制说明 |
|---|---|---|
| eBPF 运行时(rbpf + Map + Helper) | ✅ 可用 | — |
| 静态追踪点(8 个已插桩事件) | ✅ 可用 | vCPU 运行时类事件(vcpu_run/hypercall 等)待 axvm 重构后插桩 |
| hprobe/hretprobe(EL2 动态探针) | ✅ 可用 | 被探测函数须标注 #[inline(never)],否则 LTO 内联导致命中失败 |
| 统一事件管线(stream/dump/stat) | ✅ 可用 | — |
| guest-kprobe 注册/命中统计/事件发射 | ⚠️ 受限可用 | 需 guest 处于 Running 状态且已发生至少一次 VM-exit;arceos guest 退出过快,linux guest 有早期 panic |
| guest-kprobe 异常接管闭环(M1) | ✅ 已完成 | handler 语义正确;端到端验证受限于 guest 窗口 |
| GVA→GPA→HVA 地址翻译(M2a/M2b) | ✅ 基础链路完成 | 翻译失败时自动回滚,无 panic;单步恢复链路未完成 |
| guest 符号名解析(vm<id>:<symbol>) | ❌ 规划中 | 仅支持手动指定 GVA 地址 |
| guest-kprobe 完整恢复链路 | ❌ 待实现 | 缺少 Stage-2 单步(MDSCR_EL1.SS + EC::SingleStepLowerEL) |
| kretprobe trampoline(guest) | ❌ 待实现 | — |
| eBPF 验证器 | ❌ 未实现 | 当前仅允许加载预编译可信程序 |
六、下阶段计划
| 优先级 | 任务 | 预计完成 | 验收定义(DoD) |
|---|---|---|---|
| P0 | guest-kprobe Stage-2 恢复闭环 | 2026-03-07 | 在可持续运行的 guest 上,trace kprobe 命中后 guest 能继续正常执行(无 fault 循环、无 panic) |
| P0 | 接入真实 guest 寄存器上下文 | 2026-03-07 | hprobe_entry 程序能从 eBPF map 读取被探测函数的 x0–x3 参数,与 ksym lookup 地址匹配 |
| P1 | guest 符号表加载 | 2026-03-21 | trace kprobe vm0:<symbol> 命令成功注册,trace list 显示符号名而非裸地址 |
| P1 | kretprobe trampoline(guest) | 2026-03-21 | trace kprobe vm0:<addr> hprobe_exit --ret 能捕获 x0 返回值,输出在 trace stream 中可见 |
| P2 | 标准化 benchmark | 2026-04-04 | 建立 hprobe/tracepoint 命中延迟 p50/p95/p99 基线,写入 docs/perf-baseline.md |
七、技术收获
7.1 系统软件方向
- eBPF 执行模型:程序加载 → ELF 解析 → Map 重定位 → 验证 → 执行 → Helper 调用约定(r1–r5 传参,r0 返回)完整链路
- AArch64 异常体系:EL2 同步异常处理(ESR_EL2.EC/ISS、VBAR_EL2、异常返回)与 EL1 的本质差异
- 动态二进制插桩:kprobe 双 BRK 软件单步原理;页表动态权限修改(AP 位、TLB 刷新、I-cache 同步)完整流程
- Hypervisor 内存虚拟化:AArch64 两级地址翻译(Stage-1:GVA→GPA;Stage-2:GPA→HPA)及执行权限(XN 位)动态控制
7.2 工程方向
- no_std 多库集成:在无标准库环境下集成 5+ 个外部依赖,处理 alloc、spin lock、linker section 等基础设施
- 构建系统深度:定位 cargo hardlink + objcopy inode 冲突,实现 ELF 原地写入方案
- Cargo feature 依赖链:跨 crate feature 传递(
dep/feature语法),hprobe/guest-kprobe feature 层级设计 - 渐进式架构演化:单体 kprobe → hprobe/kprobe 分层 → 统一 TraceEvent 管线,三次迭代均保持已有功能不退化
附录 A:地址翻译链路
GVA(Guest Virtual Address)
↓ guest 页表 walk(从 vCPU 上下文读 TTBR1_EL1,四级页表)
GPA(Guest Physical Address)
↓ Stage-2 页表(axaddrspace,VTTBR_EL2)
HPA(Host Physical Address)
↓ phys_to_virt() 线性映射
HVA(Host Virtual Address,VMM 可直接读写)
Stage-2 fault 模式只需 GPA(修改 XN 位);BRK 注入模式需完整链路至 HVA(读写 guest 指令)。
附录 B:追踪点插桩状态
| 追踪点 | 状态 | 插桩位置 |
|---|---|---|
vmm:vmm_init |
✅ 可用 | kernel/src/main.rs |
vmm:config_load |
✅ 可用 | kernel/src/vmm/config.rs |
vmm:image_load |
✅ 可用 | kernel/src/vmm/images/mod.rs |
vmm:vm_destroy |
✅ 可用 | kernel/src/vmm/vm_list.rs |
vmm:timer_tick/event |
✅ 可用 | kernel/src/vmm/timer.rs |
shell:shell_init/command |
✅ 可用 | kernel/src/shell/ |
vmm:vcpu_run_enter/exit |
❌ 未插桩 | 待 axvm 重构后在运行循环插桩 |
vmm:hypercall |
❌ 未插桩 | 待 axvm 重构 |
vmm:external_interrupt |
❌ 未插桩 | 待 axvm 重构 |
vmm:irq_inject/handle |
❌ 未插桩 | 待 axvm 重构 |
vmm:page_fault |
❌ 未插桩 | arm_vcpu::exception::handle_data_abort |
vmm:memory_map/unmap |
❌ 未插桩 | axaddrspace::AddrSpace |