mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 10:14:44 +08:00
teach chatgpt for perf event
This commit is contained in:
@@ -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-bpf:https://github.com/eunomia-bpf/eunomia-bpf
|
||||
@@ -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 程序。
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
## eBPF 入门实践教程:
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
BIN
2-kprobe-unlink/ecli
Executable file
Binary file not shown.
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
245
6-sigsnoop/app.c
245
6-sigsnoop/app.c
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
31
README.md
31
README.md
@@ -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 world,eBPF 程序的基本结构,如何使用 eBPF 程序等等,并且让它开始编写教程
|
||||
> 3. 手动调整并纠正代码和文档中的错误
|
||||
> 4. 把修改后的代码再喂给 chatGPT,让它继续学习
|
||||
> 5. 尝试让 chatGPT 自动生成 eBPF 程序!
|
||||
>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user