[commit] Last major piece of fork tracing - vfork/exec

Daniel Jacobowitz drow@mvista.com
Sun Aug 17 20:19:00 GMT 2003


This patch is the last major piece of fork tracing: support for vfork and
exec.  I have not enabled the code in infrun which attempts to automatically
load the new executable, because I'm not happy with how it works, and
because it isn't necessary for this to be useful.

I'll move this to the branch in a day or two if there are no problems.
I suppose fixing up the expected output in the testsuite is next...

-- 
Daniel Jacobowitz
MontaVista Software                         Debian GNU/Linux Developer

2003-08-17  Daniel Jacobowitz  <drow@mvista.com>

	* linux-nat.c (PTRACE_O_TRACEVFORKDONE, PTRACE_O_TRACEEXIT): Define.
	(PTRACE_EVENT_VFORKDONE, PTRACE_EVENT_EXIT): Define.
	(linux_parent_pid, linux_supports_tracevforkdone_flag): New variable.
	(linux_test_for_tracefork): Set linux_supports_tracevforkdone_flag.
	(linux_supports_tracevforkdone): New function.
	(linux_enable_event_reporting): Enable TRACEVFORK, TRACEEXEC, and
	TRACEVFORKDONE.
	(child_follow_fork): Handle vfork.
	(linux_handle_extended_wait): Likewise.  Also handle exec.
	(child_insert_vfork_catchpoint, child_insert_exec_catchpoint): Enable.
	* NEWS: Mention fork tracing.

Index: linux-nat.c
===================================================================
RCS file: /cvs/src/src/gdb/linux-nat.c,v
retrieving revision 1.4
diff -u -p -r1.4 linux-nat.c
--- linux-nat.c	17 Aug 2003 18:22:25 -0000	1.4
+++ linux-nat.c	17 Aug 2003 19:23:20 -0000
@@ -40,12 +40,16 @@
 #define PTRACE_O_TRACEVFORK	0x00000004
 #define PTRACE_O_TRACECLONE	0x00000008
 #define PTRACE_O_TRACEEXEC	0x00000010
+#define PTRACE_O_TRACEVFORKDONE	0x00000020
+#define PTRACE_O_TRACEEXIT	0x00000040
 
 /* Wait extended result codes for the above trace options.  */
 #define PTRACE_EVENT_FORK	1
 #define PTRACE_EVENT_VFORK	2
 #define PTRACE_EVENT_CLONE	3
 #define PTRACE_EVENT_EXEC	4
+#define PTRACE_EVENT_VFORKDONE	5
+#define PTRACE_EVENT_EXIT	6
 
 #endif /* PTRACE_EVENT_FORK */
 
@@ -58,6 +62,8 @@
 
 extern struct target_ops child_ops;
 
+static int linux_parent_pid;
+
 struct simple_pid_list
 {
   int pid;
@@ -70,6 +76,11 @@ struct simple_pid_list *stopped_pids;
 
 static int linux_supports_tracefork_flag = -1;
 
+/* If we have PTRACE_O_TRACEFORK, this flag indicates whether we also have
+   PTRACE_O_TRACEVFORKDONE.  */
+
+static int linux_supports_tracevforkdone_flag = -1;
+
 
 /* Trivial list manipulation functions to keep track of a list of
    new stopped processes.  */
@@ -155,6 +166,11 @@ linux_test_for_tracefork (void)
       return;
     }
 
+  /* Check whether PTRACE_O_TRACEVFORKDONE is available.  */
+  ret = ptrace (PTRACE_SETOPTIONS, child_pid, 0,
+		PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORKDONE);
+  linux_supports_tracevforkdone_flag = (ret == 0);
+
   ptrace (PTRACE_CONT, child_pid, 0, 0);
   ret = waitpid (child_pid, &status, 0);
   if (ret == child_pid && WIFSTOPPED (status)
@@ -190,6 +206,14 @@ linux_supports_tracefork (void)
   return linux_supports_tracefork_flag;
 }
 
+static int
+linux_supports_tracevforkdone (void)
+{
+  if (linux_supports_tracefork_flag == -1)
+    linux_test_for_tracefork ();
+  return linux_supports_tracevforkdone_flag;
+}
+
 
 void
 linux_enable_event_reporting (ptid_t ptid)
@@ -200,7 +224,12 @@ linux_enable_event_reporting (ptid_t pti
   if (! linux_supports_tracefork ())
     return;
 
-  options = PTRACE_O_TRACEFORK;
+  options = PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXEC;
+  if (linux_supports_tracevforkdone ())
+    options |= PTRACE_O_TRACEVFORKDONE;
+
+  /* Do not enable PTRACE_O_TRACEEXIT until GDB is more prepared to support
+     read-only process state.  */
 
   ptrace (PTRACE_SETOPTIONS, pid, 0, options);
 }
@@ -230,9 +259,11 @@ child_follow_fork (int follow_child)
 {
   ptid_t last_ptid;
   struct target_waitstatus last_status;
+  int has_vforked;
   int parent_pid, child_pid;
 
   get_last_target_status (&last_ptid, &last_status);
+  has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED);
   parent_pid = ptid_get_pid (last_ptid);
   child_pid = last_status.value.related_pid;
 
@@ -243,6 +274,8 @@ child_follow_fork (int follow_child)
       /* Before detaching from the child, remove all breakpoints from
          it.  (This won't actually modify the breakpoint list, but will
          physically remove the breakpoints from the child.) */
+      /* If we vforked this will remove the breakpoints from the parent
+	 also, but they'll be reinserted below.  */
       detach_breakpoints (child_pid);
 
       fprintf_filtered (gdb_stdout,
@@ -250,13 +283,67 @@ child_follow_fork (int follow_child)
 			child_pid);
 
       ptrace (PTRACE_DETACH, child_pid, 0, 0);
+
+      if (has_vforked)
+	{
+	  if (linux_supports_tracevforkdone ())
+	    {
+	      int status;
+
+	      ptrace (PTRACE_CONT, parent_pid, 0, 0);
+	      waitpid (parent_pid, &status, __WALL);
+	      if ((status >> 16) != PTRACE_EVENT_VFORKDONE)
+		warning ("Unexpected waitpid result %06x when waiting for "
+			 "vfork-done", status);
+	    }
+	  else
+	    {
+	      /* We can't insert breakpoints until the child has
+		 finished with the shared memory region.  We need to
+		 wait until that happens.  Ideal would be to just
+		 call:
+		 - ptrace (PTRACE_SYSCALL, parent_pid, 0, 0);
+		 - waitpid (parent_pid, &status, __WALL);
+		 However, most architectures can't handle a syscall
+		 being traced on the way out if it wasn't traced on
+		 the way in.
+
+		 We might also think to loop, continuing the child
+		 until it exits or gets a SIGTRAP.  One problem is
+		 that the child might call ptrace with PTRACE_TRACEME.
+
+		 There's no simple and reliable way to figure out when
+		 the vforked child will be done with its copy of the
+		 shared memory.  We could step it out of the syscall,
+		 two instructions, let it go, and then single-step the
+		 parent once.  When we have hardware single-step, this
+		 would work; with software single-step it could still
+		 be made to work but we'd have to be able to insert
+		 single-step breakpoints in the child, and we'd have
+		 to insert -just- the single-step breakpoint in the
+		 parent.  Very awkward.
+
+		 In the end, the best we can do is to make sure it
+		 runs for a little while.  Hopefully it will be out of
+		 range of any breakpoints we reinsert.  Usually this
+		 is only the single-step breakpoint at vfork's return
+		 point.  */
+
+	      usleep (10000);
+	    }
+
+	  /* Since we vforked, breakpoints were removed in the parent
+	     too.  Put them back.  */
+	  reattach_breakpoints (parent_pid);
+	}
     }
   else
     {
       char child_pid_spelling[40];
 
       /* Needed to keep the breakpoint lists in sync.  */
-      detach_breakpoints (child_pid);
+      if (! has_vforked)
+	detach_breakpoints (child_pid);
 
       /* Before detaching from the parent, remove all breakpoints from it. */
       remove_breakpoints ();
@@ -265,7 +352,28 @@ child_follow_fork (int follow_child)
 			"Attaching after fork to child process %d.\n",
 			child_pid);
 
-      target_detach (NULL, 0);
+      /* If we're vforking, we may want to hold on to the parent until
+	 the child exits or execs.  At exec time we can remove the old
+	 breakpoints from the parent and detach it; at exit time we
+	 could do the same (or even, sneakily, resume debugging it - the
+	 child's exec has failed, or something similar).
+
+	 This doesn't clean up "properly", because we can't call
+	 target_detach, but that's OK; if the current target is "child",
+	 then it doesn't need any further cleanups, and lin_lwp will
+	 generally not encounter vfork (vfork is defined to fork
+	 in libpthread.so).
+
+	 The holding part is very easy if we have VFORKDONE events;
+	 but keeping track of both processes is beyond GDB at the
+	 moment.  So we don't expose the parent to the rest of GDB.
+	 Instead we quietly hold onto it until such time as we can
+	 safely resume it.  */
+
+      if (has_vforked)
+	linux_parent_pid = parent_pid;
+      else
+	target_detach (NULL, 0);
 
       inferior_ptid = pid_to_ptid (child_pid);
       push_target (&child_ops);
@@ -287,7 +395,7 @@ linux_handle_extended_wait (int pid, int
     internal_error (__FILE__, __LINE__,
 		    "unexpected clone event");
 
-  if (event == PTRACE_EVENT_FORK)
+  if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
     {
       unsigned long new_pid;
       int ret;
@@ -315,11 +423,29 @@ linux_handle_extended_wait (int pid, int
 			    "wait returned unexpected status 0x%x", status);
 	}
 
-      ourstatus->kind = TARGET_WAITKIND_FORKED;
+      ourstatus->kind = (event == PTRACE_EVENT_FORK)
+	? TARGET_WAITKIND_FORKED : TARGET_WAITKIND_VFORKED;
       ourstatus->value.related_pid = new_pid;
       return inferior_ptid;
     }
 
+  if (event == PTRACE_EVENT_EXEC)
+    {
+      ourstatus->kind = TARGET_WAITKIND_EXECD;
+      ourstatus->value.execd_pathname
+	= xstrdup (child_pid_to_exec_file (pid));
+
+      if (linux_parent_pid)
+	{
+	  detach_breakpoints (linux_parent_pid);
+	  ptrace (PTRACE_DETACH, linux_parent_pid, 0, 0);
+
+	  linux_parent_pid = 0;
+	}
+
+      return inferior_ptid;
+    }
+
   internal_error (__FILE__, __LINE__,
 		  "unknown ptrace event %d", event);
 }
@@ -337,19 +463,19 @@ child_insert_fork_catchpoint (int pid)
 int
 child_insert_vfork_catchpoint (int pid)
 {
-  if (linux_supports_tracefork ())
-    error ("Vfork catchpoints have not been implemented yet.");
-  else
+  if (!linux_supports_tracefork ())
     error ("Your system does not support vfork catchpoints.");
+
+  return 0;
 }
 
 int
 child_insert_exec_catchpoint (int pid)
 {
-  if (linux_supports_tracefork ())
-    error ("Exec catchpoints have not been implemented yet.");
-  else
+  if (!linux_supports_tracefork ())
     error ("Your system does not support exec catchpoints.");
+
+  return 0;
 }
 
 void
Index: NEWS
===================================================================
RCS file: /cvs/src/src/gdb/NEWS,v
retrieving revision 1.117
diff -u -p -r1.117 NEWS
--- NEWS	16 Aug 2003 18:38:46 -0000	1.117
+++ NEWS	17 Aug 2003 20:16:17 -0000
@@ -11,6 +11,12 @@ tested, nor mentioned in the NEWS file.
 
 *** Changes in GDB 6.0:
 
+* GNU/Linux support for fork, vfork, and exec.
+
+The "catch fork", "catch exec", "catch vfork", and "set follow-fork-mode"
+commands are now implemented for GNU/Linux.  They require a 2.5.x or later
+kernel.
+
 * GDB supports logging output to a file
 
 There are two new commands, "set logging" and "show logging", which can be



More information about the Gdb-patches mailing list