Debugging with GDB
This page is an exercise of the GNU Debugger (GDB). GDB is a debugging tool for serial programs of various programming languages. More in-depth documentation on GDB can be found here.
A debugging tool like GDB has advantages over printf() debugging by allowing easier inspection of the program state and modification of memory and variables. GDB only requires the program to be compiled once with the -g
flag, whereas printf() may require multiple compiles.
In this tutorial, we run GDB on a small C program as an exercise. We have the following code:
The program finds the sum of squares from 1 to n
, including n
. We will assume that the argument to the program is always a valid integer. If we run the program, we should notice the output is incorrect. The sum of squares from 1 to 4 should be 30, not 14, as calculated by the program. We will use GDB to debug this program.
Before we can use GDB, the program should be re-compiled with the -g
flag. Programs compiled with the -g
flag will produce extra debugging information that enables the use of GDB. Without -g
, GDB will be unable to set breakpoints. The optimization flag -O
can be simultaneously used with -g
, but GDB may behave unexpectedly due to the program's compiler optimization.
In the shell, the command gdb <program>
starts GDB and loads the compiled program.
Once GDB finishes loading, you will see the GDB command line prompt as indicated by (GDB)
, where you may continuously enter GDB commands for debugging. Debugging with GDB involves setting breakpoints at lines. Just before the program executes lines with breakpoints, GDB interrupts the program. It prompts you with the GDB command line, allowing you to inspect variables, modify values, or continue and step through the code line by line. To set breakpoints, enter break <line>
into the GDB prompt. In our example, we will set breakpoints at lines 13, 14, and 19.
The number assigned to each breakpoint tells us which breakpoint we encounter as we run through the program. We can view all the breakpoints with info break
.
A breakpoint at line 13 is unnecessary since the values should be the same as those of line 14. GDB allows either disabling breakpoints with disable <breakpoint_num>
or deleting breakpoints with delete <breakpoint_num>
. We will delete the breakpoint at line 13.
With proper breakpoints set, we can run through the program. Since square_sum
takes one argument n
, we would include the argument in the run command like this run <args1> <args2> <args3> ...
The program stops at breakpoint 2 at line 14. We can inspect the values of variables print <variable>
.
The values seem correct so far. At this point, there are a few ways to proceed. One option, step
, is to step through the program line by line, including stepping into other functions. Another option, next
, is similar but it steps over functions, meaning it stays in the current function until its end of scope. The last option, continue
, is to continue until the next breakpoint or the end of the program. We will demonstrate both step
and next
.
Note that with step
, we step line by line through the code, including going into the function square
and then encountering breakpoint 2 again, which is the start of the second iteration of the for loop.
Note that with next
, the program does not go into the function square
. Compared with step
, next
may be perferred when there are large functions that do not need debugging. For simplicity, we will go through the rest of the program with continue
.
Note how we immediately encounter breakpoint 3, which is the last line before the end of the program. The for loop never reached the fourth iteration, indicating a mistake in the for loop.
A helpful command while debugging is to view the surrounding code with GDB. The commands are list
for code near the current line, list <line>
for code near a specific line, and list <function>
for code near a specific function.
As mentioned, GDB allows you to modify variables and run the program. For example, we can change the value of sum
just before the end of the program with the command set var <variable_name>=<value>
. A note of warning is that since GDB writes the new value into the memory of the existing value, the size of the new value should equal the size of the existing value.