diff options
-rw-r--r-- | gdb/ChangeLog | 30 | ||||
-rw-r--r-- | gdb/gdbthread.h | 23 | ||||
-rw-r--r-- | gdb/infrun.c | 135 | ||||
-rw-r--r-- | gdb/infrun.h | 4 | ||||
-rw-r--r-- | gdb/thread.c | 84 |
5 files changed, 207 insertions, 69 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 5054402..f7c559b 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,35 @@ 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 ... (thread_still_needs_step_over_bp): ... this. (enum step_over_what): New. diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index fefad48..2602e8b 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -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 */ diff --git a/gdb/infrun.c b/gdb/infrun.c index d3cd0ae..9cd897a 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -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); + + 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); @@ -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; - - /* 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); } @@ -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 diff --git a/gdb/infrun.h b/gdb/infrun.h index 75bb075..7d1033c 100644 --- a/gdb/infrun.h +++ b/gdb/infrun.h @@ -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 */ diff --git a/gdb/thread.c b/gdb/thread.c index 46b5947..28e5ef8 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -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. */ |