跳到主要内容

使用 bcc 开发 eBPF

bcc 的本质是什么?

我们知道,eBPF 程序分为内核态与用户态两部分,bcc 对这两部分都提供了一系列的工具函数来简化开发:

  1. 内核态使用 C 语言开发,bcc 提供了一系列的 库函数与宏定义 来简化开发。
  2. 用户态主要负责包含 eBPF 程序加载、挂载到内核函数和跟踪点,以及通过 BPF 映射获取和打印执行结果等部分,不限制开发语言,bcc 提供了一系列的 python 函数来简化这些操作。

需要安装哪些依赖?

基础通用工具

bcc 的基础通用工具依赖有:bash, make, python3。

bcc 工具集

首先要安装有 bcc 工具集,它是用 bcc 写好的问题定位分析工具,参考 tools 目录

虽然这个不是运行 bcc 开发的 eBPF 程序的必备条件,但是安装这个工具集的其它依赖包中包含了运行 bcc 开发的 eBPF 程序所需的关键依赖:bash、pytyon3、libbpf、bcc 相关 python 包等。

Ubuntu 中对应的包名是 bpfcc-tools,TencentOS/REHL 中对应的包名是 bcc-tools,安装好后工具集通常会放在 /usr/share/bcc/tools 目录下,Ubuntu 会放在 /sbin 下(后缀为 -bpfcc 的可执行文件)。

bcc 相关动态链接库

bcc 相关的动态链接库也是需要安装的。

Ubuntu 中对应的包名是 libbpfcc-dev,TencentOS/REHL 中对应的包名是 bcc-devel。

libbpf 相关头文件

Ubuntu 中对应的包名是 libbpf-dev,TencentOS/REHL 中对应的包名是 libbpf-devel。

libelf 相关头文件

Ubuntu 中对应的包名是 libelf-dev,TencentOS/REHL 中对应的包名是 elfutils-libelf-devel。

Linux 内核头文件

bcc 编译 eBPF C 代码时依赖内核头文件。

Ubuntu 中对应的包名是 linux-headers-$(uname -r)(通常没有预装),TencentOS/REHL 中对应的包名是 kernel-headers(通常默认已经预装)。

内核头文件的安装位置是 /usr/src/kernels/$(uname -r)

clang 与 LLVM

bcc 会调用 clang 编译 eBPF 的 C 代码,LLVM 作为 clang 的后端,它们都需要安装。

Ubuntu 和 TencentOS/REHL 中对应的包名都是 clang、llvm。

一键安装所有依赖

首先需要使用 Linux,然后用发行版自带的包管理器安装必要的依赖包(不用第三方包管理器,比如 homebrew,因为 eBPF 的依赖与当前发行版内核息息相关,第三方包管理器无法适配)。

REHL、TencentOS:

sudo yum install bcc-tools bcc-devel libbpf-devel llvm clang elfutils-libelf-devel

Ubuntu:

sudo apt-get install -y  make clang llvm libelf-dev libbpf-dev bpfcc-tools libbpfcc-dev linux-tools-$(uname -r) linux-headers-$(uname -r)

hello world

我们来写一个简单的 hello world eBPF 程序,首先写内核态部分的 C 语言代码:

hello.c
int hello_world(void *ctx)
{
bpf_trace_printk("Hello, World!");
return 0;
}

再写用户态部分的 Python 语言代码:

#!/usr/bin/env python3

# 1.导入 bcc 包
from bcc import BPF

# 2. 定义 eBPF 程序,加载内核态部分的 eBPF C 语言代码
b = BPF(src_file="hello.c")

# 3. hook eBPF 程序到指定的内核事件
b.attach_kprobe(event="do_sys_openat2", fn_name="hello_world")

# 4. 打印内核 trace 输出(/sys/kernel/debug/tracing/trace_pipe,bpf_trace_printk 函数会输出到这里)
b.trace_print()

最后执行 python3 hello.py 运行 eBPF 程序,就可以看到相应的调试输出。

开发工具如何配置自动补全?

bcc 开发的 eBPF 程序有 C 和 Python 两部分代码,其中 C 代码可能涉及使用内核相关的函数与变量,自动补全会比较麻烦,不建议配置,下面主要介绍 Python 代码的自动补全。

如果你的开发环境是 Linux,且 Python 是使用发行版自带的包管理器安装的,那么安装 bcc 工具集时就已经自动安装了 bcc 的 python 包,你的 IDE/编辑器 通常可以自动识别并自动补全。

其它情况,你可以先用 Linux 发行版安装好 bcc 工具集,然后将自动安装好的 bcc 的 python 包拷贝到你的开发环境中。

先查看 bcc 的 python 包所在的目录:

$ ls -alh /usr/lib/python3.11/site-packages | grep bcc
drwxr-xr-x. 3 root root 4.0K 7920:56 bcc

然后将这个目录拷贝到你的开发环境中中的 $HOME/.local/lib/python/site-packages 目录下(目录没有就创建下)。

最后,确保你的 shell 的启动脚本中设置环境变量 PYTHONUSERBASE$HOME/.local

eBPF 程序可以调用哪些辅助函数?

可以通过 bpftool feature probe 来查询各类 eBPF 程序可以调用的辅助函数,比如 kprobe:

$ bpftool feature probe
...
eBPF helpers supported for program type kprobe:
- bpf_map_lookup_elem
- bpf_map_update_elem
- bpf_map_delete_elem
- bpf_probe_read
- bpf_ktime_get_ns
- bpf_get_prandom_u32
- bpf_get_smp_processor_id
- bpf_tail_call
- bpf_get_current_pid_tgid
- bpf_get_current_uid_gid
- bpf_get_current_comm
- bpf_perf_event_read
- bpf_perf_event_output
- bpf_get_stackid
- bpf_get_current_task
- bpf_current_task_under_cgroup
- bpf_get_numa_node_id
- bpf_probe_read_str
- bpf_perf_event_read_value
- bpf_override_return
- bpf_get_stack
- bpf_get_current_cgroup_id
- bpf_map_push_elem
- bpf_map_pop_elem
- bpf_map_peek_elem
- bpf_spin_lock
- bpf_spin_unlock
- bpf_strtol
- bpf_strtoul
- bpf_send_signal
- bpf_probe_read_user
- bpf_probe_read_kernel
- bpf_probe_read_user_str
- bpf_probe_read_kernel_str
- bpf_send_signal_thread
- bpf_jiffies64
- bpf_get_ns_current_pid_tgid
- bpf_get_current_ancestor_cgroup_id
- bpf_ktime_get_boot_ns
- bpf_ringbuf_output
- bpf_ringbuf_reserve
- bpf_ringbuf_submit
- bpf_ringbuf_discard
- bpf_ringbuf_query
- bpf_get_task_stack
- bpf_copy_from_user
- bpf_snprintf_btf
- bpf_per_cpu_ptr
- bpf_this_cpu_ptr
- bpf_task_storage_get
- bpf_task_storage_delete
- bpf_get_current_task_btf
- bpf_for_each_map_elem
- bpf_snprintf
- bpf_timer_init
- bpf_timer_set_callback
- bpf_timer_start
- bpf_timer_cancel
- bpf_get_func_ip
- bpf_get_attach_cookie
- bpf_task_pt_regs
- bpf_get_branch_snapshot
- bpf_find_vma
- bpf_loop
- bpf_strncmp
- bpf_copy_from_user_task
- bpf_kptr_xchg
- bpf_map_lookup_percpu_elem
- bpf_dynptr_from_mem
- bpf_ringbuf_reserve_dynptr
- bpf_ringbuf_submit_dynptr
- bpf_ringbuf_discard_dynptr
- bpf_dynptr_read
- bpf_dynptr_write
- bpf_dynptr_data
- bpf_ktime_get_tai_ns
- bpf_user_ringbuf_drain
- bpf_cgrp_storage_get
- bpf_cgrp_storage_delete
...

辅助函数的具体定义可以通过 man bpf-helpers 查看,或者参考内核头文件 include/uapi/linux/bpf.h 中的注释。

参考资料