Re: [patch] Fix Linux attach to signalled/stopped processes

Daniel Jacobowitz
2008-04-14T15:09:59Z

On Mon, Apr 14, 2008 at 10:34:48AM -0400, Daniel Jacobowitz wrote:
> Fixing the race is not hard though: grub around in /proc for status.
> First PTRACE_ATTACH, which sends a SIGSTOP. Then check /proc. If it
> is running, it must have a pending SIGSTOP; we can wait for it so skip
> the tkill and the PTRACE_CONT. If it is not running, it may or may
> not have a pending SIGSTOP. So do as you described above with tkill
> and PTRACE_CONT.

Which gives me this patch.

I discussed this with Jan on IRC; it does not leave processes stopped
on detach if they were stopped on attach. The Red Hat version of the
patch does so. If we like that change, we can add it separately; and
we can add an explicit command to detach-and-leave-stopped. Pedro,
I'm not sure about "detach $pid", but we can surely come up with some
spelling for it...

This version is tested on x86_64-linux and looks good. I'll wait a
few days for anyone else who wants to try or look over it.

Big thanks to Jeff Johnston and Jan Kratochvil for the original work,
Roland for advice, Pedro for async mode fixups, and Doug for prodding
me to look at this issue again.

--
Daniel Jacobowitz
CodeSourcery

2008-04-14 Jan Kratochvil <jan.kratochvil@redhat.com>
Daniel Jacobowitz <dan@codesourcery.com>
Pedro Alves <pedro@codesourcery.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-04-14 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.

Index: Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/Makefile.in,v
retrieving revision 1.998
diff -u -p -r1.998 Makefile.in
--- Makefile.in 9 Apr 2008 13:29:51 -0000 1.998
+++ Makefile.in 14 Apr 2008 14:56:16 -0000
@@ -2291,7 +2291,7 @@ infcmd.o: infcmd.c $(defs_h) $(gdb_strin
$(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)
+ $(user_regs_h) $(exceptions_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: NEWS
===================================================================
RCS file: /cvs/src/src/gdb/NEWS,v
retrieving revision 1.264
diff -u -p -r1.264 NEWS
--- NEWS 4 Apr 2008 15:51:15 -0000 1.264
+++ NEWS 14 Apr 2008 14:56:17 -0000
@@ -7,7 +7,11 @@
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.
+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

Index: infcmd.c
===================================================================
RCS file: /cvs/src/src/gdb/infcmd.c,v
retrieving revision 1.174
diff -u -p -r1.174 infcmd.c
--- infcmd.c 17 Mar 2008 17:30:29 -0000 1.174
+++ infcmd.c 14 Apr 2008 14:56:17 -0000
@@ -49,6 +49,7 @@
#include "target-descriptions.h"
#include "user-regs.h"
#include "exceptions.h"
+#include "gdbthread.h"

/* Functions exported for general use, in inferior.h: */

@@ -2067,6 +2068,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 ();
}
@@ -2085,6 +2087,7 @@ disconnect_command (char *args, int from
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: infrun.c
===================================================================
RCS file: /cvs/src/src/gdb/infrun.c,v
retrieving revision 1.267
diff -u -p -r1.267 infrun.c
--- infrun.c 15 Mar 2008 14:55:21 -0000 1.267
+++ infrun.c 14 Apr 2008 14:56:17 -0000
@@ -1947,13 +1947,15 @@ handle_inferior_event (struct execution_

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

@@ -2024,7 +2026,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;
@@ -3287,7 +3289,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: linux-nat.c
===================================================================
RCS file: /cvs/src/src/gdb/linux-nat.c,v
retrieving revision 1.80
diff -u -p -r1.80 linux-nat.c
--- linux-nat.c 25 Mar 2008 12:26:21 -0000 1.80
+++ linux-nat.c 14 Apr 2008 14:56:18 -0000
@@ -50,6 +50,21 @@
#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 significant; we just need for a signal to
+ be delivered, so that we can catch it. SIGSTOP has two problems
+ for this use. It is not a real-time signal, so it can only be
+ queued once; we do not keep track of other sources of SIGSTOP.
+ And it stops the entire thread group, not just the tkill'd thread.
+ In practice, these do not cause any problem.
+
+ 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. */
+
#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif
@@ -186,6 +201,7 @@ static void linux_nat_async (void (*call
(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 +1026,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 +1145,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 +1164,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 +1234,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 +1249,69 @@ linux_nat_attach (char *args, int from_t
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 +1322,30 @@ detach_callback (struct lwp_info *lp, vo
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 +1355,6 @@ detach_callback (struct lwp_info *lp, vo
target_pid_to_str (lp->ptid),
strsignal (WSTOPSIG (lp->status)));

- drain_queued_events (GET_LWP (lp->ptid));
delete_lwp (lp->ptid);
}

@@ -1263,6 +1365,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 +1376,21 @@ linux_nat_detach (char *args, int from_t
/* 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 +2968,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 +4327,3 @@ lin_thread_get_thread_signals (sigset_t
/* ... except during a sigsuspend. */
sigdelset (&suspend_mask, cancel);
}
-
Index: testsuite/gdb.threads/attach-into-signal.c
===================================================================
RCS file: testsuite/gdb.threads/attach-into-signal.c
diff -N testsuite/gdb.threads/attach-into-signal.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.threads/attach-into-signal.c 14 Apr 2008 14:56:18 -0000
@@ -0,0 +1,69 @@
+/* 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);
+
+ /* This should be NOTREACHED but sometimes it is reached - Bug 427860.
+ We never get here without ptrace(2). It may also be a kernel bug. */
+ for (;;)
+ pause ();
+
+ 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;
+}
Index: testsuite/gdb.threads/attach-into-signal.exp
===================================================================
RCS file: testsuite/gdb.threads/attach-into-signal.exp
diff -N testsuite/gdb.threads/attach-into-signal.exp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.threads/attach-into-signal.exp 14 Apr 2008 14:56:18 -0000
@@ -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
Index: testsuite/gdb.threads/attach-stopped.c
===================================================================
RCS file: testsuite/gdb.threads/attach-stopped.c
diff -N testsuite/gdb.threads/attach-stopped.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.threads/attach-stopped.c 14 Apr 2008 14:56:18 -0000
@@ -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;
+}
Index: testsuite/gdb.threads/attach-stopped.exp
===================================================================
RCS file: testsuite/gdb.threads/attach-stopped.exp
diff -N testsuite/gdb.threads/attach-stopped.exp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.threads/attach-stopped.exp 14 Apr 2008 14:56:18 -0000
@@ -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
Index: testsuite/gdb.threads/attachstop-mt.c
===================================================================
RCS file: testsuite/gdb.threads/attachstop-mt.c
diff -N testsuite/gdb.threads/attachstop-mt.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.threads/attachstop-mt.c 14 Apr 2008 14:56:18 -0000
@@ -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;
+}
Index: testsuite/gdb.threads/attachstop-mt.exp
===================================================================
RCS file: testsuite/gdb.threads/attachstop-mt.exp
diff -N testsuite/gdb.threads/attachstop-mt.exp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.threads/attachstop-mt.exp 14 Apr 2008 14:56:18 -0000
@@ -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