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

Message ID 54132443.5060602@codesourcery.com
State New, archived
Headers

Commit Message

Don Breazeal Sept. 12, 2014, 4:50 p.m. UTC
  On 9/9/2014 4:09 AM, Pedro Alves wrote:
> On 09/09/2014 12:54 AM, Breazeal, Don wrote:
>> Hi Pedro,
>>
>> I've consolidated my responses to the issues you raised in this email,
>> and attached an updated patch containing the proposed solutions.
>>
>> On 9/5/2014 7:20 AM, Pedro Alves wrote:
>>> linux_child_follow_fork ends up with:
>>>
>>> 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 (!follow_child)
>>>     {
>>> ...
>>>     }
>>>   else
>>>     {
>>>       struct lwp_info *child_lp;
>>>
>>>       child_lp = add_lwp (inferior_ptid);
>>>       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>>       child_lp->stopped = 1;
>>>       child_lp->last_resume_kind = resume_stop;
>>>
>>>       /* Let the thread_db layer learn about this new process.  */
>>>       check_for_thread_db ();
>>>     }
>>> }
>>>
>>> Nothing appears to switch inferior_ptid to the child, so seems
>>> like we're adding the child_lp with the wrong lwp (and calling
>>> check_for_thread_db in the wrong context) ?  Is this managing
>>> to work by chance because follow_fork_inferior leaves inferior_ptid
>>> pointing to the child?
>>
>> Yes, follow_fork_inferior always sets inferior_ptid to the followed
>> inferior.  Then on entry, linux_child_follow_fork expects inferior_ptid
>> to be the followed inferior.  So I think it is getting the expected
>> inferior from inferior_ptid in these cases.
>>
>> In the original code, inferior_ptid was the parent on entry to
>> target_follow_fork, and the followed inferior after return.  I made
>> follow_fork_inferior do the same thing (return the followed inferior),
>> but it would be no problem to have it return the parent and have
>> linux_child_follow_fork expect inferior_ptid to be the parent and return
>> the followed inferior (like it did before) if that would be preferable.
>> Let me know if you'd like me to make that change.
>>
>> Regarding check_for_thread_db, there is something unrelated that I don't
>> understand.  Maybe you can clear this up for me.  If we have reached
>> linux_child_follow_fork, then aren't we guaranteed that
>> PTRACE_O_TRACECLONE is supported, and that we are using that instead of
>> libthread_db for detecting thread events?  If so, why do we need to call
>> check_for_thread_db at all?
>>
>>   Then this at the top uses the wrong
>>> inferior_thread ():
>>>
>>>   has_vforked = (inferior_thread ()->pending_follow.kind
>>> 		 == TARGET_WAITKIND_VFORKED);
>>>
>>>
>>> and we're lucky that nothing end up using has_vforked in the
>>> follow child path?
>>
>> You are right, this is incorrect and unnecessary in the case where we
>> are following the child.
>>
>>>
>>> I'd much rather we don't have these assumptions in place.
>>
>> Would an acceptable solution be to move the definitions and assignments
>> of has_vforked, parent_pid, and child_pid into the follow-parent case,
>> as below?

This change was made per your previous comments, and comments added to
specify how inferior_ptid is changed by follow_fork_inferior and the
target follow_fork routines.

>>
>> static int
>> linux_child_follow_fork (struct target_ops *ops, int follow_child,
>>                          int detach_fork)
>> {
>>   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
>>>
>>> These files / targets also have to_follow_fork implementations:
>>>
>>>  inf-ptrace.c:  t->to_follow_fork = inf_ptrace_follow_fork;
>>>  inf-ttrace.c:  t->to_follow_fork = inf_ttrace_follow_fork;
>>>
>>> which will break if we don't adjust them as well.  Did you
>>> check whether the refactored code (follow_fork_inferior)
>>> makes sense for those?
>>
>> I completely missed these; sorry about that.  In my initial response to
>> your review, I thought I should be able to make similar changes to these
>> functions that maintained the existing functionality.  After digging into
>> this, I still think it is possible, but that a more prudent approach
>> would be to make follow_fork_inferior into a new target hook that just
>> returns zero for the non-Linux cases.
> 
> 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.

> 
>> 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.

> 
>> The changes
>> required to make it work with follow_fork_inferior are more significant
>> than I'd like, given that I don't have any way to test how the OS
>> reports the events.  
> 
> Don't worry so much about the testing.  We can go best effort, and
> if someone can help with testing, good.  If not, I'd still push
> forward and have interested parties fix things up when they
> stumble on issues.  I'm mostly interested in checking whether
> the model we're committing to makes sense and is at the right level.

I haven't tested the changes to either of inf-ptrace.c or inf-ttrace.c,
or even built them, lacking a suitable environment.  I did re-test on
x64 Ubuntu.

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". :-)

> 
> Thanks,
> Pedro Alves
> 

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.

 	  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

Don Breazeal Sept. 22, 2014, 3:52 p.m. UTC | #1
On 9/12/2014 9:50 AM, Breazeal, Don wrote:
> On 9/9/2014 4:09 AM, Pedro Alves wrote:
>> On 09/09/2014 12:54 AM, Breazeal, Don wrote:
>>> Hi Pedro,
>>>
>>> I've consolidated my responses to the issues you raised in this email,
>>> and attached an updated patch containing the proposed solutions.
>>>
>>> On 9/5/2014 7:20 AM, Pedro Alves wrote:
>>>> linux_child_follow_fork ends up with:
>>>>
>>>> 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 (!follow_child)
>>>>     {
>>>> ...
>>>>     }
>>>>   else
>>>>     {
>>>>       struct lwp_info *child_lp;
>>>>
>>>>       child_lp = add_lwp (inferior_ptid);
>>>>       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>>>       child_lp->stopped = 1;
>>>>       child_lp->last_resume_kind = resume_stop;
>>>>
>>>>       /* Let the thread_db layer learn about this new process.  */
>>>>       check_for_thread_db ();
>>>>     }
>>>> }
>>>>
>>>> Nothing appears to switch inferior_ptid to the child, so seems
>>>> like we're adding the child_lp with the wrong lwp (and calling
>>>> check_for_thread_db in the wrong context) ?  Is this managing
>>>> to work by chance because follow_fork_inferior leaves inferior_ptid
>>>> pointing to the child?
>>>
>>> Yes, follow_fork_inferior always sets inferior_ptid to the followed
>>> inferior.  Then on entry, linux_child_follow_fork expects inferior_ptid
>>> to be the followed inferior.  So I think it is getting the expected
>>> inferior from inferior_ptid in these cases.
>>>
>>> In the original code, inferior_ptid was the parent on entry to
>>> target_follow_fork, and the followed inferior after return.  I made
>>> follow_fork_inferior do the same thing (return the followed inferior),
>>> but it would be no problem to have it return the parent and have
>>> linux_child_follow_fork expect inferior_ptid to be the parent and return
>>> the followed inferior (like it did before) if that would be preferable.
>>> Let me know if you'd like me to make that change.
>>>
>>> Regarding check_for_thread_db, there is something unrelated that I don't
>>> understand.  Maybe you can clear this up for me.  If we have reached
>>> linux_child_follow_fork, then aren't we guaranteed that
>>> PTRACE_O_TRACECLONE is supported, and that we are using that instead of
>>> libthread_db for detecting thread events?  If so, why do we need to call
>>> check_for_thread_db at all?
>>>
>>>   Then this at the top uses the wrong
>>>> inferior_thread ():
>>>>
>>>>   has_vforked = (inferior_thread ()->pending_follow.kind
>>>> 		 == TARGET_WAITKIND_VFORKED);
>>>>
>>>>
>>>> and we're lucky that nothing end up using has_vforked in the
>>>> follow child path?
>>>
>>> You are right, this is incorrect and unnecessary in the case where we
>>> are following the child.
>>>
>>>>
>>>> I'd much rather we don't have these assumptions in place.
>>>
>>> Would an acceptable solution be to move the definitions and assignments
>>> of has_vforked, parent_pid, and child_pid into the follow-parent case,
>>> as below?
> 
> This change was made per your previous comments, and comments added to
> specify how inferior_ptid is changed by follow_fork_inferior and the
> target follow_fork routines.
> 
>>>
>>> static int
>>> linux_child_follow_fork (struct target_ops *ops, int follow_child,
>>>                          int detach_fork)
>>> {
>>>   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
>>>>
>>>> These files / targets also have to_follow_fork implementations:
>>>>
>>>>  inf-ptrace.c:  t->to_follow_fork = inf_ptrace_follow_fork;
>>>>  inf-ttrace.c:  t->to_follow_fork = inf_ttrace_follow_fork;
>>>>
>>>> which will break if we don't adjust them as well.  Did you
>>>> check whether the refactored code (follow_fork_inferior)
>>>> makes sense for those?
>>>
>>> I completely missed these; sorry about that.  In my initial response to
>>> your review, I thought I should be able to make similar changes to these
>>> functions that maintained the existing functionality.  After digging into
>>> this, I still think it is possible, but that a more prudent approach
>>> would be to make follow_fork_inferior into a new target hook that just
>>> returns zero for the non-Linux cases.
>>
>> 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.
> 
>>
>>> 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.
> 
>>
>>> The changes
>>> required to make it work with follow_fork_inferior are more significant
>>> than I'd like, given that I don't have any way to test how the OS
>>> reports the events.  
>>
>> Don't worry so much about the testing.  We can go best effort, and
>> if someone can help with testing, good.  If not, I'd still push
>> forward and have interested parties fix things up when they
>> stumble on issues.  I'm mostly interested in checking whether
>> the model we're committing to makes sense and is at the right level.
> 
> I haven't tested the changes to either of inf-ptrace.c or inf-ttrace.c,
> or even built them, lacking a suitable environment.  I did re-test on
> x64 Ubuntu.
> 
> 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". :-)
> 
>>
>> Thanks,
>> Pedro Alves
>>
> 
> 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.
> 
> ===============================================
> Annotated version of inf_ttrace_follow_fork:
> 
> /* 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;
> 
> 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);
> 
>   /* BLOCK 1: Since we know that on entry inferior_ptid will be the
>      ptid of the followed inferior, we can use that to derive the pid
>      information we need without any system calls.  Delete this block,
>      use inferior_ptid for the child ptid when following the child, and
>      use inferior_thread ()->pending_follow to find the child ptid
>      when following the parent.  */
>   /* 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;
>     }
>   /* BLOCK 1 END.  */
> 
>   if (follow_child)
>     {
>       /* BLOCK 2: this block is replaced almost verbatim by equivalent
> 	 code in follow_fork_inferior.  We delete this block, except
> 	 for the line that switches inferior_ptid to the 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);
>       /* END BLOCK 2.  */
> 
>       /* BLOCK 3: inf_ttrace_follow_fork deals with breakpoints by
> 	 using detach_breakpoints on the unfollowed inferior, then just
> 	 before returning it calls detach_inferior to clean up all of the
> 	 data structures related to the inferior, including the breakpoint
> 	 structures associated withthe inferior.  By contrast,
> 	 follow_fork_inferior calls remove_breakpoints_pid to clean up
> 	 the structures immediately, and it leaves the inferior intact
> 	 after detaching from it.  This allows the user to go back and
> 	 run the inferior later.  Note that follow_fork_inferior cleans up
> 	 the data structures for an unfollowed child, because from the user
> 	 standpoint it was never attached.
> 	
> 	 The bottom line is that we can delete this call, and the other
> 	 detach/reattach_breakpoints calls in this function.  */
>       detach_breakpoints (ptid_build (pid, lwpid, 0));
>       /* END BLOCK 3.  */
> 
>       /* BLOCK 4: This output is replaced by code in follow_fork_inferior.
> 	 The only difference is that in follow_fork_inferior, the user must
> 	 enable the output via 'set verbose' or 'set debug infrun 1'.  So
> 	 we delete this block.  */
>       target_terminal_ours ();
>       fprintf_unfiltered (gdb_stdlog,
> 			  _("Attaching after fork to child process %ld.\n"),
> 			  (long)fpid);
>       /* END BLOCK 4.  */
>     }
>   else
>     {
>       /* BLOCK 5: We don't need to do this, since we are guaranteed that
> 	 inferior_ptid already contained the parent's ptid on entry.  So
> 	 we delete this.  */
>       inferior_ptid = ptid_build (pid, lwpid, 0);
>       /* END BLOCK 5.  */
> 
>       /* BLOCK 6: As in BLOCK 3, removal and cleanup of breakpoints is
> 	 handled by follow_fork_inferior, and as in BLOCK 4, the output
> 	 is also handled by follow_fork_inferior.  Delete this block.  */
>       /* 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);
>       /* END BLOCK 6.  */
>     }
> 
>   if (tts.tts_event == TTEVT_VFORK)
>     {
>       gdb_assert (!tts.tts_u.tts_fork.tts_isparent);
> 
>       if (follow_child)
> 	{
> 	  /* BLOCK 7: This is the moral equivalent of struct
> 	     inferior::vfork_parent.  When we report the vfork (child)
> 	     event to infrun.c, follow_fork_inferior will set up the
> 	     inferiors with all the info (vfork_parent, pending_detach,
> 	     vfork_child) required to know how and when to detach the
> 	     unfollowed vfork parent.  In inf_ttrace_wait we will need
> 	     to report TARGET_WAITKIND_VFORK_DONE to infrun.c for
> 	     TTEVT_VFORK events reported for the parent.  We can delete
> 	     this block and eliminate the variable entirely.  */
> 	  /* We can't detach from the parent yet.  */
> 	  inf_ttrace_vfork_ppid = pid;
> 	  /* END BLOCK 7.  */
> 
> 	  /* BLOCK 8: as before, let follow_fork_inferior manage the
> 	     breakpoints.  Delete this.  */
> 	  reattach_breakpoints (fpid);
> 	  /* END BLOCK 8.  */
> 	}
>       else
> 	{
> 	  /* BLOCK 9: All of the target follow fork functions are
> 	     expected to perform the detach from an unfollowed fork
> 	     child.  (Not just vfork.)  We may be able to consolidate
> 	     with detach of regular fork child.  */
> 	  if (ttrace (TT_PROC_DETACH, fpid, 0, 0, 0, 0) == -1)
> 	    perror_with_name (("ttrace"));
> 	  /* END BLOCK 9.  */
> 
> 	  /* BLOCK 10: We want to let the generic event handling code
> 	     deal with this.  Modify inf_ttrace_wait vfork event handler
> 	     to help with this.  */
> 	  /* 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);
> 	  /* END BLOCK 10.  */
> 
> 	  /* BLOCK 11: as before, let follow_fork_inferior manage the
> 	     breakpoints.  Delete this.  */
> 	  reattach_breakpoints (pid);
> 	  /* END BLOCK 11.  */
> 	}
>     }
>   else
>     {
>       gdb_assert (tts.tts_u.tts_fork.tts_isparent);
> 
>       if (follow_child)
> 	{
> 	  /* BLOCK 12: follow_fork_inferior takes care of detaching the
> 	     parent when it calls target_detach.  Delete this.  */
> 	  if (ttrace (TT_PROC_DETACH, pid, 0, 0, 0, 0) == -1)
> 	    perror_with_name (("ttrace"));
> 	  /* END BLOCK 12.  */
> 	}
>       else
> 	{
> 	  /* BLOCK 13: This is like BLOCK 9 - we need to detach all
> 	     unfollowed child processes.  Keep this in some form.  */
> 	  if (ttrace (TT_PROC_DETACH, fpid, 0, 0, 0, 0) == -1)
> 	    perror_with_name (("ttrace"));
> 	  /* END BLOCK 13.  */
> 	}
>     }
> 
>   if (follow_child)
>     {
>       struct thread_info *ti;
> 
>       /* BLOCK 14: These variables are used throughout this file, so
> 	 we need to keep this.  */
>       /* The child will start out single-threaded.  */
>       inf_ttrace_num_lwps = 1;
>       inf_ttrace_num_lwps_in_syscall = 0;
>       /* END BLOCK 14.  */
> 
>       /* BLOCK 15: This is handled by follow_fork_inferior when it
> 	 calls target_detach on the parent, and we don't want to delete
> 	 the parent inferior, but leave it in the list for possible re-run
> 	 later on.  */
>       /* 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);
>       /* END BLOCK 15.  */
> 
>       /* BLOCK 16: We need to keep this target-specific code.  */
>       ti->private =
> 	xmalloc (sizeof (struct inf_ttrace_private_thread_info));
>       memset (ti->private, 0,
> 	      sizeof (struct inf_ttrace_private_thread_info));
>       /* END BLOCK 16.  */
>     }
> 
>   return 0;
> }
> 
> ===============================================
> Annotated version of inf_ptrace_follow_fork:
> 
> static int
> inf_ptrace_follow_fork (struct target_ops *ops, int follow_child,
> 			int detach_fork)
> {
>   /* BLOCK 1: Since we know that on entry inferior_ptid will be the
>      ptid of the followed inferior, we can use that to derive the pid
>      information we need without any system calls.  Delete this block,
>      use inferior_ptid for the child ptid when following the child, and
>      use inferior_thread ()->pending_follow to find the child ptid
>      when following the parent.  */
>   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;
>   /* BLOCK 1.  */
> 
>   if (follow_child)
>     {
>       /* BLOCK 2: This is all done in follow_fork_inferior, so delete.  */
>       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;
> 
>       /* 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"));
>       /* END BLOCK 2 */
> 
>       /* BLOCK 3: We must switch inferior_ptid to the child, so keep.  */
>       /* Switch inferior_ptid out of the parent's way.  */
>       inferior_ptid = pid_to_ptid (fpid);
>       /* END BLOCK 3.  */
> 
>       /* BLOCK 4: We don't detach the inferior, and the thread is added
> 	 by follow_fork_inferior.  */
>       /* Delete the parent.  */
>       detach_inferior (pid);
> 
>       add_thread_silent (inferior_ptid);
>       /* END BLOCK 4.  */
>     }
>   else
>     {
>       /* BLOCK 5: We are required to detach the child, so keep.  */
>       /* Breakpoints have already been detached from the child by
> 	 infrun.c.  */
> 
>       if (ptrace (PT_DETACH, fpid, (PTRACE_TYPE_ARG3)1, 0) == -1)
> 	perror_with_name (("ptrace"));
>       /* END BLOCK 5.  */
>     }
> 
>   return 0;
> }
> 
> =============================================
> 
> Thanks,
> --Don
> 	
> gdb/
> 2014-09-12  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.
> 	* infrun.c (follow_fork): Call follow_fork_inferior.
> 	(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 |  185 ++++++++--------------------------------
>  gdb/infrun.c     |  250
> +++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  gdb/infrun.h     |    2 -
>  gdb/linux-nat.c  |  243
> ++++++----------------------------------------------
>  5 files changed, 314 insertions(+), 414 deletions(-)
> 
> 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..3360ee7 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,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);
>        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);
>      }
> 
>    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);
>      }
> 
>    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)
> +	    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;
> +	}
>        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))
>  	  {
>  	    /* 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;
> +    }
> +
> +  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 (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 (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 0;
> +}
> +
> +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 1e8991d..de4ccc2 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));
>  	  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 ();
>      }
> 
Ping!

Thanks,
--Don
  
Pedro Alves Sept. 26, 2014, 6:13 p.m. UTC | #2
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 ();

>        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).

> 
>    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.

> 
>    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?

> +	    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.

> +	}
>        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.

>  	  {
>  	    /* 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.

> +      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.

Otherwise this looks good to me.

Thanks,
Pedro Alves
  

Patch

===============================================
Annotated version of inf_ttrace_follow_fork:

/* 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;

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);

  /* BLOCK 1: Since we know that on entry inferior_ptid will be the
     ptid of the followed inferior, we can use that to derive the pid
     information we need without any system calls.  Delete this block,
     use inferior_ptid for the child ptid when following the child, and
     use inferior_thread ()->pending_follow to find the child ptid
     when following the parent.  */
  /* 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;
    }
  /* BLOCK 1 END.  */

  if (follow_child)
    {
      /* BLOCK 2: this block is replaced almost verbatim by equivalent
	 code in follow_fork_inferior.  We delete this block, except
	 for the line that switches inferior_ptid to the 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);
      /* END BLOCK 2.  */

      /* BLOCK 3: inf_ttrace_follow_fork deals with breakpoints by
	 using detach_breakpoints on the unfollowed inferior, then just
	 before returning it calls detach_inferior to clean up all of the
	 data structures related to the inferior, including the breakpoint
	 structures associated withthe inferior.  By contrast,
	 follow_fork_inferior calls remove_breakpoints_pid to clean up
	 the structures immediately, and it leaves the inferior intact
	 after detaching from it.  This allows the user to go back and
	 run the inferior later.  Note that follow_fork_inferior cleans up
	 the data structures for an unfollowed child, because from the user
	 standpoint it was never attached.
	
	 The bottom line is that we can delete this call, and the other
	 detach/reattach_breakpoints calls in this function.  */
      detach_breakpoints (ptid_build (pid, lwpid, 0));
      /* END BLOCK 3.  */

      /* BLOCK 4: This output is replaced by code in follow_fork_inferior.
	 The only difference is that in follow_fork_inferior, the user must
	 enable the output via 'set verbose' or 'set debug infrun 1'.  So
	 we delete this block.  */
      target_terminal_ours ();
      fprintf_unfiltered (gdb_stdlog,
			  _("Attaching after fork to child process %ld.\n"),
			  (long)fpid);
      /* END BLOCK 4.  */
    }
  else
    {
      /* BLOCK 5: We don't need to do this, since we are guaranteed that
	 inferior_ptid already contained the parent's ptid on entry.  So
	 we delete this.  */
      inferior_ptid = ptid_build (pid, lwpid, 0);
      /* END BLOCK 5.  */

      /* BLOCK 6: As in BLOCK 3, removal and cleanup of breakpoints is
	 handled by follow_fork_inferior, and as in BLOCK 4, the output
	 is also handled by follow_fork_inferior.  Delete this block.  */
      /* 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);
      /* END BLOCK 6.  */
    }

  if (tts.tts_event == TTEVT_VFORK)
    {
      gdb_assert (!tts.tts_u.tts_fork.tts_isparent);

      if (follow_child)
	{
	  /* BLOCK 7: This is the moral equivalent of struct
	     inferior::vfork_parent.  When we report the vfork (child)
	     event to infrun.c, follow_fork_inferior will set up the
	     inferiors with all the info (vfork_parent, pending_detach,
	     vfork_child) required to know how and when to detach the
	     unfollowed vfork parent.  In inf_ttrace_wait we will need
	     to report TARGET_WAITKIND_VFORK_DONE to infrun.c for
	     TTEVT_VFORK events reported for the parent.  We can delete
	     this block and eliminate the variable entirely.  */
	  /* We can't detach from the parent yet.  */
	  inf_ttrace_vfork_ppid = pid;
	  /* END BLOCK 7.  */

	  /* BLOCK 8: as before, let follow_fork_inferior manage the
	     breakpoints.  Delete this.  */
	  reattach_breakpoints (fpid);
	  /* END BLOCK 8.  */
	}
      else
	{
	  /* BLOCK 9: All of the target follow fork functions are
	     expected to perform the detach from an unfollowed fork
	     child.  (Not just vfork.)  We may be able to consolidate
	     with detach of regular fork child.  */
	  if (ttrace (TT_PROC_DETACH, fpid, 0, 0, 0, 0) == -1)
	    perror_with_name (("ttrace"));
	  /* END BLOCK 9.  */

	  /* BLOCK 10: We want to let the generic event handling code
	     deal with this.  Modify inf_ttrace_wait vfork event handler
	     to help with this.  */
	  /* 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);
	  /* END BLOCK 10.  */

	  /* BLOCK 11: as before, let follow_fork_inferior manage the
	     breakpoints.  Delete this.  */
	  reattach_breakpoints (pid);
	  /* END BLOCK 11.  */
	}
    }
  else
    {
      gdb_assert (tts.tts_u.tts_fork.tts_isparent);

      if (follow_child)
	{
	  /* BLOCK 12: follow_fork_inferior takes care of detaching the
	     parent when it calls target_detach.  Delete this.  */
	  if (ttrace (TT_PROC_DETACH, pid, 0, 0, 0, 0) == -1)
	    perror_with_name (("ttrace"));
	  /* END BLOCK 12.  */
	}
      else
	{
	  /* BLOCK 13: This is like BLOCK 9 - we need to detach all
	     unfollowed child processes.  Keep this in some form.  */
	  if (ttrace (TT_PROC_DETACH, fpid, 0, 0, 0, 0) == -1)
	    perror_with_name (("ttrace"));
	  /* END BLOCK 13.  */
	}
    }

  if (follow_child)
    {
      struct thread_info *ti;

      /* BLOCK 14: These variables are used throughout this file, so
	 we need to keep this.  */
      /* The child will start out single-threaded.  */
      inf_ttrace_num_lwps = 1;
      inf_ttrace_num_lwps_in_syscall = 0;
      /* END BLOCK 14.  */

      /* BLOCK 15: This is handled by follow_fork_inferior when it
	 calls target_detach on the parent, and we don't want to delete
	 the parent inferior, but leave it in the list for possible re-run
	 later on.  */
      /* 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);
      /* END BLOCK 15.  */

      /* BLOCK 16: We need to keep this target-specific code.  */
      ti->private =
	xmalloc (sizeof (struct inf_ttrace_private_thread_info));
      memset (ti->private, 0,
	      sizeof (struct inf_ttrace_private_thread_info));
      /* END BLOCK 16.  */
    }

  return 0;
}

===============================================
Annotated version of inf_ptrace_follow_fork:

static int
inf_ptrace_follow_fork (struct target_ops *ops, int follow_child,
			int detach_fork)
{
  /* BLOCK 1: Since we know that on entry inferior_ptid will be the
     ptid of the followed inferior, we can use that to derive the pid
     information we need without any system calls.  Delete this block,
     use inferior_ptid for the child ptid when following the child, and
     use inferior_thread ()->pending_follow to find the child ptid
     when following the parent.  */
  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;
  /* BLOCK 1.  */

  if (follow_child)
    {
      /* BLOCK 2: This is all done in follow_fork_inferior, so delete.  */
      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;

      /* 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"));
      /* END BLOCK 2 */

      /* BLOCK 3: We must switch inferior_ptid to the child, so keep.  */
      /* Switch inferior_ptid out of the parent's way.  */
      inferior_ptid = pid_to_ptid (fpid);
      /* END BLOCK 3.  */

      /* BLOCK 4: We don't detach the inferior, and the thread is added
	 by follow_fork_inferior.  */
      /* Delete the parent.  */
      detach_inferior (pid);

      add_thread_silent (inferior_ptid);
      /* END BLOCK 4.  */
    }
  else
    {
      /* BLOCK 5: We are required to detach the child, so keep.  */
      /* Breakpoints have already been detached from the child by
	 infrun.c.  */

      if (ptrace (PT_DETACH, fpid, (PTRACE_TYPE_ARG3)1, 0) == -1)
	perror_with_name (("ptrace"));
      /* END BLOCK 5.  */
    }

  return 0;
}

=============================================

Thanks,
--Don
	
gdb/
2014-09-12  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.
	* infrun.c (follow_fork): Call follow_fork_inferior.
	(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 |  185 ++++++++--------------------------------
 gdb/infrun.c     |  250
+++++++++++++++++++++++++++++++++++++++++++++++++++++-
 gdb/infrun.h     |    2 -
 gdb/linux-nat.c  |  243
++++++----------------------------------------------
 5 files changed, 314 insertions(+), 414 deletions(-)

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..3360ee7 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,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);
       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);
     }

   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);
     }

   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)
+	    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;
+	}
       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))
 	  {
 	    /* 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;
+    }
+
+  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 (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 (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 0;
+}
+
+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 1e8991d..de4ccc2 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));