From cbb8991cabdd85b7828f59c92f754c5a2101b0b6 Mon Sep 17 00:00:00 2001 From: Don Breazeal Date: Tue, 12 May 2015 09:52:46 -0700 Subject: [PATCH] Extended-remote fork catch This patch implements catchpoints for fork events on extended-remote Linux targets. Implementation appeared to be straightforward, requiring four new functions in remote.c to implement insert/remove of fork/vfork catchpoints. These functions are essentially stubs that just return 0 ('success') if the required features are enabled. If the fork events are being reported, then catchpoints are set and hit. However, there are some extra issues that arise with catchpoints. 1) Thread creation reporting -- fork catchpoints are hit before the follow_fork has been completed. When stopped at a fork catchpoint in the native implementation, the new process is not 'reported' until after the follow is done. It doesn't show up in the inferiors list or the threads list. However, in the gdbserver case, an 'info threads' while stopped at a fork catchpoint will retrieve the new thread info from the target and add it to GDB's data structures, prior to the follow operations. Because of this premature report, things on the GDB side eventually get very confused. So in remote.c:remote_update_thread_list, we check to see if there are any pending fork parent threads. If there are we remove the related fork child thread from the thread list sent by the target. 2) Kill process before fork is followed -- on the native side in linux-nat.c:linux_nat_kill, there is some code to handle the case where a fork has occurred but follow_fork hasn't been called yet. It does this by using the last status to determine if a follow is pending, and if it is, to kill the child task. The use of last_status is fragile in situations like non-stop mode where other events may have occurred after the fork event. This patch identifies a fork parent in remote.c:extended_remote_kill in a way similar to that used in thread creation reporting above. If one is found, it kills the new child as well. Tested on x64 Ubuntu Lucid, native, remote, extended-remote. Tested the case of killing the forking process before the fork has been followed manually. gdb/ChangeLog: * remote.c (remote_insert_fork_catchpoint): New function. (remote_remove_fork_catchpoint): New function. (remote_insert_vfork_catchpoint): New function. (remote_remove_vfork_catchpoint): New function. (pending_fork_parent_callback): New function. (remove_new_fork_child): New function. (remote_update_thread_list): Call remote_notif_get_pending_events and remove_new_fork_child. (extended_remote_kill): Kill fork child when killing the parent before follow_fork completes. (init_extended_remote_ops): Initialize target vector with new fork catchpoint functions. --- gdb/ChangeLog | 15 ++++ gdb/remote.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 241 insertions(+), 3 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index e84f23faf84..53d6d5dff94 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,18 @@ +2015-05-12 Don Breazeal + + * remote.c (remote_insert_fork_catchpoint): New function. + (remote_remove_fork_catchpoint): New function. + (remote_insert_vfork_catchpoint): New function. + (remote_remove_vfork_catchpoint): New function. + (pending_fork_parent_callback): New function. + (remove_new_fork_child): New function. + (remote_update_thread_list): Call remote_notif_get_pending_events + and remove_new_fork_child. + (extended_remote_kill): Kill fork child when killing the + parent before follow_fork completes. + (init_extended_remote_ops): Initialize target vector with + new fork catchpoint functions. + 2015-05-12 Don Breazeal * remote.c (remove_vfork_event_p): New function. diff --git a/gdb/remote.c b/gdb/remote.c index df8fca3513c..dfe115b3231 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -104,6 +104,10 @@ static void remote_open_1 (const char *, int, struct target_ops *, static void remote_close (struct target_ops *self); +struct remote_state; + +static int remote_vkill (int pid, struct remote_state *rs); + static void remote_mourn (struct target_ops *ops); static void extended_remote_restart (void); @@ -181,7 +185,6 @@ static ptid_t read_ptid (char *buf, char **obuf); static void remote_set_permissions (struct target_ops *self); -struct remote_state; static int remote_get_trace_status (struct target_ops *self, struct trace_status *ts); @@ -204,6 +207,9 @@ static void push_stop_reply (struct stop_reply *); static void discard_pending_stop_replies_in_queue (struct remote_state *); static int peek_stop_reply (ptid_t ptid); +struct threads_listing_context; +static void remove_new_fork_children (struct threads_listing_context *); + static void remote_async_inferior_event_handler (gdb_client_data); static void remote_terminal_ours (struct target_ops *self); @@ -1478,6 +1484,46 @@ remote_vfork_event_p (struct remote_state *rs) return packet_support (PACKET_vfork_event_feature) == PACKET_ENABLE; } +/* Insert fork catchpoint target routine. If fork events are enabled + then return success, nothing more to do. */ + +static int +remote_insert_fork_catchpoint (struct target_ops *ops, int pid) +{ + struct remote_state *rs = get_remote_state (); + + return !remote_fork_event_p (rs); +} + +/* Remove fork catchpoint target routine. Nothing to do, just + return success. */ + +static int +remote_remove_fork_catchpoint (struct target_ops *ops, int pid) +{ + return 0; +} + +/* Insert vfork catchpoint target routine. If vfork events are enabled + then return success, nothing more to do. */ + +static int +remote_insert_vfork_catchpoint (struct target_ops *ops, int pid) +{ + struct remote_state *rs = get_remote_state (); + + return !remote_vfork_event_p (rs); +} + +/* Remove vfork catchpoint target routine. Nothing to do, just + return success. */ + +static int +remote_remove_vfork_catchpoint (struct target_ops *ops, int pid) +{ + return 0; +} + /* Tokens for use by the asynchronous signal handlers for SIGINT. */ static struct async_signal_handler *async_sigint_remote_twice_token; static struct async_signal_handler *async_sigint_remote_token; @@ -2645,6 +2691,27 @@ clear_threads_listing_context (void *p) VEC_free (thread_item_t, context->items); } +/* Remove the thread specified as the related_pid field of WS + from the CONTEXT list. */ + +static void +threads_listing_context_remove (struct target_waitstatus *ws, + struct threads_listing_context *context) +{ + struct thread_item *item; + int i; + ptid_t child_ptid = ws->value.related_pid; + + for (i = 0; VEC_iterate (thread_item_t, context->items, i, item); ++i) + { + if (ptid_equal (item->ptid, child_ptid)) + { + VEC_ordered_remove (thread_item_t, context->items, i); + break; + } + } +} + static int remote_newthread_step (threadref *ref, void *data) { @@ -2867,7 +2934,7 @@ remote_update_thread_list (struct target_ops *ops) target end. Delete GDB-side threads no longer found on the target. */ ALL_THREADS_SAFE (tp, tmp) - { + { for (i = 0; VEC_iterate (thread_item_t, context.items, i, item); ++i) @@ -2881,7 +2948,12 @@ remote_update_thread_list (struct target_ops *ops) /* Not found. */ delete_thread (tp->ptid); } - } + } + + /* 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); /* And now add threads we don't know about yet to our list. */ for (i = 0; @@ -5427,6 +5499,81 @@ struct queue_iter_param struct stop_reply *output; }; +/* Determine if THREAD is a pending fork parent thread. ARG contains + the pid of the process that owns the threads we want to check, or + -1 if we want to check all threads. */ + +static int +is_pending_fork_parent (struct target_waitstatus *ws, int event_pid, + ptid_t thread_ptid) +{ + if (ws->kind == TARGET_WAITKIND_FORKED + || ws->kind == TARGET_WAITKIND_VFORKED) + { + if (event_pid == -1 || event_pid == ptid_get_pid (thread_ptid)) + return 1; + } + + return 0; +} + +/* Check whether EVENT is a fork event, and if it is, remove the + fork child from the context list passed in DATA. */ + +static int +remove_child_of_pending_fork (QUEUE (stop_reply_p) *q, + QUEUE_ITER (stop_reply_p) *iter, + stop_reply_p event, + void *data) +{ + struct queue_iter_param *param = data; + struct threads_listing_context *context = param->input; + + if (event->ws.kind == TARGET_WAITKIND_FORKED + || event->ws.kind == TARGET_WAITKIND_VFORKED) + { + threads_listing_context_remove (&event->ws, context); + } + + return 1; +} + +/* 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. */ + +static void +remove_new_fork_children (struct threads_listing_context *context) +{ + struct thread_info * thread; + int pid = -1; + struct notif_client *notif = ¬if_client_stop; + struct queue_iter_param param; + + /* For any threads stopped at a fork event, remove the corresponding + fork child threads from the CONTEXT list. */ + ALL_NON_EXITED_THREADS (thread) + { + struct target_waitstatus *ws = &thread->pending_follow; + + if (is_pending_fork_parent (ws, pid, thread->ptid)) + { + threads_listing_context_remove (ws, context); + } + } + + /* 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. */ + remote_notif_get_pending_events (notif); + param.input = context; + param.output = NULL; + QUEUE_iterate (stop_reply_p, stop_reply_queue, + remove_child_of_pending_fork, ¶m); +} + /* Remove stop replies in the queue if its pid is equal to the given inferior's pid. */ @@ -7945,6 +8092,69 @@ getpkt_or_notif_sane (char **buf, long *sizeof_buf, int forever, is_notif); } +/* Check whether EVENT is a fork event for the process specified + by the pid passed in DATA, and if it is, kill the fork child. */ + +static int +kill_child_of_pending_fork (QUEUE (stop_reply_p) *q, + QUEUE_ITER (stop_reply_p) *iter, + stop_reply_p event, + void *data) +{ + struct queue_iter_param *param = data; + int parent_pid = *(int *) param->input; + + if (is_pending_fork_parent (&event->ws, parent_pid, event->ptid)) + { + struct remote_state *rs = get_remote_state (); + int child_pid = ptid_get_pid (event->ws.value.related_pid); + int res; + + res = remote_vkill (child_pid, rs); + if (res != 0) + error (_("Can't kill fork child process %d"), child_pid); + } + + return 1; +} + +/* Kill any new fork children of process PID that haven't been + processed by follow_fork. */ + +static void +kill_new_fork_children (int pid, struct remote_state *rs) +{ + struct thread_info *thread; + struct notif_client *notif = ¬if_client_stop; + struct queue_iter_param param; + + /* Kill the fork child threads of any threads in process PID + that are stopped at a fork event. */ + ALL_NON_EXITED_THREADS (thread) + { + struct target_waitstatus *ws = &thread->pending_follow; + + if (is_pending_fork_parent (ws, pid, thread->ptid)) + { + struct remote_state *rs = get_remote_state (); + int child_pid = ptid_get_pid (ws->value.related_pid); + int res; + + res = remote_vkill (child_pid, rs); + if (res != 0) + error (_("Can't kill fork child process %d"), child_pid); + } + } + + /* Check for any pending fork events (not reported or processed yet) + in process PID and kill those fork child threads as well. */ + remote_notif_get_pending_events (notif); + param.input = &pid; + param.output = NULL; + QUEUE_iterate (stop_reply_p, stop_reply_queue, + kill_child_of_pending_fork, ¶m); +} + static void remote_kill (struct target_ops *ops) @@ -8014,6 +8224,11 @@ extended_remote_kill (struct target_ops *ops) int pid = ptid_get_pid (inferior_ptid); struct remote_state *rs = get_remote_state (); + /* If we're stopped while forking and we haven't followed yet, kill the + child task. We need to do this before killing the parent task + because if this is a vfork then the parent will be sleeping. */ + kill_new_fork_children (pid, rs); + res = remote_vkill (pid, rs); if (res == -1 && !(rs->extended && remote_multi_process_p (rs))) { @@ -11970,6 +12185,14 @@ Specify the serial device it is connected to (e.g. /dev/ttya)."; extended_remote_ops.to_supports_disable_randomization = extended_remote_supports_disable_randomization; extended_remote_ops.to_follow_fork = remote_follow_fork; + extended_remote_ops.to_insert_fork_catchpoint + = remote_insert_fork_catchpoint; + extended_remote_ops.to_remove_fork_catchpoint + = remote_remove_fork_catchpoint; + extended_remote_ops.to_insert_vfork_catchpoint + = remote_insert_vfork_catchpoint; + extended_remote_ops.to_remove_vfork_catchpoint + = remote_remove_vfork_catchpoint; } static int -- 2.30.2