diff options
Diffstat (limited to 'gdb/linux-nat.c')
-rw-r--r-- | gdb/linux-nat.c | 319 |
1 files changed, 310 insertions, 9 deletions
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 61931c6..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 */ @@ -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 (); +} |