hphp/tools/gdb/pretty.py (391 lines of code) (raw):

#!/usr/bin/env python3 """ GDB pretty printers for HHVM types. """ from compatibility import * import gdb import gdb.types import re import gdbutils from gdbutils import * from lookup import lookup_func from sizeof import sizeof import idx from hhbc import as_idx #------------------------------------------------------------------------------ # Legacy iteration. class _BaseIterator(object): """Base iterator for Python 2 compatibility (in Python 3, next() is renamed to __next__()). See http://legacy.python.org/dev/peps/pep-3114/. """ def next(self): return self.__next__() def __iter__(self): return self #------------------------------------------------------------------------------ # StringData. class StringDataPrinter(object): RECOGNIZE = '^HPHP::StringData$' def __init__(self, val): self.val = val def to_string(self): return string_data_val(self.val) def display_hint(self): return 'string' #------------------------------------------------------------------------------ # TypedValue. class SetTVRecurseCommand(gdb.Command): """How many levels to recurse into TypedValue data ptrs when pretty-printing. and an optional filter on keys to recurse on. """ def __init__(self): super(SetTVRecurseCommand, self).__init__('set tv-recurse', gdb.COMMAND_STATUS) @errorwrap def invoke(self, args, from_tty): argv = gdb.string_to_argv(args) argn = len(argv) depth = None regex = "" if argn == 1: depth = argv[0] if argn == 2: depth, regex = argv if depth is not None: gdbutils.tv_recurse_key = regex if depth == 'true': gdbutils.tv_recurse = True return if depth == 'false': gdbutils.tv_recurse = 0 return try: gdbutils.tv_recurse = int(argv[0]) if gdbutils.tv_recurse >= 0: return except: pass print( 'tv-recurse <depth> [<regex>]\n' ' Valid values for <depth> are true, false or an integer depth (>=0)\n' ' The optional <regex> determines which array keys or object properties ' 'to recurse on\n' ) SetTVRecurseCommand() class SetTVBriefCommand(gdb.Command): """ Select brief or verbose TV pretty printing """ def __init__(self): super(SetTVBriefCommand, self).__init__('set tv-brief', gdb.COMMAND_STATUS) @errorwrap def invoke(self, args, from_tty): argv = gdb.string_to_argv(args) argn = len(argv) if argn == 1: if argv[0] == 'true': gdbutils.tv_brief = True return if argv[0] == 'false': gdbutils.tv_brief = False return print( 'tv-brief <arg>\n' ' Valid values for <arg> are true and false.\n' ) SetTVBriefCommand() class TypedValuePrinter(object): RECOGNIZE = '^HPHP::(TypedValue|Cell|Ref|Variant|VarNR)$' def __init__(self, val): self.val = val.cast(T('HPHP::TypedValue')) def to_string(self): return pretty_tv(self.val['m_type'], self.val['m_data']) #------------------------------------------------------------------------------ # Pointers. class PtrPrinter(object): def _string(self): ptr = self._pointer() if ptr == nullptr(): return None inner = self._pointer().dereference() inner_type = rawtype(inner.type) if inner_type.tag == 'HPHP::StringData': return string_data_val(inner) return nameof(inner) def to_string(self): try: s = self._string() except gdb.MemoryError: s = None out = '(%s) %s' % (str(self._ptype()), str(self._pointer())) return '%s "%s"' % (out, s) if s is not None else out class ReqPtrPrinter(PtrPrinter): RECOGNIZE = '^HPHP::(req::ptr<.*>)$' def __init__(self, val): self.val = val def _ptype(self): return self.val.type def _pointer(self): return self.val['m_px'] class StringPrinter(ReqPtrPrinter): RECOGNIZE = '^HPHP::(Static)?String$' def __init__(self, val): super(StringPrinter, self).__init__(val['m_str']) class ArrayPrinter(ReqPtrPrinter): RECOGNIZE = '^HPHP::Array$' def __init__(self, val): super(ArrayPrinter, self).__init__(val['m_arr']) class ObjectPrinter(ReqPtrPrinter): RECOGNIZE = '^HPHP::Object$' def __init__(self, val): super(ObjectPrinter, self).__init__(val['m_obj']) class ResourcePrinter(ReqPtrPrinter): RECOGNIZE = '^HPHP::Resource$' def __init__(self, val): super(ResourcePrinter, self).__init__(val['m_res']) class LowPtrPrinter(PtrPrinter): RECOGNIZE = '^HPHP::(LowPtr<.*>|detail::LowPtrImpl<.*>)$' def __init__(self, val): self.val = val def _ptype(self): return self.val.type def _pointer(self): rtype = gdb.types.get_basic_type(self.val.type) inner = rtype.template_argument(0) try: storage = template_type(rawtype(rtype.template_argument(1))) if storage == 'HPHP::detail::AtomicStorage': return atomic_get(self.val['m_s']).cast(inner.pointer()) else: return self.val['m_s'].cast(inner.pointer()) finally: return self.val['m_s'].cast(inner.pointer()) #------------------------------------------------------------------------------ # folly::Optional class OptionalPrinter(object): RECOGNIZE = '^(HPHP::req|folly)::Optional<.*>$' def __init__(self, val): self.val = val def to_string(self): if not self.val['storage_']['hasValue']: return 'folly::none' inner = self.val.type.template_argument(0) ptr = self.val['storage_']['value'].address.cast(inner.pointer()) return ptr.dereference() #------------------------------------------------------------------------------ # ArrayData. class ArrayDataPrinter(object): RECOGNIZE = '^HPHP::(ArrayData|VanillaDict)$' class _vec_iterator(_BaseIterator): def __init__(self, base, size): self.base = base self.size = size self.cur = 0 def __next__(self): if self.cur == self.size: raise StopIteration key = '%d' % self.cur try: val = idx.vec_at(self.base, self.cur) except gdb.MemoryError: data = '<invalid>' self.cur = self.cur + 1 return (key, val) class _dict_iterator(_BaseIterator): def __init__(self, begin, end): self.cur = begin self.end = end def __next__(self): if self.cur == self.end: raise StopIteration elt = self.cur try: if elt['data']['m_type'] == -128: rawkey = key = '<deleted>' elif elt['data']['m_aux']['u_hash'] < 0: rawkey = key = '%d' % elt['ikey'] else: rawkey = string_data_val(elt['skey'].dereference()) key = '"%s"' % rawkey except gdb.MemoryError: rawkey = key = '<invalid>' gdbutils.current_key = rawkey try: data = elt['data'].cast(T('HPHP::TypedValue')) except gdb.MemoryError: data = '<invalid>' finally: gdbutils.current_key = None self.cur = self.cur + 1 return (key, data) class _keyset_iterator(_BaseIterator): def __init__(self, begin, end): self.cur = begin self.end = end def __next__(self): if self.cur == self.end: raise StopIteration elt = self.cur try: if elt['tv']['m_type'] == -128: key = '<deleted>' else: key = "%s" % elt['tv'].cast(T('HPHP::TypedValue')) except gdb.MemoryError: key = '<invalid>' self.cur = self.cur + 1 return (key, key) def __init__(self, val): kind_ty = T('HPHP::ArrayData::ArrayKind') self.kind = val['m_kind'].cast(kind_ty) if self.kind == self._kind('Dict'): self.val = val.cast(T('HPHP::VanillaDict')) elif self.kind == self._kind('Keyset'): self.val = val.cast(T('HPHP::VanillaKeyset')) else: self.val = val def to_string(self): kind_int = int(self.kind.cast(T('uint8_t'))) if kind_int >= 14: return 'Invalid ArrayData (kind=%d)' % kind_int kind = str(self.kind)[len('HPHP::ArrayData::'):] return "ArrayData[%s]: %d element(s) refcount=%d" % ( kind, self.val['m_size'], self.val['m_count'] ) def children(self): data = (self.val.address.cast(T('char').pointer()) + self.val.type.sizeof) if self.kind == self._kind('Vec'): pelm = data.cast(T('char').pointer()) return self._vec_iterator(pelm, self.val['m_size']) if self.kind == self._kind('Dict'): pelm = data.cast(T('HPHP::VanillaDictElm').pointer()) return self._dict_iterator(pelm, pelm + self.val['m_used']) if self.kind == self._kind('Keyset'): pelm = data.cast(T('HPHP::VanillaKeysetElm').pointer()) return self._keyset_iterator(pelm, pelm + self.val['m_used']) return self._packed_iterator(0, 0) def _kind(self, kind): return K('HPHP::ArrayData::k' + kind + 'Kind', 'ArrayKind') #------------------------------------------------------------------------------ # ObjectData. class ObjectDataPrinter(object): RECOGNIZE = '^HPHP::(ObjectData)$' class _iterator(_BaseIterator): def __init__(self, obj): self.obj = obj self.cls = rawptr(obj['m_cls']) self.end = sizeof(self.cls['m_declProperties']) self.cur = gdb.Value(0).cast(self.end.type) def __next__(self): if self.cur == self.end: raise StopIteration decl_props = self.cls['m_declProperties'] try: name = idx.indexed_string_map_at(decl_props, self.cur)['name'] except gdb.MemoryError: name = '<invalid>' gdbutils.current_key = strinfo(name)['data'] try: val = idx.object_data_at(self.obj, self.cls, self.cur) except gdb.MemoryError: val = '<unknown>' finally: gdbutils.current_key = None self.cur = self.cur + 1 return (str(deref(name)), val) def __init__(self, val): self.val = val.cast(val.dynamic_type) self.cls = deref(val['m_cls']) def to_string(self): return 'Object of class "%s" @ %s' % ( nameof(self.cls), self.val.address) def children(self): return self._iterator(self.val) #------------------------------------------------------------------------------ # HHBC ops. # # Helpful for converting raw signed integers that GDB would typically # print for enum values of type Op class HhbcOpsPrinter(object): RECOGNIZE = '^HPHP::Op$' def __init__(self, val): int2enum = { field.enumval: field.name for field in T('HPHP::Op').fields() } self.val = int2enum[int(as_idx(val))] def to_string(self): return str(self.val) #------------------------------------------------------------------------------ # HHBBC::Bytecode class HhbbcBytecodePrinter(object): RECOGNIZE = '^HPHP::HHBBC::Bytecode$' def __init__(self, val): self.op = ("%s" % val['op']).replace("HPHP::Op::", "") self.val = val[self.op] def to_string(self): return 'bc::%s { %s }' % (self.op, self.val) #------------------------------------------------------------------------------ # Lookup function. class CompactVectorPrinter(object): RECOGNIZE = '^HPHP::CompactVector(<.*>)$' class _iterator(_BaseIterator): def __init__(self, begin, end): self.cur = begin self.end = end self.count = 0 def __next__(self): if self.cur == self.end: raise StopIteration elt = self.cur key = '%d' % self.count try: data = elt.dereference() except gdb.MemoryError: data = '<invalid>' self.cur = self.cur + 1 self.count = self.count + 1 return (key, data) def __init__(self, val): inner = val.type.template_argument(0) self.inner = inner if val['m_data'] == nullptr(): self.len = 0 self.cap = 0 else: self.len = val['m_data']['m_len'] self.cap = val['m_data']['m_capacity'] self.elems = (val['m_data'].cast(T('char').pointer()) + val['elems_offset']).cast(inner.pointer()) def to_string(self): return "CompactVector<%s>: %d element(s) capacity=%d" % ( self.inner.name, self.len, self.cap ) def children(self): if self.len == 0: return self._iterator(0, 0) else: return self._iterator(self.elems, self.elems + self.len) #------------------------------------------------------------------------------ # SrcKey. class SrcKeyPrinter(object): RECOGNIZE = '^HPHP::SrcKey$' def __init__(self, val): self.val = val def to_string(self): func_id = self.val['m_s']['m_funcID'] # Complicated cast to overcome "Invalid type combination in equality test" if int(func_id.cast(gdb.lookup_type('uint32'))) == -1: return '<invalid SrcKey>' func = nameof(lookup_func(func_id)) offset = self.val['m_s']['m_offset'] rmp = self.val['m_s']['m_resumeModeAndTags'] resume = prologue = '' if rmp == 0: # ResumeMode::None pass elif rmp == 1: resume = 'ra' elif rmp == 2: resume = 'rg' elif rmp == 3: prologue = 'p' return '%s@%d%s%s' % (func, offset, resume, prologue) #------------------------------------------------------------------------------ # Lookup function. printer_classes = [ TypedValuePrinter, ReqPtrPrinter, ArrayPrinter, ObjectPrinter, StringPrinter, ResourcePrinter, LowPtrPrinter, StringDataPrinter, ArrayDataPrinter, ObjectDataPrinter, HhbcOpsPrinter, HhbbcBytecodePrinter, CompactVectorPrinter, SrcKeyPrinter, OptionalPrinter, ] type_printers = {(re.compile(cls.RECOGNIZE), cls) for cls in printer_classes} def lookup_function(val): t = val.type if t.code == gdb.TYPE_CODE_REF: t = t.target() t = rawtype(t) # Get the type name. typename = t.tag if typename is None: return None # Iterate over local dict of types to determine if a printer is registered # for that type. Return an instantiation of the printer if found. for recognizer_regex, func in type_printers: if recognizer_regex.search(typename): return func(val) # Cannot find a pretty printer. Return None. return None gdb.pretty_printers.append(lookup_function)