反向调试如何工作?

Nat*_*man 80 reverse-debugging

GDB有一个支持反向调试的新版本(参见http://www.gnu.org/software/gdb/news/reversible.html).我想知道它是如何工作的.

为了使反向调试工作,在我看来,您需要存储整个机器状态,包括每个步骤的内存.这会使性能变得非常慢,更不用说使用大量内存了.这些问题是如何解决的?

Mic*_*der 121

我是一个gdb维护者,也是新的反向调试的作者之一.我很乐意谈谈它是如何运作的.正如一些人推测的那样,您需要保存足够的机器状态,以便以后恢复.有许多方案,其中之一是简单地保存由每个机器指令修改的寄存器或存储器位置.然后,要"撤消"该指令,您只需还原那些寄存器或存储器位置中的数据.

是的,它很昂贵,但是现代的cpu是如此之快,以至于当你是交互式的(做踩踏或断点)时,你并没有真正注意到它.

  • >但是,反向调试只允许您回滚下一步和您键入的步骤命令,或者它是否允许您撤消任意数量的指令您可以撤消任意数量的指令.你不仅限于,例如只在你前进时停在你停下的地方.你可以设置一个新的断点并向后运行它>例如,如果我在一条指令上设置一个断点并让它运行到那时,我可以回滚到上一条指令,即使我跳过它是的只要你在跑到断点之前打开录制模式 (9认同)
  • 我担心反向调试可以消除时间,让我们回到60或70年代.我不想穿喇叭裤,再长头发. (8认同)
  • 但是,反向调试是否只允许您回滚键入的"next"和"step"命令,还是允许您撤消任意数量的指令?例如,如果我在一条指令上设置一个断点并让它运行到那时,那么我是否可以回滚到上一条指令,即使我跳过了它? (4认同)
  • 以及在操作系统中修改状态的syscall?难道就是不能正常工作?修改不透明的手柄该怎么办? (3认同)
  • 抱歉没有格式化的文本,不知道是什么. (2认同)

jak*_*om2 11

请注意,您不能忘记使用模拟器,虚拟机和硬件记录器来实现反向执行.

实现它的另一个解决方案是跟踪物理硬件上的执行,例如GreenHills和Lauterbach在其基于硬件的调试器中执行的操作.根据每条指令动作的固定轨迹,您可以依次移除每条指令的效果,移动到轨迹中的任意点.请注意,这假设您可以跟踪影响调试器中可见状态的所有事物.

另一种方法是使用检查点+重新执行方法,该方法由VmWare Workstation 6.5和Virtutech Simics 3.0(及更高版本)使用,并且似乎随Visual Studio 2010一起提供.在这里,您使用虚拟机或模拟器获得系统执行的间接级别.您经常将整个状态转储到磁盘或内存,然后依赖模拟器确定性地重新执行完全相同的程序路径.

简化,它的工作方式如下:假设您在执行系统时处于时间T. 要进入时间T-1,您从点t <T获取一些检查点,然后执行(Tt-1)个周期以结束一个周期,然后再到达.这可以很好地工作,甚至适用于执行磁盘IO的工作负载,包括内核级代码,并执行设备驱动程序工作.关键是要有一个包含整个目标系统的模拟器,包括所有处理器,设备,内存和IO.有关更多详细信息,请参阅gdb邮件列表以及gdb邮件列表上的讨论.我经常使用这种方法调试棘手的代码,特别是在设备驱动程序和早期操作系统启动时.

另一个信息来源是关于检查点Virtutech白皮书(我在完整披露中写过).


Ban*_*zen 9

在EclipseCon会话期间,我们还询问了他们如何使用Chronon Debugger for Java 执行此操作.那个不允许你实际退后一步,但可以回放一个记录的程序执行,让它感觉像反向调试.(主要区别在于您无法在Chronon调试器中更改正在运行的程序,而您可以在大多数其他Java调试器中执行此操作.)

如果我理解正确,它会操纵正在运行的程序的字节代码,以便记录程序内部状态的每次更改.外部状态不需要另外记录.如果它们以某种方式影响您的程序,那么您必须具有与该外部状态匹配的内部变量(因此该内部变量就足够了).

在播放时间内,它们基本上可以从记录的状态变化中重新创建正在运行的程序的每个状态.

有趣的是,状态的变化比初看起来的要小得多.因此,如果您有一个条件"if"语句,您会认为至少需要一位来记录该程序是否采用了then或else语句.在许多情况下,您甚至可以避免这种情况,例如在那些不同的分支包含返回值的情况下.然后只记录返回值(无论如何都需要)并从返回值本身重新计算有关已执行分支的决定就足够了.


Abr*_*ham 8

虽然这个问题很老,但大多数答案都是如此,而且仍然是一个有趣的话题,我发表了2015年的答案.我的硕士论文的第1章和第2章,将反向调试和实时编程结合到计算机编程中的视觉思维,涵盖了一些反向调试的历史方法(特别关注快照(或检查点)和重放方法),以及解释它与无所不知的调试之间的区别:

计算机已经向前执行了某个程序,应该能够为我们提供有关它的信息.这种改进是可能的,并且可以在所谓的无所不知的调试器中找到.它们通常被归类为反向调试器,尽管它们可能更准确地被描述为"历史记录"调试器,因为它们仅在执行期间记录信息以便稍后查看或查询,而不是允许程序员在执行程序中实际倒退. ."Omniscient"来自这样一个事实,即已经记录的程序的整个状态历史在执行后可供调试器使用.因此无需重新运行程序,也无需手动代码检测.

基于软件的无所不知的调试始于1969年的EXDAMS系统,在那里它被称为"调试时间历史 - 回放".自2009年以来,GNU调试器GDB一直支持无所不知的调试,具有"进程记录和重放"功能.TotalView,UndoDB和Chronon似乎是目前可用的最好的全知调试器,但它们是商业系统.对于Java来说,TOD似乎是最好的开源替代方案,它利用部分确定性重放,以及部分跟踪捕获和分布式数据库来记录所涉及的大量信息.

也存在不仅允许导航记录,而且实际上能够在执行时间内倒退的调试器.它们可以更准确地描述为回溯时间,时间旅行,双向或反向调试器.

第一个这样的系统是1981年的COPE原型......


Cir*_*四事件 6

mozillarr是 GDB 逆向调试的更强大的替代方案

https://github.com/mozilla/rr

GDB的target record-full内置记录和重放受到严重限制,特别是它存储了太多的状态数据,因此记录长度非常有限。以前它不支持 AVX 指令:gdb 反向调试失败,并显示“进程记录不支持地址处的指令 0xf0d”,但这似乎已得到修复。

rr 的优点:

  • 目前更可靠。我已经测试了几个复杂软件的相对较长的运行时间。
  • 还提供带有 gdbserver 协议的 GDB 接口,使其成为一个很好的替代品
  • 大多数程序的性能下降很小,我自己在没有进行测量的情况下没有注意到这一点
  • 生成的跟踪在磁盘上很小,因为只记录了很少的非确定性事件,到目前为止我从来不必担心它们的大小

rr 通过首先以记录每个非确定性事件(例如线程切换)发生的情况的方式运行程序来实现此目的。

然后,在第二次重播运行期间,它使用该跟踪文件(该文件非常小)来准确重建原始非确定性运行中发生的情况,但以确定性方式(向前或向后)。

rr 最初由 Mozilla 开发,旨在帮助他们重现第二天夜间测试中出现的计时错误。但是,当您遇到仅在执行数小时内发生的错误时,反向调试方面也是至关重要的,因为您经常希望退后一步检查先前的状态导致了后来的失败。

以下示例展示了它的一些功能,特别是reverse-nextreverse-stepreverse-continue命令。

在 Ubuntu 18.04 上安装:

sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
sudo cpupower frequency-set -g performance
# Overcome "rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is 3."
echo 'kernel.perf_event_paranoid=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Run Code Online (Sandbox Code Playgroud)

测试程序:

反向.c

#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;
}
Run Code Online (Sandbox Code Playgroud)

编译并运行:

gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay
Run Code Online (Sandbox Code Playgroud)

现在您处于 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;
Run Code Online (Sandbox Code Playgroud)

在调试复杂的软件时,您可能会遇到崩溃点,然后陷入深层框架中。在这种情况下,不要忘记在reverse-next更高的帧上,您必须首先:

reverse-finish
Run Code Online (Sandbox Code Playgroud)

直到那个框架,仅仅做平常的事情up是不够的。

我认为 rr 最严重的局限性是:

UndoDB 是 rr 的商业替代品: https: //undo.io两者都是基于跟踪/重放的,但我不确定它们在功能和性能方面如何比较。