Next: Leak table, Previous: Testing, Up: Using mpatrol
Along with the standard set of C and C++ dynamic memory allocation functions,
the mpatrol library also comes with an additional set of functions which can be
used to provide additional information to your program, and which can be called
at various points in your code for debugging purposes. You must always include
the mpatrol.h header file in order to use these functions, but you can
check for a specific version of the mpatrol library by checking the
MPATROL_VERSION
preprocessor macro. You can check the version of the
mpatrol library that a program was linked with by calling the
__mp_libversion()
function.
Certain mpatrol library options can be set after the library has been
initialised with the __mp_setoption()
function. This allows you to
override the default options or those specified in the MPATROL_OPTIONS
environment variable from within your code. Not all options can be overridden,
however, since they would require a complete reinitialisation of the library —
the __mp_setoption()
function returns a failure indicator in these cases.
You can read the setting of any mpatrol library option with the corresponding
function, __mp_getoption()
.
On systems that support it, global functions (with C linkage) in an executable
file or shared library whose names begin with `__mp_init_' will be noted
when the mpatrol library first starts up and is reading the symbols. Such
functions will then be called as soon as the mpatrol library is initialised,
which can be useful if the initialisation occurs before main()
is called.
These functions must accept no arguments and must return no value. Similar
behaviour exists for global functions whose names begin with `__mp_fini_',
except that such functions will be executed when the mpatrol library terminates.
Note that this feature will have no effect if the symbol table is stripped from
the executable file or shared library before the program is run, and the order
in which such functions will be called if there are more than one is
unspecified. The __mp_atexit()
function can also be used to register
functions that should be called when the mpatrol library terminates.
It is possible to obtain a great deal of information about an existing memory
allocation or free block using the __mp_info()
function. This takes an
address as an argument and fills in any details about its corresponding memory
allocation in a supplied structure. The following example illustrates this (it
can be found in tests/pass/test4.c).
23 /* 24 * Demonstrates and tests the facility for obtaining information 25 * about the allocation a specific address belongs to. 26 */ 29 #include "mpatrol.h" 30 #include <stdio.h> 33 void display(void *p) 34 { 35 __mp_allocstack *s; 36 __mp_allocinfo d; 37 __mp_symbolinfo i; 39 if (!__mp_info(p, &d) || !d.allocated) 40 { 41 fprintf(stderr, "nothing known about address 0x%0*lX\n", 42 sizeof(void *) * 2, p); 43 return; 44 } 45 fprintf(stderr, "block: 0x%0*lX\n", sizeof(void *) * 2, d.block); 46 fprintf(stderr, "size: %lu\n", d.size); 47 fprintf(stderr, "type: %s\n", __mp_function(d.type)); 48 fprintf(stderr, "alloc: %lu\n", d.alloc); 49 fprintf(stderr, "realloc: %lu\n", d.realloc); 50 fprintf(stderr, "thread: %lu\n", d.thread); 51 fprintf(stderr, "event: %lu\n", d.event); 52 fprintf(stderr, "func: %s\n", d.func ? d.func : "<unknown>"); 53 fprintf(stderr, "file: %s\n", d.file ? d.file : "<unknown>"); 54 fprintf(stderr, "line: %lu\n", d.line); 55 for (s = d.stack; s != NULL; s = s->next) 56 { 57 fprintf(stderr, "\t0x%0*lX", sizeof(void *) * 2, s->addr); 58 if (__mp_syminfo(s->addr, &i)) 59 { 60 if (i.name != NULL) 61 fprintf(stderr, " %s", i.name); 62 if ((i.addr != NULL) && (i.addr != s->addr)) 63 fprintf(stderr, "%+ld", 64 (char *) s->addr - (char *) i.addr); 65 if (i.object != NULL) 66 fprintf(stderr, " [%s]", i.object); 67 } 68 else if (s->name != NULL) 69 fprintf(stderr, " %s", s->name); 70 fputc('\n', stderr); 71 } 72 fprintf(stderr, "typestr: %s\n", 73 d.typestr ? d.typestr : "<unknown>"); 74 fprintf(stderr, "typesize: %lu\n", d.typesize); 75 fprintf(stderr, "userdata: 0x%0*lX\n", sizeof(void *) * 2, d.userdata); 76 fputs("flags: ", stderr); 77 if (!d.freed && !d.marked && !d.profiled && !d.traced && !d.internal) 78 fputs(" none\n", stderr); 79 else 80 { 81 if (d.freed) 82 fputs(" freed", stderr); 83 if (d.marked) 84 fputs(" marked", stderr); 85 if (d.profiled) 86 fputs(" profiled", stderr); 87 if (d.traced) 88 fputs(" traced", stderr); 89 if (d.internal) 90 fputs(" internal", stderr); 91 fputc('\n', stderr); 92 } 93 } 96 void func2(void) 97 { 98 void *p; 100 if (p = malloc(16)) 101 { 102 display(p); 103 free(p); 104 } 105 display(p); 106 } 109 void func1(void) 110 { 111 func2(); 112 } 115 int main(void) 116 { 117 func1(); 118 return EXIT_SUCCESS; 119 }
When this is compiled and run, it should give the following output, although the pointers are likely to be different.
block: 0x0806A0E8 size: 16 type: malloc alloc: 52 realloc: 0 thread: 0 event: 97 func: func2 file: test4.c line: 100 0x0804A743 func2+35 [./a.out] 0x0804A790 func1+8 [./a.out] 0x0804A79C main+8 [./a.out] 0x4007C9CB __libc_start_main+255 [/lib/libc.so.6] 0x0804A3E1 _start+33 [./a.out] typestr: <unknown> typesize: 0 userdata: 0x00000000 flags: none nothing known about address 0x0806A0E8
As you can see, anything that the mpatrol library knows about any memory
allocation can be obtained for use in your own code, which can be very useful
if you need to write handlers to keep track of memory allocations, etc. for
debugging purposes. It can also be useful to have this information when running
your program within a debugger, so you can use the __mp_printinfo()
function to display information about a heap address if your debugger supports
calling functions from the command prompt. Note that the textual representation
of the type
field returned by the __mp_info()
function can be
obtained by calling __mp_function()
.
The mpatrol library records the error code from the most recently encountered
warning or error in the __mp_errno
global variable. This variable can be
read and compared with the known error codes listed in mpatrol.h. It can
also be reset to MP_ET_NONE
before calling any mpatrol library function
in order to check to see if a warning or error was encountered during the call.
A string representation of the error message corresponding to any mpatrol error
code can be obtained by calling the __mp_strerror()
function with the
specific code.
The userdata
field shown in the previous example can be set for any
memory allocation with the __mp_setuser()
function. This can have any
value and is not interpreted by the mpatrol library. It was added for user code
to associate its own data with memory allocations.
The marked
field that is also shown in the previous example indicates if
a memory allocation has been marked to indicate that it should never be
freed. This can only be performed from the source code by calling
__mp_setmark()
with the address of the memory allocation. Such a memory
allocation can be reallocated but never freed, and will not contribute to the
list of memory leaks. It will also be profiled and traced as freed by the end
of program execution if memory allocation profiling or tracing is enabled.
You may also have noticed the use of __mp_syminfo()
in the above example.
This function is very similar to the __mp_info()
function except that
instead of looking for the details of a memory allocation at a specific address,
it looks for the details of a function symbol at that address. This provides
user access to the data obtained by the mpatrol symbol handler, including line
number information if the USEDEBUG option is supported and used.
It is also possible for you to be able to intercept calls to allocate, reallocate and deallocate memory for your own purposes. You can install prologue and epilogue functions that the mpatrol library will call before and after every time one of its functions is called. These can be used for additional tracing or simply to add extra checks to your code. The following code is an example of this and can be found in tests/pass/test2.c.
23 /* 24 * Demonstrates and tests the facility for specifying user-defined 25 * prologue and epilogue functions. 26 */ 29 #include "mpatrol.h" 30 #include <stdio.h> 33 __mp_prologuehandler old_prologue; 34 __mp_epiloguehandler old_epilogue; 37 void prologue(MP_CONST void *p, size_t l, size_t m, MP_CONST char *s, 38 MP_CONST char *t, unsigned long u, MP_CONST void *a) 39 { 40 if (old_prologue != NULL) 41 old_prologue(p, l, m, s, t, u, a); 42 if (p == (void *) -1) 43 fprintf(stderr, "allocating %lu bytes\n", l); 44 else if (l == (size_t) -1) 45 fprintf(stderr, "freeing allocation 0x%0*lX\n", sizeof(void *) * 2, p); 46 else if (l == (size_t) -2) 47 fprintf(stderr, "duplicating string `%s'\n", p); 48 else 49 fprintf(stderr, "reallocating allocation 0x%0*lX to %lu bytes\n", 50 sizeof(void *) * 2, p, l); 51 } 54 void epilogue(MP_CONST void *p, MP_CONST char *s, MP_CONST char *t, 55 unsigned long u, MP_CONST void *a) 56 { 57 if (p != (void *) -1) 58 fprintf(stderr, "allocation returns 0x%0*lX\n", sizeof(void *) * 2, p); 59 if (old_epilogue != NULL) 60 old_epilogue(p, s, t, u, a); 61 } 64 int main(void) 65 { 66 void *p, *q; 68 old_prologue = __mp_prologue(prologue); 69 old_epilogue = __mp_epilogue(epilogue); 70 if (p = malloc(16)) 71 if (q = realloc(p, 32)) 72 free(q); 73 else 74 free(p); 75 if (p = (char *) strdup("test")) 76 free(p); 77 __mp_prologue(old_prologue); 78 __mp_epilogue(old_epilogue); 79 return EXIT_SUCCESS; 80 }
Once again, if you compile and run the above code, you should see the following output.
allocating 16 bytes allocation returns 0x0806A0E8 reallocating allocation 0x0806A0E8 to 32 bytes allocation returns 0x0806A0E8 freeing allocation 0x0806A0E8 duplicating string `test' allocation returns 0x0806A0E5 freeing allocation 0x0806A0E5
Note that in the above code, the previous prologue and epilogue functions were
recorded and called. If this is not done then your prologue and epilogue
functions will completely override all others, which is not usually the expected
behaviour. In case you're wondering what the last four arguments of the
prologue and epilogue handlers are, they are the function name, file name,
line number and call address of the function that called malloc()
or a
related function. These can be used in the handlers to see where they were
called from.
Along with being able to install prologue and epilogue functions, you can also
install a low-memory handler with the __mp_nomemory()
function, which
will be called by the mpatrol library if it ever runs out of memory during the
call to a memory allocation function. This gives you the opportunity to use
that handler to either free up any unneeded memory or simply to abort, thus
removing the need to check for failed allocations. Note that the low-memory
handler also accepts the same four common arguments that the prologue and
epilogue handlers do.
It is also possible to iterate over all of the allocated and freed memory
allocations that are currently in the heap at any point in a program. This is
done by invoking the __mp_iterate()
function with a callback function
which is called once per allocation with the start address of the memory block
being passed as the argument to the callback function. Any further information
about the memory allocation can then be obtained via the __mp_info()
function. Note that the __mp_iterateall()
function does the same as the
__mp_iterate()
function except that it also includes all free memory
blocks and memory allocations that are internal to the mpatrol library.
Differences in the heap allocations (their details, not their contents) between
a previous point in a program's execution and the current point of execution can
be determined by calling the __mp_snapshot()
function and then invoking
__mp_iterate()
with that snapshot value as its second argument at a later
point in execution. The callback function passed to __mp_iterate()
will
then only be invoked with the start address of any memory allocation that has
been allocated or reallocated (or freed if the NOFREE option is being
used) since the snapshot point. This makes it possible to detect localised
memory leaks very easily, as the following example (found in
tests/pass/test10.c) shows.
23 /* 24 * Demonstrates and tests the facility for obtaining information on 25 * local memory leaks. Will also edit or list the location of each 26 * leak if the EDIT or LIST option is in effect. 27 */ 30 #include "mpatrol.h" 31 #include <stdio.h> 34 int callback(MP_CONST void *p, void *t) 35 { 36 __mp_allocstack *s; 37 __mp_allocinfo d; 39 if (!__mp_info(p, &d) || !d.allocated) 40 { 41 fprintf(stderr, "nothing known about address 0x%0*lX\n", 42 sizeof(void *) * 2, p); 43 return -1; 44 } 45 if (!d.freed) 46 { 47 fprintf(stderr, "0x%0*lX", sizeof(void *) * 2, d.block); 48 fprintf(stderr, " %s", d.func ? d.func : "<unknown>"); 49 fprintf(stderr, " %s", d.file ? d.file : "<unknown>"); 50 fprintf(stderr, " %lu", d.line); 51 for (s = d.stack; s != NULL; s = s->next) 52 { 53 if (s == d.stack) 54 fputs(" (", stderr); 55 else 56 fputs("->", stderr); 57 if (s->name != NULL) 58 fprintf(stderr, "%s", s->name); 59 else 60 fprintf(stderr, "0x%0*lX", sizeof(void *) * 2, s->addr); 61 if (s->next == NULL) 62 fputc(')', stderr); 63 } 64 fputc('\n', stderr); 65 if ((d.file != NULL) && (d.line != 0)) 66 __mp_view(d.file, d.line); 67 *((unsigned long *) t) = *((unsigned long *) t) + d.size; 68 return 1; 69 } 70 return 0; 71 } 74 void func2(unsigned long n) 75 { 76 void *p; 78 p = malloc((n * 10) + 1); 79 if (n % 13) 80 free(p); 81 } 84 void func1(void) 85 { 86 void *p; 87 size_t i, n; 88 unsigned long s, t; 90 p = malloc(16); 91 s = __mp_snapshot(); 92 for (i = 0; i < 128; i++) 93 func2(i); 94 free(p); 95 t = 0; 96 if (n = __mp_iterate(callback, &t, s)) 97 fprintf(stderr, "Detected %lu memory leaks (%lu bytes)\n", n, t); 98 if ((n != 10) || (t != 5860)) 99 fputs("Expected 10 memory leaks (5860 bytes)\n", stderr); 100 } 103 int main(void) 104 { 105 void *p; 107 p = malloc(16); 108 func1(); 109 free(p); 110 return EXIT_SUCCESS; 111 }
Compiling this example with mpatrol and then running it will produce the
following list of memory leaks that were located between the points of calling
__mp_snapshot()
and __mp_iterate()
.
0x0806A108 func2 test10.c 78 (func2->func1->main->_start) 0x0806A674 func2 test10.c 78 (func2->func1->main->_start) 0x0806A6F8 func2 test10.c 78 (func2->func1->main->_start) 0x0806A800 func2 test10.c 78 (func2->func1->main->_start) 0x0806A988 func2 test10.c 78 (func2->func1->main->_start) 0x0806AB94 func2 test10.c 78 (func2->func1->main->_start) 0x0806AE20 func2 test10.c 78 (func2->func1->main->_start) 0x0806B130 func2 test10.c 78 (func2->func1->main->_start) 0x0806B4C0 func2 test10.c 78 (func2->func1->main->_start) 0x0806B8D4 func2 test10.c 78 (func2->func1->main->_start) Detected 10 memory leaks (5860 bytes)
The tools directory in the mpatrol distribution contains two files called
heapdiff.c and heapdiff.h which demonstrate the use of
__mp_snapshot()
and __mp_iterate()
to find localised memory leaks.
Have a look at these files to see a further example of using these functions,
or perhaps even add these files to your application for debugging purposes.
Note that it is perfectly safe to allocate memory in the callback function used
by __mp_iterate()
, and such allocations can be freed as well. The only
restriction is that the callback function should never free a memory allocation
that it has not allocated itself.
An alternative way to detect differences in the heap between two points in a
program's execution is to make use of the leak table. This is a hash table that
stores the number and size of memory allocations and deallocations referenced
by the source file and line number where they occur1. The leak table can be cleared with a call to
__mp_clearleaktable()
and can be displayed with a call to
__mp_leaktable()
, which will display a sorted summary of the allocated,
freed or unfreed memory entries stored in the leak table. Memory allocation
events can be automatically logged in the leak table by calling
__mp_startleaktable()
but this behaviour can be disabled by calling
__mp_stopleaktable()
. Additional entries can be manually added to the
leak table with __mp_addallocentry()
and __mp_addfreeentry()
.
If you wish to write your own diagnostics to the mpatrol log file from within
your source code then you can do so with the __mp_printf()
and
__mp_vprintf()
functions, which are the functional equivalents of
printf()
and vprintf()
. They prefix every line written to the
log file with `>', partly for making it clear where user diagnostics occur
and partly to avoid problems with external utilities that parse the mpatrol log
file. The __mp_locprintf()
and __mp_vlocprintf()
functions are
equivalent functions that also display the source file and line number from
where they were called along with a stack trace, if available.
It is also possible to write out a memory dump in hexadecimal format, a stack
trace at the current point in execution and details of a memory allocation to
the log file in standard format using the __mp_logmemory()
,
__mp_logstack()
and __mp_logaddr()
functions respectively.
You can also take advantage of the mpedit command from within the
mpatrol library with the __mp_edit()
, __mp_list()
and
__mp_view()
functions. The first invokes a text editor on a specified
file and line number, while the second displays a context listing of a file at a
given line number. The third function performs either or neither depending on
the setting of the EDIT or LIST options.
Finally, there are four functions which affect the mpatrol library globally.
The first, __mp_check()
, allows you to force an internal check of the
mpatrol library's data structures at any point during program execution and also
to free up any out of scope memory allocations made by the alloca()
family of functions. The __mp_memorymap()
function allows you to force
the generation of a memory map at any point in your program, in much the same
way as it would normally be displayed at the end of program execution if the
SHOWMAP option was used. The __mp_summary()
function writes
library statistics to the mpatrol log file, while the __mp_stats()
function fills in a data structure with selected statistics for examination in
user code.
[1] If that information is not available then the function name or return address will be used instead.