使用我们的eBPF库套件
Trail of Bits开发了一套开源库,旨在简化eBPF应用程序的创建和部署。这些库支持高效的进程和网络事件监控、函数追踪、内核调试符号解析以及eBPF代码生成。
以往,由于Linux内核版本差异以及需要外部工具进行C到eBPF字节码转换,部署可移植、无依赖的eBPF应用面临重大挑战。我们通过创新库解决了这些问题,这些库利用最新的eBPF和Linux内核特性来减少外部依赖。这些工具非常适合创建本地机器代理和实现云原生监控, actively维护并与各种Linux发行版和内核版本兼容。有些甚至作为知名终端可见性框架osquery的核心功能组成部分。
我们的eBPF库
该套件中的库包括linuxevents、ebpfpub、btfparse和ebpf-common。它们共同用于开发具有高度准确性和效率的流线型事件监控。其应用范围涵盖网络事件监控、函数追踪、内核调试符号解析,以及辅助生成和使用eBPF代码。
linuxevents:无运行时依赖的容器感知进程监控库
linuxevents库展示了eBPF如何在不需准确内核头文件或其他外部依赖的情况下监控事件。不再需要分发内核头文件、预编译eBPF字节码的多个副本或依赖BCC!linuxevents库支持运行时代码生成,在运行时创建自定义探针,而不仅仅在构建时。它也比传统的基于系统调用的钩子快得多,这是在单台机器上监控多个容器事件时的关键特性。
linuxevents如何实现这一点?
首先,linuxevents使用Linux内核的BTF调试数据(通过我们的btfparse库)准确识别函数原型和内核数据结构。这使得linuxevents能够自动适应数据结构布局的变化,并以极大简化追踪的方式钩取任意非公开符号。
这种方法比传统的基于系统调用的钩子更快,不仅因为它需要钩取的内容更少(sched_process_exec vs execve、execveat等),还因为它可以避免昂贵的关联计算。例如,要追踪通过execve执行的磁盘程序,通常必须将传递给execve的文件描述符与open调用和多个chdir调用关联起来以获取程序的完整路径。这种关联计算成本高昂,尤其是在具有多个活动容器的机器上。linuxevents库使用准确的内核数据结构表示,仅钩取一个函数并简单地从内核的vfs层提取路径。
linuxevents库仍是一个概念验证;它被osquery用作可切换的实验功能。该库还有一个具有跨容器可见性的追踪执行进程的规范示例。
ebpfpub:Linux函数追踪库
ebpfpub库允许在多个Linux内核版本上监控系统调用,同时依赖最少的外部运行时依赖。在ebpfpub中,eBPF探针是从通过简单自定义语言定义的函数原型自动生成的,这些原型可以从追踪点描述符创建。这种方法需要运行内核的适当头文件,并带来性能损失,例如需要将文件描述符与系统调用匹配。
根据目标事件,ebpfpub可以使用内核追踪点、kprobes或uprobes作为底层追踪机制。该库包含以下示例:
- execsnoop:展示如何使用Linux内核追踪点通过execve()检测程序执行
- kprobe_execsnoop:类似execsnoop,但使用不同的钩取机制(kprobes而非追踪点)
- readline_trace:使用uprobes钩取用户模式readline库,可支持诸如监控机器上实时shell使用的用例
- sockevents:展示如何通过一系列建立远程机器连接的connect/accept/bind调用来追踪套接字
- systemd_resolved:展示如何使用uprobes钩入systemd的DNS服务(systemd-resolved),实时显示本地机器正在查找的域名
ebpfpub库目前被osquery用于通过追踪执行的系统调用来捕获进程和套接字事件。虽然ebpfpub仍在维护并在特定情况下有用(如需要支持旧内核和使用运行时代码生成),但新项目应改用linuxevents方法。
btfparse:解析BTF格式内核调试符号的C++库
BTF(二进制类型格式)是一种紧凑的二进制格式,用于表示Linux内核中的类型信息。BTF存储结构、联合、枚举和typedef等数据。调试器和其他工具可以使用BTF数据通过理解复杂的C类型和表达式来启用更丰富的调试功能。BTF在Linux 4.20中引入,并从源代码和传统调试信息(如DWARF)生成。BTF比DWARF更紧凑,并通过传递比以前更多的语义类型信息来改善调试体验。标准化的BTF格式还允许新的调试工具跨编译器利用类型数据,实现跨语言更一致的内省质量。
btfparse库允许您在C++项目中读取BTF数据并直接在内存中生成头文件,无需任何外部应用程序。该库还附带一个名为btf-dump的工具,既作为使用btfparse的示例,也作为可以转储Linux内核映像中存在的BTF数据的独立工具。
ebpf-common:帮助编写新eBPF工具的C++库
ebpf-common库是一组实用程序,协助生成、加载和使用eBPF代码。它是支撑我们所有eBPF相关工具的共同基础。使用epbf-common创建您自己的运行时、基于eBPF的工具!
ebpf-common库的主要工作是将C代码编译为eBPF字节码,并提供有用的抽象,使编写eBPF钩子更容易。以下是ebpf-common提供的一些特性:
- 使用LLVM和clang作为库,写入内存暂存缓冲区
- 包括使访问eBPF数据结构(哈希映射、数组、环形缓冲区等)简单的抽象。这些数据结构用于在eBPF和您的应用程序之间交换数据
- 包括创建和读取perf输出的抽象,这是eBPF与追踪应用程序通信的另一种方式
- 允许管理触发eBPF程序执行的事件(如kprobes、uprobes和追踪点)
- 最后,包括通过LLVM实现eBPF助手和相关功能的函数
ebpf-common库用作我们所有其他eBPF工具的核心,这些工具作为库客户端和ebpf-common用于您应用程序的用例示例。参考我们的博客文章All your tracing are belong to BPF获取关于如何使用ebpf-common的额外指导和示例。
我们的eBPF工具
ebpfault:基于eBPF的Linux系统调用故障注入器
ebpfault是一个系统范围的故障注入器,不需要可能使系统崩溃的风险内核驱动程序。它可以启动特定程序、目标运行进程或目标除特定列表外的所有进程。一个简单的JSON格式配置文件让您通过使用系统调用名称、注入故障的概率和应返回的错误代码来配置故障。
ebpfault针对htop进程运行并通过特定配置引起故障的录制
BPF深度探讨和演讲
大多数在线材料侧重于使用演示eBPF的命令行示例工具,这些工具主要作为独立演示而非可重用库。我们希望为开发人员填补空白,并从编写追踪工具的开发人员角度提供关于如何实际从零集成eBPF的逐步指南。文档侧重于使用LLVM库的运行时代码生成。
All your tracing belong to BPF博客文章和我们的配套代码指南展示了如何使用epbf-common创建一个使用eBPF计数系统调用的工具,每个示例复杂性递增,从简单计数开始,到使用映射存储数据,最后使用perf事件进行输出。
Monitoring Linux events是Alessandro Gario关于使用eBPF进行事件监控的演讲。Alessandro描述了如何动态决定监控什么以及直接从C++生成您自己的eBPF字节码。他涉及eBPF映射、perf事件的一些复杂性以及使用我们eBPF工具和库的实际考虑。
eBPF公共贡献
eBPF的世界继续扩展并在各个领域找到应用。我们探索了eBPF在与追踪和性能监控相关的有趣任务,如改进eBPF字节码的CI/CD、为Solana平台编写eBPF到ARM64的JIT编译器,以及改善在Windows上构建eBPF项目的体验。
- ebpf-verifier:有时需要捆绑预构建的eBPF程序。Linux内核在加载时“验证”eBPF程序并拒绝任何它认为不安全的程序。捆绑eBPF字节码是CI/CD的噩梦,因为每个内核的验证都略有不同。ebpf-verifier旨在通过在运行内核之外执行eBPF验证器来消除这种噩梦,并为跨不同内核版本测试eBPF程序的可能性打开大门。
- Solana eBPF-to-ARM64 JIT编译器:eBPF出现在许多令人惊讶的地方!Solana区块链使用eBPF虚拟机运行其智能合约,并使用JIT编译器将eBPF字节码编译为本机架构。Trail of Bits将Solana eBPF JIT编译器移植到ARM64,以允许Solana应用程序在本机运行在现在非常流行的ARM64平台(如Apple Silicon)上。
- 为Windows的eBPF添加CMake支持:eBPF也可以在Windows上工作!为了使Windows开发更容易,我们将先前基于Visual Studio的构建系统移植到CMake。改进包括更好地处理传递依赖和属性、更好的打包以及增强的构建设置,以获得更高效的开发体验。
结论
我们使用eBPF为系统检测代理(如osquery)提供快速、高质量的监控数据。我们的意图是我们创建的框架和工具将帮助开发人员更无缝地将eBPF集成到他们的应用程序中。eBPF是一项有用的技术,在各种领域(包括日益增长的云原生监控和可观察性)拥有光明的未来。
我们计划在不久的将来分享更多从eBPF工具开发中获得的经验教训,并希望将这些经验应用于云原生监控和可观察性问题。