GDB - Conditional Breakpoints

Learning Outcome

Use conditional breakpoints to conditionally stop program execution.

Introduction

Breakpoints normally stop the execution every time a certain line or function is reached. However, using the condition keyword, a breakpoint will only be activated if a certain condition is true. This can avoid stopping at breakpoints until something of interest is true.

Applicable subjects

COMP1521, COMP2521, COMP3231


Set a breakpoint

The first step in setting a conditional breakpoint is to set a breakpoint as you normally would. I.e.

(gdb) break <file_name> : <line_number>
(gdb) break <function_name>

This will set a breakpoint and output the breakpoint number

Check breakpoints

If you forget which breakpoints you can add a condition to, you can list the breakpoints using:

(gdb) info breakpoints

Set a condition for a breakpoint

Set an existing breakpoint to only break if a certain condition is true:

(gdb) condition <breakpoint_number> condition

The condition is written in syntax similar to c using operators such as == != and <.

Remove a condition from a breakpoint

If no condition is required on a breakpoint anymore (i.e. the breakpoint should always break upon being reached), the breakpoint can be removed using:

(gdb) condition <breakpoint_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!. Since we know that the value is correct up until 5! we can skip checking the values up until i = 6.

$ ./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 don’t need to check the values of f for i less than 5.

So we want to put a breakpoint on line 28 (after the calculation of f for the corresponding value of i), that only is active for i > 5.

$ gdb factorial
Reading symbols from factorial...done.
(gdb) br 28
Breakpoint 1 at 0x11bf: file factorial.c, line 28.
(gdb) condition 1 i > 5

Now we need continue, and list the local variables until we notice that f != i! See the table below for a comparison of the f and i!:

(gdb) r
Starting program: ~/factorial
The factorial of 5 is 120.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 720
i = 6
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 5040
i = 7
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 40320
i = 8
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 362880
i = 9
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 3628800
i = 10
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 39916800
i = 11
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 479001600
i = 12
(gdb) c
Continuing.

Breakpoint 1, factorial (n=17) at factorial.c:28
28                  i++;
(gdb) info locals
f = 1932053504
i = 13
(gdb)

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.

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