[PATCH 0/8] Break at each iteration for breakpoints placed on a while statement

Kevin Buettner kevinb@redhat.com
Wed Aug 19 06:53:00 GMT 2015


This patch set changes the current behavior of breakpoints placed on
while loops. (It actually restores past behavior; see below.)

Consider the following code:

7         v = 0;
8
9         while (v < 3)                         /* Loop 1 condition */
10          {
11            v++;                              /* Loop 1 increment */
12          }

This example is taken from the new test case, loop-break.c.

Let's place breakpoints at lines 9 and 11:

(gdb) b 9
Breakpoint 1 at 0x4005af: file gdb.base/loop-break.c, line 9.
(gdb) b 11
Breakpoint 2 at 0x4005a0: file gdb.base/loop-break.c, line 11.

We'll run the program and then continue to get to breakpoint #2:

(gdb) run
Starting program: gdb.base/loop-break

Breakpoint 1, loop_test ()
    at gdb.base/loop-break.c:9
9         while (v < 3)                         /* Loop 1 condition */
(gdb) c
Continuing.

Breakpoint 2, loop_test ()
    at gdb.base/loop-break.c:11
11            v++;                              /* Loop 1 increment */

So far, so good.  Now, watch what happens when we continue again:

(gdb) c
Continuing.

Breakpoint 2, loop_test ()
    at /ironwood1/sourceware-git/mesquite-native-5509943/bld/../../binutils-gdb/gdb/testsuite/gdb.base/loop-break.c:11
11            v++;                              /* Loop 1 increment */

GDB has somehow missed the breakpoint placed on line 9.  The user is
unable to examine state prior to evaluation of the loop condition.

The compiler is implementing this looping construct in the following
fashion.  An unconditional branch is placed at the start of the loop,
branching to an address immediately after the loop body.  At that point,
the condition is evaluated.  If the condition evaluates as true, a
conditional branch causes execution to continue at the first instruction
of the loop body, placed immediately after the unconditional branch.

GDB is placing its breakpoint on the unconditional branch. This is fine
for the first iteration of the loop, but does not work for subsequent
iterations.

This is the code that gcc generates on x86_64:

0x000000000040059e <loop_test+14>:   jmp    0x4005af <loop_test+31>
0x00000000004005a0 <loop_test+16>:   mov    0x200a8a(%rip),%eax # 0x601030 <v>
0x00000000004005a6 <loop_test+22>:   add    $0x1,%eax
0x00000000004005a9 <loop_test+25>:   mov    %eax,0x200a81(%rip) # 0x601030 <v>
0x00000000004005af <loop_test+31>:   mov    0x200a7b(%rip),%eax # 0x601030 <v>
0x00000000004005b5 <loop_test+37>:   cmp    $0x2,%eax
0x00000000004005b8 <loop_test+40>:   jle    0x4005a0 <loop_test+16>

The breakpoint is being placed on 0x40059e (loop_test+14).  As such, it
gets hit only once.  If we could arrange to have the breakpoint placed at
the branch target, it would stop at each iteration of the loop. I.e.
it would behave like this:

(gdb) b 9
Breakpoint 1 at 0x4005af: file gdb.base/loop-break.c, line 9.
(gdb) b 11
Breakpoint 2 at 0x4005a0: file gdb.base/loop-break.c, line 11.
(gdb) command 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>p v
>end
(gdb) run
Starting program: gdb.base/loop-break

Breakpoint 1, loop_test ()
    at gdb.base/loop-break.c:9
9         while (v < 3)                         /* Loop 1 condition */
$1 = 0
(gdb) c
Continuing.

Breakpoint 2, loop_test ()
    at gdb.base/loop-break.c:11
11            v++;                              /* Loop 1 increment */
(gdb) c
Continuing.

Breakpoint 1, loop_test ()
    at gdb.base/loop-break.c:9
9         while (v < 3)                         /* Loop 1 condition */
$2 = 1

This change introduces a new gdbarch method for potentially following
an unconditional branch.  If the circumstances are right, it uses
this method to cause the breakpoint to be placed on the branch target
instead of the branch itself.

This fixes the problem described above and causes no regressions.
The new test case includes the loop shown above.  It also contains
several other loops which verify that GDB stops at the the correct
places in each.  Only while loops of the form shown above are
affected by this patch; other looping constructs continue to work
as they had before.

Of particular interest is the test which uses an explicit goto; this
mimics the code that the compiler generates for a while loop.
However, in this example, placing a breakpoint on the goto should
cause execution to stop on the goto.  My initial attempt at a fix
for this problem caused that explicit goto to be followed, which is
definitely not correct.

Lastly, I'll note that, in the distant past, GCC used to output code
for the condition at the top of the loop.  GDB from either then or now
will stop at the evaluation of the condition each time through the
loop.

I was able to locate a compiler that I could run from circa 1998. While
I was able to run the compiler, I was not able to run the linker; it
died with an internal error in collect2, undoubtedly due to not having
compatible libraries.  But I was able to use -S in order to see
what the assembly code looked like.  Here is the assembly code for
our example loop, compiled by gcc 2.9, targeting i386-pc-linux-gnu:

	    movl $0,v
    .stabn 68,0,9,.LM3-loop_test
    .LM3:
	    .align 4
    .L2:
	    movl v,%eax
	    cmpl $2,%eax
	    jle .L4
	    jmp .L3
	    .align 4
    .L4:
    .stabn 68,0,11,.LM4-loop_test
    .LM4:
	    movl v,%eax
	    leal 1(%eax),%edx
	    movl %edx,v
    .stabn 68,0,12,.LM5-loop_test
    .LM5:
	    jmp .L2
    .L3:

The loop begins with the evaluation of the condition at .L2.  The jle
branches to instructions forming the body of the loop; the jmp instruction
immediately following the jle gets us out of the loop.

This code isn't very efficient, but it is a straightforward translation
of the corresponding C code.  A breakpoint placed at the beginning of
line 9 (which is both .LM3 and .L2) will stop on each iteration through
the loop.

My patch-set restores this behavior for while loops implemented in
a more efficient manner.



More information about the Gdb-patches mailing list