return;
}
+ /* Handle the case when subroutines have multiple ranges. When we step
+ from one part to the next part of the same subroutine, all subroutine
+ levels are skipped again which begin here. Compensate for this by
+ removing all skipped subroutines, which were already executing from
+ the user's perspective. */
+
+ if (get_stack_frame_id (frame)
+ == ecs->event_thread->control.step_stack_frame_id
+ && inline_skipped_frames (ecs->event_thread) > 0
+ && ecs->event_thread->control.step_frame_id.artificial_depth > 0
+ && ecs->event_thread->control.step_frame_id.code_addr_p)
+ {
+ int depth = 0;
+ const struct block *prev
+ = block_for_pc (ecs->event_thread->control.step_frame_id.code_addr);
+ const struct block *curr = block_for_pc (ecs->event_thread->stop_pc ());
+ while (curr != nullptr && !curr->contains (prev))
+ {
+ if (curr->inlined_p ())
+ depth++;
+ else if (curr->function () != nullptr)
+ break;
+ curr = curr->superblock ();
+ }
+ while (inline_skipped_frames (ecs->event_thread) > depth)
+ step_into_inline_frame (ecs->event_thread);
+ }
+
/* Look for "calls" to inlined functions, part one. If the inline
frame machinery detected some skipped call sites, we have entered
a new inline function. */
--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2024 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/>. */
+
+/* Used to insert labels within function foo. */
+#define LABEL(N) asm ("foo_label_" #N ": .globl foo_label_" #N)
+
+volatile int global_var = 0;
+
+/* The contents of this '#if 0' block exist so the generated debug can
+ point to these as the source lines. */
+#if 0
+
+void
+bar (void) /* bar decl line */
+{
+ /* bar line 1 */
+ /* bar line 2 */
+}
+
+void
+baz (void) /* baz decl line */
+{
+ /* baz line 1 */
+ /* baz line 2 */
+}
+
+void
+foo (void) /* foo decl line */
+{
+ /* foo line 1 */
+ /* foo line 2 */
+ /* foo line 3 */
+ /* foo line 4 */
+}
+
+#endif
+
+extern void *foo_label_6 (void);
+
+void
+foo (void)
+{
+ /* This label is used to find the start of 'foo' when generating the
+ debug information. */
+ asm ("foo_label: .globl foo_label");
+ ++global_var;
+
+ LABEL (1);
+ ++global_var;
+
+ LABEL (2);
+ ++global_var;
+
+ LABEL (3);
+ ++global_var;
+
+ /* This goto will always trigger, but we make it conditional so that the
+ compiler doesn't optimise out the code between the goto and the
+ destination.
+
+ Also 'goto *ADDR' is a GCC extension, but it is critical that the
+ destination address be a global label so that we can generate DWARF
+ that has ranges that start exactly at the destination address. */
+ if (global_var > 0)
+ goto *(&foo_label_6);
+
+ LABEL (4);
+ ++global_var;
+
+ LABEL (5);
+ ++global_var;
+
+ LABEL (6);
+ ++global_var;
+
+ LABEL (7);
+ ++global_var;
+
+ LABEL (8);
+ ++global_var;
+
+ LABEL (9);
+ ++global_var;
+}
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+ foo ();
+}
--- /dev/null
+# Copyright 2024 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 is a follow on from the test:
+# gdb.dwarf2/dw2-step-between-inline-func-blocks.exp
+# It is worth reading that test before looking at this one.
+#
+# This test creates a function 'foo' that contains two inline
+# functions 'bar' and 'baz'. The function 'foo' is split into two
+# ranges. 'bar' is inline in the first range and 'baz' is inline in
+# the second range, but critically, 'baz' starts at the very start of
+# the second range. The functions are laid out like this:
+#
+# (A) (B)
+# bar: |------------|
+# baz: |---|
+# foo: |------------------| |--------|
+#
+# When the inferior reaches address (A) we jump directly to point (B).
+# At that point we expect GDB to tell the user that the inferior is in
+# 'foo'. GDB should not automatically enter 'baz'.
+#
+# This tests exists because in
+# dw2-step-between-inline-func-blocks.exp, the second range is another
+# part of 'bar' and the jump from (A) to (B) is from one part of 'bar'
+# to the next, in this case GDB does automatically reenter 'bar'.
+# This test checks that GDB isn't too keen to reenter inline
+# functions.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+# The source program use 'goto *ADDR' which is a GCC extension.
+require is_c_compiler_gcc
+
+standard_testfile
+
+# This compiles the source file and starts and stops GDB, so run it
+# before calling prepare_for_testing otherwise GDB will have exited.
+get_func_info foo
+
+# Make some DWARF for the test.
+set asm_file [standard_output_file "$::testfile-dw.S"]
+Dwarf::assemble $asm_file {
+ global srcfile
+
+ # Create local varibles like BAR_SRC_* containing the line number
+ # for the souce lines of 'foo' and 'bar' and 'baz'. These will be
+ # referenced in the generated DWARF.
+ for { set i 1 } { $i <= 2 } { incr i } {
+ set bar_src_$i [gdb_get_line_number "bar line $i"]
+ set baz_src_$i [gdb_get_line_number "baz line $i"]
+ }
+ for { set i 1 } { $i <= 4 } { incr i } {
+ set foo_src_$i [gdb_get_line_number "foo line $i"]
+ }
+
+ # More line numbers needed for the generated DWARF.
+ set foo_decl_line [gdb_get_line_number "foo decl line"]
+ set bar_decl_line [gdb_get_line_number "bar decl line"]
+ set baz_decl_line [gdb_get_line_number "baz decl line"]
+
+ # Labels used to link parts of the DWARF together.
+ declare_labels lines_table bar_label baz_label
+ declare_labels ranges_label_bar ranges_label_baz ranges_label_foo
+
+ cu { version 4 } {
+ compile_unit {
+ {producer "gcc"}
+ {language @DW_LANG_C}
+ {name ${srcfile}}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ {low_pc 0 addr}
+ } {
+ bar_label: subprogram {
+ {external 1 flag}
+ {name bar}
+ {decl_file 1 data1}
+ {decl_line $bar_decl_line data1}
+ {decl_column 1 data1}
+ {inline 3 data1}
+ }
+ baz_label: subprogram {
+ {external 1 flag}
+ {name baz}
+ {decl_file 1 data1}
+ {decl_line $baz_decl_line data1}
+ {decl_column 1 data1}
+ {inline 3 data1}
+ }
+ subprogram {
+ {name foo}
+ {decl_file 1 data1}
+ {decl_line $foo_decl_line data1}
+ {decl_column 1 data1}
+ {ranges ${ranges_label_foo} DW_FORM_sec_offset}
+ {external 1 flag}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$bar_label}
+ {call_file 1 data1}
+ {call_line $foo_src_2 data1}
+ {ranges ${ranges_label_bar} DW_FORM_sec_offset}
+ }
+ inlined_subroutine {
+ {abstract_origin %$baz_label}
+ {call_file 1 data1}
+ {call_line $foo_src_3 data1}
+ {ranges ${ranges_label_baz} DW_FORM_sec_offset}
+ }
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$srcfile" 1
+ program {
+ DW_LNE_set_address "foo_label"
+ line $foo_src_1
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_1"
+ line $foo_src_2
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_2"
+ line $bar_src_1
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_3"
+ line $bar_src_2
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_4"
+ DW_LNE_end_sequence
+
+ DW_LNE_set_address "foo_label_6"
+ line $baz_src_1
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_7"
+ line $baz_src_2
+ DW_LNS_copy
+ DW_LNS_negate_stmt
+ DW_LNE_set_address "foo_label_7"
+ line $foo_src_3
+ DW_LNS_copy
+ DW_LNS_negate_stmt
+ DW_LNE_set_address "foo_label_8"
+ line $foo_src_4
+ DW_LNS_copy
+ DW_LNE_set_address $::foo_end
+ DW_LNE_end_sequence
+ }
+ }
+
+ ranges { } {
+ ranges_label_bar: sequence {
+ range foo_label_2 foo_label_4
+ }
+ ranges_label_baz: sequence {
+ range foo_label_6 foo_label_7
+ }
+ ranges_label_foo: sequence {
+ range foo_label_1 foo_label_4
+ range foo_label_6 foo_label_9
+ }
+ }
+}
+
+if {[prepare_for_testing "failed to prepare" "${::testfile}" \
+ [list $srcfile $asm_file] {nodebug}]} {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+gdb_breakpoint bar
+gdb_continue_to_breakpoint "continue to bar line 1" \
+ ".*bar line 1\[^\r\n\]+"
+
+gdb_test "step" ".*bar line 2\[^\r\n\]+" \
+ "step to bar line 2"
+
+# This is the interesting one. This step will take us over the goto
+# and place the inferior at the start of the second range of 'foo' and
+# at the start of 'baz'.
+#
+# As we started the step in 'bar', GDB should not reenter 'baz'.
+gdb_test "step" ".*foo line 3\[^\r\n\]+" \
+ "step to foo line 3"
+
+# The next step should allow the inferior to enter 'baz'.
+gdb_test "step" ".*baz line 1\[^\r\n\]+" \
+ "step to baz line 1"
+
+# The remaining steps take the inferior through 'baz' and back into
+# 'foo'.
+gdb_test "step" ".*baz line 2\[^\r\n\]+" \
+ "step to baz line 2"
+
+gdb_test "step" ".*foo line 4\[^\r\n\]+" \
+ "step out of bar to foo line 4"
--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2024 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/>. */
+
+/* Used to insert labels within function foo. */
+#define LABEL(N) asm ("foo_label_" #N ": .globl foo_label_" #N)
+
+volatile int global_var = 0;
+
+/* The contents of this '#if 0' block exist so the generated debug can
+ point to these as the source lines. */
+#if 0
+
+void
+bar (void) /* bar decl line */
+{
+ /* bar line 1 */
+ /* bar line 2 */
+ /* bar line 3 */
+ /* bar line 4 */
+}
+
+void
+foo (void) /* foo decl line */
+{
+ /* foo line 1 */
+ /* foo line 2 */
+ /* foo line 3 */
+ /* foo line 4 */
+}
+
+#endif
+
+extern void *foo_label_6 (void);
+
+void
+foo (void)
+{
+ /* This label is used to find the start of 'foo' when generating the
+ debug information. */
+ asm ("foo_label: .globl foo_label");
+ ++global_var;
+
+ LABEL (1);
+ ++global_var;
+
+ LABEL (2);
+ ++global_var;
+
+ LABEL (3);
+ ++global_var;
+
+ /* This goto will always trigger, but we make it conditional so that the
+ compiler doesn't optimise out the code between the goto and the
+ destination.
+
+ Also 'goto *ADDR' is a GCC extension, but it is critical that the
+ destination address be a global label so that we can generate DWARF
+ that has ranges that start exactly at the destination address. */
+ if (global_var > 0)
+ goto *(&foo_label_6);
+
+ LABEL (4);
+ ++global_var;
+
+ LABEL (5);
+ ++global_var;
+
+ LABEL (6);
+ ++global_var;
+
+ LABEL (7);
+ ++global_var;
+
+ LABEL (8);
+ ++global_var;
+
+ LABEL (9);
+ ++global_var;
+}
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+ foo ();
+}
--- /dev/null
+# Copyright 2024 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 creates a function 'foo' that contains an inline function
+# 'bar'. Both 'foo' and 'bar' are split into two regions. The layout
+# in memory looks something like this:
+#
+# <part-a> <gap> <part-b>
+#
+# bar: |------------| |---|
+# foo: |------------------| |--------|
+#
+# We see things like this in "real" code where the parts after the gap
+# (part-b) are cold paths within the function that have been moved
+# aside.
+#
+# However, in this test program we use 'goto' to jump directly from
+# the first part of the function (part-a) to the second part (part-b).
+#
+# At the time we hit the goto we are inside 'bar'. After the goto we
+# expect to still be in bar. At one point GDB would get this wrong
+# and after the jump we would revert back to 'foo'.
+#
+# This bug will only trigger if both 'foo' and 'bar' are split, and
+# both 'foo' and 'bar' must start at the same address for part-b.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+# The source program use 'goto *ADDR' which is a GCC extension.
+require is_c_compiler_gcc
+
+standard_testfile
+
+# This compiles the source file and starts and stops GDB, so run it
+# before calling prepare_for_testing otherwise GDB will have exited.
+get_func_info foo
+
+# Make some DWARF for the test.
+set asm_file [standard_output_file "$::testfile-dw.S"]
+Dwarf::assemble $asm_file {
+ global srcfile
+
+ # Create local varibles BAR_SRC_* containing the line number for
+ # the four souce lines of 'foo' and 'bar'. These will be
+ # referenced in the generated DWARF.
+ for { set i 1 } { $i <= 4 } { incr i } {
+ set bar_src_$i [gdb_get_line_number "bar line $i"]
+ set foo_src_$i [gdb_get_line_number "foo line $i"]
+ }
+
+ # More line numbers needed for the generated DWARF.
+ set foo_decl_line [gdb_get_line_number "foo decl line"]
+ set bar_decl_line [gdb_get_line_number "bar decl line"]
+
+ # Labels used to link parts of the DWARF together.
+ declare_labels lines_table bar_label ranges_label_bar ranges_label_foo
+
+ cu { version 4 } {
+ compile_unit {
+ {producer "gcc"}
+ {language @DW_LANG_C}
+ {name ${srcfile}}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ {low_pc 0 addr}
+ } {
+ bar_label: subprogram {
+ {external 1 flag}
+ {name bar}
+ {decl_file 1 data1}
+ {decl_line $bar_decl_line data1}
+ {decl_column 1 data1}
+ {inline 3 data1}
+ }
+ subprogram {
+ {name foo}
+ {decl_file 1 data1}
+ {decl_line $foo_decl_line data1}
+ {decl_column 1 data1}
+ {ranges ${ranges_label_foo} DW_FORM_sec_offset}
+ {external 1 flag}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$bar_label}
+ {call_file 1 data1}
+ {call_line $foo_src_3 data1}
+ {ranges ${ranges_label_bar} DW_FORM_sec_offset}
+ }
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$srcfile" 1
+ program {
+ DW_LNE_set_address "foo_label"
+ line $foo_src_1
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_1"
+ line $foo_src_2
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_2"
+ line $foo_src_3
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_2"
+ line $bar_src_1
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_3"
+ line $bar_src_2
+ DW_LNS_copy
+
+ DW_LNE_set_address "foo_label_4"
+ DW_LNE_end_sequence
+
+ DW_LNE_set_address "foo_label_6"
+ line $bar_src_3
+ DW_LNS_copy
+ DW_LNE_set_address "foo_label_7"
+ line $bar_src_4
+ DW_LNS_copy
+
+ DW_LNS_negate_stmt
+ DW_LNE_set_address "foo_label_7"
+ line $foo_src_3
+ DW_LNS_copy
+
+ DW_LNS_negate_stmt
+ DW_LNE_set_address "foo_label_8"
+ line $foo_src_4
+ DW_LNS_copy
+
+ DW_LNE_set_address $::foo_end
+ DW_LNE_end_sequence
+ }
+ }
+
+ ranges { } {
+ ranges_label_bar: sequence {
+ range foo_label_2 foo_label_4
+ range foo_label_6 foo_label_8
+ }
+ ranges_label_foo: sequence {
+ range foo_label_1 foo_label_4
+ range foo_label_6 foo_label_9
+ }
+ }
+}
+
+if {[prepare_for_testing "failed to prepare" "${::testfile}" \
+ [list $srcfile $asm_file] {nodebug}]} {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+gdb_breakpoint bar
+gdb_continue_to_breakpoint "continue to bar line 1" \
+ ".*bar line 1\[^\r\n\]+"
+
+gdb_test "step" ".*bar line 2\[^\r\n\]+" \
+ "step to bar line 2"
+
+# This is the interesting one. This step will take us over the goto
+# and into the second range of both 'foo' and 'bar'. As we started
+# the step in 'bar' GDB should reselect 'bar' after the step.
+#
+# If this goes wrong the GDB will claim we are at foo_line_3, which is
+# the DW_AT_call_line for 'bar'.
+gdb_test "step" ".*bar line 3\[^\r\n\]+" \
+ "step to bar line 3"
+
+# These following steps should be straight forward, but lets just
+# check we can step out of 'bar' and back to 'foo', there shouldn't be
+# anything tricky going on here though.
+gdb_test "step" ".*bar line 4\[^\r\n\]+" \
+ "step to bar line 4"
+
+gdb_test "step" ".*foo line 4\[^\r\n\]+" \
+ "step out of bar to foo line 4"