diff options
author | Bruno Larsen <blarsen@redhat.com> | 2022-01-26 10:08:13 -0300 |
---|---|---|
committer | Andrew Burgess <aburgess@redhat.com> | 2022-02-11 15:15:48 +0000 |
commit | 9ab50efc463ff723b8e9102f1f68a6983d320517 (patch) | |
tree | 1daaefad5d325dc20ea22ec4be58df257041930a /gdb/infcmd.c | |
parent | b4b0dcfd03b6b40bec2e9fb21723f30d43e52f41 (diff) | |
download | gdb-9ab50efc463ff723b8e9102f1f68a6983d320517.zip gdb-9ab50efc463ff723b8e9102f1f68a6983d320517.tar.gz gdb-9ab50efc463ff723b8e9102f1f68a6983d320517.tar.bz2 |
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
Diffstat (limited to 'gdb/infcmd.c')
-rw-r--r-- | gdb/infcmd.c | 39 |
1 files changed, 39 insertions, 0 deletions
diff --git a/gdb/infcmd.c b/gdb/infcmd.c index 48dda9b..0e1cfcb 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -1346,6 +1346,45 @@ until_next_command (int from_tty) tp->control.step_range_start = BLOCK_ENTRY_PC (SYMBOL_BLOCK_VALUE (func)); tp->control.step_range_end = sal.end; + + /* By setting the step_range_end based on the current pc, we are + assuming that the last line table entry for any given source line + will have is_stmt set to true. This is not necessarily the case, + there may be additional entries for the same source line with + is_stmt set false. Consider the following code: + + for (int i = 0; i < 10; i++) + loop_body (); + + Clang-13, will generate multiple line table entries at the end of + the loop all associated with the 'for' line. The first of these + entries is marked is_stmt true, but the other entries are is_stmt + false. + + If we only use the values in SAL, then our stepping range may not + extend to the end of the loop. The until command will reach the + end of the range, find a non is_stmt instruction, and step to the + next is_stmt instruction. This stopping point, however, will be + inside the loop, which is not what we wanted. + + Instead, we now check any subsequent line table entries to see if + they are for the same line. If they are, and they are marked + is_stmt false, then we extend the end of our stepping range. + + When we finish this process the end of the stepping range will + point either to a line with a different line number, or, will + point at an address for the same line number that is marked as a + statement. */ + + struct symtab_and_line final_sal + = find_pc_line (tp->control.step_range_end, 0); + + while (final_sal.line == sal.line && final_sal.symtab == sal.symtab + && !final_sal.is_stmt) + { + tp->control.step_range_end = final_sal.end; + final_sal = find_pc_line (final_sal.end, 0); + } } tp->control.may_range_step = 1; |