eBPF理解
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了eBPF理解相关的知识,希望对你有一定的参考价值。
目录
一、bpftrace
bpftrace是linux增强的eBPF的高级跟踪语言。其使用LLVM作为后端,将脚本编译为BPF字节码,并利用BCC与linux BPF进行交互,以实现linux的跟踪功能:kprobe,uprobe和tracepoint。使用方式可参考bpftrace 教程
使用bpftrace查询跟踪点 -l
查询execve跟踪点
//查询所有内核插装和跟踪点
# bpftrace -l
//查询execve的跟踪点
# bpftrace -l '*execve*'
hardware:*execve*:
kfunc:__ia32_compat_sys_execve
kfunc:__ia32_compat_sys_execveat
kfunc:__ia32_sys_execve
kfunc:__ia32_sys_execveat
kfunc:__x64_compat_sys_execve
kfunc:__x64_compat_sys_execveat
kfunc:__x64_sys_execve
kfunc:__x64_sys_execveat
kfunc:audit_log_execve_info
kfunc:bprm_execve
kfunc:kernel_execve
kprobe:__ia32_compat_sys_execve
kprobe:__ia32_compat_sys_execveat
kprobe:__ia32_sys_execve
kprobe:__ia32_sys_execveat
kprobe:__x64_compat_sys_execve
kprobe:__x64_compat_sys_execveat
kprobe:__x64_sys_execve
kprobe:__x64_sys_execveat
kprobe:audit_log_execve_info
kprobe:bprm_execve
kprobe:do_execveat_common.isra.0
kprobe:kernel_execve
software:*execve*:
tracepoint:syscalls:sys_enter_execve
tracepoint:syscalls:sys_enter_execveat
tracepoint:syscalls:sys_exit_execve
tracepoint:syscalls:sys_exit_execveat
内核插桩(kprobe)和跟踪点(tracepoint),建议使用tracepoint,更加稳定。
查询函数的参数 -v
//-v 参数查询 函的参数
#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
#bpftrace -lv tracepoint:syscalls:sys_exit_execve
tracepoint:syscalls:sys_exit_execve
int __syscall_nr
long ret
bpftrace 实例
#sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve,tracepoint:syscalls:sys_enter_execveat printf("%-6d %-8s", pid, comm); join(args->argv);'
Attaching 1 probe...
18376 bash ls --color=auto
18377 bash ls --color=auto
命令行解析
- bpftrace -e 从后面的字符串参数读入bpftrace程序
- tracepoint:syscalls:sys_enter_execve 跟踪点处理函数
- printf() 输出,pid 进程[PID,comm进程名称是bpftrace的内置变量
- join(args->argv) 把字符串数组格式的参数用空格拼接起来
再开一个中断,输入ls命令,即可在跟踪的终端上看到输出 ls命令
二、BCC方法
C语言与Python语言
from bcc import BPF
# define BPF program
prog = """
#include <linux/sched.h>
// define output data structure in C
struct data_t
u32 pid;
u64 ts;
char comm[TASK_COMM_LEN];
;
BPF_PERF_OUTPUT(events);
int hello(struct pt_regs *ctx)
struct data_t data = ;
data.pid = bpf_get_current_pid_tgid();
data.ts = bpf_ktime_get_ns();
bpf_get_current_comm(&data.comm, sizeof(data.comm));
events.perf_submit(ctx, &data, sizeof(data));
return 0;
"""
# load BPF program
b = BPF(text=prog)
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
# header
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))
# process event
start = 0
def print_event(cpu, data, size):
global start
event = b["events"].event(data)
if start == 0:
start = event.ts
time_s = (float(event.ts - start)) / 1000000000
print("%-18.9f %-16s %-6d %s" % (time_s, event.comm, event.pid,
"Hello, perf_output!"))
# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
b.perf_buffer_poll()
程序说明:
- struct data_t 定义C结构体,在内核和用户空间传递数据
- BPF_PREF_OUTPUT(event)定义了一个性能事件映射event
- bpf_get_current_pid_tgid 取低32位为进程PID
- bpf_get_current_comm 获取进程名
- events.perf_submit() 提交事件以供用户空间通过性能环形缓冲区读取数据
b["events"].open_perf_buffer(print_event)
: 将函数与事件流相关联b.perf_buffer_poll()
: 阻塞等事件
三、libbpf方法
参考链接 bashreadline例子
1、生成 vmlinux.h文件
sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
2、bashreadline.h 文件
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2021 Facebook */
#ifndef __BASHREADLINE_H
#define __BASHREADLINE_H
#define MAX_LINE_SIZE 80
struct str_t
__u32 pid;
char str[MAX_LINE_SIZE];
;
#endif /* __BASHREADLINE_H */
3、bashreadline.bpf.c文件
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021 Facebook */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bashreadline.h"
#define TASK_COMM_LEN 16
//定义性能映射事件
struct
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
events SEC(".maps");
//跟踪点
SEC("uretprobe/readline")
int BPF_KRETPROBE(printret, const void *ret)
struct str_t data;
char comm[TASK_COMM_LEN];
u32 pid;
if (!ret)
return 0;
//获取进程名
bpf_get_current_comm(&comm, sizeof(comm));
if (comm[0] != 'b' || comm[1] != 'a' || comm[2] != 's' || comm[3] != 'h' || comm[4] != 0 )
return 0;
//获取进程号取低32位
pid = bpf_get_current_pid_tgid() >> 32;
data.pid = pid;
bpf_probe_read_user_str(&data.str, sizeof(data.str), ret);
//提交性能事件
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data));
return 0;
;
//定义许可证
char LICENSE[] SEC("license") = "GPL";
编译并生成脚手架头文件,使用 clang 和 bpftool 将其编译成 BPF 字节码,然后再生成其脚手架头文件 bashreadline.skel.h
clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I/usr/include/x86_64-linux-gnu -I. -c bashreadline.bpf.c -o bashreadline.bpf.o
bpftool gen skeleton bashreadline.bpf.o > bashreadline.skel.h
其中
- -target bpf 表示要生成 BPF 字节码,
- D__TARGET_ARCH_x86_64 表示目标的体系结构是 x86_64,
- -I 则是引入头文件路径。
4、用户态程序开发
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include "bashreadline.h"
#include "bashreadline.skel.h"
int main(int argc, char **argv)
LIBBPF_OPTS(bpf_object_open_opts, open_opts);
static const struct argp argp =
.options = opts,
.parser = parse_arg,
.doc = argp_program_doc,
;
//定义BPF程序和性能事件缓冲区
struct bashreadline_bpf *obj = NULL;
struct perf_buffer *pb = NULL;
char *readline_so_path;
off_t func_off;
int err;
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
if (err)
return err;
if (libreadline_path)
readline_so_path = libreadline_path;
else if ((readline_so_path = find_readline_so()) == NULL)
warn("failed to find readline\\n");
return 1;
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
//设置调试输出函数
libbpf_set_print(libbpf_print_fn);
err = ensure_core_btf(&open_opts);
if (err)
warn("failed to fetch necessary BTF for CO-RE: %s\\n", strerror(-err));
goto cleanup;
//初始化BPF程序
obj = bashreadline_bpf__open_opts(&open_opts);
if (!obj)
warn("failed to open BPF object\\n");
goto cleanup;
//加载BPF字节码
err = bashreadline_bpf__load(obj);
if (err)
warn("failed to load BPF object: %d\\n", err);
goto cleanup;
func_off = get_elf_func_offset(readline_so_path, "readline");
if (func_off < 0)
warn("cound not find readline in %s\\n", readline_so_path);
goto cleanup;
//挂载BPF字节码到跟踪点
obj->links.printret = bpf_program__attach_uprobe(obj->progs.printret, true, -1,
readline_so_path, func_off);
if (!obj->links.printret)
err = -errno;
warn("failed to attach readline: %d\\n", err);
goto cleanup;
//配置性能事件回调函数
pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
handle_event, handle_lost_events, NULL, NULL);
if (!pb)
err = -errno;
warn("failed to open perf buffer: %d\\n", err);
goto cleanup;
if (signal(SIGINT, sig_int) == SIG_ERR)
warn("can't set signal handler: %s\\n", strerror(errno));
err = 1;
goto cleanup;
printf("%-9s %-7s %s\\n", "TIME", "PID", "COMMAND");
while (!exiting)
//从缓冲区读取数据
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
if (err < 0 && err != -EINTR)
warn("error polling perf buffer: %s\\n", strerror(-err));
goto cleanup;
err = 0;
cleanup:
if (readline_so_path)
free(readline_so_path);
perf_buffer__free(pb);
bashreadline_bpf__destroy(obj);
cleanup_core_btf(&open_opts);
return err != 0;
编译为可执行文件
clang -g -O2 -Wall -I . -c bashreadline.c -o bashreadline.o
clang -Wall -O2 -g bashreadline.o -static -lbpf -lelf -lz -o bashreadline
总的Makefile文件
APPS = bashreadline
.PHONY: all
all: $(APPS)
$(APPS):
clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I/usr/include/x86_64-linux-gnu -I. -c $@.bpf.c -o $@.bpf.o
bpftool gen skeleton $@.bpf.o > $@.skel.h
clang -g -O2 -Wall -I . -c $@.c -o $@.o
clang -Wall -O2 -g $@.o -static -lbpf -lelf -lz -o $@
vmlinux:
$(bpftool) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
5、测试
root@root# ./bashreadline
TIME PID COMMAND
12:08:51 2897 ls
12:08:54 2897 cd
总结:
- bpftrace用在快速排除和定位系统上,简单的脚本开发并执行;
- BCC用在复杂的eBPF开发,使用最广泛;
- libbpf从内核中抽离出来的标准库,不在需要每台机器安装LLVM和内核头文件。
参考
bcc/libbpf-tools at master · iovisor/bcc · GitHub
bpftrace/reference_guide.md at master · iovisor/bpftrace · GitHub
以上是关于eBPF理解的主要内容,如果未能解决你的问题,请参考以下文章