Ubuntu 安装 libbpf 教程

libbpf C/C++库

libbpf 安装

 libbpf 编译

 参考资料


libbpf C/C++库

libbpf 库是一个基于 C/C++ 的通用 eBPF 库,它有助于将由 clang/LLVM 编译器生成的 eBPF 目标文件加载到内核中,并且通常通过提供易于使用的库 API 来抽象与 BPF 系统调用的交互应用程序。

libbpf 安装

1.获取最新的bcc代码

git clone https://github.com/iovisor/bcc.git  

 2.在bcc中获取libbpf和bpftool的代码

cd bcc
git submodule update --init --recursive

这时查看bcc/libbpf-tools/bpftool和bcc/src/cc/libbpf,发现也已经下载好了需要使用的源码。

3.回到bcc/libbpf-tools,运行make进行编译

 4.编译成功后,等待安装,并重启ubuntu。

5.开启BTF特性,以下两种方式均可。(如果没有打开,需要开启该选项并重新编译内核。如果编译过程中出现BTF的一个报错,尝试安装dwraves、libdwarves-devel包解决)

# zcat /proc/config.gz | grep BTF
CONFIG_DEBUG_INFO_BTF=y
# file /sys/kernel/btf/vmlinux
/sys/kernel/btf/vmlinux: data
//不支持BTF时,重新配置和编译内核
make menuconfig
//选中kernel hacking --> Compile-time checks and compiler option -->Generate BTF
//typeinfo后,打开编译开关CONFIG_DEBUG_INFO_BTF。

6.安装相关依赖,包括:

bpftool、libbpf、libbpf-devel、elf-utils、kernel-source

7.运行一下libbpf-tools中的工具,检测libbpf是否安装成功。运行结果如下:

 libbpf 编译

<app>.bpf.c 文件是BPF C代码,其中包含要在内核上下文中执行的逻辑;

<app>.c 是用户空间C代码,它在应用程序的整个生命周期中加载BPF代码并与其交互;

可选的<app> .h 是具有常见类型定义的头文件,并且由应用程序的BPF和用户空间代码共享。

有了 eBPF 程序,就可以使用 clang 和 bpftool 将其编译成 BPF 字节码,然后再生成其头文件编译内核态文件(此处以execsnoop为例)。

execsnoop.bpf.c

//execsnoop.bpf.c
// 包含头文件
#include "vmlinux.h"
#include "execsnoop.h"
#include <bpf/bpf_helpers.h>

static const struct event empty_event = { };

// 定义哈希映射
struct {
	__uint(type, BPF_MAP_TYPE_HASH);
	__uint(max_entries, 10240);
	__type(key, pid_t);
	__type(value, struct event);
} execs SEC(".maps");

// 定义性能事件映射
struct {
	__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
	__uint(key_size, sizeof(u32));
	__uint(value_size, sizeof(u32));
}events SEC(".maps");

// 定义sys_enter_execve跟踪点函数
SEC("tracepoint/syscalls/sys_enter_execve")
int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter
					   *ctx)
{
	struct event *event;
	const char **args = (const char **)(ctx->args[1]);
	const char *argp;

	// 查询PID
	u64 id = bpf_get_current_pid_tgid();
	pid_t pid = (pid_t) id;

	// 保存一个空的event到哈希映射中
	if (bpf_map_update_elem(&execs, &pid, &empty_event, BPF_NOEXIST)) {
		return 0;
	}
	event = bpf_map_lookup_elem(&execs, &pid);
	if (!event) {
		return 0;
	}
	// 初始化event变量
	event->pid = pid;
	event->args_count = 0;
	event->args_size = 0;

	// 查询第一个参数
	unsigned int ret = bpf_probe_read_user_str(event->args, ARGSIZE,
						   (const char *)ctx->args[0]);
	if (ret <= ARGSIZE) {
		event->args_size += ret;
	} else {
		/* write an empty string */
		event->args[0] = '\0';
		event->args_size++;
	}

	// 查询其他参数,使用pragma unroll控制循环次数
	event->args_count++;
#pragma unroll
	for (int i = 1; i < TOTAL_MAX_ARGS; i++) {
		bpf_probe_read_user(&argp, sizeof(argp), &args[i]);
		if (!argp)
			return 0;

		if (event->args_size > LAST_ARG)
			return 0;

		ret =
		    bpf_probe_read_user_str(&event->args[event->args_size],
					    ARGSIZE, argp);
		if (ret > ARGSIZE)
			return 0;

		event->args_count++;
		event->args_size += ret;
	}

	// 再尝试一次,确认是否还有未读取的参数
	bpf_probe_read_user(&argp, sizeof(argp), &args[TOTAL_MAX_ARGS]);
	if (!argp)
		return 0;

	// 如果还有未读取参数,则增加参数数量(用于输出"...")
	event->args_count++;

	return 0;
}

// 定义sys_exit_execve跟踪点函数
SEC("tracepoint/syscalls/sys_exit_execve")
int tracepoint__syscalls__sys_exit_execve(struct trace_event_raw_sys_exit *ctx)
{
	u64 id;
	pid_t pid;
	int ret;
	struct event *event;

	// 从哈希映射中查询进程基本信息
	id = bpf_get_current_pid_tgid();
	pid = (pid_t) id;
	event = bpf_map_lookup_elem(&execs, &pid);
	if (!event)
		return 0;

	// 更新返回值和进程名称
	ret = ctx->ret;
	event->retval = ret;
	bpf_get_current_comm(&event->comm, sizeof(event->comm));

	// 提交性能事件
	size_t len = EVENT_SIZE(event);
	if (len <= sizeof(*event))
		bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event,
				      len);

	// 清理哈希映射
	bpf_map_delete_elem(&execs, &pid);
	return 0;
}
// 定义许可证(前述的BCC默认使用GPL)
char LICENSE[] SEC("license") = "GPL";

execsnoop.c 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include <time.h>
#include "execsnoop.h"
#include "execsnoop.skel.h"

// libbpf错误和调试信息回调的处理程序。
static int libbpf_print_fn(enum libbpf_print_level level, const char *format,
			   va_list args)
{
#ifdef DEBUGBPF
	return vfprintf(stderr, format, args);
#else
	return 0;
#endif
}

// 丢失事件的处理程序。
void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
{
	fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
}

// 打印参数(替换'\0'为空格)
static void print_args(const struct event *e)
{
	int args_counter = 0;

	for (int i = 0; i < e->args_size && args_counter < e->args_count; i++) {
		char c = e->args[i];
		if (c == '\0') {
			args_counter++;
			putchar(' ');
		} else {
			putchar(c);
		}
	}
	if (e->args_count > TOTAL_MAX_ARGS) {
		fputs(" ...", stdout);
	}
}

// 性能事件回调函数(向终端中打印进程名、PID、返回值以及参数)
void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
{
	const struct event *e = data;
	printf("%-16s %-6d %3d ", e->comm, e->pid, e->retval);
	print_args(e);
	putchar('\n');
}

// Bump RLIMIT_MEMLOCK,允许BPF子系统做任何它需要的事情。  
static void bump_memlock_rlimit(void)
{
	struct rlimit rlim_new = {
		.rlim_cur = RLIM_INFINITY,
		.rlim_max = RLIM_INFINITY,
	};

	if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {
		fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!\n");
		exit(1);
	}
}

int main(int argc, char **argv)
{
	struct execsnoop_bpf *skel;
	struct perf_buffer_opts pb_opts;
	struct perf_buffer *pb = NULL;
	int err;

	// 1. 设置调试输出函数,libbpf发生错误会回调libbpf_print_fn
	libbpf_set_print(libbpf_print_fn);

	// 2. 增大进程限制的内存,默认值通常太小,不足以存入BPF映射的内容
	bump_memlock_rlimit();

	// 3. 打开BPF程序
	skel = execsnoop_bpf__open();
	if (!skel) {
		fprintf(stderr, "Failed to open BPF skeleton\n");
		return 1;
	}

	// 4. 加载BPF字节码
	err = execsnoop_bpf__load(skel);
	if (err) {
		fprintf(stderr, "Failed to load and verify BPF skeleton\n");
		goto cleanup;
	}

	// 5. 挂载BPF字节码到跟踪点
	err = execsnoop_bpf__attach(skel);
	if (err) {
		fprintf(stderr, "Failed to attach BPF skeleton\n");
		goto cleanup;
	}

	// 6. 配置性能事件回调函数
	pb_opts.sample_cb = handle_event;
	pb_opts.lost_cb = handle_lost_events;
	pb = perf_buffer__new(bpf_map__fd(skel->maps.events), 64, &pb_opts);
	err = libbpf_get_error(pb);
	if (err) {
		pb = NULL;
		fprintf(stderr, "failed to open perf buffer: %d\n", err);
		goto cleanup;
	}

	printf("%-16s %-6s %3s %s\n", "COMM", "PID", "RET", "ARGS");

	// 7. 从缓冲区中循环读取数据
	while ((err = perf_buffer__poll(pb, 100)) >= 0) ;
	printf("Error polling perf buffer: %d\n", err);

 cleanup:
	perf_buffer__free(pb);
	execsnoop_bpf__destroy(skel);
	return err != 0;
}

execsnoop.h

/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __EXECSNOOP_H
#define __EXECSNOOP_H

#define ARGSIZE  128
#define TASK_COMM_LEN 16
#define TOTAL_MAX_ARGS 60
#define DEFAULT_MAXARGS 20
#define FULL_MAX_ARGS_ARR (TOTAL_MAX_ARGS * ARGSIZE)
#define INVALID_UID ((uid_t)-1)
#define BASE_EVENT_SIZE (size_t)(&((struct event*)0)->args)
#define EVENT_SIZE(e) (BASE_EVENT_SIZE + e->args_size)
#define LAST_ARG (FULL_MAX_ARGS_ARR - ARGSIZE)

struct event {
	pid_t pid;
	pid_t ppid;
	uid_t uid;
	int retval;
	int args_count;
	unsigned int args_size;
	char comm[TASK_COMM_LEN];
	char args[FULL_MAX_ARGS_ARR];
};

#endif /* __EXECSNOOP_H */
//将execsnoop.bpf.c编译成execsnoop.bpf.o
clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I/usr/include/x86_64-linuxgnu -I. -c execsnoop.bpf.c -o execsnoop.bpf.o
//将execsnoop.bpf.o转换为execsnoop.skel.h
bpftool gen skeleton execsnoop.bpf.o > execsnoop.skel.h

编译用户态文件,得到execsnoop应用程序:

//编译用户态程序execsnoop.c和execsnoop.o
clang -g -O2 -Wall -I . -c execsnoop.c -o execsnoop.o
//链接成为可执行程序execsnoop
clang -Wall -O2 -g execsnoop.o -static -lbpf -lelf -lz -o execsnoop

 运行 execsnoop 应用程序

 参考资料

eBPF官方文档 - What is eBPF?

eBPF 入门之编程

eBPF学习记录(四)使用libbpf开发eBPF程序

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值