From 81d749a9cc010960374204b6d5e1ca1e82e67c8e Mon Sep 17 00:00:00 2001 From: yunwei37 <1067852565@qq.com> Date: Fri, 2 Dec 2022 19:18:03 +0800 Subject: [PATCH] init with documents from eunomia-bpf --- 0-introduce/introduce.md | 161 + 1-helloworld/.gitignore | 6 + 1-helloworld/README.md | 57 + 1-helloworld/minimal.bpf.c | 21 + 10-lsm-connect/.gitignore | 6 + 10-lsm-connect/README.md | 34 + 10-lsm-connect/lsm-connect.bpf.c | 41 + 11-tc/.gitignore | 10 + 11-tc/README.md | 56 + 11-tc/tc.bpf.c | 36 + 12-bindsnoop/.gitignore | 3 + 12-bindsnoop/README.md | 106 + 12-bindsnoop/bindsnoop.bpf.c | 151 + 12-bindsnoop/bindsnoop.bpf.h | 31 + 12-bindsnoop/bindsnoop.md | 95 + 13-tcpconnlat/.gitignore | 2 + 13-tcpconnlat/README.md | 137 + 13-tcpconnlat/tcpconnlat.bpf.c | 113 + 13-tcpconnlat/tcpconnlat.bpf.h | 26 + 13-tcpconnlat/tcpconnlat.md | 186 ++ 13-tcpconnlat/tcpconnlat1.png | Bin 0 -> 135015 bytes 13-tcpconnlat/tcpconnlat_p.png | Bin 0 -> 41522 bytes 14-tcpstates/.gitignore | 5 + 14-tcpstates/README.md | 56 + 14-tcpstates/tcpstates.bpf.c | 109 + 14-tcpstates/tcpstates.bpf.h | 24 + 15-tcprtt/tcprtt.md | 116 + 16-profile/profile.md | 104 + 17-memleak/memleak.md | 80 + 18-biopattern/biolatency.md | 121 + 18-biopattern/biopattern.md | 48 + 18-biopattern/biostacks.md | 100 + 18-biopattern/bitesize.md | 63 + 19-syscount/syscount.md | 81 + 2-fentry-unlink/.gitignore | 6 + 2-fentry-unlink/README.md | 76 + 2-fentry-unlink/fentry-link.bpf.c | 27 + 21-llcstat/llcstat.md | 75 + 3-kprobe-unlink/.gitignore | 6 + 3-kprobe-unlink/README.md | 55 + 3-kprobe-unlink/kprobe-link.bpf.c | 30 + 4-opensnoop/.gitignore | 7 + 4-opensnoop/1_opensnoop.md | 263 ++ 4-opensnoop/README.md | 281 ++ 4-opensnoop/config.yaml | 12 + 4-opensnoop/opensnoop.bpf.c | 140 + 4-opensnoop/opensnoop.h | 21 + 5-uprobe-bashreadline/.gitignore | 7 + 5-uprobe-bashreadline/README.md | 79 + 5-uprobe-bashreadline/bashreadline.bpf.c | 48 + 5-uprobe-bashreadline/bashreadline.h | 13 + 6-sigsnoop/.gitignore | 10 + 6-sigsnoop/README.md | 155 + 6-sigsnoop/app.c | 245 ++ 6-sigsnoop/eunomia-include/argp-namefrob.h | 96 + 6-sigsnoop/eunomia-include/argp.h | 1854 +++++++++++ .../eunomia-include/argparse/argparse.c | 403 +++ .../eunomia-include/argparse/argparse.h | 133 + 6-sigsnoop/eunomia-include/cJSON/cJSON.c | 2917 +++++++++++++++++ 6-sigsnoop/eunomia-include/cJSON/cJSON.h | 358 ++ 6-sigsnoop/eunomia-include/entry.h | 40 + 6-sigsnoop/eunomia-include/errno-base.h | 40 + 6-sigsnoop/eunomia-include/helpers.h | 54 + 6-sigsnoop/eunomia-include/native-ewasm.h | 50 + 6-sigsnoop/eunomia-include/sigsnoop.skel.h | 195 ++ 6-sigsnoop/eunomia-include/wasm-app.h | 8 + 6-sigsnoop/sigsnoop.bpf.c | 145 + 6-sigsnoop/sigsnoop.h | 16 + 6-sigsnoop/sigsnoop.md | 92 + 7-execsnoop/.gitignore | 3 + 7-execsnoop/README.md | 148 + 7-execsnoop/execsnoop.bpf.c | 146 + 7-execsnoop/execsnoop.bpf.h | 26 + 8-runqslower/.gitignore | 4 + 8-runqslower/README.md | 147 + 8-runqslower/core_fixes.h | 112 + 8-runqslower/runqslower.bpf.c | 117 + 8-runqslower/runqslower.bpf.h | 15 + 9-runqlat/.gitignore | 6 + 9-runqlat/README.md | 675 ++++ 9-runqlat/bits.bpf.h | 31 + 9-runqlat/core_fixes.bpf.h | 112 + 9-runqlat/maps.bpf.h | 26 + 9-runqlat/runqlat.bpf.c | 152 + 9-runqlat/runqlat.h | 14 + 85 files changed, 11876 insertions(+) create mode 100644 0-introduce/introduce.md create mode 100644 1-helloworld/.gitignore create mode 100644 1-helloworld/README.md create mode 100644 1-helloworld/minimal.bpf.c create mode 100644 10-lsm-connect/.gitignore create mode 100644 10-lsm-connect/README.md create mode 100644 10-lsm-connect/lsm-connect.bpf.c create mode 100755 11-tc/.gitignore create mode 100644 11-tc/README.md create mode 100644 11-tc/tc.bpf.c create mode 100644 12-bindsnoop/.gitignore create mode 100644 12-bindsnoop/README.md create mode 100644 12-bindsnoop/bindsnoop.bpf.c create mode 100644 12-bindsnoop/bindsnoop.bpf.h create mode 100644 12-bindsnoop/bindsnoop.md create mode 100644 13-tcpconnlat/.gitignore create mode 100644 13-tcpconnlat/README.md create mode 100644 13-tcpconnlat/tcpconnlat.bpf.c create mode 100644 13-tcpconnlat/tcpconnlat.bpf.h create mode 100644 13-tcpconnlat/tcpconnlat.md create mode 100644 13-tcpconnlat/tcpconnlat1.png create mode 100644 13-tcpconnlat/tcpconnlat_p.png create mode 100644 14-tcpstates/.gitignore create mode 100644 14-tcpstates/README.md create mode 100644 14-tcpstates/tcpstates.bpf.c create mode 100644 14-tcpstates/tcpstates.bpf.h create mode 100644 15-tcprtt/tcprtt.md create mode 100644 16-profile/profile.md create mode 100644 17-memleak/memleak.md create mode 100644 18-biopattern/biolatency.md create mode 100644 18-biopattern/biopattern.md create mode 100644 18-biopattern/biostacks.md create mode 100644 18-biopattern/bitesize.md create mode 100644 19-syscount/syscount.md create mode 100644 2-fentry-unlink/.gitignore create mode 100644 2-fentry-unlink/README.md create mode 100644 2-fentry-unlink/fentry-link.bpf.c create mode 100644 21-llcstat/llcstat.md create mode 100644 3-kprobe-unlink/.gitignore create mode 100644 3-kprobe-unlink/README.md create mode 100644 3-kprobe-unlink/kprobe-link.bpf.c create mode 100644 4-opensnoop/.gitignore create mode 100644 4-opensnoop/1_opensnoop.md create mode 100644 4-opensnoop/README.md create mode 100644 4-opensnoop/config.yaml create mode 100644 4-opensnoop/opensnoop.bpf.c create mode 100644 4-opensnoop/opensnoop.h create mode 100644 5-uprobe-bashreadline/.gitignore create mode 100644 5-uprobe-bashreadline/README.md create mode 100644 5-uprobe-bashreadline/bashreadline.bpf.c create mode 100644 5-uprobe-bashreadline/bashreadline.h create mode 100755 6-sigsnoop/.gitignore create mode 100755 6-sigsnoop/README.md create mode 100755 6-sigsnoop/app.c create mode 100644 6-sigsnoop/eunomia-include/argp-namefrob.h create mode 100644 6-sigsnoop/eunomia-include/argp.h create mode 100644 6-sigsnoop/eunomia-include/argparse/argparse.c create mode 100644 6-sigsnoop/eunomia-include/argparse/argparse.h create mode 100644 6-sigsnoop/eunomia-include/cJSON/cJSON.c create mode 100644 6-sigsnoop/eunomia-include/cJSON/cJSON.h create mode 100644 6-sigsnoop/eunomia-include/entry.h create mode 100644 6-sigsnoop/eunomia-include/errno-base.h create mode 100644 6-sigsnoop/eunomia-include/helpers.h create mode 100644 6-sigsnoop/eunomia-include/native-ewasm.h create mode 100644 6-sigsnoop/eunomia-include/sigsnoop.skel.h create mode 100644 6-sigsnoop/eunomia-include/wasm-app.h create mode 100755 6-sigsnoop/sigsnoop.bpf.c create mode 100755 6-sigsnoop/sigsnoop.h create mode 100644 6-sigsnoop/sigsnoop.md create mode 100644 7-execsnoop/.gitignore create mode 100644 7-execsnoop/README.md create mode 100644 7-execsnoop/execsnoop.bpf.c create mode 100644 7-execsnoop/execsnoop.bpf.h create mode 100644 8-runqslower/.gitignore create mode 100644 8-runqslower/README.md create mode 100644 8-runqslower/core_fixes.h create mode 100644 8-runqslower/runqslower.bpf.c create mode 100644 8-runqslower/runqslower.bpf.h create mode 100644 9-runqlat/.gitignore create mode 100755 9-runqlat/README.md create mode 100644 9-runqlat/bits.bpf.h create mode 100644 9-runqlat/core_fixes.bpf.h create mode 100644 9-runqlat/maps.bpf.h create mode 100644 9-runqlat/runqlat.bpf.c create mode 100644 9-runqlat/runqlat.h diff --git a/0-introduce/introduce.md b/0-introduce/introduce.md new file mode 100644 index 0000000..9dbe637 --- /dev/null +++ b/0-introduce/introduce.md @@ -0,0 +1,161 @@ +# eBPF 入门开发实践指南一:介绍与快速上手 + + + +- [1. 什么是eBPF](#1-什么是ebpf) + - [1.1. 起源](#11-起源) + - [1.2. 执行逻辑](#12-执行逻辑) + - [1.3. 架构](#13-架构) + - [1.3.1. 寄存器设计](#131-寄存器设计) + - [1.3.2. 指令编码格式](#132-指令编码格式) + - [1.4. 本节参考文章](#14-本节参考文章) +- [2. 如何使用eBPF编程](#2-如何使用ebpf编程) + - [2.1. BCC](#21-bcc) + - [2.2. libbpf-bootstrap](#22-libbpf-bootstrap) + - [2.3 eunomia-bpf](#23-eunomia-bpf) + + + +## 1. 什么是eBPF + +Linux内核一直是实现监控/可观测性、网络和安全功能的理想地方, +但是直接在内核中进行监控并不是一个容易的事情。在传统的Linux软件开发中, +实现这些功能往往都离不开修改内核源码或加载内核模块。修改内核源码是一件非常危险的行为, +稍有不慎可能便会导致系统崩溃,并且每次检验修改的代码都需要重新编译内核,耗时耗力。 + +加载内核模块虽然来说更为灵活,不需要重新编译源码,但是也可能导致内核崩溃,且随着内核版本的变化 +模块也需要进行相应的修改,否则将无法使用。 + +在这一背景下,eBPF技术应运而生。它是一项革命性技术,能在内核中运行沙箱程序(sandbox programs),而无需修改内核源码或者加载内核模块。用户可以使用其提供的各种接口,实现在内核中追踪、监测系统的作用。 + +### 1.1. 起源 + +eBPF的雏形是BPF(Berkeley Packet Filter, 伯克利包过滤器)。BPF于 +1992年被Steven McCanne和Van Jacobson在其[论文](https://www.tcpdump.org/papers/bpf-usenix93.pdf) +提出。二人提出BPF的初衷是是提供一种新的数据包过滤方法,该方法的模型如下图所示。 +![](../imgs/original_bpf.png) + +相较于其他过滤方法,BPF有两大创新点,首先是它使用了一个新的虚拟机,可以有效地工作在基于寄存器结构的CPU之上。其次是其不会全盘复制数据包的所有信息,只会复制相关数据,可以有效地提高效率。这两大创新使得BPF在实际应用中得到了巨大的成功,在被移植到Linux系统后,其被上层的`libcap` +和`tcpdump`等应用使用,是一个性能卓越的工具。 + +传统的BPF是32位架构,其指令集编码格式为: + +- 16 bit: 操作指令 +- 8 bit: 下一条指令跳向正确目标的偏移量 +- 8 bit: 下一条指令跳往错误目标的偏移量 + +经过十余年的沉积后,2013年,Alexei Starovoitov对BPF进行了彻底地改造,改造后的BPF被命名为eBPF(extended BPF),于Linux Kernel 3.15中引入Linux内核源码。 +eBPF相较于BPF有了革命性的变化。首先在于eBPF支持了更多领域的应用,它不仅支持网络包的过滤,还可以通过 +`kprobe`,`tracepoint`,`lsm`等Linux现有的工具对响应事件进行追踪。另一方面,其在使用上也更为 +灵活,更为方便。同时,其JIT编译器也得到了升级,解释器也被替换,这直接使得其具有达到平台原生的 +执行性能的能力。 + +### 1.2. 执行逻辑 + +eBPF在执行逻辑上和BPF有相似之处,eBPF也可以认为是一个基于寄存器的,使用自定义的64位RISC指令集的 +微型"虚拟机"。它可以在Linux内核中,以一种安全可控的方式运行本机编译的eBPF程序并且访问内核函数和内存的子集。 + +在写好程序后,我们将代码使用llvm编译得到使用BPF指令集的ELF文件,解析出需要注入的部分后调用函数将其 +注入内核。用户态的程序和注入内核态中的字节码公用一个位于内核的eBPF Map进行通信,实现数据的传递。同时, +为了防止我们写入的程序本身不会对内核产生较大影响,编译好的字节码在注入内核之前会被eBPF校验器严格地检查。 + +eBPF程序是由事件驱动的,我们在程序中需要提前确定程序的执行点。编译好的程序被注入内核后,如果提前确定的执行点 +被调用,那么注入的程序就会被触发,按照既定方式处理。 + +### 1.3. 架构 + +#### 1.3.1. 寄存器设计 + +eBPF有11个寄存器,分别是R0~R10,每个寄存器均是64位大小,有相应的32位子寄存器,其指令集是固定的64位宽。 + +#### 1.3.2. 指令编码格式 + +eBPF指令编码格式为: + +- 8 bit: 存放真实指令码 +- 4 bit: 存放指令用到的目标寄存器号 +- 4 bit: 存放指令用到的源寄存器号 +- 16 bit: 存放偏移量,具体作用取决于指令类型 +- 32 bit: 存放立即数 + +### 1.4. 本节参考文章 + +[A thorough introduction to eBPF](https://lwn.net/Articles/740157/) +[bpf简介](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/) +[bpf架构知识](https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/) + +## 2. 如何使用eBPF编程 + +原始的eBPF程序编写是非常繁琐和困难的。为了改变这一现状, +llvm于2015年推出了可以将由高级语言编写的代码编译为eBPF字节码的功能,同时,其将`bpf()` +等原始的系统调用进行了初步地封装,给出了`libbpf`库。这些库会包含将字节码加载到内核中 +的函数以及一些其他的关键函数。在Linux的源码包的`samples/bpf/`目录下,有大量Linux +提供的基于`libbpf`的eBPF样例代码。 + +一个典型的基于`libbpf`的eBPF程序具有`*_kern.c`和`*_user.c`两个文件, +`*_kern.c`中书写在内核中的挂载点以及处理函数,`*_user.c`中书写用户态代码, +完成内核态代码注入以及与用户交互的各种任务。 更为详细的教程可以参考[该视频](https://www.bilibili.com/video/BV1f54y1h74r?spm_id_from=333.999.0.0) +然而由于该方法仍然较难理解且入门存在一定的难度,因此现阶段的eBPF程序开发大多基于一些工具,比如: + +- BCC +- BPFtrace +- libbpf-bootstrap + +以及还有比较新的工具,例如 `eunomia-bpf` 将 CO-RE eBPF 功能作为服务运行,包含一个工具链和一个运行时,主要功能包括: + +- 不需要再为每个 eBPF 工具编写用户态代码框架:大多数情况下只需要编写内核态应用程序,即可实现正确加载运行 eBPF 程序;同时所需编写的内核态代码和 libbpf 完全兼容,可轻松实现迁移; +- 提供基于 async Rust 的 Prometheus 或 OpenTelemetry 自定义可观测性数据收集器,通常仅占用不到1%的资源开销,编写内核态代码和 yaml 配置文件即可实现 eBPF 信息可视化,编译后可在其他机器上通过 API 请求直接部署; + +### 2.1. BCC + +BCC全称为BPF Compiler Collection,该项目是一个python库, +包含了完整的编写、编译、和加载BPF程序的工具链,以及用于调试和诊断性能问题的工具。 + +自2015年发布以来,BCC经过上百位贡献者地不断完善后,目前已经包含了大量随时可用的跟踪工具。[其官方项目库](https://github.com/iovisor/bcc/blob/master/docs/tutorial.md) +提供了一个方便上手的教程,用户可以快速地根据教程完成BCC入门工作。 + +用户可以在BCC上使用Python、Lua等高级语言进行编程。 +相较于使用C语言直接编程,这些高级语言具有极大的便捷性,用户只需要使用C来设计内核中的 +BPF程序,其余包括编译、解析、加载等工作在内,均可由BCC完成。 + +然而使用BCC存在一个缺点便是在于其兼容性并不好。基于BCC的 +eBPF程序每次执行时候都需要进行编译,编译则需要用户配置相关的头文件和对应实现。在实际应用中, +相信大家也会有体会,编译依赖问题是一个很棘手的问题。也正是因此,在本项目的开发中我们放弃了BCC, +选择了可以做到一次编译-多次运行的libbpf-bootstrap工具。 + +### 2.2. libbpf-bootstrap + +`libbpf-bootstrap`是一个基于`libbpf`库的BPF开发脚手架,从其 +[github](https://github.com/libbpf/libbpf-bootstrap) 上可以得到其源码。 + +`libbpf-bootstrap`综合了BPF社区过去多年的实践,为开发者提了一个现代化的、便捷的工作流,实 +现了一次编译,重复使用的目的。 + +基于`libbpf-bootstrap`的BPF程序对于源文件有一定的命名规则, +用于生成内核态字节码的bpf文件以`.bpf.c`结尾,用户态加载字节码的文件以`.c`结尾,且这两个文件的 +前缀必须相同。 + +基于`libbpf-bootstrap`的BPF程序在编译时会先将`*.bpf.c`文件编译为 +对应的`.o`文件,然后根据此文件生成`skeleton`文件,即`*.skel.h`,这个文件会包含内核态中定义的一些 +数据结构,以及用于装载内核态代码的关键函数。在用户态代码`include`此文件之后调用对应的装载函数即可将 +字节码装载到内核中。同样的,`libbpf-bootstrap`也有非常完备的入门教程,用户可以在[该处](https://nakryiko.com/posts/libbpf-bootstrap/) +得到详细的入门操作介绍。 + +### 2.3 eunomia-bpf + +开发、构建和分发 eBPF 一直以来都是一个高门槛的工作,使用 BCC、bpftrace 等工具开发效率高、可移植性好,但是分发部署时需要安装 LLVM、Clang等编译环境,每次运行的时候执行本地或远程编译过程,资源消耗较大;使用原生的 CO-RE libbpf时又需要编写不少用户态加载代码来帮助 eBPF 程序正确加载和从内核中获取上报的信息,同时对于 eBPF 程序的分发、管理也没有很好地解决方案. + +[eunomia-bpf](https://github.com/eunomia-bpf/eunomia-bpf) 是一个开源的 eBPF 动态加载运行时和开发工具链,是为了简化 eBPF 程序的开发、构建、分发、运行而设计的,基于 libbpf 的 CO-RE 轻量级开发框架。 + +使用 eunomia-bpf ,可以: + +- 在编写 eBPF 程序或工具时只编写内核态代码,自动获取内核态导出信息; +- 使用 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 地址: +> - gitee 镜像: + +## 参考资料 \ No newline at end of file diff --git a/1-helloworld/.gitignore b/1-helloworld/.gitignore new file mode 100644 index 0000000..7d5aebf --- /dev/null +++ b/1-helloworld/.gitignore @@ -0,0 +1,6 @@ +.vscode +package.json +*.o +*.skel.json +*.skel.yaml +package.yaml diff --git a/1-helloworld/README.md b/1-helloworld/README.md new file mode 100644 index 0000000..f8a947e --- /dev/null +++ b/1-helloworld/README.md @@ -0,0 +1,57 @@ +--- +layout: post +title: minimal +date: 2022-10-10 16:18 +category: bpftools +author: yunwei37 +tags: [bpftools, tracepoint, example, syscall] +summary: a minimal example of a BPF application installs a tracepoint handler which is triggered by write syscall +--- + + +`minimal` is just that – a minimal practical BPF application example. It +doesn't use or require BPF CO-RE, so should run on quite old kernels. It +installs a tracepoint handler which is triggered once every second. It uses +`bpf_printk()` BPF helper to communicate with the world. + + +```console +$ sudo ecli examples/bpftools/minimal/package.json +Runing eBPF program... +``` + +To see it's output, +read `/sys/kernel/debug/tracing/trace_pipe` file as a root: + +```shell +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: BPF triggered from PID 3840345. + <...>-3840345 [010] d... 3220702.101265: bpf_trace_printk: BPF triggered from PID 3840345. +``` + +`minimal` is great as a bare-bones experimental playground to quickly try out +new ideas or BPF features. + +## Compile and Run + + + +Compile: + +```console +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +or compile with `ecc`: + +```console +$ ecc minimal.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/1-helloworld/minimal.bpf.c b/1-helloworld/minimal.bpf.c new file mode 100644 index 0000000..0c65717 --- /dev/null +++ b/1-helloworld/minimal.bpf.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#define BPF_NO_GLOBAL_DATA +#include +#include +#include + +typedef unsigned int u32; +typedef int pid_t; +const pid_t pid_filter = 0; + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +SEC("tp/syscalls/sys_enter_write") +int handle_tp(void *ctx) +{ + pid_t pid = bpf_get_current_pid_tgid() >> 32; + if (pid_filter && pid != pid_filter) + return 0; + bpf_printk("BPF triggered from PID %d.\n", pid); + return 0; +} diff --git a/10-lsm-connect/.gitignore b/10-lsm-connect/.gitignore new file mode 100644 index 0000000..7d5aebf --- /dev/null +++ b/10-lsm-connect/.gitignore @@ -0,0 +1,6 @@ +.vscode +package.json +*.o +*.skel.json +*.skel.yaml +package.yaml diff --git a/10-lsm-connect/README.md b/10-lsm-connect/README.md new file mode 100644 index 0000000..f40a75c --- /dev/null +++ b/10-lsm-connect/README.md @@ -0,0 +1,34 @@ +--- +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 +--- + + +## run + +```console +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +or compile with `ecc`: + +```console +$ ecc lsm-connect.bpf.c +Compiling bpf object... +Packing ebpf object and config into package.json... +``` + +Run: + +```console +sudo ecli examples/bpftools/lsm-connect/package.json +``` + +## reference + + \ No newline at end of file diff --git a/10-lsm-connect/lsm-connect.bpf.c b/10-lsm-connect/lsm-connect.bpf.c new file mode 100644 index 0000000..c731c93 --- /dev/null +++ b/10-lsm-connect/lsm-connect.bpf.c @@ -0,0 +1,41 @@ +#include "vmlinux.h" +#include +#include +#include + +char LICENSE[] SEC("license") = "GPL"; + +#define EPERM 1 +#define AF_INET 2 + +const __u32 blockme = 16843009; // 1.1.1.1 -> int + +SEC("lsm/socket_connect") +int BPF_PROG(restrict_connect, struct socket *sock, struct sockaddr *address, int addrlen, int ret) +{ + // Satisfying "cannot override a denial" rule + if (ret != 0) + { + return ret; + } + + // Only IPv4 in this example + if (address->sa_family != AF_INET) + { + return 0; + } + + // Cast the address to an IPv4 socket address + struct sockaddr_in *addr = (struct sockaddr_in *)address; + + // Where do you want to go? + __u32 dest = addr->sin_addr.s_addr; + bpf_printk("lsm: found connect to %d", dest); + + if (dest == blockme) + { + bpf_printk("lsm: blocking %d", dest); + return -EPERM; + } + return 0; +} diff --git a/11-tc/.gitignore b/11-tc/.gitignore new file mode 100755 index 0000000..bbee7c8 --- /dev/null +++ b/11-tc/.gitignore @@ -0,0 +1,10 @@ +.vscode +package.json +*.wasm +ewasm-skel.h +ecli +ewasm +*.o +*.skel.json +*.skel.yaml +package.yaml diff --git a/11-tc/README.md b/11-tc/README.md new file mode 100644 index 0000000..380fc7c --- /dev/null +++ b/11-tc/README.md @@ -0,0 +1,56 @@ +--- +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 +--- + + +`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. + +```shell +$ sudo ecli ./package.json +... +Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF program. +...... +``` + +The `tc` output in `/sys/kernel/debug/tracing/trace_pipe` should look +something like this: + +``` +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + node-1254811 [007] ..s1 8737831.671074: 0: Got IP packet: tot_len: 79, ttl: 64 + sshd-1254728 [006] ..s1 8737831.674334: 0: Got IP packet: tot_len: 79, ttl: 64 + sshd-1254728 [006] ..s1 8737831.674349: 0: Got IP packet: tot_len: 72, ttl: 64 + node-1254811 [007] ..s1 8737831.674550: 0: Got IP packet: tot_len: 71, ttl: 64 +``` + +## 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/11-tc/tc.bpf.c b/11-tc/tc.bpf.c new file mode 100644 index 0000000..4b82864 --- /dev/null +++ b/11-tc/tc.bpf.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2022 Hengqi Chen */ +#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"; diff --git a/12-bindsnoop/.gitignore b/12-bindsnoop/.gitignore new file mode 100644 index 0000000..a1027ce --- /dev/null +++ b/12-bindsnoop/.gitignore @@ -0,0 +1,3 @@ +.vscode +package.json +ecli diff --git a/12-bindsnoop/README.md b/12-bindsnoop/README.md new file mode 100644 index 0000000..35f167a --- /dev/null +++ b/12-bindsnoop/README.md @@ -0,0 +1,106 @@ +--- +layout: post +title: bindsnoop +date: 2022-10-10 16:18 +category: bpftools +author: yunwei37 +tags: [bpftools, syscall, kprobe, perf-event] +summary: This tool traces the kernel function performing socket binding and print socket options set before the system call. +--- + +## origin + +origin from: + +https://github.com/iovisor/bcc/blob/master/libbpf-tools/bindsnoop.bpf.c + +## Compile and Run + +Compile: + +```shell +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +Run: + +```shell +sudo ./ecli run examples/bpftools/bindsnoop/package.json +``` + +## details in bcc + +Demonstrations of bindsnoop, the Linux eBPF/bcc version. + +This tool traces the kernel function performing socket binding and +print socket options set before the system call invocation that might +```console + impact bind behavior and bound interface: + SOL_IP IP_FREEBIND F.... + SOL_IP IP_TRANSPARENT .T... + SOL_IP IP_BIND_ADDRESS_NO_PORT ..N.. + SOL_SOCKET SO_REUSEADDR ...R. + SOL_SOCKET SO_REUSEPORT ....r +``` +```console +# ./bindsnoop.py +Tracing binds ... Hit Ctrl-C to end +PID COMM PROT ADDR PORT OPTS IF +3941081 test_bind_op TCP 192.168.1.102 0 F.N.. 0 +3940194 dig TCP :: 62087 ..... 0 +3940219 dig UDP :: 48665 ..... 0 +3940893 Acceptor Thr TCP :: 35343 ...R. 0 +``` +The output shows four bind system calls: +two "test_bind_op" instances, one with IP_FREEBIND and IP_BIND_ADDRESS_NO_PORT +options, dig process called bind for TCP and UDP sockets, +and Acceptor called bind for TCP with SO_REUSEADDR option set. + + +The -t option prints a timestamp column +```console +# ./bindsnoop.py -t +TIME(s) PID COMM PROT ADDR PORT OPTS IF +0.000000 3956801 dig TCP :: 49611 ..... 0 +0.011045 3956822 dig UDP :: 56343 ..... 0 +2.310629 3956498 test_bind_op TCP 192.168.1.102 39609 F...r 0 +``` + +The -U option prints a UID column: +```console +# ./bindsnoop.py -U +Tracing binds ... Hit Ctrl-C to end + UID PID COMM PROT ADDR PORT OPTS IF +127072 3956498 test_bind_op TCP 192.168.1.102 44491 F...r 0 +127072 3960261 Acceptor Thr TCP :: 48869 ...R. 0 + 0 3960729 Acceptor Thr TCP :: 44637 ...R. 0 + 0 3959075 chef-client UDP :: 61722 ..... 0 +``` + +The -u option filtering UID: +```console +# ./bindsnoop.py -Uu 0 +Tracing binds ... Hit Ctrl-C to end + UID PID COMM PROT ADDR PORT OPTS IF + 0 3966330 Acceptor Thr TCP :: 39319 ...R. 0 + 0 3968044 python3.7 TCP ::1 59371 ..... 0 + 0 10224 fetch TCP 0.0.0.0 42091 ...R. 0 +``` + +The --cgroupmap option filters based on a cgroup set. +It is meant to be used with an externally created map. +```console +# ./bindsnoop.py --cgroupmap /sys/fs/bpf/test01 +``` +For more details, see docs/special_filtering.md + + +In order to track heavy bind usage one can use --count option +```console +# ./bindsnoop.py --count +Tracing binds ... Hit Ctrl-C to end +LADDR LPORT BINDS +0.0.0.0 6771 4 +0.0.0.0 4433 4 +127.0.0.1 33665 1 +``` \ No newline at end of file diff --git a/12-bindsnoop/bindsnoop.bpf.c b/12-bindsnoop/bindsnoop.bpf.c new file mode 100644 index 0000000..dc99ba4 --- /dev/null +++ b/12-bindsnoop/bindsnoop.bpf.c @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* Copyright (c) 2021 Hengqi Chen */ +#include +#include +#include +#include +#include +#include "bindsnoop.bpf.h" + +#define MAX_ENTRIES 10240 +#define MAX_PORTS 1024 + +const volatile bool filter_cg = false; +const volatile pid_t target_pid = 0; +const volatile bool ignore_errors = true; +const volatile bool filter_by_port = false; + +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, MAX_ENTRIES); + __type(key, __u32); + __type(value, struct socket *); +} sockets SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_PORTS); + __type(key, __u16); + __type(value, __u16); +} ports 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(struct pt_regs *ctx, struct socket *socket) +{ + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u32 pid = pid_tgid >> 32; + __u32 tid = (__u32)pid_tgid; + + if (target_pid && target_pid != pid) + return 0; + + bpf_map_update_elem(&sockets, &tid, &socket, BPF_ANY); + return 0; +}; + +static int probe_exit(struct pt_regs *ctx, short ver) +{ + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u32 pid = pid_tgid >> 32; + __u32 tid = (__u32)pid_tgid; + struct socket **socketp, *socket; + struct inet_sock *inet_sock; + struct sock *sock; + union bind_options opts; + struct bind_event event = {}; + __u16 sport = 0, *port; + int ret; + + socketp = bpf_map_lookup_elem(&sockets, &tid); + if (!socketp) + return 0; + + ret = PT_REGS_RC(ctx); + if (ignore_errors && ret != 0) + goto cleanup; + + socket = *socketp; + sock = BPF_CORE_READ(socket, sk); + inet_sock = (struct inet_sock *)sock; + + sport = bpf_ntohs(BPF_CORE_READ(inet_sock, inet_sport)); + port = bpf_map_lookup_elem(&ports, &sport); + if (filter_by_port && !port) + goto cleanup; + + opts.fields.freebind = BPF_CORE_READ_BITFIELD_PROBED(inet_sock, freebind); + opts.fields.transparent = BPF_CORE_READ_BITFIELD_PROBED(inet_sock, transparent); + opts.fields.bind_address_no_port = BPF_CORE_READ_BITFIELD_PROBED(inet_sock, bind_address_no_port); + opts.fields.reuseaddress = BPF_CORE_READ_BITFIELD_PROBED(sock, __sk_common.skc_reuse); + opts.fields.reuseport = BPF_CORE_READ_BITFIELD_PROBED(sock, __sk_common.skc_reuseport); + event.opts = opts.data; + event.ts_us = bpf_ktime_get_ns() / 1000; + event.pid = pid; + event.port = sport; + event.bound_dev_if = BPF_CORE_READ(sock, __sk_common.skc_bound_dev_if); + event.ret = ret; + event.proto = BPF_CORE_READ_BITFIELD_PROBED(sock, sk_protocol); + bpf_get_current_comm(&event.task, sizeof(event.task)); + if (ver == 4) { + event.ver = ver; + bpf_probe_read_kernel(&event.addr, sizeof(event.addr), &inet_sock->inet_saddr); + } else { /* ver == 6 */ + event.ver = ver; + bpf_probe_read_kernel(&event.addr, sizeof(event.addr), sock->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); + } + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + +cleanup: + bpf_map_delete_elem(&sockets, &tid); + return 0; +} + +SEC("kprobe/inet_bind") +int BPF_KPROBE(ipv4_bind_entry, struct socket *socket) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return probe_entry(ctx, socket); +} + +SEC("kretprobe/inet_bind") +int BPF_KRETPROBE(ipv4_bind_exit) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return probe_exit(ctx, 4); +} + +SEC("kprobe/inet6_bind") +int BPF_KPROBE(ipv6_bind_entry, struct socket *socket) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return probe_entry(ctx, socket); +} + +SEC("kretprobe/inet6_bind") +int BPF_KRETPROBE(ipv6_bind_exit) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return probe_exit(ctx, 6); +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; \ No newline at end of file diff --git a/12-bindsnoop/bindsnoop.bpf.h b/12-bindsnoop/bindsnoop.bpf.h new file mode 100644 index 0000000..9643c86 --- /dev/null +++ b/12-bindsnoop/bindsnoop.bpf.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __BINDSNOOP_H +#define __BINDSNOOP_H + +#define TASK_COMM_LEN 16 + +struct bind_event { + unsigned __int128 addr; + unsigned long long ts_us; + unsigned int pid; + unsigned int bound_dev_if; + int ret; + unsigned short port; + unsigned short proto; + unsigned char opts; + unsigned char ver; + char task[TASK_COMM_LEN]; +}; + +union bind_options { + unsigned char data; + struct { + unsigned char freebind : 1; + unsigned char transparent : 1; + unsigned char bind_address_no_port : 1; + unsigned char reuseaddress : 1; + unsigned char reuseport : 1; + } fields; +}; + +#endif /* __BINDSNOOP_H */ diff --git a/12-bindsnoop/bindsnoop.md b/12-bindsnoop/bindsnoop.md new file mode 100644 index 0000000..d98809a --- /dev/null +++ b/12-bindsnoop/bindsnoop.md @@ -0,0 +1,95 @@ +## eBPF 入门实践教程:编写 eBPF 程序 Bindsnoopn 监控 socket 端口绑定事件 + +### 背景 + +Bindsnoop 会跟踪操作 socket 端口绑定的内核函数,并且在可能会影响端口绑定的系统调用发生之前,打印 +现有的 socket 选项。 + +### 实现原理 + +Bindsnoop 通过kprobe实现。其主要挂载点为 inet_bind 和 inet6_bind。inet_bind 为处理 IPV4 类型 +socket 端口绑定系统调用的接口,inet6_bind 为处理IPV6类型 socket 端口绑定系统调用的接口。 + +```c +SEC("kprobe/inet_bind") +int BPF_KPROBE(ipv4_bind_entry, struct socket *socket) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return probe_entry(ctx, socket); +} +SEC("kretprobe/inet_bind") + +int BPF_KRETPROBE(ipv4_bind_exit) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return probe_exit(ctx, 4); +} + +SEC("kprobe/inet6_bind") +int BPF_KPROBE(ipv6_bind_entry, struct socket *socket) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return probe_entry(ctx, socket); +} + +SEC("kretprobe/inet6_bind") +int BPF_KRETPROBE(ipv6_bind_exit) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return probe_exit(ctx, 6); +} +``` + +当系统试图进行socket端口绑定操作时, kprobe挂载的处理函数会被触发。在进入绑定函数时,`probe_entry`会先被 +调用,它会以 tid 为主键将 socket 信息存入 map 中。 + +```c +static int probe_entry(struct pt_regs *ctx, struct socket *socket) +{ + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u32 pid = pid_tgid >> 32; + __u32 tid = (__u32)pid_tgid; + + if (target_pid && target_pid != pid) + return 0; + + bpf_map_update_elem(&sockets, &tid, &socket, BPF_ANY); + return 0; +}; +``` +在执行完绑定函数后,`probe_exit`函数会被调用。该函数会读取tid对应的socket信息,将其和其他信息一起 +写入 event 结构体并输出到用户态。 + +```c +struct bind_event { + unsigned __int128 addr; + __u64 ts_us; + __u32 pid; + __u32 bound_dev_if; + int ret; + __u16 port; + __u16 proto; + __u8 opts; + __u8 ver; + char task[TASK_COMM_LEN]; +}; +``` + +当用户停止该工具时,其用户态代码会读取存入的数据并按要求打印。 + +### Eunomia中使用方式 + +![result](../imgs/mountsnoop.jpg) +![result](../imgs/bindsnoop-prometheus.png) + +### 总结 + +Bindsnoop 通过 kprobe 挂载点,实现了对 socket 端口的监视,增强了 Eunomia 的应用范围。 \ No newline at end of file diff --git a/13-tcpconnlat/.gitignore b/13-tcpconnlat/.gitignore new file mode 100644 index 0000000..3e91eef --- /dev/null +++ b/13-tcpconnlat/.gitignore @@ -0,0 +1,2 @@ +.vscode +package.json diff --git a/13-tcpconnlat/README.md b/13-tcpconnlat/README.md new file mode 100644 index 0000000..a7d8589 --- /dev/null +++ b/13-tcpconnlat/README.md @@ -0,0 +1,137 @@ +--- +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. +--- + +## origin + +origin from: + +https://github.com/iovisor/bcc/blob/master/libbpf-tools/tcpconnlat.bpf.c + +## Compile and Run + +Compile: + +```shell +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +Run: + +```shell +sudo ./ecli run package.json +``` + +TODO: support union in C + +## details in bcc + +Demonstrations of tcpconnect, the Linux eBPF/bcc version. + + +This tool traces the kernel function performing active TCP connections +(eg, via a connect() syscall; accept() are passive connections). Some example +output (IP addresses changed to protect the innocent): +```console +# ./tcpconnect +PID COMM IP SADDR DADDR DPORT +1479 telnet 4 127.0.0.1 127.0.0.1 23 +1469 curl 4 10.201.219.236 54.245.105.25 80 +1469 curl 4 10.201.219.236 54.67.101.145 80 +1991 telnet 6 ::1 ::1 23 +2015 ssh 6 fe80::2000:bff:fe82:3ac fe80::2000:bff:fe82:3ac 22 +``` +This output shows four connections, one from a "telnet" process, two from +"curl", and one from "ssh". The output details shows the IP version, source +address, destination address, and destination port. This traces attempted +connections: these may have failed. + +The overhead of this tool should be negligible, since it is only tracing the +kernel functions performing connect. It is not tracing every packet and then +filtering. + + +The -t option prints a timestamp column: +```console +# ./tcpconnect -t +TIME(s) PID COMM IP SADDR DADDR DPORT +31.871 2482 local_agent 4 10.103.219.236 10.251.148.38 7001 +31.874 2482 local_agent 4 10.103.219.236 10.101.3.132 7001 +31.878 2482 local_agent 4 10.103.219.236 10.171.133.98 7101 +90.917 2482 local_agent 4 10.103.219.236 10.251.148.38 7001 +90.928 2482 local_agent 4 10.103.219.236 10.102.64.230 7001 +90.938 2482 local_agent 4 10.103.219.236 10.115.167.169 7101 +``` +The output shows some periodic connections (or attempts) from a "local_agent" +process to various other addresses. A few connections occur every minute. + +The -d option tracks DNS responses and tries to associate each connection with +the a previous DNS query issued before it. If a DNS response matching the IP +is found, it will be printed. If no match was found, "No DNS Query" is printed +in this column. Queries for 127.0.0.1 and ::1 are automatically associated with +"localhost". If the time between when the DNS response was received and a +connect call was traced exceeds 100ms, the tool will print the time delta +after the query name. See below for www.domain.com for an example. +```console +# ./tcpconnect -d +PID COMM IP SADDR DADDR DPORT QUERY +1543 amazon-ssm-a 4 10.66.75.54 176.32.119.67 443 ec2messages.us-west-1.amazonaws.com +1479 telnet 4 127.0.0.1 127.0.0.1 23 localhost +1469 curl 4 10.201.219.236 54.245.105.25 80 www.domain.com (123.342ms) +1469 curl 4 10.201.219.236 54.67.101.145 80 No DNS Query +1991 telnet 6 ::1 ::1 23 localhost +2015 ssh 6 fe80::2000:bff:fe82:3ac fe80::2000:bff:fe82:3ac 22 anotherhost.org +``` + +The -L option prints a LPORT column: +```console +# ./tcpconnect -L +PID COMM IP SADDR LPORT DADDR DPORT +3706 nc 4 192.168.122.205 57266 192.168.122.150 5000 +3722 ssh 4 192.168.122.205 50966 192.168.122.150 22 +3779 ssh 6 fe80::1 52328 fe80::2 22 +``` + +The -U option prints a UID column: +```console +# ./tcpconnect -U +UID PID COMM IP SADDR DADDR DPORT +0 31333 telnet 6 ::1 ::1 23 +0 31333 telnet 4 127.0.0.1 127.0.0.1 23 +1000 31322 curl 4 127.0.0.1 127.0.0.1 80 +1000 31322 curl 6 ::1 ::1 80 +``` + +The -u option filtering UID: +```console +# ./tcpconnect -Uu 1000 +UID PID COMM IP SADDR DADDR DPORT +1000 31338 telnet 6 ::1 ::1 23 +1000 31338 telnet 4 127.0.0.1 127.0.0.1 23 +``` +To spot heavy outbound connections quickly one can use the -c flag. It will +count all active connections per source ip and destination ip/port. +```console +# ./tcpconnect.py -c +Tracing connect ... Hit Ctrl-C to end +^C +LADDR RADDR RPORT CONNECTS +192.168.10.50 172.217.21.194 443 70 +192.168.10.50 172.213.11.195 443 34 +192.168.10.50 172.212.22.194 443 21 +[...] +``` + +The --cgroupmap option filters based on a cgroup set. It is meant to be used +with an externally created map. +```console +# ./tcpconnect --cgroupmap /sys/fs/bpf/test01 +``` +For more details, see docs/special_filtering.md + diff --git a/13-tcpconnlat/tcpconnlat.bpf.c b/13-tcpconnlat/tcpconnlat.bpf.c new file mode 100644 index 0000000..544701b --- /dev/null +++ b/13-tcpconnlat/tcpconnlat.bpf.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020 Wenbo Zhang +#include +#include +#include +#include +#include "tcpconnlat.bpf.h" + +#define AF_INET 2 +#define AF_INET6 10 + +const volatile __u64 targ_min_us = 0; +const volatile pid_t targ_tgid = 0; + +struct piddata { + char comm[TASK_COMM_LEN]; + u64 ts; + u32 tgid; +}; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 4096); + __type(key, struct sock *); + __type(value, struct piddata); +} start SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(u32)); + __uint(value_size, sizeof(u32)); +} events SEC(".maps"); + +static int trace_connect(struct sock *sk) +{ + u32 tgid = bpf_get_current_pid_tgid() >> 32; + struct piddata piddata = {}; + + if (targ_tgid && targ_tgid != tgid) + return 0; + + bpf_get_current_comm(&piddata.comm, sizeof(piddata.comm)); + piddata.ts = bpf_ktime_get_ns(); + piddata.tgid = tgid; + bpf_map_update_elem(&start, &sk, &piddata, 0); + return 0; +} + +static int handle_tcp_rcv_state_process(void *ctx, struct sock *sk) +{ + struct piddata *piddatap; + struct event event = {}; + s64 delta; + u64 ts; + + if (BPF_CORE_READ(sk, __sk_common.skc_state) != TCP_SYN_SENT) + return 0; + + piddatap = bpf_map_lookup_elem(&start, &sk); + if (!piddatap) + return 0; + + ts = bpf_ktime_get_ns(); + delta = (s64)(ts - piddatap->ts); + if (delta < 0) + goto cleanup; + + event.delta_us = delta / 1000U; + if (targ_min_us && event.delta_us < targ_min_us) + goto cleanup; + __builtin_memcpy(&event.comm, piddatap->comm, + sizeof(event.comm)); + event.ts_us = ts / 1000; + event.tgid = piddatap->tgid; + event.lport = BPF_CORE_READ(sk, __sk_common.skc_num); + event.dport = BPF_CORE_READ(sk, __sk_common.skc_dport); + event.af = BPF_CORE_READ(sk, __sk_common.skc_family); + if (event.af == AF_INET) { + event.saddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr); + event.daddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_daddr); + } else { + BPF_CORE_READ_INTO(&event.saddr_v6, sk, + __sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); + BPF_CORE_READ_INTO(&event.daddr_v6, sk, + __sk_common.skc_v6_daddr.in6_u.u6_addr32); + } + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, + &event, sizeof(event)); + +cleanup: + bpf_map_delete_elem(&start, &sk); + return 0; +} + +SEC("kprobe/tcp_v4_connect") +int BPF_KPROBE(tcp_v4_connect, struct sock *sk) +{ + return trace_connect(sk); +} + +SEC("kprobe/tcp_v6_connect") +int BPF_KPROBE(tcp_v6_connect, struct sock *sk) +{ + return trace_connect(sk); +} + +SEC("kprobe/tcp_rcv_state_process") +int BPF_KPROBE(tcp_rcv_state_process, struct sock *sk) +{ + return handle_tcp_rcv_state_process(ctx, sk); +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/13-tcpconnlat/tcpconnlat.bpf.h b/13-tcpconnlat/tcpconnlat.bpf.h new file mode 100644 index 0000000..d6cd930 --- /dev/null +++ b/13-tcpconnlat/tcpconnlat.bpf.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __TCPCONNLAT_H +#define __TCPCONNLAT_H + +#define TASK_COMM_LEN 16 + +struct event { + // union { + unsigned int saddr_v4; + unsigned char saddr_v6[16]; + // }; + // union { + unsigned int daddr_v4; + unsigned char daddr_v6[16]; + // }; + char comm[TASK_COMM_LEN]; + unsigned long long delta_us; + unsigned long long ts_us; + unsigned int tgid; + int af; + unsigned short lport; + unsigned short dport; +}; + + +#endif /* __TCPCONNLAT_H_ */ diff --git a/13-tcpconnlat/tcpconnlat.md b/13-tcpconnlat/tcpconnlat.md new file mode 100644 index 0000000..9f19bf1 --- /dev/null +++ b/13-tcpconnlat/tcpconnlat.md @@ -0,0 +1,186 @@ +## eBPF 入门实践教程:编写 eBPF 程序 tcpconnlat 测量 tcp 连接延时 + +### 背景 + +在互联网后端日常开发接口的时候中,不管你使用的是C、Java、PHP还是Golang,都避免不了需要调用mysql、redis等组件来获取数据,可能还需要执行一些rpc远程调用,或者再调用一些其它restful api。 在这些调用的底层,基本都是在使用TCP协议进行传输。这是因为在传输层协议中,TCP协议具备可靠的连接,错误重传,拥塞控制等优点,所以目前应用比UDP更广泛一些。但相对而言,tcp 连接也有一些缺点,例如建立连接的延时较长等。因此也会出现像 QUIC ,即 快速UDP网络连接 ( Quick UDP Internet Connections )这样的替代方案。 + +tcp 连接延时分析对于网络性能分析优化或者故障排查都能起到不少作用。 + +### tcpconnlat 的实现原理 + +tcpconnlat 这个工具跟踪执行活动TCP连接的内核函数 (例如,通过connect()系统调用),并显示本地测量的连接的延迟(时间),即从发送 SYN 到响应包的时间。 + +### tcp 连接原理 + +tcp 连接的整个过程如图所示: + +![tcpconnlate](tcpconnlat1.png) + +在这个连接过程中,我们来简单分析一下每一步的耗时: + +1. 客户端发出SYNC包:客户端一般是通过connect系统调用来发出 SYN 的,这里牵涉到本机的系统调用和软中断的 CPU 耗时开销 +2. SYN传到服务器:SYN从客户端网卡被发出,这是一次长途远距离的网络传输 +3. 服务器处理SYN包:内核通过软中断来收包,然后放到半连接队列中,然后再发出SYN/ACK响应。主要是 CPU 耗时开销 +4. SYC/ACK传到客户端:长途网络跋涉 +5. 客户端处理 SYN/ACK:客户端内核收包并处理SYN后,经过几us的CPU处理,接着发出 ACK。同样是软中断处理开销 +6. ACK传到服务器:长途网络跋涉 +7. 服务端收到ACK:服务器端内核收到并处理ACK,然后把对应的连接从半连接队列中取出来,然后放到全连接队列中。一次软中断CPU开销 +8. 服务器端用户进程唤醒:正在被accpet系统调用阻塞的用户进程被唤醒,然后从全连接队列中取出来已经建立好的连接。一次上下文切换的CPU开销 + +在客户端视角,在正常情况下一次TCP连接总的耗时也就就大约是一次网络RTT的耗时。但在某些情况下,可能会导致连接时的网络传输耗时上涨、CPU处理开销增加、甚至是连接失败。这种时候在发现延时过长之后,就可以结合其他信息进行分析。 + +### ebpf 实现原理 + +在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是: + +- 半连接队列,也称 SYN 队列; +- 全连接队列,也称 accepet 队列; + + +服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。 + +我们的 ebpf 代码实现在 https://github.com/yunwei37/Eunomia/blob/master/bpftools/tcpconnlat/tcpconnlat.bpf.c 中: + +它主要使用了 trace_tcp_rcv_state_process 和 kprobe/tcp_v4_connect 这样的跟踪点: + +```c + +SEC("kprobe/tcp_v4_connect") +int BPF_KPROBE(tcp_v4_connect, struct sock *sk) +{ + return trace_connect(sk); +} + +SEC("kprobe/tcp_v6_connect") +int BPF_KPROBE(tcp_v6_connect, struct sock *sk) +{ + return trace_connect(sk); +} + +SEC("kprobe/tcp_rcv_state_process") +int BPF_KPROBE(tcp_rcv_state_process, struct sock *sk) +{ + return handle_tcp_rcv_state_process(ctx, sk); +} +``` + +在 trace_connect 中,我们跟踪新的 tcp 连接,记录到达时间,并且把它加入 map 中: + +```c +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 4096); + __type(key, struct sock *); + __type(value, struct piddata); +} start SEC(".maps"); + +static int trace_connect(struct sock *sk) +{ + u32 tgid = bpf_get_current_pid_tgid() >> 32; + struct piddata piddata = {}; + + if (targ_tgid && targ_tgid != tgid) + return 0; + + bpf_get_current_comm(&piddata.comm, sizeof(piddata.comm)); + piddata.ts = bpf_ktime_get_ns(); + piddata.tgid = tgid; + bpf_map_update_elem(&start, &sk, &piddata, 0); + return 0; +} +``` + +在 handle_tcp_rcv_state_process 中,我们跟踪接收到的 tcp 数据包,从 map 从提取出对应的 connect 事件,并且计算延迟: + +```c +static int handle_tcp_rcv_state_process(void *ctx, struct sock *sk) +{ + struct piddata *piddatap; + struct event event = {}; + s64 delta; + u64 ts; + + if (BPF_CORE_READ(sk, __sk_common.skc_state) != TCP_SYN_SENT) + return 0; + + piddatap = bpf_map_lookup_elem(&start, &sk); + if (!piddatap) + return 0; + + ts = bpf_ktime_get_ns(); + delta = (s64)(ts - piddatap->ts); + if (delta < 0) + goto cleanup; + + event.delta_us = delta / 1000U; + if (targ_min_us && event.delta_us < targ_min_us) + goto cleanup; + __builtin_memcpy(&event.comm, piddatap->comm, + sizeof(event.comm)); + event.ts_us = ts / 1000; + event.tgid = piddatap->tgid; + event.lport = BPF_CORE_READ(sk, __sk_common.skc_num); + event.dport = BPF_CORE_READ(sk, __sk_common.skc_dport); + event.af = BPF_CORE_READ(sk, __sk_common.skc_family); + if (event.af == AF_INET) { + event.saddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr); + event.daddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_daddr); + } else { + BPF_CORE_READ_INTO(&event.saddr_v6, sk, + __sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); + BPF_CORE_READ_INTO(&event.daddr_v6, sk, + __sk_common.skc_v6_daddr.in6_u.u6_addr32); + } + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, + &event, sizeof(event)); + +cleanup: + bpf_map_delete_elem(&start, &sk); + return 0; +} +``` + +### Eunomia 测试 demo + +使用命令行进行追踪: + +```bash +$ sudo build/bin/Release/eunomia run tcpconnlat +[sudo] password for yunwei: +[2022-08-07 02:13:39.601] [info] eunomia run in cmd... +[2022-08-07 02:13:40.534] [info] press 'Ctrl C' key to exit... +PID COMM IP SRC DEST PORT LAT(ms) CONATINER/OS +3477 openresty 4 172.19.0.7 172.19.0.5 2379 0.05 docker-apisix_apisix_1 +3483 openresty 4 172.19.0.7 172.19.0.5 2379 0.08 docker-apisix_apisix_1 +3477 openresty 4 172.19.0.7 172.19.0.5 2379 0.04 docker-apisix_apisix_1 +3478 openresty 4 172.19.0.7 172.19.0.5 2379 0.05 docker-apisix_apisix_1 +3478 openresty 4 172.19.0.7 172.19.0.5 2379 0.03 docker-apisix_apisix_1 +3478 openresty 4 172.19.0.7 172.19.0.5 2379 0.03 docker-apisix_apisix_1 +``` + +还可以使用 eunomia 作为 prometheus exporter,在运行上述命令之后,打开 prometheus 自带的可视化面板: + +使用下述查询命令即可看到延时的统计图表: + +``` + rate(eunomia_observed_tcpconnlat_v4_histogram_sum[5m]) +/ + rate(eunomia_observed_tcpconnlat_v4_histogram_count[5m]) +``` + +结果: + +![result](tcpconnlat_p.png) + +### 总结 + +通过上面的实验,我们可以看到,tcpconnlat 工具的实现原理是基于内核的TCP连接的跟踪,并且可以跟踪到 tcp 连接的延迟时间;除了命令行使用方式之外,还可以将其和容器、k8s 等元信息综合起来,通过 `prometheus` 和 `grafana` 等工具进行网络性能分析。 + +> `Eunomia` 是一个使用 C/C++ 开发的基于 eBPF的轻量级,高性能云原生监控工具,旨在帮助用户了解容器的各项行为、监控可疑的容器安全事件,力求提供覆盖容器全生命周期的轻量级开源监控解决方案。它使用 `Linux` `eBPF` 技术在运行时跟踪您的系统和应用程序,并分析收集的事件以检测可疑的行为模式。目前,它包含性能分析、容器集群网络可视化分析*、容器安全感知告警、一键部署、持久化存储监控等功能,提供了多样化的 ebpf 追踪点。其核心导出器/命令行工具最小仅需要约 4MB 大小的二进制程序,即可在支持的 Linux 内核上启动。 + +项目地址:https://github.com/yunwei37/Eunomia + +### 参考资料 + +1. http://kerneltravel.net/blog/2020/tcpconnlat/ +2. https://network.51cto.com/article/640631.html \ No newline at end of file diff --git a/13-tcpconnlat/tcpconnlat1.png b/13-tcpconnlat/tcpconnlat1.png new file mode 100644 index 0000000000000000000000000000000000000000..4fd5eda8d0cc483e8c198cff8a12b36908f68cdb GIT binary patch literal 135015 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 B_5}a{ literal 0 HcmV?d00001 diff --git a/13-tcpconnlat/tcpconnlat_p.png b/13-tcpconnlat/tcpconnlat_p.png new file mode 100644 index 0000000000000000000000000000000000000000..74caa9ac9eee3f528e8bb6924562a021afd12491 GIT binary patch literal 41522 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�+4rJi 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π^nz2*l5=@i6Ye^x-NhPuIQB=-<8G&KN~&k+ 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 bg@F6aOF6>x_DNsBzmvzckL76mc;$Zpg=BtY literal 0 HcmV?d00001 diff --git a/14-tcpstates/.gitignore b/14-tcpstates/.gitignore new file mode 100644 index 0000000..c610807 --- /dev/null +++ b/14-tcpstates/.gitignore @@ -0,0 +1,5 @@ +.vscode +package.json +eunomia-exporter +ecli + \ No newline at end of file diff --git a/14-tcpstates/README.md b/14-tcpstates/README.md new file mode 100644 index 0000000..f284c55 --- /dev/null +++ b/14-tcpstates/README.md @@ -0,0 +1,56 @@ +--- +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 +--- + + +## origin + +origin from: + +https://github.com/iovisor/bcc/blob/master/libbpf-tools/tcpconnlat.bpf.c + +## Compile and Run + +Compile: + +```shell +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` +Run: + +```shell +sudo ./ecli run package.json +``` + +## details in bcc + +Demonstrations of tcpstates, the Linux BPF/bcc version. + + +tcpstates prints TCP state change information, including the duration in each +state as milliseconds. For example, a single TCP session: +```console +# tcpstates +SKADDR C-PID C-COMM LADDR LPORT RADDR RPORT OLDSTATE -> NEWSTATE MS +ffff9fd7e8192000 22384 curl 100.66.100.185 0 52.33.159.26 80 CLOSE -> SYN_SENT 0.000 +ffff9fd7e8192000 0 swapper/5 100.66.100.185 63446 52.33.159.26 80 SYN_SENT -> ESTABLISHED 1.373 +ffff9fd7e8192000 22384 curl 100.66.100.185 63446 52.33.159.26 80 ESTABLISHED -> FIN_WAIT1 176.042 +ffff9fd7e8192000 0 swapper/5 100.66.100.185 63446 52.33.159.26 80 FIN_WAIT1 -> FIN_WAIT2 0.536 +ffff9fd7e8192000 0 swapper/5 100.66.100.185 63446 52.33.159.26 80 FIN_WAIT2 -> CLOSE 0.006 +^C +``` +This showed that the most time was spent in the ESTABLISHED state (which then +transitioned to FIN_WAIT1), which was 176.042 milliseconds. + +The first column is the socked address, as the output may include lines from +different sessions interleaved. The next two columns show the current on-CPU +process ID and command name: these may show the process that owns the TCP +session, depending on whether the state change executes synchronously in +process context. If that's not the case, they may show kernel details. + diff --git a/14-tcpstates/tcpstates.bpf.c b/14-tcpstates/tcpstates.bpf.c new file mode 100644 index 0000000..b479ca4 --- /dev/null +++ b/14-tcpstates/tcpstates.bpf.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2021 Hengqi Chen */ +#include +#include +#include +#include +#include "tcpstates.bpf.h" + +#define MAX_ENTRIES 10240 +#define AF_INET 2 +#define AF_INET6 10 + +const volatile bool filter_by_sport = false; +const volatile bool filter_by_dport = false; +const volatile short target_family = 0; + +struct +{ + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, __u16); + __type(value, __u16); +} sports SEC(".maps"); + +struct +{ + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, __u16); + __type(value, __u16); +} dports SEC(".maps"); + +struct +{ + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, struct sock *); + __type(value, __u64); +} timestamps SEC(".maps"); + +struct +{ + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} events SEC(".maps"); + +SEC("tracepoint/sock/inet_sock_set_state") +int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx) +{ + struct sock *sk = (struct sock *)ctx->skaddr; + __u16 family = ctx->family; + __u16 sport = ctx->sport; + __u16 dport = ctx->dport; + __u64 *tsp, delta_us, ts; + struct event event = {}; + + if (ctx->protocol != IPPROTO_TCP) + return 0; + + if (target_family && target_family != family) + return 0; + + if (filter_by_sport && !bpf_map_lookup_elem(&sports, &sport)) + return 0; + + if (filter_by_dport && !bpf_map_lookup_elem(&dports, &dport)) + return 0; + + tsp = bpf_map_lookup_elem(×tamps, &sk); + ts = bpf_ktime_get_ns(); + if (!tsp) + delta_us = 0; + else + delta_us = (ts - *tsp) / 1000; + + event.skaddr = (__u64)sk; + event.ts_us = ts / 1000; + event.delta_us = delta_us; + event.pid = bpf_get_current_pid_tgid() >> 32; + event.oldstate = ctx->oldstate; + event.newstate = ctx->newstate; + event.family = family; + event.sport = sport; + event.dport = dport; + bpf_get_current_comm(&event.task, sizeof(event.task)); + + if (family == AF_INET) + { + bpf_probe_read_kernel(&event.saddr, sizeof(event.saddr), &sk->__sk_common.skc_rcv_saddr); + bpf_probe_read_kernel(&event.daddr, sizeof(event.daddr), &sk->__sk_common.skc_daddr); + } + else + { /* family == AF_INET6 */ + bpf_probe_read_kernel(&event.saddr, sizeof(event.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); + bpf_probe_read_kernel(&event.daddr, sizeof(event.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32); + } + + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + + if (ctx->newstate == TCP_CLOSE) + bpf_map_delete_elem(×tamps, &sk); + else + bpf_map_update_elem(×tamps, &sk, &ts, BPF_ANY); + + return 0; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/14-tcpstates/tcpstates.bpf.h b/14-tcpstates/tcpstates.bpf.h new file mode 100644 index 0000000..9084301 --- /dev/null +++ b/14-tcpstates/tcpstates.bpf.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2021 Hengqi Chen */ +#ifndef __TCPSTATES_H +#define __TCPSTATES_H + +#define TASK_COMM_LEN 16 + +struct event +{ + unsigned __int128 saddr; + unsigned __int128 daddr; + __u64 skaddr; + __u64 ts_us; + __u64 delta_us; + __u32 pid; + int oldstate; + int newstate; + __u16 family; + __u16 sport; + __u16 dport; + char task[TASK_COMM_LEN]; +}; + +#endif /* __TCPSTATES_H */ diff --git a/15-tcprtt/tcprtt.md b/15-tcprtt/tcprtt.md new file mode 100644 index 0000000..712164d --- /dev/null +++ b/15-tcprtt/tcprtt.md @@ -0,0 +1,116 @@ +## eBPF 入门实践教程:编写 eBPF 程序 Tcprtt 测量 TCP 连接的往返时间 + +### 背景 +网络质量在互联网社会中是一个很重要的因素。导致网络质量差的因素有很多,可能是硬件因素导致,也可能是程序 +写的不好导致。为了能更好地定位网络问题,`tcprtt` 工具被提出。它可以监测TCP链接的往返时间,从而分析 +网络质量,帮助用户定位问题来源。 + +### 实现原理 +`tcprtt` 在tcp链接建立的执行点下挂载了执行函数。 +```c +SEC("fentry/tcp_rcv_established") +int BPF_PROG(tcp_rcv, struct sock *sk) +{ + const struct inet_sock *inet = (struct inet_sock *)(sk); + struct tcp_sock *ts; + struct hist *histp; + u64 key, slot; + u32 srtt; + + if (targ_sport && targ_sport != inet->inet_sport) + return 0; + if (targ_dport && targ_dport != sk->__sk_common.skc_dport) + return 0; + if (targ_saddr && targ_saddr != inet->inet_saddr) + return 0; + if (targ_daddr && targ_daddr != sk->__sk_common.skc_daddr) + return 0; + + if (targ_laddr_hist) + key = inet->inet_saddr; + else if (targ_raddr_hist) + key = inet->sk.__sk_common.skc_daddr; + else + key = 0; + histp = bpf_map_lookup_or_try_init(&hists, &key, &zero); + if (!histp) + return 0; + ts = (struct tcp_sock *)(sk); + srtt = BPF_CORE_READ(ts, srtt_us) >> 3; + if (targ_ms) + srtt /= 1000U; + slot = log2l(srtt); + if (slot >= MAX_SLOTS) + slot = MAX_SLOTS - 1; + __sync_fetch_and_add(&histp->slots[slot], 1); + if (targ_show_ext) { + __sync_fetch_and_add(&histp->latency, srtt); + __sync_fetch_and_add(&histp->cnt, 1); + } + return 0; +} + +SEC("kprobe/tcp_rcv_established") +int BPF_KPROBE(tcp_rcv_kprobe, struct sock *sk) +{ + const struct inet_sock *inet = (struct inet_sock *)(sk); + u32 srtt, saddr, daddr; + struct tcp_sock *ts; + struct hist *histp; + u64 key, slot; + + if (targ_sport) { + u16 sport; + bpf_probe_read_kernel(&sport, sizeof(sport), &inet->inet_sport); + if (targ_sport != sport) + return 0; + } + if (targ_dport) { + u16 dport; + bpf_probe_read_kernel(&dport, sizeof(dport), &sk->__sk_common.skc_dport); + if (targ_dport != dport) + return 0; + } + bpf_probe_read_kernel(&saddr, sizeof(saddr), &inet->inet_saddr); + if (targ_saddr && targ_saddr != saddr) + return 0; + bpf_probe_read_kernel(&daddr, sizeof(daddr), &sk->__sk_common.skc_daddr); + if (targ_daddr && targ_daddr != daddr) + return 0; + + if (targ_laddr_hist) + key = saddr; + else if (targ_raddr_hist) + key = daddr; + else + key = 0; + histp = bpf_map_lookup_or_try_init(&hists, &key, &zero); + if (!histp) + return 0; + ts = (struct tcp_sock *)(sk); + bpf_probe_read_kernel(&srtt, sizeof(srtt), &ts->srtt_us); + srtt >>= 3; + if (targ_ms) + srtt /= 1000U; + slot = log2l(srtt); + if (slot >= MAX_SLOTS) + slot = MAX_SLOTS - 1; + __sync_fetch_and_add(&histp->slots[slot], 1); + if (targ_show_ext) { + __sync_fetch_and_add(&histp->latency, srtt); + __sync_fetch_and_add(&histp->cnt, 1); + } + return 0; +} + +``` +当有tcp链接建立时,该工具会自动根据当前系统的支持情况,选择合适的执行函数。 +在执行函数中,`tcprtt`会收集tcp链接的各项基本底薪,包括地址,源端口,目标端口,耗时 +等等,并将其更新到直方图的map中。运行结束后通过用户态代码,展现给用户。 + +### Eunomia中使用方式 + + +### 总结 + +`tcprtt` 通过直方图的形式,可以轻松展现当前系统中网络抖动的情况,方便开发者快速定位系统网络问题 \ No newline at end of file diff --git a/16-profile/profile.md b/16-profile/profile.md new file mode 100644 index 0000000..0fdaedd --- /dev/null +++ b/16-profile/profile.md @@ -0,0 +1,104 @@ +## eBPF 入门实践教程:编写 eBPF 程序 profile 进行性能分析 + +### 背景 + +`profile` 是一款用户追踪程序执行调用流程的工具,类似于perf中的 -g 指令。但是相较于perf而言, +`profile`的功能更为细化,它可以选择用户需要追踪的层面,比如在用户态层面进行追踪,或是在内核态进行追踪。 + +### 实现原理 + +`profile` 的实现依赖于linux中的perf_event。在注入ebpf程序前,`profile` 工具会先将 perf_event +注册好。 +```c +static int open_and_attach_perf_event(int freq, struct bpf_program *prog, + struct bpf_link *links[]) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_SOFTWARE, + .freq = env.freq, + .sample_freq = env.sample_freq, + .config = PERF_COUNT_SW_CPU_CLOCK, + }; + int i, fd; + + for (i = 0; i < nr_cpus; i++) { + if (env.cpu != -1 && env.cpu != i) + continue; + + fd = syscall(__NR_perf_event_open, &attr, -1, i, -1, 0); + if (fd < 0) { + /* Ignore CPU that is offline */ + if (errno == ENODEV) + continue; + fprintf(stderr, "failed to init perf sampling: %s\n", + strerror(errno)); + return -1; + } + links[i] = bpf_program__attach_perf_event(prog, fd); + if (!links[i]) { + fprintf(stderr, "failed to attach perf event on cpu: " + "%d\n", i); + links[i] = NULL; + close(fd); + return -1; + } + } + + return 0; +} +``` +其ebpf程序实现逻辑是对程序的堆栈进行定时采样,从而捕获程序的执行流程。 +```c +SEC("perf_event") +int do_perf_event(struct bpf_perf_event_data *ctx) +{ + __u64 id = bpf_get_current_pid_tgid(); + __u32 pid = id >> 32; + __u32 tid = id; + __u64 *valp; + static const __u64 zero; + struct key_t key = {}; + + if (!include_idle && tid == 0) + return 0; + + if (targ_pid != -1 && targ_pid != pid) + return 0; + if (targ_tid != -1 && targ_tid != tid) + return 0; + + key.pid = pid; + bpf_get_current_comm(&key.name, sizeof(key.name)); + + if (user_stacks_only) + key.kern_stack_id = -1; + else + key.kern_stack_id = bpf_get_stackid(&ctx->regs, &stackmap, 0); + + if (kernel_stacks_only) + key.user_stack_id = -1; + else + key.user_stack_id = bpf_get_stackid(&ctx->regs, &stackmap, BPF_F_USER_STACK); + + if (key.kern_stack_id >= 0) { + // populate extras to fix the kernel stack + __u64 ip = PT_REGS_IP(&ctx->regs); + + if (is_kernel_addr(ip)) { + key.kernel_ip = ip; + } + } + + valp = bpf_map_lookup_or_try_init(&counts, &key, &zero); + if (valp) + __sync_fetch_and_add(valp, 1); + + return 0; +} +``` +通过这种方式,它可以根据用户指令,简单的决定追踪用户态层面的执行流程或是内核态层面的执行流程。 +### Eunomia中使用方式 + + +### 总结 +`profile` 实现了对程序执行流程的分析,在debug等操作中可以极大的帮助开发者提高效率。 \ No newline at end of file diff --git a/17-memleak/memleak.md b/17-memleak/memleak.md new file mode 100644 index 0000000..4099494 --- /dev/null +++ b/17-memleak/memleak.md @@ -0,0 +1,80 @@ +## eBPF 入门实践教程:编写 eBPF 程序 Memleak 监控内存泄漏 + +### 背景 + +内存泄漏对于一个程序而言是一个很严重的问题。倘若放任一个存在内存泄漏的程序运行,久而久之 +系统的内存会慢慢被耗尽,导致程序运行速度显著下降。为了避免这一情况,`memleak`工具被提出。 +它可以跟踪并匹配内存分配和释放的请求,并且打印出已经被分配资源而又尚未释放的堆栈信息。 + +### 实现原理 + +`memleak` 的实现逻辑非常直观。它在我们常用的动态分配内存的函数接口路径上挂载了ebpf程序, +同时在free上也挂载了ebpf程序。在调用分配内存相关函数时,`memleak` 会记录调用者的pid,分配得到 +内存的地址,分配得到的内存大小等基本数据。在free之后,`memeleak`则会去map中删除记录的对应的分配 +信息。对于用户态常用的分配函数 `malloc`, `calloc` 等,`memleak`使用了 uporbe 技术实现挂载,对于 +内核态的函数,比如 `kmalloc` 等,`memleak` 则使用了现有的 tracepoint 来实现。 +`memleak`主要的挂载点为 +```c +SEC("uprobe/malloc") + +SEC("uretprobe/malloc") + +SEC("uprobe/calloc") + +SEC("uretprobe/calloc") + +SEC("uprobe/realloc") + +SEC("uretprobe/realloc") + +SEC("uprobe/memalign") + +SEC("uretprobe/memalign") + +SEC("uprobe/posix_memalign") + +SEC("uretprobe/posix_memalign") + +SEC("uprobe/valloc") + +SEC("uretprobe/valloc") + +SEC("uprobe/pvalloc") + +SEC("uretprobe/pvalloc") + +SEC("uprobe/aligned_alloc") + +SEC("uretprobe/aligned_alloc") + +SEC("uprobe/free") + +SEC("tracepoint/kmem/kmalloc") + +SEC("tracepoint/kmem/kfree") + + +SEC("tracepoint/kmem/kmalloc_node") + +SEC("tracepoint/kmem/kmem_cache_alloc") + +SEC("tracepoint/kmem/kmem_cache_alloc_node") + +SEC("tracepoint/kmem/kmem_cache_free") + +SEC("tracepoint/kmem/mm_page_alloc") + +SEC("tracepoint/kmem/mm_page_free") + +SEC("tracepoint/percpu/percpu_alloc_percpu") + +SEC("tracepoint/percpu/percpu_free_percpu") + +``` + +### Eunomia中使用方式 + + +### 总结 +`memleak` 实现了对内存分配系列函数的监控追踪,可以避免程序发生严重的内存泄漏事故,对于开发者而言 +具有极大的帮助。 diff --git a/18-biopattern/biolatency.md b/18-biopattern/biolatency.md new file mode 100644 index 0000000..423fca2 --- /dev/null +++ b/18-biopattern/biolatency.md @@ -0,0 +1,121 @@ +## 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/18-biopattern/biopattern.md b/18-biopattern/biopattern.md new file mode 100644 index 0000000..d06a473 --- /dev/null +++ b/18-biopattern/biopattern.md @@ -0,0 +1,48 @@ +## eBPF 入门实践教程:编写 eBPF 程序 Biopattern: 统计随机/顺序磁盘 I/O + +### 背景 + +Biopattern 可以统计随机/顺序磁盘I/O次数的比例。 + +### 实现原理 + +Biopattern 的ebpf代码在 tracepoint/block/block_rq_complete 挂载点下实现。在磁盘完成IO请求 +后,程序会经过此挂载点。Biopattern 内部存有一张以设备号为主键的哈希表,当程序经过挂载点时, Biopattern +会获得操作信息,根据哈希表中该设备的上一次操作记录来判断本次操作是随机IO还是顺序IO,并更新操作计数。 + +```c +SEC("tracepoint/block/block_rq_complete") +int handle__block_rq_complete(struct trace_event_raw_block_rq_complete *ctx) +{ + sector_t *last_sectorp, sector = ctx->sector; + struct counter *counterp, zero = {}; + u32 nr_sector = ctx->nr_sector; + dev_t dev = ctx->dev; + + if (targ_dev != -1 && targ_dev != dev) + return 0; + + counterp = bpf_map_lookup_or_try_init(&counters, &dev, &zero); + if (!counterp) + return 0; + if (counterp->last_sector) { + if (counterp->last_sector == sector) + __sync_fetch_and_add(&counterp->sequential, 1); + else + __sync_fetch_and_add(&counterp->random, 1); + __sync_fetch_and_add(&counterp->bytes, nr_sector * 512); + } + counterp->last_sector = sector + nr_sector; + return 0; +} + +``` +当用户停止Biopattern后,用户态程序会读取获得的计数信息,并将其输出给用户。 + +### Eunomia中使用方式 + +尚未集成 + +### 总结 + +Biopattern 可以展现随机/顺序磁盘I/O次数的比例,对于开发者把握整体I/O情况有较大帮助。 \ No newline at end of file diff --git a/18-biopattern/biostacks.md b/18-biopattern/biostacks.md new file mode 100644 index 0000000..3fb08fd --- /dev/null +++ b/18-biopattern/biostacks.md @@ -0,0 +1,100 @@ +## 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/18-biopattern/bitesize.md b/18-biopattern/bitesize.md new file mode 100644 index 0000000..dd4a0be --- /dev/null +++ b/18-biopattern/bitesize.md @@ -0,0 +1,63 @@ +## 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/19-syscount/syscount.md b/19-syscount/syscount.md new file mode 100644 index 0000000..167db71 --- /dev/null +++ b/19-syscount/syscount.md @@ -0,0 +1,81 @@ +## 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 diff --git a/2-fentry-unlink/.gitignore b/2-fentry-unlink/.gitignore new file mode 100644 index 0000000..7d5aebf --- /dev/null +++ b/2-fentry-unlink/.gitignore @@ -0,0 +1,6 @@ +.vscode +package.json +*.o +*.skel.json +*.skel.yaml +package.yaml diff --git a/2-fentry-unlink/README.md b/2-fentry-unlink/README.md new file mode 100644 index 0000000..8ba6d4b --- /dev/null +++ b/2-fentry-unlink/README.md @@ -0,0 +1,76 @@ +--- +layout: post +title: fentry-link +date: 2022-10-10 16:18 +category: bpftools +author: yunwei37 +tags: [bpftools, examples, fentry, no-output] +summary: an example that uses fentry and fexit BPF programs for tracing a file is deleted +--- + +## Fentry + +`fentry` is an example that uses fentry and fexit BPF programs for tracing. It +attaches `fentry` and `fexit` traces to `do_unlinkat()` which is called when a +file is deleted and logs the return value, PID, and filename to the +trace pipe. + +Important differences, compared to kprobes, are improved performance and +usability. In this example, better usability is shown with the ability to +directly dereference pointer arguments, like in normal C, instead of using +various read helpers. The big distinction between **fexit** and **kretprobe** +programs is that fexit one has access to both input arguments and returned +result, while kretprobe can only access the result. + +fentry and fexit programs are available starting from 5.5 kernels. + +```console +$ sudo ecli examples/bpftools/fentry-link/package.json +Runing eBPF program... +``` + +The `fentry` output in `/sys/kernel/debug/tracing/trace_pipe` should look +something like this: + +```console +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + rm-9290 [004] d..2 4637.798698: bpf_trace_printk: fentry: pid = 9290, filename = test_file + rm-9290 [004] d..2 4637.798843: bpf_trace_printk: fexit: pid = 9290, filename = test_file, ret = 0 + 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 +``` + +## Run + + + +- Compile: + + ```console + docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest + ``` + + or + + ```console + $ ecc fentry-link.bpf.c + Compiling bpf object... + Packing ebpf object and config into package.json... + ``` + +- Run and help: + + ```console + sudo ecli examples/bpftools/fentry-link/package.json -h + Usage: fentry_link_bpf [--help] [--version] [--verbose] + + A simple eBPF program + + Optional arguments: + -h, --help shows help message and exits + -v, --version prints version information and exits + --verbose prints libbpf debug information + + Built with eunomia-bpf framework. + See https://github.com/eunomia-bpf/eunomia-bpf for more information. + ``` \ No newline at end of file diff --git a/2-fentry-unlink/fentry-link.bpf.c b/2-fentry-unlink/fentry-link.bpf.c new file mode 100644 index 0000000..baf5575 --- /dev/null +++ b/2-fentry-unlink/fentry-link.bpf.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright (c) 2021 Sartura */ +#include "vmlinux.h" +#include +#include + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +SEC("fentry/do_unlinkat") +int BPF_PROG(do_unlinkat, int dfd, struct filename *name) +{ + pid_t pid; + + pid = bpf_get_current_pid_tgid() >> 32; + bpf_printk("fentry: pid = %d, filename = %s\n", pid, name->name); + return 0; +} + +SEC("fexit/do_unlinkat") +int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret) +{ + pid_t pid; + + pid = bpf_get_current_pid_tgid() >> 32; + bpf_printk("fexit: pid = %d, filename = %s, ret = %ld\n", pid, name->name, ret); + return 0; +} diff --git a/21-llcstat/llcstat.md b/21-llcstat/llcstat.md new file mode 100644 index 0000000..f9f887b --- /dev/null +++ b/21-llcstat/llcstat.md @@ -0,0 +1,75 @@ +## eBPF 入门实践教程:编写 eBPF 程序 llcstat 监控 cache miss 和 cache reference + +### 背景 + +为了能更好地优化程序性能,开发者有时需要考虑如何更好地减少cache miss的发生。 +但是程序到底可能发生多少次cache miss这是一个难以回答的问题。`llcstat` 通过 +ebpf技术,实现了对 cache miss 和 cache reference 的准确追踪,可以极大方便开发者 +调试程序,优化性能。 + +### 实现原理 + +`llcstat` 引入了linux中的 `perf_event` 机制,程序在用户态载入的时候, +会将现有的c `perf_event` attach到指定的位置。 +```c + if (open_and_attach_perf_event(PERF_COUNT_HW_CACHE_MISSES, + env.sample_period, + obj->progs.on_cache_miss, mlinks)) + goto cleanup; + if (open_and_attach_perf_event(PERF_COUNT_HW_CACHE_REFERENCES, + env.sample_period, + obj->progs.on_cache_ref, rlinks)) +``` + +同时,`llcstat` 在内核态中会在`perf_event`下挂载执行函数,当程序运行到了 +挂载点,执行函数会启动并开始计数,将结果写入对应的map中。 + +```c +static __always_inline +int trace_event(__u64 sample_period, bool miss) +{ + struct key_info key = {}; + struct value_info *infop, zero = {}; + + u64 pid_tgid = bpf_get_current_pid_tgid(); + key.cpu = bpf_get_smp_processor_id(); + key.pid = pid_tgid >> 32; + if (targ_per_thread) + key.tid = (u32)pid_tgid; + else + key.tid = key.pid; + + infop = bpf_map_lookup_or_try_init(&infos, &key, &zero); + if (!infop) + return 0; + if (miss) + infop->miss += sample_period; + else + infop->ref += sample_period; + bpf_get_current_comm(infop->comm, sizeof(infop->comm)); + + return 0; +} + +SEC("perf_event") +int on_cache_miss(struct bpf_perf_event_data *ctx) +{ + return trace_event(ctx->sample_period, true); +} + +SEC("perf_event") +int on_cache_ref(struct bpf_perf_event_data *ctx) +{ + return trace_event(ctx->sample_period, false); +} +``` + +用户态程序会读取map存入的 cache miss 和 cache reference 的计数信息,并 +逐进程的进行展示。 + +### Eunomia中使用方式 + + +### 总结 +`llcstat` 运用了ebpf计数,高效简洁地展示了某个线程发生cache miss和cache +reference的次数,这使得开发者们在优化程序的过程中有了更明确的量化指标。 diff --git a/3-kprobe-unlink/.gitignore b/3-kprobe-unlink/.gitignore new file mode 100644 index 0000000..7d5aebf --- /dev/null +++ b/3-kprobe-unlink/.gitignore @@ -0,0 +1,6 @@ +.vscode +package.json +*.o +*.skel.json +*.skel.yaml +package.yaml diff --git a/3-kprobe-unlink/README.md b/3-kprobe-unlink/README.md new file mode 100644 index 0000000..df7a415 --- /dev/null +++ b/3-kprobe-unlink/README.md @@ -0,0 +1,55 @@ +--- +layout: post +title: kprobe-link +date: 2022-10-10 16:18 +category: bpftools +author: yunwei37 +tags: [bpftools, examples, kprobe, no-output] +summary: an example of dealing with kernel-space entry and exit (return) probes, `kprobe` and `kretprobe` in libbpf lingo +--- + + +`kprobe` is an example of dealing with kernel-space entry and exit (return) +probes, `kprobe` and `kretprobe` in libbpf lingo. It attaches `kprobe` and +`kretprobe` BPF programs to the `do_unlinkat()` function and logs the PID, +filename, and return result, respectively, using `bpf_printk()` macro. + +```console +$ sudo ecli examples/bpftools/kprobe-link/package.json +Runing eBPF program... +``` + +The `kprobe` demo output in `/sys/kernel/debug/tracing/trace_pipe` should look +something like this: + +```shell +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + rm-9346 [005] d..3 4710.951696: bpf_trace_printk: KPROBE ENTRY pid = 9346, filename = test1 + rm-9346 [005] d..4 4710.951819: bpf_trace_printk: KPROBE EXIT: ret = 0 + rm-9346 [005] d..3 4710.951852: bpf_trace_printk: KPROBE ENTRY pid = 9346, filename = test2 + rm-9346 [005] d..4 4710.951895: bpf_trace_printk: KPROBE EXIT: ret = 0 +``` + +## Run + + + +Compile with docker: + +```console +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +or compile with `ecc`: + +```console +$ ecc kprobe-link.bpf.c +Compiling bpf object... +Packing ebpf object and config into package.json... +``` + +Run: + +```console +sudo ecli examples/bpftools/kprobe-link/package.json +``` \ No newline at end of file diff --git a/3-kprobe-unlink/kprobe-link.bpf.c b/3-kprobe-unlink/kprobe-link.bpf.c new file mode 100644 index 0000000..e1dc288 --- /dev/null +++ b/3-kprobe-unlink/kprobe-link.bpf.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* Copyright (c) 2021 Sartura */ +#include "vmlinux.h" +#include +#include +#include + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; + +SEC("kprobe/do_unlinkat") +int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name) +{ + pid_t pid; + const char *filename; + + pid = bpf_get_current_pid_tgid() >> 32; + filename = BPF_CORE_READ(name, name); + bpf_printk("KPROBE ENTRY pid = %d, filename = %s\n", pid, filename); + return 0; +} + +SEC("kretprobe/do_unlinkat") +int BPF_KRETPROBE(do_unlinkat_exit, long ret) +{ + pid_t pid; + + pid = bpf_get_current_pid_tgid() >> 32; + bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret); + return 0; +} \ No newline at end of file diff --git a/4-opensnoop/.gitignore b/4-opensnoop/.gitignore new file mode 100644 index 0000000..b669b39 --- /dev/null +++ b/4-opensnoop/.gitignore @@ -0,0 +1,7 @@ +.vscode +package.json +eunomia-exporter +ecli +*.bpf.o +*.skel.json +*.skel.yaml diff --git a/4-opensnoop/1_opensnoop.md b/4-opensnoop/1_opensnoop.md new file mode 100644 index 0000000..fdc82da --- /dev/null +++ b/4-opensnoop/1_opensnoop.md @@ -0,0 +1,263 @@ +## eBPF 入门实践教程:编写 eBPF 程序监控打开文件路径并使用 Prometheus 可视化 + +### 背景 + +通过对 open 系统调用的监测,`opensnoop`可以展现系统内所有调用了 open 系统调用的进程信息。 + +### 使用 ecli 一键运行 + +```console +$ # 下载安装 ecli 二进制 +$ wget https://aka.pw/bpf-ecli -O ./ecli && chmod +x ./ecli +$ # 使用 url 一键运行 +$ ./ecli run https://eunomia-bpf.github.io/eunomia-bpf/opensnoop/package.json + +running and waiting for the ebpf events from perf event... +time ts pid uid ret flags comm fname +00:58:08 0 812 0 9 524288 vmtoolsd /etc/mtab +00:58:08 0 812 0 11 0 vmtoolsd /proc/devices +00:58:08 0 34351 0 24 524288 ecli /etc/localtime +00:58:08 0 812 0 9 0 vmtoolsd /sys/class/block/sda5/../device/../../../class +00:58:08 0 812 0 -2 0 vmtoolsd /sys/class/block/sda5/../device/../../../label +00:58:08 0 812 0 9 0 vmtoolsd /sys/class/block/sda1/../device/../../../class +00:58:08 0 812 0 -2 0 vmtoolsd /sys/class/block/sda1/../device/../../../label +00:58:08 0 812 0 9 0 vmtoolsd /run/systemd/resolve/resolv.conf +00:58:08 0 812 0 9 0 vmtoolsd /proc/net/route +00:58:08 0 812 0 9 0 vmtoolsd /proc/net/ipv6_route +``` + +### 实现 + +使用 eunomia-bpf 可以帮助你只需要编写内核态应用程序,不需要编写任何用户态辅助框架代码;需要编写的代码由两个部分组成: + +- 头文件 opensnoop.h 里面定义需要导出的 C 语言结构体: +- 源文件 opensnoop.bpf.c 里面定义 BPF 代码: + +头文件 opensnoop.h + +```c +/* 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 */ +``` + +`opensnoop` 的实现逻辑比较简单,它在 `sys_enter_open` 和 `sys_enter_openat` 这两个追踪点下 +加了执行函数,当有 open 系统调用发生时,执行函数便会被触发。同样在,在对应的 `sys_exit_open` 和 +`sys_exit_openat` 系统调用下,`opensnoop` 也加了执行函数。 + +源文件 opensnoop.bpf.c + +```c +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Facebook +// Copyright (c) 2020 Netflix +#include +#include +#include "opensnoop.h" + +struct args_t { + const char *fname; + int flags; +}; + +const volatile pid_t targ_pid = 0; +const volatile pid_t targ_tgid = 0; +const volatile uid_t targ_uid = 0; +const volatile bool targ_failed = false; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10240); + __type(key, u32); + __type(value, struct args_t); +} start SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(u32)); + __uint(value_size, sizeof(u32)); +} events SEC(".maps"); + +static __always_inline bool valid_uid(uid_t uid) { + return uid != INVALID_UID; +} + +static __always_inline +bool trace_allowed(u32 tgid, u32 pid) +{ + u32 uid; + + /* filters */ + if (targ_tgid && targ_tgid != tgid) + return false; + if (targ_pid && targ_pid != pid) + return false; + if (valid_uid(targ_uid)) { + uid = (u32)bpf_get_current_uid_gid(); + if (targ_uid != uid) { + return false; + } + } + return true; +} + +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(); + /* use kernel terminology here for tgid/pid: */ + u32 tgid = id >> 32; + u32 pid = id; + + /* store arg info for later lookup */ + if (trace_allowed(tgid, pid)) { + struct args_t args = {}; + args.fname = (const char *)ctx->args[0]; + args.flags = (int)ctx->args[1]; + bpf_map_update_elem(&start, &pid, &args, 0); + } + 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(); + /* use kernel terminology here for tgid/pid: */ + u32 tgid = id >> 32; + u32 pid = id; + + /* store arg info for later lookup */ + if (trace_allowed(tgid, pid)) { + struct args_t args = {}; + args.fname = (const char *)ctx->args[1]; + args.flags = (int)ctx->args[2]; + bpf_map_update_elem(&start, &pid, &args, 0); + } + return 0; +} + +static __always_inline +int trace_exit(struct trace_event_raw_sys_exit* ctx) +{ + struct event event = {}; + struct args_t *ap; + int ret; + u32 pid = bpf_get_current_pid_tgid(); + + ap = bpf_map_lookup_elem(&start, &pid); + if (!ap) + return 0; /* missed entry */ + ret = ctx->ret; + if (targ_failed && ret >= 0) + goto cleanup; /* want failed only */ + + /* event data */ + event.pid = bpf_get_current_pid_tgid() >> 32; + event.uid = bpf_get_current_uid_gid(); + bpf_get_current_comm(&event.comm, sizeof(event.comm)); + bpf_probe_read_user_str(&event.fname, sizeof(event.fname), ap->fname); + event.flags = ap->flags; + event.ret = ret; + + /* emit event */ + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, + &event, sizeof(event)); + +cleanup: + bpf_map_delete_elem(&start, &pid); + return 0; +} + +SEC("tracepoint/syscalls/sys_exit_open") +int tracepoint__syscalls__sys_exit_open(struct trace_event_raw_sys_exit* ctx) +{ + return trace_exit(ctx); +} + +SEC("tracepoint/syscalls/sys_exit_openat") +int tracepoint__syscalls__sys_exit_openat(struct trace_event_raw_sys_exit* ctx) +{ + return trace_exit(ctx); +} + +char LICENSE[] SEC("license") = "GPL"; +``` + +在 enter 环节,`opensnoop` 会记录调用者的 pid, comm 等基本信息,并存入 map 中。在 exit 环节,`opensnoop` +会根据 pid 读出之前存入的数据,再结合捕获的其他数据,输出到用户态处理函数中,展现给用户。 + +完整示例代码请参考:https://github.com/eunomia-bpf/eunomia-bpf/tree/master/examples/bpftools/opensnoop + +把头文件和源文件放在独立的目录里面,编译运行: + +```bash +$ # 使用容器进行编译,生成一个 package.json 文件,里面是已经编译好的代码和一些辅助信息 +$ docker run -it -v /path/to/opensnoop:/src yunwei37/ebpm:latest +$ # 运行 eBPF 程序(root shell) +$ sudo ecli run package.json +``` + +### Prometheus 可视化 + +编写 yaml 配置文件: + +```yaml +programs: + - name: opensnoop + metrics: + counters: + - name: eunomia_file_open_counter + description: test + labels: + - name: pid + - name: comm + - name: filename + from: fname + compiled_ebpf_filename: package.json +``` + +使用 eunomia-exporter 实现导出信息到 Prometheus: + +- 通过 https://github.com/eunomia-bpf/eunomia-bpf/releases 下载 eunomia-exporter + +```console +$ ls +config.yaml eunomia-exporter package.json +$ sudo ./eunomia-exporter + +Running ebpf program opensnoop takes 46 ms +Listening on http://127.0.0.1:8526 +running and waiting for the ebpf events from perf event... +Receiving request at path /metrics +``` + +![result](../img/opensnoop_prometheus.png) + +### 总结和参考资料 + +`opensnoop` 通过对 open 系统调用的追踪,使得用户可以较为方便地掌握目前系统中调用了 open 系统调用的进程信息。 + +参考资料: + +- 源代码:https://github.com/eunomia-bpf/eunomia-bpf/tree/master/examples/bpftools/opensnoop +- libbpf 参考代码:https://github.com/iovisor/bcc/blob/master/libbpf-tools/opensnoop.bpf.c +- eunomia-bpf 手册:https://eunomia-bpf.github.io/ diff --git a/4-opensnoop/README.md b/4-opensnoop/README.md new file mode 100644 index 0000000..6954b29 --- /dev/null +++ b/4-opensnoop/README.md @@ -0,0 +1,281 @@ +--- +layout: post +title: opensnoop +date: 2022-10-10 16:18 +category: bpftools +author: yunwei37 +tags: [bpftools, syscall] +summary: opensnoop traces the open() syscall system-wide, and prints various details. +--- + +## origin + +The kernel code is origin from: + + + +result: + +```console +$ sudo ecli examples/bpftools/opensnoop/package.json -h +Usage: opensnoop_bpf [--help] [--version] [--verbose] [--pid_target VAR] [--tgid_target VAR] [--uid_target VAR] [--failed] + +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 + --tgid_target Thread ID to trace + --uid_target User ID to trace + -f, --failed trace only failed events + +Built with eunomia-bpf framework. +See https://github.com/eunomia-bpf/eunomia-bpf for more information. + +$ sudo ecli examples/bpftools/opensnoop/package.json +TIME TS PID UID RET FLAGS COMM FNAME +20:31:50 0 1 0 51 524288 systemd /proc/614/cgroup +20:31:50 0 33182 0 25 524288 ecli /etc/localtime +20:31:53 0 754 0 6 0 irqbalance /proc/interrupts +20:31:53 0 754 0 6 0 irqbalance /proc/stat +20:32:03 0 754 0 6 0 irqbalance /proc/interrupts +20:32:03 0 754 0 6 0 irqbalance /proc/stat +20:32:03 0 632 0 7 524288 vmtoolsd /etc/mtab +20:32:03 0 632 0 9 0 vmtoolsd /proc/devices + +$ sudo ecli examples/bpftools/opensnoop/package.json --pid_target 754 +TIME TS PID UID RET FLAGS COMM FNAME +20:34:13 0 754 0 6 0 irqbalance /proc/interrupts +20:34:13 0 754 0 6 0 irqbalance /proc/stat +20:34:23 0 754 0 6 0 irqbalance /proc/interrupts +20:34:23 0 754 0 6 0 irqbalance /proc/stat +``` + +## Compile and Run + +Compile with docker: + +```shell +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +or compile with `ecc`: + +```console +$ ecc opensnoop.bpf.c opensnoop.h +Compiling bpf object... +Generating export types... +Packing ebpf object and config into package.json... +``` + +Run: + +```shell +sudo ./ecli run examples/bpftools/opensnoop/package.json +``` + +## details in bcc + +Demonstrations of opensnoop, the Linux eBPF/bcc version. + +opensnoop traces the open() syscall system-wide, and prints various details. +Example output: + +```console +# ./opensnoop +PID COMM FD ERR PATH +17326 <...> 7 0 /sys/kernel/debug/tracing/trace_pipe +1576 snmpd 9 0 /proc/net/dev +1576 snmpd 11 0 /proc/net/if_inet6 +1576 snmpd 11 0 /proc/sys/net/ipv4/neigh/eth0/retrans_time_ms +1576 snmpd 11 0 /proc/sys/net/ipv6/neigh/eth0/retrans_time_ms +1576 snmpd 11 0 /proc/sys/net/ipv6/conf/eth0/forwarding +1576 snmpd 11 0 /proc/sys/net/ipv6/neigh/eth0/base_reachable_time_ms +1576 snmpd 11 0 /proc/sys/net/ipv4/neigh/lo/retrans_time_ms +1576 snmpd 11 0 /proc/sys/net/ipv6/neigh/lo/retrans_time_ms +1576 snmpd 11 0 /proc/sys/net/ipv6/conf/lo/forwarding +1576 snmpd 11 0 /proc/sys/net/ipv6/neigh/lo/base_reachable_time_ms +1576 snmpd 9 0 /proc/diskstats +1576 snmpd 9 0 /proc/stat +1576 snmpd 9 0 /proc/vmstat +1956 supervise 9 0 supervise/status.new +1956 supervise 9 0 supervise/status.new +17358 run 3 0 /etc/ld.so.cache +17358 run 3 0 /lib/x86_64-linux-gnu/libtinfo.so.5 +17358 run 3 0 /lib/x86_64-linux-gnu/libdl.so.2 +17358 run 3 0 /lib/x86_64-linux-gnu/libc.so.6 +17358 run -1 6 /dev/tty +17358 run 3 0 /proc/meminfo +17358 run 3 0 /etc/nsswitch.conf +17358 run 3 0 /etc/ld.so.cache +17358 run 3 0 /lib/x86_64-linux-gnu/libnss_compat.so.2 +17358 run 3 0 /lib/x86_64-linux-gnu/libnsl.so.1 +17358 run 3 0 /etc/ld.so.cache +17358 run 3 0 /lib/x86_64-linux-gnu/libnss_nis.so.2 +17358 run 3 0 /lib/x86_64-linux-gnu/libnss_files.so.2 +17358 run 3 0 /etc/passwd +17358 run 3 0 ./run +^C +`` +While tracing, the snmpd process opened various /proc files (reading metrics), +and a "run" process read various libraries and config files (looks like it +was starting up: a new process). + +opensnoop can be useful for discovering configuration and log files, if used +during application startup. + +```console +The -p option can be used to filter on a PID, which is filtered in-kernel. Here +I've used it with -T to print timestamps: + + ./opensnoop -Tp 1956 +TIME(s) PID COMM FD ERR PATH +0.000000000 1956 supervise 9 0 supervise/status.new +0.000289999 1956 supervise 9 0 supervise/status.new +1.023068000 1956 supervise 9 0 supervise/status.new +1.023381997 1956 supervise 9 0 supervise/status.new +2.046030000 1956 supervise 9 0 supervise/status.new +2.046363000 1956 supervise 9 0 supervise/status.new +3.068203997 1956 supervise 9 0 supervise/status.new +3.068544999 1956 supervise 9 0 supervise/status.new +``` + +This shows the supervise process is opening the status.new file twice every +second. + +The -U option include UID on output: + +```console +# ./opensnoop -U +UID PID COMM FD ERR PATH +0 27063 vminfo 5 0 /var/run/utmp +103 628 dbus-daemon -1 2 /usr/local/share/dbus-1/system-services +103 628 dbus-daemon 18 0 /usr/share/dbus-1/system-services +103 628 dbus-daemon -1 2 /lib/dbus-1/system-services +``` + +The -u option filtering UID: + +```console +# ./opensnoop -Uu 1000 +UID PID COMM FD ERR PATH +1000 30240 ls 3 0 /etc/ld.so.cache +1000 30240 ls 3 0 /lib/x86_64-linux-gnu/libselinux.so.1 +1000 30240 ls 3 0 /lib/x86_64-linux-gnu/libc.so.6 +1000 30240 ls 3 0 /lib/x86_64-linux-gnu/libpcre.so.3 +1000 30240 ls 3 0 /lib/x86_64-linux-gnu/libdl.so.2 +1000 30240 ls 3 0 /lib/x86_64-linux-gnu/libpthread.so.0 +``` + +The -x option only prints failed opens: + +```console +# ./opensnoop -x +PID COMM FD ERR PATH +18372 run -1 6 /dev/tty +18373 run -1 6 /dev/tty +18373 multilog -1 13 lock +18372 multilog -1 13 lock +18384 df -1 2 /usr/share/locale/en_US.UTF-8/LC_MESSAGES/coreutils.mo +18384 df -1 2 /usr/share/locale/en_US.utf8/LC_MESSAGES/coreutils.mo +18384 df -1 2 /usr/share/locale/en_US/LC_MESSAGES/coreutils.mo +18384 df -1 2 /usr/share/locale/en.UTF-8/LC_MESSAGES/coreutils.mo +18384 df -1 2 /usr/share/locale/en.utf8/LC_MESSAGES/coreutils.mo +18384 df -1 2 /usr/share/locale/en/LC_MESSAGES/coreutils.mo +18385 run -1 6 /dev/tty +18386 run -1 6 /dev/tty +``` + +This caught a df command failing to open a coreutils.mo file, and trying from +different directories. + +The ERR column is the system error number. Error number 2 is ENOENT: no such +file or directory. + +A maximum tracing duration can be set with the -d option. For example, to trace +for 2 seconds: + +```console +# ./opensnoop -d 2 +PID COMM FD ERR PATH +2191 indicator-multi 11 0 /sys/block +2191 indicator-multi 11 0 /sys/block +2191 indicator-multi 11 0 /sys/block +2191 indicator-multi 11 0 /sys/block +2191 indicator-multi 11 0 /sys/block + +``` + +The -n option can be used to filter on process name using partial matches: + +```console +# ./opensnoop -n ed + +PID COMM FD ERR PATH +2679 sed 3 0 /etc/ld.so.cache +2679 sed 3 0 /lib/x86_64-linux-gnu/libselinux.so.1 +2679 sed 3 0 /lib/x86_64-linux-gnu/libc.so.6 +2679 sed 3 0 /lib/x86_64-linux-gnu/libpcre.so.3 +2679 sed 3 0 /lib/x86_64-linux-gnu/libdl.so.2 +2679 sed 3 0 /lib/x86_64-linux-gnu/libpthread.so.0 +2679 sed 3 0 /proc/filesystems +2679 sed 3 0 /usr/lib/locale/locale-archive +2679 sed -1 2 +2679 sed 3 0 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache +2679 sed 3 0 /dev/null +2680 sed 3 0 /etc/ld.so.cache +2680 sed 3 0 /lib/x86_64-linux-gnu/libselinux.so.1 +2680 sed 3 0 /lib/x86_64-linux-gnu/libc.so.6 +2680 sed 3 0 /lib/x86_64-linux-gnu/libpcre.so.3 +2680 sed 3 0 /lib/x86_64-linux-gnu/libdl.so.2 +2680 sed 3 0 /lib/x86_64-linux-gnu/libpthread.so.0 +2680 sed 3 0 /proc/filesystems +2680 sed 3 0 /usr/lib/locale/locale-archive +2680 sed -1 2 +^C +``` + +This caught the 'sed' command because it partially matches 'ed' that's passed +to the '-n' option. + +The -e option prints out extra columns; for example, the following output +contains the flags passed to open(2), in octal: + +```console +# ./opensnoop -e +PID COMM FD ERR FLAGS PATH +28512 sshd 10 0 00101101 /proc/self/oom_score_adj +28512 sshd 3 0 02100000 /etc/ld.so.cache +28512 sshd 3 0 02100000 /lib/x86_64-linux-gnu/libwrap.so.0 +28512 sshd 3 0 02100000 /lib/x86_64-linux-gnu/libaudit.so.1 +28512 sshd 3 0 02100000 /lib/x86_64-linux-gnu/libpam.so.0 +28512 sshd 3 0 02100000 /lib/x86_64-linux-gnu/libselinux.so.1 +28512 sshd 3 0 02100000 /lib/x86_64-linux-gnu/libsystemd.so.0 +28512 sshd 3 0 02100000 /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.2 +28512 sshd 3 0 02100000 /lib/x86_64-linux-gnu/libutil.so.1 +``` + +The -f option filters based on flags to the open(2) call, for example: + +```console +# ./opensnoop -e -f O_WRONLY -f O_RDWR +PID COMM FD ERR FLAGS PATH +28084 clear_console 3 0 00100002 /dev/tty +28084 clear_console -1 13 00100002 /dev/tty0 +28084 clear_console -1 13 00100001 /dev/tty0 +28084 clear_console -1 13 00100002 /dev/console +28084 clear_console -1 13 00100001 /dev/console +28051 sshd 8 0 02100002 /var/run/utmp +28051 sshd 7 0 00100001 /var/log/wtmp +``` + +The --cgroupmap option filters based on a cgroup set. It is meant to be used +with an externally created map. + +```console +# ./opensnoop --cgroupmap /sys/fs/bpf/test01 +``` + +For more details, see docs/special_filtering.md diff --git a/4-opensnoop/config.yaml b/4-opensnoop/config.yaml new file mode 100644 index 0000000..0a859ba --- /dev/null +++ b/4-opensnoop/config.yaml @@ -0,0 +1,12 @@ +programs: +- name: opensnoop + metrics: + counters: + - name: eunomia_file_open_counter + description: test + labels: + - name: pid + - name: comm + - name: filename + from: fname + compiled_ebpf_filename: package.json diff --git a/4-opensnoop/opensnoop.bpf.c b/4-opensnoop/opensnoop.bpf.c new file mode 100644 index 0000000..597c760 --- /dev/null +++ b/4-opensnoop/opensnoop.bpf.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Facebook +// Copyright (c) 2020 Netflix +#include +#include +#include "opensnoop.h" + +struct args_t { + const char *fname; + int flags; +}; + +/// Process ID to trace +const volatile int pid_target = 0; +/// Thread ID to trace +const volatile int tgid_target = 0; +/// @description User ID to trace +const volatile int uid_target = 0; +/// @cmdarg {"default": false, "short": "f", "long": "failed"} +/// @description trace only failed events +const volatile bool targ_failed = false; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10240); + __type(key, u32); + __type(value, struct args_t); +} start SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(u32)); + __uint(value_size, sizeof(u32)); +} events SEC(".maps"); + +static __always_inline bool valid_uid(uid_t uid) { + return uid != INVALID_UID; +} + +static __always_inline +bool trace_allowed(u32 tgid, u32 pid) +{ + u32 uid; + + /* filters */ + if (tgid_target && tgid_target != tgid) + return false; + if (pid_target && pid_target != pid) + return false; + if (valid_uid(uid_target)) { + uid = (u32)bpf_get_current_uid_gid(); + if (uid_target != uid) { + return false; + } + } + return true; +} + +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(); + /* use kernel terminology here for tgid/pid: */ + u32 tgid = id >> 32; + u32 pid = id; + + /* store arg info for later lookup */ + if (trace_allowed(tgid, pid)) { + struct args_t args = {}; + args.fname = (const char *)ctx->args[0]; + args.flags = (int)ctx->args[1]; + bpf_map_update_elem(&start, &pid, &args, 0); + } + 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(); + /* use kernel terminology here for tgid/pid: */ + u32 tgid = id >> 32; + u32 pid = id; + + /* store arg info for later lookup */ + if (trace_allowed(tgid, pid)) { + struct args_t args = {}; + args.fname = (const char *)ctx->args[1]; + args.flags = (int)ctx->args[2]; + bpf_map_update_elem(&start, &pid, &args, 0); + } + return 0; +} + +static __always_inline +int trace_exit(struct trace_event_raw_sys_exit* ctx) +{ + struct event event = {}; + struct args_t *ap; + int ret; + u32 pid = bpf_get_current_pid_tgid(); + + ap = bpf_map_lookup_elem(&start, &pid); + if (!ap) + return 0; /* missed entry */ + ret = ctx->ret; + if (targ_failed && ret >= 0) + goto cleanup; /* want failed only */ + + /* event data */ + event.pid = bpf_get_current_pid_tgid() >> 32; + event.uid = bpf_get_current_uid_gid(); + bpf_get_current_comm(&event.comm, sizeof(event.comm)); + bpf_probe_read_user_str(&event.fname, sizeof(event.fname), ap->fname); + event.flags = ap->flags; + event.ret = ret; + + /* emit event */ + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, + &event, sizeof(event)); + +cleanup: + bpf_map_delete_elem(&start, &pid); + return 0; +} + +SEC("tracepoint/syscalls/sys_exit_open") +int tracepoint__syscalls__sys_exit_open(struct trace_event_raw_sys_exit* ctx) +{ + return trace_exit(ctx); +} + +SEC("tracepoint/syscalls/sys_exit_openat") +int tracepoint__syscalls__sys_exit_openat(struct trace_event_raw_sys_exit* ctx) +{ + return trace_exit(ctx); +} + +/// Trace open family syscalls. +char LICENSE[] SEC("license") = "GPL"; diff --git a/4-opensnoop/opensnoop.h b/4-opensnoop/opensnoop.h new file mode 100644 index 0000000..a5aa43f --- /dev/null +++ b/4-opensnoop/opensnoop.h @@ -0,0 +1,21 @@ +/* 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/.gitignore b/5-uprobe-bashreadline/.gitignore new file mode 100644 index 0000000..a857114 --- /dev/null +++ b/5-uprobe-bashreadline/.gitignore @@ -0,0 +1,7 @@ +.vscode +package.json +ecli +*.o +*.skel.json +*.skel.yaml +package.yaml \ No newline at end of file diff --git a/5-uprobe-bashreadline/README.md b/5-uprobe-bashreadline/README.md new file mode 100644 index 0000000..f81050d --- /dev/null +++ b/5-uprobe-bashreadline/README.md @@ -0,0 +1,79 @@ +--- +layout: post +title: bootstrap +date: 2022-10-10 16:18 +category: bpftools +author: yunwei37 +tags: [bpftools, examples, uprobe, perf event] +summary: an example of a simple (but realistic) BPF application prints bash commands from all running bash shells on the system. +--- + + + +This prints bash commands from all running bash shells on the system. + +## System requirements: + +- Linux kernel > 5.5 +- Eunomia's [ecli](https://github.com/eunomia-bpf/eunomia-bpf/tree/master/ecli) installed + + +## Run + +- Compile: + + ```shell + docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest + ``` + + or + + ```shell + ecc bashreadline.bpf.c bashreadline.h + ``` + +- Run: + + ```console + $ sudo ./ecli run eunomia-bpf/examples/bpftools/bootstrap/package.json + TIME PID STR + 11:17:34 28796 whoami + 11:17:41 28796 ps -ef + 11:17:51 28796 echo "Hello eBPF!" + ``` + +## details in bcc + + +``` +Demonstrations of bashreadline, the Linux eBPF/bcc version. + +This prints bash commands from all running bash shells on the system. For +example: + +# ./bashreadline +TIME PID COMMAND +05:28:25 21176 ls -l +05:28:28 21176 date +05:28:35 21176 echo hello world +05:28:43 21176 foo this command failed +05:28:45 21176 df -h +05:29:04 3059 echo another shell +05:29:13 21176 echo first shell again + +When running the script on Arch Linux, you may need to specify the location +of libreadline.so library: + +# ./bashreadline -s /lib/libreadline.so +TIME PID COMMAND +11:17:34 28796 whoami +11:17:41 28796 ps -ef +11:17:51 28796 echo "Hello eBPF!" + + +The entered command may fail. This is just showing what command lines were +entered interactively for bash to process. + +It works by tracing the return of the readline() function using uprobes +(specifically a uretprobe). +``` \ No newline at end of file diff --git a/5-uprobe-bashreadline/bashreadline.bpf.c b/5-uprobe-bashreadline/bashreadline.bpf.c new file mode 100644 index 0000000..0464cd4 --- /dev/null +++ b/5-uprobe-bashreadline/bashreadline.bpf.c @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2021 Facebook */ +#include +#include +#include +#include "bashreadline.h" + +#define TASK_COMM_LEN 16 + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} events SEC(".maps"); + +/* Format of u[ret]probe section definition supporting auto-attach: + * u[ret]probe/binary:function[+offset] + * + * binary can be an absolute/relative path or a filename; the latter is resolved to a + * full binary path via bpf_program__attach_uprobe_opts. + * + * Specifying uprobe+ ensures we carry out strict matching; either "uprobe" must be + * 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) { + struct str_t data; + char comm[TASK_COMM_LEN]; + u32 pid; + + if (!ret) + return 0; + + bpf_get_current_comm(&comm, sizeof(comm)); + if (comm[0] != 'b' || comm[1] != 'a' || comm[2] != 's' || comm[3] != 'h' || comm[4] != 0 ) + return 0; + + pid = bpf_get_current_pid_tgid() >> 32; + data.pid = pid; + bpf_probe_read_user_str(&data.str, sizeof(data.str), ret); + + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data)); + + return 0; +}; + +char LICENSE[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/5-uprobe-bashreadline/bashreadline.h b/5-uprobe-bashreadline/bashreadline.h new file mode 100644 index 0000000..9348347 --- /dev/null +++ b/5-uprobe-bashreadline/bashreadline.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* Copyright (c) 2021 Facebook */ +#ifndef __BASHREADLINE_H +#define __BASHREADLINE_H + +#define MAX_LINE_SIZE 80 + +struct str_t { + __u32 pid; + char str[MAX_LINE_SIZE]; +}; + +#endif /* __BASHREADLINE_H */ \ No newline at end of file diff --git a/6-sigsnoop/.gitignore b/6-sigsnoop/.gitignore new file mode 100755 index 0000000..bbee7c8 --- /dev/null +++ b/6-sigsnoop/.gitignore @@ -0,0 +1,10 @@ +.vscode +package.json +*.wasm +ewasm-skel.h +ecli +ewasm +*.o +*.skel.json +*.skel.yaml +package.yaml diff --git a/6-sigsnoop/README.md b/6-sigsnoop/README.md new file mode 100755 index 0000000..185aab0 --- /dev/null +++ b/6-sigsnoop/README.md @@ -0,0 +1,155 @@ +--- +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. +--- + + +## origin + +origin from: + +https://github.com/iovisor/bcc/blob/master/libbpf-tools/sigsnoop.bpf.c + +## Compile and Run + +Compile: + +```shell +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +Or compile with `ecc`: + +```console +$ ecc sigsnoop.bpf.c sigsnoop.h +Compiling bpf object... +Generating export types... +Packing ebpf object and config into package.json... +``` + +Run: + +```console +$ sudo ./ecli examples/bpftools/sigsnoop/package.json +TIME PID TPID SIG RET COMM +20:43:44 21276 3054 0 0 cpptools-srv +20:43:44 22407 3054 0 0 cpptools-srv +20:43:44 20222 3054 0 0 cpptools-srv +20:43:44 8933 3054 0 0 cpptools-srv +20:43:44 2915 2803 0 0 node +20:43:44 2943 2803 0 0 node +20:43:44 31453 3054 0 0 cpptools-srv +$ sudo ./ecli examples/bpftools/sigsnoop/package.json -h +Usage: sigsnoop_bpf [--help] [--version] [--verbose] [--filtered_pid VAR] [--target_signal VAR] [--failed_only] + +A simple eBPF program + +Optional arguments: + -h, --help shows help message and exits + -v, --version prints version information and exits + --verbose prints libbpf debug information + --filtered_pid set value of pid_t variable filtered_pid + --target_signal set value of int variable target_signal + --failed_only set value of bool variable failed_only + +Built with eunomia-bpf framework. +See https://github.com/eunomia-bpf/eunomia-bpf for more information. +``` + +## 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= target pid + -s, --signal= 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. \ No newline at end of file diff --git a/6-sigsnoop/app.c b/6-sigsnoop/app.c new file mode 100755 index 0000000..adb0ba2 --- /dev/null +++ b/6-sigsnoop/app.c @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/6-sigsnoop/eunomia-include/argp-namefrob.h b/6-sigsnoop/eunomia-include/argp-namefrob.h new file mode 100644 index 0000000..0ce1148 --- /dev/null +++ b/6-sigsnoop/eunomia-include/argp-namefrob.h @@ -0,0 +1,96 @@ +/* Name frobnication for compiling argp outside of glibc + Copyright (C) 1997 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Written by Miles Bader . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#if !_LIBC +/* This code is written for inclusion in gnu-libc, and uses names in the + namespace reserved for libc. If we're not compiling in libc, define those + names to be the normal ones instead. */ + +/* argp-parse functions */ +#undef __argp_parse +#define __argp_parse argp_parse +#undef __option_is_end +#define __option_is_end _option_is_end +#undef __option_is_short +#define __option_is_short _option_is_short +#undef __argp_input +#define __argp_input _argp_input + +/* argp-help functions */ +#undef __argp_help +#define __argp_help argp_help +#undef __argp_error +#define __argp_error argp_error +#undef __argp_failure +#define __argp_failure argp_failure +#undef __argp_state_help +#define __argp_state_help argp_state_help +#undef __argp_usage +#define __argp_usage argp_usage +#undef __argp_basename +#define __argp_basename _argp_basename +#undef __argp_short_program_name +#define __argp_short_program_name _argp_short_program_name + +/* argp-fmtstream functions */ +#undef __argp_make_fmtstream +#define __argp_make_fmtstream argp_make_fmtstream +#undef __argp_fmtstream_free +#define __argp_fmtstream_free argp_fmtstream_free +#undef __argp_fmtstream_putc +#define __argp_fmtstream_putc argp_fmtstream_putc +#undef __argp_fmtstream_puts +#define __argp_fmtstream_puts argp_fmtstream_puts +#undef __argp_fmtstream_write +#define __argp_fmtstream_write argp_fmtstream_write +#undef __argp_fmtstream_printf +#define __argp_fmtstream_printf argp_fmtstream_printf +#undef __argp_fmtstream_set_lmargin +#define __argp_fmtstream_set_lmargin argp_fmtstream_set_lmargin +#undef __argp_fmtstream_set_rmargin +#define __argp_fmtstream_set_rmargin argp_fmtstream_set_rmargin +#undef __argp_fmtstream_set_wmargin +#define __argp_fmtstream_set_wmargin argp_fmtstream_set_wmargin +#undef __argp_fmtstream_point +#define __argp_fmtstream_point argp_fmtstream_point +#undef __argp_fmtstream_update +#define __argp_fmtstream_update _argp_fmtstream_update +#undef __argp_fmtstream_ensure +#define __argp_fmtstream_ensure _argp_fmtstream_ensure +#undef __argp_fmtstream_lmargin +#define __argp_fmtstream_lmargin argp_fmtstream_lmargin +#undef __argp_fmtstream_rmargin +#define __argp_fmtstream_rmargin argp_fmtstream_rmargin +#undef __argp_fmtstream_wmargin +#define __argp_fmtstream_wmargin argp_fmtstream_wmargin + +/* normal libc functions we call */ +#undef __sleep +#define __sleep sleep +#undef __strcasecmp +#define __strcasecmp strcasecmp +#undef __vsnprintf +#define __vsnprintf vsnprintf + +#endif /* !_LIBC */ + +#ifndef __set_errno +#define __set_errno(e) (errno = (e)) +#endif diff --git a/6-sigsnoop/eunomia-include/argp.h b/6-sigsnoop/eunomia-include/argp.h new file mode 100644 index 0000000..76234a0 --- /dev/null +++ b/6-sigsnoop/eunomia-include/argp.h @@ -0,0 +1,1854 @@ +#ifndef EUNOMIA_ARGP_H +#define EUNOMIA_ARGP_H + +/* Hierarchial argument parsing. + Copyright (C) 1995, 96, 97, 98, 99, 2003 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Written by Miles Bader . + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifndef _ARGP_H +#define _ARGP_H + +#include +#include +#include + +#include "errno-base.h" +#include + +#ifndef __THROW +# define __THROW +#endif + +#ifndef __const +# define __const const +#endif + +#ifndef __error_t_defined +typedef int error_t; +# define __error_t_defined +#endif + +/* FIXME: What's the right way to check for __restrict? Sun's cc seems + not to have it. Perhaps it's easiest to just delete the use of + __restrict from the prototypes. */ +#ifndef __restrict +# ifndef __GNUC___ +# define __restrict +# endif +#endif + +/* NOTE: We can't use the autoconf tests, since this is supposed to be + an installed header file and argp's config.h is of course not + installed. */ +#ifndef PRINTF_STYLE +# if __GNUC__ >= 2 +# define PRINTF_STYLE(f, a) __attribute__ ((__format__ (__printf__, f, a))) +# else +# define PRINTF_STYLE(f, a) +# endif +#endif + + +#define assert(expr) ((void)(expr)) + +#ifdef __cplusplus +extern "C" { +#endif + +/* A description of a particular option. A pointer to an array of + these is passed in the OPTIONS field of an argp structure. Each option + entry can correspond to one long option and/or one short option; more + names for the same option can be added by following an entry in an option + array with options having the OPTION_ALIAS flag set. */ +struct argp_option +{ + /* The long option name. For more than one name for the same option, you + can use following options with the OPTION_ALIAS flag set. */ + __const char *name; + + /* What key is returned for this option. If > 0 and printable, then it's + also accepted as a short option. */ + int key; + + /* If non-NULL, this is the name of the argument associated with this + option, which is required unless the OPTION_ARG_OPTIONAL flag is set. */ + __const char *arg; + + /* OPTION_ flags. */ + int flags; + + /* The doc string for this option. If both NAME and KEY are 0, This string + will be printed outdented from the normal option column, making it + useful as a group header (it will be the first thing printed in its + group); in this usage, it's conventional to end the string with a `:'. */ + __const char *doc; + + /* The group this option is in. In a long help message, options are sorted + alphabetically within each group, and the groups presented in the order + 0, 1, 2, ..., n, -m, ..., -2, -1. Every entry in an options array with + if this field 0 will inherit the group number of the previous entry, or + zero if it's the first one, unless its a group header (NAME and KEY both + 0), in which case, the previous entry + 1 is the default. Automagic + options such as --help are put into group -1. */ + int group; +}; + +/* The argument associated with this option is optional. */ +#define OPTION_ARG_OPTIONAL 0x1 + +/* This option isn't displayed in any help messages. */ +#define OPTION_HIDDEN 0x2 + +/* This option is an alias for the closest previous non-alias option. This + means that it will be displayed in the same help entry, and will inherit + fields other than NAME and KEY from the aliased option. */ +#define OPTION_ALIAS 0x4 + +/* This option isn't actually an option (and so should be ignored by the + actual option parser), but rather an arbitrary piece of documentation that + should be displayed in much the same manner as the options. If this flag + is set, then the option NAME field is displayed unmodified (e.g., no `--' + prefix is added) at the left-margin (where a *short* option would normally + be displayed), and the documentation string in the normal place. For + purposes of sorting, any leading whitespace and puncuation is ignored, + except that if the first non-whitespace character is not `-', this entry + is displayed after all options (and OPTION_DOC entries with a leading `-') + in the same group. */ +#define OPTION_DOC 0x8 + +/* This option shouldn't be included in `long' usage messages (but is still + included in help messages). This is mainly intended for options that are + completely documented in an argp's ARGS_DOC field, in which case including + the option in the generic usage list would be redundant. For instance, + if ARGS_DOC is "FOO BAR\n-x BLAH", and the `-x' option's purpose is to + distinguish these two cases, -x should probably be marked + OPTION_NO_USAGE. */ +#define OPTION_NO_USAGE 0x10 + +struct argp; /* fwd declare this type */ +struct argp_state; /* " */ +struct argp_child; /* " */ + +/* The type of a pointer to an argp parsing function. */ +typedef error_t (*argp_parser_t) (int key, char *arg, + struct argp_state *state); + +/* What to return for unrecognized keys. For special ARGP_KEY_ keys, such + returns will simply be ignored. For user keys, this error will be turned + into EINVAL (if the call to argp_parse is such that errors are propagated + back to the user instead of exiting); returning EINVAL itself would result + in an immediate stop to parsing in *all* cases. */ +#define ARGP_ERR_UNKNOWN E2BIG /* Hurd should never need E2BIG. XXX */ + +/* Special values for the KEY argument to an argument parsing function. + ARGP_ERR_UNKNOWN should be returned if they aren't understood. + + The sequence of keys to a parsing function is either (where each + uppercased word should be prefixed by `ARGP_KEY_' and opt is a user key): + + INIT opt... NO_ARGS END SUCCESS -- No non-option arguments at all + or INIT (opt | ARG)... END SUCCESS -- All non-option args parsed + or INIT (opt | ARG)... SUCCESS -- Some non-option arg unrecognized + + The third case is where every parser returned ARGP_KEY_UNKNOWN for an + argument, in which case parsing stops at that argument (returning the + unparsed arguments to the caller of argp_parse if requested, or stopping + with an error message if not). + + If an error occurs (either detected by argp, or because the parsing + function returned an error value), then the parser is called with + ARGP_KEY_ERROR, and no further calls are made. */ + +/* This is not an option at all, but rather a command line argument. If a + parser receiving this key returns success, the fact is recorded, and the + ARGP_KEY_NO_ARGS case won't be used. HOWEVER, if while processing the + argument, a parser function decrements the NEXT field of the state it's + passed, the option won't be considered processed; this is to allow you to + actually modify the argument (perhaps into an option), and have it + processed again. */ +#define ARGP_KEY_ARG 0 +/* There are remaining arguments not parsed by any parser, which may be found + starting at (STATE->argv + STATE->next). If success is returned, but + STATE->next left untouched, it's assumed that all arguments were consume, + otherwise, the parser should adjust STATE->next to reflect any arguments + consumed. */ +#define ARGP_KEY_ARGS 0x1000006 +/* There are no more command line arguments at all. */ +#define ARGP_KEY_END 0x1000001 +/* Because it's common to want to do some special processing if there aren't + any non-option args, user parsers are called with this key if they didn't + successfully process any non-option arguments. Called just before + ARGP_KEY_END (where more general validity checks on previously parsed + arguments can take place). */ +#define ARGP_KEY_NO_ARGS 0x1000002 +/* Passed in before any parsing is done. Afterwards, the values of each + element of the CHILD_INPUT field, if any, in the state structure is + copied to each child's state to be the initial value of the INPUT field. */ +#define ARGP_KEY_INIT 0x1000003 +/* Use after all other keys, including SUCCESS & END. */ +#define ARGP_KEY_FINI 0x1000007 +/* Passed in when parsing has successfully been completed (even if there are + still arguments remaining). */ +#define ARGP_KEY_SUCCESS 0x1000004 +/* Passed in if an error occurs. */ +#define ARGP_KEY_ERROR 0x1000005 + +/* An argp structure contains a set of options declarations, a function to + deal with parsing one, documentation string, a possible vector of child + argp's, and perhaps a function to filter help output. When actually + parsing options, getopt is called with the union of all the argp + structures chained together through their CHILD pointers, with conflicts + being resolved in favor of the first occurrence in the chain. */ +struct argp +{ + /* An array of argp_option structures, terminated by an entry with both + NAME and KEY having a value of 0. */ + __const struct argp_option *options; + + /* What to do with an option from this structure. KEY is the key + associated with the option, and ARG is any associated argument (NULL if + none was supplied). If KEY isn't understood, ARGP_ERR_UNKNOWN should be + returned. If a non-zero, non-ARGP_ERR_UNKNOWN value is returned, then + parsing is stopped immediately, and that value is returned from + argp_parse(). For special (non-user-supplied) values of KEY, see the + ARGP_KEY_ definitions below. */ + argp_parser_t parser; + + /* A string describing what other arguments are wanted by this program. It + is only used by argp_usage to print the `Usage:' message. If it + contains newlines, the strings separated by them are considered + alternative usage patterns, and printed on separate lines (lines after + the first are prefix by ` or: ' instead of `Usage:'). */ + __const char *args_doc; + + /* If non-NULL, a string containing extra text to be printed before and + after the options in a long help message (separated by a vertical tab + `\v' character). */ + __const char *doc; + + /* A vector of argp_children structures, terminated by a member with a 0 + argp field, pointing to child argps should be parsed with this one. Any + conflicts are resolved in favor of this argp, or early argps in the + CHILDREN list. This field is useful if you use libraries that supply + their own argp structure, which you want to use in conjunction with your + own. */ + __const struct argp_child *children; + + /* If non-zero, this should be a function to filter the output of help + messages. KEY is either a key from an option, in which case TEXT is + that option's help text, or a special key from the ARGP_KEY_HELP_ + defines, below, describing which other help text TEXT is. The function + should return either TEXT, if it should be used as-is, a replacement + string, which should be malloced, and will be freed by argp, or NULL, + meaning `print nothing'. The value for TEXT is *after* any translation + has been done, so if any of the replacement text also needs translation, + that should be done by the filter function. INPUT is either the input + supplied to argp_parse, or NULL, if argp_help was called directly. */ + char *(*help_filter) (int __key, __const char *__text, void *__input); + + /* If non-zero the strings used in the argp library are translated using + the domain described by this string. Otherwise the currently installed + default domain is used. */ + const char *argp_domain; +}; + +/* Possible KEY arguments to a help filter function. */ +#define ARGP_KEY_HELP_PRE_DOC 0x2000001 /* Help text preceeding options. */ +#define ARGP_KEY_HELP_POST_DOC 0x2000002 /* Help text following options. */ +#define ARGP_KEY_HELP_HEADER 0x2000003 /* Option header string. */ +#define ARGP_KEY_HELP_EXTRA 0x2000004 /* After all other documentation; + TEXT is NULL for this key. */ +/* Explanatory note emitted when duplicate option arguments have been + suppressed. */ +#define ARGP_KEY_HELP_DUP_ARGS_NOTE 0x2000005 +#define ARGP_KEY_HELP_ARGS_DOC 0x2000006 /* Argument doc string. */ + +/* When an argp has a non-zero CHILDREN field, it should point to a vector of + argp_child structures, each of which describes a subsidiary argp. */ +struct argp_child +{ + /* The child parser. */ + __const struct argp *argp; + + /* Flags for this child. */ + int flags; + + /* If non-zero, an optional header to be printed in help output before the + child options. As a side-effect, a non-zero value forces the child + options to be grouped together; to achieve this effect without actually + printing a header string, use a value of "". */ + __const char *header; + + /* Where to group the child options relative to the other (`consolidated') + options in the parent argp; the values are the same as the GROUP field + in argp_option structs, but all child-groupings follow parent options at + a particular group level. If both this field and HEADER are zero, then + they aren't grouped at all, but rather merged with the parent options + (merging the child's grouping levels with the parents). */ + int group; +}; + +/* Parsing state. This is provided to parsing functions called by argp, + which may examine and, as noted, modify fields. */ +struct argp_state +{ + /* The top level ARGP being parsed. */ + __const struct argp *root_argp; + + /* The argument vector being parsed. May be modified. */ + int argc; + char **argv; + + /* The index in ARGV of the next arg that to be parsed. May be modified. */ + int next; + + /* The flags supplied to argp_parse. May be modified. */ + unsigned flags; + + /* While calling a parsing function with a key of ARGP_KEY_ARG, this is the + number of the current arg, starting at zero, and incremented after each + such call returns. At all other times, this is the number of such + arguments that have been processed. */ + unsigned arg_num; + + /* If non-zero, the index in ARGV of the first argument following a special + `--' argument (which prevents anything following being interpreted as an + option). Only set once argument parsing has proceeded past this point. */ + int quoted; + + /* An arbitrary pointer passed in from the user. */ + void *input; + /* Values to pass to child parsers. This vector will be the same length as + the number of children for the current parser. */ + void **child_inputs; + + /* For the parser's use. Initialized to 0. */ + void *hook; + + /* The name used when printing messages. This is initialized to ARGV[0], + or PROGRAM_INVOCATION_NAME if that is unavailable. */ + char *name; + + void *pstate; /* Private, for use by argp. */ +}; + +/* Flags for argp_parse (note that the defaults are those that are + convenient for program command line parsing): */ + +/* Don't ignore the first element of ARGV. Normally (and always unless + ARGP_NO_ERRS is set) the first element of the argument vector is + skipped for option parsing purposes, as it corresponds to the program name + in a command line. */ +#define ARGP_PARSE_ARGV0 0x01 + +/* Don't print error messages for unknown options to stderr; unless this flag + is set, ARGP_PARSE_ARGV0 is ignored, as ARGV[0] is used as the program + name in the error messages. This flag implies ARGP_NO_EXIT (on the + assumption that silent exiting upon errors is bad behaviour). */ +#define ARGP_NO_ERRS 0x02 + +/* Don't parse any non-option args. Normally non-option args are parsed by + calling the parse functions with a key of ARGP_KEY_ARG, and the actual arg + as the value. Since it's impossible to know which parse function wants to + handle it, each one is called in turn, until one returns 0 or an error + other than ARGP_ERR_UNKNOWN; if an argument is handled by no one, the + argp_parse returns prematurely (but with a return value of 0). If all + args have been parsed without error, all parsing functions are called one + last time with a key of ARGP_KEY_END. This flag needn't normally be set, + as the normal behavior is to stop parsing as soon as some argument can't + be handled. */ +#define ARGP_NO_ARGS 0x04 + +/* Parse options and arguments in the same order they occur on the command + line -- normally they're rearranged so that all options come first. */ +#define ARGP_IN_ORDER 0x08 + +/* Don't provide the standard long option --help, which causes usage and + option help information to be output to stdout, and exit (0) called. */ +#define ARGP_NO_HELP 0x10 + +/* Don't exit on errors (they may still result in error messages). */ +#define ARGP_NO_EXIT 0x20 + +/* Use the gnu getopt `long-only' rules for parsing arguments. */ +#define ARGP_LONG_ONLY 0x40 + +/* Turns off any message-printing/exiting options. */ +#define ARGP_SILENT (ARGP_NO_EXIT | ARGP_NO_ERRS | ARGP_NO_HELP) + +/* Parse the options strings in ARGC & ARGV according to the options in ARGP. + FLAGS is one of the ARGP_ flags above. If ARG_INDEX is non-NULL, the + index in ARGV of the first unparsed option is returned in it. If an + unknown option is present, ARGP_ERR_UNKNOWN is returned; if some parser + routine returned a non-zero value, it is returned; otherwise 0 is + returned. This function may also call exit unless the ARGP_NO_HELP flag + is set. INPUT is a pointer to a value to be passed in to the parser. */ +extern error_t argp_parse (__const struct argp *__restrict argp, + int argc, char **__restrict argv, + unsigned flags, int *__restrict arg_index, + void *__restrict input) __THROW; +extern error_t __argp_parse (__const struct argp *__restrict argp, + int argc, char **__restrict argv, + unsigned flags, int *__restrict arg_index, + void *__restrict input) __THROW; + +/* Global variables. */ + +/* If defined or set by the user program to a non-zero value, then a default + option --version is added (unless the ARGP_NO_HELP flag is used), which + will print this string followed by a newline and exit (unless the + ARGP_NO_EXIT flag is used). Overridden by ARGP_PROGRAM_VERSION_HOOK. */ +extern __const char *argp_program_version; + +/* If defined or set by the user program to a non-zero value, then a default + option --version is added (unless the ARGP_NO_HELP flag is used), which + calls this function with a stream to print the version to and a pointer to + the current parsing state, and then exits (unless the ARGP_NO_EXIT flag is + used). This variable takes precedent over ARGP_PROGRAM_VERSION. */ +extern void (*argp_program_version_hook) ( + struct argp_state *__restrict + __state); + +/* If defined or set by the user program, it should point to string that is + the bug-reporting address for the program. It will be printed by + argp_help if the ARGP_HELP_BUG_ADDR flag is set (as it is by various + standard help messages), embedded in a sentence that says something like + `Report bugs to ADDR.'. */ +extern __const char *argp_program_bug_address; + +/* The exit status that argp will use when exiting due to a parsing error. + If not defined or set by the user program, this defaults to EX_USAGE from + . */ +extern error_t argp_err_exit_status; + +/* Flags for argp_help. */ +#define ARGP_HELP_USAGE 0x01 /* a Usage: message. */ +#define ARGP_HELP_SHORT_USAGE 0x02 /* " but don't actually print options. */ +#define ARGP_HELP_SEE 0x04 /* a `Try ... for more help' message. */ +#define ARGP_HELP_LONG 0x08 /* a long help message. */ +#define ARGP_HELP_PRE_DOC 0x10 /* doc string preceding long help. */ +#define ARGP_HELP_POST_DOC 0x20 /* doc string following long help. */ +#define ARGP_HELP_DOC (ARGP_HELP_PRE_DOC | ARGP_HELP_POST_DOC) +#define ARGP_HELP_BUG_ADDR 0x40 /* bug report address */ +#define ARGP_HELP_LONG_ONLY 0x80 /* modify output appropriately to + reflect ARGP_LONG_ONLY mode. */ + +/* These ARGP_HELP flags are only understood by argp_state_help. */ +#define ARGP_HELP_EXIT_ERR 0x100 /* Call exit(1) instead of returning. */ +#define ARGP_HELP_EXIT_OK 0x200 /* Call exit(0) instead of returning. */ + +/* The standard thing to do after a program command line parsing error, if an + error message has already been printed. */ +#define ARGP_HELP_STD_ERR \ + (ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR) +/* The standard thing to do after a program command line parsing error, if no + more specific error message has been printed. */ +#define ARGP_HELP_STD_USAGE \ + (ARGP_HELP_SHORT_USAGE | ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR) +/* The standard thing to do in response to a --help option. */ +#define ARGP_HELP_STD_HELP \ + (ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG | ARGP_HELP_EXIT_OK \ + | ARGP_HELP_DOC | ARGP_HELP_BUG_ADDR) + + +/* Possibly output the standard usage message for ARGP to stderr and exit. */ +extern void argp_usage (__const struct argp_state *__state) __THROW; +extern void __argp_usage (__const struct argp_state *__state) __THROW; + +/* If appropriate, print the printf string FMT and following args, preceded + by the program name and `:', to stderr, and followed by a `Try ... --help' + message, then exit (1). */ +extern void argp_error (__const struct argp_state *__restrict __state, + __const char *__restrict __fmt, ...) __THROW + PRINTF_STYLE(2,3); +extern void __argp_error (__const struct argp_state *__restrict __state, + __const char *__restrict __fmt, ...) __THROW + PRINTF_STYLE(2,3); + +/* Returns true if the option OPT is a valid short option. */ +extern int _option_is_short (__const struct argp_option *__opt) __THROW; +extern int __option_is_short (__const struct argp_option *__opt) __THROW; + +/* Returns true if the option OPT is in fact the last (unused) entry in an + options array. */ +extern int _option_is_end (__const struct argp_option *__opt) __THROW; +extern int __option_is_end (__const struct argp_option *__opt) __THROW; + +/* Return the input field for ARGP in the parser corresponding to STATE; used + by the help routines. */ +extern void *_argp_input (__const struct argp *__restrict __argp, + __const struct argp_state *__restrict __state) + __THROW; +extern void *__argp_input (__const struct argp *__restrict __argp, + __const struct argp_state *__restrict __state) + __THROW; + +/* Used for extracting the program name from argv[0] */ +extern char *_argp_basename(char *name) __THROW; +extern char *__argp_basename(char *name) __THROW; + +/* Getting the program name given an argp state */ +extern char * +_argp_short_program_name(const struct argp_state *state) __THROW; +extern char * +__argp_short_program_name(const struct argp_state *state) __THROW; + + +#ifdef __USE_EXTERN_INLINES + +# if !_LIBC +# define __argp_usage argp_usage +# define __argp_state_help argp_state_help +# define __option_is_short _option_is_short +# define __option_is_end _option_is_end +# endif + +# ifndef ARGP_EI +# define ARGP_EI extern __inline__ +# endif + +ARGP_EI void +__argp_usage (__const struct argp_state *__state) +{ + __argp_state_help (__state, stderr, ARGP_HELP_STD_USAGE); +} + +ARGP_EI int +__option_is_short (__const struct argp_option *__opt) +{ + if (__opt->flags & OPTION_DOC) + return 0; + else + { + int __key = __opt->key; + return __key > 0 && isprint (__key); + } +} + +ARGP_EI int +__option_is_end (__const struct argp_option *__opt) +{ + return !__opt->key && !__opt->name && !__opt->doc && !__opt->group; +} + +# if !_LIBC +# undef __argp_usage +# undef __argp_state_help +# undef __option_is_short +# undef __option_is_end +# endif +#endif /* Use extern inlines. */ + +#ifdef __cplusplus +} +#endif + +#endif /* argp.h */ + + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* AIX requires this to be the first thing in the file. */ +#ifndef __GNUC__ +# if HAVE_ALLOCA_H +# include +# else +# ifdef _AIX + #pragma alloca +# else +# ifndef alloca /* predefined by HP cc +Olibcalls */ +char *alloca (); +# endif +# endif +# endif +#endif + +#include +#include +#if HAVE_UNISTD_H +# include +#endif +#include +#include + +#if HAVE_MALLOC_H +/* Needed, for alloca on windows */ +# include +#endif + +#ifndef _ +/* This is for other GNU distributions with internationalized messages. + When compiling libc, the _ macro is predefined. */ +# if defined HAVE_LIBINTL_H || defined _LIBC +# include +# ifdef _LIBC +# undef dgettext +# define dgettext(domain, msgid) __dcgettext (domain, msgid, LC_MESSAGES) +# endif +# else +# define dgettext(domain, msgid) (msgid) +# define gettext(msgid) (msgid) +# endif +#endif +#ifndef N_ +# define N_(msgid) (msgid) +#endif + +#if _LIBC - 0 +#include +#else +#ifdef HAVE_CTHREADS_H +#include +#endif +#endif /* _LIBC */ + +#include "argp.h" + + + + +/* The meta-argument used to prevent any further arguments being interpreted + as options. */ +#define QUOTE "--" + +/* EZ alias for ARGP_ERR_UNKNOWN. */ +#define EBADKEY ARGP_ERR_UNKNOWN + + +/* Default options. */ + +/* When argp is given the --HANG switch, _ARGP_HANG is set and argp will sleep + for one second intervals, decrementing _ARGP_HANG until it's zero. Thus + you can force the program to continue by attaching a debugger and setting + it to 0 yourself. */ +volatile int _argp_hang; + +#define OPT_PROGNAME -2 +#define OPT_USAGE -3 +#if HAVE_SLEEP && HAVE_GETPID +#define OPT_HANG -4 +#endif + +static const struct argp_option argp_default_options[] = +{ + {"help", '?', 0, 0, N_("Give this help list"), -1}, + {"usage", OPT_USAGE, 0, 0, N_("Give a short usage message"), 0 }, + {"program-name",OPT_PROGNAME,"NAME", OPTION_HIDDEN, + N_("Set the program name"), 0}, +#if OPT_HANG + {"HANG", OPT_HANG, "SECS", OPTION_ARG_OPTIONAL | OPTION_HIDDEN, + N_("Hang for SECS seconds (default 3600)"), 0 }, +#endif + {0, 0, 0, 0, 0, 0} +}; + +static error_t +argp_default_parser (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case '?': + // __argp_state_help (state, ARGP_HELP_STD_HELP); + break; + case OPT_USAGE: + // __argp_state_help (state, + // ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK); + break; + + case OPT_PROGNAME: /* Set the program name. */ +#if HAVE_DECL_PROGRAM_INVOCATION_NAME + program_invocation_name = arg; +#endif + /* [Note that some systems only have PROGRAM_INVOCATION_SHORT_NAME (aka + __PROGNAME), in which case, PROGRAM_INVOCATION_NAME is just defined + to be that, so we have to be a bit careful here.] */ + + /* Update what we use for messages. */ + + state->name = arg; + +#if HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME + program_invocation_short_name = state->name; +#endif + + if ((state->flags & (ARGP_PARSE_ARGV0 | ARGP_NO_ERRS)) + == ARGP_PARSE_ARGV0) + /* Update what getopt uses too. */ + state->argv[0] = arg; + + break; + +#if OPT_HANG + case OPT_HANG: + _argp_hang = atoi (arg ? arg : "3600"); + printf( "%s: pid = %ld\n", + state->name, (long) getpid()); + while (_argp_hang-- > 0) + __sleep (1); + break; +#endif + + default: + return EBADKEY; + } + return 0; +} + +static const struct argp argp_default_argp = + {argp_default_options, &argp_default_parser, NULL, NULL, NULL, NULL, "libc"}; + + +static const struct argp_option argp_version_options[] = +{ + {"version", 'V', 0, 0, N_("Print program version"), -1}, + {0, 0, 0, 0, 0, 0 } +}; + +static error_t +argp_version_parser (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case 'V': + if (argp_program_version_hook) + (*argp_program_version_hook) (state); + else if (argp_program_version) + printf ("%s\n", argp_program_version); + else; + // __argp_error (state, dgettext (state->root_argp->argp_domain, + // "(PROGRAM ERROR) No version known!?")); + if (! (state->flags & ARGP_NO_EXIT)) + exit (0); + break; + default: + return EBADKEY; + } + return 0; +} + +static const struct argp argp_version_argp = + {argp_version_options, &argp_version_parser, NULL, NULL, NULL, NULL, "libc"}; + + + +/* The state of a `group' during parsing. Each group corresponds to a + particular argp structure from the tree of such descending from the top + level argp passed to argp_parse. */ +struct group +{ + /* This group's parsing function. */ + argp_parser_t parser; + + /* Which argp this group is from. */ + const struct argp *argp; + + /* The number of non-option args sucessfully handled by this parser. */ + unsigned args_processed; + + /* This group's parser's parent's group. */ + struct group *parent; + unsigned parent_index; /* And the our position in the parent. */ + + /* These fields are swapped into and out of the state structure when + calling this group's parser. */ + void *input, **child_inputs; + void *hook; +}; + +/* Call GROUP's parser with KEY and ARG, swapping any group-specific info + from STATE before calling, and back into state afterwards. If GROUP has + no parser, EBADKEY is returned. */ +static error_t +group_parse (struct group *group, struct argp_state *state, int key, char *arg) +{ + if (group->parser) + { + error_t err; + state->hook = group->hook; + state->input = group->input; + state->child_inputs = group->child_inputs; + state->arg_num = group->args_processed; + err = (*group->parser)(key, arg, state); + group->hook = state->hook; + return err; + } + else + return EBADKEY; +} + +struct parser +{ + const struct argp *argp; + + const char *posixly_correct; + + /* True if there are only no-option arguments left, which are just + passed verbatim with ARGP_KEY_ARG. This is set if we encounter a + quote, or the end of the proper options, but may be cleared again + if the user moves the next argument pointer backwards. */ + int args_only; + + /* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, the default is + REQUIRE_ORDER if the environment variable POSIXLY_CORRECT is + defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; stop option + processing when the first non-option is seen. This is what Unix + does. This mode of operation is selected by either setting the + environment variable POSIXLY_CORRECT, or using `+' as the first + character of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we + scan, so that eventually all the non-options are at the end. This + allows options to be given in any order, even with programs that + were not written to expect this. + + RETURN_IN_ORDER is an option available to programs that were + written to expect options and other ARGV-elements in any order + and that care about the ordering of the two. We describe each + non-option ARGV-element as if it were the argument of an option + with character code 1. Using `-' as the first character of the + list of option characters selects this mode of operation. + + */ + enum { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER } ordering; + + /* A segment of non-option arguments that have been skipped for + later processing, after all options. `first_nonopt' is the index + in ARGV of the first of them; `last_nonopt' is the index after + the last of them. + + If quoted or args_only is non-zero, this segment should be empty. */ + + /* FIXME: I'd prefer to use unsigned, but it's more consistent to + use the same type as for state.next. */ + int first_nonopt; + int last_nonopt; + + /* String of all recognized short options. Needed for ARGP_LONG_ONLY. */ + /* FIXME: Perhaps change to a pointer to a suitable bitmap instead? */ + char *short_opts; + + /* For parsing combined short options. */ + char *nextchar; + + /* States of the various parsing groups. */ + struct group *groups; + /* The end of the GROUPS array. */ + struct group *egroup; + /* An vector containing storage for the CHILD_INPUTS field in all groups. */ + void **child_inputs; + + /* State block supplied to parsing routines. */ + struct argp_state state; + + /* Memory used by this parser. */ + void *storage; +}; + +/* Search for a group defining a short option. */ +static const struct argp_option * +find_short_option(struct parser *parser, int key, struct group **p) +{ + struct group *group; + + for (group = parser->groups; group < parser->egroup; group++) + { + const struct argp_option *opts; + + for (opts = group->argp->options; !__option_is_end(opts); opts++) + if (opts->key == key) + { + *p = group; + return opts; + } + } + return NULL; +} + +enum match_result { MATCH_EXACT, MATCH_PARTIAL, MATCH_NO }; + +/* If defined, allow complete.el-like abbreviations of long options. */ +#ifndef ARGP_COMPLETE +#define ARGP_COMPLETE 0 +#endif + +/* Matches an encountern long-option argument ARG against an option NAME. + * ARG is terminated by NUL or '='. */ +static enum match_result +match_option(const char *arg, const char *name) +{ + unsigned i, j; + for (i = j = 0;; i++, j++) + { + switch(arg[i]) + { + case '\0': + case '=': + return name[j] ? MATCH_PARTIAL : MATCH_EXACT; +#if ARGP_COMPLETE + case '-': + while (name[j] != '-') + if (!name[j++]) + return MATCH_NO; + break; +#endif + default: + if (arg[i] != name[j]) + return MATCH_NO; + } + } +} + +static const struct argp_option * +find_long_option(struct parser *parser, + const char *arg, + struct group **p) +{ + struct group *group; + + /* Partial match found so far. */ + struct group *matched_group = NULL; + const struct argp_option *matched_option = NULL; + + /* Number of partial matches. */ + int num_partial = 0; + + for (group = parser->groups; group < parser->egroup; group++) + { + const struct argp_option *opts; + + for (opts = group->argp->options; !__option_is_end(opts); opts++) + { + if (!opts->name) + continue; + switch (match_option(arg, opts->name)) + { + case MATCH_NO: + break; + case MATCH_PARTIAL: + num_partial++; + + matched_group = group; + matched_option = opts; + + break; + case MATCH_EXACT: + /* Exact match. */ + *p = group; + return opts; + } + } + } + if (num_partial == 1) + { + *p = matched_group; + return matched_option; + } + + return NULL; +} + + +/* The next usable entries in the various parser tables being filled in by + convert_options. */ +struct parser_convert_state +{ + struct parser *parser; + char *short_end; + void **child_inputs_end; +}; + +/* Initialize GROUP from ARGP. If CVT->SHORT_END is non-NULL, short + options are recorded in the short options string. Returns the next + unused group entry. CVT holds state used during the conversion. */ +static struct group * +convert_options (const struct argp *argp, + struct group *parent, unsigned parent_index, + struct group *group, struct parser_convert_state *cvt) +{ + const struct argp_option *opt = argp->options; + const struct argp_child *children = argp->children; + + if (opt || argp->parser) + { + /* This parser needs a group. */ + if (cvt->short_end) + { + /* Record any short options. */ + for ( ; !__option_is_end (opt); opt++) + if (__option_is_short(opt)) + *cvt->short_end++ = opt->key; + } + + group->parser = argp->parser; + group->argp = argp; + group->args_processed = 0; + group->parent = parent; + group->parent_index = parent_index; + group->input = 0; + group->hook = 0; + group->child_inputs = 0; + + if (children) + /* Assign GROUP's CHILD_INPUTS field some space from + CVT->child_inputs_end.*/ + { + unsigned num_children = 0; + while (children[num_children].argp) + num_children++; + group->child_inputs = cvt->child_inputs_end; + cvt->child_inputs_end += num_children; + } + parent = group++; + } + else + parent = 0; + + if (children) + { + unsigned index = 0; + while (children->argp) + group = + convert_options (children++->argp, parent, index++, group, cvt); + } + + return group; +} +/* Allocate and initialize the group structures, so that they are + ordered as if by traversing the corresponding argp parser tree in + pre-order. Also build the list of short options, if that is needed. */ +static void +parser_convert (struct parser *parser, const struct argp *argp) +{ + struct parser_convert_state cvt; + + cvt.parser = parser; + cvt.short_end = parser->short_opts; + cvt.child_inputs_end = parser->child_inputs; + + parser->argp = argp; + + if (argp) + parser->egroup = convert_options (argp, 0, 0, parser->groups, &cvt); + else + parser->egroup = parser->groups; /* No parsers at all! */ + + if (parser->short_opts) + *cvt.short_end ='\0'; +} + +/* Lengths of various parser fields which we will allocated. */ +struct parser_sizes +{ + /* Needed only ARGP_LONG_ONLY */ + size_t short_len; /* Number of short options. */ + + size_t num_groups; /* Group structures we allocate. */ + size_t num_child_inputs; /* Child input slots. */ +}; + +/* For ARGP, increments the NUM_GROUPS field in SZS by the total + number of argp structures descended from it, and the SHORT_LEN by + the total number of short options. */ +static void +calc_sizes (const struct argp *argp, struct parser_sizes *szs) +{ + const struct argp_child *child = argp->children; + const struct argp_option *opt = argp->options; + + if (opt || argp->parser) + { + /* This parser needs a group. */ + szs->num_groups++; + if (opt) + { + while (__option_is_short (opt++)) + szs->short_len++; + } + } + + if (child) + while (child->argp) + { + calc_sizes ((child++)->argp, szs); + szs->num_child_inputs++; + } +} + +/* Initializes PARSER to parse ARGP in a manner described by FLAGS. */ +static error_t +parser_init (struct parser *parser, const struct argp *argp, + int argc, char **argv, int flags, void *input) +{ + error_t err = 0; + struct group *group; + struct parser_sizes szs; + + if (flags & ARGP_IN_ORDER) + parser->ordering = RETURN_IN_ORDER; + else if (flags & ARGP_NO_ARGS) + parser->ordering = REQUIRE_ORDER; + else if (parser->posixly_correct) + parser->ordering = REQUIRE_ORDER; + else + parser->ordering = PERMUTE; + + szs.short_len = 0; + szs.num_groups = 0; + szs.num_child_inputs = 0; + + if (argp) + calc_sizes (argp, &szs); + + if (!(flags & ARGP_LONG_ONLY)) + /* We have no use for the short option array. */ + szs.short_len = 0; + + /* Lengths of the various bits of storage used by PARSER. */ +#define GLEN (szs.num_groups + 1) * sizeof (struct group) +#define CLEN (szs.num_child_inputs * sizeof (void *)) +#define SLEN (szs.short_len + 1) +#define STORAGE(offset) ((void *) (((char *) parser->storage) + (offset))) + + parser->storage = malloc (GLEN + CLEN + SLEN); + if (! parser->storage) + return ENOMEM; + + parser->groups = parser->storage; + + parser->child_inputs = STORAGE(GLEN); + memset (parser->child_inputs, 0, szs.num_child_inputs * sizeof (void *)); + + if (flags & ARGP_LONG_ONLY) + parser->short_opts = STORAGE(GLEN + CLEN); + else + parser->short_opts = NULL; + + parser_convert (parser, argp); + + memset (&parser->state, 0, sizeof (struct argp_state)); + + parser->state.root_argp = parser->argp; + parser->state.argc = argc; + parser->state.argv = argv; + parser->state.flags = flags; + parser->state.pstate = parser; + + parser->args_only = 0; + parser->nextchar = NULL; + parser->first_nonopt = parser->last_nonopt = 0; + + /* Call each parser for the first time, giving it a chance to propagate + values to child parsers. */ + if (parser->groups < parser->egroup) + parser->groups->input = input; + for (group = parser->groups; + group < parser->egroup && (!err || err == EBADKEY); + group++) + { + if (group->parent) + /* If a child parser, get the initial input value from the parent. */ + group->input = group->parent->child_inputs[group->parent_index]; + + if (!group->parser + && group->argp->children && group->argp->children->argp) + /* For the special case where no parsing function is supplied for an + argp, propagate its input to its first child, if any (this just + makes very simple wrapper argps more convenient). */ + group->child_inputs[0] = group->input; + + err = group_parse (group, &parser->state, ARGP_KEY_INIT, 0); + } + if (err == EBADKEY) + err = 0; /* Some parser didn't understand. */ + + if (err) + return err; + + if (argv[0] && !(parser->state.flags & ARGP_PARSE_ARGV0)) + /* There's an argv[0]; use it for messages. */ + { + parser->state.name = argv[0]; + + /* Don't parse it as an argument. */ + parser->state.next = 1; + } + else + parser->state.name = ""; + + return 0; +} + +/* Free any storage consumed by PARSER (but not PARSER itself). */ +static error_t +parser_finalize (struct parser *parser, + error_t err, int arg_ebadkey, int *end_index) +{ + struct group *group; + + if (err == EBADKEY && arg_ebadkey) + /* Suppress errors generated by unparsed arguments. */ + err = 0; + + if (! err) + { + if (parser->state.next == parser->state.argc) + /* We successfully parsed all arguments! Call all the parsers again, + just a few more times... */ + { + for (group = parser->groups; + group < parser->egroup && (!err || err==EBADKEY); + group++) + if (group->args_processed == 0) + err = group_parse (group, &parser->state, ARGP_KEY_NO_ARGS, 0); + for (group = parser->egroup - 1; + group >= parser->groups && (!err || err==EBADKEY); + group--) + err = group_parse (group, &parser->state, ARGP_KEY_END, 0); + + if (err == EBADKEY) + err = 0; /* Some parser didn't understand. */ + + /* Tell the user that all arguments are parsed. */ + if (end_index) + *end_index = parser->state.next; + } + else if (end_index) + /* Return any remaining arguments to the user. */ + *end_index = parser->state.next; + else + /* No way to return the remaining arguments, they must be bogus. */ + { + if (!(parser->state.flags & ARGP_NO_ERRS)) + printf( + dgettext (parser->argp->argp_domain, + "%s: Too many arguments\n"), + parser->state.name); + err = EBADKEY; + } + } + + /* Okay, we're all done, with either an error or success; call the parsers + to indicate which one. */ + + if (err) + { + /* Maybe print an error message. */ + if (err == EBADKEY) + /* An appropriate message describing what the error was should have + been printed earlier. */ + // __argp_state_help (&parser->state, + // ARGP_HELP_STD_ERR); + + /* Since we didn't exit, give each parser an error indication. */ + for (group = parser->groups; group < parser->egroup; group++) + group_parse (group, &parser->state, ARGP_KEY_ERROR, 0); + } + else + /* Notify parsers of success, and propagate back values from parsers. */ + { + /* We pass over the groups in reverse order so that child groups are + given a chance to do there processing before passing back a value to + the parent. */ + for (group = parser->egroup - 1 + ; group >= parser->groups && (!err || err == EBADKEY) + ; group--) + err = group_parse (group, &parser->state, ARGP_KEY_SUCCESS, 0); + if (err == EBADKEY) + err = 0; /* Some parser didn't understand. */ + } + + /* Call parsers once more, to do any final cleanup. Errors are ignored. */ + for (group = parser->egroup - 1; group >= parser->groups; group--) + group_parse (group, &parser->state, ARGP_KEY_FINI, 0); + + if (err == EBADKEY) + err = EINVAL; + + free (parser->storage); + + return err; +} + +/* Call the user parsers to parse the non-option argument VAL, at the + current position, returning any error. The state NEXT pointer + should point to the argument; this function will adjust it + correctly to reflect however many args actually end up being + consumed. */ +static error_t +parser_parse_arg (struct parser *parser, char *val) +{ + /* Save the starting value of NEXT */ + int index = parser->state.next; + error_t err = EBADKEY; + struct group *group; + int key = 0; /* Which of ARGP_KEY_ARG[S] we used. */ + + /* Try to parse the argument in each parser. */ + for (group = parser->groups + ; group < parser->egroup && err == EBADKEY + ; group++) + { + parser->state.next++; /* For ARGP_KEY_ARG, consume the arg. */ + key = ARGP_KEY_ARG; + err = group_parse (group, &parser->state, key, val); + + if (err == EBADKEY) + /* This parser doesn't like ARGP_KEY_ARG; try ARGP_KEY_ARGS instead. */ + { + parser->state.next--; /* For ARGP_KEY_ARGS, put back the arg. */ + key = ARGP_KEY_ARGS; + err = group_parse (group, &parser->state, key, 0); + } + } + + if (! err) + { + if (key == ARGP_KEY_ARGS) + /* The default for ARGP_KEY_ARGS is to assume that if NEXT isn't + changed by the user, *all* arguments should be considered + consumed. */ + parser->state.next = parser->state.argc; + + if (parser->state.next > index) + /* Remember that we successfully processed a non-option + argument -- but only if the user hasn't gotten tricky and set + the clock back. */ + (--group)->args_processed += (parser->state.next - index); + else + /* The user wants to reparse some args, so try looking for options again. */ + parser->args_only = 0; + } + + return err; +} + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,next), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (struct parser *parser) +{ + int bottom = parser->first_nonopt; + int middle = parser->last_nonopt; + int top = parser->state.next; + char **argv = parser->state.argv; + + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + parser->first_nonopt += (parser->state.next - parser->last_nonopt); + parser->last_nonopt = parser->state.next; +} + + +enum arg_type { ARG_ARG, ARG_SHORT_OPTION, + ARG_LONG_OPTION, ARG_LONG_ONLY_OPTION, + ARG_QUOTE }; + +static enum arg_type +classify_arg(struct parser *parser, char *arg, char **opt) +{ + if (arg[0] == '-') + /* Looks like an option... */ + switch (arg[1]) + { + case '\0': + /* "-" is not an option. */ + return ARG_ARG; + case '-': + /* Long option, or quote. */ + if (!arg[2]) + return ARG_QUOTE; + + /* A long option. */ + if (opt) + *opt = arg + 2; + return ARG_LONG_OPTION; + + default: + /* Short option. But if ARGP_LONG_ONLY, it can also be a long option. */ + + if (opt) + *opt = arg + 1; + + if (parser->state.flags & ARGP_LONG_ONLY) + { + /* Rules from getopt.c: + + If long_only and the ARGV-element has the form "-f", + where f is a valid short option, don't consider it an + abbreviated form of a long option that starts with f. + Otherwise there would be no way to give the -f short + option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an + abbreviation of the long option, just like "--fu", and + not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + assert(parser->short_opts); + + if (arg[2] || !strchr(parser->short_opts, arg[1])) + return ARG_LONG_ONLY_OPTION; + } + + return ARG_SHORT_OPTION; + } + + else + return ARG_ARG; +} + +/* Parse the next argument in PARSER (as indicated by PARSER->state.next). + Any error from the parsers is returned, and *ARGP_EBADKEY indicates + whether a value of EBADKEY is due to an unrecognized argument (which is + generally not fatal). */ +static error_t +parser_parse_next (struct parser *parser, int *arg_ebadkey) +{ + if (parser->state.quoted && parser->state.next < parser->state.quoted) + /* The next argument pointer has been moved to before the quoted + region, so pretend we never saw the quoting `--', and start + looking for options again. If the `--' is still there we'll just + process it one more time. */ + parser->state.quoted = parser->args_only = 0; + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if NEXT has been + moved back by the user (who may also have changed the arguments). */ + if (parser->last_nonopt > parser->state.next) + parser->last_nonopt = parser->state.next; + if (parser->first_nonopt > parser->state.next) + parser->first_nonopt = parser->state.next; + + if (parser->nextchar) + /* Deal with short options. */ + { + struct group *group; + char c; + const struct argp_option *option; + char *value = NULL;; + + assert(!parser->args_only); + + c = *parser->nextchar++; + + option = find_short_option(parser, c, &group); + if (!option) + { + if (parser->posixly_correct) + /* 1003.2 specifies the format of this message. */ + printf( + dgettext(parser->state.root_argp->argp_domain, + "%s: illegal option -- %c\n"), + parser->state.name, c); + else + printf( + dgettext(parser->state.root_argp->argp_domain, + "%s: invalid option -- %c\n"), + parser->state.name, c); + + *arg_ebadkey = 0; + return EBADKEY; + } + + if (!*parser->nextchar) + parser->nextchar = NULL; + + if (option->arg) + { + value = parser->nextchar; + parser->nextchar = NULL; + + if (!value + && !(option->flags & OPTION_ARG_OPTIONAL)) + /* We need an mandatory argument. */ + { + if (parser->state.next == parser->state.argc) + /* Missing argument */ + { + /* 1003.2 specifies the format of this message. */ + printf( + dgettext(parser->state.root_argp->argp_domain, + "%s: option requires an argument -- %c\n"), + parser->state.name, c); + + *arg_ebadkey = 0; + return EBADKEY; + } + value = parser->state.argv[parser->state.next++]; + } + } + return group_parse(group, &parser->state, + option->key, value); + } + else + /* Advance to the next ARGV-element. */ + { + if (parser->args_only) + { + *arg_ebadkey = 1; + if (parser->state.next >= parser->state.argc) + /* We're done. */ + return EBADKEY; + else + return parser_parse_arg(parser, + parser->state.argv[parser->state.next]); + } + + if (parser->state.next >= parser->state.argc) + /* Almost done. If there are non-options that we skipped + previously, we should process them now. */ + { + *arg_ebadkey = 1; + if (parser->first_nonopt != parser->last_nonopt) + { + exchange(parser); + + /* Start processing the arguments we skipped previously. */ + parser->state.next = parser->first_nonopt; + + parser->first_nonopt = parser->last_nonopt = 0; + + parser->args_only = 1; + return 0; + } + else + /* Indicate that we're really done. */ + return EBADKEY; + } + else + /* Look for options. */ + { + char *arg = parser->state.argv[parser->state.next]; + + char *optstart; + enum arg_type token = classify_arg(parser, arg, &optstart); + + switch (token) + { + case ARG_ARG: + switch (parser->ordering) + { + case PERMUTE: + if (parser->first_nonopt == parser->last_nonopt) + /* Skipped sequence is empty; start a new one. */ + parser->first_nonopt = parser->last_nonopt = parser->state.next; + + else if (parser->last_nonopt != parser->state.next) + /* We have a non-empty skipped sequence, and + we're not at the end-point, so move it. */ + exchange(parser); + + assert(parser->last_nonopt == parser->state.next); + + /* Skip this argument for now. */ + parser->state.next++; + parser->last_nonopt = parser->state.next; + + return 0; + + case REQUIRE_ORDER: + /* Implicit quote before the first argument. */ + parser->args_only = 1; + return 0; + + case RETURN_IN_ORDER: + *arg_ebadkey = 1; + return parser_parse_arg(parser, arg); + + default: + exit(1); + } + case ARG_QUOTE: + /* Skip it, then exchange with any previous non-options. */ + parser->state.next++; + assert (parser->last_nonopt != parser->state.next); + + if (parser->first_nonopt != parser->last_nonopt) + { + exchange(parser); + + /* Start processing the skipped and the quoted + arguments. */ + + parser->state.quoted = parser->state.next = parser->first_nonopt; + + /* Also empty the skipped-list, to avoid confusion + if the user resets the next pointer. */ + parser->first_nonopt = parser->last_nonopt = 0; + } + else + parser->state.quoted = parser->state.next; + + parser->args_only = 1; + return 0; + + case ARG_LONG_ONLY_OPTION: + case ARG_LONG_OPTION: + { + struct group *group; + const struct argp_option *option; + char *value; + + parser->state.next++; + option = find_long_option(parser, optstart, &group); + + if (!option) + { + /* NOTE: This includes any "=something" in the output. */ + printf( + dgettext(parser->state.root_argp->argp_domain, + "%s: unrecognized option `%s'\n"), + parser->state.name, arg); + *arg_ebadkey = 0; + return EBADKEY; + } + + value = strchr(optstart, '='); + if (value) + value++; + + if (value && !option->arg) + /* Unexpected argument. */ + { + if (token == ARG_LONG_OPTION) + /* --option */ + printf( + dgettext(parser->state.root_argp->argp_domain, + "%s: option `--%s' doesn't allow an argument\n"), + parser->state.name, option->name); + else + /* +option or -option */ + printf( + dgettext(parser->state.root_argp->argp_domain, + "%s: option `%c%s' doesn't allow an argument\n"), + parser->state.name, arg[0], option->name); + + *arg_ebadkey = 0; + return EBADKEY; + } + + if (option->arg && !value + && !(option->flags & OPTION_ARG_OPTIONAL)) + /* We need an mandatory argument. */ + { + if (parser->state.next == parser->state.argc) + /* Missing argument */ + { + if (token == ARG_LONG_OPTION) + /* --option */ + printf( + dgettext(parser->state.root_argp->argp_domain, + "%s: option `--%s' requires an argument\n"), + parser->state.name, option->name); + else + /* +option or -option */ + printf( + dgettext(parser->state.root_argp->argp_domain, + "%s: option `%c%s' requires an argument\n"), + parser->state.name, arg[0], option->name); + + *arg_ebadkey = 0; + return EBADKEY; + } + + value = parser->state.argv[parser->state.next++]; + } + *arg_ebadkey = 0; + return group_parse(group, &parser->state, + option->key, value); + } + case ARG_SHORT_OPTION: + parser->state.next++; + parser->nextchar = optstart; + return 0; + + default: + exit(1); + } + } + } +} + +/* Parse the options strings in ARGC & ARGV according to the argp in ARGP. + FLAGS is one of the ARGP_ flags above. If END_INDEX is non-NULL, the + index in ARGV of the first unparsed option is returned in it. If an + unknown option is present, EINVAL is returned; if some parser routine + returned a non-zero value, it is returned; otherwise 0 is returned. */ +error_t +argp_parse (const struct argp *argp, int argc, char **argv, unsigned flags, + int *end_index, void *input) +{ + error_t err; + struct parser parser; + + /* If true, then err == EBADKEY is a result of a non-option argument failing + to be parsed (which in some cases isn't actually an error). */ + int arg_ebadkey = 0; + + if (! (flags & ARGP_NO_HELP)) + /* Add our own options. */ + { + struct argp_child *child = alloca (4 * sizeof (struct argp_child)); + struct argp *top_argp = alloca (sizeof (struct argp)); + + /* TOP_ARGP has no options, it just serves to group the user & default + argps. */ + memset (top_argp, 0, sizeof (*top_argp)); + top_argp->children = child; + + memset (child, 0, 4 * sizeof (struct argp_child)); + + if (argp) + (child++)->argp = argp; + (child++)->argp = &argp_default_argp; + if (argp_program_version || argp_program_version_hook) + (child++)->argp = &argp_version_argp; + child->argp = 0; + + argp = top_argp; + } + + /* Construct a parser for these arguments. */ + err = parser_init (&parser, argp, argc, argv, flags, input); + + if (! err) + /* Parse! */ + { + while (! err) + err = parser_parse_next (&parser, &arg_ebadkey); + err = parser_finalize (&parser, err, arg_ebadkey, end_index); + } + + return err; +} +#ifdef weak_alias +weak_alias (__argp_parse, argp_parse) +#endif + +/* Return the input field for ARGP in the parser corresponding to STATE; used + by the help routines. */ +void * +__argp_input (const struct argp *argp, const struct argp_state *state) +{ + if (state) + { + struct group *group; + struct parser *parser = state->pstate; + + for (group = parser->groups; group < parser->egroup; group++) + if (group->argp == argp) + return group->input; + } + + return 0; +} +#ifdef weak_alias +weak_alias (__argp_input, _argp_input) +#endif + +/* Defined here, in case a user is not inlining the definitions in + * argp.h */ +void +argp_usage (__const struct argp_state *__state) +{ +// __argp_state_help (__state, ARGP_HELP_STD_USAGE); +} + +int +__option_is_short (__const struct argp_option *__opt) +{ + if (__opt->flags & OPTION_DOC) + return 0; + else + { + int __key = __opt->key; + /* FIXME: whether or not a particular key implies a short option + * ought not to be locale dependent. */ + return __key > 0 && isprint (__key); + } +} + +int +__option_is_end (__const struct argp_option *__opt) +{ + return !__opt->key && !__opt->name && !__opt->doc && !__opt->group; +} + +#endif diff --git a/6-sigsnoop/eunomia-include/argparse/argparse.c b/6-sigsnoop/eunomia-include/argparse/argparse.c new file mode 100644 index 0000000..b42bced --- /dev/null +++ b/6-sigsnoop/eunomia-include/argparse/argparse.c @@ -0,0 +1,403 @@ +#ifndef ARGPARSE_C_H_ +#define ARGPARSE_C_H_ + +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#include +#include +#include +#include +#include +#include "argparse.h" + +#define OPT_UNSET 1 +#define OPT_LONG (1 << 1) + +/* We define these the same for all machines. + Changes from this to the outside world should be done in `_exit'. */ +#define EXIT_FAILURE 1 /* Failing exit status. */ +#define EXIT_SUCCESS 0 /* Successful exit status. */ + +static const char * +prefix_skip(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) ? NULL : str + len; +} + +static int +prefix_cmp(const char *str, const char *prefix) +{ + for (;; str++, prefix++) + if (!*prefix) { + return 0; + } else if (*str != *prefix) { + return (unsigned char)*prefix - (unsigned char)*str; + } +} + +static void +argparse_error(struct argparse *self, const struct argparse_option *opt, + const char *reason, int flags) +{ + (void)self; + if (flags & OPT_LONG) { + printf("error: option `--%s` %s\n", opt->long_name, reason); + } else { + printf("error: option `-%c` %s\n", opt->short_name, reason); + } + exit(EXIT_FAILURE); +} + +static int +argparse_getvalue(struct argparse *self, const struct argparse_option *opt, + int flags) +{ + const char *s = NULL; + if (!opt->value) + goto skipped; + switch (opt->type) { + case ARGPARSE_OPT_BOOLEAN: + if (flags & OPT_UNSET) { + *(int *)opt->value = *(int *)opt->value - 1; + } else { + *(int *)opt->value = *(int *)opt->value + 1; + } + if (*(int *)opt->value < 0) { + *(int *)opt->value = 0; + } + break; + case ARGPARSE_OPT_BIT: + if (flags & OPT_UNSET) { + *(int *)opt->value &= ~opt->data; + } else { + *(int *)opt->value |= opt->data; + } + break; + case ARGPARSE_OPT_STRING: + if (self->optvalue) { + *(const char **)opt->value = self->optvalue; + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(const char **)opt->value = *++self->argv; + } else { + argparse_error(self, opt, "requires a value", flags); + } + break; + case ARGPARSE_OPT_INTEGER: + // errno = 0; + if (self->optvalue) { + *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0); + } else { + argparse_error(self, opt, "requires a value", flags); + } + // if (errno == ERANGE) + // argparse_error(self, opt, "numerical result out of range", flags); + if (s[0] != '\0') // no digits or contains invalid characters + argparse_error(self, opt, "expects an integer value", flags); + break; + case ARGPARSE_OPT_FLOAT: + // errno = 0; + if (self->optvalue) { + *(float *)opt->value = strtod(self->optvalue, (char **)&s); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(float *)opt->value = strtod(*++self->argv, (char **)&s); + } else { + argparse_error(self, opt, "requires a value", flags); + } + // if (errno == ERANGE) + // argparse_error(self, opt, "numerical result out of range", flags); + if (s[0] != '\0') // no digits or contains invalid characters + argparse_error(self, opt, "expects a numerical value", flags); + break; + default: + exit(EXIT_FAILURE); + } + +skipped: + if (opt->callback) { + return opt->callback(self, opt); + } + return 0; +} + +static void +argparse_options_check(const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + switch (options->type) { + case ARGPARSE_OPT_END: + case ARGPARSE_OPT_BOOLEAN: + case ARGPARSE_OPT_BIT: + case ARGPARSE_OPT_INTEGER: + case ARGPARSE_OPT_FLOAT: + case ARGPARSE_OPT_STRING: + case ARGPARSE_OPT_GROUP: + continue; + default: + printf("wrong option type: %d", options->type); + break; + } + } +} + +static int +argparse_short_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + if (options->short_name == *self->optvalue) { + self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL; + return argparse_getvalue(self, options, 0); + } + } + return -2; +} + +static int +argparse_long_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + const char *rest; + int opt_flags = 0; + if (!options->long_name) + continue; + + rest = prefix_skip(self->argv[0] + 2, options->long_name); + if (!rest) { + // negation disabled? + if (options->flags & OPT_NONEG) { + continue; + } + // only OPT_BOOLEAN/OPT_BIT supports negation + if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != + ARGPARSE_OPT_BIT) { + continue; + } + + if (prefix_cmp(self->argv[0] + 2, "no-")) { + continue; + } + rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name); + if (!rest) + continue; + opt_flags |= OPT_UNSET; + } + if (*rest) { + if (*rest != '=') + continue; + self->optvalue = rest + 1; + } + return argparse_getvalue(self, options, opt_flags | OPT_LONG); + } + return -2; +} + +int +argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags) +{ + memset(self, 0, sizeof(*self)); + self->options = options; + self->usages = usages; + self->flags = flags; + self->description = NULL; + self->epilog = NULL; + return 0; +} + +void +argparse_describe(struct argparse *self, const char *description, + const char *epilog) +{ + self->description = description; + self->epilog = epilog; +} + +int +argparse_parse(struct argparse *self, int argc, const char **argv) +{ + self->argc = argc - 1; + self->argv = argv + 1; + self->out = argv; + + argparse_options_check(self->options); + + for (; self->argc; self->argc--, self->argv++) { + const char *arg = self->argv[0]; + if (arg[0] != '-' || !arg[1]) { + if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) { + goto end; + } + // if it's not option or is a single char '-', copy verbatim + self->out[self->cpidx++] = self->argv[0]; + continue; + } + // short option + if (arg[1] != '-') { + self->optvalue = arg + 1; + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + while (self->optvalue) { + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + } + continue; + } + // if '--' presents + if (!arg[2]) { + self->argc--; + self->argv++; + break; + } + // long option + switch (argparse_long_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + continue; + +unknown: + printf("error: unknown option `%s`\n", self->argv[0]); + argparse_usage(self); + if (!(self->flags & ARGPARSE_IGNORE_UNKNOWN_ARGS)) { + exit(EXIT_FAILURE); + } + } + +end: + memmove(self->out + self->cpidx, self->argv, + self->argc * sizeof(*self->out)); + self->out[self->cpidx + self->argc] = NULL; + + return self->cpidx + self->argc; +} + +void +argparse_usage(struct argparse *self) +{ + if (self->usages) { + printf("Usage: %s\n", *self->usages++); + while (*self->usages && **self->usages) + printf(" or: %s\n", *self->usages++); + } else { + printf("Usage:\n"); + } + + // print description + if (self->description) + printf("%s\n", self->description); + + putchar('\n'); + + const struct argparse_option *options; + + // figure out best width + size_t usage_opts_width = 0; + size_t len; + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + len = 0; + if ((options)->short_name) { + len += 2; + } + if ((options)->short_name && (options)->long_name) { + len += 2; // separator ", " + } + if ((options)->long_name) { + len += strlen((options)->long_name) + 2; + } + if (options->type == ARGPARSE_OPT_INTEGER) { + len += strlen("="); + } + if (options->type == ARGPARSE_OPT_FLOAT) { + len += strlen("="); + } else if (options->type == ARGPARSE_OPT_STRING) { + len += strlen("="); + } + len = (len + 3) - ((len + 3) & 3); + if (usage_opts_width < len) { + usage_opts_width = len; + } + } + usage_opts_width += 4; // 4 spaces prefix + + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + size_t pos = 0; + size_t pad = 0; + if (options->type == ARGPARSE_OPT_GROUP) { + putchar('\n'); + printf("%s", options->help); + putchar('\n'); + continue; + } + pos = printf(" "); + if (options->short_name) { + pos += printf("-%c", options->short_name); + } + if (options->long_name && options->short_name) { + pos += printf(", "); + } + if (options->long_name) { + pos += printf("--%s", options->long_name); + } + if (options->type == ARGPARSE_OPT_INTEGER) { + pos += printf("="); + } else if (options->type == ARGPARSE_OPT_FLOAT) { + pos += printf("="); + } else if (options->type == ARGPARSE_OPT_STRING) { + pos += printf("="); + } + if (pos <= usage_opts_width) { + pad = usage_opts_width - pos; + } else { + putchar('\n'); + pad = usage_opts_width; + } + printf(" %s\n", options->help); + } + + // print epilog + if (self->epilog) + printf("%s\n", self->epilog); +} + +int +argparse_help_cb_no_exit(struct argparse *self, + const struct argparse_option *option) +{ + (void)option; + argparse_usage(self); + return (EXIT_SUCCESS); +} + +int +argparse_help_cb(struct argparse *self, const struct argparse_option *option) +{ + argparse_help_cb_no_exit(self, option); + exit(EXIT_SUCCESS); +} + +#endif /* ARGPARSE_C_H */ diff --git a/6-sigsnoop/eunomia-include/argparse/argparse.h b/6-sigsnoop/eunomia-include/argparse/argparse.h new file mode 100644 index 0000000..fd1ddfc --- /dev/null +++ b/6-sigsnoop/eunomia-include/argparse/argparse.h @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#ifndef ARGPARSE_H +#define ARGPARSE_H + +/* For c++ compatibility */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct argparse; +struct argparse_option; + +typedef int argparse_callback (struct argparse *self, + const struct argparse_option *option); + +enum argparse_flag { + ARGPARSE_STOP_AT_NON_OPTION = 1 << 0, + ARGPARSE_IGNORE_UNKNOWN_ARGS = 1 << 1, +}; + +enum argparse_option_type { + /* special */ + ARGPARSE_OPT_END, + ARGPARSE_OPT_GROUP, + /* options with no arguments */ + ARGPARSE_OPT_BOOLEAN, + ARGPARSE_OPT_BIT, + /* options with arguments (optional or required) */ + ARGPARSE_OPT_INTEGER, + ARGPARSE_OPT_FLOAT, + ARGPARSE_OPT_STRING, +}; + +enum argparse_option_flags { + OPT_NONEG = 1, /* disable negation */ +}; + +/** + * argparse option + * + * `type`: + * holds the type of the option, you must have an ARGPARSE_OPT_END last in your + * array. + * + * `short_name`: + * the character to use as a short option name, '\0' if none. + * + * `long_name`: + * the long option name, without the leading dash, NULL if none. + * + * `value`: + * stores pointer to the value to be filled. + * + * `help`: + * the short help message associated to what the option does. + * Must never be NULL (except for ARGPARSE_OPT_END). + * + * `callback`: + * function is called when corresponding argument is parsed. + * + * `data`: + * associated data. Callbacks can use it like they want. + * + * `flags`: + * option flags. + */ +struct argparse_option { + enum argparse_option_type type; + const char short_name; + const char *long_name; + void *value; + const char *help; + argparse_callback *callback; + intptr_t data; + int flags; +}; + +/** + * argpparse + */ +struct argparse { + // user supplied + const struct argparse_option *options; + const char *const *usages; + int flags; + const char *description; // a description after usage + const char *epilog; // a description at the end + // internal context + int argc; + const char **argv; + const char **out; + int cpidx; + const char *optvalue; // current option value +}; + +// built-in callbacks +int argparse_help_cb(struct argparse *self, + const struct argparse_option *option); +int argparse_help_cb_no_exit(struct argparse *self, + const struct argparse_option *option); + +// built-in option macros +#define OPT_END() { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 } +#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ } +#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ } +#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ } +#define OPT_FLOAT(...) { ARGPARSE_OPT_FLOAT, __VA_ARGS__ } +#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ } +#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 } +#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \ + "show this help message and exit", \ + argparse_help_cb, 0, OPT_NONEG) + +int argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags); +void argparse_describe(struct argparse *self, const char *description, + const char *epilog); +int argparse_parse(struct argparse *self, int argc, const char **argv); +void argparse_usage(struct argparse *self); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/6-sigsnoop/eunomia-include/cJSON/cJSON.c b/6-sigsnoop/eunomia-include/cJSON/cJSON.c new file mode 100644 index 0000000..270c1c8 --- /dev/null +++ b/6-sigsnoop/eunomia-include/cJSON/cJSON.c @@ -0,0 +1,2917 @@ +#ifndef CJSON_SRC_H +#define CJSON_SRC_H +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning(push) +/* disable warning about single line comments in system headers */ +#pragma warning(disable : 4001) +#endif + +#ifndef true +/* define our own boolean type */ +#define true ((cJSON_bool)1) +#endif +#ifndef false +#define false ((cJSON_bool)0) +#endif + +// declare to disable warning +void *realloc(void *__ptr, size_t __size); + +#include "cJSON.h" +#include +#include +#include +#include +#include + +// a basic strtod implementation +double +strtod(const char *str, char **endptr) +{ + double result = 0.0; + char signedResult = '\0'; + char signedExponent = '\0'; + int decimals = 0; + int isExponent = false; + int hasExponent = false; + int hasResult = false; + // exponent is logically int but is coded as double so that its eventual + // overflow detection can be the same as for double result + double exponent = 0; + char c; + + for (; '\0' != (c = *str); ++str) { + if ((c >= '0') && (c <= '9')) { + int digit = c - '0'; + if (isExponent) { + exponent = (10 * exponent) + digit; + hasExponent = true; + } + else if (decimals == 0) { + result = (10 * result) + digit; + hasResult = true; + } + else { + result += (double)digit / decimals; + decimals *= 10; + } + continue; + } + + if (c == '.') { + if (!hasResult) + break; // don't allow leading '.' + if (isExponent) + break; // don't allow decimal places in exponent + if (decimals != 0) + break; // this is the 2nd time we've found a '.' + + decimals = 10; + continue; + } + + if ((c == '-') || (c == '+')) { + if (isExponent) { + if (signedExponent || (exponent != 0)) + break; + else + signedExponent = c; + } + else { + if (signedResult || (result != 0)) + break; + else + signedResult = c; + } + continue; + } + + if (c == 'E') { + if (!hasResult) + break; // don't allow leading 'E' + if (isExponent) + break; + else + isExponent = true; + continue; + } + + break; // unexpected character + } + + if (isExponent && !hasExponent) { + while (*str != 'E') + --str; + } + + if (!hasResult && signedResult) + --str; + + if (endptr) + *endptr = (char *)(str); + + for (; exponent != 0; --exponent) { + if (signedExponent == '-') + result /= 10; + else + result *= 10; + } + + if (signedResult == '-' && result != 0) + result = -result; + + return result; +} + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char *)(global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item) +{ + if (!cJSON_IsString(item)) { + return NULL; + } + + return item->valuestring; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and + * header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) \ + || (CJSON_VERSION_PATCH != 10) +#error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char *) cJSON_Version(void) +{ + static char version[15]; + snprintf(version, sizeof(version), "%i.%i.%i", CJSON_VERSION_MAJOR, + CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal + * though */ +static int +case_insensitive_strcmp(const unsigned char *string1, + const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) { + return 1; + } + + if (string1 == string2) { + return 0; + } + + for (; tolower(*string1) == tolower(*string2); (void)string1++, string2++) { + if (*string1 == '\0') { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks { + void *(CJSON_CDECL *allocate)(size_t size); + void(CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dillimport '...' + is not static */ +static void *CJSON_CDECL +internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL +internal_free(void *pointer) +{ + free(pointer); +} +static void *CJSON_CDECL +internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* clang-format off */ +static internal_hooks global_hooks = { + internal_malloc, + internal_free, + internal_realloc +}; +/* clang-format on */ + +static unsigned char * +cJSON_strdup(const unsigned char *string, const internal_hooks *const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) { + return NULL; + } + + length = strlen((const char *)string) + sizeof(""); + copy = (unsigned char *)hooks->allocate(length); + if (copy == NULL) { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks *hooks) +{ + if (hooks == NULL) { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) + && (global_hooks.deallocate == free)) { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON * +cJSON_New_Item(const internal_hooks *const hooks) +{ + cJSON *node = (cJSON *)hooks->allocate(sizeof(cJSON)); + if (node) { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char +get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char)lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct { + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the + current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting + * with 1) */ +#define can_read(buffer, size) \ + ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) \ + ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) \ + (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result + into item. */ +static cJSON_bool +parse_number(cJSON *const item, parse_buffer *const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal + * point of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking + * the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) + && can_access_at_index(input_buffer, i); + i++) { + switch (buffer_at_offset(input_buffer)[i]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char *)number_c_string, (char **)&after_end); + if (number_c_string == after_end) { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) { + item->valueint = INT_MIN; + } + else { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or + * double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) { + object->valueint = INT_MIN; + } + else { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +typedef struct { + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char * +ensure(printbuffer *const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) { + newsize = INT_MAX; + } + else { + return NULL; + } + } + else { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) { + /* reallocate with realloc if available */ + newbuffer = (unsigned char *)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else { + /* otherwise reallocate manually */ + newbuffer = (unsigned char *)p->hooks.allocate(newsize); + if (!newbuffer) { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + if (newbuffer) { + memcpy(newbuffer, p->buffer, p->offset + 1); + } + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset + */ +static void +update_offset(printbuffer *const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char *)buffer_pointer); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool +print_number(const cJSON *const item, printbuffer *const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char + number_buffer[26]; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test; + + if (output_buffer == NULL) { + return false; + } + + /* This checks for NaN and Infinity */ + if ((d * 0) != 0) { + length = snprintf((char *)number_buffer, sizeof(number_buffer), "null"); + } + else { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero + * digits */ + length = + snprintf((char *)number_buffer, sizeof(number_buffer), "%1.15g", d); + } + + /* snprintf failed or buffer overrun occured */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) { + if (number_buffer[i] == decimal_point) { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned +parse_hex4(const unsigned char *const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) { + h += (unsigned int)input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) { + h += (unsigned int)10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) { + h += (unsigned int)10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char +utf16_literal_to_utf8(const unsigned char *const input_pointer, + const unsigned char *const input_end, + unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) { + /* invalid second half of the surrogate pair */ + goto fail; + } + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = + 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; + utf8_position--) { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = + (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) { + (*output_pointer)[0] = + (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool +parse_string(cJSON *const item, parse_buffer *const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while ( + ((size_t)(input_end - input_buffer->content) < input_buffer->length) + && (*input_end != '\"')) { + /* is escape sequence */ + if (input_end[0] == '\\') { + if ((size_t)(input_end + 1 - input_buffer->content) + >= input_buffer->length) { + /* prevent buffer overflow when last input character is a + * backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) + >= input_buffer->length) + || (*input_end != '\"')) { + goto fail; + /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t)(input_end - buffer_at_offset(input_buffer)) + - skipped_bytes; + output = (unsigned char *)input_buffer->hooks.allocate(allocation_length + + sizeof("")); + if (output == NULL) { + goto fail; + /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) { + if (*input_pointer != '\\') { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) { + goto fail; + } + + switch (input_pointer[1]) { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8( + input_pointer, input_end, &output_pointer); + if (sequence_length == 0) { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char *)output; + + input_buffer->offset = (size_t)(input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool +print_string_ptr(const unsigned char *const input, + printbuffer *const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL, *output_end; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) { + return false; + } + + /* empty string */ + if (input == NULL) { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) { + return false; + } + strcpy((char *)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) { + switch (*input_pointer) { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) { + return false; + } + output_end = output + output_length + sizeof("\"\""); + + /* no characters have to be escaped */ + if (escape_characters == 0) { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; + (void)input_pointer++, output_pointer++) { + if ((*input_pointer > 31) && (*input_pointer != '\"') + && (*input_pointer != '\\')) { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + snprintf((char *)output_pointer, + output_end - output_pointer, "u%04x", + *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool +print_string(const cJSON *const item, printbuffer *const p) +{ + return print_string_ptr((unsigned char *)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool +parse_value(cJSON *const item, parse_buffer *const input_buffer); +static cJSON_bool +print_value(const cJSON *const item, printbuffer *const output_buffer); +static cJSON_bool +parse_array(cJSON *const item, parse_buffer *const input_buffer); +static cJSON_bool +print_array(const cJSON *const item, printbuffer *const output_buffer); +static cJSON_bool +parse_object(cJSON *const item, parse_buffer *const input_buffer); +static cJSON_bool +print_object(const cJSON *const item, printbuffer *const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer * +buffer_skip_whitespace(parse_buffer *const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) { + return NULL; + } + + while (can_access_at_index(buffer, 0) + && (buffer_at_offset(buffer)[0] <= 32)) { + buffer->offset++; + } + + if (buffer->offset == buffer->length) { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer * +skip_utf8_bom(parse_buffer *const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) + || (buffer->offset != 0)) { + return NULL; + } + + if (can_access_at_index(buffer, 4) + && (strncmp((const char *)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) + == 0)) { + buffer->offset += 3; + } + + return buffer; +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) +cJSON_ParseWithOpts(const char *value, const char **return_parse_end, + cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL) { + goto fail; + } + + buffer.content = (const unsigned char *)value; + buffer.length = strlen((const char *)value) + sizeof(""); + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and + * then check for a null terminator */ + if (require_null_terminated) { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) + || buffer_at_offset(&buffer)[0] != '\0') { + goto fail; + } + } + if (return_parse_end) { + *return_parse_end = (const char *)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) { + cJSON_Delete(item); + } + + if (value != NULL) { + error local_error; + local_error.json = (const unsigned char *)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) { + *return_parse_end = + (const char *)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +#define cjson_min(a, b) ((a < b) ? a : b) + +static unsigned char * +print(const cJSON *const item, cJSON_bool format, + const internal_hooks *const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char *)hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) { + printed = (unsigned char *)hooks->reallocate(buffer->buffer, + buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char *)hooks->allocate(buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + memcpy(printed, buffer->buffer, + cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char *)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char *)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) +cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) { + return NULL; + } + + p.buffer = (unsigned char *)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char *)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) +cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, + const cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((len < 0) || (buf == NULL)) { + return false; + } + + p.buffer = (unsigned char *)buf; + p.length = (size_t)len; + p.offset = 0; + p.noalloc = true; + p.format = fmt; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool +parse_value(cJSON *const item, parse_buffer *const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) + && (strncmp((const char *)buffer_at_offset(input_buffer), "null", 4) + == 0)) { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) + && (strncmp((const char *)buffer_at_offset(input_buffer), "false", 5) + == 0)) { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) + && (strncmp((const char *)buffer_at_offset(input_buffer), "true", 4) + == 0)) { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == '\"')) { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) + && ((buffer_at_offset(input_buffer)[0] == '-') + || ((buffer_at_offset(input_buffer)[0] >= '0') + && (buffer_at_offset(input_buffer)[0] <= '9')))) { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == '[')) { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == '{')) { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool +print_value(const cJSON *const item, printbuffer *const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) { + return false; + } + + switch ((item->type) & 0xFF) { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) { + return false; + } + strcpy((char *)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) { + return false; + } + strcpy((char *)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) { + return false; + } + strcpy((char *)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool +parse_array(cJSON *const item, parse_buffer *const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == ']')) { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) { + goto fail; + /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) { + /* start the linked list */ + current_item = head = new_item; + } + else { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) { + goto fail; + /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } while (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) + || buffer_at_offset(input_buffer)[0] != ']') { + goto fail; + /* expected end of array */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool +print_array(const cJSON *const item, printbuffer *const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) { + if (!print_value(current_element, output_buffer)) { + return false; + } + update_offset(output_buffer); + if (current_element->next) { + length = (size_t)(output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) { + return false; + } + *output_pointer++ = ','; + if (output_buffer->format) { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool +parse_object(cJSON *const item, parse_buffer *const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) + || (buffer_at_offset(input_buffer)[0] != '{')) { + goto fail; + /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == '}')) { + goto success; + /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) { + goto fail; + /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) { + /* start the linked list */ + current_item = head = new_item; + } + else { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) { + goto fail; + /* faile to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) + || (buffer_at_offset(input_buffer)[0] != ':')) { + goto fail; + /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) { + goto fail; + /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } while (can_access_at_index(input_buffer, 0) + && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) + || (buffer_at_offset(input_buffer)[0] != '}')) { + goto fail; + /* expected end of object */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool +print_object(const cJSON *const item, printbuffer *const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) { + return false; + } + + /* Compose the output: */ + length = (size_t)(output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) { + if (output_buffer->format) { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) { + return false; + } + for (i = 0; i < output_buffer->depth; i++) { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char *)current_item->string, + output_buffer)) { + return false; + } + update_offset(output_buffer); + + length = (size_t)(output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) { + return false; + } + if (current_item->next) { + *output_pointer++ = ','; + } + + if (output_buffer->format) { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure( + output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) { + return false; + } + if (output_buffer->format) { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) { + return 0; + } + + child = array->child; + + while (child != NULL) { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON * +get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON * +get_object_item(const cJSON *const object, const char *const name, + const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) { + return NULL; + } + + current_element = object->child; + if (case_sensitive) { + while ((current_element != NULL) && (current_element->string != NULL) + && (strcmp(name, current_element->string) != 0)) { + current_element = current_element->next; + } + } + else { + while ((current_element != NULL) + && (case_insensitive_strcmp( + (const unsigned char *)name, + (const unsigned char *)(current_element->string)) + != 0)) { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) +cJSON_GetObjectItem(const cJSON *const object, const char *const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) +cJSON_GetObjectItemCaseSensitive(const cJSON *const object, + const char *const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) +cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void +suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON * +create_reference(const cJSON *item, const internal_hooks *const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool +add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL)) { + return false; + } + + child = array->child; + + if (child == NULL) { + /* list is empty, start new one */ + array->child = item; + } + else { + /* append to the end */ + while (child->next) { + child = child->next; + } + suffix_object(child, item); + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) \ + || (defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void * +cast_away_const(const void *string) +{ + return (void *)string; +} +#if defined(__clang__) \ + || (defined(__GNUC__) \ + && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic pop +#endif + +static cJSON_bool +add_item_to_object(cJSON *const object, const char *const string, + cJSON *const item, const internal_hooks *const hooks, + const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL)) { + return false; + } + + if (constant_key) { + new_key = (char *)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else { + new_key = (char *)cJSON_strdup((const unsigned char *)string, hooks); + if (new_key == NULL) { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) +cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) +cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) +cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) +cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) { + return false; + } + + return add_item_to_object(object, string, + create_reference(item, &global_hooks), + &global_hooks, false); +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddNullToObject(cJSON *const object, const char *const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddTrueToObject(cJSON *const object, const char *const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddFalseToObject(cJSON *const object, const char *const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddBoolToObject(cJSON *const object, const char *const name, + const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddNumberToObject(cJSON *const object, const char *const name, + const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddStringToObject(cJSON *const object, const char *const name, + const char *const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddRawToObject(cJSON *const object, const char *const name, + const char *const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddObjectToObject(cJSON *const object, const char *const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_AddArrayToObject(cJSON *const object, const char *const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) +cJSON_DetachItemViaPointer(cJSON *parent, cJSON *const item) +{ + if ((parent == NULL) || (item == NULL)) { + return NULL; + } + + if (item->prev != NULL) { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) { + /* first element */ + parent->child = item->next; + } + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, + get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) +cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) +cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) +cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) +cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) { + array->child = newitem; + } + else { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) +cJSON_ReplaceItemViaPointer(cJSON *const parent, cJSON *const item, + cJSON *replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) { + return false; + } + + if (replacement == item) { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) { + replacement->next->prev = replacement; + } + if (replacement->prev != NULL) { + replacement->prev->next = replacement; + } + if (parent->child == item) { + parent->child = replacement; + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(void) +cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) { + return; + } + + cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), + newitem); +} + +static cJSON_bool +replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, + cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) + && (replacement->string != NULL)) { + cJSON_free(replacement->string); + } + replacement->string = + (char *)cJSON_strdup((const unsigned char *)string, &global_hooks); + replacement->type &= ~cJSON_StringIsConst; + + cJSON_ReplaceItemViaPointer( + object, get_object_item(object, string, case_sensitive), replacement); + + return true; +} + +CJSON_PUBLIC(void) +cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(void) +cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, + cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = b ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) { + item->valueint = INT_MIN; + } + else { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_String; + item->valuestring = + (char *)cJSON_strdup((const unsigned char *)string, &global_hooks); + if (!item->valuestring) { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char *)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON *)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON *)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_Raw; + item->valuestring = + (char *)cJSON_strdup((const unsigned char *)raw, &global_hooks); + if (!item->valuestring) { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + for (i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateNumber(numbers[i]); + if (!n) { + cJSON_Delete(a); + return NULL; + } + if (!i) { + a->child = n; + } + else { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateNumber((double)numbers[i]); + if (!n) { + cJSON_Delete(a); + return NULL; + } + if (!i) { + a->child = n; + } + else { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateNumber(numbers[i]); + if (!n) { + cJSON_Delete(a); + return NULL; + } + if (!i) { + a->child = n; + } + else { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) { + n = cJSON_CreateString(strings[i]); + if (!n) { + cJSON_Delete(a); + return NULL; + } + if (!i) { + a->child = n; + } + else { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) { + newitem->valuestring = (char *)cJSON_strdup( + (unsigned char *)item->valuestring, &global_hooks); + if (!newitem->valuestring) { + goto fail; + } + } + if (item->string) { + newitem->string = (item->type & cJSON_StringIsConst) + ? item->string + : (char *)cJSON_strdup( + (unsigned char *)item->string, &global_hooks); + if (!newitem->string) { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) { + newchild = cJSON_Duplicate( + child, + true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) { + goto fail; + } + if (next != NULL) { + /* If newitem->child already set, then crosswire ->prev and ->next + * and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + + return newitem; + +fail: + if (newitem != NULL) { + cJSON_Delete(newitem); + } + + return NULL; +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + unsigned char *into = (unsigned char *)json; + + if (json == NULL) { + return; + } + + while (*json) { + if (*json == ' ') { + json++; + } + else if (*json == '\t') { + /* Whitespace characters. */ + json++; + } + else if (*json == '\r') { + json++; + } + else if (*json == '\n') { + json++; + } + else if ((*json == '/') && (json[1] == '/')) { + /* double-slash comments, to end of line. */ + while (*json && (*json != '\n')) { + json++; + } + } + else if ((*json == '/') && (json[1] == '*')) { + /* multiline comments. */ + while (*json && !((*json == '*') && (json[1] == '/'))) { + json++; + } + json += 2; + } + else if (*json == '\"') { + /* string literals, which are \" sensitive. */ + *into++ = (unsigned char)*json++; + while (*json && (*json != '\"')) { + if (*json == '\\') { + *into++ = (unsigned char)*json++; + } + *into++ = (unsigned char)*json++; + } + *into++ = (unsigned char)*json++; + } + else { + /* All other characters. */ + *into++ = (unsigned char)*json++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON *const item) +{ + if (item == NULL) { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) +cJSON_Compare(const cJSON *const a, const cJSON *const b, + const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) + || cJSON_IsInvalid(a)) { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) { + return true; + } + + switch (a->type & 0xFF) { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (a->valuedouble == b->valuedouble) { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = + get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a + * subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = + get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} +#endif diff --git a/6-sigsnoop/eunomia-include/cJSON/cJSON.h b/6-sigsnoop/eunomia-include/cJSON/cJSON.h new file mode 100644 index 0000000..6f42eb4 --- /dev/null +++ b/6-sigsnoop/eunomia-include/cJSON/cJSON.h @@ -0,0 +1,358 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + A header only cJSON library for C and C++. + */ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(__WINDOWS__) \ + && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) \ + || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/** + * When compiling for windows, we specify a specific calling convention to avoid + * issues where we are being called from a project with a different default + * calling convention. For windows you have 3 define options: + * CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever + * dllexport symbols + * CJSON_EXPORT_SYMBOLS - Define this on library build when you want to + * dllexport symbols (default) + * CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + * + * For *nix builds that support visibility attribute, you can define similar + * behavior by setting default visibility to hidden by adding + * -fvisibility=hidden (for gcc) + * or + * -xldscope=hidden (for sun cc) + * to CFLAGS, then using the CJSON_API_VISIBILITY flag to "export" the same + * symbols the way CJSON_EXPORT_SYMBOLS does + */ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and + header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) \ + && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined(__SUNPRO_C)) \ + && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 10 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON { + /* next/prev allow you to walk array/object chains. Alternatively, use + GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of + the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list + of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks { + /* malloc/free are CDECL on Windows regardless of the default calling + * convention of the compiler, so ensure the hooks allow passing those + * functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void(CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse + them. This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char *) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks *hooks); + +/* Memory Management: the caller is always responsible to free the results from + * all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib + * free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is + * cJSON_PrintPreallocated, where the caller has full responsibility of the + * buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. + */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +/* ParseWithOpts allows you to require (and check) that the JSON is null + * terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then + * return_parse_end will contain a pointer to the error so will match + * cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) +cJSON_ParseWithOpts(const char *value, const char **return_parse_end, + cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess + * at the final size. guessing well reduces reallocation. fmt=0 gives + * unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) +cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with + * given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will + * use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) +cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, + const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *c); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if + * unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) +cJSON_GetObjectItem(const cJSON *const object, const char *const string); +CJSON_PUBLIC(cJSON *) +cJSON_GetObjectItemCaseSensitive(const cJSON *const object, + const char *const string); +CJSON_PUBLIC(cJSON_bool) +cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. + * You'll probably need to look a few chars back to make sense of it. Defined + * when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check if the item is a string and return its valuestring */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON *const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON *const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/arrray that only references it's elements so + they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) +cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and + * will definitely survive the cJSON object. WARNING: When this function was + * used, make sure to always check that (item->type & cJSON_StringIsConst) is + * zero before writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) +cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you + * want to add an existing cJSON to a new cJSON, but don't want to corrupt your + * existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) +cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) +cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detatch items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) +cJSON_DetachItemViaPointer(cJSON *parent, cJSON *const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) +cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) +cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) +cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) +cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) +cJSON_InsertItemInArray( + cJSON *array, int which, + cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) +cJSON_ReplaceItemViaPointer(cJSON *const parent, cJSON *const item, + cJSON *replacement); +CJSON_PUBLIC(void) +cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(void) +cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem); +CJSON_PUBLIC(void) +cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, + cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new + memory that will need to be released. With recurse!=0, it will duplicate any + children connected to the item. The item->next and ->prev pointers are always + zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or + * invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or + * case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) +cJSON_Compare(const cJSON *const a, const cJSON *const b, + const cJSON_bool case_sensitive); + +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON *) +cJSON_AddNullToObject(cJSON *const object, const char *const name); +CJSON_PUBLIC(cJSON *) +cJSON_AddTrueToObject(cJSON *const object, const char *const name); +CJSON_PUBLIC(cJSON *) +cJSON_AddFalseToObject(cJSON *const object, const char *const name); +CJSON_PUBLIC(cJSON *) +cJSON_AddBoolToObject(cJSON *const object, const char *const name, + const cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) +cJSON_AddNumberToObject(cJSON *const object, const char *const name, + const double number); +CJSON_PUBLIC(cJSON *) +cJSON_AddStringToObject(cJSON *const object, const char *const name, + const char *const string); +CJSON_PUBLIC(cJSON *) +cJSON_AddRawToObject(cJSON *const object, const char *const name, + const char *const raw); +CJSON_PUBLIC(cJSON *) +cJSON_AddObjectToObject(cJSON *const object, const char *const name); +CJSON_PUBLIC(cJSON *) +cJSON_AddArrayToObject(cJSON *const object, const char *const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble + too. */ +#define cJSON_SetIntValue(object, number) \ + ((object) ? (object)->valueint = (object)->valuedouble = (number) \ + : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) \ + ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) \ + : (number)) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) \ + for (element = (array != NULL) ? (array)->child : NULL; element != NULL; \ + element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with + cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#endif \ No newline at end of file diff --git a/6-sigsnoop/eunomia-include/entry.h b/6-sigsnoop/eunomia-include/entry.h new file mode 100644 index 0000000..0aa5657 --- /dev/null +++ b/6-sigsnoop/eunomia-include/entry.h @@ -0,0 +1,40 @@ +#ifndef ENTRY_H_ +#define ENTRY_H_ + +// header only helpers for develop wasm app +#include "cJSON/cJSON.c" +#include "helpers.h" + +#define MAX_ARGS 32 + +int main(int argc, char **argv); +int bpf_main(char *env_json, int str_len) +{ + cJSON *env = cJSON_Parse(env_json); + if (!env) + { + printf("cJSON_Parse failed for env json args."); + return 1; + } + if (!cJSON_IsArray(env)) { + printf("env json args is not an array."); + return 1; + } + int argc = cJSON_GetArraySize(env); + if (argc > MAX_ARGS) { + printf("env json args is too long."); + return 1; + } + char *argv[MAX_ARGS]; + for (int i = 0; i < argc; i++) { + cJSON *item = cJSON_GetArrayItem(env, i); + if (!cJSON_IsString(item)) { + printf("env json args is not a string."); + return 1; + } + argv[i] = item->valuestring; + } + return main(argc, argv); +} + +#endif diff --git a/6-sigsnoop/eunomia-include/errno-base.h b/6-sigsnoop/eunomia-include/errno-base.h new file mode 100644 index 0000000..9653140 --- /dev/null +++ b/6-sigsnoop/eunomia-include/errno-base.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _ASM_GENERIC_ERRNO_BASE_H +#define _ASM_GENERIC_ERRNO_BASE_H + +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define ESRCH 3 /* No such process */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOEXEC 8 /* Exec format error */ +#define EBADF 9 /* Bad file number */ +#define ECHILD 10 /* No child processes */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define ENOTBLK 15 /* Block device required */ +#define EBUSY 16 /* Device or resource busy */ +#define EEXIST 17 /* File exists */ +#define EXDEV 18 /* Cross-device link */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define ENFILE 23 /* File table overflow */ +#define EMFILE 24 /* Too many open files */ +#define ENOTTY 25 /* Not a typewriter */ +#define ETXTBSY 26 /* Text file busy */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define ESPIPE 29 /* Illegal seek */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define EPIPE 32 /* Broken pipe */ +#define EDOM 33 /* Math argument out of domain of func */ +#define ERANGE 34 /* Math result not representable */ + +#endif diff --git a/6-sigsnoop/eunomia-include/helpers.h b/6-sigsnoop/eunomia-include/helpers.h new file mode 100644 index 0000000..cda0c75 --- /dev/null +++ b/6-sigsnoop/eunomia-include/helpers.h @@ -0,0 +1,54 @@ +#ifndef EWASM_APP_HELPERS_H_ +#define EWASM_APP_HELPERS_H_ + +#include "native-ewasm.h" +#include +#include +#include +#include +#include "cJSON/cJSON.h" + +/// @brief start the eBPF program with JSON and wait for it to exit +/// @param program_data the json data of eBPF program +/// @return 0 on success, -1 on failure, the eBPF program will be terminated in failure case +int +start_bpf_program(char *program_data) +{ + int res = create_bpf(program_data, strlen(program_data)); + if (res < 0) { + printf("create_bpf failed %d", res); + return -1; + } + res = run_bpf(res); + if (res < 0) { + printf("run_bpf failed %d\n", res); + return -1; + } + res = wait_and_poll_bpf(res); + if (res < 0) { + printf("wait_and_poll_bpf failed %d\n", res); + return -1; + } + return 0; +} + +/// @brief set the global variable of bpf program to the value +/// @param program the json program data +/// @param key global +/// @param value arg value +/// @return new eBPF program +cJSON * +set_bpf_program_global_var(cJSON *program, char *key, cJSON *value) +{ + + cJSON *args = cJSON_GetObjectItem(program, "runtime_args"); + if (args == NULL) + { + args = cJSON_CreateObject(); + cJSON_AddItemToObject(program, "runtime_args", args); + } + cJSON_AddItemToObject(args, key, value); + return program; +} + +#endif // EWASM_APP_INIT_H diff --git a/6-sigsnoop/eunomia-include/native-ewasm.h b/6-sigsnoop/eunomia-include/native-ewasm.h new file mode 100644 index 0000000..975d675 --- /dev/null +++ b/6-sigsnoop/eunomia-include/native-ewasm.h @@ -0,0 +1,50 @@ +#ifndef EWASM_NATIVE_API_H_ +#define EWASM_NATIVE_API_H_ + +/// c function interface to called from wasm +#ifdef __cplusplus +extern "C" { +#endif +/// @brief create a ebpf program with json data +/// @param ebpf_json +/// @return id on success, -1 on failure +int +create_bpf(char *ebpf_json, int str_len); + +/// @brief start running the ebpf program +/// @details load and attach the ebpf program to the kernel to run the ebpf +/// program if the ebpf program has maps to export to user space, you need to +/// call the wait and export. +int +run_bpf(int id); + +/// @brief wait for the program to exit and receive data from export maps and +/// print the data +/// @details if the program has a ring buffer or perf event to export data +/// to user space, the program will help load the map info and poll the +/// events automatically. +int +wait_and_poll_bpf(int id); +#ifdef __cplusplus +} +#endif + + +/// @brief init the eBPF program +/// @param env_json the env config from input +/// @return 0 on success, -1 on failure, the eBPF program will be terminated in +/// failure case +int +bpf_main(char *env_json, int str_len); + +/// @brief handle the event output from the eBPF program, valid only when +/// wait_and_poll_events is called +/// @param ctx user defined context +/// @param e json event message +/// @return 0 on success, -1 on failure, +/// the event will be send to next handler in chain on success, or dropped in +/// failure +int +process_event(int ctx, char *e, int str_len); + +#endif // NATIVE_EWASM_H_ diff --git a/6-sigsnoop/eunomia-include/sigsnoop.skel.h b/6-sigsnoop/eunomia-include/sigsnoop.skel.h new file mode 100644 index 0000000..e65696f --- /dev/null +++ b/6-sigsnoop/eunomia-include/sigsnoop.skel.h @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ + +/* THIS FILE IS AUTOGENERATED BY BPFTOOL! */ +#ifndef __SIGSNOOP_BPF_SKEL_H__ +#define __SIGSNOOP_BPF_SKEL_H__ + +extern int errno; +#include + +struct bpf_object_skeleton; +struct bpf_object; +struct bpf_map; +struct bpf_program; +struct bpf_object_open_opts; +struct bpf_link; + +struct sigsnoop_bpf { + struct bpf_object_skeleton *skeleton; + struct bpf_object *obj; + struct { + struct bpf_map *events; + struct bpf_map *values; + struct bpf_map *rodata; + } maps; + struct { + struct bpf_program *kill_entry; + struct bpf_program *kill_exit; + struct bpf_program *tkill_entry; + struct bpf_program *tkill_exit; + struct bpf_program *tgkill_entry; + struct bpf_program *tgkill_exit; + struct bpf_program *sig_trace; + } progs; + struct { + struct bpf_link *kill_entry; + struct bpf_link *kill_exit; + struct bpf_link *tkill_entry; + struct bpf_link *tkill_exit; + struct bpf_link *tgkill_entry; + struct bpf_link *tgkill_exit; + struct bpf_link *sig_trace; + } links; + struct sigsnoop_bpf__rodata { + int filtered_pid; + int target_signal; + bool failed_only; + } *rodata; + +#ifdef __cplusplus + static inline struct sigsnoop_bpf *open(const struct bpf_object_open_opts *opts = nullptr); + static inline struct sigsnoop_bpf *open_and_load(); + static inline int load(struct sigsnoop_bpf *skel); + static inline int attach(struct sigsnoop_bpf *skel); + static inline void detach(struct sigsnoop_bpf *skel); + static inline void destroy(struct sigsnoop_bpf *skel); + static inline const void *elf_bytes(size_t *sz); +#endif /* __cplusplus */ +}; + +static void +sigsnoop_bpf__destroy(struct sigsnoop_bpf *obj) +{ + +} + +static inline int +sigsnoop_bpf__create_skeleton(struct sigsnoop_bpf *obj); + +static inline struct sigsnoop_bpf * +sigsnoop_bpf__open_opts(const struct bpf_object_open_opts *opts) +{ + struct sigsnoop_bpf *obj; + int err; + + obj = (struct sigsnoop_bpf *)calloc(1, sizeof(*obj)); + if (!obj) { + errno = ENOMEM; + return NULL; + } + return obj; +} + +static inline struct sigsnoop_bpf * +sigsnoop_bpf__open(void) +{ + return sigsnoop_bpf__open_opts(NULL); +} + +static inline int +sigsnoop_bpf__load(struct sigsnoop_bpf *obj) +{ + return 0; +} + +static inline struct sigsnoop_bpf * +sigsnoop_bpf__open_and_load(void) +{ + return NULL; +} + +static inline int +sigsnoop_bpf__attach(struct sigsnoop_bpf *obj) +{ + return 0; +} + +static inline void +sigsnoop_bpf__detach(struct sigsnoop_bpf *obj) +{ +} + +static inline const void *sigsnoop_bpf__elf_bytes(size_t *sz); + +static inline int +sigsnoop_bpf__create_skeleton(struct sigsnoop_bpf *obj) +{ + return 0; +} + +#ifdef __cplusplus +struct sigsnoop_bpf *sigsnoop_bpf::open(const struct bpf_object_open_opts *opts) { return sigsnoop_bpf__open_opts(opts); } +struct sigsnoop_bpf *sigsnoop_bpf::open_and_load() { return sigsnoop_bpf__open_and_load(); } +int sigsnoop_bpf::load(struct sigsnoop_bpf *skel) { return sigsnoop_bpf__load(skel); } +int sigsnoop_bpf::attach(struct sigsnoop_bpf *skel) { return sigsnoop_bpf__attach(skel); } +void sigsnoop_bpf::detach(struct sigsnoop_bpf *skel) { sigsnoop_bpf__detach(skel); } +void sigsnoop_bpf::destroy(struct sigsnoop_bpf *skel) { sigsnoop_bpf__destroy(skel); } +const void *sigsnoop_bpf::elf_bytes(size_t *sz) { return sigsnoop_bpf__elf_bytes(sz); } +#endif /* __cplusplus */ + +__attribute__((unused)) static void +sigsnoop_bpf__assert(struct sigsnoop_bpf *s __attribute__((unused))) +{ +#ifdef __cplusplus +#define _Static_assert static_assert +#endif + _Static_assert(sizeof(s->rodata->filtered_pid) == 4, "unexpected size of 'filtered_pid'"); + _Static_assert(sizeof(s->rodata->target_signal) == 4, "unexpected size of 'target_signal'"); + _Static_assert(sizeof(s->rodata->failed_only) == 1, "unexpected size of 'failed_only'"); +#ifdef __cplusplus +#undef _Static_assert +#endif +} + +struct perf_buffer; +void perf_buffer__free(struct perf_buffer *pb) { +} +int perf_buffer__poll(struct perf_buffer *pb, int timeout_ms) { + return start_bpf_program(program_data); +} +int bpf_program__set_autoload(struct bpf_program *prog, bool autoload) { + return 0; +} +char* strerror(int errnum) { + return "error"; +} +int bpf_map__fd(const struct bpf_map *map) { + return 0; +} +typedef void (*perf_buffer_sample_fn)(void *ctx, int cpu, + void *data, unsigned int size); +typedef void (*perf_buffer_lost_fn)(void *ctx, int cpu, unsigned long long cnt); +struct perf_buffer; + +perf_buffer_sample_fn global_cb; +struct perf_buffer_opts; + +struct perf_buffer * +perf_buffer__new(int map_fd, size_t page_cnt, + perf_buffer_sample_fn sample_cb, perf_buffer_lost_fn lost_cb, void *ctx, + const struct perf_buffer_opts *opts) { + global_cb = sample_cb; + return (void*)1; + } + +int process_event(int ctx, char *e, int str_len) +{ + struct event eve = {0}; + cJSON *json = cJSON_Parse(e); + eve.sig = cJSON_GetObjectItem(json, "sig")->valueint; + eve.pid = cJSON_GetObjectItem(json, "pid")->valueint; + strcpy(eve.comm, cJSON_GetObjectItem(json, "comm")->valuestring); + eve.tpid = cJSON_GetObjectItem(json, "tpid")->valueint; + eve.ret = cJSON_GetObjectItem(json, "ret")->valueint; + global_cb((void*)ctx, 0, &eve, str_len); + return 0; +} + +extern const char argp_program_doc[]; + +void argp_state_help(const struct argp_state *__state, int flag) { + printf("%s", argp_program_doc); + exit(0); +} + +#endif /* __SIGSNOOP_BPF_SKEL_H__ */ diff --git a/6-sigsnoop/eunomia-include/wasm-app.h b/6-sigsnoop/eunomia-include/wasm-app.h new file mode 100644 index 0000000..77eee77 --- /dev/null +++ b/6-sigsnoop/eunomia-include/wasm-app.h @@ -0,0 +1,8 @@ +#ifndef EWASM_EWASM_APP_H_ +#define EWASM_EWASM_APP_H_ + +// header only helpers for develop wasm app +#include "cJSON/cJSON.c" +#include "helpers.h" + +#endif // EWASM_EWASM_APP_H diff --git a/6-sigsnoop/sigsnoop.bpf.c b/6-sigsnoop/sigsnoop.bpf.c new file mode 100755 index 0000000..e03981f --- /dev/null +++ b/6-sigsnoop/sigsnoop.bpf.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2021~2022 Hengqi Chen */ +#include +#include +#include "sigsnoop.h" + +#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); + __type(key, __u32); + __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) +{ + struct event event = {}; + __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; + 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; + + if (failed_only && ret >= 0) + goto cleanup; + + eventp->ret = ret; + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, eventp, sizeof(*eventp)); + +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); +} + +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; +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/6-sigsnoop/sigsnoop.h b/6-sigsnoop/sigsnoop.h new file mode 100755 index 0000000..a9826d8 --- /dev/null +++ b/6-sigsnoop/sigsnoop.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +/* Copyright (c) 2021~2022 Hengqi Chen */ +#ifndef __SIGSNOOP_H +#define __SIGSNOOP_H + +#define TASK_COMM_LEN 16 + +struct event { + unsigned int pid; + unsigned int tpid; + int sig; + int ret; + char comm[TASK_COMM_LEN]; +}; + +#endif /* __SIGSNOOP_H */ diff --git a/6-sigsnoop/sigsnoop.md b/6-sigsnoop/sigsnoop.md new file mode 100644 index 0000000..e59540c --- /dev/null +++ b/6-sigsnoop/sigsnoop.md @@ -0,0 +1,92 @@ +## eBPF 入门实践教程:编写 eBPF 程序 sigsnoop 工具监控全局 signal 事件 + +### 背景 + +### 实现原理 + +`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; +} + +``` + + +### Eunomia中使用方式 + +![result](../imgs/sigsnoop.png) +![result](../imgs/sigsnoop-prometheus.png) + +### 总结 diff --git a/7-execsnoop/.gitignore b/7-execsnoop/.gitignore new file mode 100644 index 0000000..0e8325e --- /dev/null +++ b/7-execsnoop/.gitignore @@ -0,0 +1,3 @@ +ecli +package.json + diff --git a/7-execsnoop/README.md b/7-execsnoop/README.md new file mode 100644 index 0000000..804c073 --- /dev/null +++ b/7-execsnoop/README.md @@ -0,0 +1,148 @@ +--- +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. +--- + +## origin + +origin from: + +https://github.com/iovisor/bcc/blob/master/libbpf-tools/execsnoop.bpf.c + +## Compile and Run + +Compile: + +```shell +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +Run: + +``` +$ sudo ./ecli run package.json + +running and waiting for the ebpf events from perf event... +time pid ppid uid retval args_count args_size comm args +23:07:35 32940 32783 1000 0 1 13 cat /usr/bin/cat +23:07:43 32946 24577 1000 0 1 10 bash /bin/bash +23:07:43 32948 32946 1000 0 1 18 lesspipe /usr/bin/lesspipe +23:07:43 32949 32948 1000 0 2 36 basename /usr/bin/basename +23:07:43 32951 32950 1000 0 2 35 dirname /usr/bin/dirname +23:07:43 32952 32946 1000 0 2 22 dircolors /usr/bin/dircolors +23:07:48 32953 32946 1000 0 2 25 ls /usr/bin/ls +23:07:53 32957 32946 1000 0 2 17 sleep /usr/bin/sleep +23:07:57 32959 32946 1000 0 1 17 oneko /usr/games/oneko + +``` + +## 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.c b/7-execsnoop/execsnoop.bpf.c new file mode 100644 index 0000000..06e08f4 --- /dev/null +++ b/7-execsnoop/execsnoop.bpf.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +#include +#include +#include +#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 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; + 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++; + 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; + + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + 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); + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; + diff --git a/7-execsnoop/execsnoop.bpf.h b/7-execsnoop/execsnoop.bpf.h new file mode 100644 index 0000000..37ed7ac --- /dev/null +++ b/7-execsnoop/execsnoop.bpf.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#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; + int ppid; + int uid; + int retval; + int args_count; + unsigned int args_size; + char comm[TASK_COMM_LEN]; + char args[FULL_MAX_ARGS_ARR]; +}; + +#endif /* __EXECSNOOP_H */ + + diff --git a/8-runqslower/.gitignore b/8-runqslower/.gitignore new file mode 100644 index 0000000..e4bde33 --- /dev/null +++ b/8-runqslower/.gitignore @@ -0,0 +1,4 @@ +.vscode +package.json +eunomia-exporter +ecli diff --git a/8-runqslower/README.md b/8-runqslower/README.md new file mode 100644 index 0000000..2190350 --- /dev/null +++ b/8-runqslower/README.md @@ -0,0 +1,147 @@ +| layout | title | date | category | author | tags | summary | +| ------ | ---------- | ---------------- | -------- | -------- | --------------- | ----------------------------------------------- | +| post | runqslower | 2022-11-11-20:50 | bpftools | yunwei37 | bpftool syscall | runqslower Trace long process scheduling delays | + +## origin + +origin from: + +https://github.com/iovisor/bcc/blob/master/libbpf-tools/runqslower.bpf.c + +result: + +``` +$ sudo ecli/build/bin/Release/ecli run examples/bpftools/runqslower/package.json + +running and waiting for the ebpf events from perf event... +time task prev_task delta_us pid prev_pid +20:11:59 gnome-shell swapper/0 32 2202 0 +20:11:59 ecli swapper/3 23 3437 0 +20:11:59 rcu_sched swapper/1 1 14 0 +20:11:59 gnome-terminal- swapper/1 13 2714 0 +20:11:59 ecli swapper/3 2 3437 0 +20:11:59 kworker/3:3 swapper/3 3 215 0 +20:11:59 containerd swapper/1 8 1088 0 +20:11:59 ecli swapper/2 5 3437 0 +20:11:59 HangDetector swapper/3 6 854 0 +20:11:59 ecli swapper/2 60 3437 0 +20:11:59 rcu_sched swapper/1 26 14 0 +20:11:59 kworker/0:1 swapper/0 26 3414 0 +20:11:59 ecli swapper/2 6 3437 0 +``` + +## Compile and Run + +Compile: + +``` +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +Run: + +``` +sudo ./ecli run examples/bpftools/runqslower/package.json +``` + +## details in bcc + +Demonstrations of runqslower, the Linux eBPF/bcc version. + +runqslower traces high scheduling delays between tasks being ready to run and them running on CPU after that. Example output: + +``` +# ./runqslower +Tracing run queue latency higher than 10000 us +TIME COMM TID LAT(us) +13:11:43 b'kworker/0:2' 8680 10250 +13:12:18 b'irq/16-vmwgfx' 422 10838 +13:12:18 b'systemd-oomd' 753 11012 +13:12:18 b'containerd' 8272 11254 +13:12:18 b'HangDetector' 764 12042 +^C +`` +This measures the time a task spends waiting on a run queue for a turn on-CPU, and shows this time as a individual events. This time should be small, but a task may need to wait its turn due to CPU load. + +This measures two types of run queue latency: +1. The time from a task being enqueued on a run queue to its context switch and execution. This traces ttwu_do_wakeup(), wake_up_new_task() -> finish_task_switch() with either raw tracepoints (if supported) or kprobes and instruments the run queue latency after a voluntary context switch. +2. The time from when a task was involuntary context switched and still in the runnable state, to when it next executed. This is instrumented from finish_task_switch() alone. + +The overhead of this tool may become significant for some workloads: see the OVERHEAD section. + +This works by tracing various kernel scheduler functions using dynamic tracing, and will need updating to match any changes to these functions. + +Since this uses BPF, only the root user can use this tool. + +```console +Usage: runqslower [-h] [-p PID | -t TID | -P] [min_us] +``` + +The min_us option sets the latency of the run queue to track: + +``` +# ./runqslower 100 +Tracing run queue latency higher than 100 us +TIME COMM TID LAT(us) +20:48:26 b'gnome-shell' 3005 201 +20:48:26 b'gnome-shell' 3005 202 +20:48:26 b'gnome-shell' 3005 254 +20:48:26 b'gnome-shell' 3005 208 +20:48:26 b'gnome-shell' 3005 132 +20:48:26 b'gnome-shell' 3005 213 +20:48:26 b'gnome-shell' 3005 205 +20:48:26 b'python3' 5224 127 +20:48:26 b'gnome-shell' 3005 214 +20:48:26 b'gnome-shell' 3005 126 +20:48:26 b'gnome-shell' 3005 285 +20:48:26 b'Xorg' 2869 296 +20:48:26 b'gnome-shell' 3005 119 +20:48:26 b'gnome-shell' 3005 206 +``` + +The -p PID option only traces this PID: + +``` +# ./runqslower -p 3005 +Tracing run queue latency higher than 10000 us +TIME COMM TID LAT(us) +20:46:22 b'gnome-shell' 3005 16024 +20:46:45 b'gnome-shell' 3005 11494 +20:46:45 b'gnome-shell' 3005 21430 +20:46:45 b'gnome-shell' 3005 14948 +20:47:16 b'gnome-shell' 3005 10164 +20:47:16 b'gnome-shell' 3005 18070 +20:47:17 b'gnome-shell' 3005 13272 +20:47:18 b'gnome-shell' 3005 10451 +20:47:18 b'gnome-shell' 3005 15010 +20:47:18 b'gnome-shell' 3005 19449 +20:47:22 b'gnome-shell' 3005 19327 +20:47:23 b'gnome-shell' 3005 13178 +20:47:23 b'gnome-shell' 3005 13483 +20:47:23 b'gnome-shell' 3005 15562 +20:47:23 b'gnome-shell' 3005 13655 +20:47:23 b'gnome-shell' 3005 19571 +``` + +The -P option also shows previous task name and TID: + +``` +# ./runqslower -P +Tracing run queue latency higher than 10000 us +TIME COMM TID LAT(us) PREV COMM PREV TID +20:42:48 b'sysbench' 5159 10562 b'sysbench' 5152 +20:42:48 b'sysbench' 5159 10367 b'sysbench' 5152 +20:42:49 b'sysbench' 5158 11818 b'sysbench' 5159 +20:42:49 b'sysbench' 5160 16913 b'sysbench' 5153 +20:42:49 b'sysbench' 5157 13742 b'sysbench' 5160 +20:42:49 b'sysbench' 5152 13746 b'sysbench' 5160 +20:42:49 b'sysbench' 5153 13731 b'sysbench' 5160 +20:42:49 b'sysbench' 5158 14688 b'sysbench' 5161 +20:42:50 b'sysbench' 5155 10468 b'sysbench' 5152 +20:42:50 b'sysbench' 5156 17695 b'sysbench' 5158 +20:42:50 b'sysbench' 5155 11251 b'sysbench' 5152 +20:42:50 b'sysbench' 5154 13283 b'sysbench' 5152 +20:42:50 b'sysbench' 5158 22278 b'sysbench' 5157 +``` + +For more details, see docs/special_filtering.md \ No newline at end of file diff --git a/8-runqslower/core_fixes.h b/8-runqslower/core_fixes.h new file mode 100644 index 0000000..003163a --- /dev/null +++ b/8-runqslower/core_fixes.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* Copyright (c) 2021 Hengqi Chen */ + +#ifndef __CORE_FIXES_BPF_H +#define __CORE_FIXES_BPF_H + +#include +#include + +/** + * commit 2f064a59a1 ("sched: Change task_struct::state") changes + * the name of task_struct::state to task_struct::__state + * see: + * https://github.com/torvalds/linux/commit/2f064a59a1 + */ +struct task_struct___o { + volatile long int state; +} __attribute__((preserve_access_index)); + +struct task_struct___x { + unsigned int __state; +} __attribute__((preserve_access_index)); + +static __always_inline __s64 get_task_state(void *task) +{ + struct task_struct___x *t = task; + + if (bpf_core_field_exists(t->__state)) + return BPF_CORE_READ(t, __state); + return BPF_CORE_READ((struct task_struct___o *)task, state); +} + +/** + * commit 309dca309fc3 ("block: store a block_device pointer in struct bio") + * adds a new member bi_bdev which is a pointer to struct block_device + * see: + * https://github.com/torvalds/linux/commit/309dca309fc3 + */ +struct bio___o { + struct gendisk *bi_disk; +} __attribute__((preserve_access_index)); + +struct bio___x { + struct block_device *bi_bdev; +} __attribute__((preserve_access_index)); + +static __always_inline struct gendisk *get_gendisk(void *bio) +{ + struct bio___x *b = bio; + + if (bpf_core_field_exists(b->bi_bdev)) + return BPF_CORE_READ(b, bi_bdev, bd_disk); + return BPF_CORE_READ((struct bio___o *)bio, bi_disk); +} + +/** + * commit d5869fdc189f ("block: introduce block_rq_error tracepoint") + * adds a new tracepoint block_rq_error and it shares the same arguments + * with tracepoint block_rq_complete. As a result, the kernel BTF now has + * a `struct trace_event_raw_block_rq_completion` instead of + * `struct trace_event_raw_block_rq_complete`. + * see: + * https://github.com/torvalds/linux/commit/d5869fdc189f + */ +struct trace_event_raw_block_rq_complete___x { + dev_t dev; + sector_t sector; + unsigned int nr_sector; +} __attribute__((preserve_access_index)); + +struct trace_event_raw_block_rq_completion___x { + dev_t dev; + sector_t sector; + unsigned int nr_sector; +} __attribute__((preserve_access_index)); + +static __always_inline bool has_block_rq_completion() +{ + if (bpf_core_type_exists(struct trace_event_raw_block_rq_completion___x)) + return true; + return false; +} + +/** + * commit d152c682f03c ("block: add an explicit ->disk backpointer to the + * request_queue") and commit f3fa33acca9f ("block: remove the ->rq_disk + * field in struct request") make some changes to `struct request` and + * `struct request_queue`. Now, to get the `struct gendisk *` field in a CO-RE + * way, we need both `struct request` and `struct request_queue`. + * see: + * https://github.com/torvalds/linux/commit/d152c682f03c + * https://github.com/torvalds/linux/commit/f3fa33acca9f + */ +struct request_queue___x { + struct gendisk *disk; +} __attribute__((preserve_access_index)); + +struct request___x { + struct request_queue___x *q; + struct gendisk *rq_disk; +} __attribute__((preserve_access_index)); + +static __always_inline struct gendisk *get_disk(void *request) +{ + struct request___x *r = request; + + if (bpf_core_field_exists(r->rq_disk)) + return BPF_CORE_READ(r, rq_disk); + return BPF_CORE_READ(r, q, disk); +} + +#endif /* __CORE_FIXES_BPF_H */ diff --git a/8-runqslower/runqslower.bpf.c b/8-runqslower/runqslower.bpf.c new file mode 100644 index 0000000..ebc0103 --- /dev/null +++ b/8-runqslower/runqslower.bpf.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Facebook +#include +#include +#include +#include +#include "runqslower.bpf.h" +#include "core_fixes.h" + +#define TASK_RUNNING 0 + +const volatile __u64 min_us = 0; +const volatile pid_t targ_pid = 0; +const volatile pid_t targ_tgid = 0; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 10240); + __type(key, u32); + __type(value, u64); +} start SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(u32)); + __uint(value_size, sizeof(u32)); +} events SEC(".maps"); + +/* record enqueue timestamp */ +static int trace_enqueue(u32 tgid, u32 pid) +{ + u64 ts; + + if (!pid) + return 0; + if (targ_tgid && targ_tgid != tgid) + return 0; + if (targ_pid && targ_pid != pid) + return 0; + + ts = bpf_ktime_get_ns(); + bpf_map_update_elem(&start, &pid, &ts, 0); + return 0; +} + +static int handle_switch(void *ctx, struct task_struct *prev, struct task_struct *next) +{ + struct event event = {}; + u64 *tsp, delta_us; + u32 pid; + + /* ivcsw: treat like an enqueue event and store timestamp */ + if (get_task_state(prev) == TASK_RUNNING) + trace_enqueue(BPF_CORE_READ(prev, tgid), BPF_CORE_READ(prev, pid)); + + pid = BPF_CORE_READ(next, pid); + + /* fetch timestamp and calculate delta */ + tsp = bpf_map_lookup_elem(&start, &pid); + if (!tsp) + return 0; /* missed enqueue */ + + delta_us = (bpf_ktime_get_ns() - *tsp) / 1000; + if (min_us && delta_us <= min_us) + return 0; + + event.pid = pid; + event.prev_pid = BPF_CORE_READ(prev, pid); + event.delta_us = delta_us; + bpf_probe_read_kernel_str(&event.task, sizeof(event.task), next->comm); + bpf_probe_read_kernel_str(&event.prev_task, sizeof(event.prev_task), prev->comm); + + /* output */ + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, + &event, sizeof(event)); + + bpf_map_delete_elem(&start, &pid); + return 0; +} + +SEC("tp_btf/sched_wakeup") +int BPF_PROG(sched_wakeup, struct task_struct *p) +{ + return trace_enqueue(p->tgid, p->pid); +} + +SEC("tp_btf/sched_wakeup_new") +int BPF_PROG(sched_wakeup_new, struct task_struct *p) +{ + return trace_enqueue(p->tgid, p->pid); +} + +SEC("tp_btf/sched_switch") +int BPF_PROG(sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next) +{ + return handle_switch(ctx, prev, next); +} + +SEC("raw_tp/sched_wakeup") +int BPF_PROG(handle_sched_wakeup, struct task_struct *p) +{ + return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid)); +} + +SEC("raw_tp/sched_wakeup_new") +int BPF_PROG(handle_sched_wakeup_new, struct task_struct *p) +{ + return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid)); +} + +SEC("raw_tp/sched_switch") +int BPF_PROG(handle_sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next) +{ + return handle_switch(ctx, prev, next); +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/8-runqslower/runqslower.bpf.h b/8-runqslower/runqslower.bpf.h new file mode 100644 index 0000000..06e91f4 --- /dev/null +++ b/8-runqslower/runqslower.bpf.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __RUNQSLOWER_H +#define __RUNQSLOWER_H + +#define TASK_COMM_LEN 16 + +struct event { + char task[TASK_COMM_LEN]; + char prev_task[TASK_COMM_LEN]; + __u64 delta_us; + int pid; + int prev_pid; +}; + +#endif /* __RUNQSLOWER_H */ diff --git a/9-runqlat/.gitignore b/9-runqlat/.gitignore new file mode 100644 index 0000000..7d5aebf --- /dev/null +++ b/9-runqlat/.gitignore @@ -0,0 +1,6 @@ +.vscode +package.json +*.o +*.skel.json +*.skel.yaml +package.yaml diff --git a/9-runqlat/README.md b/9-runqlat/README.md new file mode 100755 index 0000000..59cbe30 --- /dev/null +++ b/9-runqlat/README.md @@ -0,0 +1,675 @@ +--- +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. +--- + + +## origin + +origin from: + + + +This program summarizes scheduler run queue latency as a histogram, showing +how long tasks spent waiting their turn to run on-CPU. + +## Compile and Run + +Compile: + +```shell +docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest +``` + +```console +$ ecc runqlat.bpf.c runqlat.h +Compiling bpf object... +Generating export types... +Packing ebpf object and config into package.json... +``` + +Run: + +```console +$ sudo ecli examples/bpftools/runqlat/package.json -h +Usage: runqlat_bpf [--help] [--version] [--verbose] [--filter_cg] [--targ_per_process] [--targ_per_thread] [--targ_per_pidns] [--targ_ms] [--targ_tgid VAR] + +A simple eBPF program + +Optional arguments: + -h, --help shows help message and exits + -v, --version prints version information and exits + --verbose prints libbpf debug information + --filter_cg set value of bool variable filter_cg + --targ_per_process set value of bool variable targ_per_process + --targ_per_thread set value of bool variable targ_per_thread + --targ_per_pidns set value of bool variable targ_per_pidns + --targ_ms set value of bool variable targ_ms + --targ_tgid set value of pid_t variable targ_tgid + +Built with eunomia-bpf framework. +See https://github.com/eunomia-bpf/eunomia-bpf for more information. + +$ sudo ecli examples/bpftools/runqlat/package.json +key = 4294967295 +comm = rcu_preempt + + (unit) : count distribution + 0 -> 1 : 9 |**** | + 2 -> 3 : 6 |** | + 4 -> 7 : 12 |***** | + 8 -> 15 : 28 |************* | + 16 -> 31 : 40 |******************* | + 32 -> 63 : 83 |****************************************| + 64 -> 127 : 57 |*************************** | + 128 -> 255 : 19 |********* | + 256 -> 511 : 11 |***** | + 512 -> 1023 : 2 | | + 1024 -> 2047 : 2 | | + 2048 -> 4095 : 0 | | + 4096 -> 8191 : 0 | | + 8192 -> 16383 : 0 | | + 16384 -> 32767 : 1 | | + +$ sudo ecli examples/bpftools/runqlat/package.json --targ_per_process +key = 3189 +comm = cpptools + + (unit) : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |*** | + 16 -> 31 : 2 |******* | + 32 -> 63 : 11 |****************************************| + 64 -> 127 : 8 |***************************** | + 128 -> 255 : 3 |********** | +``` + +## details in bcc + +```text +Demonstrations of runqlat, the Linux eBPF/bcc version. + + +This program summarizes scheduler run queue latency as a histogram, showing +how long tasks spent waiting their turn to run on-CPU. + +Here is a heavily loaded system: + +# ./runqlat +Tracing run queue latency... Hit Ctrl-C to end. +^C + usecs : count distribution + 0 -> 1 : 233 |*********** | + 2 -> 3 : 742 |************************************ | + 4 -> 7 : 203 |********** | + 8 -> 15 : 173 |******** | + 16 -> 31 : 24 |* | + 32 -> 63 : 0 | | + 64 -> 127 : 30 |* | + 128 -> 255 : 6 | | + 256 -> 511 : 3 | | + 512 -> 1023 : 5 | | + 1024 -> 2047 : 27 |* | + 2048 -> 4095 : 30 |* | + 4096 -> 8191 : 20 | | + 8192 -> 16383 : 29 |* | + 16384 -> 32767 : 809 |****************************************| + 32768 -> 65535 : 64 |*** | + +The distribution is bimodal, with one mode between 0 and 15 microseconds, +and another between 16 and 65 milliseconds. These modes are visible as the +spikes in the ASCII distribution (which is merely a visual representation +of the "count" column). As an example of reading one line: 809 events fell +into the 16384 to 32767 microsecond range (16 to 32 ms) while tracing. + +I would expect the two modes to be due the workload: 16 hot CPU-bound threads, +and many other mostly idle threads doing occasional work. I suspect the mostly +idle threads will run with a higher priority when they wake up, and are +the reason for the low latency mode. The high latency mode will be the +CPU-bound threads. More analysis with this and other tools can confirm. + + +A -m option can be used to show milliseconds instead, as well as an interval +and a count. For example, showing three x five second summary in milliseconds: + +# ./runqlat -m 5 3 +Tracing run queue latency... Hit Ctrl-C to end. + + msecs : count distribution + 0 -> 1 : 3818 |****************************************| + 2 -> 3 : 39 | | + 4 -> 7 : 39 | | + 8 -> 15 : 62 | | + 16 -> 31 : 2214 |*********************** | + 32 -> 63 : 226 |** | + + msecs : count distribution + 0 -> 1 : 3775 |****************************************| + 2 -> 3 : 52 | | + 4 -> 7 : 37 | | + 8 -> 15 : 65 | | + 16 -> 31 : 2230 |*********************** | + 32 -> 63 : 212 |** | + + msecs : count distribution + 0 -> 1 : 3816 |****************************************| + 2 -> 3 : 49 | | + 4 -> 7 : 40 | | + 8 -> 15 : 53 | | + 16 -> 31 : 2228 |*********************** | + 32 -> 63 : 221 |** | + +This shows a similar distribution across the three summaries. + + +A -p option can be used to show one PID only, which is filtered in kernel for +efficiency. For example, PID 4505, and one second summaries: + +# ./runqlat -mp 4505 1 +Tracing run queue latency... Hit Ctrl-C to end. + + msecs : count distribution + 0 -> 1 : 1 |* | + 2 -> 3 : 2 |*** | + 4 -> 7 : 1 |* | + 8 -> 15 : 0 | | + 16 -> 31 : 25 |****************************************| + 32 -> 63 : 3 |**** | + + msecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 2 |** | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |* | + 16 -> 31 : 30 |****************************************| + 32 -> 63 : 1 |* | + + msecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 28 |****************************************| + 32 -> 63 : 2 |** | + + msecs : count distribution + 0 -> 1 : 1 |* | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 27 |****************************************| + 32 -> 63 : 4 |***** | +[...] + +For comparison, here is pidstat(1) for that process: + +# pidstat -p 4505 1 +Linux 4.4.0-virtual (bgregg-xxxxxxxx) 02/08/2016 _x86_64_ (8 CPU) + +08:56:11 AM UID PID %usr %system %guest %CPU CPU Command +08:56:12 AM 0 4505 9.00 3.00 0.00 12.00 0 bash +08:56:13 AM 0 4505 7.00 5.00 0.00 12.00 0 bash +08:56:14 AM 0 4505 10.00 2.00 0.00 12.00 0 bash +08:56:15 AM 0 4505 11.00 2.00 0.00 13.00 0 bash +08:56:16 AM 0 4505 9.00 3.00 0.00 12.00 0 bash +[...] + +This is a synthetic workload that is CPU bound. It's only spending 12% on-CPU +each second because of high CPU demand on this server: the remaining time +is spent waiting on a run queue, as visualized by runqlat. + + +Here is the same system, but when it is CPU idle: + +# ./runqlat 5 1 +Tracing run queue latency... Hit Ctrl-C to end. + + usecs : count distribution + 0 -> 1 : 2250 |******************************** | + 2 -> 3 : 2340 |********************************** | + 4 -> 7 : 2746 |****************************************| + 8 -> 15 : 418 |****** | + 16 -> 31 : 93 |* | + 32 -> 63 : 28 | | + 64 -> 127 : 119 |* | + 128 -> 255 : 9 | | + 256 -> 511 : 4 | | + 512 -> 1023 : 20 | | + 1024 -> 2047 : 22 | | + 2048 -> 4095 : 5 | | + 4096 -> 8191 : 2 | | + +Back to a microsecond scale, this time there is little run queue latency past 1 +millisecond, as would be expected. + + +Now 16 threads are performing heavy disk I/O: + +# ./runqlat 5 1 +Tracing run queue latency... Hit Ctrl-C to end. + + usecs : count distribution + 0 -> 1 : 204 | | + 2 -> 3 : 944 |* | + 4 -> 7 : 16315 |********************* | + 8 -> 15 : 29897 |****************************************| + 16 -> 31 : 1044 |* | + 32 -> 63 : 23 | | + 64 -> 127 : 128 | | + 128 -> 255 : 24 | | + 256 -> 511 : 5 | | + 512 -> 1023 : 13 | | + 1024 -> 2047 : 15 | | + 2048 -> 4095 : 13 | | + 4096 -> 8191 : 10 | | + +The distribution hasn't changed too much. While the disks are 100% busy, there +is still plenty of CPU headroom, and threads still don't spend much time +waiting their turn. + + +A -P option will print a distribution for each PID: + +# ./runqlat -P +Tracing run queue latency... Hit Ctrl-C to end. +^C + +pid = 0 + usecs : count distribution + 0 -> 1 : 351 |******************************** | + 2 -> 3 : 96 |******** | + 4 -> 7 : 437 |****************************************| + 8 -> 15 : 12 |* | + 16 -> 31 : 10 | | + 32 -> 63 : 0 | | + 64 -> 127 : 16 |* | + 128 -> 255 : 0 | | + 256 -> 511 : 0 | | + 512 -> 1023 : 0 | | + 1024 -> 2047 : 0 | | + 2048 -> 4095 : 0 | | + 4096 -> 8191 : 0 | | + 8192 -> 16383 : 1 | | + +pid = 12929 + usecs : count distribution + 0 -> 1 : 1 |****************************************| + 2 -> 3 : 0 | | + 4 -> 7 : 1 |****************************************| + +pid = 12930 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 1 |****************************************| + 32 -> 63 : 0 | | + 64 -> 127 : 1 |****************************************| + +pid = 12931 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 1 |******************** | + 8 -> 15 : 0 | | + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 0 | | + 128 -> 255 : 0 | | + 256 -> 511 : 0 | | + 512 -> 1023 : 2 |****************************************| + +pid = 12932 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 0 | | + 128 -> 255 : 1 |****************************************| + 256 -> 511 : 0 | | + 512 -> 1023 : 1 |****************************************| + +pid = 7 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 426 |************************************* | + 4 -> 7 : 457 |****************************************| + 8 -> 15 : 16 |* | + +pid = 9 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 425 |****************************************| + 8 -> 15 : 16 |* | + +pid = 11 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 10 |****************************************| + +pid = 14 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 8 |****************************************| + 4 -> 7 : 2 |********** | + +pid = 18 + usecs : count distribution + 0 -> 1 : 414 |****************************************| + 2 -> 3 : 0 | | + 4 -> 7 : 20 |* | + 8 -> 15 : 8 | | + +pid = 12928 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 1 |****************************************| + 8 -> 15 : 0 | | + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 1 |****************************************| + +pid = 1867 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 15 |****************************************| + 16 -> 31 : 1 |** | + 32 -> 63 : 0 | | + 64 -> 127 : 0 | | + 128 -> 255 : 4 |********** | + +pid = 1871 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 2 |****************************************| + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 0 | | + 128 -> 255 : 0 | | + 256 -> 511 : 0 | | + 512 -> 1023 : 1 |******************** | + +pid = 1876 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |****************************************| + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 0 | | + 128 -> 255 : 0 | | + 256 -> 511 : 1 |****************************************| + +pid = 1878 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 3 |****************************************| + +pid = 1880 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 3 |****************************************| + +pid = 9307 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |****************************************| + +pid = 1886 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 1 |******************** | + 8 -> 15 : 2 |****************************************| + +pid = 1888 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 3 |****************************************| + +pid = 3297 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |****************************************| + +pid = 1892 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 1 |******************** | + 32 -> 63 : 0 | | + 64 -> 127 : 0 | | + 128 -> 255 : 0 | | + 256 -> 511 : 0 | | + 512 -> 1023 : 2 |****************************************| + +pid = 7024 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 4 |****************************************| + +pid = 16468 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 3 |****************************************| + +pid = 12922 + usecs : count distribution + 0 -> 1 : 1 |****************************************| + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 1 |****************************************| + 16 -> 31 : 1 |****************************************| + 32 -> 63 : 0 | | + 64 -> 127 : 1 |****************************************| + +pid = 12923 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 1 |******************** | + 8 -> 15 : 0 | | + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 2 |****************************************| + 128 -> 255 : 0 | | + 256 -> 511 : 0 | | + 512 -> 1023 : 1 |******************** | + 1024 -> 2047 : 1 |******************** | + +pid = 12924 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 2 |******************** | + 8 -> 15 : 4 |****************************************| + 16 -> 31 : 1 |********** | + 32 -> 63 : 0 | | + 64 -> 127 : 0 | | + 128 -> 255 : 0 | | + 256 -> 511 : 0 | | + 512 -> 1023 : 0 | | + 1024 -> 2047 : 1 |********** | + +pid = 12925 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 1 |****************************************| + +pid = 12926 + usecs : count distribution + 0 -> 1 : 0 | | + 2 -> 3 : 1 |****************************************| + 4 -> 7 : 0 | | + 8 -> 15 : 1 |****************************************| + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 0 | | + 128 -> 255 : 0 | | + 256 -> 511 : 0 | | + 512 -> 1023 : 1 |****************************************| + +pid = 12927 + usecs : count distribution + 0 -> 1 : 1 |****************************************| + 2 -> 3 : 0 | | + 4 -> 7 : 1 |****************************************| + + +A -L option will print a distribution for each TID: + +# ./runqlat -L +Tracing run queue latency... Hit Ctrl-C to end. +^C + +tid = 0 + usecs : count distribution + 0 -> 1 : 593 |**************************** | + 2 -> 3 : 829 |****************************************| + 4 -> 7 : 300 |************** | + 8 -> 15 : 321 |*************** | + 16 -> 31 : 132 |****** | + 32 -> 63 : 58 |** | + 64 -> 127 : 0 | | + 128 -> 255 : 0 | | + 256 -> 511 : 13 | | + +tid = 7 + usecs : count distribution + 0 -> 1 : 8 |******** | + 2 -> 3 : 19 |******************** | + 4 -> 7 : 37 |****************************************| +[...] + + +And a --pidnss option (short for PID namespaces) will print for each PID +namespace, for analyzing container performance: + +# ./runqlat --pidnss -m +Tracing run queue latency... Hit Ctrl-C to end. +^C + +pidns = 4026532870 + msecs : count distribution + 0 -> 1 : 40 |****************************************| + 2 -> 3 : 1 |* | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 0 | | + 32 -> 63 : 2 |** | + 64 -> 127 : 5 |***** | + +pidns = 4026532809 + msecs : count distribution + 0 -> 1 : 67 |****************************************| + +pidns = 4026532748 + msecs : count distribution + 0 -> 1 : 63 |****************************************| + +pidns = 4026532687 + msecs : count distribution + 0 -> 1 : 7 |****************************************| + +pidns = 4026532626 + msecs : count distribution + 0 -> 1 : 45 |****************************************| + 2 -> 3 : 0 | | + 4 -> 7 : 0 | | + 8 -> 15 : 0 | | + 16 -> 31 : 0 | | + 32 -> 63 : 0 | | + 64 -> 127 : 3 |** | + +pidns = 4026531836 + msecs : count distribution + 0 -> 1 : 314 |****************************************| + 2 -> 3 : 1 | | + 4 -> 7 : 11 |* | + 8 -> 15 : 28 |*** | + 16 -> 31 : 137 |***************** | + 32 -> 63 : 86 |********** | + 64 -> 127 : 1 | | + +pidns = 4026532382 + msecs : count distribution + 0 -> 1 : 285 |****************************************| + 2 -> 3 : 5 | | + 4 -> 7 : 16 |** | + 8 -> 15 : 9 |* | + 16 -> 31 : 69 |********* | + 32 -> 63 : 25 |*** | + +Many of these distributions have two modes: the second, in this case, is +caused by capping CPU usage via CPU shares. + + +USAGE message: + +# ./runqlat -h +usage: runqlat.py [-h] [-T] [-m] [-P] [--pidnss] [-L] [-p PID] + [interval] [count] + +Summarize run queue (scheduler) latency as a histogram + +positional arguments: + interval output interval, in seconds + count number of outputs + +optional arguments: + -h, --help show this help message and exit + -T, --timestamp include timestamp on output + -m, --milliseconds millisecond histogram + -P, --pids print a histogram per process ID + --pidnss print a histogram per PID namespace + -L, --tids print a histogram per thread ID + -p PID, --pid PID trace this PID only + +examples: + ./runqlat # summarize run queue latency as a histogram + ./runqlat 1 10 # print 1 second summaries, 10 times + ./runqlat -mT 1 # 1s summaries, milliseconds, and timestamps + ./runqlat -P # show each PID separately + ./runqlat -p 185 # trace PID 185 only + +``` diff --git a/9-runqlat/bits.bpf.h b/9-runqlat/bits.bpf.h new file mode 100644 index 0000000..a2b7bb9 --- /dev/null +++ b/9-runqlat/bits.bpf.h @@ -0,0 +1,31 @@ +/* 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/core_fixes.bpf.h b/9-runqlat/core_fixes.bpf.h new file mode 100644 index 0000000..003163a --- /dev/null +++ b/9-runqlat/core_fixes.bpf.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* Copyright (c) 2021 Hengqi Chen */ + +#ifndef __CORE_FIXES_BPF_H +#define __CORE_FIXES_BPF_H + +#include +#include + +/** + * commit 2f064a59a1 ("sched: Change task_struct::state") changes + * the name of task_struct::state to task_struct::__state + * see: + * https://github.com/torvalds/linux/commit/2f064a59a1 + */ +struct task_struct___o { + volatile long int state; +} __attribute__((preserve_access_index)); + +struct task_struct___x { + unsigned int __state; +} __attribute__((preserve_access_index)); + +static __always_inline __s64 get_task_state(void *task) +{ + struct task_struct___x *t = task; + + if (bpf_core_field_exists(t->__state)) + return BPF_CORE_READ(t, __state); + return BPF_CORE_READ((struct task_struct___o *)task, state); +} + +/** + * commit 309dca309fc3 ("block: store a block_device pointer in struct bio") + * adds a new member bi_bdev which is a pointer to struct block_device + * see: + * https://github.com/torvalds/linux/commit/309dca309fc3 + */ +struct bio___o { + struct gendisk *bi_disk; +} __attribute__((preserve_access_index)); + +struct bio___x { + struct block_device *bi_bdev; +} __attribute__((preserve_access_index)); + +static __always_inline struct gendisk *get_gendisk(void *bio) +{ + struct bio___x *b = bio; + + if (bpf_core_field_exists(b->bi_bdev)) + return BPF_CORE_READ(b, bi_bdev, bd_disk); + return BPF_CORE_READ((struct bio___o *)bio, bi_disk); +} + +/** + * commit d5869fdc189f ("block: introduce block_rq_error tracepoint") + * adds a new tracepoint block_rq_error and it shares the same arguments + * with tracepoint block_rq_complete. As a result, the kernel BTF now has + * a `struct trace_event_raw_block_rq_completion` instead of + * `struct trace_event_raw_block_rq_complete`. + * see: + * https://github.com/torvalds/linux/commit/d5869fdc189f + */ +struct trace_event_raw_block_rq_complete___x { + dev_t dev; + sector_t sector; + unsigned int nr_sector; +} __attribute__((preserve_access_index)); + +struct trace_event_raw_block_rq_completion___x { + dev_t dev; + sector_t sector; + unsigned int nr_sector; +} __attribute__((preserve_access_index)); + +static __always_inline bool has_block_rq_completion() +{ + if (bpf_core_type_exists(struct trace_event_raw_block_rq_completion___x)) + return true; + return false; +} + +/** + * commit d152c682f03c ("block: add an explicit ->disk backpointer to the + * request_queue") and commit f3fa33acca9f ("block: remove the ->rq_disk + * field in struct request") make some changes to `struct request` and + * `struct request_queue`. Now, to get the `struct gendisk *` field in a CO-RE + * way, we need both `struct request` and `struct request_queue`. + * see: + * https://github.com/torvalds/linux/commit/d152c682f03c + * https://github.com/torvalds/linux/commit/f3fa33acca9f + */ +struct request_queue___x { + struct gendisk *disk; +} __attribute__((preserve_access_index)); + +struct request___x { + struct request_queue___x *q; + struct gendisk *rq_disk; +} __attribute__((preserve_access_index)); + +static __always_inline struct gendisk *get_disk(void *request) +{ + struct request___x *r = request; + + if (bpf_core_field_exists(r->rq_disk)) + return BPF_CORE_READ(r, rq_disk); + return BPF_CORE_READ(r, q, disk); +} + +#endif /* __CORE_FIXES_BPF_H */ diff --git a/9-runqlat/maps.bpf.h b/9-runqlat/maps.bpf.h new file mode 100644 index 0000000..51d1012 --- /dev/null +++ b/9-runqlat/maps.bpf.h @@ -0,0 +1,26 @@ +// 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/9-runqlat/runqlat.bpf.c b/9-runqlat/runqlat.bpf.c new file mode 100644 index 0000000..0659151 --- /dev/null +++ b/9-runqlat/runqlat.bpf.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020 Wenbo Zhang +#include +#include +#include +#include +#include "runqlat.h" +#include "bits.bpf.h" +#include "maps.bpf.h" +#include "core_fixes.bpf.h" + +#define MAX_ENTRIES 10240 +#define TASK_RUNNING 0 + +const volatile bool filter_cg = false; +const volatile bool targ_per_process = false; +const volatile bool targ_per_thread = false; +const volatile bool targ_per_pidns = false; +const volatile bool targ_ms = false; +const volatile pid_t targ_tgid = 0; + +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, MAX_ENTRIES); + __type(key, u32); + __type(value, u64); +} start SEC(".maps"); + +static struct hist zero; + +/// @sample {"interval": 1000, "type" : "log2_hist"} +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, u32); + __type(value, struct hist); +} hists SEC(".maps"); + +static int trace_enqueue(u32 tgid, u32 pid) +{ + u64 ts; + + if (!pid) + return 0; + if (targ_tgid && targ_tgid != tgid) + return 0; + + ts = bpf_ktime_get_ns(); + bpf_map_update_elem(&start, &pid, &ts, BPF_ANY); + return 0; +} + +static unsigned int pid_namespace(struct task_struct *task) +{ + struct pid *pid; + unsigned int level; + struct upid upid; + unsigned int inum; + + /* get the pid namespace by following task_active_pid_ns(), + * pid->numbers[pid->level].ns + */ + pid = BPF_CORE_READ(task, thread_pid); + level = BPF_CORE_READ(pid, level); + bpf_core_read(&upid, sizeof(upid), &pid->numbers[level]); + inum = BPF_CORE_READ(upid.ns, ns.inum); + + return inum; +} + +static int handle_switch(bool preempt, struct task_struct *prev, struct task_struct *next) +{ + struct hist *histp; + u64 *tsp, slot; + u32 pid, hkey; + s64 delta; + + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + if (get_task_state(prev) == TASK_RUNNING) + trace_enqueue(BPF_CORE_READ(prev, tgid), BPF_CORE_READ(prev, pid)); + + pid = BPF_CORE_READ(next, pid); + + tsp = bpf_map_lookup_elem(&start, &pid); + if (!tsp) + return 0; + delta = bpf_ktime_get_ns() - *tsp; + if (delta < 0) + goto cleanup; + + if (targ_per_process) + hkey = BPF_CORE_READ(next, tgid); + else if (targ_per_thread) + hkey = pid; + else if (targ_per_pidns) + hkey = pid_namespace(next); + else + hkey = -1; + histp = bpf_map_lookup_or_try_init(&hists, &hkey, &zero); + if (!histp) + goto cleanup; + if (!histp->comm[0]) + bpf_probe_read_kernel_str(&histp->comm, sizeof(histp->comm), + next->comm); + if (targ_ms) + delta /= 1000000U; + else + delta /= 1000U; + slot = log2l(delta); + if (slot >= MAX_SLOTS) + slot = MAX_SLOTS - 1; + __sync_fetch_and_add(&histp->slots[slot], 1); + +cleanup: + bpf_map_delete_elem(&start, &pid); + return 0; +} + +SEC("raw_tp/sched_wakeup") +int BPF_PROG(handle_sched_wakeup, struct task_struct *p) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid)); +} + +SEC("raw_tp/sched_wakeup_new") +int BPF_PROG(handle_sched_wakeup_new, struct task_struct *p) +{ + if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0)) + return 0; + + return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid)); +} + +SEC("raw_tp/sched_switch") +int BPF_PROG(handle_sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next) +{ + return handle_switch(preempt, prev, next); +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/9-runqlat/runqlat.h b/9-runqlat/runqlat.h new file mode 100644 index 0000000..b6f0a02 --- /dev/null +++ b/9-runqlat/runqlat.h @@ -0,0 +1,14 @@ + +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __RUNQLAT_H +#define __RUNQLAT_H + +#define TASK_COMM_LEN 16 +#define MAX_SLOTS 26 + +struct hist { + __u32 slots[MAX_SLOTS]; + char comm[TASK_COMM_LEN]; +}; + +#endif /* __RUNQLAT_H */