summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/displaced-stepping.c7
-rw-r--r--gdb/gdbarch-components.py4
-rw-r--r--gdb/gdbarch-gen.h6
-rw-r--r--gdb/infrun.c163
-rw-r--r--gdb/thread.c2
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;