当前位置: 首页 > article >正文

ebpf: CO-RE, BTF, and Libbpf(二)

本文内容主要来源于Learning eBPF,可阅读原文了解更全面的内容。
本文涉及源码也来自于书中对应的github:https://github.com/lizrice/learning-ebpf/

概述

上篇文章主要讲了CO-RE最关键的一环:BTF,了解其如何记录内核中的数据结构和函数信息。本文将介绍如何编写一个插入到内核中的 eBPF 程序。
示例代码使用 C 语言,编译器是 clang, 另外还需要 libbpf 库(提供一些 ebpf 程序常用的宏和函数定义)。

CO-RE eBPF 程序

一个完整的 eBPF 程序分为两个部分:用于实现具体函数功能的kernel 层的代码,文件后缀是 .bpf.c;以及用于控制 ebpf 代码的加载,卸载,生命周期等控制逻辑的用户层代码,文件后缀是.c。我们先主要看下 kernel 层代码如何编写。

下图为代码流程总览,可以看到和常规 C 程序大致相同,只是其中一些函数、结构体定义会有差异,下面将详细讲解每一部分。
在这里插入图片描述

头文件

如常规 C 文件一样,ebpf 程序也需要包含头文件,但与常规 C 程序相比要简单很多。因为内核通用的头文件都已经包含在生成的vmlinux.h了,所以对于需要用到的内核函数,我们只需要包含这一个头文件!此外,就是 bpf 需要用到的一些辅助函数的头文件,以及我们自己实现的头文件。
这里需要注意的是,vmlinux.h 并不会包含#define定义的值,所以如果需要的话需要额外包含,本文不会涉及到此类情况。

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "hello-buffer-config.h"

定义Maps

这一部分主要是定义我们需要用到的结构体,其中有两个map,第一个是 arrary 类型的 output, 另外一个是 hash 类型的 my_config. 其中会说明map中 key, value 的类型, 大小, map 最多容纳的数量等属性.

struct {__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);__uint(key_size, sizeof(u32));__uint(value_size, sizeof(u32));
} output SEC(".maps");struct user_msg_t {char message[12];
};struct {__uint(type, BPF_MAP_TYPE_HASH);__uint(max_entries, 10240);__type(key, u32);__type(value, struct user_msg_t);
} my_config SEC(".maps");

和我们之前见到的 C 程序不同的是, 每个结构体中都用宏定义来描述其类型, 属性. 如 __uint, __type 等. 这些宏定义在 bpf_helpers.h 中, 如下所示:

#define __uint(name, val) int (*name)[val]
#define __type(name, val) typeof(val) *name
#define __array(name, val) typeof(val) *name[]

使用这些宏定义会有更好的可读性.

eBPF 程序段 (Sections)

libbpf 要求 eBPF 程序需要用 SEC() 宏来定义其程序类型, 如:

SEC("kprobe")

这将会在程序最终编译生成的 elf 对象中保留一个名为 kprobe 的 section, 以便 libbpf 知道应该将这个程序加载为 BPF_PROG_TYPE_KPROBE 类型. eBPF 程序的类型定义在 include/uapi/linux/bpf.h 中, 如下图所示.
我们可以看到一些 kernel 中常见的一些机制, 如kprobe, tracepoint, perf event, cgroup 等.
eBPF 程序都可以附加在这些功能上, 用来捕获内核中程序的运行状况.
在这里插入图片描述
还可以用 SEC() 来指名我们需要将 eBPF 程序附加在什么事件上, 然后 libbpf 会自动帮我们处理, 而不用我们在函数中设定. 例如: 如果我们需要在 arm64 架构中, 将 eBPF 程序附加在 execve 系统调用的 kprobe 上, 只需要这样:

SEC("kprobe/__arm64_sys_execve")

当然, 这需要我们熟悉当前架构的系统调用函数名, libbpf 对此做了优化, 我们只需要用 ksyscall section, libbpf 就会自动找到当前架构对应的系统调用, 如:

SEC("ksyscall/execve")

eBPF 函数代码

接下来我们看实际函数功能代码

SEC("ksyscall/execve")
int BPF_KPROBE_SYSCALL(hello, const char *pathname)
{struct data_t data = {};  //用来保存最终要输出的信息struct user_msg_t *p;  //保存commanddata.pid = bpf_get_current_pid_tgid() >> 32;   //获取触发ebpf程序的piddata.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;   //获取触发ebpf程序的uidbpf_get_current_comm(&data.command, sizeof(data.command));   //获取当前commandbpf_probe_read_user_str(&data.path, sizeof(data.path), pathname);   //获取当前执行程序的路径p = bpf_map_lookup_elem(&my_config, &data.uid);  //从my_config map中寻找uid对应的messageif (p != 0) {/*如果map中有对应的值,则输出map中的message*/bpf_probe_read_kernel_str(&data.message, sizeof(data.message), p->message);} else {/*否则输出默认值: hello world*/bpf_probe_read_kernel_str(&data.message, sizeof(data.message), message); }/*将结果输出到perf buffer缓冲区中*/bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));   return 0;
}

data_t 定义在 hello-buffer-config.h 中, 如图:
在这里插入图片描述
注意: eBPF 对于代码有严格的检查, 以防止其损坏内核的稳定性. 因此 eBPF 程序中是不能直接用常规方式访问内存的, (x = p->y这种当然是不允许的) , 需要通过BPF 辅助函数, 如 bpf_probe_read_*() 函数家族.
(当然, 还有一个非常重要的原因是, 为了支持 CO-RE, 内存访问相关的代码很可能由于内核版本的不同而需要重定位, 因为有些结构体成员有变动)

另外, 对于连续的指针访问, 如 d = a->b->c->d , 我们可以用如下方式:

bpf_core_read(&b, 8, &a->b)
bpf_core_read(&c, 8, &b->c)
bpf_core_read(&d, 8, &c->d)

但是, libbpf 对此封装了更好用的宏:

d = BPF_CORE_READ(a, b, c, d);

编译 eBPF 程序

接下来我们介绍如何编写 Makefile, 以及需要添加那些选项, 以便我们可以编译出适合 CO-RE/libbpf 的 ebpf 程序.

调试信息

我们需要给编译器传入 -g 标签, 以便最终生成的二进制文件中包含调试信息, (当然, 最重要的是包含了 BTF 信息). 然而, -g 选项会同时包含 DWARF 调试信息, 这部分对于 eBPF 程序来说是不需要的, 可以去除这部分信息来减小最终生成对象的大小

llvm-strip -g <object file>

优化选项

需要将编译器优化选项设置为 -O2 (或更高的优化等级), 这样最终生成的 BPF 字节码才能通过 eBPF 验证程序. 例如: 如果没有 -O2 选项的话, Clang 编译器处理辅助函数的调用代码时, 会默认生成 output callx <register>, 但是 eBPF 程序时不支持直接从寄存器调用函数地址的.

目标架构

如果要使用 libbpf 中定义的一些宏, 我们需要在编译时指定目标机器的架构. 因为一些函数或结构体是和体系架构强相关的. 例如我们程序中用到的 BPF_KPROBE_SYSCALL 中, 需要传入一个 pt_regs 类型的参数, 就需要读取包含 CPU 寄存器的内容, 必须要知道程序运行在哪个体系架构中. 部分代码截图如图所示:
在这里插入图片描述
此外, 及时没有使用任何宏, 也仍然需要用架构特定代码来访问寄存器信息, 所以 CO-RE 实际上应该是 (compile once per architecture, run everywhere)

Makefile

Makefile 中编译 ebpf 内核部分的程序如下所示:

%.bpf.o: %.bpf.c vmlinux.h# 使用clang编译BPF程序clang \-target bpf \               # 指定目标为BPF-D __TARGET_ARCH_$(ARCH) \  # 定义目标架构-Wall \                    # 启用所有警告-O2 -g \                   # O2优化等级,添加调试信息-o $@ -c $<                # 输出目标文件和编译# 去除DWARF调试信息(减小文件大小)llvm-strip -g $@

object 文件中的 BTF 信息

可以通过 readelf 工具来查看 object 文件中的信息.
如下图所示, readelf -S 可以查看 object 文件中的 section, 红框部分可以看到, 已经包含了我们所需要的 BTF 信息.
在这里插入图片描述
然后, 可以用 bpftool 来查看文件中包含的所有 BTF 信息, 和之前看到的一样
btf dump

BPF 重定位

libbpf 需要在 eBPF 程序运行时能根据不同的内核数据结构来做适配, 因此需要在编译过程中生成 BPF CO-RE 重定位信息.

通过指令 bpftool -d prog load hello-buffer-config.bpf.o /sys/fs/bpf/hello 可以查看加载 BPF 程序时的重定位过程, 其中与重定位有关的部分如下:
BPF relocations
可以看到, 在程序的 BTF 信息中, pt_regs BTF id 为 22, 在 vmlinux 中找到了对应的结构体, id 为7. 不过, 由于我这里编译和运行是在同一个机器上, 所以不管是数据还是指令, 其修正的 offset 都是 0 .

在上述示例中, 我们是手动将 eBPF 程序加载进内核中的, 这当然不是一个好方法, 后续我们将写用户空间的程序, 来让其完成 eBPF 程序的加载工作. 另外还有一些其他的工作要做, 例如错误处理, 控制程序生命周期等.

相关文章:

ebpf: CO-RE, BTF, and Libbpf(二)

本文内容主要来源于Learning eBPF&#xff0c;可阅读原文了解更全面的内容。 本文涉及源码也来自于书中对应的github&#xff1a;https://github.com/lizrice/learning-ebpf/ 概述 上篇文章主要讲了CO-RE最关键的一环&#xff1a;BTF&#xff0c;了解其如何记录内核中的数据结…...

祁连山国家公园shp格式数据

地理位置 祁连山国家公园位于中国西北部&#xff0c;横跨甘肃省与青海省交界处&#xff0c;主体处于青藏高原东北边缘。总面积约5.02万平方公里&#xff0c;是中国首批设立的10个国家公园之一。 设立背景 保护措施 文化与历史 旅游与教育 意义与挑战 祁连山国家公园的设立标志…...

《AI大模型应知应会100篇》 第16篇:AI安全与对齐:大模型的灵魂工程

第16篇&#xff1a;AI安全与对齐&#xff1a;大模型的灵魂工程 摘要 在人工智能技术飞速发展的今天&#xff0c;大型语言模型&#xff08;LLM&#xff09;已经成为推动社会进步的重要工具。然而&#xff0c;随着这些模型能力的增强&#xff0c;如何确保它们的行为符合人类的期…...

探索QEMU-KVM虚拟化:麒麟系统下传统与云镜像创建虚拟机的最佳实践

随着云计算和虚拟化技术的不断进步&#xff0c;虚拟化在管理服务器、隔离资源以及提升性能方面的好处越来越明显。麒麟操作系统Kylin OS是我们国家自己开发的操作系统&#xff0c;在政府机构和企业中用得很多。这篇文章会教你如何在麒麟操作系统上设置QEMU-KVM虚拟化环境&#…...

[ComfyUI] 最新控制模型EasyControl,吉卜力风格一键转绘

一、EasyControl介绍 玩ComfyUI的都知道Controlnet的重要性&#xff0c;可以根据约束来引导图片的生成&#xff0c;这也是ComfyUI商业化里面很重要的一环。 不过之前我们用的Controlnet都是基于Unet技术框架下的。 最近出的这个EasyControl有点不一样&#xff0c;是基于DiT&a…...

项目执行中的目标管理:从战略到落地的闭环实践

——如何让目标不“跑偏”、团队不“掉队”&#xff1f; 引言&#xff1a;为什么目标管理决定项目成败&#xff1f; 根据PMI研究&#xff0c;47%的项目失败源于目标模糊或频繁变更。在复杂多变的项目环境中&#xff0c;目标管理不仅是制定KPI&#xff0c;更是构建“方向感-执行…...

《计算机视觉度量:从特征描述到深度学习》—深度学习工业检测方案评估

谢谢各位粉丝的支持&#xff0c;过去了一年多才再次更新技术博客。原因是个人家庭和技术发展在这短短一年多&#xff0c;发生了很大变化。本人身为技术博主&#xff0c;也在不断的探索和研究新技术在工业检测领域的技术方案。 并在这期间已经完成了基础的工业检测大模型的设计…...

网页防篡改与盗链防护:实时监控与自动化修复实践

摘要&#xff1a;针对网页内容篡改与盗链问题&#xff0c;本文基于群联AI云防护系统&#xff0c;详解如何通过哈希校验、实时监控与CDN联动实现秒级修复&#xff0c;并提供Python与AWS S3集成代码。 一、网页安全的核心需求 防篡改&#xff1a;保障页面内容完整性&#xff0c;…...

LR(0)

LR0就是当我处在自动机为红色这些结束状态的时候&#xff0c;这些红色状态就代表我们识别到了一个句柄&#xff0c;那现在的问题就是识别到了句柄&#xff0c;那要不要对他进行归约&#xff1f;LR0就是我不管当前指针指向的终结符是什么&#xff0c;我都拿它做规约 这里的二号状…...

鸿蒙开发-页面跳转

1.路由使用 //1.引入路由 import router from ohos.router//2.使用跳转router.pushUrl({url: "pages/Show"})2.页面跳转 import { router } from kit.ArkUI;Entry Component struct LoginPage {State message: string 登陆页;build() {Row() {Column() {Text(this…...

#MES系统中的一些相关的名词

&#x1f4cc;MES系统 部分 术语表 缩写英文全称中文名称详细解释MESManufacturing Execution System制造执行系统用于连接计划系统与生产现场&#xff0c;实时管理和控制整个生产过程&#xff0c;覆盖物料、人员、设备、质量、指令等。ERPEnterprise Resource Planning企业资…...

无人船 | 图解基于视线引导(LOS)的无人艇制导算法

目录 1 视线引导法介绍2 LOS制导原理推导3 Lyapunov稳定性分析4 LOS制导效果 1 视线引导法介绍 视线引导法&#xff08;Line of Sight, LOS&#xff09;作为无人水面艇&#xff08;USV&#xff09;自主导航领域的核心技术&#xff0c;通过几何制导与动态控制深度融合的机制&am…...

LeetCode LCR157 套餐内商品的排列顺序

生成字符串的全部排列&#xff08;去重&#xff09;&#xff1a;从问题到解决方案的完整解析 问题背景 在编程和算法设计中&#xff0c;生成字符串的所有排列是一个经典问题。它不仅出现在算法竞赛中&#xff0c;也在实际开发中有着广泛的应用&#xff0c;比如生成所有可能的…...

3.2.2.3 Spring Boot配置拦截器

在Spring Boot应用中配置拦截器&#xff08;Interceptor&#xff09;可以对请求进行预处理和后处理&#xff0c;实现如权限检查、日志记录等功能。通过实现HandlerInterceptor接口并注册到Spring容器&#xff0c;拦截器可以自动应用到匹配的请求路径。案例中&#xff0c;创建了…...

cryptozombies合约6

我们就快完成我们的随机僵尸制造器了&#xff0c;来写一个公共的函数把所有的部件连接起来。 写一个公共函数&#xff0c;它有一个参数&#xff0c;用来接收僵尸的名字&#xff0c;之后用它生成僵尸的DNA。 实战演习 创建一个 public 函数&#xff0c;命名为 createRandomZom…...

大模型文生图

提示词分4个部分&#xff1a;质量&#xff0c;主体&#xff0c;元素&#xff0c;风格 质量&#xff1a;杰作&#xff0c;高质量&#xff0c;超细节&#xff0c;完美的精度&#xff0c;高分辨率&#xff0c;大师级的&#xff1b; 权重&#xff1a;把图片加括号&#xff0c;&am…...

.NET MCP 示例

服务器端示例 基础服务器 以下是一个基础的 MCP 服务器示例&#xff0c;它使用标准输入输出&#xff08;stdio&#xff09;作为传输方式&#xff0c;并实现了一个简单的回显工具&#xff1a; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.H…...

daz dForce to UE 的原理分析

dForce是物理模拟&#xff0c;不是关键帧动画&#xff1a; dForce是一个物理引擎。当你运行模拟时&#xff0c;Daz Studio会根据你设置的物理属性&#xff08;如裙子的重量、布料的硬度、摩擦力&#xff09;、环境因素&#xff08;如重力、风力&#xff09;以及与角色的碰撞&am…...

LeetCode 118题解 | 杨辉三角

题目链接: https://leetcode.cn/problems/pascals-triangle/description/ 题目如下&#xff1a; 解题过程如下&#xff1a; 杨辉三角就是一个不规则的二维数组&#xff0c;实际上是一个直角三角形。如图所示&#xff1a; 杨辉三角特点&#xff1a;每一行的第一个和最后一个都是…...

『Kubernetes(K8S) 入门进阶实战』实战入门 - Pod 详解

『Kubernetes(K8S) 入门进阶实战』实战入门 - Pod 详解 Pod 结构 每个 Pod 中都可以包含一个或者多个容器&#xff0c;这些容器可以分为两类 用户程序所在的容器&#xff0c;数量可多可少Pause 容器&#xff0c;这是每个 Pod 都会有的一个根容器&#xff0c;它的作用有两个 可…...

裂缝检测数据集,支持yolo,coco json,pasical voc xml,darknet格式的标注,1673张原始训练集图片,正确识别率99.4%

数据集详情: 裂缝检测数据集,支持yolo,coco json,pasical voc xml,darknet格式的标注,1673张原始训练集图片,正确识别率99.4% 2394总图像 数据集分割 训练集占比 70% 1673图片 有效集20% 477图片 测试集...

数据库索引深度解析:原理、类型与高效使用实践

&#x1f9e0; 一句话理解索引是什么&#xff1f; 索引就是数据库中的“目录”或“书签”&#xff0c;它能帮助我们快速找到数据的位置&#xff0c;而不是一页页地翻整本书。 &#x1f9e9; 一、为什么需要索引&#xff1f;&#xff08;用生活化例子秒懂&#xff09; 想象你在…...

React 记账本项目实战:多页面路由、Context 全局

在本文中,我们将分享一个使用 React 开发的「记账本」项目的实战经验。该项目通过 VS Code 完成,包含首页、添加记录页、编辑页等多个功能页面,采用了 React Router 实现路由导航,使用 Context API 管理全局的交易记录状态,并引入数据可视化组件呈现不同月份的支出情况。项…...

易路iBuilder智能体平台:人力资源领域AI落地,给“数据权限管控”一个最优解

近日&#xff0c;加拿大电子商务巨头Shopify的CEO Tobias Ltke分享了一份内部备忘录&#xff0c;明确表示有效使用AI已成为公司对每位员工的基本期望&#xff0c;并指出&#xff1a;各团队在招募新员工前&#xff0c;必须先确定是否能够利用AI完成工作。 而在全球范围内&#…...

【3】k8s集群管理系列--包应用管理器helm之chart资源打包并推送到harbor镜像仓库

一、chart资源打包 helm package ./web-chart # 当前目录会生成一个tgz的压缩文件二、安装help push插件&#xff08;用于推送前面打包的文件&#xff0c;到镜像仓库&#xff09; .1 下载help-push二进制文件 wget https://github.com/chartmuseum/helm-push/releases/down…...

用 Python 从零构建异步回显服务器

简介 让我们从 0 开始&#xff0c;搭建一个异步服务输出服务器。 套接字 套接字&#xff08;socket&#xff09;&#xff0c;是不同计算机中实现通信的一种方式&#xff0c;你可以理解成一个接口&#xff0c;它会在客户端和服务端建立连接&#xff0c;一台发送数据&#xff…...

mybatis--多对一处理/一对多处理

多对一处理&#xff08;association&#xff09; 多个学生对一个老师 对于学生这边&#xff0c;关联&#xff1a;多个学生&#xff0c;关联一个老师[多对一] 对于老师而言&#xff0c;集合&#xff0c;一个老师有多个学生【一对多】 SQL&#xff1a; 测试环境搭建 1.导入依…...

QT Sqlite数据库-教程002 查询数据-上

【1】DQL语句&#xff1a; DQL语句&#xff08;数据查询语言&#xff09;&#xff0c;用来查询数据记录。DQL 基本结构由 SELECT FROM、WHERE、JOIN 等子句构成。DQL 语句并不会改变数据库&#xff0c;而是让数据库将查询结果发送结果集给客户端&#xff0c;返回的结果是一张虚…...

Kubernetes Operator 是什么,以及它们的用途

最近一个朋友问我关于EKS的升级问题&#xff1a; 场景&#xff1a; 如果我有 100 个 EKS 集群需要升级&#xff0c;那么所有集群都安装了安全插件。由于我不想在升级后手动在每个EKS中重复安装此插件&#xff0c;如何实现EKS升级后自动安装这个安全插件&#xff1f; 答案有多…...

鸿蒙开发08-泛型类型和函数

泛型类型和函数允许创建的代码在各种类型上运行&#xff0c;而不仅支持单一类型&#xff0c;类型参数是一种特殊的变量&#xff0c;它代表类型而不是值。 泛型函数的基本语法如下&#xff1a; function 函数名<类型参数>(参数: 类型参数): 类型参数 {// 函数体return 参…...