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
*