From patchwork Fri Jul 4 17:50:48 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pedro Alves X-Patchwork-Id: 1922 Received: (qmail 31090 invoked by alias); 4 Jul 2014 17:51:00 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 31043 invoked by uid 89); 4 Jul 2014 17:50:57 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-1.9 required=5.0 tests=AWL, BAYES_00, SPF_HELO_PASS, SPF_PASS, T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Fri, 04 Jul 2014 17:50:53 +0000 Received: from int-mx10.intmail.prod.int.phx2.redhat.com (int-mx10.intmail.prod.int.phx2.redhat.com [10.5.11.23]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id s64HooPq007056 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 4 Jul 2014 13:50:50 -0400 Received: from [127.0.0.1] (ovpn01.gateway.prod.ext.ams2.redhat.com [10.39.146.11]) by int-mx10.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id s64Hom4Q015517; Fri, 4 Jul 2014 13:50:49 -0400 Message-ID: <53B6E978.4070903@redhat.com> Date: Fri, 04 Jul 2014 18:50:48 +0100 From: Pedro Alves User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.6.0 MIME-Version: 1.0 To: Hui Zhu , gdb-patches ml Subject: [PATCH] Handle signals sent to a fork/vfork child before it has a chance to first run (Re: [PATCH] Fix gdb.base/watch-vfork.exp: Watchpoint triggers after vfork (sw) (timeout) with Linux 2.6.32 and older version) References: <533D17E2.9070402@mentor.com> <538636AF.9040208@redhat.com> <539020AB.8050105@mentor.com> <53902DAA.4090602@redhat.com> <5394460F.7060201@mentor.com> <53B583B2.1040407@mentor.com> In-Reply-To: <53B583B2.1040407@mentor.com> On 07/03/2014 05:24 PM, Hui Zhu wrote: > After this patch, I still got fail with this test in Linux 2.6.32. > The cause this: > signo = WSTOPSIG (status); > #In linux 2.6.32, signo will be SIGSTOP. > if (signo != 0 > && !signal_pass_state (gdb_signal_from_host (signo))) > signo = 0; > #SIGSTOP will send to child and make it stop. > ptrace (PTRACE_DETACH, child_pid, 0, signo); > > So I make a patch to fix it. > > Thanks, > Hui > > 2014-07-04 Hui Zhu > > * linux-nat.c(linux_child_follow_fork): Add check if signo > is SIGSTOP. > > --- a/gdb/linux-nat.c > +++ b/gdb/linux-nat.c > @@ -472,8 +472,9 @@ holding the child stopped. Try \"set de > int signo; > > signo = WSTOPSIG (status); > - if (signo != 0 > - && !signal_pass_state (gdb_signal_from_host (signo))) > + if (signo == SIGSTOP > + || (signo != 0 > + && !signal_pass_state (gdb_signal_from_host (signo)))) > signo = 0; > ptrace (PTRACE_DETACH, child_pid, 0, signo); > } > Hmm. We should actually be passing the signal the fork child had stopped for earlier, when we step it for the workaround. But we lose that signal... /me hacks. Does this patch fix the issue too ? 8<----------- From 4184b4d0ffca89776d06d6b7d1241e9c0d01017a Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Fri, 4 Jul 2014 17:31:41 +0100 Subject: [PATCH] Handle signals sent to a fork/vfork child before it has a chance to first run This is a WIP. We handle this for clone, but not for fork/vfork. For clone it's easier as we just store the status in the new child's lwp immediately. But for fork/vfork we don't add the child to the list until the next resume, when we either follow or detach the child, depending on follow-fork mode, in linux_child_follow_fork. So store the child's status in a new simple_pid_list list. I haven't tried to write a test yet. I think we have one for clones somewhere, which we can model on. Tested on x86_64 Fedora 20 with no regressions. gdb/ 2014-07-04 Pedro Alves * linux-nat.c (forked_pids): New global. (save_new_child_stop_status): New function. (get_status_pass_signal): New function. (linux_child_follow_fork): Retrieve the child's stop status from the forked pids list, and pass the signal to the child if detaching from it, or leave it pending if not detaching. (cancel_pending_sigstop): New function, factored out from detach_callback. (detach_callback): Adjust to use cancel_pending_sigstop. (linux_handle_extended_wait): Store the fork/vfork child's status. Adjust comment. --- gdb/linux-nat.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 25 deletions(-) diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 0ab0362..1435079 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -223,6 +223,10 @@ struct simple_pid_list }; struct simple_pid_list *stopped_pids; +/* The status of forked pids we've already seen the + PTRACE_EVENT_FORK/VFORK event for, but haven't followed yet. */ +static struct simple_pid_list *forked_pids; + /* Async mode support. */ /* The read/write ends of the pipe registered as waitable file in the @@ -371,12 +375,71 @@ delete_lwp_cleanup (void *lp_voidp) delete_lwp (lp->ptid); } +/* CHILD_LP is a new clone/fork child that stopped with STATUS. If + STATUS is not SIGSTOP, store the status for later, and mark the lwp + as signalled (there's a SIGSTOP pending in the kernel queue). This + can happen if someone starts sending signals to the new thread + before it gets a chance to run, which have a lower number than + SIGSTOP (e.g. SIGUSR1). */ + +static void +save_new_child_stop_status (struct lwp_info *child_lp, int *status_p) +{ + int status = *status_p; + + if (status != 0) + { + gdb_assert (WIFSTOPPED (status)); + + if (WSTOPSIG (status) != SIGSTOP) + { + child_lp->signalled = 1; + + /* Save the wait status to report later. */ + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "SCSS: waitpid of new LWP %ld, " + "saving status %s\n", + ptid_get_lwp (child_lp->ptid), + status_to_str (status)); + } + else + status = 0; + } + + child_lp->status = status; + *status_p = status; +} + +/* We're about to detach from a child that had stopped with STATUS. + Return the host signal number to pass, if any. */ + +static int +get_pass_signal_from_status (int status) +{ + if (status != 0 && WIFSTOPPED (status)) + { + int signo; + + signo = WSTOPSIG (status); + if (signo != 0 + && !signal_pass_state (gdb_signal_from_host (signo))) + signo = 0; + return signo; + } + + return 0; +} + +static void cancel_pending_sigstop (struct lwp_info *lp); + static int linux_child_follow_fork (struct target_ops *ops, int follow_child, int detach_fork) { int has_vforked; int parent_pid, child_pid; + int child_status; has_vforked = (inferior_thread ()->pending_follow.kind == TARGET_WAITKIND_VFORKED); @@ -404,6 +467,11 @@ holding the child stopped. Try \"set detach-on-fork\" or \ return 1; } + /* If we haven't already seen the new PID stop, wait for it now. */ + if (!pull_pid_from_list (&forked_pids, child_pid, &child_status)) + internal_error (__FILE__, __LINE__, + _("forked pid %d not found in forked list"), child_pid); + if (! follow_child) { struct lwp_info *child_lp = NULL; @@ -414,7 +482,6 @@ holding the child stopped. Try \"set detach-on-fork\" or \ if (detach_fork) { struct cleanup *old_chain; - int status = W_STOPCODE (0); /* Before detaching from the child, remove all breakpoints from it. If we forked, then this has already been taken @@ -444,8 +511,12 @@ holding the child stopped. Try \"set detach-on-fork\" or \ child_lp = add_lwp (inferior_ptid); child_lp->stopped = 1; child_lp->last_resume_kind = resume_stop; + save_new_child_stop_status (child_lp, &child_status); make_cleanup (delete_lwp_cleanup, child_lp); + /* If there is a pending SIGSTOP, get rid of it. */ + cancel_pending_sigstop (child_lp); + if (linux_nat_prepare_to_resume != NULL) linux_nat_prepare_to_resume (child_lp); @@ -455,26 +526,26 @@ holding the child stopped. Try \"set detach-on-fork\" or \ process starts with the TIF_SINGLESTEP/X86_EFLAGS_TF bits set if the parent process had them set. To work around this, single step the child process - once before detaching to clear the flags. */ + once before detaching to clear the flags. + Be careful not to lose any signal though. */ if (!gdbarch_software_single_step_p (target_thread_architecture - (child_lp->ptid))) + (child_lp->ptid))) { + int signo = get_pass_signal_from_status (child_status); + linux_disable_event_reporting (child_pid); - if (ptrace (PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) + signo = get_pass_signal_from_status (child_status); + if (ptrace (PTRACE_SINGLESTEP, child_pid, 0, signo) < 0) perror_with_name (_("Couldn't do single step")); - if (my_waitpid (child_pid, &status, 0) < 0) + if (my_waitpid (child_pid, &child_status, 0) < 0) perror_with_name (_("Couldn't wait vfork process")); } - if (WIFSTOPPED (status)) + if (child_status != 0 && WIFSTOPPED (child_status)) { - int signo; + int signo = get_pass_signal_from_status (child_status); - signo = WSTOPSIG (status); - if (signo != 0 - && !signal_pass_state (gdb_signal_from_host (signo))) - signo = 0; ptrace (PTRACE_DETACH, child_pid, 0, signo); } @@ -502,6 +573,7 @@ holding the child stopped. Try \"set detach-on-fork\" or \ child_lp = add_lwp (inferior_ptid); child_lp->stopped = 1; child_lp->last_resume_kind = resume_stop; + save_new_child_stop_status (child_lp, &child_status); child_inf->symfile_flags = SYMFILE_NO_READ; /* If this is a vfork child, then the address-space is @@ -696,6 +768,7 @@ holding the child stopped. Try \"set detach-on-fork\" or \ child_lp = add_lwp (inferior_ptid); child_lp->stopped = 1; child_lp->last_resume_kind = resume_stop; + save_new_child_stop_status (child_lp, &child_status); /* If this is a vfork child, then the address-space is shared with the parent. If we detached from the parent, then we can @@ -1510,27 +1583,41 @@ get_pending_status (struct lwp_info *lp, int *status) return 0; } -static int -detach_callback (struct lwp_info *lp, void *data) -{ - gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status)); - - if (debug_linux_nat && lp->status) - fprintf_unfiltered (gdb_stdlog, "DC: Pending %s for %s on detach.\n", - strsignal (WSTOPSIG (lp->status)), - target_pid_to_str (lp->ptid)); +/* If LP was signalled, can't the pending SIGSTOP with a SIGCONT. */ +static void +cancel_pending_sigstop (struct lwp_info *lp) +{ /* If there is a pending SIGSTOP, get rid of it. */ if (lp->signalled) { + /* This can happen if someone starts sending signals to + the new thread before it gets a chance to run, which + have a lower number than SIGSTOP (e.g. SIGUSR1). */ + + /* There is a pending SIGSTOP; get rid of it. */ if (debug_linux_nat) fprintf_unfiltered (gdb_stdlog, - "DC: Sending SIGCONT to %s\n", + "Sending SIGCONT to %s\n", target_pid_to_str (lp->ptid)); kill_lwp (ptid_get_lwp (lp->ptid), SIGCONT); lp->signalled = 0; } +} + +static int +detach_callback (struct lwp_info *lp, void *data) +{ + gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status)); + + if (debug_linux_nat && lp->status) + fprintf_unfiltered (gdb_stdlog, "DC: Pending %s for %s on detach.\n", + strsignal (WSTOPSIG (lp->status)), + target_pid_to_str (lp->ptid)); + + /* If there is a pending SIGSTOP, get rid of it. */ + cancel_pending_sigstop (lp); /* We don't actually detach from the LWP that has an id equal to the overall process id just yet. */ @@ -2061,6 +2148,14 @@ linux_handle_extended_wait (struct lwp_info *lp, int status, return 0; } + if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK) + { + /* The child may have stopped with a signal other than + SIGSTOP. Store the status for later, so we don't lose + the signal. */ + add_to_pid_list (&forked_pids, new_pid, status); + } + if (event == PTRACE_EVENT_FORK) ourstatus->kind = TARGET_WAITKIND_FORKED; else if (event == PTRACE_EVENT_VFORK) @@ -2086,10 +2181,8 @@ linux_handle_extended_wait (struct lwp_info *lp, int status, /* This can happen if someone starts sending signals to the new thread before it gets a chance to run, which have a lower number than SIGSTOP (e.g. SIGUSR1). - This is an unlikely case, and harder to handle for - fork / vfork than for clone, so we do not try - but - we handle it for clone events here. We'll send - the other signal on to the thread below. */ + We'll send the other signal on to the thread + below. */ new_lp->signalled = 1; }