国产系统uos系统内核版本低(4.19)的情况下不支持BTF,导致开发的ebpf程序无法被libbpf无法加载,因为新版本的libbpf依赖BTF文件,导致无法开发ebpf程序,在摸索后发现 uos linux 4.19 + Clang 13 + libbpf 0.6 这个组合环境下可以开发ebpf程序。
ebpf程序开发步骤流程
| 步骤 | 关键动作 | 工具/命令 |
|---|---|---|
| 1. 环境准备 | 安装 clang、llvm、libelf、内核头文件 | apt install clang llvm libelf-dev linux-headers-$(uname -r) |
| 2. 编写 BPF C 代码 | 用内核头文件定义结构体;禁用 CO-RE 属性 | #include <linux/sched.h> |
| 3. 编译字节码 | 指定目标架构、内核头文件路径 | clang -target bpf -O2 -c prog.bpf.c -o prog.bpf.o |
| 4. 编写用户态加载器 | 用 libbpf API 打开、加载、attach | bpf_object__open_file() → bpf_object__load() → bpf_program__attach() |
| 5. 链接 & 运行 | 把 .bpf.o 当作资源嵌入二进制;运行时灌入内核 |
gcc -o loader loader.c -lbpf -lelf |
注意:kylin 5.4.18-75版本以上支持BTF(CONFIG_DEBUG_INFO_BTF )
开发环境准备
系统环境
shell
admin@admin-UOS-PC:~/myebpf/first_demo$ cat /etc/os-version
[Version]
SystemName=UnionTech OS Desktop
SystemName[zh_CN]=统信桌面操作系统
ProductType=Desktop
ProductType[zh_CN]=桌面
EditionName=Professional
EditionName[zh_CN]=专业版
MajorVersion=20
MinorVersion=1050
OsBuild=11018.101
admin@admin-UOS-PC:~/myebpf/first_demo$ uname -a
Linux admin-UOS-PC 4.19.0-amd64-desktop #5032 SMP Mon Mar 14 13:51:18 CST 2022 x86_64 GNU/Linux
开发环境安装(Linux头文件、clang,llm,bpftool)
shell
# 1. 准备好头文件与基本工具
sudo apt update
sudo apt install -y build-essential git libelf-dev clang llvm \
linux-headers-$(uname -r)
sudo apt install -y linux-source-$(uname -r)
# 2. 进入当前内核头文件自带的 bpftool 源码目录
cd /usr/src/linux-source-$(uname -r)/tools/bpf/bpftool
make # 生成可执行文件
sudo make install # 安装到 /usr/local/sbin/
sudo ln -s /usr/local/sbin/bpftool /usr/sbin/bpftool # 可选:加进默认 PATH
# 3. 验证
bpftool version
编译bpftool遇到如下报错
shell
main.c:34:10: fatal error: bfd.h: 没有那个文件或目录
#include <bfd.h>
^~~~~~~
compilation terminated.
执行以下命令安装依赖
shell
sudo apt install -y libbfd-dev libcap-dev
安装完后可以验证
shell
admin@admin-UOS-PC:/usr/src/linux-4.19/tools/bpf/bpftool$ bpftool version
bpftool v4.19.67
查看所有的ebpf程序
shell
sudo bpftool prog list
以json格式打印
shell
sudo bpftool prog list -p
查看系统调用
shell
sudo ls /sys/kernel/debug/tracing/events/syscalls/ | grep getdents
安装libbpf-dev,建议源码安装,可以选择版本
shell
admin@admin-UOS-PC:~$ sudo apt install libbpf-dev
正在读取软件包列表... 完成
正在分析软件包的依赖关系树
正在读取状态信息... 完成
下列软件包是自动安装的并且现在不需要了:
fbterm imageworsener libheif1 liblqr-1-0 libmaxminddb0 libqtermwidget5-0 libsmi2ldbl libutempter0 libutf8proc2 libwireshark-data libwireshark11 libwiretap8 libwscodecs2 libwsutil9 libx86-1
qtermwidget5-data squashfs-tools x11-apps x11-session-utils xbitmaps xinit
使用'sudo apt autoremove'来卸载它(它们)。
将会同时安装下列软件:
libbpf4.19
下列【新】软件包将被安装:
libbpf-dev libbpf4.19
升级了 0 个软件包,新安装了 2 个软件包,要卸载 0 个软件包,有 1184 个软件包未被升级。
需要下载 672 kB 的归档。
解压缩后会消耗 836 kB 的额外空间。
您希望继续执行吗? [Y/n] y
获取:1 https://professional-packages.chinauos.com/desktop-professional eagle/main amd64 libbpf4.19 amd64 4.19.67-13eagle [335 kB]
获取:2 https://professional-packages.chinauos.com/desktop-professional eagle/main amd64 libbpf-dev amd64 4.19.67-13eagle [337 kB]
已下载 672 kB,耗时 3秒 (240 kB/s)
正在选中未选择的软件包 libbpf4.19:amd64。
(正在读取数据库 ... 系统当前共安装有 204616 个文件和目录。)
准备解压 .../libbpf4.19_4.19.67-13eagle_amd64.deb ...
正在解压 libbpf4.19:amd64 (4.19.67-13eagle) ...
正在选中未选择的软件包 libbpf-dev:amd64。
准备解压 .../libbpf-dev_4.19.67-13eagle_amd64.deb ...
正在解压 libbpf-dev:amd64 (4.19.67-13eagle) ...
正在设置 libbpf4.19:amd64 (4.19.67-13eagle) ...
正在设置 libbpf-dev:amd64 (4.19.67-13eagle) ...
正在处理用于 libc-bin (2.28.31-deepin1) 的触发器 ...
禁用 tracepoint 挂载的 eBPF 程序
shell
echo 0 | sudo tee /sys/kernel/debug/tracing/events/syscalls/sys_enter_getdents64/enable
bpftool 常用命令
# 列出程序
sudo bpftool prog show
# 列出 map
sudo bpftool map show
# pin map/prog
sudo bpftool map pin id <id> /sys/fs/bpf/<map_name>
sudo bpftool prog pin id <id> /sys/fs/bpf/<prog_name>
libbpf源码安装
shell
git clone --recurse-submodules https://github.com/libbpf/libbpf.git
#切换不同tag
git tag
git checkout tags/v0.7.0 -b v0.7.0
cd libbpf-master/src
make
sudo make install
#库文件路径/usr/lib64/,头文件路径/usr/include
读日志
shell
# 读日志
sudo cat /sys/kernel/debug/tracing/trace_pipe
bpftool prog tracelog
查看可用 tracepoint
shell
sudo cat /sys/kernel/debug/tracing/available_events | grep sys_enter_openat
sudo cat /sys/kernel/debug/tracing/available_events | grep sys_enter_unlinkat
查看bpf字节码文件
shell
admin@admin-UOS-PC:~/myebpf/first_demo$ objdump -h file_create.bpf.o
file_create.bpf.o: 文件格式 elf64-little
节:
Idx Name Size VMA LMA File off Algn
0 .text 00000000 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 tracepoint/syscalls/sys_enter_openat 00000320 0000000000000000 0000000000000000 00000040 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
2 tracepoint/syscalls/sys_enter_unlinkat 00000320 0000000000000000 0000000000000000 00000360 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
3 .maps 00000010 0000000000000000 0000000000000000 00000680 2**3
CONTENTS, ALLOC, LOAD, DATA
4 license 00000004 0000000000000000 0000000000000000 00000690 2**0
CONTENTS, ALLOC, LOAD, DATA
5 version 00000004 0000000000000000 0000000000000000 00000694 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .llvm_addrsig 00000005 0000000000000000 0000000000000000 00000748 2**0
CONTENTS, READONLY, EXCLUDE
验证是否生成了 BTF
readelf -S file_create.bpf.o | grep .BTF
git切换分支
shell
git checkout tags/v1.6.2 -b v1.6.2
git submodule update --init --recursive
使用内核源码结构编写bpf(选)
Linux源码中的bpf程序编译源码中的samples/bpf
shell
cd /usr/src/linux-source-4.19
# 1. 把当前运行内核的配置搬过来
cp /boot/config-$(uname -r) .config
# 2. 生成配置头文件 & 工具链脚本
make oldconfig && make prepare
按照示例写自己的.kern.c和.user.c,修改Makefile文件即可
tracepoint和kprobe如何选择
| 维度 | tracepoint | kprobe / kretprobe |
|---|---|---|
| 稳定性 | ★★★ 官方 ABI,跨内核不变 | ★★ 函数名/签名随内核漂移 |
| 性能开销 | ★★★ 静态插桩,几乎零成本 | ★★ 动态断点,略高 |
| 数据格式 | 固定结构体,字段已定义 | 自己读寄存器/栈,易错 |
| 系统调用 | 优先 sys_enter_* / sys_exit_* |
不建议(受 ARCH_HAS_SYSCALL_WRAPPER 影响) |
| 黑名单/内联限制 | 无 | 有(不能 attach 内联函数,部分函数在黑名单) |
| 适用场景 | 审计、观测、常驻监控 | 调试、抓内部实现、无 tracepoint 时兜底 |
数据结构
trace_event_raw_sys_enter用于 sys_enter 系列 tracepoint,即"系统调用进入"时的上下文结构。
| 成员 | 类型 | 含义 |
|---|---|---|
struct trace_entry ent |
内核公共 tracepoint 头部 | 包含事件的基本信息(时间戳、CPU、类型等)。在 eBPF 中几乎不用直接访问。 |
long id |
系统调用号 | 表示是哪一个系统调用,例如 openat 的 syscall id。可以用于区分不同系统调用。 |
unsigned long args[6] |
系统调用参数数组 | 保存系统调用的 最多前 6 个参数 。例如 openat 有参数 (dfd, filename, flags, mode),这些值都在 args[] 里。 |
char __data[0] |
可变长度数据 | 某些 tracepoint 会在结构末尾追加动态数据。通常不直接用。 |
c
struct trace_event_raw_sys_enter {
__u64 unused;
long id;
unsigned long args[6];
};
openat函数原型
c
int openat(int dfd, const char *filename, int flags, mode_t mode);
trace_event_raw_sys_enter中的args[6]就是openat的参数
| 参数 | args[] 索引 | 含义 |
|---|---|---|
| dfd | args[0] | 文件描述符 |
| filename | args[1] | 用户态字符串指针 |
| flags | args[2] | 打开模式(O_CREAT 等) |
| mode | args[3] | 权限位 |
可运行的demo
重点总结
开发环境:
uos linux 4.19 + Clang 13 + libbpf 0.6,由于uos 4.19内核版本不支持BTF,没有vmlinux文件,使用原始的map定义,不适用co-re格式的map定义,原始的map定义如下:
c
struct bpf_map_def SEC("maps") events = {
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(__u32),
.max_entries = 128,
};
代码文件
c
// file_create.bpf.c
// file_create.bpf.c
#include <linux/ptrace.h>
#include <linux/bpf.h>
#include <linux/limits.h>
#include <linux/types.h>
#include <bpf/bpf_helpers.h>
#include <asm-generic/fcntl.h>
#ifndef TASK_COMM_LEN
#define TASK_COMM_LEN 16
#endif
#define FNAME_LEN 256
#define __user
struct event {
__u32 pid;
__u32 tgid;
__u32 uid;
__u64 timestamp_ns;
int fd;
char comm[TASK_COMM_LEN];
__u8 op; /* 1=openat, 2=unlinkat */
char fname[FNAME_LEN];
};
/* 使用 legacy map 定义,兼容内核 4.19 */
struct bpf_map_def SEC("maps") events = {
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(__u32),
.max_entries = 128,
};
/* tracepoint ctx */
struct trace_event_raw_sys_enter {
__u64 unused;
long id;
unsigned long args[6];
};
SEC("tracepoint/syscalls/sys_enter_openat")
int handle_openat(struct trace_event_raw_sys_enter *ctx)
{
struct event evt = {};
const char __user *fname = (const char __user *)ctx->args[1];
int flags = (int)ctx->args[2];
if(!(flags & O_CREAT))
return 0;
__u64 id = bpf_get_current_pid_tgid();
evt.pid = id;
evt.tgid = id >> 32;
evt.uid = bpf_get_current_uid_gid();
evt.op = 1;
evt.timestamp_ns = bpf_ktime_get_ns();
evt.fd = (int)ctx->args[0];
bpf_get_current_comm(&evt.comm, sizeof(evt.comm));
bpf_probe_read_user_str(&evt.fname, sizeof(evt.fname), fname);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &evt, sizeof(evt));
return 0;
}
SEC("tracepoint/syscalls/sys_enter_unlinkat")
int handle_unlinkat(struct trace_event_raw_sys_enter *ctx)
{
struct event evt = {};
const char __user *fname = (const char __user *)ctx->args[1];
__u64 id = bpf_get_current_pid_tgid();
evt.pid = id;
evt.tgid = id >> 32;
evt.uid = bpf_get_current_uid_gid();
evt.op = 2;
evt.timestamp_ns = bpf_ktime_get_ns();
evt.fd = (int)ctx->args[0];
bpf_get_current_comm(&evt.comm, sizeof(evt.comm));
bpf_probe_read_user_str(&evt.fname, sizeof(evt.fname), fname);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &evt, sizeof(evt));
return 0;
}
char LICENSE[] SEC("license") = "GPL";
//file_create.user.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <asm-generic/fcntl.h>
#define TASK_COMM_LEN 16
#define FNAME_LEN 256
#define __user
struct event {
__u32 pid;
__u32 tgid;
__u32 uid;
__u64 timestamp_ns;
int fd;
char comm[TASK_COMM_LEN];
__u8 op; /* 1=openat, 2=unlinkat */
char fname[FNAME_LEN];
};
static volatile bool exiting = false;
static void sig_handler(int sig) { exiting = true; }
static void handle_event(void *ctx, int cpu, void *data, __u32 size)
{
struct event *evt = data;
char work_dir[256] = {0};
if (evt->pid >= 0) {
char fd_path[64];
snprintf(fd_path, sizeof(fd_path), "/proc/%d/cwd", evt->pid);
int n = readlink(fd_path, work_dir, sizeof(work_dir)-1);
if (n > 0) work_dir[n] = 0;
}
char cmdline[256] = {0};
char cmd_path[256]={0};
snprintf(cmd_path, sizeof(cmd_path), "/proc/%d/cmdline", evt->pid);
int fd2 = open(cmd_path, O_RDONLY);
int m = read(fd2, cmdline, sizeof(cmdline)-1);
if (m > 0) {
/* cmdline 里是 \0 分隔,替换成空格 */
for (int i = 0; i < m; i++) if (cmdline[i] == 0) cmdline[i] = ' ';
cmdline[m] = 0;
}
close(fd2);
uint32_t file_path_len = strlen(evt->fname)+strlen(work_dir) +2;
char *file_path = malloc(file_path_len);
if(file_path==NULL)
return;
memset(file_path, 0, file_path_len);
char *split_char = NULL;
split_char = strrchr(evt->fname, '/');
if(split_char != NULL){
memcpy(file_path, evt->fname, strlen(evt->fname));
}
else{
snprintf(file_path, file_path_len, "%s/%s", work_dir, evt->fname);
}
printf("[%.3f][%s] PID:%u TGID:%u UID:%u FD:%u COMM:%s COMMLINE:%s FILE:%s\n",evt->timestamp_ns/1e9,
evt->op == 1 ? "OPENAT" : "UNLINKAT",
evt->pid, evt->tgid, evt->uid, evt->fd, evt->comm, cmdline, file_path);
fflush(stdout);
if(file_path)
free(file_path);
}
int main(int argc, char **argv)
{
struct bpf_object *obj;
struct bpf_program *prog_open, *prog_unlink;
struct bpf_map *events_map;
struct bpf_link *link_open, *link_unlink;
struct perf_buffer *pb;
int err;
if (argc != 2) return 1;
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
obj = bpf_object__open_file(argv[1], NULL);
if (!obj) return 1;
if ((err = bpf_object__load(obj)) != 0) return 1;
prog_open = bpf_object__find_program_by_name(obj, "handle_openat");
prog_unlink = bpf_object__find_program_by_name(obj, "handle_unlinkat");
link_open = bpf_program__attach(prog_open);
link_unlink = bpf_program__attach(prog_unlink);
events_map = bpf_object__find_map_by_name(obj, "events");
if (!events_map) return 1;
pb = perf_buffer__new(bpf_map__fd(events_map), 8, handle_event, NULL, NULL, NULL);
if (!pb) { perror("perf_buffer__new"); return 1; }
printf("Monitoring... Ctrl+C to exit\n");
while (!exiting){
perf_buffer__poll(pb, 100);
}
bpf_link__destroy(link_open);
bpf_link__destroy(link_unlink);
perf_buffer__free(pb);
bpf_object__close(obj);
return 0;
}
Makefile内容
makefile
CC = gcc
CLANG = clang
LLVM_STRIP = llvm-strip
CFLAGS = -I/usr/include
LDFLAGS = -lbpf -lelf -lz
# 源文件
BPF_SRC = file_create.bpf.c
USER_SRC = file_create.user.c
BPF_OBJ = file_create.bpf.o
USER_BIN = file_create.user
# 目标
TARGETS = $(BPF_OBJ) $(USER_BIN)
all: $(TARGETS)
# 编译BPF程序
$(BPF_OBJ): $(BPF_SRC)
$(CLANG) -O2 -target bpf -D__TARGET_ARCH_x86 -c $(BPF_SRC) -o $(BPF_OBJ)
#$(LLVM_STRIP) -g $(BPF_OBJ)
# 编译用户态程序
$(USER_BIN): $(USER_SRC)
$(CC) $(CFLAGS) -o $(USER_BIN) $(USER_SRC) $(LDFLAGS)
clean:
rm -f $(BPF_OBJ) $(USER_BIN)
.PHONY: all clean
结果输出
shell
admin@admin-UOS-PC:~/myebpf/first_demo$ sudo ./file_create.user file_create.bpf.o
libbpf: map 'events' (legacy): legacy map definitions are deprecated, use BTF-defined maps instead
Monitoring... Ctrl+C to exit
[30488.003][OPENAT] PID:9835 TGID:9781 UID:0 FD:4294967196 COMM:QThread COMMLINE:/usr/bin/deepin-MonitorNetFlow FILE:/usr/share/deepin-defender/localcache.db
[30488.003][OPENAT] PID:9835 TGID:9781 UID:0 FD:4294967196 COMM:QThread COMMLINE:/usr/bin/deepin-MonitorNetFlow FILE:/usr/share/deepin-defender/localcache.db
[30488.004][OPENAT] PID:9835 TGID:9781 UID:0 FD:4294967196 COMM:QThread COMMLINE:/usr/bin/deepin-MonitorNetFlow FILE:/usr/share/deepin-defender/localcache.db
[30488.004][OPENAT] PID:9835 TGID:9781 UID:0 FD:4294967196 COMM:QThread COMMLINE:/usr/bin/deepin-MonitorNetFlow FILE:/usr/share/deepin-defender/localcache.db
错误解决
错误1
shell
/usr/include/linux/types.h:5:10: fatal error: 'asm/types.h' file not found
#include <asm/types.h>
^~~~~~~~~~~~~
1 error generated.
解决办法:
shell
sudo ln -s /usr/include/x86_64-linux-gnu/asm /usr/include/asm
错误2
shell
admin@admin-UOS-PC:~/myebpf/first_demo$ sudo ./file_create.user
./file_create.user: error while loading shared libraries: libbpf.so.0: cannot open shared object file: No such file or directory
解决办法
shell
echo "/usr/lib64" | sudo tee /etc/ld.so.conf.d/libbpf.conf
sudo ldconfig