如何在GDB中回到前一行?

97

在gdb中,是否可以跳转到当前执行行之前的某一行。


void my_fun( somePtrType** arr,int start,int end)
{
 // arr is an array of pointers to somePtrType
  //line a
 ... some assignments
 swap(&arr[ind1] , &arr[ind2] ) ;
 //line b (current line )
}
我目前在b行,可以检查那里的arr值,但我想回到a行并检查当时arr的内容。 我认为这可能不可能,因为调试器可以让代码慢动作运行,但无法倒退执行。 还有其他见解吗?
9个回答

134

没错!使用新版本7.0的gdb,你可以完全实现这一点!

命令将会是"reverse-step"或者"reverse-next"。

你可以从ftp.gnu.org:/pub/gnu/gdb获取gdb-7.0。

如果你遇到错误:Target child does not support this command.,那么在执行后,在run后加上target record

编辑:自从GDB 7.6起,target record 已经被弃用,使用target record-full代替。


7
指令存在,但是这里“目标子项不支持它”=( 无论如何还是投了赞成票。;) - freitass
@MichaelSnyder gdb 6.x在哪里? - SIFE
47
我收到了“目标多线程不支持此命令”的错误信息 :( - Derek 朕會功夫
1
现在我明白了:输入 start ... 然后 输入 target record-full。快捷方式是 rn - user2023370
1
recordtarget record-full 是一样的吗?在 https://sourceware.org/gdb/current/onlinedocs/gdb/Reverse-Execution.html#Reverse-Execution 中,它是提到的第一个命令,对我来说似乎可以工作。 - user2023370
在执行 starttarget record-full 后,rn 会产生以下错误: 进程记录和重放目标不支持系统调用编号292 进程记录:无法记录执行日志。 我猜反向调试也需要一些编译器标志吧? - SamuelMyself

16

是的,现在使用真实硬件(而不仅仅是虚拟机)非常容易就可以做到这一点。GDB-7.0支持反向调试命令,如reverse-step和reverse-continue,在本地linux x86机器上进行。

这里有一个教程:http://www.sourceware.org/gdb/wiki/ProcessRecord/Tutorial


14

mozilla rr

https://github.com/mozilla/rr

GDB内置的记录与重放功能有严重的限制,例如不支持AVX指令:gdb reverse debugging fails with "Process record does not support instruction 0xf0d at address"

rr的优点:

  • 当前更加可靠。我测试过多次运行较长复杂软件。
  • 还提供了GDB接口和gdbserver协议,是一个很好的替代品。
  • 对大多数程序的性能影响很小,没有进行测量也没有注意到明显的下降。
  • 生成的跟踪文件因为只记录了极少数的非确定性事件,所以在磁盘上很小,迄今为止我从未担心它们的大小。

以下示例展示了一些其特性,尤其是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

测试程序:

reverse.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;
}

编译并运行:

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;

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

然后,在第二次重放运行期间,它使用那个跟踪文件(出乎意料地小),以确定性的方式前进或后退重建原始非确定性运行中发生的一切。

最初,Mozilla 开发了 rr 来帮助他们重现在夜间测试之后出现的定时错误。但是,反向调试方面对于您在执行中仅发生数小时的错误非常重要,因为您通常希望后退以检查导致后来失败的先前状态。

我认为 rr 最严重的限制是:

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


7

如果你的程序很短,通常的技巧是:

  1. 在上一行设置一个新断点
    • 按下r重新启动调试

GDB 就是为此而生!


5

简短回答: 不行。

如果需要解决问题,请继续阅读以下内容。

虽然在 b 行无法确定 a 行的值,但是可以通过仅命中一个断点来记录 arr 在 a 和 b 行以及其他位置的值。

  • 使用“display”命令(display 变量名,其中变量名应替换为 arr、*arr、**arr,具体取决于您要查找的内容),以便在命中任何断点时,变量名的内容将被转储到屏幕上。请注意,在变量名在作用域内时,可以将其添加到显示列表中,因此可能需要等待第一个断点。
  • 在代码的各个位置创建断点,您希望记录变量名的值。其中一个这样的断点将位于 a 行。
  • 对于每个断点,请使用命令(command 断点号)并指示您的断点不会停止程序的执行。您需要使用的命令是 continue,后跟 end。请参见下面的示例。

(gdb) command 1

当断点 1 被命中时,输入一行命令。每行一条命令。最后一行为“end”。

continue

end

  • 在 b 行上设置断点。

现在,当命中所有其他日志记录断点时,arr 的值将被转储到屏幕上,但断点不会等待用户交互并将自动继续。当您在 b 行上命中断点时,可以在 gdb 中查看过去的 arr 值。

根据情况,您还可以转储(和显示)许多有用的信息。例如,如果上述函数在循环中调用了 10000 次,则可能还要转储循环计数器(例如 i)。这实际上取决于您想要实现什么目标。


2
这是一种非常有用的技术,即使真正的反向调试是可能的!反向调试有缺点:它很慢,在不支持的地方也无法使用,并且在保存记录数量方面存在限制。但请编辑您的答案以考虑新的开发。 - misiu_mp

2

如果您的arr设置代码就在“a行”上方(这是一个非常常见的情况),您可以按照以下方式执行:

tbreak myfilename.c:123(第123行是arr设置代码的开头),然后

jump 123

"tbreak"可以防止gdb在跳转后继续(恢复)程序。

然后,您可以逐步执行设置代码,或者只是在“line a”处设置断点并继续执行。


1
在GDB中通常不可能做到这一点,但是你可以使用一个名为QIRA的调试器轻松地回溯历史。你可以使用上下箭头来前后移动,它还会突出显示哪些寄存器发生了变化。

example session with QIRA


1
根据GDB文档,以及“如果目标环境支持”,是的。

据我所知,GDB CVS Head 已经在 Linux/i386 和 x86_64 目标平台上实现了反向调试。 - Employed Russian

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接