了解eBPF技术
By admin
- 3 minutes read - 480 wordseBPF 的全称“扩展的伯克利数据包过滤器 (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、bpftrace 与bpf
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 原理
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 参考指南
对比
BCC 与 bpftrace 两者具有互补性,bpftrace在编写功能强大的单行程序或短小的脚本方便十分理解;BCC则更适合开发大型复杂的脚本和作为后台进程使用,同时它还可以调用其它的库。比如目前很多用Python来开发BCC程序,它们使用Python的 argparse
库来提供复杂的命令行参数运行。
BCC 和 bpftrace 它们并不属于内核代码仓库的项目,而是托管在 Github 上一个名为 IO Visor的Linux基金会。
推荐阅读: https://www.brendangregg.com/ebpf.html
参考资料
- https://www.brendangregg.com/ebpf.html
- https://github.com/iovisor/bcc/
- https://github.com/iovisor/bpftrace
- https://time.geekbang.org/column/article/484207
- https://time.geekbang.org/column/article/483364