Embed the pending step-over chain in thread_info objects

In order to teach non-stop mode to do in-line step-overs (pause all
threads, remove breakpoint, single-step, reinsert breakpoint, restart
threads), we'll need to be able to queue in-line step over requests,
much like we queue displaced stepping (out-of-line) requests.
Actually, the queue should be the same -- threads wait for their turn
to step past something (breakpoint, watchpoint), doesn't matter what
technique we end up using when the step over actually starts.

I found that the queue management ends up simpler and more efficient
if embedded in the thread objects themselves.  This commit converts
the existing displaced stepping queue to that.  Later patches will
make the in-line step-overs code paths use it too.

gdb/ChangeLog:
2015-08-07  Pedro Alves  <palves@redhat.com>

	* gdbthread.h (struct thread_info) <step_over_prev,
	step_over_next>: New fields.
	(thread_step_over_chain_enqueue, thread_step_over_chain_remove)
	(thread_step_over_chain_next, thread_is_in_step_over_chain): New
	declarations.
	* infrun.c (struct displaced_step_request): Delete.
	(struct displaced_step_inferior_state) <step_request_queue>:
	Delete field.
	(displaced_step_prepare): Assert that trap_expected is set.  Use
	thread_step_over_chain_enqueue.  Split starting a new displaced
	step to ...
	(start_step_over): ... this new function.
	(resume): Assert the thread isn't waiting for a step over already.
	(proceed): Assert the thread isn't waiting for a step over
	already.
	(infrun_thread_stop_requested): Adjust to remove threads from the
	embedded step-over chain.
	(handle_inferior_event) <fork/vfork>: Call start_step_over after
	displaced_step_fixup.
	(handle_signal_stop): Call start_step_over after
	displaced_step_fixup.
	* infrun.h (step_over_queue_head): New declaration.
	* thread.c (step_over_chain_enqueue, step_over_chain_remove)
	(thread_step_over_chain_next, thread_is_in_step_over_chain)
	(thread_step_over_chain_enqueue)
	(thread_step_over_chain_remove): New functions.
	(delete_thread_1): Remove thread from the step-over chain.
This commit is contained in:
Pedro Alves 2015-08-07 17:23:57 +01:00
parent 6c4cfb244b
commit c2829269f5
5 changed files with 207 additions and 69 deletions

View File

@ -1,3 +1,33 @@
2015-08-07 Pedro Alves <palves@redhat.com>
* gdbthread.h (struct thread_info) <step_over_prev,
step_over_next>: New fields.
(thread_step_over_chain_enqueue, thread_step_over_chain_remove)
(thread_step_over_chain_next, thread_is_in_step_over_chain): New
declarations.
* infrun.c (struct displaced_step_request): Delete.
(struct displaced_step_inferior_state) <step_request_queue>:
Delete field.
(displaced_step_prepare): Assert that trap_expected is set. Use
thread_step_over_chain_enqueue. Split starting a new displaced
step to ...
(start_step_over): ... this new function.
(resume): Assert the thread isn't waiting for a step over already.
(proceed): Assert the thread isn't waiting for a step over
already.
(infrun_thread_stop_requested): Adjust to remove threads from the
embedded step-over chain.
(handle_inferior_event) <fork/vfork>: Call start_step_over after
displaced_step_fixup.
(handle_signal_stop): Call start_step_over after
displaced_step_fixup.
* infrun.h (step_over_queue_head): New declaration.
* thread.c (step_over_chain_enqueue, step_over_chain_remove)
(thread_step_over_chain_next, thread_is_in_step_over_chain)
(thread_step_over_chain_enqueue)
(thread_step_over_chain_remove): New functions.
(delete_thread_1): Remove thread from the step-over chain.
2015-08-07 Pedro Alves <palves@redhat.com>
* infrun.c (thread_still_needs_step_over): Rename to ...

View File

@ -283,6 +283,12 @@ struct thread_info
/* Values that are stored as temporaries on stack while evaluating
expressions. */
value_vec *stack_temporaries;
/* Step-over chain. A thread is in the step-over queue if these are
non-NULL. If only a single thread is in the chain, then these
fields point to self. */
struct thread_info *step_over_prev;
struct thread_info *step_over_next;
};
/* Create an empty thread list, or empty the existing one. */
@ -500,6 +506,23 @@ extern struct value *get_last_thread_stack_temporary (ptid_t);
extern int value_in_thread_stack_temporaries (struct value *, ptid_t);
/* Add TP to the end of its inferior's pending step-over chain. */
extern void thread_step_over_chain_enqueue (struct thread_info *tp);
/* Remove TP from its inferior's pending step-over chain. */
extern void thread_step_over_chain_remove (struct thread_info *tp);
/* Return the next thread in the step-over chain starting at TP. NULL
if TP is the last entry in the chain. */
extern struct thread_info *thread_step_over_chain_next (struct thread_info *tp);
/* Return true if TP is in the step-over chain. */
extern int thread_is_in_step_over_chain (struct thread_info *tp);
extern struct thread_info *thread_list;
#endif /* GDBTHREAD_H */

View File

@ -1221,6 +1221,15 @@ follow_exec (ptid_t ptid, char *execd_pathname)
matically get reset there in the new process.). */
}
/* The queue of threads that need to do a step-over operation to get
past e.g., a breakpoint. What technique is used to step over the
breakpoint/watchpoint does not matter -- all threads end up in the
same queue, to maintain rough temporal order of execution, in order
to avoid starvation, otherwise, we could e.g., find ourselves
constantly stepping the same couple threads past their breakpoints
over and over, if the single-step finish fast enough. */
struct thread_info *step_over_queue_head;
/* Bit flags indicating what the thread needs to step over. */
enum step_over_what
@ -1417,12 +1426,6 @@ step_over_info_valid_p (void)
displaced step operation on it. See displaced_step_prepare and
displaced_step_fixup for details. */
struct displaced_step_request
{
ptid_t ptid;
struct displaced_step_request *next;
};
/* Per-inferior displaced stepping state. */
struct displaced_step_inferior_state
{
@ -1432,10 +1435,6 @@ struct displaced_step_inferior_state
/* The process this displaced step state refers to. */
int pid;
/* A queue of pending displaced stepping requests. One entry per
thread that needs to do a displaced step. */
struct displaced_step_request *step_request_queue;
/* If this is not null_ptid, this is the thread carrying out a
displaced single-step in process PID. This thread's state will
require fixing up once it has completed its step. */
@ -1667,6 +1666,9 @@ displaced_step_prepare (ptid_t ptid)
support displaced stepping. */
gdb_assert (gdbarch_displaced_step_copy_insn_p (gdbarch));
/* Nor if the thread isn't meant to step over a breakpoint. */
gdb_assert (tp->control.trap_expected);
/* Disable range stepping while executing in the scratch pad. We
want a single-step even if executing the displaced instruction in
the scratch buffer lands within the stepping range (e.g., a
@ -1682,28 +1684,13 @@ displaced_step_prepare (ptid_t ptid)
{
/* Already waiting for a displaced step to finish. Defer this
request and place in queue. */
struct displaced_step_request *req, *new_req;
if (debug_displaced)
fprintf_unfiltered (gdb_stdlog,
"displaced: defering step of %s\n",
"displaced: deferring step of %s\n",
target_pid_to_str (ptid));
new_req = xmalloc (sizeof (*new_req));
new_req->ptid = ptid;
new_req->next = NULL;
if (displaced->step_request_queue)
{
for (req = displaced->step_request_queue;
req && req->next;
req = req->next)
;
req->next = new_req;
}
else
displaced->step_request_queue = new_req;
thread_step_over_chain_enqueue (tp);
return 0;
}
else
@ -1853,24 +1840,45 @@ displaced_step_fixup (ptid_t event_ptid, enum gdb_signal signal)
do_cleanups (old_cleanups);
displaced->step_ptid = null_ptid;
}
/* Are there any pending displaced stepping requests? If so, run
one now. Leave the state object around, since we're likely to
need it again soon. */
while (displaced->step_request_queue)
/* Are there any pending step-over requests? If so, run all we can
now. */
static void
start_step_over (void)
{
struct thread_info *tp, *next;
for (tp = step_over_queue_head; tp != NULL; tp = next)
{
struct displaced_step_request *head;
ptid_t ptid;
struct displaced_step_inferior_state *displaced;
struct regcache *regcache;
struct gdbarch *gdbarch;
CORE_ADDR actual_pc;
struct address_space *aspace;
struct inferior *inf = find_inferior_ptid (tp->ptid);
head = displaced->step_request_queue;
ptid = head->ptid;
displaced->step_request_queue = head->next;
xfree (head);
next = thread_step_over_chain_next (tp);
displaced = get_displaced_stepping_state (inf->pid);
/* If this inferior already has a displaced step in process,
don't start a new one. */
if (!ptid_equal (displaced->step_ptid, null_ptid))
continue;
thread_step_over_chain_remove (tp);
if (step_over_queue_head == NULL)
{
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog,
"infrun: step-over queue now empty\n");
}
ptid = tp->ptid;
context_switch (ptid);
regcache = get_thread_regcache (ptid);
@ -1905,7 +1913,6 @@ displaced_step_fixup (ptid_t event_ptid, enum gdb_signal signal)
target_resume (ptid, 0, GDB_SIGNAL_0);
/* Done, we're stepping a thread. */
break;
}
else
{
@ -1933,6 +1940,10 @@ displaced_step_fixup (ptid_t event_ptid, enum gdb_signal signal)
/* This request was discarded. See if there's any other
thread waiting for its turn. */
}
/* A new displaced stepping sequence started. Maybe we can
start a displaced step on a thread of other process.
Continue looking. */
}
}
@ -1953,10 +1964,6 @@ infrun_thread_ptid_changed (ptid_t old_ptid, ptid_t new_ptid)
{
if (ptid_equal (displaced->step_ptid, old_ptid))
displaced->step_ptid = new_ptid;
for (it = displaced->step_request_queue; it; it = it->next)
if (ptid_equal (it->ptid, old_ptid))
it->ptid = new_ptid;
}
}
@ -2135,6 +2142,8 @@ resume (enum gdb_signal sig)
tp->stepped_breakpoint = 0;
gdb_assert (!thread_is_in_step_over_chain (tp));
QUIT;
/* Depends on stepped_breakpoint. */
@ -2654,6 +2663,8 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
/* Fill in with reasonable starting values. */
init_thread_stepping_state (tp);
gdb_assert (!thread_is_in_step_over_chain (tp));
if (addr == (CORE_ADDR) -1)
{
if (pc == stop_pc
@ -2959,35 +2970,17 @@ infrun_thread_stop_requested_callback (struct thread_info *info, void *arg)
static void
infrun_thread_stop_requested (ptid_t ptid)
{
struct displaced_step_inferior_state *displaced;
struct thread_info *tp;
/* PTID was requested to stop. Remove it from the displaced
stepping queue, so we don't try to resume it automatically. */
for (displaced = displaced_step_inferior_states;
displaced;
displaced = displaced->next)
{
struct displaced_step_request *it, **prev_next_p;
it = displaced->step_request_queue;
prev_next_p = &displaced->step_request_queue;
while (it)
{
if (ptid_match (it->ptid, ptid))
{
*prev_next_p = it->next;
it->next = NULL;
xfree (it);
}
else
{
prev_next_p = &it->next;
}
it = *prev_next_p;
}
}
/* PTID was requested to stop. Remove matching threads from the
step-over queue, so we don't try to resume them
automatically. */
ALL_NON_EXITED_THREADS (tp)
if (ptid_match (tp->ptid, ptid))
{
if (thread_is_in_step_over_chain (tp))
thread_step_over_chain_remove (tp);
}
iterate_over_threads (infrun_thread_stop_requested_callback, &ptid);
}
@ -4045,6 +4038,9 @@ Cannot fill $_exitsignal with the correct signal number.\n"));
that this operation also cleans up the child process for vfork,
because their pages are shared. */
displaced_step_fixup (ecs->ptid, GDB_SIGNAL_TRAP);
/* Start a new step-over in another thread if there's one
that needs it. */
start_step_over ();
if (ecs->ws.kind == TARGET_WAITKIND_FORKED)
{
@ -4293,6 +4289,7 @@ handle_signal_stop (struct execution_control_state *ecs)
the PC, so do it here, before we set stop_pc.) */
displaced_step_fixup (ecs->ptid,
ecs->event_thread->suspend.stop_signal);
start_step_over ();
/* If we either finished a single-step or hit a breakpoint, but
the user wanted this thread to be stopped, pretend we got a

View File

@ -189,4 +189,8 @@ 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);
/* 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;
#endif /* INFRUN_H */

View File

@ -307,6 +307,86 @@ add_thread (ptid_t ptid)
return add_thread_with_info (ptid, NULL);
}
/* Add TP to the end of the step-over chain LIST_P. */
static void
step_over_chain_enqueue (struct thread_info **list_p, struct thread_info *tp)
{
gdb_assert (tp->step_over_next == NULL);
gdb_assert (tp->step_over_prev == NULL);
if (*list_p == NULL)
{
*list_p = tp;
tp->step_over_prev = tp->step_over_next = tp;
}
else
{
struct thread_info *head = *list_p;
struct thread_info *tail = head->step_over_prev;
tp->step_over_prev = tail;
tp->step_over_next = head;
head->step_over_prev = tp;
tail->step_over_next = tp;
}
}
/* Remove TP from step-over chain LIST_P. */
static void
step_over_chain_remove (struct thread_info **list_p, struct thread_info *tp)
{
gdb_assert (tp->step_over_next != NULL);
gdb_assert (tp->step_over_prev != NULL);
if (*list_p == tp)
{
if (tp == tp->step_over_next)
*list_p = NULL;
else
*list_p = tp->step_over_next;
}
tp->step_over_prev->step_over_next = tp->step_over_next;
tp->step_over_next->step_over_prev = tp->step_over_prev;
tp->step_over_prev = tp->step_over_next = NULL;
}
/* See gdbthread.h. */
struct thread_info *
thread_step_over_chain_next (struct thread_info *tp)
{
struct thread_info *next = tp->step_over_next;
return (next == step_over_queue_head ? NULL : next);
}
/* See gdbthread.h. */
int
thread_is_in_step_over_chain (struct thread_info *tp)
{
return (tp->step_over_next != NULL);
}
/* See gdbthread.h. */
void
thread_step_over_chain_enqueue (struct thread_info *tp)
{
step_over_chain_enqueue (&step_over_queue_head, tp);
}
/* See gdbthread.h. */
void
thread_step_over_chain_remove (struct thread_info *tp)
{
step_over_chain_remove (&step_over_queue_head, tp);
}
/* Delete thread PTID. If SILENT, don't notify the observer of this
exit. */
static void
@ -323,6 +403,10 @@ delete_thread_1 (ptid_t ptid, int silent)
if (!tp)
return;
/* Dead threads don't need to step-over. Remove from queue. */
if (tp->step_over_next != NULL)
thread_step_over_chain_remove (tp);
/* If this is the current thread, or there's code out there that
relies on it existing (refcount > 0) we can't delete yet. Mark
it as exited, and notify it. */