调研是一门学问,但是我并不觉得我非常擅长。过去,我没有立志于成为一个研究性的程序员,实践对于我来说更有感觉。只是呢,随着编程年轮的一圈一圈地增长,研究性的开发也变成一个不可缺少的日常活动。虽也说不上是每日必备的活动,但是呢,每隔几天、向周也得做一些相关性的研究。
调研成为日常活动时,那不可避免的,我们也会发现一些研究的手法。尽管,我想总结一些相关的模式,但是对于我来说,时机还不够成熟,我也缺乏相关的经验。
调研是一门艺术。
同样的,起因也是项目的缘故,话题是 Time Travel Debugging,也被译为时间旅行调试。所以,从这次的经验来看,我把过程分为这么几部分:
看上去平淡无奇,和普通的技术研究没啥两样。而本文所针对的场景同是,大家都研究得相对较少的领域。
定义概念本身可以分为两部分:
万事就是得这么开头易。定义概念有几个好出处:
所以,我们先简单引用微软文档的定义(机翻版):
Time Travel Debugging 是一种工具,它使您可以记录(record)正在运行的进程的执行情况,然后在以后向前和向后重放(replay)它。Time Travel Debugging(TTD)通过让您“倒带”(rewind)调试器会话,来帮助您更轻松地调试问题,而不必在发现错误之前重现问题。
我刚看到介绍的时候觉得平谈无奇,直到写这篇文章的时候,我发现了隐藏了关键字:record-replay,于是先在这里提醒一下。
然后呢,维基百科上来了一个更详细的定义:
时间旅行调试是通过源码在时间上的倒退,以了解在执行计算机程序期间发生的事情的过程。
还有对应的说明:
通常来说,调试和调试器是帮助用户进行调试过程的工具,允许用户暂停正在运行的软件的执行并检查程序的当前状态。而后,用户可以及时前进,进入或跳过语句,然后向前执行。而交互(Interactive)式调试器呢,则包括修改代码并根据更新的信息前进的功能。反向(Reverse)调试工具,使用户可以在时间上向后退,以逐步达到程序中的特定点。时间旅行调试器提供了这些功能,还允许用户与程序交互,如果需要,可以更改历史记录,并观察程序如何响应。
从结论上来说,维基百科给了概念上的定义,而微软的文档则是侧重于实现方式上的定义。这样一结论,我们就得到了简单的结论:
时间旅行调试是一种软件开发的调试方式,通过将时间与源码关联,来让开发者了解程序运行期间发生的变化。它记录(record)下了程序在不同时间的状态,以便于在调试时可以向前和向后重放(replay)状态,来展示程序的运行情况。
从理论上来说,这一步并不是过于复杂,套路都很简单,常见的来源有:
维基百科给了一些相应的示例调试器:
直接进行相关的搜索,然后阅读,如:
有意思的事情是,我找到了一个作者写的大量相关论文。即微软的 Mark Marron,写的相关论文(主要是由第一篇看到的,然后搜索作者的相关论文)
所以,我便深入研究了相关的论文和作品,然后就中奖了 —— 发现了一种解决方案:
通过查找论文 References 及对应的 Related Work,我又找到了一系列的论文。诸如于:
这一点对于那些搞学术的人来说,应该算是比较常见的。
对于诸如时间旅行调试这一类属学术上的事物。并不能像其它领域,可以通过阅读书的方式来解决,但是搜索成本点高。所以,我并没有怎么尝试去找。
一次偶然的机会,我在知乎上搜索了 Time Travel Debugging,然后看到了『存在实现了后退功能的调试器吗?这种功能在实现上有什么难点呢?』这个问题, 又搜索到一波资料。
然而,大部分的意义不大 —— 并非开源,还得自己反向编译。
对于工程师而言,我们读论文的目的嘛,不就是为了知晓他们是如何解决问题的。所以,我更关注于它实现这些问题的模式。这些会在论文中进行大致的介绍,我们只需要有耐心阅读就可以了。
如『TARDIS: Affordable Time-Travel Debugging in Managed Runtimes』 中介绍的实施 TTD 系统的标准方法是:
对应的
又或者是『Framework for Instruction-level Tracing and Analysis of Program Executions』中引入的方式,
我们的框架由两个主要组件组成:一个称为 Nirvana 的运行时引擎和一个称为 iDNA(使用 Nirvana 的诊断基础设施)的跟踪记录和检索工具。
对应的一些关键代码的设计:
Event name | Description |
---|---|
TranslationEvent | when a new instruction is translated |
SequencingEvent | start of a sequence point and other special events |
InstructionStartEvent | start of every instruction |
MemReadEvent | memory read |
MemWriteEvent | memory write |
MemRefEvent | memory reference |
FlowChangeEvent | flow control instruction (branch, call, ret) |
CallRets | calls and returns |
DllLoadEvent | load of a new module |
相似的,还有其它不同的模式,由于篇幅原因,这里就不展开介绍了。
走马观花式的阅读了一系列论文之后,我大概有了如何设计一个系统的思路了。
首先,让我们来看一下所有的一些关键信息:
因为它需要根据具体的场景来做出选择,所以便只是简单的逻列一下。
如『Framework for Instruction-level Tracing and Analysis of Program Executions』中的示例
Original (guest) code:
mov eax,[ebp+4]
Translated (host) code:
mov esi, nirvContext._ebp
add esi, 4 ; callee saved register
mov edx, esi ; 2nd argument ?→ address of memory accessed
mov ecx, nirvContext ; 1st argument ?→ pointer to NirvContext
push 4 ; 3rd argument ?→ number of bytes accessed
call MemRefCallback ; calling convention assumes ecx and edx hold 1st and 2nd argument
mov eax, [esi] ; simulate intended memory read
mov nirvContext._eax, eax ; update NirvContext with new value
有了论文,阅读了相关的源码之后,我大概有了一个思路:
当然了 Demo 已经写好,只是呢,因为某种原因不方便贴在这里。
我一直在寻找一种方式,以系统性的记录对于某一领域的调研,这一篇文章相当于作为一个开始。
相关链接: