* bsd-kvm.c: Include "gdbcore.h"
[deliverable/binutils-gdb.git] / gdb / linux-nat.c
index 0dd83e77d35853ee5819b2eabced2f8cd747e61d..e421c9ca2214f01d65eb7062a57e9132d18510dd 100644 (file)
@@ -1,5 +1,5 @@
 /* GNU/Linux native-dependent code common to multiple platforms.
-   Copyright (C) 2003 Free Software Foundation, Inc.
+   Copyright (C) 2003, 2004 Free Software Foundation, Inc.
 
    This file is part of GDB.
 
 #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,13 @@ 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
+    | PTRACE_O_TRACECLONE;
+  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 +260,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 +275,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 +284,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 +353,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);
@@ -283,11 +392,8 @@ linux_handle_extended_wait (int pid, int status,
 {
   int event = status >> 16;
 
-  if (event == PTRACE_EVENT_CLONE)
-    internal_error (__FILE__, __LINE__,
-                   "unexpected clone event");
-
-  if (event == PTRACE_EVENT_FORK)
+  if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK
+      || event == PTRACE_EVENT_CLONE)
     {
       unsigned long new_pid;
       int ret;
@@ -298,12 +404,10 @@ linux_handle_extended_wait (int pid, int status,
       if (! pull_pid_from_list (&stopped_pids, new_pid))
        {
          /* The new child has a pending SIGSTOP.  We can't affect it until it
-            hits the SIGSTOP, but we're already attached.
-
-            It won't be a clone (we didn't ask for clones in the event mask)
-            so we can just call waitpid and wait for the SIGSTOP.  */
+            hits the SIGSTOP, but we're already attached.  */
          do {
-           ret = waitpid (new_pid, &status, 0);
+           ret = waitpid (new_pid, &status,
+                          (event == PTRACE_EVENT_CLONE) ? __WCLONE : 0);
          } while (ret == -1 && errno == EINTR);
          if (ret == -1)
            perror_with_name ("waiting for new child");
@@ -315,11 +419,34 @@ linux_handle_extended_wait (int pid, int status,
                            "wait returned unexpected status 0x%x", status);
        }
 
-      ourstatus->kind = TARGET_WAITKIND_FORKED;
+      if (event == PTRACE_EVENT_FORK)
+       ourstatus->kind = TARGET_WAITKIND_FORKED;
+      else if (event == PTRACE_EVENT_VFORK)
+       ourstatus->kind = TARGET_WAITKIND_VFORKED;
+      else
+       ourstatus->kind = TARGET_WAITKIND_SPURIOUS;
+
       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 +464,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
This page took 0.027683 seconds and 4 git commands to generate.