aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/infrun.c149
-rw-r--r--gdb/testsuite/gdb.threads/interrupt-while-step-over.c75
-rw-r--r--gdb/testsuite/gdb.threads/interrupt-while-step-over.exp202
-rw-r--r--gdb/testsuite/gdb.threads/signal-while-stepping-over-bp-other-thread.exp1
4 files changed, 333 insertions, 94 deletions
diff --git a/gdb/infrun.c b/gdb/infrun.c
index bf0632e..2efbd51 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2081,6 +2081,8 @@ start_step_over (void)
step_over_what step_what;
int must_be_in_line;
+ gdb_assert (!tp->stop_requested);
+
next = thread_step_over_chain_next (tp);
/* If this inferior already has a displaced step in process,
@@ -2329,6 +2331,8 @@ do_target_resume (ptid_t resume_ptid, int step, enum gdb_signal sig)
{
struct thread_info *tp = inferior_thread ();
+ gdb_assert (!tp->stop_requested);
+
/* Install inferior's terminal modes. */
target_terminal_inferior ();
@@ -2393,6 +2397,7 @@ resume (enum gdb_signal sig)
single-step). */
int step;
+ gdb_assert (!tp->stop_requested);
gdb_assert (!thread_is_in_step_over_chain (tp));
QUIT;
@@ -3274,65 +3279,6 @@ static void keep_going (struct execution_control_state *ecs);
static void process_event_stop_test (struct execution_control_state *ecs);
static int switch_back_to_stepped_thread (struct execution_control_state *ecs);
-/* Callback for iterate over threads. If the thread is stopped, but
- the user/frontend doesn't know about that yet, go through
- normal_stop, as if the thread had just stopped now. ARG points at
- a ptid. If PTID is MINUS_ONE_PTID, applies to all threads. If
- ptid_is_pid(PTID) is true, applies to all threads of the process
- pointed at by PTID. Otherwise, apply only to the thread pointed by
- PTID. */
-
-static int
-infrun_thread_stop_requested_callback (struct thread_info *info, void *arg)
-{
- ptid_t ptid = * (ptid_t *) arg;
-
- if ((ptid_equal (info->ptid, ptid)
- || ptid_equal (minus_one_ptid, ptid)
- || (ptid_is_pid (ptid)
- && ptid_get_pid (ptid) == ptid_get_pid (info->ptid)))
- && is_running (info->ptid)
- && !is_executing (info->ptid))
- {
- struct cleanup *old_chain;
- struct execution_control_state ecss;
- struct execution_control_state *ecs = &ecss;
-
- memset (ecs, 0, sizeof (*ecs));
-
- old_chain = make_cleanup_restore_current_thread ();
-
- overlay_cache_invalid = 1;
- /* Flush target cache before starting to handle each event.
- Target was running and cache could be stale. This is just a
- heuristic. Running threads may modify target memory, but we
- don't get any event. */
- target_dcache_invalidate ();
-
- /* Go through handle_inferior_event/normal_stop, so we always
- have consistent output as if the stop event had been
- reported. */
- ecs->ptid = info->ptid;
- ecs->event_thread = info;
- ecs->ws.kind = TARGET_WAITKIND_STOPPED;
- ecs->ws.value.sig = GDB_SIGNAL_0;
-
- handle_inferior_event (ecs);
-
- if (!ecs->wait_some_more)
- {
- /* Cancel any running execution command. */
- thread_cancel_execution_command (info);
-
- normal_stop ();
- }
-
- do_cleanups (old_chain);
- }
-
- return 0;
-}
-
/* This function is attached as a "thread_stop_requested" observer.
Cleanup local state that assumed the PTID was to be resumed, and
report the stop to the frontend. */
@@ -3342,17 +3288,51 @@ infrun_thread_stop_requested (ptid_t ptid)
{
struct thread_info *tp;
- /* PTID was requested to stop. Remove matching threads from the
- step-over queue, so we don't try to resume them
- automatically. */
+ /* PTID was requested to stop. If the thread was already stopped,
+ but the user/frontend doesn't know about that yet (e.g., the
+ thread had been temporarily paused for some step-over), set up
+ for reporting the stop now. */
ALL_NON_EXITED_THREADS (tp)
if (ptid_match (tp->ptid, ptid))
{
+ if (tp->state != THREAD_RUNNING)
+ continue;
+ if (tp->executing)
+ continue;
+
+ /* Remove matching threads from the step-over queue, so
+ start_step_over doesn't try to resume them
+ automatically. */
if (thread_is_in_step_over_chain (tp))
thread_step_over_chain_remove (tp);
- }
- iterate_over_threads (infrun_thread_stop_requested_callback, &ptid);
+ /* If the thread is stopped, but the user/frontend doesn't
+ know about that yet, queue a pending event, as if the
+ thread had just stopped now. Unless the thread already had
+ a pending event. */
+ if (!tp->suspend.waitstatus_pending_p)
+ {
+ tp->suspend.waitstatus_pending_p = 1;
+ tp->suspend.waitstatus.kind = TARGET_WAITKIND_STOPPED;
+ tp->suspend.waitstatus.value.sig = GDB_SIGNAL_0;
+ }
+
+ /* Clear the inline-frame state, since we're re-processing the
+ stop. */
+ clear_inline_frame_state (tp->ptid);
+
+ /* If this thread was paused because some other thread was
+ doing an inline-step over, let that finish first. Once
+ that happens, we'll restart all threads and consume pending
+ stop events then. */
+ if (step_over_info_valid_p ())
+ continue;
+
+ /* Otherwise we can process the (new) pending event now. Set
+ it so this pending event is considered by
+ do_target_wait. */
+ tp->resumed = 1;
+ }
}
static void
@@ -5480,6 +5460,8 @@ restart_threads (struct thread_info *event_thread)
continue;
}
+ gdb_assert (!tp->stop_requested);
+
/* If some thread needs to start a step-over at this point, it
should still be in the step-over queue, and thus skipped
above. */
@@ -5549,8 +5531,7 @@ finish_step_over (struct execution_control_state *ecs)
back an event. */
gdb_assert (ecs->event_thread->control.trap_expected);
- if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP)
- clear_step_over_info ();
+ clear_step_over_info ();
}
if (!target_is_non_stop_p ())
@@ -6051,6 +6032,15 @@ handle_signal_stop (struct execution_control_state *ecs)
if (random_signal)
random_signal = !stopped_by_watchpoint;
+ /* Always stop if the user explicitly requested this thread to
+ remain stopped. */
+ if (ecs->event_thread->stop_requested)
+ {
+ random_signal = 1;
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: user-requested stop\n");
+ }
+
/* For the program's own signals, act according to
the signal handling tables. */
@@ -6097,8 +6087,6 @@ handle_signal_stop (struct execution_control_state *ecs)
&& ecs->event_thread->control.trap_expected
&& ecs->event_thread->control.step_resume_breakpoint == NULL)
{
- int was_in_line;
-
/* We were just starting a new sequence, attempting to
single-step off of a breakpoint and expecting a SIGTRAP.
Instead this signal arrives. This signal will take us out
@@ -6114,34 +6102,11 @@ handle_signal_stop (struct execution_control_state *ecs)
"infrun: signal arrived while stepping over "
"breakpoint\n");
- was_in_line = step_over_info_valid_p ();
- clear_step_over_info ();
insert_hp_step_resume_breakpoint_at_frame (frame);
ecs->event_thread->step_after_step_resume_breakpoint = 1;
/* Reset trap_expected to ensure breakpoints are re-inserted. */
ecs->event_thread->control.trap_expected = 0;
- if (target_is_non_stop_p ())
- {
- /* Either "set non-stop" is "on", or the target is
- always in non-stop mode. In this case, we have a bit
- more work to do. Resume the current thread, and if
- we had paused all threads, restart them while the
- signal handler runs. */
- keep_going (ecs);
-
- if (was_in_line)
- {
- restart_threads (ecs->event_thread);
- }
- else if (debug_infrun)
- {
- fprintf_unfiltered (gdb_stdlog,
- "infrun: no need to restart threads\n");
- }
- return;
- }
-
/* If we were nexting/stepping some other thread, switch to
it, so that we don't continue it, losing control. */
if (!switch_back_to_stepped_thread (ecs))
@@ -7682,8 +7647,6 @@ stop_waiting (struct execution_control_state *ecs)
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog, "infrun: stop_waiting\n");
- clear_step_over_info ();
-
/* Let callers know we don't want to wait for the inferior anymore. */
ecs->wait_some_more = 0;
diff --git a/gdb/testsuite/gdb.threads/interrupt-while-step-over.c b/gdb/testsuite/gdb.threads/interrupt-while-step-over.c
new file mode 100644
index 0000000..eeb81d4
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/interrupt-while-step-over.c
@@ -0,0 +1,75 @@
+/* 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>
+
+#define NUM_THREADS 20
+const int num_threads = NUM_THREADS;
+
+static pthread_t child_thread[NUM_THREADS];
+
+static pthread_barrier_t threads_started_barrier;
+
+volatile int always_zero;
+volatile unsigned int dummy;
+
+static void
+infinite_loop (void)
+{
+ while (1)
+ {
+ dummy++; /* set breakpoint here */
+ }
+}
+
+static void *
+child_function (void *arg)
+{
+ pthread_barrier_wait (&threads_started_barrier);
+
+ infinite_loop ();
+}
+
+void
+all_started (void)
+{
+}
+
+int
+main (void)
+{
+ int res;
+ int i;
+
+ alarm (300);
+
+ pthread_barrier_init (&threads_started_barrier, NULL, NUM_THREADS + 1);
+
+ for (i = 0; i < NUM_THREADS; i++)
+ {
+ res = pthread_create (&child_thread[i], NULL, child_function, NULL);
+ }
+
+ /* Wait until all threads have been scheduled. */
+ pthread_barrier_wait (&threads_started_barrier);
+
+ all_started ();
+
+ infinite_loop ();
+}
diff --git a/gdb/testsuite/gdb.threads/interrupt-while-step-over.exp b/gdb/testsuite/gdb.threads/interrupt-while-step-over.exp
new file mode 100644
index 0000000..4407eb1
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/interrupt-while-step-over.exp
@@ -0,0 +1,202 @@
+# Copyright (C) 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/>.
+
+# Regression test for PR gdb/18360. Test that "interrupt -a" while
+# some thread is stepping over a breakpoint behaves as expected.
+
+standard_testfile
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+ { debug pthreads }] == -1} {
+ return -1
+}
+
+# Read the number of threads out of the inferior.
+if ![runto_main] {
+ return -1
+}
+set NUM_THREADS [get_integer_valueof "num_threads" -1]
+
+# Account for the main thread.
+incr NUM_THREADS
+
+# Run command and wait for the prompt, without end anchor.
+
+proc gdb_test_no_anchor {cmd} {
+ global gdb_prompt
+
+ gdb_test_multiple $cmd $cmd {
+ -re "$gdb_prompt " {
+ pass $cmd
+ }
+ -re "infrun:" {
+ exp_continue
+ }
+ }
+}
+
+# Enable/disable debugging.
+
+proc enable_debug {enable} {
+
+ # Comment out to debug problems with the test.
+ return
+
+ gdb_test_no_anchor "set debug infrun $enable"
+ gdb_test_no_anchor "set debug displaced $enable"
+}
+
+# If RESULT is not zero, make the caller return RESULT.
+
+proc return_if_nonzero { result } {
+ if {$result != 0} {
+ return -code return $result
+ }
+}
+
+# Do one iteration of "c -a& -> interrupt -a". Return zero on sucess,
+# and non-zero if some test fails.
+
+proc test_one_iteration {} {
+ global gdb_prompt
+ global NUM_THREADS
+ global decimal
+
+ set saw_continuing 0
+ set test "continue -a &"
+ return_if_nonzero [gdb_test_multiple $test $test {
+ -re "Continuing.\r\n" {
+ set saw_continuing 1
+ exp_continue
+ }
+ -re "$gdb_prompt " {
+ if ![gdb_assert $saw_continuing $test] {
+ return 1
+ }
+ }
+ -re "infrun:" {
+ exp_continue
+ }
+ }]
+
+ set running_count 0
+ set test "all threads are running"
+ return_if_nonzero [gdb_test_multiple "info threads" $test {
+ -re "Thread \[^\r\n\]* \\(running\\)" {
+ incr running_count
+ exp_continue
+ }
+ -re "$gdb_prompt " {
+ if ![gdb_assert {$running_count == $NUM_THREADS} $test] {
+ return 1
+ }
+ }
+ -re "infrun:" {
+ exp_continue
+ }
+ }]
+
+ set test "interrupt -a"
+ return_if_nonzero [gdb_test_multiple $test $test {
+ -re "$gdb_prompt " {
+ pass $test
+ }
+ -re "infrun:" {
+ exp_continue
+ }
+ }]
+
+ set stopped_count 0
+ set test "wait for stops"
+ # Don't return on failure here, in order to let "info threads" put
+ # useful info in gdb.log.
+ gdb_test_multiple "" $test {
+ -re "Thread $decimal \[^\r\n\]*stopped" {
+ incr stopped_count
+ if {$stopped_count != $NUM_THREADS} {
+ exp_continue
+ }
+ }
+ -re "$gdb_prompt " {
+ gdb_assert {$stopped_count == $NUM_THREADS} $test
+ }
+ -re "infrun:" {
+ exp_continue
+ }
+ }
+
+ # Check if all threads are seen as stopped with "info
+ # threads". It's a bit redundant with the test above, but
+ # it's useful to have this in the gdb.log if the above ever
+ # happens to fail.
+ set running_count 0
+ set test "all threads are stopped"
+ return_if_nonzero [gdb_test_multiple "info threads" $test {
+ -re "Thread \[^\r\n\]* \\(running\\)" {
+ incr running_count
+ exp_continue
+ }
+ -re "$gdb_prompt " {
+ if ![gdb_assert {$running_count == 0} $test] {
+ return 1
+ }
+ }
+ }]
+
+ return 0
+}
+
+# The test driver proper. If DISPLACED is "on", turn on displaced
+# stepping. If "off", turn it off.
+
+proc testdriver {displaced} {
+ global binfile
+ global GDBFLAGS
+
+ save_vars { GDBFLAGS } {
+ append GDBFLAGS " -ex \"set non-stop on\""
+ clean_restart $binfile
+ }
+
+ gdb_test_no_output "set displaced-stepping $displaced"
+
+ if ![runto all_started] {
+ fail "couldn't run to all_started"
+ return
+ }
+ set break_line [gdb_get_line_number "set breakpoint here"]
+
+ gdb_test "break $break_line if always_zero" "Breakpoint .*" "set breakpoint"
+
+ enable_debug 1
+
+ for {set iter 0} {$iter < 20} {incr iter} {
+ with_test_prefix "iter=$iter" {
+ # Return early if some test fails, to avoid cascading
+ # timeouts if something goes wrong.
+ if {[test_one_iteration] != 0} {
+ return
+ }
+ }
+ }
+}
+
+foreach_with_prefix displaced-stepping {"on" "off"} {
+ if { ${displaced-stepping} != "off" && ![support_displaced_stepping] } {
+ continue
+ }
+
+ testdriver ${displaced-stepping}
+}
diff --git a/gdb/testsuite/gdb.threads/signal-while-stepping-over-bp-other-thread.exp b/gdb/testsuite/gdb.threads/signal-while-stepping-over-bp-other-thread.exp
index 9e73015..ef894c2 100644
--- a/gdb/testsuite/gdb.threads/signal-while-stepping-over-bp-other-thread.exp
+++ b/gdb/testsuite/gdb.threads/signal-while-stepping-over-bp-other-thread.exp
@@ -100,7 +100,6 @@ gdb_test_sequence $test $test {
"need to step-over"
"resume \\(step=1"
"signal arrived while stepping over breakpoint"
- "(restart threads|switching back to stepped thread)"
"stepped to a different line"
"callme"
}