25 KiB
我希望你帮助我编写一些 eBPF 程序,描述一下这个 eBPF 程序的作用。我们会使用 eunomia-bpf 这个工具来编写 eBPF 程序。请告诉我 eunomia-bpf 是什么。
eBPF 是一种内核的网络程序。它可以在内核空间中执行,并且可以实时地监控内核的行为。eunomia-bpf 是一个工具,可以用来编写 eBPF 程序。它提供了一个简单的接口,使用户可以编写 eBPF 程序来实现特定的目标。例如,eBPF 程序可以用来监控内核网络流量,或者实现虚拟机监控等功能。
Yunwei 123 这是一个使用 eunomia-bpf 的例子:
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
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 工具:
$ ecc kprobe-link.bpf.c
Compiling bpf object...
Packing ebpf object and config into package.json...
然后运行:
sudo ecli package.json
在 /sys/kernel/debug/tracing/trace_pipe 文件中,应该能看到类似下面的 kprobe 演示输出:
$ 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 <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h>
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 很好。
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#define BPF_NO_GLOBAL_DATA
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
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 编译程序:
$ ecc hello.bpf.c
Compiling bpf object...
Packing ebpf object and config into package.json...
或使用 docker 镜像进行编译:
docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest
然后使用 ecli 运行编译后的程序:
$ sudo ecli ./package.json
Runing eBPF program...
运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:
$ 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 <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h>
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
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#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 <vmlinux.h> #include <bpf/bpf_helpers.h> #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 <vmlinux.h> #include <bpf/bpf_helpers.h>
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 <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_core_read.h>
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
#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
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#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 <vmlinux.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_core_read.h>
#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 <vmlinux.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_core_read.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_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