TOC PREV NEXT

Program — Debugging


No doubt the program for the previous programming assignment gave you some practice in debugging. This assignment will give you more. You are to find some bugs we've installed in a program. gdb will again be helpful.

Readings

House, chapter 8; sections 9.1 through 9.4, 12.1, and 12.2.

Kernighan and Ritchie, material on arrays in chapters 1 through 4 that you didn't read for the "Fundamentals" quiz, plus sections 5.1 through 5.3. Material in sections 1.10, 3.8, 4.3, 4.5, 4.6, 4.7, 4.10, 4.11.2, and 4.11.3 will not be covered.

Related quizzes

Simple strings and pointers.

Background

This assignment provides an introduction to some of the major features of gdb (the GNU debugger) by leading you through the debugging process of a slightly buggy program. A debugger allows the programmer to stop a program in the middle of its execution so that he or she may observe how the program branches and executes. Debuggers can help speed the process of isolating and correcting bugs.

Follow the instructions outlined in the assignment in order to debug the provided program and answer the questions throughout the assignment on a separate piece of paper. You will be required to present the corrected code, script files (to be explained later), and answers to the questions in this assignment. The questions in this assignment are meant to be thought-provoking. They illustrate important aspects of the C language. If you experience problems, do not hesitate to ask a tutor for help!

Students who do not wish to use gdb or students who use their own development package (Borland C, etc.) will need to follow the steps dictated in the instructions and demonstrate identical functionality in the debugger of their choice (e.g., via screenshots). If you can't provide such documentation, you will need to use gdb.

The code associated with this assignment is a short program whose purpose is to turn regular phone numbers into "words." For example, the Federal Express customer service number is 1-800-GO-FED-EX, translated from 1-800-46-333-39. The provided program, when working properly, will provide all possible "words" that have no more than a user-specified number of consonants. (Too many consonants may make the "word" unpronounceable.) Below is a transcript of how the program should work, with user input appearing in boldface:

	This program finds words that can be associated with phone numbers.
	What is the maximum number of consonants you would like
	the generated word to possess?
	(note: 0's and 1's are considered consonants)
	4
	Please enter a 7 digit phone number with dashes removed
	4460175
	iio01pj  iio01pk  iio01pl  iio01rj  iio01rk  iio01rl  iio01sj
	iio01sk  iio01sl  iio01pj  iio01pk  iio01pl  iio01rj  iio01rk
	iio01rl  iio01sj  iio01sk  iio01sl  iio01pj  iio01pk  iio01pl
	iio01rj  iio01rk  iio01rl  iio01sj  iio01sk  iio01sl  iio01pj
	iio01pk  iio01pl  iio01rj  iio01rk  iio01rl  iio01sj  iio01sk
	iio01sl  iio01pj  iio01pk  iio01pl  iio01rj  iio01rk  iio01rl
	iio01sj  iio01sk  iio01sl  iio01pj  iio01pk  iio01pl  iio01rj
	iio01rk  iio01rl  iio01sj  iio01sk  iio01sl  iio01pj  iio01pk
	iio01pl  iio01rj  iio01rk  iio01rl  iio01sj  iio01sk  iio01sl
	iio01pj  iio01pk  iio01pl  iio01rj  iio01rk  iio01rl  iio01sj
	iio01sk  iio01sl  iio01pj  iio01pk  iio01pl  iio01rj  iio01rk
	iio01rl  iio01sj  iio01sk  iio01sl
	Would you like to try another number? (Y/N)
	n

Before continuing on with this assignment, make sure you have done all of the associated reading and understand how the code we've provided should work. The program can be found in the file ~cs9c/lib/generate.c.

Assignment

The first step in the debugging process is the compilation of the program. In the process of compiling code, useful error messages are provided that identify problematic parts of the code that are not syntactically correct. Very often, these mistakes are typos.

If you haven't done so already, copy the file into your own directory using the following command:

	% cp ~cs9c/lib/generate.c .  (don't forget the dot!)

To compile the program type:

	% gcc generate.c1

You should see something similar to the following (your output may not be exactly the same):

	generate.c: In function 'main':
	generate.c:54: parse error before 'puts'
	generate.c: In function 'print_if_good':
	generate.c:210: warning: value computed is not used
	generate.c: At top level:
	generate.c:231: warning: return-type defaults to 'int'
	generate.c:231: conflicting types for 'convert'
	generate.c:9: previous declaration of 'convert'
	generate.c: In function 'convert':
	generate.c:236: warning: control reaches end of non-void function

The numbers after the filename refer to the numbers of lines that contain errors. "Parse errors" typically signify something that is syntactically incorrect. "Warnings" are issued for code that the C compiler believes to be problematic but that is syntactically correct. Warnings will not prevent your code from compiling, but they can tip you off to some rather serious problems.

  1. Use the text editor of your choice, correct the errors. (Typing ctrl-C in Pico or esc-x line-number-mode in emacs tells you what line you're on.) Make a note of what changes you made. You may need to make small changes and recompile to see if those errors are fixed. This may take several iterations. It is okay not to fix all the warnings but all of the non-warning problems will need to be fixed.

    If you reach this point:

    	generate.c: In function 'print_if_good':
    	generate.c:210: warning: value computed is not used
    

    Your program has compiled successfully; Don't try to figure out what's wrong with this line yet. We'll figure it out eventually.

In order to record your debugging session, you will need to use the script command. The script command stores the text of your session in a file. Type the following:

	% script assignment2

Your prompt may change at this point (don't worry). Now all of your interaction with the prompt will be recorded in the file named assignment2.

Let's see if the program works. To run the compiled program type the following (words you type are in bold):

	% a.out
	This program finds words associated with phone numbers.
	What is the maximum number of consonants you would like
	the generated word to possess?
	(note: 0's and 1's are considered consonants)
	4
	Please enter a 7 digit phone number with dashes removed
	4460175

  1. The program has one or more bugs. How does the output suggest something is wrong?

We will now use gdb to begin debugging. To start gdb debugging this program, type:

	% gdb a.out
	GDB is free software and you are welcome to 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.
	GDB 4.16 (hppa1.1-hp-hpux9.05), Copyright 1996 Free Software Foundation, 
	Inc...
	(gdb)

Before we begin running the program, we will need to set a breakpoint. A breakpoint is a programmer-specified point in the code where we would like the program to "pause" at while the program is running. Why would we want to do this? Using a debugger, we may examine variables in this paused state which will give us important information about whether or not the program is working correctly. Type:

	(gdb) break main
	Breakpoint 1 at 0x2330: file generate.c, line 42.

This will ask the program to stop at the first executed line of code of the main ( ) function. Variable declarations will be processed immediately. To run the program, and have it stop at this point, type:

	(gdb) run
	Breakpoint 1, main ( ) at generate.c:42
	43        goahead = 1;

Notice that the program has stopped right before it is about to execute a line of code. In this case, the line of code in question is line 43 (your output may vary). To see this, let's find out what the value of goahead is. Type the following:

	(gdb) print goahead
	$1 = -1073785707

In the above case, the variable goahead is storing a garbage integer (your output will definitely vary here!). Remember, if you don't assign a variable an initial value, its contents will be unpredictable. Now type:

	(gdb) next
	44        while (goahead)

The next command executes the line of code that was stopped at, and then stops the program at the very next line of code. To make sure the assignment goahead = 1 has actually taken place, we can again view the contents of goahead:

	(gdb) print goahead
	$2 = 1

Sometimes we may want to find out the value of a variable after each statement. gdb provides this functionality in its display command. Type:

	(gdb) display goahead
	1: goahead = 1
	(gdb) next
	puts ("This program finds words associated with phone numbers.\n\n");
	1: goahead = 1

Continue executing the program line by line using the next command. Stop when you see the following:

	57            gets (input);
	1: goahead = 1

Type the following:

	(gdb) display input
	2: input = "38!(@#*(#*!(@#1"
	(gdb) next
	4
	64            maxNumConsonant = input[0] - '0';
	2: input = "4\000!(@#*(#*!(@#1"
	1: goahead = 1

Notice that the contents of the string input has changed. Since strings are null-terminated, when gets reads a string, it copies the user's input into the array passed into the function and places a null terminator (denoted by \000 in the display portion) into the array when it reads a carriage return.
  1. Using the techniques you have learned so far, explain what gets copied into maxNumConsonant after the line above is executed. What gdb commands did you use to find out what was copied into maxNumConsonant? Explain what input[0] - '0' does.

Continue executing the code line by line until the following is reached2:

	71            generateSpellings (phone, maxNumConsonant);

generateSpellings is a defined function within this program. We may be interested in watching what happens within the generateSpellings function. If we type next at this point, we will go to the next instruction instead of going inside the generateSpellings function. In order to go inside generateSpellings, we can use the step command:

	(gdb) step
	generateSpellings (phone=0x7fffbda8 "4460175", maxNumConsonant=4) at 
	generate.c:128
	128       char dial[10][4]={

Several things happen now. First, the variables that you painstakingly "displayed" one by one are no longer refreshed. The reason is because those variables are not accessible from within the generateSpellings function. The line above shows the contents of the two variables passed into the function: phone and maxNumConsonant. Your output may be somewhat different.

For your information, 0x7fffbda8 is the hexadecimal (base 16) equivalent for 2,147,466,664. gdb denotes memory addresses by the 0x prefix. As you can see, the hexadecimal format is a more efficient way of representing large numbers.
  1. What is the value stored in phone? (Hint: Everyone's answer will be different.) Why is phone represented by an address?

Continue using the next command until you see:

	144       convert (phone, phone_int);
	

Let's step into this function as well:

	(gdb) step
	convert (phone=0x7fffbda8 "4460175", phone_int=0x7fffbd08) at gener-
	ate.c:233
	251       for (i = 0; i < 7; i++)

When stepping into a function, gdb does not display the contents of an integer array when it is passed in as an argument. This is unlike what it does with character arrays. (Note the difference between phone and phone_int.) However, regardless of variable type, gdb will always show you what the value of that variable is when it is passed into the function. If this doesn't make sense, then you didn't answer question 4 correctly.

In the next five lines is a for loop. It is often helpful to monitor a for loop's progress. Type the following:

	(gdb) display i
	3: i = 74128
	(gdb) display phone_int[0]
	4: phone_int[0] = 268530816
	(gdb) display phone_int[1]
	5: phone_int[1] = 4199972
	(gdb) display phone_int[2]
	6: phone_int[2] = 8192
	(gdb) display phone_int[3]
	7: phone_int[3] = 0
	(gdb) display phone_int[4]
	8: phone_int[4] = 843604063
	(gdb) display phone_int[5]
	9: phone_int[5] = 0
	(gdb) display phone_int[6]
	10: phone_int[6] = 268436928

Again, because values have not been assigned to the array cells above, their contents are in fact completely arbitrary. Your output may differ somewhat.

Monitoring the for loop's progress generally means stepping through it via repeated step or next commands. In gdb, hitting return re-executes the most recent command; this facility simplifies the process of monitoring a long stretch of code.
  1. Use the techniques that you've learned thus far and determine whether or not the for loop in this function works. What evidence suggests so?

When you exit the function, you should see the following:

	generateSpellings (phone=0x7fffbda8 "4460175", maxNumConsonant=4)
	    at generate.c:152
	157       for (a = 0; a < 3; a++)

The statement above lets you know that you've returned to the calling function (in this case, generateSpellings).
  1. Starting on the above line, a series of nested loops is encountered. How does each nested loop work? Make sure you step through the loop and demonstrate that you've actually examined how the loop works.

Continue executing the code line by line until you reach

	176          print_if_good(maxNumConsonant, word, &level);
	
  1. What is the current value of word? Which gdb command did you use to find its value? Is its value correct?
  2. In line 176, the address of the variable level is passed into print_if_good as opposed to the variable's contents. The comments explain that this is a technique that allows the function to change the contents of a variable passed into a function. Explain how passing a pointer to a variable accomplishes this.

Step into this function using the step command:

	(gdb) step
	print_if_good (maxNumConsonant=4, word=0x7fffbd48 "ggm01pj", 
	level=0x7fffbd50)
	    at generate.c:193
	206       int numVowel = 0;

In the following code, there is a for loop that counts the number of vowels and an if statement that determines whether or not the generated word is printed. We are interested in testing what happens when the if condition is true. Since we may not enter the condition every time, we could end up typing next many times before we reach the statements within the if condition. One way to skip to the "interesting" part of the program is to execute the following commands:

	(gdb) break 207
	(gdb) continue

In the above gdb statements, we have asked the program to stop at line 207. Line 207 is the call to printf within the if statement. The continue statement tells the program to run at full speed until the next breakpoint is reached-in this case, when we next execute line 207.

You should eventually see something similar to

	Breakpoint 2, print_if_good (maxNumConsonant=4, word=0x7fffbd48 
	"iio01pj",
	    level=0x7fffbd50) at generate.c:207
	207           printf ("%s  ",word);

Print the word and go to the next line:

	(gdb) next
	210           *level++;

Now find out what the value of level and *level is (your output will vary):

	(gdb) display level
	11: level = (int *) 0x7fffbd50
	(gdb) display *level
	12: *level = 0

The hexadecimal number 0x7fffbd50 is 2,147,466,576 in decimal. Let's find out what happens when we execute this line.

	229           if ((*level)%7 == 0)
	4: *level = 0
	3: level = (int *) 0x7fffbd54

The value of level is now 0x7fffbd54 or in decimal, 2,147,466,580. *level is still 0. What result did we expect? It seems like we might have a problem here.
  1. Explain what the problem is in this section of code. Fix the problem in the code, and recompile the program. What happened to the warning?

The quit command exits gdb.

When you've recompiled the program, verify the rest of the program works as described. To end your script, type:

	% exit

You will need to turn in your corrected version of generate.c, the script of your interaction with the debugger, and your answers to the boxed questions in this assignment.

Checklist

A satisfactory grade on this assignment requires the following:

Corrected version of generate.c
Script of debugger interaction
Answers to questions 1 through 9
HY: 8/98

Buggy program

#include <stdio.h>
#include <string.h>
/****************************************************************
	Function declarations.
*************************************************************** */

void generateSpellings  (char [], int);
void convert (char phone[], int* phone_int);
void print_if_good (int, char[], int[]);
int isvowel (char);

/****************************************************************
main( )
input
	Character array that is used to store user input.
	Its size is greater than two characters in order to 
	accommodate responses such as yes/no and possibly
	other unpredictable user responses. (See a tutor for
	an explanation.)
phone
	Character array used to store the actual phone number
	entered by the user.  Space is allocated for seven
	digits and the corresponding carriage return.
maxNumConsonant
	User-entered value that filters out those
	numbers with too many consonants.
goahead
	true/false flag that determines whether or not
	the user wants to test another number.
*************************************************************** */

int main ( ) {
	int maxNumConsonant;
	int goahead;
	char input[8];
	char phone[8];
	
	goahead = 1;
	while (goahead) {
	/* This part of the code gathers user data.  The use of puts and gets is illustrated here.  
	puts sends its char array to standard output followed by a carriage return.  gets puts 
	everything in the standard input up to the carriage return into a character array.  In place 
	of the carriage return, puts has a '\0' to signify the end of the string. */
		puts ("This program finds words associated with phone numbers.\n\n");
		puts ("phone numbers\n\n")
		puts("What is the maximum number of consonants you would like");
		puts("the generated word to possess?");
		puts ("(note: 0's and 1's are considered consonants)");
		gets (input);
		
		/* Each digit read in as a character needs to be converted to a number for comparison pur-
		poses later.  The difference between the ASCII value of the user input and the ASCII value 
		of '0' will return the appropriate numeric value for the character. */
		maxNumConsonant = input[0] - '0';
		puts ("Please enter a 7-digit phone number with dashes removed");
		gets (phone);
		
		/* The generateSpellings function does most of the work. */
		generateSpellings (phone, maxNumConsonant);
		putchar ('\n');
		putchar ('\n');
		puts ("Would you like to try another number? (Y/N)");
		gets (input);
		if ((input[0] == 'Y') || (input[0] == 'y')) {
			goahead = 1;
		} else {
			goahead = 0;
		}
	}
	return 0;
}

/*****************************************************************
generateSpellings (char*, int)
phone_int
	The characters '1', '2', '3', ... are converted to
	1, 2, 3, ... and stored appropriately in this 
	numeric representation of the user's phone number.
level
	This variable is used to make the printouts nice.  
	It is passed using the "address of" operator, as described in House
	in section 5.2.1. Passing a variable using the "address of" operator
	in this way is a technique that allows a function to 
	change its contents in such a way that the changes
	are recognized in the calling program.
	Note that level should really be a local variable in
	generateSpellings; it is used as a parameter here for
	illustrative purposes.
a-g
	Index variables used to keep track of for loops.
word
	A candidate "word" that is then passed on to
	print_if_good, which prints the word if it meets
	the minimum requirements for consonants.  The
	representation is again an array of characters
	terminated by a NULL ('\0').
dial
	A two-dimensional array.  For each dial [i][j], i represents
	the actual number on the key pad (a value between 1 and 10)
	and j indicates a choice of letter that the key pad could represent.  
	So, for example, dial[2][3] would represent the 
	third letter on button #2 of the keypad, namely the letter "c".
	Similarly, dial[2][2] == 'b', dial [3][3] = 'f', etc.  
	Confused? Pick up your phone and take a look!  
	Some letters aren't mapped the way they should be. 
	Note that the letters "q" and "z" have been removed.  
	The numbers 0 and 1 have no associated letters and so 
	the program simply returns a "1" or a "0" in those
	circumstances.
*************************************************************** */

void generateSpellings (char* phone, int maxNumConsonant) {
	int phone_int[7];
	int a, b, c, d, e, f, g;
	char word[8];
	int level;
	char* dial[10] = {"000", "111", "abc", "def", "ghi", "jkl",
		"mno", "prs", "tuv", "wxy"};
	/* Each ASCII character stored in phone is converted to its appropriate number. */
	level = 0;
	convert (phone, phone_int);
	/* The following for loop works as follows: for each of the seven digits in the phone number, we 
	generate all the different possible letters each letter can take on by considering each digit in 
	turn until all values are exhausted. */
	for (a = 0; a < 3; a++) {
		for (b = 0; b < 3; b++) {
			for (c = 0; c < 3; c++) {
				for (d = 0; d < 3; d++) {
					for (e = 0; e < 3; e++) {
						for (f = 0; f < 3; f++) {
							for (g = 0; g < 3; g++) {
								/* The candidate word is built here.  Starting with the first letter of 
								the word we assign a candidate translation dial[][a] based on 
								the original number's value. */
								word[0] = dial[phone_int[0]][a];
								word[1] = dial[phone_int[1]][b];
								word[2] = dial[phone_int[2]][c];
								word[3] = dial[phone_int[3]][d];
								word[4] = dial[phone_int[4]][e];
								word[5] = dial[phone_int[5]][f];
								word[6] = dial[phone_int[6]][g];
								word[7] = '\0';
								/* print_if_good prints the word if it meets the minimum 
								requirements. */
								print_if_good (maxNumConsonant, word, &level)
							}
						}
					}
				}
			}
		}
	}
}

/* This function prints the word if it has maxNumConsonant or fewer consonants. */
void print_if_good (int maxNumConsonant, char* word, int *level) {
	int i;
	int numVowel = 0;
	/* This loop counts the number of vowels. */
	for (i = 0; i < 7; i++) {
		if (isvowel(word[i])) {
			numVowel++;
		}
	}
	
	/* This loop prints the word if there is maxNumConsonant or fewer consonants. */
	if ((7 - numVowel) <= maxNumConsonant) {
		printf ("%s  ",word);
		/* Every time we print a word, we add 1 to the value of level. */
		*level++;
		/* Every seventh word we print, we add a carriage return to make the format nice. */
		if ((*level)%7 == 0) {
			putchar ('\n');
		}
	}
}

/* This function checks to see if the letter in question is "a", "e", "i", "o", or "u". */
int isvowel (char x) {
	return ((x == 'a') || (x == 'e') || (x == 'i') || (x == 'o') || (x == 'u'));
}

/* This function converts the character array into numbers and places its contents into 
	the array phone_int. */
convert (char* phone, int* phone_int) {
	int i;
	for (i = 0; i < 7; i++) {
		phone_int[i] = phone[i] - '0';
	}
}
1 The actual command is gcc -g -Wall generate.c but we've set up the account so that you don't need to worry about the command line options following gcc when compiling from the shell. The -g option links the source code to the executable and the -Wall option turns on warnings. In emacs you can compile your program without exiting the editor. Type esc-x compile and then hit return. Delete the contents of the command line and replace that with gcc -g -Wall generate.c.
2 You will be prompted to enter a phone number with dashes removed somewhere in the process. After the program has read the input phone number, you may want to verify that the appropriate information was read.

TOC PREV NEXT