University of Leeds

Using GDB

Introduction

GDB is the GNU Project Debugger. It is a tool that allows you to inspect (and change) the values stored in variables in your programs as they run. It comes as a part of the GNU toolchain (along with g++ and make etc.) and is run from the command line.

Although gdb is cross-platform, I have recently been unable to get it working on macOS. Apparently LLDB (Apple's debugger) works pretty much in the same way on macOS as GDB. Thanks to Ahmad Hammouri for informing me. I guess this is installed as part of the Xcode command line tools.

Example

Below is some simple code to print the sum of the first n numbers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>

int main() {
  int n = 10;
  int sum = 0;
  for (int i = 0; i < n; i++) {
    sum += i;
    std::cout << "sum = " << sum << std::endl;
  }
}

To be able to run a program in GDB, we must compile it with the -g flag. Note here that I have also turned on extra and pedantic warnings.

g++ -o main.exe -Wall -Wextra -Wpedantic -g main.cpp

Running GDB

Now we have our executable, we can run it in GDB by typing the following on the command line - gdb main.exe. GDB will start and display some information. The GDB command line is shown as (gdb) and here is where we type the commands.

$ gdb main.exe
GNU gdb (GDB) 7.6.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "mingw32".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from C:\MinGW\msys\1.0\home\Craig A. Evans\108\main.exe...done.
(gdb)

To run the code, we simply type 'run'

(gdb) run
Starting program: C:\MinGW\msys\1.0\home\Craig A. Evans\108/main.exe
[New Thread 3124.0x1010]
sum = 0
sum = 1
sum = 3
sum = 6
sum = 10
sum = 15
sum = 21
sum = 28
sum = 36
sum = 45
[Inferior 1 (process 3124) exited normally]

Here, the program has run and exited normally.

Setting breakpoints

The above example was a bit pointless - we could simply run the code on the command-line and get the same result. The power comes from setting breakpoints in the code. These are places in which we tell GDB to stop running the program so we can look at variable values etc.

For this example, we will stop the code at line 4. This is where we declare n.

(gdb) break 4
Breakpoint 1 at 0x401476: file main.cpp, line 4.

Now when we run the code, it will stop at line 4.

(gdb) run
Starting program: C:\MinGW\msys\1.0\home\Craig A. Evans\108/main.exe
[New Thread 1748.0x494]

Breakpoint 1, main () at main.cpp:4
4           int n = 10;

Inspecting values

Once we are at a breakpoint, we can inspect values using print e.g.

(gdb) print n
$1 = 4200944

Note here that n contains a junk value. This is because the program has stopped running before the variable is assigned a value (=10). To step through the code one line at a time we use the step command.

(gdb) step
5           int sum = 0;

We are now at line 5 and can try and print n again.

(gdb) print n
$2 = 10

If we try and print sum we can see that it has not been initialised yet, as was the case with n.

(gdb) print sum
$3 = 4201035

We can add in another breakpoint. This time we will add one on line 7. We can also specify breakpoints using just b

(gdb) b 7
Breakpoint 2 at 0x401493: file main.cpp, line 7.

Any time we wish to list our breakpoints we can use the info breakpoints command.

(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x00401476 in main() at main.cpp:4
        breakpoint already hit 1 time
2       breakpoint     keep y   0x00401493 in main() at main.cpp:7

We can disable breakpoints

(gdb) disable 2
(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x00401476 in main() at main.cpp:4
        breakpoint already hit 1 time
2       breakpoint     keep n   0x00401493 in main() at main.cpp:7

Or delete them all together.

(gdb) delete 2
(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x00401476 in main() at main.cpp:4
        breakpoint already hit 1 time

Watchpoints

With breakpoints, we specify a line on which we want execution to stop. We can then inspect the values by printing. We can also set watchpoints. This is where we tell GDB to watch a variable and then stop every time it changes value e.g.

(gdb) watch sum
Hardware watchpoint 3: sum

Now, when we run the code (by typing continue as the code is currently stopped) we will see the following output.

(gdb) continue
Continuing.
Hardware watchpoint 3: sum

Old value = 4201035
New value = 0
main () at main.cpp:6
6           for (int i = 0; i < n; i++) {

Here, GDB is telling us that the value stored in sum has changed and it prints the old and new value. We can see that the variable is being initialised to 0. If we continue again, it will stop the next time around the loop.

(gdb) continue
Continuing.
sum = 0
Hardware watchpoint 3: sum

Old value = 0
New value = 1
main () at main.cpp:8
8               std::cout << "sum = " << sum << std::endl;

Here we can see the sum increment by 1, as we would expect.

We can also display information about our watchpoints.

(gdb) info watchpoints
Num     Type           Disp Enb Address    What
3       hw watchpoint  keep y              sum
        breakpoint already hit 2 times

If we carry on continuing with the program, we will be able to watch the value change. In this case, we may also want to print the value of the loop index to keep an eye on how many times the loop has executed.

(gdb) continue
Continuing.
sum = 28
Hardware watchpoint 3: sum

Old value = 28
New value = 36
main () at main.cpp:8
8               std::cout << "sum = " << sum << std::endl;
(gdb) print i
$5 = 8

Finally, we will get to the end of the loop and then to the end of main. Here we will get a message that the watchpoint has been deleted as the program has exited main.

(gdb) continue
Continuing.
sum = 45

Watchpoint 3 deleted because the program has left the block in
which its expression is valid.
0x00401288 in _Jv_RegisterClasses ()

Displaying variables

We can tell GDB to display the values stored in a variable every time it stops at a breakpoint or watchpoint. This can make it easier to track a variable. Note here that a new de-bugging session was started and the same breakpoints and watchpoints were added as before.

(gdb) display i
1: i = 2686824

This tell GDB that from now on, it should display the value stored in i every time it stops. If we then continue, it will get to a breakpoint or watchpoint and automatically display i.

(gdb) continue
Continuing.
sum = 0
Hardware watchpoint 2: sum

Old value = 0
New value = 1
main () at main.cpp:8
8               std::cout << "sum = " << sum << std::endl;
1: i = 1

Every time it stops, it will print i.

(gdb) continue
Continuing.
sum = 1
Hardware watchpoint 2: sum

Old value = 1
New value = 3
main () at main.cpp:8
8               std::cout << "sum = " << sum << std::endl;
1: i = 2

Changing variables

It is also possible to change the values stored in variables at run-time. Re-starting the de-bugging session as above and setting a breakpoint inside the for loop.

(gdb) break 7
Breakpoint 1 at 0x401493: file main.cpp, line 7.

We can run the code and it will stop within the loop.

(gdb) run
Starting program: C:\MinGW\msys\1.0\home\Craig A. Evans\108/main.exe
[New Thread 4724.0xf58]

Breakpoint 1, main () at main.cpp:7
7               sum = i;

If we print i we can see the following

(gdb) print i
$1 = 0

We can change the values stored in variables using the set var command.

(gdb) set var i = 99
(gdb) print i
$2 = 99

Here we set i to 99. Since this is now greater than n, the for loop will exit.

(gdb) continue
Continuing.
sum = 99
[Inferior 1 (process 4724) exited normally]