Debugging computer source code can be a rewarding and frustrating experience. Since source code is becoming much more complex, many tools and techniques to assist in debugging have been developed. It is helpful to be aware of these tools and techniques so that you can use them while working on your projects and labs. With them, you can become successful in submitting correct, well-written source code. One of the tools, which integrates visualization, is an interactive website at: http://www.pythontutor.com/c.html#mode=edit. A lot of us have been using it already. It is not fully tested, but quite impressive.
A debugger is a program that allows you to see what is going on while a different program is executing. Most debuggers allow programmers to set breakpoints in their code (places where execution is paused) and examine the state of variables while the program is paused. Other features include the ability to set conditional breakpoints which will pause execution at the point where the value of a variable is changed or set to a specific value. The ability to see what is happening is very handy when you are trying to track down problems inside your code.
There are many different debuggers available on many different systems. They can be divided into two types:
- Command Line Interface (CLI) - These debuggers use commands typed on a keyboard to operate. Common debuggers of this type are DBX and GDB.
- Graphical User Interface - These debuggers are often GUI wrappers over a Command Line Interface debugger. They allow programmers to use a mouse to set breakpoints, run code, and inspect variables. Certain GUI debuggers are part of a larger Integrated Desktop Environment (IDE). Examples of popular IDEs are Eclipse and Microsoft Visual Studio. The C tutor (http://www.pythontutor.com/c.html#mode=edit) belongs to this category.
This lab will introduce you to one of the more popular CLI debuggers, GDB. GDB is the GNU Project debugger, and is part of the overall GNU consortium, the same group that maintains GCC, the GNU Project C Compiler. More information about GDB can be found at its project page: (http://www.gnu.org/software/gdb/) and on the online documentation "Debugging with GDB" found at: (http://sourceware.org/gdb/download/onlinedocs/gdb/index.html)
GDB is very powerful and allows you to do many useful things to examine your code. However, this assignment will concentrate on setting breakpoints and inspecting variables, along with an introduction to finding the source of segmentation faults (a sometimes tricky issue). You will practice using gdb by debugging an existing program. The source code for this program, Debug2.c, can be found with the materials for this lab.
In order to use gdb to debug an executable, you must compile the program with
"debugging options" turned on. The most common option is the -g
option. Thus,
to compile Debug2.c, you will use the following command:
gcc -g -o Debug2 Debug2.c
The -g
option will add debugging information to your executable. This will
make your program size larger, and could potentially cause your program to run
slower. To see this, use the following command to compile Debug2.c:
gcc -o Debug2 Debug2.c
Now check the size of the Debug2 executable with the ls -l
command:
ls -l Debug2
Write the size (in bytes) of the executable _________ (Note: The size
in bytes is the number immediately before the date in the output from the
ls -l
command)
Now, recompile using the -g
command line option and write the size in bytes
of the new executable: _________
There are many options that can be used to generate debugging information in
your code. The man
page for gcc
will list many of them. For now, just stick
with the -g
option.
To start GDB, the easiest way is to type gdb
at the command line. Do so now.
You should see something similar to:
bash $ gdb
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>.
(gdb)
What you actually see depends upon what type of computer you run the command on, and the particular version of gdb installed. Copy the first line that is printed when you run gdb: _______________
Now, copy the portion in the quotes on the line that states
This GDB was configured as.
For instance, in the example above, you would
copy i686-linux-gnu: _______________
GDB has internal help. You can get a list of help topics by typing help
at
the (gdb)
prompt. Do so now, and count the number of topics shown for
List of classes of commands:
Write the number of topics/classes: _______________
To get help on a specific topic, you type help <topic name>
So, to get help
on breakpoints, you type help breakpoints
. Try it now. Get help on
breakpoints. You should see a list of all the commands you can use regarding
breakpoints.
As you can see, there are many different commands you can use regarding setting and using breakpoints. We will discuss some of the more common commands later.
The help command can also give you additional information on various commands.
For instance the break
command is one you will likely use many times in your
programming career. Try it now - type help break
to get specific information:
Write the first line printed after the executing the command here:
_______________
Just as important as starting GDB, you will need to know how to quit GDB. This
is done with the quit
command. Type quit <return>
at the (gdb)
prompt
now.
Many gdb
commands have shortcuts so you won't have to type the complete
command. For instance, instead of typing quit
to exit GDB, you can type q
to have the same result. Or, for the break command, you can just type b
.
Now that you know how to start and quit GDB, let's debug the Debug2
program
you compiled earlier. Make sure you are using the version you compiled with the
-g
option. Type the following command to start GDB:
bash$ gdb Debug2
GDB will start, and will automatically load your program so that it can be run within GDB. You should see a line such as the following as GDB loads your program:
Reading symbols from /home/user/DebugLab/Debug2...done.
While debugging your code with GDB, you may find errors and wish to recompile
and reload your program without exiting GDB. You load a program into GDB with
the file
command:
(gdb) file Debug2
GDB will prompt you to make sure you wish to Load new symbol table ...
You
can also use the file command to load a program into GDB if you started it
without specifying a file on the Unix command line. However, most of the time,
GDB will realize that you recompiled your code and automatically load the
latest version. That way, when you start your program, you are using the most
recent update.
You run your program using the run
command:
(gdb) run
Try it now. What happened? ________________
Your program takes command line arguments. However, no arguments were used when executing the run command. If you are debugging a program with command line arguments, you simply add them to the run command:
(gdb) run 1
Try the run command again, this time adding the command line argument 1
.
What happened this time? ________________
When option 1 is chosen while running Debug2
, it (incorrectly) adds the
numbers from 1 to 10 and prints the result. You might be able to inspect the
code to see the error and fix it, but let's practice using some typical GDB
commands to inspect the code and discover what the problem is. At this point,
you will likely find it easier to have three Unix windows open on your
computer. One window will have a vim/vi
session where you edit the source code,
one window will be used for compiling your code after making changes, and the
third window will contain your GDB session. Set these windows up before
continuing. In vi
you can type :set number
to turn on the line number.
:55
to go to line number 55.
There are two main ways to set breakpoints in gdb:
- Line number
- Function
When setting a breakpoint by function you use the syntax:
(gdb) break <function name>
Try to set a breakpoint so that the program pauses execution after entering the
DebugOption1()
function. The gdb
command you will use is:
(gdb) break DebugOption1
You should see gdb
give a response similar to:
Breakpoint 1 at 0x804860a: file Debug2.c, line 55.
You can set many breakpoints, so each breakpoint you set is assigned a number. This is so you can toggle breakpoints on or off, or delete them.
Now run the program in gdb
using 1 as the command line argument.
At what line number does execution pause? _________________
Before going on to step through the program, there are some other useful commands you can do at this point. One is to print the current stack. To do this, you use the command "where." Try this command now. You should see a response similar to:
#0 DebugOption1 () at Debug2.c:55
#1 0x08048564 in main (argc=2, argv=0xbffff3e4) at Debug2.c:32
The lines above state where each function call occurred up to the point
execution halted. As mentioned before (and shown by line #0
), you are
currently at line 55
in Debug2.c
, which is the start of the DebugOption1
function. You can also see (from line #1
) that this function was called from
the main()
function and the call occured at line 32
in the file. You can
also move up and down the stack using the up
and down
functions
(go figure). Try typing up
at the gdb
command line. The general "position"
of gdb
is now in the main()
function. That means you can print values of
variables that are contained in main()
but perhaps not accessable from
DebugOption1()
. For example, main()
contains a variable named option
.
According to the source code, if option is equal to 1
, it will call the
DebugOption1()
function. Let's verify the value of option by using the
following command:
(gdb) print option
Write what you see as a result: _______________________
Print
is one of the commands you will use most often when using gdb
. We
will discuss its usage a bit more later. For now, go back to the
DebugOption1()
function by typing the down
command.
Besides using GDB to print values of variables and set breakpoints, the next most common GDB usage has to do with stepping through the code line by line. There are several commands that you can use to do this:
step
- step to the next source code line. If the line to be executed is a function call, the function will be entered and the execution will pause.
next
- similar to step, but will skip over a function call before stopping. The function call is still executed, but the program does not step inside the function.
continue
- continue running from the current line until the next breakpoint or the end of the program is reached.
finish
- continue until the current function exits and stop after the return (also called "step out of function").
Most of these commands can be specified simply by using the first few letters
in the command. For instance, s
can be used for step
and n
can be used
for next
.
Notice that gdb
skipped line 54
which contained:
int i, j;
The reason it did not stop there is because there is no code to execute on that
line. That line only declares variables. Instead, the execution stops at
line 55
which contains:
int sum=0;
Because the variable sum is assigned a value which requires an "execution" of
code, gdb
stops on that line. The variable sum has not yet been assigned. Use
the print
statement mentioned above to print the current value of sum
.
What is its current value? _______________
It is likely that the current value of sum
is some large random integer. This
is because sum
has not yet been initialized. Remember how problematic using
uninitialized variables can be? This is an example.
Note: Sometimes the value of
sum
will be0
, but you can't assume this will always be the case.
Let's try using the step
command. If you have been following the directions
up to this point, you will be at the beginning of the DebugOption1()
function
(line 55
in Debug2.c
). Keep the window that shows the source code handy.
Now type the step
command at the gdb
prompt.
On what line does the program execution pause? _______________
Now, use the step
command to step over the current line.
At what line does the execution pause? ___________________
Print the value of sum
again.
What is its value now? _______________
Continue stepping through the program until the following line is reached:
sum +=sum + i;
Print the value of sum
: ________________. You can print the
value of i
as well.
Step
and next
can take an integer as an optional argument. For instance:
(gdb) step 5
will cause the program to step through five source code lines before pausing.
In essence, it is similar to using the step
command five times in a row.
Now we will demonstrate the other typical way breakpoints are set - by line
number. Note the line number of the current place where execution is paused. To
set a breakpoint at a specific line number, simply type break <line number>
.
Practice this by typing the following command:
(gdb) b 63
Note we are using the shortened command for break
this time. The second
breakpoint has now been set. This means that each time the continue
command
is given, the execution will pause at line 63
until the loop finishes.
We want to observe how the value of sum
changes as the program runs. We could
use the print
command each time we reach a breakpoint. However, this can
become tedious, especially if there are several variables we wish to monitor.
gdb
has a command called display
. The syntax for this command is
display <variable name>
.
Type the following command to turn on the display of the variable sum:
(gdb) display sum
This command is similar to print
, except that every time execution pauses,
the values of all variables that are set to display are printed. To turn off
the display of specific variables, use the undisplay <variable>
. command.
Now, continue execution several times with the continue
(cont
) command.
Stop when sum
is equal to 57
.
What is the value of the variable i
at this point? _____________
Does it make sense that 0 + 1 + 2 + 3 + 4 + 5 + 6=57
? Do you see what the bug
is in the program? If you don't see it, start the run of the program from the
beginning with the run 1
command and step through the code within this loop
carefully. The breakpoints and the display are still set, even though you are
restarting the program. You may also wish to display the i
variable instead
of printing it out each time the program pauses.
Once you see what the bug is, describe it below:
_______________________________________
Fix the bug in your source file window and save your changes. Recompile the executable using the compiling window.
Now, type the command to run the executable (run 1
). If you were still in a
debugging session, gdb
will ask you if you wish to start again from the
beginning (y
or n
). Pick y
and hit return
. gdb
will notice that the
executable has changed and will reload the symbol table (i.e. load the new
program). Note that the breakpoints will still be present. However, you will
have to redisplay any variables you had set to display automatically. Note also
that if you added or removed lines of code during editing, your breakpoints may
be at different source code lines. If this is the case, you may need to update
your breakpoints.
Suppose you have set several breakpoints, but you don't currently remember at what lines they were set. You can use the following command to list them:
(gdb) info breakpoints
You can also disable or enable breakpoints as you are debugging. Disabling a
breakpoint leaves the breakpoint present, but execution will not pause when the
breakpoint is hit. To disable or enable a breakpoint, you must use the
breakpoint number (id
). This value can be found with the info breakpoints
command. You disable a breakpoint with the disable <breakpoint id>
command. A
disabled breakpoint is enabled with enable <breakpoint id>
.
If you don't need a specific breakpoint for debugging anymore, you can delete
it with the delete <breakpoint id>
command. For example:
(gdb) delete 1
The above narrative discusses several important and useful gdb
commands.
Before moving on, you may wish to experiment with the commands you have
learned, using the given code. The more you experiment, the more comfortable
you will be using gdb
. And the more comfortable you are, the faster you will
be able to find bugs in your code.
There are many, many additional commands that can be used with gdb
, and this
portion of the assignment only scratched the surface. It is recommended that
you use the help
command liberally and seek out other gdb
tutorials so that
you may gain practice with some of the finer points of using gdb
.
The next section of this assignment will discuss using gdb
to find and debug
segmentation faults. Exit your current session of gdb
so that you can start
fresh with the next section.
As you use pointers more often, you will encounter Segmentation Fault
(seg fault
) errors. These errors may seem scary, but with gdb
, it is
usually not difficult to discover the source of the problem.
Generally, seg faults occur when a divide by 0
occurs, and when a NULL
pointer is de-referenced. A divide by 0
should be fairly self-explanatory.
However, an example of dereferencing a NULL
pointer is given below.
typedef struct _debugHw
{
int i;
char c;
} debugHw;
Later on in the code:
debugHw *dl=NULL;
dl->i=35;
Note that since *d1
is set to NULL
, it has no memory address that it can go
to in order to set the variable i
in the debugHw
structure. This is a
serious problem for the computer, so it performs a segmentation fault and ends
the program, usually dumping a large core file that takes up disk space. (Core
files can be very useful for debugging, but that technique goes far beyond the
scope of this assignment).
Run the Debug2
code on the Unix command line, but this time use option 2
as
a command line argument. You should see a segmentation fault message:
mason> Debug2 2
Segmentation Fault (core dumped)
Now, load Debug2
into gdb
and type the command run 2
. Notice that gdb
tells you exactly the line of code where the seg fault occurs.
What line is it? ___________________
Before inspecting the actual line of code where the problem occurs, let's look
at some of the other variables that did NOT cause the seg fault. Print the
variable dl3
using the print
command. Note that the entire structure is
printed with the values of the internal variables of the structure.
Write the values of i
and c
: _________________
Now print the value of dl2
using the following command:
(gdb) print dl2
What is its value? ___________________
Note that dl2
is a pointer, so a memory address is printed. To see the
contents of the memory pointed to by that address, use the following command:
(gdb) print *dl2
What are the values of i
and c
? ___________________
Now, let's look at the actual line of code causing the seg fault. Using gdb
,
print the value of dl1
using the following command:
(gdb) print dl1
What is its value? ___________________
Note that dl1
is a pointer. Now, try to dereference dl1
and print its value
using the following command:
(gdb) print *dl1
Write the message given: ___________________________
Because dl1
is set to NULL
, it can't dereference memory address 0x0
.
Therefore a segmentation fault occurs. Not all segmentation faults will be as
easy to find as this one, but knowing how to step through code and inspecting
pointer variables along the way can help you narrow down where and why such
errors occur.
The above narrative discusses several important and useful gdb
commands.
Using debuggers like gdb
can make it much easier to find bugs in your code.
The faster you find bugs in your code, the sooner you will complete your
assignments. The sooner you complete your assignments, the more your quality
of life will improve. See what wonderful things gdb
can do for you?
There are many, many additional commands that can be used with gdb
, and this
assignment only scratched the surface. If you wish to learn more about gdb
,
use the help command liberally and seek out other gdb
tutorials to gain
practice with some of the finer points.
Copy your answers from this document to the more streamlined "Answer Sheet" attached to this assignment. Submit your answer sheet and your modified source code as HW 8.