WatchPoint
Debugging with pretty printers in GDB – part 2
In this tutorial, Software Architect Mark Williamson follows on from Greg’s tutorial on Debugging with pretty-printers in GDB by illustrating how to write pretty printers for more complex data structures. Our examples in this tutorial will revolve around data structures from an imaginary vector graphics program. As we work up to more complex geometry types we will extend to our pretty printers to help us understand the structures involved. We’ll start with a very fundamental structure, representing a single point: Lets suppose our program has defined an instance of this type, representing a point in 2D space: Printing this immediately in GDB gives us a fairly familiar, though not particularly compact, representation: In our previous examples, we already learnt how to write a basic pretty printer. A similar pretty-printer script for our This gives us a more compact output format that’s more suitable for the coordinate data we’re representing: Our Let’s assume we’d like to pretty print this too. We could just copy-and-paste the code from the previous pretty-printer with some edits: But this is already getting a bit repetitive. Fortunately, GDB already has this covered for us: when formatting strings for printing, it will recursively call existing pretty printers. So, actually, we can write: Which produces the output: Deferring formatting decisions to existing pretty printers also means that formatting changes can be made in one place and reflected everywhere. For instance, if we now change our original Then we’ll see also this reflected when we print a UDB Time Travel Debugger 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: Note that this can be an arbitrarily large data structure. For our example here, let’s just chain together a couple of elements: Printing it won’t provide very helpful output by default, even with our existing pretty printers: We can see the enum value of If we write a pretty printer for the This pretty printer will: 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 Since we selected the “array” display hint, this will automatically reflect preferences for printing arrays (as set by 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. That’s it for this tutorial – we’ve seen how GDB can automatically invoke the right pretty printer to display your data and handle more advanced data structures. Use these techniques and you’ll get a better debug experience for less effort spent. The full source of the examples shown above can be downloaded for reference: If you really want to get to an advanced level on pretty printers, then read the last installment in this series. Check out part 3.In this Tutorial
Getting to the point_t
typedef struct {
int x;
int y;
} point_t;
point_t p = {
.x = 1,
.y = 2,
};
(gdb) print p
$9 = {
x = 1,
y = 2
}
point_t
might look like this:import gdb
class PointPrinter:
def __init__(self, val):
self.val = val
def to_string(self):
return '({x}, {y})'.format(
x=self.val['x'],
y=self.val['y']
)
def my_pp_func(val):
if str(val.type) == 'point_t': return PointPrinter(val)
return None
gdb.pretty_printers.append(my_pp_func)
(gdb) print p
$8 = (1, 2)
Line up for nested structures!
point_t
structure is very likely to be used as an element of other data structures. For instance, in a drawing program, we might have a line_t
that contains two points:typedef struct {
point_t start;
point_t end;
} line_t;
...
def to_string(self):
return '<({x1}, {y1}), ({x2}, {y2})>'.format(
x1=self.val['start']['x'],
y1=self.val['start']['y'],
x2=self.val['end']['x'],
y2=self.val['end']['y']
)
...
class PointPrinter:
...
class LinePrinter:
def __init__(self, val):
self.val = val
def to_string(self):
return '<{p1}, {p2}>'.format(
p1=self.val['start'], p2=self.val['end']
)
def my_pp_func(val):
if str(val.type) == 'point_t': return PointPrinter(val)
elif str(val.type) == 'line_t': return LinePrinter(val)
return None
gdb.pretty_printers.append(my_pp_func)
(gdb) print l
$7 = <(3, 4), (5, 6)>
PointPrinter
to say:...
def to_string(self):
return 'Point({x}, {y})'.format(
x=self.val['x'],
y=self.val['y']
)
...
line_t
:(gdb) print l
$8 = <Point(3, 4), Point(5, 6)>
Find and fix test failures in minutes – including C/C++ race conditions, deadlocks and memory corruptions
Learn more »More complex data structures – let GDB do the walk
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;
drawing_element_t last = {
.kind = ELEMENT_POINT,
.el = {
.point = &p,
},
.next = NULL,
};
drawing_element_t head = {
.kind = ELEMENT_LINE,
.el = {
.line = &l,
},
.next = &last,
};
(gdb) print head
$1 = {
kind = ELEMENT_LINE,
el = {
point = 0x7fffffffd980,
line = 0x7fffffffd980
},
next = 0x7fffffffd960
}
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.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)
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)}
set print array
).Let GDB help you
Learn more about pretty printers
Get tutorials straight to your inbox
Become a GDB Power User. Get Greg’s debugging tips directly in your inbox every 2 weeks.
Want GDB pro tips directly in your inbox?
Share this tutorial