Last active
April 21, 2024 15:09
-
-
Save jimblandy/6f1cfce4c1da1ddd7c6babdb42e75c22 to your computer and use it in GitHub Desktop.
GDB Python script to define a command for inspecting types.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Put this in a file BLAH/dump-type.py and then put this in your ~/.gdbinit file | |
# (or type it at the GDB prompt): | |
# | |
# source BLAH/dump-type.py | |
# | |
# Then you can say: | |
# | |
# (gdb) help jimb-dump-type | |
# | |
# and you should get a help message. | |
import gdb | |
class DumpTypeCommand(gdb.Command): | |
"""Display the structure of a type in detail. | |
Usage: jimb-dump-type[/FLAGS] TYPE | |
Available FLAGS are: | |
/p Follow pointers: show structure of the pointed-to type. | |
/E Don't show element structure of heap-allocated types like | |
vectors and maps. Useful when you're more interested in the | |
size or layout of the struct itself. | |
/d Show implementation details of boring types like | |
Maybe, nsTArray, etc. rather than just jumping to | |
their interesting contents, like the element type. | |
/s Include static struct members.""" | |
def __init__(self): | |
super (DumpTypeCommand, self).__init__("jimb-dump-type", gdb.COMMAND_USER) | |
def complete(self, text, word): | |
return gdb.COMPLETE_SYMBOL | |
def invoke(self, arg, from_tty): | |
argv = gdb.string_to_argv(arg) | |
flags = {} | |
if len(argv) >= 1 and argv[0][:1] == '/': | |
for flag in argv[0][1:]: | |
if flag == 'p': | |
flags['follow_pointers'] = True | |
elif flag == 'd': | |
flags['details'] = True | |
elif flag == 's': | |
flags['show_static'] = True | |
elif flag == 'E': | |
flags['hide_owned_heap'] = True | |
else: | |
raise gdb.GdbError("unrecognized flag: {!r}".format(flag)) | |
argv = argv[1:] | |
if len(argv) != 1: | |
raise gdb.GdbError("jimb-dump-type takes a type name as an argument.") | |
try: | |
typ = gdb.lookup_type(argv[0]) | |
except gdb.error as e: | |
print str(e) | |
return | |
dump_type(typ, flags, set()) | |
def dump_type(typ, flags, seen, indent=''): | |
def nest(t): | |
dump_type(t, flags, seen, indent + ' ') | |
def summary_only(t): | |
sflags = flags.copy() | |
sflags['summary-only'] = True | |
dump_type(t, sflags, seen, indent + ' ') | |
cn = code_name(typ.code) | |
summary = indent + cn + ' ' | |
if typ.name and typ.name != cn: | |
summary += "name: {!r} ".format(typ.name) | |
if typ.tag and typ.tag != typ.name: | |
summary += "tag: {!r} ".format(typ.tag) | |
summary += '{}B'.format(typ.sizeof) | |
print summary | |
if 'summary-only' in flags: | |
return | |
pointer_target = is_pointer(typ, flags) | |
if pointer_target: | |
if 'follow_pointers' in flags: | |
nest(pointer_target) | |
else: | |
summary_only(pointer_target) | |
return | |
container_params = is_container(typ, flags) | |
if container_params: | |
if 'hide_owned_heap' in flags: | |
# like pointer | |
for param in container_params: | |
summary_only(param) | |
else: | |
for param in container_params: | |
nest(param) | |
return | |
if typ.code == gdb.TYPE_CODE_ARRAY: | |
nest(typ.target()) | |
elif typ.code == gdb.TYPE_CODE_TYPEDEF: | |
if typ.name in seen: | |
return | |
seen.add(typ.name) | |
nest(typ.target()) | |
elif typ.code == gdb.TYPE_CODE_STRUCT or typ.code == gdb.TYPE_CODE_UNION: | |
if typ.name in seen: | |
return | |
seen.add(typ.name) | |
def try_shortcuts(): | |
if 'details' in flags: | |
return False | |
if not typ.name: | |
return False | |
if typ.name.startswith('mozilla::Maybe<'): | |
nest(typ.template_argument(0)) | |
elif typ.name.startswith('mozilla::Atomic<'): | |
nest(typ.template_argument(0)) | |
else: | |
return False | |
return True | |
if not try_shortcuts(): | |
indent += ' ' | |
for field in typ.fields(): | |
# Omit static data members. These have no bit position. | |
if not hasattr(field, 'bitpos') and 'show_static' not in flags: | |
continue | |
summary = indent | |
if field.is_base_class: | |
summary += 'base class, ' | |
if field.name: | |
summary += "name: {!r} ".format(field.name) | |
if field.bitsize != 0: | |
if field.bitsize > 1: | |
summary += "bitfield: {}..{}".format( | |
field.bitpos, field.bitpos + field.bitsize - 1) | |
else: | |
summary += "bitfield: {}".format(field.bitpos) | |
print(summary) | |
nest(field.type) | |
code_names = { | |
gdb.TYPE_CODE_PTR: "pointer", | |
gdb.TYPE_CODE_REF: "reference", | |
gdb.TYPE_CODE_RVALUE_REF: "rvalue reference", | |
gdb.TYPE_CODE_ARRAY: "array", | |
gdb.TYPE_CODE_TYPEDEF: "typedef", | |
gdb.TYPE_CODE_STRUCT: "struct", | |
gdb.TYPE_CODE_UNION: "union", | |
gdb.TYPE_CODE_ENUM: "enum", | |
gdb.TYPE_CODE_FLAGS: "bitflags", | |
gdb.TYPE_CODE_FUNC: "func", | |
gdb.TYPE_CODE_INT: "int", | |
gdb.TYPE_CODE_FLT: "flt", | |
gdb.TYPE_CODE_VOID: "void", | |
gdb.TYPE_CODE_SET: "set", | |
gdb.TYPE_CODE_RANGE: "range", | |
gdb.TYPE_CODE_STRING: "string", | |
gdb.TYPE_CODE_BITSTRING: "bitstring", | |
gdb.TYPE_CODE_ERROR: "<erroneous type>", | |
gdb.TYPE_CODE_METHOD: "method (C++)", | |
gdb.TYPE_CODE_METHODPTR: "pointer to member function (C++)", | |
gdb.TYPE_CODE_MEMBERPTR: "pointer to member (C++)", | |
gdb.TYPE_CODE_CHAR: "character", | |
gdb.TYPE_CODE_BOOL: "boolean", | |
gdb.TYPE_CODE_COMPLEX: "complex float", | |
gdb.TYPE_CODE_TYPEDEF: "typedef", | |
gdb.TYPE_CODE_NAMESPACE: "namespace (C++)", | |
gdb.TYPE_CODE_DECFLOAT: "decimal float", | |
gdb.TYPE_CODE_INTERNAL_FUNCTION: "internal function" | |
} | |
def code_name(code): | |
return code_names.get(code, "<unexpected type code>") | |
def is_pointer(typ, flags): | |
if 'details' in flags: | |
return None | |
if (typ.code == gdb.TYPE_CODE_PTR or typ.code == gdb.TYPE_CODE_REF): | |
return typ.target() | |
if (typ.code == gdb.TYPE_CODE_STRUCT | |
and typ.name | |
and (typ.name.startswith('RefPtr<') | |
or typ.name.startswith('nsCOMPtr<') | |
or typ.name.startswith('mozilla::UniquePtr<'))): | |
return typ.template_argument(0) | |
return None | |
def is_container(typ, flags): | |
if 'details' in flags: | |
return None | |
if typ.code != gdb.TYPE_CODE_STRUCT: | |
return None | |
if not typ.name: | |
return None | |
if (typ.name.startswith('nsTArray<') or | |
typ.name.startswith('AutoTArray<') or | |
typ.name.startswith('mozilla::SmallPointerArray<') or | |
typ.name.startswith('std::vector<') or | |
typ.name.startswith('std::stack<') or | |
typ.name.startswith('std::deque<') or | |
typ.name.startswith('std::unordered_set<')): | |
return [typ.template_argument(0)] | |
if (typ.name.startswith('std::unordered_map<') or | |
typ.name.startswith('std::map<') or | |
typ.name.startswith('nsRefPtrHashtable<')): | |
return [typ.template_argument(0), | |
typ.template_argument(1)] | |
if typ.name.startswith('mozilla::Variant<'): | |
return template_arguments(typ) | |
return None | |
def template_arguments(typ): | |
args = [] | |
while True: | |
try: | |
args.append(typ.template_argument(len(args))) | |
except: | |
break | |
return args | |
DumpTypeCommand() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment