bpf的加载流程分析

article/2025/9/19 22:36:01

文章目录

  • 前言
  • elf结构简介
  • load_bpf_file函数
    • 准备工作
    • 创建map
    • 处理所有的重定向section
    • 加载ebpf程序
  • 参考

前言

我们知道,使用clang/llvm编译生成的target为bpf的elf文件,使用load_bpf_file函数加载进入内核。

所以,这里,我们需要分析下load_bpf_file函数。


elf结构简介

我们首先需要一个bpf程序,并编译该程序。linux的sample提供了一个这样的示例代码:xdp_monitor_kern.c

我们需要linux的源码,有两种方式:ubuntu获取源码方式 | linux内核实验环境搭建

进入到源码目录之后,我们编译sample/bpf中的代码。

# O指定的是我之前编译好的内核位置。
# 看下sample/bpf/makefile文件,里面没有设置prefix这类变量。所以生成的二进制文件,在源码所在的目录
make -C samples/bpf/ O=../linux_image/5.6_debug# 查看编译生成的elf文件file xdp_monitor_kern.o
xdp_monitor_kern.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), with debug_info, not stripped# 不用之后,记得删除编译生成的文件
make clean -C samples/bpf/ O=../linux_image/5.6_debug

之后,我们可以使用readelf查看xdp_monitor_kern.o的相关信息。

关于elf的结构,可以参考:《程序员的自我修养 – 链接装载与库》 第3章 目标文件里有什么。

图片来源: 计算机那些事(4)——ELF文件结构

在这里插入图片描述

另外,需要安装下elf库。

sudo apt install libelf-dev

load_bpf_file函数

可以看到load_bpf_file函数,调用do_load_bpf_file函数。根据bpf_load.h里面的注释,我们知道这个函数,分为三个部分。

/* parses elf file compiled by llvm .c->.o* . parses 'maps' section and creates maps via BPF syscall* . parses 'license' section and passes it to syscall* . parses elf relocations for BPF maps and adjusts BPF_LD_IMM64 insns by*   storing map_fd into insn->imm and marking such insns as BPF_PSEUDO_MAP_FD* . loads eBPF programs via BPF syscall** One ELF file can contain multiple BPF programs which will be loaded* and their FDs stored stored in prog_fd array** returns zero on success*/
int load_bpf_file(char *path); 
int load_bpf_file_fixup_map(const char *path, fixup_map_cb fixup_map); //我们暂时不使用fixup_map,即fixup_map为NULL
  1. 解析map section,并通过bpf系统调用创建map
  2. 解析elf relocation sections。将其作用的重定向段中的insn的imm存储着map的fd,并将该insn标记为BPF_PSEUDO_MAP_FD。
  3. 通过bpf系统调用,加载eBPF程序

准备工作

打开elf文件。

	if (elf_version(EV_CURRENT) == EV_NONE)  //elf的version要为1return 1;fd = open(path, O_RDONLY, 0);if (fd < 0)return 1;elf = elf_begin(fd, ELF_C_READ, NULL); // elf指向elf文件if (!elf)return 1;if (gelf_getehdr(elf, &ehdr) != &ehdr) // 获取elf的headerreturn 1;/* clear all kprobes */i = write_kprobe_events(""); // 将/sys/kernel/debug/tracing/kprobe_events中的内容清空

扫描所有的section。

将license的相关内容从license section中取出;将kernel version的相关内容从version section中取出;

所有的map在一个map section中。保存map的数据和map section对应的section标号。

保存符号表的数据。段的类型是符号表时, sh_link的内容“操作系统相关”,(我不知道里面存储的是什么)它用于后面取map的名称。

/* scan over all elf sections to get license and map info */for (i = 1; i < ehdr.e_shnum; i++) {if (get_sec(elf, i, &ehdr, &shname, &shdr, &data))continue;if (0) /* helpful for llvm debugging */printf("section %d:%s data %p size %zd link %d flags %d\n",i, shname, data->d_buf, data->d_size,shdr.sh_link, (int) shdr.sh_flags);if (strcmp(shname, "license") == 0) {processed_sec[i] = true;memcpy(license, data->d_buf, data->d_size);} else if (strcmp(shname, "version") == 0) {processed_sec[i] = true;if (data->d_size != sizeof(int)) {printf("invalid size of version section %zd\n",data->d_size);return 1;}memcpy(&kern_version, data->d_buf, sizeof(int));} else if (strcmp(shname, "maps") == 0) {int j;maps_shndx = i;data_maps = data;for (j = 0; j < MAX_MAPS; j++)map_data[j].fd = -1;} else if (shdr.sh_type == SHT_SYMTAB) {strtabidx = shdr.sh_link;symbols = data;}}

创建map

解析map section,并通过bpf系统调用创建map。

	if (data_maps) {// 使用elf中的map section,填充map_data.nr_maps = load_elf_maps_section(map_data, maps_shndx,elf, symbols, strtabidx);if (nr_maps < 0) {printf("Error: Failed loading ELF maps (errno:%d):%s\n",nr_maps, strerror(-nr_maps));goto done;}/* 使用系统调用syscall(__NR_bpf, 0, attr, size);,创建各个map *//* 此时用户空间可以使用两个全局变量:map_fd,map_data来定位创建的map。 *//* 全局变量map_data_count,记录着该程序,创建的map数量 */if (load_maps(map_data, nr_maps, fixup_map))goto done;map_data_count = nr_maps;processed_sec[maps_shndx] = true;}

load_elf_maps_section是一个很漂亮的函数。它使用elf中的map section,填充map_data。

static int load_elf_maps_section(struct bpf_map_data *maps, int maps_shndx,Elf *elf, Elf_Data *symbols, int strtabidx)
{int map_sz_elf, map_sz_copy;bool validate_zero = false;Elf_Data *data_maps;int i, nr_maps;GElf_Sym *sym;Elf_Scn *scn;int copy_sz;if (maps_shndx < 0)return -EINVAL;if (!symbols)return -EINVAL;/* Get data for maps section via elf index */scn = elf_getscn(elf, maps_shndx);if (scn)data_maps = elf_getdata(scn, NULL);if (!scn || !data_maps) {printf("Failed to get Elf_Data from maps section %d\n",maps_shndx);return -EINVAL;}/* For each map get corrosponding symbol table entry *//* 符号表中包含map的符号和其他符号;根据指向的节是不是map的节,判断当前符号,是不是一个map的符号。所有的map在一个节中 *//* 最后map节中,每个map对应的符号在sym中 */sym = calloc(MAX_MAPS+1, sizeof(GElf_Sym));for (i = 0, nr_maps = 0; i < symbols->d_size / sizeof(GElf_Sym); i++) {assert(nr_maps < MAX_MAPS+1);if (!gelf_getsym(symbols, i, &sym[nr_maps]))continue;if (sym[nr_maps].st_shndx != maps_shndx)continue;/* Only increment iif maps section */nr_maps++;}/* Align to map_fd[] order, via sort on offset in sym.st_value *//* 根据它们的偏移量进行排序。 */qsort(sym, nr_maps, sizeof(GElf_Sym), cmp_symbols);/* Keeping compatible with ELF maps section changes* ------------------------------------------------* The program size of struct bpf_load_map_def is known by loader* code, but struct stored in ELF file can be different.** Unfortunately sym[i].st_size is zero.  To calculate the* struct size stored in the ELF file, assume all struct have* the same size, and simply divide with number of map* symbols.* * map节中存放着多个map。它们的大小通过对应的符号无法看出。* 如果添加了新的特征,导致无法将elf中的map填充到程序中,则报错EFBIG* 比如,将来使用map添加了新的特征。但是这个程序却被使用在低版本上,导致map无法完全加载,可以EFBIG,* 这个程序,漂亮。*/map_sz_elf = data_maps->d_size / nr_maps;map_sz_copy = sizeof(struct bpf_load_map_def);if (map_sz_elf < map_sz_copy) {/** Backward compat, loading older ELF file with* smaller struct, keeping remaining bytes zero.*/map_sz_copy = map_sz_elf;} else if (map_sz_elf > map_sz_copy) {/** Forward compat, loading newer ELF file with larger* struct with unknown features. Assume zero means* feature not used.  Thus, validate rest of struct* data is zero.*/validate_zero = true;}/* Memcpy relevant part of ELF maps data to loader maps */for (i = 0; i < nr_maps; i++) {struct bpf_load_map_def *def;unsigned char *addr, *end;const char *map_name;size_t offset;map_name = elf_strptr(elf, strtabidx, sym[i].st_name);maps[i].name = strdup(map_name);if (!maps[i].name) {printf("strdup(%s): %s(%d)\n", map_name,strerror(errno), errno);free(sym);return -errno;}/* Symbol value is offset into ELF maps section data area */offset = sym[i].st_value;def = (struct bpf_load_map_def *)(data_maps->d_buf + offset);maps[i].elf_offset = offset;memset(&maps[i].def, 0, sizeof(struct bpf_load_map_def));memcpy(&maps[i].def, def, map_sz_copy);/* Verify no newer features were requested */if (validate_zero) {addr = (unsigned char *) def + map_sz_copy;end  = (unsigned char *) def + map_sz_elf;for (; addr < end; addr++) {if (*addr != 0) {free(sym);return -EFBIG;}}}}free(sym);return nr_maps;
}

处理所有的重定向section

解析elf relocation sections。将其作用的重定向段中的insn的imm存储着map的fd,并将该insn标记为BPF_PSEUDO_MAP_FD。

	/* process all relo sections, and rewrite bpf insns for maps */for (i = 1; i < ehdr.e_shnum; i++) {if (processed_sec[i])continue;if (get_sec(elf, i, &ehdr, &shname, &shdr, &data))continue;if (shdr.sh_type == SHT_REL) {struct bpf_insn *insns;/* locate prog sec that need map fixup (relocations) *//* 上面通过i遍历出所有的重定位表。sh_info该重定位表所作用的节在节头表中的下标 */if (get_sec(elf, shdr.sh_info, &ehdr, &shname_prog,&shdr_prog, &data_prog))continue;/* 要求通过重定位表找到的代码,需要是程序类型,并且该节在进程空间中可被执行 */if (shdr_prog.sh_type != SHT_PROGBITS ||!(shdr_prog.sh_flags & SHF_EXECINSTR))continue;insns = (struct bpf_insn *) data_prog->d_buf;processed_sec[i] = true; /* relo section *//* 对于重定位的代码,标记为BPF_PSEUDO_MAP_FD;insn中的imm指向map的fd*/if (parse_relo_and_apply(data, symbols, &shdr, insns,map_data, nr_maps))continue;}}

加载ebpf程序

通过bpf系统调用,加载eBPF程序。

	/* load programs *//* 不需要加载的section:version,license,map,relo section*/for (i = 1; i < ehdr.e_shnum; i++) {if (processed_sec[i])continue;if (get_sec(elf, i, &ehdr, &shname, &shdr, &data))continue;if (memcmp(shname, "kprobe/", 7) == 0 ||memcmp(shname, "kretprobe/", 10) == 0 ||memcmp(shname, "tracepoint/", 11) == 0 ||memcmp(shname, "raw_tracepoint/", 15) == 0 ||memcmp(shname, "xdp", 3) == 0 ||memcmp(shname, "perf_event", 10) == 0 ||memcmp(shname, "socket", 6) == 0 ||memcmp(shname, "cgroup/", 7) == 0 ||memcmp(shname, "sockops", 7) == 0 ||memcmp(shname, "sk_skb", 6) == 0 ||memcmp(shname, "sk_msg", 6) == 0) {ret = load_and_attach(shname, data->d_buf,data->d_size);if (ret != 0)goto done;}}

SEC("tracepoint/xdp/xdp_redirect")为例,我们看下加载过程。

.../* 最后调用sys_bpf(BPF_PROG_LOAD, attr, size);系统调用,将程序加载进入*/fd = bpf_load_program(prog_type, prog, insns_cnt, license, kern_version,bpf_log_buf, BPF_LOG_BUF_SIZE);
...} else if (is_tracepoint) {event += 11;if (*event == 0) {printf("event name cannot be empty\n");return -1;}strcpy(buf, DEBUGFS);strcat(buf, "events/");strcat(buf, event);strcat(buf, "/id");}
...接下来,从/sys/kernel/debug/tracing/events/xdp/xdp_redirect/id中读取内容
...我不知道为什么需要下面这样做。efd = sys_perf_event_open(&attr, -1/*pid*/, 0/*cpu*/, -1/*group_fd*/, 0);...err = ioctl(efd, PERF_EVENT_IOC_ENABLE, 0);...err = ioctl(efd, PERF_EVENT_IOC_SET_BPF, fd);

参考

eBPF 程序装载、翻译与运行过程详解


http://chatgpt.dhexx.cn/article/O14XtTHg.shtml

相关文章

深入理解 BPF map 实现机制

揭秘 BPF map 前生今世 目录 揭秘 BPF map 前生今世1. 前言2. 简单的使用样例用户空间与内核 BPF 辅助函数参数对比 3. 深入指令分析3.1 查看 BPF 指令3.2 加载器创建 map 对象3.3 第一次变身&#xff1a; map fd 替换3.4 第二次变身&#xff1a; map fd 替换成 map 结构指针 4…

bpf简介1

文章目录 前言prefaceIntroduction历史发展结构 推荐阅读 前言 来源&#xff1a;Linux Observability with BPF 这里整理下该书第一章&#xff1a;preface && Introduction 这本书有中文版的《Linux内核观测技术BPF》。这个链接里面的资料也是很好的&#xff0c;可以…

BPF技术学习分享

什么是BPF程序&#xff1a; BPF is a highly flexible and efficient virtual machine-like construct in the Linux kernel allowing to execute bytecode at various hook points in a safe manner. BPF程序 ----LLVMClang----> BPF字节码 ----JIT----> BPF指令集&…

BPF之事件源

基础 1. BPF和eBPF概念 BPF 原是 Berkeley Packet Filter&#xff08;伯克利数据包过滤器&#xff09;的缩写&#xff0c;1992诞生&#xff0c;用于网络包过滤。2014经过修改并入 Linux 内核主线&#xff0c;从此 BPF 变成了一个更通用的执行引擎&#xff0c;主要用于网络、可…

DPDK BPF

DPDK BPF DPDK 自版本 18.05 已集成了 librte_bpf, 主要利用rte_eth_rx_burst/rte_eth_tx_burst 回调函数机制, 执行eBPF字节码. 当前支持以下特性: base eBPF ISA (except tail-pointer)JIT (x86_64 and arm64 only)eBPF code verifieruser-defined helper functions (64-bi…

Linux超能力BPF技术介绍及学习分享

近两年BPF技术跃然成为了一项热门技术&#xff0c;在刚刚结束的KubeCon 2020 Europe会议上有7个关于BPF的技术分享&#xff0c; 而在KubeCon 2020 China会议上也已有了3个关于BPF技术的中文分享&#xff0c;分别来自腾讯和PingCAP&#xff0c;涉足网络优化和系统追踪等领域。在…

bpf原理与入门

一、bpf架构 如上图所示,bpf由六部分构成,以下为其在bpf中的作用: bpf工具:该部分涉及bpf用户态程序、bpf的编译工具,通过bpf编译工具如Clang、LLVM将bpf用户态程序编译成bpf字节码; 加载器:可以简单理解为bpf系统调用,将bpf字节码加载到内核; 验证器:对bpf程序的…

BPF入门1:BPF技术简介

目录 cbpf 介绍ebpf 介绍ebpf 和 cbpf对比ebpf和内核模块的对比 ebpf应用ebpf架构Why BPF is FAST指令虚拟机JIT How BPF extends KernelLLVM 编写ebpf程序BCCBPFTraceC 语言原生方式 国内大厂使用ebpf的实践经验参考 cbpf 介绍 BPF&#xff08;Berkeley Packet Filter &#…

增广拉格朗日函数

对于优化问题 arg ⁡ min ⁡ z E ( z ) ( 1 a ) s . t . C z − b 0 ( 1 b ) \mathop{\arg\min}_{z} \ E(z)\qquad(1a)\\ s.t. \quad Cz-b0 \qquad(1b) argminz​ E(z)(1a)s.t.Cz−b0(1b) 其增广拉格朗日函数被定义为&#xff1a; L ( z , α , μ ) E ( z ) α T ( C z −…

约束优化:PHR-ALM 增广拉格朗日函数法

文章目录 约束优化&#xff1a;PHR-ALM 增广拉格朗日函数法等式约束非凸优化问题的PHR-ALM不等式约束非凸优化问题的PHR-ALM对于一般非凸优化问题的PHR-ALM参考文献 约束优化&#xff1a;PHR-ALM 增广拉格朗日函数法 基础预备&#xff1a; 约束优化&#xff1a;约束优化的三种…

matlab编写拉格朗日插值代码函数

要求&#xff1a;根据拉格朗日多项式插值法原理&#xff0c;设计算法流程并且编写拉格朗日插值代码函数。 代码如下&#xff1a; function[y]lagrange(x0,y0,x) %建立一个函数名为lagrange的函数&#xff0c;输入x0,y0为插值点的坐标&#xff0c;均为数组&#xff0c;x为要…

拉格朗日函数最优化问题

目的&#xff1a;将有约束条件的函数最优化问题通过拉格朗日函数转化为无条件的函数最优化问题。 条件极值最优化问题&#xff1a; 对于无条件的函数最优化问题&#xff0c;常用的有3种方式&#xff1a; 梯度下降&#xff1a;求解一阶导数&#xff0c;其实就是使用泰勒一阶展…

【深度学习】拉格朗日( Lagrange)中值定理

文章目录 1、定理2、几何意义3、证明思路4、有限增量定理5、推论1、定理 如果函数 f(x) 满足: 在闭区间[a,b]上连续; 在开区间(a,b)内可导。 那么在(a,b)内至少有一点ξ(a<ξ<b),使等式 : f(b)-f(a)=f′(ξ)(b-a) 成立,或: f′(ξ) =(f(b)-f(a)) / (b-a) 或存…

拉格朗日(lagrange)插值及其MATLAB程序

一、n次拉格朗日插值 根据《插值多项式的性质》中的定理6.1可得 其中&#xff08;6.19&#xff09;称为基函数&#xff0c;&#xff08;6.18&#xff09;称为拉格朗日多项式&#xff0c;用&#xff08;6.18&#xff09;计算插值称为拉格朗日多项式插值。 方法2&#xff1a;通过…

拉格朗日函数优化

等式约束最优化 可以写为&#xff1a; 引入拉格朗日乘子&#xff08;&#xff09;把问题转换成拉格朗日函数 因为对于任何可行解&#xff0c;有&#xff0c;所以有 &#xff0c;也就是&#xff0c;。 求解。对分别求的偏导数为零&#xff0c;得到方程组求解极值点&#xff0c…

增广拉格朗日函数法

增广拉格朗日函数法 在二次罚函数法中&#xff0c;为了保证可行性&#xff0c;罚因子必须趋于正无穷。此时&#xff0c;子问题因条件数爆炸而难以求解。那么&#xff0c;是否可以通过对二次罚函数进行某种修正&#xff0c;使得对有限的罚因子&#xff0c;得到的毕竟最优解也是…

增广拉格朗日函数法(ALM)

增广拉格朗日函数法&#xff08; Augmented Lagrangian method&#xff09; 一、等式约束 考虑问题&#xff1a; min ⁡ x f ( x ) s . t . c i ( x ) 0 , i 1 , ⋯ , m . \begin{array}{ll} \min_x &f(x)\\ s.t. &c_i(x) 0, \quad i1,\cdots,m. \end{array} min…

广义拉格朗日函数的理解

1、拉格朗日函数&#xff1a; 求极值 求函数f(x,y,z)在条件φ(x,y,z)0下的极值 方法&#xff08;步骤&#xff09;是&#xff1a; 1.做拉格朗日函数Lf(x,y,z)λφ(x,y,z),λ称拉格朗日乘数 2.求L分别对x,y,z,λ求偏导,得方程组,求出驻点P(x,y,z) 如果这个实际问题的最大或…

各种拉格朗日函数

目录 一&#xff0c;拉格朗日函数 二&#xff0c;部分拉格朗日函数 三&#xff0c;增广拉格朗日函数 一&#xff0c;拉格朗日函数 以三元函数为例&#xff1a; 求f(x,y,z)的极值&#xff0c;s.t.g(x,y,z)0 拉格朗日函数L(x,y,z,a) f(x,y,z) a * g(x,y,z) 在极值点处一…

拉格朗日函数相关推导

优化问题&#xff08;即高数中的求极值&#xff09;可分为三类&#xff1a;无约束、等式约束、不等式约束。 对于无约束的优化问题&#xff1a;求导&#xff0c;令导数为零即可求解。 等式约束优化&#xff1a;&#xff08;拉格朗日乘子法最开始就是求解等式约束优化的方法&…