mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-04 02:34:16 +08:00
add chatgpt doc
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
19
README.md
19
README.md
@@ -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 world,eBPF 程序的基本结构,如何使用 eBPF 程序进行追踪,并且让它开始编写教程
|
||||
3. 手动调整教程,并纠正代码和文档中的错误
|
||||
4. 把修改后的代码再喂给 chatGPT,让它继续学习
|
||||
5. 尝试让 chatGPT 自动生成 eBPF 程序和对应的教程文档!例如
|
||||
|
||||

|
||||
|
||||
完整的对话可以在这里找到: [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
828
chatGPT.md
Normal 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
1080
chatGPT1.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user