# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. # mozilla/prettyprinters.py --- infrastructure for SpiderMonkey's auto-loaded pretty-printers. import re import gdb # Decorators for declaring pretty-printers. # # In each case, the decoratee should be a SpiderMonkey-style pretty-printer # factory, taking both a gdb.Value instance and a TypeCache instance as # arguments; see TypeCache, below. def check_for_reused_pretty_printer(fn): # Check that |fn| hasn't been registered as a pretty-printer under some # other name already. (The 'enabled' flags used by GDB's # 'enable/disable/info pretty-printer' commands are simply stored as # properties of the function objects themselves, so a single function # object can't carry the 'enabled' flags for two different printers.) if hasattr(fn, "enabled"): raise RuntimeError("pretty-printer function %r registered more than once" % fn) # a dictionary mapping gdb.Type tags to pretty-printer functions. printers_by_tag = {} # A decorator: add the decoratee as a pretty-printer lookup function for types # named |type_name|. def pretty_printer(type_name): def add(fn): check_for_reused_pretty_printer(fn) add_to_subprinter_list(fn, type_name) printers_by_tag[type_name] = fn return fn return add # a dictionary mapping gdb.Type tags to pretty-printer functions for pointers to # that type. ptr_printers_by_tag = {} # A decorator: add the decoratee as a pretty-printer lookup function for # pointers to types named |type_name|. def ptr_pretty_printer(type_name): def add(fn): check_for_reused_pretty_printer(fn) add_to_subprinter_list(fn, "ptr-to-" + type_name) ptr_printers_by_tag[type_name] = fn return fn return add # a dictionary mapping gdb.Type tags to pretty-printer functions for # references to that type. ref_printers_by_tag = {} # A decorator: add the decoratee as a pretty-printer lookup function for # references to instances of types named |type_name|. def ref_pretty_printer(type_name): def add(fn): check_for_reused_pretty_printer(fn) add_to_subprinter_list(fn, "ref-to-" + type_name) ref_printers_by_tag[type_name] = fn return fn return add # a dictionary mapping the template name portion of gdb.Type tags to # pretty-printer functions for instantiations of that template. template_printers_by_tag = {} # A decorator: add the decoratee as a pretty-printer lookup function for # instantiations of templates named |template_name|. def template_pretty_printer(template_name): def add(fn): check_for_reused_pretty_printer(fn) add_to_subprinter_list(fn, "instantiations-of-" + template_name) template_printers_by_tag[template_name] = fn return fn return add # A list of (REGEXP, PRINTER) pairs, such that if REGEXP (a RegexObject) # matches the result of converting a gdb.Value's type to a string, then # PRINTER is a pretty-printer lookup function that will probably like that # value. printers_by_regexp = [] # A decorator: add the decoratee as a pretty-printer factory for types # that, when converted to a string, match |pattern|. Use |name| as the # pretty-printer's name, when listing, enabling and disabling. def pretty_printer_for_regexp(pattern, name): compiled = re.compile(pattern) def add(fn): check_for_reused_pretty_printer(fn) add_to_subprinter_list(fn, name) printers_by_regexp.append((compiled, fn)) return fn return add # Forget all pretty-printer lookup functions defined in the module name # |module_name|, if any exist. Use this at the top of each pretty-printer # module like this: # # clear_module_printers(__name__) def clear_module_printers(module_name): global printers_by_tag, ptr_printers_by_tag, ref_printers_by_tag global template_printers_by_tag, printers_by_regexp # Remove all pretty-printers defined in the module named |module_name| # from d. def clear_dictionary(d): # Walk the dictionary, building a list of keys whose entries we # should remove. (It's not safe to delete entries from a dictionary # while we're iterating over it.) to_delete = [] for (k, v) in d.items(): if v.__module__ == module_name: to_delete.append(k) remove_from_subprinter_list(v) for k in to_delete: del d[k] clear_dictionary(printers_by_tag) clear_dictionary(ptr_printers_by_tag) clear_dictionary(ref_printers_by_tag) clear_dictionary(template_printers_by_tag) # Iterate over printers_by_regexp, deleting entries from the given module. new_list = [] for p in printers_by_regexp: if p.__module__ == module_name: remove_from_subprinter_list(p) else: new_list.append(p) printers_by_regexp = new_list # Our subprinters array. The 'subprinters' attributes of all lookup # functions returned by lookup_for_objfile point to this array instance, # which we mutate as subprinters are added and removed. subprinters = [] # Set up the 'name' and 'enabled' attributes on |subprinter|, and add it to our # list of all SpiderMonkey subprinters. def add_to_subprinter_list(subprinter, name): subprinter.name = name subprinter.enabled = True subprinters.append(subprinter) # Remove |subprinter| from our list of all SpiderMonkey subprinters. def remove_from_subprinter_list(subprinter): subprinters.remove(subprinter) # An exception class meaning, "This objfile has no SpiderMonkey in it." class NotSpiderMonkeyObjfileError(TypeError): pass # TypeCache: a cache for frequently used information about an objfile. # # When a new SpiderMonkey objfile is loaded, we construct an instance of # this class for it. Then, whenever we construct a pretty-printer for some # gdb.Value, we also pass, as a second argument, the TypeCache for the # objfile to which that value's type belongs. # # if objfile doesn't seem to have SpiderMonkey code in it, the constructor # raises NotSpiderMonkeyObjfileError. # # Pretty-printer modules may add attributes to this to hold their own # cached values. Such attributes should be named mod_NAME, where the module # is named mozilla.NAME; for example, mozilla.JSString should store its # metadata in the TypeCache's mod_JSString attribute. class TypeCache(object): def __init__(self, objfile): self.objfile = objfile # Unfortunately, the Python interface doesn't allow us to specify # the objfile in whose scope lookups should occur. But simply # knowing that we need to lookup the types afresh is probably # enough. self.void_t = gdb.lookup_type("void") self.void_ptr_t = self.void_t.pointer() self.uintptr_t = gdb.lookup_type("uintptr_t") try: self.JSString_ptr_t = gdb.lookup_type("JSString").pointer() self.JSSymbol_ptr_t = gdb.lookup_type("JS::Symbol").pointer() self.JSObject_ptr_t = gdb.lookup_type("JSObject").pointer() except gdb.error: raise NotSpiderMonkeyObjfileError self.mod_GCCellPtr = None self.mod_Interpreter = None self.mod_JSObject = None self.mod_JSOp = None self.mod_JSString = None self.mod_JS_Value = None self.mod_ExecutableAllocator = None self.mod_IonGraph = None # Yield a series of all the types that |t| implements, by following typedefs # and iterating over base classes. Specifically: # - |t| itself is the first value yielded. # - If we yield a typedef, we later yield its definition. # - If we yield a type with base classes, we later yield those base classes. # - If we yield a type with some base classes that are typedefs, # we yield all the type's base classes before following the typedefs. # # This is a hokey attempt to order the implemented types by meaningfulness when # pretty-printed. Perhaps it is entirely misguided, and we should actually # collect all applicable pretty-printers, and then use some ordering on the # pretty-printers themselves. # # We may yield a type more than once (say, if it appears more than once in the # class hierarchy). def implemented_types(t): # Yield all types that follow |t|. def followers(t): if t.code == gdb.TYPE_CODE_TYPEDEF: yield t.target() for t2 in followers(t.target()): yield t2 elif is_struct_or_union(t): base_classes = [] for f in t.fields(): if f.is_base_class: yield f.type base_classes.append(f.type) for b in base_classes: for t2 in followers(b): yield t2 yield t for t2 in followers(t): yield t2 template_regexp = re.compile("([\w_:]+)<") def is_struct_or_union(t): return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION) def is_struct_or_union_or_enum(t): return t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION, gdb.TYPE_CODE_ENUM) # Construct and return a pretty-printer lookup function for objfile, or # return None if the objfile doesn't contain SpiderMonkey code # (specifically, definitions for SpiderMonkey types). def lookup_for_objfile(objfile): # Create a type cache for this objfile. try: cache = TypeCache(objfile) except NotSpiderMonkeyObjfileError: if gdb.parameter("verbose"): gdb.write( "objfile '%s' has no SpiderMonkey code; not registering pretty-printers\n" % (objfile.filename,) ) return None # Return a pretty-printer for |value|, if we have one. This is the lookup # function object we place in each gdb.Objfile's pretty-printers list, so it # carries |name|, |enabled|, and |subprinters| attributes. def lookup(value): # If |table| has a pretty-printer for |tag|, apply it to |value|. def check_table(table, tag): if tag in table: f = table[tag] if f.enabled: return f(value, cache) return None def check_table_by_type_name(table, t): if t.code == gdb.TYPE_CODE_TYPEDEF: return check_table(table, str(t)) elif is_struct_or_union_or_enum(t) and t.tag: return check_table(table, t.tag) else: return None for t in implemented_types(value.type): if t.code == gdb.TYPE_CODE_PTR: for t2 in implemented_types(t.target()): p = check_table_by_type_name(ptr_printers_by_tag, t2) if p: return p elif t.code == gdb.TYPE_CODE_REF: for t2 in implemented_types(t.target()): p = check_table_by_type_name(ref_printers_by_tag, t2) if p: return p else: p = check_table_by_type_name(printers_by_tag, t) if p: return p if is_struct_or_union(t) and t.tag: m = template_regexp.match(t.tag) if m: p = check_table(template_printers_by_tag, m.group(1)) if p: return p # Failing that, look for a printer in printers_by_regexp. We have # to scan the whole list, so regexp printers should be used # sparingly. s = str(value.type) for (r, f) in printers_by_regexp: if f.enabled: m = r.match(s) if m: p = f(value, cache) if p: return p # No luck. return None # Give |lookup| the attributes expected of a pretty-printer with # subprinters, for enabling and disabling. lookup.name = "SpiderMonkey" lookup.enabled = True lookup.subprinters = subprinters return lookup # A base class for pretty-printers for pointer values that handles null # pointers, by declining to construct a pretty-printer for them at all. # Derived classes may simply assume that self.value is non-null. # # To help share code, this class can also be used with reference types. # # This class provides the following methods, which subclasses are free to # override: # # __init__(self, value, cache): Save value and cache as properties by those names # on the instance. # # to_string(self): format the type's name and address, as GDB would, and then # call a 'summary' method (which the subclass must define) to produce a # description of the referent. # # Note that pretty-printers returning a 'string' display hint must not use # this default 'to_string' method, as GDB will take everything it returns, # including the type name and address, as string contents. class Pointer(object): def __new__(cls, value, cache): # Don't try to provide pretty-printers for NULL pointers. if value.type.strip_typedefs().code == gdb.TYPE_CODE_PTR and value == 0: return None return super(Pointer, cls).__new__(cls) def __init__(self, value, cache): self.value = value self.cache = cache def to_string(self): # See comment above. assert not hasattr(self, "display_hint") or self.display_hint() != "string" concrete_type = self.value.type.strip_typedefs() if concrete_type.code == gdb.TYPE_CODE_PTR: address = self.value.cast(self.cache.void_ptr_t) elif concrete_type.code == gdb.TYPE_CODE_REF: address = "@" + str(self.value.address.cast(self.cache.void_ptr_t)) else: assert not "mozilla.prettyprinters.Pointer applied to bad value type" try: summary = self.summary() except gdb.MemoryError as r: summary = str(r) v = "(%s) %s %s" % (self.value.type, address, summary) return v def summary(self): raise NotImplementedError field_enum_value = None # Given |t|, a gdb.Type instance representing an enum type, return the # numeric value of the enum value named |name|. # # Pre-2012-4-18 versions of GDB store the value of an enum member on the # gdb.Field's 'bitpos' attribute; later versions store it on the 'enumval' # attribute. This function retrieves the value from either. def enum_value(t, name): global field_enum_value f = t[name] # Monkey-patching is a-okay in polyfills! Just because. if not field_enum_value: if hasattr(f, "enumval"): def field_enum_value(f): return f.enumval else: def field_enum_value(f): return f.bitpos return field_enum_value(f)