CSE2305 Semester 2/2002

Monash University
Faculty of Information Technology
CSE2305 Object-oriented Software Engineering
Introduction to GDB and Make

Download programs in: code.tar.gz

Debugging with Gdb

Having written programs before, the notion of a bug will be familiar. The UNIX system provides debuggers for tackling problems that evade the Ansi C compiler. There are many debuggers available for use, we shall examine gdb.

The most common sort of errors that occur when executing your program are:

Segmentation violation
means that your program tried to reference an area of memory that it was not allowed to. A pointer maybe pointing to somewhere wild or a wrong array index value.
Bus error
Often caused by scanning a non-terminating string.
Core dumped
the kernel saved the state of your executing program in a file called core in the current directory.

Using the debuggers, the line in the program where the error occurred can be identified, as well as the value of the variables at that point.

AIM

To debug bug1.c using the available debugger, gdb. Although some of the errors are obvious let's use gdb to find them; don't modify the program directly, follow the instructions below to familiarise yourself with gdb.

Step 0. Create a backup of bug1.c

Since bug1.c will be modified, it might be wise to copy the original onto a backup. Call it bug1.c.bak. This way you can always refer to the original if you need to.

Step 1. Compiling with '-g'.

Before the debugger can be used to debug your program, the program must be compiled with the -g option. The -g option causes the compiler to generate additional information that is used by the debugger. So edit the Makefile to compile bug1.c with the -g option. Run make.

Step 2. Execute the program.

Examine the contents of bug1.c, read the header, and note what the program is supposed to do. Now try executing your program. You should find that it doesn't work according to its specification.

You should get the following error message:

Segmentation fault (core dumped)
Notice that this message has not specified where the error has occurred, the message only specifies the type of error produced.

Step 3. Starting the debugger.

To find out what is wrong in the program let's put it through our debugger. To do this type:

$gdb bug1
A message should appear indicating which debugger you are using and the debugging prompt (gdb) should also appear depending on which operating system is used.

Step 4. Debugger Help utility.

If this is the first time you have used a debugger it is a good idea to examine the sort of functions the debugger provides. Type:

(gdb) help

The gdb help utility groups its commands into various classes. For example all the commands related to examining the internal stack produced by running your program are listed under the stack command. Spend some time browsing through the types of commands available under each class. Type:

(gdb) help running
(gdb) help stack
(gdb) help data
(gdb) help breakpoints

Go down the list, without worrying too much about what each command does or how it works. For further information on each utility type help followed by the utility name. We shall now use some of gdb's features to debug our simple program.

Step 5. List the source code.

To start let's list the whole source program. Type:

(gdb) help list
(gdb) list bug1.c:1,20

Step 6. Commence program execution.

Since we aren't exactly sure where the error occurred, let's execute the program using the debugger then examine the stack to discover precisely where the program crashed. Type:

(gdb) help run
(gdb) run

You'll see the following message and prompt:

Starting program: /...pathname.../bug1
Do you wish to read in a word? (y/n) 
If the program requires input you'll have to type it in. Enter y or n.

Step 7. Examining the stack state.

Now we have reproduced the same error as before while running the program through the debugger. However this time we have a little more information. You should see something like

Program received signal 11, Segmentation fault
0x400fe8 in _doprnt () at ../doprnt.c:327
../doprnt.c:327: No such file or directory.

This may seem a little strange since we have no function doprnt() in our program. By printing a STACK TRACE we can work out where the problem occurred. Before we do so, examine the stack commands using the help utility. Type:

(gdb) help stack
(gdb) help bt
(gdb) bt

You should see something like:

#0 0x400fe8 in _doprnt () at ../doprnt.c:327
#1 0x400680 in fprintf () at ../fprintf.c:110
#2 0x400270 in main () at bug1.c:24

This says that main called fprintf, which called _doprnt. Since _doprnt isn't mentioned anywhere in bug1.c, our troubles must be somewhere in fprintf or above. If we look at the fprintf invocation in this defective program, it is wrong. There's no FILE pointer specified so it is treating the string "Finished reading in words\n" as such.

A STACK TRACE can be printed by using the "where" gdb command. The "where" command has been aliased to the "bt" command. Type:

(gdb) help aliases

for a list of aliases available.

Such errors, in which a function is called with the wrong arguments, can be found by using the C verifier lint(). Lint examines C programs for potential errors, portability problems and dubious constructs. Also such errors may be determined using the compiler gcc.

Step 8. Quitting the debugger and executing shell commands from within the debugger.

There are two ways to modify your program.

Obviously the program is not doing what it should be. Why is the program not executing the correct statement and what is causing this to happen? This is what we shall try to figure out next.

Step 9a. Examine control flow in the program.

We shall do this by tracing the program and examining the value of variables along the way. Go back into gdb if you are not already in there. List the source code.

Step 9b. Stop program execution at a particular code line.

Let's examine if the correct value has been stored in the variable ReadInWord. To do this set a break-point at the line in the program which contains the if-statement. A break point marks the position in the source code where execution should temporarily halt. Then examine the variable's value.

(gdb) help breakpoints
(gdb) break 23
To check that the break point has been set, type:
(gdb) help status
(gdb) help info
(gdb) info breakpoints
You should see the following information
Num     Type    Disp    Enb     Address What
1       breakpoint      keep    y       0x0040025c      in main at bug1.c:23

This tells you that a breakpoint is set in function main at line 23 in the program called bug1.c. Now run the program from within gdb entering 'y' as input.

The program will not run to completion, rather it will stop execution at the line where the breakpoint is. Note that line has not yet been executed.

Step 10. Examine data, ie. values of variables.

We can examine the value stored in the variable ReadInWord, by typing
(gdb) help data
(gdb) help print
(gdb) print ReadInWord[0]
You should see something like:
$1 = 121 'y'

Go back and read the help information provided for the print utility. You should work out at least that the variable ReadInWord so far contains the correct value.

Step 11. Single stepping a program statements.

Things look pretty good so far. One would now expect the program to execute the else part of the if-statement. Does this actually happen? Lets take a single step through the program. Type:
(gdb) help step
(gdb) step

What did you notice? Examine the value stored in ReadInWord. What value is stored there now? Why did it change? How can you fix the problem? Edit your program. Compile and run your program, entering 'y' as input.

Under LINUX, you should find another error occurring even before any word is entered:

Segmentation fault (core dumped)

Step 12.

Go back into gdb. Run your program from here. Note where the error occurs. Examine the variables i and c.
(gdb) print i
(gdb) print (char)c

You should find that i is set to 0 and c contains a '\n' character (ascii value 10). Why has the for-loop terminated before a word is entered? How would you discard the '\n' after the response 'n' or 'y' is entered? Fix the program, compile and run it, type in a word.

Under Linux, you should find a segmentation fault occurring.

Step 13.

The program is trying to store a character in an area of memory that it's not allowed to. Examine the variable word. It is a pointer to a character. Has space been allocated to store the word? Obviously not. Allocate space to hold the word by defining word to be an array of 20 characters or using malloc() function.

Edit and compile your program. Run it, and notice that this time no errors are produced but the program isn't printing out the word entered by the user.

Step 14. Examine the control flow of the for-loop.

Set a break point at the start of the for-loop. Rerun your program.

Step through the for-loop skipping, proceeding through the call to getchar().

(gdb) help next
(gdb) next

What is the value of i? What is stored in c? Now execute the next line. Type

(gdb) step

What is stored in the array word? What does this indicate?

Modify your program, so that the correct block is included in the body of the for-loop. Recompile your program and run it again.

Step 15. Remove unwanted files.

After you have finished debugging bug1.c type:
make clean
This will remove the core file created and other files no longer needed.
19990722:155602 ajh This document created by A.J.Hurst from an original troff document by T.I.Dix.