Hello
欢迎来到我的博客!
2026开源社区实习
这里是实习相关文档的索引:
- 实习目标 - 性能追踪技术方案与背景
- 技术选型 - 组件选择依据
- 开发计划 - 详细的任务分解与时间表
- 第一周周报 - 2026/01/20 - 2026/01/31 周报
- 第二周周报 - 2026/02/01 - 2026/02/07 周报
- 第九周周报 - 2026/03/30 - 2026/04/12 周报
- Iterator支持方案 - Iterator支持方案
- 统一探针调试系统方案 - 统一探针调试系统方案
AxVisor eBPF 性能追踪 - 项目目标
结合 eBPF 技术,跟踪 Hypervisor 和内核的性能
1. 背景与研究基础
1.1 已有技术基础
基于个人 eBPF 项目经验,已具备以下技术栈能力:
| 技术领域 | 已实现能力 |
|---|---|
| eBPF 程序类型 | Kprobe/Kretprobe、Tracepoint、XDP、TC、sock_ops/sk_msg、Uprobe |
| eBPF Maps | HashMap、SockHash、PerCPU Maps |
| 用户态框架 | Aya (Rust eBPF 框架) |
| 内核追踪 | syscall 追踪 (connect/read/write/open/sendto/recvfrom) |
| 应用追踪 | Go runtime uprobe (goroutine 调度、channel 通信) |
| 故障注入 | L3/L4/L7/Syscall/Resource 多层故障注入 |
| TraceID 传播 | 跨进程、跨网络的全链路追踪 |
已有项目核心架构:
┌─────────────────────────────────────────────────────────┐
│ User Space (cli crate) │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ ┌───────────┐ │
│ │ Agent │ │ Proxy │ │ Controller │ │ Monitor │ │
│ └────┬────┘ └────┬─────┘ └─────┬──────┘ └─────┬─────┘ │
└───────┼───────────┼─────────────┼──────────────┼────────┘
│ │ │ │
┌───────┼───────────┼─────────────┼──────────────┼────────┐
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ eBPF Maps (Shared Data) │ │
│ │ PID_GID_MAP | TRACE_FAULT_MAP | FAULT_STATS │ │
│ └─────────────────────────────────────────────────┘ │
│ Kernel Space (ebpf crate) │
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ ┌───────────┐ │
│ │ Kprobes │ │ Uprobes │ │ XDP/TC │ │ sock_ops │ │
│ └─────────┘ └──────────┘ └────────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 AxVisor 现状分析
基于对 AxVisor 代码的分析,当前架构如下:
┌────────────────────────────────────────────────────────┐
│ AxVisor Hypervisor │
│ ┌──────────────────────────────────────────────────┐ │
│ │ kernel/ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ vmm/ │ │ hal/ │ │ shell/ │ │ │
│ │ │ config │ │ arch/* │ │ command │ │ │
│ │ │ vcpus │ │ cache │ │ │ │ │
│ │ │ timer │ │ │ │ │ │ │
│ │ └────┬────┘ └────┬────┘ └─────────┘ │ │
│ └───────┼────────────┼─────────────────────────────┘ │
│ │ │ │
│ ┌───────▼────────────▼─────────────────────────────┐ │
│ │ modules/ (Hypervisor modules) │ │
│ │ axvm | axvcpu | axaddrspace | axdevice │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────▼─────────────────────────┐ │
│ │ ArceOS Kernel (axstd, axhal, axtask) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ VM 1 │ │ VM 2 │ │ VM N │
│ (Guest)│ │ (Guest)│ │ (Guest)│
└────────┘ └────────┘ └────────┘
关键性能瓶颈点:
| 序号 | 瓶颈点 | 位置 | 性能敏感度 |
|---|---|---|---|
| 1 | VM Exit/Entry 处理 | AxVCpu::run() 循环 | 极高 |
| 2 | Hypercall 处理 | AxVCpuExitReason::Hypercall 分支 | 高 |
| 3 | 中断注入 | AxVCpu::inject_interrupt() | 高 |
| 4 | MMIO/SysReg 模拟 | AxVCpuExitReason::MmioRead/Write | 中 |
| 5 | 嵌套页表处理 | AxVCpuExitReason::NestedPageFault | 高 |
| 6 | vCPU 状态转换 | VCpuState 状态机 | 中 |
1.3 AxVisor 现有可观测性基础
AxVisor 当前已具备一定的可观测性能力,但均为硬编码采集方式,缺乏灵活的动态追踪机制:
1.3.1 日志追踪 (Log-based Tracing)
当前通过 trace!/debug!/info!/warn!/error! 宏在关键路径输出日志:
| 模块 | 追踪点 | 日志级别 | 采集内容 |
|---|---|---|---|
axvcpu/vcpu.rs | run() 方法 | debug | vCPU 状态转换 |
axvcpu/exit.rs | Exit 处理 | debug | Exit reason、参数 |
axvm/vm/mod.rs | VM 生命周期 | info | 创建、启动、停止 |
kernel/vmm/mod.rs | VMM 初始化 | info | 虚拟化启用状态 |
现有日志追踪示例(基于 AxVCpuExitReason):
When VM Exit occurs:
- Only records Exit type: Hypercall/MmioRead/ExternalInterrupt etc.
- Only records parameters: hypercall_nr, addr, vector etc.
- No latency stats, no frequency stats, no aggregation analysis
1.3.2 Shell 状态查询
通过 vm show 命令提供运行时状态查询:
| 命令 | 功能 | 数据来源 |
|---|---|---|
vm list | 列出所有 VM | vm_list 模块 |
vm show <id> | 显示 VM 详情 | Vm 结构体 |
支持的统计项:
- VM 状态 (Running/Stopped)
- vCPU 数量
- 内存大小
- VM 名称
1.3.3 现有方案的局限性
| 局限性 | 描述 | eBPF 解决方案 |
|---|---|---|
| 无延迟统计 | 仅记录事件发生,无处理时长 | Tracepoint + 时间戳采集 |
| 无频率统计 | 无法统计 VM Exit/Hypercall 频率 | eBPF Map 计数聚合 |
| 无直方图 | 无法分析延迟分布 | Histogram Map |
| 静态插桩 | 需重编译才能修改追踪点 | Kprobe 动态附加 |
| 高开销 | 日志 I/O 影响性能 | Ring buffer 异步采集 |
| 无跨 VM 关联 | 无法追踪 IVC 端到端延迟 | TraceID 传播 |
1.3.4 时间采集基础设施
AxVisor 已有时间获取 API,可复用于 eBPF 追踪:
| API | 位置 | 用途 |
|---|---|---|
axhal::time::monotonic_time_nanos() | ArceOS HAL | 高精度单调时钟 |
axhal::time::wall_time() | ArceOS HAL | 墙钟时间 |
可复用点: 在 eBPF tracepoint 中调用 monotonic_time_nanos() 计算事件延迟。
2. 研究目标对比分析
2.1 方向统一性
| 维度 | 已有 eBPF 项目 | AxVisor eBPF 目标 | 统一程度 |
|---|---|---|---|
| 技术栈 | Aya + Rust eBPF | Aya + Rust eBPF | ✅ 完全统一 |
| 追踪粒度 | 应用级 (Go runtime) | 系统级 (Hypervisor) | ✅ 互补 |
| 数据采集 | Uprobe + Kprobe | Tracepoint + Kprobe | ✅ 技术共享 |
| Map 设计 | TraceID 关联 | VM/VCpu 关联 | ✅ 模式相同 |
| 用户态交互 | CLI + K8s CRD | CLI + Shell 集成 | ✅ 架构复用 |
2.2 方向差异性
| 维度 | 已有 eBPF 项目 | AxVisor eBPF |
|---|---|---|
| 目标层级 | L7 应用层 | L0 Hypervisor 层 |
| 追踪对象 | Go 协程、HTTP/gRPC 请求 | VM Exit、Hypercall、中断 |
| 内核依赖 | 标准 Linux 内核 | ArceOS 自定义内核 |
| eBPF 加载 | 标准 bpf() syscall | 需要 ArceOS eBPF 支持 |
| 符号解析 | /proc/kallsyms | 需要 ArceOS ksym 支持 |
2.3 关键挑战
┌─────────────────────────────────────────────────────────────────────────┐
│ Challenges and Solutions │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Challenge 1: ArceOS lacks standard Linux eBPF subsystem │
│ ──────────────────────────────────────────────── │
│ → Solution: Port rbpf eBPF VM based on Starry-OS approach │
│ → Reuse: kbpf-basic (Maps), tracepoint (tracing framework) │
│ │
│ Challenge 2: ArceOS lacks /proc/kallsyms │
│ ──────────────────────────────────────────────── │
│ → Solution: Use Starry-OS ksym module, generate symbol table at build │
│ → Method: Modify build process, extract symbols from ELF into kernel │
│ │
│ Challenge 3: ArceOS lacks tracepoint infrastructure │
│ ──────────────────────────────────────────────── │
│ → Solution: Port Starry-OS tracepoint implementation │
│ → Extend: Define Hypervisor-specific tracepoints (VM Exit, Hypercall) │
│ │
│ Challenge 4: Hypervisor runs in EL2/VMX Root, traditional eBPF in EL1 │
│ ──────────────────────────────────────────────── │
│ → Solution: Design Hypervisor-aware eBPF Helper functions │
│ → Add: bpf_get_current_vm_id(), bpf_get_current_vcpu_id(), etc. │
│ │
│ Challenge 5: #![no_std] environment restrictions │
│ ──────────────────────────────────────────────── │
│ → Solution: Choose no_std compatible components (rbpf, kbpf-basic) │
│ → Method: Fork and adapt if necessary, remove std library dependencies │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3. 技术研究方案
3.1 整体架构设计
┌──────────────────────────────────────────────────────────────────────────┐
│ AxVisor eBPF Tracing Architecture │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ User Space / Shell Layer │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────────┐ │ │
│ │ │ Loader │ │ Analyzer │ │ Exporter │ │ Shell Commands │ │ │
│ │ │(BPF Load)│ │(Aggregate)│ │JSON/Table│ │ trace list/stat │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────────┬─────────┘ │ │
│ └───────┼─────────────┼─────────────┼───────────────────┼────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ eBPF Maps Layer │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
│ │ │VM_EXIT_STATS│ │HYPERCALL_LAT│ │ IRQ_STATS │ │VCPU_RUNTIME│ │ │
│ │ │ HashMap │ │ Histogram │ │ PerCPU Map │ │ HashMap │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ Write Stats │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ eBPF Programs Layer │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │
│ │ │ Tracepoint │ │ Tracepoint │ │ Tracepoint │ │ Tracepoint│ │ │
│ │ │ vcpu_run_* │ │ hypercall │ │ mmio_access │ │ ept_viol │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └─────┬─────┘ │ │
│ └─────────┼────────────────┼────────────────┼───────────────┼───────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ axtracepoint Module │ │
│ │ Tracepoint definition, registration, trigger │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ Tracepoint Trigger │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ VMM Critical Path │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌─────────────────┐ │ │
│ │ │AxVCpu:: │ │ Exit │ │ MMIO/ │ │ NestedPageFault │ │ │
│ │ │ run() │ │ Handling │ │ SysReg │ │ Handling │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ └─────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
3.2 追踪点设计
基于 AxVCpuExitReason 枚举和 VMM 关键路径,定义以下追踪点:
| 子系统 | 追踪点 | 触发位置 | 采集数据 |
|---|---|---|---|
| vmm | vcpu_run_enter | AxVCpu::run() 入口 | vm_id, vcpu_id, timestamp |
| vmm | vcpu_run_exit | AxVCpu::run() 返回 | vm_id, vcpu_id, exit_reason, duration_ns |
| vmm | hypercall | Hypercall 处理完成 | vm_id, vcpu_id, nr, args, duration_ns |
| vmm | mmio_read | MmioRead 处理 | vm_id, addr, width, duration_ns |
| vmm | mmio_write | MmioWrite 处理 | vm_id, addr, width, data, duration_ns |
| vmm | sysreg_access | SysReg 读写 | vm_id, addr, is_write, duration_ns |
| vmm | interrupt_inject | 中断注入 | vm_id, vcpu_id, vector |
| vmm | ept_violation | NestedPageFault 处理 | vm_id, gpa, access_flags, duration_ns |
| vmm | vcpu_state_change | VCpuState 转换 | vm_id, vcpu_id, from_state, to_state |
| vmm | vm_lifecycle | VM 创建/启动/停止 | vm_id, event_type |
3.3 性能指标定义
| 指标类别 | 指标名称 | 单位 | 采集方式 |
|---|---|---|---|
| 频率 | VM Exit 频率 | exits/sec | Map 计数 + 时间窗口 |
| 频率 | Hypercall 调用频率 | calls/sec | Map 计数 + 时间窗口 |
| 延迟 | VM Exit 处理延迟 | ns | 时间戳差值 |
| 延迟 | Hypercall 处理延迟 | ns | 时间戳差值 |
| 延迟 | 中断注入延迟 | ns | 时间戳差值 |
| 延迟 | MMIO 模拟延迟 | ns | 时间戳差值 |
| 延迟 | EPT Violation 处理延迟 | ns | 时间戳差值 |
| 分布 | Exit Reason 分布 | % | Map 聚合统计 |
| 分布 | Hypercall 号分布 | % | Map 聚合统计 |
| 分布 | 延迟直方图 | count/bucket | Histogram Map |
| 利用率 | vCPU 运行时间占比 | % | 累计运行时间 / 墙钟时间 |
3.4 组件复用策略
| 来源 | 组件 | 复用方式 | 工作量 |
|---|---|---|---|
| Starry-OS | ksym | 直接引用 | 低 |
| Starry-OS | kbpf-basic | 直接引用 | 低 |
| Starry-OS | tp-lexer | 直接引用 | 低 |
| Starry-OS | tracepoint | 适配后复用 | 中 |
| Starry-OS | kprobe | 后续扩展 | 高 |
| 社区 | rbpf | 适配 no_std | 低 |
4. 预期成果
4.1 技术产出
- axebpf 模块: ArceOS 兼容的 eBPF 运行时
- axksym 模块: 内核符号表生成与查找
- axtracepoint 模块: Tracepoint 定义与注册框架
- Shell trace 命令: 追踪点控制与统计数据展示
4.2 文档产出
- AxVisor eBPF 追踪使用指南
- ArceOS eBPF 子系统设计文档
- Hypervisor 性能分析最佳实践
4.3 对个人技术栈的扩展
- 技术深度扩展: 从应用层追踪扩展到 Hypervisor 层
- 跨层追踪能力: 可实现 Guest → Hypervisor → Host 全栈追踪
- 故障注入扩展: 可在 Hypervisor 层注入故障 (VM Exit 延迟、中断丢失等)
4.4 预期效果演示
axvisor:/$ trace list
Tracepoints:
vmm:vcpu_run_enter [disabled]
vmm:vcpu_run_exit [disabled]
vmm:hypercall [disabled]
vmm:mmio_read [disabled]
vmm:mmio_write [disabled]
vmm:interrupt_inject [disabled]
vmm:ept_violation [disabled]
axvisor:/$ trace enable vmm:vcpu_run_exit
Enabled: vmm:vcpu_run_exit
axvisor:/$ trace stat
┌──────────────────────────────────────────────────────────────┐
│ VM Exit Statistics │
├────────┬─────────┬──────────────┬─────────┬─────────┬────────┤
│ VM ID │ vCPU ID │ Exit Reason │ Count │ Avg(μs) │ Max(μs)│
├────────┼─────────┼──────────────┼─────────┼─────────┼────────┤
│ 1 │ 0 │ Hypercall │ 12345 │ 2.3 │ 15.7 │
│ 1 │ 0 │ ExtInterrupt │ 8901 │ 1.1 │ 8.2 │
│ 1 │ 0 │ Halt │ 567 │ 45.2 │ 102.1 │
│ 1 │ 0 │ MmioRead │ 3456 │ 1.8 │ 12.3 │
│ 2 │ 0 │ Hypercall │ 4567 │ 2.8 │ 12.3 │
└────────┴─────────┴──────────────┴─────────┴─────────┴────────┘
axvisor:/$ trace latency hypercall
Hypercall Latency Distribution:
0-1μs : ████████████████████████████████ 45.2%
1-2μs : ████████████████████ 28.1%
2-5μs : ██████████ 14.3%
5-10μs : ████ 6.8%
10-50μs : ██ 4.1%
50μs+ : █ 1.5%
附录 A: 时间线 (12 周)
策略: 移植 Starry-OS 的 eBPF 工作到 AxVisor。由于 Starry-OS 是基于 ArceOS 扩展的宏内核, AxVisor 是基于 ArceOS 扩展的 VMM,大部分组件可直接引用或稍作适配。
| 阶段 | 时间 | 里程碑 | 说明 |
|---|---|---|---|
| Phase 1 | Week 1-2 | 基础设施 | 依赖集成、符号表、追踪点框架 |
| Phase 2 | Week 3-4 | eBPF 运行时 | rbpf VM、Helper 函数、Map 支持 |
| Phase 3 | Week 5-6 | VMM 追踪点与 Shell | Hypervisor 追踪点、VMM 插桩、Shell 命令 |
| Phase 4 | Week 7-8 | Kprobe/Kretprobe | 动态探针、多架构适配 |
| Phase 5 | Week 9-10 | 验证器与 Uprobe | eBPF 验证器、Uprobe 可行性研究 |
| Phase 6 | Week 11-12 | 测试与文档 | 性能测试、多架构验证、文档 |
详细里程碑
Phase 1 - 基础设施 (Week 1-2)
- 依赖集成 (ksym, kbpf-basic, tp-lexer, tracepoint)
modules/axebpf模块创建xtask symbols符号表生成- 追踪点框架基础
Phase 2 - eBPF 运行时 (Week 3-4)
- rbpf VM 集成 (no_std 适配)
- 标准 Helper 函数实现
- Map 数据结构支持 (HashMap, Array, RingBuf)
Phase 3 - VMM 追踪点与 Shell (Week 5-6)
- 定义 VMM 核心追踪点 (7+ 个)
- VMM 关键路径插桩
- Shell
trace命令组 - Hypervisor 专用 Helper
Phase 4 - Kprobe/Kretprobe (Week 7-8)
- 移植 Starry-OS kprobe 库
- aarch64/x86_64 断点机制适配
- kretprobe 函数返回探针
- Shell kprobe 命令集成
Phase 5 - 验证器与 Uprobe (Week 9-10)
- eBPF 验证器 (PREVAIL 或轻量实现)
- 安全检查 (内存边界、程序终止性)
- Uprobe 可行性研究
- Uprobe 原型 (如可行)
Phase 6 - 测试与文档 (Week 11-12)
- 单元测试、集成测试
- 性能测试与优化
- 多架构验证
- 用户文档和 API 文档
AxVisor eBPF 性能追踪 - 技术选型
本文档详细介绍 eBPF 工作原理、VMM 层支持需求,以及技术选型依据。
1. eBPF 技术原理
1.1 什么是 eBPF
eBPF (extended Berkeley Packet Filter) 是一种革命性的内核沙箱技术,允许在内核中安全地运行用户定义的程序,而无需修改内核源码或加载内核模块。
核心价值:
| 特性 | 传统方案 | eBPF 方案 |
|---|---|---|
| 添加追踪 | 修改源码 → 重编译 → 重启 | 运行时动态加载 |
| 安全性 | 可能导致内核崩溃 | 验证器保证安全 |
| 性能 | 日志 I/O 开销大 | 内核空间聚合,极低开销 |
| 灵活性 | 固定采集逻辑 | 可编程采集策略 |
1.2 eBPF 执行流程
┌─────────────────────────────────────────────────────────────────────────────┐
│ eBPF Complete Execution Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ╔═════════════════════════════════════════════════════════════════════╗ │
│ ║ Compile Phase (Host) ║ │
│ ╠═════════════════════════════════════════════════════════════════════╣ │
│ ║ ║ │
│ ║ ┌────────────────┐ Rust/C ┌────────────────────────────┐ ║ │
│ ║ │ eBPF Source │ ──────────► │ eBPF Bytecode (.bpf ELF) │ ║ │
│ ║ │ (Rust + Aya) │ Compiler │ Platform-independent ISA │ ║ │
│ ║ └────────────────┘ └────────────────────────────┘ ║ │
│ ║ ║ │
│ ╚═════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ Transfer to target system │
│ ╔═════════════════════════════════════════════════════════════════════╗ │
│ ║ Load Phase (Kernel) ║ │
│ ╠═════════════════════════════════════════════════════════════════════╣ │
│ ║ ║ │
│ ║ ┌────────────────┐ ┌────────────────────────────┐ ║ │
│ ║ │ ELF Parser │ ──────────► │ Bytecode + Map Defs │ ║ │
│ ║ └────────────────┘ └─────────────┬──────────────┘ ║ │
│ ║ │ ║ │
│ ║ ▼ ║ │
│ ║ ┌────────────────┐ Safety ┌────────────────────────────┐ ║ │
│ ║ │ Verifier │ ◄────────── │ Program Bytecode │ ║ │
│ ║ │ │ │ │ ║ │
│ ║ └───────┬────────┘ └────────────────────────────┘ ║ │
│ ║ │ ║ │
│ ║ │ Verification passed ║ │
│ ║ ▼ ║ │
│ ║ ┌────────────────┐ ┌────────────────────────────┐ ║ │
│ ║ │ JIT/Interpreter│ ──────────► │ Executable Program │ ║ │
│ ║ └────────────────┘ └─────────────┬──────────────┘ ║ │
│ ║ │ ║ │
│ ║ ▼ ║ │
│ ║ ┌────────────────────────────────────────────────────────────┐ ║ │
│ ║ │ Attach to Hook Point (Tracepoint/Kprobe) │ ║ │
│ ║ └────────────────────────────────────────────────────────────┘ ║ │
│ ║ ║ │
│ ╚═════════════════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ Event triggered │
│ ╔═════════════════════════════════════════════════════════════════════╗ │
│ ║ Execute Phase (Runtime) ║ │
│ ╠═════════════════════════════════════════════════════════════════════╣ │
│ ║ ║ │
│ ║ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ ║ │
│ ║ │ Hook Trigger │ ───────► │ eBPF Program │ ◄──────► │ Maps │ ║ │
│ ║ │ (VM Exit etc)│ │ Execute │ R/W │ (Data) │ ║ │
│ ║ └──────────────┘ └──────┬───────┘ └────┬─────┘ ║ │
│ ║ │ │ ║ │
│ ║ │ Call │ Share ║ │
│ ║ ▼ ▼ ║ │
│ ║ ┌──────────────┐ ┌──────────┐ ║ │
│ ║ │ Helpers │ │ Userspace│ ║ │
│ ║ │(Kernel Svc) │ │ Read │ ║ │
│ ║ └──────────────┘ └──────────┘ ║ │
│ ║ ║ │
│ ╚═════════════════════════════════════════════════════════════════════╝ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
1.3 eBPF 核心组件详解
1.3.1 eBPF 字节码
eBPF 使用精简的 RISC 风格指令集,运行在虚拟的 64 位寄存器机器上:
| 组件 | 规格 | 说明 |
|---|---|---|
| 寄存器 | R0-R10 (64-bit) | R0=返回值, R1-R5=参数, R10=帧指针 |
| 栈空间 | 512 字节 | 固定大小,编译时确定使用量 |
| 指令大小 | 8 字节/指令 | 固定宽度,便于解析 |
| 程序大小限制 | 100万指令 (Linux 5.2+) | 防止无限循环 |
指令格式:
┌──────────┬──────────┬──────────┬──────────┬──────────────────┐
│ opcode │ dst_reg │ src_reg │ offset │ immediate │
│ 8 bits │ 4 bits │ 4 bits │ 16 bits │ 32 bits │
└──────────┴──────────┴──────────┴──────────┴──────────────────┘
1.3.2 eBPF Maps
Maps 是 eBPF 程序与用户态之间共享数据的核心机制:
┌─────────────────────────────────────────────────────────────────┐
│ eBPF Maps Mechanism │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Userspace Tool Kernel eBPF Program │
│ (ax-trace) (tracepoint handler) │
│ │ │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ eBPF Map │ │ │
│ │ │ ┌─────────┬─────────┐ │ │ │
│ Read ──┼────►│ │ Key │ Value │ │◄────┼── Write │
│ Stats │ │ ├─────────┼─────────┤ │ │ Stats │
│ │ │ │ exit=1 │ 12345 │ │ │ │
│ │ │ │ exit=2 │ 6789 │ │ │ │
│ │ │ │ exit=3 │ 234 │ │ │ │
│ │ │ └─────────┴─────────┘ │ │ │
│ │ └────────────────────────┘ │ │
│ │
└─────────────────────────────────────────────────────────────────┘
支持的 Map 类型(本项目使用):
| Map 类型 | 数据结构 | 时间复杂度 | 典型用途 |
|---|---|---|---|
HashMap | 哈希表 | O(1) | 按 exit_reason 统计计数 |
Array | 固定数组 | O(1) | 延迟直方图桶 |
PerCPU HashMap | 每 CPU 哈希表 | O(1) | 避免锁竞争的统计 |
1.3.3 Helper 函数
eBPF 程序通过 Helper 函数访问内核服务,这是沙箱的受控出口:
| Helper ID | 函数名 | 功能 | 安全性 |
|---|---|---|---|
| 1 | bpf_map_lookup_elem | 查找 Map 元素 | 返回指针需边界检查 |
| 2 | bpf_map_update_elem | 更新 Map 元素 | 原子操作 |
| 5 | bpf_ktime_get_ns | 获取时间戳 | 只读,无副作用 |
| 6 | bpf_trace_printk | 调试输出 | 生产环境应禁用 |
1.3.4 验证器 (Verifier)
验证器是 eBPF 安全性的核心保障,通过静态分析确保程序安全:
验证内容:
| 检查项 | 目的 | 实现方式 |
|---|---|---|
| 程序终止性 | 防止无限循环 | 限制指令数 + 禁止向后跳转 |
| 内存安全 | 防止越界访问 | 追踪寄存器类型和范围 |
| 类型安全 | 防止类型混淆 | 抽象解释 + 类型推导 |
| 栈边界 | 防止栈溢出 | 静态分析栈使用量 |
Verification Flow
│
▼
┌───────────────────────┐
│ Build CFG (Control │
│ Flow Graph) │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ Simulate each path │
│ Track register state │
└───────────┬───────────┘
│
▼
┌───────────────────────┐ ┌─────────────┐
│ Unsafe operation? │─Yes─►│ Reject Load │
└───────────┬───────────┘ └─────────────┘
│ No
▼
┌───────────────────────┐
│ Allow program │
└───────────────────────┘
1.4 eBPF 程序类型
与 Hypervisor 追踪相关的程序类型:
| 程序类型 | 触发时机 | 上下文数据 | 适用场景 |
|---|---|---|---|
| Tracepoint | 预定义的静态插桩点 | 结构化事件参数 | VM Exit 统计(核心) |
| Kprobe | 任意函数入口 | 寄存器状态 | 动态探测(扩展) |
| Kretprobe | 任意函数返回 | 返回值 + 寄存器 | 延迟测量(扩展) |
2. VMM 层 eBPF 支持需求分析
2.1 与标准 Linux eBPF 的差异
AxVisor 基于 ArceOS 自定义内核,需要从零实现 eBPF 支持:
| 组件 | Linux 实现 | AxVisor 需求 |
|---|---|---|
| 系统调用 | bpf() syscall | 不存在 → 直接 API |
| 符号表 | /proc/kallsyms | 不存在 → 编译时生成 |
| Tracepoint | 内核内置 trace_* | 不存在 → 移植 Starry-OS |
| Kprobe | 内核内置 | 不存在 → 移植 Starry-OS |
| 验证器 | 内核内置 | 可选 → 初期信任程序 |
2.2 Hypervisor 特权级问题
AxVisor 运行在更高特权级,传统 eBPF 设计需要适配:
┌─────────────────────────────────────────────────────────────────┐
│ Virtualization Privilege Levels │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Guest OS (EL1 / Ring 0) │ │
│ │ Traditional Linux eBPF runs here │ │
│ │ Tracing targets: syscalls, kernel functions │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ VM Exit / VM Entry │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ AxVisor Hypervisor (EL2 / VMX Root) ◄── Our tracing layer│ │
│ │ │ │
│ │ Tracing targets: │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ VM Exit │ │ Hypercall │ │ Interrupt Inject│ │ │
│ │ │ Latency │ │ Handling │ │ Latency │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │EPT Handling │ │MMIO Emulate │ │ vCPU Scheduling │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Hardware │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2.3 VMM 需要提供的支持
为在 AxVisor 中实现 eBPF 追踪,需要 VMM 层提供以下支持:
2.3.1 追踪点插桩
在 VMM 关键路径插入追踪点宏调用:
位置:modules/axvcpu/src/vcpu.rs
#![allow(unused)]
fn main() {
// AxVCpu::run() 方法中需要添加追踪点
pub fn run(&self) -> AxResult<AxVCpuExitReason> {
self.transition_state(VCpuState::Ready, VCpuState::Running)?;
// ▼ 追踪点:VM Entry
trace_vmm_vcpu_run_enter!(self.vm_id(), self.id());
let result = self.manipulate_arch_vcpu(VCpuState::Running, VCpuState::Ready, |arch_vcpu| {
arch_vcpu.run()
});
// ▼ 追踪点:VM Exit
if let Ok(ref exit_reason) = result {
trace_vmm_vcpu_run_exit!(self.vm_id(), self.id(), exit_reason);
}
result
}
}
2.3.2 上下文信息获取
eBPF 程序需要访问当前 VM/vCPU 上下文,VMM 需要提供:
| 信息 | 来源 | Helper 函数 |
|---|---|---|
| 当前 VM ID | AxVCpu::vm_id() | bpf_get_current_vm_id() |
| 当前 vCPU ID | AxVCpu::id() | bpf_get_current_vcpu_id() |
| 当前时间戳 | axhal::time::monotonic_time_nanos() | bpf_ktime_get_ns() |
2.3.3 时间测量基础设施
延迟统计需要高精度时间源,AxVisor 已有基础:
#![allow(unused)]
fn main() {
// 已存在于 ArceOS
pub fn monotonic_time_nanos() -> u64;
// 需要封装为 eBPF Helper
fn bpf_ktime_get_ns() -> u64 {
axhal::time::monotonic_time_nanos()
}
}
2.3.4 Exit Reason 编码
为统计分析,需要将 AxVCpuExitReason 编码为数值:
#![allow(unused)]
fn main() {
// 基于 modules/axvcpu/src/exit.rs
impl AxVCpuExitReason {
pub fn to_trace_code(&self) -> u32 {
match self {
Self::Hypercall { .. } => 1,
Self::MmioRead { .. } => 2,
Self::MmioWrite { .. } => 3,
Self::SysRegRead { .. } => 4,
Self::SysRegWrite { .. } => 5,
Self::IoRead { .. } => 6,
Self::IoWrite { .. } => 7,
Self::ExternalInterrupt { .. } => 8,
Self::NestedPageFault { .. } => 9,
Self::Halt => 10,
Self::CpuUp { .. } => 11,
Self::CpuDown { .. } => 12,
Self::SystemDown => 13,
Self::Nothing => 14,
Self::FailEntry { .. } => 15,
Self::SendIPI { .. } => 16,
}
}
}
}
3. 技术选型
3.1 选型原则
| 原则 | 说明 | 权重 |
|---|---|---|
| ArceOS 兼容 | 必须支持 #![no_std] 环境 | 必须 |
| Rust 优先 | 与 AxVisor 技术栈保持一致 | 高 |
| 许可证兼容 | 必须与 AxVisor 许可证兼容 | 必须 |
| 最小依赖 | 优先选择轻量级组件 | 中 |
| 可复用性 | 优先复用 Starry-OS 已验证组件 | 高 |
3.2 组件选型结果
| 功能 | 选择 | 来源 | 复用方式 | 阶段 |
|---|---|---|---|---|
| eBPF 虚拟机 | rbpf | 社区 | 适配 no_std | Phase 2 |
| eBPF Maps | kbpf-basic | Starry-OS | 直接引用 | Phase 2 |
| 符号表生成 | ksym | Starry-OS | 直接引用 | Phase 1 |
| 追踪点框架 | tracepoint | Starry-OS | 适配后复用 | Phase 1 |
| 过滤表达式 | tp-lexer | Starry-OS | 直接引用 | Phase 3 |
| 动态探针 | kprobe | Starry-OS | 移植适配 | Phase 4 |
| 验证器 | PREVAIL | 社区 | 集成或轻量实现 | Phase 5 |
3.3 各组件详解
3.3.1 rbpf - eBPF 虚拟机
| 属性 | 值 |
|---|---|
| 仓库 | https://github.com/qmonnet/rbpf |
| 许可证 | MIT |
| 语言 | Rust |
| JIT 支持 | x86_64 |
选择理由:
- 纯 Rust 实现,无 C 依赖
- 支持
# - 提供解释器和 JIT 两种执行模式
- API 简洁,易于集成
VM 类型:
| 类型 | 说明 | 适用场景 |
|---|---|---|
EbpfVmRaw | 接收原始字节缓冲区 | Tracepoint 事件(采用) |
EbpfVmNoData | 无输入数据 | 简单计算 |
3.3.2 kbpf-basic - eBPF Maps
| 属性 | 值 |
|---|---|
| 仓库 | https://github.com/Starry-OS/kbpf-basic |
| 许可证 | MIT/Apache-2.0 |
| no_std | ✅ |
支持的 Map 类型:
| 类型 | 说明 |
|---|---|
BPF_MAP_TYPE_ARRAY | 固定大小数组 |
BPF_MAP_TYPE_HASH | 动态哈希表 |
BPF_MAP_TYPE_LRU_HASH | LRU 淘汰哈希表 |
3.3.3 ksym - 内核符号表
| 属性 | 值 |
|---|---|
| 仓库 | https://github.com/Starry-OS/ksym |
| 许可证 | MIT/Apache-2.0 |
| no_std | ✅ |
功能:
- 编译时从 ELF 生成压缩符号表
- 运行时零拷贝查找
- 支持 Rust 符号 demangle
3.3.4 tracepoint - 追踪点框架
| 属性 | 值 |
|---|---|
| 仓库 | https://github.com/Starry-OS/tracepoint |
| 许可证 | MIT/Apache-2.0 |
| no_std | ✅ |
核心功能:
define_event_trace!宏定义追踪点- 线程安全的事件管理器
- 低开销的静态追踪点
3.3.5 kprobe - 动态探针
| 属性 | 值 |
|---|---|
| 仓库 | https://github.com/Starry-OS/kprobe |
| 许可证 | MIT/Apache-2.0 |
| no_std | ✅ |
| 架构支持 | x86_64, aarch64, riscv64, loongarch64 |
核心功能:
- kprobe: 函数入口探针
- kretprobe: 函数返回探针
- 断点机制适配多架构
- 与 eBPF 程序集成
3.3.6 PREVAIL - eBPF 验证器
| 属性 | 值 |
|---|---|
| 仓库 | https://github.com/vbpf/ebpf-verifier |
| 许可证 | MIT |
| 语言 | C++ |
核心功能:
- 内存边界检查
- 程序终止性验证
- 类型安全检查
- 独立于 Linux 内核
备选方案: 如 PREVAIL 集成困难,可实现轻量级验证器,仅检查关键安全属性。
3.4 许可证兼容性
| 组件 | 许可证 | 与 AxVisor 兼容 |
|---|---|---|
| rbpf | MIT | ✅ |
| kbpf-basic | MIT/Apache-2.0 | ✅ |
| ksym | MIT/Apache-2.0 | ✅ |
| tracepoint | MIT/Apache-2.0 | ✅ |
| tp-lexer | MIT/Apache-2.0 | ✅ |
| kprobe | MIT/Apache-2.0 | ✅ |
| PREVAIL | MIT | ✅ |
AxVisor 许可证: GPL-3.0-or-later OR Apache-2.0 OR MulanPubL-2.0
4. 架构设计
4.1 模块层次结构
架构决策: 统一在
modules/axebpf内实现所有功能,不创建独立的 axksym/axtracepoint 模块。
┌─────────────────────────────────────────────────────────────────────────────┐
│ AxVisor eBPF Tracing Architecture │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Shell Command Layer │ │
│ │ kernel/src/shell/commands/trace.rs │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ trace list | enable | disable | stat | load | unload │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────▼───────────────────────────────────┐ │
│ │ axebpf Module (Unified) │ │
│ │ modules/axebpf/ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ symbols.rs │ 符号表管理 (封装 ksym) │ │ │
│ │ ├──────────────────────────────────────────────────────────────┤ │ │
│ │ │ tracepoint.rs │ 追踪点框架 (封装 tracepoint/ktracepoint) │ │ │
│ │ ├──────────────────────────────────────────────────────────────┤ │ │
│ │ │ runtime.rs │ eBPF 运行时 (封装 rbpf VM) │ │ │
│ │ ├──────────────────────────────────────────────────────────────┤ │ │
│ │ │ helpers.rs │ Helper 函数 (ktime, trace_printk, vm_id...) │ │ │
│ │ ├──────────────────────────────────────────────────────────────┤ │ │
│ │ │ maps.rs │ Map 数据结构 (封装 kbpf-basic) │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────┬───────────────────────────────────┘ │
│ │ Tracepoint Trigger │
│ ┌───────────────────────────────────▼───────────────────────────────────┐ │
│ │ VMM Tracepoint Calls │ │
│ │ modules/axvcpu/, modules/axvm/, kernel/src/vmm/ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │vcpu_run_* │ │ hypercall │ │ mmio_* │ │ ept_viol │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ xtask Symbol Generation (Compile-time) │ │
│ │ xtask/src/symbols.rs │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ cargo xtask symbols → 从 ELF 生成 kallsyms.bin │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Starry-OS Dependencies (Direct Reference) │ │
│ │ ┌────────┐ ┌────────────┐ ┌──────────┐ ┌──────┐ ┌───────────┐ │ │
│ │ │ ksym │ │ tracepoint │ │ tp-lexer │ │ rbpf │ │kbpf-basic │ │ │
│ │ └────────┘ └────────────┘ └──────────┘ └──────┘ └───────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
4.2 数据流
┌─────────────────────────────────────────────────────────────────────────────┐
│ Tracing Data Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ VMM Event eBPF Process User Query │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ VM Exit │ │ eBPF │ │ trace │ │
│ │ Occurs │ ─────────────►│ Program │ │ stat │ │
│ └─────────┘ Trigger TP │ Execute │ └────┬────┘ │
│ └────┬────┘ │ │
│ │ │ │
│ ▼ Write │ Read │
│ ┌─────────┐ │ │
│ │ Maps │ ◄──────────────────┘ │
│ │ (Stats) │ │
│ └─────────┘ │
│ │
│ Example: VM Exit Statistics │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ HashMap<VmExitKey, VmExitStats> │ │
│ │ ┌─────────────────────┬─────────────────────────────────┐ │ │
│ │ │ Key │ Value │ │ │
│ │ │ (vm=1, exit=Hyper) │ {count=12345, avg=1.2μs, ...} │ │ │
│ │ │ (vm=1, exit=MMIO) │ {count=6789, avg=2.3μs, ...} │ │ │
│ │ │ (vm=2, exit=Hyper) │ {count=4567, avg=1.5μs, ...} │ │ │
│ │ └─────────────────────┴─────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
4.3 依赖关系
架构决策: 所有功能统一在
modules/axebpf模块内实现,不创建独立的 axksym/axtracepoint 模块。 这样更简洁,减少模块间耦合。
modules/axebpf/
├── Cargo.toml
├── src/
│ ├── lib.rs # 模块入口
│ ├── symbols.rs # 符号表管理 (封装 ksym)
│ ├── tracepoint.rs # 追踪点框架 (封装 tracepoint)
│ ├── runtime.rs # eBPF 运行时 (封装 rbpf)
│ ├── helpers.rs # Helper 函数
│ └── maps.rs # Map 数据结构 (封装 kbpf-basic)
│
└── 依赖:
├── ksym (Starry-OS: 符号表生成与查找)
├── tracepoint (Starry-OS: 追踪点核心,package = "ktracepoint")
├── tp-lexer (Starry-OS: 过滤表达式)
├── rbpf (社区: eBPF VM)
└── kbpf-basic (Starry-OS: Maps)
xtask/
└── src/symbols.rs # 编译时符号表生成 (使用 ksym)
kernel (使用 ebpf feature)
└── axebpf # 单一依赖入口
5. 风险识别与缓解
| 风险 | 可能性 | 影响 | 缓解策略 |
|---|---|---|---|
| rbpf no_std 适配困难 | 中 | 高 | 优先评估,必要时 fork |
| 追踪点开销过高 | 低 | 中 | 设计时考虑禁用路径优化 |
| 多架构支持复杂 | 中 | 中 | 先完成 x86_64,再扩展 AArch64 |
| Starry-OS 组件不兼容 | 低 | 中 | 组件独立性高,可单独适配 |
6. 参考资源
6.1 Starry-OS 组件
| 组件 | 链接 |
|---|---|
| ksym | https://github.com/Starry-OS/ksym |
| kbpf-basic | https://github.com/Starry-OS/kbpf-basic |
| tp-lexer | https://github.com/Starry-OS/tp-lexer |
| tracepoint | https://github.com/Starry-OS/tracepoint |
| kprobe | https://github.com/Starry-OS/kprobe |
| eBPF Discussion | https://github.com/orgs/Starry-OS/discussions/4 |
6.2 eBPF 参考
| 资源 | 链接 |
|---|---|
| rbpf | https://github.com/qmonnet/rbpf |
| Aya (Rust eBPF) | https://aya-rs.dev/ |
| Linux eBPF 文档 | https://docs.kernel.org/bpf/ |
| eBPF 指令集规范 | https://www.ietf.org/archive/id/draft-thaler-bpf-isa-00.html |
| PREVAIL 验证器 | https://github.com/vbpf/ebpf-verifier |
6.3 非 Linux eBPF 实现参考
| 项目 | 说明 |
|---|---|
| eBPF for Windows | https://github.com/microsoft/ebpf-for-windows |
| Femto-Containers | https://github.com/future-proof-iot/Femto-Container |
| uBPF | https://github.com/iovisor/ubpf |
AxVisor eBPF 开发计划
基于移植 Starry-OS eBPF 工作的开发计划。
策略: Starry-OS 是基于 ArceOS 扩展的宏内核,AxVisor 是基于 ArceOS 扩展的 VMM, 大部分组件可直接引用或稍作适配。
参考: https://github.com/orgs/Starry-OS/discussions/4
1. 架构决策
1.1 模块结构
所有功能统一在 modules/axebpf 模块内实现,不创建独立的 axksym/axtracepoint 模块:
modules/axebpf/
├── Cargo.toml
├── src/
│ ├── lib.rs # 模块入口,feature 控制
│ ├── symbols.rs # 符号表管理 (封装 ksym)
│ ├── tracepoint.rs # 追踪点框架 (封装 tracepoint)
│ ├── kprobe.rs # 动态探针 (封装 kprobe)
│ ├── runtime.rs # eBPF 运行时 (封装 rbpf)
│ ├── verifier.rs # 程序验证器
│ ├── helpers.rs # Helper 函数实现
│ └── maps.rs # Map 数据结构 (封装 kbpf-basic)
1.2 依赖复用
| 组件 | 来源 | 复用方式 | 用途 | 阶段 |
|---|---|---|---|---|
| ksym | Starry-OS | 直接引用 | 符号表生成与查找 | Phase 1 |
| tracepoint | Starry-OS | 直接引用 | 追踪点框架 | Phase 1 |
| tp-lexer | Starry-OS | 直接引用 | 过滤表达式解析 | Phase 3 |
| kbpf-basic | Starry-OS | fork 引用 | eBPF Map/ringbuf | Phase 2 |
| rbpf | 社区 | 直接引用 | eBPF VM | Phase 2 |
| kprobe | Starry-OS | 移植适配 | 动态探针 | Phase 4 |
| PREVAIL | 社区 | 集成/实现 | 验证器 | Phase 5 |
2. 开发阶段
2.1 时间线概览 (12 周)
Week 1-2 Week 3-4 Week 5-6 Week 7-8 Week 9-10 Week 11-12
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│Phase 1│────►│Phase 2│─────►│Phase 3│─────►│Phase 4│─────►│Phase 5│─────►│Phase 6│
│ 基础 │ │eBPF │ │ VMM │ │Kprobe │ │验证器 │ │测试 │
│ 设施 │ │运行时 │ │追踪点 │ │支持 │ │Uprobe │ │文档 │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘
3. Phase 1: 基础设施 (Week 1-2)
目标: 集成 Starry-OS 组件,建立符号表和追踪点基础设施
3.1 Week 1: 依赖集成
| 任务 | 说明 |
|---|---|
| 添加 Starry-OS 依赖 | ksym, kbpf-basic, tp-lexer, tracepoint |
创建 modules/axebpf | 基础模块结构 |
实现 symbols.rs | 封装 ksym,符号查找 API |
xtask symbols | 编译时符号表生成 |
3.2 Week 2: 追踪点框架
| 任务 | 说明 |
|---|---|
实现 tracepoint.rs | 封装 tracepoint 库 |
| TracepointManager | 追踪点注册、enable/disable |
| define_tracepoint! 宏 | AxVisor 追踪点定义宏 |
交付物:
modules/axebpf模块xtask symbols命令- 追踪点定义宏可用
4. Phase 2: eBPF 运行时 (Week 3-4)
目标: 移植 rbpf VM,实现程序加载和执行
4.1 Week 3: rbpf 集成
| 任务 | 说明 |
|---|---|
| rbpf 集成 | 适配 no_std (default-features = false) |
runtime.rs | EbpfVm 结构体封装 |
| 程序加载 | execute(), execute_with_mem() |
4.2 Week 4: Helper 与 Map
| 任务 | 说明 |
|---|---|
helpers.rs | 标准 Helper 函数实现 |
maps.rs | 封装 kbpf-basic |
| Map 类型 | HashMap, Array, RingBuf |
Helper 函数清单:
| Helper ID | 函数名 | 功能 |
|---|---|---|
| 1 | bpf_map_lookup_elem | 查找 Map 元素 |
| 2 | bpf_map_update_elem | 更新 Map 元素 |
| 3 | bpf_map_delete_elem | 删除 Map 元素 |
| 5 | bpf_ktime_get_ns | 获取时间戳 |
| 6 | bpf_trace_printk | 调试输出 |
| 8 | bpf_get_smp_processor_id | 获取 CPU ID |
交付物:
- eBPF 程序加载和执行
- 标准 Helper 函数 (6 个)
- Map 数据结构支持
5. Phase 3: VMM 追踪点与 Shell (Week 5-6)
目标: 定义 Hypervisor 追踪点,实现 Shell 命令
5.1 Week 5: VMM 追踪点
追踪点列表:
| 追踪点 | 触发位置 | 采集数据 |
|---|---|---|
vmm:vcpu_run_enter | AxVCpu::run() 入口 | vm_id, vcpu_id, timestamp |
vmm:vcpu_run_exit | AxVCpu::run() 返回 | vm_id, vcpu_id, exit_reason, duration_ns |
vmm:hypercall | Hypercall 处理 | vm_id, vcpu_id, nr, args, duration_ns |
vmm:mmio_read | MmioRead 处理 | vm_id, addr, width, duration_ns |
vmm:mmio_write | MmioWrite 处理 | vm_id, addr, width, data, duration_ns |
vmm:interrupt_inject | 中断注入 | vm_id, vcpu_id, vector |
vmm:ept_violation | NestedPageFault | vm_id, gpa, access_flags, duration_ns |
| 任务 | 说明 |
|---|---|
| 定义 VMM 追踪点 | 使用 define_tracepoint! 宏 |
| axvcpu 插桩 | AxVCpu::run() 方法 |
| Exit 处理插桩 | Hypercall, MMIO, EPT violation |
| Hypervisor Helper | vm_id, vcpu_id, exit_reason |
5.2 Week 6: Shell 命令
| 命令 | 功能 |
|---|---|
trace list | 列出所有追踪点 |
trace enable <tp> | 启用追踪点 |
trace disable <tp> | 禁用追踪点 |
trace stat | 显示统计信息 |
trace load <path> | 加载 eBPF 程序 |
trace latency <type> | 延迟直方图 |
Hypervisor Helper:
| Helper ID | 函数名 | 功能 |
|---|---|---|
| 100 | bpf_get_current_vm_id | 获取当前 VM ID |
| 101 | bpf_get_current_vcpu_id | 获取当前 vCPU ID |
| 102 | bpf_get_exit_reason | 获取 VM Exit 原因 |
| 103 | bpf_get_guest_regs | 获取 Guest 寄存器 |
交付物:
- VMM 追踪点可用 (7+ 个)
- Shell
trace命令组 - Hypervisor Helper (4 个)
- 端到端演示
6. Phase 4: Kprobe/Kretprobe 支持 (Week 7-8)
目标: 移植 Starry-OS kprobe 实现,支持动态探针
6.1 Week 7: Kprobe 移植
| 任务 | 说明 |
|---|---|
| kprobe 库移植 | 从 Starry-OS 移植 |
kprobe.rs | 封装 kprobe API |
| 断点机制 | 架构相关实现 |
6.2 Week 8: Kretprobe 与多架构
| 任务 | 说明 |
|---|---|
| kretprobe | 函数返回探针 |
| aarch64 适配 | ARM64 断点机制 |
| x86_64 适配 | x86 断点机制 |
| Shell 集成 | trace kprobe/kretprobe 命令 |
交付物:
- Kprobe/Kretprobe 支持
- 动态探针 Shell 命令
- 多架构支持 (aarch64, x86_64)
7. Phase 5: 验证器与 Uprobe (Week 9-10)
目标: 实现 eBPF 验证器,探索 Uprobe 支持
7.1 Week 9: 验证器
| 任务 | 说明 |
|---|---|
| 验证器研究 | PREVAIL 或自实现 |
verifier.rs | 验证器模块 |
| 安全检查 | 内存边界、程序终止性 |
验证项目:
| 检查项 | 目的 |
|---|---|
| 程序终止性 | 防止无限循环 |
| 内存边界 | 防止越界访问 |
| 类型安全 | 防止类型混淆 |
| 栈边界 | 防止栈溢出 |
7.2 Week 10: Uprobe 研究
| 任务 | 说明 |
|---|---|
| 可行性研究 | Guest 用户态追踪 |
| 挑战分析 | 地址空间隔离、性能影响 |
| 原型实现 | 如可行,实现基础 Uprobe |
Uprobe 挑战:
- Guest 用户态地址空间隔离
- 跨 VM 的断点管理
- 性能影响评估
交付物:
- eBPF 验证器
- 安全加载不受信程序
- Uprobe 可行性报告/原型
8. Phase 6: 测试与文档 (Week 11-12)
目标: 全面测试,编写文档,性能优化
8.1 Week 11: 测试
| 任务 | 说明 |
|---|---|
| 单元测试 | symbols, runtime, helpers, maps |
| 集成测试 | 追踪点触发、eBPF 执行、kprobe |
| 性能测试 | 追踪点开销、eBPF 执行延迟 |
| 多架构验证 | aarch64, x86_64 |
8.2 Week 12: 文档与优化
| 任务 | 说明 |
|---|---|
| 性能优化 | 基于测试结果优化 |
| 用户文档 | 使用指南 |
| API 文档 | 开发者文档 |
| 代码审查 | 代码质量检查 |
性能目标:
- 追踪点禁用时:开销 < 1%
- 追踪点启用时:开销 < 5%
- eBPF 程序执行:< 1μs
交付物:
- 完整测试套件
- 性能基准测试报告
- 用户文档和 API 文档
9. 未来扩展
9.1 Rust 异步追踪 (研究方向)
问题: 现有 eBPF 追踪方案基于同步执行模型,无法有效追踪 Rust async/await 异步代码:
- 执行碎片化: async 函数在 await 点被拆分为多个状态机片段
- 上下文丢失: Future 在不同执行器线程间迁移
- 状态机隐藏: 编译器生成的状态机代码与源码结构差异大
研究方向: 探索针对 Rust async runtime 的专用追踪方案。
10. 参考资源
10.1 Starry-OS 组件
| 组件 | 链接 |
|---|---|
| ksym | https://github.com/Starry-OS/ksym |
| kbpf-basic | https://github.com/Starry-OS/kbpf-basic |
| tp-lexer | https://github.com/Starry-OS/tp-lexer |
| tracepoint | https://github.com/Starry-OS/tracepoint |
| kprobe | https://github.com/Starry-OS/kprobe |
| eBPF Discussion | https://github.com/orgs/Starry-OS/discussions/4 |
10.2 社区组件
| 组件 | 链接 |
|---|---|
| rbpf | https://github.com/qmonnet/rbpf |
| PREVAIL | https://github.com/vbpf/ebpf-verifier |
| Aya | https://aya-rs.dev/ |
| Linux eBPF 文档 | https://docs.kernel.org/bpf/ |
周报(1.20 - 1.31)
开发分支
- axvisor: https://github.com/Iscreamx/axvisor/tree/feature/ebpf
- axebpf: https://github.com/Iscreamx/axebpf
一、已完成工作
-
axebpf 独立模块:集成 ksym、tracepoint、tp-lexer、kbpf-basic、rbpf 实现 eBPF 运行时,支持程序加载执行,Array/HashMap/LruHash/Queue 四种 Map 类型,6 个标准 helper 函数和 3 个 Hypervisor 专用 helper(获取 vm_id/vcpu_id/exit_reason)。效果:可在 Hypervisor 内核中动态加载 eBPF 字节码进行追踪,无需重新编译内核。
-
Shell trace 命令:支持 trace list/enable/disable/stat/reset/load/unload/progs/latency/verbose 等命令。效果:可通过 shell 交互式启用追踪点,附加 eBPF 程序,查看统计数据和延迟直方图。
-
Tracepoint 定义:定义了 25+ 个 VMM 追踪点,其中 VM 生命周期(vm_create/boot/shutdown)、系统初始化(vmm_init/config_load)、Shell(shell_command/init)等追踪点已完成插桩并正常工作。
-
预编译 eBPF 程序:基于 aya-ebpf 框架提供 2 个预编译程序,支持 ELF 解析和 Map 重定位,可通过
trace enable xxx --prog printk附加程序。 -
上游贡献:向 Starry-OS/kbpf-basic 提交 PR 并已合并,修复了 printf-compat 和 VaList API 在 Rust nightly >= 2025-12 上的兼容性问题。使 kbpf-basic 可在最新 nightly 工具链(2025-12-12)上正常编译。
二、待完善
| 问题 | 原因 | 解决方向 |
|---|---|---|
| vCPU 运行时追踪点不可用 | vCPU 运行循环位于 axvm crate 而非 kernel 侧 | 等待 axvm 重构后在其内部添加插桩 |
| bpf_trace_printk 简化实现 | rbpf VM 执行时无法访问 ELF .rodata 段中的格式字符串 | 需要实现内存映射机制 |
| 验证器功能有限 | rbpf 内置验证器仅检查基础安全性 | 当前依赖预编译可信程序规避风险 |
| Kprobe/Kretprobe 未实现 | 下一阶段工作 | Phase 4 计划 |
三、遇到的问题与解决方案
3.1 Rust nightly VaList API 变更
问题:kbpf-basic 依赖的 printf-compat 使用了 VaList 和 VaListImpl 类型,在 Rust nightly >= 2025-12 上编译失败,错误信息为 cannot find type VaListImpl in module core::ffi。
原因:Rust 重构了 core::ffi 模块,VaListImpl 被移除,VaList 的 API 也有变化。
解决方案:
- Fork kbpf-basic 仓库
- 更新 printf-compat 依赖到兼容新 API 的版本
- 修改 VaList 相关代码适配新 API
- 提交 PR 到上游并合并
3.2 rbpf 内存边界检查导致 Map 访问失败
问题:bpf_map_lookup_elem 返回静态 LOOKUP_BUFFER 指针后,eBPF 程序尝试解引用触发 out of bounds memory load 错误。
原因:rbpf VM 对所有内存访问进行边界检查,仅允许访问 mem(上下文)、stack(栈)和显式注册的 allowed_memory。
解决方案:
#![allow(unused)]
fn main() {
// Register LOOKUP_BUFFER as accessible memory before program execution
vm.register_allowed_memory(helpers::get_lookup_buffer_range());
}
3.3 ELF Map 重定位
问题:aya-ebpf 编译的程序包含 maps section 和 .reltracepoint 重定位表,直接加载会因为 Map FD 未修补导致运行时错误。
解决方案:实现完整的 ELF 解析流程:
- 解析
mapssection 提取 Map 定义(每个 28 字节) - 解析符号表获取 Map 名称
- 调用
maps::create()创建 Map 并获取 FD - 解析
.reltracepoint重定位表 - 修补
ld_imm64指令中的 Map FD
四、工作量统计
| 指标 | 数量 |
|---|---|
| 新增代码行数 | ~3000 行 |
| 主要模块数 | 12 个(symbols、tracepoint、runtime、maps、helpers、attach、context、programs、output、map_ops、platform、tracepoints) |
| 定义追踪点数 | 25+ 个 |
| 实现 Helper 数 | 9 个(6 标准 + 3 Hypervisor 专用) |
| 支持 Map 类型 | 4 种 |
| 上游 PR | 1 个(已合并) |
五、下阶段计划
| 优先级 | 任务 | 说明 |
|---|---|---|
| P0 | 完善文档 | 补充 API 文档、使用示例、架构说明 |
| P1 | Kprobe 支持 | 移植 Starry-OS kprobe 库,实现动态探针 |
| P1 | axvm 追踪点插桩 | 待 axvm 重构后添加 vCPU 运行时追踪点 |
| P2 | 完善 bpf_trace_printk | 实现 .rodata 内存映射,支持格式字符串解析 |
| P2 | 性能测试 | 测量追踪点开销、eBPF 执行延迟 |
六、风险与阻塞项
| 风险项 | 影响 | 缓解措施 |
|---|---|---|
| axvm 重构时间不确定 | vCPU 运行时追踪点无法上线 | 优先完成其他可用追踪点的功能验证 |
| Kprobe 架构适配复杂度 | aarch64 断点机制与 x86 不同 | 参考 Starry-OS 实现,必要时只支持单架构 |
七、个人日志
7.1 五个关键依赖仓库的作用
| 依赖 | 定位 | 在 eBPF 执行流程中的作用 |
|---|---|---|
| ksym | 符号表管理 | 提供 KallsymsMapped 解析内核符号表,支持 lookup_symbol(addr) 地址→函数名和 lookup_addr(name) 函数名→地址双向查询。用于堆栈追踪符号化和 Kprobe 目标函数定位。 |
| tracepoint (ktracepoint) | 静态追踪点框架 | 提供 define_event_trace! 宏定义追踪点,TracepointManager 管理追踪点生命周期。当追踪点触发时,调用已注册的回调函数(包括执行附加的 eBPF 程序)。 |
| tp-lexer | 过滤表达式解析 | 解析追踪点过滤表达式语法,支持条件过滤(如 vm_id == 1)。当前为 tracepoint-support feature 的依赖,后续可用于实现追踪点过滤功能。 |
| rbpf | eBPF 虚拟机 | 核心执行引擎。runtime.rs 使用 EbpfVmRaw 执行带上下文的 eBPF 字节码,通过 register_helper 注册 helper 函数,register_allowed_memory 注册可访问内存区域(如 LOOKUP_BUFFER)。程序执行后返回 r0 寄存器值。 |
| kbpf-basic | eBPF Map 实现 | 提供 bpf_map_create 创建 Map,UnifiedMap 统一抽象层。maps.rs 通过实现 KernelAuxiliaryOps trait 适配 AxVisor 内核操作(cpu_id、time_ns、write_str 等),支持 Array/HashMap/LruHash/Queue 四种 Map 类型。 |
执行流程示意:
User Shell axebpf Dependencies
│ │ │
├─ trace enable ─────────────►│ │
│ ├─ TracepointManager ──────►│ tracepoint
│ │ .enable(name) │
├─ trace enable --prog ──────►│ │
│ ├─ parse_elf_with_maps() ──►│ (ELF parsing)
│ ├─ bpf_map_create() ───────►│ kbpf-basic
│ ├─ EbpfVmRaw::new() ───────►│ rbpf
│ ├─ register_helper() ──────►│ rbpf
│ │ │
[Tracepoint fires] │ │
│ ├─ execute_with_context() ─►│ rbpf
│ │ (eBPF execution) │
│ ├─ map_lookup/update ──────►│ kbpf-basic
│ │ │
├─ trace stat ───────────────►│ │
│ ├─ iter_entries() ─────────►│ kbpf-basic
│ ├─ lookup_symbol() ────────►│ ksym (optional)
7.2 实现代码过程的权衡
| 决策点 | 选项 | 最终选择 | 权衡理由 |
|---|---|---|---|
| eBPF VM 实现 | 自己实现 vs 复用 rbpf | rbpf | rbpf 是成熟的 Rust 实现,支持 no_std,指令集完整,减少开发时间。代价是依赖外部仓库。 |
| Map 实现 | 简化 Vec 实现 vs kbpf-basic | kbpf-basic | 早期用 Vec 线性扫描(O(n)),后迁移到 kbpf-basic 获得真正的 O(1) HashMap 和 LRU 淘汰。代价是需要实现 KernelAuxiliaryOps 适配层。 |
| KernelAuxiliaryOps 适配 | 完整实现 vs 最小实现 | 最小实现(5/15 方法) | 只实现 get_unified_map_from_fd、current_cpu_id、ebpf_time_ns、ebpf_write_str 等必需方法,其他返回 NotSupported。代价是不支持 RingBuf、PerCpu Map。 |
| bpf_map_lookup_elem 返回值 | 零拷贝指针 vs 静态缓冲区 | 静态缓冲区(256B) | rbpf 内存边界检查严格,需要 register_allowed_memory 注册返回指针。零拷贝需要 fork kbpf-basic 添加 lookup_elem_ptr API。当前用静态缓冲区 + Mutex 保护,有大小限制和拷贝开销。 |
| bpf_trace_printk 实现 | 完整格式字符串解析 vs 简化打印 | 简化打印 | 格式字符串在 ELF .rodata 段,rbpf VM 执行时无法访问。完整实现需要内存映射机制(复杂度高)。当前直接打印 r1/r2/r3 参数值。 |
| 程序验证 | 完整验证器 vs 依赖可信程序 | 依赖可信预编译程序 | rbpf 内置基础验证器仅检查内存越界和指令合法性。完整验证器(如 PREVAIL)集成复杂度高。当前通过只允许加载预编译可信程序规避风险。 |
| 追踪点插桩位置 | kernel 侧 vs axvm 侧 | 混合 | vCPU 运行循环在 axvm crate 中,kernel 侧的 vcpus.rs 代码路径未执行。等待 axvm 重构后迁移插桩点。 |
7.3 收获
技术收获:
-
eBPF 执行模型理解:深入理解了 eBPF 程序的加载→验证→执行→Map 交互流程,以及 helper 函数的调用约定(r1-r5 传参,r0 返回)。
-
ELF 解析实践:实现了最小化的 ELF64 解析器,支持 section 查找、符号表解析、重定位表处理。理解了
ld_imm64指令的 Map FD 修补机制。 -
no_std 环境适配:在无标准库环境下集成多个依赖,处理了 alloc、spin lock、static-keys 等基础设施问题。
-
Rust nightly 兼容性:修复 kbpf-basic 的 VaList API 变更问题并贡献上游,理解了 Rust 不稳定特性的演进。
工程收获:
-
模块化设计:axebpf 通过 feature flags 控制功能启用(symbols/tracepoint-support/runtime),便于裁剪和测试。
-
抽象层设计:
platform.rs提供 mock/real 模式切换,map_ops.rs实现KernelAuxiliaryOps适配层,解耦了依赖库与内核实现。 -
渐进式实现:先用简化方案验证端到端流程(如 Vec 实现 Map、简化 printk),再逐步替换为生产级实现。
待深入方向:
- Kprobe 动态插桩机制(需要理解 aarch64 断点指令和异常处理)
- eBPF 验证器原理(控制流图分析、类型状态追踪)
- 零拷贝 Map 访问优化
周报(2.1 - 2.7)
开发分支
- axvisor: https://github.com/Iscreamx/axvisor/tree/feature/ebpf
- axebpf: https://github.com/Iscreamx/axebpf
一、已完成工作
-
Kprobe 基础设施:Starry-OS 的 kprobe 库设计用于 EL1 宏内核环境,而 AxVisor 作为 Type-1 Hypervisor 运行在 EL2,异常处理入口和寄存器上下文完全不同,无法直接复用。本周实现了 EL2 环境下的 kprobe 断点捕获机制:在 arm_vcpu 的 EL2 同步异常处理中识别 BRK 指令 (EC=0x3C),将 Hypervisor 的 TrapFrame 转换为 eBPF 程序可访问的寄存器上下文,执行完成后恢复正常执行流。效果:可在任意 Hypervisor 内核函数入口处动态执行 eBPF 程序。
-
符号表集成:实现内核符号表加载机制,使用页对齐结构嵌入 kallsyms 二进制数据满足 ksym 库要求,获取内核代码段边界用于地址校验。效果:kprobe 可通过符号名自动解析函数地址,无需手动查找。
-
Shell ksym 命令:新增内核符号查找命令,支持地址到符号名的双向解析,以及模糊搜索功能。便于定位 kprobe 挂载点。
-
Shell trace kprobe 命令:扩展 trace 命令组,新增 kprobe/kretprobe/unkprobe 子命令,支持按程序名自动加载预编译 eBPF 程序,trace list 扩展显示已注册 kprobe 的符号名、地址、命中次数、程序 ID。效果:可通过 shell 交互式管理动态探针。
-
axebpf 子模块化:将 axebpf 改为 git submodule 引入。效果:axebpf 可独立版本管理。
-
axvm kprobe feature 传递:arm_vcpu 是 axvm 的依赖,需要通过 feature flag 控制 kprobe 功能是否编译,因此在 axvm 中添加 kprobe feature 并向下传递到 arm_vcpu。
-
链接器脚本更新:tracepoint 库使用特殊的 linker section 在链接时收集所有静态追踪点定义,需要在 axplat-aarch64-dyn 的链接器脚本中添加 tracepoint 和 __static_keys 段的起止符号定义。效果:支持 tracepoint 静态插桩点的自动收集,__static_keys 为后续静态分支优化做准备。
二、待完善
| 问题 | 原因 | 解决方向 |
|---|---|---|
| 递归调用异常 | 执行稍复杂的 eBPF 程序时出现 Error: too many nested calls (max: 8) | 可能是递归 kprobe 触发,待调试 |
| Kretprobe 未完整验证 | 函数返回探针需要栈帧管理机制 | 当前仅完成命令接口框架 |
| x86_64 架构支持待实现 | 当前仅完成 aarch64 EL2 环境的适配 | x86_64 需要适配 VMX root mode 的异常处理 |
三、遇到的问题与解决方案
3.1 EL2 与 EL1 异常处理差异
问题:Starry-OS kprobe 库基于 EL1 异常处理设计,直接移植到 AxVisor 后无法正常工作。
原因:AxVisor 运行在 EL2,使用 ESR_EL2/FAR_EL2 等寄存器,异常向量表入口和 TrapFrame 结构与 EL1 完全不同。
解决方案:
- 在 arm_vcpu 的
current_el_sync_handler中添加 BRK 指令识别逻辑 - 检查 ESR_EL2.EC == 0x3C(BRK from AArch64)
- 将 TrapFrame 指针和大小传递给 axebpf 的 kprobe handler
- 通过回调函数更新 PC 实现单步执行
3.2 符号表页对齐要求
问题:ksym 库要求 kallsyms 二进制数据必须页对齐,直接使用 include_bytes! 嵌入会导致初始化失败。
解决方案:
#![allow(unused)]
fn main() {
#[repr(C, align(4096))]
struct AlignedKallsyms<const N: usize> {
data: [u8; N],
}
static KALLSYMS_ALIGNED: AlignedKallsyms<{ include_bytes!("../../kallsyms.bin").len() }> = ...;
}
3.3 Feature Flag 跨 crate 传递
问题:kprobe 功能需要同时在 kernel、axvm、arm_vcpu 三个 crate 中启用,feature 依赖链复杂。
解决方案:在 axvm 的 Cargo.toml 中声明 kprobe = ["arm_vcpu/kprobe"],实现 feature 自动向下传递。
四、工作量统计
| 指标 | 数量 |
|---|---|
| 新增代码行数 | ~500 行 |
| 修改子模块数 | 3 个(arm_vcpu、axvm、axplat-aarch64-dyn) |
| 新增 Shell 命令 | 4 个(ksym、kprobe、kretprobe、unkprobe) |
| 新增 eBPF 程序 | 3 个(kprobe_args、kprobe_simple、kprobe_noop) |
五、下阶段计划
| 优先级 | 任务 | 说明 |
|---|---|---|
| P0 | 调试递归调用问题 | 分析 nested calls 错误原因,可能需要过滤 kprobe 自身调用 |
| P1 | 完善 Kretprobe | 实现函数返回探针的栈帧管理 |
| P1 | axvm 追踪点插桩 | 待 axvm 重构后添加 vCPU 运行时追踪点 |
| P2 | x86_64 架构支持 | 适配 VMX root mode 的异常处理机制 |
六、风险与阻塞项
| 风险项 | 影响 | 缓解措施 |
|---|---|---|
| 递归 kprobe 触发 | 复杂 eBPF 程序无法执行 | 分析调用链,添加重入保护 |
| axvm 重构时间不确定 | vCPU 运行时追踪点无法上线 | 优先完成其他可用追踪点的功能验证 |
七、个人日志
7.1 Kprobe 库架构深度分析
本周深入学习了 Starry-OS 的 kprobe 库实现,理解了其核心架构:
7.1.1 软件单步执行机制
kprobe 使用基于 BRK 指令的软件单步机制,而非硬件单步(避免 SPSR.SS 的复杂性):
Original Code: After Instrumentation:
┌─────────────────┐ ┌─────────────────┐
│ func: │ │ func: │
│ original_insn │ ────────► │ BRK #4 │ ← Main Breakpoint
│ next_insn │ │ next_insn │
└─────────────────┘ └─────────────────┘
Instruction Slot (.text.kprobe_slots):
┌─────────────────┐
│ original_insn │ ← Original instruction copied here
│ BRK #6 │ ← Single-step completion marker
└─────────────────┘
执行流程:
- CPU 执行到
BRK #4,触发同步异常(EC=0x3C, ISS=0x4) - 异常处理器执行 eBPF 程序,然后将 PC 设置为指令槽地址
- 异常返回后执行指令槽中的原始指令
- 执行到
BRK #6,再次触发异常(EC=0x3C, ISS=0x6) - 异常处理器将 PC 设置为原始函数的下一条指令(original_pc + 4)
- 异常返回,继续正常执行
7.1.2 KprobeAuxiliaryOps Trait
kprobe 库通过 KprobeAuxiliaryOps trait 抽象平台相关操作,需要实现以下方法:
| 方法 | 作用 | AxVisor 实现 |
|---|---|---|
copy_memory | 复制内存(用于保存/恢复原始指令) | 检测目标是否在指令槽区域,若是则先修改页表权限 |
set_writeable_for_address | 临时使 .text 段可写 | 调用 page_table::set_kernel_text_writable,执行后恢复并刷新 I-cache |
alloc_kernel_exec_memory | 分配可执行内存(指令槽) | 从预分配的 .text.kprobe_slots 段分配 8 字节槽位 |
free_kernel_exec_memory | 释放指令槽 | 恢复只读权限并归还槽位 |
insert_kretprobe_instance_to_task | 保存 kretprobe 返回地址 | 使用 per-CPU 栈存储(Hypervisor 无传统任务概念) |
pop_kretprobe_instance_from_task | 恢复 kretprobe 返回地址 | 从 per-CPU 栈弹出 |
7.1.3 指令槽管理
指令槽是一段预分配的可执行内存区域,用于存放被替换的原始指令:
.text.kprobe_slots (在链接器脚本中定义):
┌────────────────────────────────────────────────────────┐
│ Slot 0: [4B original_insn][4B BRK #6] = 8 bytes │
│ Slot 1: [4B original_insn][4B BRK #6] = 8 bytes │
│ ... │
│ Slot N: [4B original_insn][4B BRK #6] = 8 bytes │
└────────────────────────────────────────────────────────┘
关键实现细节:
- 槽位大小固定为 8 字节(原始指令 4B + BRK #6 4B)
- 使用位图管理槽位分配状态
- 写入前需修改页表使其可写,写入后恢复只读并刷新 I-cache
7.1.4 页表权限修改
在 AArch64 中,.text 段默认是只读可执行的。插入 BRK 指令需要临时修改页表:
#![allow(unused)]
fn main() {
// 修改页表项的 AP (Access Permission) 位
// AP[2:1] = 00: EL1 RW, EL0 无访问
// AP[2:1] = 10: EL1 RO, EL0 无访问
fn set_kernel_text_writable(addr: usize, len: usize, writable: bool) -> bool {
// 1. 遍历页表找到对应 PTE
// 2. 修改 AP 位
// 3. 刷新 TLB (TLBI)
// 4. 如果是写入后,还需刷新 I-cache (IC IVAU)
}
}
7.2 Kprobe 在 Hypervisor 中的特殊性
与传统 OS 内核的 kprobe 实现相比,Hypervisor 环境下存在以下差异:
| 方面 | 传统 OS (EL1) | Hypervisor (EL2) |
|---|---|---|
| 异常寄存器 | ESR_EL1, FAR_EL1 | ESR_EL2, FAR_EL2 |
| 异常向量表 | VBAR_EL1 | VBAR_EL2 |
| 上下文结构 | 内核 pt_regs | Hypervisor TrapFrame |
| 页表基址寄存器 | TTBR0_EL1/TTBR1_EL1 | TTBR0_EL2 |
| 任务上下文 | current_task | per-CPU 状态 |
| 追踪目标 | 内核函数 | VMM 代码路径 |
适配要点:
- 异常处理入口不同,需要在 arm_vcpu 的
current_el_sync_handler中添加 BRK 识别逻辑 - TrapFrame 结构不同,需要正确提取 PC 和传递寄存器上下文给 eBPF 程序
- Hypervisor 无传统任务概念,kretprobe 实例需使用 per-CPU 存储
7.3 Per-CPU 状态管理
Hypervisor 没有传统 OS 的任务/线程概念,kprobe 执行过程中的状态需要按 CPU 核心存储:
#![allow(unused)]
fn main() {
// 每个 CPU 核心独立的状态存储
const MAX_CPUS: usize = 8;
static ORIGINAL_PC: [AtomicUsize; MAX_CPUS] = [...];
static RETPROBE_STACKS: [Mutex<Vec<RetprobeInstance>>; MAX_CPUS] = [...];
fn save_original_pc(pc: usize) {
let cpu = platform::cpu_id();
ORIGINAL_PC[cpu].store(pc, Ordering::SeqCst);
}
}
这确保了多核环境下 kprobe 的正确性:每个 CPU 独立追踪自己的执行状态。
7.4 收获
技术收获:
-
软件单步机制:深入理解了 kprobe 使用双 BRK 指令实现软件单步的原理,比硬件单步更简单可控。
-
Trait 抽象设计:学习了
KprobeAuxiliaryOpstrait 如何将平台相关操作抽象出来,使 kprobe 库可以跨平台复用。 -
页表动态修改:掌握了运行时修改 .text 段权限的方法,包括页表项修改、TLB 刷新、I-cache 刷新的完整流程。
-
AArch64 异常模型:深入理解了 EL2 异常处理流程,ESR_EL2.EC/ISS 字段编码,以及异常返回机制。
-
Per-CPU 编程模式:理解了在无任务抽象的 Hypervisor 环境中如何管理执行状态。
工程收获:
-
跨 crate feature 管理:学习了 Cargo feature 的依赖传递机制,理解了
feature = ["dep/feature"]语法的作用。 -
链接器脚本定制:掌握了 linker section 的定义方法,理解了如何为指令槽预留可执行内存区域。
待深入方向:
- Kretprobe trampoline 机制:如何劫持函数返回地址并在返回时执行 eBPF 程序
- 递归 kprobe 的重入保护:当 eBPF 程序或 helper 函数本身触发 kprobe 时如何避免死循环
- x86_64 适配:INT3 单字节指令的处理、VMX root mode 异常处理差异
周报(2026-03-30 至 2026-04-12)
开发分支
- axvisor: https://github.com/Iscreamx/axvisor/tree/feature/ebpf
- axebpf: https://github.com/Iscreamx/axebpf/tree/main
总结
这两周没有再往共享库
uprobe/uretprobe、dlopen
生命周期这些方向扩功能,主要还是把已经做出来的主线能力尽量联调透、验证透、文档也补齐。现在
tracepoint、hprobe/hretprobe、guest-kprobe/kretprobe、guest-uprobe/uretprobe
已经能放进同一条真实的 QEMU + AxVisor + Linux guest
链路里跑,现场手工演示、fresh 验证和日志验收也都整理出来了。
这两周做下来,边界其实也更清楚了。主程序场景下的
guest-uprobe/guest-uretprobe
已经基本可以算做成了;但共享库、多对象映射、dlopen/dlclose
动态装载生命周期这条线没有闭环,所以这次没有继续硬往前推,而是把为什么没做完、后面如果有人接着看应该从哪里下手,单独写成了说明文档。
一、这两周完成的主要工作
1. 把七类能力收成一条主线联调链路
这两周最主要的事情,是把原来分开验证的 tracing 能力收成一条主线联调链路。现在在同一次真实运行里,已经可以覆盖:
| 类型 | 目标点 | 当前状态 |
|---|---|---|
uprobe |
/usr/bin/axebpf_integration_demo:demo_user_entry |
pass |
uretprobe |
/usr/bin/axebpf_integration_demo:demo_user_compute |
pass |
guest-kprobe |
vm1:gic_handle_irq |
pass |
guest-kretprobe |
vm1:__arm64_sys_getpid |
pass |
hprobe |
notify_guest_vmexit |
pass |
hretprobe |
notify_guest_vmexit |
pass |
tracepoint |
vmm:timer_tick |
pass |
这里真正有价值的,不只是“这些点能 attach 上”,而是 attach、guest
启动、运行期命中、收尾 trace list、收尾
trace stat
这几段已经能前后接起来,不再是靠零散实验凑证据。
2. 统一了 guest demo、rootfs 和触发路径
- 使用单 guest
demo:
scripts/ostool/assets/linux_axebpf_integration_demo/axebpf_integration_demo.c - demo 在一次运行里主动触发用户态函数、
getpid、文件 I/O 和重复窗口,作为七类能力联调的统一触发源 - 固化 rootfs
准备脚本:
scripts/ostool/prepare_linux_axebpf_integration_demo_rootfs.sh - 继续通过
inittab + launcher直接拉起 demo,避免 guest shell 和 host AxVisor shell 争用串口
这套东西整理完之后,现场演示和 fresh 回归都不用再临时拼步骤了。
3. 补齐自动化验收和人工联调文档
- 新增总控脚本:
tmp/run_axebpf_mainline_integration_real.py - 新增日志验收脚本:
scripts/verify/check_axebpf_mainline_integration_log.py - 固化主线
workflow:
docs/ebpf-tracing/axebpf-mainline-integration-workflow.md - 固化现场手工演示手册:
docs/ebpf-tracing/axebpf-mainline-integration-manual-workflow.md - 固化能力矩阵:
docs/ebpf-tracing/axebpf-mainline-integration-matrix.md
现在脚本已经能自动完成构建、镜像准备、attach、guest
启动、日志抓取、trace list/stat 收尾和最终 PASS/FAIL
校验;手工手册则对应现场演示,按文档一步步走,也能看到同样的结果。
二、最新联调结果
这轮 fresh 验证已经通过,验收摘要如下:
hprobe_entry=2781hretprobe=2781guest-kprobe gic_handle_irq=1129guest-kretprobe __arm64_sys_getpid=14vmm:timer_tick的 attachment 和 map activity 都存在
联调过程中,几类关键证据现在都能稳定看到:
- guest 启动后可以看到
axebpf_integration_demo wrapper: launch和demo:start - 运行期可以看到
guest_uprobe_hit和guest_uretprobe_hit - 同一运行窗口里可以看到
guest_kprobe_hits: kprobe vm1:gic_handle_irq ... - 同时也可以看到
guest_kprobe_hits: kretprobe vm1:__arm64_sys_getpid ... - host 侧能持续看到
[hprobe] ENTRY/EXIT ... notify_guest_vmexit - demo 结束后,收尾
trace list和trace stat都能拿到非零命中结果
这里有两个点需要单独说明。
2.1
guest-uprobe/guest-uretprobe 的收尾表现
guest 进程退出后,收尾 trace list 里不会保留 active
实例,而只会剩下 pending 模板,并显示
waiting-for-instance。这不是失败,而是当前实现下实例会随着进程退出一起回收。
所以这类能力到底有没有跑通,主要还是看运行期 raw log 里的:
guest_uprobe_hitguest_uretprobe_armguest_uretprobe_hit
2.2 hprobe 日志比较密
hprobe/hretprobe 的命中频率很高。如果 demo
结束后不及时关闭
verbose,串口很容易被刷屏,最后抓快照反而不好看。所以现在 workflow
和手工手册里都统一要求在 demo:done 之后立即执行:
trace verbose off
再抓最终的 trace list 和 trace stat。
三、为什么共享库等功能当前不可用
这两周没有继续把共享库
uprobe/uretprobe、dlopen
场景往下推进,不是因为“只差最后一点”,而是因为它已经超出了当前这个收尾阶段适合继续扩展的范围。
3.1 现在做成的是“主程序场景”,还不是“完整对象模型”
现在主线里已经能用的是:
- 主程序路径下的
guest-uprobe - 主程序路径下的
guest-uretprobe - 基于当前运行实例的进程隔离和回收
但共享库场景要处理的,不只是主程序映射基址,而是一个真正的“多对象运行时模型”,至少包括:
- 每个共享库各自独立的 load bias
- 重定位后的运行时地址
dlopen/dlclose带来的对象创建、激活、回收生命周期- 同一进程内多个 file-backed executable mapping 的对象识别
这些状态,目前都还没有在主线里统一建模完成。
3.2 真实失败点在 runtime observer 的对象识别阶段
这轮共享库验证里,guest 内部其实已经能把下面几步跑起来:
- 动态程序启动
dlopen成功- 目标共享库函数被调用
但 host 侧没有把这次共享库映射识别成一个能和
pending binding 对上的目标对象,所以 probe 一直停在
pending。也就是说,真实问题不在“公式是不是算对了”,而是在
observer 的对象路径和实例识别这一步就没接上。
这轮实际看到的典型现象是:
dlopen后 observer 能看到 executable file-backedmmap- 但日志里会出现
reason=no_pending_path - 最终
trace list中始终只有pending模板,没有共享库实例进入active
3.3 这时候更适合收口,不适合继续开新功能
这次没有继续修共享库能力,主要有三个原因:
- 这已经不是补一个小接口就能结束的问题,而是要继续改 runtime observer 的对象路径来源和实例识别逻辑
- 主程序场景和主线联调能力已经可以形成一份完整交付,再继续扩共享库,会把收尾重新拖回新功能开发
- 现阶段更合理的目标,是把已经做成的能力联调清楚、验证清楚、文档写清楚,便于后续交接
所以这两周的取舍是明确的:先不扩共享库,而是把已经做成的能力收成一条可复现、可演示、可验收的完整链路。
共享库未完成原因已经单独整理到:
docs/ebpf-tracing/shared-library-uprobe-status.md
四、下一步收口方向
后续工作不再增加新功能,而是继续围绕现有主线能力做交接式收口:
- 继续以
tmp/run_axebpf_mainline_integration_real.py作为 fresh 回归主入口 - 如需现场演示,优先按
docs/ebpf-tracing/axebpf-mainline-integration-manual-workflow.md执行 - 如需回看主线联调逻辑,优先看 workflow、matrix 和最新 PASS raw log
- 如果后续有人继续做共享库支持,直接从
docs/ebpf-tracing/shared-library-uprobe-status.md和对应 raw log 入手
接下来工作的重点,不再是继续往外扩,而是把已经做完的这部分能力保持在可联调、可复现、可交接的状态。就这两周的目标来说,这部分已经基本达到预期。
BPF Iterator 支持方案
1. 概述
本文档说明为什么 AxVisor 需要支持 BPF Iterator 程序类型,以及如何实现。
1.1 灵感来源
这个想法来自 FI (Fault Injection) 项目,一个基于 eBPF 的故障注入系统,用于 Go 微服务的全链路追踪和故障注入。
FI 的流量拦截流程是这样的:sock_ops 程序捕获 TCP 连接事件,将连接添加到 INTERCEPT_MAP,然后 sk_msg 程序对这些连接注入追踪信息。但 sock_ops 只能捕获程序附加后新建的连接,Agent 启动前已存在的长连接(Keep-Alive、gRPC 连接池、数据库连接等)抓不到。
我们写了 iter_tcp.bpf.c 来解决这个问题:用 BPF Iterator 在 Agent 启动时扫描系统中所有已建立的 TCP 连接,做到“启动即全量覆盖“。不过 aya-rs 目前不支持 Iterator 程序类型,只能用 C 写。
这就引出了本文的主题:如何在 ArceOS eBPF 生态中支持 Iterator。
2. BPF Iterator 工作原理
2.1 什么是 BPF Iterator
BPF Iterator(iter/*)是 Linux 5.8 引入的一种 eBPF 程序类型。和事件驱动型程序不同,Iterator 是主动遍历机制,允许 eBPF 程序在内核态安全地遍历内核数据结构。
2.2 执行流程
用户态 内核态
│ │
│ 1. bpf_link_create() │
├──────────────────────────────────>│ 创建 iterator link
│ │
│ 2. open("/sys/fs/bpf/iter_xxx") │
├──────────────────────────────────>│ 获取 iterator fd
│ │
│ 3. read(fd, buf, size) │
├──────────────────────────────────>│ 触发遍历
│ │
│ │ ┌─────────────────────────┐
│ │ │ 内核 Iterator 框架 │
│ │ │ │
│ │ │ for each object: │
│ │ │ call bpf_prog(ctx) │
│ │ │ seq_write(output) │
│ │ └─────────────────────────┘
│ │
│<──────────────────────────────────│ 返回遍历结果
│ │
2.3 支持的遍历目标
| Iterator 类型 | 遍历对象 | 典型用途 |
|---|---|---|
iter/task | 进程/线程 | 进程列表快照 |
iter/task_file | 进程打开的文件 | fd 审计 |
iter/tcp | TCP socket | 连接状态导出 |
iter/udp | UDP socket | UDP 端点枚举 |
iter/bpf_map_elem | BPF Map 条目 | Map 批量操作 |
iter/netlink | Netlink socket | 网络诊断 |
2.4 与 kprobe/kretprobe/uprobe 的区别
| 维度 | kprobe/kretprobe/uprobe | BPF Iterator |
|---|---|---|
| 触发机制 | 被动:等待事件发生 | 主动:用户态驱动遍历 |
| 执行时机 | 函数入口/出口被调用时 | 用户态 read() 时 |
| 数据来源 | 当前执行上下文(寄存器、栈) | 内核全局数据结构 |
| 观测范围 | 单次函数调用 | 整个数据结构集合 |
| 状态感知 | 只能捕获“变化“ | 可获取“当前全貌“ |
| 实现方式 | 断点指令 + 异常处理 | seq_file + 内核遍历框架 |
简单来说:
- kprobe 在函数入口插入断点(如
BRK #4),函数被调用时触发,只能观测到“正在发生的调用“。 - kretprobe 在函数返回时触发,捕获返回值,同样是事件驱动。
- uprobe 和 kprobe 类似,但作用于用户态程序。
- Iterator 不依赖任何事件,直接遍历内核维护的数据结构。比如可以获取“此刻系统中所有 TCP 连接“这样的全局快照。
2.5 内核数据结构依赖
Iterator 程序直接访问内核内部数据结构。以 iter/tcp 为例,程序需要访问 struct sock_common、struct sock 等结构体,而这些结构体的布局在不同内核版本间可能不同,字段顺序、大小甚至字段本身都可能变。
Linux 通过 BTF(BPF Type Format)和 CO-RE(Compile Once, Run Everywhere)来处理这个问题:
- BTF:内核编译时生成的类型信息,描述所有数据结构的布局
- vmlinux.h:从 BTF 导出的头文件,包含当前内核的完整类型定义
- CO-RE:编译时记录字段访问意图,运行时根据目标内核的 BTF 重定位
所以 Iterator 程序和内核版本紧密耦合,需要专门的构建和加载流程来处理跨版本兼容性。
3. 应用场景
3.1 宿主侧:热接入已有连接
这是最直接的应用场景,来源于前面提到的 FI 项目。
AxVisor 作为 Type-1 Hypervisor 运行在宿主 Linux 上。eBPF agent 拦截网络流量时,sock_ops 只能捕获新建连接。agent 启动前已存在的长连接(管理面的 gRPC 连接、监控系统的 Keep-Alive 连接等)需要用 iter/tcp 遍历并加入拦截 Map。
3.2 AxVisor 内部:现有诊断能力的不足
AxVisor 的 shell 命令系统提供了基本的 VM 管理能力,但在诊断信息获取方面有不少缺口。
已有的命令:
| 命令 | 提供的信息 |
|---|---|
vm list | VM ID、名称、状态、vCPU 数、内存大小 |
vm show <id> | 同上(--full/--config/--stats flag ) |
trace list | 已注册的 tracepoint 和 kprobe |
trace stat | eBPF 程序收集的事件统计 |
缺失的信息:
| 维度 | 缺失内容 | 诊断价值 |
|---|---|---|
| vCPU 状态 | 寄存器值、运行模式、异常状态 | 调试 Guest 卡死、分析 VM Exit |
| 内存映射 | GPA→HPA 映射、Stage-2 页表条目 | 诊断内存访问异常、审计内存分配 |
| 设备列表 | virtio 设备配置、passthrough 设备 | 排查设备模拟问题 |
| 设备统计 | I/O 请求数、中断次数 | 性能分析、瓶颈定位 |
| VM Exit 分布 | HVC/SMC/MMIO/中断等原因统计 | 优化 VMM 性能 |
| 中断状态 | GIC 配置、pending 中断队列 | 调试中断注入问题 |
3.3 Iterator 能做什么
现有的 tracepoint/kprobe 是事件驱动的,只能在事件发生时采集数据。Iterator 提供了主动遍历的方式:
| 场景 | 事件驱动的局限 | Iterator 怎么解决 |
|---|---|---|
| 诊断 vCPU 卡死 | 没有事件触发,无法采集 | 主动遍历所有 vCPU,导出当前状态 |
| 审计内存分配 | 需要 hook 每次分配,开销大 | 按需遍历页表,获取当前全貌 |
| 排查设备问题 | 需要预先埋点 | 遍历设备注册表,不用改代码 |
| 生成诊断快照 | 需要多个 tracepoint 配合 | 单次遍历,原子性获取一致状态 |
3.4 潜在的 AxVisor Iterator 类型
如果在 AxVisor 内部实现 Iterator 框架,可以定义这些遍历目标:
| Iterator 类型 | 遍历对象 | 用途 |
|---|---|---|
iter/vm | 所有 VM | 导出 VM 列表和详细配置 |
iter/vcpu | 所有 vCPU | 导出寄存器状态、运行模式 |
iter/gpa_region | Guest 物理地址区域 | 审计内存映射 |
iter/device | 模拟和透传设备 | 设备配置和统计 |
iter/irq | 中断描述符 | 中断路由和状态 |
这需要在 AxVisor 内部实现类似 Linux seq_file 的遍历框架。
4. AxVisor 环境下的实现挑战
4.1 运行环境分析
Iterator 程序依赖 Linux 内核的 seq_file 遍历框架。在 AxVisor 场景下,需要搞清楚程序在哪里运行:
┌─────────────────────────────────────────────────────────┐
│ Host Linux │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ iter_tcp │ │ iter_task │ │ Other Iterators │ │
│ │ (Traverses │ │ (Traverses │ │ │ │
│ │ Host TCP) │ │ Host Task) │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ v v v │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Linux Kernel Iterator Framework │ │
│ │ (seq_file + bpf_iter_reg) │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌─────────────────────────────────────────────────┐ │
│ │ AxVisor │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ VM 1 │ │ VM 2 │ │ VM N │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
4.2 两种实现路径
| 路径 | 说明 | 复杂度 | 适用场景 |
|---|---|---|---|
| 宿主侧 Iterator | 在宿主 Linux 上运行,遍历宿主内核数据结构 | 低 | TCP/UDP 连接、进程列表等标准对象 |
| AxVisor 侧 Iterator | 在 AxVisor 内部实现遍历框架,遍历 VMM 对象 | 高 | VM、vCPU、虚拟设备等 Hypervisor 对象 |
4.3 宿主侧 Iterator
直接复用 Linux 内核的 Iterator 框架:
- 优点:不用改 AxVisor,标准 eBPF 工具链直接可用
- 缺点:只能遍历宿主内核对象,访问不了 VMM 内部状态
- 适用于网络流量拦截、进程审计等宿主侧需求
4.4 AxVisor 侧 Iterator
如果需要遍历 VMM 内部对象(比如所有 VM 的 vCPU 状态),需要做这几件事:
- 实现遍历框架:类似
seq_file的迭代器基础设施 - 注册遍历目标:为 VM、vCPU、设备等对象提供遍历回调
- 定义上下文结构:如
bpf_iter__vm、bpf_iter__vcpu - 暴露触发接口:通过 shell 命令或特殊文件触发遍历
5. 替代方案对比
为什么选 Iterator 而不是别的方案?
5.1 方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| BPF Iterator | 内核态遍历,BPF 程序处理每个对象 | 高效、安全、可编程 | 需要特定程序类型支持 |
| 读取 /proc | 解析 /proc/net/tcp 等文件 | 简单、通用 | 用户态解析开销大,格式不稳定 |
| Netlink 查询 | 通过 NETLINK_SOCK_DIAG 获取 socket 信息 | 结构化数据 | 需要多次系统调用,无法直接操作 Map |
| 定时 kprobe 快照 | 周期性触发 kprobe 记录状态 | 复用现有机制 | 不完整,依赖事件触发 |
| 遍历 BPF Map | 用户态 bpf_map_get_next_key | 不需要额外程序类型 | 只能遍历 Map,不能遍历内核对象 |
5.2 详细分析
读取 /proc/net/tcp:
User Space Kernel Space
│ │
│ open("/proc/net/tcp") │
├────────────────────────────────────>│
│ │
│ read() × N times │
├────────────────────────────────────>│ Every read triggers formatting
│ │
│ Parse text in user space │
│ Construct data structures │
│ Update BPF Map │
├────────────────────────────────────>│ bpf() syscall
│ │
问题在于:文本解析有开销,需要多次系统调用,而且遍历期间连接状态可能变化,数据一致性没有保证。
BPF Iterator:
User Space Kernel Space
│ │
│ read(iter_fd) │
├────────────────────────────────────>│
│ │ Kernel-side traversal
│ │ Direct BPF Map manipulation
│ │ Single atomic operation
│<────────────────────────────────────│
│ │
只需要一次系统调用,内核态直接操作 Map,遍历期间持有 RCU 锁保证数据一致。
5.3 性能对比(理论)
| 指标 | /proc 解析 | Netlink | BPF Iterator |
|---|---|---|---|
| 系统调用次数 | O(n) | O(n) | O(1) |
| 数据拷贝 | 内核→用户→内核 | 内核→用户→内核 | 内核内部 |
| 一致性 | 弱 | 弱 | 强(RCU 保护) |
| 可编程性 | 无 | 无 | 完全可编程 |
5.4 结论
对于“热接入已有 TCP 连接“这个需求:
- /proc 解析:可行但低效,适合一次性脚本
- Netlink:数据结构化但仍需用户态处理
- BPF Iterator:最合适的方案,内核态完成全部工作
Iterator 是唯一能在内核态直接将连接信息写入 SOCKHASH Map 的方案。
AxVisor 统一探针调试系统设计
1. 背景与动机
当前 axebpf 模块提供了基本的 eBPF 追踪能力:rbpf 虚拟机、25+ 个 VMM 静态 tracepoint、EL2 kprobe/kretprobe,以及 shell 命令接口。但有几个问题:
- 命名混淆:现有 kprobe 实际探测的是 EL2 hypervisor 代码,而非 guest 内核
- 无法观测 guest:无法对 guest VM 内核函数设置探针
- 无法联调:缺乏跨 VMM/Guest 的统一调试视角
目标是构建一个统一的探针调试系统,支持 hprobe(VMM 探针)、kprobe(guest 探针)、tracepoint(静态探针)的联调,所有探针共享 VMM 侧的单一 eBPF 虚拟机。
2. 探针体系
按特权级和观察方向划分为三类:
2.1 hprobe(Hypervisor Probe)— 自省探针
- 探测 VMM 自身代码(EL2),即当前 kprobe/kretprobe 的重命名
- 实现机制不变:BRK 注入 + 指令槽单步执行
- 包含 hprobe(入口)和 hretprobe(返回)
2.2 kprobe(Kernel Probe)— 跨特权级探针
- 探测 guest VM 内核代码(EL1),VMM 作为外部观察者
- 本质上类比 Linux uprobe:高特权级透过地址空间边界探测低特权级代码
- 两种实现模式:Stage-2 fault(非侵入,默认)和 BRK 注入(低延迟)
- 包含 kprobe(入口)和 kretprobe(返回)
- 全局注册,可选限定到特定 VM ID
- 符号解析分阶段:先支持手动地址,后支持加载 guest 符号表
2.3 tracepoint — 静态探针(仅 VMM 侧)
- 保持现有 25+ 个 VMM tracepoint 不变
- Guest 侧不加 tracepoint,guest 观测完全通过 kprobe 和uprobe
2.4 uprobe — 预留接口
2.5 与 Linux 的类比
| Linux | AxVisor | 特权级关系 |
|---|---|---|
| 内核 (EL1) | VMM/Hypervisor (EL2) | 观察者 |
| 用户态进程 (EL0) | Guest VM (EL1/EL0) | 被观察者 |
| kprobe (内核探测自己) | hprobe (VMM 探测自己) | 自省 |
| uprobe (内核探测用户态) | kprobe (VMM 探测 guest 内核) | 跨特权级向下探测 |
3. 核心架构 — 单一 eBPF VM + 多源事件汇聚
所有探针类型最终汇聚到 VMM 侧的同一个执行路径:
┌──────────────────────────────────────────────────┐
│ VMM (EL2) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ hprobe │ │tracepoint│ │ kprobe │ │
│ │ BRK @ EL2│ │ Static │ │ S2 Fault/BRK │ │
│ └────┬─────┘ └─────┬────┘ └───────┬───────┘ │
│ │ │ │ │
│ └─────────────┬┘───────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ TraceContext │ │
│ │ - probe_type │ │
│ │ - vm_id (0=host) │ │
│ │ - cpu_id / vcpu_id │ │
│ │ - regs (EL2 or EL1) │ │
│ │ - timestamp │ │
│ └─────────┬───────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ rbpf VM Engine │ │
│ │ Helpers + Maps │ │
│ └─────────────────────┘ │
└──────────────────────────────────────────────────┘
几个设计要点:
- TraceContext 统一:扩展现有
TraceContext,增加probe_type(hprobe/kprobe/tracepoint)和vm_id字段。vm_id=0表示 host/VMM 自身,vm_id>0表示 guest。 - 寄存器上下文区分:hprobe 传入 EL2 的
TrapFrame,kprobe 传入 guest 的 EL1 寄存器状态(从 vCPU 上下文中提取)。eBPF 程序通过统一的偏移访问,但实际内容因 probe_type 而异。 - eBPF helper 扩展:现有的
bpf_get_current_vm_id()(ID 100)等 helper 在 kprobe 上下文中返回实际 VM ID 而非硬编码 0。
3.1 探针类型枚举
#![allow(unused)]
fn main() {
pub enum ProbeType {
Hprobe, // VMM function entry
Hretprobe, // VMM function return
Kprobe, // Guest kernel function entry
Kretprobe, // Guest kernel function return
Tracepoint, // VMM static probe
}
}
4. kprobe(Guest 探针)实现
4.1 模式一:Stage-2 fault(默认,非侵入)
利用 Stage-2 页表控制 guest 内存的执行权限。
- 注册:VMM 记录目标 GVA(guest 虚拟地址),通过 guest 页表(TTBR0_EL1/TTBR1_EL1)翻译为 GPA(guest 物理地址),然后将该 GPA 所在页面的 Stage-2 映射标记为不可执行(XN=1)
- 触发:guest vCPU 执行到该页面 → Stage-2 Permission Fault → 陷入 EL2
- 处理:VMM 的 fault handler 检查 fault 地址是否匹配已注册的 kprobe:
- 匹配:构建 TraceContext(从 vCPU 上下文提取 EL1 寄存器),执行 eBPF 程序,临时恢复页面执行权限,单步执行一条指令后重新标记 XN
- 不匹配:走正常 Stage-2 fault 流程
- 优点:不修改 guest 内存,guest 完全无感知
- 缺点:页面粒度(4KB),同一页面内多个函数会频繁触发 fault;单步执行需要额外处理
4.2 模式二:BRK 注入(高级,低延迟)
类似 Linux uprobe,直接修改 guest 内核内存。
- 注册:GVA → GPA → HVA(通过 Stage-2 页表),将目标指令替换为
BRK #kprobe_guest_brk_imm(使用与 hprobe 不同的立即数以区分来源) - 触发:guest 执行 BRK → EL1 异常 → 因 HCR_EL2.TGE 或路由配置陷入 EL2
- 处理:VMM 识别为 guest kprobe,执行 eBPF,恢复原指令并单步
- 优点:指令级精度,开销低
- 缺点:侵入 guest 内存,需要处理 I-cache 一致性(跨 CPU IPI flush)
两种模式通过注册时的参数选择:
trace kprobe vm0:0xffff800080012340 # 默认 Stage-2 fault
trace kprobe vm0:0xffff800080012340 --inject # BRK 注入模式
4.3 kretprobe(Guest 返回探针)
需要劫持 guest EL1 函数的 LR(x30)寄存器:
- 在 kprobe 触发时记录原始 LR
- 将 LR 修改为一个特殊的 trampoline 地址(VMM 预先在 guest 内存中映射的一段 BRK 指令)
- Guest 函数返回时执行 BRK 陷入 EL2
- VMM 捕获返回值和执行时间后恢复原始 LR
对 eBPF 程序暴露的接口与 hretprobe 一致:
probe_type: kretprobe
regs.x0: 返回值
duration: 函数执行耗时(entry 到 return)
5. 地址翻译与符号解析
5.1 地址翻译链
GVA (Guest Virtual Address)
│ guest 页表 (TTBR1_EL1, 由 vCPU 上下文持有)
▼
GPA (Guest Physical Address)
│ Stage-2 页表 (VTTBR_EL2, 由 axaddrspace 管理)
▼
HPA (Host Physical Address)
│ phys_to_virt() (线性映射)
▼
HVA (Host Virtual Address) ← VMM 可直接读写
- Stage-2 fault 模式只需 GPA,用于修改 Stage-2 页表权限
- BRK 注入模式需要完整链到 HVA,用于读写 guest 指令
新增 GuestAddressTranslator 组件,负责 walk guest 页表(从 vCPU 上下文读取 TTBR1_EL1,按 AArch64 四级页表格式逐级翻译)。
5.2 符号解析(分阶段)
阶段一(MVP):用户直接指定 GVA 地址。
trace kprobe vm0:0xffff800080012340
阶段二:支持加载 guest 内核符号表。
- 在 VM 配置文件(
configs/vms/*.toml)中新增可选字段kallsyms_path或system_map_path - VM 启动时 VMM 加载该文件,为每个 VM 维护独立的
ksym::SymbolTable - 用户按符号名注册:
trace kprobe vm0:schedule - 复用现有
ksymcrate,每个 VM 一个实例,存储在VmSymbolRegistry中
trace kprobe vm0:schedule # 按符号名
trace kprobe vm0:schedule+0x20 # 符号名 + 偏移
ksym vm0 list # 列出 VM 0 的符号
ksym vm0 lookup schedule # 查询 VM 0 中的符号地址
ksym vm0 load <path> # 手动加载符号表
6. 模块组织与文件结构
6.1 目录结构
modules/axebpf/src/
├── lib.rs # 模块入口,feature 门控,init()
├── context.rs # TraceContext(扩展:probe_type, vm_id)
├── runtime.rs # eBPF VM 封装(不变)
├── maps.rs / map_ops.rs # eBPF map(不变)
├── helpers.rs # 标准 helper(不变)
├── symbols.rs # VMM 符号表(不变)
├── attach.rs # 程序附着管理(不变)
├── output.rs / macros.rs # 输出与宏(不变)
│
├── probe/ # 统一探针框架
│ ├── mod.rs # ProbeType 枚举,ProbeManager trait
│ ├── registry.rs # 全局探针注册表(统一管理所有类型)
│ │
│ ├── hprobe/ # 从现有 kprobe_* 重命名
│ │ ├── mod.rs
│ │ ├── manager.rs # ← kprobe_manager.rs
│ │ ├── handler.rs # ← kprobe_handler.rs
│ │ └── ops.rs # ← kprobe_ops.rs
│ │
│ └── kprobe/ # Guest 探针
│ ├── mod.rs
│ ├── manager.rs # Guest kprobe 注册/生命周期
│ ├── handler.rs # Stage-2 fault / BRK 处理
│ ├── addr_translate.rs # GVA→GPA→HVA 地址翻译
│ └── guest_symbols.rs # 每 VM 符号表管理
│
├── tracepoints/ # 不变
│ ├── vmm.rs # 25+ VMM tracepoint
│ ├── shell.rs
│ ├── registry.rs
│ ├── stats.rs
│ ├── histogram.rs
│ └── hypervisor_helpers.rs # 扩展:真实 vm_id 返回
│
├── programs/ # 不变
│ ├── bytecode.rs
│ └── registry.rs
│
├── cache.rs # I-cache 刷新(hprobe/kprobe 共用)
├── insn_slot.rs # 指令槽分配(hprobe 使用)
└── page_table.rs # EL2 页表权限修改(hprobe 使用)
6.2 Feature 门控
[features]
default = ["symbols", "tracepoint-support", "runtime", "axhal"]
hprobe = ["tracepoint-support", "dep:kprobe"] # 原 kprobe feature 重命名
guest-kprobe = ["hprobe"] # 依赖 hprobe 基础设施
hprobe 复用现有 kprobe crate 依赖。guest-kprobe 是新增 feature,依赖 hprobe 因为共享 cache/insn_slot 等基础设施。
7. Shell 命令接口
7.1 命令体系
# === hprobe (VMM 探针) ===
trace hprobe <symbol> # 注册并启用 hprobe
trace hprobe <symbol> --ret # hretprobe
trace unhprobe <symbol> # 移除
# === kprobe (Guest 探针) ===
trace kprobe vm<id>:<addr> # 按地址,Stage-2 fault 模式
trace kprobe vm<id>:<addr> --inject # 按地址,BRK 注入模式
trace kprobe vm<id>:<symbol> # 按符号名(需加载符号表)
trace kprobe vm<id>:<symbol> --ret # kretprobe
trace unkprobe vm<id>:<addr|symbol> # 移除
# === tracepoint (不变) ===
trace enable <subsys>:<event>
trace disable <subsys>:<event>
# === 符号查询 ===
ksym list # VMM 符号
ksym lookup <name> # VMM 符号查找
ksym vm<id> list # Guest 符号
ksym vm<id> lookup <name> # Guest 符号查找
ksym vm<id> load <path> # 手动加载 Guest 符号表
# === 统一状态查看 ===
trace list # 列出所有活跃探针
trace stat # 统计信息
7.2 联调工作流示例
追踪 guest 调度引发的 VM Exit:
# 1. 在 VMM 侧挂 tracepoint,观察 vcpu_run_exit 事件
trace enable vmm:vcpu_run_exit
# 2. 在 guest 内核的 schedule() 入口挂 kprobe
trace kprobe vm0:schedule
# 3. 在 VMM 侧的 exit handler 挂 hprobe
trace hprobe handle_vcpu_exit
# 4. 查看所有活跃探针
trace list
[hprobe] handle_vcpu_exit hits: 0
[kprobe] vm0:schedule hits: 0 mode: s2fault
[tracepoint] vmm:vcpu_run_exit enabled
# 5. 运行 VM,触发事件后查看统计
trace stat
三种探针协同工作,eBPF 程序通过 bpf_get_current_vm_id() 关联事件,可以在 map 中构建跨层的因果链(guest schedule → VM Exit → VMM handle_vcpu_exit 的时间线)。
8. 实现阶段
阶段一:hprobe 重构 + 基础设施
- 将现有
kprobe_*文件重命名为hprobe_*,建立probe/目录结构 - 定义
ProbeType枚举和统一TraceContext(增加probe_type、vm_id字段) - 建立
probe/registry.rs统一探针注册表 - Shell 命令从
trace kprobe改为trace hprobe - 更新 feature gate:
kprobe→hprobe - 验收标准:现有 hprobe 功能完全不退化,所有现有测试通过
阶段二:Guest kprobe — Stage-2 fault 模式
- 实现
GuestAddressTranslator(walk guest 页表,GVA→GPA) - 实现 Stage-2 页表权限修改(XN 位控制)
- 实现 guest kprobe manager(注册、启用、禁用生命周期)
- 在 Stage-2 fault handler 中增加 kprobe 匹配逻辑
- 从 vCPU 上下文提取 EL1 寄存器构建 TraceContext
- 支持
trace kprobe vm<id>:<addr>命令(手动地址) - 验收标准:能在 guest 内核函数入口触发 eBPF 程序并输出
阶段三:增强功能
按优先级排列:
- kretprobe(guest):LR 劫持 + trampoline
- BRK 注入模式:完整 GVA→HVA 翻译 + guest 指令修改
- Guest 符号表加载:
VmSymbolRegistry+ VM 配置集成 bpf_get_current_vm_id()真实实现:对接 axvm 的 VM 上下文追踪