2008-05-01 Daniel Jacobowitz <dan@codesourcery.com>
authorDaniel Jacobowitz <drow@false.org>
Thu, 1 May 2008 18:50:14 +0000 (18:50 +0000)
committerDaniel Jacobowitz <drow@false.org>
Thu, 1 May 2008 18:50:14 +0000 (18:50 +0000)
    Pedro Alves  <pedro@codesourcery.com>

Based on work by Jan Kratochvil <jan.kratochvil@redhat.com> and Jeff
  Johnston <jjohnstn@redhat.com>.

* NEWS: Mention attach to stopped process fix.
* infcmd.c (detach_command, disconnect_command): Discard the thread
list.
* infrun.c (handle_inferior_event): Do not ignore non-SIGSTOP while
attaching.  Use signal_stop_state.
(signal_stop_state): Check stop_soon.
* linux-nat.c (kill_lwp): Declare earlier.
(pid_is_stopped, linux_nat_post_attach_wait): New.
(lin_lwp_attach_lwp): Use linux_nat_post_attach_wait.  Update
comments.
(linux_nat_attach): Use linux_nat_post_attach_wait.
(detach_callback, linux_nat_detach): Improve handling for signalled
processes.
(linux_nat_pid_to_str): Always print out the LWP ID if it differs
from the process ID.
* Makefile.in (infcmd.o): Update.

2008-05-01  Jan Kratochvil  <jan.kratochvil@redhat.com>
    Daniel Jacobowitz  <dan@codesourcery.com>

* gdb.threads/attach-into-signal.c, gdb.threads/attach-into-signal.exp,
gdb.threads/attach-stopped.c, gdb.threads/attach-stopped.exp,
gdb.threads/attachstop-mt.c, gdb.threads/attachstop-mt.exp: New.

gdb/Makefile.in
gdb/NEWS
gdb/infcmd.c
gdb/infrun.c
gdb/linux-nat.c
gdb/testsuite/gdb.threads/attach-into-signal.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/attach-into-signal.exp [new file with mode: 0644]
gdb/testsuite/gdb.threads/attach-stopped.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/attach-stopped.exp [new file with mode: 0644]
gdb/testsuite/gdb.threads/attachstop-mt.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/attachstop-mt.exp [new file with mode: 0644]

index 7a3ce0350018d7850897635b63d579433d2621ea..a98351cd4cc3843d7dfe0a5fe11bddd9c787b46c 100644 (file)
@@ -2316,7 +2316,7 @@ infcmd.o: infcmd.c $(defs_h) $(gdb_string_h) $(symtab_h) $(gdbtypes_h) \
        $(objfiles_h) $(completer_h) $(ui_out_h) $(event_top_h) \
        $(parser_defs_h) $(regcache_h) $(reggroups_h) $(block_h) \
        $(solib_h) $(gdb_assert_h) $(observer_h) $(target_descriptions_h) \
-       $(user_regs_h) $(exceptions_h) $(cli_decode_h)
+       $(user_regs_h) $(exceptions_h) $(cli_decode_h) $(gdbthread_h)
 inf-loop.o: inf-loop.c $(defs_h) $(inferior_h) $(target_h) $(event_loop_h) \
        $(event_top_h) $(inf_loop_h) $(remote_h) $(exceptions_h) \
        $(language_h)
index fcda6d1646a17f4078d802b0be0c965f1ea8f5e2..4602ffdc8ab008b45b19ffcd62c437079093f159 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -4,11 +4,11 @@
 *** Changes since GDB 6.8
 
 * GDB now supports multiple function calling conventions according to the
-  DWARF-2 DW_AT_calling_convention function attribute.
+DWARF-2 DW_AT_calling_convention function attribute.
   
 * The SH target utilizes the aforementioned change to distinguish between gcc
-  and Renesas calling convention.  It also adds the new CLI commands
-  `set/show sh calling-convention'.
+and Renesas calling convention.  It also adds the new CLI commands
+`set/show sh calling-convention'.
 
 * GDB can now read compressed debug sections, as produced by GNU gold
 with the --compress-debug-sections=zlib flag.
@@ -21,6 +21,10 @@ which will be allocated using malloc later in program execution.
 * The qXfer:libraries:read remote procotol packet now allows passing a
 list of section offsets.
 
+* On GNU/Linux, GDB can now attach to stopped processes.  Several race
+conditions handling signals delivered during attach or thread creation
+have also been fixed.
+
 * New features in the GDB remote stub, gdbserver
 
   - The "--wrapper" command-line argument tells gdbserver to use a
index bdbb9bf0e06b9acf9ff35dcc16e8eaceb34f25a3..3165a26f3f72add135e7ce827530ba95e9954167 100644 (file)
@@ -50,6 +50,7 @@
 #include "user-regs.h"
 #include "exceptions.h"
 #include "cli/cli-decode.h"
+#include "gdbthread.h"
 
 /* Functions exported for general use, in inferior.h: */
 
@@ -2050,6 +2051,7 @@ detach_command (char *args, int from_tty)
   dont_repeat ();              /* Not for the faint of heart.  */
   target_detach (args, from_tty);
   no_shared_libraries (NULL, from_tty);
+  init_thread_list ();
   if (deprecated_detach_hook)
     deprecated_detach_hook ();
 }
@@ -2068,6 +2070,7 @@ disconnect_command (char *args, int from_tty)
   dont_repeat ();              /* Not for the faint of heart */
   target_disconnect (args, from_tty);
   no_shared_libraries (NULL, from_tty);
+  init_thread_list ();
   if (deprecated_detach_hook)
     deprecated_detach_hook ();
 }
index f576a59123d3b0d1cdb7def6b7596c16b7df8be3..16a802bb068c9010b9ccb062b76a9051a56f5bf9 100644 (file)
@@ -1946,13 +1946,15 @@ handle_inferior_event (struct execution_control_state *ecs)
 
       /* This originates from attach_command().  We need to overwrite
          the stop_signal here, because some kernels don't ignore a
-         SIGSTOP in a subsequent ptrace(PTRACE_SONT,SOGSTOP) call.
-         See more comments in inferior.h.  */
-      if (stop_soon == STOP_QUIETLY_NO_SIGSTOP)
+         SIGSTOP in a subsequent ptrace(PTRACE_CONT,SIGSTOP) call.
+         See more comments in inferior.h.  On the other hand, if we
+        get a non-SIGSTOP, report it to the user - assume the backend
+        will handle the SIGSTOP if it should show up later.  */
+      if (stop_soon == STOP_QUIETLY_NO_SIGSTOP
+         && stop_signal == TARGET_SIGNAL_STOP)
        {
          stop_stepping (ecs);
-         if (stop_signal == TARGET_SIGNAL_STOP)
-           stop_signal = TARGET_SIGNAL_0;
+         stop_signal = TARGET_SIGNAL_0;
          return;
        }
 
@@ -2023,7 +2025,7 @@ process_event_stop_test:
          target_terminal_ours_for_output ();
          print_stop_reason (SIGNAL_RECEIVED, stop_signal);
        }
-      if (signal_stop[stop_signal])
+      if (signal_stop_state (stop_signal))
        {
          stop_stepping (ecs);
          return;
@@ -3276,7 +3278,9 @@ hook_stop_stub (void *cmd)
 int
 signal_stop_state (int signo)
 {
-  return signal_stop[signo];
+  /* Always stop on signals if we're just gaining control of the
+     program.  */
+  return signal_stop[signo] || stop_soon != NO_STOP_QUIETLY;
 }
 
 int
index 9511815ecd6f478ce87714dda4465d54abe3b06b..b09cd498960283d0b8f3dc8296756a4a40777096 100644 (file)
 #include "event-loop.h"
 #include "event-top.h"
 
+/* Note on this file's use of signals:
+
+   We stop threads by sending a SIGSTOP.  The use of SIGSTOP instead
+   of another signal is not entirely significant; we just need for a
+   signal to be delivered, so that we can intercept it.  SIGSTOP's
+   advantage is that it can not be blocked.  A disadvantage is that it
+   is not a real-time signal, so it can only be queued once; we do not
+   keep track of other sources of SIGSTOP.
+
+   Two other signals that can't be blocked are SIGCONT and SIGKILL.
+   But we can't use them, because they have special behavior when the
+   signal is generated - not when it is delivered.  SIGCONT resumes
+   the entire thread group and SIGKILL kills the entire thread group.
+
+   A delivered SIGSTOP would stop the entire thread group, not just the
+   thread we tkill'd.  But we never let the SIGSTOP deliver; we always
+   intercept and cancel it (by PTRACE_CONT without passing SIGSTOP).
+
+   We could use a real-time signal instead.  This would solve those
+   problems; we could use PTRACE_GETSIGINFO to locate the specific
+   stop signals sent by GDB.  But we would still have to have some
+   support for SIGSTOP, since PTRACE_ATTACH generates it, and there
+   are races with trying to find a signal that is not blocked.  */
+
 #ifndef O_LARGEFILE
 #define O_LARGEFILE 0
 #endif
@@ -186,6 +210,7 @@ static void linux_nat_async (void (*callback)
                             (enum inferior_event_type event_type, void *context),
                             void *context);
 static int linux_nat_async_mask (int mask);
+static int kill_lwp (int lwpid, int signo);
 
 /* Captures the result of a successful waitpid call, along with the
    options used in that call.  */
@@ -1010,10 +1035,103 @@ exit_lwp (struct lwp_info *lp)
   delete_lwp (lp->ptid);
 }
 
-/* Attach to the LWP specified by PID.  If VERBOSE is non-zero, print
-   a message telling the user that a new LWP has been added to the
-   process.  Return 0 if successful or -1 if the new LWP could not
-   be attached.  */
+/* Detect `T (stopped)' in `/proc/PID/status'.
+   Other states including `T (tracing stop)' are reported as false.  */
+
+static int
+pid_is_stopped (pid_t pid)
+{
+  FILE *status_file;
+  char buf[100];
+  int retval = 0;
+
+  snprintf (buf, sizeof (buf), "/proc/%d/status", (int) pid);
+  status_file = fopen (buf, "r");
+  if (status_file != NULL)
+    {
+      int have_state = 0;
+
+      while (fgets (buf, sizeof (buf), status_file))
+       {
+         if (strncmp (buf, "State:", 6) == 0)
+           {
+             have_state = 1;
+             break;
+           }
+       }
+      if (have_state && strstr (buf, "T (stopped)") != NULL)
+       retval = 1;
+      fclose (status_file);
+    }
+  return retval;
+}
+
+/* Wait for the LWP specified by LP, which we have just attached to.
+   Returns a wait status for that LWP, to cache.  */
+
+static int
+linux_nat_post_attach_wait (ptid_t ptid, int first, int *cloned,
+                           int *signalled)
+{
+  pid_t new_pid, pid = GET_LWP (ptid);
+  int status;
+
+  if (pid_is_stopped (pid))
+    {
+      if (debug_linux_nat)
+       fprintf_unfiltered (gdb_stdlog,
+                           "LNPAW: Attaching to a stopped process\n");
+
+      /* The process is definitely stopped.  It is in a job control
+        stop, unless the kernel predates the TASK_STOPPED /
+        TASK_TRACED distinction, in which case it might be in a
+        ptrace stop.  Make sure it is in a ptrace stop; from there we
+        can kill it, signal it, et cetera.
+
+         First make sure there is a pending SIGSTOP.  Since we are
+        already attached, the process can not transition from stopped
+        to running without a PTRACE_CONT; so we know this signal will
+        go into the queue.  The SIGSTOP generated by PTRACE_ATTACH is
+        probably already in the queue (unless this kernel is old
+        enough to use TASK_STOPPED for ptrace stops); but since SIGSTOP
+        is not an RT signal, it can only be queued once.  */
+      kill_lwp (pid, SIGSTOP);
+
+      /* Finally, resume the stopped process.  This will deliver the SIGSTOP
+        (or a higher priority signal, just like normal PTRACE_ATTACH).  */
+      ptrace (PTRACE_CONT, pid, 0, 0);
+    }
+
+  /* Make sure the initial process is stopped.  The user-level threads
+     layer might want to poke around in the inferior, and that won't
+     work if things haven't stabilized yet.  */
+  new_pid = my_waitpid (pid, &status, 0);
+  if (new_pid == -1 && errno == ECHILD)
+    {
+      if (first)
+       warning (_("%s is a cloned process"), target_pid_to_str (ptid));
+
+      /* Try again with __WCLONE to check cloned processes.  */
+      new_pid = my_waitpid (pid, &status, __WCLONE);
+      *cloned = 1;
+    }
+
+  gdb_assert (pid == new_pid && WIFSTOPPED (status));
+
+  if (WSTOPSIG (status) != SIGSTOP)
+    {
+      *signalled = 1;
+      if (debug_linux_nat)
+       fprintf_unfiltered (gdb_stdlog,
+                           "LNPAW: Received %s after attaching\n",
+                           status_to_str (status));
+    }
+
+  return status;
+}
+
+/* Attach to the LWP specified by PID.  Return 0 if successful or -1
+   if the new LWP could not be attached.  */
 
 int
 lin_lwp_attach_lwp (ptid_t ptid)
@@ -1036,9 +1154,7 @@ lin_lwp_attach_lwp (ptid_t ptid)
      to happen.  */
   if (GET_LWP (ptid) != GET_PID (ptid) && lp == NULL)
     {
-      pid_t pid;
-      int status;
-      int cloned = 0;
+      int status, cloned = 0, signalled = 0;
 
       if (ptrace (PTRACE_ATTACH, GET_LWP (ptid), 0, 0) < 0)
        {
@@ -1057,24 +1173,18 @@ lin_lwp_attach_lwp (ptid_t ptid)
                            "LLAL: PTRACE_ATTACH %s, 0, 0 (OK)\n",
                            target_pid_to_str (ptid));
 
-      pid = my_waitpid (GET_LWP (ptid), &status, 0);
-      if (pid == -1 && errno == ECHILD)
+      status = linux_nat_post_attach_wait (ptid, 0, &cloned, &signalled);
+      lp = add_lwp (ptid);
+      lp->stopped = 1;
+      lp->cloned = cloned;
+      lp->signalled = signalled;
+      if (WSTOPSIG (status) != SIGSTOP)
        {
-         /* Try again with __WCLONE to check cloned processes.  */
-         pid = my_waitpid (GET_LWP (ptid), &status, __WCLONE);
-         cloned = 1;
+         lp->resumed = 1;
+         lp->status = status;
        }
 
-      gdb_assert (pid == GET_LWP (ptid)
-                 && WIFSTOPPED (status) && WSTOPSIG (status));
-
-      if (lp == NULL)
-       lp = add_lwp (ptid);
-      lp->cloned = cloned;
-
-      target_post_attach (pid);
-
-      lp->stopped = 1;
+      target_post_attach (GET_LWP (lp->ptid));
 
       if (debug_linux_nat)
        {
@@ -1133,10 +1243,7 @@ static void
 linux_nat_attach (char *args, int from_tty)
 {
   struct lwp_info *lp;
-  pid_t pid;
   int status;
-  int cloned = 0;
-  int options = 0;
 
   /* FIXME: We should probably accept a list of process id's, and
      attach all of them.  */
@@ -1151,54 +1258,69 @@ linux_nat_attach (char *args, int from_tty)
       sigdelset (&suspend_mask, SIGCHLD);
     }
 
-  /* Make sure the initial process is stopped.  The user-level threads
-     layer might want to poke around in the inferior, and that won't
-     work if things haven't stabilized yet.  */
-  pid = my_waitpid (GET_PID (inferior_ptid), &status, options);
-  if (pid == -1 && errno == ECHILD)
-    {
-      warning (_("%s is a cloned process"), target_pid_to_str (inferior_ptid));
-
-      /* Try again with __WCLONE to check cloned processes.  */
-      options = __WCLONE;
-      pid = my_waitpid (GET_PID (inferior_ptid), &status, options);
-      cloned = 1;
-    }
-
-  gdb_assert (pid == GET_PID (inferior_ptid)
-             && WIFSTOPPED (status) && WSTOPSIG (status) == SIGSTOP);
-
   /* Add the initial process as the first LWP to the list.  */
   inferior_ptid = BUILD_LWP (GET_PID (inferior_ptid), GET_PID (inferior_ptid));
   lp = add_lwp (inferior_ptid);
-  lp->cloned = cloned;
+
+  status = linux_nat_post_attach_wait (lp->ptid, 1, &lp->cloned,
+                                      &lp->signalled);
+  lp->stopped = 1;
 
   /* If this process is not using thread_db, then we still don't
      detect any other threads, but add at least this one.  */
   add_thread_silent (lp->ptid);
 
-  lp->stopped = 1;
+  /* Save the wait status to report later.  */
   lp->resumed = 1;
+  if (debug_linux_nat)
+    fprintf_unfiltered (gdb_stdlog,
+                       "LNA: waitpid %ld, saving status %s\n",
+                       (long) GET_PID (lp->ptid), status_to_str (status));
 
   if (!target_can_async_p ())
-    {
-      /* Fake the SIGSTOP that core GDB expects.  */
-      lp->status = W_STOPCODE (SIGSTOP);
-      if (debug_linux_nat)
-       fprintf_unfiltered (gdb_stdlog,
-                           "LNA: waitpid %ld, faking SIGSTOP\n", (long) pid);
-    }
+    lp->status = status;
   else
     {
       /* We already waited for this LWP, so put the wait result on the
         pipe.  The event loop will wake up and gets us to handling
         this event.  */
-      linux_nat_event_pipe_push (pid, status, options);
+      linux_nat_event_pipe_push (GET_PID (lp->ptid), status,
+                                lp->cloned ? __WCLONE : 0);
       /* Register in the event loop.  */
       target_async (inferior_event_handler, 0);
     }
 }
 
+/* Get pending status of LP.  */
+static int
+get_pending_status (struct lwp_info *lp, int *status)
+{
+  struct target_waitstatus last;
+  ptid_t last_ptid;
+
+  get_last_target_status (&last_ptid, &last);
+
+  /* If this lwp is the ptid that GDB is processing an event from, the
+     signal will be in stop_signal.  Otherwise, in all-stop + sync
+     mode, we may cache pending events in lp->status while trying to
+     stop all threads (see stop_wait_callback).  In async mode, the
+     events are always cached in waitpid_queue.  */
+
+  *status = 0;
+  if (GET_LWP (lp->ptid) == GET_LWP (last_ptid))
+    {
+      if (stop_signal != TARGET_SIGNAL_0
+         && signal_pass_state (stop_signal))
+       *status = W_STOPCODE (target_signal_to_host (stop_signal));
+    }
+  else if (target_can_async_p ())
+    queued_waitpid (GET_LWP (lp->ptid), status, __WALL);
+  else
+    *status = lp->status;
+
+  return 0;
+}
+
 static int
 detach_callback (struct lwp_info *lp, void *data)
 {
@@ -1209,40 +1331,30 @@ detach_callback (struct lwp_info *lp, void *data)
                        strsignal (WSTOPSIG (lp->status)),
                        target_pid_to_str (lp->ptid));
 
-  while (lp->signalled && lp->stopped)
+  /* If there is a pending SIGSTOP, get rid of it.  */
+  if (lp->signalled)
     {
-      errno = 0;
-      if (ptrace (PTRACE_CONT, GET_LWP (lp->ptid), 0,
-                 WSTOPSIG (lp->status)) < 0)
-       error (_("Can't continue %s: %s"), target_pid_to_str (lp->ptid),
-              safe_strerror (errno));
-
       if (debug_linux_nat)
        fprintf_unfiltered (gdb_stdlog,
-                           "DC:  PTRACE_CONTINUE (%s, 0, %s) (OK)\n",
-                           target_pid_to_str (lp->ptid),
-                           status_to_str (lp->status));
+                           "DC: Sending SIGCONT to %s\n",
+                           target_pid_to_str (lp->ptid));
 
-      lp->stopped = 0;
+      kill_lwp (GET_LWP (lp->ptid), SIGCONT);
       lp->signalled = 0;
-      lp->status = 0;
-      /* FIXME drow/2003-08-26: There was a call to stop_wait_callback
-        here.  But since lp->signalled was cleared above,
-        stop_wait_callback didn't do anything; the process was left
-        running.  Shouldn't we be waiting for it to stop?
-        I've removed the call, since stop_wait_callback now does do
-        something when called with lp->signalled == 0.  */
-
-      gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
     }
 
   /* We don't actually detach from the LWP that has an id equal to the
      overall process id just yet.  */
   if (GET_LWP (lp->ptid) != GET_PID (lp->ptid))
     {
+      int status = 0;
+
+      /* Pass on any pending signal for this LWP.  */
+      get_pending_status (lp, &status);
+
       errno = 0;
       if (ptrace (PTRACE_DETACH, GET_LWP (lp->ptid), 0,
-                 WSTOPSIG (lp->status)) < 0)
+                 WSTOPSIG (status)) < 0)
        error (_("Can't detach %s: %s"), target_pid_to_str (lp->ptid),
               safe_strerror (errno));
 
@@ -1252,7 +1364,6 @@ detach_callback (struct lwp_info *lp, void *data)
                            target_pid_to_str (lp->ptid),
                            strsignal (WSTOPSIG (lp->status)));
 
-      drain_queued_events (GET_LWP (lp->ptid));
       delete_lwp (lp->ptid);
     }
 
@@ -1263,6 +1374,9 @@ static void
 linux_nat_detach (char *args, int from_tty)
 {
   int pid;
+  int status;
+  enum target_signal sig;
+
   if (target_can_async_p ())
     linux_nat_async (NULL, 0);
 
@@ -1271,6 +1385,21 @@ linux_nat_detach (char *args, int from_tty)
   /* Only the initial process should be left right now.  */
   gdb_assert (num_lwps == 1);
 
+  /* Pass on any pending signal for the last LWP.  */
+  if ((args == NULL || *args == '\0')
+      && get_pending_status (lwp_list, &status) != -1
+      && WIFSTOPPED (status))
+    {
+      /* Put the signal number in ARGS so that inf_ptrace_detach will
+        pass it along with PTRACE_DETACH.  */
+      args = alloca (8);
+      sprintf (args, "%d", (int) WSTOPSIG (status));
+      fprintf_unfiltered (gdb_stdlog,
+                         "LND: Sending signal %s to %s\n",
+                         args,
+                         target_pid_to_str (lwp_list->ptid));
+    }
+
   trap_ptid = null_ptid;
 
   /* Destroy LWP info; it's no longer valid.  */
@@ -2848,7 +2977,9 @@ linux_nat_pid_to_str (ptid_t ptid)
 {
   static char buf[64];
 
-  if (lwp_list && lwp_list->next && is_lwp (ptid))
+  if (is_lwp (ptid)
+      && ((lwp_list && lwp_list->next)
+         || GET_PID (ptid) != GET_LWP (ptid)))
     {
       snprintf (buf, sizeof (buf), "LWP %ld", GET_LWP (ptid));
       return buf;
@@ -4205,4 +4336,3 @@ lin_thread_get_thread_signals (sigset_t *set)
   /* ... except during a sigsuspend.  */
   sigdelset (&suspend_mask, cancel);
 }
-
diff --git a/gdb/testsuite/gdb.threads/attach-into-signal.c b/gdb/testsuite/gdb.threads/attach-into-signal.c
new file mode 100644 (file)
index 0000000..653d36c
--- /dev/null
@@ -0,0 +1,67 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2008 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef USE_THREADS
+#include <pthread.h>
+#endif
+
+void action(int sig, siginfo_t * info, void *uc)
+{
+  raise (SIGALRM);
+}
+
+static void *func (void *arg)
+{
+  struct sigaction act;
+
+  memset (&act, 0, sizeof(struct sigaction));
+  act.sa_sigaction = action;
+  act.sa_flags = SA_RESTART;
+  sigaction (SIGALRM, &act, 0);
+
+  raise (SIGALRM);
+
+  /* We must not get past this point, either in a free standing or debugged
+     state.  */
+
+  abort ();
+  /* NOTREACHED */
+  return NULL;
+}
+
+int main ()
+{
+
+#ifndef USE_THREADS
+
+  func (NULL);
+
+#else
+
+  pthread_t th;
+  pthread_create (&th, NULL, func, NULL);
+  pthread_join (th, NULL);
+
+#endif
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/attach-into-signal.exp b/gdb/testsuite/gdb.threads/attach-into-signal.exp
new file mode 100644 (file)
index 0000000..ec59e45
--- /dev/null
@@ -0,0 +1,168 @@
+# Copyright 2008
+# Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This test was created by modifying attach-stopped.exp.
+# This file was created by Jan Kratochvil <jan.kratochvil@redhat.com>.
+
+# This test only works on Linux
+if { ![isnative] || [is_remote host] || ![istarget *-linux*] } {
+    continue
+}
+
+set testfile "attach-into-signal"
+set srcfile  ${testfile}.c
+set binfile  ${objdir}/${subdir}/${testfile}
+set escapedbinfile  [string_to_regexp ${objdir}/${subdir}/${testfile}]
+
+remote_exec build "rm -f ${binfile}"
+# For debugging this test
+#
+#log_user 1
+
+proc corefunc { threadtype } {
+    global srcfile
+    global binfile
+    global escapedbinfile
+    global srcdir
+    global subdir
+    global gdb_prompt
+
+    if [get_compiler_info ${binfile}] {
+       return -1
+    }
+
+    # Start the program running and then wait for a bit, to be sure
+    # that it can be attached to.
+    # Statistically there is a better chance without giving process a nice.
+
+    set testpid [eval exec $binfile &]
+    exec sleep 2
+
+    # Run 2 passes of the test.
+    # The C file inferior stops pending its signals if a single one is lost,
+    # we test successful redelivery of the caught signal by the 2nd pass.
+
+    # linux-2.6.20.4.x86_64 had maximal attempt # 20 in 4 test runs.
+    set attempts 100
+    set attempt 1
+    set passes 1
+    while { $passes < 3 && $attempt <= $attempts } {
+       set stoppedtry 0
+       while { $stoppedtry < 10 } {
+           if [catch {open /proc/${testpid}/status r} fileid] {
+               set stoppedtry 10
+               break
+           }
+           gets $fileid line1;
+           gets $fileid line2;
+           close $fileid;
+
+           if {![string match "*(stopped)*" $line2]} {
+               # No PASS message as we may be looping in multiple attempts.
+               break
+           }
+           sleep 1
+           set stoppedtry [expr $stoppedtry + 1]
+       }
+       if { $stoppedtry >= 10 } {
+           verbose -log $line2
+           set test "$threadtype: process is still running on the attempt # $attempt of $attempts"
+           break
+       }
+
+       # Main test:
+       set test "$threadtype: attach (pass $passes), pending signal catch"
+       if {[gdb_test_multiple "attach $testpid" $test {
+           -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*Received Alarm clock.*$gdb_prompt $" {
+               # nonthreaded:
+               pass $test
+               verbose -log "$test succeeded on the attempt # $attempt of $attempts"
+               set passes [expr $passes + 1]
+           }
+           -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*$gdb_prompt $" {
+               # nonthreaded:
+               # We just lack the luck, we should try it again.
+               set attempt [expr $attempt + 1]
+           }
+           -re "Attaching to process $testpid.*Received Alarm clock.*$gdb_prompt $" {
+               # threaded:
+               pass $test
+               verbose -log "$test succeeded on the attempt # $attempt of $attempts"
+               set passes [expr $passes + 1]
+           }
+           -re "Attaching to process $testpid.*$gdb_prompt $" {
+               # threaded:
+               # We just lack the luck, we should try it again.
+               set attempt [expr $attempt - 1]
+           }
+       }] != 0 } {
+           break
+       }
+
+       gdb_test "detach" "Detaching from.*" ""
+    }
+    if {$passes < 3} {
+       if {$attempt > $attempts} {
+           unresolved $test
+       } else {
+           fail $test
+       }
+    }
+
+    # Exit and detach the process.
+       
+    gdb_exit
+
+    # Make sure we don't leave a process around to confuse
+    # the next test run (and prevent the compile by keeping
+    # the text file busy), in case the "set should_exit" didn't
+    # work.
+
+    # Continue the program - some Linux kernels need it before -9 if the
+    # process is stopped.
+    remote_exec build "kill -s CONT ${testpid}"
+       
+    remote_exec build "kill -9 ${testpid}"
+}
+
+# Start with clean gdb
+gdb_exit
+
+# build the test case first without threads
+#
+if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+    gdb_suppress_entire_file "Testcase nonthraded compile failed, so all tests in this file will automatically fail."
+}
+
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+gdb_test "set debug lin-lwp 1" "" ""
+
+corefunc nonthreaded
+
+# build the test case also with threads
+#
+if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug additional_flags=-DUSE_THREADS}] != "" } {
+    gdb_suppress_entire_file "Testcase threaded compile failed, so all tests in this file will automatically fail."
+}
+
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+gdb_test "set debug lin-lwp 1" "" ""
+
+corefunc threaded
diff --git a/gdb/testsuite/gdb.threads/attach-stopped.c b/gdb/testsuite/gdb.threads/attach-stopped.c
new file mode 100644 (file)
index 0000000..1343f67
--- /dev/null
@@ -0,0 +1,50 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2008 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* This program is intended to be started outside of gdb, then
+   manually stopped via a signal.  */
+
+#include <stddef.h>
+#include <unistd.h>
+#ifdef USE_THREADS
+#include <pthread.h>
+#endif
+
+static void *func (void *arg)
+{
+  sleep (10000);  /* Ridiculous time, but we will eventually kill it.  */
+  sleep (10000);  /* Second sleep.  */
+  return NULL;
+}
+
+int main ()
+{
+
+#ifndef USE_THREADS
+
+  func (NULL);
+
+#else
+
+  pthread_t th;
+  pthread_create (&th, NULL, func, NULL);
+  pthread_join (th, NULL);
+
+#endif
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/attach-stopped.exp b/gdb/testsuite/gdb.threads/attach-stopped.exp
new file mode 100644 (file)
index 0000000..27da341
--- /dev/null
@@ -0,0 +1,157 @@
+# Copyright 2008
+# Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This test was created by modifying attach.exp.
+# This file was created by Jeff Johnston <jjohnstn@redhat.com>.
+# This file was updated by Jan Kratochvil <jan.kratochvil@redhat.com>.
+
+# This test only works on Linux
+if { ![isnative] || [is_remote host] || ![istarget *-linux*] } {
+    continue
+}
+
+set testfile "attach-stopped"
+set srcfile  ${testfile}.c
+set binfile  ${objdir}/${subdir}/${testfile}
+set escapedbinfile  [string_to_regexp ${objdir}/${subdir}/${testfile}]
+
+#execute_anywhere "rm -f ${binfile}"
+remote_exec build "rm -f ${binfile}"
+# For debugging this test
+#
+#log_user 1
+
+proc corefunc { threadtype } {
+    global srcfile
+    global binfile
+    global escapedbinfile
+    global srcdir
+    global subdir
+    global gdb_prompt
+
+    if [get_compiler_info ${binfile}] {
+       return -1
+    }
+
+    # Start the program running and then wait for a bit, to be sure
+    # that it can be attached to.
+
+    set testpid [eval exec $binfile &]
+
+    # Avoid some race:
+    sleep 2
+
+    # Stop the program 
+    remote_exec build "kill -s STOP ${testpid}"
+
+    # Start with clean gdb
+    gdb_exit
+    gdb_start
+    gdb_reinitialize_dir $srcdir/$subdir
+    gdb_load ${binfile}
+
+    # Verify that we can attach to the stopped process.
+       
+    set test "$threadtype: attach2 to stopped, after setting file"
+    gdb_test_multiple "attach $testpid" "$test" {
+       -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*$gdb_prompt $" {
+           pass "$test"
+       }
+    }
+
+    # ".*sleep.*clone.*" would fail on s390x as bt stops at START_THREAD there.
+    if {[string equal $threadtype threaded]} {
+       gdb_test "thread apply all bt" ".*sleep.*start_thread.*" "$threadtype: attach2 to stopped bt"
+    } else {
+       gdb_test "bt" ".*sleep.*main.*" "$threadtype: attach2 to stopped bt"
+    }
+    # This breakpoint is there for old/non-x86 kernels not restarting syscalls.
+    gdb_breakpoint [gdb_get_line_number "Second sleep"]
+    set test "$threadtype: attach2 continue"
+    send_gdb "continue\n"
+    gdb_expect {
+      -re "Continuing"
+       { pass "continue ($test)" }
+      timeout
+       { fail "continue ($test) (timeout)" }
+    }
+
+    # For this to work we must be sure to consume the "Continuing."
+    # message first, or GDB's signal handler may not be in place.
+    after 1000 {send_gdb "\003"}
+    set test "$threadtype: attach2 stop interrupt"
+    gdb_expect 10 {
+      -re "Program received signal SIGINT.*$gdb_prompt $"
+       {
+         pass $test
+       }
+      -re "Breakpoint \[0-9\].*$srcfile.*$gdb_prompt $"
+       {
+         pass $test
+       }
+      timeout
+       {
+         fail $test
+       }
+    }
+
+    gdb_exit
+
+    # Avoid some race:
+    sleep 2
+
+    # At this point, the process should be sleeping
+
+    if [catch {open /proc/${testpid}/status r} fileid2] {
+       set line2 "NOTFOUND"
+    } else {
+       gets $fileid2 line1;
+       gets $fileid2 line2;
+       close $fileid2;
+    }
+
+    set test "$threadtype: attach2, exit leaves process sleeping"
+    if {[string match "*(sleeping)*" $line2]} {
+      pass $test
+    } else {
+      fail $test
+    }
+
+    # Make sure we don't leave a process around to confuse
+    # the next test run (and prevent the compile by keeping
+    # the text file busy), in case the "set should_exit" didn't
+    # work.
+       
+    remote_exec build "kill -9 ${testpid}"
+}
+
+# build the test case first without threads
+#
+if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+    gdb_suppress_entire_file "Testcase nonthraded compile failed, so all tests in this file will automatically fail."
+}
+
+corefunc nonthreaded
+
+# build the test case first without threads
+#
+if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug additional_flags=-DUSE_THREADS}] != "" } {
+    gdb_suppress_entire_file "Testcase threaded compile failed, so all tests in this file will automatically fail."
+}
+
+corefunc threaded
+
+return 0
diff --git a/gdb/testsuite/gdb.threads/attachstop-mt.c b/gdb/testsuite/gdb.threads/attachstop-mt.c
new file mode 100644 (file)
index 0000000..9d631d4
--- /dev/null
@@ -0,0 +1,55 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2008 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* This program is intended to be started outside of gdb, then
+   manually stopped via a signal.  */
+
+#include <unistd.h>
+#include <pthread.h>
+#include <stdio.h>
+
+/* Red Hat BZ PR 197584.c */
+
+void *func (void *arg)
+{
+  sleep (10000);  /* Ridiculous time, but we will eventually kill it.  */
+  sleep (10000);       /* RHEL3U8 kernel-2.4.21-47.EL will cut the sleep time.  */
+
+  return NULL; /* thread-sleep */
+}
+
+int main ()
+{
+  pthread_t t1,t2;
+  int ret;
+
+  ret = pthread_create (&t1, NULL, func, NULL);
+  if (ret)
+    fprintf(stderr, "pthread_create(): %s", strerror (ret));
+  ret = pthread_create (&t2, NULL, func, NULL);
+  if (ret)
+    fprintf(stderr, "pthread_create(): %s", strerror (ret));
+
+  ret = pthread_join (t1, NULL);
+  if (ret)     /* first-join */
+    fprintf(stderr, "pthread_join(): %s", strerror (ret));
+  ret = pthread_join (t2, NULL);
+  if (ret)
+    fprintf(stderr, "pthread_join(): %s", strerror (ret));
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/attachstop-mt.exp b/gdb/testsuite/gdb.threads/attachstop-mt.exp
new file mode 100644 (file)
index 0000000..167f052
--- /dev/null
@@ -0,0 +1,260 @@
+# Copyright 2008
+# Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This test was created by modifying gdb.threads/attachstop.
+# This file was created by Jan Kratochvil <jan.kratochvil@redhat.com>.
+# Regression for: https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=197584
+
+# This test only works on Linux
+if { ![isnative] || [is_remote host] || ![istarget *-linux*] } {
+    continue
+}
+
+set testfile "attachstop-mt"
+set srcfile  ${testfile}.c
+set binfile  ${objdir}/${subdir}/${testfile}
+set escapedbinfile  [string_to_regexp ${objdir}/${subdir}/${testfile}]
+
+#execute_anywhere "rm -f ${binfile}"
+remote_exec build "rm -f ${binfile}"
+# For debugging this test
+#
+#log_user 1
+
+# build the test case
+#
+if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+    gdb_suppress_entire_file "Testcase compile failed, so all tests in this file will automatically fail."
+}
+
+if [get_compiler_info ${binfile}] {
+    return -1
+}
+
+# Start the program running and then wait for a bit, to be sure
+# that it can be attached to.
+
+set testpid [eval exec $binfile &]
+
+# No race
+sleep 2
+
+# Do not: set testpid2 [expr $testpid + 1]
+# as it will not exist on Red Hat 2.6.9-34.0.2.ELsmp
+set testpid2 [expr $testpid + 2]
+
+set status2 /proc/${testpid}/task/${testpid2}/status
+if {[expr ! [file exists $status2]]} {
+  # kernel-2.4
+  set status2 /proc/${testpid2}/status
+}
+
+# Initial sanity test it is normally sleeping
+set fileid0 [open $status2 r];
+gets $fileid0 line1;
+gets $fileid0 line2;
+close $fileid0;
+
+set test "attach0, initial sanity check of the sleeping state"
+if {[string match "*(sleeping)*" $line2]} {
+  pass $test
+} else {
+  fail $test
+}
+
+# Sttach and detach to test it will not become stopped
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+set test "attach0 to sleeping"
+gdb_test_multiple "attach $testpid" "$test" {
+    -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*$gdb_prompt $" {
+        pass "$test"
+    }
+}
+
+gdb_test "gcore /dev/null" ".*aved corefile.*" "attach0 to sleeping gcore invocation"
+
+gdb_test "thread 2" ".*witching to thread 2 .*" "attach0 to sleeping switch thread"
+
+gdb_test "bt" ".*sleep.*func.*" "attach0 to sleeping bt"
+
+# Exit and detach the process.
+
+gdb_exit
+
+# No race
+sleep 2
+
+# Check it did not get stopped by our gdb
+set fileid1 [open $status2 r];
+gets $fileid1 line1;
+gets $fileid1 line2;
+close $fileid1;
+
+set test "attach1, post-gdb sanity check of the sleeping state - Red Hat BZ 197584"
+if {[string match "*(sleeping)*" $line2]} {
+  pass $test
+} else {
+  fail $test
+}
+
+# Stop the program 
+remote_exec build "kill -s STOP ${testpid}"
+
+# No race
+sleep 2
+
+# Check it really got stopped by kill(1)
+set fileid2 [open $status2 r];
+gets $fileid2 line1;
+gets $fileid2 line2;
+close $fileid2;
+
+set test "attach2, initial sanity check of the stopped state"
+if {[string match "*(stopped)*" $line2]} {
+  pass $test
+} else {
+  fail $test
+}
+
+# Start with clean gdb
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+# Verify that we can attach to the process by first giving its
+# executable name via the file command, and using attach with the
+# process ID.
+
+set test "set file, before attach3 to stopped process"
+gdb_test_multiple "file $binfile" "$test" {
+   -re "Load new symbol table from.*y or n. $" {
+        gdb_test "y" "Reading symbols from $escapedbinfile\.\.\.*done." \
+               "$test (re-read)"
+    }
+    -re "Reading symbols from $escapedbinfile\.\.\.*done.*$gdb_prompt $" {
+        pass "$test"
+    }
+}
+
+set test "attach3 to stopped, after setting file"
+gdb_test_multiple "attach $testpid" "$test" {
+    -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*$gdb_prompt $" {
+        pass "$test"
+    }
+}
+
+# We may be already after the threads phase.
+# `thread 2' command is important for the test to switch the current thread to
+# a non-primary one for the detach process.
+
+gdb_test "thread 2" ".*(witching to thread 2 |hread ID 2 not known).*" "attach3 to stopped switch thread"
+gdb_test "bt" ".*sleep.*(func|main).*" "attach3 to stopped bt"
+
+# Exit and detach the process.
+gdb_exit
+
+# Stop the program 
+remote_exec build "kill -s STOP ${testpid}"
+
+# No race
+sleep 2
+
+# Continue the test as we would hit another expected bug regarding
+#      Program received signal SIGSTOP, Stopped (signal).
+# across NPTL threads.
+
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+# Verify that we can attach to the process just by giving the
+# process ID.
+   
+set test "attach4 to stopped, after setting file"
+gdb_test_multiple "attach $testpid" "$test" {
+    -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*$gdb_prompt $" {
+        pass "$test"
+    }
+}
+
+# We may be already after the threads phase.
+# `thread 2' command is important for the test to switch the current thread to
+# a non-primary one for the detach process.
+
+gdb_test "thread 2" ".*(witching to thread 2 |hread ID 2 not known).*" "attach4 to stopped switch thread"
+gdb_test "bt" ".*sleep.*(func|main).*" "attach4 to stopped bt"
+
+# RHEL3U8 kernel-2.4.21-47.EL will not return SIGINT but only shorten the sleep.
+gdb_breakpoint [gdb_get_line_number "Ridiculous time"]
+gdb_breakpoint [gdb_get_line_number "cut the sleep time"]
+set test "attach4 continue"
+send_gdb "continue\n"
+gdb_expect {
+  -re "Continuing"
+    { pass "continue ($test)" }
+  timeout
+    { fail "continue ($test) (timeout)" }
+}
+
+# For this to work we must be sure to consume the "Continuing."
+# message first, or GDB's signal handler may not be in place.
+after 1000 {send_gdb "\003"}
+set test "attach4 stop by interrupt"
+gdb_expect {
+  -re "Program received signal SIGINT.*$gdb_prompt $"
+    {
+      pass $test
+    }
+  -re "Breakpoint \[0-9\].*$srcfile.*$gdb_prompt $"
+    {
+      pass $test
+    }
+  timeout
+    {
+      fail "$test (timeout)"
+    }
+}
+
+gdb_exit
+
+# No race
+sleep 2
+
+# At this point, the process should be sleeping
+
+set fileid4 [open $status2 r];
+gets $fileid4 line1;
+gets $fileid4 line2;
+close $fileid4;
+
+set test "attach4, exit leaves process sleeping"
+if {[string match "*(sleeping)*" $line2]} {
+  pass $test
+} else {
+  fail $test
+}
+
+# Make sure we don't leave a process around to confuse
+# the next test run (and prevent the compile by keeping
+# the text file busy), in case the "set should_exit" didn't
+# work.
+   
+remote_exec build "kill -9 ${testpid}"
+
+return 0