[RFC] fork in multi-threaded apps, per-thread pending_fork, other follow-fork cleanups.

Pedro Alves pedro@codesourcery.com
Tue May 19 21:38:00 GMT 2009


In trying to clean up more multi-exec bits for submission,
I came up with this patch at the bottom.

Let me start with a question.  Take this example --- a thread in
a multi-threaded forks, and we catch the event with a catchpoint:

 (gdb) set follow-fork-mode child
 (gdb) catch fork
 Catchpoint 1 (fork)
 (gdb) r
 Starting program: /home/pedro/gdb/sspaces/build/gdb/testsuite/gdb.threads/fork-thread-pending
 [Thread debugging using libthread_db enabled]
 [New Thread 0x40800950 (LWP 6277)]
 Thread <0> executing
 [New Thread 0x41001950 (LWP 6278)]
 Thread <1> executing
 [New Thread 0x41802950 (LWP 6280)]
 Thread <2> executing
 [New Thread 0x42003950 (LWP 6281)]
 Thread <3> executing
 [New Thread 0x42804950 (LWP 6282)]
 Thread <4> executing
 [New Thread 0x43005950 (LWP 6283)]
 Thread <5> executing
 [New Thread 0x43806950 (LWP 6284)]
 Thread <6> executing
 [New Thread 0x44007950 (LWP 6285)]
 Thread <7> executing
 [New Thread 0x44808950 (LWP 6286)]
 Thread <8> executing
 [New Thread 0x45009950 (LWP 6287)]
 Thread forker <9> executing
 [Switching to Thread 0x45009950 (LWP 6287)]

 Catchpoint 1 (forked process 6288), 0x00007ffff767ec4b in fork () from /lib/libc.so.6
 (gdb) info threads
 During symbol reading, incomplete CFI data; unspecified registers (e.g., rax) at 0x7ffff767ebcf.
 * 11 Thread 0x45009950 (LWP 6287)  0x00007ffff767ec4b in fork () from /lib/libc.so.6
   10 Thread 0x44808950 (LWP 6286)  0x00007ffff767eb85 in nanosleep () from /lib/libc.so.6
   9 Thread 0x44007950 (LWP 6285)  0x00007ffff767eb81 in nanosleep () from /lib/libc.so.6
   8 Thread 0x43806950 (LWP 6284)  0x00007ffff767eb81 in nanosleep () from /lib/libc.so.6
   7 Thread 0x43005950 (LWP 6283)  0x00007ffff767eb81 in nanosleep () from /lib/libc.so.6
   6 Thread 0x42804950 (LWP 6282)  0x00007ffff767eb81 in nanosleep () from /lib/libc.so.6
   5 Thread 0x42003950 (LWP 6281)  0x00007ffff767eb81 in nanosleep () from /lib/libc.so.6
   4 Thread 0x41802950 (LWP 6280)  0x00007ffff767eb81 in nanosleep () from /lib/libc.so.6
   3 Thread 0x41001950 (LWP 6278)  0x00007ffff767eb81 in nanosleep () from /lib/libc.so.6
   2 Thread 0x40800950 (LWP 6277)  0x00007ffff767eb81 in nanosleep () from /lib/libc.so.6
   1 Thread 0x7ffff7fd66e0 (LWP 6272)  0x00007ffff7bcb796 in pthread_join () from /lib/libpthread.so.0
 
So far, so good.  The current follow-fork design doesn't account for GDB being
able to debug multiple processes, so, only on next resume will the target switch the
the fork child.  But, what should happen if the user switches to another thread
before issuing a resume, like so?

 (gdb) t 10
 [Switching to thread 10 (Thread 0x44808950 (LWP 6286))]#0  0x00007ffff767eb85 in nanosleep () from /lib/libc.so.6
 (gdb) next    

This will want to resume all threads in the process, but, it
will also switch to the child fork.  The "next" command will carry
on in the fork child, but that is completely bogus!  The fork child
will be a copy of thread 11, not of thread 10, so this "next" operation
has really no meaning in the main thread of the child.  To be clear,
IMO, this next/step across fork only makes sense if applied to thread 11.

So, what to do?  One way to go about this, is to detect that
the user switched threads, since the fork event was reported, follow
the child, and refuse to resume it, leaving up to the user to
decide what to do with it:

 (gdb) next
 Single stepping until exit from function nanosleep,
 which has no line number information.
 [Thread debugging using libthread_db enabled]
 warning: Not resuming: switched threads before following fork child.
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 [Switching to Thread 0x45009950 (LWP 6288)]
 0x00007ffff767ec4b in fork () from /lib/libc.so.6
 (gdb)

 (gdb) info threads
 * 1 Thread 0x45009950 (LWP 6288)  0x00007ffff767ec4b in fork () from /lib/libc.so.6
 (gdb)

This will work for any command that wants to resume, of course,
next/step/finish/go/jump, etc.  What do you think?

For the record, here's what happens with current mainline:

 (gdb) next
 Single stepping until exit from function nanosleep,
 which has no line number information.
 [Thread debugging using libthread_db enabled]

 Program received signal SIGTRAP, Trace/breakpoint trap.
 0x00007ffff767ec51 in fork () from /lib/libc.so.6
 (gdb)  

SIGTRAP bogosity.


While doing this, I've taken to oportunity to clean up and fix several
of follow-fork known issues:

 - we follow the fork inside `resume', but only after setting up
   displaced stepping, and single-step breakpoints --- too late.

 - this concentrates the logic of "preserving the stepping state"
   over a next/step over fork in infrun.c, instead of having the
   target duplicate it.

I think we'll be getting back to the follow-fork design for
multi-process aware targets, though, non-multi-process aware
targets may still want this much support (I'm
thinking "target remote").


BTW, this next test exercises yesterday's glibc workaround
(in fact it's the same test I posted, dejagnuified)

-- 
Pedro Alves

2009-05-19  Pedro Alves  <pedro@codesourcery.com>

	* gdbthread.h (struct thread_info): New `pending_fellow' field.
	* thread.c (new_thread): New function.
	(add_thread_silent): Use it.
	* breakpoint.c (internal_breakpoint_number): New global, moved
	from inside...
	(create_internal_breakpoint): ... this.
	(clone_momentary_breakpoint): New.
	* breakpoint.h (clone_momentary_breakpoint): Declare.
	* infrun.c (nullify_last_target_wait_ptid): Move declaration
	higher.
	(pending_follow): Delete.
	(follow_fork): Handle pending follow fork event here.  Moved the
	preserving of thread stepping state here.
	(resume): Don't handle pending follow fork events here.  Only
	install the inferior's terminal modes if we're about to resume it.
	(proceed): Handle possible pending follow fork events here.
	(init_wait_for_inferior): No need to clear pending_follow anymore,
	it's gone.
	(handle_inferior_event): Adjust to per-thread `pending_follow'.
	Call `follow_fork' to handle following the fork.  If the
	follow-fork is cancelled, stop stepping.
	* linux-nat.c (linux_child_follow_fork): Adjust to per-thread
	`pending_follow' events.  Remove code that handled preserving the
	thread stepping state.

---
 gdb/breakpoint.c                                  |   37 +++
 gdb/breakpoint.h                                  |    2 
 gdb/gdbthread.h                                   |    5 
 gdb/inf-ptrace.c                                  |   37 ---
 gdb/inf-ttrace.c                                  |   44 ----
 gdb/infrun.c                                      |  233 ++++++++++++++++------
 gdb/linux-nat.c                                   |   38 ---
 gdb/testsuite/gdb.threads/fork-thread-pending.c   |  109 ++++++++++
 gdb/testsuite/gdb.threads/fork-thread-pending.exp |  122 +++++++++++
 gdb/thread.c                                      |   34 +--
 10 files changed, 489 insertions(+), 172 deletions(-)

Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h	2009-05-19 11:20:37.000000000 +0100
+++ src/gdb/gdbthread.h	2009-05-19 17:06:28.000000000 +0100
@@ -165,6 +165,11 @@ struct thread_info
      next time inferior stops if it stops due to stepping.  */
   int step_multi;
 
+  /* This is used to remember when a fork or vfork event was caught by
+     a catchpoint, and thus the event is to be followed at the next
+     resume of the thread, and not immediately.  */
+  struct target_waitstatus pending_follow;
+
   /* Last signal that the inferior received (why it stopped).  */
   enum target_signal stop_signal;
 
Index: src/gdb/thread.c
===================================================================
--- src.orig/gdb/thread.c	2009-05-19 11:20:38.000000000 +0100
+++ src/gdb/thread.c	2009-05-19 11:27:15.000000000 +0100
@@ -142,6 +142,25 @@ init_thread_list (void)
 }
 
 struct thread_info *
+new_thread (ptid_t ptid)
+{
+  struct thread_info *tp;
+
+  tp = xcalloc (1, sizeof (*tp));
+
+  tp->ptid = ptid;
+  tp->num = ++highest_thread_num;
+  tp->next = thread_list;
+  thread_list = tp;
+
+  /* Nothing to follow yet.  */
+  tp->pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
+  tp->state_ = THREAD_STOPPED;
+
+  return tp;
+}
+
+struct thread_info *
 add_thread_silent (ptid_t ptid)
 {
   struct thread_info *tp;
@@ -162,12 +181,7 @@ add_thread_silent (ptid_t ptid)
 
       if (ptid_equal (inferior_ptid, ptid))
 	{
-	  tp = xmalloc (sizeof (*tp));
-	  memset (tp, 0, sizeof (*tp));
-	  tp->ptid = minus_one_ptid;
-	  tp->num = ++highest_thread_num;
-	  tp->next = thread_list;
-	  thread_list = tp;
+	  tp = new_thread (ptid);
 
 	  /* Make switch_to_thread not read from the thread.  */
 	  tp->state_ = THREAD_EXITED;
@@ -191,13 +205,7 @@ add_thread_silent (ptid_t ptid)
 	delete_thread (ptid);
     }
 
-  tp = (struct thread_info *) xmalloc (sizeof (*tp));
-  memset (tp, 0, sizeof (*tp));
-  tp->ptid = ptid;
-  tp->num = ++highest_thread_num;
-  tp->next = thread_list;
-  thread_list = tp;
-
+  tp = new_thread (ptid);
   observer_notify_new_thread (tp);
 
   return tp;
Index: src/gdb/breakpoint.c
===================================================================
--- src.orig/gdb/breakpoint.c	2009-05-19 14:45:21.000000000 +0100
+++ src/gdb/breakpoint.c	2009-05-19 15:23:41.000000000 +0100
@@ -1456,10 +1456,11 @@ reattach_breakpoints (int pid)
   return 0;
 }
 
+static int internal_breakpoint_number = -1;
+
 static struct breakpoint *
 create_internal_breakpoint (CORE_ADDR address, enum bptype type)
 {
-  static int internal_breakpoint_number = -1;
   struct symtab_and_line sal;
   struct breakpoint *b;
 
@@ -5010,6 +5011,40 @@ set_momentary_breakpoint (struct symtab_
 }
 
 struct breakpoint *
+clone_momentary_breakpoint (struct breakpoint *bpkt)
+{
+  struct breakpoint *b;
+
+  /* If there's nothing to clone, then return nothing.  */
+  if (bpkt == NULL)
+    return NULL;
+
+  b = set_raw_breakpoint_without_location (bpkt->type);
+  b->loc = allocate_bp_location (b);
+  set_breakpoint_location_function (b->loc);
+
+  b->loc->requested_address = bpkt->loc->requested_address;
+  b->loc->address = bpkt->loc->address;
+  b->loc->section = bpkt->loc->section;
+
+  if (bpkt->source_file == NULL)
+    b->source_file = NULL;
+  else
+    b->source_file = xstrdup (bpkt->source_file);
+
+  b->line_number = bpkt->line_number;
+  b->frame_id = bpkt->frame_id;
+  b->thread = bpkt->thread;
+
+  b->enable_state = bp_enabled;
+  b->disposition = disp_donttouch;
+  b->number = internal_breakpoint_number--;
+
+  update_global_location_list_nothrow (0);
+  return b;
+}
+
+struct breakpoint *
 set_momentary_breakpoint_at_pc (CORE_ADDR pc, enum bptype type)
 {
   struct symtab_and_line sal;
Index: src/gdb/breakpoint.h
===================================================================
--- src.orig/gdb/breakpoint.h	2009-05-19 14:50:38.000000000 +0100
+++ src/gdb/breakpoint.h	2009-05-19 14:55:54.000000000 +0100
@@ -696,6 +696,8 @@ extern struct breakpoint *set_momentary_
 extern struct breakpoint *set_momentary_breakpoint_at_pc
   (CORE_ADDR pc, enum bptype type);
 
+extern struct breakpoint *clone_momentary_breakpoint (struct breakpoint *bpkt);
+
 extern void set_ignore_count (int, int, int);
 
 extern void set_default_breakpoint (int, CORE_ADDR, struct symtab *, int);
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c	2009-05-19 11:20:41.000000000 +0100
+++ src/gdb/infrun.c	2009-05-19 17:42:25.000000000 +0100
@@ -83,6 +83,8 @@ static int prepare_to_proceed (int);
 
 void _initialize_infrun (void);
 
+void nullify_last_target_wait_ptid (void);
+
 /* When set, stop the 'step' command if we enter a function which has
    no line number information.  The normal behavior is that we step
    over such function.  */
@@ -255,21 +257,6 @@ void init_thread_stepping_state (struct 
 
 void init_infwait_state (void);
 
-/* This is used to remember when a fork or vfork event was caught by a
-   catchpoint, and thus the event is to be followed at the next resume
-   of the inferior, and not immediately.  */
-static struct
-{
-  enum target_waitkind kind;
-  struct
-  {
-    ptid_t parent_pid;
-    ptid_t child_pid;
-  }
-  fork_event;
-}
-pending_follow;
-
 static const char follow_fork_mode_child[] = "child";
 static const char follow_fork_mode_parent[] = "parent";
 
@@ -290,12 +277,152 @@ Debugger response to a program call of f
 }
 

 
+/* Tell the target to follow the fork we're stopped at.  Returns true
+   if the inferior should be resumed; false, if the target for some
+   reason decided it's best not to resume.  */
+
 static int
 follow_fork (void)
 {
   int follow_child = (follow_fork_mode_string == follow_fork_mode_child);
+  int should_resume = 1;
+  struct thread_info *tp;
+
+  /* Copy user stepping state to the new inferior thread.  */
+  struct breakpoint *step_resume_breakpoint;
+  CORE_ADDR step_range_start;
+  CORE_ADDR step_range_end;
+  struct frame_id step_frame_id;
+
+  if (!non_stop)
+    {
+      ptid_t wait_ptid;
+      struct target_waitstatus wait_status;
+
+      /* Get the last target status returned by target_wait().  */
+      get_last_target_status (&wait_ptid, &wait_status);
+
+      /* If not stopped at a fork event, then there's nothing else to
+	 do.  */
+      if (wait_status.kind != TARGET_WAITKIND_FORKED
+	  && wait_status.kind != TARGET_WAITKIND_VFORKED)
+	return 1;
+
+      /* Check if we switched over from WAIT_PID, since the event was
+	 reported.  */
+      if (!ptid_equal (wait_ptid, minus_one_ptid)
+	  && !ptid_equal (inferior_ptid, wait_ptid))
+	{
+	  /* We did.  Switch back to WAIT_PID thread, to tell the
+	     target to follow it (in either direction).  We'll
+	     afterwards refuse to resume, and inform the user what
+	     happened.  */
+	  switch_to_thread (wait_ptid);
+	  should_resume = 0;
+	}
+    }
+
+  tp = inferior_thread ();
+
+  /* If there were any forks/vforks that were caught and are now to be
+     followed, then do so now.  */
+  switch (tp->pending_follow.kind)
+    {
+    case TARGET_WAITKIND_FORKED:
+    case TARGET_WAITKIND_VFORKED:
+      {
+	ptid_t parent, child;
+
+	/* If the user did a next/step, etc, over a fork call,
+	   preserve the stepping state in the fork child.  */
+	if (follow_child && should_resume)
+	  {
+	    step_resume_breakpoint
+	      = clone_momentary_breakpoint (tp->step_resume_breakpoint);
+
+	    /* For now, delete the parent's sr breakpoint, otherwise,
+	       parent/child sr breakpoints are considered duplicates,
+	       and the child version will not be installed.  Remove
+	       this when the breakpoints module becomes aware of
+	       inferiors and address spaces.  */
+	    delete_step_resume_breakpoint (tp);
+	    tp->step_resume_breakpoint = NULL;
+	    step_range_start = tp->step_range_start;
+	    step_range_end = tp->step_range_end;
+	    step_frame_id = tp->step_frame_id;
+	  }
+
+	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))
+	  {
+	    /* Target refused to follow, or there's some other reason
+	       we shouldn't resume.  */
+	    should_resume = 0;
+	  }
+	else
+	  {
+	    /* This pending follow fork event is now handled, one way
+	       or another.  The previous selected thread may be gone
+	       from the lists by now, but if it is still around, need
+	       to clear the pending follow request.  */
+	    tp = find_thread_pid (parent);
+	    if (tp)
+	      tp->pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
+
+	    /* This makes sure we don't try to apply the "Switched
+	       over from WAIT_PID" logic above.  */
+	    nullify_last_target_wait_ptid ();
+
+	    /* If we followed the child, switch to it... */
+	    if (follow_child)
+	      {
+		switch_to_thread (child);
+
+		/* ... and preserve the stepping state, in case the
+		   user was stepping over the fork call.  */
+		if (should_resume)
+		  {
+		    tp = inferior_thread ();
+		    tp->step_resume_breakpoint = step_resume_breakpoint;
+		    tp->step_range_start = step_range_start;
+		    tp->step_range_end = step_range_end;
+		    tp->step_frame_id = step_frame_id;
+		  }
+		else
+		  {
+		    /* If we get here, it was because we're trying to
+		       resume from a fork catchpoint, but, the user
+		       has switched threads away from the thread that
+		       forked.  In that case, the resume command
+		       issued is most likely not applicable to the
+		       child, so just warn, and resume to resume.  */
+		    warning (_("\
+Not resuming: switched threads before following fork child.\n"));
+		  }
+
+		/* Reset breakpoints in the child as appropriate.  */
+		follow_inferior_reset_breakpoints ();
+	      }
+	    else
+	      switch_to_thread (parent);
+	  }
+      }
+      break;
+    case TARGET_WAITKIND_SPURIOUS:
+      /* Nothing to follow.  */
+      break;
+    default:
+      internal_error (__FILE__, __LINE__,
+		      "Unexpected pending_follow.kind %d\n",
+		      tp->pending_follow.kind);
+      break;
+    }
 
-  return target_follow_fork (follow_child);
+  return should_resume;
 }
 
 void
@@ -987,8 +1114,6 @@ resume (int step, enum target_signal sig
 {
   int should_resume = 1;
   struct cleanup *old_cleanups = make_cleanup (resume_cleanups, 0);
-
-  /* Note that these must be reset if we follow a fork below.  */
   struct regcache *regcache = get_current_regcache ();
   struct gdbarch *gdbarch = get_regcache_arch (regcache);
   struct thread_info *tp = inferior_thread ();
@@ -1058,31 +1183,6 @@ a command like `return' or `jump' to con
   if (step)
     step = maybe_software_singlestep (gdbarch, pc);
 
-  /* If there were any forks/vforks/execs that were caught and are
-     now to be followed, then do so.  */
-  switch (pending_follow.kind)
-    {
-    case TARGET_WAITKIND_FORKED:
-    case TARGET_WAITKIND_VFORKED:
-      pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
-      if (follow_fork ())
-	should_resume = 0;
-
-      /* Following a child fork will change our notion of current
-	 thread.  */
-      tp = inferior_thread ();
-      regcache = get_current_regcache ();
-      gdbarch = get_regcache_arch (regcache);
-      pc = regcache_read_pc (regcache);
-      break;
-
-    default:
-      break;
-    }
-
-  /* Install inferior's terminal modes.  */
-  target_terminal_inferior ();
-
   if (should_resume)
     {
       ptid_t resume_ptid;
@@ -1164,6 +1264,9 @@ a command like `return' or `jump' to con
           displaced_step_dump_bytes (gdb_stdlog, buf, sizeof (buf));
         }
 
+      /* Install inferior's terminal modes.  */
+      target_terminal_inferior ();
+
       /* Avoid confusing the next resume, if the next stop/resume
 	 happens to apply to another thread.  */
       tp->stop_signal = TARGET_SIGNAL_0;
@@ -1305,12 +1408,26 @@ prepare_to_proceed (int step)
 void
 proceed (CORE_ADDR addr, enum target_signal siggnal, int step)
 {
-  struct regcache *regcache = get_current_regcache ();
-  struct gdbarch *gdbarch = get_regcache_arch (regcache);
+  struct regcache *regcache;
+  struct gdbarch *gdbarch;
   struct thread_info *tp;
-  CORE_ADDR pc = regcache_read_pc (regcache);
+  CORE_ADDR pc;
   int oneproc = 0;
 
+  /* If we're stopped at a fork/vfork, follow the branch set by the
+     "set follow-fork-mode" command; otherwise, we'll just proceed
+     resuming the current thread.  */
+  if (!follow_fork ())
+    {
+      /* The target for some reason decided not to resume.  */
+      normal_stop ();
+      return;
+    }
+
+  regcache = get_current_regcache ();
+  gdbarch = get_regcache_arch (regcache);
+  pc = regcache_read_pc (regcache);
+
   if (step > 0)
     step_start_function = find_pc_function (pc);
   if (step < 0)
@@ -1517,9 +1634,6 @@ init_wait_for_inferior (void)
 
   breakpoint_init_inferior (inf_starting);
 
-  /* The first resume is not following a fork/vfork/exec. */
-  pending_follow.kind = TARGET_WAITKIND_SPURIOUS;	/* I.e., none. */
-
   clear_proceed_status ();
 
   stepping_past_singlestep_breakpoint = 0;
@@ -1698,8 +1812,6 @@ infrun_thread_stop_requested (ptid_t pti
   iterate_over_threads (infrun_thread_stop_requested_callback, &ptid);
 }
 
-void nullify_last_target_wait_ptid (void);
-
 static void
 infrun_thread_thread_exit (struct thread_info *tp, int silent)
 {
@@ -2407,10 +2519,6 @@ handle_inferior_event (struct execution_
     case TARGET_WAITKIND_VFORKED:
       if (debug_infrun)
         fprintf_unfiltered (gdb_stdlog, "infrun: TARGET_WAITKIND_FORKED\n");
-      pending_follow.kind = ecs->ws.kind;
-
-      pending_follow.fork_event.parent_pid = ecs->ptid;
-      pending_follow.fork_event.child_pid = ecs->ws.value.related_pid;
 
       if (!ptid_equal (ecs->ptid, inferior_ptid))
 	{
@@ -2439,6 +2547,11 @@ handle_inferior_event (struct execution_
 	  detach_breakpoints (child_pid);
 	}
 
+      /* In case the event is caught by a catchpoint, remember that
+	 the event is to be followed at the next resume of the thread,
+	 and not immediately.  */
+      ecs->event_thread->pending_follow = ecs->ws;
+
       stop_pc = regcache_read_pc (get_thread_regcache (ecs->ptid));
 
       ecs->event_thread->stop_bpstat = bpstat_stop_status (stop_pc, ecs->ptid);
@@ -2449,7 +2562,15 @@ handle_inferior_event (struct execution_
       if (ecs->random_signal)
 	{
 	  ecs->event_thread->stop_signal = TARGET_SIGNAL_0;
-	  keep_going (ecs);
+
+	  if (follow_fork ())
+	    {
+	      ecs->event_thread = inferior_thread ();
+	      ecs->ptid = inferior_ptid;
+	      keep_going (ecs);
+	    }
+	  else
+	    stop_stepping (ecs);
 	  return;
 	}
       ecs->event_thread->stop_signal = TARGET_SIGNAL_TRAP;
Index: src/gdb/linux-nat.c
===================================================================
--- src.orig/gdb/linux-nat.c	2009-05-19 11:20:43.000000000 +0100
+++ src/gdb/linux-nat.c	2009-05-19 17:18:24.000000000 +0100
@@ -575,19 +575,16 @@ static int
 linux_child_follow_fork (struct target_ops *ops, int follow_child)
 {
   sigset_t prev_mask;
-  ptid_t last_ptid;
-  struct target_waitstatus last_status;
   int has_vforked;
   int parent_pid, child_pid;
-
   block_child_signals (&prev_mask);
 
-  get_last_target_status (&last_ptid, &last_status);
-  has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED);
-  parent_pid = ptid_get_lwp (last_ptid);
+  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 (last_ptid);
-  child_pid = PIDGET (last_status.value.related_pid);
+    parent_pid = ptid_get_pid (inferior_ptid);
+  child_pid = PIDGET (inferior_thread ()->pending_follow.value.related_pid);
 
   if (! follow_child)
     {
@@ -625,7 +622,7 @@ linux_child_follow_fork (struct target_o
 	  /* Add process to GDB's tables.  */
 	  child_inf = add_inferior (child_pid);
 
-	  parent_inf = find_inferior_pid (GET_PID (last_ptid));
+	  parent_inf = current_inferior ();
 	  child_inf->attach_flag = parent_inf->attach_flag;
 	  copy_terminal_info (child_inf, parent_inf);
 
@@ -692,21 +689,9 @@ linux_child_follow_fork (struct target_o
     }
   else
     {
-      struct thread_info *last_tp = find_thread_pid (last_ptid);
       struct thread_info *tp;
-      char child_pid_spelling[40];
       struct inferior *parent_inf, *child_inf;
 
-      /* Copy user stepping state to the new inferior thread.  */
-      struct breakpoint *step_resume_breakpoint = last_tp->step_resume_breakpoint;
-      CORE_ADDR step_range_start = last_tp->step_range_start;
-      CORE_ADDR step_range_end = last_tp->step_range_end;
-      struct frame_id step_frame_id = last_tp->step_frame_id;
-
-      /* Otherwise, deleting the parent would get rid of this
-	 breakpoint.  */
-      last_tp->step_resume_breakpoint = NULL;
-
       /* Before detaching from the parent, remove all breakpoints from it. */
       remove_breakpoints ();
 
@@ -723,7 +708,7 @@ linux_child_follow_fork (struct target_o
 
       child_inf = add_inferior (child_pid);
 
-      parent_inf = find_inferior_pid (GET_PID (last_ptid));
+      parent_inf = current_inferior ();
       child_inf->attach_flag = parent_inf->attach_flag;
       copy_terminal_info (child_inf, parent_inf);
 
@@ -772,15 +757,6 @@ linux_child_follow_fork (struct target_o
 
       linux_nat_switch_fork (inferior_ptid);
       check_for_thread_db ();
-
-      tp = inferior_thread ();
-      tp->step_resume_breakpoint = step_resume_breakpoint;
-      tp->step_range_start = step_range_start;
-      tp->step_range_end = step_range_end;
-      tp->step_frame_id = step_frame_id;
-
-      /* Reset breakpoints in the child as appropriate.  */
-      follow_inferior_reset_breakpoints ();
     }
 
   restore_child_signals_mask (&prev_mask);
Index: src/gdb/inf-ptrace.c
===================================================================
--- src.orig/gdb/inf-ptrace.c	2009-05-19 16:45:37.000000000 +0100
+++ src/gdb/inf-ptrace.c	2009-05-19 20:26:07.000000000 +0100
@@ -46,20 +46,8 @@ inf_ptrace_follow_fork (struct target_op
 {
   pid_t pid, fpid;
   ptrace_state_t pe;
-  struct thread_info *last_tp = NULL;
 
-  /* FIXME: kettenis/20050720: This stuff should really be passed as
-     an argument by our caller.  */
-  {
-    ptid_t ptid;
-    struct target_waitstatus status;
-
-    get_last_target_status (&ptid, &status);
-    gdb_assert (status.kind == TARGET_WAITKIND_FORKED);
-
-    pid = ptid_get_pid (ptid);
-    last_tp = find_thread_pid (ptid);
-  }
+  pid = ptid_get_pid (inferior_ptid);
 
   if (ptrace (PT_GET_PROCESS_STATE, pid,
 	       (PTRACE_TYPE_ARG3)&pe, sizeof pe) == -1)
@@ -70,18 +58,9 @@ inf_ptrace_follow_fork (struct target_op
 
   if (follow_child)
     {
-      /* Copy user stepping state to the new inferior thread.  */
-      struct breakpoint *step_resume_breakpoint = last_tp->step_resume_breakpoint;
-      CORE_ADDR step_range_start = last_tp->step_range_start;
-      CORE_ADDR step_range_end = last_tp->step_range_end;
-      struct frame_id step_frame_id = last_tp->step_frame_id;
       struct inferior *parent_inf, *child_inf;
       struct thread_info *tp;
 
-      /* Otherwise, deleting the parent would get rid of this
-	 breakpoint.  */
-      last_tp->step_resume_breakpoint = NULL;
-
       parent_inf = find_inferior_pid (pid);
 
       /* Add the child.  */
@@ -102,26 +81,16 @@ inf_ptrace_follow_fork (struct target_op
       /* Delete the parent.  */
       detach_inferior (pid);
 
-      tp = add_thread_silent (inferior_ptid);
-
-      tp->step_resume_breakpoint = step_resume_breakpoint;
-      tp->step_range_start = step_range_start;
-      tp->step_range_end = step_range_end;
-      tp->step_frame_id = step_frame_id;
-
-      /* Reset breakpoints in the child as appropriate.  */
-      follow_inferior_reset_breakpoints ();
+      add_thread_silent (inferior_ptid);
     }
   else
     {
-      inferior_ptid = pid_to_ptid (pid);
-
       /* 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"));
-      detach_inferior (pid);
+      detach_inferior (fpid);
     }
 
   return 0;
Index: src/gdb/inf-ttrace.c
===================================================================
--- src.orig/gdb/inf-ttrace.c	2009-05-19 16:46:38.000000000 +0100
+++ src/gdb/inf-ttrace.c	2009-05-19 17:24:14.000000000 +0100
@@ -412,25 +412,13 @@ inf_ttrace_follow_fork (struct target_op
   pid_t pid, fpid;
   lwpid_t lwpid, flwpid;
   ttstate_t tts;
-  struct thread_info *last_tp = NULL;
-  struct breakpoint *step_resume_breakpoint = NULL;
-  CORE_ADDR step_range_start = 0, step_range_end = 0;
-  struct frame_id step_frame_id = null_frame_id;
-
-  /* FIXME: kettenis/20050720: This stuff should really be passed as
-     an argument by our caller.  */
-  {
-    ptid_t ptid;
-    struct target_waitstatus status;
-
-    get_last_target_status (&ptid, &status);
-    gdb_assert (status.kind == TARGET_WAITKIND_FORKED
-		|| status.kind == TARGET_WAITKIND_VFORKED);
-
-    pid = ptid_get_pid (ptid);
-    lwpid = ptid_get_lwp (ptid);
-    last_tp = find_thread_pid (ptid);
-  }
+  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.  */
@@ -462,16 +450,6 @@ inf_ttrace_follow_fork (struct target_op
 
       parent_inf = find_inferior_pid (pid);
 
-      /* Copy user stepping state to the new inferior thread.  */
-      step_resume_breakpoint = last_tp->step_resume_breakpoint;
-      step_range_start = last_tp->step_range_start;
-      step_range_end = last_tp->step_range_end;
-      step_frame_id = last_tp->step_frame_id;
-
-      /* Otherwise, deleting the parent would get rid of this
-	 breakpoint.  */
-      last_tp->step_resume_breakpoint = NULL;
-
       inferior_ptid = ptid_build (fpid, flwpid, 0);
       inf = add_inferior (fpid);
       inf->attach_flag = parent_inf->attach_flag;
@@ -553,14 +531,6 @@ Detaching after fork from child process 
 	xmalloc (sizeof (struct inf_ttrace_private_thread_info));
       memset (ti->private, 0,
 	      sizeof (struct inf_ttrace_private_thread_info));
-
-      ti->step_resume_breakpoint = step_resume_breakpoint;
-      ti->step_range_start = step_range_start;
-      ti->step_range_end = step_range_end;
-      ti->step_frame_id = step_frame_id;
-
-      /* Reset breakpoints in the child as appropriate.  */
-      follow_inferior_reset_breakpoints ();
     }
 
   return 0;
Index: src/gdb/testsuite/gdb.threads/fork-thread-pending.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/testsuite/gdb.threads/fork-thread-pending.c	2009-05-19 17:31:44.000000000 +0100
@@ -0,0 +1,109 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2008, 2009 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#define NUMTHREADS 10
+
+volatile int done = 0;
+
+static void *
+start (void *arg)
+{
+  while (!done)
+    usleep (100);
+  assert (0);
+  return arg;
+}
+
+void *
+thread_function (void *arg)
+{
+  int x = * (int *) arg;
+
+  printf ("Thread <%d> executing\n", x);
+
+  while (!done)
+    usleep (100);
+
+  return NULL;
+}
+
+void *
+thread_forker (void *arg)
+{
+  int x = * (int *) arg;
+  pid_t pid;
+  int rv;
+  int i;
+  pthread_t thread;
+
+  printf ("Thread forker <%d> executing\n", x);
+
+  switch ((pid = fork ()))
+    {
+    case -1:
+      assert (0);
+    default:
+      wait (&rv);
+      done = 1;
+      break;
+    case 0:
+      i = pthread_create (&thread, NULL, start, NULL);
+      assert (i == 0);
+      i = pthread_join (thread, NULL);
+      assert (i == 0);
+
+      assert (0);
+    }
+
+  return NULL;
+}
+
+int
+main (void)
+{
+  pthread_t threads[NUMTHREADS];
+  int args[NUMTHREADS];
+  int i, j;
+
+  /* Create a few threads that do mostly nothing, and then one that
+     forks.  */
+  for (j = 0; j < NUMTHREADS - 1; ++j)
+    {
+      args[j] = j;
+      pthread_create (&threads[j], NULL, thread_function, &args[j]);
+    }
+
+  args[j] = j;
+  pthread_create (&threads[j], NULL, thread_forker, &args[j]);
+
+  for (j = 0; j < NUMTHREADS; ++j)
+    {
+      pthread_join (threads[j], NULL);
+    }
+
+  return 0;
+}
Index: src/gdb/testsuite/gdb.threads/fork-thread-pending.exp
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ src/gdb/testsuite/gdb.threads/fork-thread-pending.exp	2009-05-19 22:10:29.000000000 +0100
@@ -0,0 +1,122 @@
+# Copyright (C) 2009 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Only GNU/Linux is known to support `set follow-fork-mode child'.
+#
+if { ! [istarget "*-*-linux*"] } {
+    return 0
+}
+
+set testfile fork-thread-pending
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/${testfile}
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+    return -1
+}
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+
+gdb_load ${binfile}
+if ![runto_main] then {
+   fail "Can't run to main"
+   return 0
+}
+
+gdb_test "set follow-fork-mode child" "" "1, set follow-fork-mode child"
+gdb_test "catch fork" "Catchpoint \[0-9\]* \\(fork\\)" "1, insert fork catchpoint"
+gdb_breakpoint "start" "" "1, set breakpoint at start"
+
+gdb_test "continue" "Catchpoint.*" "1, get to the fork event"
+
+gdb_test "info threads" " Thread .* Thread .* Thread .* Thread .*" "1, multiple threads found"
+
+gdb_test "thread 2" "" "1, switched away from event thread"
+
+gdb_test "continue" "Not resuming.*" "1, refused to resume"
+
+set test "1, followed to the child, found one thread"
+gdb_test_multiple "info threads" "metest" {
+    -re " Thread .* Thread .*$gdb_prompt $" {
+	fail "$test"
+    }
+    -re " Thread .*$gdb_prompt $" {
+	pass "$test"
+    }
+    -re "$gdb_prompt $" {
+	fail "$test (unknown output)"
+    }
+    timeout {
+	fail "$test (timeout)"
+    }
+}
+
+gdb_test "continue" "Breakpoint 3, start.*" "1, get to the spawned thread in fork child"
+
+set test "1, followed to the child, found two threads"
+gdb_test_multiple "info threads" "metest" {
+    -re " Thread .* Thread .* Thread .*$gdb_prompt $" {
+	fail "$test"
+    }
+    -re " Thread .* Thread .*$gdb_prompt $" {
+	pass "$test"
+    }
+    -re "$gdb_prompt $" {
+	fail "$test (unknown output)"
+    }
+    timeout {
+	fail "$test (timeout)"
+    }
+}
+
+# Start over, but this time, don't switch away from the fork event thread.
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+
+gdb_load ${binfile}
+if ![runto_main] then {
+   fail "Can't run to main"
+   return 0
+}
+
+gdb_test "set follow-fork-mode child" "" "2, set follow-fork-mode child"
+gdb_test "catch fork" "Catchpoint \[0-9\]* \\(fork\\)" "2, insert fork catchpoint"
+gdb_breakpoint "start"
+
+gdb_test "continue" "Catchpoint.*" "2, get to the fork event"
+
+gdb_test "info threads" " Thread .* Thread .* Thread .* Thread .*" "2, multiple threads found"
+
+gdb_test "continue" "Breakpoint 3, start.*" "2, get to the spawned thread in fork child"
+
+set test "2, followed to the child, found two threads"
+gdb_test_multiple "info threads" "metest" {
+    -re " Thread .* Thread .* Thread .*$gdb_prompt $" {
+	fail "$test"
+    }
+    -re " Thread .* Thread .*$gdb_prompt $" {
+	pass "$test"
+    }
+    -re "$gdb_prompt $" {
+	fail "$test (unknown output)"
+    }
+    timeout {
+	fail "$test (timeout)"
+    }
+}



More information about the Gdb-patches mailing list