Teach non-stop to do in-line step-overs (stop all, step, restart)
That is, step past breakpoints by: - pausing all threads - removing breakpoint at PC - single-step - reinsert breakpoint - restart threads similarly to all-stop (with displaced stepping disabled). This allows non-stop to work on targets/architectures without displaced stepping support. That is, it makes displaced stepping an optimization instead of a requirement. For example, in principle, all GNU/Linux ports support non-stop mode at the target_ops level, but not all corresponding gdbarch's implement displaced stepping. This should make non-stop work for all (albeit, not as efficiently). And then there are scenarios where even if the architecture supports displaced stepping, we can't use it, because we e.g., don't find a usable address to use as displaced step scratch pad. It should also fix stepping past watchpoints on targets that have non-continuable watchpoints in non-stop mode (e.g., PPC, untested). Running the instruction out of line in the displaced stepping scratch pad doesn't help that case, as the copied instruction reads/writes the same watched memory... We can fix that too by teaching GDB to only remove the watchpoint from the thread that we want to move past the watchpoint (currently, removing a watchpoint always removes it from all threads), but again, that can be considered an optimization; not all targets would support it. For those familiar with the gdb and gdbserver Linux target_ops backends, the implementation should look similar, except it is done on the core side. When we pause threads, we may find they stop with an interesting event that should be handled later when the thread is re-resumed, thus we store such events in the thread object, and mark the event as pending. We should only consume pending events if the thread is indeed resumed, thus we add a new "resumed" flag to the thread object. At a later stage, we might add new target methods to accelerate some of this, like "pause all threads", with corresponding RSP packets, but we'd still need a fallback method for remote targets that don't support such packets, so, again, that can be deferred as optimization. My _real_ motivation here is making it possible to reimplement all-stop mode on top of the target always working on non-stop mode, so that e.g., we can send RSP packets to a remote target even while the target is running -- can't do that in the all-stop RSP variant, by design). Tested on x86_64 Fedora 20, with and without "set displaced off" forced. The latter forces the new code paths whenever GDB needs to step past a breakpoint. gdb/ChangeLog: 2015-08-07 Pedro Alves <pedro@codesourcery.com> * breakpoint.c (breakpoints_should_be_inserted_now): If any thread has a pending status, return true. * gdbthread.h: Include target/waitstatus.h. (struct thread_suspend_state) <stop_reason, waitstatus_pending_p, stop_pc>: New fields. (struct thread_info) <resumed>: New field. (set_resumed): Declare. * infrun.c: Include "event-loop.h". (infrun_async_inferior_event_token, infrun_is_async): New globals. (infrun_async): New function. (clear_step_over_info): Add debug output. (displaced_step_in_progress_any_inferior): New function. (displaced_step_fixup): New returns int. (start_step_over): Handle in-line step-overs too. Assert the thread is marked resumed. (resume_cleanups): Clear the thread's resumed flag. (resume): Set the thread's resumed flag. Return early if the thread has a pending status. Allow stepping a breakpoint with no signal. (proceed): Adjust to check 'resumed' instead of 'executing'. (clear_proceed_status_thread): If the thread has a pending status, and that status is a finished step, discard the pending status. (clear_proceed_status): Don't clear step_over_info here. (random_pending_event_thread, do_target_wait): New functions. (prepare_for_detach, wait_for_inferior, fetch_inferior_event): Use do_target_wait. (wait_one): New function. (THREAD_STOPPED_BY): New macro. (thread_stopped_by_watchpoint, thread_stopped_by_sw_breakpoint) (thread_stopped_by_hw_breakpoint): New functions. (switch_to_thread_cleanup, save_waitstatus, stop_all_threads): New functions. (handle_inferior_event): Also call set_resumed(false) on all threads implicitly stopped by the event. (restart_threads, resumed_thread_with_pending_status): New functions. (finish_step_over): If we were doing an in-line step-over before, and no longer are after trying to start a new step-over, restart all threads. If we have multiple threads with pending events, save the current event and go through the event loop again. (handle_signal_stop): Return early if finish_step_over returns false. <random signal>: If we get a signal while stepping over a breakpoint in-line in non-stop mode, restart all threads. Clear step_over_info before delivering the signal. (keep_going_stepped_thread): Use internal_error instead of gdb_assert. Mark the thread as resumed. (keep_going_pass_signal): Assert the thread isn't already resumed. If some other thread is doing an in-line step-over, defer the resume. If we just started a new in-line step-over, stop all threads. Don't clear step_over_info. (infrun_async_inferior_event_handler): New function. (_initialize_infrun): Create async event handler with infrun_async_inferior_event_handler as callback. (infrun_async): New declaration. * target.c (target_async): New function. * target.h (target_async): Declare macro and readd as function declaration. * target/waitstatus.h (enum target_stop_reason) <TARGET_STOPPED_BY_SINGLE_STEP>: New value. * thread.c (new_thread): Clear the new waitstatus field. (set_resumed): New function.
This commit is contained in:
parent
2ac7589cfe
commit
372316f128
@ -1,3 +1,68 @@
|
||||
2015-08-07 Pedro Alves <pedro@codesourcery.com>
|
||||
|
||||
* breakpoint.c (breakpoints_should_be_inserted_now): If any thread
|
||||
has a pending status, return true.
|
||||
* gdbthread.h: Include target/waitstatus.h.
|
||||
(struct thread_suspend_state) <stop_reason, waitstatus_pending_p,
|
||||
stop_pc>: New fields.
|
||||
(struct thread_info) <resumed>: New field.
|
||||
(set_resumed): Declare.
|
||||
* infrun.c: Include "event-loop.h".
|
||||
(infrun_async_inferior_event_token, infrun_is_async): New globals.
|
||||
(infrun_async): New function.
|
||||
(clear_step_over_info): Add debug output.
|
||||
(displaced_step_in_progress_any_inferior): New function.
|
||||
(displaced_step_fixup): New returns int.
|
||||
(start_step_over): Handle in-line step-overs too. Assert the
|
||||
thread is marked resumed.
|
||||
(resume_cleanups): Clear the thread's resumed flag.
|
||||
(resume): Set the thread's resumed flag. Return early if the
|
||||
thread has a pending status. Allow stepping a breakpoint with no
|
||||
signal.
|
||||
(proceed): Adjust to check 'resumed' instead of 'executing'.
|
||||
(clear_proceed_status_thread): If the thread has a pending status,
|
||||
and that status is a finished step, discard the pending status.
|
||||
(clear_proceed_status): Don't clear step_over_info here.
|
||||
(random_pending_event_thread, do_target_wait): New functions.
|
||||
(prepare_for_detach, wait_for_inferior, fetch_inferior_event): Use
|
||||
do_target_wait.
|
||||
(wait_one): New function.
|
||||
(THREAD_STOPPED_BY): New macro.
|
||||
(thread_stopped_by_watchpoint, thread_stopped_by_sw_breakpoint)
|
||||
(thread_stopped_by_hw_breakpoint): New functions.
|
||||
(switch_to_thread_cleanup, save_waitstatus, stop_all_threads): New
|
||||
functions.
|
||||
(handle_inferior_event): Also call set_resumed(false) on all
|
||||
threads implicitly stopped by the event.
|
||||
(restart_threads, resumed_thread_with_pending_status): New
|
||||
functions.
|
||||
(finish_step_over): If we were doing an in-line step-over before,
|
||||
and no longer are after trying to start a new step-over, restart
|
||||
all threads. If we have multiple threads with pending events,
|
||||
save the current event and go through the event loop again.
|
||||
(handle_signal_stop): Return early if finish_step_over returns
|
||||
false.
|
||||
<random signal>: If we get a signal while stepping over a
|
||||
breakpoint in-line in non-stop mode, restart all threads. Clear
|
||||
step_over_info before delivering the signal.
|
||||
(keep_going_stepped_thread): Use internal_error instead of
|
||||
gdb_assert. Mark the thread as resumed.
|
||||
(keep_going_pass_signal): Assert the thread isn't already resumed.
|
||||
If some other thread is doing an in-line step-over, defer the
|
||||
resume. If we just started a new in-line step-over, stop all
|
||||
threads. Don't clear step_over_info.
|
||||
(infrun_async_inferior_event_handler): New function.
|
||||
(_initialize_infrun): Create async event handler with
|
||||
infrun_async_inferior_event_handler as callback.
|
||||
(infrun_async): New declaration.
|
||||
* target.c (target_async): New function.
|
||||
* target.h (target_async): Declare macro and readd as function
|
||||
declaration.
|
||||
* target/waitstatus.h (enum target_stop_reason)
|
||||
<TARGET_STOPPED_BY_SINGLE_STEP>: New value.
|
||||
* thread.c (new_thread): Clear the new waitstatus field.
|
||||
(set_resumed): New function.
|
||||
|
||||
2015-08-07 Pedro Alves <palves@redhat.com>
|
||||
|
||||
* infrun.c (keep_going_stepped_thread): New function, factored out
|
||||
|
@ -468,6 +468,8 @@ breakpoints_should_be_inserted_now (void)
|
||||
}
|
||||
else if (target_has_execution)
|
||||
{
|
||||
struct thread_info *tp;
|
||||
|
||||
if (always_inserted_mode)
|
||||
{
|
||||
/* The user wants breakpoints inserted even if all threads
|
||||
@ -477,6 +479,13 @@ breakpoints_should_be_inserted_now (void)
|
||||
|
||||
if (threads_are_executing ())
|
||||
return 1;
|
||||
|
||||
/* Don't remove breakpoints yet if, even though all threads are
|
||||
stopped, we still have events to process. */
|
||||
ALL_NON_EXITED_THREADS (tp)
|
||||
if (tp->resumed
|
||||
&& tp->suspend.waitstatus_pending_p)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ struct symtab;
|
||||
#include "inferior.h"
|
||||
#include "btrace.h"
|
||||
#include "common/vec.h"
|
||||
#include "target/waitstatus.h"
|
||||
|
||||
/* Frontend view of the thread state. Possible extensions: stepping,
|
||||
finishing, until(ling),... */
|
||||
@ -157,6 +158,23 @@ struct thread_suspend_state
|
||||
should be suppressed, the core will take care of clearing this
|
||||
before the target is resumed. */
|
||||
enum gdb_signal stop_signal;
|
||||
|
||||
/* The reason the thread last stopped, if we need to track it
|
||||
(breakpoint, watchpoint, etc.) */
|
||||
enum target_stop_reason stop_reason;
|
||||
|
||||
/* The waitstatus for this thread's last event. */
|
||||
struct target_waitstatus waitstatus;
|
||||
/* If true WAITSTATUS hasn't been handled yet. */
|
||||
int waitstatus_pending_p;
|
||||
|
||||
/* Record the pc of the thread the last time it stopped. (This is
|
||||
not the current thread's PC as that may have changed since the
|
||||
last stop, e.g., "return" command, or "p $pc = 0xf000"). This is
|
||||
used in coordination with stop_reason and waitstatus_pending_p:
|
||||
if the thread's PC is changed since it last stopped, a pending
|
||||
breakpoint waitstatus is discarded. */
|
||||
CORE_ADDR stop_pc;
|
||||
};
|
||||
|
||||
typedef struct value *value_ptr;
|
||||
@ -181,6 +199,15 @@ struct thread_info
|
||||
thread is off and running. */
|
||||
int executing;
|
||||
|
||||
/* Non-zero if this thread is resumed from infrun's perspective.
|
||||
Note that a thread can be marked both as not-executing and
|
||||
resumed at the same time. This happens if we try to resume a
|
||||
thread that has a wait status pending. We shouldn't let the
|
||||
thread really run until that wait status has been processed, but
|
||||
we should not process that wait status if we didn't try to let
|
||||
the thread run. */
|
||||
int resumed;
|
||||
|
||||
/* Frontend view of the thread state. Note that the THREAD_RUNNING/
|
||||
THREAD_STOPPED states are different from EXECUTING. When the
|
||||
thread is stopped internally while handling an internal event,
|
||||
@ -398,6 +425,11 @@ extern int thread_count (void);
|
||||
/* Switch from one thread to another. */
|
||||
extern void switch_to_thread (ptid_t ptid);
|
||||
|
||||
/* Marks or clears thread(s) PTID as resumed. If PTID is
|
||||
MINUS_ONE_PTID, applies to all threads. If ptid_is_pid(PTID) is
|
||||
true, applies to all threads of the process pointed at by PTID. */
|
||||
extern void set_resumed (ptid_t ptid, int resumed);
|
||||
|
||||
/* Marks thread PTID is running, or stopped.
|
||||
If PTID is minus_one_ptid, marks all threads. */
|
||||
extern void set_running (ptid_t ptid, int running);
|
||||
|
1106
gdb/infrun.c
1106
gdb/infrun.c
File diff suppressed because it is too large
Load Diff
@ -189,6 +189,9 @@ extern void signal_catch_update (const unsigned int *);
|
||||
systems. Use of symbolic signal names is strongly encouraged. */
|
||||
enum gdb_signal gdb_signal_from_command (int num);
|
||||
|
||||
/* Enables/disables infrun's async event source in the event loop. */
|
||||
extern void infrun_async (int enable);
|
||||
|
||||
/* The global queue of threads that need to do a step-over operation
|
||||
to get past e.g., a breakpoint. */
|
||||
extern struct thread_info *step_over_queue_head;
|
||||
|
@ -3741,6 +3741,15 @@ maintenance_print_target_stack (char *cmd, int from_tty)
|
||||
}
|
||||
}
|
||||
|
||||
/* See target.h. */
|
||||
|
||||
void
|
||||
target_async (int enable)
|
||||
{
|
||||
infrun_async (enable);
|
||||
current_target.to_async (¤t_target, enable);
|
||||
}
|
||||
|
||||
/* Controls if targets can report that they can/are async. This is
|
||||
just for maintainers to use when debugging gdb. */
|
||||
int target_async_permitted = 1;
|
||||
|
@ -1742,8 +1742,7 @@ extern int target_async_permitted;
|
||||
#define target_is_async_p() (current_target.to_is_async_p (¤t_target))
|
||||
|
||||
/* Enables/disabled async target events. */
|
||||
#define target_async(ENABLE) \
|
||||
(current_target.to_async (¤t_target, (ENABLE)))
|
||||
extern void target_async (int enable);
|
||||
|
||||
#define target_execution_direction() \
|
||||
(current_target.to_execution_direction (¤t_target))
|
||||
|
23
gdb/thread.c
23
gdb/thread.c
@ -232,6 +232,7 @@ new_thread (ptid_t ptid)
|
||||
/* Nothing to follow yet. */
|
||||
tp->pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
|
||||
tp->state = THREAD_STOPPED;
|
||||
tp->suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;
|
||||
|
||||
return tp;
|
||||
}
|
||||
@ -852,6 +853,28 @@ thread_change_ptid (ptid_t old_ptid, ptid_t new_ptid)
|
||||
observer_notify_thread_ptid_changed (old_ptid, new_ptid);
|
||||
}
|
||||
|
||||
/* See gdbthread.h. */
|
||||
|
||||
void
|
||||
set_resumed (ptid_t ptid, int resumed)
|
||||
{
|
||||
struct thread_info *tp;
|
||||
int all = ptid_equal (ptid, minus_one_ptid);
|
||||
|
||||
if (all || ptid_is_pid (ptid))
|
||||
{
|
||||
for (tp = thread_list; tp; tp = tp->next)
|
||||
if (all || ptid_get_pid (tp->ptid) == ptid_get_pid (ptid))
|
||||
tp->resumed = resumed;
|
||||
}
|
||||
else
|
||||
{
|
||||
tp = find_thread_ptid (ptid);
|
||||
gdb_assert (tp != NULL);
|
||||
tp->resumed = resumed;
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper for set_running, that marks one thread either running or
|
||||
stopped. */
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user