* Teach gdbserver to step over internal breakpoints
@ 2010-03-16 18:40 Pedro Alves
2010-03-24 0:05 ` Pedro Alves
0 siblings, 1 reply; 6+ messages in thread
From: Pedro Alves @ 2010-03-16 18:40 UTC (permalink / raw)
To: gdb-patches
GDBserver has some bits to step over internal breakpoints (used for
the thread event breakpoint), but, it assumes it is fine to
miss some hits: It removes the breakpoint, steps the thread that
hit it, and then reinserts. This, while other threads may pass
by the breakpoint location.
This isn't good for tracepoints, which will be a new use for
internal gdbserver breakpoints. Obviously, we don't want
threads miss hitting tracepoints.
So, this patch teaches gdbserver to step over breakpoints
by doing the well known
- stop-all-threads
- remove breakpoint
- single-step
- insert all breakpoints
- resume all threads again
dance.
I've made a large effort in keeping this patch strictly
minimal disentabled from the tracepoints support itself.
For those, we'll want to teach linux gdbserver to support
Z0 breakpoints supported by memory breakpoints. This is
so gdbserver knows traps for GDB breakpoints set at the
same address as gdbserver internal breakpoints shall be
reported to GDB. They aren't presently. So expect
further changes to mem-break.c soon.
There are bits of this patch that I'll probably move to
common code later, like the last_resume_kind flag.
One change the patch does, is to switch to using
linux-nat.c's mechanism of canceling breakpoints,
instead of leaving them pending. This simplifies
much the corner case handing of the tracepoints support, and,
actually fixes one current bug: presently, in all-stop, if
you do "info threads", in a multi-threaded program, and
there's a thread with a pending breakpoint, you'll see
that it has an unadjusted PC, pointing into the middle
of an instruction, which should never happen.
With sufficient unluck, if the last instruction of the
function has a breakpoint, and that instruction is 1-byte
long, the PC could appear pointing at a different function.
This tests cleanly on x86_64-linux, native gdbserver, with
a small hack to force it to use the thread event breakpoint.
I'll confirm this doesn't break software-single-step archs
before commiting, although I don't expect to, since I had
tested an earlier version of this patch on ARM.
--
Pedro Alves
2010-03-16 Pedro Alves <pedro@codesourcery.com>
gdb/gdbserver/
* linux-low.c (can_hardware_single_step): New.
(supports_breakpoints): New.
(handle_extended_wait): If stopping threads, read the stop pc of
the new cloned LWP.
(get_pc): New.
(get_stop_pc): Add `lwp' parameter. Handle it. Bail out if the
low target doesn't support retrieving the PC.
(add_lwp): Set last_resume_kind to resume_continue.
(linux_attach_lwp_1): Adjust comments. Always set stop_expected.
(linux_attach): Don't clear stop_expected. Set the lwp's
last_resume_kind to resume_stop.
(linux_detach_one_lwp): Don't check for removed breakpoints.
(check_removed_breakpoint): Delete.
(status_pending_p): Rename to ...
(status_pending_p_callback): ... this. Don't check for removed
breakpoints. Don't consider threads that are stopped from GDB's
perspective.
(linux_wait_for_lwp): Always read the stop_pc here.
(cancel_breakpoint): New.
(step_over_bkpt): New global.
(linux_wait_for_event_1): Implement stepping over breakpoints.
(gdb_wants_lwp_stopped): New.
(gdb_wants_all_stopped): New.
(linux_wait_1): Tag threads as gdb-wants-stopped. Cancel finished
single-step traps here. Store the thread's last reported target
wait status.
(send_sigstop): Don't clear stop_expected. Always set it,
instead.
(mark_lwp_dead): Remove reference to pending_is_breakpoint.
(cancel_finished_single_step): New.
(cancel_finished_single_steps): New.
(wait_for_sigstop): Don't cancel finished single-step traps here.
(linux_resume_one_lwp): Don't check for removed breakpoints.
Don't set `step' on non-hardware step archs.
(linux_set_resume_request): Ignore resume_stop requests if already
stopping or stopped. Set the lwp's last_resume_kind.
(resume_status_pending_p): Don't check for removed breakpoints.
(need_step_over_p): New.
(start_step_over): New.
(finish_step_over): New.
(linux_resume_one_thread): Always queue a sigstop for resume_stop
requests. Clear the thread's last reported target waitstatus.
Don't use the `suspended' flag. Don't consider pending breakpoints.
(linux_resume): Start a step-over if necessary.
(proceed_one_lwp): New.
(proceed_all_lwps): New.
(unstop_all_lwps): New.
* linux-low.h (struct lwp_info): Rewrite comment for the
`suspended' flag. Add the `stop_pc' field. Delete the
`pending_stop_pc' field. Tweak the `stepping' flag's comment.
Add `'last_resume_kind' and `need_step_over' fields.
* inferiors.c (struct thread_info): Delete, moved elsewhere.
* mem-break.c (struct breakpoint): Delete `reinserting' flag.
Delete `breakpoint_to_reinsert' field. New flag `inserted'.
(set_raw_breakpoint_at): New.
(set_breakpoint_at): Rewrite to use it.
(reinsert_breakpoint_handler): Delete.
(set_reinsert_breakpoint): New.
(reinsert_breakpoint_by_bp): Delete.
(delete_reinsert_breakpoints): New.
(uninsert_breakpoint): Rewrite.
(uninsert_breakpoints_at): New.
(reinsert_breakpoint): Rewrite.
(reinsert_breakpoints_at): New.
(check_breakpoints): Rewrite.
(breakpoint_here): New.
(breakpoint_inserted_here): New.
(check_mem_read): Adjust.
* mem-break.h (breakpoints_supported, breakpoint_here)
(breakpoint_inserted_here, set_reinsert_breakpoint): Declare.
(reinsert_breakpoint_by_bp): Delete declaration.
(delete_reinsert_breakpoints): Declare.
(reinsert_breakpoint): Delete declaration.
(reinsert_breakpoints_at): Declare.
(uninsert_breakpoint): Delete declaration.
(uninsert_breakpoints_at): Declare.
(check_breakpoints): Adjust prototype.
* server.h: Adjust include order.
(struct thread_info): Declare here. Add a `last_status' field.
---
gdb/gdbserver/inferiors.c | 8
gdb/gdbserver/linux-low.c | 1288 ++++++++++++++++++++++++++++++++++------------
gdb/gdbserver/linux-low.h | 23
gdb/gdbserver/mem-break.c | 263 ++++++---
gdb/gdbserver/mem-break.h | 35 -
gdb/gdbserver/server.h | 25
6 files changed, 1202 insertions(+), 440 deletions(-)
Index: src/gdb/gdbserver/linux-low.c
===================================================================
--- src.orig/gdb/gdbserver/linux-low.c 2010-03-16 17:46:47.000000000 +0000
+++ src/gdb/gdbserver/linux-low.c 2010-03-16 18:22:16.000000000 +0000
@@ -140,11 +140,34 @@ static void linux_resume_one_lwp (struct
static void linux_resume (struct thread_resume *resume_info, size_t n);
static void stop_all_lwps (void);
static int linux_wait_for_event (ptid_t ptid, int *wstat, int options);
-static int check_removed_breakpoint (struct lwp_info *event_child);
static void *add_lwp (ptid_t ptid);
static int linux_stopped_by_watchpoint (void);
static void mark_lwp_dead (struct lwp_info *lwp, int wstat);
static int linux_core_of_thread (ptid_t ptid);
+static void proceed_all_lwps (void);
+static void unstop_all_lwps (struct lwp_info *except);
+static void cancel_finished_single_steps (struct lwp_info *except);
+static int finish_step_over (struct lwp_info *lwp);
+static CORE_ADDR get_stop_pc (struct lwp_info *lwp);
+static int kill_lwp (unsigned long lwpid, int signo);
+
+/* True if the low target can hardware single-step. Such targets
+ don't need a BREAKPOINT_REINSERT_ADDR callback. */
+
+static int
+can_hardware_single_step (void)
+{
+ return (the_low_target.breakpoint_reinsert_addr == NULL);
+}
+
+/* True if the low target supports memory breakpoints. If so, we'll
+ have a GET_PC implementation. */
+
+static int
+supports_breakpoints (void)
+{
+ return (the_low_target.get_pc != NULL);
+}
struct pending_signals
{
@@ -403,14 +426,18 @@ handle_extended_wait (struct lwp_info *e
If we do get another signal, be sure not to lose it. */
if (WSTOPSIG (status) == SIGSTOP)
{
- if (! stopping_threads)
+ if (stopping_threads)
+ new_lwp->stop_pc = get_stop_pc (new_lwp);
+ else
linux_resume_one_lwp (new_lwp, 0, 0, NULL);
}
else
{
new_lwp->stop_expected = 1;
+
if (stopping_threads)
{
+ new_lwp->stop_pc = get_stop_pc (new_lwp);
new_lwp->status_pending_p = 1;
new_lwp->status_pending = status;
}
@@ -427,7 +454,33 @@ handle_extended_wait (struct lwp_info *e
}
}
-/* This function should only be called if the process got a SIGTRAP.
+/* Return the PC as read from the regcache of LWP, without any
+ adjustment. */
+
+static CORE_ADDR
+get_pc (struct lwp_info *lwp)
+{
+ struct thread_info *saved_inferior;
+ struct regcache *regcache;
+ CORE_ADDR pc;
+
+ if (the_low_target.get_pc == NULL)
+ return 0;
+
+ saved_inferior = current_inferior;
+ current_inferior = get_lwp_thread (lwp);
+
+ regcache = get_thread_regcache (current_inferior, 1);
+ pc = (*the_low_target.get_pc) (regcache);
+
+ if (debug_threads)
+ fprintf (stderr, "pc is 0x%lx\n", (long) pc);
+
+ current_inferior = saved_inferior;
+ return pc;
+}
+
+/* This function should only be called if LWP got a SIGTRAP.
The SIGTRAP could mean several things.
On i386, where decr_pc_after_break is non-zero:
@@ -450,13 +503,16 @@ handle_extended_wait (struct lwp_info *e
instruction. */
static CORE_ADDR
-get_stop_pc (void)
+get_stop_pc (struct lwp_info *lwp)
{
- struct regcache *regcache = get_thread_regcache (current_inferior, 1);
- CORE_ADDR stop_pc = (*the_low_target.get_pc) (regcache);
+ CORE_ADDR stop_pc;
+
+ if (the_low_target.get_pc == NULL)
+ return 0;
- if (! get_thread_lwp (current_inferior)->stepping
- && WSTOPSIG (get_thread_lwp (current_inferior)->last_status) == SIGTRAP)
+ stop_pc = get_pc (lwp);
+
+ if (WSTOPSIG (lwp->last_status) == SIGTRAP && !lwp->stepping)
stop_pc -= the_low_target.decr_pc_after_break;
if (debug_threads)
@@ -475,6 +531,8 @@ add_lwp (ptid_t ptid)
lwp->head.id = ptid;
+ lwp->last_resume_kind = resume_continue;
+
if (the_low_target.new_thread != NULL)
lwp->arch_private = the_low_target.new_thread ();
@@ -581,14 +639,16 @@ linux_attach_lwp_1 (unsigned long lwpid,
1) gdbserver has already attached to the process and is being notified
of a new thread that is being created.
- In this case we should ignore that SIGSTOP and resume the process.
- This is handled below by setting stop_expected = 1.
+ In this case we should ignore that SIGSTOP and resume the
+ process. This is handled below by setting stop_expected = 1,
+ and the fact that add_lwp sets last_resume_kind ==
+ resume_continue.
2) This is the first thread (the process thread), and we're attaching
to it via attach_inferior.
In this case we want the process thread to stop.
- This is handled by having linux_attach clear stop_expected after
- we return.
+ This is handled by having linux_attach set last_resume_kind ==
+ resume_stop after we return.
??? If the process already has several threads we leave the other
threads running.
@@ -605,8 +665,7 @@ linux_attach_lwp_1 (unsigned long lwpid,
because we are guaranteed that the add_lwp call above added us to the
end of the list, and so the new thread has not yet reached
wait_for_sigstop (but will). */
- if (! stopping_threads)
- new_lwp->stop_expected = 1;
+ new_lwp->stop_expected = 1;
}
void
@@ -630,7 +689,7 @@ linux_attach (unsigned long pid)
process. It will be collected by wait shortly. */
lwp = (struct lwp_info *) find_inferior_id (&all_lwps,
ptid_build (pid, pid, 0));
- lwp->stop_expected = 0;
+ lwp->last_resume_kind = resume_stop;
}
return 0;
@@ -786,10 +845,6 @@ linux_detach_one_lwp (struct inferior_li
return 0;
}
- /* Make sure the process isn't stopped at a breakpoint that's
- no longer there. */
- check_removed_breakpoint (lwp);
-
/* If this process is stopped but is expecting a SIGSTOP, then make
sure we take care of that now. This isn't absolutely guaranteed
to collect the SIGSTOP, but is fairly likely to. */
@@ -879,80 +934,14 @@ linux_thread_alive (ptid_t ptid)
return 0;
}
-/* Return nonzero if this process stopped at a breakpoint which
- no longer appears to be inserted. Also adjust the PC
- appropriately to resume where the breakpoint used to be. */
-static int
-check_removed_breakpoint (struct lwp_info *event_child)
-{
- CORE_ADDR stop_pc;
- struct thread_info *saved_inferior;
- struct regcache *regcache;
-
- if (event_child->pending_is_breakpoint == 0)
- return 0;
-
- if (debug_threads)
- fprintf (stderr, "Checking for breakpoint in lwp %ld.\n",
- lwpid_of (event_child));
-
- saved_inferior = current_inferior;
- current_inferior = get_lwp_thread (event_child);
- regcache = get_thread_regcache (current_inferior, 1);
- stop_pc = get_stop_pc ();
-
- /* If the PC has changed since we stopped, then we shouldn't do
- anything. This happens if, for instance, GDB handled the
- decr_pc_after_break subtraction itself. */
- if (stop_pc != event_child->pending_stop_pc)
- {
- if (debug_threads)
- fprintf (stderr, "Ignoring, PC was changed. Old PC was 0x%08llx\n",
- event_child->pending_stop_pc);
-
- event_child->pending_is_breakpoint = 0;
- current_inferior = saved_inferior;
- return 0;
- }
-
- /* If the breakpoint is still there, we will report hitting it. */
- if ((*the_low_target.breakpoint_at) (stop_pc))
- {
- if (debug_threads)
- fprintf (stderr, "Ignoring, breakpoint is still present.\n");
- current_inferior = saved_inferior;
- return 0;
- }
-
- if (debug_threads)
- fprintf (stderr, "Removed breakpoint.\n");
-
- /* For decr_pc_after_break targets, here is where we perform the
- decrement. We go immediately from this function to resuming,
- and can not safely call get_stop_pc () again. */
- if (the_low_target.set_pc != NULL)
- {
- if (debug_threads)
- fprintf (stderr, "Set pc to 0x%lx\n", (long) stop_pc);
- (*the_low_target.set_pc) (regcache, stop_pc);
- }
-
- /* We consumed the pending SIGTRAP. */
- event_child->pending_is_breakpoint = 0;
- event_child->status_pending_p = 0;
- event_child->status_pending = 0;
-
- current_inferior = saved_inferior;
- return 1;
-}
-
/* Return 1 if this lwp has an interesting status pending. This
function may silently resume an inferior lwp. */
static int
-status_pending_p (struct inferior_list_entry *entry, void *arg)
+status_pending_p_callback (struct inferior_list_entry *entry, void *arg)
{
struct lwp_info *lwp = (struct lwp_info *) entry;
ptid_t ptid = * (ptid_t *) arg;
+ struct thread_info *thread = get_lwp_thread (lwp);
/* Check if we're only interested in events from a specific process
or its lwps. */
@@ -960,20 +949,15 @@ status_pending_p (struct inferior_list_e
&& ptid_get_pid (ptid) != ptid_get_pid (lwp->head.id))
return 0;
- if (lwp->status_pending_p && !lwp->suspended)
- if (check_removed_breakpoint (lwp))
- {
- /* This thread was stopped at a breakpoint, and the breakpoint
- is now gone. We were told to continue (or step...) all threads,
- so GDB isn't trying to single-step past this breakpoint.
- So instead of reporting the old SIGTRAP, pretend we got to
- the breakpoint just after it was removed instead of just
- before; resume the process. */
- linux_resume_one_lwp (lwp, 0, 0, NULL);
- return 0;
- }
+ thread = get_lwp_thread (lwp);
+
+ /* If we got a `vCont;t', but we haven't reported a stop yet, do
+ report any status pending the LWP may have. */
+ if (lwp->last_resume_kind == resume_stop
+ && thread->last_status.kind == TARGET_WAITKIND_STOPPED)
+ return 0;
- return (lwp->status_pending_p && !lwp->suspended);
+ return lwp->status_pending_p;
}
static int
@@ -1045,7 +1029,6 @@ retry:
goto retry;
child->stopped = 1;
- child->pending_is_breakpoint = 0;
child->last_status = *wstatp;
@@ -1107,6 +1090,13 @@ retry:
}
}
+ /* Store the STOP_PC, with adjustment applied. This depends on the
+ architecture being defined already (so that CHILD has a valid
+ regcache), and on LAST_STATUS being set (to check for SIGTRAP or
+ not). */
+ if (WIFSTOPPED (*wstatp))
+ child->stop_pc = get_stop_pc (child);
+
if (debug_threads
&& WIFSTOPPED (*wstatp)
&& the_low_target.get_pc != NULL)
@@ -1115,8 +1105,7 @@ retry:
struct regcache *regcache;
CORE_ADDR pc;
- current_inferior = (struct thread_info *)
- find_inferior_id (&all_threads, child->head.id);
+ current_inferior = get_lwp_thread (child);
regcache = get_thread_regcache (current_inferior, 1);
pc = (*the_low_target.get_pc) (regcache);
fprintf (stderr, "linux_wait_for_lwp: pc is 0x%lx\n", (long) pc);
@@ -1126,6 +1115,71 @@ retry:
return child;
}
+/* Arrange for a breakpoint to be hit again later. We don't keep the
+ SIGTRAP status and don't forward the SIGTRAP signal to the LWP. We
+ will handle the current event, eventually we will resume this LWP,
+ and this breakpoint will trap again. */
+
+static int
+cancel_breakpoint (struct lwp_info *lwp)
+{
+ struct thread_info *saved_inferior;
+ struct regcache *regcache;
+
+ /* There's nothing to do if we don't support breakpoints. */
+ if (!supports_breakpoints ())
+ return 0;
+
+ if (lwp->stepping)
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "CB: [%s] is stepping\n",
+ target_pid_to_str (lwp->head.id));
+ return 0;
+ }
+
+ regcache = get_thread_regcache (get_lwp_thread (lwp), 1);
+
+ /* breakpoint_at reads from current inferior. */
+ saved_inferior = current_inferior;
+ current_inferior = get_lwp_thread (lwp);
+
+ if ((*the_low_target.breakpoint_at) (lwp->stop_pc))
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "CB: Push back breakpoint for %s\n",
+ target_pid_to_str (lwp->head.id));
+
+ /* Back up the PC if necessary. */
+ if (the_low_target.decr_pc_after_break)
+ {
+ struct regcache *regcache
+ = get_thread_regcache (get_lwp_thread (lwp), 1);
+ (*the_low_target.set_pc) (regcache, lwp->stop_pc);
+ }
+
+ current_inferior = saved_inferior;
+ return 1;
+ }
+ else
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "CB: No breakpoint found at %s for [%s]\n",
+ paddress (lwp->stop_pc),
+ target_pid_to_str (lwp->head.id));
+ }
+
+ current_inferior = saved_inferior;
+ return 0;
+}
+
+/* When the event-loop is doing a step-over, this points at the thread
+ being stepped. */
+ptid_t step_over_bkpt;
+
/* Wait for an event from child PID. If PID is -1, wait for any
child. Store the stop status through the status pointer WSTAT.
OPTIONS is passed to the waitpid call. Return 0 if no child stop
@@ -1136,28 +1190,27 @@ static int
linux_wait_for_event_1 (ptid_t ptid, int *wstat, int options)
{
CORE_ADDR stop_pc;
- struct lwp_info *event_child = NULL;
- int bp_status;
- struct lwp_info *requested_child = NULL;
+ struct lwp_info *event_child, *requested_child;
+
+ again:
+ event_child = NULL;
+ requested_child = NULL;
/* Check for a lwp with a pending status. */
- /* It is possible that the user changed the pending task's registers since
- it stopped. We correctly handle the change of PC if we hit a breakpoint
- (in check_removed_breakpoint); signals should be reported anyway. */
if (ptid_equal (ptid, minus_one_ptid)
|| ptid_equal (pid_to_ptid (ptid_get_pid (ptid)), ptid))
{
event_child = (struct lwp_info *)
- find_inferior (&all_lwps, status_pending_p, &ptid);
+ find_inferior (&all_lwps, status_pending_p_callback, &ptid);
if (debug_threads && event_child)
fprintf (stderr, "Got a pending child %ld\n", lwpid_of (event_child));
}
else
{
requested_child = find_lwp_pid (ptid);
- if (requested_child->status_pending_p
- && !check_removed_breakpoint (requested_child))
+
+ if (requested_child->status_pending_p)
event_child = requested_child;
}
@@ -1179,10 +1232,27 @@ linux_wait_for_event_1 (ptid_t ptid, int
events. */
while (1)
{
- event_child = linux_wait_for_lwp (ptid, wstat, options);
+ int step_over_finished = 0;
+ int bp_explains_trap = 0;
+ int cancel_sigtrap;
+
+ if (ptid_equal (step_over_bkpt, null_ptid))
+ event_child = linux_wait_for_lwp (ptid, wstat, options);
+ else
+ {
+ if (debug_threads)
+ fprintf (stderr, "step_over_bkpt set [%s], doing a blocking wait\n",
+ target_pid_to_str (step_over_bkpt));
+ event_child = linux_wait_for_lwp (step_over_bkpt,
+ wstat, options & ~WNOHANG);
+ }
if ((options & WNOHANG) && event_child == NULL)
- return 0;
+ {
+ if (debug_threads)
+ fprintf (stderr, "WNOHANG set, no event found\n");
+ return 0;
+ }
if (event_child == NULL)
error ("event from unknown child");
@@ -1204,8 +1274,6 @@ linux_wait_for_event_1 (ptid_t ptid, int
return lwpid_of (event_child);
}
- delete_lwp (event_child);
-
if (!non_stop)
{
current_inferior = (struct thread_info *) all_threads.head;
@@ -1223,7 +1291,18 @@ linux_wait_for_event_1 (ptid_t ptid, int
/* If we were waiting for this particular child to do something...
well, it did something. */
if (requested_child != NULL)
- return lwpid_of (event_child);
+ {
+ int lwpid = lwpid_of (event_child);
+
+ /* Cancel the step-over operation --- the thread that
+ started it is gone. */
+ if (finish_step_over (event_child))
+ unstop_all_lwps (event_child);
+ delete_lwp (event_child);
+ return lwpid;
+ }
+
+ delete_lwp (event_child);
/* Wait for a more interesting event. */
continue;
@@ -1236,17 +1315,6 @@ linux_wait_for_event_1 (ptid_t ptid, int
event_child->must_set_ptrace_flags = 0;
}
- if (WIFSTOPPED (*wstat)
- && WSTOPSIG (*wstat) == SIGSTOP
- && event_child->stop_expected)
- {
- if (debug_threads)
- fprintf (stderr, "Expected stop.\n");
- event_child->stop_expected = 0;
- linux_resume_one_lwp (event_child, event_child->stepping, 0, NULL);
- continue;
- }
-
if (WIFSTOPPED (*wstat) && WSTOPSIG (*wstat) == SIGTRAP
&& *wstat >> 16 != 0)
{
@@ -1273,7 +1341,8 @@ linux_wait_for_event_1 (ptid_t ptid, int
||
#endif
(pass_signals[target_signal_from_host (WSTOPSIG (*wstat))]
- && (WSTOPSIG (*wstat) != SIGSTOP || !stopping_threads))))
+ && !(WSTOPSIG (*wstat) == SIGSTOP
+ && event_child->stop_expected))))
{
siginfo_t info, *info_p;
@@ -1285,129 +1354,243 @@ linux_wait_for_event_1 (ptid_t ptid, int
info_p = &info;
else
info_p = NULL;
- linux_resume_one_lwp (event_child,
- event_child->stepping,
+ linux_resume_one_lwp (event_child, event_child->stepping,
WSTOPSIG (*wstat), info_p);
continue;
}
- /* If this event was not handled above, and is not a SIGTRAP,
- report it. SIGILL and SIGSEGV are also treated as traps in case
- a breakpoint is inserted at the current PC. */
- if (!WIFSTOPPED (*wstat)
- || (WSTOPSIG (*wstat) != SIGTRAP && WSTOPSIG (*wstat) != SIGILL
- && WSTOPSIG (*wstat) != SIGSEGV))
- return lwpid_of (event_child);
-
- /* If this target does not support breakpoints, we simply report the
- signal; it's of no concern to us. */
- if (the_low_target.get_pc == NULL)
- return lwpid_of (event_child);
+ if (WIFSTOPPED (*wstat)
+ && WSTOPSIG (*wstat) == SIGSTOP
+ && event_child->stop_expected)
+ {
+ int should_stop;
+
+ if (debug_threads)
+ fprintf (stderr, "Expected stop.\n");
+ event_child->stop_expected = 0;
- stop_pc = get_stop_pc ();
+ should_stop = (event_child->last_resume_kind == resume_stop
+ || stopping_threads);
- /* Only handle SIGILL or SIGSEGV if we've hit a recognized
- breakpoint. */
- if (WSTOPSIG (*wstat) != SIGTRAP
- && (event_child->stepping
- || ! (*the_low_target.breakpoint_at) (stop_pc)))
- return lwpid_of (event_child);
-
- /* bp_reinsert will only be set if we were single-stepping.
- Notice that we will resume the process after hitting
- a gdbserver breakpoint; single-stepping to/over one
- is not supported (yet). */
- if (event_child->bp_reinsert != 0)
+ if (!should_stop)
+ {
+ linux_resume_one_lwp (event_child,
+ event_child->stepping, 0, NULL);
+ continue;
+ }
+ }
+
+ cancel_sigtrap = (stopping_threads
+ || event_child->last_resume_kind == resume_stop);
+
+ /* Do not allow nested internal breakpoint handling, or leaving
+ the visible unadjusted PCs to GDB. Simply cancel the
+ breakpoint now, and eventually when the thread is resumed, it
+ will trap again. */
+ if (WIFSTOPPED (*wstat)
+ && WSTOPSIG (*wstat) == SIGTRAP
+ && cancel_sigtrap)
{
if (debug_threads)
- fprintf (stderr, "Reinserted breakpoint.\n");
- reinsert_breakpoint (event_child->bp_reinsert);
- event_child->bp_reinsert = 0;
+ fprintf (stderr, "Got a nested SIGTRAP while stopping threads\n");
- /* Clear the single-stepping flag and SIGTRAP as we resume. */
- linux_resume_one_lwp (event_child, 0, 0, NULL);
- continue;
+ if (cancel_breakpoint (event_child))
+ {
+ if (debug_threads)
+ fprintf (stderr, " bkpt canceled\n");
+
+ /* We don't resume immediately to collect the SIGSTOP,
+ due to other reasons we may care for this SIGTRAP
+ below. Care must be taken to not process anything
+ breakpoint related though from this point on. */
+ }
}
- bp_status = check_breakpoints (stop_pc);
+ /* If this event was not handled above, and is not a SIGTRAP, we
+ report it. SIGILL and SIGSEGV are also treated as traps in
+ case a breakpoint is inserted at the current PC. If this
+ target does not support breakpoints, we also report the
+ SIGTRAP without further processing; it's of no concern to
+ us. */
+ if (!WIFSTOPPED (*wstat)
+ || !supports_breakpoints ()
+ || (WSTOPSIG (*wstat) != SIGTRAP
+ && WSTOPSIG (*wstat) != SIGILL
+ && WSTOPSIG (*wstat) != SIGSEGV)
+ /* Only handle SIGILL or SIGSEGV if we've hit a recognized
+ breakpoint. */
+ || (WSTOPSIG (*wstat) != SIGTRAP
+ && !(*the_low_target.breakpoint_at) (event_child->stop_pc)))
+ {
+ if (debug_threads && WIFSTOPPED (*wstat))
+ fprintf (stderr, "Reporting signal %d for LWP %ld.\n",
+ WSTOPSIG (*wstat), lwpid_of (event_child));
+
+ /* If we were stepping over a breakpoint, this signal
+ arriving means we didn't manage to move past the
+ breakpoint location. Cancel the operation for now --- it
+ will be handled again on the next resume, if required
+ (the breakpoint may be removed meanwhile, for
+ example). */
+ if (finish_step_over (event_child))
+ {
+ event_child->need_step_over = 1;
+ unstop_all_lwps (event_child);
+ }
+ return lwpid_of (event_child);
+ }
+
+ stop_pc = event_child->stop_pc;
+
+ /* Handle anything that requires bookkeeping before deciding to
+ report the event or continue waiting. */
- if (bp_status != 0)
+ /* First check if we can explain the SIGTRAP with an internal
+ breakpoint, or if we should possibly report the event to GDB.
+ Do this before anything that may remove or insert a
+ breakpoint. */
+ bp_explains_trap = breakpoint_inserted_here (stop_pc);
+
+ /* We have a SIGTRAP, possibly a step-over dance has just
+ finished. If so, tweak the state machine accordingly,
+ reinsert breakpoints and delete any reinsert (software
+ single-step) breakpoints. */
+ step_over_finished = finish_step_over (event_child);
+
+ /* Now invoke the callbacks of any breakpoints there. */
+ if (!cancel_sigtrap)
+ check_breakpoints (stop_pc);
+
+ /* If we're stopping threads, resume once more to collect the
+ SIGSTOP, and do nothing else. Note that we don't set
+ need_step_over. If this predicate matches, then we've
+ cancelled the SIGTRAP before reaching here, and we do want
+ any breakpoint at STOP_PC to be re-hit on resume. */
+ if (stopping_threads
+ || event_child->last_resume_kind == resume_stop)
{
+ gdb_assert (cancel_sigtrap);
+
+ if (step_over_finished)
+ unstop_all_lwps (event_child);
+
if (debug_threads)
- fprintf (stderr, "Hit a gdbserver breakpoint.\n");
+ {
+ if (event_child->last_resume_kind == resume_stop)
+ fprintf (stderr, "Bailing out; GDB wanted the LWP to stop.\n");
+ else if (stopping_threads)
+ fprintf (stderr, "Bailing out; stopping threads.\n");
+
+ if (bp_explains_trap)
+ fprintf (stderr, " Hit a breakpoint.\n");
+ if (step_over_finished)
+ fprintf (stderr, " Step-over finished.\n");
+ if (event_child->stopped_by_watchpoint)
+ fprintf (stderr, " Stopped by watchpoint.\n");
+ }
- /* We hit one of our own breakpoints. We mark it as a pending
- breakpoint, so that check_removed_breakpoint () will do the PC
- adjustment for us at the appropriate time. */
- event_child->pending_is_breakpoint = 1;
- event_child->pending_stop_pc = stop_pc;
-
- /* We may need to put the breakpoint back. We continue in the event
- loop instead of simply replacing the breakpoint right away,
- in order to not lose signals sent to the thread that hit the
- breakpoint. Unfortunately this increases the window where another
- thread could sneak past the removed breakpoint. For the current
- use of server-side breakpoints (thread creation) this is
- acceptable; but it needs to be considered before this breakpoint
- mechanism can be used in more general ways. For some breakpoints
- it may be necessary to stop all other threads, but that should
- be avoided where possible.
-
- If breakpoint_reinsert_addr is NULL, that means that we can
- use PTRACE_SINGLESTEP on this platform. Uninsert the breakpoint,
- mark it for reinsertion, and single-step.
+ /* Leave these pending. */
+ if (event_child->stopped_by_watchpoint)
+ return lwpid_of (event_child);
- Otherwise, call the target function to figure out where we need
- our temporary breakpoint, create it, and continue executing this
- process. */
+ /* Otherwise, there may or not be a pending SIGSTOP. If
+ there isn't one, queue one up. In any case, go back to
+ the event loop to collect it. Don't return yet, as we
+ don't want this SIGTRAP to be left pending. Note that
+ since we cancelled the breakpoint above, the PC is
+ already adjusted, and hence get_stop_pc returns the
+ correct PC when we collect the SIGSTOP. */
- /* NOTE: we're lifting breakpoints in non-stop mode. This
- is currently only used for thread event breakpoints, so
- it isn't that bad as long as we have PTRACE_EVENT_CLONE
- events. */
- if (bp_status == 2)
- /* No need to reinsert. */
- linux_resume_one_lwp (event_child, 0, 0, NULL);
- else if (the_low_target.breakpoint_reinsert_addr == NULL)
- {
- event_child->bp_reinsert = stop_pc;
- uninsert_breakpoint (stop_pc);
- linux_resume_one_lwp (event_child, 1, 0, NULL);
- }
- else
+ if (!event_child->stop_expected)
{
- reinsert_breakpoint_by_bp
- (stop_pc, (*the_low_target.breakpoint_reinsert_addr) ());
- linux_resume_one_lwp (event_child, 0, 0, NULL);
+ event_child->stop_expected = 1;
+ kill_lwp (lwpid_of (event_child), SIGSTOP);
}
+ /* Clear the single-stepping flag and SIGTRAP as we resume. */
+ linux_resume_one_lwp (event_child, 0, 0, NULL);
continue;
}
- if (debug_threads)
- fprintf (stderr, "Hit a non-gdbserver breakpoint.\n");
+ /* We have all the data we need. Either report the event to
+ GDB, or resume threads and keep waiting for more. */
+
+ /* Check If GDB would be interested in this event. If GDB
+ wanted this thread to single step, we always want to report
+ the SIGTRAP, and let GDB handle it. */
+ if ((event_child->last_resume_kind == resume_step)
+ || event_child->stopped_by_watchpoint)
+ {
+ if (step_over_finished)
+ unstop_all_lwps (event_child);
- /* If we were single-stepping, we definitely want to report the
- SIGTRAP. Although the single-step operation has completed,
- do not clear clear the stepping flag yet; we need to check it
- in wait_for_sigstop. */
- if (event_child->stepping)
- return lwpid_of (event_child);
-
- /* A SIGTRAP that we can't explain. It may have been a breakpoint.
- Check if it is a breakpoint, and if so mark the process information
- accordingly. This will handle both the necessary fiddling with the
- PC on decr_pc_after_break targets and suppressing extra threads
- hitting a breakpoint if two hit it at once and then GDB removes it
- after the first is reported. Arguably it would be better to report
- multiple threads hitting breakpoints simultaneously, but the current
- remote protocol does not allow this. */
- if ((*the_low_target.breakpoint_at) (stop_pc))
+ if (debug_threads)
+ {
+ if (event_child->last_resume_kind == resume_step)
+ fprintf (stderr, "GDB wanted to single-step, reporting event.\n");
+ if (event_child->stopped_by_watchpoint)
+ fprintf (stderr, "Stopped by watchpoint.\n");
+ }
+ }
+ /* We found no reason GDB would want us to stop. We either hit
+ one of our own breakpoints, or finished an internal step GDB
+ shouldn't know about. */
+ else if (step_over_finished || bp_explains_trap)
{
- event_child->pending_is_breakpoint = 1;
- event_child->pending_stop_pc = stop_pc;
+ if (debug_threads)
+ {
+ if (bp_explains_trap)
+ fprintf (stderr, "Hit a gdbserver breakpoint.\n");
+ if (step_over_finished)
+ fprintf (stderr, "Step-over finished.\n");
+ }
+
+ /* If we stepped or ran into an internal breakpoint, we've
+ already handled it. So next time we resume (from this
+ PC), we should step over it. */
+ if (breakpoint_here (stop_pc))
+ event_child->need_step_over = 1;
+
+ /* We're not reporting this breakpoint to GDB, so apply the
+ decr_pc_after_break adjustment to the inferior's regcache
+ ourselves. */
+
+ if (the_low_target.set_pc != NULL)
+ {
+ struct regcache *regcache
+ = get_thread_regcache (get_lwp_thread (event_child), 1);
+ (*the_low_target.set_pc) (regcache, stop_pc);
+ }
+
+ /* We've finished stepping over a breakpoint. We've stopped
+ all LWPs momentarily except the stepping one. This is
+ where we resume them all again. We're going to keep
+ waiting, so use proceed, which handles stepping over the
+ next breakpoint. */
+ if (debug_threads)
+ fprintf (stderr, "proceeding all threads.\n");
+ proceed_all_lwps ();
+
+ /* If we stopped threads momentarily, we may have threads
+ with pending statuses now (except when we're going to
+ force the next event out of a specific LWP, in which case
+ don't want to handle the pending events of other LWPs
+ yet. */
+ if (ptid_equal (step_over_bkpt, null_ptid))
+ goto again;
+ else
+ continue;
+ }
+ else
+ {
+ /* GDB breakpoint or program trap, perhaps. */
+ if (step_over_finished)
+ unstop_all_lwps (event_child);
}
+ if (debug_threads)
+ fprintf (stderr, "Hit a non-gdbserver trap event.\n");
+
return lwpid_of (event_child);
}
@@ -1455,6 +1638,32 @@ linux_wait_for_event (ptid_t ptid, int *
}
}
+/* Set this inferior LWP's state as "want-stopped". We won't resume
+ this LWP until the client gives us another action for it. */
+
+static void
+gdb_wants_lwp_stopped (struct inferior_list_entry *entry)
+{
+ struct lwp_info *lwp = (struct lwp_info *) entry;
+ struct thread_info *thread = get_lwp_thread (lwp);
+
+ /* Most threads are stopped implicitly (all-stop); tag that with
+ signal 0. The thread being explicitly reported stopped to the
+ client, gets it's status fixed up afterwards. */
+ thread->last_status.kind = TARGET_WAITKIND_STOPPED;
+ thread->last_status.value.sig = TARGET_SIGNAL_0;
+
+ lwp->last_resume_kind = resume_stop;
+}
+
+/* Set all LWP's states as "want-stopped". */
+
+static void
+gdb_wants_all_stopped (void)
+{
+ for_each_inferior (&all_lwps, gdb_wants_lwp_stopped);
+}
+
/* Wait for process, returns status. */
static ptid_t
@@ -1561,26 +1770,21 @@ retry:
goto retry;
}
- /* In all-stop, stop all threads. Be careful to only do this if
- we're about to report an event to GDB. */
- if (!non_stop)
- stop_all_lwps ();
-
ourstatus->kind = TARGET_WAITKIND_STOPPED;
- if (lwp->suspended && WSTOPSIG (w) == SIGSTOP)
+ /* Do this before the gdb_wants_all_stopped calls below, since they
+ always set last_resume_kind to resume_stop. */
+ if (lwp->last_resume_kind == resume_stop && WSTOPSIG (w) == SIGSTOP)
{
/* A thread that has been requested to stop by GDB with vCont;t,
and it stopped cleanly, so report as SIG0. The use of
SIGSTOP is an implementation detail. */
ourstatus->value.sig = TARGET_SIGNAL_0;
}
- else if (lwp->suspended && WSTOPSIG (w) != SIGSTOP)
+ else if (lwp->last_resume_kind == resume_stop && WSTOPSIG (w) != SIGSTOP)
{
/* A thread that has been requested to stop by GDB with vCont;t,
- but, it stopped for other reasons. Set stop_expected so the
- pending SIGSTOP is ignored and the LWP is resumed. */
- lwp->stop_expected = 1;
+ but, it stopped for other reasons. */
ourstatus->value.sig = target_signal_from_host (WSTOPSIG (w));
}
else
@@ -1588,12 +1792,40 @@ retry:
ourstatus->value.sig = target_signal_from_host (WSTOPSIG (w));
}
+ gdb_assert (ptid_equal (step_over_bkpt, null_ptid));
+
+ if (!non_stop)
+ {
+ /* In all-stop, stop all threads, we're about to report an event
+ to GDB. */
+ stop_all_lwps ();
+
+ /* Do not leave a pending single-step finish to be reported to
+ the client. The client will give us a new action for this
+ thread, possibly a continue request --- otherwise, the client
+ would consider this pending SIGTRAP reported later a spurious
+ signal. */
+ cancel_finished_single_steps (lwp);
+
+ /* From GDB's perspective, all-stop mode always stops all
+ threads implicitly. Tag all threads as "want-stopped". */
+ gdb_wants_all_stopped ();
+ }
+ else
+ {
+ /* We're reporting this LWP as stopped. Update it's
+ "want-stopped" state to what the client wants, until it gets
+ a new resume action. */
+ gdb_wants_lwp_stopped (&lwp->head);
+ }
+
if (debug_threads)
fprintf (stderr, "linux_wait ret = %s, %d, %d\n",
target_pid_to_str (lwp->head.id),
ourstatus->kind,
ourstatus->value.sig);
+ get_lwp_thread (lwp)->last_status = *ourstatus;
return lwp->head.id;
}
@@ -1696,16 +1928,13 @@ send_sigstop (struct inferior_list_entry
if (debug_threads)
fprintf (stderr, "Have pending sigstop for lwp %d\n", pid);
- /* We clear the stop_expected flag so that wait_for_sigstop
- will receive the SIGSTOP event (instead of silently resuming and
- waiting again). It'll be reset below. */
- lwp->stop_expected = 0;
return;
}
if (debug_threads)
fprintf (stderr, "Sending sigstop to lwp %d\n", pid);
+ lwp->stop_expected = 1;
kill_lwp (pid, SIGSTOP);
}
@@ -1719,10 +1948,6 @@ mark_lwp_dead (struct lwp_info *lwp, int
lwp->status_pending_p = 1;
lwp->status_pending = wstat;
- /* So that check_removed_breakpoint doesn't try to figure out if
- this is stopped at a breakpoint. */
- lwp->pending_is_breakpoint = 0;
-
/* Prevent trying to stop it. */
lwp->stopped = 1;
@@ -1730,6 +1955,45 @@ mark_lwp_dead (struct lwp_info *lwp, int
lwp->stop_expected = 0;
}
+static int
+cancel_finished_single_step (struct inferior_list_entry *entry, void *except)
+{
+ struct lwp_info *lwp = (struct lwp_info *) entry;
+ struct thread_info *saved_inferior;
+
+ if (lwp == except)
+ return 0;
+
+ saved_inferior = current_inferior;
+ current_inferior = get_lwp_thread (lwp);
+
+ /* Do not leave a pending single-step finish to be reported to the
+ client. The client will give us a new action for this thread,
+ possibly a continue request --- otherwise, the client would
+ consider this pending SIGTRAP reported later a spurious
+ signal. */
+ if (lwp->status_pending_p
+ && WSTOPSIG (lwp->status_pending) == SIGTRAP
+ && lwp->stepping
+ && !lwp->stopped_by_watchpoint)
+ {
+ if (debug_threads)
+ fprintf (stderr, " single-step SIGTRAP cancelled\n");
+
+ lwp->status_pending_p = 0;
+ lwp->status_pending = 0;
+ }
+
+ current_inferior = saved_inferior;
+ return 0;
+}
+
+static void
+cancel_finished_single_steps (struct lwp_info *except)
+{
+ find_inferior (&all_lwps, cancel_finished_single_step, except);
+}
+
static void
wait_for_sigstop (struct inferior_list_entry *entry)
{
@@ -1738,9 +2002,15 @@ wait_for_sigstop (struct inferior_list_e
int wstat;
ptid_t saved_tid;
ptid_t ptid;
+ int pid;
if (lwp->stopped)
- return;
+ {
+ if (debug_threads)
+ fprintf (stderr, "wait_for_sigstop: LWP %ld already stopped\n",
+ lwpid_of (lwp));
+ return;
+ }
saved_inferior = current_inferior;
if (saved_inferior != NULL)
@@ -1750,48 +2020,47 @@ wait_for_sigstop (struct inferior_list_e
ptid = lwp->head.id;
- linux_wait_for_event (ptid, &wstat, __WALL);
+ if (debug_threads)
+ fprintf (stderr, "wait_for_sigstop: pulling one event\n");
+
+ pid = linux_wait_for_event (ptid, &wstat, __WALL);
/* If we stopped with a non-SIGSTOP signal, save it for later
and record the pending SIGSTOP. If the process exited, just
return. */
- if (WIFSTOPPED (wstat)
- && WSTOPSIG (wstat) != SIGSTOP)
+ if (WIFSTOPPED (wstat))
{
if (debug_threads)
- fprintf (stderr, "LWP %ld stopped with non-sigstop status %06x\n",
- lwpid_of (lwp), wstat);
+ fprintf (stderr, "LWP %ld stopped with signal %d\n",
+ lwpid_of (lwp), WSTOPSIG (wstat));
- /* Do not leave a pending single-step finish to be reported to
- the client. The client will give us a new action for this
- thread, possibly a continue request --- otherwise, the client
- would consider this pending SIGTRAP reported later a spurious
- signal. */
- if (WSTOPSIG (wstat) == SIGTRAP
- && lwp->stepping
- && !lwp->stopped_by_watchpoint)
+ if (WSTOPSIG (wstat) != SIGSTOP)
{
if (debug_threads)
- fprintf (stderr, " single-step SIGTRAP ignored\n");
- }
- else
- {
+ fprintf (stderr, "LWP %ld stopped with non-sigstop status %06x\n",
+ lwpid_of (lwp), wstat);
+
lwp->status_pending_p = 1;
lwp->status_pending = wstat;
}
- lwp->stop_expected = 1;
}
- else if (!WIFSTOPPED (wstat))
+ else
{
if (debug_threads)
- fprintf (stderr, "Process %ld exited while stopping LWPs\n",
- lwpid_of (lwp));
+ fprintf (stderr, "Process %d exited while stopping LWPs\n", pid);
- /* Leave this status pending for the next time we're able to
- report it. In the mean time, we'll report this lwp as dead
- to GDB, so GDB doesn't try to read registers and memory from
- it. */
- mark_lwp_dead (lwp, wstat);
+ lwp = find_lwp_pid (pid_to_ptid (pid));
+ if (lwp)
+ {
+ /* Leave this status pending for the next time we're able to
+ report it. In the mean time, we'll report this lwp as
+ dead to GDB, so GDB doesn't try to read registers and
+ memory from it. This can only happen if this was the
+ last thread of the process; otherwise, PID is removed
+ from the thread tables before linux_wait_for_event
+ returns. */
+ mark_lwp_dead (lwp, wstat);
+ }
}
if (saved_inferior == NULL || linux_thread_alive (saved_tid))
@@ -1856,8 +2125,15 @@ linux_resume_one_lwp (struct lwp_info *l
lwp->pending_signals = p_sig;
}
- if (lwp->status_pending_p && !check_removed_breakpoint (lwp))
- return;
+ if (lwp->status_pending_p)
+ {
+ if (debug_threads)
+ fprintf (stderr, "Not resuming lwp %ld (%s, signal %d, stop %s);"
+ " has pending status\n",
+ lwpid_of (lwp), step ? "step" : "continue", signal,
+ lwp->stop_expected ? "expected" : "not expected");
+ return;
+ }
saved_inferior = current_inferior;
current_inferior = get_lwp_thread (lwp);
@@ -1880,17 +2156,21 @@ linux_resume_one_lwp (struct lwp_info *l
if (lwp->bp_reinsert != 0)
{
if (debug_threads)
- fprintf (stderr, " pending reinsert at %08lx", (long)lwp->bp_reinsert);
- if (step == 0)
- fprintf (stderr, "BAD - reinserting but not stepping.\n");
- step = 1;
+ fprintf (stderr, " pending reinsert at 0x%s\n",
+ paddress (lwp->bp_reinsert));
+
+ if (lwp->bp_reinsert != 0 && can_hardware_single_step ())
+ {
+ if (step == 0)
+ fprintf (stderr, "BAD - reinserting but not stepping.\n");
+
+ step = 1;
+ }
/* Postpone any pending signal. It was enqueued above. */
signal = 0;
}
- check_removed_breakpoint (lwp);
-
if (debug_threads && the_low_target.get_pc != NULL)
{
struct regcache *regcache = get_thread_regcache (current_inferior, 1);
@@ -1982,7 +2262,21 @@ linux_set_resume_request (struct inferio
|| (ptid_get_lwp (ptid) == -1
&& (ptid_get_pid (ptid) == pid_of (lwp))))
{
+ if (r->resume[ndx].kind == resume_stop
+ && lwp->last_resume_kind == resume_stop)
+ {
+ if (debug_threads)
+ fprintf (stderr, "already %s LWP %ld at GDB's request\n",
+ thread->last_status.kind == TARGET_WAITKIND_STOPPED
+ ? "stopped"
+ : "stopping",
+ lwpid_of (lwp));
+
+ continue;
+ }
+
lwp->resume = &r->resume[ndx];
+ lwp->last_resume_kind = lwp->resume->kind;
return 0;
}
}
@@ -2005,23 +2299,212 @@ resume_status_pending_p (struct inferior
if (lwp->resume == NULL)
return 0;
- /* If this thread has a removed breakpoint, we won't have any
- events to report later, so check now. check_removed_breakpoint
- may clear status_pending_p. We avoid calling check_removed_breakpoint
- for any thread that we are not otherwise going to resume - this
- lets us preserve stopped status when two threads hit a breakpoint.
- GDB removes the breakpoint to single-step a particular thread
- past it, then re-inserts it and resumes all threads. We want
- to report the second thread without resuming it in the interim. */
if (lwp->status_pending_p)
- check_removed_breakpoint (lwp);
+ * (int *) flag_p = 1;
+
+ return 0;
+}
+
+/* Return 1 if this lwp that GDB wants running is stopped at an
+ internal breakpoint that we need to step over. It assumes that any
+ required STOP_PC adjustment has already been propagated to the
+ inferior's regcache. */
+
+static int
+need_step_over_p (struct inferior_list_entry *entry, void *dummy)
+{
+ struct lwp_info *lwp = (struct lwp_info *) entry;
+ struct thread_info *saved_inferior;
+ CORE_ADDR pc;
+
+ /* LWPs which will not be resumed are not interesting, because we
+ might not wait for them next time through linux_wait. */
+
+ if (!lwp->stopped)
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "Need step over [LWP %ld]? Ignoring, not stopped\n",
+ lwpid_of (lwp));
+ return 0;
+ }
+
+ if (lwp->last_resume_kind == resume_stop)
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "Need step over [LWP %ld]? Ignoring, should remain stopped\n",
+ lwpid_of (lwp));
+ return 0;
+ }
+
+ if (!lwp->need_step_over)
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "Need step over [LWP %ld]? No\n", lwpid_of (lwp));
+ }
if (lwp->status_pending_p)
- * (int *) flag_p = 1;
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "Need step over [LWP %ld]? Ignoring, has pending status.\n",
+ lwpid_of (lwp));
+ return 0;
+ }
+
+ /* Note: PC, not STOP_PC. Either GDB has adjusted the PC already,
+ or we have. */
+ pc = get_pc (lwp);
+
+ /* If the PC has changed since we stopped, then don't do anything,
+ and let the breakpoint/tracepoint be hit. This happens if, for
+ instance, GDB handled the decr_pc_after_break subtraction itself,
+ GDB is OOL stepping this thread, or the user has issued a "jump"
+ command, or poked thread's registers herself. */
+ if (pc != lwp->stop_pc)
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "Need step over [LWP %ld]? Cancelling, PC was changed. "
+ "Old stop_pc was 0x%s, PC is now 0x%s\n",
+ lwpid_of (lwp), paddress (lwp->stop_pc), paddress (pc));
+
+ lwp->need_step_over = 0;
+ return 0;
+ }
+
+ saved_inferior = current_inferior;
+ current_inferior = get_lwp_thread (lwp);
+
+ /* We only step over our breakpoints. */
+ if (breakpoint_here (pc))
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "Need step over [LWP %ld]? yes, found breakpoint at 0x%s\n",
+ lwpid_of (lwp), paddress (pc));
+
+ /* We've found an lwp that needs stepping over --- return 1 so
+ that find_inferior stops looking. */
+ current_inferior = saved_inferior;
+
+ /* If the step over is cancelled, this is set again. */
+ lwp->need_step_over = 0;
+ return 1;
+ }
+
+ current_inferior = saved_inferior;
+
+ if (debug_threads)
+ fprintf (stderr,
+ "Need step over [LWP %ld]? No, no breakpoint found at 0x%s\n",
+ lwpid_of (lwp), paddress (pc));
return 0;
}
+/* Start a step-over operation on LWP. When LWP stopped at a
+ breakpoint, to make progress, we need to remove the breakpoint out
+ of the way. If we let other threads run while we do that, they may
+ pass by the breakpoint location and miss hitting it. To avoid
+ that, a step-over momentarily stops all threads while LWP is
+ single-stepped while the breakpoint is temporarily uninserted from
+ the inferior. When the single-step finishes, we reinsert the
+ breakpoint, and let all threads that are supposed to be running,
+ run again.
+
+ On targets that don't support hardware single-step, we don't
+ currently support full software single-stepping. Instead, we only
+ support stepping over the thread event breakpoint, by asking the
+ low target where to place a reinsert breakpoint. Since this
+ routine assumes the breakpoint being stepped over is a thread event
+ breakpoint, it usually assumes the return address of the current
+ function is a good enough place to set the reinsert breakpoint. */
+
+static int
+start_step_over (struct lwp_info *lwp)
+{
+ struct thread_info *saved_inferior;
+ CORE_ADDR pc;
+ int step;
+
+ if (debug_threads)
+ fprintf (stderr,
+ "Starting step-over on LWP %ld. Stopping all threads\n",
+ lwpid_of (lwp));
+
+ stop_all_lwps ();
+
+ if (debug_threads)
+ fprintf (stderr, "Done stopping all threads for step-over.\n");
+
+ /* Note, we should always reach here with an already adjusted PC,
+ either by GDB (if we're resuming due to GDB's request), or by our
+ caller, if we just finished handling an internal breakpoint GDB
+ shouldn't care about. */
+ pc = get_pc (lwp);
+
+ saved_inferior = current_inferior;
+ current_inferior = get_lwp_thread (lwp);
+
+ lwp->bp_reinsert = pc;
+ uninsert_breakpoints_at (pc);
+
+ if (can_hardware_single_step ())
+ {
+ step = 1;
+ }
+ else
+ {
+ CORE_ADDR raddr = (*the_low_target.breakpoint_reinsert_addr) ();
+ set_reinsert_breakpoint (raddr);
+ step = 0;
+ }
+
+ current_inferior = saved_inferior;
+
+ linux_resume_one_lwp (lwp, step, 0, NULL);
+
+ /* Require next event from this LWP. */
+ step_over_bkpt = lwp->head.id;
+ return 1;
+}
+
+/* Finish a step-over. Reinsert the breakpoint we had uninserted in
+ start_step_over, if still there, and delete any reinsert
+ breakpoints we've set, on non hardware single-step targets. */
+
+static int
+finish_step_over (struct lwp_info *lwp)
+{
+ if (lwp->bp_reinsert != 0)
+ {
+ if (debug_threads)
+ fprintf (stderr, "Finished step over.\n");
+
+ /* Reinsert any breakpoint at LWP->BP_REINSERT. Note that there
+ may be no breakpoint to reinsert there by now. */
+ reinsert_breakpoints_at (lwp->bp_reinsert);
+
+ lwp->bp_reinsert = 0;
+
+ /* Delete any software-single-step reinsert breakpoints. No
+ longer needed. We don't have to worry about other threads
+ hitting this trap, and later not being able to explain it,
+ because we were stepping over a breakpoint, and we hold all
+ threads but LWP stopped while doing that. */
+ if (!can_hardware_single_step ())
+ delete_reinsert_breakpoints ();
+
+ step_over_bkpt = null_ptid;
+ return 1;
+ }
+ else
+ return 0;
+}
+
/* This function is called once per thread. We check the thread's resume
request, which will tell us whether to resume, step, or leave the thread
stopped; and what signal, if any, it should be sent.
@@ -2041,7 +2524,8 @@ linux_resume_one_thread (struct inferior
struct lwp_info *lwp;
struct thread_info *thread;
int step;
- int pending_flag = * (int *) arg;
+ int leave_all_stopped = * (int *) arg;
+ int leave_pending;
thread = (struct thread_info *) entry;
lwp = get_thread_lwp (thread);
@@ -2052,65 +2536,61 @@ linux_resume_one_thread (struct inferior
if (lwp->resume->kind == resume_stop)
{
if (debug_threads)
- fprintf (stderr, "suspending LWP %ld\n", lwpid_of (lwp));
+ fprintf (stderr, "resume_stop request for LWP %ld\n", lwpid_of (lwp));
if (!lwp->stopped)
{
if (debug_threads)
- fprintf (stderr, "running -> suspending LWP %ld\n", lwpid_of (lwp));
+ fprintf (stderr, "stopping LWP %ld\n", lwpid_of (lwp));
- lwp->suspended = 1;
+ /* Stop the thread, and wait for the event asynchronously,
+ through the event loop. */
send_sigstop (&lwp->head);
}
else
{
if (debug_threads)
- {
- if (lwp->suspended)
- fprintf (stderr, "already stopped/suspended LWP %ld\n",
- lwpid_of (lwp));
- else
- fprintf (stderr, "already stopped/not suspended LWP %ld\n",
- lwpid_of (lwp));
- }
+ fprintf (stderr, "already stopped LWP %ld\n",
+ lwpid_of (lwp));
- /* Make sure we leave the LWP suspended, so we don't try to
- resume it without GDB telling us to. FIXME: The LWP may
- have been stopped in an internal event that was not meant
- to be notified back to GDB (e.g., gdbserver breakpoint),
- so we should be reporting a stop event in that case
- too. */
- lwp->suspended = 1;
+ /* The LWP may have been stopped in an internal event that
+ was not meant to be notified back to GDB (e.g., gdbserver
+ breakpoint), so we should be reporting a stop event in
+ this case too. */
+
+ /* If the thread already has a pending SIGSTOP, this is a
+ no-op. Otherwise, something later will presumably resume
+ the thread and this will cause it to cancel any pending
+ operation, due to last_resume_kind == resume_stop. If
+ the thread already has a pending status to report, we
+ will still report it the next time we wait - see
+ status_pending_p_callback. */
+ send_sigstop (&lwp->head);
}
/* For stop requests, we're done. */
lwp->resume = NULL;
+ get_lwp_thread (lwp)->last_status.kind = TARGET_WAITKIND_IGNORE;
return 0;
}
- else
- lwp->suspended = 0;
/* If this thread which is about to be resumed has a pending status,
then don't resume any threads - we can just report the pending
status. Make sure to queue any signals that would otherwise be
sent. In all-stop mode, we do this decision based on if *any*
- thread has a pending status. */
- if (non_stop)
- resume_status_pending_p (&lwp->head, &pending_flag);
+ thread has a pending status. If there's a thread that needs the
+ step-over-breakpoint dance, then don't resume any other thread
+ but that particular one. */
+ leave_pending = (lwp->status_pending_p || leave_all_stopped);
- if (!pending_flag)
+ if (!leave_pending)
{
if (debug_threads)
fprintf (stderr, "resuming LWP %ld\n", lwpid_of (lwp));
- if (ptid_equal (lwp->resume->thread, minus_one_ptid)
- && lwp->stepping
- && lwp->pending_is_breakpoint)
- step = 1;
- else
- step = (lwp->resume->kind == resume_step);
-
+ step = (lwp->resume->kind == resume_step);
linux_resume_one_lwp (lwp, step, lwp->resume->sig, NULL);
+ get_lwp_thread (lwp)->last_status.kind = TARGET_WAITKIND_IGNORE;
}
else
{
@@ -2145,29 +2625,173 @@ linux_resume_one_thread (struct inferior
static void
linux_resume (struct thread_resume *resume_info, size_t n)
{
- int pending_flag;
struct thread_resume_array array = { resume_info, n };
+ struct lwp_info *need_step_over = NULL;
+ int any_pending;
+ int leave_all_stopped;
find_inferior (&all_threads, linux_set_resume_request, &array);
- /* If there is a thread which would otherwise be resumed, which
- has a pending status, then don't resume any threads - we can just
- report the pending status. Make sure to queue any signals
- that would otherwise be sent. In non-stop mode, we'll apply this
- logic to each thread individually. */
- pending_flag = 0;
+ /* If there is a thread which would otherwise be resumed, which has
+ a pending status, then don't resume any threads - we can just
+ report the pending status. Make sure to queue any signals that
+ would otherwise be sent. In non-stop mode, we'll apply this
+ logic to each thread individually. We consume all pending events
+ before considering to start a step-over (in all-stop). */
+ any_pending = 0;
if (!non_stop)
- find_inferior (&all_lwps, resume_status_pending_p, &pending_flag);
+ find_inferior (&all_lwps, resume_status_pending_p, &any_pending);
+
+ /* If there is a thread which would otherwise be resumed, which is
+ stopped at a breakpoint that needs stepping over, then don't
+ resume any threads - have it step over the breakpoint with all
+ other threads stopped, then resume all threads again. Make sure
+ to queue any signals that would otherwise be delivered or
+ queued. */
+ if (!any_pending && supports_breakpoints ())
+ need_step_over
+ = (struct lwp_info *) find_inferior (&all_lwps,
+ need_step_over_p, NULL);
+
+ leave_all_stopped = (need_step_over != NULL || any_pending);
+
+ if (debug_threads)
+ {
+ if (need_step_over != NULL)
+ fprintf (stderr, "Not resuming all, need step over\n");
+ else if (any_pending)
+ fprintf (stderr,
+ "Not resuming, all-stop and found "
+ "an LWP with pending status\n");
+ else
+ fprintf (stderr, "Resuming, no pending status or step over needed\n");
+ }
+
+ /* Even if we're leaving threads stopped, queue all signals we'd
+ otherwise deliver. */
+ find_inferior (&all_threads, linux_resume_one_thread, &leave_all_stopped);
+
+ if (need_step_over)
+ start_step_over (need_step_over);
+}
+
+/* This function is called once per thread. We check the thread's
+ last resume request, which will tell us whether to resume, step, or
+ leave the thread stopped. Any signal the client requested to be
+ delivered has already been enqueued at this point.
+
+ If any thread that GDB wants running is stopped at an internal
+ breakpoint that needs stepping over, we start a step-over operation
+ on that particular thread, and leave all others stopped. */
+
+static void
+proceed_one_lwp (struct inferior_list_entry *entry)
+{
+ struct lwp_info *lwp;
+ int step;
+
+ lwp = (struct lwp_info *) entry;
+
+ if (debug_threads)
+ fprintf (stderr,
+ "proceed_one_lwp: lwp %ld\n", lwpid_of (lwp));
+
+ if (!lwp->stopped)
+ {
+ if (debug_threads)
+ fprintf (stderr, " LWP %ld already running\n", lwpid_of (lwp));
+ return;
+ }
+
+ if (lwp->last_resume_kind == resume_stop)
+ {
+ if (debug_threads)
+ fprintf (stderr, " client wants LWP %ld stopped\n", lwpid_of (lwp));
+ return;
+ }
+
+ if (lwp->status_pending_p)
+ {
+ if (debug_threads)
+ fprintf (stderr, " LWP %ld has pending status, leaving stopped\n",
+ lwpid_of (lwp));
+ return;
+ }
+
+ if (lwp->suspended)
+ {
+ if (debug_threads)
+ fprintf (stderr, " LWP %ld is suspended\n", lwpid_of (lwp));
+ return;
+ }
+
+ step = lwp->last_resume_kind == resume_step;
+ linux_resume_one_lwp (lwp, step, 0, NULL);
+}
+
+/* When we finish a step-over, set threads running again. If there's
+ another thread that may need a step-over, now's the time to start
+ it. Eventually, we'll move all threads past their breakpoints. */
+
+static void
+proceed_all_lwps (void)
+{
+ struct lwp_info *need_step_over;
+
+ /* If there is a thread which would otherwise be resumed, which is
+ stopped at a breakpoint that needs stepping over, then don't
+ resume any threads - have it step over the breakpoint with all
+ other threads stopped, then resume all threads again. */
+
+ if (supports_breakpoints ())
+ {
+ need_step_over
+ = (struct lwp_info *) find_inferior (&all_lwps,
+ need_step_over_p, NULL);
+
+ if (need_step_over != NULL)
+ {
+ if (debug_threads)
+ fprintf (stderr, "proceed_all_lwps: found "
+ "thread %ld needing a step-over\n",
+ lwpid_of (need_step_over));
+
+ start_step_over (need_step_over);
+ return;
+ }
+ }
if (debug_threads)
+ fprintf (stderr, "Proceeding, no step-over needed\n");
+
+ for_each_inferior (&all_lwps, proceed_one_lwp);
+}
+
+/* Stopped LWPs that the client wanted to be running, that don't have
+ pending statuses, are set to run again, except for EXCEPT, if not
+ NULL. This undoes a stop_all_lwps call. */
+
+static void
+unstop_all_lwps (struct lwp_info *except)
+{
+ if (debug_threads)
{
- if (pending_flag)
- fprintf (stderr, "Not resuming, pending status\n");
+ if (except)
+ fprintf (stderr,
+ "unstopping all lwps, except=(LWP %ld)\n", lwpid_of (except));
else
- fprintf (stderr, "Resuming, no pending status\n");
+ fprintf (stderr,
+ "unstopping all lwps\n");
}
- find_inferior (&all_threads, linux_resume_one_thread, &pending_flag);
+ /* Make sure proceed_one_lwp doesn't try to resume this thread. */
+ if (except != NULL)
+ ++except->suspended;
+
+ for_each_inferior (&all_lwps, proceed_one_lwp);
+
+ if (except != NULL)
+ --except->suspended;
}
#ifdef HAVE_LINUX_USRREGS
Index: src/gdb/gdbserver/linux-low.h
===================================================================
--- src.orig/gdb/gdbserver/linux-low.h 2010-03-16 17:43:42.000000000 +0000
+++ src/gdb/gdbserver/linux-low.h 2010-03-16 17:53:08.000000000 +0000
@@ -137,7 +137,8 @@ struct lwp_info
yet. */
int stop_expected;
- /* True if this thread was suspended (with vCont;t). */
+ /* When this is true, we shall not try to resume this thread, even
+ if last_resume_kind isn't resume_stop. */
int suspended;
/* If this flag is set, the lwp is known to be stopped right now (stop
@@ -152,15 +153,15 @@ struct lwp_info
/* When stopped is set, the last wait status recorded for this lwp. */
int last_status;
+ /* When stopped is set, this is where the lwp stopped, with
+ decr_pc_after_break already accounted for. */
+ CORE_ADDR stop_pc;
+
/* If this flag is set, STATUS_PENDING is a waitstatus that has not yet
been reported. */
int status_pending_p;
int status_pending;
- /* If this flag is set, the pending status is a (GDB-placed) breakpoint. */
- int pending_is_breakpoint;
- CORE_ADDR pending_stop_pc;
-
/* STOPPED_BY_WATCHPOINT is non-zero if this LWP stopped with a data
watchpoint trap. */
int stopped_by_watchpoint;
@@ -175,8 +176,8 @@ struct lwp_info
stop (SIGTRAP stops only). */
CORE_ADDR bp_reinsert;
- /* If this flag is set, the last continue operation on this process
- was a single-step. */
+ /* If this flag is set, the last continue operation at the ptrace
+ level on this process was a single-step. */
int stepping;
/* If this flag is set, we need to set the event request flags the
@@ -189,9 +190,15 @@ struct lwp_info
/* A link used when resuming. It is initialized from the resume request,
and then processed and cleared in linux_resume_one_lwp. */
-
struct thread_resume *resume;
+ /* The last resume GDB requested on this thread. */
+ enum resume_kind last_resume_kind;
+
+ /* True if the LWP was seen stop at an internal breakpoint and needs
+ stepping over later when it is resumed. */
+ int need_step_over;
+
int thread_known;
#ifdef HAVE_THREAD_DB_H
/* The thread handle, used for e.g. TLS access. Only valid if
Index: src/gdb/gdbserver/inferiors.c
===================================================================
--- src.orig/gdb/gdbserver/inferiors.c 2010-03-16 17:43:42.000000000 +0000
+++ src/gdb/gdbserver/inferiors.c 2010-03-16 17:53:08.000000000 +0000
@@ -23,14 +23,6 @@
#include "server.h"
-struct thread_info
-{
- struct inferior_list_entry entry;
- void *target_data;
- void *regcache_data;
- unsigned int gdb_id;
-};
-
struct inferior_list all_processes;
struct inferior_list all_threads;
struct inferior_list all_dlls;
Index: src/gdb/gdbserver/mem-break.c
===================================================================
--- src.orig/gdb/gdbserver/mem-break.c 2010-03-16 17:43:42.000000000 +0000
+++ src/gdb/gdbserver/mem-break.c 2010-03-16 17:53:08.000000000 +0000
@@ -32,38 +32,75 @@ struct breakpoint
CORE_ADDR pc;
unsigned char old_data[MAX_BREAKPOINT_LEN];
- /* Non-zero iff we are stepping over this breakpoint. */
- int reinserting;
-
- /* Non-NULL iff this breakpoint was inserted to step over
- another one. Points to the other breakpoint (which is also
- in the *next chain somewhere). */
- struct breakpoint *breakpoint_to_reinsert;
+ /* Non-zero if this breakpoint is currently inserted in the
+ inferior. */
+ int inserted;
/* Function to call when we hit this breakpoint. If it returns 1,
- the breakpoint will be deleted; 0, it will be reinserted for
- another round. */
+ the breakpoint shall be deleted; 0, it will be left inserted. */
int (*handler) (CORE_ADDR);
};
-void
-set_breakpoint_at (CORE_ADDR where, int (*handler) (CORE_ADDR))
+static struct breakpoint *
+set_raw_breakpoint_at (CORE_ADDR where)
{
struct process_info *proc = current_process ();
struct breakpoint *bp;
+ int err;
if (breakpoint_data == NULL)
error ("Target does not support breakpoints.");
- bp = xmalloc (sizeof (struct breakpoint));
- memset (bp, 0, sizeof (struct breakpoint));
+ bp = xcalloc (1, sizeof (*bp));
+ bp->pc = where;
- (*the_target->read_memory) (where, bp->old_data,
- breakpoint_len);
- (*the_target->write_memory) (where, breakpoint_data,
- breakpoint_len);
+ err = (*the_target->read_memory) (where, bp->old_data,
+ breakpoint_len);
+ if (err != 0)
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "Failed to read shadow memory of"
+ " breakpoint at 0x%s (%s).\n",
+ paddress (where), strerror (err));
+ free (bp);
+ return NULL;
+ }
- bp->pc = where;
+ err = (*the_target->write_memory) (where, breakpoint_data,
+ breakpoint_len);
+ if (err != 0)
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "Failed to insert breakpoint at 0x%s (%s).\n",
+ paddress (where), strerror (err));
+ free (bp);
+ return NULL;
+ }
+
+ /* Link the breakpoint in. */
+ bp->inserted = 1;
+ bp->next = proc->breakpoints;
+ proc->breakpoints = bp;
+ return bp;
+}
+
+void
+set_breakpoint_at (CORE_ADDR where, int (*handler) (CORE_ADDR))
+{
+ struct process_info *proc = current_process ();
+ struct breakpoint *bp;
+
+ bp = set_raw_breakpoint_at (where);
+
+ if (bp == NULL)
+ {
+ /* warn? */
+ return;
+ }
+
+ bp = xcalloc (1, sizeof (struct breakpoint));
bp->handler = handler;
bp->next = proc->breakpoints;
@@ -123,97 +160,145 @@ delete_breakpoint_at (CORE_ADDR addr)
delete_breakpoint (bp);
}
-static int
-reinsert_breakpoint_handler (CORE_ADDR stop_pc)
+void
+set_reinsert_breakpoint (CORE_ADDR stop_at)
{
- struct breakpoint *stop_bp, *orig_bp;
-
- stop_bp = find_breakpoint_at (stop_pc);
- if (stop_bp == NULL)
- error ("lost the stopping breakpoint.");
-
- orig_bp = stop_bp->breakpoint_to_reinsert;
- if (orig_bp == NULL)
- error ("no breakpoint to reinsert");
-
- (*the_target->write_memory) (orig_bp->pc, breakpoint_data,
- breakpoint_len);
- orig_bp->reinserting = 0;
- return 1;
+ set_breakpoint_at (stop_at, NULL);
}
void
-reinsert_breakpoint_by_bp (CORE_ADDR stop_pc, CORE_ADDR stop_at)
+delete_reinsert_breakpoints (void)
{
- struct breakpoint *bp, *orig_bp;
+ struct process_info *proc = current_process ();
+ struct breakpoint *bp, **bp_link;
- orig_bp = find_breakpoint_at (stop_pc);
- if (orig_bp == NULL)
- error ("Could not find original breakpoint in list.");
+ bp = proc->breakpoints;
+ bp_link = &proc->breakpoints;
- set_breakpoint_at (stop_at, reinsert_breakpoint_handler);
+ while (bp)
+ {
+ *bp_link = bp->next;
+ delete_breakpoint (bp);
+ bp = *bp_link;
+ }
+}
- bp = find_breakpoint_at (stop_at);
- if (bp == NULL)
- error ("Could not find breakpoint in list (reinserting by breakpoint).");
- bp->breakpoint_to_reinsert = orig_bp;
+static void
+uninsert_breakpoint (struct breakpoint *bp)
+{
+ if (bp->inserted)
+ {
+ int err;
- (*the_target->write_memory) (orig_bp->pc, orig_bp->old_data,
- breakpoint_len);
- orig_bp->reinserting = 1;
+ bp->inserted = 0;
+ err = (*the_target->write_memory) (bp->pc, bp->old_data,
+ breakpoint_len);
+ if (err != 0)
+ {
+ bp->inserted = 1;
+
+ if (debug_threads)
+ fprintf (stderr,
+ "Failed to uninsert raw breakpoint at 0x%s (%s).\n",
+ paddress (bp->pc), strerror (err));
+ }
+ }
}
void
-uninsert_breakpoint (CORE_ADDR stopped_at)
+uninsert_breakpoints_at (CORE_ADDR pc)
{
struct breakpoint *bp;
- bp = find_breakpoint_at (stopped_at);
+ bp = find_breakpoint_at (pc);
if (bp == NULL)
- error ("Could not find breakpoint in list (uninserting).");
+ {
+ /* This can happen when we remove all breakpoints while handling
+ a step-over. */
+ if (debug_threads)
+ fprintf (stderr,
+ "Could not find breakpoint at 0x%s "
+ "in list (uninserting).\n",
+ paddress (pc));
+ return;
+ }
- (*the_target->write_memory) (bp->pc, bp->old_data,
- breakpoint_len);
- bp->reinserting = 1;
+ if (bp->inserted)
+ uninsert_breakpoint (bp);
}
-void
-reinsert_breakpoint (CORE_ADDR stopped_at)
+static void
+reinsert_breakpoint (struct breakpoint *bp)
{
- struct breakpoint *bp;
+ int err;
- bp = find_breakpoint_at (stopped_at);
- if (bp == NULL)
- error ("Could not find breakpoint in list (uninserting).");
- if (! bp->reinserting)
+ if (bp->inserted)
error ("Breakpoint already inserted at reinsert time.");
- (*the_target->write_memory) (bp->pc, breakpoint_data,
- breakpoint_len);
- bp->reinserting = 0;
+ err = (*the_target->write_memory) (bp->pc, breakpoint_data,
+ breakpoint_len);
+ if (err == 0)
+ bp->inserted = 1;
+ else if (debug_threads)
+ fprintf (stderr,
+ "Failed to reinsert breakpoint at 0x%s (%s).\n",
+ paddress (bp->pc), strerror (err));
}
-int
-check_breakpoints (CORE_ADDR stop_pc)
+void
+reinsert_breakpoints_at (CORE_ADDR pc)
{
struct breakpoint *bp;
- bp = find_breakpoint_at (stop_pc);
+ bp = find_breakpoint_at (pc);
if (bp == NULL)
- return 0;
- if (bp->reinserting)
{
- warning ("Hit a removed breakpoint?");
- return 0;
+ /* This can happen when we remove all breakpoints while handling
+ a step-over. */
+ if (debug_threads)
+ fprintf (stderr,
+ "Could not find breakpoint at 0x%s "
+ "in list (reinserting).\n",
+ paddress (pc));
+ return;
}
- if ((*bp->handler) (bp->pc))
+ reinsert_breakpoint (bp);
+}
+
+void
+check_breakpoints (CORE_ADDR stop_pc)
+{
+ struct process_info *proc = current_process ();
+ struct breakpoint *bp, **bp_link;
+
+ bp = proc->breakpoints;
+ bp_link = &proc->breakpoints;
+
+ while (bp)
{
- delete_breakpoint (bp);
- return 2;
+ if (bp->pc == stop_pc)
+ {
+ if (!bp->inserted)
+ {
+ warning ("Hit a removed breakpoint?");
+ return 0;
+ }
+
+ if (bp->handler != NULL && (*bp->handler) (stop_pc))
+ {
+ *bp_link = bp->next;
+
+ delete_breakpoint (bp);
+
+ bp = *bp_link;
+ continue;
+ }
+ }
+
+ bp_link = &bp->next;
+ bp = *bp_link;
}
- else
- return 1;
}
void
@@ -223,6 +308,32 @@ set_breakpoint_data (const unsigned char
breakpoint_len = bp_len;
}
+int
+breakpoint_here (CORE_ADDR addr)
+{
+ struct process_info *proc = current_process ();
+ struct breakpoint *bp;
+
+ for (bp = proc->breakpoints; bp != NULL; bp = bp->next)
+ if (bp->pc == addr)
+ return 1;
+
+ return 0;
+}
+
+int
+breakpoint_inserted_here (CORE_ADDR addr)
+{
+ struct process_info *proc = current_process ();
+ struct breakpoint *bp;
+
+ for (bp = proc->breakpoints; bp != NULL; bp = bp->next)
+ if (bp->pc == addr && bp->inserted)
+ return 1;
+
+ return 0;
+}
+
void
check_mem_read (CORE_ADDR mem_addr, unsigned char *buf, int mem_len)
{
@@ -288,7 +399,7 @@ check_mem_write (CORE_ADDR mem_addr, uns
buf_offset = start - mem_addr;
memcpy (bp->old_data + copy_offset, buf + buf_offset, copy_len);
- if (bp->reinserting == 0)
+ if (bp->inserted)
memcpy (buf + buf_offset, breakpoint_data + copy_offset, copy_len);
}
}
Index: src/gdb/gdbserver/mem-break.h
===================================================================
--- src.orig/gdb/gdbserver/mem-break.h 2010-03-16 17:43:42.000000000 +0000
+++ src/gdb/gdbserver/mem-break.h 2010-03-16 17:53:08.000000000 +0000
@@ -24,6 +24,19 @@
/* Breakpoints are opaque. */
+/* Returns TRUE if breakpoints are supported on this target. */
+
+int breakpoints_supported (void);
+
+/* Returns TRUE if there's any breakpoint at ADDR in our tables,
+ inserted, or not. */
+
+int breakpoint_here (CORE_ADDR addr);
+
+/* Returns TRUE if there's any inserted breakpoint set at ADDR. */
+
+int breakpoint_inserted_here (CORE_ADDR addr);
+
/* Create a new breakpoint at WHERE, and call HANDLER when
it is hit. HANDLER should return 1 if the breakpoint
should be deleted, 0 otherwise. */
@@ -36,24 +49,28 @@ void set_breakpoint_at (CORE_ADDR where,
void delete_breakpoint_at (CORE_ADDR addr);
-/* Create a reinsertion breakpoint at STOP_AT for the breakpoint
- currently at STOP_PC (and temporarily remove the breakpoint at
- STOP_PC). */
+/* Set a reinsert breakpoint at STOP_AT. */
+
+void set_reinsert_breakpoint (CORE_ADDR stop_at);
+
+/* Delete all reinsert breakpoints. */
-void reinsert_breakpoint_by_bp (CORE_ADDR stop_pc, CORE_ADDR stop_at);
+void delete_reinsert_breakpoints (void);
-/* Change the status of the breakpoint at WHERE to inserted. */
+/* Reinsert breakpoints at WHERE (and change their status to
+ inserted). */
-void reinsert_breakpoint (CORE_ADDR where);
+void reinsert_breakpoints_at (CORE_ADDR where);
-/* Change the status of the breakpoint at WHERE to uninserted. */
+/* Uninsert breakpoints at WHERE (and change their status to
+ uninserted). This still leaves the breakpoints in the table. */
-void uninsert_breakpoint (CORE_ADDR where);
+void uninsert_breakpoints_at (CORE_ADDR where);
/* See if any breakpoint claims ownership of STOP_PC. Call the handler for
the breakpoint, if found. */
-int check_breakpoints (CORE_ADDR stop_pc);
+void check_breakpoints (CORE_ADDR stop_pc);
/* See if any breakpoints shadow the target memory area from MEM_ADDR
to MEM_ADDR + MEM_LEN. Update the data already read from the target
Index: src/gdb/gdbserver/server.h
===================================================================
--- src.orig/gdb/gdbserver/server.h 2010-03-16 17:44:01.000000000 +0000
+++ src/gdb/gdbserver/server.h 2010-03-16 17:53:08.000000000 +0000
@@ -162,8 +162,25 @@ struct inferior_list_entry
struct inferior_list_entry *next;
};
-/* Opaque type for user-visible threads. */
struct thread_info;
+struct process_info;
+struct regcache;
+
+#include "regcache.h"
+#include "gdb/signals.h"
+#include "gdb_signals.h"
+#include "target.h"
+#include "mem-break.h"
+
+struct thread_info
+{
+ struct inferior_list_entry entry;
+ void *target_data;
+ void *regcache_data;
+
+ /* The last wait status reported for this thread. */
+ struct target_waitstatus last_status;
+};
struct dll_info
{
@@ -203,12 +220,6 @@ struct process_info
struct process_info *current_process (void);
struct process_info *get_thread_process (struct thread_info *);
-#include "regcache.h"
-#include "gdb/signals.h"
-#include "gdb_signals.h"
-#include "target.h"
-#include "mem-break.h"
-
/* Target-specific functions */
void initialize_low ();
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: Teach gdbserver to step over internal breakpoints
2010-03-16 18:40 Teach gdbserver to step over internal breakpoints Pedro Alves
@ 2010-03-24 0:05 ` Pedro Alves
2010-03-24 12:13 ` Pedro Alves
0 siblings, 1 reply; 6+ messages in thread
From: Pedro Alves @ 2010-03-24 0:05 UTC (permalink / raw)
To: gdb-patches
On Tuesday 16 March 2010 18:39:52, Pedro Alves wrote:
> This tests cleanly on x86_64-linux, native gdbserver, with
> a small hack to force it to use the thread event breakpoint.
> I'll confirm this doesn't break software-single-step archs
> before commiting, although I don't expect to, since I had
> tested an earlier version of this patch on ARM.
I've now tested this on arm-none-linux-gnueabi,
and there were no regressions. I've also re-tested this
on x86_64-linux. It's now checked in.
> 2010-03-16 Pedro Alves <pedro@codesourcery.com>
>
> gdb/gdbserver/
> * linux-low.c (can_hardware_single_step): New.
> (supports_breakpoints): New.
> (handle_extended_wait): If stopping threads, read the stop pc of
> the new cloned LWP.
> (get_pc): New.
> (get_stop_pc): Add `lwp' parameter. Handle it. Bail out if the
> low target doesn't support retrieving the PC.
> (add_lwp): Set last_resume_kind to resume_continue.
> (linux_attach_lwp_1): Adjust comments. Always set stop_expected.
> (linux_attach): Don't clear stop_expected. Set the lwp's
> last_resume_kind to resume_stop.
> (linux_detach_one_lwp): Don't check for removed breakpoints.
> (check_removed_breakpoint): Delete.
> (status_pending_p): Rename to ...
> (status_pending_p_callback): ... this. Don't check for removed
> breakpoints. Don't consider threads that are stopped from GDB's
> perspective.
> (linux_wait_for_lwp): Always read the stop_pc here.
> (cancel_breakpoint): New.
> (step_over_bkpt): New global.
> (linux_wait_for_event_1): Implement stepping over breakpoints.
> (gdb_wants_lwp_stopped): New.
> (gdb_wants_all_stopped): New.
> (linux_wait_1): Tag threads as gdb-wants-stopped. Cancel finished
> single-step traps here. Store the thread's last reported target
> wait status.
> (send_sigstop): Don't clear stop_expected. Always set it,
> instead.
> (mark_lwp_dead): Remove reference to pending_is_breakpoint.
> (cancel_finished_single_step): New.
> (cancel_finished_single_steps): New.
> (wait_for_sigstop): Don't cancel finished single-step traps here.
> (linux_resume_one_lwp): Don't check for removed breakpoints.
> Don't set `step' on non-hardware step archs.
> (linux_set_resume_request): Ignore resume_stop requests if already
> stopping or stopped. Set the lwp's last_resume_kind.
> (resume_status_pending_p): Don't check for removed breakpoints.
> (need_step_over_p): New.
> (start_step_over): New.
> (finish_step_over): New.
> (linux_resume_one_thread): Always queue a sigstop for resume_stop
> requests. Clear the thread's last reported target waitstatus.
> Don't use the `suspended' flag. Don't consider pending breakpoints.
> (linux_resume): Start a step-over if necessary.
> (proceed_one_lwp): New.
> (proceed_all_lwps): New.
> (unstop_all_lwps): New.
> * linux-low.h (struct lwp_info): Rewrite comment for the
> `suspended' flag. Add the `stop_pc' field. Delete the
> `pending_stop_pc' field. Tweak the `stepping' flag's comment.
> Add `'last_resume_kind' and `need_step_over' fields.
> * inferiors.c (struct thread_info): Delete, moved elsewhere.
> * mem-break.c (struct breakpoint): Delete `reinserting' flag.
> Delete `breakpoint_to_reinsert' field. New flag `inserted'.
> (set_raw_breakpoint_at): New.
> (set_breakpoint_at): Rewrite to use it.
> (reinsert_breakpoint_handler): Delete.
> (set_reinsert_breakpoint): New.
> (reinsert_breakpoint_by_bp): Delete.
> (delete_reinsert_breakpoints): New.
> (uninsert_breakpoint): Rewrite.
> (uninsert_breakpoints_at): New.
> (reinsert_breakpoint): Rewrite.
> (reinsert_breakpoints_at): New.
> (check_breakpoints): Rewrite.
> (breakpoint_here): New.
> (breakpoint_inserted_here): New.
> (check_mem_read): Adjust.
> * mem-break.h (breakpoints_supported, breakpoint_here)
> (breakpoint_inserted_here, set_reinsert_breakpoint): Declare.
> (reinsert_breakpoint_by_bp): Delete declaration.
> (delete_reinsert_breakpoints): Declare.
> (reinsert_breakpoint): Delete declaration.
> (reinsert_breakpoints_at): Declare.
> (uninsert_breakpoint): Delete declaration.
> (uninsert_breakpoints_at): Declare.
> (check_breakpoints): Adjust prototype.
> * server.h: Adjust include order.
> (struct thread_info): Declare here. Add a `last_status' field.
--
Pedro Alves
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: Teach gdbserver to step over internal breakpoints
2010-03-24 0:05 ` Pedro Alves
@ 2010-03-24 12:13 ` Pedro Alves
2010-03-24 21:22 ` Pedro Alves
0 siblings, 1 reply; 6+ messages in thread
From: Pedro Alves @ 2010-03-24 12:13 UTC (permalink / raw)
To: gdb-patches
On Wednesday 24 March 2010 00:05:38, Pedro Alves wrote:
> I've now tested this on arm-none-linux-gnueabi,
> and there were no regressions.
I busted this. I forgot to force a thread event
breakpoint to exercise the code paths on ARM. I redid the testing
overnight, and it revealed a bunch of regressions; it turns out
I trimmed the original patch too much --- there are some needed
missing bits I left behind. I'm working on fixing this.
> I've also re-tested this
> on x86_64-linux. It's now checked in.
--
Pedro Alves
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: Teach gdbserver to step over internal breakpoints
2010-03-24 12:13 ` Pedro Alves
@ 2010-03-24 21:22 ` Pedro Alves
2010-03-24 21:34 ` Pedro Alves
2010-03-24 22:00 ` Pedro Alves
0 siblings, 2 replies; 6+ messages in thread
From: Pedro Alves @ 2010-03-24 21:22 UTC (permalink / raw)
To: gdb-patches
On Wednesday 24 March 2010 12:13:31, Pedro Alves wrote:
> On Wednesday 24 March 2010 00:05:38, Pedro Alves wrote:
> > I've now tested this on arm-none-linux-gnueabi,
> > and there were no regressions.
>
> I busted this. I forgot to force a thread event
> breakpoint to exercise the code paths on ARM. I redid the testing
> overnight, and it revealed a bunch of regressions; it turns out
> I trimmed the original patch too much --- there are some needed
> missing bits I left behind. I'm working on fixing this.
Here's one of the bits. The first issue one trips on with
current baseline, is that delete_breakpoint never worked with
more than one breakpoint in the breakpoint list correctly;
it can easily loop forever, and in fact, it's what was happening
on my testing:
delete_breakpoint:
while (cur->next)
{
if (cur->next == bp)
{
cur->next = bp->next;
(*the_target->write_memory) (bp->pc, bp->old_data,
breakpoint_len);
free (bp);
return;
}
}
The other issue is that `delete_reinsert_breakpoints' was never
deleting anything, as it was removing the breakpoint from the
list before calling delete_breakpoint. Then, delete_breakpoint
would not find the breakpoint to delete in the breakpoint
list. :-) Also, we now need to distinguish the types of
breakpoints. Presently, "reinsert breakpoints", something akin
of software single-step breakpoints, and "other breakpoints",
where thread event breakpoints and in the future tracepoints
will go to. There will be yet another type in the future
"gdb breakpoints".
This fixes most of the regressions forcing a thread event
breakpoint on ARM revealed. The next patch will fix the
rest. Also tested on x86_64, with and without a thread
event breakpoint, and also with a had that makes x86-64
use reinsert breakpoints. I'll show that for the archives
shortly.
Applied.
--
Pedro Alves
2010-03-24 Pedro Alves <pedro@codesourcery.com>
gdb/gdbserver/
* mem-break.c (enum bkpt_type): New.
(struct breakpoint): New field `type'.
(set_breakpoint_at): Change return type to struct breakpoint
pointer. Set type to `other_breakpoint' by default.
(delete_breakpoint): Rewrite, supporting more than one breakpoint
in the breakpoint list.
(delete_reinsert_breakpoints): Only delete reinsert breakpoints.
(reinsert_breakpoint): Rename to ...
(reinsert_raw_breakpoint): ... this.
(reinsert_breakpoints_at): Adjust.
* mem-break.h (struct breakpoint): Declare.
(set_breakpoint_at): Change return type to struct breakpoint
pointer.
---
gdb/gdbserver/mem-break.c | 85 +++++++++++++++++++++++++++++++++-------------
gdb/gdbserver/mem-break.h | 5 +-
2 files changed, 64 insertions(+), 26 deletions(-)
Index: src/gdb/gdbserver/mem-break.c
===================================================================
--- src.orig/gdb/gdbserver/mem-break.c 2010-03-24 11:32:21.000000000 +0000
+++ src/gdb/gdbserver/mem-break.c 2010-03-24 11:59:00.000000000 +0000
@@ -26,6 +26,17 @@ int breakpoint_len;
#define MAX_BREAKPOINT_LEN 8
+/* The type of a breakpoint. */
+enum bkpt_type
+ {
+ /* A basic-software-single-step breakpoint. */
+ reinsert_breakpoint,
+
+ /* Any other breakpoint type that doesn't require specific
+ treatment goes here. E.g., an event breakpoint. */
+ other_breakpoint,
+ };
+
struct breakpoint
{
struct breakpoint *next;
@@ -36,11 +47,16 @@ struct breakpoint
inferior. */
int inserted;
+ /* The breakpoint's type. */
+ enum bkpt_type type;
+
/* Function to call when we hit this breakpoint. If it returns 1,
the breakpoint shall be deleted; 0, it will be left inserted. */
int (*handler) (CORE_ADDR);
};
+static void uninsert_breakpoint (struct breakpoint *bp);
+
static struct breakpoint *
set_raw_breakpoint_at (CORE_ADDR where)
{
@@ -86,7 +102,7 @@ set_raw_breakpoint_at (CORE_ADDR where)
return bp;
}
-void
+struct breakpoint *
set_breakpoint_at (CORE_ADDR where, int (*handler) (CORE_ADDR))
{
struct process_info *proc = current_process ();
@@ -97,42 +113,45 @@ set_breakpoint_at (CORE_ADDR where, int
if (bp == NULL)
{
/* warn? */
- return;
+ return NULL;
}
bp = xcalloc (1, sizeof (struct breakpoint));
+ bp->type = other_breakpoint;
bp->handler = handler;
bp->next = proc->breakpoints;
proc->breakpoints = bp;
+
+ return bp;
}
static void
-delete_breakpoint (struct breakpoint *bp)
+delete_breakpoint (struct breakpoint *todel)
{
struct process_info *proc = current_process ();
- struct breakpoint *cur;
+ struct breakpoint *bp, **bp_link;
- if (proc->breakpoints == bp)
- {
- proc->breakpoints = bp->next;
- (*the_target->write_memory) (bp->pc, bp->old_data,
- breakpoint_len);
- free (bp);
- return;
- }
- cur = proc->breakpoints;
- while (cur->next)
+ bp = proc->breakpoints;
+ bp_link = &proc->breakpoints;
+
+ while (bp)
{
- if (cur->next == bp)
+ if (bp == todel)
{
- cur->next = bp->next;
- (*the_target->write_memory) (bp->pc, bp->old_data,
- breakpoint_len);
+ *bp_link = bp->next;
+
+ uninsert_breakpoint (bp);
free (bp);
return;
}
+ else
+ {
+ bp_link = &bp->next;
+ bp = *bp_link;
+ }
}
+
warning ("Could not find breakpoint in list.");
}
@@ -163,7 +182,11 @@ delete_breakpoint_at (CORE_ADDR addr)
void
set_reinsert_breakpoint (CORE_ADDR stop_at)
{
- set_breakpoint_at (stop_at, NULL);
+ struct breakpoint *bp;
+
+ bp = set_breakpoint_at (stop_at, NULL);
+
+ bp->type = reinsert_breakpoint;
}
void
@@ -177,9 +200,23 @@ delete_reinsert_breakpoints (void)
while (bp)
{
- *bp_link = bp->next;
- delete_breakpoint (bp);
- bp = *bp_link;
+ if (bp->type == reinsert_breakpoint)
+ {
+ *bp_link = bp->next;
+
+ /* If something goes wrong, maybe this is a shared library
+ breakpoint, and the shared library has been unmapped.
+ Assume the breakpoint is gone anyway. */
+ uninsert_breakpoint (bp);
+ free (bp);
+
+ bp = *bp_link;
+ }
+ else
+ {
+ bp_link = &bp->next;
+ bp = *bp_link;
+ }
}
}
@@ -228,7 +265,7 @@ uninsert_breakpoints_at (CORE_ADDR pc)
}
static void
-reinsert_breakpoint (struct breakpoint *bp)
+reinsert_raw_breakpoint (struct breakpoint *bp)
{
int err;
@@ -263,7 +300,7 @@ reinsert_breakpoints_at (CORE_ADDR pc)
return;
}
- reinsert_breakpoint (bp);
+ reinsert_raw_breakpoint (bp);
}
void
Index: src/gdb/gdbserver/mem-break.h
===================================================================
--- src.orig/gdb/gdbserver/mem-break.h 2010-03-24 11:47:23.000000000 +0000
+++ src/gdb/gdbserver/mem-break.h 2010-03-24 11:47:43.000000000 +0000
@@ -23,6 +23,7 @@
#define MEM_BREAK_H
/* Breakpoints are opaque. */
+struct breakpoint;
/* Returns TRUE if breakpoints are supported on this target. */
@@ -41,8 +42,8 @@ int breakpoint_inserted_here (CORE_ADDR
it is hit. HANDLER should return 1 if the breakpoint
should be deleted, 0 otherwise. */
-void set_breakpoint_at (CORE_ADDR where,
- int (*handler) (CORE_ADDR));
+struct breakpoint *set_breakpoint_at (CORE_ADDR where,
+ int (*handler) (CORE_ADDR));
/* Delete a breakpoint previously inserted at ADDR with
set_breakpoint_at. */
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: Teach gdbserver to step over internal breakpoints
2010-03-24 21:22 ` Pedro Alves
@ 2010-03-24 21:34 ` Pedro Alves
2010-03-24 22:00 ` Pedro Alves
1 sibling, 0 replies; 6+ messages in thread
From: Pedro Alves @ 2010-03-24 21:34 UTC (permalink / raw)
To: gdb-patches
On Wednesday 24 March 2010 21:22:39, Pedro Alves wrote:
> On Wednesday 24 March 2010 12:13:31, Pedro Alves wrote:
> > On Wednesday 24 March 2010 00:05:38, Pedro Alves wrote:
> > > I've now tested this on arm-none-linux-gnueabi,
> > > and there were no regressions.
> >
> > I busted this. I forgot to force a thread event
> > breakpoint to exercise the code paths on ARM. I redid the testing
> > overnight, and it revealed a bunch of regressions; it turns out
> > I trimmed the original patch too much --- there are some needed
> > missing bits I left behind. I'm working on fixing this.
>
> Here's one of the bits.
And here's the other bit. The issue is that if we get
rid of leaving breakpoints pending, we need to handle
starvation some other way, otherwise, schedlock.exp complains.
I took a deep breath, thought about this for a long while, and
finaly decided that doing what native GDB does gives less
headaches for both future changes, and is better in terms
of consolidating native gdb and gdbserver codebases.
This also moves the internal breakpoint handling one layer
up, from linux_wait_for_event_1 to linux_nat_1, which is
better since it's one step closer to being moved to common
gdbserver event loop code. Still not there, because that
would require also moving the "stop-all-threads" bits
for all-stop to common code as well, and then that requires
thinking what to do with targets where the debug api
implicitly stops all threads for us, like the Win32 debug API.
Tested on arm-linux-gnueabi and x86_64-linux, both with forcing
a thread event breakpoint (simulating LinuxThreads), and without
hacking anything. I've applied it.
I don't see any regressions cause by yesterday's patch
left.
--
Pedro Alves
2010-03-24 Pedro Alves <pedro@codesourcery.com>
gdb/gdbserver/
* linux-low.c (status_pending_p_callback): Fix comment.
(linux_wait_for_event_1): Move most of the internal breakpoint
handling from here...
(linux_wait_1): ... to here.
(count_events_callback): New.
(select_singlestep_lwp_callback): New.
(select_event_lwp_callback): New.
(cancel_breakpoints_callback): New.
(select_event_lwp): New.
(linux_wait_1): Simplify internal breakpoint handling. Give equal
priority to all LWPs that have had events that should be reported
to the client. Cancel breakpoints when about to reporting the
event to the client, not while stopping lwps. No longer cancel
finished single-steps here.
(cancel_finished_single_step): Delete.
(cancel_finished_single_steps): Delete.
---
gdb/gdbserver/linux-low.c | 606 +++++++++++++++++++++++-----------------------
1 file changed, 314 insertions(+), 292 deletions(-)
Index: src/gdb/gdbserver/linux-low.c
===================================================================
--- src.orig/gdb/gdbserver/linux-low.c 2010-03-24 20:58:30.000000000 +0000
+++ src/gdb/gdbserver/linux-low.c 2010-03-24 20:59:35.000000000 +0000
@@ -146,7 +146,6 @@ static void mark_lwp_dead (struct lwp_in
static int linux_core_of_thread (ptid_t ptid);
static void proceed_all_lwps (void);
static void unstop_all_lwps (struct lwp_info *except);
-static void cancel_finished_single_steps (struct lwp_info *except);
static int finish_step_over (struct lwp_info *lwp);
static CORE_ADDR get_stop_pc (struct lwp_info *lwp);
static int kill_lwp (unsigned long lwpid, int signo);
@@ -934,8 +933,7 @@ linux_thread_alive (ptid_t ptid)
return 0;
}
-/* Return 1 if this lwp has an interesting status pending. This
- function may silently resume an inferior lwp. */
+/* Return 1 if this lwp has an interesting status pending. */
static int
status_pending_p_callback (struct inferior_list_entry *entry, void *arg)
{
@@ -1189,10 +1187,8 @@ ptid_t step_over_bkpt;
static int
linux_wait_for_event_1 (ptid_t ptid, int *wstat, int options)
{
- CORE_ADDR stop_pc;
struct lwp_info *event_child, *requested_child;
- again:
event_child = NULL;
requested_child = NULL;
@@ -1232,20 +1228,7 @@ linux_wait_for_event_1 (ptid_t ptid, int
events. */
while (1)
{
- int step_over_finished = 0;
- int bp_explains_trap = 0;
- int cancel_sigtrap;
-
- if (ptid_equal (step_over_bkpt, null_ptid))
- event_child = linux_wait_for_lwp (ptid, wstat, options);
- else
- {
- if (debug_threads)
- fprintf (stderr, "step_over_bkpt set [%s], doing a blocking wait\n",
- target_pid_to_str (step_over_bkpt));
- event_child = linux_wait_for_lwp (step_over_bkpt,
- wstat, options & ~WNOHANG);
- }
+ event_child = linux_wait_for_lwp (ptid, wstat, options);
if ((options & WNOHANG) && event_child == NULL)
{
@@ -1380,218 +1363,6 @@ linux_wait_for_event_1 (ptid_t ptid, int
}
}
- cancel_sigtrap = (stopping_threads
- || event_child->last_resume_kind == resume_stop);
-
- /* Do not allow nested internal breakpoint handling, or leave
- the unadjusted PCs visible to GDB. Simply cancel the
- breakpoint now, and eventually when the thread is resumed, it
- will trap again, if the breakpoint is still there by
- then. */
- if (WIFSTOPPED (*wstat)
- && WSTOPSIG (*wstat) == SIGTRAP
- && cancel_sigtrap)
- {
- if (debug_threads)
- fprintf (stderr, "Got a nested SIGTRAP while stopping threads\n");
-
- if (cancel_breakpoint (event_child))
- {
- if (debug_threads)
- fprintf (stderr, " bkpt canceled\n");
-
- /* We don't resume immediately to collect the SIGSTOP,
- due to other reasons we may care for this SIGTRAP
- below. Care must be taken to not process anything
- breakpoint related though from this point on. */
- }
- }
-
- /* If this event was not handled above, and is not a SIGTRAP, we
- report it. SIGILL and SIGSEGV are also treated as traps in
- case a breakpoint is inserted at the current PC. If this
- target does not support breakpoints, we also report the
- SIGTRAP without further processing; it's of no concern to
- us. */
- if (!WIFSTOPPED (*wstat)
- || !supports_breakpoints ()
- || (WSTOPSIG (*wstat) != SIGTRAP
- && WSTOPSIG (*wstat) != SIGILL
- && WSTOPSIG (*wstat) != SIGSEGV)
- /* Only handle SIGILL or SIGSEGV if we've hit a recognized
- breakpoint. */
- || (WSTOPSIG (*wstat) != SIGTRAP
- && !(*the_low_target.breakpoint_at) (event_child->stop_pc)))
- {
- if (debug_threads && WIFSTOPPED (*wstat))
- fprintf (stderr, "Reporting signal %d for LWP %ld.\n",
- WSTOPSIG (*wstat), lwpid_of (event_child));
-
- /* If we were stepping over a breakpoint, this signal
- arriving means we didn't manage to move past the
- breakpoint location. Cancel the operation for now --- it
- will be handled again on the next resume, if required
- (the breakpoint may be removed meanwhile, for
- example). */
- if (finish_step_over (event_child))
- {
- event_child->need_step_over = 1;
- unstop_all_lwps (event_child);
- }
- return lwpid_of (event_child);
- }
-
- stop_pc = event_child->stop_pc;
-
- /* Handle anything that requires bookkeeping before deciding to
- report the event or continue waiting. */
-
- /* First check if we can explain the SIGTRAP with an internal
- breakpoint, or if we should possibly report the event to GDB.
- Do this before anything that may remove or insert a
- breakpoint. */
- bp_explains_trap = breakpoint_inserted_here (stop_pc);
-
- /* We have a SIGTRAP, possibly a step-over dance has just
- finished. If so, tweak the state machine accordingly,
- reinsert breakpoints and delete any reinsert (software
- single-step) breakpoints. */
- step_over_finished = finish_step_over (event_child);
-
- /* Now invoke the callbacks of any breakpoints there. */
- if (!cancel_sigtrap)
- check_breakpoints (stop_pc);
-
- /* If we're stopping threads, resume once more to collect the
- SIGSTOP, and do nothing else. Note that we don't set
- need_step_over. If this predicate matches, then we've
- cancelled the SIGTRAP before reaching here, and we do want
- any breakpoint at STOP_PC to be re-hit on resume. */
- if (stopping_threads
- || event_child->last_resume_kind == resume_stop)
- {
- gdb_assert (cancel_sigtrap);
-
- if (step_over_finished)
- unstop_all_lwps (event_child);
-
- if (debug_threads)
- {
- if (event_child->last_resume_kind == resume_stop)
- fprintf (stderr, "Bailing out; GDB wanted the LWP to stop.\n");
- else if (stopping_threads)
- fprintf (stderr, "Bailing out; stopping threads.\n");
-
- if (bp_explains_trap)
- fprintf (stderr, " Hit a breakpoint.\n");
- if (step_over_finished)
- fprintf (stderr, " Step-over finished.\n");
- if (event_child->stopped_by_watchpoint)
- fprintf (stderr, " Stopped by watchpoint.\n");
- }
-
- /* Leave these pending. */
- if (event_child->stopped_by_watchpoint)
- return lwpid_of (event_child);
-
- /* Otherwise, there may or not be a pending SIGSTOP. If
- there isn't one, queue one up. In any case, go back to
- the event loop to collect it. Don't return yet, as we
- don't want this SIGTRAP to be left pending. Note that
- since we cancelled the breakpoint above, the PC is
- already adjusted, and hence get_stop_pc returns the
- correct PC when we collect the SIGSTOP. */
-
- if (!event_child->stop_expected)
- {
- event_child->stop_expected = 1;
- kill_lwp (lwpid_of (event_child), SIGSTOP);
- }
-
- /* Clear the single-stepping flag and SIGTRAP as we resume. */
- linux_resume_one_lwp (event_child, 0, 0, NULL);
- continue;
- }
-
- /* We have all the data we need. Either report the event to
- GDB, or resume threads and keep waiting for more. */
-
- /* Check If GDB would be interested in this event. If GDB
- wanted this thread to single step, we always want to report
- the SIGTRAP, and let GDB handle it. */
- if ((event_child->last_resume_kind == resume_step)
- || event_child->stopped_by_watchpoint)
- {
- if (step_over_finished)
- unstop_all_lwps (event_child);
-
- if (debug_threads)
- {
- if (event_child->last_resume_kind == resume_step)
- fprintf (stderr, "GDB wanted to single-step, reporting event.\n");
- if (event_child->stopped_by_watchpoint)
- fprintf (stderr, "Stopped by watchpoint.\n");
- }
- }
- /* We found no reason GDB would want us to stop. We either hit
- one of our own breakpoints, or finished an internal step GDB
- shouldn't know about. */
- else if (step_over_finished || bp_explains_trap)
- {
- if (debug_threads)
- {
- if (bp_explains_trap)
- fprintf (stderr, "Hit a gdbserver breakpoint.\n");
- if (step_over_finished)
- fprintf (stderr, "Step-over finished.\n");
- }
-
- /* If we stepped or ran into an internal breakpoint, we've
- already handled it. So next time we resume (from this
- PC), we should step over it. */
- if (breakpoint_here (stop_pc))
- event_child->need_step_over = 1;
-
- /* We're not reporting this breakpoint to GDB, so apply the
- decr_pc_after_break adjustment to the inferior's regcache
- ourselves. */
-
- if (the_low_target.set_pc != NULL)
- {
- struct regcache *regcache
- = get_thread_regcache (get_lwp_thread (event_child), 1);
- (*the_low_target.set_pc) (regcache, stop_pc);
- }
-
- /* We've finished stepping over a breakpoint. We've stopped
- all LWPs momentarily except the stepping one. This is
- where we resume them all again. We're going to keep
- waiting, so use proceed, which handles stepping over the
- next breakpoint. */
- if (debug_threads)
- fprintf (stderr, "proceeding all threads.\n");
- proceed_all_lwps ();
-
- /* If we stopped threads momentarily, we may have threads
- with pending statuses now (except when we're going to
- force the next event out of a specific LWP, in which case
- don't want to handle the pending events of other LWPs
- yet. */
- if (ptid_equal (step_over_bkpt, null_ptid))
- goto again;
- else
- continue;
- }
- else
- {
- /* GDB breakpoint or program trap, perhaps. */
- if (step_over_finished)
- unstop_all_lwps (event_child);
- }
-
- if (debug_threads)
- fprintf (stderr, "Hit a non-gdbserver trap event.\n");
-
return lwpid_of (event_child);
}
@@ -1639,6 +1410,151 @@ linux_wait_for_event (ptid_t ptid, int *
}
}
+
+/* Count the LWP's that have had events. */
+
+static int
+count_events_callback (struct inferior_list_entry *entry, void *data)
+{
+ struct lwp_info *lp = (struct lwp_info *) entry;
+ int *count = data;
+
+ gdb_assert (count != NULL);
+
+ /* Count only resumed LWPs that have a SIGTRAP event pending that
+ should be reported to GDB. */
+ if (get_lwp_thread (lp)->last_status.kind == TARGET_WAITKIND_IGNORE
+ && lp->last_resume_kind != resume_stop
+ && lp->status_pending_p
+ && WIFSTOPPED (lp->status_pending)
+ && WSTOPSIG (lp->status_pending) == SIGTRAP
+ && !breakpoint_inserted_here (lp->stop_pc))
+ (*count)++;
+
+ return 0;
+}
+
+/* Select the LWP (if any) that is currently being single-stepped. */
+
+static int
+select_singlestep_lwp_callback (struct inferior_list_entry *entry, void *data)
+{
+ struct lwp_info *lp = (struct lwp_info *) entry;
+
+ if (get_lwp_thread (lp)->last_status.kind == TARGET_WAITKIND_IGNORE
+ && lp->last_resume_kind == resume_step
+ && lp->status_pending_p)
+ return 1;
+ else
+ return 0;
+}
+
+/* Select the Nth LWP that has had a SIGTRAP event that should be
+ reported to GDB. */
+
+static int
+select_event_lwp_callback (struct inferior_list_entry *entry, void *data)
+{
+ struct lwp_info *lp = (struct lwp_info *) entry;
+ int *selector = data;
+
+ gdb_assert (selector != NULL);
+
+ /* Select only resumed LWPs that have a SIGTRAP event pending. */
+ if (lp->last_resume_kind != resume_stop
+ && get_lwp_thread (lp)->last_status.kind == TARGET_WAITKIND_IGNORE
+ && lp->status_pending_p
+ && WIFSTOPPED (lp->status_pending)
+ && WSTOPSIG (lp->status_pending) == SIGTRAP
+ && !breakpoint_inserted_here (lp->stop_pc))
+ if ((*selector)-- == 0)
+ return 1;
+
+ return 0;
+}
+
+static int
+cancel_breakpoints_callback (struct inferior_list_entry *entry, void *data)
+{
+ struct lwp_info *lp = (struct lwp_info *) entry;
+ struct lwp_info *event_lp = data;
+
+ /* Leave the LWP that has been elected to receive a SIGTRAP alone. */
+ if (lp == event_lp)
+ return 0;
+
+ /* If a LWP other than the LWP that we're reporting an event for has
+ hit a GDB breakpoint (as opposed to some random trap signal),
+ then just arrange for it to hit it again later. We don't keep
+ the SIGTRAP status and don't forward the SIGTRAP signal to the
+ LWP. We will handle the current event, eventually we will resume
+ all LWPs, and this one will get its breakpoint trap again.
+
+ If we do not do this, then we run the risk that the user will
+ delete or disable the breakpoint, but the LWP will have already
+ tripped on it. */
+
+ if (lp->last_resume_kind != resume_stop
+ && get_lwp_thread (lp)->last_status.kind == TARGET_WAITKIND_IGNORE
+ && lp->status_pending_p
+ && WIFSTOPPED (lp->status_pending)
+ && WSTOPSIG (lp->status_pending) == SIGTRAP
+ && cancel_breakpoint (lp))
+ /* Throw away the SIGTRAP. */
+ lp->status_pending_p = 0;
+
+ return 0;
+}
+
+/* Select one LWP out of those that have events pending. */
+
+static void
+select_event_lwp (struct lwp_info **orig_lp)
+{
+ int num_events = 0;
+ int random_selector;
+ struct lwp_info *event_lp;
+
+ /* Give preference to any LWP that is being single-stepped. */
+ event_lp
+ = (struct lwp_info *) find_inferior (&all_lwps,
+ select_singlestep_lwp_callback, NULL);
+ if (event_lp != NULL)
+ {
+ if (debug_threads)
+ fprintf (stderr,
+ "SEL: Select single-step %s\n",
+ target_pid_to_str (ptid_of (event_lp)));
+ }
+ else
+ {
+ /* No single-stepping LWP. Select one at random, out of those
+ which have had SIGTRAP events. */
+
+ /* First see how many SIGTRAP events we have. */
+ find_inferior (&all_lwps, count_events_callback, &num_events);
+
+ /* Now randomly pick a LWP out of those that have had a SIGTRAP. */
+ random_selector = (int)
+ ((num_events * (double) rand ()) / (RAND_MAX + 1.0));
+
+ if (debug_threads && num_events > 1)
+ fprintf (stderr,
+ "SEL: Found %d SIGTRAP events, selecting #%d\n",
+ num_events, random_selector);
+
+ event_lp = (struct lwp_info *) find_inferior (&all_lwps,
+ select_event_lwp_callback,
+ &random_selector);
+ }
+
+ if (event_lp != NULL)
+ {
+ /* Switch the event LWP. */
+ *orig_lp = event_lp;
+ }
+}
+
/* Set this inferior LWP's state as "want-stopped". We won't resume
this LWP until the client gives us another action for it. */
@@ -1673,9 +1589,13 @@ linux_wait_1 (ptid_t ptid,
{
int w;
struct thread_info *thread = NULL;
- struct lwp_info *lwp = NULL;
+ struct lwp_info *event_child = NULL;
int options;
int pid;
+ int step_over_finished;
+ int bp_explains_trap;
+ int maybe_internal_trap;
+ int report_to_gdb;
/* Translate generic target options into linux options. */
options = __WALL;
@@ -1711,11 +1631,20 @@ retry:
ptid = cont_thread;
}
- pid = linux_wait_for_event (ptid, &w, options);
+ if (ptid_equal (step_over_bkpt, null_ptid))
+ pid = linux_wait_for_event (ptid, &w, options);
+ else
+ {
+ if (debug_threads)
+ fprintf (stderr, "step_over_bkpt set [%s], doing a blocking wait\n",
+ target_pid_to_str (step_over_bkpt));
+ pid = linux_wait_for_event (step_over_bkpt, &w, options & ~WNOHANG);
+ }
+
if (pid == 0) /* only if TARGET_WNOHANG */
return null_ptid;
- lwp = get_thread_lwp (current_inferior);
+ event_child = get_thread_lwp (current_inferior);
/* If we are waiting for a particular child, and it exited,
linux_wait_for_event will return its exit status. Similarly if
@@ -1733,13 +1662,13 @@ retry:
{
if (WIFEXITED (w) || WIFSIGNALED (w))
{
- int pid = pid_of (lwp);
+ int pid = pid_of (event_child);
struct process_info *process = find_process_pid (pid);
#ifdef USE_THREAD_DB
thread_db_free (process, 0);
#endif
- delete_lwp (lwp);
+ delete_lwp (event_child);
linux_remove_process (process);
current_inferior = NULL;
@@ -1771,18 +1700,161 @@ retry:
goto retry;
}
+ /* If this event was not handled before, and is not a SIGTRAP, we
+ report it. SIGILL and SIGSEGV are also treated as traps in case
+ a breakpoint is inserted at the current PC. If this target does
+ not support internal breakpoints at all, we also report the
+ SIGTRAP without further processing; it's of no concern to us. */
+ maybe_internal_trap
+ = (supports_breakpoints ()
+ && (WSTOPSIG (w) == SIGTRAP
+ || ((WSTOPSIG (w) == SIGILL
+ || WSTOPSIG (w) == SIGSEGV)
+ && (*the_low_target.breakpoint_at) (event_child->stop_pc))));
+
+ if (maybe_internal_trap)
+ {
+ /* Handle anything that requires bookkeeping before deciding to
+ report the event or continue waiting. */
+
+ /* First check if we can explain the SIGTRAP with an internal
+ breakpoint, or if we should possibly report the event to GDB.
+ Do this before anything that may remove or insert a
+ breakpoint. */
+ bp_explains_trap = breakpoint_inserted_here (event_child->stop_pc);
+
+ /* We have a SIGTRAP, possibly a step-over dance has just
+ finished. If so, tweak the state machine accordingly,
+ reinsert breakpoints and delete any reinsert (software
+ single-step) breakpoints. */
+ step_over_finished = finish_step_over (event_child);
+
+ /* Now invoke the callbacks of any internal breakpoints there. */
+ check_breakpoints (event_child->stop_pc);
+
+ if (bp_explains_trap)
+ {
+ /* If we stepped or ran into an internal breakpoint, we've
+ already handled it. So next time we resume (from this
+ PC), we should step over it. */
+ if (debug_threads)
+ fprintf (stderr, "Hit a gdbserver breakpoint.\n");
+
+ event_child->need_step_over = 1;
+ }
+ }
+ else
+ {
+ /* We have some other signal, possibly a step-over dance was in
+ progress, and it should be cancelled too. */
+ step_over_finished = finish_step_over (event_child);
+ }
+
+ /* We have all the data we need. Either report the event to GDB, or
+ resume threads and keep waiting for more. */
+
+ /* Check If GDB would be interested in this event. If GDB wanted
+ this thread to single step, we always want to report the SIGTRAP,
+ and let GDB handle it. */
+ report_to_gdb = (!maybe_internal_trap
+ || event_child->last_resume_kind == resume_step
+ || event_child->stopped_by_watchpoint
+ || (!step_over_finished && !bp_explains_trap));
+
+ /* We found no reason GDB would want us to stop. We either hit one
+ of our own breakpoints, or finished an internal step GDB
+ shouldn't know about. */
+ if (!report_to_gdb)
+ {
+ if (debug_threads)
+ {
+ if (bp_explains_trap)
+ fprintf (stderr, "Hit a gdbserver breakpoint.\n");
+ if (step_over_finished)
+ fprintf (stderr, "Step-over finished.\n");
+ }
+
+ /* We're not reporting this breakpoint to GDB, so apply the
+ decr_pc_after_break adjustment to the inferior's regcache
+ ourselves. */
+
+ if (the_low_target.set_pc != NULL)
+ {
+ struct regcache *regcache
+ = get_thread_regcache (get_lwp_thread (event_child), 1);
+ (*the_low_target.set_pc) (regcache, event_child->stop_pc);
+ }
+
+ /* We've finished stepping over a breakpoint. We've stopped all
+ LWPs momentarily except the stepping one. This is where we
+ resume them all again. We're going to keep waiting, so use
+ proceed, which handles stepping over the next breakpoint. */
+ if (debug_threads)
+ fprintf (stderr, "proceeding all threads.\n");
+ proceed_all_lwps ();
+ goto retry;
+ }
+
+ if (debug_threads)
+ {
+ if (event_child->last_resume_kind == resume_step)
+ fprintf (stderr, "GDB wanted to single-step, reporting event.\n");
+ if (event_child->stopped_by_watchpoint)
+ fprintf (stderr, "Stopped by watchpoint.\n");
+ if (debug_threads)
+ fprintf (stderr, "Hit a non-gdbserver trap event.\n");
+ }
+
+ /* Alright, we're going to report a stop. */
+
+ if (!non_stop)
+ {
+ /* In all-stop, stop all threads. */
+ stop_all_lwps ();
+
+ /* If we're not waiting for a specific LWP, choose an event LWP
+ from among those that have had events. Giving equal priority
+ to all LWPs that have had events helps prevent
+ starvation. */
+ if (ptid_equal (ptid, minus_one_ptid))
+ {
+ event_child->status_pending_p = 1;
+ event_child->status_pending = w;
+
+ select_event_lwp (&event_child);
+
+ event_child->status_pending_p = 0;
+ w = event_child->status_pending;
+ }
+
+ /* Now that we've selected our final event LWP, cancel any
+ breakpoints in other LWPs that have hit a GDB breakpoint.
+ See the comment in cancel_breakpoints_callback to find out
+ why. */
+ find_inferior (&all_lwps, cancel_breakpoints_callback, event_child);
+ }
+ else
+ {
+ /* If we just finished a step-over, then all threads had been
+ momentarily paused. In all-stop, that's fine, we want
+ threads stopped by now anyway. In non-stop, we need to
+ re-resume threads that GDB wanted to be running. */
+ if (step_over_finished)
+ unstop_all_lwps (event_child);
+ }
+
ourstatus->kind = TARGET_WAITKIND_STOPPED;
/* Do this before the gdb_wants_all_stopped calls below, since they
always set last_resume_kind to resume_stop. */
- if (lwp->last_resume_kind == resume_stop && WSTOPSIG (w) == SIGSTOP)
+ if (event_child->last_resume_kind == resume_stop && WSTOPSIG (w) == SIGSTOP)
{
/* A thread that has been requested to stop by GDB with vCont;t,
and it stopped cleanly, so report as SIG0. The use of
SIGSTOP is an implementation detail. */
ourstatus->value.sig = TARGET_SIGNAL_0;
}
- else if (lwp->last_resume_kind == resume_stop && WSTOPSIG (w) != SIGSTOP)
+ else if (event_child->last_resume_kind == resume_stop && WSTOPSIG (w) != SIGSTOP)
{
/* A thread that has been requested to stop by GDB with vCont;t,
but, it stopped for other reasons. */
@@ -1797,17 +1869,6 @@ retry:
if (!non_stop)
{
- /* In all-stop, stop all threads, we're about to report an event
- to GDB. */
- stop_all_lwps ();
-
- /* Do not leave a pending single-step finish to be reported to
- the client. The client will give us a new action for this
- thread, possibly a continue request --- otherwise, the client
- would consider this pending SIGTRAP reported later a spurious
- signal. */
- cancel_finished_single_steps (lwp);
-
/* From GDB's perspective, all-stop mode always stops all
threads implicitly. Tag all threads as "want-stopped". */
gdb_wants_all_stopped ();
@@ -1817,17 +1878,17 @@ retry:
/* We're reporting this LWP as stopped. Update it's
"want-stopped" state to what the client wants, until it gets
a new resume action. */
- gdb_wants_lwp_stopped (&lwp->head);
+ gdb_wants_lwp_stopped (&event_child->head);
}
if (debug_threads)
fprintf (stderr, "linux_wait ret = %s, %d, %d\n",
- target_pid_to_str (lwp->head.id),
+ target_pid_to_str (ptid_of (event_child)),
ourstatus->kind,
ourstatus->value.sig);
- get_lwp_thread (lwp)->last_status = *ourstatus;
- return lwp->head.id;
+ get_lwp_thread (event_child)->last_status = *ourstatus;
+ return ptid_of (event_child);
}
/* Get rid of any pending event in the pipe. */
@@ -1956,45 +2017,6 @@ mark_lwp_dead (struct lwp_info *lwp, int
lwp->stop_expected = 0;
}
-static int
-cancel_finished_single_step (struct inferior_list_entry *entry, void *except)
-{
- struct lwp_info *lwp = (struct lwp_info *) entry;
- struct thread_info *saved_inferior;
-
- if (lwp == except)
- return 0;
-
- saved_inferior = current_inferior;
- current_inferior = get_lwp_thread (lwp);
-
- /* Do not leave a pending single-step finish to be reported to the
- client. The client will give us a new action for this thread,
- possibly a continue request --- otherwise, the client would
- consider this pending SIGTRAP reported later a spurious
- signal. */
- if (lwp->status_pending_p
- && WSTOPSIG (lwp->status_pending) == SIGTRAP
- && lwp->stepping
- && !lwp->stopped_by_watchpoint)
- {
- if (debug_threads)
- fprintf (stderr, " single-step SIGTRAP cancelled\n");
-
- lwp->status_pending_p = 0;
- lwp->status_pending = 0;
- }
-
- current_inferior = saved_inferior;
- return 0;
-}
-
-static void
-cancel_finished_single_steps (struct lwp_info *except)
-{
- find_inferior (&all_lwps, cancel_finished_single_step, except);
-}
-
static void
wait_for_sigstop (struct inferior_list_entry *entry)
{
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: Teach gdbserver to step over internal breakpoints
2010-03-24 21:22 ` Pedro Alves
2010-03-24 21:34 ` Pedro Alves
@ 2010-03-24 22:00 ` Pedro Alves
1 sibling, 0 replies; 6+ messages in thread
From: Pedro Alves @ 2010-03-24 22:00 UTC (permalink / raw)
To: gdb-patches
On Wednesday 24 March 2010 21:22:39, Pedro Alves wrote:
> Also tested on x86_64, with and without a thread
> event breakpoint, and also with a had that makes x86-64
> use reinsert breakpoints. I'll show that for the archives
> shortly.
s/a had/ a hack/. Here it is. This makes an x86 gdbserver
use reinsert (single software single-step) breakpoints to
step over the thread event breakpoint, thus it make it
easier to test those code paths on my devel machine.
I've only used it thoroughly with x86-64, but it should
work with ix86 as well.
For the archives.
--
Pedro Alves
---
gdb/gdbserver/linux-low.c | 2 +-
gdb/gdbserver/linux-x86-low.c | 35 ++++++++++++++++++++++++++++++++++-
2 files changed, 35 insertions(+), 2 deletions(-)
Index: src/gdb/gdbserver/linux-x86-low.c
===================================================================
--- src.orig/gdb/gdbserver/linux-x86-low.c 2010-03-24 20:34:18.000000000 +0000
+++ src/gdb/gdbserver/linux-x86-low.c 2010-03-24 21:41:42.000000000 +0000
@@ -824,6 +824,39 @@ x86_arch_setup (void)
num_xmm_registers = 8;
}
+/* We only place breakpoints in empty marker functions, and thread
+ locking is outside of the function. So rather than importing
+ software single-step, we can just run until exit. */
+
+static CORE_ADDR
+x86_reinsert_addr (void)
+{
+ struct regcache *regcache = get_thread_regcache (current_inferior, 1);
+
+#ifdef __x86_64__
+ int use_64bit = register_size (0) == 8;
+
+ if (use_64bit)
+ {
+ unsigned long sp, ret;
+
+ collect_register_by_name (regcache, "rsp", &sp);
+ if (read_inferior_memory (sp, (unsigned char *) &ret, 8) != 0)
+ return 0;
+ return ret;
+ }
+ else
+#endif
+ {
+ unsigned int sp, ret;
+
+ collect_register_by_name (regcache, "esp", &sp);
+ if (read_inferior_memory (sp, (unsigned char *) &ret, 4) != 0)
+ return 0;
+ return ret;
+ }
+}
+
/* This is initialized assuming an amd64 target.
x86_arch_setup will correct it for i386 or amd64 targets. */
@@ -838,7 +871,7 @@ struct linux_target_ops the_low_target =
x86_set_pc,
x86_breakpoint,
x86_breakpoint_len,
- NULL,
+ x86_reinsert_addr,
1,
x86_breakpoint_at,
x86_insert_point,
Index: src/gdb/gdbserver/linux-low.c
===================================================================
--- src.orig/gdb/gdbserver/linux-low.c 2010-03-24 21:43:01.000000000 +0000
+++ src/gdb/gdbserver/linux-low.c 2010-03-24 21:55:16.000000000 +0000
@@ -3421,7 +3421,7 @@ linux_look_up_symbols (void)
/* If the kernel supports tracing forks then it also supports tracing
clones, and then we don't need to use the magic thread event breakpoint
to learn about threads. */
- thread_db_init (!linux_supports_tracefork_flag);
+ thread_db_init (1);
#endif
}
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2010-03-24 22:00 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-03-16 18:40 Teach gdbserver to step over internal breakpoints Pedro Alves
2010-03-24 0:05 ` Pedro Alves
2010-03-24 12:13 ` Pedro Alves
2010-03-24 21:22 ` Pedro Alves
2010-03-24 21:34 ` Pedro Alves
2010-03-24 22:00 ` Pedro Alves
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox