aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog17
-rw-r--r--gdb/infrun.c86
-rw-r--r--gdb/remote.c20
-rw-r--r--gdb/testsuite/ChangeLog8
-rw-r--r--gdb/testsuite/gdb.multi/multi-exit.c22
-rw-r--r--gdb/testsuite/gdb.multi/multi-exit.exp134
-rw-r--r--gdb/testsuite/gdb.multi/multi-kill.c42
-rw-r--r--gdb/testsuite/gdb.multi/multi-kill.exp127
-rw-r--r--gdb/thread.c2
9 files changed, 432 insertions, 26 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 6d56c04..c4da2a9 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,4 +1,21 @@
2020-05-14 Tankut Baris Aktemur <tankut.baris.aktemur@intel.com>
+ Tom de Vries <tdevries@suse.de>
+ Pedro Alves <palves@redhat.com>
+
+ PR threads/25478
+ * infrun.c (stop_all_threads): Do NOT ignore
+ TARGET_WAITKIND_NO_RESUMED, TARGET_WAITKIND_THREAD_EXITED,
+ TARGET_WAITKIND_EXITED, TARGET_WAITKIND_SIGNALLED wait statuses
+ received.
+ (handle_no_resumed): Remove code handling a live inferior with no
+ threads.
+ * remote.c (has_single_non_exited_thread): New.
+ (remote_target::update_thread_list): Do not delete a thread if is
+ the last thread of the process.
+ * thread.c (thread_select): Call delete_exited_threads instead of
+ prune_threads.
+
+2020-05-14 Tankut Baris Aktemur <tankut.baris.aktemur@intel.com>
* infrun.c (stop_all_threads): Enable/disable thread events of all
targets. Move a debug message denoting the end of the function
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 2a8e73c..c3e23a2 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4804,7 +4804,11 @@ stop_all_threads (void)
{
int need_wait = 0;
- update_thread_list ();
+ for (auto *target : all_non_exited_process_targets ())
+ {
+ switch_to_target_no_thread (target);
+ update_thread_list ();
+ }
/* Go through all threads looking for threads that we need
to tell the target to stop. */
@@ -4879,13 +4883,63 @@ stop_all_threads (void)
target_pid_to_str (event.ptid).c_str ());
}
- if (event.ws.kind == TARGET_WAITKIND_NO_RESUMED
- || event.ws.kind == TARGET_WAITKIND_THREAD_EXITED
- || event.ws.kind == TARGET_WAITKIND_EXITED
- || event.ws.kind == TARGET_WAITKIND_SIGNALLED)
+ if (event.ws.kind == TARGET_WAITKIND_NO_RESUMED)
+ {
+ /* All resumed threads exited. */
+ }
+ else if (event.ws.kind == TARGET_WAITKIND_THREAD_EXITED
+ || event.ws.kind == TARGET_WAITKIND_EXITED
+ || event.ws.kind == TARGET_WAITKIND_SIGNALLED)
{
- /* All resumed threads exited
- or one thread/process exited/signalled. */
+ /* One thread/process exited/signalled. */
+
+ thread_info *t = nullptr;
+
+ /* The target may have reported just a pid. If so, try
+ the first non-exited thread. */
+ if (event.ptid.is_pid ())
+ {
+ int pid = event.ptid.pid ();
+ inferior *inf = find_inferior_pid (event.target, pid);
+ for (thread_info *tp : inf->non_exited_threads ())
+ {
+ t = tp;
+ break;
+ }
+
+ /* If there is no available thread, the event would
+ have to be appended to a per-inferior event list,
+ which does not exist (and if it did, we'd have
+ to adjust run control command to be able to
+ resume such an inferior). We assert here instead
+ of going into an infinite loop. */
+ gdb_assert (t != nullptr);
+
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stop_all_threads, using %s\n",
+ target_pid_to_str (t->ptid).c_str ());
+ }
+ else
+ {
+ t = find_thread_ptid (event.target, event.ptid);
+ /* Check if this is the first time we see this thread.
+ Don't bother adding if it individually exited. */
+ if (t == nullptr
+ && event.ws.kind != TARGET_WAITKIND_THREAD_EXITED)
+ t = add_thread (event.target, event.ptid);
+ }
+
+ if (t != nullptr)
+ {
+ /* Set the threads as non-executing to avoid
+ another stop attempt on them. */
+ switch_to_thread_no_regs (t);
+ mark_non_executing_threads (event.target, event.ptid,
+ event.ws);
+ save_waitstatus (t, &event.ws);
+ t->stop_requested = false;
+ }
}
else
{
@@ -5063,24 +5117,6 @@ handle_no_resumed (struct execution_control_state *ecs)
}
}
- /* Note however that we may find no resumed thread because the whole
- process exited meanwhile (thus updating the thread list results
- in an empty thread list). In this case we know we'll be getting
- a process exit event shortly. */
- for (inferior *inf : all_non_exited_inferiors (ecs->target))
- {
- thread_info *thread = any_live_thread_of_inferior (inf);
- if (thread == NULL)
- {
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog,
- "infrun: TARGET_WAITKIND_NO_RESUMED "
- "(expect process exit)\n");
- prepare_to_wait (ecs);
- return 1;
- }
- }
-
/* Go ahead and report the event. */
return 0;
}
diff --git a/gdb/remote.c b/gdb/remote.c
index 5db406e..5b1fa84 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -3785,6 +3785,18 @@ remote_target::remote_get_threads_with_qthreadinfo (threads_listing_context *con
return 0;
}
+/* Return true if INF only has one non-exited thread. */
+
+static bool
+has_single_non_exited_thread (inferior *inf)
+{
+ int count = 0;
+ for (thread_info *tp ATTRIBUTE_UNUSED : inf->non_exited_threads ())
+ if (++count > 1)
+ break;
+ return count == 1;
+}
+
/* Implement the to_update_thread_list function for the remote
targets. */
@@ -3824,6 +3836,14 @@ remote_target::update_thread_list ()
if (!context.contains_thread (tp->ptid))
{
+ /* Do not remove the thread if it is the last thread in
+ the inferior. This situation happens when we have a
+ pending exit process status to process. Otherwise we
+ may end up with a seemingly live inferior (i.e. pid
+ != 0) that has no threads. */
+ if (has_single_non_exited_thread (tp->inf))
+ continue;
+
/* Not found. */
delete_thread (tp);
}
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index 3ce59bc..c987c82 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,4 +1,12 @@
2020-05-14 Tankut Baris Aktemur <tankut.baris.aktemur@intel.com>
+ Pedro Alves <palves@redhat.com>
+
+ * gdb.multi/multi-exit.c: New file.
+ * gdb.multi/multi-exit.exp: New file.
+ * gdb.multi/multi-kill.c: New file.
+ * gdb.multi/multi-kill.exp: New file.
+
+2020-05-14 Tankut Baris Aktemur <tankut.baris.aktemur@intel.com>
* gdb.base/annota1.exp: Update the expected output.
* gdb.cp/annota2.exp: Ditto.
diff --git a/gdb/testsuite/gdb.multi/multi-exit.c b/gdb/testsuite/gdb.multi/multi-exit.c
new file mode 100644
index 0000000..f4825c8
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/multi-exit.c
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2020 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/>. */
+
+int
+main ()
+{
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/multi-exit.exp b/gdb/testsuite/gdb.multi/multi-exit.exp
new file mode 100644
index 0000000..393093b
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/multi-exit.exp
@@ -0,0 +1,134 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2020 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/>.
+
+# Test receiving TARGET_WAITKIND_EXITED events from multiple
+# inferiors. In all stop-mode, upon receiving the exit event from one
+# of the inferiors, GDB will try to stop the other inferior, too. So,
+# a stop request will be sent. Receiving a TARGET_WAITKIND_EXITED
+# status kind as a response to that stop request instead of a
+# TARGET_WAITKIND_STOPPED should be handled by GDB without problems.
+
+standard_testfile
+
+if {[use_gdb_stub]} {
+ return 0
+}
+
+if {[build_executable "failed to prepare" $testfile $srcfile]} {
+ return -1
+}
+
+# We are testing GDB's ability to stop all threads.
+# Hence, go with the all-stop-on-top-of-non-stop mode.
+save_vars { GDBFLAGS } {
+ append GDBFLAGS " -ex \"maint set target-non-stop on\""
+ clean_restart ${binfile}
+}
+
+# Start inferior NUM.
+
+proc start_inferior {num} {
+ with_test_prefix "start_inferior $num" {
+ global srcfile binfile
+
+ if {$num != 1} {
+ gdb_test "add-inferior" "Added inferior $num.*" \
+ "add empty inferior"
+ gdb_test "inferior $num" "Switching to inferior $num.*" \
+ "switch to inferior"
+ }
+
+ gdb_load $binfile
+
+ if {[gdb_start_cmd] < 0} {
+ fail "could not start"
+ return -1
+ }
+ gdb_test "" ".*reakpoint .*, main .*${srcfile}.*" "start"
+ }
+
+ return 0
+}
+
+# Sufficient inferiors to make sure that at least some other inferior
+# exits while we're handling a process exit event.
+set NUM_INFS 10
+
+for {set i 1} {$i <= $NUM_INFS} {incr i} {
+ if {[start_inferior $i] < 0} {
+ return -1
+ }
+}
+
+# We want to continue all processes.
+gdb_test_no_output "set schedule-multiple on"
+
+# Check that "continue" continues to the end of an inferior, as many
+# times as we have inferiors.
+
+for {set i 1} {$i <= $NUM_INFS} {incr i} {
+ with_test_prefix "inf $i" {
+ set live_inferior ""
+
+ # Pick any live inferior.
+ gdb_test_multiple "info inferiors" "" {
+ -re "($decimal) *process.*$gdb_prompt $" {
+ set live_inferior $expect_out(1,string)
+ }
+ }
+
+ if {$live_inferior == ""} {
+ return -1
+ }
+
+ gdb_test "inferior $live_inferior" \
+ ".*Switching to inferior $live_inferior.*" \
+ "switch to another inferior"
+
+ set exited_inferior ""
+
+ # We want GDB to complete the command and return the prompt
+ # instead of going into an infinite loop.
+ gdb_test_multiple "continue" "continue" {
+ -re "Inferior ($decimal) \[^\n\r\]+ exited normally.*$gdb_prompt $" {
+ set exited_inferior $expect_out(1,string)
+ pass $gdb_test_name
+ }
+ }
+
+ if {$exited_inferior == ""} {
+ return -1
+ }
+ }
+}
+
+# Finally, check that we can re-run all inferiors. Note that if any
+# inferior was still alive this would catch it, as "run" would query
+# "Start it from the beginning?".
+
+delete_breakpoints
+
+for {set i 1} {$i <= $NUM_INFS} {incr i} {
+ with_test_prefix "inf $i" {
+ gdb_test "inferior $i" \
+ ".*Switching to inferior $i.*" \
+ "switch to inferior for re-run"
+
+ gdb_test "run" "$inferior_exited_re normally\]" \
+ "re-run inferior"
+ }
+}
diff --git a/gdb/testsuite/gdb.multi/multi-kill.c b/gdb/testsuite/gdb.multi/multi-kill.c
new file mode 100644
index 0000000..66642bb
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/multi-kill.c
@@ -0,0 +1,42 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2020 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 <sys/types.h>
+#include <unistd.h>
+
+static pid_t pid;
+
+static void
+initialized ()
+{
+}
+
+int
+main ()
+{
+ pid = getpid ();
+ initialized ();
+
+ /* Don't run forever in case GDB crashes and DejaGNU fails to kill
+ this program. */
+ alarm (10);
+
+ while (1)
+ ;
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/multi-kill.exp b/gdb/testsuite/gdb.multi/multi-kill.exp
new file mode 100644
index 0000000..ce60750
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/multi-kill.exp
@@ -0,0 +1,127 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2020 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/>.
+
+# Test receiving TARGET_WAITKIND_SIGNALLED events from multiple
+# inferiors. In all stop-mode, upon receiving the exit event from one
+# of the inferiors, GDB will try to stop the other inferior, too. So,
+# a stop request will be sent. Receiving a TARGET_WAITKIND_SIGNALLED
+# status kind as a response to that stop request instead of a
+# TARGET_WAITKIND_STOPPED should be handled by GDB without problems.
+
+standard_testfile
+
+if {[use_gdb_stub]} {
+ return 0
+}
+
+if {[build_executable "failed to prepare" $testfile $srcfile {debug}]} {
+ return -1
+}
+
+# We are testing GDB's ability to stop all threads.
+# Hence, go with the all-stop-on-top-of-non-stop mode.
+save_vars { GDBFLAGS } {
+ append GDBFLAGS " -ex \"maint set target-non-stop on\""
+ clean_restart ${binfile}
+}
+
+# Start inferior NUM and record its PID in the TESTPID array.
+
+proc start_inferior {num} {
+ with_test_prefix "start_inferior $num" {
+ global testpid binfile srcfile
+
+ if {$num != 1} {
+ gdb_test "add-inferior" "Added inferior .*" \
+ "add empty inferior"
+ gdb_test "inferior $num" "Switching to inferior .*" \
+ "switch to inferior"
+ }
+
+ gdb_load $binfile
+
+ gdb_breakpoint "initialized" {temporary}
+ gdb_run_cmd
+ gdb_test "" ".*reakpoint .*, initialized .*${srcfile}.*" "run"
+
+ set testpid($num) [get_integer_valueof "pid" -1]
+ if {$testpid($num) == -1} {
+ return -1
+ }
+
+ return 0
+ }
+}
+
+# Sufficient inferiors to make sure that at least some other inferior
+# is killed while we're handling a killed event.
+set NUM_INFS 10
+
+for {set i 1} {$i <= $NUM_INFS} {incr i} {
+ if {[start_inferior $i] < 0} {
+ return -1
+ }
+}
+
+# We want to continue all processes.
+gdb_test_no_output "set schedule-multiple on"
+
+# Resume, but then kill all from outside.
+gdb_test_multiple "continue" "continue processes" {
+ -re "Continuing.\[\r\n\]+" {
+ # Kill all processes at once.
+
+ set kill_cmd "kill -9"
+ for {set i 1} {$i <= $NUM_INFS} {incr i} {
+ append kill_cmd " $testpid($i)"
+ }
+
+ remote_exec target $kill_cmd
+ exp_continue
+ }
+ -re "Program terminated with signal.*$gdb_prompt $" {
+ pass $gdb_test_name
+ }
+}
+
+# Check that "continue" collects the process kill event, as many times
+# as we have inferiors left.
+
+for {set i 2} {$i <= $NUM_INFS} {incr i} {
+ with_test_prefix "inf $i" {
+ set live_inferior ""
+
+ # Pick any live inferior.
+ gdb_test_multiple "info inferiors" "" {
+ -re "($decimal) *process.*$gdb_prompt $" {
+ set live_inferior $expect_out(1,string)
+ }
+ }
+
+ if {$live_inferior == ""} {
+ return -1
+ }
+
+ gdb_test "inferior $live_inferior" \
+ ".*Switching to inferior $live_inferior.*" \
+ "switch to inferior"
+
+ gdb_test "continue" \
+ "Program terminated with signal SIGKILL, .*" \
+ "continue to SIGKILL"
+ }
+}
diff --git a/gdb/thread.c b/gdb/thread.c
index 03805bd..02672f0 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -2043,7 +2043,7 @@ thread_select (const char *tidstr, thread_info *tp)
/* Since the current thread may have changed, see if there is any
exited thread we can now delete. */
- prune_threads ();
+ delete_exited_threads ();
}
/* Print thread and frame switch command response. */