Bug 17315 - 'until' behavion in watchpoint.c (for loops) incompatible with loop condition instructions at the top of the loop
Summary: 'until' behavion in watchpoint.c (for loops) incompatible with loop condition...
Status: RESOLVED FIXED
Alias: None
Product: gdb
Classification: Unclassified
Component: c++ (show other bugs)
Version: 7.7
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
: 27196 (view as bug list)
Depends on:
Blocks:
 
Reported: 2014-08-26 22:20 UTC by David Blaikie
Modified: 2022-05-17 19:26 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description David Blaikie 2014-08-26 22:20:28 UTC
Simplifying the watchpoint.c code down to:

  void func() {}

  int main() {
    int a;
    for (a = 0; a != 2; ++a) {
      func();
    }
    return 0;
  }

And running this through clang (assembly attached) and gdb, the following behavior is observed:

=> 0x00000000004005af <main+15>:        c7 45 f8 00 00 00 00    movl   $0x0,-0x8(%rbp)
(gdb) until
=> 0x00000000004005c3 <main+35>:        e8 c8 ff ff ff  callq  0x400590 <func>
(gdb) 
=> 0x00000000004005c8 <main+40>:        8b 45 f8        mov    -0x8(%rbp),%eax
   0x00000000004005cb <main+43>:        05 01 00 00 00  add    $0x1,%eax
   0x00000000004005d0 <main+48>:        89 45 f8        mov    %eax,-0x8(%rbp)
(gdb) 
=> 0x00000000004005c3 <main+35>:        e8 c8 ff ff ff  callq  0x400590 <func>

so 'until' doesn't cause the loop to be skipped at all - continuing to run 'until' will just behave as though the user is stepping through the entire loop.

I believe the issue here is that Clang keeps the loop condition at the top of the loop, whereas GCC puts it at the end, the theory being 'until' really looks for an instruction with a higher PC than the /last/ instruction on the line you started at, not the specific instruction you started at.

(eg: while I ran "until" from <main+40>, I went through 40, 43, 48, then <main+51> jumped up to the top of the loop (+22) but on the same line according to the line table, continued on to 29, and then to 35 - since 35 is on a distinct line from 29 and 29 > 35, GDB stopped here, even though 40 !> 35)

And GCC puts the condition at the end of the loop, so you go from increment, to condition, then jump up from the condition to the body - changing lines and decreasing the PC, so that causes until to skip that and keep going... until eventually it gets out of the loop.
Comment 1 David Blaikie 2017-01-26 15:52:21 UTC
ping
Comment 2 Guinevere Larsen 2021-12-09 14:34:57 UTC
*** Bug 27196 has been marked as a duplicate of this bug. ***
Comment 3 Guinevere Larsen 2022-01-05 20:31:10 UTC
I believe I have a solution for this problem. The issue wasn't that the loop condition was at the top, but rather that there were lines at the bottom that weren't marked as is_stmt. 

This way, when calculating the maximum PC to execute to, it landed on that !is_stmt instruction, assumed the inferior was out of the block and GDB stopped at the next is_stmt instruction, the one inside the loop

My patch fixes this by searching the first instance of an is_stmt instruction, or the first instruction that happens on a different line, and sets that as the stopping PC

The patch is available here: https://sourceware.org/pipermail/gdb-patches/2022-January/184799.html
Comment 4 Sourceware Commits 2022-02-11 15:15:58 UTC
The master branch has been updated by Andrew Burgess <aburgess@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=9ab50efc463ff723b8e9102f1f68a6983d320517

commit 9ab50efc463ff723b8e9102f1f68a6983d320517
Author: Bruno Larsen <blarsen@redhat.com>
Date:   Wed Jan 26 10:08:13 2022 -0300

    gdb: fix until behavior with trailing !is_stmt lines
    
    When using the command "until", it is expected that GDB will exit a
    loop if the current instruction is the last one related to that loop.
    However, if there were trailing non-statement instructions, "until"
    would just behave as "next".  This was noticeable in clang-compiled
    code, but might happen with gcc-compiled as well.  PR gdb/17315 relates
    to this problem, as running gdb.base/watchpoint.exp with clang
    would fail for this reason.
    
    To better understand this issue, consider the following source code,
    with line numbers marked on the left:
    
      10:   for (i = 0; i < 10; ++i)
      11:     loop_body ();
      12:   other_stuff ();
    
    If we transform this to pseudo-assembler, and generate a line table,
    we could end up with something like this:
    
      Address | Pseudo-Assembler | Line | Is-Statement?
    
      0x100   | i = 0            | 10   | Yes
      0x104   | loop_body ()     | 11   | Yes
      0x108   | i = i + 1        | 10   | Yes
      0x10c   | if (i < 10):     | 10   | No
      0x110   |     goto 0x104   | 10   | No
      0x114   | other_stuff ()   | 12   | Yes
    
    Notice the two non-statement instructions at the end of the loop.
    
    The problem is that when we reach address 0x108 and use 'until',
    hoping to leave the loop, GDB sets up a stepping range that runs from
    the start of the function (0x100 in our example) to the end of the
    current line table entry, that is 0x10c in our example.  GDB then
    starts stepping forward.
    
    When 0x10c is reached GDB spots that we have left the stepping range,
    that the new location is not a statement, and that the new location is
    associated with the same source line number as the previous stepping
    range.  GDB then sets up a new stepping range that runs from 0x10c to
    0x114, and continues stepping forward.
    
    Within that stepping range the inferior hits the goto (at 0x110) and
    loops back to address 0x104.
    
    At 0x104 GDB spots that we have left the previous stepping range, that
    the new address is marked as a statement, and that the new address is
    for a different source line.  As a result, GDB stops and returns
    control to the user.  This is not what the user was expecting, they
    expected GDB to exit the loop.
    
    The fix proposed in this patch, is that, when the user issues the
    'until' command, and GDB sets up the initial stepping range, GDB will
    check subsequent SALs (symtab_and_lines) to see if they are
    non-statements associated with the same line number.  If they are then
    the end of the initial stepping range is extended to the end of the
    non-statement SALs.
    
    In our example above, the user is at 0x108 and uses 'until', GDB now
    sets up a stepping range from the start of the function 0x100 to
    0x114, the first address associated with a different line.
    
    Now as GDB steps around the loop it never leaves the initial stepping
    range.  It is only when GDB exits the loop that we leave the stepping
    range, and the stepping finishes at address 0x114.
    
    This patch also adds a test case that can be run with gcc to test that
    this functionality is not broken in the future.
    
    Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=17315
Comment 5 Guinevere Larsen 2022-05-17 19:26:33 UTC
Sorry for the delay in closing this. The above-mentioned commit should fix this issue