Quite a few of the codebases I've worked on have some form of linked list implementation; generally a list node type:

struct list_node {
    struct list_node *prev;
    struct list_node *next;
};

... and sometimes (for type-safety) a separate type for the lists themselves:

struct list {
    struct list_node head;
};

Debugging these lists often means a lot of manual pointer-chasing, which can be error prone. If your list iterators are causing segfaults, it'd be nice to be able to track down invalid list entries.

python gdb extensions

It turns out we can write a little code to automate this, using the gdb module. Amongst other things, we can define our own commands to use in the standard gdb interpreter.

The skeleton code to implement a gdb command in python goes something like this:

import gdb

class MyNewCommand(gdb.Command):

    def __init__(self):
        super(MyNewCommand, self).__init__("my-new-command",
                gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)

    def invoke(self, argument, from_tty):
        args = gdb.string_to_argv(argument)
        expr = args[0]
        list = gdb.parse_and_eval(expr)

        # command implementation goes here ...

MyNewCommand()

In a nutshell:

  • We define a new subclass of gdb.Command
  • Our new class' __init__() function will register the command name ("my-new-command" in this example) with the gdb interpreter, and pass a few hints about the command's usage.
  • The invoke() function is called when the user invokes the command at the gdb interpreter interface.
  • The invoke() implementation can access the debugger's state thorugh the gdb python module.
  • Finally, we create an instance of the new class to get it registered with the interpreter.

The class' docstring is used by gdb's inline help command, which makes documenting your new command a total piece of cake.

Back to our list example, I've written a little python gdb extension to iterate and print a list:

#!/usr/bin/env python

import gdb

class ListPrintCommand(gdb.Command):
    """Iterate and print a list.

list-print <EXPR> [MAX]

Given a list EXPR, iterate though the list nodes' ->next pointers, printing
each node iterated. We will iterate thorugh MAX list nodes, to prevent
infinite loops with corrupt lists. If MAX is zero, we will iterate the
entire list.

List nodes types are expected to have a member named "next". List types
may be the same as node types, or a separate type with an explicit
head node, called "head"."""

    MAX_ITER = 20

    def __init__(self):
        super(ListPrintCommand, self).__init__("list-print",
                gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)

    def invoke(self, argument, from_tty):
        args = gdb.string_to_argv(argument)
        if len(args) == 0:
            print "Argument required (list to iterate)"
            return

        expr = args[0]

        if len(args) == 2:
            max_iter = int(args[1])
        else:
            max_iter = self.MAX_ITER

        list = gdb.parse_and_eval(expr)

        fnames = [ f.name for f in list.type.fields() ]

        # handle lists with a separate list type....
        if 'head' in fnames:
            head = list['head']

        # ...and those with the head as a regular node
        elif 'next' in fnames:
            head = list

        else:
            print "Unknown list head type"
            return

        # if the type has a 'prev' member, we check for validity as we walk
        # the list
        check_prev = 'prev' in [ f.name for f in head.type.fields() ]

        print "list@%s: %s" % (head.address, head)
        node = head['next']
        prev = head.address
        i = 1

        while node != head.address:
            print "node@%s: %s #%d" % (node, node.dereference(), i)

            if check_prev and prev != node['prev']:
                    print " - invalid prev pointer!"

            if i == max_iter:
                print " ... (max iterations reached)"
                break

            prev = node
            node = node['next']
            i += 1

        if check_prev and i != max_iter and head['prev'] != prev:
            print "list has invalid prev pointer!"

ListPrintCommand()

This defines a new function, list-print, which takes an expression as an argument, and iterates through the list nodes:

(gdb) list-print handler->devices[0]->device->boot_options
list@0x6128f0: {prev = 0x6129f8, next = 0x613918}
node@0x613918: {prev = 0x6128f0, next = 0x613568} #1
node@0x613568: {prev = 0x613918, next = 0x6131b8} #2
node@0x6131b8: {prev = 0x613568, next = 0x612e08} #3
node@0x612e08: {prev = 0x6131b8, next = 0x6129f8} #4
node@0x6129f8: {prev = 0x612e08, next = 0x6128f0} #5
(gdb) 

To use the gdb-list macro: download gdb-list.py (2.2 kB), and source it into your gdb session:

(gdb) source ~/devel/gdb-list/gdb-list.py 

Then you should be able to invoke list-print <symbol> to debug your list structures. Typing help list-print will show the command's docstring:

(gdb) help list-print 
Iterate and print a list.

list-print <EXPR> [MAX]

Given a list EXPR, iterate though the list nodes' ->next pointers, printing
each node iterated. We will iterate thorugh MAX list nodes, to prevent
infinite loops with corrupt lists. If MAX is zero, we will iterate the
entire list.

List nodes types are expected to have a member named "next". List types
may be the same as node types, or a separate type with an explicit
head node, called "head".
(gdb)