public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] Implement pahole-like 'ptype /o' option
@ 2017-11-21 16:07 Sergio Durigan Junior
  2017-11-21 16:16 ` Sergio Durigan Junior
                   ` (8 more replies)
  0 siblings, 9 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-11-21 16:07 UTC (permalink / raw)
  To: GDB Patches; +Cc: Sergio Durigan Junior

This commit implements the pahole-like '/o' option for 'ptype', which
prints the offsets and sizes of struct fields, reporting whenever
there is a hole found.

The output is heavily based on pahole(1), with a few modifications
here and there to adjust it to our reality.  Here's an example:

  (gdb) ptype /o stap_probe
  struct stap_probe {
  /* offset    |  size */
  /*    0      |    40 */    probe p;
  /*   40      |     8 */    CORE_ADDR sem_addr;
  /*   48:31   |     4 */    unsigned int args_parsed : 1;
  /* XXX  7-bit hole   */
  /* XXX  7-byte hole  */
  /*   56      |     8 */    union {
				 const char *text;
				 VEC_stap_probe_arg_s *vec;
			     } args_u;
  } /* total size:   64 bytes */

The idea is to print offsets and sizes only for structs, so unions and
other types are mostly ignored.

A big part of this patch handles the formatting logic of 'ptype',
which is a bit messy.  I tried to be not very invasive, but I intend
to submit a follow-up patch cleaning a few things up in this code.

This patch is the start of a long-term work I'll do to flush the local
patches we carry for Fedora GDB.  In this specific case, I'm aiming at
upstreaming the feature implemented by the 'pahole.py' script that is
shipped with Fedora GDB:

  <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>

This has been regression-tested on the BuildBot.  There's a new
testcase for it, along with an update to the documentation.  I also
thought it was worth mentioning this feature in the NEWS file.

gdb/ChangeLog:
2017-11-21  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
	* c-typeprint.c (OFFSET_SPC_LEN): New define.
	(print_spaces_filtered_with_print_options): New function.
	(output_access_specifier): Take new argument FLAGS.  Modify
	function to call 'print_spaces_filtered_with_print_options'.
	(c_print_type_offset): New function.
	(c_type_print_base): Print offsets and sizes for struct
	fields.
	* typeprint.c (const struct type_print_options
	type_print_raw_options): Initialize 'print_offsets'.
	(static struct type_print_options default_ptype_flags):
	Likewise.
	(whatis_exp): Handle '/o' option.
	(_initialize_typeprint): Add '/o' flag to ptype's help.
	* typeprint.h (struct type_print_options) <print_offsets>: New
	field.

gdb/testsuite/ChangeLog:
2017-11-21  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.base/ptype-offsets.cc: New file.
	* gdb.base/ptype-offsets.exp: New file.

gdb/doc/ChangeLog:
2017-11-21  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.texinfo (ptype): Add new flag '/o'.
---
 gdb/NEWS                                 |   3 +
 gdb/c-typeprint.c                        | 144 ++++++++++++++++++++++++++++---
 gdb/doc/gdb.texinfo                      |   4 +
 gdb/testsuite/gdb.base/ptype-offsets.cc  |  77 +++++++++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.exp |  52 +++++++++++
 gdb/typeprint.c                          |   8 +-
 gdb/typeprint.h                          |   3 +
 7 files changed, 276 insertions(+), 15 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index dc070facb8..7aef0331a5 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * GDB now supports access to the guarded-storage-control registers and the
   software-based guarded-storage broadcast control registers on IBM z14.
 
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index ed5a1a4b8a..d69c789cb9 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
+					const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,14 +890,69 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
+/* Print information about the offset of TYPE inside its struct.
+   FIELD_IDX represents the index of this TYPE inside the struct, and
+   ENDPOS is the end position of the previous type (this is how we
+   calculate whether there are holes in the struct).  At the end,
+   ENDPOS is updated.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_offset (struct type *type, unsigned int field_idx,
+		     unsigned int *endpos, struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit;
+
+  if (*endpos > 0 && *endpos < bitpos)
+    {
+      /* If ENDPOS is smaller than the current type's bitpos, it means
+	 there's a hole in the struct, so we report it here.  */
+      unsigned int hole = bitpos - *endpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  /* The position of the field, relative to the beginning of the
+     struct.  Assume this number will have 4 digits.  */
+  fprintf_filtered (stream, "/* %4u", bitpos / TARGET_CHAR_BIT);
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      fieldsize_bit = TYPE_FIELD_BITSIZE (type, field_idx);
+      fprintf_filtered (stream, ":%u",
+			fieldsize_byte * TARGET_CHAR_BIT - fieldsize_bit);
+    }
+  else
+    {
+      fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  *endpos = bitpos + fieldsize_bit;
+}
+
 /* Print the name of the type (or the ultimate pointer target,
    function value or array element), or the description of a structure
    or union.
@@ -1145,6 +1223,11 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 
 	    len = TYPE_NFIELDS (type);
 	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
+	    unsigned int endpos = 0;
+	    if (flags->print_offsets
+		&& TYPE_CODE (type) == TYPE_CODE_STRUCT)
+	      fprintf_filtered (stream,
+				"/* offset    |  size */\n");
 	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
 	      {
 		QUIT;
@@ -1161,17 +1244,32 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		    section_type = output_access_specifier
 		      (stream, section_type, level,
 		       TYPE_FIELD_PROTECTED (type, i),
-		       TYPE_FIELD_PRIVATE (type, i));
+		       TYPE_FIELD_PRIVATE (type, i),
+		       flags);
+		  }
+
+		bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+		if (flags->print_offsets)
+		  {
+		    if (!is_static && TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		      c_print_type_offset (type, i, &endpos, stream);
+		    else
+		      {
+			/* We don't print offset info for union
+			   fields.  */
+			print_spaces_filtered (OFFSET_SPC_LEN, stream);
+		      }
 		  }
 
 		print_spaces_filtered (level + 4, stream);
-		if (field_is_static (&TYPE_FIELD (type, i)))
+		if (is_static)
 		  fprintf_filtered (stream, "static ");
 		c_print_type (TYPE_FIELD_TYPE (type, i),
 			      TYPE_FIELD_NAME (type, i),
 			      stream, show - 1, level + 4,
 			      &local_flags);
-		if (!field_is_static (&TYPE_FIELD (type, i))
+		if (!is_static
 		    && TYPE_FIELD_PACKED (type, i))
 		  {
 		    /* It is a bitfield.  This code does not attempt
@@ -1235,9 +1333,11 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		  section_type = output_access_specifier
 		    (stream, section_type, level,
 		     TYPE_FN_FIELD_PROTECTED (f, j),
-		     TYPE_FN_FIELD_PRIVATE (f, j));
+		     TYPE_FN_FIELD_PRIVATE (f, j),
+		     flags);
 
-		  print_spaces_filtered (level + 4, stream);
+		  print_spaces_filtered_with_print_options (level + 4, stream,
+							    flags);
 		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
 		    fprintf_filtered (stream, "virtual ");
 		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
@@ -1256,9 +1356,15 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 			   && !is_full_physname_constructor  /* " " */
 			   && !is_type_conversion_operator (type, i, j))
 		    {
+		      unsigned int old_po = local_flags.print_offsets;
+
+		      /* Temporarily disable print_offsets, because it
+			 would mess with indentation.  */
+		      local_flags.print_offsets = 0;
 		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
 				    "", stream, -1, 0,
 				    &local_flags);
+		      local_flags.print_offsets = old_po;
 		      fputs_filtered (" ", stream);
 		    }
 		  if (TYPE_FN_FIELD_STUB (f, j))
@@ -1347,9 +1453,11 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		      section_type = output_access_specifier
 			(stream, section_type, level,
 			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i),
+			 flags);
 		    }
-		  print_spaces_filtered (level + 4, stream);
+		  print_spaces_filtered_with_print_options (level + 4,
+							    stream, flags);
 		  fprintf_filtered (stream, "typedef ");
 
 		  /* We want to print typedefs with substitutions
@@ -1363,9 +1471,17 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		}
 	    }
 
+	    if (flags->print_offsets && level > 0)
+	      print_spaces_filtered (OFFSET_SPC_LEN, stream);
+
 	    fprintfi_filtered (level, stream, "}");
 	  }
 
+	if (show > 0 && flags->print_offsets
+	    && TYPE_CODE (type) == TYPE_CODE_STRUCT)
+	  fprintf_filtered (stream, " /* total size: %4u bytes */",
+			    TYPE_LENGTH (type));
+
 	do_cleanups (local_cleanups);
       }
       break;
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 29d47892fc..a2dc03cfd2 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17071,6 +17071,10 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..f6f845db74
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,77 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
+
+#include <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  char field4;
+
+  /* 3-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..c06c124152
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,52 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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/>.
+
+standard_testfile .cc ptype-offsets.cc
+
+# Test only works on x86_64 LP64 targets.  That's how we guarantee
+# that the expected holes will be present in the struct.
+if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
+    untested "test work only on x86_64 lp64"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ optimize=-O0 }] } {
+    return -1
+}
+
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+"type = struct abc {" \
+"/\\\* offset    |  size \\\*/" \
+"                         public:" \
+"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
+"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
+"/\\\* XXX  7-bit hole   \\\*/" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   20      |     4 \\\*/    int field3;" \
+"/\\\*   24      |     1 \\\*/    char field4;" \
+"/\\\* XXX  7-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
+"/\\\*   40      |     8 \\\*/    union {" \
+"                               void \\\*field6;" \
+"                               int field7;" \
+"                           } field8;" \
+"" \
+"                           abc\\(void\\);" \
+"                           ~abc\\(\\);" \
+"} /\\\* total size:   48 bytes \\\*/"]
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 427af17ad7..36e2ea9a53 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,6 +42,7 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -54,6 +55,7 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -438,6 +440,9 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  flags.print_offsets = 1;
+		  break;
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -722,7 +727,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a458aa4e2f..65cef15abe 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -35,6 +35,9 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
   /* If not NULL, a local typedef hash table used when printing a
      type.  */
   struct typedef_hash_table *local_typedefs;
-- 
2.13.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH] Implement pahole-like 'ptype /o' option
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-11-21 16:16 ` Sergio Durigan Junior
  2017-11-21 16:50 ` Eli Zaretskii
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-11-21 16:16 UTC (permalink / raw)
  To: GDB Patches

On Tuesday, November 21 2017, I wrote:

[...]
> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
> new file mode 100644
> index 0000000000..f6f845db74
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
> @@ -0,0 +1,77 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
> +
> +#include <stdint.h>
> +
> +/* A struct with many types of fields, in order to test 'ptype
> +   /o'.  */
> +
> +struct abc
> +{
> +  /* Virtual destructor.  */
> +  virtual ~abc ()
> +  {}
> +
> +  /* 8-byte address.  Because of the virtual destructor above, this
> +     field's offset will be 8.  */
> +  void *field1;
> +
> +  /* No hole here.  */
> +
> +  /* 4-byte int bitfield of 1-bit.  */
> +  unsigned int field2 : 1;
> +
> +  /* 31-bit hole here.  */
> +
> +  /* 4-byte int.  */
> +  int field3;
> +
> +  /* No hole here.  */
> +
> +  /* 1-byte char.  */
> +  char field4;
> +
> +  /* 3-byte hole here.  */

This should be "7-byte hole".  I've updated my local tree.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH] Implement pahole-like 'ptype /o' option
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-11-21 16:16 ` Sergio Durigan Junior
@ 2017-11-21 16:50 ` Eli Zaretskii
  2017-11-21 17:00   ` Sergio Durigan Junior
  2017-11-26 19:27 ` Tom Tromey
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 75+ messages in thread
From: Eli Zaretskii @ 2017-11-21 16:50 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: gdb-patches

> From: Sergio Durigan Junior <sergiodj@redhat.com>
> Cc: Sergio Durigan Junior <sergiodj@redhat.com>
> Date: Tue, 21 Nov 2017 11:07:09 -0500
> 
>  gdb/NEWS                                 |   3 +
>  gdb/c-typeprint.c                        | 144 ++++++++++++++++++++++++++++---
>  gdb/doc/gdb.texinfo                      |   4 +
>  gdb/testsuite/gdb.base/ptype-offsets.cc  |  77 +++++++++++++++++
>  gdb/testsuite/gdb.base/ptype-offsets.exp |  52 +++++++++++
>  gdb/typeprint.c                          |   8 +-
>  gdb/typeprint.h                          |   3 +
>  7 files changed, 276 insertions(+), 15 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

The documentation parts are okay, but ...

> +@item o
> +Print the offsets and sizes of fields in a struct, similar to what the
> +@command{pahole} tool does.

... how about an example showing the output of this?  I, for one, have
never heard of 'pahole', so the reference to it won't help the likes
of myself to understand what will be produced.

And here:

> +  /o    print offsets and sizes of fields in a struct (like pahole)\n"));

I wonder whether we should mention 'pahole' at all.

Thanks.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH] Implement pahole-like 'ptype /o' option
  2017-11-21 16:50 ` Eli Zaretskii
@ 2017-11-21 17:00   ` Sergio Durigan Junior
  2017-11-21 19:14     ` Eli Zaretskii
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-11-21 17:00 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On Tuesday, November 21 2017, Eli Zaretskii wrote:

>> From: Sergio Durigan Junior <sergiodj@redhat.com>
>> Cc: Sergio Durigan Junior <sergiodj@redhat.com>
>> Date: Tue, 21 Nov 2017 11:07:09 -0500
>> 
>>  gdb/NEWS                                 |   3 +
>>  gdb/c-typeprint.c                        | 144 ++++++++++++++++++++++++++++---
>>  gdb/doc/gdb.texinfo                      |   4 +
>>  gdb/testsuite/gdb.base/ptype-offsets.cc  |  77 +++++++++++++++++
>>  gdb/testsuite/gdb.base/ptype-offsets.exp |  52 +++++++++++
>>  gdb/typeprint.c                          |   8 +-
>>  gdb/typeprint.h                          |   3 +
>>  7 files changed, 276 insertions(+), 15 deletions(-)
>>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
>>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp
>
> The documentation parts are okay, but ...
>
>> +@item o
>> +Print the offsets and sizes of fields in a struct, similar to what the
>> +@command{pahole} tool does.
>
> ... how about an example showing the output of this?  I, for one, have
> never heard of 'pahole', so the reference to it won't help the likes
> of myself to understand what will be produced.

Good point.  I will include an example output, then.

> And here:
>
>> +  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
>
> I wonder whether we should mention 'pahole' at all.

I thought that it was good to mention 'pahole' here because that was the
main motivation for writing this patch.  My feeling is that it's
benefitial to mention 'pahole' because even if the user doesn't know
what it is, this will be a good opportunity for them to learn :-).

But that's my personal opinion; I won't fight if you think the reference
should be removed.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH] Implement pahole-like 'ptype /o' option
  2017-11-21 17:00   ` Sergio Durigan Junior
@ 2017-11-21 19:14     ` Eli Zaretskii
  0 siblings, 0 replies; 75+ messages in thread
From: Eli Zaretskii @ 2017-11-21 19:14 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: gdb-patches

> From: Sergio Durigan Junior <sergiodj@redhat.com>
> Cc: gdb-patches@sourceware.org
> Date: Tue, 21 Nov 2017 12:00:14 -0500
> 
> >> +  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
> >
> > I wonder whether we should mention 'pahole' at all.
> 
> I thought that it was good to mention 'pahole' here because that was the
> main motivation for writing this patch.  My feeling is that it's
> benefitial to mention 'pahole' because even if the user doesn't know
> what it is, this will be a good opportunity for them to learn :-).
> 
> But that's my personal opinion; I won't fight if you think the reference
> should be removed.

I don't have strong a opinion.  Let's see what others think.  If no
one chimes in, feel free to leave it unchanged.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH] Implement pahole-like 'ptype /o' option
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-11-21 16:16 ` Sergio Durigan Junior
  2017-11-21 16:50 ` Eli Zaretskii
@ 2017-11-26 19:27 ` Tom Tromey
  2017-11-27 19:54   ` Sergio Durigan Junior
  2017-11-28 21:21 ` [PATCH v2] " Sergio Durigan Junior
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 75+ messages in thread
From: Tom Tromey @ 2017-11-26 19:27 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches

>>>>> "Sergio" == Sergio Durigan Junior <sergiodj@redhat.com> writes:

Sergio> This commit implements the pahole-like '/o' option for 'ptype', which
Sergio> prints the offsets and sizes of struct fields, reporting whenever
Sergio> there is a hole found.

Thanks for doing this!

Sergio> The idea is to print offsets and sizes only for structs, so unions and
Sergio> other types are mostly ignored.

I tried out the patch, since I want to add support for this feature to
the Rust language code.  I have two comments.

First, I think it would be valuable to descend into embedded structures.
This would make it simpler to review layout of the entire outer struct.
That is, like this:

    struct t {
      int x;
      int y;
    };

    struct s {
      int x;
      char c;
      struct t t;
    } s;

The old pahole.py did do this, like

    (gdb) pahole struct s
                  struct s {
     /*   0   4 */  int x
     /*   4   1 */  char c
      /* XXX 24 bit hole, try to pack */
     /*   8   8 */  struct t {
     /*   0   4 */    int x
     /*   4   4 */    int y
                    } t
                  } 

... though the old code's output is kind of confusing, restarting the
offset at 0 in the embedded struct.

Second, and similarly, descending into unions does seem to make sense
sometimes (pahole.py didn't do this, but it seems like it should have).

Continuing the above example, consider:

    union u {
      struct s s;
      int i;
    };

Here ptype shows:

    (gdb) ptype/o union u
    type = union u {
                               struct s s;
                               int i;
    }

(The formatting looks weird without the layout info here...)

I think one specific case where it is interesting to see the union's
layout is when you want to know why a union field in a struct is as
large as it is.  That is, what is the largest member of the union, in
case you want to try to shrink it?

Tom

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH] Implement pahole-like 'ptype /o' option
  2017-11-26 19:27 ` Tom Tromey
@ 2017-11-27 19:54   ` Sergio Durigan Junior
  2017-11-27 22:20     ` Tom Tromey
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-11-27 19:54 UTC (permalink / raw)
  To: Tom Tromey; +Cc: GDB Patches

On Sunday, November 26 2017, Tom Tromey wrote:

>>>>>> "Sergio" == Sergio Durigan Junior <sergiodj@redhat.com> writes:
>
> Sergio> This commit implements the pahole-like '/o' option for 'ptype', which
> Sergio> prints the offsets and sizes of struct fields, reporting whenever
> Sergio> there is a hole found.
>
> Thanks for doing this!

Thanks for the insights :-).

> Sergio> The idea is to print offsets and sizes only for structs, so unions and
> Sergio> other types are mostly ignored.
>
> I tried out the patch, since I want to add support for this feature to
> the Rust language code.  I have two comments.
>
> First, I think it would be valuable to descend into embedded structures.
> This would make it simpler to review layout of the entire outer struct.
> That is, like this:
>
>     struct t {
>       int x;
>       int y;
>     };
>
>     struct s {
>       int x;
>       char c;
>       struct t t;
>     } s;
>
> The old pahole.py did do this, like
>
>     (gdb) pahole struct s
>                   struct s {
>      /*   0   4 */  int x
>      /*   4   1 */  char c
>       /* XXX 24 bit hole, try to pack */
>      /*   8   8 */  struct t {
>      /*   0   4 */    int x
>      /*   4   4 */    int y
>                     } t
>                   } 

I agree.  In order to test the patch, I've even hacked GDB and made
'ptype' print more levels instead of just 1.  However, due to the
idiosincrasies of the formatting function + the fact that this feature
would be better off as a second patch, I left it as TODO.

I think for 'ptype /o' the best would be to stick with 2 levels, because
otherwise the output gets too messed up.  But I've been thinking about
sending another patch to enable the multi-level ptype anyway.

> ... though the old code's output is kind of confusing, restarting the
> offset at 0 in the embedded struct.

The way my code is written, it also restarts the offset at 0.  Do you
think it'd be better to keep using the offset from the outter struct?

> Second, and similarly, descending into unions does seem to make sense
> sometimes (pahole.py didn't do this, but it seems like it should have).
>
> Continuing the above example, consider:
>
>     union u {
>       struct s s;
>       int i;
>     };
>
> Here ptype shows:
>
>     (gdb) ptype/o union u
>     type = union u {
>                                struct s s;
>                                int i;
>     }
>
> (The formatting looks weird without the layout info here...)
>
> I think one specific case where it is interesting to see the union's
> layout is when you want to know why a union field in a struct is as
> large as it is.  That is, what is the largest member of the union, in
> case you want to try to shrink it?

Good point.  I've implemented this on the patch.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH] Implement pahole-like 'ptype /o' option
  2017-11-27 19:54   ` Sergio Durigan Junior
@ 2017-11-27 22:20     ` Tom Tromey
  2017-11-28  0:39       ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Tom Tromey @ 2017-11-27 22:20 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: Tom Tromey, GDB Patches

>>>>> "Sergio" == Sergio Durigan Junior <sergiodj@redhat.com> writes:

Sergio> I think for 'ptype /o' the best would be to stick with 2 levels, because
Sergio> otherwise the output gets too messed up.  But I've been thinking about
Sergio> sending another patch to enable the multi-level ptype anyway.

I see what you mean, though I wonder if it's better to just overflow and
let users with deeply-nested types just widen their terminals.

Alternatively the code could wrap_here() at strategic points.

>> ... though the old code's output is kind of confusing, restarting the
>> offset at 0 in the embedded struct.

Sergio> The way my code is written, it also restarts the offset at 0.  Do you
Sergio> think it'd be better to keep using the offset from the outter struct?

I am not 100% certain but I tend to think it's more understandable to
have the offsets refer to the outermost structure.

Tom

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH] Implement pahole-like 'ptype /o' option
  2017-11-27 22:20     ` Tom Tromey
@ 2017-11-28  0:39       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-11-28  0:39 UTC (permalink / raw)
  To: Tom Tromey; +Cc: GDB Patches

On Monday, November 27 2017, Tom Tromey wrote:

>>>>>> "Sergio" == Sergio Durigan Junior <sergiodj@redhat.com> writes:
>
> Sergio> I think for 'ptype /o' the best would be to stick with 2 levels, because
> Sergio> otherwise the output gets too messed up.  But I've been thinking about
> Sergio> sending another patch to enable the multi-level ptype anyway.
>
> I see what you mean, though I wonder if it's better to just overflow and
> let users with deeply-nested types just widen their terminals.

OK, so your idea is to always "un-nest" structures/unions when doing
'ptype /o' (just like pahole.py does).  That works for me, but I'd like
to hear other opinions as well.

> Alternatively the code could wrap_here() at strategic points.

Yeah, I'll see if I can do that, but I don't know.

>>> ... though the old code's output is kind of confusing, restarting the
>>> offset at 0 in the embedded struct.
>
> Sergio> The way my code is written, it also restarts the offset at 0.  Do you
> Sergio> think it'd be better to keep using the offset from the outter struct?
>
> I am not 100% certain but I tend to think it's more understandable to
> have the offsets refer to the outermost structure.

OK, that can be arranged :-).

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
                   ` (2 preceding siblings ...)
  2017-11-26 19:27 ` Tom Tromey
@ 2017-11-28 21:21 ` Sergio Durigan Junior
  2017-11-29  3:28   ` Eli Zaretskii
                     ` (2 more replies)
  2017-12-11 19:58 ` [PATCH v3 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
                   ` (4 subsequent siblings)
  8 siblings, 3 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-11-28 21:21 UTC (permalink / raw)
  To: GDB Patches; +Cc: Tom Tromey, Eli Zaretskii, Sergio Durigan Junior

Changes from v1:

- Address Tom's comments (the command now prints offset information
  about unions, and the offset info is carried on for nested structs).

- Address Eli's comments.

- Extended testcase.

- A "bit" of cleanup on 'c_type_print_base'.


This commit implements the pahole-like '/o' option for 'ptype', which
prints the offsets and sizes of struct fields, reporting whenever
there is a hole found.

The output is heavily based on pahole(1), with a few modifications
here and there to adjust it to our reality.  Here's an example:

  (gdb) ptype /o stap_probe
  /* offset    |  size */
  struct stap_probe {
  /*    0      |    40 */    struct probe {
  /*    0      |     8 */        const probe_ops *pops;
  /*    8      |     8 */        gdbarch *arch;
  /*   16      |     8 */        const char *name;
  /*   24      |     8 */        const char *provider;
  /*   32      |     8 */        CORE_ADDR address;
			     } /* total size:   40 bytes */ p;
  /*   40      |     8 */    CORE_ADDR sem_addr;
  /*   48:31   |     4 */    unsigned int args_parsed : 1;
  /* XXX  7-bit hole   */
  /* XXX  7-byte hole  */
  /*   56      |     8 */    union {
  /*                 8 */        const char *text;
  /*                 8 */        VEC_stap_probe_arg_s *vec;
			     } /* total size:    8 bytes */ args_u;
  } /* total size:   64 bytes */

A big part of this patch handles the formatting logic of 'ptype',
which is a bit messy.  I tried to be not very invasive, but I had to
do some cleanups here and there to make life easier.

This patch is the start of a long-term work I'll do to flush the local
patches we carry for Fedora GDB.  In this specific case, I'm aiming at
upstreaming the feature implemented by the 'pahole.py' script that is
shipped with Fedora GDB:

  <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>

This has been regression-tested on the BuildBot.  There's a new
testcase for it, along with an update to the documentation.  I also
thought it was worth mentioning this feature in the NEWS file.

gdb/ChangeLog:
2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
	* c-typeprint.c (OFFSET_SPC_LEN): New define.
	(print_spaces_filtered_with_print_options): New function.
	(output_access_specifier): Take new argument FLAGS.  Modify
	function to call 'print_spaces_filtered_with_print_options'.
	(c_print_type_union_field_offset): New function.
	(c_print_type_struct_field_offset): New function.
	(need_access_label_p): New function, with contents from
	'c_type_print_base'.
	(c_type_print_base_struct_union): Likewise.
	(c_type_print_base): Print offsets and sizes for struct
	fields.  Struct/union handling code move to functions
	mentioned above.
	* typeprint.c (const struct type_print_options
	type_print_raw_options): Initialize 'print_offsets' and
	'offset_bitpos'.
	(static struct type_print_options default_ptype_flags):
	Likewise.
	(whatis_exp): Handle '/o' option.
	(_initialize_typeprint): Add '/o' flag to ptype's help.
	* typeprint.h (struct type_print_options) <print_offsets>: New
	field.
	<offset_bitpos>: Likewise.

gdb/testsuite/ChangeLog:
2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.base/ptype-offsets.cc: New file.
	* gdb.base/ptype-offsets.exp: New file.

gdb/doc/ChangeLog:
2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.texinfo (ptype): Add new flag '/o'.
---
 gdb/NEWS                                 |    3 +
 gdb/c-typeprint.c                        | 1016 +++++++++++++++++-------------
 gdb/doc/gdb.texinfo                      |    4 +
 gdb/testsuite/gdb.base/ptype-offsets.cc  |  113 ++++
 gdb/testsuite/gdb.base/ptype-offsets.exp |   77 +++
 gdb/typeprint.c                          |   15 +-
 gdb/typeprint.h                          |    9 +
 7 files changed, 812 insertions(+), 425 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 754ce103bd..1247021046 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * GDB now uses the GNU MPFR library, if available, to emulate target
   floating-point arithmetic during expression evaluation when the target
   uses different floating-point formats than the host.  At least version
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index ed5a1a4b8a..39334ccf88 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
+					const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,14 +890,569 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
+/* Print information about the offset of TYPE inside its union.
+   FIELD_IDX represents the index of this TYPE inside the union.  We
+   just print the type size, and nothing more.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
+				 struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+
+  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+}
+
+/* Print information about the offset of TYPE inside its struct.
+   FIELD_IDX represents the index of this TYPE inside the struct, and
+   ENDPOS is the end position of the previous type (this is how we
+   calculate whether there are holes in the struct).  At the end,
+   ENDPOS is updated.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
+				  unsigned int *endpos, struct ui_file *stream,
+				  unsigned int offset_bitpos)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit;
+
+  if (*endpos > 0 && *endpos < bitpos)
+    {
+      /* If ENDPOS is smaller than the current type's bitpos, it means
+	 there's a hole in the struct, so we report it here.  */
+      unsigned int hole = bitpos - *endpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  /* The position of the field, relative to the beginning of the
+     struct.  Assume this number will have 4 digits.  */
+  fprintf_filtered (stream, "/* %4u",
+		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT);
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      fieldsize_bit = TYPE_FIELD_BITSIZE (type, field_idx);
+      fprintf_filtered (stream, ":%u",
+			fieldsize_byte * TARGET_CHAR_BIT - fieldsize_bit);
+    }
+  else
+    {
+      fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  *endpos = bitpos + fieldsize_bit;
+}
+
+/* Return true is an access label (i.e., "public:", "private:",
+   "protected:") needs to be printed for TYPE.  */
+
+static bool
+need_access_label_p (struct type *type)
+{
+  bool need_access_label = false;
+  int i, j;
+  int len, len2;
+
+  if (TYPE_DECLARED_CLASS (type))
+    {
+      QUIT;
+      len = TYPE_NFIELDS (type);
+      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
+	if (!TYPE_FIELD_PRIVATE (type, i))
+	  {
+	    need_access_label = true;
+	    break;
+	  }
+      QUIT;
+      if (!need_access_label)
+	{
+	  len2 = TYPE_NFN_FIELDS (type);
+	  for (j = 0; j < len2; j++)
+	    {
+	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
+	      for (i = 0; i < len; i++)
+		if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+								j), i))
+		  {
+		    need_access_label = true;
+		    break;
+		  }
+	      if (need_access_label)
+		break;
+	    }
+	}
+      QUIT;
+      if (!need_access_label)
+	{
+	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	    {
+	      if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+		{
+		  need_access_label = true;
+		  break;
+		}
+	    }
+	}
+    }
+  else
+    {
+      QUIT;
+      len = TYPE_NFIELDS (type);
+      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
+	if (TYPE_FIELD_PRIVATE (type, i)
+	    || TYPE_FIELD_PROTECTED (type, i))
+	  {
+	    need_access_label = true;
+	    break;
+	  }
+      QUIT;
+      if (!need_access_label)
+	{
+	  len2 = TYPE_NFN_FIELDS (type);
+	  for (j = 0; j < len2; j++)
+	    {
+	      QUIT;
+	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
+	      for (i = 0; i < len; i++)
+		if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
+								 j), i)
+		    || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+								  j),
+					      i))
+		  {
+		    need_access_label = true;
+		    break;
+		  }
+	      if (need_access_label)
+		break;
+	    }
+	}
+      QUIT;
+      if (!need_access_label)
+	{
+	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	    {
+	      if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
+		  || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+		{
+		  need_access_label = true;
+		  break;
+		}
+	    }
+	}
+    }
+  return need_access_label;
+}
+
+/* Helper for 'c_type_print_base' that handles structs and unions.
+   For a description of the arguments, see 'c_type_print_base'.  */
+
+static void
+c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
+				int show, int level,
+				const struct type_print_options *flags)
+{
+  struct type_print_options local_flags = *flags;
+  struct type_print_options semi_local_flags = *flags;
+  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
+
+  local_flags.local_typedefs = NULL;
+  semi_local_flags.local_typedefs = NULL;
+
+  if (!flags->raw)
+    {
+      if (flags->local_typedefs)
+	local_flags.local_typedefs
+	  = copy_typedef_hash (flags->local_typedefs);
+      else
+	local_flags.local_typedefs = create_typedef_hash ();
+
+      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
+    }
+
+  c_type_print_modifier (type, stream, 0, 1);
+  if (TYPE_CODE (type) == TYPE_CODE_UNION)
+    fprintf_filtered (stream, "union ");
+  else if (TYPE_DECLARED_CLASS (type))
+    fprintf_filtered (stream, "class ");
+  else
+    fprintf_filtered (stream, "struct ");
+
+  /* Print the tag if it exists.  The HP aCC compiler emits a
+     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
+     enum}" tag for unnamed struct/union/enum's, which we don't
+     want to print.  */
+  if (TYPE_TAG_NAME (type) != NULL
+      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
+    {
+      /* When printing the tag name, we are still effectively
+	 printing in the outer context, hence the use of FLAGS
+	 here.  */
+      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
+      if (show > 0)
+	fputs_filtered (" ", stream);
+    }
+
+  if (show < 0)
+    {
+      /* If we just printed a tag name, no need to print anything
+	 else.  */
+      if (TYPE_TAG_NAME (type) == NULL)
+	fprintf_filtered (stream, "{...}");
+    }
+  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
+    {
+      struct type *basetype;
+
+      c_type_print_template_args (&local_flags, type, stream);
+
+      /* Add in template parameters when printing derivation info.  */
+      add_template_parameters (local_flags.local_typedefs, type);
+      cp_type_print_derivation_info (stream, type, &local_flags);
+
+      /* This holds just the global typedefs and the template
+	 parameters.  */
+      semi_local_flags.local_typedefs
+	= copy_typedef_hash (local_flags.local_typedefs);
+      if (semi_local_flags.local_typedefs)
+	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
+
+      /* Now add in the local typedefs.  */
+      recursively_update_typedef_hash (local_flags.local_typedefs, type);
+
+      fprintf_filtered (stream, "{\n");
+      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
+	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
+	{
+	  if (TYPE_STUB (type))
+	    fprintfi_filtered (level + 4, stream,
+			       _("<incomplete type>\n"));
+	  else
+	    fprintfi_filtered (level + 4, stream,
+			       _("<no data fields>\n"));
+	}
+
+      /* Start off with no specific section type, so we can print
+	 one for the first field we find, and use that section type
+	 thereafter until we find another type.  */
+      enum access_specifier section_type = s_none;
+
+      /* For a class, if all members are private, there's no need
+	 for a "private:" label; similarly, for a struct or union
+	 masquerading as a class, if all members are public, there's
+	 no need for a "public:" label.  */
+      bool need_access_label = need_access_label_p (type);
+
+      /* If there is a base class for this type,
+	 do not print the field that it occupies.  */
+
+      int len = TYPE_NFIELDS (type);
+      int vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      unsigned int endpos = 0;
+
+      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
+	{
+	  QUIT;
+
+	  /* If we have a virtual table pointer, omit it.  Even if
+	     virtual table pointers are not specifically marked in
+	     the debug info, they should be artificial.  */
+	  if ((i == vptr_fieldno && type == basetype)
+	      || TYPE_FIELD_ARTIFICIAL (type, i))
+	    continue;
+
+	  if (need_access_label)
+	    {
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FIELD_PROTECTED (type, i),
+		 TYPE_FIELD_PRIVATE (type, i),
+		 flags);
+	    }
+
+	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+	  if (flags->print_offsets)
+	    {
+	      if (!is_static)
+		{
+		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		    c_print_type_struct_field_offset (type, i, &endpos, stream,
+						      flags->offset_bitpos);
+		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
+		    c_print_type_union_field_offset (type, i, stream);
+		}
+	      else
+		print_spaces_filtered (OFFSET_SPC_LEN, stream);
+	    }
+
+	  print_spaces_filtered (level + 4, stream);
+	  if (is_static)
+	    fprintf_filtered (stream, "static ");
+
+	  int newshow = show - 1;
+
+	  if (flags->print_offsets
+	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
+		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
+	    {
+	      /* If we're printing offsets and this field's type is
+		 either a struct or an union, then we're interested in
+		 expanding it.  */
+	      ++newshow;
+
+	      /* Make sure we carry our offset when we expand the
+		 struct.  */
+	      local_flags.offset_bitpos
+		= flags->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
+	    }
+
+	  c_print_type (TYPE_FIELD_TYPE (type, i),
+			TYPE_FIELD_NAME (type, i),
+			stream, newshow, level + 4,
+			&local_flags);
+	  if (!is_static
+	      && TYPE_FIELD_PACKED (type, i))
+	    {
+	      /* It is a bitfield.  This code does not attempt
+		 to look at the bitpos and reconstruct filler,
+		 unnamed fields.  This would lead to misleading
+		 results if the compiler does not put out fields
+		 for such things (I don't know what it does).  */
+	      fprintf_filtered (stream, " : %d",
+				TYPE_FIELD_BITSIZE (type, i));
+	    }
+	  fprintf_filtered (stream, ";\n");
+	}
+
+      /* If there are both fields and methods, put a blank line
+	 between them.  Make sure to count only method that we
+	 will display; artificial methods will be hidden.  */
+      len = TYPE_NFN_FIELDS (type);
+      if (!flags->print_methods)
+	len = 0;
+      int real_len = 0;
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  int j;
+
+	  for (j = 0; j < len2; j++)
+	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
+	      real_len++;
+	}
+      if (real_len > 0 && section_type != s_none)
+	fprintf_filtered (stream, "\n");
+
+      /* C++: print out the methods.  */
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
+	  const char *name = type_name_no_tag (type);
+	  int is_constructor = name && strcmp (method_name,
+					       name) == 0;
+
+	  for (j = 0; j < len2; j++)
+	    {
+	      const char *mangled_name;
+	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
+	      char *demangled_name;
+	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
+	      int is_full_physname_constructor =
+		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
+		|| is_constructor_name (physname)
+		|| is_destructor_name (physname)
+		|| method_name[0] == '~';
+
+	      /* Do not print out artificial methods.  */
+	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
+		continue;
+
+	      QUIT;
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FN_FIELD_PROTECTED (f, j),
+		 TYPE_FN_FIELD_PRIVATE (f, j),
+		 flags);
+
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
+		fprintf_filtered (stream, "virtual ");
+	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
+		fprintf_filtered (stream, "static ");
+	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
+		{
+		  /* Keep GDB from crashing here.  */
+		  fprintf_filtered (stream,
+				    _("<undefined type> %s;\n"),
+				    TYPE_FN_FIELD_PHYSNAME (f, j));
+		  break;
+		}
+	      else if (!is_constructor	/* Constructors don't
+					   have declared
+					   types.  */
+		       && !is_full_physname_constructor  /* " " */
+		       && !is_type_conversion_operator (type, i, j))
+		{
+		  unsigned int old_po = local_flags.print_offsets;
+
+		  /* Temporarily disable print_offsets, because it
+		     would mess with indentation.  */
+		  local_flags.print_offsets = 0;
+		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+				"", stream, -1, 0,
+				&local_flags);
+		  local_flags.print_offsets = old_po;
+		  fputs_filtered (" ", stream);
+		}
+	      if (TYPE_FN_FIELD_STUB (f, j))
+		{
+		  /* Build something we can demangle.  */
+		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
+		  mangled_name = mangled_name_holder.get ();
+		}
+	      else
+		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
+
+	      demangled_name =
+		gdb_demangle (mangled_name,
+			      DMGL_ANSI | DMGL_PARAMS);
+	      if (demangled_name == NULL)
+		{
+		  /* In some cases (for instance with the HP
+		     demangling), if a function has more than 10
+		     arguments, the demangling will fail.
+		     Let's try to reconstruct the function
+		     signature from the symbol information.  */
+		  if (!TYPE_FN_FIELD_STUB (f, j))
+		    {
+		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
+		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
+
+		      cp_type_print_method_args (mtype,
+						 "",
+						 method_name,
+						 staticp,
+						 stream, &local_flags);
+		    }
+		  else
+		    fprintf_filtered (stream,
+				      _("<badly mangled name '%s'>"),
+				      mangled_name);
+		}
+	      else
+		{
+		  char *p;
+		  char *demangled_no_class
+		    = remove_qualifiers (demangled_name);
+
+		  /* Get rid of the `static' appended by the
+		     demangler.  */
+		  p = strstr (demangled_no_class, " static");
+		  if (p != NULL)
+		    {
+		      int length = p - demangled_no_class;
+		      char *demangled_no_static;
+
+		      demangled_no_static
+			= (char *) xmalloc (length + 1);
+		      strncpy (demangled_no_static,
+			       demangled_no_class, length);
+		      *(demangled_no_static + length) = '\0';
+		      fputs_filtered (demangled_no_static, stream);
+		      xfree (demangled_no_static);
+		    }
+		  else
+		    fputs_filtered (demangled_no_class, stream);
+		  xfree (demangled_name);
+		}
+
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print typedefs defined in this class.  */
+
+      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
+	{
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
+	    {
+	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
+
+	      /* Dereference the typedef declaration itself.  */
+	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
+	      target = TYPE_TARGET_TYPE (target);
+
+	      if (need_access_label)
+		{
+		  section_type = output_access_specifier
+		    (stream, section_type, level,
+		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i),
+		     flags);
+		}
+	      print_spaces_filtered_with_print_options (level + 4,
+							stream, flags);
+	      fprintf_filtered (stream, "typedef ");
+
+	      /* We want to print typedefs with substitutions
+		 from the template parameters or globally-known
+		 typedefs but not local typedefs.  */
+	      c_print_type (target,
+			    TYPE_TYPEDEF_FIELD_NAME (type, i),
+			    stream, show - 1, level + 4,
+			    &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      if (flags->print_offsets && level > 0)
+	print_spaces_filtered (OFFSET_SPC_LEN, stream);
+
+      fprintfi_filtered (level, stream, "}");
+    }
+
+  if (show > 0 && flags->print_offsets)
+    fprintf_filtered (stream, " /* total size: %4u bytes */",
+		      TYPE_LENGTH (type));
+
+  do_cleanups (local_cleanups);
+}
+
 /* Print the name of the type (or the ultimate pointer target,
    function value or array element), or the description of a structure
    or union.
@@ -898,10 +1476,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		   int show, int level, const struct type_print_options *flags)
 {
   int i;
-  int len, real_len;
-  enum access_specifier section_type;
-  int need_access_label = 0;
-  int j, len2;
+  int len;
+  int j;
 
   QUIT;
 
@@ -918,15 +1494,16 @@ c_type_print_base (struct type *type, struct ui_file *stream,
      folk tend to expect things like "class5 *foo" rather than "struct
      class5 *foo".  */
 
-  if (show <= 0
-      && TYPE_NAME (type) != NULL)
+  struct type *ttype = check_typedef (type);
+
+  if (show <= 0 && TYPE_NAME (type) != NULL)
     {
       c_type_print_modifier (type, stream, 0, 1);
       print_name_maybe_canonical (TYPE_NAME (type), flags, stream);
       return;
     }
 
-  type = check_typedef (type);
+  type = ttype;
 
   switch (TYPE_CODE (type))
     {
@@ -958,416 +1535,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      {
-	struct type_print_options local_flags = *flags;
-	struct type_print_options semi_local_flags = *flags;
-	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
-
-	local_flags.local_typedefs = NULL;
-	semi_local_flags.local_typedefs = NULL;
-
-	if (!flags->raw)
-	  {
-	    if (flags->local_typedefs)
-	      local_flags.local_typedefs
-		= copy_typedef_hash (flags->local_typedefs);
-	    else
-	      local_flags.local_typedefs = create_typedef_hash ();
-
-	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
-	  }
-
-	c_type_print_modifier (type, stream, 0, 1);
-	if (TYPE_CODE (type) == TYPE_CODE_UNION)
-	  fprintf_filtered (stream, "union ");
-	else if (TYPE_DECLARED_CLASS (type))
-	  fprintf_filtered (stream, "class ");
-	else
-	  fprintf_filtered (stream, "struct ");
-
-	/* Print the tag if it exists.  The HP aCC compiler emits a
-	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
-	   enum}" tag for unnamed struct/union/enum's, which we don't
-	   want to print.  */
-	if (TYPE_TAG_NAME (type) != NULL
-	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
-	  {
-	    /* When printing the tag name, we are still effectively
-	       printing in the outer context, hence the use of FLAGS
-	       here.  */
-	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
-	    if (show > 0)
-	      fputs_filtered (" ", stream);
-	  }
-
-	if (show < 0)
-	  {
-	    /* If we just printed a tag name, no need to print anything
-	       else.  */
-	    if (TYPE_TAG_NAME (type) == NULL)
-	      fprintf_filtered (stream, "{...}");
-	  }
-	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
-	  {
-	    struct type *basetype;
-	    int vptr_fieldno;
-
-	    c_type_print_template_args (&local_flags, type, stream);
-
-	    /* Add in template parameters when printing derivation info.  */
-	    add_template_parameters (local_flags.local_typedefs, type);
-	    cp_type_print_derivation_info (stream, type, &local_flags);
-
-	    /* This holds just the global typedefs and the template
-	       parameters.  */
-	    semi_local_flags.local_typedefs
-	      = copy_typedef_hash (local_flags.local_typedefs);
-	    if (semi_local_flags.local_typedefs)
-	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
-
-	    /* Now add in the local typedefs.  */
-	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
-
-	    fprintf_filtered (stream, "{\n");
-	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
-		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
-	      {
-		if (TYPE_STUB (type))
-		  fprintfi_filtered (level + 4, stream,
-				     _("<incomplete type>\n"));
-		else
-		  fprintfi_filtered (level + 4, stream,
-				     _("<no data fields>\n"));
-	      }
-
-	    /* Start off with no specific section type, so we can print
-	       one for the first field we find, and use that section type
-	       thereafter until we find another type.  */
-
-	    section_type = s_none;
-
-	    /* For a class, if all members are private, there's no need
-	       for a "private:" label; similarly, for a struct or union
-	       masquerading as a class, if all members are public, there's
-	       no need for a "public:" label.  */
-
-	    if (TYPE_DECLARED_CLASS (type))
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (!TYPE_FIELD_PRIVATE (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									  j), i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-	    else
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (TYPE_FIELD_PRIVATE (type, i)
-		      || TYPE_FIELD_PROTECTED (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			QUIT;
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
-									   j), i)
-			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									    j),
-							i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
-			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-
-	    /* If there is a base class for this type,
-	       do not print the field that it occupies.  */
-
-	    len = TYPE_NFIELDS (type);
-	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
-	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-	      {
-		QUIT;
-
-		/* If we have a virtual table pointer, omit it.  Even if
-		   virtual table pointers are not specifically marked in
-		   the debug info, they should be artificial.  */
-		if ((i == vptr_fieldno && type == basetype)
-		    || TYPE_FIELD_ARTIFICIAL (type, i))
-		  continue;
-
-		if (need_access_label)
-		  {
-		    section_type = output_access_specifier
-		      (stream, section_type, level,
-		       TYPE_FIELD_PROTECTED (type, i),
-		       TYPE_FIELD_PRIVATE (type, i));
-		  }
-
-		print_spaces_filtered (level + 4, stream);
-		if (field_is_static (&TYPE_FIELD (type, i)))
-		  fprintf_filtered (stream, "static ");
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show - 1, level + 4,
-			      &local_flags);
-		if (!field_is_static (&TYPE_FIELD (type, i))
-		    && TYPE_FIELD_PACKED (type, i))
-		  {
-		    /* It is a bitfield.  This code does not attempt
-		       to look at the bitpos and reconstruct filler,
-		       unnamed fields.  This would lead to misleading
-		       results if the compiler does not put out fields
-		       for such things (I don't know what it does).  */
-		    fprintf_filtered (stream, " : %d",
-				      TYPE_FIELD_BITSIZE (type, i));
-		  }
-		fprintf_filtered (stream, ";\n");
-	      }
-
-	  /* If there are both fields and methods, put a blank line
-	     between them.  Make sure to count only method that we
-	     will display; artificial methods will be hidden.  */
-	  len = TYPE_NFN_FIELDS (type);
-	  if (!flags->print_methods)
-	    len = 0;
-	  real_len = 0;
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      int j;
-
-	      for (j = 0; j < len2; j++)
-		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		  real_len++;
-	    }
-	  if (real_len > 0 && section_type != s_none)
-	    fprintf_filtered (stream, "\n");
-
-	  /* C++: print out the methods.  */
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
-	      const char *name = type_name_no_tag (type);
-	      int is_constructor = name && strcmp (method_name,
-						   name) == 0;
-
-	      for (j = 0; j < len2; j++)
-		{
-		  const char *mangled_name;
-		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
-		  char *demangled_name;
-		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
-		  int is_full_physname_constructor =
-		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
-		    || is_constructor_name (physname)
-		    || is_destructor_name (physname)
-		    || method_name[0] == '~';
-
-		  /* Do not print out artificial methods.  */
-		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		    continue;
-
-		  QUIT;
-		  section_type = output_access_specifier
-		    (stream, section_type, level,
-		     TYPE_FN_FIELD_PROTECTED (f, j),
-		     TYPE_FN_FIELD_PRIVATE (f, j));
-
-		  print_spaces_filtered (level + 4, stream);
-		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
-		    fprintf_filtered (stream, "virtual ");
-		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
-		    fprintf_filtered (stream, "static ");
-		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
-		    {
-		      /* Keep GDB from crashing here.  */
-		      fprintf_filtered (stream,
-					_("<undefined type> %s;\n"),
-					TYPE_FN_FIELD_PHYSNAME (f, j));
-		      break;
-		    }
-		  else if (!is_constructor	/* Constructors don't
-						   have declared
-						   types.  */
-			   && !is_full_physname_constructor  /* " " */
-			   && !is_type_conversion_operator (type, i, j))
-		    {
-		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				    "", stream, -1, 0,
-				    &local_flags);
-		      fputs_filtered (" ", stream);
-		    }
-		  if (TYPE_FN_FIELD_STUB (f, j))
-		    {
-		      /* Build something we can demangle.  */
-		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
-		      mangled_name = mangled_name_holder.get ();
-		    }
-		  else
-		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
-
-		  demangled_name =
-		    gdb_demangle (mangled_name,
-				  DMGL_ANSI | DMGL_PARAMS);
-		  if (demangled_name == NULL)
-		    {
-		      /* In some cases (for instance with the HP
-			 demangling), if a function has more than 10
-			 arguments, the demangling will fail.
-			 Let's try to reconstruct the function
-			 signature from the symbol information.  */
-		      if (!TYPE_FN_FIELD_STUB (f, j))
-			{
-			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
-			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
-
-			  cp_type_print_method_args (mtype,
-						     "",
-						     method_name,
-						     staticp,
-						     stream, &local_flags);
-			}
-		      else
-			fprintf_filtered (stream,
-					  _("<badly mangled name '%s'>"),
-					  mangled_name);
-		    }
-		  else
-		    {
-		      char *p;
-		      char *demangled_no_class
-			= remove_qualifiers (demangled_name);
-
-		      /* Get rid of the `static' appended by the
-			 demangler.  */
-		      p = strstr (demangled_no_class, " static");
-		      if (p != NULL)
-			{
-			  int length = p - demangled_no_class;
-			  char *demangled_no_static;
-
-			  demangled_no_static
-			    = (char *) xmalloc (length + 1);
-			  strncpy (demangled_no_static,
-				   demangled_no_class, length);
-			  *(demangled_no_static + length) = '\0';
-			  fputs_filtered (demangled_no_static, stream);
-			  xfree (demangled_no_static);
-			}
-		      else
-			fputs_filtered (demangled_no_class, stream);
-		      xfree (demangled_name);
-		    }
-
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print typedefs defined in this class.  */
-
-	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
-	    {
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
-		{
-		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
-
-		  /* Dereference the typedef declaration itself.  */
-		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
-		  target = TYPE_TARGET_TYPE (target);
-
-		  if (need_access_label)
-		    {
-		      section_type = output_access_specifier
-			(stream, section_type, level,
-			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
-		    }
-		  print_spaces_filtered (level + 4, stream);
-		  fprintf_filtered (stream, "typedef ");
-
-		  /* We want to print typedefs with substitutions
-		     from the template parameters or globally-known
-		     typedefs but not local typedefs.  */
-		  c_print_type (target,
-				TYPE_TYPEDEF_FIELD_NAME (type, i),
-				stream, show - 1, level + 4,
-				&semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	    fprintfi_filtered (level, stream, "}");
-	  }
-
-	do_cleanups (local_cleanups);
-      }
+      c_type_print_base_struct_union (type, stream, show, level, flags);
       break;
 
     case TYPE_CODE_ENUM:
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 675f6e7bc8..f7a45dd5dd 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17095,6 +17095,10 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..f9a57fd3db
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,113 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
+
+#include <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  char field4;
+
+  /* 7-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+};
+
+/* This struct will be nested inside 'struct xyz'.  */
+
+struct tuv
+{
+  int a1;
+
+  char *a2;
+
+  int a3;
+};
+
+/* This struct will be nested inside 'struct pqr'.  */
+
+struct xyz
+{
+  int f1;
+
+  char f2;
+
+  void *f3;
+
+  struct tuv f4;
+};
+
+/* A struct with a nested struct.  */
+
+struct pqr
+{
+  int ff1;
+
+  struct xyz ff2;
+
+  char ff3;
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+  struct pqr bar;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..4f84416dc5
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,77 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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/>.
+
+standard_testfile .cc ptype-offsets.cc
+
+# Test only works on x86_64 LP64 targets.  That's how we guarantee
+# that the expected holes will be present in the struct.
+if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
+    untested "test work only on x86_64 lp64"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ optimize=-O0 }] } {
+    return -1
+}
+
+# Test general offset printing, ctor/dtor printing, union, formatting.
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+"type = struct abc {" \
+"/\\\* offset    |  size \\\*/" \
+"                         public:" \
+"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
+"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
+"/\\\* XXX  7-bit hole   \\\*/" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   20      |     4 \\\*/    int field3;" \
+"/\\\*   24      |     1 \\\*/    char field4;" \
+"/\\\* XXX  7-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
+"/\\\*   40      |     8 \\\*/    union {" \
+"/\\\*                 8 \\\*/        void \\\*field6;" \
+"/\\\*                 4 \\\*/        int field7;" \
+"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
+"" \
+"                           abc\\(void\\);" \
+"                           ~abc\\(\\);" \
+"} /\\\* total size:   48 bytes \\\*/"] \
+    "ptype offset struct abc"
+
+# Test nested structs.
+gdb_test "ptype /o struct pqr" \
+    [multi_line \
+"type = struct pqr {" \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    16 \\\*/    struct xyz {" \
+"/\\\*    8      |     4 \\\*/        int f1;" \
+"/\\\*   12      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/        struct tuv {" \
+"/\\\*   24      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/            char *a2;" \
+"/\\\*   40      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*   48      |     1 \\\*/    char ff3;" \
+"} /\\\* total size:   56 bytes \\\*/"] \
+    "ptype offset struct pqr"
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 427af17ad7..1463e802ad 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,6 +42,8 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
+  0,				/* offset_bitpos */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -54,6 +56,8 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
+  0,				/* offset_bitpos */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -438,6 +442,9 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  flags.print_offsets = 1;
+		  break;
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -497,6 +504,11 @@ whatis_exp (const char *exp, int show)
 	real_type = value_rtti_type (val, &full, &top, &using_enc);
     }
 
+  if (flags.print_offsets &&
+      (TYPE_CODE (type) == TYPE_CODE_STRUCT
+       || TYPE_CODE (type) == TYPE_CODE_UNION))
+    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
+
   printf_filtered ("type = ");
 
   if (!flags.raw)
@@ -722,7 +734,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a458aa4e2f..a2a5285012 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -35,6 +35,15 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
+  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
+     This is needed for when we are printing nested structs and want
+     to make sure that the printed offset for each field carries off
+     the offset of the outter struct.  */
+  unsigned int offset_bitpos;
+
   /* If not NULL, a local typedef hash table used when printing a
      type.  */
   struct typedef_hash_table *local_typedefs;
-- 
2.13.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-11-28 21:21 ` [PATCH v2] " Sergio Durigan Junior
@ 2017-11-29  3:28   ` Eli Zaretskii
  2017-12-04 15:03   ` Sergio Durigan Junior
  2017-12-11 15:43   ` Simon Marchi
  2 siblings, 0 replies; 75+ messages in thread
From: Eli Zaretskii @ 2017-11-29  3:28 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: gdb-patches, tom

> From: Sergio Durigan Junior <sergiodj@redhat.com>
> Cc: Tom Tromey <tom@tromey.com>,
> 	Eli Zaretskii <eliz@gnu.org>,
> 	Sergio Durigan Junior <sergiodj@redhat.com>
> Date: Tue, 28 Nov 2017 16:21:37 -0500
> 
> Changes from v1:
> 
> - Address Tom's comments (the command now prints offset information
>   about unions, and the offset info is carried on for nested structs).
> 
> - Address Eli's comments.
> 
> - Extended testcase.
> 
> - A "bit" of cleanup on 'c_type_print_base'.

OK for the documentation parts.

Thanks.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-11-28 21:21 ` [PATCH v2] " Sergio Durigan Junior
  2017-11-29  3:28   ` Eli Zaretskii
@ 2017-12-04 15:03   ` Sergio Durigan Junior
  2017-12-04 15:41     ` Eli Zaretskii
  2017-12-08 21:32     ` Sergio Durigan Junior
  2017-12-11 15:43   ` Simon Marchi
  2 siblings, 2 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-04 15:03 UTC (permalink / raw)
  To: GDB Patches; +Cc: Tom Tromey, Eli Zaretskii

On Tuesday, November 28 2017, I wrote:

> Changes from v1:
>
> - Address Tom's comments (the command now prints offset information
>   about unions, and the offset info is carried on for nested structs).
>
> - Address Eli's comments.
>
> - Extended testcase.
>
> - A "bit" of cleanup on 'c_type_print_base'.

Ping.

>
> This commit implements the pahole-like '/o' option for 'ptype', which
> prints the offsets and sizes of struct fields, reporting whenever
> there is a hole found.
>
> The output is heavily based on pahole(1), with a few modifications
> here and there to adjust it to our reality.  Here's an example:
>
>   (gdb) ptype /o stap_probe
>   /* offset    |  size */
>   struct stap_probe {
>   /*    0      |    40 */    struct probe {
>   /*    0      |     8 */        const probe_ops *pops;
>   /*    8      |     8 */        gdbarch *arch;
>   /*   16      |     8 */        const char *name;
>   /*   24      |     8 */        const char *provider;
>   /*   32      |     8 */        CORE_ADDR address;
> 			     } /* total size:   40 bytes */ p;
>   /*   40      |     8 */    CORE_ADDR sem_addr;
>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>   /* XXX  7-bit hole   */
>   /* XXX  7-byte hole  */
>   /*   56      |     8 */    union {
>   /*                 8 */        const char *text;
>   /*                 8 */        VEC_stap_probe_arg_s *vec;
> 			     } /* total size:    8 bytes */ args_u;
>   } /* total size:   64 bytes */
>
> A big part of this patch handles the formatting logic of 'ptype',
> which is a bit messy.  I tried to be not very invasive, but I had to
> do some cleanups here and there to make life easier.
>
> This patch is the start of a long-term work I'll do to flush the local
> patches we carry for Fedora GDB.  In this specific case, I'm aiming at
> upstreaming the feature implemented by the 'pahole.py' script that is
> shipped with Fedora GDB:
>
>   <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>
>
> This has been regression-tested on the BuildBot.  There's a new
> testcase for it, along with an update to the documentation.  I also
> thought it was worth mentioning this feature in the NEWS file.
>
> gdb/ChangeLog:
> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>
> 	PR cli/16224
> 	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
> 	* c-typeprint.c (OFFSET_SPC_LEN): New define.
> 	(print_spaces_filtered_with_print_options): New function.
> 	(output_access_specifier): Take new argument FLAGS.  Modify
> 	function to call 'print_spaces_filtered_with_print_options'.
> 	(c_print_type_union_field_offset): New function.
> 	(c_print_type_struct_field_offset): New function.
> 	(need_access_label_p): New function, with contents from
> 	'c_type_print_base'.
> 	(c_type_print_base_struct_union): Likewise.
> 	(c_type_print_base): Print offsets and sizes for struct
> 	fields.  Struct/union handling code move to functions
> 	mentioned above.
> 	* typeprint.c (const struct type_print_options
> 	type_print_raw_options): Initialize 'print_offsets' and
> 	'offset_bitpos'.
> 	(static struct type_print_options default_ptype_flags):
> 	Likewise.
> 	(whatis_exp): Handle '/o' option.
> 	(_initialize_typeprint): Add '/o' flag to ptype's help.
> 	* typeprint.h (struct type_print_options) <print_offsets>: New
> 	field.
> 	<offset_bitpos>: Likewise.
>
> gdb/testsuite/ChangeLog:
> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>
> 	PR cli/16224
> 	* gdb.base/ptype-offsets.cc: New file.
> 	* gdb.base/ptype-offsets.exp: New file.
>
> gdb/doc/ChangeLog:
> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>
> 	PR cli/16224
> 	* gdb.texinfo (ptype): Add new flag '/o'.
> ---
>  gdb/NEWS                                 |    3 +
>  gdb/c-typeprint.c                        | 1016 +++++++++++++++++-------------
>  gdb/doc/gdb.texinfo                      |    4 +
>  gdb/testsuite/gdb.base/ptype-offsets.cc  |  113 ++++
>  gdb/testsuite/gdb.base/ptype-offsets.exp |   77 +++
>  gdb/typeprint.c                          |   15 +-
>  gdb/typeprint.h                          |    9 +
>  7 files changed, 812 insertions(+), 425 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 754ce103bd..1247021046 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,9 @@
>  
>  *** Changes since GDB 8.0
>  
> +* The 'ptype' command now accepts a '/o' flag, which prints the
> +  offsets and sizes of fields in a struct, like the pahole(1) tool.
> +
>  * GDB now uses the GNU MPFR library, if available, to emulate target
>    floating-point arithmetic during expression evaluation when the target
>    uses different floating-point formats than the host.  At least version
> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
> index ed5a1a4b8a..39334ccf88 100644
> --- a/gdb/c-typeprint.c
> +++ b/gdb/c-typeprint.c
> @@ -32,6 +32,14 @@
>  #include "cp-abi.h"
>  #include "cp-support.h"
>  
> +/* When printing the offsets of a struct and its fields (i.e., 'ptype
> +   /o'; type_print_options::print_offsets), we use this many
> +   characters when printing the offset information at the beginning of
> +   the line.  This is needed in order to generate the correct amount
> +   of whitespaces when no offset info should be printed for a certain
> +   field.  */
> +#define OFFSET_SPC_LEN 23
> +
>  /* A list of access specifiers used for printing.  */
>  
>  enum access_specifier
> @@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
>      fputs_filtered (_("] "), stream);
>  }
>  
> +/* Use 'print_spaces_filtered', but take into consideration the
> +   type_print_options FLAGS in order to determine how many whitespaces
> +   will be printed.  */
> +
> +static void
> +print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
> +					const struct type_print_options *flags)
> +{
> +  if (!flags->print_offsets)
> +    print_spaces_filtered (level, stream);
> +  else
> +    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
> +}
> +
>  /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
>     last access specifier output (typically returned by this function).  */
>  
>  static enum access_specifier
>  output_access_specifier (struct ui_file *stream,
>  			 enum access_specifier last_access,
> -			 int level, bool is_protected, bool is_private)
> +			 int level, bool is_protected, bool is_private,
> +			 const struct type_print_options *flags)
>  {
>    if (is_protected)
>      {
>        if (last_access != s_protected)
>  	{
>  	  last_access = s_protected;
> -	  fprintfi_filtered (level + 2, stream,
> -			     "protected:\n");
> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
> +	  fprintf_filtered (stream, "protected:\n");
>  	}
>      }
>    else if (is_private)
> @@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
>        if (last_access != s_private)
>  	{
>  	  last_access = s_private;
> -	  fprintfi_filtered (level + 2, stream,
> -			     "private:\n");
> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
> +	  fprintf_filtered (stream, "private:\n");
>  	}
>      }
>    else
> @@ -867,14 +890,569 @@ output_access_specifier (struct ui_file *stream,
>        if (last_access != s_public)
>  	{
>  	  last_access = s_public;
> -	  fprintfi_filtered (level + 2, stream,
> -			     "public:\n");
> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
> +	  fprintf_filtered (stream, "public:\n");
>  	}
>      }
>  
>    return last_access;
>  }
>  
> +/* Print information about the offset of TYPE inside its union.
> +   FIELD_IDX represents the index of this TYPE inside the union.  We
> +   just print the type size, and nothing more.
> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
> +				 struct ui_file *stream)
> +{
> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +
> +  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
> +}
> +
> +/* Print information about the offset of TYPE inside its struct.
> +   FIELD_IDX represents the index of this TYPE inside the struct, and
> +   ENDPOS is the end position of the previous type (this is how we
> +   calculate whether there are holes in the struct).  At the end,
> +   ENDPOS is updated.
> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
> +				  unsigned int *endpos, struct ui_file *stream,
> +				  unsigned int offset_bitpos)
> +{
> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
> +  unsigned int fieldsize_bit;
> +
> +  if (*endpos > 0 && *endpos < bitpos)
> +    {
> +      /* If ENDPOS is smaller than the current type's bitpos, it means
> +	 there's a hole in the struct, so we report it here.  */
> +      unsigned int hole = bitpos - *endpos;
> +      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
> +      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
> +
> +      if (hole_bit > 0)
> +	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
> +
> +      if (hole_byte > 0)
> +	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
> +    }
> +
> +  /* The position of the field, relative to the beginning of the
> +     struct.  Assume this number will have 4 digits.  */
> +  fprintf_filtered (stream, "/* %4u",
> +		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT);
> +
> +  if (TYPE_FIELD_PACKED (type, field_idx))
> +    {
> +      /* We're dealing with a bitfield.  Print how many bits are left
> +	 to be used.  */
> +      fieldsize_bit = TYPE_FIELD_BITSIZE (type, field_idx);
> +      fprintf_filtered (stream, ":%u",
> +			fieldsize_byte * TARGET_CHAR_BIT - fieldsize_bit);
> +    }
> +  else
> +    {
> +      fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
> +      fprintf_filtered (stream, "   ");
> +    }
> +
> +  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
> +
> +  *endpos = bitpos + fieldsize_bit;
> +}
> +
> +/* Return true is an access label (i.e., "public:", "private:",
> +   "protected:") needs to be printed for TYPE.  */
> +
> +static bool
> +need_access_label_p (struct type *type)
> +{
> +  bool need_access_label = false;
> +  int i, j;
> +  int len, len2;
> +
> +  if (TYPE_DECLARED_CLASS (type))
> +    {
> +      QUIT;
> +      len = TYPE_NFIELDS (type);
> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> +	if (!TYPE_FIELD_PRIVATE (type, i))
> +	  {
> +	    need_access_label = true;
> +	    break;
> +	  }
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  len2 = TYPE_NFN_FIELDS (type);
> +	  for (j = 0; j < len2; j++)
> +	    {
> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> +	      for (i = 0; i < len; i++)
> +		if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> +								j), i))
> +		  {
> +		    need_access_label = true;
> +		    break;
> +		  }
> +	      if (need_access_label)
> +		break;
> +	    }
> +	}
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> +	    {
> +	      if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> +		{
> +		  need_access_label = true;
> +		  break;
> +		}
> +	    }
> +	}
> +    }
> +  else
> +    {
> +      QUIT;
> +      len = TYPE_NFIELDS (type);
> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> +	if (TYPE_FIELD_PRIVATE (type, i)
> +	    || TYPE_FIELD_PROTECTED (type, i))
> +	  {
> +	    need_access_label = true;
> +	    break;
> +	  }
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  len2 = TYPE_NFN_FIELDS (type);
> +	  for (j = 0; j < len2; j++)
> +	    {
> +	      QUIT;
> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> +	      for (i = 0; i < len; i++)
> +		if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
> +								 j), i)
> +		    || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> +								  j),
> +					      i))
> +		  {
> +		    need_access_label = true;
> +		    break;
> +		  }
> +	      if (need_access_label)
> +		break;
> +	    }
> +	}
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> +	    {
> +	      if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
> +		  || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> +		{
> +		  need_access_label = true;
> +		  break;
> +		}
> +	    }
> +	}
> +    }
> +  return need_access_label;
> +}
> +
> +/* Helper for 'c_type_print_base' that handles structs and unions.
> +   For a description of the arguments, see 'c_type_print_base'.  */
> +
> +static void
> +c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
> +				int show, int level,
> +				const struct type_print_options *flags)
> +{
> +  struct type_print_options local_flags = *flags;
> +  struct type_print_options semi_local_flags = *flags;
> +  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
> +
> +  local_flags.local_typedefs = NULL;
> +  semi_local_flags.local_typedefs = NULL;
> +
> +  if (!flags->raw)
> +    {
> +      if (flags->local_typedefs)
> +	local_flags.local_typedefs
> +	  = copy_typedef_hash (flags->local_typedefs);
> +      else
> +	local_flags.local_typedefs = create_typedef_hash ();
> +
> +      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
> +    }
> +
> +  c_type_print_modifier (type, stream, 0, 1);
> +  if (TYPE_CODE (type) == TYPE_CODE_UNION)
> +    fprintf_filtered (stream, "union ");
> +  else if (TYPE_DECLARED_CLASS (type))
> +    fprintf_filtered (stream, "class ");
> +  else
> +    fprintf_filtered (stream, "struct ");
> +
> +  /* Print the tag if it exists.  The HP aCC compiler emits a
> +     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
> +     enum}" tag for unnamed struct/union/enum's, which we don't
> +     want to print.  */
> +  if (TYPE_TAG_NAME (type) != NULL
> +      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
> +    {
> +      /* When printing the tag name, we are still effectively
> +	 printing in the outer context, hence the use of FLAGS
> +	 here.  */
> +      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
> +      if (show > 0)
> +	fputs_filtered (" ", stream);
> +    }
> +
> +  if (show < 0)
> +    {
> +      /* If we just printed a tag name, no need to print anything
> +	 else.  */
> +      if (TYPE_TAG_NAME (type) == NULL)
> +	fprintf_filtered (stream, "{...}");
> +    }
> +  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
> +    {
> +      struct type *basetype;
> +
> +      c_type_print_template_args (&local_flags, type, stream);
> +
> +      /* Add in template parameters when printing derivation info.  */
> +      add_template_parameters (local_flags.local_typedefs, type);
> +      cp_type_print_derivation_info (stream, type, &local_flags);
> +
> +      /* This holds just the global typedefs and the template
> +	 parameters.  */
> +      semi_local_flags.local_typedefs
> +	= copy_typedef_hash (local_flags.local_typedefs);
> +      if (semi_local_flags.local_typedefs)
> +	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
> +
> +      /* Now add in the local typedefs.  */
> +      recursively_update_typedef_hash (local_flags.local_typedefs, type);
> +
> +      fprintf_filtered (stream, "{\n");
> +      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
> +	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
> +	{
> +	  if (TYPE_STUB (type))
> +	    fprintfi_filtered (level + 4, stream,
> +			       _("<incomplete type>\n"));
> +	  else
> +	    fprintfi_filtered (level + 4, stream,
> +			       _("<no data fields>\n"));
> +	}
> +
> +      /* Start off with no specific section type, so we can print
> +	 one for the first field we find, and use that section type
> +	 thereafter until we find another type.  */
> +      enum access_specifier section_type = s_none;
> +
> +      /* For a class, if all members are private, there's no need
> +	 for a "private:" label; similarly, for a struct or union
> +	 masquerading as a class, if all members are public, there's
> +	 no need for a "public:" label.  */
> +      bool need_access_label = need_access_label_p (type);
> +
> +      /* If there is a base class for this type,
> +	 do not print the field that it occupies.  */
> +
> +      int len = TYPE_NFIELDS (type);
> +      int vptr_fieldno = get_vptr_fieldno (type, &basetype);
> +      unsigned int endpos = 0;
> +
> +      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
> +	{
> +	  QUIT;
> +
> +	  /* If we have a virtual table pointer, omit it.  Even if
> +	     virtual table pointers are not specifically marked in
> +	     the debug info, they should be artificial.  */
> +	  if ((i == vptr_fieldno && type == basetype)
> +	      || TYPE_FIELD_ARTIFICIAL (type, i))
> +	    continue;
> +
> +	  if (need_access_label)
> +	    {
> +	      section_type = output_access_specifier
> +		(stream, section_type, level,
> +		 TYPE_FIELD_PROTECTED (type, i),
> +		 TYPE_FIELD_PRIVATE (type, i),
> +		 flags);
> +	    }
> +
> +	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
> +
> +	  if (flags->print_offsets)
> +	    {
> +	      if (!is_static)
> +		{
> +		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
> +		    c_print_type_struct_field_offset (type, i, &endpos, stream,
> +						      flags->offset_bitpos);
> +		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
> +		    c_print_type_union_field_offset (type, i, stream);
> +		}
> +	      else
> +		print_spaces_filtered (OFFSET_SPC_LEN, stream);
> +	    }
> +
> +	  print_spaces_filtered (level + 4, stream);
> +	  if (is_static)
> +	    fprintf_filtered (stream, "static ");
> +
> +	  int newshow = show - 1;
> +
> +	  if (flags->print_offsets
> +	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
> +		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
> +	    {
> +	      /* If we're printing offsets and this field's type is
> +		 either a struct or an union, then we're interested in
> +		 expanding it.  */
> +	      ++newshow;
> +
> +	      /* Make sure we carry our offset when we expand the
> +		 struct.  */
> +	      local_flags.offset_bitpos
> +		= flags->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
> +	    }
> +
> +	  c_print_type (TYPE_FIELD_TYPE (type, i),
> +			TYPE_FIELD_NAME (type, i),
> +			stream, newshow, level + 4,
> +			&local_flags);
> +	  if (!is_static
> +	      && TYPE_FIELD_PACKED (type, i))
> +	    {
> +	      /* It is a bitfield.  This code does not attempt
> +		 to look at the bitpos and reconstruct filler,
> +		 unnamed fields.  This would lead to misleading
> +		 results if the compiler does not put out fields
> +		 for such things (I don't know what it does).  */
> +	      fprintf_filtered (stream, " : %d",
> +				TYPE_FIELD_BITSIZE (type, i));
> +	    }
> +	  fprintf_filtered (stream, ";\n");
> +	}
> +
> +      /* If there are both fields and methods, put a blank line
> +	 between them.  Make sure to count only method that we
> +	 will display; artificial methods will be hidden.  */
> +      len = TYPE_NFN_FIELDS (type);
> +      if (!flags->print_methods)
> +	len = 0;
> +      int real_len = 0;
> +      for (int i = 0; i < len; i++)
> +	{
> +	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
> +	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
> +	  int j;
> +
> +	  for (j = 0; j < len2; j++)
> +	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
> +	      real_len++;
> +	}
> +      if (real_len > 0 && section_type != s_none)
> +	fprintf_filtered (stream, "\n");
> +
> +      /* C++: print out the methods.  */
> +      for (int i = 0; i < len; i++)
> +	{
> +	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
> +	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
> +	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
> +	  const char *name = type_name_no_tag (type);
> +	  int is_constructor = name && strcmp (method_name,
> +					       name) == 0;
> +
> +	  for (j = 0; j < len2; j++)
> +	    {
> +	      const char *mangled_name;
> +	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
> +	      char *demangled_name;
> +	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
> +	      int is_full_physname_constructor =
> +		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
> +		|| is_constructor_name (physname)
> +		|| is_destructor_name (physname)
> +		|| method_name[0] == '~';
> +
> +	      /* Do not print out artificial methods.  */
> +	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
> +		continue;
> +
> +	      QUIT;
> +	      section_type = output_access_specifier
> +		(stream, section_type, level,
> +		 TYPE_FN_FIELD_PROTECTED (f, j),
> +		 TYPE_FN_FIELD_PRIVATE (f, j),
> +		 flags);
> +
> +	      print_spaces_filtered_with_print_options (level + 4, stream,
> +							flags);
> +	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
> +		fprintf_filtered (stream, "virtual ");
> +	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
> +		fprintf_filtered (stream, "static ");
> +	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
> +		{
> +		  /* Keep GDB from crashing here.  */
> +		  fprintf_filtered (stream,
> +				    _("<undefined type> %s;\n"),
> +				    TYPE_FN_FIELD_PHYSNAME (f, j));
> +		  break;
> +		}
> +	      else if (!is_constructor	/* Constructors don't
> +					   have declared
> +					   types.  */
> +		       && !is_full_physname_constructor  /* " " */
> +		       && !is_type_conversion_operator (type, i, j))
> +		{
> +		  unsigned int old_po = local_flags.print_offsets;
> +
> +		  /* Temporarily disable print_offsets, because it
> +		     would mess with indentation.  */
> +		  local_flags.print_offsets = 0;
> +		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
> +				"", stream, -1, 0,
> +				&local_flags);
> +		  local_flags.print_offsets = old_po;
> +		  fputs_filtered (" ", stream);
> +		}
> +	      if (TYPE_FN_FIELD_STUB (f, j))
> +		{
> +		  /* Build something we can demangle.  */
> +		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
> +		  mangled_name = mangled_name_holder.get ();
> +		}
> +	      else
> +		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
> +
> +	      demangled_name =
> +		gdb_demangle (mangled_name,
> +			      DMGL_ANSI | DMGL_PARAMS);
> +	      if (demangled_name == NULL)
> +		{
> +		  /* In some cases (for instance with the HP
> +		     demangling), if a function has more than 10
> +		     arguments, the demangling will fail.
> +		     Let's try to reconstruct the function
> +		     signature from the symbol information.  */
> +		  if (!TYPE_FN_FIELD_STUB (f, j))
> +		    {
> +		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
> +		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
> +
> +		      cp_type_print_method_args (mtype,
> +						 "",
> +						 method_name,
> +						 staticp,
> +						 stream, &local_flags);
> +		    }
> +		  else
> +		    fprintf_filtered (stream,
> +				      _("<badly mangled name '%s'>"),
> +				      mangled_name);
> +		}
> +	      else
> +		{
> +		  char *p;
> +		  char *demangled_no_class
> +		    = remove_qualifiers (demangled_name);
> +
> +		  /* Get rid of the `static' appended by the
> +		     demangler.  */
> +		  p = strstr (demangled_no_class, " static");
> +		  if (p != NULL)
> +		    {
> +		      int length = p - demangled_no_class;
> +		      char *demangled_no_static;
> +
> +		      demangled_no_static
> +			= (char *) xmalloc (length + 1);
> +		      strncpy (demangled_no_static,
> +			       demangled_no_class, length);
> +		      *(demangled_no_static + length) = '\0';
> +		      fputs_filtered (demangled_no_static, stream);
> +		      xfree (demangled_no_static);
> +		    }
> +		  else
> +		    fputs_filtered (demangled_no_class, stream);
> +		  xfree (demangled_name);
> +		}
> +
> +	      fprintf_filtered (stream, ";\n");
> +	    }
> +	}
> +
> +      /* Print typedefs defined in this class.  */
> +
> +      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
> +	{
> +	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
> +	    fprintf_filtered (stream, "\n");
> +
> +	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
> +	    {
> +	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
> +
> +	      /* Dereference the typedef declaration itself.  */
> +	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
> +	      target = TYPE_TARGET_TYPE (target);
> +
> +	      if (need_access_label)
> +		{
> +		  section_type = output_access_specifier
> +		    (stream, section_type, level,
> +		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
> +		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i),
> +		     flags);
> +		}
> +	      print_spaces_filtered_with_print_options (level + 4,
> +							stream, flags);
> +	      fprintf_filtered (stream, "typedef ");
> +
> +	      /* We want to print typedefs with substitutions
> +		 from the template parameters or globally-known
> +		 typedefs but not local typedefs.  */
> +	      c_print_type (target,
> +			    TYPE_TYPEDEF_FIELD_NAME (type, i),
> +			    stream, show - 1, level + 4,
> +			    &semi_local_flags);
> +	      fprintf_filtered (stream, ";\n");
> +	    }
> +	}
> +
> +      if (flags->print_offsets && level > 0)
> +	print_spaces_filtered (OFFSET_SPC_LEN, stream);
> +
> +      fprintfi_filtered (level, stream, "}");
> +    }
> +
> +  if (show > 0 && flags->print_offsets)
> +    fprintf_filtered (stream, " /* total size: %4u bytes */",
> +		      TYPE_LENGTH (type));
> +
> +  do_cleanups (local_cleanups);
> +}
> +
>  /* Print the name of the type (or the ultimate pointer target,
>     function value or array element), or the description of a structure
>     or union.
> @@ -898,10 +1476,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>  		   int show, int level, const struct type_print_options *flags)
>  {
>    int i;
> -  int len, real_len;
> -  enum access_specifier section_type;
> -  int need_access_label = 0;
> -  int j, len2;
> +  int len;
> +  int j;
>  
>    QUIT;
>  
> @@ -918,15 +1494,16 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>       folk tend to expect things like "class5 *foo" rather than "struct
>       class5 *foo".  */
>  
> -  if (show <= 0
> -      && TYPE_NAME (type) != NULL)
> +  struct type *ttype = check_typedef (type);
> +
> +  if (show <= 0 && TYPE_NAME (type) != NULL)
>      {
>        c_type_print_modifier (type, stream, 0, 1);
>        print_name_maybe_canonical (TYPE_NAME (type), flags, stream);
>        return;
>      }
>  
> -  type = check_typedef (type);
> +  type = ttype;
>  
>    switch (TYPE_CODE (type))
>      {
> @@ -958,416 +1535,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>  
>      case TYPE_CODE_STRUCT:
>      case TYPE_CODE_UNION:
> -      {
> -	struct type_print_options local_flags = *flags;
> -	struct type_print_options semi_local_flags = *flags;
> -	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
> -
> -	local_flags.local_typedefs = NULL;
> -	semi_local_flags.local_typedefs = NULL;
> -
> -	if (!flags->raw)
> -	  {
> -	    if (flags->local_typedefs)
> -	      local_flags.local_typedefs
> -		= copy_typedef_hash (flags->local_typedefs);
> -	    else
> -	      local_flags.local_typedefs = create_typedef_hash ();
> -
> -	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
> -	  }
> -
> -	c_type_print_modifier (type, stream, 0, 1);
> -	if (TYPE_CODE (type) == TYPE_CODE_UNION)
> -	  fprintf_filtered (stream, "union ");
> -	else if (TYPE_DECLARED_CLASS (type))
> -	  fprintf_filtered (stream, "class ");
> -	else
> -	  fprintf_filtered (stream, "struct ");
> -
> -	/* Print the tag if it exists.  The HP aCC compiler emits a
> -	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
> -	   enum}" tag for unnamed struct/union/enum's, which we don't
> -	   want to print.  */
> -	if (TYPE_TAG_NAME (type) != NULL
> -	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
> -	  {
> -	    /* When printing the tag name, we are still effectively
> -	       printing in the outer context, hence the use of FLAGS
> -	       here.  */
> -	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
> -	    if (show > 0)
> -	      fputs_filtered (" ", stream);
> -	  }
> -
> -	if (show < 0)
> -	  {
> -	    /* If we just printed a tag name, no need to print anything
> -	       else.  */
> -	    if (TYPE_TAG_NAME (type) == NULL)
> -	      fprintf_filtered (stream, "{...}");
> -	  }
> -	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
> -	  {
> -	    struct type *basetype;
> -	    int vptr_fieldno;
> -
> -	    c_type_print_template_args (&local_flags, type, stream);
> -
> -	    /* Add in template parameters when printing derivation info.  */
> -	    add_template_parameters (local_flags.local_typedefs, type);
> -	    cp_type_print_derivation_info (stream, type, &local_flags);
> -
> -	    /* This holds just the global typedefs and the template
> -	       parameters.  */
> -	    semi_local_flags.local_typedefs
> -	      = copy_typedef_hash (local_flags.local_typedefs);
> -	    if (semi_local_flags.local_typedefs)
> -	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
> -
> -	    /* Now add in the local typedefs.  */
> -	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
> -
> -	    fprintf_filtered (stream, "{\n");
> -	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
> -		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
> -	      {
> -		if (TYPE_STUB (type))
> -		  fprintfi_filtered (level + 4, stream,
> -				     _("<incomplete type>\n"));
> -		else
> -		  fprintfi_filtered (level + 4, stream,
> -				     _("<no data fields>\n"));
> -	      }
> -
> -	    /* Start off with no specific section type, so we can print
> -	       one for the first field we find, and use that section type
> -	       thereafter until we find another type.  */
> -
> -	    section_type = s_none;
> -
> -	    /* For a class, if all members are private, there's no need
> -	       for a "private:" label; similarly, for a struct or union
> -	       masquerading as a class, if all members are public, there's
> -	       no need for a "public:" label.  */
> -
> -	    if (TYPE_DECLARED_CLASS (type))
> -	      {
> -		QUIT;
> -		len = TYPE_NFIELDS (type);
> -		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> -		  if (!TYPE_FIELD_PRIVATE (type, i))
> -		    {
> -		      need_access_label = 1;
> -		      break;
> -		    }
> -		QUIT;
> -		if (!need_access_label)
> -		  {
> -		    len2 = TYPE_NFN_FIELDS (type);
> -		    for (j = 0; j < len2; j++)
> -		      {
> -			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> -			for (i = 0; i < len; i++)
> -			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> -									  j), i))
> -			    {
> -			      need_access_label = 1;
> -			      break;
> -			    }
> -			if (need_access_label)
> -			  break;
> -		      }
> -		  }
> -		QUIT;
> -		if (!need_access_label)
> -		  {
> -		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> -		      {
> -			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> -			  {
> -			    need_access_label = 1;
> -			    break;
> -			  }
> -		      }
> -		  }
> -	      }
> -	    else
> -	      {
> -		QUIT;
> -		len = TYPE_NFIELDS (type);
> -		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> -		  if (TYPE_FIELD_PRIVATE (type, i)
> -		      || TYPE_FIELD_PROTECTED (type, i))
> -		    {
> -		      need_access_label = 1;
> -		      break;
> -		    }
> -		QUIT;
> -		if (!need_access_label)
> -		  {
> -		    len2 = TYPE_NFN_FIELDS (type);
> -		    for (j = 0; j < len2; j++)
> -		      {
> -			QUIT;
> -			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> -			for (i = 0; i < len; i++)
> -			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
> -									   j), i)
> -			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> -									    j),
> -							i))
> -			    {
> -			      need_access_label = 1;
> -			      break;
> -			    }
> -			if (need_access_label)
> -			  break;
> -		      }
> -		  }
> -		QUIT;
> -		if (!need_access_label)
> -		  {
> -		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> -		      {
> -			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
> -			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> -			  {
> -			    need_access_label = 1;
> -			    break;
> -			  }
> -		      }
> -		  }
> -	      }
> -
> -	    /* If there is a base class for this type,
> -	       do not print the field that it occupies.  */
> -
> -	    len = TYPE_NFIELDS (type);
> -	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
> -	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> -	      {
> -		QUIT;
> -
> -		/* If we have a virtual table pointer, omit it.  Even if
> -		   virtual table pointers are not specifically marked in
> -		   the debug info, they should be artificial.  */
> -		if ((i == vptr_fieldno && type == basetype)
> -		    || TYPE_FIELD_ARTIFICIAL (type, i))
> -		  continue;
> -
> -		if (need_access_label)
> -		  {
> -		    section_type = output_access_specifier
> -		      (stream, section_type, level,
> -		       TYPE_FIELD_PROTECTED (type, i),
> -		       TYPE_FIELD_PRIVATE (type, i));
> -		  }
> -
> -		print_spaces_filtered (level + 4, stream);
> -		if (field_is_static (&TYPE_FIELD (type, i)))
> -		  fprintf_filtered (stream, "static ");
> -		c_print_type (TYPE_FIELD_TYPE (type, i),
> -			      TYPE_FIELD_NAME (type, i),
> -			      stream, show - 1, level + 4,
> -			      &local_flags);
> -		if (!field_is_static (&TYPE_FIELD (type, i))
> -		    && TYPE_FIELD_PACKED (type, i))
> -		  {
> -		    /* It is a bitfield.  This code does not attempt
> -		       to look at the bitpos and reconstruct filler,
> -		       unnamed fields.  This would lead to misleading
> -		       results if the compiler does not put out fields
> -		       for such things (I don't know what it does).  */
> -		    fprintf_filtered (stream, " : %d",
> -				      TYPE_FIELD_BITSIZE (type, i));
> -		  }
> -		fprintf_filtered (stream, ";\n");
> -	      }
> -
> -	  /* If there are both fields and methods, put a blank line
> -	     between them.  Make sure to count only method that we
> -	     will display; artificial methods will be hidden.  */
> -	  len = TYPE_NFN_FIELDS (type);
> -	  if (!flags->print_methods)
> -	    len = 0;
> -	  real_len = 0;
> -	  for (i = 0; i < len; i++)
> -	    {
> -	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
> -	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
> -	      int j;
> -
> -	      for (j = 0; j < len2; j++)
> -		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
> -		  real_len++;
> -	    }
> -	  if (real_len > 0 && section_type != s_none)
> -	    fprintf_filtered (stream, "\n");
> -
> -	  /* C++: print out the methods.  */
> -	  for (i = 0; i < len; i++)
> -	    {
> -	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
> -	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
> -	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
> -	      const char *name = type_name_no_tag (type);
> -	      int is_constructor = name && strcmp (method_name,
> -						   name) == 0;
> -
> -	      for (j = 0; j < len2; j++)
> -		{
> -		  const char *mangled_name;
> -		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
> -		  char *demangled_name;
> -		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
> -		  int is_full_physname_constructor =
> -		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
> -		    || is_constructor_name (physname)
> -		    || is_destructor_name (physname)
> -		    || method_name[0] == '~';
> -
> -		  /* Do not print out artificial methods.  */
> -		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
> -		    continue;
> -
> -		  QUIT;
> -		  section_type = output_access_specifier
> -		    (stream, section_type, level,
> -		     TYPE_FN_FIELD_PROTECTED (f, j),
> -		     TYPE_FN_FIELD_PRIVATE (f, j));
> -
> -		  print_spaces_filtered (level + 4, stream);
> -		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
> -		    fprintf_filtered (stream, "virtual ");
> -		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
> -		    fprintf_filtered (stream, "static ");
> -		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
> -		    {
> -		      /* Keep GDB from crashing here.  */
> -		      fprintf_filtered (stream,
> -					_("<undefined type> %s;\n"),
> -					TYPE_FN_FIELD_PHYSNAME (f, j));
> -		      break;
> -		    }
> -		  else if (!is_constructor	/* Constructors don't
> -						   have declared
> -						   types.  */
> -			   && !is_full_physname_constructor  /* " " */
> -			   && !is_type_conversion_operator (type, i, j))
> -		    {
> -		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
> -				    "", stream, -1, 0,
> -				    &local_flags);
> -		      fputs_filtered (" ", stream);
> -		    }
> -		  if (TYPE_FN_FIELD_STUB (f, j))
> -		    {
> -		      /* Build something we can demangle.  */
> -		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
> -		      mangled_name = mangled_name_holder.get ();
> -		    }
> -		  else
> -		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
> -
> -		  demangled_name =
> -		    gdb_demangle (mangled_name,
> -				  DMGL_ANSI | DMGL_PARAMS);
> -		  if (demangled_name == NULL)
> -		    {
> -		      /* In some cases (for instance with the HP
> -			 demangling), if a function has more than 10
> -			 arguments, the demangling will fail.
> -			 Let's try to reconstruct the function
> -			 signature from the symbol information.  */
> -		      if (!TYPE_FN_FIELD_STUB (f, j))
> -			{
> -			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
> -			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
> -
> -			  cp_type_print_method_args (mtype,
> -						     "",
> -						     method_name,
> -						     staticp,
> -						     stream, &local_flags);
> -			}
> -		      else
> -			fprintf_filtered (stream,
> -					  _("<badly mangled name '%s'>"),
> -					  mangled_name);
> -		    }
> -		  else
> -		    {
> -		      char *p;
> -		      char *demangled_no_class
> -			= remove_qualifiers (demangled_name);
> -
> -		      /* Get rid of the `static' appended by the
> -			 demangler.  */
> -		      p = strstr (demangled_no_class, " static");
> -		      if (p != NULL)
> -			{
> -			  int length = p - demangled_no_class;
> -			  char *demangled_no_static;
> -
> -			  demangled_no_static
> -			    = (char *) xmalloc (length + 1);
> -			  strncpy (demangled_no_static,
> -				   demangled_no_class, length);
> -			  *(demangled_no_static + length) = '\0';
> -			  fputs_filtered (demangled_no_static, stream);
> -			  xfree (demangled_no_static);
> -			}
> -		      else
> -			fputs_filtered (demangled_no_class, stream);
> -		      xfree (demangled_name);
> -		    }
> -
> -		  fprintf_filtered (stream, ";\n");
> -		}
> -	    }
> -
> -	  /* Print typedefs defined in this class.  */
> -
> -	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
> -	    {
> -	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
> -		fprintf_filtered (stream, "\n");
> -
> -	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
> -		{
> -		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
> -
> -		  /* Dereference the typedef declaration itself.  */
> -		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
> -		  target = TYPE_TARGET_TYPE (target);
> -
> -		  if (need_access_label)
> -		    {
> -		      section_type = output_access_specifier
> -			(stream, section_type, level,
> -			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
> -			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
> -		    }
> -		  print_spaces_filtered (level + 4, stream);
> -		  fprintf_filtered (stream, "typedef ");
> -
> -		  /* We want to print typedefs with substitutions
> -		     from the template parameters or globally-known
> -		     typedefs but not local typedefs.  */
> -		  c_print_type (target,
> -				TYPE_TYPEDEF_FIELD_NAME (type, i),
> -				stream, show - 1, level + 4,
> -				&semi_local_flags);
> -		  fprintf_filtered (stream, ";\n");
> -		}
> -	    }
> -
> -	    fprintfi_filtered (level, stream, "}");
> -	  }
> -
> -	do_cleanups (local_cleanups);
> -      }
> +      c_type_print_base_struct_union (type, stream, show, level, flags);
>        break;
>  
>      case TYPE_CODE_ENUM:
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 675f6e7bc8..f7a45dd5dd 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -17095,6 +17095,10 @@ names are substituted when printing other types.
>  @item T
>  Print typedefs defined in the class.  This is the default, but the flag
>  exists in case you change the default with @command{set print type typedefs}.
> +
> +@item o
> +Print the offsets and sizes of fields in a struct, similar to what the
> +@command{pahole} tool does.
>  @end table
>  
>  @kindex ptype
> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
> new file mode 100644
> index 0000000000..f9a57fd3db
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
> @@ -0,0 +1,113 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
> +
> +#include <stdint.h>
> +
> +/* A struct with many types of fields, in order to test 'ptype
> +   /o'.  */
> +
> +struct abc
> +{
> +  /* Virtual destructor.  */
> +  virtual ~abc ()
> +  {}
> +
> +  /* 8-byte address.  Because of the virtual destructor above, this
> +     field's offset will be 8.  */
> +  void *field1;
> +
> +  /* No hole here.  */
> +
> +  /* 4-byte int bitfield of 1-bit.  */
> +  unsigned int field2 : 1;
> +
> +  /* 31-bit hole here.  */
> +
> +  /* 4-byte int.  */
> +  int field3;
> +
> +  /* No hole here.  */
> +
> +  /* 1-byte char.  */
> +  char field4;
> +
> +  /* 7-byte hole here.  */
> +
> +  /* 8-byte int.  */
> +  uint64_t field5;
> +
> +  /* We just print the offset and size of a union, ignoring its
> +     fields.  */
> +  union
> +  {
> +    /* 8-byte address.  */
> +    void *field6;
> +
> +    /* 4-byte int.  */
> +    int field7;
> +  } field8;
> +
> +  /* Empty constructor.  */
> +  abc ()
> +  {}
> +};
> +
> +/* This struct will be nested inside 'struct xyz'.  */
> +
> +struct tuv
> +{
> +  int a1;
> +
> +  char *a2;
> +
> +  int a3;
> +};
> +
> +/* This struct will be nested inside 'struct pqr'.  */
> +
> +struct xyz
> +{
> +  int f1;
> +
> +  char f2;
> +
> +  void *f3;
> +
> +  struct tuv f4;
> +};
> +
> +/* A struct with a nested struct.  */
> +
> +struct pqr
> +{
> +  int ff1;
> +
> +  struct xyz ff2;
> +
> +  char ff3;
> +};
> +
> +int
> +main (int argc, char *argv[])
> +{
> +  struct abc foo;
> +  struct pqr bar;
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
> new file mode 100644
> index 0000000000..4f84416dc5
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
> @@ -0,0 +1,77 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2017 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/>.
> +
> +standard_testfile .cc ptype-offsets.cc
> +
> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
> +# that the expected holes will be present in the struct.
> +if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
> +    untested "test work only on x86_64 lp64"
> +    return 0
> +}
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
> +	  { debug c++ optimize=-O0 }] } {
> +    return -1
> +}
> +
> +# Test general offset printing, ctor/dtor printing, union, formatting.
> +gdb_test "ptype /o struct abc" \
> +    [multi_line \
> +"type = struct abc {" \
> +"/\\\* offset    |  size \\\*/" \
> +"                         public:" \
> +"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
> +"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
> +"/\\\* XXX  7-bit hole   \\\*/" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*   20      |     4 \\\*/    int field3;" \
> +"/\\\*   24      |     1 \\\*/    char field4;" \
> +"/\\\* XXX  7-byte hole  \\\*/" \
> +"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
> +"/\\\*   40      |     8 \\\*/    union {" \
> +"/\\\*                 8 \\\*/        void \\\*field6;" \
> +"/\\\*                 4 \\\*/        int field7;" \
> +"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
> +"" \
> +"                           abc\\(void\\);" \
> +"                           ~abc\\(\\);" \
> +"} /\\\* total size:   48 bytes \\\*/"] \
> +    "ptype offset struct abc"
> +
> +# Test nested structs.
> +gdb_test "ptype /o struct pqr" \
> +    [multi_line \
> +"type = struct pqr {" \
> +"/\\\* offset    |  size \\\*/" \
> +"/\\\*    0      |     4 \\\*/    int f1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*    8      |    16 \\\*/    struct xyz {" \
> +"/\\\*    8      |     4 \\\*/        int f1;" \
> +"/\\\*   12      |     1 \\\*/        char f2;" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
> +"/\\\*   24      |    24 \\\*/        struct tuv {" \
> +"/\\\*   24      |     4 \\\*/            int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   32      |     8 \\\*/            char *a2;" \
> +"/\\\*   40      |     4 \\\*/            int a3;" \
> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
> +"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
> +"/\\\*   48      |     1 \\\*/    char ff3;" \
> +"} /\\\* total size:   56 bytes \\\*/"] \
> +    "ptype offset struct pqr"
> diff --git a/gdb/typeprint.c b/gdb/typeprint.c
> index 427af17ad7..1463e802ad 100644
> --- a/gdb/typeprint.c
> +++ b/gdb/typeprint.c
> @@ -42,6 +42,8 @@ const struct type_print_options type_print_raw_options =
>    1,				/* raw */
>    1,				/* print_methods */
>    1,				/* print_typedefs */
> +  0,				/* print_offsets */
> +  0,				/* offset_bitpos */
>    NULL,				/* local_typedefs */
>    NULL,				/* global_table */
>    NULL				/* global_printers */
> @@ -54,6 +56,8 @@ static struct type_print_options default_ptype_flags =
>    0,				/* raw */
>    1,				/* print_methods */
>    1,				/* print_typedefs */
> +  0,				/* print_offsets */
> +  0,				/* offset_bitpos */
>    NULL,				/* local_typedefs */
>    NULL,				/* global_table */
>    NULL				/* global_printers */
> @@ -438,6 +442,9 @@ whatis_exp (const char *exp, int show)
>  		case 'T':
>  		  flags.print_typedefs = 1;
>  		  break;
> +		case 'o':
> +		  flags.print_offsets = 1;
> +		  break;
>  		default:
>  		  error (_("unrecognized flag '%c'"), *exp);
>  		}
> @@ -497,6 +504,11 @@ whatis_exp (const char *exp, int show)
>  	real_type = value_rtti_type (val, &full, &top, &using_enc);
>      }
>  
> +  if (flags.print_offsets &&
> +      (TYPE_CODE (type) == TYPE_CODE_STRUCT
> +       || TYPE_CODE (type) == TYPE_CODE_UNION))
> +    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
> +
>    printf_filtered ("type = ");
>  
>    if (!flags.raw)
> @@ -722,7 +734,8 @@ Available FLAGS are:\n\
>    /m    do not print methods defined in a class\n\
>    /M    print methods defined in a class\n\
>    /t    do not print typedefs defined in a class\n\
> -  /T    print typedefs defined in a class"));
> +  /T    print typedefs defined in a class\n\
> +  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
>    set_cmd_completer (c, expression_completer);
>  
>    c = add_com ("whatis", class_vars, whatis_command,
> diff --git a/gdb/typeprint.h b/gdb/typeprint.h
> index a458aa4e2f..a2a5285012 100644
> --- a/gdb/typeprint.h
> +++ b/gdb/typeprint.h
> @@ -35,6 +35,15 @@ struct type_print_options
>    /* True means print typedefs in a class.  */
>    unsigned int print_typedefs : 1;
>  
> +  /* True means to print offsets, a la 'pahole'.  */
> +  unsigned int print_offsets : 1;
> +
> +  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
> +     This is needed for when we are printing nested structs and want
> +     to make sure that the printed offset for each field carries off
> +     the offset of the outter struct.  */
> +  unsigned int offset_bitpos;
> +
>    /* If not NULL, a local typedef hash table used when printing a
>       type.  */
>    struct typedef_hash_table *local_typedefs;
> -- 
> 2.13.3

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-04 15:03   ` Sergio Durigan Junior
@ 2017-12-04 15:41     ` Eli Zaretskii
  2017-12-04 16:47       ` Sergio Durigan Junior
  2017-12-08 21:32     ` Sergio Durigan Junior
  1 sibling, 1 reply; 75+ messages in thread
From: Eli Zaretskii @ 2017-12-04 15:41 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: gdb-patches, tom

> From: Sergio Durigan Junior <sergiodj@redhat.com>
> Cc: Tom Tromey <tom@tromey.com>, 	Eli Zaretskii <eliz@gnu.org>
> Date: Mon, 04 Dec 2017 10:03:46 -0500
> 
> On Tuesday, November 28 2017, I wrote:
> 
> > Changes from v1:
> >
> > - Address Tom's comments (the command now prints offset information
> >   about unions, and the offset info is carried on for nested structs).
> >
> > - Address Eli's comments.
> >
> > - Extended testcase.
> >
> > - A "bit" of cleanup on 'c_type_print_base'.
> 
> Ping.

I believe I already approved the documentation parts.  Right?

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-04 15:41     ` Eli Zaretskii
@ 2017-12-04 16:47       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-04 16:47 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, tom

On Monday, December 04 2017, Eli Zaretskii wrote:

>> From: Sergio Durigan Junior <sergiodj@redhat.com>
>> Cc: Tom Tromey <tom@tromey.com>, 	Eli Zaretskii <eliz@gnu.org>
>> Date: Mon, 04 Dec 2017 10:03:46 -0500
>> 
>> On Tuesday, November 28 2017, I wrote:
>> 
>> > Changes from v1:
>> >
>> > - Address Tom's comments (the command now prints offset information
>> >   about unions, and the offset info is carried on for nested structs).
>> >
>> > - Address Eli's comments.
>> >
>> > - Extended testcase.
>> >
>> > - A "bit" of cleanup on 'c_type_print_base'.
>> 
>> Ping.
>
> I believe I already approved the documentation parts.  Right?

Sorry, yes Eli, you have approved the docs already.  Now I just need
someone to look at the code ;-).

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-04 15:03   ` Sergio Durigan Junior
  2017-12-04 15:41     ` Eli Zaretskii
@ 2017-12-08 21:32     ` Sergio Durigan Junior
  1 sibling, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-08 21:32 UTC (permalink / raw)
  To: GDB Patches; +Cc: Tom Tromey, Eli Zaretskii

On Monday, December 04 2017, I wrote:

> On Tuesday, November 28 2017, I wrote:
>
>> Changes from v1:
>>
>> - Address Tom's comments (the command now prints offset information
>>   about unions, and the offset info is carried on for nested structs).
>>
>> - Address Eli's comments.
>>
>> - Extended testcase.
>>
>> - A "bit" of cleanup on 'c_type_print_base'.
>
> Ping.

Ping^2.

Eli has already approved the documentation part.

>>
>> This commit implements the pahole-like '/o' option for 'ptype', which
>> prints the offsets and sizes of struct fields, reporting whenever
>> there is a hole found.
>>
>> The output is heavily based on pahole(1), with a few modifications
>> here and there to adjust it to our reality.  Here's an example:
>>
>>   (gdb) ptype /o stap_probe
>>   /* offset    |  size */
>>   struct stap_probe {
>>   /*    0      |    40 */    struct probe {
>>   /*    0      |     8 */        const probe_ops *pops;
>>   /*    8      |     8 */        gdbarch *arch;
>>   /*   16      |     8 */        const char *name;
>>   /*   24      |     8 */        const char *provider;
>>   /*   32      |     8 */        CORE_ADDR address;
>> 			     } /* total size:   40 bytes */ p;
>>   /*   40      |     8 */    CORE_ADDR sem_addr;
>>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>>   /* XXX  7-bit hole   */
>>   /* XXX  7-byte hole  */
>>   /*   56      |     8 */    union {
>>   /*                 8 */        const char *text;
>>   /*                 8 */        VEC_stap_probe_arg_s *vec;
>> 			     } /* total size:    8 bytes */ args_u;
>>   } /* total size:   64 bytes */
>>
>> A big part of this patch handles the formatting logic of 'ptype',
>> which is a bit messy.  I tried to be not very invasive, but I had to
>> do some cleanups here and there to make life easier.
>>
>> This patch is the start of a long-term work I'll do to flush the local
>> patches we carry for Fedora GDB.  In this specific case, I'm aiming at
>> upstreaming the feature implemented by the 'pahole.py' script that is
>> shipped with Fedora GDB:
>>
>>   <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>
>>
>> This has been regression-tested on the BuildBot.  There's a new
>> testcase for it, along with an update to the documentation.  I also
>> thought it was worth mentioning this feature in the NEWS file.
>>
>> gdb/ChangeLog:
>> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>>
>> 	PR cli/16224
>> 	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
>> 	* c-typeprint.c (OFFSET_SPC_LEN): New define.
>> 	(print_spaces_filtered_with_print_options): New function.
>> 	(output_access_specifier): Take new argument FLAGS.  Modify
>> 	function to call 'print_spaces_filtered_with_print_options'.
>> 	(c_print_type_union_field_offset): New function.
>> 	(c_print_type_struct_field_offset): New function.
>> 	(need_access_label_p): New function, with contents from
>> 	'c_type_print_base'.
>> 	(c_type_print_base_struct_union): Likewise.
>> 	(c_type_print_base): Print offsets and sizes for struct
>> 	fields.  Struct/union handling code move to functions
>> 	mentioned above.
>> 	* typeprint.c (const struct type_print_options
>> 	type_print_raw_options): Initialize 'print_offsets' and
>> 	'offset_bitpos'.
>> 	(static struct type_print_options default_ptype_flags):
>> 	Likewise.
>> 	(whatis_exp): Handle '/o' option.
>> 	(_initialize_typeprint): Add '/o' flag to ptype's help.
>> 	* typeprint.h (struct type_print_options) <print_offsets>: New
>> 	field.
>> 	<offset_bitpos>: Likewise.
>>
>> gdb/testsuite/ChangeLog:
>> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>>
>> 	PR cli/16224
>> 	* gdb.base/ptype-offsets.cc: New file.
>> 	* gdb.base/ptype-offsets.exp: New file.
>>
>> gdb/doc/ChangeLog:
>> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>>
>> 	PR cli/16224
>> 	* gdb.texinfo (ptype): Add new flag '/o'.
>> ---
>>  gdb/NEWS                                 |    3 +
>>  gdb/c-typeprint.c                        | 1016 +++++++++++++++++-------------
>>  gdb/doc/gdb.texinfo                      |    4 +
>>  gdb/testsuite/gdb.base/ptype-offsets.cc  |  113 ++++
>>  gdb/testsuite/gdb.base/ptype-offsets.exp |   77 +++
>>  gdb/typeprint.c                          |   15 +-
>>  gdb/typeprint.h                          |    9 +
>>  7 files changed, 812 insertions(+), 425 deletions(-)
>>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
>>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp
>>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index 754ce103bd..1247021046 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,9 @@
>>  
>>  *** Changes since GDB 8.0
>>  
>> +* The 'ptype' command now accepts a '/o' flag, which prints the
>> +  offsets and sizes of fields in a struct, like the pahole(1) tool.
>> +
>>  * GDB now uses the GNU MPFR library, if available, to emulate target
>>    floating-point arithmetic during expression evaluation when the target
>>    uses different floating-point formats than the host.  At least version
>> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
>> index ed5a1a4b8a..39334ccf88 100644
>> --- a/gdb/c-typeprint.c
>> +++ b/gdb/c-typeprint.c
>> @@ -32,6 +32,14 @@
>>  #include "cp-abi.h"
>>  #include "cp-support.h"
>>  
>> +/* When printing the offsets of a struct and its fields (i.e., 'ptype
>> +   /o'; type_print_options::print_offsets), we use this many
>> +   characters when printing the offset information at the beginning of
>> +   the line.  This is needed in order to generate the correct amount
>> +   of whitespaces when no offset info should be printed for a certain
>> +   field.  */
>> +#define OFFSET_SPC_LEN 23
>> +
>>  /* A list of access specifiers used for printing.  */
>>  
>>  enum access_specifier
>> @@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
>>      fputs_filtered (_("] "), stream);
>>  }
>>  
>> +/* Use 'print_spaces_filtered', but take into consideration the
>> +   type_print_options FLAGS in order to determine how many whitespaces
>> +   will be printed.  */
>> +
>> +static void
>> +print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
>> +					const struct type_print_options *flags)
>> +{
>> +  if (!flags->print_offsets)
>> +    print_spaces_filtered (level, stream);
>> +  else
>> +    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
>> +}
>> +
>>  /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
>>     last access specifier output (typically returned by this function).  */
>>  
>>  static enum access_specifier
>>  output_access_specifier (struct ui_file *stream,
>>  			 enum access_specifier last_access,
>> -			 int level, bool is_protected, bool is_private)
>> +			 int level, bool is_protected, bool is_private,
>> +			 const struct type_print_options *flags)
>>  {
>>    if (is_protected)
>>      {
>>        if (last_access != s_protected)
>>  	{
>>  	  last_access = s_protected;
>> -	  fprintfi_filtered (level + 2, stream,
>> -			     "protected:\n");
>> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
>> +	  fprintf_filtered (stream, "protected:\n");
>>  	}
>>      }
>>    else if (is_private)
>> @@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
>>        if (last_access != s_private)
>>  	{
>>  	  last_access = s_private;
>> -	  fprintfi_filtered (level + 2, stream,
>> -			     "private:\n");
>> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
>> +	  fprintf_filtered (stream, "private:\n");
>>  	}
>>      }
>>    else
>> @@ -867,14 +890,569 @@ output_access_specifier (struct ui_file *stream,
>>        if (last_access != s_public)
>>  	{
>>  	  last_access = s_public;
>> -	  fprintfi_filtered (level + 2, stream,
>> -			     "public:\n");
>> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
>> +	  fprintf_filtered (stream, "public:\n");
>>  	}
>>      }
>>  
>>    return last_access;
>>  }
>>  
>> +/* Print information about the offset of TYPE inside its union.
>> +   FIELD_IDX represents the index of this TYPE inside the union.  We
>> +   just print the type size, and nothing more.
>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
>> +				 struct ui_file *stream)
>> +{
>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
>> +
>> +  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
>> +}
>> +
>> +/* Print information about the offset of TYPE inside its struct.
>> +   FIELD_IDX represents the index of this TYPE inside the struct, and
>> +   ENDPOS is the end position of the previous type (this is how we
>> +   calculate whether there are holes in the struct).  At the end,
>> +   ENDPOS is updated.
>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
>> +				  unsigned int *endpos, struct ui_file *stream,
>> +				  unsigned int offset_bitpos)
>> +{
>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
>> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
>> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
>> +  unsigned int fieldsize_bit;
>> +
>> +  if (*endpos > 0 && *endpos < bitpos)
>> +    {
>> +      /* If ENDPOS is smaller than the current type's bitpos, it means
>> +	 there's a hole in the struct, so we report it here.  */
>> +      unsigned int hole = bitpos - *endpos;
>> +      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
>> +      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
>> +
>> +      if (hole_bit > 0)
>> +	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
>> +
>> +      if (hole_byte > 0)
>> +	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
>> +    }
>> +
>> +  /* The position of the field, relative to the beginning of the
>> +     struct.  Assume this number will have 4 digits.  */
>> +  fprintf_filtered (stream, "/* %4u",
>> +		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT);
>> +
>> +  if (TYPE_FIELD_PACKED (type, field_idx))
>> +    {
>> +      /* We're dealing with a bitfield.  Print how many bits are left
>> +	 to be used.  */
>> +      fieldsize_bit = TYPE_FIELD_BITSIZE (type, field_idx);
>> +      fprintf_filtered (stream, ":%u",
>> +			fieldsize_byte * TARGET_CHAR_BIT - fieldsize_bit);
>> +    }
>> +  else
>> +    {
>> +      fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
>> +      fprintf_filtered (stream, "   ");
>> +    }
>> +
>> +  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
>> +
>> +  *endpos = bitpos + fieldsize_bit;
>> +}
>> +
>> +/* Return true is an access label (i.e., "public:", "private:",
>> +   "protected:") needs to be printed for TYPE.  */
>> +
>> +static bool
>> +need_access_label_p (struct type *type)
>> +{
>> +  bool need_access_label = false;
>> +  int i, j;
>> +  int len, len2;
>> +
>> +  if (TYPE_DECLARED_CLASS (type))
>> +    {
>> +      QUIT;
>> +      len = TYPE_NFIELDS (type);
>> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> +	if (!TYPE_FIELD_PRIVATE (type, i))
>> +	  {
>> +	    need_access_label = true;
>> +	    break;
>> +	  }
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  len2 = TYPE_NFN_FIELDS (type);
>> +	  for (j = 0; j < len2; j++)
>> +	    {
>> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> +	      for (i = 0; i < len; i++)
>> +		if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> +								j), i))
>> +		  {
>> +		    need_access_label = true;
>> +		    break;
>> +		  }
>> +	      if (need_access_label)
>> +		break;
>> +	    }
>> +	}
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> +	    {
>> +	      if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> +		{
>> +		  need_access_label = true;
>> +		  break;
>> +		}
>> +	    }
>> +	}
>> +    }
>> +  else
>> +    {
>> +      QUIT;
>> +      len = TYPE_NFIELDS (type);
>> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> +	if (TYPE_FIELD_PRIVATE (type, i)
>> +	    || TYPE_FIELD_PROTECTED (type, i))
>> +	  {
>> +	    need_access_label = true;
>> +	    break;
>> +	  }
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  len2 = TYPE_NFN_FIELDS (type);
>> +	  for (j = 0; j < len2; j++)
>> +	    {
>> +	      QUIT;
>> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> +	      for (i = 0; i < len; i++)
>> +		if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
>> +								 j), i)
>> +		    || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> +								  j),
>> +					      i))
>> +		  {
>> +		    need_access_label = true;
>> +		    break;
>> +		  }
>> +	      if (need_access_label)
>> +		break;
>> +	    }
>> +	}
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> +	    {
>> +	      if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
>> +		  || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> +		{
>> +		  need_access_label = true;
>> +		  break;
>> +		}
>> +	    }
>> +	}
>> +    }
>> +  return need_access_label;
>> +}
>> +
>> +/* Helper for 'c_type_print_base' that handles structs and unions.
>> +   For a description of the arguments, see 'c_type_print_base'.  */
>> +
>> +static void
>> +c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
>> +				int show, int level,
>> +				const struct type_print_options *flags)
>> +{
>> +  struct type_print_options local_flags = *flags;
>> +  struct type_print_options semi_local_flags = *flags;
>> +  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
>> +
>> +  local_flags.local_typedefs = NULL;
>> +  semi_local_flags.local_typedefs = NULL;
>> +
>> +  if (!flags->raw)
>> +    {
>> +      if (flags->local_typedefs)
>> +	local_flags.local_typedefs
>> +	  = copy_typedef_hash (flags->local_typedefs);
>> +      else
>> +	local_flags.local_typedefs = create_typedef_hash ();
>> +
>> +      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
>> +    }
>> +
>> +  c_type_print_modifier (type, stream, 0, 1);
>> +  if (TYPE_CODE (type) == TYPE_CODE_UNION)
>> +    fprintf_filtered (stream, "union ");
>> +  else if (TYPE_DECLARED_CLASS (type))
>> +    fprintf_filtered (stream, "class ");
>> +  else
>> +    fprintf_filtered (stream, "struct ");
>> +
>> +  /* Print the tag if it exists.  The HP aCC compiler emits a
>> +     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
>> +     enum}" tag for unnamed struct/union/enum's, which we don't
>> +     want to print.  */
>> +  if (TYPE_TAG_NAME (type) != NULL
>> +      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
>> +    {
>> +      /* When printing the tag name, we are still effectively
>> +	 printing in the outer context, hence the use of FLAGS
>> +	 here.  */
>> +      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
>> +      if (show > 0)
>> +	fputs_filtered (" ", stream);
>> +    }
>> +
>> +  if (show < 0)
>> +    {
>> +      /* If we just printed a tag name, no need to print anything
>> +	 else.  */
>> +      if (TYPE_TAG_NAME (type) == NULL)
>> +	fprintf_filtered (stream, "{...}");
>> +    }
>> +  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
>> +    {
>> +      struct type *basetype;
>> +
>> +      c_type_print_template_args (&local_flags, type, stream);
>> +
>> +      /* Add in template parameters when printing derivation info.  */
>> +      add_template_parameters (local_flags.local_typedefs, type);
>> +      cp_type_print_derivation_info (stream, type, &local_flags);
>> +
>> +      /* This holds just the global typedefs and the template
>> +	 parameters.  */
>> +      semi_local_flags.local_typedefs
>> +	= copy_typedef_hash (local_flags.local_typedefs);
>> +      if (semi_local_flags.local_typedefs)
>> +	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
>> +
>> +      /* Now add in the local typedefs.  */
>> +      recursively_update_typedef_hash (local_flags.local_typedefs, type);
>> +
>> +      fprintf_filtered (stream, "{\n");
>> +      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
>> +	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
>> +	{
>> +	  if (TYPE_STUB (type))
>> +	    fprintfi_filtered (level + 4, stream,
>> +			       _("<incomplete type>\n"));
>> +	  else
>> +	    fprintfi_filtered (level + 4, stream,
>> +			       _("<no data fields>\n"));
>> +	}
>> +
>> +      /* Start off with no specific section type, so we can print
>> +	 one for the first field we find, and use that section type
>> +	 thereafter until we find another type.  */
>> +      enum access_specifier section_type = s_none;
>> +
>> +      /* For a class, if all members are private, there's no need
>> +	 for a "private:" label; similarly, for a struct or union
>> +	 masquerading as a class, if all members are public, there's
>> +	 no need for a "public:" label.  */
>> +      bool need_access_label = need_access_label_p (type);
>> +
>> +      /* If there is a base class for this type,
>> +	 do not print the field that it occupies.  */
>> +
>> +      int len = TYPE_NFIELDS (type);
>> +      int vptr_fieldno = get_vptr_fieldno (type, &basetype);
>> +      unsigned int endpos = 0;
>> +
>> +      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
>> +	{
>> +	  QUIT;
>> +
>> +	  /* If we have a virtual table pointer, omit it.  Even if
>> +	     virtual table pointers are not specifically marked in
>> +	     the debug info, they should be artificial.  */
>> +	  if ((i == vptr_fieldno && type == basetype)
>> +	      || TYPE_FIELD_ARTIFICIAL (type, i))
>> +	    continue;
>> +
>> +	  if (need_access_label)
>> +	    {
>> +	      section_type = output_access_specifier
>> +		(stream, section_type, level,
>> +		 TYPE_FIELD_PROTECTED (type, i),
>> +		 TYPE_FIELD_PRIVATE (type, i),
>> +		 flags);
>> +	    }
>> +
>> +	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
>> +
>> +	  if (flags->print_offsets)
>> +	    {
>> +	      if (!is_static)
>> +		{
>> +		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
>> +		    c_print_type_struct_field_offset (type, i, &endpos, stream,
>> +						      flags->offset_bitpos);
>> +		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
>> +		    c_print_type_union_field_offset (type, i, stream);
>> +		}
>> +	      else
>> +		print_spaces_filtered (OFFSET_SPC_LEN, stream);
>> +	    }
>> +
>> +	  print_spaces_filtered (level + 4, stream);
>> +	  if (is_static)
>> +	    fprintf_filtered (stream, "static ");
>> +
>> +	  int newshow = show - 1;
>> +
>> +	  if (flags->print_offsets
>> +	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
>> +		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
>> +	    {
>> +	      /* If we're printing offsets and this field's type is
>> +		 either a struct or an union, then we're interested in
>> +		 expanding it.  */
>> +	      ++newshow;
>> +
>> +	      /* Make sure we carry our offset when we expand the
>> +		 struct.  */
>> +	      local_flags.offset_bitpos
>> +		= flags->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
>> +	    }
>> +
>> +	  c_print_type (TYPE_FIELD_TYPE (type, i),
>> +			TYPE_FIELD_NAME (type, i),
>> +			stream, newshow, level + 4,
>> +			&local_flags);
>> +	  if (!is_static
>> +	      && TYPE_FIELD_PACKED (type, i))
>> +	    {
>> +	      /* It is a bitfield.  This code does not attempt
>> +		 to look at the bitpos and reconstruct filler,
>> +		 unnamed fields.  This would lead to misleading
>> +		 results if the compiler does not put out fields
>> +		 for such things (I don't know what it does).  */
>> +	      fprintf_filtered (stream, " : %d",
>> +				TYPE_FIELD_BITSIZE (type, i));
>> +	    }
>> +	  fprintf_filtered (stream, ";\n");
>> +	}
>> +
>> +      /* If there are both fields and methods, put a blank line
>> +	 between them.  Make sure to count only method that we
>> +	 will display; artificial methods will be hidden.  */
>> +      len = TYPE_NFN_FIELDS (type);
>> +      if (!flags->print_methods)
>> +	len = 0;
>> +      int real_len = 0;
>> +      for (int i = 0; i < len; i++)
>> +	{
>> +	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
>> +	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
>> +	  int j;
>> +
>> +	  for (j = 0; j < len2; j++)
>> +	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
>> +	      real_len++;
>> +	}
>> +      if (real_len > 0 && section_type != s_none)
>> +	fprintf_filtered (stream, "\n");
>> +
>> +      /* C++: print out the methods.  */
>> +      for (int i = 0; i < len; i++)
>> +	{
>> +	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
>> +	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
>> +	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
>> +	  const char *name = type_name_no_tag (type);
>> +	  int is_constructor = name && strcmp (method_name,
>> +					       name) == 0;
>> +
>> +	  for (j = 0; j < len2; j++)
>> +	    {
>> +	      const char *mangled_name;
>> +	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
>> +	      char *demangled_name;
>> +	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
>> +	      int is_full_physname_constructor =
>> +		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
>> +		|| is_constructor_name (physname)
>> +		|| is_destructor_name (physname)
>> +		|| method_name[0] == '~';
>> +
>> +	      /* Do not print out artificial methods.  */
>> +	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
>> +		continue;
>> +
>> +	      QUIT;
>> +	      section_type = output_access_specifier
>> +		(stream, section_type, level,
>> +		 TYPE_FN_FIELD_PROTECTED (f, j),
>> +		 TYPE_FN_FIELD_PRIVATE (f, j),
>> +		 flags);
>> +
>> +	      print_spaces_filtered_with_print_options (level + 4, stream,
>> +							flags);
>> +	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
>> +		fprintf_filtered (stream, "virtual ");
>> +	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
>> +		fprintf_filtered (stream, "static ");
>> +	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
>> +		{
>> +		  /* Keep GDB from crashing here.  */
>> +		  fprintf_filtered (stream,
>> +				    _("<undefined type> %s;\n"),
>> +				    TYPE_FN_FIELD_PHYSNAME (f, j));
>> +		  break;
>> +		}
>> +	      else if (!is_constructor	/* Constructors don't
>> +					   have declared
>> +					   types.  */
>> +		       && !is_full_physname_constructor  /* " " */
>> +		       && !is_type_conversion_operator (type, i, j))
>> +		{
>> +		  unsigned int old_po = local_flags.print_offsets;
>> +
>> +		  /* Temporarily disable print_offsets, because it
>> +		     would mess with indentation.  */
>> +		  local_flags.print_offsets = 0;
>> +		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
>> +				"", stream, -1, 0,
>> +				&local_flags);
>> +		  local_flags.print_offsets = old_po;
>> +		  fputs_filtered (" ", stream);
>> +		}
>> +	      if (TYPE_FN_FIELD_STUB (f, j))
>> +		{
>> +		  /* Build something we can demangle.  */
>> +		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
>> +		  mangled_name = mangled_name_holder.get ();
>> +		}
>> +	      else
>> +		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
>> +
>> +	      demangled_name =
>> +		gdb_demangle (mangled_name,
>> +			      DMGL_ANSI | DMGL_PARAMS);
>> +	      if (demangled_name == NULL)
>> +		{
>> +		  /* In some cases (for instance with the HP
>> +		     demangling), if a function has more than 10
>> +		     arguments, the demangling will fail.
>> +		     Let's try to reconstruct the function
>> +		     signature from the symbol information.  */
>> +		  if (!TYPE_FN_FIELD_STUB (f, j))
>> +		    {
>> +		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
>> +		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
>> +
>> +		      cp_type_print_method_args (mtype,
>> +						 "",
>> +						 method_name,
>> +						 staticp,
>> +						 stream, &local_flags);
>> +		    }
>> +		  else
>> +		    fprintf_filtered (stream,
>> +				      _("<badly mangled name '%s'>"),
>> +				      mangled_name);
>> +		}
>> +	      else
>> +		{
>> +		  char *p;
>> +		  char *demangled_no_class
>> +		    = remove_qualifiers (demangled_name);
>> +
>> +		  /* Get rid of the `static' appended by the
>> +		     demangler.  */
>> +		  p = strstr (demangled_no_class, " static");
>> +		  if (p != NULL)
>> +		    {
>> +		      int length = p - demangled_no_class;
>> +		      char *demangled_no_static;
>> +
>> +		      demangled_no_static
>> +			= (char *) xmalloc (length + 1);
>> +		      strncpy (demangled_no_static,
>> +			       demangled_no_class, length);
>> +		      *(demangled_no_static + length) = '\0';
>> +		      fputs_filtered (demangled_no_static, stream);
>> +		      xfree (demangled_no_static);
>> +		    }
>> +		  else
>> +		    fputs_filtered (demangled_no_class, stream);
>> +		  xfree (demangled_name);
>> +		}
>> +
>> +	      fprintf_filtered (stream, ";\n");
>> +	    }
>> +	}
>> +
>> +      /* Print typedefs defined in this class.  */
>> +
>> +      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
>> +	{
>> +	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
>> +	    fprintf_filtered (stream, "\n");
>> +
>> +	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
>> +	    {
>> +	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
>> +
>> +	      /* Dereference the typedef declaration itself.  */
>> +	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
>> +	      target = TYPE_TARGET_TYPE (target);
>> +
>> +	      if (need_access_label)
>> +		{
>> +		  section_type = output_access_specifier
>> +		    (stream, section_type, level,
>> +		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
>> +		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i),
>> +		     flags);
>> +		}
>> +	      print_spaces_filtered_with_print_options (level + 4,
>> +							stream, flags);
>> +	      fprintf_filtered (stream, "typedef ");
>> +
>> +	      /* We want to print typedefs with substitutions
>> +		 from the template parameters or globally-known
>> +		 typedefs but not local typedefs.  */
>> +	      c_print_type (target,
>> +			    TYPE_TYPEDEF_FIELD_NAME (type, i),
>> +			    stream, show - 1, level + 4,
>> +			    &semi_local_flags);
>> +	      fprintf_filtered (stream, ";\n");
>> +	    }
>> +	}
>> +
>> +      if (flags->print_offsets && level > 0)
>> +	print_spaces_filtered (OFFSET_SPC_LEN, stream);
>> +
>> +      fprintfi_filtered (level, stream, "}");
>> +    }
>> +
>> +  if (show > 0 && flags->print_offsets)
>> +    fprintf_filtered (stream, " /* total size: %4u bytes */",
>> +		      TYPE_LENGTH (type));
>> +
>> +  do_cleanups (local_cleanups);
>> +}
>> +
>>  /* Print the name of the type (or the ultimate pointer target,
>>     function value or array element), or the description of a structure
>>     or union.
>> @@ -898,10 +1476,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>>  		   int show, int level, const struct type_print_options *flags)
>>  {
>>    int i;
>> -  int len, real_len;
>> -  enum access_specifier section_type;
>> -  int need_access_label = 0;
>> -  int j, len2;
>> +  int len;
>> +  int j;
>>  
>>    QUIT;
>>  
>> @@ -918,15 +1494,16 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>>       folk tend to expect things like "class5 *foo" rather than "struct
>>       class5 *foo".  */
>>  
>> -  if (show <= 0
>> -      && TYPE_NAME (type) != NULL)
>> +  struct type *ttype = check_typedef (type);
>> +
>> +  if (show <= 0 && TYPE_NAME (type) != NULL)
>>      {
>>        c_type_print_modifier (type, stream, 0, 1);
>>        print_name_maybe_canonical (TYPE_NAME (type), flags, stream);
>>        return;
>>      }
>>  
>> -  type = check_typedef (type);
>> +  type = ttype;
>>  
>>    switch (TYPE_CODE (type))
>>      {
>> @@ -958,416 +1535,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>>  
>>      case TYPE_CODE_STRUCT:
>>      case TYPE_CODE_UNION:
>> -      {
>> -	struct type_print_options local_flags = *flags;
>> -	struct type_print_options semi_local_flags = *flags;
>> -	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
>> -
>> -	local_flags.local_typedefs = NULL;
>> -	semi_local_flags.local_typedefs = NULL;
>> -
>> -	if (!flags->raw)
>> -	  {
>> -	    if (flags->local_typedefs)
>> -	      local_flags.local_typedefs
>> -		= copy_typedef_hash (flags->local_typedefs);
>> -	    else
>> -	      local_flags.local_typedefs = create_typedef_hash ();
>> -
>> -	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
>> -	  }
>> -
>> -	c_type_print_modifier (type, stream, 0, 1);
>> -	if (TYPE_CODE (type) == TYPE_CODE_UNION)
>> -	  fprintf_filtered (stream, "union ");
>> -	else if (TYPE_DECLARED_CLASS (type))
>> -	  fprintf_filtered (stream, "class ");
>> -	else
>> -	  fprintf_filtered (stream, "struct ");
>> -
>> -	/* Print the tag if it exists.  The HP aCC compiler emits a
>> -	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
>> -	   enum}" tag for unnamed struct/union/enum's, which we don't
>> -	   want to print.  */
>> -	if (TYPE_TAG_NAME (type) != NULL
>> -	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
>> -	  {
>> -	    /* When printing the tag name, we are still effectively
>> -	       printing in the outer context, hence the use of FLAGS
>> -	       here.  */
>> -	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
>> -	    if (show > 0)
>> -	      fputs_filtered (" ", stream);
>> -	  }
>> -
>> -	if (show < 0)
>> -	  {
>> -	    /* If we just printed a tag name, no need to print anything
>> -	       else.  */
>> -	    if (TYPE_TAG_NAME (type) == NULL)
>> -	      fprintf_filtered (stream, "{...}");
>> -	  }
>> -	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
>> -	  {
>> -	    struct type *basetype;
>> -	    int vptr_fieldno;
>> -
>> -	    c_type_print_template_args (&local_flags, type, stream);
>> -
>> -	    /* Add in template parameters when printing derivation info.  */
>> -	    add_template_parameters (local_flags.local_typedefs, type);
>> -	    cp_type_print_derivation_info (stream, type, &local_flags);
>> -
>> -	    /* This holds just the global typedefs and the template
>> -	       parameters.  */
>> -	    semi_local_flags.local_typedefs
>> -	      = copy_typedef_hash (local_flags.local_typedefs);
>> -	    if (semi_local_flags.local_typedefs)
>> -	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
>> -
>> -	    /* Now add in the local typedefs.  */
>> -	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
>> -
>> -	    fprintf_filtered (stream, "{\n");
>> -	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
>> -		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
>> -	      {
>> -		if (TYPE_STUB (type))
>> -		  fprintfi_filtered (level + 4, stream,
>> -				     _("<incomplete type>\n"));
>> -		else
>> -		  fprintfi_filtered (level + 4, stream,
>> -				     _("<no data fields>\n"));
>> -	      }
>> -
>> -	    /* Start off with no specific section type, so we can print
>> -	       one for the first field we find, and use that section type
>> -	       thereafter until we find another type.  */
>> -
>> -	    section_type = s_none;
>> -
>> -	    /* For a class, if all members are private, there's no need
>> -	       for a "private:" label; similarly, for a struct or union
>> -	       masquerading as a class, if all members are public, there's
>> -	       no need for a "public:" label.  */
>> -
>> -	    if (TYPE_DECLARED_CLASS (type))
>> -	      {
>> -		QUIT;
>> -		len = TYPE_NFIELDS (type);
>> -		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> -		  if (!TYPE_FIELD_PRIVATE (type, i))
>> -		    {
>> -		      need_access_label = 1;
>> -		      break;
>> -		    }
>> -		QUIT;
>> -		if (!need_access_label)
>> -		  {
>> -		    len2 = TYPE_NFN_FIELDS (type);
>> -		    for (j = 0; j < len2; j++)
>> -		      {
>> -			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> -			for (i = 0; i < len; i++)
>> -			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> -									  j), i))
>> -			    {
>> -			      need_access_label = 1;
>> -			      break;
>> -			    }
>> -			if (need_access_label)
>> -			  break;
>> -		      }
>> -		  }
>> -		QUIT;
>> -		if (!need_access_label)
>> -		  {
>> -		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> -		      {
>> -			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> -			  {
>> -			    need_access_label = 1;
>> -			    break;
>> -			  }
>> -		      }
>> -		  }
>> -	      }
>> -	    else
>> -	      {
>> -		QUIT;
>> -		len = TYPE_NFIELDS (type);
>> -		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> -		  if (TYPE_FIELD_PRIVATE (type, i)
>> -		      || TYPE_FIELD_PROTECTED (type, i))
>> -		    {
>> -		      need_access_label = 1;
>> -		      break;
>> -		    }
>> -		QUIT;
>> -		if (!need_access_label)
>> -		  {
>> -		    len2 = TYPE_NFN_FIELDS (type);
>> -		    for (j = 0; j < len2; j++)
>> -		      {
>> -			QUIT;
>> -			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> -			for (i = 0; i < len; i++)
>> -			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
>> -									   j), i)
>> -			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> -									    j),
>> -							i))
>> -			    {
>> -			      need_access_label = 1;
>> -			      break;
>> -			    }
>> -			if (need_access_label)
>> -			  break;
>> -		      }
>> -		  }
>> -		QUIT;
>> -		if (!need_access_label)
>> -		  {
>> -		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> -		      {
>> -			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
>> -			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> -			  {
>> -			    need_access_label = 1;
>> -			    break;
>> -			  }
>> -		      }
>> -		  }
>> -	      }
>> -
>> -	    /* If there is a base class for this type,
>> -	       do not print the field that it occupies.  */
>> -
>> -	    len = TYPE_NFIELDS (type);
>> -	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
>> -	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> -	      {
>> -		QUIT;
>> -
>> -		/* If we have a virtual table pointer, omit it.  Even if
>> -		   virtual table pointers are not specifically marked in
>> -		   the debug info, they should be artificial.  */
>> -		if ((i == vptr_fieldno && type == basetype)
>> -		    || TYPE_FIELD_ARTIFICIAL (type, i))
>> -		  continue;
>> -
>> -		if (need_access_label)
>> -		  {
>> -		    section_type = output_access_specifier
>> -		      (stream, section_type, level,
>> -		       TYPE_FIELD_PROTECTED (type, i),
>> -		       TYPE_FIELD_PRIVATE (type, i));
>> -		  }
>> -
>> -		print_spaces_filtered (level + 4, stream);
>> -		if (field_is_static (&TYPE_FIELD (type, i)))
>> -		  fprintf_filtered (stream, "static ");
>> -		c_print_type (TYPE_FIELD_TYPE (type, i),
>> -			      TYPE_FIELD_NAME (type, i),
>> -			      stream, show - 1, level + 4,
>> -			      &local_flags);
>> -		if (!field_is_static (&TYPE_FIELD (type, i))
>> -		    && TYPE_FIELD_PACKED (type, i))
>> -		  {
>> -		    /* It is a bitfield.  This code does not attempt
>> -		       to look at the bitpos and reconstruct filler,
>> -		       unnamed fields.  This would lead to misleading
>> -		       results if the compiler does not put out fields
>> -		       for such things (I don't know what it does).  */
>> -		    fprintf_filtered (stream, " : %d",
>> -				      TYPE_FIELD_BITSIZE (type, i));
>> -		  }
>> -		fprintf_filtered (stream, ";\n");
>> -	      }
>> -
>> -	  /* If there are both fields and methods, put a blank line
>> -	     between them.  Make sure to count only method that we
>> -	     will display; artificial methods will be hidden.  */
>> -	  len = TYPE_NFN_FIELDS (type);
>> -	  if (!flags->print_methods)
>> -	    len = 0;
>> -	  real_len = 0;
>> -	  for (i = 0; i < len; i++)
>> -	    {
>> -	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
>> -	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
>> -	      int j;
>> -
>> -	      for (j = 0; j < len2; j++)
>> -		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
>> -		  real_len++;
>> -	    }
>> -	  if (real_len > 0 && section_type != s_none)
>> -	    fprintf_filtered (stream, "\n");
>> -
>> -	  /* C++: print out the methods.  */
>> -	  for (i = 0; i < len; i++)
>> -	    {
>> -	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
>> -	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
>> -	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
>> -	      const char *name = type_name_no_tag (type);
>> -	      int is_constructor = name && strcmp (method_name,
>> -						   name) == 0;
>> -
>> -	      for (j = 0; j < len2; j++)
>> -		{
>> -		  const char *mangled_name;
>> -		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
>> -		  char *demangled_name;
>> -		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
>> -		  int is_full_physname_constructor =
>> -		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
>> -		    || is_constructor_name (physname)
>> -		    || is_destructor_name (physname)
>> -		    || method_name[0] == '~';
>> -
>> -		  /* Do not print out artificial methods.  */
>> -		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
>> -		    continue;
>> -
>> -		  QUIT;
>> -		  section_type = output_access_specifier
>> -		    (stream, section_type, level,
>> -		     TYPE_FN_FIELD_PROTECTED (f, j),
>> -		     TYPE_FN_FIELD_PRIVATE (f, j));
>> -
>> -		  print_spaces_filtered (level + 4, stream);
>> -		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
>> -		    fprintf_filtered (stream, "virtual ");
>> -		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
>> -		    fprintf_filtered (stream, "static ");
>> -		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
>> -		    {
>> -		      /* Keep GDB from crashing here.  */
>> -		      fprintf_filtered (stream,
>> -					_("<undefined type> %s;\n"),
>> -					TYPE_FN_FIELD_PHYSNAME (f, j));
>> -		      break;
>> -		    }
>> -		  else if (!is_constructor	/* Constructors don't
>> -						   have declared
>> -						   types.  */
>> -			   && !is_full_physname_constructor  /* " " */
>> -			   && !is_type_conversion_operator (type, i, j))
>> -		    {
>> -		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
>> -				    "", stream, -1, 0,
>> -				    &local_flags);
>> -		      fputs_filtered (" ", stream);
>> -		    }
>> -		  if (TYPE_FN_FIELD_STUB (f, j))
>> -		    {
>> -		      /* Build something we can demangle.  */
>> -		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
>> -		      mangled_name = mangled_name_holder.get ();
>> -		    }
>> -		  else
>> -		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
>> -
>> -		  demangled_name =
>> -		    gdb_demangle (mangled_name,
>> -				  DMGL_ANSI | DMGL_PARAMS);
>> -		  if (demangled_name == NULL)
>> -		    {
>> -		      /* In some cases (for instance with the HP
>> -			 demangling), if a function has more than 10
>> -			 arguments, the demangling will fail.
>> -			 Let's try to reconstruct the function
>> -			 signature from the symbol information.  */
>> -		      if (!TYPE_FN_FIELD_STUB (f, j))
>> -			{
>> -			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
>> -			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
>> -
>> -			  cp_type_print_method_args (mtype,
>> -						     "",
>> -						     method_name,
>> -						     staticp,
>> -						     stream, &local_flags);
>> -			}
>> -		      else
>> -			fprintf_filtered (stream,
>> -					  _("<badly mangled name '%s'>"),
>> -					  mangled_name);
>> -		    }
>> -		  else
>> -		    {
>> -		      char *p;
>> -		      char *demangled_no_class
>> -			= remove_qualifiers (demangled_name);
>> -
>> -		      /* Get rid of the `static' appended by the
>> -			 demangler.  */
>> -		      p = strstr (demangled_no_class, " static");
>> -		      if (p != NULL)
>> -			{
>> -			  int length = p - demangled_no_class;
>> -			  char *demangled_no_static;
>> -
>> -			  demangled_no_static
>> -			    = (char *) xmalloc (length + 1);
>> -			  strncpy (demangled_no_static,
>> -				   demangled_no_class, length);
>> -			  *(demangled_no_static + length) = '\0';
>> -			  fputs_filtered (demangled_no_static, stream);
>> -			  xfree (demangled_no_static);
>> -			}
>> -		      else
>> -			fputs_filtered (demangled_no_class, stream);
>> -		      xfree (demangled_name);
>> -		    }
>> -
>> -		  fprintf_filtered (stream, ";\n");
>> -		}
>> -	    }
>> -
>> -	  /* Print typedefs defined in this class.  */
>> -
>> -	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
>> -	    {
>> -	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
>> -		fprintf_filtered (stream, "\n");
>> -
>> -	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
>> -		{
>> -		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
>> -
>> -		  /* Dereference the typedef declaration itself.  */
>> -		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
>> -		  target = TYPE_TARGET_TYPE (target);
>> -
>> -		  if (need_access_label)
>> -		    {
>> -		      section_type = output_access_specifier
>> -			(stream, section_type, level,
>> -			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
>> -			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
>> -		    }
>> -		  print_spaces_filtered (level + 4, stream);
>> -		  fprintf_filtered (stream, "typedef ");
>> -
>> -		  /* We want to print typedefs with substitutions
>> -		     from the template parameters or globally-known
>> -		     typedefs but not local typedefs.  */
>> -		  c_print_type (target,
>> -				TYPE_TYPEDEF_FIELD_NAME (type, i),
>> -				stream, show - 1, level + 4,
>> -				&semi_local_flags);
>> -		  fprintf_filtered (stream, ";\n");
>> -		}
>> -	    }
>> -
>> -	    fprintfi_filtered (level, stream, "}");
>> -	  }
>> -
>> -	do_cleanups (local_cleanups);
>> -      }
>> +      c_type_print_base_struct_union (type, stream, show, level, flags);
>>        break;
>>  
>>      case TYPE_CODE_ENUM:
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index 675f6e7bc8..f7a45dd5dd 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -17095,6 +17095,10 @@ names are substituted when printing other types.
>>  @item T
>>  Print typedefs defined in the class.  This is the default, but the flag
>>  exists in case you change the default with @command{set print type typedefs}.
>> +
>> +@item o
>> +Print the offsets and sizes of fields in a struct, similar to what the
>> +@command{pahole} tool does.
>>  @end table
>>  
>>  @kindex ptype
>> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
>> new file mode 100644
>> index 0000000000..f9a57fd3db
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
>> @@ -0,0 +1,113 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
>> +
>> +#include <stdint.h>
>> +
>> +/* A struct with many types of fields, in order to test 'ptype
>> +   /o'.  */
>> +
>> +struct abc
>> +{
>> +  /* Virtual destructor.  */
>> +  virtual ~abc ()
>> +  {}
>> +
>> +  /* 8-byte address.  Because of the virtual destructor above, this
>> +     field's offset will be 8.  */
>> +  void *field1;
>> +
>> +  /* No hole here.  */
>> +
>> +  /* 4-byte int bitfield of 1-bit.  */
>> +  unsigned int field2 : 1;
>> +
>> +  /* 31-bit hole here.  */
>> +
>> +  /* 4-byte int.  */
>> +  int field3;
>> +
>> +  /* No hole here.  */
>> +
>> +  /* 1-byte char.  */
>> +  char field4;
>> +
>> +  /* 7-byte hole here.  */
>> +
>> +  /* 8-byte int.  */
>> +  uint64_t field5;
>> +
>> +  /* We just print the offset and size of a union, ignoring its
>> +     fields.  */
>> +  union
>> +  {
>> +    /* 8-byte address.  */
>> +    void *field6;
>> +
>> +    /* 4-byte int.  */
>> +    int field7;
>> +  } field8;
>> +
>> +  /* Empty constructor.  */
>> +  abc ()
>> +  {}
>> +};
>> +
>> +/* This struct will be nested inside 'struct xyz'.  */
>> +
>> +struct tuv
>> +{
>> +  int a1;
>> +
>> +  char *a2;
>> +
>> +  int a3;
>> +};
>> +
>> +/* This struct will be nested inside 'struct pqr'.  */
>> +
>> +struct xyz
>> +{
>> +  int f1;
>> +
>> +  char f2;
>> +
>> +  void *f3;
>> +
>> +  struct tuv f4;
>> +};
>> +
>> +/* A struct with a nested struct.  */
>> +
>> +struct pqr
>> +{
>> +  int ff1;
>> +
>> +  struct xyz ff2;
>> +
>> +  char ff3;
>> +};
>> +
>> +int
>> +main (int argc, char *argv[])
>> +{
>> +  struct abc foo;
>> +  struct pqr bar;
>> +
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
>> new file mode 100644
>> index 0000000000..4f84416dc5
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
>> @@ -0,0 +1,77 @@
>> +# This testcase is part of GDB, the GNU debugger.
>> +
>> +# Copyright 2017 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/>.
>> +
>> +standard_testfile .cc ptype-offsets.cc
>> +
>> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
>> +# that the expected holes will be present in the struct.
>> +if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
>> +    untested "test work only on x86_64 lp64"
>> +    return 0
>> +}
>> +
>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
>> +	  { debug c++ optimize=-O0 }] } {
>> +    return -1
>> +}
>> +
>> +# Test general offset printing, ctor/dtor printing, union, formatting.
>> +gdb_test "ptype /o struct abc" \
>> +    [multi_line \
>> +"type = struct abc {" \
>> +"/\\\* offset    |  size \\\*/" \
>> +"                         public:" \
>> +"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
>> +"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
>> +"/\\\* XXX  7-bit hole   \\\*/" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*   20      |     4 \\\*/    int field3;" \
>> +"/\\\*   24      |     1 \\\*/    char field4;" \
>> +"/\\\* XXX  7-byte hole  \\\*/" \
>> +"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
>> +"/\\\*   40      |     8 \\\*/    union {" \
>> +"/\\\*                 8 \\\*/        void \\\*field6;" \
>> +"/\\\*                 4 \\\*/        int field7;" \
>> +"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
>> +"" \
>> +"                           abc\\(void\\);" \
>> +"                           ~abc\\(\\);" \
>> +"} /\\\* total size:   48 bytes \\\*/"] \
>> +    "ptype offset struct abc"
>> +
>> +# Test nested structs.
>> +gdb_test "ptype /o struct pqr" \
>> +    [multi_line \
>> +"type = struct pqr {" \
>> +"/\\\* offset    |  size \\\*/" \
>> +"/\\\*    0      |     4 \\\*/    int f1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*    8      |    16 \\\*/    struct xyz {" \
>> +"/\\\*    8      |     4 \\\*/        int f1;" \
>> +"/\\\*   12      |     1 \\\*/        char f2;" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
>> +"/\\\*   24      |    24 \\\*/        struct tuv {" \
>> +"/\\\*   24      |     4 \\\*/            int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   32      |     8 \\\*/            char *a2;" \
>> +"/\\\*   40      |     4 \\\*/            int a3;" \
>> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
>> +"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
>> +"/\\\*   48      |     1 \\\*/    char ff3;" \
>> +"} /\\\* total size:   56 bytes \\\*/"] \
>> +    "ptype offset struct pqr"
>> diff --git a/gdb/typeprint.c b/gdb/typeprint.c
>> index 427af17ad7..1463e802ad 100644
>> --- a/gdb/typeprint.c
>> +++ b/gdb/typeprint.c
>> @@ -42,6 +42,8 @@ const struct type_print_options type_print_raw_options =
>>    1,				/* raw */
>>    1,				/* print_methods */
>>    1,				/* print_typedefs */
>> +  0,				/* print_offsets */
>> +  0,				/* offset_bitpos */
>>    NULL,				/* local_typedefs */
>>    NULL,				/* global_table */
>>    NULL				/* global_printers */
>> @@ -54,6 +56,8 @@ static struct type_print_options default_ptype_flags =
>>    0,				/* raw */
>>    1,				/* print_methods */
>>    1,				/* print_typedefs */
>> +  0,				/* print_offsets */
>> +  0,				/* offset_bitpos */
>>    NULL,				/* local_typedefs */
>>    NULL,				/* global_table */
>>    NULL				/* global_printers */
>> @@ -438,6 +442,9 @@ whatis_exp (const char *exp, int show)
>>  		case 'T':
>>  		  flags.print_typedefs = 1;
>>  		  break;
>> +		case 'o':
>> +		  flags.print_offsets = 1;
>> +		  break;
>>  		default:
>>  		  error (_("unrecognized flag '%c'"), *exp);
>>  		}
>> @@ -497,6 +504,11 @@ whatis_exp (const char *exp, int show)
>>  	real_type = value_rtti_type (val, &full, &top, &using_enc);
>>      }
>>  
>> +  if (flags.print_offsets &&
>> +      (TYPE_CODE (type) == TYPE_CODE_STRUCT
>> +       || TYPE_CODE (type) == TYPE_CODE_UNION))
>> +    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
>> +
>>    printf_filtered ("type = ");
>>  
>>    if (!flags.raw)
>> @@ -722,7 +734,8 @@ Available FLAGS are:\n\
>>    /m    do not print methods defined in a class\n\
>>    /M    print methods defined in a class\n\
>>    /t    do not print typedefs defined in a class\n\
>> -  /T    print typedefs defined in a class"));
>> +  /T    print typedefs defined in a class\n\
>> +  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
>>    set_cmd_completer (c, expression_completer);
>>  
>>    c = add_com ("whatis", class_vars, whatis_command,
>> diff --git a/gdb/typeprint.h b/gdb/typeprint.h
>> index a458aa4e2f..a2a5285012 100644
>> --- a/gdb/typeprint.h
>> +++ b/gdb/typeprint.h
>> @@ -35,6 +35,15 @@ struct type_print_options
>>    /* True means print typedefs in a class.  */
>>    unsigned int print_typedefs : 1;
>>  
>> +  /* True means to print offsets, a la 'pahole'.  */
>> +  unsigned int print_offsets : 1;
>> +
>> +  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
>> +     This is needed for when we are printing nested structs and want
>> +     to make sure that the printed offset for each field carries off
>> +     the offset of the outter struct.  */
>> +  unsigned int offset_bitpos;
>> +
>>    /* If not NULL, a local typedef hash table used when printing a
>>       type.  */
>>    struct typedef_hash_table *local_typedefs;
>> -- 
>> 2.13.3
>
> -- 
> Sergio
> GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
> Please send encrypted e-mail if possible
> http://sergiodj.net/

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-11-28 21:21 ` [PATCH v2] " Sergio Durigan Junior
  2017-11-29  3:28   ` Eli Zaretskii
  2017-12-04 15:03   ` Sergio Durigan Junior
@ 2017-12-11 15:43   ` Simon Marchi
  2017-12-11 18:59     ` Sergio Durigan Junior
  2 siblings, 1 reply; 75+ messages in thread
From: Simon Marchi @ 2017-12-11 15:43 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches; +Cc: Tom Tromey, Eli Zaretskii

On 2017-11-28 04:21 PM, Sergio Durigan Junior wrote:
> Changes from v1:
> 
> - Address Tom's comments (the command now prints offset information
>   about unions, and the offset info is carried on for nested structs).
> 
> - Address Eli's comments.
> 
> - Extended testcase.
> 
> - A "bit" of cleanup on 'c_type_print_base'.
> 
> 
> This commit implements the pahole-like '/o' option for 'ptype', which
> prints the offsets and sizes of struct fields, reporting whenever
> there is a hole found.
> 
> The output is heavily based on pahole(1), with a few modifications
> here and there to adjust it to our reality.  Here's an example:
> 
>   (gdb) ptype /o stap_probe
>   /* offset    |  size */
>   struct stap_probe {
>   /*    0      |    40 */    struct probe {
>   /*    0      |     8 */        const probe_ops *pops;
>   /*    8      |     8 */        gdbarch *arch;
>   /*   16      |     8 */        const char *name;
>   /*   24      |     8 */        const char *provider;
>   /*   32      |     8 */        CORE_ADDR address;
> 			     } /* total size:   40 bytes */ p;
>   /*   40      |     8 */    CORE_ADDR sem_addr;
>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>   /* XXX  7-bit hole   */
>   /* XXX  7-byte hole  */
>   /*   56      |     8 */    union {
>   /*                 8 */        const char *text;
>   /*                 8 */        VEC_stap_probe_arg_s *vec;
> 			     } /* total size:    8 bytes */ args_u;
>   } /* total size:   64 bytes */
> 
> A big part of this patch handles the formatting logic of 'ptype',
> which is a bit messy.  I tried to be not very invasive, but I had to
> do some cleanups here and there to make life easier.
> 
> This patch is the start of a long-term work I'll do to flush the local
> patches we carry for Fedora GDB.  In this specific case, I'm aiming at
> upstreaming the feature implemented by the 'pahole.py' script that is
> shipped with Fedora GDB:
> 
>   <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>
> 
> This has been regression-tested on the BuildBot.  There's a new
> testcase for it, along with an update to the documentation.  I also
> thought it was worth mentioning this feature in the NEWS file.

Seems like you'll need to do a bit of conflict handling with the code from

  Record and output access specifiers for nested typedefs
  c191a6875b118fce30e7dc4d9e4bd20eff850270

:(

> gdb/ChangeLog:
> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
> 
> 	PR cli/16224
> 	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
> 	* c-typeprint.c (OFFSET_SPC_LEN): New define.
> 	(print_spaces_filtered_with_print_options): New function.
> 	(output_access_specifier): Take new argument FLAGS.  Modify
> 	function to call 'print_spaces_filtered_with_print_options'.
> 	(c_print_type_union_field_offset): New function.
> 	(c_print_type_struct_field_offset): New function.
> 	(need_access_label_p): New function, with contents from
> 	'c_type_print_base'.
> 	(c_type_print_base_struct_union): Likewise.
> 	(c_type_print_base): Print offsets and sizes for struct
> 	fields.  Struct/union handling code move to functions
> 	mentioned above.
> 	* typeprint.c (const struct type_print_options
> 	type_print_raw_options): Initialize 'print_offsets' and
> 	'offset_bitpos'.
> 	(static struct type_print_options default_ptype_flags):
> 	Likewise.
> 	(whatis_exp): Handle '/o' option.
> 	(_initialize_typeprint): Add '/o' flag to ptype's help.
> 	* typeprint.h (struct type_print_options) <print_offsets>: New
> 	field.
> 	<offset_bitpos>: Likewise.
> 
> gdb/testsuite/ChangeLog:
> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
> 
> 	PR cli/16224
> 	* gdb.base/ptype-offsets.cc: New file.
> 	* gdb.base/ptype-offsets.exp: New file.
> 
> gdb/doc/ChangeLog:
> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
> 
> 	PR cli/16224
> 	* gdb.texinfo (ptype): Add new flag '/o'.
> ---
>  gdb/NEWS                                 |    3 +
>  gdb/c-typeprint.c                        | 1016 +++++++++++++++++-------------
>  gdb/doc/gdb.texinfo                      |    4 +
>  gdb/testsuite/gdb.base/ptype-offsets.cc  |  113 ++++
>  gdb/testsuite/gdb.base/ptype-offsets.exp |   77 +++
>  gdb/typeprint.c                          |   15 +-
>  gdb/typeprint.h                          |    9 +
>  7 files changed, 812 insertions(+), 425 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 754ce103bd..1247021046 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,9 @@
>  
>  *** Changes since GDB 8.0
>  
> +* The 'ptype' command now accepts a '/o' flag, which prints the
> +  offsets and sizes of fields in a struct, like the pahole(1) tool.
> +
>  * GDB now uses the GNU MPFR library, if available, to emulate target
>    floating-point arithmetic during expression evaluation when the target
>    uses different floating-point formats than the host.  At least version
> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
> index ed5a1a4b8a..39334ccf88 100644
> --- a/gdb/c-typeprint.c
> +++ b/gdb/c-typeprint.c
> @@ -32,6 +32,14 @@
>  #include "cp-abi.h"
>  #include "cp-support.h"
>  
> +/* When printing the offsets of a struct and its fields (i.e., 'ptype
> +   /o'; type_print_options::print_offsets), we use this many
> +   characters when printing the offset information at the beginning of
> +   the line.  This is needed in order to generate the correct amount
> +   of whitespaces when no offset info should be printed for a certain
> +   field.  */
> +#define OFFSET_SPC_LEN 23
> +
>  /* A list of access specifiers used for printing.  */
>  
>  enum access_specifier
> @@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
>      fputs_filtered (_("] "), stream);
>  }
>  
> +/* Use 'print_spaces_filtered', but take into consideration the
> +   type_print_options FLAGS in order to determine how many whitespaces
> +   will be printed.  */
> +
> +static void
> +print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
> +					const struct type_print_options *flags)

Missing spaces here.

> +{
> +  if (!flags->print_offsets)
> +    print_spaces_filtered (level, stream);
> +  else
> +    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
> +}
> +
>  /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
>     last access specifier output (typically returned by this function).  */
>  
>  static enum access_specifier
>  output_access_specifier (struct ui_file *stream,
>  			 enum access_specifier last_access,
> -			 int level, bool is_protected, bool is_private)
> +			 int level, bool is_protected, bool is_private,
> +			 const struct type_print_options *flags)
>  {
>    if (is_protected)
>      {
>        if (last_access != s_protected)
>  	{
>  	  last_access = s_protected;
> -	  fprintfi_filtered (level + 2, stream,
> -			     "protected:\n");
> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
> +	  fprintf_filtered (stream, "protected:\n");
>  	}
>      }
>    else if (is_private)
> @@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
>        if (last_access != s_private)
>  	{
>  	  last_access = s_private;
> -	  fprintfi_filtered (level + 2, stream,
> -			     "private:\n");
> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
> +	  fprintf_filtered (stream, "private:\n");
>  	}
>      }
>    else
> @@ -867,14 +890,569 @@ output_access_specifier (struct ui_file *stream,
>        if (last_access != s_public)
>  	{
>  	  last_access = s_public;
> -	  fprintfi_filtered (level + 2, stream,
> -			     "public:\n");
> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
> +	  fprintf_filtered (stream, "public:\n");
>  	}
>      }
>  
>    return last_access;
>  }
>  
> +/* Print information about the offset of TYPE inside its union.
> +   FIELD_IDX represents the index of this TYPE inside the union.  We
> +   just print the type size, and nothing more.
> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
> +				 struct ui_file *stream)
> +{
> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +
> +  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
> +}
> +
> +/* Print information about the offset of TYPE inside its struct.
> +   FIELD_IDX represents the index of this TYPE inside the struct, and
> +   ENDPOS is the end position of the previous type (this is how we
> +   calculate whether there are holes in the struct).  At the end,
> +   ENDPOS is updated.
> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
> +				  unsigned int *endpos, struct ui_file *stream,
> +				  unsigned int offset_bitpos)
> +{
> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
> +  unsigned int fieldsize_bit;
> +
> +  if (*endpos > 0 && *endpos < bitpos)
> +    {
> +      /* If ENDPOS is smaller than the current type's bitpos, it means
> +	 there's a hole in the struct, so we report it here.  */
> +      unsigned int hole = bitpos - *endpos;
> +      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
> +      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
> +
> +      if (hole_bit > 0)
> +	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
> +
> +      if (hole_byte > 0)
> +	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
> +    }
> +
> +  /* The position of the field, relative to the beginning of the
> +     struct.  Assume this number will have 4 digits.  */
> +  fprintf_filtered (stream, "/* %4u",
> +		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT);
> +
> +  if (TYPE_FIELD_PACKED (type, field_idx))
> +    {
> +      /* We're dealing with a bitfield.  Print how many bits are left
> +	 to be used.  */
> +      fieldsize_bit = TYPE_FIELD_BITSIZE (type, field_idx);
> +      fprintf_filtered (stream, ":%u",
> +			fieldsize_byte * TARGET_CHAR_BIT - fieldsize_bit);
> +    }
> +  else
> +    {
> +      fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
> +      fprintf_filtered (stream, "   ");
> +    }
> +
> +  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
> +
> +  *endpos = bitpos + fieldsize_bit;
> +}
> +
> +/* Return true is an access label (i.e., "public:", "private:",
> +   "protected:") needs to be printed for TYPE.  */
> +
> +static bool
> +need_access_label_p (struct type *type)
> +{
> +  bool need_access_label = false;
> +  int i, j;
> +  int len, len2;
> +
> +  if (TYPE_DECLARED_CLASS (type))
> +    {
> +      QUIT;
> +      len = TYPE_NFIELDS (type);
> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> +	if (!TYPE_FIELD_PRIVATE (type, i))
> +	  {
> +	    need_access_label = true;
> +	    break;
> +	  }
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  len2 = TYPE_NFN_FIELDS (type);
> +	  for (j = 0; j < len2; j++)
> +	    {
> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> +	      for (i = 0; i < len; i++)
> +		if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> +								j), i))
> +		  {
> +		    need_access_label = true;
> +		    break;
> +		  }
> +	      if (need_access_label)
> +		break;
> +	    }
> +	}
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> +	    {
> +	      if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> +		{
> +		  need_access_label = true;
> +		  break;
> +		}
> +	    }
> +	}
> +    }
> +  else
> +    {
> +      QUIT;
> +      len = TYPE_NFIELDS (type);
> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> +	if (TYPE_FIELD_PRIVATE (type, i)
> +	    || TYPE_FIELD_PROTECTED (type, i))
> +	  {
> +	    need_access_label = true;
> +	    break;
> +	  }
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  len2 = TYPE_NFN_FIELDS (type);
> +	  for (j = 0; j < len2; j++)
> +	    {
> +	      QUIT;
> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> +	      for (i = 0; i < len; i++)
> +		if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
> +								 j), i)
> +		    || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> +								  j),
> +					      i))
> +		  {
> +		    need_access_label = true;
> +		    break;
> +		  }
> +	      if (need_access_label)
> +		break;
> +	    }
> +	}
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> +	    {
> +	      if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
> +		  || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> +		{
> +		  need_access_label = true;
> +		  break;
> +		}
> +	    }
> +	}
> +    }
> +  return need_access_label;
> +}
> +
> +/* Helper for 'c_type_print_base' that handles structs and unions.
> +   For a description of the arguments, see 'c_type_print_base'.  */
> +
> +static void
> +c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
> +				int show, int level,
> +				const struct type_print_options *flags)
> +{
> +  struct type_print_options local_flags = *flags;
> +  struct type_print_options semi_local_flags = *flags;
> +  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
> +
> +  local_flags.local_typedefs = NULL;
> +  semi_local_flags.local_typedefs = NULL;
> +
> +  if (!flags->raw)
> +    {
> +      if (flags->local_typedefs)
> +	local_flags.local_typedefs
> +	  = copy_typedef_hash (flags->local_typedefs);
> +      else
> +	local_flags.local_typedefs = create_typedef_hash ();
> +
> +      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
> +    }
> +
> +  c_type_print_modifier (type, stream, 0, 1);
> +  if (TYPE_CODE (type) == TYPE_CODE_UNION)
> +    fprintf_filtered (stream, "union ");
> +  else if (TYPE_DECLARED_CLASS (type))
> +    fprintf_filtered (stream, "class ");
> +  else
> +    fprintf_filtered (stream, "struct ");
> +
> +  /* Print the tag if it exists.  The HP aCC compiler emits a
> +     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
> +     enum}" tag for unnamed struct/union/enum's, which we don't
> +     want to print.  */
> +  if (TYPE_TAG_NAME (type) != NULL
> +      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
> +    {
> +      /* When printing the tag name, we are still effectively
> +	 printing in the outer context, hence the use of FLAGS
> +	 here.  */
> +      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
> +      if (show > 0)
> +	fputs_filtered (" ", stream);
> +    }
> +
> +  if (show < 0)
> +    {
> +      /* If we just printed a tag name, no need to print anything
> +	 else.  */
> +      if (TYPE_TAG_NAME (type) == NULL)
> +	fprintf_filtered (stream, "{...}");
> +    }
> +  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
> +    {
> +      struct type *basetype;
> +
> +      c_type_print_template_args (&local_flags, type, stream);
> +
> +      /* Add in template parameters when printing derivation info.  */
> +      add_template_parameters (local_flags.local_typedefs, type);
> +      cp_type_print_derivation_info (stream, type, &local_flags);
> +
> +      /* This holds just the global typedefs and the template
> +	 parameters.  */
> +      semi_local_flags.local_typedefs
> +	= copy_typedef_hash (local_flags.local_typedefs);
> +      if (semi_local_flags.local_typedefs)
> +	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
> +
> +      /* Now add in the local typedefs.  */
> +      recursively_update_typedef_hash (local_flags.local_typedefs, type);
> +
> +      fprintf_filtered (stream, "{\n");
> +      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
> +	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
> +	{
> +	  if (TYPE_STUB (type))
> +	    fprintfi_filtered (level + 4, stream,
> +			       _("<incomplete type>\n"));
> +	  else
> +	    fprintfi_filtered (level + 4, stream,
> +			       _("<no data fields>\n"));
> +	}
> +
> +      /* Start off with no specific section type, so we can print
> +	 one for the first field we find, and use that section type
> +	 thereafter until we find another type.  */
> +      enum access_specifier section_type = s_none;
> +
> +      /* For a class, if all members are private, there's no need
> +	 for a "private:" label; similarly, for a struct or union
> +	 masquerading as a class, if all members are public, there's
> +	 no need for a "public:" label.  */
> +      bool need_access_label = need_access_label_p (type);
> +
> +      /* If there is a base class for this type,
> +	 do not print the field that it occupies.  */
> +
> +      int len = TYPE_NFIELDS (type);
> +      int vptr_fieldno = get_vptr_fieldno (type, &basetype);
> +      unsigned int endpos = 0;
> +
> +      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
> +	{
> +	  QUIT;
> +
> +	  /* If we have a virtual table pointer, omit it.  Even if
> +	     virtual table pointers are not specifically marked in
> +	     the debug info, they should be artificial.  */
> +	  if ((i == vptr_fieldno && type == basetype)
> +	      || TYPE_FIELD_ARTIFICIAL (type, i))
> +	    continue;
> +
> +	  if (need_access_label)
> +	    {
> +	      section_type = output_access_specifier
> +		(stream, section_type, level,
> +		 TYPE_FIELD_PROTECTED (type, i),
> +		 TYPE_FIELD_PRIVATE (type, i),
> +		 flags);
> +	    }
> +
> +	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
> +
> +	  if (flags->print_offsets)
> +	    {
> +	      if (!is_static)
> +		{
> +		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
> +		    c_print_type_struct_field_offset (type, i, &endpos, stream,
> +						      flags->offset_bitpos);
> +		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
> +		    c_print_type_union_field_offset (type, i, stream);
> +		}
> +	      else
> +		print_spaces_filtered (OFFSET_SPC_LEN, stream);
> +	    }
> +
> +	  print_spaces_filtered (level + 4, stream);
> +	  if (is_static)
> +	    fprintf_filtered (stream, "static ");
> +
> +	  int newshow = show - 1;
> +
> +	  if (flags->print_offsets
> +	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
> +		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
> +	    {
> +	      /* If we're printing offsets and this field's type is
> +		 either a struct or an union, then we're interested in
> +		 expanding it.  */
> +	      ++newshow;
> +
> +	      /* Make sure we carry our offset when we expand the
> +		 struct.  */
> +	      local_flags.offset_bitpos
> +		= flags->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
> +	    }
> +
> +	  c_print_type (TYPE_FIELD_TYPE (type, i),
> +			TYPE_FIELD_NAME (type, i),
> +			stream, newshow, level + 4,
> +			&local_flags);
> +	  if (!is_static
> +	      && TYPE_FIELD_PACKED (type, i))
> +	    {
> +	      /* It is a bitfield.  This code does not attempt
> +		 to look at the bitpos and reconstruct filler,
> +		 unnamed fields.  This would lead to misleading
> +		 results if the compiler does not put out fields
> +		 for such things (I don't know what it does).  */
> +	      fprintf_filtered (stream, " : %d",
> +				TYPE_FIELD_BITSIZE (type, i));
> +	    }
> +	  fprintf_filtered (stream, ";\n");
> +	}
> +
> +      /* If there are both fields and methods, put a blank line
> +	 between them.  Make sure to count only method that we
> +	 will display; artificial methods will be hidden.  */
> +      len = TYPE_NFN_FIELDS (type);
> +      if (!flags->print_methods)
> +	len = 0;
> +      int real_len = 0;
> +      for (int i = 0; i < len; i++)
> +	{
> +	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
> +	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
> +	  int j;
> +
> +	  for (j = 0; j < len2; j++)
> +	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
> +	      real_len++;
> +	}
> +      if (real_len > 0 && section_type != s_none)
> +	fprintf_filtered (stream, "\n");
> +
> +      /* C++: print out the methods.  */
> +      for (int i = 0; i < len; i++)
> +	{
> +	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
> +	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
> +	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
> +	  const char *name = type_name_no_tag (type);
> +	  int is_constructor = name && strcmp (method_name,
> +					       name) == 0;
> +
> +	  for (j = 0; j < len2; j++)
> +	    {
> +	      const char *mangled_name;
> +	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
> +	      char *demangled_name;
> +	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
> +	      int is_full_physname_constructor =
> +		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
> +		|| is_constructor_name (physname)
> +		|| is_destructor_name (physname)
> +		|| method_name[0] == '~';
> +
> +	      /* Do not print out artificial methods.  */
> +	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
> +		continue;
> +
> +	      QUIT;
> +	      section_type = output_access_specifier
> +		(stream, section_type, level,
> +		 TYPE_FN_FIELD_PROTECTED (f, j),
> +		 TYPE_FN_FIELD_PRIVATE (f, j),
> +		 flags);
> +
> +	      print_spaces_filtered_with_print_options (level + 4, stream,
> +							flags);
> +	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
> +		fprintf_filtered (stream, "virtual ");
> +	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
> +		fprintf_filtered (stream, "static ");
> +	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
> +		{
> +		  /* Keep GDB from crashing here.  */
> +		  fprintf_filtered (stream,
> +				    _("<undefined type> %s;\n"),
> +				    TYPE_FN_FIELD_PHYSNAME (f, j));
> +		  break;
> +		}
> +	      else if (!is_constructor	/* Constructors don't
> +					   have declared
> +					   types.  */
> +		       && !is_full_physname_constructor  /* " " */
> +		       && !is_type_conversion_operator (type, i, j))
> +		{
> +		  unsigned int old_po = local_flags.print_offsets;
> +
> +		  /* Temporarily disable print_offsets, because it
> +		     would mess with indentation.  */
> +		  local_flags.print_offsets = 0;
> +		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
> +				"", stream, -1, 0,
> +				&local_flags);
> +		  local_flags.print_offsets = old_po;
> +		  fputs_filtered (" ", stream);
> +		}
> +	      if (TYPE_FN_FIELD_STUB (f, j))
> +		{
> +		  /* Build something we can demangle.  */
> +		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
> +		  mangled_name = mangled_name_holder.get ();
> +		}
> +	      else
> +		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
> +
> +	      demangled_name =
> +		gdb_demangle (mangled_name,
> +			      DMGL_ANSI | DMGL_PARAMS);
> +	      if (demangled_name == NULL)
> +		{
> +		  /* In some cases (for instance with the HP
> +		     demangling), if a function has more than 10
> +		     arguments, the demangling will fail.
> +		     Let's try to reconstruct the function
> +		     signature from the symbol information.  */
> +		  if (!TYPE_FN_FIELD_STUB (f, j))
> +		    {
> +		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
> +		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
> +
> +		      cp_type_print_method_args (mtype,
> +						 "",
> +						 method_name,
> +						 staticp,
> +						 stream, &local_flags);
> +		    }
> +		  else
> +		    fprintf_filtered (stream,
> +				      _("<badly mangled name '%s'>"),
> +				      mangled_name);
> +		}
> +	      else
> +		{
> +		  char *p;
> +		  char *demangled_no_class
> +		    = remove_qualifiers (demangled_name);
> +
> +		  /* Get rid of the `static' appended by the
> +		     demangler.  */
> +		  p = strstr (demangled_no_class, " static");
> +		  if (p != NULL)
> +		    {
> +		      int length = p - demangled_no_class;
> +		      char *demangled_no_static;
> +
> +		      demangled_no_static
> +			= (char *) xmalloc (length + 1);
> +		      strncpy (demangled_no_static,
> +			       demangled_no_class, length);
> +		      *(demangled_no_static + length) = '\0';
> +		      fputs_filtered (demangled_no_static, stream);
> +		      xfree (demangled_no_static);
> +		    }
> +		  else
> +		    fputs_filtered (demangled_no_class, stream);
> +		  xfree (demangled_name);
> +		}
> +
> +	      fprintf_filtered (stream, ";\n");
> +	    }
> +	}
> +
> +      /* Print typedefs defined in this class.  */
> +
> +      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
> +	{
> +	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
> +	    fprintf_filtered (stream, "\n");
> +
> +	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
> +	    {
> +	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
> +
> +	      /* Dereference the typedef declaration itself.  */
> +	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
> +	      target = TYPE_TARGET_TYPE (target);
> +
> +	      if (need_access_label)
> +		{
> +		  section_type = output_access_specifier
> +		    (stream, section_type, level,
> +		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
> +		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i),
> +		     flags);
> +		}
> +	      print_spaces_filtered_with_print_options (level + 4,
> +							stream, flags);
> +	      fprintf_filtered (stream, "typedef ");
> +
> +	      /* We want to print typedefs with substitutions
> +		 from the template parameters or globally-known
> +		 typedefs but not local typedefs.  */
> +	      c_print_type (target,
> +			    TYPE_TYPEDEF_FIELD_NAME (type, i),
> +			    stream, show - 1, level + 4,
> +			    &semi_local_flags);
> +	      fprintf_filtered (stream, ";\n");
> +	    }
> +	}
> +
> +      if (flags->print_offsets && level > 0)
> +	print_spaces_filtered (OFFSET_SPC_LEN, stream);
> +
> +      fprintfi_filtered (level, stream, "}");
> +    }
> +
> +  if (show > 0 && flags->print_offsets)
> +    fprintf_filtered (stream, " /* total size: %4u bytes */",
> +		      TYPE_LENGTH (type));
> +
> +  do_cleanups (local_cleanups);
> +}
> +
>  /* Print the name of the type (or the ultimate pointer target,
>     function value or array element), or the description of a structure
>     or union.
> @@ -898,10 +1476,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>  		   int show, int level, const struct type_print_options *flags)
>  {
>    int i;
> -  int len, real_len;
> -  enum access_specifier section_type;
> -  int need_access_label = 0;
> -  int j, len2;
> +  int len;
> +  int j;
>  
>    QUIT;
>  
> @@ -918,15 +1494,16 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>       folk tend to expect things like "class5 *foo" rather than "struct
>       class5 *foo".  */
>  
> -  if (show <= 0
> -      && TYPE_NAME (type) != NULL)
> +  struct type *ttype = check_typedef (type);

Is there a reason for introducing this new variable and doing the check_typedef before the if?

> +
> +  if (show <= 0 && TYPE_NAME (type) != NULL)
>      {
>        c_type_print_modifier (type, stream, 0, 1);
>        print_name_maybe_canonical (TYPE_NAME (type), flags, stream);
>        return;
>      }
>  
> -  type = check_typedef (type);
> +  type = ttype;
>  
>    switch (TYPE_CODE (type))
>      {
> @@ -958,416 +1535,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>  
>      case TYPE_CODE_STRUCT:
>      case TYPE_CODE_UNION:
> -      {
> -	struct type_print_options local_flags = *flags;
> -	struct type_print_options semi_local_flags = *flags;
> -	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
> -
> -	local_flags.local_typedefs = NULL;
> -	semi_local_flags.local_typedefs = NULL;
> -
> -	if (!flags->raw)
> -	  {
> -	    if (flags->local_typedefs)
> -	      local_flags.local_typedefs
> -		= copy_typedef_hash (flags->local_typedefs);
> -	    else
> -	      local_flags.local_typedefs = create_typedef_hash ();
> -
> -	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
> -	  }
> -
> -	c_type_print_modifier (type, stream, 0, 1);
> -	if (TYPE_CODE (type) == TYPE_CODE_UNION)
> -	  fprintf_filtered (stream, "union ");
> -	else if (TYPE_DECLARED_CLASS (type))
> -	  fprintf_filtered (stream, "class ");
> -	else
> -	  fprintf_filtered (stream, "struct ");
> -
> -	/* Print the tag if it exists.  The HP aCC compiler emits a
> -	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
> -	   enum}" tag for unnamed struct/union/enum's, which we don't
> -	   want to print.  */
> -	if (TYPE_TAG_NAME (type) != NULL
> -	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
> -	  {
> -	    /* When printing the tag name, we are still effectively
> -	       printing in the outer context, hence the use of FLAGS
> -	       here.  */
> -	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
> -	    if (show > 0)
> -	      fputs_filtered (" ", stream);
> -	  }
> -
> -	if (show < 0)
> -	  {
> -	    /* If we just printed a tag name, no need to print anything
> -	       else.  */
> -	    if (TYPE_TAG_NAME (type) == NULL)
> -	      fprintf_filtered (stream, "{...}");
> -	  }
> -	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
> -	  {
> -	    struct type *basetype;
> -	    int vptr_fieldno;
> -
> -	    c_type_print_template_args (&local_flags, type, stream);
> -
> -	    /* Add in template parameters when printing derivation info.  */
> -	    add_template_parameters (local_flags.local_typedefs, type);
> -	    cp_type_print_derivation_info (stream, type, &local_flags);
> -
> -	    /* This holds just the global typedefs and the template
> -	       parameters.  */
> -	    semi_local_flags.local_typedefs
> -	      = copy_typedef_hash (local_flags.local_typedefs);
> -	    if (semi_local_flags.local_typedefs)
> -	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
> -
> -	    /* Now add in the local typedefs.  */
> -	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
> -
> -	    fprintf_filtered (stream, "{\n");
> -	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
> -		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
> -	      {
> -		if (TYPE_STUB (type))
> -		  fprintfi_filtered (level + 4, stream,
> -				     _("<incomplete type>\n"));
> -		else
> -		  fprintfi_filtered (level + 4, stream,
> -				     _("<no data fields>\n"));
> -	      }
> -
> -	    /* Start off with no specific section type, so we can print
> -	       one for the first field we find, and use that section type
> -	       thereafter until we find another type.  */
> -
> -	    section_type = s_none;
> -
> -	    /* For a class, if all members are private, there's no need
> -	       for a "private:" label; similarly, for a struct or union
> -	       masquerading as a class, if all members are public, there's
> -	       no need for a "public:" label.  */
> -
> -	    if (TYPE_DECLARED_CLASS (type))
> -	      {
> -		QUIT;
> -		len = TYPE_NFIELDS (type);
> -		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> -		  if (!TYPE_FIELD_PRIVATE (type, i))
> -		    {
> -		      need_access_label = 1;
> -		      break;
> -		    }
> -		QUIT;
> -		if (!need_access_label)
> -		  {
> -		    len2 = TYPE_NFN_FIELDS (type);
> -		    for (j = 0; j < len2; j++)
> -		      {
> -			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> -			for (i = 0; i < len; i++)
> -			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> -									  j), i))
> -			    {
> -			      need_access_label = 1;
> -			      break;
> -			    }
> -			if (need_access_label)
> -			  break;
> -		      }
> -		  }
> -		QUIT;
> -		if (!need_access_label)
> -		  {
> -		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> -		      {
> -			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> -			  {
> -			    need_access_label = 1;
> -			    break;
> -			  }
> -		      }
> -		  }
> -	      }
> -	    else
> -	      {
> -		QUIT;
> -		len = TYPE_NFIELDS (type);
> -		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> -		  if (TYPE_FIELD_PRIVATE (type, i)
> -		      || TYPE_FIELD_PROTECTED (type, i))
> -		    {
> -		      need_access_label = 1;
> -		      break;
> -		    }
> -		QUIT;
> -		if (!need_access_label)
> -		  {
> -		    len2 = TYPE_NFN_FIELDS (type);
> -		    for (j = 0; j < len2; j++)
> -		      {
> -			QUIT;
> -			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> -			for (i = 0; i < len; i++)
> -			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
> -									   j), i)
> -			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> -									    j),
> -							i))
> -			    {
> -			      need_access_label = 1;
> -			      break;
> -			    }
> -			if (need_access_label)
> -			  break;
> -		      }
> -		  }
> -		QUIT;
> -		if (!need_access_label)
> -		  {
> -		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> -		      {
> -			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
> -			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> -			  {
> -			    need_access_label = 1;
> -			    break;
> -			  }
> -		      }
> -		  }
> -	      }
> -
> -	    /* If there is a base class for this type,
> -	       do not print the field that it occupies.  */
> -
> -	    len = TYPE_NFIELDS (type);
> -	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
> -	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> -	      {
> -		QUIT;
> -
> -		/* If we have a virtual table pointer, omit it.  Even if
> -		   virtual table pointers are not specifically marked in
> -		   the debug info, they should be artificial.  */
> -		if ((i == vptr_fieldno && type == basetype)
> -		    || TYPE_FIELD_ARTIFICIAL (type, i))
> -		  continue;
> -
> -		if (need_access_label)
> -		  {
> -		    section_type = output_access_specifier
> -		      (stream, section_type, level,
> -		       TYPE_FIELD_PROTECTED (type, i),
> -		       TYPE_FIELD_PRIVATE (type, i));
> -		  }
> -
> -		print_spaces_filtered (level + 4, stream);
> -		if (field_is_static (&TYPE_FIELD (type, i)))
> -		  fprintf_filtered (stream, "static ");
> -		c_print_type (TYPE_FIELD_TYPE (type, i),
> -			      TYPE_FIELD_NAME (type, i),
> -			      stream, show - 1, level + 4,
> -			      &local_flags);
> -		if (!field_is_static (&TYPE_FIELD (type, i))
> -		    && TYPE_FIELD_PACKED (type, i))
> -		  {
> -		    /* It is a bitfield.  This code does not attempt
> -		       to look at the bitpos and reconstruct filler,
> -		       unnamed fields.  This would lead to misleading
> -		       results if the compiler does not put out fields
> -		       for such things (I don't know what it does).  */
> -		    fprintf_filtered (stream, " : %d",
> -				      TYPE_FIELD_BITSIZE (type, i));
> -		  }
> -		fprintf_filtered (stream, ";\n");
> -	      }
> -
> -	  /* If there are both fields and methods, put a blank line
> -	     between them.  Make sure to count only method that we
> -	     will display; artificial methods will be hidden.  */
> -	  len = TYPE_NFN_FIELDS (type);
> -	  if (!flags->print_methods)
> -	    len = 0;
> -	  real_len = 0;
> -	  for (i = 0; i < len; i++)
> -	    {
> -	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
> -	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
> -	      int j;
> -
> -	      for (j = 0; j < len2; j++)
> -		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
> -		  real_len++;
> -	    }
> -	  if (real_len > 0 && section_type != s_none)
> -	    fprintf_filtered (stream, "\n");
> -
> -	  /* C++: print out the methods.  */
> -	  for (i = 0; i < len; i++)
> -	    {
> -	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
> -	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
> -	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
> -	      const char *name = type_name_no_tag (type);
> -	      int is_constructor = name && strcmp (method_name,
> -						   name) == 0;
> -
> -	      for (j = 0; j < len2; j++)
> -		{
> -		  const char *mangled_name;
> -		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
> -		  char *demangled_name;
> -		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
> -		  int is_full_physname_constructor =
> -		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
> -		    || is_constructor_name (physname)
> -		    || is_destructor_name (physname)
> -		    || method_name[0] == '~';
> -
> -		  /* Do not print out artificial methods.  */
> -		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
> -		    continue;
> -
> -		  QUIT;
> -		  section_type = output_access_specifier
> -		    (stream, section_type, level,
> -		     TYPE_FN_FIELD_PROTECTED (f, j),
> -		     TYPE_FN_FIELD_PRIVATE (f, j));
> -
> -		  print_spaces_filtered (level + 4, stream);
> -		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
> -		    fprintf_filtered (stream, "virtual ");
> -		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
> -		    fprintf_filtered (stream, "static ");
> -		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
> -		    {
> -		      /* Keep GDB from crashing here.  */
> -		      fprintf_filtered (stream,
> -					_("<undefined type> %s;\n"),
> -					TYPE_FN_FIELD_PHYSNAME (f, j));
> -		      break;
> -		    }
> -		  else if (!is_constructor	/* Constructors don't
> -						   have declared
> -						   types.  */
> -			   && !is_full_physname_constructor  /* " " */
> -			   && !is_type_conversion_operator (type, i, j))
> -		    {
> -		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
> -				    "", stream, -1, 0,
> -				    &local_flags);
> -		      fputs_filtered (" ", stream);
> -		    }
> -		  if (TYPE_FN_FIELD_STUB (f, j))
> -		    {
> -		      /* Build something we can demangle.  */
> -		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
> -		      mangled_name = mangled_name_holder.get ();
> -		    }
> -		  else
> -		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
> -
> -		  demangled_name =
> -		    gdb_demangle (mangled_name,
> -				  DMGL_ANSI | DMGL_PARAMS);
> -		  if (demangled_name == NULL)
> -		    {
> -		      /* In some cases (for instance with the HP
> -			 demangling), if a function has more than 10
> -			 arguments, the demangling will fail.
> -			 Let's try to reconstruct the function
> -			 signature from the symbol information.  */
> -		      if (!TYPE_FN_FIELD_STUB (f, j))
> -			{
> -			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
> -			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
> -
> -			  cp_type_print_method_args (mtype,
> -						     "",
> -						     method_name,
> -						     staticp,
> -						     stream, &local_flags);
> -			}
> -		      else
> -			fprintf_filtered (stream,
> -					  _("<badly mangled name '%s'>"),
> -					  mangled_name);
> -		    }
> -		  else
> -		    {
> -		      char *p;
> -		      char *demangled_no_class
> -			= remove_qualifiers (demangled_name);
> -
> -		      /* Get rid of the `static' appended by the
> -			 demangler.  */
> -		      p = strstr (demangled_no_class, " static");
> -		      if (p != NULL)
> -			{
> -			  int length = p - demangled_no_class;
> -			  char *demangled_no_static;
> -
> -			  demangled_no_static
> -			    = (char *) xmalloc (length + 1);
> -			  strncpy (demangled_no_static,
> -				   demangled_no_class, length);
> -			  *(demangled_no_static + length) = '\0';
> -			  fputs_filtered (demangled_no_static, stream);
> -			  xfree (demangled_no_static);
> -			}
> -		      else
> -			fputs_filtered (demangled_no_class, stream);
> -		      xfree (demangled_name);
> -		    }
> -
> -		  fprintf_filtered (stream, ";\n");
> -		}
> -	    }
> -
> -	  /* Print typedefs defined in this class.  */
> -
> -	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
> -	    {
> -	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
> -		fprintf_filtered (stream, "\n");
> -
> -	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
> -		{
> -		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
> -
> -		  /* Dereference the typedef declaration itself.  */
> -		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
> -		  target = TYPE_TARGET_TYPE (target);
> -
> -		  if (need_access_label)
> -		    {
> -		      section_type = output_access_specifier
> -			(stream, section_type, level,
> -			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
> -			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
> -		    }
> -		  print_spaces_filtered (level + 4, stream);
> -		  fprintf_filtered (stream, "typedef ");
> -
> -		  /* We want to print typedefs with substitutions
> -		     from the template parameters or globally-known
> -		     typedefs but not local typedefs.  */
> -		  c_print_type (target,
> -				TYPE_TYPEDEF_FIELD_NAME (type, i),
> -				stream, show - 1, level + 4,
> -				&semi_local_flags);
> -		  fprintf_filtered (stream, ";\n");
> -		}
> -	    }
> -
> -	    fprintfi_filtered (level, stream, "}");
> -	  }
> -
> -	do_cleanups (local_cleanups);
> -      }
> +      c_type_print_base_struct_union (type, stream, show, level, flags);

Moving this code to a new function is a good idea, but can you do it in its own
preliminary patch?  It is hard to review changes when code is moved and changed
at the same time.

>        break;
>  
>      case TYPE_CODE_ENUM:
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 675f6e7bc8..f7a45dd5dd 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -17095,6 +17095,10 @@ names are substituted when printing other types.
>  @item T
>  Print typedefs defined in the class.  This is the default, but the flag
>  exists in case you change the default with @command{set print type typedefs}.
> +
> +@item o
> +Print the offsets and sizes of fields in a struct, similar to what the
> +@command{pahole} tool does.
>  @end table
>  
>  @kindex ptype
> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
> new file mode 100644
> index 0000000000..f9a57fd3db
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
> @@ -0,0 +1,113 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
> +
> +#include <stdint.h>
> +
> +/* A struct with many types of fields, in order to test 'ptype
> +   /o'.  */
> +
> +struct abc
> +{
> +  /* Virtual destructor.  */
> +  virtual ~abc ()
> +  {}
> +
> +  /* 8-byte address.  Because of the virtual destructor above, this
> +     field's offset will be 8.  */
> +  void *field1;
> +
> +  /* No hole here.  */
> +
> +  /* 4-byte int bitfield of 1-bit.  */
> +  unsigned int field2 : 1;
> +
> +  /* 31-bit hole here.  */
> +
> +  /* 4-byte int.  */
> +  int field3;
> +
> +  /* No hole here.  */
> +
> +  /* 1-byte char.  */
> +  char field4;
> +
> +  /* 7-byte hole here.  */
> +
> +  /* 8-byte int.  */
> +  uint64_t field5;
> +
> +  /* We just print the offset and size of a union, ignoring its
> +     fields.  */
> +  union
> +  {
> +    /* 8-byte address.  */
> +    void *field6;
> +
> +    /* 4-byte int.  */
> +    int field7;
> +  } field8;
> +
> +  /* Empty constructor.  */
> +  abc ()
> +  {}
> +};
> +
> +/* This struct will be nested inside 'struct xyz'.  */
> +
> +struct tuv
> +{
> +  int a1;
> +
> +  char *a2;
> +
> +  int a3;
> +};
> +
> +/* This struct will be nested inside 'struct pqr'.  */
> +
> +struct xyz
> +{
> +  int f1;
> +
> +  char f2;
> +
> +  void *f3;
> +
> +  struct tuv f4;
> +};
> +
> +/* A struct with a nested struct.  */
> +
> +struct pqr
> +{
> +  int ff1;
> +
> +  struct xyz ff2;
> +
> +  char ff3;
> +};
> +
> +int
> +main (int argc, char *argv[])
> +{
> +  struct abc foo;
> +  struct pqr bar;
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
> new file mode 100644
> index 0000000000..4f84416dc5
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
> @@ -0,0 +1,77 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2017 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/>.
> +
> +standard_testfile .cc ptype-offsets.cc

You can reduce this to just

  standard_testfile ptype-offsets.cc

or

  standard_testfile .cc

It is harmless, but specifying .cc and ptype-offsets.cc will cause you to have two
variables, srcfile and srcfile2, with the value "ptype-offsets.cc", and from what
I see you don't use srcfile2.

> +
> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
> +# that the expected holes will be present in the struct.
> +if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
> +    untested "test work only on x86_64 lp64"

work -> works

> +    return 0
> +}
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
> +	  { debug c++ optimize=-O0 }] } {

optimize=-O0 seems unnecessary to me, I've never seen it specified explicitly in a test.

> +    return -1
> +}
> +
> +# Test general offset printing, ctor/dtor printing, union, formatting.
> +gdb_test "ptype /o struct abc" \
> +    [multi_line \
> +"type = struct abc {" \
> +"/\\\* offset    |  size \\\*/" \
> +"                         public:" \
> +"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
> +"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
> +"/\\\* XXX  7-bit hole   \\\*/" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*   20      |     4 \\\*/    int field3;" \
> +"/\\\*   24      |     1 \\\*/    char field4;" \
> +"/\\\* XXX  7-byte hole  \\\*/" \
> +"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
> +"/\\\*   40      |     8 \\\*/    union {" \
> +"/\\\*                 8 \\\*/        void \\\*field6;" \
> +"/\\\*                 4 \\\*/        int field7;" \
> +"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
> +"" \
> +"                           abc\\(void\\);" \
> +"                           ~abc\\(\\);" \
> +"} /\\\* total size:   48 bytes \\\*/"] \
> +    "ptype offset struct abc"

You could save some backslashes by using {} as string delimiters, if you prefer.
Otherwise, maybe a new proc multi_line_string_to_regexp, which combines multi_line
and string_to_regexp, would be nice.  I mention this as a suggestion for future
improvement, I wouldn't want to delay the merge of this patch just for that.

> +
> +# Test nested structs.
> +gdb_test "ptype /o struct pqr" \
> +    [multi_line \
> +"type = struct pqr {" \
> +"/\\\* offset    |  size \\\*/" \
> +"/\\\*    0      |     4 \\\*/    int f1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*    8      |    16 \\\*/    struct xyz {" \
> +"/\\\*    8      |     4 \\\*/        int f1;" \
> +"/\\\*   12      |     1 \\\*/        char f2;" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
> +"/\\\*   24      |    24 \\\*/        struct tuv {" \
> +"/\\\*   24      |     4 \\\*/            int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   32      |     8 \\\*/            char *a2;" \
> +"/\\\*   40      |     4 \\\*/            int a3;" \
> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
> +"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
> +"/\\\*   48      |     1 \\\*/    char ff3;" \
> +"} /\\\* total size:   56 bytes \\\*/"] \
> +    "ptype offset struct pqr"

You could throw a few more tests in there, for example a union with two structs,
to verify that the offset does go back to 0 when showing the second struct:

  /* offset    |  size */
  type = union my_union {
  /*                 8 */    struct my_struct_1 {
  /*    0      |     4 */        int a;
  /*    4      |     4 */        int b;
                             } /* total size:    8 bytes */ s1;
  /*                 8 */    struct my_struct_2 {
  /*    0      |     4 */        int c;
  /*    4      |     4 */        int d;
                             } /* total size:    8 bytes */ s2;
  } /* total size:    8 bytes */

I also noticed that the offset is not shown in front of the struct-in-union,
as show above, but it is in the case of struct-in-struct:

  /* offset    |  size */
  type = struct my_struct_3 {
  /*    0      |     8 */    struct my_struct_1 {
  /*    0      |     4 */        int a;
  /*    4      |     4 */        int b;
                             } /* total size:    8 bytes */ s1;
  /*    8      |     8 */    struct my_struct_2 {
  /*    8      |     4 */        int c;
  /*   12      |     4 */        int d;
                             } /* total size:    8 bytes */ s2;
  } /* total size:   16 bytes */

Is this difference on purpose?

Finally, a test case on a non-struct/union type, to check that the header is not printed.

> diff --git a/gdb/typeprint.c b/gdb/typeprint.c
> index 427af17ad7..1463e802ad 100644
> --- a/gdb/typeprint.c
> +++ b/gdb/typeprint.c
> @@ -42,6 +42,8 @@ const struct type_print_options type_print_raw_options =
>    1,				/* raw */
>    1,				/* print_methods */
>    1,				/* print_typedefs */
> +  0,				/* print_offsets */
> +  0,				/* offset_bitpos */
>    NULL,				/* local_typedefs */
>    NULL,				/* global_table */
>    NULL				/* global_printers */
> @@ -54,6 +56,8 @@ static struct type_print_options default_ptype_flags =
>    0,				/* raw */
>    1,				/* print_methods */
>    1,				/* print_typedefs */
> +  0,				/* print_offsets */
> +  0,				/* offset_bitpos */
>    NULL,				/* local_typedefs */
>    NULL,				/* global_table */
>    NULL				/* global_printers */
> @@ -438,6 +442,9 @@ whatis_exp (const char *exp, int show)
>  		case 'T':
>  		  flags.print_typedefs = 1;
>  		  break;
> +		case 'o':
> +		  flags.print_offsets = 1;
> +		  break;
>  		default:
>  		  error (_("unrecognized flag '%c'"), *exp);
>  		}
> @@ -497,6 +504,11 @@ whatis_exp (const char *exp, int show)
>  	real_type = value_rtti_type (val, &full, &top, &using_enc);
>      }
>  
> +  if (flags.print_offsets &&
> +      (TYPE_CODE (type) == TYPE_CODE_STRUCT
> +       || TYPE_CODE (type) == TYPE_CODE_UNION))
> +    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
> +
>    printf_filtered ("type = ");
>  
>    if (!flags.raw)
> @@ -722,7 +734,8 @@ Available FLAGS are:\n\
>    /m    do not print methods defined in a class\n\
>    /M    print methods defined in a class\n\
>    /t    do not print typedefs defined in a class\n\
> -  /T    print typedefs defined in a class"));
> +  /T    print typedefs defined in a class\n\
> +  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
>    set_cmd_completer (c, expression_completer);
>  
>    c = add_com ("whatis", class_vars, whatis_command,
> diff --git a/gdb/typeprint.h b/gdb/typeprint.h
> index a458aa4e2f..a2a5285012 100644
> --- a/gdb/typeprint.h
> +++ b/gdb/typeprint.h
> @@ -35,6 +35,15 @@ struct type_print_options
>    /* True means print typedefs in a class.  */
>    unsigned int print_typedefs : 1;
>  
> +  /* True means to print offsets, a la 'pahole'.  */
> +  unsigned int print_offsets : 1;
> +
> +  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
> +     This is needed for when we are printing nested structs and want
> +     to make sure that the printed offset for each field carries off
> +     the offset of the outter struct.  */
> +  unsigned int offset_bitpos;
> +
>    /* If not NULL, a local typedef hash table used when printing a
>       type.  */
>    struct typedef_hash_table *local_typedefs;
> 

Thanks!

Simon

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-11 15:43   ` Simon Marchi
@ 2017-12-11 18:59     ` Sergio Durigan Junior
  2017-12-11 20:45       ` Simon Marchi
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 18:59 UTC (permalink / raw)
  To: Simon Marchi; +Cc: GDB Patches, Tom Tromey, Eli Zaretskii

Thanks for the review, Simon.

Comments below.

On Monday, December 11 2017, Simon Marchi wrote:

> On 2017-11-28 04:21 PM, Sergio Durigan Junior wrote:
>> Changes from v1:
>> 
>> - Address Tom's comments (the command now prints offset information
>>   about unions, and the offset info is carried on for nested structs).
>> 
>> - Address Eli's comments.
>> 
>> - Extended testcase.
>> 
>> - A "bit" of cleanup on 'c_type_print_base'.
>> 
>> 
>> This commit implements the pahole-like '/o' option for 'ptype', which
>> prints the offsets and sizes of struct fields, reporting whenever
>> there is a hole found.
>> 
>> The output is heavily based on pahole(1), with a few modifications
>> here and there to adjust it to our reality.  Here's an example:
>> 
>>   (gdb) ptype /o stap_probe
>>   /* offset    |  size */
>>   struct stap_probe {
>>   /*    0      |    40 */    struct probe {
>>   /*    0      |     8 */        const probe_ops *pops;
>>   /*    8      |     8 */        gdbarch *arch;
>>   /*   16      |     8 */        const char *name;
>>   /*   24      |     8 */        const char *provider;
>>   /*   32      |     8 */        CORE_ADDR address;
>> 			     } /* total size:   40 bytes */ p;
>>   /*   40      |     8 */    CORE_ADDR sem_addr;
>>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>>   /* XXX  7-bit hole   */
>>   /* XXX  7-byte hole  */
>>   /*   56      |     8 */    union {
>>   /*                 8 */        const char *text;
>>   /*                 8 */        VEC_stap_probe_arg_s *vec;
>> 			     } /* total size:    8 bytes */ args_u;
>>   } /* total size:   64 bytes */
>> 
>> A big part of this patch handles the formatting logic of 'ptype',
>> which is a bit messy.  I tried to be not very invasive, but I had to
>> do some cleanups here and there to make life easier.
>> 
>> This patch is the start of a long-term work I'll do to flush the local
>> patches we carry for Fedora GDB.  In this specific case, I'm aiming at
>> upstreaming the feature implemented by the 'pahole.py' script that is
>> shipped with Fedora GDB:
>> 
>>   <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>
>> 
>> This has been regression-tested on the BuildBot.  There's a new
>> testcase for it, along with an update to the documentation.  I also
>> thought it was worth mentioning this feature in the NEWS file.
>
> Seems like you'll need to do a bit of conflict handling with the code from
>
>   Record and output access specifiers for nested typedefs
>   c191a6875b118fce30e7dc4d9e4bd20eff850270
>
> :(

Yeah, I'll take care of that right now.  Sorry for now sending an
updated version.

>> gdb/ChangeLog:
>> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>> 
>> 	PR cli/16224
>> 	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
>> 	* c-typeprint.c (OFFSET_SPC_LEN): New define.
>> 	(print_spaces_filtered_with_print_options): New function.
>> 	(output_access_specifier): Take new argument FLAGS.  Modify
>> 	function to call 'print_spaces_filtered_with_print_options'.
>> 	(c_print_type_union_field_offset): New function.
>> 	(c_print_type_struct_field_offset): New function.
>> 	(need_access_label_p): New function, with contents from
>> 	'c_type_print_base'.
>> 	(c_type_print_base_struct_union): Likewise.
>> 	(c_type_print_base): Print offsets and sizes for struct
>> 	fields.  Struct/union handling code move to functions
>> 	mentioned above.
>> 	* typeprint.c (const struct type_print_options
>> 	type_print_raw_options): Initialize 'print_offsets' and
>> 	'offset_bitpos'.
>> 	(static struct type_print_options default_ptype_flags):
>> 	Likewise.
>> 	(whatis_exp): Handle '/o' option.
>> 	(_initialize_typeprint): Add '/o' flag to ptype's help.
>> 	* typeprint.h (struct type_print_options) <print_offsets>: New
>> 	field.
>> 	<offset_bitpos>: Likewise.
>> 
>> gdb/testsuite/ChangeLog:
>> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>> 
>> 	PR cli/16224
>> 	* gdb.base/ptype-offsets.cc: New file.
>> 	* gdb.base/ptype-offsets.exp: New file.
>> 
>> gdb/doc/ChangeLog:
>> 2017-11-28  Sergio Durigan Junior  <sergiodj@redhat.com>
>> 
>> 	PR cli/16224
>> 	* gdb.texinfo (ptype): Add new flag '/o'.
>> ---
>>  gdb/NEWS                                 |    3 +
>>  gdb/c-typeprint.c                        | 1016 +++++++++++++++++-------------
>>  gdb/doc/gdb.texinfo                      |    4 +
>>  gdb/testsuite/gdb.base/ptype-offsets.cc  |  113 ++++
>>  gdb/testsuite/gdb.base/ptype-offsets.exp |   77 +++
>>  gdb/typeprint.c                          |   15 +-
>>  gdb/typeprint.h                          |    9 +
>>  7 files changed, 812 insertions(+), 425 deletions(-)
>>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
>>  create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp
>> 
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index 754ce103bd..1247021046 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,9 @@
>>  
>>  *** Changes since GDB 8.0
>>  
>> +* The 'ptype' command now accepts a '/o' flag, which prints the
>> +  offsets and sizes of fields in a struct, like the pahole(1) tool.
>> +
>>  * GDB now uses the GNU MPFR library, if available, to emulate target
>>    floating-point arithmetic during expression evaluation when the target
>>    uses different floating-point formats than the host.  At least version
>> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
>> index ed5a1a4b8a..39334ccf88 100644
>> --- a/gdb/c-typeprint.c
>> +++ b/gdb/c-typeprint.c
>> @@ -32,6 +32,14 @@
>>  #include "cp-abi.h"
>>  #include "cp-support.h"
>>  
>> +/* When printing the offsets of a struct and its fields (i.e., 'ptype
>> +   /o'; type_print_options::print_offsets), we use this many
>> +   characters when printing the offset information at the beginning of
>> +   the line.  This is needed in order to generate the correct amount
>> +   of whitespaces when no offset info should be printed for a certain
>> +   field.  */
>> +#define OFFSET_SPC_LEN 23
>> +
>>  /* A list of access specifiers used for printing.  */
>>  
>>  enum access_specifier
>> @@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
>>      fputs_filtered (_("] "), stream);
>>  }
>>  
>> +/* Use 'print_spaces_filtered', but take into consideration the
>> +   type_print_options FLAGS in order to determine how many whitespaces
>> +   will be printed.  */
>> +
>> +static void
>> +print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
>> +					const struct type_print_options *flags)
>
> Missing spaces here.

This is actually on purpose.  If I indent the line, it will have more
than 80 chars.  I believe this is a well known method for avoiding this
problem...?

>> +{
>> +  if (!flags->print_offsets)
>> +    print_spaces_filtered (level, stream);
>> +  else
>> +    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
>> +}
>> +
>>  /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
>>     last access specifier output (typically returned by this function).  */
>>  
>>  static enum access_specifier
>>  output_access_specifier (struct ui_file *stream,
>>  			 enum access_specifier last_access,
>> -			 int level, bool is_protected, bool is_private)
>> +			 int level, bool is_protected, bool is_private,
>> +			 const struct type_print_options *flags)
>>  {
>>    if (is_protected)
>>      {
>>        if (last_access != s_protected)
>>  	{
>>  	  last_access = s_protected;
>> -	  fprintfi_filtered (level + 2, stream,
>> -			     "protected:\n");
>> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
>> +	  fprintf_filtered (stream, "protected:\n");
>>  	}
>>      }
>>    else if (is_private)
>> @@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
>>        if (last_access != s_private)
>>  	{
>>  	  last_access = s_private;
>> -	  fprintfi_filtered (level + 2, stream,
>> -			     "private:\n");
>> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
>> +	  fprintf_filtered (stream, "private:\n");
>>  	}
>>      }
>>    else
>> @@ -867,14 +890,569 @@ output_access_specifier (struct ui_file *stream,
>>        if (last_access != s_public)
>>  	{
>>  	  last_access = s_public;
>> -	  fprintfi_filtered (level + 2, stream,
>> -			     "public:\n");
>> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
>> +	  fprintf_filtered (stream, "public:\n");
>>  	}
>>      }
>>  
>>    return last_access;
>>  }
>>  
>> +/* Print information about the offset of TYPE inside its union.
>> +   FIELD_IDX represents the index of this TYPE inside the union.  We
>> +   just print the type size, and nothing more.
>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
>> +				 struct ui_file *stream)
>> +{
>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
>> +
>> +  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
>> +}
>> +
>> +/* Print information about the offset of TYPE inside its struct.
>> +   FIELD_IDX represents the index of this TYPE inside the struct, and
>> +   ENDPOS is the end position of the previous type (this is how we
>> +   calculate whether there are holes in the struct).  At the end,
>> +   ENDPOS is updated.
>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
>> +				  unsigned int *endpos, struct ui_file *stream,
>> +				  unsigned int offset_bitpos)
>> +{
>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
>> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
>> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
>> +  unsigned int fieldsize_bit;
>> +
>> +  if (*endpos > 0 && *endpos < bitpos)
>> +    {
>> +      /* If ENDPOS is smaller than the current type's bitpos, it means
>> +	 there's a hole in the struct, so we report it here.  */
>> +      unsigned int hole = bitpos - *endpos;
>> +      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
>> +      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
>> +
>> +      if (hole_bit > 0)
>> +	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
>> +
>> +      if (hole_byte > 0)
>> +	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
>> +    }
>> +
>> +  /* The position of the field, relative to the beginning of the
>> +     struct.  Assume this number will have 4 digits.  */
>> +  fprintf_filtered (stream, "/* %4u",
>> +		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT);
>> +
>> +  if (TYPE_FIELD_PACKED (type, field_idx))
>> +    {
>> +      /* We're dealing with a bitfield.  Print how many bits are left
>> +	 to be used.  */
>> +      fieldsize_bit = TYPE_FIELD_BITSIZE (type, field_idx);
>> +      fprintf_filtered (stream, ":%u",
>> +			fieldsize_byte * TARGET_CHAR_BIT - fieldsize_bit);
>> +    }
>> +  else
>> +    {
>> +      fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
>> +      fprintf_filtered (stream, "   ");
>> +    }
>> +
>> +  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
>> +
>> +  *endpos = bitpos + fieldsize_bit;
>> +}
>> +
>> +/* Return true is an access label (i.e., "public:", "private:",
>> +   "protected:") needs to be printed for TYPE.  */
>> +
>> +static bool
>> +need_access_label_p (struct type *type)
>> +{
>> +  bool need_access_label = false;
>> +  int i, j;
>> +  int len, len2;
>> +
>> +  if (TYPE_DECLARED_CLASS (type))
>> +    {
>> +      QUIT;
>> +      len = TYPE_NFIELDS (type);
>> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> +	if (!TYPE_FIELD_PRIVATE (type, i))
>> +	  {
>> +	    need_access_label = true;
>> +	    break;
>> +	  }
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  len2 = TYPE_NFN_FIELDS (type);
>> +	  for (j = 0; j < len2; j++)
>> +	    {
>> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> +	      for (i = 0; i < len; i++)
>> +		if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> +								j), i))
>> +		  {
>> +		    need_access_label = true;
>> +		    break;
>> +		  }
>> +	      if (need_access_label)
>> +		break;
>> +	    }
>> +	}
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> +	    {
>> +	      if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> +		{
>> +		  need_access_label = true;
>> +		  break;
>> +		}
>> +	    }
>> +	}
>> +    }
>> +  else
>> +    {
>> +      QUIT;
>> +      len = TYPE_NFIELDS (type);
>> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> +	if (TYPE_FIELD_PRIVATE (type, i)
>> +	    || TYPE_FIELD_PROTECTED (type, i))
>> +	  {
>> +	    need_access_label = true;
>> +	    break;
>> +	  }
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  len2 = TYPE_NFN_FIELDS (type);
>> +	  for (j = 0; j < len2; j++)
>> +	    {
>> +	      QUIT;
>> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> +	      for (i = 0; i < len; i++)
>> +		if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
>> +								 j), i)
>> +		    || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> +								  j),
>> +					      i))
>> +		  {
>> +		    need_access_label = true;
>> +		    break;
>> +		  }
>> +	      if (need_access_label)
>> +		break;
>> +	    }
>> +	}
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> +	    {
>> +	      if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
>> +		  || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> +		{
>> +		  need_access_label = true;
>> +		  break;
>> +		}
>> +	    }
>> +	}
>> +    }
>> +  return need_access_label;
>> +}
>> +
>> +/* Helper for 'c_type_print_base' that handles structs and unions.
>> +   For a description of the arguments, see 'c_type_print_base'.  */
>> +
>> +static void
>> +c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
>> +				int show, int level,
>> +				const struct type_print_options *flags)
>> +{
>> +  struct type_print_options local_flags = *flags;
>> +  struct type_print_options semi_local_flags = *flags;
>> +  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
>> +
>> +  local_flags.local_typedefs = NULL;
>> +  semi_local_flags.local_typedefs = NULL;
>> +
>> +  if (!flags->raw)
>> +    {
>> +      if (flags->local_typedefs)
>> +	local_flags.local_typedefs
>> +	  = copy_typedef_hash (flags->local_typedefs);
>> +      else
>> +	local_flags.local_typedefs = create_typedef_hash ();
>> +
>> +      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
>> +    }
>> +
>> +  c_type_print_modifier (type, stream, 0, 1);
>> +  if (TYPE_CODE (type) == TYPE_CODE_UNION)
>> +    fprintf_filtered (stream, "union ");
>> +  else if (TYPE_DECLARED_CLASS (type))
>> +    fprintf_filtered (stream, "class ");
>> +  else
>> +    fprintf_filtered (stream, "struct ");
>> +
>> +  /* Print the tag if it exists.  The HP aCC compiler emits a
>> +     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
>> +     enum}" tag for unnamed struct/union/enum's, which we don't
>> +     want to print.  */
>> +  if (TYPE_TAG_NAME (type) != NULL
>> +      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
>> +    {
>> +      /* When printing the tag name, we are still effectively
>> +	 printing in the outer context, hence the use of FLAGS
>> +	 here.  */
>> +      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
>> +      if (show > 0)
>> +	fputs_filtered (" ", stream);
>> +    }
>> +
>> +  if (show < 0)
>> +    {
>> +      /* If we just printed a tag name, no need to print anything
>> +	 else.  */
>> +      if (TYPE_TAG_NAME (type) == NULL)
>> +	fprintf_filtered (stream, "{...}");
>> +    }
>> +  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
>> +    {
>> +      struct type *basetype;
>> +
>> +      c_type_print_template_args (&local_flags, type, stream);
>> +
>> +      /* Add in template parameters when printing derivation info.  */
>> +      add_template_parameters (local_flags.local_typedefs, type);
>> +      cp_type_print_derivation_info (stream, type, &local_flags);
>> +
>> +      /* This holds just the global typedefs and the template
>> +	 parameters.  */
>> +      semi_local_flags.local_typedefs
>> +	= copy_typedef_hash (local_flags.local_typedefs);
>> +      if (semi_local_flags.local_typedefs)
>> +	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
>> +
>> +      /* Now add in the local typedefs.  */
>> +      recursively_update_typedef_hash (local_flags.local_typedefs, type);
>> +
>> +      fprintf_filtered (stream, "{\n");
>> +      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
>> +	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
>> +	{
>> +	  if (TYPE_STUB (type))
>> +	    fprintfi_filtered (level + 4, stream,
>> +			       _("<incomplete type>\n"));
>> +	  else
>> +	    fprintfi_filtered (level + 4, stream,
>> +			       _("<no data fields>\n"));
>> +	}
>> +
>> +      /* Start off with no specific section type, so we can print
>> +	 one for the first field we find, and use that section type
>> +	 thereafter until we find another type.  */
>> +      enum access_specifier section_type = s_none;
>> +
>> +      /* For a class, if all members are private, there's no need
>> +	 for a "private:" label; similarly, for a struct or union
>> +	 masquerading as a class, if all members are public, there's
>> +	 no need for a "public:" label.  */
>> +      bool need_access_label = need_access_label_p (type);
>> +
>> +      /* If there is a base class for this type,
>> +	 do not print the field that it occupies.  */
>> +
>> +      int len = TYPE_NFIELDS (type);
>> +      int vptr_fieldno = get_vptr_fieldno (type, &basetype);
>> +      unsigned int endpos = 0;
>> +
>> +      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
>> +	{
>> +	  QUIT;
>> +
>> +	  /* If we have a virtual table pointer, omit it.  Even if
>> +	     virtual table pointers are not specifically marked in
>> +	     the debug info, they should be artificial.  */
>> +	  if ((i == vptr_fieldno && type == basetype)
>> +	      || TYPE_FIELD_ARTIFICIAL (type, i))
>> +	    continue;
>> +
>> +	  if (need_access_label)
>> +	    {
>> +	      section_type = output_access_specifier
>> +		(stream, section_type, level,
>> +		 TYPE_FIELD_PROTECTED (type, i),
>> +		 TYPE_FIELD_PRIVATE (type, i),
>> +		 flags);
>> +	    }
>> +
>> +	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
>> +
>> +	  if (flags->print_offsets)
>> +	    {
>> +	      if (!is_static)
>> +		{
>> +		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
>> +		    c_print_type_struct_field_offset (type, i, &endpos, stream,
>> +						      flags->offset_bitpos);
>> +		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
>> +		    c_print_type_union_field_offset (type, i, stream);
>> +		}
>> +	      else
>> +		print_spaces_filtered (OFFSET_SPC_LEN, stream);
>> +	    }
>> +
>> +	  print_spaces_filtered (level + 4, stream);
>> +	  if (is_static)
>> +	    fprintf_filtered (stream, "static ");
>> +
>> +	  int newshow = show - 1;
>> +
>> +	  if (flags->print_offsets
>> +	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
>> +		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
>> +	    {
>> +	      /* If we're printing offsets and this field's type is
>> +		 either a struct or an union, then we're interested in
>> +		 expanding it.  */
>> +	      ++newshow;
>> +
>> +	      /* Make sure we carry our offset when we expand the
>> +		 struct.  */
>> +	      local_flags.offset_bitpos
>> +		= flags->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
>> +	    }
>> +
>> +	  c_print_type (TYPE_FIELD_TYPE (type, i),
>> +			TYPE_FIELD_NAME (type, i),
>> +			stream, newshow, level + 4,
>> +			&local_flags);
>> +	  if (!is_static
>> +	      && TYPE_FIELD_PACKED (type, i))
>> +	    {
>> +	      /* It is a bitfield.  This code does not attempt
>> +		 to look at the bitpos and reconstruct filler,
>> +		 unnamed fields.  This would lead to misleading
>> +		 results if the compiler does not put out fields
>> +		 for such things (I don't know what it does).  */
>> +	      fprintf_filtered (stream, " : %d",
>> +				TYPE_FIELD_BITSIZE (type, i));
>> +	    }
>> +	  fprintf_filtered (stream, ";\n");
>> +	}
>> +
>> +      /* If there are both fields and methods, put a blank line
>> +	 between them.  Make sure to count only method that we
>> +	 will display; artificial methods will be hidden.  */
>> +      len = TYPE_NFN_FIELDS (type);
>> +      if (!flags->print_methods)
>> +	len = 0;
>> +      int real_len = 0;
>> +      for (int i = 0; i < len; i++)
>> +	{
>> +	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
>> +	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
>> +	  int j;
>> +
>> +	  for (j = 0; j < len2; j++)
>> +	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
>> +	      real_len++;
>> +	}
>> +      if (real_len > 0 && section_type != s_none)
>> +	fprintf_filtered (stream, "\n");
>> +
>> +      /* C++: print out the methods.  */
>> +      for (int i = 0; i < len; i++)
>> +	{
>> +	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
>> +	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
>> +	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
>> +	  const char *name = type_name_no_tag (type);
>> +	  int is_constructor = name && strcmp (method_name,
>> +					       name) == 0;
>> +
>> +	  for (j = 0; j < len2; j++)
>> +	    {
>> +	      const char *mangled_name;
>> +	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
>> +	      char *demangled_name;
>> +	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
>> +	      int is_full_physname_constructor =
>> +		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
>> +		|| is_constructor_name (physname)
>> +		|| is_destructor_name (physname)
>> +		|| method_name[0] == '~';
>> +
>> +	      /* Do not print out artificial methods.  */
>> +	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
>> +		continue;
>> +
>> +	      QUIT;
>> +	      section_type = output_access_specifier
>> +		(stream, section_type, level,
>> +		 TYPE_FN_FIELD_PROTECTED (f, j),
>> +		 TYPE_FN_FIELD_PRIVATE (f, j),
>> +		 flags);
>> +
>> +	      print_spaces_filtered_with_print_options (level + 4, stream,
>> +							flags);
>> +	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
>> +		fprintf_filtered (stream, "virtual ");
>> +	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
>> +		fprintf_filtered (stream, "static ");
>> +	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
>> +		{
>> +		  /* Keep GDB from crashing here.  */
>> +		  fprintf_filtered (stream,
>> +				    _("<undefined type> %s;\n"),
>> +				    TYPE_FN_FIELD_PHYSNAME (f, j));
>> +		  break;
>> +		}
>> +	      else if (!is_constructor	/* Constructors don't
>> +					   have declared
>> +					   types.  */
>> +		       && !is_full_physname_constructor  /* " " */
>> +		       && !is_type_conversion_operator (type, i, j))
>> +		{
>> +		  unsigned int old_po = local_flags.print_offsets;
>> +
>> +		  /* Temporarily disable print_offsets, because it
>> +		     would mess with indentation.  */
>> +		  local_flags.print_offsets = 0;
>> +		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
>> +				"", stream, -1, 0,
>> +				&local_flags);
>> +		  local_flags.print_offsets = old_po;
>> +		  fputs_filtered (" ", stream);
>> +		}
>> +	      if (TYPE_FN_FIELD_STUB (f, j))
>> +		{
>> +		  /* Build something we can demangle.  */
>> +		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
>> +		  mangled_name = mangled_name_holder.get ();
>> +		}
>> +	      else
>> +		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
>> +
>> +	      demangled_name =
>> +		gdb_demangle (mangled_name,
>> +			      DMGL_ANSI | DMGL_PARAMS);
>> +	      if (demangled_name == NULL)
>> +		{
>> +		  /* In some cases (for instance with the HP
>> +		     demangling), if a function has more than 10
>> +		     arguments, the demangling will fail.
>> +		     Let's try to reconstruct the function
>> +		     signature from the symbol information.  */
>> +		  if (!TYPE_FN_FIELD_STUB (f, j))
>> +		    {
>> +		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
>> +		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
>> +
>> +		      cp_type_print_method_args (mtype,
>> +						 "",
>> +						 method_name,
>> +						 staticp,
>> +						 stream, &local_flags);
>> +		    }
>> +		  else
>> +		    fprintf_filtered (stream,
>> +				      _("<badly mangled name '%s'>"),
>> +				      mangled_name);
>> +		}
>> +	      else
>> +		{
>> +		  char *p;
>> +		  char *demangled_no_class
>> +		    = remove_qualifiers (demangled_name);
>> +
>> +		  /* Get rid of the `static' appended by the
>> +		     demangler.  */
>> +		  p = strstr (demangled_no_class, " static");
>> +		  if (p != NULL)
>> +		    {
>> +		      int length = p - demangled_no_class;
>> +		      char *demangled_no_static;
>> +
>> +		      demangled_no_static
>> +			= (char *) xmalloc (length + 1);
>> +		      strncpy (demangled_no_static,
>> +			       demangled_no_class, length);
>> +		      *(demangled_no_static + length) = '\0';
>> +		      fputs_filtered (demangled_no_static, stream);
>> +		      xfree (demangled_no_static);
>> +		    }
>> +		  else
>> +		    fputs_filtered (demangled_no_class, stream);
>> +		  xfree (demangled_name);
>> +		}
>> +
>> +	      fprintf_filtered (stream, ";\n");
>> +	    }
>> +	}
>> +
>> +      /* Print typedefs defined in this class.  */
>> +
>> +      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
>> +	{
>> +	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
>> +	    fprintf_filtered (stream, "\n");
>> +
>> +	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
>> +	    {
>> +	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
>> +
>> +	      /* Dereference the typedef declaration itself.  */
>> +	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
>> +	      target = TYPE_TARGET_TYPE (target);
>> +
>> +	      if (need_access_label)
>> +		{
>> +		  section_type = output_access_specifier
>> +		    (stream, section_type, level,
>> +		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
>> +		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i),
>> +		     flags);
>> +		}
>> +	      print_spaces_filtered_with_print_options (level + 4,
>> +							stream, flags);
>> +	      fprintf_filtered (stream, "typedef ");
>> +
>> +	      /* We want to print typedefs with substitutions
>> +		 from the template parameters or globally-known
>> +		 typedefs but not local typedefs.  */
>> +	      c_print_type (target,
>> +			    TYPE_TYPEDEF_FIELD_NAME (type, i),
>> +			    stream, show - 1, level + 4,
>> +			    &semi_local_flags);
>> +	      fprintf_filtered (stream, ";\n");
>> +	    }
>> +	}
>> +
>> +      if (flags->print_offsets && level > 0)
>> +	print_spaces_filtered (OFFSET_SPC_LEN, stream);
>> +
>> +      fprintfi_filtered (level, stream, "}");
>> +    }
>> +
>> +  if (show > 0 && flags->print_offsets)
>> +    fprintf_filtered (stream, " /* total size: %4u bytes */",
>> +		      TYPE_LENGTH (type));
>> +
>> +  do_cleanups (local_cleanups);
>> +}
>> +
>>  /* Print the name of the type (or the ultimate pointer target,
>>     function value or array element), or the description of a structure
>>     or union.
>> @@ -898,10 +1476,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>>  		   int show, int level, const struct type_print_options *flags)
>>  {
>>    int i;
>> -  int len, real_len;
>> -  enum access_specifier section_type;
>> -  int need_access_label = 0;
>> -  int j, len2;
>> +  int len;
>> +  int j;
>>  
>>    QUIT;
>>  
>> @@ -918,15 +1494,16 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>>       folk tend to expect things like "class5 *foo" rather than "struct
>>       class5 *foo".  */
>>  
>> -  if (show <= 0
>> -      && TYPE_NAME (type) != NULL)
>> +  struct type *ttype = check_typedef (type);
>
> Is there a reason for introducing this new variable and doing the check_typedef before the if?

This is a leftover from a previous version.  The patch now unwraps
typedef's inside the c_print_type_union_field_offset and
c_print_type_struct_field_offset functions.

Thanks for the catch, I removed it.

>> +
>> +  if (show <= 0 && TYPE_NAME (type) != NULL)
>>      {
>>        c_type_print_modifier (type, stream, 0, 1);
>>        print_name_maybe_canonical (TYPE_NAME (type), flags, stream);
>>        return;
>>      }
>>  
>> -  type = check_typedef (type);
>> +  type = ttype;
>>  
>>    switch (TYPE_CODE (type))
>>      {
>> @@ -958,416 +1535,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>>  
>>      case TYPE_CODE_STRUCT:
>>      case TYPE_CODE_UNION:
>> -      {
>> -	struct type_print_options local_flags = *flags;
>> -	struct type_print_options semi_local_flags = *flags;
>> -	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
>> -
>> -	local_flags.local_typedefs = NULL;
>> -	semi_local_flags.local_typedefs = NULL;
>> -
>> -	if (!flags->raw)
>> -	  {
>> -	    if (flags->local_typedefs)
>> -	      local_flags.local_typedefs
>> -		= copy_typedef_hash (flags->local_typedefs);
>> -	    else
>> -	      local_flags.local_typedefs = create_typedef_hash ();
>> -
>> -	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
>> -	  }
>> -
>> -	c_type_print_modifier (type, stream, 0, 1);
>> -	if (TYPE_CODE (type) == TYPE_CODE_UNION)
>> -	  fprintf_filtered (stream, "union ");
>> -	else if (TYPE_DECLARED_CLASS (type))
>> -	  fprintf_filtered (stream, "class ");
>> -	else
>> -	  fprintf_filtered (stream, "struct ");
>> -
>> -	/* Print the tag if it exists.  The HP aCC compiler emits a
>> -	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
>> -	   enum}" tag for unnamed struct/union/enum's, which we don't
>> -	   want to print.  */
>> -	if (TYPE_TAG_NAME (type) != NULL
>> -	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
>> -	  {
>> -	    /* When printing the tag name, we are still effectively
>> -	       printing in the outer context, hence the use of FLAGS
>> -	       here.  */
>> -	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
>> -	    if (show > 0)
>> -	      fputs_filtered (" ", stream);
>> -	  }
>> -
>> -	if (show < 0)
>> -	  {
>> -	    /* If we just printed a tag name, no need to print anything
>> -	       else.  */
>> -	    if (TYPE_TAG_NAME (type) == NULL)
>> -	      fprintf_filtered (stream, "{...}");
>> -	  }
>> -	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
>> -	  {
>> -	    struct type *basetype;
>> -	    int vptr_fieldno;
>> -
>> -	    c_type_print_template_args (&local_flags, type, stream);
>> -
>> -	    /* Add in template parameters when printing derivation info.  */
>> -	    add_template_parameters (local_flags.local_typedefs, type);
>> -	    cp_type_print_derivation_info (stream, type, &local_flags);
>> -
>> -	    /* This holds just the global typedefs and the template
>> -	       parameters.  */
>> -	    semi_local_flags.local_typedefs
>> -	      = copy_typedef_hash (local_flags.local_typedefs);
>> -	    if (semi_local_flags.local_typedefs)
>> -	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
>> -
>> -	    /* Now add in the local typedefs.  */
>> -	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
>> -
>> -	    fprintf_filtered (stream, "{\n");
>> -	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
>> -		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
>> -	      {
>> -		if (TYPE_STUB (type))
>> -		  fprintfi_filtered (level + 4, stream,
>> -				     _("<incomplete type>\n"));
>> -		else
>> -		  fprintfi_filtered (level + 4, stream,
>> -				     _("<no data fields>\n"));
>> -	      }
>> -
>> -	    /* Start off with no specific section type, so we can print
>> -	       one for the first field we find, and use that section type
>> -	       thereafter until we find another type.  */
>> -
>> -	    section_type = s_none;
>> -
>> -	    /* For a class, if all members are private, there's no need
>> -	       for a "private:" label; similarly, for a struct or union
>> -	       masquerading as a class, if all members are public, there's
>> -	       no need for a "public:" label.  */
>> -
>> -	    if (TYPE_DECLARED_CLASS (type))
>> -	      {
>> -		QUIT;
>> -		len = TYPE_NFIELDS (type);
>> -		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> -		  if (!TYPE_FIELD_PRIVATE (type, i))
>> -		    {
>> -		      need_access_label = 1;
>> -		      break;
>> -		    }
>> -		QUIT;
>> -		if (!need_access_label)
>> -		  {
>> -		    len2 = TYPE_NFN_FIELDS (type);
>> -		    for (j = 0; j < len2; j++)
>> -		      {
>> -			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> -			for (i = 0; i < len; i++)
>> -			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> -									  j), i))
>> -			    {
>> -			      need_access_label = 1;
>> -			      break;
>> -			    }
>> -			if (need_access_label)
>> -			  break;
>> -		      }
>> -		  }
>> -		QUIT;
>> -		if (!need_access_label)
>> -		  {
>> -		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> -		      {
>> -			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> -			  {
>> -			    need_access_label = 1;
>> -			    break;
>> -			  }
>> -		      }
>> -		  }
>> -	      }
>> -	    else
>> -	      {
>> -		QUIT;
>> -		len = TYPE_NFIELDS (type);
>> -		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> -		  if (TYPE_FIELD_PRIVATE (type, i)
>> -		      || TYPE_FIELD_PROTECTED (type, i))
>> -		    {
>> -		      need_access_label = 1;
>> -		      break;
>> -		    }
>> -		QUIT;
>> -		if (!need_access_label)
>> -		  {
>> -		    len2 = TYPE_NFN_FIELDS (type);
>> -		    for (j = 0; j < len2; j++)
>> -		      {
>> -			QUIT;
>> -			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> -			for (i = 0; i < len; i++)
>> -			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
>> -									   j), i)
>> -			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> -									    j),
>> -							i))
>> -			    {
>> -			      need_access_label = 1;
>> -			      break;
>> -			    }
>> -			if (need_access_label)
>> -			  break;
>> -		      }
>> -		  }
>> -		QUIT;
>> -		if (!need_access_label)
>> -		  {
>> -		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> -		      {
>> -			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
>> -			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> -			  {
>> -			    need_access_label = 1;
>> -			    break;
>> -			  }
>> -		      }
>> -		  }
>> -	      }
>> -
>> -	    /* If there is a base class for this type,
>> -	       do not print the field that it occupies.  */
>> -
>> -	    len = TYPE_NFIELDS (type);
>> -	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
>> -	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> -	      {
>> -		QUIT;
>> -
>> -		/* If we have a virtual table pointer, omit it.  Even if
>> -		   virtual table pointers are not specifically marked in
>> -		   the debug info, they should be artificial.  */
>> -		if ((i == vptr_fieldno && type == basetype)
>> -		    || TYPE_FIELD_ARTIFICIAL (type, i))
>> -		  continue;
>> -
>> -		if (need_access_label)
>> -		  {
>> -		    section_type = output_access_specifier
>> -		      (stream, section_type, level,
>> -		       TYPE_FIELD_PROTECTED (type, i),
>> -		       TYPE_FIELD_PRIVATE (type, i));
>> -		  }
>> -
>> -		print_spaces_filtered (level + 4, stream);
>> -		if (field_is_static (&TYPE_FIELD (type, i)))
>> -		  fprintf_filtered (stream, "static ");
>> -		c_print_type (TYPE_FIELD_TYPE (type, i),
>> -			      TYPE_FIELD_NAME (type, i),
>> -			      stream, show - 1, level + 4,
>> -			      &local_flags);
>> -		if (!field_is_static (&TYPE_FIELD (type, i))
>> -		    && TYPE_FIELD_PACKED (type, i))
>> -		  {
>> -		    /* It is a bitfield.  This code does not attempt
>> -		       to look at the bitpos and reconstruct filler,
>> -		       unnamed fields.  This would lead to misleading
>> -		       results if the compiler does not put out fields
>> -		       for such things (I don't know what it does).  */
>> -		    fprintf_filtered (stream, " : %d",
>> -				      TYPE_FIELD_BITSIZE (type, i));
>> -		  }
>> -		fprintf_filtered (stream, ";\n");
>> -	      }
>> -
>> -	  /* If there are both fields and methods, put a blank line
>> -	     between them.  Make sure to count only method that we
>> -	     will display; artificial methods will be hidden.  */
>> -	  len = TYPE_NFN_FIELDS (type);
>> -	  if (!flags->print_methods)
>> -	    len = 0;
>> -	  real_len = 0;
>> -	  for (i = 0; i < len; i++)
>> -	    {
>> -	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
>> -	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
>> -	      int j;
>> -
>> -	      for (j = 0; j < len2; j++)
>> -		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
>> -		  real_len++;
>> -	    }
>> -	  if (real_len > 0 && section_type != s_none)
>> -	    fprintf_filtered (stream, "\n");
>> -
>> -	  /* C++: print out the methods.  */
>> -	  for (i = 0; i < len; i++)
>> -	    {
>> -	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
>> -	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
>> -	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
>> -	      const char *name = type_name_no_tag (type);
>> -	      int is_constructor = name && strcmp (method_name,
>> -						   name) == 0;
>> -
>> -	      for (j = 0; j < len2; j++)
>> -		{
>> -		  const char *mangled_name;
>> -		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
>> -		  char *demangled_name;
>> -		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
>> -		  int is_full_physname_constructor =
>> -		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
>> -		    || is_constructor_name (physname)
>> -		    || is_destructor_name (physname)
>> -		    || method_name[0] == '~';
>> -
>> -		  /* Do not print out artificial methods.  */
>> -		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
>> -		    continue;
>> -
>> -		  QUIT;
>> -		  section_type = output_access_specifier
>> -		    (stream, section_type, level,
>> -		     TYPE_FN_FIELD_PROTECTED (f, j),
>> -		     TYPE_FN_FIELD_PRIVATE (f, j));
>> -
>> -		  print_spaces_filtered (level + 4, stream);
>> -		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
>> -		    fprintf_filtered (stream, "virtual ");
>> -		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
>> -		    fprintf_filtered (stream, "static ");
>> -		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
>> -		    {
>> -		      /* Keep GDB from crashing here.  */
>> -		      fprintf_filtered (stream,
>> -					_("<undefined type> %s;\n"),
>> -					TYPE_FN_FIELD_PHYSNAME (f, j));
>> -		      break;
>> -		    }
>> -		  else if (!is_constructor	/* Constructors don't
>> -						   have declared
>> -						   types.  */
>> -			   && !is_full_physname_constructor  /* " " */
>> -			   && !is_type_conversion_operator (type, i, j))
>> -		    {
>> -		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
>> -				    "", stream, -1, 0,
>> -				    &local_flags);
>> -		      fputs_filtered (" ", stream);
>> -		    }
>> -		  if (TYPE_FN_FIELD_STUB (f, j))
>> -		    {
>> -		      /* Build something we can demangle.  */
>> -		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
>> -		      mangled_name = mangled_name_holder.get ();
>> -		    }
>> -		  else
>> -		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
>> -
>> -		  demangled_name =
>> -		    gdb_demangle (mangled_name,
>> -				  DMGL_ANSI | DMGL_PARAMS);
>> -		  if (demangled_name == NULL)
>> -		    {
>> -		      /* In some cases (for instance with the HP
>> -			 demangling), if a function has more than 10
>> -			 arguments, the demangling will fail.
>> -			 Let's try to reconstruct the function
>> -			 signature from the symbol information.  */
>> -		      if (!TYPE_FN_FIELD_STUB (f, j))
>> -			{
>> -			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
>> -			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
>> -
>> -			  cp_type_print_method_args (mtype,
>> -						     "",
>> -						     method_name,
>> -						     staticp,
>> -						     stream, &local_flags);
>> -			}
>> -		      else
>> -			fprintf_filtered (stream,
>> -					  _("<badly mangled name '%s'>"),
>> -					  mangled_name);
>> -		    }
>> -		  else
>> -		    {
>> -		      char *p;
>> -		      char *demangled_no_class
>> -			= remove_qualifiers (demangled_name);
>> -
>> -		      /* Get rid of the `static' appended by the
>> -			 demangler.  */
>> -		      p = strstr (demangled_no_class, " static");
>> -		      if (p != NULL)
>> -			{
>> -			  int length = p - demangled_no_class;
>> -			  char *demangled_no_static;
>> -
>> -			  demangled_no_static
>> -			    = (char *) xmalloc (length + 1);
>> -			  strncpy (demangled_no_static,
>> -				   demangled_no_class, length);
>> -			  *(demangled_no_static + length) = '\0';
>> -			  fputs_filtered (demangled_no_static, stream);
>> -			  xfree (demangled_no_static);
>> -			}
>> -		      else
>> -			fputs_filtered (demangled_no_class, stream);
>> -		      xfree (demangled_name);
>> -		    }
>> -
>> -		  fprintf_filtered (stream, ";\n");
>> -		}
>> -	    }
>> -
>> -	  /* Print typedefs defined in this class.  */
>> -
>> -	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
>> -	    {
>> -	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
>> -		fprintf_filtered (stream, "\n");
>> -
>> -	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
>> -		{
>> -		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
>> -
>> -		  /* Dereference the typedef declaration itself.  */
>> -		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
>> -		  target = TYPE_TARGET_TYPE (target);
>> -
>> -		  if (need_access_label)
>> -		    {
>> -		      section_type = output_access_specifier
>> -			(stream, section_type, level,
>> -			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
>> -			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
>> -		    }
>> -		  print_spaces_filtered (level + 4, stream);
>> -		  fprintf_filtered (stream, "typedef ");
>> -
>> -		  /* We want to print typedefs with substitutions
>> -		     from the template parameters or globally-known
>> -		     typedefs but not local typedefs.  */
>> -		  c_print_type (target,
>> -				TYPE_TYPEDEF_FIELD_NAME (type, i),
>> -				stream, show - 1, level + 4,
>> -				&semi_local_flags);
>> -		  fprintf_filtered (stream, ";\n");
>> -		}
>> -	    }
>> -
>> -	    fprintfi_filtered (level, stream, "}");
>> -	  }
>> -
>> -	do_cleanups (local_cleanups);
>> -      }
>> +      c_type_print_base_struct_union (type, stream, show, level, flags);
>
> Moving this code to a new function is a good idea, but can you do it in its own
> preliminary patch?  It is hard to review changes when code is moved and changed
> at the same time.

OK, I've splitted the patch as requested.

>>        break;
>>  
>>      case TYPE_CODE_ENUM:
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index 675f6e7bc8..f7a45dd5dd 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -17095,6 +17095,10 @@ names are substituted when printing other types.
>>  @item T
>>  Print typedefs defined in the class.  This is the default, but the flag
>>  exists in case you change the default with @command{set print type typedefs}.
>> +
>> +@item o
>> +Print the offsets and sizes of fields in a struct, similar to what the
>> +@command{pahole} tool does.
>>  @end table
>>  
>>  @kindex ptype
>> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
>> new file mode 100644
>> index 0000000000..f9a57fd3db
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
>> @@ -0,0 +1,113 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
>> +
>> +#include <stdint.h>
>> +
>> +/* A struct with many types of fields, in order to test 'ptype
>> +   /o'.  */
>> +
>> +struct abc
>> +{
>> +  /* Virtual destructor.  */
>> +  virtual ~abc ()
>> +  {}
>> +
>> +  /* 8-byte address.  Because of the virtual destructor above, this
>> +     field's offset will be 8.  */
>> +  void *field1;
>> +
>> +  /* No hole here.  */
>> +
>> +  /* 4-byte int bitfield of 1-bit.  */
>> +  unsigned int field2 : 1;
>> +
>> +  /* 31-bit hole here.  */
>> +
>> +  /* 4-byte int.  */
>> +  int field3;
>> +
>> +  /* No hole here.  */
>> +
>> +  /* 1-byte char.  */
>> +  char field4;
>> +
>> +  /* 7-byte hole here.  */
>> +
>> +  /* 8-byte int.  */
>> +  uint64_t field5;
>> +
>> +  /* We just print the offset and size of a union, ignoring its
>> +     fields.  */
>> +  union
>> +  {
>> +    /* 8-byte address.  */
>> +    void *field6;
>> +
>> +    /* 4-byte int.  */
>> +    int field7;
>> +  } field8;
>> +
>> +  /* Empty constructor.  */
>> +  abc ()
>> +  {}
>> +};
>> +
>> +/* This struct will be nested inside 'struct xyz'.  */
>> +
>> +struct tuv
>> +{
>> +  int a1;
>> +
>> +  char *a2;
>> +
>> +  int a3;
>> +};
>> +
>> +/* This struct will be nested inside 'struct pqr'.  */
>> +
>> +struct xyz
>> +{
>> +  int f1;
>> +
>> +  char f2;
>> +
>> +  void *f3;
>> +
>> +  struct tuv f4;
>> +};
>> +
>> +/* A struct with a nested struct.  */
>> +
>> +struct pqr
>> +{
>> +  int ff1;
>> +
>> +  struct xyz ff2;
>> +
>> +  char ff3;
>> +};
>> +
>> +int
>> +main (int argc, char *argv[])
>> +{
>> +  struct abc foo;
>> +  struct pqr bar;
>> +
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
>> new file mode 100644
>> index 0000000000..4f84416dc5
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
>> @@ -0,0 +1,77 @@
>> +# This testcase is part of GDB, the GNU debugger.
>> +
>> +# Copyright 2017 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/>.
>> +
>> +standard_testfile .cc ptype-offsets.cc
>
> You can reduce this to just
>
>   standard_testfile ptype-offsets.cc
>
> or
>
>   standard_testfile .cc
>
> It is harmless, but specifying .cc and ptype-offsets.cc will cause you to have two
> variables, srcfile and srcfile2, with the value "ptype-offsets.cc", and from what
> I see you don't use srcfile2.

Hm, you're right.  Fixed.

>
>> +
>> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
>> +# that the expected holes will be present in the struct.
>> +if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
>> +    untested "test work only on x86_64 lp64"
>
> work -> works

Fixed.

>> +    return 0
>> +}
>> +
>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
>> +	  { debug c++ optimize=-O0 }] } {
>
> optimize=-O0 seems unnecessary to me, I've never seen it specified explicitly in a test.

There are very few tests that use it (2, currently).  I understand it's
not very common to explicitly specify -O0, but I put it there because I
decided to be on the safe side.  If the compiler performs any
optimization at all, it could mess with the layout of the structs.

>> +    return -1
>> +}
>> +
>> +# Test general offset printing, ctor/dtor printing, union, formatting.
>> +gdb_test "ptype /o struct abc" \
>> +    [multi_line \
>> +"type = struct abc {" \
>> +"/\\\* offset    |  size \\\*/" \
>> +"                         public:" \
>> +"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
>> +"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
>> +"/\\\* XXX  7-bit hole   \\\*/" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*   20      |     4 \\\*/    int field3;" \
>> +"/\\\*   24      |     1 \\\*/    char field4;" \
>> +"/\\\* XXX  7-byte hole  \\\*/" \
>> +"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
>> +"/\\\*   40      |     8 \\\*/    union {" \
>> +"/\\\*                 8 \\\*/        void \\\*field6;" \
>> +"/\\\*                 4 \\\*/        int field7;" \
>> +"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
>> +"" \
>> +"                           abc\\(void\\);" \
>> +"                           ~abc\\(\\);" \
>> +"} /\\\* total size:   48 bytes \\\*/"] \
>> +    "ptype offset struct abc"
>
> You could save some backslashes by using {} as string delimiters, if you prefer.
> Otherwise, maybe a new proc multi_line_string_to_regexp, which combines multi_line
> and string_to_regexp, would be nice.  I mention this as a suggestion for future
> improvement, I wouldn't want to delay the merge of this patch just for that.

Thanks, I'll see about simplifying this.

>
>> +
>> +# Test nested structs.
>> +gdb_test "ptype /o struct pqr" \
>> +    [multi_line \
>> +"type = struct pqr {" \
>> +"/\\\* offset    |  size \\\*/" \
>> +"/\\\*    0      |     4 \\\*/    int f1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*    8      |    16 \\\*/    struct xyz {" \
>> +"/\\\*    8      |     4 \\\*/        int f1;" \
>> +"/\\\*   12      |     1 \\\*/        char f2;" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
>> +"/\\\*   24      |    24 \\\*/        struct tuv {" \
>> +"/\\\*   24      |     4 \\\*/            int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   32      |     8 \\\*/            char *a2;" \
>> +"/\\\*   40      |     4 \\\*/            int a3;" \
>> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
>> +"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
>> +"/\\\*   48      |     1 \\\*/    char ff3;" \
>> +"} /\\\* total size:   56 bytes \\\*/"] \
>> +    "ptype offset struct pqr"
>
> You could throw a few more tests in there, for example a union with two structs,
> to verify that the offset does go back to 0 when showing the second struct:
>
>   /* offset    |  size */
>   type = union my_union {
>   /*                 8 */    struct my_struct_1 {
>   /*    0      |     4 */        int a;
>   /*    4      |     4 */        int b;
>                              } /* total size:    8 bytes */ s1;
>   /*                 8 */    struct my_struct_2 {
>   /*    0      |     4 */        int c;
>   /*    4      |     4 */        int d;
>                              } /* total size:    8 bytes */ s2;
>   } /* total size:    8 bytes */
>

Cool, I'll add a few more tests.

> I also noticed that the offset is not shown in front of the struct-in-union,
> as show above, but it is in the case of struct-in-struct:
>
>   /* offset    |  size */
>   type = struct my_struct_3 {
>   /*    0      |     8 */    struct my_struct_1 {
>   /*    0      |     4 */        int a;
>   /*    4      |     4 */        int b;
>                              } /* total size:    8 bytes */ s1;
>   /*    8      |     8 */    struct my_struct_2 {
>   /*    8      |     4 */        int c;
>   /*   12      |     4 */        int d;
>                              } /* total size:    8 bytes */ s2;
>   } /* total size:   16 bytes */
>
> Is this difference on purpose?

Yes; offsets are not shown for fields inside unions (not only structs,
but all types of fields), because it doesn't make much sense: they'd be
0 every time.  This is also inspired from pahole's output.

> Finally, a test case on a non-struct/union type, to check that the header is not printed.

Added.

>> diff --git a/gdb/typeprint.c b/gdb/typeprint.c
>> index 427af17ad7..1463e802ad 100644
>> --- a/gdb/typeprint.c
>> +++ b/gdb/typeprint.c
>> @@ -42,6 +42,8 @@ const struct type_print_options type_print_raw_options =
>>    1,				/* raw */
>>    1,				/* print_methods */
>>    1,				/* print_typedefs */
>> +  0,				/* print_offsets */
>> +  0,				/* offset_bitpos */
>>    NULL,				/* local_typedefs */
>>    NULL,				/* global_table */
>>    NULL				/* global_printers */
>> @@ -54,6 +56,8 @@ static struct type_print_options default_ptype_flags =
>>    0,				/* raw */
>>    1,				/* print_methods */
>>    1,				/* print_typedefs */
>> +  0,				/* print_offsets */
>> +  0,				/* offset_bitpos */
>>    NULL,				/* local_typedefs */
>>    NULL,				/* global_table */
>>    NULL				/* global_printers */
>> @@ -438,6 +442,9 @@ whatis_exp (const char *exp, int show)
>>  		case 'T':
>>  		  flags.print_typedefs = 1;
>>  		  break;
>> +		case 'o':
>> +		  flags.print_offsets = 1;
>> +		  break;
>>  		default:
>>  		  error (_("unrecognized flag '%c'"), *exp);
>>  		}
>> @@ -497,6 +504,11 @@ whatis_exp (const char *exp, int show)
>>  	real_type = value_rtti_type (val, &full, &top, &using_enc);
>>      }
>>  
>> +  if (flags.print_offsets &&
>> +      (TYPE_CODE (type) == TYPE_CODE_STRUCT
>> +       || TYPE_CODE (type) == TYPE_CODE_UNION))
>> +    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
>> +
>>    printf_filtered ("type = ");
>>  
>>    if (!flags.raw)
>> @@ -722,7 +734,8 @@ Available FLAGS are:\n\
>>    /m    do not print methods defined in a class\n\
>>    /M    print methods defined in a class\n\
>>    /t    do not print typedefs defined in a class\n\
>> -  /T    print typedefs defined in a class"));
>> +  /T    print typedefs defined in a class\n\
>> +  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
>>    set_cmd_completer (c, expression_completer);
>>  
>>    c = add_com ("whatis", class_vars, whatis_command,
>> diff --git a/gdb/typeprint.h b/gdb/typeprint.h
>> index a458aa4e2f..a2a5285012 100644
>> --- a/gdb/typeprint.h
>> +++ b/gdb/typeprint.h
>> @@ -35,6 +35,15 @@ struct type_print_options
>>    /* True means print typedefs in a class.  */
>>    unsigned int print_typedefs : 1;
>>  
>> +  /* True means to print offsets, a la 'pahole'.  */
>> +  unsigned int print_offsets : 1;
>> +
>> +  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
>> +     This is needed for when we are printing nested structs and want
>> +     to make sure that the printed offset for each field carries off
>> +     the offset of the outter struct.  */
>> +  unsigned int offset_bitpos;
>> +
>>    /* If not NULL, a local typedef hash table used when printing a
>>       type.  */
>>    struct typedef_hash_table *local_typedefs;
>> 
>
> Thanks!
>
> Simon

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v3 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base'
  2017-12-11 19:58 ` [PATCH v3 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-11 19:58   ` Sergio Durigan Junior
  2017-12-11 20:55     ` Simon Marchi
  2017-12-11 19:58   ` [PATCH v3 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  1 sibling, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 19:58 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Sergio Durigan Junior

While doing the 'ptype /o' work, I noticed that 'c_type_print_base'
was very long, with a big amount of code just to handle the case of
TYPE_CODE_{STRUCT,UNION}.  This made working with the function a bit
difficult, specially because of the level of indentation.

This commit moves this part of the code to their own functions.  Now
we have a 'c_type_print_base_struct_union' with most of the code, and
also 'need_access_label_p', which is a subset of the code that was
also a good candidate for having its own function.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* c-typeprint.c (need_access_label_p): New function.
	(c_type_print_base_struct_union): New function.
	(c_type_print_base): Move code to handle
	TYPE_CODE_{STRUCT,UNION} to the functions mentioned above.
---
 gdb/c-typeprint.c | 889 ++++++++++++++++++++++++++++--------------------------
 1 file changed, 455 insertions(+), 434 deletions(-)

diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index f3c3e7d706..46c814fae8 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -875,6 +875,458 @@ output_access_specifier (struct ui_file *stream,
   return last_access;
 }
 
+/* Return true is an access label (i.e., "public:", "private:",
+   "protected:") needs to be printed for TYPE.  */
+
+static bool
+need_access_label_p (struct type *type)
+{
+  bool need_access_label = false;
+  int i, j;
+  int len, len2;
+
+  if (TYPE_DECLARED_CLASS (type))
+    {
+      QUIT;
+      len = TYPE_NFIELDS (type);
+      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
+	if (!TYPE_FIELD_PRIVATE (type, i))
+	  {
+	    need_access_label = true;
+	    break;
+	  }
+      QUIT;
+      if (!need_access_label)
+	{
+	  len2 = TYPE_NFN_FIELDS (type);
+	  for (j = 0; j < len2; j++)
+	    {
+	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
+	      for (i = 0; i < len; i++)
+		if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+								j), i))
+		  {
+		    need_access_label = true;
+		    break;
+		  }
+	      if (need_access_label)
+		break;
+	    }
+	}
+      QUIT;
+      if (!need_access_label)
+	{
+	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	    {
+	      if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+		{
+		  need_access_label = true;
+		  break;
+		}
+	    }
+	}
+    }
+  else
+    {
+      QUIT;
+      len = TYPE_NFIELDS (type);
+      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
+	if (TYPE_FIELD_PRIVATE (type, i)
+	    || TYPE_FIELD_PROTECTED (type, i))
+	  {
+	    need_access_label = true;
+	    break;
+	  }
+      QUIT;
+      if (!need_access_label)
+	{
+	  len2 = TYPE_NFN_FIELDS (type);
+	  for (j = 0; j < len2; j++)
+	    {
+	      QUIT;
+	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
+	      for (i = 0; i < len; i++)
+		if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
+								 j), i)
+		    || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+								  j),
+					      i))
+		  {
+		    need_access_label = true;
+		    break;
+		  }
+	      if (need_access_label)
+		break;
+	    }
+	}
+      QUIT;
+      if (!need_access_label)
+	{
+	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	    {
+	      if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
+		  || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+		{
+		  need_access_label = true;
+		  break;
+		}
+	    }
+	}
+    }
+
+  return need_access_label;
+}
+
+/* Helper for 'c_type_print_base' that handles structs and unions.
+   For a description of the arguments, see 'c_type_print_base'.  */
+
+static void
+c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
+				int show, int level,
+				const struct type_print_options *flags)
+{
+  struct type_print_options local_flags = *flags;
+  struct type_print_options semi_local_flags = *flags;
+  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
+
+  local_flags.local_typedefs = NULL;
+  semi_local_flags.local_typedefs = NULL;
+
+  if (!flags->raw)
+    {
+      if (flags->local_typedefs)
+	local_flags.local_typedefs
+	  = copy_typedef_hash (flags->local_typedefs);
+      else
+	local_flags.local_typedefs = create_typedef_hash ();
+
+      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
+    }
+
+  c_type_print_modifier (type, stream, 0, 1);
+  if (TYPE_CODE (type) == TYPE_CODE_UNION)
+    fprintf_filtered (stream, "union ");
+  else if (TYPE_DECLARED_CLASS (type))
+    fprintf_filtered (stream, "class ");
+  else
+    fprintf_filtered (stream, "struct ");
+
+  /* Print the tag if it exists.  The HP aCC compiler emits a
+     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
+     enum}" tag for unnamed struct/union/enum's, which we don't
+     want to print.  */
+  if (TYPE_TAG_NAME (type) != NULL
+      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
+    {
+      /* When printing the tag name, we are still effectively
+	 printing in the outer context, hence the use of FLAGS
+	 here.  */
+      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
+      if (show > 0)
+	fputs_filtered (" ", stream);
+    }
+
+  if (show < 0)
+    {
+      /* If we just printed a tag name, no need to print anything
+	 else.  */
+      if (TYPE_TAG_NAME (type) == NULL)
+	fprintf_filtered (stream, "{...}");
+    }
+  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
+    {
+      struct type *basetype;
+      int vptr_fieldno;
+
+      c_type_print_template_args (&local_flags, type, stream);
+
+      /* Add in template parameters when printing derivation info.  */
+      add_template_parameters (local_flags.local_typedefs, type);
+      cp_type_print_derivation_info (stream, type, &local_flags);
+
+      /* This holds just the global typedefs and the template
+	 parameters.  */
+      semi_local_flags.local_typedefs
+	= copy_typedef_hash (local_flags.local_typedefs);
+      if (semi_local_flags.local_typedefs)
+	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
+
+      /* Now add in the local typedefs.  */
+      recursively_update_typedef_hash (local_flags.local_typedefs, type);
+
+      fprintf_filtered (stream, "{\n");
+      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
+	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
+	{
+	  if (TYPE_STUB (type))
+	    fprintfi_filtered (level + 4, stream,
+			       _("<incomplete type>\n"));
+	  else
+	    fprintfi_filtered (level + 4, stream,
+			       _("<no data fields>\n"));
+	}
+
+      /* Start off with no specific section type, so we can print
+	 one for the first field we find, and use that section type
+	 thereafter until we find another type.  */
+
+      enum access_specifier section_type = s_none;
+
+      /* For a class, if all members are private, there's no need
+	 for a "private:" label; similarly, for a struct or union
+	 masquerading as a class, if all members are public, there's
+	 no need for a "public:" label.  */
+      bool need_access_label = need_access_label_p (type);
+
+      /* If there is a base class for this type,
+	 do not print the field that it occupies.  */
+
+      int len = TYPE_NFIELDS (type);
+      vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
+	{
+	  QUIT;
+
+	  /* If we have a virtual table pointer, omit it.  Even if
+	     virtual table pointers are not specifically marked in
+	     the debug info, they should be artificial.  */
+	  if ((i == vptr_fieldno && type == basetype)
+	      || TYPE_FIELD_ARTIFICIAL (type, i))
+	    continue;
+
+	  if (need_access_label)
+	    {
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FIELD_PROTECTED (type, i),
+		 TYPE_FIELD_PRIVATE (type, i));
+	    }
+
+	  print_spaces_filtered (level + 4, stream);
+	  if (field_is_static (&TYPE_FIELD (type, i)))
+	    fprintf_filtered (stream, "static ");
+	  c_print_type (TYPE_FIELD_TYPE (type, i),
+			TYPE_FIELD_NAME (type, i),
+			stream, show - 1, level + 4,
+			&local_flags);
+	  if (!field_is_static (&TYPE_FIELD (type, i))
+	      && TYPE_FIELD_PACKED (type, i))
+	    {
+	      /* It is a bitfield.  This code does not attempt
+		 to look at the bitpos and reconstruct filler,
+		 unnamed fields.  This would lead to misleading
+		 results if the compiler does not put out fields
+		 for such things (I don't know what it does).  */
+	      fprintf_filtered (stream, " : %d",
+				TYPE_FIELD_BITSIZE (type, i));
+	    }
+	  fprintf_filtered (stream, ";\n");
+	}
+
+      /* If there are both fields and methods, put a blank line
+	 between them.  Make sure to count only method that we
+	 will display; artificial methods will be hidden.  */
+      len = TYPE_NFN_FIELDS (type);
+      if (!flags->print_methods)
+	len = 0;
+      int real_len = 0;
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  int j;
+
+	  for (j = 0; j < len2; j++)
+	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
+	      real_len++;
+	}
+      if (real_len > 0 && section_type != s_none)
+	fprintf_filtered (stream, "\n");
+
+      /* C++: print out the methods.  */
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
+	  const char *name = type_name_no_tag (type);
+	  int is_constructor = name && strcmp (method_name,
+					       name) == 0;
+
+	  for (j = 0; j < len2; j++)
+	    {
+	      const char *mangled_name;
+	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
+	      char *demangled_name;
+	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
+	      int is_full_physname_constructor =
+		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
+		|| is_constructor_name (physname)
+		|| is_destructor_name (physname)
+		|| method_name[0] == '~';
+
+	      /* Do not print out artificial methods.  */
+	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
+		continue;
+
+	      QUIT;
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FN_FIELD_PROTECTED (f, j),
+		 TYPE_FN_FIELD_PRIVATE (f, j));
+
+	      print_spaces_filtered (level + 4, stream);
+	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
+		fprintf_filtered (stream, "virtual ");
+	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
+		fprintf_filtered (stream, "static ");
+	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
+		{
+		  /* Keep GDB from crashing here.  */
+		  fprintf_filtered (stream,
+				    _("<undefined type> %s;\n"),
+				    TYPE_FN_FIELD_PHYSNAME (f, j));
+		  break;
+		}
+	      else if (!is_constructor	/* Constructors don't
+					   have declared
+					   types.  */
+		       && !is_full_physname_constructor  /* " " */
+		       && !is_type_conversion_operator (type, i, j))
+		{
+		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+				"", stream, -1, 0,
+				&local_flags);
+		  fputs_filtered (" ", stream);
+		}
+	      if (TYPE_FN_FIELD_STUB (f, j))
+		{
+		  /* Build something we can demangle.  */
+		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
+		  mangled_name = mangled_name_holder.get ();
+		}
+	      else
+		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
+
+	      demangled_name =
+		gdb_demangle (mangled_name,
+			      DMGL_ANSI | DMGL_PARAMS);
+	      if (demangled_name == NULL)
+		{
+		  /* In some cases (for instance with the HP
+		     demangling), if a function has more than 10
+		     arguments, the demangling will fail.
+		     Let's try to reconstruct the function
+		     signature from the symbol information.  */
+		  if (!TYPE_FN_FIELD_STUB (f, j))
+		    {
+		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
+		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
+
+		      cp_type_print_method_args (mtype,
+						 "",
+						 method_name,
+						 staticp,
+						 stream, &local_flags);
+		    }
+		  else
+		    fprintf_filtered (stream,
+				      _("<badly mangled name '%s'>"),
+				      mangled_name);
+		}
+	      else
+		{
+		  char *p;
+		  char *demangled_no_class
+		    = remove_qualifiers (demangled_name);
+
+		  /* Get rid of the `static' appended by the
+		     demangler.  */
+		  p = strstr (demangled_no_class, " static");
+		  if (p != NULL)
+		    {
+		      int length = p - demangled_no_class;
+		      char *demangled_no_static;
+
+		      demangled_no_static
+			= (char *) xmalloc (length + 1);
+		      strncpy (demangled_no_static,
+			       demangled_no_class, length);
+		      *(demangled_no_static + length) = '\0';
+		      fputs_filtered (demangled_no_static, stream);
+		      xfree (demangled_no_static);
+		    }
+		  else
+		    fputs_filtered (demangled_no_class, stream);
+		  xfree (demangled_name);
+		}
+
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print out nested types.  */
+      if (TYPE_NESTED_TYPES_COUNT (type) != 0
+	  && semi_local_flags.print_nested_type_limit != 0)
+	{
+	  if (semi_local_flags.print_nested_type_limit > 0)
+	    --semi_local_flags.print_nested_type_limit;
+
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
+	    {
+	      print_spaces_filtered (level + 4, stream);
+	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+			    "", stream, show, level + 4, &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print typedefs defined in this class.  */
+
+      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
+	{
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
+	      || TYPE_NESTED_TYPES_COUNT (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
+	    {
+	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
+
+	      /* Dereference the typedef declaration itself.  */
+	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
+	      target = TYPE_TARGET_TYPE (target);
+
+	      if (need_access_label)
+		{
+		  section_type = output_access_specifier
+		    (stream, section_type, level,
+		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		}
+	      print_spaces_filtered (level + 4, stream);
+	      fprintf_filtered (stream, "typedef ");
+
+	      /* We want to print typedefs with substitutions
+		 from the template parameters or globally-known
+		 typedefs but not local typedefs.  */
+	      c_print_type (target,
+			    TYPE_TYPEDEF_FIELD_NAME (type, i),
+			    stream, show - 1, level + 4,
+			    &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      fprintfi_filtered (level, stream, "}");
+    }
+
+  do_cleanups (local_cleanups);
+}
+
 /* Print the name of the type (or the ultimate pointer target,
    function value or array element), or the description of a structure
    or union.
@@ -898,10 +1350,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		   int show, int level, const struct type_print_options *flags)
 {
   int i;
-  int len, real_len;
-  enum access_specifier section_type;
-  int need_access_label = 0;
-  int j, len2;
+  int len;
+  int j;
 
   QUIT;
 
@@ -958,436 +1408,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      {
-	struct type_print_options local_flags = *flags;
-	struct type_print_options semi_local_flags = *flags;
-	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
-
-	local_flags.local_typedefs = NULL;
-	semi_local_flags.local_typedefs = NULL;
-
-	if (!flags->raw)
-	  {
-	    if (flags->local_typedefs)
-	      local_flags.local_typedefs
-		= copy_typedef_hash (flags->local_typedefs);
-	    else
-	      local_flags.local_typedefs = create_typedef_hash ();
-
-	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
-	  }
-
-	c_type_print_modifier (type, stream, 0, 1);
-	if (TYPE_CODE (type) == TYPE_CODE_UNION)
-	  fprintf_filtered (stream, "union ");
-	else if (TYPE_DECLARED_CLASS (type))
-	  fprintf_filtered (stream, "class ");
-	else
-	  fprintf_filtered (stream, "struct ");
-
-	/* Print the tag if it exists.  The HP aCC compiler emits a
-	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
-	   enum}" tag for unnamed struct/union/enum's, which we don't
-	   want to print.  */
-	if (TYPE_TAG_NAME (type) != NULL
-	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
-	  {
-	    /* When printing the tag name, we are still effectively
-	       printing in the outer context, hence the use of FLAGS
-	       here.  */
-	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
-	    if (show > 0)
-	      fputs_filtered (" ", stream);
-	  }
-
-	if (show < 0)
-	  {
-	    /* If we just printed a tag name, no need to print anything
-	       else.  */
-	    if (TYPE_TAG_NAME (type) == NULL)
-	      fprintf_filtered (stream, "{...}");
-	  }
-	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
-	  {
-	    struct type *basetype;
-	    int vptr_fieldno;
-
-	    c_type_print_template_args (&local_flags, type, stream);
-
-	    /* Add in template parameters when printing derivation info.  */
-	    add_template_parameters (local_flags.local_typedefs, type);
-	    cp_type_print_derivation_info (stream, type, &local_flags);
-
-	    /* This holds just the global typedefs and the template
-	       parameters.  */
-	    semi_local_flags.local_typedefs
-	      = copy_typedef_hash (local_flags.local_typedefs);
-	    if (semi_local_flags.local_typedefs)
-	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
-
-	    /* Now add in the local typedefs.  */
-	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
-
-	    fprintf_filtered (stream, "{\n");
-	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
-		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
-	      {
-		if (TYPE_STUB (type))
-		  fprintfi_filtered (level + 4, stream,
-				     _("<incomplete type>\n"));
-		else
-		  fprintfi_filtered (level + 4, stream,
-				     _("<no data fields>\n"));
-	      }
-
-	    /* Start off with no specific section type, so we can print
-	       one for the first field we find, and use that section type
-	       thereafter until we find another type.  */
-
-	    section_type = s_none;
-
-	    /* For a class, if all members are private, there's no need
-	       for a "private:" label; similarly, for a struct or union
-	       masquerading as a class, if all members are public, there's
-	       no need for a "public:" label.  */
-
-	    if (TYPE_DECLARED_CLASS (type))
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (!TYPE_FIELD_PRIVATE (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									  j), i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-	    else
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (TYPE_FIELD_PRIVATE (type, i)
-		      || TYPE_FIELD_PROTECTED (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			QUIT;
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
-									   j), i)
-			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									    j),
-							i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
-			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-
-	    /* If there is a base class for this type,
-	       do not print the field that it occupies.  */
-
-	    len = TYPE_NFIELDS (type);
-	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
-	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-	      {
-		QUIT;
-
-		/* If we have a virtual table pointer, omit it.  Even if
-		   virtual table pointers are not specifically marked in
-		   the debug info, they should be artificial.  */
-		if ((i == vptr_fieldno && type == basetype)
-		    || TYPE_FIELD_ARTIFICIAL (type, i))
-		  continue;
-
-		if (need_access_label)
-		  {
-		    section_type = output_access_specifier
-		      (stream, section_type, level,
-		       TYPE_FIELD_PROTECTED (type, i),
-		       TYPE_FIELD_PRIVATE (type, i));
-		  }
-
-		print_spaces_filtered (level + 4, stream);
-		if (field_is_static (&TYPE_FIELD (type, i)))
-		  fprintf_filtered (stream, "static ");
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show - 1, level + 4,
-			      &local_flags);
-		if (!field_is_static (&TYPE_FIELD (type, i))
-		    && TYPE_FIELD_PACKED (type, i))
-		  {
-		    /* It is a bitfield.  This code does not attempt
-		       to look at the bitpos and reconstruct filler,
-		       unnamed fields.  This would lead to misleading
-		       results if the compiler does not put out fields
-		       for such things (I don't know what it does).  */
-		    fprintf_filtered (stream, " : %d",
-				      TYPE_FIELD_BITSIZE (type, i));
-		  }
-		fprintf_filtered (stream, ";\n");
-	      }
-
-	  /* If there are both fields and methods, put a blank line
-	     between them.  Make sure to count only method that we
-	     will display; artificial methods will be hidden.  */
-	  len = TYPE_NFN_FIELDS (type);
-	  if (!flags->print_methods)
-	    len = 0;
-	  real_len = 0;
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      int j;
-
-	      for (j = 0; j < len2; j++)
-		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		  real_len++;
-	    }
-	  if (real_len > 0 && section_type != s_none)
-	    fprintf_filtered (stream, "\n");
-
-	  /* C++: print out the methods.  */
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
-	      const char *name = type_name_no_tag (type);
-	      int is_constructor = name && strcmp (method_name,
-						   name) == 0;
-
-	      for (j = 0; j < len2; j++)
-		{
-		  const char *mangled_name;
-		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
-		  char *demangled_name;
-		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
-		  int is_full_physname_constructor =
-		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
-		    || is_constructor_name (physname)
-		    || is_destructor_name (physname)
-		    || method_name[0] == '~';
-
-		  /* Do not print out artificial methods.  */
-		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		    continue;
-
-		  QUIT;
-		  section_type = output_access_specifier
-		    (stream, section_type, level,
-		     TYPE_FN_FIELD_PROTECTED (f, j),
-		     TYPE_FN_FIELD_PRIVATE (f, j));
-
-		  print_spaces_filtered (level + 4, stream);
-		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
-		    fprintf_filtered (stream, "virtual ");
-		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
-		    fprintf_filtered (stream, "static ");
-		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
-		    {
-		      /* Keep GDB from crashing here.  */
-		      fprintf_filtered (stream,
-					_("<undefined type> %s;\n"),
-					TYPE_FN_FIELD_PHYSNAME (f, j));
-		      break;
-		    }
-		  else if (!is_constructor	/* Constructors don't
-						   have declared
-						   types.  */
-			   && !is_full_physname_constructor  /* " " */
-			   && !is_type_conversion_operator (type, i, j))
-		    {
-		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				    "", stream, -1, 0,
-				    &local_flags);
-		      fputs_filtered (" ", stream);
-		    }
-		  if (TYPE_FN_FIELD_STUB (f, j))
-		    {
-		      /* Build something we can demangle.  */
-		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
-		      mangled_name = mangled_name_holder.get ();
-		    }
-		  else
-		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
-
-		  demangled_name =
-		    gdb_demangle (mangled_name,
-				  DMGL_ANSI | DMGL_PARAMS);
-		  if (demangled_name == NULL)
-		    {
-		      /* In some cases (for instance with the HP
-			 demangling), if a function has more than 10
-			 arguments, the demangling will fail.
-			 Let's try to reconstruct the function
-			 signature from the symbol information.  */
-		      if (!TYPE_FN_FIELD_STUB (f, j))
-			{
-			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
-			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
-
-			  cp_type_print_method_args (mtype,
-						     "",
-						     method_name,
-						     staticp,
-						     stream, &local_flags);
-			}
-		      else
-			fprintf_filtered (stream,
-					  _("<badly mangled name '%s'>"),
-					  mangled_name);
-		    }
-		  else
-		    {
-		      char *p;
-		      char *demangled_no_class
-			= remove_qualifiers (demangled_name);
-
-		      /* Get rid of the `static' appended by the
-			 demangler.  */
-		      p = strstr (demangled_no_class, " static");
-		      if (p != NULL)
-			{
-			  int length = p - demangled_no_class;
-			  char *demangled_no_static;
-
-			  demangled_no_static
-			    = (char *) xmalloc (length + 1);
-			  strncpy (demangled_no_static,
-				   demangled_no_class, length);
-			  *(demangled_no_static + length) = '\0';
-			  fputs_filtered (demangled_no_static, stream);
-			  xfree (demangled_no_static);
-			}
-		      else
-			fputs_filtered (demangled_no_class, stream);
-		      xfree (demangled_name);
-		    }
-
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print out nested types.  */
-	  if (TYPE_NESTED_TYPES_COUNT (type) != 0
-	      && semi_local_flags.print_nested_type_limit != 0)
-	    {
-	      if (semi_local_flags.print_nested_type_limit > 0)
-		--semi_local_flags.print_nested_type_limit;
-
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
-		{
-		  print_spaces_filtered (level + 4, stream);
-		  c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
-				"", stream, show, level + 4, &semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print typedefs defined in this class.  */
-
-	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
-	    {
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
-		  || TYPE_NESTED_TYPES_COUNT (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
-		{
-		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
-
-		  /* Dereference the typedef declaration itself.  */
-		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
-		  target = TYPE_TARGET_TYPE (target);
-
-		  if (need_access_label)
-		    {
-		      section_type = output_access_specifier
-			(stream, section_type, level,
-			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
-		    }
-		  print_spaces_filtered (level + 4, stream);
-		  fprintf_filtered (stream, "typedef ");
-
-		  /* We want to print typedefs with substitutions
-		     from the template parameters or globally-known
-		     typedefs but not local typedefs.  */
-		  c_print_type (target,
-				TYPE_TYPEDEF_FIELD_NAME (type, i),
-				stream, show - 1, level + 4,
-				&semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	    fprintfi_filtered (level, stream, "}");
-	  }
-
-	do_cleanups (local_cleanups);
-      }
+      c_type_print_base_struct_union (type, stream, show, level, flags);
       break;
 
     case TYPE_CODE_ENUM:
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v3 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg)
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
                   ` (3 preceding siblings ...)
  2017-11-28 21:21 ` [PATCH v2] " Sergio Durigan Junior
@ 2017-12-11 19:58 ` Sergio Durigan Junior
  2017-12-11 19:58   ` [PATCH v3 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
  2017-12-11 19:58   ` [PATCH v3 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-11 23:43 ` [PATCH v4 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
                   ` (3 subsequent siblings)
  8 siblings, 2 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 19:58 UTC (permalink / raw)
  To: GDB Patches; +Cc: Tom Tromey, Eli Zaretskii, Simon Marchi

Changes from v2:

- Split the patch between #1 (code reorganization) and #2 (actual
  implementation of 'ptype /o').

- Add a few more tests; fix a few thinkos in the testcase.

- Remove leftover code.


Hi,

This is the third version of the patch, which is now a series.
Functionality hasn't changed; this is mostly a reorganization of the
patch into two patches (one for code movement, another for the actual
implementation of 'ptype /o').  Each patch contains an explanation of
what it does.

The docs have already been approved by Eli.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v3 2/2] Implement pahole-like 'ptype /o' option
  2017-12-11 19:58 ` [PATCH v3 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2017-12-11 19:58   ` [PATCH v3 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
@ 2017-12-11 19:58   ` Sergio Durigan Junior
  2017-12-11 21:50     ` Simon Marchi
  1 sibling, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 19:58 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Sergio Durigan Junior

This commit implements the pahole-like '/o' option for 'ptype', which
prints the offsets and sizes of struct fields, reporting whenever
there is a hole found.

The output is heavily based on pahole(1), with a few modifications
here and there to adjust it to our reality.  Here's an example:

  (gdb) ptype /o stap_probe
  /* offset    |  size */
  struct stap_probe {
  /*    0      |    40 */    struct probe {
  /*    0      |     8 */        const probe_ops *pops;
  /*    8      |     8 */        gdbarch *arch;
  /*   16      |     8 */        const char *name;
  /*   24      |     8 */        const char *provider;
  /*   32      |     8 */        CORE_ADDR address;
			     } /* total size:   40 bytes */ p;
  /*   40      |     8 */    CORE_ADDR sem_addr;
  /*   48:31   |     4 */    unsigned int args_parsed : 1;
  /* XXX  7-bit hole   */
  /* XXX  7-byte hole  */
  /*   56      |     8 */    union {
  /*                 8 */        const char *text;
  /*                 8 */        VEC_stap_probe_arg_s *vec;
			     } /* total size:    8 bytes */ args_u;
  } /* total size:   64 bytes */

A big part of this patch handles the formatting logic of 'ptype',
which is a bit messy.  I tried to be not very invasive, but I had to
do some cleanups here and there to make life easier.

This patch is the start of a long-term work I'll do to flush the local
patches we carry for Fedora GDB.  In this specific case, I'm aiming at
upstreaming the feature implemented by the 'pahole.py' script that is
shipped with Fedora GDB:

  <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>

This has been regression-tested on the BuildBot.  There's a new
testcase for it, along with an update to the documentation.  I also
thought it was worth mentioning this feature in the NEWS file.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
	* c-typeprint.c (OFFSET_SPC_LEN): New define.
	(print_spaces_filtered_with_print_options): New function.
	(output_access_specifier): Take new argument FLAGS.  Modify
	function to call 'print_spaces_filtered_with_print_options'.
	(c_print_type_union_field_offset): New function.
	(c_print_type_struct_field_offset): New function.
	(c_type_print_base_struct_union): Print offsets and sizes for
	struct/union/class fields.
	* typeprint.c (const struct type_print_options
	type_print_raw_options): Initialize 'print_offsets' and
	'offset_bitpos'.
	(static struct type_print_options default_ptype_flags):
	Likewise.
	(whatis_exp): Handle '/o' option.
	(_initialize_typeprint): Add '/o' flag to ptype's help.
	* typeprint.h (struct type_print_options) <print_offsets>: New
	field.
	<offset_bitpos>: Likewise.

gdb/testsuite/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.base/ptype-offsets.cc: New file.
	* gdb.base/ptype-offsets.exp: New file.

gdb/doc/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.texinfo (ptype): Add new flag '/o'.
---
 gdb/NEWS                                 |   3 +
 gdb/c-typeprint.c                        | 193 ++++++++++++++++++++++++++++---
 gdb/doc/gdb.texinfo                      |   4 +
 gdb/testsuite/gdb.base/ptype-offsets.cc  | 138 ++++++++++++++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.exp | 158 +++++++++++++++++++++++++
 gdb/typeprint.c                          |  15 ++-
 gdb/typeprint.h                          |   9 ++
 7 files changed, 502 insertions(+), 18 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index c6fe297159..67d40d007b 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * New "--readnever" command line option instructs GDB to not read each
   symbol file's symbolic debug information.  This makes startup faster
   but at the expense of not being able to perform symbolic debugging.
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index 46c814fae8..aca5ec898d 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
+					const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,14 +890,86 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
+/* Print information about the offset of TYPE inside its union.
+   FIELD_IDX represents the index of this TYPE inside the union.  We
+   just print the type size, and nothing more.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
+				 struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+
+  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+}
+
+/* Print information about the offset of TYPE inside its struct.
+   FIELD_IDX represents the index of this TYPE inside the struct, and
+   ENDPOS is the end position of the previous type (this is how we
+   calculate whether there are holes in the struct).  At the end,
+   ENDPOS is updated.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
+				  unsigned int *endpos, struct ui_file *stream,
+				  unsigned int offset_bitpos)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit;
+
+  if (*endpos > 0 && *endpos < bitpos)
+    {
+      /* If ENDPOS is smaller than the current type's bitpos, it means
+	 there's a hole in the struct, so we report it here.  */
+      unsigned int hole = bitpos - *endpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  /* The position of the field, relative to the beginning of the
+     struct.  Assume this number will have 4 digits.  */
+  fprintf_filtered (stream, "/* %4u",
+		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT);
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      fieldsize_bit = TYPE_FIELD_BITSIZE (type, field_idx);
+      fprintf_filtered (stream, ":%u",
+			fieldsize_byte * TARGET_CHAR_BIT - fieldsize_bit);
+    }
+  else
+    {
+      fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  *endpos = bitpos + fieldsize_bit;
+}
+
 /* Return true is an access label (i.e., "public:", "private:",
    "protected:") needs to be printed for TYPE.  */
 
@@ -1083,6 +1178,8 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
       int len = TYPE_NFIELDS (type);
       vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      unsigned int endpos = 0;
+
       for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
 	{
 	  QUIT;
@@ -1099,18 +1196,51 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FIELD_PROTECTED (type, i),
-		 TYPE_FIELD_PRIVATE (type, i));
+		 TYPE_FIELD_PRIVATE (type, i), flags);
+	    }
+
+	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+	  if (flags->print_offsets)
+	    {
+	      if (!is_static)
+		{
+		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		    c_print_type_struct_field_offset (type, i, &endpos, stream,
+						      flags->offset_bitpos);
+		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
+		    c_print_type_union_field_offset (type, i, stream);
+		}
+	      else
+		print_spaces_filtered (OFFSET_SPC_LEN, stream);
 	    }
 
 	  print_spaces_filtered (level + 4, stream);
-	  if (field_is_static (&TYPE_FIELD (type, i)))
+	  if (is_static)
 	    fprintf_filtered (stream, "static ");
+
+	  int newshow = show - 1;
+
+	  if (flags->print_offsets
+	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
+		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
+	    {
+	      /* If we're printing offsets and this field's type is
+		 either a struct or an union, then we're interested in
+		 expanding it.  */
+	      ++newshow;
+
+	      /* Make sure we carry our offset when we expand the
+		 struct.  */
+	      local_flags.offset_bitpos
+		= flags->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
+	    }
+
 	  c_print_type (TYPE_FIELD_TYPE (type, i),
 			TYPE_FIELD_NAME (type, i),
-			stream, show - 1, level + 4,
+			stream, newshow, level + 4,
 			&local_flags);
-	  if (!field_is_static (&TYPE_FIELD (type, i))
-	      && TYPE_FIELD_PACKED (type, i))
+	  if (!is_static && TYPE_FIELD_PACKED (type, i))
 	    {
 	      /* It is a bitfield.  This code does not attempt
 		 to look at the bitpos and reconstruct filler,
@@ -1173,9 +1303,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FN_FIELD_PROTECTED (f, j),
-		 TYPE_FN_FIELD_PRIVATE (f, j));
+		 TYPE_FN_FIELD_PRIVATE (f, j), flags);
 
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
 		fprintf_filtered (stream, "virtual ");
 	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
@@ -1194,9 +1325,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		       && !is_full_physname_constructor  /* " " */
 		       && !is_type_conversion_operator (type, i, j))
 		{
+		  unsigned int old_po = local_flags.print_offsets;
+
+		  /* Temporarily disable print_offsets, because it
+		     would mess with indentation.  */
+		  local_flags.print_offsets = 0;
 		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
 				"", stream, -1, 0,
 				&local_flags);
+		  local_flags.print_offsets = old_po;
+
 		  fputs_filtered (" ", stream);
 		}
 	      if (TYPE_FN_FIELD_STUB (f, j))
@@ -1277,9 +1415,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
 	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
 	    {
-	      print_spaces_filtered (level + 4, stream);
+	      unsigned int old_po = semi_local_flags.print_offsets;
+
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      semi_local_flags.print_offsets = 0;
 	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
 			    "", stream, show, level + 4, &semi_local_flags);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
@@ -1305,11 +1450,17 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		  section_type = output_access_specifier
 		    (stream, section_type, level,
 		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i), flags);
 		}
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      fprintf_filtered (stream, "typedef ");
 
+	      unsigned int old_po = semi_local_flags.print_offsets;
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      semi_local_flags.print_offsets = 0;
+
 	      /* We want to print typedefs with substitutions
 		 from the template parameters or globally-known
 		 typedefs but not local typedefs.  */
@@ -1317,13 +1468,21 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 			    TYPE_TYPEDEF_FIELD_NAME (type, i),
 			    stream, show - 1, level + 4,
 			    &semi_local_flags);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
 
+      if (flags->print_offsets && level > 0)
+	print_spaces_filtered (OFFSET_SPC_LEN, stream);
+
       fprintfi_filtered (level, stream, "}");
     }
 
+  if (show > 0 && flags->print_offsets)
+    fprintf_filtered (stream, " /* total size: %4u bytes */",
+		      TYPE_LENGTH (type));
+
   do_cleanups (local_cleanups);
 }
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 062f01d5ff..7e85d92bf4 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17211,6 +17211,10 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..52d2277ce7
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,138 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
+
+#include <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  char field4;
+
+  /* 7-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+};
+
+/* This struct will be nested inside 'struct xyz'.  */
+
+struct tuv
+{
+  int a1;
+
+  char *a2;
+
+  int a3;
+};
+
+/* This struct will be nested inside 'struct pqr'.  */
+
+struct xyz
+{
+  int f1;
+
+  char f2;
+
+  void *f3;
+
+  struct tuv f4;
+};
+
+/* A struct with a nested struct.  */
+
+struct pqr
+{
+  int ff1;
+
+  struct xyz ff2;
+
+  char ff3;
+};
+
+/* A union with two nested structs.  */
+
+union qwe
+{
+  struct tuv fff1;
+
+  struct xyz fff2;
+};
+
+/* A struct with an union.  */
+
+struct poi
+{
+  int f1;
+
+  union qwe f2;
+
+  uint16_t f3;
+
+  struct pqr f4;
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+  struct pqr bar;
+  union qwe c;
+  struct poi d;
+  uint8_t i;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..fa1c0cb41c
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,158 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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/>.
+
+standard_testfile .cc
+
+# Test only works on x86_64 LP64 targets.  That's how we guarantee
+# that the expected holes will be present in the struct.
+if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
+    untested "test work only on x86_64 lp64"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ optimize=-O0 }] } {
+    return -1
+}
+
+# Test general offset printing, ctor/dtor printing, union, formatting.
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+"type = struct abc {" \
+"/\\\* offset    |  size \\\*/" \
+"                         public:" \
+"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
+"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
+"/\\\* XXX  7-bit hole   \\\*/" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   20      |     4 \\\*/    int field3;" \
+"/\\\*   24      |     1 \\\*/    char field4;" \
+"/\\\* XXX  7-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
+"/\\\*   40      |     8 \\\*/    union {" \
+"/\\\*                 8 \\\*/        void \\\*field6;" \
+"/\\\*                 4 \\\*/        int field7;" \
+"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
+"" \
+"                           abc\\(void\\);" \
+"                           ~abc\\(\\);" \
+"} /\\\* total size:   48 bytes \\\*/"] \
+    "ptype offset struct abc"
+
+# Test nested structs.
+gdb_test "ptype /o struct pqr" \
+    [multi_line \
+"type = struct pqr {" \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    16 \\\*/    struct xyz {" \
+"/\\\*    8      |     4 \\\*/        int f1;" \
+"/\\\*   12      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/        struct tuv {" \
+"/\\\*   24      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/            char *a2;" \
+"/\\\*   40      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*   48      |     1 \\\*/    char ff3;" \
+"} /\\\* total size:   56 bytes \\\*/"] \
+    "ptype offset struct pqr"
+
+# Test that the offset is properly reset when we are printing an union
+# and go inside two inner structs.
+# This also tests a struct inside a struct inside an union.
+gdb_test "ptype /o union qwe" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*                24 \\\*/    struct tuv {" \
+"/\\\*    0      |     4 \\\*/        int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/        char \\\*a2;" \
+"/\\\*   16      |     4 \\\*/        int a3;" \
+"                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
+"/\\\*                40 \\\*/    struct xyz {" \
+"/\\\*    0      |     4 \\\*/        int f1;" \
+"/\\\*    4      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   16      |    24 \\\*/        struct tuv {" \
+"/\\\*   16      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   24      |     8 \\\*/            char \\\*a2;" \
+"/\\\*   32      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ fff2;" \
+"} /\\\* total size:   40 bytes \\\*/"] \
+    "ptype offset union qwe"
+
+# Test printing a struct that contains an union, and that also
+# contains a struct.
+gdb_test "ptype /o struct poi" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    40 \\\*/    union qwe {" \
+"/\\\*                24 \\\*/        struct tuv {" \
+"/\\\*    8      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/            char \\\*a2;" \
+"/\\\*   24      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ fff1;" \
+"/\\\*                40 \\\*/        struct xyz {" \
+"/\\\*    8      |     4 \\\*/            int f1;" \
+"/\\\*   12      |     1 \\\*/            char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/            void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/            struct tuv {" \
+"/\\\*   24      |     4 \\\*/                int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/                char \\\*a2;" \
+"/\\\*   40      |     4 \\\*/                int a3;" \
+"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                               } /\\\* total size:   40 bytes \\\*/ fff2;" \
+"                           } /\\\* total size:   40 bytes \\\*/ f2;" \
+"/\\\*   48      |     2 \\\*/    uint16_t f3;" \
+"/\\\* XXX  6-byte hole  */" \
+"/\\\*   56      |    56 \\\*/    struct pqr {" \
+"/\\\*   56      |     4 \\\*/        int ff1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   64      |    40 \\\*/        struct xyz {" \
+"/\\\*   64      |     4 \\\*/            int f1;" \
+"/\\\*   68      |     1 \\\*/            char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   72      |     8 \\\*/            void \\\*f3;" \
+"/\\\*   80      |    24 \\\*/            struct tuv {" \
+"/\\\*   80      |     4 \\\*/                int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   88      |     8 \\\*/                char \\\*a2;" \
+"/\\\*   96      |     4 \\\*/                int a3;" \
+"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                               } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*  104      |     1 \\\*/        char ff3;" \
+"                           } /\\\* total size:   56 bytes \\\*/ f4;" \
+"} /\\\* total size:  112 bytes \\\*/"] \
+    "ptype offset struct poi"
+
+# Test that we don't print any header when issuing a "ptype /o" on a
+# non-struct, non-union, non-class type.
+gdb_test "ptype /o int" "int" "ptype offset int"
+gdb_test "ptype /o uint8_t" "char" "ptype offset uint8_t"
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 9d9d6f5a49..eb829c2069 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,7 +42,9 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
+  0,				/* offset_bitpos */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -55,7 +57,9 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
+  0,				/* offset_bitpos */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -440,6 +444,9 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  flags.print_offsets = 1;
+		  break;
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -499,6 +506,11 @@ whatis_exp (const char *exp, int show)
 	real_type = value_rtti_type (val, &full, &top, &using_enc);
     }
 
+  if (flags.print_offsets &&
+      (TYPE_CODE (type) == TYPE_CODE_STRUCT
+       || TYPE_CODE (type) == TYPE_CODE_UNION))
+    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
+
   printf_filtered ("type = ");
 
   if (!flags.raw)
@@ -759,7 +771,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a2058b0120..dd23c1eb6c 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -35,9 +35,18 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
   /* The number of nested type definitions to print.  -1 == all.  */
   int print_nested_type_limit;
 
+  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
+     This is needed for when we are printing nested structs and want
+     to make sure that the printed offset for each field carries off
+     the offset of the outter struct.  */
+  unsigned int offset_bitpos;
+
   /* If not NULL, a local typedef hash table used when printing a
      type.  */
   struct typedef_hash_table *local_typedefs;
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-11 18:59     ` Sergio Durigan Junior
@ 2017-12-11 20:45       ` Simon Marchi
  2017-12-11 21:07         ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Simon Marchi @ 2017-12-11 20:45 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: GDB Patches, Tom Tromey, Eli Zaretskii

>>> +/* Use 'print_spaces_filtered', but take into consideration the
>>> +   type_print_options FLAGS in order to determine how many whitespaces
>>> +   will be printed.  */
>>> +
>>> +static void
>>> +print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
>>> +					const struct type_print_options *flags)
>>
>> Missing spaces here.
> 
> This is actually on purpose.  If I indent the line, it will have more
> than 80 chars.  I believe this is a well known method for avoiding this
> problem...?

I am not aware of that.  In this case I would put the parameter list on the next,
I'm not sure if it's 100% GNU-style approved, but nobody complained when I did it
so far :)

static void
print_spaces_filtered_with_print_options
  (int level, struct ui_file *stream, const struct type_print_options *flags);

It helps with long function names.  In this case, I would probably just drop the
"struct" to save a few chars, because C++.

>>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
>>> +	  { debug c++ optimize=-O0 }] } {
>>
>> optimize=-O0 seems unnecessary to me, I've never seen it specified explicitly in a test.
> 
> There are very few tests that use it (2, currently).  I understand it's
> not very common to explicitly specify -O0, but I put it there because I
> decided to be on the safe side.  If the compiler performs any
> optimization at all, it could mess with the layout of the structs.

If GCC decided to optimize by default, so many things would break in the GDB testsuite.
We would then probably make gdb_compile add -O0, so that we wouldn't need to do it in
all tests.  The point is that IMO, tests should expect no optimization by default.

>> I also noticed that the offset is not shown in front of the struct-in-union,
>> as show above, but it is in the case of struct-in-struct:
>>
>>   /* offset    |  size */
>>   type = struct my_struct_3 {
>>   /*    0      |     8 */    struct my_struct_1 {
>>   /*    0      |     4 */        int a;
>>   /*    4      |     4 */        int b;
>>                              } /* total size:    8 bytes */ s1;
>>   /*    8      |     8 */    struct my_struct_2 {
>>   /*    8      |     4 */        int c;
>>   /*   12      |     4 */        int d;
>>                              } /* total size:    8 bytes */ s2;
>>   } /* total size:   16 bytes */
>>
>> Is this difference on purpose?
> 
> Yes; offsets are not shown for fields inside unions (not only structs,
> but all types of fields), because it doesn't make much sense: they'd be
> 0 every time.  This is also inspired from pahole's output.

Not if that union is itself in a struct.  For example with this:

struct hello
{
        int i;
        union {
                struct {
                        int x, y;
                } a;
                struct {
                        int x, y;
                } b;
        };
};

(gdb) ptype /o struct hello
/* offset    |  size */
type = struct hello {
/*    0      |     4 */    int i;
/*    4      |     8 */    union {
/*                 8 */        struct {
/*    4      |     4 */            int x;
/*    8      |     4 */            int y;
                               } /* total size:    8 bytes */ a;
/*                 8 */        struct {
/*    4      |     4 */            int x;
/*    8      |     4 */            int y;
                               } /* total size:    8 bytes */ b;
                           } /* total size:    8 bytes */;
} /* total size:   12 bytes */


But I don't mind it, it just stuck out as a little inconsistency.

I'll look at v3 now.

Simon

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base'
  2017-12-11 19:58   ` [PATCH v3 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
@ 2017-12-11 20:55     ` Simon Marchi
  2017-12-11 23:05       ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Simon Marchi @ 2017-12-11 20:55 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches; +Cc: Tom Tromey, Eli Zaretskii

Hi Sergio,

LGTM, with some nits.

On 2017-12-11 02:57 PM, Sergio Durigan Junior wrote:
> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
> index f3c3e7d706..46c814fae8 100644
> --- a/gdb/c-typeprint.c
> +++ b/gdb/c-typeprint.c
> @@ -875,6 +875,458 @@ output_access_specifier (struct ui_file *stream,
>    return last_access;
>  }
>  
> +/* Return true is an access label (i.e., "public:", "private:",
> +   "protected:") needs to be printed for TYPE.  */
> +
> +static bool
> +need_access_label_p (struct type *type)
> +{
> +  bool need_access_label = false;
> +  int i, j;
> +  int len, len2;
> +
> +  if (TYPE_DECLARED_CLASS (type))
> +    {
> +      QUIT;
> +      len = TYPE_NFIELDS (type);
> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> +	if (!TYPE_FIELD_PRIVATE (type, i))
> +	  {
> +	    need_access_label = true;
> +	    break;
> +	  }
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  len2 = TYPE_NFN_FIELDS (type);
> +	  for (j = 0; j < len2; j++)
> +	    {
> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> +	      for (i = 0; i < len; i++)
> +		if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> +								j), i))
> +		  {
> +		    need_access_label = true;
> +		    break;
> +		  }
> +	      if (need_access_label)
> +		break;
> +	    }
> +	}
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> +	    {
> +	      if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> +		{
> +		  need_access_label = true;
> +		  break;
> +		}
> +	    }
> +	}
> +    }
> +  else
> +    {
> +      QUIT;
> +      len = TYPE_NFIELDS (type);
> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
> +	if (TYPE_FIELD_PRIVATE (type, i)
> +	    || TYPE_FIELD_PROTECTED (type, i))
> +	  {
> +	    need_access_label = true;
> +	    break;
> +	  }
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  len2 = TYPE_NFN_FIELDS (type);
> +	  for (j = 0; j < len2; j++)
> +	    {
> +	      QUIT;
> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
> +	      for (i = 0; i < len; i++)
> +		if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
> +								 j), i)
> +		    || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
> +								  j),
> +					      i))
> +		  {
> +		    need_access_label = true;
> +		    break;
> +		  }
> +	      if (need_access_label)
> +		break;
> +	    }
> +	}
> +      QUIT;
> +      if (!need_access_label)
> +	{
> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
> +	    {
> +	      if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
> +		  || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
> +		{
> +		  need_access_label = true;
> +		  break;
> +		}
> +	    }
> +	}
> +    }
> +
> +  return need_access_label;

It seems to me that you could return immediately when you detect that a label is
needed.  It would allow removing a bunch of if (!need_access_label).

> @@ -898,10 +1350,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>  		   int show, int level, const struct type_print_options *flags)
>  {
>    int i;
> -  int len, real_len;
> -  enum access_specifier section_type;
> -  int need_access_label = 0;
> -  int j, len2;
> +  int len;
> +  int j;

j is unused.

Thanks,

Simon

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-11 20:45       ` Simon Marchi
@ 2017-12-11 21:07         ` Sergio Durigan Junior
  2017-12-11 22:42           ` Pedro Alves
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 21:07 UTC (permalink / raw)
  To: Simon Marchi; +Cc: GDB Patches, Tom Tromey, Eli Zaretskii

On Monday, December 11 2017, Simon Marchi wrote:

>>>> +/* Use 'print_spaces_filtered', but take into consideration the
>>>> +   type_print_options FLAGS in order to determine how many whitespaces
>>>> +   will be printed.  */
>>>> +
>>>> +static void
>>>> +print_spaces_filtered_with_print_options (int level, struct ui_file *stream,
>>>> +					const struct type_print_options *flags)
>>>
>>> Missing spaces here.
>> 
>> This is actually on purpose.  If I indent the line, it will have more
>> than 80 chars.  I believe this is a well known method for avoiding this
>> problem...?
>
> I am not aware of that.  In this case I would put the parameter list on the next,
> I'm not sure if it's 100% GNU-style approved, but nobody complained when I did it
> so far :)
>
> static void
> print_spaces_filtered_with_print_options
>   (int level, struct ui_file *stream, const struct type_print_options *flags);
>
> It helps with long function names.  In this case, I would probably just drop the
> "struct" to save a few chars, because C++.

Fair enough.  I use this trick for function prototypes, but not for the
definitions.

>>>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
>>>> +	  { debug c++ optimize=-O0 }] } {
>>>
>>> optimize=-O0 seems unnecessary to me, I've never seen it specified explicitly in a test.
>> 
>> There are very few tests that use it (2, currently).  I understand it's
>> not very common to explicitly specify -O0, but I put it there because I
>> decided to be on the safe side.  If the compiler performs any
>> optimization at all, it could mess with the layout of the structs.
>
> If GCC decided to optimize by default, so many things would break in the GDB testsuite.
> We would then probably make gdb_compile add -O0, so that we wouldn't need to do it in
> all tests.  The point is that IMO, tests should expect no optimization by default.

OK.

>>> I also noticed that the offset is not shown in front of the struct-in-union,
>>> as show above, but it is in the case of struct-in-struct:
>>>
>>>   /* offset    |  size */
>>>   type = struct my_struct_3 {
>>>   /*    0      |     8 */    struct my_struct_1 {
>>>   /*    0      |     4 */        int a;
>>>   /*    4      |     4 */        int b;
>>>                              } /* total size:    8 bytes */ s1;
>>>   /*    8      |     8 */    struct my_struct_2 {
>>>   /*    8      |     4 */        int c;
>>>   /*   12      |     4 */        int d;
>>>                              } /* total size:    8 bytes */ s2;
>>>   } /* total size:   16 bytes */
>>>
>>> Is this difference on purpose?
>> 
>> Yes; offsets are not shown for fields inside unions (not only structs,
>> but all types of fields), because it doesn't make much sense: they'd be
>> 0 every time.  This is also inspired from pahole's output.
>
> Not if that union is itself in a struct.  For example with this:
>
> struct hello
> {
>         int i;
>         union {
>                 struct {
>                         int x, y;
>                 } a;
>                 struct {
>                         int x, y;
>                 } b;
>         };
> };
>
> (gdb) ptype /o struct hello
> /* offset    |  size */
> type = struct hello {
> /*    0      |     4 */    int i;
> /*    4      |     8 */    union {
> /*                 8 */        struct {
> /*    4      |     4 */            int x;
> /*    8      |     4 */            int y;
>                                } /* total size:    8 bytes */ a;
> /*                 8 */        struct {
> /*    4      |     4 */            int x;
> /*    8      |     4 */            int y;
>                                } /* total size:    8 bytes */ b;
>                            } /* total size:    8 bytes */;
> } /* total size:   12 bytes */
>
>
> But I don't mind it, it just stuck out as a little inconsistency.

I don't see the inconsistency.

If a field is inside a struct, it has its offset *and* size printed.  No
matter if the field is an int, another struct, or an union.

If a field is inside an union, it has only its size printed.

In the case above, it makes sense to have the offsets printed for the
fields inside the two structs (inside the union), because there might be
holes to report (well, one can argue that it doesn't matter if there are
holes or not in this case, because if the other struct is bigger then
the union size will stay the same).  However, it doesn't make sense to
print the offsets for the two structs themselves, because they are
members of the union.

I hope it makes more sense now.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 2/2] Implement pahole-like 'ptype /o' option
  2017-12-11 19:58   ` [PATCH v3 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-12-11 21:50     ` Simon Marchi
  2017-12-11 23:24       ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Simon Marchi @ 2017-12-11 21:50 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches; +Cc: Tom Tromey, Eli Zaretskii

Hi Sergio,

LGTM, with some nits.

On 2017-12-11 02:57 PM, Sergio Durigan Junior wrote:
> @@ -867,14 +890,86 @@ output_access_specifier (struct ui_file *stream,
>        if (last_access != s_public)
>  	{
>  	  last_access = s_public;
> -	  fprintfi_filtered (level + 2, stream,
> -			     "public:\n");
> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
> +	  fprintf_filtered (stream, "public:\n");
>  	}
>      }
>  
>    return last_access;
>  }
>  
> +/* Print information about the offset of TYPE inside its union.
> +   FIELD_IDX represents the index of this TYPE inside the union.  We
> +   just print the type size, and nothing more.
> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
> +				 struct ui_file *stream)
> +{
> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +
> +  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
> +}
> +
> +/* Print information about the offset of TYPE inside its struct.
> +   FIELD_IDX represents the index of this TYPE inside the struct, and
> +   ENDPOS is the end position of the previous type (this is how we
> +   calculate whether there are holes in the struct).  At the end,
> +   ENDPOS is updated.

The wording confuses me a bit.  TYPE is not inside a struct; the struct
contains fields, not types.  And TYPE is the struct type IIUC, not the
field type.  So it should maybe be something like:

  Print information about field at index FIELD_IDX of the struct type TYPE.
  ENDPOS is the one-past-the-end bit position of the previous field (where
  we expect this field to be if there is no hole).  At the end, ENDPOS is
  updated to the one-past-the-end bit position of the current field.

What does OFFSET_BITPOS do?

> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
> +				  unsigned int *endpos, struct ui_file *stream,
> +				  unsigned int offset_bitpos)
> +{
> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
> +  unsigned int fieldsize_bit;
> +
> +  if (*endpos > 0 && *endpos < bitpos)

Why do you check for *endpos > 0?  Did you see a case where *endpos is 0
and bitpos > 0?  That would mean that there's a "hole" before the first field.
Would we want to show it as a hole anyway?

Simon

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-11 21:07         ` Sergio Durigan Junior
@ 2017-12-11 22:42           ` Pedro Alves
  2017-12-11 22:50             ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-11 22:42 UTC (permalink / raw)
  To: Sergio Durigan Junior, Simon Marchi
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii

On 12/11/2017 09:07 PM, Sergio Durigan Junior wrote:

>>> This is actually on purpose.  If I indent the line, it will have more
>>> than 80 chars.  I believe this is a well known method for avoiding this
>>> problem...?
>>
>> I am not aware of that.  In this case I would put the parameter list on the next,
>> I'm not sure if it's 100% GNU-style approved, but nobody complained when I did it
>> so far :)
>>
>> static void
>> print_spaces_filtered_with_print_options
>>   (int level, struct ui_file *stream, const struct type_print_options *flags);
>>
>> It helps with long function names.  In this case, I would probably just drop the
>> "struct" to save a few chars, because C++.
> 
> Fair enough.  I use this trick for function prototypes, but not for the
> definitions.

Simon's format is what I've been using for a long while too.

>> But I don't mind it, it just stuck out as a little inconsistency.
> 
> I don't see the inconsistency.
> 
> If a field is inside a struct, it has its offset *and* size printed.  No
> matter if the field is an int, another struct, or an union.
> 
> If a field is inside an union, it has only its size printed.
> 
> In the case above, it makes sense to have the offsets printed for the
> fields inside the two structs (inside the union), because there might be
> holes to report (well, one can argue that it doesn't matter if there are
> holes or not in this case, because if the other struct is bigger then
> the union size will stay the same).  However, it doesn't make sense to
> print the offsets for the two structs themselves, because they are
> members of the union.
> 
> I hope it makes more sense now.

But why do we need the special case?  Does it help anything?
So far, it seems it only added confusion.

The option is "/o" for "print offsets".  Why not print offsets always?

BTW, shouldn't the documentation in the manual include an example
of GDB's output?

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-11 22:42           ` Pedro Alves
@ 2017-12-11 22:50             ` Sergio Durigan Junior
  2017-12-11 23:46               ` Pedro Alves
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 22:50 UTC (permalink / raw)
  To: Pedro Alves; +Cc: Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

On Monday, December 11 2017, Pedro Alves wrote:

> On 12/11/2017 09:07 PM, Sergio Durigan Junior wrote:
>
>>>> This is actually on purpose.  If I indent the line, it will have more
>>>> than 80 chars.  I believe this is a well known method for avoiding this
>>>> problem...?
>>>
>>> I am not aware of that.  In this case I would put the parameter list on the next,
>>> I'm not sure if it's 100% GNU-style approved, but nobody complained when I did it
>>> so far :)
>>>
>>> static void
>>> print_spaces_filtered_with_print_options
>>>   (int level, struct ui_file *stream, const struct type_print_options *flags);
>>>
>>> It helps with long function names.  In this case, I would probably just drop the
>>> "struct" to save a few chars, because C++.
>> 
>> Fair enough.  I use this trick for function prototypes, but not for the
>> definitions.
>
> Simon's format is what I've been using for a long while too.

Well, I could post a few examples of the format I chose, but I'm pretty
sure this would be worthless.  As I said, I will change my code.

>>> But I don't mind it, it just stuck out as a little inconsistency.
>> 
>> I don't see the inconsistency.
>> 
>> If a field is inside a struct, it has its offset *and* size printed.  No
>> matter if the field is an int, another struct, or an union.
>> 
>> If a field is inside an union, it has only its size printed.
>> 
>> In the case above, it makes sense to have the offsets printed for the
>> fields inside the two structs (inside the union), because there might be
>> holes to report (well, one can argue that it doesn't matter if there are
>> holes or not in this case, because if the other struct is bigger then
>> the union size will stay the same).  However, it doesn't make sense to
>> print the offsets for the two structs themselves, because they are
>> members of the union.
>> 
>> I hope it makes more sense now.
>
> But why do we need the special case?  Does it help anything?
> So far, it seems it only added confusion.

What do you mean by "special case"?

This is what pahole does, and as I've said a few times, the output of
'ptype /o' has been based on pahole's output.  I don't consider this a
special case; I consider it to be the natural thing to do, because
offsets don't make much sense in unions.

> The option is "/o" for "print offsets".  Why not print offsets always?

I hope I explained it above.

> BTW, shouldn't the documentation in the manual include an example
> of GDB's output?

I can include an example, OK.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base'
  2017-12-11 20:55     ` Simon Marchi
@ 2017-12-11 23:05       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 23:05 UTC (permalink / raw)
  To: Simon Marchi; +Cc: GDB Patches, Tom Tromey, Eli Zaretskii

On Monday, December 11 2017, Simon Marchi wrote:

> Hi Sergio,
>
> LGTM, with some nits.

Thanks for the review.

> On 2017-12-11 02:57 PM, Sergio Durigan Junior wrote:
>> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
>> index f3c3e7d706..46c814fae8 100644
>> --- a/gdb/c-typeprint.c
>> +++ b/gdb/c-typeprint.c
>> @@ -875,6 +875,458 @@ output_access_specifier (struct ui_file *stream,
>>    return last_access;
>>  }
>>  
>> +/* Return true is an access label (i.e., "public:", "private:",
>> +   "protected:") needs to be printed for TYPE.  */
>> +
>> +static bool
>> +need_access_label_p (struct type *type)
>> +{
>> +  bool need_access_label = false;
>> +  int i, j;
>> +  int len, len2;
>> +
>> +  if (TYPE_DECLARED_CLASS (type))
>> +    {
>> +      QUIT;
>> +      len = TYPE_NFIELDS (type);
>> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> +	if (!TYPE_FIELD_PRIVATE (type, i))
>> +	  {
>> +	    need_access_label = true;
>> +	    break;
>> +	  }
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  len2 = TYPE_NFN_FIELDS (type);
>> +	  for (j = 0; j < len2; j++)
>> +	    {
>> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> +	      for (i = 0; i < len; i++)
>> +		if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> +								j), i))
>> +		  {
>> +		    need_access_label = true;
>> +		    break;
>> +		  }
>> +	      if (need_access_label)
>> +		break;
>> +	    }
>> +	}
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> +	    {
>> +	      if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> +		{
>> +		  need_access_label = true;
>> +		  break;
>> +		}
>> +	    }
>> +	}
>> +    }
>> +  else
>> +    {
>> +      QUIT;
>> +      len = TYPE_NFIELDS (type);
>> +      for (i = TYPE_N_BASECLASSES (type); i < len; i++)
>> +	if (TYPE_FIELD_PRIVATE (type, i)
>> +	    || TYPE_FIELD_PROTECTED (type, i))
>> +	  {
>> +	    need_access_label = true;
>> +	    break;
>> +	  }
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  len2 = TYPE_NFN_FIELDS (type);
>> +	  for (j = 0; j < len2; j++)
>> +	    {
>> +	      QUIT;
>> +	      len = TYPE_FN_FIELDLIST_LENGTH (type, j);
>> +	      for (i = 0; i < len; i++)
>> +		if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
>> +								 j), i)
>> +		    || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
>> +								  j),
>> +					      i))
>> +		  {
>> +		    need_access_label = true;
>> +		    break;
>> +		  }
>> +	      if (need_access_label)
>> +		break;
>> +	    }
>> +	}
>> +      QUIT;
>> +      if (!need_access_label)
>> +	{
>> +	  for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
>> +	    {
>> +	      if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
>> +		  || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
>> +		{
>> +		  need_access_label = true;
>> +		  break;
>> +		}
>> +	    }
>> +	}
>> +    }
>> +
>> +  return need_access_label;
>
> It seems to me that you could return immediately when you detect that a label is
> needed.  It would allow removing a bunch of if (!need_access_label).

Done.

>> @@ -898,10 +1350,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
>>  		   int show, int level, const struct type_print_options *flags)
>>  {
>>    int i;
>> -  int len, real_len;
>> -  enum access_specifier section_type;
>> -  int need_access_label = 0;
>> -  int j, len2;
>> +  int len;
>> +  int j;
>
> j is unused.

Removed.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 2/2] Implement pahole-like 'ptype /o' option
  2017-12-11 21:50     ` Simon Marchi
@ 2017-12-11 23:24       ` Sergio Durigan Junior
  2017-12-12  1:32         ` Simon Marchi
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 23:24 UTC (permalink / raw)
  To: Simon Marchi; +Cc: GDB Patches, Tom Tromey, Eli Zaretskii

On Monday, December 11 2017, Simon Marchi wrote:

> Hi Sergio,
>
> LGTM, with some nits.
>
> On 2017-12-11 02:57 PM, Sergio Durigan Junior wrote:
>> @@ -867,14 +890,86 @@ output_access_specifier (struct ui_file *stream,
>>        if (last_access != s_public)
>>  	{
>>  	  last_access = s_public;
>> -	  fprintfi_filtered (level + 2, stream,
>> -			     "public:\n");
>> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
>> +	  fprintf_filtered (stream, "public:\n");
>>  	}
>>      }
>>  
>>    return last_access;
>>  }
>>  
>> +/* Print information about the offset of TYPE inside its union.
>> +   FIELD_IDX represents the index of this TYPE inside the union.  We
>> +   just print the type size, and nothing more.
>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
>> +				 struct ui_file *stream)
>> +{
>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
>> +
>> +  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
>> +}
>> +
>> +/* Print information about the offset of TYPE inside its struct.
>> +   FIELD_IDX represents the index of this TYPE inside the struct, and
>> +   ENDPOS is the end position of the previous type (this is how we
>> +   calculate whether there are holes in the struct).  At the end,
>> +   ENDPOS is updated.
>
> The wording confuses me a bit.  TYPE is not inside a struct; the struct
> contains fields, not types.  And TYPE is the struct type IIUC, not the
> field type.  So it should maybe be something like:
>
>   Print information about field at index FIELD_IDX of the struct type TYPE.
>   ENDPOS is the one-past-the-end bit position of the previous field (where
>   we expect this field to be if there is no hole).  At the end, ENDPOS is
>   updated to the one-past-the-end bit position of the current field.

Thanks, I adopted your version.

> What does OFFSET_BITPOS do?

Ah, I forgot to include it in the comment.  It is the offset value we
carry over when we are printing an inner struct.  This is needed because
otherwise we'd print inner structs starting at position 0, which is
something that Tom didn't like and suggested changing.

>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
>> +				  unsigned int *endpos, struct ui_file *stream,
>> +				  unsigned int offset_bitpos)
>> +{
>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
>> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
>> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
>> +  unsigned int fieldsize_bit;
>> +
>> +  if (*endpos > 0 && *endpos < bitpos)
>
> Why do you check for *endpos > 0?  Did you see a case where *endpos is 0
> and bitpos > 0?  That would mean that there's a "hole" before the first field.
> Would we want to show it as a hole anyway?

Yeah, this situation happens when we have a virtual method in a class.
Because of the vtable, the first field of the struct will start at
offset 8 (for 64-bit architectures), and in this case *endpos will be 0
because we won't have updated it, leading to a confusing message about a
8-byte hole in the beginning of the struct:

 ...
 50 /* offset    |  size */
 51 type = struct abc {
 52                          public:
 53 /* XXX  8-byte hole  */
 54 /*    8      |     8 */    void *field1;
 ...

In order to suppress this first message, I check for *endpos > 0.

I will add a comment to the code explaining this scenario.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v4 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg)
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
                   ` (4 preceding siblings ...)
  2017-12-11 19:58 ` [PATCH v3 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-11 23:43 ` Sergio Durigan Junior
  2017-12-11 23:44   ` [PATCH v4 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
  2017-12-11 23:44   ` [PATCH v4 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-13  3:17 ` [PATCH v5 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
                   ` (2 subsequent siblings)
  8 siblings, 2 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 23:43 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	André Pönitz, Keith Seitz

Changes from v3:

- Simplified need_access_label_p.

- Fixed a few typos.

- Improved comments on c_print_type_struct_field_offset and
  c_print_type_union_field_offset.

- Removed unused variable.

- Added @smallexample to docs.


Eli had approved the documentation bits for the previous patch, but
since this one had an addition (the inclusion of an @smallexample
showing the output of 'ptype /o'), I believe it'd be good if he could
review it again.

Thanks,

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v4 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base'
  2017-12-11 23:43 ` [PATCH v4 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-11 23:44   ` Sergio Durigan Junior
  2017-12-11 23:44   ` [PATCH v4 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  1 sibling, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 23:44 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	André Pönitz, Keith Seitz, Sergio Durigan Junior

While doing the 'ptype /o' work, I noticed that 'c_type_print_base'
was very long, with a big amount of code just to handle the case of
TYPE_CODE_{STRUCT,UNION}.  This made working with the function a bit
difficult, specially because of the level of indentation.

This commit moves this part of the code to their own functions.  Now
we have a 'c_type_print_base_struct_union' with most of the code, and
also 'need_access_label_p', which is a subset of the code that was
also a good candidate for having its own function.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* c-typeprint.c (need_access_label_p): New function.
	(c_type_print_base_struct_union): New function.
	(c_type_print_base): Move code to handle
	TYPE_CODE_{STRUCT,UNION} to the functions mentioned above.
---
 gdb/c-typeprint.c | 838 ++++++++++++++++++++++++++----------------------------
 1 file changed, 404 insertions(+), 434 deletions(-)

diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index f3c3e7d706..ce10b5e005 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -875,6 +875,407 @@ output_access_specifier (struct ui_file *stream,
   return last_access;
 }
 
+/* Return true is an access label (i.e., "public:", "private:",
+   "protected:") needs to be printed for TYPE.  */
+
+static bool
+need_access_label_p (struct type *type)
+{
+  if (TYPE_DECLARED_CLASS (type))
+    {
+      QUIT;
+      for (int i = TYPE_N_BASECLASSES (type); i < TYPE_NFIELDS (type); i++)
+	if (!TYPE_FIELD_PRIVATE (type, i))
+	  return true;
+      QUIT;
+      for (int j = 0; j < TYPE_NFN_FIELDS (type); j++)
+	for (int i = 0; i < TYPE_FN_FIELDLIST_LENGTH (type, j); i++)
+	  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+							  j), i))
+	    return true;
+      QUIT;
+      for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+	  return true;
+    }
+  else
+    {
+      QUIT;
+      for (int i = TYPE_N_BASECLASSES (type); i < TYPE_NFIELDS (type); i++)
+	if (TYPE_FIELD_PRIVATE (type, i) || TYPE_FIELD_PROTECTED (type, i))
+	  return true;
+      QUIT;
+      for (int j = 0; j < TYPE_NFN_FIELDS (type); j++)
+	{
+	  QUIT;
+	  for (int i = 0; i < TYPE_FN_FIELDLIST_LENGTH (type, j); i++)
+	    if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
+							     j), i)
+		|| TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+							      j),
+					  i))
+	      return true;
+	}
+      QUIT;
+      for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
+	    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+	  return true;
+    }
+
+  return false;
+}
+
+/* Helper for 'c_type_print_base' that handles structs and unions.
+   For a description of the arguments, see 'c_type_print_base'.  */
+
+static void
+c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
+				int show, int level,
+				const struct type_print_options *flags)
+{
+  struct type_print_options local_flags = *flags;
+  struct type_print_options semi_local_flags = *flags;
+  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
+
+  local_flags.local_typedefs = NULL;
+  semi_local_flags.local_typedefs = NULL;
+
+  if (!flags->raw)
+    {
+      if (flags->local_typedefs)
+	local_flags.local_typedefs
+	  = copy_typedef_hash (flags->local_typedefs);
+      else
+	local_flags.local_typedefs = create_typedef_hash ();
+
+      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
+    }
+
+  c_type_print_modifier (type, stream, 0, 1);
+  if (TYPE_CODE (type) == TYPE_CODE_UNION)
+    fprintf_filtered (stream, "union ");
+  else if (TYPE_DECLARED_CLASS (type))
+    fprintf_filtered (stream, "class ");
+  else
+    fprintf_filtered (stream, "struct ");
+
+  /* Print the tag if it exists.  The HP aCC compiler emits a
+     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
+     enum}" tag for unnamed struct/union/enum's, which we don't
+     want to print.  */
+  if (TYPE_TAG_NAME (type) != NULL
+      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
+    {
+      /* When printing the tag name, we are still effectively
+	 printing in the outer context, hence the use of FLAGS
+	 here.  */
+      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
+      if (show > 0)
+	fputs_filtered (" ", stream);
+    }
+
+  if (show < 0)
+    {
+      /* If we just printed a tag name, no need to print anything
+	 else.  */
+      if (TYPE_TAG_NAME (type) == NULL)
+	fprintf_filtered (stream, "{...}");
+    }
+  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
+    {
+      struct type *basetype;
+      int vptr_fieldno;
+
+      c_type_print_template_args (&local_flags, type, stream);
+
+      /* Add in template parameters when printing derivation info.  */
+      add_template_parameters (local_flags.local_typedefs, type);
+      cp_type_print_derivation_info (stream, type, &local_flags);
+
+      /* This holds just the global typedefs and the template
+	 parameters.  */
+      semi_local_flags.local_typedefs
+	= copy_typedef_hash (local_flags.local_typedefs);
+      if (semi_local_flags.local_typedefs)
+	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
+
+      /* Now add in the local typedefs.  */
+      recursively_update_typedef_hash (local_flags.local_typedefs, type);
+
+      fprintf_filtered (stream, "{\n");
+      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
+	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
+	{
+	  if (TYPE_STUB (type))
+	    fprintfi_filtered (level + 4, stream,
+			       _("<incomplete type>\n"));
+	  else
+	    fprintfi_filtered (level + 4, stream,
+			       _("<no data fields>\n"));
+	}
+
+      /* Start off with no specific section type, so we can print
+	 one for the first field we find, and use that section type
+	 thereafter until we find another type.  */
+
+      enum access_specifier section_type = s_none;
+
+      /* For a class, if all members are private, there's no need
+	 for a "private:" label; similarly, for a struct or union
+	 masquerading as a class, if all members are public, there's
+	 no need for a "public:" label.  */
+      bool need_access_label = need_access_label_p (type);
+
+      /* If there is a base class for this type,
+	 do not print the field that it occupies.  */
+
+      int len = TYPE_NFIELDS (type);
+      vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
+	{
+	  QUIT;
+
+	  /* If we have a virtual table pointer, omit it.  Even if
+	     virtual table pointers are not specifically marked in
+	     the debug info, they should be artificial.  */
+	  if ((i == vptr_fieldno && type == basetype)
+	      || TYPE_FIELD_ARTIFICIAL (type, i))
+	    continue;
+
+	  if (need_access_label)
+	    {
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FIELD_PROTECTED (type, i),
+		 TYPE_FIELD_PRIVATE (type, i));
+	    }
+
+	  print_spaces_filtered (level + 4, stream);
+	  if (field_is_static (&TYPE_FIELD (type, i)))
+	    fprintf_filtered (stream, "static ");
+	  c_print_type (TYPE_FIELD_TYPE (type, i),
+			TYPE_FIELD_NAME (type, i),
+			stream, show - 1, level + 4,
+			&local_flags);
+	  if (!field_is_static (&TYPE_FIELD (type, i))
+	      && TYPE_FIELD_PACKED (type, i))
+	    {
+	      /* It is a bitfield.  This code does not attempt
+		 to look at the bitpos and reconstruct filler,
+		 unnamed fields.  This would lead to misleading
+		 results if the compiler does not put out fields
+		 for such things (I don't know what it does).  */
+	      fprintf_filtered (stream, " : %d",
+				TYPE_FIELD_BITSIZE (type, i));
+	    }
+	  fprintf_filtered (stream, ";\n");
+	}
+
+      /* If there are both fields and methods, put a blank line
+	 between them.  Make sure to count only method that we
+	 will display; artificial methods will be hidden.  */
+      len = TYPE_NFN_FIELDS (type);
+      if (!flags->print_methods)
+	len = 0;
+      int real_len = 0;
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  int j;
+
+	  for (j = 0; j < len2; j++)
+	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
+	      real_len++;
+	}
+      if (real_len > 0 && section_type != s_none)
+	fprintf_filtered (stream, "\n");
+
+      /* C++: print out the methods.  */
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
+	  const char *name = type_name_no_tag (type);
+	  int is_constructor = name && strcmp (method_name,
+					       name) == 0;
+
+	  for (j = 0; j < len2; j++)
+	    {
+	      const char *mangled_name;
+	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
+	      char *demangled_name;
+	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
+	      int is_full_physname_constructor =
+		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
+		|| is_constructor_name (physname)
+		|| is_destructor_name (physname)
+		|| method_name[0] == '~';
+
+	      /* Do not print out artificial methods.  */
+	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
+		continue;
+
+	      QUIT;
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FN_FIELD_PROTECTED (f, j),
+		 TYPE_FN_FIELD_PRIVATE (f, j));
+
+	      print_spaces_filtered (level + 4, stream);
+	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
+		fprintf_filtered (stream, "virtual ");
+	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
+		fprintf_filtered (stream, "static ");
+	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
+		{
+		  /* Keep GDB from crashing here.  */
+		  fprintf_filtered (stream,
+				    _("<undefined type> %s;\n"),
+				    TYPE_FN_FIELD_PHYSNAME (f, j));
+		  break;
+		}
+	      else if (!is_constructor	/* Constructors don't
+					   have declared
+					   types.  */
+		       && !is_full_physname_constructor  /* " " */
+		       && !is_type_conversion_operator (type, i, j))
+		{
+		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+				"", stream, -1, 0,
+				&local_flags);
+		  fputs_filtered (" ", stream);
+		}
+	      if (TYPE_FN_FIELD_STUB (f, j))
+		{
+		  /* Build something we can demangle.  */
+		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
+		  mangled_name = mangled_name_holder.get ();
+		}
+	      else
+		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
+
+	      demangled_name =
+		gdb_demangle (mangled_name,
+			      DMGL_ANSI | DMGL_PARAMS);
+	      if (demangled_name == NULL)
+		{
+		  /* In some cases (for instance with the HP
+		     demangling), if a function has more than 10
+		     arguments, the demangling will fail.
+		     Let's try to reconstruct the function
+		     signature from the symbol information.  */
+		  if (!TYPE_FN_FIELD_STUB (f, j))
+		    {
+		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
+		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
+
+		      cp_type_print_method_args (mtype,
+						 "",
+						 method_name,
+						 staticp,
+						 stream, &local_flags);
+		    }
+		  else
+		    fprintf_filtered (stream,
+				      _("<badly mangled name '%s'>"),
+				      mangled_name);
+		}
+	      else
+		{
+		  char *p;
+		  char *demangled_no_class
+		    = remove_qualifiers (demangled_name);
+
+		  /* Get rid of the `static' appended by the
+		     demangler.  */
+		  p = strstr (demangled_no_class, " static");
+		  if (p != NULL)
+		    {
+		      int length = p - demangled_no_class;
+		      char *demangled_no_static;
+
+		      demangled_no_static
+			= (char *) xmalloc (length + 1);
+		      strncpy (demangled_no_static,
+			       demangled_no_class, length);
+		      *(demangled_no_static + length) = '\0';
+		      fputs_filtered (demangled_no_static, stream);
+		      xfree (demangled_no_static);
+		    }
+		  else
+		    fputs_filtered (demangled_no_class, stream);
+		  xfree (demangled_name);
+		}
+
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print out nested types.  */
+      if (TYPE_NESTED_TYPES_COUNT (type) != 0
+	  && semi_local_flags.print_nested_type_limit != 0)
+	{
+	  if (semi_local_flags.print_nested_type_limit > 0)
+	    --semi_local_flags.print_nested_type_limit;
+
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
+	    {
+	      print_spaces_filtered (level + 4, stream);
+	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+			    "", stream, show, level + 4, &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print typedefs defined in this class.  */
+
+      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
+	{
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
+	      || TYPE_NESTED_TYPES_COUNT (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
+	    {
+	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
+
+	      /* Dereference the typedef declaration itself.  */
+	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
+	      target = TYPE_TARGET_TYPE (target);
+
+	      if (need_access_label)
+		{
+		  section_type = output_access_specifier
+		    (stream, section_type, level,
+		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		}
+	      print_spaces_filtered (level + 4, stream);
+	      fprintf_filtered (stream, "typedef ");
+
+	      /* We want to print typedefs with substitutions
+		 from the template parameters or globally-known
+		 typedefs but not local typedefs.  */
+	      c_print_type (target,
+			    TYPE_TYPEDEF_FIELD_NAME (type, i),
+			    stream, show - 1, level + 4,
+			    &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      fprintfi_filtered (level, stream, "}");
+    }
+
+  do_cleanups (local_cleanups);
+}
+
 /* Print the name of the type (or the ultimate pointer target,
    function value or array element), or the description of a structure
    or union.
@@ -898,10 +1299,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		   int show, int level, const struct type_print_options *flags)
 {
   int i;
-  int len, real_len;
-  enum access_specifier section_type;
-  int need_access_label = 0;
-  int j, len2;
+  int len;
+  int j;
 
   QUIT;
 
@@ -958,436 +1357,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      {
-	struct type_print_options local_flags = *flags;
-	struct type_print_options semi_local_flags = *flags;
-	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
-
-	local_flags.local_typedefs = NULL;
-	semi_local_flags.local_typedefs = NULL;
-
-	if (!flags->raw)
-	  {
-	    if (flags->local_typedefs)
-	      local_flags.local_typedefs
-		= copy_typedef_hash (flags->local_typedefs);
-	    else
-	      local_flags.local_typedefs = create_typedef_hash ();
-
-	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
-	  }
-
-	c_type_print_modifier (type, stream, 0, 1);
-	if (TYPE_CODE (type) == TYPE_CODE_UNION)
-	  fprintf_filtered (stream, "union ");
-	else if (TYPE_DECLARED_CLASS (type))
-	  fprintf_filtered (stream, "class ");
-	else
-	  fprintf_filtered (stream, "struct ");
-
-	/* Print the tag if it exists.  The HP aCC compiler emits a
-	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
-	   enum}" tag for unnamed struct/union/enum's, which we don't
-	   want to print.  */
-	if (TYPE_TAG_NAME (type) != NULL
-	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
-	  {
-	    /* When printing the tag name, we are still effectively
-	       printing in the outer context, hence the use of FLAGS
-	       here.  */
-	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
-	    if (show > 0)
-	      fputs_filtered (" ", stream);
-	  }
-
-	if (show < 0)
-	  {
-	    /* If we just printed a tag name, no need to print anything
-	       else.  */
-	    if (TYPE_TAG_NAME (type) == NULL)
-	      fprintf_filtered (stream, "{...}");
-	  }
-	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
-	  {
-	    struct type *basetype;
-	    int vptr_fieldno;
-
-	    c_type_print_template_args (&local_flags, type, stream);
-
-	    /* Add in template parameters when printing derivation info.  */
-	    add_template_parameters (local_flags.local_typedefs, type);
-	    cp_type_print_derivation_info (stream, type, &local_flags);
-
-	    /* This holds just the global typedefs and the template
-	       parameters.  */
-	    semi_local_flags.local_typedefs
-	      = copy_typedef_hash (local_flags.local_typedefs);
-	    if (semi_local_flags.local_typedefs)
-	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
-
-	    /* Now add in the local typedefs.  */
-	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
-
-	    fprintf_filtered (stream, "{\n");
-	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
-		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
-	      {
-		if (TYPE_STUB (type))
-		  fprintfi_filtered (level + 4, stream,
-				     _("<incomplete type>\n"));
-		else
-		  fprintfi_filtered (level + 4, stream,
-				     _("<no data fields>\n"));
-	      }
-
-	    /* Start off with no specific section type, so we can print
-	       one for the first field we find, and use that section type
-	       thereafter until we find another type.  */
-
-	    section_type = s_none;
-
-	    /* For a class, if all members are private, there's no need
-	       for a "private:" label; similarly, for a struct or union
-	       masquerading as a class, if all members are public, there's
-	       no need for a "public:" label.  */
-
-	    if (TYPE_DECLARED_CLASS (type))
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (!TYPE_FIELD_PRIVATE (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									  j), i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-	    else
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (TYPE_FIELD_PRIVATE (type, i)
-		      || TYPE_FIELD_PROTECTED (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			QUIT;
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
-									   j), i)
-			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									    j),
-							i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
-			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-
-	    /* If there is a base class for this type,
-	       do not print the field that it occupies.  */
-
-	    len = TYPE_NFIELDS (type);
-	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
-	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-	      {
-		QUIT;
-
-		/* If we have a virtual table pointer, omit it.  Even if
-		   virtual table pointers are not specifically marked in
-		   the debug info, they should be artificial.  */
-		if ((i == vptr_fieldno && type == basetype)
-		    || TYPE_FIELD_ARTIFICIAL (type, i))
-		  continue;
-
-		if (need_access_label)
-		  {
-		    section_type = output_access_specifier
-		      (stream, section_type, level,
-		       TYPE_FIELD_PROTECTED (type, i),
-		       TYPE_FIELD_PRIVATE (type, i));
-		  }
-
-		print_spaces_filtered (level + 4, stream);
-		if (field_is_static (&TYPE_FIELD (type, i)))
-		  fprintf_filtered (stream, "static ");
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show - 1, level + 4,
-			      &local_flags);
-		if (!field_is_static (&TYPE_FIELD (type, i))
-		    && TYPE_FIELD_PACKED (type, i))
-		  {
-		    /* It is a bitfield.  This code does not attempt
-		       to look at the bitpos and reconstruct filler,
-		       unnamed fields.  This would lead to misleading
-		       results if the compiler does not put out fields
-		       for such things (I don't know what it does).  */
-		    fprintf_filtered (stream, " : %d",
-				      TYPE_FIELD_BITSIZE (type, i));
-		  }
-		fprintf_filtered (stream, ";\n");
-	      }
-
-	  /* If there are both fields and methods, put a blank line
-	     between them.  Make sure to count only method that we
-	     will display; artificial methods will be hidden.  */
-	  len = TYPE_NFN_FIELDS (type);
-	  if (!flags->print_methods)
-	    len = 0;
-	  real_len = 0;
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      int j;
-
-	      for (j = 0; j < len2; j++)
-		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		  real_len++;
-	    }
-	  if (real_len > 0 && section_type != s_none)
-	    fprintf_filtered (stream, "\n");
-
-	  /* C++: print out the methods.  */
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
-	      const char *name = type_name_no_tag (type);
-	      int is_constructor = name && strcmp (method_name,
-						   name) == 0;
-
-	      for (j = 0; j < len2; j++)
-		{
-		  const char *mangled_name;
-		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
-		  char *demangled_name;
-		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
-		  int is_full_physname_constructor =
-		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
-		    || is_constructor_name (physname)
-		    || is_destructor_name (physname)
-		    || method_name[0] == '~';
-
-		  /* Do not print out artificial methods.  */
-		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		    continue;
-
-		  QUIT;
-		  section_type = output_access_specifier
-		    (stream, section_type, level,
-		     TYPE_FN_FIELD_PROTECTED (f, j),
-		     TYPE_FN_FIELD_PRIVATE (f, j));
-
-		  print_spaces_filtered (level + 4, stream);
-		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
-		    fprintf_filtered (stream, "virtual ");
-		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
-		    fprintf_filtered (stream, "static ");
-		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
-		    {
-		      /* Keep GDB from crashing here.  */
-		      fprintf_filtered (stream,
-					_("<undefined type> %s;\n"),
-					TYPE_FN_FIELD_PHYSNAME (f, j));
-		      break;
-		    }
-		  else if (!is_constructor	/* Constructors don't
-						   have declared
-						   types.  */
-			   && !is_full_physname_constructor  /* " " */
-			   && !is_type_conversion_operator (type, i, j))
-		    {
-		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				    "", stream, -1, 0,
-				    &local_flags);
-		      fputs_filtered (" ", stream);
-		    }
-		  if (TYPE_FN_FIELD_STUB (f, j))
-		    {
-		      /* Build something we can demangle.  */
-		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
-		      mangled_name = mangled_name_holder.get ();
-		    }
-		  else
-		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
-
-		  demangled_name =
-		    gdb_demangle (mangled_name,
-				  DMGL_ANSI | DMGL_PARAMS);
-		  if (demangled_name == NULL)
-		    {
-		      /* In some cases (for instance with the HP
-			 demangling), if a function has more than 10
-			 arguments, the demangling will fail.
-			 Let's try to reconstruct the function
-			 signature from the symbol information.  */
-		      if (!TYPE_FN_FIELD_STUB (f, j))
-			{
-			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
-			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
-
-			  cp_type_print_method_args (mtype,
-						     "",
-						     method_name,
-						     staticp,
-						     stream, &local_flags);
-			}
-		      else
-			fprintf_filtered (stream,
-					  _("<badly mangled name '%s'>"),
-					  mangled_name);
-		    }
-		  else
-		    {
-		      char *p;
-		      char *demangled_no_class
-			= remove_qualifiers (demangled_name);
-
-		      /* Get rid of the `static' appended by the
-			 demangler.  */
-		      p = strstr (demangled_no_class, " static");
-		      if (p != NULL)
-			{
-			  int length = p - demangled_no_class;
-			  char *demangled_no_static;
-
-			  demangled_no_static
-			    = (char *) xmalloc (length + 1);
-			  strncpy (demangled_no_static,
-				   demangled_no_class, length);
-			  *(demangled_no_static + length) = '\0';
-			  fputs_filtered (demangled_no_static, stream);
-			  xfree (demangled_no_static);
-			}
-		      else
-			fputs_filtered (demangled_no_class, stream);
-		      xfree (demangled_name);
-		    }
-
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print out nested types.  */
-	  if (TYPE_NESTED_TYPES_COUNT (type) != 0
-	      && semi_local_flags.print_nested_type_limit != 0)
-	    {
-	      if (semi_local_flags.print_nested_type_limit > 0)
-		--semi_local_flags.print_nested_type_limit;
-
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
-		{
-		  print_spaces_filtered (level + 4, stream);
-		  c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
-				"", stream, show, level + 4, &semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print typedefs defined in this class.  */
-
-	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
-	    {
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
-		  || TYPE_NESTED_TYPES_COUNT (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
-		{
-		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
-
-		  /* Dereference the typedef declaration itself.  */
-		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
-		  target = TYPE_TARGET_TYPE (target);
-
-		  if (need_access_label)
-		    {
-		      section_type = output_access_specifier
-			(stream, section_type, level,
-			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
-		    }
-		  print_spaces_filtered (level + 4, stream);
-		  fprintf_filtered (stream, "typedef ");
-
-		  /* We want to print typedefs with substitutions
-		     from the template parameters or globally-known
-		     typedefs but not local typedefs.  */
-		  c_print_type (target,
-				TYPE_TYPEDEF_FIELD_NAME (type, i),
-				stream, show - 1, level + 4,
-				&semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	    fprintfi_filtered (level, stream, "}");
-	  }
-
-	do_cleanups (local_cleanups);
-      }
+      c_type_print_base_struct_union (type, stream, show, level, flags);
       break;
 
     case TYPE_CODE_ENUM:
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v4 2/2] Implement pahole-like 'ptype /o' option
  2017-12-11 23:43 ` [PATCH v4 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2017-12-11 23:44   ` [PATCH v4 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
@ 2017-12-11 23:44   ` Sergio Durigan Junior
  2017-12-12  0:27     ` Sergio Durigan Junior
                       ` (2 more replies)
  1 sibling, 3 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-11 23:44 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	André Pönitz, Keith Seitz, Sergio Durigan Junior

This commit implements the pahole-like '/o' option for 'ptype', which
prints the offsets and sizes of struct fields, reporting whenever
there is a hole found.

The output is heavily based on pahole(1), with a few modifications
here and there to adjust it to our reality.  Here's an example:

  (gdb) ptype /o stap_probe
  /* offset    |  size */
  struct stap_probe {
  /*    0      |    40 */    struct probe {
  /*    0      |     8 */        const probe_ops *pops;
  /*    8      |     8 */        gdbarch *arch;
  /*   16      |     8 */        const char *name;
  /*   24      |     8 */        const char *provider;
  /*   32      |     8 */        CORE_ADDR address;
			     } /* total size:   40 bytes */ p;
  /*   40      |     8 */    CORE_ADDR sem_addr;
  /*   48:31   |     4 */    unsigned int args_parsed : 1;
  /* XXX  7-bit hole   */
  /* XXX  7-byte hole  */
  /*   56      |     8 */    union {
  /*                 8 */        const char *text;
  /*                 8 */        VEC_stap_probe_arg_s *vec;
			     } /* total size:    8 bytes */ args_u;
  } /* total size:   64 bytes */

A big part of this patch handles the formatting logic of 'ptype',
which is a bit messy.  I tried to be not very invasive, but I had to
do some cleanups here and there to make life easier.

This patch is the start of a long-term work I'll do to flush the local
patches we carry for Fedora GDB.  In this specific case, I'm aiming at
upstreaming the feature implemented by the 'pahole.py' script that is
shipped with Fedora GDB:

  <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>

This has been regression-tested on the BuildBot.  There's a new
testcase for it, along with an update to the documentation.  I also
thought it was worth mentioning this feature in the NEWS file.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
	* c-typeprint.c (OFFSET_SPC_LEN): New define.
	(print_spaces_filtered_with_print_options): New function.
	(output_access_specifier): Take new argument FLAGS.  Modify
	function to call 'print_spaces_filtered_with_print_options'.
	(c_print_type_union_field_offset): New function.
	(c_print_type_struct_field_offset): New function.
	(c_type_print_base_struct_union): Print offsets and sizes for
	struct/union/class fields.
	* typeprint.c (const struct type_print_options
	type_print_raw_options): Initialize 'print_offsets' and
	'offset_bitpos'.
	(static struct type_print_options default_ptype_flags):
	Likewise.
	(whatis_exp): Handle '/o' option.
	(_initialize_typeprint): Add '/o' flag to ptype's help.
	* typeprint.h (struct type_print_options) <print_offsets>: New
	field.
	<offset_bitpos>: Likewise.

gdb/testsuite/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.base/ptype-offsets.cc: New file.
	* gdb.base/ptype-offsets.exp: New file.

gdb/doc/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.texinfo (ptype): Add new flag '/o'.
---
 gdb/NEWS                                 |   3 +
 gdb/c-typeprint.c                        | 207 ++++++++++++++++++++++++++++---
 gdb/doc/gdb.texinfo                      |  56 +++++++++
 gdb/testsuite/gdb.base/ptype-offsets.cc  | 138 +++++++++++++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.exp | 158 +++++++++++++++++++++++
 gdb/typeprint.c                          |  15 ++-
 gdb/typeprint.h                          |   9 ++
 7 files changed, 566 insertions(+), 20 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index c6fe297159..67d40d007b 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * New "--readnever" command line option instructs GDB to not read each
   symbol file's symbolic debug information.  This makes startup faster
   but at the expense of not being able to perform symbolic debugging.
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index ce10b5e005..23138d8a40 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options
+  (int level, struct ui_file *stream, const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,15 +890,98 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
-/* Return true is an access label (i.e., "public:", "private:",
+/* Print information about field at index FIELD_IDX of the union type
+   TYPE.  Since union fields don't have the concept of offsets, we
+   just print their sizes.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
+				 struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+
+  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+}
+
+/* Print information about field at index FIELD_IDX of the struct type
+   TYPE.  ENDPOS is the one-past-the-end bit position of the previous
+   field (where we expect this field to be if there is no hole).  At
+   the end, ENDPOS is updated to the one-past-the-end bit position of
+   the current field.  OFFSET_BITPOS is the offset value we carry over
+   when we are printing a struct that is inside another struct; this
+   is useful so that the offset is constantly incremented (if we
+   didn't carry it over, the offset would be reset to zero when
+   printing the inner struct).
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
+				  unsigned int *endpos, struct ui_file *stream,
+				  unsigned int offset_bitpos)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit;
+
+  /* We check for *ENDPOS > 0 because there is a specific scenario
+     when *ENDPOS can be zero and BITPOS can be > 0: when we are
+     dealing with a struct/class with a virtual method.  Because of
+     the vtable, the first field of the struct/class will have an
+     offset of sizeof (void *) (the size of the vtable).  If we do not
+     check for *ENDPOS > 0 here, GDB will report a hole before the
+     first field, which is not accurate.  */
+  if (*endpos > 0 && *endpos < bitpos)
+    {
+      /* If ENDPOS is smaller than the current type's bitpos, it means
+	 there's a hole in the struct, so we report it here.  */
+      unsigned int hole = bitpos - *endpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  /* The position of the field, relative to the beginning of the
+     struct.  Assume this number will have 4 digits.  */
+  fprintf_filtered (stream, "/* %4u",
+		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT);
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      fieldsize_bit = TYPE_FIELD_BITSIZE (type, field_idx);
+      fprintf_filtered (stream, ":%u",
+			fieldsize_byte * TARGET_CHAR_BIT - fieldsize_bit);
+    }
+  else
+    {
+      fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  *endpos = bitpos + fieldsize_bit;
+}
+
+/* Return true if an access label (i.e., "public:", "private:",
    "protected:") needs to be printed for TYPE.  */
 
 static bool
@@ -1032,6 +1138,8 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
       int len = TYPE_NFIELDS (type);
       vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      unsigned int endpos = 0;
+
       for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
 	{
 	  QUIT;
@@ -1048,18 +1156,51 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FIELD_PROTECTED (type, i),
-		 TYPE_FIELD_PRIVATE (type, i));
+		 TYPE_FIELD_PRIVATE (type, i), flags);
+	    }
+
+	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+	  if (flags->print_offsets)
+	    {
+	      if (!is_static)
+		{
+		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		    c_print_type_struct_field_offset (type, i, &endpos, stream,
+						      flags->offset_bitpos);
+		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
+		    c_print_type_union_field_offset (type, i, stream);
+		}
+	      else
+		print_spaces_filtered (OFFSET_SPC_LEN, stream);
 	    }
 
 	  print_spaces_filtered (level + 4, stream);
-	  if (field_is_static (&TYPE_FIELD (type, i)))
+	  if (is_static)
 	    fprintf_filtered (stream, "static ");
+
+	  int newshow = show - 1;
+
+	  if (flags->print_offsets
+	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
+		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
+	    {
+	      /* If we're printing offsets and this field's type is
+		 either a struct or an union, then we're interested in
+		 expanding it.  */
+	      ++newshow;
+
+	      /* Make sure we carry our offset when we expand the
+		 struct.  */
+	      local_flags.offset_bitpos
+		= flags->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
+	    }
+
 	  c_print_type (TYPE_FIELD_TYPE (type, i),
 			TYPE_FIELD_NAME (type, i),
-			stream, show - 1, level + 4,
+			stream, newshow, level + 4,
 			&local_flags);
-	  if (!field_is_static (&TYPE_FIELD (type, i))
-	      && TYPE_FIELD_PACKED (type, i))
+	  if (!is_static && TYPE_FIELD_PACKED (type, i))
 	    {
 	      /* It is a bitfield.  This code does not attempt
 		 to look at the bitpos and reconstruct filler,
@@ -1122,9 +1263,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FN_FIELD_PROTECTED (f, j),
-		 TYPE_FN_FIELD_PRIVATE (f, j));
+		 TYPE_FN_FIELD_PRIVATE (f, j), flags);
 
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
 		fprintf_filtered (stream, "virtual ");
 	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
@@ -1143,9 +1285,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		       && !is_full_physname_constructor  /* " " */
 		       && !is_type_conversion_operator (type, i, j))
 		{
+		  unsigned int old_po = local_flags.print_offsets;
+
+		  /* Temporarily disable print_offsets, because it
+		     would mess with indentation.  */
+		  local_flags.print_offsets = 0;
 		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
 				"", stream, -1, 0,
 				&local_flags);
+		  local_flags.print_offsets = old_po;
+
 		  fputs_filtered (" ", stream);
 		}
 	      if (TYPE_FN_FIELD_STUB (f, j))
@@ -1226,9 +1375,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
 	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
 	    {
-	      print_spaces_filtered (level + 4, stream);
+	      unsigned int old_po = semi_local_flags.print_offsets;
+
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      semi_local_flags.print_offsets = 0;
 	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
 			    "", stream, show, level + 4, &semi_local_flags);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
@@ -1254,11 +1410,17 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		  section_type = output_access_specifier
 		    (stream, section_type, level,
 		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i), flags);
 		}
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      fprintf_filtered (stream, "typedef ");
 
+	      unsigned int old_po = semi_local_flags.print_offsets;
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      semi_local_flags.print_offsets = 0;
+
 	      /* We want to print typedefs with substitutions
 		 from the template parameters or globally-known
 		 typedefs but not local typedefs.  */
@@ -1266,13 +1428,21 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 			    TYPE_TYPEDEF_FIELD_NAME (type, i),
 			    stream, show - 1, level + 4,
 			    &semi_local_flags);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
 
+      if (flags->print_offsets && level > 0)
+	print_spaces_filtered (OFFSET_SPC_LEN, stream);
+
       fprintfi_filtered (level, stream, "}");
     }
 
+  if (show > 0 && flags->print_offsets)
+    fprintf_filtered (stream, " /* total size: %4u bytes */",
+		      TYPE_LENGTH (type));
+
   do_cleanups (local_cleanups);
 }
 
@@ -1300,7 +1470,6 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 {
   int i;
   int len;
-  int j;
 
   QUIT;
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 062f01d5ff..2ed52e6739 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17211,6 +17211,62 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.
+
+For example, given the following declarations:
+
+@smallexample
+struct tuv
+@{
+  int a1;
+  char *a2;
+  int a3;
+@};
+
+struct xyz
+@{
+  int f1;
+  char f2;
+  void *f3;
+  struct tuv f4;
+@};
+
+union qwe
+@{
+  struct tuv fff1;
+  struct xyz fff2;
+@};
+@end smallexample
+
+Issuing a @command{ptype /o union qwe} would print:
+
+@smallexample
+(@value{GDBP}) ptype /o union qwe
+/* offset    |  size */
+union qwe @{
+/*                24 */    struct tuv @{
+/*    0      |     4 */        int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */        char *a2;
+/*   16      |     4 */        int a3;
+                           @} /* total size:   24 bytes */ fff1;
+/*                40 */    struct xyz @{
+/*    0      |     4 */        int f1;
+/*    4      |     1 */        char f2;
+/* XXX  3-byte hole  */
+/*    8      |     8 */        void *f3;
+/*   16      |    24 */        struct tuv @{
+/*   16      |     4 */            int a1;
+/* XXX  4-byte hole  */
+/*   24      |     8 */            char *a2;
+/*   32      |     4 */            int a3;
+                               @} /* total size:   24 bytes */ f4;
+                           @} /* total size:   40 bytes */ fff2;
+@} /* total size:   40 bytes */
+@end smallexample
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..52d2277ce7
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,138 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
+
+#include <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  char field4;
+
+  /* 7-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+};
+
+/* This struct will be nested inside 'struct xyz'.  */
+
+struct tuv
+{
+  int a1;
+
+  char *a2;
+
+  int a3;
+};
+
+/* This struct will be nested inside 'struct pqr'.  */
+
+struct xyz
+{
+  int f1;
+
+  char f2;
+
+  void *f3;
+
+  struct tuv f4;
+};
+
+/* A struct with a nested struct.  */
+
+struct pqr
+{
+  int ff1;
+
+  struct xyz ff2;
+
+  char ff3;
+};
+
+/* A union with two nested structs.  */
+
+union qwe
+{
+  struct tuv fff1;
+
+  struct xyz fff2;
+};
+
+/* A struct with an union.  */
+
+struct poi
+{
+  int f1;
+
+  union qwe f2;
+
+  uint16_t f3;
+
+  struct pqr f4;
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+  struct pqr bar;
+  union qwe c;
+  struct poi d;
+  uint8_t i;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..fa1c0cb41c
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,158 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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/>.
+
+standard_testfile .cc
+
+# Test only works on x86_64 LP64 targets.  That's how we guarantee
+# that the expected holes will be present in the struct.
+if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
+    untested "test work only on x86_64 lp64"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ optimize=-O0 }] } {
+    return -1
+}
+
+# Test general offset printing, ctor/dtor printing, union, formatting.
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+"type = struct abc {" \
+"/\\\* offset    |  size \\\*/" \
+"                         public:" \
+"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
+"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
+"/\\\* XXX  7-bit hole   \\\*/" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   20      |     4 \\\*/    int field3;" \
+"/\\\*   24      |     1 \\\*/    char field4;" \
+"/\\\* XXX  7-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
+"/\\\*   40      |     8 \\\*/    union {" \
+"/\\\*                 8 \\\*/        void \\\*field6;" \
+"/\\\*                 4 \\\*/        int field7;" \
+"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
+"" \
+"                           abc\\(void\\);" \
+"                           ~abc\\(\\);" \
+"} /\\\* total size:   48 bytes \\\*/"] \
+    "ptype offset struct abc"
+
+# Test nested structs.
+gdb_test "ptype /o struct pqr" \
+    [multi_line \
+"type = struct pqr {" \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    16 \\\*/    struct xyz {" \
+"/\\\*    8      |     4 \\\*/        int f1;" \
+"/\\\*   12      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/        struct tuv {" \
+"/\\\*   24      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/            char *a2;" \
+"/\\\*   40      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*   48      |     1 \\\*/    char ff3;" \
+"} /\\\* total size:   56 bytes \\\*/"] \
+    "ptype offset struct pqr"
+
+# Test that the offset is properly reset when we are printing an union
+# and go inside two inner structs.
+# This also tests a struct inside a struct inside an union.
+gdb_test "ptype /o union qwe" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*                24 \\\*/    struct tuv {" \
+"/\\\*    0      |     4 \\\*/        int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/        char \\\*a2;" \
+"/\\\*   16      |     4 \\\*/        int a3;" \
+"                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
+"/\\\*                40 \\\*/    struct xyz {" \
+"/\\\*    0      |     4 \\\*/        int f1;" \
+"/\\\*    4      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   16      |    24 \\\*/        struct tuv {" \
+"/\\\*   16      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   24      |     8 \\\*/            char \\\*a2;" \
+"/\\\*   32      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ fff2;" \
+"} /\\\* total size:   40 bytes \\\*/"] \
+    "ptype offset union qwe"
+
+# Test printing a struct that contains an union, and that also
+# contains a struct.
+gdb_test "ptype /o struct poi" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    40 \\\*/    union qwe {" \
+"/\\\*                24 \\\*/        struct tuv {" \
+"/\\\*    8      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/            char \\\*a2;" \
+"/\\\*   24      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ fff1;" \
+"/\\\*                40 \\\*/        struct xyz {" \
+"/\\\*    8      |     4 \\\*/            int f1;" \
+"/\\\*   12      |     1 \\\*/            char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/            void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/            struct tuv {" \
+"/\\\*   24      |     4 \\\*/                int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/                char \\\*a2;" \
+"/\\\*   40      |     4 \\\*/                int a3;" \
+"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                               } /\\\* total size:   40 bytes \\\*/ fff2;" \
+"                           } /\\\* total size:   40 bytes \\\*/ f2;" \
+"/\\\*   48      |     2 \\\*/    uint16_t f3;" \
+"/\\\* XXX  6-byte hole  */" \
+"/\\\*   56      |    56 \\\*/    struct pqr {" \
+"/\\\*   56      |     4 \\\*/        int ff1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   64      |    40 \\\*/        struct xyz {" \
+"/\\\*   64      |     4 \\\*/            int f1;" \
+"/\\\*   68      |     1 \\\*/            char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   72      |     8 \\\*/            void \\\*f3;" \
+"/\\\*   80      |    24 \\\*/            struct tuv {" \
+"/\\\*   80      |     4 \\\*/                int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   88      |     8 \\\*/                char \\\*a2;" \
+"/\\\*   96      |     4 \\\*/                int a3;" \
+"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                               } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*  104      |     1 \\\*/        char ff3;" \
+"                           } /\\\* total size:   56 bytes \\\*/ f4;" \
+"} /\\\* total size:  112 bytes \\\*/"] \
+    "ptype offset struct poi"
+
+# Test that we don't print any header when issuing a "ptype /o" on a
+# non-struct, non-union, non-class type.
+gdb_test "ptype /o int" "int" "ptype offset int"
+gdb_test "ptype /o uint8_t" "char" "ptype offset uint8_t"
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 9d9d6f5a49..eb829c2069 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,7 +42,9 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
+  0,				/* offset_bitpos */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -55,7 +57,9 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
+  0,				/* offset_bitpos */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
@@ -440,6 +444,9 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  flags.print_offsets = 1;
+		  break;
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -499,6 +506,11 @@ whatis_exp (const char *exp, int show)
 	real_type = value_rtti_type (val, &full, &top, &using_enc);
     }
 
+  if (flags.print_offsets &&
+      (TYPE_CODE (type) == TYPE_CODE_STRUCT
+       || TYPE_CODE (type) == TYPE_CODE_UNION))
+    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
+
   printf_filtered ("type = ");
 
   if (!flags.raw)
@@ -759,7 +771,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a2058b0120..dd23c1eb6c 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -35,9 +35,18 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
   /* The number of nested type definitions to print.  -1 == all.  */
   int print_nested_type_limit;
 
+  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
+     This is needed for when we are printing nested structs and want
+     to make sure that the printed offset for each field carries off
+     the offset of the outter struct.  */
+  unsigned int offset_bitpos;
+
   /* If not NULL, a local typedef hash table used when printing a
      type.  */
   struct typedef_hash_table *local_typedefs;
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-11 22:50             ` Sergio Durigan Junior
@ 2017-12-11 23:46               ` Pedro Alves
  2017-12-12  0:25                 ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-11 23:46 UTC (permalink / raw)
  To: Sergio Durigan Junior
  Cc: Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

On 12/11/2017 10:50 PM, Sergio Durigan Junior wrote:

>>> Fair enough.  I use this trick for function prototypes, but not for the
>>> definitions.
>>
>> Simon's format is what I've been using for a long while too.
> 
> Well, I could post a few examples of the format I chose, but I'm pretty
> sure this would be worthless.  As I said, I will change my code.
> 

By stating the format that I've been using too, my intention
is to show that Simon's format isn't his own unique snowflake
either.

If there are many examples of the format you chose, I've not seem
many.  Maybe it depends on area of code one is touching, or IOW, on
the preferences of who wrote specific areas of the codebase.

IMO, the right way to go about this is to decide on a format,
document it in the wiki and then we can point everyone at it with a URL.
(ISTR that GCC's coding conventions documents yet another
way to break the line, and in that case, not even GCC follows it.)

IMO, the format you've chosen isn't ideal because it requires
manual right-alignment for every line, while breaking before the
parens makes emacs's TAB automatically align the following lines.
The latter also gives a lot more space for the params again; it's
sort of a "well, I have to do something, so might as well start
way back from the left again).

>>>> But I don't mind it, it just stuck out as a little inconsistency.
>>>
>>> I don't see the inconsistency.
>>>
>>> If a field is inside a struct, it has its offset *and* size printed.  No
>>> matter if the field is an int, another struct, or an union.
>>>
>>> If a field is inside an union, it has only its size printed.
>>>
>>> In the case above, it makes sense to have the offsets printed for the
>>> fields inside the two structs (inside the union), because there might be
>>> holes to report (well, one can argue that it doesn't matter if there are
>>> holes or not in this case, because if the other struct is bigger then
>>> the union size will stay the same).  However, it doesn't make sense to
>>> print the offsets for the two structs themselves, because they are
>>> members of the union.
>>>
>>> I hope it makes more sense now.
>>
>> But why do we need the special case?  Does it help anything?
>> So far, it seems it only added confusion.
> 
> What do you mean by "special case"?

Special case:

  "If a field is inside a union, it has only its size printed; otherwise
   print the offset and size." 

No special case:

  "Always print the offset and size."

> 
> This is what pahole does, and as I've said a few times, the output of
> 'ptype /o' has been based on pahole's output.  

That's abundantly clear, but I don't see why we need to follow
"pahole"'s output religiously...

> I don't consider this a
> special case; I consider it to be the natural thing to do, because
> offsets don't make much sense in unions.

Of course they do.  You can do 'offsetof(foo_union, foo_field)' just
fine, for example.  Saying that the offsets happen to be the same
is not the same as saying that the offsets don't exist.

I asked:

>> Does it help anything?
>> So far, it seems it only added confusion.

I think a much better rationale for omitting the offsets
would be:

Not printing the offsets in the case of union members helps
by increasing signal/noise ratio.

Why?  

With patch as is:

/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
/*    8      |     8 */        union {
/*                 8 */            LONGEST ivalue;
/*                 8 */            const block *block;
/*                 8 */            const gdb_byte *bytes;
/*                 8 */            CORE_ADDR address;
/*                 8 */            const common_block *common_block;
/*                 8 */            symbol *chain;
                               } /* total size:    8 bytes */ value;

vs always-print-offset:

/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
/*    8      |     8 */        union {
/*    8            8 */            LONGEST ivalue;
/*    8            8 */            const block *block;
/*    8            8 */            const gdb_byte *bytes;
/*    8            8 */            CORE_ADDR address;
/*    8            8 */            const common_block *common_block;
/*    8            8 */            symbol *chain;
                               } /* total size:    8 bytes */ value;

Note that it can be argued that the version that does _not_ print
the offsets includes _more_ information, or less noise, because
with that version it's much easier to not get distracted with
the offsets of the union fields, which can do nothing about.

So from that angle, I see value in not printing the offsets
of union members.

(Also note that the assertion that offsets of union members would
be 0 every time is inaccurate.  When printing a union that is itself
a field of a struct, and the union is at offset > 0 in the
containing struct, the offset to print would be offset > 0.)

> 
>> The option is "/o" for "print offsets".  Why not print offsets always?
> 
> I hope I explained it above.
> 
>> BTW, shouldn't the documentation in the manual include an example
>> of GDB's output?
> 
> I can include an example, OK.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-11 23:46               ` Pedro Alves
@ 2017-12-12  0:25                 ` Sergio Durigan Junior
  2017-12-12  0:52                   ` Pedro Alves
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-12  0:25 UTC (permalink / raw)
  To: Pedro Alves; +Cc: Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

On Monday, December 11 2017, Pedro Alves wrote:

> On 12/11/2017 10:50 PM, Sergio Durigan Junior wrote:
>
>>>> Fair enough.  I use this trick for function prototypes, but not for the
>>>> definitions.
>>>
>>> Simon's format is what I've been using for a long while too.
>> 
>> Well, I could post a few examples of the format I chose, but I'm pretty
>> sure this would be worthless.  As I said, I will change my code.
>> 
>
> By stating the format that I've been using too, my intention
> is to show that Simon's format isn't his own unique snowflake
> either.

I honestly never thought he was the only one doing this.  I certainly
remember having seen this format choice in other places as well.

> If there are many examples of the format you chose, I've not seem
> many.  Maybe it depends on area of code one is touching, or IOW, on
> the preferences of who wrote specific areas of the codebase.

I remember seeing some examples of the format I chose.

> IMO, the right way to go about this is to decide on a format,
> document it in the wiki and then we can point everyone at it with a URL.
> (ISTR that GCC's coding conventions documents yet another
> way to break the line, and in that case, not even GCC follows it.)
>
> IMO, the format you've chosen isn't ideal because it requires
> manual right-alignment for every line, while breaking before the
> parens makes emacs's TAB automatically align the following lines.
> The latter also gives a lot more space for the params again; it's
> sort of a "well, I have to do something, so might as well start
> way back from the left again).

No, it's not ideal at all.  But at least on my Emacs, the other format
has the downside of being always realigned to the column zero when I
press TAB on it.  But again, I'm not saying this is Emacs fault nor your
nor Simon's fault; this is just something I noticed.

>>>>> But I don't mind it, it just stuck out as a little inconsistency.
>>>>
>>>> I don't see the inconsistency.
>>>>
>>>> If a field is inside a struct, it has its offset *and* size printed.  No
>>>> matter if the field is an int, another struct, or an union.
>>>>
>>>> If a field is inside an union, it has only its size printed.
>>>>
>>>> In the case above, it makes sense to have the offsets printed for the
>>>> fields inside the two structs (inside the union), because there might be
>>>> holes to report (well, one can argue that it doesn't matter if there are
>>>> holes or not in this case, because if the other struct is bigger then
>>>> the union size will stay the same).  However, it doesn't make sense to
>>>> print the offsets for the two structs themselves, because they are
>>>> members of the union.
>>>>
>>>> I hope it makes more sense now.
>>>
>>> But why do we need the special case?  Does it help anything?
>>> So far, it seems it only added confusion.
>> 
>> What do you mean by "special case"?
>
> Special case:
>
>   "If a field is inside a union, it has only its size printed; otherwise
>    print the offset and size." 
>
> No special case:
>
>   "Always print the offset and size."

I don't like the expression "special case" because it diminishes the
difference that exist between structs and unions.  It is not like I went
out of my way to treat this difference and made the code complex; it is
also not like the output is extremely complex with it.

>> 
>> This is what pahole does, and as I've said a few times, the output of
>> 'ptype /o' has been based on pahole's output.  
>
> That's abundantly clear, but I don't see why we need to follow
> "pahole"'s output religiously...

Sure, but I don't see why we should deviate from the de facto convention
in this specific case.

>> I don't consider this a
>> special case; I consider it to be the natural thing to do, because
>> offsets don't make much sense in unions.
>
> Of course they do.  You can do 'offsetof(foo_union, foo_field)' just
> fine, for example.  Saying that the offsets happen to be the same
> is not the same as saying that the offsets don't exist.

I don't remember saying offsets don't exist in unions.  What I said is
that in this specific case they don't matter/make much sense to be
printed.

> I asked:
>
>>> Does it help anything?
>>> So far, it seems it only added confusion.
>
> I think a much better rationale for omitting the offsets
> would be:
>
> Not printing the offsets in the case of union members helps
> by increasing signal/noise ratio.

I think that's a matter of opinion.  I didn't think about reducing the
signal/noise ratio; I explicitly thought that printing union offsets was
not useful.

> Why?  
>
> With patch as is:
>
> /*    0      |    32 */    struct general_symbol_info {
> /*    0      |     8 */        const char *name;
> /*    8      |     8 */        union {
> /*                 8 */            LONGEST ivalue;
> /*                 8 */            const block *block;
> /*                 8 */            const gdb_byte *bytes;
> /*                 8 */            CORE_ADDR address;
> /*                 8 */            const common_block *common_block;
> /*                 8 */            symbol *chain;
>                                } /* total size:    8 bytes */ value;
>
> vs always-print-offset:
>
> /*    0      |    32 */    struct general_symbol_info {
> /*    0      |     8 */        const char *name;
> /*    8      |     8 */        union {
> /*    8            8 */            LONGEST ivalue;
> /*    8            8 */            const block *block;
> /*    8            8 */            const gdb_byte *bytes;
> /*    8            8 */            CORE_ADDR address;
> /*    8            8 */            const common_block *common_block;
> /*    8            8 */            symbol *chain;
>                                } /* total size:    8 bytes */ value;
>
> Note that it can be argued that the version that does _not_ print
> the offsets includes _more_ information, or less noise, because
> with that version it's much easier to not get distracted with
> the offsets of the union fields, which can do nothing about.
>
> So from that angle, I see value in not printing the offsets
> of union members.

Since it's still not clear whether the offsets should be printed or not
in this case, and I am not a global maintainer, I adjusted the code to
print them and will post the patch as a reply to the v4 e-mail.  This
way you can decide which version is best.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v4 2/2] Implement pahole-like 'ptype /o' option
  2017-12-11 23:44   ` [PATCH v4 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-12-12  0:27     ` Sergio Durigan Junior
  2017-12-12  0:29       ` Sergio Durigan Junior
  2017-12-12  1:59     ` Simon Marchi
  2017-12-12  3:39     ` Eli Zaretskii
  2 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-12  0:27 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	André Pönitz, Keith Seitz

On Monday, December 11 2017, I wrote:

> This commit implements the pahole-like '/o' option for 'ptype', which
> prints the offsets and sizes of struct fields, reporting whenever
> there is a hole found.
>
> The output is heavily based on pahole(1), with a few modifications
> here and there to adjust it to our reality.  Here's an example:
>
>   (gdb) ptype /o stap_probe
>   /* offset    |  size */
>   struct stap_probe {
>   /*    0      |    40 */    struct probe {
>   /*    0      |     8 */        const probe_ops *pops;
>   /*    8      |     8 */        gdbarch *arch;
>   /*   16      |     8 */        const char *name;
>   /*   24      |     8 */        const char *provider;
>   /*   32      |     8 */        CORE_ADDR address;
> 			     } /* total size:   40 bytes */ p;
>   /*   40      |     8 */    CORE_ADDR sem_addr;
>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>   /* XXX  7-bit hole   */
>   /* XXX  7-byte hole  */
>   /*   56      |     8 */    union {
>   /*                 8 */        const char *text;
>   /*                 8 */        VEC_stap_probe_arg_s *vec;
> 			     } /* total size:    8 bytes */ args_u;
>   } /* total size:   64 bytes */
>
> A big part of this patch handles the formatting logic of 'ptype',
> which is a bit messy.  I tried to be not very invasive, but I had to
> do some cleanups here and there to make life easier.
>
> This patch is the start of a long-term work I'll do to flush the local
> patches we carry for Fedora GDB.  In this specific case, I'm aiming at
> upstreaming the feature implemented by the 'pahole.py' script that is
> shipped with Fedora GDB:
>
>   <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>
>
> This has been regression-tested on the BuildBot.  There's a new
> testcase for it, along with an update to the documentation.  I also
> thought it was worth mentioning this feature in the NEWS file.

The patch below applies on top of this one and extends the output to
include offsets of union fields.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index 23138d8a40..e66129f643 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -906,11 +906,15 @@ output_access_specifier (struct ui_file *stream,
 
 static void
 c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
-				 struct ui_file *stream)
+				 struct ui_file *stream,
+				 unsigned int offset_bitpos)
 {
   struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
 
-  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+  fprintf_filtered (stream, "/* %4u      |  %4u */",
+		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT,
+		    TYPE_LENGTH (ftype));
 }
 
 /* Print information about field at index FIELD_IDX of the struct type
@@ -1169,7 +1173,8 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		    c_print_type_struct_field_offset (type, i, &endpos, stream,
 						      flags->offset_bitpos);
 		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
-		    c_print_type_union_field_offset (type, i, stream);
+		    c_print_type_union_field_offset (type, i, stream,
+						     flags->offset_bitpos);
 		}
 	      else
 		print_spaces_filtered (OFFSET_SPC_LEN, stream);
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
index fa1c0cb41c..7566dcaaab 100644
--- a/gdb/testsuite/gdb.base/ptype-offsets.exp
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -44,8 +44,8 @@ gdb_test "ptype /o struct abc" \
 "/\\\* XXX  7-byte hole  \\\*/" \
 "/\\\*   32      |     8 \\\*/    uint64_t field5;" \
 "/\\\*   40      |     8 \\\*/    union {" \
-"/\\\*                 8 \\\*/        void \\\*field6;" \
-"/\\\*                 4 \\\*/        int field7;" \
+"/\\\*   40      |     8 \\\*/        void \\\*field6;" \
+"/\\\*   40      |     4 \\\*/        int field7;" \
 "                           } /\\\* total size:    8 bytes \\\*/ field8;" \
 "" \
 "                           abc\\(void\\);" \
@@ -82,13 +82,13 @@ gdb_test "ptype /o struct pqr" \
 gdb_test "ptype /o union qwe" \
     [multi_line \
 "/\\\* offset    |  size \\\*/" \
-"/\\\*                24 \\\*/    struct tuv {" \
+"/\\\*    0      |    24 \\\*/    struct tuv {" \
 "/\\\*    0      |     4 \\\*/        int a1;" \
 "/\\\* XXX  4-byte hole  \\\*/" \
 "/\\\*    8      |     8 \\\*/        char \\\*a2;" \
 "/\\\*   16      |     4 \\\*/        int a3;" \
 "                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
-"/\\\*                40 \\\*/    struct xyz {" \
+"/\\\*    0      |    40 \\\*/    struct xyz {" \
 "/\\\*    0      |     4 \\\*/        int f1;" \
 "/\\\*    4      |     1 \\\*/        char f2;" \
 "/\\\* XXX  3-byte hole  \\\*/" \
@@ -111,13 +111,13 @@ gdb_test "ptype /o struct poi" \
 "/\\\*    0      |     4 \\\*/    int f1;" \
 "/\\\* XXX  4-byte hole  \\\*/" \
 "/\\\*    8      |    40 \\\*/    union qwe {" \
-"/\\\*                24 \\\*/        struct tuv {" \
+"/\\\*    8      |    24 \\\*/        struct tuv {" \
 "/\\\*    8      |     4 \\\*/            int a1;" \
 "/\\\* XXX  4-byte hole  \\\*/" \
 "/\\\*   16      |     8 \\\*/            char \\\*a2;" \
 "/\\\*   24      |     4 \\\*/            int a3;" \
 "                               } /\\\* total size:   24 bytes \\\*/ fff1;" \
-"/\\\*                40 \\\*/        struct xyz {" \
+"/\\\*    8      |    40 \\\*/        struct xyz {" \
 "/\\\*    8      |     4 \\\*/            int f1;" \
 "/\\\*   12      |     1 \\\*/            char f2;" \
 "/\\\* XXX  3-byte hole  \\\*/" \

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v4 2/2] Implement pahole-like 'ptype /o' option
  2017-12-12  0:27     ` Sergio Durigan Junior
@ 2017-12-12  0:29       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-12  0:29 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	André Pönitz, Keith Seitz

On Monday, December 11 2017, I wrote:

> On Monday, December 11 2017, I wrote:
>
>> This commit implements the pahole-like '/o' option for 'ptype', which
>> prints the offsets and sizes of struct fields, reporting whenever
>> there is a hole found.
>>
>> The output is heavily based on pahole(1), with a few modifications
>> here and there to adjust it to our reality.  Here's an example:
>>
>>   (gdb) ptype /o stap_probe
>>   /* offset    |  size */
>>   struct stap_probe {
>>   /*    0      |    40 */    struct probe {
>>   /*    0      |     8 */        const probe_ops *pops;
>>   /*    8      |     8 */        gdbarch *arch;
>>   /*   16      |     8 */        const char *name;
>>   /*   24      |     8 */        const char *provider;
>>   /*   32      |     8 */        CORE_ADDR address;
>> 			     } /* total size:   40 bytes */ p;
>>   /*   40      |     8 */    CORE_ADDR sem_addr;
>>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>>   /* XXX  7-bit hole   */
>>   /* XXX  7-byte hole  */
>>   /*   56      |     8 */    union {
>>   /*                 8 */        const char *text;
>>   /*                 8 */        VEC_stap_probe_arg_s *vec;
>> 			     } /* total size:    8 bytes */ args_u;
>>   } /* total size:   64 bytes */
>>
>> A big part of this patch handles the formatting logic of 'ptype',
>> which is a bit messy.  I tried to be not very invasive, but I had to
>> do some cleanups here and there to make life easier.
>>
>> This patch is the start of a long-term work I'll do to flush the local
>> patches we carry for Fedora GDB.  In this specific case, I'm aiming at
>> upstreaming the feature implemented by the 'pahole.py' script that is
>> shipped with Fedora GDB:
>>
>>   <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>
>>
>> This has been regression-tested on the BuildBot.  There's a new
>> testcase for it, along with an update to the documentation.  I also
>> thought it was worth mentioning this feature in the NEWS file.
>
> The patch below applies on top of this one and extends the output to
> include offsets of union fields.
>
> -- 
> Sergio
> GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
> Please send encrypted e-mail if possible
> http://sergiodj.net/
>
> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
> index 23138d8a40..e66129f643 100644
> --- a/gdb/c-typeprint.c
> +++ b/gdb/c-typeprint.c
> @@ -906,11 +906,15 @@ output_access_specifier (struct ui_file *stream,
>  
>  static void
>  c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
> -				 struct ui_file *stream)
> +				 struct ui_file *stream,
> +				 unsigned int offset_bitpos)
>  {
>    struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
>  
> -  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
> +  fprintf_filtered (stream, "/* %4u      |  %4u */",
> +		    (bitpos + offset_bitpos) / TARGET_CHAR_BIT,
> +		    TYPE_LENGTH (ftype));
>  }

The comment of the function above needs an update:

/* Print information about field at index FIELD_IDX of the union type
   TYPE.  Since union fields don't have the concept of offsets, we
   just print their sizes.  OFFSET_BITPOS is the offset value we carry
   over when we are printing a union that is inside a struct; this is
   useful so that the offset is constantly incremented (if we didn't
   carry it over, the offset would be reset to zero when printing the
   inner union).

   The output is strongly based on pahole(1).  */

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-12  0:25                 ` Sergio Durigan Junior
@ 2017-12-12  0:52                   ` Pedro Alves
  2017-12-12  1:25                     ` Simon Marchi
  0 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-12  0:52 UTC (permalink / raw)
  To: Sergio Durigan Junior
  Cc: Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

On 12/12/2017 12:25 AM, Sergio Durigan Junior wrote:
> On Monday, December 11 2017, Pedro Alves wrote:

>> IMO, the right way to go about this is to decide on a format,
>> document it in the wiki and then we can point everyone at it with a URL.
>> (ISTR that GCC's coding conventions documents yet another
>> way to break the line, and in that case, not even GCC follows it.)
>>
>> IMO, the format you've chosen isn't ideal because it requires
>> manual right-alignment for every line, while breaking before the
>> parens makes emacs's TAB automatically align the following lines.
>> The latter also gives a lot more space for the params again; it's
>> sort of a "well, I have to do something, so might as well start
>> way back from the left again).
> 
> No, it's not ideal at all.  But at least on my Emacs, the other format
> has the downside of being always realigned to the column zero when I
> press TAB on it. But again, I'm not saying this is Emacs fault nor your
> nor Simon's fault; this is just something I noticed.

Seems easier to fix (just two spaces, and only on one line) than
the other approach, which depends a varying number of
space/delete strokes.  Maybe there's a way to configure emacs
not to do that, even?

> 
>>>>>> But I don't mind it, it just stuck out as a little inconsistency.
>>>>>
>>>>> I don't see the inconsistency.
>>>>>
>>>>> If a field is inside a struct, it has its offset *and* size printed.  No
>>>>> matter if the field is an int, another struct, or an union.
>>>>>
>>>>> If a field is inside an union, it has only its size printed.
>>>>>
>>>>> In the case above, it makes sense to have the offsets printed for the
>>>>> fields inside the two structs (inside the union), because there might be
>>>>> holes to report (well, one can argue that it doesn't matter if there are
>>>>> holes or not in this case, because if the other struct is bigger then
>>>>> the union size will stay the same).  However, it doesn't make sense to
>>>>> print the offsets for the two structs themselves, because they are
>>>>> members of the union.
>>>>>
>>>>> I hope it makes more sense now.
>>>>
>>>> But why do we need the special case?  Does it help anything?
>>>> So far, it seems it only added confusion.
>>>
>>> What do you mean by "special case"?
>>
>> Special case:
>>
>>   "If a field is inside a union, it has only its size printed; otherwise
>>    print the offset and size." 
>>
>> No special case:
>>
>>   "Always print the offset and size."
> 
> I don't like the expression "special case" because it diminishes the
> difference that exist between structs and unions.  

I don't follow, but OK...

> It is not like I went
> out of my way to treat this difference and made the code complex; it is
> also not like the output is extremely complex with it.

The point isn't about the implementation complexity, it's about
user expectations.  Simon was seemingly surprised by "an inconsistency"
(i.e., a case is not consistent with the others; i.e., there's
special/different case), so I think it's valid to discuss a bit and maybe
double check the rationale and see if we can save users from being confused too.
If after chatting a bit we come to the conclusion skipping the offsets
makes sense, than that's fine.  No harm done.

>>> I don't consider this a
>>> special case; I consider it to be the natural thing to do, because
>>> offsets don't make much sense in unions.
>>
>> Of course they do.  You can do 'offsetof(foo_union, foo_field)' just
>> fine, for example.  Saying that the offsets happen to be the same
>> is not the same as saying that the offsets don't exist.
> 
> I don't remember saying offsets don't exist in unions.  What I said is
> that in this specific case they don't matter/make much sense to be
> printed.

Guess we're discussing semantics, which is kind of pointless...
"Don't make sense" to me is like talking about what's the 
"weight of a mile", which is truly meaningless.  Stating that
all union members live at offset 0 is not meaningless, because
that's exactly how you define a union!

>> So from that angle, I see value in not printing the offsets
>> of union members.
> 
> Since it's still not clear whether the offsets should be printed or not
> in this case, and I am not a global maintainer, I adjusted the code to
> print them and will post the patch as a reply to the v4 e-mail.  This
> way you can decide which version is best.

Fun, just when I agreed with not printing the offsets... :-P  :-)

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-12  0:52                   ` Pedro Alves
@ 2017-12-12  1:25                     ` Simon Marchi
  2017-12-12 15:50                       ` John Baldwin
  0 siblings, 1 reply; 75+ messages in thread
From: Simon Marchi @ 2017-12-12  1:25 UTC (permalink / raw)
  To: Pedro Alves
  Cc: Sergio Durigan Junior, Simon Marchi, GDB Patches, Tom Tromey,
	Eli Zaretskii

On 2017-12-11 19:51, Pedro Alves wrote:
>>> So from that angle, I see value in not printing the offsets
>>> of union members.
>> 
>> Since it's still not clear whether the offsets should be printed or 
>> not
>> in this case, and I am not a global maintainer, I adjusted the code to
>> print them and will post the patch as a reply to the v4 e-mail.  This
>> way you can decide which version is best.
> 
> Fun, just when I agreed with not printing the offsets... :-P  :-)

Damn, sorry for starting you guys on that track!  I just wanted to know 
if it was intentional or not, I am fine with either.

Simon

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 2/2] Implement pahole-like 'ptype /o' option
  2017-12-11 23:24       ` Sergio Durigan Junior
@ 2017-12-12  1:32         ` Simon Marchi
  2017-12-12  6:19           ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Simon Marchi @ 2017-12-12  1:32 UTC (permalink / raw)
  To: Sergio Durigan Junior
  Cc: Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

On 2017-12-11 18:24, Sergio Durigan Junior wrote:
>>> +
>>> +   The output is strongly based on pahole(1).  */
>>> +
>>> +static void
>>> +c_print_type_struct_field_offset (struct type *type, unsigned int 
>>> field_idx,
>>> +				  unsigned int *endpos, struct ui_file *stream,
>>> +				  unsigned int offset_bitpos)
>>> +{
>>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, 
>>> field_idx));
>>> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
>>> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
>>> +  unsigned int fieldsize_bit;
>>> +
>>> +  if (*endpos > 0 && *endpos < bitpos)
>> 
>> Why do you check for *endpos > 0?  Did you see a case where *endpos is 
>> 0
>> and bitpos > 0?  That would mean that there's a "hole" before the 
>> first field.
>> Would we want to show it as a hole anyway?
> 
> Yeah, this situation happens when we have a virtual method in a class.
> Because of the vtable, the first field of the struct will start at
> offset 8 (for 64-bit architectures), and in this case *endpos will be 0
> because we won't have updated it, leading to a confusing message about 
> a
> 8-byte hole in the beginning of the struct:
> 
>  ...
>  50 /* offset    |  size */
>  51 type = struct abc {
>  52                          public:
>  53 /* XXX  8-byte hole  */
>  54 /*    8      |     8 */    void *field1;
>  ...
> 
> In order to suppress this first message, I check for *endpos > 0.
> 
> I will add a comment to the code explaining this scenario.

Ah ok that makes sense.  Yeah, a comment would be nice.  But now I'm 
thinking that it would be nice if GDB showed the vtable.  If I say the 
first field at offset 8, it would probably take me some time to get why, 
and would maybe think it's a bug.  But if we showed a fake field, such 
as:

   /*    0      |     8 */    /* vtable */

It would be immediately obvious.

Simon

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v4 2/2] Implement pahole-like 'ptype /o' option
  2017-12-11 23:44   ` [PATCH v4 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-12  0:27     ` Sergio Durigan Junior
@ 2017-12-12  1:59     ` Simon Marchi
  2017-12-12  3:39     ` Eli Zaretskii
  2 siblings, 0 replies; 75+ messages in thread
From: Simon Marchi @ 2017-12-12  1:59 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	André Pönitz, Keith Seitz

On 2017-12-11 06:43 PM, Sergio Durigan Junior wrote:
> This commit implements the pahole-like '/o' option for 'ptype', which
> prints the offsets and sizes of struct fields, reporting whenever
> there is a hole found.
> 
> The output is heavily based on pahole(1), with a few modifications
> here and there to adjust it to our reality.  Here's an example:
> 
>   (gdb) ptype /o stap_probe
>   /* offset    |  size */
>   struct stap_probe {
>   /*    0      |    40 */    struct probe {
>   /*    0      |     8 */        const probe_ops *pops;
>   /*    8      |     8 */        gdbarch *arch;
>   /*   16      |     8 */        const char *name;
>   /*   24      |     8 */        const char *provider;
>   /*   32      |     8 */        CORE_ADDR address;
> 			     } /* total size:   40 bytes */ p;
>   /*   40      |     8 */    CORE_ADDR sem_addr;
>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>   /* XXX  7-bit hole   */
>   /* XXX  7-byte hole  */
>   /*   56      |     8 */    union {
>   /*                 8 */        const char *text;
>   /*                 8 */        VEC_stap_probe_arg_s *vec;
> 			     } /* total size:    8 bytes */ args_u;
>   } /* total size:   64 bytes */
> 
> A big part of this patch handles the formatting logic of 'ptype',
> which is a bit messy.  I tried to be not very invasive, but I had to
> do some cleanups here and there to make life easier.
> 
> This patch is the start of a long-term work I'll do to flush the local
> patches we carry for Fedora GDB.  In this specific case, I'm aiming at
> upstreaming the feature implemented by the 'pahole.py' script that is
> shipped with Fedora GDB:
> 
>   <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>
> 
> This has been regression-tested on the BuildBot.  There's a new
> testcase for it, along with an update to the documentation.  I also
> thought it was worth mentioning this feature in the NEWS file.

That patch LGTM as-is, as well as with the union offsets :)

Simon

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v4 2/2] Implement pahole-like 'ptype /o' option
  2017-12-11 23:44   ` [PATCH v4 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-12  0:27     ` Sergio Durigan Junior
  2017-12-12  1:59     ` Simon Marchi
@ 2017-12-12  3:39     ` Eli Zaretskii
  2 siblings, 0 replies; 75+ messages in thread
From: Eli Zaretskii @ 2017-12-12  3:39 UTC (permalink / raw)
  To: Sergio Durigan Junior
  Cc: gdb-patches, tom, simon.marchi, palves, apoenitz, keiths

> From: Sergio Durigan Junior <sergiodj@redhat.com>
> Cc: Tom Tromey <tom@tromey.com>,	Eli Zaretskii <eliz@gnu.org>,	Simon Marchi <simon.marchi@ericsson.com>,	Pedro Alves <palves@redhat.com>,	André Pönitz <apoenitz@t-online.de>,	Keith Seitz <keiths@redhat.com>,	Sergio Durigan Junior <sergiodj@redhat.com>
> Date: Mon, 11 Dec 2017 18:43:45 -0500
> 
> +Issuing a @command{ptype /o union qwe} would print:

This should be

  Issuing the command @kbd{ptype /o union qwe} would print:

(The @command markup is for shell commands, like 'ls' and 'grep', not
for something the user types on the keyboard.)

Otherwise, the documentation parts are okay.

Thanks.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 2/2] Implement pahole-like 'ptype /o' option
  2017-12-12  1:32         ` Simon Marchi
@ 2017-12-12  6:19           ` Sergio Durigan Junior
  2017-12-12 18:14             ` Pedro Alves
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-12  6:19 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

On Monday, December 11 2017, Simon Marchi wrote:

> On 2017-12-11 18:24, Sergio Durigan Junior wrote:
>>>> +
>>>> +   The output is strongly based on pahole(1).  */
>>>> +
>>>> +static void
>>>> +c_print_type_struct_field_offset (struct type *type, unsigned int
>>>> field_idx,
>>>> +				  unsigned int *endpos, struct ui_file *stream,
>>>> +				  unsigned int offset_bitpos)
>>>> +{
>>>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type,
>>>> field_idx));
>>>> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
>>>> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
>>>> +  unsigned int fieldsize_bit;
>>>> +
>>>> +  if (*endpos > 0 && *endpos < bitpos)
>>>
>>> Why do you check for *endpos > 0?  Did you see a case where *endpos
>>> is 0
>>> and bitpos > 0?  That would mean that there's a "hole" before the
>>> first field.
>>> Would we want to show it as a hole anyway?
>>
>> Yeah, this situation happens when we have a virtual method in a class.
>> Because of the vtable, the first field of the struct will start at
>> offset 8 (for 64-bit architectures), and in this case *endpos will be 0
>> because we won't have updated it, leading to a confusing message
>> about a
>> 8-byte hole in the beginning of the struct:
>>
>>  ...
>>  50 /* offset    |  size */
>>  51 type = struct abc {
>>  52                          public:
>>  53 /* XXX  8-byte hole  */
>>  54 /*    8      |     8 */    void *field1;
>>  ...
>>
>> In order to suppress this first message, I check for *endpos > 0.
>>
>> I will add a comment to the code explaining this scenario.
>
> Ah ok that makes sense.  Yeah, a comment would be nice.  But now I'm
> thinking that it would be nice if GDB showed the vtable.  If I say the
> first field at offset 8, it would probably take me some time to get
> why, and would maybe think it's a bug.  But if we showed a fake field,
> such as:
>
>   /*    0      |     8 */    /* vtable */
>
> It would be immediately obvious.

OK, I did that.

BTW, a little status update.

Apparently the patch can't handle bitfields very well.  I've found a few
cases where the bitfield handling gets confused, printing wrong
offsets/sizes/holes.  Bitfields can be extremely complex to deal with
when it comes to offsets...

I spent hours trying to improve the patch, managed to make some
progress, but there are still corner cases to fix.  For example, the
patch doesn't deal well with this case:

struct aa {                                    
/*    0      |     1 */    char aa;            
/*    1: 1   |     1 */    unsigned char a : 7;                                                
/*    1:15   |     4 */    int b : 10;         
} /* total size:    4 bytes */                 

In this case, the bitfield "b" would be combined with the previous
bitfield "a", like pahole reports:

struct aa {                                    
        char                       aa;                   /*     0     1 */                     
        unsigned char              a:7;                  /*     1: 1  1 */                     

        /* Bitfield combined with previous fields */                                           

        int                        b:10;                 /*     0: 7  4 */                     
}

Confusing...  I'm not sure why pahole reports b's offset to be 0.

Also, the patch doesn't understand cases like this:

struct tyu
{
  unsigned int a1 : 1;
  unsigned int a2 : 3;
  unsigned int a9 : 23;
  /* PROBLEM HAPPENS HERE */
  unsigned char a0 : 2;
  uint64_t a3;
  unsigned int a4 : 5;
  uint64_t a5 : 3;
};

In this case, we're switching types in the middle of the bitfield.  The
bitfield "unsigned char a0" would be combined with the bitfields above
it, but the patch doesn't know that:

struct tyu {
/*    0:31   |     4 */    unsigned int a1 : 1;
/*    0:28   |     4 */    unsigned int a2 : 3;
/*    0: 5   |     4 */    unsigned int a9 : 23;
/*    3: 6   |     1 */    unsigned char a0 : 2;
/* XXX  3-bit hole   */
/* XXX  4-byte hole  */
/*    8      |     8 */    uint64_t a3;
/*   16:27   |     4 */    unsigned int a4 : 5;
/*   16:56   |     8 */    uint64_t a5 : 3;
} /* total size:   24 bytes */

Whereas pahole reports:

struct tyu {
        unsigned int               a1:1;                 /*     0:31  4 */
        unsigned int               a2:3;                 /*     0:28  4 */
        unsigned int               a9:23;                /*     0: 5  4 */

        /* XXX 253 bits hole, try to pack */
        /* Bitfield combined with next fields */

        unsigned char              a0:2;                 /*     3: 3  1 */

        /* XXX 6 bits hole, try to pack */
        /* XXX 4 bytes hole, try to pack */

        uint64_t                   a3;                   /*     8     8 */
        unsigned int               a4:5;                 /*    16:27  4 */
        uint64_t                   a5:3;                 /*    16:56  8 */
};

Hm, TBH pahole itself seems a bit confused here, saying there is a
253-bit hole...


Anyway, long story short, this is much more complex that I thought it
would be (TM).  I am extremely tired right now and can't continue, but I
intend to resume work tomorrow morning.  But I'd like to leave a few
options on the table:

1) Remove the patch from the 8.1 wishlist, which will unblock the
branching.

2) Remove the bitfield handling from the patch, leaving it
feature-incomplete, but working.

3) Push the patch with these known limitations, document them, mark them
as KFAIL in the testcase, and open a bug to fix them (I personally
wouldn't like this).

4) Wait more time until these issues are resolved.

WDYT?

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-12  1:25                     ` Simon Marchi
@ 2017-12-12 15:50                       ` John Baldwin
  2017-12-12 17:04                         ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: John Baldwin @ 2017-12-12 15:50 UTC (permalink / raw)
  To: Simon Marchi, Pedro Alves
  Cc: Sergio Durigan Junior, Simon Marchi, GDB Patches, Tom Tromey,
	Eli Zaretskii

On 12/11/17 8:25 PM, Simon Marchi wrote:
> On 2017-12-11 19:51, Pedro Alves wrote:
>>>> So from that angle, I see value in not printing the offsets
>>>> of union members.
>>>
>>> Since it's still not clear whether the offsets should be printed or 
>>> not
>>> in this case, and I am not a global maintainer, I adjusted the code to
>>> print them and will post the patch as a reply to the v4 e-mail.  This
>>> way you can decide which version is best.
>>
>> Fun, just when I agreed with not printing the offsets... :-P  :-)
> 
> Damn, sorry for starting you guys on that track!  I just wanted to know 
> if it was intentional or not, I am fine with either.

The only reason I would find the offset usable is to know the offset of
a structure member inside of a union.  E.g.:

struct foo {
     int x;
     union {
         struct {
             int y;
             int z;
         };
         int a;
     };
};

I think it is useful to know the offset of 'foo.z' within the overall
structure.

-- 
John Baldwin

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v2] Implement pahole-like 'ptype /o' option
  2017-12-12 15:50                       ` John Baldwin
@ 2017-12-12 17:04                         ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-12 17:04 UTC (permalink / raw)
  To: John Baldwin
  Cc: Simon Marchi, Pedro Alves, Simon Marchi, GDB Patches, Tom Tromey,
	Eli Zaretskii

On Tuesday, December 12 2017, John Baldwin wrote:

> On 12/11/17 8:25 PM, Simon Marchi wrote:
>> On 2017-12-11 19:51, Pedro Alves wrote:
>>>>> So from that angle, I see value in not printing the offsets
>>>>> of union members.
>>>>
>>>> Since it's still not clear whether the offsets should be printed or 
>>>> not
>>>> in this case, and I am not a global maintainer, I adjusted the code to
>>>> print them and will post the patch as a reply to the v4 e-mail.  This
>>>> way you can decide which version is best.
>>>
>>> Fun, just when I agreed with not printing the offsets... :-P  :-)
>> 
>> Damn, sorry for starting you guys on that track!  I just wanted to know 
>> if it was intentional or not, I am fine with either.
>
> The only reason I would find the offset usable is to know the offset of
> a structure member inside of a union.  E.g.:
>
> struct foo {
>      int x;
>      union {
>          struct {
>              int y;
>              int z;
>          };
>          int a;
>      };
> };
>
> I think it is useful to know the offset of 'foo.z' within the overall
> structure.

You'll have that:

(gdb) ptype /o struct foo
/* offset    |  size */
struct foo {
/*    0      |     4 */    int x;
/*    4      |     8 */    union {
/*                 8 */        struct {
/*    4      |     4 */            int y;
/*    8      |     4 */            int z;
                               } /* total size:    8 bytes */;
/*                 4 */        int a;
                           } /* total size:    8 bytes */;
} /* total size:   12 bytes */


-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 2/2] Implement pahole-like 'ptype /o' option
  2017-12-12  6:19           ` Sergio Durigan Junior
@ 2017-12-12 18:14             ` Pedro Alves
  2017-12-12 18:40               ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-12 18:14 UTC (permalink / raw)
  To: Sergio Durigan Junior, Simon Marchi
  Cc: Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

On 12/12/2017 06:19 AM, Sergio Durigan Junior wrote:

> BTW, a little status update.
> 
> Apparently the patch can't handle bitfields very well.  I've found a few
> cases where the bitfield handling gets confused, printing wrong
> offsets/sizes/holes.  Bitfields can be extremely complex to deal with
> when it comes to offsets...
> 
> I spent hours trying to improve the patch, managed to make some
> progress, but there are still corner cases to fix.  For example, the
> patch doesn't deal well with this case:
> 
> struct aa {                                    
> /*    0      |     1 */    char aa;            
> /*    1: 1   |     1 */    unsigned char a : 7;                                                
> /*    1:15   |     4 */    int b : 10;         
> } /* total size:    4 bytes */                 
> 
> In this case, the bitfield "b" would be combined with the previous
> bitfield "a", like pahole reports:
> 
> struct aa {                                    
>         char                       aa;                   /*     0     1 */                     
>         unsigned char              a:7;                  /*     1: 1  1 */                     
> 
>         /* Bitfield combined with previous fields */                                           
> 
>         int                        b:10;                 /*     0: 7  4 */                     
> }
> 
> Confusing...  I'm not sure why pahole reports b's offset to be 0.

0 seems right to me.  The bitfield's type is int, with size 4,
and it lives at byte offset 0:

 <2><53>: Abbrev Number: 5 (DW_TAG_member)
    <54>   DW_AT_name        : (indirect string, offset: 0x4ae): b
    <58>   DW_AT_decl_file   : 1
    <59>   DW_AT_decl_line   : 7
    <5a>   DW_AT_type        : <0x71>
    <5e>   DW_AT_byte_size   : 4
    <5f>   DW_AT_bit_size    : 10
    <60>   DW_AT_bit_offset  : 7
    <61>   DW_AT_data_member_location: 0     <<<

Replacing the 0 with 1, like:

    int                        b:10;                 /*     1: 7  4 */

would look incorrect to me, because that'd make '(char*)&aa + 1 + 4'
(address of containing object + byte offset + byte size) overshoot the
size of the containing object.

What is that number after ":"  in bitfields supposed to mean in
pahole's output (and I assume that that's what you're trying
to emulate)?  We're missing documentation for that.

It seems like it's supposed to mean the number of bits left in the
containing anonymous object (i.e., in the 4 bytes of the declared
int)?  Then "0:7" seems right, given:

sizeof (int) * 8 - bitsof(aa.a) - bitsof(aa.a) - bits(aa.b)
   => sizeof (int) * 8 - 7 - 10 
   => 7

It took me a while to get to this conclusion (and writing a lot
of text that I ended up deleting... :-P), because I originally
assumed that this was meant to be the field's bit offset.

> 
> Also, the patch doesn't understand cases like this:
> 
> struct tyu
> {
>   unsigned int a1 : 1;
>   unsigned int a2 : 3;
>   unsigned int a9 : 23;
>   /* PROBLEM HAPPENS HERE */
>   unsigned char a0 : 2;
>   uint64_t a3;
>   unsigned int a4 : 5;
>   uint64_t a5 : 3;
> };
> 
> In this case, we're switching types in the middle of the bitfield.  The
> bitfield "unsigned char a0" would be combined with the bitfields above
> it, but the patch doesn't know that:
> 
> struct tyu {
> /*    0:31   |     4 */    unsigned int a1 : 1;
> /*    0:28   |     4 */    unsigned int a2 : 3;
> /*    0: 5   |     4 */    unsigned int a9 : 23;
> /*    3: 6   |     1 */    unsigned char a0 : 2;
> /* XXX  3-bit hole   */
> /* XXX  4-byte hole  */
> /*    8      |     8 */    uint64_t a3;
> /*   16:27   |     4 */    unsigned int a4 : 5;
> /*   16:56   |     8 */    uint64_t a5 : 3;
> } /* total size:   24 bytes */
> 
> Whereas pahole reports:
> 
> struct tyu {
>         unsigned int               a1:1;                 /*     0:31  4 */
>         unsigned int               a2:3;                 /*     0:28  4 */
>         unsigned int               a9:23;                /*     0: 5  4 */
> 
>         /* XXX 253 bits hole, try to pack */
>         /* Bitfield combined with next fields */
> 
>         unsigned char              a0:2;                 /*     3: 3  1 */
> 
>         /* XXX 6 bits hole, try to pack */
>         /* XXX 4 bytes hole, try to pack */
> 
>         uint64_t                   a3;                   /*     8     8 */
>         unsigned int               a4:5;                 /*    16:27  4 */
>         uint64_t                   a5:3;                 /*    16:56  8 */
> };
> 
> Hm, TBH pahole itself seems a bit confused here, saying there is a
> 253-bit hole...

A 253-bit hole does look odd.

> 
> 
> Anyway, long story short, this is much more complex that I thought it
> would be (TM).  I am extremely tired right now and can't continue, but I
> intend to resume work tomorrow morning.  But I'd like to leave a few
> options on the table:
> 
> 1) Remove the patch from the 8.1 wishlist, which will unblock the
> branching.
> 
> 2) Remove the bitfield handling from the patch, leaving it
> feature-incomplete, but working.
> 
> 3) Push the patch with these known limitations, document them, mark them
> as KFAIL in the testcase, and open a bug to fix them (I personally
> wouldn't like this).
> 
> 4) Wait more time until these issues are resolved.
> 

Joel said that we'd re-evaluate Wednesday, so there's still some time.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 2/2] Implement pahole-like 'ptype /o' option
  2017-12-12 18:14             ` Pedro Alves
@ 2017-12-12 18:40               ` Sergio Durigan Junior
  2017-12-12 20:12                 ` Pedro Alves
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-12 18:40 UTC (permalink / raw)
  To: Pedro Alves
  Cc: Simon Marchi, Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

Thanks for looking into this.

On Tuesday, December 12 2017, Pedro Alves wrote:

> On 12/12/2017 06:19 AM, Sergio Durigan Junior wrote:
>
>> BTW, a little status update.
>> 
>> Apparently the patch can't handle bitfields very well.  I've found a few
>> cases where the bitfield handling gets confused, printing wrong
>> offsets/sizes/holes.  Bitfields can be extremely complex to deal with
>> when it comes to offsets...
>> 
>> I spent hours trying to improve the patch, managed to make some
>> progress, but there are still corner cases to fix.  For example, the
>> patch doesn't deal well with this case:
>> 
>> struct aa {                                    
>> /*    0      |     1 */    char aa;            
>> /*    1: 1   |     1 */    unsigned char a : 7;                                                
>> /*    1:15   |     4 */    int b : 10;         
>> } /* total size:    4 bytes */                 
>> 
>> In this case, the bitfield "b" would be combined with the previous
>> bitfield "a", like pahole reports:
>> 
>> struct aa {                                    
>>         char                       aa;                   /*     0     1 */                     
>>         unsigned char              a:7;                  /*     1: 1  1 */                     
>> 
>>         /* Bitfield combined with previous fields */                                           
>> 
>>         int                        b:10;                 /*     0: 7  4 */                     
>> }
>> 
>> Confusing...  I'm not sure why pahole reports b's offset to be 0.
>
> 0 seems right to me.  The bitfield's type is int, with size 4,
> and it lives at byte offset 0:
>
>  <2><53>: Abbrev Number: 5 (DW_TAG_member)
>     <54>   DW_AT_name        : (indirect string, offset: 0x4ae): b
>     <58>   DW_AT_decl_file   : 1
>     <59>   DW_AT_decl_line   : 7
>     <5a>   DW_AT_type        : <0x71>
>     <5e>   DW_AT_byte_size   : 4
>     <5f>   DW_AT_bit_size    : 10
>     <60>   DW_AT_bit_offset  : 7
>     <61>   DW_AT_data_member_location: 0     <<<
>
> Replacing the 0 with 1, like:
>
>     int                        b:10;                 /*     1: 7  4 */
>
> would look incorrect to me, because that'd make '(char*)&aa + 1 + 4'
> (address of containing object + byte offset + byte size) overshoot the
> size of the containing object.

OK.  The GDB patch is printing "1:15" because the offset is calculated
as:

  (bitpos + offset_bitpos) / TARGET_CHAR_BIT

In this particular case, offset_bitpos is zero because we're not inside
an inner struct, so we don't care about it.  bitpos is TYPE_FIELD_BITPOS
(type, field_idx), which is 15 in this case, because sizeof (aa.aa) +
bitsof (aa.a) = 15.  When we divide it by TARGET_CHAR_BIT, we get 1.  It
seems using TYPE_FIELD_BITPOS is not reliable when dealing with
bitfields.

So there is something I'm not accounting here.

> What is that number after ":"  in bitfields supposed to mean in
> pahole's output (and I assume that that's what you're trying
> to emulate)?  We're missing documentation for that.

Yeah.  I tried to find documentation on the output, but couldn't.
Yesterday I was reading pahole's code.  From what I understand, it is
the number of bits left in the object.

> It seems like it's supposed to mean the number of bits left in the
> containing anonymous object (i.e., in the 4 bytes of the declared
> int)?  Then "0:7" seems right, given:
>
> sizeof (int) * 8 - bitsof(aa.a) - bitsof(aa.a) - bits(aa.b)
>    => sizeof (int) * 8 - 7 - 10 
>    => 7

I guess you meant:

  sizeof (int) * 8 - bitsof (aa.a) - bitsof(aa.b)

Right?

But yeah, it makes sense when you consider that the first bitfield got
"promoted" from "unsigned char" to "int".  I haven't figured out a way
to keep track of these type changes in order to calculate the right
offsets, remaining space and holes.

> It took me a while to get to this conclusion (and writing a lot
> of text that I ended up deleting... :-P), because I originally
> assumed that this was meant to be the field's bit offset.
>
>> 
>> Also, the patch doesn't understand cases like this:
>> 
>> struct tyu
>> {
>>   unsigned int a1 : 1;
>>   unsigned int a2 : 3;
>>   unsigned int a9 : 23;
>>   /* PROBLEM HAPPENS HERE */
>>   unsigned char a0 : 2;
>>   uint64_t a3;
>>   unsigned int a4 : 5;
>>   uint64_t a5 : 3;
>> };
>> 
>> In this case, we're switching types in the middle of the bitfield.  The
>> bitfield "unsigned char a0" would be combined with the bitfields above
>> it, but the patch doesn't know that:
>> 
>> struct tyu {
>> /*    0:31   |     4 */    unsigned int a1 : 1;
>> /*    0:28   |     4 */    unsigned int a2 : 3;
>> /*    0: 5   |     4 */    unsigned int a9 : 23;
>> /*    3: 6   |     1 */    unsigned char a0 : 2;
>> /* XXX  3-bit hole   */
>> /* XXX  4-byte hole  */
>> /*    8      |     8 */    uint64_t a3;
>> /*   16:27   |     4 */    unsigned int a4 : 5;
>> /*   16:56   |     8 */    uint64_t a5 : 3;
>> } /* total size:   24 bytes */
>> 
>> Whereas pahole reports:
>> 
>> struct tyu {
>>         unsigned int               a1:1;                 /*     0:31  4 */
>>         unsigned int               a2:3;                 /*     0:28  4 */
>>         unsigned int               a9:23;                /*     0: 5  4 */
>> 
>>         /* XXX 253 bits hole, try to pack */
>>         /* Bitfield combined with next fields */
>> 
>>         unsigned char              a0:2;                 /*     3: 3  1 */
>> 
>>         /* XXX 6 bits hole, try to pack */
>>         /* XXX 4 bytes hole, try to pack */
>> 
>>         uint64_t                   a3;                   /*     8     8 */
>>         unsigned int               a4:5;                 /*    16:27  4 */
>>         uint64_t                   a5:3;                 /*    16:56  8 */
>> };
>> 
>> Hm, TBH pahole itself seems a bit confused here, saying there is a
>> 253-bit hole...
>
> A 253-bit hole does look odd.

Yes.  I haven't checked all the offsets too; there may be
inconsistencies.

>> 
>> 
>> Anyway, long story short, this is much more complex that I thought it
>> would be (TM).  I am extremely tired right now and can't continue, but I
>> intend to resume work tomorrow morning.  But I'd like to leave a few
>> options on the table:
>> 
>> 1) Remove the patch from the 8.1 wishlist, which will unblock the
>> branching.
>> 
>> 2) Remove the bitfield handling from the patch, leaving it
>> feature-incomplete, but working.
>> 
>> 3) Push the patch with these known limitations, document them, mark them
>> as KFAIL in the testcase, and open a bug to fix them (I personally
>> wouldn't like this).
>> 
>> 4) Wait more time until these issues are resolved.
>> 
>
> Joel said that we'd re-evaluate Wednesday, so there's still some time.

OK, I'll keep trying here.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v3 2/2] Implement pahole-like 'ptype /o' option
  2017-12-12 18:40               ` Sergio Durigan Junior
@ 2017-12-12 20:12                 ` Pedro Alves
  0 siblings, 0 replies; 75+ messages in thread
From: Pedro Alves @ 2017-12-12 20:12 UTC (permalink / raw)
  To: Sergio Durigan Junior
  Cc: Simon Marchi, Simon Marchi, GDB Patches, Tom Tromey, Eli Zaretskii

On 12/12/2017 06:40 PM, Sergio Durigan Junior wrote:
> On Tuesday, December 12 2017, Pedro Alves wrote:
> 
>> On 12/12/2017 06:19 AM, Sergio Durigan Junior wrote:
>>
>>> Apparently the patch can't handle bitfields very well.  I've found a few
>>> cases where the bitfield handling gets confused, printing wrong
>>> offsets/sizes/holes.  Bitfields can be extremely complex to deal with
>>> when it comes to offsets...
>>>
>>> I spent hours trying to improve the patch, managed to make some
>>> progress, but there are still corner cases to fix.  For example, the
>>> patch doesn't deal well with this case:
>>>
>>> struct aa {                                    
>>> /*    0      |     1 */    char aa;            
>>> /*    1: 1   |     1 */    unsigned char a : 7;                                                
>>> /*    1:15   |     4 */    int b : 10;         
>>> } /* total size:    4 bytes */                 
>>>
>>> In this case, the bitfield "b" would be combined with the previous
>>> bitfield "a", like pahole reports:
>>>
>>> struct aa {                                    
>>>         char                       aa;                   /*     0     1 */                     
>>>         unsigned char              a:7;                  /*     1: 1  1 */                     
>>>
>>>         /* Bitfield combined with previous fields */                                           
>>>
>>>         int                        b:10;                 /*     0: 7  4 */                     
>>> }
>>>
>>> Confusing...  I'm not sure why pahole reports b's offset to be 0.
>>
>> 0 seems right to me.  The bitfield's type is int, with size 4,
>> and it lives at byte offset 0:
>>
>>  <2><53>: Abbrev Number: 5 (DW_TAG_member)
>>     <54>   DW_AT_name        : (indirect string, offset: 0x4ae): b
>>     <58>   DW_AT_decl_file   : 1
>>     <59>   DW_AT_decl_line   : 7
>>     <5a>   DW_AT_type        : <0x71>
>>     <5e>   DW_AT_byte_size   : 4
>>     <5f>   DW_AT_bit_size    : 10
>>     <60>   DW_AT_bit_offset  : 7
>>     <61>   DW_AT_data_member_location: 0     <<<
>>
>> Replacing the 0 with 1, like:
>>
>>     int                        b:10;                 /*     1: 7  4 */
>>
>> would look incorrect to me, because that'd make '(char*)&aa + 1 + 4'
>> (address of containing object + byte offset + byte size) overshoot the
>> size of the containing object.
> 
> OK.  The GDB patch is printing "1:15" because the offset is calculated
> as:
> 
>   (bitpos + offset_bitpos) / TARGET_CHAR_BIT
> 
> In this particular case, offset_bitpos is zero because we're not inside
> an inner struct, so we don't care about it.  bitpos is TYPE_FIELD_BITPOS
> (type, field_idx), which is 15 in this case, because sizeof (aa.aa) +
> bitsof (aa.a) = 15.  

Well, actually it's 15 because that's the bit number of the LSB of that
field, and x86 is a !gdbarch_bits_big_endian machine.  Because we have:

 <2><53>: Abbrev Number: 4 (DW_TAG_member)
    <54>   DW_AT_name        : b
    <56>   DW_AT_decl_file   : 1
    <57>   DW_AT_decl_line   : 7
    <58>   DW_AT_type        : <0x6f>
    <5c>   DW_AT_byte_size   : 4
    <5d>   DW_AT_bit_size    : 10
    <5e>   DW_AT_bit_offset  : 7
    <5f>   DW_AT_data_member_location: 0

and with that, we reach here:

static void
dwarf2_add_field (struct field_info *fip, struct die_info *die,
		  struct dwarf2_cu *cu)
{
...
      attr = dwarf2_attr (die, DW_AT_bit_offset, cu);
      if (attr)
	{
	  if (gdbarch_bits_big_endian (gdbarch))
	    {
...
	    }
	  else
	    {
...
	      attr = dwarf2_attr (die, DW_AT_byte_size, cu);
	      if (attr)
		{
		  /* The size of the anonymous object containing
		     the bit field is explicit, so use the
		     indicated size (in bytes).  */
		  anonymous_size = DW_UNSND (attr);
		}
	      else
		{
...
		}
	      SET_FIELD_BITPOS (*fp,
				(FIELD_BITPOS (*fp)
				 + anonymous_size * bits_per_byte
				 - bit_offset - FIELD_BITSIZE (*fp)));
	    }
	}

which gives us:

  bitpos = DW_AT_byte_size * 8 - DW_AT_bit_offset - DW_AT_bit_size
         = 4 * 8 - 7 - 10
         = 15

and since x86 is a !gdbarch_bits_big_endian machine, 15 goes here:

 byte:  0     1      2      3
 bits:  7-0   15-8   23-16   31-24
              ^

which is what we see if we write a "1" to aa.b:

 (gdb) p aa.b = 1
 $1 = 1
 (gdb) x /4xb &aa
 0x60103c <aa>:  0x00    0x80    0x00    0x00
                         ^^^^

> When we divide it by TARGET_CHAR_BIT, we get 1.  It
> seems using TYPE_FIELD_BITPOS is not reliable when dealing with
> bitfields.

I noticed:

 (gdb) p /x & aa.aa
 $1 = 0x601040
 (gdb) p /x & aa.a
 $2 = 0x601041
 (gdb) p /x & aa.b
 $3 = 0x601040
 (gdb) 

Ignoring the fact that taking the address of bitfields is
not valid C/C++ [ :-) ], it seems like the addresses above
match what we'd expect the byte offset to be.  At least for this
example.

So maybe what you need is to factor out or duplicate the logic
used by the related value printing code.  Here in
value_primitive_field:

  if (TYPE_FIELD_BITSIZE (arg_type, fieldno))
    {
      /* Handle packed fields.

...
      LONGEST bitpos = TYPE_FIELD_BITPOS (arg_type, fieldno);
      LONGEST container_bitsize = TYPE_LENGTH (type) * 8;

      v = allocate_value_lazy (type);
      v->bitsize = TYPE_FIELD_BITSIZE (arg_type, fieldno);
      if ((bitpos % container_bitsize) + v->bitsize <= container_bitsize
	  && TYPE_LENGTH (type) <= (int) sizeof (LONGEST))
	v->bitpos = bitpos % container_bitsize;
      else
	v->bitpos = bitpos % 8;
      v->offset = (value_embedded_offset (arg1)
		   + offset
		   + (bitpos - v->bitpos) / 8);
    }

Above "v->offset" may be the byte offset that you're after.

> 
> So there is something I'm not accounting here.
> 

>> What is that number after ":"  in bitfields supposed to mean in
>> pahole's output (and I assume that that's what you're trying
>> to emulate)?  We're missing documentation for that.
> 
> Yeah.  I tried to find documentation on the output, but couldn't.
> Yesterday I was reading pahole's code.  From what I understand, it is
> the number of bits left in the object.
> 
>> It seems like it's supposed to mean the number of bits left in the
>> containing anonymous object (i.e., in the 4 bytes of the declared
>> int)?  Then "0:7" seems right, given:
>>
>> sizeof (int) * 8 - bitsof(aa.a) - bitsof(aa.a) - bits(aa.b)
>>    => sizeof (int) * 8 - 7 - 10 
>>    => 7
> 
> I guess you meant:
> 
>   sizeof (int) * 8 - bitsof (aa.a) - bitsof(aa.b)

No, because that's be

     => 32 - 7 - 10
     => 15

I meant:

 sizeof (int) * 8 - bitsof(aa.aa) - bitsof(aa.a) - bits(aa.b)
    => sizeof (int) * 8 - 8 - 7 - 10 
    => 7

which gives us the 7 unused bits that pahole reports.

> 
> Right?
> 
> But yeah, it makes sense when you consider that the first bitfield got
> "promoted" from "unsigned char" to "int".  I haven't figured out a way
> to keep track of these type changes in order to calculate the right
> offsets, remaining space and holes.

Do we really need to track type changes, or do we simply need to
detect that the byte offset moved in the negative direction?
I.e., here:

struct S {                                    
        char                       aa;                   /*     0     1 */                     
        unsigned char              a:7;                  /*     1: 1  1 */                     

        /* Bitfield combined with previous fields */                                           

        int                        b:10;                 /*     0: 7  4 */                     
};

The byte offset went "0 -> 1 -> 0", and the "1 -> 0" would mean that the
bitfield was "combined"?

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13  3:17 ` [PATCH v5 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-13  3:17   ` Sergio Durigan Junior
  2017-12-13  4:50     ` Simon Marchi
                       ` (3 more replies)
  2017-12-13  3:17   ` [PATCH v5 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
  1 sibling, 4 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13  3:17 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	Keith Seitz, Sergio Durigan Junior

This commit implements the pahole-like '/o' option for 'ptype', which
prints the offsets and sizes of struct fields, reporting whenever
there is a hole found.

The output is heavily based on pahole(1), with a few modifications
here and there to adjust it to our reality.  Here's an example:

  (gdb) ptype /o stap_probe
  /* offset    |  size */
  struct stap_probe {
  /*    0      |    40 */    struct probe {
  /*    0      |     8 */        const probe_ops *pops;
  /*    8      |     8 */        gdbarch *arch;
  /*   16      |     8 */        const char *name;
  /*   24      |     8 */        const char *provider;
  /*   32      |     8 */        CORE_ADDR address;
			     } /* total size:   40 bytes */ p;
  /*   40      |     8 */    CORE_ADDR sem_addr;
  /*   48:31   |     4 */    unsigned int args_parsed : 1;
  /* XXX  7-bit hole   */
  /* XXX  7-byte hole  */
  /*   56      |     8 */    union {
  /*                 8 */        const char *text;
  /*                 8 */        VEC_stap_probe_arg_s *vec;
			     } /* total size:    8 bytes */ args_u;
  } /* total size:   64 bytes */

A big part of this patch handles the formatting logic of 'ptype',
which is a bit messy.  I tried to be not very invasive, but I had to
do some cleanups here and there to make life easier.

This patch is the start of a long-term work I'll do to flush the local
patches we carry for Fedora GDB.  In this specific case, I'm aiming at
upstreaming the feature implemented by the 'pahole.py' script that is
shipped with Fedora GDB:

  <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>

This has been regression-tested on the BuildBot.  There's a new
testcase for it, along with an update to the documentation.  I also
thought it was worth mentioning this feature in the NEWS file.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Pedro Alves  <palves@redhat.com>

	PR cli/16224
	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
	* c-typeprint.c (OFFSET_SPC_LEN): New define.
	(print_spaces_filtered_with_print_options): New function.
	(output_access_specifier): Take new argument FLAGS.  Modify
	function to call 'print_spaces_filtered_with_print_options'.
	(c_print_type_vtable_offset_marker): New function.
	(c_print_type_union_field_offset): New function.
	(c_print_type_struct_field_offset): New function.
	(c_type_print_base_struct_union): Print offsets and sizes for
	struct/union/class fields.
	* typeprint.c (const struct type_print_options
	type_print_raw_options): Initialize 'print_offsets' and
	'offset_bitpos'.
	(static struct type_print_options default_ptype_flags):
	Likewise.
	(whatis_exp): Handle '/o' option.
	(_initialize_typeprint): Add '/o' flag to ptype's help.
	* typeprint.h (struct type_print_options) <print_offsets>: New
	field.
	<offset_bitpos>: Likewise.

gdb/testsuite/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.base/ptype-offsets.cc: New file.
	* gdb.base/ptype-offsets.exp: New file.

gdb/doc/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.texinfo (ptype): Add documentation for new flag '/o'.
---
 gdb/NEWS                                 |   3 +
 gdb/c-typeprint.c                        | 248 ++++++++++++++++++++++++++++---
 gdb/doc/gdb.texinfo                      | 115 ++++++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.cc  | 158 ++++++++++++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.exp | 192 ++++++++++++++++++++++++
 gdb/typeprint.c                          |  15 +-
 gdb/typeprint.h                          |   9 ++
 7 files changed, 719 insertions(+), 21 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index c6fe297159..67d40d007b 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * New "--readnever" command line option instructs GDB to not read each
   symbol file's symbolic debug information.  This makes startup faster
   but at the expense of not being able to perform symbolic debugging.
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index ce10b5e005..cd21cb2172 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options
+  (int level, struct ui_file *stream, const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,15 +890,132 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
-/* Return true is an access label (i.e., "public:", "private:",
+static void
+c_print_type_vtable_offset_marker (struct type *type, unsigned int field_idx,
+				   struct ui_file *stream,
+				   unsigned int offset_bitpos)
+{
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int offset = (bitpos + offset_bitpos) / TARGET_CHAR_BIT;
+
+  fprintf_filtered (stream, "/* %4u     |   %4u */ /* vtable */\n", offset,
+		    TYPE_LENGTH (TYPE_FIELD_TYPE (type, field_idx)));
+}
+
+/* Print information about field at index FIELD_IDX of the union type
+   TYPE.  Since union fields don't have the concept of offsets, we
+   just print their sizes.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
+				 struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+
+  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+}
+
+/* Print information about field at index FIELD_IDX of the struct type
+   TYPE.
+
+   ENDPOS is the one-past-the-end bit position of the previous field
+   (where we expect this field to be if there is no hole).  At the
+   end, ENDPOS is updated to the one-past-the-end bit position of the
+   current field.
+
+   OFFSET_BITPOS is the offset value we carry over when we are
+   printing a struct that is inside another struct; this is useful so
+   that the offset is constantly incremented (if we didn't carry it
+   over, the offset would be reset to zero when printing the inner
+   struct).
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
+				  unsigned int *endpos, struct ui_file *stream,
+				  unsigned int offset_bitpos)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+
+  /* We check for *ENDPOS > 0 because there is a specific scenario
+     when *ENDPOS can be zero and BITPOS can be > 0: when we are
+     dealing with a struct/class with a virtual method.  Because of
+     the vtable, the first field of the struct/class will have an
+     offset of sizeof (void *) (the size of the vtable).  If we do not
+     check for *ENDPOS > 0 here, GDB will report a hole before the
+     first field, which is not accurate.  */
+  if (*endpos > 0 && *endpos < bitpos)
+    {
+      /* If ENDPOS is smaller than the current type's bitpos, it means
+	 there's a hole in the struct, so we report it here.  */
+      unsigned int hole = bitpos - *endpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      unsigned int bitsize = TYPE_FIELD_BITSIZE (type, field_idx);
+      /* The bitpos relative to the beginning of our container
+	 field.  */
+      unsigned int relative_bitpos;
+
+      /* The following was copied from
+	 value.c:value_primitive_field.  */
+      if ((bitpos % fieldsize_bit) + bitsize <= fieldsize_bit)
+	relative_bitpos = bitpos % fieldsize_bit;
+      else
+	relative_bitpos = bitpos % TARGET_CHAR_BIT;
+
+      /* This is the exact offset (in bits) of this bitfield.  */
+      unsigned int bit_offset
+	= (bitpos - relative_bitpos) + offset_bitpos;
+
+      /* The position of the field, relative to the beginning of the
+	 struct, and how many bits are left to be used in this
+	 container.  */
+      fprintf_filtered (stream, "/* %4u:%2u", bit_offset / TARGET_CHAR_BIT,
+			fieldsize_bit - (relative_bitpos + bitsize));
+      fieldsize_bit = bitsize;
+    }
+  else
+    {
+      /* The position of the field, relative to the beginning of the
+	 struct.  */
+      fprintf_filtered (stream, "/* %4u",
+			(bitpos + offset_bitpos) / TARGET_CHAR_BIT);
+
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  *endpos = bitpos + fieldsize_bit;
+}
+
+/* Return true if an access label (i.e., "public:", "private:",
    "protected:") needs to be printed for TYPE.  */
 
 static bool
@@ -1032,6 +1172,8 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
       int len = TYPE_NFIELDS (type);
       vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      unsigned int endpos = 0;
+
       for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
 	{
 	  QUIT;
@@ -1041,25 +1183,63 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	     the debug info, they should be artificial.  */
 	  if ((i == vptr_fieldno && type == basetype)
 	      || TYPE_FIELD_ARTIFICIAL (type, i))
-	    continue;
+	    {
+	      if (flags->print_offsets)
+		c_print_type_vtable_offset_marker (type, i, stream,
+						   flags->offset_bitpos);
+	      continue;
+	    }
 
 	  if (need_access_label)
 	    {
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FIELD_PROTECTED (type, i),
-		 TYPE_FIELD_PRIVATE (type, i));
+		 TYPE_FIELD_PRIVATE (type, i), flags);
+	    }
+
+	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+	  if (flags->print_offsets)
+	    {
+	      if (!is_static)
+		{
+		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		    c_print_type_struct_field_offset (type, i, &endpos, stream,
+						      flags->offset_bitpos);
+		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
+		    c_print_type_union_field_offset (type, i, stream);
+		}
+	      else
+		print_spaces_filtered (OFFSET_SPC_LEN, stream);
 	    }
 
 	  print_spaces_filtered (level + 4, stream);
-	  if (field_is_static (&TYPE_FIELD (type, i)))
+	  if (is_static)
 	    fprintf_filtered (stream, "static ");
+
+	  int newshow = show - 1;
+
+	  if (flags->print_offsets
+	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
+		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
+	    {
+	      /* If we're printing offsets and this field's type is
+		 either a struct or an union, then we're interested in
+		 expanding it.  */
+	      ++newshow;
+
+	      /* Make sure we carry our offset when we expand the
+		 struct.  */
+	      local_flags.offset_bitpos
+		= flags->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
+	    }
+
 	  c_print_type (TYPE_FIELD_TYPE (type, i),
 			TYPE_FIELD_NAME (type, i),
-			stream, show - 1, level + 4,
+			stream, newshow, level + 4,
 			&local_flags);
-	  if (!field_is_static (&TYPE_FIELD (type, i))
-	      && TYPE_FIELD_PACKED (type, i))
+	  if (!is_static && TYPE_FIELD_PACKED (type, i))
 	    {
 	      /* It is a bitfield.  This code does not attempt
 		 to look at the bitpos and reconstruct filler,
@@ -1122,9 +1302,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FN_FIELD_PROTECTED (f, j),
-		 TYPE_FN_FIELD_PRIVATE (f, j));
+		 TYPE_FN_FIELD_PRIVATE (f, j), flags);
 
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
 		fprintf_filtered (stream, "virtual ");
 	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
@@ -1143,9 +1324,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		       && !is_full_physname_constructor  /* " " */
 		       && !is_type_conversion_operator (type, i, j))
 		{
+		  unsigned int old_po = local_flags.print_offsets;
+
+		  /* Temporarily disable print_offsets, because it
+		     would mess with indentation.  */
+		  local_flags.print_offsets = 0;
 		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
 				"", stream, -1, 0,
 				&local_flags);
+		  local_flags.print_offsets = old_po;
+
 		  fputs_filtered (" ", stream);
 		}
 	      if (TYPE_FN_FIELD_STUB (f, j))
@@ -1226,9 +1414,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
 	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
 	    {
-	      print_spaces_filtered (level + 4, stream);
+	      unsigned int old_po = semi_local_flags.print_offsets;
+
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      semi_local_flags.print_offsets = 0;
 	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
 			    "", stream, show, level + 4, &semi_local_flags);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
@@ -1254,11 +1449,17 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		  section_type = output_access_specifier
 		    (stream, section_type, level,
 		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i), flags);
 		}
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      fprintf_filtered (stream, "typedef ");
 
+	      unsigned int old_po = semi_local_flags.print_offsets;
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      semi_local_flags.print_offsets = 0;
+
 	      /* We want to print typedefs with substitutions
 		 from the template parameters or globally-known
 		 typedefs but not local typedefs.  */
@@ -1266,13 +1467,21 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 			    TYPE_TYPEDEF_FIELD_NAME (type, i),
 			    stream, show - 1, level + 4,
 			    &semi_local_flags);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
 
+      if (flags->print_offsets && level > 0)
+	print_spaces_filtered (OFFSET_SPC_LEN, stream);
+
       fprintfi_filtered (level, stream, "}");
     }
 
+  if (show > 0 && flags->print_offsets)
+    fprintf_filtered (stream, " /* total size: %4u bytes */",
+		      TYPE_LENGTH (type));
+
   do_cleanups (local_cleanups);
 }
 
@@ -1300,7 +1509,6 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 {
   int i;
   int len;
-  int j;
 
   QUIT;
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 062f01d5ff..9d58f2b7d0 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17211,6 +17211,121 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.
+
+For example, given the following declarations:
+
+@smallexample
+struct tuv
+@{
+  int a1;
+  char *a2;
+  int a3;
+@};
+
+struct xyz
+@{
+  int f1;
+  char f2;
+  void *f3;
+  struct tuv f4;
+@};
+
+union qwe
+@{
+  struct tuv fff1;
+  struct xyz fff2;
+@};
+
+struct tyu
+@{
+  int a1 : 1;
+  int a2 : 3;
+  int a3 : 23;
+  char a4 : 2;
+  int64_t a5;
+  int a6 : 5;
+  int64_t a7 : 3;
+@};
+@end smallexample
+
+Issuing a @kbd{ptype /o struct tuv} would print:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tuv
+/* offset    |  size */
+struct tuv @{
+/*    0      |     4 */    int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */    char *a2;
+/*   16      |     4 */    int a3;
+@} /* total size:   24 bytes */
+@end smallexample
+
+Notice the format of the first column of comments.  There, you can
+find two parts separated by the @code{|} character: the @emph{offset},
+which indicates where the field is located inside the struct, in
+bytes, and the @emph{size} of the field.  Another interesting line is
+the marker of a @emph{hole} in the struct, indicating that it may be
+possible to pack the struct and make it use less space by reorganizing
+its fields.
+
+It is also possible to print offsets inside an union:
+
+@smallexample
+(@value{GDBP}) ptype /o union qwe
+/* offset    |  size */
+union qwe @{
+/*                24 */    struct tuv @{
+/*    0      |     4 */        int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */        char *a2;
+/*   16      |     4 */        int a3;
+                           @} /* total size:   24 bytes */ fff1;
+/*                40 */    struct xyz @{
+/*    0      |     4 */        int f1;
+/*    4      |     1 */        char f2;
+/* XXX  3-byte hole  */
+/*    8      |     8 */        void *f3;
+/*   16      |    24 */        struct tuv @{
+/*   16      |     4 */            int a1;
+/* XXX  4-byte hole  */
+/*   24      |     8 */            char *a2;
+/*   32      |     4 */            int a3;
+                               @} /* total size:   24 bytes */ f4;
+                           @} /* total size:   40 bytes */ fff2;
+@} /* total size:   40 bytes */
+@end smallexample
+
+In this case, since @code{struct tuv} and @code{struct xyz} occupy the
+same space (because we are dealing with an union), the offset is not
+printed for them.  However, you can still examine the offset of each
+of these structures' fields.
+
+Another useful scenario is printing the offsets of a struct containing
+bitfields:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tyu
+/* offset    |  size */
+struct tyu @{
+/*    0:31   |     4 */    int a1 : 1;
+/*    0:28   |     4 */    int a2 : 3;
+/*    0: 5   |     4 */    int a3 : 23;
+/*    3: 3   |     1 */    char a4 : 2;
+/* XXX  3-bit hole   */
+/* XXX  4-byte hole  */
+/*    8      |     8 */    int64_t a5;
+/*   16:27   |     4 */    int a6 : 5;
+/*   16:56   |     8 */    int64_t a7 : 3;
+@} /* total size:   24 bytes */
+@end smallexample
+
+Note how the offset information is now extended to also include how
+many bits are left to be used in each bitfield.
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..aadcace517
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,158 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
+
+#include <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  char field4;
+
+  /* 7-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+};
+
+/* This struct will be nested inside 'struct xyz'.  */
+
+struct tuv
+{
+  int a1;
+
+  char *a2;
+
+  int a3;
+};
+
+/* This struct will be nested inside 'struct pqr'.  */
+
+struct xyz
+{
+  int f1;
+
+  char f2;
+
+  void *f3;
+
+  struct tuv f4;
+};
+
+/* A struct with a nested struct.  */
+
+struct pqr
+{
+  int ff1;
+
+  struct xyz ff2;
+
+  char ff3;
+};
+
+/* A union with two nested structs.  */
+
+union qwe
+{
+  struct tuv fff1;
+
+  struct xyz fff2;
+};
+
+/* A struct with an union.  */
+
+struct poi
+{
+  int f1;
+
+  union qwe f2;
+
+  uint16_t f3;
+
+  struct pqr f4;
+};
+
+/* A struct with bitfields.  */
+
+struct tyu
+{
+  int a1 : 1;
+
+  int a2 : 3;
+
+  int a3 : 23;
+
+  char a4 : 2;
+
+  int64_t a5;
+
+  int a6 : 5;
+
+  int64_t a7 : 3;
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+  struct pqr bar;
+  union qwe c;
+  struct poi d;
+  struct tyu e;
+  uint8_t i;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..04a887a703
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,192 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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/>.
+
+standard_testfile .cc
+
+# Test only works on x86_64 LP64 targets.  That's how we guarantee
+# that the expected holes will be present in the struct.
+if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
+    untested "test work only on x86_64 lp64"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ optimize=-O0 }] } {
+    return -1
+}
+
+# Test general offset printing, ctor/dtor printing, union, formatting.
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+"type = struct abc {" \
+"/\\\* offset    |  size \\\*/" \
+"                         public:" \
+"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
+"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
+"/\\\* XXX  7-bit hole   \\\*/" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   20      |     4 \\\*/    int field3;" \
+"/\\\*   24      |     1 \\\*/    char field4;" \
+"/\\\* XXX  7-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
+"/\\\*   40      |     8 \\\*/    union {" \
+"/\\\*                 8 \\\*/        void \\\*field6;" \
+"/\\\*                 4 \\\*/        int field7;" \
+"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
+"" \
+"                           abc\\(void\\);" \
+"                           ~abc\\(\\);" \
+"} /\\\* total size:   48 bytes \\\*/"] \
+    "ptype offset struct abc"
+
+# Test nested structs.
+gdb_test "ptype /o struct pqr" \
+    [multi_line \
+"type = struct pqr {" \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    16 \\\*/    struct xyz {" \
+"/\\\*    8      |     4 \\\*/        int f1;" \
+"/\\\*   12      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/        struct tuv {" \
+"/\\\*   24      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/            char *a2;" \
+"/\\\*   40      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*   48      |     1 \\\*/    char ff3;" \
+"} /\\\* total size:   56 bytes \\\*/"] \
+    "ptype offset struct pqr"
+
+# Test that the offset is properly reset when we are printing an union
+# and go inside two inner structs.
+# This also tests a struct inside a struct inside an union.
+gdb_test "ptype /o union qwe" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*                24 \\\*/    struct tuv {" \
+"/\\\*    0      |     4 \\\*/        int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/        char \\\*a2;" \
+"/\\\*   16      |     4 \\\*/        int a3;" \
+"                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
+"/\\\*                40 \\\*/    struct xyz {" \
+"/\\\*    0      |     4 \\\*/        int f1;" \
+"/\\\*    4      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   16      |    24 \\\*/        struct tuv {" \
+"/\\\*   16      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   24      |     8 \\\*/            char \\\*a2;" \
+"/\\\*   32      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ fff2;" \
+"} /\\\* total size:   40 bytes \\\*/"] \
+    "ptype offset union qwe"
+
+# Test printing a struct that contains an union, and that also
+# contains a struct.
+gdb_test "ptype /o struct poi" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    40 \\\*/    union qwe {" \
+"/\\\*                24 \\\*/        struct tuv {" \
+"/\\\*    8      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/            char \\\*a2;" \
+"/\\\*   24      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ fff1;" \
+"/\\\*                40 \\\*/        struct xyz {" \
+"/\\\*    8      |     4 \\\*/            int f1;" \
+"/\\\*   12      |     1 \\\*/            char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/            void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/            struct tuv {" \
+"/\\\*   24      |     4 \\\*/                int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/                char \\\*a2;" \
+"/\\\*   40      |     4 \\\*/                int a3;" \
+"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                               } /\\\* total size:   40 bytes \\\*/ fff2;" \
+"                           } /\\\* total size:   40 bytes \\\*/ f2;" \
+"/\\\*   48      |     2 \\\*/    uint16_t f3;" \
+"/\\\* XXX  6-byte hole  */" \
+"/\\\*   56      |    56 \\\*/    struct pqr {" \
+"/\\\*   56      |     4 \\\*/        int ff1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   64      |    40 \\\*/        struct xyz {" \
+"/\\\*   64      |     4 \\\*/            int f1;" \
+"/\\\*   68      |     1 \\\*/            char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   72      |     8 \\\*/            void \\\*f3;" \
+"/\\\*   80      |    24 \\\*/            struct tuv {" \
+"/\\\*   80      |     4 \\\*/                int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   88      |     8 \\\*/                char \\\*a2;" \
+"/\\\*   96      |     4 \\\*/                int a3;" \
+"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                               } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*  104      |     1 \\\*/        char ff3;" \
+"                           } /\\\* total size:   56 bytes \\\*/ f4;" \
+"} /\\\* total size:  112 bytes \\\*/"] \
+    "ptype offset struct poi"
+
+# Test printing a struct with several bitfields, laid out in various
+# ways.
+#
+# Because dealing with bitfields and offsets is difficult, it can be
+# tricky to confirm that the output of the this command is accurate.
+# A nice way to do that is to use GDB's "x" command and print the
+# actual memory layout of the struct.  In order to differentiate
+# betweent bitfields and non-bitfield variables, one can assign "-1"
+# to every bitfield in the struct.  An example of the output of "x"
+# using "struct tyu" is:
+#
+#   (gdb) x/24xb &e
+#   0x7fffffffd540: 0xff    0xff    0xff    0x1f    0x00    0x00    0x00    0x00
+#   0x7fffffffd548: 0xff    0xff    0xff    0xff    0xff    0xff    0xff    0xff
+#   0x7fffffffd550: 0xff    0x00    0x00    0x00    0x00    0x00    0x00    0x00
+#
+# Thanks to Pedro Alves for coming up with this method (which can be
+# used to inspect the other cases, of course).
+gdb_test "ptype /o struct tyu" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"struct tyu {" \
+"/\\\*    0:31   |     4 \\\*/    int a1 : 1;" \
+"/\\\*    0:28   |     4 \\\*/    int a2 : 3;" \
+"/\\\*    0: 5   |     4 \\\*/    int a3 : 23;" \
+"/\\\*    3: 3   |     1 \\\*/    char a4 : 2;" \
+"/\\\* XXX  3-bit hole   \\\*/" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/    int64_t a5;" \
+"/\\\*   16:27   |     4 \\\*/    int a6 : 5;" \
+"/\\\*   16:56   |     8 \\\*/    int64_t a7 : 3;" \
+"} /\\\* total size:   24 bytes \\\*/"] \
+    "ptype offset struct tyu"
+
+# Test that we don't print any header when issuing a "ptype /o" on a
+# non-struct, non-union, non-class type.
+gdb_test "ptype /o int" "int" "ptype offset int"
+gdb_test "ptype /o uint8_t" "char" "ptype offset uint8_t"
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 9d9d6f5a49..225e94b1bb 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,6 +42,8 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
+  0,				/* offset_bitpos */
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -55,6 +57,8 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
+  0,				/* offset_bitpos */
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -440,6 +444,9 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  flags.print_offsets = 1;
+		  break;
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -499,6 +506,11 @@ whatis_exp (const char *exp, int show)
 	real_type = value_rtti_type (val, &full, &top, &using_enc);
     }
 
+  if (flags.print_offsets &&
+      (TYPE_CODE (type) == TYPE_CODE_STRUCT
+       || TYPE_CODE (type) == TYPE_CODE_UNION))
+    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
+
   printf_filtered ("type = ");
 
   if (!flags.raw)
@@ -759,7 +771,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a2058b0120..08333b3762 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -35,6 +35,15 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
+  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
+     This is needed for when we are printing nested structs and want
+     to make sure that the printed offset for each field carries over
+     the offset of the outter struct.  */
+  unsigned int offset_bitpos;
+
   /* The number of nested type definitions to print.  -1 == all.  */
   int print_nested_type_limit;
 
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v5 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base'
  2017-12-13  3:17 ` [PATCH v5 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2017-12-13  3:17   ` [PATCH v5 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-12-13  3:17   ` Sergio Durigan Junior
  1 sibling, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13  3:17 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	Keith Seitz, Sergio Durigan Junior

While doing the 'ptype /o' work, I noticed that 'c_type_print_base'
was very long, with a big amount of code just to handle the case of
TYPE_CODE_{STRUCT,UNION}.  This made working with the function a bit
difficult, specially because of the level of indentation.

This commit moves this part of the code to their own functions.  Now
we have a 'c_type_print_base_struct_union' with most of the code, and
also 'need_access_label_p', which is a subset of the code that was
also a good candidate for having its own function.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* c-typeprint.c (need_access_label_p): New function.
	(c_type_print_base_struct_union): New function.
	(c_type_print_base): Move code to handle
	TYPE_CODE_{STRUCT,UNION} to the functions mentioned above.
---
 gdb/c-typeprint.c | 838 ++++++++++++++++++++++++++----------------------------
 1 file changed, 404 insertions(+), 434 deletions(-)

diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index f3c3e7d706..ce10b5e005 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -875,6 +875,407 @@ output_access_specifier (struct ui_file *stream,
   return last_access;
 }
 
+/* Return true is an access label (i.e., "public:", "private:",
+   "protected:") needs to be printed for TYPE.  */
+
+static bool
+need_access_label_p (struct type *type)
+{
+  if (TYPE_DECLARED_CLASS (type))
+    {
+      QUIT;
+      for (int i = TYPE_N_BASECLASSES (type); i < TYPE_NFIELDS (type); i++)
+	if (!TYPE_FIELD_PRIVATE (type, i))
+	  return true;
+      QUIT;
+      for (int j = 0; j < TYPE_NFN_FIELDS (type); j++)
+	for (int i = 0; i < TYPE_FN_FIELDLIST_LENGTH (type, j); i++)
+	  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+							  j), i))
+	    return true;
+      QUIT;
+      for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+	  return true;
+    }
+  else
+    {
+      QUIT;
+      for (int i = TYPE_N_BASECLASSES (type); i < TYPE_NFIELDS (type); i++)
+	if (TYPE_FIELD_PRIVATE (type, i) || TYPE_FIELD_PROTECTED (type, i))
+	  return true;
+      QUIT;
+      for (int j = 0; j < TYPE_NFN_FIELDS (type); j++)
+	{
+	  QUIT;
+	  for (int i = 0; i < TYPE_FN_FIELDLIST_LENGTH (type, j); i++)
+	    if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
+							     j), i)
+		|| TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+							      j),
+					  i))
+	      return true;
+	}
+      QUIT;
+      for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
+	    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+	  return true;
+    }
+
+  return false;
+}
+
+/* Helper for 'c_type_print_base' that handles structs and unions.
+   For a description of the arguments, see 'c_type_print_base'.  */
+
+static void
+c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
+				int show, int level,
+				const struct type_print_options *flags)
+{
+  struct type_print_options local_flags = *flags;
+  struct type_print_options semi_local_flags = *flags;
+  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
+
+  local_flags.local_typedefs = NULL;
+  semi_local_flags.local_typedefs = NULL;
+
+  if (!flags->raw)
+    {
+      if (flags->local_typedefs)
+	local_flags.local_typedefs
+	  = copy_typedef_hash (flags->local_typedefs);
+      else
+	local_flags.local_typedefs = create_typedef_hash ();
+
+      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
+    }
+
+  c_type_print_modifier (type, stream, 0, 1);
+  if (TYPE_CODE (type) == TYPE_CODE_UNION)
+    fprintf_filtered (stream, "union ");
+  else if (TYPE_DECLARED_CLASS (type))
+    fprintf_filtered (stream, "class ");
+  else
+    fprintf_filtered (stream, "struct ");
+
+  /* Print the tag if it exists.  The HP aCC compiler emits a
+     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
+     enum}" tag for unnamed struct/union/enum's, which we don't
+     want to print.  */
+  if (TYPE_TAG_NAME (type) != NULL
+      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
+    {
+      /* When printing the tag name, we are still effectively
+	 printing in the outer context, hence the use of FLAGS
+	 here.  */
+      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
+      if (show > 0)
+	fputs_filtered (" ", stream);
+    }
+
+  if (show < 0)
+    {
+      /* If we just printed a tag name, no need to print anything
+	 else.  */
+      if (TYPE_TAG_NAME (type) == NULL)
+	fprintf_filtered (stream, "{...}");
+    }
+  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
+    {
+      struct type *basetype;
+      int vptr_fieldno;
+
+      c_type_print_template_args (&local_flags, type, stream);
+
+      /* Add in template parameters when printing derivation info.  */
+      add_template_parameters (local_flags.local_typedefs, type);
+      cp_type_print_derivation_info (stream, type, &local_flags);
+
+      /* This holds just the global typedefs and the template
+	 parameters.  */
+      semi_local_flags.local_typedefs
+	= copy_typedef_hash (local_flags.local_typedefs);
+      if (semi_local_flags.local_typedefs)
+	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
+
+      /* Now add in the local typedefs.  */
+      recursively_update_typedef_hash (local_flags.local_typedefs, type);
+
+      fprintf_filtered (stream, "{\n");
+      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
+	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
+	{
+	  if (TYPE_STUB (type))
+	    fprintfi_filtered (level + 4, stream,
+			       _("<incomplete type>\n"));
+	  else
+	    fprintfi_filtered (level + 4, stream,
+			       _("<no data fields>\n"));
+	}
+
+      /* Start off with no specific section type, so we can print
+	 one for the first field we find, and use that section type
+	 thereafter until we find another type.  */
+
+      enum access_specifier section_type = s_none;
+
+      /* For a class, if all members are private, there's no need
+	 for a "private:" label; similarly, for a struct or union
+	 masquerading as a class, if all members are public, there's
+	 no need for a "public:" label.  */
+      bool need_access_label = need_access_label_p (type);
+
+      /* If there is a base class for this type,
+	 do not print the field that it occupies.  */
+
+      int len = TYPE_NFIELDS (type);
+      vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
+	{
+	  QUIT;
+
+	  /* If we have a virtual table pointer, omit it.  Even if
+	     virtual table pointers are not specifically marked in
+	     the debug info, they should be artificial.  */
+	  if ((i == vptr_fieldno && type == basetype)
+	      || TYPE_FIELD_ARTIFICIAL (type, i))
+	    continue;
+
+	  if (need_access_label)
+	    {
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FIELD_PROTECTED (type, i),
+		 TYPE_FIELD_PRIVATE (type, i));
+	    }
+
+	  print_spaces_filtered (level + 4, stream);
+	  if (field_is_static (&TYPE_FIELD (type, i)))
+	    fprintf_filtered (stream, "static ");
+	  c_print_type (TYPE_FIELD_TYPE (type, i),
+			TYPE_FIELD_NAME (type, i),
+			stream, show - 1, level + 4,
+			&local_flags);
+	  if (!field_is_static (&TYPE_FIELD (type, i))
+	      && TYPE_FIELD_PACKED (type, i))
+	    {
+	      /* It is a bitfield.  This code does not attempt
+		 to look at the bitpos and reconstruct filler,
+		 unnamed fields.  This would lead to misleading
+		 results if the compiler does not put out fields
+		 for such things (I don't know what it does).  */
+	      fprintf_filtered (stream, " : %d",
+				TYPE_FIELD_BITSIZE (type, i));
+	    }
+	  fprintf_filtered (stream, ";\n");
+	}
+
+      /* If there are both fields and methods, put a blank line
+	 between them.  Make sure to count only method that we
+	 will display; artificial methods will be hidden.  */
+      len = TYPE_NFN_FIELDS (type);
+      if (!flags->print_methods)
+	len = 0;
+      int real_len = 0;
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  int j;
+
+	  for (j = 0; j < len2; j++)
+	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
+	      real_len++;
+	}
+      if (real_len > 0 && section_type != s_none)
+	fprintf_filtered (stream, "\n");
+
+      /* C++: print out the methods.  */
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
+	  const char *name = type_name_no_tag (type);
+	  int is_constructor = name && strcmp (method_name,
+					       name) == 0;
+
+	  for (j = 0; j < len2; j++)
+	    {
+	      const char *mangled_name;
+	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
+	      char *demangled_name;
+	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
+	      int is_full_physname_constructor =
+		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
+		|| is_constructor_name (physname)
+		|| is_destructor_name (physname)
+		|| method_name[0] == '~';
+
+	      /* Do not print out artificial methods.  */
+	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
+		continue;
+
+	      QUIT;
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FN_FIELD_PROTECTED (f, j),
+		 TYPE_FN_FIELD_PRIVATE (f, j));
+
+	      print_spaces_filtered (level + 4, stream);
+	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
+		fprintf_filtered (stream, "virtual ");
+	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
+		fprintf_filtered (stream, "static ");
+	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
+		{
+		  /* Keep GDB from crashing here.  */
+		  fprintf_filtered (stream,
+				    _("<undefined type> %s;\n"),
+				    TYPE_FN_FIELD_PHYSNAME (f, j));
+		  break;
+		}
+	      else if (!is_constructor	/* Constructors don't
+					   have declared
+					   types.  */
+		       && !is_full_physname_constructor  /* " " */
+		       && !is_type_conversion_operator (type, i, j))
+		{
+		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+				"", stream, -1, 0,
+				&local_flags);
+		  fputs_filtered (" ", stream);
+		}
+	      if (TYPE_FN_FIELD_STUB (f, j))
+		{
+		  /* Build something we can demangle.  */
+		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
+		  mangled_name = mangled_name_holder.get ();
+		}
+	      else
+		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
+
+	      demangled_name =
+		gdb_demangle (mangled_name,
+			      DMGL_ANSI | DMGL_PARAMS);
+	      if (demangled_name == NULL)
+		{
+		  /* In some cases (for instance with the HP
+		     demangling), if a function has more than 10
+		     arguments, the demangling will fail.
+		     Let's try to reconstruct the function
+		     signature from the symbol information.  */
+		  if (!TYPE_FN_FIELD_STUB (f, j))
+		    {
+		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
+		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
+
+		      cp_type_print_method_args (mtype,
+						 "",
+						 method_name,
+						 staticp,
+						 stream, &local_flags);
+		    }
+		  else
+		    fprintf_filtered (stream,
+				      _("<badly mangled name '%s'>"),
+				      mangled_name);
+		}
+	      else
+		{
+		  char *p;
+		  char *demangled_no_class
+		    = remove_qualifiers (demangled_name);
+
+		  /* Get rid of the `static' appended by the
+		     demangler.  */
+		  p = strstr (demangled_no_class, " static");
+		  if (p != NULL)
+		    {
+		      int length = p - demangled_no_class;
+		      char *demangled_no_static;
+
+		      demangled_no_static
+			= (char *) xmalloc (length + 1);
+		      strncpy (demangled_no_static,
+			       demangled_no_class, length);
+		      *(demangled_no_static + length) = '\0';
+		      fputs_filtered (demangled_no_static, stream);
+		      xfree (demangled_no_static);
+		    }
+		  else
+		    fputs_filtered (demangled_no_class, stream);
+		  xfree (demangled_name);
+		}
+
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print out nested types.  */
+      if (TYPE_NESTED_TYPES_COUNT (type) != 0
+	  && semi_local_flags.print_nested_type_limit != 0)
+	{
+	  if (semi_local_flags.print_nested_type_limit > 0)
+	    --semi_local_flags.print_nested_type_limit;
+
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
+	    {
+	      print_spaces_filtered (level + 4, stream);
+	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+			    "", stream, show, level + 4, &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print typedefs defined in this class.  */
+
+      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
+	{
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
+	      || TYPE_NESTED_TYPES_COUNT (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
+	    {
+	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
+
+	      /* Dereference the typedef declaration itself.  */
+	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
+	      target = TYPE_TARGET_TYPE (target);
+
+	      if (need_access_label)
+		{
+		  section_type = output_access_specifier
+		    (stream, section_type, level,
+		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		}
+	      print_spaces_filtered (level + 4, stream);
+	      fprintf_filtered (stream, "typedef ");
+
+	      /* We want to print typedefs with substitutions
+		 from the template parameters or globally-known
+		 typedefs but not local typedefs.  */
+	      c_print_type (target,
+			    TYPE_TYPEDEF_FIELD_NAME (type, i),
+			    stream, show - 1, level + 4,
+			    &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      fprintfi_filtered (level, stream, "}");
+    }
+
+  do_cleanups (local_cleanups);
+}
+
 /* Print the name of the type (or the ultimate pointer target,
    function value or array element), or the description of a structure
    or union.
@@ -898,10 +1299,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		   int show, int level, const struct type_print_options *flags)
 {
   int i;
-  int len, real_len;
-  enum access_specifier section_type;
-  int need_access_label = 0;
-  int j, len2;
+  int len;
+  int j;
 
   QUIT;
 
@@ -958,436 +1357,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      {
-	struct type_print_options local_flags = *flags;
-	struct type_print_options semi_local_flags = *flags;
-	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
-
-	local_flags.local_typedefs = NULL;
-	semi_local_flags.local_typedefs = NULL;
-
-	if (!flags->raw)
-	  {
-	    if (flags->local_typedefs)
-	      local_flags.local_typedefs
-		= copy_typedef_hash (flags->local_typedefs);
-	    else
-	      local_flags.local_typedefs = create_typedef_hash ();
-
-	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
-	  }
-
-	c_type_print_modifier (type, stream, 0, 1);
-	if (TYPE_CODE (type) == TYPE_CODE_UNION)
-	  fprintf_filtered (stream, "union ");
-	else if (TYPE_DECLARED_CLASS (type))
-	  fprintf_filtered (stream, "class ");
-	else
-	  fprintf_filtered (stream, "struct ");
-
-	/* Print the tag if it exists.  The HP aCC compiler emits a
-	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
-	   enum}" tag for unnamed struct/union/enum's, which we don't
-	   want to print.  */
-	if (TYPE_TAG_NAME (type) != NULL
-	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
-	  {
-	    /* When printing the tag name, we are still effectively
-	       printing in the outer context, hence the use of FLAGS
-	       here.  */
-	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
-	    if (show > 0)
-	      fputs_filtered (" ", stream);
-	  }
-
-	if (show < 0)
-	  {
-	    /* If we just printed a tag name, no need to print anything
-	       else.  */
-	    if (TYPE_TAG_NAME (type) == NULL)
-	      fprintf_filtered (stream, "{...}");
-	  }
-	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
-	  {
-	    struct type *basetype;
-	    int vptr_fieldno;
-
-	    c_type_print_template_args (&local_flags, type, stream);
-
-	    /* Add in template parameters when printing derivation info.  */
-	    add_template_parameters (local_flags.local_typedefs, type);
-	    cp_type_print_derivation_info (stream, type, &local_flags);
-
-	    /* This holds just the global typedefs and the template
-	       parameters.  */
-	    semi_local_flags.local_typedefs
-	      = copy_typedef_hash (local_flags.local_typedefs);
-	    if (semi_local_flags.local_typedefs)
-	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
-
-	    /* Now add in the local typedefs.  */
-	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
-
-	    fprintf_filtered (stream, "{\n");
-	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
-		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
-	      {
-		if (TYPE_STUB (type))
-		  fprintfi_filtered (level + 4, stream,
-				     _("<incomplete type>\n"));
-		else
-		  fprintfi_filtered (level + 4, stream,
-				     _("<no data fields>\n"));
-	      }
-
-	    /* Start off with no specific section type, so we can print
-	       one for the first field we find, and use that section type
-	       thereafter until we find another type.  */
-
-	    section_type = s_none;
-
-	    /* For a class, if all members are private, there's no need
-	       for a "private:" label; similarly, for a struct or union
-	       masquerading as a class, if all members are public, there's
-	       no need for a "public:" label.  */
-
-	    if (TYPE_DECLARED_CLASS (type))
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (!TYPE_FIELD_PRIVATE (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									  j), i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-	    else
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (TYPE_FIELD_PRIVATE (type, i)
-		      || TYPE_FIELD_PROTECTED (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			QUIT;
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
-									   j), i)
-			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									    j),
-							i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
-			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-
-	    /* If there is a base class for this type,
-	       do not print the field that it occupies.  */
-
-	    len = TYPE_NFIELDS (type);
-	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
-	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-	      {
-		QUIT;
-
-		/* If we have a virtual table pointer, omit it.  Even if
-		   virtual table pointers are not specifically marked in
-		   the debug info, they should be artificial.  */
-		if ((i == vptr_fieldno && type == basetype)
-		    || TYPE_FIELD_ARTIFICIAL (type, i))
-		  continue;
-
-		if (need_access_label)
-		  {
-		    section_type = output_access_specifier
-		      (stream, section_type, level,
-		       TYPE_FIELD_PROTECTED (type, i),
-		       TYPE_FIELD_PRIVATE (type, i));
-		  }
-
-		print_spaces_filtered (level + 4, stream);
-		if (field_is_static (&TYPE_FIELD (type, i)))
-		  fprintf_filtered (stream, "static ");
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show - 1, level + 4,
-			      &local_flags);
-		if (!field_is_static (&TYPE_FIELD (type, i))
-		    && TYPE_FIELD_PACKED (type, i))
-		  {
-		    /* It is a bitfield.  This code does not attempt
-		       to look at the bitpos and reconstruct filler,
-		       unnamed fields.  This would lead to misleading
-		       results if the compiler does not put out fields
-		       for such things (I don't know what it does).  */
-		    fprintf_filtered (stream, " : %d",
-				      TYPE_FIELD_BITSIZE (type, i));
-		  }
-		fprintf_filtered (stream, ";\n");
-	      }
-
-	  /* If there are both fields and methods, put a blank line
-	     between them.  Make sure to count only method that we
-	     will display; artificial methods will be hidden.  */
-	  len = TYPE_NFN_FIELDS (type);
-	  if (!flags->print_methods)
-	    len = 0;
-	  real_len = 0;
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      int j;
-
-	      for (j = 0; j < len2; j++)
-		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		  real_len++;
-	    }
-	  if (real_len > 0 && section_type != s_none)
-	    fprintf_filtered (stream, "\n");
-
-	  /* C++: print out the methods.  */
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
-	      const char *name = type_name_no_tag (type);
-	      int is_constructor = name && strcmp (method_name,
-						   name) == 0;
-
-	      for (j = 0; j < len2; j++)
-		{
-		  const char *mangled_name;
-		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
-		  char *demangled_name;
-		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
-		  int is_full_physname_constructor =
-		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
-		    || is_constructor_name (physname)
-		    || is_destructor_name (physname)
-		    || method_name[0] == '~';
-
-		  /* Do not print out artificial methods.  */
-		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		    continue;
-
-		  QUIT;
-		  section_type = output_access_specifier
-		    (stream, section_type, level,
-		     TYPE_FN_FIELD_PROTECTED (f, j),
-		     TYPE_FN_FIELD_PRIVATE (f, j));
-
-		  print_spaces_filtered (level + 4, stream);
-		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
-		    fprintf_filtered (stream, "virtual ");
-		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
-		    fprintf_filtered (stream, "static ");
-		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
-		    {
-		      /* Keep GDB from crashing here.  */
-		      fprintf_filtered (stream,
-					_("<undefined type> %s;\n"),
-					TYPE_FN_FIELD_PHYSNAME (f, j));
-		      break;
-		    }
-		  else if (!is_constructor	/* Constructors don't
-						   have declared
-						   types.  */
-			   && !is_full_physname_constructor  /* " " */
-			   && !is_type_conversion_operator (type, i, j))
-		    {
-		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				    "", stream, -1, 0,
-				    &local_flags);
-		      fputs_filtered (" ", stream);
-		    }
-		  if (TYPE_FN_FIELD_STUB (f, j))
-		    {
-		      /* Build something we can demangle.  */
-		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
-		      mangled_name = mangled_name_holder.get ();
-		    }
-		  else
-		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
-
-		  demangled_name =
-		    gdb_demangle (mangled_name,
-				  DMGL_ANSI | DMGL_PARAMS);
-		  if (demangled_name == NULL)
-		    {
-		      /* In some cases (for instance with the HP
-			 demangling), if a function has more than 10
-			 arguments, the demangling will fail.
-			 Let's try to reconstruct the function
-			 signature from the symbol information.  */
-		      if (!TYPE_FN_FIELD_STUB (f, j))
-			{
-			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
-			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
-
-			  cp_type_print_method_args (mtype,
-						     "",
-						     method_name,
-						     staticp,
-						     stream, &local_flags);
-			}
-		      else
-			fprintf_filtered (stream,
-					  _("<badly mangled name '%s'>"),
-					  mangled_name);
-		    }
-		  else
-		    {
-		      char *p;
-		      char *demangled_no_class
-			= remove_qualifiers (demangled_name);
-
-		      /* Get rid of the `static' appended by the
-			 demangler.  */
-		      p = strstr (demangled_no_class, " static");
-		      if (p != NULL)
-			{
-			  int length = p - demangled_no_class;
-			  char *demangled_no_static;
-
-			  demangled_no_static
-			    = (char *) xmalloc (length + 1);
-			  strncpy (demangled_no_static,
-				   demangled_no_class, length);
-			  *(demangled_no_static + length) = '\0';
-			  fputs_filtered (demangled_no_static, stream);
-			  xfree (demangled_no_static);
-			}
-		      else
-			fputs_filtered (demangled_no_class, stream);
-		      xfree (demangled_name);
-		    }
-
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print out nested types.  */
-	  if (TYPE_NESTED_TYPES_COUNT (type) != 0
-	      && semi_local_flags.print_nested_type_limit != 0)
-	    {
-	      if (semi_local_flags.print_nested_type_limit > 0)
-		--semi_local_flags.print_nested_type_limit;
-
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
-		{
-		  print_spaces_filtered (level + 4, stream);
-		  c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
-				"", stream, show, level + 4, &semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print typedefs defined in this class.  */
-
-	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
-	    {
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
-		  || TYPE_NESTED_TYPES_COUNT (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
-		{
-		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
-
-		  /* Dereference the typedef declaration itself.  */
-		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
-		  target = TYPE_TARGET_TYPE (target);
-
-		  if (need_access_label)
-		    {
-		      section_type = output_access_specifier
-			(stream, section_type, level,
-			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
-		    }
-		  print_spaces_filtered (level + 4, stream);
-		  fprintf_filtered (stream, "typedef ");
-
-		  /* We want to print typedefs with substitutions
-		     from the template parameters or globally-known
-		     typedefs but not local typedefs.  */
-		  c_print_type (target,
-				TYPE_TYPEDEF_FIELD_NAME (type, i),
-				stream, show - 1, level + 4,
-				&semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	    fprintfi_filtered (level, stream, "}");
-	  }
-
-	do_cleanups (local_cleanups);
-      }
+      c_type_print_base_struct_union (type, stream, show, level, flags);
       break;
 
     case TYPE_CODE_ENUM:
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v5 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg)
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
                   ` (5 preceding siblings ...)
  2017-12-11 23:43 ` [PATCH v4 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-13  3:17 ` Sergio Durigan Junior
  2017-12-13  3:17   ` [PATCH v5 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-13  3:17   ` [PATCH v5 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
  2017-12-14  2:48 ` [PATCH v6 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2017-12-15  1:12 ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  8 siblings, 2 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13  3:17 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves, Keith Seitz

Changes from v4:

- Fixed issue with printing bitfield offsets.  Big thanks to Pedro for
  the help with figuring this out.

- Expanded testcase to cover the bitfield offset test.

- Fixed @command/@kbd on documentation.

- Greatly expanded documentation to explain more about various types
  of output that the command generates.

- Added a marker for /* vtable */ fields.


I honestly thought this patch was not going to be ready today.  It's
now apparently printing the offsets (and remaining bits) of bitfields
correctly, at least as far I have tested.

Eli, I think you need to review the docs once again because I included
a great deal of information about the format printed by the command.

Thanks.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13  3:17   ` [PATCH v5 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-12-13  4:50     ` Simon Marchi
  2017-12-13 16:42       ` Sergio Durigan Junior
  2017-12-13 16:17     ` Eli Zaretskii
                       ` (2 subsequent siblings)
  3 siblings, 1 reply; 75+ messages in thread
From: Simon Marchi @ 2017-12-13  4:50 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves, Keith Seitz

On 2017-12-12 10:17 PM, Sergio Durigan Junior wrote:
> +/* Print information about field at index FIELD_IDX of the struct type
> +   TYPE.
> +
> +   ENDPOS is the one-past-the-end bit position of the previous field
> +   (where we expect this field to be if there is no hole).  At the
> +   end, ENDPOS is updated to the one-past-the-end bit position of the
> +   current field.
> +
> +   OFFSET_BITPOS is the offset value we carry over when we are
> +   printing a struct that is inside another struct; this is useful so
> +   that the offset is constantly incremented (if we didn't carry it
> +   over, the offset would be reset to zero when printing the inner
> +   struct).
> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
> +				  unsigned int *endpos, struct ui_file *stream,
> +				  unsigned int offset_bitpos)
> +{
> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
> +  unsigned int fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
> +
> +  /* We check for *ENDPOS > 0 because there is a specific scenario
> +     when *ENDPOS can be zero and BITPOS can be > 0: when we are
> +     dealing with a struct/class with a virtual method.  Because of
> +     the vtable, the first field of the struct/class will have an
> +     offset of sizeof (void *) (the size of the vtable).  If we do not
> +     check for *ENDPOS > 0 here, GDB will report a hole before the
> +     first field, which is not accurate.  */
> +  if (*endpos > 0 && *endpos < bitpos)
> +    {
> +      /* If ENDPOS is smaller than the current type's bitpos, it means
> +	 there's a hole in the struct, so we report it here.  */
> +      unsigned int hole = bitpos - *endpos;
> +      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
> +      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
> +
> +      if (hole_bit > 0)
> +	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
> +
> +      if (hole_byte > 0)
> +	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
> +    }
> +
> +  if (TYPE_FIELD_PACKED (type, field_idx))
> +    {
> +      /* We're dealing with a bitfield.  Print how many bits are left
> +	 to be used.  */
> +      unsigned int bitsize = TYPE_FIELD_BITSIZE (type, field_idx);
> +      /* The bitpos relative to the beginning of our container
> +	 field.  */
> +      unsigned int relative_bitpos;
> +
> +      /* The following was copied from
> +	 value.c:value_primitive_field.  */
> +      if ((bitpos % fieldsize_bit) + bitsize <= fieldsize_bit)
> +	relative_bitpos = bitpos % fieldsize_bit;
> +      else
> +	relative_bitpos = bitpos % TARGET_CHAR_BIT;
> +
> +      /* This is the exact offset (in bits) of this bitfield.  */
> +      unsigned int bit_offset
> +	= (bitpos - relative_bitpos) + offset_bitpos;
> +
> +      /* The position of the field, relative to the beginning of the
> +	 struct, and how many bits are left to be used in this
> +	 container.  */
> +      fprintf_filtered (stream, "/* %4u:%2u", bit_offset / TARGET_CHAR_BIT,
> +			fieldsize_bit - (relative_bitpos + bitsize));
> +      fieldsize_bit = bitsize;
> +    }

I won't pretend I understand this part.  I'll let Pedro look at it, or maybe try
again tomorrow, it's getting late here :)

> @@ -1041,25 +1183,63 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
>  	     the debug info, they should be artificial.  */
>  	  if ((i == vptr_fieldno && type == basetype)
>  	      || TYPE_FIELD_ARTIFICIAL (type, i))
> -	    continue;
> +	    {
> +	      if (flags->print_offsets)
> +		c_print_type_vtable_offset_marker (type, i, stream,
> +						   flags->offset_bitpos);
> +	      continue;
> +	    }

With that version, the comment above the if would need to be updted.

Note that the vtable marker won't be printed when looking at a child class.  With the
following:

struct foo
{
  virtual ~foo() = default;
  int a;
  short b;
};

struct bar : foo
{
  virtual ~bar () = default;
  int z;
  short c, d, e;
};

we get this:

(gdb) ptype /o foo
/* offset    |  size */
type = struct foo {
/*    0     |      8 */ /* vtable */
                         public:
/*    8      |     4 */    int a;
/*   12      |     2 */    short b;

                           ~foo();
} /* total size:   16 bytes */
(gdb) ptype /o bar
/* offset    |  size */
type = struct bar : public foo {
/*   16      |     4 */    int z;
/*   20      |     2 */    short c;
/*   22      |     2 */    short d;
/*   24      |     2 */    short e;
                         public:
                           ~bar();
} /* total size:   32 bytes */

If we print the vtable, I think it would make sense to print it for the child
class too.  And if we do, it would also make sense to display the fields of base
classes too, otherwise there would be a gap in the vtable offset and the first
field offset.  I think it would be useful in order to show the holes between the fields
of the base class and the child class.  In the example above, it would show that there's
a two byte hole between b and z.  Putting z at the end of bar could save some space.

But doing so would require to change ptype's behavior significantly, making it recurse
in parent classes.  Should we do that only if /o is specified?  If this makes the patch
too complex, I would suggest merging it without the vtable field, and take at adding it
after.  We don't have to include all the features we can think of in the first iteration.

For that use case, it's also interesting to see what pahole does too:

struct bar : foo {
	/* struct foo                 <ancestor>; */     /*     0    16 */

	/* XXX last struct has 2 bytes of padding */

	int                        z;                    /*    16     4 */
	short int                  c;                    /*    20     2 */
	short int                  d;                    /*    22     2 */
	short int                  e;                    /*    24     2 */
	void bar(class bar *, const class bar  &);

	void bar(class bar *);

	virtual void ~bar(class bar *, int);


	/* size: 32, cachelines: 1, members: 5 */
	/* padding: 6 */
	/* paddings: 1, sum paddings: 2 */
	/* last cacheline: 32 bytes */

	/* BRAIN FART ALERT! 32 != 10 + 0(holes), diff = 22 */

};

I am not sure why it brain farts.  Looks like it's comparing the size of the whole
structure (including fields of the base class, 32 bytes) with the size of the fields
of bar (10 bytes) and is surprised it's not the same size.

> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
> new file mode 100644
> index 0000000000..04a887a703
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
> @@ -0,0 +1,192 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2017 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/>.
> +
> +standard_testfile .cc
> +
> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
> +# that the expected holes will be present in the struct.
> +if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
> +    untested "test work only on x86_64 lp64"
> +    return 0
> +}
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
> +	  { debug c++ optimize=-O0 }] } {
> +    return -1
> +}
> +
> +# Test general offset printing, ctor/dtor printing, union, formatting.
> +gdb_test "ptype /o struct abc" \
> +    [multi_line \
> +"type = struct abc {" \
> +"/\\\* offset    |  size \\\*/" \
> +"                         public:" \
> +"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
> +"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
> +"/\\\* XXX  7-bit hole   \\\*/" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*   20      |     4 \\\*/    int field3;" \
> +"/\\\*   24      |     1 \\\*/    char field4;" \
> +"/\\\* XXX  7-byte hole  \\\*/" \
> +"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
> +"/\\\*   40      |     8 \\\*/    union {" \
> +"/\\\*                 8 \\\*/        void \\\*field6;" \
> +"/\\\*                 4 \\\*/        int field7;" \
> +"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
> +"" \
> +"                           abc\\(void\\);" \
> +"                           ~abc\\(\\);" \
> +"} /\\\* total size:   48 bytes \\\*/"] \
> +    "ptype offset struct abc"
> +
> +# Test nested structs.
> +gdb_test "ptype /o struct pqr" \
> +    [multi_line \
> +"type = struct pqr {" \
> +"/\\\* offset    |  size \\\*/" \
> +"/\\\*    0      |     4 \\\*/    int f1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*    8      |    16 \\\*/    struct xyz {" \
> +"/\\\*    8      |     4 \\\*/        int f1;" \
> +"/\\\*   12      |     1 \\\*/        char f2;" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
> +"/\\\*   24      |    24 \\\*/        struct tuv {" \
> +"/\\\*   24      |     4 \\\*/            int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   32      |     8 \\\*/            char *a2;" \
> +"/\\\*   40      |     4 \\\*/            int a3;" \
> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
> +"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
> +"/\\\*   48      |     1 \\\*/    char ff3;" \
> +"} /\\\* total size:   56 bytes \\\*/"] \
> +    "ptype offset struct pqr"
> +
> +# Test that the offset is properly reset when we are printing an union
> +# and go inside two inner structs.
> +# This also tests a struct inside a struct inside an union.
> +gdb_test "ptype /o union qwe" \
> +    [multi_line \
> +"/\\\* offset    |  size \\\*/" \
> +"/\\\*                24 \\\*/    struct tuv {" \
> +"/\\\*    0      |     4 \\\*/        int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*    8      |     8 \\\*/        char \\\*a2;" \
> +"/\\\*   16      |     4 \\\*/        int a3;" \
> +"                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
> +"/\\\*                40 \\\*/    struct xyz {" \
> +"/\\\*    0      |     4 \\\*/        int f1;" \
> +"/\\\*    4      |     1 \\\*/        char f2;" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*    8      |     8 \\\*/        void \\\*f3;" \
> +"/\\\*   16      |    24 \\\*/        struct tuv {" \
> +"/\\\*   16      |     4 \\\*/            int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   24      |     8 \\\*/            char \\\*a2;" \
> +"/\\\*   32      |     4 \\\*/            int a3;" \
> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
> +"                           } /\\\* total size:   40 bytes \\\*/ fff2;" \
> +"} /\\\* total size:   40 bytes \\\*/"] \
> +    "ptype offset union qwe"
> +
> +# Test printing a struct that contains an union, and that also
> +# contains a struct.
> +gdb_test "ptype /o struct poi" \
> +    [multi_line \
> +"/\\\* offset    |  size \\\*/" \
> +"/\\\*    0      |     4 \\\*/    int f1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*    8      |    40 \\\*/    union qwe {" \
> +"/\\\*                24 \\\*/        struct tuv {" \
> +"/\\\*    8      |     4 \\\*/            int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   16      |     8 \\\*/            char \\\*a2;" \
> +"/\\\*   24      |     4 \\\*/            int a3;" \
> +"                               } /\\\* total size:   24 bytes \\\*/ fff1;" \
> +"/\\\*                40 \\\*/        struct xyz {" \
> +"/\\\*    8      |     4 \\\*/            int f1;" \
> +"/\\\*   12      |     1 \\\*/            char f2;" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*   16      |     8 \\\*/            void \\\*f3;" \
> +"/\\\*   24      |    24 \\\*/            struct tuv {" \
> +"/\\\*   24      |     4 \\\*/                int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   32      |     8 \\\*/                char \\\*a2;" \
> +"/\\\*   40      |     4 \\\*/                int a3;" \
> +"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
> +"                               } /\\\* total size:   40 bytes \\\*/ fff2;" \
> +"                           } /\\\* total size:   40 bytes \\\*/ f2;" \
> +"/\\\*   48      |     2 \\\*/    uint16_t f3;" \
> +"/\\\* XXX  6-byte hole  */" \
> +"/\\\*   56      |    56 \\\*/    struct pqr {" \
> +"/\\\*   56      |     4 \\\*/        int ff1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   64      |    40 \\\*/        struct xyz {" \
> +"/\\\*   64      |     4 \\\*/            int f1;" \
> +"/\\\*   68      |     1 \\\*/            char f2;" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*   72      |     8 \\\*/            void \\\*f3;" \
> +"/\\\*   80      |    24 \\\*/            struct tuv {" \
> +"/\\\*   80      |     4 \\\*/                int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   88      |     8 \\\*/                char \\\*a2;" \
> +"/\\\*   96      |     4 \\\*/                int a3;" \
> +"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
> +"                               } /\\\* total size:   40 bytes \\\*/ ff2;" \
> +"/\\\*  104      |     1 \\\*/        char ff3;" \
> +"                           } /\\\* total size:   56 bytes \\\*/ f4;" \
> +"} /\\\* total size:  112 bytes \\\*/"] \
> +    "ptype offset struct poi"
> +
> +# Test printing a struct with several bitfields, laid out in various
> +# ways.
> +#
> +# Because dealing with bitfields and offsets is difficult, it can be
> +# tricky to confirm that the output of the this command is accurate.
> +# A nice way to do that is to use GDB's "x" command and print the
> +# actual memory layout of the struct.  In order to differentiate
> +# betweent bitfields and non-bitfield variables, one can assign "-1"

"betweent"

> +# to every bitfield in the struct.  An example of the output of "x"
> +# using "struct tyu" is:
> +#
> +#   (gdb) x/24xb &e
> +#   0x7fffffffd540: 0xff    0xff    0xff    0x1f    0x00    0x00    0x00    0x00
> +#   0x7fffffffd548: 0xff    0xff    0xff    0xff    0xff    0xff    0xff    0xff
> +#   0x7fffffffd550: 0xff    0x00    0x00    0x00    0x00    0x00    0x00    0x00
> +#
> +# Thanks to Pedro Alves for coming up with this method (which can be
> +# used to inspect the other cases, of course).

I'm all for thanking Pedro, but I would suggest putting this last remark in the commit
message :)

Simon

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13  3:17   ` [PATCH v5 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-13  4:50     ` Simon Marchi
@ 2017-12-13 16:17     ` Eli Zaretskii
  2017-12-13 17:14       ` Sergio Durigan Junior
  2017-12-13 16:19     ` Pedro Alves
  2017-12-13 16:20     ` Pedro Alves
  3 siblings, 1 reply; 75+ messages in thread
From: Eli Zaretskii @ 2017-12-13 16:17 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: gdb-patches, tom, simon.marchi, palves, keiths

> From: Sergio Durigan Junior <sergiodj@redhat.com>
> Cc: Tom Tromey <tom@tromey.com>,	Eli Zaretskii <eliz@gnu.org>,	Simon Marchi <simon.marchi@ericsson.com>,	Pedro Alves <palves@redhat.com>,	Keith Seitz <keiths@redhat.com>,	Sergio Durigan Junior <sergiodj@redhat.com>
> Date: Tue, 12 Dec 2017 22:17:24 -0500
> 
> This commit implements the pahole-like '/o' option for 'ptype', which
> +Issuing a @kbd{ptype /o struct tuv} would print:
                                      ^
Please insert "command" after the command, where I indicated.
Otherwise the sentence reads awkward.

> +Notice the format of the first column of comments.  There, you can
> +find two parts separated by the @code{|} character: the @emph{offset},
                                   ^^^^^^^^
@samp, not @code.

The documentation parts are okay with these fixed.

Thanks.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13  3:17   ` [PATCH v5 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-13  4:50     ` Simon Marchi
  2017-12-13 16:17     ` Eli Zaretskii
@ 2017-12-13 16:19     ` Pedro Alves
  2017-12-13 17:13       ` Sergio Durigan Junior
  2017-12-13 16:20     ` Pedro Alves
  3 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-13 16:19 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On 12/13/2017 03:17 AM, Sergio Durigan Junior wrote:

> +/* A struct with an union.  */
> +
> +struct poi
> +{
> +  int f1;
> +
> +  union qwe f2;
> +
> +  uint16_t f3;
> +
> +  struct pqr f4;
> +};
> +
> +/* A struct with bitfields.  */
> +
> +struct tyu
> +{
> +  int a1 : 1;
> +
> +  int a2 : 3;
> +
> +  int a3 : 23;
> +
> +  char a4 : 2;
> +
> +  int64_t a5;
> +
> +  int a6 : 5;
> +
> +  int64_t a7 : 3;
> +};

I think that the testcase should also make sure to exercise the new
offset computations in the case c_print_type_struct_field_offset's
'offset_bitpos' parameter is > 0.  Is it already covered?
I assume we'll need a test like tyu (struct with bitfields with
overlapping underlying objects), but that inherits some other
base structure?

> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
> @@ -0,0 +1,192 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2017 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/>.
> +

Please add an intro comment describing what this testcase is about.

> +standard_testfile .cc
> +
> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
> +# that the expected holes will be present in the struct.
> +if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
> +    untested "test work only on x86_64 lp64"
> +    return 0
> +}

I'm mildly worried about whether the bitfield handling is working
correctly on big endian machines.  We may want to lift this
x86-64-only restriction, by using e.g., alignas(N) or
__attribute__((aligned(N)) to take care of most of the differences
between architectures and end up with few per-arch code in
the .exp.  But I'm fine with starting with only x86-64 if you
confirm manually on e.g., a big endian PPC64 machine on the
compile farm, and we can extend the testcase in that direction
after this is merged.

> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
> +	  { debug c++ optimize=-O0 }] } {
> +    return -1
> +}

Weren't you going to remove that optimize thing? :-)

> +# Test that the offset is properly reset when we are printing an union
> +# and go inside two inner structs.
> +# This also tests a struct inside a struct inside an union.

"a union".  (two times here; there may be other places.)

> +gdb_test "ptype /o union qwe" \
> +    [multi_line \
> +"/\\\* offset    |  size \\\*/" \
> +"/\\\*                24 \\\*/    struct tuv {" \
> +"/\\\*    0      |     4 \\\*/        int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*    8      |     8 \\\*/        char \\\*a2;" \
> +"/\\\*   16      |     4 \\\*/        int a3;" \
> +"                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
> +"/\\\*                40 \\\*/    struct xyz {" \
> +"/\\\*    0      |     4 \\\*/        int f1;" \
> +"/\\\*    4      |     1 \\\*/        char f2;" \
> +"/\\\* XXX  3-byte hole  \\\*/" \
> +"/\\\*    8      |     8 \\\*/        void \\\*f3;" \
> +"/\\\*   16      |    24 \\\*/        struct tuv {" \
> +"/\\\*   16      |     4 \\\*/            int a1;" \
> +"/\\\* XXX  4-byte hole  \\\*/" \
> +"/\\\*   24      |     8 \\\*/            char \\\*a2;" \
> +"/\\\*   32      |     4 \\\*/            int a3;" \
> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
> +"                           } /\\\* total size:   40 bytes \\\*/ fff2;" \
> +"} /\\\* total size:   40 bytes \\\*/"] \
> +    "ptype offset union qwe"

Did you try using {} instead of "" for these strings,
avoiding all the escaping?

> @@ -499,6 +506,11 @@ whatis_exp (const char *exp, int show)
>  	real_type = value_rtti_type (val, &full, &top, &using_enc);
>      }
>  
> +  if (flags.print_offsets &&

&& goes on the next line.

> +      (TYPE_CODE (type) == TYPE_CODE_STRUCT
> +       || TYPE_CODE (type) == TYPE_CODE_UNION))
> +    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
> +
>    printf_filtered ("type = ");

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13  3:17   ` [PATCH v5 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
                       ` (2 preceding siblings ...)
  2017-12-13 16:19     ` Pedro Alves
@ 2017-12-13 16:20     ` Pedro Alves
  2017-12-13 17:41       ` Sergio Durigan Junior
  3 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-13 16:20 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

[Sending user-interface suggestions separately from core
functionality review.]

On 12/13/2017 03:17 AM, Sergio Durigan Junior wrote:
>   (gdb) ptype /o stap_probe
>   /* offset    |  size */
>   struct stap_probe {
>   /*    0      |    40 */    struct probe {
>   /*    0      |     8 */        const probe_ops *pops;
>   /*    8      |     8 */        gdbarch *arch;
>   /*   16      |     8 */        const char *name;
>   /*   24      |     8 */        const char *provider;
>   /*   32      |     8 */        CORE_ADDR address;
> 			     } /* total size:   40 bytes */ p;
>   /*   40      |     8 */    CORE_ADDR sem_addr;
>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>   /* XXX  7-bit hole   */
>   /* XXX  7-byte hole  */
>   /*   56      |     8 */    union {
>   /*                 8 */        const char *text;
>   /*                 8 */        VEC_stap_probe_arg_s *vec;
> 			     } /* total size:    8 bytes */ args_u;
>   } /* total size:   64 bytes */

I've experimented with (different versions of) of this patch
against gdb a bit, and I have to say that I'm finding myself
a bit confused by the currently produced output.  It feels
like the output should be a bit more obvious and easier to
skim quickly.

For example, I was looking at gdb's minimal_symbol (what I had
used when I noticed the bitfield handling in earlier versions was
incorrect):

(top-gdb) ptype/tom minimal_symbol
/* offset    |  size */
type = struct minimal_symbol {
/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
/*    8      |     8 */        union {
/*                 8 */            LONGEST ivalue;
/*                 8 */            const block *block;
/*                 8 */            const gdb_byte *bytes;
/*                 8 */            CORE_ADDR address;
/*                 8 */            const common_block *common_block;
/*                 8 */            symbol *chain;
                               } /* total size:    8 bytes */ value;
/*   16      |     8 */        union {
/*                 8 */            obstack *obstack;
/*                 8 */            const char *demangled_name;
                               } /* total size:    8 bytes */ language_specific;
/*   24:27   |     4 */        language language : 5;
/*   24:26   |     4 */        unsigned int ada_mangled : 1;
/* XXX  2-bit hole   */
/* XXX  1-byte hole  */
/*   26      |     2 */        short section;
                           } /* total size:   32 bytes */ mginfo;
/*   32      |     8 */    unsigned long size;
/*   40      |     8 */    const char *filename;
/*   48:28   |     4 */    minimal_symbol_type type : 4;
/*   48:27   |     4 */    unsigned int created_by_gdb : 1;
/*   48:26   |     4 */    unsigned int target_flag_1 : 1;
/*   48:25   |     4 */    unsigned int target_flag_2 : 1;
/*   48:24   |     4 */    unsigned int has_size : 1;
/* XXX  7-byte hole  */
/*   56      |     8 */    minimal_symbol *hash_next;
/*   64      |     8 */    minimal_symbol *demangled_hash_next;
} /* total size:   72 bytes */
(top-gdb) 

and I have the following observations:

#1 - left vs right

We print the extra offset/size info on the leftside of the
screen, shifting the whole type to the right.  If we were to put
the new info on the right, then a "ptype S" vs "ptype/o S" would
look mostly the same, except for the extra info that appears
on the right, and it'd map better to what you'd write in
actual code (who puts comments on the left side, right?)
pahole(1) prints the info on the right side.  Was there a
particular reason gdb's version prints it on the left hand
side?  Maybe the Python version did that too (did it?  I haven't
tried it) because it'd be difficult with the Python
implementation otherwise?  

But maybe it's just me.  On to the next point in
such case.

#2 - shift "type =" header to the right as well?

Currently, the leading "type =" and ending "}" lines
are aligned on the left, along with the offset/size info.
I think shifting those to the right as well might make the
output nicer.  The idea being that /o would split the
output in two more-clearly-separated columns, with the new info
panned hard on the left column, and old ptype info on
the right column.

I.e., currently we get:

~~~~~~~~~~~~~~~~~~~~~~~~~~~
(top-gdb) ptype/o minimal_symbol
/* offset    |  size */
type = struct minimal_symbol {
/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
...
} /* total size:   72 bytes */
~~~~~~~~~~~~~~~~~~~~~~~~~~~

And I'd propose instead:

~~~~~~~~~~~~~~~~~~~~~~~~~~~
(top-gdb) ptype/o minimal_symbol
/* offset    |  size */
                         type = struct minimal_symbol {
/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
...
                         } /* total size:   72 bytes */
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Or even saving one line:

~~~~~~~~~~~~~~~~~~~~~~~~~~~
(top-gdb) ptype/o minimal_symbol
/* offset    |  size */  type = struct minimal_symbol {
/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
...
                         } /* total size:   72 bytes */
~~~~~~~~~~~~~~~~~~~~~~~~~~~


Or in full (compare to the version further above):

(top-gdb) ptype/o minimal_symbol
/* offset    |  size */  type = struct minimal_symbol {
/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
/*    8      |     8 */        union {
/*                 8 */            LONGEST ivalue;
/*                 8 */            const block *block;
/*                 8 */            const gdb_byte *bytes;
/*                 8 */            CORE_ADDR address;
/*                 8 */            const common_block *common_block;
/*                 8 */            symbol *chain;
                               } /* total size:    8 bytes */ value;
/*   16      |     8 */        union {
/*                 8 */            obstack *obstack;
/*                 8 */            const char *demangled_name;
                               } /* total size:    8 bytes */ language_specific;
/*   24:27   |     4 */        language language : 5;
/*   24:26   |     4 */        unsigned int ada_mangled : 1;
/* XXX  2-bit hole   */
/* XXX  1-byte hole  */
/*   26      |     2 */        short section;
                           } /* total size:   32 bytes */ mginfo;
/*   32      |     8 */    unsigned long size;
/*   40      |     8 */    const char *filename;
/*   48:28   |     4 */    minimal_symbol_type type : 4;
/*   48:27   |     4 */    unsigned int created_by_gdb : 1;
/*   48:26   |     4 */    unsigned int target_flag_1 : 1;
/*   48:25   |     4 */    unsigned int target_flag_2 : 1;
/*   48:24   |     4 */    unsigned int has_size : 1;
/* XXX  7-byte hole  */
/*   56      |     8 */    minimal_symbol *hash_next;
/*   64      |     8 */    minimal_symbol *demangled_hash_next;
                         } /* total size:   72 bytes */
(top-gdb) 


#3 - I also find the position of those
"/* total size:    8 bytes */"  markers a bit hard
to grok/spot.

I'd suggest considering putting them on the left column
along with the rest of the offset/size info as well, like:

(top-gdb) ptype/tom minimal_symbol
/* offset    |  size */
type = struct minimal_symbol {
/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
/*    8      |     8 */        union {
/*                 8 */            LONGEST ivalue;
/*                 8 */            const block *block;
/*                 8 */            const gdb_byte *bytes;
/*                 8 */            CORE_ADDR address;
/*                 8 */            const common_block *common_block;
/*                 8 */            symbol *chain;
/* total size:     8 */        } value;
/*   16      |     8 */        union {
/*                 8 */            obstack *obstack;
/*                 8 */            const char *demangled_name;
/* total size:     8 */        } language_specific;
/*   24:27   |     4 */        language language : 5;
/*   24:26   |     4 */        unsigned int ada_mangled : 1;
/* XXX  2-bit hole   */
/* XXX  1-byte hole  */
/*   26      |     2 */        short section;
/* total size:    32 */    } mginfo;
/*   32      |     8 */    unsigned long size;
/*   40      |     8 */    const char *filename;
/*   48:28   |     4 */    minimal_symbol_type type : 4;
/*   48:27   |     4 */    unsigned int created_by_gdb : 1;
/*   48:26   |     4 */    unsigned int target_flag_1 : 1;
/*   48:25   |     4 */    unsigned int target_flag_2 : 1;
/*   48:24   |     4 */    unsigned int has_size : 1;
/* XXX  7-byte hole  */
/*   56      |     8 */    minimal_symbol *hash_next;
/*   64      |     8 */    minimal_symbol *demangled_hash_next;
} /* total size:   72 bytes */
(top-gdb) 

Or if you don't like it, consider putting that 
"total size:" info on a separate line before the struct/union
is closed, like pahole does:

        /* size: 128, cachelines: 2, members: 4 */
        /* sum members: 124, holes: 1, sum holes: 4 */
};

That may be even better because it provides a place
to put extra info, like pahole does (cacheline info etc.).

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13  4:50     ` Simon Marchi
@ 2017-12-13 16:42       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13 16:42 UTC (permalink / raw)
  To: Simon Marchi
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi,
	Pedro Alves, Keith Seitz

On Tuesday, December 12 2017, Simon Marchi wrote:

> On 2017-12-12 10:17 PM, Sergio Durigan Junior wrote:
>> +/* Print information about field at index FIELD_IDX of the struct type
>> +   TYPE.
>> +
>> +   ENDPOS is the one-past-the-end bit position of the previous field
>> +   (where we expect this field to be if there is no hole).  At the
>> +   end, ENDPOS is updated to the one-past-the-end bit position of the
>> +   current field.
>> +
>> +   OFFSET_BITPOS is the offset value we carry over when we are
>> +   printing a struct that is inside another struct; this is useful so
>> +   that the offset is constantly incremented (if we didn't carry it
>> +   over, the offset would be reset to zero when printing the inner
>> +   struct).
>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
>> +				  unsigned int *endpos, struct ui_file *stream,
>> +				  unsigned int offset_bitpos)
>> +{
>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
>> +  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
>> +  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
>> +  unsigned int fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
>> +
>> +  /* We check for *ENDPOS > 0 because there is a specific scenario
>> +     when *ENDPOS can be zero and BITPOS can be > 0: when we are
>> +     dealing with a struct/class with a virtual method.  Because of
>> +     the vtable, the first field of the struct/class will have an
>> +     offset of sizeof (void *) (the size of the vtable).  If we do not
>> +     check for *ENDPOS > 0 here, GDB will report a hole before the
>> +     first field, which is not accurate.  */
>> +  if (*endpos > 0 && *endpos < bitpos)
>> +    {
>> +      /* If ENDPOS is smaller than the current type's bitpos, it means
>> +	 there's a hole in the struct, so we report it here.  */
>> +      unsigned int hole = bitpos - *endpos;
>> +      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
>> +      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
>> +
>> +      if (hole_bit > 0)
>> +	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
>> +
>> +      if (hole_byte > 0)
>> +	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
>> +    }
>> +
>> +  if (TYPE_FIELD_PACKED (type, field_idx))
>> +    {
>> +      /* We're dealing with a bitfield.  Print how many bits are left
>> +	 to be used.  */
>> +      unsigned int bitsize = TYPE_FIELD_BITSIZE (type, field_idx);
>> +      /* The bitpos relative to the beginning of our container
>> +	 field.  */
>> +      unsigned int relative_bitpos;
>> +
>> +      /* The following was copied from
>> +	 value.c:value_primitive_field.  */
>> +      if ((bitpos % fieldsize_bit) + bitsize <= fieldsize_bit)
>> +	relative_bitpos = bitpos % fieldsize_bit;
>> +      else
>> +	relative_bitpos = bitpos % TARGET_CHAR_BIT;
>> +
>> +      /* This is the exact offset (in bits) of this bitfield.  */
>> +      unsigned int bit_offset
>> +	= (bitpos - relative_bitpos) + offset_bitpos;
>> +
>> +      /* The position of the field, relative to the beginning of the
>> +	 struct, and how many bits are left to be used in this
>> +	 container.  */
>> +      fprintf_filtered (stream, "/* %4u:%2u", bit_offset / TARGET_CHAR_BIT,
>> +			fieldsize_bit - (relative_bitpos + bitsize));
>> +      fieldsize_bit = bitsize;
>> +    }
>
> I won't pretend I understand this part.  I'll let Pedro look at it, or maybe try
> again tomorrow, it's getting late here :)

Yeah, it's complicated.

>> @@ -1041,25 +1183,63 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
>>  	     the debug info, they should be artificial.  */
>>  	  if ((i == vptr_fieldno && type == basetype)
>>  	      || TYPE_FIELD_ARTIFICIAL (type, i))
>> -	    continue;
>> +	    {
>> +	      if (flags->print_offsets)
>> +		c_print_type_vtable_offset_marker (type, i, stream,
>> +						   flags->offset_bitpos);
>> +	      continue;
>> +	    }
>
> With that version, the comment above the if would need to be updted.
>
> Note that the vtable marker won't be printed when looking at a child class.  With the
> following:
>
> struct foo
> {
>   virtual ~foo() = default;
>   int a;
>   short b;
> };
>
> struct bar : foo
> {
>   virtual ~bar () = default;
>   int z;
>   short c, d, e;
> };
>
> we get this:
>
> (gdb) ptype /o foo
> /* offset    |  size */
> type = struct foo {
> /*    0     |      8 */ /* vtable */
>                          public:
> /*    8      |     4 */    int a;
> /*   12      |     2 */    short b;
>
>                            ~foo();
> } /* total size:   16 bytes */
> (gdb) ptype /o bar
> /* offset    |  size */
> type = struct bar : public foo {
> /*   16      |     4 */    int z;
> /*   20      |     2 */    short c;
> /*   22      |     2 */    short d;
> /*   24      |     2 */    short e;
>                          public:
>                            ~bar();
> } /* total size:   32 bytes */
>
> If we print the vtable, I think it would make sense to print it for the child
> class too.  And if we do, it would also make sense to display the fields of base
> classes too, otherwise there would be a gap in the vtable offset and the first
> field offset.  I think it would be useful in order to show the holes between the fields
> of the base class and the child class.  In the example above, it would show that there's
> a two byte hole between b and z.  Putting z at the end of bar could save some space.
>
> But doing so would require to change ptype's behavior significantly, making it recurse
> in parent classes.  Should we do that only if /o is specified?  If this makes the patch
> too complex, I would suggest merging it without the vtable field, and take at adding it
> after.  We don't have to include all the features we can think of in the first iteration.

I agree that this use case would be good to have.  But I also agree that
it is a bit complex to implement...

(A few moments later)

OK, I gave it a try, and I may have something that works.  I am
attaching the patch to this e-mail so you can have a look.

>> diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
>> new file mode 100644
>> index 0000000000..04a887a703
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
>> @@ -0,0 +1,192 @@
>> +# This testcase is part of GDB, the GNU debugger.
>> +
>> +# Copyright 2017 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/>.
>> +
>> +standard_testfile .cc
>> +
>> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
>> +# that the expected holes will be present in the struct.
>> +if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
>> +    untested "test work only on x86_64 lp64"
>> +    return 0
>> +}
>> +
>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
>> +	  { debug c++ optimize=-O0 }] } {
>> +    return -1
>> +}
>> +
>> +# Test general offset printing, ctor/dtor printing, union, formatting.
>> +gdb_test "ptype /o struct abc" \
>> +    [multi_line \
>> +"type = struct abc {" \
>> +"/\\\* offset    |  size \\\*/" \
>> +"                         public:" \
>> +"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
>> +"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
>> +"/\\\* XXX  7-bit hole   \\\*/" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*   20      |     4 \\\*/    int field3;" \
>> +"/\\\*   24      |     1 \\\*/    char field4;" \
>> +"/\\\* XXX  7-byte hole  \\\*/" \
>> +"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
>> +"/\\\*   40      |     8 \\\*/    union {" \
>> +"/\\\*                 8 \\\*/        void \\\*field6;" \
>> +"/\\\*                 4 \\\*/        int field7;" \
>> +"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
>> +"" \
>> +"                           abc\\(void\\);" \
>> +"                           ~abc\\(\\);" \
>> +"} /\\\* total size:   48 bytes \\\*/"] \
>> +    "ptype offset struct abc"
>> +
>> +# Test nested structs.
>> +gdb_test "ptype /o struct pqr" \
>> +    [multi_line \
>> +"type = struct pqr {" \
>> +"/\\\* offset    |  size \\\*/" \
>> +"/\\\*    0      |     4 \\\*/    int f1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*    8      |    16 \\\*/    struct xyz {" \
>> +"/\\\*    8      |     4 \\\*/        int f1;" \
>> +"/\\\*   12      |     1 \\\*/        char f2;" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
>> +"/\\\*   24      |    24 \\\*/        struct tuv {" \
>> +"/\\\*   24      |     4 \\\*/            int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   32      |     8 \\\*/            char *a2;" \
>> +"/\\\*   40      |     4 \\\*/            int a3;" \
>> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
>> +"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
>> +"/\\\*   48      |     1 \\\*/    char ff3;" \
>> +"} /\\\* total size:   56 bytes \\\*/"] \
>> +    "ptype offset struct pqr"
>> +
>> +# Test that the offset is properly reset when we are printing an union
>> +# and go inside two inner structs.
>> +# This also tests a struct inside a struct inside an union.
>> +gdb_test "ptype /o union qwe" \
>> +    [multi_line \
>> +"/\\\* offset    |  size \\\*/" \
>> +"/\\\*                24 \\\*/    struct tuv {" \
>> +"/\\\*    0      |     4 \\\*/        int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*    8      |     8 \\\*/        char \\\*a2;" \
>> +"/\\\*   16      |     4 \\\*/        int a3;" \
>> +"                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
>> +"/\\\*                40 \\\*/    struct xyz {" \
>> +"/\\\*    0      |     4 \\\*/        int f1;" \
>> +"/\\\*    4      |     1 \\\*/        char f2;" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*    8      |     8 \\\*/        void \\\*f3;" \
>> +"/\\\*   16      |    24 \\\*/        struct tuv {" \
>> +"/\\\*   16      |     4 \\\*/            int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   24      |     8 \\\*/            char \\\*a2;" \
>> +"/\\\*   32      |     4 \\\*/            int a3;" \
>> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
>> +"                           } /\\\* total size:   40 bytes \\\*/ fff2;" \
>> +"} /\\\* total size:   40 bytes \\\*/"] \
>> +    "ptype offset union qwe"
>> +
>> +# Test printing a struct that contains an union, and that also
>> +# contains a struct.
>> +gdb_test "ptype /o struct poi" \
>> +    [multi_line \
>> +"/\\\* offset    |  size \\\*/" \
>> +"/\\\*    0      |     4 \\\*/    int f1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*    8      |    40 \\\*/    union qwe {" \
>> +"/\\\*                24 \\\*/        struct tuv {" \
>> +"/\\\*    8      |     4 \\\*/            int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   16      |     8 \\\*/            char \\\*a2;" \
>> +"/\\\*   24      |     4 \\\*/            int a3;" \
>> +"                               } /\\\* total size:   24 bytes \\\*/ fff1;" \
>> +"/\\\*                40 \\\*/        struct xyz {" \
>> +"/\\\*    8      |     4 \\\*/            int f1;" \
>> +"/\\\*   12      |     1 \\\*/            char f2;" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*   16      |     8 \\\*/            void \\\*f3;" \
>> +"/\\\*   24      |    24 \\\*/            struct tuv {" \
>> +"/\\\*   24      |     4 \\\*/                int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   32      |     8 \\\*/                char \\\*a2;" \
>> +"/\\\*   40      |     4 \\\*/                int a3;" \
>> +"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
>> +"                               } /\\\* total size:   40 bytes \\\*/ fff2;" \
>> +"                           } /\\\* total size:   40 bytes \\\*/ f2;" \
>> +"/\\\*   48      |     2 \\\*/    uint16_t f3;" \
>> +"/\\\* XXX  6-byte hole  */" \
>> +"/\\\*   56      |    56 \\\*/    struct pqr {" \
>> +"/\\\*   56      |     4 \\\*/        int ff1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   64      |    40 \\\*/        struct xyz {" \
>> +"/\\\*   64      |     4 \\\*/            int f1;" \
>> +"/\\\*   68      |     1 \\\*/            char f2;" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*   72      |     8 \\\*/            void \\\*f3;" \
>> +"/\\\*   80      |    24 \\\*/            struct tuv {" \
>> +"/\\\*   80      |     4 \\\*/                int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   88      |     8 \\\*/                char \\\*a2;" \
>> +"/\\\*   96      |     4 \\\*/                int a3;" \
>> +"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
>> +"                               } /\\\* total size:   40 bytes \\\*/ ff2;" \
>> +"/\\\*  104      |     1 \\\*/        char ff3;" \
>> +"                           } /\\\* total size:   56 bytes \\\*/ f4;" \
>> +"} /\\\* total size:  112 bytes \\\*/"] \
>> +    "ptype offset struct poi"
>> +
>> +# Test printing a struct with several bitfields, laid out in various
>> +# ways.
>> +#
>> +# Because dealing with bitfields and offsets is difficult, it can be
>> +# tricky to confirm that the output of the this command is accurate.
>> +# A nice way to do that is to use GDB's "x" command and print the
>> +# actual memory layout of the struct.  In order to differentiate
>> +# betweent bitfields and non-bitfield variables, one can assign "-1"
>
> "betweent"

Fixed.

>> +# to every bitfield in the struct.  An example of the output of "x"
>> +# using "struct tyu" is:
>> +#
>> +#   (gdb) x/24xb &e
>> +#   0x7fffffffd540: 0xff    0xff    0xff    0x1f    0x00    0x00    0x00    0x00
>> +#   0x7fffffffd548: 0xff    0xff    0xff    0xff    0xff    0xff    0xff    0xff
>> +#   0x7fffffffd550: 0xff    0x00    0x00    0x00    0x00    0x00    0x00    0x00
>> +#
>> +# Thanks to Pedro Alves for coming up with this method (which can be
>> +# used to inspect the other cases, of course).
>
> I'm all for thanking Pedro, but I would suggest putting this last remark in the commit
> message :)

Alright, moved to the commit message.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

diff --git a/gdb/NEWS b/gdb/NEWS
index c6fe297159..67d40d007b 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * New "--readnever" command line option instructs GDB to not read each
   symbol file's symbolic debug information.  This makes startup faster
   but at the expense of not being able to perform symbolic debugging.
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index ce10b5e005..c9906091d5 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -836,21 +844,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options
+  (int level, struct ui_file *stream, const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +881,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,15 +890,132 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
-/* Return true is an access label (i.e., "public:", "private:",
+static void
+c_print_type_vtable_offset_marker (struct type *type, unsigned int field_idx,
+				   struct ui_file *stream,
+				   unsigned int offset_bitpos)
+{
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int offset = (bitpos + offset_bitpos) / TARGET_CHAR_BIT;
+
+  fprintf_filtered (stream, "/* %4u     |   %4u */", offset,
+		    TYPE_LENGTH (TYPE_FIELD_TYPE (type, field_idx)));
+}
+
+/* Print information about field at index FIELD_IDX of the union type
+   TYPE.  Since union fields don't have the concept of offsets, we
+   just print their sizes.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
+				 struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+
+  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+}
+
+/* Print information about field at index FIELD_IDX of the struct type
+   TYPE.
+
+   ENDPOS is the one-past-the-end bit position of the previous field
+   (where we expect this field to be if there is no hole).  At the
+   end, ENDPOS is updated to the one-past-the-end bit position of the
+   current field.
+
+   OFFSET_BITPOS is the offset value we carry over when we are
+   printing a struct that is inside another struct; this is useful so
+   that the offset is constantly incremented (if we didn't carry it
+   over, the offset would be reset to zero when printing the inner
+   struct).
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
+				  struct ui_file *stream,
+				  struct print_offset_data *po_data)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+
+  /* We check for *ENDPOS > 0 because there is a specific scenario
+     when *ENDPOS can be zero and BITPOS can be > 0: when we are
+     dealing with a struct/class with a virtual method.  Because of
+     the vtable, the first field of the struct/class will have an
+     offset of sizeof (void *) (the size of the vtable).  If we do not
+     check for *ENDPOS > 0 here, GDB will report a hole before the
+     first field, which is not accurate.  */
+  if (po_data->endpos > 0 && po_data->endpos < bitpos)
+    {
+      /* If ENDPOS is smaller than the current type's bitpos, it means
+	 there's a hole in the struct, so we report it here.  */
+      unsigned int hole = bitpos - po_data->endpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      unsigned int bitsize = TYPE_FIELD_BITSIZE (type, field_idx);
+      /* The bitpos relative to the beginning of our container
+	 field.  */
+      unsigned int relative_bitpos;
+
+      /* The following was copied from
+	 value.c:value_primitive_field.  */
+      if ((bitpos % fieldsize_bit) + bitsize <= fieldsize_bit)
+	relative_bitpos = bitpos % fieldsize_bit;
+      else
+	relative_bitpos = bitpos % TARGET_CHAR_BIT;
+
+      /* This is the exact offset (in bits) of this bitfield.  */
+      unsigned int bit_offset
+	= (bitpos - relative_bitpos) + po_data->offset_bitpos;
+
+      /* The position of the field, relative to the beginning of the
+	 struct, and how many bits are left to be used in this
+	 container.  */
+      fprintf_filtered (stream, "/* %4u:%2u", bit_offset / TARGET_CHAR_BIT,
+			fieldsize_bit - (relative_bitpos + bitsize));
+      fieldsize_bit = bitsize;
+    }
+  else
+    {
+      /* The position of the field, relative to the beginning of the
+	 struct.  */
+      fprintf_filtered (stream, "/* %4u",
+			(bitpos + po_data->offset_bitpos) / TARGET_CHAR_BIT);
+
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  po_data->endpos = bitpos + fieldsize_bit;
+}
+
+/* Return true if an access label (i.e., "public:", "private:",
    "protected:") needs to be printed for TYPE.  */
 
 static bool
@@ -1004,6 +1144,21 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
       recursively_update_typedef_hash (local_flags.local_typedefs, type);
 
       fprintf_filtered (stream, "{\n");
+
+      if (flags->print_offsets)
+	{
+	  for (int i = 0; i < TYPE_N_BASECLASSES (type); ++i)
+	    {
+	      struct type *bclass = TYPE_BASECLASS (type, i);
+
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      c_print_type (bclass, "", stream, show, level + 4,
+			    &local_flags);
+	      fputs_filtered ("\n", stream);
+	    }
+	}
+
       if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
 	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
 	{
@@ -1032,6 +1187,7 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
       int len = TYPE_NFIELDS (type);
       vptr_fieldno = get_vptr_fieldno (type, &basetype);
+
       for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
 	{
 	  QUIT;
@@ -1041,25 +1197,71 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	     the debug info, they should be artificial.  */
 	  if ((i == vptr_fieldno && type == basetype)
 	      || TYPE_FIELD_ARTIFICIAL (type, i))
-	    continue;
+	    {
+	      if (flags->print_offsets)
+		{
+		  c_print_type_vtable_offset_marker
+		    (type, i, stream, flags->po_data.offset_bitpos);
+		  print_spaces_filtered (level + 4, stream);
+		  fprintf_filtered (stream, "/* vtable */\n");
+		}
+	      continue;
+	    }
 
 	  if (need_access_label)
 	    {
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FIELD_PROTECTED (type, i),
-		 TYPE_FIELD_PRIVATE (type, i));
+		 TYPE_FIELD_PRIVATE (type, i), flags);
+	    }
+
+	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+	  if (flags->print_offsets)
+	    {
+	      if (!is_static)
+		{
+		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		    {
+		      c_print_type_struct_field_offset
+			(type, i, stream, &local_flags.po_data);
+		      ((struct type_print_options *) flags)->po_data
+			= local_flags.po_data;
+		    }
+		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
+		    c_print_type_union_field_offset (type, i, stream);
+		}
+	      else
+		print_spaces_filtered (OFFSET_SPC_LEN, stream);
 	    }
 
 	  print_spaces_filtered (level + 4, stream);
-	  if (field_is_static (&TYPE_FIELD (type, i)))
+	  if (is_static)
 	    fprintf_filtered (stream, "static ");
+
+	  int newshow = show - 1;
+
+	  if (flags->print_offsets
+	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
+		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
+	    {
+	      /* If we're printing offsets and this field's type is
+		 either a struct or an union, then we're interested in
+		 expanding it.  */
+	      ++newshow;
+
+	      /* Make sure we carry our offset when we expand the
+		 struct.  */
+	      local_flags.po_data.offset_bitpos
+		= flags->po_data.offset_bitpos + TYPE_FIELD_BITPOS (type, i);
+	    }
+
 	  c_print_type (TYPE_FIELD_TYPE (type, i),
 			TYPE_FIELD_NAME (type, i),
-			stream, show - 1, level + 4,
+			stream, newshow, level + 4,
 			&local_flags);
-	  if (!field_is_static (&TYPE_FIELD (type, i))
-	      && TYPE_FIELD_PACKED (type, i))
+	  if (!is_static && TYPE_FIELD_PACKED (type, i))
 	    {
 	      /* It is a bitfield.  This code does not attempt
 		 to look at the bitpos and reconstruct filler,
@@ -1122,9 +1324,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FN_FIELD_PROTECTED (f, j),
-		 TYPE_FN_FIELD_PRIVATE (f, j));
+		 TYPE_FN_FIELD_PRIVATE (f, j), flags);
 
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
 		fprintf_filtered (stream, "virtual ");
 	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
@@ -1143,9 +1346,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		       && !is_full_physname_constructor  /* " " */
 		       && !is_type_conversion_operator (type, i, j))
 		{
+		  unsigned int old_po = local_flags.print_offsets;
+
+		  /* Temporarily disable print_offsets, because it
+		     would mess with indentation.  */
+		  local_flags.print_offsets = 0;
 		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
 				"", stream, -1, 0,
 				&local_flags);
+		  local_flags.print_offsets = old_po;
+
 		  fputs_filtered (" ", stream);
 		}
 	      if (TYPE_FN_FIELD_STUB (f, j))
@@ -1226,9 +1436,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
 	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
 	    {
-	      print_spaces_filtered (level + 4, stream);
+	      unsigned int old_po = semi_local_flags.print_offsets;
+
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      //	      semi_local_flags.print_offsets = 0;
 	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
 			    "", stream, show, level + 4, &semi_local_flags);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
@@ -1254,11 +1471,17 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		  section_type = output_access_specifier
 		    (stream, section_type, level,
 		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i), flags);
 		}
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      fprintf_filtered (stream, "typedef ");
 
+	      unsigned int old_po = semi_local_flags.print_offsets;
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      semi_local_flags.print_offsets = 0;
+
 	      /* We want to print typedefs with substitutions
 		 from the template parameters or globally-known
 		 typedefs but not local typedefs.  */
@@ -1266,13 +1489,21 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 			    TYPE_TYPEDEF_FIELD_NAME (type, i),
 			    stream, show - 1, level + 4,
 			    &semi_local_flags);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
 
+      if (flags->print_offsets && level > 0)
+	print_spaces_filtered (OFFSET_SPC_LEN, stream);
+
       fprintfi_filtered (level, stream, "}");
     }
 
+  if (show > 0 && flags->print_offsets)
+    fprintf_filtered (stream, " /* total size: %4u bytes */",
+		      TYPE_LENGTH (type));
+
   do_cleanups (local_cleanups);
 }
 
@@ -1300,7 +1531,6 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 {
   int i;
   int len;
-  int j;
 
   QUIT;
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index da3ed28dfe..08e29edb82 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17216,6 +17216,121 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.
+
+For example, given the following declarations:
+
+@smallexample
+struct tuv
+@{
+  int a1;
+  char *a2;
+  int a3;
+@};
+
+struct xyz
+@{
+  int f1;
+  char f2;
+  void *f3;
+  struct tuv f4;
+@};
+
+union qwe
+@{
+  struct tuv fff1;
+  struct xyz fff2;
+@};
+
+struct tyu
+@{
+  int a1 : 1;
+  int a2 : 3;
+  int a3 : 23;
+  char a4 : 2;
+  int64_t a5;
+  int a6 : 5;
+  int64_t a7 : 3;
+@};
+@end smallexample
+
+Issuing a @kbd{ptype /o struct tuv} would print:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tuv
+/* offset    |  size */
+struct tuv @{
+/*    0      |     4 */    int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */    char *a2;
+/*   16      |     4 */    int a3;
+@} /* total size:   24 bytes */
+@end smallexample
+
+Notice the format of the first column of comments.  There, you can
+find two parts separated by the @code{|} character: the @emph{offset},
+which indicates where the field is located inside the struct, in
+bytes, and the @emph{size} of the field.  Another interesting line is
+the marker of a @emph{hole} in the struct, indicating that it may be
+possible to pack the struct and make it use less space by reorganizing
+its fields.
+
+It is also possible to print offsets inside an union:
+
+@smallexample
+(@value{GDBP}) ptype /o union qwe
+/* offset    |  size */
+union qwe @{
+/*                24 */    struct tuv @{
+/*    0      |     4 */        int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */        char *a2;
+/*   16      |     4 */        int a3;
+                           @} /* total size:   24 bytes */ fff1;
+/*                40 */    struct xyz @{
+/*    0      |     4 */        int f1;
+/*    4      |     1 */        char f2;
+/* XXX  3-byte hole  */
+/*    8      |     8 */        void *f3;
+/*   16      |    24 */        struct tuv @{
+/*   16      |     4 */            int a1;
+/* XXX  4-byte hole  */
+/*   24      |     8 */            char *a2;
+/*   32      |     4 */            int a3;
+                               @} /* total size:   24 bytes */ f4;
+                           @} /* total size:   40 bytes */ fff2;
+@} /* total size:   40 bytes */
+@end smallexample
+
+In this case, since @code{struct tuv} and @code{struct xyz} occupy the
+same space (because we are dealing with an union), the offset is not
+printed for them.  However, you can still examine the offset of each
+of these structures' fields.
+
+Another useful scenario is printing the offsets of a struct containing
+bitfields:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tyu
+/* offset    |  size */
+struct tyu @{
+/*    0:31   |     4 */    int a1 : 1;
+/*    0:28   |     4 */    int a2 : 3;
+/*    0: 5   |     4 */    int a3 : 23;
+/*    3: 3   |     1 */    char a4 : 2;
+/* XXX  3-bit hole   */
+/* XXX  4-byte hole  */
+/*    8      |     8 */    int64_t a5;
+/*   16:27   |     4 */    int a6 : 5;
+/*   16:56   |     8 */    int64_t a7 : 3;
+@} /* total size:   24 bytes */
+@end smallexample
+
+Note how the offset information is now extended to also include how
+many bits are left to be used in each bitfield.
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..aadcace517
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,158 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
+
+#include <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  char field4;
+
+  /* 7-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+};
+
+/* This struct will be nested inside 'struct xyz'.  */
+
+struct tuv
+{
+  int a1;
+
+  char *a2;
+
+  int a3;
+};
+
+/* This struct will be nested inside 'struct pqr'.  */
+
+struct xyz
+{
+  int f1;
+
+  char f2;
+
+  void *f3;
+
+  struct tuv f4;
+};
+
+/* A struct with a nested struct.  */
+
+struct pqr
+{
+  int ff1;
+
+  struct xyz ff2;
+
+  char ff3;
+};
+
+/* A union with two nested structs.  */
+
+union qwe
+{
+  struct tuv fff1;
+
+  struct xyz fff2;
+};
+
+/* A struct with an union.  */
+
+struct poi
+{
+  int f1;
+
+  union qwe f2;
+
+  uint16_t f3;
+
+  struct pqr f4;
+};
+
+/* A struct with bitfields.  */
+
+struct tyu
+{
+  int a1 : 1;
+
+  int a2 : 3;
+
+  int a3 : 23;
+
+  char a4 : 2;
+
+  int64_t a5;
+
+  int a6 : 5;
+
+  int64_t a7 : 3;
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+  struct pqr bar;
+  union qwe c;
+  struct poi d;
+  struct tyu e;
+  uint8_t i;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..74101ac5df
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,189 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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/>.
+
+standard_testfile .cc
+
+# Test only works on x86_64 LP64 targets.  That's how we guarantee
+# that the expected holes will be present in the struct.
+if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
+    untested "test work only on x86_64 lp64"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ optimize=-O0 }] } {
+    return -1
+}
+
+# Test general offset printing, ctor/dtor printing, union, formatting.
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+"type = struct abc {" \
+"/\\\* offset    |  size \\\*/" \
+"                         public:" \
+"/\\\*    8      |     8 \\\*/    void \\\*field1;" \
+"/\\\*   16:31   |     4 \\\*/    unsigned int field2 : 1;" \
+"/\\\* XXX  7-bit hole   \\\*/" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   20      |     4 \\\*/    int field3;" \
+"/\\\*   24      |     1 \\\*/    char field4;" \
+"/\\\* XXX  7-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/    uint64_t field5;" \
+"/\\\*   40      |     8 \\\*/    union {" \
+"/\\\*                 8 \\\*/        void \\\*field6;" \
+"/\\\*                 4 \\\*/        int field7;" \
+"                           } /\\\* total size:    8 bytes \\\*/ field8;" \
+"" \
+"                           abc\\(void\\);" \
+"                           ~abc\\(\\);" \
+"} /\\\* total size:   48 bytes \\\*/"] \
+    "ptype offset struct abc"
+
+# Test nested structs.
+gdb_test "ptype /o struct pqr" \
+    [multi_line \
+"type = struct pqr {" \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    16 \\\*/    struct xyz {" \
+"/\\\*    8      |     4 \\\*/        int f1;" \
+"/\\\*   12      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/        struct tuv {" \
+"/\\\*   24      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/            char *a2;" \
+"/\\\*   40      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*   48      |     1 \\\*/    char ff3;" \
+"} /\\\* total size:   56 bytes \\\*/"] \
+    "ptype offset struct pqr"
+
+# Test that the offset is properly reset when we are printing an union
+# and go inside two inner structs.
+# This also tests a struct inside a struct inside an union.
+gdb_test "ptype /o union qwe" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*                24 \\\*/    struct tuv {" \
+"/\\\*    0      |     4 \\\*/        int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/        char \\\*a2;" \
+"/\\\*   16      |     4 \\\*/        int a3;" \
+"                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
+"/\\\*                40 \\\*/    struct xyz {" \
+"/\\\*    0      |     4 \\\*/        int f1;" \
+"/\\\*    4      |     1 \\\*/        char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/        void \\\*f3;" \
+"/\\\*   16      |    24 \\\*/        struct tuv {" \
+"/\\\*   16      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   24      |     8 \\\*/            char \\\*a2;" \
+"/\\\*   32      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                           } /\\\* total size:   40 bytes \\\*/ fff2;" \
+"} /\\\* total size:   40 bytes \\\*/"] \
+    "ptype offset union qwe"
+
+# Test printing a struct that contains an union, and that also
+# contains a struct.
+gdb_test "ptype /o struct poi" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"/\\\*    0      |     4 \\\*/    int f1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |    40 \\\*/    union qwe {" \
+"/\\\*                24 \\\*/        struct tuv {" \
+"/\\\*    8      |     4 \\\*/            int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/            char \\\*a2;" \
+"/\\\*   24      |     4 \\\*/            int a3;" \
+"                               } /\\\* total size:   24 bytes \\\*/ fff1;" \
+"/\\\*                40 \\\*/        struct xyz {" \
+"/\\\*    8      |     4 \\\*/            int f1;" \
+"/\\\*   12      |     1 \\\*/            char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   16      |     8 \\\*/            void \\\*f3;" \
+"/\\\*   24      |    24 \\\*/            struct tuv {" \
+"/\\\*   24      |     4 \\\*/                int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   32      |     8 \\\*/                char \\\*a2;" \
+"/\\\*   40      |     4 \\\*/                int a3;" \
+"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                               } /\\\* total size:   40 bytes \\\*/ fff2;" \
+"                           } /\\\* total size:   40 bytes \\\*/ f2;" \
+"/\\\*   48      |     2 \\\*/    uint16_t f3;" \
+"/\\\* XXX  6-byte hole  */" \
+"/\\\*   56      |    56 \\\*/    struct pqr {" \
+"/\\\*   56      |     4 \\\*/        int ff1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   64      |    40 \\\*/        struct xyz {" \
+"/\\\*   64      |     4 \\\*/            int f1;" \
+"/\\\*   68      |     1 \\\*/            char f2;" \
+"/\\\* XXX  3-byte hole  \\\*/" \
+"/\\\*   72      |     8 \\\*/            void \\\*f3;" \
+"/\\\*   80      |    24 \\\*/            struct tuv {" \
+"/\\\*   80      |     4 \\\*/                int a1;" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*   88      |     8 \\\*/                char \\\*a2;" \
+"/\\\*   96      |     4 \\\*/                int a3;" \
+"                                   } /\\\* total size:   24 bytes \\\*/ f4;" \
+"                               } /\\\* total size:   40 bytes \\\*/ ff2;" \
+"/\\\*  104      |     1 \\\*/        char ff3;" \
+"                           } /\\\* total size:   56 bytes \\\*/ f4;" \
+"} /\\\* total size:  112 bytes \\\*/"] \
+    "ptype offset struct poi"
+
+# Test printing a struct with several bitfields, laid out in various
+# ways.
+#
+# Because dealing with bitfields and offsets is difficult, it can be
+# tricky to confirm that the output of the this command is accurate.
+# A nice way to do that is to use GDB's "x" command and print the
+# actual memory layout of the struct.  In order to differentiate
+# between bitfields and non-bitfield variables, one can assign "-1" to
+# every bitfield in the struct.  An example of the output of "x" using
+# "struct tyu" is:
+#
+#   (gdb) x/24xb &e
+#   0x7fffffffd540: 0xff    0xff    0xff    0x1f    0x00    0x00    0x00    0x00
+#   0x7fffffffd548: 0xff    0xff    0xff    0xff    0xff    0xff    0xff    0xff
+#   0x7fffffffd550: 0xff    0x00    0x00    0x00    0x00    0x00    0x00    0x00
+gdb_test "ptype /o struct tyu" \
+    [multi_line \
+"/\\\* offset    |  size \\\*/" \
+"struct tyu {" \
+"/\\\*    0:31   |     4 \\\*/    int a1 : 1;" \
+"/\\\*    0:28   |     4 \\\*/    int a2 : 3;" \
+"/\\\*    0: 5   |     4 \\\*/    int a3 : 23;" \
+"/\\\*    3: 3   |     1 \\\*/    char a4 : 2;" \
+"/\\\* XXX  3-bit hole   \\\*/" \
+"/\\\* XXX  4-byte hole  \\\*/" \
+"/\\\*    8      |     8 \\\*/    int64_t a5;" \
+"/\\\*   16:27   |     4 \\\*/    int a6 : 5;" \
+"/\\\*   16:56   |     8 \\\*/    int64_t a7 : 3;" \
+"} /\\\* total size:   24 bytes \\\*/"] \
+    "ptype offset struct tyu"
+
+# Test that we don't print any header when issuing a "ptype /o" on a
+# non-struct, non-union, non-class type.
+gdb_test "ptype /o int" "int" "ptype offset int"
+gdb_test "ptype /o uint8_t" "char" "ptype offset uint8_t"
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 9d9d6f5a49..72a05b2471 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,6 +42,11 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
+  {
+    0,				/* offset_bitpos */
+    0,
+  },
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -55,6 +60,11 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
+  {
+    0,				/* offset_bitpos */
+    0,
+  },
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -440,6 +450,9 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  flags.print_offsets = 1;
+		  break;
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -499,6 +512,11 @@ whatis_exp (const char *exp, int show)
 	real_type = value_rtti_type (val, &full, &top, &using_enc);
     }
 
+  if (flags.print_offsets &&
+      (TYPE_CODE (type) == TYPE_CODE_STRUCT
+       || TYPE_CODE (type) == TYPE_CODE_UNION))
+    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
+
   printf_filtered ("type = ");
 
   if (!flags.raw)
@@ -759,7 +777,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a2058b0120..ba4ed8e292 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -24,6 +24,17 @@ struct ui_file;
 struct typedef_hash_table;
 struct ext_lang_type_printers;
 
+struct print_offset_data
+{
+  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
+     This is needed for when we are printing nested structs and want
+     to make sure that the printed offset for each field carries over
+     the offset of the outter struct.  */
+  unsigned int offset_bitpos;
+
+  unsigned int endpos;
+};
+
 struct type_print_options
 {
   /* True means that no special printing flags should apply.  */
@@ -35,6 +46,11 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
+  struct print_offset_data po_data;
+
   /* The number of nested type definitions to print.  -1 == all.  */
   int print_nested_type_limit;
 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13 16:19     ` Pedro Alves
@ 2017-12-13 17:13       ` Sergio Durigan Junior
  2017-12-13 20:36         ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13 17:13 UTC (permalink / raw)
  To: Pedro Alves
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On Wednesday, December 13 2017, Pedro Alves wrote:

> On 12/13/2017 03:17 AM, Sergio Durigan Junior wrote:
>
>> +/* A struct with an union.  */
>> +
>> +struct poi
>> +{
>> +  int f1;
>> +
>> +  union qwe f2;
>> +
>> +  uint16_t f3;
>> +
>> +  struct pqr f4;
>> +};
>> +
>> +/* A struct with bitfields.  */
>> +
>> +struct tyu
>> +{
>> +  int a1 : 1;
>> +
>> +  int a2 : 3;
>> +
>> +  int a3 : 23;
>> +
>> +  char a4 : 2;
>> +
>> +  int64_t a5;
>> +
>> +  int a6 : 5;
>> +
>> +  int64_t a7 : 3;
>> +};
>
> I think that the testcase should also make sure to exercise the new
> offset computations in the case c_print_type_struct_field_offset's
> 'offset_bitpos' parameter is > 0.  Is it already covered?
> I assume we'll need a test like tyu (struct with bitfields with
> overlapping underlying objects), but that inherits some other
> base structure?

Ah, good point, I'll add this test.

>
>> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
>> @@ -0,0 +1,192 @@
>> +# This testcase is part of GDB, the GNU debugger.
>> +
>> +# Copyright 2017 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/>.
>> +
>
> Please add an intro comment describing what this testcase is about.

OK.

>> +standard_testfile .cc
>> +
>> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
>> +# that the expected holes will be present in the struct.
>> +if { !([istarget "x86_64-*-*"] && [is_lp64_target]) } {
>> +    untested "test work only on x86_64 lp64"
>> +    return 0
>> +}
>
> I'm mildly worried about whether the bitfield handling is working
> correctly on big endian machines.  We may want to lift this
> x86-64-only restriction, by using e.g., alignas(N) or
> __attribute__((aligned(N)) to take care of most of the differences
> between architectures and end up with few per-arch code in
> the .exp.  But I'm fine with starting with only x86-64 if you
> confirm manually on e.g., a big endian PPC64 machine on the
> compile farm, and we can extend the testcase in that direction
> after this is merged.

OK, I'll confirm on PPC64BE.

>> +
>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
>> +	  { debug c++ optimize=-O0 }] } {
>> +    return -1
>> +}
>
> Weren't you going to remove that optimize thing? :-)

Yes, sorry.  Removed.

>> +# Test that the offset is properly reset when we are printing an union
>> +# and go inside two inner structs.
>> +# This also tests a struct inside a struct inside an union.
>
> "a union".  (two times here; there may be other places.)

Fixed.

>> +gdb_test "ptype /o union qwe" \
>> +    [multi_line \
>> +"/\\\* offset    |  size \\\*/" \
>> +"/\\\*                24 \\\*/    struct tuv {" \
>> +"/\\\*    0      |     4 \\\*/        int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*    8      |     8 \\\*/        char \\\*a2;" \
>> +"/\\\*   16      |     4 \\\*/        int a3;" \
>> +"                           } /\\\* total size:   24 bytes \\\*/ fff1;" \
>> +"/\\\*                40 \\\*/    struct xyz {" \
>> +"/\\\*    0      |     4 \\\*/        int f1;" \
>> +"/\\\*    4      |     1 \\\*/        char f2;" \
>> +"/\\\* XXX  3-byte hole  \\\*/" \
>> +"/\\\*    8      |     8 \\\*/        void \\\*f3;" \
>> +"/\\\*   16      |    24 \\\*/        struct tuv {" \
>> +"/\\\*   16      |     4 \\\*/            int a1;" \
>> +"/\\\* XXX  4-byte hole  \\\*/" \
>> +"/\\\*   24      |     8 \\\*/            char \\\*a2;" \
>> +"/\\\*   32      |     4 \\\*/            int a3;" \
>> +"                               } /\\\* total size:   24 bytes \\\*/ f4;" \
>> +"                           } /\\\* total size:   40 bytes \\\*/ fff2;" \
>> +"} /\\\* total size:   40 bytes \\\*/"] \
>> +    "ptype offset union qwe"
>
> Did you try using {} instead of "" for these strings,
> avoiding all the escaping?

Yes, Simon also made the same comment.  I tried replacing by {} but it
didn't work at the first attempt and since I was hacking other stuff at
the moment I dind't bother tweaking it and just reverted to using "".
If it's something you really want, I can do.  Otherwise I'd prefer to
leave it like that.

>> @@ -499,6 +506,11 @@ whatis_exp (const char *exp, int show)
>>  	real_type = value_rtti_type (val, &full, &top, &using_enc);
>>      }
>>  
>> +  if (flags.print_offsets &&
>
> && goes on the next line.

Fixed.

>> +      (TYPE_CODE (type) == TYPE_CODE_STRUCT
>> +       || TYPE_CODE (type) == TYPE_CODE_UNION))
>> +    fprintf_filtered (gdb_stdout, "/* offset    |  size */\n");
>> +
>>    printf_filtered ("type = ");
>
> Thanks,
> Pedro Alves

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13 16:17     ` Eli Zaretskii
@ 2017-12-13 17:14       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13 17:14 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches, tom, simon.marchi, palves, keiths

On Wednesday, December 13 2017, Eli Zaretskii wrote:

>> From: Sergio Durigan Junior <sergiodj@redhat.com>
>> Cc: Tom Tromey <tom@tromey.com>, Eli Zaretskii <eliz@gnu.org>, Simon
>> Marchi <simon.marchi@ericsson.com>, Pedro Alves <palves@redhat.com>,
>> Keith Seitz <keiths@redhat.com>, Sergio Durigan Junior
>> <sergiodj@redhat.com>
>> Date: Tue, 12 Dec 2017 22:17:24 -0500
>> 
>> This commit implements the pahole-like '/o' option for 'ptype', which
>> +Issuing a @kbd{ptype /o struct tuv} would print:
>                                       ^
> Please insert "command" after the command, where I indicated.
> Otherwise the sentence reads awkward.

Done.

>> +Notice the format of the first column of comments.  There, you can
>> +find two parts separated by the @code{|} character: the @emph{offset},
>                                    ^^^^^^^^
> @samp, not @code.

Fixed.

> The documentation parts are okay with these fixed.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13 16:20     ` Pedro Alves
@ 2017-12-13 17:41       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13 17:41 UTC (permalink / raw)
  To: Pedro Alves
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On Wednesday, December 13 2017, Pedro Alves wrote:

> [Sending user-interface suggestions separately from core
> functionality review.]

Thanks.

> On 12/13/2017 03:17 AM, Sergio Durigan Junior wrote:
>>   (gdb) ptype /o stap_probe
>>   /* offset    |  size */
>>   struct stap_probe {
>>   /*    0      |    40 */    struct probe {
>>   /*    0      |     8 */        const probe_ops *pops;
>>   /*    8      |     8 */        gdbarch *arch;
>>   /*   16      |     8 */        const char *name;
>>   /*   24      |     8 */        const char *provider;
>>   /*   32      |     8 */        CORE_ADDR address;
>> 			     } /* total size:   40 bytes */ p;
>>   /*   40      |     8 */    CORE_ADDR sem_addr;
>>   /*   48:31   |     4 */    unsigned int args_parsed : 1;
>>   /* XXX  7-bit hole   */
>>   /* XXX  7-byte hole  */
>>   /*   56      |     8 */    union {
>>   /*                 8 */        const char *text;
>>   /*                 8 */        VEC_stap_probe_arg_s *vec;
>> 			     } /* total size:    8 bytes */ args_u;
>>   } /* total size:   64 bytes */
>
> I've experimented with (different versions of) of this patch
> against gdb a bit, and I have to say that I'm finding myself
> a bit confused by the currently produced output.  It feels
> like the output should be a bit more obvious and easier to
> skim quickly.
>
> For example, I was looking at gdb's minimal_symbol (what I had
> used when I noticed the bitfield handling in earlier versions was
> incorrect):
>
> (top-gdb) ptype/tom minimal_symbol
> /* offset    |  size */
> type = struct minimal_symbol {
> /*    0      |    32 */    struct general_symbol_info {
> /*    0      |     8 */        const char *name;
> /*    8      |     8 */        union {
> /*                 8 */            LONGEST ivalue;
> /*                 8 */            const block *block;
> /*                 8 */            const gdb_byte *bytes;
> /*                 8 */            CORE_ADDR address;
> /*                 8 */            const common_block *common_block;
> /*                 8 */            symbol *chain;
>                                } /* total size:    8 bytes */ value;
> /*   16      |     8 */        union {
> /*                 8 */            obstack *obstack;
> /*                 8 */            const char *demangled_name;
>                                } /* total size:    8 bytes */ language_specific;
> /*   24:27   |     4 */        language language : 5;
> /*   24:26   |     4 */        unsigned int ada_mangled : 1;
> /* XXX  2-bit hole   */
> /* XXX  1-byte hole  */
> /*   26      |     2 */        short section;
>                            } /* total size:   32 bytes */ mginfo;
> /*   32      |     8 */    unsigned long size;
> /*   40      |     8 */    const char *filename;
> /*   48:28   |     4 */    minimal_symbol_type type : 4;
> /*   48:27   |     4 */    unsigned int created_by_gdb : 1;
> /*   48:26   |     4 */    unsigned int target_flag_1 : 1;
> /*   48:25   |     4 */    unsigned int target_flag_2 : 1;
> /*   48:24   |     4 */    unsigned int has_size : 1;
> /* XXX  7-byte hole  */
> /*   56      |     8 */    minimal_symbol *hash_next;
> /*   64      |     8 */    minimal_symbol *demangled_hash_next;
> } /* total size:   72 bytes */
> (top-gdb) 
>
> and I have the following observations:
>
> #1 - left vs right
>
> We print the extra offset/size info on the leftside of the
> screen, shifting the whole type to the right.  If we were to put
> the new info on the right, then a "ptype S" vs "ptype/o S" would
> look mostly the same, except for the extra info that appears
> on the right, and it'd map better to what you'd write in
> actual code (who puts comments on the left side, right?)
> pahole(1) prints the info on the right side.  Was there a
> particular reason gdb's version prints it on the left hand
> side?  Maybe the Python version did that too (did it?  I haven't
> tried it) because it'd be difficult with the Python
> implementation otherwise?  

I agree, and when I started working on this feature the first plan was
to print things on the right.  I even had a patch that did that.  But
the main problem was that it was hard to properly align the /* */
blocks.  For example, "ptype /o" prints nested structures, which means
that the we may print things that are aligned at level + 8, level + 12,
or even further.  This would make the /* */ blocks be aligned
differently, which IMHO makes the output much harder to read.  pahole
(the tool) solves that by printing nested structures separately, but if
we were to do that we'd need a bit refactorization of the current code.

About the Python version of pahole, it also prints the /* */ blocks on
the left side, though in an uglier way IMHO:

(gdb) pahole struct wer                        
              struct wer {                     
 /*   0  24 */  struct tuv {                   
 /*   0   4 */    int a1                       
  /* XXX 32 bit hole, try to pack */           
 /*   8   8 */    char * a2                    
 /*  16   4 */    int a3                       
                } tuv                          
 /*  24  24 */  struct tyu {                   
 /*   0   0 */    int a1                       
 /*   0   0 */    int a2                       
 /*   0   2 */    int a3                       
 /*   3   0 */    char a4                      
  /* XXX 35 bit hole, try to pack */           
 /*   8   8 */    long a5                      
 /*  16   0 */    int a6                       
 /*  16   0 */    long a7                      
                } a1                           
              }                                

> But maybe it's just me.  On to the next point in
> such case.
>
> #2 - shift "type =" header to the right as well?
>
> Currently, the leading "type =" and ending "}" lines
> are aligned on the left, along with the offset/size info.
> I think shifting those to the right as well might make the
> output nicer.  The idea being that /o would split the
> output in two more-clearly-separated columns, with the new info
> panned hard on the left column, and old ptype info on
> the right column.
>
> I.e., currently we get:
>
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
> (top-gdb) ptype/o minimal_symbol
> /* offset    |  size */
> type = struct minimal_symbol {
> /*    0      |    32 */    struct general_symbol_info {
> /*    0      |     8 */        const char *name;
> ...
> } /* total size:   72 bytes */
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> And I'd propose instead:
>
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
> (top-gdb) ptype/o minimal_symbol
> /* offset    |  size */
>                          type = struct minimal_symbol {
> /*    0      |    32 */    struct general_symbol_info {
> /*    0      |     8 */        const char *name;
> ...
>                          } /* total size:   72 bytes */
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> Or even saving one line:
>
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
> (top-gdb) ptype/o minimal_symbol
> /* offset    |  size */  type = struct minimal_symbol {
> /*    0      |    32 */    struct general_symbol_info {
> /*    0      |     8 */        const char *name;
> ...
>                          } /* total size:   72 bytes */
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
>
> Or in full (compare to the version further above):
>
> (top-gdb) ptype/o minimal_symbol
> /* offset    |  size */  type = struct minimal_symbol {
> /*    0      |    32 */    struct general_symbol_info {
> /*    0      |     8 */        const char *name;
> /*    8      |     8 */        union {
0> /*                 8 */            LONGEST ivalue;
> /*                 8 */            const block *block;
> /*                 8 */            const gdb_byte *bytes;
> /*                 8 */            CORE_ADDR address;
> /*                 8 */            const common_block *common_block;
> /*                 8 */            symbol *chain;
>                                } /* total size:    8 bytes */ value;
> /*   16      |     8 */        union {
> /*                 8 */            obstack *obstack;
> /*                 8 */            const char *demangled_name;
>                                } /* total size:    8 bytes */ language_specific;
> /*   24:27   |     4 */        language language : 5;
> /*   24:26   |     4 */        unsigned int ada_mangled : 1;
> /* XXX  2-bit hole   */
> /* XXX  1-byte hole  */
> /*   26      |     2 */        short section;
>                            } /* total size:   32 bytes */ mginfo;
> /*   32      |     8 */    unsigned long size;
> /*   40      |     8 */    const char *filename;
> /*   48:28   |     4 */    minimal_symbol_type type : 4;
> /*   48:27   |     4 */    unsigned int created_by_gdb : 1;
> /*   48:26   |     4 */    unsigned int target_flag_1 : 1;
> /*   48:25   |     4 */    unsigned int target_flag_2 : 1;
> /*   48:24   |     4 */    unsigned int has_size : 1;
> /* XXX  7-byte hole  */
> /*   56      |     8 */    minimal_symbol *hash_next;
> /*   64      |     8 */    minimal_symbol *demangled_hash_next;
>                          } /* total size:   72 bytes */
> (top-gdb) 

Yeah, this makes sense.  I'll see about doing this.

> #3 - I also find the position of those
> "/* total size:    8 bytes */"  markers a bit hard
> to grok/spot.
>
> I'd suggest considering putting them on the left column
> along with the rest of the offset/size info as well, like:
>
> (top-gdb) ptype/tom minimal_symbol
> /* offset    |  size */
> type = struct minimal_symbol {
> /*    0      |    32 */    struct general_symbol_info {
> /*    0      |     8 */        const char *name;
> /*    8      |     8 */        union {
> /*                 8 */            LONGEST ivalue;
> /*                 8 */            const block *block;
> /*                 8 */            const gdb_byte *bytes;
> /*                 8 */            CORE_ADDR address;
> /*                 8 */            const common_block *common_block;
> /*                 8 */            symbol *chain;
> /* total size:     8 */        } value;
> /*   16      |     8 */        union {
> /*                 8 */            obstack *obstack;
> /*                 8 */            const char *demangled_name;
> /* total size:     8 */        } language_specific;
> /*   24:27   |     4 */        language language : 5;
> /*   24:26   |     4 */        unsigned int ada_mangled : 1;
> /* XXX  2-bit hole   */
> /* XXX  1-byte hole  */
> /*   26      |     2 */        short section;
> /* total size:    32 */    } mginfo;
> /*   32      |     8 */    unsigned long size;
> /*   40      |     8 */    const char *filename;
> /*   48:28   |     4 */    minimal_symbol_type type : 4;
> /*   48:27   |     4 */    unsigned int created_by_gdb : 1;
> /*   48:26   |     4 */    unsigned int target_flag_1 : 1;
> /*   48:25   |     4 */    unsigned int target_flag_2 : 1;
> /*   48:24   |     4 */    unsigned int has_size : 1;
> /* XXX  7-byte hole  */
> /*   56      |     8 */    minimal_symbol *hash_next;
> /*   64      |     8 */    minimal_symbol *demangled_hash_next;
> } /* total size:   72 bytes */
> (top-gdb) 
>
> Or if you don't like it, consider putting that 
> "total size:" info on a separate line before the struct/union
> is closed, like pahole does:
>
>         /* size: 128, cachelines: 2, members: 4 */
>         /* sum members: 124, holes: 1, sum holes: 4 */
> };
>
> That may be even better because it provides a place
> to put extra info, like pahole does (cacheline info etc.).

I like the idea of putting them on a separate line; not sure how hard
that will be.  I'll take a look at implemeting this.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13 17:13       ` Sergio Durigan Junior
@ 2017-12-13 20:36         ` Sergio Durigan Junior
  2017-12-13 21:22           ` Pedro Alves
  0 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13 20:36 UTC (permalink / raw)
  To: Pedro Alves
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On Wednesday, December 13 2017, I wrote:

> On Wednesday, December 13 2017, Pedro Alves wrote:
>
>> I'm mildly worried about whether the bitfield handling is working
>> correctly on big endian machines.  We may want to lift this
>> x86-64-only restriction, by using e.g., alignas(N) or
>> __attribute__((aligned(N)) to take care of most of the differences
>> between architectures and end up with few per-arch code in
>> the .exp.  But I'm fine with starting with only x86-64 if you
>> confirm manually on e.g., a big endian PPC64 machine on the
>> compile farm, and we can extend the testcase in that direction
>> after this is merged.
>
> OK, I'll confirm on PPC64BE.

It seems like the algorithm for calculating bitfield offsets is not
working correctly on BE machines.  This is not only for "ptype /o", but
also for regular print commands.  For example, consider this test:

  struct tyu
  {
    int a1 : 1;

    int a2 : 3;

    int a3 : 23;

    char a4 : 2;

    int64_t a5;

    int a6 : 5;

    int64_t a7 : 3;
  };

  int
  main (int argc, char *argv[])
  {
    struct tyu e;

    e.a1 = e.a2 = e.a3 = e.a4 = e.a6 = e.a7 = -1;

    return 0;
  }

After stopping GDB at the "return 0;" line, here's what we see when we
print "e" on x86_64:

  (gdb) p e
  $1 = {a1 = -1, a2 = -1, a3 = -1, a4 = -1 '\377', a5 = 140737488344880, a6 = -1, a7 = -1}

While on PPC64BE:

  (gdb) p e
  $1 = {a1 = -1, a2 = 3, a3 = 3, a4 = 3 '\003', a5 = 70367536153528, a6 = -1, a7 = -1}

As for "ptype /o", the offsets printed on x86_64 and PPC64BE are the
same:

x86_64:

  /* offset    |  size */  type = struct tyu {
  /*    0:31   |     4 */    int a1 : 1;
  /*    0:28   |     4 */    int a2 : 3;
  /*    0: 5   |     4 */    int a3 : 23;
  /*    3: 3   |     1 */    char a4 : 2;
  /* XXX  3-bit hole   */
  /* XXX  4-byte hole  */
  /*    8      |     8 */    int64_t a5;
  /*   16:27   |     4 */    int a6 : 5;
  /*   16:56   |     8 */    int64_t a7 : 3;

                             /* total size (bytes):   24 */
                           }

PPC64BE:

  /* offset    |  size */  type = struct tyu {
  /*    0:31   |     4 */    int a1 : 1;
  /*    0:28   |     4 */    int a2 : 3;
  /*    0: 5   |     4 */    int a3 : 23;
  /*    3: 3   |     1 */    char a4 : 2;
  /* XXX  3-bit hole   */
  /* XXX  4-byte hole  */
  /*    8      |     8 */    int64_t a5;
  /*   16:27   |     4 */    int a6 : 5;
  /*   16:56   |     8 */    int64_t a7 : 3;

                             /* total size (bytes):   24 */
                           }

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13 20:36         ` Sergio Durigan Junior
@ 2017-12-13 21:22           ` Pedro Alves
  2017-12-13 21:30             ` Pedro Alves
  2017-12-13 21:34             ` Sergio Durigan Junior
  0 siblings, 2 replies; 75+ messages in thread
From: Pedro Alves @ 2017-12-13 21:22 UTC (permalink / raw)
  To: Sergio Durigan Junior
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On 12/13/2017 08:36 PM, Sergio Durigan Junior wrote:
> On Wednesday, December 13 2017, I wrote:

>> OK, I'll confirm on PPC64BE.

Thanks.

> 
> It seems like the algorithm for calculating bitfield offsets is not
> working correctly on BE machines.  This is not only for "ptype /o", but
> also for regular print commands.  For example, consider this test:
> 
>   struct tyu
>   {
>     int a1 : 1;
> 
>     int a2 : 3;
> 
>     int a3 : 23;
> 
>     char a4 : 2;
> 
>     int64_t a5;
> 
>     int a6 : 5;
> 
>     int64_t a7 : 3;
>   };
> 
>   int
>   main (int argc, char *argv[])
>   {
>     struct tyu e;
> 
>     e.a1 = e.a2 = e.a3 = e.a4 = e.a6 = e.a7 = -1;
> 
>     return 0;
>   }
> 
> After stopping GDB at the "return 0;" line, here's what we see when we
> print "e" on x86_64:
> 
>   (gdb) p e
>   $1 = {a1 = -1, a2 = -1, a3 = -1, a4 = -1 '\377', a5 = 140737488344880, a6 = -1, a7 = -1}
> 
> While on PPC64BE:
> 
>   (gdb) p e
>   $1 = {a1 = -1, a2 = 3, a3 = 3, a4 = 3 '\003', a5 = 70367536153528, a6 = -1, a7 = -1}
> 

You didn't initialize e.a5, so even the x86_64 version looks
wrong at first.  You're seeing stack/register garbage in
the padding holes.

You should make that "e" a global to make sure all its
underlying bytes are clear, including padding.  Or memset it.
The former is easier.

a2, a3 and a4 in the PPC64 version do look odd.  Though
maybe that's something do to with the expression you used.

Does it make a difference if you initialize all fields
with separate statements, like:

 e.a1 = -1;
 e.a2 = -1;
 etc.

> As for "ptype /o", the offsets printed on x86_64 and PPC64BE are the
> same:

So it sounds like we could remove the x86-64 check in the 
testcase and let it run on all lp64 targets?  Does it pass
cleanly on PPC64?

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13 21:22           ` Pedro Alves
@ 2017-12-13 21:30             ` Pedro Alves
  2017-12-13 21:34             ` Sergio Durigan Junior
  1 sibling, 0 replies; 75+ messages in thread
From: Pedro Alves @ 2017-12-13 21:30 UTC (permalink / raw)
  To: Sergio Durigan Junior
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On 12/13/2017 09:22 PM, Pedro Alves wrote:
> On 12/13/2017 08:36 PM, Sergio Durigan Junior wrote:
>> On Wednesday, December 13 2017, I wrote:
> 
>>> OK, I'll confirm on PPC64BE.
> 
> Thanks.
> 
>>
>> It seems like the algorithm for calculating bitfield offsets is not
>> working correctly on BE machines.  This is not only for "ptype /o", but
>> also for regular print commands.  For example, consider this test:
>>
>>   struct tyu
>>   {
>>     int a1 : 1;
>>
>>     int a2 : 3;
>>
>>     int a3 : 23;
>>
>>     char a4 : 2;
>>
>>     int64_t a5;
>>
>>     int a6 : 5;
>>
>>     int64_t a7 : 3;
>>   };
>>
>>   int
>>   main (int argc, char *argv[])
>>   {
>>     struct tyu e;
>>
>>     e.a1 = e.a2 = e.a3 = e.a4 = e.a6 = e.a7 = -1;
>>
>>     return 0;
>>   }
>>
>> After stopping GDB at the "return 0;" line, here's what we see when we
>> print "e" on x86_64:
>>
>>   (gdb) p e
>>   $1 = {a1 = -1, a2 = -1, a3 = -1, a4 = -1 '\377', a5 = 140737488344880, a6 = -1, a7 = -1}
>>
>> While on PPC64BE:
>>
>>   (gdb) p e
>>   $1 = {a1 = -1, a2 = 3, a3 = 3, a4 = 3 '\003', a5 = 70367536153528, a6 = -1, a7 = -1}
>>
> 
> You didn't initialize e.a5, so even the x86_64 version looks
> wrong at first.  You're seeing stack/register garbage in
> the padding holes.
> 
> You should make that "e" a global to make sure all its
> underlying bytes are clear, including padding.  Or memset it.
> The former is easier.
> 
> a2, a3 and a4 in the PPC64 version do look odd.  Though
> maybe that's something do to with the expression you used.
> 
> Does it make a difference if you initialize all fields
> with separate statements, like:
> 
>  e.a1 = -1;
>  e.a2 = -1;
>  etc.

Actually, I notice now that a4 is plain "char", not
"signed char" and that looks like is the issue.

Plain "char" is unsigned on PPC64.  And then given an
expression like:

  e.a1 = e.a2 = e.a3 = e.a4 ...

e.a4 ends up with an unsigned value (3).  And so
a2 and a3 end up with the same value too (3), and then
a1 ends up "-1" anyway because it's a 1-bit field, i.e.,
no matter what you put there, it ends up being -1,
because all it fits in it is the sign bit.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v5 2/2] Implement pahole-like 'ptype /o' option
  2017-12-13 21:22           ` Pedro Alves
  2017-12-13 21:30             ` Pedro Alves
@ 2017-12-13 21:34             ` Sergio Durigan Junior
  1 sibling, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-13 21:34 UTC (permalink / raw)
  To: Pedro Alves
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On Wednesday, December 13 2017, Pedro Alves wrote:

> On 12/13/2017 08:36 PM, Sergio Durigan Junior wrote:
>> On Wednesday, December 13 2017, I wrote:
>
>>> OK, I'll confirm on PPC64BE.
>
> Thanks.
>
>> 
>> It seems like the algorithm for calculating bitfield offsets is not
>> working correctly on BE machines.  This is not only for "ptype /o", but
>> also for regular print commands.  For example, consider this test:
>> 
>>   struct tyu
>>   {
>>     int a1 : 1;
>> 
>>     int a2 : 3;
>> 
>>     int a3 : 23;
>> 
>>     char a4 : 2;
>> 
>>     int64_t a5;
>> 
>>     int a6 : 5;
>> 
>>     int64_t a7 : 3;
>>   };
>> 
>>   int
>>   main (int argc, char *argv[])
>>   {
>>     struct tyu e;
>> 
>>     e.a1 = e.a2 = e.a3 = e.a4 = e.a6 = e.a7 = -1;
>> 
>>     return 0;
>>   }
>> 
>> After stopping GDB at the "return 0;" line, here's what we see when we
>> print "e" on x86_64:
>> 
>>   (gdb) p e
>>   $1 = {a1 = -1, a2 = -1, a3 = -1, a4 = -1 '\377', a5 = 140737488344880, a6 = -1, a7 = -1}
>> 
>> While on PPC64BE:
>> 
>>   (gdb) p e
>>   $1 = {a1 = -1, a2 = 3, a3 = 3, a4 = 3 '\003', a5 = 70367536153528, a6 = -1, a7 = -1}
>> 
>
> You didn't initialize e.a5, so even the x86_64 version looks
> wrong at first.  You're seeing stack/register garbage in
> the padding holes.

I should have initialized e.a5 to 0 in order to make the problem easier
to spot.  I did that now:

$1 = {a1 = -1, a2 = 3, a3 = 3, a4 = 3 '\003', a5 = 0, a6 = -1, a7 = -1}

> You should make that "e" a global to make sure all its
> underlying bytes are clear, including padding.  Or memset it.
> The former is easier.
>
> a2, a3 and a4 in the PPC64 version do look odd.  Though
> maybe that's something do to with the expression you used.
>
> Does it make a difference if you initialize all fields
> with separate statements, like:
>
>  e.a1 = -1;
>  e.a2 = -1;
>  etc.

After memset'ing the variable to 0, and separating all assignments, I
get:

[sergiodj@gcc1-power7 build-gdb]$ g++ -g ptype-offsets.cc 
ptype-offsets.cc: In function ‘int main(int, char**)’:
ptype-offsets.cc:170:8: warning: large integer implicitly truncated to unsigned type [-Woverflow]
   e.a4 = -1;
        ^


$1 = {a1 = -1, a2 = -1, a3 = -1, a4 = 3 '\003', a5 = 0, a6 = -1, a7 = -1}

After changing the declaration of a4 to "signed char a4 : 2;":

$1 = {a1 = -1, a2 = -1, a3 = -1, a4 = -1 '\377', a5 = 0, a6 = -1, a7 = -1}

>> As for "ptype /o", the offsets printed on x86_64 and PPC64BE are the
>> same:
>
> So it sounds like we could remove the x86-64 check in the 
> testcase and let it run on all lp64 targets?  Does it pass
> cleanly on PPC64?

I'm checking this right now, because I have to readjust the test due to
the changes in the output format.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v6 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg)
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
                   ` (6 preceding siblings ...)
  2017-12-13  3:17 ` [PATCH v5 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-14  2:48 ` Sergio Durigan Junior
  2017-12-14  2:48   ` [PATCH v6 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-14  2:48   ` [PATCH v6 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
  2017-12-15  1:12 ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  8 siblings, 2 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-14  2:48 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves, Keith Seitz

Changes from v5:

- Dropped support for vtable; too complicated for now, will tackle
  this later.

- Fixed documentation issues.

- Improved output format based on Pedro's suggestions.

- "ptype /o" now defaults to "/tom"; added note on docs about this.

- Added a few more tests.  Added a description to the testcase.  Use
  {} instead of "" for strings.  Removed "optimize=-O0".

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v6 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base'
  2017-12-14  2:48 ` [PATCH v6 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2017-12-14  2:48   ` [PATCH v6 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-12-14  2:48   ` Sergio Durigan Junior
  1 sibling, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-14  2:48 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	Keith Seitz, Sergio Durigan Junior

While doing the 'ptype /o' work, I noticed that 'c_type_print_base'
was very long, with a big amount of code just to handle the case of
TYPE_CODE_{STRUCT,UNION}.  This made working with the function a bit
difficult, specially because of the level of indentation.

This commit moves this part of the code to their own functions.  Now
we have a 'c_type_print_base_struct_union' with most of the code, and
also 'need_access_label_p', which is a subset of the code that was
also a good candidate for having its own function.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* c-typeprint.c (need_access_label_p): New function.
	(c_type_print_base_struct_union): New function.
	(c_type_print_base): Move code to handle
	TYPE_CODE_{STRUCT,UNION} to the functions mentioned above.
---
 gdb/c-typeprint.c | 837 ++++++++++++++++++++++++++----------------------------
 1 file changed, 403 insertions(+), 434 deletions(-)

diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index f3c3e7d706..3854886387 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -875,6 +875,407 @@ output_access_specifier (struct ui_file *stream,
   return last_access;
 }
 
+/* Return true is an access label (i.e., "public:", "private:",
+   "protected:") needs to be printed for TYPE.  */
+
+static bool
+need_access_label_p (struct type *type)
+{
+  if (TYPE_DECLARED_CLASS (type))
+    {
+      QUIT;
+      for (int i = TYPE_N_BASECLASSES (type); i < TYPE_NFIELDS (type); i++)
+	if (!TYPE_FIELD_PRIVATE (type, i))
+	  return true;
+      QUIT;
+      for (int j = 0; j < TYPE_NFN_FIELDS (type); j++)
+	for (int i = 0; i < TYPE_FN_FIELDLIST_LENGTH (type, j); i++)
+	  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+							  j), i))
+	    return true;
+      QUIT;
+      for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+	  return true;
+    }
+  else
+    {
+      QUIT;
+      for (int i = TYPE_N_BASECLASSES (type); i < TYPE_NFIELDS (type); i++)
+	if (TYPE_FIELD_PRIVATE (type, i) || TYPE_FIELD_PROTECTED (type, i))
+	  return true;
+      QUIT;
+      for (int j = 0; j < TYPE_NFN_FIELDS (type); j++)
+	{
+	  QUIT;
+	  for (int i = 0; i < TYPE_FN_FIELDLIST_LENGTH (type, j); i++)
+	    if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
+							     j), i)
+		|| TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+							      j),
+					  i))
+	      return true;
+	}
+      QUIT;
+      for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
+	    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+	  return true;
+    }
+
+  return false;
+}
+
+/* Helper for 'c_type_print_base' that handles structs and unions.
+   For a description of the arguments, see 'c_type_print_base'.  */
+
+static void
+c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
+				int show, int level,
+				const struct type_print_options *flags)
+{
+  struct type_print_options local_flags = *flags;
+  struct type_print_options semi_local_flags = *flags;
+  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
+
+  local_flags.local_typedefs = NULL;
+  semi_local_flags.local_typedefs = NULL;
+
+  if (!flags->raw)
+    {
+      if (flags->local_typedefs)
+	local_flags.local_typedefs
+	  = copy_typedef_hash (flags->local_typedefs);
+      else
+	local_flags.local_typedefs = create_typedef_hash ();
+
+      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
+    }
+
+  c_type_print_modifier (type, stream, 0, 1);
+  if (TYPE_CODE (type) == TYPE_CODE_UNION)
+    fprintf_filtered (stream, "union ");
+  else if (TYPE_DECLARED_CLASS (type))
+    fprintf_filtered (stream, "class ");
+  else
+    fprintf_filtered (stream, "struct ");
+
+  /* Print the tag if it exists.  The HP aCC compiler emits a
+     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
+     enum}" tag for unnamed struct/union/enum's, which we don't
+     want to print.  */
+  if (TYPE_TAG_NAME (type) != NULL
+      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
+    {
+      /* When printing the tag name, we are still effectively
+	 printing in the outer context, hence the use of FLAGS
+	 here.  */
+      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
+      if (show > 0)
+	fputs_filtered (" ", stream);
+    }
+
+  if (show < 0)
+    {
+      /* If we just printed a tag name, no need to print anything
+	 else.  */
+      if (TYPE_TAG_NAME (type) == NULL)
+	fprintf_filtered (stream, "{...}");
+    }
+  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
+    {
+      struct type *basetype;
+      int vptr_fieldno;
+
+      c_type_print_template_args (&local_flags, type, stream);
+
+      /* Add in template parameters when printing derivation info.  */
+      add_template_parameters (local_flags.local_typedefs, type);
+      cp_type_print_derivation_info (stream, type, &local_flags);
+
+      /* This holds just the global typedefs and the template
+	 parameters.  */
+      semi_local_flags.local_typedefs
+	= copy_typedef_hash (local_flags.local_typedefs);
+      if (semi_local_flags.local_typedefs)
+	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
+
+      /* Now add in the local typedefs.  */
+      recursively_update_typedef_hash (local_flags.local_typedefs, type);
+
+      fprintf_filtered (stream, "{\n");
+      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
+	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
+	{
+	  if (TYPE_STUB (type))
+	    fprintfi_filtered (level + 4, stream,
+			       _("<incomplete type>\n"));
+	  else
+	    fprintfi_filtered (level + 4, stream,
+			       _("<no data fields>\n"));
+	}
+
+      /* Start off with no specific section type, so we can print
+	 one for the first field we find, and use that section type
+	 thereafter until we find another type.  */
+
+      enum access_specifier section_type = s_none;
+
+      /* For a class, if all members are private, there's no need
+	 for a "private:" label; similarly, for a struct or union
+	 masquerading as a class, if all members are public, there's
+	 no need for a "public:" label.  */
+      bool need_access_label = need_access_label_p (type);
+
+      /* If there is a base class for this type,
+	 do not print the field that it occupies.  */
+
+      int len = TYPE_NFIELDS (type);
+      vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
+	{
+	  QUIT;
+
+	  /* If we have a virtual table pointer, omit it.  Even if
+	     virtual table pointers are not specifically marked in
+	     the debug info, they should be artificial.  */
+	  if ((i == vptr_fieldno && type == basetype)
+	      || TYPE_FIELD_ARTIFICIAL (type, i))
+	    continue;
+
+	  if (need_access_label)
+	    {
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FIELD_PROTECTED (type, i),
+		 TYPE_FIELD_PRIVATE (type, i));
+	    }
+
+	  print_spaces_filtered (level + 4, stream);
+	  if (field_is_static (&TYPE_FIELD (type, i)))
+	    fprintf_filtered (stream, "static ");
+	  c_print_type (TYPE_FIELD_TYPE (type, i),
+			TYPE_FIELD_NAME (type, i),
+			stream, show - 1, level + 4,
+			&local_flags);
+	  if (!field_is_static (&TYPE_FIELD (type, i))
+	      && TYPE_FIELD_PACKED (type, i))
+	    {
+	      /* It is a bitfield.  This code does not attempt
+		 to look at the bitpos and reconstruct filler,
+		 unnamed fields.  This would lead to misleading
+		 results if the compiler does not put out fields
+		 for such things (I don't know what it does).  */
+	      fprintf_filtered (stream, " : %d",
+				TYPE_FIELD_BITSIZE (type, i));
+	    }
+	  fprintf_filtered (stream, ";\n");
+	}
+
+      /* If there are both fields and methods, put a blank line
+	 between them.  Make sure to count only method that we
+	 will display; artificial methods will be hidden.  */
+      len = TYPE_NFN_FIELDS (type);
+      if (!flags->print_methods)
+	len = 0;
+      int real_len = 0;
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  int j;
+
+	  for (j = 0; j < len2; j++)
+	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
+	      real_len++;
+	}
+      if (real_len > 0 && section_type != s_none)
+	fprintf_filtered (stream, "\n");
+
+      /* C++: print out the methods.  */
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
+	  const char *name = type_name_no_tag (type);
+	  int is_constructor = name && strcmp (method_name,
+					       name) == 0;
+
+	  for (j = 0; j < len2; j++)
+	    {
+	      const char *mangled_name;
+	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
+	      char *demangled_name;
+	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
+	      int is_full_physname_constructor =
+		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
+		|| is_constructor_name (physname)
+		|| is_destructor_name (physname)
+		|| method_name[0] == '~';
+
+	      /* Do not print out artificial methods.  */
+	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
+		continue;
+
+	      QUIT;
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FN_FIELD_PROTECTED (f, j),
+		 TYPE_FN_FIELD_PRIVATE (f, j));
+
+	      print_spaces_filtered (level + 4, stream);
+	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
+		fprintf_filtered (stream, "virtual ");
+	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
+		fprintf_filtered (stream, "static ");
+	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
+		{
+		  /* Keep GDB from crashing here.  */
+		  fprintf_filtered (stream,
+				    _("<undefined type> %s;\n"),
+				    TYPE_FN_FIELD_PHYSNAME (f, j));
+		  break;
+		}
+	      else if (!is_constructor	/* Constructors don't
+					   have declared
+					   types.  */
+		       && !is_full_physname_constructor  /* " " */
+		       && !is_type_conversion_operator (type, i, j))
+		{
+		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+				"", stream, -1, 0,
+				&local_flags);
+		  fputs_filtered (" ", stream);
+		}
+	      if (TYPE_FN_FIELD_STUB (f, j))
+		{
+		  /* Build something we can demangle.  */
+		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
+		  mangled_name = mangled_name_holder.get ();
+		}
+	      else
+		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
+
+	      demangled_name =
+		gdb_demangle (mangled_name,
+			      DMGL_ANSI | DMGL_PARAMS);
+	      if (demangled_name == NULL)
+		{
+		  /* In some cases (for instance with the HP
+		     demangling), if a function has more than 10
+		     arguments, the demangling will fail.
+		     Let's try to reconstruct the function
+		     signature from the symbol information.  */
+		  if (!TYPE_FN_FIELD_STUB (f, j))
+		    {
+		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
+		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
+
+		      cp_type_print_method_args (mtype,
+						 "",
+						 method_name,
+						 staticp,
+						 stream, &local_flags);
+		    }
+		  else
+		    fprintf_filtered (stream,
+				      _("<badly mangled name '%s'>"),
+				      mangled_name);
+		}
+	      else
+		{
+		  char *p;
+		  char *demangled_no_class
+		    = remove_qualifiers (demangled_name);
+
+		  /* Get rid of the `static' appended by the
+		     demangler.  */
+		  p = strstr (demangled_no_class, " static");
+		  if (p != NULL)
+		    {
+		      int length = p - demangled_no_class;
+		      char *demangled_no_static;
+
+		      demangled_no_static
+			= (char *) xmalloc (length + 1);
+		      strncpy (demangled_no_static,
+			       demangled_no_class, length);
+		      *(demangled_no_static + length) = '\0';
+		      fputs_filtered (demangled_no_static, stream);
+		      xfree (demangled_no_static);
+		    }
+		  else
+		    fputs_filtered (demangled_no_class, stream);
+		  xfree (demangled_name);
+		}
+
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print out nested types.  */
+      if (TYPE_NESTED_TYPES_COUNT (type) != 0
+	  && semi_local_flags.print_nested_type_limit != 0)
+	{
+	  if (semi_local_flags.print_nested_type_limit > 0)
+	    --semi_local_flags.print_nested_type_limit;
+
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
+	    {
+	      print_spaces_filtered (level + 4, stream);
+	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+			    "", stream, show, level + 4, &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print typedefs defined in this class.  */
+
+      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
+	{
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
+	      || TYPE_NESTED_TYPES_COUNT (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
+	    {
+	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
+
+	      /* Dereference the typedef declaration itself.  */
+	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
+	      target = TYPE_TARGET_TYPE (target);
+
+	      if (need_access_label)
+		{
+		  section_type = output_access_specifier
+		    (stream, section_type, level,
+		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		}
+	      print_spaces_filtered (level + 4, stream);
+	      fprintf_filtered (stream, "typedef ");
+
+	      /* We want to print typedefs with substitutions
+		 from the template parameters or globally-known
+		 typedefs but not local typedefs.  */
+	      c_print_type (target,
+			    TYPE_TYPEDEF_FIELD_NAME (type, i),
+			    stream, show - 1, level + 4,
+			    &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      fprintfi_filtered (level, stream, "}");
+    }
+
+  do_cleanups (local_cleanups);
+}
+
 /* Print the name of the type (or the ultimate pointer target,
    function value or array element), or the description of a structure
    or union.
@@ -898,10 +1299,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		   int show, int level, const struct type_print_options *flags)
 {
   int i;
-  int len, real_len;
-  enum access_specifier section_type;
-  int need_access_label = 0;
-  int j, len2;
+  int len;
 
   QUIT;
 
@@ -958,436 +1356,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      {
-	struct type_print_options local_flags = *flags;
-	struct type_print_options semi_local_flags = *flags;
-	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
-
-	local_flags.local_typedefs = NULL;
-	semi_local_flags.local_typedefs = NULL;
-
-	if (!flags->raw)
-	  {
-	    if (flags->local_typedefs)
-	      local_flags.local_typedefs
-		= copy_typedef_hash (flags->local_typedefs);
-	    else
-	      local_flags.local_typedefs = create_typedef_hash ();
-
-	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
-	  }
-
-	c_type_print_modifier (type, stream, 0, 1);
-	if (TYPE_CODE (type) == TYPE_CODE_UNION)
-	  fprintf_filtered (stream, "union ");
-	else if (TYPE_DECLARED_CLASS (type))
-	  fprintf_filtered (stream, "class ");
-	else
-	  fprintf_filtered (stream, "struct ");
-
-	/* Print the tag if it exists.  The HP aCC compiler emits a
-	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
-	   enum}" tag for unnamed struct/union/enum's, which we don't
-	   want to print.  */
-	if (TYPE_TAG_NAME (type) != NULL
-	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
-	  {
-	    /* When printing the tag name, we are still effectively
-	       printing in the outer context, hence the use of FLAGS
-	       here.  */
-	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
-	    if (show > 0)
-	      fputs_filtered (" ", stream);
-	  }
-
-	if (show < 0)
-	  {
-	    /* If we just printed a tag name, no need to print anything
-	       else.  */
-	    if (TYPE_TAG_NAME (type) == NULL)
-	      fprintf_filtered (stream, "{...}");
-	  }
-	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
-	  {
-	    struct type *basetype;
-	    int vptr_fieldno;
-
-	    c_type_print_template_args (&local_flags, type, stream);
-
-	    /* Add in template parameters when printing derivation info.  */
-	    add_template_parameters (local_flags.local_typedefs, type);
-	    cp_type_print_derivation_info (stream, type, &local_flags);
-
-	    /* This holds just the global typedefs and the template
-	       parameters.  */
-	    semi_local_flags.local_typedefs
-	      = copy_typedef_hash (local_flags.local_typedefs);
-	    if (semi_local_flags.local_typedefs)
-	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
-
-	    /* Now add in the local typedefs.  */
-	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
-
-	    fprintf_filtered (stream, "{\n");
-	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
-		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
-	      {
-		if (TYPE_STUB (type))
-		  fprintfi_filtered (level + 4, stream,
-				     _("<incomplete type>\n"));
-		else
-		  fprintfi_filtered (level + 4, stream,
-				     _("<no data fields>\n"));
-	      }
-
-	    /* Start off with no specific section type, so we can print
-	       one for the first field we find, and use that section type
-	       thereafter until we find another type.  */
-
-	    section_type = s_none;
-
-	    /* For a class, if all members are private, there's no need
-	       for a "private:" label; similarly, for a struct or union
-	       masquerading as a class, if all members are public, there's
-	       no need for a "public:" label.  */
-
-	    if (TYPE_DECLARED_CLASS (type))
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (!TYPE_FIELD_PRIVATE (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									  j), i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-	    else
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (TYPE_FIELD_PRIVATE (type, i)
-		      || TYPE_FIELD_PROTECTED (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			QUIT;
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
-									   j), i)
-			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									    j),
-							i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
-			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-
-	    /* If there is a base class for this type,
-	       do not print the field that it occupies.  */
-
-	    len = TYPE_NFIELDS (type);
-	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
-	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-	      {
-		QUIT;
-
-		/* If we have a virtual table pointer, omit it.  Even if
-		   virtual table pointers are not specifically marked in
-		   the debug info, they should be artificial.  */
-		if ((i == vptr_fieldno && type == basetype)
-		    || TYPE_FIELD_ARTIFICIAL (type, i))
-		  continue;
-
-		if (need_access_label)
-		  {
-		    section_type = output_access_specifier
-		      (stream, section_type, level,
-		       TYPE_FIELD_PROTECTED (type, i),
-		       TYPE_FIELD_PRIVATE (type, i));
-		  }
-
-		print_spaces_filtered (level + 4, stream);
-		if (field_is_static (&TYPE_FIELD (type, i)))
-		  fprintf_filtered (stream, "static ");
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show - 1, level + 4,
-			      &local_flags);
-		if (!field_is_static (&TYPE_FIELD (type, i))
-		    && TYPE_FIELD_PACKED (type, i))
-		  {
-		    /* It is a bitfield.  This code does not attempt
-		       to look at the bitpos and reconstruct filler,
-		       unnamed fields.  This would lead to misleading
-		       results if the compiler does not put out fields
-		       for such things (I don't know what it does).  */
-		    fprintf_filtered (stream, " : %d",
-				      TYPE_FIELD_BITSIZE (type, i));
-		  }
-		fprintf_filtered (stream, ";\n");
-	      }
-
-	  /* If there are both fields and methods, put a blank line
-	     between them.  Make sure to count only method that we
-	     will display; artificial methods will be hidden.  */
-	  len = TYPE_NFN_FIELDS (type);
-	  if (!flags->print_methods)
-	    len = 0;
-	  real_len = 0;
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      int j;
-
-	      for (j = 0; j < len2; j++)
-		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		  real_len++;
-	    }
-	  if (real_len > 0 && section_type != s_none)
-	    fprintf_filtered (stream, "\n");
-
-	  /* C++: print out the methods.  */
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
-	      const char *name = type_name_no_tag (type);
-	      int is_constructor = name && strcmp (method_name,
-						   name) == 0;
-
-	      for (j = 0; j < len2; j++)
-		{
-		  const char *mangled_name;
-		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
-		  char *demangled_name;
-		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
-		  int is_full_physname_constructor =
-		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
-		    || is_constructor_name (physname)
-		    || is_destructor_name (physname)
-		    || method_name[0] == '~';
-
-		  /* Do not print out artificial methods.  */
-		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		    continue;
-
-		  QUIT;
-		  section_type = output_access_specifier
-		    (stream, section_type, level,
-		     TYPE_FN_FIELD_PROTECTED (f, j),
-		     TYPE_FN_FIELD_PRIVATE (f, j));
-
-		  print_spaces_filtered (level + 4, stream);
-		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
-		    fprintf_filtered (stream, "virtual ");
-		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
-		    fprintf_filtered (stream, "static ");
-		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
-		    {
-		      /* Keep GDB from crashing here.  */
-		      fprintf_filtered (stream,
-					_("<undefined type> %s;\n"),
-					TYPE_FN_FIELD_PHYSNAME (f, j));
-		      break;
-		    }
-		  else if (!is_constructor	/* Constructors don't
-						   have declared
-						   types.  */
-			   && !is_full_physname_constructor  /* " " */
-			   && !is_type_conversion_operator (type, i, j))
-		    {
-		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				    "", stream, -1, 0,
-				    &local_flags);
-		      fputs_filtered (" ", stream);
-		    }
-		  if (TYPE_FN_FIELD_STUB (f, j))
-		    {
-		      /* Build something we can demangle.  */
-		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
-		      mangled_name = mangled_name_holder.get ();
-		    }
-		  else
-		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
-
-		  demangled_name =
-		    gdb_demangle (mangled_name,
-				  DMGL_ANSI | DMGL_PARAMS);
-		  if (demangled_name == NULL)
-		    {
-		      /* In some cases (for instance with the HP
-			 demangling), if a function has more than 10
-			 arguments, the demangling will fail.
-			 Let's try to reconstruct the function
-			 signature from the symbol information.  */
-		      if (!TYPE_FN_FIELD_STUB (f, j))
-			{
-			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
-			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
-
-			  cp_type_print_method_args (mtype,
-						     "",
-						     method_name,
-						     staticp,
-						     stream, &local_flags);
-			}
-		      else
-			fprintf_filtered (stream,
-					  _("<badly mangled name '%s'>"),
-					  mangled_name);
-		    }
-		  else
-		    {
-		      char *p;
-		      char *demangled_no_class
-			= remove_qualifiers (demangled_name);
-
-		      /* Get rid of the `static' appended by the
-			 demangler.  */
-		      p = strstr (demangled_no_class, " static");
-		      if (p != NULL)
-			{
-			  int length = p - demangled_no_class;
-			  char *demangled_no_static;
-
-			  demangled_no_static
-			    = (char *) xmalloc (length + 1);
-			  strncpy (demangled_no_static,
-				   demangled_no_class, length);
-			  *(demangled_no_static + length) = '\0';
-			  fputs_filtered (demangled_no_static, stream);
-			  xfree (demangled_no_static);
-			}
-		      else
-			fputs_filtered (demangled_no_class, stream);
-		      xfree (demangled_name);
-		    }
-
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print out nested types.  */
-	  if (TYPE_NESTED_TYPES_COUNT (type) != 0
-	      && semi_local_flags.print_nested_type_limit != 0)
-	    {
-	      if (semi_local_flags.print_nested_type_limit > 0)
-		--semi_local_flags.print_nested_type_limit;
-
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
-		{
-		  print_spaces_filtered (level + 4, stream);
-		  c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
-				"", stream, show, level + 4, &semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print typedefs defined in this class.  */
-
-	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
-	    {
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
-		  || TYPE_NESTED_TYPES_COUNT (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
-		{
-		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
-
-		  /* Dereference the typedef declaration itself.  */
-		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
-		  target = TYPE_TARGET_TYPE (target);
-
-		  if (need_access_label)
-		    {
-		      section_type = output_access_specifier
-			(stream, section_type, level,
-			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
-		    }
-		  print_spaces_filtered (level + 4, stream);
-		  fprintf_filtered (stream, "typedef ");
-
-		  /* We want to print typedefs with substitutions
-		     from the template parameters or globally-known
-		     typedefs but not local typedefs.  */
-		  c_print_type (target,
-				TYPE_TYPEDEF_FIELD_NAME (type, i),
-				stream, show - 1, level + 4,
-				&semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	    fprintfi_filtered (level, stream, "}");
-	  }
-
-	do_cleanups (local_cleanups);
-      }
+      c_type_print_base_struct_union (type, stream, show, level, flags);
       break;
 
     case TYPE_CODE_ENUM:
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v6 2/2] Implement pahole-like 'ptype /o' option
  2017-12-14  2:48 ` [PATCH v6 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-14  2:48   ` Sergio Durigan Junior
  2017-12-14 14:19     ` Pedro Alves
                       ` (2 more replies)
  2017-12-14  2:48   ` [PATCH v6 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
  1 sibling, 3 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-14  2:48 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	Keith Seitz, Sergio Durigan Junior

This commit implements the pahole-like '/o' option for 'ptype', which
prints the offsets and sizes of struct fields, reporting whenever
there is a hole found.

The output is heavily based on pahole(1), with a few modifications
here and there to adjust it to our reality.  Here's an example:

  /* offset    |  size */  type = struct wer : public tuv {
			   public:
  /*   32      |    24 */    struct tyu {
  /*   32:31   |     4 */        int a1 : 1;
  /*   32:28   |     4 */        int a2 : 3;
  /*   32: 5   |     4 */        int a3 : 23;
  /*   35: 3   |     1 */        char a4 : 2;
  /* XXX  3-bit hole   */
  /* XXX  4-byte hole  */
  /*   40      |     8 */        int64_t a5;
  /*   48:27   |     4 */        int a6 : 5;
  /*   48:56   |     8 */        int64_t a7 : 3;

				 /* total size (bytes):   24 */
			     } a1;

			     /* total size (bytes):   56 */
			   }

A big part of this patch handles the formatting logic of 'ptype',
which is a bit messy.  The code to handle bitfield offsets, however,
took some time to craft.  My thanks to Pedro Alves for figuring things
out and pointing me to the right direction, as well as coming up with
a way to inspect the layout of structs with bitfields (see testcase
for comments).

After many discussions both on IRC and at the mailing list, I tried to
implement printing vtables and inherited classes.  Unfortunately the
code grew too complex and there were still a few corner cases failing
so I had to drop the attempt.  This should be implemented in a future
patch.

This patch is the start of a long-term work I'll do to flush the local
patches we carry for Fedora GDB.  In this specific case, I'm aiming at
upstreaming the feature implemented by the 'pahole.py' script that is
shipped with Fedora GDB:

  <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>

This has been regression-tested on the BuildBot.  There's a new
testcase for it, along with an update to the documentation.  I also
thought it was worth mentioning this feature in the NEWS file.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Pedro Alves  <palves@redhat.com>

	PR cli/16224
	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
	* c-typeprint.c (OFFSET_SPC_LEN): New define.
	(c_type_print_varspec_prefix): New argument 'struct
	print_offset_data *'.
	(c_type_print_base_1): New function and prototype.
	(c_print_type_1): New function, with code from 'c_print_type'.
	(c_print_type): Use 'c_print_type_1'.
	(c_type_print_varspec_prefix): New argument 'struct
	print_offset_data *'.  Use it.  Call 'c_type_print_base_1'
	instead of 'c_print_type_base'.
	(print_spaces_filtered_with_print_options): New function.
	(output_access_specifier): Take new argument FLAGS.  Modify
	function to call 'print_spaces_filtered_with_print_options'.
	(c_print_type_vtable_offset_marker): New function.
	(c_print_type_union_field_offset): New function.
	(c_print_type_struct_field_offset): New function.
	(c_type_print_base_struct_union): New argument 'struct
	print_offset_data *'.  Print offsets and sizes for
	struct/union/class fields.
	* typeprint.c (const struct type_print_options
	type_print_raw_options): Initialize 'print_offsets'.
	(static struct type_print_options default_ptype_flags):
	Likewise.
	(struct print_offset_data print_offset_default_data): New
	variable.
	(whatis_exp): Handle '/o' option.
	(_initialize_typeprint): Add '/o' flag to ptype's help.
	* typeprint.h (struct print_offset_data): New struct.
	(struct type_print_options) <print_offsets>: New field.

gdb/testsuite/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.base/ptype-offsets.cc: New file.
	* gdb.base/ptype-offsets.exp: New file.

gdb/doc/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.texinfo (ptype): Add documentation for new flag '/o'.
---
 gdb/NEWS                                 |   3 +
 gdb/c-typeprint.c                        | 380 +++++++++++++++++++++++++------
 gdb/doc/gdb.texinfo                      | 115 ++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.cc  | 158 +++++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.exp | 223 ++++++++++++++++++
 gdb/typeprint.c                          |  23 +-
 gdb/typeprint.h                          |  19 ++
 7 files changed, 855 insertions(+), 66 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index bd5ae36dd0..3a7e704cc6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * New "--readnever" command line option instructs GDB to not read each
   symbol file's symbolic debug information.  This makes startup faster
   but at the expense of not being able to perform symbolic debugging.
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index 3854886387..f85347f03e 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -45,12 +53,18 @@ enum access_specifier
 static void c_type_print_varspec_prefix (struct type *,
 					 struct ui_file *,
 					 int, int, int,
-					 const struct type_print_options *);
+					 const struct type_print_options *,
+					 struct print_offset_data *);
 
 /* Print "const", "volatile", or address space modifiers.  */
 static void c_type_print_modifier (struct type *,
 				   struct ui_file *,
 				   int, int);
+
+static void c_type_print_base_1 (struct type *type, struct ui_file *stream,
+				 int show, int level,
+				 const struct type_print_options *flags,
+				 struct print_offset_data *podata);
 \f
 
 /* A callback function for cp_canonicalize_string_full that uses
@@ -82,14 +96,15 @@ print_name_maybe_canonical (const char *name,
 
 \f
 
-/* LEVEL is the depth to indent lines by.  */
+/* Helper function for c_print_type.  */
 
-void
-c_print_type (struct type *type,
-	      const char *varstring,
-	      struct ui_file *stream,
-	      int show, int level,
-	      const struct type_print_options *flags)
+static void
+c_print_type_1 (struct type *type,
+		const char *varstring,
+		struct ui_file *stream,
+		int show, int level,
+		const struct type_print_options *flags,
+		struct print_offset_data *podata)
 {
   enum type_code code;
   int demangled_args;
@@ -108,7 +123,7 @@ c_print_type (struct type *type,
     }
   else
     {
-      c_type_print_base (type, stream, show, level, flags);
+      c_type_print_base_1 (type, stream, show, level, flags, podata);
       code = TYPE_CODE (type);
       if ((varstring != NULL && *varstring != '\0')
 	  /* Need a space if going to print stars or brackets;
@@ -124,7 +139,7 @@ c_print_type (struct type *type,
 	fputs_filtered (" ", stream);
       need_post_space = (varstring != NULL && strcmp (varstring, "") != 0);
       c_type_print_varspec_prefix (type, stream, show, 0, need_post_space,
-				   flags);
+				   flags, podata);
     }
 
   if (varstring != NULL)
@@ -143,6 +158,20 @@ c_print_type (struct type *type,
     }
 }
 
+/* LEVEL is the depth to indent lines by.  */
+
+void
+c_print_type (struct type *type,
+	      const char *varstring,
+	      struct ui_file *stream,
+	      int show, int level,
+	      const struct type_print_options *flags)
+{
+  struct print_offset_data podata = print_offset_default_data;
+
+  c_print_type_1 (type, varstring, stream, show, level, flags, &podata);
+}
+
 /* Print a typedef using C syntax.  TYPE is the underlying type.
    NEW_SYMBOL is the symbol naming the type.  STREAM is the stream on
    which to print.  */
@@ -310,7 +339,8 @@ c_type_print_varspec_prefix (struct type *type,
 			     struct ui_file *stream,
 			     int show, int passed_a_ptr,
 			     int need_post_space,
-			     const struct type_print_options *flags)
+			     const struct type_print_options *flags,
+			     struct print_offset_data *podata)
 {
   const char *name;
 
@@ -326,40 +356,40 @@ c_type_print_varspec_prefix (struct type *type,
     {
     case TYPE_CODE_PTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 1, 1, flags);
+				   stream, show, 1, 1, flags, podata);
       fprintf_filtered (stream, "*");
       c_type_print_modifier (type, stream, 1, need_post_space);
       break;
 
     case TYPE_CODE_MEMBERPTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       name = type_name_no_tag (TYPE_SELF_TYPE (type));
       if (name)
 	print_name_maybe_canonical (name, flags, stream);
       else
-	c_type_print_base (TYPE_SELF_TYPE (type),
-			   stream, -1, passed_a_ptr, flags);
+	c_type_print_base_1 (TYPE_SELF_TYPE (type),
+			     stream, -1, passed_a_ptr, flags, podata);
       fprintf_filtered (stream, "::*");
       break;
 
     case TYPE_CODE_METHODPTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       fprintf_filtered (stream, "(");
       name = type_name_no_tag (TYPE_SELF_TYPE (type));
       if (name)
 	print_name_maybe_canonical (name, flags, stream);
       else
-	c_type_print_base (TYPE_SELF_TYPE (type),
-			   stream, -1, passed_a_ptr, flags);
+	c_type_print_base_1 (TYPE_SELF_TYPE (type),
+			     stream, -1, passed_a_ptr, flags, podata);
       fprintf_filtered (stream, "::*");
       break;
 
     case TYPE_CODE_REF:
     case TYPE_CODE_RVALUE_REF:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 1, 0, flags);
+				   stream, show, 1, 0, flags, podata);
       fprintf_filtered (stream, TYPE_CODE(type) == TYPE_CODE_REF ? "&" : "&&");
       c_type_print_modifier (type, stream, 1, need_post_space);
       break;
@@ -367,21 +397,22 @@ c_type_print_varspec_prefix (struct type *type,
     case TYPE_CODE_METHOD:
     case TYPE_CODE_FUNC:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       if (passed_a_ptr)
 	fprintf_filtered (stream, "(");
       break;
 
     case TYPE_CODE_ARRAY:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       if (passed_a_ptr)
 	fprintf_filtered (stream, "(");
       break;
 
     case TYPE_CODE_TYPEDEF:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, passed_a_ptr, 0, flags);
+				   stream, show, passed_a_ptr, 0, flags,
+				   podata);
       break;
 
     case TYPE_CODE_UNDEF:
@@ -836,21 +867,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options
+  (int level, struct ui_file *stream, const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +904,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,15 +913,121 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
-/* Return true is an access label (i.e., "public:", "private:",
+/* Print information about field at index FIELD_IDX of the union type
+   TYPE.  Since union fields don't have the concept of offsets, we
+   just print their sizes.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
+				 struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+
+  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+}
+
+/* Print information about field at index FIELD_IDX of the struct type
+   TYPE.
+
+   ENDPOS is the one-past-the-end bit position of the previous field
+   (where we expect this field to be if there is no hole).  At the
+   end, ENDPOS is updated to the one-past-the-end bit position of the
+   current field.
+
+   OFFSET_BITPOS is the offset value we carry over when we are
+   printing a struct that is inside another struct; this is useful so
+   that the offset is constantly incremented (if we didn't carry it
+   over, the offset would be reset to zero when printing the inner
+   struct).
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
+				  struct ui_file *stream,
+				  unsigned int *endpos,
+				  struct print_offset_data *podata)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+
+  /* We check for *ENDPOS > 0 because there is a specific scenario
+     when *ENDPOS can be zero and BITPOS can be > 0: when we are
+     dealing with a struct/class with a virtual method.  Because of
+     the vtable, the first field of the struct/class will have an
+     offset of sizeof (void *) (the size of the vtable).  If we do not
+     check for *ENDPOS > 0 here, GDB will report a hole before the
+     first field, which is not accurate.  */
+  if (*endpos > 0 && *endpos < bitpos)
+    {
+      /* If ENDPOS is smaller than the current type's bitpos, it means
+	 there's a hole in the struct, so we report it here.  */
+      unsigned int hole = bitpos - *endpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      unsigned int bitsize = TYPE_FIELD_BITSIZE (type, field_idx);
+      /* The bitpos relative to the beginning of our container
+	 field.  */
+      unsigned int relative_bitpos;
+
+      /* The following was copied from
+	 value.c:value_primitive_field.  */
+      if ((bitpos % fieldsize_bit) + bitsize <= fieldsize_bit)
+	relative_bitpos = bitpos % fieldsize_bit;
+      else
+	relative_bitpos = bitpos % TARGET_CHAR_BIT;
+
+      /* This is the exact offset (in bits) of this bitfield.  */
+      unsigned int bit_offset
+	= (bitpos - relative_bitpos) + podata->offset_bitpos;
+
+      /* The position of the field, relative to the beginning of the
+	 struct, and how many bits are left to be used in this
+	 container.  */
+      fprintf_filtered (stream, "/* %4u:%2u", bit_offset / TARGET_CHAR_BIT,
+			fieldsize_bit - (relative_bitpos + bitsize));
+      fieldsize_bit = bitsize;
+    }
+  else
+    {
+      /* The position of the field, relative to the beginning of the
+	 struct.  */
+      fprintf_filtered (stream, "/* %4u",
+			(bitpos + podata->offset_bitpos) / TARGET_CHAR_BIT);
+
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  *endpos = bitpos + fieldsize_bit;
+}
+
+/* Return true if an access label (i.e., "public:", "private:",
    "protected:") needs to be printed for TYPE.  */
 
 static bool
@@ -932,7 +1084,8 @@ need_access_label_p (struct type *type)
 static void
 c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 				int show, int level,
-				const struct type_print_options *flags)
+				const struct type_print_options *flags,
+				struct print_offset_data *podata)
 {
   struct type_print_options local_flags = *flags;
   struct type_print_options semi_local_flags = *flags;
@@ -1004,6 +1157,7 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
       recursively_update_typedef_hash (local_flags.local_typedefs, type);
 
       fprintf_filtered (stream, "{\n");
+
       if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
 	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
 	{
@@ -1032,6 +1186,8 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
       int len = TYPE_NFIELDS (type);
       vptr_fieldno = get_vptr_fieldno (type, &basetype);
+
+      unsigned int endpos = podata->endpos;
       for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
 	{
 	  QUIT;
@@ -1048,18 +1204,57 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FIELD_PROTECTED (type, i),
-		 TYPE_FIELD_PRIVATE (type, i));
+		 TYPE_FIELD_PRIVATE (type, i), flags);
+	    }
+
+	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+	  if (flags->print_offsets)
+	    {
+	      if (!is_static)
+		{
+		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		    {
+		      c_print_type_struct_field_offset
+			(type, i, stream, &endpos, podata);
+		    }
+		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
+		    c_print_type_union_field_offset (type, i, stream);
+
+		  podata->endpos = endpos;
+		}
+	      else
+		print_spaces_filtered (OFFSET_SPC_LEN, stream);
 	    }
 
 	  print_spaces_filtered (level + 4, stream);
-	  if (field_is_static (&TYPE_FIELD (type, i)))
+	  if (is_static)
 	    fprintf_filtered (stream, "static ");
-	  c_print_type (TYPE_FIELD_TYPE (type, i),
-			TYPE_FIELD_NAME (type, i),
-			stream, show - 1, level + 4,
-			&local_flags);
-	  if (!field_is_static (&TYPE_FIELD (type, i))
-	      && TYPE_FIELD_PACKED (type, i))
+
+	  int newshow = show - 1;
+
+	  if (flags->print_offsets
+	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
+		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
+	    {
+	      /* If we're printing offsets and this field's type is
+		 either a struct or an union, then we're interested in
+		 expanding it.  */
+	      ++newshow;
+
+	      /* Make sure we carry our offset when we expand the
+		 struct.  */
+	      podata->offset_bitpos += TYPE_FIELD_BITPOS (type, i);
+	    }
+
+	  unsigned int saved_endpos = podata->endpos;
+
+	  c_print_type_1 (TYPE_FIELD_TYPE (type, i),
+			  TYPE_FIELD_NAME (type, i),
+			  stream, newshow, level + 4,
+			  &local_flags, podata);
+
+	  if (!is_static && TYPE_FIELD_PACKED (type, i))
 	    {
 	      /* It is a bitfield.  This code does not attempt
 		 to look at the bitpos and reconstruct filler,
@@ -1122,9 +1317,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FN_FIELD_PROTECTED (f, j),
-		 TYPE_FN_FIELD_PRIVATE (f, j));
+		 TYPE_FN_FIELD_PRIVATE (f, j), flags);
 
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
 		fprintf_filtered (stream, "virtual ");
 	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
@@ -1143,9 +1339,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		       && !is_full_physname_constructor  /* " " */
 		       && !is_type_conversion_operator (type, i, j))
 		{
-		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				"", stream, -1, 0,
-				&local_flags);
+		  unsigned int old_po = local_flags.print_offsets;
+
+		  /* Temporarily disable print_offsets, because it
+		     would mess with indentation.  */
+		  local_flags.print_offsets = 0;
+		  c_print_type_1 (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+				  "", stream, -1, 0,
+				  &local_flags, podata);
+		  local_flags.print_offsets = old_po;
+
 		  fputs_filtered (" ", stream);
 		}
 	      if (TYPE_FN_FIELD_STUB (f, j))
@@ -1226,9 +1429,17 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
 	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
 	    {
-	      print_spaces_filtered (level + 4, stream);
-	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
-			    "", stream, show, level + 4, &semi_local_flags);
+	      unsigned int old_po = semi_local_flags.print_offsets;
+
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      semi_local_flags.print_offsets = 0;
+	      c_print_type_1 (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+			      "", stream, show, level + 4, &semi_local_flags,
+			      podata);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
@@ -1254,22 +1465,46 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		  section_type = output_access_specifier
 		    (stream, section_type, level,
 		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i), flags);
 		}
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      fprintf_filtered (stream, "typedef ");
 
+	      unsigned int old_po = semi_local_flags.print_offsets;
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      semi_local_flags.print_offsets = 0;
+
 	      /* We want to print typedefs with substitutions
 		 from the template parameters or globally-known
 		 typedefs but not local typedefs.  */
-	      c_print_type (target,
-			    TYPE_TYPEDEF_FIELD_NAME (type, i),
-			    stream, show - 1, level + 4,
-			    &semi_local_flags);
+	      c_print_type_1 (target,
+			      TYPE_TYPEDEF_FIELD_NAME (type, i),
+			      stream, show - 1, level + 4,
+			      &semi_local_flags, podata);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
 
+      if (flags->print_offsets)
+	{
+	  if (show > 0)
+	    {
+	      fputs_filtered ("\n", stream);
+	      print_spaces_filtered_with_print_options (level + 4,
+							stream,
+							flags);
+	      fprintf_filtered (stream, "/* total size (bytes): %4u */\n",
+				TYPE_LENGTH (type));
+	    }
+
+	  print_spaces_filtered (OFFSET_SPC_LEN, stream);
+	  if (level == 0)
+	    print_spaces_filtered (2, stream);
+	}
+
       fprintfi_filtered (level, stream, "}");
     }
 
@@ -1294,9 +1529,11 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
    LEVEL is the number of spaces to indent by.
    We increase it for some recursive calls.  */
 
-void
-c_type_print_base (struct type *type, struct ui_file *stream,
-		   int show, int level, const struct type_print_options *flags)
+static void
+c_type_print_base_1 (struct type *type, struct ui_file *stream,
+		     int show, int level,
+		     const struct type_print_options *flags,
+		     struct print_offset_data *podata)
 {
   int i;
   int len;
@@ -1341,8 +1578,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
       if (TYPE_TARGET_TYPE (type) == NULL)
 	type_print_unknown_return_type (stream);
       else
-	c_type_print_base (TYPE_TARGET_TYPE (type),
-			   stream, show, level, flags);
+	c_type_print_base_1 (TYPE_TARGET_TYPE (type),
+			     stream, show, level, flags, podata);
       break;
     case TYPE_CODE_ARRAY:
     case TYPE_CODE_PTR:
@@ -1350,13 +1587,14 @@ c_type_print_base (struct type *type, struct ui_file *stream,
     case TYPE_CODE_REF:
     case TYPE_CODE_RVALUE_REF:
     case TYPE_CODE_METHODPTR:
-      c_type_print_base (TYPE_TARGET_TYPE (type),
-			 stream, show, level, flags);
+      c_type_print_base_1 (TYPE_TARGET_TYPE (type),
+			   stream, show, level, flags, podata);
       break;
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      c_type_print_base_struct_union (type, stream, show, level, flags);
+      c_type_print_base_struct_union (type, stream, show, level, flags,
+				      podata);
       break;
 
     case TYPE_CODE_ENUM:
@@ -1456,10 +1694,10 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		print_spaces_filtered (level + 4, stream);
 		/* We pass "show" here and not "show - 1" to get enum types
 		   printed.  There's no other way to see them.  */
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show, level + 4,
-			      &local_flags);
+		c_print_type_1 (TYPE_FIELD_TYPE (type, i),
+				TYPE_FIELD_NAME (type, i),
+				stream, show, level + 4,
+				&local_flags, podata);
 		fprintf_filtered (stream, " @%s",
 				  plongest (TYPE_FIELD_BITPOS (type, i)));
 		if (TYPE_FIELD_BITSIZE (type, i) > 1)
@@ -1518,3 +1756,15 @@ c_type_print_base (struct type *type, struct ui_file *stream,
       break;
     }
 }
+
+/* See c_type_print_base_1.  */
+
+void
+c_type_print_base (struct type *type, struct ui_file *stream,
+		   int show, int level,
+		   const struct type_print_options *flags)
+{
+  struct print_offset_data podata = print_offset_default_data;
+
+  c_type_print_base_1 (type, stream, show, level, flags, &podata);
+}
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index da3ed28dfe..ddcae06b60 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17216,6 +17216,121 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.  This option implies the @code{/tm} flags.
+
+For example, given the following declarations:
+
+@smallexample
+struct tuv
+@{
+  int a1;
+  char *a2;
+  int a3;
+@};
+
+struct xyz
+@{
+  int f1;
+  char f2;
+  void *f3;
+  struct tuv f4;
+@};
+
+union qwe
+@{
+  struct tuv fff1;
+  struct xyz fff2;
+@};
+
+struct tyu
+@{
+  int a1 : 1;
+  int a2 : 3;
+  int a3 : 23;
+  char a4 : 2;
+  int64_t a5;
+  int a6 : 5;
+  int64_t a7 : 3;
+@};
+@end smallexample
+
+Issuing a @kbd{ptype /o struct tuv} command would print:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tuv
+/* offset    |  size */
+struct tuv @{
+/*    0      |     4 */    int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */    char *a2;
+/*   16      |     4 */    int a3;
+@} /* total size:   24 bytes */
+@end smallexample
+
+Notice the format of the first column of comments.  There, you can
+find two parts separated by the @samp{|} character: the @emph{offset},
+which indicates where the field is located inside the struct, in
+bytes, and the @emph{size} of the field.  Another interesting line is
+the marker of a @emph{hole} in the struct, indicating that it may be
+possible to pack the struct and make it use less space by reorganizing
+its fields.
+
+It is also possible to print offsets inside an union:
+
+@smallexample
+(@value{GDBP}) ptype /o union qwe
+/* offset    |  size */
+union qwe @{
+/*                24 */    struct tuv @{
+/*    0      |     4 */        int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */        char *a2;
+/*   16      |     4 */        int a3;
+                           @} /* total size:   24 bytes */ fff1;
+/*                40 */    struct xyz @{
+/*    0      |     4 */        int f1;
+/*    4      |     1 */        char f2;
+/* XXX  3-byte hole  */
+/*    8      |     8 */        void *f3;
+/*   16      |    24 */        struct tuv @{
+/*   16      |     4 */            int a1;
+/* XXX  4-byte hole  */
+/*   24      |     8 */            char *a2;
+/*   32      |     4 */            int a3;
+                               @} /* total size:   24 bytes */ f4;
+                           @} /* total size:   40 bytes */ fff2;
+@} /* total size:   40 bytes */
+@end smallexample
+
+In this case, since @code{struct tuv} and @code{struct xyz} occupy the
+same space (because we are dealing with an union), the offset is not
+printed for them.  However, you can still examine the offset of each
+of these structures' fields.
+
+Another useful scenario is printing the offsets of a struct containing
+bitfields:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tyu
+/* offset    |  size */
+struct tyu @{
+/*    0:31   |     4 */    int a1 : 1;
+/*    0:28   |     4 */    int a2 : 3;
+/*    0: 5   |     4 */    int a3 : 23;
+/*    3: 3   |     1 */    char a4 : 2;
+/* XXX  3-bit hole   */
+/* XXX  4-byte hole  */
+/*    8      |     8 */    int64_t a5;
+/*   16:27   |     4 */    int a6 : 5;
+/*   16:56   |     8 */    int64_t a7 : 3;
+@} /* total size:   24 bytes */
+@end smallexample
+
+Note how the offset information is now extended to also include how
+many bits are left to be used in each bitfield.
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..6aa622419c
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,158 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 will be used to test 'ptype /o' on x86_64 only.  */
+
+#include <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  signed int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  signed char field4;
+
+  /* 7-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    signed int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+};
+
+/* This struct will be nested inside 'struct xyz'.  */
+
+struct tuv
+{
+  signed int a1;
+
+  signed char *a2;
+
+  signed int a3;
+};
+
+/* This struct will be nested inside 'struct pqr'.  */
+
+struct xyz
+{
+  signed int f1;
+
+  signed char f2;
+
+  void *f3;
+
+  struct tuv f4;
+};
+
+/* A struct with a nested struct.  */
+
+struct pqr
+{
+  signed int ff1;
+
+  struct xyz ff2;
+
+  signed char ff3;
+};
+
+/* A union with two nested structs.  */
+
+union qwe
+{
+  struct tuv fff1;
+
+  struct xyz fff2;
+};
+
+/* A struct with an union.  */
+
+struct poi
+{
+  signed int f1;
+
+  union qwe f2;
+
+  uint16_t f3;
+
+  struct pqr f4;
+};
+
+/* A struct with bitfields.  */
+
+struct tyu
+{
+  signed int a1 : 1;
+
+  signed int a2 : 3;
+
+  signed int a3 : 23;
+
+  signed char a4 : 2;
+
+  int64_t a5;
+
+  signed int a6 : 5;
+
+  int64_t a7 : 3;
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+  struct pqr bar;
+  union qwe c;
+  struct poi d;
+  struct tyu e;
+  uint8_t i;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..0b76671820
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,223 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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 testcase exercises the "ptype /o" feature, which can be used to
+# print the offsets and sizes of each field of a struct/union/class.
+
+standard_testfile .cc
+
+# Test only works on x86_64 LP64 targets.  That's how we guarantee
+# that the expected holes will be present in the struct.
+if { ![is_lp64_target] } {
+    untested "test work only on lp64 targets"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ }] } {
+    return -1
+}
+
+# Test general offset printing, ctor/dtor printing, union, formatting.
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct abc \{} \
+{                         public:} \
+{/\*    8      |     8 \*/    void \*field1;} \
+{/\*   16:31   |     4 \*/    unsigned int field2 : 1;} \
+{/\* XXX  7-bit hole   \*/} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   20      |     4 \*/    int field3;} \
+{/\*   24      |     1 \*/    signed char field4;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   32      |     8 \*/    uint64_t field5;} \
+{/\*   40      |     8 \*/    union \{} \
+{/\*                 8 \*/        void \*field6;} \
+{/\*                 4 \*/        int field7;} \
+{} \
+{                               /\* total size \(bytes\):    8 \*/} \
+{                           \} field8;} \
+{} \
+{                           /\* total size \(bytes\):   48 \*/} \
+{                         \}}] \
+    "ptype offset struct abc"
+
+# Test nested structs.
+gdb_test "ptype /o struct pqr" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct pqr \{} \
+{/\*    0      |     4 \*/    int ff1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |    40 \*/    struct xyz \{} \
+{/\*    8      |     4 \*/        int f1;} \
+{/\*   12      |     1 \*/        signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   16      |     8 \*/        void \*f3;} \
+{/\*   24      |    24 \*/        struct tuv \{} \
+{/\*   24      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   32      |     8 \*/            signed char \*a2;} \
+{/\*   40      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} f4;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} ff2;} \
+{/\* XXX 28-byte hole  \*/} \
+{/\*   72      |     1 \*/    signed char ff3;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}] \
+    "ptype offset struct pqr"
+
+# Test that the offset is properly reset when we are printing a union
+# and go inside two inner structs.
+# This also tests a struct inside a struct inside a union.
+gdb_test "ptype /o union qwe" \
+    [multi_line \
+{/\* offset    |  size \*/  type = union qwe \{} \
+{/\*                24 \*/    struct tuv \{} \
+{/\*    0      |     4 \*/        int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |     8 \*/        signed char \*a2;} \
+{/\*   16      |     4 \*/        int a3;} \
+{} \
+{                               /\* total size \(bytes\):   24 \*/} \
+{                           \} fff1;} \
+{/\*                40 \*/    struct xyz \{} \
+{/\*    0      |     4 \*/        int f1;} \
+{/\*    4      |     1 \*/        signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*    8      |     8 \*/        void \*f3;} \
+{/\*   16      |    24 \*/        struct tuv \{} \
+{/\*   16      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   24      |     8 \*/            signed char \*a2;} \
+{/\*   32      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} f4;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} fff2;} \
+{} \
+{                           /\* total size \(bytes\):   40 \*/} \
+{                         \}}] \
+    "ptype offset union qwe"
+
+# Test printing a struct that contains a union, and that also
+# contains a struct.
+gdb_test "ptype /o struct poi" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct poi \{} \
+{/\*    0      |     4 \*/    int f1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |    40 \*/    union qwe \{} \
+{/\*                24 \*/        struct tuv \{} \
+{/\*    8      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   16      |     8 \*/            signed char \*a2;} \
+{/\*   24      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} fff1;} \
+{/\*                40 \*/        struct xyz \{} \
+{/\*    8      |     4 \*/            int f1;} \
+{/\*   12      |     1 \*/            signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   16      |     8 \*/            void \*f3;} \
+{/\*   24      |    24 \*/            struct tuv \{} \
+{/\*   24      |     4 \*/                int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   32      |     8 \*/                signed char \*a2;} \
+{/\*   40      |     4 \*/                int a3;} \
+{} \
+{                                       /\* total size \(bytes\):   24 \*/} \
+{                                   \} f4;} \
+{} \
+{                                   /\* total size \(bytes\):   40 \*/} \
+{                               \} fff2;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} f2;} \
+{/\*   72      |     2 \*/    uint16_t f3;} \
+{/\* XXX  6-byte hole  \*/} \
+{/\*   80      |    56 \*/    struct pqr \{} \
+{/\*   80      |     4 \*/        int ff1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   88      |    40 \*/        struct xyz \{} \
+{/\*   88      |     4 \*/            int f1;} \
+{/\*   92      |     1 \*/            signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   96      |     8 \*/            void \*f3;} \
+{/\*  104      |    24 \*/            struct tuv \{} \
+{/\*  104      |     4 \*/                int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*  112      |     8 \*/                signed char \*a2;} \
+{/\*  120      |     4 \*/                int a3;} \
+{} \
+{                                       /\* total size \(bytes\):   24 \*/} \
+{                                   \} f4;} \
+{} \
+{                                   /\* total size \(bytes\):   40 \*/} \
+{                               \} ff2;} \
+{/\*  152      |     1 \*/        signed char ff3;} \
+{} \
+{                               /\* total size \(bytes\):   56 \*/} \
+{                           \} f4;} \
+{} \
+{                           /\* total size \(bytes\):  112 \*/} \
+{                         \}}] \
+    "ptype offset struct poi"
+
+# Test printing a struct with several bitfields, laid out in various
+# ways.
+#
+# Because dealing with bitfields and offsets is difficult, it can be
+# tricky to confirm that the output of the this command is accurate.
+# A nice way to do that is to use GDB's "x" command and print the
+# actual memory layout of the struct.  In order to differentiate
+# between bitfields and non-bitfield variables, one can assign "-1" to
+# every bitfield in the struct.  An example of the output of "x" using
+# "struct tyu" is:
+#
+#   (gdb) x/24xb &e
+#   0x7fffffffd540: 0xff    0xff    0xff    0x1f    0x00    0x00    0x00    0x00
+#   0x7fffffffd548: 0xff    0xff    0xff    0xff    0xff    0xff    0xff    0xff
+#   0x7fffffffd550: 0xff    0x00    0x00    0x00    0x00    0x00    0x00    0x00
+gdb_test "ptype /o struct tyu" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct tyu \{} \
+{/\*    0:31   |     4 \*/    int a1 : 1;} \
+{/\*    0:28   |     4 \*/    int a2 : 3;} \
+{/\*    0: 5   |     4 \*/    int a3 : 23;} \
+{/\*    3: 3   |     1 \*/    signed char a4 : 2;} \
+{/\* XXX  3-bit hole   \*/} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |     8 \*/    int64_t a5;} \
+{/\*   16:27   |     4 \*/    int a6 : 5;} \
+{/\*   16:56   |     8 \*/    int64_t a7 : 3;} \
+{} \
+{                           /\* total size \(bytes\):   24 \*/} \
+{                         \}}] \
+    "ptype offset struct tyu"
+
+# Test that we don't print any header when issuing a "ptype /o" on a
+# non-struct, non-union, non-class type.
+gdb_test "ptype /o int" "int" "ptype offset int"
+gdb_test "ptype /o uint8_t" "char" "ptype offset uint8_t"
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 9d9d6f5a49..5ad0d5bd17 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,6 +42,7 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -55,12 +56,21 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
   NULL				/* global_printers */
 };
 
+/* The default values for a struct print_offset_data.  */
+
+struct print_offset_data print_offset_default_data =
+{
+  0,				/* offset_bitpos */
+  0,				/* endpos */
+};
+
 \f
 
 /* A hash table holding typedef_field objects.  This is more
@@ -440,6 +450,11 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  flags.print_offsets = 1;
+		  flags.print_typedefs = 0;
+		  flags.print_methods = 0;
+		  break;
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -499,6 +514,11 @@ whatis_exp (const char *exp, int show)
 	real_type = value_rtti_type (val, &full, &top, &using_enc);
     }
 
+  if (flags.print_offsets
+      && (TYPE_CODE (type) == TYPE_CODE_STRUCT
+	  || TYPE_CODE (type) == TYPE_CODE_UNION))
+    fprintf_filtered (gdb_stdout, "/* offset    |  size */  ");
+
   printf_filtered ("type = ");
 
   if (!flags.raw)
@@ -759,7 +779,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a2058b0120..be882d0d03 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -24,6 +24,22 @@ struct ui_file;
 struct typedef_hash_table;
 struct ext_lang_type_printers;
 
+struct print_offset_data
+{
+  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
+     This is needed for when we are printing nested structs and want
+     to make sure that the printed offset for each field carries over
+     the offset of the outter struct.  */
+  unsigned int offset_bitpos;
+
+  /* ENDPOS is the one-past-the-end bit position of the previous field
+     (where we expect the current field to be if there is no
+     hole).  */
+  unsigned int endpos;
+};
+
+extern struct print_offset_data print_offset_default_data;
+
 struct type_print_options
 {
   /* True means that no special printing flags should apply.  */
@@ -35,6 +51,9 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
   /* The number of nested type definitions to print.  -1 == all.  */
   int print_nested_type_limit;
 
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v6 2/2] Implement pahole-like 'ptype /o' option
  2017-12-14  2:48   ` [PATCH v6 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-12-14 14:19     ` Pedro Alves
  2017-12-14 20:31       ` Sergio Durigan Junior
  2017-12-14 14:50     ` Pedro Alves
  2017-12-14 16:30     ` Eli Zaretskii
  2 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-14 14:19 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

Something broke in v5 -> v6 in the offset and hole computation.

I'm seeing this:

v6:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(top-gdb) ptype/o minimal_symbol
/* offset    |  size */  type = struct minimal_symbol {
/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
/*    8      |     8 */        union {
/*                 8 */            LONGEST ivalue;
/*                 8 */            const block *block;
/*                 8 */            const gdb_byte *bytes;
/*                 8 */            CORE_ADDR address;
/*                 8 */            const common_block *common_block;
/*                 8 */            symbol *chain;

                                   /* total size (bytes):    8 */
                               } value;
/*   24      |     8 */        union {
/*                 8 */            obstack *obstack;
/*                 8 */            const char *demangled_name;

                                   /* total size (bytes):    8 */
                               } language_specific;
/*   48:27   |     4 */        language language : 5;
/*   48:26   |     4 */        unsigned int ada_mangled : 1;
/* XXX  2-bit hole   */
/* XXX  1-byte hole  */
/*   50      |     2 */        short section;

                               /* total size (bytes):   32 */
                           } mginfo;
/*   56      |     8 */    unsigned long size;
/*   64      |     8 */    const char *filename;
/*   72:28   |     4 */    minimal_symbol_type type : 4;
/*   72:27   |     4 */    unsigned int created_by_gdb : 1;
/*   72:26   |     4 */    unsigned int target_flag_1 : 1;
/*   72:25   |     4 */    unsigned int target_flag_2 : 1;
/*   72:24   |     4 */    unsigned int has_size : 1;
/* XXX  7-byte hole  */
/*   80      |     8 */    minimal_symbol *hash_next;
/*   88      |     8 */    minimal_symbol *demangled_hash_next;

                           /* total size (bytes):   72 */
                         }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This says that "language_specific" is at 24, with size 8  => next
byte is at 32.  "language" is at 48.  How come no "XXX hole"
is printed?  Similarly in the "8 + 8 -> 24" jump.

It turns out the byte offsets are wrong.  Here's the actual
byte offset for language_specific is:

 (top-gdb) p /d &((minimal_symbol *) 0) ->mginfo.language_specific
 $1 = 16

while v6 says 24.

v5 seemed to get it right.  Here's what it would show:

(top-gdb) ptype /o minimal_symbol
/* offset    |  size */
type = struct minimal_symbol {
/*    0      |    32 */    struct general_symbol_info {
/*    0      |     8 */        const char *name;
/*    8      |     8 */        union {
/*                 8 */            LONGEST ivalue;
/*                 8 */            const block *block;
/*                 8 */            const gdb_byte *bytes;
/*                 8 */            CORE_ADDR address;
/*                 8 */            const common_block *common_block;
/*                 8 */            symbol *chain;
                               } /* total size:    8 bytes */ value;
/*   16      |     8 */        union {
/*                 8 */            obstack *obstack;
/*                 8 */            const char *demangled_name;
                               } /* total size:    8 bytes */ language_specific;
/*   24:27   |     4 */        language language : 5;
/*   24:26   |     4 */        unsigned int ada_mangled : 1;
/* XXX  2-bit hole   */
/* XXX  1-byte hole  */
/*   26      |     2 */        short section;
                           } /* total size:   32 bytes */ mginfo;
/*   32      |     8 */    unsigned long size;
/*   40      |     8 */    const char *filename;
/*   48:28   |     4 */    minimal_symbol_type type : 4;
/*   48:27   |     4 */    unsigned int created_by_gdb : 1;
/*   48:26   |     4 */    unsigned int target_flag_1 : 1;
/*   48:25   |     4 */    unsigned int target_flag_2 : 1;
/*   48:24   |     4 */    unsigned int has_size : 1;
/* XXX  7-byte hole  */
/*   56      |     8 */    minimal_symbol *hash_next;
/*   64      |     8 */    minimal_symbol *demangled_hash_next;
} /* total size:   72 bytes */
(top-gdb) 

Surprised this wasn't caught by the testcases.

I do like the new output format a lot better.  Thanks much for
implementing the suggestions!

Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v6 2/2] Implement pahole-like 'ptype /o' option
  2017-12-14  2:48   ` [PATCH v6 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-14 14:19     ` Pedro Alves
@ 2017-12-14 14:50     ` Pedro Alves
  2017-12-14 20:29       ` Sergio Durigan Junior
  2017-12-14 16:30     ` Eli Zaretskii
  2 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-14 14:50 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On 12/14/2017 02:48 AM, Sergio Durigan Junior wrote:

> A big part of this patch handles the formatting logic of 'ptype',
> which is a bit messy.  The code to handle bitfield offsets, however,
> took some time to craft.  My thanks to Pedro Alves for figuring things
> out and pointing me to the right direction, as well as coming up with
> a way to inspect the layout of structs with bitfields (see testcase
> for comments).

Why, thanks. :-)

> +Issuing a @kbd{ptype /o struct tuv} command would print:
> +
> +@smallexample
> +(@value{GDBP}) ptype /o struct tuv
> +/* offset    |  size */
> +struct tuv @{
> +/*    0      |     4 */    int a1;
> +/* XXX  4-byte hole  */
> +/*    8      |     8 */    char *a2;
> +/*   16      |     4 */    int a3;
> +@} /* total size:   24 bytes */
> +@end smallexample

These examples need to be updated per the new output format.

> +   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 will be used to test 'ptype /o' on x86_64 only.  */

No longer true...

> +
> +#include <stdint.h>
> +

> +
> +# Test only works on x86_64 LP64 targets.  That's how we guarantee

Remove reference to x86_64; it's no longer true.

> +# that the expected holes will be present in the struct.
> +if { ![is_lp64_target] } {
> +    untested "test work only on lp64 targets"
> +    return 0
> +}
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
> +	  { debug c++ }] } {
> +    return -1
> +}

I think we're missing a test for "ptype /oTM", to make sure that
we can still override the fact that /o implies /tm ?
I'd add at least one for "/oTM" and one for "/TMo".  The
latter ends up being the same as "/o".

> +/* The default values for a struct print_offset_data.  */
> +
> +struct print_offset_data print_offset_default_data =
> +{
> +  0,				/* offset_bitpos */
> +  0,				/* endpos */
> +};
> +

Do we really need this object ?  How about just defining
the default values in-class ?  See below.

> --- a/gdb/typeprint.h
> +++ b/gdb/typeprint.h
> @@ -24,6 +24,22 @@ struct ui_file;
>  struct typedef_hash_table;
>  struct ext_lang_type_printers;
>  
> +struct print_offset_data
> +{
> +  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
> +     This is needed for when we are printing nested structs and want
> +     to make sure that the printed offset for each field carries over
> +     the offset of the outter struct.  */
> +  unsigned int offset_bitpos;

So here:

  unsigned int offset_bitpos = 0;

> +
> +  /* ENDPOS is the one-past-the-end bit position of the previous field
> +     (where we expect the current field to be if there is no
> +     hole).  */
> +  unsigned int endpos;

and

   unsigned int endpos = 0;

Then you default-constructed print_offset_data objects have
the fields automatically zeroed:

  print_offset_data podata;

while at it, wouldn't it be better to name this one "end_bitpos" or
something like that with "bit" in it as well?

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v6 2/2] Implement pahole-like 'ptype /o' option
  2017-12-14  2:48   ` [PATCH v6 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-14 14:19     ` Pedro Alves
  2017-12-14 14:50     ` Pedro Alves
@ 2017-12-14 16:30     ` Eli Zaretskii
  2 siblings, 0 replies; 75+ messages in thread
From: Eli Zaretskii @ 2017-12-14 16:30 UTC (permalink / raw)
  To: Sergio Durigan Junior; +Cc: gdb-patches, tom, simon.marchi, palves, keiths

> From: Sergio Durigan Junior <sergiodj@redhat.com>
> Cc: Tom Tromey <tom@tromey.com>,
> 	Eli Zaretskii <eliz@gnu.org>,
> 	Simon Marchi <simon.marchi@ericsson.com>,
> 	Pedro Alves <palves@redhat.com>,
> 	Keith Seitz <keiths@redhat.com>,
> 	Sergio Durigan Junior <sergiodj@redhat.com>
> Date: Wed, 13 Dec 2017 21:48:16 -0500
> 
> gdb/ChangeLog:
> yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
> 	    Pedro Alves  <palves@redhat.com>
> 
> 	PR cli/16224
> 	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
> 	* c-typeprint.c (OFFSET_SPC_LEN): New define.
> 	(c_type_print_varspec_prefix): New argument 'struct
> 	print_offset_data *'.
> 	(c_type_print_base_1): New function and prototype.
> 	(c_print_type_1): New function, with code from 'c_print_type'.
> 	(c_print_type): Use 'c_print_type_1'.
> 	(c_type_print_varspec_prefix): New argument 'struct
> 	print_offset_data *'.  Use it.  Call 'c_type_print_base_1'
> 	instead of 'c_print_type_base'.
> 	(print_spaces_filtered_with_print_options): New function.
> 	(output_access_specifier): Take new argument FLAGS.  Modify
> 	function to call 'print_spaces_filtered_with_print_options'.
> 	(c_print_type_vtable_offset_marker): New function.
> 	(c_print_type_union_field_offset): New function.
> 	(c_print_type_struct_field_offset): New function.
> 	(c_type_print_base_struct_union): New argument 'struct
> 	print_offset_data *'.  Print offsets and sizes for
> 	struct/union/class fields.
> 	* typeprint.c (const struct type_print_options
> 	type_print_raw_options): Initialize 'print_offsets'.
> 	(static struct type_print_options default_ptype_flags):
> 	Likewise.
> 	(struct print_offset_data print_offset_default_data): New
> 	variable.
> 	(whatis_exp): Handle '/o' option.
> 	(_initialize_typeprint): Add '/o' flag to ptype's help.
> 	* typeprint.h (struct print_offset_data): New struct.
> 	(struct type_print_options) <print_offsets>: New field.
> 
> gdb/testsuite/ChangeLog:
> yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
> 
> 	PR cli/16224
> 	* gdb.base/ptype-offsets.cc: New file.
> 	* gdb.base/ptype-offsets.exp: New file.
> 
> gdb/doc/ChangeLog:
> yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
> 
> 	PR cli/16224
> 	* gdb.texinfo (ptype): Add documentation for new flag '/o'.

OK for the documentation parts, thanks.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v6 2/2] Implement pahole-like 'ptype /o' option
  2017-12-14 14:50     ` Pedro Alves
@ 2017-12-14 20:29       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-14 20:29 UTC (permalink / raw)
  To: Pedro Alves
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

Thanks for the review.

On Thursday, December 14 2017, Pedro Alves wrote:

> On 12/14/2017 02:48 AM, Sergio Durigan Junior wrote:
>
>> +Issuing a @kbd{ptype /o struct tuv} command would print:
>> +
>> +@smallexample
>> +(@value{GDBP}) ptype /o struct tuv
>> +/* offset    |  size */
>> +struct tuv @{
>> +/*    0      |     4 */    int a1;
>> +/* XXX  4-byte hole  */
>> +/*    8      |     8 */    char *a2;
>> +/*   16      |     4 */    int a3;
>> +@} /* total size:   24 bytes */
>> +@end smallexample
>
> These examples need to be updated per the new output format.

Done.

>> +   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 will be used to test 'ptype /o' on x86_64 only.  */
>
> No longer true...

Removed.

>> +
>> +#include <stdint.h>
>> +
>
>> +
>> +# Test only works on x86_64 LP64 targets.  That's how we guarantee
>
> Remove reference to x86_64; it's no longer true.

Done.

>> +# that the expected holes will be present in the struct.
>> +if { ![is_lp64_target] } {
>> +    untested "test work only on lp64 targets"
>> +    return 0
>> +}
>> +
>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
>> +	  { debug c++ }] } {
>> +    return -1
>> +}
>
> I think we're missing a test for "ptype /oTM", to make sure that
> we can still override the fact that /o implies /tm ?
> I'd add at least one for "/oTM" and one for "/TMo".  The
> latter ends up being the same as "/o".

Hm, OK.  Added the tests.

>> +/* The default values for a struct print_offset_data.  */
>> +
>> +struct print_offset_data print_offset_default_data =
>> +{
>> +  0,				/* offset_bitpos */
>> +  0,				/* endpos */
>> +};
>> +
>
> Do we really need this object ?  How about just defining
> the default values in-class ?  See below.

Indeed.  I removed it in favour of initializing in-class.

>> --- a/gdb/typeprint.h
>> +++ b/gdb/typeprint.h
>> @@ -24,6 +24,22 @@ struct ui_file;
>>  struct typedef_hash_table;
>>  struct ext_lang_type_printers;
>>  
>> +struct print_offset_data
>> +{
>> +  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
>> +     This is needed for when we are printing nested structs and want
>> +     to make sure that the printed offset for each field carries over
>> +     the offset of the outter struct.  */
>> +  unsigned int offset_bitpos;
>
> So here:
>
>   unsigned int offset_bitpos = 0;
>
>> +
>> +  /* ENDPOS is the one-past-the-end bit position of the previous field
>> +     (where we expect the current field to be if there is no
>> +     hole).  */
>> +  unsigned int endpos;
>
> and
>
>    unsigned int endpos = 0;
>
> Then you default-constructed print_offset_data objects have
> the fields automatically zeroed:
>
>   print_offset_data podata;
>
> while at it, wouldn't it be better to name this one "end_bitpos" or
> something like that with "bit" in it as well?

Right; endpos is too generic indeed.  Renamed to end_bitpos.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v6 2/2] Implement pahole-like 'ptype /o' option
  2017-12-14 14:19     ` Pedro Alves
@ 2017-12-14 20:31       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-14 20:31 UTC (permalink / raw)
  To: Pedro Alves
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On Thursday, December 14 2017, Pedro Alves wrote:

> Something broke in v5 -> v6 in the offset and hole computation.

Thanks for catching this.

Indeed, the way the offset_bitpos is propagated has changed, and the
patch is wrongly calculating it before printing inner fields of
structs/unions.

I've fixed the logic and now the behavious is back to the old one.  All
the tests that were passing before are still passing now, which means
that I wasn't testing this specific scenario.  For that reason, I've
added one more test to cover this.

Thanks,

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg)
  2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
                   ` (7 preceding siblings ...)
  2017-12-14  2:48 ` [PATCH v6 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-15  1:12 ` Sergio Durigan Junior
  2017-12-15  1:12   ` [PATCH v7 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
                     ` (2 more replies)
  8 siblings, 3 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-15  1:12 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves, Keith Seitz

Changes from v6:

- Updated examples in the documentation.

- Removed stale comments about the test being x86_64-specific from
  tests.

- Added tests for "ptype /oTM", "ptype /TMo", and for the regression
  Pedro caught.

- Removed "print_offset_default_data".

- Fixed regression reported by Pedro.

- s/endpos/end_bitpos/

- Filtered out the languages that don't implement the feature so that
  their type printing output doesn't get clobbered by "ptype /o" when
  they use "common" code (which is not actually common; it lives
  inside c-typeprint.c).  I decided to do that by checking the current
  language on typeprint.c:whatis_exp, before setting the
  "print_offset" bit.


The documentation parts have been approved by Eli, and haven't
changed.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v7 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base'
  2017-12-15  1:12 ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
@ 2017-12-15  1:12   ` Sergio Durigan Junior
  2017-12-15  1:13   ` [PATCH v7 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
  2017-12-15 20:11   ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-15  1:12 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	Keith Seitz, Sergio Durigan Junior

While doing the 'ptype /o' work, I noticed that 'c_type_print_base'
was very long, with a big amount of code just to handle the case of
TYPE_CODE_{STRUCT,UNION}.  This made working with the function a bit
difficult, specially because of the level of indentation.

This commit moves this part of the code to their own functions.  Now
we have a 'c_type_print_base_struct_union' with most of the code, and
also 'need_access_label_p', which is a subset of the code that was
also a good candidate for having its own function.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	* c-typeprint.c (need_access_label_p): New function.
	(c_type_print_base_struct_union): New function.
	(c_type_print_base): Move code to handle
	TYPE_CODE_{STRUCT,UNION} to the functions mentioned above.
---
 gdb/c-typeprint.c | 837 ++++++++++++++++++++++++++----------------------------
 1 file changed, 403 insertions(+), 434 deletions(-)

diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index f3c3e7d706..3854886387 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -875,6 +875,407 @@ output_access_specifier (struct ui_file *stream,
   return last_access;
 }
 
+/* Return true is an access label (i.e., "public:", "private:",
+   "protected:") needs to be printed for TYPE.  */
+
+static bool
+need_access_label_p (struct type *type)
+{
+  if (TYPE_DECLARED_CLASS (type))
+    {
+      QUIT;
+      for (int i = TYPE_N_BASECLASSES (type); i < TYPE_NFIELDS (type); i++)
+	if (!TYPE_FIELD_PRIVATE (type, i))
+	  return true;
+      QUIT;
+      for (int j = 0; j < TYPE_NFN_FIELDS (type); j++)
+	for (int i = 0; i < TYPE_FN_FIELDLIST_LENGTH (type, j); i++)
+	  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+							  j), i))
+	    return true;
+      QUIT;
+      for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+	  return true;
+    }
+  else
+    {
+      QUIT;
+      for (int i = TYPE_N_BASECLASSES (type); i < TYPE_NFIELDS (type); i++)
+	if (TYPE_FIELD_PRIVATE (type, i) || TYPE_FIELD_PROTECTED (type, i))
+	  return true;
+      QUIT;
+      for (int j = 0; j < TYPE_NFN_FIELDS (type); j++)
+	{
+	  QUIT;
+	  for (int i = 0; i < TYPE_FN_FIELDLIST_LENGTH (type, j); i++)
+	    if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
+							     j), i)
+		|| TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
+							      j),
+					  i))
+	      return true;
+	}
+      QUIT;
+      for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
+	if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
+	    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
+	  return true;
+    }
+
+  return false;
+}
+
+/* Helper for 'c_type_print_base' that handles structs and unions.
+   For a description of the arguments, see 'c_type_print_base'.  */
+
+static void
+c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
+				int show, int level,
+				const struct type_print_options *flags)
+{
+  struct type_print_options local_flags = *flags;
+  struct type_print_options semi_local_flags = *flags;
+  struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
+
+  local_flags.local_typedefs = NULL;
+  semi_local_flags.local_typedefs = NULL;
+
+  if (!flags->raw)
+    {
+      if (flags->local_typedefs)
+	local_flags.local_typedefs
+	  = copy_typedef_hash (flags->local_typedefs);
+      else
+	local_flags.local_typedefs = create_typedef_hash ();
+
+      make_cleanup_free_typedef_hash (local_flags.local_typedefs);
+    }
+
+  c_type_print_modifier (type, stream, 0, 1);
+  if (TYPE_CODE (type) == TYPE_CODE_UNION)
+    fprintf_filtered (stream, "union ");
+  else if (TYPE_DECLARED_CLASS (type))
+    fprintf_filtered (stream, "class ");
+  else
+    fprintf_filtered (stream, "struct ");
+
+  /* Print the tag if it exists.  The HP aCC compiler emits a
+     spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
+     enum}" tag for unnamed struct/union/enum's, which we don't
+     want to print.  */
+  if (TYPE_TAG_NAME (type) != NULL
+      && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
+    {
+      /* When printing the tag name, we are still effectively
+	 printing in the outer context, hence the use of FLAGS
+	 here.  */
+      print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
+      if (show > 0)
+	fputs_filtered (" ", stream);
+    }
+
+  if (show < 0)
+    {
+      /* If we just printed a tag name, no need to print anything
+	 else.  */
+      if (TYPE_TAG_NAME (type) == NULL)
+	fprintf_filtered (stream, "{...}");
+    }
+  else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
+    {
+      struct type *basetype;
+      int vptr_fieldno;
+
+      c_type_print_template_args (&local_flags, type, stream);
+
+      /* Add in template parameters when printing derivation info.  */
+      add_template_parameters (local_flags.local_typedefs, type);
+      cp_type_print_derivation_info (stream, type, &local_flags);
+
+      /* This holds just the global typedefs and the template
+	 parameters.  */
+      semi_local_flags.local_typedefs
+	= copy_typedef_hash (local_flags.local_typedefs);
+      if (semi_local_flags.local_typedefs)
+	make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
+
+      /* Now add in the local typedefs.  */
+      recursively_update_typedef_hash (local_flags.local_typedefs, type);
+
+      fprintf_filtered (stream, "{\n");
+      if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
+	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
+	{
+	  if (TYPE_STUB (type))
+	    fprintfi_filtered (level + 4, stream,
+			       _("<incomplete type>\n"));
+	  else
+	    fprintfi_filtered (level + 4, stream,
+			       _("<no data fields>\n"));
+	}
+
+      /* Start off with no specific section type, so we can print
+	 one for the first field we find, and use that section type
+	 thereafter until we find another type.  */
+
+      enum access_specifier section_type = s_none;
+
+      /* For a class, if all members are private, there's no need
+	 for a "private:" label; similarly, for a struct or union
+	 masquerading as a class, if all members are public, there's
+	 no need for a "public:" label.  */
+      bool need_access_label = need_access_label_p (type);
+
+      /* If there is a base class for this type,
+	 do not print the field that it occupies.  */
+
+      int len = TYPE_NFIELDS (type);
+      vptr_fieldno = get_vptr_fieldno (type, &basetype);
+      for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
+	{
+	  QUIT;
+
+	  /* If we have a virtual table pointer, omit it.  Even if
+	     virtual table pointers are not specifically marked in
+	     the debug info, they should be artificial.  */
+	  if ((i == vptr_fieldno && type == basetype)
+	      || TYPE_FIELD_ARTIFICIAL (type, i))
+	    continue;
+
+	  if (need_access_label)
+	    {
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FIELD_PROTECTED (type, i),
+		 TYPE_FIELD_PRIVATE (type, i));
+	    }
+
+	  print_spaces_filtered (level + 4, stream);
+	  if (field_is_static (&TYPE_FIELD (type, i)))
+	    fprintf_filtered (stream, "static ");
+	  c_print_type (TYPE_FIELD_TYPE (type, i),
+			TYPE_FIELD_NAME (type, i),
+			stream, show - 1, level + 4,
+			&local_flags);
+	  if (!field_is_static (&TYPE_FIELD (type, i))
+	      && TYPE_FIELD_PACKED (type, i))
+	    {
+	      /* It is a bitfield.  This code does not attempt
+		 to look at the bitpos and reconstruct filler,
+		 unnamed fields.  This would lead to misleading
+		 results if the compiler does not put out fields
+		 for such things (I don't know what it does).  */
+	      fprintf_filtered (stream, " : %d",
+				TYPE_FIELD_BITSIZE (type, i));
+	    }
+	  fprintf_filtered (stream, ";\n");
+	}
+
+      /* If there are both fields and methods, put a blank line
+	 between them.  Make sure to count only method that we
+	 will display; artificial methods will be hidden.  */
+      len = TYPE_NFN_FIELDS (type);
+      if (!flags->print_methods)
+	len = 0;
+      int real_len = 0;
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  int j;
+
+	  for (j = 0; j < len2; j++)
+	    if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
+	      real_len++;
+	}
+      if (real_len > 0 && section_type != s_none)
+	fprintf_filtered (stream, "\n");
+
+      /* C++: print out the methods.  */
+      for (int i = 0; i < len; i++)
+	{
+	  struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
+	  int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
+	  const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
+	  const char *name = type_name_no_tag (type);
+	  int is_constructor = name && strcmp (method_name,
+					       name) == 0;
+
+	  for (j = 0; j < len2; j++)
+	    {
+	      const char *mangled_name;
+	      gdb::unique_xmalloc_ptr<char> mangled_name_holder;
+	      char *demangled_name;
+	      const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
+	      int is_full_physname_constructor =
+		TYPE_FN_FIELD_CONSTRUCTOR (f, j)
+		|| is_constructor_name (physname)
+		|| is_destructor_name (physname)
+		|| method_name[0] == '~';
+
+	      /* Do not print out artificial methods.  */
+	      if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
+		continue;
+
+	      QUIT;
+	      section_type = output_access_specifier
+		(stream, section_type, level,
+		 TYPE_FN_FIELD_PROTECTED (f, j),
+		 TYPE_FN_FIELD_PRIVATE (f, j));
+
+	      print_spaces_filtered (level + 4, stream);
+	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
+		fprintf_filtered (stream, "virtual ");
+	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
+		fprintf_filtered (stream, "static ");
+	      if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
+		{
+		  /* Keep GDB from crashing here.  */
+		  fprintf_filtered (stream,
+				    _("<undefined type> %s;\n"),
+				    TYPE_FN_FIELD_PHYSNAME (f, j));
+		  break;
+		}
+	      else if (!is_constructor	/* Constructors don't
+					   have declared
+					   types.  */
+		       && !is_full_physname_constructor  /* " " */
+		       && !is_type_conversion_operator (type, i, j))
+		{
+		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+				"", stream, -1, 0,
+				&local_flags);
+		  fputs_filtered (" ", stream);
+		}
+	      if (TYPE_FN_FIELD_STUB (f, j))
+		{
+		  /* Build something we can demangle.  */
+		  mangled_name_holder.reset (gdb_mangle_name (type, i, j));
+		  mangled_name = mangled_name_holder.get ();
+		}
+	      else
+		mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
+
+	      demangled_name =
+		gdb_demangle (mangled_name,
+			      DMGL_ANSI | DMGL_PARAMS);
+	      if (demangled_name == NULL)
+		{
+		  /* In some cases (for instance with the HP
+		     demangling), if a function has more than 10
+		     arguments, the demangling will fail.
+		     Let's try to reconstruct the function
+		     signature from the symbol information.  */
+		  if (!TYPE_FN_FIELD_STUB (f, j))
+		    {
+		      int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
+		      struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
+
+		      cp_type_print_method_args (mtype,
+						 "",
+						 method_name,
+						 staticp,
+						 stream, &local_flags);
+		    }
+		  else
+		    fprintf_filtered (stream,
+				      _("<badly mangled name '%s'>"),
+				      mangled_name);
+		}
+	      else
+		{
+		  char *p;
+		  char *demangled_no_class
+		    = remove_qualifiers (demangled_name);
+
+		  /* Get rid of the `static' appended by the
+		     demangler.  */
+		  p = strstr (demangled_no_class, " static");
+		  if (p != NULL)
+		    {
+		      int length = p - demangled_no_class;
+		      char *demangled_no_static;
+
+		      demangled_no_static
+			= (char *) xmalloc (length + 1);
+		      strncpy (demangled_no_static,
+			       demangled_no_class, length);
+		      *(demangled_no_static + length) = '\0';
+		      fputs_filtered (demangled_no_static, stream);
+		      xfree (demangled_no_static);
+		    }
+		  else
+		    fputs_filtered (demangled_no_class, stream);
+		  xfree (demangled_name);
+		}
+
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print out nested types.  */
+      if (TYPE_NESTED_TYPES_COUNT (type) != 0
+	  && semi_local_flags.print_nested_type_limit != 0)
+	{
+	  if (semi_local_flags.print_nested_type_limit > 0)
+	    --semi_local_flags.print_nested_type_limit;
+
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
+	    {
+	      print_spaces_filtered (level + 4, stream);
+	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+			    "", stream, show, level + 4, &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      /* Print typedefs defined in this class.  */
+
+      if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
+	{
+	  if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
+	      || TYPE_NESTED_TYPES_COUNT (type) != 0)
+	    fprintf_filtered (stream, "\n");
+
+	  for (int i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
+	    {
+	      struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
+
+	      /* Dereference the typedef declaration itself.  */
+	      gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
+	      target = TYPE_TARGET_TYPE (target);
+
+	      if (need_access_label)
+		{
+		  section_type = output_access_specifier
+		    (stream, section_type, level,
+		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		}
+	      print_spaces_filtered (level + 4, stream);
+	      fprintf_filtered (stream, "typedef ");
+
+	      /* We want to print typedefs with substitutions
+		 from the template parameters or globally-known
+		 typedefs but not local typedefs.  */
+	      c_print_type (target,
+			    TYPE_TYPEDEF_FIELD_NAME (type, i),
+			    stream, show - 1, level + 4,
+			    &semi_local_flags);
+	      fprintf_filtered (stream, ";\n");
+	    }
+	}
+
+      fprintfi_filtered (level, stream, "}");
+    }
+
+  do_cleanups (local_cleanups);
+}
+
 /* Print the name of the type (or the ultimate pointer target,
    function value or array element), or the description of a structure
    or union.
@@ -898,10 +1299,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		   int show, int level, const struct type_print_options *flags)
 {
   int i;
-  int len, real_len;
-  enum access_specifier section_type;
-  int need_access_label = 0;
-  int j, len2;
+  int len;
 
   QUIT;
 
@@ -958,436 +1356,7 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      {
-	struct type_print_options local_flags = *flags;
-	struct type_print_options semi_local_flags = *flags;
-	struct cleanup *local_cleanups = make_cleanup (null_cleanup, NULL);
-
-	local_flags.local_typedefs = NULL;
-	semi_local_flags.local_typedefs = NULL;
-
-	if (!flags->raw)
-	  {
-	    if (flags->local_typedefs)
-	      local_flags.local_typedefs
-		= copy_typedef_hash (flags->local_typedefs);
-	    else
-	      local_flags.local_typedefs = create_typedef_hash ();
-
-	    make_cleanup_free_typedef_hash (local_flags.local_typedefs);
-	  }
-
-	c_type_print_modifier (type, stream, 0, 1);
-	if (TYPE_CODE (type) == TYPE_CODE_UNION)
-	  fprintf_filtered (stream, "union ");
-	else if (TYPE_DECLARED_CLASS (type))
-	  fprintf_filtered (stream, "class ");
-	else
-	  fprintf_filtered (stream, "struct ");
-
-	/* Print the tag if it exists.  The HP aCC compiler emits a
-	   spurious "{unnamed struct}"/"{unnamed union}"/"{unnamed
-	   enum}" tag for unnamed struct/union/enum's, which we don't
-	   want to print.  */
-	if (TYPE_TAG_NAME (type) != NULL
-	    && !startswith (TYPE_TAG_NAME (type), "{unnamed"))
-	  {
-	    /* When printing the tag name, we are still effectively
-	       printing in the outer context, hence the use of FLAGS
-	       here.  */
-	    print_name_maybe_canonical (TYPE_TAG_NAME (type), flags, stream);
-	    if (show > 0)
-	      fputs_filtered (" ", stream);
-	  }
-
-	if (show < 0)
-	  {
-	    /* If we just printed a tag name, no need to print anything
-	       else.  */
-	    if (TYPE_TAG_NAME (type) == NULL)
-	      fprintf_filtered (stream, "{...}");
-	  }
-	else if (show > 0 || TYPE_TAG_NAME (type) == NULL)
-	  {
-	    struct type *basetype;
-	    int vptr_fieldno;
-
-	    c_type_print_template_args (&local_flags, type, stream);
-
-	    /* Add in template parameters when printing derivation info.  */
-	    add_template_parameters (local_flags.local_typedefs, type);
-	    cp_type_print_derivation_info (stream, type, &local_flags);
-
-	    /* This holds just the global typedefs and the template
-	       parameters.  */
-	    semi_local_flags.local_typedefs
-	      = copy_typedef_hash (local_flags.local_typedefs);
-	    if (semi_local_flags.local_typedefs)
-	      make_cleanup_free_typedef_hash (semi_local_flags.local_typedefs);
-
-	    /* Now add in the local typedefs.  */
-	    recursively_update_typedef_hash (local_flags.local_typedefs, type);
-
-	    fprintf_filtered (stream, "{\n");
-	    if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
-		&& TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
-	      {
-		if (TYPE_STUB (type))
-		  fprintfi_filtered (level + 4, stream,
-				     _("<incomplete type>\n"));
-		else
-		  fprintfi_filtered (level + 4, stream,
-				     _("<no data fields>\n"));
-	      }
-
-	    /* Start off with no specific section type, so we can print
-	       one for the first field we find, and use that section type
-	       thereafter until we find another type.  */
-
-	    section_type = s_none;
-
-	    /* For a class, if all members are private, there's no need
-	       for a "private:" label; similarly, for a struct or union
-	       masquerading as a class, if all members are public, there's
-	       no need for a "public:" label.  */
-
-	    if (TYPE_DECLARED_CLASS (type))
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (!TYPE_FIELD_PRIVATE (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (!TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									  j), i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (!TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-	    else
-	      {
-		QUIT;
-		len = TYPE_NFIELDS (type);
-		for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-		  if (TYPE_FIELD_PRIVATE (type, i)
-		      || TYPE_FIELD_PROTECTED (type, i))
-		    {
-		      need_access_label = 1;
-		      break;
-		    }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    len2 = TYPE_NFN_FIELDS (type);
-		    for (j = 0; j < len2; j++)
-		      {
-			QUIT;
-			len = TYPE_FN_FIELDLIST_LENGTH (type, j);
-			for (i = 0; i < len; i++)
-			  if (TYPE_FN_FIELD_PROTECTED (TYPE_FN_FIELDLIST1 (type,
-									   j), i)
-			      || TYPE_FN_FIELD_PRIVATE (TYPE_FN_FIELDLIST1 (type,
-									    j),
-							i))
-			    {
-			      need_access_label = 1;
-			      break;
-			    }
-			if (need_access_label)
-			  break;
-		      }
-		  }
-		QUIT;
-		if (!need_access_label)
-		  {
-		    for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); ++i)
-		      {
-			if (TYPE_TYPEDEF_FIELD_PROTECTED (type, i)
-			    || TYPE_TYPEDEF_FIELD_PRIVATE (type, i))
-			  {
-			    need_access_label = 1;
-			    break;
-			  }
-		      }
-		  }
-	      }
-
-	    /* If there is a base class for this type,
-	       do not print the field that it occupies.  */
-
-	    len = TYPE_NFIELDS (type);
-	    vptr_fieldno = get_vptr_fieldno (type, &basetype);
-	    for (i = TYPE_N_BASECLASSES (type); i < len; i++)
-	      {
-		QUIT;
-
-		/* If we have a virtual table pointer, omit it.  Even if
-		   virtual table pointers are not specifically marked in
-		   the debug info, they should be artificial.  */
-		if ((i == vptr_fieldno && type == basetype)
-		    || TYPE_FIELD_ARTIFICIAL (type, i))
-		  continue;
-
-		if (need_access_label)
-		  {
-		    section_type = output_access_specifier
-		      (stream, section_type, level,
-		       TYPE_FIELD_PROTECTED (type, i),
-		       TYPE_FIELD_PRIVATE (type, i));
-		  }
-
-		print_spaces_filtered (level + 4, stream);
-		if (field_is_static (&TYPE_FIELD (type, i)))
-		  fprintf_filtered (stream, "static ");
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show - 1, level + 4,
-			      &local_flags);
-		if (!field_is_static (&TYPE_FIELD (type, i))
-		    && TYPE_FIELD_PACKED (type, i))
-		  {
-		    /* It is a bitfield.  This code does not attempt
-		       to look at the bitpos and reconstruct filler,
-		       unnamed fields.  This would lead to misleading
-		       results if the compiler does not put out fields
-		       for such things (I don't know what it does).  */
-		    fprintf_filtered (stream, " : %d",
-				      TYPE_FIELD_BITSIZE (type, i));
-		  }
-		fprintf_filtered (stream, ";\n");
-	      }
-
-	  /* If there are both fields and methods, put a blank line
-	     between them.  Make sure to count only method that we
-	     will display; artificial methods will be hidden.  */
-	  len = TYPE_NFN_FIELDS (type);
-	  if (!flags->print_methods)
-	    len = 0;
-	  real_len = 0;
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      int j;
-
-	      for (j = 0; j < len2; j++)
-		if (!TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		  real_len++;
-	    }
-	  if (real_len > 0 && section_type != s_none)
-	    fprintf_filtered (stream, "\n");
-
-	  /* C++: print out the methods.  */
-	  for (i = 0; i < len; i++)
-	    {
-	      struct fn_field *f = TYPE_FN_FIELDLIST1 (type, i);
-	      int j, len2 = TYPE_FN_FIELDLIST_LENGTH (type, i);
-	      const char *method_name = TYPE_FN_FIELDLIST_NAME (type, i);
-	      const char *name = type_name_no_tag (type);
-	      int is_constructor = name && strcmp (method_name,
-						   name) == 0;
-
-	      for (j = 0; j < len2; j++)
-		{
-		  const char *mangled_name;
-		  gdb::unique_xmalloc_ptr<char> mangled_name_holder;
-		  char *demangled_name;
-		  const char *physname = TYPE_FN_FIELD_PHYSNAME (f, j);
-		  int is_full_physname_constructor =
-		    TYPE_FN_FIELD_CONSTRUCTOR (f, j)
-		    || is_constructor_name (physname)
-		    || is_destructor_name (physname)
-		    || method_name[0] == '~';
-
-		  /* Do not print out artificial methods.  */
-		  if (TYPE_FN_FIELD_ARTIFICIAL (f, j))
-		    continue;
-
-		  QUIT;
-		  section_type = output_access_specifier
-		    (stream, section_type, level,
-		     TYPE_FN_FIELD_PROTECTED (f, j),
-		     TYPE_FN_FIELD_PRIVATE (f, j));
-
-		  print_spaces_filtered (level + 4, stream);
-		  if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
-		    fprintf_filtered (stream, "virtual ");
-		  else if (TYPE_FN_FIELD_STATIC_P (f, j))
-		    fprintf_filtered (stream, "static ");
-		  if (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)) == 0)
-		    {
-		      /* Keep GDB from crashing here.  */
-		      fprintf_filtered (stream,
-					_("<undefined type> %s;\n"),
-					TYPE_FN_FIELD_PHYSNAME (f, j));
-		      break;
-		    }
-		  else if (!is_constructor	/* Constructors don't
-						   have declared
-						   types.  */
-			   && !is_full_physname_constructor  /* " " */
-			   && !is_type_conversion_operator (type, i, j))
-		    {
-		      c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				    "", stream, -1, 0,
-				    &local_flags);
-		      fputs_filtered (" ", stream);
-		    }
-		  if (TYPE_FN_FIELD_STUB (f, j))
-		    {
-		      /* Build something we can demangle.  */
-		      mangled_name_holder.reset (gdb_mangle_name (type, i, j));
-		      mangled_name = mangled_name_holder.get ();
-		    }
-		  else
-		    mangled_name = TYPE_FN_FIELD_PHYSNAME (f, j);
-
-		  demangled_name =
-		    gdb_demangle (mangled_name,
-				  DMGL_ANSI | DMGL_PARAMS);
-		  if (demangled_name == NULL)
-		    {
-		      /* In some cases (for instance with the HP
-			 demangling), if a function has more than 10
-			 arguments, the demangling will fail.
-			 Let's try to reconstruct the function
-			 signature from the symbol information.  */
-		      if (!TYPE_FN_FIELD_STUB (f, j))
-			{
-			  int staticp = TYPE_FN_FIELD_STATIC_P (f, j);
-			  struct type *mtype = TYPE_FN_FIELD_TYPE (f, j);
-
-			  cp_type_print_method_args (mtype,
-						     "",
-						     method_name,
-						     staticp,
-						     stream, &local_flags);
-			}
-		      else
-			fprintf_filtered (stream,
-					  _("<badly mangled name '%s'>"),
-					  mangled_name);
-		    }
-		  else
-		    {
-		      char *p;
-		      char *demangled_no_class
-			= remove_qualifiers (demangled_name);
-
-		      /* Get rid of the `static' appended by the
-			 demangler.  */
-		      p = strstr (demangled_no_class, " static");
-		      if (p != NULL)
-			{
-			  int length = p - demangled_no_class;
-			  char *demangled_no_static;
-
-			  demangled_no_static
-			    = (char *) xmalloc (length + 1);
-			  strncpy (demangled_no_static,
-				   demangled_no_class, length);
-			  *(demangled_no_static + length) = '\0';
-			  fputs_filtered (demangled_no_static, stream);
-			  xfree (demangled_no_static);
-			}
-		      else
-			fputs_filtered (demangled_no_class, stream);
-		      xfree (demangled_name);
-		    }
-
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print out nested types.  */
-	  if (TYPE_NESTED_TYPES_COUNT (type) != 0
-	      && semi_local_flags.print_nested_type_limit != 0)
-	    {
-	      if (semi_local_flags.print_nested_type_limit > 0)
-		--semi_local_flags.print_nested_type_limit;
-
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
-		{
-		  print_spaces_filtered (level + 4, stream);
-		  c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
-				"", stream, show, level + 4, &semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	  /* Print typedefs defined in this class.  */
-
-	  if (TYPE_TYPEDEF_FIELD_COUNT (type) != 0 && flags->print_typedefs)
-	    {
-	      if (TYPE_NFIELDS (type) != 0 || TYPE_NFN_FIELDS (type) != 0
-		  || TYPE_NESTED_TYPES_COUNT (type) != 0)
-		fprintf_filtered (stream, "\n");
-
-	      for (i = 0; i < TYPE_TYPEDEF_FIELD_COUNT (type); i++)
-		{
-		  struct type *target = TYPE_TYPEDEF_FIELD_TYPE (type, i);
-
-		  /* Dereference the typedef declaration itself.  */
-		  gdb_assert (TYPE_CODE (target) == TYPE_CODE_TYPEDEF);
-		  target = TYPE_TARGET_TYPE (target);
-
-		  if (need_access_label)
-		    {
-		      section_type = output_access_specifier
-			(stream, section_type, level,
-			 TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-			 TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
-		    }
-		  print_spaces_filtered (level + 4, stream);
-		  fprintf_filtered (stream, "typedef ");
-
-		  /* We want to print typedefs with substitutions
-		     from the template parameters or globally-known
-		     typedefs but not local typedefs.  */
-		  c_print_type (target,
-				TYPE_TYPEDEF_FIELD_NAME (type, i),
-				stream, show - 1, level + 4,
-				&semi_local_flags);
-		  fprintf_filtered (stream, ";\n");
-		}
-	    }
-
-	    fprintfi_filtered (level, stream, "}");
-	  }
-
-	do_cleanups (local_cleanups);
-      }
+      c_type_print_base_struct_union (type, stream, show, level, flags);
       break;
 
     case TYPE_CODE_ENUM:
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [PATCH v7 2/2] Implement pahole-like 'ptype /o' option
  2017-12-15  1:12 ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2017-12-15  1:12   ` [PATCH v7 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
@ 2017-12-15  1:13   ` Sergio Durigan Junior
  2017-12-15 17:24     ` Pedro Alves
  2017-12-15 20:11   ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2 siblings, 1 reply; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-15  1:13 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves,
	Keith Seitz, Sergio Durigan Junior

This commit implements the pahole-like '/o' option for 'ptype', which
prints the offsets and sizes of struct fields, reporting whenever
there is a hole found.

The output is heavily based on pahole(1), with a few modifications
here and there to adjust it to our reality.  Here's an example:

  /* offset    |  size */  type = struct wer : public tuv {
			   public:
  /*   32      |    24 */    struct tyu {
  /*   32:31   |     4 */        int a1 : 1;
  /*   32:28   |     4 */        int a2 : 3;
  /*   32: 5   |     4 */        int a3 : 23;
  /*   35: 3   |     1 */        char a4 : 2;
  /* XXX  3-bit hole   */
  /* XXX  4-byte hole  */
  /*   40      |     8 */        int64_t a5;
  /*   48:27   |     4 */        int a6 : 5;
  /*   48:56   |     8 */        int64_t a7 : 3;

				 /* total size (bytes):   24 */
			     } a1;

			     /* total size (bytes):   56 */
			   }

A big part of this patch handles the formatting logic of 'ptype',
which is a bit messy.  The code to handle bitfield offsets, however,
took some time to craft.  My thanks to Pedro Alves for figuring things
out and pointing me to the right direction, as well as coming up with
a way to inspect the layout of structs with bitfields (see testcase
for comments).

After many discussions both on IRC and at the mailing list, I tried to
implement printing vtables and inherited classes.  Unfortunately the
code grew too complex and there were still a few corner cases failing
so I had to drop the attempt.  This should be implemented in a future
patch.

This patch is the start of a long-term work I'll do to flush the local
patches we carry for Fedora GDB.  In this specific case, I'm aiming at
upstreaming the feature implemented by the 'pahole.py' script that is
shipped with Fedora GDB:

  <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>

This has been regression-tested on the BuildBot.  There's a new
testcase for it, along with an update to the documentation.  I also
thought it was worth mentioning this feature in the NEWS file.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Pedro Alves  <palves@redhat.com>

	PR cli/16224
	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
	* c-typeprint.c (OFFSET_SPC_LEN): New define.
	(c_type_print_varspec_prefix): New argument 'struct
	print_offset_data *'.
	(c_type_print_base_1): New function and prototype.
	(c_print_type_1): New function, with code from 'c_print_type'.
	(c_print_type): Use 'c_print_type_1'.
	(c_type_print_varspec_prefix): New argument 'struct
	print_offset_data *'.  Use it.  Call 'c_type_print_base_1'
	instead of 'c_print_type_base'.
	(print_spaces_filtered_with_print_options): New function.
	(output_access_specifier): Take new argument FLAGS.  Modify
	function to call 'print_spaces_filtered_with_print_options'.
	(c_print_type_vtable_offset_marker): New function.
	(c_print_type_union_field_offset): New function.
	(c_print_type_struct_field_offset): New function.
	(c_type_print_base_struct_union): New argument 'struct
	print_offset_data *'.  Print offsets and sizes for
	struct/union/class fields.
	* typeprint.c (const struct type_print_options
	type_print_raw_options): Initialize 'print_offsets'.
	(static struct type_print_options default_ptype_flags):
	Likewise.
	(struct print_offset_data print_offset_default_data): New
	variable.
	(whatis_exp): Handle '/o' option.
	(_initialize_typeprint): Add '/o' flag to ptype's help.
	* typeprint.h (struct print_offset_data): New struct.
	(struct type_print_options) <print_offsets>: New field.

gdb/testsuite/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.base/ptype-offsets.cc: New file.
	* gdb.base/ptype-offsets.exp: New file.

gdb/doc/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.texinfo (ptype): Add documentation for new flag '/o'.
---
 gdb/NEWS                                 |   3 +
 gdb/c-typeprint.c                        | 388 +++++++++++++++++++++++++------
 gdb/doc/gdb.texinfo                      | 124 ++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.cc  | 193 +++++++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.exp | 317 +++++++++++++++++++++++++
 gdb/typeprint.c                          |  23 +-
 gdb/typeprint.h                          |  17 ++
 7 files changed, 999 insertions(+), 66 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index bd5ae36dd0..3a7e704cc6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * New "--readnever" command line option instructs GDB to not read each
   symbol file's symbolic debug information.  This makes startup faster
   but at the expense of not being able to perform symbolic debugging.
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index 3854886387..b977401145 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -45,12 +53,18 @@ enum access_specifier
 static void c_type_print_varspec_prefix (struct type *,
 					 struct ui_file *,
 					 int, int, int,
-					 const struct type_print_options *);
+					 const struct type_print_options *,
+					 struct print_offset_data *);
 
 /* Print "const", "volatile", or address space modifiers.  */
 static void c_type_print_modifier (struct type *,
 				   struct ui_file *,
 				   int, int);
+
+static void c_type_print_base_1 (struct type *type, struct ui_file *stream,
+				 int show, int level,
+				 const struct type_print_options *flags,
+				 struct print_offset_data *podata);
 \f
 
 /* A callback function for cp_canonicalize_string_full that uses
@@ -82,14 +96,15 @@ print_name_maybe_canonical (const char *name,
 
 \f
 
-/* LEVEL is the depth to indent lines by.  */
+/* Helper function for c_print_type.  */
 
-void
-c_print_type (struct type *type,
-	      const char *varstring,
-	      struct ui_file *stream,
-	      int show, int level,
-	      const struct type_print_options *flags)
+static void
+c_print_type_1 (struct type *type,
+		const char *varstring,
+		struct ui_file *stream,
+		int show, int level,
+		const struct type_print_options *flags,
+		struct print_offset_data *podata)
 {
   enum type_code code;
   int demangled_args;
@@ -108,7 +123,7 @@ c_print_type (struct type *type,
     }
   else
     {
-      c_type_print_base (type, stream, show, level, flags);
+      c_type_print_base_1 (type, stream, show, level, flags, podata);
       code = TYPE_CODE (type);
       if ((varstring != NULL && *varstring != '\0')
 	  /* Need a space if going to print stars or brackets;
@@ -124,7 +139,7 @@ c_print_type (struct type *type,
 	fputs_filtered (" ", stream);
       need_post_space = (varstring != NULL && strcmp (varstring, "") != 0);
       c_type_print_varspec_prefix (type, stream, show, 0, need_post_space,
-				   flags);
+				   flags, podata);
     }
 
   if (varstring != NULL)
@@ -143,6 +158,20 @@ c_print_type (struct type *type,
     }
 }
 
+/* LEVEL is the depth to indent lines by.  */
+
+void
+c_print_type (struct type *type,
+	      const char *varstring,
+	      struct ui_file *stream,
+	      int show, int level,
+	      const struct type_print_options *flags)
+{
+  struct print_offset_data podata;
+
+  c_print_type_1 (type, varstring, stream, show, level, flags, &podata);
+}
+
 /* Print a typedef using C syntax.  TYPE is the underlying type.
    NEW_SYMBOL is the symbol naming the type.  STREAM is the stream on
    which to print.  */
@@ -310,7 +339,8 @@ c_type_print_varspec_prefix (struct type *type,
 			     struct ui_file *stream,
 			     int show, int passed_a_ptr,
 			     int need_post_space,
-			     const struct type_print_options *flags)
+			     const struct type_print_options *flags,
+			     struct print_offset_data *podata)
 {
   const char *name;
 
@@ -326,40 +356,40 @@ c_type_print_varspec_prefix (struct type *type,
     {
     case TYPE_CODE_PTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 1, 1, flags);
+				   stream, show, 1, 1, flags, podata);
       fprintf_filtered (stream, "*");
       c_type_print_modifier (type, stream, 1, need_post_space);
       break;
 
     case TYPE_CODE_MEMBERPTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       name = type_name_no_tag (TYPE_SELF_TYPE (type));
       if (name)
 	print_name_maybe_canonical (name, flags, stream);
       else
-	c_type_print_base (TYPE_SELF_TYPE (type),
-			   stream, -1, passed_a_ptr, flags);
+	c_type_print_base_1 (TYPE_SELF_TYPE (type),
+			     stream, -1, passed_a_ptr, flags, podata);
       fprintf_filtered (stream, "::*");
       break;
 
     case TYPE_CODE_METHODPTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       fprintf_filtered (stream, "(");
       name = type_name_no_tag (TYPE_SELF_TYPE (type));
       if (name)
 	print_name_maybe_canonical (name, flags, stream);
       else
-	c_type_print_base (TYPE_SELF_TYPE (type),
-			   stream, -1, passed_a_ptr, flags);
+	c_type_print_base_1 (TYPE_SELF_TYPE (type),
+			     stream, -1, passed_a_ptr, flags, podata);
       fprintf_filtered (stream, "::*");
       break;
 
     case TYPE_CODE_REF:
     case TYPE_CODE_RVALUE_REF:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 1, 0, flags);
+				   stream, show, 1, 0, flags, podata);
       fprintf_filtered (stream, TYPE_CODE(type) == TYPE_CODE_REF ? "&" : "&&");
       c_type_print_modifier (type, stream, 1, need_post_space);
       break;
@@ -367,21 +397,22 @@ c_type_print_varspec_prefix (struct type *type,
     case TYPE_CODE_METHOD:
     case TYPE_CODE_FUNC:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       if (passed_a_ptr)
 	fprintf_filtered (stream, "(");
       break;
 
     case TYPE_CODE_ARRAY:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       if (passed_a_ptr)
 	fprintf_filtered (stream, "(");
       break;
 
     case TYPE_CODE_TYPEDEF:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, passed_a_ptr, 0, flags);
+				   stream, show, passed_a_ptr, 0, flags,
+				   podata);
       break;
 
     case TYPE_CODE_UNDEF:
@@ -836,21 +867,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options
+  (int level, struct ui_file *stream, const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +904,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,15 +913,121 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
-/* Return true is an access label (i.e., "public:", "private:",
+/* Print information about field at index FIELD_IDX of the union type
+   TYPE.  Since union fields don't have the concept of offsets, we
+   just print their sizes.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
+				 struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+
+  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+}
+
+/* Print information about field at index FIELD_IDX of the struct type
+   TYPE.
+
+   END_BITPOS is the one-past-the-end bit position of the previous
+   field (where we expect this field to be if there is no hole).  At
+   the end, ENDPOS is updated to the one-past-the-end bit position of
+   the current field.
+
+   OFFSET_BITPOS is the offset value we carry over when we are
+   printing a struct that is inside another struct; this is useful so
+   that the offset is constantly incremented (if we didn't carry it
+   over, the offset would be reset to zero when printing the inner
+   struct).
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
+				  struct ui_file *stream,
+				  unsigned int *end_bitpos,
+				  struct print_offset_data *podata)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+
+  /* We check for *END_BITPOS > 0 because there is a specific scenario
+     when *END_BITPOS can be zero and BITPOS can be > 0: when we are
+     dealing with a struct/class with a virtual method.  Because of
+     the vtable, the first field of the struct/class will have an
+     offset of sizeof (void *) (the size of the vtable).  If we do not
+     check for *END_BITPOS > 0 here, GDB will report a hole before the
+     first field, which is not accurate.  */
+  if (*end_bitpos > 0 && *end_bitpos < bitpos)
+    {
+      /* If END_BITPOS is smaller than the current type's bitpos, it
+	 means there's a hole in the struct, so we report it here.  */
+      unsigned int hole = bitpos - *end_bitpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      unsigned int bitsize = TYPE_FIELD_BITSIZE (type, field_idx);
+      /* The bitpos relative to the beginning of our container
+	 field.  */
+      unsigned int relative_bitpos;
+
+      /* The following was copied from
+	 value.c:value_primitive_field.  */
+      if ((bitpos % fieldsize_bit) + bitsize <= fieldsize_bit)
+	relative_bitpos = bitpos % fieldsize_bit;
+      else
+	relative_bitpos = bitpos % TARGET_CHAR_BIT;
+
+      /* This is the exact offset (in bits) of this bitfield.  */
+      unsigned int bit_offset
+	= (bitpos - relative_bitpos) + podata->offset_bitpos;
+
+      /* The position of the field, relative to the beginning of the
+	 struct, and how many bits are left to be used in this
+	 container.  */
+      fprintf_filtered (stream, "/* %4u:%2u", bit_offset / TARGET_CHAR_BIT,
+			fieldsize_bit - (relative_bitpos + bitsize));
+      fieldsize_bit = bitsize;
+    }
+  else
+    {
+      /* The position of the field, relative to the beginning of the
+	 struct.  */
+      fprintf_filtered (stream, "/* %4u",
+			(bitpos + podata->offset_bitpos) / TARGET_CHAR_BIT);
+
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  *end_bitpos = bitpos + fieldsize_bit;
+}
+
+/* Return true if an access label (i.e., "public:", "private:",
    "protected:") needs to be printed for TYPE.  */
 
 static bool
@@ -932,7 +1084,8 @@ need_access_label_p (struct type *type)
 static void
 c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 				int show, int level,
-				const struct type_print_options *flags)
+				const struct type_print_options *flags,
+				struct print_offset_data *podata)
 {
   struct type_print_options local_flags = *flags;
   struct type_print_options semi_local_flags = *flags;
@@ -1004,6 +1157,7 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
       recursively_update_typedef_hash (local_flags.local_typedefs, type);
 
       fprintf_filtered (stream, "{\n");
+
       if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
 	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
 	{
@@ -1032,6 +1186,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
       int len = TYPE_NFIELDS (type);
       vptr_fieldno = get_vptr_fieldno (type, &basetype);
+
+      unsigned int end_bitpos = podata->end_bitpos;
+      struct print_offset_data local_podata;
+
       for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
 	{
 	  QUIT;
@@ -1048,18 +1206,63 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FIELD_PROTECTED (type, i),
-		 TYPE_FIELD_PRIVATE (type, i));
+		 TYPE_FIELD_PRIVATE (type, i), flags);
+	    }
+
+	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+	  if (flags->print_offsets)
+	    {
+	      if (!is_static)
+		{
+		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		    {
+		      c_print_type_struct_field_offset
+			(type, i, stream, &end_bitpos, podata);
+		    }
+		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
+		    c_print_type_union_field_offset (type, i, stream);
+		}
+	      else
+		print_spaces_filtered (OFFSET_SPC_LEN, stream);
 	    }
 
 	  print_spaces_filtered (level + 4, stream);
-	  if (field_is_static (&TYPE_FIELD (type, i)))
+	  if (is_static)
 	    fprintf_filtered (stream, "static ");
-	  c_print_type (TYPE_FIELD_TYPE (type, i),
-			TYPE_FIELD_NAME (type, i),
-			stream, show - 1, level + 4,
-			&local_flags);
-	  if (!field_is_static (&TYPE_FIELD (type, i))
-	      && TYPE_FIELD_PACKED (type, i))
+
+	  int newshow = show - 1;
+
+	  if (flags->print_offsets
+	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
+		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
+	    {
+	      /* If we're printing offsets and this field's type is
+		 either a struct or an union, then we're interested in
+		 expanding it.  */
+	      ++newshow;
+
+	      /* Make sure we carry our offset when we expand the
+		 struct/union.  */
+	      local_podata.offset_bitpos
+		= podata->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
+	      /* We're entering a struct/union.  Right now, END_BITPOS
+		 points right *after* the struct/union.  However, when
+		 printing the first field of this inner struct/union,
+		 the end_bitpos we're expecting is exactly at the
+		 beginning of the struct/union.  Therefore, we
+		 subtract the length of the whole struct/union.  */
+	      local_podata.end_bitpos
+		= end_bitpos
+		  - TYPE_LENGTH (TYPE_FIELD_TYPE (type, i)) * TARGET_CHAR_BIT;
+	    }
+
+	  c_print_type_1 (TYPE_FIELD_TYPE (type, i),
+			  TYPE_FIELD_NAME (type, i),
+			  stream, newshow, level + 4,
+			  &local_flags, &local_podata);
+
+	  if (!is_static && TYPE_FIELD_PACKED (type, i))
 	    {
 	      /* It is a bitfield.  This code does not attempt
 		 to look at the bitpos and reconstruct filler,
@@ -1122,9 +1325,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FN_FIELD_PROTECTED (f, j),
-		 TYPE_FN_FIELD_PRIVATE (f, j));
+		 TYPE_FN_FIELD_PRIVATE (f, j), flags);
 
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
 		fprintf_filtered (stream, "virtual ");
 	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
@@ -1143,9 +1347,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		       && !is_full_physname_constructor  /* " " */
 		       && !is_type_conversion_operator (type, i, j))
 		{
-		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				"", stream, -1, 0,
-				&local_flags);
+		  unsigned int old_po = local_flags.print_offsets;
+
+		  /* Temporarily disable print_offsets, because it
+		     would mess with indentation.  */
+		  local_flags.print_offsets = 0;
+		  c_print_type_1 (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+				  "", stream, -1, 0,
+				  &local_flags, podata);
+		  local_flags.print_offsets = old_po;
+
 		  fputs_filtered (" ", stream);
 		}
 	      if (TYPE_FN_FIELD_STUB (f, j))
@@ -1226,9 +1437,17 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
 	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
 	    {
-	      print_spaces_filtered (level + 4, stream);
-	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
-			    "", stream, show, level + 4, &semi_local_flags);
+	      unsigned int old_po = semi_local_flags.print_offsets;
+
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      semi_local_flags.print_offsets = 0;
+	      c_print_type_1 (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+			      "", stream, show, level + 4, &semi_local_flags,
+			      podata);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
@@ -1254,22 +1473,46 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		  section_type = output_access_specifier
 		    (stream, section_type, level,
 		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i), flags);
 		}
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      fprintf_filtered (stream, "typedef ");
 
+	      unsigned int old_po = semi_local_flags.print_offsets;
+	      /* Temporarily disable print_offsets, because it
+		 would mess with indentation.  */
+	      semi_local_flags.print_offsets = 0;
+
 	      /* We want to print typedefs with substitutions
 		 from the template parameters or globally-known
 		 typedefs but not local typedefs.  */
-	      c_print_type (target,
-			    TYPE_TYPEDEF_FIELD_NAME (type, i),
-			    stream, show - 1, level + 4,
-			    &semi_local_flags);
+	      c_print_type_1 (target,
+			      TYPE_TYPEDEF_FIELD_NAME (type, i),
+			      stream, show - 1, level + 4,
+			      &semi_local_flags, podata);
+	      semi_local_flags.print_offsets = old_po;
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
 
+      if (flags->print_offsets)
+	{
+	  if (show > 0)
+	    {
+	      fputs_filtered ("\n", stream);
+	      print_spaces_filtered_with_print_options (level + 4,
+							stream,
+							flags);
+	      fprintf_filtered (stream, "/* total size (bytes): %4u */\n",
+				TYPE_LENGTH (type));
+	    }
+
+	  print_spaces_filtered (OFFSET_SPC_LEN, stream);
+	  if (level == 0)
+	    print_spaces_filtered (2, stream);
+	}
+
       fprintfi_filtered (level, stream, "}");
     }
 
@@ -1294,9 +1537,11 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
    LEVEL is the number of spaces to indent by.
    We increase it for some recursive calls.  */
 
-void
-c_type_print_base (struct type *type, struct ui_file *stream,
-		   int show, int level, const struct type_print_options *flags)
+static void
+c_type_print_base_1 (struct type *type, struct ui_file *stream,
+		     int show, int level,
+		     const struct type_print_options *flags,
+		     struct print_offset_data *podata)
 {
   int i;
   int len;
@@ -1341,8 +1586,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
       if (TYPE_TARGET_TYPE (type) == NULL)
 	type_print_unknown_return_type (stream);
       else
-	c_type_print_base (TYPE_TARGET_TYPE (type),
-			   stream, show, level, flags);
+	c_type_print_base_1 (TYPE_TARGET_TYPE (type),
+			     stream, show, level, flags, podata);
       break;
     case TYPE_CODE_ARRAY:
     case TYPE_CODE_PTR:
@@ -1350,13 +1595,14 @@ c_type_print_base (struct type *type, struct ui_file *stream,
     case TYPE_CODE_REF:
     case TYPE_CODE_RVALUE_REF:
     case TYPE_CODE_METHODPTR:
-      c_type_print_base (TYPE_TARGET_TYPE (type),
-			 stream, show, level, flags);
+      c_type_print_base_1 (TYPE_TARGET_TYPE (type),
+			   stream, show, level, flags, podata);
       break;
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      c_type_print_base_struct_union (type, stream, show, level, flags);
+      c_type_print_base_struct_union (type, stream, show, level, flags,
+				      podata);
       break;
 
     case TYPE_CODE_ENUM:
@@ -1456,10 +1702,10 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		print_spaces_filtered (level + 4, stream);
 		/* We pass "show" here and not "show - 1" to get enum types
 		   printed.  There's no other way to see them.  */
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show, level + 4,
-			      &local_flags);
+		c_print_type_1 (TYPE_FIELD_TYPE (type, i),
+				TYPE_FIELD_NAME (type, i),
+				stream, show, level + 4,
+				&local_flags, podata);
 		fprintf_filtered (stream, " @%s",
 				  plongest (TYPE_FIELD_BITPOS (type, i)));
 		if (TYPE_FIELD_BITSIZE (type, i) > 1)
@@ -1518,3 +1764,15 @@ c_type_print_base (struct type *type, struct ui_file *stream,
       break;
     }
 }
+
+/* See c_type_print_base_1.  */
+
+void
+c_type_print_base (struct type *type, struct ui_file *stream,
+		   int show, int level,
+		   const struct type_print_options *flags)
+{
+  struct print_offset_data podata;
+
+  c_type_print_base_1 (type, stream, show, level, flags, &podata);
+}
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index da3ed28dfe..60ed80c363 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17216,6 +17216,130 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.  This option implies the @code{/tm} flags.
+
+For example, given the following declarations:
+
+@smallexample
+struct tuv
+@{
+  int a1;
+  char *a2;
+  int a3;
+@};
+
+struct xyz
+@{
+  int f1;
+  char f2;
+  void *f3;
+  struct tuv f4;
+@};
+
+union qwe
+@{
+  struct tuv fff1;
+  struct xyz fff2;
+@};
+
+struct tyu
+@{
+  int a1 : 1;
+  int a2 : 3;
+  int a3 : 23;
+  char a4 : 2;
+  int64_t a5;
+  int a6 : 5;
+  int64_t a7 : 3;
+@};
+@end smallexample
+
+Issuing a @kbd{ptype /o struct tuv} command would print:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tuv
+/* offset    |  size */  type = struct tuv @{
+/*    0      |     4 */    int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */    char *a2;
+/*   16      |     4 */    int a3;
+
+                           /* total size (bytes):   24 */
+                         @}
+@end smallexample
+
+Notice the format of the first column of comments.  There, you can
+find two parts separated by the @samp{|} character: the @emph{offset},
+which indicates where the field is located inside the struct, in
+bytes, and the @emph{size} of the field.  Another interesting line is
+the marker of a @emph{hole} in the struct, indicating that it may be
+possible to pack the struct and make it use less space by reorganizing
+its fields.
+
+It is also possible to print offsets inside an union:
+
+@smallexample
+(@value{GDBP}) ptype /o union qwe
+/* offset    |  size */  type = union qwe @{
+/*                24 */    struct tuv @{
+/*    0      |     4 */        int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */        char *a2;
+/*   16      |     4 */        int a3;
+
+                               /* total size (bytes):   24 */
+                           @} fff1;
+/*                40 */    struct xyz @{
+/*    0      |     4 */        int f1;
+/*    4      |     1 */        char f2;
+/* XXX  3-byte hole  */
+/*    8      |     8 */        void *f3;
+/*   16      |    24 */        struct tuv @{
+/*   16      |     4 */            int a1;
+/* XXX  4-byte hole  */
+/*   24      |     8 */            char *a2;
+/*   32      |     4 */            int a3;
+
+                                   /* total size (bytes):   24 */
+                               @} f4;
+
+                               /* total size (bytes):   40 */
+                           @} fff2;
+
+                           /* total size (bytes):   40 */
+                         @}
+@end smallexample
+
+In this case, since @code{struct tuv} and @code{struct xyz} occupy the
+same space (because we are dealing with an union), the offset is not
+printed for them.  However, you can still examine the offset of each
+of these structures' fields.
+
+Another useful scenario is printing the offsets of a struct containing
+bitfields:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tyu
+/* offset    |  size */  type = struct tyu @{
+/*    0:31   |     4 */    int a1 : 1;
+/*    0:28   |     4 */    int a2 : 3;
+/*    0: 5   |     4 */    int a3 : 23;
+/*    3: 3   |     1 */    signed char a4 : 2;
+/* XXX  3-bit hole   */
+/* XXX  4-byte hole  */
+/*    8      |     8 */    int64_t a5;
+/*   16:27   |     4 */    int a6 : 5;
+/*   16:56   |     8 */    int64_t a7 : 3;
+
+                           /* total size (bytes):   24 */
+                         @}
+@end smallexample
+
+Note how the offset information is now extended to also include how
+many bits are left to be used in each bitfield.
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..86735203ad
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,193 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  signed int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  signed char field4;
+
+  /* 7-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    signed int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+
+  /* Typedef defined in-struct.  */
+  typedef int my_int_type;
+
+  my_int_type field9;
+};
+
+/* This struct will be nested inside 'struct xyz'.  */
+
+struct tuv
+{
+  signed int a1;
+
+  signed char *a2;
+
+  signed int a3;
+};
+
+/* This struct will be nested inside 'struct pqr'.  */
+
+struct xyz
+{
+  signed int f1;
+
+  signed char f2;
+
+  void *f3;
+
+  struct tuv f4;
+};
+
+/* A struct with a nested struct.  */
+
+struct pqr
+{
+  signed int ff1;
+
+  struct xyz ff2;
+
+  signed char ff3;
+};
+
+/* A union with two nested structs.  */
+
+union qwe
+{
+  struct tuv fff1;
+
+  struct xyz fff2;
+};
+
+/* A struct with an union.  */
+
+struct poi
+{
+  signed int f1;
+
+  union qwe f2;
+
+  uint16_t f3;
+
+  struct pqr f4;
+};
+
+/* A struct with bitfields.  */
+
+struct tyu
+{
+  signed int a1 : 1;
+
+  signed int a2 : 3;
+
+  signed int a3 : 23;
+
+  signed char a4 : 2;
+
+  int64_t a5;
+
+  signed int a6 : 5;
+
+  int64_t a7 : 3;
+};
+
+/* A struct with structs and unions.  */
+
+struct asd
+{
+  struct jkl
+  {
+      signed char *f1;
+      union
+      {
+	void *ff1;
+      } f2;
+      union
+      {
+	signed char *ff2;
+      } f3;
+      int f4 : 5;
+      unsigned int f5 : 1;
+      short f6;
+  } f7;
+  unsigned long f8;
+  signed char *f9;
+  int f10 : 4;
+  unsigned int f11 : 1;
+  unsigned int f12 : 1;
+  unsigned int f13 : 1;
+  unsigned int f14 : 1;
+  void *f15;
+  void *f16;
+};
+
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+  struct pqr bar;
+  union qwe c;
+  struct poi d;
+  struct tyu e;
+  struct asd f;
+  uint8_t i;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..67fbd72d64
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,317 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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 testcase exercises the "ptype /o" feature, which can be used to
+# print the offsets and sizes of each field of a struct/union/class.
+
+standard_testfile .cc
+
+# Test only works on LP64 targets.  That's how we guarantee that the
+# expected holes will be present in the struct.
+if { ![is_lp64_target] } {
+    untested "test work only on lp64 targets"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ }] } {
+    return -1
+}
+
+# Test general offset printing, ctor/dtor printing, union, formatting.
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct abc \{} \
+{                         public:} \
+{/\*    8      |     8 \*/    void \*field1;} \
+{/\*   16:31   |     4 \*/    unsigned int field2 : 1;} \
+{/\* XXX  7-bit hole   \*/} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   20      |     4 \*/    int field3;} \
+{/\*   24      |     1 \*/    signed char field4;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   32      |     8 \*/    uint64_t field5;} \
+{/\*   40      |     8 \*/    union \{} \
+{/\*                 8 \*/        void \*field6;} \
+{/\*                 4 \*/        int field7;} \
+{} \
+{                               /\* total size \(bytes\):    8 \*/} \
+{                           \} field8;} \
+{/\*   48      |     4 \*/    my_int_type field9;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}] \
+    "ptype offset struct abc"
+
+# Test "ptype /oTM".
+gdb_test "ptype /oTM struct abc" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct abc \{} \
+{                         public:} \
+{/\*    8      |     8 \*/    void \*field1;} \
+{/\*   16:31   |     4 \*/    unsigned int field2 : 1;} \
+{/\* XXX  7-bit hole   \*/} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   20      |     4 \*/    int field3;} \
+{/\*   24      |     1 \*/    signed char field4;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   32      |     8 \*/    uint64_t field5;} \
+{/\*   40      |     8 \*/    union \{} \
+{/\*                 8 \*/        void \*field6;} \
+{/\*                 4 \*/        int field7;} \
+{} \
+{                               /\* total size \(bytes\):    8 \*/} \
+{                           \} field8;} \
+{/\*   48      |     4 \*/    my_int_type field9;} \
+{} \
+{                           abc\(void\);} \
+{                           ~abc\(\);} \
+{} \
+{                           typedef int my_int_type;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}] \
+    "ptype /oTM struct abc"
+
+# Test "ptype /TMo".  This should be the same as "ptype /o".
+gdb_test "ptype /TMo struct abc" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct abc \{} \
+{                         public:} \
+{/\*    8      |     8 \*/    void \*field1;} \
+{/\*   16:31   |     4 \*/    unsigned int field2 : 1;} \
+{/\* XXX  7-bit hole   \*/} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   20      |     4 \*/    int field3;} \
+{/\*   24      |     1 \*/    signed char field4;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   32      |     8 \*/    uint64_t field5;} \
+{/\*   40      |     8 \*/    union \{} \
+{/\*                 8 \*/        void \*field6;} \
+{/\*                 4 \*/        int field7;} \
+{} \
+{                               /\* total size \(bytes\):    8 \*/} \
+{                           \} field8;} \
+{/\*   48      |     4 \*/    my_int_type field9;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}] \
+    "ptype /TMo struct abc"
+
+# Test nested structs.
+gdb_test "ptype /o struct pqr" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct pqr \{} \
+{/\*    0      |     4 \*/    int ff1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |    40 \*/    struct xyz \{} \
+{/\*    8      |     4 \*/        int f1;} \
+{/\*   12      |     1 \*/        signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   16      |     8 \*/        void \*f3;} \
+{/\*   24      |    24 \*/        struct tuv \{} \
+{/\*   24      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   32      |     8 \*/            signed char \*a2;} \
+{/\*   40      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} f4;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} ff2;} \
+{/\* XXX 28-byte hole  \*/} \
+{/\*   72      |     1 \*/    signed char ff3;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}] \
+    "ptype offset struct pqr"
+
+# Test that the offset is properly reset when we are printing a union
+# and go inside two inner structs.
+# This also tests a struct inside a struct inside a union.
+gdb_test "ptype /o union qwe" \
+    [multi_line \
+{/\* offset    |  size \*/  type = union qwe \{} \
+{/\*                24 \*/    struct tuv \{} \
+{/\*    0      |     4 \*/        int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |     8 \*/        signed char \*a2;} \
+{/\*   16      |     4 \*/        int a3;} \
+{} \
+{                               /\* total size \(bytes\):   24 \*/} \
+{                           \} fff1;} \
+{/\*                40 \*/    struct xyz \{} \
+{/\*    0      |     4 \*/        int f1;} \
+{/\*    4      |     1 \*/        signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*    8      |     8 \*/        void \*f3;} \
+{/\*   16      |    24 \*/        struct tuv \{} \
+{/\*   16      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   24      |     8 \*/            signed char \*a2;} \
+{/\*   32      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} f4;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} fff2;} \
+{} \
+{                           /\* total size \(bytes\):   40 \*/} \
+{                         \}}] \
+    "ptype offset union qwe"
+
+# Test printing a struct that contains a union, and that also
+# contains a struct.
+gdb_test "ptype /o struct poi" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct poi \{} \
+{/\*    0      |     4 \*/    int f1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |    40 \*/    union qwe \{} \
+{/\*                24 \*/        struct tuv \{} \
+{/\*    8      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   16      |     8 \*/            signed char \*a2;} \
+{/\*   24      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} fff1;} \
+{/\*                40 \*/        struct xyz \{} \
+{/\*    8      |     4 \*/            int f1;} \
+{/\*   12      |     1 \*/            signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   16      |     8 \*/            void \*f3;} \
+{/\*   24      |    24 \*/            struct tuv \{} \
+{/\*   24      |     4 \*/                int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   32      |     8 \*/                signed char \*a2;} \
+{/\*   40      |     4 \*/                int a3;} \
+{} \
+{                                       /\* total size \(bytes\):   24 \*/} \
+{                                   \} f4;} \
+{} \
+{                                   /\* total size \(bytes\):   40 \*/} \
+{                               \} fff2;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} f2;} \
+{/\*   72      |     2 \*/    uint16_t f3;} \
+{/\* XXX  6-byte hole  \*/} \
+{/\*   80      |    56 \*/    struct pqr \{} \
+{/\*   80      |     4 \*/        int ff1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   88      |    40 \*/        struct xyz \{} \
+{/\*   88      |     4 \*/            int f1;} \
+{/\*   92      |     1 \*/            signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   96      |     8 \*/            void \*f3;} \
+{/\*  104      |    24 \*/            struct tuv \{} \
+{/\*  104      |     4 \*/                int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*  112      |     8 \*/                signed char \*a2;} \
+{/\*  120      |     4 \*/                int a3;} \
+{} \
+{                                       /\* total size \(bytes\):   24 \*/} \
+{                                   \} f4;} \
+{} \
+{                                   /\* total size \(bytes\):   40 \*/} \
+{                               \} ff2;} \
+{/\*  152      |     1 \*/        signed char ff3;} \
+{} \
+{                               /\* total size \(bytes\):   56 \*/} \
+{                           \} f4;} \
+{} \
+{                           /\* total size \(bytes\):  112 \*/} \
+{                         \}}] \
+    "ptype offset struct poi"
+
+# Test printing a struct with several bitfields, laid out in various
+# ways.
+#
+# Because dealing with bitfields and offsets is difficult, it can be
+# tricky to confirm that the output of the this command is accurate.
+# A nice way to do that is to use GDB's "x" command and print the
+# actual memory layout of the struct.  In order to differentiate
+# between bitfields and non-bitfield variables, one can assign "-1" to
+# every bitfield in the struct.  An example of the output of "x" using
+# "struct tyu" is:
+#
+#   (gdb) x/24xb &e
+#   0x7fffffffd540: 0xff    0xff    0xff    0x1f    0x00    0x00    0x00    0x00
+#   0x7fffffffd548: 0xff    0xff    0xff    0xff    0xff    0xff    0xff    0xff
+#   0x7fffffffd550: 0xff    0x00    0x00    0x00    0x00    0x00    0x00    0x00
+gdb_test "ptype /o struct tyu" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct tyu \{} \
+{/\*    0:31   |     4 \*/    int a1 : 1;} \
+{/\*    0:28   |     4 \*/    int a2 : 3;} \
+{/\*    0: 5   |     4 \*/    int a3 : 23;} \
+{/\*    3: 3   |     1 \*/    signed char a4 : 2;} \
+{/\* XXX  3-bit hole   \*/} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |     8 \*/    int64_t a5;} \
+{/\*   16:27   |     4 \*/    int a6 : 5;} \
+{/\*   16:56   |     8 \*/    int64_t a7 : 3;} \
+{} \
+{                           /\* total size \(bytes\):   24 \*/} \
+{                         \}}] \
+    "ptype offset struct tyu"
+
+gdb_test "ptype /o struct asd" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct asd \{} \
+{/\*    0      |    32 \*/    struct asd::jkl \{} \
+{/\*    0      |     8 \*/        signed char \*f1;} \
+{/\*    8      |     8 \*/        union \{} \
+{/\*                 8 \*/            void \*ff1;} \
+{} \
+{                                   /\* total size \(bytes\):    8 \*/} \
+{                               \} f2;} \
+{/\*   16      |     8 \*/        union \{} \
+{/\*                 8 \*/            signed char \*ff2;} \
+{} \
+{                                   /\* total size \(bytes\):    8 \*/} \
+{                               \} f3;} \
+{/\*   24:27   |     4 \*/        int f4 : 5;} \
+{/\*   24:26   |     4 \*/        unsigned int f5 : 1;} \
+{/\* XXX  2-bit hole   \*/} \
+{/\* XXX  1-byte hole  \*/} \
+{/\*   26      |     2 \*/        short f6;} \
+{} \
+{                               /\* total size \(bytes\):   32 \*/} \
+{                           \} f7;} \
+{/\*   32      |     8 \*/    unsigned long f8;} \
+{/\*   40      |     8 \*/    signed char \*f9;} \
+{/\*   48:28   |     4 \*/    int f10 : 4;} \
+{/\*   48:27   |     4 \*/    unsigned int f11 : 1;} \
+{/\*   48:26   |     4 \*/    unsigned int f12 : 1;} \
+{/\*   48:25   |     4 \*/    unsigned int f13 : 1;} \
+{/\*   48:24   |     4 \*/    unsigned int f14 : 1;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   56      |     8 \*/    void \*f15;} \
+{/\*   64      |     8 \*/    void \*f16;} \
+{} \
+{                           /\* total size \(bytes\):   72 \*/} \
+{                         \}}] \
+    "ptype offset struct asd"
+
+# Test that we don't print any header when issuing a "ptype /o" on a
+# non-struct, non-union, non-class type.
+gdb_test "ptype /o int" "int" "ptype offset int"
+gdb_test "ptype /o uint8_t" "char" "ptype offset uint8_t"
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 9d9d6f5a49..9f44f3329a 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,6 +42,7 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -55,6 +56,7 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -440,6 +442,19 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  {
+		    /* Filter out languages which don't implement the
+		       feature.  */
+		    if (current_language->la_language == language_c
+			|| current_language->la_language == language_cplus)
+		      {
+			flags.print_offsets = 1;
+			flags.print_typedefs = 0;
+			flags.print_methods = 0;
+		      }
+		    break;
+		  }
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -499,6 +514,11 @@ whatis_exp (const char *exp, int show)
 	real_type = value_rtti_type (val, &full, &top, &using_enc);
     }
 
+  if (flags.print_offsets
+      && (TYPE_CODE (type) == TYPE_CODE_STRUCT
+	  || TYPE_CODE (type) == TYPE_CODE_UNION))
+    fprintf_filtered (gdb_stdout, "/* offset    |  size */  ");
+
   printf_filtered ("type = ");
 
   if (!flags.raw)
@@ -759,7 +779,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a2058b0120..2e19006f22 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -24,6 +24,20 @@ struct ui_file;
 struct typedef_hash_table;
 struct ext_lang_type_printers;
 
+struct print_offset_data
+{
+  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
+     This is needed for when we are printing nested structs and want
+     to make sure that the printed offset for each field carries over
+     the offset of the outter struct.  */
+  unsigned int offset_bitpos = 0;
+
+  /* END_BITPOS is the one-past-the-end bit position of the previous
+     field (where we expect the current field to be if there is no
+     hole).  */
+  unsigned int end_bitpos = 0;
+};
+
 struct type_print_options
 {
   /* True means that no special printing flags should apply.  */
@@ -35,6 +49,9 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
   /* The number of nested type definitions to print.  -1 == all.  */
   int print_nested_type_limit;
 
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v7 2/2] Implement pahole-like 'ptype /o' option
  2017-12-15  1:13   ` [PATCH v7 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-12-15 17:24     ` Pedro Alves
  2017-12-15 20:04       ` Sergio Durigan Junior
  0 siblings, 1 reply; 75+ messages in thread
From: Pedro Alves @ 2017-12-15 17:24 UTC (permalink / raw)
  To: Sergio Durigan Junior, GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

Hi Sergio,

I found a couple more nits to pick, but this is close.

This is OK with the issues below addressed.  Please address
them, and push this in.  Please post the end result for the
archives.

On 12/15/2017 01:12 AM, Sergio Durigan Junior wrote:

> @@ -867,15 +913,121 @@ output_access_specifier (struct ui_file *stream,
>        if (last_access != s_public)
>  	{
>  	  last_access = s_public;
> -	  fprintfi_filtered (level + 2, stream,
> -			     "public:\n");
> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
> +	  fprintf_filtered (stream, "public:\n");
>  	}
>      }
>  
>    return last_access;
>  }
>  
> -/* Return true is an access label (i.e., "public:", "private:",
> +/* Print information about field at index FIELD_IDX of the union type
> +   TYPE.  Since union fields don't have the concept of offsets, we
> +   just print their sizes.
> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
> +				 struct ui_file *stream)
> +{
> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
> +
> +  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
> +}
> +
> +/* Print information about field at index FIELD_IDX of the struct type
> +   TYPE.
> +
> +   END_BITPOS is the one-past-the-end bit position of the previous
> +   field (where we expect this field to be if there is no hole).  At
> +   the end, ENDPOS is updated to the one-past-the-end bit position of
> +   the current field.

There's still a reference to ENDPOS above that I assume should
be END_BITPOS.  Now that I'm looking at this, do we still need
this parameter, given print_offset_data->end_bitpos exists?
What's the relationship between the two?  It's a bit confusing
to have two things for the same.  I think the comment could/should
clarify this.

> +
> +   OFFSET_BITPOS is the offset value we carry over when we are
> +   printing a struct that is inside another struct; this is useful so
> +   that the offset is constantly incremented (if we didn't carry it
> +   over, the offset would be reset to zero when printing the inner
> +   struct).

This parameter no longer exists (it's a field of print_offset_data now,
right?).  The comment should be adjusted, moved elsewhere, or deleted.

> +
> +   The output is strongly based on pahole(1).  */
> +
> +static void
> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
> +				  struct ui_file *stream,
> +				  unsigned int *end_bitpos,
> +				  struct print_offset_data *podata)
> +{


> @@ -1143,9 +1347,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
>  		       && !is_full_physname_constructor  /* " " */
>  		       && !is_type_conversion_operator (type, i, j))
>  		{
> -		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
> -				"", stream, -1, 0,
> -				&local_flags);
> +		  unsigned int old_po = local_flags.print_offsets;
> +
> +		  /* Temporarily disable print_offsets, because it
> +		     would mess with indentation.  */
> +		  local_flags.print_offsets = 0;
> +		  c_print_type_1 (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
> +				  "", stream, -1, 0,
> +				  &local_flags, podata);
> +		  local_flags.print_offsets = old_po;

This pattern appears in several places.  Would it make sense to
add a c_print_type_no_offsets routine that handled the
print_offsets save/restore?

> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
> @@ -0,0 +1,317 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2017 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 testcase exercises the "ptype /o" feature, which can be used to
> +# print the offsets and sizes of each field of a struct/union/class.
> +
> +standard_testfile .cc
> +
> +# Test only works on LP64 targets.  That's how we guarantee that the
> +# expected holes will be present in the struct.
> +if { ![is_lp64_target] } {
> +    untested "test work only on lp64 targets"
> +    return 0
> +}
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
> +	  { debug c++ }] } {
> +    return -1
> +}
> +
> +# Test general offset printing, ctor/dtor printing, union, formatting.
> +gdb_test "ptype /o struct abc" \
> +    [multi_line \

...

> +{                         \}}] \
> +    "ptype offset struct abc"

I notice that you're replacing the "/o" in most test
names/messages.  Is there a reason for that?  Why
not just let the test name default to the command
invocation?  I.e., remove the 3rd argument in all
these calls to gdb_test.


> +
> +# Test "ptype /oTM".
> +gdb_test "ptype /oTM struct abc" \
> +    [multi_line \

..

> +    "ptype /oTM struct abc"

Here the "/" persisted, so I'm curious why the other cases
have "/o" replaced by "offset".

I'd just remove the explicit test messages.  It just seems
like potential for getting out of sync otherwise.

> +# Because dealing with bitfields and offsets is difficult, it can be
> +# tricky to confirm that the output of the this command is accurate.

typo: "of the this"

> @@ -499,6 +514,11 @@ whatis_exp (const char *exp, int show)
>  	real_type = value_rtti_type (val, &full, &top, &using_enc);
>      }
>  
> +  if (flags.print_offsets
> +      && (TYPE_CODE (type) == TYPE_CODE_STRUCT
> +	  || TYPE_CODE (type) == TYPE_CODE_UNION))
> +    fprintf_filtered (gdb_stdout, "/* offset    |  size */  ");

I noticed that "whatis" also prints this header:

 (top-gdb) whatis/o minimal_symbol
 /* offset    |  size */  type = minimal_symbol

I guess it shouldn't, for it's pointless.  You could either
use the "show" parameter here, or just ignore /o for whatis
where the options are parsed, further above.

And then please add a test covering that.  :-)

You'll have to use gdb_test_multiple with an anchor,
because 

   gdb_test "whatis/o minimal_symbol" \
      "type = minimal_symbol"

would match the bad output anyway.

So something like:

   set test "whatis /o minimal_symbol"
   gdb_test_multiple $test $test {
      -ex "^$test\r\ntype = minimal_symbol\r\n$gdb_prompt $" {
          pass $test
   }

As mentioned, OK with these issues addressed.  Please push and post.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v7 2/2] Implement pahole-like 'ptype /o' option
  2017-12-15 17:24     ` Pedro Alves
@ 2017-12-15 20:04       ` Sergio Durigan Junior
  0 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-15 20:04 UTC (permalink / raw)
  To: Pedro Alves
  Cc: GDB Patches, Tom Tromey, Eli Zaretskii, Simon Marchi, Keith Seitz

On Friday, December 15 2017, Pedro Alves wrote:

> Hi Sergio,
>
> I found a couple more nits to pick, but this is close.
>
> This is OK with the issues below addressed.  Please address
> them, and push this in.  Please post the end result for the
> archives.

Thanks for review.  I'll post the patch below.

> On 12/15/2017 01:12 AM, Sergio Durigan Junior wrote:
>
>> @@ -867,15 +913,121 @@ output_access_specifier (struct ui_file *stream,
>>        if (last_access != s_public)
>>  	{
>>  	  last_access = s_public;
>> -	  fprintfi_filtered (level + 2, stream,
>> -			     "public:\n");
>> +	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
>> +	  fprintf_filtered (stream, "public:\n");
>>  	}
>>      }
>>  
>>    return last_access;
>>  }
>>  
>> -/* Return true is an access label (i.e., "public:", "private:",
>> +/* Print information about field at index FIELD_IDX of the union type
>> +   TYPE.  Since union fields don't have the concept of offsets, we
>> +   just print their sizes.
>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
>> +				 struct ui_file *stream)
>> +{
>> +  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
>> +
>> +  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
>> +}
>> +
>> +/* Print information about field at index FIELD_IDX of the struct type
>> +   TYPE.
>> +
>> +   END_BITPOS is the one-past-the-end bit position of the previous
>> +   field (where we expect this field to be if there is no hole).  At
>> +   the end, ENDPOS is updated to the one-past-the-end bit position of
>> +   the current field.
>
> There's still a reference to ENDPOS above that I assume should
> be END_BITPOS.  Now that I'm looking at this, do we still need
> this parameter, given print_offset_data->end_bitpos exists?
> What's the relationship between the two?  It's a bit confusing
> to have two things for the same.  I think the comment could/should
> clarify this.

Yeah, you're right, I was using END_BITPOS as a separate value because
of the problem stated here:

	      /* We're entering a struct/union.  Right now,
		 PODATA->END_BITPOS points right *after* the
		 struct/union.  However, when printing the first field
		 of this inner struct/union, the end_bitpos we're
		 expecting is exactly at the beginning of the
		 struct/union.  Therefore, we subtract the length of
		 the whole struct/union.  */
	      local_podata.end_bitpos
		= podata->end_bitpos
		  - TYPE_LENGTH (TYPE_FIELD_TYPE (type, i)) * TARGET_CHAR_BIT;

However, it's just a matter of using PODATA->END_BITPOS instead, no big
deal.  So I've made the change, adapted the comments and references, and
it's all working as expected.

>> +
>> +   OFFSET_BITPOS is the offset value we carry over when we are
>> +   printing a struct that is inside another struct; this is useful so
>> +   that the offset is constantly incremented (if we didn't carry it
>> +   over, the offset would be reset to zero when printing the inner
>> +   struct).
>
> This parameter no longer exists (it's a field of print_offset_data now,
> right?).  The comment should be adjusted, moved elsewhere, or deleted.

Yes, that's correct.  I'll adjust it.

>> +
>> +   The output is strongly based on pahole(1).  */
>> +
>> +static void
>> +c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
>> +				  struct ui_file *stream,
>> +				  unsigned int *end_bitpos,
>> +				  struct print_offset_data *podata)
>> +{
>
>
>> @@ -1143,9 +1347,16 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
>>  		       && !is_full_physname_constructor  /* " " */
>>  		       && !is_type_conversion_operator (type, i, j))
>>  		{
>> -		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
>> -				"", stream, -1, 0,
>> -				&local_flags);
>> +		  unsigned int old_po = local_flags.print_offsets;
>> +
>> +		  /* Temporarily disable print_offsets, because it
>> +		     would mess with indentation.  */
>> +		  local_flags.print_offsets = 0;
>> +		  c_print_type_1 (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
>> +				  "", stream, -1, 0,
>> +				  &local_flags, podata);
>> +		  local_flags.print_offsets = old_po;
>
> This pattern appears in several places.  Would it make sense to
> add a c_print_type_no_offsets routine that handled the
> print_offsets save/restore?

Sure thing.  I'll add such function.

>
>> +++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
>> @@ -0,0 +1,317 @@
>> +# This testcase is part of GDB, the GNU debugger.
>> +
>> +# Copyright 2017 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 testcase exercises the "ptype /o" feature, which can be used to
>> +# print the offsets and sizes of each field of a struct/union/class.
>> +
>> +standard_testfile .cc
>> +
>> +# Test only works on LP64 targets.  That's how we guarantee that the
>> +# expected holes will be present in the struct.
>> +if { ![is_lp64_target] } {
>> +    untested "test work only on lp64 targets"
>> +    return 0
>> +}
>> +
>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
>> +	  { debug c++ }] } {
>> +    return -1
>> +}
>> +
>> +# Test general offset printing, ctor/dtor printing, union, formatting.
>> +gdb_test "ptype /o struct abc" \
>> +    [multi_line \
>
> ...
>
>> +{                         \}}] \
>> +    "ptype offset struct abc"
>
> I notice that you're replacing the "/o" in most test
> names/messages.  Is there a reason for that?  Why
> not just let the test name default to the command
> invocation?  I.e., remove the 3rd argument in all
> these calls to gdb_test.

I thought about that, but I guess I'm too used to always include the
test message, plus I wasn't sure if there would be some case when the
test command would be the same.  But sure, I will do that.

>
>> +
>> +# Test "ptype /oTM".
>> +gdb_test "ptype /oTM struct abc" \
>> +    [multi_line \
>
> ..
>
>> +    "ptype /oTM struct abc"
>
> Here the "/" persisted, so I'm curious why the other cases
> have "/o" replaced by "offset".
>
> I'd just remove the explicit test messages.  It just seems
> like potential for getting out of sync otherwise.

Done.

>> +# Because dealing with bitfields and offsets is difficult, it can be
>> +# tricky to confirm that the output of the this command is accurate.
>
> typo: "of the this"

Fixed.

>> @@ -499,6 +514,11 @@ whatis_exp (const char *exp, int show)
>>  	real_type = value_rtti_type (val, &full, &top, &using_enc);
>>      }
>>  
>> +  if (flags.print_offsets
>> +      && (TYPE_CODE (type) == TYPE_CODE_STRUCT
>> +	  || TYPE_CODE (type) == TYPE_CODE_UNION))
>> +    fprintf_filtered (gdb_stdout, "/* offset    |  size */  ");
>
> I noticed that "whatis" also prints this header:
>
>  (top-gdb) whatis/o minimal_symbol
>  /* offset    |  size */  type = minimal_symbol
>
> I guess it shouldn't, for it's pointless.  You could either
> use the "show" parameter here, or just ignore /o for whatis
> where the options are parsed, further above.

Aha, good catch.  Kind of forgot about "whatis" ;-).  I decided to use
the 'show' parameter, checking if it is > 0, but did that further above,
because I don't want print_offsets to be 1.

> And then please add a test covering that.  :-)
>
> You'll have to use gdb_test_multiple with an anchor,
> because 
>
>    gdb_test "whatis/o minimal_symbol" \
>       "type = minimal_symbol"
>
> would match the bad output anyway.
>
> So something like:
>
>    set test "whatis /o minimal_symbol"
>    gdb_test_multiple $test $test {
>       -ex "^$test\r\ntype = minimal_symbol\r\n$gdb_prompt $" {
>           pass $test
>    }

Cool, I didn't remember you could match the test command with
gdb_test_multiple.  BTW, I replaced -ex by -re, but it works OK.

> As mentioned, OK with these issues addressed.  Please push and post.

Thanks, here's what I'll push.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/


From ad7a465f76ace7e8e907b1616206bd6bd29c98cb Mon Sep 17 00:00:00 2001
From: Sergio Durigan Junior <sergiodj@redhat.com>
Date: Mon, 20 Nov 2017 16:34:59 -0500
Subject: [PATCH] Implement pahole-like 'ptype /o' option

This commit implements the pahole-like '/o' option for 'ptype', which
prints the offsets and sizes of struct fields, reporting whenever
there is a hole found.

The output is heavily based on pahole(1), with a few modifications
here and there to adjust it to our reality.  Here's an example:

  /* offset    |  size */  type = struct wer : public tuv {
			   public:
  /*   32      |    24 */    struct tyu {
  /*   32:31   |     4 */        int a1 : 1;
  /*   32:28   |     4 */        int a2 : 3;
  /*   32: 5   |     4 */        int a3 : 23;
  /*   35: 3   |     1 */        char a4 : 2;
  /* XXX  3-bit hole   */
  /* XXX  4-byte hole  */
  /*   40      |     8 */        int64_t a5;
  /*   48:27   |     4 */        int a6 : 5;
  /*   48:56   |     8 */        int64_t a7 : 3;

				 /* total size (bytes):   24 */
			     } a1;

			     /* total size (bytes):   56 */
			   }

A big part of this patch handles the formatting logic of 'ptype',
which is a bit messy.  The code to handle bitfield offsets, however,
took some time to craft.  My thanks to Pedro Alves for figuring things
out and pointing me to the right direction, as well as coming up with
a way to inspect the layout of structs with bitfields (see testcase
for comments).

After many discussions both on IRC and at the mailing list, I tried to
implement printing vtables and inherited classes.  Unfortunately the
code grew too complex and there were still a few corner cases failing
so I had to drop the attempt.  This should be implemented in a future
patch.

This patch is the start of a long-term work I'll do to flush the local
patches we carry for Fedora GDB.  In this specific case, I'm aiming at
upstreaming the feature implemented by the 'pahole.py' script that is
shipped with Fedora GDB:

  <https://src.fedoraproject.org/rpms/gdb/blob/master/f/gdb-archer.patch#_311>

This has been regression-tested on the BuildBot.  There's a new
testcase for it, along with an update to the documentation.  I also
thought it was worth mentioning this feature in the NEWS file.

gdb/ChangeLog:
2017-12-15  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Pedro Alves  <palves@redhat.com>

	PR cli/16224
	* NEWS (Changes since GDB 8.0): Mention new '/o' flag.
	* c-typeprint.c (OFFSET_SPC_LEN): New define.
	(c_type_print_varspec_prefix): New argument 'struct
	print_offset_data *'.
	(c_type_print_base_1): New function and prototype.
	(c_print_type_1): New function, with code from 'c_print_type'.
	(c_print_type): Use 'c_print_type_1'.
	(c_type_print_varspec_prefix): New argument 'struct
	print_offset_data *'.  Use it.  Call 'c_type_print_base_1'
	instead of 'c_print_type_base'.
	(print_spaces_filtered_with_print_options): New function.
	(output_access_specifier): Take new argument FLAGS.  Modify
	function to call 'print_spaces_filtered_with_print_options'.
	(c_print_type_vtable_offset_marker): New function.
	(c_print_type_union_field_offset): New function.
	(c_print_type_struct_field_offset): New function.
	(c_print_type_no_offsets): New function.
	(c_type_print_base_struct_union): New argument 'struct
	print_offset_data *'.  Print offsets and sizes for
	struct/union/class fields.
	* typeprint.c (const struct type_print_options
	type_print_raw_options): Initialize 'print_offsets'.
	(static struct type_print_options default_ptype_flags):
	Likewise.
	(struct print_offset_data print_offset_default_data): New
	variable.
	(whatis_exp): Handle '/o' option.
	(_initialize_typeprint): Add '/o' flag to ptype's help.
	* typeprint.h (struct print_offset_data): New struct.
	(struct type_print_options) <print_offsets>: New field.

gdb/testsuite/ChangeLog:
2017-12-15  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.base/ptype-offsets.cc: New file.
	* gdb.base/ptype-offsets.exp: New file.

gdb/doc/ChangeLog:
2017-12-15  Sergio Durigan Junior  <sergiodj@redhat.com>

	PR cli/16224
	* gdb.texinfo (ptype): Add documentation for new flag '/o'.
---
 gdb/NEWS                                 |   3 +
 gdb/c-typeprint.c                        | 391 ++++++++++++++++++++++++++-----
 gdb/doc/gdb.texinfo                      | 124 ++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.cc  | 193 +++++++++++++++
 gdb/testsuite/gdb.base/ptype-offsets.exp | 318 +++++++++++++++++++++++++
 gdb/typeprint.c                          |  24 +-
 gdb/typeprint.h                          |  17 ++
 7 files changed, 1004 insertions(+), 66 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.cc
 create mode 100644 gdb/testsuite/gdb.base/ptype-offsets.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index bd5ae36dd0..3a7e704cc6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 8.0
 
+* The 'ptype' command now accepts a '/o' flag, which prints the
+  offsets and sizes of fields in a struct, like the pahole(1) tool.
+
 * New "--readnever" command line option instructs GDB to not read each
   symbol file's symbolic debug information.  This makes startup faster
   but at the expense of not being able to perform symbolic debugging.
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index 3854886387..c68936d7db 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -32,6 +32,14 @@
 #include "cp-abi.h"
 #include "cp-support.h"
 
+/* When printing the offsets of a struct and its fields (i.e., 'ptype
+   /o'; type_print_options::print_offsets), we use this many
+   characters when printing the offset information at the beginning of
+   the line.  This is needed in order to generate the correct amount
+   of whitespaces when no offset info should be printed for a certain
+   field.  */
+#define OFFSET_SPC_LEN 23
+
 /* A list of access specifiers used for printing.  */
 
 enum access_specifier
@@ -45,12 +53,18 @@ enum access_specifier
 static void c_type_print_varspec_prefix (struct type *,
 					 struct ui_file *,
 					 int, int, int,
-					 const struct type_print_options *);
+					 const struct type_print_options *,
+					 struct print_offset_data *);
 
 /* Print "const", "volatile", or address space modifiers.  */
 static void c_type_print_modifier (struct type *,
 				   struct ui_file *,
 				   int, int);
+
+static void c_type_print_base_1 (struct type *type, struct ui_file *stream,
+				 int show, int level,
+				 const struct type_print_options *flags,
+				 struct print_offset_data *podata);
 \f
 
 /* A callback function for cp_canonicalize_string_full that uses
@@ -82,14 +96,15 @@ print_name_maybe_canonical (const char *name,
 
 \f
 
-/* LEVEL is the depth to indent lines by.  */
+/* Helper function for c_print_type.  */
 
-void
-c_print_type (struct type *type,
-	      const char *varstring,
-	      struct ui_file *stream,
-	      int show, int level,
-	      const struct type_print_options *flags)
+static void
+c_print_type_1 (struct type *type,
+		const char *varstring,
+		struct ui_file *stream,
+		int show, int level,
+		const struct type_print_options *flags,
+		struct print_offset_data *podata)
 {
   enum type_code code;
   int demangled_args;
@@ -108,7 +123,7 @@ c_print_type (struct type *type,
     }
   else
     {
-      c_type_print_base (type, stream, show, level, flags);
+      c_type_print_base_1 (type, stream, show, level, flags, podata);
       code = TYPE_CODE (type);
       if ((varstring != NULL && *varstring != '\0')
 	  /* Need a space if going to print stars or brackets;
@@ -124,7 +139,7 @@ c_print_type (struct type *type,
 	fputs_filtered (" ", stream);
       need_post_space = (varstring != NULL && strcmp (varstring, "") != 0);
       c_type_print_varspec_prefix (type, stream, show, 0, need_post_space,
-				   flags);
+				   flags, podata);
     }
 
   if (varstring != NULL)
@@ -143,6 +158,20 @@ c_print_type (struct type *type,
     }
 }
 
+/* LEVEL is the depth to indent lines by.  */
+
+void
+c_print_type (struct type *type,
+	      const char *varstring,
+	      struct ui_file *stream,
+	      int show, int level,
+	      const struct type_print_options *flags)
+{
+  struct print_offset_data podata;
+
+  c_print_type_1 (type, varstring, stream, show, level, flags, &podata);
+}
+
 /* Print a typedef using C syntax.  TYPE is the underlying type.
    NEW_SYMBOL is the symbol naming the type.  STREAM is the stream on
    which to print.  */
@@ -310,7 +339,8 @@ c_type_print_varspec_prefix (struct type *type,
 			     struct ui_file *stream,
 			     int show, int passed_a_ptr,
 			     int need_post_space,
-			     const struct type_print_options *flags)
+			     const struct type_print_options *flags,
+			     struct print_offset_data *podata)
 {
   const char *name;
 
@@ -326,40 +356,40 @@ c_type_print_varspec_prefix (struct type *type,
     {
     case TYPE_CODE_PTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 1, 1, flags);
+				   stream, show, 1, 1, flags, podata);
       fprintf_filtered (stream, "*");
       c_type_print_modifier (type, stream, 1, need_post_space);
       break;
 
     case TYPE_CODE_MEMBERPTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       name = type_name_no_tag (TYPE_SELF_TYPE (type));
       if (name)
 	print_name_maybe_canonical (name, flags, stream);
       else
-	c_type_print_base (TYPE_SELF_TYPE (type),
-			   stream, -1, passed_a_ptr, flags);
+	c_type_print_base_1 (TYPE_SELF_TYPE (type),
+			     stream, -1, passed_a_ptr, flags, podata);
       fprintf_filtered (stream, "::*");
       break;
 
     case TYPE_CODE_METHODPTR:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       fprintf_filtered (stream, "(");
       name = type_name_no_tag (TYPE_SELF_TYPE (type));
       if (name)
 	print_name_maybe_canonical (name, flags, stream);
       else
-	c_type_print_base (TYPE_SELF_TYPE (type),
-			   stream, -1, passed_a_ptr, flags);
+	c_type_print_base_1 (TYPE_SELF_TYPE (type),
+			     stream, -1, passed_a_ptr, flags, podata);
       fprintf_filtered (stream, "::*");
       break;
 
     case TYPE_CODE_REF:
     case TYPE_CODE_RVALUE_REF:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 1, 0, flags);
+				   stream, show, 1, 0, flags, podata);
       fprintf_filtered (stream, TYPE_CODE(type) == TYPE_CODE_REF ? "&" : "&&");
       c_type_print_modifier (type, stream, 1, need_post_space);
       break;
@@ -367,21 +397,22 @@ c_type_print_varspec_prefix (struct type *type,
     case TYPE_CODE_METHOD:
     case TYPE_CODE_FUNC:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       if (passed_a_ptr)
 	fprintf_filtered (stream, "(");
       break;
 
     case TYPE_CODE_ARRAY:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, 0, 0, flags);
+				   stream, show, 0, 0, flags, podata);
       if (passed_a_ptr)
 	fprintf_filtered (stream, "(");
       break;
 
     case TYPE_CODE_TYPEDEF:
       c_type_print_varspec_prefix (TYPE_TARGET_TYPE (type),
-				   stream, show, passed_a_ptr, 0, flags);
+				   stream, show, passed_a_ptr, 0, flags,
+				   podata);
       break;
 
     case TYPE_CODE_UNDEF:
@@ -836,21 +867,36 @@ c_type_print_template_args (const struct type_print_options *flags,
     fputs_filtered (_("] "), stream);
 }
 
+/* Use 'print_spaces_filtered', but take into consideration the
+   type_print_options FLAGS in order to determine how many whitespaces
+   will be printed.  */
+
+static void
+print_spaces_filtered_with_print_options
+  (int level, struct ui_file *stream, const struct type_print_options *flags)
+{
+  if (!flags->print_offsets)
+    print_spaces_filtered (level, stream);
+  else
+    print_spaces_filtered (level + OFFSET_SPC_LEN, stream);
+}
+
 /* Output an access specifier to STREAM, if needed.  LAST_ACCESS is the
    last access specifier output (typically returned by this function).  */
 
 static enum access_specifier
 output_access_specifier (struct ui_file *stream,
 			 enum access_specifier last_access,
-			 int level, bool is_protected, bool is_private)
+			 int level, bool is_protected, bool is_private,
+			 const struct type_print_options *flags)
 {
   if (is_protected)
     {
       if (last_access != s_protected)
 	{
 	  last_access = s_protected;
-	  fprintfi_filtered (level + 2, stream,
-			     "protected:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "protected:\n");
 	}
     }
   else if (is_private)
@@ -858,8 +904,8 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_private)
 	{
 	  last_access = s_private;
-	  fprintfi_filtered (level + 2, stream,
-			     "private:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "private:\n");
 	}
     }
   else
@@ -867,15 +913,121 @@ output_access_specifier (struct ui_file *stream,
       if (last_access != s_public)
 	{
 	  last_access = s_public;
-	  fprintfi_filtered (level + 2, stream,
-			     "public:\n");
+	  print_spaces_filtered_with_print_options (level + 2, stream, flags);
+	  fprintf_filtered (stream, "public:\n");
 	}
     }
 
   return last_access;
 }
 
-/* Return true is an access label (i.e., "public:", "private:",
+/* Print information about field at index FIELD_IDX of the union type
+   TYPE.  Since union fields don't have the concept of offsets, we
+   just print their sizes.
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_union_field_offset (struct type *type, unsigned int field_idx,
+				 struct ui_file *stream)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+
+  fprintf_filtered (stream, "/*              %4u */", TYPE_LENGTH (ftype));
+}
+
+/* Print information about field at index FIELD_IDX of the struct type
+   TYPE.
+
+   PODATA->END_BITPOS is the one-past-the-end bit position of the
+   previous field (where we expect this field to be if there is no
+   hole).  At the end, ENDPOS is updated to the one-past-the-end bit
+   position of the current field.
+
+   PODATA->OFFSET_BITPOS is the offset value we carry over when we are
+   printing a struct that is inside another struct; this is useful so
+   that the offset is constantly incremented (if we didn't carry it
+   over, the offset would be reset to zero when printing the inner
+   struct).
+
+   The output is strongly based on pahole(1).  */
+
+static void
+c_print_type_struct_field_offset (struct type *type, unsigned int field_idx,
+				  struct ui_file *stream,
+				  struct print_offset_data *podata)
+{
+  struct type *ftype = check_typedef (TYPE_FIELD_TYPE (type, field_idx));
+  unsigned int bitpos = TYPE_FIELD_BITPOS (type, field_idx);
+  unsigned int fieldsize_byte = TYPE_LENGTH (ftype);
+  unsigned int fieldsize_bit = fieldsize_byte * TARGET_CHAR_BIT;
+
+  /* We check for PODATA->END_BITPOS > 0 because there is a specific
+     scenario when PODATA->END_BITPOS can be zero and BITPOS can be >
+     0: when we are dealing with a struct/class with a virtual method.
+     Because of the vtable, the first field of the struct/class will
+     have an offset of sizeof (void *) (the size of the vtable).  If
+     we do not check for PODATA->END_BITPOS > 0 here, GDB will report
+     a hole before the first field, which is not accurate.  */
+  if (podata->end_bitpos > 0 && podata->end_bitpos < bitpos)
+    {
+      /* If PODATA->END_BITPOS is smaller than the current type's
+	 bitpos, it means there's a hole in the struct, so we report
+	 it here.  */
+      unsigned int hole = bitpos - podata->end_bitpos;
+      unsigned int hole_byte = hole / TARGET_CHAR_BIT;
+      unsigned int hole_bit = hole % TARGET_CHAR_BIT;
+
+      if (hole_bit > 0)
+	fprintf_filtered (stream, "/* XXX %2u-bit hole   */\n", hole_bit);
+
+      if (hole_byte > 0)
+	fprintf_filtered (stream, "/* XXX %2u-byte hole  */\n", hole_byte);
+    }
+
+  if (TYPE_FIELD_PACKED (type, field_idx))
+    {
+      /* We're dealing with a bitfield.  Print how many bits are left
+	 to be used.  */
+      unsigned int bitsize = TYPE_FIELD_BITSIZE (type, field_idx);
+      /* The bitpos relative to the beginning of our container
+	 field.  */
+      unsigned int relative_bitpos;
+
+      /* The following was copied from
+	 value.c:value_primitive_field.  */
+      if ((bitpos % fieldsize_bit) + bitsize <= fieldsize_bit)
+	relative_bitpos = bitpos % fieldsize_bit;
+      else
+	relative_bitpos = bitpos % TARGET_CHAR_BIT;
+
+      /* This is the exact offset (in bits) of this bitfield.  */
+      unsigned int bit_offset
+	= (bitpos - relative_bitpos) + podata->offset_bitpos;
+
+      /* The position of the field, relative to the beginning of the
+	 struct, and how many bits are left to be used in this
+	 container.  */
+      fprintf_filtered (stream, "/* %4u:%2u", bit_offset / TARGET_CHAR_BIT,
+			fieldsize_bit - (relative_bitpos + bitsize));
+      fieldsize_bit = bitsize;
+    }
+  else
+    {
+      /* The position of the field, relative to the beginning of the
+	 struct.  */
+      fprintf_filtered (stream, "/* %4u",
+			(bitpos + podata->offset_bitpos) / TARGET_CHAR_BIT);
+
+      fprintf_filtered (stream, "   ");
+    }
+
+  fprintf_filtered (stream, "   |  %4u */", fieldsize_byte);
+
+  podata->end_bitpos = bitpos + fieldsize_bit;
+}
+
+/* Return true if an access label (i.e., "public:", "private:",
    "protected:") needs to be printed for TYPE.  */
 
 static bool
@@ -926,13 +1078,35 @@ need_access_label_p (struct type *type)
   return false;
 }
 
+/* Helper function that temporarily disables FLAGS->PRINT_OFFSETS,
+   calls 'c_print_type_1', and then reenables FLAGS->PRINT_OFFSETS if
+   applicable.  */
+
+static void
+c_print_type_no_offsets (struct type *type,
+			 const char *varstring,
+			 struct ui_file *stream,
+			 int show, int level,
+			 struct type_print_options *flags,
+			 struct print_offset_data *podata)
+{
+  unsigned int old_po = flags->print_offsets;
+
+  /* Temporarily disable print_offsets, because it would mess with
+     indentation.  */
+  flags->print_offsets = 0;
+  c_print_type_1 (type, varstring, stream, show, level, flags, podata);
+  flags->print_offsets = old_po;
+}
+
 /* Helper for 'c_type_print_base' that handles structs and unions.
    For a description of the arguments, see 'c_type_print_base'.  */
 
 static void
 c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 				int show, int level,
-				const struct type_print_options *flags)
+				const struct type_print_options *flags,
+				struct print_offset_data *podata)
 {
   struct type_print_options local_flags = *flags;
   struct type_print_options semi_local_flags = *flags;
@@ -1004,6 +1178,7 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
       recursively_update_typedef_hash (local_flags.local_typedefs, type);
 
       fprintf_filtered (stream, "{\n");
+
       if (TYPE_NFIELDS (type) == 0 && TYPE_NFN_FIELDS (type) == 0
 	  && TYPE_TYPEDEF_FIELD_COUNT (type) == 0)
 	{
@@ -1032,6 +1207,9 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
       int len = TYPE_NFIELDS (type);
       vptr_fieldno = get_vptr_fieldno (type, &basetype);
+
+      struct print_offset_data local_podata;
+
       for (int i = TYPE_N_BASECLASSES (type); i < len; i++)
 	{
 	  QUIT;
@@ -1048,18 +1226,64 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FIELD_PROTECTED (type, i),
-		 TYPE_FIELD_PRIVATE (type, i));
+		 TYPE_FIELD_PRIVATE (type, i), flags);
+	    }
+
+	  bool is_static = field_is_static (&TYPE_FIELD (type, i));
+
+	  if (flags->print_offsets)
+	    {
+	      if (!is_static)
+		{
+		  if (TYPE_CODE (type) == TYPE_CODE_STRUCT)
+		    {
+		      c_print_type_struct_field_offset
+			(type, i, stream, podata);
+		    }
+		  else if (TYPE_CODE (type) == TYPE_CODE_UNION)
+		    c_print_type_union_field_offset (type, i, stream);
+		}
+	      else
+		print_spaces_filtered (OFFSET_SPC_LEN, stream);
 	    }
 
 	  print_spaces_filtered (level + 4, stream);
-	  if (field_is_static (&TYPE_FIELD (type, i)))
+	  if (is_static)
 	    fprintf_filtered (stream, "static ");
-	  c_print_type (TYPE_FIELD_TYPE (type, i),
-			TYPE_FIELD_NAME (type, i),
-			stream, show - 1, level + 4,
-			&local_flags);
-	  if (!field_is_static (&TYPE_FIELD (type, i))
-	      && TYPE_FIELD_PACKED (type, i))
+
+	  int newshow = show - 1;
+
+	  if (flags->print_offsets
+	      && (TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_STRUCT
+		  || TYPE_CODE (TYPE_FIELD_TYPE (type, i)) == TYPE_CODE_UNION))
+	    {
+	      /* If we're printing offsets and this field's type is
+		 either a struct or an union, then we're interested in
+		 expanding it.  */
+	      ++newshow;
+
+	      /* Make sure we carry our offset when we expand the
+		 struct/union.  */
+	      local_podata.offset_bitpos
+		= podata->offset_bitpos + TYPE_FIELD_BITPOS (type, i);
+	      /* We're entering a struct/union.  Right now,
+		 PODATA->END_BITPOS points right *after* the
+		 struct/union.  However, when printing the first field
+		 of this inner struct/union, the end_bitpos we're
+		 expecting is exactly at the beginning of the
+		 struct/union.  Therefore, we subtract the length of
+		 the whole struct/union.  */
+	      local_podata.end_bitpos
+		= podata->end_bitpos
+		  - TYPE_LENGTH (TYPE_FIELD_TYPE (type, i)) * TARGET_CHAR_BIT;
+	    }
+
+	  c_print_type_1 (TYPE_FIELD_TYPE (type, i),
+			  TYPE_FIELD_NAME (type, i),
+			  stream, newshow, level + 4,
+			  &local_flags, &local_podata);
+
+	  if (!is_static && TYPE_FIELD_PACKED (type, i))
 	    {
 	      /* It is a bitfield.  This code does not attempt
 		 to look at the bitpos and reconstruct filler,
@@ -1122,9 +1346,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 	      section_type = output_access_specifier
 		(stream, section_type, level,
 		 TYPE_FN_FIELD_PROTECTED (f, j),
-		 TYPE_FN_FIELD_PRIVATE (f, j));
+		 TYPE_FN_FIELD_PRIVATE (f, j), flags);
 
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      if (TYPE_FN_FIELD_VIRTUAL_P (f, j))
 		fprintf_filtered (stream, "virtual ");
 	      else if (TYPE_FN_FIELD_STATIC_P (f, j))
@@ -1143,9 +1368,10 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		       && !is_full_physname_constructor  /* " " */
 		       && !is_type_conversion_operator (type, i, j))
 		{
-		  c_print_type (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
-				"", stream, -1, 0,
-				&local_flags);
+		  c_print_type_no_offsets
+		    (TYPE_TARGET_TYPE (TYPE_FN_FIELD_TYPE (f, j)),
+		     "", stream, -1, 0, &local_flags, podata);
+
 		  fputs_filtered (" ", stream);
 		}
 	      if (TYPE_FN_FIELD_STUB (f, j))
@@ -1226,9 +1452,11 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 
 	  for (int i = 0; i < TYPE_NESTED_TYPES_COUNT (type); ++i)
 	    {
-	      print_spaces_filtered (level + 4, stream);
-	      c_print_type (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
-			    "", stream, show, level + 4, &semi_local_flags);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
+	      c_print_type_no_offsets (TYPE_NESTED_TYPES_FIELD_TYPE (type, i),
+				       "", stream, show, level + 4,
+				       &semi_local_flags, podata);
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
@@ -1254,22 +1482,40 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
 		  section_type = output_access_specifier
 		    (stream, section_type, level,
 		     TYPE_TYPEDEF_FIELD_PROTECTED (type, i),
-		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i));
+		     TYPE_TYPEDEF_FIELD_PRIVATE (type, i), flags);
 		}
-	      print_spaces_filtered (level + 4, stream);
+	      print_spaces_filtered_with_print_options (level + 4, stream,
+							flags);
 	      fprintf_filtered (stream, "typedef ");
 
 	      /* We want to print typedefs with substitutions
 		 from the template parameters or globally-known
 		 typedefs but not local typedefs.  */
-	      c_print_type (target,
-			    TYPE_TYPEDEF_FIELD_NAME (type, i),
-			    stream, show - 1, level + 4,
-			    &semi_local_flags);
+	      c_print_type_no_offsets (target,
+				       TYPE_TYPEDEF_FIELD_NAME (type, i),
+				       stream, show - 1, level + 4,
+				       &semi_local_flags, podata);
 	      fprintf_filtered (stream, ";\n");
 	    }
 	}
 
+      if (flags->print_offsets)
+	{
+	  if (show > 0)
+	    {
+	      fputs_filtered ("\n", stream);
+	      print_spaces_filtered_with_print_options (level + 4,
+							stream,
+							flags);
+	      fprintf_filtered (stream, "/* total size (bytes): %4u */\n",
+				TYPE_LENGTH (type));
+	    }
+
+	  print_spaces_filtered (OFFSET_SPC_LEN, stream);
+	  if (level == 0)
+	    print_spaces_filtered (2, stream);
+	}
+
       fprintfi_filtered (level, stream, "}");
     }
 
@@ -1294,9 +1540,11 @@ c_type_print_base_struct_union (struct type *type, struct ui_file *stream,
    LEVEL is the number of spaces to indent by.
    We increase it for some recursive calls.  */
 
-void
-c_type_print_base (struct type *type, struct ui_file *stream,
-		   int show, int level, const struct type_print_options *flags)
+static void
+c_type_print_base_1 (struct type *type, struct ui_file *stream,
+		     int show, int level,
+		     const struct type_print_options *flags,
+		     struct print_offset_data *podata)
 {
   int i;
   int len;
@@ -1341,8 +1589,8 @@ c_type_print_base (struct type *type, struct ui_file *stream,
       if (TYPE_TARGET_TYPE (type) == NULL)
 	type_print_unknown_return_type (stream);
       else
-	c_type_print_base (TYPE_TARGET_TYPE (type),
-			   stream, show, level, flags);
+	c_type_print_base_1 (TYPE_TARGET_TYPE (type),
+			     stream, show, level, flags, podata);
       break;
     case TYPE_CODE_ARRAY:
     case TYPE_CODE_PTR:
@@ -1350,13 +1598,14 @@ c_type_print_base (struct type *type, struct ui_file *stream,
     case TYPE_CODE_REF:
     case TYPE_CODE_RVALUE_REF:
     case TYPE_CODE_METHODPTR:
-      c_type_print_base (TYPE_TARGET_TYPE (type),
-			 stream, show, level, flags);
+      c_type_print_base_1 (TYPE_TARGET_TYPE (type),
+			   stream, show, level, flags, podata);
       break;
 
     case TYPE_CODE_STRUCT:
     case TYPE_CODE_UNION:
-      c_type_print_base_struct_union (type, stream, show, level, flags);
+      c_type_print_base_struct_union (type, stream, show, level, flags,
+				      podata);
       break;
 
     case TYPE_CODE_ENUM:
@@ -1456,10 +1705,10 @@ c_type_print_base (struct type *type, struct ui_file *stream,
 		print_spaces_filtered (level + 4, stream);
 		/* We pass "show" here and not "show - 1" to get enum types
 		   printed.  There's no other way to see them.  */
-		c_print_type (TYPE_FIELD_TYPE (type, i),
-			      TYPE_FIELD_NAME (type, i),
-			      stream, show, level + 4,
-			      &local_flags);
+		c_print_type_1 (TYPE_FIELD_TYPE (type, i),
+				TYPE_FIELD_NAME (type, i),
+				stream, show, level + 4,
+				&local_flags, podata);
 		fprintf_filtered (stream, " @%s",
 				  plongest (TYPE_FIELD_BITPOS (type, i)));
 		if (TYPE_FIELD_BITSIZE (type, i) > 1)
@@ -1518,3 +1767,15 @@ c_type_print_base (struct type *type, struct ui_file *stream,
       break;
     }
 }
+
+/* See c_type_print_base_1.  */
+
+void
+c_type_print_base (struct type *type, struct ui_file *stream,
+		   int show, int level,
+		   const struct type_print_options *flags)
+{
+  struct print_offset_data podata;
+
+  c_type_print_base_1 (type, stream, show, level, flags, &podata);
+}
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index da3ed28dfe..60ed80c363 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17216,6 +17216,130 @@ names are substituted when printing other types.
 @item T
 Print typedefs defined in the class.  This is the default, but the flag
 exists in case you change the default with @command{set print type typedefs}.
+
+@item o
+Print the offsets and sizes of fields in a struct, similar to what the
+@command{pahole} tool does.  This option implies the @code{/tm} flags.
+
+For example, given the following declarations:
+
+@smallexample
+struct tuv
+@{
+  int a1;
+  char *a2;
+  int a3;
+@};
+
+struct xyz
+@{
+  int f1;
+  char f2;
+  void *f3;
+  struct tuv f4;
+@};
+
+union qwe
+@{
+  struct tuv fff1;
+  struct xyz fff2;
+@};
+
+struct tyu
+@{
+  int a1 : 1;
+  int a2 : 3;
+  int a3 : 23;
+  char a4 : 2;
+  int64_t a5;
+  int a6 : 5;
+  int64_t a7 : 3;
+@};
+@end smallexample
+
+Issuing a @kbd{ptype /o struct tuv} command would print:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tuv
+/* offset    |  size */  type = struct tuv @{
+/*    0      |     4 */    int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */    char *a2;
+/*   16      |     4 */    int a3;
+
+                           /* total size (bytes):   24 */
+                         @}
+@end smallexample
+
+Notice the format of the first column of comments.  There, you can
+find two parts separated by the @samp{|} character: the @emph{offset},
+which indicates where the field is located inside the struct, in
+bytes, and the @emph{size} of the field.  Another interesting line is
+the marker of a @emph{hole} in the struct, indicating that it may be
+possible to pack the struct and make it use less space by reorganizing
+its fields.
+
+It is also possible to print offsets inside an union:
+
+@smallexample
+(@value{GDBP}) ptype /o union qwe
+/* offset    |  size */  type = union qwe @{
+/*                24 */    struct tuv @{
+/*    0      |     4 */        int a1;
+/* XXX  4-byte hole  */
+/*    8      |     8 */        char *a2;
+/*   16      |     4 */        int a3;
+
+                               /* total size (bytes):   24 */
+                           @} fff1;
+/*                40 */    struct xyz @{
+/*    0      |     4 */        int f1;
+/*    4      |     1 */        char f2;
+/* XXX  3-byte hole  */
+/*    8      |     8 */        void *f3;
+/*   16      |    24 */        struct tuv @{
+/*   16      |     4 */            int a1;
+/* XXX  4-byte hole  */
+/*   24      |     8 */            char *a2;
+/*   32      |     4 */            int a3;
+
+                                   /* total size (bytes):   24 */
+                               @} f4;
+
+                               /* total size (bytes):   40 */
+                           @} fff2;
+
+                           /* total size (bytes):   40 */
+                         @}
+@end smallexample
+
+In this case, since @code{struct tuv} and @code{struct xyz} occupy the
+same space (because we are dealing with an union), the offset is not
+printed for them.  However, you can still examine the offset of each
+of these structures' fields.
+
+Another useful scenario is printing the offsets of a struct containing
+bitfields:
+
+@smallexample
+(@value{GDBP}) ptype /o struct tyu
+/* offset    |  size */  type = struct tyu @{
+/*    0:31   |     4 */    int a1 : 1;
+/*    0:28   |     4 */    int a2 : 3;
+/*    0: 5   |     4 */    int a3 : 23;
+/*    3: 3   |     1 */    signed char a4 : 2;
+/* XXX  3-bit hole   */
+/* XXX  4-byte hole  */
+/*    8      |     8 */    int64_t a5;
+/*   16:27   |     4 */    int a6 : 5;
+/*   16:56   |     8 */    int64_t a7 : 3;
+
+                           /* total size (bytes):   24 */
+                         @}
+@end smallexample
+
+Note how the offset information is now extended to also include how
+many bits are left to be used in each bitfield.
 @end table
 
 @kindex ptype
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.cc b/gdb/testsuite/gdb.base/ptype-offsets.cc
new file mode 100644
index 0000000000..86735203ad
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.cc
@@ -0,0 +1,193 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2017 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 <stdint.h>
+
+/* A struct with many types of fields, in order to test 'ptype
+   /o'.  */
+
+struct abc
+{
+  /* Virtual destructor.  */
+  virtual ~abc ()
+  {}
+
+  /* 8-byte address.  Because of the virtual destructor above, this
+     field's offset will be 8.  */
+  void *field1;
+
+  /* No hole here.  */
+
+  /* 4-byte int bitfield of 1-bit.  */
+  unsigned int field2 : 1;
+
+  /* 31-bit hole here.  */
+
+  /* 4-byte int.  */
+  signed int field3;
+
+  /* No hole here.  */
+
+  /* 1-byte char.  */
+  signed char field4;
+
+  /* 7-byte hole here.  */
+
+  /* 8-byte int.  */
+  uint64_t field5;
+
+  /* We just print the offset and size of a union, ignoring its
+     fields.  */
+  union
+  {
+    /* 8-byte address.  */
+    void *field6;
+
+    /* 4-byte int.  */
+    signed int field7;
+  } field8;
+
+  /* Empty constructor.  */
+  abc ()
+  {}
+
+  /* Typedef defined in-struct.  */
+  typedef int my_int_type;
+
+  my_int_type field9;
+};
+
+/* This struct will be nested inside 'struct xyz'.  */
+
+struct tuv
+{
+  signed int a1;
+
+  signed char *a2;
+
+  signed int a3;
+};
+
+/* This struct will be nested inside 'struct pqr'.  */
+
+struct xyz
+{
+  signed int f1;
+
+  signed char f2;
+
+  void *f3;
+
+  struct tuv f4;
+};
+
+/* A struct with a nested struct.  */
+
+struct pqr
+{
+  signed int ff1;
+
+  struct xyz ff2;
+
+  signed char ff3;
+};
+
+/* A union with two nested structs.  */
+
+union qwe
+{
+  struct tuv fff1;
+
+  struct xyz fff2;
+};
+
+/* A struct with an union.  */
+
+struct poi
+{
+  signed int f1;
+
+  union qwe f2;
+
+  uint16_t f3;
+
+  struct pqr f4;
+};
+
+/* A struct with bitfields.  */
+
+struct tyu
+{
+  signed int a1 : 1;
+
+  signed int a2 : 3;
+
+  signed int a3 : 23;
+
+  signed char a4 : 2;
+
+  int64_t a5;
+
+  signed int a6 : 5;
+
+  int64_t a7 : 3;
+};
+
+/* A struct with structs and unions.  */
+
+struct asd
+{
+  struct jkl
+  {
+      signed char *f1;
+      union
+      {
+	void *ff1;
+      } f2;
+      union
+      {
+	signed char *ff2;
+      } f3;
+      int f4 : 5;
+      unsigned int f5 : 1;
+      short f6;
+  } f7;
+  unsigned long f8;
+  signed char *f9;
+  int f10 : 4;
+  unsigned int f11 : 1;
+  unsigned int f12 : 1;
+  unsigned int f13 : 1;
+  unsigned int f14 : 1;
+  void *f15;
+  void *f16;
+};
+
+
+int
+main (int argc, char *argv[])
+{
+  struct abc foo;
+  struct pqr bar;
+  union qwe c;
+  struct poi d;
+  struct tyu e;
+  struct asd f;
+  uint8_t i;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/ptype-offsets.exp b/gdb/testsuite/gdb.base/ptype-offsets.exp
new file mode 100644
index 0000000000..55b1eb2b1a
--- /dev/null
+++ b/gdb/testsuite/gdb.base/ptype-offsets.exp
@@ -0,0 +1,318 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2017 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 testcase exercises the "ptype /o" feature, which can be used to
+# print the offsets and sizes of each field of a struct/union/class.
+
+standard_testfile .cc
+
+# Test only works on LP64 targets.  That's how we guarantee that the
+# expected holes will be present in the struct.
+if { ![is_lp64_target] } {
+    untested "test work only on lp64 targets"
+    return 0
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  { debug c++ }] } {
+    return -1
+}
+
+# Test general offset printing, ctor/dtor printing, union, formatting.
+gdb_test "ptype /o struct abc" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct abc \{} \
+{                         public:} \
+{/\*    8      |     8 \*/    void \*field1;} \
+{/\*   16:31   |     4 \*/    unsigned int field2 : 1;} \
+{/\* XXX  7-bit hole   \*/} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   20      |     4 \*/    int field3;} \
+{/\*   24      |     1 \*/    signed char field4;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   32      |     8 \*/    uint64_t field5;} \
+{/\*   40      |     8 \*/    union \{} \
+{/\*                 8 \*/        void \*field6;} \
+{/\*                 4 \*/        int field7;} \
+{} \
+{                               /\* total size \(bytes\):    8 \*/} \
+{                           \} field8;} \
+{/\*   48      |     4 \*/    my_int_type field9;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}]
+
+# Test "ptype /oTM".
+gdb_test "ptype /oTM struct abc" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct abc \{} \
+{                         public:} \
+{/\*    8      |     8 \*/    void \*field1;} \
+{/\*   16:31   |     4 \*/    unsigned int field2 : 1;} \
+{/\* XXX  7-bit hole   \*/} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   20      |     4 \*/    int field3;} \
+{/\*   24      |     1 \*/    signed char field4;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   32      |     8 \*/    uint64_t field5;} \
+{/\*   40      |     8 \*/    union \{} \
+{/\*                 8 \*/        void \*field6;} \
+{/\*                 4 \*/        int field7;} \
+{} \
+{                               /\* total size \(bytes\):    8 \*/} \
+{                           \} field8;} \
+{/\*   48      |     4 \*/    my_int_type field9;} \
+{} \
+{                           abc\(void\);} \
+{                           ~abc\(\);} \
+{} \
+{                           typedef int my_int_type;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}]
+
+# Test "ptype /TMo".  This should be the same as "ptype /o".
+gdb_test "ptype /TMo struct abc" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct abc \{} \
+{                         public:} \
+{/\*    8      |     8 \*/    void \*field1;} \
+{/\*   16:31   |     4 \*/    unsigned int field2 : 1;} \
+{/\* XXX  7-bit hole   \*/} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   20      |     4 \*/    int field3;} \
+{/\*   24      |     1 \*/    signed char field4;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   32      |     8 \*/    uint64_t field5;} \
+{/\*   40      |     8 \*/    union \{} \
+{/\*                 8 \*/        void \*field6;} \
+{/\*                 4 \*/        int field7;} \
+{} \
+{                               /\* total size \(bytes\):    8 \*/} \
+{                           \} field8;} \
+{/\*   48      |     4 \*/    my_int_type field9;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}]
+
+# Test nested structs.
+gdb_test "ptype /o struct pqr" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct pqr \{} \
+{/\*    0      |     4 \*/    int ff1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |    40 \*/    struct xyz \{} \
+{/\*    8      |     4 \*/        int f1;} \
+{/\*   12      |     1 \*/        signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   16      |     8 \*/        void \*f3;} \
+{/\*   24      |    24 \*/        struct tuv \{} \
+{/\*   24      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   32      |     8 \*/            signed char \*a2;} \
+{/\*   40      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} f4;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} ff2;} \
+{/\* XXX 28-byte hole  \*/} \
+{/\*   72      |     1 \*/    signed char ff3;} \
+{} \
+{                           /\* total size \(bytes\):   56 \*/} \
+{                         \}}]
+
+# Test that the offset is properly reset when we are printing a union
+# and go inside two inner structs.
+# This also tests a struct inside a struct inside a union.
+gdb_test "ptype /o union qwe" \
+    [multi_line \
+{/\* offset    |  size \*/  type = union qwe \{} \
+{/\*                24 \*/    struct tuv \{} \
+{/\*    0      |     4 \*/        int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |     8 \*/        signed char \*a2;} \
+{/\*   16      |     4 \*/        int a3;} \
+{} \
+{                               /\* total size \(bytes\):   24 \*/} \
+{                           \} fff1;} \
+{/\*                40 \*/    struct xyz \{} \
+{/\*    0      |     4 \*/        int f1;} \
+{/\*    4      |     1 \*/        signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*    8      |     8 \*/        void \*f3;} \
+{/\*   16      |    24 \*/        struct tuv \{} \
+{/\*   16      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   24      |     8 \*/            signed char \*a2;} \
+{/\*   32      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} f4;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} fff2;} \
+{} \
+{                           /\* total size \(bytes\):   40 \*/} \
+{                         \}}]
+
+# Test printing a struct that contains a union, and that also
+# contains a struct.
+gdb_test "ptype /o struct poi" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct poi \{} \
+{/\*    0      |     4 \*/    int f1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |    40 \*/    union qwe \{} \
+{/\*                24 \*/        struct tuv \{} \
+{/\*    8      |     4 \*/            int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   16      |     8 \*/            signed char \*a2;} \
+{/\*   24      |     4 \*/            int a3;} \
+{} \
+{                                   /\* total size \(bytes\):   24 \*/} \
+{                               \} fff1;} \
+{/\*                40 \*/        struct xyz \{} \
+{/\*    8      |     4 \*/            int f1;} \
+{/\*   12      |     1 \*/            signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   16      |     8 \*/            void \*f3;} \
+{/\*   24      |    24 \*/            struct tuv \{} \
+{/\*   24      |     4 \*/                int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   32      |     8 \*/                signed char \*a2;} \
+{/\*   40      |     4 \*/                int a3;} \
+{} \
+{                                       /\* total size \(bytes\):   24 \*/} \
+{                                   \} f4;} \
+{} \
+{                                   /\* total size \(bytes\):   40 \*/} \
+{                               \} fff2;} \
+{} \
+{                               /\* total size \(bytes\):   40 \*/} \
+{                           \} f2;} \
+{/\*   72      |     2 \*/    uint16_t f3;} \
+{/\* XXX  6-byte hole  \*/} \
+{/\*   80      |    56 \*/    struct pqr \{} \
+{/\*   80      |     4 \*/        int ff1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*   88      |    40 \*/        struct xyz \{} \
+{/\*   88      |     4 \*/            int f1;} \
+{/\*   92      |     1 \*/            signed char f2;} \
+{/\* XXX  3-byte hole  \*/} \
+{/\*   96      |     8 \*/            void \*f3;} \
+{/\*  104      |    24 \*/            struct tuv \{} \
+{/\*  104      |     4 \*/                int a1;} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*  112      |     8 \*/                signed char \*a2;} \
+{/\*  120      |     4 \*/                int a3;} \
+{} \
+{                                       /\* total size \(bytes\):   24 \*/} \
+{                                   \} f4;} \
+{} \
+{                                   /\* total size \(bytes\):   40 \*/} \
+{                               \} ff2;} \
+{/\*  152      |     1 \*/        signed char ff3;} \
+{} \
+{                               /\* total size \(bytes\):   56 \*/} \
+{                           \} f4;} \
+{} \
+{                           /\* total size \(bytes\):  112 \*/} \
+{                         \}}]
+
+# Test printing a struct with several bitfields, laid out in various
+# ways.
+#
+# Because dealing with bitfields and offsets is difficult, it can be
+# tricky to confirm that the output of this command is accurate.  A
+# nice way to do that is to use GDB's "x" command and print the actual
+# memory layout of the struct.  In order to differentiate between
+# bitfields and non-bitfield variables, one can assign "-1" to every
+# bitfield in the struct.  An example of the output of "x" using
+# "struct tyu" is:
+#
+#   (gdb) x/24xb &e
+#   0x7fffffffd540: 0xff    0xff    0xff    0x1f    0x00    0x00    0x00    0x00
+#   0x7fffffffd548: 0xff    0xff    0xff    0xff    0xff    0xff    0xff    0xff
+#   0x7fffffffd550: 0xff    0x00    0x00    0x00    0x00    0x00    0x00    0x00
+gdb_test "ptype /o struct tyu" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct tyu \{} \
+{/\*    0:31   |     4 \*/    int a1 : 1;} \
+{/\*    0:28   |     4 \*/    int a2 : 3;} \
+{/\*    0: 5   |     4 \*/    int a3 : 23;} \
+{/\*    3: 3   |     1 \*/    signed char a4 : 2;} \
+{/\* XXX  3-bit hole   \*/} \
+{/\* XXX  4-byte hole  \*/} \
+{/\*    8      |     8 \*/    int64_t a5;} \
+{/\*   16:27   |     4 \*/    int a6 : 5;} \
+{/\*   16:56   |     8 \*/    int64_t a7 : 3;} \
+{} \
+{                           /\* total size \(bytes\):   24 \*/} \
+{                         \}}]
+
+gdb_test "ptype /o struct asd" \
+    [multi_line \
+{/\* offset    |  size \*/  type = struct asd \{} \
+{/\*    0      |    32 \*/    struct asd::jkl \{} \
+{/\*    0      |     8 \*/        signed char \*f1;} \
+{/\*    8      |     8 \*/        union \{} \
+{/\*                 8 \*/            void \*ff1;} \
+{} \
+{                                   /\* total size \(bytes\):    8 \*/} \
+{                               \} f2;} \
+{/\*   16      |     8 \*/        union \{} \
+{/\*                 8 \*/            signed char \*ff2;} \
+{} \
+{                                   /\* total size \(bytes\):    8 \*/} \
+{                               \} f3;} \
+{/\*   24:27   |     4 \*/        int f4 : 5;} \
+{/\*   24:26   |     4 \*/        unsigned int f5 : 1;} \
+{/\* XXX  2-bit hole   \*/} \
+{/\* XXX  1-byte hole  \*/} \
+{/\*   26      |     2 \*/        short f6;} \
+{} \
+{                               /\* total size \(bytes\):   32 \*/} \
+{                           \} f7;} \
+{/\*   32      |     8 \*/    unsigned long f8;} \
+{/\*   40      |     8 \*/    signed char \*f9;} \
+{/\*   48:28   |     4 \*/    int f10 : 4;} \
+{/\*   48:27   |     4 \*/    unsigned int f11 : 1;} \
+{/\*   48:26   |     4 \*/    unsigned int f12 : 1;} \
+{/\*   48:25   |     4 \*/    unsigned int f13 : 1;} \
+{/\*   48:24   |     4 \*/    unsigned int f14 : 1;} \
+{/\* XXX  7-byte hole  \*/} \
+{/\*   56      |     8 \*/    void \*f15;} \
+{/\*   64      |     8 \*/    void \*f16;} \
+{} \
+{                           /\* total size \(bytes\):   72 \*/} \
+{                         \}}]
+
+# Test that we don't print any header when issuing a "ptype /o" on a
+# non-struct, non-union, non-class type.
+gdb_test "ptype /o int" "int"
+gdb_test "ptype /o uint8_t" "char"
+
+# Test that the "whatis" command doesn't print anything related to the
+# "offsets" feature, even when receiving the "/o" parameter.
+set test "whatis /o asd"
+gdb_test_multiple "$test" "$test" {
+   -re "^$test\r\ntype = asd\r\n$gdb_prompt $" {
+       pass $test
+   }
+}
diff --git a/gdb/typeprint.c b/gdb/typeprint.c
index 9d9d6f5a49..15a62bef3c 100644
--- a/gdb/typeprint.c
+++ b/gdb/typeprint.c
@@ -42,6 +42,7 @@ const struct type_print_options type_print_raw_options =
   1,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -55,6 +56,7 @@ static struct type_print_options default_ptype_flags =
   0,				/* raw */
   1,				/* print_methods */
   1,				/* print_typedefs */
+  0,				/* print_offsets */
   0,				/* print_nested_type_limit  */
   NULL,				/* local_typedefs */
   NULL,				/* global_table */
@@ -440,6 +442,20 @@ whatis_exp (const char *exp, int show)
 		case 'T':
 		  flags.print_typedefs = 1;
 		  break;
+		case 'o':
+		  {
+		    /* Filter out languages which don't implement the
+		       feature.  */
+		    if (show > 0 &&
+			(current_language->la_language == language_c
+			 || current_language->la_language == language_cplus))
+		      {
+			flags.print_offsets = 1;
+			flags.print_typedefs = 0;
+			flags.print_methods = 0;
+		      }
+		    break;
+		  }
 		default:
 		  error (_("unrecognized flag '%c'"), *exp);
 		}
@@ -499,6 +515,11 @@ whatis_exp (const char *exp, int show)
 	real_type = value_rtti_type (val, &full, &top, &using_enc);
     }
 
+  if (flags.print_offsets
+      && (TYPE_CODE (type) == TYPE_CODE_STRUCT
+	  || TYPE_CODE (type) == TYPE_CODE_UNION))
+    fprintf_filtered (gdb_stdout, "/* offset    |  size */  ");
+
   printf_filtered ("type = ");
 
   if (!flags.raw)
@@ -759,7 +780,8 @@ Available FLAGS are:\n\
   /m    do not print methods defined in a class\n\
   /M    print methods defined in a class\n\
   /t    do not print typedefs defined in a class\n\
-  /T    print typedefs defined in a class"));
+  /T    print typedefs defined in a class\n\
+  /o    print offsets and sizes of fields in a struct (like pahole)\n"));
   set_cmd_completer (c, expression_completer);
 
   c = add_com ("whatis", class_vars, whatis_command,
diff --git a/gdb/typeprint.h b/gdb/typeprint.h
index a2058b0120..2e19006f22 100644
--- a/gdb/typeprint.h
+++ b/gdb/typeprint.h
@@ -24,6 +24,20 @@ struct ui_file;
 struct typedef_hash_table;
 struct ext_lang_type_printers;
 
+struct print_offset_data
+{
+  /* The offset to be applied to bitpos when PRINT_OFFSETS is true.
+     This is needed for when we are printing nested structs and want
+     to make sure that the printed offset for each field carries over
+     the offset of the outter struct.  */
+  unsigned int offset_bitpos = 0;
+
+  /* END_BITPOS is the one-past-the-end bit position of the previous
+     field (where we expect the current field to be if there is no
+     hole).  */
+  unsigned int end_bitpos = 0;
+};
+
 struct type_print_options
 {
   /* True means that no special printing flags should apply.  */
@@ -35,6 +49,9 @@ struct type_print_options
   /* True means print typedefs in a class.  */
   unsigned int print_typedefs : 1;
 
+  /* True means to print offsets, a la 'pahole'.  */
+  unsigned int print_offsets : 1;
+
   /* The number of nested type definitions to print.  -1 == all.  */
   int print_nested_type_limit;
 
-- 
2.14.3

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg)
  2017-12-15  1:12 ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
  2017-12-15  1:12   ` [PATCH v7 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
  2017-12-15  1:13   ` [PATCH v7 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
@ 2017-12-15 20:11   ` Sergio Durigan Junior
  2 siblings, 0 replies; 75+ messages in thread
From: Sergio Durigan Junior @ 2017-12-15 20:11 UTC (permalink / raw)
  To: GDB Patches
  Cc: Tom Tromey, Eli Zaretskii, Simon Marchi, Pedro Alves, Keith Seitz

On Thursday, December 14 2017, I wrote:

> Changes from v6:
>
> - Updated examples in the documentation.
>
> - Removed stale comments about the test being x86_64-specific from
>   tests.
>
> - Added tests for "ptype /oTM", "ptype /TMo", and for the regression
>   Pedro caught.
>
> - Removed "print_offset_default_data".
>
> - Fixed regression reported by Pedro.
>
> - s/endpos/end_bitpos/
>
> - Filtered out the languages that don't implement the feature so that
>   their type printing output doesn't get clobbered by "ptype /o" when
>   they use "common" code (which is not actually common; it lives
>   inside c-typeprint.c).  I decided to do that by checking the current
>   language on typeprint.c:whatis_exp, before setting the
>   "print_offset" bit.
>
>
> The documentation parts have been approved by Eli, and haven't
> changed.

Patches have been pushed:

a27ed7d613ec91c3a79965d6bdab1fa96d559c85
7c1618381fdaa0697a211721ac31844f884797ac

Thanks for all the help with reviews and suggestions.

-- 
Sergio
GPG key ID: 237A 54B1 0287 28BF 00EF  31F4 D0EB 7628 65FC 5E36
Please send encrypted e-mail if possible
http://sergiodj.net/

^ permalink raw reply	[flat|nested] 75+ messages in thread

end of thread, other threads:[~2017-12-15 20:11 UTC | newest]

Thread overview: 75+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-21 16:07 [PATCH] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
2017-11-21 16:16 ` Sergio Durigan Junior
2017-11-21 16:50 ` Eli Zaretskii
2017-11-21 17:00   ` Sergio Durigan Junior
2017-11-21 19:14     ` Eli Zaretskii
2017-11-26 19:27 ` Tom Tromey
2017-11-27 19:54   ` Sergio Durigan Junior
2017-11-27 22:20     ` Tom Tromey
2017-11-28  0:39       ` Sergio Durigan Junior
2017-11-28 21:21 ` [PATCH v2] " Sergio Durigan Junior
2017-11-29  3:28   ` Eli Zaretskii
2017-12-04 15:03   ` Sergio Durigan Junior
2017-12-04 15:41     ` Eli Zaretskii
2017-12-04 16:47       ` Sergio Durigan Junior
2017-12-08 21:32     ` Sergio Durigan Junior
2017-12-11 15:43   ` Simon Marchi
2017-12-11 18:59     ` Sergio Durigan Junior
2017-12-11 20:45       ` Simon Marchi
2017-12-11 21:07         ` Sergio Durigan Junior
2017-12-11 22:42           ` Pedro Alves
2017-12-11 22:50             ` Sergio Durigan Junior
2017-12-11 23:46               ` Pedro Alves
2017-12-12  0:25                 ` Sergio Durigan Junior
2017-12-12  0:52                   ` Pedro Alves
2017-12-12  1:25                     ` Simon Marchi
2017-12-12 15:50                       ` John Baldwin
2017-12-12 17:04                         ` Sergio Durigan Junior
2017-12-11 19:58 ` [PATCH v3 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
2017-12-11 19:58   ` [PATCH v3 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
2017-12-11 20:55     ` Simon Marchi
2017-12-11 23:05       ` Sergio Durigan Junior
2017-12-11 19:58   ` [PATCH v3 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
2017-12-11 21:50     ` Simon Marchi
2017-12-11 23:24       ` Sergio Durigan Junior
2017-12-12  1:32         ` Simon Marchi
2017-12-12  6:19           ` Sergio Durigan Junior
2017-12-12 18:14             ` Pedro Alves
2017-12-12 18:40               ` Sergio Durigan Junior
2017-12-12 20:12                 ` Pedro Alves
2017-12-11 23:43 ` [PATCH v4 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
2017-12-11 23:44   ` [PATCH v4 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
2017-12-11 23:44   ` [PATCH v4 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
2017-12-12  0:27     ` Sergio Durigan Junior
2017-12-12  0:29       ` Sergio Durigan Junior
2017-12-12  1:59     ` Simon Marchi
2017-12-12  3:39     ` Eli Zaretskii
2017-12-13  3:17 ` [PATCH v5 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
2017-12-13  3:17   ` [PATCH v5 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
2017-12-13  4:50     ` Simon Marchi
2017-12-13 16:42       ` Sergio Durigan Junior
2017-12-13 16:17     ` Eli Zaretskii
2017-12-13 17:14       ` Sergio Durigan Junior
2017-12-13 16:19     ` Pedro Alves
2017-12-13 17:13       ` Sergio Durigan Junior
2017-12-13 20:36         ` Sergio Durigan Junior
2017-12-13 21:22           ` Pedro Alves
2017-12-13 21:30             ` Pedro Alves
2017-12-13 21:34             ` Sergio Durigan Junior
2017-12-13 16:20     ` Pedro Alves
2017-12-13 17:41       ` Sergio Durigan Junior
2017-12-13  3:17   ` [PATCH v5 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
2017-12-14  2:48 ` [PATCH v6 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
2017-12-14  2:48   ` [PATCH v6 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
2017-12-14 14:19     ` Pedro Alves
2017-12-14 20:31       ` Sergio Durigan Junior
2017-12-14 14:50     ` Pedro Alves
2017-12-14 20:29       ` Sergio Durigan Junior
2017-12-14 16:30     ` Eli Zaretskii
2017-12-14  2:48   ` [PATCH v6 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
2017-12-15  1:12 ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior
2017-12-15  1:12   ` [PATCH v7 1/2] Reorganize code to handle TYPE_CODE_{STRUCT,UNION} on 'c_type_print_base' Sergio Durigan Junior
2017-12-15  1:13   ` [PATCH v7 2/2] Implement pahole-like 'ptype /o' option Sergio Durigan Junior
2017-12-15 17:24     ` Pedro Alves
2017-12-15 20:04       ` Sergio Durigan Junior
2017-12-15 20:11   ` [PATCH v7 0/2] Implement pahole-like 'ptype /o' option (and do some code reorg) Sergio Durigan Junior

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).