LWN:GCC也支持BPF了!

关注了就能看到更多这么棒的文章哦~

BPF in GCC

By Jake Edge
September 15, 2020
LPC
原文来自:https://lwn.net/Articles/831402/
DeepL assisted translation

BPF virtual machine 在内核中的应用越来越广泛,但直到最近 GCC 才在支持这个 target 方面有了一些进展。BPF 此前一直是使用 LLVM 编译器工具来编译生成代码的。Jose E. Marchesi 在 2020 年 Linux Plumbers Conference(LPC)上做了两场演讲(都属于 GNU Tools track),向与会者介绍了大约一年前开始的 BPF for GCC project。它已经取得了一些重大进展,但当然还有更多的工作要做。

预计这个项目会有三个阶段。首先是将 BPF target 添加到 GNU toolchain 中。下一步是确保生成的程序能够被内核里的 BPF verifier 验证通过,从而允许加载到内核中。Marchesi 说,这部分需要持续不断的工作,因为 BPF 领域的发展速度非常快。最后一个阶段是为 BPF 开发者提供额外工具,除了编译器和汇编器(compiler and assembler)之外,还需要调试器和模拟器(debuggers and simulators)。

 [Jose E. Marchesi]

Binutils 中支持 BPF 的代码,在 2019 年 8 月就已经合入了,而 GCC 的 BPF backend 则是在 2019 年 9 月加入的。在 2020 年 8 月,也就是此演讲的几周前,GDB 中也添加了对 BPF 的支持,同时也提供了一个模拟器。这个模拟器是配合 DejaGnu test framework 的 board file 一起使用的,这样就可以执行 BPF backend 的 GCC test suite。

他说,目前 binutils 中支持 BPF 的工作已经完成,GCC backend 里的支持也同样完成了。GDB 则只支持最基本的功能:它只支持加载 BPF 程序、单步执行、设置断点、检查 BPF 寄存器和列出汇编代码。同样,模拟器也只有最基本的功能:它和 GDB 集成在一起,大部分的 BPF 指令都可以支持,所以可以在上面运行 BPF program。后续计划在模拟器中增加各种内核上下文(kernel context),以便可以运行不同的 BPF program type。例如,用来 attach 到 kprobe 的 BPF program,在模拟器中就会得到一个 kprobe context。模拟器还需要支持更多的内核 helper function,而不仅仅是 printk()。

除了支持标准 BPF 以外,项目还一直在添加对 "experimental BPF"(即 xBPF,尽管演讲者也说 "名字不重要")的支持。它增加了一些新功能,可以绕过内核里的 verifier 带来的限制,从而可以运行完整的 GCC test suite。有成千上万的 GCC 测试程序在 BPF 上都是无法成功编译使用的,因为它们需要的一些功能,在 BPF 语言中并不支持,比如间接调用(indirect call),以及需要自己保存并还原寄存器值的函数。除了 compiler tests 之外,还需要 xBPF extension,以便在 GDB 中使用 DWARF 格式调试 BPF,这很重要,比如就可以在 BPF program 中获取 backtrace 了。

Marchesi 说,这些功能都具备之后,项目开发的重点已经转向支持 BPF type format(BTF)了,这是 BPF 使用的调试格式(debugging format)。它类似于 Compact C type format(CTF),后者在 GNU 工具链中已经得到支持了。CTF 和 BTF 都衍生自同一个设计。在使用 GCC 的 -g 选项来要求添加调试信息时,针对 BPF program 就应该生成 BTF,而不是 DWARF。BTF 是 "compile once, run everywhere 一次编译,到处运行"(CO-RE)这个功能的一个必要条件,这样才能支持在多个内核版本上运行同一个 BPF 程序。

这里有个基本问题,在编译 BPF,它使用了一组 kernel headers,用来描述该特定 kernel 版本的各种内核数据结构,这些数据结构很可能与程序真正运行时的内核里的数据结构有差异。一直以来解决这个问题的方法就是将 BPF 作为 C 代码与 Clang 编译器一起发布,从而在真正要运行这个 BPF program 的系统上再进行编译。现在,编译器在生成 BPF 的同时,也生成了他所用到的数据结构和结构成员的 BTF 信息,这样 BPF 加载器在不同的内核版本上加载程序时,就可以修正这些引用道德结构。他说:"它的工作原理很神奇",但看起来确实很好用。

通过研读 LLVM BPF backend,Marchesi 了解到有一个类(class),专门把调试信息作为中间表示(IR, intermediate representation)的一部分来进行处理。要添加新的调试格式(debugging format)的话,只需要扩展该类来支持就好。目前 LLVM 支持 DWARF、CodeView 和 BTF。GCC 的情况则完全不同。它有一个叫 debug_hooks 的东西,但它是在编译器内许多不同的地方直接调用的,包括前端、后端、链接时优化(LTO)阶段等等。通过这些 debug_hooks 生成了好几种不同的格式,其中许多是传统的格式(如 VMS dbg、DBX),他的团队最近想把 CTF 的支持也加进去。

他说,最初的计划是简单地扩展目前的方案,这个方案对于 CTF 的方案有些过于古老了,但 GCC 维护者的反馈是,不应该让这些古老的格式支持代码来阻碍新的开发。有人建议说,增加新格式的正确做法应该是利用 DWARF 的支持,因此 CTF 和 BTF 的支持代码都将通过这个方式来添加。一旦正常运行起来,后续还可以把那些老格式也移植到新方案中,最终,旧的机制都可以被移除。在这个过程中要考虑的是,新的调试格式(如 BTF、CTF)都是很紧凑的(compact),而 DWARF 是大而全的(large and comprehensive)。当只生成一种紧凑格式时,使用 DWARF 的开销可能会是个问题。

两个演讲之间有相当多的重叠(第一个演讲:原始视频[https://youtu.be/EgUTa3vpCuc?t=888]和幻灯片[https://linuxplumbersconf.org/event/7/contributions/724/attachments/636/1166/bpf.pdf];第二个演讲:原始视频[https://youtu.be/VQ89O7Z5prc?t=209]和幻灯片[https://linuxplumbersconf.org/event/7/contributions/752/attachments/689/1288/toolchain-MC-bpf-discussion.pdf]),不过第二个演讲的主要目标是让 LPC 的 LLVM 和 BPF 开发者参与进来。这个目标显然并没有很好地达成,因为这两个项目的开发者们很多似乎都在忙着参加其他同一时刻的会议去了。但所提出的问题总是要解决的。然而,BPF 邮件列表上的这个帖子(https://lwn.net/ml/bpf/87mu282gay.fsf@oracle.com/%EF%BC%89%E8%A1%A8%E6%98%8E%EF%BC%8CxBPF 计划可能会遇到一些阻力。

Marchesi 在第二个演讲中主要讲了三点。第一,关于 BPF 辅助函数的声明,这些函数会自动生成到 bpf_helpers.h 中。现有的声明是这样的:

static __u32 (* bpf_get_prandom_u32) ( void ) = ( void *) 7;

如果在没有使用-O2 或更高优化的情况下,GCC 和 LLVM 针对这些声明都会出错。LLVM 会产生一条无效的指令,GCC 则会发出一个错误。GCC 的开发者们没有使用这种把 helper number 转化成 void * 的做法,而是提出了一个 kernel_helper attribute,可以让这些声明在任何优化级别下正常使用:

static __u32 (* bpf_get_prandom_u32)( void )
    __attribute__(( kernel_helper (7)))。

他想知道 LLVM 是否可以使用同样的解决方案,这比现有的代码更加健壮。在交流中,Mark Wielaard 说,大家沉默也许就意味着同意,但实际上,除非能够彻底解决这个问题,否则的话 GCC 还是需要尽力支持现有的声明方式,Marchesi 说。

BPF FAQ 里面有提到,它不支持 signed division(带符号除法)指令,因为很少会用。但他想知道,如果 signed division 的使用频率如此之低,为什么这会成为一个经常被问到的问题呢?无论如何,缺失 signed division,这对于 GCC 测试套件来说是个大问题,因为 GCC 测试套件有很多测试都使用了带符号除法。所以 xBPF 中加入了对四条指令(sdiv、smod 和它们的 32 位版本)的支持。他问 BPF 开发者,是否会重新考虑支持 signed division。在讨论过程中,Lorenz Bauer 说这个很难做,主要受限于内核对 BPF 的即时(JIT)编译的支持程度。

Marchesi 说,将这些指令添加到 xBPF 中会带来另一些问题。指令必须有一个专用的 opcode,而 opcode 是有限的。如果 BPF 在内核中可以扩展新功能,那它可能会用上 xBPF 中已经使用了的 opcode。例如,signed division 指令目前正在使用 ALU 和 ALU64 指令类中的最后两个可用的 opcode。他想知道是否可以预留某个范围的 opcode 来专用于这些扩展。许多种类的指令的 opcode 已经用完了,但 LD 中还有 23 个、ST 中还有 28 个是可用的,所以也许可以利用这一部分。Marchesi 说,这些问题将被提到邮件列表中,希望能通过这种方式来解决。

拥有又一个支持 BPF 的 toolchain 显然对大家是有好处的:GCC 和 LLVM 这些年都变得越来越好了,主要因为这两者的 "竞争"关系。从测试的角度来看,GCC 开发者所采取的方法似乎与 LLVM 所采取的方法并不相同,这些差异需要在不久之后解决。而 GCC BPF 模拟器和 GDB 的支持,将为 BPF 开发者带来新的工具可以尝试了。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值