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
