Next: , Previous: Testing, Up: Using mpatrol


7.7 Library functions

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.


Footnotes

[1] If that information is not available then the function name or return address will be used instead.