summaryrefslogtreecommitdiff
path: root/gdb/linux-nat.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/linux-nat.c')
-rw-r--r--gdb/linux-nat.c319
1 files changed, 310 insertions, 9 deletions
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 61931c6d58f..2680422cd50 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -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 */
@@ -56,6 +60,10 @@
#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;
@@ -68,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. */
@@ -153,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)
@@ -188,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;
+}
+
+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);
+}
+
+
+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 ();
+}