diff --git a/1-helloworld/.gitignore b/1-helloworld/.gitignore index 7d5aebf..f0f4c81 100644 --- a/1-helloworld/.gitignore +++ b/1-helloworld/.gitignore @@ -4,3 +4,4 @@ package.json *.skel.json *.skel.yaml package.yaml +ecli diff --git a/11-llcstat/README.md b/11-bootstrap/README.md similarity index 100% rename from 11-llcstat/README.md rename to 11-bootstrap/README.md diff --git a/17-biopattern/biolatency.md b/17-biopattern/biolatency.md deleted file mode 100644 index 423fca2..0000000 --- a/17-biopattern/biolatency.md +++ /dev/null @@ -1,121 +0,0 @@ -## eBPF 入门实践教程:编写 eBPF 程序 Biolatency: 统计系统中发生的I/O事件 - -### 背景 - -Biolatency 可以统计在该工具运行后系统中发生的I/O事件个数,并且计算I/O事件在不同时间段内的分布情况,以 -直方图的形式展现给用户。 - -### 实现原理 - -Biolatency 主要通过 tracepoint 实现,其在 block_rq_insert, block_rq_issue, -block_rq_complete 挂载点下设置了处理函数。在 block_rq_insert 和 block_rq_issue 挂载点下, -Biolatency 会将IO操作发生时的request queue和时间计入map中。 -```c -int trace_rq_start(struct request *rq, int issue) -{ - if (issue && targ_queued && BPF_CORE_READ(rq->q, elevator)) - return 0; - - u64 ts = bpf_ktime_get_ns(); - - if (filter_dev) { - struct gendisk *disk = get_disk(rq); - u32 dev; - - dev = disk ? MKDEV(BPF_CORE_READ(disk, major), - BPF_CORE_READ(disk, first_minor)) : 0; - if (targ_dev != dev) - return 0; - } - bpf_map_update_elem(&start, &rq, &ts, 0); - return 0; -} - -SEC("tp_btf/block_rq_insert") -int block_rq_insert(u64 *ctx) -{ - if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) - return 0; - - if (LINUX_KERNEL_VERSION < KERNEL_VERSION(5, 11, 0)) - return trace_rq_start((void *)ctx[1], false); - else - return trace_rq_start((void *)ctx[0], false); -} - -SEC("tp_btf/block_rq_issue") -int block_rq_issue(u64 *ctx) -{ - if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) - return 0; - - if (LINUX_KERNEL_VERSION < KERNEL_VERSION(5, 11, 0)) - return trace_rq_start((void *)ctx[1], true); - else - return trace_rq_start((void *)ctx[0], true); -} - -``` -在block_rq_complete 挂载点下,Biolatency 会根据 request queue 从map中读取 -上一次操作发生的时间,然后计算与当前时间的差值来判断其在直方图中存在的区域,将该区域内的IO操作 -计数加一。 -```c -SEC("tp_btf/block_rq_complete") -int BPF_PROG(block_rq_complete, struct request *rq, int error, - unsigned int nr_bytes) -{ - if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) - return 0; - - u64 slot, *tsp, ts = bpf_ktime_get_ns(); - struct hist_key hkey = {}; - struct hist *histp; - s64 delta; - - tsp = bpf_map_lookup_elem(&start, &rq); - if (!tsp) - return 0; - delta = (s64)(ts - *tsp); - if (delta < 0) - goto cleanup; - - if (targ_per_disk) { - struct gendisk *disk = get_disk(rq); - - hkey.dev = disk ? MKDEV(BPF_CORE_READ(disk, major), - BPF_CORE_READ(disk, first_minor)) : 0; - } - if (targ_per_flag) - hkey.cmd_flags = rq->cmd_flags; - - histp = bpf_map_lookup_elem(&hists, &hkey); - if (!histp) { - bpf_map_update_elem(&hists, &hkey, &initial_hist, 0); - histp = bpf_map_lookup_elem(&hists, &hkey); - if (!histp) - goto cleanup; - } - - 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, &rq); - return 0; -} - -``` -当用户中止程序时,用户态程序会读取直方图map中的数据,并打印呈现。 - -### Eunomia中使用方式 - - -### 总结 -Biolatency 通过 tracepoint 挂载点实现了对IO事件个数的统计,并且能以直方图的 -形式进行展现,可以方便开发者了解系统I/O事件情况。 \ No newline at end of file diff --git a/17-biopattern/biostacks.md b/17-biopattern/biostacks.md deleted file mode 100644 index 3fb08fd..0000000 --- a/17-biopattern/biostacks.md +++ /dev/null @@ -1,100 +0,0 @@ -## eBPF 入门实践教程:编写 eBPF 程序 Biostacks: 监控内核 I/O 操作耗时 - - -### 背景 -由于有些磁盘I/O操作不是直接由应用发起的,比如元数据读写,因此有些直接捕捉磁盘I/O操作信息可能 -会有一些无法解释的I/O操作发生。为此,Biostacks 会直接追踪内核中初始化I/O操作的函数,并将磁 -盘I/O操作耗时以直方图的形式展现。 - -### 实现原理 -Biostacks 的挂载点为 fentry/blk_account_io_start, kprobe/blk_account_io_merge_bio 和 -fentry/blk_account_io_done。fentry/blk_account_io_start 和 kprobe/blk_account_io_merge_bio -挂载点均时内核需要发起I/O操作中必经的初始化路径。在经过此处时,Biostacks 会根据 request queue ,将数据存入 -map中。 -```c -static __always_inline -int trace_start(void *ctx, struct request *rq, bool merge_bio) -{ - struct internal_rqinfo *i_rqinfop = NULL, i_rqinfo = {}; - struct gendisk *disk = BPF_CORE_READ(rq, rq_disk); - dev_t dev; - - dev = disk ? MKDEV(BPF_CORE_READ(disk, major), - BPF_CORE_READ(disk, first_minor)) : 0; - if (targ_dev != -1 && targ_dev != dev) - return 0; - - if (merge_bio) - i_rqinfop = bpf_map_lookup_elem(&rqinfos, &rq); - if (!i_rqinfop) - i_rqinfop = &i_rqinfo; - - i_rqinfop->start_ts = bpf_ktime_get_ns(); - i_rqinfop->rqinfo.pid = bpf_get_current_pid_tgid(); - i_rqinfop->rqinfo.kern_stack_size = - bpf_get_stack(ctx, i_rqinfop->rqinfo.kern_stack, - sizeof(i_rqinfop->rqinfo.kern_stack), 0); - bpf_get_current_comm(&i_rqinfop->rqinfo.comm, - sizeof(&i_rqinfop->rqinfo.comm)); - i_rqinfop->rqinfo.dev = dev; - - if (i_rqinfop == &i_rqinfo) - bpf_map_update_elem(&rqinfos, &rq, i_rqinfop, 0); - return 0; -} - -SEC("fentry/blk_account_io_start") -int BPF_PROG(blk_account_io_start, struct request *rq) -{ - return trace_start(ctx, rq, false); -} - -SEC("kprobe/blk_account_io_merge_bio") -int BPF_KPROBE(blk_account_io_merge_bio, struct request *rq) -{ - return trace_start(ctx, rq, true); -} - -``` -在I/O操作完成后,fentry/blk_account_io_done 下的处理函数会从map中读取之前存入的信息,根据当下时间 -记录时间差值,得到I/O操作的耗时信息,并更新到存储直方图数据的map中。 -```c -SEC("fentry/blk_account_io_done") -int BPF_PROG(blk_account_io_done, struct request *rq) -{ - u64 slot, ts = bpf_ktime_get_ns(); - struct internal_rqinfo *i_rqinfop; - struct rqinfo *rqinfop; - struct hist *histp; - s64 delta; - - i_rqinfop = bpf_map_lookup_elem(&rqinfos, &rq); - if (!i_rqinfop) - return 0; - delta = (s64)(ts - i_rqinfop->start_ts); - if (delta < 0) - goto cleanup; - histp = bpf_map_lookup_or_try_init(&hists, &i_rqinfop->rqinfo, &zero); - if (!histp) - goto cleanup; - 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(&rqinfos, &rq); - return 0; -} -``` -在用户输入程序退出指令后,其用户态程序会将直方图map中的信息读出并打印。 - -### Eunomia中使用方式 - - -### 总结 -Biostacks 从源头实现了对I/O操作的追踪,可以极大的方便我们掌握磁盘I/O情况。 \ No newline at end of file diff --git a/17-biopattern/bitesize.md b/17-biopattern/bitesize.md deleted file mode 100644 index dd4a0be..0000000 --- a/17-biopattern/bitesize.md +++ /dev/null @@ -1,63 +0,0 @@ -## eBPF 入门实践教程:编写 eBPF 程序 Bitesize: 监控块设备 I/O - -### 背景 - -为了能更好的获得 I/O 操作需要的磁盘块大小相关信息,Bitesize 工具被开发。它可以在启动后追踪 -不同进程所需要的块大小,并以直方图的形式显示分布 - -### 实现原理 - -Biteszie 在 block_rq_issue 追踪点下挂在了处理函数。当进程对磁盘发出了块 I/O 请求操作时, -系统会经过此挂载点,此时处理函数或许请求的信息,将其存入对应的map中。 -```c -static int trace_rq_issue(struct request *rq) -{ - struct hist_key hkey; - struct hist *histp; - u64 slot; - - if (filter_dev) { - struct gendisk *disk = get_disk(rq); - u32 dev; - - dev = disk ? MKDEV(BPF_CORE_READ(disk, major), - BPF_CORE_READ(disk, first_minor)) : 0; - if (targ_dev != dev) - return 0; - } - bpf_get_current_comm(&hkey.comm, sizeof(hkey.comm)); - if (!comm_allowed(hkey.comm)) - return 0; - - histp = bpf_map_lookup_elem(&hists, &hkey); - if (!histp) { - bpf_map_update_elem(&hists, &hkey, &initial_hist, 0); - histp = bpf_map_lookup_elem(&hists, &hkey); - if (!histp) - return 0; - } - slot = log2l(rq->__data_len / 1024); - if (slot >= MAX_SLOTS) - slot = MAX_SLOTS - 1; - __sync_fetch_and_add(&histp->slots[slot], 1); - - return 0; -} - -SEC("tp_btf/block_rq_issue") -int BPF_PROG(block_rq_issue) -{ - if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(5, 11, 0)) - return trace_rq_issue((void *)ctx[0]); - else - return trace_rq_issue((void *)ctx[1]); -} -``` - -当用户发出中止工具的指令后,其用户态代码会将map中存储的数据读出并逐进程的展示追踪结果 - -### Eunomia中使用方式 - - -### 总结 -Bitesize 以进程为粒度,使得开发者可以更好的掌握程序对磁盘 I/O 的请求情况。 \ No newline at end of file diff --git a/18-further-reading/README.md b/18-further-reading/README.md index 167db71..5d71abf 100644 --- a/18-further-reading/README.md +++ b/18-further-reading/README.md @@ -1,81 +1 @@ -## eBPF 入门实践教程:编写 eBPF 程序 syscount 监控慢系统调用 - -### 背景 - -`syscount` 可以统计系统或者某个进程发生的各类syscall的总数或者时耗时。 - -### 实现原理 -`syscount` 的实现逻辑非常直观,他在 `sys_enter` 和 `sys_exit` 这两个 `tracepoint` 下挂载了 -执行函数。 -```c -SEC("tracepoint/raw_syscalls/sys_enter") -int sys_enter(struct trace_event_raw_sys_enter *args) -{ - u64 id = bpf_get_current_pid_tgid(); - pid_t pid = id >> 32; - u32 tid = id; - u64 ts; - - if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) - return 0; - - if (filter_pid && pid != filter_pid) - return 0; - - ts = bpf_ktime_get_ns(); - bpf_map_update_elem(&start, &tid, &ts, 0); - return 0; -} - -SEC("tracepoint/raw_syscalls/sys_exit") -int sys_exit(struct trace_event_raw_sys_exit *args) -{ - if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) - return 0; - - u64 id = bpf_get_current_pid_tgid(); - static const struct data_t zero; - pid_t pid = id >> 32; - struct data_t *val; - u64 *start_ts, lat = 0; - u32 tid = id; - u32 key; - - /* this happens when there is an interrupt */ - if (args->id == -1) - return 0; - - if (filter_pid && pid != filter_pid) - return 0; - if (filter_failed && args->ret >= 0) - return 0; - if (filter_errno && args->ret != -filter_errno) - return 0; - - if (measure_latency) { - start_ts = bpf_map_lookup_elem(&start, &tid); - if (!start_ts) - return 0; - lat = bpf_ktime_get_ns() - *start_ts; - } - - key = (count_by_process) ? pid : args->id; - val = bpf_map_lookup_or_try_init(&data, &key, &zero); - if (val) { - __sync_fetch_and_add(&val->count, 1); - if (count_by_process) - save_proc_name(val); - if (measure_latency) - __sync_fetch_and_add(&val->total_ns, lat); - } - return 0; -} - -``` -当syscall发生时,`syscount`会记录其tid和发生的时间并存入map中。在syscall完成时,`syscount` 会根据用户 -的需求,统计syscall持续的时间,或者是发生的次数。 -### Eunomia中使用方式 - - -### 总结 -`sycount` 使得用户可以较为方便的追踪某个进程或者是系统内系统调用发生的情况。 \ No newline at end of file +# 更多的参考资料 \ No newline at end of file diff --git a/2-kprobe-unlink/.gitignore b/2-kprobe-unlink/.gitignore index 7d5aebf..f0f4c81 100644 --- a/2-kprobe-unlink/.gitignore +++ b/2-kprobe-unlink/.gitignore @@ -4,3 +4,4 @@ package.json *.skel.json *.skel.yaml package.yaml +ecli diff --git a/2-kprobe-unlink/README.md b/2-kprobe-unlink/README.md index c0b370c..e77def0 100644 --- a/2-kprobe-unlink/README.md +++ b/2-kprobe-unlink/README.md @@ -76,6 +76,15 @@ Packing ebpf object and config into package.json... sudo ecli package.json ``` +在另外一个窗口中: + +```shell +touch test1 +rm test1 +touch test2 +rm test2 +``` + 在 /sys/kernel/debug/tracing/trace_pipe 文件中,应该能看到类似下面的 kprobe 演示输出: ```shell @@ -90,4 +99,4 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe 通过本文的示例,我们学习了如何使用 eBPF 的 kprobe 和 kretprobe 捕获 unlink 系统调用。更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:https://github.com/eunomia-bpf/eunomia-bpf -本文是 eBPF 入门开发实践指南的第二篇。下一篇文章将介绍如何使用 eBPF 的内核映射(maps)进行数据交换和共享。 +本文是 eBPF 入门开发实践指南的第二篇。下一篇文章将介绍如何在 eBPF 中使用 fentry 监测捕获 unlink 系统调用 diff --git a/20-tc/README.md b/20-tc/README.md index 3b5f1ec..501b6f2 100644 --- a/20-tc/README.md +++ b/20-tc/README.md @@ -1,8 +1,70 @@ ## eBPF 入门实践教程: -`tc` (short for Traffic Control) is an example of handling ingress network traffics. -It creates a qdisc on the `lo` interface and attaches the `tc_ingress` BPF program to it. -It reports the metadata of the IP packets that coming into the `lo` interface. +## tc 程序示例 + +```c +#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 提供附加点和选项信息。例如,在这段代码的开头,我们使用了以下注释: + +```c +/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"} +/// @tcopts {"handle":1, "priority":1} +``` + +这些注释告诉 TC 将 eBPF 程序附加到网络接口的 ingress 附加点,并指定了 handle 和 priority 选项的值。 + +总之,这段代码实现了一个简单的 eBPF 程序,用于捕获数据包并打印出它们的信息。 + +## 编译运行 + +```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 @@ -21,27 +83,3 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe 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 ``` - -## Compile and Run - - - -Compile: - -```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... -``` - -Run: - -```console -sudo ecli ./package.json -``` \ No newline at end of file diff --git a/3-fentry-unlink/.gitignore b/3-fentry-unlink/.gitignore index 7d5aebf..f0f4c81 100644 --- a/3-fentry-unlink/.gitignore +++ b/3-fentry-unlink/.gitignore @@ -4,3 +4,4 @@ package.json *.skel.json *.skel.yaml package.yaml +ecli diff --git a/3-fentry-unlink/README.md b/3-fentry-unlink/README.md index 66f16a1..d0e1393 100644 --- a/3-fentry-unlink/README.md +++ b/3-fentry-unlink/README.md @@ -50,6 +50,15 @@ $ sudo ecli package.json Runing eBPF program... ``` +在另外一个窗口中: + +```shell +touch test_file +rm test_file +touch test_file2 +rm test_file2 +``` + 运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出: ```console diff --git a/4-opensnoop/README.md b/4-opensnoop/README.md index df5fdd6..59bd1a5 100644 --- a/4-opensnoop/README.md +++ b/4-opensnoop/README.md @@ -9,45 +9,28 @@ eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网 首先,我们需要编写一段 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 +/// @description "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; + 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; + 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. +/// "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 函数在内核日志中打印出来。 @@ -66,9 +49,7 @@ Runing eBPF program... ```console $ sudo cat /sys/kernel/debug/tracing/trace_pipe - <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: Process ID: 3840345 enter sys open <...>-3840345 [010] d... 3220701.101179: bpf_trace_printk: Process ID: 3840345 enter sys openat - <...>-3840345 [010] d... 3220702.157967: bpf_trace_printk: Process ID: 3840345 enter sys open <...>-3840345 [010] d... 3220702.158000: bpf_trace_printk: Process ID: 3840345 enter sys openat ``` @@ -80,12 +61,39 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe 可以通过执行 ecli -h 命令来查看 opensnoop 的帮助信息: -```c +```console +$ ecli package.json -h +Usage: opensnoop_bpf [--help] [--version] [--verbose] [--pid_target VAR] +Trace open family syscalls. + +Optional arguments: + -h, --help shows help message and exits + -v, --version prints version information and exits + --verbose prints libbpf debug information + --pid_target Process ID to trace + +Built with eunomia-bpf framework. +See https://github.com/eunomia-bpf/eunomia-bpf for more information. +``` + +可以通过 --pid_target 参数来指定要捕获的进程的 pid,例如: + +```console +$ sudo ./ecli run package.json --pid_target 618 +Runing eBPF program... +``` + +运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出: + +```console +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + <...>-3840345 [010] d... 3220701.101179: bpf_trace_printk: Process ID: 618 enter sys openat + <...>-3840345 [010] d... 3220702.158000: bpf_trace_printk: Process ID: 618 enter sys openat ``` ## 总结 本文介绍了如何使用 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 的官方文档:https://github.com/eunomia-bpf/eunomia-bpf \ No newline at end of file +更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档: diff --git a/4-opensnoop/opensnoop.bpf.c b/4-opensnoop/opensnoop.bpf.c index 4b9f2a8..93cef09 100644 --- a/4-opensnoop/opensnoop.bpf.c +++ b/4-opensnoop/opensnoop.bpf.c @@ -1,27 +1,9 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (c) 2019 Facebook -// Copyright (c) 2020 Netflix #include #include -#include "opensnoop.h" - -/// Process ID to trace +/// @description "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) { @@ -35,5 +17,5 @@ int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter* ctx return 0; } -/// Trace open family syscalls. +/// "Trace open family syscalls." char LICENSE[] SEC("license") = "GPL"; diff --git a/4-opensnoop/opensnoop.h b/4-opensnoop/opensnoop.h deleted file mode 100644 index a5aa43f..0000000 --- a/4-opensnoop/opensnoop.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ -#ifndef __OPENSNOOP_H -#define __OPENSNOOP_H - -#define TASK_COMM_LEN 16 -#define NAME_MAX 255 -#define INVALID_UID ((uid_t)-1) - -// used for export event -struct event { - /* user terminology for pid: */ - unsigned long long ts; - int pid; - int uid; - int ret; - int flags; - char comm[TASK_COMM_LEN]; - char fname[NAME_MAX]; -}; - -#endif /* __OPENSNOOP_H */ \ No newline at end of file diff --git a/5-uprobe-bashreadline/README.md b/5-uprobe-bashreadline/README.md index 26b28f5..098b2c2 100644 --- a/5-uprobe-bashreadline/README.md +++ b/5-uprobe-bashreadline/README.md @@ -19,12 +19,9 @@ uprobe 是一种用于捕获用户空间函数调用的 eBPF 的探针,我们 例如,我们可以使用 uprobe 来捕获 bash 的 readline 函数调用,从而获取用户在 bash 中输入的命令行。示例代码如下: ```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 @@ -39,8 +36,9 @@ uprobe 是一种用于捕获用户空间函数调用的 eBPF 的探针,我们 * specified (and auto-attach is not possible) or the above format is specified for * auto-attach. */ -SEC("uprobe//bin/bash:readline") -int BPF_KRETPROBE(printret, const void *ret) { +SEC("uretprobe//bin/bash:readline") +int BPF_KRETPROBE(printret, const void *ret) +{ char str[MAX_LINE_SIZE]; char comm[TASK_COMM_LEN]; u32 pid; @@ -98,13 +96,14 @@ Runing eBPF program... ```console $ sudo cat /sys/kernel/debug/tracing/trace_pipe -PID 12345 (bash) read: ls -l -PID 12345 (bash) read: date -PID 12345 (bash) read: echo "Hello eBPF!" + bash-32969 [000] d..31 64001.375748: bpf_trace_printk: PID 32969 (bash) read: fff + bash-32969 [000] d..31 64002.056951: bpf_trace_printk: PID 32969 (bash) read: fff ``` 可以看到,我们成功的捕获了 bash 的 readline 函数调用,并获取了用户在 bash 中输入的命令行。 ## 总结 -在上述代码中,我们使用了 SEC 宏来定义了一个 uprobe 探针,它指定了要捕获的用户空间程序 (bin/bash) 和要捕获的函数 (readline)。此外,我们还使用了 BPF_KRETPROBE 宏来定义了一个用于处理 readline 函数返回值的回调函数 (printret)。该函数可以获取到 readline 函数的返回值,并将其打印到内核日志中。通过这样的方式,我们就可以使用 eBPF 来捕获 bash 的 readline 函数调用,并获取用户在 bash 中输入的命令行。更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:https://github.com/eunomia-bpf/eunomia-bpf +在上述代码中,我们使用了 SEC 宏来定义了一个 uprobe 探针,它指定了要捕获的用户空间程序 (bin/bash) 和要捕获的函数 (readline)。此外,我们还使用了 BPF_KRETPROBE 宏来定义了一个用于处理 readline 函数返回值的回调函数 (printret)。该函数可以获取到 readline 函数的返回值,并将其打印到内核日志中。通过这样的方式,我们就可以使用 eBPF 来捕获 bash 的 readline 函数调用,并获取用户在 bash 中输入的命令行。 + +更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:https://github.com/eunomia-bpf/eunomia-bpf diff --git a/5-uprobe-bashreadline/bashreadline.bpf.c b/5-uprobe-bashreadline/bashreadline.bpf.c index 4457715..8058734 100644 --- a/5-uprobe-bashreadline/bashreadline.bpf.c +++ b/5-uprobe-bashreadline/bashreadline.bpf.c @@ -1,9 +1,6 @@ -/* 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 @@ -18,8 +15,9 @@ * specified (and auto-attach is not possible) or the above format is specified for * auto-attach. */ -SEC("uprobe//bin/bash:readline") -int BPF_KRETPROBE(printret, const void *ret) { +SEC("uretprobe//bin/bash:readline") +int BPF_KRETPROBE(printret, const void *ret) +{ char str[MAX_LINE_SIZE]; char comm[TASK_COMM_LEN]; u32 pid; diff --git a/6-sigsnoop/README.md b/6-sigsnoop/README.md index 46eae45..418de0f 100755 --- a/6-sigsnoop/README.md +++ b/6-sigsnoop/README.md @@ -2,9 +2,9 @@ ## sigsnoop +示例代码如下: + ```c -// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) -/* Copyright (c) 2021~2022 Hengqi Chen */ #include #include #include "sigsnoop.h" @@ -85,7 +85,6 @@ int tkill_exit(struct trace_event_raw_sys_exit *ctx) } char LICENSE[] SEC("license") = "Dual BSD/GPL"; - ``` 上面的代码定义了一个 eBPF 程序,用于捕获进程发送信号的系统调用,包括 kill、tkill 和 tgkill。它通过使用 tracepoint 来捕获系统调用的进入和退出事件,并在这些事件发生时执行指定的探针函数,例如 probe_entry 和 probe_exit。 diff --git a/6-sigsnoop/sigsnoop.bpf.c b/6-sigsnoop/sigsnoop.bpf.c index 119512b..97e602b 100755 --- a/6-sigsnoop/sigsnoop.bpf.c +++ b/6-sigsnoop/sigsnoop.bpf.c @@ -64,34 +64,4 @@ 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); -} - -SEC("tracepoint/syscalls/sys_enter_tgkill") -int tgkill_entry(struct trace_event_raw_sys_enter *ctx) -{ - pid_t tpid = (pid_t)ctx->args[1]; - int sig = (int)ctx->args[2]; - - return probe_entry(tpid, sig); -} - -SEC("tracepoint/syscalls/sys_exit_tgkill") -int tgkill_exit(struct trace_event_raw_sys_exit *ctx) -{ - return probe_exit(ctx, ctx->ret); -} - char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/6-sigsnoop/sigsnoop.md b/6-sigsnoop/sigsnoop.md index e59540c..eb51d85 100644 --- a/6-sigsnoop/sigsnoop.md +++ b/6-sigsnoop/sigsnoop.md @@ -6,80 +6,6 @@ `sigsnoop` 在利用了linux的tracepoint挂载点,其在syscall进入和退出的各个关键挂载点均挂载了执行函数。 ```c -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); -} - -SEC("tracepoint/syscalls/sys_enter_tgkill") -int tgkill_entry(struct trace_event_raw_sys_enter *ctx) -{ - pid_t tpid = (pid_t)ctx->args[1]; - int sig = (int)ctx->args[2]; - - return probe_entry(tpid, sig); -} - -SEC("tracepoint/syscalls/sys_exit_tgkill") -int tgkill_exit(struct trace_event_raw_sys_exit *ctx) -{ - return probe_exit(ctx, ctx->ret); -} - -SEC("tracepoint/signal/signal_generate") -int sig_trace(struct trace_event_raw_signal_generate *ctx) -{ - struct event event = {}; - pid_t tpid = ctx->pid; - int ret = ctx->errno; - int sig = ctx->sig; - __u64 pid_tgid; - __u32 pid; - - if (failed_only && ret == 0) - return 0; - - if (target_signal && sig != target_signal) - return 0; - - pid_tgid = bpf_get_current_pid_tgid(); - pid = pid_tgid >> 32; - if (filtered_pid && pid != filtered_pid) - return 0; - - event.pid = pid; - event.tpid = tpid; - event.sig = sig; - event.ret = ret; - 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/README.md b/7-execsnoop/README.md index 24ce82c..a3f5f29 100644 --- a/7-execsnoop/README.md +++ b/7-execsnoop/README.md @@ -1,7 +1,35 @@ -## eBPF 入门实践教程七:捕获进程执行/退出时间,通过 perf event array 向用户态打印输出 +# eBPF 入门实践教程七:捕获进程执行/退出时间,通过 perf event array 向用户态打印输出 + +eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。 + +本文是 eBPF 入门开发实践指南的第七篇,主要介绍如何捕获 Linux 内核中进程执行的事件,并且通过 perf event array 向用户态命令行打印输出,不需要再通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出。 ## execsnoop +通过 perf event array 向用户态命令行打印输出,需要编写一个头文件,一个 C 源文件。示例代码如下: + +头文件:execsnoop.h + +```c +#ifndef __EXECSNOOP_H +#define __EXECSNOOP_H + +#define TASK_COMM_LEN 16 + +struct event { + int pid; + int ppid; + int uid; + int retval; + bool is_exit; + char comm[TASK_COMM_LEN]; +}; + +#endif /* __EXECSNOOP_H */ +``` + +源文件:execsnoop.bpf.c + ```c // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) #include @@ -76,8 +104,6 @@ char LICENSE[] SEC("license") = "GPL"; 使用这段代码,我们就可以捕获 Linux 内核中进程执行的事件。我们可以通过工具(例如 eunomia-bpf)来查看这些事件,并分析进程的执行情况。 - - ## Compile and Run Compile: @@ -104,109 +130,3 @@ time pid ppid uid retval args_count args_size comm args 23:07:57 32959 32946 1000 0 1 17 oneko /usr/games/oneko ``` - -## details in bcc - -Demonstrations of execsnoop, the Linux eBPF/bcc version. - -execsnoop traces the exec() syscall system-wide, and prints various details. -Example output: - -``` -# ./execsnoop -COMM PID PPID RET ARGS -bash 33161 24577 0 /bin/bash -lesspipe 33163 33161 0 /usr/bin/lesspipe -basename 33164 33163 0 /usr/bin/basename /usr/bin/lesspipe -dirname 33166 33165 0 /usr/bin/dirname /usr/bin/lesspipe -dircolors 33167 33161 0 /usr/bin/dircolors -b -ls 33172 33161 0 /usr/bin/ls --color=auto -top 33173 33161 0 /usr/bin/top -oneko 33174 33161 0 /usr/games/oneko -systemctl 33175 2975 0 /bin/systemctl is-enabled -q whoopsie.path -apport-checkrep 33176 2975 0 /usr/share/apport/apport-checkreports -apport-checkrep 33177 2975 0 /usr/share/apport/apport-checkreports --system -apport-checkrep 33178 2975 0 /usr/share/apport/apport-checkreports --system - -``` - -This shows process information when exec system call is called. - -USAGE message: - -``` -usage: execsnoop [-h] [-T] [-t] [-x] [--cgroupmap CGROUPMAP] - [--mntnsmap MNTNSMAP] [-u USER] [-q] [-n NAME] - [-l LINE] [-U] [--max-args MAX_ARGS] - -Trace exec() syscalls - -options: - -h, --help show this help message and exit - -T, --time include time column on output (HH:MM:SS) - -t, --timestamp include timestamp on output - -x, --fails include failed exec()s - --cgroupmap CGROUPMAP - trace cgroups in this BPF map only - --mntnsmap MNTNSMAP trace mount namespaces in this BPF map only - -u USER, --uid USER trace this UID only - -q, --quote Add quotemarks (") around arguments. - -n NAME, --name NAME only print commands matching this name (regex), any - arg - -l LINE, --line LINE only print commands where arg contains this line - (regex) - -U, --print-uid print UID column - --max-args MAX_ARGS maximum number of arguments parsed and displayed, - defaults to 20 - -examples: - ./execsnoop # trace all exec() syscalls - ./execsnoop -x # include failed exec()s - ./execsnoop -T # include time (HH:MM:SS) - ./execsnoop -U # include UID - ./execsnoop -u 1000 # only trace UID 1000 - ./execsnoop -u user # get user UID and trace only them - ./execsnoop -t # include timestamps - ./execsnoop -q # add "quotemarks" around arguments - ./execsnoop -n main # only print command lines containing "main" - ./execsnoop -l tpkg # only print command where arguments contains "tpkg" - ./execsnoop --cgroupmap mappath # only trace cgroups in this BPF map - ./execsnoop --mntnsmap mappath # only trace mount namespaces in the map - - -``` - -The -T and -t option include time and timestamps on output: - -``` -# ./execsnoop -T -t -TIME TIME(s) PCOMM PID PPID RET ARGS -23:35:25 4.335 bash 33360 24577 0 /bin/bash -23:35:25 4.338 lesspipe 33361 33360 0 /usr/bin/lesspipe -23:35:25 4.340 basename 33362 33361 0 /usr/bin/basename /usr/bin/lesspipe -23:35:25 4.342 dirname 33364 33363 0 /usr/bin/dirname /usr/bin/lesspipe -23:35:25 4.347 dircolors 33365 33360 0 /usr/bin/dircolors -b -23:35:40 19.327 touch 33367 33366 0 /usr/bin/touch /run/udev/gdm-machine-has-hardware-gpu -23:35:40 19.329 snap-device-hel 33368 33366 0 /usr/lib/snapd/snap-device-helper change snap_firefox_firefox /devices/pci0000:00/0000:00:02.0/drm/card0 226:0 -23:35:40 19.331 snap-device-hel 33369 33366 0 /usr/lib/snapd/snap-device-helper change snap_firefox_geckodriver /devices/pci0000:00/0000:00:02.0/drm/card0 226:0 -23:35:40 19.332 snap-device-hel 33370 33366 0 /usr/lib/snapd/snap-device-helper change snap_snap-store_snap-store /devices/pci0000:00/0000:00:02.0/drm/card0 226:0 - -``` - -The -u option filtering UID: - -``` -# ./execsnoop -Uu 1000 -UID PCOMM PID PPID RET ARGS -1000 bash 33604 24577 0 /bin/bash -1000 lesspipe 33606 33604 0 /usr/bin/lesspipe -1000 basename 33607 33606 0 /usr/bin/basename /usr/bin/lesspipe -1000 dirname 33609 33608 0 /usr/bin/dirname /usr/bin/lesspipe -1000 dircolors 33610 33604 0 /usr/bin/dircolors -b -1000 sleep 33615 33604 0 /usr/bin/sleep -1000 sleep 33616 33604 0 /usr/bin/sleep 1 -1000 clear 33617 33604 0 /usr/bin/clear - -``` - -Report bugs to https://github.com/iovisor/bcc/tree/master/libbpf-tools. diff --git a/7-execsnoop/execsnoop.bpf.h b/7-execsnoop/execsnoop.h similarity index 56% rename from 7-execsnoop/execsnoop.bpf.h rename to 7-execsnoop/execsnoop.h index 51dd1fd..7f7f3b1 100644 --- a/7-execsnoop/execsnoop.bpf.h +++ b/7-execsnoop/execsnoop.h @@ -2,13 +2,7 @@ #ifndef __EXECSNOOP_H #define __EXECSNOOP_H -#define ARGSIZE 128 #define TASK_COMM_LEN 16 -#define TOTAL_MAX_ARGS 60 -#define DEFAULT_MAXARGS 20 -#define FULL_MAX_ARGS_ARR (TOTAL_MAX_ARGS * ARGSIZE) -#define INVALID_UID ((uid_t)-1) -#define LAST_ARG (FULL_MAX_ARGS_ARR - ARGSIZE) struct event { int pid; diff --git a/8-runqslower/README.md b/8-runqslower/README.md index 506e6b3..d624438 100644 --- a/8-runqslower/README.md +++ b/8-runqslower/README.md @@ -28,6 +28,16 @@ time task prev_task delta_us pid prev_pid 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: diff --git a/8-runqslower/core_fixes.h b/8-runqslower/core_fixes.bpf.h similarity index 100% rename from 8-runqslower/core_fixes.h rename to 8-runqslower/core_fixes.bpf.h diff --git a/8-runqslower/runqslower.bpf.c b/8-runqslower/runqslower.bpf.c index ebc0103..e99644d 100644 --- a/8-runqslower/runqslower.bpf.c +++ b/8-runqslower/runqslower.bpf.c @@ -4,8 +4,8 @@ #include #include #include -#include "runqslower.bpf.h" -#include "core_fixes.h" +#include "runqslower.h" +#include "core_fixes.bpf.h" #define TASK_RUNNING 0 diff --git a/8-runqslower/runqslower.bpf.h b/8-runqslower/runqslower.h similarity index 100% rename from 8-runqslower/runqslower.bpf.h rename to 8-runqslower/runqslower.h diff --git a/9-runqlat/bits.bpf copy.h b/9-runqlat/bits.bpf copy.h deleted file mode 100644 index a2b7bb9..0000000 --- a/9-runqlat/bits.bpf copy.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ -#ifndef __BITS_BPF_H -#define __BITS_BPF_H - -#define READ_ONCE(x) (*(volatile typeof(x) *)&(x)) -#define WRITE_ONCE(x, val) ((*(volatile typeof(x) *)&(x)) = val) - -static __always_inline u64 log2(u32 v) -{ - u32 shift, r; - - r = (v > 0xFFFF) << 4; v >>= r; - shift = (v > 0xFF) << 3; v >>= shift; r |= shift; - shift = (v > 0xF) << 2; v >>= shift; r |= shift; - shift = (v > 0x3) << 1; v >>= shift; r |= shift; - r |= (v >> 1); - - return r; -} - -static __always_inline u64 log2l(u64 v) -{ - u32 hi = v >> 32; - - if (hi) - return log2(hi) + 32; - else - return log2(v); -} - -#endif /* __BITS_BPF_H */ diff --git a/9-runqlat/maps.bpf copy.h b/9-runqlat/maps.bpf copy.h deleted file mode 100644 index 51d1012..0000000 --- a/9-runqlat/maps.bpf copy.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) -// Copyright (c) 2020 Anton Protopopov -#ifndef __MAPS_BPF_H -#define __MAPS_BPF_H - -#include -#include - -static __always_inline void * -bpf_map_lookup_or_try_init(void *map, const void *key, const void *init) -{ - void *val; - long err; - - val = bpf_map_lookup_elem(map, key); - if (val) - return val; - - err = bpf_map_update_elem(map, key, init, BPF_NOEXIST); - if (err && err != -EEXIST) - return 0; - - return bpf_map_lookup_elem(map, key); -} - -#endif /* __MAPS_BPF_H */ diff --git a/imgs/ebpf-chatgpt-signal.png b/imgs/ebpf-chatgpt-signal.png new file mode 100644 index 0000000..028954e Binary files /dev/null and b/imgs/ebpf-chatgpt-signal.png differ diff --git a/imgs/ebpf-chatgpt-signal2.png b/imgs/ebpf-chatgpt-signal2.png new file mode 100644 index 0000000..bc364e1 Binary files /dev/null and b/imgs/ebpf-chatgpt-signal2.png differ