GDB有支持反向调试(看到一个新的版本出来http://www.gnu.org/software/gdb/news/reversible.html )。 我琢磨如何工作的。
为了得到扭转调试工作,在我看来,你需要存储包括存储每个步骤的整个机器状态。 这将使性能慢得令人难以置信,更不要说使用了大量的内存。 如何解决这些问题呢?
GDB有支持反向调试(看到一个新的版本出来http://www.gnu.org/software/gdb/news/reversible.html )。 我琢磨如何工作的。
为了得到扭转调试工作,在我看来,你需要存储包括存储每个步骤的整个机器状态。 这将使性能慢得令人难以置信,更不要说使用了大量的内存。 如何解决这些问题呢?
我是一个GDB的维护者和新的反向调试的作者之一。 我很乐意谈论它是如何工作的。 正如一些人猜测,您需要保存,可以在以后恢复足够的机器状态。 有许多方案,其中之一是简单地保存由每个机器指令修改的寄存器或存储器位置。 然后,“撤销”该指令,你只是在恢复这些寄存器或存储位置的数据。
是的,它是昂贵的,但现代的CPU是如此之快,当你无论如何互动(做步进或断点),你真的不注意到它那么多。
请注意,您一定不要忘记使用模拟器,虚拟机和硬件记录器来实施反向执行。
另一种解决方案来实现它是跟踪在物理硬件上执行,如由绿大地和劳特巴赫在他们的基于硬件的调试器来完成。 基于每个指令的作用这个固定跟踪,然后你可以通过删除依次在每个指令的影响,移动到任何跟踪点。 请注意,这个假设可以跟踪影响状态在调试器中可见的所有东西。
另一种方式是使用一个检查点+重新执行方法,它是通过使用的VMware Workstation 6.5和Virtutech公司的Simics 3.0(或更高版本),并且其似乎与Visual Studio 2010是未来在这里,你使用虚拟机或模拟器获得间接的对系统的执行水平。 你经常转储整个状态到磁盘或内存中,然后依靠模拟器能够确定性地重新执行完全相同的程序路径。
简化的,它的工作原理是这样的:说你是在时间t的系统的执行。 去时间T-1,则拾取来自点t <T一些检查点,然后执行(TT-1)周期来结束原来的位置之前的一个周期。 这可以做的工作非常出色,并应用即使对于做磁盘IO,包括内核级代码的工作量,并进行设备驱动程序的工作。 关键是有一个包含整个目标系统,其所有的处理器,设备,存储器和iOS模拟器。 见gdb的邮件列表,并遵循有关详细信息,gdb的邮件列表上的讨论。 我用这个很接近定期自我调试棘手的代码,尤其是在设备驱动程序和早期的操作系统启动。
的信息的另一种来源是Virtutech公司白纸上检查点 (我写,以便在整个公开内容)。
期间的EclipseCon会议上,我们还问他们是如何做到这一点与Chronon调试器的Java。 这一个不允许你真正退一步,但可以在它的感觉就像逆向调试这种方式播放录制的程序执行。 (主要的区别是,你不能在Chronon调试程序改变正在运行的程序,而你能做到这一点在大多数其他Java调试器。)
如果我理解正确的话,它操纵正在运行的程序的字节代码,使得程序的内部状态的每一次变化被记录下来。 外部状态并不需要额外记录。 如果他们以某种方式影响你的程序,那么你必须有一个内部变量匹配外部状态(以及因此内部变量就足够了)。
在播放过程中的时间他们就可以基本上重新从记录状态的改变正在运行的程序的每一个状态。
有趣的是,国家的变化比人们预期的先看看小得多。 所以,如果你有一个条件:“如果”的声明,你会认为你需要至少一位记录程序是否拿了则─或其他语句。 在很多情况下,你能避免,甚至,像在这些不同的分支包含返回值的情况。 然后,它足以只记录返回值(这将是无论如何需要),并重新计算大约从返回值本身的执行分支的决定。
虽然这个问题是旧的,大部分的答案太,并作为反向调试仍然是一个有趣的话题,我发布一个2015年的答案。 第一章和第二章我的硕士论文2, 结合逆向调试,对计算机编程的视觉思维直播节目 ,涵盖了一些历史的方法来扭转调试(尤其是集中在snapshot-(或检查点) -和重播的方式),并解释它,无所不知调试的区别:
计算机,具有前瞻性执行程序直到某一点上,应该真的能为我们提供相关信息。 这样的改进是可能的,而且在所谓的全知调试器中找到。 他们通常被归类为逆向调试,尽管他们可能更准确地描述为“历史记录”调试程序,在执行期间,他们只是记录信息来查看或查询后,而不是允许程序员实际上是把时间往后在执行程序步骤。 “全知”来自于已经录制的节目的整个状态历史,事实,可用于执行后的调试器。 有那么没有必要重新运行该程序,无须手动代码实现。
基于软件的调试无所不知开始与1969年EXDAMS系统,在那里被称为“调试时的历史回放”。 GNU调试器,GDB,自2009年以来一直支持全知调试,凭借其“过程记录和回放”功能。 TotalView软件,UndoDB和Chronon似乎是目前市面上最好的无所不知的调试器,但商业系统。 TOD,为Java,似乎是最好的开源替代方案,这使得使用部分确定性的重播,以及部分轨迹捕获和分布式数据库,使所涉及的大量信息的记录。
调试器不只是允许记录的导航,但实际上能够在执行时间上倒退一步,也同样存在。 他们可以更精确地描述为背在时间,时间旅行,双向或反向调试器。
第一个这样的系统是1981年COPE原型...
内森·费尔曼写道:
但相反的调试只能让你回滚next和step命令,您键入的,还是让你撤消任何数量的指令?
您可以撤消任何数量的指令。 你不是仅限于,例如,仅在你当你前进停止点停止。 您可以设置一个新的断点,向后运行它。
举例来说,如果我设置的指令断点,并让它运行到那时,我可以再回滚到以前的指令,即使我跳过了呢?
是。 只要你打开录音模式,你跑到断点前。
这里是另一个反向的调试器称为ODB是如何工作的。 提取:
全知调试是收集“时间戳”在每个“感兴趣点”(设定值,使得一个方法调用,投掷/捕获异常)中的程序,并且然后允许程序员使用这些时间戳来探索的想法该程序运行的历史。
该ODB ...因为它们是加载和程序运行时,该事件被记录代码插入到程序的类。
我猜在同一种方式一期工程的GDB。
逆向调试意味着你可以向后运行的程序,这是非常有用的跟踪问题的原因。
你并不需要保存完整的机器状态的每一步,只有变化。 它可能仍然是相当昂贵的。
Mozilla的rr
是更稳健的替代GDB反向调试
https://github.com/mozilla/rr
GDB的内置录制和回放具有很大的局限性,例如,用于AVX指令不支持: GDB调试反转失败,“过程记录不地址支持指令0xf0d”
RR的上升空间:
下面的例子展示了它的一些特性,特别是reverse-next
, reverse-step
和reverse-continue
命令。
安装Ubuntu 16.04:
sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance
还要考虑从源代码编译,以获得最新的更新,是不需要努力的。
测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int f() {
int i;
i = 0;
i = 1;
i = 2;
return i;
}
int main(void) {
int i;
i = 0;
i = 1;
i = 2;
/* Local call. */
f();
printf("i = %d\n", i);
/* Is randomness completely removed?
* Recently fixed: https://github.com/mozilla/rr/issues/2088 */
i = time(NULL);
printf("time(NULL) = %d\n", i);
return EXIT_SUCCESS;
}
编译和运行:
gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay
现在你留下了GDB会话里面,可以适当反向调试:
(rr) break main
Breakpoint 1 at 0x55da250e96b0: file a.c, line 16.
(rr) continue
Continuing.
Breakpoint 1, main () at a.c:16
16 i = 0;
(rr) next
17 i = 1;
(rr) print i
$1 = 0
(rr) next
18 i = 2;
(rr) print i
$2 = 1
(rr) reverse-next
17 i = 1;
(rr) print i
$3 = 0
(rr) next
18 i = 2;
(rr) print i
$4 = 1
(rr) next
21 f();
(rr) step
f () at a.c:7
7 i = 0;
(rr) reverse-step
main () at a.c:21
21 f();
(rr) next
23 printf("i = %d\n", i);
(rr) next
i = 2
27 i = time(NULL);
(rr) reverse-next
23 printf("i = %d\n", i);
(rr) next
i = 2
27 i = time(NULL);
(rr) next
28 printf("time(NULL) = %d\n", i);
(rr) print i
$5 = 1509245372
(rr) reverse-next
27 i = time(NULL);
(rr) next
28 printf("time(NULL) = %d\n", i);
(rr) print i
$6 = 1509245372
(rr) reverse-continue
Continuing.
Breakpoint 1, main () at a.c:16
16 i = 0;