This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [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)




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;
  	    }



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]