bpftrace 遍历 golang 链表(go17+)
不出意外的,之前提到的 ELF 文件解析内容又拖延了。目前还不知道什么时候有时间能够把希望完成的几篇文章给搞完。翻一翻目前的博客,已经有很久没有更新了。那就水一篇文章吧。目前算是项目里的低谷期,希望能够重拾程序员的意义。
在bpftrace 无侵入遍历golang链表里,笔者展示了使用bpftrace来遍历golang链表的方法。由于go-17和go-16的函数调用规约存在不同,因此bpftrace 无侵入遍历golang链表并不适用于go-17。其实这个问题在go-1.17+ 调用规约已经提到了解决方案。本文给一个实例,算是更进一步的延伸这个话题,希望能够起到一些效果。
eBPF tail-calls示例
最近在整理一些技术文章。本来希望把涉及ELF的内容整理出来,结果发现太难了。ELF涉及的内容要多很多,如果要把希望整理的内容表述清楚,还需要做一些准备的工作。刚好最近完成了tail-calls 的调研,先把关于eBPF的tail-calls的功能整理下吧。
eBPF程序是事件驱动的,这就意味着当目标事件触发后,程序才能执行。考虑这样一个场景:有几个不同的BPF程序均挂载在相同的hook点上,而执行需要保持一定的顺序。这时就需要借助tail calls的功能来实现。
BPF追踪Go程序的挑战
原文地址Challenges of BPF Tracing Go。翻译不尽如人意,继续努力。
BPF追踪Go程序的挑战
当大家对Go 1.17语言调用规约(function calling convention)调整带来的性能优化感到兴奋时,我却遗憾的看到Go 1.17并没有让BPF uretprobe变得可行。事实证明,我还没有完全意识到Go的可调整的栈空间会让事情变得多复杂。
PlantUML-文本化绘制UML多类图表
笔者一直都是文本编辑器教派的忠实拥趸:期望将所有的任务都通过文本编辑,而非鼠标/触摸板等,进行实现。从早年的
Vim,到现而今的Emacs,对文本化完成需求是越来越习惯,也越来越依赖了。最近刚好有了些时间,把最近的一些实践整理下。
在之前的文章,emacs org-mode 绘制思维导图,中,笔者有提到在探索不跳出Emacs这一文本编辑器的情况下,完成思维导图绘制的需求。在翻了一些文章后,找到了一款神器:PlantUML。其完美的匹配了笔者的需求:
- 不仅是思维导图,工程、文档常用的
UML图像也能全部支持文本化表示; - 功能强大,颜色、文本等格式均能支持;
Emacs友好,而且可以集成到Org-mode里使用。
而且,PlantUML支持在线使用,意味着能够很方便的获取、使用。这里做下介绍。
无侵入观测服务拓扑四元组的一种实现
最近有了些时间,继续整理下之前的项目。服务四元组的信息对于故障处置、根因定位等都有重要意义。使用eBPF可以做到无侵入用户代码获取服务四元组信息的功能。这一点在工程应用上很有意义。笔者在这方面投入了一些精力,这里做一下简单的总结。
服务四元组指的是[caller, caller_func, callee, callee_func]四元组。如下图是一个调用示例,站在服务A的角度,就存在如下两个四元组: [A, /a, B, /b],[A, /a, C, /c]。站在服务B, C的角度,也存在两个四元组(可能有不同的理解): [B, /b, none, none], [C, /c, none, none]。
service call
,-------. ,-. ,-. ,-.
|outisde| |A| |B| |C|
`---+---' `+' `+' `+'
| /a | | |
|-------------->| | |
| | | |
| | /b | |
| |----------->| |
| | | |
| | /c |
| |------------------------>|
,---+---. ,+. ,+. ,+.
|outisde| |A| |B| |C|
`-------' `-' `-' `-'
在弄清楚四元组是什么之后,下面进入今天的话题:如何使用BPF来采集四元组。需要说明的是,笔者这里的语言使用的是golang-1.16。golang不同语言版本间的区别,见:golang-1.17+调用规约。
值得注意的是,关于观测服务数据,是有很多解决方案的。本文仅是笔者实践的一种解决方案,在文末会简单提到这种方案的优缺点。
按照惯例,先看下效果吧:
# 启动采集
bpftrace ./http.bt
Attaching 2 probes... # 未触发请求前,停止在这里
caller: # 触发请求后,输出
caller_path: /handle
callee:
method: GET
host: 0.0.0.0:9932
url: /echo
caller:
caller_path: /echo
callee: none
# 开始服务
./http_demo &
# 触发请求
curl http://0.0.0.0:9932/handle
go-1.17+ 调用规约
go-1.17是一个很不友好的版本,这里我指的是函数调用规约的变更。在此之前,虽然栈传参比较奇怪,但是在掌握了规律后,参数信息很好获取。升级到go-1.17之后,笔者发现变更后的寄存器传值方式并不是系统的调用规约,至少和C/C++的是完全不一致的。这个问题使得笔者在处理ebpf方案时,始终无法覆盖go-1.17+的版本。虽然短期不会造成影响,线上服务使用的大多还在go-1.16以下,但是这始终是一个绕不过去的问题。近期通过查阅资料和参考其他开源项目里对这部分内容的处理,整理了一下go-1.17+的调用规约。
go在1.17之前使用的是内存栈来传递参数,这种传参的方式使得golang的语言设计很灵活:golang函数的多返回值能够很容易的实现。同样的,由于golang需要这样灵活的能力,是的系统默认的调用规约方式并不适用。在Proposal: Register-based Go calling convention文章里对这个问题进行了详细的讨论,总结起来是golang的特性使得使用系统默认规约并不能带来多语言交互上的收益,且golang希望保持独特。
本文下面会给出总结的调用规约,并且给出验证程序。本文档的整理所基于的平台是x86_64的centos8系统。其他架构下,寄存器名称可能不同。