From 9016a515a30010b1a1a18e5749d1cef907fa44e5 Mon Sep 17 00:00:00 2001 From: Daniel Jacobowitz Date: Sun, 17 Aug 2003 20:17:02 +0000 Subject: [PATCH] * 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. --- gdb/ChangeLog | 14 +++++ gdb/linux-nat.c | 148 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 151 insertions(+), 11 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index fe047c668ff..a80e7d57e9f 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,17 @@ +2003-08-17 Daniel Jacobowitz + + * 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. + 2003-08-17 Daniel Jacobowitz * lin-lwp.c (child_wait): Call linux_record_stopped_pid. diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 0dd83e77d35..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 */ @@ -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 -- 2.30.2