aboutsummaryrefslogtreecommitdiff
path: root/gdb/linux-nat.c
diff options
context:
space:
mode:
authorDaniel Jacobowitz <drow@false.org>2003-08-17 20:17:02 +0000
committerDaniel Jacobowitz <drow@false.org>2003-08-17 20:17:02 +0000
commit9016a515a30010b1a1a18e5749d1cef907fa44e5 (patch)
tree0ba09b1f5803ef929e227dd1d37e77ed9e59a75e /gdb/linux-nat.c
parentc538c11c4887a6619b7f2a3d345382efbc1cda7d (diff)
downloadgdb-9016a515a30010b1a1a18e5749d1cef907fa44e5.zip
gdb-9016a515a30010b1a1a18e5749d1cef907fa44e5.tar.gz
gdb-9016a515a30010b1a1a18e5749d1cef907fa44e5.tar.bz2
* 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.
Diffstat (limited to 'gdb/linux-nat.c')
-rw-r--r--gdb/linux-nat.c148
1 files changed, 137 insertions, 11 deletions
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 0dd83e7..2680422 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 */
@@ -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;
+
/* 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;
+}
+
void
linux_enable_event_reporting (ptid_t ptid)
@@ -200,7 +224,12 @@ 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);
}
@@ -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 status,
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 status,
"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