我希望你帮助我编写一些 eBPF 程序,描述一下这个 eBPF 程序的作用。我们会使用 eunomia-bpf 这个工具来编写 eBPF 程序。请告诉我 eunomia-bpf 是什么。 eBPF 是一种内核的网络程序。它可以在内核空间中执行,并且可以实时地监控内核的行为。eunomia-bpf 是一个工具,可以用来编写 eBPF 程序。它提供了一个简单的接口,使用户可以编写 eBPF 程序来实现特定的目标。例如,eBPF 程序可以用来监控内核网络流量,或者实现虚拟机监控等功能。 Yunwei 123 这是一个使用 eunomia-bpf 的例子: ```c #include "vmlinux.h" #include #include #include char LICENSE[] SEC("license") = "Dual BSD/GPL"; SEC("kprobe/do_unlinkat") int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name) { pid_t pid; const char *filename; pid = bpf_get_current_pid_tgid() >> 32; filename = BPF_CORE_READ(name, name); bpf_printk("KPROBE ENTRY pid = %d, filename = %s\n", pid, filename); return 0; } SEC("kretprobe/do_unlinkat") int BPF_KRETPROBE(do_unlinkat_exit, long ret) { pid_t pid; pid = bpf_get_current_pid_tgid() >> 32; bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret); return 0; } ``` kprobe 是 eBPF 用于处理内核空间入口和出口(返回)探针(kprobe 和 kretprobe)的一个例子。它将 kprobe 和 kretprobe BPF 程序附加到 do_unlinkat() 函数上,并使用 bpf_printk() 宏分别记录 PID、文件名和返回值。 要编译这个程序,请使用 ecc 工具: ```console $ ecc kprobe-link.bpf.c Compiling bpf object... Packing ebpf object and config into package.json... ``` 然后运行: ```console sudo ecli package.json ``` 在 /sys/kernel/debug/tracing/trace_pipe 文件中,应该能看到类似下面的 kprobe 演示输出: ```shell $ sudo cat /sys/kernel/debug/tracing/trace_pipe rm-9346 [005] d..3 4710.951696: bpf_trace_printk: KPROBE ENTRY pid = 9346, filename = test1 rm-9346 [005] d..4 4710.951819: bpf_trace_printk: KPROBE EXIT: ret = 0 rm-9346 [005] d..3 4710.951852: bpf_trace_printk: KPROBE ENTRY pid = 9346, filename = test2 rm-9346 [005] d..4 4710.951895: bpf_trace_printk: KPROBE EXIT: ret = 0 ``` 这段程序使用 eBPF 实现了一个 kprobe,用来监控内核中 do_unlinkat() 函数的调用。kprobe 的目的是实时监控内核函数的调用情况,通过记录调用时的参数和返回值,来提供实时状态的反馈。在这个例子中,kprobe 记录了进入 do_unlinkat() 函数时的 PID 和文件名,并在函数返回时记录了返回值。 eBPF 程序使用 ecc 工具编译成二进制文件,然后通过 ecli 工具运行。运行时,ecli 会将 eBPF 程序附加到内核上,并通过 /sys/kernel/debug/tracing/trace_pipe 文件来输出 kprobe 的日志信息。 这个程序的作用是实时监控内核中 do_unlinkat() 函数的调用情况,并记录相关信息。通过这种方式,我们可以对内核的行为进行实时监控,并及时发现可能的异常情况。 Yunwei 123 请帮我编写一个eBPF 程序,通过定义两个函数 tracepoint__syscalls__sys_enter_open 和 tracepoint__syscalls__sys_enter_openat 并使用 SEC 宏把它们附加到 sys_enter_open 和 sys_enter_openat 两个 tracepoint(即在进入 open 和 openat 系统调用时执行)。这两个函数通过使用 bpf_get_current_pid_tgid 函数获取调用 open 或 openat 系统调用的进程 ID,并使用 bpf_printk 函数在内核日志中打印出来。 编写 eBPF 程序可以实现上述目标,具体代码如下: Copy code #include #include char LICENSE[] SEC("license") = "Dual BSD/GPL"; SEC("tracepoint/syscalls/sys_enter_open") int BPF_TRACEPOINT(sys_enter_open) { pid_t pid = bpf_get_current_pid_tgid() >> 32; bpf_printk("sys_enter_open: pid = %d\n", pid); return 0; } SEC("tracepoint/syscalls/sys_enter_openat") int BPF_TRACEPOINT(sys_enter_openat) { pid_t pid = bpf_get_current_pid_tgid() >> 32; bpf_printk("sys_enter_openat: pid = %d\n", pid); return 0; } 这段程序定义了两个函数 tracepoint__syscalls__sys_enter_open 和 tracepoint__syscalls__sys_enter_openat,并使用 SEC 宏把它们附加到 sys_enter_open 和 sys_enter_openat 两个 tracepoint 上。这两个函数分别在进入 open 和 openat 系统调用时执行,并使用 bpf_get_current_pid_tgid 函数获取调用 open 或 openat 系统调用的进程 ID,并使用 bpf_printk 函数在内核日志中打印出来。 这段程序的作用是实时监控内核中 open 和 openat 系统调用的情况,并记录调用这两个系统调用的进程 ID。通过这种方式,我们可以实时监控进程对文件系统的访问情况,并及时发现可能的异常情况。 要编译这段程序,请使用 ecc 工具: Copy code $ ecc open Yunwei 123 很好。 ```c /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ #define BPF_NO_GLOBAL_DATA #include #include #include typedef unsigned int u32; typedef int pid_t; const pid_t pid_filter = 0; char LICENSE[] SEC("license") = "Dual BSD/GPL"; SEC("tp/syscalls/sys_enter_write") int handle_tp(void *ctx) { pid_t pid = bpf_get_current_pid_tgid() >> 32; if (pid_filter && pid != pid_filter) return 0; bpf_printk("BPF triggered from PID %d.\n", pid); return 0; } ``` 这段程序通过定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint(即在进入 write 系统调用时执行)。该函数通过使用 bpf_get_current_pid_tgid 和 bpf_printk 函数获取调用 write 系统调用的进程 ID,并在内核日志中打印出来。请告诉我它的输出结果。 这段程序的作用是实时监控内核中 write 系统调用的情况,并记录调用 write 系统调用的进程 ID。当进程调用 write 系统调用时,该函数会执行,并使用 bpf_printk 函数在内核日志中打印出调用 write 系统调用的进程 ID。具体的输出结果应该类似于: Copy code BPF triggered from PID 123. 这段程序会在内核日志中打印出调用 write 系统调用的进程 ID,例如 "BPF triggered from PID 123"。这里的 PID 指的是调用 write 系统调用的进程 ID。 需要注意的是,如果 pid_filter 变量不为 0,那么只有当调用 write 系统调用的进程 ID 等于 pid_filter 时,才会执行 bpf_printk 函数,其他情况都不会打印内核日志。因此,如果要输出所有调用 write 系统调用的进程 ID,需要确保 pid_filter 变量的值为 0。 Yunwei 123 是的。 要编译和运行这段程序,可以使用 ecc 工具和 ecli 命令。首先使用 ecc 编译程序: ```console $ ecc hello.bpf.c Compiling bpf object... Packing ebpf object and config into package.json... ``` 或使用 docker 镜像进行编译: ```shell docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest ``` 然后使用 ecli 运行编译后的程序: ```console $ sudo ecli ./package.json Runing eBPF program... ``` 运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出: ```console $ sudo cat /sys/kernel/debug/tracing/trace_pipe <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345. <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345. ``` 请告诉我如何编译运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序。 要编译和运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序,可以按照下面的步骤进行: 将程序保存到文件中,例如叫做 "open.bpf.c"。 Copy code #include #include char LICENSE[] SEC("license") = "Dual BSD/GPL"; SEC("tracepoint/syscalls/sys_enter_open") int BPF_TRACEPOINT(sys_enter_open) { pid_t pid = bpf_get_current_pid_tgid() >> 32; bpf_printk("sys_enter_open: pid = %d\n", pid); return 0; } SEC("tracepoint/syscalls/sys_enter_openat") int BPF_TRACEPOINT(sys_enter_openat) { pid_t pid = bpf_get_current_pid_tgid() >> 32; bpf_printk("sys_enter_openat: pid = %d\n", pid); return 0; } 使用 ecc 工具编译程序: Copy code $ ecc open.bpf.c Compiling bpf object... Packing ebpf object and config into package.json... 使用 ecli 命令运行编译后的程序: Copy code $ sudo ecli ./package.json Runing eBPF program... 查看 /sys/kernel/debug Yunwei 123 请继续。查看 /sys/kernel/debug 运行 eBPF 程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出。例如,如果有进程在调用 open 或 openat 系统调用,那么可以看到类似下面的输出: Copy code $ sudo cat /sys/kernel/debug/tracing/trace_pipe sys_enter_open: pid = 123 sys_enter_openat: pid = 456 这些输出表明,在运行 eBPF 程序的过程中,有两个进程(PID 为 123 和 456)分别调用了 open 和 openat 系统调用。对于每个系统调用,eBPF 程序都会记录调用进程的 ID,并在内核日志中输出。 需要注意的是,如果内核中有多个进程同时调用 open 或 openat 系统调用,那么输出结果可能会有所不同。例如,可能会有多个进程的 ID 同时出现在内核日志中。这取决于系统的具体情况。 Yunwei 123 ```c #include #include #include "sigsnoop.h" #define MAX_ENTRIES 10240 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, MAX_ENTRIES); __type(key, __u32); __type(value, struct event); } values SEC(".maps"); static int probe_entry(pid_t tpid, int sig) { struct event event = {}; __u64 pid_tgid; __u32 pid, tid; pid_tgid = bpf_get_current_pid_tgid(); pid = pid_tgid >> 32; event.pid = pid; event.tpid = tpid; event.sig = sig; bpf_get_current_comm(event.comm, sizeof(event.comm)); bpf_map_update_elem(&values, &tid, &event, BPF_ANY); return 0; } static int probe_exit(void *ctx, int ret) { __u64 pid_tgid = bpf_get_current_pid_tgid(); __u32 tid = (__u32)pid_tgid; struct event *eventp; eventp = bpf_map_lookup_elem(&values, &tid); if (!eventp) return 0; eventp->ret = ret; bpf_printk("PID %d (%s) sent signal %d to PID %d, ret = %d", eventp->pid, eventp->comm, eventp->sig, eventp->tpid, eventp->ret); cleanup: bpf_map_delete_elem(&values, &tid); return 0; } SEC("tracepoint/syscalls/sys_enter_kill") int kill_entry(struct trace_event_raw_sys_enter *ctx) { pid_t tpid = (pid_t)ctx->args[0]; int sig = (int)ctx->args[1]; return probe_entry(tpid, sig); } SEC("tracepoint/syscalls/sys_exit_kill") int kill_exit(struct trace_event_raw_sys_exit *ctx) { return probe_exit(ctx, ctx->ret); } SEC("tracepoint/syscalls/sys_enter_tkill") int tkill_entry(struct trace_event_raw_sys_enter *ctx) { pid_t tpid = (pid_t)ctx->args[0]; int sig = (int)ctx->args[1]; return probe_entry(tpid, sig); } SEC("tracepoint/syscalls/sys_exit_tkill") int tkill_exit(struct trace_event_raw_sys_exit *ctx) { return probe_exit(ctx, ctx->ret); } char LICENSE[] SEC("license") = "Dual BSD/GPL"; ``` 上面的代码定义了一个 eBPF 程序,用于捕获进程发送信号的系统调用,包括 kill、tkill 和 tgkill。它通过使用 tracepoint 来捕获系统调用的进入和退出事件,并在这些事件发生时执行指定的探针函数,例如 probe_entry 和 probe_exit。 在探针函数中,我们使用 bpf_map 存储捕获的事件信息,包括发送信号的进程 ID、接收信号的进程 ID、信号值和系统调用的返回值。在系统调用退出时,我们将获取存储在 bpf_map 中的事件信息,并使用 bpf_printk 打印进程 ID、进程名称、发送的信号和系统调用的返回值。 要编译这段 eBPF 程序,需要使用 LLVM Clang 工具链,并确保安装了 eBPF 相关的头文件,例如 vmlinux.h 和 bpf_helpers.h。 首先,按照以下命令编译 eBPF 程序: Copy code $ clang -O2 -Wall -target bpf -c sigsnoop.c -o sigsnoop.o 编译完成后,可以使用 bpftool 工具将 eBPF 字节码文件加载到内核中: Copy code $ sudo bpftool prog load sigsnoop.o /sys/fs/bpf/sigsnoop 最后,查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出: Copy code $ sudo cat /sys/kernel/debug/tracing/trace_pipe <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: PID 3840345 (bash) sent signal 15 to PID 3840345, ret = 0 <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: PID 3840345 (bash) sent signal 17 to PID 3840345, ret = 0 输出结果显示了进程发送信号的细节,包括发送信号的进程 ID、发送信号的进程名、信号值以及接收到信号的进程 ID。 Yunwei 123 请告诉我如何编写一段 eBPF 程序,追踪 stat 相关的系统调用。 下面是一个示例 eBPF 程序,用于跟踪执行 stat 相关系统调用(例如 stat、fstat、lstat)的进程信息和文件路径。 Copy code #include #include #include "sigsnoop.h" #define MAX_ENTRIES 10240 struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, MAX_ENTRIES); __type(key, __u32); __type(value, struct event); } values SEC(".maps"); static int probe_stat_entry(void *ctx) { struct event event = {}; __u64 pid_tgid; __u32 pid, tid; pid_tgid = bpf_get_current_pid_tgid(); pid = pid_tgid >> 32; event.pid = pid; bpf_get_current_comm(event.comm, sizeof(event.comm)); bpf_map_update_elem(&values, &tid, &event, BPF_ANY); return 0; } static int probe_stat_exit(void *ctx, int ret) { __u64 pid_tgid = bpf_get_current_pid_tgid(); __u32 tid = (__u32)pid_tgid; struct event *eventp; eventp = bpf_map_lookup_elem(&values, &tid); if (!eventp) return 0; eventp->ret = ret; if (ret == 0) bpf_probe_read_str(eventp->path, sizeof(eventp->path), (void *)ctx->args[0]); bpf_printk("PID %d (%s) executed stat syscall, file path = %s, ret = %d", eventp->pid, eventp->comm, eventp->path, eventp->ret); cleanup: bpf_map_delete_elem(&values, &tid); return 0; } SEC("kprobe/sys_stat") int sys_stat_entry(struct pt_regs *ctx) { return probe_stat_entry(ctx); } SEC("kretprobe/sys_stat") int sys_stat_exit(struct pt_regs *ctx) { return probe_stat_exit(ctx, PT_REGS_RC(ctx)); } SEC("kprobe/sys_fstat") int sys Yunwei 123 我如何使用 eunomia-bpf 编译运行它? 下面是一个简单的 eBPF 程序,用于跟踪 stat 相关的系统调用。它通过定义两个函数:tracepoint__syscalls__sys_enter_stat 和 tracepoint__syscalls__sys_exit_stat,并使用 SEC 宏把它们附加到 stat 系统调用的进入和退出事件上,来捕获 stat 系统调用。 Copy code #include #include struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 1024); __type(key, int); __type(value, struct event); } events SEC(".maps"); SEC("tracepoint/syscalls/sys_enter_stat") int sys_enter_stat(struct trace_event_raw_sys_enter *ctx) { struct event event = {}; __u64 pid_tgid; __u32 pid; int fd = (int)ctx->args[0]; pid_tgid = bpf_get_current_pid_tgid(); pid = pid_tgid >> 32; event.pid = pid; bpf_get_current_comm(event.comm, sizeof(event.comm)); bpf_map_update_elem(&events, &fd, &event, BPF_ANY); return 0; } SEC("tracepoint/syscalls/sys_exit_stat") int sys_exit_stat(struct trace_event_raw_sys_exit *ctx) { struct event *eventp; __u64 pid_tgid = bpf_get_current_pid_tgid(); __u32 tid = (__u32)pid_tgid; int fd = (int)ctx->args[0]; eventp = bpf_map_lookup_elem(&events, &fd); if (!eventp) return 0; eventp->ret = ctx->ret; bpf_printk("PID %d (%s) called stat on fd %d, ret = %d\n", eventp->pid, eventp->comm, fd, eventp->ret); bpf_map_delete_elem(& Yunwei 123 请继续。 编译运行 eBPF 程序的方法与之前给出的方法类似,首先使用 ecc 编译程序: Copy code $ ecc stat.bpf.c Compiling bpf object... Packing ebpf object and config into package.json... 或使用 docker 镜像进行编译: Copy code docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest 然后使用 ecli 运行编译后的程序: Copy code $ sudo ecli ./package.json Runing eBPF program... 运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出: Copy code $ sudo cat /sys/kernel/debug/tracing/trace_pipe <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: stat system call from PID 3840345. <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: stat system call from PID 3840345. 也可以使用其他工具,如 perf 或 bpftrace,来查看 eBPF 程序的输出。例如,使用 perf 命令查看 eBPF 程序的输出: Copy code $ perf record -e bpf_printk -a Running 'record' for 'bpf_printk' (5 secs): [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.178 MB perf.data (~1623 samples) ] 使用 perf script 命令来查看 perf.data 中的输出: Copy code $ perf script bash-37048 [002] .... 1617.738101: bpf_trace_printk: stat system call from PID 370 Yunwei 123 编写一个 eBPF 程序,追踪进程执行。通过 perf event array 向用户态打印输出。 下面是一个简单的 eBPF 程序,它定义了一个 kprobe 探针函数,用于捕获内核函数 do_syscall_64 的进入事件,并记录调用该函数的进程的 PID、函数名和返回值。 Copy code #include "vmlinux.h" #include #include #include char LICENSE[] SEC("license") = "Dual BSD/GPL"; SEC("kprobe/do_syscall_64") int BPF_KPROBE(do_syscall_64, struct pt_regs *regs, long id) { pid_t pid; const char *func; pid = bpf_get_current_pid_tgid() >> 32; func = __func__; bpf_printk("KPROBE ENTRY pid = %d, func = %s, id = %d\n", pid, func, id); return 0; } SEC("kretprobe/do_syscall_64") int BPF_KRETPROBE(do_syscall_64_exit, long ret) { pid_t pid; pid = bpf_get_current_pid_tgid() >> 32; bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret); return 0; } 要编译这段代码,可以使用 ecc 工具: Copy code $ ecc kprobe-syscall.bpf.c Compiling bpf object... Packing ebpf object and config into package.json... 然后可以使用 ecli 运行编译后的程序: Copy code Yunwei 123 ## execsnoop 通过 perf event array 向用户态命令行打印输出,需要编写一个头文件,一个 C 源文件。示例代码如下: 头文件:execsnoop.h ```c #ifndef __EXECSNOOP_H #define __EXECSNOOP_H #define TASK_COMM_LEN 16 struct event { int pid; int ppid; int uid; int retval; bool is_exit; char comm[TASK_COMM_LEN]; }; #endif /* __EXECSNOOP_H */ ``` 源文件:execsnoop.bpf.c ```c // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) #include #include #include #include "execsnoop.bpf.h" struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(u32)); __uint(value_size, sizeof(u32)); } events SEC(".maps"); SEC("tracepoint/syscalls/sys_enter_execve") int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter* ctx) { u64 id; pid_t pid, tgid; unsigned int ret; struct event event; struct task_struct *task; const char **args = (const char **)(ctx->args[1]); const char *argp; uid_t uid = (u32)bpf_get_current_uid_gid(); int i; id = bpf_get_current_pid_tgid(); pid = (pid_t)id; tgid = id >> 32; event.pid = tgid; event.uid = uid; task = (struct task_struct*)bpf_get_current_task(); bpf_probe_read_str(&event.comm, sizeof(event.comm), task->comm); event.is_exit = false; bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); return 0; } 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; u32 uid = (u32)bpf_get_current_uid_gid(); id = bpf_get_current_pid_tgid(); pid = (pid_t)id; ret = ctx->ret; event.retval = ret; event.pid = pid; event.uid = uid; event.is_exit = true; bpf_get_current_comm(&event.comm, sizeof(event.comm)); bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); return 0; } char LICENSE[] SEC("license") = "GPL"; ``` 这段代码定义了两个 eBPF 程序,一个用于捕获进程执行 execve 系统调用的入口,另一个用于捕获进程执行 execve 系统调用的出口。 在入口程序中,我们首先获取了当前进程的进程 ID 和用户 ID,然后通过 bpf_get_current_task 函数获取了当前进程的 task_struct 结构体,并通过 bpf_probe_read_str 函数读取了进程名称。最后,我们通过 bpf_perf_event_output 函数将进程执行事件输出到 perf buffer。 在出口程序中,我们首先获取了进程的进程 ID 和用户 ID,然后通过 bpf_get_current_comm 函数获取了进程的名称,最后通过 bpf_perf_event_output 函数将进程执行事件输出到 perf buffer。 使用这段代码,我们就可以捕获 Linux 内核中进程执行的事件。我们可以通过工具(例如 eunomia-bpf)来查看这些事件,并分析进程的执行情况。 接着,使用如下命令编译这个 eBPF 程序: Copy code clang -O2 -emit-llvm -c -g execsnoop.bpf.c llc -march=bpf -filetype=obj -o execsnoop.o execsnoop.bpf.ll 编译后的文件 execsnoop.o 就可以使用 eunomia-bpf 工具来运行了。例如,可以使用如下命令运行这个程序: Copy code eunomia-bpf -o execsnoop.o 执行这个命令后,程序将在内核中运行,并在追踪到进程执行 execve 系统调用时向用户态命令行打印输出。 可以通过使用 -p 参数指定进程 ID,来只监控指定的进程。例如: Copy code eunomia-bpf -p 1234 -o execsnoop.o 这样就只会监控进程 ID 为 1234 的进程的执行。 还可以通过使用 -C 参数来指定要监控的系统调用,例如: Copy code eunomia-bpf -C execve -o execsnoop.o 这样就只会监 Yunwei 123 please give me an eBPF program Trace all stat syscall variants with perf event output. 首先,需要定义一个包含所需信息的结构体: Copy code struct stat_event { u64 id; pid_t pid; uid_t uid; char comm[TASK_COMM_LEN]; char pathname[PATH_MAX]; }; 然后,可以定义一个 perf event array 用于向用户态传递输出: Copy code struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(u32)); __uint(value_size, sizeof(u32)); } events SEC(".maps"); 接下来,可以编写捕获 stat 系统调用的 eBPF 程序: Copy code SEC("tracepoint/syscalls/sys_enter_stat") int tracepoint__syscalls__sys_enter_stat(struct trace_event_raw_sys_enter *ctx) { // 获取进程 ID 和 UID u64 id = bpf_get_current_pid_tgid(); pid_t pid = (pid_t)id; uid_t uid = (u32)bpf_get_current_uid_gid(); // 获取文件名 char pathname[PATH_MAX]; bpf_probe_read_str(pathname, sizeof(pathname), (void *)ctx->args[0]); // 获取进程名 struct task_struct *task; struct stat_event event = {}; task = (struct task_struct *)bpf_get_current_task(); bpf_probe Yunwei 123 请继续。 这段 eBPF 程序的完整代码如下所示: Copy code #include #include #include #define MAX_ENTRIES 10240 struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(u32)); __uint(value_size, sizeof(u32)); } events SEC(".maps"); SEC("tracepoint/syscalls/sys_enter_stat") int tracepoint__syscalls__sys_enter_stat(struct trace_event_raw_sys_enter* ctx) { u64 id = bpf_get_current_pid_tgid(); u32 pid = (u32)id; u32 tgid = id >> 32; const char *filename = (const char*)(ctx->args[0]); struct stat *stat = (struct stat*)(ctx->args[1]); int ret = ctx->ret; bpf_probe_read_str(&filename, sizeof(filename), filename); bpf_probe_read(&stat, sizeof(stat), stat); struct data_t { u32 pid; u32 tgid; int ret; char filename[256]; } data = { .pid = pid, .tgid = tgid, .ret = ret, }; bpf_probe_read_str(&data.filename, sizeof(data.filename), filename); bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data)); return 0; } char LICENSE[] SEC("license") = "GPL"; 通过这段代码 Yunwei 123 请继续,通过这段代码 您可以使用以下代码来跟踪 stat 相关的系统调用: Copy code #include #include #include struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(u32)); __uint(value_size, sizeof(u32)); } events SEC(".maps"); SEC("tracepoint/syscalls/sys_enter_fstat") int tracepoint__syscalls__sys_enter_fstat(struct trace_event_raw_sys_enter* ctx) { struct stat *st = (struct stat *)(ctx->args[1]); struct event { int pid; int fd; char comm[TASK_COMM_LEN]; bool is_exit; int retval; } event = {}; event.fd = (int)(ctx->args[0]); event.pid = (int)(bpf_get_current_pid_tgid() >> 32); bpf_get_current_comm(&event.comm, sizeof(event.comm)); event.is_exit = false; bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); return 0; } SEC("tracepoint/syscalls/sys_exit_fstat") int tracepoint__syscalls__sys_exit_fstat(struct trace_event_raw_sys_exit* ctx) { struct event { int pid; int fd; char comm