From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 116335 invoked by alias); 14 Oct 2015 15:33:47 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Received: (qmail 116251 invoked by uid 89); 14 Oct 2015 15:33:46 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-1.9 required=5.0 tests=AWL,BAYES_00,SPF_HELO_PASS,T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Wed, 14 Oct 2015 15:33:29 +0000 Received: from int-mx14.intmail.prod.int.phx2.redhat.com (int-mx14.intmail.prod.int.phx2.redhat.com [10.5.11.27]) by mx1.redhat.com (Postfix) with ESMTPS id 68AA0AEF1A for ; Wed, 14 Oct 2015 15:28:18 +0000 (UTC) Received: from brno.lan (ovpn01.gateway.prod.ext.ams2.redhat.com [10.39.146.11]) by int-mx14.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id t9EFS6Dj016846 for ; Wed, 14 Oct 2015 11:28:17 -0400 From: Pedro Alves To: gdb-patches@sourceware.org Subject: [PATCH 10/18] Remote thread create/exit events Date: Wed, 14 Oct 2015 15:33:00 -0000 Message-Id: <1444836486-25679-11-git-send-email-palves@redhat.com> In-Reply-To: <1444836486-25679-1-git-send-email-palves@redhat.com> References: <1444836486-25679-1-git-send-email-palves@redhat.com> X-SW-Source: 2015-10/txt/msg00221.txt.bz2 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-10-14 Pedro Alves * 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-10-14 Pedro Alves * 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. * server.h (report_thread_events): Declare. gdb/ChangeLog: 2015-10-14 Pedro Alves * 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) : 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) waitstatus.kind == TARGET_WAITKIND_IGNORE); + if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK) || (event == PTRACE_EVENT_CLONE)) { @@ -587,6 +589,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; @@ -2266,25 +2274,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; } } @@ -2627,18 +2632,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); } @@ -2923,6 +2916,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 @@ -3028,6 +3045,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); } @@ -3512,6 +3532,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); } @@ -3912,6 +3935,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); diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c index e366091..0d9cde3 100644 --- a/gdb/gdbserver/remote-utils.c +++ b/gdb/gdbserver/remote-utils.c @@ -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; diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c index e0ce524..2ffc5d0 100644 --- a/gdb/gdbserver/server.c +++ b/gdb/gdbserver/server.c @@ -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; @@ -2259,6 +2293,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 (); diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h index 96ad4fa..dc0361f 100644 --- a/gdb/gdbserver/server.h +++ b/gdb/gdbserver/server.h @@ -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; diff --git a/gdb/infrun.c b/gdb/infrun.c index d2587f1..6052e8f 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -4388,6 +4388,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 @@ -4407,6 +4415,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 @@ -4484,7 +4495,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) @@ -4635,6 +4647,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) { @@ -4839,6 +4859,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) diff --git a/gdb/remote.c b/gdb/remote.c index 9a23ca6..f4714bc 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -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); @@ -1434,6 +1436,9 @@ enum { /* Support for the QNonStop packet. */ PACKET_QNonStop, + /* Support for the QThreadEvents packet. */ + PACKET_QThreadEvents, + /* Support for multi-process extensions. */ PACKET_multiprocess_feature, @@ -4494,7 +4499,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; @@ -4590,6 +4596,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+"); + q = reconcat (q, "qSupported:", q, (char *) NULL); putpkt (q); @@ -6016,10 +6025,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; } @@ -6417,6 +6425,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; @@ -6487,6 +6500,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': { @@ -12941,6 +12967,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; @@ -13118,6 +13145,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) { @@ -13662,6 +13720,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. */ { diff --git a/gdb/target-delegates.c b/gdb/target-delegates.c index a7271fa..58dc133 100644 --- a/gdb/target-delegates.c +++ b/gdb/target-delegates.c @@ -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; diff --git a/gdb/target.c b/gdb/target.c index b8b1e9b..6bc1c02 100644 --- a/gdb/target.c +++ b/gdb/target.c @@ -3833,6 +3833,14 @@ target_async (int enable) current_target.to_async (¤t_target, enable); } +/* See target.h. */ + +void +target_thread_events (int enable) +{ + current_target.to_thread_events (¤t_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; diff --git a/gdb/target.h b/gdb/target.h index 2b2db45..007f293 100644 --- a/gdb/target.h +++ b/gdb/target.h @@ -670,6 +670,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 *) @@ -1791,6 +1793,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; diff --git a/gdb/target/waitstatus.c b/gdb/target/waitstatus.c index 739c4b8..42b5620 100644 --- a/gdb/target/waitstatus.c +++ b/gdb/target/waitstatus.c @@ -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); } diff --git a/gdb/target/waitstatus.h b/gdb/target/waitstatus.h index ffaddc1..2e39344 100644 --- a/gdb/target/waitstatus.h +++ b/gdb/target/waitstatus.h @@ -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 -- 1.9.3