Fix for even more missed events; eliminate thread-hop code.
authorPedro Alves <palves@redhat.com>
Thu, 20 Mar 2014 13:26:32 +0000 (13:26 +0000)
committerPedro Alves <palves@redhat.com>
Thu, 20 Mar 2014 13:42:23 +0000 (13:42 +0000)
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  <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.

gdb/testsuite/
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.

gdb/ChangeLog
gdb/breakpoint.c
gdb/breakpoint.h
gdb/infrun.c
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/step-over-trips-on-watchpoint.exp [new file with mode: 0644]

index 7f99a5a0b385f11cfb078b38bf14beccbc72b97e..e0a58aae63a89440686b2d289a1100a12e2619b8 100644 (file)
@@ -1,3 +1,29 @@
+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
index 03b21cbf217dffbf7be936b33cc60da4aae8d514..19af9df6d4d13f062b4053ba56d8bbb8a58f2a2b 100644 (file)
@@ -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)
 {
index b4abcb864c5f83b0960d6b383f90e934c0d8b862..d8e88fc26b2488bed88a766b589e928ec110416d 100644 (file)
@@ -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 *,
index ed8ec302df41c2ea8bb469c0d95585002147cdf2..60abd8ca663b6905d73985132d84b6961870e1c8 100644 (file)
@@ -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;
 }
 
 \f
@@ -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
index ee71b7462fe271dbbc0dcd80f4a2a7cfeb6d665b..85373596899c27757634bdf1cc07a6b4a8233d90 100644 (file)
@@ -1,3 +1,8 @@
+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
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 (file)
index 0000000..4e8ca9c
--- /dev/null
@@ -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 (file)
index 0000000..c6c58cc
--- /dev/null
@@ -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