[RFA] Ensure GDB printf command can print convenience var strings without a target.

Message ID 20190610211622.15237-1-philippe.waroquiers@skynet.be
State New
Headers show
Series
  • [RFA] Ensure GDB printf command can print convenience var strings without a target.
Related show

Commit Message

Philippe Waroquiers June 10, 2019, 9:16 p.m.
Without this patch, GDB printf command calls malloc on the target,
writes the convenience var content to the target,
re-reads the content from the target, and then locally printf the string.

This implies inferior calls, and does not work when there is no inferior,
or when the inferior is a core dump.

With this patch, printf command can printf string convenience variables
without inferior function calls.
Ada string convenience variables can also be printed.

gdb/ChangeLog

2019-06-10  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

	* NEWS: Mention that GDB printf and eval commands can now print
	C-style and Ada-style convenience var strings without
	calling the inferior.
	* printcmd.c (printf_c_string): Locally print GDB internal var
	instead of transiting via the inferior.
	(printf_wide_c_string): Likewise.

2019-06-10  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

	* gdb.base/printcmds.exp: Test printing C strings and
	C wide strings convenience var without transiting via the inferior.
---
 gdb/NEWS                             |   7 ++
 gdb/printcmd.c                       | 143 +++++++++++++++++----------
 gdb/testsuite/gdb.base/printcmds.exp |  39 ++++++++
 3 files changed, 136 insertions(+), 53 deletions(-)

-- 
2.20.1

Comments

Eli Zaretskii June 11, 2019, 2:29 a.m. | #1
> From: Philippe Waroquiers <philippe.waroquiers@skynet.be>

> Cc: Philippe Waroquiers <philippe.waroquiers@skynet.be>

> Date: Mon, 10 Jun 2019 23:16:22 +0200

> 

> Without this patch, GDB printf command calls malloc on the target,

> writes the convenience var content to the target,

> re-reads the content from the target, and then locally printf the string.

> 

> This implies inferior calls, and does not work when there is no inferior,

> or when the inferior is a core dump.

> 

> With this patch, printf command can printf string convenience variables

> without inferior function calls.

> Ada string convenience variables can also be printed.

> 

> gdb/ChangeLog

> 

> 2019-06-10  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

> 

> 	* NEWS: Mention that GDB printf and eval commands can now print

> 	C-style and Ada-style convenience var strings without

> 	calling the inferior.

> 	* printcmd.c (printf_c_string): Locally print GDB internal var

> 	instead of transiting via the inferior.

> 	(printf_wide_c_string): Likewise.

> 

> 2019-06-10  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

> 

> 	* gdb.base/printcmds.exp: Test printing C strings and

> 	C wide strings convenience var without transiting via the inferior.

> ---

>  gdb/NEWS                             |   7 ++

>  gdb/printcmd.c                       | 143 +++++++++++++++++----------

>  gdb/testsuite/gdb.base/printcmds.exp |  39 ++++++++

>  3 files changed, 136 insertions(+), 53 deletions(-)


The NEWS part is OK, thanks.
Philippe Waroquiers July 5, 2019, 8:01 p.m. | #2
Ping ?

Thanks

Philippe

On Mon, 2019-06-10 at 23:16 +0200, Philippe Waroquiers wrote:
> Without this patch, GDB printf command calls malloc on the target,

> writes the convenience var content to the target,

> re-reads the content from the target, and then locally printf the string.

> 

> This implies inferior calls, and does not work when there is no inferior,

> or when the inferior is a core dump.

> 

> With this patch, printf command can printf string convenience variables

> without inferior function calls.

> Ada string convenience variables can also be printed.

> 

> gdb/ChangeLog

> 

> 2019-06-10  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

> 

> 	* NEWS: Mention that GDB printf and eval commands can now print

> 	C-style and Ada-style convenience var strings without

> 	calling the inferior.

> 	* printcmd.c (printf_c_string): Locally print GDB internal var

> 	instead of transiting via the inferior.

> 	(printf_wide_c_string): Likewise.

> 

> 2019-06-10  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

> 

> 	* gdb.base/printcmds.exp: Test printing C strings and

> 	C wide strings convenience var without transiting via the inferior.

> ---

>  gdb/NEWS                             |   7 ++

>  gdb/printcmd.c                       | 143 +++++++++++++++++----------

>  gdb/testsuite/gdb.base/printcmds.exp |  39 ++++++++

>  3 files changed, 136 insertions(+), 53 deletions(-)

> 

> diff --git a/gdb/NEWS b/gdb/NEWS

> index 9e1462b6bf..9d6a2de661 100644

> --- a/gdb/NEWS

> +++ b/gdb/NEWS

> @@ -98,6 +98,13 @@ apropos [-v] REGEXP

>    of matching commands and to use the highlight style to mark

>    the documentation parts matching REGEXP.

>  

> +printf

> +eval

> +  The GDB printf and eval commands can now print C-style and Ada-style

> +  convenience variables without calling functions in the program.

> +  This allows to do formatted printing of strings without having

> +  an inferior, or when debugging a core dump.

> +

>  show style

>    The "show style" and its subcommands are now styling

>    a style name in their output using its own style, to help

> diff --git a/gdb/printcmd.c b/gdb/printcmd.c

> index 9e84594fe6..d7b8b9a1c1 100644

> --- a/gdb/printcmd.c

> +++ b/gdb/printcmd.c

> @@ -23,6 +23,7 @@

>  #include "gdbtypes.h"

>  #include "value.h"

>  #include "language.h"

> +#include "c-lang.h"

>  #include "expression.h"

>  #include "gdbcore.h"

>  #include "gdbcmd.h"

> @@ -2200,91 +2201,127 @@ print_variable_and_value (const char *name, struct symbol *var,

>  

>  /* Subroutine of ui_printf to simplify it.

>     Print VALUE to STREAM using FORMAT.

> -   VALUE is a C-style string on the target.  */

> +   VALUE is a C-style string on the target or a C-style string

> +   in a GDB internal variable.  */

>  

>  static void

>  printf_c_string (struct ui_file *stream, const char *format,

>  		 struct value *value)

>  {

> -  gdb_byte *str;

> -  CORE_ADDR tem;

> -  int j;

> +  const gdb_byte *str;

>  

> -  tem = value_as_address (value);

> -  if (tem == 0)

> +  if (VALUE_LVAL (value) == lval_internalvar

> +      && c_is_string_type_p (value_type (value)))

>      {

> -      DIAGNOSTIC_PUSH

> -      DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> -      fprintf_filtered (stream, format, "(null)");

> -      DIAGNOSTIC_POP

> -      return;

> -    }

> +      gdb_byte *tem_str;

> +      size_t len  = TYPE_LENGTH (value_type (value));

>  

> -  /* This is a %s argument.  Find the length of the string.  */

> -  for (j = 0;; j++)

> +      /* Copy the internal var value to tem_str and append a terminating null

> +	 character.  This protects against corrupted C-style strings that lacks

> +	 the terminating null char.  It also allows Ada style strings (not not

> +	 null terminated) to be printed without problems.  */

> +      tem_str = (gdb_byte *) alloca (len + 1);

> +      memcpy (tem_str, value_contents (value), len);

> +      tem_str [len] = 0;

> +      str = tem_str;

> +    }

> +  else

>      {

> -      gdb_byte c;

> +      int len;

> +      CORE_ADDR tem;

> +      gdb_byte *tem_str;

>  

> -      QUIT;

> -      read_memory (tem + j, &c, 1);

> -      if (c == 0)

> -	break;

> -    }

> +      tem = value_as_address (value);

> +      if (tem == 0)

> +	{

> +	  DIAGNOSTIC_PUSH

> +	    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> +	    fprintf_filtered (stream, format, "(null)");

> +	  DIAGNOSTIC_POP

> +	    return;

> +	}

>  

> -  /* Copy the string contents into a string inside GDB.  */

> -  str = (gdb_byte *) alloca (j + 1);

> -  if (j != 0)

> -    read_memory (tem, str, j);

> -  str[j] = 0;

> +      /* This is a %s argument.  Find the length of the string.  */

> +      for (len = 0;; len++)

> +	{

> +	  gdb_byte c;

> +

> +	  QUIT;

> +	  read_memory (tem + len, &c, 1);

> +	  if (c == 0)

> +	    break;

> +	}

> +

> +      /* Copy the string contents into a string inside GDB.  */

> +      tem_str = (gdb_byte *) alloca (len + 1);

> +      if (len != 0)

> +	read_memory (tem, tem_str, len);

> +      tem_str[len] = 0;

> +      str = tem_str;

> +    }

>  

>    DIAGNOSTIC_PUSH

> -  DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> -  fprintf_filtered (stream, format, (char *) str);

> +    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> +    fprintf_filtered (stream, format, (char *) str);

>    DIAGNOSTIC_POP

> -}

> +    }

>  

>  /* Subroutine of ui_printf to simplify it.

>     Print VALUE to STREAM using FORMAT.

> -   VALUE is a wide C-style string on the target.  */

> +   VALUE is a wide C-style string on the target or a wide C-style string

> +   in a GDB internal variable.  */

>  

>  static void

>  printf_wide_c_string (struct ui_file *stream, const char *format,

>  		      struct value *value)

>  {

> -  gdb_byte *str;

> -  CORE_ADDR tem;

> +  const gdb_byte *str;

>    int j;

>    struct gdbarch *gdbarch = get_type_arch (value_type (value));

> -  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);

>    struct type *wctype = lookup_typename (current_language, gdbarch,

>  					 "wchar_t", NULL, 0);

>    int wcwidth = TYPE_LENGTH (wctype);

> -  gdb_byte *buf = (gdb_byte *) alloca (wcwidth);

>  

> -  tem = value_as_address (value);

> -  if (tem == 0)

> +  if (VALUE_LVAL (value) == lval_internalvar

> +      && c_is_string_type_p (value_type (value)))

>      {

> -      DIAGNOSTIC_PUSH

> -      DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> -      fprintf_filtered (stream, format, "(null)");

> -      DIAGNOSTIC_POP

> -      return;

> +      str = value_contents (value);

> +      j = TYPE_LENGTH (value_type (value));

>      }

> -

> -  /* This is a %s argument.  Find the length of the string.  */

> -  for (j = 0;; j += wcwidth)

> +  else

>      {

> -      QUIT;

> -      read_memory (tem + j, buf, wcwidth);

> -      if (extract_unsigned_integer (buf, wcwidth, byte_order) == 0)

> -	break;

> -    }

> +      gdb_byte *tem_str;

> +      CORE_ADDR tem;

> +      gdb_byte *buf = (gdb_byte *) alloca (wcwidth);

>  

> -  /* Copy the string contents into a string inside GDB.  */

> -  str = (gdb_byte *) alloca (j + wcwidth);

> -  if (j != 0)

> -    read_memory (tem, str, j);

> -  memset (&str[j], 0, wcwidth);

> +      enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);

> +

> +      tem = value_as_address (value);

> +      if (tem == 0)

> +	{

> +	  DIAGNOSTIC_PUSH

> +	    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> +	    fprintf_filtered (stream, format, "(null)");

> +	  DIAGNOSTIC_POP

> +	    return;

> +	}

> +

> +      /* This is a %s argument.  Find the length of the string.  */

> +      for (j = 0;; j += wcwidth)

> +	{

> +	  QUIT;

> +	  read_memory (tem + j, buf, wcwidth);

> +	  if (extract_unsigned_integer (buf, wcwidth, byte_order) == 0)

> +	    break;

> +	}

> +

> +      /* Copy the string contents into a string inside GDB.  */

> +      tem_str = (gdb_byte *) alloca (j + wcwidth);

> +      if (j != 0)

> +	read_memory (tem, tem_str, j);

> +      memset (&tem_str[j], 0, wcwidth);

> +      str = tem_str;

> +    }

>  

>    auto_obstack output;

>  

> diff --git a/gdb/testsuite/gdb.base/printcmds.exp b/gdb/testsuite/gdb.base/printcmds.exp

> index f2d6ee229d..3b6562426e 100644

> --- a/gdb/testsuite/gdb.base/printcmds.exp

> +++ b/gdb/testsuite/gdb.base/printcmds.exp

> @@ -932,6 +932,32 @@ proc test_repeat_bytes {} {

>      }

>  }

>  

> +proc test_printf_convenience_var {prefix do_wstring} {

> +

> +    with_test_prefix $prefix {

> +	gdb_test_no_output "set var \$cstr = \"abcde\"" "set \$cstr, conv var"

> +	gdb_test "printf \"cstr val = %s\\n\", \$cstr" "cstr val = abcde" \

> +	    "printf \$cstr, conv var"

> +	gdb_test_no_output "set var \$abcde = \"ABCDE\"" "set \$abcde, conv var"

> +	gdb_test "eval \"print \$%s\\n\", \$cstr" "= \"ABCDE\"" \

> +	    "indirect print abcde"

> +	gdb_test "set language ada" ".*" "set language ada, conv var"

> +	gdb_test_no_output "set var \$astr := \"fghij\"" "set \$astr, conv var"

> +	gdb_test "printf \"astr val = %s\\n\", \$astr" "astr val = fghij" \

> +	    "printf \$astr, conv var"

> +	gdb_test "set language auto" ".*" "set language auto, conv var"

> +	gdb_test "printf \"astr val = %s\\n\", \$astr" "astr val = fghij" \

> +	    "printf \$astr, conv var, auto language"

> +	if ($do_wstring) {

> +	    gdb_test_no_output "set var \$wstr = L\"facile\"" \

> +		"set \$wstr, conv var"

> +	    gdb_test "printf \"wstr val = %ls\\n\", \$wstr" \

> +		"wstr val = facile" "printf \$wstr, conv var"

> +	}

> +    }

> +}

> +

> +

>  # Start with a fresh gdb.

>  

>  gdb_exit

> @@ -948,6 +974,11 @@ gdb_test "ptype \"abc\"" " = char \\\[4\\\]"

>  gdb_test "print \$cvar = \"abc\"" " = \"abc\""

>  gdb_test "print sizeof (\$cvar)" " = 4"

>  

> +# Similarly, printf of convenience var should work without a target.

> +# At this point, we cannot create wide strings convenience var, as the

> +# type wchar_t is not yet known, so skip the wide string tests.

> +test_printf_convenience_var "no target" 0

> +

>  # GDB used to complete the explicit location options even when

>  # printing expressions.

>  gdb_test_no_output "complete p -function"

> @@ -977,6 +1008,14 @@ if ![runto_main] then {

>      return 0

>  }

>  

> +# With a target, printf convenience var should of course work.

> +test_printf_convenience_var "with target" 1

> +

> +# But it should also work when inferior function calls are forbidden.

> +gdb_test_no_output "set may-call-functions off"

> +test_printf_convenience_var "with target, may-call-functions off" 1

> +gdb_test_no_output "set may-call-functions on"

> +

>  test_integer_literals_accepted

>  test_integer_literals_rejected

>  test_float_accepted
Pedro Alves July 8, 2019, 6:12 p.m. | #3
Looks fine to me, with the nits below fixed.

On 6/10/19 10:16 PM, Philippe Waroquiers wrote:
> Without this patch, GDB printf command calls malloc on the target,

> writes the convenience var content to the target,

> re-reads the content from the target, and then locally printf the string.

> 

> This implies inferior calls, and does not work when there is no inferior,

> or when the inferior is a core dump.

> 

> With this patch, printf command can printf string convenience variables

> without inferior function calls.

> Ada string convenience variables can also be printed.

> 

> gdb/ChangeLog

> 

> 2019-06-10  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

> 

> 	* NEWS: Mention that GDB printf and eval commands can now print

> 	C-style and Ada-style convenience var strings without

> 	calling the inferior.

> 	* printcmd.c (printf_c_string): Locally print GDB internal var

> 	instead of transiting via the inferior.

> 	(printf_wide_c_string): Likewise.

> 

> 2019-06-10  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

> 

> 	* gdb.base/printcmds.exp: Test printing C strings and

> 	C wide strings convenience var without transiting via the inferior.

> ---

>  gdb/NEWS                             |   7 ++

>  gdb/printcmd.c                       | 143 +++++++++++++++++----------

>  gdb/testsuite/gdb.base/printcmds.exp |  39 ++++++++

>  3 files changed, 136 insertions(+), 53 deletions(-)

> 

> diff --git a/gdb/NEWS b/gdb/NEWS

> index 9e1462b6bf..9d6a2de661 100644

> --- a/gdb/NEWS

> +++ b/gdb/NEWS

> @@ -98,6 +98,13 @@ apropos [-v] REGEXP

>    of matching commands and to use the highlight style to mark

>    the documentation parts matching REGEXP.

>  

> +printf

> +eval

> +  The GDB printf and eval commands can now print C-style and Ada-style

> +  convenience variables without calling functions in the program.

> +  This allows to do formatted printing of strings without having

> +  an inferior, or when debugging a core dump.


Better say without having a _running_ inferior, since there's
always an inferior.

> +

>  show style

>    The "show style" and its subcommands are now styling

>    a style name in their output using its own style, to help

> diff --git a/gdb/printcmd.c b/gdb/printcmd.c

> index 9e84594fe6..d7b8b9a1c1 100644

> --- a/gdb/printcmd.c

> +++ b/gdb/printcmd.c

> @@ -23,6 +23,7 @@

>  #include "gdbtypes.h"

>  #include "value.h"

>  #include "language.h"

> +#include "c-lang.h"

>  #include "expression.h"

>  #include "gdbcore.h"

>  #include "gdbcmd.h"

> @@ -2200,91 +2201,127 @@ print_variable_and_value (const char *name, struct symbol *var,

>  

>  /* Subroutine of ui_printf to simplify it.

>     Print VALUE to STREAM using FORMAT.

> -   VALUE is a C-style string on the target.  */

> +   VALUE is a C-style string on the target or a C-style string

> +   in a GDB internal variable.  */


You could avoid the repetition:

   VALUE is a C-style string either on the target or in
   a GDB internal variable.  */

>  

>  static void

>  printf_c_string (struct ui_file *stream, const char *format,

>  		 struct value *value)

>  {

> -  gdb_byte *str;

> -  CORE_ADDR tem;

> -  int j;

> +  const gdb_byte *str;

>  

> -  tem = value_as_address (value);

> -  if (tem == 0)

> +  if (VALUE_LVAL (value) == lval_internalvar

> +      && c_is_string_type_p (value_type (value)))

>      {

> -      DIAGNOSTIC_PUSH

> -      DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> -      fprintf_filtered (stream, format, "(null)");

> -      DIAGNOSTIC_POP

> -      return;

> -    }

> +      gdb_byte *tem_str;


You can declare tem_str at the point of initialization.

> +      size_t len  = TYPE_LENGTH (value_type (value));


Spurious double space after len.

>  

> -  /* This is a %s argument.  Find the length of the string.  */

> -  for (j = 0;; j++)

> +      /* Copy the internal var value to tem_str and append a terminating null


TEM_STR

> +	 character.  This protects against corrupted C-style strings that lacks


"strings that lack"

> +	 the terminating null char.  It also allows Ada style strings (not not


"Ada style strings" -> "Ada-style strings"

"not not" -> "not".

> +	 null terminated) to be printed without problems.  */

> +      tem_str = (gdb_byte *) alloca (len + 1);

> +      memcpy (tem_str, value_contents (value), len);

> +      tem_str [len] = 0;

> +      str = tem_str;

> +    }

> +  else

>      {

> -      gdb_byte c;

> +      int len;

> +      CORE_ADDR tem;

> +      gdb_byte *tem_str;


Ditto.


>  

> -      QUIT;

> -      read_memory (tem + j, &c, 1);

> -      if (c == 0)

> -	break;

> -    }

> +      tem = value_as_address (value);

> +      if (tem == 0)

> +	{

> +	  DIAGNOSTIC_PUSH

> +	    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> +	    fprintf_filtered (stream, format, "(null)");

> +	  DIAGNOSTIC_POP


Please align all these on the same column, like it was before.

> +	    return;

> +	}

>  

> -  /* Copy the string contents into a string inside GDB.  */

> -  str = (gdb_byte *) alloca (j + 1);

> -  if (j != 0)

> -    read_memory (tem, str, j);

> -  str[j] = 0;

> +      /* This is a %s argument.  Find the length of the string.  */

> +      for (len = 0;; len++)

> +	{

> +	  gdb_byte c;

> +

> +	  QUIT;

> +	  read_memory (tem + len, &c, 1);

> +	  if (c == 0)

> +	    break;

> +	}

> +

> +      /* Copy the string contents into a string inside GDB.  */

> +      tem_str = (gdb_byte *) alloca (len + 1);

> +      if (len != 0)

> +	read_memory (tem, tem_str, len);

> +      tem_str[len] = 0;

> +      str = tem_str;


I notice this renamed "j" -> "len", but the wide version
did not get the same treatment.

> +    }

>  

>    DIAGNOSTIC_PUSH

> -  DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> -  fprintf_filtered (stream, format, (char *) str);

> +    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> +    fprintf_filtered (stream, format, (char *) str);

>    DIAGNOSTIC_POP


Ditto.

> -}

> +    }

>  

>  /* Subroutine of ui_printf to simplify it.

>     Print VALUE to STREAM using FORMAT.

> -   VALUE is a wide C-style string on the target.  */

> +   VALUE is a wide C-style string on the target or a wide C-style string

> +   in a GDB internal variable.  */


Same comments as in the non-wide version apply.

>  

>  static void

>  printf_wide_c_string (struct ui_file *stream, const char *format,

>  		      struct value *value)

>  {

> -  gdb_byte *str;

> -  CORE_ADDR tem;

> +  const gdb_byte *str;

>    int j;

>    struct gdbarch *gdbarch = get_type_arch (value_type (value));

> -  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);

>    struct type *wctype = lookup_typename (current_language, gdbarch,

>  					 "wchar_t", NULL, 0);

>    int wcwidth = TYPE_LENGTH (wctype);

> -  gdb_byte *buf = (gdb_byte *) alloca (wcwidth);

>  

> -  tem = value_as_address (value);

> -  if (tem == 0)

> +  if (VALUE_LVAL (value) == lval_internalvar

> +      && c_is_string_type_p (value_type (value)))

>      {

> -      DIAGNOSTIC_PUSH

> -      DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> -      fprintf_filtered (stream, format, "(null)");

> -      DIAGNOSTIC_POP

> -      return;

> +      str = value_contents (value);

> +      j = TYPE_LENGTH (value_type (value));

>      }

> -

> -  /* This is a %s argument.  Find the length of the string.  */

> -  for (j = 0;; j += wcwidth)

> +  else

>      {

> -      QUIT;

> -      read_memory (tem + j, buf, wcwidth);

> -      if (extract_unsigned_integer (buf, wcwidth, byte_order) == 0)

> -	break;

> -    }

> +      gdb_byte *tem_str;

> +      CORE_ADDR tem;

> +      gdb_byte *buf = (gdb_byte *) alloca (wcwidth);

>  

> -  /* Copy the string contents into a string inside GDB.  */

> -  str = (gdb_byte *) alloca (j + wcwidth);

> -  if (j != 0)

> -    read_memory (tem, str, j);

> -  memset (&str[j], 0, wcwidth);

> +      enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);


You could move this after the following if block, since byte_order
won't be needed until then.

> +

> +      tem = value_as_address (value);

> +      if (tem == 0)

> +	{

> +	  DIAGNOSTIC_PUSH

> +	    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL

> +	    fprintf_filtered (stream, format, "(null)");

> +	  DIAGNOSTIC_POP

> +	    return;

> +	}

> +

> +      /* This is a %s argument.  Find the length of the string.  */

> +      for (j = 0;; j += wcwidth)

> +	{

> +	  QUIT;

> +	  read_memory (tem + j, buf, wcwidth);

> +	  if (extract_unsigned_integer (buf, wcwidth, byte_order) == 0)

> +	    break;

> +	}

> +

> +      /* Copy the string contents into a string inside GDB.  */

> +      tem_str = (gdb_byte *) alloca (j + wcwidth);

> +      if (j != 0)

> +	read_memory (tem, tem_str, j);

> +      memset (&tem_str[j], 0, wcwidth);

> +      str = tem_str;

> +    }

>  

>    auto_obstack output;

>  

> diff --git a/gdb/testsuite/gdb.base/printcmds.exp b/gdb/testsuite/gdb.base/printcmds.exp

> index f2d6ee229d..3b6562426e 100644

> --- a/gdb/testsuite/gdb.base/printcmds.exp

> +++ b/gdb/testsuite/gdb.base/printcmds.exp

> @@ -932,6 +932,32 @@ proc test_repeat_bytes {} {

>      }

>  }

>  

> +proc test_printf_convenience_var {prefix do_wstring} {


Needs an intro comment.

> +

> +    with_test_prefix $prefix {

> +	gdb_test_no_output "set var \$cstr = \"abcde\"" "set \$cstr, conv var"

> +	gdb_test "printf \"cstr val = %s\\n\", \$cstr" "cstr val = abcde" \

> +	    "printf \$cstr, conv var"

> +	gdb_test_no_output "set var \$abcde = \"ABCDE\"" "set \$abcde, conv var"

> +	gdb_test "eval \"print \$%s\\n\", \$cstr" "= \"ABCDE\"" \

> +	    "indirect print abcde"


Missing ", conv var" ?  But see below.

> +	gdb_test "set language ada" ".*" "set language ada, conv var"


gdb_test_no_output ?

> +	gdb_test_no_output "set var \$astr := \"fghij\"" "set \$astr, conv var"

> +	gdb_test "printf \"astr val = %s\\n\", \$astr" "astr val = fghij" \

> +	    "printf \$astr, conv var"

> +	gdb_test "set language auto" ".*" "set language auto, conv var"


gdb_test_no_output ?


> +	gdb_test "printf \"astr val = %s\\n\", \$astr" "astr val = fghij" \

> +	    "printf \$astr, conv var, auto language"

> +	if ($do_wstring) {


Use {} instead of ().

> +	    gdb_test_no_output "set var \$wstr = L\"facile\"" \

> +		"set \$wstr, conv var"

> +	    gdb_test "printf \"wstr val = %ls\\n\", \$wstr" \

> +		"wstr val = facile" "printf \$wstr, conv var"

> +	}


All these "conv var" in the test names seem redundant, given the whole
proc body is wrapped in with_test_prefix.  How about replacing all
that with:

 -    with_test_prefix $prefix {
 +    with_test_prefix "conv var: $prefix" {

> +    }

> +}

> +

> +

>  # Start with a fresh gdb.

>  

>  gdb_exit

> @@ -948,6 +974,11 @@ gdb_test "ptype \"abc\"" " = char \\\[4\\\]"

>  gdb_test "print \$cvar = \"abc\"" " = \"abc\""

>  gdb_test "print sizeof (\$cvar)" " = 4"

>  

> +# Similarly, printf of convenience var should work without a target.


"of convenience var" -> "of a convenience var" or "of convenience vars".

Or maybe even: 

  printf of a string convenience var

> +# At this point, we cannot create wide strings convenience var, as the

> +# type wchar_t is not yet known, so skip the wide string tests.


"create wide strings convenience var" -> "create a wide string convenience var"

"wchar_t type" -> "wchar_t type"

> +test_printf_convenience_var "no target" 0

> +

>  # GDB used to complete the explicit location options even when

>  # printing expressions.

>  gdb_test_no_output "complete p -function"

> @@ -977,6 +1008,14 @@ if ![runto_main] then {

>      return 0

>  }

>  

> +# With a target, printf convenience var should of course work.


"With a running target"

"printf convenience vars"

> +test_printf_convenience_var "with target" 1

> +

> +# But it should also work when inferior function calls are forbidden.


"But it" -> "It".

> +gdb_test_no_output "set may-call-functions off"

> +test_printf_convenience_var "with target, may-call-functions off" 1

> +gdb_test_no_output "set may-call-functions on"

> +

>  test_integer_literals_accepted

>  test_integer_literals_rejected

>  test_float_accepted

> 


Thanks,
Pedro Alves
Philippe Waroquiers July 8, 2019, 10:41 p.m. | #4
On Mon, 2019-07-08 at 19:12 +0100, Pedro Alves wrote:
> Looks fine to me, with the nits below fixed.

Thanks.  I have applied all the suggested changes (except one)
and pushed the below patch as a result.

I kept 
  gdb_test "set language ada" ".*" "set language ada"
and clarified why with:
+	# Without a target, the below produces no output
+	# but with a target, it gives a warning.
+	# So, use gdb_test expecting ".*" instead of gdb_test_no_output.
+	gdb_test "set language ada" ".*" "set language ada"


Thanks for the review.

Philippe


From 1f6f6e21fa86dc3411a6498608f32e9eb24b7851 Mon Sep 17 00:00:00 2001
From: Philippe Waroquiers <philippe.waroquiers@skynet.be>

Date: Mon, 10 Jun 2019 21:41:51 +0200
Subject: [PATCH] Ensure GDB printf command can print convenience var strings
 without a target.

Without this patch, GDB printf command calls malloc on the target,
writes the convenience var content to the target,
re-reads the content from the target, and then locally printf the string.

This implies inferior calls, and does not work when there is no running
inferior, or when the inferior is a core dump.

With this patch, printf command can printf string convenience variables
without inferior function calls.
Ada string convenience variables can also be printed.

gdb/ChangeLog
2019-07-08  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

	* NEWS: Mention that GDB printf and eval commands can now print
	C-style and Ada-style convenience var strings without
	calling the inferior.
	* printcmd.c (printf_c_string): Locally print GDB internal var
	instead of transiting via the inferior.
	(printf_wide_c_string): Likewise.

gdb/testsuite/ChangeLog
2019-07-08  Philippe Waroquiers  <philippe.waroquiers@skynet.be>

	* gdb.base/printcmds.exp: Test printing C string and
	C wide string convenience vars without transiting via the inferior.
	Also make test names unique.
---
 gdb/ChangeLog                        |  11 ++-
 gdb/NEWS                             |   7 ++
 gdb/printcmd.c                       | 140 +++++++++++++++++----------
 gdb/testsuite/ChangeLog              |   6 ++
 gdb/testsuite/gdb.base/printcmds.exp |  59 ++++++++++-
 5 files changed, 165 insertions(+), 58 deletions(-)

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 218bbf6223..2f406ae2bd 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,4 +1,13 @@
-2019-08-04  Alan Hayward  <alan.hayward@arm.com>
+2019-07-08  Philippe Waroquiers  <philippe.waroquiers@skynet.be>
+
+	* NEWS: Mention that GDB printf and eval commands can now print
+	C-style and Ada-style convenience var strings without
+	calling the inferior.
+	* printcmd.c (printf_c_string): Locally print GDB internal var
+	instead of transiting via the inferior.
+	(printf_wide_c_string): Likewise.
+
+2019-07-04  Alan Hayward  <alan.hayward@arm.com>
 
 	* symfile.c (symbol_file_command): Call solib_create_inferior_hook.
 
diff --git a/gdb/NEWS b/gdb/NEWS
index 34c544c3d5..f7b6b88a22 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -118,6 +118,13 @@ apropos [-v] REGEXP
   of matching commands and to use the highlight style to mark
   the documentation parts matching REGEXP.
 
+printf
+eval
+  The GDB printf and eval commands can now print C-style and Ada-style
+  string convenience variables without calling functions in the program.
+  This allows to do formatted printing of strings without having
+  a running inferior, or when debugging a core dump.
+
 show style
   The "show style" and its subcommands are now styling
   a style name in their output using its own style, to help
diff --git a/gdb/printcmd.c b/gdb/printcmd.c
index 0509360581..714a2e981e 100644
--- a/gdb/printcmd.c
+++ b/gdb/printcmd.c
@@ -23,6 +23,7 @@
 #include "gdbtypes.h"
 #include "value.h"
 #include "language.h"
+#include "c-lang.h"
 #include "expression.h"
 #include "gdbcore.h"
 #include "gdbcmd.h"
@@ -2222,42 +2223,64 @@ print_variable_and_value (const char *name, struct symbol *var,
 
 /* Subroutine of ui_printf to simplify it.
    Print VALUE to STREAM using FORMAT.
-   VALUE is a C-style string on the target.  */
+   VALUE is a C-style string either on the target or
+   in a GDB internal variable.  */
 
 static void
 printf_c_string (struct ui_file *stream, const char *format,
 		 struct value *value)
 {
-  gdb_byte *str;
-  CORE_ADDR tem;
-  int j;
+  const gdb_byte *str;
 
-  tem = value_as_address (value);
-  if (tem == 0)
+  if (VALUE_LVAL (value) == lval_internalvar
+      && c_is_string_type_p (value_type (value)))
     {
-      DIAGNOSTIC_PUSH
-      DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
-      fprintf_filtered (stream, format, "(null)");
-      DIAGNOSTIC_POP
-      return;
-    }
+      size_t len = TYPE_LENGTH (value_type (value));
 
-  /* This is a %s argument.  Find the length of the string.  */
-  for (j = 0;; j++)
-    {
-      gdb_byte c;
+      /* Copy the internal var value to TEM_STR and append a terminating null
+	 character.  This protects against corrupted C-style strings that lack
+	 the terminating null char.  It also allows Ada-style strings (not
+	 null terminated) to be printed without problems.  */
+      gdb_byte *tem_str = (gdb_byte *) alloca (len + 1);
 
-      QUIT;
-      read_memory (tem + j, &c, 1);
-      if (c == 0)
-	break;
+      memcpy (tem_str, value_contents (value), len);
+      tem_str [len] = 0;
+      str = tem_str;
     }
+  else
+    {
+      CORE_ADDR tem = value_as_address (value);;
+
+      if (tem == 0)
+	{
+	  DIAGNOSTIC_PUSH
+	  DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
+	  fprintf_filtered (stream, format, "(null)");
+	  DIAGNOSTIC_POP
+	  return;
+	}
+
+      /* This is a %s argument.  Find the length of the string.  */
+      size_t len;
+
+      for (len = 0;; len++)
+	{
+	  gdb_byte c;
 
-  /* Copy the string contents into a string inside GDB.  */
-  str = (gdb_byte *) alloca (j + 1);
-  if (j != 0)
-    read_memory (tem, str, j);
-  str[j] = 0;
+	  QUIT;
+	  read_memory (tem + len, &c, 1);
+	  if (c == 0)
+	    break;
+	}
+
+      /* Copy the string contents into a string inside GDB.  */
+      gdb_byte *tem_str = (gdb_byte *) alloca (len + 1);
+
+      if (len != 0)
+	read_memory (tem, tem_str, len);
+      tem_str[len] = 0;
+      str = tem_str;
+    }
 
   DIAGNOSTIC_PUSH
   DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
@@ -2267,52 +2290,65 @@ printf_c_string (struct ui_file *stream, const char *format,
 
 /* Subroutine of ui_printf to simplify it.
    Print VALUE to STREAM using FORMAT.
-   VALUE is a wide C-style string on the target.  */
+   VALUE is a wide C-style string on the target or
+   in a GDB internal variable.  */
 
 static void
 printf_wide_c_string (struct ui_file *stream, const char *format,
 		      struct value *value)
 {
-  gdb_byte *str;
-  CORE_ADDR tem;
-  int j;
+  const gdb_byte *str;
+  size_t len;
   struct gdbarch *gdbarch = get_type_arch (value_type (value));
-  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
   struct type *wctype = lookup_typename (current_language, gdbarch,
 					 "wchar_t", NULL, 0);
   int wcwidth = TYPE_LENGTH (wctype);
-  gdb_byte *buf = (gdb_byte *) alloca (wcwidth);
 
-  tem = value_as_address (value);
-  if (tem == 0)
+  if (VALUE_LVAL (value) == lval_internalvar
+      && c_is_string_type_p (value_type (value)))
     {
-      DIAGNOSTIC_PUSH
-      DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
-      fprintf_filtered (stream, format, "(null)");
-      DIAGNOSTIC_POP
-      return;
+      str = value_contents (value);
+      len = TYPE_LENGTH (value_type (value));
     }
-
-  /* This is a %s argument.  Find the length of the string.  */
-  for (j = 0;; j += wcwidth)
+  else
     {
-      QUIT;
-      read_memory (tem + j, buf, wcwidth);
-      if (extract_unsigned_integer (buf, wcwidth, byte_order) == 0)
-	break;
-    }
+      CORE_ADDR tem = value_as_address (value);
 
-  /* Copy the string contents into a string inside GDB.  */
-  str = (gdb_byte *) alloca (j + wcwidth);
-  if (j != 0)
-    read_memory (tem, str, j);
-  memset (&str[j], 0, wcwidth);
+      if (tem == 0)
+	{
+	  DIAGNOSTIC_PUSH
+	  DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
+	  fprintf_filtered (stream, format, "(null)");
+	  DIAGNOSTIC_POP
+	  return;
+	}
+
+      /* This is a %s argument.  Find the length of the string.  */
+      enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+      gdb_byte *buf = (gdb_byte *) alloca (wcwidth);
+
+      for (len = 0;; len += wcwidth)
+	{
+	  QUIT;
+	  read_memory (tem + len, buf, wcwidth);
+	  if (extract_unsigned_integer (buf, wcwidth, byte_order) == 0)
+	    break;
+	}
+
+      /* Copy the string contents into a string inside GDB.  */
+      gdb_byte *tem_str = (gdb_byte *) alloca (len + wcwidth);
+
+      if (len != 0)
+	read_memory (tem, tem_str, len);
+      memset (&tem_str[len], 0, wcwidth);
+      str = tem_str;
+    }
 
   auto_obstack output;
 
   convert_between_encodings (target_wide_charset (gdbarch),
 			     host_charset (),
-			     str, j, wcwidth,
+			     str, len, wcwidth,
 			     &output, translit_char);
   obstack_grow_str0 (&output, "");
 
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index a6d6843d96..f8ef540b4e 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,9 @@
+2019-07-08  Philippe Waroquiers  <philippe.waroquiers@skynet.be>
+
+	* gdb.base/printcmds.exp: Test printing C string and
+	C wide string convenience vars without transiting via the inferior.
+	Also make test names unique.
+
 2019-07-08  Alan Hayward  <alan.hayward@arm.com>
 
 	* gdb.base/break-idempotent.exp: Test both PIE and non PIE.
diff --git a/gdb/testsuite/gdb.base/printcmds.exp b/gdb/testsuite/gdb.base/printcmds.exp
index f2d6ee229d..0e3126bcf2 100644
--- a/gdb/testsuite/gdb.base/printcmds.exp
+++ b/gdb/testsuite/gdb.base/printcmds.exp
@@ -438,7 +438,7 @@ proc test_print_repeats_10 {} {
     global gdb_prompt decimal
 
     for { set x 1 } { $x <= 16 } { incr x } {
-	gdb_test_no_output "set print elements $x"
+	gdb_test_no_output "set print elements $x" "elements $x repeats"
 	for { set e 1 } { $e <= 16 } {incr e } {
 	    set v [expr $e - 1]
 	    set command "p &ctable2\[${v}*16\]"
@@ -596,7 +596,7 @@ proc test_print_strings {} {
 proc test_print_int_arrays {} {
     global gdb_prompt
 
-    gdb_test_no_output "set print elements 24"
+    gdb_test_no_output "set print elements 24" "elements 24 int arrays"
 
     gdb_test_escape_braces "p int1dim" \
 	" = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}"
@@ -621,7 +621,7 @@ proc test_print_int_arrays {} {
 proc test_print_typedef_arrays {} {
     global gdb_prompt
 
-    gdb_test_no_output "set print elements 24"
+    gdb_test_no_output "set print elements 24" "elements 24 typedef_arrays"
 
     gdb_test_escape_braces "p a1" \
 	" = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}"
@@ -666,7 +666,7 @@ proc test_print_char_arrays {} {
     global gdb_prompt
     global hex decimal
 
-    gdb_test_no_output "set print elements 24"
+    gdb_test_no_output "set print elements 24" "elements 24 char_arrays"
     gdb_test_no_output "set print address on"
 
     gdb_test "p arrays" \
@@ -684,7 +684,7 @@ proc test_print_char_arrays {} {
     gdb_test "p parrays->array5"	" = \"hij\""
     gdb_test "p &parrays->array5"	" = \\(unsigned char \\(\\*\\)\\\[4\\\]\\) $hex <arrays\\+$decimal>"
 
-    gdb_test_no_output "set print address off"
+    gdb_test_no_output "set print address off" "address off char arrays"
 }
 
 proc test_print_string_constants {} {
@@ -932,6 +932,42 @@ proc test_repeat_bytes {} {
     }
 }
 
+# Test printf of convenience variables.
+# These tests can be done with or without a running inferior.
+# PREFIX ensures uniqueness of test names.
+# DO_WSTRING 1 tells to test printf of wide strings.  Wide strings tests
+# must be skipped (DO_WSTRING 0) if the wchar_t type is not yet known by
+# GDB, as this type is needed to create wide strings.
+
+proc test_printf_convenience_var {prefix do_wstring} {
+
+    with_test_prefix "conv var: $prefix" {
+	gdb_test_no_output "set var \$cstr = \"abcde\"" "set \$cstr"
+	gdb_test "printf \"cstr val = %s\\n\", \$cstr" "cstr val = abcde" \
+	    "printf \$cstr"
+	gdb_test_no_output "set var \$abcde = \"ABCDE\"" "set \$abcde"
+	gdb_test "eval \"print \$%s\\n\", \$cstr" "= \"ABCDE\"" \
+	    "indirect print abcde"
+	# Without a target, the below produces no output
+	# but with a target, it gives a warning.
+	# So, use gdb_test expecting ".*" instead of gdb_test_no_output.
+	gdb_test "set language ada" ".*" "set language ada"
+	gdb_test_no_output "set var \$astr := \"fghij\"" "set \$astr"
+	gdb_test "printf \"astr val = %s\\n\", \$astr" "astr val = fghij" \
+	    "printf \$astr"
+	gdb_test_no_output "set language auto" "set language auto"
+	gdb_test "printf \"astr val = %s\\n\", \$astr" "astr val = fghij" \
+	    "printf \$astr, auto language"
+	if {$do_wstring} {
+	    gdb_test_no_output "set var \$wstr = L\"facile\"" \
+		"set \$wstr"
+	    gdb_test "printf \"wstr val = %ls\\n\", \$wstr" \
+		"wstr val = facile" "printf \$wstr"
+	}
+    }
+}
+
+
 # Start with a fresh gdb.
 
 gdb_exit
@@ -948,6 +984,11 @@ gdb_test "ptype \"abc\"" " = char \\\[4\\\]"
 gdb_test "print \$cvar = \"abc\"" " = \"abc\""
 gdb_test "print sizeof (\$cvar)" " = 4"
 
+# Similarly, printf of a string convenience var should work without a target.
+# At this point, we cannot create a wide string convenience var, as the
+# wchar_t type is not yet known, so skip the wide string tests.
+test_printf_convenience_var "no target" 0
+
 # GDB used to complete the explicit location options even when
 # printing expressions.
 gdb_test_no_output "complete p -function"
@@ -977,6 +1018,14 @@ if ![runto_main] then {
     return 0
 }
 
+# With a running target, printf convenience vars should of course work.
+test_printf_convenience_var "with target" 1
+
+# It should also work when inferior function calls are forbidden.
+gdb_test_no_output "set may-call-functions off"
+test_printf_convenience_var "with target, may-call-functions off" 1
+gdb_test_no_output "set may-call-functions on"
+
 test_integer_literals_accepted
 test_integer_literals_rejected
 test_float_accepted
-- 
2.20.1

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 9e1462b6bf..9d6a2de661 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -98,6 +98,13 @@  apropos [-v] REGEXP
   of matching commands and to use the highlight style to mark
   the documentation parts matching REGEXP.
 
+printf
+eval
+  The GDB printf and eval commands can now print C-style and Ada-style
+  convenience variables without calling functions in the program.
+  This allows to do formatted printing of strings without having
+  an inferior, or when debugging a core dump.
+
 show style
   The "show style" and its subcommands are now styling
   a style name in their output using its own style, to help
diff --git a/gdb/printcmd.c b/gdb/printcmd.c
index 9e84594fe6..d7b8b9a1c1 100644
--- a/gdb/printcmd.c
+++ b/gdb/printcmd.c
@@ -23,6 +23,7 @@ 
 #include "gdbtypes.h"
 #include "value.h"
 #include "language.h"
+#include "c-lang.h"
 #include "expression.h"
 #include "gdbcore.h"
 #include "gdbcmd.h"
@@ -2200,91 +2201,127 @@  print_variable_and_value (const char *name, struct symbol *var,
 
 /* Subroutine of ui_printf to simplify it.
    Print VALUE to STREAM using FORMAT.
-   VALUE is a C-style string on the target.  */
+   VALUE is a C-style string on the target or a C-style string
+   in a GDB internal variable.  */
 
 static void
 printf_c_string (struct ui_file *stream, const char *format,
 		 struct value *value)
 {
-  gdb_byte *str;
-  CORE_ADDR tem;
-  int j;
+  const gdb_byte *str;
 
-  tem = value_as_address (value);
-  if (tem == 0)
+  if (VALUE_LVAL (value) == lval_internalvar
+      && c_is_string_type_p (value_type (value)))
     {
-      DIAGNOSTIC_PUSH
-      DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
-      fprintf_filtered (stream, format, "(null)");
-      DIAGNOSTIC_POP
-      return;
-    }
+      gdb_byte *tem_str;
+      size_t len  = TYPE_LENGTH (value_type (value));
 
-  /* This is a %s argument.  Find the length of the string.  */
-  for (j = 0;; j++)
+      /* Copy the internal var value to tem_str and append a terminating null
+	 character.  This protects against corrupted C-style strings that lacks
+	 the terminating null char.  It also allows Ada style strings (not not
+	 null terminated) to be printed without problems.  */
+      tem_str = (gdb_byte *) alloca (len + 1);
+      memcpy (tem_str, value_contents (value), len);
+      tem_str [len] = 0;
+      str = tem_str;
+    }
+  else
     {
-      gdb_byte c;
+      int len;
+      CORE_ADDR tem;
+      gdb_byte *tem_str;
 
-      QUIT;
-      read_memory (tem + j, &c, 1);
-      if (c == 0)
-	break;
-    }
+      tem = value_as_address (value);
+      if (tem == 0)
+	{
+	  DIAGNOSTIC_PUSH
+	    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
+	    fprintf_filtered (stream, format, "(null)");
+	  DIAGNOSTIC_POP
+	    return;
+	}
 
-  /* Copy the string contents into a string inside GDB.  */
-  str = (gdb_byte *) alloca (j + 1);
-  if (j != 0)
-    read_memory (tem, str, j);
-  str[j] = 0;
+      /* This is a %s argument.  Find the length of the string.  */
+      for (len = 0;; len++)
+	{
+	  gdb_byte c;
+
+	  QUIT;
+	  read_memory (tem + len, &c, 1);
+	  if (c == 0)
+	    break;
+	}
+
+      /* Copy the string contents into a string inside GDB.  */
+      tem_str = (gdb_byte *) alloca (len + 1);
+      if (len != 0)
+	read_memory (tem, tem_str, len);
+      tem_str[len] = 0;
+      str = tem_str;
+    }
 
   DIAGNOSTIC_PUSH
-  DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
-  fprintf_filtered (stream, format, (char *) str);
+    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
+    fprintf_filtered (stream, format, (char *) str);
   DIAGNOSTIC_POP
-}
+    }
 
 /* Subroutine of ui_printf to simplify it.
    Print VALUE to STREAM using FORMAT.
-   VALUE is a wide C-style string on the target.  */
+   VALUE is a wide C-style string on the target or a wide C-style string
+   in a GDB internal variable.  */
 
 static void
 printf_wide_c_string (struct ui_file *stream, const char *format,
 		      struct value *value)
 {
-  gdb_byte *str;
-  CORE_ADDR tem;
+  const gdb_byte *str;
   int j;
   struct gdbarch *gdbarch = get_type_arch (value_type (value));
-  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
   struct type *wctype = lookup_typename (current_language, gdbarch,
 					 "wchar_t", NULL, 0);
   int wcwidth = TYPE_LENGTH (wctype);
-  gdb_byte *buf = (gdb_byte *) alloca (wcwidth);
 
-  tem = value_as_address (value);
-  if (tem == 0)
+  if (VALUE_LVAL (value) == lval_internalvar
+      && c_is_string_type_p (value_type (value)))
     {
-      DIAGNOSTIC_PUSH
-      DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
-      fprintf_filtered (stream, format, "(null)");
-      DIAGNOSTIC_POP
-      return;
+      str = value_contents (value);
+      j = TYPE_LENGTH (value_type (value));
     }
-
-  /* This is a %s argument.  Find the length of the string.  */
-  for (j = 0;; j += wcwidth)
+  else
     {
-      QUIT;
-      read_memory (tem + j, buf, wcwidth);
-      if (extract_unsigned_integer (buf, wcwidth, byte_order) == 0)
-	break;
-    }
+      gdb_byte *tem_str;
+      CORE_ADDR tem;
+      gdb_byte *buf = (gdb_byte *) alloca (wcwidth);
 
-  /* Copy the string contents into a string inside GDB.  */
-  str = (gdb_byte *) alloca (j + wcwidth);
-  if (j != 0)
-    read_memory (tem, str, j);
-  memset (&str[j], 0, wcwidth);
+      enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+
+      tem = value_as_address (value);
+      if (tem == 0)
+	{
+	  DIAGNOSTIC_PUSH
+	    DIAGNOSTIC_IGNORE_FORMAT_NONLITERAL
+	    fprintf_filtered (stream, format, "(null)");
+	  DIAGNOSTIC_POP
+	    return;
+	}
+
+      /* This is a %s argument.  Find the length of the string.  */
+      for (j = 0;; j += wcwidth)
+	{
+	  QUIT;
+	  read_memory (tem + j, buf, wcwidth);
+	  if (extract_unsigned_integer (buf, wcwidth, byte_order) == 0)
+	    break;
+	}
+
+      /* Copy the string contents into a string inside GDB.  */
+      tem_str = (gdb_byte *) alloca (j + wcwidth);
+      if (j != 0)
+	read_memory (tem, tem_str, j);
+      memset (&tem_str[j], 0, wcwidth);
+      str = tem_str;
+    }
 
   auto_obstack output;
 
diff --git a/gdb/testsuite/gdb.base/printcmds.exp b/gdb/testsuite/gdb.base/printcmds.exp
index f2d6ee229d..3b6562426e 100644
--- a/gdb/testsuite/gdb.base/printcmds.exp
+++ b/gdb/testsuite/gdb.base/printcmds.exp
@@ -932,6 +932,32 @@  proc test_repeat_bytes {} {
     }
 }
 
+proc test_printf_convenience_var {prefix do_wstring} {
+
+    with_test_prefix $prefix {
+	gdb_test_no_output "set var \$cstr = \"abcde\"" "set \$cstr, conv var"
+	gdb_test "printf \"cstr val = %s\\n\", \$cstr" "cstr val = abcde" \
+	    "printf \$cstr, conv var"
+	gdb_test_no_output "set var \$abcde = \"ABCDE\"" "set \$abcde, conv var"
+	gdb_test "eval \"print \$%s\\n\", \$cstr" "= \"ABCDE\"" \
+	    "indirect print abcde"
+	gdb_test "set language ada" ".*" "set language ada, conv var"
+	gdb_test_no_output "set var \$astr := \"fghij\"" "set \$astr, conv var"
+	gdb_test "printf \"astr val = %s\\n\", \$astr" "astr val = fghij" \
+	    "printf \$astr, conv var"
+	gdb_test "set language auto" ".*" "set language auto, conv var"
+	gdb_test "printf \"astr val = %s\\n\", \$astr" "astr val = fghij" \
+	    "printf \$astr, conv var, auto language"
+	if ($do_wstring) {
+	    gdb_test_no_output "set var \$wstr = L\"facile\"" \
+		"set \$wstr, conv var"
+	    gdb_test "printf \"wstr val = %ls\\n\", \$wstr" \
+		"wstr val = facile" "printf \$wstr, conv var"
+	}
+    }
+}
+
+
 # Start with a fresh gdb.
 
 gdb_exit
@@ -948,6 +974,11 @@  gdb_test "ptype \"abc\"" " = char \\\[4\\\]"
 gdb_test "print \$cvar = \"abc\"" " = \"abc\""
 gdb_test "print sizeof (\$cvar)" " = 4"
 
+# Similarly, printf of convenience var should work without a target.
+# At this point, we cannot create wide strings convenience var, as the
+# type wchar_t is not yet known, so skip the wide string tests.
+test_printf_convenience_var "no target" 0
+
 # GDB used to complete the explicit location options even when
 # printing expressions.
 gdb_test_no_output "complete p -function"
@@ -977,6 +1008,14 @@  if ![runto_main] then {
     return 0
 }
 
+# With a target, printf convenience var should of course work.
+test_printf_convenience_var "with target" 1
+
+# But it should also work when inferior function calls are forbidden.
+gdb_test_no_output "set may-call-functions off"
+test_printf_convenience_var "with target, may-call-functions off" 1
+gdb_test_no_output "set may-call-functions on"
+
 test_integer_literals_accepted
 test_integer_literals_rejected
 test_float_accepted