目前eBPF存在几个开发工具链来协助 eBPF 程序的开发和管理,用于满足用户的不同需求:
本篇我们将对几种常用开发工具进行介绍。
bpftrace 是 Linux eBPF 的高级跟踪语言,可用于最新的 Linux 内核。 bpftrace 使用 LLVM 作为后端将脚本编译为 eBPF 字节码,并利用 BCC 与 Linux eBPF 子系统以及现有的 Linux 跟踪功能进行交互:内核动态跟踪 (kprobes)、用户级动态跟踪 (uprobes) 和跟踪点。bpftrace 语言的灵感来自 awk、C 和前身跟踪器,例如 DTrace 和 SystemTap。
apt-get install bpftrace
1)列出所有插桩点
bpftrace -l
2)添加查询条件,例如只查询xxx类型的插桩点
bpftrace -l 'xxx:*'
例:
root@ubuntu:~# bpftrace -l "tracepoint:*"
tracepoint:vsock:virtio_transport_alloc_pkt
tracepoint:vsock:virtio_transport_recv_pkt
tracepoint:vb2:vb2_buf_done
...
3)查询插桩点声明
cat /sys/kernel/debug/tracing/events/syscalls/xxx/format
root@ubuntu:~# cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
name: sys_enter_execve
ID: 716
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:int __syscall_nr; offset:8; size:4; signed:1;
field:const char * filename; offset:16; size:8; signed:0;
field:const char *const * argv; offset:24; size:8; signed:0;
field:const char *const * envp; offset:32; size:8; signed:0;
print fmt: "filename: 0x%08lx, argv: 0x%08lx, envp: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->argv)), ((unsigned long)(REC->envp))
bpftrace -lv xxx
root@ubuntu:~# 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;
4)跟踪execve系统调用,监控enter状态的命令与参数
bpftrace -e 'tracepoint:syscalls:sys_enter_execve{printf("pid=%d called=%s filename=%s argv=",pid,comm,str(args->filename));join(args->argv);}'
5)跟踪execve系统调用,监控exit状态的返回值
bpftrace -e 'tracepoint:syscalls:sys_exit_execve{printf("pid=%d command=%s ret=%d \n",pid,comm,args->ret);}'
6)查询内核函数调用栈(ctrl+c后显示结果)
sudo bpftrace -e 'kprobe:vfs_open {@[kstack(perf)] = count();}'
7)追踪自定义程序
/*
/* Filename: test.c
/* Compile: gcc -g test.c -o test
*/
#include
int main(int argc, char **argv)
{
printf("hello world!\n");
return 0;
}
追踪main函数,打印参数个数和第一个参数:
bpftrace -e 'uprobe:/Work/Project/eBPF/bpftrace/test:main{printf("argc=%d argv[0]=",arg0);join(arg1);}'
追踪main函数,打印返回值:
sudo bpftrace -e 'uretprobe:/Work/Project/eBPF/bpftrace/test:main{printf("main:return_value=%d\n",retval);}'
8)跟踪开源应用bash
查询bash指令的位置:
root@ubuntu:~# which bash
/usr/bin/bash
查询bash中与readline相关的跟踪点:
root@ubuntu:~# bpftrace -l "uprobe:/usr/bin/bash:*" | grep readline
uprobe:/usr/bin/bash:readline_internal_char
uprobe:/usr/bin/bash:readline_internal_setup
uprobe:/usr/bin/bash:posix_readline_initialize
uprobe:/usr/bin/bash:readline
uprobe:/usr/bin/bash:initialize_readline
uprobe:/usr/bin/bash:pcomp_set_readline_variables
uprobe:/usr/bin/bash:readline_internal_teardown
跟踪bash应用的readline探测点,列出用户与命令:
root@ubuntu:~# bpftrace -e 'uretprobe:/usr/bin/bash:readline{printf("User %d executed \"%s\" command\n",uid,str(retval));}'
Attaching 1 probe...
User 0 executed "ls" command
...
9)探测软件事件
查询探针software支持的事件列表:
root@ubuntu:~# bpftrace -l 's:*'
software:alignment-faults:
software:bpf-output:
software:context-switches:
software:cpu-clock:
software:cpu-migrations:
software:dummy:
software:emulation-faults:
software:major-faults:
software:minor-faults:
software:page-faults:
software:task-clock:
探测page-faults:100事件(ctrl+c后显示结果):
bpftrace -e 'software:page-faults:100{@[comm]=count();}'
10)探测硬件事件
查询探针hardware支持的事件列表:
root@ubuntu:~# bpftrace -l 'h:*'
hardware:backend-stalls:
hardware:branch-instructions:
hardware:branch-misses:
hardware:bus-cycles:
hardware:cache-misses:
hardware:cache-references:
hardware:cpu-cycles:
hardware:frontend-stalls:
hardware:instructions:
hardware:ref-cycles:
尝试统计30秒内cache-missed次数超过1000000的进程,不知道为什么跟踪hardware所有事件都报错:
bpftrace -e 'hardware:cache-misses:1000000{@[pid]=count();}interval:s:30{exit();}'
将命令编写为脚本模式,文件名:script.bt
#!/usr/bin/env bpftrace
tracepoint:syscalls:sys_enter_execve
{
printf("pid=%d command=%s called=%s \n",pid,comm,str(args->filename));
}
运行效果:
BCC 是一个框架,使用户能够编写带有嵌入其中的 eBPF 程序的 python 程序。 该框架主要针对涉及应用程序和系统分析/跟踪的用例,其中 eBPF 程序用于收集统计信息或生成事件,而用户空间中的对应物收集数据并以人类可读的形式显示。 运行 python 程序将生成 eBPF 字节码并将其加载到内核中。
可以参考官方提供的安装文档或尝试使用以下命令安装:
sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
文件名:hello.c
int hello_world(void *ctx)
{
bpf_trace_printk("Hello, World!");
return 0;
}
文件名:hello.py
#!/usr/bin/env python3
# 1) import bcc library
from bcc import BPF
# 2) load BPF program
b = BPF(src_file="hello.c", cflags=["-Wno-macro-redefined"])
# 3) attach kprobe
b.attach_kprobe(event="__x64_sys_execve", fn_name="hello_world")
# 4) read and print /sys/kernel/debug/tracing/trace_pipe
b.trace_print()
运行效果:
Map映射能够将BPF数据在内核态与用户态互相映射,部分细节笔者还没有研究得很透彻。
文件名:open.c
#include
#include
// 定义数据结构
struct data_t {
u32 pid;
u64 ts;
char comm[TASK_COMM_LEN];
char fname[NAME_MAX];
};
// 定义性能事件映射
BPF_PERF_OUTPUT(events);
// 定义kprobe处理函数
int bcc_do_sys_openat2(struct pt_regs *ctx, int dfd, const char __user * filename, struct open_how *how)
{
struct data_t data = { };
// 获取PID和时间
data.pid = bpf_get_current_pid_tgid();
data.ts = bpf_ktime_get_ns();
// 获取进程名
if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0)
{
bpf_probe_read(&data.fname, sizeof(data.fname), (void *)filename);
}
// 提交性能事件
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
文件名:open.py
from bcc import BPF
# 1) 加载eBPF代码
b = BPF(src_file="open.c", cflags=["-Wno-macro-redefined"])
b.attach_kprobe(event="do_sys_openat2", fn_name="bcc_do_sys_openat2")
# 2) 输出头
print("%-18s %-16s %-6s %-16s" % ("TIME(s)", "COMM", "PID", "FILE"))
# 3) 定义性能事件打印函数
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 %-16s" % (time_s, event.comm, event.pid, event.fname))
# 4) 绑定性能事件映射和输出函数,并从映射中循环读取数据
b["events"].open_perf_buffer(print_event)
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
运行效果:
Hash映射能使多个跟踪点之间互相共享数据,例如让sys_enter_execve和sys_exit_execve共享同一段数据。
文件名:execsnoop.c
#include
#include
#include
// consts for arguments (ensure below stack size limit 512)
#define ARGSIZE 64
#define TOTAL_MAX_ARGS 5
#define FULL_MAX_ARGS_ARR (TOTAL_MAX_ARGS * ARGSIZE)
#define LAST_ARG (FULL_MAX_ARGS_ARR - ARGSIZE)
// perf event map (sharing data to userspace) and hash map (sharing data between tracepoints)
struct data_t {
u32 pid;
char comm[TASK_COMM_LEN];
int retval;
unsigned int args_size;
char argv[FULL_MAX_ARGS_ARR];
};
BPF_PERF_OUTPUT(events);
BPF_HASH(tasks, u32, struct data_t);
// helper function to read string from userspace.
static int __bpf_read_arg_str(struct data_t *data, const char *ptr)
{
if (data->args_size > LAST_ARG) {
return -1;
}
int ret = bpf_probe_read_user_str(&data->argv[data->args_size], ARGSIZE,
(void *)ptr);
if (ret > ARGSIZE || ret < 0) {
return -1;
}
// increase the args size. the first tailing '\0' is not counted and hence it
// would be overwritten by the next call.
data->args_size += (ret - 1);
return 0;
}
//定义sys_enter_execve跟踪点处理函数.
TRACEPOINT_PROBE(syscalls, sys_enter_execve)
{
// 变量定义
unsigned int ret = 0;
const char **argv = (const char **)(args->argv);
// 获取进程PID和进程名称
struct data_t data = { };
u32 pid = bpf_get_current_pid_tgid();
data.pid = pid;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
// 获取第一个参数(即可执行文件的名字)
if (__bpf_read_arg_str(&data, (const char *)argv[0]) < 0) {
goto out;
}
// 获取其他参数(限定最多5个)
for (int i = 1; i < TOTAL_MAX_ARGS; i++) {
if (__bpf_read_arg_str(&data, (const char *)argv[i]) < 0) {
goto out;
}
}
out:
// 存储到哈希映射中
tasks.update(&pid, &data);
return 0;
}
// 定义sys_exit_execve跟踪点处理函数.
TRACEPOINT_PROBE(syscalls, sys_exit_execve)
{
// 从哈希映射中查询进程基本信息
u32 pid = bpf_get_current_pid_tgid();
struct data_t *data = tasks.lookup(&pid);
// 填充返回值并提交到性能事件映射中
if (data != NULL) {
data->retval = args->ret;
events.perf_submit(args, data, sizeof(struct data_t));
// 最后清理进程信息
tasks.delete(&pid);
}
return 0;
}
文件名:execsnoop.py
from bcc import BPF
from bcc.utils import printb
# 1) 加载eBPF代码
b = BPF(src_file="execsnoop.c", cflags=["-Wno-macro-redefined"])
# 2) print header
print("%-6s %-16s %-3s %s" % ("PID", "COMM", "RET", "ARGS"))
# 3) 定义性能事件打印函数
def print_event(cpu, data, size):
# BCC自动根据"struct data_t"生成数据结构
event = b["events"].event(data)
printb(b"%-6d %-16s %-3d %-16s" % (event.pid, event.comm, event.retval, event.argv))
# 4) 绑定性能事件映射和输出函数,并从映射中循环读取数据
b["events"].open_perf_buffer(print_event)
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
运行效果:
libbpf 库是一个基于 C/C++ 的通用 eBPF 库,它有助于将由 clang/LLVM 编译器生成的 eBPF 目标文件加载到内核中,并且通常通过提供易于使用的库 API 来抽象与 BPF 系统调用的交互应用程序。
方法一:使用apt安装相关模块
apt-get install libbpf-dev
方法二:使用源码构建静态libbpf.a和共享libbpf.so,尽量选择与当前系统内核版本相近的源码版本
cd src
make
CO-RE (Compile Once – Run Everywhere)中文译为”一次编译,到处运行“。使用libbpf编译的程序,只要目标服务器内核支持BPF,即可将程序在不依赖LLVM、CLANG及内核头文件的情况下部署到目标服务器。
Libbpf+BPF CO-RE的理念是,BPF程序与任何"正常"用户空间程序没有太大区别:它们应该汇编成小型二进制文件,然后以紧凑的形式进行部署,以瞄准主机。Libbpf 扮演 BPF 程序装载机的角色,执行平凡的设置工作(重定位、加载和验证 BPF 程序、创建 BPF map、连接到 BPF 挂钩等),让开发人员只担心 BPF 程序的正确性和性能。这种方法将开销保持在最低水平,消除沉重的依赖关系,使整体开发人员体验更加愉快。
1)生成内核头文件
sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
2)开发内核态程序,生成脚手架文件(包含各项声明与BPF字节码,解除环境依赖)
文件名:execsnoop.h
#ifndef __EXECSNOOP_H
#define __EXECSNOOP_H
#define ARGSIZE 128
#define TASK_COMM_LEN 16
#define TOTAL_MAX_ARGS 60
#define FULL_MAX_ARGS_ARR (TOTAL_MAX_ARGS * ARGSIZE)
#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 {
char comm[TASK_COMM_LEN];
pid_t pid;
int retval;
int args_count;
unsigned int args_size;
char args[FULL_MAX_ARGS_ARR];
};
#endif /* __EXECSNOOP_H */
文件名:execsnoop.bpf.c
#include "vmlinux.h"
#include "execsnoop.h"
#include
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";
编译命令:
# clang -g -O2 -target bpf -D__TARGET_ARCH_x86_64 -I/usr/include/x86_64-linux-gnu -I. -c execsnoop.bpf.c -o execsnoop.bpf.o
# bpftool gen skeleton execsnoop.bpf.o > execsnoop.skel.h
3)开发用户态程序,处理收集的信息
文件名:execsnoop.c
#include
#include
#include
#include
#include
#include
#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;
}
编译命令:
# clang -g -O2 -Wall -I . -c execsnoop.c -o execsnoop.o
# clang -Wall -O2 -g execsnoop.o -static -lbpf -lelf -lz -o execsnoop
运行效果:
libbpf 在使用上并不是很直观,所以 eBPF 维护者开发了一个脚手架项目 libbpf-bootstrap,它结合了 BPF 社区的最佳开发实践,为初学者提供了一个简单易用的上手框架。
apt install clang libelf1 libelf-dev zlib1g-dev
# checkout libbpf-bootstrap
git clone https://github.com/libbpf/libbpf-bootstrap
# update submodules
cd libbpf-bootstrap
git submodule update --init --recursive
# build existing samples
cd example/c
make
文件名:hello.bpf.c
#include
#include
SEC("tracepoint/syscalls/sys_enter_execve")
int handle_tp(void *ctx)
{
int pid = bpf_get_current_pid_tgid()>> 32;
char fmt[] = "BPF triggered from PID %d.\n";
bpf_trace_printk(fmt, sizeof(fmt), pid);
return 0;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";
文件名:hello.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "hello.skel.h"
#define DEBUGFS "/sys/kernel/debug/tracing/"
/* logging function used for debugging */
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
}
/* read trace logs from debug fs */
void read_trace_pipe(void)
{
int trace_fd;
trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
if (trace_fd < 0)
return;
while (1) {
static char buf[4096];
ssize_t sz;
sz = read(trace_fd, buf, sizeof(buf) - 1);
if (sz> 0) {
buf[sz] = 0;
puts(buf);
}
}
}
/* set rlimit (required for every app) */
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 hello_bpf *skel;
int err;
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Bump RLIMIT_MEMLOCK to allow BPF sub-system to do anything */
bump_memlock_rlimit();
/* Open BPF application */
skel = hello_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}
/* Load & verify BPF programs */
err = hello_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
/* Attach tracepoint handler */
err = hello_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
printf("Hello BPF started, hit Ctrl+C to stop!\n");
read_trace_pipe();
cleanup:
hello_bpf__destroy(skel);
return -err;
}
更新Makefile中待编译的APP列表:
…
APPS = minimal minimal_legacy bootstrap uprobe kprobe fentry usdt sockfilter tchello
…
编译:
运行效果: