[v2,3/6] Add new vDefaultInferiorFd packet

Message ID 20240119115659.491195-5-ahajkova@redhat.com
State New
Headers
Series Add vDefaultInferiorFd feature |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Testing passed

Commit Message

Alexandra Hájková Jan. 19, 2024, 11:56 a.m. UTC
  Add a new DefaultInferiorFd feature and the corresponding packet.
This feature allows GDB to send, to GDBserver, the file descriptor
numbers of the terminal to which GDB is connected. The inferior is
then started connected to the same terminal as GDB. This allows the
inferior run by local GDBserver to read from GDB's STDIN and write
its output to GDB's STOUT/ERR the same way as native target.
---
 gdb/doc/gdb.texinfo    |  42 +++++++++++++
 gdb/remote.c           |  28 +++++++++
 gdbserver/linux-low.cc |   6 ++
 gdbserver/linux-low.h  |   2 +
 gdbserver/server.cc    | 135 ++++++++++++++++++++++++++++++++++++++++-
 gdbserver/server.h     |  12 ++++
 gdbserver/target.cc    |   6 ++
 gdbserver/target.h     |   6 ++
 8 files changed, 234 insertions(+), 3 deletions(-)
  

Comments

Eli Zaretskii Jan. 19, 2024, 12:04 p.m. UTC | #1
> From: Alexandra Hájková <ahajkova@redhat.com>
> Date: Fri, 19 Jan 2024 12:56:27 +0100
> 
> Add a new DefaultInferiorFd feature and the corresponding packet.
> This feature allows GDB to send, to GDBserver, the file descriptor
> numbers of the terminal to which GDB is connected. The inferior is
> then started connected to the same terminal as GDB. This allows the
> inferior run by local GDBserver to read from GDB's STDIN and write
> its output to GDB's STOUT/ERR the same way as native target.
> ---
>  gdb/doc/gdb.texinfo    |  42 +++++++++++++
>  gdb/remote.c           |  28 +++++++++
>  gdbserver/linux-low.cc |   6 ++
>  gdbserver/linux-low.h  |   2 +
>  gdbserver/server.cc    | 135 ++++++++++++++++++++++++++++++++++++++++-
>  gdbserver/server.h     |  12 ++++
>  gdbserver/target.cc    |   6 ++
>  gdbserver/target.h     |   6 ++
>  8 files changed, 234 insertions(+), 3 deletions(-)

The documentation part of this is okay, thanks.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  

Patch

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index e98c15242bc..8ea8d96f73c 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -23251,6 +23251,18 @@  If you specify the program to debug on the command line, then the
 resume using commands like @kbd{step} and @kbd{continue} as with
 @code{target remote} mode.
 
+When GDBserver is run locally using stdio, for example, with
+@w{@kbd{target remote | gdbserver - @var{inferior}}}, the inferior's
+@code{stdin} is closed, which means the inferior can't read any
+input.
+
+If GDBserver supports the default inferior file descriptors feature
+(@pxref{default inferior file descriptors}), it will start the
+inferior connected to the same terminal as @value{GDBN}, which
+enables the inferior to read from @code{stdin} and write to
+@code{stdout} and @code{stderr}, in the same way native targets do.
+
+
 @anchor{Attaching in Types of Remote Connections}
 @item Attaching
 @strong{With target remote mode:} The @value{GDBN} command @code{attach} is
@@ -43114,6 +43126,36 @@  for success (@pxref{Stop Reply Packets})
 @cindex @samp{vStopped} packet
 @xref{Notification Packets}.
 
+@anchor{default inferior file descriptors}
+@cindex @samp{vDefaultInferiorFd} packet
+@item vDefaultInferiorFd;@var{fd0};@var{fd1};@var{fd2}
+@itemx vDefaultInferiorFd
+vDefaultInferiorFd packet contains the file descriptors of the same @code{stdin}/
+@code{stdout}/@code{stderr} @value{GDBN} is connected to.  If GDBserver is run
+locally, it will accept the packet and will use the file descriptors sent by @value{GDBN}
+to start the inferior connected to the same @code{stdin}/@code{stdout}/@code{stderr}
+@value{GDBN} is connected to.  This allows the inferior run under the local GDBserver
+to read from the @code{stdin} and write to @code{stdout}/@code{stderr}.
+This makes a remote target (which is run locally) to behave similarly to the native target.
+
+When this feature it not used, inferior is started with its @code{stdin} closed and its
+@code{stdout} is redirected to @code{stderr} @value{GDBN} is connected to.
+
+This packet is not probed by default; the remote stub must request it,
+by supplying an appropriate @samp{qSupported} response (@pxref{qSupported}).
+
+This packet is also available in extended mode (@pxref{extended mode}).
+This feature does not support @code{set inferior-tty} command.
+At the time of writing this feature only works on Linux.
+  
+Reply:
+@table @samp
+@item OK
+for success
+@item E.errtext
+for an error
+@end table
+
 @item X @var{addr},@var{length}:@var{XX@dots{}}
 @anchor{X packet}
 @cindex @samp{X} packet
diff --git a/gdb/remote.c b/gdb/remote.c
index 72f14e28f54..d90e071f4fb 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -305,6 +305,10 @@  enum {
      packets and the tag violation stop replies.  */
   PACKET_memory_tagging_feature,
 
+  /* Support for connecting inferior to the same terminal as
+     GDB when running GDBserver locally.  */
+  PACKET_vDefaultInferiorFd,
+
   PACKET_MAX
 };
 
@@ -726,6 +730,11 @@  struct remote_features
   bool remote_memory_tagging_p () const
   { return packet_support (PACKET_memory_tagging_feature) == PACKET_ENABLE; }
 
+  /* Returns true if connecting inferior to the same terminal as GDB is
+     supported, false otherwise.  */
+  bool remote_fd_switch_p () const
+  { return packet_support (PACKET_vDefaultInferiorFd) == PACKET_ENABLE; }
+
   /* Reset all packets back to "unknown support".  Called when opening a
      new connection to a remote target.  */
   void reset_all_packet_configs_support ();
@@ -5016,6 +5025,18 @@  remote_target::start_remote_1 (int from_tty, int extended_p)
      which later probes to skip.  */
   remote_query_supported ();
 
+  if (m_features.packet_support (PACKET_vDefaultInferiorFd) != PACKET_DISABLE
+      && rs->remote_desc->fds[0] != -1 && rs->remote_desc->fds[1] != -1
+      && rs->remote_desc->fds[2] != -1)
+  {
+      xsnprintf (rs->buf.data(), rs->buf.size (), "vDefaultInferiorFd;%d;%d;%d",
+		 rs->remote_desc->fds[0], rs->remote_desc->fds[1],
+		 rs->remote_desc->fds[2]);
+
+      putpkt (rs->buf);
+      getpkt (&rs->buf, 0);
+  }
+
   /* Check vCont support and set the remote state's vCont_action_support
      attribute.  */
   remote_vcont_probe ();
@@ -5723,6 +5744,7 @@  static const struct protocol_feature remote_protocol_features[] = {
   { "no-resumed", PACKET_DISABLE, remote_supported_packet, PACKET_no_resumed },
   { "memory-tagging", PACKET_DISABLE, remote_supported_packet,
     PACKET_memory_tagging_feature },
+  { "vDefaultInferiorFd", PACKET_DISABLE, remote_supported_packet, PACKET_vDefaultInferiorFd },
 };
 
 static char *remote_support_xml;
@@ -5834,6 +5856,10 @@  remote_target::remote_query_supported ()
 	  != AUTO_BOOLEAN_FALSE)
 	remote_query_supported_append (&q, "memory-tagging+");
 
+      if (m_features.packet_set_cmd_state (PACKET_vDefaultInferiorFd)
+	  != AUTO_BOOLEAN_FALSE)
+	remote_query_supported_append (&q, "vDefaultInferiorFd+");
+
       /* Keep this one last to work around a gdbserver <= 7.10 bug in
 	 the qSupported:xmlRegisters=i386 handling.  */
       if (remote_support_xml != NULL
@@ -15990,6 +16016,8 @@  Show the maximum size of the address (in bits) in a memory packet."), NULL,
   add_packet_config_cmd (PACKET_memory_tagging_feature,
 			 "memory-tagging-feature", "memory-tagging-feature", 0);
 
+  add_packet_config_cmd (PACKET_vDefaultInferiorFd, "vDefaultInferiorFd", "vDefaultInferiorFd", 0);
+
   /* Assert that we've registered "set remote foo-packet" commands
      for all packet configs.  */
   {
diff --git a/gdbserver/linux-low.cc b/gdbserver/linux-low.cc
index 444eebc6bbe..dc3d2629fdb 100644
--- a/gdbserver/linux-low.cc
+++ b/gdbserver/linux-low.cc
@@ -6199,6 +6199,12 @@  linux_process_target::low_supports_catch_syscall ()
   return false;
 }
 
+bool
+linux_process_target::supports_default_fds ()
+{
+  return true;
+}
+
 CORE_ADDR
 linux_process_target::read_pc (regcache *regcache)
 {
diff --git a/gdbserver/linux-low.h b/gdbserver/linux-low.h
index d34d2738238..dfbd079e223 100644
--- a/gdbserver/linux-low.h
+++ b/gdbserver/linux-low.h
@@ -322,6 +322,8 @@  class linux_process_target : public process_stratum_target
 
   bool supports_catch_syscall () override;
 
+  bool supports_default_fds () override;
+
   /* Return the information to access registers.  This has public
      visibility because proc-service uses it.  */
   virtual const regs_info *get_regs_info () = 0;
diff --git a/gdbserver/server.cc b/gdbserver/server.cc
index 74c7763d777..f9ce02a8594 100644
--- a/gdbserver/server.cc
+++ b/gdbserver/server.cc
@@ -81,6 +81,12 @@  static bool extended_protocol;
 static bool response_needed;
 static bool exit_requested;
 
+/* To be able to connect the inferior to GDB's stdin/out/err,
+   when GDBserver is run locally, we need to wait with starting
+   the inferior to have time to recieve the vDefaultInferiorFd
+   packet.  Set to true if need to postpone starting the inferior.  */
+static bool deferred_inferior_startup = false;
+
 /* --once: Exit after the first connection has closed.  */
 bool run_once;
 
@@ -171,6 +177,34 @@  get_client_state ()
   return cs;
 }
 
+int multi_mode = 0;
+
+/* To be able to connect the inferior to GDB's stdin/out/err,
+   when GDBserver is run locally, we need to wait with starting
+   the inferior to have time to recieve the vDefaultInferiorFd
+   packet.
+ 
+   Can only be called when deferred_inferior_startup is true.
+   Starts the inferior and throws an error if startup fails.
+   If startup is successful then deferred_inferior_startup is
+   reset to false. Throw an error on failure.  */
+static void
+do_deferred_startup ()
+{
+  gdb_assert (deferred_inferior_startup);
+  client_state &cs = get_client_state ();
+  target_create_inferior (program_path.get (), program_args);
+
+  if (current_thread != nullptr)
+    current_process ()->dlls_changed = false;
+
+  if ((cs.last_status.kind () == TARGET_WAITKIND_EXITED
+       || cs.last_status.kind () == TARGET_WAITKIND_SIGNALLED)
+       && !multi_mode)
+      error ("No program to debug");
+
+  deferred_inferior_startup = false;
+}
 
 /* Put a stop reply to the stop reply queue.  */
 
@@ -2501,6 +2535,45 @@  supported_btrace_packets (char *buf)
   strcat (buf, ";qXfer:btrace-conf:read+");
 }
 
+/* Parse a vDefaultInferiorFd packet from pkt
+   and save the received file descriptors into
+   fds.  Return true  on success, false otherwise.
+   Set the file descriptors to -1 on failure.  */
+static bool
+parse_vdefaultinf (const char *pkt, int *fds)
+{
+  char *end;
+  int ret = true;
+  errno = 0;
+
+  if (*pkt != '\0')
+    {
+      fds[0] = (int) strtoul (pkt, &end, 10);
+      if ((pkt == end) || (*end != ';'))
+        ret = false;
+      pkt = end + 1;
+      fds[1] = (int) strtoul (pkt, &end, 10);
+      if ((pkt == end) || (*end != ';'))
+        ret = false;
+      pkt = end + 1;
+      fds[2] = (int) strtoul (pkt, &end, 10);
+      if ((pkt == end) || (*end != '\0'))
+        ret = false;
+      if (errno != 0)
+        ret = false;
+    }
+  else
+    ret = false;
+  if (!ret)
+  {
+    fds[0] = -1;
+    fds[1] = -1;
+    fds[2] = -1;
+  }
+
+  return ret;
+}
+
 /* Handle all of the extended 'q' packets.  */
 
 static void
@@ -2711,6 +2784,8 @@  handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
 		  if (target_supports_memory_tagging ())
 		    cs.memory_tagging_feature = true;
 		}
+	      else if (feature == "vDefaultInferiorFd+")
+		cs.vDefaultInferiorFd_supported = 1;
 	      else
 		{
 		  /* Move the unknown features all together.  */
@@ -2840,6 +2915,18 @@  handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
       if (target_supports_memory_tagging ())
 	strcat (own_buf, ";memory-tagging+");
 
+      if (cs.vDefaultInferiorFd_supported)
+      {
+         /* If GDB didn't advertise vDefaultInferiorFd then we don't mention
+            vDefaultInferiorFd in the reply.  If GDB did mention vDefaultInferiorFd
+            then we only claim support if we are started as an stdio target.  */
+          if (remote_connection_is_stdio ()
+	      && target_supports_default_fds ())
+	    strcat (own_buf, ";vDefaultInferiorFd+");
+	  else
+	    strcat (own_buf, ";vDefaultInferiorFd-");
+	}
+
       /* Reinitialize components as needed for the new connection.  */
       hostio_handle_new_gdb_connection ();
       target_handle_new_gdb_connection ();
@@ -3543,6 +3630,19 @@  handle_v_requests (char *own_buf, int packet_len, int *new_packet_len)
       return;
     }
 
+  if (startswith (own_buf, "vDefaultInferiorFd;"))
+  {
+    if (!parse_vdefaultinf (own_buf + 19, cs.fds))
+      {
+	strcpy (own_buf, "E.invalid vDefaultInferiorFd packet");
+	return;
+      }
+
+    cs.vDefaultInferiorFd_accepted = true;
+    write_ok (own_buf);
+    return;
+  }
+
   if (startswith (own_buf, "vKill;"))
     {
       if (!target_running ())
@@ -4038,7 +4138,6 @@  captured_main (int argc, char *argv[])
   char *arg_end;
   const char *port = NULL;
   char **next_arg = &argv[1];
-  volatile int multi_mode = 0;
   volatile int attach = 0;
   int was_running;
   bool selftest = false;
@@ -4307,8 +4406,27 @@  captured_main (int argc, char *argv[])
       for (i = 1; i < n; i++)
 	program_args.push_back (xstrdup (next_arg[i]));
 
-      /* Wait till we are at first instruction in program.  */
-      target_create_inferior (program_path.get (), program_args);
+      if (remote_connection_is_stdio ())
+	{
+	  /* Debugger might want to set the default inferior
+	     in/out/err file descriptors, in which case we need to
+	     defer starting the inferior until this information
+	     arrives.  */
+	  deferred_inferior_startup = true;
+          multi_mode = 1;
+
+	  /* Only used when reporting the first stop to GDB.  This
+	     should be replaced when we start the inferior, but lets
+	     be on the safe side and set this now just so we carry
+	     around a sane state.  */
+	  cs.last_status.set_exited (0);
+	  cs.last_ptid = minus_one_ptid;
+	}
+      else
+	{
+	  /* Wait till we are at first instruction in program.  */
+	  target_create_inferior (program_path.get (), program_args);
+	}
 
       /* We are now (hopefully) stopped at the first instruction of
 	 the target process.  This assumes that the target process was
@@ -4358,6 +4476,7 @@  captured_main (int argc, char *argv[])
       cs.hwbreak_feature = 0;
       cs.vCont_supported = 0;
       cs.memory_tagging_feature = false;
+      cs.vDefaultInferiorFd_supported = false;
 
       remote_open (port);
 
@@ -4547,6 +4666,16 @@  process_serial_event (void)
   response_needed = true;
 
   char ch = cs.own_buf[0];
+  if (deferred_inferior_startup)
+  {
+    /* Actually start the inferior if not a qSupported or
+       vDefaultInferiorFd packet.  vDefaultInferiorFd is
+       only accepted after qSupported and before any other
+       request.  */
+    if (!startswith(cs.own_buf, "qSupported")
+       && !startswith(cs.own_buf, "vDefaultInferiorFd"))
+      do_deferred_startup ();
+  }
   switch (ch)
     {
     case 'q':
diff --git a/gdbserver/server.h b/gdbserver/server.h
index 0074818c6df..dacc5cb5d6f 100644
--- a/gdbserver/server.h
+++ b/gdbserver/server.h
@@ -192,6 +192,18 @@  struct client_state
   /* If true, memory tagging features are supported.  */
   bool memory_tagging_feature = false;
 
+  /* If true, connecting the inferior to the terminal's
+     stdin/out/err when running GDBserver locally is supported.  */
+  bool vDefaultInferiorFd_supported = false;
+
+  /* If true, GDBserver accepted file descriptors sent by GDB
+    and connecting the inferior to the same terminal as GDB
+    when running GDBserver locally is then supported.  */
+  bool vDefaultInferiorFd_accepted = false;
+
+  /* File descriptors pointing to terminal's stdin/out/err
+     sent by GDB to GDBserver.  */
+  int fds[3];
 };
 
 client_state &get_client_state ();
diff --git a/gdbserver/target.cc b/gdbserver/target.cc
index 23b699d33bb..f19edb67dc5 100644
--- a/gdbserver/target.cc
+++ b/gdbserver/target.cc
@@ -840,6 +840,12 @@  process_stratum_target::supports_catch_syscall ()
   return false;
 }
 
+bool
+process_stratum_target::supports_default_fds ()
+{
+  return false;
+}
+
 int
 process_stratum_target::get_ipa_tdesc_idx ()
 {
diff --git a/gdbserver/target.h b/gdbserver/target.h
index 3643b9110da..01d2882a7e1 100644
--- a/gdbserver/target.h
+++ b/gdbserver/target.h
@@ -497,6 +497,9 @@  class process_stratum_target
   /* Return true if the target supports catch syscall.  */
   virtual bool supports_catch_syscall ();
 
+  /* Return true if the target supports vDefaultInferiorFd.  */
+  virtual bool supports_default_fds ();
+
   /* Return tdesc index for IPA.  */
   virtual int get_ipa_tdesc_idx ();
 
@@ -578,6 +581,9 @@  int kill_inferior (process_info *proc);
 #define target_supports_catch_syscall()              	\
   the_target->supports_catch_syscall ()
 
+#define target_supports_default_fds()              	\
+  the_target->supports_default_fds ()
+
 #define target_get_ipa_tdesc_idx()			\
   the_target->get_ipa_tdesc_idx ()