aboutsummaryrefslogtreecommitdiff
path: root/gdb/gdbserver/server.c
diff options
context:
space:
mode:
authorPedro Alves <palves@redhat.com>2010-04-11 16:33:56 +0000
committerPedro Alves <palves@redhat.com>2010-04-11 16:33:56 +0000
commit8336d594d5107cb86bf1b569962bf1496e2c7c30 (patch)
tree6e8f6c708234c278bdf701a1356fca739cd51c86 /gdb/gdbserver/server.c
parent5d267c4c705d1d22e21b0dde92750b3afe699681 (diff)
downloadbinutils-8336d594d5107cb86bf1b569962bf1496e2c7c30.zip
binutils-8336d594d5107cb86bf1b569962bf1496e2c7c30.tar.gz
binutils-8336d594d5107cb86bf1b569962bf1496e2c7c30.tar.bz2
GDBserver disconnected tracing support.
* linux-low.c (linux_remove_process): Delete. (add_lwp): Don't set last_resume_kind here. (linux_kill): Use `mourn'. (linux_detach): Use `thread_db_detach', and `mourn'. (linux_mourn): New. (linux_attach_lwp_1): Adjust comment. (linux_attach): last_resume_kind moved the thread_info; adjust. (status_pending_p_callback): Adjust. (linux_wait_for_event_1): Adjust. (count_events_callback, select_singlestep_lwp_callback) (select_event_lwp_callback, cancel_breakpoints_callback) (db_wants_lwp_stopped, linux_wait_1, need_step_over_p) (proceed_one_lwp): Adjust. (linux_async): Add debug output. (linux_thread_stopped): New. (linux_pause_all): New. (linux_target_ops): Install linux_mourn, linux_thread_stopped and linux_pause_all. * linux-low.h (struct lwp_info): Delete last_resume_kind field. (thread_db_free): Delete declaration. (thread_db_detach, thread_db_mourn): Declare. * thread-db.c (thread_db_init): Use thread_db_mourn. (thread_db_free): Delete, split in two. (disable_thread_event_reporting): New. (thread_db_detach): New. (thread_db_mourn): New. * server.h (struct thread_info) <last_resume_kind>: New field. <attached>: Add comment. <gdb_detached>: New field. (handler_func): Change return type to int. (handle_serial_event, handle_target_event): Ditto. (gdb_connected): Declare. (tracing): Delete. (disconnected_tracing): Declare. (stop_tracing): Declare. * server.c (handle_query) <qSupported>: Report support for disconnected tracing. (queue_stop_reply_callback): Account for running threads. (gdb_wants_thread_stopped): New. (gdb_wants_all_threads_stopped): New. (gdb_reattached_process): New. (handle_status): Clear the `gdb_detached' flag of all processes. In all-stop, stop all threads. (main): Be sure to leave tfind mode. Handle disconnected tracing. (process_serial_event): If the remote connection breaks, or if an exit was forced with "monitor exit", force an event loop exit. Handle disconnected tracing on detach. (handle_serial_event): Adjust. (handle_target_event): If GDB isn't connected, forward events back to the inferior, unless the last process exited, in which case, exit gdbserver. Adjust interface. * remote-utils.c (remote_open): Don't block in accept. Instead register an event loop source on the listen socket file descriptor. Refactor bits into ... (listen_desc): ... this new global. (gdb_connected): ... this new function. (enable_async_notification): ... this new function. (handle_accept_event): ... this new function. (remote_close): Clear remote_desc. * inferiors.c (add_thread): Set the new thread's last_resume_kind. * target.h (struct target_ops) <mourn, thread_stopped, pause_all>: New fields. (mourn_inferior): Define. (target_process_qsupported): Avoid the dangling else problem. (thread_stopped): Define. (pause_all): Define. (target_waitstatus_to_string): Declare. * target.c (target_waitstatus_to_string): New. * tracepoint.c (tracing): Make extern. (disconnected_tracing): New. (stop_tracing): Make extern. Handle tracing stops due to GDB disconnecting. (cmd_qtdisconnected): New. (cmd_qtstatus): Report disconnected tracing status in trace reply. (handle_tracepoint_general_set): Handle QTDisconnected. * event-loop.c (event_handler_func): Change return type to int. (process_event): Bail out if the event handler wants the event loop to stop. (handle_file_event): Ditto. (start_event_loop): Bail out if the event handler wants the event loop to stop. * nto-low.c (nto_target_ops): Adjust. * spu-low.c (spu_wait): Don't remove the process here. (spu_target_ops): Adjust. * win32-low.c (win32_wait): Don't remove the process here. (win32_target_ops): Adjust.
Diffstat (limited to 'gdb/gdbserver/server.c')
-rw-r--r--gdb/gdbserver/server.c249
1 files changed, 216 insertions, 33 deletions
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 0fd82ee..8666bcd 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -1392,6 +1392,7 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
strcat (own_buf, ";ConditionalTracepoints+");
strcat (own_buf, ";TraceStateVariables+");
strcat (own_buf, ";TracepointSource+");
+ strcat (own_buf, ";DisconnectedTracing+");
}
return;
@@ -1976,31 +1977,81 @@ myresume (char *own_buf, int step, int sig)
static int
queue_stop_reply_callback (struct inferior_list_entry *entry, void *arg)
{
- int pid = * (int *) arg;
+ struct thread_info *thread = (struct thread_info *) entry;
- if (pid == -1
- || ptid_get_pid (entry->id) == pid)
+ /* For now, assume targets that don't have this callback also don't
+ manage the thread's last_status field. */
+ if (the_target->thread_stopped == NULL)
{
struct target_waitstatus status;
status.kind = TARGET_WAITKIND_STOPPED;
status.value.sig = TARGET_SIGNAL_TRAP;
- /* Pass the last stop reply back to GDB, but don't notify. */
- queue_stop_reply (entry->id, &status);
+ /* Pass the last stop reply back to GDB, but don't notify
+ yet. */
+ queue_stop_reply (entry->id, &thread->last_status);
+ }
+ else
+ {
+ if (thread_stopped (thread))
+ {
+ if (debug_threads)
+ fprintf (stderr, "Reporting thread %s as already stopped with %s\n",
+ target_pid_to_str (entry->id),
+ target_waitstatus_to_string (&thread->last_status));
+
+ /* Pass the last stop reply back to GDB, but don't notify
+ yet. */
+ queue_stop_reply (entry->id, &thread->last_status);
+ }
}
return 0;
}
+/* Set this inferior LWP's state as "want-stopped". We won't resume
+ this LWP until the client gives us another action for it. */
+
+static void
+gdb_wants_thread_stopped (struct inferior_list_entry *entry)
+{
+ struct thread_info *thread = (struct thread_info *) entry;
+
+ thread->last_resume_kind = resume_stop;
+
+ if (thread->last_status.kind == TARGET_WAITKIND_IGNORE)
+ {
+ thread->last_status.kind = TARGET_WAITKIND_STOPPED;
+ thread->last_status.value.sig = TARGET_SIGNAL_0;
+ }
+}
+
+/* Set all threads' states as "want-stopped". */
+
+static void
+gdb_wants_all_threads_stopped (void)
+{
+ for_each_inferior (&all_threads, gdb_wants_thread_stopped);
+}
+
+/* Clear the gdb_detached flag of every process. */
+
+static void
+gdb_reattached_process (struct inferior_list_entry *entry)
+{
+ struct process_info *process = (struct process_info *) entry;
+
+ process->gdb_detached = 0;
+}
+
/* Status handler for the '?' packet. */
static void
handle_status (char *own_buf)
{
- struct target_waitstatus status;
- status.kind = TARGET_WAITKIND_STOPPED;
- status.value.sig = TARGET_SIGNAL_TRAP;
+ /* GDB is connected, don't forward events to the target anymore. */
+ for_each_inferior (&all_processes, gdb_reattached_process);
/* In non-stop mode, we must send a stop reply for each stopped
thread. In all-stop mode, just send one for the first stopped
@@ -2008,9 +2059,8 @@ handle_status (char *own_buf)
if (non_stop)
{
- int pid = -1;
- discard_queued_stop_replies (pid);
- find_inferior (&all_threads, queue_stop_reply_callback, &pid);
+ discard_queued_stop_replies (-1);
+ find_inferior (&all_threads, queue_stop_reply_callback, NULL);
/* The first is sent immediatly. OK is sent if there is no
stopped thread, which is the same handling of the vStopped
@@ -2019,9 +2069,18 @@ handle_status (char *own_buf)
}
else
{
+ pause_all ();
+ gdb_wants_all_threads_stopped ();
+
if (all_threads.head)
- prepare_resume_reply (own_buf,
- all_threads.head->id, &status);
+ {
+ struct target_waitstatus status;
+
+ status.kind = TARGET_WAITKIND_STOPPED;
+ status.value.sig = TARGET_SIGNAL_TRAP;
+ prepare_resume_reply (own_buf,
+ all_threads.head->id, &status);
+ }
else
strcpy (own_buf, "W00");
}
@@ -2390,7 +2449,8 @@ main (int argc, char *argv[])
{
noack_mode = 0;
multi_process = 0;
- non_stop = 0;
+ /* Be sure we're out of tfind mode. */
+ current_traceframe = -1;
remote_open (port);
@@ -2405,7 +2465,7 @@ main (int argc, char *argv[])
}
/* Wait for events. This will return when all event sources are
- removed from the event loop. */
+ removed from the event loop. */
start_event_loop ();
/* If an exit was requested (using the "monitor exit" command),
@@ -2418,9 +2478,37 @@ main (int argc, char *argv[])
detach_or_kill_for_exit ();
exit (0);
}
- else
- fprintf (stderr, "Remote side has terminated connection. "
- "GDBserver will reopen the connection.\n");
+
+ fprintf (stderr,
+ "Remote side has terminated connection. "
+ "GDBserver will reopen the connection.\n");
+
+ if (tracing)
+ {
+ if (disconnected_tracing)
+ {
+ /* Try to enable non-stop/async mode, so we we can both
+ wait for an async socket accept, and handle async
+ target events simultaneously. There's also no point
+ either in having the target always stop all threads,
+ when we're going to pass signals down without
+ informing GDB. */
+ if (!non_stop)
+ {
+ if (start_non_stop (1))
+ non_stop = 1;
+
+ /* Detaching implicitly resumes all threads; simply
+ disconnecting does not. */
+ }
+ }
+ else
+ {
+ fprintf (stderr,
+ "Disconnected tracing disabled; stopping trace run.\n");
+ stop_tracing ();
+ }
+ }
}
}
@@ -2429,7 +2517,7 @@ main (int argc, char *argv[])
a brisk pace, so we read the rest of the packet with a blocking
getpkt call. */
-static void
+static int
process_serial_event (void)
{
char ch;
@@ -2455,9 +2543,9 @@ process_serial_event (void)
packet_len = getpkt (own_buf);
if (packet_len <= 0)
{
- target_async (0);
remote_close ();
- return;
+ /* Force an event loop break. */
+ return -1;
}
response_needed = 1;
@@ -2483,7 +2571,49 @@ process_serial_event (void)
pid =
ptid_get_pid (((struct inferior_list_entry *) current_inferior)->id);
+ if (tracing && disconnected_tracing)
+ {
+ struct thread_resume resume_info;
+ struct process_info *process = find_process_pid (pid);
+
+ if (process == NULL)
+ {
+ write_enn (own_buf);
+ break;
+ }
+
+ fprintf (stderr,
+ "Disconnected tracing in effect, "
+ "leaving gdbserver attached to the process\n");
+
+ /* Make sure we're in non-stop/async mode, so we we can both
+ wait for an async socket accept, and handle async target
+ events simultaneously. There's also no point either in
+ having the target stop all threads, when we're going to
+ pass signals down without informing GDB. */
+ if (!non_stop)
+ {
+ if (debug_threads)
+ fprintf (stderr, "Forcing non-stop mode\n");
+
+ non_stop = 1;
+ start_non_stop (1);
+ }
+
+ process->gdb_detached = 1;
+
+ /* Detaching implicitly resumes all threads. */
+ resume_info.thread = minus_one_ptid;
+ resume_info.kind = resume_continue;
+ resume_info.sig = 0;
+ (*the_target->resume) (&resume_info, 1);
+
+ write_ok (own_buf);
+ break; /* from switch/case */
+ }
+
fprintf (stderr, "Detaching from process %d\n", pid);
+ stop_tracing ();
if (detach_inferior (pid) != 0)
write_enn (own_buf);
else
@@ -2727,7 +2857,7 @@ process_serial_event (void)
if (!target_running ())
/* The packet we received doesn't make sense - but we can't
reply to it, either. */
- return;
+ return 0;
fprintf (stderr, "Killing all inferiors\n");
for_each_inferior (&all_processes, kill_inferior_callback);
@@ -2738,13 +2868,11 @@ process_serial_event (void)
{
last_status.kind = TARGET_WAITKIND_EXITED;
last_status.value.sig = TARGET_SIGNAL_KILL;
- return;
+ return 0;
}
else
- {
- exit (0);
- break;
- }
+ exit (0);
+
case 'T':
{
ptid_t gdb_id, thread_id;
@@ -2785,7 +2913,7 @@ process_serial_event (void)
last_status.kind = TARGET_WAITKIND_EXITED;
last_status.value.sig = TARGET_SIGNAL_KILL;
}
- return;
+ return 0;
}
else
{
@@ -2826,27 +2954,35 @@ process_serial_event (void)
exit (0);
}
}
+
+ if (exit_requested)
+ return -1;
+
+ return 0;
}
/* Event-loop callback for serial events. */
-void
+int
handle_serial_event (int err, gdb_client_data client_data)
{
if (debug_threads)
fprintf (stderr, "handling possible serial event\n");
/* Really handle it. */
- process_serial_event ();
+ if (process_serial_event () < 0)
+ return -1;
/* Be sure to not change the selected inferior behind GDB's back.
Important in the non-stop mode asynchronous protocol. */
set_desired_inferior (1);
+
+ return 0;
}
/* Event-loop callback for target events. */
-void
+int
handle_target_event (int err, gdb_client_data client_data)
{
if (debug_threads)
@@ -2857,11 +2993,58 @@ handle_target_event (int err, gdb_client_data client_data)
if (last_status.kind != TARGET_WAITKIND_IGNORE)
{
- /* Something interesting. Tell GDB about it. */
- push_event (last_ptid, &last_status);
+ int pid = ptid_get_pid (last_ptid);
+ struct process_info *process = find_process_pid (pid);
+ int forward_event = !gdb_connected () || process->gdb_detached;
+
+ if (last_status.kind == TARGET_WAITKIND_EXITED
+ || last_status.kind == TARGET_WAITKIND_SIGNALLED)
+ {
+ mourn_inferior (process);
+ remove_process (process);
+ }
+
+ if (forward_event)
+ {
+ if (!target_running ())
+ {
+ /* The last process exited. We're done. */
+ exit (0);
+ }
+
+ if (last_status.kind == TARGET_WAITKIND_STOPPED)
+ {
+ /* A thread stopped with a signal, but gdb isn't
+ connected to handle it. Pass it down to the
+ inferior, as if it wasn't being traced. */
+ struct thread_resume resume_info;
+
+ if (debug_threads)
+ fprintf (stderr,
+ "GDB not connected; forwarding event %d for [%s]\n",
+ (int) last_status.kind,
+ target_pid_to_str (last_ptid));
+
+ resume_info.thread = last_ptid;
+ resume_info.kind = resume_continue;
+ resume_info.sig = last_status.value.sig;
+ (*the_target->resume) (&resume_info, 1);
+ }
+ else if (debug_threads)
+ fprintf (stderr, "GDB not connected; ignoring event %d for [%s]\n",
+ (int) last_status.kind,
+ target_pid_to_str (last_ptid));
+ }
+ else
+ {
+ /* Something interesting. Tell GDB about it. */
+ push_event (last_ptid, &last_status);
+ }
}
/* Be sure to not change the selected inferior behind GDB's back.
Important in the non-stop mode asynchronous protocol. */
set_desired_inferior (1);
+
+ return 0;
}