| # Pretty-printer utilities. | |
| # Copyright (C) 2010-2023 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 <http://www.gnu.org/licenses/>. | |
| """Utilities for working with pretty-printers.""" | |
| import gdb | |
| import gdb.types | |
| import re | |
| class PrettyPrinter(object): | |
| """A basic pretty-printer. | |
| Attributes: | |
| name: A unique string among all printers for the context in which | |
| it is defined (objfile, progspace, or global(gdb)), and should | |
| meaningfully describe what can be pretty-printed. | |
| E.g., "StringPiece" or "protobufs". | |
| subprinters: An iterable object with each element having a `name' | |
| attribute, and, potentially, "enabled" attribute. | |
| Or this is None if there are no subprinters. | |
| enabled: A boolean indicating if the printer is enabled. | |
| Subprinters are for situations where "one" pretty-printer is actually a | |
| collection of several printers. E.g., The libstdc++ pretty-printer has | |
| a pretty-printer for each of several different types, based on regexps. | |
| """ | |
| # While one might want to push subprinters into the subclass, it's | |
| # present here to formalize such support to simplify | |
| # commands/pretty_printers.py. | |
| def __init__(self, name, subprinters=None): | |
| self.name = name | |
| self.subprinters = subprinters | |
| self.enabled = True | |
| def __call__(self, val): | |
| # The subclass must define this. | |
| raise NotImplementedError("PrettyPrinter __call__") | |
| class SubPrettyPrinter(object): | |
| """Baseclass for sub-pretty-printers. | |
| Sub-pretty-printers needn't use this, but it formalizes what's needed. | |
| Attributes: | |
| name: The name of the subprinter. | |
| enabled: A boolean indicating if the subprinter is enabled. | |
| """ | |
| def __init__(self, name): | |
| self.name = name | |
| self.enabled = True | |
| def register_pretty_printer(obj, printer, replace=False): | |
| """Register pretty-printer PRINTER with OBJ. | |
| The printer is added to the front of the search list, thus one can override | |
| an existing printer if one needs to. Use a different name when overriding | |
| an existing printer, otherwise an exception will be raised; multiple | |
| printers with the same name are disallowed. | |
| Arguments: | |
| obj: Either an objfile, progspace, or None (in which case the printer | |
| is registered globally). | |
| printer: Either a function of one argument (old way) or any object | |
| which has attributes: name, enabled, __call__. | |
| replace: If True replace any existing copy of the printer. | |
| Otherwise if the printer already exists raise an exception. | |
| Returns: | |
| Nothing. | |
| Raises: | |
| TypeError: A problem with the type of the printer. | |
| ValueError: The printer's name contains a semicolon ";". | |
| RuntimeError: A printer with the same name is already registered. | |
| If the caller wants the printer to be listable and disableable, it must | |
| follow the PrettyPrinter API. This applies to the old way (functions) too. | |
| If printer is an object, __call__ is a method of two arguments: | |
| self, and the value to be pretty-printed. See PrettyPrinter. | |
| """ | |
| # Watch for both __name__ and name. | |
| # Functions get the former for free, but we don't want to use an | |
| # attribute named __foo__ for pretty-printers-as-objects. | |
| # If printer has both, we use `name'. | |
| if not hasattr(printer, "__name__") and not hasattr(printer, "name"): | |
| raise TypeError("printer missing attribute: name") | |
| if hasattr(printer, "name") and not hasattr(printer, "enabled"): | |
| raise TypeError("printer missing attribute: enabled") | |
| if not hasattr(printer, "__call__"): | |
| raise TypeError("printer missing attribute: __call__") | |
| if hasattr(printer, "name"): | |
| name = printer.name | |
| else: | |
| name = printer.__name__ | |
| if obj is None or obj is gdb: | |
| if gdb.parameter("verbose"): | |
| gdb.write("Registering global %s pretty-printer ...\n" % name) | |
| obj = gdb | |
| else: | |
| if gdb.parameter("verbose"): | |
| gdb.write( | |
| "Registering %s pretty-printer for %s ...\n" % (name, obj.filename) | |
| ) | |
| # Printers implemented as functions are old-style. In order to not risk | |
| # breaking anything we do not check __name__ here. | |
| if hasattr(printer, "name"): | |
| if not isinstance(printer.name, str): | |
| raise TypeError("printer name is not a string") | |
| # If printer provides a name, make sure it doesn't contain ";". | |
| # Semicolon is used by the info/enable/disable pretty-printer commands | |
| # to delimit subprinters. | |
| if printer.name.find(";") >= 0: | |
| raise ValueError("semicolon ';' in printer name") | |
| # Also make sure the name is unique. | |
| # Alas, we can't do the same for functions and __name__, they could | |
| # all have a canonical name like "lookup_function". | |
| # PERF: gdb records printers in a list, making this inefficient. | |
| i = 0 | |
| for p in obj.pretty_printers: | |
| if hasattr(p, "name") and p.name == printer.name: | |
| if replace: | |
| del obj.pretty_printers[i] | |
| break | |
| else: | |
| raise RuntimeError( | |
| "pretty-printer already registered: %s" % printer.name | |
| ) | |
| i = i + 1 | |
| obj.pretty_printers.insert(0, printer) | |
| class RegexpCollectionPrettyPrinter(PrettyPrinter): | |
| """Class for implementing a collection of regular-expression based pretty-printers. | |
| Intended usage: | |
| pretty_printer = RegexpCollectionPrettyPrinter("my_library") | |
| pretty_printer.add_printer("myclass1", "^myclass1$", MyClass1Printer) | |
| ... | |
| pretty_printer.add_printer("myclassN", "^myclassN$", MyClassNPrinter) | |
| register_pretty_printer(obj, pretty_printer) | |
| """ | |
| class RegexpSubprinter(SubPrettyPrinter): | |
| def __init__(self, name, regexp, gen_printer): | |
| super(RegexpCollectionPrettyPrinter.RegexpSubprinter, self).__init__(name) | |
| self.regexp = regexp | |
| self.gen_printer = gen_printer | |
| self.compiled_re = re.compile(regexp) | |
| def __init__(self, name): | |
| super(RegexpCollectionPrettyPrinter, self).__init__(name, []) | |
| def add_printer(self, name, regexp, gen_printer): | |
| """Add a printer to the list. | |
| The printer is added to the end of the list. | |
| Arguments: | |
| name: The name of the subprinter. | |
| regexp: The regular expression, as a string. | |
| gen_printer: A function/method that given a value returns an | |
| object to pretty-print it. | |
| Returns: | |
| Nothing. | |
| """ | |
| # NOTE: A previous version made the name of each printer the regexp. | |
| # That makes it awkward to pass to the enable/disable commands (it's | |
| # cumbersome to make a regexp of a regexp). So now the name is a | |
| # separate parameter. | |
| self.subprinters.append(self.RegexpSubprinter(name, regexp, gen_printer)) | |
| def __call__(self, val): | |
| """Lookup the pretty-printer for the provided value.""" | |
| # Get the type name. | |
| typename = gdb.types.get_basic_type(val.type).tag | |
| if not typename: | |
| typename = val.type.name | |
| if not typename: | |
| return None | |
| # Iterate over table of type regexps to determine | |
| # if a printer is registered for that type. | |
| # Return an instantiation of the printer if found. | |
| for printer in self.subprinters: | |
| if printer.enabled and printer.compiled_re.search(typename): | |
| return printer.gen_printer(val) | |
| # Cannot find a pretty printer. Return None. | |
| return None | |
| # A helper class for printing enum types. This class is instantiated | |
| # with a list of enumerators to print a particular Value. | |
| class _EnumInstance: | |
| def __init__(self, enumerators, val): | |
| self.enumerators = enumerators | |
| self.val = val | |
| def to_string(self): | |
| flag_list = [] | |
| v = int(self.val) | |
| any_found = False | |
| for (e_name, e_value) in self.enumerators: | |
| if v & e_value != 0: | |
| flag_list.append(e_name) | |
| v = v & ~e_value | |
| any_found = True | |
| if not any_found or v != 0: | |
| # Leftover value. | |
| flag_list.append("<unknown: 0x%x>" % v) | |
| return "0x%x [%s]" % (int(self.val), " | ".join(flag_list)) | |
| class FlagEnumerationPrinter(PrettyPrinter): | |
| """A pretty-printer which can be used to print a flag-style enumeration. | |
| A flag-style enumeration is one where the enumerators are or'd | |
| together to create values. The new printer will print these | |
| symbolically using '|' notation. The printer must be registered | |
| manually. This printer is most useful when an enum is flag-like, | |
| but has some overlap. GDB's built-in printing will not handle | |
| this case, but this printer will attempt to.""" | |
| def __init__(self, enum_type): | |
| super(FlagEnumerationPrinter, self).__init__(enum_type) | |
| self.initialized = False | |
| def __call__(self, val): | |
| if not self.initialized: | |
| self.initialized = True | |
| flags = gdb.lookup_type(self.name) | |
| self.enumerators = [] | |
| for field in flags.fields(): | |
| self.enumerators.append((field.name, field.enumval)) | |
| # Sorting the enumerators by value usually does the right | |
| # thing. | |
| self.enumerators.sort(key=lambda x: x[1]) | |
| if self.enabled: | |
| return _EnumInstance(self.enumerators, val) | |
| else: | |
| return None | |
| # Builtin pretty-printers. | |
| # The set is defined as empty, and files in printing/*.py add their printers | |
| # to this with add_builtin_pretty_printer. | |
| _builtin_pretty_printers = RegexpCollectionPrettyPrinter("builtin") | |
| register_pretty_printer(None, _builtin_pretty_printers) | |
| # Add a builtin pretty-printer. | |
| def add_builtin_pretty_printer(name, regexp, printer): | |
| _builtin_pretty_printers.add_printer(name, regexp, printer) |