最后一公里:十年磨一剑,BPF 编译器的双引擎时代
本文地址:https://www.ebpf.top/post/gcc16-bpf
十年来,写 BPF 程序几乎等于默认用 Clang。直到 2026 年 5 月,一份来自峰会的对照表,让这句话开始松动。
本文根据 LWN.net 记者 Daroc Alden 于 2026 年 5 月 21 日发表的报道《BPF support in GCC 16 and beyond》改写,原文基于 LSFMM+BPF Summit 2026 上 José Marchesi 及 GCC-BPF 团队的 90 分钟演讲(幻灯片)。
1. 连续第三年,这次不一样
2026 年 5 月,LSFMM+BPF 峰会,José Marchesi 走上台。
这已经是 GCC-BPF 团队连续第三年在这里做年度更新了——2024 年一次,2025 年一次,今年又来。以往的惯例是:发言一小时,回答几个问题,然后结束。
但 Marchesi 今年开场就说,他想做得不一样。他承诺整个峰会期间都会保持联系,随时可以找他对表、讨论、推进修复。 这不是一句客套话——这是把 GCC-BPF 当成真正可交付产品来对待的信号。
多年来,GCC 的 BPF 后端在社区里更像一个实验性存在。默认共识就是:eBPF 开发?装 Clang,装 libbpf,走你。没有人认真考虑过 GCC 能成为替代品。
今年的峰会,这个共识开始动摇。
2. 601 / 713:一个说明问题的数字
Marchesi 展示了一张功能对照表,左边 LLVM/Clang,右边 GCC 16。
扫一遍下来:基础特性部分两者已基本对齐;但 production 级别的高级特性——内存序感知原子操作(memory ordering aware atomic built-ins)、attribute push/pop pragma、small record arg passing、fast calls——GCC 还有几项空白。 差距在缩小,但仍然清晰可见。
测试数据更直观。GCC 现在能通过内核 BPF 自测套件中的 601 个测试,共 713 个,涵盖 5488 个子测试。(原文初版误写为"601 of 5488",后经读者 Faust 指正,正确理解是 601/713 个测试,共含 5488 个子测试。)
84% 的通过率,这不是"HelloWorld 能跑"的那种能力,是实打实对着内核测试集逐项比对的结果。
Marchesi 对剩余失败项的判断是:“影响大量测试的往往是相对集中的一组问题”,意味着并不需要全面重写,而是找到并修掉少数几个关键 bug,就能把通过率大幅拉升。
GCC 16.1 已于 2026 年 4 月 30 日发布,首次包含 Vineet Gupta 的主力贡献。Marchesi 开玩笑说 Gupta 让团队里其他人"看起来不够努力"。GNU 工具链侧也在全线跟进:binutils、DejaGNU、GNU poke、GDB 都在适配 BPF。甚至专门建立了邮件列表 bpf@gcc.gnu.org,每周一在 Software Freedom Conservancy 的平台上召开例会。
实际可用性方面,Gentoo 已将 GCC 设为可选的 BPF 编译器,真实 bug 开始从用户侧回流,这对工程质量的帮助比实验室测试更直接。
3. 三块硬骨头:CO-RE、BTF 去重、验证器
距离 GCC 真正能在生产环境中替代 Clang,还有三个核心问题横在前面。它们不是随机分布的 bug,而是架构层面需要啃的硬骨头。
3.1 CO-RE:属性传播的细节魔鬼
CO-RE(Compile Once - Run Everywhere)是 eBPF 的灵魂特性。一个 BPF 程序能在不同内核版本上运行,靠的就是 CO-RE 在加载时自动修正结构体字段偏移——前提是编译器正确标记了所有需要重定位的访问。
问题出在嵌套结构上。Clang 会把 preserve-index-access 属性自动传播到嵌套的 struct 内部;GCC 目前不会。这导致同一份代码,两个编译器编译出来的 CO-RE 行为不一致。
GCC 团队的 Cupertino Miranda 给出的解法是:实现与 Clang 相同的 pragma 压栈/弹出机制,让开发者可以指示 GCC 把每个遇到的 struct 都视为具有 preserve-index-access 属性。Marchesi 对此的原话颇为直白:“We’ve had enough. So, we’re going to implement those, if only for structs."(我们受够了,就算只为 struct,也要把这个做掉。)
CO-RE 的麻烦不止于此,还有两个衍生问题:
bitfield:内核网络代码有时会在 bitfield 和整型定义之间来回切换。Miranda 问,如果实际代码已经用宏绕过了这个问题,是否真的需要去实现完整支持?Andrii Nakryiko 的回答是:GCC 应该尽量生成正确代码,但当 CO-RE 可重定位结构里出现 bitfield 时,应该主动发出 warning,提醒开发者这里可能有问题。
packed 结构体:网络子系统里有存量的 packed struct,需要能正确处理。Nakryiko 确认这是必须支持的路径,不是可选项。未来虽然会减少 packed 的使用,但现有代码不能不管。
3.2 BTF 去重:三套实现是一场灾难
BTF(BPF Type Format)是 BPF 程序和内核之间的"类型合同”,加载器靠它找到函数签名、字段偏移。问题是,内核在 GCC 优化构建时可能会改变函数原型——比如删掉恒定参数、把传值结构体拆开只传用到的字段——导致 BTF 里的签名和实际运行时的函数对不上。
更棘手的是去重问题。David Faust 报告说,BTF 类型和声明标签方面,GCC 16 已经获得了与 Clang 相同的标签支持集合,但实现上略有差异(为了通过其他 GCC 维护者的审查,使用了不同的标识符)。poke-a-hole 工具已经适配了新标识符,“不应该是大问题”。
但 Alexei Starovoitov 在 BTF 去重上提出了更根本的担忧:当年 Clang 添加 BTF 直接输出支持时,Clang 开发者把 libbpf 的去重逻辑复制了一份进去。他担心 GCC 如果再照抄一遍,就会变成"三个略微不同、分别维护的相同逻辑版本"。
他的判断很直接:"现实中,只有 libbpf 里的去重器才真正有效。"
Nakryiko 补充了另一层复杂性:尝试同时去重 weak 和 non-weak BTF map 定义,本身就引入了额外的状态管理负担。
出路只有一条:GCC 对齐 libbpf 去重器,而不是另起炉灶。否则三套分叉实现会成为长期维护的噩梦。
3.3 验证器:它其实更"认识"LLVM
这是三个问题里最微妙的一个。
Vineet Gupta 在演讲中直接点明:GCC 和 LLVM 生成的字节码都是合法的 BPF,但验证器更容易处理 LLVM 生成的版本。 原文的表述是 “the verifier has an easier time working with LLVM’s version”。
这不是说 GCC 生成了不合规的代码。而是说,BPF 验证器在内部的判断逻辑,是在长期以 LLVM 输出为事实标准的环境下演化出来的——它对 LLVM 风格的代码模式更熟悉,分析路径更短,通过率自然更高。GCC 生成的同样合法的代码,验证器反而要走更多分析步骤,甚至可能因保守策略而拒绝。
修法是双轨并行:
- GCC 这侧:为 BPF 后端增加代价模型(cost model),引导优化器生成更接近 LLVM 风格的字节码
- 验证器那侧:扩展验证器能力,让它能理解更多 GCC 生成的代码模式
两侧同时推进,才能真正解决兼容性问题,而不只是让 GCC 去模仿 LLVM。
另外,对照表里还有一个被内核开发者现场点名的缺项:间接调用和间接跳转的支持状态没有入表。这是 Marchesi 团队需要在下一版对照表里补上的项目。
4. 意外的扩展:Solana 和位操作 kfunc
演讲里有两个有意思的旁支话题,反映了 BPF 生态正在向外延伸。
Solana BPF 变体。GCC 团队目前有 work-in-progress 的支持,针对 Solana 智能合约使用的 BPF 修改版本。Marchesi 坦言自己对区块链了解不多,也不完全清楚为什么 Solana 要用 BPF 的修改版本。但他看到了借鉴的机会——比如 Solana BPF 支持 64 位乘积、商和余数指令,这些设计值得考虑并入"正统"BPF 规范。BPF 的应用场景已经不限于内核观测,工具链的竞争也随之扩展到更多领域。
位操作内建 → kfunc。David Faust 提出了一个实用问题:__builtin_clz() 等编译器位操作内建函数,展开后会生成约 30 条 BPF 指令,对验证器和运行时性能都不友好。能不能用 kfunc 来封装这些操作?Nakryiko 表示同意,这正是"快速 kfunc 调用"设计的动机之一。但他提了一个要求:所有位操作函数必须一次性成套提交,命名要统一,不能零散地一个一个加。Faust 欣然接受。
5. 对你意味着什么
不同角色的人,现在应该做的事不一样:
如果你写简单 BPF 程序、不依赖 CO-RE 跨内核版本:现在就可以试试 gcc -target bpf 编译,对比 Clang 的输出,感受两者差异。GCC 对 systemd 等场景的简单 BPF 程序已经可以正常编译。
如果你深度依赖 CO-RE 跨内核版本:继续以 Clang 为主。但值得跟踪 GCC pragma 传播机制的进展,bitfield warning 策略的落地,以及 BTF 去重的统一方向。这些问题的解法正在收敛。
如果你是发行版或包维护者:可以考虑像 Gentoo 那样把 GCC-BPF 设为可选项,收集真实用户遇到的 bug。实验室测试和真实负载的差距,往往只有在真实用户反馈里才能暴露。
如果你是内核或 BPF 维护者:间接调用/跳转的支持状态需要进入对照表;BTF 去重统一到 libbpf 的路径需要讨论和推动;验证器扩展的方向也值得参与。
6. 追赶正在发生,且可度量
原文对整体进展的评价是克制的:“GCC support for BPF seems to be coming along nicely. It is already usable for simple real-world programs, and will only become more so if more projects start using it."(GCC 的 BPF 支持进展顺利,已可用于简单的真实程序,随着更多项目采用并提交 bug 报告,会持续改善。)
这不是革命宣言。但方向是清晰的。
601/713 测试通过,不是终点,而是刻度。 每一个刻度都说明追赶在量化地进行,而不只是方向上的愿景。
GCC-BPF 真正在做的事,是在挑战一种惯性——不靠宣告,而靠一条条功能对齐、一个个测试通过、一次次峰会上的当面对表。当 713/713 全绿、CO-RE 行为完全一致、BTF 去重统一管理的那一天,BPF 工具链的格局将真正改变。
那一天,编者判断,并不遥远。
参考资料
主要来源
- Daroc Alden. BPF support in GCC 16 and beyond. LWN.net, 2026-05-21. https://lwn.net/Articles/1071973/
会议背景
- Linux Storage, Filesystem, Memory-management, and BPF Summit (LSFMM+BPF) 2026
- 演讲者:José Marchesi(GCC-BPF 项目负责人)、Vineet Gupta、Cupertino Miranda、David Faust
- 演讲幻灯片:https://drive.google.com/file/d/1MLTPaBBCTAVwN31fC8FhfGDq2Uq18uOT/view
- 往届更新:2025 年 · 2024 年
延伸跟踪
- GCC BPF 专用邮件列表:https://gcc.gnu.org/mailman/listinfo/bpf
- GCC 16.1 发布说明:https://gcc.gnu.org/gcc-16/
- BPF CO-RE 参考:Andrii Nakryiko, BPF CO-RE reference guide. https://nakryiko.com/posts/bpf-core-reference-guide/
- libbpf 文档:https://libbpf.readthedocs.io/
- poke-a-hole 工具:https://lwn.net/Articles/335942/
本文为要点提炼,非会议实录。完整原文见上方参考资料链接。
- 原文作者:DavidDi
- 原文链接:https://www.ebpf.top/post/gcc16-bpf/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 最后更新时间:2026-07-04 13:01:35.507112027 +0800 CST