eBPF在android上的使用
一、eBPF是什么
eBPF是extended BPF的缩写,而BPF是Berkeley Packet Filter的缩写。对linux网络比较熟悉的伙伴对BPF应该比较了解,它通过特定的语法规则使用基于寄存器的虚拟机来描述包过滤的行为。比较常用的功能是通过过滤来统计流量,tcpdump工具就是基于BPF实现的。而eBPF对它进行了扩展来实现更多的功能。
主要区别如下:
对于eBPF可以简单的理解成kernel实现了一个虚拟机机制,将类C代码编译成字节码(后文有详细解释),挂在到内核的钩子上,当钩子被触发时,kernel在虚拟机的"沙盒"中运行字节码,这样既能方便的实现很多功能,也能通过沙箱保证内核的安全性。
二、eBPF能干什么
如果说BPF专注于流量监控,那么eBPF主要专注的是性能领域,通过各种钩子,能在用户空间得到系统各种性能指标。可以大到监控系统整体的统计指标,也可以小到一个系统函数的运行时间。
这里需要提一下开源项目 BPF Compiler Collection (BCC),这是一个很方便的基于eBPF的系统监视工具,下面这张BCC的说明图就能很好的说明我们使用eBPF能够做到的事。BCC在android系统上也可以运行,但是要对系统进行一定程度的修改,后续可能会写单独的文章进行讲解。对于内核开发者我还比较关注怎么自己来实现监控的功能,下文也将做简单的讲解。
从上图,我么可以看到,eBPF几乎能监控系统的所有方面:
三、eBPF框架
在开始说明之前先解释下eBPF上的名词,来帮忙更好的理解。
关于eBPF机制详细的讲解网上有很多,这里就不展开了,这里先上一张图,这里包括了使用或者编写ebpf涉及到的所有东西,下面会对这个图进行详细的讲解。
声明使用的Map节点
声明钩子挂载点及处理函数
编译命令:clang --target=bpf
android平台有集成eBPF的编译,后文会提到
将foo_kern.c 编译成的字节码加载到kenel中
读取Map中的信息并处理输出给用户
a. 检查是否声明了GNU GPL,检查kernel的版本是否支持
b. 函数调用规则:
允许bpf函数之间的相互调用
只允许调用kernel允许的BPF helper函数,具体可以参考linux/bpf.h文件
上述以外的函数及动态链接都是不允许的。
c. 流程处理规则:
不允许使用loop循环以防止进入死循环卡死kernel
不允许有不可到达的分支代码
d. 堆栈大小被限制在MAX_BPF_STACK范围内。
e. 编译的字节码大小被限制在BPF_COMPLEXITY_LIMIT_INSNS范围内。
另外在kernel的源代码中samples/bpf目录下有大量的示例,感兴趣的可以阅读下。
四、eBPF在Android平台的使用
经过上面枯燥的讲解,大家应该对eBPF有了基础的认识,下面我们就来通过android平台上的一个监控性能的小例子来实操下。
这个小例子的需求是统计系统中每个应用在一段时间内系统调用的次数。
1. android系统对eBPF的编译支持
目前android编译系统已经对eBPF进行了集成,通过android.bp就能很方便的在android源代码中编译eBPF的字节码。
android.bp示例:
相关的编译代码在soong的bpf.go,虽然google关于soong的文档很少,但是至少代码是比较清晰的。
这里的$ccCmd一般是clang, 所以它的编译命令主要是clang --target=bpf。和普通的bpf编译没有区别。
2. eBPF钩子代码实现
解决了编译问题,下一步我们开始实现钩子代码,我们准备使用tracepoint钩子,首先要找到我们需要的tracepoint函数sys_enter和sys_exit。
函数定义在include/trace/events/syscalls.h文件中
找到了钩子后,下一步就可以编写钩子处理代码了:
bpf_pid_syscall_map_lookup_elem
bpf_pid_syscall_map_update_elem
bpf_pid_syscall_map_delete_elem
3. 加载钩子代码
我们只需要把我们编译出来的*.o文件push到手机的system/etc/bpf目录下,重启手机,系统会自动加载我们的钩子文件,加载成功后会在 /sys/fs/bpf目录下显示我们定义的map及prog文件。
系统加载代码在system/bpf/bpfloader中,代码很简单。
主要有如下操作:
– /proc/sys/net/core/bpf_jit_enable
使能eBPF JIT,当内核设定BPF_JIT_ALWAYS_ON的时候,默认为1
– /proc/sys/net/core/bpf_jit_kallsyms
使特权用户可以通过kallsyms节点读取kernel的symbols
– 读取system/etc/bpf目录下的*.o文件,调用libbpf_android.so中的loadProg函数加载进内核。
– 生成相应的/sys/fs/bpf/节点。
– 设置属性bpf.progs_loaded为1
sys节点分为map节点和prog节点两种, 分别为map_
下面是Android Q版本上的节点信息。
可以使用下面的命令调试动态加载
4. 用户空间程序实现
下面我们需要编写用户空间的显示程序,本质上就是在用户态通过系统调用把BPF map给读出来。
5. 运行结果查看
直接在目录下执行mm,将编译出来的bpf.o push到/system/etc/bpf目录下,将统计程序push到/system/bin目录下,重启,看下结果。
前面的是pid, 后面的是系统调用次数。
至此,如何在android平台使用eBPF实现统计系统中每个pid在一段时间内系统调用的次数的功能就介绍完了。
此外还有很多技术细节没有深入研究,不过毕竟只是初探,就先讲到这里了,后续有时间再进一步深入研究。研究的时间还是比较短,如果有任何错误的地方欢迎指正。
参考资料
eBPF 简史 (下篇):
https://cloud.tencent.com/developer/article/1006318
goolge原生使用ebpf的两篇文章:
https://source.android.com/devices/architecture/kernel/bpf
https://source.android.com/devices/tech/datausage/ebpf-traffic-monitor
BCC:
https://github.com/iovisor/bcc
5T技术资源大放送!包括但不限于:C/C++,Arm, Linux,Android,人工智能,单片机,树莓派,等等。在公众号内回复「peter」,即可免费获取!!
记得点击分享、赞和在看,给我充点儿电吧