This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[patch] Fix Linux attach to signalled/stopped processes
- From: Jan Kratochvil <jan dot kratochvil at redhat dot com>
- To: gdb-patches at sourceware dot org
- Date: Wed, 6 Jun 2007 16:34:32 +0200
- Subject: [patch] Fix Linux attach to signalled/stopped processes
Hi,
GDB hangs while attaching to an already SIGSTOPped process.
GDB also crashes if some signal delivery was pending right at the time GDB was
attaching:
linux-nat.c:981: internal-error: linux_nat_attach: Assertion `pid == GET_PID (inferior_ptid) && WIFSTOPPED (status) && WSTOPSIG (status) == SIGSTOP' failed.
The attached patch was tested on kernel.org linux-2.6.20.4 on x86_64.
The patch changes the functionality of TO_ATTACH. The former functionality was
too UNIX centric. I was reading all the TO_ATTACH OS-flavor implementations
and I believe the non-"inf-ptrace.c" ones do not need update. The code was
tested only on Linux kernel, though.
GDB will leave the detached processes still stopped in the default case.
If you ever run the `continue' command the process will be left running.
This behavior was coded this way before by Jeff Johnston.
FYI waitpid(2) may return a non-SIGSTOP signal before getting SIGSTOP if the
non-SIGSTOP signal was already in the queue. It is a very rare race.
Regards,
Jan
Already posted a broken patch before:
http://sources.redhat.com/ml/gdb-patches/2006-09/msg00092.html
2007-06-06 Jan Kratochvil <jan.kratochvil@redhat.com>
* gdb/target.h (target_attach): Comment it also calls WAITPID now.
* gdb/inf-ptrace.c (stopped_pid): New variable.
(inf_ptrace_follow_fork): Optionally stop detached parents of the
followed children.
(my_waitpid): New function copied from `linux-nat.c'.
(inf_ptrace_attach): New variables GOT_PID, STATUS, SIG, STATUS_FILE,
BUF, SIGSTOP. Initialize STOPPED_PID. Handle processes stopped before.
Call also WAITPID before returning. Handle signals returned by WAITPID.
Handle exits during the attachment.
(inf_ptrace_detach): Removed handling of the parameter ARGS as not useful
and already disabled since the `detach checkpoint' command existence.
Optionally stop the detached process.
(inf_ptrace_resume): Clear STOPPED_PID if appropriate.
* gdb/linux-nat.c (lin_lwp_attach_lwp): Handle processes stopped before.
Handle signals returned by WAITPID. Handle exits during the attachment.
(linux_nat_attach): Removed the WAITPID functionality there.
2007-06-06 Jan Kratochvil <jan.kratochvil@redhat.com>
gdb.threads/attach-into-signal.c, gdb.threads/attach-into-signal.exp,
gdb.threads/attach-stopped.c, gdb.threads/attach-stopped.exp: New files.
--- ./gdb/target.h 11 May 2007 19:55:20 -0000 1.101
+++ ./gdb/target.h 6 Jun 2007 13:46:22 -0000
@@ -537,9 +537,9 @@ void target_close (struct target_ops *ta
to the `attach' command by the user. This routine can be called
when the target is not on the target-stack, if the target_can_run
routine returns 1; in that case, it must push itself onto the stack.
- Upon exit, the target should be ready for normal operations, and
- should be ready to deliver the status of the process immediately
- (without waiting) to an upcoming target_wait call. */
+ Upon exit, the target should be ready for normal operations.
+ The status of the inferior is already processed and possibly pending
+ signals redelivered. */
#define target_attach(args, from_tty) \
(*current_target.to_attach) (args, from_tty)
--- ./gdb/inf-ptrace.c 31 May 2007 17:32:21 -0000 1.38
+++ ./gdb/inf-ptrace.c 6 Jun 2007 13:46:22 -0000
@@ -39,6 +39,9 @@
/* HACK: Save the ptrace ops returned by inf_ptrace_target. */
static struct target_ops *ptrace_ops_hack;
+
+/* Stored PID of the process being stopped during attach. */
+static pid_t stopped_pid;
#ifdef PT_GET_PROCESS_STATE
@@ -70,14 +73,20 @@ inf_ptrace_follow_fork (struct target_op
if (follow_child)
{
+ unsigned long sig = 0;
+
inferior_ptid = pid_to_ptid (fpid);
detach_breakpoints (pid);
/* Reset breakpoints in the child as appropriate. */
follow_inferior_reset_breakpoints ();
- if (ptrace (PT_DETACH, pid, (PTRACE_TYPE_ARG3)1, 0) == -1)
- perror_with_name (("ptrace"));
+ /* Stop the process again if it was stopped during the attachment. */
+ if (pid == stopped_pid)
+ sig = target_signal_to_host (TARGET_SIGNAL_STOP));
+
+ if (ptrace (PT_DETACH, pid, (PTRACE_TYPE_ARG3)1, (void *) sig) == -1)
+ perror_with_name (("ptrace PT_DETACH"));
}
else
{
@@ -174,6 +183,21 @@ inf_ptrace_mourn_inferior (void)
generic_mourn_inferior ();
}
+/* Wrapper function for waitpid which handles EINTR. */
+
+static int
+my_waitpid (int pid, int *status, int flags)
+{
+ int ret;
+ do
+ {
+ ret = waitpid (pid, status, flags);
+ }
+ while (ret == -1 && errno == EINTR);
+
+ return ret;
+}
+
/* Attach to the process specified by ARGS. If FROM_TTY is non-zero,
be chatty about it. */
@@ -181,8 +205,13 @@ static void
inf_ptrace_attach (char *args, int from_tty)
{
char *exec_file;
- pid_t pid;
+ pid_t pid, got_pid;
char *dummy;
+ int status;
+ unsigned long sig;
+ FILE *status_file;
+ char buf[100];
+ int sigstop = target_signal_to_host (TARGET_SIGNAL_STOP);
if (!args)
error_no_arg (_("process-id to attach"));
@@ -211,11 +241,64 @@ inf_ptrace_attach (char *args, int from_
}
#ifdef PT_ATTACH
+ stopped_pid = 0;
+ /* There is a small moment after PTRACE_ATTACH where PTRACE_CONT will
+ succeed only for originally stopped processes. Unfortunately in a moment
+ PTRACE_ATTACH will deliver its SIGSTOP and PTRACE_CONT shows no difference
+ since that moment.
+ "/proc/%d/status" is also a race but it is safe for unstopped cases. */
+ 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 != 0 && strstr (buf, "T (stopped)") != NULL)
+ stopped_pid = pid;
+ fclose (status_file);
+ }
+
errno = 0;
ptrace (PT_ATTACH, pid, (PTRACE_TYPE_ARG3)0, 0);
if (errno != 0)
perror_with_name (("ptrace"));
attach_flag = 1;
+
+ /* Deliver one SIGSTOP just for sure.
+ If the process was already stopped AND some other process (like shell)
+ has already waited for it we would get stuck in waitpid (). */
+ sig = sigstop;
+ do
+ {
+ if (sig != sigstop)
+ printf_unfiltered (_("Redelivering pending %s.\n"),
+ target_signal_to_string (target_signal_from_host (sig)));
+ errno = 0;
+ ptrace (PT_CONTINUE, pid, (PTRACE_TYPE_ARG3)1, (void *) sig);
+ /* For unstopped processes the preventive signal may ESRCH. */
+ if (errno != 0 && sig != sigstop)
+ perror_with_name ("ptrace PT_CONTINUE");
+
+ got_pid = my_waitpid (pid, &status, 0);
+ gdb_assert (got_pid == pid);
+
+ /* Check if the thread has exited. */
+ if (WIFEXITED (status) || WIFSIGNALED (status))
+ error (_("Program %s exited.\n"),
+ target_pid_to_str (pid_to_ptid (pid)));
+ gdb_assert (WIFSTOPPED (status));
+ sig = WSTOPSIG (status);
+ gdb_assert (sig != 0);
+ }
+ while (sig != sigstop);
#else
error (_("This system does not support attaching to a process"));
#endif
@@ -241,14 +324,16 @@ inf_ptrace_post_attach (int pid)
#endif
-/* Detach from the inferior, optionally passing it the signal
- specified by ARGS. If FROM_TTY is non-zero, be chatty about it. */
+/* Detach from the inferior. If FROM_TTY is non-zero, be chatty about it. */
static void
inf_ptrace_detach (char *args, int from_tty)
{
pid_t pid = ptid_get_pid (inferior_ptid);
- int sig = 0;
+ unsigned long sig = 0;
+
+ if (args)
+ error (_("Too many arguments"));
if (from_tty)
{
@@ -259,18 +344,19 @@ inf_ptrace_detach (char *args, int from_
target_pid_to_str (pid_to_ptid (pid)));
gdb_flush (gdb_stdout);
}
- if (args)
- sig = atoi (args);
#ifdef PT_DETACH
/* We'd better not have left any breakpoints in the program or it'll
die when it hits one. Also note that this may only work if we
previously attached to the inferior. It *might* work if we
started the process ourselves. */
+ /* Stop the process again if it was stopped during the attachment. */
+ if (pid == stopped_pid)
+ sig = target_signal_to_host (TARGET_SIGNAL_STOP);
errno = 0;
- ptrace (PT_DETACH, pid, (PTRACE_TYPE_ARG3)1, sig);
+ ptrace (PT_DETACH, pid, (PTRACE_TYPE_ARG3)1, (void *) sig);
if (errno != 0)
- perror_with_name (("ptrace"));
+ perror_with_name (("ptrace PT_DETACH"));
attach_flag = 0;
#else
error (_("This system does not support detaching from a process"));
@@ -325,6 +411,12 @@ inf_ptrace_resume (ptid_t ptid, int step
single-threaded processes, so simply resume the inferior. */
pid = ptid_get_pid (inferior_ptid);
+ /* At this point, we are going to resume the inferior and if we
+ have attached to a stopped process, we no longer should leave
+ it as stopped if the user detaches. */
+ if (!step && pid == stopped_pid)
+ stopped_pid = 0;
+
if (step)
{
/* If this system does not support PT_STEP, a higher level
--- ./gdb/linux-nat.c 10 May 2007 21:36:00 -0000 1.61
+++ ./gdb/linux-nat.c 6 Jun 2007 13:46:22 -0000
@@ -886,6 +886,7 @@ lin_lwp_attach_lwp (ptid_t ptid, int ver
{
pid_t pid;
int status;
+ unsigned long sig;
if (ptrace (PTRACE_ATTACH, GET_LWP (ptid), 0, 0) < 0)
{
@@ -907,28 +908,50 @@ lin_lwp_attach_lwp (ptid_t ptid, int ver
"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)
+ sig = SIGSTOP;
+ do
{
- /* Try again with __WCLONE to check cloned processes. */
- pid = my_waitpid (GET_LWP (ptid), &status, __WCLONE);
- lp->cloned = 1;
- }
+ if (sig != SIGSTOP)
+ printf_unfiltered (_("Redelivering pending %s.\n"),
+ target_signal_to_string (target_signal_from_host (sig)));
+ /* For unstopped processes the preventive signal may ESRCH. */
+ if (ptrace (PTRACE_CONT, GET_LWP (ptid), (PTRACE_TYPE_ARG3)1,
+ (void *) sig) != 0 && sig != SIGSTOP)
+ perror_with_name ("ptrace");
+
+ pid = my_waitpid (GET_LWP (ptid), &status, 0);
+ if (pid == -1 && errno == ECHILD)
+ {
+ /* Try again with __WCLONE to check cloned processes. */
+ pid = my_waitpid (GET_LWP (ptid), &status, __WCLONE);
+ lp->cloned = 1;
+ }
+ gdb_assert (pid == GET_LWP (ptid));
- gdb_assert (pid == GET_LWP (ptid)
- && WIFSTOPPED (status) && WSTOPSIG (status));
+ if (debug_linux_nat)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "LLAL: waitpid %s received %s\n",
+ target_pid_to_str (ptid),
+ status_to_str (status));
+ }
+
+ /* Check if the thread has exited. */
+ if (WIFEXITED (status) || WIFSIGNALED (status))
+ {
+ warning (_("Thread %s exited: %s"), target_pid_to_str (ptid),
+ status_to_str (status));
+ return -1;
+ }
+ gdb_assert (WIFSTOPPED (status));
+ sig = WSTOPSIG (status);
+ gdb_assert (sig != 0);
+ }
+ while (sig != SIGSTOP);
target_post_attach (pid);
lp->stopped = 1;
-
- if (debug_linux_nat)
- {
- fprintf_unfiltered (gdb_stdlog,
- "LLAL: waitpid %s received %s\n",
- target_pid_to_str (ptid),
- status_to_str (status));
- }
}
else
{
@@ -953,8 +976,6 @@ static void
linux_nat_attach (char *args, int from_tty)
{
struct lwp_info *lp;
- pid_t pid;
- int status;
/* FIXME: We should probably accept a list of process id's, and
attach all of them. */
@@ -964,22 +985,6 @@ linux_nat_attach (char *args, int from_t
inferior_ptid = BUILD_LWP (GET_PID (inferior_ptid), GET_PID (inferior_ptid));
lp = add_lwp (inferior_ptid);
- /* 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, 0);
- 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. */
- pid = my_waitpid (GET_PID (inferior_ptid), &status, __WCLONE);
- lp->cloned = 1;
- }
-
- gdb_assert (pid == GET_PID (inferior_ptid)
- && WIFSTOPPED (status) && WSTOPSIG (status) == SIGSTOP);
-
lp->stopped = 1;
/* Fake the SIGSTOP that core GDB expects. */
@@ -987,8 +992,8 @@ linux_nat_attach (char *args, int from_t
lp->resumed = 1;
if (debug_linux_nat)
{
- fprintf_unfiltered (gdb_stdlog,
- "LLA: waitpid %ld, faking SIGSTOP\n", (long) pid);
+ fprintf_unfiltered (gdb_stdlog, "LLA: waitpid %d, faking SIGSTOP\n",
+ GET_PID (inferior_ptid));
}
}
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/attach-into-signal.c 6 Jun 2007 13:46:22 -0000
@@ -0,0 +1,65 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2007 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+#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);
+
+ 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;
+}
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/attach-into-signal.exp 6 Jun 2007 13:46:22 -0000
@@ -0,0 +1,149 @@
+# Copyright 2007
+
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# This test was created by modifying attach-stopped.exp.
+# This file was created by Jan Kratochvil <jan.kratochvil@redhat.com>.
+
+if $tracelevel then {
+ strace $tracelevel
+}
+
+set prms_id 0
+set bug_id 0
+
+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 0
+ set passes 1
+ while { $passes < 3 && $attempt < $attempts } {
+
+ # Start with clean gdb
+ gdb_exit
+ gdb_start
+ gdb_reinitialize_dir $srcdir/$subdir
+ gdb_load ${binfile}
+
+ # No PASS message as we may be looping in multiple attempts.
+ gdb_test "set debug lin-lwp 1" "" ""
+
+ set test "$threadtype: set file (pass $passes), before attach1 to stopped process"
+ gdb_test_multiple "file $binfile" $test {
+ -re "Load new symbol table from.*y or n. $" {
+ # No PASS message as we may be looping in multiple attempts.
+ gdb_test "y" "Reading symbols from $escapedbinfile\.\.\.*done." ""
+ }
+ -re "Reading symbols from $escapedbinfile\.\.\.*done.*$gdb_prompt $" {
+ # No PASS message as we may be looping in multiple attempts.
+ }
+ }
+
+ # Main test:
+ set test "$threadtype: attach (pass $passes), pending signal catch"
+ gdb_test_multiple "attach $testpid" $test {
+ -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*Redelivering pending 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.*Redelivering pending 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]
+ }
+ }
+ }
+ if {$passes < 3} {
+ 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}"
+}
+
+# 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
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/attach-stopped.c 6 Jun 2007 13:46:22 -0000
@@ -0,0 +1,51 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2005-2007 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
+
+/* 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;
+}
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/attach-stopped.exp 6 Jun 2007 13:46:22 -0000
@@ -0,0 +1,208 @@
+# Copyright 2005-2007
+
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# 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>.
+
+if $tracelevel then {
+ strace $tracelevel
+}
+
+set prms_id 0
+set bug_id 0
+
+# This test only works on Linux
+if { ![istarget "*-*-linux-gnu*"] } {
+ return 0
+}
+
+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 &]
+ exec 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 process by first giving its
+ # executable name via the file command, and using attach with the
+ # process ID.
+
+ set test "$threadtype: set file, before attach1 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 "$threadtype: attach1 to stopped, after setting file"
+ gdb_test_multiple "attach $testpid" "$test" {
+ -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*$gdb_prompt $" {
+ pass "$test"
+ }
+ }
+
+ if {[string equal $threadtype threaded]} {
+ gdb_test "thread apply all bt" ".*sleep.*clone.*" "$threadtype: attach1 to stopped bt"
+ } else {
+ gdb_test "bt" ".*sleep.*main.*" "$threadtype: attach1 to stopped bt"
+ }
+
+ # Exit and detach the process.
+
+ gdb_exit
+
+ set fileid [open /proc/${testpid}/status r];
+ gets $fileid line1;
+ gets $fileid line2;
+ close $fileid;
+
+ set test "$threadtype: attach1, exit leaves process stopped"
+ if {[string match "*(stopped)*" $line2]} {
+ pass $test
+ } else {
+ fail $test
+ }
+
+ # At this point, the process should still be stopped
+
+ 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 "$threadtype: attach2 to stopped, after setting file"
+ gdb_test_multiple "attach $testpid" "$test" {
+ -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*$gdb_prompt $" {
+ pass "$test"
+ }
+ }
+
+ if {[string equal $threadtype threaded]} {
+ gdb_test "thread apply all bt" ".*sleep.*clone.*" "$threadtype: attach2 to stopped bt"
+ } else {
+ gdb_test "bt" ".*sleep.*main.*" "$threadtype: attach2 to stopped bt"
+ }
+ gdb_breakpoint [gdb_get_line_number "$threadtype: 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:
+ exec sleep 2
+
+ # At this point, the process should be sleeping
+
+ set fileid2 [open /proc/${testpid}/status r];
+ 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