diff options
Diffstat (limited to 'gdb/testsuite/gdb.threads')
-rw-r--r-- | gdb/testsuite/gdb.threads/process-dies-while-detaching.c | 116 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/process-dies-while-detaching.exp | 327 |
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 + } + } +} |