2025年bfp笔记

bfp笔记BPF 执行原理 编译 BPF 程序 BPF 程序可以使用 C 语言 BPF 汇编语言或编译器插件进行编写 编写完成后 BPF 程序会被编译为一段机器码 该机器码是一组低级指令 类似于虚拟机指令 加载 BPF 程序 BPF 程序通常被封装在一个 BPF 对象文件中 该文件包含了 BPF 程序的机器码 符号表 重定位表等元数据

大家好,我是讯享网,很高兴认识大家。

BPF执行原理

  1. 编译BPF程序:BPF程序可以使用C语言、BPF汇编语言或编译器插件进行编写。编写完成后,BPF程序会被编译为一段机器码,该机器码是一组低级指令,类似于虚拟机指令。
  2. 加载BPF程序:BPF程序通常被封装在一个BPF对象文件中,该文件包含了BPF程序的机器码、符号表、重定位表等元数据。使用libbpf库或其他BPF工具,可以将BPF对象文件加载到内核中。
  3. 验证BPF程序:在加载BPF程序之前,内核会对其进行验证。这个过程包括检查程序的合法性、查找和解析程序中的符号等。验证确保BPF程序不会破坏内核的稳定性和安全性。
  4. JIT编译(可选):为了提高执行速度,BPF程序可以进行即时编译(JIT)。JIT编译器会将BPF程序的机器码转换为与当前CPU架构兼容的本地机器码。这样,BPF程序就可以直接在CPU上运行,而不需要解释执行。
  5. 执行BPF程序:一旦BPF程序被加载和编译,内核就可以执行该程序了。BPF程序通常与网络数据包处理相关,可以被挂钩到网络接口的不同阶段,如入站、出站、转发等。当网络数据包经过被挂钩的阶段时,内核会执行相应的BPF程序来过滤或处理数据包。
  6. 程序返回和结果处理:BPF程序执行完成后,会返回一个结果。这个结果可以是允许或拒绝数据包通过,也可以是对数据包进行修改或采集一些元数据等。内核会根据BPF程序的返回值来决定如何处理数据包。

BPF指令由Linux内核的BPF运行时模块执行,具体来说,该运行时模块提供两种执行机制:一个解释器和一个将BPF指令动态转换为本地化指令的即时编译器(JIT)。注意只有当BPF程序通过解释器执行时其实现才是一种虚拟机。当前JIT启用后BFP指令将通过JIT编译后像如何其它本地内核代码一样,直接在处理器上运行。如果没有启用JIT则经由解释器执行。相比于解释器执行,JIT执行的性能更好。一些发行版在x86架构上JIT是默认开启的,完全移除了内核中解释器的实现。

 BPF Hooks

SEC关键字

在BPF中,SEC是一个用于指定BPF程序属性的关键字。SEC是Section的缩写,表示代码段或数据段。SEC关键字用于将BPF程序中的函数或变量放置在特定的代码段或数据段中,以便在加载和执行时进行控制。

BPF程序可以包含多个函数和变量,而SEC关键字用于对它们进行分组和分类。常用的SEC关键字包括:

  1. SEC("kprobe/"):用于将BPF程序挂钩到内核的kprobe(内核探针)上。kprobe允许在内核函数的入口点处执行自定义的BPF程序。
  2. SEC("kretprobe/"):用于将BPF程序挂钩到内核的kretprobe(内核返回探针)上。kretprobe允许在内核函数的返回点处执行自定义的BPF程序。
  3. SEC("tracepoint/"):用于将BPF程序挂钩到内核的tracepoint(跟踪点)上。tracepoint是内核中预定义的事件,可以用于跟踪内核的各种操作。
  4. SEC("perf_event/"):用于将BPF程序挂钩到内核的性能事件上。perf_event是内核中的性能事件接口,可以用于测量和跟踪系统的性能指标。
  5. SEC(“tracepoint”):指定函数为跟踪点(tracepoint)。跟踪点是BPF程序中用于在Linux内核的跟踪事件中插入代码的部分。
  6. SEC(“maps”):指定变量为BPF映射(maps)。BPF映射是一种数据结构,用于在BPF程序和用户空间之间共享数据。
  7. SEC(“license”):指定变量为BPF程序的许可证(license)。许可证用于说明BPF程序的使用权限和限制。
  8. SEC("xdp_sock")是一个特殊的BPF宏,用于指定一个BPF程序的类型为XDP(eXpress Data Path)套接字型。在BPF程序中使用SEC("xdp_sock")宏,可以将该程序挂钩到Linux内核中的XDP处理路径上

BPF映射

在eBPF(extended Berkeley Packet Filter)中,映射(Map)是一种用于在用户空间和内核空间之间共享数据的机制。映射可以看作是一种键-值存储结构,用于在eBPF程序执行期间读取和写入数据。

映射提供了一种将数据从用户空间传递到内核空间的方式,以及将数据从内核空间传递回用户空间的方式。映射可以从用户空间被读取和修改,并且在eBPF程序执行期间可以在内核空间中进行读取和修改。

所有 map 类型的定义:

enum bpf_map_type { BPF_MAP_TYPE_UNSPEC, BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_ARRAY, BPF_MAP_TYPE_PROG_ARRAY, BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_PERCPU_ARRAY, BPF_MAP_TYPE_STACK_TRACE, BPF_MAP_TYPE_CGROUP_ARRAY, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH, BPF_MAP_TYPE_LPM_TRIE, BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_HASH_OF_MAPS, BPF_MAP_TYPE_DEVMAP, BPF_MAP_TYPE_SOCKMAP, BPF_MAP_TYPE_CPUMAP, BPF_MAP_TYPE_XSKMAP, BPF_MAP_TYPE_SOCKHASH, BPF_MAP_TYPE_CGROUP_STORAGE, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY, BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE, BPF_MAP_TYPE_QUEUE, BPF_MAP_TYPE_STACK, BPF_MAP_TYPE_SK_STORAGE, BPF_MAP_TYPE_DEVMAP_HASH, BPF_MAP_TYPE_STRUCT_OPS, BPF_MAP_TYPE_RINGBUF, BPF_MAP_TYPE_INODE_STORAGE, };

讯享网

创建BPF maps 

BPF_MAP_TYPE_ARRAY类型的map创建

讯享网struct { //指定了映射的类型为数组。BPF_MAP_TYPE_ARRAY是一个预定义的宏,用于表示数组映射。 __uint(type, BPF_MAP_TYPE_ARRAY); //指定了映射键的类型为u32。 __type(key, u32); //指定了映射值的类型为long __type(value, long); //指定了映射的最大条目数为256,即映射中可以存储的最大元素数量 __uint(max_entries, 256); //指定了映射应该被放置在特定的eBPF映射节中。这是为了在加载eBPF程序时将映射与相应的节关联起来 } my_map SEC(".maps");

 BPF_MAP_TYPE_HASH类型的map创建

struct pair { long packets; long bytes; }; struct { __uint(type, BPF_MAP_TYPE_HASH); // BPF map 类型 __type(key, __be32); // 目的 IP 地址 __type(value, struct pair); // 包数和字节数 __uint(max_entries, 1024); // 最大 entry 数量 } hash_map SEC(".maps");

SEC(".maps")bpf_create_map都是用于创建eBPF映射(Map)的机制,但它们的原理和实现方式略有不同。

  1. SEC(".maps")原理:SEC(".maps")是一个编译器指示,用于在eBPF程序中定义映射的结构和初始化数据。在编译eBPF程序时,编译器会将使用SEC(".maps")指示定义的映射结构和初始化数据放置在特定的程序节(Section)中。当eBPF程序加载到内核时,内核会解析程序节,并根据SEC(".maps")指示找到映射的定义和初始化数据。然后,内核根据这些信息来创建映射,并将其挂载到内核的映射表中。这种方式适用于在编译时确定映射结构和数据的情况。
  2. bpf_create_map原理:bpf_create_map是一个运行时的系统调用,用于在eBPF程序运行时动态创建映射。当程序调用bpf_create_map系统调用时,内核会根据传递的映射类型和配置参数,创建一个新的映射对象。映射对象在内核中分配内存,并初始化相关数据结构。然后,内核会返回一个映射的文件描述符(File Descriptor)给程序,程序可以通过该文件描述符来对映射进行操作。这种方式适用于需要在程序运行时根据需求动态创建映射的情况。

更新元素

bpf_map_update_elem是eBPF中的一个函数,用于在eBPF程序中更新映射中的元素或插入新的元素

讯享网int bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags); 
  • map是一个指向映射的指针,表示要进行更新或插入操作的映射。
  • key是一个指向要更新或插入的元素的键的指针。


    讯享网

  • value是一个指向要更新或插入的元素的值的指针。
  • flags是一个用于指定更新操作的标志位。可以使用BPF_NOEXIST标志表示仅在键不存在时进行插入操作,使用BPF_EXIST标志表示仅在键已存在时进行更新操作。
  • 该函数返回一个整数,表示操作的结果。如果操作成功,则返回0;如果操作失败,则返回一个负数错误代码。

读取元素

bpf_map_lookup_elem是eBPF中的一个函数,用于在eBPF程序中查找映射中的元素

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key); 
  • map是一个指向映射的指针,表示要进行查找的映射。
  • key是一个指向要查找的元素的键的指针。
  • 该函数返回一个void指针,指向映射中与给定键匹配的元素的值。如果找到匹配的元素,则返回对应的值的指针;如果未找到匹配的元素,则返回NULL。

 迭代遍历元素和查找

bpf_map_lookup_elem 是eBPF中的一个函数,用于在eBPF程序中查找映射中的元素。

该函数的原型如下:

讯享网void *bpf_map_lookup_elem(struct bpf_map *map, const void *key); 
  • map是一个指向映射的指针,表示要进行查找的映射。
  • key是一个指向要查找的元素的键的指针。

该函数返回一个void指针,指向映射中与给定键匹配的元素的值。如果找到匹配的元素,则返回对应的值的指针;如果未找到匹配的元素,则返回NULL。

bpf_map_get_next_key 是eBPF中的一个函数,用于在eBPF程序中获取映射中下一个键的值。

该函数的原型如下:

int bpf_map_get_next_key(struct bpf_map *map, const void *key, void *next_key); 
  • map是一个指向映射的指针,表示要进行操作的映射。
  • key是一个指向当前键的指针,表示要获取下一个键的值。
  • next_key是一个指向存储下一个键的值的缓冲区的指针。

该函数返回一个整数,表示操作的结果。如果成功获取到下一个键的值,则返回0;如果没有下一个键或发生其他错误,则返回一个负数错误代码。

删除

bpf_map_delete_elem 是eBPF中的一个函数,用于在eBPF程序中删除映射中的元素。

该函数的原型如下:

讯享网int bpf_map_delete_elem(struct bpf_map *map, const void *key); 
  • map是一个指向映射的指针,表示要进行操作的映射。
  • key是一个指向要删除的元素的键的指针。

该函数返回一个整数,表示操作的结果。如果成功删除元素,则返回0;如果未找到匹配的元素或发生其他错误,则返回一个负数错误代码。

案例1

内核态代码

// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2017 Facebook */ #include <uapi/linux/bpf.h> #include <bpf/bpf_helpers.h> struct syscalls_enter_open_args { unsigned long long unused; long syscall_nr; long filename_ptr; long flags; long mode; }; struct syscalls_exit_open_args { unsigned long long unused; long syscall_nr; long ret; }; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __type(key, u32); __type(value, u32); __uint(max_entries, 1); } enter_open_map SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_ARRAY); __type(key, u32); __type(value, u32); __uint(max_entries, 1); } exit_open_map SEC(".maps"); static __always_inline void count(void *map) { u32 key = 0; u32 *value, init_val = 1; value = bpf_map_lookup_elem(map, &key); if (value) *value += 1; else bpf_map_update_elem(map, &key, &init_val, BPF_NOEXIST); } SEC("tracepoint/syscalls/sys_enter_open") int trace_enter_open(struct syscalls_enter_open_args *ctx) { count(&enter_open_map); return 0; } SEC("tracepoint/syscalls/sys_enter_openat") int trace_enter_open_at(struct syscalls_enter_open_args *ctx) { count(&enter_open_map); return 0; } SEC("tracepoint/syscalls/sys_exit_open") int trace_enter_exit(struct syscalls_exit_open_args *ctx) { count(&exit_open_map); return 0; } SEC("tracepoint/syscalls/sys_exit_openat") int trace_enter_exit_at(struct syscalls_exit_open_args *ctx) { count(&exit_open_map); return 0; } 

用户态代码

讯享网// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2017 Facebook */ #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <linux/perf_event.h> #include <errno.h> #include <sys/resource.h> #include <bpf/libbpf.h> #include <bpf/bpf.h> /* This program verifies bpf attachment to tracepoint sys_enter_* and sys_exit_*. * This requires kernel CONFIG_FTRACE_SYSCALLS to be set. */ static void usage(const char *cmd) { printf("USAGE: %s [-i num_progs] [-h]\n", cmd); printf(" -i num_progs # number of progs of the test\n"); printf(" -h # help\n"); } static void verify_map(int map_id,const char* name) { __u32 key = 0; __u32 val; if (bpf_map_lookup_elem(map_id, &key, &val) != 0) { fprintf(stderr, "map_lookup failed: %s\n", strerror(errno)); return; } if (val == 0) { fprintf(stderr, "failed: map #%d returns value 0\n", map_id); return; } printf("name=%s, var=%d\n",name,val); val = 0; if (bpf_map_update_elem(map_id, &key, &val, BPF_ANY) != 0) { fprintf(stderr, "map_update failed: %s\n", strerror(errno)); return; } } static int test(char *filename, int num_progs) { int map0_fds[num_progs], map1_fds[num_progs], fd, i, j = 0; struct bpf_link *links[num_progs * 4]; struct bpf_object *objs[num_progs]; struct bpf_program *prog; for (i = 0; i < num_progs; i++) { objs[i] = bpf_object__open_file(filename, NULL); if (libbpf_get_error(objs[i])) { fprintf(stderr, "opening BPF object file failed\n"); objs[i] = NULL; goto cleanup; } /* load BPF program */ if (bpf_object__load(objs[i])) { fprintf(stderr, "loading BPF object file failed\n"); goto cleanup; } map0_fds[i] = bpf_object__find_map_fd_by_name(objs[i], "enter_open_map"); map1_fds[i] = bpf_object__find_map_fd_by_name(objs[i], "exit_open_map"); if (map0_fds[i] < 0 || map1_fds[i] < 0) { fprintf(stderr, "finding a map in obj file failed\n"); goto cleanup; } bpf_object__for_each_program(prog, objs[i]) { links[j] = bpf_program__attach(prog); if (libbpf_get_error(links[j])) { fprintf(stderr, "bpf_program__attach failed\n"); links[j] = NULL; goto cleanup; } j++; } printf("prog #%d: map ids %d %d\n", i, map0_fds[i], map1_fds[i]); } /* current load_bpf_file has perf_event_open default pid = -1 * and cpu = 0, which permits attached bpf execution on * all cpus for all pid's. bpf program execution ignores * cpu affinity. */ /* trigger some "open" operations */ fd = open(filename, O_RDONLY); if (fd < 0) { fprintf(stderr, "open failed: %s\n", strerror(errno)); return 1; } close(fd); /* verify the map */ for (i = 0; i < num_progs; i++) { verify_map(map0_fds[i],"enter_open_map"); verify_map(map1_fds[i],"exit_open_map"); } cleanup: for (j--; j >= 0; j--) bpf_link__destroy(links[j]); for (i--; i >= 0; i--) bpf_object__close(objs[i]); return 0; } int main(int argc, char argv) { struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; int opt, num_progs = 1; char filename[256]; while ((opt = getopt(argc, argv, "i:h")) != -1) { switch (opt) { case 'i': num_progs = atoi(optarg); break; case 'h': default: usage(argv[0]); return 0; } } setrlimit(RLIMIT_MEMLOCK, &r); snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]); return test(filename, num_progs); } 

小讯
上一篇 2025-03-04 19:15
下一篇 2025-01-29 18:05

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/70832.html