#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 */
extern struct target_ops child_ops;
+static int linux_parent_pid;
+
struct simple_pid_list
{
int pid;
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. */
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)
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)
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);
}
{
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;
/* 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,
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 ();
"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);
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;
"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);
}
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