aboutsummaryrefslogtreecommitdiff
path: root/gdb/infrun.c
diff options
context:
space:
mode:
authorPedro Alves <palves@redhat.com>2014-03-20 13:26:32 +0000
committerPedro Alves <palves@redhat.com>2014-03-20 13:42:23 +0000
commit2adfaa28b5ba2fb78ba5113977082c4d04752bd6 (patch)
treef590227ff8fa056cfbe079c76334a4cbbe68ec05 /gdb/infrun.c
parent31e77af205cf6564c2bf4c18400b4ca16bdf92cd (diff)
downloadgdb-2adfaa28b5ba2fb78ba5113977082c4d04752bd6.zip
gdb-2adfaa28b5ba2fb78ba5113977082c4d04752bd6.tar.gz
gdb-2adfaa28b5ba2fb78ba5113977082c4d04752bd6.tar.bz2
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 <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.
Diffstat (limited to 'gdb/infrun.c')
-rw-r--r--gdb/infrun.c308
1 files changed, 111 insertions, 197 deletions
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