Debugging by Setting Breakpoints


Most new dbx users find the utility helpful because it lets them 
execute their programs a line at a time.  Before you can use dbx 
in this manner, you must tell the debugger where to halt the program's initial 
execution.  This is done by setting a breakpoint.


Use the debugger's list command to display main.c's 
source:


(dbx) list

    1

    2   #include 

    3   #define MAXLEN 1000

    4

    5   main()

    6   {

    7     char instring[MAXLEN];

    8

    9     printf("Enter a string with leading and

                 trailing blanks:\n");

   10     if (gets(instring))


When you use list without arguments, the command displays the 
source file's next ten lines of code.  Use the command again:


(dbx) list

   11       {

   12         printf("input = '%s'\n", instring);

   13         preblank(instring);

   14         tblank(instring);

   15         printf("output = '%s'\n", instring);

   16       }

   17     else

   18       {

   19         printf("You didn't enter a string.\n");

   20       }


The program doesn't do anything until the first printf() 
statement.  This line of code certainly doesn't generate the bug, so you can 
set the breakpoint at a line after that.  Since the next statement, line 11, 
is the part of the program that takes the data from the user's terminal, set 
the breakpoint there.  Set it by using the stop command as 
follows:


(dbx) stop at 10

[1] stop at "main.c":10


Now begin the program's execution with the run command:


(dbx) run

Enter a string with leading and trailing blanks:

[1] stopped in main at line 10 in file "main.c"

   10     if (gets(instring))}


Execution halts at the breakpoint.  When dbx halts execution at 
a breakpoint, it does so before executing the code on that line.  Our 
breakpoint halts execution just before main() calls the 
gets() function.


You can now step through the program by using the next command:


(dbx) next


Nothing happens.  That's because the program is waiting for you to enter the 
test string.


    asdf    	<-- Enter data with four leading 
and trailing blanks.


stopped in main at line 12 in file "main.c"

   12         printf("input = '%s'\n", instring);


next is not the only command you could have used to execute the 
line.  The debugger also provides a step command.  The 
difference between the commands is this:  if the current line of the program 
is a function call, next will execute the call and stop 
execution on the next line of the program; the step command will 
halt execution on a source line within the function being called.


To demonstrate the difference, use the step command as follows:


(dbx) step

input = '    asdf    '

stopped in main at line 13 in file "main.c"

   13         preblank(instring);

(dbx) step

stopped in preblank.preblank at line 4 in file "preblank.c"

    4       int n = 0;


You have stepped your way into the preblank() 
function.  If you had continued using next, dbx 
would have executed the function call and would have halted execution on the 
next line of main.c.


Now that control has passed to preblank(), you can display 
its source with the list command:


{    5

    6       while (s[n] == ' ' || s[n] == '\t')

    7       {

    8         ++n;

    9       }

   10       if (n > 0)

   11       {

   12         adjstring(s, n);

   13       }

   14       return;}


You could at this point continue by executing this function line by line, but 
since blankstrip appears to be removing leading blanks correctly 
(that is, preblank seems to be working fine), you want to return 
control back to main(). You can do so by setting a new 
breakpoint at the end of preblank(), then continuing 
execution until the new breakpoint is reached:


(dbx) stop at 14	<-- Set a breakpoint at the 
end of the function.


[6] stop at "preblank.c":14


(dbx) cont	<-- Continue execution to the breakpoint.


[6] stopped in preblank.preblank at line 14 in file "preblank.c"

   13           return;

(dbx) step	<-- Step back to main.c.


stopped in main at line 14 in file "main.c"

   14         tblank(instring);


Control has passed back to main().  The next line within 
main.c is the call to tblank(), the function 
suspected of being buggy.


Now that you are back in main.c, display the value of 
instring:


(dbx) print instring

"asdf    "}


preblank() has removed the leading blanks.  What will 
tblank() do?  Execute the function call with 
next, and then display instring's value:


(dbx) next

stopped in main at line 15 in file "main.c"

   15         printf("output = '%s'\n", instring);

(dbx) print instring

"asdf    "


The trailing blanks remain; the bug appears to lie within tblank.c. 
 Finish executing the program with the cont command:


(dbx) cont

output = 'asdf    '


execution completed}


Now you want to remove the old breakpoints and set a new breakpoint in 
tblank.c.  Use the status command to display the 
current breakpoints, then remove them with the delete command:


(dbx) status

[1] stop at "main.c":10

[3] stop at "preblank.c":14

(dbx) delete 1

(dbx) delete 3

(dbx) status

(dbx)}


You must now find a suitable place to set the new breakpoint.  Before you can 
list tblank.c's source, you need to tell dbx to 
switch from the main.c file to the new source file.  Do this with 
the file command:


(dbx) file tblank.c


Now display the file:


(dbx) list

    1

    2   tblank(s)

    3   char *s;

    4   {

    5       int slength;

    6

    7       slength = strlen(s);

    8

    9           while((s[slength] == ' ' || s[slength] == '\t'))

   10           {}


The function's execution begins at line seven, so set the new breakpoint 
there:


(dbx) stop at 7

[4] stop at "tblank.c":7


Now run the program again:


(dbx) run

Enter a string with leading and trailing blanks:

    asdf

input = '    asdf    '

[6] stopped in tblank.tblank at line 7 in file "tblank.c"

    7       slength = strlen(s);}


Use the next command to go through the function and the 
print command to display slength's value:


(dbx) next

stopped in tblank.tblank at line 9 in file "tblank.c"

    9           while((s[slength] == ' ' || s[slength] == '\t'))

(dbx) print slength

8

(dbx) next

stopped in tblank.tblank at line 13 in file "tblank.c"

   13           s[++slength] = '\0';}


Wait a minute . . .  Execution skipped from line 9 to line 13!  What's going 
on?  Display the missing lines:


(dbx) list 9, 13

    9           while((s[slength] == ' ' || s[slength] == '\t'))

   10           {

   11               --slength;

   12           }

   13           s[++slength] = '\0';


In jumping from line 9 to line 13, execution bypasses the 
while loop.  This is the part of the program that removes 
trailing blanks.  Apparently the value of s[slength] fails 
the loop's test.  In otherwords, the end of the string is not a blank or a tab 
character.  Display the value of s[slength]:


(dbx) print s[slength]

'\0'}


The value is supposed to be a blank space, but instead it is the null 
character.  Display more of s's values:


(dbx) print s[slength-1]

' '                               <-- Here's a blank.

(dbx) print s[slength-2]

' '                               <-- And another.


Aha!  That's the bug.  The strlen() function at line 7 
returns the number of characters in the string.  The tblank() 
function uses that value--8 in this case--to set the upper boundary of the 
string's array.  But C arrays are indexed starting from 0, not starting from 
1.  A string that holds 8 characters is indexed from 0 to 7, not 1 to 8.  When 
tblank() tests the value of s[8] at the head 
of the while loop, it uses the null character that marks the 
end of the string.  The program should subtract 1 from the value returned by 
strlen() before using it as the upper index boundary.  You 
can test the hypothesis like this:  rerun the program to the breakpoint in 
tblank.c; after strlen() has returned the value of 
slength, subtract 1 from that value and see if the new 
s[slength] satisfies the while test.


(dbx) cont		<-- Finish executing the program.

output = 'asdf    '


execution completed

(dbx) run			<-- Run the program again.


Enter a string with leading and trailing blanks:

    asdf

input = '    asdf    '

[6] stopped in tblank.tblank at line 7 in file "tblank.c

"

    7       slength = strlen(s);

(dbx) next		<-- Execute the strlen() call.


stopped in tblank.tblank at line 9 in file "tblank.c"

    9           while((s[slength] == ' ' || s[slength] == '\t'))


Now use the assign command to subract 1 from 
slength:


(dbx) assign slength = slength-1

(dbx) print slength

7}


Continue executing the program:


(dbx) next

stopped in tblank.tblank at line 11 in file "tblank.c"


   11               --slength;	<-- Now the loop executes


(dbx) next

stopped in tblank.tblank at line 12 in file "tblank.c"

   12           }

(dbx) next

stopped in tblank.tblank at line 9 in file "tblank.c"

    9           while((s[slength] == ' ' || s[slength] == '\t'))

(dbx) next

stopped in tblank.tblank at line 11 in file "tblank.c"

   11               --slength;}


Since tblank() appears to be working properly, let the 
program finish executing and see if it produces the correct results:


(dbx) cont

output = 'asdf'		<-- All right!


execution completed}


Use the quit command to exit the debugger:


(dbx) quit