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.
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
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.
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;
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
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 ()
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
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]