public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 00/31] Step over thread clone and thread exit
@ 2022-12-12 20:30 Pedro Alves
  2022-12-12 20:30 ` [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal Pedro Alves
                   ` (32 more replies)
  0 siblings, 33 replies; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

Here's v3 of the series I previously posted here:

  https://inbox.sourceware.org/gdb-patches/20220713222433.374898-1-pedro@palves.net/

New in v3:

  - Addressed Simon's comments throughout.

  - Addressed Tom de Vries' comments: now builds with
    --enable-targets=all, and the testcases should work with no glibc
    debug info.

  - There are a few new patches:

      - [PATCH 07/31] enum_flags to_string

      Already approved by Simon.  The patches that introduce the new
      GDB_THREAD_OPTION_XXX option flags make use of this now to
      pretty-print the flags.

      - [PATCH 16/31] Move deleting thread on TARGET_WAITKIND_THREAD_EXITED to core

      This was half done in another patch previously.  In v3, it's
      been moved to a separate preparatory patch, and in all-stop, we
      now delete threads when pending-exits just before we report a
      stop to the user.

      - [PATCH 31/31] Cancel execution command on thread exit, when stepping, nexting, etc.

      The testcase was augmented to better cover all scenarios.

  - This series now applies on top of this other series I posted last
    week:

    [PATCH 0/6] Eliminate infrun_thread_thread_exit observer
    https://inbox.sourceware.org/gdb-patches/20221203211338.2264994-1-pedro@palves.net/T/#t

For your convenience, I've pushed this to the
users/palves/step-over-thread-exit-v3 branch.

Here's the series description, updated for v3:

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 involves 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 alluded 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.

- Centralizes "[Thread ...exited]" notifications in core code.

- Cancels next/step/until/etc. commands on thread exit event, like so:

     (gdb) n
     [Thread 0x7ffff7d89700 (LWP 3961883) exited]
     Command aborted, thread exited.
     (gdb)

There are documentation changes in the following patches:

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

... and they have both already been approved by Eli, in v2.  (They
haven't changed in v3).

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

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

Pedro Alves (29):
  displaced step: pass down target_waitstatus instead of gdb_signal
  linux-nat: introduce pending_status_str
  gdb/linux: Delete all other LWPs immediately on ptrace exec event
  Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED
  Support clone events in the remote protocol
  Avoid duplicate QThreadEvents packets
  enum_flags to_string
  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
  gdbserver/linux-low.cc: Ignore event_ptid if TARGET_WAITKIND_IGNORE
  Move deleting thread on TARGET_WAITKIND_THREAD_EXITED to core
  Introduce GDB_THREAD_OPTION_EXIT thread option, fix
    step-over-thread-exit
  Implement GDB_THREAD_OPTION_EXIT support for Linux GDBserver
  Implement GDB_THREAD_OPTION_EXIT support for native Linux
  gdb: clear step over information on thread exit (PR gdb/27338)
  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
  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
  inferior::clear_thread_list always silent
  Centralize "[Thread ...exited]" notifications
  Cancel execution command on thread exit, when stepping, nexting, etc.

Simon Marchi (1):
  Testcases for stepping over thread exit syscall (PR gdb/27338)

 gdb/NEWS                                      |  27 +
 gdb/annotate.c                                |   4 +-
 gdb/breakpoint.c                              |   4 +-
 gdb/displaced-stepping.c                      |  18 +-
 gdb/displaced-stepping.h                      |   2 +-
 gdb/doc/gdb.texinfo                           | 131 +++-
 gdb/fbsd-nat.c                                |   3 -
 gdb/gdbarch-components.py                     |   6 +-
 gdb/gdbarch-gen.h                             |  10 +-
 gdb/gdbarch.c                                 |   4 +-
 gdb/gdbthread.h                               |  38 +-
 gdb/inferior.c                                |  12 +-
 gdb/inferior.h                                |   7 +-
 gdb/infrun.c                                  | 615 +++++++++++++++---
 gdb/linux-nat.c                               | 362 ++++++-----
 gdb/linux-nat.h                               |   4 +
 gdb/linux-tdep.c                              |   5 +-
 gdb/linux-tdep.h                              |   2 +-
 gdb/mi/mi-interp.c                            |   8 +-
 gdb/netbsd-nat.c                              |   5 -
 gdb/observable.h                              |  11 +-
 gdb/procfs.c                                  |   6 -
 gdb/python/py-inferior.c                      |   4 +-
 gdb/remote.c                                  | 264 +++++++-
 gdb/rs6000-tdep.c                             |   4 +-
 gdb/target-debug.h                            |   2 +
 gdb/target-delegates.c                        |  52 ++
 gdb/target.c                                  |  16 +
 gdb/target.h                                  |  10 +
 gdb/target/target.c                           |  12 +
 gdb/target/target.h                           |  20 +
 gdb/target/waitstatus.c                       |   1 +
 gdb/target/waitstatus.h                       |  31 +-
 gdb/testsuite/gdb.base/step-over-syscall.exp  |  44 +-
 .../gdb.threads/schedlock-new-thread.c        |  54 ++
 .../gdb.threads/schedlock-new-thread.exp      |  67 ++
 gdb/testsuite/gdb.threads/step-over-exec.exp  |   6 +
 ...-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     | 151 +++++
 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 +
 gdb/thread.c                                  |  71 +-
 gdb/unittests/enum-flags-selftests.c          |  69 +-
 gdb/windows-nat.c                             |  16 +-
 gdbserver/gdbthread.h                         |   3 +
 gdbserver/linux-low.cc                        | 399 +++++++-----
 gdbserver/linux-low.h                         |  56 +-
 gdbserver/remote-utils.cc                     |  26 +-
 gdbserver/server.cc                           | 158 ++++-
 gdbserver/target.cc                           |  15 +-
 gdbserver/target.h                            |  27 +-
 gdbsupport/enum-flags.h                       |  66 ++
 56 files changed, 3043 insertions(+), 624 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: fb699bafb5f23c2fd43d7f20495171b16903b20f
prerequisite-patch-id: 75b36824a7ee067b57a8d8db6016cb57e4d7f620
prerequisite-patch-id: d5bf8b612deed2abf58a48c553178e080347f34d
prerequisite-patch-id: 3f00d1a451bed27d1b1c27174e15eb604d109a44
prerequisite-patch-id: 5c458d627f349446e40114cb5424b42af08c0824
prerequisite-patch-id: d81a20c336a23c79dafd4da3d780033052b3ac28
prerequisite-patch-id: 8cf0c661a601ff3eacfcd26f11e26bcd11cc867d
-- 
2.36.0


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

* [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-02-03 10:44   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 02/31] linux-nat: introduce pending_status_str Pedro Alves
                   ` (31 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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 +-
 gdb/rs6000-tdep.c         |  4 ++--
 9 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
index 7dfd63d8716..7b5d327008d 100644
--- a/gdb/displaced-stepping.c
+++ b/gdb/displaced-stepping.c
@@ -192,10 +192,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 ())
@@ -210,7 +211,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 ());
 
@@ -256,7 +257,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 e7230949aad..5d60f7677f0 100644
--- a/gdb/gdbarch-components.py
+++ b/gdb/gdbarch-components.py
@@ -1829,7 +1829,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 a663316df16..5c9390ea6b3 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1080,8 +1080,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 ddb8dec1c72..559b1dea0d9 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -4097,13 +4097,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 b24cc6d932d..0590310ffac 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1894,7 +1894,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;
 
@@ -1916,7 +1917,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
@@ -5068,7 +5069,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.  */
@@ -5083,7 +5084,6 @@ handle_one (const wait_one_event &event)
 	}
       else
 	{
-	  enum gdb_signal sig;
 	  struct regcache *regcache;
 
 	  infrun_debug_printf
@@ -5094,10 +5094,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.  */
@@ -5699,7 +5696,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 ();
@@ -6064,7 +6061,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 c30d9fb13f8..8a1d701d7c9 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -2621,13 +2621,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 95cc29c828c..cba67351574 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.  */
 
diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
index cbd84514795..dc0f78ed9ab 100644
--- a/gdb/rs6000-tdep.c
+++ b/gdb/rs6000-tdep.c
@@ -1088,13 +1088,13 @@ ppc_displaced_step_prepare  (gdbarch *arch, thread_info *thread,
 
 static displaced_step_finish_status
 ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
-			   gdb_signal sig)
+			   const target_waitstatus &status)
 {
   ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
 
   gdb_assert (per_inferior->disp_step_buf.has_value ());
 
-  return per_inferior->disp_step_buf->finish (arch, thread, sig);
+  return per_inferior->disp_step_buf->finish (arch, thread, status);
 }
 
 /* Implementation of gdbarch_displaced_step_restore_all_in_ptid.  */
-- 
2.36.0


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

* [PATCH 02/31] linux-nat: introduce pending_status_str
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
  2022-12-12 20:30 ` [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-02-03 12:00   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event Pedro Alves
                   ` (30 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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 | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 17e5dce08c3..9b78fd1f8e8 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -256,6 +256,19 @@ 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)
+{
+  gdb_assert (lwp_status_pending_p (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 +1660,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 +3150,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] 100+ messages in thread

* [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
  2022-12-12 20:30 ` [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal Pedro Alves
  2022-12-12 20:30 ` [PATCH 02/31] linux-nat: introduce pending_status_str Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-03-21 14:50   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 04/31] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED Pedro Alves
                   ` (29 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

I noticed that after a following patch ("Step over clone syscall w/
breakpoint, TARGET_WAITKIND_THREAD_CLONED"), the
gdb.threads/step-over-exec.exp was passing cleanly, but still, we'd
end up with four new unexpected GDB core dumps:

		 === gdb Summary ===

 # of unexpected core files      4
 # of expected passes            48

That said patch is making the pre-existing
gdb.threads/step-over-exec.exp testcase (almost silently) expose a
latent problem in gdb/linux-nat.c, resulting in a GDB crash when:

 #1 - a non-leader thread execs
 #2 - the post-exec program stops somewhere
 #3 - you kill the inferior

Instead of #3 directly, the testcase just returns, which ends up in
gdb_exit, tearing down GDB, which kills the inferior, and is thus
equivalent to #3 above.

Vis:

 $ gdb --args ./gdb /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true
 ...
 (top-gdb) r
 ...
 (gdb) b main
 ...
 (gdb) r
 ...
 Breakpoint 1, main (argc=1, argv=0x7fffffffdb88) at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec.c:69
 69        argv0 = argv[0];
 (gdb) c
 Continuing.
 [New Thread 0x7ffff7d89700 (LWP 2506975)]
 Other going in exec.
 Exec-ing /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
 process 2506769 is executing new program: /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd

 Thread 1 "step-over-exec-" hit Breakpoint 1, main () at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec-execd.c:28
 28        foo ();
 (gdb) k
 ...
 Thread 1 "gdb" received signal SIGSEGV, Segmentation fault.
 0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
 393         return m_suspend.waitstatus_pending_p;
 (top-gdb) bt
 #0  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
 #1  0x0000555555a884d1 in get_pending_child_status (lp=0x5555579b8230, ws=0x7fffffffd130) at ../../src/gdb/linux-nat.c:1345
 #2  0x0000555555a8e5e6 in kill_unfollowed_child_callback (lp=0x5555579b8230) at ../../src/gdb/linux-nat.c:3564
 #3  0x0000555555a92a26 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (this=0x0, ecall=..., args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:284
 #4  0x0000555555a92a51 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:278
 #5  0x0000555555a91f84 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffd210, args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:247
 #6  0x0000555555a87072 in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:864
 #7  0x0000555555a8e732 in linux_nat_target::kill (this=0x55555653af40 <the_amd64_linux_nat_target>) at ../../src/gdb/linux-nat.c:3590
 #8  0x0000555555cfdc11 in target_kill () at ../../src/gdb/target.c:911
 ...

The root of the problem is that when a non-leader LWP execs, it just
changes its tid to the tgid, replacing the pre-exec leader thread,
becoming the new leader.  There's no thread exit event for the execing
thread.  It's as if the old pre-exec LWP vanishes without trace.  The
ptrace man page says:

"PTRACE_O_TRACEEXEC (since Linux 2.5.46)
	Stop the tracee at the next execve(2).  A waitpid(2) by the
	tracer will return a status value such that

	  status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))

	If the execing thread is not a thread group leader, the thread
	ID is reset to thread group leader's ID before this stop.
	Since Linux 3.0, the former thread ID can be retrieved with
	PTRACE_GETEVENTMSG."

When the core of GDB processes an exec events, it deletes all the
threads of the inferior.  But, that is too late -- deleting the thread
does not delete the corresponding LWP, so we end leaving the pre-exec
non-leader LWP stale in the LWP list.  That's what leads to the crash
above -- linux_nat_target::kill iterates over all LWPs, and after the
patch in question, that code will look for the corresponding
thread_info for each LWP.  For the pre-exec non-leader LWP still
listed, won't find one.

This patch fixes it, by deleting the pre-exec non-leader LWP (and
thread) from the LWP/thread lists as soon as we get an exec event out
of ptrace.

GDBserver does not need an equivalent fix, because it is already doing
this, as side effect of mourning the pre-exec process, in
gdbserver/linux-low.cc:

  else if (event == PTRACE_EVENT_EXEC && cs.report_exec_events)
    {
...
      /* Delete the execing process and all its threads.  */
      mourn (proc);
      switch_to_thread (nullptr);

Change-Id: I21ec18072c7750f3a972160ae6b9e46590376643
---
 gdb/linux-nat.c                              | 15 +++++++++++++++
 gdb/testsuite/gdb.threads/step-over-exec.exp |  6 ++++++
 2 files changed, 21 insertions(+)

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 9b78fd1f8e8..5ee3227f1b9 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1986,6 +1986,21 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
 	 thread execs, it changes its tid to the tgid, and the old
 	 tgid thread might have not been resumed.  */
       lp->resumed = 1;
+
+      /* All other LWPs are gone now.  We'll have received a thread
+	 exit notification for all threads other the execing one.
+	 That one, if it wasn't the leader, just silently changes its
+	 tid to the tgid, and the previous leader vanishes.  Since
+	 Linux 3.0, the former thread ID can be retrieved with
+	 PTRACE_GETEVENTMSG, but since we support older kernels, don't
+	 bother with it, and just walk the LWP list.  Even with
+	 PTRACE_GETEVENTMSG, we'd still need to lookup the
+	 corresponding LWP object, and it would be an extra ptrace
+	 syscall, so this way may even be more efficient.  */
+      for (lwp_info *other_lp : all_lwps_safe ())
+	if (other_lp != lp && other_lp->ptid.pid () == lp->ptid.pid ())
+	  exit_lwp (other_lp);
+
       return 0;
     }
 
diff --git a/gdb/testsuite/gdb.threads/step-over-exec.exp b/gdb/testsuite/gdb.threads/step-over-exec.exp
index 783f865585c..a8b01f8aeda 100644
--- a/gdb/testsuite/gdb.threads/step-over-exec.exp
+++ b/gdb/testsuite/gdb.threads/step-over-exec.exp
@@ -102,6 +102,12 @@ proc do_test { execr_thread different_text_segments displaced_stepping } {
     gdb_breakpoint foo
     gdb_test "continue" "Breakpoint $decimal, foo .*" \
 	"continue to foo"
+
+    # Test that GDB is able to kill the inferior.  This may fail if
+    # e.g., GDB does not dispose of the pre-exec threads properly.
+    gdb_test "with confirm off -- kill" \
+	"\\\[Inferior 1 (.*) killed\\\]" \
+	"kill inferior"
 }
 
 foreach_with_prefix displaced_stepping {auto off} {
-- 
2.36.0


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

* [PATCH 04/31] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (2 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-02-04 15:38   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 05/31] Support clone events in the remote protocol Pedro Alves
                   ` (28 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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 affected, 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 held 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 becomes unused with this patch, but
a new use will reappear later in the series.  To avoid deleting it and
readding it back, this patch marks it with attribute unused, and the
latter patch removes the attribute again.  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            | 158 +++++++++++++------------
 gdb/linux-nat.c         | 248 +++++++++++++++++++++-------------------
 gdb/linux-nat.h         |   2 +
 gdb/target-delegates.c  |  24 ++++
 gdb/target.c            |   7 ++
 gdb/target.h            |   2 +
 gdb/target/waitstatus.c |   1 +
 gdb/target/waitstatus.h |  31 ++++-
 8 files changed, 281 insertions(+), 192 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 0590310ffac..f7786672004 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1583,6 +1583,7 @@ step_over_info_valid_p (void)
 /* Return true if THREAD is doing a displaced step.  */
 
 static bool
+ATTRIBUTE_UNUSED
 displaced_step_in_progress_thread (thread_info *thread)
 {
   gdb_assert (thread != nullptr);
@@ -1897,6 +1898,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?  */
@@ -1916,8 +1942,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
@@ -5663,67 +5720,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);
 
@@ -5739,7 +5742,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.  */
@@ -5771,14 +5774,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.  */
@@ -5789,16 +5802,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 5ee3227f1b9..f3d02b740e8 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1286,64 +1286,79 @@ 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.  */
+/* If LP has a pending fork/vfork/clone status, return it.  */
 
-static void
-detach_one_lwp (struct lwp_info *lp, int *signo_p)
+static gdb::optional<target_waitstatus>
+get_pending_child_status (lwp_info *lp)
 {
-  int lwpid = lp->ptid.lwp ();
-  int signo;
-
-  gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
-
-  /* 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.  */
-
   /* 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);
+	    {
+	      target_waitstatus ws;
+
+	      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 ws;
+	    }
 	  else
-	    perror_warning_with_name (_("Failed to detach fork child"));
+	    {
+	      perror_warning_with_name (_("Failed to retrieve event msg"));
+	      return {};
+	    }
 	}
     }
 
   /* 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_new_child_status (lp->waitstatus.kind ()))
+    return lp->waitstatus;
 
-  /* Check in thread_info::pending_waitstatus.  */
   thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
-  if (tp->has_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);
-    }
+  /* Check in thread_info::pending_waitstatus.  */
+  if (tp->has_pending_waitstatus ()
+      && is_new_child_status (tp->pending_waitstatus ().kind ()))
+    return tp->pending_waitstatus ();
 
   /* 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_new_child_status (tp->pending_follow.kind ()))
+    return tp->pending_follow;
 
-  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 {};
+}
+
+/* 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.  */
+
+  if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
+    detach_one_pid (ws->child_ptid ().lwp (), 0);
 
   /* If there is a pending SIGSTOP, get rid of it.  */
   if (lp->signalled)
@@ -1821,6 +1836,53 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
   return 1;
 }
 
+void
+linux_nat_target::follow_clone (ptid_t child_ptid)
+{
+  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.  */
+      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 (_("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 +1923,9 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
 	    internal_error (_("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,66 +1962,18 @@ 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.  */
-	      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;
@@ -3536,59 +3548,55 @@ 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 ())
+  if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
     {
-      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 a9b91a5e908..3ed25cc5ba4 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 daf46821be0..bee46608c38 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 f781f7e4f96..2fb09c2817d 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2732,6 +2732,13 @@ default_follow_fork (struct target_ops *self, inferior *child_inf,
   internal_error (_("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 (_("could not find a target to follow clone"));
+}
+
 /* See target.h.  */
 
 void
diff --git a/gdb/target.h b/gdb/target.h
index 28aa9273893..aab390aec57 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -637,6 +637,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..2072eb018ae 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,
 
@@ -102,6 +109,17 @@ enum target_waitkind
   TARGET_WAITKIND_THREAD_EXITED,
 };
 
+/* Determine if KIND represents an event with a new child - a fork,
+   vfork, or clone.  */
+
+static inline bool
+is_new_child_status (target_waitkind kind)
+{
+  return (kind == TARGET_WAITKIND_FORKED
+	  || kind == TARGET_WAITKIND_VFORKED
+	  || kind == TARGET_WAITKIND_THREAD_CLONED);
+}
+
 /* Return KIND as a string.  */
 
 static inline const char *
@@ -125,6 +143,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 +345,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 ();
@@ -369,8 +397,7 @@ struct target_waitstatus
 
   ptid_t child_ptid () const
   {
-    gdb_assert (m_kind == TARGET_WAITKIND_FORKED
-		|| m_kind == TARGET_WAITKIND_VFORKED);
+    gdb_assert (is_new_child_status (m_kind));
     return m_value.child_ptid;
   }
 
-- 
2.36.0


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

* [PATCH 05/31] Support clone events in the remote protocol
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (3 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 04/31] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-03-22 15:46   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 06/31] Avoid duplicate QThreadEvents packets Pedro Alves
                   ` (27 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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              | 99 ++++++++++++++++++++++++++-------------
 gdbserver/remote-utils.cc | 26 ++++++++--
 gdbserver/server.cc       |  3 +-
 3 files changed, 91 insertions(+), 37 deletions(-)

diff --git a/gdb/remote.c b/gdb/remote.c
index 53c4f19c5a4..a7430eb79dd 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;
@@ -765,7 +766,7 @@ class remote_target : public process_stratum_target
 
   void remote_btrace_maybe_reopen ();
 
-  void remove_new_fork_children (threads_listing_context *context);
+  void remove_new_children (threads_listing_context *context);
   void kill_new_fork_children (inferior *inf);
   void discard_pending_stop_replies (struct inferior *inf);
   int stop_reply_queue_length ();
@@ -2579,9 +2580,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);
@@ -3983,10 +3985,11 @@ remote_target::update_thread_list ()
 	    }
 	}
 
-      /* Remove any unreported fork child threads from CONTEXT so
-	 that we don't interfere with follow fork, which is where
-	 creation of such threads is handled.  */
-      remove_new_fork_children (&context);
+      /* Remove any unreported fork/vfork/clone child threads from
+	 CONTEXT so that we don't interfere with follow
+	 fork/vfork/clone, which is where creation of such threads is
+	 handled.  */
+      remove_new_children (&context);
 
       /* And now add threads we don't know about yet to our list.  */
       for (thread_item &item : context.items)
@@ -4940,6 +4943,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
@@ -5893,16 +5898,25 @@ 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.  */
+/* Return a reference to the field where a pending child status, if
+   there's one, is recorded.  If there's no child event pending, the
+   returned waitstatus has TARGET_WAITKIND_IGNORE kind.  */
+
+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;
@@ -5910,6 +5924,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
@@ -6075,6 +6103,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.  */
 
@@ -6807,10 +6841,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;
     }
 
@@ -7276,22 +7310,22 @@ struct notif_client notif_client_stop =
   REMOTE_NOTIF_STOP,
 };
 
-/* If CONTEXT contains any fork child threads that have not been
-   reported yet, remove them from the CONTEXT list.  If such a
-   thread exists it is because we are stopped at a fork catchpoint
-   and have not yet called follow_fork, which will set up the
-   host-side data structures for the new process.  */
+/* If CONTEXT contains any fork/vfork/clone child threads that have
+   not been reported yet, remove them from the CONTEXT list.  If such
+   a thread exists it is because we are stopped at a fork/vfork/clone
+   catchpoint and have not yet called follow_fork/follow_clone, which
+   will set up the host-side data structures for the new child.  */
 
 void
-remote_target::remove_new_fork_children (threads_listing_context *context)
+remote_target::remove_new_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;
@@ -7299,13 +7333,12 @@ 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)
+    if (is_new_child_status (event->ws.kind ()))
       context->remove_thread (event->ws.child_ptid ());
     else if (event->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
       context->remove_thread (event->ptid);
@@ -7634,6 +7667,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 2ddb275bd15..6a673cb1ca8 100644
--- a/gdbserver/remote-utils.cc
+++ b/gdbserver/remote-utils.cc
@@ -1062,6 +1062,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:
@@ -1071,13 +1072,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 aaef38e0062..56b7f97a388 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] 100+ messages in thread

* [PATCH 06/31] Avoid duplicate QThreadEvents packets
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (4 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 05/31] Support clone events in the remote protocol Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-05-26 15:53   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 07/31] enum_flags to_string Pedro Alves
                   ` (26 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

Similarly to QProgramSignals and QPassSignals, avoid sending duplicate
QThreadEvents packets.

Change-Id: Iaf5babb0b64e1527ba4db31aac8674d82b17e8b4
---
 gdb/remote.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/gdb/remote.c b/gdb/remote.c
index a7430eb79dd..41348a65dc4 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -314,6 +314,10 @@ class remote_state
      the target know about program signals list changes.  */
   char *last_program_signals_packet = nullptr;
 
+  /* Similarly, the last QThreadEvents state we sent to the
+     target.  */
+  bool last_thread_events = false;
+
   gdb_signal last_sent_signal = GDB_SIGNAL_0;
 
   bool last_sent_step = false;
@@ -14507,6 +14511,9 @@ remote_target::thread_events (int enable)
   if (packet_support (PACKET_QThreadEvents) == PACKET_DISABLE)
     return;
 
+  if (rs->last_thread_events == enable)
+    return;
+
   xsnprintf (rs->buf.data (), size, "QThreadEvents:%x", enable ? 1 : 0);
   putpkt (rs->buf);
   getpkt (&rs->buf, 0);
@@ -14517,6 +14524,7 @@ remote_target::thread_events (int enable)
     case PACKET_OK:
       if (strcmp (rs->buf.data (), "OK") != 0)
 	error (_("Remote refused setting thread events: %s"), rs->buf.data ());
+      rs->last_thread_events = enable;
       break;
     case PACKET_ERROR:
       warning (_("Remote failure reply: %s"), rs->buf.data ());
-- 
2.36.0


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

* [PATCH 07/31] enum_flags to_string
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (5 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 06/31] Avoid duplicate QThreadEvents packets Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-01-30 20:07   ` Simon Marchi
  2022-12-12 20:30 ` [PATCH 08/31] Thread options & clone events (core + remote) Pedro Alves
                   ` (25 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

This commit introduces shared infrastructure that can be used to
implement enum_flags -> to_string functions.  With this, if we want to
support converting a given enum_flags specialization to string, we
just need to implement a function that provides the enumerator->string
mapping, like so:

 enum some_flag
   {
     SOME_FLAG1 = 1 << 0,
     SOME_FLAG2 = 1 << 1,
     SOME_FLAG3 = 1 << 2,
   };

 DEF_ENUM_FLAGS_TYPE (some_flag, some_flags);

 static std::string
 to_string (some_flags flags)
 {
   static constexpr some_flags::string_mapping mapping[] = {
     MAP_ENUM_FLAG (SOME_FLAG1),
     MAP_ENUM_FLAG (SOME_FLAG2),
     MAP_ENUM_FLAG (SOME_FLAG3),
   };
   return flags.to_string (mapping);
 }

.. and then to_string(SOME_FLAG2 | SOME_FLAG3) produces a string like
"0x6 [SOME_FLAG2 SOME_FLAG3]".

If we happen to forget to update the mapping array when we introduce a
new enumerator, then the string representation will pretty-print the
flags it knows about, and then the leftover flags in hex (one single
number).  For example, if we had missed mapping SOME_FLAG2 above, we'd
end up with:

  to_string(SOME_FLAG2 | SOME_FLAG3)  => "0x6 [SOME_FLAG2 0x4]");

Other than in the unit tests included, no actual usage of the
functionality is added in this commit.

Approved-By: Simon Marchi <simon.marchi@efficios.com>
Change-Id: I835de43c33d13bc0c95132f42c3f97318b875779
---
 gdb/unittests/enum-flags-selftests.c | 69 +++++++++++++++++++++++++---
 gdbsupport/enum-flags.h              | 66 ++++++++++++++++++++++++++
 2 files changed, 129 insertions(+), 6 deletions(-)

diff --git a/gdb/unittests/enum-flags-selftests.c b/gdb/unittests/enum-flags-selftests.c
index f52fc7220a1..0fd35262469 100644
--- a/gdb/unittests/enum-flags-selftests.c
+++ b/gdb/unittests/enum-flags-selftests.c
@@ -359,21 +359,47 @@ CHECK_VALID (true,  bool, NF (1) == char (1))
 
 enum test_flag
   {
-    FLAG1 = 1 << 1,
-    FLAG2 = 1 << 2,
-    FLAG3 = 1 << 3,
+    FLAG1 = 1 << 0,
+    FLAG2 = 1 << 1,
+    FLAG3 = 1 << 2,
+    FLAG4 = 1 << 3,
   };
 
 enum test_uflag : unsigned
   {
-    UFLAG1 = 1 << 1,
-    UFLAG2 = 1 << 2,
-    UFLAG3 = 1 << 3,
+    UFLAG1 = 1 << 0,
+    UFLAG2 = 1 << 1,
+    UFLAG3 = 1 << 2,
+    UFLAG4 = 1 << 3,
   };
 
 DEF_ENUM_FLAGS_TYPE (test_flag, test_flags);
 DEF_ENUM_FLAGS_TYPE (test_uflag, test_uflags);
 
+/* to_string enumerator->string mapping functions used to test
+   enum_flags::to_string.  These intentionally miss mapping a couple
+   enumerators each (xFLAG2, xFLAG4).  */
+
+static std::string
+to_string_flags (test_flags flags)
+{
+  static constexpr test_flags::string_mapping mapping[] = {
+    MAP_ENUM_FLAG (FLAG1),
+    MAP_ENUM_FLAG (FLAG3),
+  };
+  return flags.to_string (mapping);
+}
+
+static std::string
+to_string_uflags (test_uflags flags)
+{
+  static constexpr test_uflags::string_mapping mapping[] = {
+    MAP_ENUM_FLAG (UFLAG1),
+    MAP_ENUM_FLAG (UFLAG3),
+  };
+  return flags.to_string (mapping);
+}
+
 static void
 self_test ()
 {
@@ -581,6 +607,37 @@ self_test ()
 
     SELF_CHECK (ok);
   }
+
+  /* Check string conversion.  */
+  {
+    SELF_CHECK (to_string_uflags (0)
+		== "0x0 []");
+    SELF_CHECK (to_string_uflags (UFLAG1)
+		== "0x1 [UFLAG1]");
+    SELF_CHECK (to_string_uflags (UFLAG1 | UFLAG3)
+		== "0x5 [UFLAG1 UFLAG3]");
+    SELF_CHECK (to_string_uflags (UFLAG1 | UFLAG2 | UFLAG3)
+		== "0x7 [UFLAG1 UFLAG3 0x2]");
+    SELF_CHECK (to_string_uflags (UFLAG2)
+		== "0x2 [0x2]");
+    /* Check that even with multiple unmapped flags, we only print one
+       unmapped hex number (0xa, in this case).  */
+    SELF_CHECK (to_string_uflags (UFLAG1 | UFLAG2 | UFLAG3 | UFLAG4)
+		== "0xf [UFLAG1 UFLAG3 0xa]");
+
+    SELF_CHECK (to_string_flags (0)
+		== "0x0 []");
+    SELF_CHECK (to_string_flags (FLAG1)
+		== "0x1 [FLAG1]");
+    SELF_CHECK (to_string_flags (FLAG1 | FLAG3)
+		== "0x5 [FLAG1 FLAG3]");
+    SELF_CHECK (to_string_flags (FLAG1 | FLAG2 | FLAG3)
+		== "0x7 [FLAG1 FLAG3 0x2]");
+    SELF_CHECK (to_string_flags (FLAG2)
+		== "0x2 [0x2]");
+    SELF_CHECK (to_string_flags (FLAG1 | FLAG2 | FLAG3 | FLAG4)
+		== "0xf [FLAG1 FLAG3 0xa]");
+  }
 }
 
 } /* namespace enum_flags_tests */
diff --git a/gdbsupport/enum-flags.h b/gdbsupport/enum-flags.h
index cd500f55ff3..8546b6e75f2 100644
--- a/gdbsupport/enum-flags.h
+++ b/gdbsupport/enum-flags.h
@@ -130,6 +130,17 @@ class enum_flags
   typedef E enum_type;
   typedef typename enum_underlying_type<enum_type>::type underlying_type;
 
+  /* For to_string.  Maps one enumerator of E to a string.  */
+  struct string_mapping
+  {
+    E flag;
+    const char *str;
+  };
+
+  /* Convenience for to_string implementations, to build a
+     string_mapping array.  */
+#define MAP_ENUM_FLAG(ENUM_FLAG) { ENUM_FLAG, #ENUM_FLAG }
+
 public:
   /* Allow default construction.  */
   constexpr enum_flags ()
@@ -183,6 +194,18 @@ class enum_flags
   /* Binary operations involving some unrelated type (which would be a
      bug) are implemented as non-members, and deleted.  */
 
+  /* Convert this object to a std::string, using MAPPING as
+     enumerator-to-string mapping array.  This is not meant to be
+     called directly.  Instead, enum_flags specializations should have
+     their own to_string function wrapping this one, thus hidding the
+     mapping array from callers.
+
+     Note: this is defined outside the template class so it can use
+     the global operators for enum_type, which are only defined after
+     the template class.  */
+  template<size_t N>
+  std::string to_string (const string_mapping (&mapping)[N]) const;
+
 private:
   /* Stored as enum_type because GDB knows to print the bit flags
      neatly if the enum values look like bit flags.  */
@@ -415,6 +438,49 @@ template <typename enum_type, typename any_type,
 	  typename = is_enum_flags_enum_type_t<enum_type>>
 void operator>> (const enum_flags<enum_type> &, const any_type &) = delete;
 
+template<typename E>
+template<size_t N>
+std::string
+enum_flags<E>::to_string (const string_mapping (&mapping)[N]) const
+{
+  enum_type flags = raw ();
+  std::string res = hex_string (flags);
+  res += " [";
+
+  bool need_space = false;
+  for (const auto &entry : mapping)
+    {
+      if ((flags & entry.flag) != 0)
+	{
+	  /* Work with an unsigned version of the underlying type,
+	     because if enum_type's underlying type is signed, op~
+	     won't be defined for it, and, bitwise operations on
+	     signed types are implementation defined.  */
+	  using uns = typename std::make_unsigned<underlying_type>::type;
+	  flags &= (enum_type) ~(uns) entry.flag;
+
+	  if (need_space)
+	    res += " ";
+	  res += entry.str;
+
+	  need_space = true;
+	}
+    }
+
+  /* If there were flags not included in the mapping, print them as
+     a hex number.  */
+  if (flags != 0)
+    {
+      if (need_space)
+	res += " ";
+      res += hex_string (flags);
+    }
+
+  res += "]";
+
+  return res;
+}
+
 #else /* __cplusplus */
 
 /* In C, the flags type is just a typedef for the enum type.  */
-- 
2.36.0


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

* [PATCH 08/31] Thread options & clone events (core + remote)
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (6 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 07/31] enum_flags to_string Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-01-31 12:25   ` Lancelot SIX
  2022-12-12 20:30 ` [PATCH 09/31] Thread options & clone events (native Linux) Pedro Alves
                   ` (24 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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_THREAD_OPTION_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_THREAD_OPTION_CLONE when stepping
over a breakpoint.

Unlike PTRACE_SETOPTIONS, fork/vfork/clone children do NOT inherit
their parent's thread options.  This is so that GDB can send e.g.,
"QThreadOptions;0;1:TID" without worrying about threads it doesn't
know about yet.

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

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
Change-Id: Ie41e5093b2573f14cf6ac41b0b5804eba75be37e
---
 gdb/gdbthread.h        |  16 +++++
 gdb/infrun.c           |  63 +++++++++++++++++-
 gdb/remote.c           | 143 ++++++++++++++++++++++++++++++++++++++++-
 gdb/target-debug.h     |   2 +
 gdb/target-delegates.c |  28 ++++++++
 gdb/target.c           |   9 +++
 gdb/target.h           |   8 +++
 gdb/target/target.c    |  11 ++++
 gdb/target/target.h    |  16 +++++
 gdb/thread.c           |  15 +++++
 gdbserver/gdbthread.h  |   3 +
 gdbserver/server.cc    | 130 +++++++++++++++++++++++++++++++++++++
 gdbserver/target.cc    |   6 ++
 gdbserver/target.h     |   6 ++
 14 files changed, 454 insertions(+), 2 deletions(-)

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 1a33eb61221..43e9d6ea484 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,17 @@ 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);
+
+  /* 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 +589,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 f7786672004..c100bc70034 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1583,7 +1583,6 @@ step_over_info_valid_p (void)
 /* Return true if THREAD is doing a displaced step.  */
 
 static bool
-ATTRIBUTE_UNUSED
 displaced_step_in_progress_thread (thread_info *thread)
 {
   gdb_assert (thread != nullptr);
@@ -1885,6 +1884,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.  */
+      event_thread->set_thread_options (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
@@ -1929,6 +1950,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--;
 
@@ -2422,6 +2445,42 @@ 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_THREAD_OPTION_CLONE;
+      if (target_supports_set_thread_options (options))
+	tp->set_thread_options (options);
+      else
+	target_thread_events (true);
+    }
+
+  /* If we're resuming more than one thread simultaneously, then any
+     thread other than the leader is being set to run free.  Clear any
+     previous thread option for those threads.  */
+  if (resume_ptid != inferior_ptid && target_supports_set_thread_options (0))
+    {
+      process_stratum_target *resume_target = tp->inf->process_target ();
+      for (thread_info *thr_iter : all_non_exited_threads (resume_target,
+							   resume_ptid))
+	if (thr_iter != tp)
+	  thr_iter->set_thread_options (0);
+    }
+
   infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
 		       resume_ptid.to_string ().c_str (),
 		       step, gdb_signal_to_symbol_string (sig));
@@ -6090,6 +6149,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/remote.c b/gdb/remote.c
index 41348a65dc4..9de8ed8a068 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -385,6 +385,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
@@ -421,6 +425,8 @@ class remote_target : public process_stratum_target
   void detach (inferior *, int) override;
   void disconnect (const char *, int) override;
 
+  void commit_requested_thread_options ();
+
   void commit_resumed () override;
   void resume (ptid_t, int, enum gdb_signal) override;
   ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
@@ -547,6 +553,8 @@ class remote_target : public process_stratum_target
 
   void thread_events (int) override;
 
+  bool supports_set_thread_options (gdb_thread_options) override;
+
   int can_do_single_step () override;
 
   void terminal_inferior () override;
@@ -837,6 +845,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 ();
 
@@ -2169,6 +2180,9 @@ enum {
   /* Support for the QThreadEvents packet.  */
   PACKET_QThreadEvents,
 
+  /* Support for the QThreadOptions packet.  */
+  PACKET_QThreadOptions,
+
   /* Support for multi-process extensions.  */
   PACKET_multiprocess_feature,
 
@@ -5290,7 +5304,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 ();
 
@@ -5327,6 +5342,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,
@@ -5429,6 +5487,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 },
@@ -5523,6 +5583,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+");
 
@@ -6599,6 +6662,8 @@ remote_target::resume (ptid_t scope_ptid, int step, enum gdb_signal siggnal)
       return;
     }
 
+  commit_requested_thread_options ();
+
   /* In all-stop, we can't mark REMOTE_ASYNC_GET_PENDING_EVENTS_TOKEN
      (explained in remote-notif.c:handle_notification) so
      remote_notif_process is not called.  We need find a place where
@@ -6761,6 +6826,8 @@ remote_target::commit_resumed ()
   if (!target_is_non_stop_p () || ::execution_direction == EXEC_REVERSE)
     return;
 
+  commit_requested_thread_options ();
+
   /* Try to send wildcard actions ("vCont;c" or "vCont;c:pPID.-1")
      instead of resuming all threads of each process individually.
      However, if any thread of a process must remain halted, we can't
@@ -14534,6 +14601,77 @@ 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);
+}
+
+/* For coalescing reasons, actually sending the options to the target
+   happens at resume time, via this function.  See target_resume for
+   all-stop, and target_commit_resumed for non-stop.  */
+
+void
+remote_target::commit_requested_thread_options ()
+{
+  struct remote_state *rs = get_remote_state ();
+
+  if (packet_support (PACKET_QThreadOptions) != PACKET_ENABLE)
+    return;
+
+  char *p = rs->buf.data ();
+  char *endp = p + get_remote_packet_size ();
+
+  /* Clear options for all threads by default.  Note that unlike
+     vCont, the rightmost options that match a thread apply, so we
+     don't have to worry about whether we can use wildcard ptids.  */
+  strcpy (p, "QThreadOptions;0");
+  p += strlen (p);
+
+  /* Now set non-zero options for threads that need them.  We don't
+     bother with the case of all threads of a process wanting the same
+     non-zero options as that's not an expected scenario.  */
+  for (thread_info *tp : all_non_exited_threads (this))
+    {
+      gdb_thread_options options = tp->thread_options ();
+
+      if (options == 0)
+	continue;
+
+      *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)
 {
@@ -15313,6 +15451,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 77033f00289..67f18e6e6bb 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 (to_string (X).c_str ())
 
 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 bee46608c38..7e0c67f6cb0 100644
--- a/gdb/target-delegates.c
+++ b/gdb/target-delegates.c
@@ -106,6 +106,7 @@ 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;
   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 +282,7 @@ 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;
   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 +2274,32 @@ 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;
+}
+
 bool
 target_ops::supports_non_stop ()
 {
diff --git a/gdb/target.c b/gdb/target.c
index 2fb09c2817d..f088f945b4d 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -4389,6 +4389,15 @@ 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);
+}
+
 /* 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 aab390aec57..52a31390264 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -731,6 +731,10 @@ 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);
     /* This method must be implemented in some situations.  See the
        comment on 'can_run'.  */
     virtual bool supports_non_stop ()
@@ -1894,6 +1898,10 @@ extern void target_async (bool 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);
+
 /* 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.c b/gdb/target/target.c
index 0b165bc05fe..453167a2ad4 100644
--- a/gdb/target/target.c
+++ b/gdb/target/target.c
@@ -188,3 +188,14 @@ target_read_string (CORE_ADDR memaddr, int len, int *bytes_read)
 
   return gdb::unique_xmalloc_ptr<char> ((char *) buffer.release ());
 }
+
+/* See target/target.h.  */
+
+std::string
+to_string (gdb_thread_options options)
+{
+  static constexpr gdb_thread_options::string_mapping mapping[] = {
+    MAP_ENUM_FLAG (GDB_THREAD_OPTION_CLONE),
+  };
+  return options.to_string (mapping);
+}
diff --git a/gdb/target/target.h b/gdb/target/target.h
index a5b0dd3ed1a..139e371e8d8 100644
--- a/gdb/target/target.h
+++ b/gdb/target/target.h
@@ -22,9 +22,25 @@
 
 #include "target/waitstatus.h"
 #include "target/wait.h"
+#include "gdbsupport/enum-flags.h"
 
 /* This header is a stopgap until more code is shared.  */
 
+/* Available thread options.  Keep this in sync with to_string, in
+   target.c.  */
+
+enum gdb_thread_option : unsigned
+{
+  /* Tell the target to report TARGET_WAITKIND_THREAD_CLONED events
+     for the thread.  */
+  GDB_THREAD_OPTION_CLONE = 1 << 0,
+};
+
+DEF_ENUM_FLAGS_TYPE (enum gdb_thread_option, gdb_thread_options);
+
+/* Convert gdb_thread_option to a string.  */
+extern std::string to_string (gdb_thread_options 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/gdb/thread.c b/gdb/thread.c
index cd7f1a7d5bb..d607ad9303a 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -398,6 +398,21 @@ thread_info::clear_pending_waitstatus ()
 
 /* See gdbthread.h.  */
 
+void
+thread_info::set_thread_options (gdb_thread_options thread_options)
+{
+  if (m_thread_options == thread_options)
+    return;
+
+  m_thread_options = thread_options;
+
+  infrun_debug_printf ("[options for %s are now %s]",
+		       this->ptid.to_string ().c_str (),
+		       to_string (thread_options).c_str ());
+}
+
+/* See gdbthread.h.  */
+
 int
 thread_is_in_step_over_chain (struct thread_info *tp)
 {
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 56b7f97a388..5b07c4e4388 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -36,6 +36,7 @@
 #include "dll.h"
 #include "hostio.h"
 #include <vector>
+#include <unordered_map>
 #include "gdbsupport/common-inferior.h"
 #include "gdbsupport/job-control.h"
 #include "gdbsupport/environ.h"
@@ -611,6 +612,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 +904,114 @@ handle_general_set (char *own_buf)
       return;
     }
 
+  if (startswith (own_buf, "QThreadOptions;"))
+    {
+      const char *p = own_buf + strlen ("QThreadOptions");
+
+      gdb_thread_options supported_options = target_supported_thread_options ();
+      if (supported_options == 0)
+	{
+	  /* Something went wrong -- we don't support any option, but
+	     GDB sent the packet anyway.  */
+	  write_enn (own_buf);
+	  return;
+	}
+
+      /* We could store the options directly in thread->thread_options
+	 without this map, but that would mean that a QThreadOptions
+	 packet with a wildcard like "QThreadOptions;0;3:TID" would
+	 result in the debug logs showing:
+
+	   [options for TID are now 0x0]
+	   [options for TID are now 0x3]
+
+	 It's nicer if we only print the final options for each TID,
+	 and if we only print about it if the options changed compared
+	 to the options that were previously set on the thread.  */
+      std::unordered_map<thread_info *, gdb_thread_options> set_options;
+
+      while (*p != '\0')
+	{
+	  if (p[0] != ';')
+	    {
+	      write_enn (own_buf);
+	      return;
+	    }
+	  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 thread options requested: %s\n",
+				 to_string (options).c_str ());
+	      strcpy (own_buf, err.c_str ());
+	      return;
+	    }
+
+	  ptid_t ptid;
+
+	  if (p[0] == ';' || 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;
+	    }
+
+	  /* Convert PID.-1 => PID.0 for ptid.matches.  */
+	  if (ptid.lwp () == -1)
+	    ptid = ptid_t (ptid.pid ());
+
+	  for_each_thread ([&] (thread_info *thread)
+	    {
+	      if (ptid_of (thread).matches (ptid))
+		set_options[thread] = options;
+	    });
+	}
+
+      for (const auto &iter : set_options)
+	{
+	  thread_info *thread = iter.first;
+	  gdb_thread_options options = iter.second;
+
+	  if (thread->thread_options != options)
+	    {
+	      threads_debug_printf ("[options for %s are now %s]\n",
+				    target_pid_to_str (ptid_of (thread)).c_str (),
+				    to_string (options).c_str ());
+
+	      thread->thread_options = options;
+	    }
+	}
+
+      write_ok (own_buf);
+      return;
+    }
+
   if (startswith (own_buf, "QStartupWithShell:"))
     {
       const char *value = own_buf + strlen ("QStartupWithShell:");
@@ -2364,6 +2484,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
@@ -2490,6 +2612,14 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
 
       strcat (own_buf, ";vContSupported+");
 
+      gdb_thread_options supported_options = target_supported_thread_options ();
+      if (supported_options != 0)
+	{
+	  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 c06a67600b1..168b843d2ec 100644
--- a/gdbserver/target.cc
+++ b/gdbserver/target.cc
@@ -530,6 +530,12 @@ process_stratum_target::supports_vfork_events ()
   return false;
 }
 
+gdb_thread_options
+process_stratum_target::supported_thread_options ()
+{
+  return 0;
+}
+
 bool
 process_stratum_target::supports_exec_events ()
 {
diff --git a/gdbserver/target.h b/gdbserver/target.h
index 18ab969dda7..8a0d9f42f7d 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -277,6 +277,9 @@ class process_stratum_target
   /* Returns true if vfork events are supported.  */
   virtual bool supports_vfork_events ();
 
+  /* Returns the set of supported thread options.  */
+  virtual gdb_thread_options supported_thread_options ();
+
   /* Returns true if exec events are supported.  */
   virtual bool supports_exec_events ();
 
@@ -532,6 +535,9 @@ int kill_inferior (process_info *proc);
 #define target_supports_vfork_events() \
   the_target->supports_vfork_events ()
 
+#define target_supported_thread_options(options) \
+  the_target->supported_thread_options (options)
+
 #define target_supports_exec_events() \
   the_target->supports_exec_events ()
 
-- 
2.36.0


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

* [PATCH 09/31] Thread options & clone events (native Linux)
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (7 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 08/31] Thread options & clone events (core + remote) Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-06 13:43   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 10/31] Thread options & clone events (Linux GDBserver) Pedro Alves
                   ` (23 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

This commit teaches the native Linux target about the
GDB_THREAD_OPTION_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 to report support for the option,
otherwise GDB falls back to use target_thread_events().

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
Change-Id: If90316e2dcd0c61d0fefa0d463c046011698acf9
---
 gdb/linux-nat.c | 7 +++++++
 gdb/linux-nat.h | 2 ++
 2 files changed, 9 insertions(+)

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index f3d02b740e8..5fadc82deb0 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -4468,6 +4468,13 @@ 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_THREAD_OPTION_CLONE;
+  return ((options & supported_options) == options);
+}
+
 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 3ed25cc5ba4..258041b8271 100644
--- a/gdb/linux-nat.h
+++ b/gdb/linux-nat.h
@@ -82,6 +82,8 @@ class linux_nat_target : public inf_ptrace_target
 
   void thread_events (int) override;
 
+  bool supports_set_thread_options (gdb_thread_options options) override;
+
   bool can_async_p () override;
 
   bool supports_non_stop () override;
-- 
2.36.0


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

* [PATCH 10/31] Thread options & clone events (Linux GDBserver)
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (8 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 09/31] Thread options & clone events (native Linux) Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-06 14:12   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 11/31] gdbserver: Hide and don't detach pending clone children Pedro Alves
                   ` (22 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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_THREAD_OPTION_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.

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

diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 6f96e16d6f0..d755cda0e44 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,65 @@ 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);
 
+      /* 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 +592,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_THREAD_OPTION_CLONE) != 0)
+	event_lwp->waitstatus.set_thread_cloned (child_ptid);
+
+      if (event != PTRACE_EVENT_CLONE
+	  || (event_thr->thread_options & GDB_THREAD_OPTION_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_THREAD_OPTION_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)
     {
@@ -3527,7 +3538,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;
@@ -4263,15 +4275,14 @@ 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/vfork/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))
+		  && is_new_child_status (rel->waitstatus.kind ()))
 		{
 		  threads_debug_printf
 		    ("not resuming LWP %ld: has queued stop reply",
@@ -5894,6 +5905,14 @@ linux_process_target::supports_vfork_events ()
   return true;
 }
 
+/* Return the set of supported thread options.  */
+
+gdb_thread_options
+linux_process_target::supported_thread_options ()
+{
+  return GDB_THREAD_OPTION_CLONE;
+}
+
 /* Check if exec events are supported.  */
 
 bool
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index 1594f063f47..69a34fb96fc 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;
 
+  gdb_thread_options supported_thread_options () override;
+
   bool supports_exec_events () override;
 
   void handle_new_gdb_connection () override;
-- 
2.36.0


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

* [PATCH 11/31] gdbserver: Hide and don't detach pending clone children
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (9 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 10/31] Thread options & clone events (Linux GDBserver) Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-07 16:10   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 12/31] Remove gdb/19675 kfails (displaced stepping + clone) Pedro Alves
                   ` (21 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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 d755cda0e44..a7c310260ca 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -6940,9 +6940,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 69a34fb96fc..c9f9db71e09 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -315,7 +315,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;
 
@@ -734,8 +735,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)
@@ -743,10 +744,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);
 
@@ -756,24 +757,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);
 
@@ -782,8 +784,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 5b07c4e4388..07a3319d114 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -1344,8 +1344,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);
@@ -1765,9 +1766,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 168b843d2ec..4584e9b3a8e 100644
--- a/gdbserver/target.cc
+++ b/gdbserver/target.cc
@@ -814,7 +814,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 8a0d9f42f7d..e2e818b130b 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] 100+ messages in thread

* [PATCH 12/31] Remove gdb/19675 kfails (displaced stepping + clone)
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (10 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 11/31] gdbserver: Hide and don't detach pending clone children Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-07 17:08   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 13/31] Add test for stepping over clone syscall Pedro Alves
                   ` (20 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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 4192fa10826..e87382b2fa6 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} \
@@ -299,15 +270,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] 100+ messages in thread

* [PATCH 13/31] Add test for stepping over clone syscall
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (11 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 12/31] Remove gdb/19675 kfails (displaced stepping + clone) Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-07 17:42   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 14/31] all-stop/synchronous RSP support thread-exit events Pedro Alves
                   ` (19 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Pedro Alves

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

- New in v3:

  Addressed issue Tom de Vries ran into with no debug info for glibc.

- New in v2:

  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..580cf2d921b
--- /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 < 300; ++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 < 300; ++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..d9cbe2b957a
--- /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\\), .*"
+
+# 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] 100+ messages in thread

* [PATCH 14/31] all-stop/synchronous RSP support thread-exit events
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (12 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 13/31] Add test for stepping over clone syscall Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-07 17:52   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 15/31] gdbserver/linux-low.cc: Ignore event_ptid if TARGET_WAITKIND_IGNORE Pedro Alves
                   ` (18 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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_THREAD_OPTION_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 9de8ed8a068..f7ab8523fd5 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -8172,7 +8172,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);
@@ -8358,7 +8359,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 07a3319d114..5099db1ee31 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -3061,6 +3061,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] 100+ messages in thread

* [PATCH 15/31] gdbserver/linux-low.cc: Ignore event_ptid if TARGET_WAITKIND_IGNORE
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (13 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 14/31] all-stop/synchronous RSP support thread-exit events Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2022-12-12 20:30 ` [PATCH 16/31] Move deleting thread on TARGET_WAITKIND_THREAD_EXITED to core Pedro Alves
                   ` (17 subsequent siblings)
  32 siblings, 0 replies; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

gdbserver's linux_process_target::wait loops if called sync mode, and
wait_1 returns TARGET_WAITKIND_IGNORE, _and_ wait_1 also returns
null_ptid.  The null_ptid check fails however when this path is taken:

   ptid_t
   linux_process_target::filter_exit_event (lwp_info *event_child,
					    target_waitstatus *ourstatus)
   {
   ...
     if (!is_leader (thread))
       {
	 if (report_exit_events_for (thread))
	   ourstatus->set_thread_exited (0);
	 else
	   ourstatus->set_ignore ();            <<<<<<<

	 delete_lwp (event_child);
       }
     return ptid;
   }

This makes linux_process_target::wait return TARGET_WAITKIND_IGNORE in
sync mode, which is unexpected by the core and fails an assertion.

This commit fixes it by just making linux_process_target::wait loop if
it got a TARGET_WAITKIND_IGNORE, irrespective of event_ptid.

Change-Id: I39776908a6c75cbd68aa04139ffcf7be334868cf
---
 gdbserver/linux-low.cc | 1 -
 1 file changed, 1 deletion(-)

diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index a7c310260ca..8c0d3103398 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -3636,7 +3636,6 @@ linux_process_target::wait (ptid_t ptid,
       event_ptid = wait_1 (ptid, ourstatus, target_options);
     }
   while ((target_options & TARGET_WNOHANG) == 0
-	 && event_ptid == null_ptid
 	 && ourstatus->kind () == TARGET_WAITKIND_IGNORE);
 
   /* If at least one stop was reported, there may be more.  A single
-- 
2.36.0


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

* [PATCH 16/31] Move deleting thread on TARGET_WAITKIND_THREAD_EXITED to core
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (14 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 15/31] gdbserver/linux-low.cc: Ignore event_ptid if TARGET_WAITKIND_IGNORE Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-08 12:27   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 17/31] Introduce GDB_THREAD_OPTION_EXIT thread option, fix step-over-thread-exit Pedro Alves
                   ` (16 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

Currently, infrun assumes that when TARGET_WAITKIND_THREAD_EXITED is
reported, the corresponding GDB thread has already been removed from
the GDB thread list.

Later in the series, that will no longer work, as infrun will need to
refer to the thread's thread_info when it processes
TARGET_WAITKIND_THREAD_EXITED.

As preparation, this patch makes deleting the GDB thread
responsibility of infrun, instead of the target.

Change-Id: I013d87f61ffc9aaca49f0d6ce2a43e3ea69274de
---
 gdb/infrun.c     | 31 ++++++++++++++++++++++++++-----
 gdb/linux-nat.c  | 22 +++++++++++++++-------
 gdb/netbsd-nat.c |  1 -
 3 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index c100bc70034..6fdffb31884 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4238,7 +4238,12 @@ reinstall_readline_callback_handler_cleanup ()
 }
 
 /* Clean up the FSMs of threads that are now stopped.  In non-stop,
-   that's just the event thread.  In all-stop, that's all threads.  */
+   that's just the event thread.  In all-stop, that's all threads.  In
+   all-stop, threads that had a pending exit no longer have a reason
+   to be around, as their FSMs/commands are canceled, so we delete
+   them.  This avoids "info threads" listing such threads as if they
+   were alive (and failing to read their registers), the user being to
+   select and resume them (and that failing), etc.  */
 
 static void
 clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
@@ -4256,15 +4261,28 @@ clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
     {
       scoped_restore_current_thread restore_thread;
 
-      for (thread_info *thr : all_non_exited_threads ())
+      for (thread_info *thr : all_threads_safe ())
 	{
-	  if (thr->thread_fsm () == nullptr)
+	  if (thr->state == THREAD_EXITED)
 	    continue;
+
 	  if (thr == ecs->event_thread)
 	    continue;
 
-	  switch_to_thread (thr);
-	  thr->thread_fsm ()->clean_up (thr);
+	  if (thr->thread_fsm () != nullptr)
+	    {
+	      switch_to_thread (thr);
+	      thr->thread_fsm ()->clean_up (thr);
+	    }
+
+	  /* As we are cancelling the command/FSM of this thread,
+	     whatever was the reason we needed to report a thread
+	     exited event to the user, that reason is gone.  Delete
+	     the thread, so that the user doesn't see it in the thread
+	     list, the next proceed doesn't try to resume it, etc.  */
+	  if (thr->has_pending_waitstatus ()
+	      && thr->pending_waitstatus ().kind () == TARGET_WAITKIND_THREAD_EXITED)
+	    delete_thread (thr);
 	}
     }
 }
@@ -5537,6 +5555,9 @@ handle_inferior_event (struct execution_control_state *ecs)
 
   if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
     {
+      ecs->event_thread = find_thread_ptid (ecs->target, ecs->ptid);
+      gdb_assert (ecs->event_thread != nullptr);
+      delete_thread (ecs->event_thread);
       prepare_to_wait (ecs);
       return;
     }
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 5fadc82deb0..b576ce60b75 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -898,10 +898,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);
 
@@ -911,7 +912,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);
@@ -3135,11 +3137,17 @@ filter_exit_event (struct lwp_info *event_child,
   if (!is_leader (event_child))
     {
       if (report_thread_events)
-	ourstatus->set_thread_exited (0);
+	{
+	  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;
diff --git a/gdb/netbsd-nat.c b/gdb/netbsd-nat.c
index bbadd865823..aa16a6cc5bd 100644
--- a/gdb/netbsd-nat.c
+++ b/gdb/netbsd-nat.c
@@ -629,7 +629,6 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
 	  if (print_thread_events)
 	    gdb_printf (_("[%s exited]\n"),
 			target_pid_to_str (wptid).c_str ());
-	  delete_thread (thr);
 	}
 
       /* The GDB core expects that the rest of the threads are running.  */
-- 
2.36.0


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

* [PATCH 17/31] Introduce GDB_THREAD_OPTION_EXIT thread option, fix step-over-thread-exit
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (15 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 16/31] Move deleting thread on TARGET_WAITKIND_THREAD_EXITED to core Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-08 13:17   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 18/31] Implement GDB_THREAD_OPTION_EXIT support for Linux GDBserver Pedro Alves
                   ` (15 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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_THREAD_OPTION_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        | 15 ++++++++++-----
 gdb/remote.c        |  9 +++++++++
 gdb/target/target.c |  1 +
 gdb/target/target.h |  4 ++++
 4 files changed, 24 insertions(+), 5 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 6fdffb31884..e47e3c688e7 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2445,24 +2445,29 @@ 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_THREAD_OPTION_CLONE;
+      gdb_thread_options options
+	= GDB_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
       if (target_supports_set_thread_options (options))
 	tp->set_thread_options (options);
       else
diff --git a/gdb/remote.c b/gdb/remote.c
index f7ab8523fd5..0d2b7c09a07 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -3998,6 +3998,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_THREAD_OPTION_EXIT) != 0)
+		continue;
+
 	      /* Not found.  */
 	      delete_thread (tp);
 	    }
diff --git a/gdb/target/target.c b/gdb/target/target.c
index 453167a2ad4..2dc033e63b7 100644
--- a/gdb/target/target.c
+++ b/gdb/target/target.c
@@ -196,6 +196,7 @@ to_string (gdb_thread_options options)
 {
   static constexpr gdb_thread_options::string_mapping mapping[] = {
     MAP_ENUM_FLAG (GDB_THREAD_OPTION_CLONE),
+    MAP_ENUM_FLAG (GDB_THREAD_OPTION_EXIT),
   };
   return options.to_string (mapping);
 }
diff --git a/gdb/target/target.h b/gdb/target/target.h
index 139e371e8d8..4e8839c5667 100644
--- a/gdb/target/target.h
+++ b/gdb/target/target.h
@@ -34,6 +34,10 @@ enum gdb_thread_option : unsigned
   /* Tell the target to report TARGET_WAITKIND_THREAD_CLONED events
      for the thread.  */
   GDB_THREAD_OPTION_CLONE = 1 << 0,
+
+  /* Tell the target to report TARGET_WAITKIND_THREAD_EXIT events for
+     the thread.  */
+  GDB_THREAD_OPTION_EXIT = 1 << 1,
 };
 
 DEF_ENUM_FLAGS_TYPE (enum gdb_thread_option, gdb_thread_options);
-- 
2.36.0


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

* [PATCH 18/31] Implement GDB_THREAD_OPTION_EXIT support for Linux GDBserver
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (16 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 17/31] Introduce GDB_THREAD_OPTION_EXIT thread option, fix step-over-thread-exit Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-08 14:14   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 19/31] Implement GDB_THREAD_OPTION_EXIT support for native Linux Pedro Alves
                   ` (14 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

This implements support for the new GDB_THREAD_OPTION_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 8c0d3103398..ebc3bf34127 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_THREAD_OPTION_EXIT) != 0);
+}
+
 /* LWP accessors.  */
 
 /* See nat/linux-nat.h.  */
@@ -2230,7 +2242,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;
@@ -2317,7 +2328,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
@@ -2879,13 +2890,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 ();
@@ -3028,10 +3046,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
@@ -3600,10 +3615,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.  */
@@ -5909,7 +5921,7 @@ linux_process_target::supports_vfork_events ()
 gdb_thread_options
 linux_process_target::supported_thread_options ()
 {
-  return GDB_THREAD_OPTION_CLONE;
+  return GDB_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
 }
 
 /* Check if exec events are supported.  */
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index c9f9db71e09..1c1754d2b59 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -575,10 +575,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] 100+ messages in thread

* [PATCH 19/31] Implement GDB_THREAD_OPTION_EXIT support for native Linux
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (17 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 18/31] Implement GDB_THREAD_OPTION_EXIT support for Linux GDBserver Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-08 14:17   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 20/31] gdb: clear step over information on thread exit (PR gdb/27338) Pedro Alves
                   ` (13 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

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

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

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index b576ce60b75..75f81edf20a 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -269,6 +269,18 @@ pending_status_str (lwp_info *lp)
     return status_to_str (lp->status);
 }
 
+/* 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_thread_events
+	  || (thr->thread_options () & GDB_THREAD_OPTION_EXIT) != 0);
+}
+
 \f
 /* LWP accessors.  */
 
@@ -2131,8 +2143,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 ());
 
@@ -2913,7 +2924,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 ());
@@ -3123,10 +3134,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,
@@ -3134,9 +3146,17 @@ 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)
+      if (report_exit_events_for (event_child))
 	{
 	  ourstatus->set_thread_exited (0);
 	  /* Delete lwp, but not thread_info, infrun will need it to
@@ -3369,10 +3389,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
@@ -4479,7 +4496,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_THREAD_OPTION_CLONE;
+  constexpr gdb_thread_options supported_options
+    = GDB_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
   return ((options & supported_options) == options);
 }
 
-- 
2.36.0


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

* [PATCH 20/31] gdb: clear step over information on thread exit (PR gdb/27338)
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (18 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 19/31] Implement GDB_THREAD_OPTION_EXIT support for native Linux Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-08 15:29   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 21/31] stop_all_threads: (re-)enable async before waiting for stops Pedro Alves
                   ` (12 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Simon Marchi

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: Simon Marchi <simon.marchi@efficios.com>
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              | 171 ++++++++++++++++++++++++++++++++++----
 gdb/thread.c              |   3 +
 5 files changed, 174 insertions(+), 17 deletions(-)

diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
index 7b5d327008d..aa8571d51e2 100644
--- a/gdb/displaced-stepping.c
+++ b/gdb/displaced-stepping.c
@@ -254,6 +254,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 5d60f7677f0..875c784dc0f 100644
--- a/gdb/gdbarch-components.py
+++ b/gdb/gdbarch-components.py
@@ -1826,6 +1826,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 5c9390ea6b3..69212216f03 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1078,7 +1078,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 e47e3c688e7..2866962d2dc 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1888,13 +1888,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.  */
-      event_thread->set_thread_options (0);
+	 event thread, unless the thread is gone.  */
+      if (event_status.kind () != TARGET_WAITKIND_THREAD_EXITED)
+	event_thread->set_thread_options (0);
     }
   else
     {
@@ -1950,7 +1952,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--;
@@ -4054,6 +4056,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
@@ -5181,6 +5184,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
@@ -5392,7 +5405,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)
@@ -5520,6 +5535,125 @@ 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;
+
+  /* Continue handling the event as if we had gotten a
+     TARGET_WAITKIND_NO_RESUMED.  */
+  auto handle_as_no_resumed = [ecs] ()
+  {
+    /* handle_no_resumed doesn't really look at the event kind, but
+       normal_stop does.  */
+    ecs->ws.set_no_resumed ();
+    ecs->event_thread = nullptr;
+    ecs->ptid = minus_one_ptid;
+
+    /* Re-record the last target status.  */
+    set_last_target_status (ecs->target, ecs->ptid, ecs->ws);
+
+    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.
@@ -5558,15 +5692,6 @@ handle_inferior_event (struct execution_control_state *ecs)
       return;
     }
 
-  if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
-    {
-      ecs->event_thread = find_thread_ptid (ecs->target, ecs->ptid);
-      gdb_assert (ecs->event_thread != nullptr);
-      delete_thread (ecs->event_thread);
-      prepare_to_wait (ecs);
-      return;
-    }
-
   if (ecs->ws.kind () == TARGET_WAITKIND_NO_RESUMED
       && handle_no_resumed (ecs))
     return;
@@ -5581,7 +5706,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;
     }
@@ -5730,6 +5854,12 @@ handle_inferior_event (struct execution_control_state *ecs)
 	keep_going (ecs);
       return;
 
+    case TARGET_WAITKIND_THREAD_EXITED:
+      if (handle_thread_exited (ecs))
+	return;
+      stop_waiting (ecs);
+      break;
+
     case TARGET_WAITKIND_EXITED:
     case TARGET_WAITKIND_SIGNALLED:
       {
@@ -6175,7 +6305,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 ();
     }
@@ -6221,6 +6351,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,
 				      nullptr);
       if (pending != nullptr)
@@ -8859,6 +8996,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/thread.c b/gdb/thread.c
index d607ad9303a..2c45d528bba 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -401,6 +401,9 @@ thread_info::clear_pending_waitstatus ()
 void
 thread_info::set_thread_options (gdb_thread_options thread_options)
 {
+  gdb_assert (this->state != THREAD_EXITED);
+  gdb_assert (!this->executing ());
+
   if (m_thread_options == thread_options)
     return;
 
-- 
2.36.0


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

* [PATCH 21/31] stop_all_threads: (re-)enable async before waiting for stops
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (19 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 20/31] gdb: clear step over information on thread exit (PR gdb/27338) Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-08 15:49   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 22/31] gdbserver: Queue no-resumed event after thread exit Pedro Alves
                   ` (11 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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 2866962d2dc..31321d758da 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -5011,6 +5011,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 {nullptr, minus_one_ptid, std::move (ws)};
@@ -5269,6 +5271,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
@@ -5395,6 +5474,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] 100+ messages in thread

* [PATCH 22/31] gdbserver: Queue no-resumed event after thread exit
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (20 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 21/31] stop_all_threads: (re-)enable async before waiting for stops Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-08 18:16   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 23/31] Don't resume new threads if scheduler-locking is in effect Pedro Alves
                   ` (10 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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_THREAD_OPTION_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 ebc3bf34127..bf6dc1d995a 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -2963,7 +2963,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 ());
 
@@ -2977,23 +2976,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);
@@ -3004,7 +2987,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);
 
@@ -6166,6 +6149,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 1c1754d2b59..33a14e15173 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 5099db1ee31..77b4b57466d 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 4584e9b3a8e..833e32a4830 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 e2e818b130b..c3345fc67e8 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -320,6 +320,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] 100+ messages in thread

* [PATCH 23/31] Don't resume new threads if scheduler-locking is in effect
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (21 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 22/31] gdbserver: Queue no-resumed event after thread exit Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-08 18:24   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 24/31] Report thread exit event for leader if reporting thread exit events Pedro Alves
                   ` (9 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Eli Zaretskii

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.

Approved-By: Eli Zaretskii <eliz@gnu.org>
Change-Id: Ie12140138b37534b7fc1d904da34f0f174aa11ce
---
 gdb/NEWS                                      |  7 ++
 gdb/doc/gdb.texinfo                           |  4 +-
 gdb/infrun.c                                  | 41 +++++++++---
 .../gdb.threads/schedlock-new-thread.c        | 54 +++++++++++++++
 .../gdb.threads/schedlock-new-thread.exp      | 67 +++++++++++++++++++
 5 files changed, 164 insertions(+), 9 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 c4ccfcc9e32..0aa153b83da 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -15,6 +15,13 @@
   from the current process state.  GDB will show this additional information
   automatically, or through one of the memory-tag subcommands.
 
+* 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 5b566669975..5e75f32e0cd 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -7037,7 +7037,9 @@ the following:
 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.
+Only the current thread may run when the inferior is resumed.  New
+threads created by the resumed thread are held stopped at their entry
+point, before they execute any instruction.
 
 @item step
 Behaves like @code{on} when stepping, and @code{off} otherwise.
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 31321d758da..09391d85256 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;
@@ -1891,7 +1893,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.  */
@@ -2464,9 +2472,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_THREAD_OPTION_CLONE | GDB_THREAD_OPTION_EXIT;
@@ -2475,6 +2488,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))
+	tp->set_thread_options (0);
+      else if (!displaced_step_in_progress_any_thread ())
+	target_thread_events (false);
+    }
 
   /* If we're resuming more than one thread simultaneously, then any
      thread other than the leader is being set to run free.  Clear any
@@ -6103,16 +6123,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..aba32f3c19d
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/schedlock-new-thread.c
@@ -0,0 +1,54 @@
+/* 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)
+{
+#if !SCHEDLOCK
+  while (1)
+    sleep (1);
+#endif
+
+  return NULL;
+}
+
+int
+main (void)
+{
+  pthread_t thread;
+  int ret;
+
+  ret = pthread_create (&thread, NULL, thread_func, NULL); /* set break 1 here */
+  assert (ret == 0);
+
+#if SCHEDLOCK
+  /* 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);
+#else
+  pthread_join (thread, NULL);
+#endif
+
+  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..0a22cd178f2
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/schedlock-new-thread.exp
@@ -0,0 +1,67 @@
+# 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
+
+foreach_with_prefix schedlock {off on} {
+    set sl [expr $schedlock == "on" ? 1 : 0]
+    if { [build_executable "failed to prepare" $testfile-$sl \
+	      $srcfile \
+	      [list debug pthreads additional_flags=-DSCHEDLOCK=$sl]] \
+	     == -1 } {
+	return
+    }
+}
+
+proc test {non-stop schedlock} {
+    save_vars ::GDBFLAGS {
+	append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
+	set sl [expr $schedlock == "on" ? 1 : 0]
+	clean_restart $::binfile-$sl
+    }
+
+    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] 100+ messages in thread

* [PATCH 24/31] Report thread exit event for leader if reporting thread exit events
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (22 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 23/31] Don't resume new threads if scheduler-locking is in effect Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-09 13:11   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 25/31] Ignore failure to read PC when resuming Pedro Alves
                   ` (8 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

If GDB sets the GDB_THREAD_OPTION_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_THREAD_OPTION_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_THREAD_OPTION_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_THREAD_OPTION_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 bf6dc1d995a..c197846810c 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);
@@ -1800,10 +1801,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));
@@ -1872,9 +1875,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
@@ -2333,7 +2346,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
@@ -2646,7 +2659,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)
 	{
@@ -2893,6 +2907,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.
@@ -3012,7 +3037,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_THREAD_OPTION_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",
@@ -3720,8 +3758,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;
@@ -3730,9 +3775,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 33a14e15173..ffbc3c6f095 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -574,8 +574,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] 100+ messages in thread

* [PATCH 25/31] Ignore failure to read PC when resuming
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (23 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 24/31] Report thread exit event for leader if reporting thread exit events Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-10 10:33   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 26/31] gdb/testsuite/lib/my-syscalls.S: Refactor new SYSCALL macro Pedro Alves
                   ` (7 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

If GDB sets a GDB_THREAD_OPTION_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_THREAD_OPTION_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 09391d85256..21e5aa0f50e 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2595,7 +2595,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] 100+ messages in thread

* [PATCH 26/31] gdb/testsuite/lib/my-syscalls.S: Refactor new SYSCALL macro
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (24 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 25/31] Ignore failure to read PC when resuming Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-10 10:33   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 27/31] Testcases for stepping over thread exit syscall (PR gdb/27338) Pedro Alves
                   ` (6 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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 7f06f9c398b..6fb53624f31 100644
--- a/gdb/testsuite/lib/my-syscalls.S
+++ b/gdb/testsuite/lib/my-syscalls.S
@@ -21,38 +21,52 @@
 
 #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)
+
 	.section	.note.GNU-stack,"",@progbits
-- 
2.36.0


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

* [PATCH 27/31] Testcases for stepping over thread exit syscall (PR gdb/27338)
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (25 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 26/31] gdb/testsuite/lib/my-syscalls.S: Refactor new SYSCALL macro Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-12  9:53   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 28/31] Document remote clone events, and QThreadOptions packet Pedro Alves
                   ` (5 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 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     | 126 ++++++++++++++++++
 gdb/testsuite/lib/my-syscalls.S               |   4 +
 gdb/testsuite/lib/my-syscalls.h               |   5 +
 6 files changed, 333 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..ed8534cf518
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
@@ -0,0 +1,126 @@
+# 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
+		}
+
+		if {${non-stop}} {
+		    gdb_test "thread $thread" "Switching to thread .*" \
+			"switch to event 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 6fb53624f31..ff62b5eb4e9 100644
--- a/gdb/testsuite/lib/my-syscalls.S
+++ b/gdb/testsuite/lib/my-syscalls.S
@@ -69,4 +69,8 @@ NAME ## _syscall:		;\
 
 SYSCALL (my_execve, __NR_execve)
 
+/* void my_exit (int code);  */
+
+SYSCALL (my_exit, __NR_exit)
+
 	.section	.note.GNU-stack,"",@progbits
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] 100+ messages in thread

* [PATCH 28/31] Document remote clone events, and QThreadOptions packet
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (26 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 27/31] Testcases for stepping over thread exit syscall (PR gdb/27338) Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-05 15:53   ` Andrew Burgess
  2023-06-12 12:06   ` Andrew Burgess
  2022-12-12 20:30 ` [PATCH 29/31] inferior::clear_thread_list always silent Pedro Alves
                   ` (4 subsequent siblings)
  32 siblings, 2 replies; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Eli Zaretskii

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.

Approved-By: Eli Zaretskii <eliz@gnu.org>
Change-Id: Ic1c8de1fefba95729bbd242969284265de42427e
---
 gdb/NEWS            |  20 +++++++
 gdb/doc/gdb.texinfo | 127 ++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 144 insertions(+), 3 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index 0aa153b83da..b1d3dd7e7d9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -160,6 +160,10 @@ set style tui-current-position [on|off]
   Whether to style the source and assembly code highlighted by the
   TUI's current position indicator.  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
 
 document user-defined
@@ -285,6 +289,22 @@ GNU/Linux/CSKY (gdbserver) csky*-*linux*
 
 GDB now supports floating-point on LoongArch GNU/Linux.
 
+* New remote packets
+
+New stop reason: clone
+  Indicates that a clone system call was executed.
+
+QThreadOptions
+  Enable/disable optional event reporting, on a per-thread basis.
+  Currently supported options are GDB_THREAD_OPTION_CLONE, to enable
+  clone event reporting, and GDB_THREAD_OPTION_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 5e75f32e0cd..ef62fac366f 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -24079,6 +24079,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.
@@ -42110,6 +42114,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, as specified in @ref{thread-id
+syntax}.  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
@@ -42148,9 +42163,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
@@ -42875,6 +42891,11 @@ 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/disables 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
@@ -42891,6 +42912,94 @@ 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 last @var{options} in the list 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_THREAD_OPTION_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_THREAD_OPTION_EXIT (0x2)
+Report thread exit events (@pxref{thread exit event}).
+@end table
+
+@noindent
+
+For example, @value{GDBN} enables the @code{GDB_THREAD_OPTION_EXIT}
+and @code{GDB_THREAD_OPTION_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_THREAD_OPTION_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_THREAD_OPTION_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
+
+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
@@ -43336,6 +43445,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{-}
@@ -43557,6 +43671,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] 100+ messages in thread

* [PATCH 29/31] inferior::clear_thread_list always silent
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (27 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 28/31] Document remote clone events, and QThreadOptions packet Pedro Alves
@ 2022-12-12 20:30 ` Pedro Alves
  2023-06-12 12:20   ` Andrew Burgess
  2022-12-12 20:31 ` [PATCH 30/31] Centralize "[Thread ...exited]" notifications Pedro Alves
                   ` (3 subsequent siblings)
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:30 UTC (permalink / raw)
  To: gdb-patches

Now that the MI mi_thread_exit observer ignores "silent", there's no
difference between inferior::clean_thread_list with silent true or
false.  And for the CLI, clean_thread_list() never printed anything
either.  So we can eliminate the 'silent' parameter.

Change-Id: I90ff9f07537d22ea70986fbcfe8819cac7e06613
---
 gdb/inferior.c | 12 ++++++------
 gdb/inferior.h |  5 ++---
 gdb/thread.c   |  2 +-
 3 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/gdb/inferior.c b/gdb/inferior.c
index 23cbfd63bde..eacb65ec1d7 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -168,13 +168,13 @@ add_inferior (int pid)
 /* See inferior.h.  */
 
 void
-inferior::clear_thread_list (bool silent)
+inferior::clear_thread_list ()
 {
   thread_list.clear_and_dispose ([=] (thread_info *thr)
     {
-      threads_debug_printf ("deleting thread %s, silent = %d",
-			    thr->ptid.to_string ().c_str (), silent);
-      set_thread_exited (thr, silent);
+      threads_debug_printf ("deleting thread %s",
+			    thr->ptid.to_string ().c_str ());
+      set_thread_exited (thr, true);
       if (thr->deletable ())
 	delete thr;
     });
@@ -184,7 +184,7 @@ inferior::clear_thread_list (bool silent)
 void
 delete_inferior (struct inferior *inf)
 {
-  inf->clear_thread_list (true);
+  inf->clear_thread_list ();
 
   auto it = inferior_list.iterator_to (*inf);
   inferior_list.erase (it);
@@ -204,7 +204,7 @@ delete_inferior (struct inferior *inf)
 static void
 exit_inferior_1 (struct inferior *inf, int silent)
 {
-  inf->clear_thread_list (silent);
+  inf->clear_thread_list ();
 
   gdb::observers::inferior_exit.notify (inf);
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 69525a2e053..07d9527a802 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -425,9 +425,8 @@ class inferior : public refcounted_object,
   inline safe_inf_threads_range threads_safe ()
   { return safe_inf_threads_range (this->thread_list.begin ()); }
 
-  /* Delete all threads in the thread list.  If SILENT, exit threads
-     silently.  */
-  void clear_thread_list (bool silent);
+  /* Delete all threads in the thread list, silently.  */
+  void clear_thread_list ();
 
   /* Continuations-related methods.  A continuation is an std::function
      to be called to finish the execution of a command when running
diff --git a/gdb/thread.c b/gdb/thread.c
index 2c45d528bba..2ca3a867d8c 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -235,7 +235,7 @@ init_thread_list (void)
   highest_thread_num = 0;
 
   for (inferior *inf : all_inferiors ())
-    inf->clear_thread_list (true);
+    inf->clear_thread_list ();
 }
 
 /* Allocate a new thread of inferior INF with target id PTID and add
-- 
2.36.0


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

* [PATCH 30/31] Centralize "[Thread ...exited]" notifications
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (28 preceding siblings ...)
  2022-12-12 20:30 ` [PATCH 29/31] inferior::clear_thread_list always silent Pedro Alves
@ 2022-12-12 20:31 ` Pedro Alves
  2023-02-04 16:05   ` Andrew Burgess
  2023-02-16 15:40   ` Andrew Burgess
  2022-12-12 20:31 ` [PATCH 31/31] Cancel execution command on thread exit, when stepping, nexting, etc Pedro Alves
                   ` (2 subsequent siblings)
  32 siblings, 2 replies; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:31 UTC (permalink / raw)
  To: gdb-patches

Currently, each target backend is responsible for printing "[Thread
...exited]" before deleting a thread.  This leads to unnecessary
differences between targets, like e.g. with the remote target, we
never print such messages, even though we do print "[New Thread ...]".

E.g., debugging the gdb.threads/attach-many-short-lived-threads.exp
with gdbserver, letting it run for a bit, and then pressing Ctrl-C, we
currently see:

 (gdb) c
 Continuing.
 ^C[New Thread 3850398.3887449]
 [New Thread 3850398.3887500]
 [New Thread 3850398.3887551]
 [New Thread 3850398.3887602]
 [New Thread 3850398.3887653]
 ...

 Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
 0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
     at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
 78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
 (gdb)

Above, we only see "New Thread" notifications, even though threads
were deleted.

After this patch, we'll see:

 (gdb) c
 Continuing.
 ^C[Thread 3558643.3577053 exited]
 [Thread 3558643.3577104 exited]
 [Thread 3558643.3577155 exited]
 [Thread 3558643.3579603 exited]
 ...
 [New Thread 3558643.3597415]
 [New Thread 3558643.3600015]
 [New Thread 3558643.3599965]
 ...

 Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
 0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
     at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
 78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
 (gdb) q


This commit fixes this by moving the thread exit printing to common
code instead, triggered from within delete_thread (or rather,
set_thread_exited).

There's one wrinkle, though.  While most targest want to print:

 [Thread ... exited]

the Windows target wants to print:

 [Thread ... exited with code <exit_code>]

... and sometimes wants to suppress the notification for the main
thread.  To address that, this commits adds a delete_thread_with_code
function, only used by that target (so far).

Change-Id: I06ec07b7c51527872a9713dd11cf7867b50fc5ff
---
 gdb/annotate.c           |  4 +++-
 gdb/breakpoint.c         |  4 +++-
 gdb/fbsd-nat.c           |  3 ---
 gdb/gdbthread.h          | 22 +++++++++++++----
 gdb/inferior.c           |  2 +-
 gdb/inferior.h           |  2 ++
 gdb/linux-nat.c          | 11 +++------
 gdb/mi/mi-interp.c       |  8 +++++--
 gdb/netbsd-nat.c         |  4 ----
 gdb/observable.h         | 11 +++++----
 gdb/procfs.c             |  6 -----
 gdb/python/py-inferior.c |  4 +++-
 gdb/thread.c             | 51 ++++++++++++++++++++++++++++++----------
 gdb/windows-nat.c        | 16 ++++---------
 14 files changed, 89 insertions(+), 59 deletions(-)

diff --git a/gdb/annotate.c b/gdb/annotate.c
index 33805dcdb30..b45384ddb15 100644
--- a/gdb/annotate.c
+++ b/gdb/annotate.c
@@ -233,7 +233,9 @@ annotate_thread_changed (void)
 /* Emit notification on thread exit.  */
 
 static void
-annotate_thread_exited (struct thread_info *t, int silent)
+annotate_thread_exited (thread_info *t,
+			gdb::optional<ULONGEST> exit_code,
+			bool /* silent */)
 {
   if (annotation_level > 1)
     {
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index f0276a963c0..0736231e470 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -3237,7 +3237,9 @@ remove_breakpoints (void)
    that thread.  */
 
 static void
-remove_threaded_breakpoints (struct thread_info *tp, int silent)
+remove_threaded_breakpoints (thread_info *tp,
+			     gdb::optional<ULONGEST> exit_code,
+			     bool /* silent */)
 {
   for (breakpoint *b : all_breakpoints_safe ())
     {
diff --git a/gdb/fbsd-nat.c b/gdb/fbsd-nat.c
index 1aec75050ae..3d1e742f4e3 100644
--- a/gdb/fbsd-nat.c
+++ b/gdb/fbsd-nat.c
@@ -1300,9 +1300,6 @@ fbsd_nat_target::wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
 		{
 		  fbsd_lwp_debug_printf ("deleting thread for LWP %u",
 					 pl.pl_lwpid);
-		  if (print_thread_events)
-		    gdb_printf (_("[%s exited]\n"),
-				target_pid_to_str (wptid).c_str ());
 		  low_delete_thread (thr);
 		  delete_thread (thr);
 		}
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 43e9d6ea484..7ab02873f17 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -636,16 +636,30 @@ extern struct thread_info *add_thread_with_info (process_stratum_target *targ,
 
 /* Delete thread THREAD and notify of thread exit.  If the thread is
    currently not deletable, don't actually delete it but still tag it
-   as exited and do the notification.  */
-extern void delete_thread (struct thread_info *thread);
+   as exited and do the notification.  EXIT_CODE is the thread's exit
+   code.  If SILENT, don't actually notify the CLI.  THREAD must not
+   be NULL or an assertion will fail.  */
+extern void delete_thread_with_exit_code (thread_info *thread,
+					  ULONGEST exit_code,
+					  bool silent = false);
+
+/* Delete thread THREAD and notify of thread exit.  If the thread is
+   currently not deletable, don't actually delete it but still tag it
+   as exited and do the notification.  THREAD must not be NULL or an
+   assertion will fail.  */
+extern void delete_thread (thread_info *thread);
 
 /* Like delete_thread, but be quiet about it.  Used when the process
    this thread belonged to has already exited, for example.  */
 extern void delete_thread_silent (struct thread_info *thread);
 
 /* Mark the thread exited, but don't delete it or remove it from the
-   inferior thread list.  */
-extern void set_thread_exited (thread_info *tp, bool silent);
+   inferior thread list.  EXIT_CODE is the thread's exit code, if
+   available.  If SILENT, then don't inform the CLI about the
+   exit.  */
+extern void set_thread_exited (thread_info *tp,
+			       gdb::optional<ULONGEST> exit_code = {},
+			       bool silent = false);
 
 /* Delete a step_resume_breakpoint from the thread database.  */
 extern void delete_step_resume_breakpoint (struct thread_info *);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index eacb65ec1d7..834eabdf2ca 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -174,7 +174,7 @@ inferior::clear_thread_list ()
     {
       threads_debug_printf ("deleting thread %s",
 			    thr->ptid.to_string ().c_str ());
-      set_thread_exited (thr, true);
+      set_thread_exited (thr, {}, true);
       if (thr->deletable ())
 	delete thr;
     });
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 07d9527a802..cf3f1275cc1 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -628,6 +628,8 @@ extern void detach_inferior (inferior *inf);
 
 extern void exit_inferior (inferior *inf);
 
+/* Like exit_inferior, but be quiet -- don't announce the exit of the
+   inferior's threads to the CLI.  */
 extern void exit_inferior_silent (inferior *inf);
 
 extern void exit_inferior_num_silent (int num);
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 75f81edf20a..acf5fd3f1b1 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -916,15 +916,10 @@ linux_nat_switch_fork (ptid_t new_ptid)
 static void
 exit_lwp (struct lwp_info *lp, bool del_thread = true)
 {
-  struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
-
-  if (th)
+  if (del_thread)
     {
-      if (print_thread_events)
-	gdb_printf (_("[%s exited]\n"),
-		    target_pid_to_str (lp->ptid).c_str ());
-
-      if (del_thread)
+      thread_info *th = find_thread_ptid (linux_target, lp->ptid);
+      if (th != nullptr)
 	delete_thread (th);
     }
 
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index 3cc2462f672..189dd1f302f 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -68,7 +68,9 @@ static void mi_on_normal_stop (struct bpstat *bs, int print_frame);
 static void mi_on_no_history (void);
 
 static void mi_new_thread (struct thread_info *t);
-static void mi_thread_exit (struct thread_info *t, int silent);
+static void mi_thread_exit (thread_info *t,
+			    gdb::optional<ULONGEST> exit_code,
+			    bool silent);
 static void mi_record_changed (struct inferior*, int, const char *,
 			       const char *);
 static void mi_inferior_added (struct inferior *inf);
@@ -351,8 +353,10 @@ mi_new_thread (struct thread_info *t)
     }
 }
 
+/* Observer for the thread_exit notification.  */
+
 static void
-mi_thread_exit (struct thread_info *t, int silent)
+mi_thread_exit (thread_info *t, gdb::optional<ULONGEST> exit_code, bool silent)
 {
   SWITCH_THRU_ALL_UIS ()
     {
diff --git a/gdb/netbsd-nat.c b/gdb/netbsd-nat.c
index aa16a6cc5bd..9674baeb846 100644
--- a/gdb/netbsd-nat.c
+++ b/gdb/netbsd-nat.c
@@ -625,10 +625,6 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
 	{
 	  /* NetBSD does not store an LWP exit status.  */
 	  ourstatus->set_thread_exited (0);
-
-	  if (print_thread_events)
-	    gdb_printf (_("[%s exited]\n"),
-			target_pid_to_str (wptid).c_str ());
 	}
 
       /* The GDB core expects that the rest of the threads are running.  */
diff --git a/gdb/observable.h b/gdb/observable.h
index 1103c5c98a6..a4ab4f1e38f 100644
--- a/gdb/observable.h
+++ b/gdb/observable.h
@@ -126,10 +126,13 @@ extern observable<struct objfile */* objfile */> free_objfile;
 /* The thread specified by T has been created.  */
 extern observable<struct thread_info */* t */> new_thread;
 
-/* The thread specified by T has exited.  The SILENT argument
-   indicates that gdb is removing the thread from its tables without
-   wanting to notify the user about it.  */
-extern observable<struct thread_info */* t */, int /* silent */> thread_exit;
+/* The thread specified by T has exited.  EXIT_CODE is the thread's
+   exit code, if available.  The SILENT argument indicates that GDB is
+   removing the thread from its tables without wanting to notify the
+   CLI about it.  */
+extern observable<thread_info */* t */,
+		  gdb::optional<ULONGEST> /* exit_code */,
+		  bool /* silent */> thread_exit;
 
 /* An explicit stop request was issued to PTID.  If PTID equals
    minus_one_ptid, the request applied to all threads.  If
diff --git a/gdb/procfs.c b/gdb/procfs.c
index ffc26c8fb9e..7d0e6e9a4c9 100644
--- a/gdb/procfs.c
+++ b/gdb/procfs.c
@@ -2115,9 +2115,6 @@ procfs_target::wait (ptid_t ptid, struct target_waitstatus *status,
 	      case PR_SYSENTRY:
 		if (what == SYS_lwp_exit)
 		  {
-		    if (print_thread_events)
-		      gdb_printf (_("[%s exited]\n"),
-				  target_pid_to_str (retval).c_str ());
 		    delete_thread (find_thread_ptid (this, retval));
 		    target_continue_no_signal (ptid);
 		    goto wait_again;
@@ -2222,9 +2219,6 @@ procfs_target::wait (ptid_t ptid, struct target_waitstatus *status,
 		  }
 		else if (what == SYS_lwp_exit)
 		  {
-		    if (print_thread_events)
-		      gdb_printf (_("[%s exited]\n"),
-				  target_pid_to_str (retval).c_str ());
 		    delete_thread (find_thread_ptid (this, retval));
 		    status->set_spurious ();
 		    return retval;
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index 4d5e09db680..be5597c4a2e 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -360,7 +360,9 @@ add_thread_object (struct thread_info *tp)
 }
 
 static void
-delete_thread_object (struct thread_info *tp, int ignore)
+delete_thread_object (thread_info *tp,
+		      gdb::optional<ULONGEST> /* exit_code */,
+		      bool /* silent */)
 {
   if (!gdb_python_initialized)
     return;
diff --git a/gdb/thread.c b/gdb/thread.c
index 2ca3a867d8c..7ab30562fd3 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -192,7 +192,8 @@ clear_thread_inferior_resources (struct thread_info *tp)
 /* See gdbthread.h.  */
 
 void
-set_thread_exited (thread_info *tp, bool silent)
+set_thread_exited (thread_info *tp, gdb::optional<ULONGEST> exit_code,
+		   bool silent)
 {
   /* Dead threads don't need to step-over.  Remove from chain.  */
   if (thread_is_in_step_over_chain (tp))
@@ -211,7 +212,22 @@ set_thread_exited (thread_info *tp, bool silent)
       if (proc_target != nullptr)
 	proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
 
-      gdb::observers::thread_exit.notify (tp, silent);
+      if (!silent && print_thread_events)
+	{
+	  if (exit_code.has_value ())
+	    {
+	      gdb_printf (_("[%s exited with code %s]\n"),
+			  target_pid_to_str (tp->ptid).c_str (),
+			  pulongest (*exit_code));
+	    }
+	  else
+	    {
+	      gdb_printf (_("[%s exited]\n"),
+			  target_pid_to_str (tp->ptid).c_str ());
+	    }
+	}
+
+      gdb::observers::thread_exit.notify (tp, exit_code, silent);
 
       /* Tag it as exited.  */
       tp->state = THREAD_EXITED;
@@ -468,20 +484,22 @@ global_thread_step_over_chain_remove (struct thread_info *tp)
   global_thread_step_over_list.erase (it);
 }
 
-/* Delete the thread referenced by THR.  If SILENT, don't notify
-   the observer of this exit.
-   
-   THR must not be NULL or a failed assertion will be raised.  */
+/* Helper for the different delete_thread variants.  */
 
 static void
-delete_thread_1 (thread_info *thr, bool silent)
+delete_thread_1 (thread_info *thr, gdb::optional<ULONGEST> exit_code,
+		 bool silent)
 {
   gdb_assert (thr != nullptr);
 
-  threads_debug_printf ("deleting thread %s, silent = %d",
-			thr->ptid.to_string ().c_str (), silent);
+  threads_debug_printf ("deleting thread %s, exit_code = %s, silent = %d",
+			thr->ptid.to_string ().c_str (),
+			(exit_code.has_value ()
+			 ? pulongest (*exit_code)
+			 : "<none>"),
+			silent);
 
-  set_thread_exited (thr, silent);
+  set_thread_exited (thr, exit_code, silent);
 
   if (!thr->deletable ())
     {
@@ -497,16 +515,25 @@ delete_thread_1 (thread_info *thr, bool silent)
 
 /* See gdbthread.h.  */
 
+void
+delete_thread_with_exit_code (thread_info *thread, ULONGEST exit_code,
+			      bool silent)
+{
+  delete_thread_1 (thread, exit_code, false /* not silent */);
+}
+
+/* See gdbthread.h.  */
+
 void
 delete_thread (thread_info *thread)
 {
-  delete_thread_1 (thread, false /* not silent */);
+  delete_thread_1 (thread, {}, false /* not silent */);
 }
 
 void
 delete_thread_silent (thread_info *thread)
 {
-  delete_thread_1 (thread, true /* silent */);
+  delete_thread_1 (thread, {}, true /* not silent */);
 }
 
 struct thread_info *
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index ee4e78bdabf..2764fc694b3 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -611,21 +611,13 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
 
   id = ptid.lwp ();
 
-  /* Emit a notification about the thread being deleted.
-
-     Note that no notification was printed when the main thread
+  /* Note that no notification was printed when the main thread
      was created, and thus, unless in verbose mode, we should be
      symmetrical, and avoid that notification for the main thread
      here as well.  */
-
-  if (info_verbose)
-    gdb_printf ("[Deleting %s]\n", target_pid_to_str (ptid).c_str ());
-  else if (print_thread_events && !main_thread_p)
-    gdb_printf (_("[%s exited with code %u]\n"),
-		target_pid_to_str (ptid).c_str (),
-		(unsigned) exit_code);
-
-  ::delete_thread (find_thread_ptid (this, ptid));
+  bool silent = (main_thread_p && !info_verbose);
+  thread_info *todel = find_thread_ptid (this, ptid);
+  delete_thread_with_exit_code (todel, exit_code, silent);
 
   auto iter = std::find_if (windows_process.thread_list.begin (),
 			    windows_process.thread_list.end (),
-- 
2.36.0


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

* [PATCH 31/31] Cancel execution command on thread exit, when stepping, nexting, etc.
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (29 preceding siblings ...)
  2022-12-12 20:31 ` [PATCH 30/31] Centralize "[Thread ...exited]" notifications Pedro Alves
@ 2022-12-12 20:31 ` Pedro Alves
  2023-06-12 13:12   ` Andrew Burgess
  2023-01-24 19:47 ` [PATCH v3 00/31] Step over thread clone and thread exit Pedro Alves
  2023-11-13 14:24 ` [PATCH " Pedro Alves
  32 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2022-12-12 20:31 UTC (permalink / raw)
  To: gdb-patches

If your target has no support for TARGET_WAITKIND_NO_RESUMED events
(and no way to support them, such as the yet-unsubmitted AMDGPU
target), and you step over thread exit with scheduler-locking on, this
is what you get:

 (gdb) n
 [Thread ... exited]
 *hang*

Getting back the prompt by typing Ctrl-C may not even work, since no
inferior thread is running to receive the SIGINT.  Even if it works,
it seems unnecessarily harsh.  If you started an execution command for
which there's a clear thread of interest (step, next, until, etc.),
and that thread disappears, then I think it's more user friendly if
GDB just detects the situation and aborts the command, giving back the
prompt.

That is what this commit implements.  It does this by explicitly
requesting the target to report thread exit events whenever the main
resumed thread has a thread_fsm.  Note that unlike stepping over a
breakpoint, we don't need to enable clone events in this case.

With this patch, we get:

 (gdb) n
 [Thread 0x7ffff7d89700 (LWP 3961883) exited]
 Command aborted, thread exited.
 (gdb)

Change-Id: I901ab64c91d10830590b2dac217b5264635a2b95
---
 gdb/infrun.c                                  | 73 ++++++++++++++---
 .../gdb.threads/step-over-thread-exit.exp     | 79 ++++++++++++-------
 2 files changed, 115 insertions(+), 37 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 21e5aa0f50e..61d2a14c646 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1886,6 +1886,22 @@ displaced_step_prepare (thread_info *thread)
   return status;
 }
 
+/* True if any thread of TARGET that matches RESUME_PTID requires
+   target_thread_events enabled.  This assumes TARGET does not support
+   target thread options.  */
+
+static bool
+any_thread_needs_target_thread_events (process_stratum_target *target,
+				       ptid_t resume_ptid)
+{
+  for (thread_info *tp : all_non_exited_threads (target, resume_ptid))
+    if (displaced_step_in_progress_thread (tp)
+	|| schedlock_applies (tp)
+	|| tp->thread_fsm () != nullptr)
+      return true;
+  return false;
+}
+
 /* Maybe disable thread-{cloned,created,exited} event reporting after
    a step-over (either in-line or displaced) finishes.  */
 
@@ -1909,9 +1925,10 @@ update_thread_events_after_step_over (thread_info *event_thread,
   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 ())
+	 setting.  Disable it, but only if other threads in the target
+	 don't need it enabled.  */
+      process_stratum_target *target = event_thread->inf->process_target ();
+      if (!any_thread_needs_target_thread_events (target, minus_one_ptid))
 	target_thread_events (false);
     }
 }
@@ -2488,12 +2505,25 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
       else
 	target_thread_events (true);
     }
+  else if (tp->thread_fsm () != nullptr)
+    {
+      gdb_thread_options options = GDB_THREAD_OPTION_EXIT;
+      if (target_supports_set_thread_options (options))
+	tp->set_thread_options (options);
+      else
+	target_thread_events (true);
+    }
   else
     {
       if (target_supports_set_thread_options (0))
 	tp->set_thread_options (0);
-      else if (!displaced_step_in_progress_any_thread ())
-	target_thread_events (false);
+      else
+	{
+	  process_stratum_target *resume_target = tp->inf->process_target ();
+	  if (!any_thread_needs_target_thread_events (resume_target,
+						      resume_ptid))
+	    target_thread_events (false);
+	}
     }
 
   /* If we're resuming more than one thread simultaneously, then any
@@ -5671,6 +5701,13 @@ handle_thread_exited (execution_control_state *ecs)
   ecs->event_thread->stepping_over_breakpoint = 0;
   ecs->event_thread->stepping_over_watchpoint = 0;
 
+  /* If the thread had an FSM, then abort the command.  But only after
+     finishing the step over, as in non-stop mode, aborting this
+     thread's command should not interfere with other threads.  We
+     must check this before finish_step over, however, which may
+     update the thread list and delete the event thread.  */
+  bool abort_cmd = (ecs->event_thread->thread_fsm () != nullptr);
+
   /* Maybe the thread was doing a step-over, if so release
      resources and start any further pending step-overs.
 
@@ -5684,6 +5721,13 @@ handle_thread_exited (execution_control_state *ecs)
      the event thread has exited.  */
   gdb_assert (ret == 0);
 
+  if (abort_cmd)
+    {
+      delete_thread (ecs->event_thread);
+      ecs->event_thread = nullptr;
+      return false;
+    }
+
   /* If finish_step_over started a new in-line step-over, don't
      try to restart anything else.  */
   if (step_over_info_valid_p ())
@@ -9062,7 +9106,8 @@ normal_stop (void)
       if (inferior_ptid != null_ptid)
 	finish_ptid = ptid_t (inferior_ptid.pid ());
     }
-  else if (last.kind () != TARGET_WAITKIND_NO_RESUMED)
+  else if (last.kind () != TARGET_WAITKIND_NO_RESUMED
+	   && last.kind () != TARGET_WAITKIND_THREAD_EXITED)
     finish_ptid = inferior_ptid;
 
   gdb::optional<scoped_finish_thread_state> maybe_finish_thread_state;
@@ -9105,7 +9150,8 @@ normal_stop (void)
     {
       if ((last.kind () != TARGET_WAITKIND_SIGNALLED
 	   && last.kind () != TARGET_WAITKIND_EXITED
-	   && last.kind () != TARGET_WAITKIND_NO_RESUMED)
+	   && last.kind () != TARGET_WAITKIND_NO_RESUMED
+	   && last.kind () != TARGET_WAITKIND_THREAD_EXITED)
 	  && target_has_execution ()
 	  && previous_thread != inferior_thread ())
 	{
@@ -9121,7 +9167,8 @@ normal_stop (void)
       update_previous_thread ();
     }
 
-  if (last.kind () == TARGET_WAITKIND_NO_RESUMED)
+  if (last.kind () == TARGET_WAITKIND_NO_RESUMED
+      || last.kind () == TARGET_WAITKIND_THREAD_EXITED)
     {
       stop_print_frame = false;
 
@@ -9129,7 +9176,12 @@ normal_stop (void)
 	if (current_ui->prompt_state == PROMPT_BLOCKED)
 	  {
 	    target_terminal::ours_for_output ();
-	    gdb_printf (_("No unwaited-for children left.\n"));
+	    if (last.kind () == TARGET_WAITKIND_NO_RESUMED)
+	      gdb_printf (_("No unwaited-for children left.\n"));
+	    else if (last.kind () == TARGET_WAITKIND_THREAD_EXITED)
+	      gdb_printf (_("Command aborted, thread exited.\n"));
+	    else
+	      gdb_assert_not_reached ("unhandled");
 	  }
     }
 
@@ -9214,7 +9266,8 @@ normal_stop (void)
     {
       if (last.kind () != TARGET_WAITKIND_SIGNALLED
 	  && last.kind () != TARGET_WAITKIND_EXITED
-	  && last.kind () != TARGET_WAITKIND_NO_RESUMED)
+	  && last.kind () != TARGET_WAITKIND_NO_RESUMED
+	  && last.kind () != TARGET_WAITKIND_THREAD_EXITED)
 	/* Delete the breakpoint we stopped at, if it wants to be deleted.
 	   Delete any breakpoint that is to be deleted at the next stop.  */
 	breakpoint_auto_delete (inferior_thread ()->control.stop_bpstat);
diff --git a/gdb/testsuite/gdb.threads/step-over-thread-exit.exp b/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
index ed8534cf518..a0056740478 100644
--- a/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
+++ b/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
@@ -29,7 +29,7 @@ if { [build_executable "failed to prepare" $testfile \
 # 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} {
+proc test {displaced-stepping non-stop target-non-stop schedlock cmd ns_stop_all} {
     if {${non-stop} == "off" && $ns_stop_all} {
 	error "invalid arguments"
     }
@@ -72,31 +72,54 @@ proc test {displaced-stepping non-stop target-non-stop schedlock ns_stop_all} {
 
 	gdb_test_no_output "set scheduler-locking ${schedlock}"
 
-	gdb_test "continue" \
-	    "No unwaited-for children left." \
-	    "continue stops when thread exits"
+	if {$cmd == "continue"} {
+	    gdb_test "continue" \
+		"No unwaited-for children left." \
+		"continue stops when thread exits"
+	} else {
+	    gdb_test $cmd \
+		"Command aborted, thread exited\\." \
+		"command aborts 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
+	if {$cmd != "continue"} {
+	    set thread "<unknown>"
+	    gdb_test_multiple "continue" "" {
+		-re -wrap "Thread ($::decimal) .*hit Breakpoint $::decimal.* my_exit_syscall .*" {
+		    set thread $expect_out(1,string)
 		}
+	    }
+	    if {${non-stop}} {
+		gdb_test -nopass "thread $thread" "Switching to thread .*" \
+		    "switch to event thread"
+	    }
 
-		if {${non-stop}} {
-		    gdb_test "thread $thread" "Switching to thread .*" \
-			"switch to event thread"
+	    gdb_test $cmd \
+		"Command aborted, thread exited\\." \
+		"command aborts when thread exits"
+	} else {
+	    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
+		    }
+
+		    if {${non-stop}} {
+			gdb_test -nopass "thread $thread" "Switching to thread .*" \
+			    "switch to event thread"
+		    }
 		}
 	    }
 	}
@@ -112,13 +135,15 @@ foreach_with_prefix displaced-stepping {off auto} {
 	    }
 
 	    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}
+		foreach_with_prefix cmd {"next" "continue"} {
+		    if {${non-stop} == "on"} {
+			foreach_with_prefix ns_stop_all {0 1} {
+			    test ${displaced-stepping} ${non-stop} ${target-non-stop} \
+				${schedlock} ${cmd} ${ns_stop_all}
+			}
+		    } else {
+			test ${displaced-stepping} ${non-stop} ${target-non-stop} ${schedlock} ${cmd} 0
 		    }
-		} else {
-		    test ${displaced-stepping} ${non-stop} ${target-non-stop} ${schedlock} 0
 		}
 	    }
 	}
-- 
2.36.0


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

* Re: [PATCH v3 00/31] Step over thread clone and thread exit
  2022-12-12 20:30 [PATCH 00/31] Step over thread clone and thread exit Pedro Alves
                   ` (30 preceding siblings ...)
  2022-12-12 20:31 ` [PATCH 31/31] Cancel execution command on thread exit, when stepping, nexting, etc Pedro Alves
@ 2023-01-24 19:47 ` Pedro Alves
  2023-11-13 14:24 ` [PATCH " Pedro Alves
  32 siblings, 0 replies; 100+ messages in thread
From: Pedro Alves @ 2023-01-24 19:47 UTC (permalink / raw)
  To: gdb-patches

(Slightly fixing subject, I had forgotten to include "v3" there when I posted this.)

I had planned to wait until the branch was cut before considering putting this in, and meanwhile
it's been a few weeks since that has happened.  So I am considering doing that (putting this in).
I'll wait a bit more in case someone wants to chime in.

Pedro Alves

On 2022-12-12 8:30 p.m., Pedro Alves wrote:
> Here's v3 of the series I previously posted here:
> 
>   https://inbox.sourceware.org/gdb-patches/20220713222433.374898-1-pedro@palves.net/
> 
> New in v3:
> 
>   - Addressed Simon's comments throughout.
> 
>   - Addressed Tom de Vries' comments: now builds with
>     --enable-targets=all, and the testcases should work with no glibc
>     debug info.
> 
>   - There are a few new patches:
> 
>       - [PATCH 07/31] enum_flags to_string
> 
>       Already approved by Simon.  The patches that introduce the new
>       GDB_THREAD_OPTION_XXX option flags make use of this now to
>       pretty-print the flags.
> 
>       - [PATCH 16/31] Move deleting thread on TARGET_WAITKIND_THREAD_EXITED to core
> 
>       This was half done in another patch previously.  In v3, it's
>       been moved to a separate preparatory patch, and in all-stop, we
>       now delete threads when pending-exits just before we report a
>       stop to the user.
> 
>       - [PATCH 31/31] Cancel execution command on thread exit, when stepping, nexting, etc.
> 
>       The testcase was augmented to better cover all scenarios.
> 
>   - This series now applies on top of this other series I posted last
>     week:
> 
>     [PATCH 0/6] Eliminate infrun_thread_thread_exit observer
>     https://inbox.sourceware.org/gdb-patches/20221203211338.2264994-1-pedro@palves.net/T/#t
> 
> For your convenience, I've pushed this to the
> users/palves/step-over-thread-exit-v3 branch.
> 
> Here's the series description, updated for v3:
> 
> 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 involves 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 alluded 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.
> 
> - Centralizes "[Thread ...exited]" notifications in core code.
> 
> - Cancels next/step/until/etc. commands on thread exit event, like so:
> 
>      (gdb) n
>      [Thread 0x7ffff7d89700 (LWP 3961883) exited]
>      Command aborted, thread exited.
>      (gdb)
> 
> There are documentation changes in the following patches:
> 
>    [PATCH 23/31] Don't resume new threads if scheduler-locking is in effect
>    [PATCH 28/31] Document remote clone events, and QThreadOptions packet
> 
> ... and they have both already been approved by Eli, in v2.  (They
> haven't changed in v3).
> 
> Tested on x86-64 Ubuntu 20.04, native and gdbserver.
> 
> Andrew Burgess (1):
>   Add test for stepping over clone syscall
> 
> Pedro Alves (29):
>   displaced step: pass down target_waitstatus instead of gdb_signal
>   linux-nat: introduce pending_status_str
>   gdb/linux: Delete all other LWPs immediately on ptrace exec event
>   Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED
>   Support clone events in the remote protocol
>   Avoid duplicate QThreadEvents packets
>   enum_flags to_string
>   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
>   gdbserver/linux-low.cc: Ignore event_ptid if TARGET_WAITKIND_IGNORE
>   Move deleting thread on TARGET_WAITKIND_THREAD_EXITED to core
>   Introduce GDB_THREAD_OPTION_EXIT thread option, fix
>     step-over-thread-exit
>   Implement GDB_THREAD_OPTION_EXIT support for Linux GDBserver
>   Implement GDB_THREAD_OPTION_EXIT support for native Linux
>   gdb: clear step over information on thread exit (PR gdb/27338)
>   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
>   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
>   inferior::clear_thread_list always silent
>   Centralize "[Thread ...exited]" notifications
>   Cancel execution command on thread exit, when stepping, nexting, etc.
> 
> Simon Marchi (1):
>   Testcases for stepping over thread exit syscall (PR gdb/27338)
> 
>  gdb/NEWS                                      |  27 +
>  gdb/annotate.c                                |   4 +-
>  gdb/breakpoint.c                              |   4 +-
>  gdb/displaced-stepping.c                      |  18 +-
>  gdb/displaced-stepping.h                      |   2 +-
>  gdb/doc/gdb.texinfo                           | 131 +++-
>  gdb/fbsd-nat.c                                |   3 -
>  gdb/gdbarch-components.py                     |   6 +-
>  gdb/gdbarch-gen.h                             |  10 +-
>  gdb/gdbarch.c                                 |   4 +-
>  gdb/gdbthread.h                               |  38 +-
>  gdb/inferior.c                                |  12 +-
>  gdb/inferior.h                                |   7 +-
>  gdb/infrun.c                                  | 615 +++++++++++++++---
>  gdb/linux-nat.c                               | 362 ++++++-----
>  gdb/linux-nat.h                               |   4 +
>  gdb/linux-tdep.c                              |   5 +-
>  gdb/linux-tdep.h                              |   2 +-
>  gdb/mi/mi-interp.c                            |   8 +-
>  gdb/netbsd-nat.c                              |   5 -
>  gdb/observable.h                              |  11 +-
>  gdb/procfs.c                                  |   6 -
>  gdb/python/py-inferior.c                      |   4 +-
>  gdb/remote.c                                  | 264 +++++++-
>  gdb/rs6000-tdep.c                             |   4 +-
>  gdb/target-debug.h                            |   2 +
>  gdb/target-delegates.c                        |  52 ++
>  gdb/target.c                                  |  16 +
>  gdb/target.h                                  |  10 +
>  gdb/target/target.c                           |  12 +
>  gdb/target/target.h                           |  20 +
>  gdb/target/waitstatus.c                       |   1 +
>  gdb/target/waitstatus.h                       |  31 +-
>  gdb/testsuite/gdb.base/step-over-syscall.exp  |  44 +-
>  .../gdb.threads/schedlock-new-thread.c        |  54 ++
>  .../gdb.threads/schedlock-new-thread.exp      |  67 ++
>  gdb/testsuite/gdb.threads/step-over-exec.exp  |   6 +
>  ...-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     | 151 +++++
>  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 +
>  gdb/thread.c                                  |  71 +-
>  gdb/unittests/enum-flags-selftests.c          |  69 +-
>  gdb/windows-nat.c                             |  16 +-
>  gdbserver/gdbthread.h                         |   3 +
>  gdbserver/linux-low.cc                        | 399 +++++++-----
>  gdbserver/linux-low.h                         |  56 +-
>  gdbserver/remote-utils.cc                     |  26 +-
>  gdbserver/server.cc                           | 158 ++++-
>  gdbserver/target.cc                           |  15 +-
>  gdbserver/target.h                            |  27 +-
>  gdbsupport/enum-flags.h                       |  66 ++
>  56 files changed, 3043 insertions(+), 624 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: fb699bafb5f23c2fd43d7f20495171b16903b20f
> prerequisite-patch-id: 75b36824a7ee067b57a8d8db6016cb57e4d7f620
> prerequisite-patch-id: d5bf8b612deed2abf58a48c553178e080347f34d
> prerequisite-patch-id: 3f00d1a451bed27d1b1c27174e15eb604d109a44
> prerequisite-patch-id: 5c458d627f349446e40114cb5424b42af08c0824
> prerequisite-patch-id: d81a20c336a23c79dafd4da3d780033052b3ac28
> prerequisite-patch-id: 8cf0c661a601ff3eacfcd26f11e26bcd11cc867d
> 

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

* Re: [PATCH 07/31] enum_flags to_string
  2022-12-12 20:30 ` [PATCH 07/31] enum_flags to_string Pedro Alves
@ 2023-01-30 20:07   ` Simon Marchi
  0 siblings, 0 replies; 100+ messages in thread
From: Simon Marchi @ 2023-01-30 20:07 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches; +Cc: Simon Marchi

On 12/12/22 15:30, Pedro Alves wrote:
> This commit introduces shared infrastructure that can be used to
> implement enum_flags -> to_string functions.  With this, if we want to
> support converting a given enum_flags specialization to string, we
> just need to implement a function that provides the enumerator->string
> mapping, like so:
>
> ...

I pushed this patch, since I was using it in my DWARF cooked index dump
patch.

Simon

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

* Re: [PATCH 08/31] Thread options & clone events (core + remote)
  2022-12-12 20:30 ` [PATCH 08/31] Thread options & clone events (core + remote) Pedro Alves
@ 2023-01-31 12:25   ` Lancelot SIX
  2023-03-10 19:16     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Lancelot SIX @ 2023-01-31 12:25 UTC (permalink / raw)
  To: Pedro Alves; +Cc: gdb-patches

Hi,

> diff --git a/gdb/remote.c b/gdb/remote.c
> index 41348a65dc4..9de8ed8a068 100644
> --- a/gdb/remote.c
> +++ b/gdb/remote.c
> @@ -14534,6 +14601,77 @@ 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);
> +}
> +
> +/* For coalescing reasons, actually sending the options to the target
> +   happens at resume time, via this function.  See target_resume for
> +   all-stop, and target_commit_resumed for non-stop.  */
> +
> +void
> +remote_target::commit_requested_thread_options ()
> +{
> +  struct remote_state *rs = get_remote_state ();
> +
> +  if (packet_support (PACKET_QThreadOptions) != PACKET_ENABLE)
> +    return;
> +
> +  char *p = rs->buf.data ();
> +  char *endp = p + get_remote_packet_size ();
> +
> +  /* Clear options for all threads by default.  Note that unlike
> +     vCont, the rightmost options that match a thread apply, so we
> +     don't have to worry about whether we can use wildcard ptids.  */
> +  strcpy (p, "QThreadOptions;0");
> +  p += strlen (p);
> +
> +  /* Now set non-zero options for threads that need them.  We don't
> +     bother with the case of all threads of a process wanting the same
> +     non-zero options as that's not an expected scenario.  */
> +  for (thread_info *tp : all_non_exited_threads (this))
> +    {
> +      gdb_thread_options options = tp->thread_options ();
> +
> +      if (options == 0)
> +	continue;
> +
> +      *p++ = ';';
> +      p += xsnprintf (p, endp - p, "%s", phex_nz (options, sizeof (options)));

I am not super familiar with how big the buffer is guaranteed to be.
Can we imagine a situation where the number of thread and options to
send exceed the packet size capacity?  If so, this seems dangerous.  'p'
would be incremented by the size which would have been necessary to do
the print, so it means it could now point past the end of the buffer.
Even the `*p++'= ';'` above and similar `*p++ =` below are subject to
overflow if the number of options to encode grow too high.

See man vsnprintf(3) which is used by xsnprintf:

    The functions snprintf() and vsnprintf() do not write more than size
    bytes[...].  If the output  was  truncated due to this limit, then
    the return value is the number of characters [...] which would have
    been written to the final string if enough space had been
    available.

As I do not feel that we can have a guaranty regarding the maximum
number of non exited threads with non-0 options (I might be wrong, but
the set of options can be extended so this can show in the future),
I would check the returned value of xsnprintf before adding it to p (the
same might apply to remote_target::write_ptid, and other increments to p).

Did I miss some precondition which guarantee the buffer to be big enough?

Best,
Lancelot.

> +      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)
>  {

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

* Re: [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal
  2022-12-12 20:30 ` [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal Pedro Alves
@ 2023-02-03 10:44   ` Andrew Burgess
  2023-03-10 17:15     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-02-03 10:44 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> This commit tweaks displaced_step_finish & friends to pass down a
> target_waitstatus instead of a gdb_signal.  This needed because a

missing word: "This IS needed".

> 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 +-
>  gdb/rs6000-tdep.c         |  4 ++--
>  9 files changed, 25 insertions(+), 26 deletions(-)
>
> diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
> index 7dfd63d8716..7b5d327008d 100644
> --- a/gdb/displaced-stepping.c
> +++ b/gdb/displaced-stepping.c
> @@ -192,10 +192,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 ())
> @@ -210,7 +211,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 ());
>  
> @@ -256,7 +257,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 e7230949aad..5d60f7677f0 100644
> --- a/gdb/gdbarch-components.py
> +++ b/gdb/gdbarch-components.py
> @@ -1829,7 +1829,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 a663316df16..5c9390ea6b3 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -1080,8 +1080,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 ddb8dec1c72..559b1dea0d9 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -4097,13 +4097,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 b24cc6d932d..0590310ffac 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -1894,7 +1894,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;
>  
> @@ -1916,7 +1917,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
> @@ -5068,7 +5069,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.  */
> @@ -5083,7 +5084,6 @@ handle_one (const wait_one_event &event)
>  	}
>        else
>  	{
> -	  enum gdb_signal sig;
>  	  struct regcache *regcache;
>  
>  	  infrun_debug_printf
> @@ -5094,10 +5094,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.  */
> @@ -5699,7 +5696,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);

This change is interesting.

If I understand the code correctly, this call will eventually end up in
displaced_step_buffers::finish (displaced-stepping.c), which in turn
calls displaced_step_instruction_executed_successfully.

Previously, we always passed GDB_SIGNAL_TRAP here, which (if we ignore
the watchpoint check in
displaced_step_instruction_executed_successfully) means that
displaced_step_instruction_executed_successfully would always return
true, and then displaced_step_buffers::finish would call
gdbarch_displaced_step_fixup.

After this change, we know that esc->ws.kind is either
TARGET_WAITKIND_FORKED or  TARGET_WAITKIND_VFORKED, so we know that
displaced_step_instruction_executed_successfully will always return
false, and displaced_step_buffers::finish will no longer call
gdbarch_displaced_step_fixup.

What I don't understand well enough is what this actually means for a
running inferior.

It's odd because the comment in infrun.c (just above your change)
indicates that to get to this point the displaced step must have
completed successfully, while after this change, the new code path in
displaced_step_buffers::finish indicates we believe the displaced step
didn't complete successfully:

  /* Since the instruction didn't complete, all we can do is relocate the
     PC.  */

Do you know if any of our test cases hit this path?

Thanks,
Andrew

>  	    /* Start a new step-over in another thread if there's one
>  	       that needs it.  */
>  	    start_step_over ();
> @@ -6064,7 +6061,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 c30d9fb13f8..8a1d701d7c9 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -2621,13 +2621,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 95cc29c828c..cba67351574 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.  */
>  
> diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
> index cbd84514795..dc0f78ed9ab 100644
> --- a/gdb/rs6000-tdep.c
> +++ b/gdb/rs6000-tdep.c
> @@ -1088,13 +1088,13 @@ ppc_displaced_step_prepare  (gdbarch *arch, thread_info *thread,
>  
>  static displaced_step_finish_status
>  ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
> -			   gdb_signal sig)
> +			   const target_waitstatus &status)
>  {
>    ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
>  
>    gdb_assert (per_inferior->disp_step_buf.has_value ());
>  
> -  return per_inferior->disp_step_buf->finish (arch, thread, sig);
> +  return per_inferior->disp_step_buf->finish (arch, thread, status);
>  }
>  
>  /* Implementation of gdbarch_displaced_step_restore_all_in_ptid.  */
> -- 
> 2.36.0


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

* Re: [PATCH 02/31] linux-nat: introduce pending_status_str
  2022-12-12 20:30 ` [PATCH 02/31] linux-nat: introduce pending_status_str Pedro Alves
@ 2023-02-03 12:00   ` Andrew Burgess
  2023-03-10 17:15     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-02-03 12:00 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

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

This patch looks fine.  I had one slightly related question:  I took a
look at the comment on lwp_info::waitstatus in linux-nat.h, which says:

  /* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
     for this LWP's last event.  This may correspond to STATUS above,
     or to a local variable in lin_lwp_wait.  */
  struct target_waitstatus waitstatus;

Am I right in thinking that this comment is wrong; it should say
TARGET_WAITKIND_IGNORE, not TARGET_WAITKIND_SPURIOUS, right?

Thanks,
Andrew


>
> Change-Id: I66e5c7a363d30a925b093b195d72925ce5b6b980
> ---
>  gdb/linux-nat.c | 19 ++++++++++++++++---
>  1 file changed, 16 insertions(+), 3 deletions(-)
>
> diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
> index 17e5dce08c3..9b78fd1f8e8 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -256,6 +256,19 @@ 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)
> +{
> +  gdb_assert (lwp_status_pending_p (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 +1660,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 +3150,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] 100+ messages in thread

* Re: [PATCH 04/31] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED
  2022-12-12 20:30 ` [PATCH 04/31] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED Pedro Alves
@ 2023-02-04 15:38   ` Andrew Burgess
  2023-03-10 17:16     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-02-04 15:38 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> (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 affected, 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 held 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 becomes unused with this patch, but
> a new use will reappear later in the series.  To avoid deleting it and
> readding it back, this patch marks it with attribute unused, and the
> latter patch removes the attribute again.  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            | 158 +++++++++++++------------
>  gdb/linux-nat.c         | 248 +++++++++++++++++++++-------------------
>  gdb/linux-nat.h         |   2 +
>  gdb/target-delegates.c  |  24 ++++
>  gdb/target.c            |   7 ++
>  gdb/target.h            |   2 +
>  gdb/target/waitstatus.c |   1 +
>  gdb/target/waitstatus.h |  31 ++++-
>  8 files changed, 281 insertions(+), 192 deletions(-)
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index 0590310ffac..f7786672004 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -1583,6 +1583,7 @@ step_over_info_valid_p (void)
>  /* Return true if THREAD is doing a displaced step.  */
>  
>  static bool
> +ATTRIBUTE_UNUSED
>  displaced_step_in_progress_thread (thread_info *thread)
>  {
>    gdb_assert (thread != nullptr);
> @@ -1897,6 +1898,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?  */
> @@ -1916,8 +1942,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
> @@ -5663,67 +5720,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);
>  
> @@ -5739,7 +5742,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.  */
> @@ -5771,14 +5774,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.  */
> @@ -5789,16 +5802,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 5ee3227f1b9..f3d02b740e8 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -1286,64 +1286,79 @@ 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.  */
> +/* If LP has a pending fork/vfork/clone status, return it.  */
>  
> -static void
> -detach_one_lwp (struct lwp_info *lp, int *signo_p)
> +static gdb::optional<target_waitstatus>
> +get_pending_child_status (lwp_info *lp)
>  {
> -  int lwpid = lp->ptid.lwp ();
> -  int signo;
> -
> -  gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
> -
> -  /* 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.  */
> -
>    /* 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);
> +	    {
> +	      target_waitstatus ws;
> +
> +	      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 ws;
> +	    }
>  	  else
> -	    perror_warning_with_name (_("Failed to detach fork child"));
> +	    {
> +	      perror_warning_with_name (_("Failed to retrieve event msg"));
> +	      return {};
> +	    }
>  	}
>      }
>  
>    /* 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_new_child_status (lp->waitstatus.kind ()))
> +    return lp->waitstatus;
>  
> -  /* Check in thread_info::pending_waitstatus.  */
>    thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
> -  if (tp->has_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);
> -    }
> +  /* Check in thread_info::pending_waitstatus.  */
> +  if (tp->has_pending_waitstatus ()
> +      && is_new_child_status (tp->pending_waitstatus ().kind ()))
> +    return tp->pending_waitstatus ();
>  
>    /* 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_new_child_status (tp->pending_follow.kind ()))
> +    return tp->pending_follow;
>  
> -  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 {};
> +}
> +
> +/* 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.  */
> +
> +  if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))

I know this is just a style issue, but I'm really not a fan of this
assignment in the if condition.  I know it probably seems silly, but I'd
find it much clearer if we did:

  gdb::optional<target_waitstatus> ws = get_pending_child_status (lp)
  if (ws.has_value ())
    ...

The first time I read the initial line, my first thought was s/=/==/,
then I had to read it again and figure out what was going on...

> +    detach_one_pid (ws->child_ptid ().lwp (), 0);
>  
>    /* If there is a pending SIGSTOP, get rid of it.  */
>    if (lp->signalled)
> @@ -1821,6 +1836,53 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
>    return 1;
>  }
>

I think there should be a:

  /* See target.h.  */

comment here to point to the documentation in target.h.

> +void
> +linux_nat_target::follow_clone (ptid_t child_ptid)
> +{
> +  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.  */
> +      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 (_("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 +1923,9 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
>  	    internal_error (_("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,66 +1962,18 @@ 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.  */
> -	      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;
> @@ -3536,59 +3548,55 @@ 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 ())
> +  if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
>      {
> -      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 a9b91a5e908..3ed25cc5ba4 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 daf46821be0..bee46608c38 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 f781f7e4f96..2fb09c2817d 100644
> --- a/gdb/target.c
> +++ b/gdb/target.c
> @@ -2732,6 +2732,13 @@ default_follow_fork (struct target_ops *self, inferior *child_inf,
>    internal_error (_("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 (_("could not find a target to follow clone"));
> +}
> +
>  /* See target.h.  */
>  
>  void
> diff --git a/gdb/target.h b/gdb/target.h
> index 28aa9273893..aab390aec57 100644
> --- a/gdb/target.h
> +++ b/gdb/target.h
> @@ -637,6 +637,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);

One thing that sucks about some of the older parts of the target API is
just how undocumented it is.  The only way to figure out what things
like follow_fork do is to look at the code.

Some of the newer methods do have good comments.  Or at least comments
that give a good hint to what the method does.

I'd be really grateful if new target API methods could be given a good
comment in target.h.

Thanks,
Andrew

>      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..2072eb018ae 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,
>  
> @@ -102,6 +109,17 @@ enum target_waitkind
>    TARGET_WAITKIND_THREAD_EXITED,
>  };
>  
> +/* Determine if KIND represents an event with a new child - a fork,
> +   vfork, or clone.  */
> +
> +static inline bool
> +is_new_child_status (target_waitkind kind)
> +{
> +  return (kind == TARGET_WAITKIND_FORKED
> +	  || kind == TARGET_WAITKIND_VFORKED
> +	  || kind == TARGET_WAITKIND_THREAD_CLONED);
> +}
> +
>  /* Return KIND as a string.  */
>  
>  static inline const char *
> @@ -125,6 +143,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 +345,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 ();
> @@ -369,8 +397,7 @@ struct target_waitstatus
>  
>    ptid_t child_ptid () const
>    {
> -    gdb_assert (m_kind == TARGET_WAITKIND_FORKED
> -		|| m_kind == TARGET_WAITKIND_VFORKED);
> +    gdb_assert (is_new_child_status (m_kind));
>      return m_value.child_ptid;
>    }
>  
> -- 
> 2.36.0


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

* Re: [PATCH 30/31] Centralize "[Thread ...exited]" notifications
  2022-12-12 20:31 ` [PATCH 30/31] Centralize "[Thread ...exited]" notifications Pedro Alves
@ 2023-02-04 16:05   ` Andrew Burgess
  2023-03-10 17:21     ` Pedro Alves
  2023-02-16 15:40   ` Andrew Burgess
  1 sibling, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-02-04 16:05 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> Currently, each target backend is responsible for printing "[Thread
> ...exited]" before deleting a thread.  This leads to unnecessary
> differences between targets, like e.g. with the remote target, we
> never print such messages, even though we do print "[New Thread ...]".
>
> E.g., debugging the gdb.threads/attach-many-short-lived-threads.exp
> with gdbserver, letting it run for a bit, and then pressing Ctrl-C, we
> currently see:
>
>  (gdb) c
>  Continuing.
>  ^C[New Thread 3850398.3887449]
>  [New Thread 3850398.3887500]
>  [New Thread 3850398.3887551]
>  [New Thread 3850398.3887602]
>  [New Thread 3850398.3887653]
>  ...
>
>  Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
>  0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
>      at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
>  78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
>  (gdb)
>
> Above, we only see "New Thread" notifications, even though threads
> were deleted.
>
> After this patch, we'll see:
>
>  (gdb) c
>  Continuing.
>  ^C[Thread 3558643.3577053 exited]
>  [Thread 3558643.3577104 exited]
>  [Thread 3558643.3577155 exited]
>  [Thread 3558643.3579603 exited]
>  ...
>  [New Thread 3558643.3597415]
>  [New Thread 3558643.3600015]
>  [New Thread 3558643.3599965]
>  ...
>
>  Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
>  0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
>      at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
>  78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
>  (gdb) q
>
>
> This commit fixes this by moving the thread exit printing to common
> code instead, triggered from within delete_thread (or rather,
> set_thread_exited).
>
> There's one wrinkle, though.  While most targest want to print:
>
>  [Thread ... exited]
>
> the Windows target wants to print:
>
>  [Thread ... exited with code <exit_code>]
>
> ... and sometimes wants to suppress the notification for the main
> thread.  To address that, this commits adds a delete_thread_with_code
> function, only used by that target (so far).

You mention gdb.threads/attach-many-short-lived-threads.exp as an
example, but I don't think that test actually fails due to this issue,
right?

It might be worth mentioning gdb.threads/thread-specific-bp.exp, that is
a test script that should actually start passing after this commit.

Thanks,
Andrew

>
> Change-Id: I06ec07b7c51527872a9713dd11cf7867b50fc5ff
> ---
>  gdb/annotate.c           |  4 +++-
>  gdb/breakpoint.c         |  4 +++-
>  gdb/fbsd-nat.c           |  3 ---
>  gdb/gdbthread.h          | 22 +++++++++++++----
>  gdb/inferior.c           |  2 +-
>  gdb/inferior.h           |  2 ++
>  gdb/linux-nat.c          | 11 +++------
>  gdb/mi/mi-interp.c       |  8 +++++--
>  gdb/netbsd-nat.c         |  4 ----
>  gdb/observable.h         | 11 +++++----
>  gdb/procfs.c             |  6 -----
>  gdb/python/py-inferior.c |  4 +++-
>  gdb/thread.c             | 51 ++++++++++++++++++++++++++++++----------
>  gdb/windows-nat.c        | 16 ++++---------
>  14 files changed, 89 insertions(+), 59 deletions(-)
>
> diff --git a/gdb/annotate.c b/gdb/annotate.c
> index 33805dcdb30..b45384ddb15 100644
> --- a/gdb/annotate.c
> +++ b/gdb/annotate.c
> @@ -233,7 +233,9 @@ annotate_thread_changed (void)
>  /* Emit notification on thread exit.  */
>  
>  static void
> -annotate_thread_exited (struct thread_info *t, int silent)
> +annotate_thread_exited (thread_info *t,
> +			gdb::optional<ULONGEST> exit_code,
> +			bool /* silent */)
>  {
>    if (annotation_level > 1)
>      {
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index f0276a963c0..0736231e470 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -3237,7 +3237,9 @@ remove_breakpoints (void)
>     that thread.  */
>  
>  static void
> -remove_threaded_breakpoints (struct thread_info *tp, int silent)
> +remove_threaded_breakpoints (thread_info *tp,
> +			     gdb::optional<ULONGEST> exit_code,
> +			     bool /* silent */)
>  {
>    for (breakpoint *b : all_breakpoints_safe ())
>      {
> diff --git a/gdb/fbsd-nat.c b/gdb/fbsd-nat.c
> index 1aec75050ae..3d1e742f4e3 100644
> --- a/gdb/fbsd-nat.c
> +++ b/gdb/fbsd-nat.c
> @@ -1300,9 +1300,6 @@ fbsd_nat_target::wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
>  		{
>  		  fbsd_lwp_debug_printf ("deleting thread for LWP %u",
>  					 pl.pl_lwpid);
> -		  if (print_thread_events)
> -		    gdb_printf (_("[%s exited]\n"),
> -				target_pid_to_str (wptid).c_str ());
>  		  low_delete_thread (thr);
>  		  delete_thread (thr);
>  		}
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index 43e9d6ea484..7ab02873f17 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -636,16 +636,30 @@ extern struct thread_info *add_thread_with_info (process_stratum_target *targ,
>  
>  /* Delete thread THREAD and notify of thread exit.  If the thread is
>     currently not deletable, don't actually delete it but still tag it
> -   as exited and do the notification.  */
> -extern void delete_thread (struct thread_info *thread);
> +   as exited and do the notification.  EXIT_CODE is the thread's exit
> +   code.  If SILENT, don't actually notify the CLI.  THREAD must not
> +   be NULL or an assertion will fail.  */
> +extern void delete_thread_with_exit_code (thread_info *thread,
> +					  ULONGEST exit_code,
> +					  bool silent = false);
> +
> +/* Delete thread THREAD and notify of thread exit.  If the thread is
> +   currently not deletable, don't actually delete it but still tag it
> +   as exited and do the notification.  THREAD must not be NULL or an
> +   assertion will fail.  */
> +extern void delete_thread (thread_info *thread);
>  
>  /* Like delete_thread, but be quiet about it.  Used when the process
>     this thread belonged to has already exited, for example.  */
>  extern void delete_thread_silent (struct thread_info *thread);
>  
>  /* Mark the thread exited, but don't delete it or remove it from the
> -   inferior thread list.  */
> -extern void set_thread_exited (thread_info *tp, bool silent);
> +   inferior thread list.  EXIT_CODE is the thread's exit code, if
> +   available.  If SILENT, then don't inform the CLI about the
> +   exit.  */
> +extern void set_thread_exited (thread_info *tp,
> +			       gdb::optional<ULONGEST> exit_code = {},
> +			       bool silent = false);
>  
>  /* Delete a step_resume_breakpoint from the thread database.  */
>  extern void delete_step_resume_breakpoint (struct thread_info *);
> diff --git a/gdb/inferior.c b/gdb/inferior.c
> index eacb65ec1d7..834eabdf2ca 100644
> --- a/gdb/inferior.c
> +++ b/gdb/inferior.c
> @@ -174,7 +174,7 @@ inferior::clear_thread_list ()
>      {
>        threads_debug_printf ("deleting thread %s",
>  			    thr->ptid.to_string ().c_str ());
> -      set_thread_exited (thr, true);
> +      set_thread_exited (thr, {}, true);
>        if (thr->deletable ())
>  	delete thr;
>      });
> diff --git a/gdb/inferior.h b/gdb/inferior.h
> index 07d9527a802..cf3f1275cc1 100644
> --- a/gdb/inferior.h
> +++ b/gdb/inferior.h
> @@ -628,6 +628,8 @@ extern void detach_inferior (inferior *inf);
>  
>  extern void exit_inferior (inferior *inf);
>  
> +/* Like exit_inferior, but be quiet -- don't announce the exit of the
> +   inferior's threads to the CLI.  */
>  extern void exit_inferior_silent (inferior *inf);
>  
>  extern void exit_inferior_num_silent (int num);
> diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
> index 75f81edf20a..acf5fd3f1b1 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -916,15 +916,10 @@ linux_nat_switch_fork (ptid_t new_ptid)
>  static void
>  exit_lwp (struct lwp_info *lp, bool del_thread = true)
>  {
> -  struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
> -
> -  if (th)
> +  if (del_thread)
>      {
> -      if (print_thread_events)
> -	gdb_printf (_("[%s exited]\n"),
> -		    target_pid_to_str (lp->ptid).c_str ());
> -
> -      if (del_thread)
> +      thread_info *th = find_thread_ptid (linux_target, lp->ptid);
> +      if (th != nullptr)
>  	delete_thread (th);
>      }
>  
> diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
> index 3cc2462f672..189dd1f302f 100644
> --- a/gdb/mi/mi-interp.c
> +++ b/gdb/mi/mi-interp.c
> @@ -68,7 +68,9 @@ static void mi_on_normal_stop (struct bpstat *bs, int print_frame);
>  static void mi_on_no_history (void);
>  
>  static void mi_new_thread (struct thread_info *t);
> -static void mi_thread_exit (struct thread_info *t, int silent);
> +static void mi_thread_exit (thread_info *t,
> +			    gdb::optional<ULONGEST> exit_code,
> +			    bool silent);
>  static void mi_record_changed (struct inferior*, int, const char *,
>  			       const char *);
>  static void mi_inferior_added (struct inferior *inf);
> @@ -351,8 +353,10 @@ mi_new_thread (struct thread_info *t)
>      }
>  }
>  
> +/* Observer for the thread_exit notification.  */
> +
>  static void
> -mi_thread_exit (struct thread_info *t, int silent)
> +mi_thread_exit (thread_info *t, gdb::optional<ULONGEST> exit_code, bool silent)
>  {
>    SWITCH_THRU_ALL_UIS ()
>      {
> diff --git a/gdb/netbsd-nat.c b/gdb/netbsd-nat.c
> index aa16a6cc5bd..9674baeb846 100644
> --- a/gdb/netbsd-nat.c
> +++ b/gdb/netbsd-nat.c
> @@ -625,10 +625,6 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
>  	{
>  	  /* NetBSD does not store an LWP exit status.  */
>  	  ourstatus->set_thread_exited (0);
> -
> -	  if (print_thread_events)
> -	    gdb_printf (_("[%s exited]\n"),
> -			target_pid_to_str (wptid).c_str ());
>  	}
>  
>        /* The GDB core expects that the rest of the threads are running.  */
> diff --git a/gdb/observable.h b/gdb/observable.h
> index 1103c5c98a6..a4ab4f1e38f 100644
> --- a/gdb/observable.h
> +++ b/gdb/observable.h
> @@ -126,10 +126,13 @@ extern observable<struct objfile */* objfile */> free_objfile;
>  /* The thread specified by T has been created.  */
>  extern observable<struct thread_info */* t */> new_thread;
>  
> -/* The thread specified by T has exited.  The SILENT argument
> -   indicates that gdb is removing the thread from its tables without
> -   wanting to notify the user about it.  */
> -extern observable<struct thread_info */* t */, int /* silent */> thread_exit;
> +/* The thread specified by T has exited.  EXIT_CODE is the thread's
> +   exit code, if available.  The SILENT argument indicates that GDB is
> +   removing the thread from its tables without wanting to notify the
> +   CLI about it.  */
> +extern observable<thread_info */* t */,
> +		  gdb::optional<ULONGEST> /* exit_code */,
> +		  bool /* silent */> thread_exit;
>  
>  /* An explicit stop request was issued to PTID.  If PTID equals
>     minus_one_ptid, the request applied to all threads.  If
> diff --git a/gdb/procfs.c b/gdb/procfs.c
> index ffc26c8fb9e..7d0e6e9a4c9 100644
> --- a/gdb/procfs.c
> +++ b/gdb/procfs.c
> @@ -2115,9 +2115,6 @@ procfs_target::wait (ptid_t ptid, struct target_waitstatus *status,
>  	      case PR_SYSENTRY:
>  		if (what == SYS_lwp_exit)
>  		  {
> -		    if (print_thread_events)
> -		      gdb_printf (_("[%s exited]\n"),
> -				  target_pid_to_str (retval).c_str ());
>  		    delete_thread (find_thread_ptid (this, retval));
>  		    target_continue_no_signal (ptid);
>  		    goto wait_again;
> @@ -2222,9 +2219,6 @@ procfs_target::wait (ptid_t ptid, struct target_waitstatus *status,
>  		  }
>  		else if (what == SYS_lwp_exit)
>  		  {
> -		    if (print_thread_events)
> -		      gdb_printf (_("[%s exited]\n"),
> -				  target_pid_to_str (retval).c_str ());
>  		    delete_thread (find_thread_ptid (this, retval));
>  		    status->set_spurious ();
>  		    return retval;
> diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
> index 4d5e09db680..be5597c4a2e 100644
> --- a/gdb/python/py-inferior.c
> +++ b/gdb/python/py-inferior.c
> @@ -360,7 +360,9 @@ add_thread_object (struct thread_info *tp)
>  }
>  
>  static void
> -delete_thread_object (struct thread_info *tp, int ignore)
> +delete_thread_object (thread_info *tp,
> +		      gdb::optional<ULONGEST> /* exit_code */,
> +		      bool /* silent */)
>  {
>    if (!gdb_python_initialized)
>      return;
> diff --git a/gdb/thread.c b/gdb/thread.c
> index 2ca3a867d8c..7ab30562fd3 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -192,7 +192,8 @@ clear_thread_inferior_resources (struct thread_info *tp)
>  /* See gdbthread.h.  */
>  
>  void
> -set_thread_exited (thread_info *tp, bool silent)
> +set_thread_exited (thread_info *tp, gdb::optional<ULONGEST> exit_code,
> +		   bool silent)
>  {
>    /* Dead threads don't need to step-over.  Remove from chain.  */
>    if (thread_is_in_step_over_chain (tp))
> @@ -211,7 +212,22 @@ set_thread_exited (thread_info *tp, bool silent)
>        if (proc_target != nullptr)
>  	proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
>  
> -      gdb::observers::thread_exit.notify (tp, silent);
> +      if (!silent && print_thread_events)
> +	{
> +	  if (exit_code.has_value ())
> +	    {
> +	      gdb_printf (_("[%s exited with code %s]\n"),
> +			  target_pid_to_str (tp->ptid).c_str (),
> +			  pulongest (*exit_code));
> +	    }
> +	  else
> +	    {
> +	      gdb_printf (_("[%s exited]\n"),
> +			  target_pid_to_str (tp->ptid).c_str ());
> +	    }
> +	}
> +
> +      gdb::observers::thread_exit.notify (tp, exit_code, silent);
>  
>        /* Tag it as exited.  */
>        tp->state = THREAD_EXITED;
> @@ -468,20 +484,22 @@ global_thread_step_over_chain_remove (struct thread_info *tp)
>    global_thread_step_over_list.erase (it);
>  }
>  
> -/* Delete the thread referenced by THR.  If SILENT, don't notify
> -   the observer of this exit.
> -   
> -   THR must not be NULL or a failed assertion will be raised.  */
> +/* Helper for the different delete_thread variants.  */
>  
>  static void
> -delete_thread_1 (thread_info *thr, bool silent)
> +delete_thread_1 (thread_info *thr, gdb::optional<ULONGEST> exit_code,
> +		 bool silent)
>  {
>    gdb_assert (thr != nullptr);
>  
> -  threads_debug_printf ("deleting thread %s, silent = %d",
> -			thr->ptid.to_string ().c_str (), silent);
> +  threads_debug_printf ("deleting thread %s, exit_code = %s, silent = %d",
> +			thr->ptid.to_string ().c_str (),
> +			(exit_code.has_value ()
> +			 ? pulongest (*exit_code)
> +			 : "<none>"),
> +			silent);
>  
> -  set_thread_exited (thr, silent);
> +  set_thread_exited (thr, exit_code, silent);
>  
>    if (!thr->deletable ())
>      {
> @@ -497,16 +515,25 @@ delete_thread_1 (thread_info *thr, bool silent)
>  
>  /* See gdbthread.h.  */
>  
> +void
> +delete_thread_with_exit_code (thread_info *thread, ULONGEST exit_code,
> +			      bool silent)
> +{
> +  delete_thread_1 (thread, exit_code, false /* not silent */);
> +}
> +
> +/* See gdbthread.h.  */
> +
>  void
>  delete_thread (thread_info *thread)
>  {
> -  delete_thread_1 (thread, false /* not silent */);
> +  delete_thread_1 (thread, {}, false /* not silent */);
>  }
>  
>  void
>  delete_thread_silent (thread_info *thread)
>  {
> -  delete_thread_1 (thread, true /* silent */);
> +  delete_thread_1 (thread, {}, true /* not silent */);
>  }
>  
>  struct thread_info *
> diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
> index ee4e78bdabf..2764fc694b3 100644
> --- a/gdb/windows-nat.c
> +++ b/gdb/windows-nat.c
> @@ -611,21 +611,13 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
>  
>    id = ptid.lwp ();
>  
> -  /* Emit a notification about the thread being deleted.
> -
> -     Note that no notification was printed when the main thread
> +  /* Note that no notification was printed when the main thread
>       was created, and thus, unless in verbose mode, we should be
>       symmetrical, and avoid that notification for the main thread
>       here as well.  */
> -
> -  if (info_verbose)
> -    gdb_printf ("[Deleting %s]\n", target_pid_to_str (ptid).c_str ());
> -  else if (print_thread_events && !main_thread_p)
> -    gdb_printf (_("[%s exited with code %u]\n"),
> -		target_pid_to_str (ptid).c_str (),
> -		(unsigned) exit_code);
> -
> -  ::delete_thread (find_thread_ptid (this, ptid));
> +  bool silent = (main_thread_p && !info_verbose);
> +  thread_info *todel = find_thread_ptid (this, ptid);
> +  delete_thread_with_exit_code (todel, exit_code, silent);
>  
>    auto iter = std::find_if (windows_process.thread_list.begin (),
>  			    windows_process.thread_list.end (),
> -- 
> 2.36.0


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

* Re: [PATCH 30/31] Centralize "[Thread ...exited]" notifications
  2022-12-12 20:31 ` [PATCH 30/31] Centralize "[Thread ...exited]" notifications Pedro Alves
  2023-02-04 16:05   ` Andrew Burgess
@ 2023-02-16 15:40   ` Andrew Burgess
  2023-06-12 12:23     ` Andrew Burgess
  1 sibling, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-02-16 15:40 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> Currently, each target backend is responsible for printing "[Thread
> ...exited]" before deleting a thread.  This leads to unnecessary
> differences between targets, like e.g. with the remote target, we
> never print such messages, even though we do print "[New Thread ...]".
>
> E.g., debugging the gdb.threads/attach-many-short-lived-threads.exp
> with gdbserver, letting it run for a bit, and then pressing Ctrl-C, we
> currently see:

I'm currently writing some tests that run into this issue, and I needed
a bug-id to use in the XFAIL messages.  I've created PR gdb/30129 for
this issue:

https://sourceware.org/bugzilla/show_bug.cgi?id=30129

Thanks,
Andrew

>
>  (gdb) c
>  Continuing.
>  ^C[New Thread 3850398.3887449]
>  [New Thread 3850398.3887500]
>  [New Thread 3850398.3887551]
>  [New Thread 3850398.3887602]
>  [New Thread 3850398.3887653]
>  ...
>
>  Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
>  0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
>      at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
>  78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
>  (gdb)
>
> Above, we only see "New Thread" notifications, even though threads
> were deleted.
>
> After this patch, we'll see:
>
>  (gdb) c
>  Continuing.
>  ^C[Thread 3558643.3577053 exited]
>  [Thread 3558643.3577104 exited]
>  [Thread 3558643.3577155 exited]
>  [Thread 3558643.3579603 exited]
>  ...
>  [New Thread 3558643.3597415]
>  [New Thread 3558643.3600015]
>  [New Thread 3558643.3599965]
>  ...
>
>  Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
>  0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
>      at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
>  78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
>  (gdb) q
>
>
> This commit fixes this by moving the thread exit printing to common
> code instead, triggered from within delete_thread (or rather,
> set_thread_exited).
>
> There's one wrinkle, though.  While most targest want to print:
>
>  [Thread ... exited]
>
> the Windows target wants to print:
>
>  [Thread ... exited with code <exit_code>]
>
> ... and sometimes wants to suppress the notification for the main
> thread.  To address that, this commits adds a delete_thread_with_code
> function, only used by that target (so far).
>
> Change-Id: I06ec07b7c51527872a9713dd11cf7867b50fc5ff
> ---
>  gdb/annotate.c           |  4 +++-
>  gdb/breakpoint.c         |  4 +++-
>  gdb/fbsd-nat.c           |  3 ---
>  gdb/gdbthread.h          | 22 +++++++++++++----
>  gdb/inferior.c           |  2 +-
>  gdb/inferior.h           |  2 ++
>  gdb/linux-nat.c          | 11 +++------
>  gdb/mi/mi-interp.c       |  8 +++++--
>  gdb/netbsd-nat.c         |  4 ----
>  gdb/observable.h         | 11 +++++----
>  gdb/procfs.c             |  6 -----
>  gdb/python/py-inferior.c |  4 +++-
>  gdb/thread.c             | 51 ++++++++++++++++++++++++++++++----------
>  gdb/windows-nat.c        | 16 ++++---------
>  14 files changed, 89 insertions(+), 59 deletions(-)
>
> diff --git a/gdb/annotate.c b/gdb/annotate.c
> index 33805dcdb30..b45384ddb15 100644
> --- a/gdb/annotate.c
> +++ b/gdb/annotate.c
> @@ -233,7 +233,9 @@ annotate_thread_changed (void)
>  /* Emit notification on thread exit.  */
>  
>  static void
> -annotate_thread_exited (struct thread_info *t, int silent)
> +annotate_thread_exited (thread_info *t,
> +			gdb::optional<ULONGEST> exit_code,
> +			bool /* silent */)
>  {
>    if (annotation_level > 1)
>      {
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index f0276a963c0..0736231e470 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -3237,7 +3237,9 @@ remove_breakpoints (void)
>     that thread.  */
>  
>  static void
> -remove_threaded_breakpoints (struct thread_info *tp, int silent)
> +remove_threaded_breakpoints (thread_info *tp,
> +			     gdb::optional<ULONGEST> exit_code,
> +			     bool /* silent */)
>  {
>    for (breakpoint *b : all_breakpoints_safe ())
>      {
> diff --git a/gdb/fbsd-nat.c b/gdb/fbsd-nat.c
> index 1aec75050ae..3d1e742f4e3 100644
> --- a/gdb/fbsd-nat.c
> +++ b/gdb/fbsd-nat.c
> @@ -1300,9 +1300,6 @@ fbsd_nat_target::wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
>  		{
>  		  fbsd_lwp_debug_printf ("deleting thread for LWP %u",
>  					 pl.pl_lwpid);
> -		  if (print_thread_events)
> -		    gdb_printf (_("[%s exited]\n"),
> -				target_pid_to_str (wptid).c_str ());
>  		  low_delete_thread (thr);
>  		  delete_thread (thr);
>  		}
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index 43e9d6ea484..7ab02873f17 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -636,16 +636,30 @@ extern struct thread_info *add_thread_with_info (process_stratum_target *targ,
>  
>  /* Delete thread THREAD and notify of thread exit.  If the thread is
>     currently not deletable, don't actually delete it but still tag it
> -   as exited and do the notification.  */
> -extern void delete_thread (struct thread_info *thread);
> +   as exited and do the notification.  EXIT_CODE is the thread's exit
> +   code.  If SILENT, don't actually notify the CLI.  THREAD must not
> +   be NULL or an assertion will fail.  */
> +extern void delete_thread_with_exit_code (thread_info *thread,
> +					  ULONGEST exit_code,
> +					  bool silent = false);
> +
> +/* Delete thread THREAD and notify of thread exit.  If the thread is
> +   currently not deletable, don't actually delete it but still tag it
> +   as exited and do the notification.  THREAD must not be NULL or an
> +   assertion will fail.  */
> +extern void delete_thread (thread_info *thread);
>  
>  /* Like delete_thread, but be quiet about it.  Used when the process
>     this thread belonged to has already exited, for example.  */
>  extern void delete_thread_silent (struct thread_info *thread);
>  
>  /* Mark the thread exited, but don't delete it or remove it from the
> -   inferior thread list.  */
> -extern void set_thread_exited (thread_info *tp, bool silent);
> +   inferior thread list.  EXIT_CODE is the thread's exit code, if
> +   available.  If SILENT, then don't inform the CLI about the
> +   exit.  */
> +extern void set_thread_exited (thread_info *tp,
> +			       gdb::optional<ULONGEST> exit_code = {},
> +			       bool silent = false);
>  
>  /* Delete a step_resume_breakpoint from the thread database.  */
>  extern void delete_step_resume_breakpoint (struct thread_info *);
> diff --git a/gdb/inferior.c b/gdb/inferior.c
> index eacb65ec1d7..834eabdf2ca 100644
> --- a/gdb/inferior.c
> +++ b/gdb/inferior.c
> @@ -174,7 +174,7 @@ inferior::clear_thread_list ()
>      {
>        threads_debug_printf ("deleting thread %s",
>  			    thr->ptid.to_string ().c_str ());
> -      set_thread_exited (thr, true);
> +      set_thread_exited (thr, {}, true);
>        if (thr->deletable ())
>  	delete thr;
>      });
> diff --git a/gdb/inferior.h b/gdb/inferior.h
> index 07d9527a802..cf3f1275cc1 100644
> --- a/gdb/inferior.h
> +++ b/gdb/inferior.h
> @@ -628,6 +628,8 @@ extern void detach_inferior (inferior *inf);
>  
>  extern void exit_inferior (inferior *inf);
>  
> +/* Like exit_inferior, but be quiet -- don't announce the exit of the
> +   inferior's threads to the CLI.  */
>  extern void exit_inferior_silent (inferior *inf);
>  
>  extern void exit_inferior_num_silent (int num);
> diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
> index 75f81edf20a..acf5fd3f1b1 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -916,15 +916,10 @@ linux_nat_switch_fork (ptid_t new_ptid)
>  static void
>  exit_lwp (struct lwp_info *lp, bool del_thread = true)
>  {
> -  struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
> -
> -  if (th)
> +  if (del_thread)
>      {
> -      if (print_thread_events)
> -	gdb_printf (_("[%s exited]\n"),
> -		    target_pid_to_str (lp->ptid).c_str ());
> -
> -      if (del_thread)
> +      thread_info *th = find_thread_ptid (linux_target, lp->ptid);
> +      if (th != nullptr)
>  	delete_thread (th);
>      }
>  
> diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
> index 3cc2462f672..189dd1f302f 100644
> --- a/gdb/mi/mi-interp.c
> +++ b/gdb/mi/mi-interp.c
> @@ -68,7 +68,9 @@ static void mi_on_normal_stop (struct bpstat *bs, int print_frame);
>  static void mi_on_no_history (void);
>  
>  static void mi_new_thread (struct thread_info *t);
> -static void mi_thread_exit (struct thread_info *t, int silent);
> +static void mi_thread_exit (thread_info *t,
> +			    gdb::optional<ULONGEST> exit_code,
> +			    bool silent);
>  static void mi_record_changed (struct inferior*, int, const char *,
>  			       const char *);
>  static void mi_inferior_added (struct inferior *inf);
> @@ -351,8 +353,10 @@ mi_new_thread (struct thread_info *t)
>      }
>  }
>  
> +/* Observer for the thread_exit notification.  */
> +
>  static void
> -mi_thread_exit (struct thread_info *t, int silent)
> +mi_thread_exit (thread_info *t, gdb::optional<ULONGEST> exit_code, bool silent)
>  {
>    SWITCH_THRU_ALL_UIS ()
>      {
> diff --git a/gdb/netbsd-nat.c b/gdb/netbsd-nat.c
> index aa16a6cc5bd..9674baeb846 100644
> --- a/gdb/netbsd-nat.c
> +++ b/gdb/netbsd-nat.c
> @@ -625,10 +625,6 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
>  	{
>  	  /* NetBSD does not store an LWP exit status.  */
>  	  ourstatus->set_thread_exited (0);
> -
> -	  if (print_thread_events)
> -	    gdb_printf (_("[%s exited]\n"),
> -			target_pid_to_str (wptid).c_str ());
>  	}
>  
>        /* The GDB core expects that the rest of the threads are running.  */
> diff --git a/gdb/observable.h b/gdb/observable.h
> index 1103c5c98a6..a4ab4f1e38f 100644
> --- a/gdb/observable.h
> +++ b/gdb/observable.h
> @@ -126,10 +126,13 @@ extern observable<struct objfile */* objfile */> free_objfile;
>  /* The thread specified by T has been created.  */
>  extern observable<struct thread_info */* t */> new_thread;
>  
> -/* The thread specified by T has exited.  The SILENT argument
> -   indicates that gdb is removing the thread from its tables without
> -   wanting to notify the user about it.  */
> -extern observable<struct thread_info */* t */, int /* silent */> thread_exit;
> +/* The thread specified by T has exited.  EXIT_CODE is the thread's
> +   exit code, if available.  The SILENT argument indicates that GDB is
> +   removing the thread from its tables without wanting to notify the
> +   CLI about it.  */
> +extern observable<thread_info */* t */,
> +		  gdb::optional<ULONGEST> /* exit_code */,
> +		  bool /* silent */> thread_exit;
>  
>  /* An explicit stop request was issued to PTID.  If PTID equals
>     minus_one_ptid, the request applied to all threads.  If
> diff --git a/gdb/procfs.c b/gdb/procfs.c
> index ffc26c8fb9e..7d0e6e9a4c9 100644
> --- a/gdb/procfs.c
> +++ b/gdb/procfs.c
> @@ -2115,9 +2115,6 @@ procfs_target::wait (ptid_t ptid, struct target_waitstatus *status,
>  	      case PR_SYSENTRY:
>  		if (what == SYS_lwp_exit)
>  		  {
> -		    if (print_thread_events)
> -		      gdb_printf (_("[%s exited]\n"),
> -				  target_pid_to_str (retval).c_str ());
>  		    delete_thread (find_thread_ptid (this, retval));
>  		    target_continue_no_signal (ptid);
>  		    goto wait_again;
> @@ -2222,9 +2219,6 @@ procfs_target::wait (ptid_t ptid, struct target_waitstatus *status,
>  		  }
>  		else if (what == SYS_lwp_exit)
>  		  {
> -		    if (print_thread_events)
> -		      gdb_printf (_("[%s exited]\n"),
> -				  target_pid_to_str (retval).c_str ());
>  		    delete_thread (find_thread_ptid (this, retval));
>  		    status->set_spurious ();
>  		    return retval;
> diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
> index 4d5e09db680..be5597c4a2e 100644
> --- a/gdb/python/py-inferior.c
> +++ b/gdb/python/py-inferior.c
> @@ -360,7 +360,9 @@ add_thread_object (struct thread_info *tp)
>  }
>  
>  static void
> -delete_thread_object (struct thread_info *tp, int ignore)
> +delete_thread_object (thread_info *tp,
> +		      gdb::optional<ULONGEST> /* exit_code */,
> +		      bool /* silent */)
>  {
>    if (!gdb_python_initialized)
>      return;
> diff --git a/gdb/thread.c b/gdb/thread.c
> index 2ca3a867d8c..7ab30562fd3 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -192,7 +192,8 @@ clear_thread_inferior_resources (struct thread_info *tp)
>  /* See gdbthread.h.  */
>  
>  void
> -set_thread_exited (thread_info *tp, bool silent)
> +set_thread_exited (thread_info *tp, gdb::optional<ULONGEST> exit_code,
> +		   bool silent)
>  {
>    /* Dead threads don't need to step-over.  Remove from chain.  */
>    if (thread_is_in_step_over_chain (tp))
> @@ -211,7 +212,22 @@ set_thread_exited (thread_info *tp, bool silent)
>        if (proc_target != nullptr)
>  	proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
>  
> -      gdb::observers::thread_exit.notify (tp, silent);
> +      if (!silent && print_thread_events)
> +	{
> +	  if (exit_code.has_value ())
> +	    {
> +	      gdb_printf (_("[%s exited with code %s]\n"),
> +			  target_pid_to_str (tp->ptid).c_str (),
> +			  pulongest (*exit_code));
> +	    }
> +	  else
> +	    {
> +	      gdb_printf (_("[%s exited]\n"),
> +			  target_pid_to_str (tp->ptid).c_str ());
> +	    }
> +	}
> +
> +      gdb::observers::thread_exit.notify (tp, exit_code, silent);
>  
>        /* Tag it as exited.  */
>        tp->state = THREAD_EXITED;
> @@ -468,20 +484,22 @@ global_thread_step_over_chain_remove (struct thread_info *tp)
>    global_thread_step_over_list.erase (it);
>  }
>  
> -/* Delete the thread referenced by THR.  If SILENT, don't notify
> -   the observer of this exit.
> -   
> -   THR must not be NULL or a failed assertion will be raised.  */
> +/* Helper for the different delete_thread variants.  */
>  
>  static void
> -delete_thread_1 (thread_info *thr, bool silent)
> +delete_thread_1 (thread_info *thr, gdb::optional<ULONGEST> exit_code,
> +		 bool silent)
>  {
>    gdb_assert (thr != nullptr);
>  
> -  threads_debug_printf ("deleting thread %s, silent = %d",
> -			thr->ptid.to_string ().c_str (), silent);
> +  threads_debug_printf ("deleting thread %s, exit_code = %s, silent = %d",
> +			thr->ptid.to_string ().c_str (),
> +			(exit_code.has_value ()
> +			 ? pulongest (*exit_code)
> +			 : "<none>"),
> +			silent);
>  
> -  set_thread_exited (thr, silent);
> +  set_thread_exited (thr, exit_code, silent);
>  
>    if (!thr->deletable ())
>      {
> @@ -497,16 +515,25 @@ delete_thread_1 (thread_info *thr, bool silent)
>  
>  /* See gdbthread.h.  */
>  
> +void
> +delete_thread_with_exit_code (thread_info *thread, ULONGEST exit_code,
> +			      bool silent)
> +{
> +  delete_thread_1 (thread, exit_code, false /* not silent */);
> +}
> +
> +/* See gdbthread.h.  */
> +
>  void
>  delete_thread (thread_info *thread)
>  {
> -  delete_thread_1 (thread, false /* not silent */);
> +  delete_thread_1 (thread, {}, false /* not silent */);
>  }
>  
>  void
>  delete_thread_silent (thread_info *thread)
>  {
> -  delete_thread_1 (thread, true /* silent */);
> +  delete_thread_1 (thread, {}, true /* not silent */);
>  }
>  
>  struct thread_info *
> diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
> index ee4e78bdabf..2764fc694b3 100644
> --- a/gdb/windows-nat.c
> +++ b/gdb/windows-nat.c
> @@ -611,21 +611,13 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
>  
>    id = ptid.lwp ();
>  
> -  /* Emit a notification about the thread being deleted.
> -
> -     Note that no notification was printed when the main thread
> +  /* Note that no notification was printed when the main thread
>       was created, and thus, unless in verbose mode, we should be
>       symmetrical, and avoid that notification for the main thread
>       here as well.  */
> -
> -  if (info_verbose)
> -    gdb_printf ("[Deleting %s]\n", target_pid_to_str (ptid).c_str ());
> -  else if (print_thread_events && !main_thread_p)
> -    gdb_printf (_("[%s exited with code %u]\n"),
> -		target_pid_to_str (ptid).c_str (),
> -		(unsigned) exit_code);
> -
> -  ::delete_thread (find_thread_ptid (this, ptid));
> +  bool silent = (main_thread_p && !info_verbose);
> +  thread_info *todel = find_thread_ptid (this, ptid);
> +  delete_thread_with_exit_code (todel, exit_code, silent);
>  
>    auto iter = std::find_if (windows_process.thread_list.begin (),
>  			    windows_process.thread_list.end (),
> -- 
> 2.36.0


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

* Re: [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal
  2023-02-03 10:44   ` Andrew Burgess
@ 2023-03-10 17:15     ` Pedro Alves
  2023-03-16 16:07       ` Andrew Burgess
  0 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2023-03-10 17:15 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-02-03 10:44 a.m., Andrew Burgess wrote:
> Pedro Alves <pedro@palves.net> writes:
> 
>> This commit tweaks displaced_step_finish & friends to pass down a
>> target_waitstatus instead of a gdb_signal.  This needed because a
> 
> missing word: "This IS needed".

Fixed.

>> @@ -5699,7 +5696,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);
> 
> This change is interesting.
> 
> If I understand the code correctly, this call will eventually end up in
> displaced_step_buffers::finish (displaced-stepping.c), which in turn
> calls displaced_step_instruction_executed_successfully.
> 
> Previously, we always passed GDB_SIGNAL_TRAP here, which (if we ignore
> the watchpoint check in
> displaced_step_instruction_executed_successfully) means that
> displaced_step_instruction_executed_successfully would always return
> true, and then displaced_step_buffers::finish would call
> gdbarch_displaced_step_fixup.
> 
> After this change, we know that esc->ws.kind is either
> TARGET_WAITKIND_FORKED or  TARGET_WAITKIND_VFORKED, so we know that
> displaced_step_instruction_executed_successfully will always return
> false, and displaced_step_buffers::finish will no longer call
> gdbarch_displaced_step_fixup.
> 

Good catch!

I was tweaking the change to address your comment, and was coming to the
conclusion that what I really wanted was this:

static bool
displaced_step_instruction_executed_successfully
  (gdbarch *arch, const target_waitstatus &status)
{
  if (status.kind () == TARGET_WAITKIND_STOPPED
      && status.sig () != GDB_SIGNAL_TRAP)
    return false;

  /* All other waitkinds can only happen if the instruction fully
     executed.  For example, a fork, or a syscall entry can only
     happen if the syscall instruction actually executed.  */

(the comment is new)

And then, I remembered, I actually wrote that early if like that originally,
and I changed it in response to this review:

 https://inbox.sourceware.org/gdb-patches/44f74af8-248b-1af8-3612-980c08607bf4@simark.ca/

The review was totally right, it was my response that was misguided.

But I'm confused because I am pretty sure that I wrote a reply to that
message, saying that I did not intend to change the behavior, so I'd "fix" it.
I can't find it in my outbox either, I guess I erroneously canceled my
email window instead of sending the message...

Anyhow, looks like I made it worse while trying to address Simon's comment.  :-P

So I think I should go back to what I had, like before, and my response
to Simon should have been instead:

 - yes, the change is intended.  If we stopped for an event other than
TARGET_WAITKIND_STOPPED, like for instance, TARGET_WAITKIND_FORKED,
TARGET_WAITKIND_SYSCALL_ENTRY, then it must be that the instruction
executed successfully, otherwise the syscall wouldn't have triggered.

> What I don't understand well enough is what this actually means for a
> running inferior.
> 
> It's odd because the comment in infrun.c (just above your change)
> indicates that to get to this point the displaced step must have
> completed successfully, while after this change, the new code path in
> displaced_step_buffers::finish indicates we believe the displaced step
> didn't complete successfully:
> 
>   /* Since the instruction didn't complete, all we can do is relocate the
>      PC.  */
> 
> Do you know if any of our test cases hit this path?
> 

I know that you posted a series for this, which I plan to take a good look
at (I actually planned on doing that earlier this week, but I had a couple
major distractions, sorry).

WDYT of the version below?

From 0ccfd6822ef83eddb48c6f7bf21533858f177bc9 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 22 Jun 2021 15:42:51 +0100
Subject: [PATCH] displaced step: pass down target_waitstatus instead of
 gdb_signal

This commit tweaks displaced_step_finish & friends to pass down a
target_waitstatus instead of a gdb_signal.  This is 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 later in
the series.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
Change-Id: I4c5d338647b028071bc498c4e47063795a2db4c0
---
 gdb/displaced-stepping.c  | 16 +++++++++++-----
 gdb/displaced-stepping.h  |  2 +-
 gdb/gdbarch-gen.h         |  4 ++--
 gdb/gdbarch.c             |  4 ++--
 gdb/gdbarch_components.py |  2 +-
 gdb/infrun.c              | 17 +++++++----------
 gdb/linux-tdep.c          |  5 +++--
 gdb/linux-tdep.h          |  2 +-
 gdb/rs6000-tdep.c         |  4 ++--
 9 files changed, 30 insertions(+), 26 deletions(-)

diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
index 06b32a80f6a..08ae1ce4397 100644
--- a/gdb/displaced-stepping.c
+++ b/gdb/displaced-stepping.c
@@ -192,12 +192,18 @@ 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;
 
+  /* All other (thread event) waitkinds can only happen if the
+     instruction fully executed.  For example, a fork, or a syscall
+     entry can only happen if the syscall instruction actually
+     executed.  */
+
   if (target_stopped_by_watchpoint ())
     {
       if (gdbarch_have_nonsteppable_watchpoint (arch)
@@ -210,7 +216,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 ());
 
@@ -256,7 +262,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 e154927ad92..d2c3fc1b2b4 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-gen.h b/gdb/gdbarch-gen.h
index ddb97f60315..5805dcb75ef 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1103,8 +1103,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 7e4e34d5aca..0b122a86055 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -4096,13 +4096,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/gdbarch_components.py b/gdb/gdbarch_components.py
index caa65c334ec..8d72bff347e 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -1878,7 +1878,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/infrun.c b/gdb/infrun.c
index ab77300f1ff..99f2a8e039d 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1895,7 +1895,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;
 
@@ -1917,7 +1918,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
@@ -5122,7 +5123,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.  */
@@ -5137,7 +5138,6 @@ handle_one (const wait_one_event &event)
 	}
       else
 	{
-	  enum gdb_signal sig;
 	  struct regcache *regcache;
 
 	  infrun_debug_printf
@@ -5148,10 +5148,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.  */
@@ -5753,7 +5750,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 ();
@@ -6118,7 +6115,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 e6ce13a1c67..036ea9f931d 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -2621,13 +2621,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 16e1b806b26..e09a6ef32b1 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.  */
 
diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
index 104515de030..f9bbc435184 100644
--- a/gdb/rs6000-tdep.c
+++ b/gdb/rs6000-tdep.c
@@ -1088,13 +1088,13 @@ ppc_displaced_step_prepare  (gdbarch *arch, thread_info *thread,
 
 static displaced_step_finish_status
 ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
-			   gdb_signal sig)
+			   const target_waitstatus &status)
 {
   ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
 
   gdb_assert (per_inferior->disp_step_buf.has_value ());
 
-  return per_inferior->disp_step_buf->finish (arch, thread, sig);
+  return per_inferior->disp_step_buf->finish (arch, thread, status);
 }
 
 /* Implementation of gdbarch_displaced_step_restore_all_in_ptid.  */

base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
-- 
2.36.0


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

* Re: [PATCH 02/31] linux-nat: introduce pending_status_str
  2023-02-03 12:00   ` Andrew Burgess
@ 2023-03-10 17:15     ` Pedro Alves
  2023-03-16 16:19       ` Andrew Burgess
  0 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2023-03-10 17:15 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-02-03 12:00 p.m., Andrew Burgess wrote:
> Pedro Alves <pedro@palves.net> writes:
> 
>> 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.
> 
> This patch looks fine.  I had one slightly related question:  I took a
> look at the comment on lwp_info::waitstatus in linux-nat.h, which says:
> 
>   /* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
>      for this LWP's last event.  This may correspond to STATUS above,
>      or to a local variable in lin_lwp_wait.  */
>   struct target_waitstatus waitstatus;
> 
> Am I right in thinking that this comment is wrong; it should say
> TARGET_WAITKIND_IGNORE, not TARGET_WAITKIND_SPURIOUS, right?

You're right.

I tweaked the comments in linux-nat.h in this new version.  Let me know what
you think.

From 10f88baff2e25fb87f37d1665bf283206171dd42 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Fri, 12 Nov 2021 20:50:29 +0000
Subject: [PATCH] linux-nat: introduce pending_status_str

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.

Also fix the comment in gdb/linux-nat.h describing
lwp_info::waitstatus and details the description of lwp_info::status
while at it.

Change-Id: I66e5c7a363d30a925b093b195d72925ce5b6b980
---
 gdb/linux-nat.c | 19 ++++++++++++++++---
 gdb/linux-nat.h | 11 +++++++----
 2 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index e5391b9ce35..9d811bbf3ff 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -255,6 +255,19 @@ 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)
+{
+  gdb_assert (lwp_status_pending_p (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 +1660,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 +3150,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 ());
     }
 
diff --git a/gdb/linux-nat.h b/gdb/linux-nat.h
index 45534c92386..770fe924427 100644
--- a/gdb/linux-nat.h
+++ b/gdb/linux-nat.h
@@ -232,7 +232,9 @@ struct lwp_info : intrusive_list_node<lwp_info>
   /* The last resume GDB requested on this thread.  */
   resume_kind last_resume_kind = resume_continue;
 
-  /* If non-zero, a pending wait status.  */
+  /* If non-zero, a pending wait status.  A pending process exit is
+     recorded in WAITSTATUS, because W_EXITCODE(0,0) happens to be
+     0.  */
   int status = 0;
 
   /* When 'stopped' is set, this is where the lwp last stopped, with
@@ -260,9 +262,10 @@ struct lwp_info : intrusive_list_node<lwp_info>
   /* Non-zero if we expect a duplicated SIGINT.  */
   int ignore_sigint = 0;
 
-  /* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
-     for this LWP's last event.  This may correspond to STATUS above,
-     or to a local variable in lin_lwp_wait.  */
+  /* If WAITSTATUS->KIND != TARGET_WAITKIND_IGNORE, the waitstatus for
+     this LWP's last event.  This usually corresponds to STATUS above,
+     however because W_EXITCODE(0,0) happens to be 0, a process exit
+     will be recorded here, while 'status == 0' is ambiguous.  */
   struct target_waitstatus waitstatus;
 
   /* Signal whether we are in a SYSCALL_ENTRY or

base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
prerequisite-patch-id: bbc9918ac5f79de07a29f34ec072794d270f942d
-- 
2.36.0


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

* Re: [PATCH 04/31] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED
  2023-02-04 15:38   ` Andrew Burgess
@ 2023-03-10 17:16     ` Pedro Alves
  2023-03-21 16:06       ` Andrew Burgess
  0 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2023-03-10 17:16 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-02-04 3:38 p.m., Andrew Burgess wrote:
> Pedro Alves <pedro@palves.net> writes:

>> +/* 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.  */
>> +
>> +  if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
> 
> I know this is just a style issue, but I'm really not a fan of this
> assignment in the if condition.  I know it probably seems silly, but I'd
> find it much clearer if we did:
> 
>   gdb::optional<target_waitstatus> ws = get_pending_child_status (lp)
>   if (ws.has_value ())
>     ...
> 
> The first time I read the initial line, my first thought was s/=/==/,
> then I had to read it again and figure out what was going on...
> 

Ah, I think I was asked to switch to this style in a previous review, so
clearly it's a matter of taste.  :-D

I was using this originally:

    target_waitstatus ws;
    if (get_pending_child_status (lp, &ws))
      {


I used to think that "if (auto opt = foo ())" might be confusing too, to be honest,
but I've convinced myself that it is OK (but please see further below) for a couple
reasons:

#1 - these aren't really equivalent:

    a) if (type var = func ())

    b) itype var = func ();
       if (var)

  because in a), the scope of the variable is just the if then/else
  scopes.  So you can for example reuse the name like:

    if (type var = func ())
       {
       }

    if (type var = bar ())
       {
       }

  and also you're guaranteed that the variable's dtor is run at the end of the
  if block.  So it'd be more equivalent to writing an explicit scope, like:

       {
         itype var = func ();
         if (var)
           {
             ...
           }
       }

#2 - the presence of the type makes it visually distinctive from the problematic case
     of writing to a variable by accident:

       if (var = func ())        // bad

       if (var == func ())       // ok
  
       if (auto var = func ())   // ok

       if (auto var == func ())  // not valid


     and, it's not different from initializing a variable in a for, like:

       for (auto var = func (); ...; ...)

     and we don't bat an eye when we see that.

     in fact, C++17 let's you write an if with a for-like init-statement, like so:

       if (auto var = func (); var)

     so I think this is a case of being surprised at first, but then we just
     get used to it quickly.


However, I've still converted to use the style you suggested, because
there might be a different reason for wanting to avoid that style, which is that
this:

   if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))

begs the question of whether we want to allow:

   if (foo *ptr = get_foo ())

and this runs counter to our style of doing explicit nullptr checks...  So
we can leave that to a seperate discussion and I don't want to be blocked
by it.  :-)


>> diff --git a/gdb/target.h b/gdb/target.h
>> index 28aa9273893..aab390aec57 100644
>> --- a/gdb/target.h
>> +++ b/gdb/target.h
>> @@ -637,6 +637,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);
> 
> One thing that sucks about some of the older parts of the target API is
> just how undocumented it is.  The only way to figure out what things
> like follow_fork do is to look at the code.

I don't disagree completely, but note that the comments for the target_ops methods
tend to be in the target_foo wrapper method.  For example, target_follow_fork.
In this case, there's no such wrapper method, so I have no good excuse.  :-)

I've added a comment, like:

--- c/gdb/target.h
+++ w/gdb/target.h
@@ -637,8 +637,13 @@ 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)
+
+    /* Add CHILD_PTID to the thread list, after handling a
+       TARGET_WAITKIND_THREAD_CLONE event for the clone parent.  The
+       parent is inferior_ptid.  */
+    virtual void follow_clone (ptid_t child_ptid)
       TARGET_DEFAULT_FUNC (default_follow_clone);
+
     virtual int insert_exec_catchpoint (int)
       TARGET_DEFAULT_RETURN (1);
     virtual int remove_exec_catchpoint (int)

> 
> Some of the newer methods do have good comments.  Or at least comments
> that give a good hint to what the method does.
> 
> I'd be really grateful if new target API methods could be given a good
> comment in target.h.

Here's the full patch.  Let me know what you think.

From 3b2638cf7507146385e16473c883b7d0d4a75277 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Fri, 12 Nov 2021 20:50:29 +0000
Subject: [PATCH] Step over clone syscall w/ breakpoint,
 TARGET_WAITKIND_THREAD_CLONED

(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 affected, 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 held 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 becomes unused with this patch, but
a new use will reappear later in the series.  To avoid deleting it and
readding it back, this patch marks it with attribute unused, and the
latter patch removes the attribute again.  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            | 158 ++++++++++++++-----------
 gdb/linux-nat.c         | 250 +++++++++++++++++++++-------------------
 gdb/linux-nat.h         |   2 +
 gdb/target-delegates.c  |  24 ++++
 gdb/target.c            |   7 ++
 gdb/target.h            |   2 +
 gdb/target/waitstatus.c |   1 +
 gdb/target/waitstatus.h |  31 ++++-
 8 files changed, 283 insertions(+), 192 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 99f2a8e039d..d1e6233591c 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1584,6 +1584,7 @@ step_over_info_valid_p (void)
 /* Return true if THREAD is doing a displaced step.  */
 
 static bool
+ATTRIBUTE_UNUSED
 displaced_step_in_progress_thread (thread_info *thread)
 {
   gdb_assert (thread != nullptr);
@@ -1898,6 +1899,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?  */
@@ -1917,8 +1943,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
@@ -5717,67 +5774,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);
 
@@ -5793,7 +5796,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.  */
@@ -5825,14 +5828,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.  */
@@ -5843,16 +5856,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 5f67bcbcb4f..6db21e809c5 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1286,64 +1286,80 @@ 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.  */
+/* If LP has a pending fork/vfork/clone status, return it.  */
 
-static void
-detach_one_lwp (struct lwp_info *lp, int *signo_p)
+static gdb::optional<target_waitstatus>
+get_pending_child_status (lwp_info *lp)
 {
-  int lwpid = lp->ptid.lwp ();
-  int signo;
-
-  gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
-
-  /* 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.  */
-
   /* 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);
+	    {
+	      target_waitstatus ws;
+
+	      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 ws;
+	    }
 	  else
-	    perror_warning_with_name (_("Failed to detach fork child"));
+	    {
+	      perror_warning_with_name (_("Failed to retrieve event msg"));
+	      return {};
+	    }
 	}
     }
 
   /* 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_new_child_status (lp->waitstatus.kind ()))
+    return lp->waitstatus;
 
-  /* Check in thread_info::pending_waitstatus.  */
   thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
-  if (tp->has_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);
-    }
+  /* Check in thread_info::pending_waitstatus.  */
+  if (tp->has_pending_waitstatus ()
+      && is_new_child_status (tp->pending_waitstatus ().kind ()))
+    return tp->pending_waitstatus ();
 
   /* 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_new_child_status (tp->pending_follow.kind ()))
+    return tp->pending_follow;
 
-  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 {};
+}
+
+/* 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.  */
+
+  gdb::optional<target_waitstatus> ws = get_pending_child_status (lp);
+  if (ws.has_value ())
+    detach_one_pid (ws->child_ptid ().lwp (), 0);
 
   /* If there is a pending SIGSTOP, get rid of it.  */
   if (lp->signalled)
@@ -1821,6 +1837,53 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
   return 1;
 }
 
+void
+linux_nat_target::follow_clone (ptid_t child_ptid)
+{
+  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.  */
+      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 (_("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 +1924,9 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
 	    internal_error (_("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,66 +1963,18 @@ 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.  */
-	      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;
@@ -3536,59 +3549,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 ())
+  gdb::optional<target_waitstatus> ws = get_pending_child_status (lp);
+  if (ws.has_value ())
     {
-      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 770fe924427..1cdbeafd4f3 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 57b66ce87b1..7a4ef05b4e1 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 0cebecfafc3..9835222e5da 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2701,6 +2701,13 @@ default_follow_fork (struct target_ops *self, inferior *child_inf,
   internal_error (_("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 (_("could not find a target to follow clone"));
+}
+
 /* See target.h.  */
 
 void
diff --git a/gdb/target.h b/gdb/target.h
index 2dac86c394d..43ab71093d9 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -637,6 +637,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 2b8404fb75b..a8edbb17d60 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 0a88b869044..4c3de005315 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,
 
@@ -102,6 +109,17 @@ enum target_waitkind
   TARGET_WAITKIND_THREAD_EXITED,
 };
 
+/* Determine if KIND represents an event with a new child - a fork,
+   vfork, or clone.  */
+
+static inline bool
+is_new_child_status (target_waitkind kind)
+{
+  return (kind == TARGET_WAITKIND_FORKED
+	  || kind == TARGET_WAITKIND_VFORKED
+	  || kind == TARGET_WAITKIND_THREAD_CLONED);
+}
+
 /* Return KIND as a string.  */
 
 static inline const char *
@@ -125,6 +143,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 +345,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 ();
@@ -369,8 +397,7 @@ struct target_waitstatus
 
   ptid_t child_ptid () const
   {
-    gdb_assert (m_kind == TARGET_WAITKIND_FORKED
-		|| m_kind == TARGET_WAITKIND_VFORKED);
+    gdb_assert (is_new_child_status (m_kind));
     return m_value.child_ptid;
   }
 

base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
prerequisite-patch-id: bbc9918ac5f79de07a29f34ec072794d270f942d
prerequisite-patch-id: c0bc5b4f99193bb50cb31f551673de1808dcda35
prerequisite-patch-id: ab0838f2bc02931d7e830abe23833e7a8224442c
-- 
2.36.0


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

* Re: [PATCH 30/31] Centralize "[Thread ...exited]" notifications
  2023-02-04 16:05   ` Andrew Burgess
@ 2023-03-10 17:21     ` Pedro Alves
  0 siblings, 0 replies; 100+ messages in thread
From: Pedro Alves @ 2023-03-10 17:21 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-02-04 4:05 p.m., Andrew Burgess wrote:

> You mention gdb.threads/attach-many-short-lived-threads.exp as an
> example, but I don't think that test actually fails due to this issue,
> right?

Right.

> It might be worth mentioning gdb.threads/thread-specific-bp.exp, that is
> a test script that should actually start passing after this commit.
> 

Meanwhile, this:

 commit 89702edd933a5595557bcd9cc4a0dcc3262226d4
 Author:     Tom de Vries <tdevries@suse.de>
 AuthorDate: Thu Mar 9 12:31:26 2023 +0100
 Commit:     Tom de Vries <tdevries@suse.de>
 CommitDate: Thu Mar 9 12:31:26 2023 +0100

    [gdb/testsuite] Fix gdb.threads/thread-specific-bp.exp on native-gdbserver

fixed that test in a different way, so this series no longer has an
effect there.

Your tests for PR gdb/30129 have since gone in too, so I'm referring to
those instead, and also removing their kfails, of course.

Below's the updated patch.  I've also pushed the whole series to the

  users/palves/step-over-thread-exit-v3.1

branch, for your convenience.

Let me know what you think.

From 9ec64120f3208d2ef4294e261e8641184d1bf2b9 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 21 Jun 2022 19:30:48 +0100
Subject: [PATCH] Centralize "[Thread ...exited]" notifications

Currently, each target backend is responsible for printing "[Thread
...exited]" before deleting a thread.  This leads to unnecessary
differences between targets, like e.g. with the remote target, we
never print such messages, even though we do print "[New Thread ...]".

E.g., debugging the gdb.threads/attach-many-short-lived-threads.exp
with gdbserver, letting it run for a bit, and then pressing Ctrl-C, we
currently see:

 (gdb) c
 Continuing.
 ^C[New Thread 3850398.3887449]
 [New Thread 3850398.3887500]
 [New Thread 3850398.3887551]
 [New Thread 3850398.3887602]
 [New Thread 3850398.3887653]
 ...

 Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
 0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
     at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
 78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
 (gdb)

Above, we only see "New Thread" notifications, even though threads
were deleted.

After this patch, we'll see:

 (gdb) c
 Continuing.
 ^C[Thread 3558643.3577053 exited]
 [Thread 3558643.3577104 exited]
 [Thread 3558643.3577155 exited]
 [Thread 3558643.3579603 exited]
 ...
 [New Thread 3558643.3597415]
 [New Thread 3558643.3600015]
 [New Thread 3558643.3599965]
 ...

 Thread 1 "attach-many-sho" received signal SIGINT, Interrupt.
 0x00007ffff7e6a23f in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fffffffda80, rem=rem@entry=0x7fffffffda80)
     at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:78
 78      in ../sysdeps/unix/sysv/linux/clock_nanosleep.c
 (gdb) q


This commit fixes this by moving the thread exit printing to common
code instead, triggered from within delete_thread (or rather,
set_thread_exited).

There's one wrinkle, though.  While most targest want to print:

 [Thread ... exited]

the Windows target wants to print:

 [Thread ... exited with code <exit_code>]

... and sometimes wants to suppress the notification for the main
thread.  To address that, this commits adds a delete_thread_with_code
function, only used by that target (so far).

The fact that remote is missing thread exited messages is PR
remote/30129, and kfailed in gdb.threads/thread-bp-deleted.exp and
gdb.mi/mi-thread-bp-deleted.exp.  This commit removes the now
unnecessary kfails (and kpasses) from those testcases, switching to
simpler gdb_assert instead.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30129
Change-Id: I06ec07b7c51527872a9713dd11cf7867b50fc5ff
---
 gdb/annotate.c                                |  4 +-
 gdb/breakpoint.c                              |  4 +-
 gdb/fbsd-nat.c                                |  3 --
 gdb/gdbthread.h                               | 22 ++++++--
 gdb/inferior.c                                |  2 +-
 gdb/inferior.h                                |  2 +
 gdb/linux-nat.c                               | 11 ++--
 gdb/mi/mi-interp.c                            |  8 ++-
 gdb/netbsd-nat.c                              |  4 --
 gdb/observable.h                              | 11 ++--
 gdb/procfs.c                                  |  6 ---
 gdb/python/py-inferior.c                      |  4 +-
 gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp | 18 ++-----
 .../gdb.threads/thread-bp-deleted.exp         | 12 +----
 gdb/thread.c                                  | 51 ++++++++++++++-----
 gdb/windows-nat.c                             | 16 ++----
 16 files changed, 95 insertions(+), 83 deletions(-)

diff --git a/gdb/annotate.c b/gdb/annotate.c
index 60fe6ccd5c2..a24841f7c0d 100644
--- a/gdb/annotate.c
+++ b/gdb/annotate.c
@@ -233,7 +233,9 @@ annotate_thread_changed (void)
 /* Emit notification on thread exit.  */
 
 static void
-annotate_thread_exited (struct thread_info *t, int silent)
+annotate_thread_exited (thread_info *t,
+			gdb::optional<ULONGEST> exit_code,
+			bool /* silent */)
 {
   if (annotation_level > 1)
     {
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index a42d26fd25a..25c98919d94 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -3243,7 +3243,9 @@ remove_breakpoints (void)
    that thread.  */
 
 static void
-remove_threaded_breakpoints (struct thread_info *tp, int silent)
+remove_threaded_breakpoints (thread_info *tp,
+			     gdb::optional<ULONGEST> exit_code,
+			     bool /* silent */)
 {
   for (breakpoint *b : all_breakpoints_safe ())
     {
diff --git a/gdb/fbsd-nat.c b/gdb/fbsd-nat.c
index 27d2fe45092..612ebcc924f 100644
--- a/gdb/fbsd-nat.c
+++ b/gdb/fbsd-nat.c
@@ -1300,9 +1300,6 @@ fbsd_nat_target::wait_1 (ptid_t ptid, struct target_waitstatus *ourstatus,
 		{
 		  fbsd_lwp_debug_printf ("deleting thread for LWP %u",
 					 pl.pl_lwpid);
-		  if (print_thread_events)
-		    gdb_printf (_("[%s exited]\n"),
-				target_pid_to_str (wptid).c_str ());
 		  low_delete_thread (thr);
 		  delete_thread (thr);
 		}
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 79dedb23d4d..905ed8bddd5 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -636,16 +636,30 @@ extern struct thread_info *add_thread_with_info (process_stratum_target *targ,
 
 /* Delete thread THREAD and notify of thread exit.  If the thread is
    currently not deletable, don't actually delete it but still tag it
-   as exited and do the notification.  */
-extern void delete_thread (struct thread_info *thread);
+   as exited and do the notification.  EXIT_CODE is the thread's exit
+   code.  If SILENT, don't actually notify the CLI.  THREAD must not
+   be NULL or an assertion will fail.  */
+extern void delete_thread_with_exit_code (thread_info *thread,
+					  ULONGEST exit_code,
+					  bool silent = false);
+
+/* Delete thread THREAD and notify of thread exit.  If the thread is
+   currently not deletable, don't actually delete it but still tag it
+   as exited and do the notification.  THREAD must not be NULL or an
+   assertion will fail.  */
+extern void delete_thread (thread_info *thread);
 
 /* Like delete_thread, but be quiet about it.  Used when the process
    this thread belonged to has already exited, for example.  */
 extern void delete_thread_silent (struct thread_info *thread);
 
 /* Mark the thread exited, but don't delete it or remove it from the
-   inferior thread list.  */
-extern void set_thread_exited (thread_info *tp, bool silent);
+   inferior thread list.  EXIT_CODE is the thread's exit code, if
+   available.  If SILENT, then don't inform the CLI about the
+   exit.  */
+extern void set_thread_exited (thread_info *tp,
+			       gdb::optional<ULONGEST> exit_code = {},
+			       bool silent = false);
 
 /* Delete a step_resume_breakpoint from the thread database.  */
 extern void delete_step_resume_breakpoint (struct thread_info *);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 80d53bb4e81..ee710964249 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -223,7 +223,7 @@ inferior::clear_thread_list ()
     {
       threads_debug_printf ("deleting thread %s",
 			    thr->ptid.to_string ().c_str ());
-      set_thread_exited (thr, true);
+      set_thread_exited (thr, {}, true);
       if (thr->deletable ())
 	delete thr;
     });
diff --git a/gdb/inferior.h b/gdb/inferior.h
index d64e7cc015c..4c2d0505a91 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -697,6 +697,8 @@ extern void detach_inferior (inferior *inf);
 
 extern void exit_inferior (inferior *inf);
 
+/* Like exit_inferior, but be quiet -- don't announce the exit of the
+   inferior's threads to the CLI.  */
 extern void exit_inferior_silent (inferior *inf);
 
 extern void exit_inferior_num_silent (int num);
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 043c9a0489c..dcd5d07728d 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -916,15 +916,10 @@ linux_nat_switch_fork (ptid_t new_ptid)
 static void
 exit_lwp (struct lwp_info *lp, bool del_thread = true)
 {
-  struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
-
-  if (th)
+  if (del_thread)
     {
-      if (print_thread_events)
-	gdb_printf (_("[%s exited]\n"),
-		    target_pid_to_str (lp->ptid).c_str ());
-
-      if (del_thread)
+      thread_info *th = find_thread_ptid (linux_target, lp->ptid);
+      if (th != nullptr)
 	delete_thread (th);
     }
 
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index e1244f3df43..dd1dc3126eb 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -68,7 +68,9 @@ static void mi_on_normal_stop (struct bpstat *bs, int print_frame);
 static void mi_on_no_history (void);
 
 static void mi_new_thread (struct thread_info *t);
-static void mi_thread_exit (struct thread_info *t, int silent);
+static void mi_thread_exit (thread_info *t,
+			    gdb::optional<ULONGEST> exit_code,
+			    bool silent);
 static void mi_record_changed (struct inferior*, int, const char *,
 			       const char *);
 static void mi_inferior_added (struct inferior *inf);
@@ -345,8 +347,10 @@ mi_new_thread (struct thread_info *t)
     }
 }
 
+/* Observer for the thread_exit notification.  */
+
 static void
-mi_thread_exit (struct thread_info *t, int silent)
+mi_thread_exit (thread_info *t, gdb::optional<ULONGEST> exit_code, bool silent)
 {
   SWITCH_THRU_ALL_UIS ()
     {
diff --git a/gdb/netbsd-nat.c b/gdb/netbsd-nat.c
index 37f2fa49b0b..133a3cf788f 100644
--- a/gdb/netbsd-nat.c
+++ b/gdb/netbsd-nat.c
@@ -625,10 +625,6 @@ nbsd_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
 	{
 	  /* NetBSD does not store an LWP exit status.  */
 	  ourstatus->set_thread_exited (0);
-
-	  if (print_thread_events)
-	    gdb_printf (_("[%s exited]\n"),
-			target_pid_to_str (wptid).c_str ());
 	}
 
       /* The GDB core expects that the rest of the threads are running.  */
diff --git a/gdb/observable.h b/gdb/observable.h
index efd0446e168..d6be15348d5 100644
--- a/gdb/observable.h
+++ b/gdb/observable.h
@@ -126,10 +126,13 @@ extern observable<struct objfile */* objfile */> free_objfile;
 /* The thread specified by T has been created.  */
 extern observable<struct thread_info */* t */> new_thread;
 
-/* The thread specified by T has exited.  The SILENT argument
-   indicates that gdb is removing the thread from its tables without
-   wanting to notify the user about it.  */
-extern observable<struct thread_info */* t */, int /* silent */> thread_exit;
+/* The thread specified by T has exited.  EXIT_CODE is the thread's
+   exit code, if available.  The SILENT argument indicates that GDB is
+   removing the thread from its tables without wanting to notify the
+   CLI about it.  */
+extern observable<thread_info */* t */,
+		  gdb::optional<ULONGEST> /* exit_code */,
+		  bool /* silent */> thread_exit;
 
 /* An explicit stop request was issued to PTID.  If PTID equals
    minus_one_ptid, the request applied to all threads.  If
diff --git a/gdb/procfs.c b/gdb/procfs.c
index 741e62a2402..a8a50c4ddc5 100644
--- a/gdb/procfs.c
+++ b/gdb/procfs.c
@@ -2115,9 +2115,6 @@ procfs_target::wait (ptid_t ptid, struct target_waitstatus *status,
 	      case PR_SYSENTRY:
 		if (what == SYS_lwp_exit)
 		  {
-		    if (print_thread_events)
-		      gdb_printf (_("[%s exited]\n"),
-				  target_pid_to_str (retval).c_str ());
 		    delete_thread (find_thread_ptid (this, retval));
 		    target_continue_no_signal (ptid);
 		    goto wait_again;
@@ -2222,9 +2219,6 @@ procfs_target::wait (ptid_t ptid, struct target_waitstatus *status,
 		  }
 		else if (what == SYS_lwp_exit)
 		  {
-		    if (print_thread_events)
-		      gdb_printf (_("[%s exited]\n"),
-				  target_pid_to_str (retval).c_str ());
 		    delete_thread (find_thread_ptid (this, retval));
 		    status->set_spurious ();
 		    return retval;
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index 8b21f28afbe..5bb9d6a9fdc 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -360,7 +360,9 @@ add_thread_object (struct thread_info *tp)
 }
 
 static void
-delete_thread_object (struct thread_info *tp, int ignore)
+delete_thread_object (thread_info *tp,
+		      gdb::optional<ULONGEST> /* exit_code */,
+		      bool /* silent */)
 {
   if (!gdb_python_initialized)
     return;
diff --git a/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp b/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp
index 0ebca924801..6830991cc81 100644
--- a/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp
+++ b/gdb/testsuite/gdb.mi/mi-thread-bp-deleted.exp
@@ -233,19 +233,11 @@ foreach_mi_ui_mode mode {
 		exp_continue
 	    }
 
-	    # The output has arrived!  Check how we did.  There are other bugs
-	    # that come into play here which change what output we'll see.
-	    if { $saw_mi_thread_exited && $saw_mi_bp_deleted \
-		     && $saw_cli_thread_exited \
-		     && $saw_cli_bp_deleted } {
-		kpass "gdb/30129" $gdb_test_name
-	    } elseif { $saw_mi_thread_exited && $saw_mi_bp_deleted \
-			   && !$saw_cli_thread_exited \
-			   && $saw_cli_bp_deleted } {
-		kfail "gdb/30129" $gdb_test_name
-	    } else {
-		fail "$gdb_test_name"
-	    }
+	    # The output has arrived!  Check how we did.
+	    gdb_assert { $saw_mi_thread_exited && $saw_mi_bp_deleted \
+			     && $saw_cli_thread_exited \
+			     && $saw_cli_bp_deleted } \
+		$gdb_test_name
 	}
     }
 
diff --git a/gdb/testsuite/gdb.threads/thread-bp-deleted.exp b/gdb/testsuite/gdb.threads/thread-bp-deleted.exp
index 019bdddee81..92178ff838b 100644
--- a/gdb/testsuite/gdb.threads/thread-bp-deleted.exp
+++ b/gdb/testsuite/gdb.threads/thread-bp-deleted.exp
@@ -155,17 +155,7 @@ if {$is_remote} {
 		exp_continue
 	    }
 
-	    # When PR gdb/30129 is fixed then this can all be collapsed down
-	    # into a single gdb_assert call.  This is split out like this
-	    # because the SAW_BP_DELETED part is working, and we want to
-	    # spot if that stops working.
-	    if { $saw_thread_exited && $saw_bp_deleted } {
-		kpass "gdb/30129" $gdb_test_name
-	    } elseif {!$saw_thread_exited && $saw_bp_deleted} {
-		kfail "gdb/30129" $gdb_test_name
-	    } else {
-		fail $gdb_test_name
-	    }
+	    gdb_assert { $saw_thread_exited && $saw_bp_deleted } $gdb_test_name
 	}
     }
 } else {
diff --git a/gdb/thread.c b/gdb/thread.c
index 0c5039530b0..d6b3f074e78 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -193,7 +193,8 @@ clear_thread_inferior_resources (struct thread_info *tp)
 /* See gdbthread.h.  */
 
 void
-set_thread_exited (thread_info *tp, bool silent)
+set_thread_exited (thread_info *tp, gdb::optional<ULONGEST> exit_code,
+		   bool silent)
 {
   /* Dead threads don't need to step-over.  Remove from chain.  */
   if (thread_is_in_step_over_chain (tp))
@@ -212,7 +213,22 @@ set_thread_exited (thread_info *tp, bool silent)
       if (proc_target != nullptr)
 	proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
 
-      gdb::observers::thread_exit.notify (tp, silent);
+      if (!silent && print_thread_events)
+	{
+	  if (exit_code.has_value ())
+	    {
+	      gdb_printf (_("[%s exited with code %s]\n"),
+			  target_pid_to_str (tp->ptid).c_str (),
+			  pulongest (*exit_code));
+	    }
+	  else
+	    {
+	      gdb_printf (_("[%s exited]\n"),
+			  target_pid_to_str (tp->ptid).c_str ());
+	    }
+	}
+
+      gdb::observers::thread_exit.notify (tp, exit_code, silent);
 
       /* Tag it as exited.  */
       tp->state = THREAD_EXITED;
@@ -469,20 +485,22 @@ global_thread_step_over_chain_remove (struct thread_info *tp)
   global_thread_step_over_list.erase (it);
 }
 
-/* Delete the thread referenced by THR.  If SILENT, don't notify
-   the observer of this exit.
-   
-   THR must not be NULL or a failed assertion will be raised.  */
+/* Helper for the different delete_thread variants.  */
 
 static void
-delete_thread_1 (thread_info *thr, bool silent)
+delete_thread_1 (thread_info *thr, gdb::optional<ULONGEST> exit_code,
+		 bool silent)
 {
   gdb_assert (thr != nullptr);
 
-  threads_debug_printf ("deleting thread %s, silent = %d",
-			thr->ptid.to_string ().c_str (), silent);
+  threads_debug_printf ("deleting thread %s, exit_code = %s, silent = %d",
+			thr->ptid.to_string ().c_str (),
+			(exit_code.has_value ()
+			 ? pulongest (*exit_code)
+			 : "<none>"),
+			silent);
 
-  set_thread_exited (thr, silent);
+  set_thread_exited (thr, exit_code, silent);
 
   if (!thr->deletable ())
     {
@@ -498,16 +516,25 @@ delete_thread_1 (thread_info *thr, bool silent)
 
 /* See gdbthread.h.  */
 
+void
+delete_thread_with_exit_code (thread_info *thread, ULONGEST exit_code,
+			      bool silent)
+{
+  delete_thread_1 (thread, exit_code, false /* not silent */);
+}
+
+/* See gdbthread.h.  */
+
 void
 delete_thread (thread_info *thread)
 {
-  delete_thread_1 (thread, false /* not silent */);
+  delete_thread_1 (thread, {}, false /* not silent */);
 }
 
 void
 delete_thread_silent (thread_info *thread)
 {
-  delete_thread_1 (thread, true /* silent */);
+  delete_thread_1 (thread, {}, true /* not silent */);
 }
 
 struct thread_info *
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 26ad04b27be..03123e777eb 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -612,21 +612,13 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
 
   id = ptid.lwp ();
 
-  /* Emit a notification about the thread being deleted.
-
-     Note that no notification was printed when the main thread
+  /* Note that no notification was printed when the main thread
      was created, and thus, unless in verbose mode, we should be
      symmetrical, and avoid that notification for the main thread
      here as well.  */
-
-  if (info_verbose)
-    gdb_printf ("[Deleting %s]\n", target_pid_to_str (ptid).c_str ());
-  else if (print_thread_events && !main_thread_p)
-    gdb_printf (_("[%s exited with code %u]\n"),
-		target_pid_to_str (ptid).c_str (),
-		(unsigned) exit_code);
-
-  ::delete_thread (find_thread_ptid (this, ptid));
+  bool silent = (main_thread_p && !info_verbose);
+  thread_info *todel = find_thread_ptid (this, ptid);
+  delete_thread_with_exit_code (todel, exit_code, silent);
 
   auto iter = std::find_if (windows_process.thread_list.begin (),
 			    windows_process.thread_list.end (),

base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
prerequisite-patch-id: bbc9918ac5f79de07a29f34ec072794d270f942d
prerequisite-patch-id: c0bc5b4f99193bb50cb31f551673de1808dcda35
prerequisite-patch-id: ab0838f2bc02931d7e830abe23833e7a8224442c
prerequisite-patch-id: f00d4a73e58c28ff2e92e7bfd5f644503de81054
prerequisite-patch-id: cc6043ae4b28f0c93f798c5864393509d793ab28
prerequisite-patch-id: 254a23b7d7cec889924daaf288304494c93fe1aa
prerequisite-patch-id: b1fe92da846e52cce1e9f13498cf668c5cdd6ee4
prerequisite-patch-id: 775dbe2e67b84cb4fcbcf1a1d787135ffed616ce
prerequisite-patch-id: b93a162a108b255e818453c05f2fe0301bbf05a3
prerequisite-patch-id: ea5513927a9e5e2ed866004a5a4d49fd6aff4f73
prerequisite-patch-id: 9792772eeaeb80692bfea4ab089a57b371b1979c
prerequisite-patch-id: 4e5f0e4dbbf8aeba626e23d37e6d31ed815385dc
prerequisite-patch-id: 8a36a3aaa1a67508b457e0531babe6202e91bbd2
prerequisite-patch-id: 4ff7d91769a04054d9dd04827017b37d57ec6aca
prerequisite-patch-id: 6615810ee06c2bd62885ac620dd693e851ab9c21
prerequisite-patch-id: f4a22f1604e46c73ab3847459af3b77ec36f8ff8
prerequisite-patch-id: b836d8555b80b08b380405d9bd1d2724f750dc8e
prerequisite-patch-id: dc3c3f4f4a1d30557b636c8c7cefc5dc2cb8fa97
prerequisite-patch-id: 7941c40b2bc5d5b7384a376f0733a7bbb1646484
prerequisite-patch-id: b8261218aa0bb0a40ae72a72ee24ad8c01efce9a
prerequisite-patch-id: ab8af04341457c6db4c0bff3d85b0adec2113970
prerequisite-patch-id: 084b68a90f8fdd2cfe667a1cbe0c8b171b4551f3
prerequisite-patch-id: a05d1cc3d645a3765fad072cf93d7d803442dba4
prerequisite-patch-id: 64b5e99813b6507ee9d6adec09746227eb3a006b
prerequisite-patch-id: 0d2098a7d10285588614db649783d013684f55a8
prerequisite-patch-id: ad7e5efca7c9e2160caf5ce216e347146b7d6c93
prerequisite-patch-id: 39c82dddeb7135a8a5dcfeeda3f5990d1564f845
prerequisite-patch-id: 4d3f5c603d36dc2df7b9363021044fdab0b60c90
prerequisite-patch-id: 25d7d10e191b1b7310e2c95a787a1b48286302b8
-- 
2.36.0


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

* Re: [PATCH 08/31] Thread options & clone events (core + remote)
  2023-01-31 12:25   ` Lancelot SIX
@ 2023-03-10 19:16     ` Pedro Alves
  2023-06-06 13:29       ` Andrew Burgess
  0 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2023-03-10 19:16 UTC (permalink / raw)
  To: Lancelot SIX; +Cc: gdb-patches

On 2023-01-31 12:25 p.m., Lancelot SIX wrote:
> Hi,
> 
>> diff --git a/gdb/remote.c b/gdb/remote.c
>> index 41348a65dc4..9de8ed8a068 100644
>> --- a/gdb/remote.c
>> +++ b/gdb/remote.c
>> @@ -14534,6 +14601,77 @@ 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);
>> +}
>> +
>> +/* For coalescing reasons, actually sending the options to the target
>> +   happens at resume time, via this function.  See target_resume for
>> +   all-stop, and target_commit_resumed for non-stop.  */
>> +
>> +void
>> +remote_target::commit_requested_thread_options ()
>> +{
>> +  struct remote_state *rs = get_remote_state ();
>> +
>> +  if (packet_support (PACKET_QThreadOptions) != PACKET_ENABLE)
>> +    return;
>> +
>> +  char *p = rs->buf.data ();
>> +  char *endp = p + get_remote_packet_size ();
>> +
>> +  /* Clear options for all threads by default.  Note that unlike
>> +     vCont, the rightmost options that match a thread apply, so we
>> +     don't have to worry about whether we can use wildcard ptids.  */
>> +  strcpy (p, "QThreadOptions;0");
>> +  p += strlen (p);
>> +
>> +  /* Now set non-zero options for threads that need them.  We don't
>> +     bother with the case of all threads of a process wanting the same
>> +     non-zero options as that's not an expected scenario.  */
>> +  for (thread_info *tp : all_non_exited_threads (this))
>> +    {
>> +      gdb_thread_options options = tp->thread_options ();
>> +
>> +      if (options == 0)
>> +	continue;
>> +
>> +      *p++ = ';';
>> +      p += xsnprintf (p, endp - p, "%s", phex_nz (options, sizeof (options)));
> 
> I am not super familiar with how big the buffer is guaranteed to be.
> Can we imagine a situation where the number of thread and options to
> send exceed the packet size capacity?  If so, this seems dangerous.  'p'
> would be incremented by the size which would have been necessary to do
> the print, so it means it could now point past the end of the buffer.

Note that xsnprintf has an assertion that ensures that the string fits:

int
xsnprintf (char *str, size_t size, const char *format, ...)
{
  va_list args;
  int ret;

  va_start (args, format);
  ret = vsnprintf (str, size, format, args);
  gdb_assert (ret < size);                           <<<< here
  va_end (args);


> Even the `*p++'= ';'` above and similar `*p++ =` below are subject to
> overflow if the number of options to encode grow too high.
> 
> See man vsnprintf(3) which is used by xsnprintf:
> 
>     The functions snprintf() and vsnprintf() do not write more than size
>     bytes[...].  If the output  was  truncated due to this limit, then
>     the return value is the number of characters [...] which would have
>     been written to the final string if enough space had been
>     available.
> 
> As I do not feel that we can have a guaranty regarding the maximum
> number of non exited threads with non-0 options (I might be wrong, but
> the set of options can be extended so this can show in the future),
> I would check the returned value of xsnprintf before adding it to p (the
> same might apply to remote_target::write_ptid, and other increments to p).
> 
> Did I miss some precondition which guarantee the buffer to be big enough?

Nope.  You've missed my laziness.  :-)

Here's a version of the patch that sends QThreadOptions packets incrementally
if needed.  This is the same thing we do for vCont actions (in vcont_builder::push_action).

I've tested the flush/restart path with a local hack to force the path all the time:

        size_t osize = obuf_p - obuf;
 -      if (osize > endp - p)
 +      if (1 || osize > endp - p)
           {

I force-pushed the whole series to users/palves/step-over-thread-exit-v3.1,
with this updated patch.

Let me know what you think.

Pedro Alves

From 10cf06f133fb69b093dc74a515db34410be8af40 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 23 Nov 2021 20:35:12 +0000
Subject: [PATCH] Thread options & clone events (core + remote)

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_THREAD_OPTION_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_THREAD_OPTION_CLONE when stepping
over a breakpoint.

Unlike PTRACE_SETOPTIONS, fork/vfork/clone children do NOT inherit
their parent's thread options.  This is so that GDB can send e.g.,
"QThreadOptions;0;1:TID" without worrying about threads it doesn't
know about yet.

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

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
Change-Id: Ie41e5093b2573f14cf6ac41b0b5804eba75be37e
---
 gdb/gdbthread.h        |  16 ++++
 gdb/infrun.c           |  63 +++++++++++++-
 gdb/remote.c           | 182 ++++++++++++++++++++++++++++++++++++++++-
 gdb/target-debug.h     |   2 +
 gdb/target-delegates.c |  28 +++++++
 gdb/target.c           |   9 ++
 gdb/target.h           |   8 ++
 gdb/target/target.c    |  11 +++
 gdb/target/target.h    |  16 ++++
 gdb/thread.c           |  15 ++++
 gdbserver/gdbthread.h  |   3 +
 gdbserver/server.cc    | 130 +++++++++++++++++++++++++++++
 gdbserver/target.cc    |   6 ++
 gdbserver/target.h     |   6 ++
 14 files changed, 493 insertions(+), 2 deletions(-)

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index c0f27a8a66e..79dedb23d4d 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,17 @@ 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);
+
+  /* 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 +589,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 d1e6233591c..e5f8dc8d8ab 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1584,7 +1584,6 @@ step_over_info_valid_p (void)
 /* Return true if THREAD is doing a displaced step.  */
 
 static bool
-ATTRIBUTE_UNUSED
 displaced_step_in_progress_thread (thread_info *thread)
 {
   gdb_assert (thread != nullptr);
@@ -1886,6 +1885,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.  */
+      event_thread->set_thread_options (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
@@ -1930,6 +1951,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--;
 
@@ -2423,6 +2446,42 @@ 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_THREAD_OPTION_CLONE;
+      if (target_supports_set_thread_options (options))
+	tp->set_thread_options (options);
+      else
+	target_thread_events (true);
+    }
+
+  /* If we're resuming more than one thread simultaneously, then any
+     thread other than the leader is being set to run free.  Clear any
+     previous thread option for those threads.  */
+  if (resume_ptid != inferior_ptid && target_supports_set_thread_options (0))
+    {
+      process_stratum_target *resume_target = tp->inf->process_target ();
+      for (thread_info *thr_iter : all_non_exited_threads (resume_target,
+							   resume_ptid))
+	if (thr_iter != tp)
+	  thr_iter->set_thread_options (0);
+    }
+
   infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
 		       resume_ptid.to_string ().c_str (),
 		       step, gdb_signal_to_symbol_string (sig));
@@ -6144,6 +6203,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/remote.c b/gdb/remote.c
index ed9c2a0946a..62f25b03928 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -248,6 +248,9 @@ enum {
   /* Support for the QThreadEvents packet.  */
   PACKET_QThreadEvents,
 
+  /* Support for the QThreadOptions packet.  */
+  PACKET_QThreadOptions,
+
   /* Support for multi-process extensions.  */
   PACKET_multiprocess_feature,
 
@@ -560,6 +563,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
@@ -720,6 +727,8 @@ class remote_target : public process_stratum_target
   void detach (inferior *, int) override;
   void disconnect (const char *, int) override;
 
+  void commit_requested_thread_options ();
+
   void commit_resumed () override;
   void resume (ptid_t, int, enum gdb_signal) override;
   ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
@@ -846,6 +855,8 @@ class remote_target : public process_stratum_target
 
   void thread_events (int) override;
 
+  bool supports_set_thread_options (gdb_thread_options) override;
+
   int can_do_single_step () override;
 
   void terminal_inferior () override;
@@ -1144,6 +1155,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 ();
 
@@ -5471,7 +5485,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 ();
 
@@ -5508,6 +5523,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 ();
+
+  m_features.m_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,
@@ -5610,6 +5668,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 },
@@ -5712,6 +5772,10 @@ remote_target::remote_query_supported ()
 	  != AUTO_BOOLEAN_FALSE)
 	remote_query_supported_append (&q, "QThreadEvents+");
 
+      if (m_features.packet_set_cmd_state (PACKET_QThreadOptions)
+	  != AUTO_BOOLEAN_FALSE)
+	remote_query_supported_append (&q, "QThreadOptions+");
+
       if (m_features.packet_set_cmd_state (PACKET_no_resumed)
 	  != AUTO_BOOLEAN_FALSE)
 	remote_query_supported_append (&q, "no-resumed+");
@@ -6784,6 +6848,8 @@ remote_target::resume (ptid_t scope_ptid, int step, enum gdb_signal siggnal)
       return;
     }
 
+  commit_requested_thread_options ();
+
   /* In all-stop, we can't mark REMOTE_ASYNC_GET_PENDING_EVENTS_TOKEN
      (explained in remote-notif.c:handle_notification) so
      remote_notif_process is not called.  We need find a place where
@@ -6946,6 +7012,8 @@ remote_target::commit_resumed ()
   if (!target_is_non_stop_p () || ::execution_direction == EXEC_REVERSE)
     return;
 
+  commit_requested_thread_options ();
+
   /* Try to send wildcard actions ("vCont;c" or "vCont;c:pPID.-1")
      instead of resuming all threads of each process individually.
      However, if any thread of a process must remain halted, we can't
@@ -14706,6 +14774,115 @@ 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 (m_features.packet_support (PACKET_QThreadOptions) == PACKET_ENABLE
+	  && (rs->supported_thread_options & options) == options);
+}
+
+/* For coalescing reasons, actually sending the options to the target
+   happens at resume time, via this function.  See target_resume for
+   all-stop, and target_commit_resumed for non-stop.  */
+
+void
+remote_target::commit_requested_thread_options ()
+{
+  struct remote_state *rs = get_remote_state ();
+
+  if (m_features.packet_support (PACKET_QThreadOptions) != PACKET_ENABLE)
+    return;
+
+  char *p = rs->buf.data ();
+  char *endp = p + get_remote_packet_size ();
+
+  /* Clear options for all threads by default.  Note that unlike
+     vCont, the rightmost options that match a thread apply, so we
+     don't have to worry about whether we can use wildcard ptids.  */
+  strcpy (p, "QThreadOptions;0");
+  p += strlen (p);
+
+  /* Send the QThreadOptions packet stored in P.  */
+  auto flush = [&] ()
+    {
+      *p++ = '\0';
+
+      putpkt (rs->buf);
+      getpkt (&rs->buf, 0);
+
+      switch (m_features.packet_ok (rs->buf, 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;
+	}
+    };
+
+  /* Prepare P for another QThreadOptions packet.  */
+  auto restart = [&] ()
+    {
+      p = rs->buf.data ();
+      strcpy (p, "QThreadOptions");
+      p += strlen (p);
+    };
+
+  /* Now set non-zero options for threads that need them.  We don't
+     bother with the case of all threads of a process wanting the same
+     non-zero options as that's not an expected scenario.  */
+  for (thread_info *tp : all_non_exited_threads (this))
+    {
+      gdb_thread_options options = tp->thread_options ();
+
+      if (options == 0)
+	continue;
+
+      /* It might be possible to we have more threads with options
+	 than can fit a single QThreadOptions packet.  So build each
+	 options/thread pair in this separate buffer to make sure it
+	 fits.  */
+      constexpr size_t max_options_size = 100;
+      char obuf[max_options_size];
+      char *obuf_p = obuf;
+      char *obuf_endp = obuf + max_options_size;
+
+      *obuf_p++ = ';';
+      obuf_p += xsnprintf (obuf_p, obuf_endp - obuf_p, "%s",
+			   phex_nz (options, sizeof (options)));
+      if (tp->ptid != magic_null_ptid)
+	{
+	  *obuf_p++ = ':';
+	  obuf_p = write_ptid (obuf_p, obuf_endp, tp->ptid);
+	}
+
+      size_t osize = obuf_p - obuf;
+      if (osize > endp - p)
+	{
+	  /* This new options/thread pair doesn't fit the packet
+	     buffer.  Send what we have already.  */
+	  flush ();
+	  restart ();
+
+	  /* Should now fit.  */
+	  gdb_assert (osize <= endp - p);
+	}
+
+      memcpy (p, obuf, osize);
+      p += osize;
+    }
+
+  flush ();
+}
+
 static void
 show_remote_cmd (const char *args, int from_tty)
 {
@@ -15446,6 +15623,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
   add_packet_config_cmd (PACKET_QThreadEvents, "QThreadEvents", "thread-events",
 			 0);
 
+  add_packet_config_cmd (PACKET_QThreadOptions, "QThreadOptions",
+			 "thread-options", 0);
+
   add_packet_config_cmd (PACKET_no_resumed, "N stop reply",
 			 "no-resumed-stop-reply", 0);
 
diff --git a/gdb/target-debug.h b/gdb/target-debug.h
index acb01d47e7c..72fb31f4b59 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 (to_string (X).c_str ())
 
 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 7a4ef05b4e1..63338d82834 100644
--- a/gdb/target-delegates.c
+++ b/gdb/target-delegates.c
@@ -106,6 +106,7 @@ 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;
   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 +282,7 @@ 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;
   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 +2274,32 @@ 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;
+}
+
 bool
 target_ops::supports_non_stop ()
 {
diff --git a/gdb/target.c b/gdb/target.c
index 9835222e5da..d1d3b6913fc 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -4358,6 +4358,15 @@ 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);
+}
+
 /* 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 58e24a5c28e..1657fe2fba7 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -736,6 +736,10 @@ 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);
     /* This method must be implemented in some situations.  See the
        comment on 'can_run'.  */
     virtual bool supports_non_stop ()
@@ -1895,6 +1899,10 @@ extern void target_async (bool 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);
+
 /* 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.c b/gdb/target/target.c
index 8089918f1d0..3af7d73df5a 100644
--- a/gdb/target/target.c
+++ b/gdb/target/target.c
@@ -188,3 +188,14 @@ target_read_string (CORE_ADDR memaddr, int len, int *bytes_read)
 
   return gdb::unique_xmalloc_ptr<char> ((char *) buffer.release ());
 }
+
+/* See target/target.h.  */
+
+std::string
+to_string (gdb_thread_options options)
+{
+  static constexpr gdb_thread_options::string_mapping mapping[] = {
+    MAP_ENUM_FLAG (GDB_THREAD_OPTION_CLONE),
+  };
+  return options.to_string (mapping);
+}
diff --git a/gdb/target/target.h b/gdb/target/target.h
index d1a18ee2212..2691f92e4ef 100644
--- a/gdb/target/target.h
+++ b/gdb/target/target.h
@@ -22,9 +22,25 @@
 
 #include "target/waitstatus.h"
 #include "target/wait.h"
+#include "gdbsupport/enum-flags.h"
 
 /* This header is a stopgap until more code is shared.  */
 
+/* Available thread options.  Keep this in sync with to_string, in
+   target.c.  */
+
+enum gdb_thread_option : unsigned
+{
+  /* Tell the target to report TARGET_WAITKIND_THREAD_CLONED events
+     for the thread.  */
+  GDB_THREAD_OPTION_CLONE = 1 << 0,
+};
+
+DEF_ENUM_FLAGS_TYPE (enum gdb_thread_option, gdb_thread_options);
+
+/* Convert gdb_thread_option to a string.  */
+extern std::string to_string (gdb_thread_options 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/gdb/thread.c b/gdb/thread.c
index 5b472150a62..8958ce1000b 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -399,6 +399,21 @@ thread_info::clear_pending_waitstatus ()
 
 /* See gdbthread.h.  */
 
+void
+thread_info::set_thread_options (gdb_thread_options thread_options)
+{
+  if (m_thread_options == thread_options)
+    return;
+
+  m_thread_options = thread_options;
+
+  infrun_debug_printf ("[options for %s are now %s]",
+		       this->ptid.to_string ().c_str (),
+		       to_string (thread_options).c_str ());
+}
+
+/* See gdbthread.h.  */
+
 int
 thread_is_in_step_over_chain (struct thread_info *tp)
 {
diff --git a/gdbserver/gdbthread.h b/gdbserver/gdbthread.h
index 493e1dbf6cb..a4dff0fe1a2 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 29f2d2a386c..7f7efd1fcc0 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -36,6 +36,7 @@
 #include "dll.h"
 #include "hostio.h"
 #include <vector>
+#include <unordered_map>
 #include "gdbsupport/common-inferior.h"
 #include "gdbsupport/job-control.h"
 #include "gdbsupport/environ.h"
@@ -616,6 +617,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
@@ -897,6 +909,114 @@ handle_general_set (char *own_buf)
       return;
     }
 
+  if (startswith (own_buf, "QThreadOptions;"))
+    {
+      const char *p = own_buf + strlen ("QThreadOptions");
+
+      gdb_thread_options supported_options = target_supported_thread_options ();
+      if (supported_options == 0)
+	{
+	  /* Something went wrong -- we don't support any option, but
+	     GDB sent the packet anyway.  */
+	  write_enn (own_buf);
+	  return;
+	}
+
+      /* We could store the options directly in thread->thread_options
+	 without this map, but that would mean that a QThreadOptions
+	 packet with a wildcard like "QThreadOptions;0;3:TID" would
+	 result in the debug logs showing:
+
+	   [options for TID are now 0x0]
+	   [options for TID are now 0x3]
+
+	 It's nicer if we only print the final options for each TID,
+	 and if we only print about it if the options changed compared
+	 to the options that were previously set on the thread.  */
+      std::unordered_map<thread_info *, gdb_thread_options> set_options;
+
+      while (*p != '\0')
+	{
+	  if (p[0] != ';')
+	    {
+	      write_enn (own_buf);
+	      return;
+	    }
+	  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 thread options requested: %s\n",
+				 to_string (options).c_str ());
+	      strcpy (own_buf, err.c_str ());
+	      return;
+	    }
+
+	  ptid_t ptid;
+
+	  if (p[0] == ';' || 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;
+	    }
+
+	  /* Convert PID.-1 => PID.0 for ptid.matches.  */
+	  if (ptid.lwp () == -1)
+	    ptid = ptid_t (ptid.pid ());
+
+	  for_each_thread ([&] (thread_info *thread)
+	    {
+	      if (ptid_of (thread).matches (ptid))
+		set_options[thread] = options;
+	    });
+	}
+
+      for (const auto &iter : set_options)
+	{
+	  thread_info *thread = iter.first;
+	  gdb_thread_options options = iter.second;
+
+	  if (thread->thread_options != options)
+	    {
+	      threads_debug_printf ("[options for %s are now %s]\n",
+				    target_pid_to_str (ptid_of (thread)).c_str (),
+				    to_string (options).c_str ());
+
+	      thread->thread_options = options;
+	    }
+	}
+
+      write_ok (own_buf);
+      return;
+    }
+
   if (startswith (own_buf, "QStartupWithShell:"))
     {
       const char *value = own_buf + strlen ("QStartupWithShell:");
@@ -2348,6 +2468,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
@@ -2474,6 +2596,14 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
 
       strcat (own_buf, ";vContSupported+");
 
+      gdb_thread_options supported_options = target_supported_thread_options ();
+      if (supported_options != 0)
+	{
+	  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 f8e592d20c3..1c740bbf583 100644
--- a/gdbserver/target.cc
+++ b/gdbserver/target.cc
@@ -532,6 +532,12 @@ process_stratum_target::supports_vfork_events ()
   return false;
 }
 
+gdb_thread_options
+process_stratum_target::supported_thread_options ()
+{
+  return 0;
+}
+
 bool
 process_stratum_target::supports_exec_events ()
 {
diff --git a/gdbserver/target.h b/gdbserver/target.h
index d993e361b76..fe68716c868 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -276,6 +276,9 @@ class process_stratum_target
   /* Returns true if vfork events are supported.  */
   virtual bool supports_vfork_events ();
 
+  /* Returns the set of supported thread options.  */
+  virtual gdb_thread_options supported_thread_options ();
+
   /* Returns true if exec events are supported.  */
   virtual bool supports_exec_events ();
 
@@ -531,6 +534,9 @@ int kill_inferior (process_info *proc);
 #define target_supports_vfork_events() \
   the_target->supports_vfork_events ()
 
+#define target_supported_thread_options(options) \
+  the_target->supported_thread_options (options)
+
 #define target_supports_exec_events() \
   the_target->supports_exec_events ()
 

base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
prerequisite-patch-id: bbc9918ac5f79de07a29f34ec072794d270f942d
prerequisite-patch-id: c0bc5b4f99193bb50cb31f551673de1808dcda35
prerequisite-patch-id: ab0838f2bc02931d7e830abe23833e7a8224442c
prerequisite-patch-id: 3a896bfe4b7c66a2e3a6aa668c5ae8395e5d8a52
prerequisite-patch-id: 254a23b7d7cec889924daaf288304494c93fe1aa
prerequisite-patch-id: b1fe92da846e52cce1e9f13498cf668c5cdd6ee4
-- 
2.36.0


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

* Re: [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal
  2023-03-10 17:15     ` Pedro Alves
@ 2023-03-16 16:07       ` Andrew Burgess
  2023-03-22 21:29         ` Andrew Burgess
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-03-16 16:07 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-02-03 10:44 a.m., Andrew Burgess wrote:
>> Pedro Alves <pedro@palves.net> writes:
>> 
>>> This commit tweaks displaced_step_finish & friends to pass down a
>>> target_waitstatus instead of a gdb_signal.  This needed because a
>> 
>> missing word: "This IS needed".
>
> Fixed.
>
>>> @@ -5699,7 +5696,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);
>> 
>> This change is interesting.
>> 
>> If I understand the code correctly, this call will eventually end up in
>> displaced_step_buffers::finish (displaced-stepping.c), which in turn
>> calls displaced_step_instruction_executed_successfully.
>> 
>> Previously, we always passed GDB_SIGNAL_TRAP here, which (if we ignore
>> the watchpoint check in
>> displaced_step_instruction_executed_successfully) means that
>> displaced_step_instruction_executed_successfully would always return
>> true, and then displaced_step_buffers::finish would call
>> gdbarch_displaced_step_fixup.
>> 
>> After this change, we know that esc->ws.kind is either
>> TARGET_WAITKIND_FORKED or  TARGET_WAITKIND_VFORKED, so we know that
>> displaced_step_instruction_executed_successfully will always return
>> false, and displaced_step_buffers::finish will no longer call
>> gdbarch_displaced_step_fixup.
>> 
>
> Good catch!
>
> I was tweaking the change to address your comment, and was coming to the
> conclusion that what I really wanted was this:
>
> static bool
> displaced_step_instruction_executed_successfully
>   (gdbarch *arch, const target_waitstatus &status)
> {
>   if (status.kind () == TARGET_WAITKIND_STOPPED
>       && status.sig () != GDB_SIGNAL_TRAP)
>     return false;
>
>   /* All other waitkinds can only happen if the instruction fully
>      executed.  For example, a fork, or a syscall entry can only
>      happen if the syscall instruction actually executed.  */
>
> (the comment is new)
>
> And then, I remembered, I actually wrote that early if like that originally,
> and I changed it in response to this review:
>
>  https://inbox.sourceware.org/gdb-patches/44f74af8-248b-1af8-3612-980c08607bf4@simark.ca/
>
> The review was totally right, it was my response that was misguided.
>
> But I'm confused because I am pretty sure that I wrote a reply to that
> message, saying that I did not intend to change the behavior, so I'd "fix" it.
> I can't find it in my outbox either, I guess I erroneously canceled my
> email window instead of sending the message...
>
> Anyhow, looks like I made it worse while trying to address Simon's comment.  :-P
>
> So I think I should go back to what I had, like before, and my response
> to Simon should have been instead:
>
>  - yes, the change is intended.  If we stopped for an event other than
> TARGET_WAITKIND_STOPPED, like for instance, TARGET_WAITKIND_FORKED,
> TARGET_WAITKIND_SYSCALL_ENTRY, then it must be that the instruction
> executed successfully, otherwise the syscall wouldn't have triggered.
>
>> What I don't understand well enough is what this actually means for a
>> running inferior.
>> 
>> It's odd because the comment in infrun.c (just above your change)
>> indicates that to get to this point the displaced step must have
>> completed successfully, while after this change, the new code path in
>> displaced_step_buffers::finish indicates we believe the displaced step
>> didn't complete successfully:
>> 
>>   /* Since the instruction didn't complete, all we can do is relocate the
>>      PC.  */
>> 
>> Do you know if any of our test cases hit this path?
>> 
>
> I know that you posted a series for this, which I plan to take a good look
> at (I actually planned on doing that earlier this week, but I had a couple
> major distractions, sorry).
>
> WDYT of the version below?

I took a look through and I'm happy with it.  But I would like you to
consider holding off until my displaced step patch has some feedback.

I'm currently rebasing the patch.  My patch deletes
displaced_step_instruction_executed_successfully, which I realised makes
passing the signal (or now target_waitstatus) redundant.  As such I'm
just testing an additional patch in the series which touches every place
you're touching - but removes the signal instead of changing it.

I hope to post my updated series later today (once testing completes).

Thanks,
Andrew

>
> From 0ccfd6822ef83eddb48c6f7bf21533858f177bc9 Mon Sep 17 00:00:00 2001
> From: Pedro Alves <pedro@palves.net>
> Date: Tue, 22 Jun 2021 15:42:51 +0100
> Subject: [PATCH] displaced step: pass down target_waitstatus instead of
>  gdb_signal
>
> This commit tweaks displaced_step_finish & friends to pass down a
> target_waitstatus instead of a gdb_signal.  This is 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 later in
> the series.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
> Change-Id: I4c5d338647b028071bc498c4e47063795a2db4c0
> ---
>  gdb/displaced-stepping.c  | 16 +++++++++++-----
>  gdb/displaced-stepping.h  |  2 +-
>  gdb/gdbarch-gen.h         |  4 ++--
>  gdb/gdbarch.c             |  4 ++--
>  gdb/gdbarch_components.py |  2 +-
>  gdb/infrun.c              | 17 +++++++----------
>  gdb/linux-tdep.c          |  5 +++--
>  gdb/linux-tdep.h          |  2 +-
>  gdb/rs6000-tdep.c         |  4 ++--
>  9 files changed, 30 insertions(+), 26 deletions(-)
>
> diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
> index 06b32a80f6a..08ae1ce4397 100644
> --- a/gdb/displaced-stepping.c
> +++ b/gdb/displaced-stepping.c
> @@ -192,12 +192,18 @@ 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;
>  
> +  /* All other (thread event) waitkinds can only happen if the
> +     instruction fully executed.  For example, a fork, or a syscall
> +     entry can only happen if the syscall instruction actually
> +     executed.  */
> +
>    if (target_stopped_by_watchpoint ())
>      {
>        if (gdbarch_have_nonsteppable_watchpoint (arch)
> @@ -210,7 +216,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 ());
>  
> @@ -256,7 +262,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 e154927ad92..d2c3fc1b2b4 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-gen.h b/gdb/gdbarch-gen.h
> index ddb97f60315..5805dcb75ef 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -1103,8 +1103,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 7e4e34d5aca..0b122a86055 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -4096,13 +4096,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/gdbarch_components.py b/gdb/gdbarch_components.py
> index caa65c334ec..8d72bff347e 100644
> --- a/gdb/gdbarch_components.py
> +++ b/gdb/gdbarch_components.py
> @@ -1878,7 +1878,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/infrun.c b/gdb/infrun.c
> index ab77300f1ff..99f2a8e039d 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -1895,7 +1895,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;
>  
> @@ -1917,7 +1918,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
> @@ -5122,7 +5123,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.  */
> @@ -5137,7 +5138,6 @@ handle_one (const wait_one_event &event)
>  	}
>        else
>  	{
> -	  enum gdb_signal sig;
>  	  struct regcache *regcache;
>  
>  	  infrun_debug_printf
> @@ -5148,10 +5148,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.  */
> @@ -5753,7 +5750,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 ();
> @@ -6118,7 +6115,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 e6ce13a1c67..036ea9f931d 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -2621,13 +2621,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 16e1b806b26..e09a6ef32b1 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.  */
>  
> diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
> index 104515de030..f9bbc435184 100644
> --- a/gdb/rs6000-tdep.c
> +++ b/gdb/rs6000-tdep.c
> @@ -1088,13 +1088,13 @@ ppc_displaced_step_prepare  (gdbarch *arch, thread_info *thread,
>  
>  static displaced_step_finish_status
>  ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
> -			   gdb_signal sig)
> +			   const target_waitstatus &status)
>  {
>    ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
>  
>    gdb_assert (per_inferior->disp_step_buf.has_value ());
>  
> -  return per_inferior->disp_step_buf->finish (arch, thread, sig);
> +  return per_inferior->disp_step_buf->finish (arch, thread, status);
>  }
>  
>  /* Implementation of gdbarch_displaced_step_restore_all_in_ptid.  */
>
> base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
> -- 
> 2.36.0


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

* Re: [PATCH 02/31] linux-nat: introduce pending_status_str
  2023-03-10 17:15     ` Pedro Alves
@ 2023-03-16 16:19       ` Andrew Burgess
  2023-03-27 18:05         ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-03-16 16:19 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-02-03 12:00 p.m., Andrew Burgess wrote:
>> Pedro Alves <pedro@palves.net> writes:
>> 
>>> 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.
>> 
>> This patch looks fine.  I had one slightly related question:  I took a
>> look at the comment on lwp_info::waitstatus in linux-nat.h, which says:
>> 
>>   /* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
>>      for this LWP's last event.  This may correspond to STATUS above,
>>      or to a local variable in lin_lwp_wait.  */
>>   struct target_waitstatus waitstatus;
>> 
>> Am I right in thinking that this comment is wrong; it should say
>> TARGET_WAITKIND_IGNORE, not TARGET_WAITKIND_SPURIOUS, right?
>
> You're right.
>
> I tweaked the comments in linux-nat.h in this new version.  Let me know what
> you think.
>
> From 10f88baff2e25fb87f37d1665bf283206171dd42 Mon Sep 17 00:00:00 2001
> From: Pedro Alves <pedro@palves.net>
> Date: Fri, 12 Nov 2021 20:50:29 +0000
> Subject: [PATCH] linux-nat: introduce pending_status_str
>
> 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.
>
> Also fix the comment in gdb/linux-nat.h describing
> lwp_info::waitstatus and details the description of lwp_info::status
> while at it.
>
> Change-Id: I66e5c7a363d30a925b093b195d72925ce5b6b980
> ---
>  gdb/linux-nat.c | 19 ++++++++++++++++---
>  gdb/linux-nat.h | 11 +++++++----
>  2 files changed, 23 insertions(+), 7 deletions(-)

LGTM.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

>
> diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
> index e5391b9ce35..9d811bbf3ff 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -255,6 +255,19 @@ 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)
> +{
> +  gdb_assert (lwp_status_pending_p (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 +1660,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 +3150,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 ());
>      }
>  
> diff --git a/gdb/linux-nat.h b/gdb/linux-nat.h
> index 45534c92386..770fe924427 100644
> --- a/gdb/linux-nat.h
> +++ b/gdb/linux-nat.h
> @@ -232,7 +232,9 @@ struct lwp_info : intrusive_list_node<lwp_info>
>    /* The last resume GDB requested on this thread.  */
>    resume_kind last_resume_kind = resume_continue;
>  
> -  /* If non-zero, a pending wait status.  */
> +  /* If non-zero, a pending wait status.  A pending process exit is
> +     recorded in WAITSTATUS, because W_EXITCODE(0,0) happens to be
> +     0.  */
>    int status = 0;
>  
>    /* When 'stopped' is set, this is where the lwp last stopped, with
> @@ -260,9 +262,10 @@ struct lwp_info : intrusive_list_node<lwp_info>
>    /* Non-zero if we expect a duplicated SIGINT.  */
>    int ignore_sigint = 0;
>  
> -  /* If WAITSTATUS->KIND != TARGET_WAITKIND_SPURIOUS, the waitstatus
> -     for this LWP's last event.  This may correspond to STATUS above,
> -     or to a local variable in lin_lwp_wait.  */
> +  /* If WAITSTATUS->KIND != TARGET_WAITKIND_IGNORE, the waitstatus for
> +     this LWP's last event.  This usually corresponds to STATUS above,
> +     however because W_EXITCODE(0,0) happens to be 0, a process exit
> +     will be recorded here, while 'status == 0' is ambiguous.  */
>    struct target_waitstatus waitstatus;
>  
>    /* Signal whether we are in a SYSCALL_ENTRY or
>
> base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
> prerequisite-patch-id: bbc9918ac5f79de07a29f34ec072794d270f942d
> -- 
> 2.36.0


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

* Re: [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event
  2022-12-12 20:30 ` [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event Pedro Alves
@ 2023-03-21 14:50   ` Andrew Burgess
  2023-04-04 13:57     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-03-21 14:50 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> I noticed that after a following patch ("Step over clone syscall w/
> breakpoint, TARGET_WAITKIND_THREAD_CLONED"), the
> gdb.threads/step-over-exec.exp was passing cleanly, but still, we'd
> end up with four new unexpected GDB core dumps:
>
> 		 === gdb Summary ===
>
>  # of unexpected core files      4
>  # of expected passes            48
>
> That said patch is making the pre-existing
> gdb.threads/step-over-exec.exp testcase (almost silently) expose a
> latent problem in gdb/linux-nat.c, resulting in a GDB crash when:
>
>  #1 - a non-leader thread execs
>  #2 - the post-exec program stops somewhere
>  #3 - you kill the inferior
>
> Instead of #3 directly, the testcase just returns, which ends up in
> gdb_exit, tearing down GDB, which kills the inferior, and is thus
> equivalent to #3 above.
>
> Vis:
>
>  $ gdb --args ./gdb /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true
>  ...
>  (top-gdb) r
>  ...
>  (gdb) b main
>  ...
>  (gdb) r
>  ...
>  Breakpoint 1, main (argc=1, argv=0x7fffffffdb88) at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec.c:69
>  69        argv0 = argv[0];
>  (gdb) c
>  Continuing.
>  [New Thread 0x7ffff7d89700 (LWP 2506975)]
>  Other going in exec.
>  Exec-ing /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
>  process 2506769 is executing new program: /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
>
>  Thread 1 "step-over-exec-" hit Breakpoint 1, main () at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec-execd.c:28
>  28        foo ();
>  (gdb) k
>  ...
>  Thread 1 "gdb" received signal SIGSEGV, Segmentation fault.
>  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
>  393         return m_suspend.waitstatus_pending_p;
>  (top-gdb) bt
>  #0  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
>  #1  0x0000555555a884d1 in get_pending_child_status (lp=0x5555579b8230, ws=0x7fffffffd130) at ../../src/gdb/linux-nat.c:1345
>  #2  0x0000555555a8e5e6 in kill_unfollowed_child_callback (lp=0x5555579b8230) at ../../src/gdb/linux-nat.c:3564
>  #3  0x0000555555a92a26 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (this=0x0, ecall=..., args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:284
>  #4  0x0000555555a92a51 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:278
>  #5  0x0000555555a91f84 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffd210, args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:247
>  #6  0x0000555555a87072 in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:864
>  #7  0x0000555555a8e732 in linux_nat_target::kill (this=0x55555653af40 <the_amd64_linux_nat_target>) at ../../src/gdb/linux-nat.c:3590
>  #8  0x0000555555cfdc11 in target_kill () at ../../src/gdb/target.c:911
>  ...

It wasn't 100% clear to me if the above session was supposed to show a
failure with GDB prior to *this* commit, or was a demonstration of what
would happen if this commit is skipped, and the later commits applied.

I thought it was the second case, but I was so unsure that I tried the
reproducer anyway.   Just in case I'm wrong, the above example doesn't
seem to fail prior to this commit.

>
> The root of the problem is that when a non-leader LWP execs, it just
> changes its tid to the tgid, replacing the pre-exec leader thread,
> becoming the new leader.  There's no thread exit event for the execing
> thread.  It's as if the old pre-exec LWP vanishes without trace.  The
> ptrace man page says:
>
> "PTRACE_O_TRACEEXEC (since Linux 2.5.46)
> 	Stop the tracee at the next execve(2).  A waitpid(2) by the
> 	tracer will return a status value such that
>
> 	  status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
>
> 	If the execing thread is not a thread group leader, the thread
> 	ID is reset to thread group leader's ID before this stop.
> 	Since Linux 3.0, the former thread ID can be retrieved with
> 	PTRACE_GETEVENTMSG."
>
> When the core of GDB processes an exec events, it deletes all the
> threads of the inferior.  But, that is too late -- deleting the thread
> does not delete the corresponding LWP, so we end leaving the pre-exec
> non-leader LWP stale in the LWP list.  That's what leads to the crash
> above -- linux_nat_target::kill iterates over all LWPs, and after the
> patch in question, that code will look for the corresponding
> thread_info for each LWP.  For the pre-exec non-leader LWP still
> listed, won't find one.
>
> This patch fixes it, by deleting the pre-exec non-leader LWP (and
> thread) from the LWP/thread lists as soon as we get an exec event out
> of ptrace.

Given that we don't have a test *right now* for this issue, and instead
rely on a future patch not failing.  I wondered if there was any way
that we could trigger a failure.

So I was poking around looking for places where we iterate over the
all_lwps() list wondering which we could trigger that might cause a
failure...

... and then I thought: why not just have GDB tell us that the
all_lwps() list is broken.

So I hacked up a new 'maint info linux-lwps' command.  It's not very
interesting right now, here's the output in a multi-threaded inferior
prior to the exec:

  (gdb) maintenance info linux-lwps 
  LWP Ptid          Thread ID
  1707218.1707239.0 2           
  1707218.1707218.0 1           

And in your failure case (after the exec):

  (gdb) maintenance info linux-lwps 
  LWP Ptid          Thread ID 
  1708883.1708895.0 None      
  1708883.1708883.0 1         

And then we can check this from the testscript, and now we have a test
that fails before this commit, and passes afterwards.

And in the future we might find other information we want to add in the
new maintenance command.

What are your thoughts on including this, or something like this with
this commit?  My patch, which applies on top of this commit, is included
at the end of this email.  Please feel free to take any changes that you
feel add value.

>
> GDBserver does not need an equivalent fix, because it is already doing
> this, as side effect of mourning the pre-exec process, in
> gdbserver/linux-low.cc:
>
>   else if (event == PTRACE_EVENT_EXEC && cs.report_exec_events)
>     {
> ...
>       /* Delete the execing process and all its threads.  */
>       mourn (proc);
>       switch_to_thread (nullptr);
>
> Change-Id: I21ec18072c7750f3a972160ae6b9e46590376643
> ---
>  gdb/linux-nat.c                              | 15 +++++++++++++++
>  gdb/testsuite/gdb.threads/step-over-exec.exp |  6 ++++++
>  2 files changed, 21 insertions(+)
>
> diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
> index 9b78fd1f8e8..5ee3227f1b9 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -1986,6 +1986,21 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
>  	 thread execs, it changes its tid to the tgid, and the old
>  	 tgid thread might have not been resumed.  */
>        lp->resumed = 1;
> +
> +      /* All other LWPs are gone now.  We'll have received a thread
> +	 exit notification for all threads other the execing one.
> +	 That one, if it wasn't the leader, just silently changes its
> +	 tid to the tgid, and the previous leader vanishes.  Since
> +	 Linux 3.0, the former thread ID can be retrieved with
> +	 PTRACE_GETEVENTMSG, but since we support older kernels, don't
> +	 bother with it, and just walk the LWP list.  Even with
> +	 PTRACE_GETEVENTMSG, we'd still need to lookup the
> +	 corresponding LWP object, and it would be an extra ptrace
> +	 syscall, so this way may even be more efficient.  */
> +      for (lwp_info *other_lp : all_lwps_safe ())
> +	if (other_lp != lp && other_lp->ptid.pid () == lp->ptid.pid ())
> +	  exit_lwp (other_lp);
> +
>        return 0;
>      }
>  
> diff --git a/gdb/testsuite/gdb.threads/step-over-exec.exp b/gdb/testsuite/gdb.threads/step-over-exec.exp
> index 783f865585c..a8b01f8aeda 100644
> --- a/gdb/testsuite/gdb.threads/step-over-exec.exp
> +++ b/gdb/testsuite/gdb.threads/step-over-exec.exp
> @@ -102,6 +102,12 @@ proc do_test { execr_thread different_text_segments displaced_stepping } {
>      gdb_breakpoint foo
>      gdb_test "continue" "Breakpoint $decimal, foo .*" \
>  	"continue to foo"
> +
> +    # Test that GDB is able to kill the inferior.  This may fail if
> +    # e.g., GDB does not dispose of the pre-exec threads properly.
> +    gdb_test "with confirm off -- kill" \
> +	"\\\[Inferior 1 (.*) killed\\\]" \
> +	"kill inferior"
>  }
>

These changes all look good.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

>  foreach_with_prefix displaced_stepping {auto off} {
> -- 
> 2.36.0


---

diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 5f67bcbcb4f..9b1e071b5f6 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -4482,6 +4485,49 @@ current_lwp_ptid (void)
   return inferior_ptid;
 }
 
+/* Implement 'maintenance info linux-lwps'.  Displays some basic
+   information about all the current lwp_info objects.  */
+
+static void
+maintenance_info_lwps (const char *arg, int from_tty)
+{
+  if (all_lwps ().size () == 0)
+    {
+      gdb_printf ("No Linux LWPs\n");
+      return;
+    }
+
+  /* Start the width at 8 to match the column heading below, then figure
+     out the widest ptid string.  We'll use this to build our output table
+     below.  */
+  size_t ptid_width = 8;
+  for (lwp_info *lp : all_lwps ())
+    ptid_width = std::max (ptid_width, lp->ptid.to_string ().size ());
+
+  /* Setup the table headers.  */
+  struct ui_out *uiout = current_uiout;
+  ui_out_emit_table table_emitter (uiout, 2, -1, "linux-lwps");
+  uiout->table_header (ptid_width, ui_left, "lwp-ptid", _("LWP Ptid"));
+  uiout->table_header (9, ui_left, "thread-info", _("Thread ID"));
+  uiout->table_body ();
+
+  /* Display one table row for each lwp_info.  */
+  for (lwp_info *lp : all_lwps ())
+    {
+      ui_out_emit_tuple tuple_emitter (uiout, "lwp-entry");
+
+      struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
+
+      uiout->field_string ("lwp-ptid", lp->ptid.to_string ().c_str ());
+      if (th == nullptr)
+	uiout->field_string ("thread-info", "None");
+      else
+	uiout->field_string ("thread-info", print_thread_id (th));
+
+      uiout->message ("\n");
+    }
+}
+
 void _initialize_linux_nat ();
 void
 _initialize_linux_nat ()
@@ -4519,6 +4565,9 @@ Enables printf debugging output."),
   sigemptyset (&blocked_mask);
 
   lwp_lwpid_htab_create ();
+
+  add_cmd ("linux-lwps", class_maintenance, maintenance_info_lwps,
+	 _("List the Linux LWPS."), &maintenanceinfolist);
 }
 \f
 
diff --git a/gdb/testsuite/gdb.threads/step-over-exec.exp b/gdb/testsuite/gdb.threads/step-over-exec.exp
index c9a067b23aa..8ab027f6f08 100644
--- a/gdb/testsuite/gdb.threads/step-over-exec.exp
+++ b/gdb/testsuite/gdb.threads/step-over-exec.exp
@@ -103,6 +103,49 @@ proc do_test { execr_thread different_text_segments displaced_stepping } {
     gdb_test "continue" "Breakpoint $decimal, foo .*" \
 	"continue to foo"
 
+    # If we have a linux target then there used to be a bug that in
+    # some situations we'd leave an orphaned lwp object around.  Check
+    # the 'maint info linux-lwp' output to spot any orphans.
+    #
+    # If linux native support is not built in then we'll get an
+    # undefined maintenance command error, which is fine.  The bug
+    # we're checking for was in linux native code, so we know we're
+    # fine.
+    #
+    # Alternatively, linux native support might be built in, but we
+    # might be using an alternative target (e.g. a remote target), in
+    # this case we'll get a message about 'No Linux LWPs'.  Again
+    # there's nothing that needs testing in this case.
+    gdb_test_multiple "maint info linux-lwp" "" {
+	-re "^maint info linux-lwp\r\n" {
+	    exp_continue
+	}
+
+	-re "^Undefined maintenance info command: \"linux-lwp\"\\.  Try \"help maintenance info\"\\.\r\n$::gdb_prompt $" {
+	    unsupported $gdb_test_name
+	}
+
+	-re "^LWP Ptid\\s+Thread Info\\s*\r\n" {
+	    exp_continue
+	}
+
+	-re "^\\d+\\.\\d+\\.\\d+\\s+\\d+(?:\\.\\d+)?\\s*\r\n" {
+	    exp_continue
+	}
+
+	-re "^\\d+\\.\\d+\\.\\d+\\s+None\\s*\r\n" {
+	    fail $gdb_test_name
+	}
+
+	-re "^No Linux LWPs\r\n$::gdb_prompt" {
+	    unsupported $gdb_test_name
+	}
+
+	-re "^$::gdb_prompt $" {
+	    pass $gdb_test_name
+	}
+    }
+
     # Test that GDB is able to kill the inferior.  This may fail if
     # e.g., GDB does not dispose of the pre-exec threads properly.
     gdb_test "with confirm off -- kill" \


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

* Re: [PATCH 04/31] Step over clone syscall w/ breakpoint, TARGET_WAITKIND_THREAD_CLONED
  2023-03-10 17:16     ` Pedro Alves
@ 2023-03-21 16:06       ` Andrew Burgess
  2023-11-13 14:05         ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-03-21 16:06 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-02-04 3:38 p.m., Andrew Burgess wrote:
>> Pedro Alves <pedro@palves.net> writes:
>
>>> +/* 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.  */
>>> +
>>> +  if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
>> 
>> I know this is just a style issue, but I'm really not a fan of this
>> assignment in the if condition.  I know it probably seems silly, but I'd
>> find it much clearer if we did:
>> 
>>   gdb::optional<target_waitstatus> ws = get_pending_child_status (lp)
>>   if (ws.has_value ())
>>     ...
>> 
>> The first time I read the initial line, my first thought was s/=/==/,
>> then I had to read it again and figure out what was going on...
>> 
>
> Ah, I think I was asked to switch to this style in a previous review, so
> clearly it's a matter of taste.  :-D
>
> I was using this originally:
>
>     target_waitstatus ws;
>     if (get_pending_child_status (lp, &ws))
>       {
>
>
> I used to think that "if (auto opt = foo ())" might be confusing too, to be honest,
> but I've convinced myself that it is OK (but please see further below) for a couple
> reasons:
>
> #1 - these aren't really equivalent:
>
>     a) if (type var = func ())
>
>     b) itype var = func ();
>        if (var)
>
>   because in a), the scope of the variable is just the if then/else
>   scopes.  So you can for example reuse the name like:
>
>     if (type var = func ())
>        {
>        }
>
>     if (type var = bar ())
>        {
>        }
>
>   and also you're guaranteed that the variable's dtor is run at the end of the
>   if block.  So it'd be more equivalent to writing an explicit scope, like:
>
>        {
>          itype var = func ();
>          if (var)
>            {
>              ...
>            }
>        }
>
> #2 - the presence of the type makes it visually distinctive from the problematic case
>      of writing to a variable by accident:
>
>        if (var = func ())        // bad
>
>        if (var == func ())       // ok
>   
>        if (auto var = func ())   // ok
>
>        if (auto var == func ())  // not valid
>
>
>      and, it's not different from initializing a variable in a for, like:
>
>        for (auto var = func (); ...; ...)
>
>      and we don't bat an eye when we see that.
>
>      in fact, C++17 let's you write an if with a for-like init-statement, like so:
>
>        if (auto var = func (); var)
>
>      so I think this is a case of being surprised at first, but then we just
>      get used to it quickly.
>
>
> However, I've still converted to use the style you suggested, because
> there might be a different reason for wanting to avoid that style, which is that
> this:
>
>    if (gdb::optional<target_waitstatus> ws = get_pending_child_status (lp))
>
> begs the question of whether we want to allow:
>
>    if (foo *ptr = get_foo ())
>
> and this runs counter to our style of doing explicit nullptr checks...  So
> we can leave that to a seperate discussion and I don't want to be blocked
> by it.  :-)
>
>
>>> diff --git a/gdb/target.h b/gdb/target.h
>>> index 28aa9273893..aab390aec57 100644
>>> --- a/gdb/target.h
>>> +++ b/gdb/target.h
>>> @@ -637,6 +637,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);
>> 
>> One thing that sucks about some of the older parts of the target API is
>> just how undocumented it is.  The only way to figure out what things
>> like follow_fork do is to look at the code.
>
> I don't disagree completely, but note that the comments for the target_ops methods
> tend to be in the target_foo wrapper method.  For example, target_follow_fork.
> In this case, there's no such wrapper method, so I have no good excuse.  :-)
>
> I've added a comment, like:
>
> --- c/gdb/target.h
> +++ w/gdb/target.h
> @@ -637,8 +637,13 @@ 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)
> +
> +    /* Add CHILD_PTID to the thread list, after handling a
> +       TARGET_WAITKIND_THREAD_CLONE event for the clone parent.  The
> +       parent is inferior_ptid.  */
> +    virtual void follow_clone (ptid_t child_ptid)
>        TARGET_DEFAULT_FUNC (default_follow_clone);
> +
>      virtual int insert_exec_catchpoint (int)
>        TARGET_DEFAULT_RETURN (1);
>      virtual int remove_exec_catchpoint (int)
>
>> 
>> Some of the newer methods do have good comments.  Or at least comments
>> that give a good hint to what the method does.
>> 
>> I'd be really grateful if new target API methods could be given a good
>> comment in target.h.
>
> Here's the full patch.  Let me know what you think.
>
> From 3b2638cf7507146385e16473c883b7d0d4a75277 Mon Sep 17 00:00:00 2001
> From: Pedro Alves <pedro@palves.net>
> Date: Fri, 12 Nov 2021 20:50:29 +0000
> Subject: [PATCH] Step over clone syscall w/ breakpoint,
>  TARGET_WAITKIND_THREAD_CLONED
>
> (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 affected, 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 held 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.

What's the motivation for splitting the implementation from the
associated tests?

Though your implementation may be different (better) than mine, the
original test I wrote[1] still passes just fine with this commit.

When digging back through old patches it's much nicer to easier to find
the GDB changes and the tests in a single commit IMHO.  Do feel free to
take the tests from that commit, if that helps at all (though I notice I
left some debug logging in which would need cleaning up, and the test
fails when using a remote target, but that's easily fixed by skipping
the test in that case).

[1] https://sourceware.org/pipermail/gdb-patches/2021-June/180520.html

I've looked through the code and it all looks good to me.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

>
> displaced_step_in_progress_thread becomes unused with this patch, but
> a new use will reappear later in the series.  To avoid deleting it and
> readding it back, this patch marks it with attribute unused, and the
> latter patch removes the attribute again.  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            | 158 ++++++++++++++-----------
>  gdb/linux-nat.c         | 250 +++++++++++++++++++++-------------------
>  gdb/linux-nat.h         |   2 +
>  gdb/target-delegates.c  |  24 ++++
>  gdb/target.c            |   7 ++
>  gdb/target.h            |   2 +
>  gdb/target/waitstatus.c |   1 +
>  gdb/target/waitstatus.h |  31 ++++-
>  8 files changed, 283 insertions(+), 192 deletions(-)
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index 99f2a8e039d..d1e6233591c 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -1584,6 +1584,7 @@ step_over_info_valid_p (void)
>  /* Return true if THREAD is doing a displaced step.  */
>  
>  static bool
> +ATTRIBUTE_UNUSED
>  displaced_step_in_progress_thread (thread_info *thread)
>  {
>    gdb_assert (thread != nullptr);
> @@ -1898,6 +1899,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?  */
> @@ -1917,8 +1943,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
> @@ -5717,67 +5774,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);
>  
> @@ -5793,7 +5796,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.  */
> @@ -5825,14 +5828,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.  */
> @@ -5843,16 +5856,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 5f67bcbcb4f..6db21e809c5 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -1286,64 +1286,80 @@ 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.  */
> +/* If LP has a pending fork/vfork/clone status, return it.  */
>  
> -static void
> -detach_one_lwp (struct lwp_info *lp, int *signo_p)
> +static gdb::optional<target_waitstatus>
> +get_pending_child_status (lwp_info *lp)
>  {
> -  int lwpid = lp->ptid.lwp ();
> -  int signo;
> -
> -  gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
> -
> -  /* 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.  */
> -
>    /* 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);
> +	    {
> +	      target_waitstatus ws;
> +
> +	      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 ws;
> +	    }
>  	  else
> -	    perror_warning_with_name (_("Failed to detach fork child"));
> +	    {
> +	      perror_warning_with_name (_("Failed to retrieve event msg"));
> +	      return {};
> +	    }
>  	}
>      }
>  
>    /* 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_new_child_status (lp->waitstatus.kind ()))
> +    return lp->waitstatus;
>  
> -  /* Check in thread_info::pending_waitstatus.  */
>    thread_info *tp = find_thread_ptid (linux_target, lp->ptid);
> -  if (tp->has_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);
> -    }
> +  /* Check in thread_info::pending_waitstatus.  */
> +  if (tp->has_pending_waitstatus ()
> +      && is_new_child_status (tp->pending_waitstatus ().kind ()))
> +    return tp->pending_waitstatus ();
>  
>    /* 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_new_child_status (tp->pending_follow.kind ()))
> +    return tp->pending_follow;
>  
> -  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 {};
> +}
> +
> +/* 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.  */
> +
> +  gdb::optional<target_waitstatus> ws = get_pending_child_status (lp);
> +  if (ws.has_value ())
> +    detach_one_pid (ws->child_ptid ().lwp (), 0);
>  
>    /* If there is a pending SIGSTOP, get rid of it.  */
>    if (lp->signalled)
> @@ -1821,6 +1837,53 @@ linux_handle_syscall_trap (struct lwp_info *lp, int stopping)
>    return 1;
>  }
>  
> +void
> +linux_nat_target::follow_clone (ptid_t child_ptid)
> +{
> +  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.  */
> +      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 (_("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 +1924,9 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
>  	    internal_error (_("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,66 +1963,18 @@ 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.  */
> -	      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;
> @@ -3536,59 +3549,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 ())
> +  gdb::optional<target_waitstatus> ws = get_pending_child_status (lp);
> +  if (ws.has_value ())
>      {
> -      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 770fe924427..1cdbeafd4f3 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 57b66ce87b1..7a4ef05b4e1 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 0cebecfafc3..9835222e5da 100644
> --- a/gdb/target.c
> +++ b/gdb/target.c
> @@ -2701,6 +2701,13 @@ default_follow_fork (struct target_ops *self, inferior *child_inf,
>    internal_error (_("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 (_("could not find a target to follow clone"));
> +}
> +
>  /* See target.h.  */
>  
>  void
> diff --git a/gdb/target.h b/gdb/target.h
> index 2dac86c394d..43ab71093d9 100644
> --- a/gdb/target.h
> +++ b/gdb/target.h
> @@ -637,6 +637,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 2b8404fb75b..a8edbb17d60 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 0a88b869044..4c3de005315 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,
>  
> @@ -102,6 +109,17 @@ enum target_waitkind
>    TARGET_WAITKIND_THREAD_EXITED,
>  };
>  
> +/* Determine if KIND represents an event with a new child - a fork,
> +   vfork, or clone.  */
> +
> +static inline bool
> +is_new_child_status (target_waitkind kind)
> +{
> +  return (kind == TARGET_WAITKIND_FORKED
> +	  || kind == TARGET_WAITKIND_VFORKED
> +	  || kind == TARGET_WAITKIND_THREAD_CLONED);
> +}
> +
>  /* Return KIND as a string.  */
>  
>  static inline const char *
> @@ -125,6 +143,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 +345,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 ();
> @@ -369,8 +397,7 @@ struct target_waitstatus
>  
>    ptid_t child_ptid () const
>    {
> -    gdb_assert (m_kind == TARGET_WAITKIND_FORKED
> -		|| m_kind == TARGET_WAITKIND_VFORKED);
> +    gdb_assert (is_new_child_status (m_kind));
>      return m_value.child_ptid;
>    }
>  
>
> base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
> prerequisite-patch-id: bbc9918ac5f79de07a29f34ec072794d270f942d
> prerequisite-patch-id: c0bc5b4f99193bb50cb31f551673de1808dcda35
> prerequisite-patch-id: ab0838f2bc02931d7e830abe23833e7a8224442c
> -- 
> 2.36.0


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

* Re: [PATCH 05/31] Support clone events in the remote protocol
  2022-12-12 20:30 ` [PATCH 05/31] Support clone events in the remote protocol Pedro Alves
@ 2023-03-22 15:46   ` Andrew Burgess
  2023-11-13 14:05     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-03-22 15:46 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> 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              | 99 ++++++++++++++++++++++++++-------------
>  gdbserver/remote-utils.cc | 26 ++++++++--
>  gdbserver/server.cc       |  3 +-
>  3 files changed, 91 insertions(+), 37 deletions(-)
>
> diff --git a/gdb/remote.c b/gdb/remote.c
> index 53c4f19c5a4..a7430eb79dd 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;
> @@ -765,7 +766,7 @@ class remote_target : public process_stratum_target
>  
>    void remote_btrace_maybe_reopen ();
>  
> -  void remove_new_fork_children (threads_listing_context *context);
> +  void remove_new_children (threads_listing_context *context);
>    void kill_new_fork_children (inferior *inf);
>    void discard_pending_stop_replies (struct inferior *inf);
>    int stop_reply_queue_length ();
> @@ -2579,9 +2580,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 feels like that comment is now out of date.  We surely aren't
assuming anything anymore, rather marking the thread as resumed when its
executing.

Otherwise, this commit looks good.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

> +  if (executing)
> +    get_remote_thread_info (thread)->set_resumed ();
>  
>    set_executing (this, ptid, executing);
>    set_running (this, ptid, running);
> @@ -3983,10 +3985,11 @@ remote_target::update_thread_list ()
>  	    }
>  	}
>  
> -      /* Remove any unreported fork child threads from CONTEXT so
> -	 that we don't interfere with follow fork, which is where
> -	 creation of such threads is handled.  */
> -      remove_new_fork_children (&context);
> +      /* Remove any unreported fork/vfork/clone child threads from
> +	 CONTEXT so that we don't interfere with follow
> +	 fork/vfork/clone, which is where creation of such threads is
> +	 handled.  */
> +      remove_new_children (&context);
>  
>        /* And now add threads we don't know about yet to our list.  */
>        for (thread_item &item : context.items)
> @@ -4940,6 +4943,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
> @@ -5893,16 +5898,25 @@ 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.  */
> +/* Return a reference to the field where a pending child status, if
> +   there's one, is recorded.  If there's no child event pending, the
> +   returned waitstatus has TARGET_WAITKIND_IGNORE kind.  */
> +
> +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;
> @@ -5910,6 +5924,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
> @@ -6075,6 +6103,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.  */
>  
> @@ -6807,10 +6841,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;
>      }
>  
> @@ -7276,22 +7310,22 @@ struct notif_client notif_client_stop =
>    REMOTE_NOTIF_STOP,
>  };
>  
> -/* If CONTEXT contains any fork child threads that have not been
> -   reported yet, remove them from the CONTEXT list.  If such a
> -   thread exists it is because we are stopped at a fork catchpoint
> -   and have not yet called follow_fork, which will set up the
> -   host-side data structures for the new process.  */
> +/* If CONTEXT contains any fork/vfork/clone child threads that have
> +   not been reported yet, remove them from the CONTEXT list.  If such
> +   a thread exists it is because we are stopped at a fork/vfork/clone
> +   catchpoint and have not yet called follow_fork/follow_clone, which
> +   will set up the host-side data structures for the new child.  */
>  
>  void
> -remote_target::remove_new_fork_children (threads_listing_context *context)
> +remote_target::remove_new_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;
> @@ -7299,13 +7333,12 @@ 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)
> +    if (is_new_child_status (event->ws.kind ()))
>        context->remove_thread (event->ws.child_ptid ());
>      else if (event->ws.kind () == TARGET_WAITKIND_THREAD_EXITED)
>        context->remove_thread (event->ptid);
> @@ -7634,6 +7667,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 2ddb275bd15..6a673cb1ca8 100644
> --- a/gdbserver/remote-utils.cc
> +++ b/gdbserver/remote-utils.cc
> @@ -1062,6 +1062,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:
> @@ -1071,13 +1072,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 aaef38e0062..56b7f97a388 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] 100+ messages in thread

* Re: [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal
  2023-03-16 16:07       ` Andrew Burgess
@ 2023-03-22 21:29         ` Andrew Burgess
  2023-03-23 15:15           ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-03-22 21:29 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Andrew Burgess <aburgess@redhat.com> writes:

> Pedro Alves <pedro@palves.net> writes:
>
>> On 2023-02-03 10:44 a.m., Andrew Burgess wrote:
>>> Pedro Alves <pedro@palves.net> writes:
>>> 
>>>> This commit tweaks displaced_step_finish & friends to pass down a
>>>> target_waitstatus instead of a gdb_signal.  This needed because a
>>> 
>>> missing word: "This IS needed".
>>
>> Fixed.
>>
>>>> @@ -5699,7 +5696,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);
>>> 
>>> This change is interesting.
>>> 
>>> If I understand the code correctly, this call will eventually end up in
>>> displaced_step_buffers::finish (displaced-stepping.c), which in turn
>>> calls displaced_step_instruction_executed_successfully.
>>> 
>>> Previously, we always passed GDB_SIGNAL_TRAP here, which (if we ignore
>>> the watchpoint check in
>>> displaced_step_instruction_executed_successfully) means that
>>> displaced_step_instruction_executed_successfully would always return
>>> true, and then displaced_step_buffers::finish would call
>>> gdbarch_displaced_step_fixup.
>>> 
>>> After this change, we know that esc->ws.kind is either
>>> TARGET_WAITKIND_FORKED or  TARGET_WAITKIND_VFORKED, so we know that
>>> displaced_step_instruction_executed_successfully will always return
>>> false, and displaced_step_buffers::finish will no longer call
>>> gdbarch_displaced_step_fixup.
>>> 
>>
>> Good catch!
>>
>> I was tweaking the change to address your comment, and was coming to the
>> conclusion that what I really wanted was this:
>>
>> static bool
>> displaced_step_instruction_executed_successfully
>>   (gdbarch *arch, const target_waitstatus &status)
>> {
>>   if (status.kind () == TARGET_WAITKIND_STOPPED
>>       && status.sig () != GDB_SIGNAL_TRAP)
>>     return false;
>>
>>   /* All other waitkinds can only happen if the instruction fully
>>      executed.  For example, a fork, or a syscall entry can only
>>      happen if the syscall instruction actually executed.  */
>>
>> (the comment is new)
>>
>> And then, I remembered, I actually wrote that early if like that originally,
>> and I changed it in response to this review:
>>
>>  https://inbox.sourceware.org/gdb-patches/44f74af8-248b-1af8-3612-980c08607bf4@simark.ca/
>>
>> The review was totally right, it was my response that was misguided.
>>
>> But I'm confused because I am pretty sure that I wrote a reply to that
>> message, saying that I did not intend to change the behavior, so I'd "fix" it.
>> I can't find it in my outbox either, I guess I erroneously canceled my
>> email window instead of sending the message...
>>
>> Anyhow, looks like I made it worse while trying to address Simon's comment.  :-P
>>
>> So I think I should go back to what I had, like before, and my response
>> to Simon should have been instead:
>>
>>  - yes, the change is intended.  If we stopped for an event other than
>> TARGET_WAITKIND_STOPPED, like for instance, TARGET_WAITKIND_FORKED,
>> TARGET_WAITKIND_SYSCALL_ENTRY, then it must be that the instruction
>> executed successfully, otherwise the syscall wouldn't have triggered.
>>
>>> What I don't understand well enough is what this actually means for a
>>> running inferior.
>>> 
>>> It's odd because the comment in infrun.c (just above your change)
>>> indicates that to get to this point the displaced step must have
>>> completed successfully, while after this change, the new code path in
>>> displaced_step_buffers::finish indicates we believe the displaced step
>>> didn't complete successfully:
>>> 
>>>   /* Since the instruction didn't complete, all we can do is relocate the
>>>      PC.  */
>>> 
>>> Do you know if any of our test cases hit this path?
>>> 
>>
>> I know that you posted a series for this, which I plan to take a good look
>> at (I actually planned on doing that earlier this week, but I had a couple
>> major distractions, sorry).
>>
>> WDYT of the version below?
>
> I took a look through and I'm happy with it.  But I would like you to
> consider holding off until my displaced step patch has some feedback.
>
> I'm currently rebasing the patch.  My patch deletes
> displaced_step_instruction_executed_successfully, which I realised makes
> passing the signal (or now target_waitstatus) redundant.  As such I'm
> just testing an additional patch in the series which touches every place
> you're touching - but removes the signal instead of changing it.
>
> I hope to post my updated series later today (once testing completes).

Pedro,

Thanks for your feedback on my displaced stepping series. You're right
that just checking the $pc isn't going to be enough.  So I'm now
thinking that I should be passing the gdb_signal through to the
gdbarch_displaced_step_fixup function.

Rather than change things to pass through the gdb_signal though, and
then have this patch come along and s/gdb_signal/target_waitstatus/, I
wonder how you'd feel about merging this patch sooner rather than later?

I'm planning to rebase my displaced stepping series off this patch -- I
just want to check you'd be OK with this patch possibly landing before
the rest of this series?

Thanks,
Andrew


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

* Re: [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal
  2023-03-22 21:29         ` Andrew Burgess
@ 2023-03-23 15:15           ` Pedro Alves
  2023-03-27 12:40             ` Andrew Burgess
  0 siblings, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2023-03-23 15:15 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

Hi!

On 2023-03-22 9:29 p.m., Andrew Burgess wrote:
> Andrew Burgess <aburgess@redhat.com> writes:
> 
...
>> I took a look through and I'm happy with it.  But I would like you to
>> consider holding off until my displaced step patch has some feedback.
>>
>> I'm currently rebasing the patch.  My patch deletes
>> displaced_step_instruction_executed_successfully, which I realised makes
>> passing the signal (or now target_waitstatus) redundant.  As such I'm
>> just testing an additional patch in the series which touches every place
>> you're touching - but removes the signal instead of changing it.
>>
>> I hope to post my updated series later today (once testing completes).
> 
> Pedro,
> 
> Thanks for your feedback on my displaced stepping series. You're right
> that just checking the $pc isn't going to be enough.  So I'm now
> thinking that I should be passing the gdb_signal through to the
> gdbarch_displaced_step_fixup function.

I guess passing down a boolean (or enum) indicating "success/failure" would
also be an alternative.  That may avoid every arch having to do the same check.

> 
> Rather than change things to pass through the gdb_signal though, and
> then have this patch come along and s/gdb_signal/target_waitstatus/, I
> wonder how you'd feel about merging this patch sooner rather than later?
> 
> I'm planning to rebase my displaced stepping series off this patch -- I
> just want to check you'd be OK with this patch possibly landing before
> the rest of this series?

I'm absolutely fine with putting this in before the rest of this series.

How about I just merge it immediately, so none of us have to carry it around?

I've updated the commit log a bit so it doesn't assume it is being merged along
the rest of the series.

From 7abd5c8ee74055621dc3a73fbcf2e8940712bb01 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 22 Jun 2021 15:42:51 +0100
Subject: [PATCH] displaced step: pass down target_waitstatus instead of
 gdb_signal

This commit tweaks displaced_step_finish & friends to pass down a
target_waitstatus instead of a gdb_signal.  This is needed because a
patch later in the step-over-{thread-exit,clone] series will want to
make displaced_step_buffers::finish handle
TARGET_WAITKIND_THREAD_EXITED.  It also helps with the
TARGET_WAITKIND_THREAD_CLONED patch later in that same series.

It's also a bit more logical this way, as we don't have to pass down
signals when the thread didn't actually stop for a signal.  So we can
also think of it as a clean up.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
Change-Id: I4c5d338647b028071bc498c4e47063795a2db4c0
Approved-By: Andrew Burgess <aburgess@redhat.com>
---
 gdb/displaced-stepping.c  | 16 +++++++++++-----
 gdb/displaced-stepping.h  |  2 +-
 gdb/gdbarch-gen.h         |  4 ++--
 gdb/gdbarch.c             |  4 ++--
 gdb/gdbarch_components.py |  2 +-
 gdb/infrun.c              | 17 +++++++----------
 gdb/linux-tdep.c          |  5 +++--
 gdb/linux-tdep.h          |  2 +-
 gdb/rs6000-tdep.c         |  4 ++--
 9 files changed, 30 insertions(+), 26 deletions(-)

diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
index 9f98ea8c35b..3fefdf322d8 100644
--- a/gdb/displaced-stepping.c
+++ b/gdb/displaced-stepping.c
@@ -192,12 +192,18 @@ 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;
 
+  /* All other (thread event) waitkinds can only happen if the
+     instruction fully executed.  For example, a fork, or a syscall
+     entry can only happen if the syscall instruction actually
+     executed.  */
+
   if (target_stopped_by_watchpoint ())
     {
       if (gdbarch_have_nonsteppable_watchpoint (arch)
@@ -210,7 +216,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 ());
 
@@ -256,7 +262,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 e154927ad92..d2c3fc1b2b4 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-gen.h b/gdb/gdbarch-gen.h
index a3fc0b9272b..bbf2376fc4b 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1101,8 +1101,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 b676e346fd0..84beb087336 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -4098,13 +4098,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/gdbarch_components.py b/gdb/gdbarch_components.py
index 2b1a2b4f602..52beaeaa245 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -1819,7 +1819,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/infrun.c b/gdb/infrun.c
index 5c9babb9104..ee812baf8da 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1895,7 +1895,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;
 
@@ -1917,7 +1918,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
@@ -5128,7 +5129,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.  */
@@ -5143,7 +5144,6 @@ handle_one (const wait_one_event &event)
 	}
       else
 	{
-	  enum gdb_signal sig;
 	  struct regcache *regcache;
 
 	  infrun_debug_printf
@@ -5154,10 +5154,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.  */
@@ -5759,7 +5756,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 ();
@@ -6124,7 +6121,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 d0bda5ad9c4..1fc9cb6faee 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -2626,13 +2626,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 16e1b806b26..e09a6ef32b1 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.  */
 
diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
index 52dcc89b2df..8b400047cfb 100644
--- a/gdb/rs6000-tdep.c
+++ b/gdb/rs6000-tdep.c
@@ -1089,13 +1089,13 @@ ppc_displaced_step_prepare  (gdbarch *arch, thread_info *thread,
 
 static displaced_step_finish_status
 ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
-			   gdb_signal sig)
+			   const target_waitstatus &status)
 {
   ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
 
   gdb_assert (per_inferior->disp_step_buf.has_value ());
 
-  return per_inferior->disp_step_buf->finish (arch, thread, sig);
+  return per_inferior->disp_step_buf->finish (arch, thread, status);
 }
 
 /* Implementation of gdbarch_displaced_step_restore_all_in_ptid.  */

base-commit: 91ffa03af1cc32515190c3b52d7b964f5abead5f
-- 
2.36.0


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

* Re: [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal
  2023-03-23 15:15           ` Pedro Alves
@ 2023-03-27 12:40             ` Andrew Burgess
  2023-03-27 16:21               ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-03-27 12:40 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> Hi!
>
> On 2023-03-22 9:29 p.m., Andrew Burgess wrote:
>> Andrew Burgess <aburgess@redhat.com> writes:
>> 
> ...
>>> I took a look through and I'm happy with it.  But I would like you to
>>> consider holding off until my displaced step patch has some feedback.
>>>
>>> I'm currently rebasing the patch.  My patch deletes
>>> displaced_step_instruction_executed_successfully, which I realised makes
>>> passing the signal (or now target_waitstatus) redundant.  As such I'm
>>> just testing an additional patch in the series which touches every place
>>> you're touching - but removes the signal instead of changing it.
>>>
>>> I hope to post my updated series later today (once testing completes).
>> 
>> Pedro,
>> 
>> Thanks for your feedback on my displaced stepping series. You're right
>> that just checking the $pc isn't going to be enough.  So I'm now
>> thinking that I should be passing the gdb_signal through to the
>> gdbarch_displaced_step_fixup function.
>
> I guess passing down a boolean (or enum) indicating "success/failure" would
> also be an alternative.  That may avoid every arch having to do the
> same check.

That's what I did in the end.  I posted a V3 for my displaced step series.

>
>> 
>> Rather than change things to pass through the gdb_signal though, and
>> then have this patch come along and s/gdb_signal/target_waitstatus/, I
>> wonder how you'd feel about merging this patch sooner rather than later?
>> 
>> I'm planning to rebase my displaced stepping series off this patch -- I
>> just want to check you'd be OK with this patch possibly landing before
>> the rest of this series?
>
> I'm absolutely fine with putting this in before the rest of this series.
>
> How about I just merge it immediately, so none of us have to carry it
> around?

That would be great, as our patches will have a minor merge conflict in
displaced-stepping.c.

>
> I've updated the commit log a bit so it doesn't assume it is being merged along
> the rest of the series.
>
> From 7abd5c8ee74055621dc3a73fbcf2e8940712bb01 Mon Sep 17 00:00:00 2001
> From: Pedro Alves <pedro@palves.net>
> Date: Tue, 22 Jun 2021 15:42:51 +0100
> Subject: [PATCH] displaced step: pass down target_waitstatus instead of
>  gdb_signal
>
> This commit tweaks displaced_step_finish & friends to pass down a
> target_waitstatus instead of a gdb_signal.  This is needed because a
> patch later in the step-over-{thread-exit,clone] series will want to
> make displaced_step_buffers::finish handle
> TARGET_WAITKIND_THREAD_EXITED.  It also helps with the
> TARGET_WAITKIND_THREAD_CLONED patch later in that same series.
>
> It's also a bit more logical this way, as we don't have to pass down
> signals when the thread didn't actually stop for a signal.  So we can
> also think of it as a clean up.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27338
> Change-Id: I4c5d338647b028071bc498c4e47063795a2db4c0
> Approved-By: Andrew Burgess <aburgess@redhat.com>

LGTM.

Thanks,
Andrew

> ---
>  gdb/displaced-stepping.c  | 16 +++++++++++-----
>  gdb/displaced-stepping.h  |  2 +-
>  gdb/gdbarch-gen.h         |  4 ++--
>  gdb/gdbarch.c             |  4 ++--
>  gdb/gdbarch_components.py |  2 +-
>  gdb/infrun.c              | 17 +++++++----------
>  gdb/linux-tdep.c          |  5 +++--
>  gdb/linux-tdep.h          |  2 +-
>  gdb/rs6000-tdep.c         |  4 ++--
>  9 files changed, 30 insertions(+), 26 deletions(-)
>
> diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c
> index 9f98ea8c35b..3fefdf322d8 100644
> --- a/gdb/displaced-stepping.c
> +++ b/gdb/displaced-stepping.c
> @@ -192,12 +192,18 @@ 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;
>  
> +  /* All other (thread event) waitkinds can only happen if the
> +     instruction fully executed.  For example, a fork, or a syscall
> +     entry can only happen if the syscall instruction actually
> +     executed.  */
> +
>    if (target_stopped_by_watchpoint ())
>      {
>        if (gdbarch_have_nonsteppable_watchpoint (arch)
> @@ -210,7 +216,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 ());
>  
> @@ -256,7 +262,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 e154927ad92..d2c3fc1b2b4 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-gen.h b/gdb/gdbarch-gen.h
> index a3fc0b9272b..bbf2376fc4b 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -1101,8 +1101,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 b676e346fd0..84beb087336 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -4098,13 +4098,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/gdbarch_components.py b/gdb/gdbarch_components.py
> index 2b1a2b4f602..52beaeaa245 100644
> --- a/gdb/gdbarch_components.py
> +++ b/gdb/gdbarch_components.py
> @@ -1819,7 +1819,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/infrun.c b/gdb/infrun.c
> index 5c9babb9104..ee812baf8da 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -1895,7 +1895,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;
>  
> @@ -1917,7 +1918,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
> @@ -5128,7 +5129,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.  */
> @@ -5143,7 +5144,6 @@ handle_one (const wait_one_event &event)
>  	}
>        else
>  	{
> -	  enum gdb_signal sig;
>  	  struct regcache *regcache;
>  
>  	  infrun_debug_printf
> @@ -5154,10 +5154,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.  */
> @@ -5759,7 +5756,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 ();
> @@ -6124,7 +6121,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 d0bda5ad9c4..1fc9cb6faee 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -2626,13 +2626,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 16e1b806b26..e09a6ef32b1 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.  */
>  
> diff --git a/gdb/rs6000-tdep.c b/gdb/rs6000-tdep.c
> index 52dcc89b2df..8b400047cfb 100644
> --- a/gdb/rs6000-tdep.c
> +++ b/gdb/rs6000-tdep.c
> @@ -1089,13 +1089,13 @@ ppc_displaced_step_prepare  (gdbarch *arch, thread_info *thread,
>  
>  static displaced_step_finish_status
>  ppc_displaced_step_finish (gdbarch *arch, thread_info *thread,
> -			   gdb_signal sig)
> +			   const target_waitstatus &status)
>  {
>    ppc_inferior_data *per_inferior = get_ppc_per_inferior (thread->inf);
>  
>    gdb_assert (per_inferior->disp_step_buf.has_value ());
>  
> -  return per_inferior->disp_step_buf->finish (arch, thread, sig);
> +  return per_inferior->disp_step_buf->finish (arch, thread, status);
>  }
>  
>  /* Implementation of gdbarch_displaced_step_restore_all_in_ptid.  */
>
> base-commit: 91ffa03af1cc32515190c3b52d7b964f5abead5f
> -- 
> 2.36.0


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

* Re: [PATCH 01/31] displaced step: pass down target_waitstatus instead of gdb_signal
  2023-03-27 12:40             ` Andrew Burgess
@ 2023-03-27 16:21               ` Pedro Alves
  0 siblings, 0 replies; 100+ messages in thread
From: Pedro Alves @ 2023-03-27 16:21 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-03-27 1:40 p.m., Andrew Burgess wrote:
> Pedro Alves <pedro@palves.net> writes:

>> I'm absolutely fine with putting this in before the rest of this series.
>>
>> How about I just merge it immediately, so none of us have to carry it
>> around?
> 
> That would be great, as our patches will have a minor merge conflict in
> displaced-stepping.c.

...

> 
> LGTM.

Alright, pushed.

Thanks,
Pedro Alves

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

* Re: [PATCH 02/31] linux-nat: introduce pending_status_str
  2023-03-16 16:19       ` Andrew Burgess
@ 2023-03-27 18:05         ` Pedro Alves
  0 siblings, 0 replies; 100+ messages in thread
From: Pedro Alves @ 2023-03-27 18:05 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-03-16 4:19 p.m., Andrew Burgess wrote:
> Pedro Alves <pedro@palves.net> writes:

>> I tweaked the comments in linux-nat.h in this new version.  Let me know what
>> you think.
>>
>> From 10f88baff2e25fb87f37d1665bf283206171dd42 Mon Sep 17 00:00:00 2001
>> From: Pedro Alves <pedro@palves.net>
>> Date: Fri, 12 Nov 2021 20:50:29 +0000
>> Subject: [PATCH] linux-nat: introduce pending_status_str
>>
>> 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.
>>
>> Also fix the comment in gdb/linux-nat.h describing
>> lwp_info::waitstatus and details the description of lwp_info::status
>> while at it.
>>
>> Change-Id: I66e5c7a363d30a925b093b195d72925ce5b6b980
>> ---
>>  gdb/linux-nat.c | 19 ++++++++++++++++---
>>  gdb/linux-nat.h | 11 +++++++----
>>  2 files changed, 23 insertions(+), 7 deletions(-)
> 
> LGTM.
> 
> Reviewed-By: Andrew Burgess <aburgess@redhat.com>
> 

Thanks, I've merged this one as well, as it's pretty much independent/standalone.

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

* Re: [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event
  2023-03-21 14:50   ` Andrew Burgess
@ 2023-04-04 13:57     ` Pedro Alves
  2023-04-14 19:29       ` Pedro Alves
  2023-05-26 14:45       ` Andrew Burgess
  0 siblings, 2 replies; 100+ messages in thread
From: Pedro Alves @ 2023-04-04 13:57 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

Hi Andrew,

Took me a bit to find time to investigate this.  See below.

On 2023-03-21 2:50 p.m., Andrew Burgess wrote:
> Pedro Alves <pedro@palves.net> writes:
> 
>> I noticed that after a following patch ("Step over clone syscall w/
>> breakpoint, TARGET_WAITKIND_THREAD_CLONED"), the
>> gdb.threads/step-over-exec.exp was passing cleanly, but still, we'd
>> end up with four new unexpected GDB core dumps:
>>
>> 		 === gdb Summary ===
>>
>>  # of unexpected core files      4
>>  # of expected passes            48
>>
>> That said patch is making the pre-existing
>> gdb.threads/step-over-exec.exp testcase (almost silently) expose a
>> latent problem in gdb/linux-nat.c, resulting in a GDB crash when:
>>
>>  #1 - a non-leader thread execs
>>  #2 - the post-exec program stops somewhere
>>  #3 - you kill the inferior
>>
>> Instead of #3 directly, the testcase just returns, which ends up in
>> gdb_exit, tearing down GDB, which kills the inferior, and is thus
>> equivalent to #3 above.
>>
>> Vis:
>>
>>  $ gdb --args ./gdb /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true
>>  ...
>>  (top-gdb) r
>>  ...
>>  (gdb) b main
>>  ...
>>  (gdb) r
>>  ...
>>  Breakpoint 1, main (argc=1, argv=0x7fffffffdb88) at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec.c:69
>>  69        argv0 = argv[0];
>>  (gdb) c
>>  Continuing.
>>  [New Thread 0x7ffff7d89700 (LWP 2506975)]
>>  Other going in exec.
>>  Exec-ing /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
>>  process 2506769 is executing new program: /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
>>
>>  Thread 1 "step-over-exec-" hit Breakpoint 1, main () at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec-execd.c:28
>>  28        foo ();
>>  (gdb) k
>>  ...
>>  Thread 1 "gdb" received signal SIGSEGV, Segmentation fault.
>>  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
>>  393         return m_suspend.waitstatus_pending_p;
>>  (top-gdb) bt
>>  #0  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
>>  #1  0x0000555555a884d1 in get_pending_child_status (lp=0x5555579b8230, ws=0x7fffffffd130) at ../../src/gdb/linux-nat.c:1345
>>  #2  0x0000555555a8e5e6 in kill_unfollowed_child_callback (lp=0x5555579b8230) at ../../src/gdb/linux-nat.c:3564
>>  #3  0x0000555555a92a26 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (this=0x0, ecall=..., args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:284
>>  #4  0x0000555555a92a51 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:278
>>  #5  0x0000555555a91f84 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffd210, args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:247
>>  #6  0x0000555555a87072 in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:864
>>  #7  0x0000555555a8e732 in linux_nat_target::kill (this=0x55555653af40 <the_amd64_linux_nat_target>) at ../../src/gdb/linux-nat.c:3590
>>  #8  0x0000555555cfdc11 in target_kill () at ../../src/gdb/target.c:911
>>  ...
> 
> It wasn't 100% clear to me if the above session was supposed to show a
> failure with GDB prior to *this* commit, or was a demonstration of what
> would happen if this commit is skipped, and the later commits applied.

Yes, prior to this commit.

> 
> I thought it was the second case, but I was so unsure that I tried the
> reproducer anyway.   Just in case I'm wrong, the above example doesn't
> seem to fail prior to this commit.

This surprised me, and when I tried it myself, I was even more surprised,
for I couldn't reproduce it either!

But I figured it out.

I'm usually using Ubuntu 22.04 for development nowadays, and in that system, indeed I can't
reproduce it.  Right after the exec, GDB traps a load event for "libc.so.6", which leads to
gdb trying to open libthread_db for the post-exec inferior, and, it succeeds.  When we load
libthread_db, we call linux_stop_and_wait_all_lwps, which, as the name suggests, stops all lwps,
and then waits to see their stops.  While doing this, GDB detects that the pre-exec stale
LWP is gone, and deletes it.

The logs show:

[linux-nat] linux_nat_wait_1: waitpid 1725529 received SIGTRAP - Trace/breakpoint trap (stopped)
[linux-nat] save_stop_reason: 1725529.1725529.0 stopped by software breakpoint
[linux-nat] linux_nat_wait_1: waitpid(-1, ...) returned 0, ERRNO-OK
[linux-nat] resume_stopped_resumed_lwps: NOT resuming LWP 1725529.1725658.0, not stopped
[linux-nat] resume_stopped_resumed_lwps: NOT resuming LWP 1725529.1725529.0, has pending status
[linux-nat] linux_nat_wait_1: trap ptid is 1725529.1725529.0.
[linux-nat] linux_nat_wait_1: exit
[linux-nat] stop_callback: kill 1725529.1725658.0 **<SIGSTOP>**
[linux-nat] stop_callback: lwp kill -1 No such process
[linux-nat] wait_lwp: 1725529.1725658.0 vanished.

And the backtrace is:

(top-gdb) bt
#0  wait_lwp (lp=0x555556f37350) at ../../src/gdb/linux-nat.c:2069
#1  0x0000555555aa8fbf in stop_wait_callback (lp=0x555556f37350) at ../../src/gdb/linux-nat.c:2375
#2  0x0000555555ab12b3 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (__closure=0x0, ecall=..., args#0=0x555556f37350) at ../../src/gdb/../gdbsupport/function-view.h:326
#3  0x0000555555ab12e2 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:320
#4  0x0000555555ab0610 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffca90, args#0=0x555556f37350) at ../../src/gdb/../gdbsupport/function-view.h:289
#5  0x0000555555aa4c2d in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:867
#6  0x0000555555aa8a03 in linux_stop_and_wait_all_lwps () at ../../src/gdb/linux-nat.c:2229
#7  0x0000555555ac8525 in try_thread_db_load_1 (info=0x555556a66dd0) at ../../src/gdb/linux-thread-db.c:923
#8  0x0000555555ac89d5 in try_thread_db_load (library=0x5555560eca27 "libthread_db.so.1", check_auto_load_safe=false) at ../../src/gdb/linux-thread-db.c:1024
#9  0x0000555555ac8eda in try_thread_db_load_from_sdir () at ../../src/gdb/linux-thread-db.c:1108
#10 0x0000555555ac9278 in thread_db_load_search () at ../../src/gdb/linux-thread-db.c:1163
#11 0x0000555555ac9518 in thread_db_load () at ../../src/gdb/linux-thread-db.c:1225
#12 0x0000555555ac95e1 in check_for_thread_db () at ../../src/gdb/linux-thread-db.c:1268
#13 0x0000555555ac9657 in thread_db_new_objfile (objfile=0x555556943ed0) at ../../src/gdb/linux-thread-db.c:1297
#14 0x000055555569e2d2 in std::__invoke_impl<void, void (*&)(objfile*), objfile*> (__f=@0x5555567925d8: 0x555555ac95e8 <thread_db_new_objfile(objfile*)>) at /usr/include/c++/11/bits/invoke.h:61
#15 0x000055555569c44a in std::__invoke_r<void, void (*&)(objfile*), objfile*> (__fn=@0x5555567925d8: 0x555555ac95e8 <thread_db_new_objfile(objfile*)>) at /usr/include/c++/11/bits/invoke.h:111
#16 0x0000555555699d69 in std::_Function_handler<void (objfile*), void (*)(objfile*)>::_M_invoke(std::_Any_data const&, objfile*&&) (__functor=..., __args#0=@0x7fffffffce50: 0x555556943ed0) at /usr/include/c++/11/bits/std_function.h:290
#17 0x0000555555b5f48b in std::function<void (objfile*)>::operator()(objfile*) const (this=0x5555567925d8, __args#0=0x555556943ed0) at /usr/include/c++/11/bits/std_function.h:590
#18 0x0000555555b5eba4 in gdb::observers::observable<objfile*>::notify (this=0x5555565b5680 <gdb::observers::new_objfile>, args#0=0x555556943ed0) at ../../src/gdb/../gdbsupport/observable.h:166
#19 0x0000555555cdd85b in symbol_file_add_with_addrs (abfd=..., name=0x5555569794e0 "/lib/x86_64-linux-gnu/libc.so.6", add_flags=..., addrs=0x7fffffffd0c0, flags=..., parent=0x0) at ../../src/gdb/symfile.c:1131
#20 0x0000555555cdd9c5 in symbol_file_add_from_bfd (abfd=..., name=0x5555569794e0 "/lib/x86_64-linux-gnu/libc.so.6", add_flags=..., addrs=0x7fffffffd0c0, flags=..., parent=0x0) at ../../src/gdb/symfile.c:1167
#21 0x0000555555c9dd69 in solib_read_symbols (so=0x5555569792d0, flags=...) at ../../src/gdb/solib.c:730
#22 0x0000555555c9e7b7 in solib_add (pattern=0x0, from_tty=0, readsyms=1) at ../../src/gdb/solib.c:1041
#23 0x0000555555c9f61d in handle_solib_event () at ../../src/gdb/solib.c:1315
#24 0x0000555555729c26 in bpstat_stop_status (aspace=0x555556606800, bp_addr=0x7ffff7fe7278, thread=0x555556816bd0, ws=..., stop_chain=0x0) at ../../src/gdb/breakpoint.c:5702
#25 0x0000555555a62e41 in handle_signal_stop (ecs=0x7fffffffd670) at ../../src/gdb/infrun.c:6517
#26 0x0000555555a61479 in handle_inferior_event (ecs=0x7fffffffd670) at ../../src/gdb/infrun.c:6000
#27 0x0000555555a5c7b5 in fetch_inferior_event () at ../../src/gdb/infrun.c:4403
#28 0x0000555555a35b65 in inferior_event_handler (event_type=INF_REG_EVENT) at ../../src/gdb/inf-loop.c:41
#29 0x0000555555aae0c9 in handle_target_event (error=0, client_data=0x0) at ../../src/gdb/linux-nat.c:4231


Now, when I try the same on a Fedora 32 machine, I see the GDB crash due to the stale
LWP still in the LWP list with no corresponding thread_info.  On this
machine, glibc predates the changes that make it possible to use libthread_db with
non-threaded processes, so try_thread_db_load doesn't manage to open a connection
to libthread_db, and thus we don't end up in linux_stop_and_wait_all_lwps, and thus
the stale lwp is not deleted.  And so a subsequent "kill" command crashes.

I wrote that patch originally on an Ubuntu 20.04 machine (vs the Ubuntu 22.04 I have now),
and it must be that that version also predates the glibc change, and thus behaves like
this Fedora 32 box.  You are very likely using a newer Fedora which has the glibc change.

> 
>>
>> The root of the problem is that when a non-leader LWP execs, it just
>> changes its tid to the tgid, replacing the pre-exec leader thread,
>> becoming the new leader.  There's no thread exit event for the execing
>> thread.  It's as if the old pre-exec LWP vanishes without trace.  The
>> ptrace man page says:
>>
>> "PTRACE_O_TRACEEXEC (since Linux 2.5.46)
>> 	Stop the tracee at the next execve(2).  A waitpid(2) by the
>> 	tracer will return a status value such that
>>
>> 	  status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
>>
>> 	If the execing thread is not a thread group leader, the thread
>> 	ID is reset to thread group leader's ID before this stop.
>> 	Since Linux 3.0, the former thread ID can be retrieved with
>> 	PTRACE_GETEVENTMSG."
>>
>> When the core of GDB processes an exec events, it deletes all the
>> threads of the inferior.  But, that is too late -- deleting the thread
>> does not delete the corresponding LWP, so we end leaving the pre-exec
>> non-leader LWP stale in the LWP list.  That's what leads to the crash
>> above -- linux_nat_target::kill iterates over all LWPs, and after the
>> patch in question, that code will look for the corresponding
>> thread_info for each LWP.  For the pre-exec non-leader LWP still
>> listed, won't find one.
>>
>> This patch fixes it, by deleting the pre-exec non-leader LWP (and
>> thread) from the LWP/thread lists as soon as we get an exec event out
>> of ptrace.
> 
> Given that we don't have a test *right now* for this issue, and instead
> rely on a future patch not failing.  I wondered if there was any way
> that we could trigger a failure.
> 
> So I was poking around looking for places where we iterate over the
> all_lwps() list wondering which we could trigger that might cause a
> failure...
> 
> ... and then I thought: why not just have GDB tell us that the
> all_lwps() list is broken.
> 
> So I hacked up a new 'maint info linux-lwps' command.  It's not very
> interesting right now, here's the output in a multi-threaded inferior
> prior to the exec:
> 
>   (gdb) maintenance info linux-lwps 
>   LWP Ptid          Thread ID
>   1707218.1707239.0 2           
>   1707218.1707218.0 1           
> 
> And in your failure case (after the exec):
> 
>   (gdb) maintenance info linux-lwps 
>   LWP Ptid          Thread ID 
>   1708883.1708895.0 None      
>   1708883.1708883.0 1         
> 
> And then we can check this from the testscript, and now we have a test
> that fails before this commit, and passes afterwards.
> 
> And in the future we might find other information we want to add in the
> new maintenance command.
> 
> What are your thoughts on including this, or something like this with
> this commit?  My patch, which applies on top of this commit, is included
> at the end of this email.  Please feel free to take any changes that you
> feel add value.

I'm totally fine with such a command, though the test I had added covers
as much as it would, as the "kill" command fails when the maint command
would fail, and passes when the maint command passes.  But I'll incorporate
it.

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

* Re: [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event
  2023-04-04 13:57     ` Pedro Alves
@ 2023-04-14 19:29       ` Pedro Alves
  2023-05-26 15:04         ` Andrew Burgess
  2023-05-26 14:45       ` Andrew Burgess
  1 sibling, 1 reply; 100+ messages in thread
From: Pedro Alves @ 2023-04-14 19:29 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

[-- Attachment #1: Type: text/plain, Size: 7855 bytes --]


Hi!

On 2023-04-04 2:57 p.m., Pedro Alves wrote:
> On 2023-03-21 2:50 p.m., Andrew Burgess wrote:
>>
>> I thought it was the second case, but I was so unsure that I tried the
>> reproducer anyway.   Just in case I'm wrong, the above example doesn't
>> seem to fail prior to this commit.
> 
> This surprised me, and when I tried it myself, I was even more surprised,
> for I couldn't reproduce it either!
> 
> But I figured it out.
> 
> I'm usually using Ubuntu 22.04 for development nowadays, and in that system, indeed I can't
> reproduce it.  Right after the exec, GDB traps a load event for "libc.so.6", which leads to
> gdb trying to open libthread_db for the post-exec inferior, and, it succeeds.  When we load
> libthread_db, we call linux_stop_and_wait_all_lwps, which, as the name suggests, stops all lwps,
> and then waits to see their stops.  While doing this, GDB detects that the pre-exec stale
> LWP is gone, and deletes it.
> 
> The logs show:
> 
> [linux-nat] linux_nat_wait_1: waitpid 1725529 received SIGTRAP - Trace/breakpoint trap (stopped)
> [linux-nat] save_stop_reason: 1725529.1725529.0 stopped by software breakpoint
> [linux-nat] linux_nat_wait_1: waitpid(-1, ...) returned 0, ERRNO-OK
> [linux-nat] resume_stopped_resumed_lwps: NOT resuming LWP 1725529.1725658.0, not stopped
> [linux-nat] resume_stopped_resumed_lwps: NOT resuming LWP 1725529.1725529.0, has pending status
> [linux-nat] linux_nat_wait_1: trap ptid is 1725529.1725529.0.
> [linux-nat] linux_nat_wait_1: exit
> [linux-nat] stop_callback: kill 1725529.1725658.0 **<SIGSTOP>**
> [linux-nat] stop_callback: lwp kill -1 No such process
> [linux-nat] wait_lwp: 1725529.1725658.0 vanished.
> 
> And the backtrace is:
> 
> (top-gdb) bt
> #0  wait_lwp (lp=0x555556f37350) at ../../src/gdb/linux-nat.c:2069
> #1  0x0000555555aa8fbf in stop_wait_callback (lp=0x555556f37350) at ../../src/gdb/linux-nat.c:2375
> #2  0x0000555555ab12b3 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (__closure=0x0, ecall=..., args#0=0x555556f37350) at ../../src/gdb/../gdbsupport/function-view.h:326
> #3  0x0000555555ab12e2 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:320
> #4  0x0000555555ab0610 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffca90, args#0=0x555556f37350) at ../../src/gdb/../gdbsupport/function-view.h:289
> #5  0x0000555555aa4c2d in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:867
> #6  0x0000555555aa8a03 in linux_stop_and_wait_all_lwps () at ../../src/gdb/linux-nat.c:2229
> #7  0x0000555555ac8525 in try_thread_db_load_1 (info=0x555556a66dd0) at ../../src/gdb/linux-thread-db.c:923
> #8  0x0000555555ac89d5 in try_thread_db_load (library=0x5555560eca27 "libthread_db.so.1", check_auto_load_safe=false) at ../../src/gdb/linux-thread-db.c:1024
> #9  0x0000555555ac8eda in try_thread_db_load_from_sdir () at ../../src/gdb/linux-thread-db.c:1108
> #10 0x0000555555ac9278 in thread_db_load_search () at ../../src/gdb/linux-thread-db.c:1163
> #11 0x0000555555ac9518 in thread_db_load () at ../../src/gdb/linux-thread-db.c:1225
> #12 0x0000555555ac95e1 in check_for_thread_db () at ../../src/gdb/linux-thread-db.c:1268
> #13 0x0000555555ac9657 in thread_db_new_objfile (objfile=0x555556943ed0) at ../../src/gdb/linux-thread-db.c:1297
> #14 0x000055555569e2d2 in std::__invoke_impl<void, void (*&)(objfile*), objfile*> (__f=@0x5555567925d8: 0x555555ac95e8 <thread_db_new_objfile(objfile*)>) at /usr/include/c++/11/bits/invoke.h:61
> #15 0x000055555569c44a in std::__invoke_r<void, void (*&)(objfile*), objfile*> (__fn=@0x5555567925d8: 0x555555ac95e8 <thread_db_new_objfile(objfile*)>) at /usr/include/c++/11/bits/invoke.h:111
> #16 0x0000555555699d69 in std::_Function_handler<void (objfile*), void (*)(objfile*)>::_M_invoke(std::_Any_data const&, objfile*&&) (__functor=..., __args#0=@0x7fffffffce50: 0x555556943ed0) at /usr/include/c++/11/bits/std_function.h:290
> #17 0x0000555555b5f48b in std::function<void (objfile*)>::operator()(objfile*) const (this=0x5555567925d8, __args#0=0x555556943ed0) at /usr/include/c++/11/bits/std_function.h:590
> #18 0x0000555555b5eba4 in gdb::observers::observable<objfile*>::notify (this=0x5555565b5680 <gdb::observers::new_objfile>, args#0=0x555556943ed0) at ../../src/gdb/../gdbsupport/observable.h:166
> #19 0x0000555555cdd85b in symbol_file_add_with_addrs (abfd=..., name=0x5555569794e0 "/lib/x86_64-linux-gnu/libc.so.6", add_flags=..., addrs=0x7fffffffd0c0, flags=..., parent=0x0) at ../../src/gdb/symfile.c:1131
> #20 0x0000555555cdd9c5 in symbol_file_add_from_bfd (abfd=..., name=0x5555569794e0 "/lib/x86_64-linux-gnu/libc.so.6", add_flags=..., addrs=0x7fffffffd0c0, flags=..., parent=0x0) at ../../src/gdb/symfile.c:1167
> #21 0x0000555555c9dd69 in solib_read_symbols (so=0x5555569792d0, flags=...) at ../../src/gdb/solib.c:730
> #22 0x0000555555c9e7b7 in solib_add (pattern=0x0, from_tty=0, readsyms=1) at ../../src/gdb/solib.c:1041
> #23 0x0000555555c9f61d in handle_solib_event () at ../../src/gdb/solib.c:1315
> #24 0x0000555555729c26 in bpstat_stop_status (aspace=0x555556606800, bp_addr=0x7ffff7fe7278, thread=0x555556816bd0, ws=..., stop_chain=0x0) at ../../src/gdb/breakpoint.c:5702
> #25 0x0000555555a62e41 in handle_signal_stop (ecs=0x7fffffffd670) at ../../src/gdb/infrun.c:6517
> #26 0x0000555555a61479 in handle_inferior_event (ecs=0x7fffffffd670) at ../../src/gdb/infrun.c:6000
> #27 0x0000555555a5c7b5 in fetch_inferior_event () at ../../src/gdb/infrun.c:4403
> #28 0x0000555555a35b65 in inferior_event_handler (event_type=INF_REG_EVENT) at ../../src/gdb/inf-loop.c:41
> #29 0x0000555555aae0c9 in handle_target_event (error=0, client_data=0x0) at ../../src/gdb/linux-nat.c:4231
> 
> 
> Now, when I try the same on a Fedora 32 machine, I see the GDB crash due to the stale
> LWP still in the LWP list with no corresponding thread_info.  On this
> machine, glibc predates the changes that make it possible to use libthread_db with
> non-threaded processes, so try_thread_db_load doesn't manage to open a connection
> to libthread_db, and thus we don't end up in linux_stop_and_wait_all_lwps, and thus
> the stale lwp is not deleted.  And so a subsequent "kill" command crashes.
> 
> I wrote that patch originally on an Ubuntu 20.04 machine (vs the Ubuntu 22.04 I have now),
> and it must be that that version also predates the glibc change, and thus behaves like
> this Fedora 32 box.  You are very likely using a newer Fedora which has the glibc change.

...

>> What are your thoughts on including this, or something like this with
>> this commit?  My patch, which applies on top of this commit, is included
>> at the end of this email.  Please feel free to take any changes that you
>> feel add value.
> 
> I'm totally fine with such a command, though the test I had added covers
> as much as it would, as the "kill" command fails when the maint command
> would fail, and passes when the maint command passes.  But I'll incorporate
> it.
> 

I realized that my description of the problem above practically
suggests a way to expose the crash everywhere -- just catch the exec
event with "catch exec", so that the post-exec program doesn't even
get to the libc.so.6 load event, and issue "kill" there, or use "maint info linux-lwps".
So I've adjusted the patch to add a new testcase doing that.  I've attached two
patches, one adding your "maint info linux-lwps", now with NEWS/docs, and
the updated version of the crash fix and testcase.

WDYT?

Pedro Alves

[-- Attachment #2: 0001-Add-maint-info-linux-lwps-command.patch --]
[-- Type: text/x-patch, Size: 3930 bytes --]

From 450e0133fc884f027cce4ae65378ea5560f6464d Mon Sep 17 00:00:00 2001
From: Andrew Burgess <aburgess@redhat.com>
Date: Tue, 4 Apr 2023 14:50:35 +0100
Subject: [PATCH 1/2] Add "maint info linux-lwps" command

This adds a maintenance command that lets you list all the LWPs under
control of the linux-nat target.

For example:

 (gdb) maint info linux-lwps
 LWP Ptid        Thread ID
 560948.561047.0 None
 560948.560948.0 1.1

This shows that "560948.561047.0" LWP doesn't map to any thread_info
object, which is bogus.  We'll be using this in a testcase in a
following patch.

Co-Authored-By: Pedro Alves <pedro@palves.net>
Change-Id: Ic4e9e123385976e5cd054391990124b7a20fb3f5
---
 gdb/NEWS            |  3 +++
 gdb/doc/gdb.texinfo |  4 ++++
 gdb/linux-nat.c     | 46 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 53 insertions(+)

diff --git a/gdb/NEWS b/gdb/NEWS
index d729aa24056..3747e7d52c1 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -78,6 +78,9 @@ maintenance info frame-unwinders
 maintenance wait-for-index-cache
   Wait until all pending writes to the index cache have completed.
 
+maintenance info linux-lwps
+  List all LWPs under control of the linux-nat target.
+
 set always-read-ctf on|off
 show always-read-ctf
   When off, CTF is only read if DWARF is not present.  When on, CTF is
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 6c811b8be2e..398bbb88af6 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -40605,6 +40605,10 @@ module (@pxref{Disassembly In Python}), and will only be present after
 that module has been imported.  To force the module to be imported do
 the following:
 
+@kindex maint info linux-lwps
+@item maint info linux-lwps
+Print information about LWPs under control of the Linux native target.
+
 @smallexample
 (@value{GDBP}) python import gdb.disassembler
 @end smallexample
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 944f23de01a..68816ddc999 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -4479,6 +4479,49 @@ current_lwp_ptid (void)
   return inferior_ptid;
 }
 
+/* Implement 'maintenance info linux-lwps'.  Displays some basic
+   information about all the current lwp_info objects.  */
+
+static void
+maintenance_info_lwps (const char *arg, int from_tty)
+{
+  if (all_lwps ().size () == 0)
+    {
+      gdb_printf ("No Linux LWPs\n");
+      return;
+    }
+
+  /* Start the width at 8 to match the column heading below, then
+     figure out the widest ptid string.  We'll use this to build our
+     output table below.  */
+  size_t ptid_width = 8;
+  for (lwp_info *lp : all_lwps ())
+    ptid_width = std::max (ptid_width, lp->ptid.to_string ().size ());
+
+  /* Setup the table headers.  */
+  struct ui_out *uiout = current_uiout;
+  ui_out_emit_table table_emitter (uiout, 2, -1, "linux-lwps");
+  uiout->table_header (ptid_width, ui_left, "lwp-ptid", _("LWP Ptid"));
+  uiout->table_header (9, ui_left, "thread-info", _("Thread ID"));
+  uiout->table_body ();
+
+  /* Display one table row for each lwp_info.  */
+  for (lwp_info *lp : all_lwps ())
+    {
+      ui_out_emit_tuple tuple_emitter (uiout, "lwp-entry");
+
+      struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);
+
+      uiout->field_string ("lwp-ptid", lp->ptid.to_string ().c_str ());
+      if (th == nullptr)
+	uiout->field_string ("thread-info", "None");
+      else
+	uiout->field_string ("thread-info", print_full_thread_id (th));
+
+      uiout->message ("\n");
+    }
+}
+
 void _initialize_linux_nat ();
 void
 _initialize_linux_nat ()
@@ -4516,6 +4559,9 @@ Enables printf debugging output."),
   sigemptyset (&blocked_mask);
 
   lwp_lwpid_htab_create ();
+
+  add_cmd ("linux-lwps", class_maintenance, maintenance_info_lwps,
+	 _("List the Linux LWPS."), &maintenanceinfolist);
 }
 \f
 

base-commit: 57573e54afb9f7ed957eec43dfd2830f2384c970
prerequisite-patch-id: 3a896bfe4b7c66a2e3a6aa668c5ae8395e5d8a52
-- 
2.36.0


[-- Attachment #3: 0002-gdb-linux-Delete-all-other-LWPs-immediately-on-ptrac.patch --]
[-- Type: text/x-patch, Size: 11883 bytes --]

From ee0a276c08b829ae504fe0eba5badc4f7faf3676 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Wed, 13 Jul 2022 17:16:38 +0100
Subject: [PATCH 2/2] gdb/linux: Delete all other LWPs immediately on ptrace
 exec event

I noticed that on an Ubuntu 20.04 system, after a following patch
("Step over clone syscall w/ breakpoint,
TARGET_WAITKIND_THREAD_CLONED"), the gdb.threads/step-over-exec.exp
was passing cleanly, but still, we'd end up with four new unexpected
GDB core dumps:

		 === gdb Summary ===

 # of unexpected core files      4
 # of expected passes            48

That said patch is making the pre-existing
gdb.threads/step-over-exec.exp testcase (almost silently) expose a
latent problem in gdb/linux-nat.c, resulting in a GDB crash when:

 #1 - a non-leader thread execs
 #2 - the post-exec program stops somewhere
 #3 - you kill the inferior

Instead of #3 directly, the testcase just returns, which ends up in
gdb_exit, tearing down GDB, which kills the inferior, and is thus
equivalent to #3 above.

Vis:

 $ gdb --args ./gdb /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true
 ...
 (top-gdb) r
 ...
 (gdb) b main
 ...
 (gdb) r
 ...
 Breakpoint 1, main (argc=1, argv=0x7fffffffdb88) at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec.c:69
 69        argv0 = argv[0];
 (gdb) c
 Continuing.
 [New Thread 0x7ffff7d89700 (LWP 2506975)]
 Other going in exec.
 Exec-ing /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
 process 2506769 is executing new program: /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd

 Thread 1 "step-over-exec-" hit Breakpoint 1, main () at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec-execd.c:28
 28        foo ();
 (gdb) k
 ...
 Thread 1 "gdb" received signal SIGSEGV, Segmentation fault.
 0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
 393         return m_suspend.waitstatus_pending_p;
 (top-gdb) bt
 #0  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
 #1  0x0000555555a884d1 in get_pending_child_status (lp=0x5555579b8230, ws=0x7fffffffd130) at ../../src/gdb/linux-nat.c:1345
 #2  0x0000555555a8e5e6 in kill_unfollowed_child_callback (lp=0x5555579b8230) at ../../src/gdb/linux-nat.c:3564
 #3  0x0000555555a92a26 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (this=0x0, ecall=..., args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:284
 #4  0x0000555555a92a51 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:278
 #5  0x0000555555a91f84 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffd210, args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:247
 #6  0x0000555555a87072 in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:864
 #7  0x0000555555a8e732 in linux_nat_target::kill (this=0x55555653af40 <the_amd64_linux_nat_target>) at ../../src/gdb/linux-nat.c:3590
 #8  0x0000555555cfdc11 in target_kill () at ../../src/gdb/target.c:911
 ...

The root of the problem is that when a non-leader LWP execs, it just
changes its tid to the tgid, replacing the pre-exec leader thread,
becoming the new leader.  There's no thread exit event for the execing
thread.  It's as if the old pre-exec LWP vanishes without trace.  The
ptrace man page says:

"PTRACE_O_TRACEEXEC (since Linux 2.5.46)
	Stop the tracee at the next execve(2).  A waitpid(2) by the
	tracer will return a status value such that

	  status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))

	If the execing thread is not a thread group leader, the thread
	ID is reset to thread group leader's ID before this stop.
	Since Linux 3.0, the former thread ID can be retrieved with
	PTRACE_GETEVENTMSG."

When the core of GDB processes an exec events, it deletes all the
threads of the inferior.  But, that is too late -- deleting the thread
does not delete the corresponding LWP, so we end leaving the pre-exec
non-leader LWP stale in the LWP list.  That's what leads to the crash
above -- linux_nat_target::kill iterates over all LWPs, and after the
patch in question, that code will look for the corresponding
thread_info for each LWP.  For the pre-exec non-leader LWP still
listed, won't find one.

This patch fixes it, by deleting the pre-exec non-leader LWP (and
thread) from the LWP/thread lists as soon as we get an exec event out
of ptrace.

GDBserver does not need an equivalent fix, because it is already doing
this, as side effect of mourning the pre-exec process, in
gdbserver/linux-low.cc:

  else if (event == PTRACE_EVENT_EXEC && cs.report_exec_events)
    {
...
      /* Delete the execing process and all its threads.  */
      mourn (proc);
      switch_to_thread (nullptr);


The crash with gdb.threads/step-over-exec.exp is not observable on
newer systems, which postdate the glibc change to move "libpthread.so"
internals to "libc.so.6", because right after the exec, GDB traps a
load event for "libc.so.6", which leads to GDB trying to open
libthread_db for the post-exec inferior, and, on such systems that
succeeds.  When we load libthread_db, we call
linux_stop_and_wait_all_lwps, which, as the name suggests, stops all
lwps, and then waits to see their stops.  While doing this, GDB
detects that the pre-exec stale LWP is gone, and deletes it.

If we use "catch exec" to stop right at the exec before the
"libc.so.6" load event ever happens, and issue "kill" right there,
then GDB crashes on newer systems as well.  So instead of tweaking
gdb.threads/step-over-exec.exp to cover the fix, add a new
gdb.threads/threads-after-exec.exp testcase that uses "catch exec".

Also tweak a comment in infrun.c:follow_exec referring to how
linux-nat.c used to behave, as it would become stale otherwise.

Change-Id: I21ec18072c7750f3a972160ae6b9e46590376643
---
 gdb/infrun.c                                  |  8 +--
 gdb/linux-nat.c                               | 15 ++++
 .../gdb.threads/threads-after-exec.exp        | 70 +++++++++++++++++++
 3 files changed, 88 insertions(+), 5 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/threads-after-exec.exp

diff --git a/gdb/infrun.c b/gdb/infrun.c
index abe49ae0f2f..93edc224622 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1224,13 +1224,11 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
      some other thread does the exec, and even if the main thread was
      stopped or already gone.  We may still have non-leader threads of
      the process on our list.  E.g., on targets that don't have thread
-     exit events (like remote); or on native Linux in non-stop mode if
-     there were only two threads in the inferior and the non-leader
-     one is the one that execs (and nothing forces an update of the
-     thread list up to here).  When debugging remotely, it's best to
+     exit events (like remote) and nothing forces an update of the
+     thread list up to here.  When debugging remotely, it's best to
      avoid extra traffic, when possible, so avoid syncing the thread
      list with the target, and instead go ahead and delete all threads
-     of the process but one that reported the event.  Note this must
+     of the process but the one that reported the event.  Note this must
      be done before calling update_breakpoints_after_exec, as
      otherwise clearing the threads' resources would reference stale
      thread breakpoints -- it may have been one of these threads that
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 68816ddc999..90ac94440b8 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -2001,6 +2001,21 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
 	 thread execs, it changes its tid to the tgid, and the old
 	 tgid thread might have not been resumed.  */
       lp->resumed = 1;
+
+      /* All other LWPs are gone now.  We'll have received a thread
+	 exit notification for all threads other the execing one.
+	 That one, if it wasn't the leader, just silently changes its
+	 tid to the tgid, and the previous leader vanishes.  Since
+	 Linux 3.0, the former thread ID can be retrieved with
+	 PTRACE_GETEVENTMSG, but since we support older kernels, don't
+	 bother with it, and just walk the LWP list.  Even with
+	 PTRACE_GETEVENTMSG, we'd still need to lookup the
+	 corresponding LWP object, and it would be an extra ptrace
+	 syscall, so this way may even be more efficient.  */
+      for (lwp_info *other_lp : all_lwps_safe ())
+	if (other_lp != lp && other_lp->ptid.pid () == lp->ptid.pid ())
+	  exit_lwp (other_lp);
+
       return 0;
     }
 
diff --git a/gdb/testsuite/gdb.threads/threads-after-exec.exp b/gdb/testsuite/gdb.threads/threads-after-exec.exp
new file mode 100644
index 00000000000..824dda349a6
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/threads-after-exec.exp
@@ -0,0 +1,70 @@
+# Copyright 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test that after an exec of a non-leader thread, we don't leave the
+# non-leader thread listed in internal thread lists, causing problems.
+
+standard_testfile .c -execd.c
+
+proc do_test { } {
+    global srcdir subdir srcfile srcfile2 binfile testfile
+    global decimal
+
+    # Compile main binary (the one that does the exec).
+    if {[gdb_compile_pthreads $srcdir/$subdir/$srcfile $binfile \
+	     executable {debug}] != "" } {
+	return -1
+    }
+
+    # Compile the second binary (the one that gets exec'd).
+    if {[gdb_compile $srcdir/$subdir/$srcfile2 $binfile-execd \
+	     executable {debug}] != "" } {
+	return -1
+    }
+
+    clean_restart $binfile
+
+    if ![runto_main] {
+	return
+    }
+
+    gdb_test "catch exec" "Catchpoint $decimal \\(exec\\)"
+
+    gdb_test "continue" "Catchpoint $decimal .*" "continue until exec"
+
+    # Confirm we only have one thread in the thread list.
+    gdb_test "info threads" "\\* 1\[ \t\]+\[^\r\n\]+.*"
+
+    if {[istarget *-*-linux*] && [gdb_is_target_native]} {
+	# Confirm there's only one LWP in the list as well, and that
+	# it is bound to thread 1.1.
+	set inf_pid [get_inferior_pid]
+	gdb_test_multiple "maint info linux-lwps" "" {
+	    -wrap -re "Thread ID *\r\n$inf_pid\.$inf_pid\.0\[ \t\]+1\.1 *" {
+		pass $gdb_test_name
+	    }
+	}
+    }
+
+    # Test that GDB is able to kill the inferior.  This used to crash
+    # on native Linux as GDB did not dispose of the pre-exec LWP for
+    # the non-leader (and that LWP did not have a matching thread in
+    # the core thread list).
+    gdb_test "with confirm off -- kill" \
+	"\\\[Inferior 1 (.*) killed\\\]" \
+	"kill inferior"
+}
+
+do_test
-- 
2.36.0


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

* Re: [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event
  2023-04-04 13:57     ` Pedro Alves
  2023-04-14 19:29       ` Pedro Alves
@ 2023-05-26 14:45       ` Andrew Burgess
  1 sibling, 0 replies; 100+ messages in thread
From: Andrew Burgess @ 2023-05-26 14:45 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches


Hi Pedro,

Sorry for the delay in looking at this again.  I had to find some time
to investigate this a little more as my result were still not aligning
with what you reported, but I think I understand what's going on now...

Pedro Alves <pedro@palves.net> writes:

> Hi Andrew,
>
> Took me a bit to find time to investigate this.  See below.
>
> On 2023-03-21 2:50 p.m., Andrew Burgess wrote:
>> Pedro Alves <pedro@palves.net> writes:
>> 
>>> I noticed that after a following patch ("Step over clone syscall w/
>>> breakpoint, TARGET_WAITKIND_THREAD_CLONED"), the
>>> gdb.threads/step-over-exec.exp was passing cleanly, but still, we'd
>>> end up with four new unexpected GDB core dumps:
>>>
>>> 		 === gdb Summary ===
>>>
>>>  # of unexpected core files      4
>>>  # of expected passes            48
>>>
>>> That said patch is making the pre-existing
>>> gdb.threads/step-over-exec.exp testcase (almost silently) expose a
>>> latent problem in gdb/linux-nat.c, resulting in a GDB crash when:
>>>
>>>  #1 - a non-leader thread execs
>>>  #2 - the post-exec program stops somewhere
>>>  #3 - you kill the inferior
>>>
>>> Instead of #3 directly, the testcase just returns, which ends up in
>>> gdb_exit, tearing down GDB, which kills the inferior, and is thus
>>> equivalent to #3 above.
>>>
>>> Vis:
>>>
>>>  $ gdb --args ./gdb /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true
>>>  ...
>>>  (top-gdb) r
>>>  ...
>>>  (gdb) b main
>>>  ...
>>>  (gdb) r
>>>  ...
>>>  Breakpoint 1, main (argc=1, argv=0x7fffffffdb88) at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec.c:69
>>>  69        argv0 = argv[0];
>>>  (gdb) c
>>>  Continuing.
>>>  [New Thread 0x7ffff7d89700 (LWP 2506975)]
>>>  Other going in exec.
>>>  Exec-ing /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
>>>  process 2506769 is executing new program: /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
>>>
>>>  Thread 1 "step-over-exec-" hit Breakpoint 1, main () at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec-execd.c:28
>>>  28        foo ();
>>>  (gdb) k
>>>  ...
>>>  Thread 1 "gdb" received signal SIGSEGV, Segmentation fault.
>>>  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
>>>  393         return m_suspend.waitstatus_pending_p;
>>>  (top-gdb) bt
>>>  #0  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
>>>  #1  0x0000555555a884d1 in get_pending_child_status (lp=0x5555579b8230, ws=0x7fffffffd130) at ../../src/gdb/linux-nat.c:1345
>>>  #2  0x0000555555a8e5e6 in kill_unfollowed_child_callback (lp=0x5555579b8230) at ../../src/gdb/linux-nat.c:3564
>>>  #3  0x0000555555a92a26 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (this=0x0, ecall=..., args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:284
>>>  #4  0x0000555555a92a51 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:278
>>>  #5  0x0000555555a91f84 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffd210, args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:247
>>>  #6  0x0000555555a87072 in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:864
>>>  #7  0x0000555555a8e732 in linux_nat_target::kill (this=0x55555653af40 <the_amd64_linux_nat_target>) at ../../src/gdb/linux-nat.c:3590
>>>  #8  0x0000555555cfdc11 in target_kill () at ../../src/gdb/target.c:911
>>>  ...
>> 
>> It wasn't 100% clear to me if the above session was supposed to show a
>> failure with GDB prior to *this* commit, or was a demonstration of what
>> would happen if this commit is skipped, and the later commits applied.
>
> Yes, prior to this commit.

OK, but check your backtrace, notice frame #2 is in
kill_unfollowed_child_callback.  This is only added in a later patch in
this series.  If we roll back to just this patch then the crash doesn't
reproduce!  But I can explain that...

>
>> 
>> I thought it was the second case, but I was so unsure that I tried the
>> reproducer anyway.   Just in case I'm wrong, the above example doesn't
>> seem to fail prior to this commit.
>
> This surprised me, and when I tried it myself, I was even more surprised,
> for I couldn't reproduce it either!
>
> But I figured it out.
>
> I'm usually using Ubuntu 22.04 for development nowadays, and in that system, indeed I can't
> reproduce it.  Right after the exec, GDB traps a load event for "libc.so.6", which leads to
> gdb trying to open libthread_db for the post-exec inferior, and, it succeeds.  When we load
> libthread_db, we call linux_stop_and_wait_all_lwps, which, as the name suggests, stops all lwps,
> and then waits to see their stops.  While doing this, GDB detects that the pre-exec stale
> LWP is gone, and deletes it.
>
> The logs show:
>
> [linux-nat] linux_nat_wait_1: waitpid 1725529 received SIGTRAP - Trace/breakpoint trap (stopped)
> [linux-nat] save_stop_reason: 1725529.1725529.0 stopped by software breakpoint
> [linux-nat] linux_nat_wait_1: waitpid(-1, ...) returned 0, ERRNO-OK
> [linux-nat] resume_stopped_resumed_lwps: NOT resuming LWP 1725529.1725658.0, not stopped
> [linux-nat] resume_stopped_resumed_lwps: NOT resuming LWP 1725529.1725529.0, has pending status
> [linux-nat] linux_nat_wait_1: trap ptid is 1725529.1725529.0.
> [linux-nat] linux_nat_wait_1: exit
> [linux-nat] stop_callback: kill 1725529.1725658.0 **<SIGSTOP>**
> [linux-nat] stop_callback: lwp kill -1 No such process
> [linux-nat] wait_lwp: 1725529.1725658.0 vanished.

When we look at just this patch, the linux_nat_target::kill function
calls kill_unfollowed_fork_children, which, unlike your later updated
kill_unfollowed_child_callback, doesn't require us to lookup a
thread_info object.  It is the lookup (and dereference) of the
thread_info object that causes the segfault you are seeing.

Once we skip that code, the current linux_nat_target::kill function
actually starts with exactly the same function calls as
linux_stop_and_wait_all_lwps (maybe we should be calling that
function?)

And so, for _me_ when I 'kill' I end up calling the stop_callback
followed by the stop_wait_callback, which cleans up the rogue thread
just like you see.

>
> And the backtrace is:
>
> (top-gdb) bt
> #0  wait_lwp (lp=0x555556f37350) at ../../src/gdb/linux-nat.c:2069
> #1  0x0000555555aa8fbf in stop_wait_callback (lp=0x555556f37350) at ../../src/gdb/linux-nat.c:2375
> #2  0x0000555555ab12b3 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (__closure=0x0, ecall=..., args#0=0x555556f37350) at ../../src/gdb/../gdbsupport/function-view.h:326
> #3  0x0000555555ab12e2 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:320
> #4  0x0000555555ab0610 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffca90, args#0=0x555556f37350) at ../../src/gdb/../gdbsupport/function-view.h:289
> #5  0x0000555555aa4c2d in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:867
> #6  0x0000555555aa8a03 in linux_stop_and_wait_all_lwps () at ../../src/gdb/linux-nat.c:2229
> #7  0x0000555555ac8525 in try_thread_db_load_1 (info=0x555556a66dd0) at ../../src/gdb/linux-thread-db.c:923
> #8  0x0000555555ac89d5 in try_thread_db_load (library=0x5555560eca27 "libthread_db.so.1", check_auto_load_safe=false) at ../../src/gdb/linux-thread-db.c:1024
> #9  0x0000555555ac8eda in try_thread_db_load_from_sdir () at ../../src/gdb/linux-thread-db.c:1108
> #10 0x0000555555ac9278 in thread_db_load_search () at ../../src/gdb/linux-thread-db.c:1163
> #11 0x0000555555ac9518 in thread_db_load () at ../../src/gdb/linux-thread-db.c:1225
> #12 0x0000555555ac95e1 in check_for_thread_db () at ../../src/gdb/linux-thread-db.c:1268
> #13 0x0000555555ac9657 in thread_db_new_objfile (objfile=0x555556943ed0) at ../../src/gdb/linux-thread-db.c:1297
> #14 0x000055555569e2d2 in std::__invoke_impl<void, void (*&)(objfile*), objfile*> (__f=@0x5555567925d8: 0x555555ac95e8 <thread_db_new_objfile(objfile*)>) at /usr/include/c++/11/bits/invoke.h:61
> #15 0x000055555569c44a in std::__invoke_r<void, void (*&)(objfile*), objfile*> (__fn=@0x5555567925d8: 0x555555ac95e8 <thread_db_new_objfile(objfile*)>) at /usr/include/c++/11/bits/invoke.h:111
> #16 0x0000555555699d69 in std::_Function_handler<void (objfile*), void (*)(objfile*)>::_M_invoke(std::_Any_data const&, objfile*&&) (__functor=..., __args#0=@0x7fffffffce50: 0x555556943ed0) at /usr/include/c++/11/bits/std_function.h:290
> #17 0x0000555555b5f48b in std::function<void (objfile*)>::operator()(objfile*) const (this=0x5555567925d8, __args#0=0x555556943ed0) at /usr/include/c++/11/bits/std_function.h:590
> #18 0x0000555555b5eba4 in gdb::observers::observable<objfile*>::notify (this=0x5555565b5680 <gdb::observers::new_objfile>, args#0=0x555556943ed0) at ../../src/gdb/../gdbsupport/observable.h:166
> #19 0x0000555555cdd85b in symbol_file_add_with_addrs (abfd=..., name=0x5555569794e0 "/lib/x86_64-linux-gnu/libc.so.6", add_flags=..., addrs=0x7fffffffd0c0, flags=..., parent=0x0) at ../../src/gdb/symfile.c:1131
> #20 0x0000555555cdd9c5 in symbol_file_add_from_bfd (abfd=..., name=0x5555569794e0 "/lib/x86_64-linux-gnu/libc.so.6", add_flags=..., addrs=0x7fffffffd0c0, flags=..., parent=0x0) at ../../src/gdb/symfile.c:1167
> #21 0x0000555555c9dd69 in solib_read_symbols (so=0x5555569792d0, flags=...) at ../../src/gdb/solib.c:730
> #22 0x0000555555c9e7b7 in solib_add (pattern=0x0, from_tty=0, readsyms=1) at ../../src/gdb/solib.c:1041
> #23 0x0000555555c9f61d in handle_solib_event () at ../../src/gdb/solib.c:1315
> #24 0x0000555555729c26 in bpstat_stop_status (aspace=0x555556606800, bp_addr=0x7ffff7fe7278, thread=0x555556816bd0, ws=..., stop_chain=0x0) at ../../src/gdb/breakpoint.c:5702
> #25 0x0000555555a62e41 in handle_signal_stop (ecs=0x7fffffffd670) at ../../src/gdb/infrun.c:6517
> #26 0x0000555555a61479 in handle_inferior_event (ecs=0x7fffffffd670) at ../../src/gdb/infrun.c:6000
> #27 0x0000555555a5c7b5 in fetch_inferior_event () at ../../src/gdb/infrun.c:4403
> #28 0x0000555555a35b65 in inferior_event_handler (event_type=INF_REG_EVENT) at ../../src/gdb/inf-loop.c:41
> #29 0x0000555555aae0c9 in handle_target_event (error=0, client_data=0x0) at ../../src/gdb/linux-nat.c:4231
>
>
> Now, when I try the same on a Fedora 32 machine, I see the GDB crash due to the stale
> LWP still in the LWP list with no corresponding thread_info.  On this
> machine, glibc predates the changes that make it possible to use libthread_db with
> non-threaded processes, so try_thread_db_load doesn't manage to open a connection
> to libthread_db, and thus we don't end up in linux_stop_and_wait_all_lwps, and thus
> the stale lwp is not deleted.  And so a subsequent "kill" command crashes.
>
> I wrote that patch originally on an Ubuntu 20.04 machine (vs the Ubuntu 22.04 I have now),
> and it must be that that version also predates the glibc change, and thus behaves like
> this Fedora 32 box.  You are very likely using a newer Fedora which has the glibc change.

To my shame I actually running an even older Fedora install.  One day
GDB will be finished, then I'll find time to reinstall :)  So I'm not
cleaning up the thread in the libthread_db code.

>
>> 
>>>
>>> The root of the problem is that when a non-leader LWP execs, it just
>>> changes its tid to the tgid, replacing the pre-exec leader thread,
>>> becoming the new leader.  There's no thread exit event for the execing
>>> thread.  It's as if the old pre-exec LWP vanishes without trace.  The
>>> ptrace man page says:
>>>
>>> "PTRACE_O_TRACEEXEC (since Linux 2.5.46)
>>> 	Stop the tracee at the next execve(2).  A waitpid(2) by the
>>> 	tracer will return a status value such that
>>>
>>> 	  status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
>>>
>>> 	If the execing thread is not a thread group leader, the thread
>>> 	ID is reset to thread group leader's ID before this stop.
>>> 	Since Linux 3.0, the former thread ID can be retrieved with
>>> 	PTRACE_GETEVENTMSG."
>>>
>>> When the core of GDB processes an exec events, it deletes all the
>>> threads of the inferior.  But, that is too late -- deleting the thread
>>> does not delete the corresponding LWP, so we end leaving the pre-exec
>>> non-leader LWP stale in the LWP list.  That's what leads to the crash
>>> above -- linux_nat_target::kill iterates over all LWPs, and after the
>>> patch in question, that code will look for the corresponding
>>> thread_info for each LWP.  For the pre-exec non-leader LWP still
>>> listed, won't find one.
>>>
>>> This patch fixes it, by deleting the pre-exec non-leader LWP (and
>>> thread) from the LWP/thread lists as soon as we get an exec event out
>>> of ptrace.
>> 
>> Given that we don't have a test *right now* for this issue, and instead
>> rely on a future patch not failing.  I wondered if there was any way
>> that we could trigger a failure.
>> 
>> So I was poking around looking for places where we iterate over the
>> all_lwps() list wondering which we could trigger that might cause a
>> failure...
>> 
>> ... and then I thought: why not just have GDB tell us that the
>> all_lwps() list is broken.
>> 
>> So I hacked up a new 'maint info linux-lwps' command.  It's not very
>> interesting right now, here's the output in a multi-threaded inferior
>> prior to the exec:
>> 
>>   (gdb) maintenance info linux-lwps 
>>   LWP Ptid          Thread ID
>>   1707218.1707239.0 2           
>>   1707218.1707218.0 1           
>> 
>> And in your failure case (after the exec):
>> 
>>   (gdb) maintenance info linux-lwps 
>>   LWP Ptid          Thread ID 
>>   1708883.1708895.0 None      
>>   1708883.1708883.0 1         
>> 
>> And then we can check this from the testscript, and now we have a test
>> that fails before this commit, and passes afterwards.
>> 
>> And in the future we might find other information we want to add in the
>> new maintenance command.
>> 
>> What are your thoughts on including this, or something like this with
>> this commit?  My patch, which applies on top of this commit, is included
>> at the end of this email.  Please feel free to take any changes that you
>> feel add value.
>
> I'm totally fine with such a command, though the test I had added covers
> as much as it would, as the "kill" command fails when the maint command
> would fail, and passes when the maint command passes.  But I'll incorporate
> it.

Given the above this isn't quite right.  It would appear that someone
using the libthread_db code would, as you point out, see both tests
fail.  But without the libthread_db code the 'maint' command will show
the problem, while the 'kill' command isn't going to, even if you
force the 'kill' earlier like you propose in your new patch[1].

This doesn't actually change anything -- you did add the new 'maint'
command in your next patch, it was just the behaviour I was seeing
didn't align with what you saw, and I think we now know why.

I'll reply separately to [1] with my feedback on that patch.

[1] https://inbox.sourceware.org/gdb-patches/5b80a2c3-3679-fb86-27f3-0dcc9c019562@palves.net/#t


Thanks,
Andrew


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

* Re: [PATCH 03/31] gdb/linux: Delete all other LWPs immediately on ptrace exec event
  2023-04-14 19:29       ` Pedro Alves
@ 2023-05-26 15:04         ` Andrew Burgess
  2023-11-13 14:04           ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-05-26 15:04 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> Hi!
>
> On 2023-04-04 2:57 p.m., Pedro Alves wrote:
>> On 2023-03-21 2:50 p.m., Andrew Burgess wrote:
>>>
>>> I thought it was the second case, but I was so unsure that I tried the
>>> reproducer anyway.   Just in case I'm wrong, the above example doesn't
>>> seem to fail prior to this commit.
>> 
>> This surprised me, and when I tried it myself, I was even more surprised,
>> for I couldn't reproduce it either!
>> 
>> But I figured it out.
>> 
>> I'm usually using Ubuntu 22.04 for development nowadays, and in that system, indeed I can't
>> reproduce it.  Right after the exec, GDB traps a load event for "libc.so.6", which leads to
>> gdb trying to open libthread_db for the post-exec inferior, and, it succeeds.  When we load
>> libthread_db, we call linux_stop_and_wait_all_lwps, which, as the name suggests, stops all lwps,
>> and then waits to see their stops.  While doing this, GDB detects that the pre-exec stale
>> LWP is gone, and deletes it.
>> 
>> The logs show:
>> 
>> [linux-nat] linux_nat_wait_1: waitpid 1725529 received SIGTRAP - Trace/breakpoint trap (stopped)
>> [linux-nat] save_stop_reason: 1725529.1725529.0 stopped by software breakpoint
>> [linux-nat] linux_nat_wait_1: waitpid(-1, ...) returned 0, ERRNO-OK
>> [linux-nat] resume_stopped_resumed_lwps: NOT resuming LWP 1725529.1725658.0, not stopped
>> [linux-nat] resume_stopped_resumed_lwps: NOT resuming LWP 1725529.1725529.0, has pending status
>> [linux-nat] linux_nat_wait_1: trap ptid is 1725529.1725529.0.
>> [linux-nat] linux_nat_wait_1: exit
>> [linux-nat] stop_callback: kill 1725529.1725658.0 **<SIGSTOP>**
>> [linux-nat] stop_callback: lwp kill -1 No such process
>> [linux-nat] wait_lwp: 1725529.1725658.0 vanished.
>> 
>> And the backtrace is:
>> 
>> (top-gdb) bt
>> #0  wait_lwp (lp=0x555556f37350) at ../../src/gdb/linux-nat.c:2069
>> #1  0x0000555555aa8fbf in stop_wait_callback (lp=0x555556f37350) at ../../src/gdb/linux-nat.c:2375
>> #2  0x0000555555ab12b3 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (__closure=0x0, ecall=..., args#0=0x555556f37350) at ../../src/gdb/../gdbsupport/function-view.h:326
>> #3  0x0000555555ab12e2 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:320
>> #4  0x0000555555ab0610 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffca90, args#0=0x555556f37350) at ../../src/gdb/../gdbsupport/function-view.h:289
>> #5  0x0000555555aa4c2d in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:867
>> #6  0x0000555555aa8a03 in linux_stop_and_wait_all_lwps () at ../../src/gdb/linux-nat.c:2229
>> #7  0x0000555555ac8525 in try_thread_db_load_1 (info=0x555556a66dd0) at ../../src/gdb/linux-thread-db.c:923
>> #8  0x0000555555ac89d5 in try_thread_db_load (library=0x5555560eca27 "libthread_db.so.1", check_auto_load_safe=false) at ../../src/gdb/linux-thread-db.c:1024
>> #9  0x0000555555ac8eda in try_thread_db_load_from_sdir () at ../../src/gdb/linux-thread-db.c:1108
>> #10 0x0000555555ac9278 in thread_db_load_search () at ../../src/gdb/linux-thread-db.c:1163
>> #11 0x0000555555ac9518 in thread_db_load () at ../../src/gdb/linux-thread-db.c:1225
>> #12 0x0000555555ac95e1 in check_for_thread_db () at ../../src/gdb/linux-thread-db.c:1268
>> #13 0x0000555555ac9657 in thread_db_new_objfile (objfile=0x555556943ed0) at ../../src/gdb/linux-thread-db.c:1297
>> #14 0x000055555569e2d2 in std::__invoke_impl<void, void (*&)(objfile*), objfile*> (__f=@0x5555567925d8: 0x555555ac95e8 <thread_db_new_objfile(objfile*)>) at /usr/include/c++/11/bits/invoke.h:61
>> #15 0x000055555569c44a in std::__invoke_r<void, void (*&)(objfile*), objfile*> (__fn=@0x5555567925d8: 0x555555ac95e8 <thread_db_new_objfile(objfile*)>) at /usr/include/c++/11/bits/invoke.h:111
>> #16 0x0000555555699d69 in std::_Function_handler<void (objfile*), void (*)(objfile*)>::_M_invoke(std::_Any_data const&, objfile*&&) (__functor=..., __args#0=@0x7fffffffce50: 0x555556943ed0) at /usr/include/c++/11/bits/std_function.h:290
>> #17 0x0000555555b5f48b in std::function<void (objfile*)>::operator()(objfile*) const (this=0x5555567925d8, __args#0=0x555556943ed0) at /usr/include/c++/11/bits/std_function.h:590
>> #18 0x0000555555b5eba4 in gdb::observers::observable<objfile*>::notify (this=0x5555565b5680 <gdb::observers::new_objfile>, args#0=0x555556943ed0) at ../../src/gdb/../gdbsupport/observable.h:166
>> #19 0x0000555555cdd85b in symbol_file_add_with_addrs (abfd=..., name=0x5555569794e0 "/lib/x86_64-linux-gnu/libc.so.6", add_flags=..., addrs=0x7fffffffd0c0, flags=..., parent=0x0) at ../../src/gdb/symfile.c:1131
>> #20 0x0000555555cdd9c5 in symbol_file_add_from_bfd (abfd=..., name=0x5555569794e0 "/lib/x86_64-linux-gnu/libc.so.6", add_flags=..., addrs=0x7fffffffd0c0, flags=..., parent=0x0) at ../../src/gdb/symfile.c:1167
>> #21 0x0000555555c9dd69 in solib_read_symbols (so=0x5555569792d0, flags=...) at ../../src/gdb/solib.c:730
>> #22 0x0000555555c9e7b7 in solib_add (pattern=0x0, from_tty=0, readsyms=1) at ../../src/gdb/solib.c:1041
>> #23 0x0000555555c9f61d in handle_solib_event () at ../../src/gdb/solib.c:1315
>> #24 0x0000555555729c26 in bpstat_stop_status (aspace=0x555556606800, bp_addr=0x7ffff7fe7278, thread=0x555556816bd0, ws=..., stop_chain=0x0) at ../../src/gdb/breakpoint.c:5702
>> #25 0x0000555555a62e41 in handle_signal_stop (ecs=0x7fffffffd670) at ../../src/gdb/infrun.c:6517
>> #26 0x0000555555a61479 in handle_inferior_event (ecs=0x7fffffffd670) at ../../src/gdb/infrun.c:6000
>> #27 0x0000555555a5c7b5 in fetch_inferior_event () at ../../src/gdb/infrun.c:4403
>> #28 0x0000555555a35b65 in inferior_event_handler (event_type=INF_REG_EVENT) at ../../src/gdb/inf-loop.c:41
>> #29 0x0000555555aae0c9 in handle_target_event (error=0, client_data=0x0) at ../../src/gdb/linux-nat.c:4231
>> 
>> 
>> Now, when I try the same on a Fedora 32 machine, I see the GDB crash due to the stale
>> LWP still in the LWP list with no corresponding thread_info.  On this
>> machine, glibc predates the changes that make it possible to use libthread_db with
>> non-threaded processes, so try_thread_db_load doesn't manage to open a connection
>> to libthread_db, and thus we don't end up in linux_stop_and_wait_all_lwps, and thus
>> the stale lwp is not deleted.  And so a subsequent "kill" command crashes.
>> 
>> I wrote that patch originally on an Ubuntu 20.04 machine (vs the Ubuntu 22.04 I have now),
>> and it must be that that version also predates the glibc change, and thus behaves like
>> this Fedora 32 box.  You are very likely using a newer Fedora which has the glibc change.
>
> ...
>
>>> What are your thoughts on including this, or something like this with
>>> this commit?  My patch, which applies on top of this commit, is included
>>> at the end of this email.  Please feel free to take any changes that you
>>> feel add value.
>> 
>> I'm totally fine with such a command, though the test I had added covers
>> as much as it would, as the "kill" command fails when the maint command
>> would fail, and passes when the maint command passes.  But I'll incorporate
>> it.
>> 
>
> I realized that my description of the problem above practically
> suggests a way to expose the crash everywhere -- just catch the exec
> event with "catch exec", so that the post-exec program doesn't even
> get to the libc.so.6 load event, and issue "kill" there, or use "maint info linux-lwps".
> So I've adjusted the patch to add a new testcase doing that.  I've attached two
> patches, one adding your "maint info linux-lwps", now with NEWS/docs, and
> the updated version of the crash fix and testcase.
>
> WDYT?
>
> Pedro Alves
> From 450e0133fc884f027cce4ae65378ea5560f6464d Mon Sep 17 00:00:00 2001
> From: Andrew Burgess <aburgess@redhat.com>
> Date: Tue, 4 Apr 2023 14:50:35 +0100
> Subject: [PATCH 1/2] Add "maint info linux-lwps" command
>
> This adds a maintenance command that lets you list all the LWPs under
> control of the linux-nat target.
>
> For example:
>
>  (gdb) maint info linux-lwps
>  LWP Ptid        Thread ID
>  560948.561047.0 None
>  560948.560948.0 1.1
>
> This shows that "560948.561047.0" LWP doesn't map to any thread_info
> object, which is bogus.  We'll be using this in a testcase in a
> following patch.
>
> Co-Authored-By: Pedro Alves <pedro@palves.net>
> Change-Id: Ic4e9e123385976e5cd054391990124b7a20fb3f5
> ---
>  gdb/NEWS            |  3 +++
>  gdb/doc/gdb.texinfo |  4 ++++
>  gdb/linux-nat.c     | 46 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 53 insertions(+)
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index d729aa24056..3747e7d52c1 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -78,6 +78,9 @@ maintenance info frame-unwinders
>  maintenance wait-for-index-cache
>    Wait until all pending writes to the index cache have completed.
>  
> +maintenance info linux-lwps
> +  List all LWPs under control of the linux-nat target.
> +
>  set always-read-ctf on|off
>  show always-read-ctf
>    When off, CTF is only read if DWARF is not present.  When on, CTF is
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 6c811b8be2e..398bbb88af6 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -40605,6 +40605,10 @@ module (@pxref{Disassembly In Python}), and will only be present after
>  that module has been imported.  To force the module to be imported do
>  the following:
>  
> +@kindex maint info linux-lwps
> +@item maint info linux-lwps
> +Print information about LWPs under control of the Linux native target.
> +
>  @smallexample
>  (@value{GDBP}) python import gdb.disassembler
>  @end smallexample
> diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
> index 944f23de01a..68816ddc999 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -4479,6 +4479,49 @@ current_lwp_ptid (void)
>    return inferior_ptid;
>  }
>  
> +/* Implement 'maintenance info linux-lwps'.  Displays some basic
> +   information about all the current lwp_info objects.  */
> +
> +static void
> +maintenance_info_lwps (const char *arg, int from_tty)
> +{
> +  if (all_lwps ().size () == 0)
> +    {
> +      gdb_printf ("No Linux LWPs\n");
> +      return;
> +    }
> +
> +  /* Start the width at 8 to match the column heading below, then
> +     figure out the widest ptid string.  We'll use this to build our
> +     output table below.  */
> +  size_t ptid_width = 8;
> +  for (lwp_info *lp : all_lwps ())
> +    ptid_width = std::max (ptid_width, lp->ptid.to_string ().size ());
> +
> +  /* Setup the table headers.  */
> +  struct ui_out *uiout = current_uiout;
> +  ui_out_emit_table table_emitter (uiout, 2, -1, "linux-lwps");
> +  uiout->table_header (ptid_width, ui_left, "lwp-ptid", _("LWP Ptid"));
> +  uiout->table_header (9, ui_left, "thread-info", _("Thread ID"));
> +  uiout->table_body ();
> +
> +  /* Display one table row for each lwp_info.  */
> +  for (lwp_info *lp : all_lwps ())
> +    {
> +      ui_out_emit_tuple tuple_emitter (uiout, "lwp-entry");
> +
> +      struct thread_info *th = find_thread_ptid (linux_target, lp->ptid);

After recent changes this line becomes:

  struct thread_info *th = linux_target->find_thread (lp->ptid);

> +
> +      uiout->field_string ("lwp-ptid", lp->ptid.to_string ().c_str ());
> +      if (th == nullptr)
> +	uiout->field_string ("thread-info", "None");
> +      else
> +	uiout->field_string ("thread-info", print_full_thread_id (th));
> +
> +      uiout->message ("\n");
> +    }
> +}
> +
>  void _initialize_linux_nat ();
>  void
>  _initialize_linux_nat ()
> @@ -4516,6 +4559,9 @@ Enables printf debugging output."),
>    sigemptyset (&blocked_mask);
>  
>    lwp_lwpid_htab_create ();
> +
> +  add_cmd ("linux-lwps", class_maintenance, maintenance_info_lwps,
> +	 _("List the Linux LWPS."), &maintenanceinfolist);
>  }
>  \f
>  
>
> base-commit: 57573e54afb9f7ed957eec43dfd2830f2384c970
> prerequisite-patch-id: 3a896bfe4b7c66a2e3a6aa668c5ae8395e5d8a52
> -- 
> 2.36.0
>
> From ee0a276c08b829ae504fe0eba5badc4f7faf3676 Mon Sep 17 00:00:00 2001
> From: Pedro Alves <pedro@palves.net>
> Date: Wed, 13 Jul 2022 17:16:38 +0100
> Subject: [PATCH 2/2] gdb/linux: Delete all other LWPs immediately on ptrace
>  exec event
>
> I noticed that on an Ubuntu 20.04 system, after a following patch
> ("Step over clone syscall w/ breakpoint,
> TARGET_WAITKIND_THREAD_CLONED"), the gdb.threads/step-over-exec.exp
> was passing cleanly, but still, we'd end up with four new unexpected
> GDB core dumps:
>
> 		 === gdb Summary ===
>
>  # of unexpected core files      4
>  # of expected passes            48
>
> That said patch is making the pre-existing
> gdb.threads/step-over-exec.exp testcase (almost silently) expose a
> latent problem in gdb/linux-nat.c, resulting in a GDB crash when:
>
>  #1 - a non-leader thread execs
>  #2 - the post-exec program stops somewhere
>  #3 - you kill the inferior
>
> Instead of #3 directly, the testcase just returns, which ends up in
> gdb_exit, tearing down GDB, which kills the inferior, and is thus
> equivalent to #3 above.
>
> Vis:
>
>  $ gdb --args ./gdb /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true
>  ...
>  (top-gdb) r
>  ...
>  (gdb) b main
>  ...
>  (gdb) r
>  ...
>  Breakpoint 1, main (argc=1, argv=0x7fffffffdb88) at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec.c:69
>  69        argv0 = argv[0];
>  (gdb) c
>  Continuing.
>  [New Thread 0x7ffff7d89700 (LWP 2506975)]
>  Other going in exec.
>  Exec-ing /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
>  process 2506769 is executing new program: /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.threads/step-over-exec/step-over-exec-execr-thread-other-diff-text-segs-true-execd
>
>  Thread 1 "step-over-exec-" hit Breakpoint 1, main () at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.threads/step-over-exec-execd.c:28
>  28        foo ();
>  (gdb) k
>  ...
>  Thread 1 "gdb" received signal SIGSEGV, Segmentation fault.
>  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
>  393         return m_suspend.waitstatus_pending_p;
>  (top-gdb) bt
>  #0  0x000055555574444c in thread_info::has_pending_waitstatus (this=0x0) at ../../src/gdb/gdbthread.h:393
>  #1  0x0000555555a884d1 in get_pending_child_status (lp=0x5555579b8230, ws=0x7fffffffd130) at ../../src/gdb/linux-nat.c:1345
>  #2  0x0000555555a8e5e6 in kill_unfollowed_child_callback (lp=0x5555579b8230) at ../../src/gdb/linux-nat.c:3564
>  #3  0x0000555555a92a26 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::operator()(gdb::fv_detail::erased_callable, lwp_info*) const (this=0x0, ecall=..., args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:284
>  #4  0x0000555555a92a51 in gdb::function_view<int (lwp_info*)>::bind<int, lwp_info*>(int (*)(lwp_info*))::{lambda(gdb::fv_detail::erased_callable, lwp_info*)#1}::_FUN(gdb::fv_detail::erased_callable, lwp_info*) () at ../../src/gdb/../gdbsupport/function-view.h:278
>  #5  0x0000555555a91f84 in gdb::function_view<int (lwp_info*)>::operator()(lwp_info*) const (this=0x7fffffffd210, args#0=0x5555579b8230) at ../../src/gdb/../gdbsupport/function-view.h:247
>  #6  0x0000555555a87072 in iterate_over_lwps(ptid_t, gdb::function_view<int (lwp_info*)>) (filter=..., callback=...) at ../../src/gdb/linux-nat.c:864
>  #7  0x0000555555a8e732 in linux_nat_target::kill (this=0x55555653af40 <the_amd64_linux_nat_target>) at ../../src/gdb/linux-nat.c:3590
>  #8  0x0000555555cfdc11 in target_kill () at ../../src/gdb/target.c:911
>  ...

As I mentioned in my other message, this backtrace includes
kill_unfollowed_child_callback, which doesn't exist yet!  I think that's
OK though, the text before the backtrace does make it clear that you saw
this problem only after applying a later patch.

>
> The root of the problem is that when a non-leader LWP execs, it just
> changes its tid to the tgid, replacing the pre-exec leader thread,
> becoming the new leader.  There's no thread exit event for the execing
> thread.  It's as if the old pre-exec LWP vanishes without trace.  The
> ptrace man page says:
>
> "PTRACE_O_TRACEEXEC (since Linux 2.5.46)
> 	Stop the tracee at the next execve(2).  A waitpid(2) by the
> 	tracer will return a status value such that
>
> 	  status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
>
> 	If the execing thread is not a thread group leader, the thread
> 	ID is reset to thread group leader's ID before this stop.
> 	Since Linux 3.0, the former thread ID can be retrieved with
> 	PTRACE_GETEVENTMSG."
>
> When the core of GDB processes an exec events, it deletes all the
> threads of the inferior.  But, that is too late -- deleting the thread
> does not delete the corresponding LWP, so we end leaving the pre-exec
> non-leader LWP stale in the LWP list.  That's what leads to the crash
> above -- linux_nat_target::kill iterates over all LWPs, and after the
> patch in question, that code will look for the corresponding
> thread_info for each LWP.  For the pre-exec non-leader LWP still
> listed, won't find one.
>
> This patch fixes it, by deleting the pre-exec non-leader LWP (and
> thread) from the LWP/thread lists as soon as we get an exec event out
> of ptrace.
>
> GDBserver does not need an equivalent fix, because it is already doing
> this, as side effect of mourning the pre-exec process, in
> gdbserver/linux-low.cc:
>
>   else if (event == PTRACE_EVENT_EXEC && cs.report_exec_events)
>     {
> ...
>       /* Delete the execing process and all its threads.  */
>       mourn (proc);
>       switch_to_thread (nullptr);
>
>
> The crash with gdb.threads/step-over-exec.exp is not observable on
> newer systems, which postdate the glibc change to move "libpthread.so"
> internals to "libc.so.6", because right after the exec, GDB traps a
> load event for "libc.so.6", which leads to GDB trying to open
> libthread_db for the post-exec inferior, and, on such systems that
> succeeds.  When we load libthread_db, we call
> linux_stop_and_wait_all_lwps, which, as the name suggests, stops all
> lwps, and then waits to see their stops.  While doing this, GDB
> detects that the pre-exec stale LWP is gone, and deletes it.
>
> If we use "catch exec" to stop right at the exec before the
> "libc.so.6" load event ever happens, and issue "kill" right there,
> then GDB crashes on newer systems as well.  So instead of tweaking
> gdb.threads/step-over-exec.exp to cover the fix, add a new
> gdb.threads/threads-after-exec.exp testcase that uses "catch exec".

Maybe it's worth mentioning that because the crash itself only happens
once a later patch is applied we use 'maint info linux-lwps' to reveal
the issue for now?

>
> Also tweak a comment in infrun.c:follow_exec referring to how
> linux-nat.c used to behave, as it would become stale otherwise.
>
> Change-Id: I21ec18072c7750f3a972160ae6b9e46590376643
> ---
>  gdb/infrun.c                                  |  8 +--
>  gdb/linux-nat.c                               | 15 ++++
>  .../gdb.threads/threads-after-exec.exp        | 70 +++++++++++++++++++

Oops, this diff is missing the two source files for this test (.c and
-execd.c).  I was able to figure something out though so I could test
the rest of this patch :)

>  3 files changed, 88 insertions(+), 5 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.threads/threads-after-exec.exp
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index abe49ae0f2f..93edc224622 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -1224,13 +1224,11 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
>       some other thread does the exec, and even if the main thread was
>       stopped or already gone.  We may still have non-leader threads of
>       the process on our list.  E.g., on targets that don't have thread
> -     exit events (like remote); or on native Linux in non-stop mode if
> -     there were only two threads in the inferior and the non-leader
> -     one is the one that execs (and nothing forces an update of the
> -     thread list up to here).  When debugging remotely, it's best to
> +     exit events (like remote) and nothing forces an update of the
> +     thread list up to here.  When debugging remotely, it's best to
>       avoid extra traffic, when possible, so avoid syncing the thread
>       list with the target, and instead go ahead and delete all threads
> -     of the process but one that reported the event.  Note this must
> +     of the process but the one that reported the event.  Note this must
>       be done before calling update_breakpoints_after_exec, as
>       otherwise clearing the threads' resources would reference stale
>       thread breakpoints -- it may have been one of these threads that
> diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
> index 68816ddc999..90ac94440b8 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -2001,6 +2001,21 @@ linux_handle_extended_wait (struct lwp_info *lp, int status)
>  	 thread execs, it changes its tid to the tgid, and the old
>  	 tgid thread might have not been resumed.  */
>        lp->resumed = 1;
> +
> +      /* All other LWPs are gone now.  We'll have received a thread
> +	 exit notification for all threads other the execing one.
> +	 That one, if it wasn't the leader, just silently changes its
> +	 tid to the tgid, and the previous leader vanishes.  Since
> +	 Linux 3.0, the former thread ID can be retrieved with
> +	 PTRACE_GETEVENTMSG, but since we support older kernels, don't
> +	 bother with it, and just walk the LWP list.  Even with
> +	 PTRACE_GETEVENTMSG, we'd still need to lookup the
> +	 corresponding LWP object, and it would be an extra ptrace
> +	 syscall, so this way may even be more efficient.  */
> +      for (lwp_info *other_lp : all_lwps_safe ())
> +	if (other_lp != lp && other_lp->ptid.pid () == lp->ptid.pid ())
> +	  exit_lwp (other_lp);
> +
>        return 0;
>      }
>  
> diff --git a/gdb/testsuite/gdb.threads/threads-after-exec.exp b/gdb/testsuite/gdb.threads/threads-after-exec.exp
> new file mode 100644
> index 00000000000..824dda349a6
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threads-after-exec.exp
> @@ -0,0 +1,70 @@
> +# Copyright 2023 Free Software Foundation, Inc.
> +
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# Test that after an exec of a non-leader thread, we don't leave the
> +# non-leader thread listed in internal thread lists, causing problems.
> +
> +standard_testfile .c -execd.c
> +
> +proc do_test { } {
> +    global srcdir subdir srcfile srcfile2 binfile testfile
> +    global decimal
> +
> +    # Compile main binary (the one that does the exec).
> +    if {[gdb_compile_pthreads $srcdir/$subdir/$srcfile $binfile \
> +	     executable {debug}] != "" } {
> +	return -1
> +    }

You can do:

    if {[build_executable "failed to build main executable" \
             $binfile $srcfile {debug pthread}] == -1} {
	return -1
    }

> +
> +    # Compile the second binary (the one that gets exec'd).
> +    if {[gdb_compile $srcdir/$subdir/$srcfile2 $binfile-execd \
> +	     executable {debug}] != "" } {
> +	return -1
> +    }

And:

    if {[build_executable "failed to build execd executable" \
             $binfile-execd $srcfile2 {debug}] == -1} {
	return -1
    }

I thought we were moving away from calling the gdb_compile* functions
directly.

Assuming the missing source files are added, this all looks great.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

> +
> +    clean_restart $binfile
> +
> +    if ![runto_main] {
> +	return
> +    }
> +
> +    gdb_test "catch exec" "Catchpoint $decimal \\(exec\\)"
> +
> +    gdb_test "continue" "Catchpoint $decimal .*" "continue until exec"
> +
> +    # Confirm we only have one thread in the thread list.
> +    gdb_test "info threads" "\\* 1\[ \t\]+\[^\r\n\]+.*"
> +
> +    if {[istarget *-*-linux*] && [gdb_is_target_native]} {
> +	# Confirm there's only one LWP in the list as well, and that
> +	# it is bound to thread 1.1.
> +	set inf_pid [get_inferior_pid]
> +	gdb_test_multiple "maint info linux-lwps" "" {
> +	    -wrap -re "Thread ID *\r\n$inf_pid\.$inf_pid\.0\[ \t\]+1\.1 *" {
> +		pass $gdb_test_name
> +	    }
> +	}
> +    }
> +
> +    # Test that GDB is able to kill the inferior.  This used to crash
> +    # on native Linux as GDB did not dispose of the pre-exec LWP for
> +    # the non-leader (and that LWP did not have a matching thread in
> +    # the core thread list).
> +    gdb_test "with confirm off -- kill" \
> +	"\\\[Inferior 1 (.*) killed\\\]" \
> +	"kill inferior"
> +}
> +
> +do_test
> -- 
> 2.36.0


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

* Re: [PATCH 06/31] Avoid duplicate QThreadEvents packets
  2022-12-12 20:30 ` [PATCH 06/31] Avoid duplicate QThreadEvents packets Pedro Alves
@ 2023-05-26 15:53   ` Andrew Burgess
  0 siblings, 0 replies; 100+ messages in thread
From: Andrew Burgess @ 2023-05-26 15:53 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> Similarly to QProgramSignals and QPassSignals, avoid sending duplicate
> QThreadEvents packets.

LGTM.

Approved-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

>
> Change-Id: Iaf5babb0b64e1527ba4db31aac8674d82b17e8b4
> ---
>  gdb/remote.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
>
> diff --git a/gdb/remote.c b/gdb/remote.c
> index a7430eb79dd..41348a65dc4 100644
> --- a/gdb/remote.c
> +++ b/gdb/remote.c
> @@ -314,6 +314,10 @@ class remote_state
>       the target know about program signals list changes.  */
>    char *last_program_signals_packet = nullptr;
>  
> +  /* Similarly, the last QThreadEvents state we sent to the
> +     target.  */
> +  bool last_thread_events = false;
> +
>    gdb_signal last_sent_signal = GDB_SIGNAL_0;
>  
>    bool last_sent_step = false;
> @@ -14507,6 +14511,9 @@ remote_target::thread_events (int enable)
>    if (packet_support (PACKET_QThreadEvents) == PACKET_DISABLE)
>      return;
>  
> +  if (rs->last_thread_events == enable)
> +    return;
> +
>    xsnprintf (rs->buf.data (), size, "QThreadEvents:%x", enable ? 1 : 0);
>    putpkt (rs->buf);
>    getpkt (&rs->buf, 0);
> @@ -14517,6 +14524,7 @@ remote_target::thread_events (int enable)
>      case PACKET_OK:
>        if (strcmp (rs->buf.data (), "OK") != 0)
>  	error (_("Remote refused setting thread events: %s"), rs->buf.data ());
> +      rs->last_thread_events = enable;
>        break;
>      case PACKET_ERROR:
>        warning (_("Remote failure reply: %s"), rs->buf.data ());
> -- 
> 2.36.0


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

* Re: [PATCH 28/31] Document remote clone events, and QThreadOptions packet
  2022-12-12 20:30 ` [PATCH 28/31] Document remote clone events, and QThreadOptions packet Pedro Alves
@ 2023-06-05 15:53   ` Andrew Burgess
  2023-11-13 14:13     ` Pedro Alves
  2023-06-12 12:06   ` Andrew Burgess
  1 sibling, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-06-05 15:53 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches; +Cc: Eli Zaretskii

Pedro Alves <pedro@palves.net> writes:

> 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.
>
> Approved-By: Eli Zaretskii <eliz@gnu.org>
> Change-Id: Ic1c8de1fefba95729bbd242969284265de42427e
> ---
>  gdb/NEWS            |  20 +++++++
>  gdb/doc/gdb.texinfo | 127 ++++++++++++++++++++++++++++++++++++++++++--
>  2 files changed, 144 insertions(+), 3 deletions(-)
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 0aa153b83da..b1d3dd7e7d9 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -160,6 +160,10 @@ set style tui-current-position [on|off]
>    Whether to style the source and assembly code highlighted by the
>    TUI's current position indicator.  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
>  
>  document user-defined
> @@ -285,6 +289,22 @@ GNU/Linux/CSKY (gdbserver) csky*-*linux*
>  
>  GDB now supports floating-point on LoongArch GNU/Linux.
>  
> +* New remote packets
> +
> +New stop reason: clone
> +  Indicates that a clone system call was executed.
> +
> +QThreadOptions
> +  Enable/disable optional event reporting, on a per-thread basis.
> +  Currently supported options are GDB_THREAD_OPTION_CLONE, to enable
> +  clone event reporting, and GDB_THREAD_OPTION_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 5e75f32e0cd..ef62fac366f 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -24079,6 +24079,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.
> @@ -42110,6 +42114,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, as specified in @ref{thread-id
> +syntax}.  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
> @@ -42148,9 +42163,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
> @@ -42875,6 +42891,11 @@ 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/disables 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
> @@ -42891,6 +42912,94 @@ 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 last @var{options} in the list 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.

I think it is implied, but would be must better explicitly stated, that
it is valid to send 0 in order to clear all options.

Thanks,
Andrew



> +
> +@table @code
> +@item GDB_THREAD_OPTION_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_THREAD_OPTION_EXIT (0x2)
> +Report thread exit events (@pxref{thread exit event}).
> +@end table
> +
> +@noindent
> +
> +For example, @value{GDBN} enables the @code{GDB_THREAD_OPTION_EXIT}
> +and @code{GDB_THREAD_OPTION_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_THREAD_OPTION_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_THREAD_OPTION_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
> +
> +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
> @@ -43336,6 +43445,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{-}
> @@ -43557,6 +43671,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] 100+ messages in thread

* Re: [PATCH 08/31] Thread options & clone events (core + remote)
  2023-03-10 19:16     ` Pedro Alves
@ 2023-06-06 13:29       ` Andrew Burgess
  2023-11-13 14:07         ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-06-06 13:29 UTC (permalink / raw)
  To: Pedro Alves, Lancelot SIX; +Cc: gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-01-31 12:25 p.m., Lancelot SIX wrote:
>> Hi,
>> 
>>> diff --git a/gdb/remote.c b/gdb/remote.c
>>> index 41348a65dc4..9de8ed8a068 100644
>>> --- a/gdb/remote.c
>>> +++ b/gdb/remote.c
>>> @@ -14534,6 +14601,77 @@ 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);
>>> +}
>>> +
>>> +/* For coalescing reasons, actually sending the options to the target
>>> +   happens at resume time, via this function.  See target_resume for
>>> +   all-stop, and target_commit_resumed for non-stop.  */
>>> +
>>> +void
>>> +remote_target::commit_requested_thread_options ()
>>> +{
>>> +  struct remote_state *rs = get_remote_state ();
>>> +
>>> +  if (packet_support (PACKET_QThreadOptions) != PACKET_ENABLE)
>>> +    return;
>>> +
>>> +  char *p = rs->buf.data ();
>>> +  char *endp = p + get_remote_packet_size ();
>>> +
>>> +  /* Clear options for all threads by default.  Note that unlike
>>> +     vCont, the rightmost options that match a thread apply, so we
>>> +     don't have to worry about whether we can use wildcard ptids.  */
>>> +  strcpy (p, "QThreadOptions;0");
>>> +  p += strlen (p);
>>> +
>>> +  /* Now set non-zero options for threads that need them.  We don't
>>> +     bother with the case of all threads of a process wanting the same
>>> +     non-zero options as that's not an expected scenario.  */
>>> +  for (thread_info *tp : all_non_exited_threads (this))
>>> +    {
>>> +      gdb_thread_options options = tp->thread_options ();
>>> +
>>> +      if (options == 0)
>>> +	continue;
>>> +
>>> +      *p++ = ';';
>>> +      p += xsnprintf (p, endp - p, "%s", phex_nz (options, sizeof (options)));
>> 
>> I am not super familiar with how big the buffer is guaranteed to be.
>> Can we imagine a situation where the number of thread and options to
>> send exceed the packet size capacity?  If so, this seems dangerous.  'p'
>> would be incremented by the size which would have been necessary to do
>> the print, so it means it could now point past the end of the buffer.
>
> Note that xsnprintf has an assertion that ensures that the string fits:
>
> int
> xsnprintf (char *str, size_t size, const char *format, ...)
> {
>   va_list args;
>   int ret;
>
>   va_start (args, format);
>   ret = vsnprintf (str, size, format, args);
>   gdb_assert (ret < size);                           <<<< here
>   va_end (args);
>
>
>> Even the `*p++'= ';'` above and similar `*p++ =` below are subject to
>> overflow if the number of options to encode grow too high.
>> 
>> See man vsnprintf(3) which is used by xsnprintf:
>> 
>>     The functions snprintf() and vsnprintf() do not write more than size
>>     bytes[...].  If the output  was  truncated due to this limit, then
>>     the return value is the number of characters [...] which would have
>>     been written to the final string if enough space had been
>>     available.
>> 
>> As I do not feel that we can have a guaranty regarding the maximum
>> number of non exited threads with non-0 options (I might be wrong, but
>> the set of options can be extended so this can show in the future),
>> I would check the returned value of xsnprintf before adding it to p (the
>> same might apply to remote_target::write_ptid, and other increments to p).
>> 
>> Did I miss some precondition which guarantee the buffer to be big enough?
>
> Nope.  You've missed my laziness.  :-)
>
> Here's a version of the patch that sends QThreadOptions packets incrementally
> if needed.  This is the same thing we do for vCont actions (in vcont_builder::push_action).
>
> I've tested the flush/restart path with a local hack to force the path all the time:
>
>         size_t osize = obuf_p - obuf;
>  -      if (osize > endp - p)
>  +      if (1 || osize > endp - p)
>            {
>
> I force-pushed the whole series to users/palves/step-over-thread-exit-v3.1,
> with this updated patch.
>
> Let me know what you think.
>
> Pedro Alves
>
> From 10cf06f133fb69b093dc74a515db34410be8af40 Mon Sep 17 00:00:00 2001
> From: Pedro Alves <pedro@palves.net>
> Date: Tue, 23 Nov 2021 20:35:12 +0000
> Subject: [PATCH] Thread options & clone events (core + remote)
>
> 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_THREAD_OPTION_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_THREAD_OPTION_CLONE when stepping
> over a breakpoint.
>
> Unlike PTRACE_SETOPTIONS, fork/vfork/clone children do NOT inherit
> their parent's thread options.  This is so that GDB can send e.g.,
> "QThreadOptions;0;1:TID" without worrying about threads it doesn't
> know about yet.
>
> Documentation for this new remote protocol feature is included in a
> documentation patch later in the series.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
> Change-Id: Ie41e5093b2573f14cf6ac41b0b5804eba75be37e
> ---
>  gdb/gdbthread.h        |  16 ++++
>  gdb/infrun.c           |  63 +++++++++++++-
>  gdb/remote.c           | 182 ++++++++++++++++++++++++++++++++++++++++-
>  gdb/target-debug.h     |   2 +
>  gdb/target-delegates.c |  28 +++++++
>  gdb/target.c           |   9 ++
>  gdb/target.h           |   8 ++
>  gdb/target/target.c    |  11 +++
>  gdb/target/target.h    |  16 ++++
>  gdb/thread.c           |  15 ++++
>  gdbserver/gdbthread.h  |   3 +
>  gdbserver/server.cc    | 130 +++++++++++++++++++++++++++++
>  gdbserver/target.cc    |   6 ++
>  gdbserver/target.h     |   6 ++
>  14 files changed, 493 insertions(+), 2 deletions(-)
>
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index c0f27a8a66e..79dedb23d4d 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,17 @@ 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);
> +
> +  /* 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 +589,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.  */

I think: s/target_set_thread_options/set_thread_options/ here?

Otherwise LGTM.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

> +  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 d1e6233591c..e5f8dc8d8ab 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -1584,7 +1584,6 @@ step_over_info_valid_p (void)
>  /* Return true if THREAD is doing a displaced step.  */
>  
>  static bool
> -ATTRIBUTE_UNUSED
>  displaced_step_in_progress_thread (thread_info *thread)
>  {
>    gdb_assert (thread != nullptr);
> @@ -1886,6 +1885,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.  */
> +      event_thread->set_thread_options (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
> @@ -1930,6 +1951,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--;
>  
> @@ -2423,6 +2446,42 @@ 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_THREAD_OPTION_CLONE;
> +      if (target_supports_set_thread_options (options))
> +	tp->set_thread_options (options);
> +      else
> +	target_thread_events (true);
> +    }
> +
> +  /* If we're resuming more than one thread simultaneously, then any
> +     thread other than the leader is being set to run free.  Clear any
> +     previous thread option for those threads.  */
> +  if (resume_ptid != inferior_ptid && target_supports_set_thread_options (0))
> +    {
> +      process_stratum_target *resume_target = tp->inf->process_target ();
> +      for (thread_info *thr_iter : all_non_exited_threads (resume_target,
> +							   resume_ptid))
> +	if (thr_iter != tp)
> +	  thr_iter->set_thread_options (0);
> +    }
> +
>    infrun_debug_printf ("resume_ptid=%s, step=%d, sig=%s",
>  		       resume_ptid.to_string ().c_str (),
>  		       step, gdb_signal_to_symbol_string (sig));
> @@ -6144,6 +6203,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/remote.c b/gdb/remote.c
> index ed9c2a0946a..62f25b03928 100644
> --- a/gdb/remote.c
> +++ b/gdb/remote.c
> @@ -248,6 +248,9 @@ enum {
>    /* Support for the QThreadEvents packet.  */
>    PACKET_QThreadEvents,
>  
> +  /* Support for the QThreadOptions packet.  */
> +  PACKET_QThreadOptions,
> +
>    /* Support for multi-process extensions.  */
>    PACKET_multiprocess_feature,
>  
> @@ -560,6 +563,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
> @@ -720,6 +727,8 @@ class remote_target : public process_stratum_target
>    void detach (inferior *, int) override;
>    void disconnect (const char *, int) override;
>  
> +  void commit_requested_thread_options ();
> +
>    void commit_resumed () override;
>    void resume (ptid_t, int, enum gdb_signal) override;
>    ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override;
> @@ -846,6 +855,8 @@ class remote_target : public process_stratum_target
>  
>    void thread_events (int) override;
>  
> +  bool supports_set_thread_options (gdb_thread_options) override;
> +
>    int can_do_single_step () override;
>  
>    void terminal_inferior () override;
> @@ -1144,6 +1155,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 ();
>  
> @@ -5471,7 +5485,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 ();
>  
> @@ -5508,6 +5523,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 ();
> +
> +  m_features.m_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,
> @@ -5610,6 +5668,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 },
> @@ -5712,6 +5772,10 @@ remote_target::remote_query_supported ()
>  	  != AUTO_BOOLEAN_FALSE)
>  	remote_query_supported_append (&q, "QThreadEvents+");
>  
> +      if (m_features.packet_set_cmd_state (PACKET_QThreadOptions)
> +	  != AUTO_BOOLEAN_FALSE)
> +	remote_query_supported_append (&q, "QThreadOptions+");
> +
>        if (m_features.packet_set_cmd_state (PACKET_no_resumed)
>  	  != AUTO_BOOLEAN_FALSE)
>  	remote_query_supported_append (&q, "no-resumed+");
> @@ -6784,6 +6848,8 @@ remote_target::resume (ptid_t scope_ptid, int step, enum gdb_signal siggnal)
>        return;
>      }
>  
> +  commit_requested_thread_options ();
> +
>    /* In all-stop, we can't mark REMOTE_ASYNC_GET_PENDING_EVENTS_TOKEN
>       (explained in remote-notif.c:handle_notification) so
>       remote_notif_process is not called.  We need find a place where
> @@ -6946,6 +7012,8 @@ remote_target::commit_resumed ()
>    if (!target_is_non_stop_p () || ::execution_direction == EXEC_REVERSE)
>      return;
>  
> +  commit_requested_thread_options ();
> +
>    /* Try to send wildcard actions ("vCont;c" or "vCont;c:pPID.-1")
>       instead of resuming all threads of each process individually.
>       However, if any thread of a process must remain halted, we can't
> @@ -14706,6 +14774,115 @@ 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 (m_features.packet_support (PACKET_QThreadOptions) == PACKET_ENABLE
> +	  && (rs->supported_thread_options & options) == options);
> +}
> +
> +/* For coalescing reasons, actually sending the options to the target
> +   happens at resume time, via this function.  See target_resume for
> +   all-stop, and target_commit_resumed for non-stop.  */
> +
> +void
> +remote_target::commit_requested_thread_options ()
> +{
> +  struct remote_state *rs = get_remote_state ();
> +
> +  if (m_features.packet_support (PACKET_QThreadOptions) != PACKET_ENABLE)
> +    return;
> +
> +  char *p = rs->buf.data ();
> +  char *endp = p + get_remote_packet_size ();
> +
> +  /* Clear options for all threads by default.  Note that unlike
> +     vCont, the rightmost options that match a thread apply, so we
> +     don't have to worry about whether we can use wildcard ptids.  */
> +  strcpy (p, "QThreadOptions;0");
> +  p += strlen (p);
> +
> +  /* Send the QThreadOptions packet stored in P.  */
> +  auto flush = [&] ()
> +    {
> +      *p++ = '\0';
> +
> +      putpkt (rs->buf);
> +      getpkt (&rs->buf, 0);
> +
> +      switch (m_features.packet_ok (rs->buf, 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;
> +	}
> +    };
> +
> +  /* Prepare P for another QThreadOptions packet.  */
> +  auto restart = [&] ()
> +    {
> +      p = rs->buf.data ();
> +      strcpy (p, "QThreadOptions");
> +      p += strlen (p);
> +    };
> +
> +  /* Now set non-zero options for threads that need them.  We don't
> +     bother with the case of all threads of a process wanting the same
> +     non-zero options as that's not an expected scenario.  */
> +  for (thread_info *tp : all_non_exited_threads (this))
> +    {
> +      gdb_thread_options options = tp->thread_options ();
> +
> +      if (options == 0)
> +	continue;
> +
> +      /* It might be possible to we have more threads with options
> +	 than can fit a single QThreadOptions packet.  So build each
> +	 options/thread pair in this separate buffer to make sure it
> +	 fits.  */
> +      constexpr size_t max_options_size = 100;
> +      char obuf[max_options_size];
> +      char *obuf_p = obuf;
> +      char *obuf_endp = obuf + max_options_size;
> +
> +      *obuf_p++ = ';';
> +      obuf_p += xsnprintf (obuf_p, obuf_endp - obuf_p, "%s",
> +			   phex_nz (options, sizeof (options)));
> +      if (tp->ptid != magic_null_ptid)
> +	{
> +	  *obuf_p++ = ':';
> +	  obuf_p = write_ptid (obuf_p, obuf_endp, tp->ptid);
> +	}
> +
> +      size_t osize = obuf_p - obuf;
> +      if (osize > endp - p)
> +	{
> +	  /* This new options/thread pair doesn't fit the packet
> +	     buffer.  Send what we have already.  */
> +	  flush ();
> +	  restart ();
> +
> +	  /* Should now fit.  */
> +	  gdb_assert (osize <= endp - p);
> +	}
> +
> +      memcpy (p, obuf, osize);
> +      p += osize;
> +    }
> +
> +  flush ();
> +}
> +
>  static void
>  show_remote_cmd (const char *args, int from_tty)
>  {
> @@ -15446,6 +15623,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
>    add_packet_config_cmd (PACKET_QThreadEvents, "QThreadEvents", "thread-events",
>  			 0);
>  
> +  add_packet_config_cmd (PACKET_QThreadOptions, "QThreadOptions",
> +			 "thread-options", 0);
> +
>    add_packet_config_cmd (PACKET_no_resumed, "N stop reply",
>  			 "no-resumed-stop-reply", 0);
>  
> diff --git a/gdb/target-debug.h b/gdb/target-debug.h
> index acb01d47e7c..72fb31f4b59 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 (to_string (X).c_str ())
>  
>  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 7a4ef05b4e1..63338d82834 100644
> --- a/gdb/target-delegates.c
> +++ b/gdb/target-delegates.c
> @@ -106,6 +106,7 @@ 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;
>    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 +282,7 @@ 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;
>    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 +2274,32 @@ 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;
> +}
> +
>  bool
>  target_ops::supports_non_stop ()
>  {
> diff --git a/gdb/target.c b/gdb/target.c
> index 9835222e5da..d1d3b6913fc 100644
> --- a/gdb/target.c
> +++ b/gdb/target.c
> @@ -4358,6 +4358,15 @@ 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);
> +}
> +
>  /* 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 58e24a5c28e..1657fe2fba7 100644
> --- a/gdb/target.h
> +++ b/gdb/target.h
> @@ -736,6 +736,10 @@ 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);
>      /* This method must be implemented in some situations.  See the
>         comment on 'can_run'.  */
>      virtual bool supports_non_stop ()
> @@ -1895,6 +1899,10 @@ extern void target_async (bool 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);
> +
>  /* 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.c b/gdb/target/target.c
> index 8089918f1d0..3af7d73df5a 100644
> --- a/gdb/target/target.c
> +++ b/gdb/target/target.c
> @@ -188,3 +188,14 @@ target_read_string (CORE_ADDR memaddr, int len, int *bytes_read)
>  
>    return gdb::unique_xmalloc_ptr<char> ((char *) buffer.release ());
>  }
> +
> +/* See target/target.h.  */
> +
> +std::string
> +to_string (gdb_thread_options options)
> +{
> +  static constexpr gdb_thread_options::string_mapping mapping[] = {
> +    MAP_ENUM_FLAG (GDB_THREAD_OPTION_CLONE),
> +  };
> +  return options.to_string (mapping);
> +}
> diff --git a/gdb/target/target.h b/gdb/target/target.h
> index d1a18ee2212..2691f92e4ef 100644
> --- a/gdb/target/target.h
> +++ b/gdb/target/target.h
> @@ -22,9 +22,25 @@
>  
>  #include "target/waitstatus.h"
>  #include "target/wait.h"
> +#include "gdbsupport/enum-flags.h"
>  
>  /* This header is a stopgap until more code is shared.  */
>  
> +/* Available thread options.  Keep this in sync with to_string, in
> +   target.c.  */
> +
> +enum gdb_thread_option : unsigned
> +{
> +  /* Tell the target to report TARGET_WAITKIND_THREAD_CLONED events
> +     for the thread.  */
> +  GDB_THREAD_OPTION_CLONE = 1 << 0,
> +};
> +
> +DEF_ENUM_FLAGS_TYPE (enum gdb_thread_option, gdb_thread_options);
> +
> +/* Convert gdb_thread_option to a string.  */
> +extern std::string to_string (gdb_thread_options 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/gdb/thread.c b/gdb/thread.c
> index 5b472150a62..8958ce1000b 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -399,6 +399,21 @@ thread_info::clear_pending_waitstatus ()
>  
>  /* See gdbthread.h.  */
>  
> +void
> +thread_info::set_thread_options (gdb_thread_options thread_options)
> +{
> +  if (m_thread_options == thread_options)
> +    return;
> +
> +  m_thread_options = thread_options;
> +
> +  infrun_debug_printf ("[options for %s are now %s]",
> +		       this->ptid.to_string ().c_str (),
> +		       to_string (thread_options).c_str ());
> +}
> +
> +/* See gdbthread.h.  */
> +
>  int
>  thread_is_in_step_over_chain (struct thread_info *tp)
>  {
> diff --git a/gdbserver/gdbthread.h b/gdbserver/gdbthread.h
> index 493e1dbf6cb..a4dff0fe1a2 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 29f2d2a386c..7f7efd1fcc0 100644
> --- a/gdbserver/server.cc
> +++ b/gdbserver/server.cc
> @@ -36,6 +36,7 @@
>  #include "dll.h"
>  #include "hostio.h"
>  #include <vector>
> +#include <unordered_map>
>  #include "gdbsupport/common-inferior.h"
>  #include "gdbsupport/job-control.h"
>  #include "gdbsupport/environ.h"
> @@ -616,6 +617,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
> @@ -897,6 +909,114 @@ handle_general_set (char *own_buf)
>        return;
>      }
>  
> +  if (startswith (own_buf, "QThreadOptions;"))
> +    {
> +      const char *p = own_buf + strlen ("QThreadOptions");
> +
> +      gdb_thread_options supported_options = target_supported_thread_options ();
> +      if (supported_options == 0)
> +	{
> +	  /* Something went wrong -- we don't support any option, but
> +	     GDB sent the packet anyway.  */
> +	  write_enn (own_buf);
> +	  return;
> +	}
> +
> +      /* We could store the options directly in thread->thread_options
> +	 without this map, but that would mean that a QThreadOptions
> +	 packet with a wildcard like "QThreadOptions;0;3:TID" would
> +	 result in the debug logs showing:
> +
> +	   [options for TID are now 0x0]
> +	   [options for TID are now 0x3]
> +
> +	 It's nicer if we only print the final options for each TID,
> +	 and if we only print about it if the options changed compared
> +	 to the options that were previously set on the thread.  */
> +      std::unordered_map<thread_info *, gdb_thread_options> set_options;
> +
> +      while (*p != '\0')
> +	{
> +	  if (p[0] != ';')
> +	    {
> +	      write_enn (own_buf);
> +	      return;
> +	    }
> +	  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 thread options requested: %s\n",
> +				 to_string (options).c_str ());
> +	      strcpy (own_buf, err.c_str ());
> +	      return;
> +	    }
> +
> +	  ptid_t ptid;
> +
> +	  if (p[0] == ';' || 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;
> +	    }
> +
> +	  /* Convert PID.-1 => PID.0 for ptid.matches.  */
> +	  if (ptid.lwp () == -1)
> +	    ptid = ptid_t (ptid.pid ());
> +
> +	  for_each_thread ([&] (thread_info *thread)
> +	    {
> +	      if (ptid_of (thread).matches (ptid))
> +		set_options[thread] = options;
> +	    });
> +	}
> +
> +      for (const auto &iter : set_options)
> +	{
> +	  thread_info *thread = iter.first;
> +	  gdb_thread_options options = iter.second;
> +
> +	  if (thread->thread_options != options)
> +	    {
> +	      threads_debug_printf ("[options for %s are now %s]\n",
> +				    target_pid_to_str (ptid_of (thread)).c_str (),
> +				    to_string (options).c_str ());
> +
> +	      thread->thread_options = options;
> +	    }
> +	}
> +
> +      write_ok (own_buf);
> +      return;
> +    }
> +
>    if (startswith (own_buf, "QStartupWithShell:"))
>      {
>        const char *value = own_buf + strlen ("QStartupWithShell:");
> @@ -2348,6 +2468,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
> @@ -2474,6 +2596,14 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
>  
>        strcat (own_buf, ";vContSupported+");
>  
> +      gdb_thread_options supported_options = target_supported_thread_options ();
> +      if (supported_options != 0)
> +	{
> +	  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 f8e592d20c3..1c740bbf583 100644
> --- a/gdbserver/target.cc
> +++ b/gdbserver/target.cc
> @@ -532,6 +532,12 @@ process_stratum_target::supports_vfork_events ()
>    return false;
>  }
>  
> +gdb_thread_options
> +process_stratum_target::supported_thread_options ()
> +{
> +  return 0;
> +}
> +
>  bool
>  process_stratum_target::supports_exec_events ()
>  {
> diff --git a/gdbserver/target.h b/gdbserver/target.h
> index d993e361b76..fe68716c868 100644
> --- a/gdbserver/target.h
> +++ b/gdbserver/target.h
> @@ -276,6 +276,9 @@ class process_stratum_target
>    /* Returns true if vfork events are supported.  */
>    virtual bool supports_vfork_events ();
>  
> +  /* Returns the set of supported thread options.  */
> +  virtual gdb_thread_options supported_thread_options ();
> +
>    /* Returns true if exec events are supported.  */
>    virtual bool supports_exec_events ();
>  
> @@ -531,6 +534,9 @@ int kill_inferior (process_info *proc);
>  #define target_supports_vfork_events() \
>    the_target->supports_vfork_events ()
>  
> +#define target_supported_thread_options(options) \
> +  the_target->supported_thread_options (options)
> +
>  #define target_supports_exec_events() \
>    the_target->supports_exec_events ()
>  
>
> base-commit: 2562954ede66f32bff7d985e752b8052c2ae5775
> prerequisite-patch-id: bbc9918ac5f79de07a29f34ec072794d270f942d
> prerequisite-patch-id: c0bc5b4f99193bb50cb31f551673de1808dcda35
> prerequisite-patch-id: ab0838f2bc02931d7e830abe23833e7a8224442c
> prerequisite-patch-id: 3a896bfe4b7c66a2e3a6aa668c5ae8395e5d8a52
> prerequisite-patch-id: 254a23b7d7cec889924daaf288304494c93fe1aa
> prerequisite-patch-id: b1fe92da846e52cce1e9f13498cf668c5cdd6ee4
> -- 
> 2.36.0


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

* Re: [PATCH 09/31] Thread options & clone events (native Linux)
  2022-12-12 20:30 ` [PATCH 09/31] Thread options & clone events (native Linux) Pedro Alves
@ 2023-06-06 13:43   ` Andrew Burgess
  0 siblings, 0 replies; 100+ messages in thread
From: Andrew Burgess @ 2023-06-06 13:43 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> This commit teaches the native Linux target about the
> GDB_THREAD_OPTION_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 to report support for the option,
> otherwise GDB falls back to use target_thread_events().
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
> Change-Id: If90316e2dcd0c61d0fefa0d463c046011698acf9

LGTM.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew


> ---
>  gdb/linux-nat.c | 7 +++++++
>  gdb/linux-nat.h | 2 ++
>  2 files changed, 9 insertions(+)
>
> diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
> index f3d02b740e8..5fadc82deb0 100644
> --- a/gdb/linux-nat.c
> +++ b/gdb/linux-nat.c
> @@ -4468,6 +4468,13 @@ 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_THREAD_OPTION_CLONE;
> +  return ((options & supported_options) == options);
> +}
> +
>  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 3ed25cc5ba4..258041b8271 100644
> --- a/gdb/linux-nat.h
> +++ b/gdb/linux-nat.h
> @@ -82,6 +82,8 @@ class linux_nat_target : public inf_ptrace_target
>  
>    void thread_events (int) override;
>  
> +  bool supports_set_thread_options (gdb_thread_options options) override;
> +
>    bool can_async_p () override;
>  
>    bool supports_non_stop () override;
> -- 
> 2.36.0


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

* Re: [PATCH 10/31] Thread options & clone events (Linux GDBserver)
  2022-12-12 20:30 ` [PATCH 10/31] Thread options & clone events (Linux GDBserver) Pedro Alves
@ 2023-06-06 14:12   ` Andrew Burgess
  2023-11-13 14:07     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-06-06 14:12 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> This patch teaches the Linux GDBserver backend to report clone events
> to GDB, when GDB has requested them with the GDB_THREAD_OPTION_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.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19675
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=27830
> Change-Id: I3a19bc98801ec31e5c6fdbe1ebe17df855142bb2
> ---
>  gdbserver/linux-low.cc | 235 ++++++++++++++++++++++-------------------
>  gdbserver/linux-low.h  |   2 +
>  2 files changed, 129 insertions(+), 108 deletions(-)
>
> diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
> index 6f96e16d6f0..d755cda0e44 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,65 @@ 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);
>  
> +      /* 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 +592,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_THREAD_OPTION_CLONE) != 0)
> +	event_lwp->waitstatus.set_thread_cloned (child_ptid);
> +
> +      if (event != PTRACE_EVENT_CLONE
> +	  || (event_thr->thread_options & GDB_THREAD_OPTION_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_THREAD_OPTION_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)
>      {
> @@ -3527,7 +3538,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)

Can we not use:

  if (is_new_child_status (event_child->waitstatus.kind ()))

here?

I almost asked here: do we really need to check for THREAD_CLONED here,
given the body of the `if` only changes the `fork_relative` field, which
surely is only related to forks, right?

... but then I dug deeper and spotted that the field also used for clone
events now, so: I wonder if we should rename `fork_relative` to
something better, maybe `fork_clone_relative`, or maybe go generic with
something like `related_lwp`?

It just seemed a little confusing (to me).

But otherwise, this patch looks good.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

>  	{
>  	  event_child->fork_relative->fork_relative = NULL;
>  	  event_child->fork_relative = NULL;
> @@ -4263,15 +4275,14 @@ 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/vfork/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))
> +		  && is_new_child_status (rel->waitstatus.kind ()))
>  		{
>  		  threads_debug_printf
>  		    ("not resuming LWP %ld: has queued stop reply",
> @@ -5894,6 +5905,14 @@ linux_process_target::supports_vfork_events ()
>    return true;
>  }
>  
> +/* Return the set of supported thread options.  */
> +
> +gdb_thread_options
> +linux_process_target::supported_thread_options ()
> +{
> +  return GDB_THREAD_OPTION_CLONE;
> +}
> +
>  /* Check if exec events are supported.  */
>  
>  bool
> diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
> index 1594f063f47..69a34fb96fc 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;
>  
> +  gdb_thread_options supported_thread_options () override;
> +
>    bool supports_exec_events () override;
>  
>    void handle_new_gdb_connection () override;
> -- 
> 2.36.0


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

* Re: [PATCH 11/31] gdbserver: Hide and don't detach pending clone children
  2022-12-12 20:30 ` [PATCH 11/31] gdbserver: Hide and don't detach pending clone children Pedro Alves
@ 2023-06-07 16:10   ` Andrew Burgess
  2023-11-13 14:08     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-06-07 16:10 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> 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 d755cda0e44..a7c310260ca 100644
> --- a/gdbserver/linux-low.cc
> +++ b/gdbserver/linux-low.cc
> @@ -6940,9 +6940,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 69a34fb96fc..c9f9db71e09 100644
> --- a/gdbserver/linux-low.h
> +++ b/gdbserver/linux-low.h
> @@ -315,7 +315,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;
>  
> @@ -734,8 +735,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)
> @@ -743,10 +744,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);
>  
> @@ -756,24 +757,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);
>  
> @@ -782,8 +784,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 5b07c4e4388..07a3319d114 100644
> --- a/gdbserver/server.cc
> +++ b/gdbserver/server.cc
> @@ -1344,8 +1344,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);
> @@ -1765,9 +1766,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 168b843d2ec..4584e9b3a8e 100644
> --- a/gdbserver/target.cc
> +++ b/gdbserver/target.cc
> @@ -814,7 +814,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 8a0d9f42f7d..e2e818b130b 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);

Should the comment not say what happens to KIND?  I think this comment
applies throughout this patch where KIND was added as an argument.

Otherwise, this looks good.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

>  
>    /* 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] 100+ messages in thread

* Re: [PATCH 12/31] Remove gdb/19675 kfails (displaced stepping + clone)
  2022-12-12 20:30 ` [PATCH 12/31] Remove gdb/19675 kfails (displaced stepping + clone) Pedro Alves
@ 2023-06-07 17:08   ` Andrew Burgess
  0 siblings, 0 replies; 100+ messages in thread
From: Andrew Burgess @ 2023-06-07 17:08 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> 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

Awesome!  LGTM.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

> ---
>  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 4192fa10826..e87382b2fa6 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} \
> @@ -299,15 +270,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] 100+ messages in thread

* Re: [PATCH 13/31] Add test for stepping over clone syscall
  2022-12-12 20:30 ` [PATCH 13/31] Add test for stepping over clone syscall Pedro Alves
@ 2023-06-07 17:42   ` Andrew Burgess
  2023-11-13 14:09     ` Pedro Alves
  0 siblings, 1 reply; 100+ messages in thread
From: Andrew Burgess @ 2023-06-07 17:42 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches; +Cc: Pedro Alves

Pedro Alves <pedro@palves.net> writes:

> From: Andrew Burgess <andrew.burgess@embecosm.com>
>
> - New in v3:
>
>   Addressed issue Tom de Vries ran into with no debug info for glibc.
>
> - New in v2:
>
>   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..580cf2d921b
> --- /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.

<sigh> lets see if we can land this in 2023!

> +
> +   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 < 300; ++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 < 300; ++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..d9cbe2b957a
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/stepi-over-clone.exp
> @@ -0,0 +1,392 @@
> +# Copyright 2021-2022 Free Software Foundation, Inc.

Date range again.

> +
> +# 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
> +}

This will need updating to the new 'require' mechanism.

> +
> +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

I know the mistakes here are mine rather than yours, but ...

... the 'set supported 0' appears to be pointless.  And given we are
returning here due to some feature not being supported, I think we
should drop the 'pass $gdb_test_name' and instead do something like
'unsupported "unable to catch clone syscall"' before the return.

Otherwise, LGTM.

Reviewed-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

> +    }
> +    -re ".*$gdb_prompt $" {
> +	pass $gdb_test_name
> +    }
> +}
> +
> +gdb_test "continue" \
> +    "Catchpoint $decimal \\(call to syscall 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 s