Memory errors are one of the most difficult classes of software errors to analyze and fix, because the source of memory corruption and the manifestation of the error are far apart, making it hard to correlate the cause and the effect. Moreover, the errors often occur in exceptional conditions, making it hard to reproduce them consistently. Typically, these errors result from complex interactions between different components of your program, third-party libraries, and the operating system. It is extremely hard to predict and comprehend these possibilities and scenarios just by inspecting source code. Often, it requires a considerable amount of debugging and investigation to understand mistakes in your program logic and design that are causing memory errors.
IBM® Rational® Purify® is an advanced memory-debugging tool to assist you in quickly locating the cause of memory corruption errors accurately. To use the tool, you first use Purity to instrument your program. Then, when you run the instrumented program, Purify scrutinizes memory accesses and manipulations by your program and identifies memory errors that are about to happen. This considerably reduces the debugging time and complexity.
You can learn about various types of memory errors and how to use Purify to detect them by reading this IBM developerWorks article: Navigating "C" in a "leaky" boat? Try Purify. If you are familiar with Purify and memory errors, you can either skim or skip that article.
Purify has several unique and advanced features to assist you in debugging memory errors. In this article, you will first learn about application programming interfaces (APIs) in Purify and how to use them from the debugger. Then you will learn about APIs specific to memory watch points.
Application programming interface
Purify provides various APIs that you can invoke from your program or debugger
to further assist you in debugging memory problems. For example, you can use the
purify_what_colors function to find out the status of a
memory range:
int purify_what_colors (char *addr, unsigned int size); |
Purify keeps track of the status of every byte of memory used by your program and uses four colors to represent the status: red, yellow, green, and blue. Initially, all memory is red, which represents unallocated and uninitialized memory. After you allocate memory, it becomes yellow, which represents allocated but uninitialized memory. After you initialize a memory location, it becomes green, representing allocated and initialized memory. And when you free memory, it becomes blue, representing previously allocated but then freed memory. This lifecycle is shown in Figure 1.
- It is legal to read from or write to memory marked green.
- It is legal to write to yellow memory, but illegal to read from it. Purify reports a Uninitialized Memory Read (UMR) error when you do.
- It is illegal to read or write blue and red memory.
While debugging a memory corruption, you can call the
purify_what_colors API from the debugger to see the
Purify color state for an interesting memory location. This example shows the
color state for the bytes of an eight-byte buffer, where only the first two have been
initialized:
(gdb) print purify_what_colors(buf, sizeof(buf)+1) color codes of 9 bytes at 0xffffe820: GGYYYYYYR |
The API prints out the memory state of
sizeof(buf)+1 bytes, starting at memory
address buf. The memory state of each byte of memory is
represented by one of these letters R, Y, G, or B.
These letters correspond to the colors red, yellow, green, and blue, respectively.
Figure 1. Lifecycle of memory locations
Using Rational Purify with the debugger
Most of the time, Purify provides enough information about a memory error for you to identify the cause and fix it. But sometimes, you may need to use that information as a starting point and debug the program to find the cause. For example, let's say that Purify reports a UMR error that surprises you because you do see a statement in your program that initializes that memory. Clearly, there exists a control path in which either the initialization statement is not executed or, in the case of a pointer to a memory buffer, the pointer is being reassigned to another memory buffer that may not have been initialized yet.
In such situations, you need to debug your program to find the exact cause. The good news is that instead of debugging your program, you can debug the Purify-instrumented program. Purify issues memory error reports just before the error is about to happen. That allows you to examine and analyze relevant variables and memory contents in a debugger. Purify also provides APIs for examining the status of memory locations.
There are two ways that you can engage a debugger for a Purify-instrumented program:
- First, start the instrumented program under the debugger and put a breakpoint
at
purify_stop_herePurify API function. The debugger will stop at every Purify error message:(gdb) break purify_stop_here (dbx) stop in purify_stop_here (xdb) b purify_stop_here
- Alternatively you can configure just-in-time (JIT) debugging through the Options > JIT Debug menu in the Purify GUI (see Figure 2), and select the error types of interest. Whenever a Purify error of the selected type is reported, Purify invokes the debugger and attaches it to your running application.
Figure 2. Purify JIT debugger dialog
When you are inside a debugger, you can use various Purify API functions to investigate the status and type of various memory locations:
-
purify_what_colors(char *addr, unsigned int size):
Prints the memory state ofsizebytes starting at memory address (addr), as explained previously in the Application programming interface section.
-
purify_describe(void *addr):
Prints specific details about the memory at locationaddr, including its location (stack, heap, text) and, if it is heap memory, then the call chains of its allocation and free history.
-
purify_assert_is_readable(const char *addr, int size):
Simulates readingsizebytes starting at addressaddr, generates any Purify errors that read would cause, and callspurify_stop_hereupon error. Returns 0 if errors are detected, and returns 1 if no errors are detected.
-
purify_assert_is_writable(const char *addr, int size):
Simulates writingsizebytes starting at addressaddr, generates any Purify errors that write would cause, and callspurify_stop_hereupon error. Returns 0 if errors are detected, and returns 1 if no errors are detected.
While debugging, you may want to focus on some piece of code, thus not be interested in Purify errors that are reported before the program control reaches that piece of code. Purify provides APIs to turn error reporting off or on (notice that memory use monitoring is not turned off):
- Using a debugger, put a breakpoint at the
mainfunction (or at the program location where you want to turn off Purify error reporting), and run the instrumented program. - When the debugger stops at that breakpoint, type this
command:
(gdb) print purify_stop_checking()
- Put a breakpoint at the program location where you want to resume Purify error reporting, and continue running the program.
- When the debugger stops at that breakpoint, type this
command:
(gdb) print purify_start_checking()
Purify offers a wide set of memory watch point APIs that you can call from the debugger to assist you in debugging the memory corruption problems in your program. The code shown in Listing 1 shows both a memory leak and a dangling pointer.
Listing 1. Code with memory leak and dangling pointer (mem_errors.c)
1 #include <stdio.h>
2
3 char *namestr;
4
5 void foo() {
6 namestr = (char *) strdup("Rational PurifyPlus");
7 printf("Product = %s\n", namestr);
8 free(namestr); /* free the memory allocated by strdup */
9 }
10
11 void main() {
12 namestr = (char *)malloc(20 * sizeof(char));
13 foo();
14 strcpy(namestr, "IBM");
15 printf("Company = %s\n", namestr);
16 free(namestr);
17 }
|
Interestingly, if you look at the method main() or
foo() independently, both functions look correct. The
method main() allocates memory, calls
foo(), uses the allocated memory, and then frees it and
exits. The method foo() calls method
strdup(), which allocates memory, uses that memory, and
then frees it. However, it is the interaction of these two functions and
using a global pointer variable called namestr that causes both the leak
and the dangling pointer. When strdup() is called in
foo(), the namestr variable value is
overwritten, thereby losing the pointer to the memory allocated in
main(), and that causes the leak. In
main, after returning from
foo(), namestr is actually a dangling pointer,
because foo() has freed that memory before returning.
It is easy to spot the problems in this simple example by inspecting the code. But that is not possible in large programs with complex control flows where the problematic functions could be in different libraries. That is when Purify and its memory watch point API becomes handy.
Here is how you can purify this program:
$ purify cc -g mem_errors.c -o mem_errors.pure |
When you run the purified program, Purify will report the following errors (also see Figure 3):
-
MLK (Memory Leak)for the memory allocated for namestr in the functionmain -
FMW (Free Memory Write)at thestrcpy()call in the functionmain -
FMR (Free Memory Read)at theprintf()call in the functionmain -
FUM (Freeing Unallocated Memory)at thefree()call in the functionmain
Figure 3. Memory errors reported by Purify in mem_errors.c
For this small example, the programming mistake can be easily fixed by using
information provided along with Purify errors. But for a complex program, where a
function such as foo() might be called from various
locations and possibly in a loop, debugging the program by using the Purify memory
watch point APIs will be very useful.
The watch point feature lets you ask Purify to pay special attention to an area of memory and issue a report any time that memory gets read (WPR: Watch Point Read), written (WPW: Watch Point Write), or freed (WPF: Watch Point Free). This way, you can answer questions such as, "Where in the program does this variable get written?" or "Where does this variable get used?" And for memory in the heap, "Which function frees this memory?"
Here is how you can purify your program and run it under a debugger
(gdb is used here to illustrate the process, but you
can use any of your favorite debuggers):
$ purify cc -g mem_errors.c -o mem_errors.pure $ gdb mem_errors.pure |
Looking at Purify reporting memory leaks and FMR, FMW, or FUM, there are several interesting questions that you may want to ask:
- Given that the namestr variable is a pointer to memory allocated in
main(), why does namestrstart pointing to some other memory?
- Where exactly does namestr get written with another address value, and
thereby lose the last pointer to memory allocated in
main(), causing the leak?
- After the memory allocation in
main(), where is the namestr variable used and overwritten?
You can answer these questions by putting a breakpoint just after the
malloc call in the
main()function (Line 13), and then using Purify to set
a memory watch point on &namestr to track all
write operations happening on the namestr variable. Whenever an address is
written to the namestr variable, the Purify watch point will show a WPW
(Watch Point Write) message in the Purify viewer:
$ gdb mem_errors.pure (gdb) break 13 Breakpoint 1 at 0x10000aec: file mem_errors.c, line 13. (gdb) run Starting program: mem_errors.pure Breakpoint 1, main () at mem_errors.c:13 13 foo(); (gdb) print purify_watch_n(&namestr, 4, "w") $1 = 1 (gdb) continue |
The purify_watch_n() function takes the address of the
memory location to be watched (&namestr), the
size (4 bytes, the size of a pointer) and the watch mode (r for read,
w for write, rw for read-write). Whenever a new address is stored in
namestr, Purify will show a WPW (Watch Point
Write) result in the viewer. On expanding, it looks like this message:
WPW: Watch point write:
* This is occurring while in:
foo [mem_errors.c:6]
main [mem_errors.c:13]
__start [mem_errors.pure]
* Watchpoint 1
* Writing 4 bytes to 0x20103b38 in the initialized data section.
* Value changing from 537934728 (0x20103b88, " \020;\210")
to 537934968 (0x20103c78, " \020")
* Address 0x20103b38 is global variable "namestr".
This is defined in mem_errors.pure.
|
This message indicates that in the foo() method at
Line 6, another address is stored in namestr. Even before freeing the
memory, the value of namestr has been changed, and that is why Purify
reports an MLK (memory leak) error. Let's now debug the cause of the FMR
(Free Memory Read) and FMW (Free Memory Write) errors. When reporting FMR or FMW,
Purify also specifies where the memory allocation happened. For this example,
Purify indicates that the FMW and FMR errors at Lines 14 and 15, respectively, in
method main() happen because of accesses to
already-freed memory that was allocated by the strdup()
call at Line 6 in method foo(). Therefore, you need to
track all reads and writes on the memory block (and not the pointer) allocated at
Line 6. You can do that by putting a read-write watch point after the
strdup()call:
(gdb) break 7
Breakpoint 2 at 0x10000c38: file mem_errors.c, line 7.
(gdb) continue
Continuing.
Breakpoint 2, foo () at mem_errors.c:7
7 printf("Product = %s\n", namestr);
(gdb) print purify_watch_n(namestr, 20, "rw")
$2 = 2
|
Because this is a read-write watch point, any attempt to read or modify the
contents of the memory block that namestr points to would trigger a WPR or
WPW message, respectively.
Notice the difference between using
namestr here and
&namestr earlier. The earlier example was
watching the memory that held the namestr pointer
itself; therefore, the address of the watched area is given by
&namestr. This second example is watching the
memory that namestr points to, instead.
A WPR shows at Line 7 at the printf() call, and then a
WPF (Watch Point Free) shows on Line 8 at the free()
call. Both of these are expected, but now, after reporting WPF, you should follow
the control path more carefully.
In fact, you can set a breakpoint at purify_stop_here
(as explained earlier in the "Using Purify with the debugger"
section) for Purify to stop at every error (or message). Any access to this memory
thereafter should be an error, because the memory pointed to by namestr has
been freed. Therefore, stepping through the code at Line 14 generates a WPW
message, because the memory that has already been freed is being written to that
message by a call to strcpy(). This WPW explains the
FMW (Free Memory Write) that Purify reported:
(gdb) next
main () at mem_errors.c:14
14 strcpy(namestr, "IBM");
(gdb) next
15 printf("Company = %s\n", namestr); |
Similarly, stepping through Line 15 generates a WPR message and, thus, a FMR
error. The FUM error reported toward the end of the program at Line 16 is also
obvious now, because the memory for namestr(the new value, allocated by
strdup) was already freed at Line 8 in function
foo (where a WPF message was reported).
Figure 4 shows a sample Purify window with all of these watch point errors. To summarize: Memory watch points help you in tracing the use of a given memory block. Using them along with the debugger helps you track memory use with the program execution which manifests a memory error.
Figure 4. Watch point messages reported by Purify
Purify watch points can report the following messages for the given memory address:
- Reads
- Writes
- Allocation
- De-allocation
- Coming into scope at function entry
- Going out of scope at function exit
There are several watch point API functions for convenience (Table 1). The
simplest is purify_watch(addr), which sets a read-write
watch point on four bytes starting at the given address. The APIs that set watch
points return an integer value, which is the watch point number that was just set.
You can pass that integer to watchpoint_remove to
remove it. All of the API convenience functions are equivalent to using
purify_watch_n with the appropriate address, size, and
type.
Table 1. APIs to set watch points
| Watch point API | Description |
|---|---|
| purify_watch_n(addr, size, type) | Set a watch point of type type on size bytes starting at addr. Set Type to read ("r"), write ("w"), or read and write("rw"). |
| purify_watch(addr)
purify_watch_1(addr) purify_watch_2(addr) purify_watch_4(addr) purify_watch_8(addr) | Watch four bytes (or the indicated number) starting at addr, type ("rw"). |
| purify_watch_r(addr)
purify_watch_r_1(addr) purify_watch_r_2(addr) purify_watch_r_4(addr) purify_watch_r_8(addr) | Watch four bytes (or the indicated number) starting at addr, type ("r"). |
| purify_watch_w(addr)
purify_watch_w_1(addr) purify_watch_w_2(addr) purify_watch_w_4(addr) purify_watch_w_8(addr) | Watch four bytes (or the indicated number) starting at addr, type ("w"). |
| purify_watch_rw(addr)
purify_watch_rw_1(addr) purify_watch_rw_2(addr) purify_watch_rw_4(addr) purify_watch_rw_8(addr) | Watch four bytes (or the indicated number) starting at addr, type ("rw"). |
To get information about watch points and remove them, you can use the following APIs:
-
purify_watch_info(), which shows all active Purify memory watch points -
purify_watch_remove(int watchpoint_no), which removes the watch point with the given number -
purify_watch_remove_all(), which removes all watch points
Using Purify APIs in your programs
Apart from using Purify APIs in the debugger, you can also embed them from programs for checking errors and reporting extra information. In that case, even if you run your purified program through an automated test suite; when an error occurs, it will dump extra messages into the Purify log that will help you identify the problem.
There are two ways of embedding Purifying APIs in your program:
- Using
#ifdefguards - Linking with Purify stubs
As shown in the example in Listing 2, you can guard Purify API calls in your
program by surrounding them with #ifdef definition
guards. In this way, you don't need to change the source code to build
the purified executable program that exploits Purify APIs nor to build the
executable program that you ship as a product.
The example has an implementation of strncpy, where
the source and destination strings are checked first, respectively, to be readable
and writable. If any of the tests fail, an appropriate message is
printed in the Purify console or log by calling
purify_printf. Then
purify_describe is called, which prints specific
details about the memory address, including its location (stack, heap, text)
and, for heap memory, the call chains at its allocation time and its free() call history.
Finally, purify_what_colors is called to print the
color of the memory buffer. The copy is performed only if no error is found.
Listing 2: Part of file
mystring.c that uses Purify API with guards
#ifdef PURIFY
#include <purify.h>
/*
* The purify.h file has needed API declaration.
*/
#endif
void mystrncpy(char* dest, const char* src, int length) {
#ifdef PURIFY
if (!purify_assert_is_readable(src, length)) {
purify_printf("strncat: caller gave bad source");
purify_describe(src);
purify_what_colors(src, length);
} else if (!purify_assert_is_writable(dest, length)) {
purify_printf("strncat: caller gave bad destination");
purify_describe(dest);
purify_what_colors(dest, length);
} else {
#endif
/*
* skip: copy n bytes from src to dest only if safe
*/
#ifdef PURIFY
}
#endif
}
int main() {
/* skip: main body that calls mystrncpy */
}
|
The makefile shown in Listing 3 demonstrates how you
can turn on Purify API calls by using the -DPURIFY flag
for building a purified version of the executable file (see the rule for
mystring.pure) and linking it with the Purify API
library.
Listing 3. Part of makefile that builds
mystring and mystring.pure
#
# makefile to build mystring programs, and its purified versions
#
# ... skip ...
# Purify header and API lib locations
PURIFYINCLUDE = -I`purify -print-home-dir`
# For 64-bit program, replace lib32 by lib64 in following:
PURIFYAPILIB = `purify -print-home-dir`/lib32/libpurify_stubs.a
# ... skip ...
mystring : mystring.c
$(CC) $(FLAGS) -o $@ $?
mystring.pure : mystring.c
purify $(CC) $(FLAGS) -g -DPURIFY $(PURIFYINCLUDE) -o $@ $? \
$(PURIFYAPILIB)
# ... skip ...
|
The drawback of using a guard is that you have to recompile the whole program. In this example, only one C file is used, but in large systems, typically various libraries are built and finally linked to build the executable file. In such situations, if you want to purify your program, you must recompile all C files that use the guard.
An alternative is to always link your application with the libpurify_stubs.a by changing the rule in Listing 3 to
build mystring:
mystring : mystring.c
$(CC) $(FLAGS) -o $@ $? $(PURIFYAPILIB)
|
The libpurify_stubs.a is a small library that has empty stubs for all Purify API
functions. When you instrument your program, Purify provides the real definitions
for the API functions, and stubs are ignored.
You can surround multiple Purify APIs with
if(purify_is_running()) to keep Purify function calls
from slowing down your uninstrumented program (see Listing 4).
Listing 4. Part of file
mystring.c that uses Purify API without guards
#include <purify.h>
/*
* The purify.h file has needed API declaration.
*/
void mystrncpy(char* dest, const char* src, int length) {
if (purify_is_running()) {
if (!purify_assert_is_readable(src, length)) {
purify_printf("strncat: caller gave bad source");
purify_describe(src);
purify_what_colors(src, length);
} else if (!purify_assert_is_writable(dest, length)) {
purify_printf("strncat: caller gave bad destination");
purify_describe(dest);
purify_what_colors(dest, length);
}
}
/*
* skip: copy n bytes from src to dest only if safe
*/
}
int main() {
/* skip: main body that calls mystrncpy */
}
|
The tradeoffs between using #ifdef and Purify stubs
are that the former requires you to recompile your program with the -DPURIFY
flag, while the latter involves a runtime cost of calling
purify_is_running (which is negligible), and linking
your program with Purify's empty stubs even in production code. Use the alternative that suits
your need.
In this article, you learned about the memory color concept in Purify, the APIs, and the memory watch points. You can use these APIs from the debugger, or you can embed them in your programs, instead. Either way, with the help of Purify APIs and memory watch points, you can debug memory errors in your programs more effectively.
Learn
- Read Goran Begic's article to get
An introduction to runtime analysis with Rational PurifyPlus,
(IBM® developerWorks®, November 2003).
- Learn about
different
types of memory errors,
and how to use Rational Purify to detect them.
- Read Anandi Krishnamurthy's article on
using
PurifyPlus with IBM Rational Systems Developer.
- Visit the
Rational software area on developerWorks
for technical resources and best practices for Rational Software Delivery Platform
products.
- Subscribe to the
developerWorks Rational zone newsletter.
Keep up with developerWorks Rational content. Every other week, you'll
receive updates on the latest technical resources and best practices for the
Rational Software Delivery Platform.
- Subscribe to the
Rational Edge newsletter
for articles on the concepts behind effective software development.
- Subscribe to the
IBM developerWorks newsletter,
a weekly update on the best of developerWorks tutorials, articles, downloads,
community activities, webcasts and events.
- Browse the
technology bookstore
for books on these and other technical topics.
Get products and technologies
- Download
a trial version of IBM® Rational® PurifyPlus®
.
- Download
trial versions of other IBM Rational software.
- Download these
IBM product evaluation versions
and get your hands on application development tools and middleware products from
DB2®, Lotus®, Tivoli®, and WebSphere®.
Discuss
- Check out
developerWorks
blogs and
get involved in the
developerWorks community.

Satish Chandra Gupta is a developer in the IBM Rational® PurifyPlus® group in Bangalore, India. His interests include compilers, programming languages, runtime analysis, Java memory leaks, type theory, software engineering, and software development environments. His research has been published in ACM/IEEE conferences. He received a B.Tech. from the Indian Institute of Technology in Kanpur (India), and an M.S. from the University of Wisconsin in Milwaukee (USA).





