eBPF 基础知识-如何构建BTF

概念

BTF(BPF Type Format)、CO-RE(Compile Once, Run Everywhere)、和 libbpf 之间有紧密的联系,它们共同协作使得 eBPF 程序在不同内核版本上运行更加便捷和可靠。以下是它们之间的关系和各自的功能:

BTF(BPF Type Format)

BTF 是一种紧凑的类型信息格式,描述了内核数据结构的布局。它的主要作用包括:

  1. 内核数据结构的描述:BTF 提供了内核数据结构的详细描述,使得 eBPF 程序可以在运行时了解这些结构的布局。
  2. 调试和验证:BTF 信息可以用于调试和验证 eBPF 程序,确保程序访问内核数据时的正确性。
  3. 不幸的是,BTF(以及 DWARF)不记录#define宏,因此一些常用的宏可能缺失。最常见的缺失宏可能作为 libbpf 的bpf_helpers.h (内核端"库",由 libbpf 提供)vmlinux.h的一部分提供 。

CO-RE(Compile Once, Run Everywhere)

CO-RE 是一种技术,使得 eBPF 程序可以在编译一次后在不同内核版本上运行。它依赖于 BTF 提供的类型信息来适应不同内核版本的数据结构变化。CO-RE 的主要优点包括:

  1. 跨内核版本兼容性:使用 CO-RE 技术编写的 eBPF 程序可以在不同内核版本上运行,无需重新编译。
  2. 简化开发流程:开发者不需要针对每个内核版本进行单独的编译和调整,减少了维护的复杂性。

libbpf

libbpf 是一个用于加载和管理 eBPF 程序的用户空间库。它提供了一整套 API,用于简化 eBPF 程序的开发、加载、和调试。libbpf 的主要功能包括:

  1. 加载 eBPF 程序 :通过 libbpf 提供的 API,可以方便地加载和附加 eBPF 程序。
  2. 管理 eBPF 资源libbpf 提供了管理 eBPF map、ring buffer 等资源的功能。
  3. 支持 CO-RElibbpf 完全集成了 CO-RE 技术,允许 eBPF 程序使用 BTF 信息来适应不同内核版本的数据结构。

三者之间的关系

  1. BTF 与 CO-RE:BTF 提供了内核类型信息,使得 CO-RE 技术可以利用这些信息来实现 eBPF 程序的跨内核版本兼容性。通过 BTF,CO-RE 可以在运行时确定内核数据结构的实际布局,从而正确地访问这些数据。
  2. CO-RE 与 libbpflibbpf 提供了对 CO-RE 的支持,使得开发者可以方便地编写、编译和加载支持 CO-RE 的 eBPF 程序。libbpf 利用 BTF 信息来解析和调整 eBPF 程序中的数据访问,使其适应不同的内核版本。
  3. libbpf 与 BTFlibbpf 使用 BTF 信息来辅助 eBPF 程序的加载和验证。通过 BTF,libbpf 可以在加载 eBPF 程序时检查程序访问的内核数据结构是否正确,并进行必要的调整。

vmlinux

vmlinux 是 Linux 内核的非压缩可执行二进制文件,包含了内核代码和数据。它通常用于内核开发和调试,因为它保留了调试符号,可以通过工具如 GDB 进行详细的调试。vmlinux 文件通常位于内核构建目录中,包含完整的内核映像,但不经过任何压缩或打包,因此体积较大。调试信息和符号可以帮助开发人员更深入地了解内核行为,分析和修复内核中的问题。

主要用途

  1. 内核调试:使用 GDB 等调试工具分析内核问题。
  2. 性能分析 :与 perf 等工具结合使用进行性能调优。
  3. eBPF 和 BTF:为 eBPF 程序生成 BTF 信息,用于类型检查和跨内核版本兼容。

生成 BTF(BPF Type Format)信息需要 vmlinux 文件,因为它包含了内核的完整调试信息,包括数据结构的详细布局。这些信息以 DWARF 格式存储在 vmlinux 文件中。pahole 工具从 vmlinux 中提取这些 DWARF 信息,并将其转换为更紧凑的 BTF 格式。

主要原因

  1. 类型信息 :BTF 需要详细的类型信息,这些信息在 vmlinux 的 DWARF 调试数据中。
  2. 内核数据结构vmlinux 包含了所有内核数据结构的定义和布局。
  3. 调试符号:调试符号提供了所需的元数据,以便正确地生成 BTF 信息。

生成 BTF

DebugInfo 包

Debuginfo 包主要用于调试目的,包含了程序的调试信息,如符号表、变量名、函数名和 DWARF 格式的调试数据。这些信息可以帮助开发人员和调试工具理解程序的内部结构和状态,进行调试和性能分析。

具体用途包括:

  1. 调试程序:使用 GDB 等调试工具,调试运行中的程序。
  2. 生成 BTF 文件:从 DWARF 调试信息生成 BTF 文件,供 eBPF 程序使用。
  3. 性能分析:使用调试信息进行性能分析和代码优化。

Debuginfo 包通常与相应的二进制包一起提供,以便开发人员在需要时可以安装和使用。

获取 Ky10 SP1.1 Kernel debug info 包

  1. 获取当前操作系统的内核

    ini 复制代码
    # uname -a
    Linux master2 4.19.90-23.15.v2101.ky10.aarch64 #1 SMP Wed Sep 1 16:42:05 CST 2021 aarch64 aarch64 aarch64 GNU/Linux
  2. 下载对应 debuginfo 包,下载地址: update.cs2c.com.cn/NS/V10/V10S...,如果不确定当前操作系统属于 SP几,可以去 update.cs2c.com.cn/NS/V10 下找一下,debuginfo 包只会在 debug 目录下。

  3. 安装 debuginfo 包:

    复制代码
    rpm -ivh kernel-debuginfo-4.19.90-23.15.v2101.ky10.aarch64.rpm

安装 cMake

  1. 安装 cmak,地址:github.com/Kitware/CMa...
  2. 解压之后放到 /usr/bin 目录

This error typically occurs when you're trying to compile or run software that requires ELF (Executable and Linkable Format) and DWARF (debugging format) libraries, but the necessary development packages aren't installed on your system.

Here are the solutions for different operating systems:

Ubuntu/Debian:

sql 复制代码
sudo apt-get update
sudo apt-get install libelf-dev libdw-dev

CentOS/RHEL/Fedora:

bash 复制代码
# For newer versions (dnf)
sudo dnf install elfutils-devel elfutils-libelf-devel
​
# For older versions (yum)
sudo yum install elfutils-devel elfutils-libelf-devel

Arch Linux:

复制代码
sudo pacman -S libelf

Alpine Linux:

csharp 复制代码
sudo apk add elfutils-dev libelf-static

macOS (using Homebrew):

复制代码
brew install libelf

If you're still having issues, you might also need these additional packages:

Ubuntu/Debian:

arduino 复制代码
sudo apt-get install binutils-dev

For Python development specifically:

复制代码
pip install pyelftools

Could you let me know what operating system you're using and what specific software or compilation you're trying to run? This would help me provide more targeted guidance.

编译 dwarves

  1. 下载源码:

    bash 复制代码
    git clone https://github.com/acmel/dwarves.git
  2. 下载依赖包:

    bash 复制代码
    git submodule update --init --recursive
  3. 编译:

    bash 复制代码
    install cmake
    mkdir build
    cd build
    cmake -D__LIB=lib ..
    make install

生成 BTF

bash 复制代码
pahole --btf_encode_detached external.btf /usr/lib/debug/lib/modules/4.19.90-23.15.v2101.ky10.aarch64/vmlinux

生成 vmlinux.h

bash 复制代码
pahole --compile /usr/lib/debug/lib/modules/4.19.90-23.15.v2101.ky10.aarch64/vmlinux > vmlinux.h

生成 BTF 信息的工具

通常,BTF 信息编码在 .BTF 和 .BTF.ext ELF 节中,或在原始 BTF 文件中。主要有两种方法来编码 BTF 信息:

  1. pahole 工具(来自 dwarves 项目):

    该工具利用包含 DWARF 调试数据 的非剥离 ELF 文件。输入的 ELF 文件可以是内核或标准 eBPF ELF 对象。该过程会在输入 ELF 文件中添加两个包含 BTF 编码的 ELF 节。最近,这种 BTF 编码也可以添加到外部原始 BTF 文件中,可以用作 libbpf 的输入。

  2. LLVM:

    编译 eBPF 代码时,LLVM 会自动生成 .BTF 和 .BTF.ext ELF 节。值得注意的是,与 pahole 不同,LLVM 能生成 BTF 重定位信息。这对 libbpf 成功执行 CO-RE 重定位至关重要。

通过选择这些方法之一,可以根据应用程序的需求有效生成 BTF 信息。

读取 BTF 信息

  1. 使用 bpftool

    如果当前运行的内核支持嵌入式 BTF(大多数现代 Linux 发行版都支持),则可以在 /sys/kernel/btf/vmlinux 中找到相应的 BTF 信息。可以通过执行以下命令检查该文件的内容:

    ini 复制代码
    $ bpftool btf dump file /sys/kernel/btf/vmlinux format raw
    
    [1] INT 'long unsigned int' size=8 bits_offset=0 nr_bits=64 encoding=(none)
    [2] CONST '(anon)' type_id=1
    [3] ARRAY '(anon)' type_id=1 index_type_id=18 nr_elems=2
    [4] PTR '(anon)' type_id=6
    [5] INT 'char' size=1 bits_offset=0 nr_bits=8 encoding=(none)
    [6] CONST '(anon)' type_id=5
    [7] INT 'unsigned int' size=4 bits_offset=0 nr_bits=32 encoding=(none)
    [8] CONST '(anon)' type_id=7
    [9] INT 'signed char' size=1 bits_offset=0 nr_bits=8 encoding=(none)
    [10] TYPEDEF '__u8' type_id=11
    ...

    格式可以是 "c" 或 "raw",根据需要选择。

  2. 使用 pahole

    arduino 复制代码
    $ pahole /sys/kernel/btf/vmlinux
    struct list_head {
        struct list_head *         next;                 /*     0     8 */
        struct list_head *         prev;                 /*     8     8 */
    
        /* size: 16, cachelines: 1, members: 2 */
        /* last cacheline: 16 bytes */
    };
    ...

    如果当前内核或打算支持 eBPF 应用程序的内核中没有 /sys/kernel/btf/vmlinux 文件,可能需要转向 BTFhub。这种情况通常发生在内核没有启用 DEBUG_INFO_BTF kconfig 选项时,这是 BTFhub-Archive 存储库中大多数内核的常见情况。因此,在这种情况下,BTFhub 可能对有所帮助。

Linux 内核如何生成自己的 BTF 信息?

BTFhubBTFhub-Archive 的核心在于将内核调试包自动转换为 BTF 信息,这对于 libbpf 运行 CO-RE 能力的 eBPF 应用至关重要。通过 btfhub 应用程序(通过 make 编译),下载所有现有的调试内核包,并将嵌入的 DWARF 信息转换为 BTF 格式。

BTFhub 以类似于 cron 作业的方式操作 btfhub 应用程序,每天执行。生成的 BTF 文件随后上传到 BTFhub-Archive 存储库,可以在的项目中使用。

  1. 实践中,可以通过执行以下命令将 .BTF ELF 节添加到非剥离的 vmlinuz(未压缩)内核文件:

    复制代码
    pahole -J vmlinux
  2. 或者,可以使用以下命令生成外部原始 BTF 文件:

    css 复制代码
    pahole --btf_encode_detached external.btf vmlinux
  3. 如果选择在 vmlinuz 文件中生成新的 .BTF ELF 节,稍后可以使用以下命令将其提取到外部原始 BTF 文件(例如 vmlinux.btf)中:

    csharp 复制代码
    llvm-objcopy --only-section=.BTF --set-section-flags .BTF=alloc,readonly vmlinux vmlinux.btf

这种灵活性确保了 BTF 文件生成的定制方法,增强了不同内核版本和发行版之间的兼容性。

核心配置

如何创建 Map

在 Linux 操作系统中,涉及到内核相关操作,主要是通过 Syscall 来实现,以 Golang 的 Syscall 为例,代码如下:

c 复制代码
package main

import (
	"fmt"
	"syscall"
	"unsafe"
)

// 构建系统调用结构体
type BPFMapCreateAttr struct {
  // Map 类型
	MapType    uint32
  // key 长度
	KeySize    uint32
  // value 长度
	ValueSize  uint32
  // map 数据长度,需要是 2 的倍数
	MaxEntries uint32
  // 用于控制 map 行为
	MapFlags   uint32
}

const (
  // BPF 系统调用命令行
	BPF_MAP_CREATE    = 0
  // BPF 类型
	BPF_MAP_TYPE_HASH = 27
  // BPF 系统调用 ID
	SYS_BPF           = 280 
	BPF_ANY           = 0
)

func main() {
  // 初始化 map 
	attr := BPFMapCreateAttr{
		MapType:    BPF_MAP_TYPE_HASH,
    KeySize:    4,
		ValueSize:  4,
		MaxEntries: 100,
	}

	// 调用系统调用创建 map
	fd, _, errno := syscall.Syscall(SYS_BPF, uintptr(BPF_MAP_CREATE), uintptr(unsafe.Pointer(&attr)), unsafe.Sizeof(attr))
	if errno != 0 {
		fmt.Println("Error creating BPF map:", errno)
		return
	}

	fmt.Println("BPF map created with file descriptor:", fd)
}

SYS_BPF

__NR_bpf 是一个宏定义,表示在特定平台上的 bpf 系统调用号。在 Linux 内核中,每个系统调用都有一个唯一的编号,这个编号被称为系统调用号,用于标识特定的系统调用。

什么是 __NR_bpf

  • __NR_bpf 是系统调用 bpf 的系统调用号宏。
  • 系统调用号用于在用户空间和内核空间之间进行系统调用,用户空间程序通过系统调用号请求内核执行特定的操作。
  • bpf 系统调用用于创建和管理 eBPF 程序和映射。

在 Linux 系统中,系统调用号通常定义在头文件中,如 <asm/unistd.h> 或其平台特定的变种。以下是查找 __NR_bpf 系统调用号的方法:

可以使用 grep 命令在系统头文件中搜索 __NR_bpf 的定义。

arduino 复制代码
grep __NR_bpf /usr/include/asm-generic/unistd*.h

示例输出可能如下所示:

arduino 复制代码
#define __NR_bpf 280
__SYSCALL(__NR_bpf, sys_bpf)

这表示在 unistd.h 文件中,__NR_bpf 的系统调用号定义为 280。

BPF MAP 类型

ini 复制代码
type MapType uint32
const (
	BPF_MAP_TYPE_UNSPEC                           MapType = 0
	BPF_MAP_TYPE_HASH                             MapType = 1
	BPF_MAP_TYPE_ARRAY                            MapType = 2
	BPF_MAP_TYPE_PROG_ARRAY                       MapType = 3
	BPF_MAP_TYPE_PERF_EVENT_ARRAY                 MapType = 4
	BPF_MAP_TYPE_PERCPU_HASH                      MapType = 5
	BPF_MAP_TYPE_PERCPU_ARRAY                     MapType = 6
	BPF_MAP_TYPE_STACK_TRACE                      MapType = 7
	BPF_MAP_TYPE_CGROUP_ARRAY                     MapType = 8
	BPF_MAP_TYPE_LRU_HASH                         MapType = 9
	BPF_MAP_TYPE_LRU_PERCPU_HASH                  MapType = 10
	BPF_MAP_TYPE_LPM_TRIE                         MapType = 11
	BPF_MAP_TYPE_ARRAY_OF_MAPS                    MapType = 12
	BPF_MAP_TYPE_HASH_OF_MAPS                     MapType = 13
	BPF_MAP_TYPE_DEVMAP                           MapType = 14
	BPF_MAP_TYPE_SOCKMAP                          MapType = 15
	BPF_MAP_TYPE_CPUMAP                           MapType = 16
	BPF_MAP_TYPE_XSKMAP                           MapType = 17
	BPF_MAP_TYPE_SOCKHASH                         MapType = 18
	BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED        MapType = 19
	BPF_MAP_TYPE_CGROUP_STORAGE                   MapType = 19
	BPF_MAP_TYPE_REUSEPORT_SOCKARRAY              MapType = 20
	BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED MapType = 21
	BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE            MapType = 21
	BPF_MAP_TYPE_QUEUE                            MapType = 22
	BPF_MAP_TYPE_STACK                            MapType = 23
	BPF_MAP_TYPE_SK_STORAGE                       MapType = 24
	BPF_MAP_TYPE_DEVMAP_HASH                      MapType = 25
	BPF_MAP_TYPE_STRUCT_OPS                       MapType = 26
	BPF_MAP_TYPE_RINGBUF                          MapType = 27
	BPF_MAP_TYPE_INODE_STORAGE                    MapType = 28
	BPF_MAP_TYPE_TASK_STORAGE                     MapType = 29
	BPF_MAP_TYPE_BLOOM_FILTER                     MapType = 30
	BPF_MAP_TYPE_USER_RINGBUF                     MapType = 31
	BPF_MAP_TYPE_CGRP_STORAGE                     MapType = 32
)

以上是常见 Map 类型,对于不同操作系统来说,由于内核版本不同导致支持的 map 类型都不一样,可以通过 bpftool 进行支持的 Map 类型进行核实,命令如下:

perl 复制代码
bpftool feature probe|grep map_type

结果如下,NOT available 为当前内核还未支持的 map 类型:

rust 复制代码
eBPF map_type hash is available
eBPF map_type array is available
eBPF map_type prog_array is available
eBPF map_type perf_event_array is available
eBPF map_type percpu_hash is available
eBPF map_type percpu_array is available
eBPF map_type stack_trace is available
eBPF map_type cgroup_array is available
eBPF map_type lru_hash is available
eBPF map_type lru_percpu_hash is available
eBPF map_type lpm_trie is available
eBPF map_type array_of_maps is available
eBPF map_type hash_of_maps is available
eBPF map_type devmap is available
eBPF map_type sockmap is available
eBPF map_type cpumap is available
eBPF map_type xskmap is available
eBPF map_type sockhash is available
eBPF map_type cgroup_storage is available
eBPF map_type reuseport_sockarray is available
eBPF map_type percpu_cgroup_storage is NOT available
eBPF map_type queue is NOT available
eBPF map_type stack is NOT available
eBPF map_type sk_storage is NOT available
eBPF map_type devmap_hash is NOT available
eBPF map_type struct_ops is NOT available
eBPF map_type ringbuf is NOT available
eBPF map_type inode_storage is NOT available
eBPF map_type task_storage is NOT available
eBPF map_type bloom_filter is NOT available
eBPF map_type user_ringbuf is NOT available
eBPF map_type cgrp_storage is NOT available
eBPF map_type arena is NOT available

BPF MAP Flags

在 eBPF 程序中,map_flags 是一个位掩码,可以包含多个标志。每个标志都有一个对应的整数值,多个标志可以通过按位或操作组合在一起。以下是常见的 map_flags 及其对应的整数值:

BPF_F_NO_PREALLOC :不预分配内存。适用于 BPF_MAP_TYPE_HASHBPF_MAP_TYPE_LPM_TRIE

BPF_F_NUMA_NODE:指定 map 应该分配在特定的 NUMA 节点上。

BPF_F_RDONLY_PROG:map 只读,适用于程序。

BPF_F_WRONLY_PROG:map 只写,适用于程序。

BPF_F_STACK_BUILD_ID :使用 BPF_MAP_TYPE_STACK_TRACE 时记录构建 ID。

示例代码

ini 复制代码
#define BPF_F_NO_PREALLOC 0x00000001
#define BPF_F_NUMA_NODE 0x00000002

struct bpf_map_def SEC("maps") my_map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(int),
    .value_size = sizeof(int),
    .max_entries = 1024,
    .map_flags = BPF_F_NO_PREALLOC | BPF_F_NUMA_NODE, // 组合多个标志
};

在这个示例中,map_flags 将包含 BPF_F_NO_PREALLOCBPF_F_NUMA_NODE,它们通过按位或操作组合在一起,最终的整数值为 0x00000003

BPF MAP 指令

以下是目前支持的 BPF map 操作指令:

BPF_MAP_CREATE:创建一个新的 eBPF map。

BPF_MAP_LOOKUP_ELEM:在 map 中查找一个元素。

BPF_MAP_UPDATE_ELEM:更新或添加 map 中的元素。

BPF_MAP_DELETE_ELEM:删除 map 中的元素。

BPF_MAP_GET_NEXT_KEY:获取 map 中的下一个键。

相关推荐
wmze21 分钟前
InnoDB存储引擎
后端
lifallen1 小时前
Java BitSet类解析:高效位向量实现
java·开发语言·后端·算法
子恒20052 小时前
警惕GO的重复初始化
开发语言·后端·云原生·golang
daiyunchao2 小时前
如何理解"LLM并不理解用户的需求,只是下一个Token的预测,但他能很好的完成任务,比如写对你想要的代码"
后端·ai编程
Android洋芋2 小时前
SettingsActivity.kt深度解析
后端
onejason3 小时前
如何利用 PHP 爬虫按关键字搜索 Amazon 商品
前端·后端·php
令狐冲不冲3 小时前
常用设计模式介绍
后端
Java水解3 小时前
深度解析MySQL中的Join算法:原理、实现与优化
后端·mysql
一语长情3 小时前
关于Netty的DefaultEventExecutorGroup使用
java·后端·架构
易元3 小时前
设计模式-状态模式
后端·设计模式