WatchPoint

Image link

Save time debugging in GDB with pretty printing

Intro to Pretty Printing

Pretty printing is displaying information in the terminal in such a way that the information can be understood more easily at a glance, saving time when debugging. It is most often done using colour and formatting of data, as opposed to printing out a data structure in its raw form.

Example struct

Here’s an example program called students.c

#include <stdlib.h>

typedef struct
{
     const char *first_name;
     const char *last_name;
     struct
     {
     int day;
     int month;
     int year;
     } dob;
     char *comments;
} student;

student students[] =

{
     [0] = {.first_name = "Fred", .last_name = "Smith", .dob = {1,1,1970}},
     [1] = {.first_name = "Sarah", .last_name = "G0T0", .dob = {1,1,2002}, .comments = "Lorem ipsum enchanta constrata"}
};

int 
main(void) 
{
     students[1].comments = malloc(128);
     students[1].comments[51] = 'X';
return 0;
}

 

Time Travel Debugging Demo Video Banner

Using GDB’s pretty print

Now if you were to print the structure normally, it would technically be readable but not very efficiently. There is no formatting retained from the file, and all the data is compressed into the terminal.

$ gdb students
(gdb) start
Temporary breakpoint 1 at 0x1151: file students.c, line 22
22  int main(void) {
(gdb) print students
$1 = {{first_name = 0x555555556008 “Fred”, last_name = 0x55555555600d “Smith”, dob = {day = 1, month = 1, year = 1970}, comments = 0x0}, {first_name = 0x555555556013 “Sarah”, last_name = 0x555555556019 “G0T0”, dob = {day = 1, month = 1, year = 2002}, comments = 0x5555555560020 “Lorem ipsum enchanta constrata”}}

You could also try and use GDB’s pretty printing to look at the struct.

(gdb) set print pretty on
(gdb) print students
$2 = {{
     first_name = 0x555555556008 “Fred”,
     last_name = 0x55555555600d “Smith”,
     dob = {
           day = 1,
           month = 1,
           year = 1970},
     },
     comments = 0x0
}, {
     first_name = …
…

This is nicer to look at as each element of the struct has its own line, so each piece of data can be found more easily. However, this now means the output is significantly longer, taking up more of the terminal, so the gdb pretty printing does come with drawbacks. It can also be enabled for one command, rather than setting it to be on all the time. This next snippet will achieve the same as the previous one:

(gdb) set print pretty off
(gdb) print -pretty on --
…

 

Printing and watching elements of arrays

There are other helpful tricks to streamline your print output using GDB. Strings and arrays are usually capped at 200 elements displayed, however this can be customized.

(gdb) print -elements 10 -- students
…
comments = 0x5555555560020 “Lorem ipsu”...}}

(gdb) print -elements unlimited --
…
comments = 0x5555555560020 “Lorem ipsum enchanta constrata”}}

(gdb) print students[1].comments[5]@10
$7 = “ ipsum enc”

As you can see, you can set a specific size of the different strings arrays inside the struct to be output, as well as slice particular parts of an array. You can also set watchpoints on specific slices of the array using GDB. If you continue the program from the breakpoint we’re currently at, then the comments of student[1] are set to a non-literal string using malloc(), allowing the string to be altered one element at a time (which we couldn’t do with string literals, which is how it was initially defined).

(gdb) n
24 return 0;
(gdb) print students[1].comments[50]@4
$8 = “\000\000\000”
(gdb) watch students[1].comments [50]@4
Hardware watchpoint 2: students[1].comments [50]@4
(gdb) continue
Continuing.

Hardware watchpoint 2: students[1].comments [50]@4

Old value = “\000\000\000”
New value = “\000X\000”
Main () at students.c:25
25 return 0;

This allows a watchpoint to be set on a small number of bytes of a large piece of data. GDB doesn’t know how big the malloc string is without being told, but even if you did tell GDB to watch all 128 bytes of the malloc string the x86 watchpoint registers can’t cover that range, as there are only 4 registers of 8 bytes each.

Printing also allows you to save a copy of the slice of the array from earlier in the execution. The string at where we are in the program has the X we inserted:

(gdb) print students[1].comments[50]@4
$9 = “\000X\000”

However if you print $8, our variable from when earlier when we printed the slice before, it is unchanged:

(gdb) p $8
$10 = “\000\000\000”

 

GDB Training Course


More basic customization

GDB can be customized further for printing, with extra options to work better for specific data types, or to see raw values or static members, etc.

(gdb) print -
-address            -memory-tag-violations  -repeats
-array              -null-stop          -static-members
-array-indexes      -object             -symbol
-elements           -pretty             -union
-max-depth          -raw-values         -vtbl

You can also use specifiers to customize the format of the value you’re printing.

(gdb) print /x # print previous variable printed in hex
$11 = {0x0, 0x0, 0x0, 0x0}

The same can be done with:

  • o – octal
  • u – unsigned decimal
  • t – binary
  • f – floating point
  • a – address
  • c – char
  • s – string

 

Custom pretty printers with python

This customization is useful but not truly specific to our use case. If, for example, we only wanted to see the name of each student in the array, we could use GDB’s Pretty Printing API to manually curate the output. You can learn more about how to use Python pretty printers to get full customization in this tutorial.

Don’t miss my next C++ debugging tutorial: sign up to my WatchPoint mailing list below.
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