Writing custom GDB pretty-printers

Index, feed.

[ Date | 2018-05-01 03:12 -0400 ]
[ Current movie | Cube ]

The GNU Project Debugger can be scripted in at least three languages:

Example user-defined command

Let us say you have data structures WORD_DESC and WORD_LIST defined as follows (those come from the GNU Bash source code in command.h):

typedef struct word_desc {
  char *word;     /* Zero terminated string. */
  int flags;      /* Flags associated with this word. */
} WORD_DESC;

typedef struct word_list {
  struct word_list *next;
  WORD_DESC *word;
} WORD_LIST;

Let us further assume that you have a pointer to a WORD_LIST in variable wl. Here is how GDB's built-in print (or p for short) displays it:

(gdb) p *wl
$1 = {next = 0x73cc48, word = 0x73ca68}

This is nice (the field names are listed, yay), but not as exciting as it could be; for example, the field values are printed as numeric addresses, whereas it would be more enlightening to print the WORD_DESC structure (for word) and the rest of the linked list (for next).

Improved, custom results can be obtained using user-defined commands, such as:

define print_word_desc
  set var $wd = $arg0
  printf "(word-desc (word \"%s\") (flags %d))\n", \
    $wd->word, $wd->flags
end

define print_word_list
  set var $wl = $arg0
  while $wl != 0
    print_word_desc $wl->word
    set var $wl = $wl->next
  end
end

(Those definitions can be entered directly at the (gdb) prompt.)

Now we can use print_word_list to pretty-print a WORD_LIST, and also print_word_desc to print a WORD_DESC:

(gdb) print_word_desc wd
(word-desc (word "date") (flags 0))

(gdb) print_word_list wl
(word-desc (word "date") (flags 0))
(word-desc (word "-u") (flags 0))
(word-desc (word "-d") (flags 0))
(word-desc (word "@0") (flags 0))
(word-desc (word "+'%Y-%m-%d'") (flags 2))

And, indeed, the command I had asked asked my bash executable to run was date -u -d @0 +'%Y-%m-%d'.

This is helpful, but:

  1. Not the prettiest: it would be better to have flags printed as a list of named flags (in this codebase, for example, 2 stands for W_QUOTED, "some form of quoted character is present"). I believe it would be impractical to do this in the user-defined command language, expecially given the availability of more featureful alternatives.

  2. Requires calling print_word_list explicitly whenever we have a WORD_LIST that we need to print: it would be helpful if p could do this automatically. As far as I can tell, this cannot be done without using Python or Guile.

Defining a Python pretty-printer

The code will be similar to that written in the "user-defined command" language, except a lot longer. We define two classes, one each for WORD_DESC and WORD_LIST; in addition to what the previous simple implementation did, this prints flags using their symbolic names rather than a single int value, and the WORD_LIST printer defines a children method returning a list of name-value pairs, which GDB picks up and recursively applies the right formatter to (prettybash.py):

class WordDescPrinter(object):
    """Print a WORD_DESC"""
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "{word = %s, flags = %s}" \
            % (self.val['word'], self.pretty_flags(self.val['flags']))

    def pretty_flags(self, flags):
        flag_names = []
        flags_dict = {
            'W_HASDOLLAR': 0x000001,
            'W_QUOTED': 0x000002,
            # (etc.)
        }

        for k in flags_dict:
            if flags & flags_dict[k]:
                flag_names.append(k)

        if flag_names == []:
            flag_names.append('none')

        return ' | '.join(flag_names)

class WordListPrinter(object):
    """Print a WORD_LIST"""
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "WORD_LIST" % self.val.address

    def children(self):
        c = []
        o = self.val
        i = 0
        while o['next'].address != 0:
            c.append(('[%d]' % i, o['word'].dereference()))
            o = o['next']
            i += 1
        return c

Plus some code to attach printers to types (bash-gdb.py; I don't know exactly why matching on word_desc works when GDB normally displays the type of wd as (WORD_DESC *), rather than the equivalent (struct word_desc *)):

import gdb.printing
import prettybash

def build_pretty_printer():
    pp = gdb.printing.RegexpCollectionPrettyPrinter("prettybash")
    pp.add_printer('word-desc', 'word_desc', prettybash.WordDescPrinter)
    pp.add_printer('word-list', 'word_list', prettybash.WordListPrinter)
    return pp

gdb.printing.register_pretty_printer(
    gdb.current_objfile(),
    build_pretty_printer())

We can confirm that everything has been loaded by asking GDB what pretty-printers it knows about:

(gdb) info pretty-printer
global pretty-printers:
  builtin
    mpx_bound128
  prettybash
    word-desc
    word-list

And then calling just print within GDB results in our pretty-printers getting used when the parameter has type WORD_DESC or WORD_LIST (output line-wrapped by hand; GDB does not actually print this prettily):

(gdb) p *wd
$1 = {word = 0x73cb08 "date", flags = none}

(gdb) p *wl
$2 = WORD_LIST = {
  [0] = {word = 0x73cb08 "date", flags = none},
  [1] = {word = 0x739218 "-u", flags = none},
  [2] = {word = 0x739238 "-d", flags = none},
  [3] = {word = 0x739248 "@0", flags = none},
  [4] = {word = 0x73ccc8 "+'%Y-%m-%d'", flags = W_QUOTED}
}

This makes our life much easier than the default printer did; we can see meaningful flag names, and a single print will show the entire linked list that is a WORD_LIST.

Notes

www.kurokatta.org


www.kurokatta.org

Quick links:

Photos
Montréal
Oregon
Paris
Camp info 2007
Camp Faécum 2007
--more--
Doc
Jussieu
Japanese adjectives
Muttrc
Bcc
Montréal
Couleurs LTP
French English words
Petites arnaques
--more--
Hacks
Statmail
DSC-W17 patch
Scarab: dictionnaire de Scrabble
Sigpue
Recipes
Omelette soufflée au sirop d'érable
Camembert fondu au sirop d'érable
La Mona de Tata Zineb
Cake aux bananes, au beurre de cacahuÚtes et aux pépites de chocolat
*