Skip to content

8. Pretty printing, Part 2

In the previous entry we covered the basics of pretty-printing: how printers are found, the use of the to_string method to customize display of a value, and the usefulness of autoloading.  This is sufficient for simple objects, but there are a few additions which are helpful with more complex data types.  This post will explain the other printer methods used by gdb, and will explain how pretty-printing interacts with MI, the gdb machine interface.

Python-gdb’s internal model is that a value can be printed in two parts: its immediate value, and its children.  The immediate value is whatever is returned by the to_string method.  Children are any sub-objects associated with the current object; for instance, a structure’s children would be its fields, while an array’s children would be its elements.

When pretty-printing from the CLI, gdb will call a printer’s “children” method to fetch a list of children, which it will then print.  This method can return any iterable object which, when iterated over, returns pairs. The first item in the pair is the “name” of the child, which gdb might print to give the user some help, and the second item in the pair is a value. This value can be be a string, or a Python value, or an instance of gdb.Value.

Notice how “pretty-printers” don’t actually print anything?  Funny.  The reason for this is to separate the printing logic from the data-structure-dissection logic.  This way, we can easily implement support for gdb options like “set print pretty” (which itself has nothing to do with this style of pretty-printing — sigh. Maybe we need a new name) or “set print elements“, or even add new print-style options, without having to modify every printer object in existence.

Gdb tries to be smart about how it iterates over the children returned by the children method.  If your data structure potentially has many children, you should write an iterator which computes them lazily.  This way, only the children which will actually be printed will be computed.

There’s one more method that a pretty-printer can provide: display_hint.  This method can return a string that gives gdb (or the MI user, see below) a hint as to how to display this object.  Right now the only recognizedd hint is “map”, which means that the children represent a map-like data structure.  In this case, gdb will assume that the elements of children alternate between keys and values, and will print appropriately.

We’ll probably define a couple more hint types.  I’ve been thinking about “array” and maybe “string”; I assume we’ll find we want more in the future.

Here’s a real-life printer showing the new features.  It prints a C++ map, specifically a std::tr1::unordered_map.  Please excuse the length — it is real code, printing a complex data structure, so there’s a bit to it.  Note that we define a generic iterator for the libstdc++ hash table implementation — this is for reuse in other printers.

import gdb
import itertools

class Tr1HashtableIterator:
    def __init__ (self, hash):
        self.count = 0
        self.n_buckets = hash['_M_bucket_count']
        if self.n_buckets == 0:
            self.node = False
        else:
            self.bucket = hash['_M_buckets']
            self.node = self.bucket[0]
            self.update ()

    def __iter__ (self):
        return self

    def update (self):
        # If we advanced off the end of the chain, move to the next
        # bucket.
        while self.node == 0:
            self.bucket = self.bucket + 1
            self.node = self.bucket[0]
            self.count = self.count + 1
            # If we advanced off the end of the bucket array, then
            # we're done.
            if self.count == self.n_buckets:
                self.node = False

    def next (self):
        if not self.node:
            raise StopIteration
        result = self.node.dereference()['_M_v']
        self.node = self.node.dereference()['_M_next']
        self.update ()
        return result

class Tr1UnorderedMapPrinter:
    "Print a tr1::unordered_map"

    def __init__ (self, typename, val):
        self.typename = typename
        self.val = val

    def to_string (self):
        return '%s with %d elements' % (self.typename, self.val['_M_element_count'])

    @staticmethod
    def flatten (list):
        for elt in list:
            for i in elt:
                yield i

    @staticmethod
    def format_one (elt):
        return (elt['first'], elt['second'])

    @staticmethod
    def format_count (i):
        return '[%d]' % i

    def children (self):
        counter = itertools.imap (self.format_count, itertools.count())
        # Map over the hash table and flatten the result.
        data = self.flatten (itertools.imap (self.format_one, Tr1HashtableIterator (self.val)))
        # Zip the two iterators together.
        return itertools.izip (counter, data)

    def display_hint (self):
        return 'map'

If you plan to write lazy children methods like this, I recommend reading up on the itertools package.

Here’s how a map looks when printed.  Notice the effect of the “map” hint:

(gdb) print uomap
$1 = std::tr1::unordered_map with 2 elements = {
  [23] = 0x804f766 "maude",
  [5] = 0x804f777 "liver"
}

The pretty-printer API was designed so that it could be used from MI.  This means that the same pretty-printer code that works for the CLI will also work in IDEs and other gdb GUIs — sometimes the GUI needs a few changes to make this work properly, but not many.  If you are an MI user, just note that the to_string and children methods are wired directly to varobjs; the change you may have to make is that a varobj‘s children can change dynamically.  We’ve also added new varobj methods to request raw printing (bypassing pretty-printers), to allow efficient selection of a sub-range of children, and to expose the display_hint method so that a GUI may take advantage of customized display types.  (This stuff is all documented in the manual.)

Next we’ll learn a bit about scripting gdb.  That is, instead of using Python to extend gdb from the inside, we’ll see how to use Python to drive gdb.

9 Comments

  1. dm wrote:

    I am trying to write a pretty printer for a linked list structure. I am stuck at comparing gdb.Value holding a pointer (the next field) to a NULL pointer (end of list). Any idea how to represent a NULL pointer as a gdb.Value, so that I can compare it to the ‘next’ field? Thanks.

    Thursday, November 12, 2009 at 4:57 pm | Permalink
  2. tom wrote:

    You can cast 0 to the appropriate pointer type.
    Or you can get the value of the pointer as a python long and compare it to zero.

    Friday, November 13, 2009 at 6:02 pm | Permalink
  3. dm wrote:

    Thanks, Tom! Casting 0 worked.

    Monday, November 16, 2009 at 6:52 pm | Permalink
  4. dm wrote:

    I wrote some pretty-printers that I am using in CLI. However, the types that have Python pretty-printers ignore “set print pretty off” setting. Is there a way to make Python pretty-printers print on one line? Thanks.
    P.S. I have a pretty-printer for a struct that has another embedded struct without pretty-printer. The fields of the outer struct are always printer on separate lines, but the embedded struct respects the “set pretty print” setting.

    Tuesday, January 19, 2010 at 8:12 pm | Permalink
  5. tom wrote:

    “set print pretty” should work. Please report a bug if not.

    Perhaps your python code is adding newlines itself?
    It is better to return a container of key/value pairs and let
    gdb do the formatting.

    Saturday, January 23, 2010 at 10:41 pm | Permalink
  6. dm wrote:

    No new lines. I am returning (key, value) pairs from the iterator. Where can I report a bug for this? Thanks.

    Monday, February 8, 2010 at 10:56 pm | Permalink
  7. tom wrote:

    @dm: you can report it to the gdb bugzilla, see http://sourceware.org/gdb/
    File it in the “python” category. Thanks.

    Thursday, February 11, 2010 at 5:21 pm | Permalink
  8. Another Tom wrote:

    I have been hacking around with pretty printers for some of my C++ classes and am really happy with the results. However I am having difficulty printing the address of one of my class in its ‘to_string’ method.

    I am not sure if this is down to my lack of Python knowledge (this is my first go a Python) or something I am not grasping with the Python-gdb implementation. Is there an appropriate forum to ask such a question or is here the best place?

    Saturday, July 3, 2010 at 2:42 am | Permalink
  9. graham wrote:

    Hi
    I was wondering how one writes a pretty printer that handles pointers to the type being printed.
    For example, I have a printer for a QFile type, but cannot see how to implement this for QFile*

    Friday, June 15, 2012 at 3:56 pm | Permalink

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*