BTF:实践指南

news2024/11/25 1:56:59

本文地址:BTF:实践指南 | 深入浅出 eBPF

  • 1. BPF 的常见限制
    • 1.1 调试限制
    • 1.2 可移植性
  • 2. BTF 是什么?
  • 3. BTF 快速入门
    • 3.1 BPF 快速入门
    • 3.1 BTF 和 CO-RE
  • 4. 结论

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

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

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

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

1. BPF 的常见限制

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

1.1 调试限制

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

GDB 漂亮打印变量的屏幕截图

​ 图 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)。使用 BCC,你通常将 BPF 程序作为纯字符串嵌入到用户空间程序(例如,Python 程序)中。在目标机器上执行期间,BCC 使用其嵌入式 Clang/LLVM 组合并使用本地安装的内核头文件动态编译程序。

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

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

2. BTF 是什么?

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

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

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

  • BTF_KIND_INT
  • BTF_KIND_PTR
  • BTF_KIND_ARRAY
  • BTF_KIND_STRUCT
  • 等等

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

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

3. BTF 快速入门

3.1 BPF 快速入门

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

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

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

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

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

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

$ git clone git@github.com:xdp-project/xdp-tutorial.git
$ cd xdp-tutorial/testenv
$ sudo ./testenv.sh setup --name=test1 --legacy-ip

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

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <arpa/inet.h>

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


SEC("xdp_count")
int xdp_count_prog(struct xdp_md *ctx)
{
        void *data_end = (void *)(long)ctx->data_end;
        void *data = (void *)(long)ctx->data;
        __u32 ipv6_key = 0;
        __u32 ipv4_key = 1;
        long *value;
        __u16 h_proto;
        struct ethhdr *eth = data;
        if (data + sizeof(struct ethhdr) > data_end) // This check is necessary to pass verification
                return XDP_DROP;
        
        h_proto = eth->h_proto;
        if (h_proto == htons(ETH_P_IPV6)) { // Check if IPv6 packet
                value = bpf_map_lookup_elem(&cnt, &ipv6_key);
                if (value)
                        *value += 1;
                return XDP_PASS;
        }
        value = bpf_map_lookup_elem(&cnt, &ipv4_key);
        if (value)
            *value += 1;
        return XDP_PASS;

}

char _license[] SEC("license") = "GPL";

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

使用 Clang 编译代码:

$ clang -O2 -Wall -g -target bpf -c xdp_count.c -o xdp_count.o

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

$ sudo bpftool prog load xdp_count.o /sys/fs/bpf/xdp_count type xdp

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

$ sudo bpftool prog list

Output of bpftool prog list

​ 图 bpftool prog 列表的输出

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

Output of the bpftool map list command

​ 图 bpftool map list 的内容输出

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

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

$ sudo bpftool net attach xdpgeneric id <program_id> dev test1

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

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

$ sudo ./testenv.sh ping # For IPv6
$ sudo ./testenv.sh ping --legacy-ip # For IPv4

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

$ sudo bpftool map dump id <map_id>

The dumped value of the map

​ 图 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);
} cnt SEC(".maps");
...

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

使用 Clang 编译代码:

clang -O2 -Wall -g -target bpf -c xdp_count_btf.c -o xdp_count_btf.o

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

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

$ llvm-objdump -h xdp_count_btf.o

Output of llvm-objdump

​ 图 llvm-objdump 的输出

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

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

1
$ sudo bpftool net detach xdpgeneric dev test1

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

$ sudo bpftool prog load xdp_count_btf.o /sys/fs/bpf/xdp_count_btf type xdp
$ sudo bpftool prog list
$ sudo bpftool net attach xdpgeneric id <program_id> dev test1
$ sudo ./testenv.sh ping
$ sudo ./testenv.sh ping --legacy-ip

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

映射的转储值

​ 图 map 的结构的打印值

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

3.1 BTF 和 CO-RE

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

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

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

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

4. 结论

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

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

原文地址:https://www.containiq.com/post/btf-bpf-type-format

作者:Aniket Bhattacharyea

时间: July 13, 2022

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1332964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32】STM32学习笔记-TIM定时中断(13)

00. 目录 文章目录 00. 目录01. TIM简介02. 定时器类型03. 基本定时器04. 通用定时器05. 高级定时器06. 定时中断基本结构07. 预分频器时序08. 计数器时序09. 计数器无预装时序10. 计数器有预装时序11. RCC时钟树12. 附录 01. TIM简介 TIM&#xff08;Timer&#xff09;定时器…

数据库中间件介绍

文章目录 什么是数据库中间件&#xff1f;Smart-client 模式优点缺点 Proxy 模式优点缺点 单元化架构优点缺点 总结 数据库中间件是连接数据库和应用程序之间的软件层&#xff0c;用于简化数据库管理、提高性能和可伸缩性&#xff0c;同时提供额外的功能和服务。在分布式系统和…

mybatis的二级缓存使用以及禁用

目录 mybatis 二级缓存配置有两处 全局设置 mapper 设置 测试代码 执行结果 源码执行逻辑 创建 SqlSession 二级缓存配置是否添加 解析 cache 标签 XMLMapperBuilder MapperBuilderAssistant CacheBuilder PerpetualCache SerializedCache LoggingCache 将 cach…

Python教程(17)——python模块是什么?python模块详解

Python模块简介 模块是一个包含了Python定义和语句的文件&#xff0c;可用于将功能组织成可重用和可维护的代码块。每个Python文件都可以作为一个模块&#xff0c;模块可以包含变量、函数、类或可执行代码。通过使用模块&#xff0c;我们可以将代码分离成逻辑单元&#xff0c;…

【vtkWidgetRepresentation】第十八期 vtkHoverWidget

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享vtkHoverWidget,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. vtkHoverWidget vtkHoverWidget用于在呈现窗口中…

Python生成圣诞节贺卡-代码案例剖析【第18篇—python圣诞节系列】

文章目录 ❄️Python制作圣诞节贺卡&#x1f42c;展示效果&#x1f338;代码&#x1f334;代码剖析 ❄️Python制作圣诞树贺卡&#x1f42c;展示效果&#x1f338;代码&#x1f334;代码剖析&#x1f338;总结 &#x1f385;圣诞节快乐&#xff01; ❄️Python制作圣诞节贺卡 …

商户如何去申请支付宝小程序以及对接团购(天财商龙)

申请支付宝小程序的前提&#xff1a;必须要是支付宝实名认证用户&#xff08;要申请企业支付宝&#xff09; 1.申请企业支付宝 申请网址&#xff1a;支付宝商家后台 https://b.alipay.com 企业支付宝必须是法人扫码进行注册 申请资料&#xff1a;企业的营业执照&#xff0c;没…

simulink代码生成(二)——ADC采样模块

这一节梳理如何使用C2000库中的ADC模块&#xff0c;从而实现采样&#xff1b; 先预留几个问题&#xff0c;逐步进行解决。 &#xff08;1&#xff09;在simulink中C2000的ADC采样模块设置是怎么样的&#xff1f;各个选项卡代表什么&#xff1f; &#xff08;2&#xff09;AD…

【开源】基于JAVA语言的学校热点新闻推送系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 新闻类型模块2.2 新闻档案模块2.3 新闻留言模块2.4 新闻评论模块2.5 新闻收藏模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 新闻类型表3.2.2 新闻表3.2.3 新闻留言表3.2.4 新闻评论表3.2.5 新闻收藏表 四、系统展…

谷粒商城|仓储服务-仓库管理

配置服务中心 cloud:nacos:discovery:server-addr: 127.0.0.1:8848application:name: guliware在网关配置路由转发 guligateway .yml配置文件 - id: ware_routeuri: lb://guliwarepredicates:- Path/api/guliware/**filters:- RewritePath/api/(?<segment>.*),/$\{segm…

计算机网络复习-OSI TCP/IP 物理层

我膨胀了&#xff0c;挂我啊~ 作者简介&#xff1a; 每年都吐槽吉师网安奇怪的课程安排、全校正经学网络安全不超20人情景以及割韭菜企业合作的FW&#xff0c;今年是第一年。。 TCP/IP模型 先做两道题&#xff1a; TCP/IP协议模型由高层到低层分为哪几层&#xff1a; 这题…

EasyPoi(excel导入导出)

一&#xff0c;依赖包&#xff1a; <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.1.3</version></dependency> 二&#xff0c;官网文档 1. 前言 - Powered…

格密码基础:q-ary格

目录 一. 格密码的重要性 二. 格密码基础 2.1 格点的另一种理解方式 三. q-ary格 3.1 q-ary垂直格 3.2 q-ary格 3.3 二者结合 四. 论文中的q-ary格 4.1 定理1 4.2 定理2 4.3 定理3 一. 格密码的重要性 格密码的基础是研究格点上的困难问题&#xff0c;这种格点使用…

从浮点数度分秒1.40000中无损精度提取1度40分00.0秒的方法

那些最好的程序员不是为了得到更高的薪水或者得到公众的仰慕而编程&#xff0c;他们只是觉得这是一件有趣的事情&#xff01; 从浮点数度分秒1.40000中无损精度提取1度40分00.0秒的方法 &#x1f340;前言&#x1f338;传统的提取方法&#x1f516;算法介绍&#x1f9fe;Python…

Java研学-HTTP 协议

一 概述 1 概念和作用 概念&#xff1a;HTTP 是 HyperText Transfer Protocol (超文本传输协议)的简写&#xff0c;它是 TCP/IP 协议之上的一个应用层协议。简单理解就是 HTTP 协议底层是对 TCP/IP 协议的封装。   作用&#xff1a;用于规定浏览器和服务器之间数据传输的格式…

(企业 / 公司项目)如何使用分布式任务调度框架Quartz集成 和 SpringBoot自带的定时任务集成?

SpringBoot自带的定时任务 首先在你的微服务项目中创建一个新的模块&#xff0c;定时调度模块 pom.xml里面关联公共模块common的依赖其他不需要改变 然后启动类别删&#xff0c;启动项目是否报错&#xff0c;写一个简单的测试类访问路径是否成功 package com.jiawa.train.bat…

力扣日记12.24-【二叉树篇】236. 二叉树的最近公共祖先

力扣日记&#xff1a;【二叉树篇】236. 二叉树的最近公共祖先 日期&#xff1a;2023.12.24 参考&#xff1a;代码随想录、力扣 ps&#xff1a;提前祝 平安夜快乐&#xff01; 236. 二叉树的最近公共祖先 题目描述 难度&#xff1a;中等 给定一个二叉树, 找到该树中两个指定节点…

nodejs+vue+ElementUi摄影作品图片分享工作室管理系统

第1周 2.21&#xff5e;2.27 查阅资料&#xff0c;学习vscode开发平台和vue框架技术 第2周 2.28&#xff5e;3.6 对软件功能需求进行分析, 软件功能模块划分及软件界面设计 第3周 3.7&#xff5e;3.13 撰写并提交毕业设计开题报告、英文资料翻译 第4周 3.14&#xff5…

[Linux] MySQL数据库之索引

一、索引的相关知识 1.1 索引的简介 索引是一个排序列表&#xff0c;包含索引值和包含该值的数据行的物理地址&#xff08;类似于 c 语言链表&#xff0c;通过指针指向数据记录的内存地址&#xff09;。 使用索引后可以不用扫描全表来定位某行的数据&#xff0c;而是先通过索…

Linux环境变量剖析

一、什么是环境变量 概念&#xff1a;环境变量&#xff08;environment variables&#xff09;一般是指在操作系统中用来指定操作系统运行环境的一些参数&#xff0c;是在操作系统中一个具有特定名字的对象&#xff0c;它包含了一个或多个应用程序所将使用到的信息&#xff0c…