[v2,7/8] fbsd-nat: Defer resume of inferiors.

Message ID 20230717192039.13976-8-jhb@FreeBSD.org
State New
Headers
Series Fixes for multiprocess for FreeBSD's native target |

Commit Message

John Baldwin July 17, 2023, 7:20 p.m. UTC
  For at least some tests in the testsuite, the core can resume multiple
individual threads belonging to the same process before waiting.  To
support this, change the resume method to record resumption requests
and defer resuming processes until either the commit_resumed target
method is called or until wait is called.

This requires tracking a set of resumed LWPs in the per-inferior
data rather than a single ptid_t to describe the set of resumed LWPs.
---
 gdb/fbsd-nat.c | 292 +++++++++++++++++++++++++++++++++----------------
 gdb/fbsd-nat.h |  12 +-
 2 files changed, 205 insertions(+), 99 deletions(-)
  

Patch

diff --git a/gdb/fbsd-nat.c b/gdb/fbsd-nat.c
index 195c4feb49a..f114f88e649 100644
--- a/gdb/fbsd-nat.c
+++ b/gdb/fbsd-nat.c
@@ -47,6 +47,8 @@ 
 #include "fbsd-nat.h"
 #include "fbsd-tdep.h"
 
+#include <unordered_set>
+
 #ifndef PT_GETREGSET
 #define	PT_GETREGSET	42	/* Get a target register set */
 #define	PT_SETREGSET	43	/* Set a target register set */
@@ -55,17 +57,52 @@ 
 /* Information stored about each inferior.  */
 struct fbsd_inferior_info
 {
-  /* Filter for resumed LWPs which can report events from wait.  */
-  ptid_t resumed_lwps = null_ptid;
+  /* Needs to be resumed.  */
+  bool resume_pending = false;
+
+  /* Either all threads in a process are resumed, a subset of threads
+     are resumed, or the entire process is stopped.  For the first
+     case, resumed is true and resumed_lwps is empty.  For the second
+     case, resumed is false and resumed_lwps contains the set of LWPs
+     resumed.  For the third case, resumed is false and resumed_lwps
+     is empty.  */
+  bool resumed_all_lwps = false;
+  std::unordered_set<lwpid_t> resumed_lwps;
+
+  /* Thread to step on next resume.  -1 if no thread to step.  */
+  lwpid_t stepping_lwp = -1;
+
+  /* Signal to send on next resume.  */
+  enum gdb_signal resume_signal = GDB_SIGNAL_0;
 
   /* Number of LWPs this process contains.  */
   unsigned int num_lwps = 0;
 
-  /* Number of LWPs currently running.  */
+  /* Number of resumed LWPs currently running.  */
   unsigned int running_lwps = 0;
 
   /* Have a pending SIGSTOP event that needs to be discarded.  */
   bool pending_sigstop = false;
+
+  /* Return true if a specific LWP has been resumed.  */
+  bool is_lwp_resumed (lwpid_t lwpid)
+  {
+    return (resumed_all_lwps || resumed_lwps.count (lwpid) != 0);
+  }
+
+  /* Return true if any LWPs in the process have been resumed.  */
+  bool is_process_resumed ()
+  {
+    return (resumed_all_lwps || !resumed_lwps.empty ());
+  }
+
+  /* Mark the process as stopped with no threads resumed or running.  */
+  void mark_stopped ()
+  {
+    resumed_all_lwps = false;
+    resumed_lwps.clear ();
+    running_lwps = 0;
+  }
 };
 
 /* Per-inferior data key.  */
@@ -103,7 +140,7 @@  fbsd_nat_target::take_pending_event (ptid_t filter)
       {
 	inferior *inf = find_inferior_ptid (this, it->ptid);
 	fbsd_inferior_info *info = fbsd_inferior_data.get (inf);
-	if (it->ptid.matches (info->resumed_lwps))
+	if (info->is_lwp_resumed (it->ptid.lwp ()))
 	  {
 	    pending_event event = *it;
 	    m_pending_events.erase (it);
@@ -798,7 +835,10 @@  show_fbsd_nat_debug (struct ui_file *file, int from_tty,
 #define fbsd_nat_debug_printf(fmt, ...) \
   debug_prefixed_printf_cond (debug_fbsd_nat, "fbsd-nat", fmt, ##__VA_ARGS__)
 
-#define fbsd_nat_debug_start_end(fmt, ...) \
+#define fbsd_nat_debug_enter_exit() \
+  scoped_debug_enter_exit (debug_fbsd_nat, "fbsd-nat")
+
+#define fbsd_nat_debug_start_end(fmt, ...)				\
   scoped_debug_start_end (debug_fbsd_nat, "fbsd-nat", fmt, ##__VA_ARGS__)
 
 /*
@@ -1177,11 +1217,11 @@  fbsd_add_vfork_done (ptid_t pid)
 #endif
 #endif
 
-/* Resume a single process.  */
+/* Mark a single process for resumption.  */
 
 void
-fbsd_nat_target::resume_one_process (ptid_t ptid, int step,
-				     enum gdb_signal signo)
+fbsd_nat_target::record_resume (ptid_t ptid, int step,
+				enum gdb_signal signo)
 {
   fbsd_nat_debug_printf ("[%s], step %d, signo %d (%s)",
 			 target_pid_to_str (ptid).c_str (), step, signo,
@@ -1189,80 +1229,39 @@  fbsd_nat_target::resume_one_process (ptid_t ptid, int step,
 
   inferior *inf = find_inferior_ptid (this, ptid);
   fbsd_inferior_info *info = fbsd_inferior_data.get (inf);
-  info->resumed_lwps = ptid;
   gdb_assert (info->running_lwps == 0);
 
-  /* Don't PT_CONTINUE a thread or process which has a pending event.  */
-  if (have_pending_event (ptid))
+  if (!info->resume_pending)
     {
-      fbsd_nat_debug_printf ("found pending event");
-      return;
+      info->stepping_lwp = -1;
+      info->resume_signal = GDB_SIGNAL_0;
+      info->resume_pending = true;
     }
 
-  for (thread_info *tp : inf->non_exited_threads ())
+  if (ptid.lwp_p ())
     {
-      int request;
-
-      /* If ptid is a specific LWP, suspend all other LWPs in the
-	 process, otherwise resume all LWPs in the process..  */
-      if (!ptid.lwp_p() || tp->ptid.lwp () == ptid.lwp ())
-	{
-	  if (ptrace (PT_RESUME, tp->ptid.lwp (), NULL, 0) == -1)
-	    perror_with_name (("ptrace (PT_RESUME)"));
-	  low_prepare_to_resume (tp);
-	  info->running_lwps++;
-	}
-      else
-	{
-	  if (ptrace (PT_SUSPEND, tp->ptid.lwp (), NULL, 0) == -1)
-	    perror_with_name (("ptrace (PT_SUSPEND)"));
-	}
-    }
-
-  if (ptid.pid () != inferior_ptid.pid ())
-    {
-      step = 0;
-      signo = GDB_SIGNAL_0;
-      gdb_assert (!ptid.lwp_p ());
+      gdb_assert (!info->is_lwp_resumed (ptid.lwp ()));
+      info->resumed_lwps.insert (ptid.lwp ());
     }
   else
     {
-      ptid = inferior_ptid;
-#if __FreeBSD_version < 1200052
-      /* When multiple threads within a process wish to report STOPPED
-	 events from wait(), the kernel picks one thread event as the
-	 thread event to report.  The chosen thread event is retrieved
-	 via PT_LWPINFO by passing the process ID as the request pid.
-	 If multiple events are pending, then the subsequent wait()
-	 after resuming a process will report another STOPPED event
-	 after resuming the process to handle the next thread event
-	 and so on.
+      gdb_assert (!info->is_process_resumed ());
+      info->resumed_all_lwps = true;
+    }
 
-	 A single thread event is cleared as a side effect of resuming
-	 the process with PT_CONTINUE, PT_STEP, etc.  In older
-	 kernels, however, the request pid was used to select which
-	 thread's event was cleared rather than always clearing the
-	 event that was just reported.  To avoid clearing the event of
-	 the wrong LWP, always pass the process ID instead of an LWP
-	 ID to PT_CONTINUE or PT_SYSCALL.
-
-	 In the case of stepping, the process ID cannot be used with
-	 PT_STEP since it would step the thread that reported an event
-	 which may not be the thread indicated by PTID.  For stepping,
-	 use PT_SETSTEP to enable stepping on the desired thread
-	 before resuming the process via PT_CONTINUE instead of using
-	 PT_STEP.  */
+  if (ptid.pid () == inferior_ptid.pid ())
+    {
       if (step)
 	{
-	  if (ptrace (PT_SETSTEP, get_ptrace_pid (ptid), NULL, 0) == -1)
-	    perror_with_name (("ptrace (PT_SETSTEP)"));
-	  step = 0;
+	  gdb_assert (info->stepping_lwp == -1);
+	  info->stepping_lwp = inferior_ptid.lwp ();
+	}
+      if (signo != GDB_SIGNAL_0)
+	{
+	  gdb_assert (info->resume_signal == GDB_SIGNAL_0);
+	  info->resume_signal = signo;
 	}
-      ptid = ptid_t (ptid.pid ());
-#endif
     }
-
-  inf_ptrace_target::resume (ptid, step, signo);
 }
 
 /* Implement the "resume" target_ops method.  */
@@ -1280,14 +1279,94 @@  fbsd_nat_target::resume (ptid_t scope_ptid, int step, enum gdb_signal signo)
   if (scope_ptid == minus_one_ptid)
     {
       for (inferior *inf : all_non_exited_inferiors (this))
-	resume_one_process (ptid_t (inf->pid), step, signo);
+	record_resume (ptid_t (inf->pid), step, signo);
     }
   else
     {
-      resume_one_process (scope_ptid, step, signo);
+      record_resume (scope_ptid, step, signo);
     }
 }
 
+/* Resume a single process.  */
+
+void
+fbsd_nat_target::resume_one_process (inferior *inf)
+{
+  fbsd_inferior_info *info = fbsd_inferior_data.get (inf);
+
+  if (!info->resume_pending)
+    return;
+  info->resume_pending = false;
+
+  ptid_t ptid (inf->pid);
+  fbsd_nat_debug_printf ("[%s], step LWP %d, signo %d (%s)",
+			 target_pid_to_str (ptid).c_str (),
+			 info->stepping_lwp, info->resume_signal,
+			 gdb_signal_to_name (info->resume_signal));
+
+  /* Enable stepping if requested.  Note that if a different resumed
+     thread in the process has a pending event the step might not
+     occur until a future resume.  */
+  if (info->stepping_lwp != -1)
+    {
+      gdb_assert (!have_pending_event (ptid_t (inf->pid, info->stepping_lwp)));
+      if (ptrace (PT_SETSTEP, info->stepping_lwp, NULL, 0) == -1)
+	perror_with_name (("ptrace (PT_SETSTEP)"));
+    }
+
+  /* Don't PT_CONTINUE a thread or process which has a pending event.  */
+  if (info->resumed_all_lwps)
+    {
+      if (have_pending_event (ptid))
+	{
+	  fbsd_nat_debug_printf ("found pending event");
+	  gdb_assert (info->resume_signal == GDB_SIGNAL_0);
+	  return;
+	}
+    }
+  else
+    for (lwpid_t lwp : info->resumed_lwps)
+      {
+	if (have_pending_event (ptid_t (inf->pid, lwp)))
+	  {
+	    fbsd_nat_debug_printf ("found pending event");
+	    gdb_assert (info->resume_signal == GDB_SIGNAL_0);
+	    return;
+	  }
+      }
+
+  gdb_assert (info->running_lwps == 0);
+
+  /* Suspend or resume individual threads.  */
+  for (thread_info *tp : inf->non_exited_threads ())
+    {
+      if (info->is_lwp_resumed (tp->ptid.lwp ()))
+	{
+	  if (ptrace (PT_RESUME, tp->ptid.lwp (), NULL, 0) == -1)
+	    perror_with_name (("ptrace (PT_RESUME)"));
+	  low_prepare_to_resume (tp);
+	  info->running_lwps++;
+	}
+      else
+	{
+	  if (ptrace (PT_SUSPEND, tp->ptid.lwp (), NULL, 0) == -1)
+	    perror_with_name (("ptrace (PT_SUSPEND)"));
+	}
+    }
+
+  inf_ptrace_target::resume (ptid, 0, info->resume_signal);
+}
+
+/* Implement the "commit_resumed" target_ops method.  */
+
+void
+fbsd_nat_target::commit_resumed ()
+{
+  fbsd_nat_debug_enter_exit ();
+  for (inferior *inf : all_non_exited_inferiors (this))
+    resume_one_process (inf);
+}
+
 #ifdef USE_SIGTRAP_SIGINFO
 /* Handle breakpoint and trace traps reported via SIGTRAP.  If the
    trap was a breakpoint or trace trap that should be reported to the
@@ -1397,18 +1476,23 @@  fbsd_nat_target::wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
 		  delete_thread (thr);
 		  info->num_lwps--;
 
-		  /* If this LWP was the only resumed LWP from the
-		     process, report an event to the core.  */
-		  if (wptid == info->resumed_lwps)
+		  /* During process exit LWPs that were suspended will
+		     report exit events which should be quietly
+		     ignored.  Only update accounting for exit events
+		     on resumed LWPs. */
+		  if (info->is_lwp_resumed (pl.pl_lwpid))
 		    {
-		      ourstatus->set_spurious ();
-		      return wptid;
-		    }
+		      /* If this LWP was the only resumed LWP from the
+			 process still running, report an event to the
+			 core.  */
+		      if (info->running_lwps == 1)
+			{
+			  ourstatus->set_spurious ();
+			  return wptid;
+			}
 
-		  /* During process exit LWPs that were not resumed
-		     will report exit events.  */
-		  if (wptid.matches (info->resumed_lwps))
-		    info->running_lwps--;
+		      info->running_lwps--;
+		    }
 		}
 	      if (ptrace (PT_CONTINUE, pid, (caddr_t) 1, 0) == -1)
 		perror_with_name (("ptrace (PT_CONTINUE)"));
@@ -1443,7 +1527,7 @@  fbsd_nat_target::wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
 		  add_thread (this, wptid);
 		  info->num_lwps++;
 
-		  if (wptid.matches(info->resumed_lwps))
+		  if (info->resumed_all_lwps)
 		    info->running_lwps++;
 		}
 	      ourstatus->set_spurious ();
@@ -1588,9 +1672,16 @@  fbsd_nat_target::stop_process (inferior *inf)
   fbsd_inferior_info *info = fbsd_inferior_data.get (inf);
   gdb_assert (info != nullptr);
 
-  info->resumed_lwps = null_ptid;
   if (info->running_lwps == 0)
-    return;
+    {
+      /* If a resume is pending cancel it.  The process might be
+	 resumed even without any running LWPs if it had a pending
+	 event when it was resumed.  */
+      info->resume_pending = false;
+      info->mark_stopped ();
+      return;
+    }
+  gdb_assert (info->resume_pending == false);
 
   ptid_t ptid (inf->pid);
   target_waitstatus status;
@@ -1600,7 +1691,7 @@  fbsd_nat_target::stop_process (inferior *inf)
     {
       /* Save the current event as a pending event.  */
       add_pending_event (wptid, status);
-      info->running_lwps = 0;
+      info->mark_stopped ();
       return;
     }
 
@@ -1653,7 +1744,7 @@  fbsd_nat_target::stop_process (inferior *inf)
       info->pending_sigstop = true;
       break;
     }
-  info->running_lwps = 0;
+  info->mark_stopped ();
 }
 
 ptid_t
@@ -1663,6 +1754,15 @@  fbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
   fbsd_nat_debug_printf ("[%s], [%s]", target_pid_to_str (ptid).c_str (),
 			 target_options_to_string (target_options).c_str ());
 
+  /* Resume any pending processes with a pending signal even if we are
+     going to return a pending event so as to not lose the signal.  */
+  for (inferior *inf : all_non_exited_inferiors (this))
+    {
+      fbsd_inferior_info *info = fbsd_inferior_data.get (inf);
+      if (info->resume_pending && info->resume_signal != GDB_SIGNAL_0)
+	resume_one_process (inf);
+    }
+
   /* If there is a valid pending event, return it.  */
   gdb::optional<pending_event> event = take_pending_event (ptid);
   if (event.has_value ())
@@ -1683,6 +1783,9 @@  fbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
   if (is_async_p ())
     async_file_flush ();
 
+  /* Resume any pending processes.  */
+  commit_resumed ();
+
   ptid_t wptid;
   while (1)
     {
@@ -1697,14 +1800,14 @@  fbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
       gdb_assert (inf != nullptr);
       fbsd_inferior_info *info = fbsd_inferior_data.get (inf);
       gdb_assert (info != nullptr);
-      gdb_assert (info->resumed_lwps != null_ptid);
+      gdb_assert (info->is_process_resumed ());
       gdb_assert (info->running_lwps > 0);
 
       /* If an event is reported for a thread or process while
 	 stepping some other thread, suspend the thread reporting the
 	 event and defer the event until it can be reported to the
 	 core.  */
-      if (!wptid.matches (info->resumed_lwps))
+      if (!info->is_lwp_resumed (wptid.lwp ()))
 	{
 	  add_pending_event (wptid, *ourstatus);
 	  fbsd_nat_debug_printf ("deferring event [%s], [%s]",
@@ -1718,8 +1821,7 @@  fbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
 	}
 
       /* This process is no longer running.  */
-      info->resumed_lwps = null_ptid;
-      info->running_lwps = 0;
+      info->mark_stopped ();
 
       /* Stop any other inferiors currently running.  */
       for (inferior *inf : all_non_exited_inferiors (this))
@@ -1830,7 +1932,7 @@  fbsd_nat_target::create_inferior (const char *exec_file,
 #endif
 
   fbsd_inferior_info *info = fbsd_inferior_data.emplace (current_inferior ());
-  info->resumed_lwps = minus_one_ptid;
+  info->resumed_all_lwps = true;
   info->num_lwps = 1;
   info->running_lwps = 1;
   inf_ptrace_target::create_inferior (exec_file, allargs, env, from_tty);
@@ -1840,7 +1942,7 @@  void
 fbsd_nat_target::attach (const char *args, int from_tty)
 {
   fbsd_inferior_info *info = fbsd_inferior_data.emplace (current_inferior ());
-  info->resumed_lwps = minus_one_ptid;
+  info->resumed_all_lwps = true;
   info->num_lwps = 1;
   info->running_lwps = 1;
   inf_ptrace_target::attach (args, from_tty);
@@ -1889,12 +1991,12 @@  fbsd_nat_target::detach_fork_children (inferior *inf)
   for (thread_info *tp : inf->non_exited_threads ())
     detach_fork_children (tp);
 
-  /* Unwind state associated with any pending events.  Reset
-     info->resumed_lwps so that take_pending_event will harvest
+  /* Unwind state associated with any pending events.  Set
+     info->resumed_all_lwps so that take_pending_event will harvest
      events.  */
   fbsd_inferior_info *info = fbsd_inferior_data.get (inf);
   ptid_t ptid = ptid_t (inf->pid);
-  info->resumed_lwps = ptid;
+  info->resumed_all_lwps = true;
 
   while (1)
     {
@@ -2030,7 +2132,7 @@  fbsd_nat_target::detach (inferior *inf, int from_tty)
       info->pending_sigstop = false;
 
       /* Report event for all threads from wait_1.  */
-      info->resumed_lwps = ptid;
+      info->resumed_all_lwps = true;
 
       do
 	{
diff --git a/gdb/fbsd-nat.h b/gdb/fbsd-nat.h
index 7016cc0242a..b149217b71b 100644
--- a/gdb/fbsd-nat.h
+++ b/gdb/fbsd-nat.h
@@ -89,6 +89,8 @@  class fbsd_nat_target : public inf_ptrace_target
 
   void resume (ptid_t, int, enum gdb_signal) override;
 
+  void commit_resumed () override;
+
   ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
 
   void post_attach (int) override;
@@ -143,7 +145,9 @@  class fbsd_nat_target : public inf_ptrace_target
 private:
   ptid_t wait_1 (ptid_t, struct target_waitstatus *, target_wait_flags);
 
-  void resume_one_process (ptid_t, int, enum gdb_signal);
+  void record_resume (ptid_t, int, enum gdb_signal);
+
+  void resume_one_process (inferior *);
 
   void stop_process (inferior *);
 
@@ -258,9 +262,9 @@  class fbsd_nat_target : public inf_ptrace_target
 
   bool have_pending_event (ptid_t filter);
 
-  /* Check if there is a pending event for a resumed process matching
-     FILTER.  If there is a matching event, the event is removed from
-     the pending list and returned.  */
+  /* Check if there is a pending event for a resumed thread or process
+     matching FILTER.  If there is a matching event, the event is
+     removed from the pending list and returned.  */
 
   gdb::optional<pending_event> take_pending_event (ptid_t filter);