Implement pahole-like 'ptype /o' option

Message ID 20171121160709.23248-1-sergiodj@redhat.com
State New, archived
Headers

Commit Message

Sergio Durigan Junior Nov. 21, 2017, 4:07 p.m. UTC
  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
  

Comments

Sergio Durigan Junior Nov. 21, 2017, 4:16 p.m. UTC | #1
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.
  
Eli Zaretskii Nov. 21, 2017, 4:50 p.m. UTC | #2
> 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.
  
Sergio Durigan Junior Nov. 21, 2017, 5 p.m. UTC | #3
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,
  
Eli Zaretskii Nov. 21, 2017, 7:14 p.m. UTC | #4
> 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.
  
Tom Tromey Nov. 26, 2017, 7:27 p.m. UTC | #5
>>>>> "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
  
Sergio Durigan Junior Nov. 27, 2017, 7:54 p.m. UTC | #6
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,
  
Tom Tromey Nov. 27, 2017, 10:16 p.m. UTC | #7
>>>>> "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
  
Sergio Durigan Junior Nov. 28, 2017, 12:39 a.m. UTC | #8
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 Durigan Junior Dec. 11, 2017, 7:57 p.m. UTC | #9
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.
  
Sergio Durigan Junior Dec. 11, 2017, 11:43 p.m. UTC | #10
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,
  
Sergio Durigan Junior Dec. 13, 2017, 3:17 a.m. UTC | #11
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.
  
Sergio Durigan Junior Dec. 14, 2017, 2:48 a.m. UTC | #12
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".
  
Sergio Durigan Junior Dec. 15, 2017, 1:12 a.m. UTC | #13
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.
  
Sergio Durigan Junior Dec. 15, 2017, 8:11 p.m. UTC | #14
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.
  

Patch

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;