- 实验内容
学习BPF机制,了解BCC(BPF Compiler Collection)和bpftarce的实现原理,利用BPF工具实现对系统的跟踪和探测,如跟踪新创建的进程,统计线程占用CPU的时间,统计某内核函数的调用次数,跟踪系统中的内存调用和页错误,跟踪文件打开信息等。
- 实验目的
通过实验,让学生了解BPF机制,掌握利用BPF工具对系统进行跟踪与探测的方法。
- 设计思路和流程图
- 认识BCC:
BCC 是一个用于创建高效内核跟踪和操作程序的工具包,包括几个有用的工具和示例。它利用了扩展的 BPF(Berkeley Packet Filters),正式名称为 eBPF,这是首次添加到 Linux 3.15 的新功能。BCC 使用的大部分内容都需要 Linux 4.1 及更高版本。
BCC 使 BPF 程序更易于编写,使用 C 中的内核工具(并包括围绕 LLVM 的 C 包装器),以及 Python 和 lua 中的前端。它适用于许多任务,包括性能分析和网络流量控制。
BCC 是 eBPF 的一个工具集,是 eBPF 提取数据的上层封装,它的形式是
python 等程序中嵌套 bpf 程序。python 部分的作用是为用户提供友好的使
用 eBPF 的上层接口,也用于数据处理。bpf 程序会注入内核,提取数据。
当 bpf 程序运行时,通过 LLVM 将 bpf 程序编译得到 bpf 指令集的 elf 文
件,从 elf 文件中解析出可以注入内核的部分,用 bpf_load_program()方
法完成注入。注入程序 bpf_load_program()加入了更复杂的 verifier 机
制,在运行注入程序之前,先进行一系列的安全检查,最大限度的保证系统
的安全。
- 了解BPF
BPF,及伯克利包过滤器Berkeley Packet Filter,最初构想提出于 1992 年,其目的是为了提供一种过滤包的方法,并且要避免从内核空间到用户空间的无用的数据包复制行为。它最初是由从用户空间注入到内核的一个简单的字节码构成,它在那个位置利用一个校验器进行检查 —— 以避免内核崩溃或者安全问题 —— 并附着到一个套接字上,接着在每个接收到的包上运行。几年后它被移植到 Linux 上,并且应用于一小部分应用程序上(例如,tcpdump)。其简化的语言以及存在于内核中的即时编译器(JIT),使 BPF 成为一个性能卓越的工具。
在 2013 年,Alexei Starovoitov 对 BPF 进行彻底地改造,并增加了新的功能,改善了它的性能。这个新版本被命名为 eBPF (意思是 “extended BPF”),与此同时,将以前的 BPF 变成 cBPF(意思是 “classic” BPF)。新版本出现了如映射和尾调用tail call这样的新特性,并且 JIT 编译器也被重写了。新的语言比 cBPF 更接近于原生机器语言。并且,在内核中创建了新的附着点。
用Linux Kernel Module来做一个类比说明eBPF诞生的目的。
Kernel Module的主要目的就是让用户可以通过这种机制,实现对内核的“赋能”,动态添加一些内核本身不支持的功能,比如硬件的驱动能力,新的文件系统或是系统调用。当然也可以融合到现有的内核处理流程中,比如在netfilter的某个hook点中添加包处理方法等。
Kernel Module的优点:
动态添加/删除,无需重新编译内核
减小内核体积
缺点:
一旦出现BUG可能导致内核直接崩溃
增加内核攻击面,影响内核安全
eBPF要做的事情也非常类似,但它想要克服Kernel Module的缺点,即确保执行的代码绝对安全。
为了达到这一目的,eBPF在内核中实现了一个虚拟机执行用户的指令。与Kernel Module直接在真实的物理硬件上执行用户的指令不同,eBPF提供给用户一个虚拟的RISC处理器,以及一组相关的指令。用户可以直接用这组指令编写程序。同时,程序在下发到该虚拟机之前也会经过eBPF的检查,比如会不会进入无限循环,会不会访问不合法的内存地址等等。只有在通过检查之后才可以进入执行的环节。
对eBPF来说,和Kernle Module一样,也是通过特定的Hook点监听内核中的特定事件,进而执行用户定义的处理。这些Hook点包括:
静态tracepoint
动态内核态探针(Dynamic Kernel probes)
动态用户态探针(Dynamic User Probes)
其他hook点
针对主要是监控、跟踪使用的eBPF应用来说,主要通过这种方式取得内核运行时的一些参数和统计信息。例如,系统调用的参数值、返回值,通过eBPF map将得到的信息送给用户态程序,进而在用户态完成后处理流程。
另外一类应用则直接在一些内核处理流程中加入自己的处理逻辑,例如XDP,就是在网卡驱动和内核协议栈之间插入了eBPF扩展的网包过滤、转发功能。
eBPF 是一个在内核中运行的虚拟机,它可以去运行用户。在用户态实现的这种 eBPF 的代码,在内核以本地代码的形式和速度去执行,它可以跟内核的 Trace 系统相结合,给我们提供了几乎无限的可观测性。
eBPF 的基本原理——它所有的接口都是通过 BPF 系统调用来跟内核进行交互,eBPF 程序通过 LVM 和 Cline 进行编译,产生 eBPF 的字节码,通过 BPF 系统调用,加载到内核,验证代码的安全性,从而通过 JIT 实时的转化成 Native 的 X86 的指令。
3. 了解 bpftrace:
bpftrace 是一个基于bcc和bpf的开源跟踪器
bpftrace 提供了一种快速利用 eBPF 实现动态追踪的方法,可以作为简
单的命令行工具或者入门级编程工具来使用。动态追踪是一种高级的内核调
试技术,通过探针机制,采集内核态或者用户态程序的运行信息,而不需要
修改内核和应用程序的代码。这种机制性能损耗小,不会对系统运行构成任
何危险。因此,能够以非常低的成本,在短时间内获得丰富的运行信息,进
而可以快速的分析、排查、发现系统运行中的问题。
- 主要数据结构及其说明
无
- 源程序并附上注释(关键部分)
有关安装相关代码:
sudo apt-get install -y bpftrace
安装依赖:
sudo apt-get -y install bison build-essential cmake flex git libedit-dev \
libllvm6.0 llvm-6.0-dev libclang-6.0-dev python zlib1g-dev libelf-dev libfl-dev python3-distutils
sudo apt-get install python3-distutils
sudo apt-get install python3-pip
sudo apt-get install python3-setuptools
安装bcc:
tar zxvf bcc-src-with-submodule.tar.gz
cd bcc
mkdir build
cd build
cmake ..
make
sudo make install
cmake - DPYTHON_CMD = python3 .. #build python3 binding
pushd src/python/
make
sudo make install
popd
使用BCC构建一个Trace Point程序:
#!/usr/bin/python
#
# urandomread Example of instrumenting a kernel tracepoint.
# For Linux, uses BCC, BPF. Embedded C.
#
# REQUIRES: Linux 4.7+ (BPF_PROG_TYPE_TRACEPOINT support).
#
# Test by running this, then in another shell, run:
# dd if=/dev/urandom of=/dev/null bs=1k count=5
#
# Copyright 2016 Netflix, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
from __future__ import print_function
from bcc import BPF
from bcc.utils import printb
# load BPF program
b = BPF(text="""
TRACEPOINT_PROBE(random, urandom_read) {
// args is from /sys/kernel/debug/tracing/events/random/urandom_read/format
bpf_trace_printk("%d\\n", args->got_bits);
return 0;
}
""")
# header
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "GOTBITS"))
# format output
while 1:
try:
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
except ValueError:
continue
except KeyboardInterrupt:
exit()
printb(b"%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
TRACEPOINT_PROBE(random, urandom_read): 启动内核跟踪点。根据目录/random/urandom_read输入参数。
args->got_bits: 自动生成的参数,每个event的参数各有不同
- 程序运行结果及分析
查看 TCP 会话的生命周期和吞吐量统计:
输入指令:sudo python tcplife.py:
跟踪 hit radio:
输入指令:sudo python cachestat.py:
追踪每个任务占用 CPU 时间:
输入指令:sudo python cpudist.py:
追踪新的进程:
输入指令:sudo python execsnoop.py:
追踪新创建的线程:
输入指令:sudo python threadsnoop.py:
内存泄露使用的是memleak.py文件,作用是统计从运行开始之后,内存的分配与释放情况,每隔一段时间,打印出分配了,但没有被释放的内存,信息包括分配的函数栈以及内存大小、个数:
输入指令:sudo python memleak.py:
使用cachetop.py文件,可以统计每个进程的缓存命中率,原理是在内核cache相关的几个函数上加了kprobe进行统计。
输入指令:sudo python cachetop.py:
biolatency 统计bio的耗时分布情况:
输入指令:sudo python biolatency.py:
biotop 统计每个进程的bio数据量大小:
输入指令:sudo python biotop.py:
biosnoop 统计每个bio的进程、大小、耗时等信息:
输入指令:sudo python biosnoop.py:
bitesize 统计每个进程的bio大小的分布情况:
输入指令:sudo python bitesize.py:
使用BCC构建一个Trace Point程序:
TRACEPOINT_PROBE(random, urandom_read): 启动内核跟踪点。根据目录/random/urandom_read输入参数。
args->got_bits: 自动生成的参数,每个event的参数各有不同,见下文
输入指令:sudo python3 hello.py:
urandomread的参数设置如下:
输入指令:
cat /sys/kernel/debug/tracing/events/random/urandom_read/format
结果:
从最后一行可以看到,可以输出go_bits,pool_left,input_left三个参数。
- 实验体会
在网上查看有关 BCC、eBPF 和 bpftrace 的有关资料,令我对他们有了从一无所知到初步了解。在安装的时候曾经遇到过各种各样的问题,比如说由于安装bcc所需的版本必须在4.1以上,所以在老师所提供的环境下无法进行。在SEED上进行安装的时候,也出现了各种各样的问题,比如:
当时还一度打开了系统文件:
在向老师和助教询问之后,我最终发觉是版本与系统的不适应所导致的,最后下载了2021年九月份的bcc,最终得以将问题解决。