diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..af2b805 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "numbers": "c" + } +} \ No newline at end of file diff --git a/4-opensnoop/README.md b/4-opensnoop/README.md index 59bd1a5..1d4e792 100644 --- a/4-opensnoop/README.md +++ b/4-opensnoop/README.md @@ -33,7 +33,7 @@ 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 程序通过定义函数 tracepoint__syscalls__sys_enter_openat 并使用 SEC 宏把它们附加到 sys_enter_openat 的 tracepoint(即在进入 openat 系统调用时执行)。这个函数通过使用 bpf_get_current_pid_tgid 函数获取调用 openat 系统调用的进程 ID,并使用 bpf_printk 函数在内核日志中打印出来。 编译运行上述代码: @@ -94,6 +94,6 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe ## 总结 -本文介绍了如何使用 eBPF 程序来捕获进程打开文件的系统调用。在 eBPF 程序中,我们可以通过定义 tracepoint__syscalls__sys_enter_open 和 tracepoint__syscalls__sys_enter_openat 函数并使用 SEC 宏把它们附加到 sys_enter_open 和 sys_enter_openat 两个 tracepoint 来捕获进程打开文件的系统调用。在这两个函数中,我们可以使用 bpf_get_current_pid_tgid 函数获取调用 open 或 openat 系统调用的进程 ID,并使用 bpf_printk 函数在内核日志中打印出来。在 eBPF 程序中,我们还可以通过定义一个全局变量 pid_target 来指定要捕获的进程的 pid,从而过滤输出,只输出指定的进程的信息。 +本文介绍了如何使用 eBPF 程序来捕获进程打开文件的系统调用。在 eBPF 程序中,我们可以通过定义 tracepoint__syscalls__sys_enter_open 和 tracepoint__syscalls__sys_enter_openat 函数并使用 SEC 宏把它们附加到 sys_enter_open 和 sys_enter_openat 两个 tracepoint 来捕获进程打开文件的系统调用。我们可以使用 bpf_get_current_pid_tgid 函数获取调用 open 或 openat 系统调用的进程 ID,并使用 bpf_printk 函数在内核日志中打印出来。在 eBPF 程序中,我们还可以通过定义一个全局变量 pid_target 来指定要捕获的进程的 pid,从而过滤输出,只输出指定的进程的信息。 更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档: diff --git a/5-uprobe-bashreadline/README.md b/5-uprobe-bashreadline/README.md index 098b2c2..e65080f 100644 --- a/5-uprobe-bashreadline/README.md +++ b/5-uprobe-bashreadline/README.md @@ -83,7 +83,6 @@ BPF_KRETPROBE(printret, const void *ret) 编译运行上述代码: - ```console $ ecc bashreadline.bpf.c bashreadline.h Compiling bpf object... diff --git a/6-sigsnoop/README.md b/6-sigsnoop/README.md index 418de0f..fe8416e 100755 --- a/6-sigsnoop/README.md +++ b/6-sigsnoop/README.md @@ -1,5 +1,9 @@ # eBPF 入门开发实践指南六:捕获进程发送信号的系统调用集合,使用 hash map 保存状态 +eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。 + +本文是 eBPF 入门开发实践指南的第六篇,主要介绍如何实现一个 eBPF 工具,捕获进程发送信号的系统调用集合,使用 hash map 保存状态。 + ## sigsnoop 示例代码如下: @@ -7,9 +11,18 @@ ```c #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); @@ -23,11 +36,11 @@ static int probe_entry(pid_t tpid, int sig) { struct event event = {}; __u64 pid_tgid; - __u32 pid, tid; + __u32 tid; pid_tgid = bpf_get_current_pid_tgid(); - pid = pid_tgid >> 32; - event.pid = pid; + tid = (__u32)pid_tgid; + event.pid = pid_tgid >> 32; event.tpid = tpid; event.sig = sig; bpf_get_current_comm(event.comm, sizeof(event.comm)); @@ -47,7 +60,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); @@ -69,21 +82,6 @@ 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"; ``` @@ -93,52 +91,34 @@ char LICENSE[] SEC("license") = "Dual BSD/GPL"; 最后,我们还需要使用 SEC 宏来定义探针,并指定要捕获的系统调用的名称,以及要执行的探针函数。 -origin from: - -https://github.com/iovisor/bcc/blob/master/libbpf-tools/sigsnoop.bpf.c - -## Compile and Run - -Compile: +编译运行上述代码: ```shell docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest ``` -Or compile with `ecc`: +或者 ```console $ ecc sigsnoop.bpf.c sigsnoop.h Compiling bpf object... Generating export types... Packing ebpf object and config into package.json... +$ sudo ecli package.json +Runing eBPF program... ``` -Run: +运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出: ```console -$ sudo ./ecli examples/bpftools/sigsnoop/package.json -TIME PID TPID SIG RET COMM -20:43:44 21276 3054 0 0 cpptools-srv -20:43:44 22407 3054 0 0 cpptools-srv -20:43:44 20222 3054 0 0 cpptools-srv -20:43:44 8933 3054 0 0 cpptools-srv -20:43:44 2915 2803 0 0 node -20:43:44 2943 2803 0 0 node -20:43:44 31453 3054 0 0 cpptools-srv -$ sudo ./ecli examples/bpftools/sigsnoop/package.json -h -Usage: sigsnoop_bpf [--help] [--version] [--verbose] [--filtered_pid VAR] [--target_signal VAR] [--failed_only] - -A simple eBPF program - -Optional arguments: - -h, --help shows help message and exits - -v, --version prints version information and exits - --verbose prints libbpf debug information - --filtered_pid set value of pid_t variable filtered_pid - --target_signal set value of int variable target_signal - --failed_only set value of bool variable failed_only - -Built with eunomia-bpf framework. -See https://github.com/eunomia-bpf/eunomia-bpf for more information. +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + node-3517 [003] d..31 82575.798191: bpf_trace_printk: PID 3517 (node) sent signal 0 to PID 3427, ret = 0 + node-15194 [003] d..31 82575.849227: bpf_trace_printk: PID 15194 (node) sent signal 0 to PID 3427, ret = 0 + node-30016 [003] d..31 82576.001361: bpf_trace_printk: PID 30016 (node) sent signal 0 to PID 3427, ret = 0 + cpptools-srv-38617 [002] d..31 82576.461085: bpf_trace_printk: PID 38617 (cpptools-srv) sent signal 0 to PID 30496, ret = 0 + node-30040 [002] d..31 82576.467720: bpf_trace_printk: PID 30016 (node) sent signal 0 to PID 3427, ret = 0 ``` + +## 总结 + +更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:https://github.com/eunomia-bpf/eunomia-bpf \ No newline at end of file diff --git a/6-sigsnoop/sigsnoop.bpf.c b/6-sigsnoop/sigsnoop.bpf.c index d740906..552dbb7 100755 --- a/6-sigsnoop/sigsnoop.bpf.c +++ b/6-sigsnoop/sigsnoop.bpf.c @@ -25,11 +25,11 @@ static int probe_entry(pid_t tpid, int sig) { struct event event = {}; __u64 pid_tgid; - __u32 pid, tid; + __u32 tid; pid_tgid = bpf_get_current_pid_tgid(); - pid = pid_tgid >> 32; - event.pid = pid; + tid = (__u32)pid_tgid; + event.pid = pid_tgid >> 32; event.tpid = tpid; event.sig = sig; bpf_get_current_comm(event.comm, sizeof(event.comm)); diff --git a/6-sigsnoop/sigsnoop.md b/6-sigsnoop/sigsnoop.md deleted file mode 100644 index eb51d85..0000000 --- a/6-sigsnoop/sigsnoop.md +++ /dev/null @@ -1,18 +0,0 @@ -## eBPF 入门实践教程:编写 eBPF 程序 sigsnoop 工具监控全局 signal 事件 - -### 背景 - -### 实现原理 - -`sigsnoop` 在利用了linux的tracepoint挂载点,其在syscall进入和退出的各个关键挂载点均挂载了执行函数。 -```c - -``` - - -### Eunomia中使用方式 - -![result](../imgs/sigsnoop.png) -![result](../imgs/sigsnoop-prometheus.png) - -### 总结 diff --git a/7-execsnoop/.gitignore b/7-execsnoop/.gitignore index 0e8325e..e78ed98 100644 --- a/7-execsnoop/.gitignore +++ b/7-execsnoop/.gitignore @@ -1,3 +1,3 @@ ecli -package.json +*.json diff --git a/7-execsnoop/README.md b/7-execsnoop/README.md index a3f5f29..a8b5ced 100644 --- a/7-execsnoop/README.md +++ b/7-execsnoop/README.md @@ -35,7 +35,7 @@ struct event { #include #include #include -#include "execsnoop.bpf.h" +#include "execsnoop.h" struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); @@ -48,14 +48,10 @@ 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; @@ -63,70 +59,43 @@ int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter* ctx 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; + event.ppid = BPF_CORE_READ(task, real_parent, tgid); 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 系统调用的出口。 +这段代码定义了个 eBPF 程序,用于捕获进程执行 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 内核中进程执行的事件, 并分析进程的执行情况。 -使用这段代码,我们就可以捕获 Linux 内核中进程执行的事件。我们可以通过工具(例如 eunomia-bpf)来查看这些事件,并分析进程的执行情况。 - -## Compile and Run - -Compile: +使用容器编译: ```shell docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest ``` -Run: +或者使用 ecc 编译: +```shell +ecc bootstrap.bpf.c bootstrap.h ``` -$ sudo ./ecli run package.json -running and waiting for the ebpf events from perf event... -time pid ppid uid retval args_count args_size comm args -23:07:35 32940 32783 1000 0 1 13 cat /usr/bin/cat -23:07:43 32946 24577 1000 0 1 10 bash /bin/bash -23:07:43 32948 32946 1000 0 1 18 lesspipe /usr/bin/lesspipe -23:07:43 32949 32948 1000 0 2 36 basename /usr/bin/basename -23:07:43 32951 32950 1000 0 2 35 dirname /usr/bin/dirname -23:07:43 32952 32946 1000 0 2 22 dircolors /usr/bin/dircolors -23:07:48 32953 32946 1000 0 2 25 ls /usr/bin/ls -23:07:53 32957 32946 1000 0 2 17 sleep /usr/bin/sleep -23:07:57 32959 32946 1000 0 1 17 oneko /usr/games/oneko +运行 +```console +$ sudo ./ecli run package.json +TIME PID PPID UID COMM +21:28:30 40747 3517 1000 node +21:28:30 40748 40747 1000 sh +21:28:30 40749 3517 1000 node +21:28:30 40750 40749 1000 sh +21:28:30 40751 3517 1000 node +21:28:30 40752 40751 1000 sh +21:28:30 40753 40752 1000 cpuUsage.sh ``` diff --git a/7-execsnoop/execsnoop.bpf.c b/7-execsnoop/execsnoop.bpf.c index 7a12668..4ea9978 100644 --- a/7-execsnoop/execsnoop.bpf.c +++ b/7-execsnoop/execsnoop.bpf.c @@ -2,7 +2,7 @@ #include #include #include -#include "execsnoop.bpf.h" +#include "execsnoop.h" struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); @@ -15,14 +15,10 @@ 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; @@ -30,30 +26,7 @@ int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter* ctx 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; + event.ppid = BPF_CORE_READ(task, real_parent, tgid); bpf_get_current_comm(&event.comm, sizeof(event.comm)); bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); return 0; diff --git a/7-execsnoop/execsnoop.h b/7-execsnoop/execsnoop.h index 7f7f3b1..d5e8965 100644 --- a/7-execsnoop/execsnoop.h +++ b/7-execsnoop/execsnoop.h @@ -8,8 +8,6 @@ struct event { int pid; int ppid; int uid; - int retval; - bool is_exit; char comm[TASK_COMM_LEN]; }; diff --git a/8-runqslower/.gitignore b/8-exitsnoop/.gitignore similarity index 69% rename from 8-runqslower/.gitignore rename to 8-exitsnoop/.gitignore index e4bde33..84c0a30 100644 --- a/8-runqslower/.gitignore +++ b/8-exitsnoop/.gitignore @@ -1,4 +1,4 @@ .vscode -package.json eunomia-exporter ecli +*.json diff --git a/8-exitsnoop/README.md b/8-exitsnoop/README.md new file mode 100644 index 0000000..c2e8976 --- /dev/null +++ b/8-exitsnoop/README.md @@ -0,0 +1,72 @@ +## eBPF 入门实践教程: + +## origin + +origin from: + +https://github.com/iovisor/bcc/blob/master/libbpf-tools/runqslower.bpf.c + +result: + +``` +$ sudo ecli/build/bin/Release/ecli run examples/bpftools/runqslower/package.json + +running and waiting for the ebpf events from perf event... +time task prev_task delta_us pid prev_pid +20:11:59 gnome-shell swapper/0 32 2202 0 +20:11:59 ecli swapper/3 23 3437 0 +20:11:59 rcu_sched swapper/1 1 14 0 +20:11:59 gnome-terminal- swapper/1 13 2714 0 +20:11:59 ecli swapper/3 2 3437 0 +20:11:59 kworker/3:3 swapper/3 3 215 0 +20:11:59 containerd swapper/1 8 1088 0 +20:11:59 ecli swapper/2 5 3437 0 +20:11:59 HangDetector swapper/3 6 854 0 +20:11:59 ecli swapper/2 60 3437 0 +20:11:59 rcu_sched swapper/1 26 14 0 +20:11:59 kworker/0:1 swapper/0 26 3414 0 +20:11:59 ecli swapper/2 6 3437 0 +``` + +这段代码定义了一个 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 事件中,供用户空间工具进行捕获和分析。 + +## Compile and Run + +Compile: + +``` +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +Or + +```console +$ ecc exitsnoop.bpf.c exitsnoop.h +Compiling bpf object... +Generating export types... +Packing ebpf object and config into package.json... +``` + +Run: + +```console +$ sudo ./ecli run package.json +TIME PID PPID EXIT_CODE DURATION_NS COMM +21:40:09 42050 42049 0 0 which +21:40:09 42049 3517 0 0 sh +21:40:09 42052 42051 0 0 ps +21:40:09 42051 3517 0 0 sh +21:40:09 42055 42054 0 0 sed +21:40:09 42056 42054 0 0 cat +21:40:09 42057 42054 0 0 cat +21:40:09 42058 42054 0 0 cat +21:40:09 42059 42054 0 0 cat +``` diff --git a/8-exitsnoop/exitsnoop.bpf.c b/8-exitsnoop/exitsnoop.bpf.c new file mode 100644 index 0000000..a2c41d9 --- /dev/null +++ b/8-exitsnoop/exitsnoop.bpf.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright (c) 2020 Facebook */ +#include "vmlinux.h" +#include +#include +#include +#include "exitsnoop.h" + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 256 * 1024); +} rb SEC(".maps"); + +SEC("tp/sched/sched_process_exit") +int handle_exit(struct trace_event_raw_sched_process_template* ctx) +{ + struct task_struct *task; + struct event *e; + pid_t pid, tid; + u64 id, ts, *start_ts, duration_ns = 0; + + /* get PID and TID of exiting thread/process */ + id = bpf_get_current_pid_tgid(); + pid = id >> 32; + tid = (u32)id; + + /* ignore thread exits */ + if (pid != tid) + return 0; + + /* reserve sample from BPF ringbuf */ + e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0); + if (!e) + return 0; + + /* fill out the sample with data */ + task = (struct task_struct *)bpf_get_current_task(); + + e->duration_ns = duration_ns; + e->pid = pid; + e->ppid = BPF_CORE_READ(task, real_parent, tgid); + e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff; + bpf_get_current_comm(&e->comm, sizeof(e->comm)); + + /* send data to user-space for post-processing */ + bpf_ringbuf_submit(e, 0); + return 0; +} diff --git a/8-exitsnoop/exitsnoop.h b/8-exitsnoop/exitsnoop.h new file mode 100644 index 0000000..fdd9034 --- /dev/null +++ b/8-exitsnoop/exitsnoop.h @@ -0,0 +1,15 @@ +#ifndef __BOOTSTRAP_H +#define __BOOTSTRAP_H + +#define TASK_COMM_LEN 16 +#define MAX_FILENAME_LEN 127 + +struct event { + int pid; + int ppid; + unsigned exit_code; + unsigned long long duration_ns; + char comm[TASK_COMM_LEN]; +}; + +#endif /* __BOOTSTRAP_H */ \ No newline at end of file diff --git a/8-runqslower/README.md b/8-runqslower/README.md deleted file mode 100644 index d624438..0000000 --- a/8-runqslower/README.md +++ /dev/null @@ -1,155 +0,0 @@ -## eBPF 入门实践教程: - -## origin - -origin from: - -https://github.com/iovisor/bcc/blob/master/libbpf-tools/runqslower.bpf.c - -result: - -``` -$ sudo ecli/build/bin/Release/ecli run examples/bpftools/runqslower/package.json - -running and waiting for the ebpf events from perf event... -time task prev_task delta_us pid prev_pid -20:11:59 gnome-shell swapper/0 32 2202 0 -20:11:59 ecli swapper/3 23 3437 0 -20:11:59 rcu_sched swapper/1 1 14 0 -20:11:59 gnome-terminal- swapper/1 13 2714 0 -20:11:59 ecli swapper/3 2 3437 0 -20:11:59 kworker/3:3 swapper/3 3 215 0 -20:11:59 containerd swapper/1 8 1088 0 -20:11:59 ecli swapper/2 5 3437 0 -20:11:59 HangDetector swapper/3 6 854 0 -20:11:59 ecli swapper/2 60 3437 0 -20:11:59 rcu_sched swapper/1 26 14 0 -20:11:59 kworker/0:1 swapper/0 26 3414 0 -20:11:59 ecli swapper/2 6 3437 0 -``` - -这段代码定义了一个 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 事件中,供用户空间工具进行捕获和分析。 - -## Compile and Run - -Compile: - -``` -docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest -``` - -Run: - -``` -sudo ./ecli run examples/bpftools/runqslower/package.json -``` - -## details in bcc - -Demonstrations of runqslower, the Linux eBPF/bcc version. - -runqslower traces high scheduling delays between tasks being ready to run and them running on CPU after that. Example output: - -``` -# ./runqslower -Tracing run queue latency higher than 10000 us -TIME COMM TID LAT(us) -13:11:43 b'kworker/0:2' 8680 10250 -13:12:18 b'irq/16-vmwgfx' 422 10838 -13:12:18 b'systemd-oomd' 753 11012 -13:12:18 b'containerd' 8272 11254 -13:12:18 b'HangDetector' 764 12042 -^C -`` -This measures the time a task spends waiting on a run queue for a turn on-CPU, and shows this time as a individual events. This time should be small, but a task may need to wait its turn due to CPU load. - -This measures two types of run queue latency: -1. The time from a task being enqueued on a run queue to its context switch and execution. This traces ttwu_do_wakeup(), wake_up_new_task() -> finish_task_switch() with either raw tracepoints (if supported) or kprobes and instruments the run queue latency after a voluntary context switch. -2. The time from when a task was involuntary context switched and still in the runnable state, to when it next executed. This is instrumented from finish_task_switch() alone. - -The overhead of this tool may become significant for some workloads: see the OVERHEAD section. - -This works by tracing various kernel scheduler functions using dynamic tracing, and will need updating to match any changes to these functions. - -Since this uses BPF, only the root user can use this tool. - -```console -Usage: runqslower [-h] [-p PID | -t TID | -P] [min_us] -``` - -The min_us option sets the latency of the run queue to track: - -``` -# ./runqslower 100 -Tracing run queue latency higher than 100 us -TIME COMM TID LAT(us) -20:48:26 b'gnome-shell' 3005 201 -20:48:26 b'gnome-shell' 3005 202 -20:48:26 b'gnome-shell' 3005 254 -20:48:26 b'gnome-shell' 3005 208 -20:48:26 b'gnome-shell' 3005 132 -20:48:26 b'gnome-shell' 3005 213 -20:48:26 b'gnome-shell' 3005 205 -20:48:26 b'python3' 5224 127 -20:48:26 b'gnome-shell' 3005 214 -20:48:26 b'gnome-shell' 3005 126 -20:48:26 b'gnome-shell' 3005 285 -20:48:26 b'Xorg' 2869 296 -20:48:26 b'gnome-shell' 3005 119 -20:48:26 b'gnome-shell' 3005 206 -``` - -The -p PID option only traces this PID: - -``` -# ./runqslower -p 3005 -Tracing run queue latency higher than 10000 us -TIME COMM TID LAT(us) -20:46:22 b'gnome-shell' 3005 16024 -20:46:45 b'gnome-shell' 3005 11494 -20:46:45 b'gnome-shell' 3005 21430 -20:46:45 b'gnome-shell' 3005 14948 -20:47:16 b'gnome-shell' 3005 10164 -20:47:16 b'gnome-shell' 3005 18070 -20:47:17 b'gnome-shell' 3005 13272 -20:47:18 b'gnome-shell' 3005 10451 -20:47:18 b'gnome-shell' 3005 15010 -20:47:18 b'gnome-shell' 3005 19449 -20:47:22 b'gnome-shell' 3005 19327 -20:47:23 b'gnome-shell' 3005 13178 -20:47:23 b'gnome-shell' 3005 13483 -20:47:23 b'gnome-shell' 3005 15562 -20:47:23 b'gnome-shell' 3005 13655 -20:47:23 b'gnome-shell' 3005 19571 -``` - -The -P option also shows previous task name and TID: - -``` -# ./runqslower -P -Tracing run queue latency higher than 10000 us -TIME COMM TID LAT(us) PREV COMM PREV TID -20:42:48 b'sysbench' 5159 10562 b'sysbench' 5152 -20:42:48 b'sysbench' 5159 10367 b'sysbench' 5152 -20:42:49 b'sysbench' 5158 11818 b'sysbench' 5159 -20:42:49 b'sysbench' 5160 16913 b'sysbench' 5153 -20:42:49 b'sysbench' 5157 13742 b'sysbench' 5160 -20:42:49 b'sysbench' 5152 13746 b'sysbench' 5160 -20:42:49 b'sysbench' 5153 13731 b'sysbench' 5160 -20:42:49 b'sysbench' 5158 14688 b'sysbench' 5161 -20:42:50 b'sysbench' 5155 10468 b'sysbench' 5152 -20:42:50 b'sysbench' 5156 17695 b'sysbench' 5158 -20:42:50 b'sysbench' 5155 11251 b'sysbench' 5152 -20:42:50 b'sysbench' 5154 13283 b'sysbench' 5152 -20:42:50 b'sysbench' 5158 22278 b'sysbench' 5157 -``` - -For more details, see docs/special_filtering.md \ No newline at end of file diff --git a/8-runqslower/core_fixes.bpf.h b/8-runqslower/core_fixes.bpf.h deleted file mode 100644 index 003163a..0000000 --- a/8-runqslower/core_fixes.bpf.h +++ /dev/null @@ -1,112 +0,0 @@ -/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ -/* Copyright (c) 2021 Hengqi Chen */ - -#ifndef __CORE_FIXES_BPF_H -#define __CORE_FIXES_BPF_H - -#include -#include - -/** - * commit 2f064a59a1 ("sched: Change task_struct::state") changes - * the name of task_struct::state to task_struct::__state - * see: - * https://github.com/torvalds/linux/commit/2f064a59a1 - */ -struct task_struct___o { - volatile long int state; -} __attribute__((preserve_access_index)); - -struct task_struct___x { - unsigned int __state; -} __attribute__((preserve_access_index)); - -static __always_inline __s64 get_task_state(void *task) -{ - struct task_struct___x *t = task; - - if (bpf_core_field_exists(t->__state)) - return BPF_CORE_READ(t, __state); - return BPF_CORE_READ((struct task_struct___o *)task, state); -} - -/** - * commit 309dca309fc3 ("block: store a block_device pointer in struct bio") - * adds a new member bi_bdev which is a pointer to struct block_device - * see: - * https://github.com/torvalds/linux/commit/309dca309fc3 - */ -struct bio___o { - struct gendisk *bi_disk; -} __attribute__((preserve_access_index)); - -struct bio___x { - struct block_device *bi_bdev; -} __attribute__((preserve_access_index)); - -static __always_inline struct gendisk *get_gendisk(void *bio) -{ - struct bio___x *b = bio; - - if (bpf_core_field_exists(b->bi_bdev)) - return BPF_CORE_READ(b, bi_bdev, bd_disk); - return BPF_CORE_READ((struct bio___o *)bio, bi_disk); -} - -/** - * commit d5869fdc189f ("block: introduce block_rq_error tracepoint") - * adds a new tracepoint block_rq_error and it shares the same arguments - * with tracepoint block_rq_complete. As a result, the kernel BTF now has - * a `struct trace_event_raw_block_rq_completion` instead of - * `struct trace_event_raw_block_rq_complete`. - * see: - * https://github.com/torvalds/linux/commit/d5869fdc189f - */ -struct trace_event_raw_block_rq_complete___x { - dev_t dev; - sector_t sector; - unsigned int nr_sector; -} __attribute__((preserve_access_index)); - -struct trace_event_raw_block_rq_completion___x { - dev_t dev; - sector_t sector; - unsigned int nr_sector; -} __attribute__((preserve_access_index)); - -static __always_inline bool has_block_rq_completion() -{ - if (bpf_core_type_exists(struct trace_event_raw_block_rq_completion___x)) - return true; - return false; -} - -/** - * commit d152c682f03c ("block: add an explicit ->disk backpointer to the - * request_queue") and commit f3fa33acca9f ("block: remove the ->rq_disk - * field in struct request") make some changes to `struct request` and - * `struct request_queue`. Now, to get the `struct gendisk *` field in a CO-RE - * way, we need both `struct request` and `struct request_queue`. - * see: - * https://github.com/torvalds/linux/commit/d152c682f03c - * https://github.com/torvalds/linux/commit/f3fa33acca9f - */ -struct request_queue___x { - struct gendisk *disk; -} __attribute__((preserve_access_index)); - -struct request___x { - struct request_queue___x *q; - struct gendisk *rq_disk; -} __attribute__((preserve_access_index)); - -static __always_inline struct gendisk *get_disk(void *request) -{ - struct request___x *r = request; - - if (bpf_core_field_exists(r->rq_disk)) - return BPF_CORE_READ(r, rq_disk); - return BPF_CORE_READ(r, q, disk); -} - -#endif /* __CORE_FIXES_BPF_H */ diff --git a/8-runqslower/runqslower.bpf.c b/8-runqslower/runqslower.bpf.c deleted file mode 100644 index e99644d..0000000 --- a/8-runqslower/runqslower.bpf.c +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (c) 2019 Facebook -#include -#include -#include -#include -#include "runqslower.h" -#include "core_fixes.bpf.h" - -#define TASK_RUNNING 0 - -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); -} - -char LICENSE[] SEC("license") = "GPL"; diff --git a/8-runqslower/runqslower.h b/8-runqslower/runqslower.h deleted file mode 100644 index 06e91f4..0000000 --- a/8-runqslower/runqslower.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ -#ifndef __RUNQSLOWER_H -#define __RUNQSLOWER_H - -#define TASK_COMM_LEN 16 - -struct event { - char task[TASK_COMM_LEN]; - char prev_task[TASK_COMM_LEN]; - __u64 delta_us; - int pid; - int prev_pid; -}; - -#endif /* __RUNQSLOWER_H */ diff --git a/README.md b/README.md index a03ab69..0ee188d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# bpf-develop-tutorial: learn CO-RE ebpf with example tools +# bpf-develop-tutorial: learn CO-RE ebpf with example tools 这是一个基于 `CO-RE`(一次编译,到处运行)的 `libbpf` 的 eBPF 的开发教程,提供了从入门到进阶的 eBPF 开发实践指南,包括基本概念、代码实例、实际应用等内容。我们主要提供了一些 eBPF 工具的案例,帮助开发者学习 eBPF 的开发方法和技巧。教程内容可以在目录中找到,每个目录都是一个独立的 eBPF 工具案例。 @@ -6,8 +6,7 @@ 本项目主要基于 [libbpf-boostrap](https://github.com/libbpf/libbpf-bootstrap) 和 [eunomia-bpf](https://github.com/eunomia-bpf/eunomia-bpf) 两个框架完成,并使用 eunomia-bpf 帮助简化一部分 libbpf eBPF 用户态代码的编写。 -教程主要关注于可观察性,并简要介绍了 eBPF 的其他应用。 - +教程主要关注于可观察性,并简要介绍了 eBPF 的其他应用,例如网络、安全等等。 ## 让 chatGPT 来帮助我们 @@ -21,7 +20,7 @@ ![ebpf-chatgpt-signal](imgs/ebpf-chatgpt-signal.png) -完整的对话可以在这里找到: [chatGPT.md](chatGPT.md) +完整的对话记录可以在这里找到: [chatGPT.md](chatGPT.md) ## 目录 @@ -32,8 +31,8 @@ - [lesson 4-opensnoop](4-opensnoop/README.md) 使用 eBPF 捕获进程打开文件的系统调用集合,使用全局变量在 eBPF 中过滤进程 pid - [lesson 5-uprobe-bashreadline](5-uprobe-bashreadline/README.md) 在 eBPF 中使用 uprobe 捕获 bash 的 readline 函数调用 - [lesson 6-sigsnoop](6-sigsnoop/README.md) 捕获进程发送信号的系统调用集合,使用 hash map 保存状态 -- [lesson 7-execsnoop](7-execsnoop/README.md) 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出 -- [lesson 8-runqslower](8-runqslower/README.md) 捕获进程调度事件,使用 ring buffer 向用户态打印输出 +- [lesson 7-execsnoop](7-execsnoop/README.md) 捕获进程执行时间,通过 perf event array 向用户态打印输出 +- [lesson 8-execsnoop](8-exitsnoop/README.md) 捕获进程退出事件,使用 ring buffer 向用户态打印输出 - [lesson 9-runqlat](9-runqlat/README.md) 捕获进程调度延迟,以直方图方式记录 - [lesson 10-hardirqs](20-hardirqs/README.md) 使用 hardirqs 或 softirqs 捕获中断事件 - [lesson 11-bootstrap](11-bootstrap/README.md) 使用 libbpf-boostrap 为 eBPF 编写原生的的用户态代码 @@ -51,10 +50,11 @@ ## 为什么需要基于 libbpf 和 BPF CO-RE 的教程? > 历史上,当需要开发一个BPF应用时可以选择BCC 框架,在实现各种用于Tracepoints的BPF程序时需要将BPF程序加载到内核中。BCC提供了内置的Clang编译器,可以在运行时编译BPF代码,并将其定制为符合特定主机内核的程序。这是在不断变化的内核内部下开发可维护的BPF应用程序的唯一方法。在BPF的可移植性和CO-RE一文中详细介绍了为什么会这样,以及为什么BCC是之前唯一的可行方式,此外还解释了为什么 libbpf 是目前比较好的选择。去年,Libbpf的功能和复杂性得到了重大提升,消除了与BCC之间的很多差异(特别是对Tracepoints应用来说),并增加了很多BCC不支持的新的且强大的特性(如全局变量和BPF skeletons)。 -> +> > 诚然,BCC会竭尽全力简化BPF开发人员的工作,但有时在获取便利性的同时也增加了问题定位和修复的困难度。用户必须记住其命名规范以及自动生成的用于Tracepoints的结构体,且必须依赖这些代码的重写来读取内核数据和获取kprobe参数。当使用BPF map时,需要编写一个半面向对象的C代码,这与内核中发生的情况并不完全匹配。除此之外,BCC使得用户在用户空间编写了大量样板代码,且需要手动配置最琐碎的部分。 -> +> > 如上所述,BCC依赖运行时编译,且本身嵌入了庞大的LLVM/Clang库,由于这些原因,BCC与理想的使用有一定差距: +> > - 编译时的高资源利用率(内存和CPU),在繁忙的服务器上时有可能干扰主流程。 > - 依赖内核头文件包,不得不在每台目标主机上进行安装。即使这样,如果需要某些没有通过公共头文件暴露的内核内容时,需要将类型定义拷贝黏贴到BPF代码中,通过这种方式达成目的。 > - 即使是很小的编译时错误也只能在运行时被检测到,之后不得不重新编译并重启用户层的应用;这大大影响了开发的迭代时间(并增加了挫败感...) @@ -79,15 +79,3 @@ eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的 > - eunomia-bpf 项目 Github 地址: > - gitee 镜像: - -## 让 chatGPT 来帮助我们 - -> 本教程大部分内容由 chatGPT 生成,我们尝试教会 chatGPT 编写 eBPF 程序: -> -> 1. 告诉它基本的 eBPF 编程相关的常识 -> 2. 一些案例:hello world,eBPF 程序的基本结构,如何使用 eBPF 程序等等,并且让它开始编写教程 -> 3. 手动调整并纠正代码和文档中的错误 -> 4. 把修改后的代码再喂给 chatGPT,让它继续学习 -> 5. 尝试让 chatGPT 自动生成 eBPF 程序! -> - diff --git a/chatGPT.md b/chatGPT.md index a34a1a6..275552c 100644 --- a/chatGPT.md +++ b/chatGPT.md @@ -1,11 +1,11 @@ +# chat with ChatGPT + +## Thread 1 + 我希望你帮助我编写一些 eBPF 程序,描述一下这个 eBPF 程序的作用。我们会使用 eunomia-bpf 这个工具来编写 eBPF 程序。请告诉我 eunomia-bpf 是什么。 -eBPF 是一种内核的网络程序。它可以在内核空间中执行,并且可以实时地监控内核的行为。eunomia-bpf 是一个工具,可以用来编写 eBPF 程序。它提供了一个简单的接口,使用户可以编写 eBPF 程序来实现特定的目标。例如,eBPF 程序可以用来监控内核网络流量,或者实现虚拟机监控等功能。 +> eBPF 是一种内核的网络程序。它可以在内核空间中执行,并且可以实时地监控内核的行为。eunomia-bpf 是一个工具,可以用来编写 eBPF 程序。它提供了一个简单的接口,使用户可以编写 eBPF 程序来实现特定的目标。例如,eBPF 程序可以用来监控内核网络流量,或者实现虚拟机监控等功能。 - - - -Yunwei 123 这是一个使用 eunomia-bpf 的例子: ```c #include "vmlinux.h" @@ -64,21 +64,17 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe rm-9346 [005] d..4 4710.951895: bpf_trace_printk: KPROBE EXIT: ret = 0 ``` -这段程序使用 eBPF 实现了一个 kprobe,用来监控内核中 do_unlinkat() 函数的调用。kprobe 的目的是实时监控内核函数的调用情况,通过记录调用时的参数和返回值,来提供实时状态的反馈。在这个例子中,kprobe 记录了进入 do_unlinkat() 函数时的 PID 和文件名,并在函数返回时记录了返回值。 +> 这段程序使用 eBPF 实现了一个 kprobe,用来监控内核中 do_unlinkat() 函数的调用。kprobe 的目的是实时监控内核函数的调用情况,通过记录调用时的参数和返回值,来提供实时状态的反馈。在这个例子中,kprobe 记录了进入 do_unlinkat() 函数时的 PID 和文件名,并在函数返回时记录了返回值。 +> +> eBPF 程序使用 ecc 工具编译成二进制文件,然后通过 ecli 工具运行。运行时,ecli 会将 eBPF 程序附加到内核上,并通过 /sys/kernel/debug/tracing/trace_pipe 文件来输出 kprobe 的日志信息。 +> +> 这个程序的作用是实时监控内核中 do_unlinkat() 函数的调用情况,并记录相关信息。通过这种方式,我们可以对内核的行为进行实时监控,并及时发现可能的异常情况。 -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 程序可以实现上述目标,具体代码如下: +> 编写 eBPF 程序可以实现上述目标,具体代码如下: -Copy code +```c #include #include @@ -99,19 +95,18 @@ int BPF_TRACEPOINT(sys_enter_openat) 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。通过这种方式,我们可以实时监控进程对文件系统的访问情况,并及时发现可能的异常情况。 +> 这段程序定义了两个函数 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 工具: +> +> ```console +> $ ecc open +> ``` -要编译这段程序,请使用 ecc 工具: - -Copy code -$ ecc open - - - -Yunwei 123 很好。 + ```c /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ #define BPF_NO_GLOBAL_DATA @@ -138,18 +133,13 @@ int handle_tp(void *ctx) 这段程序通过定义一个 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。具体的输出结果应该类似于: +> 这段程序的作用是实时监控内核中 write 系统调用的情况,并记录调用 write 系统调用的进程 ID。当进程调用 write 系统调用时,该函数会执行,并使用 bpf_printk 函数在内核日志中打印出调用 write 系统调用的进程 ID。具体的输出结果应该类似于: +> +> 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。 -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 编译程序: @@ -181,10 +171,11 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe ``` 请告诉我如何编译运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序。 -要编译和运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序,可以按照下面的步骤进行: +> 要编译和运行实时监控内核中 open 和 openat 系统调用的 eBPF 程序,可以按照下面的步骤进行: +> +> 将程序保存到文件中,例如叫做 "open.bpf.c"。 -将程序保存到文件中,例如叫做 "open.bpf.c"。 -Copy code +```c #include #include @@ -205,37 +196,40 @@ int BPF_TRACEPOINT(sys_enter_openat) bpf_printk("sys_enter_openat: pid = %d\n", pid); return 0; } -使用 ecc 工具编译程序: -Copy code +``` + +> 使用 ecc 工具编译程序: + +```console $ ecc open.bpf.c Compiling bpf object... Packing ebpf object and config into package.json... -使用 ecli 命令运行编译后的程序: -Copy code +``` + +> 使用 ecli 命令运行编译后的程序: + +```console $ sudo ecli ./package.json Runing eBPF program... -查看 /sys/kernel/debug +``` + +> 查看 /sys/kernel/debug - -Yunwei 123 请继续。查看 /sys/kernel/debug -运行 eBPF 程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出。例如,如果有进程在调用 open 或 openat 系统调用,那么可以看到类似下面的输出: +> 运行 eBPF 程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出。例如,如果有进程在调用 open 或 openat 系统调用,那么可以看到类似下面的输出: -Copy code +```c $ 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 同时出现在内核日志中。这取决于系统的具体情况。 +> 这些输出表明,在运行 eBPF 程序的过程中,有两个进程(PID 为 123 和 456)分别调用了 open 和 openat 系统调用。对于每个系统调用,eBPF 程序都会记录调用进程的 ID,并在内核日志中输出。 +> 需要注意的是,如果内核中有多个进程同时调用 open 或 openat 系统调用,那么输出结果可能会有所不同。例如,可能会有多个进程的 ID 同时出现在内核日志中。这取决于系统的具体情况。 - - -Yunwei 123 - ```c #include #include @@ -321,35 +315,32 @@ 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、进程名称、发送的信号和系统调用的返回值。 +> 在探针函数中,我们使用 bpf_map 存储捕获的事件信息,包括发送信号的进程 ID、接收信号的进程 ID、信号值和系统调用的返回值。在系统调用退出时,我们将获取存储在 bpf_map 中的事件信息,并使用 bpf_printk 打印进程 ID、进程名称、发送的信号和系统调用的返回值。 +> +> 要编译这段 eBPF 程序,需要使用 LLVM Clang 工具链,并确保安装了 eBPF 相关的头文件,例如 vmlinux.h 和 bpf_helpers.h。 +> +> 首先,按照以下命令编译 eBPF 程序: +> +> $ clang -O2 -Wall -target bpf -c sigsnoop.c -o sigsnoop.o +> +> 编译完成后,可以使用 bpftool 工具将 eBPF 字节码文件加载到内核中: +> +> $ sudo bpftool prog load sigsnoop.o /sys/fs/bpf/sigsnoop +> 最后,查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出: -要编译这段 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 +```console $ 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。 +``` +> 输出结果显示了进程发送信号的细节,包括发送信号的进程 ID、发送信号的进程名、信号值以及接收到信号的进程 ID。 - - -Yunwei 123 请告诉我如何编写一段 eBPF 程序,追踪 stat 相关的系统调用。 -下面是一个示例 eBPF 程序,用于跟踪执行 stat 相关系统调用(例如 stat、fstat、lstat)的进程信息和文件路径。 +> 下面是一个示例 eBPF 程序,用于跟踪执行 stat 相关系统调用(例如 stat、fstat、lstat)的进程信息和文件路径。 -Copy code +```c #include #include #include "sigsnoop.h" @@ -414,15 +405,13 @@ int sys_stat_exit(struct pt_regs *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 系统调用。 +> 下面是一个简单的 eBPF 程序,用于跟踪 stat 相关的系统调用。它通过定义两个函数:tracepoint__syscalls__sys_enter_stat 和 tracepoint__syscalls__sys_exit_stat,并使用 SEC 宏把它们附加到 stat 系统调用的进入和退出事件上,来捕获 stat 系统调用。 -Copy code +```c #include #include @@ -468,10 +457,9 @@ int sys_exit_stat(struct trace_event_raw_sys_exit *ctx) bpf_map_delete_elem(& +``` - -Yunwei 123 -请继续。 +> 请继续。 编译运行 eBPF 程序的方法与之前给出的方法类似,首先使用 ecc 编译程序: @@ -650,46 +638,41 @@ char LICENSE[] SEC("license") = "GPL"; ``` -这段代码定义了两个 eBPF 程序,一个用于捕获进程执行 execve 系统调用的入口,另一个用于捕获进程执行 execve 系统调用的出口。 +> 这段代码定义了两个 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 程序: +> +> clang -O2 -emit-llvm -c -g execsnoop.bpf.c +> +> llc -march=bpf -filetype=obj -o execsnoop.o execsnoop.bpf.ll +> +> 编译后的文件 execsnoop.o 就可以使用 eunomia-bpf 工具来运行了。例如,可以使用如下命令运行这个程序: +> +> eunomia-bpf -o execsnoop.o +> +> 执行这个命令后,程序将在内核中运行,并在追踪到进程执行 execve 系统调用时向用户态命令行打印输出。 +> +> 可以通过使用 -p 参数指定进程 ID,来只监控指定的进程。例如: +> +> eunomia-bpf -p 1234 -o execsnoop.o +> +> 这样就只会监控进程 ID 为 1234 的进程的执行。 +> +> 还可以通过使用 -C 参数来指定要监控的系统调用,例如: +> +> eunomia-bpf -C execve -o execsnoop.o +> 这样就只会监 -在入口程序中,我们首先获取了当前进程的进程 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 +```c struct stat_event { u64 id; pid_t pid; @@ -697,17 +680,21 @@ struct stat_event { char comm[TASK_COMM_LEN]; char pathname[PATH_MAX]; }; -然后,可以定义一个 perf event array 用于向用户态传递输出: +``` -Copy code +> 然后,可以定义一个 perf event array 用于向用户态传递输出: + +```c 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 +> 接下来,可以编写捕获 stat 系统调用的 eBPF 程序: + +```c SEC("tracepoint/syscalls/sys_enter_stat") int tracepoint__syscalls__sys_enter_stat(struct trace_event_raw_sys_enter *ctx) { @@ -725,15 +712,13 @@ int tracepoint__syscalls__sys_enter_stat(struct trace_event_raw_sys_enter *ctx) struct stat_event event = {}; task = (struct task_struct *)bpf_get_current_task(); bpf_probe +``` - - -Yunwei 123 请继续。 -这段 eBPF 程序的完整代码如下所示: +> 这段 eBPF 程序的完整代码如下所示: -Copy code +```c #include #include #include @@ -776,17 +761,15 @@ int tracepoint__syscalls__sys_enter_stat(struct trace_event_raw_sys_enter* ctx) } char LICENSE[] SEC("license") = "GPL"; -通过这段代码 +``` +> 通过这段代码 - - -Yunwei 123 请继续,通过这段代码 -您可以使用以下代码来跟踪 stat 相关的系统调用: +> 您可以使用以下代码来跟踪 stat 相关的系统调用: -Copy code +```c #include #include #include @@ -826,3 +809,1085 @@ int tracepoint__syscalls__sys_exit_fstat(struct trace_event_raw_sys_exit* ctx) int fd; char comm +``` + +## thread 2 + +我希望你帮助我编写一些 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 diff --git a/chatGPT1.md b/chatGPT1.md deleted file mode 100644 index 4cac8bf..0000000 --- a/chatGPT1.md +++ /dev/null @@ -1,1080 +0,0 @@ -# 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