diff mbox

[01/16,v2] Refactor native follow-fork

Message ID 54299FF8.7000205@codesourcery.com
State New
Headers show

Commit Message

Don Breazeal Sept. 29, 2014, 6:07 p.m. UTC
On 9/26/2014 11:13 AM, Pedro Alves wrote:
> On 09/12/2014 05:50 PM, Breazeal, Don wrote:
>> On 9/9/2014 4:09 AM, Pedro Alves wrote:
>>> On 09/09/2014 12:54 AM, Breazeal, Don wrote:
> 
>>> I'd rather not.  That'll just add more technical debt.  :-)  E.g.,
>>> if we can't model this on the few native targets we have, then that'd
>>> indicate that remote debugging against one of those targets (when
>>> we get to gdb/gdbserver parity), wouldn't be correctly modelled, as
>>> you'd be installing those methods in remote.c too.  Plus, if we have
>>> target_follow_fork_inferior as an extra method in addition
>>> to target_follow_fork_inferior, and both are always called in
>>> succession, then we might as well _not_ introduce a new target hook,
>>> and just call infrun_follow_fork_inferior from the
>>> top of linux-nat.c:linux_child_follow_fork, and the top of whatever
>>> other target's to_follow_fork method that wants to use it.
>>
>> I've made modifications to inf_ttrace_follow_fork, inf_ttrace_wait,
>> and inf_ptrace_follow_fork in the included patch that I think should
>> allow them to work with follow_fork_inferior.  Some other adjustments
>> were necessary in inf-ttrace.c.  Some of the changes weren't strictly
>> necessary to get things working with follow_fork_inferior, but it
>> seemed appropriate to make all the implementations more consistent.
> 
> Thanks.
> 
>>>> The changes to inf_ptrace_follow_fork to get it to work with
>>>> follow_fork_inferior are straightforward.  Making those changes to
>>>> inf_ttrace_follow_fork is problematic, primarily because it handles the
>>>> moral equivalent of the vfork-done event differently.
>>>
>>> Can you summarize the differences ?
>>
>> HP-UX apparently delivers a TTEVT_VFORK event for the parent inferior
>> where Linux would report PTRACE_EVENT_VFORK_DONE -- when the child
>> process has either execd or exited.  inf_ttrace_follow_fork was handling
>> the parent VFORK event in-line, where the Linux implementation expects
>> a TARGET_WAITKIND_VFORK_DONE event to be reported so it can be handled
>> in the generic event code.
>>
> 
> Right.  Linux also used to handle that in-line, before 6c95b8df.
> 
>> I've included annotated versions of the original implementations of
>> inf_ttrace_follow_fork and inf-ptrace_follow_fork below for reference,
>> to explain how I made decisions on the changes.  Each annotation is
>> a comment beginning with "BLOCK n".  If you want to skip past all of
>> that you can just search for "Don". :-)
> 
> Excellent.  That sure made it easier.
> 
> 
>>>
>>
>> My assumptions about how HP-UX ttrace events work:
>> - FORK:
>>   - TTEVT_FORK is reported for both the parent and child processes.
>>     The event can be reported for the parent or child in any order.
>>
>> - VFORK:
>>   - TTEVT_VFORK is reported for both the parent and child
>>     processes.
>>   - TTEVT_VFORK is reported for the child first.  It is reported
>>     for the parent when the vfork is "done" as with the Linux
>>     PTRACE_EVENT_VFORK_DONE event, meaning that the parent has
>>     called exec or exited.  See this comment in inf_ttrace_follow_fork:
>>
>>   /* Wait till we get the TTEVT_VFORK event in the parent.
>>      This indicates that the child has called exec(3) or has
>>      exited and that the parent is ready to be traced again.  */
>>
>>     The online HP-UX ttrace documentation doesn't really make this
>>     ordering explicit, but it doesn't contradict the implementation.
> 
> Yeah, I can't imagine any way a TTEVT_VFORK could ever be reported to
> the parent first.  When the parent vforks, it blocks until the child
> execs or exits.  And the child won't do that until GDB resumes
> the child after handling the TTEVT_VFORK that is reported for the
> child.  So the kernel must always report the child's TTEVT_VFORK first.
> 
> 
>>    if (follow_child)
>>      {
>>        struct thread_info *ti;
>> @@ -533,17 +423,22 @@ inf_ttrace_follow_fork (struct target_ops *ops,
>> int follow_child,
>>        inf_ttrace_num_lwps = 1;
>>        inf_ttrace_num_lwps_in_syscall = 0;
>>
>> -      /* Delete parent.  */
>> -      delete_thread_silent (ptid_build (pid, lwpid, 0));
>> -      detach_inferior (pid);
>> -
>> -      /* Add child thread.  inferior_ptid was already set above.  */
>> -      ti = add_thread_silent (inferior_ptid);
>> +      ti = find_thread_ptid (inferior_ptid);
>> +      gdb_assert (ti != NULL);
> 
> Replace these with:
> 
>          ti = inferior_thread ();

Done.

> 
>>        ti->private =
>>  	xmalloc (sizeof (struct inf_ttrace_private_thread_info));
>>        memset (ti->private, 0,
>>  	      sizeof (struct inf_ttrace_private_thread_info));
>>      }
>> +  else
>> +    {
>> +      pid_t child_pid;
>> +
>> +      /* Following parent.  Detach child now.  */
>> +      child_pid = ptid_get_pid (tp->pending_follow.value.related_pid);
>> +      if (ttrace (TT_PROC_DETACH, child_pid, 0, 0, 0, 0) == -1)
>> +	perror_with_name (("ttrace"));
>> +    }
>>
>>    return 0;
>>  }
>> @@ -661,7 +556,6 @@ inf_ttrace_create_inferior (struct target_ops *ops,
>> char *exec_file,
>>    gdb_assert (inf_ttrace_num_lwps_in_syscall == 0);
>>    gdb_assert (inf_ttrace_page_dict.count == 0);
>>    gdb_assert (inf_ttrace_reenable_page_protections == 0);
>> -  gdb_assert (inf_ttrace_vfork_ppid == -1);
>>
>>    pid = fork_inferior (exec_file, allargs, env, inf_ttrace_me, NULL,
>>  		       inf_ttrace_prepare, NULL, NULL);
>> @@ -772,7 +666,6 @@ inf_ttrace_attach (struct target_ops *ops, const
>> char *args, int from_tty)
>>
>>    gdb_assert (inf_ttrace_num_lwps == 0);
>>    gdb_assert (inf_ttrace_num_lwps_in_syscall == 0);
>> -  gdb_assert (inf_ttrace_vfork_ppid == -1);
>>
>>    if (ttrace (TT_PROC_ATTACH, pid, 0, TT_KILL_ON_EXIT, TT_VERSION, 0)
>> == -1)
>>      perror_with_name (("ttrace"));
>> @@ -822,11 +715,12 @@ inf_ttrace_detach (struct target_ops *ops, const
>> char *args, int from_tty)
>>    if (ttrace (TT_PROC_DETACH, pid, 0, 0, sig, 0) == -1)
>>      perror_with_name (("ttrace"));
>>
>> -  if (inf_ttrace_vfork_ppid != -1)
>> +  if (current_inferior ()->vfork_parent != NULL)
>>      {
>> -      if (ttrace (TT_PROC_DETACH, inf_ttrace_vfork_ppid, 0, 0, 0, 0) == -1)
>> +      pid_t ppid = current_inferior ()->vfork_parent->pid;
>> +      if (ttrace (TT_PROC_DETACH, ppid, 0, 0, 0, 0) == -1)
>>  	perror_with_name (("ttrace"));
>> -      inf_ttrace_vfork_ppid = -1;
>> +      detach_inferior (ppid);
>>      }
> 
> I think we should just delete this block instead.
> We shouldn't be blindly detaching from the parent here.
> The user may well still want to debug it.  The case of the
> user doing "detach" on the child when the parent is waiting
> for the the vfork-done should be handled by common code.
> (and we should be going through the whole target_detach).
> 

Done.

>>
>>    inf_ttrace_num_lwps = 0;
>> @@ -850,11 +744,12 @@ inf_ttrace_kill (struct target_ops *ops)
>>      perror_with_name (("ttrace"));
>>    /* ??? Is it necessary to call ttrace_wait() here?  */
>>
>> -  if (inf_ttrace_vfork_ppid != -1)
>> +  if (current_inferior ()->vfork_parent != NULL)
>>      {
>> -      if (ttrace (TT_PROC_DETACH, inf_ttrace_vfork_ppid, 0, 0, 0, 0) == -1)
>> +      pid_t ppid = current_inferior ()->vfork_parent->pid;
>> +      if (ttrace (TT_PROC_DETACH, ppid, 0, 0, 0, 0) == -1)
>>  	perror_with_name (("ttrace"));
>> -      inf_ttrace_vfork_ppid = -1;
>> +      detach_inferior (ppid);
>>      }
> 
> This too.
> 
Done.

>>
>>    target_mourn_inferior ();
>> @@ -967,20 +862,6 @@ inf_ttrace_wait (struct target_ops *ops,
>>        if (ttrace_wait (pid, lwpid, TTRACE_WAITOK, &tts, sizeof tts) == -1)
>>  	perror_with_name (("ttrace_wait"));
>>
>> -      if (tts.tts_event == TTEVT_VFORK && tts.tts_u.tts_fork.tts_isparent)
>> -	{
>> -	  if (inf_ttrace_vfork_ppid != -1)
>> -	    {
>> -	      gdb_assert (inf_ttrace_vfork_ppid == tts.tts_pid);
>> -
>> -	      if (ttrace (TT_PROC_DETACH, tts.tts_pid, 0, 0, 0, 0) == -1)
>> -		perror_with_name (("ttrace"));
>> -	      inf_ttrace_vfork_ppid = -1;
>> -	    }
>> -
>> -	  tts.tts_event = TTEVT_NONE;
>> -	}
>> -
>>        clear_sigint_trap ();
>>      }
>>    while (tts.tts_event == TTEVT_NONE);
>> @@ -1075,17 +956,23 @@ inf_ttrace_wait (struct target_ops *ops,
>>        break;
>>
>>      case TTEVT_VFORK:
>> -      gdb_assert (!tts.tts_u.tts_fork.tts_isparent);
>> -
>> -      related_ptid = ptid_build (tts.tts_u.tts_fork.tts_fpid,
>> -				 tts.tts_u.tts_fork.tts_flwpid, 0);
>> +      if (tts.tts_u.tts_fork.tts_isparent)
>> +	{
>> +	  if (current_inferior ()->waiting_fork_vfork_done)
> 
> I don't think we should have this waiting_fork_vfork_done check here.
> This should always be a TARGET_WAITKIND_VFORK_DONE?

You are right, at best this check was redundant.  Removed it.

> 
>> +	    ourstatus->kind = TARGET_WAITKIND_VFORK_DONE;
>> +	}
>> +      else
>> +	{
>> +	  related_ptid = ptid_build (tts.tts_u.tts_fork.tts_fpid,
>> +				     tts.tts_u.tts_fork.tts_flwpid, 0);
>>
>> -      ourstatus->kind = TARGET_WAITKIND_VFORKED;
>> -      ourstatus->value.related_pid = related_ptid;
>> +	  ourstatus->kind = TARGET_WAITKIND_VFORKED;
>> +	  ourstatus->value.related_pid = related_ptid;
>>
>> -      /* HACK: To avoid touching the parent during the vfork, switch
>> -	 away from it.  */
>> -      inferior_ptid = ptid;
>> +	  /* HACK: To avoid touching the parent during the vfork, switch
>> +	     away from it.  */
>> +	  inferior_ptid = ptid;
> 
> The core is now aware of the vfork parent inferior.  Just delete
> this hack.

Done.

> 
>> +	}
>>        break;
>>
>>      case TTEVT_LWP_CREATE:
>> diff --git a/gdb/infrun.c b/gdb/infrun.c
>> index c18267f..af9cbf8 100644
>> --- a/gdb/infrun.c
>> +++ b/gdb/infrun.c
>> @@ -60,6 +60,7 @@
>>  #include "completer.h"
>>  #include "target-descriptions.h"
>>  #include "target-dcache.h"
>> +#include "terminal.h"
>>
>>  /* Prototypes for local functions */
>>
>> @@ -79,6 +80,10 @@ static int restore_selected_frame (void *);
>>
>>  static int follow_fork (void);
>>
>> +static int follow_fork_inferior (int follow_child, int detach_fork);
>> +
>> +static void follow_inferior_reset_breakpoints (void);
>> +
>>  static void set_schedlock_func (char *args, int from_tty,
>>  				struct cmd_list_element *c);
>>
>> @@ -486,9 +491,11 @@ follow_fork (void)
>>  	parent = inferior_ptid;
>>  	child = tp->pending_follow.value.related_pid;
>>
>> -	/* Tell the target to do whatever is necessary to follow
>> -	   either parent or child.  */
>> -	if (target_follow_fork (follow_child, detach_fork))
>> +	/* Set up inferior(s) as specified by the caller, and tell the
>> +	   target to do whatever is necessary to follow either parent
>> +	   or child.  */
>> +	if (follow_fork_inferior (follow_child, detach_fork)
>> +	    || target_follow_fork (follow_child, detach_fork))
> 
> I don't think we should ever call follow_fork_inferior without
> calling target_follow_fork, right?  I think it'd be clearer if
> we tail called target_follow_fork inside follow_fork_inferior.

Done.

> 
>>  	  {
>>  	    /* Target refused to follow, or there's some other reason
>>  	       we shouldn't resume.  */
>> @@ -560,7 +567,242 @@ follow_fork (void)
>>    return should_resume;
>>  }
>>
>> -void
>> +/* Handle changes to the inferior list based on the type of fork,
>> +   which process is being followed, and whether the other process
>> +   should be detached.  On entry inferior_ptid must be the ptid of
>> +   the fork parent.  At return inferior_ptid is the ptid of the
>> +   followed inferior.  */
>> +
>> +int
>> +follow_fork_inferior (int follow_child, int detach_fork)
>> +{
>> +  int has_vforked;
>> +  int parent_pid, child_pid;
>> +
>> +  has_vforked = (inferior_thread ()->pending_follow.kind
>> +		 == TARGET_WAITKIND_VFORKED);
>> +  parent_pid = ptid_get_lwp (inferior_ptid);
>> +  if (parent_pid == 0)
>> +    parent_pid = ptid_get_pid (inferior_ptid);
>> +  child_pid
>> +    = ptid_get_pid (inferior_thread ()->pending_follow.value.related_pid);
>> +
>> +  if (has_vforked
>> +      && !non_stop /* Non-stop always resumes both branches.  */
>> +      && (!target_is_async_p () || sync_execution)
>> +      && !(follow_child || detach_fork || sched_multi))
>> +    {
>> +      /* The parent stays blocked inside the vfork syscall until the
>> +	 child execs or exits.  If we don't let the child run, then
>> +	 the parent stays blocked.  If we're telling the parent to run
>> +	 in the foreground, the user will not be able to ctrl-c to get
>> +	 back the terminal, effectively hanging the debug session.  */
>> +      fprintf_filtered (gdb_stderr, _("\
>> +Can not resume the parent process over vfork in the foreground while\n\
>> +holding the child stopped.  Try \"set detach-on-fork\" or \
>> +\"set schedule-multiple\".\n"));
>> +      /* FIXME output string > 80 columns.  */
>> +      return 1;
>> +    }
>> +
> 
> Moving this to common code isn't strictly correct, as we'd probably
> be able to interrupt the vfork parent in this case when remote
> debugging, or not sharing the terminal with the inferior.  But let's
> put this here for now.
> 
> 
>> +	  else
>> +	    {
>> +	      child_inf->aspace = new_address_space ();
>> +	      child_inf->pspace = add_program_space (child_inf->aspace);
>> +	      child_inf->removable = 1;
>> +	      set_current_program_space (child_inf->pspace);
>> +	      clone_program_space (child_inf->pspace, parent_inf->pspace);
>> +
>> +	      /* Let the shared library layer (solib-svr4) learn about
> 
> It's not always solib-svr4, so make that e.g., "e.g., solib-svr4" now.

Done.

> 
>> +      else
>> +	{
>> +	  child_inf->aspace = new_address_space ();
>> +	  child_inf->pspace = add_program_space (child_inf->aspace);
>> +	  child_inf->removable = 1;
>> +	  child_inf->symfile_flags = SYMFILE_NO_READ;
>> +	  set_current_program_space (child_inf->pspace);
>> +	  clone_program_space (child_inf->pspace, parent_pspace);
>> +
>> +	  /* Let the shared library layer (solib-svr4) learn about
> 
> Likewise.

Done.

> 
> Otherwise this looks good to me.

Pedro, thanks for this review.  The updated patch is included below.
I have one remaining question:  I took this (above) to mean that the patch
was approved with the requested changes, but since this is part of a series
I'm not sure if I have the go-ahead to push it.  The patch is standalone
and I believe it has value independent of the rest of the series.

OK to push?

Thanks
--Don

gdb/
2014-09-26  Don Breazeal  <donb@codesourcery.com>

	* inf-ptrace.c (inf_ptrace_follow_fork): Remove target-independent
	code so as to work with follow_fork_inferior.
	* inf-ttrace.c (inf_ttrace_follow_fork): Ditto.
	(inf_ttrace_create_inferior): Remove reference to
	inf_ttrace_vfork_ppid.
	(inf_ttrace_attach): Ditto.
	(inf_ttrace_detach): Ditto.
	(inf_ttrace_kill): Use current_inferior instead of
	inf_ttrace_vfork_ppid.
	(inf_ttrace_wait): Eliminate use of inf_ttrace_vfork_ppid, report
	TARGET_WAITKIND_VFORK_DONE event, delete HACK that switched the
	inferior away from the parent.
	* infrun.c (follow_fork): Call follow_fork_inferior instead of
	target_follow_fork.
	(follow_fork_inferior): New function.
	(follow_inferior_reset_breakpoints): Make function static.
	* infrun.h (follow_inferior_reset_breakpoints): Remove declaration.
	* linux-nat.c (linux_child_follow_fork): Move target-independent
	code to infrun.c:follow_fork_inferior.

---
 gdb/inf-ptrace.c |   48 ++---------
 gdb/inf-ttrace.c |  179 +++++----------------------------------
 gdb/infrun.c     |  249
+++++++++++++++++++++++++++++++++++++++++++++++++++++-
 gdb/infrun.h     |    2 -
 gdb/linux-nat.c  |  243
++++++-----------------------------------------------
 5 files changed, 298 insertions(+), 423 deletions(-)

 	  gdb_assert (linux_supports_tracefork () >= 0);
@@ -628,98 +523,12 @@ holding the child stopped.  Try \"set
detach-on-fork\" or \
     }
   else
     {
-      struct inferior *parent_inf, *child_inf;
       struct lwp_info *child_lp;
-      struct program_space *parent_pspace;
-
-      if (info_verbose || debug_linux_nat)
-	{
-	  target_terminal_ours ();
-	  if (has_vforked)
-	    fprintf_filtered (gdb_stdlog,
-			      _("Attaching after process %d "
-				"vfork to child process %d.\n"),
-			      parent_pid, child_pid);
-	  else
-	    fprintf_filtered (gdb_stdlog,
-			      _("Attaching after process %d "
-				"fork to child process %d.\n"),
-			      parent_pid, child_pid);
-	}
-
-      /* Add the new inferior first, so that the target_detach below
-	 doesn't unpush the target.  */
-
-      child_inf = add_inferior (child_pid);
-
-      parent_inf = current_inferior ();
-      child_inf->attach_flag = parent_inf->attach_flag;
-      copy_terminal_info (child_inf, parent_inf);
-      child_inf->gdbarch = parent_inf->gdbarch;
-      copy_inferior_target_desc_info (child_inf, parent_inf);
-
-      parent_pspace = parent_inf->pspace;
-
-      /* If we're vforking, we want to hold on to the parent until the
-	 child exits or execs.  At child exec or exit time we can
-	 remove the old breakpoints from the parent and detach or
-	 resume debugging it.  Otherwise, detach the parent now; we'll
-	 want to reuse it's program/address spaces, but we can't set
-	 them to the child before removing breakpoints from the
-	 parent, otherwise, the breakpoints module could decide to
-	 remove breakpoints from the wrong process (since they'd be
-	 assigned to the same address space).  */

-      if (has_vforked)
-	{
-	  gdb_assert (child_inf->vfork_parent == NULL);
-	  gdb_assert (parent_inf->vfork_child == NULL);
-	  child_inf->vfork_parent = parent_inf;
-	  child_inf->pending_detach = 0;
-	  parent_inf->vfork_child = child_inf;
-	  parent_inf->pending_detach = detach_fork;
-	  parent_inf->waiting_for_vfork_done = 0;
-	}
-      else if (detach_fork)
-	target_detach (NULL, 0);
-
-      /* Note that the detach above makes PARENT_INF dangling.  */
-
-      /* Add the child thread to the appropriate lists, and switch to
-	 this new thread, before cloning the program space, and
-	 informing the solib layer about this new process.  */
-
-      inferior_ptid = ptid_build (child_pid, child_pid, 0);
-      add_thread (inferior_ptid);
       child_lp = add_lwp (inferior_ptid);
       child_lp->stopped = 1;
       child_lp->last_resume_kind = resume_stop;

-      /* If this is a vfork child, then the address-space is shared
-	 with the parent.  If we detached from the parent, then we can
-	 reuse the parent's program/address spaces.  */
-      if (has_vforked || detach_fork)
-	{
-	  child_inf->pspace = parent_pspace;
-	  child_inf->aspace = child_inf->pspace->aspace;
-	}
-      else
-	{
-	  child_inf->aspace = new_address_space ();
-	  child_inf->pspace = add_program_space (child_inf->aspace);
-	  child_inf->removable = 1;
-	  child_inf->symfile_flags = SYMFILE_NO_READ;
-	  set_current_program_space (child_inf->pspace);
-	  clone_program_space (child_inf->pspace, parent_pspace);
-
-	  /* Let the shared library layer (solib-svr4) learn about
-	     this new process, relocate the cloned exec, pull in
-	     shared libraries, and install the solib event breakpoint.
-	     If a "cloned-VM" event was propagated better throughout
-	     the core, this wouldn't be required.  */
-	  solib_create_inferior_hook (0);
-	}
-
       /* Let the thread_db layer learn about this new process.  */
       check_for_thread_db ();
     }

Comments

Pedro Alves Sept. 30, 2014, 10:56 a.m. UTC | #1
On 09/29/2014 07:07 PM, Breazeal, Don wrote:

> The patch is standalone and I believe it has value independent
> of the rest of the series.

Yes, agreed.

> OK to push?

Yes, please push.

> +/* Handle changes to the inferior list based on the type of fork,
> +   which process is being followed, and whether the other process
> +   should be detached.  On entry inferior_ptid must be the ptid of
> +   the fork parent.  At return inferior_ptid is the ptid of the
> +   followed inferior.  */
> +
> +int

'static int' here too.

Consider putting the function above its caller thus avoiding
the need for the other declaration at the top.

> +follow_fork_inferior (int follow_child, int detach_fork)
> +{

Thanks,
Pedro Alves
Don Breazeal Sept. 30, 2014, 6:43 p.m. UTC | #2
On 9/30/2014 3:56 AM, Pedro Alves wrote:
> On 09/29/2014 07:07 PM, Breazeal, Don wrote:
> 
>> The patch is standalone and I believe it has value independent
>> of the rest of the series.
> 
> Yes, agreed.
> 
>> OK to push?
> 
> Yes, please push.
> 
>> +/* Handle changes to the inferior list based on the type of fork,
>> +   which process is being followed, and whether the other process
>> +   should be detached.  On entry inferior_ptid must be the ptid of
>> +   the fork parent.  At return inferior_ptid is the ptid of the
>> +   followed inferior.  */
>> +
>> +int
> 
> 'static int' here too.
> 
> Consider putting the function above its caller thus avoiding
> the need for the other declaration at the top.
> 
>> +follow_fork_inferior (int follow_child, int detach_fork)
>> +{
> 
> Thanks,
> Pedro Alves
> 
Patch is pushed with changes.
Thanks,
--Don
diff mbox

Patch

diff --git a/gdb/inf-ptrace.c b/gdb/inf-ptrace.c
index dd71b3a..6eb8080 100644
--- a/gdb/inf-ptrace.c
+++ b/gdb/inf-ptrace.c
@@ -36,57 +36,21 @@ 

 #ifdef PT_GET_PROCESS_STATE

+/* Target hook for follow_fork.  On entry and at return inferior_ptid is
+   the ptid of the followed inferior.  */
+
 static int
 inf_ptrace_follow_fork (struct target_ops *ops, int follow_child,
 			int detach_fork)
 {
-  pid_t pid, fpid;
-  ptrace_state_t pe;
-
-  pid = ptid_get_pid (inferior_ptid);
-
-  if (ptrace (PT_GET_PROCESS_STATE, pid,
-	       (PTRACE_TYPE_ARG3)&pe, sizeof pe) == -1)
-    perror_with_name (("ptrace"));
-
-  gdb_assert (pe.pe_report_event == PTRACE_FORK);
-  fpid = pe.pe_other_pid;
-
-  if (follow_child)
+  if (!follow_child)
     {
-      struct inferior *parent_inf, *child_inf;
-      struct thread_info *tp;
-
-      parent_inf = find_inferior_pid (pid);
-
-      /* Add the child.  */
-      child_inf = add_inferior (fpid);
-      child_inf->attach_flag = parent_inf->attach_flag;
-      copy_terminal_info (child_inf, parent_inf);
-      child_inf->pspace = parent_inf->pspace;
-      child_inf->aspace = parent_inf->aspace;
+      pid_t child_pid = inferior_thread->pending_follow.value.related_pid;

-      /* Before detaching from the parent, remove all breakpoints from
-	 it.  */
-      remove_breakpoints ();
-
-      if (ptrace (PT_DETACH, pid, (PTRACE_TYPE_ARG3)1, 0) == -1)
-	perror_with_name (("ptrace"));
-
-      /* Switch inferior_ptid out of the parent's way.  */
-      inferior_ptid = pid_to_ptid (fpid);
-
-      /* Delete the parent.  */
-      detach_inferior (pid);
-
-      add_thread_silent (inferior_ptid);
-    }
-  else
-    {
       /* Breakpoints have already been detached from the child by
 	 infrun.c.  */

-      if (ptrace (PT_DETACH, fpid, (PTRACE_TYPE_ARG3)1, 0) == -1)
+      if (ptrace (PT_DETACH, child_pid, (PTRACE_TYPE_ARG3)1, 0) == -1)
 	perror_with_name (("ptrace"));
     }

diff --git a/gdb/inf-ttrace.c b/gdb/inf-ttrace.c
index 847beb3..dceea42 100644
--- a/gdb/inf-ttrace.c
+++ b/gdb/inf-ttrace.c
@@ -403,128 +403,18 @@  inf_ttrace_stopped_by_watchpoint (struct
target_ops *ops)
 }
 

-/* When tracking a vfork(2), we cannot detach from the parent until
-   after the child has called exec(3) or has exited.  If we are still
-   attached to the parent, this variable will be set to the process ID
-   of the parent.  Otherwise it will be set to zero.  */
-static pid_t inf_ttrace_vfork_ppid = -1;
+/* Target hook for follow_fork.  On entry and at return inferior_ptid
+   is the ptid of the followed inferior.  */

 static int
 inf_ttrace_follow_fork (struct target_ops *ops, int follow_child,
 			int detach_fork)
 {
-  pid_t pid, fpid;
-  lwpid_t lwpid, flwpid;
-  ttstate_t tts;
   struct thread_info *tp = inferior_thread ();

   gdb_assert (tp->pending_follow.kind == TARGET_WAITKIND_FORKED
 	      || tp->pending_follow.kind == TARGET_WAITKIND_VFORKED);

-  pid = ptid_get_pid (inferior_ptid);
-  lwpid = ptid_get_lwp (inferior_ptid);
-
-  /* Get all important details that core GDB doesn't (and shouldn't)
-     know about.  */
-  if (ttrace (TT_LWP_GET_STATE, pid, lwpid,
-	      (uintptr_t)&tts, sizeof tts, 0) == -1)
-    perror_with_name (("ttrace"));
-
-  gdb_assert (tts.tts_event == TTEVT_FORK || tts.tts_event == TTEVT_VFORK);
-
-  if (tts.tts_u.tts_fork.tts_isparent)
-    {
-      pid = tts.tts_pid;
-      lwpid = tts.tts_lwpid;
-      fpid = tts.tts_u.tts_fork.tts_fpid;
-      flwpid = tts.tts_u.tts_fork.tts_flwpid;
-    }
-  else
-    {
-      pid = tts.tts_u.tts_fork.tts_fpid;
-      lwpid = tts.tts_u.tts_fork.tts_flwpid;
-      fpid = tts.tts_pid;
-      flwpid = tts.tts_lwpid;
-    }
-
-  if (follow_child)
-    {
-      struct inferior *inf;
-      struct inferior *parent_inf;
-
-      parent_inf = find_inferior_pid (pid);
-
-      inferior_ptid = ptid_build (fpid, flwpid, 0);
-      inf = add_inferior (fpid);
-      inf->attach_flag = parent_inf->attach_flag;
-      inf->pspace = parent_inf->pspace;
-      inf->aspace = parent_inf->aspace;
-      copy_terminal_info (inf, parent_inf);
-      detach_breakpoints (ptid_build (pid, lwpid, 0));
-
-      target_terminal_ours ();
-      fprintf_unfiltered (gdb_stdlog,
-			  _("Attaching after fork to child process %ld.\n"),
-			  (long)fpid);
-    }
-  else
-    {
-      inferior_ptid = ptid_build (pid, lwpid, 0);
-      /* Detach any remaining breakpoints in the child.  In the case
-	 of fork events, we do not need to do this, because breakpoints
-	 should have already been removed earlier.  */
-      if (tts.tts_event == TTEVT_VFORK)
-	detach_breakpoints (ptid_build (fpid, flwpid, 0));
-
-      target_terminal_ours ();
-      fprintf_unfiltered (gdb_stdlog,
-			  _("Detaching after fork from child process %ld.\n"),
-			  (long)fpid);
-    }
-
-  if (tts.tts_event == TTEVT_VFORK)
-    {
-      gdb_assert (!tts.tts_u.tts_fork.tts_isparent);
-
-      if (follow_child)
-	{
-	  /* We can't detach from the parent yet.  */
-	  inf_ttrace_vfork_ppid = pid;
-
-	  reattach_breakpoints (fpid);
-	}
-      else
-	{
-	  if (ttrace (TT_PROC_DETACH, fpid, 0, 0, 0, 0) == -1)
-	    perror_with_name (("ttrace"));
-
-	  /* Wait till we get the TTEVT_VFORK event in the parent.
-	     This indicates that the child has called exec(3) or has
-	     exited and that the parent is ready to be traced again.  */
-	  if (ttrace_wait (pid, lwpid, TTRACE_WAITOK, &tts, sizeof tts) == -1)
-	    perror_with_name (("ttrace_wait"));
-	  gdb_assert (tts.tts_event == TTEVT_VFORK);
-	  gdb_assert (tts.tts_u.tts_fork.tts_isparent);
-
-	  reattach_breakpoints (pid);
-	}
-    }
-  else
-    {
-      gdb_assert (tts.tts_u.tts_fork.tts_isparent);
-
-      if (follow_child)
-	{
-	  if (ttrace (TT_PROC_DETACH, pid, 0, 0, 0, 0) == -1)
-	    perror_with_name (("ttrace"));
-	}
-      else
-	{
-	  if (ttrace (TT_PROC_DETACH, fpid, 0, 0, 0, 0) == -1)
-	    perror_with_name (("ttrace"));
-	}
-    }
-
   if (follow_child)
     {
       struct thread_info *ti;
@@ -533,17 +423,21 @@  inf_ttrace_follow_fork (struct target_ops *ops,
int follow_child,
       inf_ttrace_num_lwps = 1;
       inf_ttrace_num_lwps_in_syscall = 0;

-      /* Delete parent.  */
-      delete_thread_silent (ptid_build (pid, lwpid, 0));
-      detach_inferior (pid);
-
-      /* Add child thread.  inferior_ptid was already set above.  */
-      ti = add_thread_silent (inferior_ptid);
+      ti = inferior_thread ();
       ti->private =
 	xmalloc (sizeof (struct inf_ttrace_private_thread_info));
       memset (ti->private, 0,
 	      sizeof (struct inf_ttrace_private_thread_info));
     }
+  else
+    {
+      pid_t child_pid;
+
+      /* Following parent.  Detach child now.  */
+      child_pid = ptid_get_pid (tp->pending_follow.value.related_pid);
+      if (ttrace (TT_PROC_DETACH, child_pid, 0, 0, 0, 0) == -1)
+	perror_with_name (("ttrace"));
+    }

   return 0;
 }
@@ -661,7 +555,6 @@  inf_ttrace_create_inferior (struct target_ops *ops,
char *exec_file,
   gdb_assert (inf_ttrace_num_lwps_in_syscall == 0);
   gdb_assert (inf_ttrace_page_dict.count == 0);
   gdb_assert (inf_ttrace_reenable_page_protections == 0);
-  gdb_assert (inf_ttrace_vfork_ppid == -1);

   pid = fork_inferior (exec_file, allargs, env, inf_ttrace_me, NULL,
 		       inf_ttrace_prepare, NULL, NULL);
@@ -772,7 +665,6 @@  inf_ttrace_attach (struct target_ops *ops, const
char *args, int from_tty)

   gdb_assert (inf_ttrace_num_lwps == 0);
   gdb_assert (inf_ttrace_num_lwps_in_syscall == 0);
-  gdb_assert (inf_ttrace_vfork_ppid == -1);

   if (ttrace (TT_PROC_ATTACH, pid, 0, TT_KILL_ON_EXIT, TT_VERSION, 0)
== -1)
     perror_with_name (("ttrace"));
@@ -822,13 +714,6 @@  inf_ttrace_detach (struct target_ops *ops, const
char *args, int from_tty)
   if (ttrace (TT_PROC_DETACH, pid, 0, 0, sig, 0) == -1)
     perror_with_name (("ttrace"));

-  if (inf_ttrace_vfork_ppid != -1)
-    {
-      if (ttrace (TT_PROC_DETACH, inf_ttrace_vfork_ppid, 0, 0, 0, 0) == -1)
-	perror_with_name (("ttrace"));
-      inf_ttrace_vfork_ppid = -1;
-    }
-
   inf_ttrace_num_lwps = 0;
   inf_ttrace_num_lwps_in_syscall = 0;

@@ -850,13 +735,6 @@  inf_ttrace_kill (struct target_ops *ops)
     perror_with_name (("ttrace"));
   /* ??? Is it necessary to call ttrace_wait() here?  */

-  if (inf_ttrace_vfork_ppid != -1)
-    {
-      if (ttrace (TT_PROC_DETACH, inf_ttrace_vfork_ppid, 0, 0, 0, 0) == -1)
-	perror_with_name (("ttrace"));
-      inf_ttrace_vfork_ppid = -1;
-    }
-
   target_mourn_inferior ();
 }

@@ -967,20 +845,6 @@  inf_ttrace_wait (struct target_ops *ops,
       if (ttrace_wait (pid, lwpid, TTRACE_WAITOK, &tts, sizeof tts) == -1)
 	perror_with_name (("ttrace_wait"));

-      if (tts.tts_event == TTEVT_VFORK && tts.tts_u.tts_fork.tts_isparent)
-	{
-	  if (inf_ttrace_vfork_ppid != -1)
-	    {
-	      gdb_assert (inf_ttrace_vfork_ppid == tts.tts_pid);
-
-	      if (ttrace (TT_PROC_DETACH, tts.tts_pid, 0, 0, 0, 0) == -1)
-		perror_with_name (("ttrace"));
-	      inf_ttrace_vfork_ppid = -1;
-	    }
-
-	  tts.tts_event = TTEVT_NONE;
-	}
-
       clear_sigint_trap ();
     }
   while (tts.tts_event == TTEVT_NONE);
@@ -1075,17 +939,16 @@  inf_ttrace_wait (struct target_ops *ops,
       break;

     case TTEVT_VFORK:
-      gdb_assert (!tts.tts_u.tts_fork.tts_isparent);
-
-      related_ptid = ptid_build (tts.tts_u.tts_fork.tts_fpid,
-				 tts.tts_u.tts_fork.tts_flwpid, 0);
-
-      ourstatus->kind = TARGET_WAITKIND_VFORKED;
-      ourstatus->value.related_pid = related_ptid;
+      if (tts.tts_u.tts_fork.tts_isparent)
+	ourstatus->kind = TARGET_WAITKIND_VFORK_DONE;
+      else
+	{
+	  related_ptid = ptid_build (tts.tts_u.tts_fork.tts_fpid,
+				     tts.tts_u.tts_fork.tts_flwpid, 0);

-      /* HACK: To avoid touching the parent during the vfork, switch
-	 away from it.  */
-      inferior_ptid = ptid;
+	  ourstatus->kind = TARGET_WAITKIND_VFORKED;
+	  ourstatus->value.related_pid = related_ptid;
+	}
       break;

     case TTEVT_LWP_CREATE:
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 5e123be..e8b1f01 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -60,6 +60,7 @@ 
 #include "completer.h"
 #include "target-descriptions.h"
 #include "target-dcache.h"
+#include "terminal.h"

 /* Prototypes for local functions */

@@ -79,6 +80,10 @@  static int restore_selected_frame (void *);

 static int follow_fork (void);

+static int follow_fork_inferior (int follow_child, int detach_fork);
+
+static void follow_inferior_reset_breakpoints (void);
+
 static void set_schedlock_func (char *args, int from_tty,
 				struct cmd_list_element *c);

@@ -486,9 +491,10 @@  follow_fork (void)
 	parent = inferior_ptid;
 	child = tp->pending_follow.value.related_pid;

-	/* Tell the target to do whatever is necessary to follow
-	   either parent or child.  */
-	if (target_follow_fork (follow_child, detach_fork))
+	/* Set up inferior(s) as specified by the caller, and tell the
+	   target to do whatever is necessary to follow either parent
+	   or child.  */
+	if (follow_fork_inferior (follow_child, detach_fork))
 	  {
 	    /* Target refused to follow, or there's some other reason
 	       we shouldn't resume.  */
@@ -560,7 +566,242 @@  follow_fork (void)
   return should_resume;
 }

-void
+/* Handle changes to the inferior list based on the type of fork,
+   which process is being followed, and whether the other process
+   should be detached.  On entry inferior_ptid must be the ptid of
+   the fork parent.  At return inferior_ptid is the ptid of the
+   followed inferior.  */
+
+int
+follow_fork_inferior (int follow_child, int detach_fork)
+{
+  int has_vforked;
+  int parent_pid, child_pid;
+
+  has_vforked = (inferior_thread ()->pending_follow.kind
+		 == TARGET_WAITKIND_VFORKED);
+  parent_pid = ptid_get_lwp (inferior_ptid);
+  if (parent_pid == 0)
+    parent_pid = ptid_get_pid (inferior_ptid);
+  child_pid
+    = ptid_get_pid (inferior_thread ()->pending_follow.value.related_pid);
+
+  if (has_vforked
+      && !non_stop /* Non-stop always resumes both branches.  */
+      && (!target_is_async_p () || sync_execution)
+      && !(follow_child || detach_fork || sched_multi))
+    {
+      /* The parent stays blocked inside the vfork syscall until the
+	 child execs or exits.  If we don't let the child run, then
+	 the parent stays blocked.  If we're telling the parent to run
+	 in the foreground, the user will not be able to ctrl-c to get
+	 back the terminal, effectively hanging the debug session.  */
+      fprintf_filtered (gdb_stderr, _("\
+Can not resume the parent process over vfork in the foreground while\n\
+holding the child stopped.  Try \"set detach-on-fork\" or \
+\"set schedule-multiple\".\n"));
+      /* FIXME output string > 80 columns.  */
+      return 1;
+    }
+
+  if (!follow_child)
+    {
+      /* Detach new forked process?  */
+      if (detach_fork)
+	{
+	  struct cleanup *old_chain;
+
+	  /* Before detaching from the child, remove all breakpoints
+	     from it.  If we forked, then this has already been taken
+	     care of by infrun.c.  If we vforked however, any
+	     breakpoint inserted in the parent is visible in the
+	     child, even those added while stopped in a vfork
+	     catchpoint.  This will remove the breakpoints from the
+	     parent also, but they'll be reinserted below.  */
+	  if (has_vforked)
+	    {
+	      /* Keep breakpoints list in sync.  */
+	      remove_breakpoints_pid (ptid_get_pid (inferior_ptid));
+	    }
+
+	  if (info_verbose || debug_infrun)
+	    {
+	      target_terminal_ours ();
+	      fprintf_filtered (gdb_stdlog,
+				"Detaching after fork from "
+				"child process %d.\n",
+				child_pid);
+	    }
+	}
+      else
+	{
+	  struct inferior *parent_inf, *child_inf;
+	  struct cleanup *old_chain;
+
+	  /* Add process to GDB's tables.  */
+	  child_inf = add_inferior (child_pid);
+
+	  parent_inf = current_inferior ();
+	  child_inf->attach_flag = parent_inf->attach_flag;
+	  copy_terminal_info (child_inf, parent_inf);
+	  child_inf->gdbarch = parent_inf->gdbarch;
+	  copy_inferior_target_desc_info (child_inf, parent_inf);
+
+	  old_chain = save_inferior_ptid ();
+	  save_current_program_space ();
+
+	  inferior_ptid = ptid_build (child_pid, child_pid, 0);
+	  add_thread (inferior_ptid);
+	  child_inf->symfile_flags = SYMFILE_NO_READ;
+
+	  /* If this is a vfork child, then the address-space is
+	     shared with the parent.  */
+	  if (has_vforked)
+	    {
+	      child_inf->pspace = parent_inf->pspace;
+	      child_inf->aspace = parent_inf->aspace;
+
+	      /* The parent will be frozen until the child is done
+		 with the shared region.  Keep track of the
+		 parent.  */
+	      child_inf->vfork_parent = parent_inf;
+	      child_inf->pending_detach = 0;
+	      parent_inf->vfork_child = child_inf;
+	      parent_inf->pending_detach = 0;
+	    }
+	  else
+	    {
+	      child_inf->aspace = new_address_space ();
+	      child_inf->pspace = add_program_space (child_inf->aspace);
+	      child_inf->removable = 1;
+	      set_current_program_space (child_inf->pspace);
+	      clone_program_space (child_inf->pspace, parent_inf->pspace);
+
+	      /* Let the shared library layer (e.g., solib-svr4) learn
+		 about this new process, relocate the cloned exec, pull
+		 in shared libraries, and install the solib event
+		 breakpoint.  If a "cloned-VM" event was propagated
+		 better throughout the core, this wouldn't be
+		 required.  */
+	      solib_create_inferior_hook (0);
+	    }
+
+	  do_cleanups (old_chain);
+	}
+
+      if (has_vforked)
+	{
+	  struct inferior *parent_inf;
+
+	  parent_inf = current_inferior ();
+
+	  /* If we detached from the child, then we have to be careful
+	     to not insert breakpoints in the parent until the child
+	     is done with the shared memory region.  However, if we're
+	     staying attached to the child, then we can and should
+	     insert breakpoints, so that we can debug it.  A
+	     subsequent child exec or exit is enough to know when does
+	     the child stops using the parent's address space.  */
+	  parent_inf->waiting_for_vfork_done = detach_fork;
+	  parent_inf->pspace->breakpoints_not_allowed = detach_fork;
+	}
+    }
+  else
+    {
+      /* Follow the child.  */
+      struct inferior *parent_inf, *child_inf;
+      struct program_space *parent_pspace;
+
+      if (info_verbose || debug_infrun)
+	{
+	  target_terminal_ours ();
+	  if (has_vforked)
+	    fprintf_filtered (gdb_stdlog,
+			      _("Attaching after process %d "
+				"vfork to child process %d.\n"),
+			      parent_pid, child_pid);
+	  else
+	    fprintf_filtered (gdb_stdlog,
+			      _("Attaching after process %d "
+				"fork to child process %d.\n"),
+			      parent_pid, child_pid);
+	}
+
+      /* Add the new inferior first, so that the target_detach below
+	 doesn't unpush the target.  */
+
+      child_inf = add_inferior (child_pid);
+
+      parent_inf = current_inferior ();
+      child_inf->attach_flag = parent_inf->attach_flag;
+      copy_terminal_info (child_inf, parent_inf);
+      child_inf->gdbarch = parent_inf->gdbarch;
+      copy_inferior_target_desc_info (child_inf, parent_inf);
+
+      parent_pspace = parent_inf->pspace;
+
+      /* If we're vforking, we want to hold on to the parent until the
+	 child exits or execs.  At child exec or exit time we can
+	 remove the old breakpoints from the parent and detach or
+	 resume debugging it.  Otherwise, detach the parent now; we'll
+	 want to reuse it's program/address spaces, but we can't set
+	 them to the child before removing breakpoints from the
+	 parent, otherwise, the breakpoints module could decide to
+	 remove breakpoints from the wrong process (since they'd be
+	 assigned to the same address space).  */
+
+      if (has_vforked)
+	{
+	  gdb_assert (child_inf->vfork_parent == NULL);
+	  gdb_assert (parent_inf->vfork_child == NULL);
+	  child_inf->vfork_parent = parent_inf;
+	  child_inf->pending_detach = 0;
+	  parent_inf->vfork_child = child_inf;
+	  parent_inf->pending_detach = detach_fork;
+	  parent_inf->waiting_for_vfork_done = 0;
+	}
+      else if (detach_fork)
+	target_detach (NULL, 0);
+
+      /* Note that the detach above makes PARENT_INF dangling.  */
+
+      /* Add the child thread to the appropriate lists, and switch to
+	 this new thread, before cloning the program space, and
+	 informing the solib layer about this new process.  */
+
+      inferior_ptid = ptid_build (child_pid, child_pid, 0);
+      add_thread (inferior_ptid);
+
+      /* If this is a vfork child, then the address-space is shared
+	 with the parent.  If we detached from the parent, then we can
+	 reuse the parent's program/address spaces.  */
+      if (has_vforked || detach_fork)
+	{
+	  child_inf->pspace = parent_pspace;
+	  child_inf->aspace = child_inf->pspace->aspace;
+	}
+      else
+	{
+	  child_inf->aspace = new_address_space ();
+	  child_inf->pspace = add_program_space (child_inf->aspace);
+	  child_inf->removable = 1;
+	  child_inf->symfile_flags = SYMFILE_NO_READ;
+	  set_current_program_space (child_inf->pspace);
+	  clone_program_space (child_inf->pspace, parent_pspace);
+
+	  /* Let the shared library layer (e.g., solib-svr4) learn
+	     about this new process, relocate the cloned exec, pull in
+	     shared libraries, and install the solib event breakpoint.
+	     If a "cloned-VM" event was propagated better throughout
+	     the core, this wouldn't be required.  */
+	  solib_create_inferior_hook (0);
+	}
+    }
+
+  return target_follow_fork (follow_child, detach_fork);
+}
+
+static void
 follow_inferior_reset_breakpoints (void)
 {
   struct thread_info *tp = inferior_thread ();
diff --git a/gdb/infrun.h b/gdb/infrun.h
index cc9cb33..fb6276b 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -115,8 +115,6 @@  extern void insert_step_resume_breakpoint_at_sal
(struct gdbarch *,
 						  struct symtab_and_line ,
 						  struct frame_id);

-extern void follow_inferior_reset_breakpoints (void);
-
 /* Returns true if we're trying to step past the instruction at
    ADDRESS in ASPACE.  */
 extern int stepping_past_instruction_at (struct address_space *aspace,
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 0fe4b0b..df830ae 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -54,7 +54,6 @@ 
 #include <sys/types.h>
 #include <dirent.h>
 #include "xml-support.h"
-#include "terminal.h"
 #include <sys/vfs.h>
 #include "solib.h"
 #include "nat/linux-osdata.h"
@@ -369,79 +368,41 @@  delete_lwp_cleanup (void *lp_voidp)
   delete_lwp (lp->ptid);
 }

+/* Target hook for follow_fork.  On entry inferior_ptid must be the
+   ptid of the followed inferior.  At return, inferior_ptid will be
+   unchanged.  */
+
 static int
 linux_child_follow_fork (struct target_ops *ops, int follow_child,
 			 int detach_fork)
 {
-  int has_vforked;
-  int parent_pid, child_pid;
-
-  has_vforked = (inferior_thread ()->pending_follow.kind
-		 == TARGET_WAITKIND_VFORKED);
-  parent_pid = ptid_get_lwp (inferior_ptid);
-  if (parent_pid == 0)
-    parent_pid = ptid_get_pid (inferior_ptid);
-  child_pid
-    = ptid_get_pid (inferior_thread ()->pending_follow.value.related_pid);
-
-  if (has_vforked
-      && !non_stop /* Non-stop always resumes both branches.  */
-      && (!target_is_async_p () || sync_execution)
-      && !(follow_child || detach_fork || sched_multi))
-    {
-      /* The parent stays blocked inside the vfork syscall until the
-	 child execs or exits.  If we don't let the child run, then
-	 the parent stays blocked.  If we're telling the parent to run
-	 in the foreground, the user will not be able to ctrl-c to get
-	 back the terminal, effectively hanging the debug session.  */
-      fprintf_filtered (gdb_stderr, _("\
-Can not resume the parent process over vfork in the foreground while\n\
-holding the child stopped.  Try \"set detach-on-fork\" or \
-\"set schedule-multiple\".\n"));
-      /* FIXME output string > 80 columns.  */
-      return 1;
-    }
-
-  if (! follow_child)
+  if (!follow_child)
     {
       struct lwp_info *child_lp = NULL;
+      int status = W_STOPCODE (0);
+      struct cleanup *old_chain;
+      int has_vforked;
+      int parent_pid, child_pid;
+
+      has_vforked = (inferior_thread ()->pending_follow.kind
+		     == TARGET_WAITKIND_VFORKED);
+      parent_pid = ptid_get_lwp (inferior_ptid);
+      if (parent_pid == 0)
+	parent_pid = ptid_get_pid (inferior_ptid);
+      child_pid
+	= ptid_get_pid (inferior_thread ()->pending_follow.value.related_pid);
+

       /* We're already attached to the parent, by default.  */
+      old_chain = save_inferior_ptid ();
+      inferior_ptid = ptid_build (child_pid, child_pid, 0);
+      child_lp = add_lwp (inferior_ptid);
+      child_lp->stopped = 1;
+      child_lp->last_resume_kind = resume_stop;

       /* Detach new forked process?  */
       if (detach_fork)
 	{
-	  struct cleanup *old_chain;
-	  int status = W_STOPCODE (0);
-
-	  /* Before detaching from the child, remove all breakpoints
-	     from it.  If we forked, then this has already been taken
-	     care of by infrun.c.  If we vforked however, any
-	     breakpoint inserted in the parent is visible in the
-	     child, even those added while stopped in a vfork
-	     catchpoint.  This will remove the breakpoints from the
-	     parent also, but they'll be reinserted below.  */
-	  if (has_vforked)
-	    {
-	      /* keep breakpoints list in sync.  */
-	      remove_breakpoints_pid (ptid_get_pid (inferior_ptid));
-	    }
-
-	  if (info_verbose || debug_linux_nat)
-	    {
-	      target_terminal_ours ();
-	      fprintf_filtered (gdb_stdlog,
-				"Detaching after fork from "
-				"child process %d.\n",
-				child_pid);
-	    }
-
-	  old_chain = save_inferior_ptid ();
-	  inferior_ptid = ptid_build (child_pid, child_pid, 0);
-
-	  child_lp = add_lwp (inferior_ptid);
-	  child_lp->stopped = 1;
-	  child_lp->last_resume_kind = resume_stop;
 	  make_cleanup (delete_lwp_cleanup, child_lp);

 	  if (linux_nat_prepare_to_resume != NULL)
@@ -476,86 +437,20 @@  holding the child stopped.  Try \"set
detach-on-fork\" or \
 	      ptrace (PTRACE_DETACH, child_pid, 0, signo);
 	    }

+	  /* Resets value of inferior_ptid to parent ptid.  */
 	  do_cleanups (old_chain);
 	}
       else
 	{
-	  struct inferior *parent_inf, *child_inf;
-	  struct cleanup *old_chain;
-
-	  /* Add process to GDB's tables.  */
-	  child_inf = add_inferior (child_pid);
-
-	  parent_inf = current_inferior ();
-	  child_inf->attach_flag = parent_inf->attach_flag;
-	  copy_terminal_info (child_inf, parent_inf);
-	  child_inf->gdbarch = parent_inf->gdbarch;
-	  copy_inferior_target_desc_info (child_inf, parent_inf);
-
-	  old_chain = save_inferior_ptid ();
-	  save_current_program_space ();
-
-	  inferior_ptid = ptid_build (child_pid, child_pid, 0);
-	  add_thread (inferior_ptid);
-	  child_lp = add_lwp (inferior_ptid);
-	  child_lp->stopped = 1;
-	  child_lp->last_resume_kind = resume_stop;
-	  child_inf->symfile_flags = SYMFILE_NO_READ;
-
-	  /* If this is a vfork child, then the address-space is
-	     shared with the parent.  */
-	  if (has_vforked)
-	    {
-	      child_inf->pspace = parent_inf->pspace;
-	      child_inf->aspace = parent_inf->aspace;
-
-	      /* The parent will be frozen until the child is done
-		 with the shared region.  Keep track of the
-		 parent.  */
-	      child_inf->vfork_parent = parent_inf;
-	      child_inf->pending_detach = 0;
-	      parent_inf->vfork_child = child_inf;
-	      parent_inf->pending_detach = 0;
-	    }
-	  else
-	    {
-	      child_inf->aspace = new_address_space ();
-	      child_inf->pspace = add_program_space (child_inf->aspace);
-	      child_inf->removable = 1;
-	      set_current_program_space (child_inf->pspace);
-	      clone_program_space (child_inf->pspace, parent_inf->pspace);
-
-	      /* Let the shared library layer (solib-svr4) learn about
-		 this new process, relocate the cloned exec, pull in
-		 shared libraries, and install the solib event
-		 breakpoint.  If a "cloned-VM" event was propagated
-		 better throughout the core, this wouldn't be
-		 required.  */
-	      solib_create_inferior_hook (0);
-	    }
-
 	  /* Let the thread_db layer learn about this new process.  */
 	  check_for_thread_db ();
-
-	  do_cleanups (old_chain);
 	}

+      do_cleanups (old_chain);
+
       if (has_vforked)
 	{
 	  struct lwp_info *parent_lp;
-	  struct inferior *parent_inf;
-
-	  parent_inf = current_inferior ();
-
-	  /* If we detached from the child, then we have to be careful
-	     to not insert breakpoints in the parent until the child
-	     is done with the shared memory region.  However, if we're
-	     staying attached to the child, then we can and should
-	     insert breakpoints, so that we can debug it.  A
-	     subsequent child exec or exit is enough to know when does
-	     the child stops using the parent's address space.  */
-	  parent_inf->waiting_for_vfork_done = detach_fork;
-	  parent_inf->pspace->breakpoints_not_allowed = detach_fork;

 	  parent_lp = find_lwp_pid (pid_to_ptid (parent_pid));