西瓜视频稳定性治理体系建设三:Sliver 原理及实践

本文介绍了Android稳定性治理中Sliver的实现原理和实践,包括快速抓栈、线程挂起、多线程、锁信息、数据结构等方面。通过分析,选择了线程挂起而非信号中断,以提高稳定性和降低维护成本。Sliver使用StackVisitor来抓取函数栈,并通过内存布局技巧构造StackVisitor实例,以避免厂商定制带来的问题。在多线程抓取中,采用分组方式减少性能损耗。同时,获取锁信息以辅助问题定位。整体结构上,Sliver在Java层和native层交互,高效记录和输出函数出入栈信息,实现高性能和低适配成本的稳定性治理。

WalkStack 函数遍历 ManagedStack,每个 ManagedStack 节点只能存在一种 frame 类型,然后在循环里不断遍历 cur_shadow_frame_ 或 cur_quick_frame_,每次得到一层函数栈,然后通过 StackVisitor 的虚函数 VisitFrame 来判断是否需要继续回溯。

art 里有许多 StackVisitor 的实现,在实现的 VisitFrame 内获取函数栈然后进行各种操作。比如上面提到的 FetchStackTraceVisitor 就在实现的 VisitFrame 内进行 ArtMethod 的筛选和记录等操作。

综上分析,抓取 Java 函数栈的核心函数就是 StackVisitor::WalkStack,art 内抓取函数栈的最终实现都是该函数。其实通过阅读 Profilo 的源码也可以发现,其抓栈的核心函数就是通过指针+偏移量的方案实现了 WalkStack,写死了大量偏移,并做了很多适配工作。这样做虽然可以实现功能,但稳定性就会变差,维护成本也会增高。Profilo 的开源版本截至目前仅适配到了 Android 9,适配 Android 10、11 和 12 的 32&64 位架构又是极大的工作量,而且厂商定制 ROM 若更改了任何一处源码,导致偏移量发生变化,那么轻则功能不可用,重则发生 Crash。

快速抓栈

既然自己实现栈回溯十分复杂且风险较大,那就直接用系统的函数,通过 xDL 查找符号表拿到 WalkStack 的函数指针直接调用。

该函数只有一个 bool 型入参,但该函数是成员函数,编译后会增加一个入参即 this 指针,而且 StackVisitor 还包含虚函数 VisitFrame,所以需要构造出一个实现 VisitFrame 的 StackVisitor。最简单的方案就是利用内存布局的特性,直接复制 StackVisitor 的代码到工程内,使用时直接 new 即可。

通过阅读源码发现,StackVisitor 的结构包含了如图中“CodeInfo”和“BitTableRange”这类变量,若直接采用复制的方案,一是复杂,二是厂商修改可能性大。通过上面对 WalkStack 的原理分析,回溯中的核心变量是 cur_shadow_frame_ 与 cur_quick_frame_,至于行号在 trace 中是非必须的,解析行号只会徒增成本,所以作为调用方其实只关心前四个变量,后面的变量我们并不关心其值,只要保证运行时有足够的内存 get/set 即可。

这里采用一种取巧的方案,自己构造的 StackVisitor 中,只写出虚函数和前四个核心变量,而后面的内容,使用长度足够的 char 数组占位,这样在运行时就有了足够的内存读写其他的变量,而且只使用前四个变量可以降低厂商定制带来问题的概率。

然后再实现一个继承自 StackVisitor 并实现了 VisitFrame 的函数,在其内部通过 cur_shadow_frame_ 与 cur_quick_frame_ 获取到每一帧对应的 ArtMethod,这样即可完成 StackVisitor 的构造。

通过函数指针调用 WalkStack,即可完成一次栈回溯的操作。

在 Callback 内将每一层的 ArtMethod* 保存到数组中,完成堆栈的记录。

线程挂起

实现了抓栈能力后,还需要选择暂停目标线程的方式,有两种方案,一是通过发送信号来实现中断,二是通过虚拟机的 Suspend 函数来实现挂起。

信号机制无论是

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值