public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 00/25] Step over thread clone and thread exit
@ 2022-06-20 22:53 Pedro Alves
  2022-06-20 22:53 ` [PATCH 01/25] Don't use pthread_mutex_t in gdb.base/step-over-clone.c Pedro Alves
                   ` (24 more replies)
  0 siblings, 25 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:53 UTC (permalink / raw)
  To: gdb-patches

Hi!

This is a new series that replaces two different series from last
year.

The first is this series Simon and I wrote, here:

  [PATCH 00/10] Step over thread exit (PR gdb/27338)
  https://sourceware.org/pipermail/gdb-patches/2021-July/180567.html

The other is a series that coincidentally, back then, Andrew posted at
about the same time, and that addressed problems in kind of the mirror
scenario.  His patch series was about stepping over clone (creating
new threads), instead of stepping over thread exit:

  [PATCH 0/3] Stepping over clone syscall
  https://sourceware.org/pipermail/gdb-patches/2021-June/180517.html

My & Simon's solution back then involved adding a new contract between
GDB and GDBserver -- if a thread is single stepping, and it exits, the
server was supposed to report back the thread's exit to GDB.  One of
the motivations for this approach was to be able to control the
enablement of thread exit events per thread, to avoid creating
thread-exit event traffic unnecessarily, as done by
target_thread_events()/QThreadEvents.

Andrew's solution envolves using the QThreadEvents mechanism, which
tells the server to report thread create and thread exit events for
all threads.  This would conflict with the desire to avoid unnecessary
traffic in the step over thread exit series.

The step over clone fixes back then also weren't yet fully complete,
as Andrew's series only addressed inline step overs.  Fixing displaced
stepping over clone syscall would still remain broken.

This new series fixes all of stepping over thread exit and clone, for
both of displaced stepping and inline step overs.  It:

- Merges both Andrew's and my/Simon's series, and then reworks both
  parts in different ways.

- Introduces clone events at the GDB core and remote protocol level.

- Gets rid of the idea of "reporting thread exit if thread is
  single-stepping", replaces it by a new mechanism GDB can use to
  explicitly enable thread clone and/or thread exit events, and other
  events in the future.  The old mechanism also only worked when the
  remote server supported hardware single-stepping.  This new approach
  has an advantage of also working on software single-step targets.

- Uses the new clone events to fix displaced stepping over clone
  syscalls too.

- Addresses an issue that Andrew aluded to in his series, and that
  coincidentally, we/AMD also ran into with AMDGPU debugging --
  currently, with "set scheduler-locking on", if you step over a
  function that spawns a thread, that thread runs free, for a bit at
  least, and then may stop or not, basically in an unspecified manner.

- Addresses Simon's review comments on the original "Step over thread
  exit" series referenced above.

There are documentation changes in the following patches:

   [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
   [PATCH 25/25] Document remote clone events, and QThreadOptions packet

I'm aware that Tankut also has patches addressing issues around
reading registers of already-exited processes, but I haven't looked at
them in any detail yet.  So I guess patch #22 ("Ignore failure to read
PC when resuming") may end up changing or be replaced by Tankut's.

Tested on x86-64 Ubuntu 20.04, native and gdbserver.

Andrew Burgess (1):
  Add test for stepping over clone syscall

Pedro Alves (22):
  Don't use pthread_mutex_t in gdb.base/step-over-clone.c
  displaced step: pass down target_waitstatus instead of gdb_signal
  linux-nat: introduce pending_status_str
  Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED
  Support clone events in the remote protocol
  Thread options & clone events (core + remote)
  Thread options & clone events (native Linux)
  Thread options & clone events (Linux GDBserver)
  gdbserver: Hide and don't detach pending clone children
  Remove gdb/19675 kfails (displaced stepping + clone)
  all-stop/synchronous RSP support thread-exit events
  Introduce GDB_TO_EXIT thread option, fix step-over-thread-exit
  Implement GDB_TO_EXIT support for Linux GDBserver
  Implement GDB_TO_EXIT support for native Linux
  stop_all_threads: (re-)enable async before waiting for stops
  gdbserver: Queue no-resumed event after thread exit
  Don't resume new threads if scheduler-locking is in effect
  Tighten gdb.threads/no-unwaited-for-left.exp regexps
  Report thread exit event for leader if reporting thread exit events
  Ignore failure to read PC when resuming
  gdb/testsuite/lib/my-syscalls.S: Refactor new SYSCALL macro
  Document remote clone events, and QThreadOptions packet

Simon Marchi (2):
  gdb: clear step over information on thread exit (PR gdb/27338)
  Testcases for stepping over thread exit syscall (PR gdb/27338)

 gdb/NEWS                                      |  22 +
 gdb/displaced-stepping.c                      |  18 +-
 gdb/displaced-stepping.h                      |   2 +-
 gdb/doc/gdb.texinfo                           | 133 ++++-
 gdb/gdbarch-components.py                     |   6 +-
 gdb/gdbarch-gen.h                             |  10 +-
 gdb/gdbarch.c                                 |   4 +-
 gdb/gdbthread.h                               |  19 +
 gdb/infrun.c                                  | 510 ++++++++++++++----
 gdb/linux-nat.c                               | 383 ++++++++-----
 gdb/linux-nat.h                               |   5 +
 gdb/linux-tdep.c                              |   5 +-
 gdb/linux-tdep.h                              |   2 +-
 gdb/process-stratum-target.c                  |   4 +-
 gdb/remote.c                                  | 217 +++++++-
 gdb/target-debug.h                            |   2 +
 gdb/target-delegates.c                        |  78 +++
 gdb/target.c                                  |  41 ++
 gdb/target.h                                  |  17 +
 gdb/target/target.h                           |  14 +
 gdb/target/waitstatus.c                       |   1 +
 gdb/target/waitstatus.h                       |  20 +-
 gdb/testsuite/gdb.base/step-over-clone.c      |  69 ++-
 gdb/testsuite/gdb.base/step-over-syscall.exp  |  51 +-
 .../gdb.threads/no-unwaited-for-left.exp      |   4 +-
 .../gdb.threads/schedlock-new-thread.c        |  46 ++
 .../gdb.threads/schedlock-new-thread.exp      |  63 +++
 ...-over-thread-exit-while-stop-all-threads.c |  77 +++
 ...ver-thread-exit-while-stop-all-threads.exp |  69 +++
 .../gdb.threads/step-over-thread-exit.c       |  52 ++
 .../gdb.threads/step-over-thread-exit.exp     | 122 +++++
 gdb/testsuite/gdb.threads/stepi-over-clone.c  |  90 ++++
 .../gdb.threads/stepi-over-clone.exp          | 392 ++++++++++++++
 gdb/testsuite/lib/my-syscalls.S               |  54 +-
 gdb/testsuite/lib/my-syscalls.h               |   5 +
 gdbserver/gdbthread.h                         |   3 +
 gdbserver/linux-low.cc                        | 403 ++++++++------
 gdbserver/linux-low.h                         |  56 +-
 gdbserver/remote-utils.cc                     |  26 +-
 gdbserver/server.cc                           | 152 +++++-
 gdbserver/target.cc                           |  15 +-
 gdbserver/target.h                            |  30 +-
 42 files changed, 2733 insertions(+), 559 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/schedlock-new-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/schedlock-new-thread.exp
 create mode 100644 gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.c
 create mode 100644 gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.exp
 create mode 100644 gdb/testsuite/gdb.threads/step-over-thread-exit.c
 create mode 100644 gdb/testsuite/gdb.threads/step-over-thread-exit.exp
 create mode 100644 gdb/testsuite/gdb.threads/stepi-over-clone.c
 create mode 100644 gdb/testsuite/gdb.threads/stepi-over-clone.exp


base-commit: 0d02e70b197c786f26175b9a73f94e01d14abdab
-- 
2.36.0


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

* [PATCH 01/25] Don't use pthread_mutex_t in gdb.base/step-over-clone.c
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
@ 2022-06-20 22:53 ` Pedro Alves
  2022-07-13 21:35   ` Pedro Alves
  2022-06-20 22:53 ` [PATCH 02/25] displaced step: pass down target_waitstatus instead of gdb_signal Pedro Alves
                   ` (23 subsequent siblings)
  24 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:53 UTC (permalink / raw)
  To: gdb-patches

I noticed this in gdb.log after running gdb.base/step-over-clone.exp:

 ...
 gdbserver: PID mismatch!  Expected 1790818, got 1790817
 gdbserver: Cannot find thread after clone.
 gdbserver: PID mismatch!  Expected 1790819, got 1790817
 gdbserver: Cannot find thread after clone.
 gdbserver: PID mismatch!  Expected 1790820, got 1790817
 gdbserver: Cannot find thread after clone.
 ...

Those "PID mismatch" come from gdbserver/thread_db.c.  The problem is
that the testcase program is testing raw clone, which bypasses
libpthread entirely and leaves libthread_db confused.  The testcase is
linking with pthreads because it wants to use pthread_mutex_t for
synchronization between the clones.  Mixing pthreads and raw clone is
just something we shouldn't do, however.

My first thought was to fix this by using an atomic decrement
(__atomic_fetch_sub) instead of a mutex, for synchronization.
However, on some archs, that may require linking with -latomic, which
can itself pull in libpthread.

My next idea, is to make each thread write to its own "I'm ready"
variable, such that we can't actually have read-modify-write races.
This is what this patch does.

Change-Id: Id418978ac86bfa6d51d0af1e1625a86cdd039a20
---
 gdb/testsuite/gdb.base/step-over-clone.c     | 69 +++++++++++++-------
 gdb/testsuite/gdb.base/step-over-syscall.exp |  7 +-
 2 files changed, 45 insertions(+), 31 deletions(-)

diff --git a/gdb/testsuite/gdb.base/step-over-clone.c b/gdb/testsuite/gdb.base/step-over-clone.c
index c0f67af188b..8a56b492e5e 100644
--- a/gdb/testsuite/gdb.base/step-over-clone.c
+++ b/gdb/testsuite/gdb.base/step-over-clone.c
@@ -19,7 +19,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <sched.h>
-#include <pthread.h>
+#include <signal.h>
 
 static void
 marker ()
@@ -27,30 +27,55 @@ marker ()
 
 #define STACK_SIZE 0x1000
 
-/* These are used to signal that the threads have started correctly.  The
-   GLOBAL_THREAD_COUNT is set to the number of threads in main, then
-   decremented (under a lock) in each new thread.  */
-pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;
-int global_thread_count = 0;
+#define NUM_THREADS 6
+
+/* This is used to signal that the threads have started correctly.  We
+   can't use a single global updated by all thread guarded by a
+   pthread mutex, or anything pthread related for the matter, since we
+   are using raw clone.  A single global updated with atomics
+   (__atomic_fetch* etc.) instead of a pthread mutex would sound
+   appealing, but we avoid that too because for some architectures,
+   we'd have to link with -latomic, which itself links with
+   -lpthread...  So instead have one array with one element per
+   thread, and each thread only ever writes to its own array element.
+   We make the array have sig_atomic_t elements so that the elements
+   are portably naturally aligned and free from data races on all
+   archs, when different threads write to different elements.  In
+   practice, "int" would work too, as accesses to int are pretty much
+   garanteed to be atomic on all Linux systems, but sig_atomic_t is
+   explicit.  */
+volatile sig_atomic_t thread_started[NUM_THREADS];
 
 static int
-clone_fn (void *unused)
+clone_fn (void *started)
 {
   /* Signal that this thread has started correctly.  */
-  if (pthread_mutex_lock (&global_lock) != 0)
-    abort ();
-  global_thread_count--;
-  if (pthread_mutex_unlock (&global_lock) != 0)
-    abort ();
+  *(volatile sig_atomic_t *) started = 1;
 
   return 0;
 }
 
+/* Return true if all threads have started.  */
+
+static int
+all_threads_started (void)
+{
+  int i;
+
+  /* Force full memory barrier so that caches are flushed and
+     THREAD_STARTED is refetched.  */
+  __sync_synchronize ();
+  for (i = 0; i < NUM_THREADS; i++)
+    if (thread_started[i] == 0)
+      return 0;
+  return 1;
+}
+
 int
 main (void)
 {
   int i, pid;
-  unsigned char *stack[6];
+  unsigned char *stack[NUM_THREADS];
 
   /* Due to bug gdb/19675 the cloned thread _might_ try to reenter main
      (this depends on where the displaced instruction is placed for
@@ -62,18 +87,16 @@ main (void)
   else
     abort ();
 
-  for (i = 0; i < (sizeof (stack) / sizeof (stack[0])); i++)
+  for (i = 0; i < NUM_THREADS; i++)
     stack[i] = malloc (STACK_SIZE);
 
-  global_thread_count = (sizeof (stack) / sizeof (stack[0]));
-
-  for (i = 0; i < (sizeof (stack) / sizeof (stack[0])); i++)
+  for (i = 0; i < NUM_THREADS; i++)
     {
       pid = clone (clone_fn, stack[i] + STACK_SIZE, CLONE_FILES | CLONE_VM,
-		   NULL);
+		   (void *) &thread_started[i]);
     }
 
-  for (i = 0; i < (sizeof (stack) / sizeof (stack[0])); i++)
+  for (i = 0; i < NUM_THREADS; i++)
     free (stack[i]);
 
   /* Set an alarm so we don't end up stuck waiting for threads that might
@@ -81,12 +104,8 @@ main (void)
   alarm (120);
 
   /* Now wait for all the threads to start up.  */
-  while (global_thread_count != 0)
-    {
-      /* Force memory barrier so GLOBAL_THREAD_COUNT will be refetched.  */
-      asm volatile ("" ::: "memory");
-      sleep (1);
-    }
+  while (!all_threads_started ())
+    sleep (1);
 
   /* Call marker, this is what GDB is waiting for.  */
   marker ();
diff --git a/gdb/testsuite/gdb.base/step-over-syscall.exp b/gdb/testsuite/gdb.base/step-over-syscall.exp
index 788f6e3f5d0..e87d391cd5f 100644
--- a/gdb/testsuite/gdb.base/step-over-syscall.exp
+++ b/gdb/testsuite/gdb.base/step-over-syscall.exp
@@ -241,12 +241,7 @@ proc step_over_syscall { syscall } {
 
 	set testfile "step-over-$syscall"
 
-	set options [list debug]
-	if { $syscall == "clone" } {
-	    lappend options "pthreads"
-	}
-
-	if [build_executable ${testfile}.exp ${testfile} ${testfile}.c $options] {
+	if [build_executable ${testfile}.exp ${testfile} ${testfile}.c {debug}] {
 	    untested "failed to compile"
 	    return -1
 	}
-- 
2.36.0


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

* [PATCH 02/25] displaced step: pass down target_waitstatus instead of gdb_signal
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
  2022-06-20 22:53 ` [PATCH 01/25] Don't use pthread_mutex_t in gdb.base/step-over-clone.c Pedro Alves
@ 2022-06-20 22:53 ` Pedro Alves
  2022-06-20 22:53 ` [PATCH 03/25] linux-nat: introduce pending_status_str Pedro Alves
                   ` (22 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:53 UTC (permalink / raw)
  To: gdb-patches

This commit tweaks displaced_step_finish & friends to pass down a
target_waitstatus instead of a gdb_signal.  This needed because a
patch later in the series will want to make
displaced_step_buffers::finish handle TARGET_WAITKIND_THREAD_EXITED.
It also helps with the TARGET_WAITKIND_THREAD_CLONED patch.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
Change-Id: I4c5d338647b028071bc498c4e47063795a2db4c0
---
 gdb/displaced-stepping.c  | 11 ++++++-----
 gdb/displaced-stepping.h  |  2 +-
 gdb/gdbarch-components.py |  2 +-
 gdb/gdbarch-gen.h         |  4 ++--
 gdb/gdbarch.c             |  4 ++--
 gdb/infrun.c              | 17 +++++++----------
 gdb/linux-tdep.c          |  5 +++--
 gdb/linux-tdep.h          |  2 +-
 8 files changed, 23 insertions(+), 24 deletions(-)

diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
index eac2c5dab94..83080cf6bdd 100644
--- a/gdb/displaced-stepping.c
+++ b/gdb/displaced-stepping.c
@@ -174,10 +174,11 @@ write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr,
 }
 
 static bool
-displaced_step_instruction_executed_successfully (gdbarch *arch,
-						  gdb_signal signal)
+displaced_step_instruction_executed_successfully
+  (gdbarch *arch, const target_waitstatus &status)
 {
-  if (signal != GDB_SIGNAL_TRAP)
+  if (status.kind() == TARGET_WAITKIND_STOPPED
+      && status.sig () != GDB_SIGNAL_TRAP)
     return false;
 
   if (target_stopped_by_watchpoint ())
@@ -192,7 +193,7 @@ displaced_step_instruction_executed_successfully (gdbarch *arch,
 
 displaced_step_finish_status
 displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
-				gdb_signal sig)
+				const target_waitstatus &status)
 {
   gdb_assert (thread->displaced_step_state.in_progress ());
 
@@ -238,7 +239,7 @@ displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
   regcache *rc = get_thread_regcache (thread);
 
   bool instruction_executed_successfully
-    = displaced_step_instruction_executed_successfully (arch, sig);
+    = displaced_step_instruction_executed_successfully (arch, status);
 
   if (instruction_executed_successfully)
     {
diff --git a/gdb/displaced-stepping.h b/gdb/displaced-stepping.h
index de40ae2f3d8..e23a8d6736b 100644
--- a/gdb/displaced-stepping.h
+++ b/gdb/displaced-stepping.h
@@ -168,7 +168,7 @@ struct displaced_step_buffers
 					 CORE_ADDR &displaced_pc);
 
   displaced_step_finish_status finish (gdbarch *arch, thread_info *thread,
-				       gdb_signal sig);
+				       const target_waitstatus &status);
 
   const displaced_step_copy_insn_closure *
     copy_insn_closure_by_addr (CORE_ADDR addr);
diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
index fc10e8600ba..5d391aa7dc0 100644
--- a/gdb/gdbarch-components.py
+++ b/gdb/gdbarch-components.py
@@ -1743,7 +1743,7 @@ Clean up after a displaced step of THREAD.
 """,
     type="displaced_step_finish_status",
     name="displaced_step_finish",
-    params=[("thread_info *", "thread"), ("gdb_signal", "sig")],
+    params=[("thread_info *", "thread"), ("const target_waitstatus &", "ws")],
     predefault="NULL",
     invalid="(! gdbarch->displaced_step_finish) != (! gdbarch->displaced_step_prepare)",
 )
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index ddcb4c55615..221dd84008c 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1032,8 +1032,8 @@ extern void set_gdbarch_displaced_step_prepare (struct gdbarch *gdbarch, gdbarch
 
 /* Clean up after a displaced step of THREAD. */
 
-typedef displaced_step_finish_status (gdbarch_displaced_step_finish_ftype) (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig);
-extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig);
+typedef displaced_step_finish_status (gdbarch_displaced_step_finish_ftype) (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws);
+extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws);
 extern void set_gdbarch_displaced_step_finish (struct gdbarch *gdbarch, gdbarch_displaced_step_finish_ftype *displaced_step_finish);
 
 /* Return the closure associated to the displaced step buffer that is at ADDR. */
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index 68ef0480219..4ab7628c60a 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -4056,13 +4056,13 @@ set_gdbarch_displaced_step_prepare (struct gdbarch *gdbarch,
 }
 
 displaced_step_finish_status
-gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, gdb_signal sig)
+gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws)
 {
   gdb_assert (gdbarch != NULL);
   gdb_assert (gdbarch->displaced_step_finish != NULL);
   if (gdbarch_debug >= 2)
     gdb_printf (gdb_stdlog, "gdbarch_displaced_step_finish called\n");
-  return gdbarch->displaced_step_finish (gdbarch, thread, sig);
+  return gdbarch->displaced_step_finish (gdbarch, thread, ws);
 }
 
 void
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 02c98b50c8c..db2828628f7 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1826,7 +1826,8 @@ displaced_step_prepare (thread_info *thread)
    DISPLACED_STEP_FINISH_STATUS_OK as well.  */
 
 static displaced_step_finish_status
-displaced_step_finish (thread_info *event_thread, enum gdb_signal signal)
+displaced_step_finish (thread_info *event_thread,
+		       const target_waitstatus &event_status)
 {
   displaced_step_thread_state *displaced = &event_thread->displaced_step_state;
 
@@ -1848,7 +1849,7 @@ displaced_step_finish (thread_info *event_thread, enum gdb_signal signal)
   /* Do the fixup, and release the resources acquired to do the displaced
      step. */
   return gdbarch_displaced_step_finish (displaced->get_original_gdbarch (),
-					event_thread, signal);
+					event_thread, event_status);
 }
 
 /* Data to be passed around while handling an event.  This data is
@@ -4997,7 +4998,7 @@ handle_one (const wait_one_event &event)
 	  /* We caught the event that we intended to catch, so
 	     there's no event to save as pending.  */
 
-	  if (displaced_step_finish (t, GDB_SIGNAL_0)
+	  if (displaced_step_finish (t, event.ws)
 	      == DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED)
 	    {
 	      /* Add it back to the step-over queue.  */
@@ -5012,7 +5013,6 @@ handle_one (const wait_one_event &event)
 	}
       else
 	{
-	  enum gdb_signal sig;
 	  struct regcache *regcache;
 
 	  infrun_debug_printf
@@ -5023,10 +5023,7 @@ handle_one (const wait_one_event &event)
 	  /* Record for later.  */
 	  save_waitstatus (t, event.ws);
 
-	  sig = (event.ws.kind () == TARGET_WAITKIND_STOPPED
-		 ? event.ws.sig () : GDB_SIGNAL_0);
-
-	  if (displaced_step_finish (t, sig)
+	  if (displaced_step_finish (t, event.ws)
 	      == DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED)
 	    {
 	      /* Add it back to the step-over queue.  */
@@ -5629,7 +5626,7 @@ handle_inferior_event (struct execution_control_state *ecs)
 	       has been done.  Perform cleanup for parent process here.  Note
 	       that this operation also cleans up the child process for vfork,
 	       because their pages are shared.  */
-	    displaced_step_finish (ecs->event_thread, GDB_SIGNAL_TRAP);
+	    displaced_step_finish (ecs->event_thread, ecs->ws);
 	    /* Start a new step-over in another thread if there's one
 	       that needs it.  */
 	    start_step_over ();
@@ -5998,7 +5995,7 @@ resumed_thread_with_pending_status (struct thread_info *tp,
 static int
 finish_step_over (struct execution_control_state *ecs)
 {
-  displaced_step_finish (ecs->event_thread, ecs->event_thread->stop_signal ());
+  displaced_step_finish (ecs->event_thread, ecs->ws);
 
   bool had_step_over_info = step_over_info_valid_p ();
 
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 4e728a06e7e..259c260395f 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -2593,13 +2593,14 @@ linux_displaced_step_prepare (gdbarch *arch, thread_info *thread,
 /* See linux-tdep.h.  */
 
 displaced_step_finish_status
-linux_displaced_step_finish (gdbarch *arch, thread_info *thread, gdb_signal sig)
+linux_displaced_step_finish (gdbarch *arch, thread_info *thread,
+			     const target_waitstatus &status)
 {
   linux_info *per_inferior = get_linux_inferior_data (thread->inf);
 
   gdb_assert (per_inferior->disp_step_bufs.has_value ());
 
-  return per_inferior->disp_step_bufs->finish (arch, thread, sig);
+  return per_inferior->disp_step_bufs->finish (arch, thread, status);
 }
 
 /* See linux-tdep.h.  */
diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h
index bb907f2c8f3..104218c5fa7 100644
--- a/gdb/linux-tdep.h
+++ b/gdb/linux-tdep.h
@@ -72,7 +72,7 @@ extern displaced_step_prepare_status linux_displaced_step_prepare
 /* Implementation of gdbarch_displaced_step_finish.  */
 
 extern displaced_step_finish_status linux_displaced_step_finish
-  (gdbarch *arch, thread_info *thread, gdb_signal sig);
+  (gdbarch *arch, thread_info *thread, const target_waitstatus &status);
 
 /* Implementation of gdbarch_displaced_step_copy_insn_closure_by_addr.  */
 
-- 
2.36.0


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

* [PATCH 03/25] linux-nat: introduce pending_status_str
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
  2022-06-20 22:53 ` [PATCH 01/25] Don't use pthread_mutex_t in gdb.base/step-over-clone.c Pedro Alves
  2022-06-20 22:53 ` [PATCH 02/25] displaced step: pass down target_waitstatus instead of gdb_signal Pedro Alves
@ 2022-06-20 22:53 ` Pedro Alves
  2022-06-20 22:53 ` [PATCH 04/25] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED Pedro Alves
                   ` (21 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:53 UTC (permalink / raw)
  To: gdb-patches

I noticed that some debug log output printing an lwp's pending status
wasn't considering lp->waitstatus.  This fixes it, by introducing a
new pending_status_str function.

Change-Id: I66e5c7a363d30a925b093b195d72925ce5b6b980
---
 gdb/linux-nat.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index b9164e621db..1a6ae7b44c0 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -255,6 +255,17 @@ is_leader (lwp_info *lp)
   return lp->ptid.pid () == lp->ptid.lwp ();
 }
 
+/* Convert an LWP's pending status to a std::string.  */
+
+static std::string
+pending_status_str (lwp_info *lp)
+{
+  if (lp->waitstatus.kind () != TARGET_WAITKIND_IGNORE)
+    return lp->waitstatus.to_string ();
+  else
+    return status_to_str (lp->status);
+}
+
 \f
 /* LWP accessors.  */
 
@@ -1647,8 +1658,8 @@ linux_nat_target::resume (ptid_t scope_ptid, int step, enum gdb_signal signo)
 	 this thread with a signal?  */
       gdb_assert (signo == GDB_SIGNAL_0);
 
-      linux_nat_debug_printf ("Short circuiting for status 0x%x",
-			      lp->status);
+      linux_nat_debug_printf ("Short circuiting for status %s",
+			      pending_status_str (lp).c_str ());
 
       if (target_can_async_p ())
 	{
@@ -3137,7 +3148,7 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
   if (lp != NULL)
     {
       linux_nat_debug_printf ("Using pending wait status %s for %s.",
-			      status_to_str (lp->status).c_str (),
+			      pending_status_str (lp).c_str (),
 			      lp->ptid.to_string ().c_str ());
     }
 
-- 
2.36.0


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

* [PATCH 04/25] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (2 preceding siblings ...)
  2022-06-20 22:53 ` [PATCH 03/25] linux-nat: introduce pending_status_str Pedro Alves
@ 2022-06-20 22:53 ` Pedro Alves
  2022-06-20 22:53 ` [PATCH 05/25] Support clone events in the remote protocol Pedro Alves
                   ` (20 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:53 UTC (permalink / raw)
  To: gdb-patches

(A good chunk of the problem statement in the commit log below is
Andrew's, adjusted for a different solution, and for covering
displaced stepping too.)

This commit addresses bugs gdb/19675 and gdb/27830, which are about
stepping over a breakpoint set at a clone syscall instruction, one is
about displaced stepping, and the other about in-line stepping.

Currently, when a new thread is created through a clone syscall, GDB
sets the new thread running.  With 'continue' this makes sense
(assuming no schedlock):

 - all-stop mode, user issues 'continue', all threads are set running,
   a newly created thread should also be set running.

 - non-stop mode, user issues 'continue', other pre-existing threads
   are not effected, but as the new thread is (sort-of) a child of the
   thread the user asked to run, it makes sense that the new threads
   should be created in the running state.

Similarly, if we are stopped at the clone syscall, and there's no
software breakpoint at this address, then the current behaviour is
fine:

 - all-stop mode, user issues 'stepi', stepping will be done in place
   (as there's no breakpoint to step over).  While stepping the thread
   of interest all the other threads will be allowed to continue.  A
   newly created thread will be set running, and then stopped once the
   thread of interest has completed its step.

 - non-stop mode, user issues 'stepi', stepping will be done in place
   (as there's no breakpoint to step over).  Other threads might be
   running or stopped, but as with the continue case above, the new
   thread will be created running.  The only possible issue here is
   that the new thread will be left running after the initial thread
   has completed its stepi.  The user would need to manually select
   the thread and interrupt it, this might not be what the user
   expects.  However, this is not something this commit tries to
   change.

The problem then is what happens when we try to step over a clone
syscall if there is a breakpoint at the syscall address.

- For both all-stop and non-stop modes, with in-line stepping:

   + user issues 'stepi',
   + [non-stop mode only] GDB stops all threads.  In all-stop mode all
     threads are already stopped.
   + GDB removes s/w breakpoint at syscall address,
   + GDB single steps just the thread of interest, all other threads
     are left stopped,
   + New thread is created running,
   + Initial thread completes its step,
   + [non-stop mode only] GDB resumes all threads that it previously
     stopped.

There are two problems in the in-line stepping scenario above:

  1. The new thread might pass through the same code that the initial
     thread is in (i.e. the clone syscall code), in which case it will
     fail to hit the breakpoint in clone as this was removed so the
     first thread can single step,

  2. The new thread might trigger some other stop event before the
     initial thread reports its step completion.  If this happens we
     end up triggering an assertion as GDB assumes that only the
     thread being stepped should stop.  The assert looks like this:

     infrun.c:5899: internal-error: int finish_step_over(execution_control_state*): Assertion `ecs->event_thread->control.trap_expected' failed.

- For both all-stop and non-stop modes, with displaced stepping:

   + user issues 'stepi',
   + GDB starts the displaced step, moves thread's PC to the
     out-of-line scratch pad, maybe adjusts registers,
   + GDB single steps the thread of interest, [non-stop mode only] all
     other threads are left as they were, either running or stopped.
     In all-stop, all other threads are left stopped.
   + New thread is created running,
   + Initial thread completes its step, GDB re-adjusts its PC,
     restores/releases scratchpad,
   + [non-stop mode only] GDB resumes the thread, now past its
     breakpoint.
   + [all-stop mode only] GDB resumes all threads.

There is one problem with the displaced stepping scenario above:

  3. When the parent thread completed its step, GDB adjusted its PC,
     but did not adjust the child's PC, thus that new child thread
     will continue execution in the scratch pad, invoking undefined
     behavior.  If you're lucky, you see a crash.  If unlucky, the
     inferior gets silently corrupted.

What is needed is for GDB to have more control over whether the new
thread is created running or not.  Issue #1 above requires that the
new thread not be allowed to run until the breakpoint has been
reinserted.  The only way to guarantee this is if the new thread is
held in a stopped state until the single step has completed.  Issue #3
above requires that GDB is informed of when a thread clones itself,
and of what is the child's ptid, so that GDB can fixup both the parent
and the child.

When looking for solutions to this problem I considered how GDB
handles fork/vfork as these have some of the same issues.  The main
difference between fork/vfork and clone is that the clone events are
not reported back to core GDB.  Instead, the clone event is handled
automatically in the target code and the child thread is immediately
set running.

Note we have support for requesting thread creation events out of the
target (TARGET_WAITKIND_THREAD_CREATED).  However, those are reported
for the new/child thread.  That would be sufficient to address in-line
stepping (issue #1), but not for displaced-stepping (issue #3).  To
handle displaced-stepping, we need an event that is reported to the
_parent_ of the clone, as the information about the displaced step is
associated with the clone parent.  TARGET_WAITKIND_THREAD_CREATED
includes no indication of which thread is the parent that spawned the
new child.  In fact, for some targets, like e.g., Windows, it would be
impossible to know which thread that was, as thread creation there
doesn't work by "cloning".

The solution implemented here is to model clone on fork/vfork, and
introduce a new TARGET_WAITKIND_THREAD_CLONED event.  This event is
similar to TARGET_WAITKIND_FORKED and TARGET_WAITKIND_VFORKED, except
that we end up with a new thread in the same process, instead of a new
thread of a new process.  Like FORKED and VFORKED, THREAD_CLONED
waitstatuses have a child_ptid property, and the child is help stopped
until GDB explicitly resumes it.  This addresses the in-line stepping
case (issues #1 and #2).

The infrun code that handles displaced stepping fixup for the child
after a fork/vfork event is thus reused for THREAD_CLONE, with some
minimal conditions added, addressing the displaced stepping case
(issue #3).

The native Linux backend is adjusted to unconditionally report
TARGET_WAITKIND_THREAD_CLONED events to the core.

Following the follow_fork model in core GDB, we introduce a
target_follow_clone target method, which is responsible for making the
new clone child visible to the rest of GDB.

Subsequent patches will add clone events support to the remote
protocol and gdbserver.

A testcase will be added by a later patch.

displaced_step_in_progress_thread is removed in this patch, but it is
added back again in a subsequent patch.  We need to do this because
the function is static, and with no callers, the compiler would warn,
(error with -Werror), breaking the build.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830

Change-Id: I474e9a7015dd3d33469e322a5764ae83f8a32787
---
 gdb/infrun.c            | 171 ++++++++++++-------------
 gdb/linux-nat.c         | 269 ++++++++++++++++++++++------------------
 gdb/linux-nat.h         |   2 +
 gdb/target-delegates.c  |  24 ++++
 gdb/target.c            |   8 ++
 gdb/target.h            |   2 +
 gdb/target/waitstatus.c |   1 +
 gdb/target/waitstatus.h |  20 ++-
 8 files changed, 293 insertions(+), 204 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index db2828628f7..9f09373c1db 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1512,16 +1512,6 @@ step_over_info_valid_p (void)
    displaced step operation on it.  See displaced_step_prepare and
    displaced_step_finish for details.  */
 
-/* Return true if THREAD is doing a displaced step.  */
-
-static bool
-displaced_step_in_progress_thread (thread_info *thread)
-{
-  gdb_assert (thread != NULL);
-
-  return thread->displaced_step_state.in_progress ();
-}
-
 /* Return true if INF has a thread doing a displaced step.  */
 
 static bool
@@ -1829,6 +1819,31 @@ static displaced_step_finish_status
 displaced_step_finish (thread_info *event_thread,
 		       const target_waitstatus &event_status)
 {
+  /* Check whether the parent is displaced stepping.  */
+  struct regcache *regcache = get_thread_regcache (event_thread);
+  struct gdbarch *gdbarch = regcache->arch ();
+  inferior *parent_inf = event_thread->inf;
+
+  /* If this was a fork/vfork/clone, this event indicates that the
+     displaced stepping of the syscall instruction has been done, so
+     we perform cleanup for parent here.  Also note that this
+     operation also cleans up the child for vfork, because their pages
+     are shared.  */
+
+  /* If this is a fork (child gets its own address space copy) and
+     some displaced step buffers were in use at the time of the fork,
+     restore the displaced step buffer bytes in the child process.
+
+     Architectures which support displaced stepping and fork events
+     must supply an implementation of
+     gdbarch_displaced_step_restore_all_in_ptid.  This is not enforced
+     during gdbarch validation to support architectures which support
+     displaced stepping but not forks.  */
+  if (event_status.kind () == TARGET_WAITKIND_FORKED
+      && gdbarch_supports_displaced_stepping (gdbarch))
+    gdbarch_displaced_step_restore_all_in_ptid
+      (gdbarch, parent_inf, event_status.child_ptid ());
+
   displaced_step_thread_state *displaced = &event_thread->displaced_step_state;
 
   /* Was this thread performing a displaced step?  */
@@ -1848,8 +1863,39 @@ displaced_step_finish (thread_info *event_thread,
 
   /* Do the fixup, and release the resources acquired to do the displaced
      step. */
-  return gdbarch_displaced_step_finish (displaced->get_original_gdbarch (),
-					event_thread, event_status);
+  displaced_step_finish_status status
+    = gdbarch_displaced_step_finish (displaced->get_original_gdbarch (),
+				     event_thread, event_status);
+
+  if (event_status.kind () == TARGET_WAITKIND_FORKED
+      || event_status.kind () == TARGET_WAITKIND_VFORKED
+      || event_status.kind () == TARGET_WAITKIND_THREAD_CLONED)
+    {
+      /* Since the vfork/fork/clone syscall instruction was executed
+	 in the scratchpad, the child's PC is also within the
+	 scratchpad.  Set the child's PC to the parent's PC value,
+	 which has already been fixed up.  Note: we use the parent's
+	 aspace here, although we're touching the child, because the
+	 child hasn't been added to the inferior list yet at this
+	 point.  */
+
+      struct regcache *child_regcache
+	= get_thread_arch_aspace_regcache (parent_inf->process_target (),
+					   event_status.child_ptid (),
+					   gdbarch,
+					   parent_inf->aspace);
+      /* Read PC value of parent.  */
+      CORE_ADDR parent_pc = regcache_read_pc (regcache);
+
+      displaced_debug_printf ("write child pc from %s to %s",
+			      paddress (gdbarch,
+					regcache_read_pc (child_regcache)),
+			      paddress (gdbarch, parent_pc));
+
+      regcache_write_pc (child_regcache, parent_pc);
+    }
+
+  return status;
 }
 
 /* Data to be passed around while handling an event.  This data is
@@ -5013,8 +5059,6 @@ handle_one (const wait_one_event &event)
 	}
       else
 	{
-	  struct regcache *regcache;
-
 	  infrun_debug_printf
 	    ("target_wait %s, saving status for %s",
 	     event.ws.to_string ().c_str (),
@@ -5032,7 +5076,7 @@ handle_one (const wait_one_event &event)
 		global_thread_step_over_chain_enqueue (t);
 	    }
 
-	  regcache = get_thread_regcache (t);
+	  struct regcache *regcache = get_thread_regcache (t);
 	  t->set_stop_pc (regcache_read_pc (regcache));
 
 	  infrun_debug_printf ("saved stop_pc=%s for %s "
@@ -5593,67 +5637,13 @@ handle_inferior_event (struct execution_control_state *ecs)
 
     case TARGET_WAITKIND_FORKED:
     case TARGET_WAITKIND_VFORKED:
-      /* Check whether the inferior is displaced stepping.  */
-      {
-	struct regcache *regcache = get_thread_regcache (ecs->event_thread);
-	struct gdbarch *gdbarch = regcache->arch ();
-	inferior *parent_inf = find_inferior_ptid (ecs->target, ecs->ptid);
-
-	/* If this is a fork (child gets its own address space copy)
-	   and some displaced step buffers were in use at the time of
-	   the fork, restore the displaced step buffer bytes in the
-	   child process.
-
-	   Architectures which support displaced stepping and fork
-	   events must supply an implementation of
-	   gdbarch_displaced_step_restore_all_in_ptid.  This is not
-	   enforced during gdbarch validation to support architectures
-	   which support displaced stepping but not forks.  */
-	if (ecs->ws.kind () == TARGET_WAITKIND_FORKED
-	    && gdbarch_supports_displaced_stepping (gdbarch))
-	  gdbarch_displaced_step_restore_all_in_ptid
-	    (gdbarch, parent_inf, ecs->ws.child_ptid ());
-
-	/* If displaced stepping is supported, and thread ecs->ptid is
-	   displaced stepping.  */
-	if (displaced_step_in_progress_thread (ecs->event_thread))
-	  {
-	    struct regcache *child_regcache;
-	    CORE_ADDR parent_pc;
-
-	    /* GDB has got TARGET_WAITKIND_FORKED or TARGET_WAITKIND_VFORKED,
-	       indicating that the displaced stepping of syscall instruction
-	       has been done.  Perform cleanup for parent process here.  Note
-	       that this operation also cleans up the child process for vfork,
-	       because their pages are shared.  */
-	    displaced_step_finish (ecs->event_thread, ecs->ws);
-	    /* Start a new step-over in another thread if there's one
-	       that needs it.  */
-	    start_step_over ();
-
-	    /* Since the vfork/fork syscall instruction was executed in the scratchpad,
-	       the child's PC is also within the scratchpad.  Set the child's PC
-	       to the parent's PC value, which has already been fixed up.
-	       FIXME: we use the parent's aspace here, although we're touching
-	       the child, because the child hasn't been added to the inferior
-	       list yet at this point.  */
-
-	    child_regcache
-	      = get_thread_arch_aspace_regcache (parent_inf->process_target (),
-						 ecs->ws.child_ptid (),
-						 gdbarch,
-						 parent_inf->aspace);
-	    /* Read PC value of parent process.  */
-	    parent_pc = regcache_read_pc (regcache);
-
-	    displaced_debug_printf ("write child pc from %s to %s",
-				    paddress (gdbarch,
-					      regcache_read_pc (child_regcache)),
-				    paddress (gdbarch, parent_pc));
-
-	    regcache_write_pc (child_regcache, parent_pc);
-	  }
-      }
+    case TARGET_WAITKIND_THREAD_CLONED:
+
+      displaced_step_finish (ecs->event_thread, ecs->ws);
+
+      /* Start a new step-over in another thread if there's one that
+	 needs it.  */
+      start_step_over ();
 
       context_switch (ecs);
 
@@ -5669,7 +5659,7 @@ handle_inferior_event (struct execution_control_state *ecs)
 	 need to unpatch at follow/detach time instead to be certain
 	 that new breakpoints added between catchpoint hit time and
 	 vfork follow are detached.  */
-      if (ecs->ws.kind () != TARGET_WAITKIND_VFORKED)
+      if (ecs->ws.kind () == TARGET_WAITKIND_FORKED)
 	{
 	  /* This won't actually modify the breakpoint list, but will
 	     physically remove the breakpoints from the child.  */
@@ -5701,14 +5691,24 @@ handle_inferior_event (struct execution_control_state *ecs)
       if (!bpstat_causes_stop (ecs->event_thread->control.stop_bpstat))
 	{
 	  bool follow_child
-	    = (follow_fork_mode_string == follow_fork_mode_child);
+	    = (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
+	       && follow_fork_mode_string == follow_fork_mode_child);
 
 	  ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
 
 	  process_stratum_target *targ
 	    = ecs->event_thread->inf->process_target ();
 
-	  bool should_resume = follow_fork ();
+	  bool should_resume;
+	  if (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED)
+	    should_resume = follow_fork ();
+	  else
+	    {
+	      should_resume = true;
+	      inferior *inf = ecs->event_thread->inf;
+	      inf->top_target ()->follow_clone (ecs->ws.child_ptid ());
+	      ecs->event_thread->pending_follow.set_spurious ();
+	    }
 
 	  /* Note that one of these may be an invalid pointer,
 	     depending on detach_fork.  */
@@ -5719,16 +5719,21 @@ handle_inferior_event (struct execution_control_state *ecs)
 	     child is marked stopped.  */
 
 	  /* If not resuming the parent, mark it stopped.  */
-	  if (follow_child && !detach_fork && !non_stop && !sched_multi)
+	  if (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
+	      && follow_child && !detach_fork && !non_stop && !sched_multi)
 	    parent->set_running (false);
 
 	  /* If resuming the child, mark it running.  */
-	  if (follow_child || (!detach_fork && (non_stop || sched_multi)))
+	  if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
+	      || (follow_child || (!detach_fork && (non_stop || sched_multi))))
 	    child->set_running (true);
 
 	  /* In non-stop mode, also resume the other branch.  */
-	  if (!detach_fork && (non_stop
-			       || (sched_multi && target_is_non_stop_p ())))
+	  if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
+	       && target_is_non_stop_p ())
+	      || (!detach_fork && (non_stop
+				   || (sched_multi
+				       && target_is_non_stop_p ()))))
 	    {
 	      if (follow_child)
 		switch_to_thread (parent);
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 1a6ae7b44c0..138632667e4 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1284,64 +1284,98 @@ get_detach_signal (struct lwp_info *lp)
   return 0;
 }
 
-/* Detach from LP.  If SIGNO_P is non-NULL, then it points to the
-   signal number that should be passed to the LWP when detaching.
-   Otherwise pass any pending signal the LWP may have, if any.  */
+/* Return true if WS is a fork, vfork or clone event.  */
 
-static void
-detach_one_lwp (struct lwp_info *lp, int *signo_p)
+static bool
+is_fork_clone (const target_waitstatus &ws)
 {
-  int lwpid = lp->ptid.lwp ();
-  int signo;
-
-  gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
+  return (ws.kind () == TARGET_WAITKIND_FORKED
+	  || ws.kind () == TARGET_WAITKIND_VFORKED
+	  || ws.kind () == TARGET_WAITKIND_THREAD_CLONED);
+}
 
-  /* If the lwp/thread we are about to detach has a pending fork event,
-     there is a process GDB is attached to that the core of GDB doesn't know
-     about.  Detach from it.  */
+/* If LP has a pending fork/vfork/clone status, store it in WS and
+   return true.  Otherwise, return false.  */
 
+static bool
+get_pending_child_status (lwp_info *lp, target_waitstatus *ws)
+{
   /* Check in lwp_info::status.  */
   if (WIFSTOPPED (lp->status) && linux_is_extended_waitstatus (lp->status))
     {
       int event = linux_ptrace_get_extended_event (lp->status);
 
-      if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
+      if (event == PTRACE_EVENT_FORK
+	  || event == PTRACE_EVENT_VFORK
+	  || event == PTRACE_EVENT_CLONE)
 	{
 	  unsigned long child_pid;
 	  int ret = ptrace (PTRACE_GETEVENTMSG, lp->ptid.lwp (), 0, &child_pid);
 	  if (ret == 0)
-	    detach_one_pid (child_pid, 0);
+	    {
+	      if (event == PTRACE_EVENT_FORK)
+		ws->set_forked (ptid_t (child_pid, child_pid));
+	      else if (event == PTRACE_EVENT_VFORK)
+		ws->set_vforked (ptid_t (child_pid, child_pid));
+	      else if (event == PTRACE_EVENT_CLONE)
+		ws->set_thread_cloned (ptid_t (lp->ptid.pid (), child_pid));
+	      else
+		gdb_assert_not_reached ("unhandled");
+
+	      return true;
+	    }
 	  else
-	    perror_warning_with_name (_("Failed to detach fork child"));
+	    {
+	      perror_warning_with_name (_("Failed to retrieve event msg"));
+	      return false;
+	    }
 	}
     }
 
   /* Check in lwp_info::waitstatus.  */
-  if (lp->waitstatus.kind () == TARGET_WAITKIND_VFORKED
-      || lp->waitstatus.kind () == TARGET_WAITKIND_FORKED)
-    detach_one_pid (lp->waitstatus.child_ptid ().pid (), 0);
+  if (is_fork_clone (lp->waitstatus))
+    {
+      *ws = lp->waitstatus;
+      return true;
+    }
 
+  thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
 
   /* Check in thread_info::pending_waitstatus.  */
-  thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
-  if (tp->has_pending_waitstatus ())
+  if (tp->has_pending_waitstatus ()
+      && is_fork_clone (tp->pending_waitstatus ()))
     {
-      const target_waitstatus &ws = tp->pending_waitstatus ();
-
-      if (ws.kind () == TARGET_WAITKIND_VFORKED
-	  || ws.kind () == TARGET_WAITKIND_FORKED)
-	detach_one_pid (ws.child_ptid ().pid (), 0);
+      *ws = tp->pending_waitstatus ();
+      return true;
     }
 
   /* Check in thread_info::pending_follow.  */
-  if (tp->pending_follow.kind () == TARGET_WAITKIND_VFORKED
-      || tp->pending_follow.kind () == TARGET_WAITKIND_FORKED)
-    detach_one_pid (tp->pending_follow.child_ptid ().pid (), 0);
+  if (is_fork_clone (tp->pending_follow))
+    {
+      *ws = tp->pending_follow;
+      return true;
+    }
 
-  if (lp->status != 0)
-    linux_nat_debug_printf ("Pending %s for %s on detach.",
-			    strsignal (WSTOPSIG (lp->status)),
-			    lp->ptid.to_string ().c_str ());
+  return false;
+}
+
+/* Detach from LP.  If SIGNO_P is non-NULL, then it points to the
+   signal number that should be passed to the LWP when detaching.
+   Otherwise pass any pending signal the LWP may have, if any.  */
+
+static void
+detach_one_lwp (struct lwp_info *lp, int *signo_p)
+{
+  int lwpid = lp->ptid.lwp ();
+  int signo;
+
+  /* If the lwp/thread we are about to detach has a pending fork/clone
+     event, there is a process/thread GDB is attached to that the core
+     of GDB doesn't know about.  Detach from it.  */
+
+  target_waitstatus ws;
+  if (get_pending_child_status (lp, &ws))
+    detach_one_pid (ws.child_ptid ().lwp (), 0);
 
   /* If there is a pending SIGSTOP, get rid of it.  */
   if (lp->signalled)
@@ -1819,6 +1853,58 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
   return 1;
 }
 
+void
+linux_nat_target::follow_clone (ptid_t child_ptid)
+{
+  linux_nat_debug_printf
+    ("Got clone event from LWP %ld, new child is LWP %ld",
+     inferior_ptid.lwp (), child_ptid.lwp ());
+
+  lwp_info *new_lp = add_lwp (child_ptid);
+  new_lp->stopped = 1;
+
+  /* If the thread_db layer is active, let it record the user
+     level thread id and status, and add the thread to GDB's
+     list.  */
+  if (!thread_db_notice_clone (inferior_ptid, new_lp->ptid))
+    {
+      /* The process is not using thread_db.  Add the LWP to
+	 GDB's list.  */
+      target_post_attach (new_lp->ptid.lwp ());
+      add_thread (linux_target, new_lp->ptid);
+    }
+
+  /* We just created NEW_LP so it cannot yet contain STATUS.  */
+  gdb_assert (new_lp->status == 0);
+
+  if (!pull_pid_from_list (&stopped_pids, child_ptid.lwp (), &new_lp->status))
+    internal_error (__FILE__, __LINE__, _("no saved status for clone lwp"));
+
+  if (WSTOPSIG (new_lp->status) != SIGSTOP)
+    {
+      /* This can happen if someone starts sending signals to
+	 the new thread before it gets a chance to run, which
+	 have a lower number than SIGSTOP (e.g. SIGUSR1).
+	 This is an unlikely case, and harder to handle for
+	 fork / vfork than for clone, so we do not try - but
+	 we handle it for clone events here.  */
+
+      new_lp->signalled = 1;
+
+      /* Save the wait status to report later.  */
+      linux_nat_debug_printf
+	("waitpid of new LWP %ld, saving status %s",
+	 (long) new_lp->ptid.lwp (), status_to_str (new_lp->status).c_str ());
+    }
+  else
+    {
+      new_lp->status = 0;
+
+      if (report_thread_events)
+	new_lp->waitstatus.set_thread_created ();
+    }
+}
+
 /* Handle a GNU/Linux extended wait response.  If we see a clone
    event, we need to add the new LWP to our list (and not report the
    trap to higher layers).  This function returns non-zero if the
@@ -1861,11 +1947,9 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
 			    _("wait returned unexpected status 0x%x"), status);
 	}
 
-      ptid_t child_ptid (new_pid, new_pid);
-
       if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
 	{
-	  open_proc_mem_file (child_ptid);
+	  open_proc_mem_file (ptid_t (new_pid, new_pid));
 
 	  /* The arch-specific native code may need to know about new
 	     forks even if those end up never mapped to an
@@ -1902,67 +1986,15 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
 	}
 
       if (event == PTRACE_EVENT_FORK)
-	ourstatus->set_forked (child_ptid);
+	ourstatus->set_forked (ptid_t (new_pid, new_pid));
       else if (event == PTRACE_EVENT_VFORK)
-	ourstatus->set_vforked (child_ptid);
+	ourstatus->set_vforked (ptid_t (new_pid, new_pid));
       else if (event == PTRACE_EVENT_CLONE)
 	{
-	  struct lwp_info *new_lp;
-
-	  ourstatus->set_ignore ();
-
-	  linux_nat_debug_printf
-	    ("Got clone event from LWP %d, new child is LWP %ld", pid, new_pid);
-
-	  new_lp = add_lwp (ptid_t (lp->ptid.pid (), new_pid));
-	  new_lp->stopped = 1;
-	  new_lp->resumed = 1;
+	  /* Save the status again, we'll use it in follow_clone.  */
+	  add_to_pid_list (&stopped_pids, new_pid, status);
 
-	  /* If the thread_db layer is active, let it record the user
-	     level thread id and status, and add the thread to GDB's
-	     list.  */
-	  if (!thread_db_notice_clone (lp->ptid, new_lp->ptid))
-	    {
-	      /* The process is not using thread_db.  Add the LWP to
-		 GDB's list.  */
-	      target_post_attach (new_lp->ptid.lwp ());
-	      add_thread (linux_target, new_lp->ptid);
-	    }
-
-	  /* Even if we're stopping the thread for some reason
-	     internal to this module, from the perspective of infrun
-	     and the user/frontend, this new thread is running until
-	     it next reports a stop.  */
-	  set_running (linux_target, new_lp->ptid, true);
-	  set_executing (linux_target, new_lp->ptid, true);
-
-	  if (WSTOPSIG (status) != SIGSTOP)
-	    {
-	      /* This can happen if someone starts sending signals to
-		 the new thread before it gets a chance to run, which
-		 have a lower number than SIGSTOP (e.g. SIGUSR1).
-		 This is an unlikely case, and harder to handle for
-		 fork / vfork than for clone, so we do not try - but
-		 we handle it for clone events here.  */
-
-	      new_lp->signalled = 1;
-
-	      /* We created NEW_LP so it cannot yet contain STATUS.  */
-	      gdb_assert (new_lp->status == 0);
-
-	      /* Save the wait status to report later.  */
-	      linux_nat_debug_printf
-		("waitpid of new LWP %ld, saving status %s",
-		 (long) new_lp->ptid.lwp (), status_to_str (status).c_str ());
-	      new_lp->status = status;
-	    }
-	  else if (report_thread_events)
-	    {
-	      new_lp->waitstatus.set_thread_created ();
-	      new_lp->status = status;
-	    }
-
-	  return 1;
+	  ourstatus->set_thread_cloned (ptid_t (lp->ptid.pid (), new_pid));
 	}
 
       return 0;
@@ -3519,59 +3551,56 @@ kill_wait_callback (struct lwp_info *lp)
   return 0;
 }
 
-/* Kill the fork children of any threads of inferior INF that are
-   stopped at a fork event.  */
+/* Kill the fork/clone child of LP if it has an unfollowed child.  */
 
-static void
-kill_unfollowed_fork_children (struct inferior *inf)
+static int
+kill_unfollowed_child_callback (lwp_info *lp)
 {
-  for (thread_info *thread : inf->non_exited_threads ())
+  target_waitstatus ws;
+  if (get_pending_child_status (lp, &ws))
     {
-      struct target_waitstatus *ws = &thread->pending_follow;
-
-      if (ws->kind () == TARGET_WAITKIND_FORKED
-	  || ws->kind () == TARGET_WAITKIND_VFORKED)
-	{
-	  ptid_t child_ptid = ws->child_ptid ();
-	  int child_pid = child_ptid.pid ();
-	  int child_lwp = child_ptid.lwp ();
+      ptid_t child_ptid = ws.child_ptid ();
+      int child_pid = child_ptid.pid ();
+      int child_lwp = child_ptid.lwp ();
 
-	  kill_one_lwp (child_lwp);
-	  kill_wait_one_lwp (child_lwp);
+      kill_one_lwp (child_lwp);
+      kill_wait_one_lwp (child_lwp);
 
-	  /* Let the arch-specific native code know this process is
-	     gone.  */
-	  linux_target->low_forget_process (child_pid);
-	}
+      /* Let the arch-specific native code know this process is
+	 gone.  */
+      if (ws.kind () != TARGET_WAITKIND_THREAD_CLONED)
+	linux_target->low_forget_process (child_pid);
     }
+
+  return 0;
 }
 
 void
 linux_nat_target::kill ()
 {
-  /* If we're stopped while forking and we haven't followed yet,
-     kill the other task.  We need to do this first because the
+  ptid_t pid_ptid (inferior_ptid.pid ());
+
+  /* If we're stopped while forking/cloning and we haven't followed
+     yet, kill the child task.  We need to do this first because the
      parent will be sleeping if this is a vfork.  */
-  kill_unfollowed_fork_children (current_inferior ());
+  iterate_over_lwps (pid_ptid, kill_unfollowed_child_callback);
 
   if (forks_exist_p ())
     linux_fork_killall ();
   else
     {
-      ptid_t ptid = ptid_t (inferior_ptid.pid ());
-
       /* Stop all threads before killing them, since ptrace requires
 	 that the thread is stopped to successfully PTRACE_KILL.  */
-      iterate_over_lwps (ptid, stop_callback);
+      iterate_over_lwps (pid_ptid, stop_callback);
       /* ... and wait until all of them have reported back that
 	 they're no longer running.  */
-      iterate_over_lwps (ptid, stop_wait_callback);
+      iterate_over_lwps (pid_ptid, stop_wait_callback);
 
       /* Kill all LWP's ...  */
-      iterate_over_lwps (ptid, kill_callback);
+      iterate_over_lwps (pid_ptid, kill_callback);
 
       /* ... and wait until we've flushed all events.  */
-      iterate_over_lwps (ptid, kill_wait_callback);
+      iterate_over_lwps (pid_ptid, kill_wait_callback);
     }
 
   target_mourn_inferior (inferior_ptid);
diff --git a/gdb/linux-nat.h b/gdb/linux-nat.h
index 11043c4b9f6..683173dbd38 100644
--- a/gdb/linux-nat.h
+++ b/gdb/linux-nat.h
@@ -129,6 +129,8 @@ class linux_nat_target : public inf_ptrace_target
 
   void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
 
+  void follow_clone (ptid_t) override;
+
   std::vector<static_tracepoint_marker>
     static_tracepoint_markers_by_strid (const char *id) override;
 
diff --git a/gdb/target-delegates.c b/gdb/target-delegates.c
index 8a9986454dd..f58fbe44094 100644
--- a/gdb/target-delegates.c
+++ b/gdb/target-delegates.c
@@ -76,6 +76,7 @@ struct dummy_target : public target_ops
   int insert_vfork_catchpoint (int arg0) override;
   int remove_vfork_catchpoint (int arg0) override;
   void follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bool arg3, bool arg4) override;
+  void follow_clone (ptid_t arg0) override;
   int insert_exec_catchpoint (int arg0) override;
   int remove_exec_catchpoint (int arg0) override;
   void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) override;
@@ -250,6 +251,7 @@ struct debug_target : public target_ops
   int insert_vfork_catchpoint (int arg0) override;
   int remove_vfork_catchpoint (int arg0) override;
   void follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bool arg3, bool arg4) override;
+  void follow_clone (ptid_t arg0) override;
   int insert_exec_catchpoint (int arg0) override;
   int remove_exec_catchpoint (int arg0) override;
   void follow_exec (inferior *arg0, ptid_t arg1, const char *arg2) override;
@@ -1545,6 +1547,28 @@ debug_target::follow_fork (inferior *arg0, ptid_t arg1, target_waitkind arg2, bo
   gdb_puts (")\n", gdb_stdlog);
 }
 
+void
+target_ops::follow_clone (ptid_t arg0)
+{
+  this->beneath ()->follow_clone (arg0);
+}
+
+void
+dummy_target::follow_clone (ptid_t arg0)
+{
+  default_follow_clone (this, arg0);
+}
+
+void
+debug_target::follow_clone (ptid_t arg0)
+{
+  gdb_printf (gdb_stdlog, "-> %s->follow_clone (...)\n", this->beneath ()->shortname ());
+  this->beneath ()->follow_clone (arg0);
+  gdb_printf (gdb_stdlog, "<- %s->follow_clone (", this->beneath ()->shortname ());
+  target_debug_print_ptid_t (arg0);
+  gdb_puts (")\n", gdb_stdlog);
+}
+
 int
 target_ops::insert_exec_catchpoint (int arg0)
 {
diff --git a/gdb/target.c b/gdb/target.c
index 18e53aa5d27..d1ba229189f 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2717,6 +2717,14 @@ default_follow_fork (struct target_ops *self, inferior *child_inf,
 		  _("could not find a target to follow fork"));
 }
 
+static void
+default_follow_clone (struct target_ops *self, ptid_t child_ptid)
+{
+  /* Some target returned a clone event, but did not know how to follow it.  */
+  internal_error (__FILE__, __LINE__,
+		  _("could not find a target to follow clone"));
+}
+
 /* See target.h.  */
 
 void
diff --git a/gdb/target.h b/gdb/target.h
index 18559feef89..1cab47147e3 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -636,6 +636,8 @@ struct target_ops
       TARGET_DEFAULT_RETURN (1);
     virtual void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool)
       TARGET_DEFAULT_FUNC (default_follow_fork);
+    virtual void follow_clone (ptid_t)
+      TARGET_DEFAULT_FUNC (default_follow_clone);
     virtual int insert_exec_catchpoint (int)
       TARGET_DEFAULT_RETURN (1);
     virtual int remove_exec_catchpoint (int)
diff --git a/gdb/target/waitstatus.c b/gdb/target/waitstatus.c
index ef432bb629d..3e45e4f32fa 100644
--- a/gdb/target/waitstatus.c
+++ b/gdb/target/waitstatus.c
@@ -45,6 +45,7 @@ DIAGNOSTIC_ERROR_SWITCH
 
     case TARGET_WAITKIND_FORKED:
     case TARGET_WAITKIND_VFORKED:
+    case TARGET_WAITKIND_THREAD_CLONED:
       return string_appendf (str, ", child_ptid = %s",
 			     this->child_ptid ().to_string ().c_str ());
 
diff --git a/gdb/target/waitstatus.h b/gdb/target/waitstatus.h
index 63bbd737749..5dcdbc8fe09 100644
--- a/gdb/target/waitstatus.h
+++ b/gdb/target/waitstatus.h
@@ -95,6 +95,13 @@ enum target_waitkind
   /* There are no resumed children left in the program.  */
   TARGET_WAITKIND_NO_RESUMED,
 
+  /* The thread was cloned.  The event's ptid corresponds to the
+     cloned parent.  The cloned child is held stopped at its entry
+     point, and its ptid is in the event's m_child_ptid.  The target
+     must not add the cloned child to GDB's thread list until
+     target_ops::follow_clone() is called.  */
+  TARGET_WAITKIND_THREAD_CLONED,
+
   /* The thread was created.  */
   TARGET_WAITKIND_THREAD_CREATED,
 
@@ -125,6 +132,8 @@ DIAGNOSTIC_ERROR_SWITCH
       return "FORKED";
     case TARGET_WAITKIND_VFORKED:
       return "VFORKED";
+    case TARGET_WAITKIND_THREAD_CLONED:
+      return "THREAD_CLONED";
     case TARGET_WAITKIND_EXECD:
       return "EXECD";
     case TARGET_WAITKIND_VFORK_DONE:
@@ -325,6 +334,14 @@ struct target_waitstatus
     return *this;
   }
 
+  target_waitstatus &set_thread_cloned (ptid_t child_ptid)
+  {
+    this->reset ();
+    m_kind = TARGET_WAITKIND_THREAD_CLONED;
+    m_value.child_ptid = child_ptid;
+    return *this;
+  }
+
   target_waitstatus &set_thread_created ()
   {
     this->reset ();
@@ -370,7 +387,8 @@ struct target_waitstatus
   ptid_t child_ptid () const
   {
     gdb_assert (m_kind == TARGET_WAITKIND_FORKED
-		|| m_kind == TARGET_WAITKIND_VFORKED);
+		|| m_kind == TARGET_WAITKIND_VFORKED
+		|| m_kind == TARGET_WAITKIND_THREAD_CLONED);
     return m_value.child_ptid;
   }
 
-- 
2.36.0


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

* [PATCH 05/25] Support clone events in the remote protocol
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (3 preceding siblings ...)
  2022-06-20 22:53 ` [PATCH 04/25] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED Pedro Alves
@ 2022-06-20 22:53 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 06/25] Thread options & clone events (core + remote) Pedro Alves
                   ` (19 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:53 UTC (permalink / raw)
  To: gdb-patches

The previous patch taught GDB about a new
TARGET_WAITKIND_THREAD_CLONED event kind, and made the Linux target
report clone events.

A following patch will teach Linux GDBserver to do the same thing.

But before we get there, we need to teach the remote protocol about
TARGET_WAITKIND_THREAD_CLONED.  That's what this patch does.  Clone is
very similar to vfork and fork, and the new stop reply is likewise
handled similarly.  The stub reports "T05clone:...".

GDBserver core is taught to handle TARGET_WAITKIND_THREAD_CLONED and
forward it to GDB in this patch, but no backend actually emits it yet.
That will be done in a following patch.

Documentation for this new remote protocol feature is included in a
documentation patch later in the series.

Change-Id: If271f20320d864f074d8ac0d531cc1a323da847f
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
---
 gdb/remote.c              | 86 ++++++++++++++++++++++++++++++---------
 gdbserver/remote-utils.cc | 26 ++++++++++--
 gdbserver/server.cc       |  3 +-
 3 files changed, 90 insertions(+), 25 deletions(-)

diff --git a/gdb/remote.c b/gdb/remote.c
index ed834228829..c0bd075e9e5 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -672,6 +672,7 @@ class remote_target : public process_stratum_target
   const struct btrace_config *btrace_conf (const struct btrace_target_info *) override;
   bool augmented_libraries_svr4_read () override;
   void follow_fork (inferior *, ptid_t, target_waitkind, bool, bool) override;
+  void follow_clone (ptid_t child_ptid) override;
   void follow_exec (inferior *, ptid_t, const char *) override;
   int insert_fork_catchpoint (int) override;
   int remove_fork_catchpoint (int) override;
@@ -2581,9 +2582,10 @@ remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
   else
     thread = add_thread (this, ptid);
 
-  /* We start by assuming threads are resumed.  That state then gets updated
-     when we process a matching stop reply.  */
-  get_remote_thread_info (thread)->set_resumed ();
+  /* We start by assuming threads are resumed.  That state then gets
+     updated when we process a matching stop reply.  */
+  if (executing)
+    get_remote_thread_info (thread)->set_resumed ();
 
   set_executing (this, ptid, executing);
   set_running (this, ptid, running);
@@ -4944,6 +4946,8 @@ remote_target::start_remote_1 (int from_tty, int extended_p)
 	    }
 	  else
 	    switch_to_thread (find_thread_ptid (this, curr_thread));
+
+	  get_remote_thread_info (inferior_thread ())->set_resumed ();
 	}
 
       /* init_wait_for_inferior should be called before get_offsets in order
@@ -5897,16 +5901,35 @@ is_fork_status (target_waitkind kind)
 	  || kind == TARGET_WAITKIND_VFORKED);
 }
 
-/* Return THREAD's pending status if it is a pending fork parent, else
-   return nullptr.  */
+/* Determine if WS represents an event with a new child - a fork,
+   vfork, or clone.  */
+
+static bool
+is_new_child_status (target_waitkind kind)
+{
+  return (kind == TARGET_WAITKIND_FORKED
+	  || kind == TARGET_WAITKIND_VFORKED
+	  || kind == TARGET_WAITKIND_THREAD_CLONED);
+}
+
+/* Return a reference to the field that records the THREAD's pending
+   child status, if there's one.  */
+
+static const target_waitstatus &
+thread_pending_status (struct thread_info *thread)
+{
+  return (thread->has_pending_waitstatus ()
+	  ? thread->pending_waitstatus ()
+	  : thread->pending_follow);
+}
+
+/* Return THREAD's pending status if it is a pending fork/vfork (but
+   not clone) parent, else return nullptr.  */
 
 static const target_waitstatus *
 thread_pending_fork_status (struct thread_info *thread)
 {
-  const target_waitstatus &ws
-    = (thread->has_pending_waitstatus ()
-       ? thread->pending_waitstatus ()
-       : thread->pending_follow);
+  const target_waitstatus &ws = thread_pending_status (thread);
 
   if (!is_fork_status (ws.kind ()))
     return nullptr;
@@ -5914,6 +5937,20 @@ thread_pending_fork_status (struct thread_info *thread)
   return &ws;
 }
 
+/* Return THREAD's pending status if is is a pending fork/vfork/clone
+   event, else return nullptr.  */
+
+static const target_waitstatus *
+thread_pending_child_status (thread_info *thread)
+{
+  const target_waitstatus &ws = thread_pending_status (thread);
+
+  if (!is_new_child_status (ws.kind ()))
+    return nullptr;
+
+  return &ws;
+}
+
 /* Detach the specified process.  */
 
 void
@@ -6079,6 +6116,12 @@ remote_target::follow_fork (inferior *child_inf, ptid_t child_ptid,
     }
 }
 
+void
+remote_target::follow_clone (ptid_t child_ptid)
+{
+  remote_add_thread (child_ptid, false, false, false);
+}
+
 /* Target follow-exec function for remote targets.  Save EXECD_PATHNAME
    in the program space of the new inferior.  */
 
@@ -6811,10 +6854,10 @@ remote_target::commit_resumed ()
       if (priv->get_resume_state () == resume_state::RESUMED_PENDING_VCONT)
 	any_pending_vcont_resume = true;
 
-      /* If a thread is the parent of an unfollowed fork, then we
-	 can't do a global wildcard, as that would resume the fork
-	 child.  */
-      if (thread_pending_fork_status (tp) != nullptr)
+      /* If a thread is the parent of an unfollowed fork/vfork/clone,
+	 then we can't do a global wildcard, as that would resume the
+	 pending child.  */
+      if (thread_pending_child_status (tp) != nullptr)
 	may_global_wildcard_vcont = false;
     }
 
@@ -7291,11 +7334,11 @@ remote_target::remove_new_fork_children (threads_listing_context *context)
 {
   struct notif_client *notif = &notif_client_stop;
 
-  /* For any threads stopped at a fork event, remove the corresponding
-     fork child threads from the CONTEXT list.  */
+  /* For any threads stopped at a (v)fork/clone event, remove the
+     corresponding child threads from the CONTEXT list.  */
   for (thread_info *thread : all_non_exited_threads (this))
     {
-      const target_waitstatus *ws = thread_pending_fork_status (thread);
+      const target_waitstatus *ws = thread_pending_child_status (thread);
 
       if (ws == nullptr)
 	continue;
@@ -7303,13 +7346,14 @@ remote_target::remove_new_fork_children (threads_listing_context *context)
       context->remove_thread (ws->child_ptid ());
     }
 
-  /* Check for any pending fork events (not reported or processed yet)
-     in process PID and remove those fork child threads from the
-     CONTEXT list as well.  */
+  /* Check for any pending (v)fork/clone events (not reported or
+     processed yet) in process PID and remove those child threads from
+     the CONTEXT list as well.  */
   remote_notif_get_pending_events (notif);
   for (auto &event : get_remote_state ()->stop_reply_queue)
     if (event->ws.kind () == TARGET_WAITKIND_FORKED
-	|| event->ws.kind () == TARGET_WAITKIND_VFORKED)
+	|| event->ws.kind () == TARGET_WAITKIND_VFORKED
+	|| event->ws.kind () == TARGET_WAITKIND_THREAD_CLONED)
       context->remove_thread (event->ws.child_ptid ());
     else if (event->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
       context->remove_thread (event->ptid);
@@ -7638,6 +7682,8 @@ Packet: '%s'\n"),
 	    event->ws.set_forked (read_ptid (++p1, &p));
 	  else if (strprefix (p, p1, "vfork"))
 	    event->ws.set_vforked (read_ptid (++p1, &p));
+	  else if (strprefix (p, p1, "clone"))
+	    event->ws.set_thread_cloned (read_ptid (++p1, &p));
 	  else if (strprefix (p, p1, "vforkdone"))
 	    {
 	      event->ws.set_vfork_done ();
diff --git a/gdbserver/remote-utils.cc b/gdbserver/remote-utils.cc
index db9b2a66f3c..b6a5a17fcd3 100644
--- a/gdbserver/remote-utils.cc
+++ b/gdbserver/remote-utils.cc
@@ -1063,6 +1063,7 @@ prepare_resume_reply (char *buf, ptid_t ptid, const target_waitstatus &status)
     case TARGET_WAITKIND_FORKED:
     case TARGET_WAITKIND_VFORKED:
     case TARGET_WAITKIND_VFORK_DONE:
+    case TARGET_WAITKIND_THREAD_CLONED:
     case TARGET_WAITKIND_EXECD:
     case TARGET_WAITKIND_THREAD_CREATED:
     case TARGET_WAITKIND_SYSCALL_ENTRY:
@@ -1072,13 +1073,30 @@ prepare_resume_reply (char *buf, ptid_t ptid, const target_waitstatus &status)
 	struct regcache *regcache;
 	char *buf_start = buf;
 
-	if ((status.kind () == TARGET_WAITKIND_FORKED && cs.report_fork_events)
+	if ((status.kind () == TARGET_WAITKIND_FORKED
+	     && cs.report_fork_events)
 	    || (status.kind () == TARGET_WAITKIND_VFORKED
-		&& cs.report_vfork_events))
+		&& cs.report_vfork_events)
+	    || status.kind () == TARGET_WAITKIND_THREAD_CLONED)
 	  {
 	    enum gdb_signal signal = GDB_SIGNAL_TRAP;
-	    const char *event = (status.kind () == TARGET_WAITKIND_FORKED
-				 ? "fork" : "vfork");
+
+	    auto kind_remote_str = [] (target_waitkind kind)
+	    {
+	      switch (kind)
+		{
+		case TARGET_WAITKIND_FORKED:
+		  return "fork";
+		case TARGET_WAITKIND_VFORKED:
+		  return "vfork";
+		case TARGET_WAITKIND_THREAD_CLONED:
+		  return "clone";
+		default:
+		  gdb_assert_not_reached ("unhandled kind");
+		}
+	    };
+
+	    const char *event = kind_remote_str (status.kind ());
 
 	    sprintf (buf, "T%02x%s:", signal, event);
 	    buf += strlen (buf);
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index f9c02a9c6da..30a1ccbb367 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -236,7 +236,8 @@ in_queued_stop_replies_ptid (struct notif_event *event, ptid_t filter_ptid)
 
   /* Don't resume fork children that GDB does not know about yet.  */
   if ((vstop_event->status.kind () == TARGET_WAITKIND_FORKED
-       || vstop_event->status.kind () == TARGET_WAITKIND_VFORKED)
+       || vstop_event->status.kind () == TARGET_WAITKIND_VFORKED
+       || vstop_event->status.kind () == TARGET_WAITKIND_THREAD_CLONED)
       && vstop_event->status.child_ptid ().matches (filter_ptid))
     return true;
 
-- 
2.36.0


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

* [PATCH 06/25] Thread options & clone events (core + remote)
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (4 preceding siblings ...)
  2022-06-20 22:53 ` [PATCH 05/25] Support clone events in the remote protocol Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 07/25] Thread options & clone events (native Linux) Pedro Alves
                   ` (18 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

A previous patch taught GDB about a new TARGET_WAITKIND_THREAD_CLONED
event kind, and made the Linux target report clone events.

A following patch will teach Linux GDBserver to do the same thing.

However, for remote debugging, it wouldn't be ideal for GDBserver to
report every clone event to GDB, when GDB only cares about such events
in some specific situations.  Reporting clone events all the time
would be potentially chatty.  We don't enable thread create/exit
events all the time for the same reason.  Instead we have the
QThreadEvents packet.  QThreadEvents is target-wide, though.

This patch makes GDB instead explicitly request that the target
reports clone events or not, on a per-thread basis.

In order to be able to do that with GDBserver, we need a new remote
protocol feature.  Since a following patch will want to enable thread
exit events on per-thread basis too, the packet introduced here is
more generic than just for clone events.  It lets you enable/disable a
set of options at once, modelled on Linux ptrace's PTRACE_SETOPTIONS.

IOW, this commit introduces a new QThreadOptions packet, that lets you
specify a set of per-thread event options you want to enable.  The
packet accepts a list of options/thread-id pairs, similarly to vCont,
processed left to right, with the options field being a number
interpreted as a bit mask of options.  The only option defined in this
commit is GDB_TO_CLONE (0x1), which ask the remote target to report
clone events.  Another patch later in the series will introduce
another option.

For example, this packet sets option "1" (clone events) on thread
p1000.2345:

  QThreadOptions;1:p1000.2345

and this clears options for all threads of process 1000, and then sets
option "1" (clone events) on thread p1000.2345:

  QThreadOptions;0:p1000.-1;1:p1000.2345

This clears options of all threads of all processes:

  QThreadOptions;0

The target reports the set of supported options by including
"QThreadOptions=<supported options>" in its qSupported response.

infrun is then tweaked to enable GDB_TO_CLONE when stepping over a
breakpoint.

Similarly to PTRACE_SETOPTIONS, fork/vfork/clone children inherit
their parent's thread options.

Documentation for this new remote protocol feature is included in a
documentation patch later in the series.

Change-Id: If271f20320d864f074d8ac0d531cc1a323da847f
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
---
 gdb/gdbthread.h              |  19 ++++++
 gdb/infrun.c                 |  60 +++++++++++++++++
 gdb/process-stratum-target.c |   4 +-
 gdb/remote.c                 | 119 ++++++++++++++++++++++++++++++++-
 gdb/target-debug.h           |   2 +
 gdb/target-delegates.c       |  54 +++++++++++++++
 gdb/target.c                 |  32 +++++++++
 gdb/target.h                 |  15 +++++
 gdb/target/target.h          |  10 +++
 gdbserver/gdbthread.h        |   3 +
 gdbserver/server.cc          | 124 +++++++++++++++++++++++++++++++++++
 gdbserver/target.cc          |   6 ++
 gdbserver/target.h           |   9 +++
 13 files changed, 454 insertions(+), 3 deletions(-)

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 1a33eb61221..a73d3c4eda0 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -28,6 +28,7 @@ struct symtab;
 #include "ui-out.h"
 #include "btrace.h"
 #include "target/waitstatus.h"
+#include "target/target.h"
 #include "cli/cli-utils.h"
 #include "gdbsupport/refcounted-object.h"
 #include "gdbsupport/common-gdbthread.h"
@@ -470,6 +471,20 @@ class thread_info : public refcounted_object,
     m_thread_fsm = std::move (fsm);
   }
 
+  /* Record the thread options last set for this thread.  */
+
+  void set_thread_options (gdb_thread_options thread_options)
+  {
+    m_thread_options = thread_options;
+  }
+
+  /* Get the thread options last set for this thread.  */
+
+  gdb_thread_options thread_options () const
+  {
+    return m_thread_options;
+  }
+
   int current_line = 0;
   struct symtab *current_symtab = NULL;
 
@@ -577,6 +592,10 @@ class thread_info : public refcounted_object,
      left to do for the thread's execution command after the target
      stops.  Several execution commands use it.  */
   std::unique_ptr<struct thread_fsm> m_thread_fsm;
+
+  /* The thread options as last set with a call to
+     target_set_thread_options.  */
+  gdb_thread_options m_thread_options;
 };
 
 using thread_info_resumed_with_pending_wait_status_node
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 9f09373c1db..78ba81f530f 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1512,6 +1512,16 @@ step_over_info_valid_p (void)
    displaced step operation on it.  See displaced_step_prepare and
    displaced_step_finish for details.  */
 
+/* Return true if THREAD is doing a displaced step.  */
+
+static bool
+displaced_step_in_progress_thread (thread_info *thread)
+{
+  gdb_assert (thread != NULL);
+
+  return thread->displaced_step_state.in_progress ();
+}
+
 /* Return true if INF has a thread doing a displaced step.  */
 
 static bool
@@ -1806,6 +1816,28 @@ displaced_step_prepare (thread_info *thread)
   return status;
 }
 
+/* Maybe disable thread-{cloned,created,exited} event reporting after
+   a step-over (either in-line or displaced) finishes.  */
+
+static void
+update_thread_events_after_step_over (thread_info *event_thread)
+{
+  if (target_supports_set_thread_options (0))
+    {
+      /* We can control per-thread options.  Disable events for the
+	 event thread.  */
+      target_set_thread_options (event_thread, 0);
+    }
+  else
+    {
+      /* We can only control the target-wide target_thread_events
+	 setting.  Disable it, but only if other threads don't need it
+	 enabled.  */
+      if (!displaced_step_in_progress_any_thread ())
+	target_thread_events (false);
+    }
+}
+
 /* If we displaced stepped an instruction successfully, adjust registers and
    memory to yield the same effect the instruction would have had if we had
    executed it at its original address, and return
@@ -1850,6 +1882,8 @@ displaced_step_finish (thread_info *event_thread,
   if (!displaced->in_progress ())
     return DISPLACED_STEP_FINISH_STATUS_OK;
 
+  update_thread_events_after_step_over (event_thread);
+
   gdb_assert (event_thread->inf->displaced_step_state.in_progress_count > 0);
   event_thread->inf->displaced_step_state.in_progress_count--;
 
@@ -2342,6 +2376,30 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
   else
     target_pass_signals (signal_pass);
 
+  /* Request that the target report thread-{created,cloned} events in
+     the following situations:
+
+     - If we are performing an in-line step-over-breakpoint, then we
+       will remove a breakpoint from the target and only run the
+       current thread.  We don't want any new thread (spawned by the
+       step) to start running, as it might miss the breakpoint.
+
+     - If we are stepping over a breakpoint out of line (displaced
+       stepping) then we won't remove a breakpoint from the target,
+       but, if the step spawns a new clone thread, then we will need
+       to fixup the $pc address in the clone child too, so we need it
+       to start stopped.
+  */
+  if (step_over_info_valid_p ()
+      || displaced_step_in_progress_thread (tp))
+    {
+      gdb_thread_options options = GDB_TO_CLONE;
+      if (target_supports_set_thread_options (options))
+	target_set_thread_options (tp, options);
+      else
+	target_thread_events (true);
+    }
+
   infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
 		       resume_ptid.to_string ().c_str (),
 		       step, gdb_signal_to_symbol_string (sig));
@@ -6011,6 +6069,8 @@ finish_step_over (struct execution_control_state *ecs)
 	 back an event.  */
       gdb_assert (ecs->event_thread->control.trap_expected);
 
+      update_thread_events_after_step_over (ecs->event_thread);
+
       clear_step_over_info ();
     }
 
diff --git a/gdb/process-stratum-target.c b/gdb/process-stratum-target.c
index 50bb13e4b83..fddb8dbdb87 100644
--- a/gdb/process-stratum-target.c
+++ b/gdb/process-stratum-target.c
@@ -101,6 +101,7 @@ process_stratum_target::follow_exec (inferior *follow_inf, ptid_t ptid,
 	 after that, at its discretion.  */
       follow_inf->push_target (orig_inf->process_target ());
       thread_info *t = add_thread (follow_inf->process_target (), ptid);
+      t->set_thread_options (inferior_thread ()->thread_options ());
 
       /* Leave the new inferior / thread as the current inferior / thread.  */
       switch_to_thread (t);
@@ -118,7 +119,8 @@ process_stratum_target::follow_fork (inferior *child_inf, ptid_t child_ptid,
   if (child_inf != nullptr)
     {
       child_inf->push_target (this);
-      add_thread_silent (this, child_ptid);
+      thread_info *child = add_thread_silent (this, child_ptid);
+      child->set_thread_options (inferior_thread ()->thread_options ());
     }
 }
 
diff --git a/gdb/remote.c b/gdb/remote.c
index c0bd075e9e5..20f2b7f399d 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -381,6 +381,10 @@ class remote_state
      this can go away.  */
   int wait_forever_enabled_p = 1;
 
+  /* The set of thread options the target reported it supports, via
+     qSupported.  */
+  gdb_thread_options supported_thread_options = 0;
+
 private:
   /* Mapping of remote protocol data for each gdbarch.  Usually there
      is only one entry here, though we may see more with stubs that
@@ -543,6 +547,9 @@ class remote_target : public process_stratum_target
 
   void thread_events (int) override;
 
+  bool supports_set_thread_options (gdb_thread_options) override;
+  void set_thread_options (thread_info *, gdb_thread_options) override;
+
   int can_do_single_step () override;
 
   void terminal_inferior () override;
@@ -833,6 +840,9 @@ class remote_target : public process_stratum_target
 
   void remote_packet_size (const protocol_feature *feature,
 			   packet_support support, const char *value);
+  void remote_supported_thread_options (const protocol_feature *feature,
+					enum packet_support support,
+					const char *value);
 
   void remote_serial_quit_handler ();
 
@@ -2167,6 +2177,9 @@ enum {
   /* Support for the QThreadEvents packet.  */
   PACKET_QThreadEvents,
 
+  /* Support for the QThreadOptions packet.  */
+  PACKET_QThreadOptions,
+
   /* Support for multi-process extensions.  */
   PACKET_multiprocess_feature,
 
@@ -5289,7 +5302,8 @@ remote_supported_packet (remote_target *remote,
 
 void
 remote_target::remote_packet_size (const protocol_feature *feature,
-				   enum packet_support support, const char *value)
+				   enum packet_support support,
+				   const char *value)
 {
   struct remote_state *rs = get_remote_state ();
 
@@ -5326,6 +5340,49 @@ remote_packet_size (remote_target *remote, const protocol_feature *feature,
   remote->remote_packet_size (feature, support, value);
 }
 
+void
+remote_target::remote_supported_thread_options (const protocol_feature *feature,
+						enum packet_support support,
+						const char *value)
+{
+  struct remote_state *rs = get_remote_state ();
+
+  remote_protocol_packets[feature->packet].support = support;
+
+  if (support != PACKET_ENABLE)
+    return;
+
+  if (value == nullptr || *value == '\0')
+    {
+      warning (_("Remote target reported \"%s\" without supported options."),
+	       feature->name);
+      return;
+    }
+
+  ULONGEST options = 0;
+  const char *p = unpack_varlen_hex (value, &options);
+
+  if (*p != '\0')
+    {
+      warning (_("Remote target reported \"%s\" with "
+		 "bad thread options: \"%s\"."),
+	       feature->name, value);
+      return;
+    }
+
+  /* Record the set of supported options.  */
+  rs->supported_thread_options = (gdb_thread_option) options;
+}
+
+static void
+remote_supported_thread_options (remote_target *remote,
+				 const protocol_feature *feature,
+				 enum packet_support support,
+				 const char *value)
+{
+  remote->remote_supported_thread_options (feature, support, value);
+}
+
 static const struct protocol_feature remote_protocol_features[] = {
   { "PacketSize", PACKET_DISABLE, remote_packet_size, -1 },
   { "qXfer:auxv:read", PACKET_DISABLE, remote_supported_packet,
@@ -5428,6 +5485,8 @@ static const struct protocol_feature remote_protocol_features[] = {
     PACKET_Qbtrace_conf_pt_size },
   { "vContSupported", PACKET_DISABLE, remote_supported_packet, PACKET_vContSupported },
   { "QThreadEvents", PACKET_DISABLE, remote_supported_packet, PACKET_QThreadEvents },
+  { "QThreadOptions", PACKET_DISABLE, remote_supported_thread_options,
+    PACKET_QThreadOptions },
   { "no-resumed", PACKET_DISABLE, remote_supported_packet, PACKET_no_resumed },
   { "memory-tagging", PACKET_DISABLE, remote_supported_packet,
     PACKET_memory_tagging_feature },
@@ -5522,6 +5581,9 @@ remote_target::remote_query_supported ()
       if (packet_set_cmd_state (PACKET_QThreadEvents) != AUTO_BOOLEAN_FALSE)
 	remote_query_supported_append (&q, "QThreadEvents+");
 
+      if (packet_set_cmd_state (PACKET_QThreadOptions) != AUTO_BOOLEAN_FALSE)
+	remote_query_supported_append (&q, "QThreadOptions+");
+
       if (packet_set_cmd_state (PACKET_no_resumed) != AUTO_BOOLEAN_FALSE)
 	remote_query_supported_append (&q, "no-resumed+");
 
@@ -6119,7 +6181,8 @@ remote_target::follow_fork (inferior *child_inf, ptid_t child_ptid,
 void
 remote_target::follow_clone (ptid_t child_ptid)
 {
-  remote_add_thread (child_ptid, false, false, false);
+  thread_info *child = remote_add_thread (child_ptid, false, false, false);
+  child->set_thread_options (inferior_thread ()->thread_options ());
 }
 
 /* Target follow-exec function for remote targets.  Save EXECD_PATHNAME
@@ -14603,6 +14666,55 @@ remote_target::thread_events (int enable)
     }
 }
 
+/* Implementation of the supports_set_thread_options target
+   method.  */
+
+bool
+remote_target::supports_set_thread_options (gdb_thread_options options)
+{
+  remote_state *rs = get_remote_state ();
+  return (packet_support (PACKET_QThreadOptions) == PACKET_ENABLE
+	  && (rs->supported_thread_options & options) == options);
+}
+
+/* Implementation of the set_thread_options target method.  */
+
+void
+remote_target::set_thread_options (thread_info *tp, gdb_thread_options options)
+{
+  struct remote_state *rs = get_remote_state ();
+
+  char *p = rs->buf.data ();
+  char *endp = p + get_remote_packet_size ();
+
+  strcpy (p, "QThreadOptions;");
+  p += strlen (p);
+  p += xsnprintf (p, endp - p, "%s", phex_nz (options, sizeof (options)));
+  if (tp->ptid != magic_null_ptid)
+    {
+      *p++ = ':';
+      p = write_ptid (p, endp, tp->ptid);
+    }
+  *p++ = '\0';
+
+  putpkt (rs->buf);
+  getpkt (&rs->buf, 0);
+
+  switch (packet_ok (rs->buf,
+		     &remote_protocol_packets[PACKET_QThreadOptions]))
+    {
+    case PACKET_OK:
+      if (strcmp (rs->buf.data (), "OK") != 0)
+	error (_("Remote refused setting thread options: %s"), rs->buf.data ());
+      break;
+    case PACKET_ERROR:
+      error (_("Remote failure reply: %s"), rs->buf.data ());
+    case PACKET_UNKNOWN:
+      gdb_assert_not_reached ("PACKET_UNKNOWN");
+      break;
+    }
+}
+
 static void
 show_remote_cmd (const char *args, int from_tty)
 {
@@ -15386,6 +15498,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
   add_packet_config_cmd (&remote_protocol_packets[PACKET_QThreadEvents],
 			 "QThreadEvents", "thread-events", 0);
 
+  add_packet_config_cmd (&remote_protocol_packets[PACKET_QThreadOptions],
+			 "QThreadOptions", "thread-options", 0);
+
   add_packet_config_cmd (&remote_protocol_packets[PACKET_no_resumed],
 			 "N stop reply", "no-resumed-stop-reply", 0);
 
diff --git a/gdb/target-debug.h b/gdb/target-debug.h
index c2b1db1ce8e..0fe5e5e6022 100644
--- a/gdb/target-debug.h
+++ b/gdb/target-debug.h
@@ -176,6 +176,8 @@
   target_debug_do_print (X.get ())
 #define target_debug_print_target_waitkind(X) \
   target_debug_do_print (pulongest (X))
+#define target_debug_print_gdb_thread_options(X) \
+  target_debug_do_print (pulongest (X))
 
 static void
 target_debug_print_struct_target_waitstatus_p (struct target_waitstatus *status)
diff --git a/gdb/target-delegates.c b/gdb/target-delegates.c
index f58fbe44094..c09e6eba922 100644
--- a/gdb/target-delegates.c
+++ b/gdb/target-delegates.c
@@ -106,6 +106,8 @@ struct dummy_target : public target_ops
   int async_wait_fd () override;
   bool has_pending_events () override;
   void thread_events (int arg0) override;
+  bool supports_set_thread_options (gdb_thread_options arg0) override;
+  void set_thread_options (thread_info *arg0, gdb_thread_options arg1) override;
   bool supports_non_stop () override;
   bool always_non_stop_p () override;
   int find_memory_regions (find_memory_region_ftype arg0, void *arg1) override;
@@ -281,6 +283,8 @@ struct debug_target : public target_ops
   int async_wait_fd () override;
   bool has_pending_events () override;
   void thread_events (int arg0) override;
+  bool supports_set_thread_options (gdb_thread_options arg0) override;
+  void set_thread_options (thread_info *arg0, gdb_thread_options arg1) override;
   bool supports_non_stop () override;
   bool always_non_stop_p () override;
   int find_memory_regions (find_memory_region_ftype arg0, void *arg1) override;
@@ -2272,6 +2276,56 @@ debug_target::thread_events (int arg0)
   gdb_puts (")\n", gdb_stdlog);
 }
 
+bool
+target_ops::supports_set_thread_options (gdb_thread_options arg0)
+{
+  return this->beneath ()->supports_set_thread_options (arg0);
+}
+
+bool
+dummy_target::supports_set_thread_options (gdb_thread_options arg0)
+{
+  return false;
+}
+
+bool
+debug_target::supports_set_thread_options (gdb_thread_options arg0)
+{
+  bool result;
+  gdb_printf (gdb_stdlog, "-> %s->supports_set_thread_options (...)\n", this->beneath ()->shortname ());
+  result = this->beneath ()->supports_set_thread_options (arg0);
+  gdb_printf (gdb_stdlog, "<- %s->supports_set_thread_options (", this->beneath ()->shortname ());
+  target_debug_print_gdb_thread_options (arg0);
+  gdb_puts (") = ", gdb_stdlog);
+  target_debug_print_bool (result);
+  gdb_puts ("\n", gdb_stdlog);
+  return result;
+}
+
+void
+target_ops::set_thread_options (thread_info *arg0, gdb_thread_options arg1)
+{
+  this->beneath ()->set_thread_options (arg0, arg1);
+}
+
+void
+dummy_target::set_thread_options (thread_info *arg0, gdb_thread_options arg1)
+{
+  tcomplain ();
+}
+
+void
+debug_target::set_thread_options (thread_info *arg0, gdb_thread_options arg1)
+{
+  gdb_printf (gdb_stdlog, "-> %s->set_thread_options (...)\n", this->beneath ()->shortname ());
+  this->beneath ()->set_thread_options (arg0, arg1);
+  gdb_printf (gdb_stdlog, "<- %s->set_thread_options (", this->beneath ()->shortname ());
+  target_debug_print_thread_info_p (arg0);
+  gdb_puts (", ", gdb_stdlog);
+  target_debug_print_gdb_thread_options (arg1);
+  gdb_puts (")\n", gdb_stdlog);
+}
+
 bool
 target_ops::supports_non_stop ()
 {
diff --git a/gdb/target.c b/gdb/target.c
index d1ba229189f..52eee4b8140 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -4377,6 +4377,38 @@ target_thread_events (int enable)
   current_inferior ()->top_target ()->thread_events (enable);
 }
 
+/* See target.h.  */
+
+bool
+target_supports_set_thread_options (gdb_thread_options options)
+{
+  inferior *inf = current_inferior ();
+  return inf->top_target ()->supports_set_thread_options (options);
+}
+
+/* See target.h.  */
+
+void
+target_set_thread_options (thread_info *thr, gdb_thread_options options)
+{
+  gdb_assert (thr->inf->process_target ()
+	      == current_inferior ()->process_target ());
+
+  if (thr->thread_options () == options)
+    {
+      /* Avoid propagating the new options to the target if nothing
+	 would change.  This avoids redundant remote protocol
+	 packets.  */
+      return;
+    }
+
+  thr->set_thread_options (options);
+  current_inferior ()->top_target ()->set_thread_options (thr, options);
+  infrun_debug_printf ("[options for %s are now 0x%x]",
+		       target_pid_to_str (thr->ptid).c_str (),
+		       (unsigned) options);
+}
+
 /* Controls if targets can report that they can/are async.  This is
    just for maintainers to use when debugging gdb.  */
 bool target_async_permitted = true;
diff --git a/gdb/target.h b/gdb/target.h
index 1cab47147e3..b1447cc908c 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -730,6 +730,13 @@ struct target_ops
       TARGET_DEFAULT_RETURN (false);
     virtual void thread_events (int)
       TARGET_DEFAULT_IGNORE ();
+    /* Returns true if the target supports setting thread options
+       OPTIONS, false otherwise.  */
+    virtual bool supports_set_thread_options (gdb_thread_options options)
+      TARGET_DEFAULT_RETURN (false);
+    /* Set thread options for the specified thread.  */
+    virtual void set_thread_options (thread_info *, gdb_thread_options)
+      TARGET_DEFAULT_NORETURN (tcomplain ());
     /* This method must be implemented in some situations.  See the
        comment on 'can_run'.  */
     virtual bool supports_non_stop ()
@@ -1893,6 +1900,14 @@ extern void target_async (int enable);
 /* Enables/disables thread create and exit events.  */
 extern void target_thread_events (int enable);
 
+/* Returns true if the target supports setting thread options
+   OPTIONS.  */
+extern bool target_supports_set_thread_options (gdb_thread_options options);
+
+/* Sets TP's options.  */
+extern void target_set_thread_options (thread_info *tp,
+				       gdb_thread_options options);
+
 /* Whether support for controlling the target backends always in
    non-stop mode is enabled.  */
 extern enum auto_boolean target_non_stop_enabled;
diff --git a/gdb/target/target.h b/gdb/target/target.h
index a5b0dd3ed1a..cba98114b5d 100644
--- a/gdb/target/target.h
+++ b/gdb/target/target.h
@@ -22,9 +22,19 @@
 
 #include "target/waitstatus.h"
 #include "target/wait.h"
+#include "gdbsupport/enum-flags.h"
 
 /* This header is a stopgap until more code is shared.  */
 
+enum gdb_thread_option : unsigned
+{
+  /* Tell the target to report TARGET_WAITKIND_THREAD_CLONED events
+     for the thread.  */
+  GDB_TO_CLONE = 1 << 0,
+};
+
+DEF_ENUM_FLAGS_TYPE (enum gdb_thread_option, gdb_thread_options);
+
 /* Read LEN bytes of target memory at address MEMADDR, placing the
    results in GDB's memory at MYADDR.  Return zero for success,
    nonzero if any error occurs.  This function must be provided by
diff --git a/gdbserver/gdbthread.h b/gdbserver/gdbthread.h
index 8b897e73d33..30040e8afb6 100644
--- a/gdbserver/gdbthread.h
+++ b/gdbserver/gdbthread.h
@@ -80,6 +80,9 @@ struct thread_info
 
   /* Branch trace target information for this thread.  */
   struct btrace_target_info *btrace = nullptr;
+
+  /* Thread options GDB requested with QThreadOptions.  */
+  gdb_thread_options thread_options = 0;
 };
 
 extern std::list<thread_info *> all_threads;
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index 30a1ccbb367..2b78ba7292d 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -611,6 +611,17 @@ parse_store_memtags_request (char *request, CORE_ADDR *addr, size_t *len,
   return true;
 }
 
+/* Parse thread options starting at *P and return them.  On exit,
+   advance *P past the options.  */
+
+static gdb_thread_options
+parse_gdb_thread_options (const char **p)
+{
+  ULONGEST options = 0;
+  *p = unpack_varlen_hex (*p, &options);
+  return (gdb_thread_option) options;
+}
+
 /* Handle all of the extended 'Q' packets.  */
 
 static void
@@ -892,6 +903,109 @@ handle_general_set (char *own_buf)
       return;
     }
 
+  if (startswith (own_buf, "QThreadOptions;"))
+    {
+      const char *p = own_buf + strlen ("QThreadOptions");
+
+      gdb_thread_options supported_options;
+      if (!target_supports_set_thread_options (&supported_options))
+	{
+	  /* Something went wrong -- we don't support options, but GDB
+	     sent the packet anyway.  */
+	  write_enn (own_buf);
+	  return;
+	}
+
+      auto set_thread_options = [] (thread_info *thread,
+				    gdb_thread_options options)
+        {
+	  thread->thread_options = options;
+
+	  if (debug_threads)
+	    {
+	      debug_printf ("[options for %s are now 0x%x]\n",
+			    target_pid_to_str (ptid_of (thread)).c_str (),
+			    (unsigned) options);
+	    }
+	};
+
+      while (*p != '\0')
+	{
+	  p++;
+
+	  /* Read the options.  */
+
+	  gdb_thread_options options = parse_gdb_thread_options (&p);
+
+	  if ((options & ~supported_options) != 0)
+	    {
+	      /* GDB asked for an unknown or unsupported option, so
+		 error out.  */
+	      std::string err
+		= string_printf ("E.Unknown option requested: %s\n",
+				 hex_string (options));
+	      strcpy (own_buf, err.c_str ());
+	      return;
+	    }
+
+	  ptid_t ptid;
+
+	  if (p[0] == '\0')
+	    ptid = minus_one_ptid;
+	  else if (p[0] == ':')
+	    {
+	      const char *q;
+
+	      ptid = read_ptid (p + 1, &q);
+
+	      if (p == q)
+		{
+		  write_enn (own_buf);
+		  return;
+		}
+	      p = q;
+	      if (p[0] != ';' && p[0] != '\0')
+		{
+		  write_enn (own_buf);
+		  return;
+		}
+	    }
+	  else
+	    {
+	      write_enn (own_buf);
+	      return;
+	    }
+
+	  if (ptid != minus_one_ptid && ptid.lwp () != -1)
+	    {
+	      thread_info *thread = find_thread_ptid (ptid);
+	      if (thread == nullptr)
+		{
+		  std::string err
+		    = string_printf ("E.No such thread %s",
+				     ptid.to_string ().c_str ());
+		  strcpy (own_buf, err.c_str ());
+		  return;
+		}
+
+	      set_thread_options (thread, options);
+	    }
+	  else
+	    {
+	      for_each_thread ([&] (thread_info *thread)
+	        {
+		  if (!ptid.matches (ptid_of (thread)))
+		    return;
+
+		  set_thread_options (thread, options);
+		});
+	    }
+	}
+
+      write_ok (own_buf);
+      return;
+    }
+
   if (startswith (own_buf, "QStartupWithShell:"))
     {
       const char *value = own_buf + strlen ("QStartupWithShell:");
@@ -2364,6 +2478,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
 		cs.vCont_supported = 1;
 	      else if (feature == "QThreadEvents+")
 		;
+	      else if (feature == "QThreadOptions+")
+		;
 	      else if (feature == "no-resumed+")
 		{
 		  /* GDB supports and wants TARGET_WAITKIND_NO_RESUMED
@@ -2489,6 +2605,14 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
 
       strcat (own_buf, ";vContSupported+");
 
+      gdb_thread_options supported_options;
+      if (target_supports_set_thread_options (&supported_options))
+	{
+	  char *end_buf = own_buf + strlen (own_buf);
+	  sprintf (end_buf, ";QThreadOptions=%s",
+		   phex_nz (supported_options, sizeof (supported_options)));
+	}
+
       strcat (own_buf, ";QThreadEvents+");
 
       strcat (own_buf, ";no-resumed+");
diff --git a/gdbserver/target.cc b/gdbserver/target.cc
index adcfe6e7bcc..70fec6d2078 100644
--- a/gdbserver/target.cc
+++ b/gdbserver/target.cc
@@ -530,6 +530,12 @@ process_stratum_target::supports_vfork_events ()
   return false;
 }
 
+bool
+process_stratum_target::supports_set_thread_options (gdb_thread_options *)
+{
+  return false;
+}
+
 bool
 process_stratum_target::supports_exec_events ()
 {
diff --git a/gdbserver/target.h b/gdbserver/target.h
index 6c536a30778..33142363a02 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -277,6 +277,12 @@ class process_stratum_target
   /* Returns true if vfork events are supported.  */
   virtual bool supports_vfork_events ();
 
+  /* Returns true if the target supports setting thread options.  If
+     options are supported, write into SUPPORTED_OPTIONS the set of
+     supported options.  */
+  virtual bool supports_set_thread_options
+    (gdb_thread_options *supported_options);
+
   /* Returns true if exec events are supported.  */
   virtual bool supports_exec_events ();
 
@@ -529,6 +535,9 @@ int kill_inferior (process_info *proc);
 #define target_supports_vfork_events() \
   the_target->supports_vfork_events ()
 
+#define target_supports_set_thread_options(options) \
+  the_target->supports_set_thread_options (options)
+
 #define target_supports_exec_events() \
   the_target->supports_exec_events ()
 
-- 
2.36.0


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

* [PATCH 07/25] Thread options & clone events (native Linux)
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (5 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 06/25] Thread options & clone events (core + remote) Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 08/25] Thread options & clone events (Linux GDBserver) Pedro Alves
                   ` (17 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

This commit teaches the native Linux target about the GDB_TO_CLONE
thread option.  It's actually simpler to just continue reporting all
clone events unconditionally to the core.  There's never any harm in
reporting a clone event when the option is disabled.  All we need to
do is copy the thread options from clone parent to clone child, and
report support for the option, otherwise GDB falls back to use
target_thread_events().

Change-Id: If271f20320d864f074d8ac0d531cc1a323da847f
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
---
 gdb/linux-nat.c | 26 +++++++++++++++++++++++++-
 gdb/linux-nat.h |  3 +++
 2 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 138632667e4..a446a755242 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1863,6 +1863,8 @@ linux_nat_target::follow_clone (ptid_t child_ptid)
   lwp_info *new_lp = add_lwp (child_ptid);
   new_lp->stopped = 1;
 
+  thread_info *new_thr = nullptr;
+
   /* If the thread_db layer is active, let it record the user
      level thread id and status, and add the thread to GDB's
      list.  */
@@ -1871,12 +1873,19 @@ linux_nat_target::follow_clone (ptid_t child_ptid)
       /* The process is not using thread_db.  Add the LWP to
 	 GDB's list.  */
       target_post_attach (new_lp->ptid.lwp ());
-      add_thread (linux_target, new_lp->ptid);
+      new_thr = add_thread (linux_target, new_lp->ptid);
     }
+  else
+    new_thr = find_thread_ptid (linux_target, new_lp->ptid);
+
+  gdb_assert (new_thr != nullptr);
 
   /* We just created NEW_LP so it cannot yet contain STATUS.  */
   gdb_assert (new_lp->status == 0);
 
+  /* Copy parent options to the child.  */
+  new_thr->set_thread_options (inferior_thread ()->thread_options ());
+
   if (!pull_pid_from_list (&stopped_pids, child_ptid.lwp (), &new_lp->status))
     internal_error (__FILE__, __LINE__, _("no saved status for clone lwp"));
 
@@ -4397,6 +4406,21 @@ linux_nat_target::thread_events (int enable)
   report_thread_events = enable;
 }
 
+bool
+linux_nat_target::supports_set_thread_options (gdb_thread_options options)
+{
+  constexpr gdb_thread_options supported_options = GDB_TO_CLONE;
+  return ((options & supported_options) == options);
+}
+
+void
+linux_nat_target::set_thread_options (thread_info *thr,
+				      gdb_thread_options options)
+{
+  /* Nothing to do.  target_set_thread_options already recorded the
+     options in THR.  */
+}
+
 linux_nat_target::linux_nat_target ()
 {
   /* We don't change the stratum; this target will sit at
diff --git a/gdb/linux-nat.h b/gdb/linux-nat.h
index 683173dbd38..b848c9bac6a 100644
--- a/gdb/linux-nat.h
+++ b/gdb/linux-nat.h
@@ -82,6 +82,9 @@ class linux_nat_target : public inf_ptrace_target
 
   void thread_events (int) override;
 
+  bool supports_set_thread_options (gdb_thread_options options) override;
+  void set_thread_options (thread_info *, gdb_thread_options) override;
+
   bool can_async_p () override;
 
   bool supports_non_stop () override;
-- 
2.36.0


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

* [PATCH 08/25] Thread options & clone events (Linux GDBserver)
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (6 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 07/25] Thread options & clone events (native Linux) Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 09/25] gdbserver: Hide and don't detach pending clone children Pedro Alves
                   ` (16 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

This patch teaches the Linux GDBserver backend to report clone events
to GDB, when GDB has requested them with the GDB_TO_CLONE thread
option, via the new QThreadOptions packet.

This shuffles code in linux_process_target::handle_extended_wait
around to a more logical order when we now have to handle and
potentially report all of fork/vfork/clone.

Change-Id: If271f20320d864f074d8ac0d531cc1a323da847f
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
---
 gdbserver/linux-low.cc | 240 +++++++++++++++++++++++------------------
 gdbserver/linux-low.h  |   2 +
 2 files changed, 135 insertions(+), 107 deletions(-)

diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 8b8614f6ed4..1c76a9774d6 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -491,7 +491,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
   struct lwp_info *event_lwp = *orig_event_lwp;
   int event = linux_ptrace_get_extended_event (wstat);
   struct thread_info *event_thr = get_lwp_thread (event_lwp);
-  struct lwp_info *new_lwp;
 
   gdb_assert (event_lwp->waitstatus.kind () == TARGET_WAITKIND_IGNORE);
 
@@ -503,7 +502,6 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
   if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK)
       || (event == PTRACE_EVENT_CLONE))
     {
-      ptid_t ptid;
       unsigned long new_pid;
       int ret, status;
 
@@ -527,61 +525,68 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
 	    warning ("wait returned unexpected status 0x%x", status);
 	}
 
-      if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
+      if (debug_threads)
 	{
-	  struct process_info *parent_proc;
-	  struct process_info *child_proc;
-	  struct lwp_info *child_lwp;
-	  struct thread_info *child_thr;
+	  debug_printf ("HEW: Got %s event from LWP %ld, new child is %ld\n",
+			(event == PTRACE_EVENT_FORK ? "fork"
+			 : event == PTRACE_EVENT_VFORK ? "vfork"
+			 : event == PTRACE_EVENT_CLONE ? "clone"
+			 : "???"),
+			ptid_of (event_thr).lwp (),
+			new_pid);
+	}
+
+      ptid_t child_ptid = (event != PTRACE_EVENT_CLONE
+			   ? ptid_t (new_pid, new_pid)
+			   : ptid_t (ptid_of (event_thr).pid (), new_pid));
 
-	  ptid = ptid_t (new_pid, new_pid);
+      lwp_info *child_lwp = add_lwp (child_ptid);
+      gdb_assert (child_lwp != NULL);
+      child_lwp->stopped = 1;
+      if (event != PTRACE_EVENT_CLONE)
+	child_lwp->must_set_ptrace_flags = 1;
+      child_lwp->status_pending_p = 0;
 
-	  threads_debug_printf ("Got fork event from LWP %ld, "
-				"new child is %d",
-				ptid_of (event_thr).lwp (),
-				ptid.pid ());
+      thread_info *child_thr = get_lwp_thread (child_lwp);
+
+      /* Copy parent options to the child.  */
+      child_thr->thread_options = event_thr->thread_options;
+
+      /* If we're suspending all threads, leave this one suspended
+	 too.  If the fork/clone parent is stepping over a breakpoint,
+	 all other threads have been suspended already.  Leave the
+	 child suspended too.  */
+      if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
+	  || event_lwp->bp_reinsert != 0)
+	{
+	  threads_debug_printf ("leaving child suspended");
+	  child_lwp->suspended = 1;
+	}
 
+      if (event_lwp->bp_reinsert != 0
+	  && supports_software_single_step ()
+	  && event == PTRACE_EVENT_VFORK)
+	{
+	  /* If we leave single-step breakpoints there, child will
+	     hit it, so uninsert single-step breakpoints from parent
+	     (and child).  Once vfork child is done, reinsert
+	     them back to parent.  */
+	  uninsert_single_step_breakpoints (event_thr);
+	}
+
+      if (event != PTRACE_EVENT_CLONE)
+	{
 	  /* Add the new process to the tables and clone the breakpoint
 	     lists of the parent.  We need to do this even if the new process
 	     will be detached, since we will need the process object and the
 	     breakpoints to remove any breakpoints from memory when we
 	     detach, and the client side will access registers.  */
-	  child_proc = add_linux_process (new_pid, 0);
+	  process_info *child_proc = add_linux_process (new_pid, 0);
 	  gdb_assert (child_proc != NULL);
-	  child_lwp = add_lwp (ptid);
-	  gdb_assert (child_lwp != NULL);
-	  child_lwp->stopped = 1;
-	  child_lwp->must_set_ptrace_flags = 1;
-	  child_lwp->status_pending_p = 0;
-	  child_thr = get_lwp_thread (child_lwp);
-	  child_thr->last_resume_kind = resume_stop;
-	  child_thr->last_status.set_stopped (GDB_SIGNAL_0);
-
-	  /* If we're suspending all threads, leave this one suspended
-	     too.  If the fork/clone parent is stepping over a breakpoint,
-	     all other threads have been suspended already.  Leave the
-	     child suspended too.  */
-	  if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
-	      || event_lwp->bp_reinsert != 0)
-	    {
-	      threads_debug_printf ("leaving child suspended");
-	      child_lwp->suspended = 1;
-	    }
 
-	  parent_proc = get_thread_process (event_thr);
+	  process_info *parent_proc = get_thread_process (event_thr);
 	  child_proc->attached = parent_proc->attached;
 
-	  if (event_lwp->bp_reinsert != 0
-	      && supports_software_single_step ()
-	      && event == PTRACE_EVENT_VFORK)
-	    {
-	      /* If we leave single-step breakpoints there, child will
-		 hit it, so uninsert single-step breakpoints from parent
-		 (and child).  Once vfork child is done, reinsert
-		 them back to parent.  */
-	      uninsert_single_step_breakpoints (event_thr);
-	    }
-
 	  clone_all_breakpoints (child_thr, event_thr);
 
 	  target_desc_up tdesc = allocate_target_description ();
@@ -590,88 +595,97 @@ linux_process_target::handle_extended_wait (lwp_info **orig_event_lwp,
 
 	  /* Clone arch-specific process data.  */
 	  low_new_fork (parent_proc, child_proc);
+	}
 
-	  /* Save fork info in the parent thread.  */
-	  if (event == PTRACE_EVENT_FORK)
-	    event_lwp->waitstatus.set_forked (ptid);
-	  else if (event == PTRACE_EVENT_VFORK)
-	    event_lwp->waitstatus.set_vforked (ptid);
-
+      /* Save fork/clone info in the parent thread.  */
+      if (event == PTRACE_EVENT_FORK)
+	event_lwp->waitstatus.set_forked (child_ptid);
+      else if (event == PTRACE_EVENT_VFORK)
+	event_lwp->waitstatus.set_vforked (child_ptid);
+      else if (event == PTRACE_EVENT_CLONE
+	       && (event_thr->thread_options & GDB_TO_CLONE) != 0)
+	event_lwp->waitstatus.set_thread_cloned (child_ptid);
+
+      if (event != PTRACE_EVENT_CLONE
+	  || (event_thr->thread_options & GDB_TO_CLONE) != 0)
+	{
 	  /* The status_pending field contains bits denoting the
-	     extended event, so when the pending event is handled,
-	     the handler will look at lwp->waitstatus.  */
+	     extended event, so when the pending event is handled, the
+	     handler will look at lwp->waitstatus.  */
 	  event_lwp->status_pending_p = 1;
 	  event_lwp->status_pending = wstat;
 
-	  /* Link the threads until the parent event is passed on to
-	     higher layers.  */
+	  /* Link the threads until the parent's event is passed on to
+	     GDB.  */
 	  event_lwp->fork_relative = child_lwp;
 	  child_lwp->fork_relative = event_lwp;
-
-	  /* If the parent thread is doing step-over with single-step
-	     breakpoints, the list of single-step breakpoints are cloned
-	     from the parent's.  Remove them from the child process.
-	     In case of vfork, we'll reinsert them back once vforked
-	     child is done.  */
-	  if (event_lwp->bp_reinsert != 0
-	      && supports_software_single_step ())
-	    {
-	      /* The child process is forked and stopped, so it is safe
-		 to access its memory without stopping all other threads
-		 from other processes.  */
-	      delete_single_step_breakpoints (child_thr);
-
-	      gdb_assert (has_single_step_breakpoints (event_thr));
-	      gdb_assert (!has_single_step_breakpoints (child_thr));
-	    }
-
-	  /* Report the event.  */
-	  return 0;
 	}
 
-      threads_debug_printf
-	("Got clone event from LWP %ld, new child is LWP %ld",
-	 lwpid_of (event_thr), new_pid);
-
-      ptid = ptid_t (pid_of (event_thr), new_pid);
-      new_lwp = add_lwp (ptid);
-
-      /* Either we're going to immediately resume the new thread
-	 or leave it stopped.  resume_one_lwp is a nop if it
-	 thinks the thread is currently running, so set this first
-	 before calling resume_one_lwp.  */
-      new_lwp->stopped = 1;
+      /* If the parent thread is doing step-over with single-step
+	 breakpoints, the list of single-step breakpoints are cloned
+	 from the parent's.  Remove them from the child process.
+	 In case of vfork, we'll reinsert them back once vforked
+	 child is done.  */
+      if (event_lwp->bp_reinsert != 0
+	  && supports_software_single_step ())
+	{
+	  /* The child process is forked and stopped, so it is safe
+	     to access its memory without stopping all other threads
+	     from other processes.  */
+	  delete_single_step_breakpoints (child_thr);
 
-      /* If we're suspending all threads, leave this one suspended
-	 too.  If the fork/clone parent is stepping over a breakpoint,
-	 all other threads have been suspended already.  Leave the
-	 child suspended too.  */
-      if (stopping_threads == STOPPING_AND_SUSPENDING_THREADS
-	  || event_lwp->bp_reinsert != 0)
-	new_lwp->suspended = 1;
+	  gdb_assert (has_single_step_breakpoints (event_thr));
+	  gdb_assert (!has_single_step_breakpoints (child_thr));
+	}
 
       /* Normally we will get the pending SIGSTOP.  But in some cases
 	 we might get another signal delivered to the group first.
 	 If we do get another signal, be sure not to lose it.  */
       if (WSTOPSIG (status) != SIGSTOP)
 	{
-	  new_lwp->stop_expected = 1;
-	  new_lwp->status_pending_p = 1;
-	  new_lwp->status_pending = status;
+	  child_lwp->stop_expected = 1;
+	  child_lwp->status_pending_p = 1;
+	  child_lwp->status_pending = status;
 	}
-      else if (cs.report_thread_events)
+      else if (event == PTRACE_EVENT_CLONE && cs.report_thread_events)
 	{
-	  new_lwp->waitstatus.set_thread_created ();
-	  new_lwp->status_pending_p = 1;
-	  new_lwp->status_pending = status;
+	  child_lwp->waitstatus.set_thread_created ();
+	  child_lwp->status_pending_p = 1;
+	  child_lwp->status_pending = status;
 	}
 
+      if (event == PTRACE_EVENT_CLONE)
+	{
 #ifdef USE_THREAD_DB
-      thread_db_notice_clone (event_thr, ptid);
+	  thread_db_notice_clone (event_thr, child_ptid);
 #endif
+	}
 
-      /* Don't report the event.  */
-      return 1;
+      if (event == PTRACE_EVENT_CLONE
+	  && (event_thr->thread_options & GDB_TO_CLONE) == 0)
+	{
+	  threads_debug_printf
+	    ("not reporting clone event from LWP %ld, new child is %ld\n",
+	     ptid_of (event_thr).lwp (),
+	     new_pid);
+	  return 1;
+	}
+
+      /* Leave the child stopped until GDB processes the parent
+	 event.  */
+      child_thr->last_resume_kind = resume_stop;
+      child_thr->last_status.set_stopped (GDB_SIGNAL_0);
+
+      /* Report the event.  */
+      threads_debug_printf
+	("reporting %s event from LWP %ld, new child is %ld\n",
+	 (event == PTRACE_EVENT_FORK ? "fork"
+	  : event == PTRACE_EVENT_VFORK ? "vfork"
+	  : event == PTRACE_EVENT_CLONE ? "clone"
+	  : "???"),
+	 ptid_of (event_thr).lwp (),
+	 new_pid);
+      return 0;
     }
   else if (event == PTRACE_EVENT_VFORK_DONE)
     {
@@ -3521,7 +3535,8 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
 
       /* Break the unreported fork relationship chain.  */
       if (event_child->waitstatus.kind () == TARGET_WAITKIND_FORKED
-	  || event_child->waitstatus.kind () == TARGET_WAITKIND_VFORKED)
+	  || event_child->waitstatus.kind () == TARGET_WAITKIND_VFORKED
+	  || event_child->waitstatus.kind () == TARGET_WAITKIND_THREAD_CLONED)
 	{
 	  event_child->fork_relative->fork_relative = NULL;
 	  event_child->fork_relative = NULL;
@@ -4260,15 +4275,16 @@ linux_set_resume_request (thread_info *thread, thread_resume *resume, size_t n)
 	      continue;
 	    }
 
-	  /* Don't let wildcard resumes resume fork children that GDB
-	     does not yet know are new fork children.  */
+	  /* Don't let wildcard resumes resume fork/clone children
+	     that GDB does not yet know are new children.  */
 	  if (lwp->fork_relative != NULL)
 	    {
 	      struct lwp_info *rel = lwp->fork_relative;
 
 	      if (rel->status_pending_p
 		  && (rel->waitstatus.kind () == TARGET_WAITKIND_FORKED
-		      || rel->waitstatus.kind () == TARGET_WAITKIND_VFORKED))
+		      || rel->waitstatus.kind () == TARGET_WAITKIND_VFORKED
+		      || rel->waitstatus.kind () == TARGET_WAITKIND_THREAD_CLONED))
 		{
 		  threads_debug_printf
 		    ("not resuming LWP %ld: has queued stop reply",
@@ -5889,6 +5905,16 @@ linux_process_target::supports_vfork_events ()
   return true;
 }
 
+/* Check if thread options are supported.  */
+
+bool
+linux_process_target::supports_set_thread_options
+  (gdb_thread_options *supported_options)
+{
+  *supported_options = GDB_TO_CLONE;
+  return true;
+}
+
 /* Check if exec events are supported.  */
 
 bool
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index 79be31b8f72..c81b93f1171 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -234,6 +234,8 @@ class linux_process_target : public process_stratum_target
 
   bool supports_vfork_events () override;
 
+  bool supports_set_thread_options (gdb_thread_options *) override;
+
   bool supports_exec_events () override;
 
   void handle_new_gdb_connection () override;
-- 
2.36.0


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

* [PATCH 09/25] gdbserver: Hide and don't detach pending clone children
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (7 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 08/25] Thread options & clone events (Linux GDBserver) Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 10/25] Remove gdb/19675 kfails (displaced stepping + clone) Pedro Alves
                   ` (15 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

This commit extends the logic added by these two commits from a while
ago:

 #1  7b961964f866  (gdbserver: hide fork child threads from GDB),
 #2  df5ad102009c  (gdb, gdbserver: detach fork child when detaching from fork parent)

... to handle thread clone events, which are very similar to (v)fork
events.

For #1, we want to hide clone children as well, so just update the
comments.

For #2, unlike (v)fork children, pending clone children aren't full
processes, they're just threads, so don't detach them in
handle_detach.  linux-low.cc will take care of detaching them along
with all other threads of the process, there's nothing special that
needs to be done.

Change-Id: I7f5901d07efda576a2522d03e183994e071b8ffc
---
 gdbserver/linux-low.cc |  5 +++--
 gdbserver/linux-low.h  | 36 ++++++++++++++++++++----------------
 gdbserver/server.cc    | 12 +++++++-----
 gdbserver/target.cc    |  3 ++-
 gdbserver/target.h     | 15 ++++++++-------
 5 files changed, 40 insertions(+), 31 deletions(-)

diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 1c76a9774d6..8bfcd5bf740 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -6869,9 +6869,10 @@ linux_process_target::thread_pending_parent (thread_info *thread)
 }
 
 thread_info *
-linux_process_target::thread_pending_child (thread_info *thread)
+linux_process_target::thread_pending_child (thread_info *thread,
+					    target_waitkind *kind)
 {
-  lwp_info *child = get_thread_lwp (thread)->pending_child ();
+  lwp_info *child = get_thread_lwp (thread)->pending_child (kind);
 
   if (child == nullptr)
     return nullptr;
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index c81b93f1171..e7237bba3b3 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -313,7 +313,8 @@ class linux_process_target : public process_stratum_target
 #endif
 
   thread_info *thread_pending_parent (thread_info *thread) override;
-  thread_info *thread_pending_child (thread_info *thread) override;
+  thread_info *thread_pending_child (thread_info *thread,
+				     target_waitkind *kind) override;
 
   bool supports_catch_syscall () override;
 
@@ -732,8 +733,8 @@ struct pending_signal
 
 struct lwp_info
 {
-  /* If this LWP is a fork child that wasn't reported to GDB yet, return
-     its parent, else nullptr.  */
+  /* If this LWP is a fork/vfork/clone child that wasn't reported to
+     GDB yet, return its parent, else nullptr.  */
   lwp_info *pending_parent () const
   {
     if (this->fork_relative == nullptr)
@@ -741,10 +742,10 @@ struct lwp_info
 
     gdb_assert (this->fork_relative->fork_relative == this);
 
-    /* In a fork parent/child relationship, the parent has a status pending and
-       the child does not, and a thread can only be in one such relationship
-       at most.  So we can recognize who is the parent based on which one has
-       a pending status.  */
+    /* In a parent/child relationship, the parent has a status pending
+       and the child does not, and a thread can only be in one such
+       relationship at most.  So we can recognize who is the parent
+       based on which one has a pending status.  */
     gdb_assert (!!this->status_pending_p
 		!= !!this->fork_relative->status_pending_p);
 
@@ -754,24 +755,25 @@ struct lwp_info
     const target_waitstatus &ws
       = this->fork_relative->waitstatus;
     gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
-		|| ws.kind () == TARGET_WAITKIND_VFORKED);
+		|| ws.kind () == TARGET_WAITKIND_VFORKED
+		|| ws.kind () == TARGET_WAITKIND_THREAD_CLONED);
 
     return this->fork_relative;
   }
 
-  /* If this LWP is the parent of a fork child we haven't reported to GDB yet,
-     return that child, else nullptr.  */
-  lwp_info *pending_child () const
+  /* If this LWP is the parent of a fork/vfork/clone child we haven't
+     reported to GDB yet, return that child, else nullptr.  */
+  lwp_info *pending_child (target_waitkind *kind) const
   {
     if (this->fork_relative == nullptr)
       return nullptr;
 
     gdb_assert (this->fork_relative->fork_relative == this);
 
-    /* In a fork parent/child relationship, the parent has a status pending and
-       the child does not, and a thread can only be in one such relationship
-       at most.  So we can recognize who is the parent based on which one has
-       a pending status.  */
+    /* In a parent/child relationship, the parent has a status pending
+       and the child does not, and a thread can only be in one such
+       relationship at most.  So we can recognize who is the parent
+       based on which one has a pending status.  */
     gdb_assert (!!this->status_pending_p
 		!= !!this->fork_relative->status_pending_p);
 
@@ -780,8 +782,10 @@ struct lwp_info
 
     const target_waitstatus &ws = this->waitstatus;
     gdb_assert (ws.kind () == TARGET_WAITKIND_FORKED
-		|| ws.kind () == TARGET_WAITKIND_VFORKED);
+		|| ws.kind () == TARGET_WAITKIND_VFORKED
+		|| ws.kind () == TARGET_WAITKIND_THREAD_CLONED);
 
+    *kind = ws.kind ();
     return this->fork_relative;
   }
 
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index 2b78ba7292d..45b8ac60db0 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -1338,8 +1338,9 @@ handle_detach (char *own_buf)
 	continue;
 
       /* Only threads that have a pending fork event.  */
-      thread_info *child = target_thread_pending_child (thread);
-      if (child == nullptr)
+      target_waitkind kind;
+      thread_info *child = target_thread_pending_child (thread, &kind);
+      if (child == nullptr || kind == TARGET_WAITKIND_THREAD_CLONED)
 	continue;
 
       process_info *fork_child_process = get_thread_process (child);
@@ -1759,9 +1760,10 @@ handle_qxfer_threads_worker (thread_info *thread, struct buffer *buffer)
   gdb_byte *handle;
   bool handle_status = target_thread_handle (ptid, &handle, &handle_len);
 
-  /* If this is a fork or vfork child (has a fork parent), GDB does not yet
-     know about this process, and must not know about it until it gets the
-     corresponding (v)fork event.  Exclude this thread from the list.  */
+  /* If this is a (v)fork/clone child (has a (v)fork/clone parent),
+     GDB does not yet know about this thread, and must not know about
+     it until it gets the corresponding (v)fork/clone event.  Exclude
+     this thread from the list.  */
   if (target_thread_pending_parent (thread) != nullptr)
     return;
 
diff --git a/gdbserver/target.cc b/gdbserver/target.cc
index 70fec6d2078..ad32af0416e 100644
--- a/gdbserver/target.cc
+++ b/gdbserver/target.cc
@@ -808,7 +808,8 @@ process_stratum_target::thread_pending_parent (thread_info *thread)
 }
 
 thread_info *
-process_stratum_target::thread_pending_child (thread_info *thread)
+process_stratum_target::thread_pending_child (thread_info *thread,
+					      target_waitkind *kind)
 {
   return nullptr;
 }
diff --git a/gdbserver/target.h b/gdbserver/target.h
index 33142363a02..0aa7db49c18 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -479,13 +479,14 @@ class process_stratum_target
   virtual bool thread_handle (ptid_t ptid, gdb_byte **handle,
 			      int *handle_len);
 
-  /* If THREAD is a fork child that was not reported to GDB, return its parent
-     else nullptr.  */
+  /* If THREAD is a fork/vfork/clone child that was not reported to
+     GDB, return its parent else nullptr.  */
   virtual thread_info *thread_pending_parent (thread_info *thread);
 
-  /* If THREAD is the parent of a fork child that was not reported to GDB,
-     return this child, else nullptr.  */
-  virtual thread_info *thread_pending_child (thread_info *thread);
+  /* If THREAD is the parent of a fork/vfork/clone child that was not
+     reported to GDB, return this child, else nullptr.  */
+  virtual thread_info *thread_pending_child (thread_info *thread,
+					     target_waitkind *kind);
 
   /* Returns true if the target can software single step.  */
   virtual bool supports_software_single_step ();
@@ -701,9 +702,9 @@ target_thread_pending_parent (thread_info *thread)
 }
 
 static inline thread_info *
-target_thread_pending_child (thread_info *thread)
+target_thread_pending_child (thread_info *thread, target_waitkind *kind)
 {
-  return the_target->thread_pending_child (thread);
+  return the_target->thread_pending_child (thread, kind);
 }
 
 int read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len);
-- 
2.36.0


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

* [PATCH 10/25] Remove gdb/19675 kfails (displaced stepping + clone)
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (8 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 09/25] gdbserver: Hide and don't detach pending clone children Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 11/25] Add test for stepping over clone syscall Pedro Alves
                   ` (14 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

Now that gdb/19675 is fixed for both native and gdbserver GNU/Linux,
remove the gdb/19675 kfails.

Change-Id: I95c1c38ca370100675d303cd3c8995860bef465d
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
---
 gdb/testsuite/gdb.base/step-over-syscall.exp | 44 ++------------------
 1 file changed, 3 insertions(+), 41 deletions(-)

diff --git a/gdb/testsuite/gdb.base/step-over-syscall.exp b/gdb/testsuite/gdb.base/step-over-syscall.exp
index e87d391cd5f..8f22d46b709 100644
--- a/gdb/testsuite/gdb.base/step-over-syscall.exp
+++ b/gdb/testsuite/gdb.base/step-over-syscall.exp
@@ -42,46 +42,17 @@ if { [istarget "i\[34567\]86-*-linux*"] || [istarget "x86_64-*-linux*"] } {
 }
 
 proc_with_prefix check_pc_after_cross_syscall { displaced syscall syscall_insn_next_addr } {
-    global gdb_prompt
-
     set syscall_insn_next_addr_found [get_hexadecimal_valueof "\$pc" "0"]
 
     # After the 'stepi' we expect thread 1 to still be selected.
-    # However, when displaced stepping over a clone bug gdb/19675
-    # means this might not be the case.
-    #
-    # Which thread we end up in depends on a race between the original
-    # thread-1, and the new thread (created by the clone), so we can't
-    # guarantee which thread we will be in at this point.
-    #
-    # For the fork/vfork syscalls, which are correctly handled by
-    # displaced stepping we will always be in thread-1 or the original
-    # process at this point.
     set curr_thread "unknown"
-    gdb_test_multiple "info threads" "" {
-	-re "Id\\s+Target Id\\s+Frame\\s*\r\n" {
-	    exp_continue
-	}
-	-re "^\\* (\\d+)\\s+\[^\r\n\]+\r\n" {
+    gdb_test_multiple "thread" "" {
+	-re -wrap "Current thread is (\\d+) .*" {
 	    set curr_thread $expect_out(1,string)
-	    exp_continue
-	}
-	-re "^\\s+\\d+\\s+\[^\r\n\]+\r\n" {
-	    exp_continue
-	}
-	-re "$gdb_prompt " {
+	    pass $gdb_test_name
 	}
     }
 
-    # If we are displaced stepping over a clone, and we ended up in
-    # the wrong thread then the following check of the $pc value will
-    # fail.
-    if { $displaced == "on" && $syscall == "clone" && $curr_thread != 1 } {
-	# GDB doesn't support stepping over clone syscall with
-	# displaced stepping.
-	setup_kfail "*-*-*" "gdb/19675"
-    }
-
     gdb_assert {$syscall_insn_next_addr != 0 \
       && $syscall_insn_next_addr == $syscall_insn_next_addr_found \
       && $curr_thread == 1} \
@@ -294,15 +265,6 @@ proc step_over_syscall { syscall } {
 
 	    gdb_test "break marker" "Breakpoint.*at.* file .*${testfile}.c, line.*"
 
-	    # If we are displaced stepping over a clone syscall then
-	    # we expect the following check to fail.  See also the
-	    # code in check_pc_after_cross_syscall.
-	    if { $displaced == "on" && $syscall == "clone" } {
-		# GDB doesn't support stepping over clone syscall with
-		# displaced stepping.
-		setup_kfail "*-*-*" "gdb/19675"
-	    }
-
 	    gdb_test "continue" "Continuing\\..*Breakpoint \[0-9\]+, marker \\(\\) at.*" \
 		"continue to marker ($syscall)"
 	}
-- 
2.36.0


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

* [PATCH 11/25] Add test for stepping over clone syscall
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (9 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 10/25] Remove gdb/19675 kfails (displaced stepping + clone) Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 12/25] all-stop/synchronous RSP support thread-exit events Pedro Alves
                   ` (13 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches; +Cc: Pedro Alves

From: Andrew Burgess <andrew.burgess@embecosm.com>

- New in this in series version:

  Fixed race, remove end anchor after prompt.

  Fix leading anchor and inferior output flushing issue against
  gdbserver.

  Now works with displaced stepping.

  Avoid printing inferior addresses in gdb.sum messages

  Fail when clone child thread is stuck on scratchpad

  Misc minor tweaks throughout.

This adds a new gdb.threads/stepi-over-clone.exp testcase, which
exercises stepping over a clone syscall, with displaced stepping vs
inline stepping, and all-stop vs non-stop.  We already test stepping
over clone syscalls with gdb.base/step-over-syscall.exp, but this test
uses pthreads, while the other test uses raw clone, and this one is
more thorough.

Co-authored-by: Pedro Alves <pedro@palves.net>
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
Change-Id: I95c06024736384ae8542a67ed9fdf6534c325c8e
---
 gdb/testsuite/gdb.threads/stepi-over-clone.c  |  90 ++++
 .../gdb.threads/stepi-over-clone.exp          | 392 ++++++++++++++++++
 2 files changed, 482 insertions(+)
 create mode 100644 gdb/testsuite/gdb.threads/stepi-over-clone.c
 create mode 100644 gdb/testsuite/gdb.threads/stepi-over-clone.exp

diff --git a/gdb/testsuite/gdb.threads/stepi-over-clone.c b/gdb/testsuite/gdb.threads/stepi-over-clone.c
new file mode 100644
index 00000000000..d54d9f1c0bf
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/stepi-over-clone.c
@@ -0,0 +1,90 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2022 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/>.  */
+
+#include <stdio.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+
+/* Set this to non-zero from GDB to start a third worker thread.  */
+volatile int start_third_thread = 0;
+
+void *
+thread_worker_2 (void *arg)
+{
+  int i;
+
+  printf ("Hello from the third thread.\n");
+  fflush (stdout);
+
+  for (i = 0; i < 600; ++i)
+    sleep (1);
+
+  return NULL;
+}
+
+void *
+thread_worker_1 (void *arg)
+{
+  int i;
+  pthread_t thr;
+  void *val;
+
+  if (start_third_thread)
+    pthread_create (&thr, NULL, thread_worker_2, NULL);
+
+  printf ("Hello from the first thread.\n");
+  fflush (stdout);
+
+  for (i = 0; i < 300; ++i)
+    sleep (1);
+
+  if (start_third_thread)
+    pthread_join (thr, &val);
+
+  return NULL;
+}
+
+void *
+thread_idle_loop (void *arg)
+{
+  int i;
+
+  for (i = 0; i < 600; ++i)
+    sleep (1);
+
+  return NULL;
+}
+
+int
+main ()
+{
+  pthread_t thr, thr_idle;
+  void *val;
+
+  if (getenv ("MAKE_EXTRA_THREAD") != NULL)
+    pthread_create (&thr_idle, NULL, thread_idle_loop, NULL);
+
+  pthread_create (&thr, NULL, thread_worker_1, NULL);
+  pthread_join (thr, &val);
+
+  if (getenv ("MAKE_EXTRA_THREAD") != NULL)
+    pthread_join (thr_idle, &val);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/stepi-over-clone.exp b/gdb/testsuite/gdb.threads/stepi-over-clone.exp
new file mode 100644
index 00000000000..9ccf68d3ebe
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/stepi-over-clone.exp
@@ -0,0 +1,392 @@
+# Copyright 2021-2022 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 performing a 'stepi' over a clone syscall instruction.
+
+# This test relies on us being able to spot syscall instructions in
+# disassembly output.  For now this is only implemented for x86-64.
+if { ![istarget x86_64-*-* ] } {
+    return
+}
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  {debug pthreads additional_flags=-static}] } {
+    return
+}
+
+if {![runto_main]} {
+    return
+}
+
+# Arrange to catch the 'clone' syscall, run until we catch the
+# syscall, and try to figure out the address of the actual syscall
+# instruction so we can place a breakpoint at this address.
+
+gdb_test_multiple "catch syscall clone" "" {
+    -re "The feature \'catch syscall\' is not supported.*\r\n$gdb_prompt $" {
+	set supported 0
+	pass $gdb_test_name
+	return
+    }
+    -re ".*$gdb_prompt $" {
+	pass $gdb_test_name
+    }
+}
+
+gdb_test "continue" \
+    "Catchpoint $decimal \\(call to syscall clone\\), $hex in clone \\(\\)"
+
+# Return true if INSN is a syscall instruction.
+
+proc is_syscall_insn { insn } {
+    if [istarget x86_64-*-* ] {
+	return { $insn == "syscall" }
+    } else {
+	error "port me"
+    }
+}
+
+# A list of addresses with syscall instructions.
+set syscall_addrs {}
+
+# Get list of addresses with syscall instructions.
+gdb_test_multiple "disassemble" "" {
+    -re "Dump of assembler code for function \[^\r\n\]+:\r\n" {
+	exp_continue
+    }
+    -re "^(?:=>)?\\s+(${hex})\\s+<\\+${decimal}>:\\s+(\[^\r\n\]+)\r\n" {
+	set addr $expect_out(1,string)
+	set insn [string trim $expect_out(2,string)]
+	if [is_syscall_insn $insn] {
+	    verbose -log "Found a syscall at: $addr"
+	    lappend syscall_addrs $addr
+	}
+	exp_continue
+    }
+    -re "^End of assembler dump\\.\r\n$gdb_prompt $" {
+	if { [llength $syscall_addrs] == 0 } {
+	    unsupported "no syscalls found"
+	    return -1
+	}
+    }
+}
+
+# The test proc.  NON_STOP and DISPLACED are either 'on' or 'off', and are
+# used to configure how GDB starts up.  THIRD_THREAD is either true or false,
+# and is used to configure the inferior.
+proc test {non_stop displaced third_thread} {
+    global binfile srcfile
+    global syscall_addrs
+    global GDBFLAGS
+    global gdb_prompt hex decimal
+
+    for { set i 0 } { $i < 3 } { incr i } {
+	with_test_prefix "i=$i" {
+
+	    # Arrange to start GDB in the correct mode.
+	    save_vars { GDBFLAGS } {
+		append GDBFLAGS " -ex \"set non-stop $non_stop\""
+		append GDBFLAGS " -ex \"set displaced $displaced\""
+		clean_restart $binfile
+	    }
+
+	    runto_main
+
+	    # Setup breakpoints at all the syscall instructions we
+	    # might hit.  Only issue one pass/fail to make tests more
+	    # comparable between systems.
+	    set test "break at syscall insns"
+	    foreach addr $syscall_addrs {
+		if {[gdb_test -nopass "break *$addr" \
+			 ".*" \
+			 $test] != 0} {
+		    return
+		}
+	    }
+	    # If we got here, all breakpoints were set successfully.
+	    # We used -nopass above, so issue a pass now.
+	    pass $test
+
+	    # Continue until we hit the syscall.
+	    gdb_test "continue"
+
+	    if { $third_thread } {
+		gdb_test_no_output "set start_third_thread=1"
+	    }
+
+	    set stepi_error_count 0
+	    set stepi_new_thread_count 0
+	    set thread_1_stopped false
+	    set thread_2_stopped false
+	    set seen_prompt false
+	    set hello_first_thread false
+
+	    # The program is now stopped at main, but if testing
+	    # against GDBserver, inferior_spawn_id is GDBserver's
+	    # spawn_id, and the GDBserver output emitted before the
+	    # program stopped isn't flushed unless we explicitly do
+	    # so, because it is on a different spawn_id.  We could try
+	    # flushing it now, to avoid confusing the following tests,
+	    # but that would have to be done under a timeout, and
+	    # would thus slow down the testcase.  Instead, if inferior
+	    # output goes to a different spawn id, then we don't need
+	    # to wait for the first message from the inferior with an
+	    # anchor, as we know consuming inferior output won't
+	    # consume GDB output.  OTOH, if inferior output is coming
+	    # out on GDB's terminal, then we must use an anchor,
+	    # otherwise matching inferior output without one could
+	    # consume GDB output that we are waiting for in regular
+	    # expressions that are written after the inferior output
+	    # regular expression match.
+	    if {$::inferior_spawn_id != $::gdb_spawn_id} {
+		set anchor ""
+	    } else {
+		set anchor "^"
+	    }
+
+	    gdb_test_multiple "stepi" "" {
+		-re "^stepi\r\n" {
+		    verbose -log "XXX: Consume the initial command"
+		    exp_continue
+		}
+		-re "^\\\[New Thread\[^\r\n\]+\\\]\r\n" {
+		    verbose -log "XXX: Consume new thread line"
+		    incr stepi_new_thread_count
+		    exp_continue
+		}
+		-re "^\\\[Switching to Thread\[^\r\n\]+\\\]\r\n" {
+		    verbose -log "XXX: Consume switching to thread line"
+		    exp_continue
+		}
+		-re "^\\s*\r\n" {
+		    verbose -log "XXX: Consume blank line"
+		    exp_continue
+		}
+
+		-i $::inferior_spawn_id
+
+		-re "${anchor}Hello from the first thread\\.\r\n" {
+		    set hello_first_thread true
+
+		    verbose -log "XXX: Consume first worker thread message"
+		    if { $third_thread } {
+			# If we are going to start a third thread then GDB
+			# should hit the breakpoint in clone before printing
+			# this message.
+			incr stepi_error_count
+		    }
+		    if { !$seen_prompt } {
+			exp_continue
+		    }
+		}
+		-re "^Hello from the third thread\\.\r\n" {
+		    # We should never see this message.
+		    verbose -log "XXX: Consume third worker thread message"
+		    incr stepi_error_count
+		    if { !$seen_prompt } {
+			exp_continue
+		    }
+		}
+
+		-i $::gdb_spawn_id
+
+		-re "^$hex in clone \\(\\)\r\n" {
+		    verbose -log "XXX: Consume stop location line"
+		    set thread_1_stopped true
+		    if { !$seen_prompt } {
+			verbose -log "XXX: Continuing to look for the prompt"
+			exp_continue
+		    }
+		}
+		-re "^$gdb_prompt " {
+		    verbose -log "XXX: Consume the final prompt"
+		    gdb_assert { $stepi_error_count == 0 }
+		    gdb_assert { $stepi_new_thread_count == 1 }
+		    set seen_prompt true
+		    if { $third_thread } {
+			if { $non_stop } {
+			    # In non-stop mode if we are trying to start a
+			    # third thread (from the second thread), then the
+			    # second thread should hit the breakpoint in clone
+			    # before actually starting the third thread.  And
+			    # so, at this point both thread 1, and thread 2
+			    # should now be stopped.
+			    if { !$thread_1_stopped || !$thread_2_stopped } {
+				verbose -log "XXX: Continue looking for an additional stop event"
+				exp_continue
+			    }
+			} else {
+			    # All stop mode.  Something should have stoppped
+			    # by now otherwise we shouldn't have a prompt, but
+			    # we can't know which thread will have stopped as
+			    # that is a race condition.
+			    gdb_assert { $thread_1_stopped || $thread_2_stopped }
+			}
+		    }
+
+		    if {$non_stop && !$hello_first_thread} {
+			exp_continue
+		    }
+
+		}
+		-re "^Thread 2\[^\r\n\]+ hit Breakpoint $decimal, $hex in clone \\(\\)\r\n" {
+		    verbose -log "XXX: Consume thread 2 hit breakpoint"
+		    set thread_2_stopped true
+		    if { !$seen_prompt } {
+			verbose -log "XXX: Continuing to look for the prompt"
+			exp_continue
+		    }
+		}
+		-re "^PC register is not available\r\n" {
+		    # This is the error we'd see for remote targets.
+		    verbose -log "XXX: Consume error line"
+		    incr stepi_error_count
+		    exp_continue
+		}
+		-re "^Couldn't get registers: No such process\\.\r\n" {
+		    # This is the error we see'd for native linux
+		    # targets.
+		    verbose -log "XXX: Consume error line"
+		    incr stepi_error_count
+		    exp_continue
+		}
+	    }
+
+	    # Ensure we are back at a GDB prompt, resynchronise.
+	    verbose -log "XXX: Have completed scanning the 'stepi' output"
+	    gdb_test "p 1 + 2 + 3" " = 6"
+
+	    # Check the number of threads we have, it should be exactly two.
+	    set thread_count 0
+	    set bad_threads 0
+
+	    # Build up our expectations for what the current thread state
+	    # should be.  Thread 1 is the easiest, this is the thread we are
+	    # stepping, so this thread should always be stopped, and should
+	    # always still be in clone.
+	    set match_code {}
+	    lappend match_code {
+		-re "\\*?\\s+1\\s+Thread\[^\r\n\]+clone \\(\\)\r\n" {
+		    incr thread_count
+		    exp_continue
+		}
+	    }
+
+	    # What state should thread 2 be in?
+	    if { $non_stop == "on" } {
+		if { $third_thread } {
+		    # With non-stop mode on, and creation of a third thread
+		    # having been requested, we expect Thread 2 to exist, and
+		    # be stopped at the breakpoint in clone (just before the
+		    # third thread is actually created).
+		    lappend match_code {
+			-re "\\*?\\s+2\\s+Thread\[^\r\n\]+$hex in clone \\(\\)\r\n" {
+			    incr thread_count
+			    exp_continue
+			}
+			-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
+			    incr thread_count
+			    incr bad_threads
+			    exp_continue
+			}
+			-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
+			    verbose -log "XXX: thread 2 is bad, unknown state"
+			    incr thread_count
+			    incr bad_threads
+			    exp_continue
+			}
+		    }
+
+		} else {
+		    # With non-stop mode on, and no third thread having been
+		    # requested, then we expect Thread 2 to exist, and still
+		    # be running.
+		    lappend match_code {
+			-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
+			    incr thread_count
+			    exp_continue
+			}
+			-re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
+			    verbose -log "XXX: thread 2 is bad, unknown state"
+			    incr thread_count
+			    incr bad_threads
+			    exp_continue
+			}
+		    }
+		}
+	    } else {
+		# With non-stop mode off then we expect Thread 2 to exist, and
+		# be stopped.  We don't have any guarantee about where the
+		# thread will have stopped though, so we need to be vague.
+		lappend match_code {
+		    -re "\\*?\\s+2\\s+Thread\[^\r\n\]+\\(running\\)\r\n" {
+			verbose -log "XXX: thread 2 is bad, unexpectedly running"
+			incr thread_count
+			incr bad_threads
+			exp_continue
+		    }
+		    -re "\\*?\\s+2\\s+Thread\[^\r\n\]+_start\[^\r\n\]+\r\n" {
+			# We know that the thread shouldn't be stopped
+			# at _start, though.  This is the location of
+			# the scratch pad on Linux at the time of
+			# writting.
+			verbose -log "XXX: thread 2 is bad, stuck in scratchpad"
+			incr thread_count
+			incr bad_threads
+			exp_continue
+		    }
+		    -re "\\*?\\s+2\\s+Thread\[^\r\n\]+\r\n" {
+			incr thread_count
+			exp_continue
+		    }
+		}
+	    }
+
+	    # We don't expect to ever see a thread 3.  Even when we are
+	    # requesting that this third thread be created, thread 2, the
+	    # thread that creates thread 3, should stop before executing the
+	    # clone syscall.  So, if we do ever see this then something has
+	    # gone wrong.
+	    lappend match_code {
+		-re "\\s+3\\s+Thread\[^\r\n\]+\r\n" {
+		    incr thread_count
+		    incr bad_threads
+		    exp_continue
+		}
+	    }
+
+	    lappend match_code {
+		-re "$gdb_prompt $" {
+		    gdb_assert { $thread_count == 2 }
+		    gdb_assert { $bad_threads == 0 }
+		}
+	    }
+
+	    set match_code [join $match_code]
+	    gdb_test_multiple "info threads" "" $match_code
+	}
+    }
+}
+
+# Run the test in all suitable configurations.
+foreach_with_prefix third_thread { false true } {
+    foreach_with_prefix non-stop { "on" "off" } {
+	foreach_with_prefix displaced { "off" "on" } {
+	    test ${non-stop} ${displaced} ${third_thread}
+	}
+    }
+}
-- 
2.36.0


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

* [PATCH 12/25] all-stop/synchronous RSP support thread-exit events
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (10 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 11/25] Add test for stepping over clone syscall Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 13/25] Introduce GDB_TO_EXIT thread option, fix step-over-thread-exit Pedro Alves
                   ` (12 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

Currently, GDB does not understand the THREAD_EXITED stop reply in
remote all-stop mode.  There's no good reason for this, it just
happened that THREAD_EXITED was only ever reported in non-stop mode so
far.  This patch teaches GDB to parse that event in all-stop RSP too.
There is no need to add a qSupported feature for this, because the
server won't send a THREAD_EXITED event unless GDB explicitly asks for
it, with QThreadEvents, or with the GDB_TO_EXIT QThreadOptions option
added in the next patch.

Change-Id: Ide5d12391adf432779fe4c79526801c4a5630966
---
 gdb/remote.c        | 5 +++--
 gdbserver/server.cc | 1 +
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/gdb/remote.c b/gdb/remote.c
index 20f2b7f399d..5bfbe28b8c3 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -8179,7 +8179,8 @@ remote_target::process_stop_reply (struct stop_reply *stop_reply,
       && status->kind () != TARGET_WAITKIND_NO_RESUMED)
     {
       /* Expedited registers.  */
-      if (!stop_reply->regcache.empty ())
+      if (status->kind () != TARGET_WAITKIND_THREAD_EXITED
+	  && !stop_reply->regcache.empty ())
 	{
 	  struct regcache *regcache
 	    = get_thread_arch_regcache (this, ptid, stop_reply->arch);
@@ -8365,7 +8366,7 @@ remote_target::wait_as (ptid_t ptid, target_waitstatus *status,
 	     again.  Keep waiting for events.  */
 	  rs->waiting_for_stop_reply = 1;
 	  break;
-	case 'N': case 'T': case 'S': case 'X': case 'W':
+	case 'N': case 'T': case 'S': case 'X': case 'W': case 'w':
 	  {
 	    /* There is a stop reply to handle.  */
 	    rs->waiting_for_stop_reply = 0;
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index 45b8ac60db0..fc64a442ab4 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -3054,6 +3054,7 @@ resume (struct thread_resume *actions, size_t num_actions)
 
       if (cs.last_status.kind () != TARGET_WAITKIND_EXITED
 	  && cs.last_status.kind () != TARGET_WAITKIND_SIGNALLED
+	  && cs.last_status.kind () != TARGET_WAITKIND_THREAD_EXITED
 	  && cs.last_status.kind () != TARGET_WAITKIND_NO_RESUMED)
 	current_thread->last_status = cs.last_status;
 
-- 
2.36.0


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

* [PATCH 13/25] Introduce GDB_TO_EXIT thread option, fix step-over-thread-exit
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (11 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 12/25] all-stop/synchronous RSP support thread-exit events Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 14/25] Implement GDB_TO_EXIT support for Linux GDBserver Pedro Alves
                   ` (11 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

When stepping over a breakpoint with displaced stepping, GDB needs to
be informed if the stepped thread exits, otherwise the displaced
stepping buffer that was allocated to that thread leaks, and this can
result in deadlock, with other threads waiting for their turn to
displaced step, but their turn never comes.

Similarly, when stepping over a breakpoint in line, GDB also needs to
be informed if the stepped thread exits, so that is can clear the step
over state and re-resume threads.

This commit makes it possible for GDB to ask the target to report
thread exit events for a given thread, using the new "thread options"
mechanism introduced by a previous patch.

This only adds the core bits.  Following patches in the series will
teach the Linux backends (native & gdbserver) to handle the
GDB_TO_EXIT option, and then a later patch will make use of these
thread exit events to clean up displaced stepping and inline stepping
state properly.

Change-Id: I96b719fdf7fee94709e98bb3a90751d8134f3a38
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
---
 gdb/infrun.c        | 14 +++++++++-----
 gdb/remote.c        |  9 +++++++++
 gdb/target/target.h |  4 ++++
 3 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 78ba81f530f..c6df86fadaf 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2376,24 +2376,28 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
   else
     target_pass_signals (signal_pass);
 
-  /* Request that the target report thread-{created,cloned} events in
-     the following situations:
+  /* Request that the target report thread-{created,cloned,exited}
+     events in the following situations:
 
      - If we are performing an in-line step-over-breakpoint, then we
        will remove a breakpoint from the target and only run the
        current thread.  We don't want any new thread (spawned by the
-       step) to start running, as it might miss the breakpoint.
+       step) to start running, as it might miss the breakpoint.  We
+       need to clear the step-over state if the stepped thread exits,
+       so we also enable thread-exit events.
 
      - If we are stepping over a breakpoint out of line (displaced
        stepping) then we won't remove a breakpoint from the target,
        but, if the step spawns a new clone thread, then we will need
        to fixup the $pc address in the clone child too, so we need it
-       to start stopped.
+       to start stopped.  We need to release the displaced stepping
+       buffer if the stepped thread exits, so we also enable
+       thread-exit events.
   */
   if (step_over_info_valid_p ()
       || displaced_step_in_progress_thread (tp))
     {
-      gdb_thread_options options = GDB_TO_CLONE;
+      gdb_thread_options options = GDB_TO_CLONE | GDB_TO_EXIT;
       if (target_supports_set_thread_options (options))
 	target_set_thread_options (tp, options);
       else
diff --git a/gdb/remote.c b/gdb/remote.c
index 5bfbe28b8c3..e445959ccf0 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -3995,6 +3995,15 @@ remote_target::update_thread_list ()
 	      if (has_single_non_exited_thread (tp->inf))
 		continue;
 
+	      /* Do not remove the thread if we've requested to be
+		 notified of its exit.  For example, the thread may be
+		 displaced stepping, infrun will need to handle the
+		 exit event, and displaced stepping info is recorded
+		 in the thread object.  If we deleted the thread now,
+		 we'd lose that info.  */
+	      if ((tp->thread_options () & GDB_TO_EXIT) != 0)
+		continue;
+
 	      /* Not found.  */
 	      delete_thread (tp);
 	    }
diff --git a/gdb/target/target.h b/gdb/target/target.h
index cba98114b5d..571d90cf7c0 100644
--- a/gdb/target/target.h
+++ b/gdb/target/target.h
@@ -31,6 +31,10 @@ enum gdb_thread_option : unsigned
   /* Tell the target to report TARGET_WAITKIND_THREAD_CLONED events
      for the thread.  */
   GDB_TO_CLONE = 1 << 0,
+
+  /* Tell the target to report TARGET_WAITKIND_THREAD_EXIT events for
+     the thread.  */
+  GDB_TO_EXIT = 1 << 1,
 };
 
 DEF_ENUM_FLAGS_TYPE (enum gdb_thread_option, gdb_thread_options);
-- 
2.36.0


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

* [PATCH 14/25] Implement GDB_TO_EXIT support for Linux GDBserver
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (12 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 13/25] Introduce GDB_TO_EXIT thread option, fix step-over-thread-exit Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 15/25] Implement GDB_TO_EXIT support for native Linux Pedro Alves
                   ` (10 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

This implements support for the new GDB_TO_EXIT thread option for
Linux GDBserver.

Change-Id: I96b719fdf7fee94709e98bb3a90751d8134f3a38
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
---
 gdbserver/linux-low.cc | 38 +++++++++++++++++++++++++-------------
 gdbserver/linux-low.h  |  9 +++++----
 2 files changed, 30 insertions(+), 17 deletions(-)

diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 8bfcd5bf740..8154d8088c1 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -144,6 +144,18 @@ is_leader (thread_info *thread)
   return ptid.pid () == ptid.lwp ();
 }
 
+/* Return true if we should report thread exit events to GDB, for
+   THR.  */
+
+static bool
+report_exit_events_for (thread_info *thr)
+{
+  client_state &cs = get_client_state ();
+
+  return (cs.report_thread_events
+	  || (thr->thread_options & GDB_TO_EXIT) != 0);
+}
+
 /* LWP accessors.  */
 
 /* See nat/linux-nat.h.  */
@@ -2226,7 +2238,6 @@ linux_low_ptrace_options (int attached)
 void
 linux_process_target::filter_event (int lwpid, int wstat)
 {
-  client_state &cs = get_client_state ();
   struct lwp_info *child;
   struct thread_info *thread;
   int have_stop_pc = 0;
@@ -2313,7 +2324,7 @@ linux_process_target::filter_event (int lwpid, int wstat)
       /* If this is not the leader LWP, then the exit signal was not
 	 the end of the debugged application and should be ignored,
 	 unless GDB wants to hear about thread exits.  */
-      if (cs.report_thread_events || is_leader (thread))
+      if (report_exit_events_for (thread) || is_leader (thread))
 	{
 	  /* Since events are serialized to GDB core, and we can't
 	     report this one right now.  Leave the status pending for
@@ -2876,13 +2887,20 @@ ptid_t
 linux_process_target::filter_exit_event (lwp_info *event_child,
 					 target_waitstatus *ourstatus)
 {
-  client_state &cs = get_client_state ();
   struct thread_info *thread = get_lwp_thread (event_child);
   ptid_t ptid = ptid_of (thread);
 
+  /* Note we must filter TARGET_WAITKIND_SIGNALLED as well, otherwise
+     if a non-leader thread exits with a signal, we'd report it to the
+     core which would interpret it as the whole-process exiting.
+     There is no TARGET_WAITKIND_THREAD_SIGNALLED event kind.  */
+  if (ourstatus->kind () != TARGET_WAITKIND_EXITED
+      && ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
+    return ptid;
+
   if (!is_leader (thread))
     {
-      if (cs.report_thread_events)
+      if (report_exit_events_for (thread))
 	ourstatus->set_thread_exited (0);
       else
 	ourstatus->set_ignore ();
@@ -3025,10 +3043,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
 	     WTERMSIG (w));
 	}
 
-      if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
-	return filter_exit_event (event_child, ourstatus);
-
-      return ptid_of (current_thread);
+      return filter_exit_event (event_child, ourstatus);
     }
 
   /* If step-over executes a breakpoint instruction, in the case of a
@@ -3597,10 +3612,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
 			target_pid_to_str (ptid_of (current_thread)).c_str (),
 			ourstatus->to_string ().c_str ());
 
-  if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
-    return filter_exit_event (event_child, ourstatus);
-
-  return ptid_of (current_thread);
+  return filter_exit_event (event_child, ourstatus);
 }
 
 /* Get rid of any pending event in the pipe.  */
@@ -5911,7 +5923,7 @@ bool
 linux_process_target::supports_set_thread_options
   (gdb_thread_options *supported_options)
 {
-  *supported_options = GDB_TO_CLONE;
+  *supported_options = GDB_TO_CLONE | GDB_TO_EXIT;
   return true;
 }
 
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index e7237bba3b3..051a7196ef7 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -573,10 +573,11 @@ class linux_process_target : public process_stratum_target
      exited.  */
   void check_zombie_leaders ();
 
-  /* Convenience function that is called when the kernel reports an exit
-     event.  This decides whether to report the event to GDB as a
-     process exit event, a thread exit event, or to suppress the
-     event.  */
+  /* Convenience function that is called when we're about to return an
+     event to the core.  If the event is an exit or signalled event,
+     then this decides whether to report it as process-wide event, as
+     a thread exit event, or to suppress it.  All other event kinds
+     are passed through unmodified.  */
   ptid_t filter_exit_event (lwp_info *event_child,
 			    target_waitstatus *ourstatus);
 
-- 
2.36.0


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

* [PATCH 15/25] Implement GDB_TO_EXIT support for native Linux
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (13 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 14/25] Implement GDB_TO_EXIT support for Linux GDBserver Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 16/25] gdb: clear step over information on thread exit (PR gdb/27338) Pedro Alves
                   ` (9 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

This implements support for the new GDB_TO_EXIT thread option for
native Linux.

Change-Id: Ia69fc0b9b96f9af7de7cefc1ddb1fba9bbb0bb90
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
---
 gdb/linux-nat.c | 77 +++++++++++++++++++++++++++++++++++--------------
 1 file changed, 56 insertions(+), 21 deletions(-)

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index a446a755242..65d0bae588f 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -266,6 +266,27 @@ pending_status_str (lwp_info *lp)
     return status_to_str (lp->status);
 }
 
+/* Return true if we should report exit events for THR.  */
+
+static bool
+report_exit_events_for (thread_info *thr)
+{
+  return (report_thread_events
+	  || (thr->thread_options () & GDB_TO_EXIT) != 0);
+}
+
+/* Convenience overload for the above -- return true if we should
+   report exit events for LP.  */
+
+static bool
+report_exit_events_for (lwp_info *lp)
+{
+  thread_info *thr = find_thread_ptid (linux_target, lp->ptid);
+  gdb_assert (thr != nullptr);
+
+  return report_exit_events_for (thr);
+}
+
 \f
 /* LWP accessors.  */
 
@@ -895,10 +916,11 @@ linux_nat_switch_fork (ptid_t new_ptid)
   registers_changed ();
 }
 
-/* Handle the exit of a single thread LP.  */
+/* Handle the exit of a single thread LP.  If DEL_THREAD is true,
+   delete the thread_info associated to LP, if it exists.  */
 
 static void
-exit_lwp (struct lwp_info *lp)
+exit_lwp (struct lwp_info *lp, bool del_thread = true)
 {
   struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
 
@@ -908,7 +930,8 @@ exit_lwp (struct lwp_info *lp)
 	gdb_printf (_("[%s exited]\n"),
 		    target_pid_to_str (lp->ptid).c_str ());
 
-      delete_thread (th);
+      if (del_thread)
+	delete_thread (th);
     }
 
   delete_lwp (lp->ptid);
@@ -1909,7 +1932,7 @@ linux_nat_target::follow_clone (ptid_t child_ptid)
     {
       new_lp->status = 0;
 
-      if (report_thread_events)
+      if (report_exit_events_for (new_thr))
 	new_lp->waitstatus.set_thread_created ();
     }
 }
@@ -2145,8 +2168,7 @@ wait_lwp (struct lwp_info *lp)
       /* Check if the thread has exited.  */
       if (WIFEXITED (status) || WIFSIGNALED (status))
 	{
-	  if (report_thread_events
-	      || lp->ptid.pid () == lp->ptid.lwp ())
+	  if (report_exit_events_for (lp) || is_leader (lp))
 	    {
 	      linux_nat_debug_printf ("LWP %d exited.", lp->ptid.pid ());
 
@@ -2923,7 +2945,7 @@ linux_nat_filter_event (int lwpid, int status)
   /* Check if the thread has exited.  */
   if (WIFEXITED (status) || WIFSIGNALED (status))
     {
-      if (!report_thread_events && !is_leader (lp))
+      if (!report_exit_events_for (lp) && !is_leader (lp))
 	{
 	  linux_nat_debug_printf ("%s exited.",
 				  lp->ptid.to_string ().c_str ());
@@ -3133,10 +3155,11 @@ check_zombie_leaders (void)
     }
 }
 
-/* Convenience function that is called when the kernel reports an exit
-   event.  This decides whether to report the event to GDB as a
-   process exit event, a thread exit event, or to suppress the
-   event.  */
+/* Convenience function that is called when we're about to return an
+   event to the core.  If the event is an exit or signalled event,
+   then this decides whether to report it as process-wide event, as a
+   thread exit event, or to suppress it.  All other event kinds are
+   passed through unmodified.  */
 
 static ptid_t
 filter_exit_event (struct lwp_info *event_child,
@@ -3144,14 +3167,28 @@ filter_exit_event (struct lwp_info *event_child,
 {
   ptid_t ptid = event_child->ptid;
 
+  /* Note we must filter TARGET_WAITKIND_SIGNALLED as well, otherwise
+     if a non-leader thread exits with a signal, we'd report it to the
+     core which would interpret it as the whole-process exiting.
+     There is no TARGET_WAITKIND_THREAD_SIGNALLED event kind.  */
+  if (ourstatus->kind () != TARGET_WAITKIND_EXITED
+      && ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
+    return ptid;
+
   if (!is_leader (event_child))
     {
-      if (report_thread_events)
-	ourstatus->set_thread_exited (0);
+      if (report_exit_events_for (event_child))
+	{
+	  ourstatus->set_thread_exited (0);
+	  /* Delete lwp, but not thread_info, infrun will need it to
+	     process the event.  */
+	  exit_lwp (event_child, false);
+	}
       else
-	ourstatus->set_ignore ();
-
-      exit_lwp (event_child);
+	{
+	  ourstatus->set_ignore ();
+	  exit_lwp (event_child);
+	}
     }
 
   return ptid;
@@ -3373,10 +3410,7 @@ linux_nat_wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
   else
     lp->core = linux_common_core_of_thread (lp->ptid);
 
-  if (ourstatus->kind () == TARGET_WAITKIND_EXITED)
-    return filter_exit_event (lp, ourstatus);
-
-  return lp->ptid;
+  return filter_exit_event (lp, ourstatus);
 }
 
 /* Resume LWPs that are currently stopped without any pending status
@@ -4409,7 +4443,8 @@ linux_nat_target::thread_events (int enable)
 bool
 linux_nat_target::supports_set_thread_options (gdb_thread_options options)
 {
-  constexpr gdb_thread_options supported_options = GDB_TO_CLONE;
+  constexpr gdb_thread_options supported_options
+    = GDB_TO_CLONE | GDB_TO_EXIT;
   return ((options & supported_options) == options);
 }
 
-- 
2.36.0


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

* [PATCH 16/25] gdb: clear step over information on thread exit (PR gdb/27338)
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (14 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 15/25] Implement GDB_TO_EXIT support for native Linux Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 17/25] stop_all_threads: (re-)enable async before waiting for stops Pedro Alves
                   ` (8 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches; +Cc: Pedro Alves

From: Simon Marchi <simon.marchi@efficios.com>

GDB doesn't handle correctly the case where a thread steps over a
breakpoint (using either in-line or displaced stepping), and the
executed instruction causes the thread to exit.

Using the test program included later in the series, this is what it
looks like with displaced-stepping, on x86-64 Linux, where we have two
displaced-step buffers:

  $ ./gdb -q -nx --data-directory=data-directory build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-thread-exit/step-over-thread-exit -ex "b my_exit_syscall" -ex r
  Reading symbols from build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-thread-exit/step-over-thread-exit...
  Breakpoint 1 at 0x123c: file src/binutils-gdb/gdb/testsuite/lib/my-syscalls.S, line 68.
  Starting program: build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-thread-exit/step-over-thread-exit
  [Thread debugging using libthread_db enabled]
  Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
  [New Thread 0x7ffff7c5f640 (LWP 2915510)]
  [Switching to Thread 0x7ffff7c5f640 (LWP 2915510)]

  Thread 2 "step-over-threa" hit Breakpoint 1, my_exit_syscall () at src/binutils-gdb/gdb/testsuite/lib/my-syscalls.S:68
  68              syscall
  (gdb) c
  Continuing.
  [New Thread 0x7ffff7c5f640 (LWP 2915524)]
  [Thread 0x7ffff7c5f640 (LWP 2915510) exited]
  [Switching to Thread 0x7ffff7c5f640 (LWP 2915524)]

  Thread 3 "step-over-threa" hit Breakpoint 1, my_exit_syscall () at src/binutils-gdb/gdb/testsuite/lib/my-syscalls.S:68
  68              syscall
  (gdb) c
  Continuing.
  [New Thread 0x7ffff7c5f640 (LWP 2915616)]
  [Thread 0x7ffff7c5f640 (LWP 2915524) exited]
  [Switching to Thread 0x7ffff7c5f640 (LWP 2915616)]

  Thread 4 "step-over-threa" hit Breakpoint 1, my_exit_syscall () at src/binutils-gdb/gdb/testsuite/lib/my-syscalls.S:68
  68              syscall
  (gdb) c
  Continuing.
  ... hangs ...

The first two times we do "continue", we displaced-step the syscall
instruction that causes the thread to exit.  When the thread exits,
the main thread, waiting on pthread_join, is unblocked.  It spawns a
new thread, which hits the breakpoint on the syscall again.  However,
infrun was never notified that the displaced-stepping threads are done
using the displaced-step buffer, so now both buffers are marked as
used.  So when we do the third continue, there are no buffers
available to displaced-step the syscall, so the thread waits forever
for its turn.

When trying the same but with in-line step over (displaced-stepping
disabled):

  $ ./gdb -q -nx --data-directory=data-directory \
  build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-thread-exit/step-over-thread-exit \
    -ex "b my_exit_syscall" -ex "set displaced-stepping off" -ex r
  Reading symbols from build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-thread-exit/step-over-thread-exit...
  Breakpoint 1 at 0x123c: file src/binutils-gdb/gdb/testsuite/lib/my-syscalls.S, line 68.
  Starting program: build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/step-over-thread-exit/step-over-thread-exit
  [Thread debugging using libthread_db enabled]
  Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
  [New Thread 0x7ffff7c5f640 (LWP 2928290)]
  [Switching to Thread 0x7ffff7c5f640 (LWP 2928290)]

  Thread 2 "step-over-threa" hit Breakpoint 1, my_exit_syscall () at src/binutils-gdb/gdb/testsuite/lib/my-syscalls.S:68
  68              syscall
  (gdb) c
  Continuing.
  [Thread 0x7ffff7c5f640 (LWP 2928290) exited]
  No unwaited-for children left.
  (gdb) i th
    Id   Target Id                                             Frame
    1    Thread 0x7ffff7c60740 (LWP 2928285) "step-over-threa" 0x00007ffff7f7c9b7 in __pthread_clockjoin_ex () from /usr/lib/libpthread.so.0

  The current thread <Thread ID 2> has terminated.  See `help thread'.
  (gdb) thread 1
  [Switching to thread 1 (Thread 0x7ffff7c60740 (LWP 2928285))]
  #0  0x00007ffff7f7c9b7 in __pthread_clockjoin_ex () from /usr/lib/libpthread.so.0
  (gdb) c
  Continuing.
  ^C^C
  ... hangs ...

The "continue" causes an in-line step to occur, meaning the main
thread is stopped while we step the syscall.  The stepped thread exits
when executing the syscall, the linux-nat target notices there are no
more resumed threads to be waited for, so returns
TARGET_WAITKIND_NO_RESUMED, which causes the prompt to return.  But
infrun never clears the in-line step over info.  So if we try
continuing the main thread, GDB doesn't resume it, because it thinks
there's an in-line step in progress that we need to wait for to
finish, and we are stuck there.

To fix this, infrun needs to be informed when a thread doing a
displaced or in-line step over exits.  We can do that with the new
target_set_thread_options mechanism which is optimal for only enabling
exit events of the thread that needs it; or, if that is not supported,
by using target_thread_events, which enables thread exit events for
all threads.  This is done by this commit.

This patch then modifies handle_inferior_event in infrun.c to clean up
any step-over the exiting thread might have been doing at the time of
the exit.  The cases to consider are:

 - the exiting thread was doing an in-line step-over with an all-stop
   target
 - the exiting thread was doing an in-line step-over with a non-stop
   target
 - the exiting thread was doing a displaced step-over with a non-stop
   target

The displaced-stepping buffer implementation in displaced-stepping.c
is modified to account for the fact that it's possible that we
"finish" a displaced step after a thread exit event.  The buffer that
the exiting thread was using is marked as available again and the
original instructions under the scratch pad are restored.  However, it
skips applying the fixup, which wouldn't make sense since the thread
does not exist anymore.

Another case that needs handling is if a displaced-stepping thread
exits, and the event is reported while we are in stop_all_threads.  We
should call displaced_step_finish in the handle_one function, in that
case.  It was already called in other code paths, just not the "thread
exited" path.

This commit doesn't make infrun ask the target to report the
TARGET_WAITKIND_THREAD_EXITED events yet, that'll be done later in the
series.

Note that "stop_print_frame = false;" line is moved to normal_stop,
because TARGET_WAITKIND_THREAD_EXITED can also end up with the event
transmorphed into TARGET_WAITKIND_NO_RESUMED.  Moving it to
normal_stop keeps it centralized.

Co-authored-by: Pedro Alves <pedro@palves.net>
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
Change-Id: I745c6955d7ef90beb83bcf0ff1d1ac8b9b6285a5
---
 gdb/displaced-stepping.c  |   7 ++
 gdb/gdbarch-components.py |   4 +
 gdb/gdbarch-gen.h         |   6 +-
 gdb/infrun.c              | 163 +++++++++++++++++++++++++++++++++++---
 gdb/target.c              |   1 +
 5 files changed, 167 insertions(+), 14 deletions(-)

diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
index 83080cf6bdd..96d4732b7dc 100644
--- a/gdb/displaced-stepping.c
+++ b/gdb/displaced-stepping.c
@@ -236,6 +236,13 @@ displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
 			  thread->ptid.to_string ().c_str (),
 			  paddress (arch, buffer->addr));
 
+  /* If the thread exited while stepping, we are done.  The code above
+     made the buffer available again, and we restored the bytes in the
+     buffer.  We don't want to run the fixup: since the thread is now
+     dead there's nothing to adjust.  */
+  if (status.kind () == TARGET_WAITKIND_THREAD_EXITED)
+    return DISPLACED_STEP_FINISH_STATUS_OK;
+
   regcache *rc = get_thread_regcache (thread);
 
   bool instruction_executed_successfully
diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
index 5d391aa7dc0..d4c6c3bc94e 100644
--- a/gdb/gdbarch-components.py
+++ b/gdb/gdbarch-components.py
@@ -1740,6 +1740,10 @@ Throw an exception if any unexpected error happens.
 Method(
     comment="""
 Clean up after a displaced step of THREAD.
+
+It is possible for the displaced-stepped instruction to have caused
+the thread to exit.  The implementation can detect this case by
+checking if WS.kind is TARGET_WAITKIND_THREAD_EXITED.
 """,
     type="displaced_step_finish_status",
     name="displaced_step_finish",
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index 221dd84008c..b0643616561 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1030,7 +1030,11 @@ typedef displaced_step_prepare_status (gdbarch_displaced_step_prepare_ftype) (st
 extern displaced_step_prepare_status gdbarch_displaced_step_prepare (struct gdbarch *gdbarch, thread_info *thread, CORE_ADDR &displaced_pc);
 extern void set_gdbarch_displaced_step_prepare (struct gdbarch *gdbarch, gdbarch_displaced_step_prepare_ftype *displaced_step_prepare);
 
-/* Clean up after a displaced step of THREAD. */
+/* Clean up after a displaced step of THREAD.
+
+   It is possible for the displaced-stepped instruction to have caused
+   the thread to exit.  The implementation can detect this case by
+   checking if WS.kind is TARGET_WAITKIND_THREAD_EXITED. */
 
 typedef displaced_step_finish_status (gdbarch_displaced_step_finish_ftype) (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws);
 extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws);
diff --git a/gdb/infrun.c b/gdb/infrun.c
index c6df86fadaf..c0fbca16b5c 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1820,13 +1820,15 @@ displaced_step_prepare (thread_info *thread)
    a step-over (either in-line or displaced) finishes.  */
 
 static void
-update_thread_events_after_step_over (thread_info *event_thread)
+update_thread_events_after_step_over (thread_info *event_thread,
+				      const target_waitstatus &event_status)
 {
   if (target_supports_set_thread_options (0))
     {
       /* We can control per-thread options.  Disable events for the
-	 event thread.  */
-      target_set_thread_options (event_thread, 0);
+	 event thread, unless the thread is gone.  */
+      if (event_status.kind () != TARGET_WAITKIND_THREAD_EXITED)
+	target_set_thread_options (event_thread, 0);
     }
   else
     {
@@ -1882,7 +1884,7 @@ displaced_step_finish (thread_info *event_thread,
   if (!displaced->in_progress ())
     return DISPLACED_STEP_FINISH_STATUS_OK;
 
-  update_thread_events_after_step_over (event_thread);
+  update_thread_events_after_step_over (event_thread, event_status);
 
   gdb_assert (event_thread->inf->displaced_step_state.in_progress_count > 0);
   event_thread->inf->displaced_step_state.in_progress_count--;
@@ -3982,6 +3984,7 @@ struct wait_one_event
 };
 
 static bool handle_one (const wait_one_event &event);
+static int finish_step_over (struct execution_control_state *ecs);
 
 /* Prepare and stabilize the inferior for detaching it.  E.g.,
    detaching while a thread is displaced stepping is a recipe for
@@ -5079,6 +5082,16 @@ handle_one (const wait_one_event &event)
 				      event.ws);
 	  save_waitstatus (t, event.ws);
 	  t->stop_requested = false;
+
+	  if (event.ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
+	    {
+	      if (displaced_step_finish (t, event.ws)
+		  != DISPLACED_STEP_FINISH_STATUS_OK)
+		{
+		  gdb_assert_not_reached ("displaced_step_finish on "
+					  "exited thread failed");
+		}
+	    }
 	}
     }
   else
@@ -5288,7 +5301,9 @@ stop_all_threads (const char *reason, inferior *inf)
     }
 }
 
-/* Handle a TARGET_WAITKIND_NO_RESUMED event.  */
+/* Handle a TARGET_WAITKIND_NO_RESUMED event.  Return true if we
+   handled the event and should continue waiting.  Return false if we
+   should stop and report the event to the user.  */
 
 static bool
 handle_no_resumed (struct execution_control_state *ecs)
@@ -5416,6 +5431,117 @@ handle_no_resumed (struct execution_control_state *ecs)
   return false;
 }
 
+/* Handle a TARGET_WAITKIND_THREAD_EXITED event.  Return true if we
+   handled the event and should continue waiting.  Return false if we
+   should stop and report the event to the user.  */
+
+static bool
+handle_thread_exited (execution_control_state *ecs)
+{
+  context_switch (ecs);
+
+  /* Clear these so we don't re-start the thread stepping over a
+     breakpoint/watchpoint.  */
+  ecs->event_thread->stepping_over_breakpoint = 0;
+  ecs->event_thread->stepping_over_watchpoint = 0;
+
+  /* Maybe the thread was doing a step-over, if so release
+     resources and start any further pending step-overs.
+
+     If we are on a non-stop target and the thread was doing an
+     in-line step, this also restarts the other threads.  */
+  int ret = finish_step_over (ecs);
+
+  /* finish_step_over returns true if it moves ecs' wait status
+     back into the thread, so that we go handle another pending
+     event before this one.  But we know it never does that if
+     the event thread has exited.  */
+  gdb_assert (ret == 0);
+
+  /* If finish_step_over started a new in-line step-over, don't
+     try to restart anything else.  */
+  if (step_over_info_valid_p ())
+    {
+      delete_thread (ecs->event_thread);
+      return true;
+    }
+
+  /* Maybe we are on an all-stop target and we got this event
+     while doing a step-like command on another thread.  If so,
+     go back to doing that.  If this thread was stepping,
+     switch_back_to_stepped_thread will consider that the thread
+     was interrupted mid-step and will try keep stepping it.  We
+     don't want that, the thread is gone.  So clear the proceed
+     status so it doesn't do that.  */
+  clear_proceed_status_thread (ecs->event_thread);
+  if (switch_back_to_stepped_thread (ecs))
+    {
+      delete_thread (ecs->event_thread);
+      return true;
+    }
+
+  inferior *inf = ecs->event_thread->inf;
+  bool slock_applies = schedlock_applies (ecs->event_thread);
+
+  delete_thread (ecs->event_thread);
+  ecs->event_thread = nullptr;
+
+  auto handle_as_no_resumed = [ecs] ()
+  {
+    ecs->ws.set_no_resumed ();
+    ecs->event_thread = nullptr;
+    ecs->ptid = minus_one_ptid;
+    return handle_no_resumed (ecs);
+  };
+
+  /* If we are on an all-stop target, the target has stopped all
+     threads to report the event.  We don't actually want to
+     stop, so restart the threads.  */
+  if (!target_is_non_stop_p ())
+    {
+      if (slock_applies)
+	{
+	  /* Since the target is !non-stop, then everything is stopped
+	     at this point, and we can't assume we'll get further
+	     events until we resume the target again.  Handle this
+	     event like if it were a TARGET_WAITKIND_NO_RESUMED.  Note
+	     this refreshes the thread list and checks whether there
+	     are other resumed threads before deciding whether to
+	     print "no-unwaited-for left".  This is important because
+	     the user could have done:
+
+	      (gdb) set scheduler-locking on
+	      (gdb) thread 1
+	      (gdb) c&
+	      (gdb) thread 2
+	      (gdb) c
+
+	     ... and only one of the threads exited.  */
+	  return handle_as_no_resumed ();
+	}
+      else
+	{
+	  /* Switch to the first non-exited thread we can find, and
+	     resume.  */
+	  auto range = inf->non_exited_threads ();
+	  if (range.begin () == range.end ())
+	    {
+	      /* Looks like the target reported a
+		 TARGET_WAITKIND_THREAD_EXITED for its last known
+		 thread.  */
+	      return handle_as_no_resumed ();
+	    }
+	  thread_info *non_exited_thread = *range.begin ();
+	  switch_to_thread (non_exited_thread);
+	  insert_breakpoints ();
+	  resume (GDB_SIGNAL_0);
+	}
+    }
+
+  prepare_to_wait (ecs);
+  return true;
+}
+
 /* Given an execution control state that has been freshly filled in by
    an event from the inferior, figure out what it means and take
    appropriate action.
@@ -5454,12 +5580,6 @@ handle_inferior_event (struct execution_control_state *ecs)
       return;
     }
 
-  if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
-    {
-      prepare_to_wait (ecs);
-      return;
-    }
-
   if (ecs->ws.kind () == TARGET_WAITKIND_NO_RESUMED
       && handle_no_resumed (ecs))
     return;
@@ -5474,7 +5594,6 @@ handle_inferior_event (struct execution_control_state *ecs)
     {
       /* No unwaited-for children left.  IOW, all resumed children
 	 have exited.  */
-      stop_print_frame = false;
       stop_waiting (ecs);
       return;
     }
@@ -5624,6 +5743,15 @@ handle_inferior_event (struct execution_control_state *ecs)
 	keep_going (ecs);
       return;
 
+    case TARGET_WAITKIND_THREAD_EXITED:
+      if (handle_thread_exited (ecs))
+	return;
+      /* Need to re-record the last target status because the waitkind
+	 may have changed to TARGET_WAITKIND_NO_RESUMED.  */
+      set_last_target_status (ecs->target, ecs->ptid, ecs->ws);
+      stop_waiting (ecs);
+      break;
+
     case TARGET_WAITKIND_EXITED:
     case TARGET_WAITKIND_SIGNALLED:
       {
@@ -6073,7 +6201,7 @@ finish_step_over (struct execution_control_state *ecs)
 	 back an event.  */
       gdb_assert (ecs->event_thread->control.trap_expected);
 
-      update_thread_events_after_step_over (ecs->event_thread);
+      update_thread_events_after_step_over (ecs->event_thread, ecs->ws);
 
       clear_step_over_info ();
     }
@@ -6119,6 +6247,13 @@ finish_step_over (struct execution_control_state *ecs)
       if (ecs->event_thread->stepping_over_watchpoint)
 	return 0;
 
+      /* The code below is meant to avoid one thread hogging the event
+	 loop by doing constant in-line step overs.  If the stepping
+	 thread exited, there's no risk for this to happen, so we can
+	 safely let our caller process the event immediately.  */
+      if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
+       return 0;
+
       pending = iterate_over_threads (resumed_thread_with_pending_status,
 				      NULL);
       if (pending != NULL)
@@ -8760,6 +8895,8 @@ normal_stop (void)
 
   if (last.kind () == TARGET_WAITKIND_NO_RESUMED)
     {
+      stop_print_frame = false;
+
       SWITCH_THRU_ALL_UIS ()
 	if (current_ui->prompt_state == PROMPT_BLOCKED)
 	  {
diff --git a/gdb/target.c b/gdb/target.c
index 52eee4b8140..828bf3ddc54 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -4393,6 +4393,7 @@ target_set_thread_options (thread_info *thr, gdb_thread_options options)
 {
   gdb_assert (thr->inf->process_target ()
 	      == current_inferior ()->process_target ());
+  gdb_assert (thr->state != THREAD_EXITED && !thr->executing ());
 
   if (thr->thread_options () == options)
     {
-- 
2.36.0


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

* [PATCH 17/25] stop_all_threads: (re-)enable async before waiting for stops
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (15 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 16/25] gdb: clear step over information on thread exit (PR gdb/27338) Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 18/25] gdbserver: Queue no-resumed event after thread exit Pedro Alves
                   ` (7 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

Running the
gdb.threads/step-over-thread-exit-while-stop-all-threads.exp testcase
added later in the series against gdbserver, after the
TARGET_WAITKIND_NO_RESUMED fix from the following patch, would run
into an infinite loop in stop_all_threads, leading to a timeout:

  FAIL: gdb.threads/step-over-thread-exit-while-stop-all-threads.exp: displaced-stepping=off: target-non-stop=on: iter 0: continue (timeout)

The is really a latent bug, and it is about the fact that
stop_all_threads stops listening to events from a target as soon as it
sees a TARGET_WAITKIND_NO_RESUMED, ignoring that
TARGET_WAITKIND_NO_RESUMED may be delayed.  handle_no_resumed knows
how to handle delayed no-resumed events, but stop_all_threads was
never taught to.

In more detail, here's what happens with that testcase:

#1 - Multiple threads report breakpoint hits to gdb.

#2 - gdb picks one events, and it's for thread 1.  All other stops are
     left pending.  thread 1 needs to move past a breakpoint, so gdb
     stops all threads to start an inline step over for thread 1.
     While stopping threads, some of the threads that were still
     running report events that are also left pending.

#2 - gdb steps thread 1

#3 - Thread 1 exits while stepping (it steps over an exit syscall),
     gdbserver reports thread exit for thread 1

#4 - Thread 1 was the last resumed thread, so gdbserver also reports
     no-resumed:

    [remote]   Notification received: Stop:w0;p3445d0.3445d3
    [remote] Sending packet: $vStopped#55
    [remote] Packet received: N
    [remote] Sending packet: $vStopped#55
    [remote] Packet received: OK

#5 - gdb processes the thread exit for thread 1, finishes the step
     over and restarts threads.

#6 - gdb picks the next event to process out of one of the resumed
     threads with pending events:

    [infrun] random_resumed_with_pending_wait_status: Found 32 events, selecting #11

#7 - This is again a breakpoint hit and the breakpoint needs to be
     stepped over too, so gdb starts a step-over dance again.

#8 - We reach stop_all_threads, which finds that some threads need to
     be stopped.

#9 - wait_one finally consumes the no-resumed event queue by #4.
     Seeing this, wait_one disable target async, to stop listening for
     events out of the remote target.

#10 - We still haven't seen all the stops expected, so
      stop_all_threads tries another iteration.

#11 - Because the remote target is no longer async, and there are no
      other targets, wait_one return no-resumed immediately without
      polling the remote target.

#12 - We still haven't seen all the stops expected, so
      stop_all_threads tries another iteration.  goto #11, looping
      forever.

Fix this by explicitly enabling/re-enabling target async on targets
that can async, before waiting for stops.

Change-Id: Ie3ffb0df89635585a6631aa842689cecc989e33f
---
 gdb/infrun.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index c0fbca16b5c..c7d5acf2326 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4909,6 +4909,8 @@ wait_one ()
       if (nfds == 0)
 	{
 	  /* No waitable targets left.  All must be stopped.  */
+	  infrun_debug_printf ("no waitable targets left");
+
 	  target_waitstatus ws;
 	  ws.set_no_resumed ();
 	  return {NULL, minus_one_ptid, std::move (ws)};
@@ -5165,6 +5167,83 @@ handle_one (const wait_one_event &event)
   return false;
 }
 
+/* Helper for stop_all_threads.  wait_one waits for events until it
+   sees a TARGET_WAITKIND_NO_RESUMED event.  When it sees one, it
+   disables target_async for the target to stop waiting for events
+   from it.  TARGET_WAITKIND_NO_RESUMED can be delayed though,
+   consider, debugging against gdbserver:
+
+    #1 - Threads 1-5 are running, and thread 1 hits a breakpoint.
+
+    #2 - gdb processes the breakpoint hit for thread 1, stops all
+	 threads, and steps thread 1 over the breakpoint.  while
+	 stopping threads, some other threads reported interesting
+	 events, which were left pending in the thread's objects
+	 (infrun's queue).
+
+    #2 - Thread 1 exits (it stepped an exit syscall), and gdbserver
+	 reports the thread exit for thread 1.	The event ends up in
+	 remote's stop reply queue.
+
+    #3 - That was the last resumed thread, so gdbserver reports
+	 no-resumed, and that event also ends up in remote's stop
+	 reply queue, queued after the thread exit from #2.
+
+    #4 - gdb processes the thread exit event, which finishes the
+	 step-over, and so gdb restarts all threads (threads with
+	 pending events are left marked resumed, but aren't set
+	 executing).  The no-resumed event is still left pending in
+	 the remote stop reply queue.
+
+    #5 - Since there are now resumed threads with pending breakpoint
+	 hits, gdb picks one at random to process next.
+
+    #5 - gdb picks the breakpoint hit for thread 2 this time, and that
+	 breakpoint also needs to be stepped over, so gdb stops all
+	 threads again.
+
+    #6 - stop_all_threads counts number of expected stops and calls
+	 wait_one once for each.
+
+    #7 - The first wait_one call collects the no-resumed event from #3
+	 above.
+
+    #9 - Seeing the no-resumed event, wait_one disables target async
+	 for the remote target, to stop waiting for events from it.
+	 wait_one from here on always return no-resumed directly
+	 without reaching the target.
+
+    #10 - stop_all_threads still hasn't seen all the stops it expects,
+	  so it does another pass.
+
+    #11 - Since the remote target is not async (disabled in #9),
+	  wait_one doesn't wait on it, so it won't see the expected
+	  stops, and instead returns no-resumed directly.
+
+    #12 - stop_all_threads still haven't seen all the stops, so it
+	  does another pass.  goto #b, looping forever.
+
+   To handle this, we explicitly (re-)enable target async on all
+   targets that can async every time stop_all_threads goes wait for
+   the expected stops.  */
+
+static void
+reenable_target_async ()
+{
+  for (inferior *inf : all_inferiors ())
+    {
+      process_stratum_target *target = inf->process_target ();
+      if (target != nullptr
+	  && target->threads_executing
+	  && target->can_async_p ()
+	  && !target->is_async_p ())
+	{
+	  switch_to_inferior_no_thread (inf);
+	  target_async (1);
+	}
+    }
+}
+
 /* See infrun.h.  */
 
 void
@@ -5291,6 +5370,8 @@ stop_all_threads (const char *reason, inferior *inf)
 	  if (pass > 0)
 	    pass = -1;
 
+	  reenable_target_async ();
+
 	  for (int i = 0; i < waits_needed; i++)
 	    {
 	      wait_one_event event = wait_one ();
-- 
2.36.0


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

* [PATCH 18/25] gdbserver: Queue no-resumed event after thread exit
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (16 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 17/25] stop_all_threads: (re-)enable async before waiting for stops Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect Pedro Alves
                   ` (6 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

Normally, if the last thread resumed thread on the target exits, the
server sends a no-resumed event to GDB.  If however, GDB enables the
GDB_TO_EXIT option on a thread, and, that thread exits, the server
sends a thread exit event for that thread instead.

In all-stop RSP mode, since events can only be forwarded to GDB one at
a time, and the whole target stops whenever an event is reported, GDB
resumes the target again after getting a THREAD_EXITED event, and then
the server finally reports back a no-resumed event if/when
appropriate.

For non-stop RSP though, events are asynchronous, and if the server
sends a thread-exit event for the last resumed thread, the no-resumed
event is never sent.  This patch makes sure that in non-stop mode, the
server queues a no-resumed event after the thread-exit event if it was
the last resumed thread that exited.

Without this, we'd see failures in step-over-thread-exit testcases
added later in the series, like so:

   continue
   Continuing.
 - No unwaited-for children left.
 - (gdb) PASS: gdb.threads/step-over-thread-exit.exp: displaced-stepping=off: non-stop=on: target-non-stop=on: schedlock=off: ns_stop_all=1: continue stops when thread exits
 + FAIL: gdb.threads/step-over-thread-exit.exp: displaced-stepping=off: non-stop=on: target-non-stop=on: schedlock=off: ns_stop_all=1: continue stops when thread exits (timeout)

(and other similar ones)

Change-Id: I927d78b30f88236dbd5634b051a716f72420e7c7
---
 gdbserver/linux-low.cc | 47 +++++++++++++++++++++++++-----------------
 gdbserver/linux-low.h  |  2 ++
 gdbserver/server.cc    | 12 ++++++++++-
 gdbserver/target.cc    |  6 ++++++
 gdbserver/target.h     |  6 ++++++
 5 files changed, 53 insertions(+), 20 deletions(-)

diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 8154d8088c1..50aa369ccf0 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -2960,7 +2960,6 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
   int report_to_gdb;
   int trace_event;
   int in_step_range;
-  int any_resumed;
 
   threads_debug_printf ("[%s]", target_pid_to_str (ptid).c_str ());
 
@@ -2974,23 +2973,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
   in_step_range = 0;
   ourstatus->set_ignore ();
 
-  auto status_pending_p_any = [&] (thread_info *thread)
-    {
-      return status_pending_p_callback (thread, minus_one_ptid);
-    };
-
-  auto not_stopped = [&] (thread_info *thread)
-    {
-      return not_stopped_callback (thread, minus_one_ptid);
-    };
-
-  /* Find a resumed LWP, if any.  */
-  if (find_thread (status_pending_p_any) != NULL)
-    any_resumed = 1;
-  else if (find_thread (not_stopped) != NULL)
-    any_resumed = 1;
-  else
-    any_resumed = 0;
+  bool was_any_resumed = any_resumed ();
 
   if (step_over_bkpt == null_ptid)
     pid = wait_for_event (ptid, &w, options);
@@ -3001,7 +2984,7 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
       pid = wait_for_event (step_over_bkpt, &w, options & ~WNOHANG);
     }
 
-  if (pid == 0 || (pid == -1 && !any_resumed))
+  if (pid == 0 || (pid == -1 && !was_any_resumed))
     {
       gdb_assert (target_options & TARGET_WNOHANG);
 
@@ -6169,6 +6152,32 @@ linux_process_target::thread_stopped (thread_info *thread)
   return get_thread_lwp (thread)->stopped;
 }
 
+bool
+linux_process_target::any_resumed ()
+{
+  bool any_resumed;
+
+  auto status_pending_p_any = [&] (thread_info *thread)
+    {
+      return status_pending_p_callback (thread, minus_one_ptid);
+    };
+
+  auto not_stopped = [&] (thread_info *thread)
+    {
+      return not_stopped_callback (thread, minus_one_ptid);
+    };
+
+  /* Find a resumed LWP, if any.  */
+  if (find_thread (status_pending_p_any) != NULL)
+    any_resumed = 1;
+  else if (find_thread (not_stopped) != NULL)
+    any_resumed = 1;
+  else
+    any_resumed = 0;
+
+  return any_resumed;
+}
+
 /* This exposes stop-all-threads functionality to other modules.  */
 
 void
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index 051a7196ef7..43c35009030 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -259,6 +259,8 @@ class linux_process_target : public process_stratum_target
 
   bool thread_stopped (thread_info *thread) override;
 
+  bool any_resumed () override;
+
   void pause_all (bool freeze) override;
 
   void unpause_all (bool unfreeze) override;
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index fc64a442ab4..ea990ebe1d3 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -4740,7 +4740,17 @@ handle_target_event (int err, gdb_client_data client_data)
 	    }
 	}
       else
-	push_stop_notification (cs.last_ptid, cs.last_status);
+	{
+	  push_stop_notification (cs.last_ptid, cs.last_status);
+
+	  if (cs.last_status.kind () == TARGET_WAITKIND_THREAD_EXITED
+	      && !target_any_resumed ())
+	    {
+	      target_waitstatus ws;
+	      ws.set_no_resumed ();
+	      push_stop_notification (null_ptid, ws);
+	    }
+	}
     }
 
   /* Be sure to not change the selected thread behind GDB's back.
diff --git a/gdbserver/target.cc b/gdbserver/target.cc
index ad32af0416e..96c0f49d2dd 100644
--- a/gdbserver/target.cc
+++ b/gdbserver/target.cc
@@ -612,6 +612,12 @@ process_stratum_target::thread_stopped (thread_info *thread)
   gdb_assert_not_reached ("target op thread_stopped not supported");
 }
 
+bool
+process_stratum_target::any_resumed ()
+{
+  return true;
+}
+
 bool
 process_stratum_target::supports_get_tib_address ()
 {
diff --git a/gdbserver/target.h b/gdbserver/target.h
index 0aa7db49c18..dd8a55cf827 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -323,6 +323,9 @@ class process_stratum_target
   /* Return true if THREAD is known to be stopped now.  */
   virtual bool thread_stopped (thread_info *thread);
 
+  /* Return true if any thread is known to be resumed.  */
+  virtual bool any_resumed ();
+
   /* Return true if the get_tib_address op is supported.  */
   virtual bool supports_get_tib_address ();
 
@@ -683,6 +686,9 @@ target_read_btrace_conf (struct btrace_target_info *tinfo,
 #define target_supports_software_single_step() \
   the_target->supports_software_single_step ()
 
+#define target_any_resumed() \
+  the_target->any_resumed ()
+
 ptid_t mywait (ptid_t ptid, struct target_waitstatus *ourstatus,
 	       target_wait_flags options, int connected_wait);
 
-- 
2.36.0


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

* [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (17 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 18/25] gdbserver: Queue no-resumed event after thread exit Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-21 11:07   ` Eli Zaretskii
  2022-06-20 22:54 ` [PATCH 20/25] Tighten gdb.threads/no-unwaited-for-left.exp regexps Pedro Alves
                   ` (5 subsequent siblings)
  24 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

If scheduler-locking is in effect, like e.g., with "set
scheduler-locking on", and you step over a function that spawns a new
thread, the new thread is allowed to run free, at least until some
event is hit, at which point, whether the new thread is re-resumed
depends on a number of seemingly random factors.  E.g., if the target
is all-stop, and the parent thread hits a breakpoint, and gdb decides
the breakpoint isn't interesting to report to the user, then the
parent thread is resumed, but the new thread is left stopped.

I think that letting the new threads run with scheduler-locking
enabled is a defect.  This commit fixes that, making use of the new
clone events on Linux, and of target_thread_events() on targets where
new threads have no connection to the thread that spawned them.

Testcase and documentation changes included.

Change-Id: Ie12140138b37534b7fc1d904da34f0f174aa11ce
---
 gdb/NEWS                                      |  3 +
 gdb/doc/gdb.texinfo                           |  3 +
 gdb/infrun.c                                  | 41 +++++++++---
 .../gdb.threads/schedlock-new-thread.c        | 46 ++++++++++++++
 .../gdb.threads/schedlock-new-thread.exp      | 63 +++++++++++++++++++
 5 files changed, 148 insertions(+), 8 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/schedlock-new-thread.c
 create mode 100644 gdb/testsuite/gdb.threads/schedlock-new-thread.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 5576c355b7a..2e842cb00f9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 12
 
+* If supported by the target, when scheduler-locking is in effect, new
+  threads created by the resumed thread are held stopped.
+
 * "info breakpoints" now displays enabled breakpoint locations of
   disabled breakpoints as in the "y-" state.  For example:
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index b3e995f166b..775c4b1347b 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -6932,6 +6932,9 @@ current thread away from the thread that you are debugging.  The
 @code{replay} mode behaves like @code{off} in record mode and like
 @code{on} in replay mode.
 
+If supported by the target, when scheduler-locking is in effect, new
+threads created by the resumed thread are held stopped.
+
 @item show scheduler-locking
 Display the current scheduler locking mode.
 @end table
diff --git a/gdb/infrun.c b/gdb/infrun.c
index c7d5acf2326..6c273fd8cf2 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -103,6 +103,8 @@ static bool start_step_over (void);
 
 static bool step_over_info_valid_p (void);
 
+static bool schedlock_applies (struct thread_info *tp);
+
 /* 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;
@@ -1823,7 +1825,13 @@ static void
 update_thread_events_after_step_over (thread_info *event_thread,
 				      const target_waitstatus &event_status)
 {
-  if (target_supports_set_thread_options (0))
+  if (schedlock_applies (event_thread))
+    {
+      /* If scheduler-locking applies, continue reporting
+	 thread-created/thread-cloned events.  */
+      return;
+    }
+  else if (target_supports_set_thread_options (0))
     {
       /* We can control per-thread options.  Disable events for the
 	 event thread, unless the thread is gone.  */
@@ -2395,9 +2403,14 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
        to start stopped.  We need to release the displaced stepping
        buffer if the stepped thread exits, so we also enable
        thread-exit events.
+
+     - If scheduler-locking applies, threads that the current thread
+       spawns should remain halted.  It's not strictly necessary to
+       enable thread-exit events in this case, but it doesn't hurt.
   */
   if (step_over_info_valid_p ()
-      || displaced_step_in_progress_thread (tp))
+      || displaced_step_in_progress_thread (tp)
+      || schedlock_applies (tp))
     {
       gdb_thread_options options = GDB_TO_CLONE | GDB_TO_EXIT;
       if (target_supports_set_thread_options (options))
@@ -2405,6 +2418,13 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
       else
 	target_thread_events (true);
     }
+  else
+    {
+      if (target_supports_set_thread_options (0))
+	target_set_thread_options (tp, 0);
+      else if (!displaced_step_in_progress_any_thread ())
+	target_thread_events (false);
+    }
 
   infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
 		       resume_ptid.to_string ().c_str (),
@@ -5995,16 +6015,21 @@ handle_inferior_event (struct execution_control_state *ecs)
 	    parent->set_running (false);
 
 	  /* If resuming the child, mark it running.  */
-	  if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
-	      || (follow_child || (!detach_fork && (non_stop || sched_multi))))
+	  if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
+	       && !schedlock_applies (ecs->event_thread))
+	      || (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
+		  && (follow_child
+		      || (!detach_fork && (non_stop || sched_multi)))))
 	    child->set_running (true);
 
 	  /* In non-stop mode, also resume the other branch.  */
 	  if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
-	       && target_is_non_stop_p ())
-	      || (!detach_fork && (non_stop
-				   || (sched_multi
-				       && target_is_non_stop_p ()))))
+	       && target_is_non_stop_p ()
+	       && !schedlock_applies (ecs->event_thread))
+	      || (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
+		  && (!detach_fork && (non_stop
+				       || (sched_multi
+					   && target_is_non_stop_p ())))))
 	    {
 	      if (follow_child)
 		switch_to_thread (parent);
diff --git a/gdb/testsuite/gdb.threads/schedlock-new-thread.c b/gdb/testsuite/gdb.threads/schedlock-new-thread.c
new file mode 100644
index 00000000000..67a2ef61a7b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/schedlock-new-thread.c
@@ -0,0 +1,46 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2022 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/>.  */
+
+#include <pthread.h>
+#include <assert.h>
+#include <unistd.h>
+
+static void *
+thread_func (void *arg)
+{
+  while (1)
+    sleep (1);
+}
+
+int
+main (void)
+{
+  pthread_t thread;
+  int ret;
+
+  ret = pthread_create (&thread, NULL, thread_func, NULL); /* set break 1 here */
+  assert (ret == 0);
+
+  /* When testing with schedlock enabled, the new thread won't run, so
+     we can't join it, as that would hang forever.  Instead, sleep for
+     a bit, enough that if the spawned thread is scheduled, it hits
+     the thread_func breakpoint before the main thread reaches the
+     "return 0" line below.  */
+  sleep (3);
+
+  return 0; /* set break 2 here */
+}
diff --git a/gdb/testsuite/gdb.threads/schedlock-new-thread.exp b/gdb/testsuite/gdb.threads/schedlock-new-thread.exp
new file mode 100644
index 00000000000..8952cb7531c
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/schedlock-new-thread.exp
@@ -0,0 +1,63 @@
+# Copyright 2021-2022 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 continuing over a thread spawn with scheduler-locking on.
+
+standard_testfile .c
+
+set syscalls_src $srcdir/lib/my-syscalls.S
+
+if { [build_executable "failed to prepare" $testfile \
+	  [list $srcfile $syscalls_src] {debug pthreads}] == -1 } {
+    return
+}
+
+proc test {non-stop schedlock} {
+    save_vars ::GDBFLAGS {
+	append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
+	clean_restart $::binfile
+    }
+
+    set linenum1 [gdb_get_line_number "set break 1 here"]
+
+    if { ![runto $::srcfile:$linenum1] } {
+	return
+    }
+
+    delete_breakpoints
+
+    set linenum2 [gdb_get_line_number "set break 2 here"]
+    gdb_breakpoint $linenum2
+
+    gdb_breakpoint "thread_func"
+
+    gdb_test_no_output "set scheduler-locking $schedlock"
+
+    if {$schedlock} {
+	gdb_test "continue" \
+	    "return 0.*set break 2 here .*" \
+	    "continue does not stop in new thread"
+    } else {
+	gdb_test "continue" \
+	    "thread_func .*" \
+	    "continue stops in new thread"
+    }
+}
+
+foreach_with_prefix non-stop {off on} {
+    foreach_with_prefix schedlock {off on} {
+	test ${non-stop} ${schedlock}
+    }
+}
-- 
2.36.0


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

* [PATCH 20/25] Tighten gdb.threads/no-unwaited-for-left.exp regexps
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (18 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-07-13 21:32   ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 21/25] Report thread exit event for leader if reporting thread exit events Pedro Alves
                   ` (4 subsequent siblings)
  24 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

A WIP version of the following patch resulted in a bug that went
unnoticed by the testuite, like so:

 (gdb) PASS: gdb.threads/no-unwaited-for-left.exp: enable scheduler-locking, for main thread
 continue
 Continuing.
 [New Thread 1251861.1251861]
 No unwaited-for children left.
 (gdb) PASS: gdb.threads/no-unwaited-for-left.exp: continue stops when the main thread exits
 info threads
   Id   Target Id                                Frame
   3    Thread 1251861.1251863 "no-unwaited-for" __pthread_clockjoin_ex (threadid=140737351558976, thread_return=0x0, clockid=<optimized out>, abstime=<optimized out>, block=<optimized out>) at pthread_join_common.c:145
   4    Thread 1251861.1251861 "no-unwaited-for" <unavailable> in ?? ()

 The current thread <Thread ID 1> has terminated.  See `help thread'.
 (gdb) PASS: gdb.threads/no-unwaited-for-left.exp: only thread 3 left, main thread terminated

Somehow, above, GDB re-added the zombie leader back before printing
"No unwaited-for children left.".  The "only thread 3 left, main
thread terminated" test should have caught this, but didn't.  That is
because the test's regexp has a ".*" after the part that matches
thread 3.  This commit tightens that regexp to catch such a bug.  It
also tightens the "only main thread left, thread 2 terminated" test's
regexp in the same way.

Change-Id: I8744f327a0aa0e2669d1ddda88247e99b91cefff
---
 gdb/testsuite/gdb.threads/no-unwaited-for-left.exp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp b/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp
index cfeb495594d..6773aa46d71 100644
--- a/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp
+++ b/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp
@@ -42,7 +42,7 @@ gdb_test "continue" \
     "continue stops when thread 2 exits"
 
 gdb_test "info threads" \
-	 "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *1 *Thread \[^\r\n\]* \[^\r\n\]*.*The current thread <Thread ID 2> has terminated.*" \
+	 "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *1 *Thread \[^\r\n\]* \[^\r\n\]*\[\r\n\]*The current thread <Thread ID 2> has terminated.*" \
 	 "only main thread left, thread 2 terminated"
 
 # Select the main thread, let the third thread start, and stop at the
@@ -63,7 +63,7 @@ gdb_test "continue" \
     "continue stops when the main thread exits"
 
 gdb_test "info threads" \
-	 "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *3 *Thread \[^\r\n\]* \[^\r\n\]*.*The current thread <Thread ID 1> has terminated.*" \
+	 "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *3 *Thread \[^\r\n\]* \[^\r\n\]*\[\r\n\]*The current thread <Thread ID 1> has terminated.*" \
 	 "only thread 3 left, main thread terminated"
 
 # Make sure thread apply all works when we have exited threads in the
-- 
2.36.0


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

* [PATCH 21/25] Report thread exit event for leader if reporting thread exit events
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (19 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 20/25] Tighten gdb.threads/no-unwaited-for-left.exp regexps Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 22/25] Ignore failure to read PC when resuming Pedro Alves
                   ` (3 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

If GDB sets the GDB_TO_EXIT option on a thread, then if the thread
disappears from the thread list, GDB expects to shortly see a thread
exit event for it.  See e.g., here, in
remote_target::update_thread_list():

    /* Do not remove the thread if we've requested to be
       notified of its exit.  For example, the thread may be
       displaced stepping, infrun will need to handle the
       exit event, and displaced stepping info is recorded
       in the thread object.  If we deleted the thread now,
       we'd lose that info.  */
    if ((tp->thread_options () & GDB_TO_EXIT) != 0)
      continue;

There's one scenario that is deleting a thread from the
remote/gdbserver thread list without ever reporting a corresponding
thread exit event though -- check_zombie_leaders.  This can lead to
GDB getting confused.  For example, with a following patch that
enables GDB_TO_EXIT whenever schedlock is enabled, we'd see this
regression:

 $ make check RUNTESTFLAGS="--target_board=native-extended-gdbserver" TESTS="gdb.threads/no-unwaited-for-left.exp"
 ...
 Running src/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp ...
 FAIL: gdb.threads/no-unwaited-for-left.exp: continue stops when the main thread exits (timeout)
 ... some more cascading FAILs ...

gdb.log shows:

 (gdb) continue
 Continuing.
 FAIL: gdb.threads/no-unwaited-for-left.exp: continue stops when the main thread exits (timeout)

A passing run would have resulted in:

 (gdb) continue
 Continuing.
 No unwaited-for children left.
 (gdb) PASS: gdb.threads/no-unwaited-for-left.exp: continue stops when the main thread exits

note how the leader thread is not listed in the remote-reported XML
thread list below:

 (gdb) set debug remote 1
 (gdb) set debug infrun 1
 (gdb) info threads
   Id   Target Id                                Frame
 * 1    Thread 1163850.1163850 "no-unwaited-for" main () at /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/no-unwaited-for-left.c:65
   3    Thread 1163850.1164130 "no-unwaited-for" [remote] Sending packet: $Hgp11c24a.11c362#39
 (gdb) c
 Continuing.
 [infrun] clear_proceed_status_thread: 1163850.1163850.0
 ...
     [infrun] resume_1: step=1, signal=GDB_SIGNAL_0, trap_expected=1, current thread [1163850.1163850.0] at 0x55555555534f
     [remote] Sending packet: $QPassSignals:#f3
     [remote] Packet received: OK
     [remote] Sending packet: $QThreadOptions;3:p11c24a.11c24a#f3
     [remote] Packet received: OK
 ...
     [infrun] target_set_thread_options: [options for Thread 1163850.1163850 are now 0x3]
 ...
   [infrun] do_target_resume: resume_ptid=1163850.1163850.0, step=0, sig=GDB_SIGNAL_0
   [remote] Sending packet: $vCont;c:p11c24a.11c24a#98
   [infrun] prepare_to_wait: prepare_to_wait
   [infrun] reset: reason=handling event
   [infrun] maybe_set_commit_resumed_all_targets: enabling commit-resumed for target extended-remote
   [infrun] maybe_call_commit_resumed_all_targets: calling commit_resumed for target extended-remote
 [infrun] fetch_inferior_event: exit
 [infrun] fetch_inferior_event: enter
   [infrun] scoped_disable_commit_resumed: reason=handling event
   [infrun] random_pending_event_thread: None found.
   [remote] wait: enter
     [remote] Packet received: N
   [remote] wait: exit
   [infrun] print_target_wait_results: target_wait (-1.0.0 [process -1], status) =
   [infrun] print_target_wait_results:   -1.0.0 [process -1],
   [infrun] print_target_wait_results:   status->kind = NO_RESUMED
   [infrun] handle_inferior_event: status->kind = NO_RESUMED
   [remote] Sending packet: $Hgp0.0#ad
   [remote] Packet received: OK
   [remote] Sending packet: $qXfer:threads:read::0,1000#92
   [remote] Packet received: l<threads>\n<thread id="p11c24a.11c362" core="0" name="no-unwaited-for" handle="0097d8f7ff7f0000"/>\n</threads>\n
   [infrun] handle_no_resumed: TARGET_WAITKIND_NO_RESUMED (ignoring: found resumed)
 ...

... however, infrun decided there was a resumed thread still, so
ignored the TARGET_WAITKIND_NO_RESUMED event.  Debugging GDB, we see
that the "found resumed" thread that GDB finds, is the leader thread.
Even though that thread is not on the remote-reported thread list, it
is still on the GDB thread list, due to the special case in remote.c
mentioned above.

This commit addresses the issue by fixing GDBserver to report a thread
exit event for the zombie leader too, i.e., making GDBserver respect
the "if thread has GDB_TO_EXIT set, report a thread exit" invariant.
To do that, it takes a bit more code than one would imagine off hand,
due to the fact that we currently always report LWP exit pending
events as TARGET_WAITKIND_EXITED, and then decide whether to convert
it to TARGET_WAITKIND_THREAD_EXITED just before reporting the event to
GDBserver core.  For the zombie leader scenario described, we need to
record early on that we want to report a THREAD_EXITED event, and then
make sure that decision isn't lost along the way to reporting the
event to GDBserver core.

Change-Id: I1e68fccdbc9534434dee07163d3fd19744c8403b
---
 gdbserver/linux-low.cc | 75 ++++++++++++++++++++++++++++++++++++------
 gdbserver/linux-low.h  |  5 +--
 2 files changed, 68 insertions(+), 12 deletions(-)

diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 50aa369ccf0..11375a56332 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -279,7 +279,8 @@ int using_threads = 1;
 static int stabilizing_threads;
 
 static void unsuspend_all_lwps (struct lwp_info *except);
-static void mark_lwp_dead (struct lwp_info *lwp, int wstat);
+static void mark_lwp_dead (struct lwp_info *lwp, int wstat,
+			   bool thread_event);
 static int lwp_is_marked_dead (struct lwp_info *lwp);
 static int kill_lwp (unsigned long lwpid, int signo);
 static void enqueue_pending_signal (struct lwp_info *lwp, int signal, siginfo_t *info);
@@ -1795,10 +1796,12 @@ iterate_over_lwps (ptid_t filter,
   return get_thread_lwp (thread);
 }
 
-void
+bool
 linux_process_target::check_zombie_leaders ()
 {
-  for_each_process ([this] (process_info *proc)
+  bool new_pending_event = false;
+
+  for_each_process ([&] (process_info *proc)
     {
       pid_t leader_pid = pid_of (proc);
       lwp_info *leader_lp = find_lwp_pid (ptid_t (leader_pid));
@@ -1867,9 +1870,19 @@ linux_process_target::check_zombie_leaders ()
 				"(it exited, or another thread execd), "
 				"deleting it.",
 				leader_pid);
-	  delete_lwp (leader_lp);
+
+	  thread_info *leader_thread = get_lwp_thread (leader_lp);
+	  if (report_exit_events_for (leader_thread))
+	    {
+	      mark_lwp_dead (leader_lp, W_EXITCODE (0, 0), true);
+	      new_pending_event = true;
+	    }
+	  else
+	    delete_lwp (leader_lp);
 	}
     });
+
+  return new_pending_event;
 }
 
 /* Callback for `find_thread'.  Returns the first LWP that is not
@@ -2329,7 +2342,7 @@ linux_process_target::filter_event (int lwpid, int wstat)
 	  /* Since events are serialized to GDB core, and we can't
 	     report this one right now.  Leave the status pending for
 	     the next time we're able to report it.  */
-	  mark_lwp_dead (child, wstat);
+	  mark_lwp_dead (child, wstat, false);
 	  return;
 	}
       else
@@ -2643,7 +2656,8 @@ linux_process_target::wait_for_event_filtered (ptid_t wait_ptid,
 
       /* Check for zombie thread group leaders.  Those can't be reaped
 	 until all other threads in the thread group are.  */
-      check_zombie_leaders ();
+      if (check_zombie_leaders ())
+	goto retry;
 
       auto not_stopped = [&] (thread_info *thread)
 	{
@@ -2890,6 +2904,17 @@ linux_process_target::filter_exit_event (lwp_info *event_child,
   struct thread_info *thread = get_lwp_thread (event_child);
   ptid_t ptid = ptid_of (thread);
 
+  if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED)
+    {
+      /* We're reporting a thread exit for the leader.  The exit was
+	 detected by check_zombie_leaders.  */
+      gdb_assert (is_leader (thread));
+      gdb_assert (report_exit_events_for (thread));
+
+      delete_lwp (event_child);
+      return ptid;
+    }
+
   /* Note we must filter TARGET_WAITKIND_SIGNALLED as well, otherwise
      if a non-leader thread exits with a signal, we'd report it to the
      core which would interpret it as the whole-process exiting.
@@ -3009,7 +3034,20 @@ linux_process_target::wait_1 (ptid_t ptid, target_waitstatus *ourstatus,
     {
       if (WIFEXITED (w))
 	{
-	  ourstatus->set_exited (WEXITSTATUS (w));
+	  /* If we already have the exit recorded in waitstatus, use
+	     it.  This will happen when we detect a zombie leader ,
+	     when we had GDB_TO_EXIT enabled for it.  We want to
+	     report its exit as TARGET_WAITKIND_THREAD_EXITED, as the
+	     whole process hasn't exited yet.  */
+	  const target_waitstatus &ws = event_child->waitstatus;
+	  if (ws.kind () != TARGET_WAITKIND_IGNORE)
+	    {
+	      gdb_assert (ws.kind () == TARGET_WAITKIND_EXITED
+			  || ws.kind () == TARGET_WAITKIND_THREAD_EXITED);
+	      *ourstatus = ws;
+	    }
+	  else
+	    ourstatus->set_exited (WEXITSTATUS (w));
 
 	  threads_debug_printf
 	    ("ret = %s, exited with retcode %d",
@@ -3718,8 +3756,15 @@ suspend_and_send_sigstop (thread_info *thread, lwp_info *except)
   send_sigstop (thread, except);
 }
 
+/* Mark LWP dead, with WSTAT as exit status pending to report later.
+   If THREAD_EVENT is true, interpret WSTAT as a thread exit event
+   instead of a process exit event.  This is meaningful for the leader
+   thread, as we normally report a process-wide exit event when we see
+   the leader exit, and a thread exit event when we see any other
+   thread exit.  */
+
 static void
-mark_lwp_dead (struct lwp_info *lwp, int wstat)
+mark_lwp_dead (struct lwp_info *lwp, int wstat, bool thread_event)
 {
   /* Store the exit status for later.  */
   lwp->status_pending_p = 1;
@@ -3728,9 +3773,19 @@ mark_lwp_dead (struct lwp_info *lwp, int wstat)
   /* Store in waitstatus as well, as there's nothing else to process
      for this event.  */
   if (WIFEXITED (wstat))
-    lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
+    {
+      if (thread_event)
+	lwp->waitstatus.set_thread_exited (WEXITSTATUS (wstat));
+      else
+	lwp->waitstatus.set_exited (WEXITSTATUS (wstat));
+    }
   else if (WIFSIGNALED (wstat))
-    lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
+    {
+      gdb_assert (!thread_event);
+      lwp->waitstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstat)));
+    }
+  else
+    gdb_assert_not_reached ("unknown status kind");
 
   /* Prevent trying to stop it.  */
   lwp->stopped = 1;
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index 43c35009030..09a71b795d9 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -572,8 +572,9 @@ class linux_process_target : public process_stratum_target
 
   /* Detect zombie thread group leaders, and "exit" them.  We can't
      reap their exits until all other threads in the group have
-     exited.  */
-  void check_zombie_leaders ();
+     exited.  Returns true if we left any new event pending, false
+     otherwise.  */
+  bool check_zombie_leaders ();
 
   /* Convenience function that is called when we're about to return an
      event to the core.  If the event is an exit or signalled event,
-- 
2.36.0


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

* [PATCH 22/25] Ignore failure to read PC when resuming
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (20 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 21/25] Report thread exit event for leader if reporting thread exit events Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 23/25] gdb/testsuite/lib/my-syscalls.S: Refactor new SYSCALL macro Pedro Alves
                   ` (2 subsequent siblings)
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

If GDB sets a GDB_TO_EXIT option on a thread, and the thread exits,
the server reports the corresponding thread exit event, and forgets
about the thread, i.e., removes the exited thread from its thread
list.

On the GDB side, GDB set the GDB_TO_EXIT option on a thread, GDB
delays deleting the thread from its thread list until it sees the
corresponding thread exit event, as that event needs special handling
in infrun.

When a thread disappears from the target, but it still exists on GDB's
thread list, in all-stop RSP mode, it can happen that GDB ends up
trying to resume such an already-exited-thread that GDB doesn't yet
know is gone.  When that happens, against GDBserver, typically the
ongoing execution command fails with this error:

 ...
 PC register is not available
 (gdb)

At the remote protocol level, we may see e.g., this:

      [remote] Packet received: w0;p97479.978d2
    [remote] wait: exit
    [infrun] print_target_wait_results: target_wait (-1.0.0 [process -1], status) =
    [infrun] print_target_wait_results:   619641.620754.0 [Thread 619641.620754],
    [infrun] print_target_wait_results:   status->kind = THREAD_EXITED, exit_status = 0
    [infrun] handle_inferior_event: status->kind = THREAD_EXITED, exit_status = 0
    [infrun] context_switch: Switching context from 0.0.0 to 619641.620754.0
    [infrun] clear_proceed_status_thread: 619641.620754.0

GDB saw an exit event for thread 619641.620754.  After processing it,
infrun decides to re-resume the target again.  To do that, infrun
picks some other thread that isn't exited yet from GDB's perspective,
switches to it, and calls keep_going.  Below, infrun happens to pick
thread p97479.97479, the leader, which also exited, but GDB doesn't
know yet:

...
    [remote] Sending packet: $Hgp97479.97479#75
    [remote] Packet received: OK
    [remote] Sending packet: $g#67
    [remote] Packet received: xxxxxxxxxxxxxxxxx (...snip...) [1120 bytes omitted]
    [infrun] reset: reason=handling event
    [infrun] maybe_set_commit_resumed_all_targets: not requesting commit-resumed for target remote, no resumed threads
  [infrun] fetch_inferior_event: exit
  PC register is not available
  (gdb)

The Linux backends, both in GDB and in GDBserver, already silently
ignore failures to resume, with the understanding that we'll see an
exit event soon.  Core of GDB doesn't do that yet, though.

This patch is a small step in that direction.  It swallows the error
when thrown from within resume_1.  There are likely are spots where we
will need similar treatment, but we can tackle them as we find them.

After this patch, we'll see something like this instead:

    [infrun] resume_1: step=0, signal=GDB_SIGNAL_0, trap_expected=0, current thread [640478.640478.0] at 0x0
    [infrun] do_target_resume: resume_ptid=640478.0.0, step=0, sig=GDB_SIGNAL_0
    [remote] Sending packet: $vCont;c:p9c5de.-1#78
    [infrun] prepare_to_wait: prepare_to_wait
    [infrun] reset: reason=handling event
    [infrun] maybe_set_commit_resumed_all_targets: enabling commit-resumed for target remote
    [infrun] maybe_call_commit_resumed_all_targets: calling commit_resumed for target remote
  [infrun] fetch_inferior_event: exit
  [infrun] fetch_inferior_event: enter
    [infrun] scoped_disable_commit_resumed: reason=handling event
    [infrun] random_pending_event_thread: None found.
    [remote] wait: enter
      [remote] Packet received: W0;process:9c5de
    [remote] wait: exit
    [infrun] print_target_wait_results: target_wait (-1.0.0 [process -1], status) =
    [infrun] print_target_wait_results:   640478.0.0 [process 640478],
    [infrun] print_target_wait_results:   status->kind = EXITED, exit_status = 0
    [infrun] handle_inferior_event: status->kind = EXITED, exit_status = 0
  [Inferior 1 (process 640478) exited normally]
    [infrun] stop_waiting: stop_waiting
    [infrun] reset: reason=handling event
  (gdb) [infrun] fetch_inferior_event: exit

Change-Id: I7f1c7610923435c4e98e70acc5ebe5ebbac581e2
---
 gdb/infrun.c | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 6c273fd8cf2..babdb94696e 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2513,7 +2513,28 @@ resume_1 (enum gdb_signal sig)
       step = false;
     }
 
-  CORE_ADDR pc = regcache_read_pc (regcache);
+  CORE_ADDR pc = 0;
+  try
+    {
+      pc = regcache_read_pc (regcache);
+    }
+  catch (const gdb_exception_error &err)
+    {
+      /* Swallow errors as it may be that the current thread exited
+	 and we've haven't seen its exit status yet.  Let the
+	 resumption continue and we'll collect the exit event
+	 shortly.  */
+      if (err.error == TARGET_CLOSE_ERROR)
+	throw;
+
+      if (debug_infrun)
+	{
+	  string_file buf;
+	  exception_print (&buf, err);
+	  infrun_debug_printf ("resume: swallowing error: %s",
+			       buf.string ().c_str ());
+	}
+    }
 
   infrun_debug_printf ("step=%d, signal=%s, trap_expected=%d, "
 		       "current thread [%s] at %s",
-- 
2.36.0


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

* [PATCH 23/25] gdb/testsuite/lib/my-syscalls.S: Refactor new SYSCALL macro
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (21 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 22/25] Ignore failure to read PC when resuming Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 24/25] Testcases for stepping over thread exit syscall (PR gdb/27338) Pedro Alves
  2022-06-20 22:54 ` [PATCH 25/25] Document remote clone events, and QThreadOptions packet Pedro Alves
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

Refactor the syscall assembly code in gdb/testsuite/lib/my-syscalls.S
behind a SYSCALL macro so that it's easy to add new syscalls without
duplicating code.

Note that the way the macro is implemented, it only works correctly
for syscalls with up to 3 arguments, and, if the syscall doesn't
return (the macro doesn't bother to save/restore callee-saved
registers).

The following patch will want to use the macro to define a wrapper for
the "exit" syscall, so the limitations continue to be sufficient.

Change-Id: I8acf1463b11a084d6b4579aaffb49b5d0dea3bba
---
 gdb/testsuite/lib/my-syscalls.S | 50 +++++++++++++++++++++------------
 1 file changed, 32 insertions(+), 18 deletions(-)

diff --git a/gdb/testsuite/lib/my-syscalls.S b/gdb/testsuite/lib/my-syscalls.S
index 5fc38948217..50544d101e0 100644
--- a/gdb/testsuite/lib/my-syscalls.S
+++ b/gdb/testsuite/lib/my-syscalls.S
@@ -21,36 +21,50 @@
 
 #include <asm/unistd.h>
 
-/* int my_execve (const char *file, char *argv[], char *envp[]);  */
-
-.global my_execve
-my_execve:
+/* The SYSCALL macro below current supports calling syscalls with up
+   to 3 arguments, and, assumes the syscall never returns, like exec
+   and exit.  If you need to call syscalls with more arguments or you
+   need to call syscalls that actually return, you'll need to update
+   the macros.  We don't bother with optimizing setting up fewer
+   arguments for syscalls that take fewer arguments, as we're not
+   optimizating for speed or space, but for maintainability.  */
 
 #if defined(__x86_64__)
 
-	mov $__NR_execve, %rax
-	/* rdi, rsi and rdx already contain the right arguments.  */
-my_execve_syscall:
-	syscall
-	ret
+#define SYSCALL(NAME, NR)	\
+.global NAME			;\
+NAME:				;\
+	mov $NR, %rax		;\
+	/* rdi, rsi and rdx already contain the right arguments.  */ \
+NAME ## _syscall:		;\
+	syscall			;\
+	ret			;
 
 #elif defined(__i386__)
 
-	mov $__NR_execve, %eax
-	mov 4(%esp), %ebx
-	mov 8(%esp), %ecx
-	mov 12(%esp), %edx
-my_execve_syscall:
-	int $0x80
+#define SYSCALL(NAME, NR)	\
+.global NAME			;\
+NAME:				;\
+	mov $NR, %eax		;\
+	mov 4(%esp), %ebx	;\
+	mov 8(%esp), %ecx	;\
+	mov 12(%esp), %edx	;\
+NAME ## _syscall:		;\
+	int $0x80		;\
 	ret
 
 #elif defined(__aarch64__)
 
-	mov x8, #__NR_execve
-	/* x0, x1 and x2 already contain the right arguments.  */
-my_execve_syscall:
+#define SYSCALL(NAME, NR)	\
+.global NAME			;\
+NAME:				;\
+	mov x8, NR		;\
+	/* x0, x1 and x2 already contain the right arguments.  */ \
+NAME ## _syscall:		;\
 	svc #0
 
 #else
 # error "Unsupported architecture"
 #endif
+
+SYSCALL (my_execve, __NR_execve)
-- 
2.36.0


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

* [PATCH 24/25] Testcases for stepping over thread exit syscall (PR gdb/27338)
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (22 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 23/25] gdb/testsuite/lib/my-syscalls.S: Refactor new SYSCALL macro Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-20 22:54 ` [PATCH 25/25] Document remote clone events, and QThreadOptions packet Pedro Alves
  24 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches; +Cc: Pedro Alves

From: Simon Marchi <simon.marchi@efficios.com>

- New in this series version:

 - gdb.threads/step-over-thread-exit.exp has a couple new testing axes:

   - non-stop vs all-stop.

   - in non-stop have the testcase explicitly stop all threads in
     non-stop mode, vs not doing that.

 - bail out of gdb.threads/step-over-thread-exit.exp early on FAIL, to
   avoid cascading timeouts.

Add new gdb.threads/step-over-thread-exit.exp and
gdb.threads/step-over-thread-exit-while-stop-all-threads.exp
testcases, exercising stepping over thread exit syscall.  These make
use of lib/my-syscalls.S to define the exit syscall.

Co-authored-by: Pedro Alves <pedro@palves.net>
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
Change-Id: Ie8b2c5747db99b7023463a897a8390d9e814a9c9
---
 ...-over-thread-exit-while-stop-all-threads.c |  77 +++++++++++
 ...ver-thread-exit-while-stop-all-threads.exp |  69 ++++++++++
 .../gdb.threads/step-over-thread-exit.c       |  52 ++++++++
 .../gdb.threads/step-over-thread-exit.exp     | 122 ++++++++++++++++++
 gdb/testsuite/lib/my-syscalls.S               |   4 +
 gdb/testsuite/lib/my-syscalls.h               |   5 +
 6 files changed, 329 insertions(+)
 create mode 100644 gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.c
 create mode 100644 gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.exp
 create mode 100644 gdb/testsuite/gdb.threads/step-over-thread-exit.c
 create mode 100644 gdb/testsuite/gdb.threads/step-over-thread-exit.exp

diff --git a/gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.c b/gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.c
new file mode 100644
index 00000000000..2699ad5d714
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.c
@@ -0,0 +1,77 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2022 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/>.  */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include "../lib/my-syscalls.h"
+
+#define NUM_THREADS 32
+
+static void *
+stepper_over_exit_thread (void *v)
+{
+  my_exit (0);
+
+  /* my_exit above should exit the thread, we don't expect to reach
+     here.  */
+  abort ();
+}
+
+static void *
+spawner_thread (void *v)
+{
+  for (;;)
+    {
+      pthread_t threads[NUM_THREADS];
+      int i;
+
+      for (i = 0; i < NUM_THREADS; i++)
+	pthread_create (&threads[i], NULL, stepper_over_exit_thread, NULL);
+
+      for (i = 0; i < NUM_THREADS; i++)
+	pthread_join (threads[i], NULL);
+    }
+}
+
+static void
+break_here (void)
+{
+}
+
+static void *
+breakpoint_hitter_thread (void *v)
+{
+  for (;;)
+    break_here ();
+}
+
+int
+main ()
+{
+  pthread_t breakpoint_hitter;
+  pthread_t spawner;
+
+  alarm (60);
+
+  pthread_create (&spawner, NULL, spawner_thread, NULL);
+  pthread_create (&breakpoint_hitter, NULL, breakpoint_hitter_thread, NULL);
+
+  pthread_join (spawner, NULL);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.exp b/gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.exp
new file mode 100644
index 00000000000..6a46aff700e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-thread-exit-while-stop-all-threads.exp
@@ -0,0 +1,69 @@
+# Copyright 2021-2022 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 stepping over a breakpoint installed on an instruction that
+# exits the thread, while another thread is repeatedly hitting a
+# breakpoint, causing GDB to stop all threads.
+
+standard_testfile .c
+
+set syscalls_src $srcdir/lib/my-syscalls.S
+
+if { [build_executable "failed to prepare" $testfile \
+	  [list $srcfile $syscalls_src] {debug pthreads}] == -1 } {
+    return
+}
+
+proc test {displaced-stepping target-non-stop} {
+    save_vars ::GDBFLAGS {
+	append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
+	clean_restart $::binfile
+    }
+
+    gdb_test_no_output "set displaced-stepping ${displaced-stepping}"
+
+    if { ![runto_main] } {
+	return
+    }
+
+    # The "stepper over exit" threads will step over an instruction
+    # that causes them to exit.
+    gdb_test "break my_exit_syscall if 0"
+
+    # The "breakpoint hitter" thread will repeatedly hit this
+    # breakpoint, causing GDB to stop all threads.
+    gdb_test "break break_here"
+
+    # To avoid flooding the log with thread created/exited messages.
+    gdb_test_no_output "set print thread-events off"
+
+    # Make sure the target reports the breakpoint stops.
+    gdb_test_no_output "set breakpoint condition-evaluation host"
+
+    for { set i 0 } { $i < 30 } { incr i } {
+	with_test_prefix "iter $i" {
+	    if { [gdb_test "continue" "hit Breakpoint $::decimal, break_here .*"] != 0 } {
+		# Exit if there's a failure to avoid lengthy timeouts.
+		break
+	    }
+	}
+    }
+}
+
+foreach_with_prefix displaced-stepping {off auto} {
+    foreach_with_prefix target-non-stop {off on} {
+	test ${displaced-stepping} ${target-non-stop}
+    }
+}
diff --git a/gdb/testsuite/gdb.threads/step-over-thread-exit.c b/gdb/testsuite/gdb.threads/step-over-thread-exit.c
new file mode 100644
index 00000000000..878e5924c5c
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-thread-exit.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021-2022 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/>.  */
+
+#include <pthread.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "../lib/my-syscalls.h"
+
+static void *
+thread_func (void *arg)
+{
+  my_exit (0);
+
+  /* my_exit above should exit the thread, we don't expect to reach
+     here.  */
+  abort ();
+}
+
+int
+main (void)
+{
+  int i;
+
+  /* Spawn and join a thread, 100 times.  */
+  for (i = 0; i < 100; i++)
+    {
+      pthread_t thread;
+      int ret;
+
+      ret = pthread_create (&thread, NULL, thread_func, NULL);
+      assert (ret == 0);
+
+      ret = pthread_join (thread, NULL);
+      assert (ret == 0);
+    }
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/step-over-thread-exit.exp b/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
new file mode 100644
index 00000000000..910577f5b9f
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
@@ -0,0 +1,122 @@
+# Copyright 2021-2022 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 stepping over a breakpoint installed on an instruction that
+# exits the thread.
+
+standard_testfile .c
+
+set syscalls_src $srcdir/lib/my-syscalls.S
+
+if { [build_executable "failed to prepare" $testfile \
+	  [list $srcfile $syscalls_src] {debug pthreads}] == -1 } {
+    return
+}
+
+# Each argument is a different testing axis, most of them obvious.
+# NS_STOP_ALL is only used if testing "set non-stop on", and indicates
+# whether to have GDB explicitly stop all threads before continuing to
+# thread exit.
+proc test {displaced-stepping non-stop target-non-stop schedlock ns_stop_all} {
+    if {${non-stop} == "off" && $ns_stop_all} {
+	error "invalid arguments"
+    }
+
+    save_vars ::GDBFLAGS {
+	append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
+	append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
+	clean_restart $::binfile
+    }
+
+    gdb_test_no_output "set displaced-stepping ${displaced-stepping}"
+
+    if { ![runto_main] } {
+	return
+    }
+
+    gdb_breakpoint "my_exit_syscall"
+
+    if {$schedlock
+	|| (${non-stop} == "on" && $ns_stop_all)} {
+	gdb_test "continue" \
+	    "Thread 2 .*hit Breakpoint $::decimal.* my_exit_syscall .*" \
+	    "continue until syscall"
+
+	if {${non-stop} == "on"} {
+	    # The test only spawns one thread at a time, so this just
+	    # stops the main thread.
+	    gdb_test_multiple "interrupt -a" "" {
+		-re "$::gdb_prompt " {
+		    gdb_test_multiple "" $gdb_test_name {
+			-re "Thread 1 \[^\r\n\]*stopped." {
+			    pass $gdb_test_name
+			}
+		    }
+		}
+	    }
+	}
+
+	gdb_test "thread 2" "Switching to thread 2 .*"
+
+	gdb_test_no_output "set scheduler-locking ${schedlock}"
+
+	gdb_test "continue" \
+	    "No unwaited-for children left." \
+	    "continue stops when thread exits"
+    } else {
+	gdb_test_no_output "set scheduler-locking ${schedlock}"
+
+	for { set i 0 } { $i < 100 } { incr i } {
+	    with_test_prefix "iter $i" {
+		set ok 0
+		set thread "<unknown>"
+		gdb_test_multiple "continue" "" {
+		    -re -wrap "Thread ($::decimal) .*hit Breakpoint $::decimal.* my_exit_syscall .*" {
+			set thread $expect_out(1,string)
+			set ok 1
+		    }
+		}
+		if {!${ok}} {
+		    # Exit if there's a failure to avoid lengthy
+		    # timeouts.
+		    break
+		}
+		gdb_test "thread $thread" ".*"
+	    }
+	}
+    }
+}
+
+foreach_with_prefix displaced-stepping {off auto} {
+    foreach_with_prefix non-stop {off on} {
+	foreach_with_prefix target-non-stop {off on} {
+	    if {${non-stop} == "on" && ${target-non-stop} == "off"} {
+		# Invalid combination.
+		continue
+	    }
+
+	    foreach_with_prefix schedlock {off on} {
+		if {${non-stop} == "on"} {
+		    foreach_with_prefix ns_stop_all {0 1} {
+			test ${displaced-stepping} ${non-stop} ${target-non-stop} \
+			    ${schedlock} ${ns_stop_all}
+		    }
+		} else {
+		    test ${displaced-stepping} ${non-stop} ${target-non-stop} ${schedlock} 0
+		}
+	    }
+	}
+    }
+}
diff --git a/gdb/testsuite/lib/my-syscalls.S b/gdb/testsuite/lib/my-syscalls.S
index 50544d101e0..ddef0f04bd4 100644
--- a/gdb/testsuite/lib/my-syscalls.S
+++ b/gdb/testsuite/lib/my-syscalls.S
@@ -68,3 +68,7 @@ NAME ## _syscall:		;\
 #endif
 
 SYSCALL (my_execve, __NR_execve)
+
+/* void my_exit (int code);  */
+
+SYSCALL (my_exit, __NR_exit)
diff --git a/gdb/testsuite/lib/my-syscalls.h b/gdb/testsuite/lib/my-syscalls.h
index a0c069c58e5..ceaa587b3e6 100644
--- a/gdb/testsuite/lib/my-syscalls.h
+++ b/gdb/testsuite/lib/my-syscalls.h
@@ -22,4 +22,9 @@
 
 int my_execve (const char *file, char *argv[], char *envp[]);
 
+/* `exit` syscall, which makes the thread exit (as opposed to
+   `exit_group`, which makes the process exit).  */
+
+void my_exit (int code);
+
 #endif /* MY_SYSCALLS_H */
-- 
2.36.0


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

* [PATCH 25/25] Document remote clone events, and QThreadOptions packet
  2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
                   ` (23 preceding siblings ...)
  2022-06-20 22:54 ` [PATCH 24/25] Testcases for stepping over thread exit syscall (PR gdb/27338) Pedro Alves
@ 2022-06-20 22:54 ` Pedro Alves
  2022-06-21 12:07   ` Eli Zaretskii
  24 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-06-20 22:54 UTC (permalink / raw)
  To: gdb-patches

This commit documents in both manual and NEWS:

 - the new remote clone event stop reply,
 - the new QThreadOptions packet and its current defined options,
 - the associated "set/show remote thread-events-packet" command,
 - and the associated QThreadOptions qSupported feature.

Change-Id: Ic1c8de1fefba95729bbd242969284265de42427e
---
 gdb/NEWS            |  19 +++++++
 gdb/doc/gdb.texinfo | 130 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 146 insertions(+), 3 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 2e842cb00f9..f7aeb8882cd 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -44,6 +44,10 @@ show print nibbles
   This controls whether the 'print/t' command will display binary values
   in groups of four bits, known as "nibbles".  The default is 'off'.
 
+set remote thread-options-packet
+show remote thread-options-packet
+  Set/show the use of the thread options packet.
+
 * Changed commands
 
 maintenance info line-table
@@ -113,6 +117,21 @@ GNU/Linux/LoongArch (gdbserver)	loongarch*-*-linux*
 
   ** GDBserver is now supported on LoongArch GNU/Linux.
 
+* New remote packets
+
+clone stop reason
+  Indicates that a clone system call was executed.
+
+QThreadOptions
+  Enable/disable optional event reporting, on a per-thread basis.
+  Currently supported options are GDB_TO_CLONE, to enable clone event
+  reporting, and GDB_TO_EXIT to enable thread exit event reporting.
+
+QThreadOptions in qSupported
+  The qSupported packet allows GDB to inform the stub it supports the
+  QThreadOptions packet, and the qSupported response can contain the
+  set of thread options the remote stub supports.
+
 *** Changes in GDB 12
 
 * DBX mode is deprecated, and will be removed in GDB 13
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 775c4b1347b..d0266256874 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -23906,6 +23906,10 @@ are:
 @tab @code{QThreadEvents}
 @tab Tracking thread lifetime.
 
+@item @code{thread-options}
+@tab @code{QThreadOptions}
+@tab Set thread event reporting options.
+
 @item @code{no-resumed-stop-reply}
 @tab @code{no resumed thread left stop reply}
 @tab Tracking thread lifetime.
@@ -41687,6 +41691,17 @@ appropriate @samp{qSupported} feature (@pxref{qSupported}).  The
 remote stub must also supply the appropriate @samp{qSupported} feature
 indicating support.
 
+@cindex thread clone events, remote reply
+@anchor{thread clone event}
+@item clone
+The packet indicates that @code{clone} was called, and @var{r} is the
+thread ID of the new child thread.  Refer to @ref{thread-id syntax}
+for the format of the @var{thread-id} field.  This packet is only
+applicable to targets that support clone events.
+
+This packet should not be sent by default; @value{GDBN} requests it
+with the @ref{QThreadOptions} packet.
+
 @cindex thread create event, remote reply
 @anchor{thread create event}
 @item create
@@ -41725,9 +41740,10 @@ hex strings.
 @item w @var{AA} ; @var{tid}
 
 The thread exited, and @var{AA} is the exit status.  This response
-should not be sent by default; @value{GDBN} requests it with the
-@ref{QThreadEvents} packet.  See also @ref{thread create event} above.
-@var{AA} is formatted as a big-endian hex string.
+should not be sent by default; @value{GDBN} requests it with either
+the @ref{QThreadEvents} or @ref{QThreadOptions} packets.  See also
+@ref{thread create event} above.  @var{AA} is formatted as a
+big-endian hex string.
 
 @item N
 There are no resumed threads left in the target.  In other words, even
@@ -42452,6 +42468,10 @@ same thread.  @value{GDBN} does not enable this feature unless the
 stub reports that it supports it by including @samp{QThreadEvents+} in
 its @samp{qSupported} reply.
 
+This packet always enables event reporting for all threads of all
+processes under control of the remote stub.  For per-thread control of
+optional event reporting, see the @ref{QThreadOptions} packet.
+
 Reply:
 @table @samp
 @item OK
@@ -42468,6 +42488,98 @@ the stub.
 Use of this packet is controlled by the @code{set remote thread-events}
 command (@pxref{Remote Configuration, set remote thread-events}).
 
+@anchor{QThreadOptions}
+@item QThreadOptions@r{[};@var{options}@r{[}:@var{thread-id}@r{]]}@dots{}
+@cindex thread options, remote request
+@cindex @samp{QThreadOptions} packet
+
+For each inferior thread, the rightmost options with a matching
+@var{thread-id} are applied.  Any options previously set on a thread
+are discarded and replaced by the new options specified.  Threads that
+do not match any @var{thread-id} retain their previously-set options.
+Thread IDs are specified using the syntax described in @ref{thread-id
+syntax}.  If multiprocess extensions (@pxref{multiprocess extensions})
+are supported, options can be specified to apply to all threads of a
+process by using the @samp{p@var{pid}.-1} form of @var{thread-id}.
+Options with no @var{thread-id} apply to all threads.  Specifying no
+options is an error.
+
+@var{options} is the bitwise @code{OR} of the following values.  All
+values are given in hexadecimal representation.
+
+@table @code
+@item GDB_TO_CLONE (0x1)
+Report thread clone events (@pxref{thread clone event}).  This is only
+meaningful for targets that support clone events (e.g., GNU/Linux
+systems).
+
+@item GDB_TO_EXIT (0x2)
+Report thread exit events (@pxref{thread exit event}).
+@end table
+
+@noindent
+
+For example, @value{GDBN} enables the @code{GDB_TO_EXIT} and
+@code{GDB_TO_CLONE} options when single-stepping a thread past a
+breakpoint, for the following reasons:
+
+@itemize @bullet
+@item
+Without exit events, if the single-stepped thread exits (e.g., it
+executes a thread exit system call), @value{GDBN} would wait forever
+not knowing that it should no longer expect a stop for that same
+thread, blocking other threads from progressing.
+
+@item
+Without clone events (on systems where threads are spawned via a clone
+system call), if the single-stepped thread spawns a new clone child
+(i.e., it executes a clone system call), and:
+
+@itemize @minus
+@item
+the breakpoint is stepped-over in-line, the spawned thread incorrectly
+runs free while the breakpoint being stepped over is not inserted,
+thus the spawned thread may potentially run past the breakpoint
+without stopping for it.  By enabling @code{GDB_TO_CLONE}, the new
+cloned thread is halted before it executes any instruction;
+
+@item
+if alternativelly displaced (out-of-line) stepping is used, the
+spawned thread starts running at the out-of-line PC, leading to
+undefined behavior, usually crashes or data corruption.  By enabling
+@code{GDB_TO_CLONE}, the new cloned thread is halted before it
+executes any instruction, and @value{GDBN} adjusts its PC before
+resuming its execution.
+@end itemize
+
+@end itemize
+
+On targets that support fork, vfork, and/or clone events (e.g.,
+GNU/Linux systems), fork, vfork, and clone children threads inherit
+their parent's thread options.  Otherwise, new threads start with
+thread options cleared.
+
+@value{GDBN} does not enable this feature unless the stub reports that
+it supports it by including
+@samp{QThreadOptions=@var{supported_options}} in its @samp{qSupported}
+reply.
+
+Reply:
+@table @samp
+@item OK
+The request succeeded.
+
+@item E @var{nn}
+An error occurred.  The error number @var{nn} is given as hex digits.
+
+@item @w{}
+An empty reply indicates that @samp{QThreadOptions} is not supported by
+the stub.
+@end table
+
+Use of this packet is controlled by the @code{set remote thread-options}
+command (@pxref{Remote Configuration, set remote thread-options}).
+
 @item qRcmd,@var{command}
 @cindex execute remote command, remote request
 @cindex @samp{qRcmd} packet
@@ -42913,6 +43025,11 @@ These are the currently defined stub features and their properties:
 @tab @samp{-}
 @tab No
 
+@item @samp{QThreadOptions}
+@tab Yes
+@tab @samp{-}
+@tab No
+
 @item @samp{no-resumed}
 @tab No
 @tab @samp{-}
@@ -43134,6 +43251,13 @@ The remote stub reports the supported actions in the reply to
 @item QThreadEvents
 The remote stub understands the @samp{QThreadEvents} packet.
 
+@item QThreadOptions=@var{supported_options}
+The remote stub understands the @samp{QThreadOptions} packet.
+@var{supported_options} indicates the set of thread options the remote
+stub supports.  @var{supported_options} has the same format as the
+@var{options} parameter of the @code{QThreadOptions} packet, described
+at @ref{QThreadOptions}.
+
 @item no-resumed
 The remote stub reports the @samp{N} stop reply.
 
-- 
2.36.0


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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-06-20 22:54 ` [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect Pedro Alves
@ 2022-06-21 11:07   ` Eli Zaretskii
  2022-07-11 14:20     ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-06-21 11:07 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 20 Jun 2022 23:54:13 +0100
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 5576c355b7a..2e842cb00f9 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,9 @@
>  
>  *** Changes since GDB 12
>  
> +* If supported by the target, when scheduler-locking is in effect, new
> +  threads created by the resumed thread are held stopped.
                        ^^^^^^^^^^^^^^^^^^
"a resumed thread", I guess?

> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index b3e995f166b..775c4b1347b 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -6932,6 +6932,9 @@ current thread away from the thread that you are debugging.  The
>  @code{replay} mode behaves like @code{off} in record mode and like
>  @code{on} in replay mode.
>  
> +If supported by the target, when scheduler-locking is in effect, new
> +threads created by the resumed thread are held stopped.

Same here, but maybe the following slight rewording will make the
sentence simpler:

  When scheduler-locking is supported by the target and is in effect,
  new threads created by a resumed thread are held stopped.

The documentation parts are okay with these nits fixed.

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

* Re: [PATCH 25/25] Document remote clone events, and QThreadOptions packet
  2022-06-20 22:54 ` [PATCH 25/25] Document remote clone events, and QThreadOptions packet Pedro Alves
@ 2022-06-21 12:07   ` Eli Zaretskii
  2022-07-11 15:19     ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-06-21 12:07 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 20 Jun 2022 23:54:19 +0100
> 
> This commit documents in both manual and NEWS:
> 
>  - the new remote clone event stop reply,
>  - the new QThreadOptions packet and its current defined options,
>  - the associated "set/show remote thread-events-packet" command,
>  - and the associated QThreadOptions qSupported feature.

Thanks.

>    ** GDBserver is now supported on LoongArch GNU/Linux.
>  
> +* New remote packets
> +
> +clone stop reason
> +  Indicates that a clone system call was executed.

I'm confused: what is the relation between the "stop reason" part and
the description saying that "a clone system call was executed"?  The
gdb.texinfo description only mentions "clone" as the packet name,
without the other 2 words.  What am I missing?

> +@cindex thread clone events, remote reply
> +@anchor{thread clone event}
> +@item clone
> +The packet indicates that @code{clone} was called, and @var{r} is the
> +thread ID of the new child thread.  Refer to @ref{thread-id syntax}
> +for the format of the @var{thread-id} field.  This packet is only
> +applicable to targets that support clone events.

The text refers to @var{r} and @var{thread-id}, but they are not
present on the @item line that describes the packet itself.  Where are
those parameters used in this case.

> +@item QThreadOptions@r{[};@var{options}@r{[}:@var{thread-id}@r{]]}@dots{}
> +@cindex thread options, remote request
> +@cindex @samp{QThreadOptions} packet
> +
> +For each inferior thread, the rightmost options with a matching
> +@var{thread-id} are applied.

"Rightmost" means here "the last in the list"?  If so, perhaps it's
worth saying that explicitly to avoid possible confusion.

> +@item
> +Without clone events (on systems where threads are spawned via a clone
> +system call), if the single-stepped thread spawns a new clone child
> +(i.e., it executes a clone system call), and:
> +
> +@itemize @minus
> +@item
> +the breakpoint is stepped-over in-line, the spawned thread incorrectly
> +runs free while the breakpoint being stepped over is not inserted,
> +thus the spawned thread may potentially run past the breakpoint
> +without stopping for it.  By enabling @code{GDB_TO_CLONE}, the new
> +cloned thread is halted before it executes any instruction;
> +
> +@item
> +if alternativelly displaced (out-of-line) stepping is used, the
> +spawned thread starts running at the out-of-line PC, leading to
> +undefined behavior, usually crashes or data corruption.  By enabling
> +@code{GDB_TO_CLONE}, the new cloned thread is halted before it
> +executes any instruction, and @value{GDBN} adjusts its PC before
> +resuming its execution.
> +@end itemize

This reads awkwardly, because you say "and:", and start each @item of
the following itemized list with a lower-case letter, but each @item
includes more than one sentence.  How about the following rephrasing:

    For example, @value{GDBN} enables the @code{GDB_TO_EXIT} and
    @code{GDB_TO_CLONE} options when single-stepping a thread past a
    breakpoint, for the following reasons:

    @itemize @bullet
    @item
    If the single-stepped thread exits (e.g., it executes a thread exit
    system call), enabling @code{GDB_TO_EXIT} prevents @value{GDBN} from
    waiting forever, not knowing that it should no longer expect a stop for
    that same thread, and blocking other threads from progressing.

    @item
    If the single-stepped thread spawns a new clone child (i.e., it
    executes a clone system call), enabling @code{GDB_TO_CLONE} halts the
    cloned thread before it executes any instructions, and thus prevents
    the following problematic situations:

    @itemize @minus
    @item
    if the breakpoint is stepped-over in-line, the spawned thread would
    incorrectly run free while the breakpoint being stepped over is not
    inserted, and thus the cloned thread may potentially run past the
    breakpoint without stopping for it;

    @item
    if displaced (out-of-line) stepping is used, the cloned thread starts
    running at the out-of-line PC, leading to undefined behavior, usually
    crashing or corrupting data.
    @end itemize

    @end itemize

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-06-21 11:07   ` Eli Zaretskii
@ 2022-07-11 14:20     ` Pedro Alves
  2022-07-11 15:44       ` Eli Zaretskii
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-07-11 14:20 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

Hi,

On 2022-06-21 12:07 p.m., Eli Zaretskii wrote:
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 20 Jun 2022 23:54:13 +0100
>>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index 5576c355b7a..2e842cb00f9 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,9 @@
>>  
>>  *** Changes since GDB 12
>>  
>> +* If supported by the target, when scheduler-locking is in effect, new
>> +  threads created by the resumed thread are held stopped.
>                         ^^^^^^^^^^^^^^^^^^
> "a resumed thread", I guess?

When scheduler locking is in effect, execution commands only resume one thread,
so "the" seems more correct to me.

I wrote the sentence in the manual first, before copying it here, and in the manual,
the text describing scheduler locking, above my new sentence says for example:

  "If @code{on}, then only the current thread may run when the inferior is resumed."

> 
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index b3e995f166b..775c4b1347b 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -6932,6 +6932,9 @@ current thread away from the thread that you are debugging.  The
>>  @code{replay} mode behaves like @code{off} in record mode and like
>>  @code{on} in replay mode.
>>  
>> +If supported by the target, when scheduler-locking is in effect, new
>> +threads created by the resumed thread are held stopped.
> 
> Same here, but maybe the following slight rewording will make the
> sentence simpler:
> 
>   When scheduler-locking is supported by the target and is in effect,
>   new threads created by a resumed thread are held stopped.
> 

Hmm, that is actually semantically different from what I intended.  I see the ambiguity,
but the part that I intended to cover with "if supported" is the hold-new-threads-stopped
part.  The intent is to document how targets ideally should behave when they implement
scheduler-locking.  However, not all targets that support scheduler locking
will hold new threads stopped, as this needs to be implemented on
the target/backend side.

I notice now that the whole "set scheduler-locking" description doesn't even
say that not all targets support scheduler locking.  Hmm.  How about I just
remove the "If supported by the target" part.  We can just consider that targets
that don't behave that way yet should be fixed (there's a new testcase that
will fail for such targets), while older GDBservers just can't ever be fixed,
but such is life of software progress.

> The documentation parts are okay with these nits fixed.
> 

How about this, then, given the explanations above?  Is it OK?

diff --git c/gdb/NEWS w/gdb/NEWS
index 1178a37017e..94553f18608 100644
--- c/gdb/NEWS
+++ w/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 12
 
+* When scheduler-locking is in effect, new threads created by the
+  resumed thread are held stopped.
+
 * "info breakpoints" now displays enabled breakpoint locations of
   disabled breakpoints as in the "y-" state.  For example:
 
diff --git c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
index 7a4e337d15b..fc297218696 100644
--- c/gdb/doc/gdb.texinfo
+++ w/gdb/doc/gdb.texinfo
@@ -6953,6 +6953,9 @@ current thread away from the thread that you are debugging.  The
 @code{replay} mode behaves like @code{off} in record mode and like
 @code{on} in replay mode.
 
+When scheduler-locking is in effect, new threads created by the
+resumed thread are held stopped.
+
 @item show scheduler-locking
 Display the current scheduler locking mode.
 @end table

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

* Re: [PATCH 25/25] Document remote clone events, and QThreadOptions packet
  2022-06-21 12:07   ` Eli Zaretskii
@ 2022-07-11 15:19     ` Pedro Alves
  2022-07-11 16:09       ` Eli Zaretskii
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-07-11 15:19 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches


Hi!

On 2022-06-21 1:07 p.m., Eli Zaretskii wrote:
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 20 Jun 2022 23:54:19 +0100
>>
>> This commit documents in both manual and NEWS:
>>
>>  - the new remote clone event stop reply,
>>  - the new QThreadOptions packet and its current defined options,
>>  - the associated "set/show remote thread-events-packet" command,
>>  - and the associated QThreadOptions qSupported feature.
> 
> Thanks.
> 
>>    ** GDBserver is now supported on LoongArch GNU/Linux.
>>  
>> +* New remote packets
>> +
>> +clone stop reason
>> +  Indicates that a clone system call was executed.
> 
> I'm confused: what is the relation between the "stop reason" part and
> the description saying that "a clone system call was executed"?  The
> gdb.texinfo description only mentions "clone" as the packet name,
> without the other 2 words.  What am I missing?

A "stop reason" is a remote-protocol defined term, which should be familiar
to remote stub maintainers.  See here:

 @item
 If @var{n} is a recognized @dfn{stop reason}, it describes a more
 specific event that stopped the target.  The currently defined stop
 reasons are listed below.  The @var{aa} should be @samp{05}, the trap
 signal.  At most one stop reason should be present.

So that new line in NEWS is saying that we're added a new "stop reason",
called "clone". When the remote target reports it, it means that the
program being debugged execute a clone system call.

This is exactly how e.g. the "fork" and "vfork" stop reasons (and others) were
mentioned in NEWS back when they were introduced:

~~~
fork stop reason
  Indicates that a fork system call was executed.

vfork stop reason
  Indicates that a vfork system call was executed.
~~~


> 
>> +@cindex thread clone events, remote reply
>> +@anchor{thread clone event}
>> +@item clone
>> +The packet indicates that @code{clone} was called, and @var{r} is the
>> +thread ID of the new child thread.  Refer to @ref{thread-id syntax}
>> +for the format of the @var{thread-id} field.  This packet is only
>> +applicable to targets that support clone events.
> 
> The text refers to @var{r} and @var{thread-id}, but they are not
> present on the @item line that describes the packet itself.  Where are
> those parameters used in this case.

Hmm.  @var{r} is described further up.  This is all within the description
of the T stop reply, which mentions:

@item T @var{AA} @var{n1}:@var{r1};@var{n2}:@var{r2};@dots{}
...
Each @samp{@var{n}:@var{r}} pair is interpreted as follows:
...
@item
If @var{n} is a recognized @dfn{stop reason}, it describes a more
specific event that stopped the target.  The currently defined stop
reasons are listed below. 
...


So for the "clone" case in question, "clone" is the recognized stop reason,
and in the n:r pair, r is the thread ID of the child.


However, I agree that @var{thread-id} appears a bit out of the blue.  Note this
is the exact same wording as in the existing fork and vfork stop reasons, which
I just copied.  E.g.:

 @cindex fork events, remote reply
 @item fork
 The packet indicates that @code{fork} was called, and @var{r}
 is the thread ID of the new child process.  Refer to
 @ref{thread-id syntax} for the format of the @var{thread-id}
 field. (...)

A bit further above, we say:

 @item
 If @var{n} is @samp{thread}, then @var{r} is the @var{thread-id} of
 the stopped thread, as specified in @ref{thread-id syntax}.

so how about we use the same wording for fork/vfork?

Like so (against current master):

~~~~~~~~~~~~~~~
diff --git c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
index 7a4e337d15b..79b2fccb501 100644
--- c/gdb/doc/gdb.texinfo
+++ w/gdb/doc/gdb.texinfo
@@ -41655,11 +41655,10 @@ apply.
 
 @cindex fork events, remote reply
 @item fork
-The packet indicates that @code{fork} was called, and @var{r}
-is the thread ID of the new child process.  Refer to
-@ref{thread-id syntax} for the format of the @var{thread-id}
-field.  This packet is only applicable to targets that support
-fork events.
+The packet indicates that @code{fork} was called, and @var{r} is the
+@var{thread-id} of the new child process, as specified in
+@ref{thread-id syntax}.  This packet is only applicable to targets
+that support fork events.
 
 This packet should not be sent by default; older @value{GDBN} versions
 did not support it.  @value{GDBN} requests it, by supplying an
@@ -41669,11 +41668,10 @@ indicating support.
 
 @cindex vfork events, remote reply
 @item vfork
-The packet indicates that @code{vfork} was called, and @var{r}
-is the thread ID of the new child process. Refer to
-@ref{thread-id syntax} for the format of the @var{thread-id}
-field.  This packet is only applicable to targets that support
-vfork events.
+The packet indicates that @code{vfork} was called, and @var{r} is the
+@var{thread-id} of the new child process, as specified in
+@ref{thread-id syntax}.  This packet is only applicable to targets
+that support vfork events.
 
 This packet should not be sent by default; older @value{GDBN} versions
 did not support it.  @value{GDBN} requests it, by supplying an
~~~~~~~~~~~~~~~

I'd push this as a separate patch right away, if OK.

> 
>> +@item QThreadOptions@r{[};@var{options}@r{[}:@var{thread-id}@r{]]}@dots{}
>> +@cindex thread options, remote request
>> +@cindex @samp{QThreadOptions} packet
>> +
>> +For each inferior thread, the rightmost options with a matching
>> +@var{thread-id} are applied.
> 
> "Rightmost" means here "the last in the list"?  If so, perhaps it's
> worth saying that explicitly to avoid possible confusion.

I can change it, but I'm curious what the confusion could be?  I used
rightmost here to contrast with the vCont description, which uses "leftmost".

> 
>> +@item
>> +Without clone events (on systems where threads are spawned via a clone
>> +system call), if the single-stepped thread spawns a new clone child
>> +(i.e., it executes a clone system call), and:
>> +
>> +@itemize @minus
>> +@item
>> +the breakpoint is stepped-over in-line, the spawned thread incorrectly
>> +runs free while the breakpoint being stepped over is not inserted,
>> +thus the spawned thread may potentially run past the breakpoint
>> +without stopping for it.  By enabling @code{GDB_TO_CLONE}, the new
>> +cloned thread is halted before it executes any instruction;
>> +
>> +@item
>> +if alternativelly displaced (out-of-line) stepping is used, the
>> +spawned thread starts running at the out-of-line PC, leading to
>> +undefined behavior, usually crashes or data corruption.  By enabling
>> +@code{GDB_TO_CLONE}, the new cloned thread is halted before it
>> +executes any instruction, and @value{GDBN} adjusts its PC before
>> +resuming its execution.
>> +@end itemize
> 
> This reads awkwardly, because you say "and:", and start each @item of
> the following itemized list with a lower-case letter, but each @item
> includes more than one sentence.  How about the following rephrasing:
> 
>     For example, @value{GDBN} enables the @code{GDB_TO_EXIT} and
>     @code{GDB_TO_CLONE} options when single-stepping a thread past a
>     breakpoint, for the following reasons:
> 
>     @itemize @bullet
>     @item
>     If the single-stepped thread exits (e.g., it executes a thread exit
>     system call), enabling @code{GDB_TO_EXIT} prevents @value{GDBN} from
>     waiting forever, not knowing that it should no longer expect a stop for
>     that same thread, and blocking other threads from progressing.
> 
>     @item
>     If the single-stepped thread spawns a new clone child (i.e., it
>     executes a clone system call), enabling @code{GDB_TO_CLONE} halts the
>     cloned thread before it executes any instructions, and thus prevents
>     the following problematic situations:
> 
>     @itemize @minus
>     @item
>     if the breakpoint is stepped-over in-line, the spawned thread would
>     incorrectly run free while the breakpoint being stepped over is not
>     inserted, and thus the cloned thread may potentially run past the
>     breakpoint without stopping for it;
> 
>     @item
>     if displaced (out-of-line) stepping is used, the cloned thread starts
>     running at the out-of-line PC, leading to undefined behavior, usually
>     crashing or corrupting data.
>     @end itemize
> 
>     @end itemize
> 

Thank you, that is perfect, I'll take it.

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 14:20     ` Pedro Alves
@ 2022-07-11 15:44       ` Eli Zaretskii
  2022-07-11 16:09         ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-07-11 15:44 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 11 Jul 2022 15:20:03 +0100
> 
> >> +* If supported by the target, when scheduler-locking is in effect, new
> >> +  threads created by the resumed thread are held stopped.
> >                         ^^^^^^^^^^^^^^^^^^
> > "a resumed thread", I guess?
> 
> When scheduler locking is in effect, execution commands only resume one thread,
> so "the" seems more correct to me.

I don't think so, because which thread that is, is unknown to the
reader at this point.

As a compromise, I suggest to say "new threads created by resumed
threads".

> I wrote the sentence in the manual first, before copying it here, and in the manual,
> the text describing scheduler locking, above my new sentence says for example:
> 
>   "If @code{on}, then only the current thread may run when the inferior is resumed."

That's fine, but I don't see how it implies anything for the NEWS
entry.  Besides, the m,annual has context that NEWS not necessarily does.

> I notice now that the whole "set scheduler-locking" description doesn't even
> say that not all targets support scheduler locking.  Hmm.  How about I just
> remove the "If supported by the target" part.  We can just consider that targets
> that don't behave that way yet should be fixed (there's a new testcase that
> will fail for such targets), while older GDBservers just can't ever be fixed,
> but such is life of software progress.

That's okay, but it takes care of a different wording issue.

> How about this, then, given the explanations above?  Is it OK?
> 
> diff --git c/gdb/NEWS w/gdb/NEWS
> index 1178a37017e..94553f18608 100644
> --- c/gdb/NEWS
> +++ w/gdb/NEWS
> @@ -3,6 +3,9 @@
>  
>  *** Changes since GDB 12
>  
> +* When scheduler-locking is in effect, new threads created by the
> +  resumed thread are held stopped.
> +
>  * "info breakpoints" now displays enabled breakpoint locations of
>    disabled breakpoints as in the "y-" state.  For example:
>  
> diff --git c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
> index 7a4e337d15b..fc297218696 100644
> --- c/gdb/doc/gdb.texinfo
> +++ w/gdb/doc/gdb.texinfo
> @@ -6953,6 +6953,9 @@ current thread away from the thread that you are debugging.  The
>  @code{replay} mode behaves like @code{off} in record mode and like
>  @code{on} in replay mode.
>  
> +When scheduler-locking is in effect, new threads created by the
> +resumed thread are held stopped.

This still has the same "the resumed thread" issue.

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 15:44       ` Eli Zaretskii
@ 2022-07-11 16:09         ` Pedro Alves
  2022-07-11 16:30           ` Eli Zaretskii
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-07-11 16:09 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 2022-07-11 4:44 p.m., Eli Zaretskii wrote:
>> Cc: gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 11 Jul 2022 15:20:03 +0100
>>
>>>> +* If supported by the target, when scheduler-locking is in effect, new
>>>> +  threads created by the resumed thread are held stopped.
>>>                         ^^^^^^^^^^^^^^^^^^
>>> "a resumed thread", I guess?
>>
>> When scheduler locking is in effect, execution commands only resume one thread,
>> so "the" seems more correct to me.
> 
> I don't think so, because which thread that is, is unknown to the
> reader at this point.
> 
> As a compromise, I suggest to say "new threads created by resumed
> threads".
> 
>> I wrote the sentence in the manual first, before copying it here, and in the manual,
>> the text describing scheduler locking, above my new sentence says for example:
>>
>>   "If @code{on}, then only the current thread may run when the inferior is resumed."
> 
> That's fine, but I don't see how it implies anything for the NEWS
> entry.  Besides, the m,annual has context that NEWS not necessarily does.

You had said "Same here, " for the manual part, and I was explaining that
for the manual it makes sense to use "the", and that I just copied it over
to NEWS.

> 
>> I notice now that the whole "set scheduler-locking" description doesn't even
>> say that not all targets support scheduler locking.  Hmm.  How about I just
>> remove the "If supported by the target" part.  We can just consider that targets
>> that don't behave that way yet should be fixed (there's a new testcase that
>> will fail for such targets), while older GDBservers just can't ever be fixed,
>> but such is life of software progress.
> 
> That's okay, but it takes care of a different wording issue.
> 
>> How about this, then, given the explanations above?  Is it OK?
>>
>> diff --git c/gdb/NEWS w/gdb/NEWS
>> index 1178a37017e..94553f18608 100644
>> --- c/gdb/NEWS
>> +++ w/gdb/NEWS
>> @@ -3,6 +3,9 @@
>>  
>>  *** Changes since GDB 12
>>  
>> +* When scheduler-locking is in effect, new threads created by the
>> +  resumed thread are held stopped.
>> +
>>  * "info breakpoints" now displays enabled breakpoint locations of
>>    disabled breakpoints as in the "y-" state.  For example:
>>  
>> diff --git c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
>> index 7a4e337d15b..fc297218696 100644
>> --- c/gdb/doc/gdb.texinfo
>> +++ w/gdb/doc/gdb.texinfo
>> @@ -6953,6 +6953,9 @@ current thread away from the thread that you are debugging.  The
>>  @code{replay} mode behaves like @code{off} in record mode and like
>>  @code{on} in replay mode.
>>  
>> +When scheduler-locking is in effect, new threads created by the
>> +resumed thread are held stopped.
> 
> This still has the same "the resumed thread" issue.
> 

Sure, because I had explained why I thought it was still better.

Let's treat NEWS and manual differently, then.  This version adds the missing context
in the NEWS entry.  WDYT?

diff --git c/gdb/NEWS w/gdb/NEWS
index 1178a37017e..d474ed21350 100644
--- c/gdb/NEWS
+++ w/gdb/NEWS
@@ -3,6 +3,13 @@
 
 *** Changes since GDB 12
 
+* Scheduler-locking and new threads
+
+  When scheduler-locking is in effect, only the current thread may run
+  when the inferior is resumed.  However, previously, new threads
+  created by the resumed thread would still be able to run free.  Now,
+  they are held stopped.
+
 * "info breakpoints" now displays enabled breakpoint locations of
   disabled breakpoints as in the "y-" state.  For example:
 
diff --git c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
index 7a4e337d15b..fc297218696 100644
--- c/gdb/doc/gdb.texinfo
+++ w/gdb/doc/gdb.texinfo
@@ -6953,6 +6953,9 @@ current thread away from the thread that you are debugging.  The
 @code{replay} mode behaves like @code{off} in record mode and like
 @code{on} in replay mode.
 
+When scheduler-locking is in effect, new threads created by the
+resumed thread are held stopped.
+
 @item show scheduler-locking
 Display the current scheduler locking mode.
 @end table

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

* Re: [PATCH 25/25] Document remote clone events, and QThreadOptions packet
  2022-07-11 15:19     ` Pedro Alves
@ 2022-07-11 16:09       ` Eli Zaretskii
  2022-07-11 16:54         ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-07-11 16:09 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 11 Jul 2022 16:19:57 +0100
> 
> >> +* New remote packets
> >> +
> >> +clone stop reason
> >> +  Indicates that a clone system call was executed.
> > 
> > I'm confused: what is the relation between the "stop reason" part and
> > the description saying that "a clone system call was executed"?  The
> > gdb.texinfo description only mentions "clone" as the packet name,
> > without the other 2 words.  What am I missing?
> 
> A "stop reason" is a remote-protocol defined term, which should be familiar
> to remote stub maintainers.  See here:

I see.  It's highly confusing, IMO, although we use it elsewhere in
NEWS.  I would suggest at least

  'clone', a stop reason

or

  'clone' (a stop reason)

or even

  new stop reason: 'clone'

(Yes, for the rest of similar entries as well.  No, I don't insist.)

> fork stop reason
>   Indicates that a fork system call was executed.
> 
> vfork stop reason
>   Indicates that a vfork system call was executed.

Just for the record: you do realize that using 3 nouns, 2 of which can
also be verbs, in a row, without any context creates very ambiguous
text?  "clone stop reason" could be interpreted as "clone the stop
reason" (i.e. the stop reason should be cloned) or "reason for
stopping the clone" or "stop reason of the clone" and probably a few
others.

>  @cindex fork events, remote reply
>  @item fork
>  The packet indicates that @code{fork} was called, and @var{r}
>  is the thread ID of the new child process.  Refer to
>  @ref{thread-id syntax} for the format of the @var{thread-id}
>  field. (...)
> 
> A bit further above, we say:
> 
>  @item
>  If @var{n} is @samp{thread}, then @var{r} is the @var{thread-id} of
>  the stopped thread, as specified in @ref{thread-id syntax}.

"thread-id" should not be in @var here, since it is not referenced
anywhere.  The text about fork events that you cite above gets that
right, and the new text should so the same.

> >> +@item QThreadOptions@r{[};@var{options}@r{[}:@var{thread-id}@r{]]}@dots{}
> >> +@cindex thread options, remote request
> >> +@cindex @samp{QThreadOptions} packet
> >> +
> >> +For each inferior thread, the rightmost options with a matching
> >> +@var{thread-id} are applied.
> > 
> > "Rightmost" means here "the last in the list"?  If so, perhaps it's
> > worth saying that explicitly to avoid possible confusion.
> 
> I can change it, but I'm curious what the confusion could be?  I used
> rightmost here to contrast with the vCont description, which uses "leftmost".

I don't think "left" and "right" are well defined in this context, so
they sound strange (especially to someone who reads RTL text every
day).  Why not "first" and "last"?

> >     For example, @value{GDBN} enables the @code{GDB_TO_EXIT} and
> >     @code{GDB_TO_CLONE} options when single-stepping a thread past a
> >     breakpoint, for the following reasons:
> > 
> >     @itemize @bullet
> >     @item
> >     If the single-stepped thread exits (e.g., it executes a thread exit
> >     system call), enabling @code{GDB_TO_EXIT} prevents @value{GDBN} from
> >     waiting forever, not knowing that it should no longer expect a stop for
> >     that same thread, and blocking other threads from progressing.
> > 
> >     @item
> >     If the single-stepped thread spawns a new clone child (i.e., it
> >     executes a clone system call), enabling @code{GDB_TO_CLONE} halts the
> >     cloned thread before it executes any instructions, and thus prevents
> >     the following problematic situations:
> > 
> >     @itemize @minus
> >     @item
> >     if the breakpoint is stepped-over in-line, the spawned thread would
> >     incorrectly run free while the breakpoint being stepped over is not
> >     inserted, and thus the cloned thread may potentially run past the
> >     breakpoint without stopping for it;
> > 
> >     @item
> >     if displaced (out-of-line) stepping is used, the cloned thread starts
> >     running at the out-of-line PC, leading to undefined behavior, usually
> >     crashing or corrupting data.
> >     @end itemize
> > 
> >     @end itemize
> > 
> 
> Thank you, that is perfect, I'll take it.

But please capitalize the text of each @item (I forgot to do that in
the last two).

Thanks.

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 16:09         ` Pedro Alves
@ 2022-07-11 16:30           ` Eli Zaretskii
  2022-07-11 16:38             ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-07-11 16:30 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 11 Jul 2022 17:09:13 +0100
> 
> > That's fine, but I don't see how it implies anything for the NEWS
> > entry.  Besides, the m,annual has context that NEWS not necessarily does.
> 
> You had said "Same here, " for the manual part, and I was explaining that
> for the manual it makes sense to use "the", and that I just copied it over
> to NEWS.

The remark about the manual was a general one.

> --- c/gdb/NEWS
> +++ w/gdb/NEWS
> @@ -3,6 +3,13 @@
>  
>  *** Changes since GDB 12
>  
> +* Scheduler-locking and new threads
> +
> +  When scheduler-locking is in effect, only the current thread may run
> +  when the inferior is resumed.  However, previously, new threads
> +  created by the resumed thread would still be able to run free.  Now,
> +  they are held stopped.

This is okay.

> diff --git c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
> index 7a4e337d15b..fc297218696 100644
> --- c/gdb/doc/gdb.texinfo
> +++ w/gdb/doc/gdb.texinfo
> @@ -6953,6 +6953,9 @@ current thread away from the thread that you are debugging.  The
>  @code{replay} mode behaves like @code{off} in record mode and like
>  @code{on} in replay mode.
>  
> +When scheduler-locking is in effect, new threads created by the
> +resumed thread are held stopped.

Here, I'd add this new info where the manual describes the feature
itself:

  @item set scheduler-locking @var{mode}
  @cindex scheduler locking mode
  @cindex lock scheduler
  Set the scheduler locking mode.  It applies to normal execution,
  record mode, and replay mode.  If it is @code{off}, then there is no
  locking and any thread may run at any time.  If @code{on}, then only
  the current thread may run when the inferior is resumed; even threads
  created by the resumed thread are held stopped.          ^^^^^^^^^^^^
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

And now I wonder what happens when scheduler-locking is set to 'step':
are new threads created by the resumed thread held stopped as in the
ON case?

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 16:30           ` Eli Zaretskii
@ 2022-07-11 16:38             ` Pedro Alves
  2022-07-11 17:00               ` Eli Zaretskii
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-07-11 16:38 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 2022-07-11 5:30 p.m., Eli Zaretskii wrote:
>> Cc: gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 11 Jul 2022 17:09:13 +0100
>>
>>> That's fine, but I don't see how it implies anything for the NEWS
>>> entry.  Besides, the m,annual has context that NEWS not necessarily does.
>>
>> You had said "Same here, " for the manual part, and I was explaining that
>> for the manual it makes sense to use "the", and that I just copied it over
>> to NEWS.
> 
> The remark about the manual was a general one.
> 
>> --- c/gdb/NEWS
>> +++ w/gdb/NEWS
>> @@ -3,6 +3,13 @@
>>  
>>  *** Changes since GDB 12
>>  
>> +* Scheduler-locking and new threads
>> +
>> +  When scheduler-locking is in effect, only the current thread may run
>> +  when the inferior is resumed.  However, previously, new threads
>> +  created by the resumed thread would still be able to run free.  Now,
>> +  they are held stopped.
> 
> This is okay.
> 
>> diff --git c/gdb/doc/gdb.texinfo w/gdb/doc/gdb.texinfo
>> index 7a4e337d15b..fc297218696 100644
>> --- c/gdb/doc/gdb.texinfo
>> +++ w/gdb/doc/gdb.texinfo
>> @@ -6953,6 +6953,9 @@ current thread away from the thread that you are debugging.  The
>>  @code{replay} mode behaves like @code{off} in record mode and like
>>  @code{on} in replay mode.
>>  
>> +When scheduler-locking is in effect, new threads created by the
>> +resumed thread are held stopped.
> 
> Here, I'd add this new info where the manual describes the feature
> itself:
> 
>   @item set scheduler-locking @var{mode}
>   @cindex scheduler locking mode
>   @cindex lock scheduler
>   Set the scheduler locking mode.  It applies to normal execution,
>   record mode, and replay mode.  If it is @code{off}, then there is no
>   locking and any thread may run at any time.  If @code{on}, then only
>   the current thread may run when the inferior is resumed; even threads
>   created by the resumed thread are held stopped.          ^^^^^^^^^^^^
>   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> And now I wonder what happens when scheduler-locking is set to 'step':
> are new threads created by the resumed thread held stopped as in the
> ON case?
> 

Yes, they are.  That's why I put the new sentence at the end and not connected to "on",
and  said "is in effect", not "on".  I.e., when the scheduler is actually locked.

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

* Re: [PATCH 25/25] Document remote clone events, and QThreadOptions packet
  2022-07-11 16:09       ` Eli Zaretskii
@ 2022-07-11 16:54         ` Pedro Alves
  2022-07-11 17:02           ` Eli Zaretskii
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-07-11 16:54 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 2022-07-11 5:09 p.m., Eli Zaretskii wrote:
>> Cc: gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 11 Jul 2022 16:19:57 +0100
>>
>>>> +* New remote packets
>>>> +
>>>> +clone stop reason
>>>> +  Indicates that a clone system call was executed.
>>>
>>> I'm confused: what is the relation between the "stop reason" part and
>>> the description saying that "a clone system call was executed"?  The
>>> gdb.texinfo description only mentions "clone" as the packet name,
>>> without the other 2 words.  What am I missing?
>>
>> A "stop reason" is a remote-protocol defined term, which should be familiar
>> to remote stub maintainers.  See here:
> 
> I see.  It's highly confusing, IMO, although we use it elsewhere in
> NEWS.  I would suggest at least
> 
>   'clone', a stop reason
> 
> or
> 
>   'clone' (a stop reason)
> 
> or even
> 
>   new stop reason: 'clone'

I'll go with the last one.

> 
> (Yes, for the rest of similar entries as well.  No, I don't insist.)
> 
>> fork stop reason
>>   Indicates that a fork system call was executed.
>>
>> vfork stop reason
>>   Indicates that a vfork system call was executed.
> 
> Just for the record: you do realize that using 3 nouns, 2 of which can
> also be verbs, in a row, without any context creates very ambiguous
> text?  "clone stop reason" could be interpreted as "clone the stop
> reason" (i.e. the stop reason should be cloned) or "reason for
> stopping the clone" or "stop reason of the clone" and probably a few
> others.

There is context.  It's a section about new packets, and "stop reason"
is a familiar term.  And the sentence just below removed any ambiguity, IMO.
Anyhow, I can change it, no problem.

> 
>>  @cindex fork events, remote reply
>>  @item fork
>>  The packet indicates that @code{fork} was called, and @var{r}
>>  is the thread ID of the new child process.  Refer to
>>  @ref{thread-id syntax} for the format of the @var{thread-id}
>>  field. (...)
>>
>> A bit further above, we say:
>>
>>  @item
>>  If @var{n} is @samp{thread}, then @var{r} is the @var{thread-id} of
>>  the stopped thread, as specified in @ref{thread-id syntax}.
> 
> "thread-id" should not be in @var here, since it is not referenced
> anywhere.  The text about fork events that you cite above gets that
> right, and the new text should so the same.

The text for fork events uses "@var{thread-id}" too, in the
"Refer to @ref{thread-id syntax} for the format of the @var{thread-id} field."
sentence.  The new text was just copying that.

How about this to fix the preexisting issues?

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

From 77f4d421d311c12360d5051cc7047a51cf8e8cc4 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Mon, 11 Jul 2022 16:05:00 +0100
Subject: [PATCH] Fix non-existent "@var{thread-id}" in stop reply descriptions

In the description stop replies, where the "thread" register is
explained, and where the fork and vfork stop reasons are detailed,
there are references to "@var{thread-id}", but such variable does not
actually exist.  This commit fixes it.

Change-Id: If679944aaf15f6f64aabe506339a9e7667864cab
---
 gdb/doc/gdb.texinfo | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 7a4e337d15b..5250709818e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -41578,7 +41578,7 @@ series of bytes in target byte order, with each byte given by a
 two-digit hex number.
 
 @item
-If @var{n} is @samp{thread}, then @var{r} is the @var{thread-id} of
+If @var{n} is @samp{thread}, then @var{r} is the thread ID of
 the stopped thread, as specified in @ref{thread-id syntax}.
 
 @item
@@ -41655,11 +41655,10 @@ apply.
 
 @cindex fork events, remote reply
 @item fork
-The packet indicates that @code{fork} was called, and @var{r}
-is the thread ID of the new child process.  Refer to
-@ref{thread-id syntax} for the format of the @var{thread-id}
-field.  This packet is only applicable to targets that support
-fork events.
+The packet indicates that @code{fork} was called, and @var{r} is the
+thread ID of the new child process, as specified in @ref{thread-id
+syntax}.  This packet is only applicable to targets that support fork
+events.
 
 This packet should not be sent by default; older @value{GDBN} versions
 did not support it.  @value{GDBN} requests it, by supplying an
@@ -41669,11 +41668,10 @@ indicating support.
 
 @cindex vfork events, remote reply
 @item vfork
-The packet indicates that @code{vfork} was called, and @var{r}
-is the thread ID of the new child process. Refer to
-@ref{thread-id syntax} for the format of the @var{thread-id}
-field.  This packet is only applicable to targets that support
-vfork events.
+The packet indicates that @code{vfork} was called, and @var{r} is the
+thread ID of the new child process, as specified in @ref{thread-id
+syntax}.  This packet is only applicable to targets that support vfork
+events.
 
 This packet should not be sent by default; older @value{GDBN} versions
 did not support it.  @value{GDBN} requests it, by supplying an

base-commit: 53a7a7e17c5d21b7b182ddf6bd8bfc092af196f5
-- 
2.36.0

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


> 
>>>> +@item QThreadOptions@r{[};@var{options}@r{[}:@var{thread-id}@r{]]}@dots{}
>>>> +@cindex thread options, remote request
>>>> +@cindex @samp{QThreadOptions} packet
>>>> +
>>>> +For each inferior thread, the rightmost options with a matching
>>>> +@var{thread-id} are applied.
>>>
>>> "Rightmost" means here "the last in the list"?  If so, perhaps it's
>>> worth saying that explicitly to avoid possible confusion.
>>
>> I can change it, but I'm curious what the confusion could be?  I used
>> rightmost here to contrast with the vCont description, which uses "leftmost".
> 
> I don't think "left" and "right" are well defined in this context, so
> they sound strange (especially to someone who reads RTL text every
> day).  Why not "first" and "last"?

Would a RTL reader really be confused with "right"?  "Right" is always
on the "right", even for RTL, no?  I'd think it's actually "first" and "last" that
would be confusing to RTL readers, as they could misinterpret "last" as being
on the left, being used to start reading from the right.  Anyhow, 
I'll (begrudgingly) change it.

> 
>>>     For example, @value{GDBN} enables the @code{GDB_TO_EXIT} and
>>>     @code{GDB_TO_CLONE} options when single-stepping a thread past a
>>>     breakpoint, for the following reasons:
>>>
>>>     @itemize @bullet
>>>     @item
>>>     If the single-stepped thread exits (e.g., it executes a thread exit
>>>     system call), enabling @code{GDB_TO_EXIT} prevents @value{GDBN} from
>>>     waiting forever, not knowing that it should no longer expect a stop for
>>>     that same thread, and blocking other threads from progressing.
>>>
>>>     @item
>>>     If the single-stepped thread spawns a new clone child (i.e., it
>>>     executes a clone system call), enabling @code{GDB_TO_CLONE} halts the
>>>     cloned thread before it executes any instructions, and thus prevents
>>>     the following problematic situations:
>>>
>>>     @itemize @minus
>>>     @item
>>>     if the breakpoint is stepped-over in-line, the spawned thread would
>>>     incorrectly run free while the breakpoint being stepped over is not
>>>     inserted, and thus the cloned thread may potentially run past the
>>>     breakpoint without stopping for it;
>>>
>>>     @item
>>>     if displaced (out-of-line) stepping is used, the cloned thread starts
>>>     running at the out-of-line PC, leading to undefined behavior, usually
>>>     crashing or corrupting data.
>>>     @end itemize
>>>
>>>     @end itemize
>>>
>>
>> Thank you, that is perfect, I'll take it.
> 
> But please capitalize the text of each @item (I forgot to do that in
> the last two).

OK.

> 
> Thanks.
> 


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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 16:38             ` Pedro Alves
@ 2022-07-11 17:00               ` Eli Zaretskii
  2022-07-11 17:48                 ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-07-11 17:00 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 11 Jul 2022 17:38:48 +0100
> 
> >   @item set scheduler-locking @var{mode}
> >   @cindex scheduler locking mode
> >   @cindex lock scheduler
> >   Set the scheduler locking mode.  It applies to normal execution,
> >   record mode, and replay mode.  If it is @code{off}, then there is no
> >   locking and any thread may run at any time.  If @code{on}, then only
> >   the current thread may run when the inferior is resumed; even threads
> >   created by the resumed thread are held stopped.          ^^^^^^^^^^^^
> >   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > 
> > And now I wonder what happens when scheduler-locking is set to 'step':
> > are new threads created by the resumed thread held stopped as in the
> > ON case?
> > 
> 
> Yes, they are.  That's why I put the new sentence at the end and not connected to "on",
> and  said "is in effect", not "on".  I.e., when the scheduler is actually locked.

But that is too far: it comes after you tell how the replay mode
behaves, which is a separate issue.  So if you want to say it once for
both ON and STEP values, I suggest to say something like

  Both the @code{on} and @code{step} settings hold stopped any new
  threads created by the resumed thread.

before you describe how the rep[lay mode behaves.

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

* Re: [PATCH 25/25] Document remote clone events, and QThreadOptions packet
  2022-07-11 16:54         ` Pedro Alves
@ 2022-07-11 17:02           ` Eli Zaretskii
  0 siblings, 0 replies; 47+ messages in thread
From: Eli Zaretskii @ 2022-07-11 17:02 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 11 Jul 2022 17:54:10 +0100
> 
> The text for fork events uses "@var{thread-id}" too, in the
> "Refer to @ref{thread-id syntax} for the format of the @var{thread-id} field."
> sentence.  The new text was just copying that.
> 
> How about this to fix the preexisting issues?

LGTM, thanks.

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 17:00               ` Eli Zaretskii
@ 2022-07-11 17:48                 ` Pedro Alves
  2022-07-11 17:50                   ` Eli Zaretskii
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-07-11 17:48 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 2022-07-11 6:00 p.m., Eli Zaretskii wrote:
>> Cc: gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 11 Jul 2022 17:38:48 +0100
>>
>>>   @item set scheduler-locking @var{mode}
>>>   @cindex scheduler locking mode
>>>   @cindex lock scheduler
>>>   Set the scheduler locking mode.  It applies to normal execution,
>>>   record mode, and replay mode.  If it is @code{off}, then there is no
>>>   locking and any thread may run at any time.  If @code{on}, then only
>>>   the current thread may run when the inferior is resumed; even threads
>>>   created by the resumed thread are held stopped.          ^^^^^^^^^^^^
>>>   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>>
>>> And now I wonder what happens when scheduler-locking is set to 'step':
>>> are new threads created by the resumed thread held stopped as in the
>>> ON case?
>>>
>>
>> Yes, they are.  That's why I put the new sentence at the end and not connected to "on",
>> and  said "is in effect", not "on".  I.e., when the scheduler is actually locked.
> 
> But that is too far: it comes after you tell how the replay mode
> behaves, which is a separate issue.  So if you want to say it once for
> both ON and STEP values, I suggest to say something like
> 
>   Both the @code{on} and @code{step} settings hold stopped any new
>   threads created by the resumed thread.
> 
> before you describe how the rep[lay mode behaves.
> 

I don't see it as a separate issue.  The last sentence just before mine says:

 "The @code{replay} mode behaves like @code{off} in record mode and like
 @code{on} in replay mode."

I.e., the scheduler is locked in replay mode.  This is another case of
scheduler-locking being in effect.

This is exactly how GDB looks at things:

/* Returns true if scheduler locking applies.  STEP indicates whether
   we're about to do a step/next-like command to a thread.  */

static bool
schedlock_applies (struct thread_info *tp)
{
  return (scheduler_mode == schedlock_on
	  || (scheduler_mode == schedlock_step
	      && tp->control.stepping_command)
	  || (scheduler_mode == schedlock_replay
	      && target_record_will_replay (minus_one_ptid,
					    execution_direction)));
}

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 17:48                 ` Pedro Alves
@ 2022-07-11 17:50                   ` Eli Zaretskii
  2022-07-11 18:18                     ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-07-11 17:50 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 11 Jul 2022 18:48:19 +0100
> 
> > But that is too far: it comes after you tell how the replay mode
> > behaves, which is a separate issue.  So if you want to say it once for
> > both ON and STEP values, I suggest to say something like
> > 
> >   Both the @code{on} and @code{step} settings hold stopped any new
> >   threads created by the resumed thread.
> > 
> > before you describe how the rep[lay mode behaves.
> > 
> 
> I don't see it as a separate issue.  The last sentence just before mine says:
> 
>  "The @code{replay} mode behaves like @code{off} in record mode and like
>  @code{on} in replay mode."
> 
> I.e., the scheduler is locked in replay mode.  This is another case of
> scheduler-locking being in effect.

Fine, but is something wrong with the text I proposed?

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 17:50                   ` Eli Zaretskii
@ 2022-07-11 18:18                     ` Pedro Alves
  2022-07-11 18:29                       ` Eli Zaretskii
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-07-11 18:18 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 2022-07-11 6:50 p.m., Eli Zaretskii wrote:
>> Cc: gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 11 Jul 2022 18:48:19 +0100
>>
>>> But that is too far: it comes after you tell how the replay mode
>>> behaves, which is a separate issue.  So if you want to say it once for
>>> both ON and STEP values, I suggest to say something like
>>>
>>>   Both the @code{on} and @code{step} settings hold stopped any new
>>>   threads created by the resumed thread.
>>>
>>> before you describe how the rep[lay mode behaves.
>>>
>>
>> I don't see it as a separate issue.  The last sentence just before mine says:
>>
>>  "The @code{replay} mode behaves like @code{off} in record mode and like
>>  @code{on} in replay mode."
>>
>> I.e., the scheduler is locked in replay mode.  This is another case of
>> scheduler-locking being in effect.
> 
> Fine, but is something wrong with the text I proposed?
> 

Yes, you said to put it before the description of how the replay mode behaves,
and your text implies the change only affects "on" and "step", while it
affects "replay" as well.  As I said, the change affects all cases of
actually locking the scheduler, so it is not appropriate to single out just
some modes.

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 18:18                     ` Pedro Alves
@ 2022-07-11 18:29                       ` Eli Zaretskii
  2022-07-11 19:39                         ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-07-11 18:29 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 11 Jul 2022 19:18:56 +0100
> 
> > Fine, but is something wrong with the text I proposed?
> > 
> 
> Yes, you said to put it before the description of how the replay mode behaves,
> and your text implies the change only affects "on" and "step", while it
> affects "replay" as well.  As I said, the change affects all cases of
> actually locking the scheduler, so it is not appropriate to single out just
> some modes.

But "replay" behaves either as "on" or as "off", and the effect of
"on" on new threads is covered by the text I proposed.  So I don't see
the problem with it being before the description of "replay".

The advantage of my proposal is that it doesn't interrupt the
continuity of the description of how "on" and "step" affect the
threads created by the single thread being resumed.  The description
of "replay" is an interruption.

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 18:29                       ` Eli Zaretskii
@ 2022-07-11 19:39                         ` Pedro Alves
  2022-07-12 16:08                           ` Eli Zaretskii
  0 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-07-11 19:39 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 2022-07-11 7:29 p.m., Eli Zaretskii wrote:
>> Cc: gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 11 Jul 2022 19:18:56 +0100
>>
>>> Fine, but is something wrong with the text I proposed?
>>>
>>
>> Yes, you said to put it before the description of how the replay mode behaves,
>> and your text implies the change only affects "on" and "step", while it
>> affects "replay" as well.  As I said, the change affects all cases of
>> actually locking the scheduler, so it is not appropriate to single out just
>> some modes.
> 
> But "replay" behaves either as "on" or as "off", and the effect of
> "on" on new threads is covered by the text I proposed.  So I don't see
> the problem with it being before the description of "replay".
> 
> The advantage of my proposal is that it doesn't interrupt the
> continuity of the description of how "on" and "step" affect the
> threads created by the single thread being resumed.  The description
> of "replay" is an interruption.
> 

I can see that, but putting it just before replay is described is just one
small sentence before, i.e., before "The @code{replay} mode behaves like @code{off} in
record mode and like @code{on} in replay mode." one, and I have trouble seeing
how that doesn't break the flow in the same way.

Also, what you suggested:

 "Both the @code{on} and @code{step} settings hold stopped any new
 threads created by the resumed thread."

has another problem -- it is incorrect for "step", for it implies that
with "set scheduler-locking step", new threads are always held stopped,
when they are only held stopped if you step; they won't be held stopped
if you "continue" instead, for example.

So if we take this "behave like on" approach, then it seems better to move the
new sentence even earlier, right where "on" is described, and then tweak
the "step" mode to say "behaves like on" too.

And then, the more I stare at this whole modes description, with 4 different modes
already (and there was a proposal to add one more some time ago), the more it seems to me
like it would be better to redo it using a table.

While at it, I think the "replay" mode's description is a bit backwards (should
describe replay mode first instead of record mode), and it also fails to mention
how the mode behaves during normal execution.

Also, we miss saying what is the default mode...

So all in all, how about the version below?

If OK, I should perhaps put the manual (not NEWS) changes in immediately without the
"New threads created by the resumed thread are also held stopped." sentence,
as a pure clarification/rewording patch.  I'm going to post a v2 of the series
soon, and in that version, the manual change would then only be one added sentence
to the "on" mode, about the new threads.

diff --git a/gdb/NEWS b/gdb/NEWS
index 742f4fe47fb..f0f6990dc59 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,13 @@
 
 *** Changes since GDB 12
 
+* Scheduler-locking and new threads
+
+  When scheduler-locking is in effect, only the current thread may run
+  when the inferior is resumed.  However, previously, new threads
+  created by the resumed thread would still be able to run free.  Now,
+  they are held stopped.
+
 * "info breakpoints" now displays enabled breakpoint locations of
   disabled breakpoints as in the "y-" state.  For example:
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 562e4c1f628..77524095480 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -6939,19 +6939,34 @@ locking the OS scheduler to allow only a single thread to run.
 @cindex scheduler locking mode
 @cindex lock scheduler
 Set the scheduler locking mode.  It applies to normal execution,
-record mode, and replay mode.  If it is @code{off}, then there is no
-locking and any thread may run at any time.  If @code{on}, then only
-the current thread may run when the inferior is resumed.  The
-@code{step} mode optimizes for single-stepping; it prevents other
-threads from preempting the current thread while you are stepping, so
-that the focus of debugging does not change unexpectedly.  Other
-threads never get a chance to run when you step, and they are
-completely free to run when you use commands like @samp{continue},
-@samp{until}, or @samp{finish}.  However, unless another thread hits a
-breakpoint during its timeslice, @value{GDBN} does not change the
-current thread away from the thread that you are debugging.  The
-@code{replay} mode behaves like @code{off} in record mode and like
-@code{on} in replay mode.
+record mode, and replay mode.  @var{mode} can be one of
+the following:
+
+@table @code
+@item off
+There is no locking and any thread may run at any time.
+
+@item on
+Only the current thread may run when the inferior is resumed.  New
+threads created by the resumed thread are also held stopped.
+
+@item step
+Behaves like @code{on} when stepping, and @code{off} otherwise.
+Threads other than the current never get a chance to run when you
+step, and they are completely free to run when you use commands like
+@samp{continue}, @samp{until}, or @samp{finish}.
+
+This mode optimizes for single-stepping; it prevents other threads
+from preempting the current thread while you are stepping, so that the
+focus of debugging does not change unexpectedly.  However, unless
+another thread hits a breakpoint during its timeslice, @value{GDBN}
+does not change the current thread away from the thread that you are
+debugging.
+
+@item replay
+Behaves like @code{on} in replay mode, and @code{off} in either record
+mode or during normal execution.  This is the default mode.
+@end table
 
 @item show scheduler-locking
 Display the current scheduler locking mode.

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-11 19:39                         ` Pedro Alves
@ 2022-07-12 16:08                           ` Eli Zaretskii
  2022-07-12 17:14                             ` Pedro Alves
  0 siblings, 1 reply; 47+ messages in thread
From: Eli Zaretskii @ 2022-07-12 16:08 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 11 Jul 2022 20:39:31 +0100
> 
> So all in all, how about the version below?

It's fine, thanks.

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

* Re: [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect
  2022-07-12 16:08                           ` Eli Zaretskii
@ 2022-07-12 17:14                             ` Pedro Alves
  0 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-07-12 17:14 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 2022-07-12 5:08 p.m., Eli Zaretskii wrote:
>> Cc: gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 11 Jul 2022 20:39:31 +0100
>>
>> So all in all, how about the version below?
> 
> It's fine, thanks.
> 

Great, thanks.  As mentioned before I'd do, I removed the new sentence about new threads,
and pushed the resulting patch as a pure cleanup/improvement, as below.

From d21d919bc1d75f89140218f0d5702d0afff41209 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 30 Nov 2021 19:52:11 +0000
Subject: [PATCH] Improve "set scheduler-locking" documentation

This improves the "set scheduler-locking" documentation in the GDB
manual:

 - Use a table to describe the four available modes.

 - Describe "step" in terms of "on" and "off".

 - Tweak the "replay" mode's description to describe replay first
   instead of recording, and also mention how the mode behaves during
   normal execution.

 - Say what is the default mode.

Change-Id: Ie12140138b37534b7fc1d904da34f0f174aa11ce
---
 gdb/doc/gdb.texinfo | 40 +++++++++++++++++++++++++++-------------
 1 file changed, 27 insertions(+), 13 deletions(-)

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 562e4c1f628..9a8b010476a 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -6939,19 +6939,33 @@ locking the OS scheduler to allow only a single thread to run.
 @cindex scheduler locking mode
 @cindex lock scheduler
 Set the scheduler locking mode.  It applies to normal execution,
-record mode, and replay mode.  If it is @code{off}, then there is no
-locking and any thread may run at any time.  If @code{on}, then only
-the current thread may run when the inferior is resumed.  The
-@code{step} mode optimizes for single-stepping; it prevents other
-threads from preempting the current thread while you are stepping, so
-that the focus of debugging does not change unexpectedly.  Other
-threads never get a chance to run when you step, and they are
-completely free to run when you use commands like @samp{continue},
-@samp{until}, or @samp{finish}.  However, unless another thread hits a
-breakpoint during its timeslice, @value{GDBN} does not change the
-current thread away from the thread that you are debugging.  The
-@code{replay} mode behaves like @code{off} in record mode and like
-@code{on} in replay mode.
+record mode, and replay mode.  @var{mode} can be one of
+the following:
+
+@table @code
+@item off
+There is no locking and any thread may run at any time.
+
+@item on
+Only the current thread may run when the inferior is resumed.
+
+@item step
+Behaves like @code{on} when stepping, and @code{off} otherwise.
+Threads other than the current never get a chance to run when you
+step, and they are completely free to run when you use commands like
+@samp{continue}, @samp{until}, or @samp{finish}.
+
+This mode optimizes for single-stepping; it prevents other threads
+from preempting the current thread while you are stepping, so that the
+focus of debugging does not change unexpectedly.  However, unless
+another thread hits a breakpoint during its timeslice, @value{GDBN}
+does not change the current thread away from the thread that you are
+debugging.
+
+@item replay
+Behaves like @code{on} in replay mode, and @code{off} in either record
+mode or during normal execution.  This is the default mode.
+@end table
 
 @item show scheduler-locking
 Display the current scheduler locking mode.

base-commit: 3da5576c911a0f3fc608471f1486dc6db11052ef
-- 
2.36.0


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

* Re: [PATCH 20/25] Tighten gdb.threads/no-unwaited-for-left.exp regexps
  2022-06-20 22:54 ` [PATCH 20/25] Tighten gdb.threads/no-unwaited-for-left.exp regexps Pedro Alves
@ 2022-07-13 21:32   ` Pedro Alves
  0 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-07-13 21:32 UTC (permalink / raw)
  To: gdb-patches

This one is I think pretty much unobjectionable, so I went ahead and merged it, to
avoid reposting it in a v2 of the series.  (I replaced the "following patch" wording in
the commit log.)

Pedro Alves

On 2022-06-20 11:54 p.m., Pedro Alves wrote:
> A WIP version of the following patch resulted in a bug that went
> unnoticed by the testuite, like so:
> 
>  (gdb) PASS: gdb.threads/no-unwaited-for-left.exp: enable scheduler-locking, for main thread
>  continue
>  Continuing.
>  [New Thread 1251861.1251861]
>  No unwaited-for children left.
>  (gdb) PASS: gdb.threads/no-unwaited-for-left.exp: continue stops when the main thread exits
>  info threads
>    Id   Target Id                                Frame
>    3    Thread 1251861.1251863 "no-unwaited-for" __pthread_clockjoin_ex (threadid=140737351558976, thread_return=0x0, clockid=<optimized out>, abstime=<optimized out>, block=<optimized out>) at pthread_join_common.c:145
>    4    Thread 1251861.1251861 "no-unwaited-for" <unavailable> in ?? ()
> 
>  The current thread <Thread ID 1> has terminated.  See `help thread'.
>  (gdb) PASS: gdb.threads/no-unwaited-for-left.exp: only thread 3 left, main thread terminated
> 
> Somehow, above, GDB re-added the zombie leader back before printing
> "No unwaited-for children left.".  The "only thread 3 left, main
> thread terminated" test should have caught this, but didn't.  That is
> because the test's regexp has a ".*" after the part that matches
> thread 3.  This commit tightens that regexp to catch such a bug.  It
> also tightens the "only main thread left, thread 2 terminated" test's
> regexp in the same way.
> 
> Change-Id: I8744f327a0aa0e2669d1ddda88247e99b91cefff
> ---
>  gdb/testsuite/gdb.threads/no-unwaited-for-left.exp | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp b/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp
> index cfeb495594d..6773aa46d71 100644
> --- a/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp
> +++ b/gdb/testsuite/gdb.threads/no-unwaited-for-left.exp
> @@ -42,7 +42,7 @@ gdb_test "continue" \
>      "continue stops when thread 2 exits"
>  
>  gdb_test "info threads" \
> -	 "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *1 *Thread \[^\r\n\]* \[^\r\n\]*.*The current thread <Thread ID 2> has terminated.*" \
> +	 "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *1 *Thread \[^\r\n\]* \[^\r\n\]*\[\r\n\]*The current thread <Thread ID 2> has terminated.*" \
>  	 "only main thread left, thread 2 terminated"
>  
>  # Select the main thread, let the third thread start, and stop at the
> @@ -63,7 +63,7 @@ gdb_test "continue" \
>      "continue stops when the main thread exits"
>  
>  gdb_test "info threads" \
> -	 "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *3 *Thread \[^\r\n\]* \[^\r\n\]*.*The current thread <Thread ID 1> has terminated.*" \
> +	 "\r\n\[ \t\]*Id\[ \t\]+Target\[ \t\]+Id\[ \t\]+Frame\[ \t\]*\r\n *3 *Thread \[^\r\n\]* \[^\r\n\]*\[\r\n\]*The current thread <Thread ID 1> has terminated.*" \
>  	 "only thread 3 left, main thread terminated"
>  
>  # Make sure thread apply all works when we have exited threads in the
> 


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

* Re: [PATCH 01/25] Don't use pthread_mutex_t in gdb.base/step-over-clone.c
  2022-06-20 22:53 ` [PATCH 01/25] Don't use pthread_mutex_t in gdb.base/step-over-clone.c Pedro Alves
@ 2022-07-13 21:35   ` Pedro Alves
  0 siblings, 0 replies; 47+ messages in thread
From: Pedro Alves @ 2022-07-13 21:35 UTC (permalink / raw)
  To: gdb-patches

I'm dropping this patch from a v2 of this series, because I've since remembered
that current glibc is eliminating separte libpthread, merging it into the main library, and
gdb will load libthread_db.so even with non-threaded programs, so that printing errno
always works...  This all means that with new enough glibc, I think we'd get back the
warnings...

Pedro Alves

On 2022-06-20 11:53 p.m., Pedro Alves wrote:
> I noticed this in gdb.log after running gdb.base/step-over-clone.exp:
> 
>  ...
>  gdbserver: PID mismatch!  Expected 1790818, got 1790817
>  gdbserver: Cannot find thread after clone.
>  gdbserver: PID mismatch!  Expected 1790819, got 1790817
>  gdbserver: Cannot find thread after clone.
>  gdbserver: PID mismatch!  Expected 1790820, got 1790817
>  gdbserver: Cannot find thread after clone.
>  ...
> 
> Those "PID mismatch" come from gdbserver/thread_db.c.  The problem is
> that the testcase program is testing raw clone, which bypasses
> libpthread entirely and leaves libthread_db confused.  The testcase is
> linking with pthreads because it wants to use pthread_mutex_t for
> synchronization between the clones.  Mixing pthreads and raw clone is
> just something we shouldn't do, however.
> 
> My first thought was to fix this by using an atomic decrement
> (__atomic_fetch_sub) instead of a mutex, for synchronization.
> However, on some archs, that may require linking with -latomic, which
> can itself pull in libpthread.
> 
> My next idea, is to make each thread write to its own "I'm ready"
> variable, such that we can't actually have read-modify-write races.
> This is what this patch does.
> 
> Change-Id: Id418978ac86bfa6d51d0af1e1625a86cdd039a20
> ---
>  gdb/testsuite/gdb.base/step-over-clone.c     | 69 +++++++++++++-------
>  gdb/testsuite/gdb.base/step-over-syscall.exp |  7 +-
>  2 files changed, 45 insertions(+), 31 deletions(-)
> 
> diff --git a/gdb/testsuite/gdb.base/step-over-clone.c b/gdb/testsuite/gdb.base/step-over-clone.c
> index c0f67af188b..8a56b492e5e 100644
> --- a/gdb/testsuite/gdb.base/step-over-clone.c
> +++ b/gdb/testsuite/gdb.base/step-over-clone.c
> @@ -19,7 +19,7 @@
>  #include <stdlib.h>
>  #include <unistd.h>
>  #include <sched.h>
> -#include <pthread.h>
> +#include <signal.h>
>  
>  static void
>  marker ()
> @@ -27,30 +27,55 @@ marker ()
>  
>  #define STACK_SIZE 0x1000
>  
> -/* These are used to signal that the threads have started correctly.  The
> -   GLOBAL_THREAD_COUNT is set to the number of threads in main, then
> -   decremented (under a lock) in each new thread.  */
> -pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;
> -int global_thread_count = 0;
> +#define NUM_THREADS 6
> +
> +/* This is used to signal that the threads have started correctly.  We
> +   can't use a single global updated by all thread guarded by a
> +   pthread mutex, or anything pthread related for the matter, since we
> +   are using raw clone.  A single global updated with atomics
> +   (__atomic_fetch* etc.) instead of a pthread mutex would sound
> +   appealing, but we avoid that too because for some architectures,
> +   we'd have to link with -latomic, which itself links with
> +   -lpthread...  So instead have one array with one element per
> +   thread, and each thread only ever writes to its own array element.
> +   We make the array have sig_atomic_t elements so that the elements
> +   are portably naturally aligned and free from data races on all
> +   archs, when different threads write to different elements.  In
> +   practice, "int" would work too, as accesses to int are pretty much
> +   garanteed to be atomic on all Linux systems, but sig_atomic_t is
> +   explicit.  */
> +volatile sig_atomic_t thread_started[NUM_THREADS];
>  
>  static int
> -clone_fn (void *unused)
> +clone_fn (void *started)
>  {
>    /* Signal that this thread has started correctly.  */
> -  if (pthread_mutex_lock (&global_lock) != 0)
> -    abort ();
> -  global_thread_count--;
> -  if (pthread_mutex_unlock (&global_lock) != 0)
> -    abort ();
> +  *(volatile sig_atomic_t *) started = 1;
>  
>    return 0;
>  }
>  
> +/* Return true if all threads have started.  */
> +
> +static int
> +all_threads_started (void)
> +{
> +  int i;
> +
> +  /* Force full memory barrier so that caches are flushed and
> +     THREAD_STARTED is refetched.  */
> +  __sync_synchronize ();
> +  for (i = 0; i < NUM_THREADS; i++)
> +    if (thread_started[i] == 0)
> +      return 0;
> +  return 1;
> +}
> +
>  int
>  main (void)
>  {
>    int i, pid;
> -  unsigned char *stack[6];
> +  unsigned char *stack[NUM_THREADS];
>  
>    /* Due to bug gdb/19675 the cloned thread _might_ try to reenter main
>       (this depends on where the displaced instruction is placed for
> @@ -62,18 +87,16 @@ main (void)
>    else
>      abort ();
>  
> -  for (i = 0; i < (sizeof (stack) / sizeof (stack[0])); i++)
> +  for (i = 0; i < NUM_THREADS; i++)
>      stack[i] = malloc (STACK_SIZE);
>  
> -  global_thread_count = (sizeof (stack) / sizeof (stack[0]));
> -
> -  for (i = 0; i < (sizeof (stack) / sizeof (stack[0])); i++)
> +  for (i = 0; i < NUM_THREADS; i++)
>      {
>        pid = clone (clone_fn, stack[i] + STACK_SIZE, CLONE_FILES | CLONE_VM,
> -		   NULL);
> +		   (void *) &thread_started[i]);
>      }
>  
> -  for (i = 0; i < (sizeof (stack) / sizeof (stack[0])); i++)
> +  for (i = 0; i < NUM_THREADS; i++)
>      free (stack[i]);
>  
>    /* Set an alarm so we don't end up stuck waiting for threads that might
> @@ -81,12 +104,8 @@ main (void)
>    alarm (120);
>  
>    /* Now wait for all the threads to start up.  */
> -  while (global_thread_count != 0)
> -    {
> -      /* Force memory barrier so GLOBAL_THREAD_COUNT will be refetched.  */
> -      asm volatile ("" ::: "memory");
> -      sleep (1);
> -    }
> +  while (!all_threads_started ())
> +    sleep (1);
>  
>    /* Call marker, this is what GDB is waiting for.  */
>    marker ();
> diff --git a/gdb/testsuite/gdb.base/step-over-syscall.exp b/gdb/testsuite/gdb.base/step-over-syscall.exp
> index 788f6e3f5d0..e87d391cd5f 100644
> --- a/gdb/testsuite/gdb.base/step-over-syscall.exp
> +++ b/gdb/testsuite/gdb.base/step-over-syscall.exp
> @@ -241,12 +241,7 @@ proc step_over_syscall { syscall } {
>  
>  	set testfile "step-over-$syscall"
>  
> -	set options [list debug]
> -	if { $syscall == "clone" } {
> -	    lappend options "pthreads"
> -	}
> -
> -	if [build_executable ${testfile}.exp ${testfile} ${testfile}.c $options] {
> +	if [build_executable ${testfile}.exp ${testfile} ${testfile}.c {debug}] {
>  	    untested "failed to compile"
>  	    return -1
>  	}
> 


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

end of thread, other threads:[~2022-07-13 21:35 UTC | newest]

Thread overview: 47+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-20 22:53 [PATCH 00/25] Step over thread clone and thread exit Pedro Alves
2022-06-20 22:53 ` [PATCH 01/25] Don't use pthread_mutex_t in gdb.base/step-over-clone.c Pedro Alves
2022-07-13 21:35   ` Pedro Alves
2022-06-20 22:53 ` [PATCH 02/25] displaced step: pass down target_waitstatus instead of gdb_signal Pedro Alves
2022-06-20 22:53 ` [PATCH 03/25] linux-nat: introduce pending_status_str Pedro Alves
2022-06-20 22:53 ` [PATCH 04/25] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED Pedro Alves
2022-06-20 22:53 ` [PATCH 05/25] Support clone events in the remote protocol Pedro Alves
2022-06-20 22:54 ` [PATCH 06/25] Thread options & clone events (core + remote) Pedro Alves
2022-06-20 22:54 ` [PATCH 07/25] Thread options & clone events (native Linux) Pedro Alves
2022-06-20 22:54 ` [PATCH 08/25] Thread options & clone events (Linux GDBserver) Pedro Alves
2022-06-20 22:54 ` [PATCH 09/25] gdbserver: Hide and don't detach pending clone children Pedro Alves
2022-06-20 22:54 ` [PATCH 10/25] Remove gdb/19675 kfails (displaced stepping + clone) Pedro Alves
2022-06-20 22:54 ` [PATCH 11/25] Add test for stepping over clone syscall Pedro Alves
2022-06-20 22:54 ` [PATCH 12/25] all-stop/synchronous RSP support thread-exit events Pedro Alves
2022-06-20 22:54 ` [PATCH 13/25] Introduce GDB_TO_EXIT thread option, fix step-over-thread-exit Pedro Alves
2022-06-20 22:54 ` [PATCH 14/25] Implement GDB_TO_EXIT support for Linux GDBserver Pedro Alves
2022-06-20 22:54 ` [PATCH 15/25] Implement GDB_TO_EXIT support for native Linux Pedro Alves
2022-06-20 22:54 ` [PATCH 16/25] gdb: clear step over information on thread exit (PR gdb/27338) Pedro Alves
2022-06-20 22:54 ` [PATCH 17/25] stop_all_threads: (re-)enable async before waiting for stops Pedro Alves
2022-06-20 22:54 ` [PATCH 18/25] gdbserver: Queue no-resumed event after thread exit Pedro Alves
2022-06-20 22:54 ` [PATCH 19/25] Don't resume new threads if scheduler-locking is in effect Pedro Alves
2022-06-21 11:07   ` Eli Zaretskii
2022-07-11 14:20     ` Pedro Alves
2022-07-11 15:44       ` Eli Zaretskii
2022-07-11 16:09         ` Pedro Alves
2022-07-11 16:30           ` Eli Zaretskii
2022-07-11 16:38             ` Pedro Alves
2022-07-11 17:00               ` Eli Zaretskii
2022-07-11 17:48                 ` Pedro Alves
2022-07-11 17:50                   ` Eli Zaretskii
2022-07-11 18:18                     ` Pedro Alves
2022-07-11 18:29                       ` Eli Zaretskii
2022-07-11 19:39                         ` Pedro Alves
2022-07-12 16:08                           ` Eli Zaretskii
2022-07-12 17:14                             ` Pedro Alves
2022-06-20 22:54 ` [PATCH 20/25] Tighten gdb.threads/no-unwaited-for-left.exp regexps Pedro Alves
2022-07-13 21:32   ` Pedro Alves
2022-06-20 22:54 ` [PATCH 21/25] Report thread exit event for leader if reporting thread exit events Pedro Alves
2022-06-20 22:54 ` [PATCH 22/25] Ignore failure to read PC when resuming Pedro Alves
2022-06-20 22:54 ` [PATCH 23/25] gdb/testsuite/lib/my-syscalls.S: Refactor new SYSCALL macro Pedro Alves
2022-06-20 22:54 ` [PATCH 24/25] Testcases for stepping over thread exit syscall (PR gdb/27338) Pedro Alves
2022-06-20 22:54 ` [PATCH 25/25] Document remote clone events, and QThreadOptions packet Pedro Alves
2022-06-21 12:07   ` Eli Zaretskii
2022-07-11 15:19     ` Pedro Alves
2022-07-11 16:09       ` Eli Zaretskii
2022-07-11 16:54         ` Pedro Alves
2022-07-11 17:02           ` Eli Zaretskii

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).