eBPF理解

Posted 为了维护世界和平_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了eBPF理解相关的知识,希望对你有一定的参考价值。

目录

一、bpftrace

二、BCC方法

三、libbpf方法

总结:


一、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方法

github参考

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

总结:

  1. bpftrace用在快速排除和定位系统上,简单的脚本开发并执行;
  2. BCC用在复杂的eBPF开发,使用最广泛;
  3. libbpf从内核中抽离出来的标准库,不在需要每台机器安装LLVM和内核头文件。

参考

bcc/libbpf-tools at master · iovisor/bcc · GitHub

bpftrace/reference_guide.md at master · iovisor/bpftrace · GitHub


以上是关于eBPF理解的主要内容,如果未能解决你的问题,请参考以下文章

eBPF理解

eBPF理解

eBPF理解

eBPF理解

eBPF理解

基于 eBPF 的 Kubernetes 可观测实践