public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH v4 0/4] btrace: infrun fixes
@ 2024-03-12 11:34 Markus Metzger
  2024-03-12 11:34 ` [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history Markus Metzger
                   ` (3 more replies)
  0 siblings, 4 replies; 16+ messages in thread
From: Markus Metzger @ 2024-03-12 11:34 UTC (permalink / raw)
  To: gdb-patches

Reviving an old patch series that was first submitted in Jan'21.

It fixes issues with breakpoints at the end of the execution history.

Markus Metzger (4):
  gdb, infrun, btrace: fix reverse/replay stepping at end of execution
    history
  gdb, infrun, record: fix hang when step-over fails with no-history
  gdb, infrun, record: move no-history notification into normal_stop
  gdb, infrun: fix multi-threaded reverse stepping

 gdb/gdbthread.h                               | 13 +++
 gdb/infrun.c                                  | 88 +++++++++++++-----
 gdb/infrun.h                                  |  7 --
 gdb/record-btrace.c                           | 19 ++--
 gdb/testsuite/gdb.btrace/cont-hang.exp        | 43 +++++++++
 .../gdb.btrace/implicit-stop-replaying.exp    | 90 +++++++++++++++++++
 .../gdb.btrace/multi-thread-break-hang.exp    | 84 +++++++++++++++++
 gdb/testsuite/gdb.btrace/step-hang.exp        | 42 +++++++++
 gdb/testsuite/gdb.btrace/stepn.exp            | 50 +++++++++++
 9 files changed, 400 insertions(+), 36 deletions(-)
 create mode 100644 gdb/testsuite/gdb.btrace/cont-hang.exp
 create mode 100644 gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp
 create mode 100644 gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp
 create mode 100644 gdb/testsuite/gdb.btrace/step-hang.exp
 create mode 100644 gdb/testsuite/gdb.btrace/stepn.exp

-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history
  2024-03-12 11:34 [PATCH v4 0/4] btrace: infrun fixes Markus Metzger
@ 2024-03-12 11:34 ` Markus Metzger
  2024-03-12 11:56   ` Hannes Domani
  2024-04-04 18:03   ` Guinevere Larsen
  2024-03-12 11:34 ` [PATCH v4 2/4] gdb, infrun, record: fix hang when step-over fails with no-history Markus Metzger
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 16+ messages in thread
From: Markus Metzger @ 2024-03-12 11:34 UTC (permalink / raw)
  To: gdb-patches

When trying to step over a breakpoint at the end of the trace, the
step-over will fail with no-history.  This does not clear step_over_info
so a subsequent resume will cause GDB to not resume the thread and expect
a SIGTRAP to complete the step-over.  This will never come causing GDB to
hang in the wait-for-event poll.

That step-over failed after actually completing the step.  This is wrong.
The step-over itself should have failed and the step should not have
completed.  Fix it by moving the end of execution history check to before
we are stepping.

This exposes another issue, however.  When completing a step-over at the
end of the execution history, we implicitly stop replaying that thread.  A
continue command would resume after the step-over and, since we're no
longer replaying, would continue recording.

Fix that by recording the replay state in the thread's control state and
failing with no-history in keep_going if we're switching from replay to
recording.
---
 gdb/gdbthread.h                        |  3 ++
 gdb/infrun.c                           | 25 +++++++++++++
 gdb/record-btrace.c                    | 19 +++++-----
 gdb/testsuite/gdb.btrace/cont-hang.exp | 43 ++++++++++++++++++++++
 gdb/testsuite/gdb.btrace/step-hang.exp | 42 ++++++++++++++++++++++
 gdb/testsuite/gdb.btrace/stepn.exp     | 50 ++++++++++++++++++++++++++
 6 files changed, 173 insertions(+), 9 deletions(-)
 create mode 100644 gdb/testsuite/gdb.btrace/cont-hang.exp
 create mode 100644 gdb/testsuite/gdb.btrace/step-hang.exp
 create mode 100644 gdb/testsuite/gdb.btrace/stepn.exp

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 11c553a99ca..4931a0cc2f1 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -173,6 +173,9 @@ struct thread_control_state
      command.  This is used to decide whether "set scheduler-locking
      step" behaves like "on" or "off".  */
   int stepping_command = 0;
+
+  /* Whether the thread was replaying when the command was issued.  */
+  bool is_replaying = false;
 };
 
 /* Inferior thread specific part of `struct infcall_suspend_state'.  */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index bbb98f6dcdb..f38d96b64df 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -3089,6 +3089,8 @@ clear_proceed_status_thread (struct thread_info *tp)
 
   /* Discard any remaining commands or status from previous stop.  */
   bpstat_clear (&tp->control.stop_bpstat);
+
+  tp->control.is_replaying = target_record_is_replaying (tp->ptid);
 }
 
 /* Notify the current interpreter and observers that the target is about to
@@ -8972,6 +8974,29 @@ keep_going_pass_signal (struct execution_control_state *ecs)
   gdb_assert (ecs->event_thread->ptid == inferior_ptid);
   gdb_assert (!ecs->event_thread->resumed ());
 
+  /* When a thread reaches the end of its execution history, it automatically
+     stops replaying.  This is so the user doesn't need to explicitly stop it
+     with a separate command.
+
+     We do not want a single command (e.g. continue) to transition from
+     replaying to recording, though, e.g. when starting from a breakpoint we
+     needed to step over at the end of the trace.  When we reach the end of the
+     execution history during stepping, stop with no-history.
+
+     The other direction is fine.  When we're at the end of the execution
+     history, we may reverse-continue to start replaying.  */
+  if (ecs->event_thread->control.is_replaying
+      && !target_record_is_replaying (ecs->event_thread->ptid))
+    {
+      interps_notify_no_history ();
+      ecs->ws.set_no_history ();
+      set_last_target_status (ecs->target, ecs->ptid, ecs->ws);
+      stop_print_frame = true;
+      stop_waiting (ecs);
+      normal_stop ();
+      return;
+    }
+
   /* Save the pc before execution, to compare with pc after stop.  */
   ecs->event_thread->prev_pc
     = regcache_read_pc_protected (get_thread_regcache (ecs->event_thread));
diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
index 6350400c318..ae273fda507 100644
--- a/gdb/record-btrace.c
+++ b/gdb/record-btrace.c
@@ -2340,6 +2340,16 @@ record_btrace_single_step_forward (struct thread_info *tp)
   if (replay == NULL)
     return btrace_step_no_history ();
 
+  /* The execution trace contains (and ends with) the current instruction.
+     This instruction has not been executed, yet, so the trace really ends
+     one instruction earlier.
+
+     We'd fail later on in btrace_insn_next () but we must not trigger
+     breakpoints as we're not really able to step.  */
+  btrace_insn_end (&end, btinfo);
+  if (btrace_insn_cmp (replay, &end) == 0)
+    return btrace_step_no_history ();
+
   /* Check if we're stepping a breakpoint.  */
   if (record_btrace_replay_at_breakpoint (tp))
     return btrace_step_stopped ();
@@ -2362,15 +2372,6 @@ record_btrace_single_step_forward (struct thread_info *tp)
     }
   while (btrace_insn_get (replay) == NULL);
 
-  /* Determine the end of the instruction trace.  */
-  btrace_insn_end (&end, btinfo);
-
-  /* The execution trace contains (and ends with) the current instruction.
-     This instruction has not been executed, yet, so the trace really ends
-     one instruction earlier.  */
-  if (btrace_insn_cmp (replay, &end) == 0)
-    return btrace_step_no_history ();
-
   return btrace_step_spurious ();
 }
 
diff --git a/gdb/testsuite/gdb.btrace/cont-hang.exp b/gdb/testsuite/gdb.btrace/cont-hang.exp
new file mode 100644
index 00000000000..cddcf68b3ab
--- /dev/null
+++ b/gdb/testsuite/gdb.btrace/cont-hang.exp
@@ -0,0 +1,43 @@
+# 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/>.
+
+# Test that we do not hang when trying to continue over a breakpoint at
+# the end of the trace.
+
+require allow_btrace_tests
+
+standard_testfile record_goto.c
+if [prepare_for_testing "failed to prepare" $testfile $srcfile] {
+    return -1
+}
+
+if ![runto_main] {
+    return -1
+}
+
+# Trace the call to the test function.
+gdb_test_no_output "record btrace"
+gdb_test "next" "main\.3.*"
+
+# We need to be replaying, otherwise, we'd just continue recording.
+gdb_test "reverse-stepi"
+gdb_test "break"
+
+# Continuing will step over the breakpoint and then run into the end of
+# the execution history.  This ends replay, so we can continue recording.
+gdb_test "continue" "No more reverse-execution history.*"
+gdb_continue_to_end
diff --git a/gdb/testsuite/gdb.btrace/step-hang.exp b/gdb/testsuite/gdb.btrace/step-hang.exp
new file mode 100644
index 00000000000..91ea813955d
--- /dev/null
+++ b/gdb/testsuite/gdb.btrace/step-hang.exp
@@ -0,0 +1,42 @@
+# 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/>.
+
+# Test that we do not hang when trying to step over a breakpoint at the
+# end of the trace.
+
+require allow_btrace_tests
+
+standard_testfile record_goto.c
+if [prepare_for_testing "failed to prepare" $testfile $srcfile] {
+    return -1
+}
+
+if ![runto_main] {
+    return -1
+}
+
+# Trace the call to the test function.
+gdb_test_no_output "record btrace"
+gdb_test "next" "main\.3.*"
+
+# We need to be replaying, otherwise, we'd just continue recording.
+gdb_test "reverse-stepi"
+gdb_test "break"
+
+# Stepping over the breakpoint ends replaying and we can continue recording.
+gdb_test "step"  "main\.3.*"
+gdb_continue_to_end
diff --git a/gdb/testsuite/gdb.btrace/stepn.exp b/gdb/testsuite/gdb.btrace/stepn.exp
new file mode 100644
index 00000000000..4aec90adc65
--- /dev/null
+++ b/gdb/testsuite/gdb.btrace/stepn.exp
@@ -0,0 +1,50 @@
+# 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/>.
+
+# Test that step n does not start recording when issued while replaying.
+
+require allow_btrace_tests
+
+standard_testfile record_goto.c
+if [prepare_for_testing "failed to prepare" $testfile $srcfile] {
+    return -1
+}
+
+if ![runto_main] {
+    return -1
+}
+
+# Trace the call to the test function.
+gdb_test_no_output "record btrace"
+gdb_test "next" "main\.3.*"
+
+# Stepping should bring us to the end of the execution history, but should
+# not resume recording.
+with_test_prefix "stepi" {
+    gdb_test "reverse-stepi"
+    gdb_test "stepi 5" "No more reverse-execution history.*main\.3.*"
+}
+
+with_test_prefix "step" {
+    gdb_test "reverse-step"
+    gdb_test "step 5" "No more reverse-execution history.*main\.3.*"
+}
+
+with_test_prefix "next" {
+    gdb_test "reverse-next"
+    gdb_test "next 5" "No more reverse-execution history.*main\.3.*"
+}
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v4 2/4] gdb, infrun, record: fix hang when step-over fails with no-history
  2024-03-12 11:34 [PATCH v4 0/4] btrace: infrun fixes Markus Metzger
  2024-03-12 11:34 ` [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history Markus Metzger
@ 2024-03-12 11:34 ` Markus Metzger
  2024-04-04 18:27   ` Guinevere Larsen
  2024-03-12 11:34 ` [PATCH v4 3/4] gdb, infrun, record: move no-history notification into normal_stop Markus Metzger
  2024-03-12 11:34 ` [PATCH v4 4/4] gdb, infrun: fix multi-threaded reverse stepping Markus Metzger
  3 siblings, 1 reply; 16+ messages in thread
From: Markus Metzger @ 2024-03-12 11:34 UTC (permalink / raw)
  To: gdb-patches

When trying to step over a breakpoint at the end of the trace while
another thread is replaying, the step-over will fail with no-history.
This does not clear step_over_info so a subsequent resume will cause GDB
to not resume the thread and expect a SIGTRAP to complete the step-over.
This will never come causing GDB to hang in the wait-for-event poll.

This is a variant of the issue fixed in the parent commit.  That commit
addressed the issue for a single-threaded process and fixed an issue with
reverse/replay stepping in general.

This commit addresses the issue for a multi-threaded process.  In this
case, the single-step does not complete.

Finish an in-flight step-over when a thread stopped with NO_HISTORY.
Since we did not move, we will simply start the step-over again.
---
 gdb/infrun.c                                  | 14 ++++
 .../gdb.btrace/multi-thread-break-hang.exp    | 84 +++++++++++++++++++
 2 files changed, 98 insertions(+)
 create mode 100644 gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp

diff --git a/gdb/infrun.c b/gdb/infrun.c
index f38d96b64df..eb5164b4066 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -78,6 +78,8 @@
 #include "disasm.h"
 #include "interps.h"
 
+struct execution_control_state;
+
 /* Prototypes for local functions */
 
 static void sig_print_info (enum gdb_signal);
@@ -109,6 +111,8 @@ static bool step_over_info_valid_p (void);
 
 static bool schedlock_applies (struct thread_info *tp);
 
+static int finish_step_over (execution_control_state *ecs);
+
 /* Asynchronous signal handler registered as event loop source for
    when we have pending events ready to be passed to the core.  */
 static struct async_event_handler *infrun_async_inferior_event_token;
@@ -6535,6 +6539,16 @@ handle_inferior_event (struct execution_control_state *ecs)
 	return;
 
       interps_notify_no_history ();
+
+      /* Cancel an in-flight step-over.  It will not succeed since we
+	 won't be able to step at the end of the execution history.  */
+      {
+	/* finish_step_over may call restart_threads, which may change the
+	   current thread.  make sure we leave the event thread as the
+	   current thread.  */
+	scoped_restore_current_thread restore_thread;
+	finish_step_over (ecs);
+      }
       stop_waiting (ecs);
       return;
     }
diff --git a/gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp b/gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp
new file mode 100644
index 00000000000..33edcf05612
--- /dev/null
+++ b/gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp
@@ -0,0 +1,84 @@
+# 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/>.
+
+# Test that we cancel an in-flight step-over at the end of the execution
+# history as long as some other thread is still replaying.
+#
+# This used to cause GDB to hang in poll ().
+
+require allow_btrace_tests
+
+standard_testfile multi-thread-step.c
+if [prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthreads}] {
+    return -1
+}
+
+if ![runto_main] {
+    return -1
+}
+
+# Set up breakpoints.
+set bp_1 [gdb_get_line_number "bp.1" $srcfile]
+set bp_2 [gdb_get_line_number "bp.2" $srcfile]
+
+# Trace the code between the two breakpoints.
+gdb_breakpoint $srcfile:$bp_1
+gdb_continue_to_breakpoint "continue to bp.1" ".*$srcfile:$bp_1\r\n.*"
+
+gdb_test_no_output "record btrace"
+
+# We have two threads at or close to bp.1 but handled only one stop event.
+# Remove the breakpoint so we do not need to deal with the 2nd event.
+delete_breakpoints
+gdb_breakpoint $srcfile:$bp_2
+gdb_continue_to_breakpoint "continue to bp.2" ".*$srcfile:$bp_2\r\n.*"
+
+# Determine the thread that reported the breakpoint.
+set thread [get_integer_valueof "\$_thread" bad]
+
+# Determine the other thread.
+set other "bad"
+if { $thread == 1 } {
+    set other 2
+} elseif { $thread == 2 } {
+    set other 1
+}
+
+# This test requires scheduler-locking 'on' or 'step'; 'replay' would
+# implicitly stop replaying, avoiding the problem; 'off' would step one
+# and resume the other.
+#
+# With the current record-btrace implementation that steps all resumed
+# threads in lock-step, 'off' might actually pass but we don't want to
+# bake that behavior into tests.
+gdb_test_no_output "set scheduler-locking step"
+
+# Start replaying the other thread.  This will prevent stepping the thread
+# that reported the event.
+gdb_test "thread apply $other record goto begin"
+gdb_test "thread apply $other info record" "Replay in progress.*"
+
+# We're at a breakpoint so this triggers step-over.  Since we're at the
+# end of the trace, the step will fail.
+gdb_test "stepi" "No more reverse-execution history.*" "stepi.1"
+
+# We used to hang at the second step since step-over insisted on polling
+# the next event.
+gdb_test "stepi" "No more reverse-execution history.*" "stepi.2"
+
+# Do one more just in case.
+gdb_test "stepi" "No more reverse-execution history.*" "stepi.3"
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v4 3/4] gdb, infrun, record: move no-history notification into normal_stop
  2024-03-12 11:34 [PATCH v4 0/4] btrace: infrun fixes Markus Metzger
  2024-03-12 11:34 ` [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history Markus Metzger
  2024-03-12 11:34 ` [PATCH v4 2/4] gdb, infrun, record: fix hang when step-over fails with no-history Markus Metzger
@ 2024-03-12 11:34 ` Markus Metzger
  2024-04-04 18:57   ` Guinevere Larsen
  2024-03-12 11:34 ` [PATCH v4 4/4] gdb, infrun: fix multi-threaded reverse stepping Markus Metzger
  3 siblings, 1 reply; 16+ messages in thread
From: Markus Metzger @ 2024-03-12 11:34 UTC (permalink / raw)
  To: gdb-patches

Leave calling gdb::observers::no_history.notify to normal_stop based on
the last waitstatus.
---
 gdb/infrun.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index eb5164b4066..f92f529412f 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -6538,8 +6538,6 @@ handle_inferior_event (struct execution_control_state *ecs)
       if (handle_stop_requested (ecs))
 	return;
 
-      interps_notify_no_history ();
-
       /* Cancel an in-flight step-over.  It will not succeed since we
 	 won't be able to step at the end of the execution history.  */
       {
@@ -9002,7 +9000,6 @@ keep_going_pass_signal (struct execution_control_state *ecs)
   if (ecs->event_thread->control.is_replaying
       && !target_record_is_replaying (ecs->event_thread->ptid))
     {
-      interps_notify_no_history ();
       ecs->ws.set_no_history ();
       set_last_target_status (ecs->target, ecs->ptid, ecs->ws);
       stop_print_frame = true;
@@ -9642,6 +9639,9 @@ normal_stop ()
   if (saved_context.changed ())
     return true;
 
+  if (last.kind () == TARGET_WAITKIND_NO_HISTORY)
+    interps_notify_no_history ();
+
   /* Notify observers about the stop.  This is where the interpreters
      print the stop event.  */
   notify_normal_stop ((inferior_ptid != null_ptid
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v4 4/4] gdb, infrun: fix multi-threaded reverse stepping
  2024-03-12 11:34 [PATCH v4 0/4] btrace: infrun fixes Markus Metzger
                   ` (2 preceding siblings ...)
  2024-03-12 11:34 ` [PATCH v4 3/4] gdb, infrun, record: move no-history notification into normal_stop Markus Metzger
@ 2024-03-12 11:34 ` Markus Metzger
  2024-04-03 20:27   ` Guinevere Larsen
  3 siblings, 1 reply; 16+ messages in thread
From: Markus Metzger @ 2024-03-12 11:34 UTC (permalink / raw)
  To: gdb-patches

When reverse-stepping a thread that has a pending breakpoint event, the
thread is not resumed as part of the infcmd function.  A first resume
notices the event and returns without resuming the target.

If the corresponding breakpoint has been deleted, event processing results
in a second resume that performs the intended stepping action.  That
resume happens after the infcmd function returned and the temporarily
modified execution direction was restored.  We end up resuming in the
wrong direction.

Store the direction in a thread's control state and change most of
infrun to take it from there rather than using the global variable.
---
 gdb/gdbthread.h                               | 10 +++
 gdb/infrun.c                                  | 47 ++++++----
 gdb/infrun.h                                  |  7 --
 .../gdb.btrace/implicit-stop-replaying.exp    | 90 +++++++++++++++++++
 4 files changed, 128 insertions(+), 26 deletions(-)
 create mode 100644 gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 4931a0cc2f1..64f9e231f38 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -92,6 +92,13 @@ enum step_over_calls_kind
     STEP_OVER_UNDEBUGGABLE
   };
 
+/* Reverse execution.  */
+enum exec_direction_kind
+  {
+    EXEC_FORWARD,
+    EXEC_REVERSE
+  };
+
 /* Inferior thread specific part of `struct infcall_control_state'.
 
    Inferior process counterpart is `struct inferior_control_state'.  */
@@ -176,6 +183,9 @@ struct thread_control_state
 
   /* Whether the thread was replaying when the command was issued.  */
   bool is_replaying = false;
+
+  /* The execution direction when the command was issued.  */
+  enum exec_direction_kind execution_direction = EXEC_FORWARD;
 };
 
 /* Inferior thread specific part of `struct infcall_suspend_state'.  */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index f92f529412f..9db396821d2 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -96,7 +96,8 @@ static void insert_step_resume_breakpoint_at_caller (const frame_info_ptr &);
 
 static void insert_longjmp_resume_breakpoint (struct gdbarch *, CORE_ADDR);
 
-static bool maybe_software_singlestep (struct gdbarch *gdbarch);
+static bool maybe_software_singlestep (const thread_info *tp,
+				       gdbarch *gdbarch, CORE_ADDR pc);
 
 static void resume (gdb_signal sig);
 
@@ -2372,11 +2373,12 @@ bool sched_multi = false;
    GDBARCH the current gdbarch.  */
 
 static bool
-maybe_software_singlestep (struct gdbarch *gdbarch)
+maybe_software_singlestep (const thread_info *tp, gdbarch *gdbarch,
+			   CORE_ADDR pc)
 {
   bool hw_step = true;
 
-  if (execution_direction == EXEC_FORWARD
+  if (tp->control.execution_direction == EXEC_FORWARD
       && gdbarch_software_single_step_p (gdbarch))
     hw_step = !insert_single_step_breakpoints (gdbarch);
 
@@ -2527,6 +2529,10 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
   /* Install inferior's terminal modes.  */
   target_terminal::inferior ();
 
+  scoped_restore save_exec_dir
+    = make_scoped_restore (&execution_direction,
+			   tp->control.execution_direction);
+
   /* Avoid confusing the next resume, if the next stop/resume
      happens to apply to another thread.  */
   tp->set_stop_signal (GDB_SIGNAL_0);
@@ -2787,6 +2793,7 @@ resume_1 (enum gdb_signal sig)
 	      insert_breakpoints ();
 
 	      resume_ptid = internal_resume_ptid (user_step);
+
 	      do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
 	      tp->set_resumed (true);
 	      return;
@@ -2836,7 +2843,7 @@ resume_1 (enum gdb_signal sig)
 	  set_step_over_info (aspace, regcache_read_pc (regcache), 0,
 			      tp->global_num);
 
-	  step = maybe_software_singlestep (gdbarch);
+	  step = maybe_software_singlestep (tp, gdbarch, pc);
 
 	  insert_breakpoints ();
 	}
@@ -2855,7 +2862,7 @@ resume_1 (enum gdb_signal sig)
 
   /* Do we need to do it the hard way, w/temp breakpoints?  */
   else if (step)
-    step = maybe_software_singlestep (gdbarch);
+    step = maybe_software_singlestep (tp, gdbarch, pc);
 
   /* Currently, our software single-step implementation leads to different
      results than hardware single-stepping in one situation: when stepping
@@ -2926,7 +2933,7 @@ resume_1 (enum gdb_signal sig)
   else
     resume_ptid = internal_resume_ptid (user_step);
 
-  if (execution_direction != EXEC_REVERSE
+  if (tp->control.execution_direction != EXEC_REVERSE
       && step && breakpoint_inserted_here_p (aspace, pc))
     {
       /* There are two cases where we currently need to step a
@@ -3095,6 +3102,7 @@ clear_proceed_status_thread (struct thread_info *tp)
   bpstat_clear (&tp->control.stop_bpstat);
 
   tp->control.is_replaying = target_record_is_replaying (tp->ptid);
+  tp->control.execution_direction = ::execution_direction;
 }
 
 /* Notify the current interpreter and observers that the target is about to
@@ -3204,7 +3212,7 @@ schedlock_applies (struct thread_info *tp)
 	      && tp->control.stepping_command)
 	  || (scheduler_mode == schedlock_replay
 	      && target_record_will_replay (minus_one_ptid,
-					    execution_direction)));
+					    tp->control.execution_direction)));
 }
 
 /* Set process_stratum_target::COMMIT_RESUMED_STATE in all target
@@ -3629,7 +3637,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
       if (cur_thr->stop_pc_p ()
 	  && pc == cur_thr->stop_pc ()
 	  && breakpoint_here_p (aspace, pc) == ordinary_breakpoint_here
-	  && execution_direction != EXEC_REVERSE)
+	  && cur_thr->control.execution_direction != EXEC_REVERSE)
 	/* There is a breakpoint at the address we will resume at,
 	   step one instruction before inserting breakpoints so that
 	   we do not stop right away (and report a second hit at this
@@ -4927,7 +4935,7 @@ adjust_pc_after_break (struct thread_info *thread,
      breakpoint at PC - 1.  We'd then report a hit on B1, although
      INSN1 hadn't been de-executed yet.  Doing nothing is the correct
      behaviour.  */
-  if (execution_direction == EXEC_REVERSE)
+  if (thread->control.execution_direction == EXEC_REVERSE)
     return;
 
   /* If the target can tell whether the thread hit a SW breakpoint,
@@ -7526,7 +7534,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 
       delete_step_resume_breakpoint (ecs->event_thread);
       if (ecs->event_thread->control.proceed_to_finish
-	  && execution_direction == EXEC_REVERSE)
+	  && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
 	{
 	  struct thread_info *tp = ecs->event_thread;
 
@@ -7541,7 +7549,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 	}
       fill_in_stop_func (gdbarch, ecs);
       if (ecs->event_thread->stop_pc () == ecs->stop_func_start
-	  && execution_direction == EXEC_REVERSE)
+	  && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
 	{
 	  /* We are stepping over a function call in reverse, and just
 	     hit the step-resume breakpoint at the start address of
@@ -7666,7 +7674,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 
   if (pc_in_thread_step_range (ecs->event_thread->stop_pc (),
 			       ecs->event_thread)
-      && (execution_direction != EXEC_REVERSE
+      && (ecs->event_thread->control.execution_direction != EXEC_REVERSE
 	  || *curr_frame_id == original_frame_id))
     {
       infrun_debug_printf
@@ -7685,7 +7693,7 @@ process_event_stop_test (struct execution_control_state *ecs)
       CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
       if (stop_pc == ecs->event_thread->control.step_range_start
 	  && stop_pc != ecs->stop_func_start
-	  && execution_direction == EXEC_REVERSE)
+	  && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
 	end_stepping_range (ecs);
       else
 	keep_going (ecs);
@@ -7707,7 +7715,7 @@ process_event_stop_test (struct execution_control_state *ecs)
      backward through the trampoline code, and that's handled further
      down, so there is nothing for us to do here.  */
 
-  if (execution_direction != EXEC_REVERSE
+  if (ecs->event_thread->control.execution_direction != EXEC_REVERSE
       && ecs->event_thread->control.step_over_calls == STEP_OVER_UNDEBUGGABLE
       && in_solib_dynsym_resolve_code (ecs->event_thread->stop_pc ())
       && (ecs->event_thread->control.step_start_function == nullptr
@@ -7856,7 +7864,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 
       /* Reverse stepping through solib trampolines.  */
 
-      if (execution_direction == EXEC_REVERSE
+      if (ecs->event_thread->control.execution_direction == EXEC_REVERSE
 	  && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE
 	  && (gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc)
 	      || (ecs->stop_func_start == 0
@@ -7884,7 +7892,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 	     stepped into (backwards), and continue to there.  When we
 	     get there, we'll need to single-step back to the caller.  */
 
-	  if (execution_direction == EXEC_REVERSE)
+	  if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
 	    {
 	      /* If we're already at the start of the function, we've either
 		 just stepped backward into a single instruction function,
@@ -7947,7 +7955,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 						  tmp_sal)
 	    && !inline_frame_is_marked_for_skip (true, ecs->event_thread))
 	  {
-	    if (execution_direction == EXEC_REVERSE)
+	    if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
 	      handle_step_into_function_backward (gdbarch, ecs);
 	    else
 	      handle_step_into_function (gdbarch, ecs);
@@ -7965,7 +7973,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 	  return;
 	}
 
-      if (execution_direction == EXEC_REVERSE)
+      if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
 	{
 	  /* If we're already at the start of the function, we've either just
 	     stepped backward into a single instruction function without line
@@ -7994,7 +8002,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 
   /* Reverse stepping through solib trampolines.  */
 
-  if (execution_direction == EXEC_REVERSE
+  if (ecs->event_thread->control.execution_direction == EXEC_REVERSE
       && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
     {
       CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
@@ -8577,6 +8585,7 @@ keep_going_stepped_thread (struct thread_info *tp)
 
       tp->set_resumed (true);
       resume_ptid = internal_resume_ptid (tp->control.stepping_command);
+
       do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
     }
   else
diff --git a/gdb/infrun.h b/gdb/infrun.h
index 6339fd997e1..1a52d6250e5 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -107,13 +107,6 @@ extern bool disable_randomization;
    current location.  */
 extern ULONGEST get_stop_id (void);
 
-/* Reverse execution.  */
-enum exec_direction_kind
-  {
-    EXEC_FORWARD,
-    EXEC_REVERSE
-  };
-
 /* The current execution direction.  */
 extern enum exec_direction_kind execution_direction;
 
diff --git a/gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp b/gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp
new file mode 100644
index 00000000000..20240da1dc1
--- /dev/null
+++ b/gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp
@@ -0,0 +1,90 @@
+# 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/>.
+
+# Test that we stop replaying other threads when stepping a thread at the
+# end of its execution history.
+#
+# This is similar to the last test in multi-thread-step.exp, except that
+# we reverse-step instead of record goto begin to start replaying and we
+# step instead of continuing.
+#
+# This triggered a bug where GDB confused the execution direction and kept
+# stepping both threads backwards instead of forwards.
+
+require allow_btrace_tests
+
+standard_testfile multi-thread-step.c
+if [prepare_for_testing "failed to prepare" $testfile $srcfile \
+    {debug pthreads}] {
+    return -1
+}
+
+if ![runto_main] {
+    return -1
+}
+
+# Set up breakpoints.
+set bp_1 [gdb_get_line_number "bp.1" $srcfile]
+set bp_2 [gdb_get_line_number "bp.2" $srcfile]
+
+# Trace the code between the two breakpoints.
+gdb_breakpoint $srcfile:$bp_1
+gdb_continue_to_breakpoint "continue to bp.1" ".*$srcfile:$bp_1\r\n.*"
+
+# Make sure GDB knows about the new thread.
+gdb_test "info threads"
+gdb_test_no_output "record btrace"
+
+# We have two threads at or close to bp.1 but handled only one stop event.
+# Remove the breakpoint so we do not need to deal with the 2nd event.
+delete_breakpoints
+gdb_breakpoint $srcfile:$bp_2
+gdb_continue_to_breakpoint "continue to bp.2" ".*$srcfile:$bp_2\r\n.*"
+
+# Determine the thread that reported the breakpoint.
+set thread "bad"
+gdb_test_multiple "thread" "thread" {
+    -re -wrap "Current thread is \($decimal\).*" {
+	set thread $expect_out(1,string)
+    }
+}
+
+# Determine the other thread.
+set other "bad"
+if { $thread == 1 } {
+    set other 2
+} elseif { $thread == 2 } {
+    set other 1
+}
+
+# This test only works for scheduler-locking 'replay'.
+gdb_test_no_output "set scheduler-locking replay"
+
+# Remove breakpoints or we might run into it right away.
+delete_breakpoints
+
+# Start replaying the other thread.
+gdb_test "thread apply $other reverse-stepi"
+gdb_test "thread apply $other info record" "Replay in progress.*" \
+    "other thread is replaying"
+
+# Step the thread that reported the breakpoint, which is not replaying.
+gdb_test "next" "return arg;.*"
+
+# The other thread stopped replaying.
+gdb_test "thread apply $other info record" "Recorded \[^\\\r\\\n\]*" \
+    "other thread stopped replaying"
-- 
2.34.1

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history
  2024-03-12 11:34 ` [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history Markus Metzger
@ 2024-03-12 11:56   ` Hannes Domani
  2024-03-13 11:47     ` Hannes Domani
  2024-04-04 18:03   ` Guinevere Larsen
  1 sibling, 1 reply; 16+ messages in thread
From: Hannes Domani @ 2024-03-12 11:56 UTC (permalink / raw)
  To: gdb-patches, Markus Metzger

 Am Dienstag, 12. März 2024 um 12:35:10 MEZ hat Markus Metzger <markus.t.metzger@intel.com> Folgendes geschrieben:

> When trying to step over a breakpoint at the end of the trace, the
> step-over will fail with no-history.  This does not clear step_over_info
> so a subsequent resume will cause GDB to not resume the thread and expect
> a SIGTRAP to complete the step-over.  This will never come causing GDB to
> hang in the wait-for-event poll.
>
> That step-over failed after actually completing the step.  This is wrong.
> The step-over itself should have failed and the step should not have
> completed.  Fix it by moving the end of execution history check to before
> we are stepping.
>
> This exposes another issue, however.  When completing a step-over at the
> end of the execution history, we implicitly stop replaying that thread.  A
> continue command would resume after the step-over and, since we're no
> longer replaying, would continue recording.
>
> Fix that by recording the replay state in the thread's control state and
> failing with no-history in keep_going if we're switching from replay to
> recording.

That sounds a lot like https://sourceware.org/bugzilla/show_bug.cgi?id=31353.


Hannes

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history
  2024-03-12 11:56   ` Hannes Domani
@ 2024-03-13 11:47     ` Hannes Domani
  0 siblings, 0 replies; 16+ messages in thread
From: Hannes Domani @ 2024-03-13 11:47 UTC (permalink / raw)
  To: gdb-patches, Markus Metzger

 Am Dienstag, 12. März 2024 um 12:56:39 MEZ hat Hannes Domani <ssbssa@yahoo.de> Folgendes geschrieben:

> Am Dienstag, 12. März 2024 um 12:35:10 MEZ hat Markus Metzger <markus.t.metzger@intel.com> Folgendes geschrieben:
>
>
> > When trying to step over a breakpoint at the end of the trace, the
> > step-over will fail with no-history.  This does not clear step_over_info
> > so a subsequent resume will cause GDB to not resume the thread and expect
> > a SIGTRAP to complete the step-over.  This will never come causing GDB to
> > hang in the wait-for-event poll.
> >
> > That step-over failed after actually completing the step.  This is wrong.
> > The step-over itself should have failed and the step should not have
> > completed.  Fix it by moving the end of execution history check to before
> > we are stepping.
> >
> > This exposes another issue, however.  When completing a step-over at the
> > end of the execution history, we implicitly stop replaying that thread.  A
> > continue command would resume after the step-over and, since we're no
> > longer replaying, would continue recording.
> >
> > Fix that by recording the replay state in the thread's control state and
> > failing with no-history in keep_going if we're switching from replay to
> > recording.
>
>
> That sounds a lot like https://sourceware.org/bugzilla/show_bug.cgi?id=31353.

I've now tested this, it does not fix PR31353, but the next patch does:
[PATCH v4 2/4] gdb, infrun, record: fix hang when step-over fails with no-history


Hannes

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 4/4] gdb, infrun: fix multi-threaded reverse stepping
  2024-03-12 11:34 ` [PATCH v4 4/4] gdb, infrun: fix multi-threaded reverse stepping Markus Metzger
@ 2024-04-03 20:27   ` Guinevere Larsen
  2024-04-09  9:14     ` Metzger, Markus T
  0 siblings, 1 reply; 16+ messages in thread
From: Guinevere Larsen @ 2024-04-03 20:27 UTC (permalink / raw)
  To: Markus Metzger, gdb-patches

On 3/12/24 08:34, Markus Metzger wrote:
> When reverse-stepping a thread that has a pending breakpoint event, the
> thread is not resumed as part of the infcmd function.  A first resume
> notices the event and returns without resuming the target.
>
> If the corresponding breakpoint has been deleted, event processing results
> in a second resume that performs the intended stepping action.  That
> resume happens after the infcmd function returned and the temporarily
> modified execution direction was restored.  We end up resuming in the
> wrong direction.
>
> Store the direction in a thread's control state and change most of
> infrun to take it from there rather than using the global variable.

I'm slowly making my way through this series, but this patch 
specifically seems to not apply at all. Trying to use git am --3way 
gives me the following output:

Applying: gdb, infrun: fix multi-threaded reverse stepping
error: sha1 information is lacking or useless (gdb/infrun.c).
error: could not build fake ancestor
Patch failed at 0001 gdb, infrun: fix multi-threaded reverse stepping

Reviews for the other patches should follow soon

--
Cheers,
Guinevere Larsen
She/Her/Hers

> ---
>   gdb/gdbthread.h                               | 10 +++
>   gdb/infrun.c                                  | 47 ++++++----
>   gdb/infrun.h                                  |  7 --
>   .../gdb.btrace/implicit-stop-replaying.exp    | 90 +++++++++++++++++++
>   4 files changed, 128 insertions(+), 26 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp
>
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index 4931a0cc2f1..64f9e231f38 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -92,6 +92,13 @@ enum step_over_calls_kind
>       STEP_OVER_UNDEBUGGABLE
>     };
>   
> +/* Reverse execution.  */
> +enum exec_direction_kind
> +  {
> +    EXEC_FORWARD,
> +    EXEC_REVERSE
> +  };
> +
>   /* Inferior thread specific part of `struct infcall_control_state'.
>   
>      Inferior process counterpart is `struct inferior_control_state'.  */
> @@ -176,6 +183,9 @@ struct thread_control_state
>   
>     /* Whether the thread was replaying when the command was issued.  */
>     bool is_replaying = false;
> +
> +  /* The execution direction when the command was issued.  */
> +  enum exec_direction_kind execution_direction = EXEC_FORWARD;
>   };
>   
>   /* Inferior thread specific part of `struct infcall_suspend_state'.  */
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index f92f529412f..9db396821d2 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -96,7 +96,8 @@ static void insert_step_resume_breakpoint_at_caller (const frame_info_ptr &);
>   
>   static void insert_longjmp_resume_breakpoint (struct gdbarch *, CORE_ADDR);
>   
> -static bool maybe_software_singlestep (struct gdbarch *gdbarch);
> +static bool maybe_software_singlestep (const thread_info *tp,
> +				       gdbarch *gdbarch, CORE_ADDR pc);
>   
>   static void resume (gdb_signal sig);
>   
> @@ -2372,11 +2373,12 @@ bool sched_multi = false;
>      GDBARCH the current gdbarch.  */
>   
>   static bool
> -maybe_software_singlestep (struct gdbarch *gdbarch)
> +maybe_software_singlestep (const thread_info *tp, gdbarch *gdbarch,
> +			   CORE_ADDR pc)
>   {
>     bool hw_step = true;
>   
> -  if (execution_direction == EXEC_FORWARD
> +  if (tp->control.execution_direction == EXEC_FORWARD
>         && gdbarch_software_single_step_p (gdbarch))
>       hw_step = !insert_single_step_breakpoints (gdbarch);
>   
> @@ -2527,6 +2529,10 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
>     /* Install inferior's terminal modes.  */
>     target_terminal::inferior ();
>   
> +  scoped_restore save_exec_dir
> +    = make_scoped_restore (&execution_direction,
> +			   tp->control.execution_direction);
> +
>     /* Avoid confusing the next resume, if the next stop/resume
>        happens to apply to another thread.  */
>     tp->set_stop_signal (GDB_SIGNAL_0);
> @@ -2787,6 +2793,7 @@ resume_1 (enum gdb_signal sig)
>   	      insert_breakpoints ();
>   
>   	      resume_ptid = internal_resume_ptid (user_step);
> +
>   	      do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
>   	      tp->set_resumed (true);
>   	      return;
> @@ -2836,7 +2843,7 @@ resume_1 (enum gdb_signal sig)
>   	  set_step_over_info (aspace, regcache_read_pc (regcache), 0,
>   			      tp->global_num);
>   
> -	  step = maybe_software_singlestep (gdbarch);
> +	  step = maybe_software_singlestep (tp, gdbarch, pc);
>   
>   	  insert_breakpoints ();
>   	}
> @@ -2855,7 +2862,7 @@ resume_1 (enum gdb_signal sig)
>   
>     /* Do we need to do it the hard way, w/temp breakpoints?  */
>     else if (step)
> -    step = maybe_software_singlestep (gdbarch);
> +    step = maybe_software_singlestep (tp, gdbarch, pc);
>   
>     /* Currently, our software single-step implementation leads to different
>        results than hardware single-stepping in one situation: when stepping
> @@ -2926,7 +2933,7 @@ resume_1 (enum gdb_signal sig)
>     else
>       resume_ptid = internal_resume_ptid (user_step);
>   
> -  if (execution_direction != EXEC_REVERSE
> +  if (tp->control.execution_direction != EXEC_REVERSE
>         && step && breakpoint_inserted_here_p (aspace, pc))
>       {
>         /* There are two cases where we currently need to step a
> @@ -3095,6 +3102,7 @@ clear_proceed_status_thread (struct thread_info *tp)
>     bpstat_clear (&tp->control.stop_bpstat);
>   
>     tp->control.is_replaying = target_record_is_replaying (tp->ptid);
> +  tp->control.execution_direction = ::execution_direction;
>   }
>   
>   /* Notify the current interpreter and observers that the target is about to
> @@ -3204,7 +3212,7 @@ schedlock_applies (struct thread_info *tp)
>   	      && tp->control.stepping_command)
>   	  || (scheduler_mode == schedlock_replay
>   	      && target_record_will_replay (minus_one_ptid,
> -					    execution_direction)));
> +					    tp->control.execution_direction)));
>   }
>   
>   /* Set process_stratum_target::COMMIT_RESUMED_STATE in all target
> @@ -3629,7 +3637,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
>         if (cur_thr->stop_pc_p ()
>   	  && pc == cur_thr->stop_pc ()
>   	  && breakpoint_here_p (aspace, pc) == ordinary_breakpoint_here
> -	  && execution_direction != EXEC_REVERSE)
> +	  && cur_thr->control.execution_direction != EXEC_REVERSE)
>   	/* There is a breakpoint at the address we will resume at,
>   	   step one instruction before inserting breakpoints so that
>   	   we do not stop right away (and report a second hit at this
> @@ -4927,7 +4935,7 @@ adjust_pc_after_break (struct thread_info *thread,
>        breakpoint at PC - 1.  We'd then report a hit on B1, although
>        INSN1 hadn't been de-executed yet.  Doing nothing is the correct
>        behaviour.  */
> -  if (execution_direction == EXEC_REVERSE)
> +  if (thread->control.execution_direction == EXEC_REVERSE)
>       return;
>   
>     /* If the target can tell whether the thread hit a SW breakpoint,
> @@ -7526,7 +7534,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>   
>         delete_step_resume_breakpoint (ecs->event_thread);
>         if (ecs->event_thread->control.proceed_to_finish
> -	  && execution_direction == EXEC_REVERSE)
> +	  && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
>   	{
>   	  struct thread_info *tp = ecs->event_thread;
>   
> @@ -7541,7 +7549,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>   	}
>         fill_in_stop_func (gdbarch, ecs);
>         if (ecs->event_thread->stop_pc () == ecs->stop_func_start
> -	  && execution_direction == EXEC_REVERSE)
> +	  && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
>   	{
>   	  /* We are stepping over a function call in reverse, and just
>   	     hit the step-resume breakpoint at the start address of
> @@ -7666,7 +7674,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>   
>     if (pc_in_thread_step_range (ecs->event_thread->stop_pc (),
>   			       ecs->event_thread)
> -      && (execution_direction != EXEC_REVERSE
> +      && (ecs->event_thread->control.execution_direction != EXEC_REVERSE
>   	  || *curr_frame_id == original_frame_id))
>       {
>         infrun_debug_printf
> @@ -7685,7 +7693,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>         CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
>         if (stop_pc == ecs->event_thread->control.step_range_start
>   	  && stop_pc != ecs->stop_func_start
> -	  && execution_direction == EXEC_REVERSE)
> +	  && ecs->event_thread->control.execution_direction == EXEC_REVERSE)
>   	end_stepping_range (ecs);
>         else
>   	keep_going (ecs);
> @@ -7707,7 +7715,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>        backward through the trampoline code, and that's handled further
>        down, so there is nothing for us to do here.  */
>   
> -  if (execution_direction != EXEC_REVERSE
> +  if (ecs->event_thread->control.execution_direction != EXEC_REVERSE
>         && ecs->event_thread->control.step_over_calls == STEP_OVER_UNDEBUGGABLE
>         && in_solib_dynsym_resolve_code (ecs->event_thread->stop_pc ())
>         && (ecs->event_thread->control.step_start_function == nullptr
> @@ -7856,7 +7864,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>   
>         /* Reverse stepping through solib trampolines.  */
>   
> -      if (execution_direction == EXEC_REVERSE
> +      if (ecs->event_thread->control.execution_direction == EXEC_REVERSE
>   	  && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE
>   	  && (gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc)
>   	      || (ecs->stop_func_start == 0
> @@ -7884,7 +7892,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>   	     stepped into (backwards), and continue to there.  When we
>   	     get there, we'll need to single-step back to the caller.  */
>   
> -	  if (execution_direction == EXEC_REVERSE)
> +	  if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
>   	    {
>   	      /* If we're already at the start of the function, we've either
>   		 just stepped backward into a single instruction function,
> @@ -7947,7 +7955,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>   						  tmp_sal)
>   	    && !inline_frame_is_marked_for_skip (true, ecs->event_thread))
>   	  {
> -	    if (execution_direction == EXEC_REVERSE)
> +	    if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
>   	      handle_step_into_function_backward (gdbarch, ecs);
>   	    else
>   	      handle_step_into_function (gdbarch, ecs);
> @@ -7965,7 +7973,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>   	  return;
>   	}
>   
> -      if (execution_direction == EXEC_REVERSE)
> +      if (ecs->event_thread->control.execution_direction == EXEC_REVERSE)
>   	{
>   	  /* If we're already at the start of the function, we've either just
>   	     stepped backward into a single instruction function without line
> @@ -7994,7 +8002,7 @@ process_event_stop_test (struct execution_control_state *ecs)
>   
>     /* Reverse stepping through solib trampolines.  */
>   
> -  if (execution_direction == EXEC_REVERSE
> +  if (ecs->event_thread->control.execution_direction == EXEC_REVERSE
>         && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
>       {
>         CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
> @@ -8577,6 +8585,7 @@ keep_going_stepped_thread (struct thread_info *tp)
>   
>         tp->set_resumed (true);
>         resume_ptid = internal_resume_ptid (tp->control.stepping_command);
> +
>         do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
>       }
>     else
> diff --git a/gdb/infrun.h b/gdb/infrun.h
> index 6339fd997e1..1a52d6250e5 100644
> --- a/gdb/infrun.h
> +++ b/gdb/infrun.h
> @@ -107,13 +107,6 @@ extern bool disable_randomization;
>      current location.  */
>   extern ULONGEST get_stop_id (void);
>   
> -/* Reverse execution.  */
> -enum exec_direction_kind
> -  {
> -    EXEC_FORWARD,
> -    EXEC_REVERSE
> -  };
> -
>   /* The current execution direction.  */
>   extern enum exec_direction_kind execution_direction;
>   
> diff --git a/gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp b/gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp
> new file mode 100644
> index 00000000000..20240da1dc1
> --- /dev/null
> +++ b/gdb/testsuite/gdb.btrace/implicit-stop-replaying.exp
> @@ -0,0 +1,90 @@
> +# 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/>.
> +
> +# Test that we stop replaying other threads when stepping a thread at the
> +# end of its execution history.
> +#
> +# This is similar to the last test in multi-thread-step.exp, except that
> +# we reverse-step instead of record goto begin to start replaying and we
> +# step instead of continuing.
> +#
> +# This triggered a bug where GDB confused the execution direction and kept
> +# stepping both threads backwards instead of forwards.
> +
> +require allow_btrace_tests
> +
> +standard_testfile multi-thread-step.c
> +if [prepare_for_testing "failed to prepare" $testfile $srcfile \
> +    {debug pthreads}] {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +# Set up breakpoints.
> +set bp_1 [gdb_get_line_number "bp.1" $srcfile]
> +set bp_2 [gdb_get_line_number "bp.2" $srcfile]
> +
> +# Trace the code between the two breakpoints.
> +gdb_breakpoint $srcfile:$bp_1
> +gdb_continue_to_breakpoint "continue to bp.1" ".*$srcfile:$bp_1\r\n.*"
> +
> +# Make sure GDB knows about the new thread.
> +gdb_test "info threads"
> +gdb_test_no_output "record btrace"
> +
> +# We have two threads at or close to bp.1 but handled only one stop event.
> +# Remove the breakpoint so we do not need to deal with the 2nd event.
> +delete_breakpoints
> +gdb_breakpoint $srcfile:$bp_2
> +gdb_continue_to_breakpoint "continue to bp.2" ".*$srcfile:$bp_2\r\n.*"
> +
> +# Determine the thread that reported the breakpoint.
> +set thread "bad"
> +gdb_test_multiple "thread" "thread" {
> +    -re -wrap "Current thread is \($decimal\).*" {
> +	set thread $expect_out(1,string)
> +    }
> +}
> +
> +# Determine the other thread.
> +set other "bad"
> +if { $thread == 1 } {
> +    set other 2
> +} elseif { $thread == 2 } {
> +    set other 1
> +}
> +
> +# This test only works for scheduler-locking 'replay'.
> +gdb_test_no_output "set scheduler-locking replay"
> +
> +# Remove breakpoints or we might run into it right away.
> +delete_breakpoints
> +
> +# Start replaying the other thread.
> +gdb_test "thread apply $other reverse-stepi"
> +gdb_test "thread apply $other info record" "Replay in progress.*" \
> +    "other thread is replaying"
> +
> +# Step the thread that reported the breakpoint, which is not replaying.
> +gdb_test "next" "return arg;.*"
> +
> +# The other thread stopped replaying.
> +gdb_test "thread apply $other info record" "Recorded \[^\\\r\\\n\]*" \
> +    "other thread stopped replaying"


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history
  2024-03-12 11:34 ` [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history Markus Metzger
  2024-03-12 11:56   ` Hannes Domani
@ 2024-04-04 18:03   ` Guinevere Larsen
  2024-04-05  9:12     ` Metzger, Markus T
  1 sibling, 1 reply; 16+ messages in thread
From: Guinevere Larsen @ 2024-04-04 18:03 UTC (permalink / raw)
  To: Markus Metzger, gdb-patches

On 3/12/24 08:34, Markus Metzger wrote:
> When trying to step over a breakpoint at the end of the trace, the
> step-over will fail with no-history.  This does not clear step_over_info
> so a subsequent resume will cause GDB to not resume the thread and expect
> a SIGTRAP to complete the step-over.  This will never come causing GDB to
> hang in the wait-for-event poll.
>
> That step-over failed after actually completing the step.  This is wrong.
> The step-over itself should have failed and the step should not have
> completed.  Fix it by moving the end of execution history check to before
> we are stepping.
>
> This exposes another issue, however.  When completing a step-over at the
> end of the execution history, we implicitly stop replaying that thread.  A
> continue command would resume after the step-over and, since we're no
> longer replaying, would continue recording.
>
> Fix that by recording the replay state in the thread's control state and
> failing with no-history in keep_going if we're switching from replay to
> recording.

I am a little confused by your test case. From the commit message 
description, I expected the following GDB session to have been problematic:

$ gdb record_goto
(gdb) start
49 fun4 ();
(gdb) record btrace
(gdb) break fun1
(gdb) continue
in frame fun1 ()
23 }
(gdb) reverse-next
In frame fun4 ()
41 fun1 ();
(gdb) next
No more reverse-execution history.
23 }
(gdb) next
# problems due to the SIGTRAP from the previous line

(Pardon my hand written gdb session, my new system has an AMD cpu, so I 
seem to be locked out of btrace).

My confusion comes from your test case never explicitly setting any 
breakpoints. I don't see why the inferior would have a sigtrap generated 
to test it. Am I misunderstanding the issue you described?


-- 
Cheers,
Guinevere Larsen
She/Her/Hers

> ---
>   gdb/gdbthread.h                        |  3 ++
>   gdb/infrun.c                           | 25 +++++++++++++
>   gdb/record-btrace.c                    | 19 +++++-----
>   gdb/testsuite/gdb.btrace/cont-hang.exp | 43 ++++++++++++++++++++++
>   gdb/testsuite/gdb.btrace/step-hang.exp | 42 ++++++++++++++++++++++
>   gdb/testsuite/gdb.btrace/stepn.exp     | 50 ++++++++++++++++++++++++++
>   6 files changed, 173 insertions(+), 9 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.btrace/cont-hang.exp
>   create mode 100644 gdb/testsuite/gdb.btrace/step-hang.exp
>   create mode 100644 gdb/testsuite/gdb.btrace/stepn.exp
>
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index 11c553a99ca..4931a0cc2f1 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -173,6 +173,9 @@ struct thread_control_state
>        command.  This is used to decide whether "set scheduler-locking
>        step" behaves like "on" or "off".  */
>     int stepping_command = 0;
> +
> +  /* Whether the thread was replaying when the command was issued.  */
> +  bool is_replaying = false;
>   };
>   
>   /* Inferior thread specific part of `struct infcall_suspend_state'.  */
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index bbb98f6dcdb..f38d96b64df 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -3089,6 +3089,8 @@ clear_proceed_status_thread (struct thread_info *tp)
>   
>     /* Discard any remaining commands or status from previous stop.  */
>     bpstat_clear (&tp->control.stop_bpstat);
> +
> +  tp->control.is_replaying = target_record_is_replaying (tp->ptid);
>   }
>   
>   /* Notify the current interpreter and observers that the target is about to
> @@ -8972,6 +8974,29 @@ keep_going_pass_signal (struct execution_control_state *ecs)
>     gdb_assert (ecs->event_thread->ptid == inferior_ptid);
>     gdb_assert (!ecs->event_thread->resumed ());
>   
> +  /* When a thread reaches the end of its execution history, it automatically
> +     stops replaying.  This is so the user doesn't need to explicitly stop it
> +     with a separate command.
> +
> +     We do not want a single command (e.g. continue) to transition from
> +     replaying to recording, though, e.g. when starting from a breakpoint we
> +     needed to step over at the end of the trace.  When we reach the end of the
> +     execution history during stepping, stop with no-history.
> +
> +     The other direction is fine.  When we're at the end of the execution
> +     history, we may reverse-continue to start replaying.  */
> +  if (ecs->event_thread->control.is_replaying
> +      && !target_record_is_replaying (ecs->event_thread->ptid))
> +    {
> +      interps_notify_no_history ();
> +      ecs->ws.set_no_history ();
> +      set_last_target_status (ecs->target, ecs->ptid, ecs->ws);
> +      stop_print_frame = true;
> +      stop_waiting (ecs);
> +      normal_stop ();
> +      return;
> +    }
> +
>     /* Save the pc before execution, to compare with pc after stop.  */
>     ecs->event_thread->prev_pc
>       = regcache_read_pc_protected (get_thread_regcache (ecs->event_thread));
> diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
> index 6350400c318..ae273fda507 100644
> --- a/gdb/record-btrace.c
> +++ b/gdb/record-btrace.c
> @@ -2340,6 +2340,16 @@ record_btrace_single_step_forward (struct thread_info *tp)
>     if (replay == NULL)
>       return btrace_step_no_history ();
>   
> +  /* The execution trace contains (and ends with) the current instruction.
> +     This instruction has not been executed, yet, so the trace really ends
> +     one instruction earlier.
> +
> +     We'd fail later on in btrace_insn_next () but we must not trigger
> +     breakpoints as we're not really able to step.  */
> +  btrace_insn_end (&end, btinfo);
> +  if (btrace_insn_cmp (replay, &end) == 0)
> +    return btrace_step_no_history ();
> +
>     /* Check if we're stepping a breakpoint.  */
>     if (record_btrace_replay_at_breakpoint (tp))
>       return btrace_step_stopped ();
> @@ -2362,15 +2372,6 @@ record_btrace_single_step_forward (struct thread_info *tp)
>       }
>     while (btrace_insn_get (replay) == NULL);
>   
> -  /* Determine the end of the instruction trace.  */
> -  btrace_insn_end (&end, btinfo);
> -
> -  /* The execution trace contains (and ends with) the current instruction.
> -     This instruction has not been executed, yet, so the trace really ends
> -     one instruction earlier.  */
> -  if (btrace_insn_cmp (replay, &end) == 0)
> -    return btrace_step_no_history ();
> -
>     return btrace_step_spurious ();
>   }
>   
> diff --git a/gdb/testsuite/gdb.btrace/cont-hang.exp b/gdb/testsuite/gdb.btrace/cont-hang.exp
> new file mode 100644
> index 00000000000..cddcf68b3ab
> --- /dev/null
> +++ b/gdb/testsuite/gdb.btrace/cont-hang.exp
> @@ -0,0 +1,43 @@
> +# 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/>.
> +
> +# Test that we do not hang when trying to continue over a breakpoint at
> +# the end of the trace.
> +
> +require allow_btrace_tests
> +
> +standard_testfile record_goto.c
> +if [prepare_for_testing "failed to prepare" $testfile $srcfile] {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +# Trace the call to the test function.
> +gdb_test_no_output "record btrace"
> +gdb_test "next" "main\.3.*"
> +
> +# We need to be replaying, otherwise, we'd just continue recording.
> +gdb_test "reverse-stepi"
> +gdb_test "break"
> +
> +# Continuing will step over the breakpoint and then run into the end of
> +# the execution history.  This ends replay, so we can continue recording.
> +gdb_test "continue" "No more reverse-execution history.*"
> +gdb_continue_to_end
> diff --git a/gdb/testsuite/gdb.btrace/step-hang.exp b/gdb/testsuite/gdb.btrace/step-hang.exp
> new file mode 100644
> index 00000000000..91ea813955d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.btrace/step-hang.exp
> @@ -0,0 +1,42 @@
> +# 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/>.
> +
> +# Test that we do not hang when trying to step over a breakpoint at the
> +# end of the trace.
> +
> +require allow_btrace_tests
> +
> +standard_testfile record_goto.c
> +if [prepare_for_testing "failed to prepare" $testfile $srcfile] {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +# Trace the call to the test function.
> +gdb_test_no_output "record btrace"
> +gdb_test "next" "main\.3.*"
> +
> +# We need to be replaying, otherwise, we'd just continue recording.
> +gdb_test "reverse-stepi"
> +gdb_test "break"
> +
> +# Stepping over the breakpoint ends replaying and we can continue recording.
> +gdb_test "step"  "main\.3.*"
> +gdb_continue_to_end
> diff --git a/gdb/testsuite/gdb.btrace/stepn.exp b/gdb/testsuite/gdb.btrace/stepn.exp
> new file mode 100644
> index 00000000000..4aec90adc65
> --- /dev/null
> +++ b/gdb/testsuite/gdb.btrace/stepn.exp
> @@ -0,0 +1,50 @@
> +# 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/>.
> +
> +# Test that step n does not start recording when issued while replaying.
> +
> +require allow_btrace_tests
> +
> +standard_testfile record_goto.c
> +if [prepare_for_testing "failed to prepare" $testfile $srcfile] {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +# Trace the call to the test function.
> +gdb_test_no_output "record btrace"
> +gdb_test "next" "main\.3.*"
> +
> +# Stepping should bring us to the end of the execution history, but should
> +# not resume recording.
> +with_test_prefix "stepi" {
> +    gdb_test "reverse-stepi"
> +    gdb_test "stepi 5" "No more reverse-execution history.*main\.3.*"
> +}
> +
> +with_test_prefix "step" {
> +    gdb_test "reverse-step"
> +    gdb_test "step 5" "No more reverse-execution history.*main\.3.*"
> +}
> +
> +with_test_prefix "next" {
> +    gdb_test "reverse-next"
> +    gdb_test "next 5" "No more reverse-execution history.*main\.3.*"
> +}


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 2/4] gdb, infrun, record: fix hang when step-over fails with no-history
  2024-03-12 11:34 ` [PATCH v4 2/4] gdb, infrun, record: fix hang when step-over fails with no-history Markus Metzger
@ 2024-04-04 18:27   ` Guinevere Larsen
  0 siblings, 0 replies; 16+ messages in thread
From: Guinevere Larsen @ 2024-04-04 18:27 UTC (permalink / raw)
  To: Markus Metzger, gdb-patches

On 3/12/24 08:34, Markus Metzger wrote:
> When trying to step over a breakpoint at the end of the trace while
> another thread is replaying, the step-over will fail with no-history.
> This does not clear step_over_info so a subsequent resume will cause GDB
> to not resume the thread and expect a SIGTRAP to complete the step-over.
> This will never come causing GDB to hang in the wait-for-event poll.
>
> This is a variant of the issue fixed in the parent commit.  That commit
> addressed the issue for a single-threaded process and fixed an issue with
> reverse/replay stepping in general.
>
> This commit addresses the issue for a multi-threaded process.  In this
> case, the single-step does not complete.
>
> Finish an in-flight step-over when a thread stopped with NO_HISTORY.
> Since we did not move, we will simply start the step-over again.

Apart from a very minor nit inlined, this patch LGTM. I can't test the 
fix directly, but I tested for regressions with special care for clang 
and see no new failures, so FWIW:

Reviewed-By: Guinevere Larsen <blarsen@redhat.com>

> ---
>   gdb/infrun.c                                  | 14 ++++
>   .../gdb.btrace/multi-thread-break-hang.exp    | 84 +++++++++++++++++++
>   2 files changed, 98 insertions(+)
>   create mode 100644 gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index f38d96b64df..eb5164b4066 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -78,6 +78,8 @@
>   #include "disasm.h"
>   #include "interps.h"
>   
> +struct execution_control_state;
> +
>   /* Prototypes for local functions */
>   
>   static void sig_print_info (enum gdb_signal);
> @@ -109,6 +111,8 @@ static bool step_over_info_valid_p (void);
>   
>   static bool schedlock_applies (struct thread_info *tp);
>   
> +static int finish_step_over (execution_control_state *ecs);
> +
This declaration already exists in line 4253. If you want it higher in 
the file, I think it would be good to remove that one.
>   /* Asynchronous signal handler registered as event loop source for
>      when we have pending events ready to be passed to the core.  */
>   static struct async_event_handler *infrun_async_inferior_event_token;
> @@ -6535,6 +6539,16 @@ handle_inferior_event (struct execution_control_state *ecs)
>   	return;
>   
>         interps_notify_no_history ();
> +
> +      /* Cancel an in-flight step-over.  It will not succeed since we
> +	 won't be able to step at the end of the execution history.  */
> +      {
> +	/* finish_step_over may call restart_threads, which may change the
> +	   current thread.  make sure we leave the event thread as the
> +	   current thread.  */
> +	scoped_restore_current_thread restore_thread;
> +	finish_step_over (ecs);
> +      }
>         stop_waiting (ecs);
>         return;
>       }
> diff --git a/gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp b/gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp
> new file mode 100644
> index 00000000000..33edcf05612
> --- /dev/null
> +++ b/gdb/testsuite/gdb.btrace/multi-thread-break-hang.exp
> @@ -0,0 +1,84 @@
> +# 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/>.
> +
> +# Test that we cancel an in-flight step-over at the end of the execution
> +# history as long as some other thread is still replaying.
> +#
> +# This used to cause GDB to hang in poll ().
> +
> +require allow_btrace_tests
> +
> +standard_testfile multi-thread-step.c
> +if [prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthreads}] {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +# Set up breakpoints.
> +set bp_1 [gdb_get_line_number "bp.1" $srcfile]
> +set bp_2 [gdb_get_line_number "bp.2" $srcfile]
> +
> +# Trace the code between the two breakpoints.
> +gdb_breakpoint $srcfile:$bp_1
> +gdb_continue_to_breakpoint "continue to bp.1" ".*$srcfile:$bp_1\r\n.*"
> +
> +gdb_test_no_output "record btrace"
> +
> +# We have two threads at or close to bp.1 but handled only one stop event.
> +# Remove the breakpoint so we do not need to deal with the 2nd event.
> +delete_breakpoints
> +gdb_breakpoint $srcfile:$bp_2
> +gdb_continue_to_breakpoint "continue to bp.2" ".*$srcfile:$bp_2\r\n.*"
> +
> +# Determine the thread that reported the breakpoint.
> +set thread [get_integer_valueof "\$_thread" bad]
> +
> +# Determine the other thread.
> +set other "bad"
> +if { $thread == 1 } {
> +    set other 2
> +} elseif { $thread == 2 } {
> +    set other 1
> +}
> +
> +# This test requires scheduler-locking 'on' or 'step'; 'replay' would
> +# implicitly stop replaying, avoiding the problem; 'off' would step one
> +# and resume the other.
> +#
> +# With the current record-btrace implementation that steps all resumed
> +# threads in lock-step, 'off' might actually pass but we don't want to
> +# bake that behavior into tests.
> +gdb_test_no_output "set scheduler-locking step"
> +
> +# Start replaying the other thread.  This will prevent stepping the thread
> +# that reported the event.
> +gdb_test "thread apply $other record goto begin"
> +gdb_test "thread apply $other info record" "Replay in progress.*"
> +
> +# We're at a breakpoint so this triggers step-over.  Since we're at the
> +# end of the trace, the step will fail.
> +gdb_test "stepi" "No more reverse-execution history.*" "stepi.1"
> +
> +# We used to hang at the second step since step-over insisted on polling
> +# the next event.
> +gdb_test "stepi" "No more reverse-execution history.*" "stepi.2"
> +
> +# Do one more just in case.
> +gdb_test "stepi" "No more reverse-execution history.*" "stepi.3"


-- 
Cheers,
Guinevere Larsen
She/Her/Hers


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 3/4] gdb, infrun, record: move no-history notification into normal_stop
  2024-03-12 11:34 ` [PATCH v4 3/4] gdb, infrun, record: move no-history notification into normal_stop Markus Metzger
@ 2024-04-04 18:57   ` Guinevere Larsen
  2024-04-05  9:16     ` Metzger, Markus T
  0 siblings, 1 reply; 16+ messages in thread
From: Guinevere Larsen @ 2024-04-04 18:57 UTC (permalink / raw)
  To: Markus Metzger, gdb-patches

On 3/12/24 08:34, Markus Metzger wrote:
> Leave calling gdb::observers::no_history.notify to normal_stop based on
> the last waitstatus.
I don't have a problem with this, but I don't really understand why it 
is needed. Do you see any thing that is an actual problem right now, or 
this is just cleanup?
> ---
>   gdb/infrun.c | 6 +++---
>   1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index eb5164b4066..f92f529412f 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -6538,8 +6538,6 @@ handle_inferior_event (struct execution_control_state *ecs)
>         if (handle_stop_requested (ecs))
>   	return;
>   
> -      interps_notify_no_history ();
> -
>         /* Cancel an in-flight step-over.  It will not succeed since we
>   	 won't be able to step at the end of the execution history.  */
>         {
> @@ -9002,7 +9000,6 @@ keep_going_pass_signal (struct execution_control_state *ecs)
>     if (ecs->event_thread->control.is_replaying
>         && !target_record_is_replaying (ecs->event_thread->ptid))
>       {
> -      interps_notify_no_history ();
>         ecs->ws.set_no_history ();
>         set_last_target_status (ecs->target, ecs->ptid, ecs->ws);
>         stop_print_frame = true;
> @@ -9642,6 +9639,9 @@ normal_stop ()
>     if (saved_context.changed ())
>       return true;
>   
> +  if (last.kind () == TARGET_WAITKIND_NO_HISTORY)
> +    interps_notify_no_history ();
> +
>     /* Notify observers about the stop.  This is where the interpreters
>        print the stop event.  */
>     notify_normal_stop ((inferior_ptid != null_ptid


^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history
  2024-04-04 18:03   ` Guinevere Larsen
@ 2024-04-05  9:12     ` Metzger, Markus T
  2024-04-05 12:53       ` Guinevere Larsen
  0 siblings, 1 reply; 16+ messages in thread
From: Metzger, Markus T @ 2024-04-05  9:12 UTC (permalink / raw)
  To: Guinevere Larsen; +Cc: gdb-patches

>> When trying to step over a breakpoint at the end of the trace, the
>> step-over will fail with no-history.  This does not clear step_over_info
>> so a subsequent resume will cause GDB to not resume the thread and
>expect
>> a SIGTRAP to complete the step-over.  This will never come causing GDB to
>> hang in the wait-for-event poll.
>>
>> That step-over failed after actually completing the step.  This is wrong.
>> The step-over itself should have failed and the step should not have
>> completed.  Fix it by moving the end of execution history check to before
>> we are stepping.
>>
>> This exposes another issue, however.  When completing a step-over at the
>> end of the execution history, we implicitly stop replaying that thread.  A
>> continue command would resume after the step-over and, since we're no
>> longer replaying, would continue recording.
>>
>> Fix that by recording the replay state in the thread's control state and
>> failing with no-history in keep_going if we're switching from replay to
>> recording.
>
>I am a little confused by your test case. From the commit message
>description, I expected the following GDB session to have been problematic:
>
>$ gdb record_goto
>(gdb) start
>49 fun4 ();
>(gdb) record btrace
>(gdb) break fun1
>(gdb) continue
>in frame fun1 ()
>23 }
>(gdb) reverse-next
>In frame fun4 ()
>41 fun1 ();
>(gdb) next
>No more reverse-execution history.
>23 }
>(gdb) next
># problems due to the SIGTRAP from the previous line
>
>(Pardon my hand written gdb session, my new system has an AMD cpu, so I
>seem to be locked out of btrace).
>
>My confusion comes from your test case never explicitly setting any
>breakpoints. I don't see why the inferior would have a sigtrap generated
>to test it. Am I misunderstanding the issue you described?

There are three tests contained in this patch.  Two of them do

>> +# We need to be replaying, otherwise, we'd just continue recording.
>> +gdb_test "reverse-stepi"
>> +gdb_test "break"
>> +
>> +# Continuing will step over the breakpoint and then run into the end of
>> +# the execution history.  This ends replay, so we can continue recording.
>> +gdb_test "continue" "No more reverse-execution history.*"
>> +gdb_continue_to_end

The third test makes sure that 'step n' doesn't suddenly start recording.

Regards,
Markus.

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCH v4 3/4] gdb, infrun, record: move no-history notification into normal_stop
  2024-04-04 18:57   ` Guinevere Larsen
@ 2024-04-05  9:16     ` Metzger, Markus T
  2024-04-05 15:51       ` Guinevere Larsen
  0 siblings, 1 reply; 16+ messages in thread
From: Metzger, Markus T @ 2024-04-05  9:16 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches

>> Leave calling gdb::observers::no_history.notify to normal_stop based on
>> the last waitstatus.
>I don't have a problem with this, but I don't really understand why it
>is needed. Do you see any thing that is an actual problem right now, or
>this is just cleanup?

This is to not change current behavior.

Markus.
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history
  2024-04-05  9:12     ` Metzger, Markus T
@ 2024-04-05 12:53       ` Guinevere Larsen
  0 siblings, 0 replies; 16+ messages in thread
From: Guinevere Larsen @ 2024-04-05 12:53 UTC (permalink / raw)
  To: Metzger, Markus T; +Cc: gdb-patches

On 4/5/24 06:12, Metzger, Markus T wrote:
>>> When trying to step over a breakpoint at the end of the trace, the
>>> step-over will fail with no-history.  This does not clear step_over_info
>>> so a subsequent resume will cause GDB to not resume the thread and
>> expect
>>> a SIGTRAP to complete the step-over.  This will never come causing GDB to
>>> hang in the wait-for-event poll.
>>>
>>> That step-over failed after actually completing the step.  This is wrong.
>>> The step-over itself should have failed and the step should not have
>>> completed.  Fix it by moving the end of execution history check to before
>>> we are stepping.
>>>
>>> This exposes another issue, however.  When completing a step-over at the
>>> end of the execution history, we implicitly stop replaying that thread.  A
>>> continue command would resume after the step-over and, since we're no
>>> longer replaying, would continue recording.
>>>
>>> Fix that by recording the replay state in the thread's control state and
>>> failing with no-history in keep_going if we're switching from replay to
>>> recording.
>> I am a little confused by your test case. From the commit message
>> description, I expected the following GDB session to have been problematic:
>>
>> $ gdb record_goto
>> (gdb) start
>> 49 fun4 ();
>> (gdb) record btrace
>> (gdb) break fun1
>> (gdb) continue
>> in frame fun1 ()
>> 23 }
>> (gdb) reverse-next
>> In frame fun4 ()
>> 41 fun1 ();
>> (gdb) next
>> No more reverse-execution history.
>> 23 }
>> (gdb) next
>> # problems due to the SIGTRAP from the previous line
>>
>> (Pardon my hand written gdb session, my new system has an AMD cpu, so I
>> seem to be locked out of btrace).
>>
>> My confusion comes from your test case never explicitly setting any
>> breakpoints. I don't see why the inferior would have a sigtrap generated
>> to test it. Am I misunderstanding the issue you described?
> There are three tests contained in this patch.  Two of them do

My bad, I should have said that no test set the breakpoint to the 
furthest point that is executed.

I didn't understand that the break should be in the previous 
instruction. Now that I'm rethinking my example, it doesn't really make 
sense, since as the testcase says, GDB wouldn't be replaying at that 
point. With this new understanding, this change LGTM.

Reviewed-By: Guinevere Larsen <blarsen@redhat.com>

-- 
Cheers,
Guinevere Larsen
She/Her/Hers

>
>>> +# We need to be replaying, otherwise, we'd just continue recording.
>>> +gdb_test "reverse-stepi"
>>> +gdb_test "break"
>>> +
>>> +# Continuing will step over the breakpoint and then run into the end of
>>> +# the execution history.  This ends replay, so we can continue recording.
>>> +gdb_test "continue" "No more reverse-execution history.*"
>>> +gdb_continue_to_end
> The third test makes sure that 'step n' doesn't suddenly start recording.
>
> Regards,
> Markus.
>
> Intel Deutschland GmbH
> Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
> Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
> Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Office: Munich
> Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 3/4] gdb, infrun, record: move no-history notification into normal_stop
  2024-04-05  9:16     ` Metzger, Markus T
@ 2024-04-05 15:51       ` Guinevere Larsen
  0 siblings, 0 replies; 16+ messages in thread
From: Guinevere Larsen @ 2024-04-05 15:51 UTC (permalink / raw)
  To: Metzger, Markus T, gdb-patches

On 4/5/24 06:16, Metzger, Markus T wrote:
>>> Leave calling gdb::observers::no_history.notify to normal_stop based on
>>> the last waitstatus.
>> I don't have a problem with this, but I don't really understand why it
>> is needed. Do you see any thing that is an actual problem right now, or
>> this is just cleanup?
> This is to not change current behavior.

That's fine, I was just curious. You can add my review tag, Reviewed-By: 
Guinevere Larsen <blarsen@redhat.com>

-- 
Cheers,
Guinevere Larsen
She/Her/Hers

>
> Markus.
> Intel Deutschland GmbH
> Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
> Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
> Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Office: Munich
> Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 16+ messages in thread

* RE: [PATCH v4 4/4] gdb, infrun: fix multi-threaded reverse stepping
  2024-04-03 20:27   ` Guinevere Larsen
@ 2024-04-09  9:14     ` Metzger, Markus T
  0 siblings, 0 replies; 16+ messages in thread
From: Metzger, Markus T @ 2024-04-09  9:14 UTC (permalink / raw)
  To: Guinevere Larsen; +Cc: gdb-patches

>I'm slowly making my way through this series, but this patch
>specifically seems to not apply at all. Trying to use git am --3way
>gives me the following output:
>
>Applying: gdb, infrun: fix multi-threaded reverse stepping
>error: sha1 information is lacking or useless (gdb/infrun.c).
>error: could not build fake ancestor
>Patch failed at 0001 gdb, infrun: fix multi-threaded reverse stepping

That is very strange.  I just did a 'git pull --rebase' without problems.

I also tried 'git am' on the patches I had sent out.  They do not apply
cleanly on current master but when I reset to an older commit, they
do apply cleanly.

I can try sending the version with the nit fixed rebased on current master.

Markus.

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2024-04-09  9:14 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-12 11:34 [PATCH v4 0/4] btrace: infrun fixes Markus Metzger
2024-03-12 11:34 ` [PATCH v4 1/4] gdb, infrun, btrace: fix reverse/replay stepping at end of execution history Markus Metzger
2024-03-12 11:56   ` Hannes Domani
2024-03-13 11:47     ` Hannes Domani
2024-04-04 18:03   ` Guinevere Larsen
2024-04-05  9:12     ` Metzger, Markus T
2024-04-05 12:53       ` Guinevere Larsen
2024-03-12 11:34 ` [PATCH v4 2/4] gdb, infrun, record: fix hang when step-over fails with no-history Markus Metzger
2024-04-04 18:27   ` Guinevere Larsen
2024-03-12 11:34 ` [PATCH v4 3/4] gdb, infrun, record: move no-history notification into normal_stop Markus Metzger
2024-04-04 18:57   ` Guinevere Larsen
2024-04-05  9:16     ` Metzger, Markus T
2024-04-05 15:51       ` Guinevere Larsen
2024-03-12 11:34 ` [PATCH v4 4/4] gdb, infrun: fix multi-threaded reverse stepping Markus Metzger
2024-04-03 20:27   ` Guinevere Larsen
2024-04-09  9:14     ` Metzger, Markus T

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).