diff options
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/ChangeLog | 26 | ||||
-rw-r--r-- | gdb/breakpoint.c | 5 | ||||
-rw-r--r-- | gdb/breakpoint.h | 3 | ||||
-rw-r--r-- | gdb/infrun.c | 308 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 5 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c | 67 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.exp | 90 |
7 files changed, 303 insertions, 201 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 7f99a5a..e0a58aa 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,31 @@ 2014-03-20 Pedro Alves <palves@redhat.com> + * breakpoint.c (single_step_breakpoint_inserted_here_p): Make + extern. + * breakpoint.h (single_step_breakpoint_inserted_here_p): Declare. + * infrun.c (saved_singlestep_ptid) + (stepping_past_singlestep_breakpoint): Delete. + (resume): Remove stepping_past_singlestep_breakpoint handling. + (proceed): Store the prev_pc of the stepping thread too. + (init_wait_for_inferior): Adjust. Clear singlestep_ptid and + singlestep_pc. + (enum infwait_states): Delete infwait_thread_hop_state. + (struct execution_control_state) <hit_singlestep_breakpoint>: New + field. + (handle_inferior_event): Adjust. + (handle_signal_stop): Delete stepping_past_singlestep_breakpoint + handling and the thread-hop code. Before removing single-step + breakpoints, check whether the thread hit a single-step breakpoint + of another thread. If it did, the trap is not a random signal. + (switch_back_to_stepped_thread): If the event thread hit a + single-step breakpoint, unblock it before switching to the + stepping thread. Handle the case of the stepped thread having + advanced already. + (keep_going): Handle the case of the current thread moving past a + single-step breakpoint. + +2014-03-20 Pedro Alves <palves@redhat.com> + PR breakpoints/7143 * breakpoint.c (should_be_inserted): Don't insert breakpoints that are being stepped over. diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 03b21cb..19af9df 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -228,9 +228,6 @@ static void tcatch_command (char *arg, int from_tty); static void detach_single_step_breakpoints (void); -static int single_step_breakpoint_inserted_here_p (struct address_space *, - CORE_ADDR pc); - static void free_bp_location (struct bp_location *loc); static void incref_bp_location (struct bp_location *loc); static void decref_bp_location (struct bp_location **loc); @@ -15148,7 +15145,7 @@ detach_single_step_breakpoints (void) /* Check whether a software single-step breakpoint is inserted at PC. */ -static int +int single_step_breakpoint_inserted_here_p (struct address_space *aspace, CORE_ADDR pc) { diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h index b4abcb8..d8e88fc 100644 --- a/gdb/breakpoint.h +++ b/gdb/breakpoint.h @@ -1126,6 +1126,9 @@ extern int regular_breakpoint_inserted_here_p (struct address_space *, extern int software_breakpoint_inserted_here_p (struct address_space *, CORE_ADDR); +extern int single_step_breakpoint_inserted_here_p (struct address_space *, + CORE_ADDR); + /* Returns true if there's a hardware watchpoint or access watchpoint inserted in the range defined by ADDR and LEN. */ extern int hardware_watchpoint_inserted_in_range (struct address_space *, diff --git a/gdb/infrun.c b/gdb/infrun.c index ed8ec30..60abd8c 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -972,11 +972,6 @@ static ptid_t singlestep_ptid; /* PC when we started this single-step. */ static CORE_ADDR singlestep_pc; -/* If another thread hit the singlestep breakpoint, we save the original - thread here so that we can resume single-stepping it later. */ -static ptid_t saved_singlestep_ptid; -static int stepping_past_singlestep_breakpoint; - /* Info about an instruction that is being stepped over. Invalid if ASPACE is NULL. */ @@ -1943,24 +1938,8 @@ a command like `return' or `jump' to continue execution.")); resume_ptid = user_visible_resume_ptid (step); /* Maybe resume a single thread after all. */ - if (singlestep_breakpoints_inserted_p - && stepping_past_singlestep_breakpoint) - { - /* The situation here is as follows. In thread T1 we wanted to - single-step. Lacking hardware single-stepping we've - set breakpoint at the PC of the next instruction -- call it - P. After resuming, we've hit that breakpoint in thread T2. - Now we've removed original breakpoint, inserted breakpoint - at P+1, and try to step to advance T2 past breakpoint. - We need to step only T2, as if T1 is allowed to freely run, - it can run past P, and if other threads are allowed to run, - they can hit breakpoint at P+1, and nested hits of single-step - breakpoints is not something we'd want -- that's complicated - to support, and has no value. */ - resume_ptid = inferior_ptid; - } - else if ((step || singlestep_breakpoints_inserted_p) - && tp->control.trap_expected) + if ((step || singlestep_breakpoints_inserted_p) + && tp->control.trap_expected) { /* We're allowing a thread to run past a breakpoint it has hit, by single-stepping the thread with the breakpoint @@ -2222,6 +2201,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal, int step) gdbarch = get_regcache_arch (regcache); aspace = get_regcache_aspace (regcache); pc = regcache_read_pc (regcache); + tp = inferior_thread (); if (step > 0) step_start_function = find_pc_function (pc); @@ -2278,13 +2258,19 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal, int step) thread that reported the most recent event. If a step-over is required it returns TRUE and sets the current thread to the old thread. */ + + /* Store the prev_pc for the stepping thread too, needed by + switch_back_to_stepping thread. */ + tp->prev_pc = regcache_read_pc (get_current_regcache ()); + if (prepare_to_proceed (step)) - force_step = 1; + { + force_step = 1; + /* The current thread changed. */ + tp = inferior_thread (); + } } - /* prepare_to_proceed may change the current thread. */ - tp = inferior_thread (); - if (force_step) tp->control.trap_expected = 1; @@ -2434,8 +2420,6 @@ init_wait_for_inferior (void) clear_proceed_status (); - stepping_past_singlestep_breakpoint = 0; - target_last_wait_ptid = minus_one_ptid; previous_inferior_ptid = inferior_ptid; @@ -2443,6 +2427,9 @@ init_wait_for_inferior (void) /* Discard any skipped inlined frames. */ clear_inline_frame_state (minus_one_ptid); + + singlestep_ptid = null_ptid; + singlestep_pc = 0; } @@ -2453,7 +2440,6 @@ init_wait_for_inferior (void) enum infwait_states { infwait_normal_state, - infwait_thread_hop_state, infwait_step_watch_state, infwait_nonstep_watch_state }; @@ -2484,6 +2470,12 @@ struct execution_control_state infwait_nonstep_watch_state state, and the thread reported an event. */ int stepped_after_stopped_by_watchpoint; + + /* True if the event thread hit the single-step breakpoint of + another thread. Thus the event doesn't cause a stop, the thread + needs to be single-stepped past the single-step breakpoint before + we can switch back to the original stepping thread. */ + int hit_singlestep_breakpoint; }; static void handle_inferior_event (struct execution_control_state *ecs); @@ -3364,11 +3356,6 @@ handle_inferior_event (struct execution_control_state *ecs) switch (infwait_state) { - case infwait_thread_hop_state: - if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, "infrun: infwait_thread_hop_state\n"); - break; - case infwait_normal_state: if (debug_infrun) fprintf_unfiltered (gdb_stdlog, "infrun: infwait_normal_state\n"); @@ -3937,163 +3924,6 @@ handle_signal_stop (struct execution_control_state *ecs) return; } - if (stepping_past_singlestep_breakpoint) - { - gdb_assert (singlestep_breakpoints_inserted_p); - gdb_assert (ptid_equal (singlestep_ptid, ecs->ptid)); - gdb_assert (!ptid_equal (singlestep_ptid, saved_singlestep_ptid)); - - stepping_past_singlestep_breakpoint = 0; - - /* We've either finished single-stepping past the single-step - breakpoint, or stopped for some other reason. It would be nice if - we could tell, but we can't reliably. */ - if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP) - { - if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, - "infrun: stepping_past_" - "singlestep_breakpoint\n"); - /* Pull the single step breakpoints out of the target. */ - if (!ptid_equal (ecs->ptid, inferior_ptid)) - context_switch (ecs->ptid); - remove_single_step_breakpoints (); - singlestep_breakpoints_inserted_p = 0; - - ecs->event_thread->control.trap_expected = 0; - - context_switch (saved_singlestep_ptid); - if (deprecated_context_hook) - deprecated_context_hook (pid_to_thread_id (saved_singlestep_ptid)); - - resume (1, GDB_SIGNAL_0); - prepare_to_wait (ecs); - return; - } - } - - /* See if a thread hit a thread-specific breakpoint that was meant for - another thread. If so, then step that thread past the breakpoint, - and continue it. */ - - if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP) - { - int thread_hop_needed = 0; - struct address_space *aspace = - get_regcache_aspace (get_thread_regcache (ecs->ptid)); - - /* Check if a regular breakpoint has been hit before checking - for a potential single step breakpoint. Otherwise, GDB will - not see this breakpoint hit when stepping onto breakpoints. */ - if (regular_breakpoint_inserted_here_p (aspace, stop_pc)) - { - if (!breakpoint_thread_match (aspace, stop_pc, ecs->ptid)) - thread_hop_needed = 1; - } - else if (singlestep_breakpoints_inserted_p) - { - /* We have not context switched yet, so this should be true - no matter which thread hit the singlestep breakpoint. */ - gdb_assert (ptid_equal (inferior_ptid, singlestep_ptid)); - if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, "infrun: software single step " - "trap for %s\n", - target_pid_to_str (ecs->ptid)); - - /* The call to in_thread_list is necessary because PTIDs sometimes - change when we go from single-threaded to multi-threaded. If - the singlestep_ptid is still in the list, assume that it is - really different from ecs->ptid. */ - if (!ptid_equal (singlestep_ptid, ecs->ptid) - && in_thread_list (singlestep_ptid)) - { - /* If the PC of the thread we were trying to single-step - has changed, discard this event (which we were going - to ignore anyway), and pretend we saw that thread - trap. This prevents us continuously moving the - single-step breakpoint forward, one instruction at a - time. If the PC has changed, then the thread we were - trying to single-step has trapped or been signalled, - but the event has not been reported to GDB yet. - - There might be some cases where this loses signal - information, if a signal has arrived at exactly the - same time that the PC changed, but this is the best - we can do with the information available. Perhaps we - should arrange to report all events for all threads - when they stop, or to re-poll the remote looking for - this particular thread (i.e. temporarily enable - schedlock). */ - - CORE_ADDR new_singlestep_pc - = regcache_read_pc (get_thread_regcache (singlestep_ptid)); - - if (new_singlestep_pc != singlestep_pc) - { - enum gdb_signal stop_signal; - - if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, "infrun: unexpected thread," - " but expected thread advanced also\n"); - - /* The current context still belongs to - singlestep_ptid. Don't swap here, since that's - the context we want to use. Just fudge our - state and continue. */ - stop_signal = ecs->event_thread->suspend.stop_signal; - ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0; - ecs->ptid = singlestep_ptid; - ecs->event_thread = find_thread_ptid (ecs->ptid); - ecs->event_thread->suspend.stop_signal = stop_signal; - stop_pc = new_singlestep_pc; - } - else - { - if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, - "infrun: unexpected thread\n"); - - thread_hop_needed = 1; - stepping_past_singlestep_breakpoint = 1; - saved_singlestep_ptid = singlestep_ptid; - } - } - } - - if (thread_hop_needed) - { - if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, "infrun: thread_hop_needed\n"); - - /* Switch context before touching inferior memory, the - previous thread may have exited. */ - if (!ptid_equal (inferior_ptid, ecs->ptid)) - context_switch (ecs->ptid); - - /* Saw a breakpoint, but it was hit by the wrong thread. - Just continue. */ - - if (singlestep_breakpoints_inserted_p) - { - /* Pull the single step breakpoints out of the target. */ - remove_single_step_breakpoints (); - singlestep_breakpoints_inserted_p = 0; - } - - if (!non_stop) - { - /* Only need to require the next event from this thread - in all-stop mode. */ - waiton_ptid = ecs->ptid; - infwait_state = infwait_thread_hop_state; - } - - ecs->event_thread->stepping_over_breakpoint = 1; - keep_going (ecs); - return; - } - } - /* See if something interesting happened to the non-current thread. If so, then switch to that thread. */ if (!ptid_equal (ecs->ptid, inferior_ptid)) @@ -4111,9 +3941,36 @@ handle_signal_stop (struct execution_control_state *ecs) frame = get_current_frame (); gdbarch = get_frame_arch (frame); + /* Pull the single step breakpoints out of the target. */ if (singlestep_breakpoints_inserted_p) { - /* Pull the single step breakpoints out of the target. */ + /* However, before doing so, if this single-step breakpoint was + actually for another thread, set this thread up for moving + past it. */ + if (!ptid_equal (ecs->ptid, singlestep_ptid) + && ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP) + { + struct regcache *regcache; + struct address_space *aspace; + CORE_ADDR pc; + + regcache = get_thread_regcache (ecs->ptid); + aspace = get_regcache_aspace (regcache); + pc = regcache_read_pc (regcache); + if (single_step_breakpoint_inserted_here_p (aspace, pc)) + { + if (debug_infrun) + { + fprintf_unfiltered (gdb_stdlog, + "infrun: [%s] hit step over single-step" + " breakpoint of [%s]\n", + target_pid_to_str (ecs->ptid), + target_pid_to_str (singlestep_ptid)); + } + ecs->hit_singlestep_breakpoint = 1; + } + } + remove_single_step_breakpoints (); singlestep_breakpoints_inserted_p = 0; } @@ -4308,6 +4165,12 @@ handle_signal_stop (struct execution_control_state *ecs) random_signal = !(ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP && currently_stepping (ecs->event_thread)); + /* Perhaps the thread hit a single-step breakpoint of _another_ + thread. Single-step breakpoints are transparent to the + breakpoints module. */ + if (random_signal) + random_signal = !ecs->hit_singlestep_breakpoint; + /* No? Perhaps we got a moribund watchpoint. */ if (random_signal) random_signal = !stopped_by_watchpoint; @@ -5271,12 +5134,16 @@ switch_back_to_stepped_thread (struct execution_control_state *ecs) ecs->event_thread); if (tp) { + struct frame_info *frame; + struct gdbarch *gdbarch; + /* However, if the current thread is blocked on some internal breakpoint, and we simply need to step over that breakpoint to get it going again, do that first. */ if ((ecs->event_thread->control.trap_expected && ecs->event_thread->suspend.stop_signal != GDB_SIGNAL_TRAP) - || ecs->event_thread->stepping_over_breakpoint) + || ecs->event_thread->stepping_over_breakpoint + || ecs->hit_singlestep_breakpoint) { keep_going (ecs); return 1; @@ -5326,7 +5193,52 @@ switch_back_to_stepped_thread (struct execution_control_state *ecs) ecs->event_thread = tp; ecs->ptid = tp->ptid; context_switch (ecs->ptid); - keep_going (ecs); + + stop_pc = regcache_read_pc (get_thread_regcache (ecs->ptid)); + frame = get_current_frame (); + gdbarch = get_frame_arch (frame); + + /* If the PC of the thread we were trying to single-step has + changed, then the thread we were trying to single-step + has trapped or been signalled, but the event has not been + reported to GDB yet. Re-poll the remote looking for this + particular thread (i.e. temporarily enable schedlock) by: + + - setting a break at the current PC + - resuming that particular thread, only (by setting + trap expected) + + This prevents us continuously moving the single-step + breakpoint forward, one instruction at a time, + overstepping. */ + + if (gdbarch_software_single_step_p (gdbarch) + && stop_pc != tp->prev_pc) + { + if (debug_infrun) + fprintf_unfiltered (gdb_stdlog, + "infrun: expected thread advanced also\n"); + + insert_single_step_breakpoint (get_frame_arch (frame), + get_frame_address_space (frame), + stop_pc); + singlestep_breakpoints_inserted_p = 1; + ecs->event_thread->control.trap_expected = 1; + singlestep_ptid = inferior_ptid; + singlestep_pc = stop_pc; + + resume (0, GDB_SIGNAL_0); + prepare_to_wait (ecs); + } + else + { + if (debug_infrun) + fprintf_unfiltered (gdb_stdlog, + "infrun: expected thread still " + "hasn't advanced\n"); + keep_going (ecs); + } + return 1; } } @@ -5799,7 +5711,8 @@ keep_going (struct execution_control_state *ecs) (watchpoints, etc.) but the one we're stepping over, step one instruction, and then re-insert the breakpoint when that step is finished. */ - if (ecs->event_thread->stepping_over_breakpoint + if ((ecs->hit_singlestep_breakpoint + || ecs->event_thread->stepping_over_breakpoint) && !use_displaced_stepping (get_regcache_arch (regcache))) { set_step_over_info (get_regcache_aspace (regcache), @@ -5821,7 +5734,8 @@ keep_going (struct execution_control_state *ecs) } ecs->event_thread->control.trap_expected - = ecs->event_thread->stepping_over_breakpoint; + = (ecs->event_thread->stepping_over_breakpoint + || ecs->hit_singlestep_breakpoint); /* Do not deliver GDB_SIGNAL_TRAP (except when the user explicitly specifies that such a signal should be delivered diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index ee71b746..8537359 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,5 +1,10 @@ 2014-03-20 Pedro Alves <palves@redhat.com> + * gdb.threads/step-over-trips-on-watchpoint.c: New file. + * gdb.threads/step-over-trips-on-watchpoint.exp: New file. + +2014-03-20 Pedro Alves <palves@redhat.com> + PR breakpoints/7143 * gdb.base/watchpoint.exp: Mention bugzilla bug number instead of old gnats gdb/38. Remove kfail. Adjust to use gdb_test instead diff --git a/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c b/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c new file mode 100644 index 0000000..4e8ca9c --- /dev/null +++ b/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c @@ -0,0 +1,67 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2014 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> + +pthread_barrier_t barrier; +pthread_t child_thread; + +volatile unsigned int counter = 1; +volatile unsigned int watch_me; +volatile unsigned int other; + +void * +child_function (void *arg) +{ + pthread_barrier_wait (&barrier); + + while (counter > 0) + { + counter++; + + watch_me = 1; /* set breakpoint child here */ + other = 1; /* set thread-specific breakpoint here */ + usleep (1); + } + + pthread_exit (NULL); +} + +static int +wait_threads (void) +{ + return 1; /* in wait_threads */ +} + +int +main () +{ + int res; + long i; + + pthread_barrier_init (&barrier, NULL, 2); + + res = pthread_create (&child_thread, NULL, child_function, NULL); + pthread_barrier_wait (&barrier); + wait_threads (); /* set wait-thread breakpoint here */ + + pthread_join (child_thread, NULL); + + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.exp b/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.exp new file mode 100644 index 0000000..c6c58cc --- /dev/null +++ b/gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.exp @@ -0,0 +1,90 @@ +# Copyright (C) 2014 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 that when a step-over trips on a watchpoint, that watchpoint is +# reported. + +standard_testfile +set executable ${testfile} + +# This test verifies that a watchpoint is detected in a multithreaded +# program so the test is only meaningful on a system with hardware +# watchpoints. +if {[skip_hw_watchpoint_tests]} { + return 0 +} + +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" \ + executable [list debug "incdir=${objdir}"]] != "" } { + return -1 +} + +proc do_test { with_bp } { + global executable + + if ${with_bp} { + set prefix "with thread-specific bp" + } else { + set prefix "no thread-specific bp" + } + with_test_prefix $prefix { + # Cover both stepping and non-stepping execution commands. + foreach command {"step" "next" "continue" } { + with_test_prefix $command { + clean_restart $executable + + if ![runto_main] { + continue + } + + gdb_breakpoint [gdb_get_line_number "set wait-thread breakpoint here"] + gdb_continue_to_breakpoint "run to wait-thread breakpoint" + gdb_test "info threads" "2 .*\\\* 1.*" "info threads shows all threads" + + gdb_test_no_output "set scheduler-locking on" + + delete_breakpoints + + gdb_breakpoint [gdb_get_line_number "set breakpoint child here"] + gdb_test "thread 2" "Switching to .*" + gdb_continue_to_breakpoint "run to breakpoint in thread 2" + gdb_test "p counter = 0" " = 0" "unbreak loop in thread 2" + gdb_test "p watch_me = 0" " = 0" "clear watch_me" + gdb_test "watch watch_me" "Hardware watchpoint .*" + + if ${with_bp} { + # Set a thread-specific breakpoint (for the wrong + # thread) right after instruction that triggers + # the watchpoint. + set linenum [gdb_get_line_number "set thread-specific breakpoint here"] + gdb_test "b $linenum thread 1" + } + + # Switch back to thread 1 and disable scheduler locking. + gdb_test "thread 1" "Switching to .*" + gdb_test_no_output "set scheduler-locking off" + + # Thread 2 is still stopped at a breakpoint that needs to be + # stepped over before proceeding thread 1. However, right + # where the step-over lands there's another breakpoint + # installed, which should trap and be reported to the user. + gdb_test "$command" "Hardware watchpoint.*: watch_me.*New value = 1.*" + } + } + } +} + +do_test 0 +do_test 1 |