This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[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)
- From: Pedro Alves <palves at redhat dot com>
- To: Hui Zhu <hui_zhu at mentor dot com>, gdb-patches ml <gdb-patches at sourceware dot org>
- Date: Fri, 04 Jul 2014 18:50:48 +0100
- 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)
- Authentication-results: sourceware.org; auth=none
- References: <533D17E2 dot 9070402 at mentor dot com> <538636AF dot 9040208 at redhat dot com> <539020AB dot 8050105 at mentor dot com> <53902DAA dot 4090602 at redhat dot com> <5394460F dot 7060201 at mentor dot com> <53B583B2 dot 1040407 at mentor dot 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 <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.
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;
}
--
1.9.3