From 7b51bc51e108b017e45141b15d940514a31260f6 Mon Sep 17 00:00:00 2001 From: Doug Evans Date: Tue, 2 Nov 2010 22:44:13 +0000 Subject: [PATCH] New python module gdb.printing, and new commands info pretty-printer, enable pretty-printer, disable pretty-printer. * NEWS: Mention them. * data-directory/Makefile.in (PYTHON_FILES): Add gdb/printing.py, gdb/command/__init__.py, gdb/command/pretty_printers.py. * python/lib/gdb/__init__.py: Install pretty-printer commands. * python/lib/gdb/printing.py: New file. * python/lib/gdb/command/__init__.py: New file. * python/lib/gdb/command/pretty_printers.py: New file. doc/ * gdb.texinfo (Pretty Printing): Expand into three sections, introduction, example, and commands. (Python API): Delete section Disabling Pretty-Printers, merge into Selecting Pretty-Printers. (Writing a Pretty-Printer): New section. Move the pretty-printer example here, and reformat to match python coding style. Add a second example using the gdb.printing module. (Python modules): Add gdb.printing. testsuite/ * gdb.python/py-pp-maint.c: New file. * gdb.python/py-pp-maint.exp: New file. * gdb.python/py-pp-maint.py: New file. --- gdb/ChangeLog | 12 + gdb/NEWS | 9 + gdb/data-directory/Makefile.in | 5 +- gdb/doc/ChangeLog | 11 + gdb/doc/gdb.texinfo | 329 ++++++++++++++-- gdb/python/lib/gdb/__init__.py | 4 + gdb/python/lib/gdb/command/__init__.py | 16 + gdb/python/lib/gdb/command/pretty_printers.py | 369 ++++++++++++++++++ gdb/python/lib/gdb/printing.py | 197 ++++++++++ gdb/testsuite/ChangeLog | 6 + gdb/testsuite/gdb.python/py-pp-maint.c | 68 ++++ gdb/testsuite/gdb.python/py-pp-maint.exp | 126 ++++++ gdb/testsuite/gdb.python/py-pp-maint.py | 74 ++++ 13 files changed, 1184 insertions(+), 42 deletions(-) create mode 100644 gdb/python/lib/gdb/command/__init__.py create mode 100644 gdb/python/lib/gdb/command/pretty_printers.py create mode 100644 gdb/python/lib/gdb/printing.py create mode 100644 gdb/testsuite/gdb.python/py-pp-maint.c create mode 100644 gdb/testsuite/gdb.python/py-pp-maint.exp create mode 100644 gdb/testsuite/gdb.python/py-pp-maint.py diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 1e764df6fa..684372252a 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,15 @@ +2010-11-02 Doug Evans + + New python module gdb.printing, and new commands info pretty-printer, + enable pretty-printer, disable pretty-printer. + * NEWS: Mention them. + * data-directory/Makefile.in (PYTHON_FILES): Add gdb/printing.py, + gdb/command/__init__.py, gdb/command/pretty_printers.py. + * python/lib/gdb/__init__.py: Install pretty-printer commands. + * python/lib/gdb/printing.py: New file. + * python/lib/gdb/command/__init__.py: New file. + * python/lib/gdb/command/pretty_printers.py: New file. + 2010-11-02 Tom Tromey * NEWS: Mention Guile removal. diff --git a/gdb/NEWS b/gdb/NEWS index c870f01074..a71461cbf8 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -16,6 +16,15 @@ It contains a collection of utilities for working with gdb.Types objects: get_basic_type, has_field, make_enum_dict. + ** Module gdb.printing has been added. + It contains utilities for writing and registering pretty-printers. + New classes: PrettyPrinter, SubPrettyPrinter, + RegexpCollectionPrettyPrinter. + New function: register_pretty_printer. + + ** New commands "info pretty-printers", "enable pretty-printer" and + "disable pretty-printer" have been added. + * C++ Improvements: ** GDB now puts template parameters in scope when debugging in an diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in index 9153a29a6c..84c618c4a5 100644 --- a/gdb/data-directory/Makefile.in +++ b/gdb/data-directory/Makefile.in @@ -52,7 +52,10 @@ PYTHON_DIR = python PYTHON_INSTALL_DIR = $(DESTDIR)/$(GDB_DATADIR)/$(PYTHON_DIR) PYTHON_FILES = \ gdb/__init__.py \ - gdb/types.py + gdb/types.py \ + gdb/printing.py \ + gdb/command/__init__.py \ + gdb/command/pretty_printers.py FLAGS_TO_PASS = \ "prefix=$(prefix)" \ diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index 052f580909..a8df072696 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,14 @@ +2010-11-02 Doug Evans + + * gdb.texinfo (Pretty Printing): Expand into three sections, + introduction, example, and commands. + (Python API): Delete section Disabling Pretty-Printers, merge into + Selecting Pretty-Printers. + (Writing a Pretty-Printer): New section. Move the pretty-printer + example here, and reformat to match python coding style. Add a second + example using the gdb.printing module. + (Python modules): Add gdb.printing. + 2010-10-29 Doug Evans * gdb.texinfo (Python): Fix long line. diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index d4a04f778a..069dce4bb4 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -8127,8 +8127,60 @@ Show whether C@t{++} virtual function tables are pretty printed, or not. Python code. It greatly simplifies the display of complex objects. This mechanism works for both MI and the CLI. -For example, here is how a C@t{++} @code{std::string} looks without a -pretty-printer: +@menu +* Pretty-Printer Introduction:: Introduction to pretty-printers +* Pretty-Printer Example:: An example pretty-printer +* Pretty-Printer Commands:: Pretty-printer commands +@end menu + +@node Pretty-Printer Introduction +@subsection Pretty-Printer Introduction + +When @value{GDBN} prints a value, it first sees if there is a pretty-printer +registered for the value. If there is then @value{GDBN} invokes the +pretty-printer to print the value. Otherwise the value is printed normally. + +Pretty-printers are normally named. This makes them easy to manage. +The @samp{info pretty-printer} command will list all the installed +pretty-printers with their names. +If a pretty-printer can handle multiple data types, then its +@dfn{subprinters} are the printers for the individual data types. +Each such subprinter has its own name. +The format of the name is @var{printer-name}:@var{subprinter-name}. + +Pretty-printers are installed by @dfn{registering} them with @value{GDBN}. +Typically they are automatically loaded and registered when the corresponding +debug information is loaded, thus making them available without having to +do anything special. + +There are three places where a pretty-printer can be registered. + +@itemize @bullet +@item +Pretty-printers registered globally are available when debugging +all inferiors. + +@item +Pretty-printers registered with a program space are available only +when debugging that program. +@xref{Progspaces In Python}, for more details on program spaces in Python. + +@item +Pretty-printers registered with an objfile are loaded and unloaded +with the corresponding objfile (e.g., shared library). +@xref{Objfiles In Python}, for more details on objfiles in Python. +@end itemize + +@xref{Selecting Pretty-Printers}, for further information on how +pretty-printers are selected, + +@xref{Writing a Pretty-Printer}, for implementing pretty printers +for new types. + +@node Pretty-Printer Example +@subsection Pretty-Printer Example + +Here is how a C@t{++} @code{std::string} looks without a pretty-printer: @smallexample (@value{GDBP}) print s @@ -8153,8 +8205,91 @@ With a pretty-printer for @code{std::string} only the contents are printed: $2 = "abcd" @end smallexample -For implementing pretty printers for new types you should read the Python API -details (@pxref{Pretty Printing API}). +@node Pretty-Printer Commands +@subsection Pretty-Printer Commands +@cindex pretty-printer commands + +@table @code +@kindex info pretty-printer +@item info pretty-printer [@var{object-regexp} [@var{name-regexp}]] +Print the list of installed pretty-printers. +This includes disabled pretty-printers, which are marked as such. + +@var{object-regexp} is a regular expression matching the objects +whose pretty-printers to list. +Objects can be @code{global}, the program space's file +(@pxref{Progspaces In Python}), +and the object files within that program space (@pxref{Objfiles In Python}). +@xref{Selecting Pretty-Printers}, for details on how @value{GDBN} +looks up a printer from these three objects. + +@var{name-regexp} is a regular expression matching the name of the printers +to list. + +@kindex disable pretty-printer +@item disable pretty-printer [@var{object-regexp} [@var{name-regexp}]] +Disable pretty-printers matching @var{object-regexp} and @var{name-regexp}. +A disabled pretty-printer is not forgotten, it may be enabled again later. + +@kindex enable pretty-printer +@item enable pretty-printer [@var{object-regexp} [@var{name-regexp}]] +Enable pretty-printers matching @var{object-regexp} and @var{name-regexp}. +@end table + +Example: + +Suppose we have three pretty-printers installed: one from library1.so +named @code{foo} that prints objects of type @code{foo}, and +another from library2.so named @code{bar} that prints two types of objects, +@code{bar1} and @code{bar2}. + +@smallexample +(gdb) info pretty-printer +library1.so: + foo +library2.so: + bar + bar1 + bar2 +(gdb) info pretty-printer library2 +library2.so: + bar + bar1 + bar2 +(gdb) disable pretty-printer library1 +1 printer disabled +2 of 3 printers enabled +(gdb) info pretty-printer +library1.so: + foo [disabled] +library2.so: + bar + bar1 + bar2 +(gdb) disable pretty-printer library2 bar:bar1 +1 printer disabled +1 of 3 printers enabled +(gdb) info pretty-printer library2 +library1.so: + foo [disabled] +library2.so: + bar + bar1 [disabled] + bar2 +(gdb) disable pretty-printer library2 bar +1 printer disabled +0 of 3 printers enabled +(gdb) info pretty-printer library2 +library1.so: + foo [disabled] +library2.so: + bar [disabled] + bar1 [disabled] + bar2 +@end smallexample + +Note that for @code{bar} the entire printer can be disabled, +as can each individual subprinter. @node Value History @section Value History @@ -20484,7 +20619,7 @@ situation, a Python @code{KeyboardInterrupt} exception is thrown. * Types In Python:: Python representation of types. * Pretty Printing API:: Pretty-printing values. * Selecting Pretty-Printers:: How GDB chooses a pretty-printer. -* Disabling Pretty-Printers:: Disabling broken printers. +* Writing a Pretty-Printer:: Writing a Pretty-Printer. * Inferiors In Python:: Python representation of inferiors (processes) * Threads In Python:: Accessing inferior threads from Python. * Commands In Python:: Implementing new commands in Python. @@ -21349,12 +21484,13 @@ printer exists, then this returns @code{None}. The Python list @code{gdb.pretty_printers} contains an array of functions or callable objects that have been registered via addition -as a pretty-printer. +as a pretty-printer. Printers in this list are called @code{global} +printers, they're available when debugging all inferiors. Each @code{gdb.Progspace} contains a @code{pretty_printers} attribute. Each @code{gdb.Objfile} also contains a @code{pretty_printers} attribute. -A function on one of these lists is passed a single @code{gdb.Value} +Each function on these lists is passed a single @code{gdb.Value} argument and should return a pretty-printer object conforming to the interface definition above (@pxref{Pretty Printing API}). If a function cannot create a pretty-printer for the value, it should return @@ -21362,9 +21498,8 @@ cannot create a pretty-printer for the value, it should return @value{GDBN} first checks the @code{pretty_printers} attribute of each @code{gdb.Objfile} in the current program space and iteratively calls -each enabled function (@pxref{Disabling Pretty-Printers}) -in the list for that @code{gdb.Objfile} until it receives -a pretty-printer object. +each enabled lookup routine in the list for that @code{gdb.Objfile} +until it receives a pretty-printer object. If no pretty-printer is found in the objfile lists, @value{GDBN} then searches the pretty-printer list of the current program space, calling each enabled function until an object is returned. @@ -21377,20 +21512,43 @@ given list, functions are always invoked from the head of the list, and iterated over sequentially until the end of the list, or a printer object is returned. +For various reasons a pretty-printer may not work. +For example, the underlying data structure may have changed and +the pretty-printer is out of date. + +The consequences of a broken pretty-printer are severe enough that +@value{GDBN} provides support for enabling and disabling individual +printers. For example, if @code{print frame-arguments} is on, +a backtrace can become highly illegible if any argument is printed +with a broken printer. + +Pretty-printers are enabled and disabled by attaching an @code{enabled} +attribute to the registered function or callable object. If this attribute +is present and its value is @code{False}, the printer is disabled, otherwise +the printer is enabled. + +@node Writing a Pretty-Printer +@subsubsection Writing a Pretty-Printer +@cindex writing a pretty-printer + +A pretty-printer consists of two parts: a lookup function to detect +if the type is supported, and the printer itself. + Here is an example showing how a @code{std::string} printer might be -written: +written. @xref{Pretty Printing API}, for details on the API this class +must provide. @smallexample -class StdStringPrinter: +class StdStringPrinter(object): "Print a std::string" - def __init__ (self, val): + def __init__(self, val): self.val = val - def to_string (self): + def to_string(self): return self.val['_M_dataplus']['_M_p'] - def display_hint (self): + def display_hint(self): return 'string' @end smallexample @@ -21398,15 +21556,13 @@ And here is an example showing how a lookup function for the printer example above might be written. @smallexample -def str_lookup_function (val): - +def str_lookup_function(val): lookup_tag = val.type.tag - regex = re.compile ("^std::basic_string$") if lookup_tag == None: return None - if regex.match (lookup_tag): - return StdStringPrinter (val) - + regex = re.compile("^std::basic_string$") + if regex.match(lookup_tag): + return StdStringPrinter(val) return None @end smallexample @@ -21442,8 +21598,8 @@ To continue the @code{std::string} example (@pxref{Pretty Printing API}), this code might appear in @code{gdb.libstdcxx.v6}: @smallexample -def register_printers (objfile): - objfile.pretty_printers.add (str_lookup_function) +def register_printers(objfile): + objfile.pretty_printers.add(str_lookup_function) @end smallexample @noindent @@ -21451,27 +21607,92 @@ And then the corresponding contents of the auto-load file would be: @smallexample import gdb.libstdcxx.v6 -gdb.libstdcxx.v6.register_printers (gdb.current_objfile ()) +gdb.libstdcxx.v6.register_printers(gdb.current_objfile()) @end smallexample -@node Disabling Pretty-Printers -@subsubsection Disabling Pretty-Printers -@cindex disabling pretty-printers +The previous example illustrates a basic pretty-printer. +There are a few things that can be improved on. +The printer doesn't have a name, making it hard to identify in a +list of installed printers. The lookup function has a name, but +lookup functions can have arbitrary, even identical, names. -For various reasons a pretty-printer may not work. -For example, the underlying data structure may have changed and -the pretty-printer is out of date. +Second, the printer only handles one type, whereas a library typically has +several types. One could install a lookup function for each desired type +in the library, but one could also have a single lookup function recognize +several types. The latter is the conventional way this is handled. +If a pretty-printer can handle multiple data types, then its +@dfn{subprinters} are the printers for the individual data types. -The consequences of a broken pretty-printer are severe enough that -@value{GDBN} provides support for enabling and disabling individual -printers. For example, if @code{print frame-arguments} is on, -a backtrace can become highly illegible if any argument is printed -with a broken printer. +The @code{gdb.printing} module provides a formal way of solving these +problems (@pxref{gdb.printing}). +Here is another example that handles multiple types. -Pretty-printers are enabled and disabled by attaching an @code{enabled} -attribute to the registered function or callable object. If this attribute -is present and its value is @code{False}, the printer is disabled, otherwise -the printer is enabled. +These are the types we are going to pretty-print: + +@smallexample +struct foo @{ int a, b; @}; +struct bar @{ struct foo x, y; @}; +@end smallexample + +Here are the printers: + +@smallexample +class fooPrinter: + """Print a foo object.""" + + def __init__(self, val): + self.val = val + + def to_string(self): + return ("a=<" + str(self.val["a"]) + + "> b=<" + str(self.val["b"]) + ">") + +class barPrinter: + """Print a bar object.""" + + def __init__(self, val): + self.val = val + + def to_string(self): + return ("x=<" + str(self.val["x"]) + + "> y=<" + str(self.val["y"]) + ">") +@end smallexample + +This example doesn't need a lookup function, that is handled by the +@code{gdb.printing} module. Instead a function is provided to build up +the object that handles the lookup. + +@smallexample +import gdb.printing + +def build_pretty_printer(): + pp = gdb.printing.RegexpCollectionPrettyPrinter( + "my_library") + pp.add_printer('foo', '^foo$', fooPrinter) + pp.add_printer('bar', '^bar$', barPrinter) + return pp +@end smallexample + +And here is the autoload support: + +@smallexample +import gdb.printing +import my_library +gdb.printing.register_pretty_printer( + gdb.current_objfile(), + my_library.build_pretty_printer()) +@end smallexample + +Finally, when this printer is loaded into @value{GDBN}, here is the +corresponding output of @samp{info pretty-printer}: + +@smallexample +(gdb) info pretty-printer +my_library.so: + my_library + foo + bar +@end smallexample @node Inferiors In Python @subsubsection Inferiors In Python @@ -22920,16 +23141,42 @@ top of the source tree to the source search path. @subsection Python modules @cindex python modules -@c It is assumed that at least one more module will be added before -@c the next release of gdb. Thus we use a menu here. @value{GDBN} comes with a module to assist writing Python code. @menu +* gdb.printing:: Building and registering pretty-printers. * gdb.types:: Utilities for working with types. @end menu +@node gdb.printing +@subsubsection gdb.printing +@cindex gdb.printing + +This module provides a collection of utilities for working with +pretty-printers. + +@table @code +@item PrettyPrinter (@var{name}, @var{subprinters}=None) +This class specifies the API that makes @samp{info pretty-printer}, +@samp{enable pretty-printer} and @samp{disable pretty-printer} work. +Pretty-printers should generally inherit from this class. + +@item SubPrettyPrinter (@var{name}) +For printers that handle multiple types, this class specifies the +corresponding API for the subprinters. + +@item RegexpCollectionPrettyPrinter (@var{name}) +Utility class for handling multiple printers, all recognized via +regular expressions. +@xref{Writing a Pretty-Printer}, for an example. + +@item register_pretty_printer (@var{obj}, @var{printer}) +Register @var{printer} with the pretty-printer list of @var{obj}. +@end table + @node gdb.types @subsubsection gdb.types +@cindex gdb.types This module provides a collection of utilities for working with @code{gdb.Types} objects. diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 102939be32..d084fe4d3b 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -12,3 +12,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + +import gdb.command.pretty_printers + +gdb.command.pretty_printers.register_pretty_printer_commands() diff --git a/gdb/python/lib/gdb/command/__init__.py b/gdb/python/lib/gdb/command/__init__.py new file mode 100644 index 0000000000..4263473c8b --- /dev/null +++ b/gdb/python/lib/gdb/command/__init__.py @@ -0,0 +1,16 @@ +# Copyright (C) 2010 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 . + + diff --git a/gdb/python/lib/gdb/command/pretty_printers.py b/gdb/python/lib/gdb/command/pretty_printers.py new file mode 100644 index 0000000000..4e341a57e6 --- /dev/null +++ b/gdb/python/lib/gdb/command/pretty_printers.py @@ -0,0 +1,369 @@ +# Pretty-printer commands. +# Copyright (C) 2010 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 = copy.copy(pretty_printers) + sorted_pretty_printers.sort(lambda x, y: + cmp(self.printer_name(x), + self.printer_name(y))) + 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 = copy.copy(printer.subprinters) + sorted_subprinters.sort(lambda x, y: + cmp(self.printer_name(x), + self.printer_name(y))) + 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"): + 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() diff --git a/gdb/python/lib/gdb/printing.py b/gdb/python/lib/gdb/printing.py new file mode 100644 index 0000000000..0971375d2d --- /dev/null +++ b/gdb/python/lib/gdb/printing.py @@ -0,0 +1,197 @@ +# Pretty-printer utilities. +# Copyright (C) 2010 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 . + +"""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): + """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. + + 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__. + + Returns: + Nothing. + + Raises: + TypeError: A problem with the type of the printer. + ValueError: The printer's name contains a colon ":". + + 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 obj is None: + 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" % + (printer.name, obj.filename)) + + if hasattr(printer, "name"): + if not isinstance(printer.name, basestring): + raise TypeError("printer name is not a string") + # If printer provides a name, make sure it doesn't contain ":". + # Colon is used by the info/enable/disable pretty-printer commands + # to delimit subprinters. + if printer.name.find(":") >= 0: + raise ValueError("colon ':' 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. + if (printer.name in + [p.name for p in obj.pretty_printers if hasattr(p, "name")]): + raise RuntimeError("pretty-printer already registered: %s" % + printer.name) + + 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: + 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 diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index da64857b5f..4b24a22ae8 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,9 @@ +2010-11-02 Doug Evans + + * gdb.python/py-pp-maint.c: New file. + * gdb.python/py-pp-maint.exp: New file. + * gdb.python/py-pp-maint.py: New file. + 2010-11-02 Tom Tromey * gdb.base/default.exp: Remove "scheme" from language list. diff --git a/gdb/testsuite/gdb.python/py-pp-maint.c b/gdb/testsuite/gdb.python/py-pp-maint.c new file mode 100644 index 0000000000..e72bd419ee --- /dev/null +++ b/gdb/testsuite/gdb.python/py-pp-maint.c @@ -0,0 +1,68 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2010 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 . */ + +#include + +struct function_lookup_test +{ + int x,y; +}; + +void +init_flt (struct function_lookup_test *p, int x, int y) +{ + p->x = x; + p->y = y; +} + +struct s +{ + int a; + int *b; +}; + +struct ss +{ + struct s a; + struct s b; +}; + +void +init_s (struct s *s, int a) +{ + s->a = a; + s->b = &s->a; +} + +void +init_ss (struct ss *s, int a, int b) +{ + init_s (&s->a, a); + init_s (&s->b, b); +} + +int +main () +{ + struct function_lookup_test flt; + struct ss ss; + + init_flt (&flt, 42, 43); + init_ss (&ss, 1, 2); + + return 0; /* break to inspect */ +} diff --git a/gdb/testsuite/gdb.python/py-pp-maint.exp b/gdb/testsuite/gdb.python/py-pp-maint.exp new file mode 100644 index 0000000000..0aa79568e8 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-pp-maint.exp @@ -0,0 +1,126 @@ +# Copyright (C) 2010 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 . + +# This file is part of the GDB testsuite. It tests Python-based +# pretty-printing for the CLI. + +if $tracelevel then { + strace $tracelevel +} + +if [is_remote host] { + untested "py-pp-maint.exp can only be run locally" + return -1 +} + +load_lib gdb-python.exp + +set testfile "py-pp-maint" +set srcfile ${testfile}.c +set binfile ${objdir}/${subdir}/${testfile} + +# Start with a fresh gdb. +gdb_exit +gdb_start + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable "debug"] != "" } { + untested "Couldn't compile ${srcfile}" + return -1 +} + +gdb_reinitialize_dir $srcdir/$subdir +gdb_load ${binfile} + +if ![runto_main ] then { + fail "Can't run to main" + return -1 +} + +# Ensure sys.path, et.al. are initialized properly. +gdb_check_python_config + +gdb_test "b [gdb_get_line_number {break to inspect} ${testfile}.c ]" \ + ".*Breakpoint.*" +gdb_test "continue" ".*Breakpoint.*" + +set python_file ${srcdir}/${subdir}/${testfile}.py + +gdb_test_no_output "python execfile ('${python_file}')" "" + +gdb_test "info pretty-printer" \ + {.*function_lookup_test.*pp-test.*struct ss.*} + +gdb_test "info pretty-printer global .*function" \ + {.*function_lookup_test.*} + +gdb_test "info pretty-printer .* pp-test" \ + {.*pp-test.*struct ss.*} + +gdb_test "print flt" " = x=<42> y=<43>" \ + "print flt enabled #1" + +gdb_test "print ss" " = a= b=<$hex>> b= b=<$hex>>" \ + "print ss enabled #1" + +gdb_test "disable pretty-printer" \ + "5 printers disabled.*0 of 5 printers enabled" + +gdb_test "disable pretty-printer global" \ + "0 printers disabled.*0 of 5 printers enabled" + +gdb_test "disable pretty-printer global lookup_function_lookup_test" \ + "0 printers disabled.*0 of 5 printers enabled" + +gdb_test "disable pretty-printer global pp-test:.*" \ + "0 printers disabled.*0 of 5 printers enabled" + +gdb_test "info pretty-printer global .*function" \ + {.*function_lookup_test \[disabled\].*} + +gdb_test "info pretty-printer .* pp-test" \ + {.*pp-test.*struct ss \[disabled\].*} + +gdb_test "print flt" " = {x = 42, y = 43}" \ + "print flt disabled" + +gdb_test "print ss" " = {a = {a = 1, b = $hex}, b = {a = 2, b = $hex}}" \ + "print ss disabled" + +gdb_test "enable pretty-printer global lookup_function_lookup_test" \ + "1 printer enabled.*1 of 5 printers enabled" + +# This doesn't enable any printers because each subprinter in the collection +# is still individually disabled. But this is still needed, to enable the +# collection itself. +gdb_test "enable pretty-printer global pp-test" \ + "0 printers enabled.*1 of 5 printers enabled" + +gdb_test "enable pretty-printer global pp-test:.*ss.*" \ + "2 printers enabled.*3 of 5 printers enabled" + +gdb_test "enable pretty-printer global pp-test:.*s.*" \ + "2 printers enabled.*5 of 5 printers enabled" + +gdb_test "info pretty-printer" \ + {.*function_lookup_test.*pp-test.*struct ss.*} + +gdb_test "print flt" " = x=<42> y=<43>" \ + "print flt re-enabled" + +gdb_test "print ss" " = a= b=<$hex>> b= b=<$hex>>" \ + "print ss re-enabled" diff --git a/gdb/testsuite/gdb.python/py-pp-maint.py b/gdb/testsuite/gdb.python/py-pp-maint.py new file mode 100644 index 0000000000..5638a42cd5 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-pp-maint.py @@ -0,0 +1,74 @@ +# Copyright (C) 2010 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 . + +# This file is part of the GDB testsuite. It tests python pretty +# printers. + +import re +import gdb.types +import gdb.printing + + +def lookup_function_lookup_test(val): + class PrintFunctionLookup(object): + def __init__(self, val): + self.val = val + + def to_string(self): + return ("x=<" + str(self.val["x"]) + + "> y=<" + str(self.val["y"]) + ">") + + typename = gdb.types.get_basic_type(val.type).tag + # Note: typename could be None. + if typename == "function_lookup_test": + return PrintFunctionLookup(val) + return None + + +class pp_s: + def __init__(self, val): + self.val = val + + def to_string(self): + a = self.val["a"] + b = self.val["b"] + if a.address != b: + raise Exception("&a(%s) != b(%s)" % (str(a.address), str(b))) + return "a=<" + str(self.val["a"]) + "> b=<" + str(self.val["b"]) + ">" + + +class pp_ss: + def __init__(self, val): + self.val = val + + def to_string(self): + return "a=<" + str(self.val["a"]) + "> b=<" + str(self.val["b"]) + ">" + + +def build_pretty_printer(): + pp = gdb.printing.RegexpCollectionPrettyPrinter("pp-test") + + pp.add_printer('struct s', '^struct s$', pp_s) + pp.add_printer('s', '^s$', pp_s) + + # Use a lambda this time to exercise doing things this way. + pp.add_printer('struct ss', '^struct ss$', lambda val: pp_ss(val)) + pp.add_printer('ss', '^ss$', lambda val: pp_ss(val)) + + return pp + + +gdb.printing.register_pretty_printer(gdb, lookup_function_lookup_test) +gdb.printing.register_pretty_printer(gdb, build_pretty_printer())