teach chatgpt for perf event

This commit is contained in:
yunwei37
2022-12-05 19:47:51 +08:00
parent f05a840fce
commit 0fd76b02b8
26 changed files with 335 additions and 587 deletions

View File

@@ -11,6 +11,7 @@
- [1.3.2. 指令编码格式](#132-指令编码格式)
- [1.4. 本节参考文章](#14-本节参考文章)
- [2. 如何使用eBPF编程](#2-如何使用ebpf编程)
- [编写 eBPF 程序](#编写-ebpf-程序)
- [2.1. BCC](#21-bcc)
- [2.2. libbpf-bootstrap](#22-libbpf-bootstrap)
- [2.3 eunomia-bpf](#23-eunomia-bpf)
@@ -90,7 +91,7 @@ llvm于2015年推出了可以将由高级语言编写的代码编译为eBPF字
的函数以及一些其他的关键函数。在Linux的源码包的`samples/bpf/`目录下有大量Linux
提供的基于`libbpf`的eBPF样例代码。
一个典型的基于`libbpf`的eBPF程序具有`*_kern.c``*_user.c`两个文件,
一个典型的基于 `libbpf` 的eBPF程序具有`*_kern.c``*_user.c`两个文件,
`*_kern.c`中书写在内核中的挂载点以及处理函数,`*_user.c`中书写用户态代码,
完成内核态代码注入以及与用户交互的各种任务。 更为详细的教程可以参考[该视频](https://www.bilibili.com/video/BV1f54y1h74r?spm_id_from=333.999.0.0)
然而由于该方法仍然较难理解且入门存在一定的难度因此现阶段的eBPF程序开发大多基于一些工具比如
@@ -99,10 +100,22 @@ llvm于2015年推出了可以将由高级语言编写的代码编译为eBPF字
- BPFtrace
- libbpf-bootstrap
以及还有比较新的工具,例如 `eunomia-bpf` 将 CO-RE eBPF 功能作为服务运行,包含一个工具链和一个运行时,主要功能包括:
以及还有比较新的工具,例如 `eunomia-bpf`.
- 不需要再为每个 eBPF 工具编写用户态代码框架:大多数情况下只需要编写内核态应用程序,即可实现正确加载运行 eBPF 程序;同时所需编写的内核态代码和 libbpf 完全兼容,可轻松实现迁移;
- 提供基于 async Rust 的 Prometheus 或 OpenTelemetry 自定义可观测性数据收集器通常仅占用不到1%的资源开销,编写内核态代码和 yaml 配置文件即可实现 eBPF 信息可视化,编译后可在其他机器上通过 API 请求直接部署;
## 编写 eBPF 程序
eBPF 程序由内核态部分和用户态部分构成。内核态部分包含程序的实际逻辑,用户态部分负责加载和管理内核态部分。使用 eunomia-bpf 开发工具,只需编写内核态部分的代码。
内核态部分的代码需要符合 eBPF 的语法和指令集。eBPF 程序主要由若干个函数组成,每个函数都有其特定的作用。可以使用的函数类型包括:
- kprobe插探函数在指定的内核函数前或后执行。
- tracepoint跟踪点函数在指定的内核跟踪点处执行。
- raw_tracepoint原始跟踪点函数在指定的内核原始跟踪点处执行。
- xdp网络数据处理函数拦截和处理网络数据包。
- perf_event性能事件函数用于处理内核性能事件。
- kretprobe函数返回插探函数在指定的内核函数返回时执行。
- tracepoint_return跟踪点函数返回在指定的内核跟踪点返回时执行。
- raw_tracepoint_return原始跟踪点函数返回在指定的内核原始跟踪
### 2.1. BCC
@@ -156,4 +169,8 @@ eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的
> - eunomia-bpf 项目 Github 地址: <https://github.com/eunomia-bpf/eunomia-bpf>
> - gitee 镜像: <https://gitee.com/anolis/eunomia>
## 参考资料
## 参考资料
- eBPF 介绍https://ebpf.io/
- BPF Compiler Collection (BCC)https://github.com/iovisor/bcc
- eunomia-bpfhttps://github.com/eunomia-bpf/eunomia-bpf

View File

@@ -4,6 +4,32 @@ eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网
本文是 eBPF 入门开发实践指南的第二篇,主要介绍 eBPF 的基本框架和开发流程。
开发 eBPF 程序可以使用多种工具,如 BCC、eunomia-bpf 等。不同的工具有不同的特点,但基本流程大致相同。
## 开发 eBPF 程序的流程
下面以 BCC 工具为例,介绍 eBPF 程序的基本开发流程。
1. 安装编译环境和依赖。使用 BCC 开发 eBPF 程序需要安装 LLVM/Clang 和 bcc以及其它的依赖库。
2. 编写 eBPF 程序。eBPF 程序主要由两部分构成:内核态部分和用户态部分。内核态部分包含 eBPF 程序的实际逻辑,用户态部分负责加载、运行和监控内核态程序。
3. 编译和加载 eBPF 程序。使用 bcc 工具将 eBPF 程序编译成机器码,然后使用用户态代码加载并运行该程序。
4. 运行程序并处理数据。eBPF 程序在内核运行时会触发事件,并将事件相关的信息传递给用户态程序。用户态程序负责处理这些信息并将结果输出。
5. 结束程序。当 eBPF 程序运行完成后,用户态程序可以卸载并结束运行。
通过这个过程,你可以开发出一个能够在内核中运行的 eBPF 程序。
## 使用 eunomia-bpf 开发 eBPF 程序
eunomia-bpf 是一个开源的 eBPF 动态加载运行时和开发工具链,它的目的是简化 eBPF 程序的开发、构建、分发、运行。它基于 libbpf 的 CO-RE 轻量级开发框架,支持通过用户态 WASM 虚拟机控制 eBPF 程序的加载和执行,并将预编译的 eBPF 程序打包为通用的 JSON 或 WASM 模块进行分发。使用 eunomia-bpf 可以大幅简化 eBPF 程序的开发流程。
使用 eunomia-bpf 开发 eBPF 程序的流程也大致相同,只是细节略有不同。
1. 安装编译环境和依赖。使用 eunomia-bpf 开发 eBPF 程序需要安装 eunomia-bpf 工具链和运行时库,以及其它的依赖库。
2. 编写 eBPF 程序。eBPF 程序主要由两部分构成:内核态部分和用户态部分。内核态部分包含 eBPF 程序的实际逻辑,用户态部分负责加载、运行和监控内核态程序。使用 eunomia-bpf只需编写内核态代码即可无需编写用户态代码。
3. 编译和加载 eBPF 程序。使用 eunomia-bpf 工具链将 eBPF 程序编译成机器码,并将编译后的代码打包为可以在任何系统上运行的模块。然后使用 eunomia-bpf 运行时库加载并运行该模块。
4. 运行程序并处理数据。eBPF 程序在内核运行时会触发事件并将事件相关的信息传递给用户态程序。eunomia-bpf 的运行时库负责处理这些信息并将结果输出。
5. 结束程序。当 eBPF 程序运行完成后eunomia-bpf 的运行时库可以卸载并结束运行
## 下载安装 eunomia-bpf 开发工具
可以通过以下步骤下载和安装 eunomia-bpf
@@ -94,15 +120,19 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
如上所述, eBPF 程序的基本框架包括:
- 包含头文件:需要包含 <linux/bpf.h> 和 <bpf/bpf_helpers.h>。
- 包含头文件:需要包含 <linux/bpf.h> 和 <bpf/bpf_helpers.h> 等头文件
- 定义许可证:需要定义许可证,通常使用 "Dual BSD/GPL"。
- 定义 BPF 函数:需要定义一个 BPF 函数,例如其名称为 handle_tp其参数为 void *ctx返回值为 int。
- 定义 BPF 函数:需要定义一个 BPF 函数,例如其名称为 handle_tp其参数为 void *ctx返回值为 int。通常用 C 语言编写。
- 使用 BPF 助手函数:在例如 BPF 函数中,可以使用 BPF 助手函数 bpf_get_current_pid_tgid() 和 bpf_printk()。
- 返回值
## eBPF 程序的开发流程
## tracepoints
eBPF 程序的开发流程可以概括为如下几个步骤:
跟踪点tracepoints是内核静态插桩技术跟踪点在技术上只是放置在内核源代码中的跟踪函数实际上就是在源码中插入的一些带有控制条件的探测点这些探测点允许事后再添加处理函数。比如在内核中最常见的静态跟踪方法就是 printk即输出日志。又比如在系统调用、调度程序事件、文件系统操作和磁盘 I/O 的开始和结束时都有跟踪点。 于 2009 年在 Linux 2.6.32 版本中首次提供。跟踪点是一种稳定的 API数量有限。
## 总结
eBPF 程序的开发和使用流程可以概括为如下几个步骤:
- 定义 eBPF 程序的接口和类型:这包括定义 eBPF 程序的接口函数,定义和实现 eBPF 内核映射maps和共享内存perf events以及定义和使用 eBPF 内核帮助函数helpers
- 编写 eBPF 程序的代码:这包括编写 eBPF 程序的主要逻辑,实现 eBPF 内核映射的读写操作,以及使用 eBPF 内核帮助函数。
@@ -111,4 +141,4 @@ eBPF 程序的开发流程可以概括为如下几个步骤:
- 使用 eBPF 程序:这包括监测 eBPF 程序的运行情况,并使用 eBPF 内核映射和共享内存进行数据交换和共享。
- 在实际开发中,还可能需要进行其他的步骤,例如配置编译和加载参数,管理 eBPF 内核模块和内核映射,以及使用其他高级功能等。
需要注意的是BPF 程序的执行是在内核空间进行的,因此需要使用特殊的工具和技术来编写、编译和调试 BPF 程序。eunomia-bpf 是一个开源的 BPF 编译器和工具包,它可以帮助开发者快速和简单地编写和运行 BPF 程序。
需要注意的是BPF 程序的执行是在内核空间进行的,因此需要使用特殊的工具和技术来编写、编译和调试 BPF 程序。eunomia-bpf 是一个开源的 BPF 编译器和工具包,它可以帮助开发者快速和简单地编写和运行 BPF 程序。

View File

@@ -0,0 +1 @@
## eBPF 入门实践教程:

View File

@@ -1,12 +1,4 @@
---
layout: post
title: tcpconnlat
date: 2022-10-10 16:18
category: bpftools
author: yunwei37
tags: [bpftools, syscall, network]
summary: Traces the kernel function performing active TCP connections(eg, via a connect() syscall; accept() are passive connections). and show connection latency.
---
## eBPF 入门实践教程:
## origin

View File

@@ -1,13 +1,4 @@
---
layout: post
title: tcpstates
date: 2022-10-10 16:18
category: bpftools
author: yunwei37
tags: [bpftools, syscall, network]
summary: Tcpstates prints TCP state change information, including the duration in each state as milliseconds
---
## eBPF 入门实践教程:
## origin

View File

@@ -1,13 +1,4 @@
---
layout: post
title: lsm-connect
date: 2022-10-10 16:18
category: bpftools
author: yunwei37
tags: [bpftools, examples, lsm, no-output]
summary: BPF LSM program (on socket_connect hook) that prevents any connection towards 1.1.1.1 to happen. Found in demo-cloud-native-ebpf-day
---
## eBPF 入门实践教程:
## run

View File

@@ -1,9 +1,32 @@
# eBPF 入门开发实践指南二:在 eBPF 中使用 kprobe 捕获 unlink 系统调用
# eBPF 入门开发实践指南二:在 eBPF 中使用 kprobe 监测捕获 unlink 系统调用
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
本文是 eBPF 入门开发实践指南的第二篇,在 eBPF 中使用 kprobe 捕获 unlink 系统调用。
## kprobes技术背景
开发人员在内核或者模块的调试过程中,往往会需要要知道其中的一些函数有无被调用、何时被调用、执行是否正确以及函数的入参和返回值是什么等等。比较简单的做法是在内核代码对应的函数中添加日志打印信息,但这种方式往往需要重新编译内核或模块,重新启动设备之类的,操作较为复杂甚至可能会破坏原有的代码执行过程。
而利用kprobes技术用户可以定义自己的回调函数然后在内核或者模块中几乎所有的函数中有些函数是不可探测的例如kprobes自身的相关实现函数后文会有详细说明动态的插入探测点当内核执行流程执行到指定的探测函数时会调用该回调函数用户即可收集所需的信息了同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息不再需要继续探测则同样可以动态地移除探测点。因此kprobes技术具有对内核执行流程影响小和操作方便的优点。
kprobes技术包括的3种探测手段分别时kprobe、jprobe和kretprobe。首先kprobe是最基本的探测方式是实现后两种的基础它可以在任意的位置放置探测点就连函数内部的某条指令处也可以它提供了探测点的调用前、调用后和内存访问出错3种回调方式分别是pre_handler、post_handler和fault_handler其中pre_handler函数将在被探测指令被执行前回调post_handler会在被探测指令执行完毕后回调注意不是被探测函数fault_handler会在内存访问出错时被调用jprobe基于kprobe实现它用于获取被探测函数的入参值最后kretprobe从名字中就可以看出其用途了它同样基于kprobe实现用于获取被探测函数的返回值。
kprobes的技术原理并不仅仅包含存软件的实现方案它也需要硬件架构提供支持。其中涉及硬件架构相关的是CPU的异常处理和单步调试技术前者用于让程序的执行流程陷入到用户注册的回调函数中去而后者则用于单步执行被探测点指令因此并不是所有的架构均支持目前kprobes技术已经支持多种架构包括i386、x86_64、ppc64、ia64、sparc64、arm、ppc和mips有些架构实现可能并不完全具体可参考内核的Documentation/kprobes.txt
kprobes的特点与使用限制
1. kprobes允许在同一个被被探测位置注册多个kprobe但是目前jprobe却不可以同时也不允许以其他的jprobe回调函数和kprobe的post_handler回调函数作为被探测点。
2. 一般情况下可以探测内核中的任何函数包括中断处理函数。不过在kernel/kprobes.c和arch/*/kernel/kprobes.c程序中用于实现kprobes自身的函数是不允许被探测的另外还有do_page_fault和notifier_call_chain
3. 如果以一个内联函数为探测点则kprobes可能无法保证对该函数的所有实例都注册探测点。由于gcc可能会自动将某些函数优化为内联函数因此可能无法达到用户预期的探测效果
4. 一个探测点的回调函数可能会修改被探测函数运行的上下文例如通过修改内核的数据结构或者保存与struct pt_regs结构体中的触发探测器之前寄存器信息。因此kprobes可以被用来安装bug修复代码或者注入故障测试代码
5. kprobes会避免在处理探测点函数时再次调用另一个探测点的回调函数例如在printk()函数上注册了探测点则在它的回调函数中可能再次调用printk函数此时将不再触发printk探测点的回调仅仅时增加了kprobe结构体中nmissed字段的数值
6. 在kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存
7. kprobes回调函数的运行期间是关闭内核抢占的同时也可能在关闭中断的情况下执行具体要视CPU架构而定。因此不论在何种情况下在回调函数中不要调用会放弃CPU的函数如信号量、mutex锁等
8. kretprobe通过替换返回地址为预定义的trampoline的地址来实现因此栈回溯和gcc内嵌函数__builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址
9. 如果一个函数的调用次数和返回次数不相等则在类似这样的函数上注册kretprobe将可能不会达到预期的效果例如do_exit()函数会存在问题而do_execve()函数和do_fork()函数不会;
10. 如果当在进入和退出一个函数时CPU运行在非当前任务所有的栈上那么往该函数上注册kretprobe可能会导致不可预料的后果因此kprobes不支持在X86_64的结构下为__switch_to()函数注册kretprobe将直接返回-EINVAL。
## kprobe
```c
@@ -37,7 +60,6 @@ int BPF_KRETPROBE(do_unlinkat_exit, long ret)
}
```
kprobe 是 eBPF 用于处理内核空间入口和出口返回探针kprobe 和 kretprobe的一个例子。它将 kprobe 和 kretprobe BPF 程序附加到 do_unlinkat() 函数上,并使用 bpf_printk() 宏分别记录 PID、文件名和返回值。
要编译这个程序,请使用 ecc 工具:
@@ -64,3 +86,8 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
rm-9346 [005] d..4 4710.951895: bpf_trace_printk: KPROBE EXIT: ret = 0
```
## 总结
通过本文的示例,我们学习了如何使用 eBPF 的 kprobe 和 kretprobe 捕获 unlink 系统调用。更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档https://github.com/eunomia-bpf/eunomia-bpf
本文是 eBPF 入门开发实践指南的第二篇。下一篇文章将介绍如何使用 eBPF 的内核映射maps进行数据交换和共享。

BIN
2-kprobe-unlink/ecli Executable file

Binary file not shown.

View File

@@ -27,4 +27,4 @@ int BPF_KRETPROBE(do_unlinkat_exit, long ret)
pid = bpf_get_current_pid_tgid() >> 32;
bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret);
return 0;
}
}

View File

@@ -1,13 +1,4 @@
---
layout: post
title: tc
date: 2022-10-10 16:18
category: bpftools
author: yunwei37
tags: [bpftools, tc, example]
summary: a minimal example of a BPF application use tc
---
## 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.

View File

@@ -1,4 +1,4 @@
# eBPF 入门开发实践指南三:在 eBPF 中使用 fentry 捕获 unlink 系统调用
# eBPF 入门开发实践指南三:在 eBPF 中使用 fentry 监测捕获 unlink 系统调用
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
@@ -59,3 +59,9 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
rm-9290 [004] d..2 4637.798698: bpf_trace_printk: fentry: pid = 9290, filename = test_file2
rm-9290 [004] d..2 4637.798843: bpf_trace_printk: fexit: pid = 9290, filename = test_file2, ret = 0
```
## 总结
这段程序是一个 eBPF 程序,通过使用 fentry 和 fexit 捕获 do_unlinkat 和 do_unlinkat_exit 函数,并通过使用 bpf_get_current_pid_tgid 和 bpf_printk 函数获取调用 do_unlinkat 的进程 ID、文件名和返回值并在内核日志中打印出来。
编译这个程序可以使用 ecc 工具,运行时可以使用 ecli 命令,并通过查看 /sys/kernel/debug/tracing/trace_pipe 文件查看 eBPF 程序的输出。更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档https://github.com/eunomia-bpf/eunomia-bpf

View File

@@ -1,4 +1,4 @@
# eBPF 入门开发实践指南四:捕获进程打开文件的系统调用集合,使用全局变量在 eBPF 中过滤进程 pid
# eBPF 入门开发实践指南四:在 eBPF 中捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
@@ -52,7 +52,6 @@ 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 函数在内核日志中打印出来。
编译运行上述代码:
```console
@@ -83,4 +82,10 @@ $ sudo cat /sys/kernel/debug/tracing/trace_pipe
```c
```
```
## 总结
本文介绍了如何使用 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

View File

@@ -1,9 +1,17 @@
# eBPF 入门开发实践指南五:使用 uprobe 捕获 bash 的 readline 函数调用
# eBPF 入门开发实践指南五:在 eBPF 中使用 uprobe 捕获 bash 的 readline 函数调用
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
本文是 eBPF 入门开发实践指南的第五篇,主要介绍如何使用 uprobe 捕获 bash 的 readline 函数调用。
## 什么是uprobe
uprobe是一种用户空间探针uprobe探针允许在用户空间程序中动态插桩插桩位置包括函数入口、特定偏移处以及函数返回处。当我们定义uprobe时内核会在附加的指令上创建快速断点指令x86机器上为int3指令当程序执行到该指令时内核将触发事件程序陷入到内核态并以回调函数的方式调用探针函数执行完探针函数再返回到用户态继续执行后序的指令。
uprobe基于文件当一个二进制文件中的一个函数被跟踪时所有使用到这个文件的进程都会被插桩包括那些尚未启动的进程这样就可以在全系统范围内跟踪系统调用。
uprobe适用于在用户态去解析一些内核态探针无法解析的流量例如http2流量报文header被编码内核无法解码https流量加密流量内核无法解密
## 使用 uprobe 捕获 bash 的 readline 函数调用
uprobe 是一种用于捕获用户空间函数调用的 eBPF 的探针,我们可以通过它来捕获用户空间程序调用的系统函数。
@@ -97,9 +105,6 @@ PID 12345 (bash) read: echo "Hello eBPF!"
可以看到,我们成功的捕获了 bash 的 readline 函数调用,并获取了用户在 bash 中输入的命令行。
请注意,在上述代码中,我们使用了 SEC 宏来定义了一个 uprobe 探针,它指定了要捕获的用户空间程序 (bin/bash) 和要捕获的函数 (readline)。
此外,我们还使用了 BPF_KRETPROBE 宏来定义了一个用于处理 readline 函数返回值的回调函数 (printret)。该函数可以获取到 readline 函数的返回值,并将其打印到内核日志中。
通过这样的方式,我们就可以使用 eBPF 来捕获 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

View File

@@ -1,15 +1,98 @@
---
layout: post
title: sigsnoop
date: 2022-10-10 16:18
category: bpftools
author: yunwei37
tags: [bpftools, syscall, kprobe, tracepoint]
summary: Trace signals generated system wide, from syscalls and others.
---
# eBPF 入门开发实践指南六:捕获进程发送信号的系统调用集合,使用 hash map 保存状态
## sigsnoop
```c
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2021~2022 Hengqi Chen */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include "sigsnoop.h"
#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");
## origin
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 宏来定义探针,并指定要捕获的系统调用的名称,以及要执行的探针函数。
origin from:
@@ -60,96 +143,3 @@ Optional arguments:
Built with eunomia-bpf framework.
See https://github.com/eunomia-bpf/eunomia-bpf for more information.
```
## WASM example
Generate WASM skel:
```shell
docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest gen-wasm-skel
```
> The skel is generated and commit, so you don't need to generate it again.
> skel includes:
>
> - eunomia-include: include headers for WASM
> - app.c: the WASM app. all library is header only.
Build WASM module
```shell
docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest build-wasm
```
Run:
```console
$ sudo ./ecli run app.wasm -h
Usage: sigsnoop [-h] [-x] [-k] [-n] [-p PID] [-s SIGNAL]
Trace standard and real-time signals.
-h, --help show this help message and exit
-x, --failed failed signals only
-k, --killed kill only
-p, --pid=<int> target pid
-s, --signal=<int> target signal
$ sudo ./ecli run app.wasm
running and waiting for the ebpf events from perf event...
{"pid":185539,"tpid":185538,"sig":17,"ret":0,"comm":"cat","sig_name":"SIGCHLD"}
{"pid":185540,"tpid":185538,"sig":17,"ret":0,"comm":"grep","sig_name":"SIGCHLD"}
$ sudo ./ecli run app.wasm -p 1641
running and waiting for the ebpf events from perf event...
{"pid":1641,"tpid":2368,"sig":23,"ret":0,"comm":"YDLive","sig_name":"SIGURG"}
{"pid":1641,"tpid":2368,"sig":23,"ret":0,"comm":"YDLive","sig_name":"SIGURG"}
```
## details in bcc
Demonstrations of sigsnoop.
This traces signals generated system wide. For example:
```console
# ./sigsnoop -n
TIME PID COMM SIG TPID RESULT
19:56:14 3204808 a.out SIGSEGV 3204808 0
19:56:14 3204808 a.out SIGPIPE 3204808 0
19:56:14 3204808 a.out SIGCHLD 3204722 0
```
The first line showed that a.out (a test program) deliver a SIGSEGV signal.
The result, 0, means success.
The second and third lines showed that a.out also deliver SIGPIPE/SIGCHLD
signals successively.
USAGE message:
```console
# ./sigsnoop -h
Usage: sigsnoop [OPTION...]
Trace standard and real-time signals.
USAGE: sigsnoop [-h] [-x] [-k] [-n] [-p PID] [-s SIGNAL]
EXAMPLES:
sigsnoop # trace signals system-wide
sigsnoop -k # trace signals issued by kill syscall only
sigsnoop -x # trace failed signals only
sigsnoop -p 1216 # only trace PID 1216
sigsnoop -s 9 # only trace signal 9
-k, --kill Trace signals issued by kill syscall only.
-n, --name Output signal name instead of signal number.
-p, --pid=PID Process ID to trace
-s, --signal=SIGNAL Signal to trace.
-x, --failed Trace failed signals only.
-?, --help Give this help list
--usage Give a short usage message
-V, --version Print program version
```
Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.
Report bugs to https://github.com/iovisor/bcc/tree/master/libbpf-tools.

View File

@@ -1,245 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdbool.h>
#include "eunomia-include/wasm-app.h"
#include "eunomia-include/entry.h"
#include "eunomia-include/argp.h"
#include "sigsnoop.bpf.h"
#include "ewasm-skel.h"
#include "eunomia-include/sigsnoop.skel.h"
#define PERF_BUFFER_PAGES 16
#define PERF_POLL_TIMEOUT_MS 100
#define warn(...) printf(__VA_ARGS__)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
static volatile int exiting = 0;
static int target_pid = 0;
static int target_signal = 0;
static bool failed_only = false;
static bool kill_only = false;
static bool signal_name = false;
static bool verbose = false;
static const char *sig_name[] = {
[0] = "N/A",
[1] = "SIGHUP",
[2] = "SIGINT",
[3] = "SIGQUIT",
[4] = "SIGILL",
[5] = "SIGTRAP",
[6] = "SIGABRT",
[7] = "SIGBUS",
[8] = "SIGFPE",
[9] = "SIGKILL",
[10] = "SIGUSR1",
[11] = "SIGSEGV",
[12] = "SIGUSR2",
[13] = "SIGPIPE",
[14] = "SIGALRM",
[15] = "SIGTERM",
[16] = "SIGSTKFLT",
[17] = "SIGCHLD",
[18] = "SIGCONT",
[19] = "SIGSTOP",
[20] = "SIGTSTP",
[21] = "SIGTTIN",
[22] = "SIGTTOU",
[23] = "SIGURG",
[24] = "SIGXCPU",
[25] = "SIGXFSZ",
[26] = "SIGVTALRM",
[27] = "SIGPROF",
[28] = "SIGWINCH",
[29] = "SIGIO",
[30] = "SIGPWR",
[31] = "SIGSYS",
};
const char *argp_program_version = "sigsnoop 0.1";
const char *argp_program_bug_address =
"https://github.com/iovisor/bcc/tree/master/libbpf-tools";
const char argp_program_doc[] =
"Trace standard and real-time signals.\n"
"\n"
"USAGE: sigsnoop [-h] [-x] [-k] [-n] [-p PID] [-s SIGNAL]\n"
"\n"
"EXAMPLES:\n"
" sigsnoop # trace signals system-wide\n"
" sigsnoop -k # trace signals issued by kill syscall only\n"
" sigsnoop -x # trace failed signals only\n"
" sigsnoop -p 1216 # only trace PID 1216\n"
" sigsnoop -s 9 # only trace signal 9\n";
static const struct argp_option opts[] = {
{ "failed", 'x', NULL, 0, "Trace failed signals only." },
{ "kill", 'k', NULL, 0, "Trace signals issued by kill syscall only." },
{ "pid", 'p', "PID", 0, "Process ID to trace" },
{ "signal", 's', "SIGNAL", 0, "Signal to trace." },
{ "name", 'n', NULL, 0, "Output signal name instead of signal number." },
{ "verbose", 'v', NULL, 0, "Verbose debug output" },
{ NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
long pid, sig;
switch (key) {
case 'p':
errno = 0;
pid = strtol(arg, NULL, 10);
if (errno || pid <= 0) {
warn("Invalid PID: %s\n", arg);
argp_usage(state);
}
target_pid = pid;
break;
case 's':
errno = 0;
sig = strtol(arg, NULL, 10);
if (errno || sig <= 0) {
warn("Invalid SIGNAL: %s\n", arg);
argp_usage(state);
}
target_signal = sig;
break;
case 'n':
signal_name = true;
break;
case 'x':
failed_only = true;
break;
case 'k':
kill_only = true;
break;
case 'v':
verbose = true;
break;
case 'h':
argp_state_help(state, ARGP_HELP_STD_HELP);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static int libbpf_print_fn(const char *format, va_list args)
{
if (!verbose)
return 0;
return printf(format, args);
}
static void alias_parse(char *prog)
{
char *name = prog;
if (!strcmp(name, "killsnoop")) {
kill_only = true;
}
}
static void sig_int(int signo)
{
exiting = 1;
}
static void handle_event(void *ctx, int cpu, void *data, unsigned int data_sz)
{
struct event *e = data;
char ts[32] = "12:47:32";
if (signal_name && e->sig < ARRAY_SIZE(sig_name))
printf("%-8s %-7d %-16s %-9s %-7d %-6d\n",
ts, e->pid, e->comm, sig_name[e->sig], e->tpid, e->ret);
else
printf("%-8s %-7d %-16s %-9d %-7d %-6d\n",
ts, e->pid, e->comm, e->sig, e->tpid, e->ret);
}
static void handle_lost_events(void *ctx, int cpu, unsigned long long lost_cnt)
{
warn("lost %llu events on CPU #%d\n", lost_cnt, cpu);
}
int main(int argc, char **argv)
{
static const struct argp argp = {
.options = opts,
.parser = parse_arg,
.doc = argp_program_doc,
};
struct perf_buffer *pb = NULL;
struct sigsnoop_bpf *obj;
int err;
alias_parse(argv[0]);
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
if (err)
return err;
obj = sigsnoop_bpf__open();
if (!obj) {
warn("failed to open BPF object\n");
return 1;
}
obj->rodata->filtered_pid = target_pid;
obj->rodata->target_signal = target_signal;
obj->rodata->failed_only = failed_only;
if (kill_only) {
bpf_program__set_autoload(obj->progs.sig_trace, false);
} else {
bpf_program__set_autoload(obj->progs.kill_entry, false);
bpf_program__set_autoload(obj->progs.kill_exit, false);
bpf_program__set_autoload(obj->progs.tkill_entry, false);
bpf_program__set_autoload(obj->progs.tkill_exit, false);
bpf_program__set_autoload(obj->progs.tgkill_entry, false);
bpf_program__set_autoload(obj->progs.tgkill_exit, false);
}
err = sigsnoop_bpf__load(obj);
if (err) {
warn("failed to load BPF object: %d\n", err);
goto cleanup;
}
err = sigsnoop_bpf__attach(obj);
if (err) {
warn("failed to attach BPF programs: %d\n", err);
goto cleanup;
}
pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
handle_event, handle_lost_events, NULL, NULL);
if (!pb) {
warn("failed to open perf buffer: %d\n", err);
goto cleanup;
}
printf("%-8s %-7s %-16s %-9s %-7s %-6s\n",
"TIME", "PID", "COMM", "SIG", "TPID", "RESULT");
while (!exiting) {
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
if (err < 0 && err != -EINTR) {
warn("error polling perf buffer: %s\n", strerror(-err));
goto cleanup;
}
/* reset err to return 0 if exiting */
err = 0;
}
cleanup:
perf_buffer__free(pb);
sigsnoop_bpf__destroy(obj);
return err != 0;
}

View File

@@ -6,10 +6,6 @@
#define MAX_ENTRIES 10240
const volatile pid_t filtered_pid = 0;
const volatile int target_signal = 0;
const volatile bool failed_only = false;
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_ENTRIES);
@@ -17,11 +13,6 @@ struct {
__type(value, struct event);
} values SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} events SEC(".maps");
static int probe_entry(pid_t tpid, int sig)
{
@@ -29,15 +20,8 @@ static int probe_entry(pid_t tpid, int sig)
__u64 pid_tgid;
__u32 pid, tid;
if (target_signal && sig != target_signal)
return 0;
pid_tgid = bpf_get_current_pid_tgid();
pid = pid_tgid >> 32;
tid = (__u32)pid_tgid;
if (filtered_pid && pid != filtered_pid)
return 0;
event.pid = pid;
event.tpid = tpid;
event.sig = sig;
@@ -56,11 +40,9 @@ static int probe_exit(void *ctx, int ret)
if (!eventp)
return 0;
if (failed_only && ret >= 0)
goto cleanup;
eventp->ret = ret;
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, eventp, sizeof(*eventp));
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);
@@ -112,34 +94,4 @@ 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;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";

View File

@@ -1,18 +1,82 @@
---
layout: post
title: execsnoop
date: 2022-11-17 19:57
category: bpftools
author: yunwei37
tags: [bpftools, syscall]
summary: execsnoop traces the exec() syscall system-wide, and prints various details.
---
## eBPF 入门实践教程七:捕获进程执行/退出时间,通过 perf event array 向用户态打印输出
## origin
## execsnoop
```c
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include "execsnoop.bpf.h"
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
} events SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_execve")
int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter* ctx)
{
u64 id;
pid_t pid, tgid;
unsigned int ret;
struct event event;
struct task_struct *task;
const char **args = (const char **)(ctx->args[1]);
const char *argp;
uid_t uid = (u32)bpf_get_current_uid_gid();
int i;
id = bpf_get_current_pid_tgid();
pid = (pid_t)id;
tgid = id >> 32;
event.pid = tgid;
event.uid = uid;
task = (struct task_struct*)bpf_get_current_task();
bpf_probe_read_str(&event.comm, sizeof(event.comm), task->comm);
event.is_exit = false;
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
return 0;
}
SEC("tracepoint/syscalls/sys_exit_execve")
int tracepoint__syscalls__sys_exit_execve(struct trace_event_raw_sys_exit* ctx)
{
u64 id;
pid_t pid;
int ret;
struct event event;
u32 uid = (u32)bpf_get_current_uid_gid();
id = bpf_get_current_pid_tgid();
pid = (pid_t)id;
ret = ctx->ret;
event.retval = ret;
event.pid = pid;
event.uid = uid;
event.is_exit = true;
bpf_get_current_comm(&event.comm, sizeof(event.comm));
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
return 0;
}
char LICENSE[] SEC("license") = "GPL";
```
这段代码定义了两个 eBPF 程序,一个用于捕获进程执行 execve 系统调用的入口,另一个用于捕获进程执行 execve 系统调用的出口。
在入口程序中,我们首先获取了当前进程的进程 ID 和用户 ID然后通过 bpf_get_current_task 函数获取了当前进程的 task_struct 结构体,并通过 bpf_probe_read_str 函数读取了进程名称。最后,我们通过 bpf_perf_event_output 函数将进程执行事件输出到 perf buffer。
在出口程序中,我们首先获取了进程的进程 ID 和用户 ID然后通过 bpf_get_current_comm 函数获取了进程的名称,最后通过 bpf_perf_event_output 函数将进程执行事件输出到 perf buffer。
使用这段代码,我们就可以捕获 Linux 内核中进程执行的事件。我们可以通过工具(例如 eunomia-bpf来查看这些事件并分析进程的执行情况。
origin from:
https://github.com/iovisor/bcc/blob/master/libbpf-tools/execsnoop.bpf.c
## Compile and Run

View File

@@ -4,107 +4,35 @@
#include <bpf/bpf_core_read.h>
#include "execsnoop.bpf.h"
const volatile bool filter_cg = false;
const volatile bool ignore_failed = true;
const volatile uid_t targ_uid = INVALID_UID;
const volatile int max_args = DEFAULT_MAXARGS;
static const struct event empty_event = {};
struct {
__uint(type, BPF_MAP_TYPE_CGROUP_ARRAY);
__type(key, u32);
__type(value, u32);
__uint(max_entries, 1);
} cgroup_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, pid_t);
__type(value, struct event);
} execs SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
} events SEC(".maps");
static __always_inline bool valid_uid(uid_t uid) {
return uid != INVALID_UID;
}
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 event event;
struct task_struct *task;
const char **args = (const char **)(ctx->args[1]);
const char *argp;
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
return 0;
uid_t uid = (u32)bpf_get_current_uid_gid();
int i;
if (valid_uid(targ_uid) && targ_uid != uid)
return 0;
id = bpf_get_current_pid_tgid();
pid = (pid_t)id;
tgid = id >> 32;
if (bpf_map_update_elem(&execs, &pid, &empty_event, BPF_NOEXIST))
return 0;
event = bpf_map_lookup_elem(&execs, &pid);
if (!event)
return 0;
event->pid = tgid;
event->uid = uid;
event.pid = tgid;
event.uid = uid;
task = (struct task_struct*)bpf_get_current_task();
event->ppid = (pid_t)BPF_CORE_READ(task, real_parent, tgid);
event->args_count = 0;
event->args_size = 0;
ret = bpf_probe_read_user_str(event->args, ARGSIZE, (const char*)ctx->args[0]);
if (ret <= ARGSIZE) {
event->args_size += ret;
} else {
/* write an empty string */
event->args[0] = '\0';
event->args_size++;
}
event->args_count++;
#pragma unroll
for (i = 1; i < TOTAL_MAX_ARGS && i < max_args; i++) {
bpf_probe_read_user(&argp, sizeof(argp), &args[i]);
if (!argp)
return 0;
if (event->args_size > LAST_ARG)
return 0;
ret = bpf_probe_read_user_str(&event->args[event->args_size], ARGSIZE, argp);
if (ret > ARGSIZE)
return 0;
event->args_count++;
event->args_size += ret;
}
/* try to read one more argument to check if there is one */
bpf_probe_read_user(&argp, sizeof(argp), &args[max_args]);
if (!argp)
return 0;
/* pointer to max_args+1 isn't null, asume we have more arguments */
event->args_count++;
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;
}
@@ -114,31 +42,20 @@ int tracepoint__syscalls__sys_exit_execve(struct trace_event_raw_sys_exit* ctx)
u64 id;
pid_t pid;
int ret;
struct event *event;
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
return 0;
struct event event;
u32 uid = (u32)bpf_get_current_uid_gid();
if (valid_uid(targ_uid) && targ_uid != uid)
return 0;
id = bpf_get_current_pid_tgid();
pid = (pid_t)id;
event = bpf_map_lookup_elem(&execs, &pid);
if (!event)
return 0;
ret = ctx->ret;
if (ignore_failed && ret < 0)
goto cleanup;
event->retval = ret;
bpf_get_current_comm(&event->comm, sizeof(event->comm));
size_t len =((size_t)(&((struct event*)0)->args) + event->args_size);
if (len <= sizeof(*event))
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event, len);
cleanup:
bpf_map_delete_elem(&execs, &pid);
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;
}

View File

@@ -15,10 +15,8 @@ struct event {
int ppid;
int uid;
int retval;
int args_count;
unsigned int args_size;
bool is_exit;
char comm[TASK_COMM_LEN];
char args[FULL_MAX_ARGS_ARR];
};
#endif /* __EXECSNOOP_H */

View File

@@ -1,6 +1,4 @@
| layout | title | date | category | author | tags | summary |
| ------ | ---------- | ---------------- | -------- | -------- | --------------- | ----------------------------------------------- |
| post | runqslower | 2022-11-11-20:50 | bpftools | yunwei37 | bpftool syscall | runqslower Trace long process scheduling delays |
## eBPF 入门实践教程:
## origin

View File

@@ -1,13 +1,4 @@
---
layout: post
title: runqlat
date: 2022-10-10 16:18
category: bpftools
author: yunwei37
tags: [bpftools, syscall, tracepoint]
summary: Summarize run queue (scheduler) latency as a histogram.
---
## eBPF 入门实践教程:
## origin

View File

@@ -14,8 +14,8 @@
- [lesson 1-helloworld](1-helloworld/README.md) 使用 eBPF 开发最简单的「Hello World」程序介绍 eBPF 的基本框架和开发流程
- [lesson 2-kprobe-unlink](2-kprobe-unlink/README.md) 在 eBPF 中使用 kprobe 捕获 unlink 系统调用
- [lesson 3-fentry-unlink](3-fentry-unlink/README.md) 在 eBPF 中使用 fentry 捕获 unlink 系统调用
- [lesson 4-opensnoop](4-opensnoop/README.md) 捕获进程打开文件的系统调用集合,使用全局变量在 eBPF 中过滤进程 pid
- [lesson 5-uprobe-bashreadline](5-uprobe-bashreadline/README.md) 使用 uprobe 捕获 bash 的 readline 函数调用
- [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 向用户态打印输出
@@ -49,3 +49,30 @@
> 在API和代码约定方面libbpf坚持"最少意外"的哲学即大部分内容都需要明确地阐述不会隐含任何头文件也不会重写代码。仅使用简单的C代码和适当的辅助宏即可消除大部分单调的环节。 此外用户编写的是需要执行的内容BPF应用程序的结构是一对一的最终由内核验证并执行。
>
> 参考:[BCC 到libbpf 的转换指南【译】 - 深入浅出eBPF: https://www.ebpf.top/post/bcc-to-libbpf-guid/](https://www.ebpf.top/post/bcc-to-libbpf-guid/)
## eunomia-bpf
[eunomia-bpf](https://github.com/eunomia-bpf/eunomia-bpf) 是一个开源的 eBPF 动态加载运行时和开发工具链,是为了简化 eBPF 程序的开发、构建、分发、运行而设计的,基于 libbpf 的 CO-RE 轻量级开发框架。
使用 eunomia-bpf ,可以:
- 在编写 eBPF 程序或工具时只编写 libbpf 内核态代码,自动获取内核态导出信息;
- 使用 WASM 进行用户态交互程序的开发,在 WASM 虚拟机内部控制整个 eBPF 程序的加载和执行,以及处理相关数据;
- eunomia-bpf 可以将预编译的 eBPF 程序打包为通用的 JSON 或 WASM 模块,跨架构和内核版本进行分发,无需重新编译即可动态加载运行。
eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的 BCC、原生 libbpf 等框架,大幅简化了 eBPF 程序的开发流程,在大多数时候只需编写内核态代码,即可轻松构建、打包、发布完整的 eBPF 应用,同时内核态 eBPF 代码保证和主流的 libbpf, libbpfgo, libbpf-rs 等开发框架的 100% 兼容性。需要编写用户态代码的时候,也可以借助 Webassembly 实现通过多种语言进行用户态开发。和 bpftrace 等脚本工具相比, eunomia-bpf 保留了类似的便捷性, 同时不仅局限于 trace 方面, 可以用于更多的场景, 如网络、安全等等。
> - eunomia-bpf 项目 Github 地址: <https://github.com/eunomia-bpf/eunomia-bpf>
> - gitee 镜像: <https://gitee.com/anolis/eunomia>
## 让 chatGPT 来帮助我们
> 本教程大部分内容由 chatGPT 生成,我们尝试教会 chatGPT 编写 eBPF 程序:
>
> 1. 告诉它基本的 eBPF 编程相关的常识
> 2. 一些案例hello worldeBPF 程序的基本结构,如何使用 eBPF 程序等等,并且让它开始编写教程
> 3. 手动调整并纠正代码和文档中的错误
> 4. 把修改后的代码再喂给 chatGPT让它继续学习
> 5. 尝试让 chatGPT 自动生成 eBPF 程序!
>