From 2adfaa28b5ba2fb78ba5113977082c4d04752bd6 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Thu, 20 Mar 2014 13:26:32 +0000 Subject: [PATCH] Fix for even more missed events; eliminate thread-hop code. Even with deferred_step_ptid out of the way, GDB can still lose watchpoints. If a watchpoint triggers and the PC points to an address where a thread-specific breakpoint for another thread is set, the thread-hop code triggers, and we lose the watchpoint: 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; ^^^^^^^^^^^^^^^^^^^^^ } And on software single-step targets, even without a thread-specific breakpoint in the way, here in the thread-hop code: else if (singlestep_breakpoints_inserted_p) { ... 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; } } } we either end up with thread_hop_needed, ignoring the watchpoint SIGTRAP, or switch to the stepping thread, again ignoring that the SIGTRAP could be for some other event. The new test added by this patch exercises both paths. So the fix is similar to the deferred_step_ptid fix -- defer the thread hop to _after_ the SIGTRAP had a change of passing through the regular bpstat handling. If the wrong thread hits a breakpoint, we'll just end up with BPSTAT_WHAT_SINGLE, and if nothing causes a stop, keep_going starts a step-over. Most of the stepping_past_singlestep_breakpoint mechanism is really not necessary -- setting the thread to step over a breakpoint with thread->trap_expected is sufficient to keep all other threads locked. It's best to still keep the flag in some form though, because when we get to keep_going, the software single-step breakpoint we need to step over is already gone -- an optimization done by a follow up patch will check whether a step-over is still be necessary by looking to see whether the breakpoint is still there, and would find the thread no longer needs a step-over, while we still want it. Special care is still needed to handle the case of PC of the thread we were trying to single-step having changed, like in the old code. We can't just keep_going and re-step it, as in that case we can over-step the thread (if it was already done with the step, but hasn't reported it yet, we'd ask it to step even further). That's now handled in switch_back_to_stepped_thread. As bonus, we're now using a technique that doesn't lose signals, unlike the old code -- we now insert a breakpoint at PC, and resume, which either reports the breakpoint immediately, or any pending signal. Tested on x86_64 Fedora 17, against pristine mainline, and against a branch that implements software single-step on x86. gdb/ 2014-03-20 Pedro Alves * 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) : 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. gdb/testsuite/ 2014-03-20 Pedro Alves * gdb.threads/step-over-trips-on-watchpoint.c: New file. * gdb.threads/step-over-trips-on-watchpoint.exp: New file. --- gdb/ChangeLog | 26 ++ gdb/breakpoint.c | 5 +- gdb/breakpoint.h | 3 + gdb/infrun.c | 308 +++++++----------- gdb/testsuite/ChangeLog | 5 + .../step-over-trips-on-watchpoint.c | 67 ++++ .../step-over-trips-on-watchpoint.exp | 90 +++++ 7 files changed, 303 insertions(+), 201 deletions(-) create mode 100644 gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c create mode 100644 gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.exp diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 7f99a5a0b3..e0a58aae63 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,29 @@ +2014-03-20 Pedro Alves + + * 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) : 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 PR breakpoints/7143 diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 03b21cbf21..19af9df6d4 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 b4abcb864c..d8e88fc26b 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 ed8ec302df..60abd8ca66 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 ee71b7462f..8537359689 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2014-03-20 Pedro Alves + + * 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 PR breakpoints/7143 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 0000000000..4e8ca9c0ef --- /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 . */ + +#include +#include +#include + +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 0000000000..c6c58ccd99 --- /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 . + +# 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 -- 2.34.1