From a0ef42744d050b642351a3974faca030269f5504 Mon Sep 17 00:00:00 2001 From: Daniel Jacobowitz Date: Thu, 1 May 2008 18:50:14 +0000 Subject: [PATCH] 2008-05-01 Daniel Jacobowitz Pedro Alves Based on work by Jan Kratochvil and Jeff Johnston . * 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-05-01 Jan Kratochvil Daniel Jacobowitz * 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. --- gdb/Makefile.in | 2 +- gdb/NEWS | 10 +- gdb/infcmd.c | 3 + gdb/infrun.c | 18 +- gdb/linux-nat.c | 282 +++++++++++++----- .../gdb.threads/attach-into-signal.c | 67 +++++ .../gdb.threads/attach-into-signal.exp | 168 +++++++++++ gdb/testsuite/gdb.threads/attach-stopped.c | 50 ++++ gdb/testsuite/gdb.threads/attach-stopped.exp | 157 ++++++++++ gdb/testsuite/gdb.threads/attachstop-mt.c | 55 ++++ gdb/testsuite/gdb.threads/attachstop-mt.exp | 260 ++++++++++++++++ 11 files changed, 985 insertions(+), 87 deletions(-) create mode 100644 gdb/testsuite/gdb.threads/attach-into-signal.c create mode 100644 gdb/testsuite/gdb.threads/attach-into-signal.exp create mode 100644 gdb/testsuite/gdb.threads/attach-stopped.c create mode 100644 gdb/testsuite/gdb.threads/attach-stopped.exp create mode 100644 gdb/testsuite/gdb.threads/attachstop-mt.c create mode 100644 gdb/testsuite/gdb.threads/attachstop-mt.exp diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 7a3ce035001..a98351cd4cc 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -2316,7 +2316,7 @@ infcmd.o: infcmd.c $(defs_h) $(gdb_string_h) $(symtab_h) $(gdbtypes_h) \ $(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) $(cli_decode_h) + $(user_regs_h) $(exceptions_h) $(cli_decode_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) diff --git a/gdb/NEWS b/gdb/NEWS index fcda6d1646a..4602ffdc8ab 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -4,11 +4,11 @@ *** Changes since GDB 6.8 * GDB now supports multiple function calling conventions according to the - DWARF-2 DW_AT_calling_convention function attribute. +DWARF-2 DW_AT_calling_convention function attribute. * The SH target utilizes the aforementioned change to distinguish between gcc - and Renesas calling convention. It also adds the new CLI commands - `set/show sh calling-convention'. +and Renesas calling convention. It also adds the new CLI commands +`set/show sh calling-convention'. * GDB can now read compressed debug sections, as produced by GNU gold with the --compress-debug-sections=zlib flag. @@ -21,6 +21,10 @@ 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. +* 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 - The "--wrapper" command-line argument tells gdbserver to use a diff --git a/gdb/infcmd.c b/gdb/infcmd.c index bdbb9bf0e06..3165a26f3f7 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -50,6 +50,7 @@ #include "user-regs.h" #include "exceptions.h" #include "cli/cli-decode.h" +#include "gdbthread.h" /* Functions exported for general use, in inferior.h: */ @@ -2050,6 +2051,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 (); } @@ -2068,6 +2070,7 @@ disconnect_command (char *args, int from_tty) 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 (); } diff --git a/gdb/infrun.c b/gdb/infrun.c index f576a59123d..16a802bb068 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -1946,13 +1946,15 @@ handle_inferior_event (struct execution_control_state *ecs) /* 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; } @@ -2023,7 +2025,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; @@ -3276,7 +3278,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 diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c index 9511815ecd6..b09cd498960 100644 --- a/gdb/linux-nat.c +++ b/gdb/linux-nat.c @@ -50,6 +50,30 @@ #include "event-loop.h" #include "event-top.h" +/* Note on this file's use of signals: + + We stop threads by sending a SIGSTOP. The use of SIGSTOP instead + of another signal is not entirely significant; we just need for a + signal to be delivered, so that we can intercept it. SIGSTOP's + advantage is that it can not be blocked. A disadvantage is that it + is not a real-time signal, so it can only be queued once; we do not + keep track of other sources of SIGSTOP. + + Two other signals that can't be blocked are SIGCONT and SIGKILL. + But we can't use them, because they have special behavior when the + signal is generated - not when it is delivered. SIGCONT resumes + the entire thread group and SIGKILL kills the entire thread group. + + A delivered SIGSTOP would stop the entire thread group, not just the + thread we tkill'd. But we never let the SIGSTOP deliver; we always + intercept and cancel it (by PTRACE_CONT without passing SIGSTOP). + + We could use a real-time signal instead. This would solve those + problems; we could use PTRACE_GETSIGINFO to locate the specific + stop signals sent by GDB. But we would still have to have some + support for SIGSTOP, since PTRACE_ATTACH generates it, and there + are races with trying to find a signal that is not blocked. */ + #ifndef O_LARGEFILE #define O_LARGEFILE 0 #endif @@ -186,6 +210,7 @@ static void linux_nat_async (void (*callback) (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 +1035,103 @@ exit_lwp (struct lwp_info *lp) delete_lwp (lp->ptid); } -/* Attach to the LWP specified by PID. If VERBOSE is non-zero, print - a message telling the user that a new LWP has been added to the - process. Return 0 if successful or -1 if the new LWP could not - be attached. */ +/* Detect `T (stopped)' in `/proc/PID/status'. + Other states including `T (tracing stop)' are reported as false. */ + +static int +pid_is_stopped (pid_t pid) +{ + FILE *status_file; + char buf[100]; + int retval = 0; + + snprintf (buf, sizeof (buf), "/proc/%d/status", (int) pid); + status_file = fopen (buf, "r"); + if (status_file != NULL) + { + int have_state = 0; + + while (fgets (buf, sizeof (buf), status_file)) + { + if (strncmp (buf, "State:", 6) == 0) + { + have_state = 1; + break; + } + } + if (have_state && strstr (buf, "T (stopped)") != NULL) + retval = 1; + fclose (status_file); + } + return retval; +} + +/* Wait for the LWP specified by LP, which we have just attached to. + Returns a wait status for that LWP, to cache. */ + +static int +linux_nat_post_attach_wait (ptid_t ptid, int first, int *cloned, + int *signalled) +{ + pid_t new_pid, pid = GET_LWP (ptid); + int status; + + if (pid_is_stopped (pid)) + { + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "LNPAW: Attaching to a stopped process\n"); + + /* The process is definitely stopped. It is in a job control + stop, unless the kernel predates the TASK_STOPPED / + TASK_TRACED distinction, in which case it might be in a + ptrace stop. Make sure it is in a ptrace stop; from there we + can kill it, signal it, et cetera. + + First make sure there is a pending SIGSTOP. Since we are + already attached, the process can not transition from stopped + to running without a PTRACE_CONT; so we know this signal will + go into the queue. The SIGSTOP generated by PTRACE_ATTACH is + probably already in the queue (unless this kernel is old + enough to use TASK_STOPPED for ptrace stops); but since SIGSTOP + is not an RT signal, it can only be queued once. */ + kill_lwp (pid, SIGSTOP); + + /* Finally, resume the stopped process. This will deliver the SIGSTOP + (or a higher priority signal, just like normal PTRACE_ATTACH). */ + ptrace (PTRACE_CONT, pid, 0, 0); + } + + /* Make sure the initial process is stopped. The user-level threads + layer might want to poke around in the inferior, and that won't + work if things haven't stabilized yet. */ + new_pid = my_waitpid (pid, &status, 0); + if (new_pid == -1 && errno == ECHILD) + { + if (first) + warning (_("%s is a cloned process"), target_pid_to_str (ptid)); + + /* Try again with __WCLONE to check cloned processes. */ + new_pid = my_waitpid (pid, &status, __WCLONE); + *cloned = 1; + } + + gdb_assert (pid == new_pid && WIFSTOPPED (status)); + + if (WSTOPSIG (status) != SIGSTOP) + { + *signalled = 1; + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "LNPAW: Received %s after attaching\n", + status_to_str (status)); + } + + return status; +} + +/* Attach to the LWP specified by PID. Return 0 if successful or -1 + if the new LWP could not be attached. */ int lin_lwp_attach_lwp (ptid_t ptid) @@ -1036,9 +1154,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 +1173,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 +1243,7 @@ static void linux_nat_attach (char *args, int from_tty) { struct lwp_info *lp; - pid_t pid; int status; - int cloned = 0; - int options = 0; /* FIXME: We should probably accept a list of process id's, and attach all of them. */ @@ -1151,54 +1258,69 @@ linux_nat_attach (char *args, int from_tty) sigdelset (&suspend_mask, SIGCHLD); } - /* Make sure the initial process is stopped. The user-level threads - layer might want to poke around in the inferior, and that won't - work if things haven't stabilized yet. */ - pid = my_waitpid (GET_PID (inferior_ptid), &status, options); - if (pid == -1 && errno == ECHILD) - { - warning (_("%s is a cloned process"), target_pid_to_str (inferior_ptid)); - - /* Try again with __WCLONE to check cloned processes. */ - options = __WCLONE; - pid = my_waitpid (GET_PID (inferior_ptid), &status, options); - cloned = 1; - } - - gdb_assert (pid == GET_PID (inferior_ptid) - && WIFSTOPPED (status) && WSTOPSIG (status) == SIGSTOP); - /* Add the initial process as the first LWP to the list. */ inferior_ptid = BUILD_LWP (GET_PID (inferior_ptid), GET_PID (inferior_ptid)); lp = add_lwp (inferior_ptid); - lp->cloned = cloned; + + status = linux_nat_post_attach_wait (lp->ptid, 1, &lp->cloned, + &lp->signalled); + lp->stopped = 1; /* If this process is not using thread_db, then we still don't detect any other threads, but add at least this one. */ add_thread_silent (lp->ptid); - lp->stopped = 1; + /* Save the wait status to report later. */ lp->resumed = 1; + if (debug_linux_nat) + fprintf_unfiltered (gdb_stdlog, + "LNA: waitpid %ld, saving status %s\n", + (long) GET_PID (lp->ptid), status_to_str (status)); if (!target_can_async_p ()) - { - /* Fake the SIGSTOP that core GDB expects. */ - lp->status = W_STOPCODE (SIGSTOP); - if (debug_linux_nat) - fprintf_unfiltered (gdb_stdlog, - "LNA: waitpid %ld, faking SIGSTOP\n", (long) pid); - } + lp->status = status; else { /* We already waited for this LWP, so put the wait result on the pipe. The event loop will wake up and gets us to handling this event. */ - linux_nat_event_pipe_push (pid, status, options); + linux_nat_event_pipe_push (GET_PID (lp->ptid), status, + lp->cloned ? __WCLONE : 0); /* Register in the event loop. */ target_async (inferior_event_handler, 0); } } +/* Get pending status of LP. */ +static int +get_pending_status (struct lwp_info *lp, int *status) +{ + struct target_waitstatus last; + ptid_t last_ptid; + + get_last_target_status (&last_ptid, &last); + + /* If this lwp is the ptid that GDB is processing an event from, the + signal will be in stop_signal. Otherwise, in all-stop + sync + mode, we may cache pending events in lp->status while trying to + stop all threads (see stop_wait_callback). In async mode, the + events are always cached in waitpid_queue. */ + + *status = 0; + if (GET_LWP (lp->ptid) == GET_LWP (last_ptid)) + { + if (stop_signal != TARGET_SIGNAL_0 + && signal_pass_state (stop_signal)) + *status = W_STOPCODE (target_signal_to_host (stop_signal)); + } + else if (target_can_async_p ()) + queued_waitpid (GET_LWP (lp->ptid), status, __WALL); + else + *status = lp->status; + + return 0; +} + static int detach_callback (struct lwp_info *lp, void *data) { @@ -1209,40 +1331,30 @@ detach_callback (struct lwp_info *lp, void *data) strsignal (WSTOPSIG (lp->status)), target_pid_to_str (lp->ptid)); - while (lp->signalled && lp->stopped) + /* If there is a pending SIGSTOP, get rid of it. */ + if (lp->signalled) { - errno = 0; - if (ptrace (PTRACE_CONT, GET_LWP (lp->ptid), 0, - WSTOPSIG (lp->status)) < 0) - error (_("Can't continue %s: %s"), target_pid_to_str (lp->ptid), - safe_strerror (errno)); - if (debug_linux_nat) fprintf_unfiltered (gdb_stdlog, - "DC: PTRACE_CONTINUE (%s, 0, %s) (OK)\n", - target_pid_to_str (lp->ptid), - status_to_str (lp->status)); + "DC: Sending SIGCONT to %s\n", + target_pid_to_str (lp->ptid)); - lp->stopped = 0; + kill_lwp (GET_LWP (lp->ptid), SIGCONT); lp->signalled = 0; - lp->status = 0; - /* FIXME drow/2003-08-26: There was a call to stop_wait_callback - here. But since lp->signalled was cleared above, - stop_wait_callback didn't do anything; the process was left - running. Shouldn't we be waiting for it to stop? - I've removed the call, since stop_wait_callback now does do - something when called with lp->signalled == 0. */ - - gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status)); } /* We don't actually detach from the LWP that has an id equal to the overall process id just yet. */ if (GET_LWP (lp->ptid) != GET_PID (lp->ptid)) { + int status = 0; + + /* Pass on any pending signal for this LWP. */ + get_pending_status (lp, &status); + errno = 0; if (ptrace (PTRACE_DETACH, GET_LWP (lp->ptid), 0, - WSTOPSIG (lp->status)) < 0) + WSTOPSIG (status)) < 0) error (_("Can't detach %s: %s"), target_pid_to_str (lp->ptid), safe_strerror (errno)); @@ -1252,7 +1364,6 @@ detach_callback (struct lwp_info *lp, void *data) target_pid_to_str (lp->ptid), strsignal (WSTOPSIG (lp->status))); - drain_queued_events (GET_LWP (lp->ptid)); delete_lwp (lp->ptid); } @@ -1263,6 +1374,9 @@ static void linux_nat_detach (char *args, int from_tty) { int pid; + int status; + enum target_signal sig; + if (target_can_async_p ()) linux_nat_async (NULL, 0); @@ -1271,6 +1385,21 @@ linux_nat_detach (char *args, int from_tty) /* Only the initial process should be left right now. */ gdb_assert (num_lwps == 1); + /* Pass on any pending signal for the last LWP. */ + if ((args == NULL || *args == '\0') + && get_pending_status (lwp_list, &status) != -1 + && WIFSTOPPED (status)) + { + /* Put the signal number in ARGS so that inf_ptrace_detach will + pass it along with PTRACE_DETACH. */ + args = alloca (8); + sprintf (args, "%d", (int) WSTOPSIG (status)); + fprintf_unfiltered (gdb_stdlog, + "LND: Sending signal %s to %s\n", + args, + target_pid_to_str (lwp_list->ptid)); + } + trap_ptid = null_ptid; /* Destroy LWP info; it's no longer valid. */ @@ -2848,7 +2977,9 @@ linux_nat_pid_to_str (ptid_t ptid) { static char buf[64]; - if (lwp_list && lwp_list->next && is_lwp (ptid)) + if (is_lwp (ptid) + && ((lwp_list && lwp_list->next) + || GET_PID (ptid) != GET_LWP (ptid))) { snprintf (buf, sizeof (buf), "LWP %ld", GET_LWP (ptid)); return buf; @@ -4205,4 +4336,3 @@ lin_thread_get_thread_signals (sigset_t *set) /* ... except during a sigsuspend. */ sigdelset (&suspend_mask, cancel); } - diff --git a/gdb/testsuite/gdb.threads/attach-into-signal.c b/gdb/testsuite/gdb.threads/attach-into-signal.c new file mode 100644 index 00000000000..653d36ce48c --- /dev/null +++ b/gdb/testsuite/gdb.threads/attach-into-signal.c @@ -0,0 +1,67 @@ +/* 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 . */ + +#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); + + /* We must not get past this point, either in a free standing or debugged + state. */ + + 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; +} diff --git a/gdb/testsuite/gdb.threads/attach-into-signal.exp b/gdb/testsuite/gdb.threads/attach-into-signal.exp new file mode 100644 index 00000000000..ec59e45516f --- /dev/null +++ b/gdb/testsuite/gdb.threads/attach-into-signal.exp @@ -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 . + +# This test was created by modifying attach-stopped.exp. +# This file was created by Jan Kratochvil . + +# 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 diff --git a/gdb/testsuite/gdb.threads/attach-stopped.c b/gdb/testsuite/gdb.threads/attach-stopped.c new file mode 100644 index 00000000000..1343f673021 --- /dev/null +++ b/gdb/testsuite/gdb.threads/attach-stopped.c @@ -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 . */ + +/* 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; +} diff --git a/gdb/testsuite/gdb.threads/attach-stopped.exp b/gdb/testsuite/gdb.threads/attach-stopped.exp new file mode 100644 index 00000000000..27da3416955 --- /dev/null +++ b/gdb/testsuite/gdb.threads/attach-stopped.exp @@ -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 . + +# This test was created by modifying attach.exp. +# This file was created by Jeff Johnston . +# This file was updated by Jan Kratochvil . + +# 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 diff --git a/gdb/testsuite/gdb.threads/attachstop-mt.c b/gdb/testsuite/gdb.threads/attachstop-mt.c new file mode 100644 index 00000000000..9d631d48f4f --- /dev/null +++ b/gdb/testsuite/gdb.threads/attachstop-mt.c @@ -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 . */ + +/* This program is intended to be started outside of gdb, then + manually stopped via a signal. */ + +#include +#include +#include + +/* 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; +} diff --git a/gdb/testsuite/gdb.threads/attachstop-mt.exp b/gdb/testsuite/gdb.threads/attachstop-mt.exp new file mode 100644 index 00000000000..167f05260c4 --- /dev/null +++ b/gdb/testsuite/gdb.threads/attachstop-mt.exp @@ -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 . + +# This test was created by modifying gdb.threads/attachstop. +# This file was created by Jan Kratochvil . +# 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 -- 2.30.2