Make handle_no_resumed transfer terminal
authorPedro Alves <pedro@palves.net>
Sat, 4 Jul 2020 19:51:36 +0000 (20:51 +0100)
committerPedro Alves <palves@redhat.com>
Fri, 10 Jul 2020 22:50:39 +0000 (23:50 +0100)
Let's consider the same use case as in the previous commit:

Say you have two inferiors 1 and 2, each connected to a different
target, A and B.

Now say you set inferior 2 running, with "continue &".

Now you select a thread of inferior 1, say thread 1.2, and continue in
the foreground.  All other threads of inferior 1 are left stopped.
Thread 1.2 exits, and thus target A has no other resumed thread, so it
reports TARGET_WAITKIND_NO_RESUMED.

At this point, because the threads of inferior 2 are still executing
the TARGET_WAITKIND_NO_RESUMED event is ignored.

Now, the user types Ctrl-C.  Because GDB had previously put inferior 1
in the foreground, the kernel sends the SIGINT to that inferior.
However, no thread in that inferior is executing right now, so ptrace
never intercepts the SIGINT -- it is never dequeued by any thread.
The result is that GDB's CLI is stuck.  There's no way to get back the
prompt (unless inferior 2 happens to report some event).

The fix in this commit is to make handle_no_resumed give the terminal
to some other inferior that still has threads executing so that a
subsequent Ctrl-C reaches that target first (and then GDB intercepts
the SIGINT).  This is a bit hacky, but seems like the best we can do
with the current design.

I think that putting all native inferiors in their own session would
help fixing this in a clean way, since with that a Ctrl-C on GDB's
terminal will _always_ reach GDB first, and then GDB can decide how to
pause the inferior.  But that's a much larger change.

The testcase added by the following patch needs this fix.

gdb/ChangeLog:

PR gdb/26199
* infrun.c (handle_no_resumed): Transfer terminal to inferior with
executing threads.

gdb/ChangeLog
gdb/infrun.c

index 3fb80abb23ef6c02f39c31414102be470924395c..e35b276309d54caca3759b14bfe29632ae0e6417 100644 (file)
@@ -1,3 +1,9 @@
+2020-07-10  Pedro Alves  <pedro@palves.net>
+
+       PR gdb/26199
+       * infrun.c (handle_no_resumed): Transfer terminal to inferior with
+       executing threads.
+
 2020-07-10  Pedro Alves  <pedro@palves.net>
 
        PR gdb/26199
index 0f2f45a4d2d7773fcc2566a2ad537c2d97a3d025..158b1990694be41093b2f862389e68f6f0d3c6df 100644 (file)
@@ -5071,20 +5071,52 @@ handle_no_resumed (struct execution_control_state *ecs)
      the synchronous command and show "no unwaited-for " to the
      user.  */
 
-  {
-    scoped_restore_current_thread restore_thread;
+  inferior *curr_inf = current_inferior ();
 
-    for (auto *target : all_non_exited_process_targets ())
-      {
-       switch_to_target_no_thread (target);
-       update_thread_list ();
-      }
-  }
+  scoped_restore_current_thread restore_thread;
+
+  for (auto *target : all_non_exited_process_targets ())
+    {
+      switch_to_target_no_thread (target);
+      update_thread_list ();
+    }
+
+  /* If:
+
+       - the current target has no thread executing, and
+       - the current inferior is native, and
+       - the current inferior is the one which has the terminal, and
+       - we did nothing,
+
+     then a Ctrl-C from this point on would remain stuck in the
+     kernel, until a thread resumes and dequeues it.  That would
+     result in the GDB CLI not reacting to Ctrl-C, not able to
+     interrupt the program.  To address this, if the current inferior
+     no longer has any thread executing, we give the terminal to some
+     other inferior that has at least one thread executing.  */
+  bool swap_terminal = true;
+
+  /* Whether to ignore this TARGET_WAITKIND_NO_RESUMED event, or
+     whether to report it to the user.  */
+  bool ignore_event = false;
 
   for (thread_info *thread : all_non_exited_threads ())
     {
-      if (thread->executing
-         || thread->suspend.waitstatus_pending_p)
+      if (swap_terminal && thread->executing)
+       {
+         if (thread->inf != curr_inf)
+           {
+             target_terminal::ours ();
+
+             switch_to_thread (thread);
+             target_terminal::inferior ();
+           }
+         swap_terminal = false;
+       }
+
+      if (!ignore_event
+         && (thread->executing
+             || thread->suspend.waitstatus_pending_p))
        {
          /* Either there were no unwaited-for children left in the
             target at some point, but there are now, or some target
@@ -5094,9 +5126,19 @@ handle_no_resumed (struct execution_control_state *ecs)
            fprintf_unfiltered (gdb_stdlog,
                                "infrun: TARGET_WAITKIND_NO_RESUMED "
                                "(ignoring: found resumed)\n");
-         prepare_to_wait (ecs);
-         return 1;
+
+         ignore_event = true;
        }
+
+      if (ignore_event && !swap_terminal)
+       break;
+    }
+
+  if (ignore_event)
+    {
+      switch_to_inferior_no_thread (curr_inf);
+      prepare_to_wait (ecs);
+      return 1;
     }
 
   /* Go ahead and report the event.  */