Gdb dereference pointer

More complex data structures – let GDB do the walk

We can also use pretty printers to handle more complicated data structures, for instance ones that use pointers to chain together multiple constituent structs.

Our vector drawing program will need a data structure to record all of the objects in the system. This structure allows us to track all the allocated points and lines:

struct drawing_element; typedef struct drawing_element { enum { ELEMENT_POINT, ELEMENT_LINE } kind; union element { point_t *point; line_t *line; } el; struct drawing_element *next; /* NULL-terminated */ } drawing_element_t;

Note that this can be an arbitrarily large data structure. For our example here, let’s just chain together a couple of elements:

drawing_element_t last = { .kind = ELEMENT_POINT, .el = { .point = &p, }, .next = NULL, }; drawing_element_t head = { .kind = ELEMENT_LINE, .el = { .line = &l, }, .next = &last, };

Printing it won’t provide very helpful output by default, even with our existing pretty printers:

(gdb) print head $1 = { kind = ELEMENT_LINE, el = { point = 0x7fffffffd980, line = 0x7fffffffd980 }, next = 0x7fffffffd960}

We can see the enum value of kind – which is helpful. Other than that, we just have a pair of pointers to the el member and a next pointer. We could walk along these pointers manually but it would be much nicer if GDB could do that work for us.

If we write a pretty printer for the drawing_element_t then we can build a more complex string renderer that will walk the data structure and give us a summary. To do this, we’ll define a children() method, which tells GDB that the type it’s pretty printing somehow contains other values of interest:

class DrawingElementPrinter: def __init__(self, val): self.val = val def to_string(self): # Describe the overall container structure. return ‘drawing_element_t @ {}’.format(self.val.address) def children(self): curr = self.val enum_type = self.val[‘kind’].type while curr: # For each drawing_element_t in the list, check “kind” and # choose the point_t or line_t pointer as appropriate. if curr[‘kind’] == enum_type[‘ELEMENT_POINT’].enumval: el_ptr = curr[‘el’][‘point’] elif curr[‘kind’] == enum_type[‘ELEMENT_LINE’].enumval: el_ptr = curr[‘el’][‘line’] # Prepare for next loop. curr = curr[‘next’].dereference() if curr[‘next’] != 0 else None # Yield the child element – a string name and the value # we want to show. yield ‘el’, el_ptr.dereference() def display_hint(self): # Tell GDB how to display the output of children(). return ‘array’ # Format our sequence like an array’s contents. def my_pp_func(val): if str(val.type) == ‘point_t’: return PointPrinter(val) elif str(val.type) == ‘line_t’: return LinePrinter(val) elif str(val.type) == ‘drawing_element_t’: return DrawingElementPrinter(val) return None gdb.pretty_printers.append(my_pp_func)

This pretty printer will:

  • Display the location of our data structure
  • Walk the linked list to retrieve all the elements
  • For each element, use the kind field to determine its type
  • Delegate display of that element to the existing pretty printer (by dereferencing the appropriate pointer)

By combining these elements, we have built a pretty printer for the whole chained data structure rather than for a single value. When we try our print command again, we’ll see something much more interesting:

(gdb) p head $2 = drawing_element_t @ 0x7fffffffd820 = {<Point(3, 4), Point(5, 6)>, Point(1, 2)}

Since we selected the “array” display hint, this will automatically reflect preferences for printing arrays (as set by set print array).

The printer we’ve built will automatically walk a list of arbitrary length; with the full power of Python available, combined with GDB’s access to data values and types, it is possible to decode arbitrarily complex data structures.

UDB Time Travel DebuggerFind and fix bugs in minutes – save time on debugging Learn more »

Related Posts