Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Hello

欢迎来到我的博客!

2026开源社区实习

这里是实习相关文档的索引:

AxVisor eBPF 性能追踪 - 项目目标

结合 eBPF 技术,跟踪 Hypervisor 和内核的性能


1. 背景与研究基础

1.1 已有技术基础

基于个人 eBPF 项目经验,已具备以下技术栈能力:

技术领域已实现能力
eBPF 程序类型Kprobe/Kretprobe、Tracepoint、XDP、TC、sock_ops/sk_msg、Uprobe
eBPF MapsHashMap、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)│
         └────────┘  └────────┘  └────────┘

关键性能瓶颈点:

序号瓶颈点位置性能敏感度
1VM Exit/Entry 处理AxVCpu::run() 循环极高
2Hypercall 处理AxVCpuExitReason::Hypercall 分支
3中断注入AxVCpu::inject_interrupt()
4MMIO/SysReg 模拟AxVCpuExitReason::MmioRead/Write
5嵌套页表处理AxVCpuExitReason::NestedPageFault
6vCPU 状态转换VCpuState 状态机

1.3 AxVisor 现有可观测性基础

AxVisor 当前已具备一定的可观测性能力,但均为硬编码采集方式,缺乏灵活的动态追踪机制:

1.3.1 日志追踪 (Log-based Tracing)

当前通过 trace!/debug!/info!/warn!/error! 宏在关键路径输出日志:

模块追踪点日志级别采集内容
axvcpu/vcpu.rsrun() 方法debugvCPU 状态转换
axvcpu/exit.rsExit 处理debugExit reason、参数
axvm/vm/mod.rsVM 生命周期info创建、启动、停止
kernel/vmm/mod.rsVMM 初始化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列出所有 VMvm_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 eBPFAya + Rust eBPF✅ 完全统一
追踪粒度应用级 (Go runtime)系统级 (Hypervisor)✅ 互补
数据采集Uprobe + KprobeTracepoint + Kprobe✅ 技术共享
Map 设计TraceID 关联VM/VCpu 关联✅ 模式相同
用户态交互CLI + K8s CRDCLI + 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 关键路径,定义以下追踪点:

子系统追踪点触发位置采集数据
vmmvcpu_run_enterAxVCpu::run() 入口vm_id, vcpu_id, timestamp
vmmvcpu_run_exitAxVCpu::run() 返回vm_id, vcpu_id, exit_reason, duration_ns
vmmhypercallHypercall 处理完成vm_id, vcpu_id, nr, args, duration_ns
vmmmmio_readMmioRead 处理vm_id, addr, width, duration_ns
vmmmmio_writeMmioWrite 处理vm_id, addr, width, data, duration_ns
vmmsysreg_accessSysReg 读写vm_id, addr, is_write, duration_ns
vmminterrupt_inject中断注入vm_id, vcpu_id, vector
vmmept_violationNestedPageFault 处理vm_id, gpa, access_flags, duration_ns
vmmvcpu_state_changeVCpuState 转换vm_id, vcpu_id, from_state, to_state
vmmvm_lifecycleVM 创建/启动/停止vm_id, event_type

3.3 性能指标定义

指标类别指标名称单位采集方式
频率VM Exit 频率exits/secMap 计数 + 时间窗口
频率Hypercall 调用频率calls/secMap 计数 + 时间窗口
延迟VM Exit 处理延迟ns时间戳差值
延迟Hypercall 处理延迟ns时间戳差值
延迟中断注入延迟ns时间戳差值
延迟MMIO 模拟延迟ns时间戳差值
延迟EPT Violation 处理延迟ns时间戳差值
分布Exit Reason 分布%Map 聚合统计
分布Hypercall 号分布%Map 聚合统计
分布延迟直方图count/bucketHistogram Map
利用率vCPU 运行时间占比%累计运行时间 / 墙钟时间

3.4 组件复用策略

来源组件复用方式工作量
Starry-OSksym直接引用
Starry-OSkbpf-basic直接引用
Starry-OStp-lexer直接引用
Starry-OStracepoint适配后复用
Starry-OSkprobe后续扩展
社区rbpf适配 no_std

4. 预期成果

4.1 技术产出

  1. axebpf 模块: ArceOS 兼容的 eBPF 运行时
  2. axksym 模块: 内核符号表生成与查找
  3. axtracepoint 模块: Tracepoint 定义与注册框架
  4. Shell trace 命令: 追踪点控制与统计数据展示

4.2 文档产出

  1. AxVisor eBPF 追踪使用指南
  2. ArceOS eBPF 子系统设计文档
  3. Hypervisor 性能分析最佳实践

4.3 对个人技术栈的扩展

  1. 技术深度扩展: 从应用层追踪扩展到 Hypervisor 层
  2. 跨层追踪能力: 可实现 Guest → Hypervisor → Host 全栈追踪
  3. 故障注入扩展: 可在 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 1Week 1-2基础设施依赖集成、符号表、追踪点框架
Phase 2Week 3-4eBPF 运行时rbpf VM、Helper 函数、Map 支持
Phase 3Week 5-6VMM 追踪点与 ShellHypervisor 追踪点、VMM 插桩、Shell 命令
Phase 4Week 7-8Kprobe/Kretprobe动态探针、多架构适配
Phase 5Week 9-10验证器与 UprobeeBPF 验证器、Uprobe 可行性研究
Phase 6Week 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函数名功能安全性
1bpf_map_lookup_elem查找 Map 元素返回指针需边界检查
2bpf_map_update_elem更新 Map 元素原子操作
5bpf_ktime_get_ns获取时间戳只读,无副作用
6bpf_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 IDAxVCpu::vm_id()bpf_get_current_vm_id()
当前 vCPU IDAxVCpu::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_stdPhase 2
eBPF Mapskbpf-basicStarry-OS直接引用Phase 2
符号表生成ksymStarry-OS直接引用Phase 1
追踪点框架tracepointStarry-OS适配后复用Phase 1
过滤表达式tp-lexerStarry-OS直接引用Phase 3
动态探针kprobeStarry-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 依赖
  • 支持 #![no_std](需适配)
  • 提供解释器和 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_HASHLRU 淘汰哈希表

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 兼容
rbpfMIT
kbpf-basicMIT/Apache-2.0
ksymMIT/Apache-2.0
tracepointMIT/Apache-2.0
tp-lexerMIT/Apache-2.0
kprobeMIT/Apache-2.0
PREVAILMIT

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 组件

组件链接
ksymhttps://github.com/Starry-OS/ksym
kbpf-basichttps://github.com/Starry-OS/kbpf-basic
tp-lexerhttps://github.com/Starry-OS/tp-lexer
tracepointhttps://github.com/Starry-OS/tracepoint
kprobehttps://github.com/Starry-OS/kprobe
eBPF Discussionhttps://github.com/orgs/Starry-OS/discussions/4

6.2 eBPF 参考

资源链接
rbpfhttps://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 Windowshttps://github.com/microsoft/ebpf-for-windows
Femto-Containershttps://github.com/future-proof-iot/Femto-Container
uBPFhttps://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 依赖复用

组件来源复用方式用途阶段
ksymStarry-OS直接引用符号表生成与查找Phase 1
tracepointStarry-OS直接引用追踪点框架Phase 1
tp-lexerStarry-OS直接引用过滤表达式解析Phase 3
kbpf-basicStarry-OSfork 引用eBPF Map/ringbufPhase 2
rbpf社区直接引用eBPF VMPhase 2
kprobeStarry-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.rsEbpfVm 结构体封装
程序加载execute(), execute_with_mem()

4.2 Week 4: Helper 与 Map

任务说明
helpers.rs标准 Helper 函数实现
maps.rs封装 kbpf-basic
Map 类型HashMap, Array, RingBuf

Helper 函数清单:

Helper ID函数名功能
1bpf_map_lookup_elem查找 Map 元素
2bpf_map_update_elem更新 Map 元素
3bpf_map_delete_elem删除 Map 元素
5bpf_ktime_get_ns获取时间戳
6bpf_trace_printk调试输出
8bpf_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_enterAxVCpu::run() 入口vm_id, vcpu_id, timestamp
vmm:vcpu_run_exitAxVCpu::run() 返回vm_id, vcpu_id, exit_reason, duration_ns
vmm:hypercallHypercall 处理vm_id, vcpu_id, nr, args, duration_ns
vmm:mmio_readMmioRead 处理vm_id, addr, width, duration_ns
vmm:mmio_writeMmioWrite 处理vm_id, addr, width, data, duration_ns
vmm:interrupt_inject中断注入vm_id, vcpu_id, vector
vmm:ept_violationNestedPageFaultvm_id, gpa, access_flags, duration_ns
任务说明
定义 VMM 追踪点使用 define_tracepoint! 宏
axvcpu 插桩AxVCpu::run() 方法
Exit 处理插桩Hypercall, MMIO, EPT violation
Hypervisor Helpervm_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函数名功能
100bpf_get_current_vm_id获取当前 VM ID
101bpf_get_current_vcpu_id获取当前 vCPU ID
102bpf_get_exit_reason获取 VM Exit 原因
103bpf_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 组件

组件链接
ksymhttps://github.com/Starry-OS/ksym
kbpf-basichttps://github.com/Starry-OS/kbpf-basic
tp-lexerhttps://github.com/Starry-OS/tp-lexer
tracepointhttps://github.com/Starry-OS/tracepoint
kprobehttps://github.com/Starry-OS/kprobe
eBPF Discussionhttps://github.com/orgs/Starry-OS/discussions/4

10.2 社区组件

组件链接
rbpfhttps://github.com/qmonnet/rbpf
PREVAILhttps://github.com/vbpf/ebpf-verifier
Ayahttps://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 独立模块:集成 ksymtracepointtp-lexerkbpf-basicrbpf 实现 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 使用了 VaListVaListImpl 类型,在 Rust nightly >= 2025-12 上编译失败,错误信息为 cannot find type VaListImpl in module core::ffi

原因:Rust 重构了 core::ffi 模块,VaListImpl 被移除,VaList 的 API 也有变化。

解决方案

  1. Fork kbpf-basic 仓库
  2. 更新 printf-compat 依赖到兼容新 API 的版本
  3. 修改 VaList 相关代码适配新 API
  4. 提交 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 解析流程:

  1. 解析 maps section 提取 Map 定义(每个 28 字节)
  2. 解析符号表获取 Map 名称
  3. 调用 maps::create() 创建 Map 并获取 FD
  4. 解析 .reltracepoint 重定位表
  5. 修补 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 种
上游 PR1 个(已合并)

五、下阶段计划

优先级任务说明
P0完善文档补充 API 文档、使用示例、架构说明
P1Kprobe 支持移植 Starry-OS kprobe 库,实现动态探针
P1axvm 追踪点插桩待 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 的依赖,后续可用于实现追踪点过滤功能。
rbpfeBPF 虚拟机核心执行引擎。runtime.rs 使用 EbpfVmRaw 执行带上下文的 eBPF 字节码,通过 register_helper 注册 helper 函数,register_allowed_memory 注册可访问内存区域(如 LOOKUP_BUFFER)。程序执行后返回 r0 寄存器值。
kbpf-basiceBPF 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 复用 rbpfrbpfrbpf 是成熟的 Rust 实现,支持 no_std,指令集完整,减少开发时间。代价是依赖外部仓库。
Map 实现简化 Vec 实现 vs kbpf-basickbpf-basic早期用 Vec 线性扫描(O(n)),后迁移到 kbpf-basic 获得真正的 O(1) HashMap 和 LRU 淘汰。代价是需要实现 KernelAuxiliaryOps 适配层。
KernelAuxiliaryOps 适配完整实现 vs 最小实现最小实现(5/15 方法)只实现 get_unified_map_from_fdcurrent_cpu_idebpf_time_nsebpf_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 收获

技术收获

  1. eBPF 执行模型理解:深入理解了 eBPF 程序的加载→验证→执行→Map 交互流程,以及 helper 函数的调用约定(r1-r5 传参,r0 返回)。

  2. ELF 解析实践:实现了最小化的 ELF64 解析器,支持 section 查找、符号表解析、重定位表处理。理解了 ld_imm64 指令的 Map FD 修补机制。

  3. no_std 环境适配:在无标准库环境下集成多个依赖,处理了 alloc、spin lock、static-keys 等基础设施问题。

  4. Rust nightly 兼容性:修复 kbpf-basic 的 VaList API 变更问题并贡献上游,理解了 Rust 不稳定特性的演进。

工程收获

  1. 模块化设计:axebpf 通过 feature flags 控制功能启用(symbols/tracepoint-support/runtime),便于裁剪和测试。

  2. 抽象层设计platform.rs 提供 mock/real 模式切换,map_ops.rs 实现 KernelAuxiliaryOps 适配层,解耦了依赖库与内核实现。

  3. 渐进式实现:先用简化方案验证端到端流程(如 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 完全不同。

解决方案

  1. 在 arm_vcpu 的 current_el_sync_handler 中添加 BRK 指令识别逻辑
  2. 检查 ESR_EL2.EC == 0x3C(BRK from AArch64)
  3. 将 TrapFrame 指针和大小传递给 axebpf 的 kprobe handler
  4. 通过回调函数更新 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实现函数返回探针的栈帧管理
P1axvm 追踪点插桩待 axvm 重构后添加 vCPU 运行时追踪点
P2x86_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
└─────────────────┘

执行流程

  1. CPU 执行到 BRK #4,触发同步异常(EC=0x3C, ISS=0x4)
  2. 异常处理器执行 eBPF 程序,然后将 PC 设置为指令槽地址
  3. 异常返回后执行指令槽中的原始指令
  4. 执行到 BRK #6,再次触发异常(EC=0x3C, ISS=0x6)
  5. 异常处理器将 PC 设置为原始函数的下一条指令(original_pc + 4)
  6. 异常返回,继续正常执行

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_EL1ESR_EL2, FAR_EL2
异常向量表VBAR_EL1VBAR_EL2
上下文结构内核 pt_regsHypervisor TrapFrame
页表基址寄存器TTBR0_EL1/TTBR1_EL1TTBR0_EL2
任务上下文current_taskper-CPU 状态
追踪目标内核函数VMM 代码路径

适配要点

  1. 异常处理入口不同,需要在 arm_vcpu 的 current_el_sync_handler 中添加 BRK 识别逻辑
  2. TrapFrame 结构不同,需要正确提取 PC 和传递寄存器上下文给 eBPF 程序
  3. 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 收获

技术收获

  1. 软件单步机制:深入理解了 kprobe 使用双 BRK 指令实现软件单步的原理,比硬件单步更简单可控。

  2. Trait 抽象设计:学习了 KprobeAuxiliaryOps trait 如何将平台相关操作抽象出来,使 kprobe 库可以跨平台复用。

  3. 页表动态修改:掌握了运行时修改 .text 段权限的方法,包括页表项修改、TLB 刷新、I-cache 刷新的完整流程。

  4. AArch64 异常模型:深入理解了 EL2 异常处理流程,ESR_EL2.EC/ISS 字段编码,以及异常返回机制。

  5. Per-CPU 编程模式:理解了在无任务抽象的 Hypervisor 环境中如何管理执行状态。

工程收获

  1. 跨 crate feature 管理:学习了 Cargo feature 的依赖传递机制,理解了 feature = ["dep/feature"] 语法的作用。

  2. 链接器脚本定制:掌握了 linker section 的定义方法,理解了如何为指令槽预留可执行内存区域。

待深入方向

  • Kretprobe trampoline 机制:如何劫持函数返回地址并在返回时执行 eBPF 程序
  • 递归 kprobe 的重入保护:当 eBPF 程序或 helper 函数本身触发 kprobe 时如何避免死循环
  • x86_64 适配:INT3 单字节指令的处理、VMX root mode 异常处理差异

周报(2026-03-30 至 2026-04-12)

开发分支


总结

这两周没有再往共享库 uprobe/uretprobedlopen 生命周期这些方向扩功能,主要还是把已经做出来的主线能力尽量联调透、验证透、文档也补齐。现在 tracepointhprobe/hretprobeguest-kprobe/kretprobeguest-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=2781
  • hretprobe=2781
  • guest-kprobe gic_handle_irq=1129
  • guest-kretprobe __arm64_sys_getpid=14
  • vmm:timer_tick 的 attachment 和 map activity 都存在

联调过程中,几类关键证据现在都能稳定看到:

  1. guest 启动后可以看到 axebpf_integration_demo wrapper: launchdemo:start
  2. 运行期可以看到 guest_uprobe_hitguest_uretprobe_hit
  3. 同一运行窗口里可以看到 guest_kprobe_hits: kprobe vm1:gic_handle_irq ...
  4. 同时也可以看到 guest_kprobe_hits: kretprobe vm1:__arm64_sys_getpid ...
  5. host 侧能持续看到 [hprobe] ENTRY/EXIT ... notify_guest_vmexit
  6. demo 结束后,收尾 trace listtrace stat 都能拿到非零命中结果

这里有两个点需要单独说明。

2.1 guest-uprobe/guest-uretprobe 的收尾表现

guest 进程退出后,收尾 trace list 里不会保留 active 实例,而只会剩下 pending 模板,并显示 waiting-for-instance。这不是失败,而是当前实现下实例会随着进程退出一起回收。

所以这类能力到底有没有跑通,主要还是看运行期 raw log 里的:

  • guest_uprobe_hit
  • guest_uretprobe_arm
  • guest_uretprobe_hit

2.2 hprobe 日志比较密

hprobe/hretprobe 的命中频率很高。如果 demo 结束后不及时关闭 verbose,串口很容易被刷屏,最后抓快照反而不好看。所以现在 workflow 和手工手册里都统一要求在 demo:done 之后立即执行:

  • trace verbose off

再抓最终的 trace listtrace stat


三、为什么共享库等功能当前不可用

这两周没有继续把共享库 uprobe/uretprobedlopen 场景往下推进,不是因为“只差最后一点”,而是因为它已经超出了当前这个收尾阶段适合继续扩展的范围。

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-backed mmap
  • 但日志里会出现 reason=no_pending_path
  • 最终 trace list 中始终只有 pending 模板,没有共享库实例进入 active

3.3 这时候更适合收口,不适合继续开新功能

这次没有继续修共享库能力,主要有三个原因:

  1. 这已经不是补一个小接口就能结束的问题,而是要继续改 runtime observer 的对象路径来源和实例识别逻辑
  2. 主程序场景和主线联调能力已经可以形成一份完整交付,再继续扩共享库,会把收尾重新拖回新功能开发
  3. 现阶段更合理的目标,是把已经做成的能力联调清楚、验证清楚、文档写清楚,便于后续交接

所以这两周的取舍是明确的:先不扩共享库,而是把已经做成的能力收成一条可复现、可演示、可验收的完整链路。

共享库未完成原因已经单独整理到:

  • docs/ebpf-tracing/shared-library-uprobe-status.md

四、下一步收口方向

后续工作不再增加新功能,而是继续围绕现有主线能力做交接式收口:

  1. 继续以 tmp/run_axebpf_mainline_integration_real.py 作为 fresh 回归主入口
  2. 如需现场演示,优先按 docs/ebpf-tracing/axebpf-mainline-integration-manual-workflow.md 执行
  3. 如需回看主线联调逻辑,优先看 workflow、matrix 和最新 PASS raw log
  4. 如果后续有人继续做共享库支持,直接从 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/tcpTCP socket连接状态导出
iter/udpUDP socketUDP 端点枚举
iter/bpf_map_elemBPF Map 条目Map 批量操作
iter/netlinkNetlink socket网络诊断

2.4 与 kprobe/kretprobe/uprobe 的区别

维度kprobe/kretprobe/uprobeBPF Iterator
触发机制被动:等待事件发生主动:用户态驱动遍历
执行时机函数入口/出口被调用时用户态 read() 时
数据来源当前执行上下文(寄存器、栈)内核全局数据结构
观测范围单次函数调用整个数据结构集合
状态感知只能捕获“变化“可获取“当前全貌“
实现方式断点指令 + 异常处理seq_file + 内核遍历框架

简单来说:

  • kprobe 在函数入口插入断点(如 BRK #4),函数被调用时触发,只能观测到“正在发生的调用“。
  • kretprobe 在函数返回时触发,捕获返回值,同样是事件驱动。
  • uprobe 和 kprobe 类似,但作用于用户态程序。
  • Iterator 不依赖任何事件,直接遍历内核维护的数据结构。比如可以获取“此刻系统中所有 TCP 连接“这样的全局快照。

2.5 内核数据结构依赖

Iterator 程序直接访问内核内部数据结构。以 iter/tcp 为例,程序需要访问 struct sock_commonstruct 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 listVM ID、名称、状态、vCPU 数、内存大小
vm show <id>同上(--full/--config/--stats flag )
trace list已注册的 tracepoint 和 kprobe
trace stateBPF 程序收集的事件统计

缺失的信息:

维度缺失内容诊断价值
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_regionGuest 物理地址区域审计内存映射
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 状态),需要做这几件事:

  1. 实现遍历框架:类似 seq_file 的迭代器基础设施
  2. 注册遍历目标:为 VM、vCPU、设备等对象提供遍历回调
  3. 定义上下文结构:如 bpf_iter__vmbpf_iter__vcpu
  4. 暴露触发接口:通过 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 解析NetlinkBPF 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 的类比

LinuxAxVisor特权级关系
内核 (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 内存的执行权限。

  1. 注册:VMM 记录目标 GVA(guest 虚拟地址),通过 guest 页表(TTBR0_EL1/TTBR1_EL1)翻译为 GPA(guest 物理地址),然后将该 GPA 所在页面的 Stage-2 映射标记为不可执行(XN=1)
  2. 触发:guest vCPU 执行到该页面 → Stage-2 Permission Fault → 陷入 EL2
  3. 处理:VMM 的 fault handler 检查 fault 地址是否匹配已注册的 kprobe:
    • 匹配:构建 TraceContext(从 vCPU 上下文提取 EL1 寄存器),执行 eBPF 程序,临时恢复页面执行权限,单步执行一条指令后重新标记 XN
    • 不匹配:走正常 Stage-2 fault 流程
  4. 优点:不修改 guest 内存,guest 完全无感知
  5. 缺点:页面粒度(4KB),同一页面内多个函数会频繁触发 fault;单步执行需要额外处理

4.2 模式二:BRK 注入(高级,低延迟)

类似 Linux uprobe,直接修改 guest 内核内存。

  1. 注册:GVA → GPA → HVA(通过 Stage-2 页表),将目标指令替换为 BRK #kprobe_guest_brk_imm(使用与 hprobe 不同的立即数以区分来源)
  2. 触发:guest 执行 BRK → EL1 异常 → 因 HCR_EL2.TGE 或路由配置陷入 EL2
  3. 处理:VMM 识别为 guest kprobe,执行 eBPF,恢复原指令并单步
  4. 优点:指令级精度,开销低
  5. 缺点:侵入 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)寄存器:

  1. 在 kprobe 触发时记录原始 LR
  2. 将 LR 修改为一个特殊的 trampoline 地址(VMM 预先在 guest 内存中映射的一段 BRK 指令)
  3. Guest 函数返回时执行 BRK 陷入 EL2
  4. 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_pathsystem_map_path
  • VM 启动时 VMM 加载该文件,为每个 VM 维护独立的 ksym::SymbolTable
  • 用户按符号名注册:trace kprobe vm0:schedule
  • 复用现有 ksym crate,每个 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_typevm_id 字段)
  • 建立 probe/registry.rs 统一探针注册表
  • Shell 命令从 trace kprobe 改为 trace hprobe
  • 更新 feature gate:kprobehprobe
  • 验收标准:现有 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 程序并输出

阶段三:增强功能

按优先级排列:

  1. kretprobe(guest):LR 劫持 + trampoline
  2. BRK 注入模式:完整 GVA→HVA 翻译 + guest 指令修改
  3. Guest 符号表加载:VmSymbolRegistry + VM 配置集成
  4. bpf_get_current_vm_id() 真实实现:对接 axvm 的 VM 上下文追踪