# 'pahole' and 'offset' commands for examining types # Copyright (C) 2008, 2009, 2012 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 . import gdb import re from enum import Enum class TraversalNodeType(Enum): SIMPLE = 1, START_STRUCT = 2, END_STRUCT = 3, HOLE = 4 def type_to_string(type): # I had some complicated code to display template parameters here, but it # doesn't work quite right and on further reflection the actual problem is # a gdb bug anyway. So stop trying. # # See https://sourceware.org/bugzilla/show_bug.cgi?id=23545 name = str(type) csu = 'struct' if type.code == gdb.TYPE_CODE_STRUCT else 'union' if name.startswith(csu + ' '): return name else: return '%s %s' % (csu, name) def traverse_type(type, max_level=0, name_anon=False): def calc_sizeof(type): '''Same as type.sizeof, except do not inflate empty structs to 1 byte.''' type = type.strip_typedefs() if type.sizeof != 1 or type.code != gdb.TYPE_CODE_STRUCT: return type.sizeof size = 0 for field in type.fields(): size += calc_sizeof(field.type) return size def traverse(type, parent, level, field_name, top_bitpos, size_bits, bitpos): stripped_type = type.strip_typedefs() if parent is None: path = 'this' elif field_name: path = parent['path'] + '.' + field_name else: if name_anon: anon = '' if type.code == gdb.TYPE_CODE_UNION else '' path = parent['path'] + '.' + anon else: path = parent['path'] info = { 'type': type, #'name': type.name or type.tag or stripped_type.name or stripped_type.tag, 'name': type_to_string(type), 'field_name': field_name, 'level': level, 'parent': parent, 'top_bitpos': top_bitpos, 'bitpos': bitpos, 'size_bits': size_bits, 'path': path, 'truncated': (max_level and level >= max_level), } if stripped_type.code not in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION): # For now, treat everything but class/struct/union as a simple type. info['node_type'] = TraversalNodeType.SIMPLE yield info return info['node_type'] = TraversalNodeType.START_STRUCT yield info base_counter = 0 bitpos = 0 for field in stripped_type.fields(): # Skip static fields. if not hasattr(field, 'bitpos'): continue # Allow limiting the depth of traversal. if max_level and info['level'] >= max_level: continue ftype = field.type.strip_typedefs() fsize = calc_sizeof(ftype) fbitpos = field.bitpos if fsize > 0 else bitpos if bitpos != fbitpos: yield { 'node_type': TraversalNodeType.HOLE, 'type': '', 'name': '<%d-bit hole>' % (fbitpos - bitpos), 'field_name': None, 'level': level + 1, 'parent': info, 'top_bitpos': top_bitpos + bitpos, 'bitpos': bitpos, 'size_bits': fbitpos - bitpos, 'path': path, 'next_field': field.name, } # Advance past the hole, to the start of the field. bitpos = fbitpos if field.bitsize > 0: fieldsize = field.bitsize else: # TARGET_CHAR_BIT here... fieldsize = 8 * fsize field_name = field.name if field.is_base_class: field_name = '' base_counter += 1 yield from traverse(field.type, info, level + 1, field_name, top_bitpos + bitpos, fieldsize, bitpos) if stripped_type.code == gdb.TYPE_CODE_STRUCT: bitpos += fieldsize info['node_type'] = TraversalNodeType.END_STRUCT yield info yield from traverse(type, parent=None, level=0, field_name=None, top_bitpos=0, size_bits=type.sizeof*8, bitpos=0) class Pahole (gdb.Command): """Show the holes in a structure. This command takes a single argument, a type name. It prints the type, including any holes it finds. It accepts an optional max-depth argument: `pahole/1 mytype` will not recurse into contained structs.""" def __init__ (self): super (Pahole, self).__init__ ("pahole", gdb.COMMAND_NONE, gdb.COMPLETE_SYMBOL) def invoke (self, arg, from_tty): max_level = 0 if arg.startswith("/"): m = re.match(r'^/(\d+) +', arg) if m: max_level = int(m.group(1), 0) arg = arg[m.span()[1]:] type = gdb.lookup_type(arg) type = type.strip_typedefs () if type.code not in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION): raise TypeError('%s is not a class/struct/union type' % arg) info_pattern = ' %4d %4d : ' inner_info_pattern = '%4d%+4d %4d : ' empty_inner_info_pattern = ' %4d : ' header_len = len(inner_info_pattern % (0, 0, 0)) print(' offset size') for info in traverse_type(type, max_level=max_level): nt = info['node_type'] sofar = 0 if nt != TraversalNodeType.END_STRUCT: bytepos = int(info['bitpos'] / 8) top_bytepos = int(info['top_bitpos'] / 8) bytesize = int(info['size_bits'] / 8) if info['level'] > 1: if bytesize == 0: out = empty_inner_info_pattern % bytesize else: out = inner_info_pattern % (top_bytepos - bytepos, bytepos, bytesize) else: out = info_pattern % (bytepos, bytesize) sofar = len(out) print(out, end="") indent = ' ' * (2 * info['level']) if nt == TraversalNodeType.START_STRUCT: desc = indent if info['field_name']: desc += '%s : ' % info['field_name'] desc += info['name'] if not info['truncated']: desc += ' {' print(desc) elif nt == TraversalNodeType.END_STRUCT: if not info['truncated']: print('%s%s} %s' % (' ' * (header_len - sofar), indent, info['name'] or '')) elif nt == TraversalNodeType.SIMPLE: print('%s%s : %s' % (indent, info['field_name'], info['type'])) elif nt == TraversalNodeType.HOLE: parent_name = (info['parent'] or {}).get('name', None) where = 'in ' + parent_name if parent_name else '' print("--> %d bit hole %s <--" % (info['size_bits'], where)) Pahole() class TypeOffset (gdb.Command): """Displays the fields at the given offset (in bytes) of a type. The optional /N parameter determines the size of the region inspected; defaults to the size of a pointer.""" default_width = gdb.lookup_type("void").pointer().sizeof def __init__ (self): super (TypeOffset, self).__init__ ("offset", gdb.COMMAND_NONE, gdb.COMPLETE_SYMBOL) def invoke (self, arg, from_tty): width = gdb.lookup_type("void").pointer().sizeof m = re.match(r'/(\d+) ', arg) if m: width = int(m.group(1), 0) arg = arg[m.span()[1]:] (offset, typename) = arg.split(" ") offset = int(offset, 0) type = gdb.lookup_type(typename) type = type.strip_typedefs () if type.code not in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION): raise TypeError('%s is not a class/struct/union type' % arg) begin, end = offset, offset + width - 1 print("Scanning byte offsets %d..%d" % (begin, end)) for info in traverse_type(type, name_anon=True): if info['node_type'] == TraversalNodeType.END_STRUCT: continue if 'top_bitpos' not in info or 'size_bits' not in info: continue # Not all that interesting to say that the whole type overlaps. if info['level'] == 0: continue (bytepos, bytesize) = (int(info['top_bitpos']/8), int(info['size_bits']/8)) fend = bytepos + bytesize - 1 if fend < begin: continue if bytepos > end: continue name_of_type = info.get('name') or type_to_string(info['type']) if info['node_type'] == TraversalNodeType.HOLE: name_of_type += " in " + (info['parent'] or {}).get('name', 'struct') if info['next_field']: name_of_type += " before field '" + info['next_field'] + "'" print('overlap at byte %d..%d with %s : %s' % (bytepos, fend, info['path'], name_of_type)) TypeOffset()