[2/2] gdb: Introduce 'print max-depth' feature

Message ID b0991d2905ba6291911824ffd20fc646e0eb5e00.1553723416.git.andrew.burgess@embecosm.com
State New, archived
Headers

Commit Message

Andrew Burgess March 27, 2019, 9:53 p.m. UTC
  Introduce a new print setting max-depth which can be set with 'set
print max-depth DEPTH'.  The default value of DEPTH is 20, but this
can also be set to unlimited.

When GDB is printing a value containing nested structures GDB will
stop descending at depth DEPTH.  Here is a small example:

    typedef struct s1 { int a; } s1;
    typedef struct s2 { s1 b; } s2;
    typedef struct s3 { s2 c; } s3;
    typedef struct s4 { s3 d; } s4;

    s4 var = { { { { 3 } } } };

The following table shows how various depth settings effect printing
of 'var':

    | Depth Setting | Result of 'p var'              |
    |---------------+--------------------------------|
    |     Unlimited | $1 = {d = {c = {b = {a = 3}}}} |
    |             4 | $1 = {d = {c = {b = {a = 3}}}} |
    |             3 | $1 = {d = {c = {b = {...}}}}   |
    |             2 | $1 = {d = {c = {...}}}         |
    |             1 | $1 = {d = {...}}               |
    |             0 | $1 = {...}                     |

Only structures, unions, and arrays are replaced in this way, scalars
and strings are not replaced.

The replacement is counted from the level at which you print, not from
the top level of the structure.  So, consider the above example and
this GDB session:

    (gdb) set print max-depth 2
    (gdb) p var
    $1 = {d = {c = {...}}}
    (gdb) p var.d
    $2 = {c = {b = {...}}}
    (gdb) p var.d.c
    $3 = {b = {a = 3}}

Setting the max-depth to 2 doesn't prevent the user from exploring
deeper into 'var' by asking for specific sub-fields to be printed.

The motivation behind this feature is to try and give the user more
control over how much is printed when examining large, complex data
structures.

The default max-depth of 20 means that there is a change in GDB's
default behaviour.  Someone printing a data structure with 20 levels
of nesting will now see '{...}' instead of their data, they would need
to adjust the max depth, or call print again naming a specific field
in order to dig deeper into their data structure.  If this is
considered a problem then we could increase the default, or even make
the default unlimited.

This commit relies on the previous commit, which added a new field to
the language structure, this new field was a string that contained the
pattern that should be used when a structure/union/array is replaced
in the output, this allows languages to use a syntax that is more
appropriate, mostly this will be selecting the correct types of
bracket '(...)' or '{...}', both of which are currently in use.

gdb/ChangeLog:

	* cp-valprint.c (cp_print_value_fields): Allow an additional level
	of depth when printing anonymous structs or unions.
	* guile/scm-pretty-print.c (ppscm_print_children): Don't print the
	children if the max-depth is already exceeded.  When printing the
	key of a map, allow one extra level of depth.
	* python/py-prettyprint.c (print_children): Likewise.
	* valprint.c: Add 'c-lang.h' include.
	(PRINT_MAX_DEPTH_DEFAULT): Define.
	(user_print_options): Initialise max_depth field.
	(val_print_scalar_or_string_type_p): New function.
	(val_print): Check to see if the max depth has been reached.
	(show_print_max_depth): New function.
	(_initialize_valprint): Add 'print max-depth' option.
	* valprint.h (struct value_print_options) <max_depth>: New field.
	* NEWS: Document new feature.

gdb/doc/ChangeLog:

	* gdb.texinfo (Print Settings): Document 'print max-depth'.
	* guile.texi (Guile Pretty Printing API): Document that 'print
	max-depth' can effect the display of a values children.
	* python.texi (Pretty Printing API): Likewise.

gdb/testsuite/ChangeLog:

	* gdb.base/max-depth.c: New file.
	* gdb.base/max-depth.exp: New file.
	* gdb.python/py-nested-maps.c: New file.
	* gdb.python/py-nested-maps.exp: New file.
	* gdb.python/py-nested-maps.py: New file.
	* gdb.fortran/max-depth.exp: New file.
	* gdb.fortran/max-depth.f90: New file.
	* lib/gdb.exp (get_print_expr_at_depths): New proc.
---
 gdb/ChangeLog                               |  22 +++
 gdb/NEWS                                    |   9 ++
 gdb/cp-valprint.c                           |  32 +++--
 gdb/doc/ChangeLog                           |   7 +
 gdb/doc/gdb.texinfo                         |  57 ++++++++
 gdb/doc/guile.texi                          |   3 +
 gdb/doc/python.texi                         |   3 +
 gdb/guile/scm-pretty-print.c                |  23 ++-
 gdb/python/py-prettyprint.c                 |  22 ++-
 gdb/testsuite/ChangeLog                     |  12 ++
 gdb/testsuite/gdb.base/max-depth.c          | 209 +++++++++++++++++++++++++++
 gdb/testsuite/gdb.base/max-depth.exp        | 122 ++++++++++++++++
 gdb/testsuite/gdb.fortran/max-depth.exp     |  41 ++++++
 gdb/testsuite/gdb.fortran/max-depth.f90     |  40 ++++++
 gdb/testsuite/gdb.python/py-nested-maps.c   | 122 ++++++++++++++++
 gdb/testsuite/gdb.python/py-nested-maps.exp | 211 ++++++++++++++++++++++++++++
 gdb/testsuite/gdb.python/py-nested-maps.py  | 129 +++++++++++++++++
 gdb/testsuite/lib/gdb.exp                   |  30 ++++
 gdb/valprint.c                              |  66 ++++++++-
 gdb/valprint.h                              |   3 +
 20 files changed, 1148 insertions(+), 15 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/max-depth.c
 create mode 100644 gdb/testsuite/gdb.base/max-depth.exp
 create mode 100644 gdb/testsuite/gdb.fortran/max-depth.exp
 create mode 100644 gdb/testsuite/gdb.fortran/max-depth.f90
 create mode 100644 gdb/testsuite/gdb.python/py-nested-maps.c
 create mode 100644 gdb/testsuite/gdb.python/py-nested-maps.exp
 create mode 100644 gdb/testsuite/gdb.python/py-nested-maps.py
  

Comments

Pedro Alves March 28, 2019, 12:42 p.m. UTC | #1
Noice!

Overall looks good to me.

I think you're missing tests for C++ inheritance (all of regular, multiple and
virtual).

E.g,. with:

 struct A { int a; int x; };
 struct B : A { int b; int x; };
 struct C : B { int c; int x; };

 C c;

Today we print:

 (gdb) p c
 $1 = {<B> = {<A> = {a = 0, x = 0}, b = 0, x = 0}, c = 0, x = 0}

I suppose max-depth affects this as well.  But given no tests,
can't be sure.  :-)

Some nits pointed out below.

On 03/27/2019 09:53 PM, Andrew Burgess wrote:
> Introduce a new print setting max-depth which can be set with 'set
> print max-depth DEPTH'.  The default value of DEPTH is 20, but this
> can also be set to unlimited.
> 
> When GDB is printing a value containing nested structures GDB will
> stop descending at depth DEPTH.  Here is a small example:
> 
>     typedef struct s1 { int a; } s1;
>     typedef struct s2 { s1 b; } s2;
>     typedef struct s3 { s2 c; } s3;
>     typedef struct s4 { s3 d; } s4;
> 
>     s4 var = { { { { 3 } } } };
> 
> The following table shows how various depth settings effect printing
> of 'var':

s/effect/affect/


> 
>     | Depth Setting | Result of 'p var'              |
>     |---------------+--------------------------------|
>     |     Unlimited | $1 = {d = {c = {b = {a = 3}}}} |
>     |             4 | $1 = {d = {c = {b = {a = 3}}}} |
>     |             3 | $1 = {d = {c = {b = {...}}}}   |
>     |             2 | $1 = {d = {c = {...}}}         |
>     |             1 | $1 = {d = {...}}               |
>     |             0 | $1 = {...}                     |
> 
> Only structures, unions, and arrays are replaced in this way, scalars
> and strings are not replaced.
> 
> The replacement is counted from the level at which you print, not from
> the top level of the structure.  So, consider the above example and
> this GDB session:
> 
>     (gdb) set print max-depth 2
>     (gdb) p var
>     $1 = {d = {c = {...}}}
>     (gdb) p var.d
>     $2 = {c = {b = {...}}}
>     (gdb) p var.d.c
>     $3 = {b = {a = 3}}
> 
> Setting the max-depth to 2 doesn't prevent the user from exploring
> deeper into 'var' by asking for specific sub-fields to be printed.
> 
> The motivation behind this feature is to try and give the user more
> control over how much is printed when examining large, complex data
> structures.
> 
> The default max-depth of 20 means that there is a change in GDB's
> default behaviour.  Someone printing a data structure with 20 levels
> of nesting will now see '{...}' instead of their data, they would need
> to adjust the max depth, or call print again naming a specific field
> in order to dig deeper into their data structure.  If this is
> considered a problem then we could increase the default, or even make
> the default unlimited.
> 
> This commit relies on the previous commit, which added a new field to
> the language structure, this new field was a string that contained the
> pattern that should be used when a structure/union/array is replaced
> in the output, this allows languages to use a syntax that is more
> appropriate, mostly this will be selecting the correct types of
> bracket '(...)' or '{...}', both of which are currently in use.


Could you say something about if/how this affects Python, Guile, and MI ?


>  * GDB and GDBserver now support access to additional registers on
> diff --git a/gdb/cp-valprint.c b/gdb/cp-valprint.c
> index 443c3b06dac..71da2b19755 100644
> --- a/gdb/cp-valprint.c
> +++ b/gdb/cp-valprint.c
> @@ -272,10 +272,23 @@ cp_print_value_fields (struct type *type, struct type *real_type,
>  				   current_language->la_language,
>  				   DMGL_PARAMS | DMGL_ANSI);
>  	  annotate_field_name_end ();
> +
> +	  /* We tweak various options in a few cases below.  */
> +	  struct value_print_options options_copy = *options;
> +	  struct value_print_options *opts = &options_copy;

You could drop the redundant "struct".

> +
>  	  /* Do not print leading '=' in case of anonymous
>  	     unions.  */
>  	  if (strcmp (TYPE_FIELD_NAME (type, i), ""))
>  	    fputs_filtered (" = ", stream);
> +	  else
> +	    {
> +	      /* If this is an anonymous field then we want to consider it
> +		 as though it is at its parents depth when it comes to the

"parent's"

> +		 max print depth.  */
> +	      if (opts->max_depth != -1 && opts->max_depth < INT_MAX)
> +		++opts->max_depth;
> +	    }


>  @defun pretty_printer.display_hint (self)
> diff --git a/gdb/guile/scm-pretty-print.c b/gdb/guile/scm-pretty-print.c
> index e5096944b68..219e0ef63ad 100644
> --- a/gdb/guile/scm-pretty-print.c
> +++ b/gdb/guile/scm-pretty-print.c
> @@ -744,6 +744,16 @@ ppscm_print_children (SCM printer, enum display_hint hint,
>        return;
>      }
>  
> +  if (options->max_depth > -1
> +      && recurse >= options->max_depth)
> +    {
> +      if (language->la_language == language_fortran)
> +	fputs_filtered ("(...)", stream);
> +      else
> +	fputs_filtered ("{...}", stream);

Shouldn't this be using la_struct_too_deep_ellipsis here too?

> +      return;
> +    }

> diff --git a/gdb/python/py-prettyprint.c b/gdb/python/py-prettyprint.c
> index e64d1f88af8..504644143f3 100644
> --- a/gdb/python/py-prettyprint.c
> +++ b/gdb/python/py-prettyprint.c
> @@ -361,6 +361,16 @@ print_children (PyObject *printer, const char *hint,
>    if (! PyObject_HasAttr (printer, gdbpy_children_cst))
>      return;
>  
> +  if (options->max_depth > -1
> +      && recurse >= options->max_depth)
> +    {
> +      if (language->la_language == language_fortran)
> +	fputs_filtered ("(...)", stream);
> +      else
> +	fputs_filtered ("{...}", stream);

Ditto.

> +      return;
> +    }
> +


> diff --git a/gdb/testsuite/gdb.base/max-depth.exp b/gdb/testsuite/gdb.base/max-depth.exp
> new file mode 100644
> index 00000000000..777f66fab4e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/max-depth.exp

> +proc compile_and_run_tests { lang } {
> +    global testfile
> +    global srcfile
> +    global binfile
> +    global subdir
> +    global srcdir
> +
> +    standard_testfile .c
> +    set dir "$lang"
> +
> +    # Create the additional flags

Add period.

> +    set flags "debug"
> +    lappend flags $lang
> +
> +    set binfile [standard_output_file ${dir}/${testfile}]
> +    if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable "${flags}"] != "" } {

Spurious double space after "if".

Wonder if this could use build_executable or prepare_for_testing (eliminating
the clean_restart below).

> +	unresolved "failed to compile"
> +	return 0
> +    }
> +
> +    # Start with a fresh gdb.
> +    clean_restart ${binfile}
> +
> +    # Advance to main

Add period.

> +    if { ![runto_main] } then {
> +	fail "can't run to main"
> +	return 0
> +    }


> +    # Check handling of typedefs.
> +    gdb_print_expr_at_depths "s6" {"{...}" \
> +				       "{{raw = {...}, {x = 0, y = 0, z = 0}}}" \
> +				       "{{raw = \\\{0, 0, 0\\\}, {x = 0, y = 0, z = 0}}}"}
> +
> +    # Multiple anonymous structures in parallel.
> +    gdb_print_expr_at_depths "s7" {"{...}" \
> +				       "{{x = 0, y = 0}, {z = 0, a = 0}, {b = 0, c = 0}}" \
> +				       "{{x = 0, y = 0}, {z = 0, a = 0}, {b = 0, c = 0}}"}
> +
> +    # Flip flop between named and anonymous. Expected to unfurl to the

Double space before "Expected".

> +++ b/gdb/testsuite/gdb.python/py-nested-maps.c
> @@ -0,0 +1,122 @@

> +
> +struct map_map_t *
> +create_map_map ()

create_map_map (void)

> +{

> diff --git a/gdb/testsuite/gdb.python/py-nested-maps.exp b/gdb/testsuite/gdb.python/py-nested-maps.exp
> new file mode 100644
> index 00000000000..146b9cab075
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-nested-maps.exp
> @@ -0,0 +1,211 @@
> +# Copyright (C) 2019 Free Software Foundation, Inc.
> +
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This tests GDB's python pretty printing of nested map like
> +# structures using structures as keys and values.
> +

Should this say something about depth?


> +gdb_breakpoint [gdb_get_line_number "Break here"]
> +gdb_continue_to_breakpoint "run to testing point" ".*Break here.*"
> +
> +set remote_python_file [gdb_remote_download host \
> +			    ${srcdir}/${subdir}/${testfile}.py]
> +gdb_test_no_output "source ${remote_python_file}" "load python file"
> +
> +# Test printing with 'set print pretty off'

Missing period.

> +gdb_test_no_output "set print pretty off"
> +with_test_prefix "pretty=off" {
> +    gdb_print_expr_at_depths "*m1" \
> +	[list \
> +	     "\{\\.\\.\\.\}" \
> +	     "\{\\\[\{a = 3, b = 4\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 4, b = 5\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 5, b = 6\}\\\] = \{\\.\\.\\.\}\}" \
> +	     "\{\\\[\{a = 3, b = 4\}\\\] = \{x = 0, y = 1, z = 2\}, \\\[\{a = 4, b = 5\}\\\] = \{x = 3, y = 4, z = 5\}, \\\[\{a = 5, b = 6\}\\\] = \{x = 6, y = 7, z = 8\}\}" \
> +	    ]
> +
> +    gdb_print_expr_at_depths "*mm" \
> +	[list \
> +	     "\{\\.\\.\\.\}" \
> +	     "\{\\\[$hex \"m1\"\\\] = \{\\.\\.\\.\}, \\\[$hex \"m2\"\\\] = \{\\.\\.\\.\}\}" \
> +	     "\{\\\[$hex \"m1\"\\\] = \{\\\[\{a = 3, b = 4\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 4, b = 5\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 5, b = 6\}\\\] = \{\\.\\.\\.\}\}, \\\[$hex \"m2\"\\\] = \{\\\[\{a = 6, b = 7\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 7, b = 8\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 8, b = 9\}\\\] = \{\\.\\.\\.\}\}\}" \
> +	     "\{\\\[$hex \"m1\"\\\] = \{\\\[\{a = 3, b = 4\}\\\] = \{x = 0, y = 1, z = 2\}, \\\[\{a = 4, b = 5\}\\\] = \{x = 3, y = 4, z = 5\}, \\\[\{a = 5, b = 6\}\\\] = \{x = 6, y = 7, z = 8\}\}, \\\[$hex \"m2\"\\\] = \{\\\[\{a = 6, b = 7\}\\\] = \{x = 9, y = 0, z = 1\}, \\\[\{a = 7, b = 8\}\\\] = \{x = 2, y = 3, z = 4\}, \\\[\{a = 8, b = 9\}\\\] = \{x = 5, y = 6, z = 7\}\}\}" \
> +	    ]
> +}
> +
> +# Now again, but with 'set print pretty on'

Missing period.

>  
>  # Issue a PASS and return true if evaluating CONDITION in the caller's
> diff --git a/gdb/valprint.c b/gdb/valprint.c
> index 10020901c2d..e47ac981ce8 100644
> --- a/gdb/valprint.c
> +++ b/gdb/valprint.c
> @@ -28,6 +28,7 @@
>  #include "annotate.h"
>  #include "valprint.h"
>  #include "target-float.h"
> +#include "c-lang.h" /* For c_textual_element_type.  */
>  #include "extension.h"
>  #include "ada-lang.h"
>  #include "gdb_obstack.h"
> @@ -88,6 +89,7 @@ static void val_print_type_code_flags (struct type *type,
>  				       struct ui_file *stream);
>  
>  #define PRINT_MAX_DEFAULT 200	/* Start print_max off at this value.  */
> +#define PRINT_MAX_DEPTH_DEFAULT 20	/* Start print_max_depth off at this value. */
>  
>  struct value_print_options user_print_options =
>  {
> @@ -109,7 +111,8 @@ struct value_print_options user_print_options =
>    1,				/* pascal_static_field_print */
>    0,				/* raw */
>    0,				/* summary */
> -  1				/* symbol_print */
> +  1,				/* symbol_print */
> +  PRINT_MAX_DEPTH_DEFAULT	/* max_depth */
>  };
>  
>  /* Initialize *OPTS to be a copy of the user print options.  */
> @@ -281,6 +284,39 @@ val_print_scalar_type_p (struct type *type)
>      }
>  }
>  
> +/* A helper function for val_print.  When printing with limited depth we
> +   want to print string and scalar arguments, but not aggregate arguments.
> +   This function distinguishes between the two.  */
> +
> +static bool
> +val_print_scalar_or_string_type_p (struct type *type)
> +{
> +  /* Resolve the real type.  */
> +  type = check_typedef (type);
> +  while (TYPE_CODE (type) == TYPE_CODE_REF)
> +    {
> +      type = TYPE_TARGET_TYPE (type);
> +      type = check_typedef (type);
> +    }
> +
> +  switch (TYPE_CODE (type))
> +    {
> +    case TYPE_CODE_ARRAY:
> +      {
> +	/* See if target type looks like a string.  */
> +	struct type * array_target_type = TYPE_TARGET_TYPE (type);

Spurious space after "*".

> +	return (TYPE_LENGTH (type) > 0
> +		&& TYPE_LENGTH (array_target_type) > 0
> +		&& c_textual_element_type (array_target_type, 0));

Is using c_textual_element_type safe / the right thing to do
for all languages?


> +      }
> +    case TYPE_CODE_STRING:
> +      return true;
> +    default:
> +      /* Check for scalar.  */
> +      return val_print_scalar_type_p(type);
> +    }
> +}
> +
>  /* See its definition in value.h.  */
>  
>  int
> @@ -1054,6 +1090,15 @@ val_print (struct type *type, LONGEST embedded_offset,
>        return;
>      }
>  
> +  if (!val_print_scalar_or_string_type_p (type)
> +      && options->max_depth > -1
> +      && recurse >= options->max_depth)
> +    {
> +      gdb_assert (language->la_struct_too_deep_ellipsis != NULL);

Maybe put this assertion / validation where we register languages
instead, so you'd do this only once and in one place, instead of
potentially at all usage spots?

> +      fputs_filtered (language->la_struct_too_deep_ellipsis, stream);
> +      return;
> +    }
> +
Thanks,
Pedro Alves
  
Eli Zaretskii March 28, 2019, 4:07 p.m. UTC | #2
> From: Andrew Burgess <andrew.burgess@embecosm.com>
> Cc: richard.bunt@arm.com,	Andrew Burgess <andrew.burgess@embecosm.com>
> Date: Wed, 27 Mar 2019 21:53:24 +0000
> 
> +* New commands
> +
> +set print max-depth
> +show print max-depth
> +  Allows deeply nested structures to be simplified when printing by
> +  replacing deeply nested parts (beyond the max-depth) with ellipses.
> +  The default max-depth is 20, but this can be set to unlimited to get
> +  the old behaviour back.

This part is OK, but please use "behavior", as our convention is to
use US English spelling.

> +@noindent
> +and with @code{set print max-depth unlimited} in effect @samp{p var}
> +would print

I think this and other similar sentences should begin with a capital
letter.

The documentation parts are okay with these nits fixed.

Thanks.
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index edcc9c951a0..6b17640547f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -10,6 +10,15 @@ 
 
 * Support for Pointer Authentication on AArch64 Linux.
 
+* New commands
+
+set print max-depth
+show print max-depth
+  Allows deeply nested structures to be simplified when printing by
+  replacing deeply nested parts (beyond the max-depth) with ellipses.
+  The default max-depth is 20, but this can be set to unlimited to get
+  the old behaviour back.
+
 *** Changes in GDB 8.3
 
 * GDB and GDBserver now support access to additional registers on
diff --git a/gdb/cp-valprint.c b/gdb/cp-valprint.c
index 443c3b06dac..71da2b19755 100644
--- a/gdb/cp-valprint.c
+++ b/gdb/cp-valprint.c
@@ -272,10 +272,23 @@  cp_print_value_fields (struct type *type, struct type *real_type,
 				   current_language->la_language,
 				   DMGL_PARAMS | DMGL_ANSI);
 	  annotate_field_name_end ();
+
+	  /* We tweak various options in a few cases below.  */
+	  struct value_print_options options_copy = *options;
+	  struct value_print_options *opts = &options_copy;
+
 	  /* Do not print leading '=' in case of anonymous
 	     unions.  */
 	  if (strcmp (TYPE_FIELD_NAME (type, i), ""))
 	    fputs_filtered (" = ", stream);
+	  else
+	    {
+	      /* If this is an anonymous field then we want to consider it
+		 as though it is at its parents depth when it comes to the
+		 max print depth.  */
+	      if (opts->max_depth != -1 && opts->max_depth < INT_MAX)
+		++opts->max_depth;
+	    }
 	  annotate_field_value ();
 
 	  if (!field_is_static (&TYPE_FIELD (type, i))
@@ -299,14 +312,12 @@  cp_print_value_fields (struct type *type, struct type *real_type,
 		}
 	      else
 		{
-		  struct value_print_options opts = *options;
-
-		  opts.deref_ref = 0;
+		  opts->deref_ref = 0;
 
 		  v = value_field_bitfield (type, i, valaddr, offset, val);
 
-		  common_val_print (v, stream, recurse + 1, &opts,
-				    current_language);
+		  common_val_print (v, stream, recurse + 1,
+				    opts, current_language);
 		}
 	    }
 	  else
@@ -334,8 +345,7 @@  cp_print_value_fields (struct type *type, struct type *real_type,
 		  END_CATCH
 
 		  cp_print_static_field (TYPE_FIELD_TYPE (type, i),
-					 v, stream, recurse + 1,
-					 options);
+					 v, stream, recurse + 1, opts);
 		}
 	      else if (i == vptr_fieldno && type == vptr_basetype)
 		{
@@ -347,20 +357,18 @@  cp_print_value_fields (struct type *type, struct type *real_type,
 		      CORE_ADDR addr;
 		      
 		      addr = extract_typed_address (valaddr + i_offset, i_type);
-		      print_function_pointer_address (options,
+		      print_function_pointer_address (opts,
 						      get_type_arch (type),
 						      addr, stream);
 		    }
 		}
 	      else
 		{
-		  struct value_print_options opts = *options;
-
-		  opts.deref_ref = 0;
+		  opts->deref_ref = 0;
 		  val_print (TYPE_FIELD_TYPE (type, i),
 			     offset + TYPE_FIELD_BITPOS (type, i) / 8,
 			     address,
-			     stream, recurse + 1, val, &opts,
+			     stream, recurse + 1, val, opts,
 			     current_language);
 		}
 	    }
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 4c1d5e8294f..94f820d3ff2 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -10560,6 +10560,63 @@ 
 Display the current threshold for printing repeated identical
 elements.
 
+@item set print max-depth @var{depth}
+@item set print max-depth unlimited
+@cindex printing nested structures
+Set the threshold after which nested structures are replaced with
+ellipsis, this can make visualising deeply nested structures easier.
+
+For example, given this C code
+
+@smallexample
+typedef struct s1 @{ int a; @} s1;
+typedef struct s2 @{ s1 b; @} s2;
+typedef struct s3 @{ s2 c; @} s3;
+typedef struct s4 @{ s3 d; @} s4;
+
+s4 var = @{ @{ @{ @{ 3 @} @} @} @};
+@end smallexample
+
+@noindent
+and with @code{set print max-depth unlimited} in effect @samp{p var}
+would print
+
+@smallexample
+$1 = @{d = @{c = @{b = @{a = 3@}@}@}@}
+@end smallexample
+
+@noindent
+with @code{set print max-depth 0} in effect @samp{p var}
+would print
+
+@smallexample
+$1 = @{...@}
+@end smallexample
+
+@noindent
+with @code{set print max-depth 1} in effect @samp{p var}
+would print
+
+@smallexample
+$1 = @{d = @{...@}@}
+@end smallexample
+
+@noindent
+with @code{set print max-depth 2} in effect @samp{p var}
+would print
+
+@smallexample
+$1 = @{d = @{c = @{...@}@}@}
+@end smallexample
+
+The pattern used to replace nested structures varies based on
+language, for most languages @code{@{...@}} is used, but Fortran uses
+@code{(...)}.
+
+@item show print max-depth
+Display the current threshold after which nested structures are
+replaces with ellipsis.
+
 @item set print null-stop
 @cindex @sc{null} elements in arrays
 Cause @value{GDBN} to stop printing the characters of an array when the first
diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
index 721338b050c..f4c29dc3df8 100644
--- a/gdb/doc/guile.texi
+++ b/gdb/doc/guile.texi
@@ -1469,6 +1469,9 @@ 
 
 If @var{children} is @code{#f}, @value{GDBN} will act
 as though the value has no children.
+
+Children may be hidden from display based on the value of @samp{set
+print max-depth} (@pxref{Print Settings}).
 @end table
 @end deffn
 
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 56c925d4dd2..26320539db5 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -1285,6 +1285,9 @@ 
 
 This method is optional.  If it does not exist, @value{GDBN} will act
 as though the value has no children.
+
+Children may be hidden from display based on the value of @samp{set
+print max-depth} (@pxref{Print Settings}).
 @end defun
 
 @defun pretty_printer.display_hint (self)
diff --git a/gdb/guile/scm-pretty-print.c b/gdb/guile/scm-pretty-print.c
index e5096944b68..219e0ef63ad 100644
--- a/gdb/guile/scm-pretty-print.c
+++ b/gdb/guile/scm-pretty-print.c
@@ -744,6 +744,16 @@  ppscm_print_children (SCM printer, enum display_hint hint,
       return;
     }
 
+  if (options->max_depth > -1
+      && recurse >= options->max_depth)
+    {
+      if (language->la_language == language_fortran)
+	fputs_filtered ("(...)", stream);
+      else
+	fputs_filtered ("{...}", stream);
+      return;
+    }
+
   /* If we are printing a map or an array, we want special formatting.  */
   is_map = hint == HINT_MAP;
   is_array = hint == HINT_ARRAY;
@@ -898,7 +908,18 @@  ppscm_print_children (SCM printer, enum display_hint hint,
 	      ppscm_print_exception_unless_memory_error (except_scm, stream);
 	      break;
 	    }
-	  common_val_print (value, stream, recurse + 1, options, language);
+	  else
+	    {
+	      /* When printing the key of a map we allow one additional
+		 level of depth.  This means the key will print before the
+		 value does.  */
+	      struct value_print_options opt = *options;
+	      if (is_map && i % 2 == 0
+		  && opt.max_depth != -1
+		  && opt.max_depth < INT_MAX)
+		++opt.max_depth;
+	      common_val_print (value, stream, recurse + 1, &opt, language);
+	    }
 	}
 
       if (is_map && i % 2 == 0)
diff --git a/gdb/python/py-prettyprint.c b/gdb/python/py-prettyprint.c
index e64d1f88af8..504644143f3 100644
--- a/gdb/python/py-prettyprint.c
+++ b/gdb/python/py-prettyprint.c
@@ -361,6 +361,16 @@  print_children (PyObject *printer, const char *hint,
   if (! PyObject_HasAttr (printer, gdbpy_children_cst))
     return;
 
+  if (options->max_depth > -1
+      && recurse >= options->max_depth)
+    {
+      if (language->la_language == language_fortran)
+	fputs_filtered ("(...)", stream);
+      else
+	fputs_filtered ("{...}", stream);
+      return;
+    }
+
   /* If we are printing a map or an array, we want some special
      formatting.  */
   is_map = hint && ! strcmp (hint, "map");
@@ -518,7 +528,17 @@  print_children (PyObject *printer, const char *hint,
 	      error (_("Error while executing Python code."));
 	    }
 	  else
-	    common_val_print (value, stream, recurse + 1, options, language);
+	    {
+	      /* When printing the key of a map we allow one additional
+		 level of depth.  This means the key will print before the
+		 value does.  */
+	      struct value_print_options opt = *options;
+	      if (is_map && i % 2 == 0
+		  && opt.max_depth != -1
+		  && opt.max_depth < INT_MAX)
+		++opt.max_depth;
+	      common_val_print (value, stream, recurse + 1, &opt, language);
+	    }
 	}
 
       if (is_map && i % 2 == 0)
diff --git a/gdb/testsuite/gdb.base/max-depth.c b/gdb/testsuite/gdb.base/max-depth.c
new file mode 100644
index 00000000000..cf2484fa59b
--- /dev/null
+++ b/gdb/testsuite/gdb.base/max-depth.c
@@ -0,0 +1,209 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2019 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+struct s1
+{
+  int x;
+  int y;
+} s1;
+
+struct s2
+{
+  int x;
+  int y;
+  struct
+  {
+    int z;
+    int a;
+  };
+} s2;
+
+struct s3
+{
+  int x;
+  int y;
+  struct
+  {
+    int z;
+    int a;
+    struct
+    {
+      int b;
+      int c;
+    };
+  };
+} s3;
+
+struct s4
+{
+  int x;
+  int y;
+  struct
+  {
+    int x;
+    int y;
+    struct
+    {
+      int x;
+      int y;
+    } l2;
+  } l1;
+} s4;
+
+struct s5
+{
+  union
+  {
+    int raw[3];
+    struct
+    {
+      int x;
+      int y;
+      int z;
+    };
+  };
+} s5;
+
+typedef struct
+{
+  union
+  {
+    int raw[3];
+    struct
+    {
+      int x;
+      int y;
+      int z;
+    };
+  };
+} s6_t;
+
+s6_t s6;
+
+struct s7
+{
+  struct
+  {
+    int x;
+    int y;
+  };
+  struct
+  {
+    int z;
+    int a;
+  };
+  struct
+  {
+    int b;
+    int c;
+  };
+} s7;
+
+struct s8
+{
+  int x;
+  int y;
+  struct
+  {
+    int z;
+    int a;
+    struct
+    {
+      int b;
+      int c;
+    };
+  } d1;
+} s8;
+
+struct s9
+{
+  int x;
+  int y;
+  struct
+  {
+    int k;
+    int j;
+    struct
+    {
+      int z;
+      int a;
+      struct
+      {
+        int b;
+        int c;
+      };
+    } d1;
+  };
+  struct
+  {
+    int z;
+    int a;
+    struct
+    {
+      int b;
+      int c;
+    };
+  } d2;
+} s9;
+
+struct s10
+{
+  int x[10];
+  int y;
+  struct
+  {
+    int k[10];
+    int j;
+    struct
+    {
+      int z;
+      int a;
+      struct
+      {
+        int b[10];
+        int c;
+      };
+    } d1;
+  };
+  struct
+  {
+    int z;
+    int a;
+    struct
+    {
+      int b[10];
+      int c;
+    };
+  } d2;
+} s10;
+
+struct s11
+{
+  int x;
+  char s[10];
+  struct
+  {
+    int z;
+    int a;
+  };
+} s11;
+
+int
+main ()
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/max-depth.exp b/gdb/testsuite/gdb.base/max-depth.exp
new file mode 100644
index 00000000000..777f66fab4e
--- /dev/null
+++ b/gdb/testsuite/gdb.base/max-depth.exp
@@ -0,0 +1,122 @@ 
+# Copyright 2019 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Tests GDB's handling of 'set print max-depth'.
+
+# Only test C++ if we are able.  Always use C.
+if { [skip_cplus_tests] || [get_compiler_info "c++"] } {
+    set lang {c}
+} else {
+    set lang {c c++}
+}
+
+foreach l $lang {
+    set dir "$l"
+    remote_exec host "rm -rf [standard_output_file ${dir}]"
+    remote_exec host "mkdir -p [standard_output_file ${dir}]"
+}
+
+proc compile_and_run_tests { lang } {
+    global testfile
+    global srcfile
+    global binfile
+    global subdir
+    global srcdir
+
+    standard_testfile .c
+    set dir "$lang"
+
+    # Create the additional flags
+    set flags "debug"
+    lappend flags $lang
+
+    set binfile [standard_output_file ${dir}/${testfile}]
+    if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable "${flags}"] != "" } {
+	unresolved "failed to compile"
+	return 0
+    }
+
+    # Start with a fresh gdb.
+    clean_restart ${binfile}
+
+    # Advance to main
+    if { ![runto_main] } then {
+	fail "can't run to main"
+	return 0
+    }
+
+    # The max-depth setting has no effect as the anonymous scopes are
+    # ignored and the members are aggregated into the parent scope.
+    gdb_print_expr_at_depths "s1" {"{...}" \
+				       "{x = 0, y = 0}"\
+				       "{x = 0, y = 0}"}
+
+    gdb_print_expr_at_depths "s2" {"{...}" \
+				       "{x = 0, y = 0, {z = 0, a = 0}}" \
+				       "{x = 0, y = 0, {z = 0, a = 0}}"}
+
+    gdb_print_expr_at_depths "s3" {"{...}" \
+				       "{x = 0, y = 0, {z = 0, a = 0, {b = 0, c = 0}}}" \
+				       "{x = 0, y = 0, {z = 0, a = 0, {b = 0, c = 0}}}" \
+				       "{x = 0, y = 0, {z = 0, a = 0, {b = 0, c = 0}}}"}
+
+    # Increasing max-depth unfurls more of the object.
+    gdb_print_expr_at_depths "s4" {"{...}" \
+				       "{x = 0, y = 0, l1 = {...}}" \
+				       "{x = 0, y = 0, l1 = {x = 0, y = 0, l2 = {...}}}" \
+				       "{x = 0, y = 0, l1 = {x = 0, y = 0, l2 = {x = 0, y = 0}}}"}
+
+    # Check handling of unions, in this case 'raw' is printed instead of
+    # just {...} as this is not useful.
+    gdb_print_expr_at_depths "s5" {"{...}" \
+				       "{{raw = {...}, {x = 0, y = 0, z = 0}}}" \
+				       "{{raw = \\\{0, 0, 0\\\}, {x = 0, y = 0, z = 0}}}"}
+
+    # Check handling of typedefs.
+    gdb_print_expr_at_depths "s6" {"{...}" \
+				       "{{raw = {...}, {x = 0, y = 0, z = 0}}}" \
+				       "{{raw = \\\{0, 0, 0\\\}, {x = 0, y = 0, z = 0}}}"}
+
+    # Multiple anonymous structures in parallel.
+    gdb_print_expr_at_depths "s7" {"{...}" \
+				       "{{x = 0, y = 0}, {z = 0, a = 0}, {b = 0, c = 0}}" \
+				       "{{x = 0, y = 0}, {z = 0, a = 0}, {b = 0, c = 0}}"}
+
+    # Flip flop between named and anonymous. Expected to unfurl to the
+    # first non-anonymous type.
+    gdb_print_expr_at_depths "s8" {"{...}" \
+				       "{x = 0, y = 0, d1 = {...}}" \
+				       "{x = 0, y = 0, d1 = {z = 0, a = 0, {b = 0, c = 0}}}"}
+
+    # Imbalanced tree, this will unfurl one size more than the other as
+    # one side has more anonymous levels.
+    gdb_print_expr_at_depths "s9" {"{...}" \
+				       "{x = 0, y = 0, {k = 0, j = 0, d1 = {...}}, d2 = {...}}" \
+				       "{x = 0, y = 0, {k = 0, j = 0, d1 = {z = 0, a = 0, {b = 0, c = 0}}}, d2 = {z = 0, a = 0, {b = 0, c = 0}}}"}
+
+    # Arrays are treated as an extra level, while scalars are not.
+    gdb_print_expr_at_depths "s10" {"{...}" \
+					"{x = {...}, y = 0, {k = {...}, j = 0, d1 = {...}}, d2 = {...}}" \
+					"{x = \\\{0, 0, 0, 0, 0, 0, 0, 0, 0, 0\\\}, y = 0, {k = \\\{0, 0, 0, 0, 0, 0, 0, 0, 0, 0\\\}, j = 0, d1 = {z = 0, a = 0, {b = {...}, c = 0}}}, d2 = {z = 0, a = 0, {b = {...}, c = 0}}}" \
+					"{x = \\\{0, 0, 0, 0, 0, 0, 0, 0, 0, 0\\\}, y = 0, {k = \\\{0, 0, 0, 0, 0, 0, 0, 0, 0, 0\\\}, j = 0, d1 = {z = 0, a = 0, {b = \\\{0, 0, 0, 0, 0, 0, 0, 0, 0, 0\\\}, c = 0}}}, d2 = {z = 0, a = 0, {b = \\\{0, 0, 0, 0, 0, 0, 0, 0, 0, 0\\\}, c = 0}}}"}
+
+    # Strings are treated as scalars.
+    gdb_print_expr_at_depths "s11" {"{...}" \
+					"{x = 0, s = \"\\\\000\\\\000\\\\000\\\\000\\\\000\\\\000\\\\000\\\\000\\\\000\", {z = 0, a = 0}}"}
+}
+
+foreach_with_prefix l $lang {
+    compile_and_run_tests $l
+}
diff --git a/gdb/testsuite/gdb.fortran/max-depth.exp b/gdb/testsuite/gdb.fortran/max-depth.exp
new file mode 100644
index 00000000000..9f1f9fac76f
--- /dev/null
+++ b/gdb/testsuite/gdb.fortran/max-depth.exp
@@ -0,0 +1,41 @@ 
+# Copyright 2019 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file tests GDB's handling of 'set print max-depth' for nested
+# fortran types.
+
+load_lib "fortran.exp"
+
+if { [skip_fortran_tests] } { continue }
+
+standard_testfile .f90
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile {debug f90}] } {
+    return -1
+}
+
+if { ![runto MAIN__] } {
+    perror "Could not run to breakpoint `MAIN__'."
+    continue
+}
+
+gdb_breakpoint [gdb_get_line_number "stop-here"]
+gdb_continue_to_breakpoint "stop-here" ".*stop-here.*"
+
+gdb_print_expr_at_depths "var" {"\\(\\.\\.\\.\\)" \
+				    "\\( d = \\(\\.\\.\\.\\) \\)" \
+				    "\\( d = \\( c = \\(\\.\\.\\.\\) \\) \\)" \
+				    "\\( d = \\( c = \\( b = \\(\\.\\.\\.\\), array = \\(\\.\\.\\.\\) \\) \\) \\)" \
+				    "\\( d = \\( c = \\( b = \\( a = 1 \\), array = \\(0, 0, 0, 0, 0\\) \\) \\) \\)" }
diff --git a/gdb/testsuite/gdb.fortran/max-depth.f90 b/gdb/testsuite/gdb.fortran/max-depth.f90
new file mode 100644
index 00000000000..6af105d37e1
--- /dev/null
+++ b/gdb/testsuite/gdb.fortran/max-depth.f90
@@ -0,0 +1,40 @@ 
+! Copyright 2019 Free Software Foundation, Inc.
+!
+! This program is free software; you can redistribute it and/or modify
+! it under the terms of the GNU General Public License as published by
+! the Free Software Foundation; either version 2 of the License, or
+! (at your option) any later version.
+!
+! This program is distributed in the hope that it will be useful,
+! but WITHOUT ANY WARRANTY; without even the implied warranty of
+! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+! GNU General Public License for more details.
+!
+! You should have received a copy of the GNU General Public License
+! along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+program max_depth_test
+  type :: s1
+     integer :: a
+  end type s1
+
+  type :: s2
+     type (s1) :: b
+     integer :: array (5)
+  end type s2
+
+  type :: s3
+     type (s2) :: c
+  end type s3
+
+  type :: s4
+     type (s3) :: d
+  end type s4
+
+  logical :: l
+  type (s4) :: var
+
+  var%d%c%b%a = 1
+  var%d%c%array = 0
+  l = .FALSE.					! stop-here
+end program max_depth_test
diff --git a/gdb/testsuite/gdb.python/py-nested-maps.c b/gdb/testsuite/gdb.python/py-nested-maps.c
new file mode 100644
index 00000000000..29503908eae
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-nested-maps.c
@@ -0,0 +1,122 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2019 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see  <http://www.gnu.org/licenses/>.  */
+
+#include <stdlib.h>
+#include <string.h>
+
+#define FIXED_MAP_SIZE 10
+
+struct key_t
+{
+  int a;
+  int b;
+};
+
+struct value_t
+{
+  int x;
+  int y;
+  int z;
+};
+
+struct map_t
+{
+  const char *name;
+  int length;
+  struct key_t *keys;
+  struct value_t *values;
+};
+
+struct map_map_t
+{
+  int length;
+  struct map_t **values;
+};
+
+struct map_t *
+create_map (const char *name)
+{
+  struct map_t *m = malloc (sizeof (struct map_t));
+  m->name = strdup (name);
+  m->length = 0;
+  m->keys = NULL;
+  m->values = NULL;
+}
+
+void
+add_map_element (struct map_t *m, struct key_t k, struct value_t v)
+{
+  if (m->length == 0)
+    {
+      m->keys = malloc (sizeof (struct key_t) * FIXED_MAP_SIZE);
+      m->values = malloc (sizeof (struct value_t) * FIXED_MAP_SIZE);
+    }
+
+  m->keys[m->length] = k;
+  m->values[m->length] = v;
+  m->length++;
+}
+
+struct map_map_t *
+create_map_map ()
+{
+  struct map_map_t *mm = malloc (sizeof (struct map_map_t));
+  mm->length = 0;
+  mm->values = NULL;
+}
+
+void
+add_map_map_element (struct map_map_t *mm, struct map_t *map)
+{
+  if (mm->length == 0)
+    mm->values = malloc (sizeof (struct map_t *) * FIXED_MAP_SIZE);
+
+  mm->values[mm->length] = map;
+  mm->length++;
+}
+
+int
+main ()
+{
+  struct map_t *m1 = create_map ("m1");
+  struct key_t k1 = {3, 4};
+  struct key_t k2 = {4, 5};
+  struct key_t k3 = {5, 6};
+  struct key_t k4 = {6, 7};
+  struct key_t k5 = {7, 8};
+  struct key_t k6 = {8, 9};
+  struct value_t v1 = {0, 1, 2};
+  struct value_t v2 = {3, 4, 5};
+  struct value_t v3 = {6, 7, 8};
+  struct value_t v4 = {9, 0, 1};
+  struct value_t v5 = {2, 3, 4};
+  struct value_t v6 = {5, 6, 7};
+  add_map_element (m1, k1, v1);
+  add_map_element (m1, k2, v2);
+  add_map_element (m1, k3, v3);
+
+  struct map_t *m2 = create_map ("m2");
+  add_map_element (m2, k4, v4);
+  add_map_element (m2, k5, v5);
+  add_map_element (m2, k6, v6);
+
+  struct map_map_t *mm = create_map_map ();
+  add_map_map_element (mm, m1);
+  add_map_map_element (mm, m2);
+
+  return 0; /* Break here.  */
+}
diff --git a/gdb/testsuite/gdb.python/py-nested-maps.exp b/gdb/testsuite/gdb.python/py-nested-maps.exp
new file mode 100644
index 00000000000..146b9cab075
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-nested-maps.exp
@@ -0,0 +1,211 @@ 
+# Copyright (C) 2019 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This tests GDB's python pretty printing of nested map like
+# structures using structures as keys and values.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+# Start with a fresh gdb.
+gdb_exit
+gdb_start
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile {debug}] } {
+    return -1
+}
+
+if ![runto_main ] then {
+    return -1
+}
+
+gdb_breakpoint [gdb_get_line_number "Break here"]
+gdb_continue_to_breakpoint "run to testing point" ".*Break here.*"
+
+set remote_python_file [gdb_remote_download host \
+			    ${srcdir}/${subdir}/${testfile}.py]
+gdb_test_no_output "source ${remote_python_file}" "load python file"
+
+# Test printing with 'set print pretty off'
+gdb_test_no_output "set print pretty off"
+with_test_prefix "pretty=off" {
+    gdb_print_expr_at_depths "*m1" \
+	[list \
+	     "\{\\.\\.\\.\}" \
+	     "\{\\\[\{a = 3, b = 4\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 4, b = 5\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 5, b = 6\}\\\] = \{\\.\\.\\.\}\}" \
+	     "\{\\\[\{a = 3, b = 4\}\\\] = \{x = 0, y = 1, z = 2\}, \\\[\{a = 4, b = 5\}\\\] = \{x = 3, y = 4, z = 5\}, \\\[\{a = 5, b = 6\}\\\] = \{x = 6, y = 7, z = 8\}\}" \
+	    ]
+
+    gdb_print_expr_at_depths "*mm" \
+	[list \
+	     "\{\\.\\.\\.\}" \
+	     "\{\\\[$hex \"m1\"\\\] = \{\\.\\.\\.\}, \\\[$hex \"m2\"\\\] = \{\\.\\.\\.\}\}" \
+	     "\{\\\[$hex \"m1\"\\\] = \{\\\[\{a = 3, b = 4\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 4, b = 5\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 5, b = 6\}\\\] = \{\\.\\.\\.\}\}, \\\[$hex \"m2\"\\\] = \{\\\[\{a = 6, b = 7\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 7, b = 8\}\\\] = \{\\.\\.\\.\}, \\\[\{a = 8, b = 9\}\\\] = \{\\.\\.\\.\}\}\}" \
+	     "\{\\\[$hex \"m1\"\\\] = \{\\\[\{a = 3, b = 4\}\\\] = \{x = 0, y = 1, z = 2\}, \\\[\{a = 4, b = 5\}\\\] = \{x = 3, y = 4, z = 5\}, \\\[\{a = 5, b = 6\}\\\] = \{x = 6, y = 7, z = 8\}\}, \\\[$hex \"m2\"\\\] = \{\\\[\{a = 6, b = 7\}\\\] = \{x = 9, y = 0, z = 1\}, \\\[\{a = 7, b = 8\}\\\] = \{x = 2, y = 3, z = 4\}, \\\[\{a = 8, b = 9\}\\\] = \{x = 5, y = 6, z = 7\}\}\}" \
+	    ]
+}
+
+# Now again, but with 'set print pretty on'
+gdb_test_no_output "set print pretty on"
+with_test_prefix "pretty=on" {
+    gdb_print_expr_at_depths "*m1" \
+	[list \
+	     "\{\\.\\.\\.\}" \
+	     [multi_line \
+		  " = \{" \
+		  "  \\\[\{" \
+		  "    a = 3," \
+		  "    b = 4" \
+		  "  \}\\\] = \{\\.\\.\\.\}," \
+		  "  \\\[\{" \
+		  "    a = 4," \
+		  "    b = 5" \
+		  "  \}\\\] = \{\\.\\.\\.\}," \
+		  "  \\\[\{" \
+		  "    a = 5," \
+		  "    b = 6" \
+		  "  \}\\\] = \{\\.\\.\\.\}" \
+		  "\}" ] \
+	     [multi_line \
+		  " = \{" \
+		  "  \\\[\{" \
+		  "    a = 3," \
+		  "    b = 4" \
+		  "  \}\\\] = \{" \
+		  "    x = 0," \
+		  "    y = 1," \
+		  "    z = 2" \
+		  "  \}," \
+		  "  \\\[\{" \
+		  "    a = 4," \
+		  "    b = 5" \
+		  "  \}\\\] = \{" \
+		  "    x = 3," \
+		  "    y = 4," \
+		  "    z = 5" \
+		  "  \}," \
+		  "  \\\[\{" \
+		  "    a = 5," \
+		  "    b = 6" \
+		  "  \}\\\] = \{" \
+		  "    x = 6," \
+		  "    y = 7," \
+		  "    z = 8" \
+		  "  \}" \
+		  "\}" ] \
+	    ]
+
+     gdb_print_expr_at_depths "*mm" \
+	[list \
+	     "\{\\.\\.\\.\}" \
+	     [multi_line \
+		  " = \{" \
+		  "  \\\[$hex \"m1\"\\\] = \{\\.\\.\\.\}," \
+		  "  \\\[$hex \"m2\"\\\] = \{\\.\\.\\.\}" \
+		  "\}" ] \
+	     [multi_line \
+		  " = \{" \
+		  "  \\\[$hex \"m1\"\\\] = \{" \
+		  "    \\\[\{" \
+		  "      a = 3," \
+		  "      b = 4" \
+		  "    \}\\\] = \{\\.\\.\\.\}," \
+		  "    \\\[\{" \
+		  "      a = 4," \
+		  "      b = 5" \
+		  "    \}\\\] = \{\\.\\.\\.\}," \
+		  "    \\\[\{" \
+		  "      a = 5," \
+		  "      b = 6" \
+		  "    \}\\\] = \{\\.\\.\\.\}" \
+		  "  \}," \
+		  "  \\\[$hex \"m2\"\\\] = \{" \
+		  "    \\\[\{" \
+		  "      a = 6," \
+		  "      b = 7" \
+		  "    \}\\\] = \{\\.\\.\\.\}," \
+		  "    \\\[\{" \
+		  "      a = 7," \
+		  "      b = 8" \
+		  "    \}\\\] = \{\\.\\.\\.\}," \
+		  "    \\\[\{" \
+		  "      a = 8," \
+		  "      b = 9" \
+		  "    \}\\\] = \{\\.\\.\\.\}" \
+		  "  \}" \
+		  "\}" ] \
+	     [multi_line \
+		  " = \{" \
+		  "  \\\[$hex \"m1\"\\\] = \{" \
+		  "    \\\[\{" \
+		  "      a = 3," \
+		  "      b = 4" \
+		  "    \}\\\] = \{" \
+		  "      x = 0," \
+		  "      y = 1," \
+		  "      z = 2" \
+		  "    \}," \
+		  "    \\\[\{" \
+		  "      a = 4," \
+		  "      b = 5" \
+		  "    \}\\\] = \{" \
+		  "      x = 3," \
+		  "      y = 4," \
+		  "      z = 5" \
+		  "    \}," \
+		  "    \\\[\{" \
+		  "      a = 5," \
+		  "      b = 6" \
+		  "    \}\\\] = \{" \
+		  "      x = 6," \
+		  "      y = 7," \
+		  "      z = 8" \
+		  "    \}" \
+		  "  \}," \
+		  "  \\\[$hex \"m2\"\\\] = \{" \
+		  "    \\\[\{" \
+		  "      a = 6," \
+		  "      b = 7" \
+		  "    \}\\\] = \{" \
+		  "      x = 9," \
+		  "      y = 0," \
+		  "      z = 1" \
+		  "    \}," \
+		  "    \\\[\{" \
+		  "      a = 7," \
+		  "      b = 8" \
+		  "    \}\\\] = \{" \
+		  "      x = 2," \
+		  "      y = 3," \
+		  "      z = 4" \
+		  "    \}," \
+		  "    \\\[\{" \
+		  "      a = 8," \
+		  "      b = 9" \
+		  "    \}\\\] = \{" \
+		  "      x = 5," \
+		  "      y = 6," \
+		  "      z = 7" \
+		  "    \}" \
+		  "  \}" \
+		  "\}" ] \
+	    ]
+}
diff --git a/gdb/testsuite/gdb.python/py-nested-maps.py b/gdb/testsuite/gdb.python/py-nested-maps.py
new file mode 100644
index 00000000000..e6489d5636c
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-nested-maps.py
@@ -0,0 +1,129 @@ 
+# Copyright (C) 2019 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.  It tests GDB's printing of
+# nested map like structures.
+
+import re
+import gdb
+
+def _iterator1 (pointer, len):
+    while len > 0:
+        map = pointer.dereference()
+        yield ('', map['name'])
+        yield ('', map.dereference())
+        pointer += 1
+        len -= 1
+
+def _iterator2 (pointer1, pointer2, len):
+    while len > 0:
+        yield ("", pointer1.dereference())
+        yield ("", pointer2.dereference())
+        pointer1 += 1
+        pointer2 += 1
+        len -= 1
+
+class pp_map (object):
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        return None
+
+    def children(self):
+        return _iterator2(self.val['keys'],
+                          self.val['values'],
+                          self.val['length'])
+
+    def display_hint (self):
+        return 'map'
+
+class pp_map_map (object):
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        return None
+
+    def children(self):
+        return _iterator1(self.val['values'],
+                          self.val['length'])
+
+    def display_hint (self):
+        return 'map'
+
+def lookup_function (val):
+    "Look-up and return a pretty-printer that can print val."
+
+    # Get the type.
+    type = val.type
+
+    # If it points to a reference, get the reference.
+    if type.code == gdb.TYPE_CODE_REF:
+        type = type.target ()
+
+    # Get the unqualified type, stripped of typedefs.
+    type = type.unqualified ().strip_typedefs ()
+
+    # Get the type name.
+    typename = type.tag
+
+    if typename == None:
+        return None
+
+    # Iterate over local dictionary of types to determine
+    # if a printer is registered for that type.  Return an
+    # instantiation of the printer if found.
+    for function in pretty_printers_dict:
+        if function.match (typename):
+            return pretty_printers_dict[function] (val)
+
+    # Cannot find a pretty printer.  Return None.
+    return None
+
+# Lookup a printer for VAL in the typedefs dict.
+def lookup_typedefs_function (val):
+    "Look-up and return a pretty-printer that can print val (typedefs)."
+
+    # Get the type.
+    type = val.type
+
+    if type == None or type.name == None or type.code != gdb.TYPE_CODE_TYPEDEF:
+        return None
+
+    # Iterate over local dictionary of typedef types to determine if a
+    # printer is registered for that type.  Return an instantiation of
+    # the printer if found.
+    for function in typedefs_pretty_printers_dict:
+        if function.match (type.name):
+            return typedefs_pretty_printers_dict[function] (val)
+
+    # Cannot find a pretty printer.
+    return None
+
+def register_pretty_printers ():
+    pretty_printers_dict[re.compile ('^struct map_t$')] = pp_map
+    pretty_printers_dict[re.compile ('^map_t$')] = pp_map
+    pretty_printers_dict[re.compile ('^struct map_map_t$')] = pp_map_map
+    pretty_printers_dict[re.compile ('^map_map_t$')] = pp_map_map
+
+# Dict for struct types with typedefs fully stripped.
+pretty_printers_dict = {}
+# Dict for typedef types.
+typedefs_pretty_printers_dict = {}
+
+register_pretty_printers ()
+gdb.pretty_printers.append (lookup_function)
+gdb.pretty_printers.append (lookup_typedefs_function)
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index e3c6b2d6f5e..629107251b7 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -1390,6 +1390,36 @@  proc gdb_test_stdio {command inferior_pattern {gdb_pattern ""} {message ""}} {
     return $res
 }
 
+# get_print_expr_at_depths EXP OUTPUTS
+#
+# Used for testing 'set print max-depth'.  Prints the expression EXP
+# with 'set print max-depth' set to various depths.  OUTPUTS is a list
+# of `n` different patterns to match at each of the depths from 0 to
+# (`n` - 1).
+#
+# This proc does one final check with the max-depth set to 'unlimited'
+# which is tested against the last pattern in the OUTPUTS list.  The
+# OUTPUTS list is therefore required to match every depth from 0 to a
+# depth where the whole of EXP is printed with no ellipsis.
+#
+# This proc leaves the 'set print max-depth' set to 'unlimited'.
+proc gdb_print_expr_at_depths {exp outputs} {
+    for { set depth 0 } { $depth <= [llength $outputs] } { incr depth } {
+	if { $depth == [llength $outputs] } {
+	    set expected_result [lindex $outputs [expr [llength $outputs] - 1]]
+	    set depth_string "unlimited"
+	} else {
+	    set expected_result [lindex $outputs $depth]
+	    set depth_string $depth
+	}
+
+	with_test_prefix "exp='$exp': depth=${depth_string}" {
+	    gdb_test_no_output "set print max-depth ${depth_string}"
+	    gdb_test "p $exp" "$expected_result"
+	}
+    }
+}
+
 
 
 # Issue a PASS and return true if evaluating CONDITION in the caller's
diff --git a/gdb/valprint.c b/gdb/valprint.c
index 10020901c2d..e47ac981ce8 100644
--- a/gdb/valprint.c
+++ b/gdb/valprint.c
@@ -28,6 +28,7 @@ 
 #include "annotate.h"
 #include "valprint.h"
 #include "target-float.h"
+#include "c-lang.h" /* For c_textual_element_type.  */
 #include "extension.h"
 #include "ada-lang.h"
 #include "gdb_obstack.h"
@@ -88,6 +89,7 @@  static void val_print_type_code_flags (struct type *type,
 				       struct ui_file *stream);
 
 #define PRINT_MAX_DEFAULT 200	/* Start print_max off at this value.  */
+#define PRINT_MAX_DEPTH_DEFAULT 20	/* Start print_max_depth off at this value. */
 
 struct value_print_options user_print_options =
 {
@@ -109,7 +111,8 @@  struct value_print_options user_print_options =
   1,				/* pascal_static_field_print */
   0,				/* raw */
   0,				/* summary */
-  1				/* symbol_print */
+  1,				/* symbol_print */
+  PRINT_MAX_DEPTH_DEFAULT	/* max_depth */
 };
 
 /* Initialize *OPTS to be a copy of the user print options.  */
@@ -281,6 +284,39 @@  val_print_scalar_type_p (struct type *type)
     }
 }
 
+/* A helper function for val_print.  When printing with limited depth we
+   want to print string and scalar arguments, but not aggregate arguments.
+   This function distinguishes between the two.  */
+
+static bool
+val_print_scalar_or_string_type_p (struct type *type)
+{
+  /* Resolve the real type.  */
+  type = check_typedef (type);
+  while (TYPE_CODE (type) == TYPE_CODE_REF)
+    {
+      type = TYPE_TARGET_TYPE (type);
+      type = check_typedef (type);
+    }
+
+  switch (TYPE_CODE (type))
+    {
+    case TYPE_CODE_ARRAY:
+      {
+	/* See if target type looks like a string.  */
+	struct type * array_target_type = TYPE_TARGET_TYPE (type);
+	return (TYPE_LENGTH (type) > 0
+		&& TYPE_LENGTH (array_target_type) > 0
+		&& c_textual_element_type (array_target_type, 0));
+      }
+    case TYPE_CODE_STRING:
+      return true;
+    default:
+      /* Check for scalar.  */
+      return val_print_scalar_type_p(type);
+    }
+}
+
 /* See its definition in value.h.  */
 
 int
@@ -1054,6 +1090,15 @@  val_print (struct type *type, LONGEST embedded_offset,
       return;
     }
 
+  if (!val_print_scalar_or_string_type_p (type)
+      && options->max_depth > -1
+      && recurse >= options->max_depth)
+    {
+      gdb_assert (language->la_struct_too_deep_ellipsis != NULL);
+      fputs_filtered (language->la_struct_too_deep_ellipsis, stream);
+      return;
+    }
+
   TRY
     {
       language->la_val_print (type, embedded_offset, address,
@@ -2860,6 +2905,15 @@  val_print_string (struct type *elttype, const char *encoding,
 
   return (bytes_read / width);
 }
+
+/* Handle 'show print max-depth'.  */
+
+static void
+show_print_max_depth (struct ui_file *file, int from_tty,
+		      struct cmd_list_element *c, const char *value)
+{
+  fprintf_filtered (file, _("Maximum print depth is %s.\n"), value);
+}
 
 
 /* The 'set input-radix' command writes to this auxiliary variable.
@@ -3153,4 +3207,14 @@  Use 'show input-radix' or 'show output-radix' to independently show each."),
 Set printing of array indexes."), _("\
 Show printing of array indexes"), NULL, NULL, show_print_array_indexes,
                            &setprintlist, &showprintlist);
+
+  add_setshow_zuinteger_unlimited_cmd ("max-depth", class_support,
+                            &user_print_options.max_depth, _("\
+Set maximum print depth for nested structures, unions and arrays."), _("\
+Show maximum print depth for nested structures, unions, and arrays."), _("\
+When structures, unions, or arrays are nested beyond this depth then they\n\
+will be replaced with either '{...}' or '(...)' depending on the language.\n\
+Use 'set print max-depth unlimited' to print the complete structure."),
+				       NULL, show_print_max_depth,
+				       &setprintlist, &showprintlist);
 }
diff --git a/gdb/valprint.h b/gdb/valprint.h
index db99b52b2ad..a0395f67a07 100644
--- a/gdb/valprint.h
+++ b/gdb/valprint.h
@@ -92,6 +92,9 @@  struct value_print_options
   /* If nonzero, when printing a pointer, print the symbol to which it
      points, if any.  */
   int symbol_print;
+
+  /* Maximum print depth when printing nested aggregates.  */
+  int max_depth;
 };
 
 /* The global print options set by the user.  In general this should