[3/8] Introduce gdb-specific %p format suffixes

Message ID 20190927212520.20073-4-tom@tromey.com
State New, archived
Headers

Commit Message

Tom Tromey Sept. 27, 2019, 9:25 p.m. UTC
  From: Pedro Alves <palves@redhat.com>

This introduces a few gdb-specific %p format suffixes.  This is useful
for emitting gdb-specific output in an ergonomic way.  It also yields
code that is more i18n-friendly.

The comment before ui_out::message explains the details.

Note that the tests had to change a little.  When using one of the gdb
printf functions with styling, there can be spurious style changes
emitted to the output.  This did not seem worthwhile to fix, as the
low-level output functions are rather spaghetti-ish already, and I
didn't want to make them even worse.

This change also necessitated adding support for "*" as precision and
width in format_pieces.  These are used in various spots in gdb, and
it seemed better to me to implement them than to remove the uses.

gdb/ChangeLog
2019-09-27  Pedro Alves  <palves@redhat.com>
	    Tom Tromey  <tom@tromey.com>

	* unittests/format_pieces-selftests.c: Add gdb_format parameter.
	(test_gdb_formats): New function.
	(run_tests): Call it.
	(test_format_specifier): Update.
	* utils.h (fputs_filtered): Update comment.
	(vfprintf_styled, vfprintf_styled_no_gdbfmt)
	(fputs_styled_unfiltered): Declare.
	* utils.c (fputs_styled_unfiltered): New function.
	(vfprintf_maybe_filtered): Add gdbfmt parameter.
	(vfprintf_filtered): Update.
	(vfprintf_unfiltered, vprintf_filtered): Update.
	(vfprintf_styled, vfprintf_styled_no_gdbfmt): New functions.
	* ui-out.h (enum ui_out_flag) <unfiltered_output,
	disallow_ui_out_field>: New constants.
	(enum class field_kind): New.
	(struct base_field_s, struct signed_field_s): New.
	(signed_field): New function.
	(struct string_field_s): New.
	(string_field): New function.
	(struct styled_string_s): New.
	(styled_string): New function.
	(class ui_out) <message>: Add comment.
	<vmessage, call_do_message>: New methods.
	<do_message>: Add style parameter.
	* ui-out.c (ui_out::call_do_message, ui_out::vmessage): New
	methods.
	(ui_out::message): Rewrite.
	* mi/mi-out.h (class mi_ui_out) <do_message>: Add style
	parameter.
	* mi/mi-out.c (mi_ui_out::do_message): Add style parameter.
	* gdbsupport/format.h (class format_pieces) <format_pieces>: Add
	gdb_extensions parameter.
	(class format_piece): Add parameter to constructor.
	(n_int_args): New field.
	* gdbsupport/format.c (format_pieces::format_pieces): Add
	gdb_extensions parameter.  Handle '*'.
	* cli-out.h (class cli_ui_out) <do_message>: Add style parameter.
	* cli-out.c (cli_ui_out::do_message): Add style parameter.  Call
	vfprintf_styled_no_gdbfmt.
	(cli_ui_out::do_field_string, cli_ui_out::do_spaces)
	(cli_ui_out::do_text, cli_ui_out::field_separator): Allow
	unfiltered output.
	* ui-style.h (struct ui_file_style) <ptr>: New method.

gdb/testsuite/ChangeLog
2019-09-27  Tom Tromey  <tom@tromey.com>

	* gdb.base/style.exp: Update tests.
---
 gdb/ChangeLog                           |  47 ++++++
 gdb/cli-out.c                           |  31 +++-
 gdb/cli-out.h                           |   5 +-
 gdb/gdbsupport/format.c                 | 166 +++++++++++++--------
 gdb/gdbsupport/format.h                 |  11 +-
 gdb/mi/mi-out.c                         |   3 +-
 gdb/mi/mi-out.h                         |   5 +-
 gdb/testsuite/ChangeLog                 |   4 +
 gdb/testsuite/gdb.base/style.exp        |  10 +-
 gdb/ui-out.c                            | 182 +++++++++++++++++++++++-
 gdb/ui-out.h                            | 142 +++++++++++++++++-
 gdb/ui-style.h                          |   6 +
 gdb/unittests/format_pieces-selftests.c |  35 +++--
 gdb/utils.c                             |  81 +++++++++--
 gdb/utils.h                             |  25 +++-
 15 files changed, 646 insertions(+), 107 deletions(-)
  

Comments

Pedro Alves Sept. 30, 2019, 2:05 p.m. UTC | #1
Hi,

I noticed a coupled typos while reading this, probably mine...

On 9/27/19 10:25 PM, Tom Tromey wrote:
> +/* The possible kinds of fields.  */
> +enum class field_kind
> +  {
> +    SIGNED,
> +    STRING,
> +  };
> +
> +/* An int field, to be passed to %pF in format strings.  */

This should read something like:

/* The base type of all fields.  */

> +
> +struct base_field_s
> +{
> +  const char *name;
> +  field_kind kind;
> +};
> +
> +/* An int field, to be passed to %pF in format strings.  */

This used to be called int_field_s, before the
'uiout->field_int -> uiout->field_signed' rename.

Should now read:

/* A signed integer field, to be passed to %pF in format strings.  */

> +
> +struct signed_field_s : base_field_s
> +{
> +  LONGEST val;
> +};
> +
> +/* Construct a temporary signed_field_s on the caller's stack and
> +   return a pointer to the constructed object.  We use this because
> +   it's not possible to pass a reference via va_args.  */
> +
> +static inline signed_field_s *
> +signed_field (const char *name, LONGEST val,
> +	      signed_field_s &&tmp = {})
> +{
> +  tmp.name = name;
> +  tmp.kind = field_kind::SIGNED;
> +  tmp.val = val;
> +  return &tmp;
> +}
> +
> +/* A string field, to be passed to %pF in format strings.  */
> +
> +struct string_field_s : base_field_s
> +{
> +  const char *str;
> +};
> +
> +/* Construct a temporary string_field_s on the caller's stack and
> +   return a pointer to the constructed object.  We use this because
> +   it's not possible to pass a reference via va_args.  */
> +
> +static inline string_field_s *
> +string_field (const char *name, const char *str,
> +	      string_field_s &&tmp = {})
> +{
> +  tmp.name = name;
> +  tmp.kind = field_kind::STRING;
> +  tmp.str = str;
> +  return &tmp;
> +}
> +
> +/* A styled string.  */
> +
> +struct styled_string_s
> +{
> +  /* The style.  */
> +  ui_file_style style;
> +
> +  /* The string.  */
> +  const char *str;
> +};
> +
> +/* Construct a temporary styled_string_s on the caller's stack and
> +   return a pointer to the constructed object.  We use this because
> +   it's not possible to pass a reference via va_args.  */
> +
> +static inline styled_string_s *
> +styled_string (const ui_file_style &style, const char *str,
> +	       styled_string_s &&tmp = {})
> +{
> +  tmp.style = style;
> +  tmp.str = str;
> +  return &tmp;
> +}
> +
>  class ui_out
>  {
>   public:
> @@ -110,7 +197,55 @@ class ui_out
>  
>    void spaces (int numspaces);
>    void text (const char *string);
> +
> +  /* Output a printf-style formatted string.  In addition to the usual
> +     printf format specs, this supports a few GDB-specific
> +     formatters:
> +
> +     - '%pF' - output a field.
> +
> +       The argument is a field, wrapped in any of the base_field_s
> +       subclasses.  signed_field for integer fields, styled_field for

styled_field -> string_field

> +       string fields.  This is preferred over separate
> +       uiout->field_int(), uiout_>field_string() etc. calls when the

field_int -> field_signed

> +       formatted message is translatable.  E.g.:
> +

Thanks,
Pedro Alves
  

Patch

diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index fa72a1d344f..c713607e068 100644
--- a/gdb/cli-out.c
+++ b/gdb/cli-out.c
@@ -170,7 +170,12 @@  cli_ui_out::do_field_string (int fldno, int width, ui_align align,
     spaces (before);
 
   if (string)
-    fputs_styled (string, style, m_streams.back ());
+    {
+      if (test_flags (unfiltered_output))
+	fputs_styled_unfiltered (string, style, m_streams.back ());
+      else
+	fputs_styled (string, style, m_streams.back ());
+    }
 
   if (after)
     spaces (after);
@@ -201,7 +206,10 @@  cli_ui_out::do_spaces (int numspaces)
   if (m_suppress_output)
     return;
 
-  print_spaces_filtered (numspaces, m_streams.back ());
+  if (test_flags (unfiltered_output))
+    print_spaces (numspaces, m_streams.back ());
+  else
+    print_spaces_filtered (numspaces, m_streams.back ());
 }
 
 void
@@ -210,16 +218,24 @@  cli_ui_out::do_text (const char *string)
   if (m_suppress_output)
     return;
 
-  fputs_filtered (string, m_streams.back ());
+  if (test_flags (unfiltered_output))
+    fputs_unfiltered (string, m_streams.back ());
+  else
+    fputs_filtered (string, m_streams.back ());
 }
 
 void
-cli_ui_out::do_message (const char *format, va_list args)
+cli_ui_out::do_message (const ui_file_style &style,
+			const char *format, va_list args)
 {
   if (m_suppress_output)
     return;
 
-  vfprintf_unfiltered (m_streams.back (), format, args);
+  /* Use the "no_gdbfmt" variant here to avoid recursion.
+     vfprintf_styled calls into cli_ui_out::message to handle the
+     gdb-specific printf formats.  */
+  vfprintf_styled_no_gdbfmt (m_streams.back (), style,
+			     !test_flags (unfiltered_output), format, args);
 }
 
 void
@@ -255,7 +271,10 @@  cli_ui_out::do_redirect (ui_file *outstream)
 void
 cli_ui_out::field_separator ()
 {
-  fputc_filtered (' ', m_streams.back ());
+  if (test_flags (unfiltered_output))
+    fputc_unfiltered (' ', m_streams.back ());
+  else
+    fputc_filtered (' ', m_streams.back ());
 }
 
 /* Constructor for cli_ui_out.  */
diff --git a/gdb/cli-out.h b/gdb/cli-out.h
index bc8b781d605..d7bd23b0ef2 100644
--- a/gdb/cli-out.h
+++ b/gdb/cli-out.h
@@ -64,8 +64,9 @@  protected:
     override ATTRIBUTE_PRINTF (6,0);
   virtual void do_spaces (int numspaces) override;
   virtual void do_text (const char *string) override;
-  virtual void do_message (const char *format, va_list args) override
-    ATTRIBUTE_PRINTF (2,0);
+  virtual void do_message (const ui_file_style &style,
+			   const char *format, va_list args) override
+    ATTRIBUTE_PRINTF (3,0);
   virtual void do_wrap_hint (const char *identstring) override;
   virtual void do_flush () override;
   virtual void do_redirect (struct ui_file *outstream) override;
diff --git a/gdb/gdbsupport/format.c b/gdb/gdbsupport/format.c
index a5a367015f1..1e803501ae6 100644
--- a/gdb/gdbsupport/format.c
+++ b/gdb/gdbsupport/format.c
@@ -20,10 +20,10 @@ 
 #include "common-defs.h"
 #include "format.h"
 
-format_pieces::format_pieces (const char **arg)
+format_pieces::format_pieces (const char **arg, bool gdb_extensions)
 {
   const char *s;
-  char *f, *string;
+  const char *string;
   const char *prev_start;
   const char *percent_loc;
   char *sub_start, *current_substring;
@@ -31,70 +31,79 @@  format_pieces::format_pieces (const char **arg)
 
   s = *arg;
 
-  /* Parse the format-control string and copy it into the string STRING,
-     processing some kinds of escape sequence.  */
+  if (gdb_extensions)
+    {
+      string = *arg;
+      *arg += strlen (*arg);
+    }
+  else
+    {
+      /* Parse the format-control string and copy it into the string STRING,
+	 processing some kinds of escape sequence.  */
 
-  f = string = (char *) alloca (strlen (s) + 1);
+      char *f = (char *) alloca (strlen (s) + 1);
+      string = f;
 
-  while (*s != '"' && *s != '\0')
-    {
-      int c = *s++;
-      switch (c)
+      while ((gdb_extensions || *s != '"') && *s != '\0')
 	{
-	case '\0':
-	  continue;
-
-	case '\\':
-	  switch (c = *s++)
+	  int c = *s++;
+	  switch (c)
 	    {
+	    case '\0':
+	      continue;
+
 	    case '\\':
-	      *f++ = '\\';
-	      break;
-	    case 'a':
-	      *f++ = '\a';
-	      break;
-	    case 'b':
-	      *f++ = '\b';
-	      break;
-	    case 'e':
-	      *f++ = '\e';
-	      break;
-	    case 'f':
-	      *f++ = '\f';
-	      break;
-	    case 'n':
-	      *f++ = '\n';
-	      break;
-	    case 'r':
-	      *f++ = '\r';
-	      break;
-	    case 't':
-	      *f++ = '\t';
-	      break;
-	    case 'v':
-	      *f++ = '\v';
-	      break;
-	    case '"':
-	      *f++ = '"';
+	      switch (c = *s++)
+		{
+		case '\\':
+		  *f++ = '\\';
+		  break;
+		case 'a':
+		  *f++ = '\a';
+		  break;
+		case 'b':
+		  *f++ = '\b';
+		  break;
+		case 'e':
+		  *f++ = '\e';
+		  break;
+		case 'f':
+		  *f++ = '\f';
+		  break;
+		case 'n':
+		  *f++ = '\n';
+		  break;
+		case 'r':
+		  *f++ = '\r';
+		  break;
+		case 't':
+		  *f++ = '\t';
+		  break;
+		case 'v':
+		  *f++ = '\v';
+		  break;
+		case '"':
+		  *f++ = '"';
+		  break;
+		default:
+		  /* ??? TODO: handle other escape sequences.  */
+		  error (_("Unrecognized escape character \\%c in format string."),
+			 c);
+		}
 	      break;
+
 	    default:
-	      /* ??? TODO: handle other escape sequences.  */
-	      error (_("Unrecognized escape character \\%c in format string."),
-		     c);
+	      *f++ = c;
 	    }
-	  break;
-
-	default:
-	  *f++ = c;
 	}
-    }
 
-  /* Terminate our escape-processed copy.  */
-  *f++ = '\0';
+      /* Terminate our escape-processed copy.  */
+      *f++ = '\0';
 
-  /* Whether the format string ended with double-quote or zero, we're
-     done with it; it's up to callers to complain about syntax.  */
-  *arg = s;
+      /* Whether the format string ended with double-quote or zero, we're
+	 done with it; it's up to callers to complain about syntax.  */
+      *arg = s;
+    }
 
   /* Need extra space for the '\0's.  Doubling the size is sufficient.  */
 
@@ -105,7 +114,7 @@  format_pieces::format_pieces (const char **arg)
      argclass classifies the %-specs so we can give printf-type functions
      something of the right size.  */
 
-  f = string;
+  const char *f = string;
   prev_start = string;
   while (*f)
     if (*f++ == '%')
@@ -115,6 +124,7 @@  format_pieces::format_pieces (const char **arg)
 	int seen_big_l = 0, seen_h = 0, seen_big_h = 0;
 	int seen_big_d = 0, seen_double_big_d = 0;
 	int bad = 0;
+	int n_int_args = 0;
 
 	/* Skip over "%%", it will become part of a literal piece.  */
 	if (*f == '%')
@@ -130,7 +140,7 @@  format_pieces::format_pieces (const char **arg)
 	*current_substring++ = '\0';
 
 	if (*sub_start != '\0')
-	  m_pieces.emplace_back (sub_start, literal_piece);
+	  m_pieces.emplace_back (sub_start, literal_piece, 0);
 
 	percent_loc = f - 1;
 
@@ -155,16 +165,32 @@  format_pieces::format_pieces (const char **arg)
 	  }
 
 	/* The next part of a format specifier is a width.  */
-	while (*f != '\0' && strchr ("0123456789", *f))
-	  f++;
+	if (gdb_extensions && *f == '*')
+	  {
+	    ++f;
+	    ++n_int_args;
+	  }
+	else
+	  {
+	    while (*f != '\0' && strchr ("0123456789", *f))
+	      f++;
+	  }
 
 	/* The next part of a format specifier is a precision.  */
 	if (*f == '.')
 	  {
 	    seen_prec = 1;
 	    f++;
-	    while (*f != '\0' && strchr ("0123456789", *f))
-	      f++;
+	    if (gdb_extensions && *f == '*')
+	      {
+		++f;
+		++n_int_args;
+	      }
+	    else
+	      {
+		while (*f != '\0' && strchr ("0123456789", *f))
+		  f++;
+	      }
 	  }
 
 	/* The next part of a format specifier is a length modifier.  */
@@ -252,6 +278,20 @@  format_pieces::format_pieces (const char **arg)
 	      bad = 1;
 	    if (seen_hash || seen_zero || seen_space || seen_plus)
 	      bad = 1;
+
+	    if (gdb_extensions)
+	      {
+		switch (f[1])
+		  {
+		  case 's':
+		  case 'F':
+		  case '[':
+		  case ']':
+		    f++;
+		    break;
+		  }
+	      }
+
 	    break;
 
 	  case 's':
@@ -336,7 +376,7 @@  format_pieces::format_pieces (const char **arg)
 
 	prev_start = f;
 
-	m_pieces.emplace_back (sub_start, this_argclass);
+	m_pieces.emplace_back (sub_start, this_argclass, n_int_args);
       }
 
   /* Record the remainder of the string.  */
@@ -349,6 +389,6 @@  format_pieces::format_pieces (const char **arg)
       current_substring += f - prev_start;
       *current_substring++ = '\0';
 
-      m_pieces.emplace_back (sub_start, literal_piece);
+      m_pieces.emplace_back (sub_start, literal_piece, 0);
     }
 }
diff --git a/gdb/gdbsupport/format.h b/gdb/gdbsupport/format.h
index 08ef66a7602..e2a47ba5187 100644
--- a/gdb/gdbsupport/format.h
+++ b/gdb/gdbsupport/format.h
@@ -50,9 +50,10 @@  enum argclass
 
 struct format_piece
 {
-  format_piece (const char *str, enum argclass argc)
+  format_piece (const char *str, enum argclass argc, int n)
     : string (str),
-      argclass (argc)
+      argclass (argc),
+      n_int_args (n)
   {
   }
 
@@ -64,13 +65,17 @@  struct format_piece
 
   const char *string;
   enum argclass argclass;
+  /* Count the number of preceding 'int' arguments that must be passed
+     along.  This is used for a width or precision of '*'.  Note that
+     this feature is only available in "gdb_extensions" mode.  */
+  int n_int_args;
 };
 
 class format_pieces
 {
 public:
 
-  format_pieces (const char **arg);
+  format_pieces (const char **arg, bool gdb_extensions = false);
   ~format_pieces () = default;
 
   DISABLE_COPY_AND_ASSIGN (format_pieces);
diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c
index 0b930738f1d..71af4865e97 100644
--- a/gdb/mi/mi-out.c
+++ b/gdb/mi/mi-out.c
@@ -166,7 +166,8 @@  mi_ui_out::do_text (const char *string)
 }
 
 void
-mi_ui_out::do_message (const char *format, va_list args)
+mi_ui_out::do_message (const ui_file_style &style,
+		       const char *format, va_list args)
 {
 }
 
diff --git a/gdb/mi/mi-out.h b/gdb/mi/mi-out.h
index 90528fd4e84..9393809b5f2 100644
--- a/gdb/mi/mi-out.h
+++ b/gdb/mi/mi-out.h
@@ -72,8 +72,9 @@  protected:
     override ATTRIBUTE_PRINTF (6,0);
   virtual void do_spaces (int numspaces) override;
   virtual void do_text (const char *string) override;
-  virtual void do_message (const char *format, va_list args) override
-    ATTRIBUTE_PRINTF (2,0);
+  virtual void do_message (const ui_file_style &style,
+			   const char *format, va_list args) override
+    ATTRIBUTE_PRINTF (3,0);
   virtual void do_wrap_hint (const char *identstring) override;
   virtual void do_flush () override;
   virtual void do_redirect (struct ui_file *outstream) override;
diff --git a/gdb/testsuite/gdb.base/style.exp b/gdb/testsuite/gdb.base/style.exp
index 41c43dc8f7c..d2c3105bb9f 100644
--- a/gdb/testsuite/gdb.base/style.exp
+++ b/gdb/testsuite/gdb.base/style.exp
@@ -94,13 +94,13 @@  save_vars { env(TERM) } {
     gdb_test "" "${vers}.*" \
 	"version is styled"
 
-    set address_style_expr [style "\"address\" style" address]
+    set address_style_expr [style ".*\".*address.*\".*style.*" address]
     gdb_test "show style address foreground" \
 	"The ${address_style_expr} foreground color is: blue" \
 	"style name and style word styled using its own style in show style"
 
-    set aliases_expr [style "aliases" title]
-    set breakpoints_expr [style "breakpoints" title]
+    set aliases_expr [style ".*aliases.*" title]
+    set breakpoints_expr [style ".*breakpoints.*" title]
     gdb_test "help" \
 	[multi_line \
 	     "List of classes of commands:" \
@@ -111,8 +111,8 @@  save_vars { env(TERM) } {
 	    ] \
 	"help classes of commands styled with title"
 
-    set taas_expr  [style "taas" title]
-    set tfaas_expr  [style "tfaas" title]
+    set taas_expr  [style ".*taas.*" title]
+    set tfaas_expr  [style ".*tfaas.*" title]
     set cut_for_thre_expr [style "cut for 'thre" highlight]
     gdb_test "apropos -v cut for 'thre" \
 	[multi_line \
diff --git a/gdb/ui-out.c b/gdb/ui-out.c
index e8fe44c8268..8cbaa4e0bc1 100644
--- a/gdb/ui-out.c
+++ b/gdb/ui-out.c
@@ -563,12 +563,190 @@  ui_out::text (const char *string)
 }
 
 void
-ui_out::message (const char *format, ...)
+ui_out::call_do_message (const ui_file_style &style, const char *format,
+			 ...)
 {
   va_list args;
 
   va_start (args, format);
-  do_message (format, args);
+  do_message (style, format, args);
+  va_end (args);
+}
+
+void
+ui_out::vmessage (const ui_file_style &in_style, const char *format,
+		  va_list args)
+{
+  format_pieces fpieces (&format, true);
+
+  ui_file_style style = in_style;
+
+  for (auto &&piece : fpieces)
+    {
+      const char *current_substring = piece.string;
+
+      gdb_assert (piece.n_int_args >= 0 && piece.n_int_args <= 2);
+      int intvals[2] = { 0, 0 };
+      for (int i = 0; i < piece.n_int_args; ++i)
+	intvals[i] = va_arg (args, int);
+
+      /* The only ones we support for now.  */
+      gdb_assert (piece.n_int_args == 0
+		  || piece.argclass == string_arg
+		  || piece.argclass == int_arg
+		  || piece.argclass == long_arg);
+
+      switch (piece.argclass)
+	{
+	case string_arg:
+	  {
+	    const char *str = va_arg (args, const char *);
+	    switch (piece.n_int_args)
+	      {
+	      case 0:
+		call_do_message (style, current_substring, str);
+		break;
+	      case 1:
+		call_do_message (style, current_substring, intvals[0], str);
+		break;
+	      case 2:
+		call_do_message (style, current_substring,
+				 intvals[0], intvals[1], str);
+		break;
+	      }
+	  }
+	  break;
+	case wide_string_arg:
+	  gdb_assert_not_reached (_("wide_string_arg not supported in vmessage"));
+	  break;
+	case wide_char_arg:
+	  gdb_assert_not_reached (_("wide_char_arg not supported in vmessage"));
+	  break;
+	case long_long_arg:
+	  call_do_message (style, current_substring, va_arg (args, long long));
+	  break;
+	case int_arg:
+	  {
+	    int val = va_arg (args, int);
+	    switch (piece.n_int_args)
+	      {
+	      case 0:
+		call_do_message (style, current_substring, val);
+		break;
+	      case 1:
+		call_do_message (style, current_substring, intvals[0], val);
+		break;
+	      case 2:
+		call_do_message (style, current_substring,
+				 intvals[0], intvals[1], val);
+		break;
+	      }
+	  }
+	  break;
+	case long_arg:
+	  {
+	    long val = va_arg (args, long);
+	    switch (piece.n_int_args)
+	      {
+	      case 0:
+		call_do_message (style, current_substring, val);
+		break;
+	      case 1:
+		call_do_message (style, current_substring, intvals[0], val);
+		break;
+	      case 2:
+		call_do_message (style, current_substring,
+				 intvals[0], intvals[1], val);
+		break;
+	      }
+	  }
+	  break;
+	case double_arg:
+	  call_do_message (style, current_substring, va_arg (args, double));
+	  break;
+	case long_double_arg:
+	  gdb_assert_not_reached (_("long_double_arg not supported in vmessage"));
+	  break;
+	case dec32float_arg:
+	  gdb_assert_not_reached (_("dec32float_arg not supported in vmessage"));
+	  break;
+	case dec64float_arg:
+	  gdb_assert_not_reached (_("dec64float_arg not supported in vmessage"));
+	  break;
+	case dec128float_arg:
+	  gdb_assert_not_reached (_("dec128float_arg not supported in vmessage"));
+	  break;
+	case ptr_arg:
+	  switch (current_substring[2])
+	    {
+	    case 'F':
+	      {
+		gdb_assert (!test_flags (disallow_ui_out_field));
+		base_field_s *bf = va_arg (args, base_field_s *);
+		switch (bf->kind)
+		  {
+		  case field_kind::SIGNED:
+		    {
+		      auto *f = (signed_field_s *) bf;
+		      field_signed (f->name, f->val);
+		    }
+		    break;
+		  case field_kind::STRING:
+		    {
+		      auto *f = (string_field_s *) bf;
+		      field_string (f->name, f->str);
+		    }
+		    break;
+		  }
+	      }
+	      break;
+	    case 's':
+	      {
+		styled_string_s *ss = va_arg (args, styled_string_s *);
+		call_do_message (ss->style, "%s", ss->str);
+	      }
+	      break;
+	    case '[':
+	      style = *va_arg (args, const ui_file_style *);
+	      break;
+	    case ']':
+	      {
+		void *arg = va_arg (args, void *);
+		gdb_assert (arg == nullptr);
+
+		style = {};
+	      }
+	      break;
+	    default:
+	      call_do_message (style, current_substring, va_arg (args, void *));
+	      break;
+	    }
+	  break;
+	case literal_piece:
+	  /* Print a portion of the format string that has no
+	     directives.  Note that this will not include any ordinary
+	     %-specs, but it might include "%%".  That is why we use
+	     call_do_message here.  Also, we pass a dummy argument
+	     because some platforms have modified GCC to include
+	     -Wformat-security by default, which will warn here if
+	     there is no argument.  */
+	  call_do_message (style, current_substring, 0);
+	  break;
+	default:
+	  internal_error (__FILE__, __LINE__,
+			  _("failed internal consistency check"));
+	}
+    }
+}
+
+void
+ui_out::message (const char *format, ...)
+{
+  va_list args;
+  va_start (args, format);
+
+  vmessage (ui_file_style (), format, args);
+
   va_end (args);
 }
 
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index 6732f046719..d8adf7b7f0d 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -53,6 +53,12 @@  enum ui_out_flag
 {
   ui_source_list = (1 << 0),
   fix_multi_location_breakpoint_output = (1 << 1),
+  /* For CLI output, this flag is set if unfiltered output is desired.
+     This should only be used by low-level formatting functions.  */
+  unfiltered_output = (1 << 2),
+  /* This indicates that %pF should be disallowed in a printf format
+     string.  */
+  disallow_ui_out_field = (1 << 3)
 };
 
 DEF_ENUM_FLAGS_TYPE (ui_out_flag, ui_out_flags);
@@ -68,6 +74,87 @@  enum ui_out_type
     ui_out_type_list
   };
 
+/* The possible kinds of fields.  */
+enum class field_kind
+  {
+    SIGNED,
+    STRING,
+  };
+
+/* An int field, to be passed to %pF in format strings.  */
+
+struct base_field_s
+{
+  const char *name;
+  field_kind kind;
+};
+
+/* An int field, to be passed to %pF in format strings.  */
+
+struct signed_field_s : base_field_s
+{
+  LONGEST val;
+};
+
+/* Construct a temporary signed_field_s on the caller's stack and
+   return a pointer to the constructed object.  We use this because
+   it's not possible to pass a reference via va_args.  */
+
+static inline signed_field_s *
+signed_field (const char *name, LONGEST val,
+	      signed_field_s &&tmp = {})
+{
+  tmp.name = name;
+  tmp.kind = field_kind::SIGNED;
+  tmp.val = val;
+  return &tmp;
+}
+
+/* A string field, to be passed to %pF in format strings.  */
+
+struct string_field_s : base_field_s
+{
+  const char *str;
+};
+
+/* Construct a temporary string_field_s on the caller's stack and
+   return a pointer to the constructed object.  We use this because
+   it's not possible to pass a reference via va_args.  */
+
+static inline string_field_s *
+string_field (const char *name, const char *str,
+	      string_field_s &&tmp = {})
+{
+  tmp.name = name;
+  tmp.kind = field_kind::STRING;
+  tmp.str = str;
+  return &tmp;
+}
+
+/* A styled string.  */
+
+struct styled_string_s
+{
+  /* The style.  */
+  ui_file_style style;
+
+  /* The string.  */
+  const char *str;
+};
+
+/* Construct a temporary styled_string_s on the caller's stack and
+   return a pointer to the constructed object.  We use this because
+   it's not possible to pass a reference via va_args.  */
+
+static inline styled_string_s *
+styled_string (const ui_file_style &style, const char *str,
+	       styled_string_s &&tmp = {})
+{
+  tmp.style = style;
+  tmp.str = str;
+  return &tmp;
+}
+
 class ui_out
 {
  public:
@@ -110,7 +197,55 @@  class ui_out
 
   void spaces (int numspaces);
   void text (const char *string);
+
+  /* Output a printf-style formatted string.  In addition to the usual
+     printf format specs, this supports a few GDB-specific
+     formatters:
+
+     - '%pF' - output a field.
+
+       The argument is a field, wrapped in any of the base_field_s
+       subclasses.  signed_field for integer fields, styled_field for
+       string fields.  This is preferred over separate
+       uiout->field_int(), uiout_>field_string() etc. calls when the
+       formatted message is translatable.  E.g.:
+
+         uiout->message (_("\nWatchpoint %pF deleted because the program has "
+                         "left the block in\n"
+                         "which its expression is valid.\n"),
+                         signed_field ("wpnum", b->number));
+
+     - '%p[' - output the following text in a specified style.
+       '%p]' - output the following text in the default style.
+
+       The argument to '%p[' is a ui_file_style pointer.  The argument
+       to '%p]' must be nullptr.
+
+       This is useful when you want to output some portion of a string
+       literal in some style.  E.g.:
+
+	 uiout->message (_(" %p[<repeats %u times>%p]"),
+			 metadata_style.style ().ptr (),
+			 reps, repeats, nullptr);
+
+     - '%ps' - output a styled string.
+
+       The argument is the result of a call to styled_string.  This is
+       useful when you want to output some runtime-generated string in
+       some style.  E.g.:
+
+	 uiout->message (_("this is a target address %ps.\n"),
+			 styled_string (address_style.style (),
+					paddress (gdbarch, pc)));
+
+     Note that these all "abuse" the %p printf format spec, in order
+     to be compatible with GCC's printf format checking.  This is OK
+     because code in GDB that wants to print a host address should use
+     host_address_to_string instead of %p.  */
   void message (const char *format, ...) ATTRIBUTE_PRINTF (2, 3);
+  void vmessage (const ui_file_style &in_style,
+		 const char *format, va_list args) ATTRIBUTE_PRINTF (3, 0);
+
   void wrap_hint (const char *identstring);
 
   void flush ();
@@ -161,8 +296,9 @@  class ui_out
     ATTRIBUTE_PRINTF (6,0) = 0;
   virtual void do_spaces (int numspaces) = 0;
   virtual void do_text (const char *string) = 0;
-  virtual void do_message (const char *format, va_list args)
-    ATTRIBUTE_PRINTF (2,0) = 0;
+  virtual void do_message (const ui_file_style &style,
+			   const char *format, va_list args)
+    ATTRIBUTE_PRINTF (3,0) = 0;
   virtual void do_wrap_hint (const char *identstring) = 0;
   virtual void do_flush () = 0;
   virtual void do_redirect (struct ui_file *outstream) = 0;
@@ -174,6 +310,8 @@  class ui_out
   { return false; }
 
  private:
+  void call_do_message (const ui_file_style &style, const char *format,
+			...);
 
   ui_out_flags m_flags;
 
diff --git a/gdb/ui-style.h b/gdb/ui-style.h
index 24b4b59ed9f..d2e9c136263 100644
--- a/gdb/ui-style.h
+++ b/gdb/ui-style.h
@@ -223,6 +223,12 @@  struct ui_file_style
      BUF.  */
   bool parse (const char *buf, size_t *n_read);
 
+  /* We need this because we can't pass a reference via va_args.  */
+  const ui_file_style *ptr () const
+  {
+    return this;
+  }
+
 private:
 
   color m_foreground = NONE;
diff --git a/gdb/unittests/format_pieces-selftests.c b/gdb/unittests/format_pieces-selftests.c
index 862b2da0f48..ed83d9670fd 100644
--- a/gdb/unittests/format_pieces-selftests.c
+++ b/gdb/unittests/format_pieces-selftests.c
@@ -27,9 +27,10 @@  namespace format_pieces {
 /* Verify that parsing STR gives pieces equal to EXPECTED_PIECES.  */
 
 static void
-check (const char *str, const std::vector<format_piece> &expected_pieces)
+check (const char *str, const std::vector<format_piece> &expected_pieces,
+       bool gdb_format = false)
 {
-  ::format_pieces pieces (&str);
+  ::format_pieces pieces (&str, gdb_format);
 
   SELF_CHECK ((pieces.end () - pieces.begin ()) == expected_pieces.size ());
   SELF_CHECK (std::equal (pieces.begin (), pieces.end (),
@@ -41,7 +42,7 @@  test_escape_sequences ()
 {
   check ("This is an escape sequence: \\e",
     {
-      format_piece ("This is an escape sequence: \e", literal_piece),
+      format_piece ("This is an escape sequence: \e", literal_piece, 0),
     });
 }
 
@@ -50,21 +51,37 @@  test_format_specifier ()
 {
   /* The format string here ends with a % sequence, to ensure we don't
      see a trailing empty literal piece.  */
-  check ("Hello %d%llx%%d%d", /* ARI: %ll */
+  check ("Hello\\t %d%llx%%d%d", /* ARI: %ll */
     {
-      format_piece ("Hello ", literal_piece),
-      format_piece ("%d", int_arg),
-      format_piece ("%llx", long_long_arg), /* ARI: %ll */
-      format_piece ("%%d", literal_piece),
-      format_piece ("%d", int_arg),
+      format_piece ("Hello\t ", literal_piece, 0),
+      format_piece ("%d", int_arg, 0),
+      format_piece ("%llx", long_long_arg, 0), /* ARI: %ll */
+      format_piece ("%%d", literal_piece, 0),
+      format_piece ("%d", int_arg, 0),
     });
 }
 
+static void
+test_gdb_formats ()
+{
+  check ("Hello\\t \"%p[%pF%ps%*.*d%p]\"",
+    {
+      format_piece ("Hello\\t \"", literal_piece, 0),
+      format_piece ("%p[", ptr_arg, 0),
+      format_piece ("%pF", ptr_arg, 0),
+      format_piece ("%ps", ptr_arg, 0),
+      format_piece ("%*.*d", int_arg, 2),
+      format_piece ("%p]", ptr_arg, 0),
+      format_piece ("\"", literal_piece, 0),
+    }, true);
+}
+
 static void
 run_tests ()
 {
   test_escape_sequences ();
   test_format_specifier ();
+  test_gdb_formats ();
 }
 
 } /* namespace format_pieces */
diff --git a/gdb/utils.c b/gdb/utils.c
index b7d380073ff..e685cc20847 100644
--- a/gdb/utils.c
+++ b/gdb/utils.c
@@ -73,13 +73,15 @@ 
 #include "cli/cli-style.h"
 #include "gdbsupport/scope-exit.h"
 #include "gdbarch.h"
+#include "cli-out.h"
 
 void (*deprecated_error_begin_hook) (void);
 
 /* Prototypes for local functions */
 
 static void vfprintf_maybe_filtered (struct ui_file *, const char *,
-				     va_list, int) ATTRIBUTE_PRINTF (2, 0);
+				     va_list, bool, bool)
+  ATTRIBUTE_PRINTF (2, 0);
 
 static void fputs_maybe_filtered (const char *, struct ui_file *, int);
 
@@ -1854,6 +1856,24 @@  fputs_styled (const char *linebuffer, const ui_file_style &style,
 
 /* See utils.h.  */
 
+void
+fputs_styled_unfiltered (const char *linebuffer, const ui_file_style &style,
+			 struct ui_file *stream)
+{
+  /* This just makes it so we emit somewhat fewer escape
+     sequences.  */
+  if (style.is_default ())
+    fputs_maybe_filtered (linebuffer, stream, 0);
+  else
+    {
+      set_output_style (stream, style);
+      fputs_maybe_filtered (linebuffer, stream, 0);
+      set_output_style (stream, ui_file_style ());
+    }
+}
+
+/* See utils.h.  */
+
 void
 fputs_highlighted (const char *str, const compiled_regex &highlight,
 		   struct ui_file *stream)
@@ -2021,34 +2041,46 @@  puts_debug (char *prefix, char *string, char *suffix)
    We implement three variants, vfprintf (takes a vararg list and stream),
    fprintf (takes a stream to write on), and printf (the usual).
 
-   Note also that a longjmp to top level may occur in this routine
-   (since prompt_for_continue may do so) so this routine should not be
-   called when cleanups are not in place.  */
+   Note also that this may throw a quit (since prompt_for_continue may
+   do so).  */
 
 static void
 vfprintf_maybe_filtered (struct ui_file *stream, const char *format,
-			 va_list args, int filter)
+			 va_list args, bool filter, bool gdbfmt)
 {
-  std::string linebuffer = string_vprintf (format, args);
-  fputs_maybe_filtered (linebuffer.c_str (), stream, filter);
+  if (gdbfmt)
+    {
+      ui_out_flags flags = disallow_ui_out_field;
+      if (!filter)
+	flags |= unfiltered_output;
+      cli_ui_out (stream, flags).vmessage (applied_style, format, args);
+    }
+  else
+    {
+      std::string str = string_vprintf (format, args);
+      fputs_maybe_filtered (str.c_str (), stream, filter);
+    }
 }
 
 
 void
 vfprintf_filtered (struct ui_file *stream, const char *format, va_list args)
 {
-  vfprintf_maybe_filtered (stream, format, args, 1);
+  vfprintf_maybe_filtered (stream, format, args, true, true);
 }
 
 void
 vfprintf_unfiltered (struct ui_file *stream, const char *format, va_list args)
 {
-  std::string linebuffer = string_vprintf (format, args);
   if (debug_timestamp && stream == gdb_stdlog)
     {
       using namespace std::chrono;
       int len, need_nl;
 
+      string_file sfile;
+      cli_ui_out (&sfile, 0).vmessage (ui_file_style (), format, args);
+      std::string linebuffer = std::move (sfile.string ());
+
       steady_clock::time_point now = steady_clock::now ();
       seconds s = duration_cast<seconds> (now.time_since_epoch ());
       microseconds us = duration_cast<microseconds> (now.time_since_epoch () - s);
@@ -2064,13 +2096,13 @@  vfprintf_unfiltered (struct ui_file *stream, const char *format, va_list args)
       fputs_unfiltered (timestamp.c_str (), stream);
     }
   else
-    fputs_unfiltered (linebuffer.c_str (), stream);
+    vfprintf_maybe_filtered (stream, format, args, false, true);
 }
 
 void
 vprintf_filtered (const char *format, va_list args)
 {
-  vfprintf_maybe_filtered (gdb_stdout, format, args, 1);
+  vfprintf_maybe_filtered (gdb_stdout, format, args, true, false);
 }
 
 void
@@ -2130,6 +2162,33 @@  fprintf_styled (struct ui_file *stream, const ui_file_style &style,
   set_output_style (stream, ui_file_style ());
 }
 
+/* See utils.h.  */
+
+void
+vfprintf_styled (struct ui_file *stream, const ui_file_style &style,
+		 const char *format, va_list args)
+{
+  set_output_style (stream, style);
+  vfprintf_filtered (stream, format, args);
+  set_output_style (stream, ui_file_style ());
+}
+
+/* See utils.h.  */
+
+void
+vfprintf_styled_no_gdbfmt (struct ui_file *stream, const ui_file_style &style,
+			   bool filter, const char *format, va_list args)
+{
+  std::string str = string_vprintf (format, args);
+  if (!str.empty ())
+    {
+      if (!style.is_default ())
+	set_output_style (stream, style);
+      fputs_maybe_filtered (str.c_str (), stream, filter);
+      if (!style.is_default ())
+	set_output_style (stream, ui_file_style ());
+    }
+}
 
 void
 printf_filtered (const char *format, ...)
diff --git a/gdb/utils.h b/gdb/utils.h
index 7df86beec47..76f0da69f71 100644
--- a/gdb/utils.h
+++ b/gdb/utils.h
@@ -350,7 +350,10 @@  extern struct ui_file *gdb_stdtargin;
 extern void set_screen_width_and_height (int width, int height);
 
 /* More generic printf like operations.  Filtered versions may return
-   non-locally on error.  */
+   non-locally on error.  As an extension over plain printf, these
+   support some GDB-specific format specifiers.  Particularly useful
+   here are the styling formatters: '%p[', '%p]' and '%ps'.  See
+   ui_out::message for details.  */
 
 extern void fputs_filtered (const char *, struct ui_file *);
 
@@ -430,6 +433,20 @@  extern void fprintf_styled (struct ui_file *stream,
 			    ...)
   ATTRIBUTE_PRINTF (3, 4);
 
+extern void vfprintf_styled (struct ui_file *stream,
+			     const ui_file_style &style,
+			     const char *fmt,
+			     va_list args)
+  ATTRIBUTE_PRINTF (3, 0);
+
+/* Like vfprintf_styled, but do not process gdb-specific format
+   specifiers.  */
+extern void vfprintf_styled_no_gdbfmt (struct ui_file *stream,
+				       const ui_file_style &style,
+				       bool filter,
+				       const char *fmt, va_list args)
+  ATTRIBUTE_PRINTF (4, 0);
+
 /* Like fputs_filtered, but styles the output according to STYLE, when
    appropriate.  */
 
@@ -437,6 +454,12 @@  extern void fputs_styled (const char *linebuffer,
 			  const ui_file_style &style,
 			  struct ui_file *stream);
 
+/* Unfiltered variant of fputs_styled.  */
+
+extern void fputs_styled_unfiltered (const char *linebuffer,
+				     const ui_file_style &style,
+				     struct ui_file *stream);
+
 /* Like fputs_styled, but uses highlight_style to highlight the
    parts of STR that match HIGHLIGHT.  */