@@ -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.
@@ -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);
/* A callback function for cp_canonicalize_string_full that uses
@@ -82,14 +96,15 @@ print_name_maybe_canonical (const char *name,
-/* 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);
+}
@@ -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
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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"
@@ -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,
@@ -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;