Linux动态追踪——eBPF

2024-04-21 18:04
文章标签 动态 linux 追踪 ebpf

本文主要是介绍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

这篇关于Linux动态追踪——eBPF的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/923721

相关文章

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

linux下shell脚本启动jar包实现过程

《linux下shell脚本启动jar包实现过程》确保APP_NAME和LOG_FILE位于目录内,首次启动前需手动创建log文件夹,否则报错,此为个人经验,供参考,欢迎支持脚本之家... 目录linux下shell脚本启动jar包样例1样例2总结linux下shell脚本启动jar包样例1#!/bin

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

Linux之platform平台设备驱动详解

《Linux之platform平台设备驱动详解》Linux设备驱动模型中,Platform总线作为虚拟总线统一管理无物理总线依赖的嵌入式设备,通过platform_driver和platform_de... 目录platform驱动注册platform设备注册设备树Platform驱动和设备的关系总结在 l

linux批量替换文件内容的实现方式

《linux批量替换文件内容的实现方式》本文总结了Linux中批量替换文件内容的几种方法,包括使用sed替换文件夹内所有文件、单个文件内容及逐行字符串,强调使用反引号和绝对路径,并分享个人经验供参考... 目录一、linux批量替换文件内容 二、替换文件内所有匹配的字符串 三、替换每一行中全部str1为st