diff --git a/gdb/infrun.c b/gdb/infrun.c
index 943ea88538c..62b306ff347 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1956,6 +1956,22 @@ displaced_step_prepare (thread_info *thread)
   return status;
 }
 
+/* True if any thread of TARGET that matches RESUME_PTID requires
+   target_thread_events enabled.  This assumes TARGET does not support
+   target thread options.  */
+
+static bool
+any_thread_needs_target_thread_events (process_stratum_target *target,
+				       ptid_t resume_ptid)
+{
+  for (thread_info *tp : all_non_exited_threads (target, resume_ptid))
+    if (displaced_step_in_progress_thread (tp)
+	|| schedlock_applies (tp)
+	|| tp->thread_fsm () != nullptr)
+      return true;
+  return false;
+}
+
 /* Maybe disable thread-{cloned,created,exited} event reporting after
    a step-over (either in-line or displaced) finishes.  */
 
@@ -1979,9 +1995,10 @@ update_thread_events_after_step_over (thread_info *event_thread,
   else
     {
       /* We can only control the target-wide target_thread_events
-	 setting.  Disable it, but only if other threads don't need it
-	 enabled.  */
-      if (!displaced_step_in_progress_any_thread ())
+	 setting.  Disable it, but only if other threads in the target
+	 don't need it enabled.  */
+      process_stratum_target *target = event_thread->inf->process_target ();
+      if (!any_thread_needs_target_thread_events (target, minus_one_ptid))
 	target_thread_events (false);
     }
 }
@@ -2559,12 +2576,25 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
       else
 	target_thread_events (true);
     }
+  else if (tp->thread_fsm () != nullptr)
+    {
+      gdb_thread_options options = GDB_THREAD_OPTION_EXIT;
+      if (target_supports_set_thread_options (options))
+	tp->set_thread_options (options);
+      else
+	target_thread_events (true);
+    }
   else
     {
       if (target_supports_set_thread_options (0))
 	tp->set_thread_options (0);
-      else if (!displaced_step_in_progress_any_thread ())
-	target_thread_events (false);
+      else
+	{
+	  process_stratum_target *resume_target = tp->inf->process_target ();
+	  if (!any_thread_needs_target_thread_events (resume_target,
+						      resume_ptid))
+	    target_thread_events (false);
+	}
     }
 
   /* If we're resuming more than one thread simultaneously, then any
@@ -5842,6 +5872,13 @@ handle_thread_exited (execution_control_state *ecs)
   ecs->event_thread->stepping_over_breakpoint = 0;
   ecs->event_thread->stepping_over_watchpoint = 0;
 
+  /* If the thread had an FSM, then abort the command.  But only after
+     finishing the step over, as in non-stop mode, aborting this
+     thread's command should not interfere with other threads.  We
+     must check this before finish_step over, however, which may
+     update the thread list and delete the event thread.  */
+  bool abort_cmd = (ecs->event_thread->thread_fsm () != nullptr);
+
   /* Maybe the thread was doing a step-over, if so release
      resources and start any further pending step-overs.
 
@@ -5855,6 +5892,13 @@ handle_thread_exited (execution_control_state *ecs)
      the event thread has exited.  */
   gdb_assert (ret == 0);
 
+  if (abort_cmd)
+    {
+      delete_thread (ecs->event_thread);
+      ecs->event_thread = nullptr;
+      return false;
+    }
+
   /* If finish_step_over started a new in-line step-over, don't
      try to restart anything else.  */
   if (step_over_info_valid_p ())
@@ -9287,7 +9331,8 @@ normal_stop ()
       if (inferior_ptid != null_ptid)
 	finish_ptid = ptid_t (inferior_ptid.pid ());
     }
-  else if (last.kind () != TARGET_WAITKIND_NO_RESUMED)
+  else if (last.kind () != TARGET_WAITKIND_NO_RESUMED
+	   && last.kind () != TARGET_WAITKIND_THREAD_EXITED)
     finish_ptid = inferior_ptid;
 
   gdb::optional<scoped_finish_thread_state> maybe_finish_thread_state;
@@ -9330,7 +9375,8 @@ normal_stop ()
     {
       if ((last.kind () != TARGET_WAITKIND_SIGNALLED
 	   && last.kind () != TARGET_WAITKIND_EXITED
-	   && last.kind () != TARGET_WAITKIND_NO_RESUMED)
+	   && last.kind () != TARGET_WAITKIND_NO_RESUMED
+	   && last.kind () != TARGET_WAITKIND_THREAD_EXITED)
 	  && target_has_execution ()
 	  && previous_thread != inferior_thread ())
 	{
@@ -9346,7 +9392,8 @@ normal_stop ()
       update_previous_thread ();
     }
 
-  if (last.kind () == TARGET_WAITKIND_NO_RESUMED)
+  if (last.kind () == TARGET_WAITKIND_NO_RESUMED
+      || last.kind () == TARGET_WAITKIND_THREAD_EXITED)
     {
       stop_print_frame = false;
 
@@ -9354,7 +9401,12 @@ normal_stop ()
 	if (current_ui->prompt_state == PROMPT_BLOCKED)
 	  {
 	    target_terminal::ours_for_output ();
-	    gdb_printf (_("No unwaited-for children left.\n"));
+	    if (last.kind () == TARGET_WAITKIND_NO_RESUMED)
+	      gdb_printf (_("No unwaited-for children left.\n"));
+	    else if (last.kind () == TARGET_WAITKIND_THREAD_EXITED)
+	      gdb_printf (_("Command aborted, thread exited.\n"));
+	    else
+	      gdb_assert_not_reached ("unhandled");
 	  }
     }
 
@@ -9437,7 +9489,8 @@ normal_stop ()
     {
       if (last.kind () != TARGET_WAITKIND_SIGNALLED
 	  && last.kind () != TARGET_WAITKIND_EXITED
-	  && last.kind () != TARGET_WAITKIND_NO_RESUMED)
+	  && last.kind () != TARGET_WAITKIND_NO_RESUMED
+	  && last.kind () != TARGET_WAITKIND_THREAD_EXITED)
 	/* Delete the breakpoint we stopped at, if it wants to be deleted.
 	   Delete any breakpoint that is to be deleted at the next stop.  */
 	breakpoint_auto_delete (inferior_thread ()->control.stop_bpstat);
diff --git a/gdb/testsuite/gdb.threads/step-over-thread-exit.exp b/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
index ed8534cf518..615bd838763 100644
--- a/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
+++ b/gdb/testsuite/gdb.threads/step-over-thread-exit.exp
@@ -29,7 +29,7 @@ if { [build_executable "failed to prepare" $testfile \
 # NS_STOP_ALL is only used if testing "set non-stop on", and indicates
 # whether to have GDB explicitly stop all threads before continuing to
 # thread exit.
-proc test {displaced-stepping non-stop target-non-stop schedlock ns_stop_all} {
+proc test {displaced-stepping non-stop target-non-stop schedlock cmd ns_stop_all} {
     if {${non-stop} == "off" && $ns_stop_all} {
 	error "invalid arguments"
     }
@@ -72,31 +72,58 @@ proc test {displaced-stepping non-stop target-non-stop schedlock ns_stop_all} {
 
 	gdb_test_no_output "set scheduler-locking ${schedlock}"
 
-	gdb_test "continue" \
-	    "No unwaited-for children left." \
-	    "continue stops when thread exits"
+	if {$cmd == "continue"} {
+	    gdb_test "continue" \
+		"No unwaited-for children left." \
+		"continue stops when thread exits"
+	} else {
+	    gdb_test_multiple $cmd "command aborts when thread exits" {
+		-re "Command aborted, thread exited\\.\r\n$::gdb_prompt " {
+		    pass $gdb_test_name
+		}
+	    }
+	}
     } else {
 	gdb_test_no_output "set scheduler-locking ${schedlock}"
 
-	for { set i 0 } { $i < 100 } { incr i } {
-	    with_test_prefix "iter $i" {
-		set ok 0
-		set thread "<unknown>"
-		gdb_test_multiple "continue" "" {
-		    -re -wrap "Thread ($::decimal) .*hit Breakpoint $::decimal.* my_exit_syscall .*" {
-			set thread $expect_out(1,string)
-			set ok 1
-		    }
+	if {$cmd != "continue"} {
+	    set thread "<unknown>"
+	    gdb_test_multiple "continue" "" {
+		-re -wrap "Thread ($::decimal) .*hit Breakpoint $::decimal.* my_exit_syscall .*" {
+		    set thread $expect_out(1,string)
 		}
-		if {!${ok}} {
-		    # Exit if there's a failure to avoid lengthy
-		    # timeouts.
-		    break
+	    }
+	    if {${non-stop}} {
+		gdb_test -nopass "thread $thread" "Switching to thread .*" \
+		    "switch to event thread"
+	    }
+
+	    gdb_test_multiple $cmd "command aborts when thread exits" {
+		-re "Command aborted, thread exited\\.\r\n$::gdb_prompt " {
+		    pass $gdb_test_name
 		}
+	    }
+	} else {
+	    for { set i 0 } { $i < 100 } { incr i } {
+		with_test_prefix "iter $i" {
+		    set ok 0
+		    set thread "<unknown>"
+		    gdb_test_multiple "continue" "" {
+			-re -wrap "Thread ($::decimal) .*hit Breakpoint $::decimal.* my_exit_syscall .*" {
+			    set thread $expect_out(1,string)
+			    set ok 1
+			}
+		    }
+		    if {!${ok}} {
+			# Exit if there's a failure to avoid lengthy
+			# timeouts.
+			break
+		    }
 
-		if {${non-stop}} {
-		    gdb_test "thread $thread" "Switching to thread .*" \
-			"switch to event thread"
+		    if {${non-stop}} {
+			gdb_test -nopass "thread $thread" "Switching to thread .*" \
+			    "switch to event thread"
+		    }
 		}
 	    }
 	}
@@ -112,13 +139,15 @@ foreach_with_prefix displaced-stepping {off auto} {
 	    }
 
 	    foreach_with_prefix schedlock {off on} {
-		if {${non-stop} == "on"} {
-		    foreach_with_prefix ns_stop_all {0 1} {
-			test ${displaced-stepping} ${non-stop} ${target-non-stop} \
-			    ${schedlock} ${ns_stop_all}
+		foreach_with_prefix cmd {"next" "continue"} {
+		    if {${non-stop} == "on"} {
+			foreach_with_prefix ns_stop_all {0 1} {
+			    test ${displaced-stepping} ${non-stop} ${target-non-stop} \
+				${schedlock} ${cmd} ${ns_stop_all}
+			}
+		    } else {
+			test ${displaced-stepping} ${non-stop} ${target-non-stop} ${schedlock} ${cmd} 0
 		    }
-		} else {
-		    test ${displaced-stepping} ${non-stop} ${target-non-stop} ${schedlock} 0
 		}
 	    }
 	}
