2007-06-30 Jan Kratochvil * 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 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 +#include +#include +#include +#include +#ifdef USE_THREADS +#include +#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 . + +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 +#include +#ifdef USE_THREADS +#include +#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 . +# This file was updated by Jan Kratochvil . + +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