* linux-nat.c (PTRACE_O_TRACEVFORKDONE, PTRACE_O_TRACEEXIT): Define.
authorDaniel Jacobowitz <drow@false.org>
Sun, 17 Aug 2003 20:17:02 +0000 (20:17 +0000)
committerDaniel Jacobowitz <drow@false.org>
Sun, 17 Aug 2003 20:17:02 +0000 (20:17 +0000)
(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.

gdb/ChangeLog
gdb/linux-nat.c

index fe047c668ffacce80ddfbd7b26554ea6ba7b4367..a80e7d57e9f410e7b797c638eb18391608de83a7 100644 (file)
@@ -1,3 +1,17 @@
+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.
+
 2003-08-17  Daniel Jacobowitz  <drow@mvista.com>
 
        * lin-lwp.c (child_wait): Call linux_record_stopped_pid.
index 0dd83e77d35853ee5819b2eabced2f8cd747e61d..2680422cd50ff672d65ab3fa6039b5cd98932208 100644 (file)
 #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;
+
 \f
 /* 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;
+}
+
 \f
 void
 linux_enable_event_reporting (ptid_t ptid)
@@ -200,7 +224,12 @@ linux_enable_event_reporting (ptid_t ptid)
   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 status,
     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 status,
                            "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