GDB - Navigating your program

Learning Outcome

Able to control the execution of a program using commands step, next and continue.

Introduction

Breakpoints halt execution at the point specified, however the specified point is usually only our best guess as to where the problem might be. Once halted, we need to continue execution a small amount at a time so we can observe what the problem might be.

The GDB commands step, next and continue can be used execution the program in small amounts to investigate how variables change as the program runs.

Applicable subjects

COMP1521, COMP2521, COMP3231


Note

The next line of code to execute is the one displayed beside the displayed line number when the program halts at a breakpoint or after using a step or a next command. This line of code has not run yet.

step

Execute the next statement. If the next statement is a function call, step into the function (i.e. start debugging inside the function itself).

(gdb) step

continue

Contine the execution of the program until it reaches a breakpoint, a fatal error or finishes execution normally.

(gdb) continue

where

Print out the function call stack including the current line number.

If you are not familiar with the function call stack, just look at the top line for the current file and line number.

Not an execution related command but helpful if you get lost in navigating the program.

(gdb) where

list

Print out 5 lines of code above and below the current statement.

Like where, this is also not execution related but helpful to display of surrounding context of where execution has stopped.

(gdb) list

Example

In the previous modules we learned how to set a breakpoint, run our program in GDB and inspect the state of the program. However, to find the bug in factorial.c we must move through the execution of our program. In this example, we will be using step and next to move through factorial.c and inspect variables of interest.

factorial.c

factorial.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//This program calculates and prints out the factorials of 5 and 17


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

int factorial(int n);

int main(void) {
	
	int n = 5;
	int f = factorial(n);
	printf("The factorial of %d is %d.\n", n, f);
	n = 17;
	f = factorial(n);
	printf("The factorial of %d is %d.\n", n, f);

	return 0;
		
}
//A factorial is calculated by n! = n * (n - 1) * (n - 2) * ... * 1
//E.g. 5! = 5 * 4 * 3 * 2 * 1 = 120
int factorial(int n) {
	int f = 1;
	int i = 1;
	while (i <= n) {
		f = f * i;
		i++;
	}
	return f;	
}

When we run the above code without gdb, the following output is given:

The factorial of 5 is 120.
The factorial of 17 is -288522240.

Recall, we know that the value of 17! should be 355,687,428,096,000 not -288522240, so something has gone wrong.

Recap

In the previous modules we compiled the code for use with GDB, started a gdb session and set a breakpoint on line 15.

$ gcc -Wall -g -o factorial factorial.c
$ gdb factorial

(gdb) break 15
(gdb) run
Starting program: /code/factorial
The factorial of 5 is 120.
Breakpoint 1, main () at factorial.c:15
15              f = factorial(n);

We want to debug inside the factorial function so we use step to step into it:

(gdb) step
factorial (n=17) at factorial.c:24
24              int f = 1;

We are now on the first line of the factorial function.

We want to look at the variables in this function, especially the result of f and the corresponding value of i after the multiplication (i.e. we’d like to inspect line 26). To get to line 26 we use the next command:

(gdb) next
25              int i = 1;
(gdb) next
26              while (i <= n) {
(gdb) next
27                      f = f * i;
(gdb) next
28                      i++;

Note

To repeat the previous command, just press enter.

Now we are at a place in the code where we want to check if the values are what we expect them to be. We have three variables available on line 28, n, f and i. For each of the iterations, f should be equal to i! after the execution of the multiplication. We can compare the values of f against known factorial values of i by filling out the third column of the table below.

On the first iteration of the loop we expect i == 1 and f == 1. Let’s check this is true using gdb.

(gdb) print i
$2 = 1
(gdb) print f
$3 = 1

The values of f and i are correct for the first iteration of the loop.

If we forget where we are, we can use the where command.

(gdb) where
=0  factorial (n=17) at factorial.c:28
=1  0x000000000040056a in main () at factorial.c:15

From this stack trace, we know that we are at line 28 in factorial.c in the function factorial. This was called at line 15 in factorial.c in the function main

If we want to see the code around where we are we can use the list command.

(gdb) list
23      int factorial(int n) {
24              int f = 1;
25              int i = 1;
26              while (i <= n) {
27                      f = f * i;
28                      i++;
29              }

Now let’s check the values of f against i for the rest of the iterations.

It is quite tedious stepping through line by line. To speed things up we can use a second breakpoint on line 28.

(gdb) break 28
Breakpoint 2 at 0x4005ac: file factorial.c, line 28.

We can then use continue to execute until we reach line 28 again.

(gdb) continue
Continuing.
Breakpoint 2, factorial (n=17) at factorial.c:28
28                      i++;

And we can again print out f and i.

(gdb) info locals
f = 2
i = 2

This is again what we expect.

We repeat this process of continuing and printing until we notice that f does not equal i!. At i = 13 we notice that the value of f is 1,932,053,504 but it should be 6,227,020,800. Instead of increasing, f has decreased. Therefore, we know that our function works up until f reaches a certain very large number. It then decreases and later even becomes negative. This seems consistent with integer overflow.

A quick google reveals that depending on the computer, the range of an integer is either -32,768 to 32,767 or -2,147,483,647 to 2,147,483,647. The function stops working when f is between 479,001,600 and 6,227,020,800 which is conistent with the 2,147,483,647 integer limit. The variable can’t store a number larger than the maximum integer size.

Several options are available to fix this issue:

  • A larger data type such as a long long could be used

  • If the program should never be used with a value of n larger than 12, error handling can be added and the result can be left as an integer.

i

i!

f

0

1

1

1

1

1

2

2

2

3

6

6

4

24

24

5

120

120

6

720

720

7

5,040

5,040

8

40,320

40,320

9

362,880

362,880

10

3,628,800

3,628,800

11

39,916,800

39,916,800

12

479,001,600

479,001,600

13

6,227,020,800

1932053504 (wrong)

14

87,178,291,200

don’t care yet

15

1,307,674,368,000

don’t care yet

16

20,922,789,888,000

don’t care yet

17

355,687,428,096,000

-288522240 (wrong)

Module author: Liz Willer <e.willer@unsw.edu.au>

Date

2020-01-15