gdb: make packet_command function available outside remote.c
authorAndrew Burgess <aburgess@redhat.com>
Tue, 31 Aug 2021 13:04:11 +0000 (14:04 +0100)
committerAndrew Burgess <aburgess@redhat.com>
Tue, 30 Nov 2021 12:10:40 +0000 (12:10 +0000)
In a later commit I will add a Python API to access the 'maint packet'
functionality, that is, sending a user specified packet to the target.

To make implementing this easier, this commit refactors how this
command is currently implemented so that the packet_command function
is now global.

The new global send_remote_packet function takes an object that is an
implementation of an abstract interface.  Two functions within this
interface are then called, one just before a packet is sent to the
remote target, and one when the reply has been received from the
remote target.  Using an interface object in this way allows (1) for
the error checking to be done before the first callback is made, this
means we only print out what packet it being sent once we know we are
going to actually send it, and (2) we don't need to make a copy of the
reply if all we want to do is print it.

One user visible changes after this commit are the error
messages, which I've changed to be less 'maint packet' command
focused, this will make them (I hope) better for when
send_remote_packet can be called from Python code.

So:      "command can only be used with remote target"
Becomes: "packets can only be sent to a remote target"

And:     "remote-packet command requires packet text as argument"
Becomes: "a remote packet must not be empty"

Additionally, in this commit, I've added support for packet replies
that contain binary data.  Before this commit, the code that printed
the reply treated the reply as a C string, it assumed that the string
only contained printable characters, and had a null character only at
the end.

One way to show the problem with this is if we try to read the auxv
data from a remote target, the auxv data is binary, so, before this
commit:

  (gdb) target remote :54321
  ...
  (gdb) maint packet qXfer:auxv:read::0,1000
  sending: "qXfer:auxv:read::0,1000"
  received: "l!"
  (gdb)

And after this commit:

  (gdb) target remote :54321
  ...
  (gdb) maint packet qXfer:auxv:read::0,1000
  sending: "qXfer:auxv:read::0,1000"
  received: "l!\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xfc\xf7\xff\x7f\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\xff\xf>
  (gdb)

The binary contents of the reply are now printed as escaped hex.

gdb/NEWS
gdb/doc/gdb.texinfo
gdb/remote.c
gdb/remote.h

index b55d9bc47f79a65e487b9c1a2bcc3dd73cfabb99..09c5169e4ed36eccce449692c34ad490bd9c1abf 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -39,6 +39,14 @@ set logging enabled on|off
 show logging enabled
   These commands set or show whether logging is enabled or disabled.
 
+* Changed commands
+
+maint packet
+  This command can now print a reply, if the reply includes
+  non-printable characters.  Any non-printable characters are printed
+  as escaped hex, e.g. \x?? where '??' is replaces with the value of
+  the non-printable character.
+
 * Python API
 
   ** New function gdb.add_history(), which takes a gdb.Value object
index 1a944b10ae52752fc720fbea138d7897a53131a7..738f2e4c0c7eefd1ef85f2c8fdeef4f3e992d9d5 100644 (file)
@@ -39286,6 +39286,9 @@ displays the response packet.  @value{GDBN} supplies the initial
 @samp{$} character, the terminating @samp{#} character, and the
 checksum.
 
+Any non-printable characters in the reply are printed as escaped hex,
+e.g. @samp{\x00}, @samp{\x01}, etc.
+
 @kindex maint print architecture
 @item maint print architecture @r{[}@var{file}@r{]}
 Print the entire architecture configuration.  The optional argument
index 25a4d3cab6ecf81992bc6e82f388d3730b6a597d..b4269809b4ed6deca528a7c5f7683644b8815a2e 100644 (file)
@@ -956,8 +956,6 @@ public: /* Remote specific methods.  */
 
   bool vcont_r_supported ();
 
-  void packet_command (const char *args, int from_tty);
-
 private: /* data fields */
 
   /* The remote state.  Don't reference this directly.  Use the
@@ -1030,8 +1028,6 @@ static int hexnumnstr (char *, ULONGEST, int);
 
 static CORE_ADDR remote_address_masked (CORE_ADDR);
 
-static void print_packet (const char *);
-
 static int stub_unpack_int (const char *buff, int fieldlength);
 
 struct packet_config;
@@ -9495,17 +9491,6 @@ escape_buffer (const char *buf, int n)
   return std::move (stb.string ());
 }
 
-/* Display a null-terminated packet on stdout, for debugging, using C
-   string notation.  */
-
-static void
-print_packet (const char *buf)
-{
-  puts_filtered ("\"");
-  fputstr_filtered (buf, '"', gdb_stdout);
-  puts_filtered ("\"");
-}
-
 int
 remote_target::putpkt (const char *buf)
 {
@@ -11592,34 +11577,87 @@ remote_target::memory_map ()
   return result;
 }
 
-static void
-packet_command (const char *args, int from_tty)
+/* Set of callbacks used to implement the 'maint packet' command.  */
+
+struct cli_packet_command_callbacks : public send_remote_packet_callbacks
 {
-  remote_target *remote = get_current_remote_target ();
+  /* Called before the packet is sent.  BUF is the packet content before
+     the protocol specific prefix, suffix, and escaping is added.  */
 
-  if (remote == nullptr)
-    error (_("command can only be used with remote target"));
+  void sending (gdb::array_view<const char> &buf) override
+  {
+    puts_filtered ("sending: ");
+    print_packet (buf);
+    puts_filtered ("\n");
+  }
 
-  remote->packet_command (args, from_tty);
-}
+  /* Called with BUF, the reply from the remote target.  */
+
+  void received (gdb::array_view<const char> &buf) override
+  {
+    puts_filtered ("received: \"");
+    print_packet (buf);
+    puts_filtered ("\"\n");
+  }
+
+private:
+
+  /* Print BUF o gdb_stdout.  Any non-printable bytes in BUF are printed as
+     '\x??' with '??' replaced by the hexadecimal value of the byte.  */
+
+  static void
+  print_packet (gdb::array_view<const char> &buf)
+  {
+    string_file stb;
+
+    for (int i = 0; i < buf.size (); ++i)
+      {
+       gdb_byte c = buf[i];
+       if (isprint (c))
+         fputc_unfiltered (c, &stb);
+       else
+         fprintf_unfiltered (&stb, "\\x%02x", (unsigned char) c);
+      }
+
+    puts_filtered (stb.string ().c_str ());
+  }
+};
+
+/* See remote.h.  */
 
 void
-remote_target::packet_command (const char *args, int from_tty)
+send_remote_packet (gdb::array_view<const char> &buf,
+                   send_remote_packet_callbacks *callbacks)
 {
-  if (!args)
-    error (_("remote-packet command requires packet text as argument"));
+  if (buf.size () == 0 || buf.data ()[0] == '\0')
+    error (_("a remote packet must not be empty"));
 
-  puts_filtered ("sending: ");
-  print_packet (args);
-  puts_filtered ("\n");
-  putpkt (args);
+  remote_target *remote = get_current_remote_target ();
+  if (remote == nullptr)
+    error (_("packets can only be sent to a remote target"));
 
-  remote_state *rs = get_remote_state ();
+  callbacks->sending (buf);
 
-  getpkt (&rs->buf, 0);
-  puts_filtered ("received: ");
-  print_packet (rs->buf.data ());
-  puts_filtered ("\n");
+  remote->putpkt_binary (buf.data (), buf.size ());
+  remote_state *rs = remote->get_remote_state ();
+  int bytes = remote->getpkt_sane (&rs->buf, 0);
+
+  if (bytes < 0)
+    error (_("error while fetching packet from remote target"));
+
+  gdb::array_view<const char> view (&rs->buf[0], bytes);
+  callbacks->received (view);
+}
+
+/* Entry point for the 'maint packet' command.  */
+
+static void
+cli_packet_command (const char *args, int from_tty)
+{
+  cli_packet_command_callbacks cb;
+  gdb::array_view<const char> view
+    = gdb::make_array_view (args, args == nullptr ? 0 : strlen (args));
+  send_remote_packet (view, &cb);
 }
 
 #if 0
@@ -14890,7 +14928,7 @@ Argument is a single section name (default: all loaded sections).\n\
 To compare only read-only loaded sections, specify the -r option."),
           &cmdlist);
 
-  add_cmd ("packet", class_maintenance, packet_command, _("\
+  add_cmd ("packet", class_maintenance, cli_packet_command, _("\
 Send an arbitrary packet to a remote target.\n\
    maintenance packet TEXT\n\
 If GDB is talking to an inferior via the GDB serial protocol, then\n\
index 46bfa01fc79bf03a3364dc7312564a7056d2b981..0178294ab1d6fc95a49bc4255eb4efc9cf0b2a12 100644 (file)
@@ -78,4 +78,39 @@ extern int remote_register_number_and_offset (struct gdbarch *gdbarch,
 extern void remote_notif_get_pending_events (remote_target *remote,
                                             struct notif_client *np);
 extern bool remote_target_is_non_stop_p (remote_target *t);
+
+/* An abstract class that represents the set of callbacks that are made
+   from the send_remote_packet function (declared below).  */
+
+struct send_remote_packet_callbacks
+{
+  /* The SENDING callback is called once send_remote_packet has performed
+     its error checking and setup, just before the packet is sent to the
+     remote target.  BUF is the content of the packet that will be sent
+     (before any of the protocol specific prefix, suffix, or escaping is
+     applied).  */
+
+  virtual void sending (gdb::array_view<const char> &buf) = 0;
+
+  /* The RECEIVED callback is called once a reply has been received from
+     the remote target.  The content of the reply is in BUF which can't be
+     modified, and which is not guaranteed to remain valid after the
+     RECEIVED call has returned.  If you need to preserve the contents of
+     BUF then a copy should be taken.  */
+
+  virtual void received (gdb::array_view<const char> &buf) = 0;
+};
+
+/* Send BUF to the current remote target.  If BUF points to an empty
+   string, either zero length, or the first character is the null
+   character, then an error is thrown.  If the current target is not a
+   remote target then an error is thrown.
+
+   Calls CALLBACKS->sending() just before the packet is sent to the remote
+   target, and calls CALLBACKS->received() with the reply once this is
+   received from the remote target.  */
+
+extern void send_remote_packet (gdb::array_view<const char> &buf,
+                               send_remote_packet_callbacks *callbacks);
+
 #endif