Message ID | 53B6E978.4070903@redhat.com |
---|---|
State | New |
Headers | show |
On 07/05/14 01:50, Pedro Alves wrote: > 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 <hui@codesourcery.com> >> >> * 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 <palves@redhat.com> > 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. Hi Pedro, Thanks for your work. The patch fixed the issue and pass regression test in Ubuntu 10.04 that use Linux 2.6.32. Best, Hui > > gdb/ > 2014-07-04 Pedro Alves <palves@redhat.com> > > * 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; > } >
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; }