eBPF 的全称“扩展的伯克利数据包过滤器 (Extended Berkeley Packet Filter)” 来看,它是一种数据包过滤技术,是从 BPF (Berkeley Packet Filter) 技术扩展而来的。
BPF 提供了一种在 内核事件 和 用户程序 事件发生时安全注入代码的机制,这就让非内核开发人员也可以对内核进行控制。随着内核的发展,BPF 逐步从最初的数据包过滤扩展到了网络、内核、安全、跟踪等,而且它的功能特性还在快速发展中,这种扩展后的 BPF 被简称为 eBPF(早期的 BPF 被称为经典 BPF,简称 cBPF)。实际上,现代内核所运行的都是 eBPF,如果没有特殊说明,内核和开源社区中提到的 BPF 等同于 eBPF 。
使用场景及分类
根据 eBPF 的功能和使用场景,主要分类三类:
跟踪
从内核和程序的运行状态中提取跟踪信息,来了解当前系统正在发生什么。
跟踪类 eBPF 程序主要用于从系统中提取跟踪信息,进而为监控、排错、性能优化等提供数据支撑。
其中 BCC 工具集中包含的绝大部分工具也都属于这个类型。
网络
对网络数据包进行过滤和处理,以便了解和控制网络数据包的收发过程。
网络类 eBPF 程序主要用于对网络数据包进行过滤和处理,进而实现网络的观测、过滤、流量控制以及性能优化等各种丰富的功能。根据事件触发位置的不同,网络类 eBPF 程序又可以分为 XDP(eXpress Data Path,高速数据路径)程序、TC(Traffic Control,流量控制)程序、套接字程序以及 cgroup 程序。
如著名的开源项目 cilium ,主要用到了ebpf 中的 XDP (eXpress Data Path,高速数据路径)技术。
安全
第三类是除跟踪和网络之外的其他类型,包括安全控制、BPF 扩展等等。
插桩技术
eBPF支持多种事件源,可以在整个软件栈中提供能见度,其实现目前主要通过两种技术,分别为 动态插桩
和 静态插桩
,有时候只用其中一种方式是无法实现我们的需求,这时就需要两者相互配合使用了。
动态插桩: kprobes 和 uprobes
动态插桩方式可以做到在程序运行期间,动态的插入观察点,而软件的运行不会受到任何影响,在这点做到了零开销。
kprobes
一般指内核态级别的函数插桩,而 uprobes
则指用户态级别的函数插桩。
动态插桩技术一般需要在 内核函数
或 应用函数
的 开始位置
和结束位置
进行插桩。例如
#!/usr/local/bin/bpftrace
// this program times vfs_read()
kprobe:vfs_read
{
@start[tid] = nsecs;
}
kretbrobe:vfs_read
/@start[tid]/
{
$duration_us = (nsecs - @start[tid]) / 1000;
@us = hist($duration_us);
delete(@start[tid]));
}
这里kprobe:vfs_read
表示是内核函数bfs_read
的起始插桩,而kretbroke:vfs_read
则是函数的结果插桩,通过对这两个地方分别插桩就可以计算出当前函数的执行时间。
被插桩的函数在整个软件栈中有成千上万个,所以我们可以在任意关注的地方进行插桩,并编写自己的代码实现想要的功能。
动态插桩技术有一点不好的地方就是随着软件版本的迭代变更,被插桩的函数有可能会被重命名或者被移除,这时候会导致一些BPF工具没有办法直接使用,要想继续使用这些BPF工具只能跟着将其进行调整,这个问题十分令人头疼;除此之外还有一个问题就是编译器可能会将一些函数进行 inline
化,这时候会导致这些函数无法使用 kprobes
或 uprobes
动态插桩。
静态插桩: tracepoint 和 USDT
静态插桩会将一些稳定的事件名字编码到软件代码中,由开发者自行维护。BPF跟踪工具支持内核的静态插桩技术,也支持用户态的静态定义跟踪插桩 USDT(user level statically defined tracing)。
不过静态插桩技术也存在一定的问题,那就是会增加开发者的维护成本,同时静态插桩点数量一般很有限。
所有如果我们要开发 BPF工具的话,一般都推荐优先使用静态插桩技术,如果仍无法满足需求的话,再考虑使用动态插桩技(kprobes 或 uprobes)
开发工具
直接通过BPF指令编写BPF程序是件非常繁琐的一件事,因此出现了一些高级语言支持的BPF前端工具,主流开发工具主要是 BCC
和 bpftrade
。

BCC
BBC(BPF编译器集合,BPF Compiler Collection) 是最早的开发BPF的开发框架。它提供了一个编写内核BPF程序的C语言环境,同时还提供了其它高级语言如Python、Lua 或 C++环境来实现用户端接口,它也是 libbcc
和 libbpf
库的前身,这两个库提供了使用BPF程序对事件进行观测的库函数。
用户可以直接在系统上 安装BCC 即可,不用手动亲自编码代码就可以直接使用自带的一些工具,这些命令的使用场景及用法请参考官方文档 https://github.com/iovisor/bcc/blob/master/docs/tutorial.md。
sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
工具集将安装在 /sbin
(/usr/sbin
in Ubuntu 18.04) 目录,他们的扩展名为 -bpfcc
.
$ ls /sbin | grep bpfcc argdist-bpfcc bashreadline-bpfcc bindsnoop-bpfcc biolatency-bpfcc biolatpcts-bpfcc biosnoop-bpfcc biotop-bpfcc bitesize-bpfcc bpflist-bpfcc btrfsdist-bpfcc btrfsslower-bpfcc cachestat-bpfcc cachetop-bpfcc capable-bpfcc cobjnew-bpfcc compactsnoop-bpfcc cpudist-bpfcc cpuunclaimed-bpfcc criticalstat-bpfcc dbslower-bpfcc dbstat-bpfcc dcsnoop-bpfcc dcstat-bpfcc deadlock-bpfcc dirtop-bpfcc drsnoop-bpfcc execsnoop-bpfcc exitsnoop-bpfcc ext4dist-bpfcc ext4slower-bpfcc filelife-bpfcc fileslower-bpfcc filetop-bpfcc funccount-bpfcc funcinterval-bpfcc funclatency-bpfcc funcslower-bpfcc gethostlatency-bpfcc hardirqs-bpfcc inject-bpfcc javacalls-bpfcc javaflow-bpfcc javagc-bpfcc javaobjnew-bpfcc javastat-bpfcc javathreads-bpfcc killsnoop-bpfcc klockstat-bpfcc llcstat-bpfcc mdflush-bpfcc memleak-bpfcc mountsnoop-bpfcc mysqld_qslower-bpfcc netqtop-bpfcc nfsdist-bpfcc nfsslower-bpfcc nodegc-bpfcc nodestat-bpfcc offcputime-bpfcc offwaketime-bpfcc oomkill-bpfcc opensnoop-bpfcc perlcalls-bpfcc perlflow-bpfcc perlstat-bpfcc phpcalls-bpfcc phpflow-bpfcc phpstat-bpfcc pidpersec-bpfcc profile-bpfcc pythoncalls-bpfcc pythonflow-bpfcc pythongc-bpfcc pythonstat-bpfcc readahead-bpfcc reset-trace-bpfcc rubycalls-bpfcc rubyflow-bpfcc rubygc-bpfcc rubyobjnew-bpfcc rubystat-bpfcc runqlat-bpfcc runqlen-bpfcc runqslower-bpfcc shmsnoop-bpfcc slabratetop-bpfcc sofdsnoop-bpfcc softirqs-bpfcc solisten-bpfcc sslsniff-bpfcc stackcount-bpfcc statsnoop-bpfcc swapin-bpfcc syncsnoop-bpfcc syscount-bpfcc tclcalls-bpfcc tclflow-bpfcc tclobjnew-bpfcc tclstat-bpfcc tcpaccept-bpfcc tcpconnect-bpfcc tcpconnlat-bpfcc tcpdrop-bpfcc tcplife-bpfcc tcpretrans-bpfcc tcprtt-bpfcc tcpstates-bpfcc tcpsubnet-bpfcc tcpsynbl-bpfcc tcptop-bpfcc tcptracer-bpfcc threadsnoop-bpfcc tplist-bpfcc trace-bpfcc ttysnoop-bpfcc vfscount-bpfcc vfsstat-bpfcc wakeuptime-bpfcc xfsdist-bpfcc xfsslower-bpfcc zfsdist-bpfcc zfsslower-bpfcc
目前大概有120多个命令,可以直接执行这些命令
sudo opensnoop-bpfcc
相关文档
开发教程 https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md
参考指南 https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
如果要用 BCC 开发其它程序的话,一般是通过 TRACEPOINT_PROBE(category, event) 来定义一个跟踪点处理函数。
对我们要跟踪的短时进程问题来说,也就是下面这两个跟踪点:
// 定义sys_enter_execve跟踪点处理函数. TRACEPOINT_PROBE(syscalls, sys_enter_execve) { //待添加处理逻辑 } // 定义sys_exit_execve跟踪点处理函数. TRACEPOINT_PROBE(syscalls, sys_exit_execve) { //待添加处理逻辑 }
其中 syscalls 是分类,而 sys_enter_execve 是跟踪点。它其实就是下面即将介绍的 bpftrace 中的 tracepoint:syscalls:sys_enter_execve 跟踪点。
bpftrace
bpftrace是一个新兴的前端,其源代码非常简洁,它同样也是基于 libbcc
和 libbpf
库构建的。

bpftrace 会把你开发的脚本借助 BCC 编译加载到内核中执行,再通过 BPF 映射获取执行的结果。
对于 bpftrace 安装很简单
# Ubuntu 19.04 sudo apt-get install -y bpftrace # RHEL8/CentOS8 sudo dnf install -y bpftrace
安装好 bpftrace 之后,你就可以执行 bpftrace -l
来查询 内核插桩 和 跟踪点 。
# 查询所有 内核插桩 和 跟踪点 sudo bpftrace -l # 使用通配符查询所有的系统调用 跟踪点 sudo bpftrace -l 'tracepoint:syscalls:*' # 使用通配符查询所有名字包含"execve"的跟踪点 sudo bpftrace -l '*execve*'
对于 跟踪点 来说,你还可以加上 -v 参数查询函数的入口参数或返回值。
# 查询execve入口参数格式 $ sudo bpftrace -lv tracepoint:syscalls:sys_enter_execve tracepoint:syscalls:sys_enter_execve int __syscall_nr const char * filename const char *const * argv const char *const * envp # 查询execve返回值格式 $ sudo bpftrace -lv tracepoint:syscalls:sys_exit_execve tracepoint:syscalls:sys_exit_execve int __syscall_nr long ret
而由于内核函数属于不稳定的 API,在 bpftrace 中只能通过 arg0、arg1 这样的参数来访问,具体的参数格式还需要参考内核源代码( https://www.kernel.org/ ),注意内核的版本号差异。
相关文档
bpftrace一行教程 https://github.com/iovisor/bpftrace/blob/master/docs/developers.md
参考指南 https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
对比
BCC 与 bpftrace 两者具有互补性,bpftrace在编写功能强大的单行程序或短小的脚本方便十分理解;BCC则更适合开发大型复杂的脚本和作为后台进程使用,同时它还可以调用其它的库。比如目前很多用Python来开发BCC程序,它们使用Python的 argparse
库来提供复杂的命令行参数运行。
BCC 和 bpftrace 它们并不属于内核代码仓库的项目,而是托管在 Github 上一个名为 IO Visor的Linux基金会。
推荐阅读:https://www.brendangregg.com/ebpf.html