diff options
author | Pedro Alves <palves@redhat.com> | 2015-11-30 16:05:21 +0000 |
---|---|---|
committer | Pedro Alves <palves@redhat.com> | 2015-11-30 18:40:30 +0000 |
commit | 65706a29bac50c2c971227a1945e46502845766b (patch) | |
tree | 170c4ec5d7e5ea2620858226b6561cdbd854a3ea /gdb/gdbserver | |
parent | 09df4675f2e4f8f098954f9a38f44d12089f1c4e (diff) | |
download | gdb-65706a29bac50c2c971227a1945e46502845766b.zip gdb-65706a29bac50c2c971227a1945e46502845766b.tar.gz gdb-65706a29bac50c2c971227a1945e46502845766b.tar.bz2 |
Remote thread create/exit events
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.
Diffstat (limited to 'gdb/gdbserver')
-rw-r--r-- | gdb/gdbserver/ChangeLog | 21 | ||||
-rw-r--r-- | gdb/gdbserver/linux-low.c | 79 | ||||
-rw-r--r-- | gdb/gdbserver/remote-utils.c | 13 | ||||
-rw-r--r-- | gdb/gdbserver/server.c | 55 | ||||
-rw-r--r-- | gdb/gdbserver/server.h | 1 |
5 files changed, 136 insertions, 33 deletions
diff --git a/gdb/gdbserver/ChangeLog b/gdb/gdbserver/ChangeLog index 3d00ed4..2d04899 100644 --- a/gdb/gdbserver/ChangeLog +++ b/gdb/gdbserver/ChangeLog @@ -1,5 +1,26 @@ 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 the thread's last_resume_kind was resume_stop. diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c index 1f7d8d1..cde59a7 100644 --- a/gdb/gdbserver/linux-low.c +++ b/gdb/gdbserver/linux-low.c @@ -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); 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 f6245d7..0762f6a 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; @@ -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 { 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; |