gdbWatchPoint

Debugging with pretty printers in GDB - part 3

Last updated 5th Oct 2021

In this tutorial, Software Architect Mark Williamson follows on from our previous tutorial on advanced pretty-printers for GDB, showing how to configure and control the behaviour of your printers.

Read this article alongside the example code from the previous tutorial and the GDB documentation on writing pretty printers.

Printing What You Need

In our previous tutorial on GDB pretty-printers, we wrote code to display the data structures used in an imaginary vector graphics program.  Using these, we were able to provide clearer presentation of our data and to automatically walk data structures for display.

With our pretty printers loaded we can, for instance, print our point_t structure and see a friendly representation of it:

(gdb) print p
$2 = Point(1, 2)

But what if we actually want the default GDB representation, including all the fields?  Pretty printers aren't obliged to show us the complete contents of the structure - indeed, omitting rarely-needed information is one reason to write a pretty-printer.

We can revert to the default GDB formatting for just this command using the /r formatting switch:

(gdb) print /r p 
$3 = {
 x = 1,
 y = 2
}

This allows us to view the raw data on a case-by-case basis without losing the benefit of pretty-printing in other contexts.

Controlling Pretty Printers

We'll sometimes want to temporarily turn off a particular pretty-printer - for instance, when we know we're interested in the detailed contents of a structure during a debug session.  GDB provides a set of commands to list and control pretty-printers.

Listing Pretty Printers

First of all, lets see what pretty printers are registered in our system.  Use the info pretty-printer command.  In a debug session of our vector drawing example, this gives us:

(gdb) info pretty-printer  
global pretty-printers:
 builtin
   mpx_bound128
 my_pp_func

After the list of builtin printers, we can see the my_pp_func callback we registered in our previous tutorial.  This was a single function, registered with gdb.pretty_printers.append(), which we made return the correct printer for each structure we could handle.  Since there's only one callback, its represented in this list as if it were a single pretty-printer.  By default, GDB identifies it by the callback's function name.

Switching Off Our Pretty Printer

We can now switch this pretty-printer on and off, by name:

(gdb) disable pretty-printer global my_pp_func 
1 printer disabled
1 of 2 printers enabled

Having disabled our pretty-printers we will now see the raw, default GDB formatting when we print our point_t structure, without having to specify /r:

(gdb) print p 
$7 = {
 x = 1,
 y = 2
}

Since all our pretty printers are handled by the one callback, this setting will apply to any other structures that were handled by our pretty-printers.  For instance, our drawing_element_t:

(gdb) print last 
$4 = {
 kind = ELEMENT_POINT,
 el = {
   point = 0x7fffffffd988,
   line = 0x7fffffffd988
 },
 next = 0x0
}

The corresponding command can be used to re-enable them:

(gdb) enable pretty-printer global my_pp_func                                                   
1 printer enabled
2 of 2 printers enabled

UDB Time Travel Debugger
Find and fix test failures in minutes  - including C/C++ race conditions, deadlocks and memory corruptions
Learn more »

Flexible Control

We've seen that we can control our pretty-printers by switching on and off the top-level callback we registered for them.  The user experience isn't great, though.

Firstly, the name my_pp_func is not very descriptive and leaks information about our Python code.  Secondly, we've ended up with just one setting to toggle all of our pretty-printers at once.  As we write more pretty-printers, we're going to want to control them more precisely.

In this section, we'll learn how to build configurability into pretty printers using the APIs GDB provides.  Hint: the way we show here offers most flexibility but, if you're looking for a quick fix, you have permission skip to the end!

Meet the gdb.printing classes

GDB provides additional classes within the gdb.printing module that can make our printers easier to use (see the official gdb.printing documentation).  Interestingly, this contains a class called PrettyPrinter.   We will need to use this but beware: it isn't simply a base class for pretty printers.

In fact, note that we have not needed to use the PrettyPrinter class at all so far.  It shouldn't be confused with the core implementation of a pretty-printer, which is just a plain Python object that implements the pretty-printer API:

class MyPrinter:
    def __init__(self, val):
        ...
    def to_string(self):
        ...
    def children(self):
        ...
    def display_hint(self):
        ...

The PrettyPrinter class, on the other hand, is used to describe a pretty printer to GDB.  It makes it possible to look up and control pretty-printer implementations by name.  For more sophisticated uses, it's possible to use combinations of this and SubPrettyPrinter directly to create hierarchies of printers (though that's beyond the scope of this article).

To add listing and configuration to our point_t printer class (PointPrinter), we could add something like the following to our printers.py:

class PointPrinterControl(gdb.printing.PrettyPrinter):
    def __init__(self):
        super().__init__('point_t printer')

    def __call__(self, val):
        return PointPrinter(val) if val.type.name == 'point_t' else None

gdb.pretty_printers.append(PointPrinterControl())

This class encapsulates knowledge of the name we want to display to the user - point_t printer - and the lookup behaviour previously provided by my_pp_func() (which no longer needs to handle this type).  If we now attempt to list our pretty-printers we get a more interesting result:

(gdb) info pretty-printer 
global pretty-printers:
  builtin
    mpx_bound128
  my_pp_func
  point_t printer

We're seeing our new printer as a separate entry.  We can use enable / disable pretty-printer global "point_t printer" to turn it on and off, independently of our other printers.

Wrapping it all up

It's not really necessary to define such a control class for each pretty-printer we've written.  We can use a little Python magic to simplify things:

class PrinterControl(gdb.printing.PrettyPrinter):
    def __init__(self, type_name, printer):
        super().__init__(type_name)
        self.printer = printer

    def __call__(self, val):
        return self.printer(val) if val.type.name == self.name else None

def type_printer(type_name):
    # Decorator for pretty printers.
    def _register_printer(printer):
        gdb.printing.register_pretty_printer(
            None,
            PrinterControl(type_name, printer)
        )
    return _register_printer

Here, we've defined a PrinterControl class that can handle any of our existing printers.  It takes the C type name and the printer itself as constructor arguments; we are reusing the C type name as the name of the pretty-printer when we register it with GDB.

We can use this @type_printer() decorator to annotate any of our printers and make it visible to GDB's printer configuration.  Applying this to our PointPrinter looks like:

@type_printer('point_t')
class PointPrinter:
    ...

This automatically adds a lookup function that will call this printer for instances of the C point_t type and, via the PrettyPrinter class, adds configurability.

Bringing it all together

By adding this decorator to all of our classes, we create a library of named and configurable pretty printers:

(gdb) info pretty-printer                  
global pretty-printers:
 builtin
   mpx_bound128
 drawing_element_t
 line_t [disabled]
 point_t

Each printer here can be enabled and disabled individually, by name, using enable / disable pretty-printer global TYPE_NAME.  In the example shown above, the line_t printer has been separately disabled.

The Power Of Configuration

We've seen how to build a configurable library of pretty-printers from the ground up using GDB's PrettyPrinter class - on top of our previous examples, we've added naming support and the ability to control individual printers.

We have the full power of Python available, so enormous flexibility is possible here.  Use the SubPrettyPrinter and other provided classes to create more sophisticated hierarchies of printers.  Use custom code to provide more complex registration and lookup behaviours.

By building on these techniques you can build a sophisticated library of printers whilst retaining fine-grained control of display.

The source of the examples shown above can be downloaded for reference:

Bonus - A shortcut

In practice, our needs are often simple - a mapping of C type names to printer classes is likely to be adequate.  In our example above we built this from lower-level components, allowing further customisation to be built through additional code.  However, for most straightforward mappings of types to printers GDB's RegexpCollectionPrettyPrinter class will already provide the best solution.

This class achieves a similar goal to our example code (with the additional ability to specify regular expression matches for C types) and is likely to be a good place to start your journey.  Visit the GDB docs example of registering simple pretty printers and scroll to the bottom for a concrete example.

UDB Time Travel Debugger
Find and fix bugs in minutes  - save time on debugging
Learn more »

Don’t miss the next debugging tutorial: sign up to the gdbWatchPoint mailing list now!

Get tutorials straight to your inbox
Become a GDB Power User. Get Greg's debugging tips directly in your inbox every 2 weeks.

A dedicated resource to learn about debugging in GDB by industry leader in debugging and founder of Undo

Meet Greg