eBPF 与 Go,超能力组合(含视频)
本文地址:https://www.ebpf.top/post/ebpf_and_go
本文为 “Go 夜读” 分享的 “eBPF 与 Go 超能力组合” 的简单文字版本,完整视频可以在 Youtube 和 B 站观看,PPT 下载, 8.7M , Github 地址:DavadDi/bpf_demo
-
B 站 #123 eBPF 与Go,超能力组合【Go 夜读】
-
Youtube #123 eBPF 与Go,超能力组合 1:29:21
markdown 文档中嵌入 bilibili 视频参考这里
1. 前言
本文主要对 eBPF 程序跟踪 Go 程序的函数调用、Go 函数调用参数等方面进行介绍, 而且还基于 bpf_probe_write_user
函数对跟踪 Go 语言函数进行修改的样例。本文涉及的完整环境搭建,请参见 “环境准备” 篇。
被跟踪的 Go 语言代码非常简单,仅实现了一个 ebpfDemo
函数供 main
函数调用,主要代码如下:
|
|
其中第 5 行的 //go:noinline
注解明确告诉编译器不适用内联优化,避免造成造成我们无法对函数进行跟踪,相关内容可参考:Golang bcc/BPF Function Tracing 或者中文。
2. 使用 eBPF 跟踪 Go 程序
一般情况下的跟踪我们无需直接使用 C 代码编写 BPF 程序,社区相关的 BCC 或者 bpftrace 可协助我们快速上手,这里我们使用 bpftrace 工具,相比较 BCC,其提供的语法更加高阶和方便 ,更加详细的使用方式参见 bpftrace 参考。
2.1 查询可跟踪 uprobe 列表
首先我们可以使用 bpftrace 快速查看二进制文件提供的 uprobe 函数列表,语法支持通配符过滤。
|
|
上述命令运行后,我们可以获取到以 main 开头的两个函数 main.ebpfDemo
和 main.main
。
uprobe:./tracee:main.ebpfDemo
格式分别对应为:
uprobe
代表我们跟踪点的类型;./tracee
表示跟踪的二进制文件;main.ebpfDemo
代表我们跟踪的完整函数名词,其中main
为包名。
2.2 打印函数参数
|
|
其中 sarg0
, sarg1
, …, sargN
,用于表示基于栈保存的函数参数列表,默认参数是 64 位。
在函数 func ebpfDemo(a1 int, a2 bool, a3 float32)
中,sarg0
则代表了参数 a1
。 每次我们允许 tracee
上述这段 bpftrace 代码就会打印出我们传入的 a1
参数。
2.3 打印函数调用堆栈
我们也可以使用 bpftrace 来打印调用 main.ebpfDemo
的调用堆栈:
|
|
上述跟踪代码中的 ustack
表示用户空间的堆栈,其中 perf
表示栈的格式,还可以指定用户空间栈的层级,比如 ustack(perf, 3)
表示仅对 perf
格式的用户空间栈选取最近的 3 层。
2.4 跟踪函数调用延迟
如果要实现跟踪函数的调用延时,我们就需要在函数的入口和出口处进行相关时间的统计,这里不再方便使用 bpftrace -e 这种单行模式,这里我们定一个 call.bt
文件,内容如下:
|
|
uprobe
行表明我们跟踪函数 main.ebpfDemo
的入口,并将相关入口的时间信息以 pid 作为键值保存到名为 start
的 map 中。
uretprobe
表示在匹配在函数出口处,如果匹配了 start[pid]
,那么这将函数入口与出口的差值作为直方图数据保存至 ns
map 中,同时删除 start
中的相关数据。
在程序最终退出时,会统一打印出来保存的整体直方图的数据。
|
|
运行几次 tracee 后,我们可以在程序的结果看到函数延迟分布区间的直方图,这对于我们跟踪某些复杂场景下函数延迟情况提供了良好的数据分布展示。
3. 使用 eBPF 跟踪 Go 函数参数及修改对应参数
效果演示:
3.1 跟踪程序代码
3.2 跟踪和修改 ebpfDemo 入参的 BPF 程序代码如下:
|
|
其中 bpf_probe_write_user
用于修改 ebpfDemo
的第一个入参。
单次运行 tracee 结果如下:
|
|
启动 tracer 程序(make build && make run),然后再次运行 tracee (make run),tracee 和 tracer 的输出如下:
|
|
tracee 的输出结果如下:
|
|
通过运行结果我们发现通过 BPF 程序完成了 ebpfDemo
的参数跟踪和参数修改。
3.3 uprobe 原理
对于 Go 程序的跟踪,我们主要使用 uprobe/uretprobe 来进行跟踪,在整个跟踪架构中的位置如下图所示:
uprobe 的跟踪原理是在用户空间的地址中通过中断机制注入内核层面运行的 eBPF 代码,与 kprobe 在内核中直接调用 eBPF 代码相比,性能和吞吐量略有下降,但是就跟踪的场景看性能还是符合预期。
图片来自 Pixie 博客。
uprobe 注入函数的整体原理架构图如下:
以上跟踪使用 uprobe 方式进行跟踪,被跟踪的函数入口处会被 “int 3 ” 中断指令替换,在函数执行到此处时,会进入到中断,中断处理程序会查找到前期注册的 eBPF 程序并运行。
Uprobe 原理演示图如下:
完整演示版本参考如下:
此种方式的局限性如下:
eBPF 在分析 Go 语言 ABI 的时候局限于简单类型,更加复杂的类型(用户自定义结构体/指针/引用/通道接口等),比较适用的场景是函数的调用次数,函数延迟,函数返回值等;
基于 uprobe 需要被跟踪的程序带有符号表(not stripped)
eBPF 需要特权用户,一般情况下会限制适用范围;
4. 环境准备
本文我们测试的环境如下:
- Ubuntu 20.04 LTS , amd64
- bcc v0.23.0(源码安装)
- iovisor/gobpf v0.2.0
- Go1.15 linux/amd64 (最新 1.17 版本调用方式可能有变化)
需要注意 iovisor/gobpf 需要与 bcc 版本相对应,否则可能会报接口函数签名不一致,本质上 gobpf 库只是 bcc 底层开发库的一层 cgo 包装。
在 Ubuntu 20.04 LTS 系统中,仓库中对应的包存在一些问题,所以必须采用源码方式进行安装,报错信息如下:
|
|
详情可参见 Github Issue 214。
4.1 Ubuntu 20.04 环境安装
这里我们使用 multipass 进行环境安装:
|
|
直接使用 multipass launch -n ubuntu -c 4 -m 4G -d 40G 20.04
命令进行安装,详细参数介绍如下:
- -n ubuntu 创建虚拟机的名字,后续登录需要基于该名字;
- -c 虚拟机使用的 CPU 函数可以根据自己的情况进行调整;
- -m 虚拟机占用的内存大小;
- -d 虚拟机占用的磁盘,默认只有 5G,涉及编译可以根据自己情况调整;
- 20.04 为我们要安装的系统镜像名字,也可以使用别名,比如 focal(20.04);
系统安装成功后,使用 multipass shell ubuntu
即可进行登录。
BPF 程序运行需要依赖 linux-header文件,我们提前进行安装:
|
|
4.2 BCC 源码安装
安装流程参考 BCC 安装文档
|
|
4.3 Go 安装
|
|
5. 参考文档
- Introducing gobpf - Using eBPF from Go 主要介绍了 gobpf 库的使用姿势,文章比较老,可能有最新的使用优化。
- ebpf 修改golang 函数参数 和 Go internal ABI specification => 原文
- Debugging with eBPF Part 1: Tracing Go function arguments in prod 作者 为 Pixie 的Zain Asgar,从原理到最终使用都有比较详细的介绍
- YouTube 分享的视频 No-Instrumenttation Golang Logging with eBPF Golang Online Meetup 57
- 样例仓库参见 trace_example。
- Pixie 产品追踪 Go 功能参见 Dynamic Logging In Go (Alpha)
- 系列文章 Debugging with eBPF Part 2: Tracing full body HTTP request/responses 介绍了追踪 HTTP 使用 kprobe 和 urprobe 的各种利弊和性能对比
- 站点中 eBPF 相关文档 https://blog.px.dev/ebpf
- Tracing Go Functions with eBPF Part 1 作者为 grantseltze,2021 年的 Gopher 分享参见这里 Tracing Go Programs with eBPF!, YouTube 地址 GopherCon Europe 2021: Grant Seltzer Richman - Unlocking eBPF from Go
- Tracing Go Functions with eBPF Part 2 作者的开源工具 weaver,博客有不少 eBPF 文章,参见 https://www.grant.pizza/
- YouTube Tracing Go with eBPF / Florian Lehner @ GDG Berlin Golang 06/2021 ✅ 有 eBPF 背景介绍和 Go 跟踪的各个方面包括函数堆栈等 GitHub Slides;
- 其他样例代码参见这里 modify-arguments-on-the-fly 该样例代码不能稳定重现。
- 原文作者:DavidDi
- 原文链接:https://www.ebpf.top/post/ebpf_and_go/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 最后更新时间:2022-11-05 21:36:51.960881542 +0800 CST