diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2004-05-21 19:18:40 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2004-05-21 19:18:40 +0000 |
commit | 6a565344557d0acb4bd34cc9a0bf698662f9006b (patch) | |
tree | 5c120526742e6dbb98c8c3d2857c18f7de16143a /gnu/usr.bin/binutils/gdb/linux-nat.c | |
parent | a0769fe1e18fcff10de0bca7c087aacab3cda1cb (diff) |
GDB 6.1 (excluding .info files)
Diffstat (limited to 'gnu/usr.bin/binutils/gdb/linux-nat.c')
-rw-r--r-- | gnu/usr.bin/binutils/gdb/linux-nat.c | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/gnu/usr.bin/binutils/gdb/linux-nat.c b/gnu/usr.bin/binutils/gdb/linux-nat.c new file mode 100644 index 00000000000..2680422cd50 --- /dev/null +++ b/gnu/usr.bin/binutils/gdb/linux-nat.c @@ -0,0 +1,520 @@ +/* GNU/Linux native-dependent code common to multiple platforms. + Copyright (C) 2003 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#include "defs.h" +#include "inferior.h" +#include "target.h" + +#include "gdb_wait.h" +#include <sys/ptrace.h> + +#include "linux-nat.h" + +/* If the system headers did not provide the constants, hard-code the normal + values. */ +#ifndef PTRACE_EVENT_FORK + +#define PTRACE_SETOPTIONS 0x4200 +#define PTRACE_GETEVENTMSG 0x4201 + +/* options set using PTRACE_SETOPTIONS */ +#define PTRACE_O_TRACESYSGOOD 0x00000001 +#define PTRACE_O_TRACEFORK 0x00000002 +#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 */ + +/* We can't always assume that this flag is available, but all systems + with the ptrace event handlers also have __WALL, so it's safe to use + here. */ +#ifndef __WALL +#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; + struct simple_pid_list *next; +}; +struct simple_pid_list *stopped_pids; + +/* This variable is a tri-state flag: -1 for unknown, 0 if PTRACE_O_TRACEFORK + can not be used, 1 if it can. */ + +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. */ +static void +add_to_pid_list (struct simple_pid_list **listp, int pid) +{ + struct simple_pid_list *new_pid = xmalloc (sizeof (struct simple_pid_list)); + new_pid->pid = pid; + new_pid->next = *listp; + *listp = new_pid; +} + +static int +pull_pid_from_list (struct simple_pid_list **listp, int pid) +{ + struct simple_pid_list **p; + + for (p = listp; *p != NULL; p = &(*p)->next) + if ((*p)->pid == pid) + { + struct simple_pid_list *next = (*p)->next; + xfree (*p); + *p = next; + return 1; + } + return 0; +} + +void +linux_record_stopped_pid (int pid) +{ + add_to_pid_list (&stopped_pids, pid); +} + + +/* A helper function for linux_test_for_tracefork, called after fork (). */ + +static void +linux_tracefork_child (void) +{ + int ret; + + ptrace (PTRACE_TRACEME, 0, 0, 0); + kill (getpid (), SIGSTOP); + fork (); + exit (0); +} + +/* Determine if PTRACE_O_TRACEFORK can be used to follow fork events. We + create a child process, attach to it, use PTRACE_SETOPTIONS to enable + fork tracing, and let it fork. If the process exits, we assume that + we can't use TRACEFORK; if we get the fork notification, and we can + extract the new child's PID, then we assume that we can. */ + +static void +linux_test_for_tracefork (void) +{ + int child_pid, ret, status; + long second_pid; + + child_pid = fork (); + if (child_pid == -1) + perror_with_name ("linux_test_for_tracefork: fork"); + + if (child_pid == 0) + linux_tracefork_child (); + + ret = waitpid (child_pid, &status, 0); + if (ret == -1) + perror_with_name ("linux_test_for_tracefork: waitpid"); + else if (ret != child_pid) + error ("linux_test_for_tracefork: waitpid: unexpected result %d.", ret); + if (! WIFSTOPPED (status)) + error ("linux_test_for_tracefork: waitpid: unexpected status %d.", status); + + linux_supports_tracefork_flag = 0; + + ret = ptrace (PTRACE_SETOPTIONS, child_pid, 0, PTRACE_O_TRACEFORK); + if (ret != 0) + { + ptrace (PTRACE_KILL, child_pid, 0, 0); + waitpid (child_pid, &status, 0); + 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) + && status >> 16 == PTRACE_EVENT_FORK) + { + second_pid = 0; + ret = ptrace (PTRACE_GETEVENTMSG, child_pid, 0, &second_pid); + if (ret == 0 && second_pid != 0) + { + int second_status; + + linux_supports_tracefork_flag = 1; + waitpid (second_pid, &second_status, 0); + ptrace (PTRACE_DETACH, second_pid, 0, 0); + } + } + + if (WIFSTOPPED (status)) + { + ptrace (PTRACE_DETACH, child_pid, 0, 0); + waitpid (child_pid, &status, 0); + } +} + +/* Return non-zero iff we have tracefork functionality available. + This function also sets linux_supports_tracefork_flag. */ + +static int +linux_supports_tracefork (void) +{ + if (linux_supports_tracefork_flag == -1) + linux_test_for_tracefork (); + 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_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; + + 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 ("Your system does not support vfork catchpoints."); + + return 0; +} + +int +child_insert_exec_catchpoint (int pid) +{ + 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 (); +} |