【信创系统】统信UOS Linux4.19+libbpf开发ebpf程序实现文件操作的实时监控

国产系统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
相关推荐
祎直向前3 小时前
在Ubuntu中安装并配置ssh
linux·ubuntu·ssh
南林yan4 小时前
Debian系统的多内核共存
linux·debian·linux内核
skywalk81635 小时前
尝试Auto-coder.chat使用星河社区AIStudio部署的几个大模型:文心4.5-21b、Deepseek r1 70b、llama 3.1 8b
linux·服务器·人工智能·大模型·aistudio
QiTinna6 小时前
系统运维Day02_数据同步服务
linux·同步·rsync
阿猿收手吧!6 小时前
【Linux网络】shutdown()与close()的区别
linux·网络
LCG元6 小时前
Linux 磁盘管理从入门到精通:LVM 扩容实战案例
linux
liu****6 小时前
12.线程(二)
linux·开发语言·c++·1024程序员节
咯哦哦哦哦6 小时前
vscode arm交叉编译 中 cmakeTools 编译器设置
linux·arm开发·vscode·编辑器
工具人55557 小时前
Linux 抓取 RAM Dump 完整指南
linux·运维·安全