0%

2026开源社区实习总结

AxVisor 中统一 eBPF Tracing 系统的设计与实现

摘要

本文总结了在 AxVisor 中构建统一 eBPF tracing 系统的阶段性工作。系统面向 VMM、Guest Kernel 和 Guest Userspace 三个执行层次,目标是把分散的事件源接入同一条程序装载、命中处理、运行时执行和事件输出链路中。实现过程中,先建立了 tracepoint、runtime、map、helper 和统一事件流等基础设施,再逐步扩展到 hprobe/hretprobeguest-kprobe/kretprobeguest-uprobe/uretprobe。其中,userspace tracing 重点解决了对象元数据装载、运行期激活、实例管理和生命周期回收等问题。最终形成了一套可以继续扩展、并已具备联调与验收路径的 tracing 基础框架。

1. 引言

本阶段工作的目标,是在 AxVisor 中建立一套统一的 tracing 系统,使 Hypervisor 一侧能够持续观察 VMM、Guest Kernel 和 Guest Userspace 的关键运行路径。这个问题之所以需要单独处理,是因为 AxVisor 的观测对象天然分布在不同执行层次中:VMM 运行在 EL2,Guest Kernel 运行在 EL1,Guest Userspace 运行在 EL0。实际联调时,很多问题都跨越这些边界。仅依赖 VMM 日志,通常只能看到 VM-exit、异常类型和少量上下文;仅依赖 Guest 内部工具,又难以把 Guest 的执行位置与 Hypervisor 侧的异常接管、执行权限变化和探针状态对应起来。

因此,这项工作关注的是一条完整的运行时观测链路。系统需要回答以下几个问题:程序如何进入系统,用户如何表达探测意图,目标在什么条件下能够真正启用或激活,命中之后怎样形成统一上下文,eBPF 程序如何执行,结果如何进入统一事件流,以及生命周期结束后相关状态如何回收。本文围绕这些问题,讨论这套 tracing 系统的整体组织方式、基础设施建立过程、Guest Kernel Tracing、Guest Userspace Tracing,以及相应的验证路径和当前边界。

2. 总体架构

2.1 一条统一的运行链路

从使用者角度看,系统最终提供了四类主要入口:静态 tracepoint、VMM 自探针 hprobe/hretprobe、Guest Kernel 探针 guest-kprobe/kretprobe,以及 Guest Userspace 探针 guest-uprobe/uretprobe。从内部实现看,这四类能力共享一条统一运行链路:

  1. 用户先把 eBPF 程序装入系统。
  2. 用户再把程序绑定到某个事件源或探测目标。
  3. 目标在运行中被触发或命中。
  4. 系统把这次触发整理成统一上下文。
  5. runtime 执行对应程序。
  6. 执行结果进入统一事件流和状态统计。
  7. 用户通过统一命令查看状态、事件和统计结果。

这条链路的意义在于,系统被自然分成了两个部分。前半段负责让不同来源的命中成立,后半段负责把这些命中统一整理成程序执行和事件输出。前半段会随着 probe 类型变化而变化,后半段则尽量保持稳定。这样,系统后续继续扩展新的 probe 类型时,重点会落在目标管理、命中条件、恢复和生命周期处理上,而不需要每次都重做一套新的程序执行和输出方式。

2.2 程序、目标、绑定和实例

这套系统内部一直围绕四类对象组织状态:程序、目标、绑定和实例。

程序是已经装入系统、可以被执行的 eBPF 程序。它包含字节码、关联 map 和必要元数据。程序先进入系统,随后再被不同目标引用。

目标是用户希望观察的对象。对 tracepoint 来说,目标是某个静态事件;对 hprobe 来说,目标是某个 VMM 符号;对 guest-kprobe 来说,目标是某个 Guest Kernel 地址或符号;对 guest-uprobe 来说,目标是某个路径对象中的符号或偏移。

绑定表示程序与目标之间的关系。例如,一段程序可以绑定到某个 tracepoint,也可以绑定到某个 hprobe 或 guest-kprobe 目标。绑定关系在目标生命周期中通常比实例更稳定。

实例表示某个目标在运行时真正活跃的状态。这个概念在 userspace 场景中尤为重要,因为 userspace 目标在注册时只是逻辑对象,只有当路径对象真正进入某个地址空间后,系统才会生成对应实例。实例会随着运行条件变化而生成和回收。

这套划分的直接作用,是让系统可以自然表达几类真实状态:程序已经装入但尚未使用,目标已经登记但尚未启用,userspace 目标已经存在绑定但尚未激活出运行实例。没有这层划分,很多运行时状态就只能被硬塞进同一种结构里,后续逻辑会很快变乱。

2.3 统一上下文和统一事件流

不同 probe 的命中方式差异很大,系统没有试图把这些差异全部消掉,而是把差异保留在命中前后的管理阶段,把命中之后的输入和输出统一起来。

统一上下文是第一步。一次命中进入 runtime 之前,系统会整理出一份统一输入,其中至少包含事件或探针标识、时间戳、VM 与 vCPU 信息、探针类型以及若干通用参数。在 Guest 探针场景下,还会附带 Guest 的寄存器内容。这样,程序面对的是稳定输入模型,而不是随着 probe 类型变化的多套完全不同的数据结构。

统一事件流是第二步。命中完成后,系统会把结果写成统一事件记录。事件中保存 probe 类型、事件标识、归属信息、时间戳、参数和值得保留的时长信息。事件优先进入统一事件通道;若主通道当前不可用,系统仍会保留近期事件的本地副本,保证 shell 侧能够继续读取结果。这样,trace streamtrace dumptrace stat 面对的始终是一类统一事件,只是来源和语义不同。

这里还需要说明一个实现上的差别。tracepoint 的程序执行是通过统一事件发射路径触发的:静态事件先生成事件,再根据事件名查找已绑定程序并执行。动态 probe 则不是这样。hprobeguest-kprobeguest-uprobe 的程序执行是在各自的命中处理逻辑中直接调用 runtime 完成的,同时再把结果写入统一事件流。也就是说,事件流对所有 probe 都统一存在,但“程序执行发生在事件发射前还是事件发射阶段”这一点,在静态 tracepoint 和动态 probe 之间是不同的。

2.4 四类 probe 的运行差异

虽然系统使用统一上下文和统一事件流,但不同 probe 的主要差异仍然很明显。

tracepoint 的目标在代码里已经定义好,命中条件由事件点自身决定。
hprobe/hretprobe 面向 EL2 自身代码,需要处理入口和返回路径上的动态探测。
guest-kprobe/kretprobe 需要先解决 Guest 地址翻译、启用条件、异常接管和单步恢复。
guest-uprobe/uretprobe 在此基础上还要处理对象元数据装入、运行时激活以及地址空间级实例管理。

也就是说,这套系统真正稳定下来的,是命中之后的统一处理链;而命中之前的目标管理和运行时控制,则根据 probe 类型各自展开。

3. 第一阶段:Host/VMM 侧基础设施的建立

3.1 建立顺序

第一阶段的工作有明确顺序。系统先解决程序装载和执行问题,再建立统一事件流,随后用静态 tracepoint 验证整条基础链能否跑通,最后把 hprobe/hretprobe 作为第一类动态事件源接入。

这个顺序的意义很直接。动态探针一旦进入系统,运行时问题会立刻增多:目标怎么命中,命中后如何整理上下文,程序能否执行,结果从哪里输出,命中后是否需要恢复。如果程序装载和事件输出本身都还不稳定,后续调试时很难判断问题到底出在命中逻辑,还是出在 runtime 或输出链路。因此,系统先把 tracing 的后半段建立起来,再逐步补前半段的命中逻辑。

3.2 程序如何进入系统

程序装载链路是第一阶段最先建立的部分。用户加载 eBPF 程序时,系统先识别输入是原始字节码还是 ELF 对象。如果是 ELF,对象解析会继续向下展开:识别程序段、map 定义和重定位信息,为每个 map 创建运行时对象,并把对象中的 map 名称和系统中创建出的 map 实例对应起来。

map 创建完成后,系统继续处理两类重定位。一类是 map 相关重定位,使程序中的 map 引用能够正确指向系统中已创建的对象;另一类是对象内部函数调用重定位,使程序能够正确跳转到对象中的其他函数。只有这些步骤完成之后,系统才得到一段真正可执行的 eBPF 指令流。

装载结束后,程序会被登记到统一注册表中,并保留与之关联的 map 集合和必要元数据。后续 tracepoint 或 probe 命中时,系统根据程序标识直接取出对应实例执行,无需重复解析对象。换句话说,程序装载和探针注册在系统中是两条分开的链:程序先进入系统,探针之后再引用它。

3.3 运行环境如何建立

程序装入系统之后,还需要稳定运行环境。这个环境主要由 map、helper 和程序实例构成。

map 在 tracing 中承担多个角色。它既可以保存命中计数,也可以保留跨命中状态,还可以承载一些需要在 shell 中读取的执行结果。由于 map 在装载阶段已经创建好,因此后续程序执行时可以直接访问这些对象,而不需要临时构造外围状态。

helper 为程序提供运行期能力。系统在 runtime 初始化时统一注册一组 helper,包括时间获取、CPU 信息获取、map 操作,以及与 Hypervisor 场景直接相关的 VM、vCPU 和退出原因等信息获取能力。这样,程序在执行时既可以读取统一上下文中的输入,也可以通过 helper 主动获取当前运行环境。

程序实例是真正执行的运行单元。装载阶段生成的程序对象,在这里被封装成“可执行程序体 + 关联 map + 元数据”的组合。命中发生时,系统不再回到装载逻辑,而是直接取出实例、构造 VM、注册 helper、传入上下文并执行。

3.4 统一上下文和统一事件流如何立起来

在第一阶段,系统先定义了统一上下文,再建立统一事件流。

统一上下文的目的,是让不同事件源都能进入同一条程序执行路径。上下文中至少包含事件或探针标识、时间戳、VM 与 vCPU 信息、探针类型以及若干参数。后面 Guest 侧探针接进来时,还会在这份上下文中附加寄存器内容。这样,程序面对的输入模型保持稳定,不需要随着事件来源变化而切换到完全不同的结构。

在上下文之上,系统继续建立统一事件流。每次命中和程序执行之后,系统都会把结果整理成固定格式事件记录。事件优先进入统一事件通道;若主通道当前不可用,系统仍会保留近期事件的本地副本。这样一来,shell 侧的观察路径从一开始就是统一的:无论事件来自 tracepoint 还是后续的动态 probe,都会进入同一类事件输出。

3.5 Tracepoint 为什么最先接入

在程序装载、运行环境和事件流已经建立之后,tracepoint 成为第一类接入系统的事件源。原因很清楚:tracepoint 的触发条件由代码本身决定,不涉及地址翻译、断点注入或单步恢复,因此最适合用来验证整条基础链是否成立。

一次 tracepoint 触发时,系统会完成以下动作:构造上下文,检查当前是否有程序绑定在该事件上,生成统一事件,更新统计信息,并根据事件名查找已绑定程序并执行。只要这条链能够稳定工作,就说明程序装载、上下文组织、runtime 执行和事件输出已经形成完整路径。

因此,tracepoint 在第一阶段承担的作用不仅是提供最早可用功能,也用于验证 tracing 的基础设施是否已经站稳。

3.6 hprobe/hretprobe 如何接入现有基础设施

当 tracepoint 路径已经成立之后,系统接入了 hprobe/hretprobe。这时新增的重点不再是“命中之后如何执行程序”,而是“VMM 自身函数如何进入这条已有链路”。

用户注册 hprobe 时,系统先解析目标符号并保存探针记录。为了支持同一函数同时观察入口和返回路径,系统按地址维护成对的探针槽位:一个对应入口探针,一个对应返回探针。

启用之后,入口探针在函数进入时触发,返回探针则维护额外的返回路径状态。命中发生后,入口探针会直接在命中回调中执行绑定程序。随后,异常处理路径再写入一条统一的 hprobe 事件记录。返回探针则在返回回调中执行绑定程序,并写入一条 hretprobe 事件记录。这样,hprobe/hretprobe 成为 tracing 中的第一类动态事件源,但仍然建立在第一阶段已经完成的基础设施之上。

3.7 第一阶段的结果

到第一阶段结束时,系统已经具备了完整基础链:

  • 程序可以装载、解析、重定位并进入系统;
  • map 和 helper 可以为程序执行提供稳定运行环境;
  • 统一上下文和统一事件流已经建立;
  • shell 侧已有统一命令入口;
  • tracepoint 可以稳定触发程序执行;
  • hprobe/hretprobe 已经作为动态事件源接入同一条链路。

后面的 Guest Kernel Tracing 和 Guest Userspace Tracing,都是建立在这条基础链之上的扩展。

4. 第二阶段:Guest Kernel Tracing 的运行逻辑

4.1 用户输入之后,系统先做什么

用户注册一条 Guest 内核探针时,通常会给出 VM 标识、目标地址或符号、探针类型以及要绑定的程序。系统收到请求后,第一步并不是立即建立命中条件,而是先把这条探针记录下来。

这样处理的原因在于,Guest Kernel Tracing 是否能立即启用,取决于当前 Guest 的运行时状态。系统此时虽然已经知道“用户想观察哪个目标”,但还不一定具备“现在就能安全启用这条探针”的条件。因此,探针生命周期被拆成两个阶段:注册阶段负责保存目标和参数;启用阶段负责在条件满足后真正建立命中条件。

4.2 地址翻译为什么是前提

Guest 内核探针的目标最初是 Guest 虚拟地址。Hypervisor 若要控制这个目标,就必须把它一步步翻译到可操作位置。这要求 VMM 向 tracing 子系统提供一组运行时能力:获取 Guest 页表根信息、读取 Guest 页表、把 Guest 物理地址映射到 Hypervisor 可访问位置,以及修改对应执行区域的 Stage-2 执行属性。

有了这些能力之后,系统才能判断目标地址当前是否可翻译、属于哪个执行区域,以及是否已经具备启用条件。因此,地址翻译在 Guest Kernel Tracing 中是前提条件,而不是一个附属工具。

4.3 延迟启用如何发生

注册完成后,系统会立即尝试启用探针。如果此时 Guest 页表上下文已经可用,启用过程会继续向下执行;如果系统还拿不到必要翻译信息,探针就停留在已注册但未启用状态。之后,系统会在 Guest 真实运行的关键时机重新尝试启用,尤其是在 VM-exit 之后。此时若上下文已经具备,探针会自动进入已启用状态。

这样处理之后,用户可以先登记目标,再等待 Guest 进入可探测状态。对系统来说,这使 Guest 的运行时不确定性能够被纳入探针生命周期管理中。

4.4 两种命中模式如何工作

系统为 Guest Kernel Tracing 保留了两种命中方式。

第一种通过 Stage-2 执行权限控制建立命中条件。启用探针后,系统会限制目标执行区域的可执行属性。Guest 一旦运行到该区域,会因为执行异常回到 EL2。系统随后判断这次 fault 是否对应已启用探针,若匹配成功,就把它解释为一次 probe 命中。

第二种通过断点注入建立命中条件。启用时,系统先保存目标位置的原始指令,再把该位置改写为断点指令。Guest 运行到该位置时,通过断点异常进入 EL2。系统据此识别 probe 命中。

这两种方式都能让 Guest 把控制权带回 EL2,但后续恢复逻辑不同。执行权限控制侧重执行区域管理与临时执行窗口;断点注入侧重原始指令保存、重新注入和中间状态清理。系统同时保留两种模式,使不同调试场景可以选择不同命中路径。

4.5 命中之后,系统怎样处理一次 Guest Probe

无论采用哪种命中方式,一次 Guest Kernel Probe 命中后,系统都会进入统一处理链。系统先在 EL2 异常入口识别当前 VM 和异常类型,再判断这次异常是否对应某条已启用探针。若匹配成功,就提取 Guest 当前寄存器和相关上下文,构造统一输入,执行绑定程序,并把结果写入统一事件流和统计信息中。

到这里,命中只是完成了一半。剩下的一半,是系统必须把 Guest 带回原控制流。Guest 侧 tracing 最困难的地方,主要就落在这一段。

4.6 恢复为什么是关键环节

Guest 命中探针之后仍然要继续运行,因此恢复是 Guest Kernel Tracing 中最关键的环节之一。

如果系统在断点命中后只记录一次事件而不恢复原始指令,Guest 会在同一位置反复异常,无法继续向前执行。反过来,如果系统恢复了原始指令,但不再重新建立探针条件,那么这条探针只会命中一次,之后就失去作用。执行权限控制路径也有类似问题:如果系统不在合适时机重新恢复执行限制,Guest 要么反复 fault,要么失去后续探测能力。

因此,恢复链必须完成以下动作:

  • 让当前命中对应的原始执行内容能够继续执行;
  • 使用单步或等价方式把 Guest 安全带过当前探测点;
  • 在执行通过之后重新建立探针条件;
  • 最后把 Guest 送回正常控制流。

恢复失败会直接带来两类后果:Guest 在同一位置重复异常,或者探针在首次命中后失效。也正因为这一点,恢复链是命中链的一部分。

4.7 guest-kretprobe 如何工作

guest-kprobe 关注的是函数进入时发生了什么,guest-kretprobe 关注的是这一次进入最终怎样返回。因此,返回探针比入口探针多出一层调用级状态管理。

在入口命中时,系统会记录与当前调用对应的返回路径信息;函数真正返回时,系统再利用这部分信息识别返回命中,补充返回值和持续时间,并在结束后清理对应状态。这里增加的内容不是简单“多拿一个返回值”,而是让系统能够把某次入口命中和稍后发生的一次返回关联起来。

4.8 卸载和回收

Guest 探针生命周期结束时,系统还要处理运行中的残留状态。例如,探针启用时可能已经修改过执行权限,或在断点模式下替换过原始指令,还可能在返回路径上保留中间状态。如果这些状态不被回收,Guest 后续运行时仍可能撞上已失效探针留下的执行条件。因此,探针卸载和 VM 生命周期结束时,系统会一并清理这些状态。系统还专门保留了 stale BRK 处理逻辑,用来消化探针卸载后可能残留的旧断点命中。

4.9 第二阶段的结果

到第二阶段结束时,Guest Kernel Tracing 已经形成了完整运行链:

  • 用户可以按地址或按符号注册目标;
  • 探针可以先注册,再在条件满足后启用;
  • 系统能够通过两种模式建立命中条件;
  • EL2 能够识别并接管 Guest 探针命中;
  • 命中后能够执行程序、生成事件并完成恢复;
  • 卸载和 VM 生命周期结束时,相关状态能够回收。

5. 第三阶段:Guest Userspace Tracing 的运行逻辑

5.1 对象元数据如何进入系统

Userspace 场景中,用户输入的是 VM、路径、符号或偏移,以及要绑定的程序。系统首先要理解目标对象本身,因此 userspace tracing 的起点是对象元数据装载。加载完成后,系统能够把某个路径对象中的符号和偏移解析出来。之后注册 uprobe 时,系统根据“路径 + 符号/偏移”建立一条待激活绑定。

这一阶段得到的只是逻辑目标。系统知道将来需要观察哪个对象里的哪个位置,但还不知道这个对象是否已经被执行,也不知道它当前位于哪个地址空间位置。

5.2 待激活绑定和运行实例

Userspace Tracing 内部同时维护两类对象:待激活绑定和运行实例。

待激活绑定记录用户的探测意图,它在对象元数据已经进入系统后建立,可以长期存在。
运行实例则表示某条绑定在某个具体地址空间中的一次真实激活。运行实例只在对象真正进入地址空间之后生成,并在地址空间退出后回收。

这种分层非常必要。因为 userspace probe 在注册时只有逻辑目标,没有运行地址。只有在对象真正被执行并映射之后,系统才知道应该在哪个 runtime PC 上建立命中条件。

5.3 observer 如何把逻辑目标变成实例

运行期 observer 的作用,就是把逻辑目标变成运行实例。它通过跟踪程序执行和可执行映射建立来回答两个问题:某个路径对象是否已经和某个进程建立关系,以及它是否已经真正映射到某个地址空间中。

实现上,这个 observer 并不是独立运行的一套旁路,而是建立在一组隐藏的 Guest 内核探针之上。系统会在 Guest 内核侧挂接与 execveexecveatvm_mmap_pgoff、运行期重试激活以及 exit_mmap 等过程相关的隐藏探针,用它们采集 userspace 对象进入地址空间时所需的关键信息。

一旦这两个条件都满足,系统就根据映射起点、文件偏移和对象内偏移计算 runtime PC,把待激活绑定推进成运行实例。直到这一步完成,userspace probe 才真正具备可命中的运行位置。

5.4 为什么按地址空间管理实例

系统采用地址空间级实例管理。也就是说,userspace probe 的管理单位不是“某条路径”,而是“某条路径在某个具体地址空间中的一次映射实例”。

这样做的原因在于,同一路径对象可以被多个进程同时执行,而不同进程拥有不同的地址空间、运行地址和命中状态。如果系统只按路径维护状态,一个进程中的命中和返回路径信息会影响另一个进程。地址空间级管理之后,每次命中都可以明确归属到某个实例上,返回路径管理和后续回收也都围绕这个实例展开。

5.5 命中和恢复

实例激活完成后,系统才会真正建立 userspace probe 的命中条件。命中发生时,系统先确认当前 PC 属于哪个实例,再提取对应上下文,执行绑定程序,更新实例命中次数,并生成统一事件。之后,系统还要完成恢复,让用户态程序继续运行。

uretprobe 会在此基础上增加一层返回路径状态管理。入口命中时,系统保存与当前实例对应的返回路径信息;返回命中时,再补充返回值和执行时间,并清理相关状态。

5.6 回收

userspace tracing 的实例具有天然生命周期。某个对象实例一旦所在进程退出,或者地址空间被销毁,它就不应继续保留在运行实例集合中。因此,observer 不仅负责激活实例,也负责在适当时机回收实例。回收时,系统会删除与该地址空间相关的运行实例,并同时清理与这些实例绑定的运行期状态。

这里回收的是“这一次运行实例”,不是“用户的探测意图”。待激活绑定仍然保留,因此当同一路径对象将来再次进入某个地址空间时,系统仍然可以重新生成新实例。

5.7 第三阶段的结果

到第三阶段结束时,Guest Userspace Tracing 已经形成完整生命周期链:

  • 用户加载对象元数据;
  • 系统建立待激活绑定;
  • observer 在运行中识别对象进入地址空间;
  • 待激活绑定被激活成实例;
  • 实例建立命中条件并在运行中触发;
  • 命中结果进入统一上下文和事件流;
  • 地址空间退出后,实例和相关状态被回收。

主程序场景已经覆盖这条完整路径。共享库动态装载场景仍保留在后续工作范围内。

6. 验证体系与工程化支撑

6.1 验证对象分成三层

这套 tracing 系统的验证对象可以分成三层:配置是否生效,命中是否发生,命中后系统状态是否仍然正确。

第一层验证配置是否生效,主要关注程序是否已经装入、目标是否已经登记,以及当前是否处于已启用或已激活状态。
第二层验证命中是否发生,主要关注运行中是否真的出现对应事件。
第三层验证命中后的系统状态,主要关注恢复是否完成、实例是否仍然归属正确、回收是否在正确时机发生。

只有这三层都成立,某条 tracing 链路才可以被视为稳定可用。

6.2 统一验证入口

系统通过统一 shell 入口提供这三层验证能力。状态类命令回答“系统当前处于什么状态”;事件类命令回答“运行过程中实际发生了什么”;统计类命令回答“一段运行之后系统累计得到了什么结果”。

不同 probe 的重点落在不同层面上。例如,userspace probe 更依赖运行期事件与实例状态,guest-kprobe 更依赖命中和恢复路径是否完成。但无论重点落在哪里,使用者都沿着同一条入口查看系统状态,而不必为不同 probe 使用不同工具。

6.3 固定联调流程

当系统已经包含 tracepoint、hprobe、guest-kprobe 和 guest-uprobe 之后,验证工作逐渐围绕固定联调流程展开。这条流程至少包括:

  • 装载程序;
  • 登记目标;
  • 检查当前是否处于待启用或待激活状态;
  • 驱动运行场景;
  • 观察运行期命中;
  • 查看收尾状态和统计结果。

这条流程把 tracing 的验证建立在系统真实状态上,而不是建立在零散日志或一次性操作经验上。

不同 probe 在这条流程中的判据也可以进一步细化。例如,对 guest-kprobe 来说,除了看到命中事件,还要确认恢复完成后 Guest 能继续越过探测点执行;对 guest-uprobe 来说,除了看到命中事件,还要确认实例始终归属于正确地址空间,并能在地址空间退出后被及时回收。

7. 当前边界与后续工作

当前系统已经能够覆盖一组连续场景:VMM 静态事件、VMM 自探针、Guest Kernel 探针以及主程序场景下的 Guest Userspace 探针。但系统边界同样明确。

性能测量尚未系统展开。当前阶段重点放在功能链路完整性和运行逻辑正确性上,后续仍需补齐不同探针模式的开销、恢复成本和对目标 VM 的影响评估。

程序安全约束仍以可信输入为前提。系统已经具备程序装载和执行能力,但完整 verifier 尚未纳入这一阶段工作范围。

Userspace 更复杂对象场景仍需继续推进。共享库动态装载带来的对象识别、实例匹配和返回路径管理问题,比主程序场景复杂得多,适合在现有主线稳定之后继续处理。

8. 结论

这三个月里,AxVisor 的 tracing 已经从若干局部实现发展成一套结构清楚的系统。系统先建立了程序装载、runtime、统一上下文、统一事件输出和命令入口,随后沿着 VMM 自身、Guest Kernel 和 Guest Userspace 三条路径继续扩展,把命中前后的运行时管理逻辑逐步补齐。

整个实现过程始终围绕完整运行链展开。系统处理的不只是目标能否命中,还包括程序是否已经装入、目标是否已经建立绑定、当前条件是否允许启用或激活、命中之后能否恢复执行,以及生命周期结束后状态是否能够回收。也正因为这些环节都逐步建立起来,最终形成的是一套后续仍可继续扩展的 tracing 基础设施。

9. 相关链接