[v2,2/3] gdb: Don't stop at non-statement line after stepping into inline function

Message ID 20240201154325.603608-1-tlloyddavies@undo.io
State New
Headers
Series None |

Commit Message

Toby Lloyd Davies Feb. 1, 2024, 3:43 p.m. UTC
  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
  

Patch

diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 5e5f75021f2..22ab4dbebbc 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -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);
diff --git a/gdb/infrun.c b/gdb/infrun.c
index a782c115cad..927e464e479 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -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
 	{
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-inline-stepping-3.c b/gdb/testsuite/gdb.dwarf2/dw2-inline-stepping-3.c
new file mode 100644
index 00000000000..3b6bc84fb05
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-inline-stepping-3.c
@@ -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 */
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-inline-stepping-3.exp b/gdb/testsuite/gdb.dwarf2/dw2-inline-stepping-3.exp
new file mode 100644
index 00000000000..638c2635d5f
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-inline-stepping-3.exp
@@ -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"