add chatgpt doc

This commit is contained in:
yunwei37
2022-12-06 16:16:45 +08:00
parent c572e7f511
commit 8bb5d23a40
5 changed files with 1936 additions and 22 deletions

View File

@@ -1,10 +1,17 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2021~2022 Hengqi Chen */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include "sigsnoop.h"
#include <bpf/bpf_tracing.h>
#define MAX_ENTRIES 10240
#define TASK_COMM_LEN 16
struct event {
unsigned int pid;
unsigned int tpid;
int sig;
int ret;
char comm[TASK_COMM_LEN];
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
@@ -42,7 +49,7 @@ static int probe_exit(void *ctx, int ret)
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);
eventp->pid, eventp->comm, eventp->sig, eventp->tpid, ret);
cleanup:
bpf_map_delete_elem(&values, &tid);

View File

@@ -1,16 +0,0 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2021~2022 Hengqi Chen */
#ifndef __SIGSNOOP_H
#define __SIGSNOOP_H
#define TASK_COMM_LEN 16
struct event {
unsigned int pid;
unsigned int tpid;
int sig;
int ret;
char comm[TASK_COMM_LEN];
};
#endif /* __SIGSNOOP_H */

View File

@@ -4,10 +4,25 @@
在学习 eBPF 的过程中,我们受到了 [tutorial_bcc_python_developer](https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md) 的许多启发和帮助,但从 2022 年的角度出发,使用 libbpf 开发 eBPF 的应用是目前相对更好的选择。但目前似乎很少有基于 libbpf 和 BPF CO-RE 出发的、通过案例和工具介绍 eBPF 开发的教程,因此我们发起了这个项目。
本项目主要基于 [libbpf-boostrap](https://github.com/libbpf/libbpf-bootstrap) 和 [eunomia-bpf](https://github.com/eunomia-bpf/eunomia-bpf) 两个框架完成,并使用 eunomia-bpf 帮助简化一部分 eBPF 用户态代码的编写。
本项目主要基于 [libbpf-boostrap](https://github.com/libbpf/libbpf-bootstrap) 和 [eunomia-bpf](https://github.com/eunomia-bpf/eunomia-bpf) 两个框架完成,并使用 eunomia-bpf 帮助简化一部分 libbpf eBPF 用户态代码的编写。
教程主要关注于可观察性,并简要介绍了 eBPF 的其他应用。
## 让 chatGPT 来帮助我们
本教程大部分内容由 chatGPT 生成,我们尝试教会 chatGPT 编写 eBPF 程序:
1. 告诉它基本的 eBPF 编程相关的常识
2. 告诉它一些案例hello worldeBPF 程序的基本结构,如何使用 eBPF 程序进行追踪,并且让它开始编写教程
3. 手动调整教程,并纠正代码和文档中的错误
4. 把修改后的代码再喂给 chatGPT让它继续学习
5. 尝试让 chatGPT 自动生成 eBPF 程序和对应的教程文档!例如
![ebpf-chatgpt-signal](imgs/ebpf-chatgpt-signal.png)
完整的对话可以在这里找到: [chatGPT.md](chatGPT.md)
## 目录
- [lesson 0-introduce](0-introduce/README.md) 介绍 eBPF 的基本概念和常见的开发工具
@@ -21,7 +36,7 @@
- [lesson 8-runqslower](8-runqslower/README.md) 捕获进程调度事件,使用 ring buffer 向用户态打印输出
- [lesson 9-runqlat](9-runqlat/README.md) 捕获进程调度延迟,以直方图方式记录
- [lesson 10-hardirqs](20-hardirqs/README.md) 使用 hardirqs 或 softirqs 捕获中断事件
- [lesson 11-llcstat](21-llcstat/README.md) 通过 PID 统计 cache references and misses
- [lesson 11-bootstrap](11-bootstrap/README.md) 使用 libbpf-boostrap 为 eBPF 编写原生的的用户态代码
- [lesson 12-profile](12-profile/README.md) 使用 eBPF 进行性能分析
- [lesson 13-tcpconnlat](13-tcpconnlat/README.md) 记录 TCP 连接延迟
- [lesson 14-tcpstates](14-tcpstates/README.md) 记录 TCP 连接状态

828
chatGPT.md Normal file
View File

@@ -0,0 +1,828 @@
我希望你帮助我编写一些 eBPF 程序,描述一下这个 eBPF 程序的作用。我们会使用 eunomia-bpf 这个工具来编写 eBPF 程序。请告诉我 eunomia-bpf 是什么。
eBPF 是一种内核的网络程序。它可以在内核空间中执行并且可以实时地监控内核的行为。eunomia-bpf 是一个工具,可以用来编写 eBPF 程序。它提供了一个简单的接口,使用户可以编写 eBPF 程序来实现特定的目标。例如eBPF 程序可以用来监控内核网络流量,或者实现虚拟机监控等功能。
Yunwei 123
这是一个使用 eunomia-bpf 的例子:
```c
#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 工具:
```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 <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
很好。
```c
/* 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 编译程序:
```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 <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
```c
#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
```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 <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

1080
chatGPT1.md Normal file

File diff suppressed because it is too large Load Diff