* configure.host: Add i[34567]86-*-openbsd[0-2].* and
[deliverable/binutils-gdb.git] / gdb / linux-nat.c
index bef737b92a9bbcf178d3c059e28bca69b2fe8395..2680422cd50ff672d65ab3fa6039b5cd98932208 100644 (file)
@@ -25,6 +25,8 @@
 #include "gdb_wait.h"
 #include <sys/ptrace.h>
 
+#include "linux-nat.h"
+
 /* If the system headers did not provide the constants, hard-code the normal
    values.  */
 #ifndef PTRACE_EVENT_FORK
 #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 */
 
 #define __WALL          0x40000000 /* Wait for any child.  */
 #endif
 
+extern struct target_ops child_ops;
+
+static int linux_parent_pid;
+
 struct simple_pid_list
 {
   int pid;
@@ -66,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.  */
@@ -151,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)
@@ -186,32 +206,315 @@ 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)
+{
+  int pid = ptid_get_pid (ptid);
+  int options;
+
+  if (! linux_supports_tracefork ())
+    return;
+
+  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);
+}
+
+void
+child_post_attach (int pid)
+{
+  linux_enable_event_reporting (pid_to_ptid (pid));
+}
+
+void
+linux_child_post_startup_inferior (ptid_t ptid)
+{
+  linux_enable_event_reporting (ptid);
+}
+
+#ifndef LINUX_CHILD_POST_STARTUP_INFERIOR
+void
+child_post_startup_inferior (ptid_t ptid)
+{
+  linux_child_post_startup_inferior (ptid);
+}
+#endif
+
 int
-child_insert_fork_catchpoint (int pid)
+child_follow_fork (int follow_child)
 {
-  if (linux_supports_tracefork ())
-    error ("Fork catchpoints have not been implemented yet.");
+  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;
+
+  if (! follow_child)
+    {
+      /* We're already attached to the parent, by default. */
+
+      /* 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,
+                       "Detaching after fork from child process %d.\n",
+                       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.  */
+      if (! has_vforked)
+       detach_breakpoints (child_pid);
+
+      /* Before detaching from the parent, remove all breakpoints from it. */
+      remove_breakpoints ();
+
+      fprintf_filtered (gdb_stdout,
+                       "Attaching after fork to child process %d.\n",
+                       child_pid);
+
+      /* 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);
+
+      /* Reset breakpoints in the child as appropriate.  */
+      follow_inferior_reset_breakpoints ();
+    }
+
+  return 0;
+}
+
+ptid_t
+linux_handle_extended_wait (int pid, int status,
+                           struct target_waitstatus *ourstatus)
+{
+  int event = status >> 16;
+
+  if (event == PTRACE_EVENT_CLONE)
+    internal_error (__FILE__, __LINE__,
+                   "unexpected clone event");
+
+  if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
+    {
+      unsigned long new_pid;
+      int ret;
+
+      ptrace (PTRACE_GETEVENTMSG, pid, 0, &new_pid);
+
+      /* If we haven't already seen the new PID stop, wait for it now.  */
+      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.  */
+         do {
+           ret = waitpid (new_pid, &status, 0);
+         } while (ret == -1 && errno == EINTR);
+         if (ret == -1)
+           perror_with_name ("waiting for new child");
+         else if (ret != new_pid)
+           internal_error (__FILE__, __LINE__,
+                           "wait returned unexpected PID %d", ret);
+         else if (!WIFSTOPPED (status) || WSTOPSIG (status) != SIGSTOP)
+           internal_error (__FILE__, __LINE__,
+                           "wait returned unexpected status 0x%x", status);
+       }
+
+      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);
+}
+
+\f
+int
+child_insert_fork_catchpoint (int pid)
+{
+  if (! linux_supports_tracefork ())
     error ("Your system does not support fork catchpoints.");
+
+  return 0;
 }
 
 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
+kill_inferior (void)
+{
+  int status;
+  int pid =  PIDGET (inferior_ptid);
+  struct target_waitstatus last;
+  ptid_t last_ptid;
+  int ret;
+
+  if (pid == 0)
+    return;
+
+  /* If we're stopped while forking and we haven't followed yet, kill the
+     other task.  We need to do this first because the parent will be
+     sleeping if this is a vfork.  */
+
+  get_last_target_status (&last_ptid, &last);
+
+  if (last.kind == TARGET_WAITKIND_FORKED
+      || last.kind == TARGET_WAITKIND_VFORKED)
+    {
+      ptrace (PT_KILL, last.value.related_pid);
+      ptrace_wait (null_ptid, &status);
+    }
+
+  /* Kill the current process.  */
+  ptrace (PT_KILL, pid, (PTRACE_ARG3_TYPE) 0, 0);
+  ret = ptrace_wait (null_ptid, &status);
+
+  /* We might get a SIGCHLD instead of an exit status.  This is
+     aggravated by the first kill above - a child has just died.  */
+
+  while (ret == pid && WIFSTOPPED (status))
+    {
+      ptrace (PT_KILL, pid, (PTRACE_ARG3_TYPE) 0, 0);
+      ret = ptrace_wait (null_ptid, &status);
+    }
 
+  target_mourn_inferior ();
+}
This page took 0.027168 seconds and 4 git commands to generate.