当前位置: 首页 > Linux

Observability项目对uprobe理解和实现的需求

时间:2023-04-06 19:00:13 Linux

什么是uprobeuprobe是一个用户空间probe,uprobeprobe允许在用户空间程序中动态插入,插入位置包括:函数入口、具体偏移量、函数返回位置。当我们定义uprobe时,内核会在附加指令上创建一个快速断点指令(x86机器上的int3指令)。当程序执行到这条指令时,内核会触发一个事件,程序会进入内核态,回调函数同理调用探测函数,然后回到用户态继续执行后面的指令执行探测功能后。Uprobe基于文件。跟踪二进制文件中的函数时,将检测所有使用该文件的进程,包括尚未启动的进程,以便可以在系统范围内跟踪系统调用。简单介绍一下uprobe的使用场景。Uprobe适用于分析一些用户态内核态探针无法解析的流量,如http2流量(包头经过编码,内核无法解码)、https流量(内核无法解密的加密流量)).例如:对于grpc(基于http2)请求调用流量,请求开始后,wireshark无法正常解析报文的header部分:但是如果使用uprobe,将uprobe字节码注入到对应的二进制文件中grpc服务器,可以报相关流量在header部分文本编码之前被拦截,这样header信息就可以正常显示了。可观察性需要uprobe。可观察性旨在通过分析系统产生的数据来理解和推断系统的内部状态。一般需要24小时间歇运行,因为uprobe是基于文件的,依赖于hook点在二进制文件指针中偏移的特性,当两者结合起来,可观测性对uprobe主要有以下三个要求:01Generalfunctions-basicloading,基本eBPF字节码加载函数的基本清洗,至少保证硬编码的【二进制文件:挂钩点】能够正常加载;当项目退出时,清理所有运行时注入内核态的eBPF字节码,并创建perf资源。02函数符号偏移量分析——确保字节码被加载到正确的位置。首先也是最基本的是函数符号偏移量的分析。通常,uprobe的挂钩点是二进制符号表中的一个函数符号。uprobe挂载的hook点的名称在代码中一般是固定的,但是hook点在不同的二进制文件中的偏移量是不同的,所以我们需要能够动态解析二进制文件的符号表,获取其location根据挂钩点的名称在不同二进制文件中的偏移量,并通知eBPF加载器,从而将eBPF字节码加载到二进制文件的相应位置。03DynamicallyloadeBPFbytecode-ensurebytecodeisapplyedtonewcreatedprocesses动态加载eBPF字节码是指在可观察性项目启动后,为新创建的进程加载eBPF字节码,底层实现是将eBPF字节码加载到对应的二进制文件中过程。一个二进制文件可能会启动多个进程,所以也要避免为同一个二进制文件多次加载相同的eBPF字节码,否则会同时执行两个eBPF程序,输出两份相同的数据,导致data冗余。Uprobe本质上是依附于文件,但是很难捕捉到文件的创建,所以可以换个思路,捕捉到进程的创建,通过进程信息将其与二进制文件关联起来。04Docker容器删除后,清理内核残留资源——保证内核中挂起的资源被清理干净。在云原生环境下,容器的删除和重构是非常频繁的操作。相应的eBPF字节码和perf_event资源将保留在内核中,并处于活动状态。对于需要7*24小时不间断运行的observability项目,理论上项目不会退出或重启,那么这些残留资源就永远不会被清理掉,会不断堆积在内核中,占用内核空间资源。关于删除uprobe附带的文件是否需要清理相关资源的问题,我查阅了相关资料,在libbpfissues中找到了类似的问题。这是很多人的共同疑问。为了让bpf社区更多的人阅读,最后将该issue的讨论移至Linux内核邮件讨论区。从讨论区我们可以知道,在BPF世界中,uprobe由两个对象组成,即uprobe字节码本身和关联的perf_event资源,它们都以文件描述符fd的形式存在于用户空间。当uprobe附带的二进制文件被删除时,只要这两个文件描述符没有被释放,对应的资源就会一直存在于内核空间,处于活动状态,属于挂起资源。只有当加载eBPF字节码的程序完全退出时,内核才会统一清理这些挂起的资源。比如BCC项目,从BCC项目的一个issue可以知道,对于PerfEvent-baseduprobe,当BCC完全退出时,内核会统一清理所有相关的挂起资源。uprobe在业界的实现现状01BCCBCC是eBPF编程的脚手架,提供对eBPF的支持,封装eBPF编程,提供简单丰富的API。BCC对uprobe的支持如下:已经支持:常规函数(baseload,basecleanup),函数符号偏移量解析BCC本身只是一个简化传统eBPF编程的工具,不需要observability,所以BCC没有实现dynamic加载动态清理函数。不支持:动态加载eBPF字节码清理Docker容器删除后的残留资源02PixiePixie是一个开源的Kubernetes应用的可观察性工具。属于业界标杆,Pixie对uprobe的支持如下:支持:常规函数(baseloading,basecleanup)动态加载eBPF字节码函数符号偏移量分析另外,Pixie支持二进制字段偏移量动态分析,即也就是说,对于不同版本的库,对于库中的一些变量域,Pixie可以自动解析出变量域在栈中相对于栈顶指针的偏移量,因为即使是同一个库,同一个变量字段在高版本库和低版本库中相对于栈顶的偏移量也可能存在一些差异。暂不支持:删除Docker容器后,清理内核残留资源来自Pixie的源码(初学者个人理解,如有遗漏请指出),Pixie维护了一个加载二进制文件的集合uprobe,但是对于旧的二进制文件来说,就是一个已经被删除的二进制文件。Pixie目前无法识别删除二进制文件(TODO),以清理相关内核挂起资源。目前Pixie仅使用BCC的detach_uprobe()API来卸载一个统一的eBPF程序,并在整个Pixie程序退出时清理挂起的资源。Pixie的这个实现非常简单,这个实现在大多数情况下不会造成很大的影响,但是在一些极端的情况下,比如需要加载大量uprobe程序的时候,容器删改频繁的情况在某些情况下情况下,内核中的剩余资源不断积累,会给内核带来一定的损失。Kindling的实现已经支持:常规函数(基本加载、基本清理)、函数符号偏移量解析、动态加载eBPF字节码、清理Docker容器删除后内核残留资源正在支持:二进制字段偏移量动态解析功能我们按顺序实现为了满足uprobe的四个主要可观察性要求,以及清理eBPF字节码和perf_event资源,避免uprobe对进程(二进制文件)的重复加载,我们相对于当前的行业实践做了一些改进。01避免动态加载重复加载的改进。我们分析文件inode以控制相同的eBPF字节码对同一个文件只加载一次。与目前业界实现相比,它可以更好地处理一些二进制文件的删除和重建。02引入动态清理由于Kindling本身具备捕获进程创建和退出的能力,因此很容易在此基础上添加动态清理功能。我们实现了在运行过程中动态识别无效的eBPF字节码和perf。资源,当Kindling不需要退出时,动态清理无效的eBPF字节码和perf资源。这样即使长时间运行,内核中也不会有任何无效的悬空资源。