如何实现确定性调试
1. 问题背景
1.1 Bug并不总是可以稳定复现
在软件开发和测试过程中,我们经常遇到一些难以稳定复现的bug。以软件测试中的flaky tests为例,如果我们有沉淀测试用例,团队也有例行测试用例执行检查,我们有可能会发现一些flaky tests。但是因为flaky tests出现的原因多种多样,偶现或者很难出现,所以导致难以定位。
复现问题可能比解决问题更复杂,因为通常一个问题可以稳定复现,解决它只是确定最终方法的工作量,而复现问题需要花多长时间,则可能是无法预知的,有可能不得不观察1天、一周甚至更久。
1.2 调试会话中的效率问题
有时调试会话中一不小心错过感兴趣的断点位置,只能重新启动调试活动,比较低效。如restart从头开始,或者continue执行等到下一个感兴趣事件到来命中感兴趣的断点。如果能回滚刚才执行的程序语句,回到之前的状态,则有机会在感兴趣的位置设置断点,以进行更高效地调试。
2. 调试难点
2.1 如何进行确定性地调试
有时可能我们发现了故障,问题现场也被保留了,但是依然难以推敲先在这种情形是如何造成的。如果能将问题出现之前、问题发生时、问题表现出故障这一连串的程序执行过程全部给录制下来,并能够稳定地回放,那我们就可以进行确定性的调试。record & replay可能是对此类问题最好的解法。
2.2 Record & Replay的广泛应用
当然,record & replay也不光可以用于解决此类刁钻问题,它也可以用于其他一般情景中的问题定位。因为录制和回放技术,它允许你撤销回放中的某几步操作,以此来实现撤销程序执行的指令、恢复执行前的状态,这使得我们可以在一般的调试活动中可以避免因为错过兴趣点而不得不重启调试活动,如restart从头开始,或者continue执行等到下一个感兴趣事件到来命中感兴趣的断点,然后才能再对目标问题发起debug。
3. 核心问题
那么核心问题就是如何实现record & replay,record哪些内容?如何record这些内容?如何replay这些内容?调试器如何使用这些recorded traces进行精确地回放?
Mozilla RR提供了record & replay的能力支持,并且RR基于这项核心技术对GDB进行了增强,可以直接将RR作为调试器前端的对应调试器后端,前后端通过GDB serial协议进行通信即可,调试器前端可以支持反向next、step、stepin、stepout、continue等操作,以实现正常执行流程下的reverse direction操作下的同等类型操作,如当前是反向执行模式,continue将执行到之前执行过的最近的断点位置处。
在这个能力基础上,Mozilla RR实现了这个宏伟的目标:"记录一次失败,你就可以无限制地进行确定性地调试"。
4. RR实现细节
那么Mozilla RR是如何实现这些record & replay技术的呢?这可以参考Mozilla RR的论文以及它的文档:
- 官方主页:rr-project
- Github地址:rr-debugger/rr
- 论文:Engineering Record And Replay For Deployability Extended Technical Report
4.1 设计原理
RR的设计基于一个关键观察:CPU在大多数情况下是确定性的。RR识别状态和计算的边界,记录边界内所有非确定性来源和跨越边界的输入,然后通过重放非确定性和输入来重新执行边界内的计算。如果所有输入和非确定性都被真正捕获,那么重放期间边界内的状态和计算将与记录期间匹配。
4.2 技术约束
RR依赖于现代硬件和操作系统特性,这些特性原本是为其他目标设计的。RR需要:
- 现代Intel CPU:支持性能计数器,用于测量应用程序进度
- Linux内核:支持ptrace、seccomp-bpf、perf context-switch事件等特性
- 用户空间实现:完全在用户空间运行,不需要内核修改
4.3 核心实现技术
4.3.1 系统调用拦截
RR使用ptrace来记录和重放系统调用结果和信号。为了避免非确定性数据竞争,RR一次只运行一个线程。使用CPU硬件性能计数器来测量应用程序进度,确保异步信号和上下文切换事件在正确的时刻传递。
4.3.2 进程内系统调用拦截优化
RR实现了一个新颖的进程内系统调用拦截技术来消除上下文切换,这大大减少了重要实际工作负载的录制和重放开销。这个优化依赖于现代Linux内核特性:
- seccomp-bpf:选择性地抑制某些系统调用的ptrace陷阱
- perf context-switch事件:检测记录的线程在内核中阻塞
4.3.3 内存和状态管理
RR几乎保留用户空间执行的每个细节。特别是,用户空间内存和寄存器值被精确保留,这意味着CPU级控制流在记录和重放之间是相同的,内存布局也是如此。
4.4 性能表现
根据RR论文的数据,对于低并行度工作负载(特别是运行Firefox测试套件的Firefox),RR的录制和重放开销小于2倍,这在可部署性相当的方案中是最低的。
4.5 实际应用
RR被许多开发者日常使用,作为高效反向执行调试器的基础,可以处理复杂的应用程序,如:
- Samba
- Firefox
- Chromium
- QEMU
- LibreOffice
- Wine
5. 参考资料
- 论文:Engineering Record And Replay For Deployability
- 官方主页:https://rr-project.org/
- GitHub地址:https://github.com/rr-debugger/rr
6. 总结
确定性调试通过record & replay技术解决了传统调试中的两个核心问题:难以复现的bug和调试会话中的效率损失。Mozilla RR通过巧妙利用现代硬件和操作系统的特性,实现了完全用户空间的record & replay系统,为开发者提供了强大的调试工具。
RR的成功证明了在标准硬件和软件上构建实用的record & replay系统是可能的,这为未来的调试工具开发提供了重要的参考和基础。