异常原因修复后GDB恢复执行

GDB resuming execution after the exception cause is fixed

假设我有一个程序,第 5 行 有一个语句,它创建一个算术异常:被零除。

当我 run GDB 中的程序时,我得到如下信息:

Program received signal SIGFPE, Arithmetic exception.
0x... in ... at prog.c:5

那时,我使用以下方法将除数变量更改为非零值 set var b=1 然后尝试从发生异常的同一语句“恢复”。

使用命令 continue 给出以下结果:

Program terminated with signal SIGFPE, Arithmetic exception.
The program no longer exists.

所以正确的方法是使用(而不是 continue 命令)像 jump 5 这样的命令,它按预期工作。

是否有任何其他更简单 GDB 命令可以执行此操作而无需指定程序出现异常的行号?一行中可能有多个语句,所以不能一直用行号寻址。

我在 GDB 文档中找不到与此相关的任何内容。

这种方法不能在一般情况下工作,特别是在优化开启的情况下。

您所做的 恰好 为您的特定 line/case 工作。我们不能说太多,因为您没有 post 代码。

变量b在内存中开始(在栈上或在全局内存中)。

在某些时候,它可能最终进入 FP 寄存器(例如,对于 x86,xmm*)。

所以,当您这样做时:set var b = 1 您是在设置内存位置还是 xmm 寄存器?

即使假设每行只有一个语句,当您执行 jump 时,您正在重新启动 语句(它可以包含多个 asm 指令).

我们还需要做什么来确保所有 other 值(寄存器、内存等)都处于 pre-statement/pre-instruction 状态?

通过优化,b 的值可能已通过 prior 语句缓存在 xmm 寄存器中,而您正在执行的语句jump 可能 不会 重新加载它 [从内存]

您要做的是重新启动指令。此时,指令的第二个参数可能是 xmm 寄存器或内存位置。我们怎么知道是哪一个?

此外,如果您有(例如):

a = get_a_value();
// several more statements ...
a /= b;  // line 5

然后,我们假设 a 在(例如)xmm0 [并且 bxmm1] 中。以下为伪汇编:

fdiv %xmm0,%xmm1 // a /= b

[伪]fdiv指令对%xmm0a的值做了什么?在指令之后 a 的值仍然完好无损,还是 %xmm0 现在有一个 partial/garbage 值?失败指令后 %xmm0 中的值取决于体系结构。如果值被丢弃,我们如何确定 original/correct 值放回 %xmm0?

必须执行多少先前的语句才能恢复a的正确值?

您可能必须反汇编“有问题的”指令并更改第二个参数的值。如果它是内存位置,请更改它。如果是 xmm* 寄存器,则必须更改它。

并且,您必须确定必须执行哪些操作才能恢复 a 值(它所在的任何地方)。

您要进行的补救需要在指令级别处理。语句级别太粗粒度。

你找不到这方面的文档的原因是你所做的在一般情况下不会很好地工作并且正在扭曲 gdb

gdb 旨在让您捕获此类异常,确定程序中的错误是什么,修复 source 代码,重建并重新运行。

C [and/or 机器代码] 的设计目的不是为了允许这种动态补丁并从之前的点重新启动 [在调试器控制台中],就像您在 [=106= 等解释语言中可能有的那样].

Program received signal SIGFPE, Arithmetic exception. 0x... in ... at prog.c:5 At that point, I change the divisor variable to a nonzero value using set var b=1 and then try to "resume" from the same statement that got the exception. Using the command continue gives the following:...

如果程序接收到异常,这意味着程序已经完成(更好的说,已经被内核完成)所以你不能继续。

要想做你假装做的事,你需要在程序崩溃之前进行干预。因此,在程序必须进行除法之前放置一个断点,以便调试器在程序崩溃之前停止程序,然后更改除数变量(使用 set var... 命令),然后让它继续,并观察它不会崩溃。

set br 5
run
set var b = 5
cont

现在一切顺利。

如果你的程序在没有通知调试器的情况下结束,内核会通知它程序终止...但是一旦程序终止,就没有机会 运行 它从它的状态 (即使你有一个核心来做 post-mortem debugging)异常的核心是不可继续的,因为程序已经死亡并失去了控制。调试器不知道从哪里再次启动它 运行ning,因为它处于错误状态。

但是相信我,最好的办法是检查除数为什么为零,并在程序本身中避免这种情况,而不必每次都运行调试器你想通过除法点来改变除数,这样程序就不会失败。

正如@MarkPlotnick 在我的问题中评论的那样,signal 0 是我一直在寻找的答案(受限于@CraigEstey 的回答中提到的所有注意事项):

$ gdb -silent ./a.out  ## Program compiled with disabled optimizations!
Reading symbols from ./a.out...
(gdb) l 1,20
1   #include <stdio.h>
2   
3   void bol (int a, int b)
4   {
5     printf("%d / %d = %d\n", a, b, a/b);
6   } /* bol */
7   
8   int main (void)
9   {
10    int x, y;
11    for (x=170, y=6; x>-170; x-=30, y-=2)
12      bol(x, y);
13  } /* main */
(gdb) r
Starting program: [...]/a.out 
170 / 6 = 28
140 / 4 = 35
110 / 2 = 55

Program received signal SIGFPE, Arithmetic exception.
0x000055555555515f in bol (a=80, b=0) at prog.c:5
5     printf("%d / %d = %d\n", a, b, a/b);
(gdb) set var b=1
(gdb) sig 0
Continuing with no signal.
80 / 1 = 80
50 / -2 = -25
20 / -4 = -5
-10 / -6 = 1
-40 / -8 = 5
-70 / -10 = 7
-100 / -12 = 8
-130 / -14 = 9
-160 / -16 = 10
[Inferior 1 (process 7088) exited normally]
(gdb) q