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;
--- /dev/null
+/* Copyright 2022 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int
+main ()
+{ /* TAG: main prologue */
+ asm ("main_label: .globl main_label");
+ asm ("loop_start: .globl loop_start");
+ int a, i;
+ i = 0; /* TAG: loop assignment */
+ while (1) /* TAG: loop line */
+ {
+ asm ("loop_condition: .globl loop_condition");
+ if (i >= 10) break; /* TAG: loop condition */
+ asm ("loop_code: .globl loop_code");
+ a = i; /* TAG: loop code */
+ asm ("loop_increment: .globl loop_increment");
+ i++; /* TAG: loop increment */
+ asm ("loop_jump: .globl loop_jump");
+ }
+ asm ("main_return: .globl main_return");
+ return 0; /* TAG: main return */
+}
--- /dev/null
+# Copyright 2022 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This test sets up debug information for a loop as we see in some cases
+# from clang-13. In this situation, instructions at both the start and end
+# of the loop are associated (in the line table), with the header line of
+# the loop (line 10 in the example below).
+#
+# At the end of the loop we see some instructions marked as not a statement,
+# but still associated with the same loop header line. For example,
+# consider the following C code:
+#
+# 10: for (i = 0; i < 10; ++i)
+# 11: loop_body ();
+# 12: other_stuff ();
+#
+# Transformed into the following pseudo-assembler, with associated line table:
+#
+# 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 here 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 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 not to
+# stop until they were outside of the loop.
+#
+# The fix is that, when the user issues the 'until' command, and GDB sets up
+# the initial stepping range, GDB will check subsequent SALs 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 pushed out 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 test checks this behaviour using the DWARF assembler.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+if {![dwarf2_support]} {
+ unsupported "dwarf2 support required for this test"
+ return 0
+}
+
+if [get_compiler_info] {
+ return -1
+}
+
+# The DWARF assembler requires the gcc compiler.
+if {!$gcc_compiled} {
+ unsupported "gcc is required for this test"
+ return 0
+}
+
+standard_testfile .c .S
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+ return -1
+}
+
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+ global srcdir subdir srcfile
+ declare_labels integer_label L
+ set int_size [get_sizeof "int" 4]
+
+ # Find start address and length for our functions.
+ lassign [function_range main [list ${srcdir}/${subdir}/$srcfile]] \
+ main_start main_len
+ set main_end "$main_start + $main_len"
+
+ cu {} {
+ compile_unit {
+ {language @DW_LANG_C}
+ {name until-trailing-isns.c}
+ {stmt_list $L DW_FORM_sec_offset}
+ {low_pc 0 addr}
+ } {
+ subprogram {
+ {external 1 flag}
+ {name main}
+ {low_pc $main_start addr}
+ {high_pc $main_len DW_FORM_data4}
+ }
+ }
+ }
+
+ lines {version 2 default_is_stmt 1} L {
+ include_dir "${srcdir}/${subdir}"
+ file_name "$srcfile" 1
+
+ # Generate a line table program. This mimicks clang-13's behavior
+ # of adding some !is_stmt at the end of a loop line, making until
+ # not work properly.
+ program {
+ {DW_LNE_set_address $main_start}
+ {line [gdb_get_line_number "TAG: main prologue"]}
+ {DW_LNS_copy}
+ {DW_LNE_set_address loop_start}
+ {line [gdb_get_line_number "TAG: loop line"]}
+ {DW_LNS_copy}
+ {DW_LNE_set_address loop_condition}
+ {line [gdb_get_line_number "TAG: loop line"]}
+ {DW_LNS_negate_stmt}
+ {DW_LNS_copy}
+ {DW_LNE_set_address loop_code}
+ {line [gdb_get_line_number "TAG: loop code"]}
+ {DW_LNS_negate_stmt}
+ {DW_LNS_copy}
+ {DW_LNE_set_address loop_increment}
+ {line [gdb_get_line_number "TAG: loop line"]}
+ {DW_LNS_copy}
+ {DW_LNE_set_address loop_jump}
+ {line [gdb_get_line_number "TAG: loop line"]}
+ {DW_LNS_negate_stmt}
+ {DW_LNS_copy}
+ {DW_LNE_set_address main_return}
+ {line [gdb_get_line_number "TAG: main return"]}
+ {DW_LNS_negate_stmt}
+ {DW_LNS_copy}
+ {DW_LNE_set_address $main_end}
+ {line [expr [gdb_get_line_number "TAG: main return"] + 1]}
+ {DW_LNS_copy}
+ {DW_LNE_end_sequence}
+ }
+ }
+
+}
+
+if { [prepare_for_testing "failed to prepare" ${testfile} \
+ [list $srcfile $asm_file] {nodebug} ] } {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+gdb_test "next" ".* TAG: loop code .*" "inside the loop"
+gdb_test "next" ".* TAG: loop line .*" "ending of loop"
+gdb_test "until" ".* TAG: main return .*" "left loop"