CSCI 1300 - Exercise 2
Programming Errors and the GDB Debugger
What You'll Get from This Exercise
Last week
you learned how to use emacs and bgi++ to edit and compile programs.
If this editor and compiler are not installed on your machine, then
you may obtain install them as shown in the box to the right.
.
Now we would like you to learn about a debugger called gdb that you will
use this semester.
By using emacs in conjunction with
gdb, you obtain a some degree of integration among your
editor/compiler/debugger.
Create a Working Directory
As you did last week, make sure that the cs1300 software is installed
and create a working directory.
I would suggest that you use lab2 for
the name of the directory so that you don't get your work mixed
up with another's. So run these commands, filling in the blank with your login name:
D:
cd \Users
cd ________
mkdir lab2
cd lab2
Once this directory is created, download these two
files from the cs1300 online directory
(heatwave.cxx and bugs.cxx).
(You can download a file by right-clicking on the link and choosing
Save File As or Save Target As or some option like that.)
Starting the Debugger within Emacs
Today you'll start by running a debugger session using the
heatwave program.
To get the debugger started on the heatwave program,
make sure that you are in the working directory that you used in the previous exercise,
and open heatwave.cxx with the emacs editor.
Then recompile heatwave.cxx by giving the "ESC x compile" command.
In the miniwindow, you should erase "make -k" and type this
compilation command instead:
bgi++ -Wall -g heatwave.cxx -o heatwave.exe
The reason for recompiling is that the debugger
requires the compilation to have an extra -g switch. I would
suggest that you use -g in all your future compilations. You never
know when you might need to debug!
After the compilation finishes, you can
start the debugger with these steps:
Shut the compilation window (Ctrl-x 1, which says "give me
just one window").
Split the window into two (Ctrl-x 2). Both the top and bottom
window will have heatwave.cxx.
Move the cursor to the "other" window (Ctrl-x o). The cursor
should now be in the bottom window.
Start the debugger with the
command: "ESCAPE x gdb RETURN". The miniwindow will print a small
prompt "Run gdb (like this): gdb". You should type the name of the
executable file, as shown here:
Run gdb (like this): gdb --annotate=3 heatwave
Then press return, and the gdb debugger starts in the bottom
window.
Setting and Removing Breakpoints
When the debugger starts, the bottom screen prints a message and the
prompt (gdb). With the cursor at this prompt, you can give commands to
the debugger, and there are also a few special emacs commands that you
can give while the debugger is running. We'll start by
setting a breakpoint at the start of the main program. A breakpoint is
just a line of code where the program's execution will stop. I like to
stop at the beginning of the main program so that I can assess my
situation and proceed from there. Here's
how to set or remove a breakpoint:
Move to the code window (use "Ctrl-x o" to move to the other
window if you are not already in the code window).
Move the cursor desired line in the program. In this case, you
should move down to the beginning of the first cout
statement in the main program.
Ctrl-x SPACE (that is, Ctrl-x and then the space bar).
You'll see a message about the breakpoint appear in the bottom
window.
Or you might get an error message saying that there is no source
file HEATWAVE.CXX. If you get this error message, the problem is
that the file has been recorded as HEATWAVE.CXX (all capitals) but
it should be heatwave.cxx (lower case). To fix this problem, quit
emacs and give the rename command:
rename HEATWAVE.CXX heatwave.cxx
Later, if you need to remove the breakpoint, move the cursor back to
this line, and give the "remove breakpoint" command (Ctrl-x Ctrl-a
Ctrl-d).
An alternative to "Ctrl-x SPACE" is "Ctrl-x Ctrl-a Ctrl-t", setting
a temporary breakpoint (which will be removed the first time it is
used).
Running the Program
Once the breakpoint is set, you can run your program. Of course, it
won't run very far, since there is a breakpoint at the top of the
program. Move to the bottom window and type the word "run" at the
(gdb) prompt, and press return.
You might get a few warnings or messages about Windows DLLs.
Then you'll see messages similar to this:
Breakpoint 1, main () at heatwave.cxx:41
(gdb)
This message means that your program ran until it reached "Breakpoint
1", which was in the main() function on line 41 of heatwave.cxx. If
you look in the code window at the top, you'll see that emacs placed
an arrow on line 41 to indicate where the breakpoint occurred.
The prompt (gdb) in the bottom window means that the debugger is ready
for you to issue another command. You
can proceed in many ways. You can examine the values of variables, you
can look at the runtime stack (which indicates the current sequence of
function calls), you can continue executing the program. For now, you
should continue executing the program by typing "cont" and pressing
return. You will get the message "Continuing," and the program will
execute its dialog with you. You should answer all of the program's
questions until execution ends. At the end you see the message
"Program exited normally" and the (gdb) prompt appears once more.
Display, Next, Print, Step
The display command allows you to display the current values of particular
variables. To see how the display works, you should first restart your
program, by typing the run command once again. We'll run through the
program in a slightly different way, displaying the value of the variable
height. In order to display this variable, type the command "display
height" in the gdb window. You'll get a response such as "height=0",
or maybe "height=42" or "height=126429"--you really don't know what
value height will have right now because it hasn't yet been given a
value.
Now we will continue executing the program, but we'll execute only one
line at a time. In order to execute one line at a time, type "next"
command in the gdb window and press return. Type this command once now.
The next statement of your program is executed! In this program,
that statement is an output statement, and the output message
"How tall is your tree in feet?" appears in the gdb window. In the
code window, the arrow has moved down one statement to indicate the
current location of the execution.
Gdb also keeps you updated on the value of the display variable
(height), which has not changed since the last time it was displayed.
Finally, the (gdb) prompt appears again, indicating that the debugger
is ready for another command.
At this point, you should give another next command and press return.
The cursor will move to the next line and just sit there. Why did this
happen? Has the debugger crashed? No! You see, the next statement of
the program is an input statement to read the value of height. The
program is waiting for you to type that input. Go ahead and type a
number now, and press return. When you press return, the number is
read, the new value of height is displayed, and the (gdb) prompt
appears once more. Also, the arrow in the code window has moved down
to the next statement.
Let's add one more variable to the display. Type "display volume" and
press return. At this point, the volume just contains garbage because
the program has not yet assigned a value to the volume. Now, continue
executing the program one statement at a time, and stop when the
volume does change. Keep in mind that when the (gdb) prompt
appear, you must type a debugger command. When the cursor just sits
on the left with no prompt, you must type input for the program.
Also, keep an eye on the arrow in the code window to see
which statements are about to be executed.
After the volume changes to its new value, give the cont command to
execute the remainder of the program.
There are three other commands that you will find useful:
(1) undisplay followed by a number n will remove item
n from the display list. (2) print followed by an
expression will evaluate and print the expression once (without adding
it to the display list).
(3) step is similar to "next", executing one statement.
The difference is that the step command tries to step into the body of
each function, and execute the lines of each function one at a time. Since
heatwave has no functions, we're using next. (Also, step would cause
problems with heatwave, since the debugger would try to step into
the input >> and output << functions.)
Quitting and More About Gdb
To end gdb, type the quit command, press return, move the cursor
to the top window (Ctrl-x o) and set things to just one window (Ctrl-x 1).
As the semester progresses, you'll learn a lot more about gdb.
You can also print the
gdb reference card (in pdf format) at
www.cs.colorado.edu/~main/cs1300/lab/refcard-gdb.pdf
.
For now however, the few commands that you know
should be sufficient to finish this exercise.
Different Kinds of Errors
In the textbook, you'll read
about three kinds of errors that a program might have.
Briefly, the kinds of errors are:
Syntax Error: An error that occurs when the compiler does not understand what you have written. These are often small, such as a missing semi-colon.
Run-time Error: An error that occurs when a program is actually running. Typically, a run-time error causes a running program to stop immediately, and print an error message. For example, if a program includes an arithmetic expression x/y, and y happens to be zero—then a run-time error will occur when the expression x/y is encountered. But, this error does not occur until your program is actually running.
Logic Error: Sometimes your program compiles correctly, and has all the necessary directives, and runs without producing a run-time error. But, the answer that the program produces might still be wrong. These errors, also called “program bugs” are the toughest errors to find and eliminate.
During the rest of this lab exercise, we’ll give you a program that has each
of these kinds of errors. You’ll learn the typical ways of
tracking down and elmininating these errors. The most important thing
that you’ll do is continue
to use the debugger, which is a tool to help you track down those elusive logic errors.
Finding and Correcting the Syntax Error
Quit emacs and then restart it on the bugs.cxx file that you copied
from the cs1300 material.
The file is a C++ program that I wrote.
But I have made errors that you will fix.
Start by
trying to compile it in the usual way (ESC-X compile,
and type the compile command given here:
bgi++ -Wall -g bugs.cxx -o bugs.exe
The compiler will give a list of errors in a message window.
These are usually syntax errors.
When several syntax errors appear in the error message box, always deal with the topmost error first. Often, if you fix the topmost error, then the others will disappear.
In this exercise, there are several syntax errors.
The topmost error says something similar to this:
bugs.cxx: In function `int main()':
bugs.cxx:46: parse error before `='
This means that the compiler thinks that there is a something missing on line 46
of the program. To deal with this error, make sure that the cursor is
in the code window and then type Ctrl-X and press the backward quote
key (usually in the upper left corner of a keyboard).
Emacs
will move the first error message to the top of the error window and
place the cursor on line 46 of your code, where it thinks something
is missing. That line looks like this:
sum_square = height*height + base*base;
But this line looks okay! There doesn't seem to be anything wrong
before the '='. What is the compiler complaining about?
When the compiler complains about a line, and you don’t see a problem, start looking at the previous lines in your program. Often the error will be one or two lines before the compiler’s guess.
In this exercise, the error is a few lines before line 46. Can you see this line in the code:
cin >> base
This line is missing the semicolon at the end. The compiler didn’t realize that the semicolon was missing until it got to line 46.
In any case, you can fix the syntax error by placing a semicolon at the end of the statement
cin >> base;
Finding and Correcting the Run-time Error
Once more, compile your program. It should compile with no errors, and you
can run the program (from the DOS prompt). The program runs and asks you for
some information. But after reading the information, the program
crashes--maybe with that ominous "Illegal operation" message from Windows.
The phrase "illegal operation" indicates that the program did
something that the Windows operating system won't allow.
In the case of this program, you might
be able to figure out what caused the error by simply examining the
program. But other times it's hard to see exactly where an
error like this occurs. In such a case, the debugger can help you.
The debugger lets you step through the program one line at a time, showing
exactly which line is being executed.
In order to use the debugger, move the cursor to the compile window
and start the debugger with the ESC-X gdb command. When you are asked
how to run the debugger, indicate that you are running "gdb bugs" and
press return. When the degugger starts, go back to the code window and
put a breakpoint on the first cout statement of the program.
Now, back to the debugger window and run the program. The
program will run until you reach the breakpoint, which will be highlighted
by an arrow to indicate that it is about to be executed.
We're ready to execute the program one line at a time. Start by
pressing the n key and return. When you press this key,
the compiler executes one line of code and highlights the next line.
Press return again, and the compiler will execute the
n-command again.
Each time you press return, the debugger executes the
highlighted statement and moves to the next line of your program.
For example, when you execute a cout statement, the message will
appear in the gdb window. When you execute a cin statement, the
debugger waits for you to type the input before
continuing. Warning:
It is tempting to type the input when a prompt appears such as
"How tall is your right triangle in inches?" But don't! That prompt
appeared from the cout statement. You must wait until the cin
statement is executed before you type any input.
Keep pressing return, running the program one line at a time.
In this example, stop when you reach the line:
cout << 1/zero << endl;
Press return one more time to execute this line. The debugger should
intercept the illegal operation and print a message about an
"exception". This is somewhat useful because you can now see exactly
which line caused the error. In this case, the error is a division by
zero, which results in an undefined value. Of course, we shouldn't be
doing this division at all, so delete the whole line, quit the
debugger,
return to the code window and recompile. You'll probably still get one
warning from the compiler, indicating that the variable "zero" is not
used. To fix this warning, go near the top of the main program and
delete the declaration of the variable zero. Then recompile and run
the program once more. (You have to quit the debugger with the q
command before you can recompile.)
Finding and Correcting the Logic Error
Finally the program is working. Or is it? There are no syntax or link errors.
And there is no run-time error. But look at the answer that the program
wrote:
This is the same as 0 feet.
That's not right!
Even if a program compiles and runs without errors, you still must check
that the output is correct.
When a program has incorrect output, you can use the debugger to step through
it one line at a time with the variables displayed.
Go ahead and do this now, in the same way that you did before with a
breakpoint
on the first cout statement.
Start the programming running, and when you reach the breakpoint, you
should
put displays
on the five variables by typing these commands in the gdb window:
disp height
disp base
disp sum_square
disp hypotenuse
disp feet
You'll then see displays of all their current values.
At this point, the values of the variables will
look strange indeed. They are likely to contain garbage--whatever numbers
happen to be in the computer’s memory at the moment. But, soon this will
change. To see this change, run through the program, entering the number 30
for both pieces of data. After entering the second peice of data, the
highlighted statement will be the assignment statement:
sum_square = height*height + base*base;
Execute this highlighted statement, and keep one eye on the variables.
The value of sum_square should change to 1800 (if you used 30 for
each input number). Then execute the next statement, and the value of
hypotenuse will change to about 42.
At this point you are about to execute the next assignment statement:
feet = (1/12) * hypotenuse;
Now, hypotenuse is around 42, so we would expect feet to be a bit
less than 4. So, execute the statement, and oops! Why did feet change to zero?...
The reason for the zero has to do with the factor (1/12) in the arithmetic
expression. Remember that when C++ does division with integers,
the answer is the quotient, and any remainder is thrown away. So 1/12
is actually zero. Here are two possible ways to fix the assignment
statement:
The first solution works okay because we are not dividing two integers
(hypotenuse is a float number). The second line is another way to fix the
problem becuase 1.0 and 12.0 are treated as non-integer
numbers (the decimal point is enough to make them non-integer).
Go ahead and make this last correction, and run the program once more.
A Smooth Ending to the Program
The end of our triangle program contains these lines:
cout << "Please press the return key to end the program." << endl;
cin.ignore( ); // Read the return key at the end of the second input
cin.ignore( ); // Read the return key that’s pressed to end the program
These statements allow for a more elegant end to the program. The message
"Please press the return key to end the program" is printed.
Then a function, cin.ignore() is called twice. Each time cin.ignore()
is called, a character is read from the keyboard and thrown away.
The first ignored character is actually something that was typed long ago:
The return key after the second input number. The other ignored character is
the return key that is pressed to end the program. So, with our smooth
program ending, the message appears and the program then waits for you to
press the return key. After you press the return key, the program finally
ends.
Copying your File to a Floppy Disk or University Machine
At this point, you are done with the lab part of this exercise. You can exit
the compiler. If you like, you can send yourself a copy of the work by e-mail.
Saving the bugs program isn’t too important, but in the future you should
save all your work.