# Pretty-printer commands. # Copyright (C) 2010-2013 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 . """GDB commands for working with pretty-printers.""" import copy import gdb import re def parse_printer_regexps(arg): """Internal utility to parse a pretty-printer command argv. Arguments: arg: The arguments to the command. The format is: [object-regexp [name-regexp]]. Individual printers in a collection are named as printer-name;subprinter-name. Returns: The result is a 3-tuple of compiled regular expressions, except that the resulting compiled subprinter regexp is None if not provided. Raises: SyntaxError: an error processing ARG """ argv = gdb.string_to_argv(arg); argc = len(argv) object_regexp = "" # match everything name_regexp = "" # match everything subname_regexp = None if argc > 3: raise SyntaxError("too many arguments") if argc >= 1: object_regexp = argv[0] if argc >= 2: name_subname = argv[1].split(";", 1) name_regexp = name_subname[0] if len(name_subname) == 2: subname_regexp = name_subname[1] # That re.compile raises SyntaxError was determined empirically. # We catch it and reraise it to provide a slightly more useful # error message for the user. try: object_re = re.compile(object_regexp) except SyntaxError: raise SyntaxError("invalid object regexp: %s" % object_regexp) try: name_re = re.compile (name_regexp) except SyntaxError: raise SyntaxError("invalid name regexp: %s" % name_regexp) if subname_regexp is not None: try: subname_re = re.compile(subname_regexp) except SyntaxError: raise SyntaxError("invalid subname regexp: %s" % subname_regexp) else: subname_re = None return(object_re, name_re, subname_re) def printer_enabled_p(printer): """Internal utility to see if printer (or subprinter) is enabled.""" if hasattr(printer, "enabled"): return printer.enabled else: return True class InfoPrettyPrinter(gdb.Command): """GDB command to list all registered pretty-printers. Usage: info pretty-printer [object-regexp [name-regexp]] OBJECT-REGEXP is a regular expression matching the objects to list. Objects are "global", the program space's file, and the objfiles within that program space. NAME-REGEXP matches the name of the pretty-printer. Individual printers in a collection are named as printer-name;subprinter-name. """ def __init__ (self): super(InfoPrettyPrinter, self).__init__("info pretty-printer", gdb.COMMAND_DATA) @staticmethod def enabled_string(printer): """Return "" if PRINTER is enabled, otherwise " [disabled]".""" if printer_enabled_p(printer): return "" else: return " [disabled]" @staticmethod def printer_name(printer): """Return the printer's name.""" if hasattr(printer, "name"): return printer.name if hasattr(printer, "__name__"): return printer.__name__ # This "shouldn't happen", but the public API allows for # direct additions to the pretty-printer list, and we shouldn't # crash because someone added a bogus printer. # Plus we want to give the user a way to list unknown printers. return "unknown" def list_pretty_printers(self, pretty_printers, name_re, subname_re): """Print a list of pretty-printers.""" # A potential enhancement is to provide an option to list printers in # "lookup order" (i.e. unsorted). sorted_pretty_printers = sorted (copy.copy(pretty_printers), key = self.printer_name) for printer in sorted_pretty_printers: name = self.printer_name(printer) enabled = self.enabled_string(printer) if name_re.match(name): print (" %s%s" % (name, enabled)) if (hasattr(printer, "subprinters") and printer.subprinters is not None): sorted_subprinters = sorted (copy.copy(printer.subprinters), key = self.printer_name) for subprinter in sorted_subprinters: if (not subname_re or subname_re.match(subprinter.name)): print (" %s%s" % (subprinter.name, self.enabled_string(subprinter))) def invoke1(self, title, printer_list, obj_name_to_match, object_re, name_re, subname_re): """Subroutine of invoke to simplify it.""" if printer_list and object_re.match(obj_name_to_match): print (title) self.list_pretty_printers(printer_list, name_re, subname_re) def invoke(self, arg, from_tty): """GDB calls this to perform the command.""" (object_re, name_re, subname_re) = parse_printer_regexps(arg) self.invoke1("global pretty-printers:", gdb.pretty_printers, "global", object_re, name_re, subname_re) cp = gdb.current_progspace() self.invoke1("progspace %s pretty-printers:" % cp.filename, cp.pretty_printers, "progspace", object_re, name_re, subname_re) for objfile in gdb.objfiles(): self.invoke1(" objfile %s pretty-printers:" % objfile.filename, objfile.pretty_printers, objfile.filename, object_re, name_re, subname_re) def count_enabled_printers(pretty_printers): """Return a 2-tuple of number of enabled and total printers.""" enabled = 0 total = 0 for printer in pretty_printers: if (hasattr(printer, "subprinters") and printer.subprinters is not None): if printer_enabled_p(printer): for subprinter in printer.subprinters: if printer_enabled_p(subprinter): enabled += 1 total += len(printer.subprinters) else: if printer_enabled_p(printer): enabled += 1 total += 1 return (enabled, total) def count_all_enabled_printers(): """Return a 2-tuble of the enabled state and total number of all printers. This includes subprinters. """ enabled_count = 0 total_count = 0 (t_enabled, t_total) = count_enabled_printers(gdb.pretty_printers) enabled_count += t_enabled total_count += t_total (t_enabled, t_total) = count_enabled_printers(gdb.current_progspace().pretty_printers) enabled_count += t_enabled total_count += t_total for objfile in gdb.objfiles(): (t_enabled, t_total) = count_enabled_printers(objfile.pretty_printers) enabled_count += t_enabled total_count += t_total return (enabled_count, total_count) def pluralize(text, n, suffix="s"): """Return TEXT pluralized if N != 1.""" if n != 1: return "%s%s" % (text, suffix) else: return text def show_pretty_printer_enabled_summary(): """Print the number of printers enabled/disabled. We count subprinters individually. """ (enabled_count, total_count) = count_all_enabled_printers() print ("%d of %d printers enabled" % (enabled_count, total_count)) def do_enable_pretty_printer_1 (pretty_printers, name_re, subname_re, flag): """Worker for enabling/disabling pretty-printers. Arguments: pretty_printers: list of pretty-printers name_re: regular-expression object to select printers subname_re: regular expression object to select subprinters or None if all are affected flag: True for Enable, False for Disable Returns: The number of printers affected. This is just for informational purposes for the user. """ total = 0 for printer in pretty_printers: if (hasattr(printer, "name") and name_re.match(printer.name) or hasattr(printer, "__name__") and name_re.match(printer.__name__)): if (hasattr(printer, "subprinters") and printer.subprinters is not None): if not subname_re: # Only record printers that change state. if printer_enabled_p(printer) != flag: for subprinter in printer.subprinters: if printer_enabled_p(subprinter): total += 1 # NOTE: We preserve individual subprinter settings. printer.enabled = flag else: # NOTE: Whether this actually disables the subprinter # depends on whether the printer's lookup function supports # the "enable" API. We can only assume it does. for subprinter in printer.subprinters: if subname_re.match(subprinter.name): # Only record printers that change state. if (printer_enabled_p(printer) and printer_enabled_p(subprinter) != flag): total += 1 subprinter.enabled = flag else: # This printer has no subprinters. # If the user does "disable pretty-printer .* .* foo" # should we disable printers that don't have subprinters? # How do we apply "foo" in this context? Since there is no # "foo" subprinter it feels like we should skip this printer. # There's still the issue of how to handle # "disable pretty-printer .* .* .*", and every other variation # that can match everything. For now punt and only support # "disable pretty-printer .* .*" (i.e. subname is elided) # to disable everything. if not subname_re: # Only record printers that change state. if printer_enabled_p(printer) != flag: total += 1 printer.enabled = flag return total def do_enable_pretty_printer (arg, flag): """Internal worker for enabling/disabling pretty-printers.""" (object_re, name_re, subname_re) = parse_printer_regexps(arg) total = 0 if object_re.match("global"): total += do_enable_pretty_printer_1(gdb.pretty_printers, name_re, subname_re, flag) cp = gdb.current_progspace() if object_re.match("progspace"): total += do_enable_pretty_printer_1(cp.pretty_printers, name_re, subname_re, flag) for objfile in gdb.objfiles(): if object_re.match(objfile.filename): total += do_enable_pretty_printer_1(objfile.pretty_printers, name_re, subname_re, flag) if flag: state = "enabled" else: state = "disabled" print ("%d %s %s" % (total, pluralize("printer", total), state)) # Print the total list of printers currently enabled/disabled. # This is to further assist the user in determining whether the result # is expected. Since we use regexps to select it's useful. show_pretty_printer_enabled_summary() # Enable/Disable one or more pretty-printers. # # This is intended for use when a broken pretty-printer is shipped/installed # and the user wants to disable that printer without disabling all the other # printers. # # A useful addition would be -v (verbose) to show each printer affected. class EnablePrettyPrinter (gdb.Command): """GDB command to enable the specified pretty-printer. Usage: enable pretty-printer [object-regexp [name-regexp]] OBJECT-REGEXP is a regular expression matching the objects to examine. Objects are "global", the program space's file, and the objfiles within that program space. NAME-REGEXP matches the name of the pretty-printer. Individual printers in a collection are named as printer-name;subprinter-name. """ def __init__(self): super(EnablePrettyPrinter, self).__init__("enable pretty-printer", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): """GDB calls this to perform the command.""" do_enable_pretty_printer(arg, True) class DisablePrettyPrinter (gdb.Command): """GDB command to disable the specified pretty-printer. Usage: disable pretty-printer [object-regexp [name-regexp]] OBJECT-REGEXP is a regular expression matching the objects to examine. Objects are "global", the program space's file, and the objfiles within that program space. NAME-REGEXP matches the name of the pretty-printer. Individual printers in a collection are named as printer-name;subprinter-name. """ def __init__(self): super(DisablePrettyPrinter, self).__init__("disable pretty-printer", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): """GDB calls this to perform the command.""" do_enable_pretty_printer(arg, False) def register_pretty_printer_commands(): """Call from a top level script to install the pretty-printer commands.""" InfoPrettyPrinter() EnablePrettyPrinter() DisablePrettyPrinter() register_pretty_printer_commands()