From 8bb5d23a40cd5e95adc4e84ad229556b11756c2c Mon Sep 17 00:00:00 2001 From: yunwei37 <1067852565@qq.com> Date: Tue, 6 Dec 2022 16:16:45 +0800 Subject: [PATCH] add chatgpt doc --- 6-sigsnoop/sigsnoop.bpf.c | 15 +- 6-sigsnoop/sigsnoop.h | 16 - README.md | 19 +- chatGPT.md | 828 ++++++++++++++++++++++++++++ chatGPT1.md | 1080 +++++++++++++++++++++++++++++++++++++ 5 files changed, 1936 insertions(+), 22 deletions(-) delete mode 100755 6-sigsnoop/sigsnoop.h create mode 100644 chatGPT.md create mode 100644 chatGPT1.md diff --git a/6-sigsnoop/sigsnoop.bpf.c b/6-sigsnoop/sigsnoop.bpf.c index 97e602b..d740906 100755 --- a/6-sigsnoop/sigsnoop.bpf.c +++ b/6-sigsnoop/sigsnoop.bpf.c @@ -1,10 +1,17 @@ -// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) -/* Copyright (c) 2021~2022 Hengqi Chen */ #include #include -#include "sigsnoop.h" +#include #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); diff --git a/6-sigsnoop/sigsnoop.h b/6-sigsnoop/sigsnoop.h deleted file mode 100755 index a9826d8..0000000 --- a/6-sigsnoop/sigsnoop.h +++ /dev/null @@ -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 */ diff --git a/README.md b/README.md index 25d23b0..a03ab69 100644 --- a/README.md +++ b/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 程序和对应的教程文档!例如 + +![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 连接状态 diff --git a/chatGPT.md b/chatGPT.md new file mode 100644 index 0000000..a34a1a6 --- /dev/null +++ b/chatGPT.md @@ -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 +#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 + diff --git a/chatGPT1.md b/chatGPT1.md new file mode 100644 index 0000000..4cac8bf --- /dev/null +++ b/chatGPT1.md @@ -0,0 +1,1080 @@ +# Chat with ChatGPT + +我希望你帮助我编写一些 eBPF 程序,描述一下这个 eBPF 程序的作用。我们会使用 eunomia-bpf 这个工具来编写 eBPF 程序。eBPF 程序主要由两部分构成:内核态部分和用户态部分。内核态部分包含 eBPF 程序的实际逻辑,用户态部分负责加载、运行和监控内核态程序。使用 eunomia-bpf,只需编写内核态代码即可,无需编写用户态代码。如果你理解了这些,请说好的。 + +> 确实,eBPF 程序由内核态部分和用户态部分构成。eBPF 程序在内核态运行,用户态程序负责加载、运行和监控内核态程序。eunomia-bpf 是一个用于编写和管理 eBPF 程序的工具,它只需要编写内核态代码,无需编写用户态代码。我可以帮助你编写 eBPF 程序,只要你能够告诉我你想要它做什么。 +> + +这段程序通过定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint(即在进入 write 系统调用时执行)。该函数通过使用 bpf_get_current_pid_tgid 和 bpf_printk 函数获取调用 write 系统调用的进程 ID,并在内核日志中打印出来。 +请编写一下这个 eBPF 程序,它有一个 hello.bpf.c 文件。 + +> 可以使用如下代码编写 eBPF 程序: + +Copy code +#include +#include + +SEC("tp/syscalls/sys_enter_write") +int handle_tp(void *ctx) +{ + u32 pid = bpf_get_current_pid_tgid(); + bpf_printk("PID of process calling write: %d\n", pid); + return 0; +} + +char _license[] SEC("license") = "GPL"; +这个 eBPF 程序通过定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint。在进入 write 系统调用时,handle_tp 函数会被执行。该函数会使用 bpf_get_current_pid_tgid 函数获取调用 write 系统调用的进程 ID,然后使用 bpf_printk 函数在内核日志中打印出来。 + +eBPF 程序的输出结果将是内核日志中打印的调用 write 系统调用的进程的 PID。例如,如果进程 42 调用了 write 系统调用,则输出结果将是: + +Copy code +PID of process calling write: 42 +> 需要注意的是,在使用此 eBPF 程序之前,需要编译它并将其加载到内核中。此外,由于 eBPF 程序在内核态运行,因此它可能需要特殊权限才能加载和使用。 + +```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 系统调用时,handle_tp 函数会被执行。该函数会使用 bpf_get_current_pid_tgid 函数获取调用 write 系统调用的进程 ID,然后使用 bpf_printk 函数在内核日志中打印出来。如果需要,还可以使用 pid_filter 变量来过滤进程。如果您需要进一步改进您的 eBPF 程序,请告诉我您的需求,我可以帮助您实现它。 + + +要编译和运行这段程序,可以使用 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. +``` + +您的描述是正确的。eBPF 程序需要编译才能运行。可以使用 ecc 工具来编译 eBPF 程序,也可以使用 docker 镜像进行编译。运行 eBPF 程序的方法有多种,可以使用 ecli 命令运行编译后的程序,也可以使用其他工具。输出结果将在内核日志中打印,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看。 + +请注意,使用 ecc 编译程序后,会生成一个 package.json 文件,其中包含编译后的 eBPF 程序和配置信息。运行 eBPF 程序时,需要指定该文件,以便 ecli 命令能够找到并运行 eBPF 程序。例如,可以使用以下命令运行 eBPF 程序: + +Copy code +$ sudo ecli ./package.json +如果您还有任何问题,请随时告诉我,我很乐意帮助您解决问题。 + +```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、文件名和返回值。请告诉我如何编译运行这段程序。 + +您的 eBPF 程序使用 kprobe 和 kretprobe 宏附加到 do_unlinkat 函数上。kprobe 宏将 BPF 程序附加到 do_unlinkat 函数的入口,kretprobe 宏将 BPF 程序附加到 do_unlinkat 函数的出口(返回)处。这样,您就可以在 do_unlinkat 函数的入口和出口(返回)处捕获数据,并使用 bpf_printk 宏在内核日志中打印数据。 + +要编译和运行您的 eBPF 程序,您需要安装 bcc 工具集。然后,在终端中输入以下命令: + +Copy code +# 编译 eBPF 程序 +clang -O2 -target bpf -c my_bpf_prog.c -o my_bpf_prog.o + +# 运行 eBPF 程序 +sudo ./bcc.py run my_bpf_prog.o +这样,就会编译并运行您的 eBPF 程序。在运行 eBPF 程序时,会在内核日志中打 + +不对,使用 eunomia-bpf 工具编译。要编译这个程序,请使用 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 +``` + +您的描述是正确的。您可以使用 ecc 工具编译您的 eBPF 程序,编译后会生成一个 package.json 文件。然后,可以使用 ecli 命令运行该程序,以便监控系统调用。运行 eBPF 程序后,您可以查看 /sys/kernel/debug/tracing/trace_pipe 文件,以查看程序的输出。 + +请注意,编译和运行 eBPF 程序的步骤可能会有所不同,具体取决于使用的工具和系统环境。如果您在编译和运行 eBPF 程序时遇到任何问题,请随时告评,我很乐意帮助您解决问题。 + +是的。 + +```c +#include "vmlinux.h" +#include +#include + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +SEC("fentry/do_unlinkat") +int BPF_PROG(do_unlinkat, int dfd, struct filename *name) +{ + pid_t pid; + + pid = bpf_get_current_pid_tgid() >> 32; + bpf_printk("fentry: pid = %d, filename = %s\n", pid, name->name); + return 0; +} + +SEC("fexit/do_unlinkat") +int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret) +{ + pid_t pid; + + pid = bpf_get_current_pid_tgid() >> 32; + bpf_printk("fexit: pid = %d, filename = %s, ret = %ld\n", pid, name->name, ret); + return 0; +} +``` + +这段程序通过定义两个函数,分别附加到 do_unlinkat 和 do_unlinkat_exit 上。这两个函数分别在进入 do_unlinkat 和离开 do_unlinkat 时执行。这两个函数通过使用 bpf_get_current_pid_tgid 和 bpf_printk 函数来获取调用 do_unlinkat 的进程 ID,文件名和返回值,并在内核日志中打印出来。请告诉我如何编译运行这段程序。 + +您的 eBPF 程序定义了两个函数:do_unlinkat 和 do_unlinkat_exit。这两个函数都使用 BPF_PROG 宏附加到 do_unlinkat 和 do_unlinkat_exit 函数上。do_unlinkat 函数在进入 do_unlinkat 时执行,do_unlinkat_exit 函数在离开 do_unlinkat 时执行。这两个函数通过使用 bpf_get_current_pid_tgid 和 bpf_printk 函数来获取调用 do_unlinkat 的进程 ID,文件名和返回值,并在内核日志中打印出来。 + +要编译和运行这段程序,您可以使用 bcc 工具集。首先,在终端中输入以下命令来安装 bcc 工具集: + +Copy code +sudo apt install bcc-tools libbcc-examples linux-headers-$(uname -r) +然后,在终端中输入以下命令来编译您的 eBPF 程序: + +`` + +我不希望你继续使用 BCC 工具集,请使用 eunomia-bpf + +好的。您可以使用 eunomia-bpf 工具编译您的 eBPF 程序。首先,在终端中输入以下命令来安装 eunomia-bpf: + +Copy code +sudo npm install -g eunomia-bpf +然后,在终端中输入以下命令来编译您的 eBPF 程序: + +Copy code +ecc kprobe-link.bpf.c +编译后,您会得到一个 package.json 文件。您可以使用 ecli 命令运行该程序: + +Copy code +sudo ecli package.json +运行该程序后,您可以查看 /sys/kernel/debug/tracing/trace_pipe 文件,以查看程序的输出。请注意,编译和运行 eBPF 程序的步骤可能会有所不同,具体取决于使用的工具和系统环境。如果您在编 + +我们需要编写一段 eBPF 程序来捕获进程打开文件的系统调用,具体实现如下: + +```c +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Facebook +// Copyright (c) 2020 Netflix +#include +#include +#include "opensnoop.h" + + +/// Process ID to trace +const volatile int pid_target = 0; + +SEC("tracepoint/syscalls/sys_enter_open") +int tracepoint__syscalls__sys_enter_open(struct trace_event_raw_sys_enter* ctx) +{ + u64 id = bpf_get_current_pid_tgid(); + u32 pid = id; + + if (pid_target && pid_target != pid) + return false; + // Use bpf_printk to print the process information + bpf_printk("Process ID: %d enter sys open\n", pid); + return 0; +} + +SEC("tracepoint/syscalls/sys_enter_openat") +int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter* ctx) +{ + u64 id = bpf_get_current_pid_tgid(); + u32 pid = id; + + if (pid_target && pid_target != pid) + return false; + // Use bpf_printk to print the process information + bpf_printk("Process ID: %d enter sys openat\n", pid); + return 0; +} + +/// Trace open family syscalls. +char LICENSE[] SEC("license") = "GPL"; +``` + +上面的 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 程序,请使用 ecc 工具编译: + +Copy code +$ ecc capture-sys-open.bpf.c +Compiling bpf object... +Packing ebpf object and config into package.json... +然后运行: + +Copy code +sudo ecli package.json +在 /sys/kernel/debug/tracing/trace_pipe 文件中,应该能看到类似下面的输出: + +Copy code +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + cat-9349 [005] d..3 4710.951696: bpf_trace_printk: Process ID: 9349 enter sys open + cat-9349 [005] d..3 4710.951696: bpf_trace_printk: Process ID: 9349 enter sys openat +如果想要只跟踪特定的进程,可以在程序中设置 pid_target 变量的值,例如: + +Copy code +const volatile int pid_target = 1234; +这样,程序只会输出 PID 为 1234 的进程的信息。 + +是的,谢谢你。我会继续给你一些 eBPF 程序。 + +好的,请继续。 + +```c +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2021 Facebook */ +#include +#include +#include +#include "bashreadline.h" + +#define TASK_COMM_LEN 16 +#define MAX_LINE_SIZE 80 + +SEC("uprobe//bin/bash:readline") +int BPF_KRETPROBE(printret, const void *ret) { + char str[MAX_LINE_SIZE]; + char comm[TASK_COMM_LEN]; + u32 pid; + + if (!ret) + return 0; + + bpf_get_current_comm(&comm, sizeof(comm)); + + pid = bpf_get_current_pid_tgid() >> 32; + bpf_probe_read_user_str(str, sizeof(str), ret); + + bpf_printk("PID %d (%s) read: %s ", pid, comm, str); + + return 0; +}; + +char LICENSE[] SEC("license") = "GPL"; +``` + +这段代码的作用是在 bash 的 readline 函数返回时执行指定的 BPF_KRETPROBE 函数,即 printret 函数。 + +在 printret 函数中,我们首先获取了调用 readline 函数的进程的进程名称和进程 ID,然后通过 bpf_probe_read_user_str 函数读取了用户输入的命令行字符串,最后通过 bpf_printk 函数打印出进程 ID、进程名称和输入的命令行字符串。 + +除此之外,我们还需要通过 SEC 宏来定义 uprobe 探针,并使用 BPF_KRETPROBE 宏来定义探针函数。 + +在 SEC 宏中,我们需要指定 uprobe 的类型、要捕获的二进制文件的路径和要捕获的函数名称。例如,上面的代码中的 SEC 宏的定义如下: + +```c +SEC("uprobe//bin/bash:readline") +``` + +这表示我们要捕获的是 /bin/bash 二进制文件中的 readline 函数。 + +是的,你描述得很清楚。 + +编译这段程序的方法与其他 eBPF 程序的编译方法相同,需要使用 eunomia-bpf 工具集。首先使用 ecc 编译程序: + +Copy code +$ ecc bashreadline.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/tracing/trace_pipe 文件中,应该能看到类似下面的输出: + +Copy code +$ sudo cat /sys/kernel/debug/tracing/trace_pipe +PID 1234 (bash) read: some command line input +PID 1234 (bash) read: another command line input +... +这些输出表示有一个进程 ID 为 1234,名称为 bash 的进程在调用 readline 函数时输入了相应的命令行输入。 + + + + +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、进程名称、发送的信号和系统调用的返回值。 + +最后,我们还需要使用 SEC 宏来定义探针,并指定要捕获的系统调用的名称,以及要执行的探针函数。 + +```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 程序来捕获进程执行 execve 系统调用的入口和出口事件。在入口程序中,我们获取了当前进程的进程 ID 和用户 ID,并通过 bpf_get_current_task 函数获取了当前进程的 task_struct 结构体,并通过 bpf_probe_read_str 函数读取了进程名称。在出口程序中,我们获取了当前进程的进程 ID 和用户 ID,并获取了 execve 系统调用的返回值。最后,在 + +#include +#include +#include +#include + +#define TC_ACT_OK 0 +#define ETH_P_IP 0x0800 /* Internet Protocol packet */ + +/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"} +/// @tcopts {"handle":1, "priority":1} +SEC("tc") +int tc_ingress(struct __sk_buff *ctx) +{ + void *data_end = (void *)(__u64)ctx->data_end; + void *data = (void *)(__u64)ctx->data; + struct ethhdr *l2; + struct iphdr *l3; + + if (ctx->protocol != bpf_htons(ETH_P_IP)) + return TC_ACT_OK; + + l2 = data; + if ((void *)(l2 + 1) > data_end) + return TC_ACT_OK; + + l3 = (struct iphdr *)(l2 + 1); + if ((void *)(l3 + 1) > data_end) + return TC_ACT_OK; + + bpf_printk("Got IP packet: tot_len: %d, ttl: %d", bpf_ntohs(l3->tot_len), l3->ttl); + return TC_ACT_OK; +} + +char __license[] SEC("license") = "GPL"; + +这段代码定义了一个 eBPF 程序,它可以通过 Linux TC(Transmission Control)来捕获数据包并进行处理。在这个程序中,我们限定了只捕获 IPv4 协议的数据包,然后通过 bpf_printk 函数打印出数据包的总长度和 Time-To-Live(TTL)字段的值。 + +需要注意的是,我们在代码中使用了一些 BPF 库函数,例如 bpf_htons 和 bpf_ntohs 函数,它们用于进行网络字节序和主机字节序之间的转换。此外,我们还使用了一些注释来为 TC 提供附加点和选项信息。例如,在这段代码的开头,我们使用了以下注释: + +Copy code +/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"} +/// @tcopts {"handle":1, "priority":1} +这些注释告诉 TC 将 eBPF 程序附加到网络接口的 ingress 附加点,并指定了 handle 和 priority 选项的值。 + +总之,这段代码实现了一个简单的 eBPF 程序,用于捕获数据包并打印出它们的信息。 + + + + +Yunwei 123 +编译运行 + +```console +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +or compile with `ecc`: + +```console +$ ecc tc.bpf.c +Compiling bpf object... +Packing ebpf object and config into package.json... +``` + +```shell +$ sudo ecli ./package.json +... +Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF program. +...... +``` + +The `tc` output in `/sys/kernel/debug/tracing/trace_pipe` should look +something like this: + +``` +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + node-1254811 [007] ..s1 8737831.671074: 0: Got IP packet: tot_len: 79, ttl: 64 + sshd-1254728 [006] ..s1 8737831.674334: 0: Got IP packet: tot_len: 79, ttl: 64 + sshd-1254728 [006] ..s1 8737831.674349: 0: Got IP packet: tot_len: 72, ttl: 64 + node-1254811 [007] ..s1 8737831.674550: 0: Got IP packet: tot_len: 71, ttl: 64 +``` + +通过 tc.bpf.c 中的代码,我们可以发现,该 eBPF 程序通过在 TC 入口阶段注册一个挂载点来捕获网络数据包,并判断数据包的协议类型是否为 IP 协议,如果是,则解析数据包头部,获取关键信息,如包长度、TTL 等,最后通过 bpf_printk 函数将信息打印出来。 + +这段代码还使用了 @tchook 和 @tcopts 注释,用于指定该 eBPF 程序的挂载点和处理方式。例如,@tchook 注释中的 ifindex 参数指定了该 eBPF 程序 + + + + +Yunwei 123 + +const volatile __u64 min_us = 0; +const volatile pid_t targ_pid = 0; +const volatile pid_t targ_tgid = 0; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10240); + __type(key, u32); + __type(value, u64); +} start SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(u32)); + __uint(value_size, sizeof(u32)); +} events SEC(".maps"); + +/* record enqueue timestamp */ +static int trace_enqueue(u32 tgid, u32 pid) +{ + u64 ts; + + if (!pid) + return 0; + if (targ_tgid && targ_tgid != tgid) + return 0; + if (targ_pid && targ_pid != pid) + return 0; + + ts = bpf_ktime_get_ns(); + bpf_map_update_elem(&start, &pid, &ts, 0); + return 0; +} + +static int handle_switch(void *ctx, struct task_struct *prev, struct task_struct *next) +{ + struct event event = {}; + u64 *tsp, delta_us; + u32 pid; + + /* ivcsw: treat like an enqueue event and store timestamp */ + if (get_task_state(prev) == TASK_RUNNING) + trace_enqueue(BPF_CORE_READ(prev, tgid), BPF_CORE_READ(prev, pid)); + + pid = BPF_CORE_READ(next, pid); + + /* fetch timestamp and calculate delta */ + tsp = bpf_map_lookup_elem(&start, &pid); + if (!tsp) + return 0; /* missed enqueue */ + + delta_us = (bpf_ktime_get_ns() - *tsp) / 1000; + if (min_us && delta_us <= min_us) + return 0; + + event.pid = pid; + event.prev_pid = BPF_CORE_READ(prev, pid); + event.delta_us = delta_us; + bpf_probe_read_kernel_str(&event.task, sizeof(event.task), next->comm); + bpf_probe_read_kernel_str(&event.prev_task, sizeof(event.prev_task), prev->comm); + + /* output */ + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, + &event, sizeof(event)); + + bpf_map_delete_elem(&start, &pid); + return 0; +} + +SEC("tp_btf/sched_wakeup") +int BPF_PROG(sched_wakeup, struct task_struct *p) +{ + return trace_enqueue(p->tgid, p->pid); +} + +SEC("tp_btf/sched_wakeup_new") +int BPF_PROG(sched_wakeup_new, struct task_struct *p) +{ + return trace_enqueue(p->tgid, p->pid); +} + +SEC("tp_btf/sched_switch") +int BPF_PROG(sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next) +{ + return handle_switch(ctx, prev, next); +} + +SEC("raw_tp/sched_wakeup") +int BPF_PROG(handle_sched_wakeup, struct task_struct *p) +{ + return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid)); +} + +SEC("raw_tp/sched_wakeup_new") +int BPF_PROG(handle_sched_wakeup_new, struct task_struct *p) +{ + return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid)); +} + +SEC("raw_tp/sched_switch") +int BPF_PROG(handle_sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next) +{ + return handle_switch(ctx, prev, next); +} + +这段代码定义了一个 eBPF 程序,该程序用于跟踪进程在运行队列中的等待时间。它通过使用 tracepoint 和 perf event 输出来实现。 + +程序首先定义了两个 BPF 内核映射:start 映射用于存储每个进程在被调度运行之前的时间戳,events 映射用于存储 perf 事件。 + +然后,程序定义了一些帮助函数,用于跟踪每个进程的调度状态。 trace_enqueue 函数用于在进程被调度运行之前记录时间戳, handle_switch 函数用于处理进程切换,并计算进程在队列中等待的时间。 + +接下来,程序定义了五个 tracepoint 程序,用于捕获不同的调度器事件。 sched_wakeup 和 sched_wakeup_new 程序用于捕获新进程被唤醒的事件, sched_switch 程序用于捕获进程切换事件, handle_sched_wakeup 和 handle_sched_wakeup_new 程序用于捕获 raw tracepoint 事件。这些 tracepoint 程序调用了前面定义的帮助函数来跟踪进程的调度状态。 + +最后,程序将计算得到的等待时间输出到 perf 事件中,供用户空间工具进行捕获和分析。 + + + + +Yunwei 123 + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, u32); + __type(value, u64); +} start SEC(".maps"); + +static struct hist zero; + +/// @sample {"interval": 1000, "type" : "log2_hist"} +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, u32); + __type(value, struct hist); +} hists SEC(".maps"); + +static int trace_enqueue(u32 tgid, u32 pid) +{ + u64 ts; + + if (!pid) + return 0; + if (targ_tgid && targ_tgid != tgid) + return 0; + + ts = bpf_ktime_get_ns(); + bpf_map_update_elem(&start, &pid, &ts, BPF_ANY); + return 0; +} + +static unsigned int pid_namespace(struct task_struct *task) +{ + struct pid *pid; + unsigned int level; + struct upid upid; + unsigned int inum; + + /* get the pid namespace by following task_active_pid_ns(), + * pid->numbers[pid->level].ns + */ + pid = BPF_CORE_READ(task, thread_pid); + level = BPF_CORE_READ(pid, level); + bpf_core_read(&upid, sizeof(upid), &pid->numbers[level]); + inum = BPF_CORE_READ(upid.ns, ns.inum); + + return inum; +} + +static int handle_switch(bool preempt, struct task_struct *prev, struct task_struct *next) +{ + struct hist *histp; + u64 *tsp, slot; + u32 pid, hkey; + s64 delta; + + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + if (get_task_state(prev) == TASK_RUNNING) + trace_enqueue(BPF_CORE_READ(prev, tgid), BPF_CORE_READ(prev, pid)); + + pid = BPF_CORE_READ(next, pid); + + tsp = bpf_map_lookup_elem(&start, &pid); + if (!tsp) + return 0; + delta = bpf_ktime_get_ns() - *tsp; + if (delta < 0) + goto cleanup; + + if (targ_per_process) + hkey = BPF_CORE_READ(next, tgid); + else if (targ_per_thread) + hkey = pid; + else if (targ_per_pidns) + hkey = pid_namespace(next); + else + hkey = -1; + histp = bpf_map_lookup_or_try_init(&hists, &hkey, &zero); + if (!histp) + goto cleanup; + if (!histp->comm[0]) + bpf_probe_read_kernel_str(&histp->comm, sizeof(histp->comm), + next->comm); + if (targ_ms) + delta /= 1000000U; + else + delta /= 1000U; + slot = log2l(delta); + if (slot >= MAX_SLOTS) + slot = MAX_SLOTS - 1; + __sync_fetch_and_add(&histp->slots[slot], 1); + +cleanup: + bpf_map_delete_elem(&start, &pid); + return 0; +} + +SEC("raw_tp/sched_wakeup") +int BPF_PROG(handle_sched_wakeup, struct task_struct *p) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid)); +} + +SEC("raw_tp/sched_wakeup_new") +int BPF_PROG(handle_sched_wakeup_new, struct task_struct *p) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid)); +} + +SEC("raw_tp/sched_switch") +int BPF_PROG(handle_sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next) +{ + return handle_switch(preempt, prev, next); +} + +char LICENSE[] SEC("license") = "GPL"; + +这个 BPF 程序用于监控进程的调度时间。它使用了 tracepoints 和 raw tracepoints,用于监控进程的唤醒和切换事件,并记录进程调度的时间。它还使用了 BPF 的统计功能,以便对时间进行统计,以查看统计信息。这个程序可以按照进程、进程组或进程命名空间来统计信息,并可以针对特定进程或进程组进行过滤。 + + + + +Yunwei 123 +它的输出结果类似这样: +$ sudo ecli examples/bpftools/runqlat/package.json +key = 4294967295 +comm = rcu_preempt + + (unit) : count distribution + 0 -> 1 : 9 |**** | + 2 -> 3 : 6 |** | + 4 -> 7 : 12 |***** | + 8 -> 15 : 28 |************* | + 16 -> 31 : 40 |******************* | + 32 -> 63 : 83 |****************************************| + 64 -> 127 : 57 |*************************** | + 128 -> 255 : 19 |********* | + 256 -> 511 : 11 |***** | + 512 -> 1023 : 2 | | + 1024 -> 2047 : 2 | | + 2048 -> 4095 : 0 | | + 4096 -> 8191 : 0 | | + 8192 -> 16383 : 0 | | + 16384 -> 32767 : 1 | | + +本程序监测了系统内进程执行时间。它通过在内核事件 sched_wakeup 和 sched_switch 时记录进程时间,然后在下一次 sched_switch 时计算执行时间并进行输出。 + +在程序开头,定义了一些用于存储进程信息的 BPF map,并定义了两个辅助函数 trace_enqueue 和 handle_switch,用于在事件触发时记录进程信息,并计算执行时间。 + +接着,在程序中定义了三个 BPF 程序,分别用于监测事件 sched_wakeup、sched_switch 和 sched_wakeup_new。这些事件在系统中进程被唤醒、切换时触发。每当一个事件触发时,相应的 BPF 程序都会调用辅助函数 handle_switch 计算执行时间。 + +最后,程序通过 BPF perf events 把执行时间的统计信息输出到标准输出。可以通过工具 bpftrace 或者 ecli 来查看统计结果。 + + + + +Yunwei 123 +不是 BPF perf events 。通过 sample hash map 来输出直方图。/// @sample {"interval": 1000, "type" : "log2_hist"} +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, u32); + __type(value, struct hist); +} hists SEC(".maps"); + +正确的。该程序使用 BPF hash map 来记录每个线程的执行时间,并使用 BPF sample hash map 来定期输出每个线程的直方图。您可以通过在 /sys/kernel/debug/tracing/ 文件系统中执行 "cat runqslat" 来查看输出。 + + + + +Yunwei 123 +不,应该通过 sudo ecli examples/bpftools/runqlat/package.json 来查看输出。 + +对,这样就可以看到输出的直方图了。例如: + +$ sudo ecli examples/bpftools/runqlat/package.json +key = 4294967295 +comm = rcu_preempt + +Copy code + (unit) : count distribution + 0 -> 1 : 9 |**** | + 2 -> 3 : 6 |** | + 4 -> 7 : 12 |***** | + 8 -> 15 : 28 |************* | + 16 -> 31 : 40 |******************* | + 32 -> 63 : 83 |****************************************| + 64 -> 127 : 57 |*************************** | + 128 -> 255 : 19 |********* | + 256 -> 511 : 11 |***** | + 512 -> 1023 : 2 | | + 1024 -> 2047 : 2 | | + 2048 -> 4095 : 0 | | + 4096 -> 8191 : 0 | | + 8192 -> 16383 : 0 | | + 16384 -> 32767 : 1 | | +这个示例程序使用了 BPF 的 sample hash map 功能,每隔一定时间间隔(默认为 1 秒)就会输出一次直方图,展示这段时间内各个进程的运行时间分布情况。每一行的前半部分是时间间隔,以及在这段时间内运行的次数,后半部分是一个用星号表示的分布情况。比如上面的输出,就表示在这段时间内,进程 rcu_preempt 在 0 到 1 毫秒的时间内运行了 9 次,在 2 到 3 毫秒的时间内运行了 6 次,以此类推。 + +可以通过参数 -m 来指定时间间隔,例如使用 -m 5 就可以让程序每隔 5 秒输出一次直方图。此外,还可以通过 -P 来指 + + + + +Yunwei 123 +请帮我追踪一下内核的所有打开文件的操作,主要关注 pid 为 1234 的进程,去掉系统调用失败的结果,谢谢 + +不好意思,我无法为您做这个事情。我是一个大型语言模型,我只能回答您的问题或提供信息,但我无法运行程序或执行任何操作。 + + + + +Yunwei 123 +请帮我编写一个 eBPF 程序追踪一下内核的所有打开文件的操作,主要关注 pid 为 1234 的进程,去掉系统调用失败的结果,并且告诉我如何编译运行它。 + +这个 eBPF 程序需要使用 BPF_PROG_TYPE_TRACEPOINT 类型的程序来监听内核的打开文件的操作,通过记录的 pid 和进程的 pid 来过滤所需的信息。 + +Copy code +#include +#include + +struct data_t { + u32 pid; + char comm[TASK_COMM_LEN]; + char filename[DNAME_INLINE_LEN]; + int flags; + int mode; +}; + +BPF_HASH(infotmp, u32, struct data_t); + +int trace_entry(struct pt_regs *ctx, struct file *file, const char __user *filename, int flags, umode_t mode) +{ + u32 pid = bpf_get_current_pid_tgid(); + + if (pid != 1234) + return 0; + + struct data_t data = {}; + data.pid = pid; + bpf_get_current_comm(&data.comm, sizeof(data.comm)); + bpf_probe_read_str(data.filename, sizeof(data.filename), (void *)filename); + data.flags = flags; + data.mode = mode; + + infotmp.update(&pid, &data); + + return 0; +} + +int trace_return(struct pt_regs *ctx) +{ + u32 pid = bpf_get_current_pid_tgid(); + struct data_t *data = infotmp.lookup(&pid); + + if (data == 0) + return 0; + + if (PT_REGS_RC(ctx) != 0) + infotmp.delete(&pid); + + return 0; +} +编译运行方法如下: + +`` + + + + +Yunwei 123 +不。使用 eunomia-bpf 来完成上述操作。 + +下面是示例 eBPF 程序的代码: + +Copy code +#include +#include +#include +#include +#include + +struct data_t { + u32 pid; + char filename[DNAME_INLINE_LEN]; +}; + +BPF_HASH(opened_files, u32, struct data_t); + +int trace_open_entry(struct pt_regs *ctx, const char __user *filename) +{ + u32 pid = bpf_get_current_pid_tgid(); + struct data_t data = {}; + + if (pid == 1234) { + bpf_probe_read_user(&data.filename, sizeof(data.filename), filename); + data.pid = pid; + opened_files.update(&pid, &data); + } + + return 0; +} + +int trace_open_return(struct pt_regs *ctx) +{ + int ret = PT_REGS_RC(ctx); + u32 pid = bpf_get_current_pid_tgid(); + + if (pid == 1234 && ret >= 0) { + opened_files.delete(&pid); + } + + return 0; +} +可以使用下面的命令来编译和运行这个程序: + +Copy code +# 编译 +$ clang -O2 -target bpf -c open.c -o open.o + +# 运行 +$ sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter_openat /pid == 1234/ { @[comm] = count(); \ No newline at end of file