摘要
- RFC 9669 为 BPF 指令和一致性组建立共同语言;它只强制
base32,并没有要求每个实现支持注册表里的全部指令。 - 指令可识别只是第一层证据。整段程序能否通过验证、完成加载、挂到正确 hook、真正被调用并产生外部结果,仍须逐层确认。
发布系统读取 IANA 页面,看到某条原子操作的状态是 Permanent,便把它加入默认编译目标。测试机运行成功,生产机却在加载阶段报告不支持。事故记录最初写成“标准不一致”。
生产机其实如实实现了 base32,从未声称支持 atomic64。Permanent 描述的是登记项的治理状态;编译器却把它改写成了本机能力。
错误不是少装了一条指令,而是把两个现实层合并成一个字段。
注册表回答“这组比特表示什么”
RFC 9669 规定两种指令宽度。基本指令占 64 位,wide instruction 再接一个 64 位字,总计 128 位。opcode、源和目标寄存器、offset、immediate 等字段按照指令类别获得语义。工具链和运行时因此可以对同一组比特作出一致解释。
标准同时明确,具体实现不必支持文档中的全部指令。base32 是最低要求,其他组可以选择实现。base64 包含 base32,atomic64 包含 atomic32,divmul64 包含 divmul32。一旦宣称支持某个组,就必须覆盖该组的全部指令。
这种设计比“支持 eBPF”精确得多。编译器可以针对已发现的组选择指令,运行时也能用有限的声明表达能力。IANA 的一致性组注册表保存名称、描述、包含与排除关系、状态、变更控制者和规范引用;指令注册表则保存字段组合、语义描述、所属组和引用。
Permanent 的作用,是让赋值不会被随意重用,并明确今后如何变更。它不测量部署覆盖率,不代表某个内核版本已经实现,也不代表管理员启用了功能,更不代表网卡 offload 路径与主机有相同能力。能力发现必须落到具体目标、版本、架构和时间。
标准还为演进保留边界。已有一致性组注册后,不再把新指令偷偷塞进去;扩展应建立新组,并通过 includes 关系继承旧组。弃用则可以由 Historical 组和排除关系表达。这样,旧声明不会在未来自动扩大含义。
合法词汇仍能组成非法程序
即使每条 opcode 都有定义,整段程序仍可能不可执行。RFC 9669 的跳转 offset 以 64 位指令单元计数。如果跳转落在 128 位 wide instruction 的第二个字上,行为就是 undefined。单个字段有名字,不等于控制流有意义。
内存、map 和函数调用更依赖上下文。规范用抽象操作描述 map 地址、变量地址与函数调用;实际可用对象及编号来自平台文档。某种 program type 允许的 context 字段和 helper,不必对另一种 program type 开放。同样的指令序列可以符合 ISA,却不符合目标环境的 API 合同。
RFC 9669 说明验证器通常负责确认程序能在合理时间内终止、只以安全方式访问内存、遵守平台 API 合同,并避免 undefined behavior。它又明确把验证器细节放在本文范围之外。这条边界十分重要:ISA 管共同含义,平台管特权代码能否进入本地执行环境。
Linux 文档展示了一个具体实现。验证器先检查控制流图,再沿可能路径模拟执行,跟踪寄存器和栈状态。一个寄存器在某条路径上是合法 context pointer,在错误运算后可能只剩 scalar。访问还要满足类型、边界和对齐。program type 回调决定哪些 context 字段可读、哪些函数原型可调用。
Linux 的设计问答没有承诺仅凭静态清单预测一切。它直言,要知道某段程序能否被验证器接受,实际办法是尝试加载。验证器会演进,内部限制也会变化。曾被旧版 Linux 接受的程序有兼容承诺,但这不能外推成所有 BPF runtime、硬件 offload 或本地策略的统一保证。
通过验证还没有到达 hook
验证器接受后,loader 才能创建程序对象。程序 ID 证明对象存在,却不证明目标 hook 已经指向它。link 或 attach 操作还要把特定程序代次绑定到特定执行点。绑定成功后,也可能因为没有事件经过该 hook 而保持零调用。
即使计数增长,程序内部证据也不能独自证明外部结果。map 显示“拒绝”可能只说明分支被执行;数据包是否真的未离开另一条路径、应用事务是否被阻止、设备状态是否改变,需要机制之外的观测。
Linux 当前的 BPF signing 文档又提供一条清楚分界。有效签名证明该机制覆盖范围内的来源与完整性,不替代权限或验证器。早期 hook 看到 BPF_SIG_VERIFIED,只表示签名有效,不表示程序已经 fully loaded。后续验证与对象绑定仍可拒绝。签名是 Linux 实现例子,不是 RFC 9669 的普遍要求,但它说明强证据也必须只回答自己的问题。
用代次把十张回执串成一条链
可靠上线至少要记录:源代码与构建输入、最终对象或指令 hash、编译器选择的功能集、目标 runtime 与架构、发现的一致性组、program type、relocation 与平台对象解析、验证器结论与日志 hash、已加载程序 ID、link 与 hook 身份、map 代次、挂接与卸载时间、调用计数、程序内决策,以及独立的网络或应用结果。
负面回执同样重要。不支持的组、relocation 失败、验证器拒绝、加载失败、挂接失败、零调用和结果不一致指向不同责任人。如果监控统一显示“BPF 失败”,修复就会在错误层反复发生。
代次是防止假成功的关键。新对象已经加载,而旧 link 仍在;新程序复用旧 map,计数无法归属;回滚删除了最新 ID,却没有枚举仍活跃的旧 hook。只按策略名称 join 的仪表盘会把这些混合状态涂成一个绿色格子。
RFC 9669 提供的是最小共同规范:对指令、组和扩展程序达成一致。它没有夺走运行者对可选能力、验证政策、接口暴露和信任决定的本地权力。恰恰因为标准边界清楚,运营证据才可以继续向现实层延伸。
来源
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance

