一文讲解eBPF helper 函数的设计与实现

您是否想为内核添加一个新的 eBPF 辅助(helper)函数,但不知道从何入手?或者,您是否曾遇到过类似于 R2 type=ctx expected=fp, pkt, pkt_meta, map_value 的 eBPF verifier 报错?本文将从代码层面对 eBPF 辅助函数在内核中的设计与实现进行深入浅出的分析。相信在阅读本文后,您不仅能够轻松应对由于错误调用辅助函数导致的 eBPF verifier 问题,还能了解如何实现一个新的 eBPF 辅助函数。

本文首先简单介绍了 eBPF 辅助函数的概念,并探讨了其在内核中的设计,包括哪些重要组成部分。随后,结合辅助函数 bpf_perf_event_output 的实现,帮助读者了解实现一个 eBPF 辅助函数所需的要素。最后,我们通过一段代码分析了调用辅助函数时传入不匹配的参数类型导致 eBPF verifier 报错的问题。

简介

什么是 eBPF 辅助函数?eBPF 辅助函数是内核提供给开发者的接口。

为什么要有 eBPF 辅助函数呢?为什么不能像驱动一样直接调用内核函数呢?这主要是为了保证系统安全。由于 eBPF 程序运行在内核态,为了防止不当调用内核函数导致系统崩溃或安全漏洞,eBPF 程序只能调用内核提供的 eBPF 辅助函数。

截止目前内核共提供了 210 多个 eBPF 辅助函数,具体详细列表可见内核源码文件:include/uapi/linux/bpf.h

eBPF 辅助函数的设计

在内核中,struct bpf_func_proto 描述了 eBPF 辅助函数的定义、入参类型、返回值类型等重要信息。这些信息的指定主要是为了通过 eBPF verifier 的安全验证,确保传入数据的可靠性,避免传入错误的参数导致系统崩溃。struct bpf_func_proto 的具体形式的代码片段如下所示:

struct bpf_func_proto {//eBPF 辅助函数具体实现u64 (*func)(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5);bool gpl_only;bool pkt_access;bool might_sleep;// 返回类型enum bpf_return_type ret_type;union {// 参数类型struct {enum bpf_arg_type arg1_type;enum bpf_arg_type arg2_type;enum bpf_arg_type arg3_type;enum bpf_arg_type arg4_type;enum bpf_arg_type arg5_type;};enum bpf_arg_type arg_type [5];};union {// 当参数类型为 ARG_PTR_TO_BTF_ID,需要指明参数的 BTF 编号struct {u32 *arg1_btf_id;u32 *arg2_btf_id;u32 *arg3_btf_id;u32 *arg4_btf_id;u32 *arg5_btf_id;};u32 *arg_btf_id [5];struct {size_t arg1_size;size_t arg2_size;size_t arg3_size;size_t arg4_size;size_t arg5_size;};size_t arg_size [5];};// 返回参数的 BTF 编号int *ret_btf_id;bool (*allowed)(const struct bpf_prog *prog);
};

其中,func 表示该 eBPF 辅助函数的具体实现,实现了特定的功能。bpf_return_type 描述该 eBPF 辅助函数的返回参数类型,而 argx_type 描述该函数的入参类型。下面将对入参类型和返回值类型进行解析。

入参类型

入参类型分为基本类型和扩展类型。扩展类型在基本类型的基础上,添加了空指针类型,即允许入参为空指针。另外,当参数类型为 ARG_PTR_TO_BTF_ID 时,则需要在 struct bpf_func_proto 的成员 argx_btf_id 指明具体的 btf 编号。

注:BTF 编号可以看成内核数据类型的编号,通过该编号可以确定数据类型。

基本类型

基本类型大致包含三类:

  1. 指针类型,指针类型又可以进行细分:1)具体类型的指针类型,如 ARG_PTR_TO_SOCKET 表示 struct socket 指针;2)由 BTF 编号确定数据类型的指针类型,如 ARG_PTR_TO_BTF_ID 表示某一内核数据类型指针,且该内核数据类型由 BTF 编号指定;3)指向某一类型内存的指针,如 ARG_PTR_TO_MAP_KEY 指向 eBPF 程序栈内存的指针。
  2. 整数类型,如 ARG_CONST_SIZE 表示整数,且该整数的值不能为 0;
  3. 任意类型,即 ARG_ANYTHING,其表示任意类型,但是需要初始化该值,否则 eBPF verifier 会报 未初始化 等相关错误。

完整的基本类型如下表所示:

扩展类型

包含的扩展类型如下表所示:

返回值类型

同参数类型类似,返回值类型也分为基本类型和扩展类型。扩展类型也是在基本类型的基础添加了空指针类型。

  资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linuxc/c++高级开发【直播公开课】

零声白金VIP体验卡:零声白金VIP体验卡(含基础架构/高性能存储/golang/QT/音视频/Linux内核)

基本类型

扩展类型

扩展类型是在基本类型的基础上,添加了空指针类型,表示返回值可能是空指针,那么 eBPF verifier 需要考虑针对空指针进行安全验证。

eBPF 辅助函数的实现

本小节以 bpf_perf_event_output 为例介绍 eBPF 辅助函数的实现。eBPF 辅助函数 bpf_perf_event_output 是应用最广泛的一个,其主要功能是将数据通过 perf 缓冲区传送给用户态程序。实现 bpf_perf_event_output 需要完成以下三个步骤:

  1. 定义 struct bpf_func_proto 结构体,为 bpf_perf_event_output 辅助函数指定功能函数、参数类型、返回值类型等;
  2. 为 bpf_perf_event_output 辅助函数分配唯一的编号;
  3. 将 bpf_perf_event_output 与特定的 eBPF 程序类型绑定,以确保只有该类型的程序才能调用该辅助函数。

定义 struct bpf_func_proto

BPF_CALL_5 (bpf_perf_event_output, struct pt_regs *, regs, struct bpf_map *, map,u64, flags, void *, data, u64, size)
{......return err;
}static const struct bpf_func_proto bpf_perf_event_output_proto = {.func  = bpf_perf_event_output,.gpl_only = true,.ret_type = RET_INTEGER,.arg1_type = ARG_PTR_TO_CTX,.arg2_type = ARG_CONST_MAP_PTR,.arg3_type = ARG_ANYTHING,.arg4_type = ARG_PTR_TO_MEM | MEM_RDONLY,.arg5_type = ARG_CONST_SIZE_OR_ZERO,
};

bpf_perf_event_output 的入参类型分别是:

  1. ARG_PTR_TO_CTXstruct pt_regs 指针
  2. ARG_CONST_MAP_PTRstruct bpf_map 指针
  3. ARG_ANYTHING:任意类型,且数值已初始化
  4. ARG_PTR_TO_MEM | MEM_RDONLY: 指向栈、报文或 eBPF map 元素值的指针
  5. ARG_CONST_SIZE_OR_ZERO: 整数且该整数值可为 0

返回值类型是整数类型:RET_INTEGER

添加编号

在完成 struct bpf_func_proto 的定义之后,需要为其分配一个唯一的编号。下面的代码片段通过将其扩展为 BPF_FUNC_perf_event_output 宏定义,并将该辅助函数的编号设置为 25,即 #define BPF_FUNC_perf_event_output 25

注:该代码片段位于内核源文件:include/uapi/linux/bpf.h

#define ___BPF_FUNC_MAPPER (FN, ctx...) FN (unspec, 0, ##ctx)    \......FN (perf_event_output, 25, ##ctx)  \......

绑定 eBPF 程序类型

最后一步是要指定允许调用该辅助函数的 eBPF 程序类型。例如,下面的代码片段中,允许 BPF_PROG_TYPE_KPROBE 类型的 eBPF 程序调用 bpf_perf_event_output 辅助函数。如果未指定允许调用该辅助函数的程序类型的 eBPF 程序调用了该辅助函数,则在 eBPF 程序加载过程会出现类似于 unknown func bpf_perf_event_output#25 的 eBPF verifier 错误提示。

static const struct bpf_func_proto *
kprobe_prog_func_proto (enum bpf_func_id func_id, const struct bpf_prog *prog)
{switch (func_id) {case BPF_FUNC_perf_event_output:return &bpf_perf_event_output_proto;......default:return bpf_tracing_func_proto (func_id, prog);}
}
const struct bpf_verifier_ops kprobe_verifier_ops = {.get_func_proto  = kprobe_prog_func_proto,  // 验证改类型的 eBPF 程序是否可调用 func_id 所代表的辅助函数.is_valid_access = kprobe_prog_is_valid_access,
};

小试牛刀

在理解了上述的理论知识后,我们可以来看看如何定位并解决开篇提到的问题:R2 type=ctx expected=fp, pkt, pkt_meta, map_value。下面是引起该错误的代码示例,读者可以分析该代码存在哪些问题以及如何解决这些问题。

struct
{__uint (type, BPF_MAP_TYPE_HASH);__type (key, struct sock *);__type (value, struct sockmap_val);__uint (max_entries, 1024);
} sockmap SEC (".maps");struct sockmap_val
{int nothing;
};SEC ("tracepoint/tcp/tcp_rcv_space_adjust")
int tp__tcp_rcv_space_adjust (struct trace_event_raw_tcp_event_sk *ctx)
{struct sockmap_val *sv = bpf_map_lookup_elem (&sockmap, &ctx->skaddr);if (sv)bpf_printk ("% d\n", sv->nothing);return 0;
}

问题解析

首先解释一下错误信息 R2 type=ctx expected=fp, pkt, pkt_meta, map_value 的含义。该错误表示 R2 寄存器的数据类型应该是指向栈内存的指针、报文指针、或者 eBPF map 的元素值指针,但实际数据类型是 ctx,即指向 struct pt_regs 的指针。因此,该问题实际上是因为数据类型不匹配引起的。

在调用 bpf_map_lookup_elem (&sockmap, &ctx->skaddr) 函数时,我们传递的参数 &ctx->skaddr 是 ctx 类型参数,而不是 fp 类型参数。那么为什么会有这个限制呢?

根据上文所述,eBPF 辅助函数的入参类型是通过 struct bpf_func_proto 进行定义的。我们可以参考 bpf_map_lookup_elem 辅助函数在内核代码中的实现来解释这个问题。在该函数的代码片段中,可以看到它的第二个入参类型为 ARG_PTR_TO_MAP_KEY,即指向 eBPF 程序栈内存的指针,也就是 fp。

const struct bpf_func_proto bpf_map_lookup_elem_proto = {.func  = bpf_map_lookup_elem,.gpl_only = false,.pkt_access = true,.ret_type = RET_PTR_TO_MAP_VALUE_OR_NULL,.arg1_type = ARG_CONST_MAP_PTR,.arg2_type = ARG_PTR_TO_MAP_KEY,
};

解决方案

针对这个问题,一般的解决方法是先定义一个栈变量,将 ctx->skaddr 的值存储到栈上,例如 u64 skaddr = ctx->skaddr,然后在调用 bpf_map_lookup_elem 函数时,将该栈变量的地址 &skaddr 作为函数的参数传递进去。

总结

本文重点介绍了 eBPF 辅助函数在内核中的设计,并描述了参数类型、返回值类型等重要概念。以 bpf_perf_event_output 为例,介绍了实现一个 eBPF 辅助函数的核心要素。eBPF 辅助函数在开发 eBPF 程序中扮演着重要的角色,深入地了解 eBPF 辅助函数的设计和实现可以帮助解决开发过程中的许多相关问题。

原文作者:酷玩BPF

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

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

相关文章

“一键导出,高效整理:将之前的部分记录导出!“

亲爱的朋友们,你们是否曾经为了导出之前的记录而感到烦恼?冗长的过程,无法精确控制的选项,实在让人感到心力交瘁。但现在,我们为你带来一种全新的解决方案,让你的工作更轻松,更高效!…

功率放大器应用领域分享:微流控细胞分选在“软骨”芯片关节炎治疗研究中的应用

微流控技术是一种通过微小的通道和微型装置对流体进行精确操控和分析的技术,它是现代医学技术发展过程中的一种重要的生物医学工程技术,具有广泛的应用前景和重要性,它在高通量分析、个性化医疗、细胞筛选等方面有着巨大的潜力,Ai…

Sui生态多家协议上线流动质押,兼顾收益与灵活性

在Sui上,流动质押协议允许DeFi用户质押SUI,并获得可交易或用于其他DeFi活动的流动质押标记token。这一过程绕过了传统质押中验证节点锁定token的问题。用户可以通过Sui的权益证明机制(PoS)确保网络的安全,同时参与生态…

k8s无法删除pv,pvc问题

问题: 在k8s里面创建了pv,pvc删除时报错:error: resource(s) were provided, but no name was specified 解决: 正确的删除顺序:1.先删除pod2.再删除pv 3.在删除pvc 删除pv,pvc命令: kubect…

【图像分类】【深度学习】【Pytorch版本】Inception-ResNet模型算法详解

【图像分类】【深度学习】【Pytorch版本】Inception-ResNet模型算法详解 文章目录 【图像分类】【深度学习】【Pytorch版本】Inception-ResNet模型算法详解前言Inception-ResNet讲解Inception-ResNet-V1Inception-ResNet-V2残差模块的缩放(Scaling of the Residuals)Inception-…

图解算法数据结构-LeetBook-栈和队列04_望远镜中最高的海拔_滑动窗口

科技馆内有一台虚拟观景望远镜,它可以用来观测特定纬度地区的地形情况。该纬度的海拔数据记于数组 heights ,其中 heights[i] 表示对应位置的海拔高度。请找出并返回望远镜视野范围 limit 内,可以观测到的最高海拔值。 示例 1: 输…

为什么需要MuleSoft?如何与Salesforce协同工作?

MuleSoft通过一个集成平台将应用程序及其数据(跨云和内部云)连接起来。这被称为iPaaS,可将云应用程序相互集成,以及与本地和传统应用程序集成。 MuleSoft非常适合希望过渡到云的组织,提供了一种强大的集成解决方案。随着组织越来越依赖云及其…

CV计算机视觉每日开源代码Paper with code速览-2023.11.17

点击CV计算机视觉,关注更多CV干货 论文已打包,点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【点云分割】(CVPR2023)Center Focusing Network for Real-Time LiDAR Panoptic Segmentation 论文地址:…

第2关:图的深度遍历

任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务:以邻接表存储图,要求编写程序实现图的深度优先遍历。 相关知识 图的深度优先遍历类似于树的先序遍历, 是树的先序遍历的推广,其基本思想如下: 访…

CFCA证书——基于SM2/3算法的安全信任

在中国金融认证中心(CFCA)发行的证书中,采用了最新的国密SM2/3算法来提供更高的安全保障。这一创新举措进一步增强了我国网络安全能力,并为用户提供了一种更可靠、更安全的选择。 SM2/3算法是中国自主研发的非对称加密算法&#…

2023年亚太杯数学建模思路 - 案例:感知机原理剖析及实现

文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法,其…

基于FPGA的五子棋(论文+源码)

1.系统设计 在本次设计中,整个系统硬件框图如下图所示,以ALTERA的FPGA作为硬件载体,VGA接口,PS/2鼠标来完成设计,整个系统可以完成人人对战,人机对战的功能。系统通过软件编程来实现上述功能。将在硬件设计…

真菌DAP-seq|丝状真菌中与碳利用相关的调控和转录景观

转录因子 (Transcription Factors, TFs)是指能够以序列特异性方式结合DNA并且调节转录的蛋白质。TF与特异性DNA序列结合调节转录,同时会和其它功能蛋白结合调控下游基因的转录和翻译过程,也会和增强子等其它顺式作用元件结合,使整个调控过程更…

时间序列预测中的4大类8种异常值检测方法(从根源上提高预测精度)

一、本文介绍 本文给大家带来的是时间序列预测中异常值检测,在我们的数据当中有一些异常值(Outliers)是指在数据集中与其他数据点显著不同的数据点。它们可能是一些极端值,与数据集中的大多数数据呈现明显的差异。异常值可能由于…

linux在anaconda环境中配置GPU版本的cuda+cudnn+pytorch深度学习环境(简单可行!一次完成!)

一、安装前要知道的事情: pytorch是基于CUDA的深度学习框架,因此,pytorch的版本必须依赖于cuda toolkit的版本CUDA Toolkit可以理解成一个工具包,主要包含了CUDA-C和CUDA-C编译器、一些科学库和实用程序库、CUDA和library API的代…

postman查询数据库-Xmysql

步骤1:安装node.js 下载地址:Download | Node.js步骤2:安装Xmysql工具,命令行输入 npm install -g xmysql ,过程中会自动安装相关依赖;步骤3:连接数据库 xmysql -h ip -u 账号 -p 密码 -d 库名 如下表示连…

【Proteus仿真】【Arduino单片机】多功能数字时钟设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器,使用PCF8574、LCD1602液晶、DS1302温度传感器、DS1302时钟、按键、蜂鸣器等。 主要功能: 系统运行后,LCD1602显示当前日期…

【数据结构初阶(3)】双向带头结点循环链表

文章目录 Ⅰ 概念及结构Ⅱ 基本操作实现1. 结点的定义2. 创建头节点3. 创建新结点4. 双向链表销毁5. 双向链表打印6. 双向链表尾插7. 双向链表尾删8. 双向链表头插9. 双向链表头删10. 双向链表查找11. 在指定 pos 位置前插入新结点12. 删除指定 pos 位置的结点 Ⅲ 十分钟手搓链…

​vmware虚拟机ubuntu系统配置静态ip​

把虚拟机当成服务器,如果虚拟机的ip是一直变化的,每次远程连接需要都修改连接虚拟机的ip地址,这肯定是麻烦的。 一、设置一下本机的VMnet8的ip 配置路径:控制面板->所有控制面板项->网络和共享中心 二、首先设置NAT 选自…

数据结构【DS】树的性质

度为m的树 m叉树 至少有一个节点的度m 允许所有节点的度都<m 一定是非空树&#xff0c;至少有m1个节点 可以是空树 节点数 总度数 1m叉树&#xff1a; 高度为h的m叉树 节点数最少为&#xff1a;h具有n个结点的m叉树 最大高度&#xff1a;n度为m的树&#xff1a; 具有…