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

Jan Kratochvil
2007-06-30T10:19:17Z

[-- Attachment #1: Type: text/plain, Size: 532 bytes --]

On Wed, 27 Jun 2007 08:52:03 +0200, Jan Kratochvil wrote:
...
> OK, going to move it to linux-nat.c.

Done; various parts changed.

There is one ugly workaround part (described in the comments in the patch).
Testcase for this workaround is also attached, it appears as (another) Linux
kernel ptrace(2) bug to me. Is it worth workarounding it? IMO not worth
fixing as it is a corner case and UTRACE is going to provide alternative API.

Tested on x86_64, on upstream 2.6.22-rc4-git7 and Red Hat kernel-2.6.9-55.EL.

Regards,
Jan

[-- Attachment #2: gdb-attach-signalled-ver5.patch --]
[-- Type: text/plain, Size: 27234 bytes --]

2007-06-30 Jan Kratochvil <jan.kratochvil@redhat.com>

* gdb/linux-nat.h (struct lwp_info): New field WAS_STOPPED.
* gdb/linux-nat.c (STRINGIFY, STRINGIFY_ARG): New macros.
(kill_lwp): New declaration.
(linux_ptrace_post_attach, pid_is_stopped): New function.
(linux_child_follow_fork): New comment about WAS_STOPPED.
(lin_lwp_attach_lwp): Variable PID removed. Part replaced by a call to
LINUX_PTRACE_POST_ATTACH.
(linux_nat_attach): Likewise.
(linux_nat_detach): Optionally stop the detached process.
(linux_nat_resume): Clear WAS_STOPPED if appropriate.

2007-06-30 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/linux-nat.c 16 Jun 2007 17:16:25 -0000 1.64
+++ ./gdb/linux-nat.c 29 Jun 2007 22:06:05 -0000
@@ -86,6 +86,12 @@
#define __WALL 0x40000000 /* Wait for any child. */
#endif

+#define STRINGIFY_ARG(x) #x
+#define STRINGIFY(x) STRINGIFY_ARG (x)
+
+static int linux_ptrace_post_attach (struct lwp_info *lp);
+static int kill_lwp (int lwpid, int signo);
+
/* The single-threaded native GNU/Linux target_ops. We save a pointer for
the use of the multi-threaded target. */
static struct target_ops *linux_ops;
@@ -502,6 +508,11 @@ linux_child_follow_fork (struct target_o
}
else
{
+ /* We should check LP->WAS_STOPPED and detach it stopped accordingly.
+ In this point of code it cannot be 1 as we would not get FORK
+ executed without CONTINUE first which resets LP->WAS_STOPPED.
+ We would have to first TARGET_STOP and WAITPID it as with running
+ inferior PTRACE_DETACH, SIGSTOP will ignore the signal. */
target_detach (NULL, 0);
}

@@ -884,7 +895,6 @@ lin_lwp_attach_lwp (ptid_t ptid, int ver
to happen. */
if (GET_LWP (ptid) != GET_PID (ptid) && lp == NULL)
{
- pid_t pid;
int status;

if (ptrace (PTRACE_ATTACH, GET_LWP (ptid), 0, 0) < 0)
@@ -902,33 +912,17 @@ lin_lwp_attach_lwp (ptid_t ptid, int ver
if (lp == NULL)
lp = add_lwp (ptid);

- if (debug_linux_nat)
- fprintf_unfiltered (gdb_stdlog,
- "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)
- {
- /* Try again with __WCLONE to check cloned processes. */
- pid = my_waitpid (GET_LWP (ptid), &status, __WCLONE);
- lp->cloned = 1;
+ status = linux_ptrace_post_attach (lp);
+ if (status != 0)
+ {
+ warning (_("Thread %s exited: %s"), target_pid_to_str (ptid),
+ status_to_str (status));
+ return -1;
}

- gdb_assert (pid == GET_LWP (ptid)
- && WIFSTOPPED (status) && WSTOPSIG (status));
-
- target_post_attach (pid);
+ target_post_attach (GET_LWP (ptid));

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
{
@@ -949,11 +943,172 @@ lin_lwp_attach_lwp (ptid_t ptid, int ver
return 0;
}

+/* 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;
+}
+
+/* Handle the processing after PTRACE_ATTACH, the first WAITPID -> SIGSTOP.
+ Returns STATUS if the thread has exited, 0 otherwise.
+ Sets LP->WAS_STOPPED if the process was originally stopped.
+ Sets LP->CLONED if the given LWP is not the thread leader.
+
+ Scenario for a standard unstopped inferior:
+ * `S (sleeping)' or `R (running)' or similiar states.
+ * PTRACE_ATTACH is called.
+ * `S (sleeping)' (or similiar) for some while.
+ * `T (tracing stop)'.
+ * WAITPID succeeds here returning SIGSTOP (signalled by PTRACE_ATTACH).
+
+ Scenario for a formerly stopped inferior:
+ * `T (stopped)'.
+ * PTRACE_ATTACH is called.
+ * `T (stopped)' would stay indefinitely
+ Note since this moment the `TracerPid' field gets filled
+ (by PTRACE_ATTACH), it is no longer just the common `T (stopped)' state.
+ * If no one did WAITPID since sending SIGSTOP our WAITPID would return
+ SIGSTOP. The state still would not turn to `T (tracing stop)'.
+ * Usually its original parent (before PTRACE_ATTACH was applied) already
+ did WAITPID. The original parent already received our SIGSTOP
+ sinalled by our PTRACE_ATTACH.
+ In this case our own WAITPID would hang. Therefore...
+ * ... we do artificial: tkill (SIGCONT);
+ `PTRACE_CONT, SIGSTOP' does not work in 100% cases as sometimes SIGSTOP
+ gets remembered by kernel during the first PTRACE_CONT later and we get
+ spurious SIGSTOP event. Expecting the signal may get delivered to
+ a different task of the thread group.
+ `kill_lwp (SIGSTOP)' has no effect in this moment (it is already stopped).
+ * WAITPID returns the artifical SIGCONT.
+ (The possibly pending SIGSTOP gets vanished by specifically SIGCONT.)
+ * State turns `T (tracing stop)'.
+ In this moment everything is almost fine but we need a workaround as final
+ `PTRACE_DETACH, SIGSTOP' would leave the process unstopped otherwise:
+ * tkill (SIGSTOP);
+ * `PTRACE_CONT, 0'
+ * WAITPID returns the artifical SIGSTOP.
+
+ With the pending (unwaited for) SIGSTOP the artifical signal effects are:
+ kill (SIGSTOP)
+ PTRACE_ATTACH
+ /-tkill (SIGCONT), WAITPID: SIGCONT, WAITPID: hang !
+ //-tkill (SIGCONT), WAITPID: SIGCONT, PTRACE_CONT (SIG_0), WAITPID: wait (OK)
+ \\-tkill (SIGALRM), WAITPID: SIGSTOP, WAITPID: hang !
+ \-tkill (SIGALRM), WAITPID: SIGSTOP, PTRACE_CONT (SIG_0), WAITPID: SIGALRM !
+ Therefore we signal artifical SIGCONT and stop waiting after its reception.
+
+ For the detection whether the process was formerly stopped we need to
+ read `/proc/PID/status'. `PTRACE_CONT, SIGSTOP' returns ESRCH
+ for `S (sleeping)' and succeeds for `T (stopped)' but it unfortunately
+ succeeds even for `T (tracing stop)'. Depending on PTRACE_CONT, SIGSTOP
+ success value for formerly stopped processes would mean a race condition
+ as we would get false stopped processes detection if we get too slow.
+
+ `waitid (..., WSTOPPED)' hangs the same way as WAITPID.
+
+ Signals get queued for WAITPID. PTRACE_ATTACH (or TKILL) enqueues SIGSTOP
+ there but WAITPID may return an already pending signal.
+ Redeliver it by PTRACE_CONT, SIGxxx as otherwise it would get lost.
+ Similiar processing is being done in this file by WAIT_LWP. */
+
+static int
+linux_ptrace_post_attach (struct lwp_info *lp)
+{
+ ptid_t ptid = lp->ptid;
+ unsigned long sig;
+
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "LLAL: PTRACE_ATTACH %s, 0, 0 (OK)\n",
+ target_pid_to_str (ptid));
+
+ lp->was_stopped = pid_is_stopped (GET_LWP (ptid));
+ if (lp->was_stopped)
+ {
+ if (kill_lwp (GET_LWP (ptid), SIGCONT) != 0)
+ perror_with_name (("kill_lwp (SIGCONT)"));
+ }
+
+ for (;;)
+ {
+ pid_t pid;
+ int status;
+
+ 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));
+
+ 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))
+ return status;
+ gdb_assert (WIFSTOPPED (status));
+ sig = WSTOPSIG (status);
+ gdb_assert (sig != 0);
+ if (sig == SIGSTOP)
+ break;
+
+ /* As the second signal for stopped processes we send SIGSTOP. */
+ if (lp->was_stopped && sig == SIGCONT)
+ sig = SIGSTOP;
+
+ printf_unfiltered (_("Redelivering pending %s.\n"),
+ target_signal_to_string (target_signal_from_host (sig)));
+ if (sig == SIGSTOP)
+ {
+ if (kill_lwp (GET_LWP (ptid), sig) != 0)
+ perror_with_name (("kill_lwp"));
+ /* We now must resume the inferior to get SIGSTOP delivered. */
+ sig = 0;
+ }
+ if (ptrace (PTRACE_CONT, GET_LWP (ptid), NULL, (void *) sig) != 0)
+ perror_with_name (("ptrace"));
+ }
+
+ return 0;
+}
+
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
@@ -964,21 +1122,12 @@ 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);
+ status = linux_ptrace_post_attach (lp);
+ if (status != 0)
+ error (_("Program %s exited: %s\n"), target_pid_to_str (inferior_ptid),
+ status_to_str (status));
+ if (lp->cloned)
+ warning (_("%s is a cloned process"), target_pid_to_str (inferior_ptid));

lp->stopped = 1;

@@ -987,8 +1136,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));
}
}

@@ -1061,6 +1210,9 @@ linux_nat_detach (char *args, int from_t

trap_ptid = null_ptid;

+ if (lwp_list->was_stopped)
+ args = STRINGIFY (SIGSTOP);
+
/* Destroy LWP info; it's no longer valid. */
init_lwp_list ();

@@ -1196,6 +1348,12 @@ linux_nat_resume (ptid_t ptid, int step,
lp->stopped = 0;
}

+ /* 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 && lp != NULL)
+ lp->was_stopped = 0;
+
if (resume_all)
iterate_over_lwps (resume_callback, NULL);

--- ./gdb/linux-nat.h 10 May 2007 21:36:00 -0000 1.18
+++ ./gdb/linux-nat.h 29 Jun 2007 22:06:05 -0000
@@ -42,6 +42,9 @@ struct lwp_info
/* Non-zero if this LWP is stopped. */
int stopped;

+ /* Non-zero if this LWP was stopped by SIGSTOP before attaching. */
+ int was_stopped;
+
/* Non-zero if this LWP will be/has been resumed. Note that an LWP
can be marked both as stopped and resumed at the same time. This
happens if we try to resume an LWP that has a wait status
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/attach-into-signal.c 29 Jun 2007 22:06:05 -0000
@@ -0,0 +1,70 @@
+/* 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);
+
+ /* It is an upstream kernel bug (2.6.22-rc4-git7.x86_64, PREEMPT, SMP).
+ We never get here without ptrace(2) but we get while under ptrace(2). */
+ 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;
+}
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/attach-into-signal.exp 29 Jun 2007 22:06:06 -0000
@@ -0,0 +1,176 @@
+# 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 1
+ set passes 1
+ while { $passes < 3 && $attempt <= $attempts } {
+
+ # Start with clean gdb
+ gdb_exit
+
+ set stoppedtry 0
+ while { $stoppedtry < 10 } {
+ set fileid [open /proc/${testpid}/status r];
+ 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
+ }
+
+ 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"
+ if {[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.
+ }
+ }] != 0 } {
+ 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.*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]
+ }
+ }] != 0 } {
+ break
+ }
+ }
+ 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}"
+}
+
+# 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 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."
+}
+
+corefunc threaded
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ./gdb/testsuite/gdb.threads/attach-stopped.c 29 Jun 2007 22:06:06 -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 29 Jun 2007 22:06:06 -0000
@@ -0,0 +1,213 @@
+# 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 &]
+
+ # 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 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
+
+ # Avoid some race:
+ sleep 2
+
+ 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:
+ 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

[-- Attachment #3: ignored-detach-stop-ver0.c --]
[-- Type: text/plain, Size: 6714 bytes --]

/* 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. */

/* Expected runs (on 2.6.22-rc4-git7.x86_64):

130 - after fork(): T (stopped); 0
150 - after non-debugger waitid (..., child, ..., WSTOPPED | ...): T (stopped); 0
158 - after PTRACE_ATTACH: T (stopped); 10368
170 - after tkill (SIGCONT): T (tracing stop); 10368
177 - after waitpid (child): T (tracing stop); 10368
Workaround disabled.
209 - after PTRACE_DETACH (SIGSTOP): S (sleeping); 0
FAIL

140 - after fork(): T (stopped); 0
160 - after non-debugger waitid (..., child, ..., WSTOPPED | ...): T (stopped); 0
168 - after PTRACE_ATTACH: T (stopped); 10406
180 - after tkill (SIGCONT): T (tracing stop); 10406
187 - after waitpid (child): T (tracing stop); 10406
Workaround active!
199 - after tkill (SIGSTOP): T (tracing stop); 10406
204 - after PTRACE_CONT (SIG_0): T (tracing stop); 10406
210 - after waitpid (child): T (tracing stop); 10406
219 - after PTRACE_DETACH (SIGSTOP): T (stopped); 0
PASS
*/

#define _GNU_SOURCE
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <ctype.h>
#include <linux/ptrace.h>

#include <asm/unistd.h>
#include <unistd.h>
#define tkill(tid, sig) syscall (__NR_tkill, (tid), (sig))

#define STATE(pid, label) state_impl (__LINE__, (pid), (label))

static const char *state_impl (int line, pid_t pid, const char *label)
{
char buf[LINE_MAX];
int i;
FILE *f;
char *state = NULL, *tracerpid = NULL;

free (state);
state = NULL;
free (tracerpid);
tracerpid = NULL;
sleep (1);

snprintf (buf, sizeof buf, "/proc/%d/status", (int) pid);
f = fopen (buf, "r");
assert (f != NULL);
while (fgets (buf, sizeof buf, f) != NULL)
{
char *s;

if (strncmp (buf, "TracerPid:", strlen ("TracerPid:")) == 0)
{
for (s = buf + strlen ("TracerPid:"); isspace (*s); s++);
tracerpid = strdup (s);
s = strchr (tracerpid, '\n');
if (s != NULL)
*s = 0;
}
if (strncmp (buf, "State:", strlen ("State:")) == 0)
{
for (s = buf + strlen ("State:"); isspace (*s); s++);
state = strdup (s);
s = strchr (state, '\n');
if (s != NULL)
*s = 0;
}

if (state == NULL || tracerpid == NULL)
continue;

printf ("%d - %s: %s; %s\n", line, label, state, tracerpid);
i = fclose (f);
assert (i == 0);
return state;
}
assert (0);
}

static pid_t child;

static void
cleanup (void)
{
if (child != 0)
kill (child, SIGKILL);
}

static void
handler_fail (int signo)
{
cleanup ();

abort ();
}

int main (void)
{
pid_t got_pid;
int status;
const char *final;

setbuf (stdout, NULL);

child = fork ();
switch (child)
{
case -1:
abort ();
case 0:
raise (SIGSTOP);
for (;;)
pause ();
/* NOTREACHED */
default:
break;
}
atexit (cleanup);
signal (SIGABRT, handler_fail);
STATE (child, "after fork()");
/* `T (stopped)' here. */

#if 1
/* This step is not needed for the reproducibility below.
Present here as it may happen before we - as a debugger - get started.
Without this WAITPID -> SIGSTOP the `tkill (child, SIGCONT)' call below is
not needed and the whole problem is no longer present.
Only removing the `tkill (child, SIGCONT)' call is not the solution. */
{
int i;
siginfo_t siginfo;

i = waitid (P_PID, child, &siginfo, WEXITED | WSTOPPED | WCONTINUED);
assert (i == 0);
assert (siginfo.si_pid == child);
assert (siginfo.si_signo == SIGCHLD);
assert (siginfo.si_code == CLD_STOPPED);
assert (siginfo.si_status == SIGSTOP);
}
STATE (child, "after non-debugger waitid (..., child, ..., WSTOPPED | ...)");
/* `T (stopped)' here. */
#endif

/* Here is a point where we - as a debugger - start to attach. */
errno = 0;
ptrace (PTRACE_ATTACH, child, NULL, NULL);
assert_perror (errno);
STATE (child, "after PTRACE_ATTACH");
/* Still `T (stopped)' here. */

/* `PTRACE_CONT, SIGSTOP' does not work in 100% cases as sometimes SIGSTOP
gets remembered by kernel during the first PTRACE_CONT later and we get
spurious SIGSTOP event. Expecting the signal may get delivered to
a different task of the thread group.
`tkill (SIGSTOP)' has no effect in this moment (it is already stopped). */
errno = 0;
tkill (child, SIGCONT);
assert_perror (errno);
errno = 0;
STATE (child, "after tkill (SIGCONT)");
/* Turned `T (tracing stop)' here. */

got_pid = waitpid (child, &status, 0);
assert (got_pid == child);
assert (WIFSTOPPED (status));
assert (WSTOPSIG (status) == SIGCONT);
STATE (child, "after waitpid (child)");
/* `T (tracing stop)' here. */

/* Workaround.
In this moment everything is almost fine but we need a workaround as final
`PTRACE_DETACH, SIGSTOP' would leave the process unstopped otherwise: */
#if 0
puts ("Workaround active!");

errno = 0;
tkill (child, SIGSTOP);
assert_perror (errno);
STATE (child, "after tkill (SIGSTOP)");

errno = 0;
ptrace (PTRACE_CONT, child, NULL, NULL);
assert_perror (errno);
STATE (child, "after PTRACE_CONT (SIG_0)");

got_pid = waitpid (child, &status, 0);
assert (got_pid == child);
assert (WIFSTOPPED (status));
assert (WSTOPSIG (status) == SIGSTOP);
STATE (child, "after waitpid (child)");
#else
puts ("Workaround disabled.");
#endif

/* We would like to leave the process stopped (`T (stopped)'). */
errno = 0;
ptrace (PTRACE_DETACH, child, NULL, (void *) SIGSTOP);
assert_perror (errno);
final = STATE (child, "after PTRACE_DETACH (SIGSTOP)");
if (strcmp (final, "T (stopped)") == 0)
{
puts ("PASS");
return 0;
}
if (strcmp (final, "S (sleeping)") == 0)
{
/* 2.6.22-rc4-git7.x86_64 */
puts ("FAIL");
return 1;
}
puts ("FAIL (unexpected)");
return 2;
}