From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 17813 invoked by alias); 18 Jun 2003 23:29:46 -0000 Mailing-List: contact gdb-patches-help@sources.redhat.com; run by ezmlm Precedence: bulk List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sources.redhat.com Received: (qmail 17789 invoked from network); 18 Jun 2003 23:29:45 -0000 Received: from unknown (HELO crack.them.org) (146.82.138.56) by sources.redhat.com with SMTP; 18 Jun 2003 23:29:45 -0000 Received: from dsl093-172-017.pit1.dsl.speakeasy.net ([66.93.172.17] helo=nevyn.them.org ident=mail) by crack.them.org with asmtp (Exim 3.12 #1 (Debian)) id 19SmNu-0000BY-00; Wed, 18 Jun 2003 18:30:34 -0500 Received: from drow by nevyn.them.org with local (Exim 3.36 #1 (Debian)) id 19SmN4-0000JD-00; Wed, 18 Jun 2003 19:29:42 -0400 Date: Wed, 18 Jun 2003 23:29:00 -0000 From: Daniel Jacobowitz To: gdb-patches@sources.redhat.com Cc: msnyder@redhat.com Subject: RFA: Actual support for tracing forks on GNU/Linux Message-ID: <20030618232942.GA982@nevyn.them.org> Mail-Followup-To: gdb-patches@sources.redhat.com, msnyder@redhat.com Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline User-Agent: Mutt/1.5.1i X-SW-Source: 2003-06/txt/msg00610.txt.bz2 This patch enables "catch fork" and "set follow-fork-mode child" for fork(). Other things from my working directory that doesn't include: - vfork support - I'll do that separately once this in - exec support - the code is still a mess - exit event support - i.e. stopping the process just before it exits, to take a look around - this confuses GDB very badly - gdbserver support for any of the above - enabling the testsuite checks for this - even worse of a mess I hope to get at least native vfork support into GDB 6.0. This patch works by adding a hook every time we attach to an LWP or fork a new LWP, to set tracing flags on it. We enable event reporting for fork() [which requires ~ 2.5.34 kernel; I heard that one of RH's backport kernels included this, but I don't know if it still does.] Then, when we get a fork, we end up attached to both parent and child. We remove breakpoints in whichever one we don't care about, and then we detach it. This both lets us choose which one to trace, and also fixes the problem where breakpoints would be left in the inferior after it forked, causing the child to die with SIGTRAP. I had to override kill_inferior, for an issue discovered in testing: when we're stopped with both processes attached, we have to make sure to kill them both. We have to deal with this because the user could "catch fork", and when they see the fork decide which one to debug. I think that's everything. I'd like at least a nod from the threading maintainers, since I had to hook into lin-lwp.c. OK? -- Daniel Jacobowitz MontaVista Software Debian GNU/Linux Developer 2003-06-18 Daniel Jacobowitz * config/i386/nm-linux.h (LINUX_CHILD_POST_STARTUP_INFERIOR): Define. * config/nm-linux.h (linux_enable_event_reporting) (linux_handle_extended_wait, linux_child_post_startup_inferior): New prototypes. (CHILD_POST_STARTUP_INFERIOR, CHILD_POST_ATTACH, CHILD_FOLLOW_FORK) (KILL_INFERIOR): Define. * i386-linux-nat.c (child_post_startup_inferior): New function. * i386-nat.c (child_post_startup_inferior): Wrap in #ifdef. * infptrace.c (kill_inferior): Wrap in #ifdef. * lin-lwp.c (lin_lwp_attach_lwp): Call child_post_attach after attaching to each LWP. (child_wait, lin_lwp_wait): Call linux_handle_extended_wait. (init_lin_lwp_ops): Fill in some more operations. * linux-nat.c (linux_enable_event_reporting): New function. (child_post_attach, linux_child_post_startup_inferior) (child_post_startup_inferior, child_follow_fork) (linux_handle_extended_wait, kill_inferior): New functions. diff -Nurp -x '*.gmo' src-one/gdb/config/i386/nm-linux.h src-two/gdb/config/i386/nm-linux.h --- src-one/gdb/config/i386/nm-linux.h 2002-11-09 16:31:12.000000000 -0500 +++ src-two/gdb/config/i386/nm-linux.h 2003-06-18 17:54:59.000000000 -0400 @@ -82,4 +82,9 @@ extern int cannot_store_register (int re /* Override child_resume in `infptrace.c'. */ #define CHILD_RESUME +/* `linux-nat.c' and `i386-nat.c' have their own versions of + child_post_startup_inferior. Define this to use the copy in + `i386-linux-nat.c' instead, which calls both. */ +#define LINUX_CHILD_POST_STARTUP_INFERIOR + #endif /* nm-linux.h */ diff -Nurp -x '*.gmo' src-one/gdb/config/nm-linux.h src-two/gdb/config/nm-linux.h --- src-one/gdb/config/nm-linux.h 2003-06-18 18:27:58.000000000 -0400 +++ src-two/gdb/config/nm-linux.h 2003-06-18 18:10:47.000000000 -0400 @@ -79,7 +79,15 @@ extern int linux_proc_xfer_memory (CORE_ struct target_ops *target); extern void linux_record_stopped_pid (int pid); +extern void linux_enable_event_reporting (ptid_t ptid); +extern ptid_t linux_handle_extended_wait (int pid, int status, + struct target_waitstatus *ourstatus); +extern void linux_child_post_startup_inferior (ptid_t ptid); #define CHILD_INSERT_FORK_CATCHPOINT #define CHILD_INSERT_VFORK_CATCHPOINT #define CHILD_INSERT_EXEC_CATCHPOINT +#define CHILD_POST_STARTUP_INFERIOR +#define CHILD_POST_ATTACH +#define CHILD_FOLLOW_FORK +#define KILL_INFERIOR diff -Nurp -x '*.gmo' src-one/gdb/i386-linux-nat.c src-two/gdb/i386-linux-nat.c --- src-one/gdb/i386-linux-nat.c 2003-06-04 16:51:29.000000000 -0400 +++ src-two/gdb/i386-linux-nat.c 2003-06-18 17:16:59.000000000 -0400 @@ -890,6 +890,13 @@ child_resume (ptid_t ptid, int step, enu if (ptrace (request, pid, 0, target_signal_to_host (signal)) == -1) perror_with_name ("ptrace"); } + +void +child_post_startup_inferior (ptid_t ptid) +{ + i386_cleanup_dregs (); + linux_child_post_startup_inferior (ptid); +} /* Register that we are able to handle GNU/Linux ELF core file diff -Nurp -x '*.gmo' src-one/gdb/i386-nat.c src-two/gdb/i386-nat.c --- src-one/gdb/i386-nat.c 2002-07-04 08:32:28.000000000 -0400 +++ src-two/gdb/i386-nat.c 2003-06-18 17:17:40.000000000 -0400 @@ -230,6 +230,7 @@ i386_cleanup_dregs (void) dr_status_mirror = 0; } +#ifndef LINUX_CHILD_POST_STARTUP_INFERIOR /* Reset all debug registers at each new startup to avoid missing watchpoints after restart. */ void @@ -237,6 +238,7 @@ child_post_startup_inferior (ptid_t ptid { i386_cleanup_dregs (); } +#endif /* LINUX_CHILD_POST_STARTUP_INFERIOR */ /* Print the values of the mirrored debug registers. This is called when maint_show_dr is non-zero. To set that diff -Nurp -x '*.gmo' src-one/gdb/infptrace.c src-two/gdb/infptrace.c --- src-one/gdb/infptrace.c 2003-06-18 17:18:44.000000000 -0400 +++ src-two/gdb/infptrace.c 2003-06-18 17:19:07.000000000 -0400 @@ -208,6 +208,7 @@ ptrace_wait (ptid_t ptid, int *status) return wstate; } +#ifndef KILL_INFERIOR void kill_inferior (void) { @@ -229,6 +230,7 @@ kill_inferior (void) ptrace_wait (null_ptid, &status); target_mourn_inferior (); } +#endif /* KILL_INFERIOR */ #ifndef CHILD_RESUME diff -Nurp -x '*.gmo' src-one/gdb/lin-lwp.c src-two/gdb/lin-lwp.c --- src-one/gdb/lin-lwp.c 2003-06-18 18:27:58.000000000 -0400 +++ src-two/gdb/lin-lwp.c 2003-06-18 17:48:29.000000000 -0400 @@ -399,6 +399,8 @@ lin_lwp_attach_lwp (ptid_t ptid, int ver gdb_assert (pid == GET_LWP (ptid) && WIFSTOPPED (status) && WSTOPSIG (status)); + child_post_attach (pid); + lp->stopped = 1; if (debug_lin_lwp) @@ -1142,6 +1144,10 @@ child_wait (ptid_t ptid, struct target_w return minus_one_ptid; } + /* Handle GNU/Linux's extended waitstatus for trace events. */ + if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0) + return linux_handle_extended_wait (pid, status, ourstatus); + store_waitstatus (ourstatus, status); return pid_to_ptid (pid); } @@ -1563,6 +1569,14 @@ retry: else trap_ptid = null_ptid; + /* Handle GNU/Linux's extended waitstatus for trace events. */ + if (WIFSTOPPED (status) && WSTOPSIG (status) == SIGTRAP && status >> 16 != 0) + { + linux_handle_extended_wait (ptid_get_pid (trap_ptid), + status, ourstatus); + return trap_ptid; + } + store_waitstatus (ourstatus, status); return (threaded ? lp->ptid : pid_to_ptid (GET_LWP (lp->ptid))); } @@ -1732,6 +1746,12 @@ init_lin_lwp_ops (void) lin_lwp_ops.to_mourn_inferior = lin_lwp_mourn_inferior; lin_lwp_ops.to_thread_alive = lin_lwp_thread_alive; lin_lwp_ops.to_pid_to_str = lin_lwp_pid_to_str; + lin_lwp_ops.to_post_startup_inferior = child_post_startup_inferior; + lin_lwp_ops.to_post_attach = child_post_attach; + lin_lwp_ops.to_insert_fork_catchpoint = child_insert_fork_catchpoint; + lin_lwp_ops.to_insert_vfork_catchpoint = child_insert_vfork_catchpoint; + lin_lwp_ops.to_insert_exec_catchpoint = child_insert_exec_catchpoint; + lin_lwp_ops.to_stratum = thread_stratum; lin_lwp_ops.to_has_thread_control = tc_schedlock; lin_lwp_ops.to_magic = OPS_MAGIC; diff -Nurp -x '*.gmo' src-one/gdb/linux-nat.c src-two/gdb/linux-nat.c --- src-one/gdb/linux-nat.c 2003-06-18 18:27:58.000000000 -0400 +++ src-two/gdb/linux-nat.c 2003-06-18 18:30:10.000000000 -0400 @@ -54,6 +54,8 @@ #define __WALL 0x40000000 /* Wait for any child. */ #endif +extern struct target_ops child_ops; + struct simple_pid_list { int pid; @@ -187,13 +189,147 @@ linux_supports_tracefork (void) } +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 (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 parent_pid, child_pid; + + get_last_target_status (&last_ptid, &last_status); + 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.) */ + 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); + } else + { + char child_pid_spelling[40]; + + /* Needed to keep the breakpoint lists in sync. */ + 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); + + 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) + { + 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 = TARGET_WAITKIND_FORKED; + ourstatus->value.related_pid = new_pid; + 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 @@ -214,4 +350,43 @@ child_insert_exec_catchpoint (int pid) error ("Your system does not support exec catchpoints."); } +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 (); +}