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:

#include <stdio.h>
#include <stdlib.h>

int square(int i) {
    return i * i;
}

// sum the squares of 1 to n including n, where n is the first argument
int main(int argc, char *argv[]) {
    int sum = 0;
    int n = atoi(argv[1]);

    for (int i = 1; i < n; i++) {
        int squared = square(i);
        sum += squared;

    }

    printf("sum is %d \n", sum);
}
C

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.

gcc -g -o square_sum square_sum.c
Shell

In the shell, the command gdb <program> starts GDB and loads the compiled program.

gdb ./square_sum
Shell

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.

(gdb) break 13
Breakpoint 1 at 0x40117e: file square_sum.c, line 13.
(gdb) break 14
Breakpoint 2 at 0x401187: file square_sum.c, line 14.
(gdb) break 19
Breakpoint 3 at 0x4011a6: file square_sum.c, line 19.
Shell

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.

(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040117e in main at square_sum.c:13
2       breakpoint     keep y   0x0000000000401187 in main at square_sum.c:14
3       breakpoint     keep y   0x00000000004011a6 in main at square_sum.c:19
Shell

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.

(gdb) delete 1
(gdb) info break
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000401187 in main at square_sum.c:14
3       breakpoint     keep y   0x00000000004011a6 in main at square_sum.c:19
Shell

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> ...

(gdb) run 4
Starting program: /home1/10000/zw427/c/./square_sum 4
Shell

The program stops at breakpoint 2 at line 14. We can inspect the values of variables print <variable>.

Breakpoint 2, main (argc=2, argv=0x7fffffff9be8) at square_sum.c:14
14              int squared = square(i);
(gdb) print sum
$1 = 0
(gdb) print n
$2 = 4
(gdb) print i
$3 = 1
Shell

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.

(gdb) step
square (i=1) at square_sum.c:5
5           return i * i;
(gdb) step
6       }
(gdb) step
main (argc=2, argv=0x7fffffff9be8) at square_sum.c:15
15              sum += squared;
(gdb) step
13          for (int i = 1; i < n; i++) {
(gdb) step

Breakpoint 2, main (argc=2, argv=0x7fffffff9be8) at square_sum.c:14
14              int squared = square(i);
(gdb) print i
$4 = 2
(gdb) print sum
$5 = 1
Shell

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.

(gdb) next
15              sum += squared;
(gdb) next
13          for (int i = 1; i < n; i++) {
(gdb) next

Breakpoint 2, main (argc=2, argv=0x7fffffff9be8) at square_sum.c:14
14              int squared = square(i);
(gdb) print i
$6 = 3
(gdb) print sum
$7 = 5
Shell

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.

(gdb) continue
Continuing.

Breakpoint 3, main (argc=2, argv=0x7fffffff9be8) at square_sum.c:19
19          printf("sum is %d \n", sum);
Shell

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.

(gdb) list 17
12
13          for (int i = 1; i < n; i++) {
14              int squared = square(i);
15              sum += squared;
16
17          }
18
19          printf("sum is %d \n", sum);
20      }
(gdb) list square
1       #include <stdio.h>
2       #include <stdlib.h>
3
4       int square(int i) {
5           return i * i;
6       }
7
8       // sum the squares of 1 to n including n, where n is the first argument
9       int main(int argc, char *argv[]) {
10          int sum = 0;
Shell

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.

(gdb) break 19
Breakpoint 1 at 0x4011a6: file square_sum.c, line 19.
(gdb) run 5
Starting program: /home1/10000/zw427/c/./square_sum 5

Breakpoint 1, main (argc=2, argv=0x7fffffff9be8) at square_sum.c:19
19          printf("sum is %d \n", sum);
(gdb) set var sum = 1000
(gdb) c
Continuing.
sum is 1000
[Inferior 1 (process 49505) exited normally]
Shell
 
© 2025  |   Cornell University    |   Center for Advanced Computing    |   Copyright Statement    |   Access Statement