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)