关注了就能看到更多这么棒的文章哦~
An update on GCC BPF support
By Daroc Alden
April 2, 2025
LSFMM+BPF
Gemini-1.5-flash translation
https://lwn.net/Articles/1015747/
José Marchesi 和 David Faust 在 2025 年 Linux 存储、文件系统、内存管理和 BPF 峰会上开启了 BPF(Berkeley Packet Filter,伯克利包过滤器)专场,他们用一场超长的会议介绍了他们在 GCC 中为支持编译到 BPF 所做的工作。总的来说,该项目正朝着完全支持 BPF 的方向缓慢前进,使用 Faust 正在进行的补丁,现在大部分自测都已通过。然而,朝着这个目标前进的过程中,发现了一些 Clang 支持 BPF 的问题,需要进行长时间的讨论,以便为这两个项目找到前进的道路。
Marchesi 首先表示,今年实际上没有什么可讲的。悲观地说,进展正在放缓。乐观地说,对 BPF 的支持正在稳定,并且“要做的事情更少了,因为工作已经完成了”。根据他演讲的其余部分,在我看来,乐观的观点似乎更合适。
现在,支持 BPF 的 GCC 版本已经添加到 Fedora 中,并最终会以某种方式进入 CentOS、RHEL 以及其他从 Fedora 的软件包生态系统中提取内容的发行版中。包括 Debian、Ubuntu、Gentoo、Arch 及其所有衍生版本在内的大多数主要发行版现在都支持使用 GCC 编译到 BPF。Marchesi 说,Gentoo 实际上正在使用 GCC 来构建某些软件包的 BPF 组件。
将支持 BPF 的 GCC 纳入发行版非常重要,不仅因为它易于获取,还因为它意味着该项目已经开始收到错误报告。Marchesi 表示,他们非常感谢错误报告,收到这些报告将有助于巩固 GCC 对 BPF 的支持。包含在各种发行版中也表明,去年引入 BPF 专用维护分支 的计划是不必要的 — 这很好,因为它减少了每个人的工作量。
BPF 支持也被证明对 GCC 的其他部分很有用,特别是对 NVPTX(某些 Nvidia GPU 使用的架构)的支持。Marchesi 说,这也是一种有限的架构,需要特别关注。
Tagging misadventures (标签相关的遭遇)
GCC 现在也通过了更多的 BPF 自测。特别是,在峰会前提交给上游进行审查的一些工作修复了 GCC 如何为内核指针生成 BPF 类型格式(BTF)标签。BTF 将复合类型表示为不同标签的链;例如,指向整数的内核指针(这与 BPF 中的普通指针不同,以便验证器可以对其进行特殊处理)表示为如下内容:
// In C:int __kptr * name;// In BTF:pointer type tag -> kernel pointer annotation -> integer
将指针类型标记为内核指针的注解出现在指针类型本身之后,这一事实有点不寻常。其他注解,例如 const
,由出现在指针类型之前的标签表示。以前,GCC 在与所有其他注解相同的位置发出内核指针注解;将其更改为匹配 Clang 的行为大大增加了通过 GCC 的 BPF 自测的数量。
不过,Marchesi 对此并不满意,因为他认为内核指针注解应该与所有其他注解放在相同的位置。他建议应该更改 Clang 和 libbpf 来以这种方式做事。Yonghong Song 说,在 BTF 的最初设计期间已经讨论过这种可能性,并且他想知道自那时以来是否发生了任何变化来证明重新审视该决定的合理性。Faust 建议可以允许两种排序,尽管他不确定 pahole 将如何知道差异。Pahole 维护者 Arnaldo Carvalho de Melo 说,该程序可以只检查 BTF 的生产者,因此这不会构成问题 — 尽管目标仍然是最终从 BPF 编译管道中删除 pahole,因此 GCC 需要以符合内核期望的方式生成标签。
Faust 询问 Clang 如何表示非指针类型上的类型标签;Song 回复说它没有,如果需要这样做,则需要更改。Faust 说,在非指针上放置类型标签的能力很有用 — 这种说法稍后在讨论内联 BPF 函数的表示时再次出现。Alexei Starovoitov 要求 Faust 和 Marchesi 提交一个内核补丁,在验证器中添加对他们首选的标签排序的支持,Faust 同意这样做。
但是,对 GCC 如何处理 BTF 标签的任何更改几乎肯定都需要等到 4 月底 GCC 15 发布最终版本之后才能进行,因为许多 GCC 维护者都在忙于此事。Faust 还有另一个与标记相关的更改也在等待审查:对 DWARF 调试信息的扩展,该扩展在 2024 年 Linux Plumbers Conference 上进行了讨论,该扩展允许合并具有匹配尾部的调试信息列表。由于 GCC 从其内部 DWARF 表示生成 BTF 信息,因此这也应该导致 BPF 程序的更紧凑的 BTF 部分。
Marchesi 解释说,BTF 信息的大小对于内核来说并不是真正的问题,“但是我们需要让 [核心 GCC 开发人员] 满意”。Song 说,Clang 还不需要通过共享调试信息来节省空间,因为它具有更少的类型标签。Marchesi 说,他希望 Clang 最终也需要它,并且 Faust 认为该功能还有其他用途。
Google Summer of Code
GCC 每年都参与 Google Summer of Code;这一次,Marchesi 说他已经提议让某人编写基础架构,用于在特定的内核上运行 GCC 的 BPF 测试。目前,Ihor Solodrai 维护着 bpf-next 树的持续集成测试(他稍后在会议期间对此进行了介绍),该测试会测试每个提交的补丁,以确保验证器接受的内容没有退化。Marchesi 希望 GCC 的更改也能做到这一点,以便 GCC 贡献者可以快速获得关于新优化是否破坏验证器的反馈。
他建议学生可以编写一个测试工具,以在虚拟机中启动合适的内核并将 BPF 程序加载到其中,并将其集成到 GCC 现有的测试基础架构中。但是,一些与会者对此表示怀疑。Starovoitov 指出,为每个测试启动虚拟机将非常缓慢,并想知道是否可以在主机上运行 BPF 程序。Marchesi 说,没有现有的测试使用虚拟机,但是 GCC 确实有一些测试依赖于特定的外部硬件,这与测试工具的角度来看并没有什么不同。
一位与会者询问他们计划测试哪个内核版本。“一些稳定的东西”,Faust 回复道。Starovoitov 说,从事这些测试似乎是 GCC 的一个很好的方向,但是对于 Google Summer of Code 项目来说可能有点太多了。Marchesi 说,申请的截止日期是 4 月 8 日,因此他们很快就会知道是否有人认为他们能够胜任这项挑战。
Non-lexical annotations (非词法注解)
Faust 和 Marchesi 在尝试匹配 Clang 的行为时遇到的一个更为实质性的问题是如何处理 preserve_access_index
。此属性是使“一次编译,到处运行”(CO-RE)重定位成为可能的核心部分之一。它指示 BPF 验证器使用 BTF 调试信息中有关字段名称的信息,在运行时重写对结构中字段的访问,以应对结构布局的任何更改。这使针对来自一个内核版本的头文件编译的 BPF 程序可以在任何足够相似的内核上运行,即使结构中字段的确切顺序已更改。
今天,基本属性在 GCC 中可以正常工作。问题在于如何处理属性指定为嵌套结构的一部分的情况。考虑以下示例:
struct other { char c; int i;}struct outer_attr { struct other other; struct { long l; } nested;} __attribute__((preserve_access_index));
鉴于该属性仅应用于外部结构,编译器应如何处理对 nested.l
或 other.i
的访问?Clang 今天处理这种情况的方式 — 几乎可以肯定不符合标准 — 是将属性传播到 nested
,但不传播到 other
。如果程序是用 C++ 编写的,这将是有意义的,C++ 将嵌套结构定义为与非嵌套结构不同,但是 C 标准没有。实际上,Marchesi 指出,C 标准根本没有嵌套结构的概念;在另一个结构定义中编写一个结构定义的能力纯粹是一种句法上的便利。
另一方面,一些与会者认为传播该属性在直觉上是有意义的。拒绝为 nested
发出 CO-RE 重定位信息似乎没有任何好处。不过,Marchesi 说,实现 Clang 的行为对于 GCC 来说是困难的。GCC 的解析器在解析时取消嵌套结构定义,因此编译器甚至无法访问有关它们在代码生成时是否嵌套的信息。“如果我们想做 Clang 所做的事情 — 而我们不想 — 我们需要钩住解析器”。他希望 Clang 能够将此视为一个错误,并要求程序员手动在任何嵌套结构上编写 __attribute__((preserve_access_index))
。
Song 说,这种 Clang 行为既是故意的,也是 BPF 特定的;他们不想强迫用户向每个嵌套结构添加属性。另一位与会者建议不要管嵌套结构,但是为了保持一致性,使按引用包含的结构(如上面的 =other=)的行为相同。他们说,如果外部结构是 CO-RE 可重定位的,那么从逻辑上讲,内部结构也应该是 CO-RE 可重定位的,无论它是如何进入该结构的。
Faust 原则上同意,但指出这些属性应用于类型;如果一个结构在一个上下文中未嵌套使用,而在另一个上下文中嵌套在使用该属性的结构中,则表明它仅应在后一种情况下进行 CO-RE 重定位。但是没有办法在 BTF 中指定它,因为两种用法都是相同的类型。最终,该小组未能就该属性的所需语义达成共识 — 这意味着希望使用 GCC 编写 BPF 程序的用戶可能需要手动将属性应用于其嵌套结构,至少目前是这样。
Actual changes (实际的改变)
Marchesi 说,以前尚不清楚包含标准库头文件的程序在编译到 BPF 时是否应使用安装在构建主机上的版本。好吧,这个问题已经得到解答:C 标准(自 C99 以来)实际上要求构建环境提供这些头文件。因此,GCC 已被更改为匹配 Clang 的行为,并向 BPF 程序提供(经过少量修改的)主机上可用的标准库头文件。如果 BPF 程序最终依赖于 GNU C 库的某些部分,这可能会成为一个问题,因为从编译器的角度来看,BPF 实际上是一个“裸机目标”。想要旧行为的用户可以传递 -fno-hosted
命令行参数。
Faust 还在扩展对 BPF may_goto 指令 的支持。目前,该指令只能在内联汇编中使用;Song 说,Clang 的 C 前端永远不会生成它。但是,Faust 称让 C 代码生成它所需的工作“非常简单”,因此 GCC 的代码生成器可能很快就会使用它。
Marchesi 质疑 bpf_fastcall
指令是否仍然是可选的 — 也就是说,不实现它是否会导致任何问题,除了次优的性能。Starovoitov 说“理论上是的”,但是“随着时间的推移,它变得越来越不是可选的了”。这促使 Faust 询问已组装的 BPF 开发人员是否希望按某种顺序实现 may_goto
、BPF 原子内存排序的修复、 bpf_fastcall
、 preserve_static_offset
和 BPF 标签的修复。
Starovoitov 说,类型标签是最重要的,其次是在内联汇编中支持 may_goto
,其次是 bpf_fastcall
,最后是 preserve_static_offset。似乎没有人不同意该评估。
Marchesi 期待的另一个潜在改进是在编译器中包含其他“must”注解,例如 musttail。该注解是最近添加的,并且正在 越来越多地使用,如果编译器无法确保函数中的最后一个调用是尾调用,则会发出错误信号。这种类型的另一个可能的注解是 “must inline” 注解,它将比 always_inline
更强,因为如果函数太大,后者可能会静默地无法内联函数。
John Fastabend 询问这是否允许在 BPF 程序中编写递归。Starovoitov 的简洁回答是“也许”。Fastabend 详细说明说,阻止 Tetragon 在其 BPF 程序中使用普通函数调用的最后一件事是缺乏对递归的支持;从理论上讲,拥有 musttail 支持将使验证器现有的循环处理逻辑能够处理递归调用。Starovoitov 警告说,这并非那么简单,并且可能还需要对验证器进行其他更改。
Runtime BTF and debugging BTF (运行时 BTF 和调试 BTF)
在分配时间的最后,Marchesi 提出了 BTF 具有双重作用的事实。一方面,BTF 信息应该可以用作调试格式,因此理想情况下,它应该反映用户编写的源程序。另一方面,BTF 被验证器用来理解程序,并用于 CO-RE 重定位,为此,它应该反映程序的二进制形式中的内容。
例如,GCC 有几个优化过程可以更改函数签名。主要的一个是“过程间标量替换聚合”(ISRA),其中包括删除未使用的参数并将某些参数转换为按值传递。如果编译器在进行优化之前生成 BTF,则 BTF 将反映函数的原始签名,这些签名不一定包含验证器想要的信息。如果编译器在进行优化之后生成 BTF,则用户将无法使用它来理解他们的程序,因为它将不再对应于他们编写的内容。
目前,GCC 和 Clang 都在进行优化之前发出 BTF。这可能会导致验证器将无法找到与使用 ISRA 优化的函数相对应的 BPF 的问题。在场的所有人都同意这是一个问题,但并非所有人都对另一位 pahole 维护者提出的建议感到满意:生成两者。虽然它具有作为一个相对简单的修复程序的好处,但它并不是特别优雅。关于利弊的讨论持续了一段时间,然后最终为了符合时间表而结束。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~