From patchwork Tue Nov 21 16:07:09 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergio Durigan Junior X-Patchwork-Id: 24410 Received: (qmail 70470 invoked by alias); 21 Nov 2017 16:07:30 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 70461 invoked by uid 89); 21 Nov 2017 16:07:30 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-26.7 required=5.0 tests=BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, KB_WAM_FROM_NAME_SINGLEWORD, SPF_HELO_PASS, T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 spammy= X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Tue, 21 Nov 2017 16:07:22 +0000 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 28F1180C1B for ; Tue, 21 Nov 2017 16:07:21 +0000 (UTC) Received: from psique.yyz.redhat.com (unused-10-15-17-193.yyz.redhat.com [10.15.17.193]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7879D5D960; Tue, 21 Nov 2017 16:07:18 +0000 (UTC) From: Sergio Durigan Junior To: GDB Patches Cc: Sergio Durigan Junior Subject: [PATCH] Implement pahole-like 'ptype /o' option Date: Tue, 21 Nov 2017 11:07:09 -0500 Message-Id: <20171121160709.23248-1-sergiodj@redhat.com> X-IsSubscribed: yes 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: 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 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) : New field. gdb/testsuite/ChangeLog: 2017-11-21 Sergio Durigan Junior 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 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 . */ + +/* This file will be used to test 'ptype /o' on x86_64 only. */ + +#include + +/* 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 . + +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;