|
|
A. Buggy Problems
B. Debugging Techniques
C. Using the DDD Debugger
You have solved a problem, designed a solution, and implemented the solution in C++. Assume you compile the source and the compiler finds no errors. Next you start execution of your program but it does not work correctly. Maybe it gives the wrong answer or goes into an infinite loop. Maybe it "aborts" and a message similar to the following is printed:
"segmentation fault: core dumped"
For whatever reason, the outcome of executing the program does not produce
the expected results. There is an error in your program. Historically
these errors have been called "bugs." The term bug was first
coined by Grace Hopper, the inventor of COBOL and the first assembly language.
In 1945, the famous Mark I computer came to a halt. Workers found
a moth inside the computer, removed the offending beast and the computer
was fine! From then on, all mysterious problems with computing were
called bugs. All programmers run into problems related to bugs.
Therefore, let's now address how to avoid and deal with "bugs".
There are numerous famous bugs in computing. For example, the Russians lost a space ship in mid-flight when an erroneous command transmitted from mission control sent the space ship into a test routine from which it never recovered. There should have been some safety "switch" that prevented the accidental execution of such a test sequence.
A multi-million dollar X-ray machine killed at least two people by administering excessive doses of radiation. The cause of the error was traced to a certain sequence of editing commands. One day programmers may need "malpractice insurance" like physicians do today to protect themselves in such situations.
Can bugs be avoided? Yes and no. Software Engineering is
dedicated to developing techniques for building better software.
Our goal is to be as good at building software as an engineer is at building
a bridge. Bridges rarely fail just as software should rarely fail.
To help reduce errors in software we have developed paradigms of
software development. A paradigm is a method with all its related
steps and techniques (i.e., an approach). In the 1970's, a software
development paradigm called the Waterfall paradigm was developed.
It was so named because each step or phase supplies information to the
next like water flows over a waterfall. The phases are as follows:
| Analysis | ||||
![]() |
Design | |||
![]() |
Implementation | |||
![]() |
Testing | |||
![]() |
Maintenance |
In the analysis phase, requirements describing what the software product is to do are determined by interviewing the client. The requirements are analyzed and a specifications document is produced. In the design phase, the specifications document is turned into an architectural design in which the software is broken down into modules. The modules are designed in a more detailed fashion that focuses on procedural detail. The data structures are defined at this point. During the implementation phase, the modules are coded and tested. The modules are integrated and tested as a whole in the testing phase. The maintenance phase includes making changes to the software including corrections and enhancements. Many methods such as step-wise refinement and structured programming have been developed to improve the process of software development. We will focus in this lab (and in this course) on the Waterfall paradigm for software development. The object oriented paradigm is another popular method in use today.
Errors can occur at any phase of software development. Any of these errors can lead to serious problems and result in software that does not function as desired. Given the disastrous results with the software in the X-ray machine, we must take every precaution possible to be sure there are no errors in our systems. Several things have been done.
"Formal languages" have been developed in which requirements can be expressed instead of expressing them in ambiguous and imprecise English. The design can be reviewed by a team of professionals for potential problems. Prototypes can be used to clarify requirements or difficult design algorithms. An appropriate high-level language choice will reduce errors. Code walkthroughs help the developer locate many errors. A code walkthrough is a line-by-line reading of a unit of code by a team of professionals. The purpose of each line in the given program is explained. Good documentation habits and good naming conventions decrease errors too. A good design and a good programmer are the best way to avoid coding errors.
Finally, careful testing must be done before software is released. A later lab will address testing. Testing is the process of showing that errors exist and debugging is the process of localizing, analyzing, and removing suspected errors in the code.
If an instruction is written incorrectly it is called a syntax error
and will be caught by the compiler. If an incorrect formula or algorithm
is used it is called a logic error and must be caught by the programmer
or tester. Errors that are exposed at run time are called run
time errors (i.e., dividing by zero or array index out of range).
A theory of debugging was developed by Poston that is based on Most Probable Errors (MPEs). MPEs are the most frequent mistakes made by programmers. The most likely place to contain an error is in an IF - THEN - ELSE conditional expression. The second most likely place for errors is in input/output statements and the third is in loops. According to this theory, you should check the conditional expression in any If statement first. Then I/O statements should be checked and finally loops should be examined.
How does one localize and analyze an error? There are standard debugging techniques that can be used. You will practice these in this lab. They are as follows:
A program is written only once but is read many times. Donald Knuth has developed a system, called CWEB , that helps one develop software as a work of literature. Explaining CWEB is beyond the scope of this lab exercise but we must learn to read our programs for debugging purposes and write programs that are readable. If we read our code to ourselves we often read over our errors. It is a good idea to read our code to another knowledgeable person explaining how each line contributes to the solution.
Some of the exercises that follow make use of command line arguments. If you are unfamiliar with command line arguments, read Getting Command Line Arguments in C++ before continuing.
NOTE: For each of the following exercises, indicate answers on the answer sheet.
When reading the code does not help us identify the error(s), we can insert output (cout <<) statements at strategic places in the code to localize errors. These output statements allow us to trace the path of execution or to trace the value of certain variables as the program is executed to determine what part of the code is not functioning correctly. There are 3 types of traces that can be implemented by placing output statements in the code - subprogram trace, statement trace, and variable trace. A subprogram trace is used to determine the order in which subprograms are called. An output statement that outputs the name of the subprogram is placed at the start of every subprogram to implement a subprogram trace. A statement level trace is used to determine where the execution of the program halts. An output statement can be placed after different statements in the code to determine which instructions are executed before the program halts.
If an answer produced by the program is wrong, we look at variables used to calculate this answer. To implement a variable trace, we print the value of variables involved in the calculation at different places to see where one or more of them became incorrect. When a variable of interest is changed, the new value should be printed with the name of the enclosing block and a line number to help identify where the change occurred. All input to the program as well as input to all subprograms should be checked to make sure the values are valid.
Once we locate and fix the problem, the output statements are no longer needed. They can be removed with the editor and the code recompiled. In some languages such as "C++" there is a mechanism by which the output statements can physically be left in the source code but directions can be given to the compiler to omit them when the last compile is done. If the statements are needed later they do not have to be retyped.
A debugger is a program that controls the execution of another program for the purpose of locating (and/or repairing) errors. In this lab, we are using the DDD (the Data Display Debugger) debugger. DDD is free software. A debugger allows you to list source lines, print values of variables and expressions, or step through the code one instruction at a time. With DDD you can set a breakpoint to halt execution at a certain line or function, run the program from the start or continue after a break in the execution, do a backtrace to see who has called whom to get to where you are, or quit. There are dozens more commands, but we will only look at a few here.
To begin, you must be running XWindows for DDD to work. This means that you must be in the lab and you should use Exceed when you log on to frank from one of the windows machines. Once you are on frank, copy the program inlab2c.cc to your account. Read the code to get an idea of what the program should do. Compile and run this program by typing.
aCC inlab2c.cc -o inlab2c inlab2c
At the prompts enter that there are 2 numbers in the list both with a value of 22. Look at the results. Are they correct? Which results are wrong? Once you realize a program has errors, you may decide to run it under the control of a debugger to help find the error. Now compile the program so it can be run under debugger control:
aCC -g inlab2c.cc -o inlab2c
Now rerun the program to see that the errors are still there. Don’t laugh, it does happen that an error disappears when running under the control of the debugger. Use the same input as before.
Look at the source code for inlab2c.cc. Then start the DDD debugger by typing in the following at the frank% prompt:
ddd inlab2c
You should see the following screen:

Read the Tip of the Day and then click on the close button in the tips dialog box. You should now see two windows on the screen. The large window on the upper portion of the screen is called the Source Window and should display your source program. The smaller window on the bottom of the screen is called the Debugger Console where the output from the debugger and from running the source code will appear. As shown below, you can now see the source code for inlab2c.cc in the source window. On the right is a set of shortcut buttons for executing various commands.

If you place the cursor on one of the shortcut buttons, you will see a yellow popup window defining the use of the button. For example, the Step command is shown below:

Here is the list of the shortcut button commands with explanations.
|
Run |
Start debugged program |
|
Interrupt |
Interrupt debugged program |
|
Step |
Step program until it reaches a different source line |
|
Stepi |
Step one instruction exactly |
|
Next |
Step program, proceeding through subroutine calls |
|
Nexti |
Step one instruction, but proceed through subroutine calls |
|
Until |
Execute until the program reaches a source line greater than the current |
|
Finish |
Execute until selected stack frame returns |
|
Cont |
Continue program being debugged, after signal or breakpoint |
|
Kill |
Kill execution of program being debugged |
|
Up |
Select and print stack frame that called this one |
|
Down |
Select and print stack frame called by this one |
|
Back |
Previous source position |
|
Fwd |
Next source position |
|
Edit |
Edit source file |
|
Make |
Run the make program |
Let’s practice debugging inlab2c.cc. First we need to set a breakpoint on a line of code that could potentially show us what the problem is. It should be a line of executable code. Scroll through the code and find the line in main
total = sum(listOfNumbers, howMany);
To set the breakpoint, click and hold the right mouse button to the left of the line of code and select Set a Breakpoint. A stop sign should appear as shown below. (If you wish to delete a breakpoint, right click on it and select Delete Breakpoint. Do not delete the breakpoint you just set. If you accidentally deleted it, then simply reset it.)

Let’s run the program and see what happens. Click on the Run shortcut button. Notice the prompt for entering the number of items in the list is in the Debugger Console (the small window at the bottom) (see below). You will need to click on this window to enter the input. Use the same input as you did when you ran the program before.

After you enter the input, the debugger will execute the program up to the breakpoint. It will place a green arrow beside the stop sign as shown below.

To see the current values of different variables, simply place the mouse cursor on the variable you are interested in. A yellow popup window will appear containing the value of the variable. In the picture below, the cursor was on the variable listOfNumbers which is an array of ten integers. Notice that the popup window contains a list of ten numbers and the first two are 22 and 22 which are the numbers we entered.

A. What is the current value of the variable howMany?___
B. What is the current value of the variable total?___
It is not always possible to display the value of more complicated variables such as pointers and structs using the mouse. In order to visualize data, it is also possible to use the Data Window. In order to see how the Data Window works, click on Data and select Display Local Variables.

You should now be able to see the small Data Window at the top (as shown below). In it, on the left, you will see a black box containing the local variables. You will have to use the vertical scroll bar in the Data Window to see all the local variables. When you have scrolled down, your window should appear like the one below.

Notice that the variable listOfNumbers just has a […] beside it whereas howMany and total have a 0 beside them. In order to see the values in each of the locations of the array number, you will have to create a new display for number. To do this, click and hold the right mouse button on the variable listOfNumbers in the Data Window, then select New Display and Convert to Dec (see below).

You should now see the new display for listOfNumbers. You can scroll through the Data Window to see all the values.

Now let’s determine the cause of the error in the program.
C. Look at the cout << statement which is two lines below the current program statement (breakpoint). This statement will output the average. Given the current value of the variables, what is the problem with this statement?___
D. From reading the comment before the call to readNumbers(listOfNumber, howMany) in main(), and from reading the header comments in the function readNumbers(), what did the programmer intend for readNumbers() to do to the variable howMany?___
E. Why didn’t readNumbers() work?___
F. How would you fix it?___
Click on the Cont short cut button. This will continue execution of the program from the breakpoint. You should see Program exited normally in the Debugger Console window.
To exit the debugger, click on File and Exit
G. How do you set a breakpoint in DDD?___
H. Once all the breakpoints are set, how do you begin execution of the program in DDD?___
I. How do you inspect the value of a variable in DDD?___
J. How do you continue the execution of the program after a breakpoint has been reached?___
K. Where is the Debugger Console Window located?___
Turn in your answer sheet. Make sure you mark the errors on the program from Exercise 1.