CS248 Menu Buttons

UNIX Programming

"Chapter Nine - Debugging"

Chapter Outline

Lecture Notes

Debugging

Software systems that misbehave in this way are said to contain bugs..

This chapter looks at software defects and consider some tools and techniques for tracking down specific instances of erroneous behavior.

Type of Error

Bugs usually arise from a small number of basic causes, each of which suggests a method of detection and removal.

Specification Errors

If a program is incorrectly specified, it will inevitably fail to perform as required.

Design Errors

Design errors involve misinterpreting of the specification.

Coding Errors

Of course, everyone makes typing errors. You can also mistranslate the design into code.

Preparing to Debug

Now let's look at computer-based debugging tools and techniques and look at ways of tracking down a very common form of bug, the coding error.

The five stages of debugging are:

A Bugged Program

Let's look at a sample program that contains bugs.

Unfortunately, the code is not very readable, there are no comments and the original programmer isn't available.

We start from the basic routine debug1.c.


Let's try to compile the program.

It compiles OK, with no reported errors or warnings.

Before we run the program we add code to print out the results of sorting.

We call the new version debug2.c.

Now compile again and, this time, run the program.

The following is what happened on one of the author's system: But on the other machine the output was: You have an out of range array error.

If you increase the array size to 4096, as in the debug3.c program, both machines get a segmentation fault.

Code Inspection

It is often a good idea to re-read your program when it fails to run as expected.

There are tools that you can use to help with code reviews, the compiler being an obvious one. It will tell you if you have any syntax errors in your program.

Instrumentation

Instrumentation is the adding of code to aprogram for the purpose of collecting more information about the behavior of the program as it runs.

You can use the C preprocessor to selectively include instrumentation code.

You just need to recompile the program to include or exclude instrumentation code.

We can do this quite simply with constructs like:

We can compile the program with the compiler flag -DDEBUG to define the DEBUG symbol and include the extra code, or without to exclude it..

We can make more sophisticated use of a numeric debug macro, like this:


In this case, we must always define the DEBUG macro.

Alternatively, including the following lines eliminates the need to specify DEBUG on the command line in the case where no debugging is required:

There are several macros defined by the C preprocessor that can help with debug information.

These are macros that expand to give information about the current compilations.

Try It Out - Debug Information

Here's a program, cinfo.c, that prints information about its compilation date and time, if debugging is enabled:

When we compile this program with debug enabled (using -DDEBUG, we see the compilation information:

How It Works

The C preprocessor part of the compiler keeps a track of the current line and file when it's compiling.

The symbols __LINE__, __FILE__, __DATE__, and __TIME__ are made available.

Debugging without Recompiling

A global variable as a debug flag, allow a -d option at the command line, allows the user to switch debugging on and off without recompiling.

Now you can intersperse the program code with things like this:

Controlled Execution

We can use a debugger to control the program's execution and view its state as execution proceeds.

Debugging with gdb

We'll use the GNU debugger, gdb, to debug this program.

Starting gdb

Let's recompile our example program for debugging and start gdb.

gdb has extense online help and the complete manual is available as a set of files which may be viewed with the info program, or from within emacs.


Running a Program

We can execute the program with the run command.

We pick up the program again at debug4.c, when the first of the memory access problems has been fixed.

The program runs, incorrectly, as before. gdb shows us the reason and location.

Stack Trace

The program has been halted in the sort function, at line 25 of the source file debug3.c.

We can see how we got to this positon by using the backtrace command:

You can see that sort was called from main at line 37 of the same file, debug3.c.

Examining Variables

The information printed by gdb when it stopped the program and in the stack trace shows up the values of function arguments.

The sort function was called with a parameter, a, that has the value 0x8001764. This is the address of the array.

The offending line, 25, is an assignment of one array element to another:

The print command shows us the contents of variables and other expressions. j has the value of 4. This means that the program has attempted to execute the statement: The array that we have passed to sort, array, has only five elements, which will be indexed 0 through 4. So, this statement reads forn the non-existing array[5].

We can look at the elements of the passed array by using an expression with print.

Listing the Program

We can view the source code of the program from within gdb by using the list command.

Subsequent uses of list will print out more of the code.

We see on line 22, the loop is set to execute while the variable j is less than n.

In this case, n is 5, so j will have the final value of 4, which is one too far.

A value of 4 causes a comparison of a[4] and a[5] and possibly a swap.

One solution to this particular problem is to correct the loop termination condition to be j < n-1.

Let's make that change, call the new program debug4.c, recompile and try again.


The program executes, but doesn't do the sort correctly!

Setting Breakpoints

To find out where the program is failing, we need to be able to see what it's doing as it runs.

We can stop the program at any point by setting breakpoints.

There are a number of commands used for setting breakpoints. These are list by gdb with help breakpoint:

Let's set a breakpoint at line 20 and run the program:

We can print out the array value and then allow the program to continue with the cont command.

We can have many breakpoints active at any time.

To print all five elements of array we can use:

When we allow the program to continue, we see successive alternations to array as execution proceeds:

We can use the display command to set gdb to automatically display the array whenever the program stops at a breakpoint:

We can use the commands command to simply display the data and continue to execute the program.

Now, when we allow the program to continue it runs to completion, printing the value of the array each time around the outer loop:


gdb reports that the program exits with an unusual exit code. Our program should have provided one with a statement like:

The outer loop doesn't execute as many times as expected. The culpit is the decrement of n on line 30:

The line was a bad attempt tooptimize the program. One fix is to just remove the statement.

Patching with the Debugger

We can 'patch the program using the debugger. It uses a breakpoint with actions, we can try out a fix, called patch before changing the source code.

In this case, we need to break the program on line 30 and increment the variable n.

First we see what breakpoints and displays we have enabled using the info command:

We can either disable these or delete them entirely. If we disable thme, we retain the option to re-enable them at a later time if we need to:


The program runs to completion and prints the correct results.

Learning More about gdb

On systems that support hardware breakpoints, you can use gdb to monitor changes to variables in real time.

gdb is able to attach itself to programs that are already running.

More Debugging Tools

Programs like ctags, cxref and cflow work with source files and provide useful information about function calling and location.

Programs like prof and gprof provide information about which functions have been executed and for how long.

Lint: Removing the Fluff from Your Programs

The lint-like tool, lclint can provide useful code review comments.

Here's a sample output from lclint running on an early version of the example program that we debugged earlier:

It has also detected two real bugs in the following code fragment:

The lclint tool has determined that the variable s is used on line 20, but hasn't been initialized and the opertor & has been used in place of the more usual &&.

Function Call Tools

Three utilities, ctags, cxref and cflow, form part of the X/Open specification and, therefore, must be present on UNIX branded systems with software development capabilities.

ctags

The ctags program creates an index of functions.

For each function, you get a list of the places it's used, like the index of a book.

By default, ctags cretes a file, called tags in the current directory, which contains, for each function declard in any of the input source files, lines of the form:

Alternatively, using the -x option to ctags produces lines of this form on the standard output:

cxref

The cxref program analyses C source code and produces a cross-reference. It shows where each symbol is mentioned in the program.

Typing

on the author's machine produced the following output.

cflow

The cflow program prints a function call tree, a diagram that shows which function calls which others, and which functions are called by them and so on.

Here's some sample output taken from a version of cflow (cflow-2.0) that is available on the Internet and maintained by Marty Leisner:

This sample tells us that main calls (among others) show_all_lists and that show_all_lists in turns calls display_list, which itself calls printf.

The -i option produces an inverted flow graph.


Executing Profiling

You can use execution profiling to track down performanace problems.

prof/gprof

The prof program (and its GNU equivalent, gprof) prints a report from an execution trace file that is produced when a profiled program is run.

A profiled executable is created by specifying the -pg flag to the compiler.

The program is linked with a special version of the C library and is changed to include monitoring code.

The monitor data is written to a file in the current directory, mon.out (gmon.out for gprof).

The prof/gprof program reads this monitoring data and produces a report.

Assertions

Problems occur during the program operation that are related to incorrect assumptions, rather than coding errors.

For example, a function may be written with the understanding that its input parameters will be within a certain range.

If it's passed incorrect data, it might invalidate the whole system!

For these cases, where the internal logic of the system needs to be confirmed, X/Open provides the assert macro which can be used to test that an assumption is correct and halt the program if not.

The assert macro evaluates the expression and, if it's non-zero, writes some diagnostic information to the standard error and calls abort to end the program.

The header file assert.h defines the macro depending on the definition of NDEBUG.

If NDEBUG is defined when the header file is processed, assert does nothing.

Problems with assert

You must be careful that there are no side-effects in the assert expression.

Try It Out - assert

Here's a program,assert.c, that defines a function that must take a positive value.

Including the assert.h header file and a 'square root' function which checks that the parameter is positive, we can then write the main function:

Now, when we run the program, we see an assertion violation when we pass an illegal value:

How It Works

When we try to call the function, my_sqrt, with a negative number , the assertion fails.

If we recompile the program with -DNDEBUG, the assertion is compiled out and we a math error when we call the sqrt functon form my_sqrt.

Memory Debugging

If you write outside of an allocated block, you'll very likely corrupt the data structures used by the malloc library to keep track of allocations.

There are some tools available for detecting such problems.

ElectricFence

ElectricFence aims to halt the program at the point of memory corruption.

Test It Out - ElectricFence

Here's a program, efence.c, that allocates a memory block with malloc and then writes beyond the end of the block.

When we compile and run the program, we see no untoward behavior. But, we have likely corrupted part of memeory.

Now, we take the same program and link the ElectricFence library, libefence.a, we get an immediate response.

Running under the debugger pinpoints the problem:

How it works

ElectricFence replaces malloc and assoicated functions with versions that protect against illegal memory access.

Purify

purify is a commerical product that uses a proprietary object code insertion technique to modify programs and detect memory access problems.

Checker

Checker is a modified compiler back end and C library for Linux that is capable of detecting many of the problems the purify address, such as memory leaks.

Try It Out - Checker

Here's a program, checker.c, that allocates some memory, reads from an unitialized portion of that memory, writes beyond the end of it and then makes it inaccessible.

To use Checker, we simply have to replace our compile command with checkergcc, a driver program that takes care of invoking the correect compiler version and linking with special, Checkered, libraries.

When we run the program, we see lots of problems diagnosed:


To check for memory leaks when the program ends, we need to specify -D=end:

How It Works

We have modified our program by using the checkergcc compiler to include additional code to check each pointer reference as it happens.

Resources

Many of the utilities were taken from the Linux archive ftp://sunsite.unc.edu:/Linux.

Summary

This chapter looked at some debugging tools and techniques.


CS 248 - UNIX Programming Web Site Menu
Information | Syllabus | Schedule | Online "Lectures" | Projects | Quizzes | Web Board



Copyright © 2001 by James L. Fuller, all rights reserved.