aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/gdb.threads
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/testsuite/gdb.threads')
-rw-r--r--gdb/testsuite/gdb.threads/process-dies-while-detaching.c116
-rw-r--r--gdb/testsuite/gdb.threads/process-dies-while-detaching.exp327
2 files changed, 443 insertions, 0 deletions
diff --git a/gdb/testsuite/gdb.threads/process-dies-while-detaching.c b/gdb/testsuite/gdb.threads/process-dies-while-detaching.c
new file mode 100644
index 0000000..a28e804
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/process-dies-while-detaching.c
@@ -0,0 +1,116 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2016 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <pthread.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <assert.h>
+
+/* This barrier ensures we only reach the initial breakpoint after all
+ threads have started. */
+pthread_barrier_t start_threads_barrier;
+
+/* Many threads in order to be fairly sure the process exits while GDB
+ is detaching from each thread in the process, on targets that need
+ to detach from each thread individually. */
+#define NTHREADS 256
+
+/* GDB sets a watchpoint here. */
+int globalvar = 1;
+
+/* GDB reads this. */
+int mypid;
+
+/* Threads' entry point. */
+
+void *
+thread_function (void *arg)
+{
+ pthread_barrier_wait (&start_threads_barrier);
+ _exit (0);
+}
+
+/* The fork child's entry point. */
+
+void
+child_function (void)
+{
+ pthread_t threads[NTHREADS];
+ int i;
+
+ pthread_barrier_init (&start_threads_barrier, NULL, NTHREADS + 1);
+
+ for (i = 0; i < NTHREADS; i++)
+ pthread_create (&threads[i], NULL, thread_function, NULL);
+ pthread_barrier_wait (&start_threads_barrier);
+
+ exit (0);
+}
+
+/* This is defined by the .exp file if testing the multi-process
+ variant. */
+#ifdef MULTIPROCESS
+
+/* The fork parent's entry point. */
+
+void
+parent_function (pid_t child)
+{
+ int status, ret;
+
+ alarm (300);
+
+ ret = waitpid (child, &status, 0);
+ if (ret == -1)
+ exit (1);
+ else if (!WIFEXITED (status))
+ exit (2);
+ else
+ {
+ printf ("exited, status=%d\n", WEXITSTATUS (status));
+ exit (0);
+ }
+}
+
+#endif
+
+int
+main (void)
+{
+#ifdef MULTIPROCESS
+ pid_t child;
+
+ child = fork ();
+ if (child == -1)
+ return 1;
+#endif
+
+ mypid = getpid ();
+
+#ifdef MULTIPROCESS
+ if (child != 0)
+ parent_function (child);
+ else
+#endif
+ child_function ();
+
+ /* Not reached. */
+ abort ();
+}
diff --git a/gdb/testsuite/gdb.threads/process-dies-while-detaching.exp b/gdb/testsuite/gdb.threads/process-dies-while-detaching.exp
new file mode 100644
index 0000000..8dc1aec
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/process-dies-while-detaching.exp
@@ -0,0 +1,327 @@
+# Copyright 2016 Free Software Foundation, Inc.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This test spawns a few threads that immediately exit the whole
+# process. On targets where the debugger needs to detach from each
+# thread individually (such as on the Linux kernel), the debugger must
+# handle the case of the process exiting while the detach is ongoing.
+#
+# Similarly, the process can also be killed from outside the debugger
+# (e.g., with SIGKILL), _before_ the user requests a detach. The
+# debugger must likewise detach gracefully.
+#
+# The testcase actually builds two variants of the test program:
+# single-process, and multi-process. In the multi-process variant,
+# the test program forks, and it's the fork child that spawns threads
+# that exit just while the process is being detached from. The fork
+# parent waits for its child to exit, so if GDB fails to detach from
+# the child correctly, the parent hangs. Because continuing the
+# parent can mask failure to detach from the child correctly (e.g.,
+# due to waitpid(-1,...) calls deep in the target layers managing to
+# reap the child), we try immediately detaching from the parent too,
+# and observing whether the parent exits via standard output.
+#
+# Normally, if testing with "target remote" against gdbserver, then
+# after detaching from all attached processes, gdbserver exits.
+# However, when gdbserver detaches from a process that is its own
+# direct child, gdbserver does not exit immediately. Instead it
+# "joins" (waits for) the child, only exiting when the child itself
+# exits too. Thus, on Linux, if gdbserver fails to detach from the
+# zombie child's threads correctly (or rather, reap them), it'll hang,
+# because the leader thread will only return an exit status after all
+# threads are reaped. We test that as well.
+
+standard_testfile
+
+# Test that GDBserver exits.
+
+proc test_server_exit {} {
+ global server_spawn_id
+
+ set test "server exits"
+ gdb_expect {
+ -i $server_spawn_id
+ eof {
+ pass $test
+ wait -i $server_spawn_id
+ unset server_spawn_id
+ }
+ timeout {
+ fail "$test (timeout)"
+ }
+ }
+}
+
+# If RESULT is not zero, make the caller return.
+
+proc return_if_fail { result } {
+ if {$result != 0} {
+ return -code return
+ }
+}
+
+# Detach from a process, and ensure that it exits after detaching.
+# This relies on inferior I/O.
+
+proc detach_and_expect_exit {test} {
+ global decimal
+ global gdb_spawn_id
+ global inferior_spawn_id
+ global gdb_prompt
+
+ return_if_fail [gdb_test_multiple "detach" $test {
+ -re "Detaching from .*, process $decimal" {
+ }
+ }]
+
+ set saw_prompt 0
+ set saw_inf_exit 0
+ while { !$saw_prompt && ! $saw_inf_exit } {
+ # We don't know what order the interesting things will arrive in.
+ # Using a pattern of the form 'x|y|z' instead of -re x ... -re y
+ # ... -re z ensures that expect always chooses the match that
+ # occurs leftmost in the input, and not the pattern appearing
+ # first in the script that occurs anywhere in the input, so that
+ # we don't skip anything.
+ return_if_fail [gdb_test_multiple "" $test {
+ -i "$inferior_spawn_id $gdb_spawn_id"
+ -re "(exited, status=0)|($gdb_prompt )" {
+ if {[info exists expect_out(1,string)]} {
+ verbose -log "saw inferior exit"
+ set saw_inf_exit 1
+ } elseif {[info exists expect_out(2,string)]} {
+ verbose -log "saw prompt"
+ set saw_prompt 1
+ }
+ array unset expect_out
+ }
+ }]
+ }
+
+ pass $test
+}
+
+# Run to _exit in the child.
+
+proc continue_to_exit_bp {} {
+ gdb_breakpoint "_exit" temporary
+ gdb_continue_to_breakpoint "_exit" ".*_exit.*"
+}
+
+# If testing single-process, simply detach from the process.
+#
+# If testing multi-process, first detach from the child, then detach
+# from the parent and confirm that the parent exits, thus ensuring
+# we've detached from the child successfully, as the parent hangs in
+# its waitpid call otherwise.
+#
+# If connected with "target remote", make sure gdbserver exits.
+#
+# CMD indicates what to do with the parent after detaching the child.
+# Can be either "detach" to detach, or "continue", to continue to
+# exit. If "continue", then CONTINUE_RE is the regexp to expect.
+# Defaults to normal exit output.
+#
+proc do_detach {multi_process cmd {continue_re ""}} {
+ global decimal
+ global server_spawn_id
+
+ if {$continue_re == ""} {
+ set continue_re "exited normally.*"
+ }
+
+ set is_remote [expr {[target_info exists gdb_protocol]
+ && [target_info gdb_protocol] == "remote"}]
+
+ if {$multi_process} {
+ gdb_test "detach" "Detaching from .*, process $decimal" \
+ "detach child"
+
+ gdb_test "inferior 1" "\[Switching to inferior $decimal\].*" \
+ "switch to parent"
+
+ if {$cmd == "detach"} {
+ # Make sure that detach works and that the parent process
+ # exits cleanly.
+ detach_and_expect_exit "detach parent"
+ } elseif {$cmd == "continue"} {
+ # Make sure that continuing works and that the parent process
+ # exits cleanly.
+ gdb_test "continue" $continue_re
+ } else {
+ perror "unhandled command: $cmd"
+ }
+ } else {
+ if $is_remote {
+ set extra "\r\nEnding remote debugging\."
+ } else {
+ set extra ""
+ }
+ if {$cmd == "detach"} {
+ gdb_test "detach" "Detaching from .*, process $decimal$extra"
+ } elseif {$cmd == "continue"} {
+ gdb_test "continue" $continue_re
+ } else {
+ perror "unhandled command: $cmd"
+ }
+ }
+
+ # When connected in "target remote" mode, the server should exit
+ # when there are no processes left to debug.
+ if { $is_remote && [info exists server_spawn_id]} {
+ test_server_exit
+ }
+}
+
+# Test detaching from a process that dies just while GDB is detaching.
+
+proc test_detach {multi_process cmd} {
+ with_test_prefix "detach" {
+ global binfile
+
+ clean_restart ${binfile}
+
+ if ![runto_main] {
+ fail "Can't run to main"
+ return -1
+ }
+
+ if {$multi_process} {
+ gdb_test_no_output "set detach-on-fork off"
+ gdb_test_no_output "set follow-fork-mode child"
+ }
+
+ # Run to _exit in the child.
+ continue_to_exit_bp
+
+ do_detach $multi_process $cmd
+ }
+}
+
+# Same as test_detach, except set a watchpoint before detaching.
+
+proc test_detach_watch {multi_process cmd} {
+ with_test_prefix "watchpoint" {
+ global binfile decimal
+
+ clean_restart ${binfile}
+
+ if ![runto_main] {
+ fail "Can't run to main"
+ return -1
+ }
+
+ if {$multi_process} {
+ gdb_test_no_output "set detach-on-fork off"
+ gdb_test_no_output "set follow-fork-mode child"
+
+ gdb_breakpoint "child_function" temporary
+ gdb_continue_to_breakpoint "child_function" ".*"
+ }
+
+ # Set a watchpoint in the child.
+ gdb_test "watch globalvar" ".* watchpoint $decimal: globalvar"
+
+ # Continue to the _exit breakpoint. This arms the watchpoint
+ # registers in all threads. Detaching will thus need to clear
+ # them out, and handle the case of the thread disappearing
+ # while doing that (on targets that need to detach from each
+ # thread individually).
+ continue_to_exit_bp
+
+ do_detach $multi_process $cmd
+ }
+}
+
+# Test detaching from a process that dies _before_ GDB starts
+# detaching.
+
+proc test_detach_killed_outside {multi_process cmd} {
+ with_test_prefix "killed outside" {
+ global binfile
+
+ clean_restart ${binfile}
+
+ if ![runto_main] {
+ fail "Can't run to main"
+ return -1
+ }
+
+ gdb_test_no_output "set breakpoint always-inserted on"
+
+ if {$multi_process} {
+ gdb_test_no_output "set detach-on-fork off"
+ gdb_test_no_output "set follow-fork-mode child"
+ }
+
+ # Run to _exit in the child.
+ continue_to_exit_bp
+
+ set childpid [get_integer_valueof "mypid" -1]
+ if { $childpid == -1 } {
+ untested "failed to extract child pid"
+ return -1
+ }
+
+ remote_exec target "kill -9 ${childpid}"
+
+ # Give it some time to die.
+ sleep 2
+
+ if {$multi_process} {
+ set continue_re "exited with code 02.*"
+ } else {
+ set continue_re "terminated with signal SIGKILL.*"
+ }
+ do_detach $multi_process $cmd $continue_re
+ }
+}
+
+# The test proper. MULTI_PROCESS is true if testing the multi-process
+# variant.
+
+proc do_test {multi_process cmd} {
+ global testfile srcfile binfile
+
+ if {$multi_process && $cmd == "detach"
+ && [target_info exists gdb,noinferiorio]} {
+ # This requires inferior I/O to tell whether both the parent
+ # and child exit successfully.
+ return
+ }
+
+ set binfile [standard_output_file ${testfile}-$multi_process-$cmd]
+ set options {debug pthreads}
+ if {$multi_process} {
+ lappend options "additional_flags=-DMULTIPROCESS"
+ }
+
+ if {[build_executable "failed to build" \
+ $testfile-$multi_process-$cmd $srcfile $options] == -1} {
+ return -1
+ }
+
+ test_detach $multi_process $cmd
+ test_detach_watch $multi_process $cmd
+ test_detach_killed_outside $multi_process $cmd
+}
+
+foreach multi_process {0 1} {
+ set mode [expr {$multi_process ? "single-process" : "multi-process"}]
+ foreach cmd {"detach" "continue"} {
+ with_test_prefix "$mode: $cmd" {
+ do_test $multi_process $cmd
+ }
+ }
+}