0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

BTF 实践指南[译]

Linux阅码场 来源:Linux阅码场 2023-03-02 10:27 次阅读

BPF 是 Linux 内核中基于寄存器的虚拟机,可安全、高效和事件驱动的方式执行加载至内核的字节码。与内核模块不同,BPF 程序经过验证以确保它们终止并且不包含任何可能锁定内核的循环。BPF 程序允许调用的内核函数也受到限制,以确保最大的安全性以防止非法的访问。

尽管 BPF 为编写事件驱动的内核空间代码提供了一种有效的解决方案,但开发人员的体验仍无法与其他编程语言或框架相提并论。BPF 开发的两个最重要的问题是缺乏简单的调试和可移植性。

为了缓解这些问题,我们转向 BTF[1]。BTF 是针对 BPF 程序的类型信息进行编码文件格式,通过 BPF 程序的类型信息进行编码,为程序提供更好的内省(introspection)和可见性。本文我们将介绍 BPF 的典型局限性以及如何使用 BTF 来克服。

请注意,本文使用术语 BPF 来表示 eBPF[2](扩展的 Berkeley 数据包过滤器),eBPF 扩展 “ 经典 ” cBPF。

1. BPF 的常见限制

在 BPF 程序的开发和运行过程中,我们会经常会面临调试限制和可移植性问题,如前所述。

1.1 调试限制

几乎所有现代编程语言都有对应的调试器,通过调试器可以帮助我们更好了解正在运行的程序。例如,GDB[3] 是 C 和 C++ 的常用调试器,除其他外,基于 GDB 我们可以打印正在运行的程序中的变量值。

722d9c0e-b813-11ed-bfe3-dac502259ad0.png图 GDB 变量打印

但是很不幸,BPF 程序并没有类似的这样的工具。尽管检查数据只是调试的一小部分,但为 BPF 实现类似的结果可以为未来的广泛调试工具打开一扇门。为了实现这一点,BPF 需要知道关于程序的相关的部分元数据。

这类关于类型信息的元数据,正是 BTF 封装的内容。

1.2 可移植性

BPF 程序在内核空间中运行,可以访问内部内核状态和数据结构。但是,并没有办法保证内核数据结构和类型在不同内核版本是相同的,甚至相同内核版本的不同机器之间也可能不同(这可能取决于内核编译选项)。这意味着在一台机器上编译的 BPF 程序并不能保证在另一台机器上正确运行。

假设 BPF 程序正在从内核结构中读取一个字段,该字段位于距结构开头的偏移量 8 处。现在在更高版本的内核中,在该变量之前添加了其他字段,导致访问的字段的偏移量变成了 24,这会导致 BPF 程序在偏移量 8 读取的数据可能为垃圾数据。类似情况,也可能会发生某些字段最终得到在后续内核版本中的重命名。例如,在内核版本 4.6 和 4.7 之间,thread_struct 的 fs 字段可能会重命名为 fsbase 。最后,还可能是因为配置禁用了某些功能并编译了部分结构,导致可能 BPF 程序在不同的内核配置上运行。

所有上述这些场景的存在,意味着你不能在当前机器上编译 BPF 程序并将二进制文件分发到其他系统。

一个标准的解决方案是使用 BPF Compiler Collection (BCC)[4]。使用 BCC,你通常将 BPF 程序作为纯字符串嵌入到用户空间程序(例如,Python 程序)中。在目标机器上执行期间,BCC 使用其嵌入式 Clang/LLVM 组合并使用本地安装的内核头文件动态编译程序。

然而,这种方法引入了更多问题。首先,Clang/LLVM 组合非常庞大,将其嵌入到应用程序中会导致二进制文件大小过大。它还占用大量资源,并且会在编译期间耗尽大量资源。最后,这种方法需要在目标机器上安装内核头文件,但情况可能并非总是如此。

解决方案是 BPF CO-RE(一次编译 —— 随处运行)。使用 BTF,我们可以消除在目标机器上安装内核头文件或将 Clang/LLVM 嵌入应用程序并在目标机器上编译的需要。

2. BTF 是什么?

如前所述,BTF 是编码 BPF 程序和 map 结构等相关的调试信息的元数据格式。BTF 可以将元数据数据类型、函数信息和行信息编码成一种紧凑的格式。

在非 BPF 程序中,这些元数据通常使用 DWARF[5] 格式存储。但是,DWARF 格式的实现还是相当复杂和冗长,并且由于其在大小方面的开销,使其不适合包含在内核中。而 BTF 是一种紧凑而简单的格式,让其可以包含在内核镜像中。

BTF 使用少数类型描述[6] 符之一表示每种数据类型:

  • BTF_KIND_INT
  • BTF_KIND_PTR
  • BTF_KIND_ARRAY
  • BTF_KIND_STRUCT
  • 等等

类型信息存储在生成的 ELF 的 .BTF 部分中。除了类型描述符之外,此部分还对字符串进行编码。函数和行信息存储在 .BTF.ext 部分中。

关于 BTF 的详细说明,可以查看 Linux Kernel 文档[7]

3. BTF 快速入门

3.1 BPF 快速入门

现在让我们通过使用 BTF 漂亮地打印[8] BPF map 的教程进行更多实践,从而显著改进调试。

要开始,我们需要在启用 CONFIG_DEBUG_INFO_BTF 选项的情况下编译 Linux 内核。大多数发行版都启用了此选项,但你可以通过运行以下命令进行检查:

$zgrepCONFIG_DEBUG_INFO_BTF=y/proc/config.gz
#可选:grep CONFIG_DEBUG_INFO_BTF=y /boot/config*

当然,我们还需要在计算机上安装 Clang 和 LLVM[9]

由于我们需要将编写 XDP[10] 程序来处理网络设备上的数据包,因此创建一个虚拟网络接口[11] 是个好主意,这样就不会最终失去物理接口中的互联网连接。设置虚拟接口的最简单方法是使用此 repo[12]

克隆 repo 并设置一个名为 test1 的虚拟接口:

$gitclonegit@github.com:xdp-project/xdp-tutorial.git
$cdxdp-tutorial/testenv
$sudo./testenv.shsetup--name=test1--legacy-ip

现在编写一个 BPF 程序来计算接口上接收到的 IPv4 和 IPv6 数据包的数量。文件 xdp_count.c 的文件内容如下:

#include
#include
#include
#include

structbpf_map_defSEC("maps")cnt={
.type=BPF_MAP_TYPE_ARRAY,
.key_size=sizeof(__u32),
.value_size=sizeof(long),
.max_entries=2,
};


SEC("xdp_count")
intxdp_count_prog(structxdp_md*ctx)
{
void*data_end=(void*)(long)ctx->data_end;
void*data=(void*)(long)ctx->data;
__u32ipv6_key=0;
__u32ipv4_key=1;
long*value;
__u16h_proto;
structethhdr*eth=data;
if(data+sizeof(structethhdr)>data_end)//Thischeckisnecessarytopassverification
returnXDP_DROP;

h_proto=eth->h_proto;
if(h_proto==htons(ETH_P_IPV6)){//CheckifIPv6packet
value=bpf_map_lookup_elem(&cnt,&ipv6_key);
if(value)
*value+=1;
returnXDP_PASS;
}
value=bpf_map_lookup_elem(&cnt,&ipv4_key);
if(value)
*value+=1;
returnXDP_PASS;

}

char_license[]SEC("license")="GPL";

在前面的代码中,名为 cnt 的 BPF map 存储数据包的数量。cnt 是两个元素的数组。IPv6 数据包的数量存储在 key 0 中,IPv4 数据包的数量存储在 key 1 中。

使用 Clang 编译代码:

$clang-O2-Wall-g-targetbpf-cxdp_count.c-oxdp_count.o

接下来,使用 bpftool 加载程序:

$sudobpftoolprogloadxdp_count.o/sys/fs/bpf/xdp_counttypexdp

运行以下命令并记下我们刚加载的程序的 ID 和程序正在使用的 map 的 ID:

$sudobpftoolproglist
7249ce06-b813-11ed-bfe3-dac502259ad0.png图 bpftool prog 列表的输出

我们还可以通过运行 sudo bpftool map list 来获取 map ID。

72555a8c-b813-11ed-bfe3-dac502259ad0.png

图 bpftool map list 的内容输出

此命令为我们提供 map 的名称、类型、键大小、值大小和最大条目数。

现在,将 BPF 程序附加到网络设备。

$sudobpftoolnetattachxdpgenericiddevtest1

将 program_id 替换为程序的 ID,并将 device_name 替换为程序附加到的网络设备的名称(例如 enp34s0)。

现在,向该设备发送一些数据包。测试环境脚本已经提供了一个方便的 ping 命令来执行此操作:

$sudo./testenv.shping#ForIPv6
$sudo./testenv.shping--legacy-ip#ForIPv4

打印 map 并检查处理的数据包情况:

$sudobpftoolmapdumpid

726968d8-b813-11ed-bfe3-dac502259ad0.png

图 map 的打印值

如图所示,map 中有两个预期的元素。这些值采用十六进制格式,并且还取决于运行机器的字节顺序。在截图中,它是小端格式,这意味着已经处理了 22 个 IPv6 和 4 个 IPv4 数据包。

很明显,结果是十六进制的,小端格式,一看就不好调试。因此,我们需要使用 BTF 对 map 进行注释,以便更好地展示。

如下更改 cnt 的声明并将新代码保存在 xdp_count_btf.c 中 -

...
struct{
__uint(type,BPF_MAP_TYPE_ARRAY);
__type(key,__u32);
__type(value,long);
__uint(max_entries,2);
}cntSEC(".maps");
...

请注意,部分名称现在为 .maps,并且地图本身已使用启用 BTF 的宏 __uint__type 进行了注释。

使用 Clang 编译代码:

clang-O2-Wall-g-targetbpf-cxdp_count_btf.c-oxdp_count_btf.o

使用 -g 标志将创建调试信息并生成 BTF。请注意,之前也使用了 -g 标志,因为 libbpf需要它[13] 来加载程序;然而,以前 map 没有被 BTF 注释,所以 bpftool 不能够优雅地进行打印。

验证 BTF 部分是否存在于生成的目标文件中。

$llvm-objdump-hxdp_count_btf.o

728674be-b813-11ed-bfe3-dac502259ad0.png

图 llvm-objdump 的输出

如前所述,.BTF 部分包含类型和字符串数据,.BTF.ext 部分对 func_info 和 line_info 数据进行编码。

首先,卸载前面的 BPF 程序。

$sudobpftoolnetdetachxdpgenericdevtest1

然后按照类似的过程加载新程序并将其附加到接口,然后对接口发送一些数据:

$sudobpftoolprogloadxdp_count_btf.o/sys/fs/bpf/xdp_count_btftypexdp
$sudobpftoolproglist
$sudobpftoolnetattachxdpgenericiddevtest1
$sudo./testenv.shping
$sudo./testenv.shping--legacy-ip

最后,打印新程序对应的 map 。如果一切顺利,这一次的输出会有很大的不同。

7292e6cc-b813-11ed-bfe3-dac502259ad0.png

图 map 的结构的打印值

它不仅以 JSON 格式打印得很漂亮,而且值也是十进制的,使其更具可读性和易懂性。

3.1 BTF 和 CO-RE

如前所述,BTF 可以启用 CO-RE 使 BPF 程序可移植到不同的内核版本或用户配置。我们也可以通过生成内核本身的 BTF 信息来消除对本地内核头文件的需求:

$bpftoolbtfdumpfile/sys/kernel/btf/vmlinuxformatc>vmlinux.h

上述命令将创建一个巨大的 vmlinux.h 文件,其中包含所有内核类型,包括作为 UAPI 的一部分公开的类型、内部类型和通过 kernel-devel 可用的类型,以及一些其他地方不可用的更多内部类型。在 BPF 程序中,我们可以只 #include "vmlinux.h" 并删除其他内核头文件,如 等。

摆脱内核头文件依赖性只是 BTF 可以实现的目标的冰山一角。如需 BTF 和 CO-RE 的详尽解释,可以阅读这篇文章[14]

4. 结论

BTF 是一个非常强大的工具,可以使 BPF 程序更易于调试和移植。由于它是一项相对较新的技术,因此开发仍在进行中,你可以期待在未来看到大量改进。

本文让你大致了解 BTF 可以实现什么。你可能已经了解了 BPF 的缺点、BPF 是什么以及如何使用 BTF 注解 map 和打印 map 结构。最后,你还了解了 BTF 如何充当通过 CO-RE 增强可移植性的起点。

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    31

    文章

    5357

    浏览量

    120734
  • 编程语言
    +关注

    关注

    10

    文章

    1947

    浏览量

    34835
  • 虚拟机
    +关注

    关注

    1

    文章

    919

    浏览量

    28296

原文标题:BTF 实践指南[译]

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    请问I2C的BTF位什么时候置1?

    参考手册上的原话是“如果在上一次数据传输结束之前TxE位已置1,但数据字节尚未写入DR寄存器,则BTF位会置1,而接口会一直延长SCL低电平,等待I2C_DR寄存器被写入,以将BTF清零。” 我结合
    发表于 05-06 06:35

    高速PCB布线实践指南

    高速PCB布线实践指南_(上)
    发表于 08-20 16:26

    数字信号处理实践方法——EC.Ifeachor著,罗鹏飞等

    本帖最后由 怒放_的_生命 于 2013-10-30 22:31 编辑 数字信号处理实践方法——EC.Ifeachor著,罗鹏飞等。。网上最高清的扫描版本。。《数字信号处理实践方法(第二
    发表于 10-28 12:36

    机器学习实践指南——案例应用解析

    机器学习实践指南——案例应用解析
    发表于 04-13 16:40

    高速PCB布线实践指南

    高速PCB布线实践指南
    发表于 06-12 21:52

    开关电源设计指南_(英)布朗(Brown.M.)著/徐德鸿等

    电子发烧友网站提供《开关电源设计指南_(英)布朗(Brown.M.)著/徐德鸿等.txt》资料免费下载
    发表于 03-02 15:02 0次下载

    高速PCB布线实践指南

    高速PCB布线实践指南,只是理论知识,不是实际软件操作
    发表于 12-11 16:59 0次下载

    Lua游戏开发实践指南 - 详章

    Lua游戏开发实践指南 - 详章.pdf
    发表于 01-14 16:13 29次下载

    最重要的电磁兼容设计指南_龙凤呈祥_

    最重要的电磁兼容设计指南【龙凤呈祥
    发表于 01-14 16:22 0次下载

    高速PCB布线实践指南

    高速PCB布线实践指南,好东西,喜欢的朋友可以下载来学习。
    发表于 01-18 15:41 0次下载

    高速PCB布线实践指南_(上册)

    高速PCB布线实践指南_(上册),好东西,喜欢的朋友可以下载来学习。
    发表于 01-18 15:41 0次下载

    Linux系统命令及shell脚本实践指南

    Linux系统命令及shell脚本实践指南资料下载。
    发表于 06-01 14:47 28次下载

    《STM32 Cortex-M3权威指南》宋岩

    《STM32 Cortex-M3权威指南》宋岩
    发表于 12-30 10:24 58次下载

    SAN管理最佳实践指南

    电子发烧友网站提供《SAN管理最佳实践指南.pdf》资料免费下载
    发表于 08-29 09:20 0次下载
    SAN管理最佳<b class='flag-5'>实践</b><b class='flag-5'>指南</b>

    SAN设计和最佳实践指南

    电子发烧友网站提供《SAN设计和最佳实践指南.pdf》资料免费下载
    发表于 09-01 11:02 0次下载
    SAN设计和最佳<b class='flag-5'>实践</b><b class='flag-5'>指南</b>