From 582511be69deb0e9d52efd6d51f860b6bee02a64 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Mon, 29 Dec 2014 19:41:07 +0000 Subject: [PATCH] [gdbserver] linux-low.c: better starvation avoidance, handle non-stop mode too This patch applies the same starvation avoidance improvements of the previous patch to the Linux gdbserver side. Without this, the test added by the following commit (gdb.threads/non-stop-fair-events.exp) always fails with time outs. gdb/gdbserver/ 2015-01-09 Pedro Alves * linux-low.c (step_over_bkpt): Move higher up in the file. (handle_extended_wait): Don't store the stop_pc here. (get_stop_pc): Adjust comments and rename to ... (check_stopped_by_breakpoint): ... this. Record whether the LWP stopped for a software breakpoint or hardware breakpoint. (thread_still_has_status_pending_p): New function. (status_pending_p_callback): Use thread_still_has_status_pending_p. If the event is no longer interesting, resume the LWP. (handle_tracepoints): Add assert. (maybe_move_out_of_jump_pad): Remove cancel_breakpoints call. (wstatus_maybe_breakpoint): New function. (cancel_breakpoint): Delete function. (check_stopped_by_watchpoint): New function, factored out from linux_low_filter_event. (lp_status_maybe_breakpoint): Delete function. (linux_low_filter_event): Remove filter_ptid argument. Leave thread group exits pending here. Store the LWP's stop PC. Always leave events pending. (linux_wait_for_event_filtered): Pull all events out of the kernel, and leave them all pending. (count_events_callback, select_event_lwp_callback): Consider all events. (cancel_breakpoints_callback, linux_cancel_breakpoints): Delete. (select_event_lwp): Only give preference to the stepping LWP in all-stop mode. Adjust comments. (ignore_event): New function. (linux_wait_1): Delete 'retry' label. Use ignore_event. Remove references to cancel_breakpoints. Adjust to renames. Also give equal priority to all LWPs that have had events in non-stop mode. If reporting a software breakpoint event, unadjust the LWP's PC. (linux_wait): If linux_wait_1 returned an ignored event, retry. (stuck_in_jump_pad_callback, move_out_of_jump_pad_callback): Adjust. (linux_resume_one_lwp): Store the LWP's PC. Adjust. (resume_status_pending_p): Use thread_still_has_status_pending_p. (linux_stopped_by_watchpoint): Adjust. (linux_target_ops): Remove reference to linux_cancel_breakpoints. * linux-low.h (enum lwp_stop_reason): New. (struct lwp_info) : Adjust comment. : Delete field. : New field. * linux-x86-low.c (x86_linux_prepare_to_resume): Adjust. * mem-break.c (software_breakpoint_inserted_here) (hardware_breakpoint_inserted_here): New function. * mem-break.h (software_breakpoint_inserted_here) (hardware_breakpoint_inserted_here): Declare. * target.h (struct target_ops) : Remove field. (cancel_breakpoints): Delete. * tracepoint.c (clear_installed_tracepoints, stop_tracing) (upload_fast_traceframes): Remove references to cancel_breakpoints. --- gdb/gdbserver/ChangeLog | 55 +++ gdb/gdbserver/linux-low.c | 707 ++++++++++++++++++---------------- gdb/gdbserver/linux-low.h | 29 +- gdb/gdbserver/linux-x86-low.c | 2 +- gdb/gdbserver/mem-break.c | 34 ++ gdb/gdbserver/mem-break.h | 10 + gdb/gdbserver/target.h | 10 - gdb/gdbserver/tracepoint.c | 5 - 8 files changed, 506 insertions(+), 346 deletions(-) diff --git a/gdb/gdbserver/ChangeLog b/gdb/gdbserver/ChangeLog index d6bafae9b8d..180e341c9e2 100644 --- a/gdb/gdbserver/ChangeLog +++ b/gdb/gdbserver/ChangeLog @@ -1,3 +1,58 @@ +2015-01-09 Pedro Alves + + * linux-low.c (step_over_bkpt): Move higher up in the file. + (handle_extended_wait): Don't store the stop_pc here. + (get_stop_pc): Adjust comments and rename to ... + (check_stopped_by_breakpoint): ... this. Record whether the LWP + stopped for a software breakpoint or hardware breakpoint. + (thread_still_has_status_pending_p): New function. + (status_pending_p_callback): Use + thread_still_has_status_pending_p. If the event is no longer + interesting, resume the LWP. + (handle_tracepoints): Add assert. + (maybe_move_out_of_jump_pad): Remove cancel_breakpoints call. + (wstatus_maybe_breakpoint): New function. + (cancel_breakpoint): Delete function. + (check_stopped_by_watchpoint): New function, factored out from + linux_low_filter_event. + (lp_status_maybe_breakpoint): Delete function. + (linux_low_filter_event): Remove filter_ptid argument. + Leave thread group exits pending here. Store the LWP's stop PC. + Always leave events pending. + (linux_wait_for_event_filtered): Pull all events out of the + kernel, and leave them all pending. + (count_events_callback, select_event_lwp_callback): Consider all + events. + (cancel_breakpoints_callback, linux_cancel_breakpoints): Delete. + (select_event_lwp): Only give preference to the stepping LWP in + all-stop mode. Adjust comments. + (ignore_event): New function. + (linux_wait_1): Delete 'retry' label. Use ignore_event. Remove + references to cancel_breakpoints. Adjust to renames. Also give + equal priority to all LWPs that have had events in non-stop mode. + If reporting a software breakpoint event, unadjust the LWP's PC. + (linux_wait): If linux_wait_1 returned an ignored event, retry. + (stuck_in_jump_pad_callback, move_out_of_jump_pad_callback): + Adjust. + (linux_resume_one_lwp): Store the LWP's PC. Adjust. + (resume_status_pending_p): Use thread_still_has_status_pending_p. + (linux_stopped_by_watchpoint): Adjust. + (linux_target_ops): Remove reference to linux_cancel_breakpoints. + * linux-low.h (enum lwp_stop_reason): New. + (struct lwp_info) : Adjust comment. + : Delete field. + : New field. + * linux-x86-low.c (x86_linux_prepare_to_resume): Adjust. + * mem-break.c (software_breakpoint_inserted_here) + (hardware_breakpoint_inserted_here): New function. + * mem-break.h (software_breakpoint_inserted_here) + (hardware_breakpoint_inserted_here): Declare. + * target.h (struct target_ops) : Remove field. + (cancel_breakpoints): Delete. + * tracepoint.c (clear_installed_tracepoints, stop_tracing) + (upload_fast_traceframes): Remove references to + cancel_breakpoints. + 2015-01-09 Pedro Alves * thread-db.c (find_new_threads_callback): Ignore thread if the diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c index 268ee5ceab9..4d19c870da2 100644 --- a/gdb/gdbserver/linux-low.c +++ b/gdb/gdbserver/linux-low.c @@ -218,9 +218,12 @@ static int linux_stopped_by_watchpoint (void); static void mark_lwp_dead (struct lwp_info *lwp, int wstat); static void proceed_all_lwps (void); 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); +/* When the event-loop is doing a step-over, this points at the thread + being stepped. */ +ptid_t step_over_bkpt; + /* True if the low target can hardware single-step. Such targets don't need a BREAKPOINT_REINSERT_ADDR callback. */ @@ -363,6 +366,8 @@ linux_add_process (int pid, int attached) return proc; } +static CORE_ADDR get_pc (struct lwp_info *lwp); + /* Handle a GNU/Linux extended wait response. If we see a clone event, we need to add the new LWP to our list (and not report the trap to higher layers). */ @@ -423,9 +428,7 @@ handle_extended_wait (struct lwp_info *event_child, int wstat) If we do get another signal, be sure not to lose it. */ if (WSTOPSIG (status) == SIGSTOP) { - if (stopping_threads != NOT_STOPPING_THREADS) - new_lwp->stop_pc = get_stop_pc (new_lwp); - else + if (stopping_threads == NOT_STOPPING_THREADS) linux_resume_one_lwp (new_lwp, 0, 0, NULL); } else @@ -434,7 +437,6 @@ handle_extended_wait (struct lwp_info *event_child, int wstat) if (stopping_threads != NOT_STOPPING_THREADS) { - new_lwp->stop_pc = get_stop_pc (new_lwp); new_lwp->status_pending_p = 1; new_lwp->status_pending = status; } @@ -481,44 +483,96 @@ get_pc (struct lwp_info *lwp) The SIGTRAP could mean several things. On i386, where decr_pc_after_break is non-zero: - If we were single-stepping this process using PTRACE_SINGLESTEP, - we will get only the one SIGTRAP (even if the instruction we - stepped over was a breakpoint). The value of $eip will be the - next instruction. + + If we were single-stepping this process using PTRACE_SINGLESTEP, we + will get only the one SIGTRAP. The value of $eip will be the next + instruction. If the instruction we stepped over was a breakpoint, + we need to decrement the PC. + If we continue the process using PTRACE_CONT, we will get a SIGTRAP when we hit a breakpoint. The value of $eip will be the instruction after the breakpoint (i.e. needs to be decremented). If we report the SIGTRAP to GDB, we must also - report the undecremented PC. If we cancel the SIGTRAP, we + report the undecremented PC. If the breakpoint is removed, we must resume at the decremented PC. - (Presumably, not yet tested) On a non-decr_pc_after_break machine - with hardware or kernel single-step: - If we single-step over a breakpoint instruction, our PC will - point at the following instruction. If we continue and hit a - breakpoint instruction, our PC will point at the breakpoint + On a non-decr_pc_after_break machine with hardware or kernel + single-step: + + If we either single-step a breakpoint instruction, or continue and + hit a breakpoint instruction, our PC will point at the breakpoint instruction. */ -static CORE_ADDR -get_stop_pc (struct lwp_info *lwp) +static int +check_stopped_by_breakpoint (struct lwp_info *lwp) { - CORE_ADDR stop_pc; + CORE_ADDR pc; + CORE_ADDR sw_breakpoint_pc; + struct thread_info *saved_thread; if (the_low_target.get_pc == NULL) return 0; - stop_pc = get_pc (lwp); + pc = get_pc (lwp); + sw_breakpoint_pc = pc - the_low_target.decr_pc_after_break; - if (WSTOPSIG (lwp->last_status) == SIGTRAP - && !lwp->stepping - && !lwp->stopped_by_watchpoint - && !linux_is_extended_waitstatus (lwp->last_status)) - stop_pc -= the_low_target.decr_pc_after_break; + /* breakpoint_at reads from the current thread. */ + saved_thread = current_thread; + current_thread = get_lwp_thread (lwp); - if (debug_threads) - debug_printf ("stop pc is 0x%lx\n", (long) stop_pc); + /* We may have just stepped a breakpoint instruction. E.g., in + non-stop mode, GDB first tells the thread A to step a range, and + then the user inserts a breakpoint inside the range. In that + case, we need to report the breakpoint PC. But, when we're + trying to step past one of our own breakpoints, that happens to + have been placed on top of a permanent breakpoint instruction, we + shouldn't adjust the PC, otherwise the program would keep + trapping the permanent breakpoint forever. */ + if ((!lwp->stepping + || (!ptid_equal (ptid_of (current_thread), step_over_bkpt) + && lwp->stop_pc == sw_breakpoint_pc)) + && (*the_low_target.breakpoint_at) (sw_breakpoint_pc)) + { + if (debug_threads) + { + struct thread_info *thr = get_lwp_thread (lwp); + + debug_printf ("CSBB: %s stopped by software breakpoint\n", + target_pid_to_str (ptid_of (thr))); + } + + /* Back up the PC if necessary. */ + if (pc != sw_breakpoint_pc) + { + struct regcache *regcache + = get_thread_regcache (current_thread, 1); + (*the_low_target.set_pc) (regcache, sw_breakpoint_pc); + } + + lwp->stop_pc = sw_breakpoint_pc; + lwp->stop_reason = LWP_STOPPED_BY_SW_BREAKPOINT; + current_thread = saved_thread; + return 1; + } + + if (hardware_breakpoint_inserted_here (pc)) + { + if (debug_threads) + { + struct thread_info *thr = get_lwp_thread (lwp); + + debug_printf ("CSBB: %s stopped by hardware breakpoint\n", + target_pid_to_str (ptid_of (thr))); + } - return stop_pc; + lwp->stop_pc = pc; + lwp->stop_reason = LWP_STOPPED_BY_HW_BREAKPOINT; + current_thread = saved_thread; + return 1; + } + + current_thread = saved_thread; + return 0; } static struct lwp_info * @@ -1197,12 +1251,83 @@ linux_thread_alive (ptid_t ptid) return 0; } +/* Return 1 if this lwp still has an interesting status pending. If + not (e.g., it had stopped for a breakpoint that is gone), return + false. */ + +static int +thread_still_has_status_pending_p (struct thread_info *thread) +{ + struct lwp_info *lp = get_thread_lwp (thread); + + if (!lp->status_pending_p) + return 0; + + /* If we got a `vCont;t', but we haven't reported a stop yet, do + report any status pending the LWP may have. */ + if (thread->last_resume_kind == resume_stop + && thread->last_status.kind != TARGET_WAITKIND_IGNORE) + return 0; + + if (thread->last_resume_kind != resume_stop + && (lp->stop_reason == LWP_STOPPED_BY_SW_BREAKPOINT + || lp->stop_reason == LWP_STOPPED_BY_HW_BREAKPOINT)) + { + struct thread_info *saved_thread; + CORE_ADDR pc; + int discard = 0; + + gdb_assert (lp->last_status != 0); + + pc = get_pc (lp); + + saved_thread = current_thread; + current_thread = thread; + + if (pc != lp->stop_pc) + { + if (debug_threads) + debug_printf ("PC of %ld changed\n", + lwpid_of (thread)); + discard = 1; + } + else if (lp->stop_reason == LWP_STOPPED_BY_SW_BREAKPOINT + && !(*the_low_target.breakpoint_at) (pc)) + { + if (debug_threads) + debug_printf ("previous SW breakpoint of %ld gone\n", + lwpid_of (thread)); + discard = 1; + } + else if (lp->stop_reason == LWP_STOPPED_BY_HW_BREAKPOINT + && !hardware_breakpoint_inserted_here (pc)) + { + if (debug_threads) + debug_printf ("previous HW breakpoint of %ld gone\n", + lwpid_of (thread)); + discard = 1; + } + + current_thread = saved_thread; + + if (discard) + { + if (debug_threads) + debug_printf ("discarding pending breakpoint status\n"); + lp->status_pending_p = 0; + return 0; + } + } + + return 1; +} + /* Return 1 if this lwp has an interesting status pending. */ static int status_pending_p_callback (struct inferior_list_entry *entry, void *arg) { struct thread_info *thread = (struct thread_info *) entry; - struct lwp_info *lwp = get_thread_lwp (thread); + struct lwp_info *lp = get_thread_lwp (thread); ptid_t ptid = * (ptid_t *) arg; /* Check if we're only interested in events from a specific process @@ -1211,13 +1336,14 @@ status_pending_p_callback (struct inferior_list_entry *entry, void *arg) && ptid_get_pid (ptid) != ptid_get_pid (thread->entry.id)) return 0; - /* If we got a `vCont;t', but we haven't reported a stop yet, do - report any status pending the LWP may have. */ - if (thread->last_resume_kind == resume_stop - && thread->last_status.kind != TARGET_WAITKIND_IGNORE) - return 0; + if (lp->status_pending_p + && !thread_still_has_status_pending_p (thread)) + { + linux_resume_one_lwp (lp, lp->stepping, GDB_SIGNAL_0, NULL); + return 0; + } - return lwp->status_pending_p; + return lp->status_pending_p; } static int @@ -1363,6 +1489,8 @@ handle_tracepoints (struct lwp_info *lwp) struct thread_info *tinfo = get_lwp_thread (lwp); int tpoint_related_event = 0; + gdb_assert (lwp->suspended == 0); + /* If this tracepoint hit causes a tracing stop, we'll immediately uninsert tracepoints. To do this, we temporarily pause all threads, unpatch away, and then unpause threads. We need to make @@ -1528,7 +1656,6 @@ maybe_move_out_of_jump_pad (struct lwp_info *lwp, int *wstat) "stopping all threads momentarily.\n"); stop_all_lwps (1, lwp); - cancel_breakpoints (); delete_breakpoint (lwp->exit_jump_pad_bkpt); lwp->exit_jump_pad_bkpt = NULL; @@ -1654,65 +1781,59 @@ dequeue_one_deferred_signal (struct lwp_info *lwp, int *wstat) return 0; } -/* 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. */ +/* Return true if the event in LP may be caused by breakpoint. */ static int -cancel_breakpoint (struct lwp_info *lwp) +wstatus_maybe_breakpoint (int wstatus) { - struct thread_info *saved_thread; + return (WIFSTOPPED (wstatus) + && (WSTOPSIG (wstatus) == SIGTRAP + /* SIGILL and SIGSEGV are also treated as traps in case a + breakpoint is inserted at the current PC. */ + || WSTOPSIG (wstatus) == SIGILL + || WSTOPSIG (wstatus) == SIGSEGV)); +} - /* There's nothing to do if we don't support breakpoints. */ - if (!supports_breakpoints ()) - return 0; +/* Fetch the possibly triggered data watchpoint info and store it in + CHILD. - /* breakpoint_at reads from current inferior. */ - saved_thread = current_thread; - current_thread = get_lwp_thread (lwp); + On some archs, like x86, that use debug registers to set + watchpoints, it's possible that the way to know which watched + address trapped, is to check the register that is used to select + which address to watch. Problem is, between setting the watchpoint + and reading back which data address trapped, the user may change + the set of watchpoints, and, as a consequence, GDB changes the + debug registers in the inferior. To avoid reading back a stale + stopped-data-address when that happens, we cache in LP the fact + that a watchpoint trapped, and the corresponding data address, as + soon as we see CHILD stop with a SIGTRAP. If GDB changes the debug + registers meanwhile, we have the cached data we can rely on. */ - if ((*the_low_target.breakpoint_at) (lwp->stop_pc)) +static int +check_stopped_by_watchpoint (struct lwp_info *child) +{ + if (the_low_target.stopped_by_watchpoint != NULL) { - if (debug_threads) - debug_printf ("CB: Push back breakpoint for %s\n", - target_pid_to_str (ptid_of (current_thread))); + struct thread_info *saved_thread; - /* Back up the PC if necessary. */ - if (the_low_target.decr_pc_after_break) + saved_thread = current_thread; + current_thread = get_lwp_thread (child); + + if (the_low_target.stopped_by_watchpoint ()) { - struct regcache *regcache - = get_thread_regcache (current_thread, 1); - (*the_low_target.set_pc) (regcache, lwp->stop_pc); + child->stop_reason = LWP_STOPPED_BY_WATCHPOINT; + + if (the_low_target.stopped_data_address != NULL) + child->stopped_data_address + = the_low_target.stopped_data_address (); + else + child->stopped_data_address = 0; } current_thread = saved_thread; - return 1; - } - else - { - if (debug_threads) - debug_printf ("CB: No breakpoint found at %s for [%s]\n", - paddress (lwp->stop_pc), - target_pid_to_str (ptid_of (current_thread))); } - current_thread = saved_thread; - return 0; -} - -/* Return true if the event in LP may be caused by breakpoint. */ - -static int -lp_status_maybe_breakpoint (struct lwp_info *lp) -{ - return (lp->status_pending_p - && WIFSTOPPED (lp->status_pending) - && (WSTOPSIG (lp->status_pending) == SIGTRAP - /* SIGILL and SIGSEGV are also treated as traps in case a - breakpoint is inserted at the current PC. */ - || WSTOPSIG (lp->status_pending) == SIGILL - || WSTOPSIG (lp->status_pending) == SIGSEGV)); + return child->stop_reason == LWP_STOPPED_BY_WATCHPOINT; } /* Do low-level handling of the event, and check if we should go on @@ -1720,10 +1841,11 @@ lp_status_maybe_breakpoint (struct lwp_info *lp) NULL otherwise. */ static struct lwp_info * -linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat) +linux_low_filter_event (int lwpid, int wstat) { struct lwp_info *child; struct thread_info *thread; + int have_stop_pc = 0; child = find_lwp_pid (pid_to_ptid (lwpid)); @@ -1745,6 +1867,35 @@ linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat) child->last_status = wstat; + /* Check if the thread has exited. */ + if ((WIFEXITED (wstat) || WIFSIGNALED (wstat))) + { + if (debug_threads) + debug_printf ("LLFE: %d exited.\n", lwpid); + if (num_lwps (pid_of (thread)) > 1) + { + + /* If there is at least one more LWP, then the exit signal was + not the end of the debugged application and should be + ignored. */ + delete_lwp (child); + return NULL; + } + else + { + /* This was the last lwp in the process. Since events are + serialized to GDB core, and we can't report this one + right now, but GDB core and the other target layers will + want to be notified about the exit code/signal, leave the + status pending for the next time we're able to report + it. */ + mark_lwp_dead (child, wstat); + return child; + } + } + + gdb_assert (WIFSTOPPED (wstat)); + if (WIFSTOPPED (wstat)) { struct process_info *proc; @@ -1769,75 +1920,6 @@ linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat) } } - /* 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 (wstat)) - { - if (debug_threads - && the_low_target.get_pc != NULL) - { - struct thread_info *saved_thread; - struct regcache *regcache; - CORE_ADDR pc; - - saved_thread = current_thread; - current_thread = thread; - regcache = get_thread_regcache (current_thread, 1); - pc = (*the_low_target.get_pc) (regcache); - debug_printf ("linux_low_filter_event: pc is 0x%lx\n", (long) pc); - current_thread = saved_thread; - } - - child->stop_pc = get_stop_pc (child); - } - - /* Fetch the possibly triggered data watchpoint info and store it in - CHILD. - - On some archs, like x86, that use debug registers to set - watchpoints, it's possible that the way to know which watched - address trapped, is to check the register that is used to select - which address to watch. Problem is, between setting the - watchpoint and reading back which data address trapped, the user - may change the set of watchpoints, and, as a consequence, GDB - changes the debug registers in the inferior. To avoid reading - back a stale stopped-data-address when that happens, we cache in - LP the fact that a watchpoint trapped, and the corresponding data - address, as soon as we see CHILD stop with a SIGTRAP. If GDB - changes the debug registers meanwhile, we have the cached data we - can rely on. */ - - if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP) - { - if (the_low_target.stopped_by_watchpoint == NULL) - { - child->stopped_by_watchpoint = 0; - } - else - { - struct thread_info *saved_thread; - - saved_thread = current_thread; - current_thread = thread; - - child->stopped_by_watchpoint - = the_low_target.stopped_by_watchpoint (); - - if (child->stopped_by_watchpoint) - { - if (the_low_target.stopped_data_address != NULL) - child->stopped_data_address - = the_low_target.stopped_data_address (); - else - child->stopped_data_address = 0; - } - - current_thread = saved_thread; - } - } - if (WIFSTOPPED (wstat) && child->must_set_ptrace_flags) { struct process_info *proc = find_process_pid (pid_of (thread)); @@ -1846,13 +1928,28 @@ linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat) child->must_set_ptrace_flags = 0; } + /* Be careful to not overwrite stop_pc until + check_stopped_by_breakpoint is called. */ if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP && linux_is_extended_waitstatus (wstat)) { + child->stop_pc = get_pc (child); handle_extended_wait (child, wstat); return NULL; } + if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP + && check_stopped_by_watchpoint (child)) + ; + else if (WIFSTOPPED (wstat) && wstatus_maybe_breakpoint (wstat)) + { + if (check_stopped_by_breakpoint (child)) + have_stop_pc = 1; + } + + if (!have_stop_pc) + child->stop_pc = get_pc (child); + if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGSTOP && child->stop_expected) { @@ -1868,7 +1965,7 @@ linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat) else if (stopping_threads != NOT_STOPPING_THREADS) { /* Stopping threads. We don't want this SIGSTOP to end up - pending in the FILTER_PTID handling below. */ + pending. */ return NULL; } else @@ -1879,79 +1976,11 @@ linux_low_filter_event (ptid_t filter_ptid, int lwpid, int wstat) } } - /* Check if the thread has exited. */ - if ((WIFEXITED (wstat) || WIFSIGNALED (wstat)) - && num_lwps (pid_of (thread)) > 1) - { - if (debug_threads) - debug_printf ("LLW: %d exited.\n", lwpid); - - /* If there is at least one more LWP, then the exit signal - was not the end of the debugged application and should be - ignored. */ - delete_lwp (child); - return NULL; - } - - if (!ptid_match (ptid_of (thread), filter_ptid)) - { - if (debug_threads) - debug_printf ("LWP %d got an event %06x, leaving pending.\n", - lwpid, wstat); - - if (WIFSTOPPED (wstat)) - { - child->status_pending_p = 1; - child->status_pending = wstat; - - if (WSTOPSIG (wstat) != SIGSTOP) - { - /* Cancel breakpoint hits. The breakpoint may be - removed before we fetch events from this process to - report to the core. It is best not to assume the - moribund breakpoints heuristic always handles these - cases --- it could be too many events go through to - the core before this one is handled. All-stop always - cancels breakpoint hits in all threads. */ - if (non_stop - && lp_status_maybe_breakpoint (child) - && cancel_breakpoint (child)) - { - /* Throw away the SIGTRAP. */ - child->status_pending_p = 0; - - if (debug_threads) - debug_printf ("LLW: LWP %d hit a breakpoint while" - " waiting for another process;" - " cancelled it\n", lwpid); - } - } - } - else if (WIFEXITED (wstat) || WIFSIGNALED (wstat)) - { - if (debug_threads) - debug_printf ("LLWE: process %d exited while fetching " - "event from another LWP\n", lwpid); - - /* This was the last lwp in the process. Since events are - serialized to GDB core, and we can't report this one - right now, but GDB core and the other target layers will - want to be notified about the exit code/signal, leave the - status pending for the next time we're able to report - it. */ - mark_lwp_dead (child, wstat); - } - - return NULL; - } - + child->status_pending_p = 1; + child->status_pending = wstat; return child; } -/* 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(ren) WAIT_PTID, and return any that match FILTER_PTID (leaving others pending). The PTIDs can be: minus_one_ptid, to specify any child; a pid PTID, specifying all @@ -2041,6 +2070,9 @@ linux_wait_for_event_filtered (ptid_t wait_ptid, ptid_t filter_ptid, sigfillset (&block_mask); sigprocmask (SIG_BLOCK, &block_mask, &prev_mask); + /* Always pull all events out of the kernel. We'll randomly select + an event LWP out of all that have events, to prevent + starvation. */ while (event_child == NULL) { pid_t ret = 0; @@ -2073,20 +2105,28 @@ linux_wait_for_event_filtered (ptid_t wait_ptid, ptid_t filter_ptid, (long) ret, status_to_str (*wstatp)); } - event_child = linux_low_filter_event (filter_ptid, - ret, *wstatp); - if (event_child != NULL) - { - /* We got an event to report to the core. */ - event_thread = get_lwp_thread (event_child); - break; - } - + /* Filter all events. IOW, leave all events pending. We'll + randomly select an event LWP out of all that have events + below. */ + linux_low_filter_event (ret, *wstatp); /* Retry until nothing comes out of waitpid. A single SIGCHLD can indicate more than one child stopped. */ continue; } + /* Now that we've pulled all events out of the kernel, check if + there's any LWP with a status to report to the core. */ + event_thread = (struct thread_info *) + find_inferior (&all_threads, status_pending_p_callback, &filter_ptid); + if (event_thread != NULL) + { + event_child = get_thread_lwp (event_thread); + *wstatp = event_child->status_pending; + event_child->status_pending_p = 0; + event_child->status_pending = 0; + break; + } + /* Check for zombie thread group leaders. Those can't be reaped until all other threads in the thread group are. */ check_zombie_leaders (); @@ -2166,17 +2206,14 @@ static int count_events_callback (struct inferior_list_entry *entry, void *data) { struct thread_info *thread = (struct thread_info *) entry; - struct lwp_info *lp = get_thread_lwp (thread); int *count = data; gdb_assert (count != NULL); - /* Count only resumed LWPs that have a SIGTRAP event pending that - should be reported to GDB. */ + /* Count only resumed LWPs that have an event pending. */ if (thread->last_status.kind == TARGET_WAITKIND_IGNORE && thread->last_resume_kind != resume_stop - && lp_status_maybe_breakpoint (lp) - && !breakpoint_inserted_here (lp->stop_pc)) + && thread->status_pending_p) (*count)++; return 0; @@ -2205,62 +2242,20 @@ static int select_event_lwp_callback (struct inferior_list_entry *entry, void *data) { struct thread_info *thread = (struct thread_info *) entry; - struct lwp_info *lp = get_thread_lwp (thread); int *selector = data; gdb_assert (selector != NULL); - /* Select only resumed LWPs that have a SIGTRAP event pending. */ + /* Select only resumed LWPs that have an event pending. */ if (thread->last_resume_kind != resume_stop && thread->last_status.kind == TARGET_WAITKIND_IGNORE - && lp_status_maybe_breakpoint (lp) - && !breakpoint_inserted_here (lp->stop_pc)) + && thread->status_pending_p) if ((*selector)-- == 0) return 1; return 0; } -static int -cancel_breakpoints_callback (struct inferior_list_entry *entry, void *data) -{ - struct thread_info *thread = (struct thread_info *) entry; - struct lwp_info *lp = get_thread_lwp (thread); - 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 (thread->last_resume_kind != resume_stop - && thread->last_status.kind == TARGET_WAITKIND_IGNORE - && lp_status_maybe_breakpoint (lp) - && !lp->stepping - && !lp->stopped_by_watchpoint - && cancel_breakpoint (lp)) - /* Throw away the SIGTRAP. */ - lp->status_pending_p = 0; - - return 0; -} - -static void -linux_cancel_breakpoints (void) -{ - find_inferior (&all_threads, cancel_breakpoints_callback, NULL); -} - /* Select one LWP out of those that have events pending. */ static void @@ -2268,20 +2263,30 @@ select_event_lwp (struct lwp_info **orig_lp) { int num_events = 0; int random_selector; - struct thread_info *event_thread; - - /* Give preference to any LWP that is being single-stepped. */ - event_thread - = (struct thread_info *) find_inferior (&all_threads, - select_singlestep_lwp_callback, - NULL); - if (event_thread != NULL) + struct thread_info *event_thread = NULL; + + /* In all-stop, give preference to the LWP that is being + single-stepped. There will be at most one, and it's the LWP that + the core is most interested in. If we didn't do this, then we'd + have to handle pending step SIGTRAPs somehow in case the core + later continues the previously-stepped thread, otherwise we'd + report the pending SIGTRAP, and the core, not having stepped the + thread, wouldn't understand what the trap was for, and therefore + would report it to the user as a random signal. */ + if (!non_stop) { - if (debug_threads) - debug_printf ("SEL: Select single-step %s\n", - target_pid_to_str (ptid_of (event_thread))); + event_thread + = (struct thread_info *) find_inferior (&all_threads, + select_singlestep_lwp_callback, + NULL); + if (event_thread != NULL) + { + if (debug_threads) + debug_printf ("SEL: Select single-step %s\n", + target_pid_to_str (ptid_of (event_thread))); + } } - else + if (event_thread == NULL) { /* No single-stepping LWP. Select one at random, out of those which have had SIGTRAP events. */ @@ -2448,6 +2453,23 @@ linux_stabilize_threads (void) } } +static void async_file_mark (void); + +/* Convenience function that is called when the kernel reports an + event that is not passed out to GDB. */ + +static ptid_t +ignore_event (struct target_waitstatus *ourstatus) +{ + /* If we got an event, there may still be others, as a single + SIGCHLD can indicate more than one child stopped. This forces + another target_wait call. */ + async_file_mark (); + + ourstatus->kind = TARGET_WAITKIND_IGNORE; + return null_ptid; +} + /* Wait for process, returns status. */ static ptid_t @@ -2476,7 +2498,6 @@ linux_wait_1 (ptid_t ptid, if (target_options & TARGET_WNOHANG) options |= WNOHANG; -retry: bp_explains_trap = 0; trace_event = 0; in_step_range = 0; @@ -2641,7 +2662,8 @@ retry: WSTOPSIG (w), lwpid_of (current_thread)); linux_resume_one_lwp (event_child, 0, 0, NULL); - goto retry; + + return ignore_event (ourstatus); } } @@ -2675,7 +2697,6 @@ retry: care of while the breakpoint is still inserted. */ stop_all_lwps (1, event_child); - cancel_breakpoints (); delete_breakpoint (event_child->exit_jump_pad_bkpt); event_child->exit_jump_pad_bkpt = NULL; @@ -2759,7 +2780,7 @@ retry: info_p = NULL; linux_resume_one_lwp (event_child, event_child->stepping, WSTOPSIG (w), info_p); - goto retry; + return ignore_event (ourstatus); } /* Note that all addresses are always "out of the step range" when @@ -2778,7 +2799,7 @@ retry: report_to_gdb = (!maybe_internal_trap || (current_thread->last_resume_kind == resume_step && !in_step_range) - || event_child->stopped_by_watchpoint + || event_child->stop_reason == LWP_STOPPED_BY_WATCHPOINT || (!step_over_finished && !in_step_range && !bp_explains_trap && !trace_event) || (gdb_breakpoint_here (event_child->stop_pc) @@ -2830,7 +2851,7 @@ retry: unsuspend_all_lwps (event_child); proceed_all_lwps (); - goto retry; + return ignore_event (ourstatus); } if (debug_threads) @@ -2842,9 +2863,9 @@ retry: else if (!lwp_in_step_range (event_child)) debug_printf ("Out of step range, reporting event.\n"); } - if (event_child->stopped_by_watchpoint) + if (event_child->stop_reason == LWP_STOPPED_BY_WATCHPOINT) debug_printf ("Stopped by watchpoint.\n"); - if (gdb_breakpoint_here (event_child->stop_pc)) + else if (gdb_breakpoint_here (event_child->stop_pc)) debug_printf ("Stopped by GDB breakpoint.\n"); if (debug_threads) debug_printf ("Hit a non-gdbserver trap event.\n"); @@ -2852,10 +2873,11 @@ retry: /* Alright, we're going to report a stop. */ - if (!non_stop && !stabilizing_threads) + if (!stabilizing_threads) { /* In all-stop, stop all threads. */ - stop_all_lwps (0, NULL); + if (!non_stop) + stop_all_lwps (0, NULL); /* If we're not waiting for a specific LWP, choose an event LWP from among those that have had events. Giving equal priority @@ -2875,23 +2897,33 @@ retry: 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_threads, cancel_breakpoints_callback, event_child); - - /* If we were going a step-over, all other threads but the stepping one - had been paused in start_step_over, with their suspend counts - incremented. We don't want to do a full unstop/unpause, because we're - in all-stop mode (so we want threads stopped), but we still need to - unsuspend the other threads, to decrement their `suspended' count - back. */ if (step_over_finished) - unsuspend_all_lwps (event_child); + { + if (!non_stop) + { + /* If we were doing a step-over, all other threads but + the stepping one had been paused in start_step_over, + with their suspend counts incremented. We don't want + to do a full unstop/unpause, because we're in + all-stop mode (so we want threads stopped), but we + still need to unsuspend the other threads, to + decrement their `suspended' count back. */ + unsuspend_all_lwps (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. */ + unstop_all_lwps (1, event_child); + } + } /* Stabilize threads (move out of jump pads). */ - stabilize_threads (); + if (!non_stop) + stabilize_threads (); } else { @@ -2905,6 +2937,20 @@ retry: ourstatus->kind = TARGET_WAITKIND_STOPPED; + /* Now that we've selected our final event LWP, un-adjust its PC if + it was a software breakpoint. */ + if (event_child->stop_reason == LWP_STOPPED_BY_SW_BREAKPOINT) + { + int decr_pc = the_low_target.decr_pc_after_break; + + if (decr_pc != 0) + { + struct regcache *regcache + = get_thread_regcache (current_thread, 1); + (*the_low_target.set_pc) (regcache, event_child->stop_pc + decr_pc); + } + } + if (current_thread->last_resume_kind == resume_stop && WSTOPSIG (w) == SIGSTOP) { @@ -2976,7 +3022,13 @@ linux_wait (ptid_t ptid, if (target_is_async_p ()) async_file_flush (); - event_ptid = linux_wait_1 (ptid, ourstatus, target_options); + do + { + event_ptid = linux_wait_1 (ptid, ourstatus, target_options); + } + while ((target_options & TARGET_WNOHANG) == 0 + && ptid_equal (event_ptid, null_ptid) + && ourstatus->kind == TARGET_WAITKIND_IGNORE); /* If at least one stop was reported, there may be more. A single SIGCHLD can signal more than one child stop. */ @@ -3164,7 +3216,7 @@ stuck_in_jump_pad_callback (struct inferior_list_entry *entry, void *data) return (supports_fast_tracepoints () && agent_loaded_p () && (gdb_breakpoint_here (lwp->stop_pc) - || lwp->stopped_by_watchpoint + || lwp->stop_reason == LWP_STOPPED_BY_WATCHPOINT || thread->last_resume_kind == resume_step) && linux_fast_tracepoint_collecting (lwp, NULL)); } @@ -3183,7 +3235,7 @@ move_out_of_jump_pad_callback (struct inferior_list_entry *entry) /* Allow debugging the jump pad, gdb_collect, etc. */ if (!gdb_breakpoint_here (lwp->stop_pc) - && !lwp->stopped_by_watchpoint + && lwp->stop_reason != LWP_STOPPED_BY_WATCHPOINT && thread->last_resume_kind != resume_step && maybe_move_out_of_jump_pad (lwp, wstat)) { @@ -3407,11 +3459,17 @@ linux_resume_one_lwp (struct lwp_info *lwp, step = 1; } - if (debug_threads && the_low_target.get_pc != NULL) + if (the_low_target.get_pc != NULL) { struct regcache *regcache = get_thread_regcache (current_thread, 1); - CORE_ADDR pc = (*the_low_target.get_pc) (regcache); - debug_printf (" resuming from pc 0x%lx\n", (long) pc); + + lwp->stop_pc = (*the_low_target.get_pc) (regcache); + + if (debug_threads) + { + debug_printf (" %s from pc 0x%lx\n", step ? "step" : "continue", + (long) lwp->stop_pc); + } } /* If we have pending signals, consume one unless we are trying to @@ -3442,7 +3500,7 @@ linux_resume_one_lwp (struct lwp_info *lwp, regcache_invalidate_thread (thread); errno = 0; lwp->stopped = 0; - lwp->stopped_by_watchpoint = 0; + lwp->stop_reason = LWP_STOPPED_BY_NO_REASON; lwp->stepping = step; ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, lwpid_of (thread), (PTRACE_TYPE_ARG3) 0, @@ -3563,7 +3621,7 @@ resume_status_pending_p (struct inferior_list_entry *entry, void *flag_p) if (lwp->resume == NULL) return 0; - if (lwp->status_pending_p) + if (thread_still_has_status_pending_p (thread)) * (int *) flag_p = 1; return 0; @@ -4848,7 +4906,7 @@ linux_stopped_by_watchpoint (void) { struct lwp_info *lwp = get_thread_lwp (current_thread); - return lwp->stopped_by_watchpoint; + return lwp->stop_reason == LWP_STOPPED_BY_WATCHPOINT; } static CORE_ADDR @@ -6017,7 +6075,6 @@ static struct target_ops linux_target_ops = { NULL, linux_pause_all, linux_unpause_all, - linux_cancel_breakpoints, linux_stabilize_threads, linux_install_fast_tracepoint_jump_pad, linux_emit_ops, diff --git a/gdb/gdbserver/linux-low.h b/gdb/gdbserver/linux-low.h index 97b163fbb5d..bea4a377ed8 100644 --- a/gdb/gdbserver/linux-low.h +++ b/gdb/gdbserver/linux-low.h @@ -230,6 +230,24 @@ extern struct linux_target_ops the_low_target; #define get_thread_lwp(thr) ((struct lwp_info *) (inferior_target_data (thr))) #define get_lwp_thread(lwp) ((lwp)->thread) +/* Reasons an LWP last stopped. */ + +enum lwp_stop_reason +{ + /* Either not stopped, or stopped for a reason that doesn't require + special tracking. */ + LWP_STOPPED_BY_NO_REASON, + + /* Stopped by a software breakpoint. */ + LWP_STOPPED_BY_SW_BREAKPOINT, + + /* Stopped by a hardware breakpoint. */ + LWP_STOPPED_BY_HW_BREAKPOINT, + + /* Stopped by a watchpoint. */ + LWP_STOPPED_BY_WATCHPOINT +}; + /* This struct is recorded in the target_data field of struct thread_info. On linux ``all_threads'' is keyed by the LWP ID, which we use as the @@ -269,8 +287,9 @@ 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. */ + /* When stopped is set, this is where the lwp last stopped, with + decr_pc_after_break already accounted for. If the LWP is + running, this is the address at which the lwp was resumed. */ CORE_ADDR stop_pc; /* If this flag is set, STATUS_PENDING is a waitstatus that has not yet @@ -278,9 +297,9 @@ struct lwp_info int status_pending_p; int status_pending; - /* STOPPED_BY_WATCHPOINT is non-zero if this LWP stopped with a data - watchpoint trap. */ - int stopped_by_watchpoint; + /* The reason the LWP last stopped, if we need to track it + (breakpoint, watchpoint, etc.) */ + enum lwp_stop_reason stop_reason; /* On architectures where it is possible to know the data address of a triggered watchpoint, STOPPED_DATA_ADDRESS is non-zero, and diff --git a/gdb/gdbserver/linux-x86-low.c b/gdb/gdbserver/linux-x86-low.c index f0939ce306f..2c3fccca811 100644 --- a/gdb/gdbserver/linux-x86-low.c +++ b/gdb/gdbserver/linux-x86-low.c @@ -805,7 +805,7 @@ x86_linux_prepare_to_resume (struct lwp_info *lwp) lwp->arch_private->debug_registers_changed = 0; } - if (clear_status || lwp->stopped_by_watchpoint) + if (clear_status || lwp->stop_reason == LWP_STOPPED_BY_WATCHPOINT) x86_linux_dr_set (ptid, DR_STATUS, 0); } diff --git a/gdb/gdbserver/mem-break.c b/gdb/gdbserver/mem-break.c index c3114b9fa9b..70fab2e3a73 100644 --- a/gdb/gdbserver/mem-break.c +++ b/gdb/gdbserver/mem-break.c @@ -1610,6 +1610,40 @@ breakpoint_inserted_here (CORE_ADDR addr) return 0; } +/* See mem-break.h. */ + +int +software_breakpoint_inserted_here (CORE_ADDR addr) +{ + struct process_info *proc = current_process (); + struct raw_breakpoint *bp; + + for (bp = proc->raw_breakpoints; bp != NULL; bp = bp->next) + if (bp->raw_type == raw_bkpt_type_sw + && bp->pc == addr + && bp->inserted) + return 1; + + return 0; +} + +/* See mem-break.h. */ + +int +hardware_breakpoint_inserted_here (CORE_ADDR addr) +{ + struct process_info *proc = current_process (); + struct raw_breakpoint *bp; + + for (bp = proc->raw_breakpoints; bp != NULL; bp = bp->next) + if (bp->raw_type == raw_bkpt_type_hw + && bp->pc == addr + && bp->inserted) + return 1; + + return 0; +} + static int validate_inserted_breakpoint (struct raw_breakpoint *bp) { diff --git a/gdb/gdbserver/mem-break.h b/gdb/gdbserver/mem-break.h index 056a256d9b9..8b010c14dfa 100644 --- a/gdb/gdbserver/mem-break.h +++ b/gdb/gdbserver/mem-break.h @@ -90,6 +90,16 @@ int breakpoint_here (CORE_ADDR addr); int breakpoint_inserted_here (CORE_ADDR addr); +/* Returns TRUE if there's any inserted software breakpoint at + ADDR. */ + +int software_breakpoint_inserted_here (CORE_ADDR addr); + +/* Returns TRUE if there's any inserted hardware (code) breakpoint at + ADDR. */ + +int hardware_breakpoint_inserted_here (CORE_ADDR addr); + /* Clear all breakpoint conditions and commands associated with a breakpoint. */ diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h index 9bcf246bde3..bbb056733e2 100644 --- a/gdb/gdbserver/target.h +++ b/gdb/gdbserver/target.h @@ -304,9 +304,6 @@ struct target_ops the pause call. */ void (*unpause_all) (int unfreeze); - /* Cancel all pending breakpoints hits in all threads. */ - void (*cancel_breakpoints) (void); - /* Stabilize all threads. That is, force them out of jump pads. */ void (*stabilize_threads) (void); @@ -453,13 +450,6 @@ int kill_inferior (int); (*the_target->unpause_all) (unfreeze); \ } while (0) -#define cancel_breakpoints() \ - do \ - { \ - if (the_target->cancel_breakpoints) \ - (*the_target->cancel_breakpoints) (); \ - } while (0) - #define stabilize_threads() \ do \ { \ diff --git a/gdb/gdbserver/tracepoint.c b/gdb/gdbserver/tracepoint.c index b5ea56b35ab..82d6ce5d51e 100644 --- a/gdb/gdbserver/tracepoint.c +++ b/gdb/gdbserver/tracepoint.c @@ -2443,7 +2443,6 @@ clear_installed_tracepoints (void) struct tracepoint *prev_stpoint; pause_all (1); - cancel_breakpoints (); prev_stpoint = NULL; @@ -3433,9 +3432,6 @@ stop_tracing (void) We can't now, since we may be getting here due to the inferior agent calling us. */ pause_all (1); - /* Since we're removing breakpoints, cancel breakpoint hits, - possibly related to the breakpoints we're about to delete. */ - cancel_breakpoints (); /* Stop logging. Tracepoints can still be hit, but they will not be recorded. */ @@ -6538,7 +6534,6 @@ upload_fast_traceframes (void) trace_debug ("Done uploading traceframes [%d]\n", curr_tbctrl_idx); pause_all (1); - cancel_breakpoints (); delete_breakpoint (about_to_request_buffer_space_bkpt); about_to_request_buffer_space_bkpt = NULL; -- 2.30.2