This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[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:

  (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


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]