diff mbox

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

Message ID 540E41C5.2000600@codesourcery.com
State New
Headers show

Commit Message

Don Breazeal Sept. 8, 2014, 11:54 p.m. UTC
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?

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.

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.  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.  So I opted for turning follow_fork_inferior into a
target routine.

The updated patch is below.  I tested it on native x64 Ubuntu.  Let me
know what you think.

> 
> Thanks,
> Pedro Alves
> 

Thanks again for your comments,
--Don

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

	* infrun.c (follow_fork): Call target_follow_fork_inferior.
	(infrun_follow_fork_inferior): New function.
	(follow_inferior_reset_breakpoints): Make function static.
	* infrun.h (infrun_follow_fork_inferior): Declare.
	(follow_inferior_reset_breakpoints): Remove declaration.
	* linux-nat.c (linux_child_follow_fork): Move target-independent
	code to infrun.c:infrun_follow_fork_inferior.
	(linux_target_install_ops): Initialize new target_ops member.
	* target-delegates.c (delegate_follow_fork_inferior): New
	generated function.
	(debug_follow_fork_inferior): New generated function.
	(delegate_follow_fork): New generated function.
	(install_delegators): Initialize new target_ops member.
	(install_dummy_methods): Initialize new target_ops member.
	(init_debug_target): Initialize new target_ops member.
	* target.c (default_follow_fork_inferior): New function.
	(target_follow_fork_inferior): New function.
	* target.h (struct target_ops)<to_follow_fork_inferior>: New
	member.
	
---
 gdb/infrun.c           | 247
++++++++++++++++++++++++++++++++++++++++++++++++-
 gdb/infrun.h           |   5 +-
 gdb/linux-nat.c        | 238
+++++------------------------------------------
 gdb/target-delegates.c |  29 ++++++
 gdb/target.c           |  23 +++++
 gdb/target.h           |   7 +-
 6 files changed, 324 insertions(+), 225 deletions(-)

 /* On some targets, we can catch an inferior exec event when it

Comments

Pedro Alves Sept. 9, 2014, 11:09 a.m. UTC | #1
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?
> 
> 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.

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

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

Thanks,
Pedro Alves
diff mbox

Patch

diff --git a/gdb/infrun.c b/gdb/infrun.c
index c18267f..b4c10de 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,8 @@  static int restore_selected_frame (void *);

 static int follow_fork (void);

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

@@ -486,9 +489,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 (target_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 +565,241 @@  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.  */
+
+int
+infrun_follow_fork_inferior (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)
+    {
+      /* 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..bc1ce52 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -80,6 +80,9 @@  extern int execution_direction;
    register).  */
 extern struct regcache *stop_registers;

+extern int infrun_follow_fork_inferior (struct target_ops *ops,
+					int follow_child, int detach_fork);
+
 extern void start_remote (int from_tty);

 /* Clear out all variables saying what to do when inferior is
@@ -115,8 +118,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..d82dfca 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"
@@ -373,75 +372,32 @@  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)
@@ -480,82 +436,15 @@  holding the child stopped.  Try \"set
detach-on-fork\" or \
 	}
       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 +517,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 ();
     }
@@ -4510,6 +4313,7 @@  linux_target_install_ops (struct target_ops *t)
   t->to_pid_to_exec_file = linux_child_pid_to_exec_file;
   t->to_post_startup_inferior = linux_child_post_startup_inferior;
   t->to_post_attach = linux_child_post_attach;
+  t->to_follow_fork_inferior = infrun_follow_fork_inferior;
   t->to_follow_fork = linux_child_follow_fork;
   t->to_make_corefile_notes = linux_nat_make_corefile_notes;

diff --git a/gdb/target-delegates.c b/gdb/target-delegates.c
index 843a954..4469394 100644
--- a/gdb/target-delegates.c
+++ b/gdb/target-delegates.c
@@ -1046,6 +1046,31 @@  debug_remove_vfork_catchpoint (struct target_ops
*self, int arg1)
 }

 static int
+delegate_follow_fork_inferior (struct target_ops *self, int arg1, int arg2)
+{
+  self = self->beneath;
+  return self->to_follow_fork_inferior (self, arg1, arg2);
+}
+
+static int
+debug_follow_fork_inferior (struct target_ops *self, int arg1, int arg2)
+{
+  int result;
+  fprintf_unfiltered (gdb_stdlog, "-> %s->to_follow_fork_inferior
(...)\n", debug_target.to_shortname);
+  result = debug_target.to_follow_fork_inferior (&debug_target, arg1,
arg2);
+  fprintf_unfiltered (gdb_stdlog, "<- %s->to_follow_fork_inferior (",
debug_target.to_shortname);
+  target_debug_print_struct_target_ops_p (&debug_target);
+  fputs_unfiltered (", ", gdb_stdlog);
+  target_debug_print_int (arg1);
+  fputs_unfiltered (", ", gdb_stdlog);
+  target_debug_print_int (arg2);
+  fputs_unfiltered (") = ", gdb_stdlog);
+  target_debug_print_int (result);
+  fputs_unfiltered ("\n", gdb_stdlog);
+  return result;
+}
+
+static int
 delegate_follow_fork (struct target_ops *self, int arg1, int arg2)
 {
   self = self->beneath;
@@ -3832,6 +3857,8 @@  install_delegators (struct target_ops *ops)
     ops->to_insert_vfork_catchpoint = delegate_insert_vfork_catchpoint;
   if (ops->to_remove_vfork_catchpoint == NULL)
     ops->to_remove_vfork_catchpoint = delegate_remove_vfork_catchpoint;
+  if (ops->to_follow_fork_inferior == NULL)
+    ops->to_follow_fork_inferior = delegate_follow_fork_inferior;
   if (ops->to_follow_fork == NULL)
     ops->to_follow_fork = delegate_follow_fork;
   if (ops->to_insert_exec_catchpoint == NULL)
@@ -4077,6 +4104,7 @@  install_dummy_methods (struct target_ops *ops)
   ops->to_remove_fork_catchpoint = tdefault_remove_fork_catchpoint;
   ops->to_insert_vfork_catchpoint = tdefault_insert_vfork_catchpoint;
   ops->to_remove_vfork_catchpoint = tdefault_remove_vfork_catchpoint;
+  ops->to_follow_fork_inferior = default_follow_fork_inferior;
   ops->to_follow_fork = default_follow_fork;
   ops->to_insert_exec_catchpoint = tdefault_insert_exec_catchpoint;
   ops->to_remove_exec_catchpoint = tdefault_remove_exec_catchpoint;
@@ -4221,6 +4249,7 @@  init_debug_target (struct target_ops *ops)
   ops->to_remove_fork_catchpoint = debug_remove_fork_catchpoint;
   ops->to_insert_vfork_catchpoint = debug_insert_vfork_catchpoint;
   ops->to_remove_vfork_catchpoint = debug_remove_vfork_catchpoint;
+  ops->to_follow_fork_inferior = debug_follow_fork_inferior;
   ops->to_follow_fork = debug_follow_fork;
   ops->to_insert_exec_catchpoint = debug_insert_exec_catchpoint;
   ops->to_remove_exec_catchpoint = debug_remove_exec_catchpoint;
diff --git a/gdb/target.c b/gdb/target.c
index 8bf6031..53fb9bc 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -62,6 +62,9 @@  static void default_rcmd (struct target_ops *, const
char *, struct ui_file *);
 static ptid_t default_get_ada_task_ptid (struct target_ops *self,
 					 long lwp, long tid);

+static int default_follow_fork_inferior (struct target_ops *self,
+					 int follow_child, int detach_fork);
+
 static int default_follow_fork (struct target_ops *self, int follow_child,
 				int detach_fork);

@@ -2098,6 +2101,26 @@  target_program_signals (int numsigs, unsigned
char *program_signals)
 					numsigs, program_signals);
 }

+/* Default target function for follow_fork_inferior hook.  Some targets
+   follow forks without using follow_fork_inferior functionality, so we
+   just return success.  */
+
+static int
+default_follow_fork_inferior (struct target_ops *self, int follow_child,
+			      int detach_fork)
+{
+  return 0;
+}
+
+/* Target wrapper for follow_fork_inferior hook.  */
+
+int
+target_follow_fork_inferior (int follow_child, int detach_fork)
+{
+  return current_target.to_follow_fork_inferior (&current_target,
+						 follow_child, detach_fork);
+}
+
 static int
 default_follow_fork (struct target_ops *self, int follow_child,
 		     int detach_fork)
diff --git a/gdb/target.h b/gdb/target.h
index 4d91b6b..bdba55f 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -531,6 +531,8 @@  struct target_ops
       TARGET_DEFAULT_RETURN (1);
     int (*to_remove_vfork_catchpoint) (struct target_ops *, int)
       TARGET_DEFAULT_RETURN (1);
+    int (*to_follow_fork_inferior) (struct target_ops *, int, int)
+      TARGET_DEFAULT_FUNC (default_follow_fork_inferior);
     int (*to_follow_fork) (struct target_ops *, int, int)
       TARGET_DEFAULT_FUNC (default_follow_fork);
     int (*to_insert_exec_catchpoint) (struct target_ops *, int)
@@ -1478,14 +1480,15 @@  extern void target_load (const char *arg, int
from_tty);
 #define target_remove_vfork_catchpoint(pid) \
      (*current_target.to_remove_vfork_catchpoint) (&current_target, pid)

-/* If the inferior forks or vforks, this function will be called at
+/* If the inferior forks or vforks, these functions will be called at
    the next resume in order to perform any bookkeeping and fiddling
    necessary to continue debugging either the parent or child, as
    requested, and releasing the other.  Information about the fork
    or vfork event is available via get_last_target_status ().
-   This function returns 1 if the inferior should not be resumed
+   The functions return 1 if the inferior should not be resumed
    (i.e. there is another event pending).  */

+int target_follow_fork_inferior (int follow_child, int detach_fork);
 int target_follow_fork (int follow_child, int detach_fork);