diff options
-rw-r--r-- | gdb/displaced-stepping.c | 7 | ||||
-rw-r--r-- | gdb/gdbarch-components.py | 4 | ||||
-rw-r--r-- | gdb/gdbarch-gen.h | 6 | ||||
-rw-r--r-- | gdb/infrun.c | 163 | ||||
-rw-r--r-- | gdb/thread.c | 2 |
5 files changed, 168 insertions, 14 deletions
diff --git a/gdb/displaced-stepping.c b/gdb/displaced-stepping.c index 83080cf6bdd..96d4732b7dc 100644 --- a/gdb/displaced-stepping.c +++ b/gdb/displaced-stepping.c @@ -236,6 +236,13 @@ displaced_step_buffers::finish (gdbarch *arch, thread_info *thread, thread->ptid.to_string ().c_str (), paddress (arch, buffer->addr)); + /* If the thread exited while stepping, we are done. The code above + made the buffer available again, and we restored the bytes in the + buffer. We don't want to run the fixup: since the thread is now + dead there's nothing to adjust. */ + if (status.kind () == TARGET_WAITKIND_THREAD_EXITED) + return DISPLACED_STEP_FINISH_STATUS_OK; + regcache *rc = get_thread_regcache (thread); bool instruction_executed_successfully diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py index 5d391aa7dc0..d4c6c3bc94e 100644 --- a/gdb/gdbarch-components.py +++ b/gdb/gdbarch-components.py @@ -1740,6 +1740,10 @@ Throw an exception if any unexpected error happens. Method( comment=""" Clean up after a displaced step of THREAD. + +It is possible for the displaced-stepped instruction to have caused +the thread to exit. The implementation can detect this case by +checking if WS.kind is TARGET_WAITKIND_THREAD_EXITED. """, type="displaced_step_finish_status", name="displaced_step_finish", diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index 221dd84008c..b0643616561 100644 --- a/gdb/gdbarch-gen.h +++ b/gdb/gdbarch-gen.h @@ -1030,7 +1030,11 @@ typedef displaced_step_prepare_status (gdbarch_displaced_step_prepare_ftype) (st extern displaced_step_prepare_status gdbarch_displaced_step_prepare (struct gdbarch *gdbarch, thread_info *thread, CORE_ADDR &displaced_pc); extern void set_gdbarch_displaced_step_prepare (struct gdbarch *gdbarch, gdbarch_displaced_step_prepare_ftype *displaced_step_prepare); -/* Clean up after a displaced step of THREAD. */ +/* Clean up after a displaced step of THREAD. + + It is possible for the displaced-stepped instruction to have caused + the thread to exit. The implementation can detect this case by + checking if WS.kind is TARGET_WAITKIND_THREAD_EXITED. */ typedef displaced_step_finish_status (gdbarch_displaced_step_finish_ftype) (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws); extern displaced_step_finish_status gdbarch_displaced_step_finish (struct gdbarch *gdbarch, thread_info *thread, const target_waitstatus &ws); diff --git a/gdb/infrun.c b/gdb/infrun.c index b20158f4a1c..b29a907b0c7 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -1820,13 +1820,15 @@ displaced_step_prepare (thread_info *thread) a step-over (either in-line or displaced) finishes. */ static void -update_thread_events_after_step_over (thread_info *event_thread) +update_thread_events_after_step_over (thread_info *event_thread, + const target_waitstatus &event_status) { if (target_supports_set_thread_options (0)) { /* We can control per-thread options. Disable events for the - event thread. */ - event_thread->set_thread_options (0); + event thread, unless the thread is gone. */ + if (event_status.kind () != TARGET_WAITKIND_THREAD_EXITED) + event_thread->set_thread_options (0); } else { @@ -1882,7 +1884,7 @@ displaced_step_finish (thread_info *event_thread, if (!displaced->in_progress ()) return DISPLACED_STEP_FINISH_STATUS_OK; - update_thread_events_after_step_over (event_thread); + update_thread_events_after_step_over (event_thread, event_status); gdb_assert (event_thread->inf->displaced_step_state.in_progress_count > 0); event_thread->inf->displaced_step_state.in_progress_count--; @@ -3994,6 +3996,7 @@ struct wait_one_event }; static bool handle_one (const wait_one_event &event); +static int finish_step_over (struct execution_control_state *ecs); /* Prepare and stabilize the inferior for detaching it. E.g., detaching while a thread is displaced stepping is a recipe for @@ -5091,6 +5094,16 @@ handle_one (const wait_one_event &event) event.ws); save_waitstatus (t, event.ws); t->stop_requested = false; + + if (event.ws.kind () == TARGET_WAITKIND_THREAD_EXITED) + { + if (displaced_step_finish (t, event.ws) + != DISPLACED_STEP_FINISH_STATUS_OK) + { + gdb_assert_not_reached ("displaced_step_finish on " + "exited thread failed"); + } + } } } else @@ -5300,7 +5313,9 @@ stop_all_threads (const char *reason, inferior *inf) } } -/* Handle a TARGET_WAITKIND_NO_RESUMED event. */ +/* Handle a TARGET_WAITKIND_NO_RESUMED event. Return true if we + handled the event and should continue waiting. Return false if we + should stop and report the event to the user. */ static bool handle_no_resumed (struct execution_control_state *ecs) @@ -5428,6 +5443,117 @@ handle_no_resumed (struct execution_control_state *ecs) return false; } +/* Handle a TARGET_WAITKIND_THREAD_EXITED event. Return true if we + handled the event and should continue waiting. Return false if we + should stop and report the event to the user. */ + +static bool +handle_thread_exited (execution_control_state *ecs) +{ + context_switch (ecs); + + /* Clear these so we don't re-start the thread stepping over a + breakpoint/watchpoint. */ + ecs->event_thread->stepping_over_breakpoint = 0; + ecs->event_thread->stepping_over_watchpoint = 0; + + /* Maybe the thread was doing a step-over, if so release + resources and start any further pending step-overs. + + If we are on a non-stop target and the thread was doing an + in-line step, this also restarts the other threads. */ + int ret = finish_step_over (ecs); + + /* finish_step_over returns true if it moves ecs' wait status + back into the thread, so that we go handle another pending + event before this one. But we know it never does that if + the event thread has exited. */ + gdb_assert (ret == 0); + + /* If finish_step_over started a new in-line step-over, don't + try to restart anything else. */ + if (step_over_info_valid_p ()) + { + delete_thread (ecs->event_thread); + return true; + } + + /* Maybe we are on an all-stop target and we got this event + while doing a step-like command on another thread. If so, + go back to doing that. If this thread was stepping, + switch_back_to_stepped_thread will consider that the thread + was interrupted mid-step and will try keep stepping it. We + don't want that, the thread is gone. So clear the proceed + status so it doesn't do that. */ + clear_proceed_status_thread (ecs->event_thread); + if (switch_back_to_stepped_thread (ecs)) + { + delete_thread (ecs->event_thread); + return true; + } + + inferior *inf = ecs->event_thread->inf; + bool slock_applies = schedlock_applies (ecs->event_thread); + + delete_thread (ecs->event_thread); + ecs->event_thread = nullptr; + + auto handle_as_no_resumed = [ecs] () + { + ecs->ws.set_no_resumed (); + ecs->event_thread = nullptr; + ecs->ptid = minus_one_ptid; + return handle_no_resumed (ecs); + }; + + /* If we are on an all-stop target, the target has stopped all + threads to report the event. We don't actually want to + stop, so restart the threads. */ + if (!target_is_non_stop_p ()) + { + if (slock_applies) + { + /* Since the target is !non-stop, then everything is stopped + at this point, and we can't assume we'll get further + events until we resume the target again. Handle this + event like if it were a TARGET_WAITKIND_NO_RESUMED. Note + this refreshes the thread list and checks whether there + are other resumed threads before deciding whether to + print "no-unwaited-for left". This is important because + the user could have done: + + (gdb) set scheduler-locking on + (gdb) thread 1 + (gdb) c& + (gdb) thread 2 + (gdb) c + + ... and only one of the threads exited. */ + return handle_as_no_resumed (); + } + else + { + /* Switch to the first non-exited thread we can find, and + resume. */ + auto range = inf->non_exited_threads (); + if (range.begin () == range.end ()) + { + /* Looks like the target reported a + TARGET_WAITKIND_THREAD_EXITED for its last known + thread. */ + return handle_as_no_resumed (); + } + thread_info *non_exited_thread = *range.begin (); + switch_to_thread (non_exited_thread); + insert_breakpoints (); + resume (GDB_SIGNAL_0); + } + } + + prepare_to_wait (ecs); + return true; +} + /* Given an execution control state that has been freshly filled in by an event from the inferior, figure out what it means and take appropriate action. @@ -5466,12 +5592,6 @@ handle_inferior_event (struct execution_control_state *ecs) return; } - if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED) - { - prepare_to_wait (ecs); - return; - } - if (ecs->ws.kind () == TARGET_WAITKIND_NO_RESUMED && handle_no_resumed (ecs)) return; @@ -5486,7 +5606,6 @@ handle_inferior_event (struct execution_control_state *ecs) { /* No unwaited-for children left. IOW, all resumed children have exited. */ - stop_print_frame = false; stop_waiting (ecs); return; } @@ -5636,6 +5755,15 @@ handle_inferior_event (struct execution_control_state *ecs) keep_going (ecs); return; + case TARGET_WAITKIND_THREAD_EXITED: + if (handle_thread_exited (ecs)) + return; + /* Need to re-record the last target status because the waitkind + may have changed to TARGET_WAITKIND_NO_RESUMED. */ + set_last_target_status (ecs->target, ecs->ptid, ecs->ws); + stop_waiting (ecs); + break; + case TARGET_WAITKIND_EXITED: case TARGET_WAITKIND_SIGNALLED: { @@ -6085,7 +6213,7 @@ finish_step_over (struct execution_control_state *ecs) back an event. */ gdb_assert (ecs->event_thread->control.trap_expected); - update_thread_events_after_step_over (ecs->event_thread); + update_thread_events_after_step_over (ecs->event_thread, ecs->ws); clear_step_over_info (); } @@ -6131,6 +6259,13 @@ finish_step_over (struct execution_control_state *ecs) if (ecs->event_thread->stepping_over_watchpoint) return 0; + /* The code below is meant to avoid one thread hogging the event + loop by doing constant in-line step overs. If the stepping + thread exited, there's no risk for this to happen, so we can + safely let our caller process the event immediately. */ + if (ecs->ws.kind () == TARGET_WAITKIND_THREAD_EXITED) + return 0; + pending = iterate_over_threads (resumed_thread_with_pending_status, NULL); if (pending != NULL) @@ -8772,6 +8907,8 @@ normal_stop (void) if (last.kind () == TARGET_WAITKIND_NO_RESUMED) { + stop_print_frame = false; + SWITCH_THRU_ALL_UIS () if (current_ui->prompt_state == PROMPT_BLOCKED) { diff --git a/gdb/thread.c b/gdb/thread.c index 6ea05f70a41..a83db6b07fd 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -401,6 +401,8 @@ thread_info::clear_pending_waitstatus () void thread_info::set_thread_options (gdb_thread_options thread_options) { + gdb_assert (this->state != THREAD_EXITED && !this->executing ()); + if (m_thread_options == thread_options) return; |