更多文章目录:点击这里
GitHub地址:https://github.com/ljrkernel
eBPF是将原先的BPF发展成一个指令集更复杂、应用范围更广的“内核虚拟机”。eBPF支持在用户态将C语言编写的一小段“内核代码”注入到内核中运行,注入时要先用llvm编译得到使用BPF指令集的elf文件,然后从elf文件中解析出可以注入内核的部分,最后用bpf_load_program
方法完成注入。 用户态程序和注入到内核中的程序通过共用一个位于内核中map实现通信。为了防止注入的代码导致内核崩溃,eBPF会对注入的代码进行严格检查,拒绝不合格的代码的注入。
注入程序bpf_load_program()
加入了更复杂的verifier 机制,在运行注入程序之前,先进行一系列的安全检查,最大限度的保证系统的安全。具体来说,verifier机制会对注入的程序做两轮检查:
check_cfg()
)可以被认为是一次深度优先搜索,主要目的是对注入代码进行一次 DAG(Directed Acyclic Graph,有向无环图)检测,以保证其中没有循环存在,除此之外,一旦在代码中发现以下特征,verifier 也会拒绝注入:
- 代码长度超过上限;
- 存在可能会跳出 eBPF 代码范围的 JMP,这主要是为了防止恶意代码故意让程序跑飞;
- 存在永远无法运行(unreachable)的 eBPF 指令,例如位于 exit 之后的指令;
do_check()
)较之于首轮则要细致很多:在本轮检测中注入代码的所有逻辑分支从头到尾都会被完全跑上一遍,所有指令的参数(寄存器)、访问的内存、调用的函数都会被仔细的捋一遍,任何的错误都会导致注入程序失败。目前可以用 C 来实现 BPF,但编译出来的却仍然是 ELF 文件,无法直接注入内核,bcc实现了一步到位的生成出 BPF 代码。bcc 是一个 python 库,其中有很大一部分的实现是基于 C 和 C++的,python 是实现对 bcc 应用层接口的封装。使用 BCC 进行 BPF 的开发仍然需要我们自行利用 C 来设计 BPF 程序,但编译、解析 ELF、加载 BPF 代码块以及创建 map 等可以由 bcc框架实现。
在使用bcc之前需要检查Linux系统内核配置选项,可以通过less
命令查看内核配置选项
/proc/config.gz
或
/boot/config-
less /boot/config-4.18.0-15-generic
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
# [optional, for tc filters]
CONFIG_NET_CLS_BPF=m
# [optional, for tc actions]
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_JIT=y
# [for Linux kernel versions 4.1 through 4.6]
CONFIG_HAVE_BPF_JIT=y
# [for Linux kernel versions 4.7 and later]
CONFIG_HAVE_EBPF_JIT=y
# [optional, for kprobes]
CONFIG_BPF_EVENTS=y
bcc 框架检查项:
CONFIG_NET_SCH_SFQ=m
CONFIG_NET_ACT_POLICE=m
CONFIG_NET_ACT_GACT=m
CONFIG_DUMMY=m
CONFIG_VXLAN=m
若检查内核配置选项与上述检查项不符,在/usr/src
目录下使用 make menuconfig
命令设置内核配置选项:
设置完成后需要编译内核。
设置好内核配置选项后,开始安装bcc,使用如下命令:
第一步,安装 bcc 软件包
sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
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
git clone https://github.com/iovisor/bcc.git
mkdir bcc/build; cd bcc/build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
make
sudo make install
下面介绍一个简单的例子,代码如下:
from bcc import BPF
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()
可以看到,这段代码是在 python 中内嵌 C 语言程序的,请注意六点:
text='...'
这里定义了一个内联的、C 语言写的 BPF 程序。kprobe__sys_clone()
这是一个通过内核探针(kprobe)进行内核动态跟踪的快捷方式。如果一个 C 函数名开头为 kprobe__ ,则后面的部分实际为设备的内核函数名,这里是 sys_clone() 。bpf_trace_printk()
这是一个简单的内核工具,用于 printf() 到 trace_pipe(译者注:可以理解为 BPF C 代码中的 printf())。它一般来快速调试一些东西,不过有一些限制:最多有三个参数,一个%s ,并且 trace_pipe 是全局共享的,所以会导致并发程序的输出冲突,因而 BPF_PERF_OUTPUT() 是一个更棒的方案,我们后面会提到。trace_print()
一个 bcc 实例会通过这个读取 trace_pipe 并打印出来。使用如下命令运行此示例:
sudo python2 hello_world.py