Re: [PATCH 06/16 v2] Extended-remote Linux follow fork

Breazeal, Don
2014-09-19T20:57:41Z

This is a small update to this patch so that it will work on targets
where hardware single step is not supported. I had noted previously
that I had not tested inheritance of hardware watchpoints across a
fork. I have now completed that testing, which exposed a couple of
problems, one of which is fixed by this update. The only change from
the previous version of this patch is to initialize the variable
'status' in linux-low.c:linux_follow_fork.

Thanks,
--Don

On 8/20/2014 5:29 PM, Don Breazeal wrote:
> This patch implements basic support for follow-fork and detach-on-fork on
> extended-remote Linux targets. Only 'fork' is supported in this patch;
> 'vfork' support is added n a subsequent patch. Sufficient extended-remote
> functionality has been implemented here to pass gdb.base/foll-fork.exp with
> the catchpoint tests commented out.
>
> The implementation follows the same general structure as for the native
> implementation as much as possible.
>
> This implementation included:
> * enabling fork events in linux-low.c in initialize_low and
> linux_enable_extended_features
>
> - this adds the ptrace option to trace fork events to the new functions
> from patch 4 that set up ptrace options.
>
> * handling fork events in gdbserver/linux-low.c:handle_extended_wait
>
> - when a fork event occurs in gdbserver, we must do the full creation
> of the new process, thread, lwp, and breakpoint lists. This is
> required whether or not the new child is destined to be
> detached-on-fork, because GDB will make target calls that require all
> the structures. In particular we need the breakpoint lists in order
> to remove the breakpoints from a detaching child. If we are not
> detaching the child we will need all these structures anyway.
>
> - as part of this event handling we store the target_waitstatus in a new
> member of the parent thread_info structure, 'pending_follow'. This
> mimics a similar mechanism used in the native implementation. Here is
> it used in several ways:
> - in remote_detach_1 to distinguish between a process that is being
> detached-on-fork vs. just detached. In the fork case we don't want
> to mourn the process because we want to keep the inferior around in
> case the user decides to run the inferior again.
> - to record the child pid for the expected follow_fork request.
> - to find the parent in follow_fork (and later, elsewhere).
>
> - we also store the waitstatus in a new lwp_info member, 'waitstatus',
> which is used in controlling the reporting of the event in
> linux_wait_1. We cannot re-use pending_follow for this because we
> need to mark this one with 'ignored' status as part of the
> linux_wait_1 procedure to stop event processing, and we need to keep
> pending_follow intact for use later on. We will also need this later
> on for exec event handling, where pending_follow makes no sense.
>
> - handle_extended_wait is given a return value, denoting whether the
> handled event should be reported to GDB. Previously it had only
> handled clone events, which were never reported.
>
> * using a new predicate to control handling of the fork event (and
> eventually all extended events) in linux_wait_1. The predicate,
> extended_event_reported, checks a target_waitstatus.kind for an
> extended ptrace event.
>
> * implementing a new RSP 'T' Stop Reply Packet stop reason: "fork", in
> gdbserver/remote-utils.c and remote.c.
>
> * implementing new target and RSP support for target_follow_fork with
> target extended-remote. (The RSP components were actually defined in
> patch 4, but they see their first use here).
>
> - extended_remote target routine extended_remote_follow_fork
>
> - RSP packet vFollowFork
>
> - in gdbserver struct target_ops, add functions linux_supports_follow_fork
> and linux_follow_fork. The linux_follow_fork routine mimics the
> implementation of linux-nat.c:linux_child_follow_fork, but the data
> structures in use prevented turning this into a common function for now.
>
> Tested on x64 Ubuntu Lucid, native, remote, extended-remote.
>
> Thanks
> --Don
>
> gdb/
> 2014-08-20 Don Breazeal <donb@codesourcery.com>
>
> * remote.c (remote_detach_1): Add target_ops argument, handle
> detach-on-fork.
> (remote_detach, extended_remote_detach): Call remote_detach_1
> with target_ops argument.
> (remote_parse_stop_reply): Handle new RSP stop reason "fork" in
> 'T' stop reply packet.
> (extended_remote_follow_fork): Implement follow-fork support.
> (remote_pid_to_str): Print process.
> (_initialize_remote): Call add_packet_config_cmd for new RSP packet.
>
> gdb/gdbserver/
> 2014-08-20 Don Breazeal <donb@codesourcery.com>
>
> * gdbthread.h (struct thread_info) <pending_follow>: New member.
> * linux-low.c (handle_extended_wait): Change function type from
> void to int, handle PTRACE_EVENT_FORK, call internal_error.
> (is_parent_callback): New function.
> (linux_follow_fork): New function.
> (linux_low_filter_event): Handle return value from
> handle_extended_wait.
> (extended_event_reported): New function.
> (linux_write_memory): Add pid to debug print.
> (linux_target_ops) <follow_fork>: Initialize new member.
> (initialize_low): Add PTRACE_O_TRACEFORK option.
> * linux-low.h (struct lwp_info) <waitstatus>: New member.
> * lynx-low.c (lynx_target_ops) <follow_fork>: Initialize new member.
> * nto-low.c (nto_target_ops) <follow_fork>: Initialize new member.
> * remote-utils.c (prepare_resume_reply): New RSP stop reason "fork"
> for 'T' stop reply.
> * server.c (handle_v_follow_fork): New function.
> (handle_v_requests): Handle vFollowFork packet, call
> handle_v_follow_fork.
> * spu-low.c (spu_target_ops) <follow_fork>: Initialize new member.
> * target.h (struct target_ops) <follow_fork>: New member.
> (target_follow_fork): Define macro.
> * win32-low.c (win32_target_ops) <follow_fork>: Initialize new member.
>

---
gdb/gdbserver/gdbthread.h | 5 +
gdb/gdbserver/linux-low.c | 207
+++++++++++++++++++++++++++++++++++++++---
gdb/gdbserver/linux-low.h | 5 +
gdb/gdbserver/lynx-low.c | 1 +
gdb/gdbserver/nto-low.c | 1 +
gdb/gdbserver/remote-utils.c | 14 +++-
gdb/gdbserver/server.c | 41 ++++++++
gdb/gdbserver/spu-low.c | 1 +
gdb/gdbserver/target.h | 13 +++
gdb/gdbserver/win32-low.c | 1 +
gdb/remote.c | 80 ++++++++++++----
11 files changed, 334 insertions(+), 35 deletions(-)

diff --git a/gdb/gdbserver/gdbthread.h b/gdb/gdbserver/gdbthread.h
index 8290ec1..3d003b9 100644
--- a/gdb/gdbserver/gdbthread.h
+++ b/gdb/gdbserver/gdbthread.h
@@ -41,6 +41,11 @@ struct thread_info
/* True if LAST_STATUS hasn't been reported to GDB yet. */
int status_pending_p;

+ /* This is used to remember when a fork or vfork event was caught by
+ a catchpoint, and thus the event is to be followed at the next
+ resume of the thread, and not immediately. */
+ struct target_waitstatus pending_follow;
+
/* Given `while-stepping', a thread may be collecting data for more
than one tracepoint simultaneously. E.g.:

diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 045d507..56993bd 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -20,6 +20,7 @@
#include "linux-low.h"
#include "nat/linux-osdata.h"
#include "agent.h"
+#include "tdesc.h"

#include "nat/linux-nat.h"
#include "nat/linux-waitpid.h"
@@ -368,17 +369,17 @@ linux_add_process (int pid, int attached)
}

/* Handle a GNU/Linux extended wait response. If we see a clone
- event, we need to add the new LWP to our list (and not report the
- trap to higher layers). */
+ event, we need to add the new LWP to our list (and return 0 so as
+ not to report the trap to higher layers). */

-static void
+static int
handle_extended_wait (struct lwp_info *event_child, int wstat)
{
int event = linux_ptrace_get_extended_event (wstat);
struct thread_info *event_thr = get_lwp_thread (event_child);
struct lwp_info *new_lwp;

- if (event == PTRACE_EVENT_CLONE)
+ if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_CLONE)
{
ptid_t ptid;
unsigned long new_pid;
@@ -403,6 +404,56 @@ handle_extended_wait (struct lwp_info *event_child,
int wstat)
warning ("wait returned unexpected status 0x%x", status);
}

+ if (event == PTRACE_EVENT_FORK)
+ {
+ struct process_info *parent_proc;
+ struct process_info *child_proc;
+ struct lwp_info *child_lwp;
+ struct target_desc *tdesc;
+
+ ptid = ptid_build (new_pid, new_pid, 0);
+
+ if (debug_threads)
+ {
+ debug_printf ("HEW: Got fork event "
+ "from LWP %ld, new child is %d\n",
+ ptid_get_lwp (ptid_of (event_thr)),
+ ptid_get_pid (ptid));
+ }
+
+ /* Add the new process to the tables and clone the breakpoint
+ lists of the parent. We need to do this even if the new process
+ will be detached, since we will need the process object and the
+ breakpoints to remove any breakpoints from memory when we
+ detach, and the host side will access registers. */
+ child_proc = linux_add_process (new_pid, 0);
+ gdb_assert (child_proc != NULL);
+ child_lwp = add_lwp (ptid);
+ gdb_assert (child_lwp != NULL);
+ child_lwp->stopped = 1;
+ parent_proc = get_thread_process (current_inferior);
+ child_proc->attached = parent_proc->attached;
+ clone_all_breakpoints (&child_proc->breakpoints,
+ &child_proc->raw_breakpoints,
+ parent_proc->breakpoints);
+
+ tdesc = xmalloc (sizeof (struct target_desc));
+ copy_target_description (tdesc, parent_proc->tdesc);
+ child_proc->tdesc = tdesc;
+ child_lwp->must_set_ptrace_flags = 1;
+
+ /* Save fork info for target processing. */
+ current_inferior->pending_follow.kind = TARGET_WAITKIND_FORKED;
+ current_inferior->pending_follow.value.related_pid = ptid;
+
+ /* Save fork info for reporting to GDB. */
+ event_child->waitstatus.kind = TARGET_WAITKIND_FORKED;
+ event_child->waitstatus.value.related_pid = ptid;
+
+ /* Report the event. */
+ return 0;
+ }
+
if (debug_threads)
debug_printf ("HEW: Got clone event "
"from LWP %ld, new child is LWP %ld\n",
@@ -452,7 +503,12 @@ handle_extended_wait (struct lwp_info *event_child,
int wstat)
threads, it will have a pending SIGSTOP; we may as well
collect it now. */
linux_resume_one_lwp (event_child, event_child->stepping, 0, NULL);
+
+ /* Don't report the event. */
+ return 1;
}
+
+ internal_error (__FILE__, __LINE__, _("unknown ptrace event %d"), event);
}

/* Return the PC as read from the regcache of LWP, without any
@@ -1177,6 +1233,97 @@ linux_detach (int pid)
return 0;
}

+/* Callback used to find the parent process of a fork. */
+
+static int
+is_parent_callback (struct inferior_list_entry *entry, void *ignore)
+{
+ struct thread_info *thread = (struct thread_info *) entry;
+
+ if (thread->pending_follow.kind == TARGET_WAITKIND_FORKED
+ || thread->pending_follow.kind == TARGET_WAITKIND_VFORKED)
+ return 1;
+
+ return 0;
+}
+
+/* Handle a fork in the inferior process. Mainly this consists of
+ handling the case where we are detaching the new child process by
+ cleaning up its state so it can proceed. Note that if we are
+ detaching the parent process, GDB has already done that via
+ target_detach. */
+
+static int
+linux_follow_fork (int follow_child, int detach_fork)
+{
+ struct inferior_list_entry *parent_inf;
+ struct thread_info *parent_thread;
+
+ parent_inf = find_inferior (&all_threads, is_parent_callback, NULL);
+
+ /* If we can't find the parent, we are following the child and the
+ parent has already been detached. Nothing to do, so return OK. */
+ if (parent_inf == NULL)
+ return 0;
+
+ parent_thread = (struct thread_info *)parent_inf;
+ parent_thread->pending_follow.kind = TARGET_WAITKIND_IGNORE;
+
+ if (!follow_child)
+ {
+ if (detach_fork)
+ {
+ int status = W_STOPCODE (0);
+ ptid_t child_ptid = parent_thread->pending_follow.value.related_pid;
+ pid_t child_pid = ptid_get_pid (child_ptid);
+ struct lwp_info *child_lwp = find_lwp_pid (child_ptid);
+
+ if (the_low_target.prepare_to_resume != NULL)
+ the_low_target.prepare_to_resume (child_lwp);
+
+ /* When debugging an inferior in an architecture that supports
+ hardware single stepping on a kernel without commit
+ 6580807da14c423f0d0a708108e6df6ebc8bc83d, the vfork child
+ process starts with the TIF_SINGLESTEP/X86_EFLAGS_TF bits
+ set if the parent process had them set.
+ To work around this, single step the child process
+ once before detaching to clear the flags. */
+
+ if (can_hardware_single_step ())
+ {
+ linux_ptrace_disable_options (child_pid);
+ if (ptrace (PTRACE_SINGLESTEP, child_pid, 0, 0) < 0)
+ perror_with_name (_("Couldn't do single step"));
+ if (my_waitpid (child_pid, &status, 0) < 0)
+ perror_with_name (_("Couldn't wait vfork process"));
+ }
+
+ if (WIFSTOPPED (status))
+ {
+ int signo;
+ struct process_info *child_proc;
+
+ signo = WSTOPSIG (status);
+ if (signo == SIGSTOP
+ || (signo != 0
+ && !pass_signals[gdb_signal_from_host (signo)]))
+ signo = 0;
+
+ ptrace (PTRACE_DETACH, child_pid, 0, signo);
+
+ /* Deallocate all process-related storage. */
+ child_proc = find_process_pid (child_pid);
+ if (child_proc != NULL)
+ the_target->mourn (child_proc);
+
+ current_inferior = NULL;
+ }
+ }
+ }
+
+ return 0;
+}
+
/* Remove all LWPs that belong to process PROC from the lwp list. */

static int
@@ -1875,8 +2022,10 @@ linux_low_filter_event (ptid_t filter_ptid, int
lwpid, int wstat)
if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP
&& linux_is_extended_waitstatus (wstat))
{
- handle_extended_wait (child, wstat);
- return NULL;
+ if (handle_extended_wait (child, wstat))
+ return NULL;
+ else
+ return child;
}

if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGSTOP
@@ -2477,6 +2626,19 @@ linux_stabilize_threads (void)
}
}

+/* Return non-zero if WAITSTATUS reflects an extended linux
+ event that gdbserver supports. Otherwise, return zero. */
+
+static int
+extended_event_reported (const struct target_waitstatus *waitstatus)
+{
+
+ if (waitstatus == NULL)
+ return 0;
+
+ return (waitstatus->kind == TARGET_WAITKIND_FORKED);
+}
+
/* Wait for process, returns status. */

static ptid_t
@@ -2840,7 +3002,8 @@ retry:
&& !bp_explains_trap && !trace_event)
|| (gdb_breakpoint_here (event_child->stop_pc)
&& gdb_condition_true_at_breakpoint (event_child->stop_pc)
- && gdb_no_commands_at_breakpoint (event_child->stop_pc)));
+ && gdb_no_commands_at_breakpoint (event_child->stop_pc))
+ || extended_event_reported (&event_child->waitstatus));

run_breakpoint_commands (event_child->stop_pc);

@@ -2862,6 +3025,13 @@ retry:
paddress (event_child->stop_pc),
paddress (event_child->step_range_start),
paddress (event_child->step_range_end));
+ if (extended_event_reported (&event_child->waitstatus))
+ {
+ char *str = target_waitstatus_to_string (ourstatus);
+ debug_printf ("LWP %ld: extended event with waitstatus %s\n",
+ lwpid_of (get_lwp_thread (event_child)), str);
+ xfree (str);
+ }
}

/* We're not reporting this breakpoint to GDB, so apply the
@@ -2960,7 +3130,17 @@ retry:
unstop_all_lwps (1, event_child);
}

- ourstatus->kind = TARGET_WAITKIND_STOPPED;
+ if (extended_event_reported (&event_child->waitstatus))
+ {
+ /* If the reported event is a fork, vfork or exec, let GDB know. */
+ ourstatus->kind = event_child->waitstatus.kind;
+ ourstatus->value = event_child->waitstatus.value;
+
+ /* Reset the event child's waitstatus since we handled it
already. */
+ event_child->waitstatus.kind = TARGET_WAITKIND_IGNORE;
+ }
+ else
+ ourstatus->kind = TARGET_WAITKIND_STOPPED;

if (current_thread->last_resume_kind == resume_stop
&& WSTOPSIG (w) == SIGSTOP)
@@ -2977,7 +3157,7 @@ retry:
but, it stopped for other reasons. */
ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
}
- else
+ else if (ourstatus->kind == TARGET_WAITKIND_STOPPED)
{
ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
}
@@ -4769,8 +4949,8 @@ linux_write_memory (CORE_ADDR memaddr, const
unsigned char *myaddr, int len)
val = val & 0xffff;
else if (len == 3)
val = val & 0xffffff;
- debug_printf ("Writing %0*x to 0x%08lx\n", 2 * ((len < 4) ? len : 4),
- val, (long)memaddr);
+ debug_printf ("Writing %0*x to 0x%08lx in process %d\n",
+ 2 * ((len < 4) ? len : 4), val, (long)memaddr, pid);
}

/* Fill start and end extra bytes of buffer with existing memory
data. */
@@ -6065,6 +6245,7 @@ static struct target_ops linux_target_ops = {
linux_supports_follow_fork,
linux_supports_follow_exec,
linux_enable_extended_features,
+ linux_follow_fork,
linux_mourn,
linux_join,
linux_thread_alive,
@@ -6180,7 +6361,7 @@ initialize_low (void)

initialize_low_arch ();

- /* Placeholder to enable extended events. */
- linux_ptrace_set_requested_options (0);
+ /* Enable extended events. */
+ linux_ptrace_set_requested_options (PTRACE_O_TRACEFORK);
linux_ptrace_check_options ();
}
diff --git a/gdb/gdbserver/linux-low.h b/gdb/gdbserver/linux-low.h
index 4820929..a903430 100644
--- a/gdb/gdbserver/linux-low.h
+++ b/gdb/gdbserver/linux-low.h
@@ -266,6 +266,11 @@ struct lwp_info
status_pending). */
int dead;

+ /* If WAITSTATUS->KIND != TARGET_WAITKIND_IGNORE, the waitstatus for
+ this LWP's last event. This is used to maintain the current status
+ during event processing. */
+ struct target_waitstatus waitstatus;
+
/* When stopped is set, the last wait status recorded for this lwp. */
int last_status;

diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index d399e67..902302a 100644
--- a/gdb/gdbserver/lynx-low.c
+++ b/gdb/gdbserver/lynx-low.c
@@ -725,6 +725,7 @@ static struct target_ops lynx_target_ops = {
NULL, /* supports_follow_fork */
NULL, /* supports_follow_exec */
NULL, /* enable_extended_features */
+ NULL, /* follow_fork */
lynx_mourn,
lynx_join,
lynx_thread_alive,
diff --git a/gdb/gdbserver/nto-low.c b/gdb/gdbserver/nto-low.c
index 17f2a14..934657f 100644
--- a/gdb/gdbserver/nto-low.c
+++ b/gdb/gdbserver/nto-low.c
@@ -931,6 +931,7 @@ static struct target_ops nto_target_ops = {
NULL, /* supports_follow_fork */
NULL, /* supports_follow_exec */
NULL, /* enable_extended_features */
+ NULL, /* follow_fork */
nto_mourn,
NULL, /* nto_join */
nto_thread_alive,
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index 373fc15..e62b4b8 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1105,12 +1105,24 @@ prepare_resume_reply (char *buf, ptid_t ptid,
switch (status->kind)
{
case TARGET_WAITKIND_STOPPED:
+ case TARGET_WAITKIND_FORKED:
{
struct thread_info *saved_thread;
const char **regp;
struct regcache *regcache;

- sprintf (buf, "T%02x", status->value.sig);
+ if (status->kind == TARGET_WAITKIND_FORKED && multi_process)
+ {
+ enum gdb_signal signal = GDB_SIGNAL_TRAP;
+ const char *event = "fork";
+
+ sprintf (buf, "T%02x%s:p%x.%lx;", signal, event,
+ ptid_get_pid (status->value.related_pid),
+ ptid_get_lwp (status->value.related_pid));
+ }
+ else
+ sprintf (buf, "T%02x", status->value.sig);
+
buf += strlen (buf);

saved_thread = current_thread;
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index a13d2d5..d756c94 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -2558,6 +2558,29 @@ handle_v_kill (char *own_buf)
}
}

+/* Handle forked process. */
+
+static void
+handle_v_follow_fork (char *own_buf)
+{
+ int follow_child;
+ int detach_fork;
+ char *p = &own_buf[12];
+ int ret;
+
+ gdb_assert (extended_protocol);
+
+ follow_child = strtol (p, NULL, 16);
+ p = strchr (p, ';') + 1;
+ detach_fork = strtol (p, NULL, 16);
+
+ ret = target_follow_fork (follow_child, detach_fork);
+ if (ret == 0)
+ write_ok (own_buf);
+ else
+ write_enn (own_buf);
+}
+
/* Handle all of the extended 'v' packets. */
void
handle_v_requests (char *own_buf, int packet_len, int *new_packet_len)
@@ -2623,6 +2646,24 @@ handle_v_requests (char *own_buf, int packet_len,
int *new_packet_len)
return;
}

+ if (strncmp (own_buf, "vFollowFork;", 6) == 0)
+ {
+ if (!target_running ())
+ {
+ fprintf (stderr, "No process to follow\n");
+ write_enn (own_buf);
+ return;
+ }
+ if (!extended_protocol || !multi_process)
+ {
+ fprintf (stderr, "Target doesn't support follow-fork\n");
+ write_enn (own_buf);
+ return;
+ }
+ handle_v_follow_fork (own_buf);
+ return;
+ }
+
if (handle_notif_ack (own_buf, packet_len))
return;

diff --git a/gdb/gdbserver/spu-low.c b/gdb/gdbserver/spu-low.c
index d4c5429..9becef6 100644
--- a/gdb/gdbserver/spu-low.c
+++ b/gdb/gdbserver/spu-low.c
@@ -644,6 +644,7 @@ static struct target_ops spu_target_ops = {
NULL, /* supports_follow_fork */
NULL, /* supports_follow_exec */
NULL, /* enable_extended_features */
+ NULL, /* follow_fork */
spu_mourn,
spu_join,
spu_thread_alive,
diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
index abce9b3..03bfe76 100644
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -105,6 +105,11 @@ struct target_ops

void (*enable_extended_features) (void);

+ /* Handle a call to fork as specified by follow-fork-mode and
+ detach-on-fork. */
+
+ int (*follow_fork) (int follow_child, int detach_fork);
+
/* The inferior process has died. Do what is right. */

void (*mourn) (struct process_info *proc);
@@ -438,6 +443,14 @@ int kill_inferior (int);
(the_target->supports_multi_process ? \
(*the_target->supports_multi_process) () : 0)

+#define target_supports_follow_fork() \
+ (the_target->supports_follow_fork ? \
+ (*the_target->supports_follow_fork) () : 0)
+
+#define target_follow_fork(follow_child, detach_fork) \
+ (the_target->follow_fork ? \
+ (*the_target->follow_fork) (follow_child, detach_fork) : 0)
+
#define target_process_qsupported(query) \
do \
{ \
diff --git a/gdb/gdbserver/win32-low.c b/gdb/gdbserver/win32-low.c
index 42cbf32..944d06b 100644
--- a/gdb/gdbserver/win32-low.c
+++ b/gdb/gdbserver/win32-low.c
@@ -1770,6 +1770,7 @@ static struct target_ops win32_target_ops = {
NULL, /* supports_follow_fork */
NULL, /* supports_follow_exec */
NULL, /* enable_extended_features */
+ NULL, /* follow_fork */
win32_mourn,
win32_join,
win32_thread_alive,
diff --git a/gdb/remote.c b/gdb/remote.c
index f3076b3..19f3efb 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -24,6 +24,7 @@
#include <fcntl.h>
#include "inferior.h"
#include "infrun.h"
+#include "inf-child.h"
#include "bfd.h"
#include "symfile.h"
#include "exceptions.h"
@@ -3916,6 +3917,8 @@ static const struct protocol_feature
remote_protocol_features[] = {
PACKET_QStartNoAckMode },
{ "multiprocess", PACKET_DISABLE, remote_supported_packet,
PACKET_multiprocess_feature },
+ { "vFollowFork", PACKET_DISABLE, remote_supported_packet,
+ PACKET_vFollowFork },
{ "QNonStop", PACKET_DISABLE, remote_supported_packet, PACKET_QNonStop },
{ "qXfer:siginfo:read", PACKET_DISABLE, remote_supported_packet,
PACKET_qXfer_siginfo_read },
@@ -4333,10 +4336,12 @@ remote_open_1 (const char *name, int from_tty,
die when it hits one. */

static void
-remote_detach_1 (const char *args, int from_tty, int extended)
+remote_detach_1 (struct target_ops *ops, const char *args,
+ int from_tty, int extended)
{
int pid = ptid_get_pid (inferior_ptid);
struct remote_state *rs = get_remote_state ();
+ struct thread_info *tp = first_thread_of_process (pid);

if (args)
error (_("Argument given to \"detach\" when remotely debugging."));
@@ -4373,19 +4378,28 @@ remote_detach_1 (const char *args, int from_tty,
int extended)
if (from_tty && !extended)
puts_filtered (_("Ending remote debugging.\n"));

- target_mourn_inferior ();
+ /* If doing detach-on-fork, we don't mourn, because that will delete
+ breakpoints that should be available for the child. */
+ if (tp->pending_follow.kind != TARGET_WAITKIND_FORKED)
+ target_mourn_inferior ();
+ else
+ {
+ inferior_ptid = null_ptid;
+ detach_inferior (pid);
+ inf_child_maybe_unpush_target (ops);
+ }
}

static void
remote_detach (struct target_ops *ops, const char *args, int from_tty)
{
- remote_detach_1 (args, from_tty, 0);
+ remote_detach_1 (ops, args, from_tty, 0);
}

static void
extended_remote_detach (struct target_ops *ops, const char *args, int
from_tty)
{
- remote_detach_1 (args, from_tty, 1);
+ remote_detach_1 (ops, args, from_tty, 1);
}

/* Same as remote_detach, but don't send the "D" packet; just
disconnect. */
@@ -5481,7 +5495,8 @@ remote_parse_stop_reply (char *buf, struct
stop_reply *event)
as a register number. */

if (strncmp (p, "awatch", strlen("awatch")) != 0
- && strncmp (p, "core", strlen ("core") != 0))
+ && strncmp (p, "core", strlen ("core") != 0)
+ && strncmp (p, "fork", strlen ("fork") != 0))
{
/* Read the ``P'' register number. */
pnum = strtol (p, &p_temp, 16);
@@ -5533,6 +5548,11 @@ Packet: '%s'\n"),
p = unpack_varlen_hex (++p1, &c);
event->core = c;
}
+ else if (strncmp (p, "fork", p1 - p) == 0)
+ {
+ event->ws.value.related_pid = read_ptid (++p1, &p);
+ event->ws.kind = TARGET_WAITKIND_FORKED;
+ }
else
{
/* Silently skip unknown optional info. */
@@ -7820,6 +7840,32 @@ extended_remote_kill (struct target_ops *ops)
target_mourn_inferior ();
}

+/* Target routine for follow-fork. */
+
+static int
+extended_remote_follow_fork (struct target_ops *ops, int follow_child,
+ int detach_fork)
+{
+ struct remote_state *rs = get_remote_state ();
+
+ if (extended_remote_feature_supported (FORK_EVENT))
+ {
+ char *p = rs->buf;
+ char *endbuf = rs->buf + get_remote_packet_size ();
+
+ xsnprintf (rs->buf, get_remote_packet_size (), "vFollowFork;%d;%d",
+ follow_child, detach_fork);
+
+ putpkt (rs->buf);
+ getpkt (&rs->buf, &rs->buf_size, 0);
+
+ if (rs->buf[0] == 'E')
+ return 1;
+ }
+
+ return 0;
+}
+
static void
remote_mourn (struct target_ops *ops)
{
@@ -7836,20 +7882,6 @@ remote_mourn_1 (struct target_ops *target)
generic_mourn_inferior ();
}

-/* Target follow-fork function for extended-remote targets. */
-
-static int
-extended_remote_follow_fork (struct target_ops *target, int follow_child,
- int detach_fork)
-{
- if (extended_remote_feature_supported (FORK_EVENT))
- {
- /* FIXME: Implement follow-fork here. */
- return -1;
- }
- return 0;
-}
-
static void
extended_remote_mourn_1 (struct target_ops *target)
{
@@ -9356,8 +9388,11 @@ remote_pid_to_str (struct target_ops *ops, ptid_t
ptid)
if (ptid_equal (magic_null_ptid, ptid))
xsnprintf (buf, sizeof buf, "Thread ");
else if (rs->extended && remote_multi_process_p (rs))
- xsnprintf (buf, sizeof buf, "Thread %d.%ld",
- ptid_get_pid (ptid), ptid_get_lwp (ptid));
+ if (ptid_get_lwp (ptid) == 0)
+ return normal_pid_to_str (ptid);
+ else
+ xsnprintf (buf, sizeof buf, "Thread %d.%ld",
+ ptid_get_pid (ptid), ptid_get_lwp (ptid));
else
xsnprintf (buf, sizeof buf, "Thread %ld",
ptid_get_lwp (ptid));
@@ -12184,6 +12219,9 @@ Show the maximum size of the address (in bits)
in a memory packet."), NULL,
add_packet_config_cmd (&remote_protocol_packets[PACKET_vKill],
"vKill", "kill", 0);

+ add_packet_config_cmd (&remote_protocol_packets[PACKET_vFollowFork],
+ "vFollowFork", "follow-fork", 0);
+
add_packet_config_cmd (&remote_protocol_packets[PACKET_qAttached],
"qAttached", "query-attached", 0);

--
1.7.0.4