bpftime(为什么要有,介绍,原理图),如何编译运行其代码,示例代码(运行结果+解释+内核层代码,用户层代码分析)

目录

bpftime(开源用户态 eBPF 运行时)

引入

在内核态实现用户态追踪的性能损失 

内核空间执行ebpf的弊端

内核态 -> 用户态

介绍 

原理图 

示例代码

如何编译和运行 

编译

运行

运行结果

运行结果

代码分析 

.c

源码

语法

#include "malloc.skel.h"

LIBBPF_OPTS(bpf_uprobe_opts, attach_opts, .func_name = "malloc",            .retprobe = false);

bpf_program__attach_uprobe_opts 

bpf_map__fd()

bpf_map_get_next_key(fd, prev_key, &key) 

bpf_map_delete_elem()

.bpf.c

源码

​​​​​​​


bpftime(开源用户态 eBPF 运行时)

参考 -- bpftime: 让 eBPF 从内核扩展到用户空间 - 知乎 (zhihu.com),用户空间 eBPF 运行时:深度解析与应用实践 - 知乎 (zhihu.com)

引入

在内核态实现用户态追踪的性能损失 

ebpf中的Uprobe 是一种强大的用户级动态追踪机制,它允许开发者在用户空间的程序中进行动态插入探测点

  • eg:在函数的入口点、特定的代码偏移位置,以及函数的返回点

这种技术的实现是通过在指定的位置设置断点

  • 例如在 x86 架构上使用 int3 指令:
  • 当执行流到达这一点时,程序会陷入内核,触发一个事件,随后执行预定义的探针函数,完成后返回用户态继续执行
  • 这种动态追踪方法,能够在系统范围内跟踪和插桩所有执行特定文件的进程 -- 即允许在不修改代码、重新编译或重启进程的情况下,收集性能分析和故障诊断的关键数据

但是,eBPF 虚拟机是在内核态执行

  • kprobe实现并不会被影响什么
  • 但当前的 uprobe插入的探测点是在用户空间内的程序 -> 会在内核中引入两次上下文切换,造成了显著的性能开销(特别是在延迟敏感的应用中这种开销会严重影响性能)
  • 和 Kprobe 相比,Uprobe 的开销接近十倍:

另一方面,Uprobe 目前也仅限于追踪,无法修改用户态函数的执行流程,或者修改函数的返回值

  • 这也限制了 Uprobe 的使用场景,无法进行代码扩展、热补丁、缺陷注入等操作

内核空间执行ebpf的弊端

在内核空间,ebpf的执行通常需要 root 访问权限

  • 这可能无意中增加了系统的攻击面,使其容易受到例如容器逃逸或潜在的内核利用等安全威胁

而用户空间的实现,可以让ebpf程序在这种高风险环境之外运作

  • 它们在用户空间中运行,大大降低了对高权限的依赖,从而减少了潜在的安全风险

内核态 -> 用户态

虽然eBPF 最初是为内核设计的,但它在用户空间也具有巨大的潜力 -- 也就是我们在上面介绍的两种原因(当然不止这些),以及内核对于GPL LICENSE的限制,这样自然而然地就催生出了早期的用户空间 eBPF 运行时

  • eg: ubpf 和 rbpf
  • GPL LICENSE -- 自由软件许可证

这些运行时允许开发者内核之外利用 eBPF 的能力,提供了一个在内核之外的运行平台,扩展其实用性和适用性,同时不受限于 GPL LICENSE

但是,这些工具有很多缺点:

  • 编写适用于 ubpf 和 rbpf 的程序可能需要一个特定的、和内核不完全兼容的工具链,同时只有有限的单线程哈希 maps 实现,难以运行实际的 eBPF 程序
  • ubpf 和 rbpf 本身只是一个执行 eBPF 字节码虚拟机,在实际的使用中,依然需要编写胶水代码,和其他用户空间程序进行编译、链接后才能使用,它们本身也不提供动态追踪的功能

所以,bpftime就是基于[认识到其他用户空间运行时的弊端+ebpf的用户空间化的需求],设计出的一个高性能ebpf用户空间运行时

介绍 

bpftime是基于 LLVM JIT/AOT 构建的bpftime是专为用户空间操作设计的一个高性能 eBPF 运行时

  • bpftime 希望能保持和现有的内核 eBPF 的良好兼容性,作为内核 eBPF 的一种用户态替代和改进方案,并且希望能最大程度上利用现有 eBPF 丰富的生态和工具
  • 它以其快速的 Uprobe 能力和 Syscall 钩子脱颖而出(尤其是uprobe性能比在内核中运行提供了10倍):

bpftime 可以类似 Kernel 中的 Uprobe 那样

  • 自动将 eBPF 运行时注入到用户空间进程中
  • 无需修改用户空间进程的代码,也无需进行重启进程即可使用
  • 只不过一个运行在内核态,一个运行在用户态

在某些场景下,bpftime 可能能作为 kernel eBPF 的一种替代方案

  • 它也不依赖于具体内核版本或 Linux 平台,可以在其他平台上运行

原理图 

  • 它是直接向用户空间程序里注入ebpf程序
  • 并且创建了用户空间的两个共享内存,用于存放ebpf程序和用户程序之间的通信数据
  • 它依赖于bpftime中提供的文件

除此之外,它也可以和内核内的uprobe一起运行:

  • 可以看见,内核中的uprobe是需要依赖内核中的bpf沙盒程序的,所以他必然要经历内核与用户态之间的转换

来源于 -- GitHub - eunomia-bpf/bpftime: Userspace eBPF runtime for fast Uprobe & Syscall hook & Extensions with LLVM JIT

示例代码

项目的安装方法在之前已经介绍过了 -- bpf,ebpf,libbpf,在ubuntu22中ebpf工具的安装和介绍(libbpf_bootstarp,eunomia-bpf,bpftiime),关系总结,ebpf程序执行流程_libbpf_bpftool ebpf-CSDN博客

如何编译和运行 

我们以官方文档中的例子来测试(在bpftime目录下执行):

编译
make -C example/malloc

当然也可以直接在malloc目录下执行make

运行

这里如果直接运行的话会提示缺少某个动态库文件

我们可以手动搞一个符号链接,让他可以识别上

或者在命令行里链接

  • LD_PRELOAD是一个环境变量,用于指定要在程序运行时预加载的共享库
LD_PRELOAD=build/runtime/syscall-server/libbpftime-syscall-server.so example/malloc/malloc

这是我们的ebpf程序

运行结果

向内核注入ebpf程序,每隔1s打印一下时间,并从map中统计进程调用malloc的次数(此时因为还没有进程向map中插入数据,所以只会打印时间):

LD_PRELOAD=build/runtime/agent/libbpftime-agent.so example/malloc/victim

这是测试程序

运行结果
  • 运行测试代码,它每隔100ms调用一次malloc:

  • 在他运行后,malloc程序就可以记录那个测试进程每秒执行的malloc次数了

代码分析 

.c

源码
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <unistd.h>
#include <stdlib.h>
#include "malloc.skel.h"
#include <inttypes.h>
#define warn(...) fprintf(stderr, __VA_ARGS__)static int libbpf_print_fn(enum libbpf_print_level level, const char *format,va_list args)
{return vfprintf(stderr, format, args);
}static volatile bool exiting = false;static void sig_handler(int sig)
{exiting = true;
}static int print_stat(struct malloc_bpf *obj)
{time_t t;struct tm *tm;char ts[16];uint32_t key, *prev_key = NULL;uint64_t value;int err = 0;int fd = bpf_map__fd(obj->maps.libc_malloc_calls_total);time(&t);tm = localtime(&t);strftime(ts, sizeof(ts), "%H:%M:%S", tm);printf("%-9s\n", ts);while (1) {  //从一个 eBPF Map 中获取键值对并进行处理err = bpf_map_get_next_key(fd, prev_key, &key);//获取下一个键,当prev=null时,获取第一个键if (err) {if (errno == ENOENT) { //没有其他键err = 0;break;}warn("bpf_map_get_next_key failed: %s\n",strerror(errno));return err;}err = bpf_map_lookup_elem(fd, &key, &value); //查找指定键的值if (err) {warn("bpf_map_lookup_elem failed: %s\n",strerror(errno));return err;}printf("	pid=%-5" PRIu32 " ", key);printf("	malloc calls: %" PRIu64 "\n", value);err = bpf_map_delete_elem(fd, &key); //处理完成后删除,这样就能实现 记录1s中malloc调用的次数if (err) {warn("bpf_map_delete_elem failed: %s\n",strerror(errno));return err;}prev_key = &key;//更新prev}fflush(stdout); //及时刷新缓冲区return err;
}int main(int argc, char **argv)
{struct malloc_bpf *skel;int err;/* Set up libbpf errors and debug info callback */libbpf_set_print(libbpf_print_fn);/* Cleaner handling of Ctrl-C */signal(SIGINT, sig_handler);signal(SIGTERM, sig_handler);/* Load and verify BPF application */skel = malloc_bpf__open();if (!skel) {fprintf(stderr, "Failed to open and load BPF skeleton\n");return 1;}/* Load & verify BPF programs */err = malloc_bpf__load(skel);if (err) {fprintf(stderr, "Failed to load and verify BPF skeleton\n");goto cleanup;}LIBBPF_OPTS(bpf_uprobe_opts, attach_opts, .func_name = "malloc",.retprobe = false);   //设置选项,宏定义一个类型为bpf_uprobe_opts的结构体attach_opts,设置监视函数名为malloc,在函数调用前设置执行钩子动作struct bpf_link *attach = bpf_program__attach_uprobe_opts(  //将 uprobes 附加到指定的函数上,并传递了设置的选项//(指定了 BPF 程序的文件描述符,监视的进程pid,监视函数所在的共享对象的名称,skel->progs.do_count, -1, "libc.so.6", 0, &attach_opts);if (!attach) {fprintf(stderr, "Failed to attach BPF skeleton\n");err = -1;goto cleanup;}while (!exiting) {sleep(1);print_stat(skel); //打印当前时间}
cleanup:/* Clean up */malloc_bpf__destroy(skel);return err < 0 ? -err : 0;
}

总之,总体框架和libbpf-bootstrap工具中编写的用户层代码基本一致

  • 它主要的工作是控制ebpf程序的生命周期,并且在将ebpf成功挂接到探测点时,每隔1s从map中获取数据,打印并删除
语法
#include "malloc.skel.h"

和libbpf-bootstrap工具一样,都会根据内核层代码生成skel.h辅助头文件,需要被用户层代码包含

LIBBPF_OPTS(bpf_uprobe_opts, attach_opts, .func_name = "malloc",
            .retprobe = false);

和之前介绍的语法是一样的,只是写法不同(这里是一块赋值,之前是分开赋值):

-- uprobes技术介绍+示例代码,ebpf下的uprobe介绍+代码解释(用户层+内核层代码),修改内核层写法,将两个函数与bpf程序分离,去掉用户函数所在程序的符号表(strip,如何解决)-CSDN博客

bpf_program__attach_uprobe_opts 

第一个参数使用的do_count,是因为progs结构体内就只有这一个字段

而之前使用的是自定义的函数名,也是因为progs里有这些字段:

我猜测,如果是自定义的用户函数,使用函数名即可,如果是官方库提供的,可能是其他规定好的字段

bpf_map__fd()

使用该函数可以获取map的fd(用户层是通过fd操作map的)

  • 参数是skel.h文件中自动为我们生成的malloc_bpf结构体中的maps中的字段(其实就是map对象名):
bpf_map_get_next_key(fd, prev_key, &key) 

可以从map中(通过fd标识map)获取下一个键(赋值给key)

  • 当prev=null时,获取第一个键
  • 如果返回非0,表示没有找到;如果同时错误码被设置为ENOENT,说明此时map中没有其他键
bpf_map_delete_elem()

删除指定键值对

 

.bpf.c

源码
#define BPF_NO_GLOBAL_DATA
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>struct {__uint(type, BPF_MAP_TYPE_HASH);__uint(max_entries, 1024);__type(key, u32); //进程id__type(value, u64);  //调用此书
} libc_malloc_calls_total SEC(".maps");static int increment_map(void *map, void *key, u64 increment)
{u64 zero = 0, *count = bpf_map_lookup_elem(map, key);if (!count) { //如果count不存在bpf_map_update_elem(map, key, &zero, BPF_NOEXIST);count = bpf_map_lookup_elem(map, key);if (!count) { //如果还是不存在return 0;}}//已经存在,更新次数u64 res = *count + increment;  bpf_map_update_elem(map, key, &res, BPF_EXIST);return *count;
}SEC("uprobe/libc.so.6:malloc")
int do_count(struct pt_regs *ctx)
{u32 pid = bpf_get_current_pid_tgid() >> 32;bpf_printk("malloc called from pid %d\n", pid);increment_map(&libc_malloc_calls_total, &pid, 1); //每调用一次,就+1return 0;
}char LICENSE[] SEC("license") = "GPL";

执行两种操作:

  • 如果key值不存在,就插入(key,0)
  • 如果key值存在,更新对应的value(+1)
​​​​​​​

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

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

相关文章

EPSON开发新IMU产品M-G370PDS改善姿态和震动控制

爱普生IMU于2011年首次推出&#xff0c;已在一系列客户应用中使用&#xff0c;因其出色的性能和质量而享有盛誉。近年来&#xff0c;IMU的使用已经扩展到无人系统测量、航空和水下视频摄影等领域。对更准确的位置和姿态控制的需求不断增长&#xff0c;不仅如此&#xff0c;高效…

【小程序】生成短信中可点击的链接

文章目录 前言一、如何生成链接二、仔细拜读小程序开发文档文档说明1文档说明2 总结 前言 由于线上运营需求&#xff0c;需要给用户发送炮轰短信&#xff0c;用户通过短信点击链接直接跳转进入小程序 一、如何生成链接 先是找了一些三方的&#xff0c;生成的倒是快速&#xf…

DDoS攻击类型与应对措施详解

攻击与防御简介 SYN Flood攻击 原理&#xff1a; SYN Flood攻击利用的是TCP协议的三次握手机制。在正常的TCP连接建立过程中&#xff0c;客户端发送一个SYN&#xff08;同步序列编号&#xff09;报文给服务器&#xff0c;服务器回应一个SYN-ACK&#xff08;同步和确认&#xf…

微信小程序wx.getLocation 真机调试不出现隐私弹窗

在小程序的开发过程中&#xff0c;首页中包含要获取用户地理位置的功能&#xff0c;所以在这里的onLoad&#xff08;&#xff09;中调用了wx.getLocation()&#xff0c;模拟调试时一切正常&#xff0c;但到了真机环境中就隐私框就不再弹出&#xff0c;并且出现了报错&#xff0…

开源相机管理库Aravis例程学习(一)——单帧采集single-acquisition

开源相机管理库Aravis例程学习&#xff08;一&#xff09;——单帧采集single-acquisition 简介源码函数说明arv_camera_newarv_camera_acquisitionarv_camera_get_model_namearv_buffer_get_image_widtharv_buffer_get_image_height 简介 本文针对官方例程中的第一个例程&…

maven引入外部jar包

将jar包放入文件夹lib包中 pom文件 <dependency><groupId>com.jyx</groupId><artifactId>Spring-xxl</artifactId><version>1.0-SNAPSHOT</version><scope>system</scope><systemPath>${project.basedir}/lib/Spr…

三款好用的 Docker 可视化管理工具

文章目录 1、Docker Desktop1.1、介绍1.2、下载地址1.3、在Windows上安装Docker桌面1.4、启动Docker Desktop1.5、Docker相关学习网址 2、Portainer2.1、介绍2.2、安装使用 3、Docker UI3.1、介绍3.2、安装使用3.2.1、常规方式安装3.2.2、通过容器安装 Docker提供了命令行工具&…

图深度学习(一):介绍与概念

目录 一、介绍 二、图的数据结构 三、图深度学习的基本模型 四、图深度学习的基本操作和概念 五、训练过程 六、主要应用场景 七、总结 一、介绍 图深度学习是将深度学习应用于图形数据结构的领域&#xff0c;它结合了图论的概念和深度学习的技术&#xff0c;用以处理和…

第二证券今日投资参考:铜价持续上涨 医药政策向好态势明显

昨日&#xff0c;A股在金融板块的带动下强势拉升&#xff0c;沪指涨超1%。到收盘&#xff0c;沪指涨1.26%报3057.38点&#xff0c;深证成指涨1.53%报9369.7点&#xff0c;创业板指涨1.85%报1795.52点&#xff0c;上证50指数涨2.1%&#xff1b;两市合计成交9971亿元&#xff0c;…

5.2 mybatis之autoMappingBehavior作用

文章目录 1. NONE关闭自动映射2. PARTIAL非嵌套结果映射3. FULL全自动映射 众所周知mybatis中标签< resultMap >是用来处理数据库库字段与java对象属性映射的。通常java对象属性&#xff08;驼峰格式&#xff09;与数据库表字段&#xff08;下划线形式&#xff09;是一 一…

分布式文件系统HDFS-2

文章目录 主要内容一.HDFS1.数据错误与恢复2.名称节点出错3.数据节点出错4.数据出错5.HDFS读写过程 6.写操作7.读操作8.读写数据过程 总结 主要内容 分布式文件系统HDFS 一.HDFS 1.数据错误与恢复 HDFS具有较高的容错性&#xff0c;可以兼容廉价的硬件&#xff0c;它把硬件出…

【Qt 学习笔记】Qt常用控件 | 按钮类控件Push Button的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 按钮类控件Push Button的使用及说明 文章编号&#xff1…

Redis中的订阅发布(三)

订阅发布 发送消息 当一个Redis客户端执行PUBLISH 命令将消息message发送给频道channel的时候&#xff0c;服务器需要执行以下 两个动作: 1.将消息message发送给channel频道的所有订阅者2.如果一个或多个模式pattern与频道channel相匹配&#xff0c;那么将消息message发送给…

Open3D (C++) 点云投影至主成分空间

目录 一、算法原理二、代码实现三、结果展示四、相关连接Open3D (C++) 点云投影至主成分空间由CSDN点云侠原创,爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 p r o j

k8s的service为什么不能ping通?——所有的service都不能ping通吗

点击阅读原文 前提&#xff1a;kube-proxy使用iptables模式 Q service能不能ping通&#xff1f; A: 不能&#xff0c;因为k8s的service禁止了icmp协议 B: 不能&#xff0c;因为clusterIP是一个虚拟IP&#xff0c;只是用于配置netfilter规则&#xff0c;不会实际绑定设备&…

AI智能分析网关V4平台告警数据清理方法:自动清理与手动清理

TSINGSEE青犀智能分析网关V4属于高性能、低功耗的软硬一体AI边缘计算硬件设备&#xff0c;目前拥有3种型号&#xff08;8路/16路/32路&#xff09;&#xff0c;支持Caffe/DarkNet/TensorFlow/PyTorch/MXNet/ONNX/PaddlePaddle等主流深度学习框架。硬件内部署了近40种AI算法模型…

飞书API(4):筛选数据的三种思路

截止到上一篇&#xff0c;终于通过飞书 API 完整获取到飞书多维表的数据。但是&#xff0c;有些场景&#xff0c;比如数据源会出现脏数据&#xff0c;毕竟如果是运营过程多人协作维护的数据&#xff0c;要想保持数据完美简直是天方夜谭&#xff01;再比如我们不需要完整的数据&…

2.SG90舵机模块

当我们输出一段脉冲信号的时候就可以调节舵机的角度 我们可以从原理图可以看到舵机的脚在PA6 从芯片手册我们又可以看到PA6对应TIM3_CH1,并且不用开启部分重映像就能使用 新建Servo.c存放PWM初始化 配置PWM void Servo_TIM3_Init(u16 arr,u16 psc) {//开启TIM3的时钟RCC_APB1…

vue中使用水印

1. 在utils下创建watermark.js const watermark {}/**** param {要设置的水印的内容} str* param {需要设置水印的容器} container* param {需要设置水印的每一块的宽度} canWidth* param {需要设置水印的每一块的高度} canHeight* param {需要设置水印的字体} canFont* para…

在Ubuntu服务器上快速安装一个redis并提供远程服务

文章目录 一、快速安装一个Redis第一步&#xff1a;更新apt源第二步&#xff1a;下载Redis第三步&#xff1a;查看Redis是否已自启动 二、配置Redis提供远程服务第一步&#xff1a;先确保6379端口正常开放第二步&#xff1a;修改配置文件第三步&#xff1a;重启Redis 三、补充&…