Remote thread create/exit events
authorPedro Alves <palves@redhat.com>
Mon, 30 Nov 2015 16:05:21 +0000 (16:05 +0000)
committerPedro Alves <palves@redhat.com>
Mon, 30 Nov 2015 18:40:30 +0000 (18:40 +0000)
When testing with "maint set target-non-stop on", a few
threading-related tests expose an issue that requires new RSP packets.

Say there are 3 threads running, 1-3.  If GDB tries to stop thread 1,
2 and 3, and then waits for their stops, but meanwhile say, thread 2
exits, GDB hangs forever waiting for a stop for thread 2 that won't
ever happen.

This patch fixes the issue by adding support for thread exit events to
the protocol.  However, we don't want these always enabled, as they're
useless most of the time, and would slow down remote debugging.  So I
made it so that GDB can enable/disable them, and then made gdb do that
around the cases that need it, which currently is only
infrun.c:stop_all_threads.

In turn, if we have thread exit events, then the extra "thread x
exited" traffic slows down attach-many-short-lived-threads.exp enough
that gdb has trouble keeping up with new threads that are spawned
while gdb tries to stop existing ones.  To fix that I added support
for the counterpart thread created events too.  Enabling those when we
try to stop threads ensures that new threads never get a chance to
themselves start new threads, killing the race.

gdb/doc/ChangeLog:
2015-11-30  Pedro Alves  <palves@redhat.com>

* gdb.texinfo (Remote Configuration): List "set/show remote
thread-events" command in configuration table.
(Stop Reply Packets): Document "T05 create" stop
reason and 'w' stop reply.
(General Query Packets): Document QThreadEvents packet.  Document
QThreadEvents qSupported feature.

gdb/gdbserver/ChangeLog:
2015-11-30  Pedro Alves  <palves@redhat.com>

* linux-low.c (handle_extended_wait): Assert that the LWP's
waitstatus is TARGET_WAITKIND_IGNORE.  If GDB wants to hear about
thread create events, leave the new child's status pending.
(linux_low_filter_event): If GDB wants to hear about thread exit
events, leave the LWP marked dead and don't delete it.
(linux_wait_for_event_filtered): Don't check for thread exit.
(filter_exit_event): New function.
(linux_wait_1): Use it, when returning an exit event.
(linux_resume_one_lwp_throw): Assert that the LWP's
waitstatus is TARGET_WAITKIND_IGNORE.
* remote-utils.c (prepare_resume_reply): Handle
TARGET_WAITKIND_THREAD_CREATED and TARGET_WAITKIND_THREAD_EXITED.
* server.c (report_thread_events): New global.
(handle_general_set): Handle QThreadEvents.
(handle_query) <qSupported>: Handle and report QThreadEvents+;
(handle_target_event): Handle TARGET_WAITKIND_THREAD_CREATED and
TARGET_WAITKIND_THREAD_EXITED.
* server.h (report_thread_events): Declare.

gdb/ChangeLog:
2015-11-30  Pedro Alves  <palves@redhat.com>

* NEWS (New commands): Mention "set/show remote thread-events"
commands.
(New remote packets): Mention thread created/exited stop reasons
and QThreadEvents packet.
* infrun.c (disable_thread_events): New function.
(stop_all_threads): Disable/enable thread create/exit events.
Handle TARGET_WAITKIND_THREAD_EXITED.
(handle_inferior_event_1): Handle TARGET_WAITKIND_THREAD_CREATED
and TARGET_WAITKIND_THREAD_EXITED.
* remote.c (remove_child_of_pending_fork): Also remove threads of
threads that have TARGET_WAITKIND_THREAD_EXITED events.
(remote_parse_stop_reply): Handle "create" magic register.  Handle
'w' stop reply.
(initialize_remote): Install remote_thread_events as
to_thread_events target hook.
(remote_thread_events): New function.
* target-delegates.c: Regenerate.
* target.c (target_thread_events): New function.
* target.h (struct target_ops) <to_thread_events>: New field.
(target_thread_events): Declare.
* target/waitstatus.c (target_waitstatus_to_string): Handle
TARGET_WAITKIND_THREAD_CREATED and TARGET_WAITKIND_THREAD_EXITED.
* target/waitstatus.h (enum target_waitkind)
<TARGET_WAITKIND_THREAD_CREATED, TARGET_WAITKIND_THREAD_EXITED):
New values.

16 files changed:
gdb/ChangeLog
gdb/NEWS
gdb/doc/ChangeLog
gdb/doc/gdb.texinfo
gdb/gdbserver/ChangeLog
gdb/gdbserver/linux-low.c
gdb/gdbserver/remote-utils.c
gdb/gdbserver/server.c
gdb/gdbserver/server.h
gdb/infrun.c
gdb/remote.c
gdb/target-delegates.c
gdb/target.c
gdb/target.h
gdb/target/waitstatus.c
gdb/target/waitstatus.h

index 0d4ea33a44efa0201e7dd54e38227ed21a8cb1d0..fdbc7d90971a783227f1a8a2bfab0fac95fa4c33 100644 (file)
@@ -1,3 +1,31 @@
+2015-11-30  Pedro Alves  <palves@redhat.com>
+
+       * NEWS (New commands): Mention "set/show remote thread-events"
+       commands.
+       (New remote packets): Mention thread created/exited stop reasons
+       and QThreadEvents packet.
+       * infrun.c (disable_thread_events): New function.
+       (stop_all_threads): Disable/enable thread create/exit events.
+       Handle TARGET_WAITKIND_THREAD_EXITED.
+       (handle_inferior_event_1): Handle TARGET_WAITKIND_THREAD_CREATED
+       and TARGET_WAITKIND_THREAD_EXITED.
+       * remote.c (remove_child_of_pending_fork): Also remove threads of
+       threads that have TARGET_WAITKIND_THREAD_EXITED events.
+       (remote_parse_stop_reply): Handle "create" magic register.  Handle
+       'w' stop reply.
+       (initialize_remote): Install remote_thread_events as
+       to_thread_events target hook.
+       (remote_thread_events): New function.
+       * target-delegates.c: Regenerate.
+       * target.c (target_thread_events): New function.
+       * target.h (struct target_ops) <to_thread_events>: New field.
+       (target_thread_events): Declare.
+       * target/waitstatus.c (target_waitstatus_to_string): Handle
+       TARGET_WAITKIND_THREAD_CREATED and TARGET_WAITKIND_THREAD_EXITED.
+       * target/waitstatus.h (enum target_waitkind)
+       <TARGET_WAITKIND_THREAD_CREATED, TARGET_WAITKIND_THREAD_EXITED):
+       New values.
+
 2015-11-30  Pedro Alves  <palves@redhat.com>
 
        * NEWS (New remote packets): Mention vCtrlC.
index a7c12e79f34c04bc8e0836385939d99db2acabc7..a236161c7b34c19cb4883f342c117836dd1cdb70 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -48,6 +48,10 @@ set remote multiprocess-extensions-packet
 show remote multiprocess-extensions-packet
   Set/show the use of the remote protocol multiprocess extensions.
 
+set remote thread-events
+show remote thread-events
+  Set/show the use of thread create/exit events.
+
 * The "disassemble" command accepts a new modifier: /s.
   It prints mixed source+disassembly like /m with two differences:
   - disassembled instructions are now printed in program order, and
@@ -88,6 +92,20 @@ vCtrlC
   Equivalent to interrupting with the ^C character, but works in
   non-stop mode.
 
+thread created stop reason (T05 create:...)
+  Indicates that the thread was just created and is stopped at entry.
+
+thread exit stop reply (w exitcode;tid)
+  Indicates that the thread has terminated.
+
+QThreadEvents
+  Enables/disables thread create and exit event reporting.  For
+  example, this is used in non-stop mode when GDB stops a set of
+  threads and synchronously waits for the their corresponding stop
+  replies.  Without exit events, if one of the threads exits, GDB
+  would hang forever not knowing that it should no longer expect a
+  stop for that same thread.
+
 * Extended-remote exec events
 
   ** GDB now has support for exec events on extended-remote Linux targets.
index 3e0d6262f242fe828dd3147b3a9d75ebf4baf313..b5eddc9f51e918fda9c80f8f53b539a9d9dc9daa 100644 (file)
@@ -1,3 +1,12 @@
+2015-11-30  Pedro Alves  <palves@redhat.com>
+
+       * gdb.texinfo (Remote Configuration): List "set/show remote
+       thread-events" command in configuration table.
+       (Stop Reply Packets): Document "T05 create" stop
+       reason and 'w' stop reply.
+       (General Query Packets): Document QThreadEvents packet.  Document
+       QThreadEvents qSupported feature.
+
 2015-11-30  Pedro Alves  <palves@redhat.com>
 
        * gdb.texinfo (Bootstrapping): Add "interrupting remote targets"
index 972ace08d994ac1ce1f0c68d03822a3a5924ca8a..d2ec3549520412ca7aff2f0028446ead72fe88a8 100644 (file)
@@ -20235,6 +20235,10 @@ are:
 @tab @code{exec stop reason}
 @tab @code{exec}
 
+@item @code{thread-events}
+@tab @code{QThreadEvents}
+@tab Tracking thread lifetime.
+
 @end multitable
 
 @node Remote Stub
@@ -35546,6 +35550,15 @@ appropriate @samp{qSupported} feature (@pxref{qSupported}).  The
 remote stub must also supply the appropriate @samp{qSupported} feature
 indicating support.
 
+@cindex thread create event, remote reply
+@anchor{thread create event}
+@item create
+The packet indicates that the thread was just created.  The new thread
+is stopped until @value{GDBN} sets it running with a resumption packet
+(@pxref{vCont packet}).  This packet should not be sent by default;
+@value{GDBN} requests it with the @ref{QThreadEvents} packet.  See
+also the @samp{w} (@ref{thread exit event}) remote reply below.
+
 @end table
 
 @item W @var{AA}
@@ -35567,6 +35580,14 @@ terminated process, can be used only when @value{GDBN} has reported
 support for multiprocess protocol extensions; see @ref{multiprocess
 extensions}.  The @var{pid} is formatted as a big-endian hex string.
 
+@anchor{thread exit event}
+@cindex thread exit event, remote reply
+@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.
+
 @item O @var{XX}@dots{}
 @samp{@var{XX}@dots{}} is hex encoding of @sc{ascii} data, to be
 written as the program's console output.  This can happen at any time
@@ -35984,6 +36005,39 @@ command (@pxref{Remote Configuration, set remote program-signals}).
 This packet is not probed by default; the remote stub must request it,
 by supplying an appropriate @samp{qSupported} response (@pxref{qSupported}).
 
+@anchor{QThreadEvents}
+@item QThreadEvents:1
+@itemx QThreadEvents:0
+@cindex thread create/exit events, remote request
+@cindex @samp{QThreadEvents} packet
+
+Enable (@samp{QThreadEvents:1}) or disable (@samp{QThreadEvents:0})
+reporting of thread create and exit events.  @xref{thread create
+event}, for the reply specifications.  For example, this is used in
+non-stop mode when @value{GDBN} stops a set of threads and
+synchronously waits for the their corresponding stop replies.  Without
+exit events, if one of the threads exits, @value{GDBN} would hang
+forever not knowing that it should no longer expect a stop for that
+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.
+
+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{QThreadEvents} is not supported by
+the stub.
+@end table
+
+Use of this packet is controlled by the @code{set remote thread-events}
+command (@pxref{Remote Configuration, set remote thread-events}).
+
 @item qRcmd,@var{command}
 @cindex execute remote command, remote request
 @cindex @samp{qRcmd} packet
@@ -36428,6 +36482,11 @@ These are the currently defined stub features and their properties:
 @tab @samp{-}
 @tab No
 
+@item @samp{QThreadEvents}
+@tab No
+@tab @samp{-}
+@tab No
+
 @end multitable
 
 These are the currently defined stub features, in more detail:
@@ -36640,6 +36699,9 @@ The remote stub reports the @samp{exec} stop reason for exec events.
 The remote stub reports the supported actions in the reply to
 @samp{vCont?} packet.
 
+@item QThreadEvents
+The remote stub understands the @samp{QThreadEvents} packet.
+
 @end table
 
 @item qSymbol::
index 3d00ed431db4665ab32cc04e1f5e3e1cb9be32b6..2d0489968021957ddcf3bc6d2eb6dcd1f7856115 100644 (file)
@@ -1,3 +1,24 @@
+2015-11-30  Pedro Alves  <palves@redhat.com>
+
+       * linux-low.c (handle_extended_wait): Assert that the LWP's
+       waitstatus is TARGET_WAITKIND_IGNORE.  If GDB wants to hear about
+       thread create events, leave the new child's status pending.
+       (linux_low_filter_event): If GDB wants to hear about thread exit
+       events, leave the LWP marked dead and don't delete it.
+       (linux_wait_for_event_filtered): Don't check for thread exit.
+       (filter_exit_event): New function.
+       (linux_wait_1): Use it, when returning an exit event.
+       (linux_resume_one_lwp_throw): Assert that the LWP's
+       waitstatus is TARGET_WAITKIND_IGNORE.
+       * remote-utils.c (prepare_resume_reply): Handle
+       TARGET_WAITKIND_THREAD_CREATED and TARGET_WAITKIND_THREAD_EXITED.
+       * server.c (report_thread_events): New global.
+       (handle_general_set): Handle QThreadEvents.
+       (handle_query) <qSupported>: Handle and report QThreadEvents+;
+       (handle_target_event): Handle TARGET_WAITKIND_THREAD_CREATED and
+       TARGET_WAITKIND_THREAD_EXITED.
+       * server.h (report_thread_events): Declare.
+
 2015-11-30  Pedro Alves  <palves@redhat.com>
 
        * linux-low.c (resume_stopped_resumed_lwps): Don't check whether
index 1f7d8d17f6220cca9bb16f3da6e466510ed6f728..cde59a72925e0458ea8f2a656d7f769f5108fdc5 100644 (file)
@@ -447,6 +447,8 @@ handle_extended_wait (struct lwp_info **orig_event_lwp, int 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);
+
   if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK)
       || (event == PTRACE_EVENT_CLONE))
     {
@@ -577,6 +579,12 @@ handle_extended_wait (struct lwp_info **orig_event_lwp, int wstat)
          new_lwp->status_pending_p = 1;
          new_lwp->status_pending = status;
        }
+      else if (report_thread_events)
+       {
+         new_lwp->waitstatus.kind = TARGET_WAITKIND_THREAD_CREATED;
+         new_lwp->status_pending_p = 1;
+         new_lwp->status_pending = status;
+       }
 
       /* Don't report the event.  */
       return 1;
@@ -2256,25 +2264,22 @@ linux_low_filter_event (int lwpid, int wstat)
     {
       if (debug_threads)
        debug_printf ("LLFE: %d exited.\n", lwpid);
-      if (num_lwps (pid_of (thread)) > 1)
+      /* If there is at least one more 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 (report_thread_events
+         || last_thread_of_process_p (pid_of (thread)))
        {
-
-         /* If there is at least one more LWP, then the exit signal was
-            not the end of the debugged application and should be
-            ignored.  */
-         delete_lwp (child);
-         return NULL;
+         /* 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);
+         return child;
        }
       else
        {
-         /* This was the last lwp in the process.  Since events are
-            serialized to GDB core, and we can't report this one
-            right now, but GDB core and the other target layers will
-            want to be notified about the exit code/signal, leave the
-            status pending for the next time we're able to report
-            it.  */
-         mark_lwp_dead (child, wstat);
-         return child;
+         delete_lwp (child);
+         return NULL;
        }
     }
 
@@ -2617,18 +2622,6 @@ linux_wait_for_event_filtered (ptid_t wait_ptid, ptid_t filter_ptid,
 
   current_thread = event_thread;
 
-  /* Check for thread exit.  */
-  if (! WIFSTOPPED (*wstatp))
-    {
-      gdb_assert (last_thread_of_process_p (pid_of (event_thread)));
-
-      if (debug_threads)
-       debug_printf ("LWP %d is the last lwp of process.  "
-                     "Process %ld exiting.\n",
-                     pid_of (event_thread), lwpid_of (event_thread));
-      return lwpid_of (event_thread);
-    }
-
   return lwpid_of (event_thread);
 }
 
@@ -2913,6 +2906,30 @@ ignore_event (struct target_waitstatus *ourstatus)
   return null_ptid;
 }
 
+/* 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.  */
+
+static ptid_t
+filter_exit_event (struct lwp_info *event_child,
+                  struct target_waitstatus *ourstatus)
+{
+  struct thread_info *thread = get_lwp_thread (event_child);
+  ptid_t ptid = ptid_of (thread);
+
+  if (!last_thread_of_process_p (pid_of (thread)))
+    {
+      if (report_thread_events)
+       ourstatus->kind = TARGET_WAITKIND_THREAD_EXITED;
+      else
+       ourstatus->kind = TARGET_WAITKIND_IGNORE;
+
+      delete_lwp (event_child);
+    }
+  return ptid;
+}
+
 /* Wait for process, returns status.  */
 
 static ptid_t
@@ -3018,6 +3035,9 @@ linux_wait_1 (ptid_t ptid,
            }
        }
 
+      if (ourstatus->kind == TARGET_WAITKIND_EXITED)
+       return filter_exit_event (event_child, ourstatus);
+
       return ptid_of (current_thread);
     }
 
@@ -3507,6 +3527,9 @@ linux_wait_1 (ptid_t ptid,
       debug_exit ();
     }
 
+  if (ourstatus->kind == TARGET_WAITKIND_EXITED)
+    return filter_exit_event (event_child, ourstatus);
+
   return ptid_of (current_thread);
 }
 
@@ -3907,6 +3930,8 @@ linux_resume_one_lwp_throw (struct lwp_info *lwp,
   if (lwp->stopped == 0)
     return;
 
+  gdb_assert (lwp->waitstatus.kind == TARGET_WAITKIND_IGNORE);
+
   fast_tp_collecting = lwp->collecting_fast_tracepoint;
 
   gdb_assert (!stabilizing_threads || fast_tp_collecting);
index e36609176afa336c2d8a206f322052480b0472ad..0d9cde389213066961446385421855df84551ee2 100644 (file)
@@ -1119,6 +1119,7 @@ prepare_resume_reply (char *buf, ptid_t ptid,
     case TARGET_WAITKIND_VFORKED:
     case TARGET_WAITKIND_VFORK_DONE:
     case TARGET_WAITKIND_EXECD:
+    case TARGET_WAITKIND_THREAD_CREATED:
       {
        struct thread_info *saved_thread;
        const char **regp;
@@ -1161,6 +1162,13 @@ prepare_resume_reply (char *buf, ptid_t ptid,
            status->value.execd_pathname = NULL;
            buf += strlen (buf);
          }
+       else if (status->kind == TARGET_WAITKIND_THREAD_CREATED
+                && report_thread_events)
+         {
+           enum gdb_signal signal = GDB_SIGNAL_TRAP;
+
+           sprintf (buf, "T%02xcreate:;", signal);
+         }
        else
          sprintf (buf, "T%02x", status->value.sig);
 
@@ -1276,6 +1284,11 @@ prepare_resume_reply (char *buf, ptid_t ptid,
       else
        sprintf (buf, "X%02x", status->value.sig);
       break;
+    case TARGET_WAITKIND_THREAD_EXITED:
+      sprintf (buf, "w%x;", status->value.integer);
+      buf += strlen (buf);
+      buf = write_ptid (buf, ptid);
+      break;
     default:
       error ("unhandled waitkind");
       break;
index f6245d76f75a8139e6cf421340fa9856add5952f..0762f6aba4450fe7d9f74dd77a3cd19acedab2c1 100644 (file)
@@ -60,6 +60,7 @@ int multi_process;
 int report_fork_events;
 int report_vfork_events;
 int report_exec_events;
+int report_thread_events;
 int non_stop;
 int swbreak_feature;
 int hwbreak_feature;
@@ -723,6 +724,39 @@ handle_general_set (char *own_buf)
   if (handle_btrace_conf_general_set (own_buf))
     return;
 
+  if (startswith (own_buf, "QThreadEvents:"))
+    {
+      char *mode = own_buf + strlen ("QThreadEvents:");
+      enum tribool req = TRIBOOL_UNKNOWN;
+
+      if (strcmp (mode, "0") == 0)
+       req = TRIBOOL_FALSE;
+      else if (strcmp (mode, "1") == 0)
+       req = TRIBOOL_TRUE;
+      else
+       {
+         char *mode_copy = xstrdup (mode);
+
+         /* We don't know what this mode is, so complain to GDB.  */
+         sprintf (own_buf, "E.Unknown thread-events mode requested: %s\n",
+                  mode_copy);
+         xfree (mode_copy);
+         return;
+       }
+
+      report_thread_events = (req == TRIBOOL_TRUE);
+
+      if (remote_debug)
+       {
+         const char *req_str = report_thread_events ? "enabled" : "disabled";
+
+         fprintf (stderr, "[thread events are now %s]\n", req_str);
+       }
+
+      write_ok (own_buf);
+      return;
+    }
+
   /* Otherwise we didn't know what packet it was.  Say we didn't
      understand it.  */
   own_buf[0] = 0;
@@ -2151,6 +2185,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
                }
              else if (strcmp (p, "vContSupported+") == 0)
                vCont_supported = 1;
+             else if (strcmp (p, "QThreadEvents+") == 0)
+               ;
              else
                {
                  /* Move the unknown features all together.  */
@@ -2271,6 +2307,8 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
 
       strcat (own_buf, ";vContSupported+");
 
+      strcat (own_buf, ";QThreadEvents+");
+
       /* Reinitialize components as needed for the new connection.  */
       hostio_handle_new_gdb_connection ();
       target_handle_new_gdb_connection ();
@@ -4321,6 +4359,8 @@ handle_target_event (int err, gdb_client_data client_data)
          mark_breakpoints_out (process);
          mourn_inferior (process);
        }
+      else if (last_status.kind == TARGET_WAITKIND_THREAD_EXITED)
+       ;
       else
        {
          /* We're reporting this thread as stopped.  Update its
@@ -4338,7 +4378,11 @@ handle_target_event (int err, gdb_client_data client_data)
              exit (0);
            }
 
-         if (last_status.kind == TARGET_WAITKIND_STOPPED)
+         if (last_status.kind == TARGET_WAITKIND_EXITED
+             || last_status.kind == TARGET_WAITKIND_SIGNALLED
+             || last_status.kind == TARGET_WAITKIND_THREAD_EXITED)
+           ;
+         else
            {
              /* A thread stopped with a signal, but gdb isn't
                 connected to handle it.  Pass it down to the
@@ -4353,13 +4397,12 @@ handle_target_event (int err, gdb_client_data client_data)
 
              resume_info.thread = last_ptid;
              resume_info.kind = resume_continue;
-             resume_info.sig = gdb_signal_to_host (last_status.value.sig);
+             if (last_status.kind == TARGET_WAITKIND_STOPPED)
+               resume_info.sig = gdb_signal_to_host (last_status.value.sig);
+             else
+               resume_info.sig = 0;
              (*the_target->resume) (&resume_info, 1);
            }
-         else if (debug_threads)
-           debug_printf ("GDB not connected; ignoring event %d for [%s]\n",
-                         (int) last_status.kind,
-                         target_pid_to_str (last_ptid));
        }
       else
        {
index 96ad4fa58b7c346a9fafbe4a599abd3ff79a9acf..dc0361fdcd144528e40cc4ff4e12a86e3874ab7e 100644 (file)
@@ -87,6 +87,7 @@ extern int multi_process;
 extern int report_fork_events;
 extern int report_vfork_events;
 extern int report_exec_events;
+extern int report_thread_events;
 extern int non_stop;
 extern int extended_protocol;
 
index 266b89278c4a3814efd89cc7deb68e7044a12df0..1226dc110446e887d34314b341fd22974f44832a 100644 (file)
@@ -4436,6 +4436,14 @@ save_waitstatus (struct thread_info *tp, struct target_waitstatus *ws)
     }
 }
 
+/* A cleanup that disables thread create/exit events.  */
+
+static void
+disable_thread_events (void *arg)
+{
+  target_thread_events (0);
+}
+
 /* See infrun.h.  */
 
 void
@@ -4455,6 +4463,9 @@ stop_all_threads (void)
   entry_ptid = inferior_ptid;
   old_chain = make_cleanup (switch_to_thread_cleanup, &entry_ptid);
 
+  target_thread_events (1);
+  make_cleanup (disable_thread_events, NULL);
+
   /* Request threads to stop, and then wait for the stops.  Because
      threads we already know about can spawn more threads while we're
      trying to stop them, and we only learn about new threads when we
@@ -4532,7 +4543,8 @@ stop_all_threads (void)
            {
              /* All resumed threads exited.  */
            }
-         else if (ws.kind == TARGET_WAITKIND_EXITED
+         else if (ws.kind == TARGET_WAITKIND_THREAD_EXITED
+                  || ws.kind == TARGET_WAITKIND_EXITED
                   || ws.kind == TARGET_WAITKIND_SIGNALLED)
            {
              if (debug_infrun)
@@ -4683,6 +4695,14 @@ handle_inferior_event_1 (struct execution_control_state *ecs)
       return;
     }
 
+  if (ecs->ws.kind == TARGET_WAITKIND_THREAD_EXITED)
+    {
+      if (debug_infrun)
+       fprintf_unfiltered (gdb_stdlog, "infrun: TARGET_WAITKIND_THREAD_EXITED\n");
+      prepare_to_wait (ecs);
+      return;
+    }
+
   if (ecs->ws.kind == TARGET_WAITKIND_NO_RESUMED
       && target_can_async_p () && !sync_execution)
     {
@@ -4887,6 +4907,15 @@ handle_inferior_event_1 (struct execution_control_state *ecs)
       prepare_to_wait (ecs);
       return;
 
+    case TARGET_WAITKIND_THREAD_CREATED:
+      if (debug_infrun)
+        fprintf_unfiltered (gdb_stdlog, "infrun: TARGET_WAITKIND_THREAD_CREATED\n");
+      if (!ptid_equal (ecs->ptid, inferior_ptid))
+       context_switch (ecs->ptid);
+      if (!switch_back_to_stepped_thread (ecs))
+       keep_going (ecs);
+      return;
+
     case TARGET_WAITKIND_EXITED:
     case TARGET_WAITKIND_SIGNALLED:
       if (debug_infrun)
index 7256c23b4ffb697551a0362e134c28feecb96668..4daaf08c4b4954b02c6d179917247d1b8f169e30 100644 (file)
@@ -139,6 +139,8 @@ static int remote_is_async_p (struct target_ops *);
 
 static void remote_async (struct target_ops *ops, int enable);
 
+static void remote_thread_events (struct target_ops *ops, int enable);
+
 static void sync_remote_interrupt_twice (int signo);
 
 static void interrupt_query (void);
@@ -1436,6 +1438,9 @@ enum {
   /* Support for the QNonStop packet.  */
   PACKET_QNonStop,
 
+  /* Support for the QThreadEvents packet.  */
+  PACKET_QThreadEvents,
+
   /* Support for multi-process extensions.  */
   PACKET_multiprocess_feature,
 
@@ -4520,7 +4525,8 @@ static const struct protocol_feature remote_protocol_features[] = {
     PACKET_exec_event_feature },
   { "Qbtrace-conf:pt:size", PACKET_DISABLE, remote_supported_packet,
     PACKET_Qbtrace_conf_pt_size },
-  { "vContSupported", PACKET_DISABLE, remote_supported_packet, PACKET_vContSupported }
+  { "vContSupported", PACKET_DISABLE, remote_supported_packet, PACKET_vContSupported },
+  { "QThreadEvents", PACKET_DISABLE, remote_supported_packet, PACKET_QThreadEvents },
 };
 
 static char *remote_support_xml;
@@ -4613,6 +4619,9 @@ remote_query_supported (void)
       if (packet_set_cmd_state (PACKET_vContSupported) != AUTO_BOOLEAN_FALSE)
        q = remote_query_supported_append (q, "vContSupported+");
 
+      if (packet_set_cmd_state (PACKET_QThreadEvents) != AUTO_BOOLEAN_FALSE)
+       q = remote_query_supported_append (q, "QThreadEvents+");
+
       /* Keep this one last to work around a gdbserver <= 7.10 bug in
         the qSupported:xmlRegisters=i386 handling.  */
       if (remote_support_xml != NULL)
@@ -6045,10 +6054,9 @@ remove_child_of_pending_fork (QUEUE (stop_reply_p) *q,
     = (struct threads_listing_context *) param->input;
 
   if (event->ws.kind == TARGET_WAITKIND_FORKED
-      || event->ws.kind == TARGET_WAITKIND_VFORKED)
-    {
-      threads_listing_context_remove (&event->ws, context);
-    }
+      || event->ws.kind == TARGET_WAITKIND_VFORKED
+      || event->ws.kind == TARGET_WAITKIND_THREAD_EXITED)
+    threads_listing_context_remove (&event->ws, context);
 
   return 1;
 }
@@ -6446,6 +6454,11 @@ Packet: '%s'\n"),
                 one used by the original program.  */
              skipregs = 1;
            }
+         else if (strprefix (p, p1, "create"))
+           {
+             event->ws.kind = TARGET_WAITKIND_THREAD_CREATED;
+             p = skip_to_semicolon (p1 + 1);
+           }
          else
            {
              ULONGEST pnum;
@@ -6516,6 +6529,19 @@ Packet: '%s'\n"),
          event->ws.value.sig = GDB_SIGNAL_UNKNOWN;
       }
       break;
+    case 'w':          /* Thread exited.  */
+      {
+       char *p;
+       ULONGEST value;
+
+       event->ws.kind = TARGET_WAITKIND_THREAD_EXITED;
+       p = unpack_varlen_hex (&buf[1], &value);
+       event->ws.value.integer = value;
+       if (*p != ';')
+         error (_("stop reply packet badly formatted: %s"), buf);
+       event->ptid = read_ptid (++p, &p);
+       break;
+      }
     case 'W':          /* Target exited.  */
     case 'X':
       {
@@ -12965,6 +12991,7 @@ Specify the serial device it is connected to\n\
   remote_ops.to_can_async_p = remote_can_async_p;
   remote_ops.to_is_async_p = remote_is_async_p;
   remote_ops.to_async = remote_async;
+  remote_ops.to_thread_events = remote_thread_events;
   remote_ops.to_can_do_single_step = remote_can_do_single_step;
   remote_ops.to_terminal_inferior = remote_terminal_inferior;
   remote_ops.to_terminal_ours = remote_terminal_ours;
@@ -13150,6 +13177,37 @@ remote_async (struct target_ops *ops, int enable)
     }
 }
 
+/* Implementation of the to_thread_events method.  */
+
+static void
+remote_thread_events (struct target_ops *ops, int enable)
+{
+  struct remote_state *rs = get_remote_state ();
+  size_t size = get_remote_packet_size ();
+  char *p = rs->buf;
+
+  if (packet_support (PACKET_QThreadEvents) == PACKET_DISABLE)
+    return;
+
+  xsnprintf (rs->buf, size, "QThreadEvents:%x", enable ? 1 : 0);
+  putpkt (rs->buf);
+  getpkt (&rs->buf, &rs->buf_size, 0);
+
+  switch (packet_ok (rs->buf,
+                    &remote_protocol_packets[PACKET_QThreadEvents]))
+    {
+    case PACKET_OK:
+      if (strcmp (rs->buf, "OK") != 0)
+       error (_("Remote refused setting thread events: %s"), rs->buf);
+      break;
+    case PACKET_ERROR:
+      warning (_("Remote failure reply: %s"), rs->buf);
+      break;
+    case PACKET_UNKNOWN:
+      break;
+    }
+}
+
 static void
 set_remote_cmd (char *args, int from_tty)
 {
@@ -13694,6 +13752,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
   add_packet_config_cmd (&remote_protocol_packets[PACKET_vCtrlC],
                         "vCtrlC", "ctrl-c", 0);
 
+  add_packet_config_cmd (&remote_protocol_packets[PACKET_QThreadEvents],
+                        "QThreadEvents", "thread-events", 0);
+
   /* Assert that we've registered "set remote foo-packet" commands
      for all packet configs.  */
   {
index b41b3168c8a986691571442a6aa03057792be6f2..d23bc759e5a777a034488b2654e6f7d146272189 100644 (file)
@@ -1809,6 +1809,30 @@ debug_async (struct target_ops *self, int arg1)
   fputs_unfiltered (")\n", gdb_stdlog);
 }
 
+static void
+delegate_thread_events (struct target_ops *self, int arg1)
+{
+  self = self->beneath;
+  self->to_thread_events (self, arg1);
+}
+
+static void
+tdefault_thread_events (struct target_ops *self, int arg1)
+{
+}
+
+static void
+debug_thread_events (struct target_ops *self, int arg1)
+{
+  fprintf_unfiltered (gdb_stdlog, "-> %s->to_thread_events (...)\n", debug_target.to_shortname);
+  debug_target.to_thread_events (&debug_target, arg1);
+  fprintf_unfiltered (gdb_stdlog, "<- %s->to_thread_events (", debug_target.to_shortname);
+  target_debug_print_struct_target_ops_p (&debug_target);
+  fputs_unfiltered (", ", gdb_stdlog);
+  target_debug_print_int (arg1);
+  fputs_unfiltered (")\n", gdb_stdlog);
+}
+
 static int
 delegate_supports_non_stop (struct target_ops *self)
 {
@@ -4186,6 +4210,8 @@ install_delegators (struct target_ops *ops)
     ops->to_is_async_p = delegate_is_async_p;
   if (ops->to_async == NULL)
     ops->to_async = delegate_async;
+  if (ops->to_thread_events == NULL)
+    ops->to_thread_events = delegate_thread_events;
   if (ops->to_supports_non_stop == NULL)
     ops->to_supports_non_stop = delegate_supports_non_stop;
   if (ops->to_always_non_stop_p == NULL)
@@ -4424,6 +4450,7 @@ install_dummy_methods (struct target_ops *ops)
   ops->to_can_async_p = tdefault_can_async_p;
   ops->to_is_async_p = tdefault_is_async_p;
   ops->to_async = tdefault_async;
+  ops->to_thread_events = tdefault_thread_events;
   ops->to_supports_non_stop = tdefault_supports_non_stop;
   ops->to_always_non_stop_p = tdefault_always_non_stop_p;
   ops->to_find_memory_regions = dummy_find_memory_regions;
@@ -4579,6 +4606,7 @@ init_debug_target (struct target_ops *ops)
   ops->to_can_async_p = debug_can_async_p;
   ops->to_is_async_p = debug_is_async_p;
   ops->to_async = debug_async;
+  ops->to_thread_events = debug_thread_events;
   ops->to_supports_non_stop = debug_supports_non_stop;
   ops->to_always_non_stop_p = debug_always_non_stop_p;
   ops->to_find_memory_regions = debug_find_memory_regions;
index b43c12a34de8254d0d7396d8d0ce68ee85b401ca..e5e81728b93bad7cb9970ad5e4cbb6df530339a4 100644 (file)
@@ -3850,6 +3850,14 @@ target_async (int enable)
   current_target.to_async (&current_target, enable);
 }
 
+/* See target.h.  */
+
+void
+target_thread_events (int enable)
+{
+  current_target.to_thread_events (&current_target, enable);
+}
+
 /* Controls if targets can report that they can/are async.  This is
    just for maintainers to use when debugging gdb.  */
 int target_async_permitted = 1;
index 65b717c730889f25b8a305ec2371cfb78e647009..e929eee1fb4d90f82d0769844b2b02299229fb65 100644 (file)
@@ -672,6 +672,8 @@ struct target_ops
       TARGET_DEFAULT_RETURN (0);
     void (*to_async) (struct target_ops *, int)
       TARGET_DEFAULT_NORETURN (tcomplain ());
+    void (*to_thread_events) (struct target_ops *, int)
+      TARGET_DEFAULT_IGNORE ();
     /* This method must be implemented in some situations.  See the
        comment on 'to_can_run'.  */
     int (*to_supports_non_stop) (struct target_ops *)
@@ -1793,6 +1795,9 @@ extern int target_async_permitted;
 /* Enables/disabled async target events.  */
 extern void target_async (int enable);
 
+/* Enables/disables thread create and exit events.  */
+extern void target_thread_events (int enable);
+
 /* Whether support for controlling the target backends always in
    non-stop mode is enabled.  */
 extern enum auto_boolean target_non_stop_enabled;
index 739c4b8430ebe3c67d1a993e3139e7193f32f0d7..42b562087d1616a6456e36fefd97cc5cf949487e 100644 (file)
@@ -63,6 +63,11 @@ target_waitstatus_to_string (const struct target_waitstatus *ws)
       return xstrprintf ("%sno-history", kind_str);
     case TARGET_WAITKIND_NO_RESUMED:
       return xstrprintf ("%sno-resumed", kind_str);
+    case TARGET_WAITKIND_THREAD_CREATED:
+      return xstrprintf ("%sthread created", kind_str);
+    case TARGET_WAITKIND_THREAD_EXITED:
+      return xstrprintf ("%sthread exited, status = %d",
+                        kind_str, ws->value.integer);
     default:
       return xstrprintf ("%sunknown???", kind_str);
     }
index ffaddc10c4db2ed63c47e4c21db5c09ed2fe2197..2e393442f51f0dcd0db0e487751bfb38e1f80e6b 100644 (file)
@@ -92,7 +92,13 @@ enum target_waitkind
   TARGET_WAITKIND_NO_HISTORY,
  
   /* There are no resumed children left in the program.  */
-  TARGET_WAITKIND_NO_RESUMED
+  TARGET_WAITKIND_NO_RESUMED,
+
+  /* The thread was created.  */
+  TARGET_WAITKIND_THREAD_CREATED,
+
+  /* The thread has exited.  The exit status is in value.integer.  */
+  TARGET_WAITKIND_THREAD_EXITED,
 };
 
 struct target_waitstatus