libbpf-bootstrap开发指南:使用ringbuf 进行通信 - bootstrap

目录

代码

        comm 数据结构部分(用于bpf&user 数据share)

BPF 程序部分

功能说明

头文件引入说明

bpf_probe_read_str 读取filename

bpf_ringbuf_submit 将信息提交到BPF 的 ring buffer 中

bpf_map_delete_elem

用户程序部分

ring_buffer__new

ring_buffer__poll

ring_buffer__free

执行效果


代码

comm 数据结构部分(用于bpf&user 数据share)
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2020 Facebook */
#ifndef __BOOTSTRAP_H
#define __BOOTSTRAP_H

#define TASK_COMM_LEN	 16
#define MAX_FILENAME_LEN 127

struct event {
	int pid;
	int ppid;
	unsigned exit_code;
	unsigned long long duration_ns;
	char comm[TASK_COMM_LEN];
	char filename[MAX_FILENAME_LEN];
	bool exit_event;
};

#endif /* __BOOTSTRAP_H */
BPF 程序部分
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "bootstrap.h"

char LICENSE[] SEC("license") = "Dual BSD/GPL";

struct {
	__uint(type, BPF_MAP_TYPE_HASH);
	__uint(max_entries, 8192);
	__type(key, pid_t);
	__type(value, u64);
} exec_start SEC(".maps");

struct {
	__uint(type, BPF_MAP_TYPE_RINGBUF);
	__uint(max_entries, 256 * 1024);
} rb SEC(".maps");

const volatile unsigned long long min_duration_ns = 0;

SEC("tp/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
{
	struct task_struct *task;
	unsigned fname_off;
	struct event *e;
	pid_t pid;
	u64 ts;

	/* remember time exec() was executed for this PID */
	pid = bpf_get_current_pid_tgid() >> 32;
	ts = bpf_ktime_get_ns();
	bpf_map_update_elem(&exec_start, &pid, &ts, BPF_ANY);

	/* don't emit exec events when minimum duration is specified */
	if (min_duration_ns)
		return 0;

	/* reserve sample from BPF ringbuf */
	e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
	if (!e)
		return 0;

	/* fill out the sample with data */
	task = (struct task_struct *)bpf_get_current_task();

	e->exit_event = false;
	e->pid = pid;
	e->ppid = BPF_CORE_READ(task, real_parent, tgid);
	bpf_get_current_comm(&e->comm, sizeof(e->comm));

	fname_off = ctx->__data_loc_filename & 0xFFFF;
	bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);

	/* successfully submit it to user-space for post-processing */
	bpf_ringbuf_submit(e, 0);
	return 0;
}

SEC("tp/sched/sched_process_exit")
int handle_exit(struct trace_event_raw_sched_process_template *ctx)
{
	struct task_struct *task;
	struct event *e;
	pid_t pid, tid;
	u64 id, ts, *start_ts, duration_ns = 0;

	/* get PID and TID of exiting thread/process */
	id = bpf_get_current_pid_tgid();
	pid = id >> 32;
	tid = (u32)id;

	/* ignore thread exits */
	if (pid != tid)
		return 0;

	/* if we recorded start of the process, calculate lifetime duration */
	start_ts = bpf_map_lookup_elem(&exec_start, &pid);
	if (start_ts)
		duration_ns = bpf_ktime_get_ns() - *start_ts;
	else if (min_duration_ns)
		return 0;
	bpf_map_delete_elem(&exec_start, &pid);

	/* if process didn't live long enough, return early */
	if (min_duration_ns && duration_ns < min_duration_ns)
		return 0;

	/* reserve sample from BPF ringbuf */
	e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
	if (!e)
		return 0;

	/* fill out the sample with data */
	task = (struct task_struct *)bpf_get_current_task();

	e->exit_event = true;
	e->duration_ns = duration_ns;
	e->pid = pid;
	e->ppid = BPF_CORE_READ(task, real_parent, tgid);
	e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff;
	bpf_get_current_comm(&e->comm, sizeof(e->comm));

	/* send data to user-space for post-processing */
	bpf_ringbuf_submit(e, 0);
	return 0;
}
功能说明

这个 BPF 程序的主要目标是追踪 Linux 中的进程执行和退出事件。它利用 tracepoint 机制跟踪 sched/sched_process_exec 和 sched/sched_process_exit 事件,然后记录相关信息。

头文件引入说明

#include <bpf/bpf_core_read.h> 这个头文件为 BPF 程序提供了 BPF_CORE_READ 宏,这个宏可以用来读取内核数据结构的字段值。这是 BPF CO-RE(Compile Once, Run Everywhere)技术的一部分,使得 BPF 程序能够在不同版本的内核上运行,即使这些内核的数据结构有所不同。

在给出的代码中,BPF_CORE_READ 被用来读取以下字段:

  1. BPF_CORE_READ(task, real_parent, tgid):读取当前进程的父进程的进程 ID (PID)。real_parent 是 task_struct 结构中的一个字段,表示进程的父进程,tgid 是 task_struct 结构中的一个字段,表示线程组 ID,对于单线程进程,它就是 PID。
  2. BPF_CORE_READ(task, exit_code):读取进程的退出状态码。exit_code 是 task_struct 结构中的一个字段,表示进程的退出状态码。

bpf_probe_read_str 读取filename
fname_off = ctx->__data_loc_filename & 0xFFFF;
bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);

这里需要先看一个结构:trace_event_raw_sched_process_exec

struct trace_event_raw_sched_process_exec {
	struct trace_entry ent;
	u32 __data_loc_filename;
	pid_t pid;
	pid_t old_pid;
	char __data[0];
};

这里其实是使用了柔性数组的一个技巧

  • struct trace_entry ent;:trace_entry 是一个在 Linux tracepoints 机制中用来表示一个 trace 事件的通用结构体。它包含了一些基本信息,如事件的时间戳、进程 ID(PID)、线程 ID(TID)等。
  • u32 __data_loc_filename;:这个字段是一个 32 位整数,其中包含文件名在上下文中的偏移量(低 16 位)和长度(高 16 位)。文件名是进程所执行的文件的名字。
  • pid_t pid;:这个字段表示新开始执行的进程的进程 ID (PID)。
  • pid_t old_pid;:这个字段表示原来正在执行的进程的进程 ID。当一个新的进程开始执行时,原来正在执行的进程会被替换掉。
  • char __data[0];:这是一个零长度的数组,用来表示结构体后面可能会跟着的数据。这种技巧在 C 中常用来表示一个可变长度的数据。在这个结构体中,__data 用来存储文件名的实际数据。文件名的位置可以通过 __data_loc_filename 字段确定。

因此想要知道实际的文件名的地址需要使用(void *)ctx + fname_off

bpf_probe_read_str 函数是 BPF(Berkeley Packet Filter)程序库中的一个函数,它用于从用户空间或者内核空间读取一个以 null 结尾的字符串。

int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr);
bpf_ringbuf_submit 将信息提交到BPF 的 ring buffer 中

BPF ring buffer 是一种在内核和用户空间之间传输数据的机制。BPF 程序在内核中运行,当它们需要将数据传输到用户空间时(例如,将数据传输给一个监控工具或者诊断工具),它们可以将数据写入 ring buffer,然后用户空间的程序可以从 ring buffer 中读取数据。

bpf_ringbuf_submit 函数就是用来将数据写入 ring buffer 的。在这个函数被调用之后,用户空间的程序就可以从 ring buffer 中读取 e 中的数据了。这种机制可以使得 BPF 程序能够将数据高效地传输到用户空间,而不需要使用比较复杂和耗时的系统调用。

void bpf_ringbuf_submit(void *data, u64 flags);

参数解释如下:

  • void *data:这是一个指向你想要提交到 ring buffer 的数据的指针。这通常是一个指向你的 BPF 程序中的结构体的指针,该结构体包含了你想要传输到用户空间的数据。
  • u64 flags:这是一个标志位,用于控制函数的行为。到目前为止,这个参数应该总是设置为 0,因为目前还没有定义任何标志。

bpf_map_delete_elem

从 exec_start map 中删除记录,因为进程已经退出,不再需要这个记录。

用户程序部分
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
#include <argp.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "bootstrap.h"
#include "bootstrap.skel.h"

static struct env {
	bool verbose;
	long min_duration_ms;
} env;

const char *argp_program_version = "bootstrap 0.0";
const char *argp_program_bug_address = "<bpf@vger.kernel.org>";
const char argp_program_doc[] = "BPF bootstrap demo application.\n"
				"\n"
				"It traces process start and exits and shows associated \n"
				"information (filename, process duration, PID and PPID, etc).\n"
				"\n"
				"USAGE: ./bootstrap [-d <min-duration-ms>] [-v]\n";

static const struct argp_option opts[] = {
	{ "verbose", 'v', NULL, 0, "Verbose debug output" },
	{ "duration", 'd', "DURATION-MS", 0, "Minimum process duration (ms) to report" },
	{},
};

static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
	switch (key) {
	case 'v':
		env.verbose = true;
		break;
	case 'd':
		errno = 0;
		env.min_duration_ms = strtol(arg, NULL, 10);
		if (errno || env.min_duration_ms <= 0) {
			fprintf(stderr, "Invalid duration: %s\n", arg);
			argp_usage(state);
		}
		break;
	case ARGP_KEY_ARG:
		argp_usage(state);
		break;
	default:
		return ARGP_ERR_UNKNOWN;
	}
	return 0;
}

static const struct argp argp = {
	.options = opts,
	.parser = parse_arg,
	.doc = argp_program_doc,
};

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
	if (level == LIBBPF_DEBUG && !env.verbose)
		return 0;
	return vfprintf(stderr, format, args);
}

static volatile bool exiting = false;

static void sig_handler(int sig)
{
	exiting = true;
}

static int handle_event(void *ctx, void *data, size_t data_sz)
{
	const struct event *e = data;
	struct tm *tm;
	char ts[32];
	time_t t;

	time(&t);
	tm = localtime(&t);
	strftime(ts, sizeof(ts), "%H:%M:%S", tm);

	if (e->exit_event) {
		printf("%-8s %-5s %-16s %-7d %-7d [%u]", ts, "EXIT", e->comm, e->pid, e->ppid,
		       e->exit_code);
		if (e->duration_ns)
			printf(" (%llums)", e->duration_ns / 1000000);
		printf("\n");
	} else {
		printf("%-8s %-5s %-16s %-7d %-7d %s\n", ts, "EXEC", e->comm, e->pid, e->ppid,
		       e->filename);
	}

	return 0;
}

int main(int argc, char **argv)
{
	struct ring_buffer *rb = NULL;
	struct bootstrap_bpf *skel;
	int err;

	/* Parse command line arguments */
	err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
	if (err)
		return err;

	/* Set up libbpf errors and debug info callback */
	libbpf_set_print(libbpf_print_fn);

	/* Cleaner handling of Ctrl-C */
	signal(SIGINT, sig_handler);
	signal(SIGTERM, sig_handler);

	/* Load and verify BPF application */
	skel = bootstrap_bpf__open();
	if (!skel) {
		fprintf(stderr, "Failed to open and load BPF skeleton\n");
		return 1;
	}

	/* Parameterize BPF code with minimum duration parameter */
	skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL;

	/* Load & verify BPF programs */
	err = bootstrap_bpf__load(skel);
	if (err) {
		fprintf(stderr, "Failed to load and verify BPF skeleton\n");
		goto cleanup;
	}

	/* Attach tracepoints */
	err = bootstrap_bpf__attach(skel);
	if (err) {
		fprintf(stderr, "Failed to attach BPF skeleton\n");
		goto cleanup;
	}

	/* Set up ring buffer polling */
	rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
	if (!rb) {
		err = -1;
		fprintf(stderr, "Failed to create ring buffer\n");
		goto cleanup;
	}

	/* Process events */
	printf("%-8s %-5s %-16s %-7s %-7s %s\n", "TIME", "EVENT", "COMM", "PID", "PPID",
	       "FILENAME/EXIT CODE");
	while (!exiting) {
		err = ring_buffer__poll(rb, 100 /* timeout, ms */);
		/* Ctrl-C will cause -EINTR */
		if (err == -EINTR) {
			err = 0;
			break;
		}
		if (err < 0) {
			printf("Error polling perf buffer: %d\n", err);
			break;
		}
	}

cleanup:
	/* Clean up */
	ring_buffer__free(rb);
	bootstrap_bpf__destroy(skel);

	return err < 0 ? -err : 0;
}
ring_buffer__new

rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);

  1. bpf_map__fd(skel->maps.rb):这个函数调用获取了 BPF map 的文件描述符,该 map 是在 BPF 程序中定义的 ring buffer。skel->maps.rb 是这个 ring buffer map 的引用,其中 skel 是加载并验证 BPF 程序结构体的实例。
  2. handle_event:这是一个函数指针,指向的函数会在每次从 ring buffer 读取数据时被调用。在这个例子中,handle_event 函数会处理从 BPF 程序发送过来的每一个事件。
  3. NULL:这两个 NULL 参数分别代表了 ring buffer 的上下文对象和析构函数。在这个例子中,它们都没有用到,所以被设置为 NULL。
  4. ring_buffer__new:这个函数创建了一个新的 ring buffer 实例。这个新实例将会用来读取从 BPF 程序发送过来的数据。

如果 ring buffer 创建成功,ring_buffer__new 函数将返回一个指向新 ring buffer 的指针。如果创建失败,它将返回 NULL。在这个例子中,返回的指针被存储在 rb 变量中。

ring_buffer__poll

目的是检查 ring buffer 中是否有新数据,这些数据可能来自 BPF 程序。如果有新数据,ring_buffer__poll 函数将触发在创建 ring buffer 时指定的回调函数 (handle_event),并将新数据作为参数传递给该函数。

下面是对这行代码的详细解释:

err = ring_buffer__poll(rb, 100 /* timeout, ms */);

  1. rb:这是一个指向 ring buffer 的指针,该 ring buffer 是我们要轮询的目标。
  2. 100:这是轮询操作的超时时间,单位是毫秒。这意味着如果在 100 毫秒内 ring buffer 中没有新的数据,ring_buffer__poll 函数将返回一个超时错误。
  3. ring_buffer__poll:这个函数阻塞当前线程,直到以下条件之一满足:ring buffer 中有新数据,超时(在这个例子中是 100 毫秒),或者收到了一个中断信号。
  4. err:这个变量存储了 ring_buffer__poll 函数的返回值。如果函数成功地从 ring buffer 读取了数据,它将返回读取的事件数量。如果发生了超时,它将返回 0。如果发生了错误(例如由于中断),它将返回一个负的错误代码。
ring_buffer__free

ring_buffer__free 是一个用于释放之前通过 ring_buffer__new 创建的 ring buffer 的函数。

执行效果

TIME     EVENT COMM             PID     PPID    FILENAME/EXIT CODE
21:29:15 EXEC  gio-launch-desk  34135   2947    /usr/lib/x86_64-linux-gnu/glib-2.0/gio-launch-desktop
21:29:15 EXEC  google-chrome-s  34135   2947    /usr/bin/google-chrome-stable
21:29:15 EXEC  readlink         34138   34135   /usr/bin/readlink
21:29:15 EXIT  readlink         34138   34135   [0] (0ms)
21:29:15 EXEC  dirname          34139   34135   /usr/bin/dirname
21:29:15 EXIT  dirname          34139   34135   [0] (0ms)
21:29:15 EXEC  mkdir            34140   34135   /usr/bin/mkdir
21:29:15 EXIT  mkdir            34140   34135   [0] (0ms)
21:29:15 EXEC  cat              34141   34135   /usr/bin/cat
21:29:15 EXEC  chrome           34135   2947    /opt/google/chrome/chrome
21:29:15 EXEC  cat              34142   34135   /usr/bin/cat
21:29:15 EXEC  chrome_crashpad  34144   34143   /opt/google/chrome/chrome_crashpad_handler
21:29:15 EXIT  chrome           34143   34135   [0]
21:29:15 EXEC  chrome_crashpad  34146   34145   /opt/google/chrome/chrome_crashpad_handler
21:29:15 EXIT  chrome_crashpad  34145   34144   [0]
21:29:15 EXIT  chrome           34151   34135   [0]
21:29:15 EXEC  chrome           34152   34135   /opt/google/chrome/chrome
21:29:15 EXEC  chrome           34153   34135   /opt/google/chrome/chrome
21:29:15 EXEC  nacl_helper      34154   34153   /opt/google/chrome/nacl_helper

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值