[commit] Last major piece of fork tracing - vfork/exec

Daniel Jacobowitz
2003-08-17T20:19:26Z

This patch is the last major piece of fork tracing: support for vfork and
exec. I have not enabled the code in infrun which attempts to automatically
load the new executable, because I'm not happy with how it works, and
because it isn't necessary for this to be useful.

I'll move this to the branch in a day or two if there are no problems.
I suppose fixing up the expected output in the testsuite is next...

--
Daniel Jacobowitz
MontaVista Software Debian GNU/Linux Developer

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.

Index: linux-nat.c
===================================================================
RCS file: /cvs/src/src/gdb/linux-nat.c,v
retrieving revision 1.4
diff -u -p -r1.4 linux-nat.c
--- linux-nat.c 17 Aug 2003 18:22:25 -0000 1.4
+++ linux-nat.c 17 Aug 2003 19:23:20 -0000
@@ -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 */

@@ -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 pti
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
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
"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
Index: NEWS
===================================================================
RCS file: /cvs/src/src/gdb/NEWS,v
retrieving revision 1.117
diff -u -p -r1.117 NEWS
--- NEWS 16 Aug 2003 18:38:46 -0000 1.117
+++ NEWS 17 Aug 2003 20:16:17 -0000
@@ -11,6 +11,12 @@ tested, nor mentioned in the NEWS file.

*** Changes in GDB 6.0:

+* GNU/Linux support for fork, vfork, and exec.
+
+The "catch fork", "catch exec", "catch vfork", and "set follow-fork-mode"
+commands are now implemented for GNU/Linux. They require a 2.5.x or later
+kernel.
+
* GDB supports logging output to a file

There are two new commands, "set logging" and "show logging", which can be