GDB - watch and display

Learning Outcome

Able to set a watchpoint on a variable in order to break a program when a variable changes.

Use display to automatically print how variables change throughout the program’s execution.

Introduction

watch allows us to stop the execution every time the value of a variable changes.

display prints variables every time the program’s execution stops (i.e. at a watchpoint, breakpoint, etc…)

Using both allows us to automatically stop at various points throughout a loop, and print all the relevant variables. Therefore, the only command needed to move through a loop is continue.

Applicable subjects

COMP1521, COMP2521, COMP3231


watch

Set a watchpoint To set a watchpoint every time the value of a particular variable changes set a watchpoint on that variable using:

(gdb) watch <variable_name>

View Watchpoints

You can view both watchpoints and breakpoints using:

(gdb) info breakpoints

Remove a watchpoint

To remove a watchpoint, use:

(gdb) disable <watchpoint_number>

Note

Watchpoints don’t work on WSL


display

Set an Automatic Display

Display an expression every time the program stops:

(gdb) display expression

View all Automatic Displays

Print a list of the automatically displayed expressions including the display number:

(gdb) info display

Remove Automatic Display

Remove an automatic display:

(gdb) delete display <display_number>

Example

We have a factorial program which calculates the factorials of 5 and 17. When we run the program the factoiral of 5 is correct but the factorial of 17 is incorrect. Our factorial function is iterative, it is helpful to view the values of f and i as they change and compare the two, since with our algorithm, f = i!. Doing this with next and print is tedious, so we can use watchpoints, continue and display to simplify the process.

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

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;	
}

We can compile with the debug flag and run our program with gdb:

$ gcc -g -o factorial factorial.c
$ gdb factorial
Reading symbols from factorial...done.

The output of the factorial function is correct when n = 5, so we want to skip the first call to the function.

(gdb) br factorial
Breakpoint 1 at 0x11a5: file factorial.c, line 24.
(gdb) r
Starting program: ~/factorial
Breakpoint 1, factorial (n=5) at factorial.c:24
24          int f = 1;
(gdb) c
Continuing.
The factorial of 5 is 120.
Breakpoint 1, factorial (n=17) at factorial.c:24
24          int f = 1;

We are now in the second call of the factorial function, but we want to set our watchpoints and displays once the variables have been initialised (i.e. line 26):

(gdb) n
25          int i = 1;
(gdb) n
26          while (i <= n) {

We want to print out both f and i when f changes within the loop. So we set a watchpoint on f and display i:

(gdb) watch f
Hardware watchpoint 2: f
(gdb) display i
1: i = 1

Now all we need to do is type continue until we notice that f != i! See the table below for a comparison of the f and i!:

(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 1
New value = 2
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 2
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 2
New value = 6
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 3
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 6
New value = 24
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 4
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 24
New value = 120
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 5
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 120
New value = 720
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 6
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 720
New value = 5040
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 7
(gdb) c

Continuing.

Hardware watchpoint 2: f

Old value = 5040
New value = 40320
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 8
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 40320
New value = 362880
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 9
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 362880
New value = 3628800
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 10
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 3628800
New value = 39916800
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 11
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 39916800
New value = 479001600
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 12
(gdb) c
Continuing.

Hardware watchpoint 2: f

Old value = 479001600
New value = 1932053504
factorial (n=17) at factorial.c:28
28                  i++;
1: i = 13

When i = 13, f should be 6,227,020,800, but it is not.

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.

We found out the same information we could have using next and print, except it was a lot faster and easier.

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