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 应用程序