A Quickstart Guide to Debugging C Programs with gdb

If you're writing C or C++ in a GNU/Linux[1] environment, gdb is a great tool for finding bugs in your code. This document is a very quick introduction to gdb to help you solve some common problems.

Jump To...

What is gdb?
Before Using gdb
Starting gdb
Quitting from gdb
Where Did My Program Crash?
Display Source Code
Which Function Calls Caused the Problem?
Using Breakpoints and print to Examine Executing Code
Continuing or Stepping Execution After a Breakpoint
Command Summary
Finding Out More
Footnotes

What Is gdb?

gdb is a special program that lets you inspect other programs as they run. For example, you can use gdb to spy on the values of variables in a C program you're working on to see if there are any problems with your logic.

Errors in programs are often called bugs. If a program unexpectedly stops executing because it has a bug, we say the program has crashed. Not all bugs cause programs to crash: bugs can cause all sorts of unexpected and often damaging behaviour. Examples include printing incorrect information, refusing to accept input from users, corrupting databases, and (in rare cases) even erasing entire disks.

Fortunately GNU/Linux does a pretty good job of protecting users from bugs in each other's code, so a simple mistake in your code won't erase a whole disk. Most of the time, a bug in your code will just crash your program or make it behave strangely.

Programs like gdb are called debuggers. They help you find bugs in your code. Sadly they don't find bugs automatically and eliminate them, but they can save a lot of time by helping you track the bugs down.

Before Using gdb

Before using gdb to debug your code, you should compile your code with the -ggdb option. For example instead of typing:

gcc -ansi -Wall -pedantic -o buggy buggy.c

you should type:

gcc -ggdb -ansi -Wall -pedantic -o buggy buggy.c

The -ggdb option tells the compiler to insert debugging information into the compiled code. That information enables gdb to do its job.

If you try to use gdb on code that hasn't been compiled with the debugging information, you'll typically see mysterious errors like these:

No symbol "myVariable" in current context.
Function "myFunction" not defined.
No line 34 in file "../sysdeps/i386/elf/start.S".

Recompile your code with the -ggdb option and the errors should disappear.

Starting gdb

This example assumes the executable you want to debug is named buggy. To start a gdb session on buggy, type:

gdb buggy

gdb will print some information about itself and then prompt you to type gdb commands. You'll see something like this:

GNU gdb 5.3.92
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i586-suse-linux"...
(gdb)

The version numbers and operating system name may be slightly different, but the (gdb) prompt at the bottom is where you'll type gdb commands. Don't forget to press the Enter key after you type each command.

Quitting from gdb

To quit from a gdb session, type q (short for quit). If your program is still running, gdb will ask if you really want to quit. If you do, type y followed by the Enter key.

This check may see a little unnecessary, but it helps prevent people quitting accidentally at that crucial moment in a lengthy debugging session. It has saved me from frustration a few times.

Where Did My Program Crash?

Often, if you run a program with a bug from the shell, the program just stops running and the shell prints a message like "Segmentation fault" or "Illegal instruction". That's not very helpful. If you run your program from inside gdb instead, gdb will tell you what line of your program was executing when the program stopped.

To run your program inside gdb, start gdb from the shell (e.g. "gdb buggy", replacing buggy with the name of your program), then type r (shorthand for run). Here's an example:

(gdb) run
Starting program: /home/jonathan/tmp/gdb-intro/buggy 

Program received signal SIGSEGV, Segmentation fault.
0x08048326 in main () at buggy.c:5
5           *p = 'a';

This tells us that the program stopped at line 5 of buggy.c which is in the program's main() function. gdb helpfully prints out the offending line of source code. Hmmmm, this bug looks like a pointer problem.

If gdb doesn't print the line number or the source code on that line, you've probably forgotten to compile your code with the -ggdb option. Just quit gdb, recompile your code with the -ggdb option, and try again.

Display Source Code

You can display your source code inside gdb using the l (or list) command. gdb will print ten lines of source code at a time, with a line number at the start of each line. You can give the list command a line number or function name to tell gdb where to start. Here are a couple of examples. The first uses a line number, the second a function name:

(gdb) l 12
7           int result = 0;
8
9           if (!isdigit(*p))
10              return -1;
11          else
12              while (isdigit(*p))
13                  result += *p - '0';
14          return result;
15      }
16
(gdb) l checkUserInput
14          return result;
15      }
16
17      void
18      checkUserInput(char* p)
19      {
20          int id = isValidNumber(p);
21          if (id < 0)
22              fputs("Invalid id number\n", stderr);
23      }

gdb actually prints a few lines before the position you ask for to give you some extra context. Entering the l (or list) command without a line number or function name continues listing where the last list command finished.

As a shortcut, pressing the Enter key without typing a gdb command repeats the previous command.

Which Function Calls Caused the Problem?

When your code has a few different functions in it, it's often useful to know which functions called which other functions when a bug crashes your program. gdb's bt (or backtrace) command can help here:

(gdb) run
Starting program: /home/jonathan/tmp/gdb-intro/buggy 

Program received signal SIGSEGV, Segmentation fault.
0x0804836c in isValidNumber (p=0x0) at buggy.c:6
6           if (isdigit(*p))
(gdb) bt
#0  0x0804836c in isValidNumber (p=0x0) at buggy.c:6
#1  0x080483ae in checkUserInput (p=0x0) at buggy.c:15
#2  0x080483d5 in main () at buggy.c:22

This tells us that in file buggy.c, on line 22 main() called a function named checkUserInput(), which in turn called another function named isValidNumber() on line 15. It was on line 6 of buggy.c in isValidNumber() that the program crashed.

Looking carefully at what gdb prints, you can see the parameters that were passed to each function when it was called. Hmmmm, it looks like that pointer p wasn't initialised...

Even if you forgot to compile your code with debugging information in it, gdb can sometimes provide a backtrace. You'll probably just see the names of some functions and no parameters.

Using Breakpoints and print to Examine Executing Code

The b (or break) command tells gdb to pause execution of your program at some point to allow you to inspect the value of variables. Just like the list command, the break command accepts a line number or a function name. gdb will pause executing the program just before the specified line number is executed or just before the first line of the specified function is executed.

You normally need to set breakpoints before you start executing code inside gdb.

Once you have paused a program executing in gdb, you can use the p (or print) command to print the values of variables or parameters. Here's an example:

(gdb) b 14
Breakpoint 1 at 0x804846d: file buggy.c, line 14.
(gdb) run
Starting program: /home/jonathan/tmp/gdb-intro/buggy 
id: 123

Breakpoint 1, isValidNumber (p=0xbffff260 "123") at buggy.c:14
14                  result += *p - '0';
(gdb) p result
$1 = 0

In this case the value of result is 0. Remember that gdb stops just before executing a line of code with a breakpoint. So 0 is the value of result before the code on line 14 has been executed. You'll find out how to print the new value of result shortly.

You can print the value of almost any expression that is valid in C, including array values, members of structures, etc. However the normal C rules about which variables are accessible apply when you print values. You can only use variables that are in scope at the current point of execution where gdb is paused.

You can set as many breakpoints as you like in your code. Each one is given a unique number (e.g. 1 in the example above) when you set the breakpoint. The number is printed every time gdb stops at the breakpoint. If you want to delete a breakpoint, use the d (or delete) command and give it the number of the breakpoint you want to delete. For example, to delete the breakpoint in the previous example, you would type:

(gdb) d 1

Continuing or Stepping Execution After a Breakpoint

When execution is paused and you've finished examining variables, you can resume executing your code by using the c (or continue) command. Execution will continue until the next time gdb encounters a breakpoint in your code.

Instead of continuing, sometimes it's good to step through your code one line at a time. The n (or next) command will execute the line of code gdb is currently paused at, display the next line it will execute, and stop just before executing that line.

Here's an example of using next on a function call after a breakpoint has stopped the program:

Breakpoint 2, checkUserInput (p=0xbffff260 "2345") at buggy.c:23
23          int id = isValidNumber(p);
(gdb) n
24          if (id < 0)

Notice how the line number on the left went from 23 to 24? The next command moved past the function call without going into the isValidNumber() function. If I had typed n again, the if's condition would have been evaluated. If id was less than zero, gdb would next stop at the first line of code within the if statement. Otherwise gdb would either stop in the first line of code in the else, or the statement after the if (if there was no else).

Remember that gdb stops just before executing each line of code it displays. The next command actually executes the line of code that is currently displayed, moves to the start of the next line of code, then displays the code without executing it.

If you want to find out what is going on inside a function when it is called, use the s (or step) to step into the function call. gdb will pause just before executing the first line of code in the function. Once you're in the function, you can use any combination of next and step commands you like.

Here's an example of using step on the same function call we saw in the previous example:

Breakpoint 2, checkUserInput (p=0xbffff260 "456") at buggy.c:23
23          int id = isValidNumber(p);
(gdb) s
isValidNumber (p=0xbffff260 "456") at buggy.c:7
7           int result = 0;

Notice that the line number went from 23 to 7. We obviously jumped to another place in the source code. gdb helpfully printed the name and parameters of the isValidNumber() function we stepped into.

Summarising, next calls functions without going into them, while step goes into the function so you can debug it.

A jump in line numbers doesn't always mean a function call. If you have blank lines or multi-line comments in your code, gdb will skip them since they aren't executable. That can cause sudden jumps in the line numbers printed by the next and step commands. Jumping to the else of an if statement or jumping back the beginning of a loop can cause that too.

Using the next and step commands on a return statement in your code causes gdb to return from the function and move the current point of execution to the place where the function was called.

Command Summary

q
quit
Quit gdb.
r
run
Run your program.
l 247
list 247
List your source code at line 247.
l myFunc
list myFunc
List your source code starting at the definition of function myFunc.
p myVar
print myVar
Print the current value of myVar. As well as variables, you can also print most C expressions, e.g. myList[i] + 10, student.name. Note that #defined values don't work.
r
run
Run your program.
bt
backtrace
Print a backtrace of the functions that have been called to reach the current execution point in your program.
b 36
break 36
Set a breakpoint at line 36 of your code.
b myFunc
break myFunc
Set a breakpoint at the start of the function myFunc in your code.
d 3
delete 3
Delete breakpoint number 3.
c
continue
Continue running your program after a breakpoint has paused it.
n
next
If your program is paused, execute the next line of code. Execute entire function calls rather than stepping into them.
s
step
If your program is paused, execute the next line of code, stepping into any function calls rather than executing them completely.

As a shortcut, pressing the Enter key without typing a gdb command repeats the previous command.

Finding Out More

What you've just read about gdb should give you enough knowledge to tackle most common problems in your code.

There is much more to gdb than what you've read here. There is extensive info documentation for gdb. If you haven't used the info command, it's well worth spending a few minutes learning a little about it. Many GNU/Linux utilities like gdb come with info documentation.

To spend a few moments learning about info, type "info info" as a shell command, and follow the instructions until you've learned how to quit from info. Now you should be familiar enough with info to read the gdb documentation.

To find out more about gdb, type "info gdb" as a shell command.

Happy bug hunting!

Footnotes

1
Linux is an operating system kernel. Software like gcc, gdb and the bash shell is part of a project called GNU (Gnu's Not UNIX). When most people say they're using Linux, they really mean they're using a Linux kernel and a mix of GNU and other software. I refer to GNU/Linux rather than Linux out of respect for the many people who have contributed to GNU projects over the years. That's particularly important since this document describes how to use a GNU utility. Without GNU we probably wouldn't have GNU/Linux as we know it today.

© Jonathan Knispel 2005

Valid XHTML 1.0 Strict