[Table of Contents] [Next] [Previous]

Chapter 3

Error Detection


This chapter describes how to use Memory Advisor with your debugger to find and correct memory access errors in your programs. To familiarize you with Memory Advisor's features, you will use a sample program to find and correct memory access errors in your programs.

Dynamic Memory Access Error Detection
Memory Access Error Reporting
The Demonstration Program
Compiling and Running the Demonstration Program
Building the Processed Demonstration Program
Running the Processed Demonstration Program
Running the Demonstration Program Under a Debugger
Finding the First Error
Finding the Second Error
Finding the Third Error
Finding the Fourth Error
The Memory Leak and Open File Descriptor Reports

Dynamic Memory Access Error Detection


Memory Advisor monitors your program's use of dynamically allocated memory, recording the calling stack context whenever your program allocates or frees memory. It reports the following error conditions in both LITE and OMT modes:

In addition, it reports the following error conditions in OMT mode:


Memory Access Error Reporting


When Memory Advisor detects a memory access error, it immediately produces a detailed message about the error to its error output file, stderr, by default. The message includes a full stack trace of the location where the error occurred, as well as a full stack trace of the location where your program allocated and freed the data. This lets you identify the source of any memory errors. Memory Advisor reports:

The stack trace includes the function name, file name, and line number for each level of the stack if you compiled your program with the -g option (which enables symbolic debugging). If you did not compile your program with the -g option, Memory Advisor provides the function name and, where possible, the file name. To make the best use of Memory Advisor's error reports, you should compile your programs with the -g option.


The Demonstration Program


Your distribution package includes the source code for a demonstration program called search. This program demonstrates Memory Advisor's memory access error detection capabilities. It reads a text file as input, then reads your keyboard for text-string patterns, and searches for each pattern in the text read from the file. It then prints the location in the file at which it found the pattern, or it prints a message indicating that it did not find the pattern.

The files for the search program are in the $MA_HOME/examples/tutorial directory. See the README file in that directory for information on each file.

When you use the search program to experiment with Memory Advisor, you will:

All tutorial examples for Memory Advisor assume that you are using the default OMT mode for building the programs.

Note: When built without Memory Advisor, the search program may produce unexpected results. On most systems, the program will behave as if it has no errors. On some systems, however, the program may produce invalid results or even terminate with a fatal error. This is because memory access errors cause differing behavior with different allocation patterns or with different memory allocators. This "random" behavior is exactly why Memory Advisor is so important to your development process. It concretely demonstrates where errors exist in your program, even when your program appears to be operating without errors. Even when built with Memory Advisor, the search program may not display exactly the same behavior as shown here until you correct the errors. However, Memory Advisor will report the source of each error, so you can correct them in sequence.

Compiling and Running the Demonstration Program

The tutorial directory contains a Makefile for building the search program both with and without Memory Advisor. To build the program without Memory Advisor, enter the following command:

make search
This compiles the .c files and creates the search program. Next, run the program using Preamble as the input file.

./search Preamble
Enter a few search patterns on the keyboard. Here is an example session:

We
`We' found at pos 3
the
'the' found at pos 7
fubar
'fubar' not found
America
'America' found at pos 352
quit
'quit' not found
When you are done, press ^D (Control+D, the end-of-file character). Notice that the program appears to operate without errors. However, building the program with Memory Advisor will reveal several serious errors.

Building the Processed Demonstration Program

You can use Memory Advisor to look for errors in the search program. Remember that you need to set your $MA_HOME environment variable to the Memory Advisor installation directory (and export it for the sh and ksh shells) prior to invoking Memory Advisor.

Type the following command to specify the path where Memory Advisor is installed:

setenv MA_HOME <path>
where <path> is the directory where you installed Memory Advisor.
The Makefile provided with the tutorial contains a rule for building a processed version of the program. To create the program, type:

make search.madv
This creates the search.madv program, the processed version of search.

To create the search.madv rule in the Makefile, we added the word "memadvise" before the command used to link the search program, and changed the name of the output file.

memadvise cc -g -o search.madv search.o
To process your own programs, follow this model and insert the word "memadvise" at the beginning of your link command. You may also want to use a file extension (for example, .madv) to distinguish the processed and nonprocessed versions of your program.

Running the Processed Demonstration Program

You are now ready to run the processed version of the search program. Type the following to start the program:

./search.madv Preamble
As the program runs, you will see several error messages. The first message identifies an attempt to read or write outside the allocated boundaries of an array:

MemAdvise:	Warning [AccOutOfBounds,14]: An attempt was made to access
		data beyond the end of an allocated data section. The 
		program attempted to write 121 bytes to location 0x80F38. 
		That address is at offset 0 in the 120 byte data area that 
		starts at location 0x80F38 (there is only room to write 
		120 bytes).
This error message is a warning, which reports an error but does not stop program execution. The error's mnemonic class and numeric code are inside the square brackets. In this case, the mnemonic class is AccOutOfBounds (access out of bounds), which applies to several different types of errors. The number 14 identifies the specific error code. A description of the error follows the error code. Chapter 11, Error Messages, contains the full list of error messages.

Memory Advisor next provides information about the error's location. This is in the form of a stack trace. The report also provides information on the location of the data allocation. In each case, Memory Advisor includes the function name, file name, and line number (if you compiled with the -g option) for each level of the stack.

	This problem was detected at the following location:

		strcpy()        [string.o]
		main()          [search.c:160]

	This problem is *probably* associated with a 120 byte data 
	area allocated on the 5th call to malloc() which returned 
	0x80F38. The context of the call to malloc() was as 
	follows:

		malloc()        [intercept.o]
		main()          [search.c:159]

Memory Advisor will display several error messages as the program proceeds. This is because it lets the program proceed after reporting the warning. This is consistent with the way the C language compiler operates. It does not stop at the first error encountered. It reports the error and continues running, even though you might eliminate later errors by correcting the error the first time it occurs.

After you finish providing input to the search program, Memory Advisor performs some additional error detection during program termination. By default, it checks for leaks and reports on file descriptors that the program opened but did not close. See Chapter 4, Memory Leaks, for a detailed discussion.

Memory Advisor produces the following leak and open file descriptor reports:

MemAdvise:	Info [Info,91]: Searching for leaks (this may take a while)...

****************** MemAdvise: List of Memory Leaks ******************

Data                                        Which  Number Total Data
Address              Location                Call   Leaks    Leaked
-------- --------------------------------- ------  ------ ----------

0x080F38 main(           [search.c:159]       5th       1        120
0x080FD8 main()          [search.c:164]       6th       2        600
0x081418 initnext()      [search.c:40]        9th       5         21
         search()        [search.c:91]
         main()          [search.c:182]

************* MemAdvise: List of Open File Descriptors **************

FD:   5 "Preamble"
        Opened by (the 1st call to) open()
        Called from: main()          [search.c:148]

Running the Demonstration Program Under a Debugger

One of the best ways to find a memory access error with Memory Advisor is to run the processed program under a debugger, instructing it to stop each time Memory Advisor issues a message. This lets you examine the program code at the location where Memory Advisor detected the error.

In this section, you execute the search program with the dbx debugger. You can also use other debuggers, such as sdb, gdb, or xdb. The commands in these debuggers vary from the dbx examples shown here. Consult your debugger's documentation.

Using the following instructions, you can set a breakpoint in a special function called MaStop(). Memory Advisor calls this function each time it displays an error message.

Invoke the dbx debugger:

dbx search.madv
Set a breakpoint in MaStop():

stop in MaStop
The following displays:

(2) stop in MaStop
Now you can run the program, using the same input file that you used before:

run Preamble

Finding the First Error

When you run the program using the same input file as before, Memory Advisor produces the following:

MemAdvise:	Warning [AccOutOfBounds,14]: An attempt was made to access 
	data beyond the end of an allocated data section. The 
	program attempted to write 121 bytes to location 0x80F38. 
	That address is at offset 0 in the 120 byte data area that 
	starts at location 0x80F38 (there is only room to write 
	120 bytes).

	This problem was detected at the following location:

		strcpy()        [string.o]
		main()          [search.c:160]

	This problem is *probably* associated with a 120 byte data 
	area allocated on the 5th call to malloc() which returned 
	0x80F38. The context of the call to malloc() was as 
	follows:

		malloc()        [intercept.o]
		main()          [search.c:159]

stopped in MaStop at 0x18700
MaStop:    sethi   %hi(0x68c00), %o0
Current function is main
  160                           strcpy(buf, line);

This error message tells you that the program tried to use strcpy() to write more data than the program has allocated to the destination array. You can see from the stack trace that main() called strcpy() at line 160 of the search.c file.

The program stops running at MaStop(). This lets you examine the code at the location of the error. Here is a partial listing of search.c, where the program is reading in the input file:

154	while( (len = read(fd, line, BUFLEN)) > 0 )
155	{
156		line[len] = '\0';
157		if( buf == (char *)NULL )
158		{
159			buf = (char *)malloc(strlen(line));
160			strcpy(buf, line);
161		}
162		else
163		{
164			p = (char *)malloc(strlen(buf)+strlen(line));
165			strcpy(p, buf);
166			strcat(p, line);
167			buf = p;
168		}
169	}

You can see that the malloc() call on line 159 is too short. The program did not allocate space for the terminating null byte at the end of the string (a common error in C). The same error occurs on line 164. If you allow Memory Advisor to continue running, it will eventually produce the messages for this error as well.

Finding the Second Error

Continuing in the debugger (using the cont command) to the next error message shows how a single allocation error can cascade. The second error is at a different location, but the same erroneous allocation caused it. Because of differences in error detection mechanisms, Memory Advisor may provide different output for the next error detected, depending on whether you are using LITE mode or OMT mode.

OMT mode output. Memory Advisor displays the following:

MemAdvise:	Warning [AccOutOfBounds,14]: An attempt was made to access 
	data beyond the end of an allocated data section. The 
	program attempted to read 121 bytes from location 0x80F38. 
	That address is at offset 0 in the 120 byte data area that 
	starts at location 0x80F38 (there is only room to read 120 
	bytes).

	This problem was detected at the following location:

		strlen()        [string.o]
		main()          [search.c:164]

	This problem is *probably* associated with a 120 byte data 
	area allocated on the 5th call to malloc() which returned 
	0x80F38. The context of the call to malloc() was as follows:

		malloc()        [intercept.o]
		main()          [search.c:159]

stopped in MaStop at 0x18700
MaStop:    sethi   %hi(0x68c00), %o0
Current function is main
  164                         p = (char *)malloc(strlen(buf)+strlen(line));

LITE mode output. Memory Advisor displays the following:

MemAdvise:	Warning [AccOutOfBounds,14]: An attempt was made to access 
	data beyond the end of an allocated data section. The 
	program attempted to write 241 bytes to location 0x97248. 
	That address is at offset 0 in the 240 byte data area that 
	starts at location 0x97248 (there is only room to write 
	240 bytes).

	This problem was detected at the following location:

		strcat()        [string.o]
		main()          [search.c:166]

	This problem is *probably* associated with a 240 byte data 
	area allocated on the 6th call to malloc() which returned 
	0x97248. The context of the call to malloc() was as 
	follows:

		malloc()        [intercept.o]
		main()          [search.c:164]

stopped in MaStop at 0x47070
MaStop:    sethi   %hi(0x84800), %o0
Current function is main
  166                           strcat(p, line);

To correct this, simply add 1 (one) to each allocation (line 159 and line 164).

Use an editor to correct these errors in search.c:

vi search.c
After saving your changes, rebuild search.madv:

make search.madv
Now return to the debugger:

dbx search.madv

Finding the Third Error

When you run the program this time, Memory Advisor does not detect any errors when reading in the Preamble file. The program waits for you to enter the first search string. Enter "We" as the search string. Memory Advisor then detects the following errors:

MemAdvise:	Warning [AccOutOfBounds,14]: An attempt was made to access 
	data beyond the end of an allocated data section. The 
	program attempted to write 8 bytes to location 0x81430. 
	That address is at offset 0 in the 2 byte data area that 
	starts at location 0x81430 (there is only room to write 2 
	bytes).

	This problem was detected at the following location:

		memset()        [memext.o]
		initnext()      [search.c:41]
		search()        [search.c:91]
		main()          [search.c:182]

	This problem is *probably* associated with a 2 byte data 
	area allocated on the 9th call to malloc() which returned 
	0x81430. The context of the call to malloc() was as follows:

		malloc()        [intercept.o]
		initnext()      [search.c:40]
		search()        [search.c:91]
		main()          [search.c:182]

stopped in MaStop at 0x18708
MaStop:    sethi   %hi(0x68c00), %o0
Current function is initnext
   41           memset(next, '\0', pat_len * sizeof(int));

OMT mode output. Memory Advisor detects errors exactly on the line of code where they occur. If you are using Memory Advisor and allow the debugger to continue using the cont command, the following error message displays:

MemAdvise:	Warning [AccOutOfBounds,14]: An attempt was made to access 
	data beyond the end of an allocated data section. The 
	program attempted to write 4 bytes to location 0x81430. 
	That address is at offset 0 in the 2 byte data area that 
	starts at location 0x81430 (there is only room to write 2 
	bytes).

	This problem was detected at the following location:

		initnext()      [search.c:46]
		search()        [search.c:91]
		main()          [search.c:182]

	This problem is *probably* associated with a 2 byte data 
	area allocated on the 9th call to malloc() which returned 
	0x81430. The context of the call to malloc() was as 
	follows:

		malloc()        [intercept.o]
		initnext()      [search.c:40]
		search()        [search.c:91]
		main()          [search.c:182]

stopped in MaStop at 0x18708
MaStop:    sethi   %hi(0x68c00), %o0
Current function is initnext
   46           next[0] = -1;

The following code fragment shows the source of the problem:

39	pat_len = strlen(pattern);
40	next = (int *)malloc(pat_len * sizeof(char));
41	memset(next, '\0', pat_len * sizeof(int));
42
43	/*
44	 * Initialize locations, coalescing repeated characters
45	 */
46	next[0] = -1;
47	for( i = 0, j = -1; i < pat_len; i++, j++ )
48	{
49		while( (j >= 0) && (pattern[i] != pattern[j]) )
50		{
51			j = next[j];
52		}
53		next[i] = j;
54	}

Examining this code fragment shows that the allocation on line 40 used sizeof(char), but the code is using the array to hold integers. To correct this problem, change char to int.

After correcting this problem and rebuilding the program, rerun it under the debugger. This shows that you fixed the errors in building the next array. At this point, no errors appear until you enter the second search pattern:

stop in MaStop
(2) stop in MaStop
run Preamble
Running: search.madv Preamble
We
'We' found at pos 3
the
...

Finding the Fourth Error

After running the program under the debugger, Memory Advisor reports the following:

MemAdvise:	Warning [AccFreed,16]: An attempt was made to access data 
	that has been freed. The program attempted to read 1 byte 
	from location 0x81298. That address is located within the 
	freed 362 byte data area that starts at location 0x81298.

	This problem was detected at the following location:

		strlen()        [string.o]
		search()        [search.c:85]
		main()          [search.c:182]

	This problem is *probably* associated with a 362 byte data 
	area allocated on the 8th call to malloc() which returned 
	0x81298. The context of the call to malloc() was as 
	follows:

		malloc()        [intercept.o]
		main()          [search.c:164]

	This block was freed on the 4th call to free() in the 
	following context:

		free()          [intercept.o]
		main()          [search.c:198]

stopped in MaStop at 0x18700
MaStop:    sethi   %hi(0x68c00), %o0
Current function is search
   85           buf_len = strlen(buffer);

This error shows that the program is reusing memory that it has already freed. This is a dangerous error because, once a program frees memory, it may reallocate it. This error causes the program to read from or write to memory that is in use for another purpose. In this instance, examining the code at the source of the error does not reveal an obvious problem:

71	search(pattern, buffer)
72	char * pattern;
73	char * buffer;
74	{
75		int	buf_len;
76		int	i;
77		int	j;
78		int	* next;
79		int	pat_len;
80		int	rtn = -1;
81
82		/*
83		 * Get buffer and pattern sizes
84		 */
85		buf_len = strlen(buffer);
86		pat_len = strlen(pattern);

The problem is with the buffer that the program passed in, not with the code that is doing the invalid access. Therefore, you should examine the locations at which the program freed memory, as reported in the error message above.

171	while( (len = read(0, line, BUFLEN)) > 0 )
172	{
173		line[len-1] = '\0';
174		p = line;
175		while( p != (char *)NULL )
176		{
177			q = (char *)strchr(p, '\n');
178			if( q != (char *)NULL )
179			{
180				*q = '\0';
181			}
182			if( (ret = search(p, buf)) != -1 )
183			{
184				sprintf(out, "'%s' found at pos %d\n",
185					p, ret);
186				write(1, out, strlen(out));
187			}
188			else
189			{
190				sprintf(out, "'%s' not found\n", p);
191				write(1, out, strlen(out));
192			}
193			p = q;
194			if( p != (char *)NULL )
195			{
196				p++;
197			}
198			free(buf);
199		}
200	}

This shows that the program freed the buffer containing the text to be searched inside the loop. This is incorrect. You need to move free() outside the loop (after line 200).

The Memory Leak and Open File Descriptor Reports

After correcting this problem and rebuilding the program, run it under the debugger again and enter some search patterns. This shows that you have fixed all memory access errors.

run Preamble<
Running: search.madv Preamble 
We
'We' found at pos 3
the
'the' found at pos 7
fubar
'fubar' not found
America
'America' found at pos 352
quit
'quit' not found
When you are done, press ^D. This time, Memory Advisor does not report any messages until the program is ready to exit. It then presents a leak report and a report of open file descriptors, as shown below:

MemAdvise: Info [Info,91]: Searching for leaks (this may take a while).....

Reading symbol table...Sun format...............................Done

****************** MemAdvise: List of Memory Leaks ******************

Data                                        Which  Number Total Data
Address              Location                Call   Leaks    Leaked
-------- --------------------------------- ------  ------ ----------

20001B18 malloc()                             1st       1        120
         main()          [search.c:159]

20001BB8 malloc()                             2nd       2        600
         main()          [search.c:164]

20002038 malloc()                             6th       3          8
         initnext()      [search.c:40]
         search()        [search.c:91]
         main()          [search.c:182]

************* MemAdvise: List of Open File Descriptors **************

FD:   5 "Preamble"
        Opened by (the 1st call to) open()
        Called from: main()          [search.c:148]

Chapter 4, Memory Leaks, discusses the leak report in detail.

Memory Advisor reports the list of open file descriptors because file descriptors are a limited resource. A program that fails to close file descriptors can run out of them. The search program only opens one file; therefore, this is not a significant problem. To correct the problem, close the file descriptor after the program reads the file; for example, after line 169.


Copyright ©1996 PLATINUM technology, inc. All rights reserved.
Last modified 1/29/96
For more information contact info@platinum.com