| Home | Classes | Papers | Essays | Short Fiction | About |

CS211: (Appendix) Debugging with gdb

Table of Contents

Introduction

A lot of beginner programmers do what's called "print debugging"—using print statements to debug their programs. Although it tends to be fine for tiny programs, it is often not enough to get at the heart of catastrophic errors in complicated programs. This is where the use of programs specifically made for debugging shines.

gdb

gdb is the GNU debugger which was made to pair with gcc in order to debug C programs. Whenever using gdb it's best to make sure your C programs are compiled with the -g flag which sets special debugging symbols in the binary that gdb can pick up on.

Below is an example C program which has a simple bug in it:

 1: #include <stdio.h>
 2: #include <stdlib.h>
 3: 
 4: int sum_array(int *arr, int n){
 5:     int total = 0;
 6:     for(int i = 0; i <= n; i++){   // bug: should be i < n
 7:         total += arr[i];
 8:     }
 9:     return total;
10: }
11: 
12: int main(){
13:     int arr[5] = {1, 2, 3, 4, 5};
14:     int result = sum_array(arr, 5);
15:     printf("Sum: %d\n", result);
16:     return 0;
17: }

Compiling and running it I get:

josephraskind@stargazer:/tmp/gdb$ gcc arr_bug.c -g -o ./arr_bug; ./arr_bug
Sum: 32779

Which is clearly not the 15 I would have expected. This tells me I should fire up the debugger. I can do so with a call to gdb ./arr_bug:

I've now entered gdb. There are a handful of core gdb instructions that are often always helpful:

  • run
    • Execute the program
  • break
    • fn break breaks at the beginning of the fn function
  • next
    • Advances one line at a time (bypasses functions)
  • step
    • Advances one line at a time (enters functions)
  • info locals
    • Prints out all local variables and their values

gdb let's you freeze a program and look at it step by step:

(gdb) b sum_array 
Breakpoint 1 at 0x1169: file arr_bug.c, line 4.
(gdb) run
Starting program: /tmp/gdb/arr_bug 

Breakpoint 1, sum_array (arr=0xf0b5ff, n=21845) at arr_bug.c:4
4       int sum_array(int *arr, int n){
(gdb) 

Here I set a breakpoint at the entrance to the function sum_array and run the program. Once program execution hits sum_array it is paused. I can then step one line (after the function arguments are initialized) and look at the local variables and arguments:

Breakpoint 1, sum_array (arr=0xf0b5ff, n=21845) at arr_bug.c:4
4       int sum_array(int *arr, int n){
(gdb) step
5           int total = 0;
(gdb) info local
total = -8729
(gdb) info args
arr = 0x7fffffffdde0
n = 5

We see that gdb is now paused at the int total = 0 statement. I use info locals to check the value of all local variables (here total has yet to be initialized). I then use info args to check that the arguments look right—everything seems to check out.

I can then keep using next to go from one line to the next. Eventually, we'd be able to see that i goes beyond the boundaries of arr:

6           for(int i = 0; i <= n; i++){   /* bug: should be i < n */
(gdb) info locals
i = 5
(gdb) print arr[i]
$1 = 32767
(gdb) print arr[5]
$2 = 32767

I can use the print function to evaluate expressions using the available symbols at that point in the runtime. We can see that the access out of the array boundaries is what is returning that large value. Now it is a simple process of exiting gdb with quit and fixing the code!

Using gdb can get extremely complicated. For this class, most major issues can be solved with setting a breakpoint and stepping through a function line by line while examining local variables/global data structures. It is a great resource and should not be ignored when you are in trouble.

Contact: [email protected] | rss feed | Compiled with org-mode | Licensed under CC BY-NC-SA 4.0