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

Daniel Jacobowitz
2008-04-11T21:03:01Z

On Thu, Apr 10, 2008 at 04:12:05PM -0700, Roland McGrath wrote:
> > As the shell already did WAITPID on the process and ate the SIGSTOP
> > notification so there is no other notification left there for GDB.
> > A process already stopped does not generate another SIGSTOP during
> > PTRACE_ATTACH as signals never count.
>
> To be pedantic about it: a SIGSTOP is generated, but causes no wakeup or
> delivery for ptrace to see (because it's already stopped). You can see
> the SIGSTOP pending in /proc/pid/status. Generating a SIGCONT clears
> any pending stop signals, so normally it's as if it never existed. But,
> I think if you were to do PTRACE_CONT,pid,0,0 you would make it wake up,
> dequeue the new SIGSTOP and then report to you for wait/SIGCHLD. And
> since PTRACE_CONT would give ESRCH if it weren't stopped yet, you might
> be able to do PTRACE_ATTACH;PTRACE_CONT;wait reliably. But don't hold
> me to it.

There's a race: if the SIGSTOP has already stopped the attachee,
whether or not it's been waited for yet, PTRACE_CONT will clear it.
So PTRACE_CONT,pid,0,0 is quite likely to leave it running.

I also experimented with PTRACE_ATTACH; PTRACE_CONT,pid,0,SIGSTOP;
waitpid. We go from ptrace_stop in get_signal_to_deliver, off to the
normal signal handling via finish_stop. This is not so good, because
normal ptrace behavior doesn't apply there. For instance, we can't
PTRACE_KILL; that only sets the exit_code and wakes the process but at
this point nothing reads the exit code so the process starts running
again. For the same reason, we can't send a signal during wakeup
(PTRACE_CONT is basically the same as PTRACE_KILL in this regard).

I feel like there's a kernel bug here somewhere, but it's buried in
the depths of poorly defined ptrace semantics, so maybe not. I think
it prudent not to deliberately put things into the job-control stop
state because of this limitation.

We can determine where we are either by parsing /proc, or by trying
PTRACE_GETSIGINFO; at the moment, at least, it returns EINVAL if we
are in some other stopped state that did not come through ptrace_stop
(the comments above ptrace_stop describe this too; I think I wrote
that...). As our first ptrace call will transform TASK_STOPPED into
TASK_TRACED /proc is somewhat tricky but fortunately I only need
it at the very beginning of the attach process.

What we need is to get from the job control stopped state, back into
the signal delivery stopped state. Jan's artificially delivered
SIGCONT is the only way I can think of to do this.

Which gives me these steps:

PTRACE_ATTACH
A SIGSTOP is generated. The process may still run until it is
delivered. If it was running, we will be able to wait for it
and it will end up in ptrace_stop. If it was stopped but not
waited for by its parent, we will be able to wait for it and it
will end up in job control stop. If it was stopped and waited
for, it will already be in job control stop and we will not be
able to wait for it.

Check /proc
The process may be running. If so, waiting for it will be fine.
It may be in a tracing stop. If so, it's our tracing stop, and
waiting for it will be fine. It may be blocked - not much we can
do about that, we have to wait. It may be stopped. On old
kernels I believe this will happen even if we're in a tracing
stop, so whatever we do should work OK from tracing stops too.

If the process was T (stopped):
In this case, waiting might or might not work (depending on
whether the real parent has waited yet), but if it's going
to work it will immediately. So we can use WNOHANG. After that,
whether the wait succeeded or not, we can get the process into a
sane state by tkill(SIGCONT) and waiting for it. If a signal
other than the SIGCONT arrives, we have to be prepared for the
SIGCONT to show up again later. The least intrusive way to handle
that in GDB is, oddly, tkill (SIGSTOP) to cancel the pending SIGCONT.

Otherwise:
Just wait for the process and expect the SIGSTOP.

We can do it a little more simply than in Jan's patch, by making the
GDB core display non-SIGSTOP signals that come in during attach. As
far as I know this should be safe for other targets.

Then I worked through the RH package's tests and fixed the rest of
them, again a little differently. Sorry about that. I deleted the
tests that detach left processes stopped, since I don't think it's
in line with GDB's past behavior. Otherwise I left the tests pretty
much alone (though restricted to Linux native); we'll see how portable
they end up being.

And all that leaves me with the attached patch. Anyone else want to
test it before I check it in, or see any problems with it? Tested
x86_64-linux.

--
Daniel Jacobowitz
CodeSourcery

2008-04-11 Jan Kratochvil <jan.kratochvil@redhat.com>
Daniel Jacobowitz <dan@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-11 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: NEWS
===================================================================
RCS file: /cvs/src/src/gdb/NEWS,v
retrieving revision 1.263
diff -u -p -r1.263 NEWS
--- NEWS 14 Mar 2008 22:30:07 -0000 1.263
+++ NEWS 11 Apr 2008 20:40:19 -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 11 Apr 2008 20:40:20 -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 11 Apr 2008 20:40:20 -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 11 Apr 2008 20:40:20 -0000
@@ -186,6 +186,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 +1011,125 @@ 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 sent_sigcont = 0;
+ int status;
+
+ if (pid_is_stopped (pid))
+ {
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "LNPAW: Attaching to a stopped process\n");
+
+ /* Consume any pending SIGSTOP, if there is one. */
+ new_pid = my_waitpid (pid, &status, WNOHANG);
+ if (new_pid == -1 && errno == ECHILD)
+ new_pid = my_waitpid (pid, &status, __WCLONE | WNOHANG);
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "LNPAW: Wait for stopped process returned %ld, "
+ "status %s\n",
+ (long) new_pid, status_to_str (status));
+
+ /* The process is definitely stopped, whether the waits above succeeded
+ or not. 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. */
+ kill_lwp (pid, SIGCONT);
+ sent_sigcont = 1;
+ }
+
+ /* 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 (sent_sigcont)
+ {
+ gdb_assert (WSTOPSIG (status) != SIGSTOP);
+
+ if (WSTOPSIG (status) != SIGCONT)
+ {
+ /* There's a pending SIGCONT, but a higher-priority
+ (i.e. lower-numbered) signal was delivered. Clear the
+ pending SIGCONT; we already know how to handle a pending
+ SIGSTOP, and this will clear the pending SIGCONT. */
+ kill_lwp (GET_PID (inferior_ptid), SIGSTOP);
+ *signalled = 1;
+
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "LNPAW: Received a signal after SIGCONT; "
+ "sending SIGSTOP\n");
+ }
+ else
+ /* Pretend the whole SIGCONT dance never happened. */
+ status = W_STOPCODE (SIGSTOP);
+ }
+ else 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 +1152,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 +1171,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 +1241,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,49 +1256,34 @@ 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);
}
@@ -1209,33 +1299,23 @@ 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));
}

+ /* Pass on the last signal, if appropriate. */
+ if (lp->status == 0 && GET_LWP (lp->ptid) == GET_LWP (inferior_ptid)
+ && stop_signal != TARGET_SIGNAL_0 && signal_pass_state (stop_signal))
+ lp->status = W_STOPCODE (target_signal_to_host (stop_signal));
+
/* 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))
@@ -1263,14 +1343,29 @@ 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);

- iterate_over_lwps (detach_callback, NULL);
+ iterate_over_lwps (detach_callback, &status);

/* Only the initial process should be left right now. */
gdb_assert (num_lwps == 1);

+ /* Pass on any pending signal for the last LWP. */
+ status = lwp_list->status;
+ if (WIFSTOPPED (status) && (args == NULL || *args == '\0'))
+ {
+ 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 +2943,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;
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 11 Apr 2008 20:40:21 -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 11 Apr 2008 20:40:21 -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 11 Apr 2008 20:40:21 -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 11 Apr 2008 20:40:21 -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 11 Apr 2008 20:40:21 -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 11 Apr 2008 20:40:21 -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
Index: Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/Makefile.in,v
retrieving revision 1.996
diff -u -p -r1.996 Makefile.in
--- Makefile.in 26 Mar 2008 14:53:28 -0000 1.996
+++ Makefile.in 11 Apr 2008 20:41:44 -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)