[v2,2/3] gdb: Don't stop at non-statement line after stepping into inline function
Commit Message
Generally when stepping we only want to stop at lines that mark the
beginning of a statement i.e. have is_stmt set. However, when we stepped
into an inline function we would always stop regardless of whether
is_stmt was set on first line of the inline function. Fix this in
infrun.c:process_event_stop_test by not immediately stopping when
entering an inline function. Then code later in the function will decide
whether to stop based on whether is_stmt is set on the line.
Additionally, check if is_stmt is set on the line after stepping from an
inline callsite in infcmd.c:prepare_one_step. This fixes the bug when
the step starts directly at the inline function callsite.
---
gdb/infcmd.c | 3 +-
gdb/infrun.c | 32 ++--
.../gdb.dwarf2/dw2-inline-stepping-3.c | 50 ++++++
.../gdb.dwarf2/dw2-inline-stepping-3.exp | 149 ++++++++++++++++++
4 files changed, 222 insertions(+), 12 deletions(-)
create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-inline-stepping-3.c
create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-inline-stepping-3.exp
@@ -964,7 +964,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
fn = sym->print_name ();
if (sal.line == 0
- || !function_name_is_marked_for_skip (fn, sal))
+ || ((inline_skipped_frames (tp) || sal.is_stmt)
+ && !function_name_is_marked_for_skip (fn, sal)))
{
sm->count--;
return prepare_one_step (tp, sm);
@@ -8104,10 +8104,10 @@ process_event_stop_test (struct execution_control_state *ecs)
if (ecs->event_thread->control.step_over_calls == STEP_OVER_ALL
|| inline_frame_is_marked_for_skip (false, ecs->event_thread))
- keep_going (ecs);
- else
- end_stepping_range (ecs);
- return;
+ {
+ keep_going (ecs);
+ return;
+ }
}
/* Look for "calls" to inlined functions, part two. If the inline
@@ -8122,25 +8122,35 @@ process_event_stop_test (struct execution_control_state *ecs)
if (ecs->event_thread->control.step_over_calls != STEP_OVER_ALL)
{
- /* For "step", we're going to stop. But if the call site
- for this inlined function is on the same source line as
- we were previously stepping, go down into the function
- first. Otherwise stop at the call site. */
+ /* For "step", if the call site for this inlined function is on the
+ same source line as we were previously stepping, go down into the
+ function first before deciding whether to stop. Otherwise stop at
+ the call site. */
if (*curr_frame_id == original_frame_id
&& call_sal.line == ecs->event_thread->current_line
&& call_sal.symtab == ecs->event_thread->current_symtab)
{
step_into_inline_frame (ecs->event_thread);
+ frame = get_current_frame ();
if (inline_frame_is_marked_for_skip (false, ecs->event_thread))
{
keep_going (ecs);
return;
}
+ else if (inline_skipped_frames (ecs->event_thread))
+ {
+ end_stepping_range (ecs);
+ return;
+ }
+ /* We are no longer at an inline function callsite.
+ We use the checks further down to determine whether to stop. */
+ }
+ else
+ {
+ end_stepping_range (ecs);
+ return;
}
-
- end_stepping_range (ecs);
- return;
}
else
{
new file mode 100644
@@ -0,0 +1,50 @@
+/* Copyright 2019-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 relies on foo and bar being inlined into main. */
+
+volatile int global_var;
+
+static inline int __attribute__ ((always_inline))
+foo ()
+{
+ asm ("foo_label: .globl foo_label");
+ global_var++; /* foo inc global_var*/
+ asm ("foo_label2: .globl foo_label2");
+ return global_var; /* foo return global_var */
+} /* foo end */
+
+static inline int __attribute__ ((always_inline))
+bar ()
+{
+ asm ("bar_label: .globl bar_label");
+ global_var++; /* bar inc global_var*/
+ asm ("bar_label2: .globl bar_label2");
+ return global_var; /* bar return global_var */
+} /* bar end */
+
+int
+main ()
+{ /* main prologue */
+ int ans;
+ asm ("main_label: .globl main_label");
+ global_var = 0; /* main set global_var */
+ asm ("main_label2: .globl main_label2");
+ ans = foo (); /* main call foo */
+ asm ("main_label3: .globl main_label3");
+ asm ("nop"); ans = bar (); /* main call bar */
+ asm ("main_label4: .globl main_label4");
+ return ans;
+} /* main end */
new file mode 100644
@@ -0,0 +1,149 @@
+# Copyright 2019-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 checks that when stepping into an inline function we step past the
+# first line if it does not have is_stmt set. We check two cases. One where the
+# inline callsite line contains no instructions (i.e. it begins at the same
+# address that the inline function begins). This exercices the codepath in
+# infcmd.c:prepare_one_step. The other is when the inline callsite line
+# contains one instruction. This exercises the codepath in
+# infrun.c:prepare_one_step.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+# The .c files use __attribute__.
+require is_c_compiler_gcc
+
+standard_testfile .c .S
+
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+ global srcdir subdir srcfile srcfile2
+ declare_labels ranges_label lines_label foo_prog bar_prog
+
+ lassign [function_range main [list ${srcdir}/${subdir}/$srcfile]] \
+ main_start main_len
+ set main_end "$main_start + $main_len"
+
+ set foo_call_line [gdb_get_line_number "main call foo"]
+ set bar_call_line [gdb_get_line_number "main call bar"]
+
+ cu {} {
+ compile_unit {
+ {language @DW_LANG_C}
+ {name dw2-inline-stepping-2.c}
+ {low_pc 0 addr}
+ {stmt_list ${lines_label} DW_FORM_sec_offset}
+ {ranges ${ranges_label} DW_FORM_sec_offset}
+ } {
+ bar_prog: subprogram {
+ {name bar}
+ {inline 3 data1}
+ }
+ foo_prog: subprogram {
+ {name foo}
+ {inline 3 data1}
+ }
+ subprogram {
+ {external 1 flag}
+ {name main}
+ {low_pc $main_start addr}
+ {high_pc "$main_start + $main_len" addr}
+ } {
+ inlined_subroutine {
+ {abstract_origin %$foo_prog}
+ {low_pc main_label2 addr}
+ {high_pc main_label3 addr}
+ {call_file 1 data1}
+ {call_line $foo_call_line data1}
+ }
+ inlined_subroutine {
+ {abstract_origin %$bar_prog}
+ {low_pc bar_label addr}
+ {high_pc main_label4 addr}
+ {call_file 1 data1}
+ {call_line $bar_call_line data1}
+ }
+ }
+ }
+ }
+
+ lines {version 2} lines_label {
+ include_dir "${srcdir}/${subdir}"
+ file_name "$srcfile" 1
+
+ program {
+
+ DW_LNE_set_address main_label
+ line [gdb_get_line_number "main set global_var"]
+ DW_LNS_copy
+
+ DW_LNE_set_address main_label2
+ DW_LNS_negate_stmt
+ line [gdb_get_line_number "foo inc global_var"]
+ DW_LNS_copy
+ DW_LNS_negate_stmt
+
+ DW_LNE_set_address foo_label2
+ line [gdb_get_line_number "foo return global_var"]
+ DW_LNS_copy
+
+ DW_LNE_set_address main_label3
+ line [gdb_get_line_number "main call bar"]
+ DW_LNS_copy
+
+ DW_LNE_set_address bar_label
+ DW_LNS_negate_stmt
+ line [gdb_get_line_number "bar inc global_var"]
+ DW_LNS_copy
+ DW_LNS_negate_stmt
+
+ DW_LNE_set_address bar_label2
+ line [gdb_get_line_number "bar return global_var"]
+ DW_LNS_copy
+
+ DW_LNE_set_address main_label4
+ line [gdb_get_line_number "return ans"]
+ DW_LNS_copy
+
+ DW_LNE_set_address $main_end
+ DW_LNE_end_sequence
+ }
+ }
+
+ ranges {is_64 [is_64_target]} {
+ ranges_label: sequence {
+ range ${main_start} ${main_end}
+ }
+ }
+}
+
+if { [prepare_for_testing "failed to prepare" ${testfile} \
+ [list $srcfile $asm_file] {nodebug}] } {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+gdb_test "next" ".* main call foo.*" "step to foo callsite"
+gdb_test "step" ".* foo return global_var.*" "step to foo inc global_var"
+
+gdb_test "next" ".*main call bar.*" "step to bar callsite"
+gdb_test "step" ".* bar return global_var.*" "step to bar inc global_var"