+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 ...
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
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
{
/* 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. */
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
{
/* 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
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);
+
+ next = thread_step_over_chain_next (tp);
- head = displaced->step_request_queue;
- ptid = head->ptid;
- displaced->step_request_queue = head->next;
- xfree (head);
+ 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);
target_resume (ptid, 0, GDB_SIGNAL_0);
/* Done, we're stepping a thread. */
- break;
}
else
{
/* 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. */
}
}
{
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;
}
}
tp->stepped_breakpoint = 0;
+ gdb_assert (!thread_is_in_step_over_chain (tp));
+
QUIT;
/* Depends on stepped_breakpoint. */
/* 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
static void
infrun_thread_stop_requested (ptid_t ptid)
{
- struct displaced_step_inferior_state *displaced;
-
- /* 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;
- }
+ struct thread_info *tp;
- 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);
}
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)
{
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
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
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. */