eBPF 在云原生中很重要,所以来学习一下,第一次接触该系列可能会很懵逼,即使是我已经从原项目中抛开次要提取出最主要的部分并做了详细的解释。可以多看几遍然后跑下代码和问 openai。
开发环境
Ubuntu20.04 64位
下载安装 eunomia-bpf 开发工具
# 下载 ecli 工具,用于运行 eBPF 程序
wget https://aka.pw/bpf-ecli -O ecli && chmod +x ./ecli
./ecli -h
# 下载编译器工具链,用于将 eBPF 内核代码编译为 config 文件或 WASM 模块
wget https://github.com/eunomia-bpf/eunomia-bpf/releases/latest/download/ecc && chmod +x ./ecc
./ecc -h
sudo apt install clang llvm
处理一下问题
vim ~/.bashrc
# 设置环境变量 BTF_FILE_PATH,将下行添加到文件的末尾。正在运行的内核的 vmlinux 文件,通常位于 /boot 目录下。
# 比如我的是 /boot/vmlinuz-5.4.0-70-generic
export BTF_FILE_PATH=/boot/vmlinuz-5.4.0-70-generic
source ~/.bashrc
代码案例
检测并打印从指定PID进入write系统调用的事件
这段程序是一个基于eBPF的跟踪程序,它捕获并记录从指定进程ID(PID)执行的 write 系统调用。这个程序被注册为 sys_enter_write 的一个跟踪点,当系统中的某个进程执行 write 系统调用时,它将被触发,并输出该进程的 PID。
./ecc minimal.bpf.c
./ecli run package.json
cat /sys/kernel/debug/tracing/trace_pipe | grep "BPF triggered sys_enter_write"
sshd-17097 [000] .... 1256.254917: 0: BPF triggered sys_enter_write from PID 17097.
sshd-17097 [000] .... 1256.254939: 0: BPF triggered sys_enter_write from PID 17097.
grep-25513 [000] .... 1256.254960: 0: BPF triggered sys_enter_write from PID 25513.
grep-25513 [000] .... 1256.254971: 0: BPF triggered sys_enter_write from PID 25513.
grep-25513 [000] .... 1256.254977: 0: BPF triggered sys_enter_write from PID 25513.
grep-25513 [000] .... 1256.254983: 0: BPF triggered sys_enter_write from PID 25513.
grep-25513 [000] .... 1256.254990: 0: BPF triggered sys_enter_write from PID 25513.
grep-25513 [000] .... 1256.254996: 0: BPF triggered sys_enter_write from PID 25513.
minimal.bpf.c
/* 定义许可证,通常使用 "Dual BSD/GPL" */
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* 禁用全局数据 */
/* eBPF的核心定义 */
/* BPF辅助函数和跟踪功能 */
typedef unsigned int u32;
typedef int pid_t;
const pid_t pid_filter = 0;
char LICENSE[] SEC("license") = "Dual BSD/GPL";
/* 定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint(即在进入 write 系统调用时执行)。*/
SEC("tp/syscalls/sys_enter_write")
int handle_tp(void* ctx)
{
/* 获取当前进程的PID,eBPF中没有直接获取PID的方法,可以使用下方函数获取当前线程的PID和TID(线程ID),右移32位可以获得PID*/
pid_t pid = bpf_get_current_pid_tgid() >> 32;
if (pid_filter && pid != pid_filter)
return 0;
/* 打印从特定PID进入 sys_enter_write 的消息。*/
bpf_printk("BPF triggered sys_enter_write from PID %d.n", pid);
return 0;
}
捕获进程打开文件的系统调用
当进程打开一个文件时,它会向内核发出 sys_openat 系统调用,并传递相关参数(例如文件路径、打开模式等)。内核会处理这个请求,并返回一个文件描述符(file descriptor),这个描述符将在后续的文件操作中用作引用。通过捕获 sys_openat 系统调用,我们可以了解进程在什么时候以及如何打开文件。
下面程序将捕获指定进程(或所有进程)的 sys_openat 系统调用,并在用户空间输出相关信息。
./ecc opensnoop.bpf.c
./ecli run package.json
# 通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出
cat /sys/kernel/debug/tracing/trace_pipe
# 输出
<...>-165144 [000] .... 14738.287516: 0: Process ID: 165144 enter sys openat
<...>-165145 [000] .... 14738.288076: 0: Process ID: 165145 enter sys openat
<...>-165145 [000] .... 14738.288155: 0: Process ID: 165145 enter sys openat
<...>-165145 [000] .... 14738.288357: 0: Process ID: 165145 enter sys openat
<...>-165145 [000] .... 14738.288478: 0: Process ID: 165145 enter sys openat
<...>-165145 [000] .... 14738.288564: 0: Process ID: 165145 enter sys openat
<...>-165145 [000] .... 14738.288615: 0: Process ID: 165145 enter sys openat
<...>-165145 [000] .... 14738.288668: 0: Process ID: 165145 enter sys openat
<...>-165146 [000] .... 14738.289536: 0: Process ID: 165146 enter sys openat
<...>-165146 [000] .... 14738.289623: 0: Process ID: 165146 enter sys openat
使用全局变量在 eBPF 中过滤进程 pid。全局变量在 eBPF 程序中充当一种数据共享机制,它们允许用户态程序与 eBPF 程序之间进行数据交互。这在过滤特定条件或修改 eBPF 程序行为时非常有用。这种设计使得用户态程序能够在运行时动态地控制 eBPF 程序的行为。下面全局变量 pid_target 用于过滤进程 PID。用户态程序可以设置此变量的值,以便在 eBPF 程序中只捕获与指定 PID 相关的 sys_openat 系统调用。
./ecli run package.json -- --pid_target 165146
opensnoop.bpf.c
/* 禁用全局数据 */
/* 包含了内核数据结构的定义 */
/* BPF辅助函数 */
/* 用于过滤指定进程 ID。这里设为 0 表示捕获所有进程的 sys_openat 调用 */
const volatile int pid_target = 0;
/* 使用 SEC 宏定义一个 eBPF 程序,关联到 tracepoint "tracepoint/syscalls/sys_enter_openat"。这个 tracepoint 会在进程发起 sys_openat 系统调用时触发。*/
SEC( "tracepoint/syscalls/sys_enter_openat" )
int tracepoint__syscalls__sys_enter_openat( struct trace_event_raw_sys_enter* ctx ) /* trace_event_raw_sys_enter 包含了关于系统调用的信息*/
{
/* 获取当前进程的PID,eBPF中没有直接获取PID的方法,可以使用下方函数获取当前线程的PID和TID(线程ID),右移32位可以获得PID*/
u64 id = bpf_get_current_pid_tgid();
u32 pid = id >> 32;
if ( pid_target && pid_target != pid )
return(false);
bpf_printk( "Process ID: %d enter sys openatn", pid );
return(0);
}
/* 将程序许可证设置为 "GPL",这是运行 eBPF 程序的必要条件 */
char LICENSE[] SEC( "license" ) = "GPL";
参考资料
https://github.com/eunomia-bpf/bpf-developer-tutoria
封面图
图源:https://github.com/eunomia-bpf/bpf-developer-tutorial
原文始发于微信公众号(安全小将李坦然):eBPF 开发入门一
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论